View Javadoc
1   package com.github.celldynamics.quimp.omero;
2   
3   import java.io.Closeable;
4   import java.io.File;
5   import java.io.FileInputStream;
6   import java.io.FileOutputStream;
7   import java.io.IOException;
8   import java.net.URISyntaxException;
9   import java.nio.ByteBuffer;
10  import java.nio.file.Path;
11  import java.nio.file.Paths;
12  import java.util.ArrayList;
13  import java.util.Arrays;
14  import java.util.Collection;
15  import java.util.Iterator;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.Properties;
19  import java.util.concurrent.ExecutionException;
20  
21  import org.apache.commons.io.FilenameUtils;
22  import org.scijava.Context;
23  import org.scijava.io.IOService;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import com.github.celldynamics.quimp.QuimpException;
28  
29  import Glacier2.CannotCreateSessionException;
30  import Glacier2.PermissionDeniedException;
31  import edu.emory.mathcs.backport.java.util.Collections;
32  import loci.formats.in.DefaultMetadataOptions;
33  import loci.formats.in.MetadataLevel;
34  import net.imagej.Dataset;
35  import net.imagej.omero.DefaultOMEROSession;
36  import net.imagej.omero.OMEROLocation;
37  import net.imagej.omero.OMEROService;
38  import ome.formats.OMEROMetadataStoreClient;
39  import ome.formats.importer.ImportCandidates;
40  import ome.formats.importer.ImportConfig;
41  import ome.formats.importer.ImportLibrary;
42  import ome.formats.importer.OMEROWrapper;
43  import ome.formats.importer.cli.ErrorHandler;
44  import ome.formats.importer.cli.LoggingImportMonitor;
45  import omero.ServerError;
46  import omero.client;
47  import omero.api.IMetadataPrx;
48  import omero.api.RawFileStorePrx;
49  import omero.gateway.Gateway;
50  import omero.gateway.SecurityContext;
51  import omero.gateway.exception.DSAccessException;
52  import omero.gateway.exception.DSOutOfServiceException;
53  import omero.gateway.facility.BrowseFacility;
54  import omero.gateway.facility.DataManagerFacility;
55  import omero.gateway.model.DatasetData;
56  import omero.gateway.model.FileAnnotationData;
57  import omero.gateway.model.ImageData;
58  import omero.gateway.model.ProjectData;
59  import omero.model.Annotation;
60  import omero.model.ChecksumAlgorithm;
61  import omero.model.ChecksumAlgorithmI;
62  import omero.model.FileAnnotation;
63  import omero.model.FileAnnotationI;
64  import omero.model.Image;
65  import omero.model.ImageAnnotationLink;
66  import omero.model.ImageAnnotationLinkI;
67  import omero.model.OriginalFile;
68  import omero.model.OriginalFileI;
69  import omero.model.enums.ChecksumAlgorithmSHA1160;
70  import omero.sys.ParametersI;
71  
72  /**
73   * Provide access to Omero.
74   * 
75   * @author p.baniukiewicz
76   *
77   */
78  public class OmeroBrowser implements Closeable {
79    private static final String NAMESPACE = "QCONF";
80    static final Logger LOGGER = LoggerFactory.getLogger(OmeroBrowser.class.getName());
81    static final int DEF_PORT = 4064;
82  
83    private Gateway gateway = null;
84    private SecurityContext ctx;
85    private String user;
86    private String host;
87    private String pass;
88    private client client;
89    private DefaultOMEROSession ds;
90    OMEROService os;
91    Context context;
92    private int port;
93  
94    /**
95     * Initialise browser.
96     * 
97     * <p>Do not open connection. Use {@link #connect()} instead.
98     * 
99     * @param user omero user name
100    * @param pass omero password
101    * @param host host
102    * @param port port, see {@link #DEF_PORT}
103    */
104   public OmeroBrowser(String user, String pass, String host, int port) {
105     this.port = port;
106     this.host = host;
107     this.user = user;
108     this.pass = pass;
109   }
110 
111   /**
112    * Connect to Omero.
113    * 
114    * <p>All fields provided to {@link #OmeroBrowser(String, String, String, int)} must contain
115    * nonempty strings. Throw exception if connection unsuccessful.
116    * 
117    * @throws DSOutOfServiceException connection error
118    * @throws QuimpException if any of fields: user/pass/host is empty or null
119    * @throws URISyntaxException on Omero error
120    * @throws CannotCreateSessionException on Omero error
121    * @throws PermissionDeniedException on Omero error
122    * @throws ServerError on Omero error
123    */
124   public void connect() throws DSOutOfServiceException, QuimpException, URISyntaxException,
125           ServerError, PermissionDeniedException, CannotCreateSessionException {
126     silentClose(); // close old connection
127     LOGGER.debug("Opening connection:" + user + ", " + pass + ", " + host);
128     if (user == null || user.isEmpty() || host == null || host.isEmpty() || pass == null
129             || pass.isEmpty()) {
130       throw new QuimpException("One of required fields is empty");
131     }
132     OMEROLocation credentials = new OMEROLocation(host, port, user, pass);
133     context = new Context();
134     os = context.getService(OMEROService.class);
135     ds = new DefaultOMEROSession(credentials, os);
136     client = ds.getClient();
137     gateway = ds.getGateway();
138     ctx = ds.getSecurityContext();
139   }
140 
141   /*
142    * (non-Javadoc)
143    * 
144    * @see java.io.Closeable#close()
145    */
146   @Override
147   public void close() throws IOException {
148     silentClose();
149   }
150 
151   /**
152    * Close Omero without exception.
153    */
154   public void silentClose() {
155     if (gateway != null) {
156       ds.close();
157       client.closeSession();
158       gateway.disconnect();
159       os.dispose();
160       context.dispose();
161       LOGGER.debug("Omero disconnected");
162     }
163   }
164 
165   /**
166    * List projects.
167    * 
168    * @return list of projects for user.
169    * @throws DSOutOfServiceException on Omero error
170    * @throws DSAccessException on Omero error
171    * @throws ExecutionException on Omero error
172    */
173   public List<ProjectData> listProjects()
174           throws DSOutOfServiceException, DSAccessException, ExecutionException {
175     BrowseFacility browse = gateway.getFacility(BrowseFacility.class);
176     return new ArrayList<ProjectData>(browse.getProjects(ctx));
177   }
178 
179   /**
180    * List datasets.
181    * 
182    * @return List of datasets for user.
183    * @throws DSOutOfServiceException on Omero error
184    * @throws DSAccessException on Omero error
185    * @throws ExecutionException on Omero error
186    */
187   public List<DatasetData> listDatasets()
188           throws DSOutOfServiceException, DSAccessException, ExecutionException {
189     BrowseFacility browse = gateway.getFacility(BrowseFacility.class);
190     return new ArrayList<DatasetData>(browse.getDatasets(ctx));
191   }
192 
193   /**
194    * Open dataset.
195    * 
196    * @param name dataset name
197    * @return images from dataset
198    * @throws ExecutionException on Omero error
199    * @throws DSOutOfServiceException on Omero error
200    * @throws DSAccessException on Omero error
201    */
202   public Collection<ImageData> openDataset(DatasetData name)
203           throws ExecutionException, DSOutOfServiceException, DSAccessException {
204     BrowseFacility browse = gateway.getFacility(BrowseFacility.class);
205     List<Long> ida = new ArrayList<>();
206     ida.add(name.getId());
207     return browse.getImagesForDatasets(ctx, ida);
208 
209   }
210 
211   /**
212    * Find dataset of specified name.
213    * 
214    * @param name name of dataset
215    * @return dataset object
216    * @throws DSOutOfServiceException on Omero error
217    * @throws DSAccessException on Omero error
218    * @throws ExecutionException on Omero error
219    */
220   public DatasetData findDataset(String name)
221           throws DSOutOfServiceException, DSAccessException, ExecutionException {
222     for (DatasetData ds : listDatasets()) {
223       if (ds.getName().equals(name)) {
224         LOGGER.debug("Found dataset of name " + name + " (" + ds.toString() + ")");
225         return ds;
226       }
227     }
228     return null;
229   }
230 
231   /**
232    * Find dataset of specified id.
233    * 
234    * @param id id of dataset
235    * @return dataset object
236    * @throws DSOutOfServiceException on Omero error
237    * @throws DSAccessException on Omero error
238    * @throws ExecutionException on Omero error
239    */
240   public DatasetData findDataset(Long id)
241           throws DSOutOfServiceException, DSAccessException, ExecutionException {
242     for (DatasetData ds : listDatasets()) {
243       if (ds.getId() == id) {
244         LOGGER.debug("Found dataset of id " + id + " (" + ds.toString() + ")");
245         return ds;
246       }
247     }
248     return null;
249   }
250 
251   /**
252    * Find image of specified name in the dataset.
253    * 
254    * <p>If there is more than 1 image with specified name, then depending on
255    * <tt>allowDuplicates</tt> the newest will be returned (true) or exception thrown (false).
256    * 
257    * @param imageName image name to look for
258    * @param name dataset name
259    * @param allowDuplicates if true newest image is returned if there is more than 1 image with
260    *        imageName in the dataset, otherwise exception is thrown
261    * @return found image object
262    * 
263    * @throws ExecutionException omero error
264    * @throws DSOutOfServiceException omero error
265    * @throws DSAccessException omero error
266    * @throws QuimpException if there are multiple images with the same name and
267    *         allowDuplicates==false
268    */
269   public ImageData findImage(String imageName, DatasetData name, boolean allowDuplicates)
270           throws ExecutionException, DSOutOfServiceException, DSAccessException, QuimpException {
271     ArrayList<ImageData> imgs = new ArrayList<>();
272     for (ImageData im : openDataset(name)) {
273       if (im.getName().equals(imageName)) {
274         LOGGER.debug("Found image of name " + name + " (" + im.toString() + "), inserted: "
275                 + im.getInserted());
276         imgs.add(im);
277       }
278     }
279     if (imgs.size() > 1 && allowDuplicates == false) {
280       throw new QuimpException(
281               "There are at least two images with name " + imageName + " in dataset");
282     }
283     if (imgs.size() == 0) {
284       return null;
285     }
286     if (imgs.size() == 1) {
287       return imgs.get(0);
288     } else { // return newest
289       Long now = System.currentTimeMillis(); // current time
290       ArrayList<Long> deltas = new ArrayList<>(); // time from now for each file (with same name)
291       for (ImageData im : imgs) { // check each file
292         Long timeFile = im.getInserted().getTime(); // get upload time
293         deltas.add(now - timeFile); // store difference from now
294       }
295       int closestInd = deltas.indexOf(Collections.min(deltas)); // index of smallest diff
296       return imgs.get(closestInd); // file under this index
297     }
298 
299   }
300 
301   /**
302    * Find image of specified id in dataset.
303    * 
304    * @param imageId id to find
305    * @param name dataset name
306    * @return image object
307    * @throws ExecutionException on Omero error
308    * @throws DSOutOfServiceException on Omero error
309    * @throws DSAccessException on Omero error
310    */
311   public ImageData findImage(Long imageId, DatasetData name)
312           throws ExecutionException, DSOutOfServiceException, DSAccessException {
313     for (ImageData im : openDataset(name)) {
314       if (im.getId() == imageId) {
315         LOGGER.debug("Found image of id " + imageId + " (" + imageId.toString() + ")");
316         return im;
317       }
318     }
319     return null;
320 
321   }
322 
323   /**
324    * Upload specified image to dataset.
325    * 
326    * @param pathsToImages list paths of images
327    * @param name dataset name
328    * @throws Exception on error
329    */
330   public void upload(String[] pathsToImages, DatasetData name) throws Exception {
331     LOGGER.info("Trying to upload: " + Arrays.toString(pathsToImages));
332     ImportConfig config = new ome.formats.importer.ImportConfig();
333     config.email.set("");
334     config.sendFiles.set(true);
335     config.sendReport.set(false);
336     config.contOnError.set(false);
337     config.debug.set(true);
338 
339     config.hostname.set(host);
340     config.port.set(port);
341     config.username.set(user);
342     config.password.set(pass);
343     config.target.set("omero.model.Dataset:" + name.getId());
344 
345     OMEROMetadataStoreClient store;
346     store = config.createStore();
347     store.logVersionInfo(config.getIniVersionNumber());
348     OMEROWrapper reader = new OMEROWrapper(config);
349     ImportLibrary library = new ImportLibrary(store, reader);
350 
351     ErrorHandler handler = new ErrorHandler(config);
352     library.addObserver(new LoggingImportMonitor());
353 
354     ImportCandidates candidates = new ImportCandidates(reader, pathsToImages, handler);
355     reader.setMetadataOptions(new DefaultMetadataOptions(MetadataLevel.ALL));
356     library.importCandidates(config, candidates);
357 
358     store.logout();
359     LOGGER.info("Image uploaded");
360 
361   }
362 
363   /**
364    * Upload attachment and attach it to image from dataset.
365    * 
366    * <p>Image is already in dataset.
367    * 
368    * @param imageName image name in dataset
369    * @param pathToAttach path to attachment
370    * @param name dataset name
371    * @throws IOException on file error
372    * @throws DSOutOfServiceException on Omero error
373    * @throws DSAccessException on Omero error
374    * @throws ServerError on Omero error
375    * @throws ExecutionException on Omero error
376    * @throws QuimpException on Omero error - no image in dataset
377    */
378   public void upload(String imageName, String pathToAttach, DatasetData name)
379           throws IOException, DSOutOfServiceException, DSAccessException, ServerError,
380           ExecutionException, QuimpException {
381     LOGGER.info("Trying to upload attachement: " + pathToAttach + " to " + imageName);
382 
383     File file = new File(pathToAttach);
384     String name1 = Paths.get(pathToAttach).getFileName().toString();
385     String absolutePath = file.getAbsolutePath();
386     String path = absolutePath.substring(0, absolutePath.length() - name1.length());
387 
388     // create the original file object.
389     OriginalFile originalFile = new OriginalFileI();
390     originalFile.setName(omero.rtypes.rstring(name1));
391     originalFile.setPath(omero.rtypes.rstring(path));
392     originalFile.setSize(omero.rtypes.rlong(file.length()));
393     final ChecksumAlgorithm checksumAlgorithm = new ChecksumAlgorithmI();
394     checksumAlgorithm.setValue(omero.rtypes.rstring(ChecksumAlgorithmSHA1160.value));
395     originalFile.setHasher(checksumAlgorithm);
396     originalFile.setMimetype(omero.rtypes.rstring("application/octet-stream"));
397     // Now we save the originalFile object
398     DataManagerFacility dm = gateway.getFacility(DataManagerFacility.class);
399     originalFile = (OriginalFile) dm.saveAndReturnObject(ctx, originalFile);
400 
401     // Initialize the service to load the raw data
402     RawFileStorePrx rawFileStore = gateway.getRawFileService(ctx);
403 
404     long inc = file.length();
405     long pos = 0;
406     int rlen;
407     byte[] buf = new byte[(int) inc];
408     ByteBuffer bbuf;
409     // Open file and read stream
410     try (FileInputStream stream = new FileInputStream(file)) {
411       rawFileStore.setFileId(originalFile.getId().getValue());
412       while ((rlen = stream.read(buf)) > 0) {
413         rawFileStore.write(buf, pos, rlen);
414         pos += rlen;
415         bbuf = ByteBuffer.wrap(buf);
416         bbuf.limit(rlen);
417       }
418       originalFile = rawFileStore.save();
419     } finally {
420       rawFileStore.close();
421     }
422     // now we have an original File in DB and raw data uploaded.
423     // We now need to link the Original file to the image using
424     // the File annotation object. That's the way to do it.
425     FileAnnotation fa = new FileAnnotationI();
426     fa.setFile(originalFile);
427     // The description set above e.g. PointsModel
428     fa.setDescription(omero.rtypes.rstring("PointsModel"));
429     // The name space you have set to identify the file annotation.
430     fa.setNs(omero.rtypes.rstring(NAMESPACE));
431 
432     // save the file annotation.
433     fa = (FileAnnotation) dm.saveAndReturnObject(ctx, fa);
434 
435     // now link the image and the annotation
436     ImageAnnotationLink link = new ImageAnnotationLinkI();
437     link.setChild(fa);
438     ImageData imageData = findImage(imageName, name, true);
439     link.setParent(imageData.asImage());
440     // save the link back to the server.
441     link = (ImageAnnotationLink) dm.saveAndReturnObject(ctx, link);
442     // o attach to a Dataset use DatasetAnnotationLink;
443     LOGGER.info("Attachement uploaded");
444 
445   }
446 
447   /**
448    * Download image and attachment.
449    * 
450    * @param image image with attachment (if no attachment only image is downloaded)
451    * @param path path to file (should be not file name)
452    * @throws DSOutOfServiceException on Omero error
453    * @throws ServerError on Omero error
454    * @throws DSAccessException on Omero error
455    * @throws ExecutionException on Omero error
456    * @throws IOException on file error
457    * @throws URISyntaxException on Omero error
458    * @throws PermissionDeniedException on Omero error
459    * @throws CannotCreateSessionException on Omero error
460    */
461   public void download(ImageData image, Path path)
462           throws DSOutOfServiceException, ServerError, DSAccessException, ExecutionException,
463           IOException, URISyntaxException, PermissionDeniedException, CannotCreateSessionException {
464 
465     if (path.toFile().isFile()) {
466       throw new IllegalArgumentException("Path must point to folder");
467     }
468 
469     Dataset img = os.downloadImage(client, image.getId());
470     IOService io = context.getService(IOService.class);
471     Path imageFile = path.resolve(image.getName());
472     io.save(img, imageFile.toString());
473     long[] dim = new long[img.numDimensions()];
474     img.dimensions(dim);
475     LOGGER.info("Saved: " + imageFile.toString() + " : " + img.toString() + " dim: "
476             + Arrays.toString(dim));
477 
478     long userId = gateway.getLoggedInUser().getId();
479     List<String> nsToInclude = new ArrayList<String>();
480     nsToInclude.add(NAMESPACE);
481     List<String> nsToExclude = new ArrayList<String>();
482     ParametersI param = new ParametersI();
483     param.exp(omero.rtypes.rlong(userId)); // load the annotation for a given user.
484     IMetadataPrx proxy = gateway.getMetadataService(ctx);
485     List<Long> ids = new ArrayList<>();
486     ids.add(image.getId());
487     Map<Long, List<Annotation>> annotations =
488             proxy.loadSpecifiedAnnotationsLinkedTo(FileAnnotation.class.getName(), nsToInclude,
489                     nsToExclude, Image.class.getName(), ids, param);
490     LOGGER.debug("Length: " + annotations.size());
491     // for (Annotation a : annotations.get(image.getId())) {
492     // if (a instanceof FileAnnotation) {
493     // FileAnnotationData fa = new FileAnnotationData((FileAnnotation) a);
494     // LOGGER.debug("id " + fa.getFileID());
495     // }
496     // }
497 
498     if (annotations.get(image.getId()) != null) {
499       Iterator<Annotation> j = annotations.get(image.getId()).iterator();
500       Annotation annotation;
501       FileAnnotationData fa;
502       RawFileStorePrx store = gateway.getRawFileService(ctx);
503       File qconfFile =
504               path.resolve(FilenameUtils.removeExtension(image.getName()) + ".QCONF").toFile();
505       int index = 0;
506 
507       int inc;
508       try (FileOutputStream stream = new FileOutputStream(qconfFile)) {
509         while (j.hasNext()) {
510           annotation = j.next();
511           if (annotation instanceof FileAnnotation && index == 0) {
512             fa = new FileAnnotationData((FileAnnotation) annotation);
513             // // The id of the original file
514             // create the original file object.
515             OriginalFile originalFile = new OriginalFileI();
516             originalFile.setName(omero.rtypes.rstring(qconfFile.getName()));
517             originalFile.setPath(omero.rtypes.rstring(qconfFile.getParent()));
518             originalFile.setSize(omero.rtypes.rlong(fa.getFileSize()));
519             final ChecksumAlgorithm checksumAlgorithm = new ChecksumAlgorithmI();
520             checksumAlgorithm.setValue(omero.rtypes.rstring(ChecksumAlgorithmSHA1160.value));
521             originalFile.setHasher(checksumAlgorithm);
522             originalFile.setMimetype(omero.rtypes.rstring("application/octet-stream"));
523             // Now we save the originalFile object
524             DataManagerFacility dm = gateway.getFacility(DataManagerFacility.class);
525             originalFile = (OriginalFile) dm.saveAndReturnObject(ctx, originalFile);
526 
527             store.setFileId(fa.getFileID());
528             int offset = 0;
529             inc = (int) fa.getFileSize();
530             long size = originalFile.getSize().getValue();
531             try {
532               for (offset = 0; (offset + inc) < size;) {
533                 stream.write(store.read(offset, inc));
534                 offset += inc;
535               }
536             } finally {
537               stream.write(store.read(offset, (int) (size - offset)));
538               LOGGER.info("Saved: " + qconfFile.toString() + " from image " + image.getId());
539             }
540             index++;
541           }
542         }
543       } finally {
544         store.close();
545       }
546     } else {
547       LOGGER.warn("There is no QCONF file attached to image [ " + image.getId() + "]");
548     }
549   }
550 
551   /**
552    * Dummy tests.
553    * 
554    * @param args args
555    */
556   public static void main(String[] args) {
557     String user;
558     String pass;
559     String host;
560     Properties prop = new Properties();
561     try (FileInputStream input = new FileInputStream(
562             Paths.get(System.getProperty("user.home"), "omero.properties").toFile())) {
563       prop.load(input);
564       user = prop.getProperty("user");
565       pass = prop.getProperty("pass");
566       host = prop.getProperty("host");
567     } catch (IOException ex) {
568       ex.printStackTrace();
569       return;
570     }
571 
572     try (OmeroBrowsermero/OmeroBrowser.html#OmeroBrowser">OmeroBrowser client = new OmeroBrowser(user, pass, host, DEF_PORT)) {
573       client.connect();
574       LOGGER.debug("Projects:");
575       client.listDatasets().stream().forEach(x -> LOGGER.debug("dataset " + x.getName()));
576       // pick dataset
577       DatasetData ds = client.findDataset("Q");
578       // list images
579       client.openDataset(ds).stream()
580               .forEach(x -> LOGGER.debug("image: " + x.getName() + ": " + x.getId()));
581 
582       // client.upload(new String[] {
583       // "src/test/Resources-static/ProtAnalysisTest/fluoreszenz-test.tif kept stack.tif" }, ds);
584       // client.upload("fluoreszenz-test.tif kept stack.tif",
585       // "src/test/Resources-static/ProtAnalysisTest/fluoreszenz-test.QCONF", ds);
586       client.download(client.findImage(194021L, ds), Paths.get("./").toAbsolutePath());
587 
588       // client.downloadIJ_TEST(); // this or these above
589 
590     } catch (Exception e) {
591       // TODO Auto-generated catch block
592       e.printStackTrace();
593     }
594 
595     // https://github.com/ome/minimal-omero-client/blob/master/src/main/java/com/example/SimpleConnection.java
596     // https://docs.openmicroscopy.org/omero/5.4.10/developers/Java.html
597 
598   }
599 
600 }