/* *------------------------------------------------------------------------------ * Copyright (C) 2015-2016 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package omero.gateway.facility; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutionException; import org.apache.commons.collections.CollectionUtils; import omero.RLong; import omero.ServerError; import omero.api.IQueryPrx; import omero.api.IRoiPrx; import omero.api.IUpdatePrx; import omero.api.RoiOptions; import omero.api.RoiResult; import omero.gateway.Gateway; import omero.gateway.SecurityContext; import omero.gateway.exception.DSAccessException; import omero.gateway.exception.DSOutOfServiceException; import omero.gateway.model.ROIResult; import omero.gateway.util.ModelMapper; import omero.gateway.util.PyTablesUtils; import omero.model.IObject; import omero.model.Image; import omero.model.ImageI; import omero.model.Line; import omero.model.Polyline; import omero.model.Roi; import omero.model.Shape; import omero.sys.ParametersI; import omero.gateway.model.ROICoordinate; import omero.gateway.model.ROIData; import omero.gateway.model.ShapeData; import omero.gateway.util.PojoMapper; /** * A {@link Facility} for ROI. * * @author Dominik Lindner      <a * href="mailto:d.lindner@dundee.ac.uk">d.lindner@dundee.ac.uk</a> * @since 5.1 */ public class ROIFacility extends Facility { private DataManagerFacility dm; /** * Creates a new instance * @param gateway Reference to the {@link Gateway} * @throws ExecutionException */ ROIFacility(Gateway gateway) throws ExecutionException { super(gateway); this.dm = gateway.getFacility(DataManagerFacility.class); } /** * Get the number of ROIs for an image (<code>-1</code> * in case of error) * * @param ctx * The {@link SecurityContext} * @param imageId * The image Id * @return See above * @throws DSOutOfServiceException * @throws DSAccessException */ public int getROICount(SecurityContext ctx, long imageId) throws DSOutOfServiceException, DSAccessException { try { ParametersI p = new ParametersI(); p.addId(imageId); String query = "select count(*) from Roi roi " + "where roi.image.id = :id"; IQueryPrx service = gateway.getQueryService(ctx); List<List<omero.RType>> tmp1 = service.projection(query, p); if (CollectionUtils.isEmpty(tmp1)) { throw new Exception("Unexpected HQL result"); } List<omero.RType> tmp2 = tmp1.iterator().next(); if (CollectionUtils.isEmpty(tmp2)) { throw new Exception("Unexpected HQL result"); } omero.RType result = tmp2.iterator().next(); if (!(result instanceof RLong)) { throw new Exception("Unexpected HQL result"); } return (int) (((RLong) result).getValue()); } catch (Exception e) { handleException(this, e, "Can't load ROI count for image " + imageId); } return -1; } /** * Loads the ROI related to the specified image. * * @param ctx * The security context. * @param roiId * The ROI's id. * @return See above. * @throws DSOutOfServiceException * If the connection is broken, or not logged in * @throws DSAccessException * If an error occurred while trying to retrieve data from OMERO * service. */ public ROIResult loadROI(SecurityContext ctx, long roiId) throws DSOutOfServiceException, DSAccessException { try { IRoiPrx svc = gateway.getROIService(ctx); RoiOptions options = new RoiOptions(); RoiResult rr = svc.findByRoi(roiId, options); ROIResult result = new ROIResult( PojoMapper.<ROIData> asCastedDataObjects(rr.rois)); return result; } catch (ServerError e) { handleException(this, e, "Couldn't get ROIs by plane."); } return null; } /** * Loads the ROI related to the specified image. * * @param ctx * The security context. * @param imageID * The image's ID. * @param z * The selection z-section. * @param t * The selection timepoint. * @return See above. * @throws DSOutOfServiceException * If the connection is broken, or not logged in * @throws DSAccessException * If an error occurred while trying to retrieve data from OMERO * service. */ public List<ROIResult> loadROIsByPlane(SecurityContext ctx, long imageID, int z, int t) throws DSOutOfServiceException, DSAccessException { List<ROIResult> results = new ArrayList<ROIResult>(); try { IRoiPrx svc = gateway.getROIService(ctx); RoiOptions options = new RoiOptions(); RoiResult r = svc.findByPlane(imageID, z, t, options); ROIResult result = new ROIResult(PojoMapper.<ROIData>asCastedDataObjects(r.rois)); results.add(result); } catch (ServerError e) { handleException(this, e, "Couldn't get ROIs by plane."); } return results; } /** * Loads the ROI related to the specified image. * * @param ctx * The security context. * @param imageID * The image's ID. * @return See above. * @throws DSOutOfServiceException * If the connection is broken, or not logged in * @throws DSAccessException * If an error occurred while trying to retrieve data from OMERO * service. */ public List<ROIResult> loadROIs(SecurityContext ctx, long imageID) throws DSOutOfServiceException, DSAccessException { return loadROIs(ctx, imageID, null, gateway.getLoggedInUser().getId()); } /** * Loads the ROI related to the specified image. * * @param ctx * The security context. * @param imageID * The image's ID. * @param measurements The measurements IDs linked to the image if any. * @return See above. * @throws DSOutOfServiceException * If the connection is broken, or logged in. * @throws DSAccessException * If an error occurred while trying to retrieve data from OMEDS * service. */ public List<ROIResult> loadROIs(SecurityContext ctx, long imageID, List<Long> measurements) throws DSOutOfServiceException, DSAccessException { return loadROIs(ctx, imageID, measurements, -1); } /** * Loads the ROI related to the specified image. * * @param ctx * The security context. * @param imageID * The image's ID. * @param measurements The measurements IDs linked to the image if any. * @param userID * The user's ID. * @return See above. * @throws DSOutOfServiceException * If the connection is broken, or not logged in * @throws DSAccessException * If an error occurred while trying to retrieve data from OMERO * service. */ public List<ROIResult> loadROIs(SecurityContext ctx, long imageID, List<Long> measurements, long userID) throws DSOutOfServiceException, DSAccessException { List<ROIResult> results = new ArrayList<ROIResult>(); try { IRoiPrx svc = gateway.getROIService(ctx); RoiOptions options = new RoiOptions(); if (userID >= 0) options.userId = omero.rtypes.rlong(userID); RoiResult r; ROIResult result; if (CollectionUtils.isEmpty(measurements)) { options = new RoiOptions(); r = svc.findByImage(imageID, options); if (r == null) return results; results.add(new ROIResult(PojoMapper.<ROIData>asCastedDataObjects(r.rois))); } else { // measurements Map<Long, RoiResult> map = svc.getMeasuredRoisMap(imageID, measurements, options); if (map == null) return results; Iterator i = map.entrySet().iterator(); Long id; Entry entry; while (i.hasNext()) { entry = (Entry) i.next(); id = (Long) entry.getKey(); r = (RoiResult) entry.getValue(); // get the table result = new ROIResult(PojoMapper.<ROIData>asCastedDataObjects(r.rois), id); result.setResult(PyTablesUtils.createTableResult( svc.getTable(id), "Image", imageID)); results.add(result); } } } catch (Exception e) { handleException(this, e, "Cannot load the ROI for image: " + imageID); } return results; } /** * Save the ROI for the image to the server. * * @param ctx * The security context. * @param imageID * The image's ID. * @param roiList * The list of ROI to save. * @return updated list of ROIData objects. * @throws DSOutOfServiceException * If the connection is broken, or not logged in * @throws DSAccessException * If an error occurred while trying to retrieve data from OMERO * service. */ public Collection<ROIData> saveROIs(SecurityContext ctx, long imageID, Collection<ROIData> roiList) throws DSOutOfServiceException, DSAccessException { return saveROIs(ctx, imageID, -1, roiList); } /** * Save the ROI for the image to the server. * * @param ctx * The security context. * @param imageID * The image's ID. * @param userID * The user's ID. * @param roiList * The list of ROI to save. * @return updated list of ROIData objects. * @throws DSOutOfServiceException * If the connection is broken, or not logged in * @throws DSAccessException * If an error occurred while trying to retrieve data from OMERO * service. */ public Collection<ROIData> saveROIs(SecurityContext ctx, long imageID, long userID, Collection<ROIData> roiList) throws DSOutOfServiceException, DSAccessException { try { IUpdatePrx updateService = gateway.getUpdateService(ctx); IRoiPrx svc = gateway.getROIService(ctx); RoiOptions options = new RoiOptions(); if (userID >= 0) options.userId = omero.rtypes.rlong(userID); RoiResult serverReturn; serverReturn = svc.findByImage(imageID, new RoiOptions()); Map<Long, Roi> roiMap = new HashMap<Long, Roi>(); List<Roi> serverRoiList = serverReturn.rois; /* Create a map of all the client roi with id as key */ Map<Long, ROIData> clientROIMap = new HashMap<Long, ROIData>(); for (ROIData roi : roiList) { if (roi != null) clientROIMap.put(roi.getId(), roi); } /* Create a map of the <id, serverROI>, but remove any roi from * the server that should be deleted, before creating map. * To delete an roi we first must delete all the roiShapes in * the roi. */ for (Roi r : serverRoiList) { if (r != null) { //rois are now deleted using the roi service. if (clientROIMap.containsKey(r.getId().getValue())) roiMap.put(r.getId().getValue(), r); } } /* For each roi in the client, see what should be done: * 1. Create a new roi if it does not exist. * 2. build a map of the roiShapes in the clientROI with * ROICoordinate as a key. * 3. as above but for server roiShapes. * 4. iterate through the maps to see if the shapes have been * deleted in the roi on the client, if so then delete the shape on * the server. * 5. Somehow the server roi becomes stale on the client so we have * to retrieve the roi again from the server before updating it. * 6. Check to see if the roi in the cleint has been updated */ List<ShapeData> shapeList; ShapeData shape; Map<ROICoordinate, ShapeData> clientCoordMap; Roi serverRoi; Iterator<List<ShapeData>> shapeIterator; Map<ROICoordinate, Shape>serverCoordMap; Shape s; ROICoordinate coord; int shapeIndex; Collection<ROIData> updated = new ArrayList<ROIData>(); List<Long> deleted = new ArrayList<Long>(); Image unloaded = new ImageI(imageID, false); Roi rr; int z, t; for (ROIData roi : roiList) { /* * Step 1. Add new ROI to the server. */ if (!roiMap.containsKey(roi.getId())) { rr = (Roi) roi.asIObject(); rr.setImage(unloaded); rr = (Roi) updateService.saveAndReturnObject(rr); updated.add(new ROIData(rr)); continue; } /* * Step 2. create the client roiShape map. */ serverRoi = roiMap.get(roi.getId()); shapeIterator = roi.getIterator(); clientCoordMap = new HashMap<ROICoordinate, ShapeData>(); while (shapeIterator.hasNext()) { shapeList = shapeIterator.next(); shape = shapeList.get(0); if (shape != null) clientCoordMap.put(shape.getROICoordinate(), shape); } /* * Step 3. create the server roiShape map. */ serverCoordMap = new HashMap<ROICoordinate, Shape>(); if (serverRoi != null) { for (int i = 0 ; i < serverRoi.sizeOfShapes(); i++) { s = serverRoi.getShape(i); if (s != null) { z = 0; t = 0; if (s.getTheZ() != null) z = s.getTheZ().getValue(); if (s.getTheT() != null) t = s.getTheT().getValue(); serverCoordMap.put(new ROICoordinate(z, t), s); } } } /* * Step 4. delete any shapes in the server that have been deleted * in the client. */ Iterator si = serverCoordMap.entrySet().iterator(); Entry entry; List<ROICoordinate> removed = new ArrayList<ROICoordinate>(); while (si.hasNext()) { entry = (Entry) si.next(); coord = (ROICoordinate) entry.getKey(); if (!clientCoordMap.containsKey(coord)) { s = (Shape) entry.getValue(); if (s != null) { serverRoi.removeShape(s); serverRoi = (Roi) updateService.saveAndReturnObject(serverRoi); updated.add(new ROIData(serverRoi)); } } else { s = (Shape) entry.getValue(); if (s instanceof Line || s instanceof Polyline) { shape = clientCoordMap.get(coord); if ((s instanceof Line && shape.asIObject() instanceof Polyline) || (s instanceof Polyline && shape.asIObject() instanceof Line)) { removed.add(coord); serverRoi.removeShape(s); serverRoi = (Roi) updateService.saveAndReturnObject(serverRoi); updated.add(new ROIData(serverRoi)); deleted.add(s.getId().getValue()); } } } } /* * Step 6. Check to see if the roi in the client has been updated * if so replace the server roiShape with the client one. */ si = clientCoordMap.entrySet().iterator(); Shape serverShape; long sid; Shape sh; while (si.hasNext()) { entry = (Entry) si.next(); coord = (ROICoordinate) entry.getKey(); shape = (ShapeData) entry.getValue(); sh = (Shape) shape.asIObject(); ModelMapper.unloadCollections(sh); if (shape != null) { if (!serverCoordMap.containsKey(coord)) serverRoi.addShape(sh); else if (shape.isDirty()) { shapeIndex = -1; if (deleted.contains(shape.getId())) { serverRoi.addShape(sh); break; } for (int j = 0 ; j < serverRoi.sizeOfShapes() ; j++) { if (serverRoi != null) { serverShape = serverRoi.getShape(j); if (serverShape != null && serverShape.getId() != null) { sid = serverShape.getId().getValue(); if (sid == shape.getId()) { shapeIndex = j; break; } } } } if (shapeIndex == -1) { serverShape = null; shapeIndex = -1; for (int j = 0 ; j < serverRoi.sizeOfShapes() ; j++) { if (serverRoi != null) { z = 0; t = 0; serverShape = serverRoi.getShape(j); if (serverShape != null) { if (serverShape.getTheT() != null) t = serverShape.getTheT().getValue(); if (serverShape.getTheZ() != null) z = serverShape.getTheZ().getValue(); if (t == shape.getT() && z == shape.getZ()) { shapeIndex = j; break; } } } } if (shapeIndex !=-1) { if (!removed.contains(coord)) dm.deleteObject(ctx, serverShape); serverRoi.addShape(sh); } else { throw new Exception("serverRoi.shapeList " + "is corrupted"); } } else { serverRoi.setShape(shapeIndex, sh); } } } } /* * Step 7. update properties of ROI, if they are changed. * */ if (serverRoi != null) { Roi ri = (Roi) roi.asIObject(); serverRoi.setDescription(ri.getDescription()); serverRoi.setNamespaces(ri.getNamespaces()); serverRoi.setKeywords(ri.getKeywords()); serverRoi.setImage(unloaded); ri = (Roi) updateService.saveAndReturnObject(serverRoi); updated.add(new ROIData(ri)); } } return updated; } catch (Exception e) { handleException(this, e, "Cannot Save the ROI for image: " + imageID); } return new ArrayList<ROIData>(); } }