/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2015 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 org.openmicroscopy.shoola.agents.measurement.view; import java.awt.Color; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.Map.Entry; import org.apache.commons.collections.CollectionUtils; import org.jhotdraw.draw.AttributeKey; import org.jhotdraw.draw.Drawing; import org.jhotdraw.draw.DrawingEditor; import org.jhotdraw.draw.Figure; import org.openmicroscopy.shoola.agents.events.SaveData; import org.openmicroscopy.shoola.agents.events.iviewer.SaveRelatedData; import org.openmicroscopy.shoola.agents.measurement.Analyser; import org.openmicroscopy.shoola.agents.measurement.IconManager; import org.openmicroscopy.shoola.agents.measurement.MeasurementAgent; import org.openmicroscopy.shoola.agents.measurement.MeasurementViewerLoader; import org.openmicroscopy.shoola.agents.measurement.ROIAnnotationLoader; import org.openmicroscopy.shoola.agents.measurement.ROIAnnotationSaver; import org.openmicroscopy.shoola.agents.measurement.ROILoader; import org.openmicroscopy.shoola.agents.measurement.ROISaver; import org.openmicroscopy.shoola.agents.measurement.ServerSideROILoader; import org.openmicroscopy.shoola.agents.measurement.TagsLoader; import org.openmicroscopy.shoola.agents.measurement.util.FileMap; import org.openmicroscopy.shoola.agents.util.EditorUtil; import org.openmicroscopy.shoola.agents.util.ViewerSorter; import org.openmicroscopy.shoola.env.data.OmeroImageService; import org.openmicroscopy.shoola.env.data.model.DeletableObject; import org.openmicroscopy.shoola.env.data.model.DeleteActivityParam; import omero.gateway.SecurityContext; import omero.gateway.model.ROIResult; import org.openmicroscopy.shoola.env.event.EventBus; import omero.log.Logger; import org.openmicroscopy.shoola.env.ui.UserNotifier; import org.openmicroscopy.shoola.util.file.IOUtil; import org.openmicroscopy.shoola.util.roi.model.annotation.AnnotationKeys; import org.openmicroscopy.shoola.util.roi.model.annotation.MeasurementAttributes; import org.openmicroscopy.shoola.util.roi.ROIComponent; import org.openmicroscopy.shoola.util.roi.exception.NoSuchROIException; import org.openmicroscopy.shoola.util.roi.exception.ParsingException; import org.openmicroscopy.shoola.util.roi.exception.ROICreationException; import org.openmicroscopy.shoola.util.roi.figures.ROIFigure; import org.openmicroscopy.shoola.util.roi.model.ROI; import org.openmicroscopy.shoola.util.roi.model.ROIShape; import org.openmicroscopy.shoola.util.roi.model.ShapeList; import org.openmicroscopy.shoola.util.roi.model.util.Coord3D; import org.openmicroscopy.shoola.util.roi.model.util.MeasurementUnits; import org.openmicroscopy.shoola.util.ui.drawingtools.DrawingComponent; import org.openmicroscopy.shoola.util.ui.drawingtools.canvas.DrawingCanvasView; import omero.gateway.model.AnnotationData; import omero.gateway.model.ChannelData; import omero.gateway.model.DataObject; import omero.gateway.model.ExperimenterData; import omero.gateway.model.FileAnnotationData; import omero.gateway.model.GroupData; import omero.gateway.model.ImageData; import omero.gateway.model.PixelsData; import omero.gateway.model.ROIData; import ome.model.units.BigResult; import omero.model.Length; import omero.model.LengthI; import omero.model.enums.UnitsLength; /** * The Model component in the <code>MeasurementViewer</code> MVC triad. * This class tracks the <code>MeasurementViewer</code>'s state and knows how to * initiate data retrievals. It also knows how to store and manipulate * the results. This class provides a suitable data loader. * The {@link MeasurementViewerComponent} intercepts the results of data * loadings, feeds them back to this class and fires state transitions as * appropriate. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * @since OME3.0 */ class MeasurementViewerModel { /** The id of the image this {@link MeasurementViewer} is for. */ private long imageID; /** The name of the image this {@link MeasurementViewer} is for. */ private String name; /** The bounds of the component requesting the viewer. */ private Rectangle requesterBounds; /** Holds one of the state flags defined by {@link MeasurementViewer}. */ private int state; /** * The drawing component to create drawing, view and editor and link them. */ private DrawingComponent drawingComponent; /** The component managing the ROI. */ private ROIComponent roiComponent; /** The currently selected plane. */ private Coord3D currentPlane; /** The pixels set. */ private PixelsData pixels; /** The image's magnification factor. */ private double magnification; /** Collection of pairs (channel's index, channel's color). */ private Map activeChannels; /** Collection of pairs (ROIShape, Map of ROIShapeStats). */ private Map analysisResults; /** Metadata for the pixels set. */ private List<ChannelData> metadata; /** * Will either be a data loader or * <code>null</code> depending on the current state. */ private MeasurementViewerLoader currentLoader; /** * The ROISaver. */ private MeasurementViewerLoader currentSaver; /** Reference to the component that embeds this model. */ private MeasurementViewer component; /** * Reference to the event posted to save the data when closing the * viewer. */ private SaveRelatedData event; /** The rendered image either a buffered image or a texture data. */ private Object rndImage; /** The roi file previously saved if any. */ private String fileSaved; /** The measurements associated to the image. */ private List<FileAnnotationData> measurements; /** The collection of ROIs and tables related to the measurements. */ private Collection measurementResults; /** Flag indicating if the tool is for HCS data. */ private boolean HCSData; /** Collection of ROIs to delete. */ private List<ROI> roiToDelete; /** Flag indicating that the current user can deleted the ROI. */ private boolean dataToDelete; /** Flag indicating if it is a big image or not.*/ private boolean bigImage; /** The security context.*/ private SecurityContext ctx; /** The sorter to order shapes.*/ private ViewerSorter sorter; /** Collection of existing tags if any. */ private Collection existingTags; /** * Map figure attributes to ROI and ROIShape annotations where necessary. * @param attribute see above. * @param figure see above. */ private void mapFigureAttributeToROIAnnotation(AttributeKey attribute, ROIFigure figure) { if (MeasurementAttributes.TEXT.getKey().equals(attribute.getKey())) { ROIShape shape = figure.getROIShape(); AnnotationKeys.TEXT.set(shape, MeasurementAttributes.TEXT.get(figure)); } } /** Checks the user currently logged in has ROI to delete. */ private void checkIfHasROIToDelete() { if (dataToDelete) return; Collection<ROI> rois = roiComponent.getROIMap().values(); Iterator<ROI> i = rois.iterator(); List<ROI> ownedRois = new ArrayList<ROI>(); ROI roi; List<ROIFigure> figures = new ArrayList<ROIFigure>(); while (i.hasNext()) { roi = i.next(); //if (roi.getOwnerID() == ownerID || roi.getOwnerID() == -1) { if (roi.canDelete()) { figures.addAll(roi.getAllFigures()); ownedRois.add(roi); } } dataToDelete = ownedRois.size() > 0; } /** * Creates a new instance. * * @param ctx The security context. * @param imageID The image's id. * @param pixels The pixels set the measurement tool is for. * @param name The image's name. * @param bounds The bounds of the component requesting the model. * @param channelsData The channel metadata. */ MeasurementViewerModel(SecurityContext ctx, long imageID, PixelsData pixels, String name, Rectangle bounds, List<ChannelData> channelsData) { metadata = channelsData; this.ctx = ctx; this.imageID = imageID; this.pixels = pixels; this.name = name; requesterBounds = bounds; state = MeasurementViewer.NEW; sorter = new ViewerSorter(); drawingComponent = new DrawingComponent(); roiComponent = new ROIComponent(); fileSaved = null; roiComponent.setPixelSizes(getPixelSizeX(), getPixelSizeY(), getPixelSizeZ()); setPlane(0, 0); } /** * Called by the <code>ROIViewer</code> after creation to allow this * object to store a back reference to the embedding component. * * @param component The embedding component. */ void initialize(MeasurementViewer component) { this.component = component; } /** * Get a link to the ROIComponent. * @return see above. */ ROIComponent getROIComponent() { return roiComponent; } /** * Returns all the figures hosted by the <code>ROIComponent</code>. * * @return See above. */ Collection<ROIFigure> getAllFigures() { TreeMap<Long, ROI> rois = roiComponent.getROIMap(); List<ROIFigure> all = new ArrayList<ROIFigure>(); if (rois == null) return all; Iterator i = rois.entrySet().iterator(); Entry entry; ROI roi; List<ROIFigure> l; while (i.hasNext()) { entry = (Entry) i.next(); roi = (ROI) entry.getValue(); l = roi.getAllFigures(); if (l != null && l.size() > 0) all.addAll(l); } return all; } /** * Returns the name used to log in. * * @return See above. */ String getUserName() { return MeasurementAgent.getRegistry().getAdminService().getLoggingName(); } /** * Returns the user currently logged in. * * @return See above. */ ExperimenterData getCurrentUser() { return MeasurementAgent.getUserDetails(); } /** * Returns the name of the server the user is connected to. * * @return See above. */ String getServerName() { return MeasurementAgent.getRegistry().getAdminService().getServerName(); } /** * Sets the selected z-section and timepoint. * * @param z The selected z-section. * @param t The selected timepoint. */ void setPlane(int z, int t) { if (z != -1 && t !=-1) { currentPlane = new Coord3D(z, t); } else if (z == -1 && t !=-1) { currentPlane = new Coord3D(currentPlane.getZSection(), t); } else if (z != -1 && t ==-1) { currentPlane = new Coord3D(z, currentPlane.getTimePoint()); } } /** * Compares another model to this one to tell if they would result in * having the same display. * * @param other The other model to compare. * @return <code>true</code> if <code>other</code> would lead to a viewer * with the same display as the one in which this model belongs; * <code>false</code> otherwise. */ boolean isSameDisplay(MeasurementViewerModel other) { if (other == null) return false; return ((other.pixels.getId() == getPixelsID()) && (other.imageID == imageID)); } /** * Returns the ID of the image. * * @return See above. */ long getImageID() { return imageID; } /** * Returns the ID of the pixels set this model is for. * * @return See above. */ long getPixelsID() { return pixels.getId(); } /** * Returns the name of the image. * * @return See above. */ String getImageName() { return name; } /** * Returns the name of image and id. * * @return See above. */ String getImageTitle() { return "[ID: "+getImageID()+"] "+ EditorUtil.getPartialName(getImageName()); } /** * Returns the bounds of the component invoking the * {@link MeasurementViewer} or <code>null</code> if not available. * * @return See above. */ Rectangle getRequesterBounds() { return requesterBounds; } /** * Returns the current state. * * @return One of the flags defined by the {@link MeasurementViewer} * interface. */ int getState() { return state; } /** * Returns the drawing editor. * * @return See above. */ DrawingEditor getDrawingEditor() { return drawingComponent.getEditor(); } /** * Returns the drawing. * * @return See above. */ Drawing getDrawing() { return drawingComponent.getDrawing(); } /** * Sets the object in the {@link MeasurementViewer#DISCARDED} state. * Any ongoing data loading will be cancelled. */ void discard() { cancel(); state = MeasurementViewer.DISCARDED; } /** * Sets the object in the {@link MeasurementViewer#READY} state. * Any ongoing data loading will be cancelled. */ void cancel() { if (currentLoader != null) currentLoader.cancel(); state = MeasurementViewer.READY; } /** * Returns the currently selected z-section. * * @return See above. */ int getDefaultZ() { return currentPlane.getZSection(); } /** * Returns the currently selected timepoint. * * @return See above. */ int getDefaultT() { return currentPlane.getTimePoint(); } /** * Returns the image's magnification factor. * * @return See above. */ double getMagnification() { return magnification; } /** * Returns the image's magnification factor. * * @param magnification The value to set. */ void setMagnification(double magnification) { int sizeX = getSizeX(); int sizeY = getSizeY(); this.magnification = magnification; getDrawingView().setScaleFactor(magnification, new Dimension(sizeX, sizeY)); } /** * Sets the attribute of all the ROI in the current plane to the key with * value. * * @param key see above. * @param value see above. */ void setAttributes(AttributeKey key, Object value) { List<Figure> figures = getDrawing().getFigures(); for (Figure f : figures) f.setAttribute(key, value); getDrawingView().repaint(); } /** * Sets the state. * * @param state The value to set. */ void setState(int state) { this.state = state; } /** * Sets the ROI for the pixels set. Returns <code>true</code> * if the ROI are compatible with the image, <code>false</code> otherwise. * * @param input The value to set. * @return See above. * @throws ROICreationException If the ROI cannot be created. * @throws NoSuchROIException If the ROI does not exist. * @throws ParsingException Thrown when an error occurred * while parsing the stream. */ boolean setROI(InputStream input) throws ROICreationException, NoSuchROIException, ParsingException { state = MeasurementViewer.READY; if (input == null) return false; List<ROI> roiList = roiComponent.loadROI(input); if (roiList == null) return false; Iterator<ROI> i = roiList.iterator(); ROI roi; TreeMap<Coord3D, ROIShape> shapeList; Iterator<ROIShape> shapeIterator; ROIShape shape; Coord3D c; int sizeZ = pixels.getSizeZ(); int sizeT = pixels.getSizeT(); boolean b = true; while (i.hasNext()) { roi = i.next(); shapeList = roi.getShapes(); shapeIterator = shapeList.values().iterator(); while (shapeIterator.hasNext()) { shape = shapeIterator.next(); c = shape.getCoord3D(); if (c.getTimePoint() > sizeT) { b = false; break; } if (c.getZSection() > sizeZ) { b = false; break; } } } if (!b) { i = roiList.iterator(); while (i.hasNext()) { roi = i.next(); roiComponent.deleteROI(roi.getID()); } return false; } notifyDataChanged(true); return true; } /** * Returns the file corresponding to the passed id. * * @param fileID The id of the file. * @return See above. */ FileAnnotationData getMeasurement(long fileID) { if (measurements == null) return null; Iterator<FileAnnotationData> i = measurements.iterator(); FileAnnotationData fa; while (i.hasNext()) { fa = i.next(); if (fa.getId() == fileID) return fa; } return null; } /** * Returns the measurements. * * @return See above. */ List<FileAnnotationData> getMeasurements() { return measurements; } /** * Returns the collection of measurements results. * * @return See above. */ Collection getMeasurementResults() { return measurementResults; } /** * Sets the server ROIS. * * @param rois The collection of Rois. * @return See above. * @throws ROICreationException * @throws NoSuchROIException */ List<DataObject> setServerROI(Collection rois) throws ROICreationException, NoSuchROIException { List<DataObject> nodes = new ArrayList<DataObject>(); measurementResults = rois; state = MeasurementViewer.READY; List<ROI> roiList = new ArrayList<ROI>(); Iterator r = rois.iterator(); ROIResult result; long userID = MeasurementAgent.getUserDetails().getId(); while (r.hasNext()) { result = (ROIResult) r.next(); roiList.addAll(roiComponent.loadROI(result.getFileID(), result.getROIs(), userID)); } if (roiList == null) return nodes; Iterator<ROI> i = roiList.iterator(); ROI roi; TreeMap<Coord3D, ROIShape> shapeList; Iterator j; ROIShape shape; Coord3D coord; int sizeZ = pixels.getSizeZ(); int sizeT = pixels.getSizeT(); Entry entry; int c; ROIFigure f; while (i.hasNext()) { roi = i.next(); shapeList = roi.getShapes(); j = shapeList.entrySet().iterator(); while (j.hasNext()) { entry = (Entry) j.next(); shape = (ROIShape) entry.getValue(); coord = shape.getCoord3D(); if (coord.getTimePoint() < sizeT && coord.getZSection() < sizeZ) { c = coord.getChannel(); f = shape.getFigure(); if (shape.getData() != null) { nodes.add(shape.getData()); } if (c >= 0 && f.isVisible()) f.setVisible(isChannelActive(c)); } } } checkIfHasROIToDelete(); return nodes; } /** * Returns the ROI. * * @return See above. */ TreeMap getROI() { return roiComponent.getROIMap(); } /** * Returns the currently selected plane. * * @return See above. */ Coord3D getCurrentView() { return currentPlane; } /** * Returns <code>true</code> if the size in microns can be displayed, this * only if a valid value is stored, <code>false</code> otherwise. * * @return */ boolean sizeInMicrons() { return !getPixelSizeX().getUnit().equals(UnitsLength.PIXEL); } /** * Returns the size of a pixel along the X-axis. * * @return See above. */ Length getPixelSizeX() { Length l = null; try { l = pixels.getPixelSizeX(UnitsLength.MICROMETER); } catch (BigResult e) { MeasurementAgent.getRegistry().getLogger() .warn(this, "Could not get pixel size X in micrometer"); } return l != null ? l : new LengthI(1, UnitsLength.PIXEL); } /** * Returns the size of a pixel along the Y-axis. * * @return See above. */ Length getPixelSizeY() { Length l = null; try { l = pixels.getPixelSizeY(UnitsLength.MICROMETER); } catch (BigResult e) { MeasurementAgent.getRegistry().getLogger() .warn(this, "Could not get pixel size Y in micrometer"); } return l != null ? l : new LengthI(1, UnitsLength.PIXEL); } /** * Returns the size of a pixel along the Z-axis. * * @return See above. */ Length getPixelSizeZ() { try { return pixels.getPixelSizeZ(UnitsLength.MICROMETER); } catch (BigResult e) { MeasurementAgent.getRegistry().getLogger() .warn(this, "Could not get pixel size Z in micrometer"); } return null; } /** * Returns the number of z sections in an image. * * @return See above. */ int getNumZSections() { return pixels.getSizeZ(); } /** * Returns the number of timepoints in an image. * * @return See above. */ int getNumTimePoints() { return pixels.getSizeT(); } /** * Returns the number of pixels along the X-axis. * * @return See above. */ int getSizeX() { return pixels.getSizeX(); } /** * Returns the number of pixels along the Y-axis. * * @return See above. */ int getSizeY() { return pixels.getSizeY(); } /** * Returns the {@link DrawingCanvasView}. * * @return See above. */ DrawingCanvasView getDrawingView() { return drawingComponent.getDrawingView(); } /** * Returns the ROI of the currently selected figure in the drawing view. * * @return see above. */ Collection<ROI> getSelectedROI() { Collection<Figure> selectedFigs = getDrawingView().getSelectedFigures(); List<ROI> roiList = new ArrayList<ROI>(); Iterator<Figure> figIterator = selectedFigs.iterator(); ROIFigure fig; while (figIterator.hasNext()) { fig = (ROIFigure) figIterator.next(); roiList.add(fig.getROI()); } return roiList; } /** * Returns a collection of the currently selected shapes in the drawing view. * * @return see above. */ Collection<ROIShape> getSelectedShapes() { Collection<Figure> selectedFigs = getDrawingView().getSelectedFigures(); List<ROIShape> l = new ArrayList<ROIShape>(); Iterator<Figure> figIterator = selectedFigs.iterator(); ROIFigure fig; while (figIterator.hasNext()) { fig = (ROIFigure) figIterator.next(); l.add(fig.getROIShape()); } return l; } /** * Removes the <code>ROIShape</code> on the current View corresponding * to the passed id. * * @param id The id of the <code>ROI</code>. * @throws NoSuchROIException If the ROI does not exist. */ void removeROIShape(long id) throws NoSuchROIException { ROIShape shape = roiComponent.getShape(id, getCurrentView()); if (shape != null) { if (drawingComponent.contains(shape.getFigure())) drawingComponent.removeFigure(shape.getFigure()); else roiComponent.deleteShape(id, getCurrentView()); } } /** * Removes the <code>ROIShape</code> corresponding to the passed id on * the plane coordinates. * * @param id The id of the <code>ROI</code>. * @param coord the coordinates of the shape to delete. * @throws NoSuchROIException If the ROI does not exist. */ void removeROIShape(long id, Coord3D coord) throws NoSuchROIException { ROIShape shape = roiComponent.getShape(id, coord); if (shape != null) { if (drawingComponent.contains(shape.getFigure())) drawingComponent.removeFigure(shape.getFigure()); else roiComponent.deleteShape(id, coord); } } /** * Removes the <code>ROI</code> corresponding to the passed id. * * @param id The id of the <code>ROI</code>. * @throws NoSuchROIException If the ROI does not exist. */ void removeROI(long id) throws NoSuchROIException { ROIShape shape = roiComponent.getShape(id, getCurrentView()); if (shape != null) { if (drawingComponent.contains(shape.getFigure())) drawingComponent.removeFigure(shape.getFigure()); } if (roiComponent.containsROI(id)) roiComponent.deleteROI(id); } /** * Removes all the <code>ROI</code> in the system. * * @throws NoSuchROIException If the ROI does not exist. */ void removeAllROI() throws NoSuchROIException { state = MeasurementViewer.READY; drawingComponent.removeAllFigures(); int size = roiComponent.getROIMap().values().size(); ROI[] valueList = new ROI[size]; roiComponent.getROIMap().values().toArray(valueList); if (valueList != null) for (ROI roi: valueList) roiComponent.deleteROI(roi.getID()); } /** * Removes all the <code>ROI</code> in the system. * Returns the collection of figures. * * @return See above. * @throws NoSuchROIException If the ROI does not exist. */ List<ROIFigure> removeAllROI(long ownerID, int level) throws NoSuchROIException { Collection<ROI> rois = roiComponent.getROIMap().values(); Iterator<ROI> i = rois.iterator(); List<ROI> ownedRois = new ArrayList<ROI>(); ROI roi; List<ROIFigure> figures = new ArrayList<ROIFigure>(); switch (level) { case MeasurementViewer.ALL: while (i.hasNext()) { roi = i.next(); figures.addAll(roi.getAllFigures()); ownedRois.add(roi); } break; case MeasurementViewer.ME: while (i.hasNext()) { roi = i.next(); if (roi.getOwnerID() == ownerID || roi.getOwnerID() == -1) { figures.addAll(roi.getAllFigures()); ownedRois.add(roi); } } break; case MeasurementViewer.OTHER: while (i.hasNext()) { roi = i.next(); if (roi.getOwnerID() != ownerID) { figures.addAll(roi.getAllFigures()); ownedRois.add(roi); } } } i = ownedRois.iterator(); while (i.hasNext()) { roi = i.next(); roiComponent.deleteROI(roi.getID()); } Iterator<ROIFigure> j = figures.iterator(); while (j.hasNext()) { drawingComponent.removeFigure(j.next()); } event = null; notifyDataChanged(false); dataToDelete = false; return figures; } /** * Returns the <code>ROI</code> corresponding to the passed id. * * @param id The id of the <code>ROI</code>. * @return See above. * @throws NoSuchROIException If the ROI does not exist. */ ROI getROI(long id) throws NoSuchROIException { return roiComponent.getROI(id); } /** * Returns the ROIComponent to create a <code>ROI</code> from * the passed figure. * * @param figure The figure to create the <code>ROI</code> from. * @param addAttribs add attributes to figure * @return Returns the created <code>ROI</code>. * @throws ROICreationException If the ROI cannot be created. * @throws NoSuchROIException If the ROI does not exist. */ ROI createROI(ROIFigure figure, boolean addAttribs) throws ROICreationException, NoSuchROIException { return roiComponent.addROI(figure, getCurrentView(), addAttribs); } /** * Returns the {@link ShapeList} for the current plane. * * @return See above. * @throws NoSuchROIException Thrown if the ROI doesn't exist. */ ShapeList getShapeList() throws NoSuchROIException { return roiComponent.getShapeList(currentPlane); } /** * Figure attribute has changed, need to add any special processing to see * if it should affect ROIShape, ROI or other object. * * @param attribute * @param figure */ void figureAttributeChanged(AttributeKey attribute, ROIFigure figure) { mapFigureAttributeToROIAnnotation(attribute, figure); } /** * Loads the ROI associated to the image. * * @param measurements The measurements if any. */ void fireLoadROIFromServer(List<FileAnnotationData> measurements) { this.measurements = measurements; List<Long> files = null; if (measurements != null) { files = new ArrayList<Long>(); Iterator<FileAnnotationData> i = measurements.iterator(); while (i.hasNext()) files.add(i.next().getId()); } state = MeasurementViewer.LOADING_ROI; ExperimenterData exp = (ExperimenterData) MeasurementAgent.getUserDetails(); currentLoader = new ROILoader(component, getSecurityContext(), getImageID(), files, exp.getId()); currentLoader.load(); } /** * Fires an asynchronous retrieval of the ROI related to the pixels set. * * @param dataChanged Pass <code>true</code> if the ROI has been changed. * <code>false</code> otherwise. */ void fireLoadROIServerOrClient(boolean dataChanged) { state = MeasurementViewer.LOADING_ROI; ExperimenterData exp = (ExperimenterData) MeasurementAgent.getUserDetails(); currentLoader = new ServerSideROILoader(component, getSecurityContext(), getImageID(), exp.getId()); currentLoader.load(); notifyDataChanged(dataChanged); } /** * Fires an asynchronous retrieval of the ROI related to the pixels set. * * @param fileName The name of the file to load. If <code>null</code> * is selected. */ void fireROILoading(String fileName) { InputStream stream = null; state = MeasurementViewer.LOADING_ROI; try { if (fileName == null) fileName = FileMap.getSavedFile(getServerName(), getUserName(), getPixelsID()); fileSaved = fileName; if (fileSaved != null) stream = IOUtil.readFileAsInputStream(fileName); } catch (Exception e) { Logger log = MeasurementAgent.getRegistry().getLogger(); log.warn(this, "Cannot load the ROI "+e.getMessage()); } component.setROI(stream); try { if (stream != null) stream.close(); } catch (Exception e) { Logger log = MeasurementAgent.getRegistry().getLogger(); log.warn(this, "Cannot close the stream "+e.getMessage()); } } /** * Loads the ROI associated to the image. * * @param measurements The measurements if any. */ /* void fireLoadROIFromServer(List<FileAnnotationData> measurements) { this.measurements = measurements; List<Long> files = null; if (measurements != null) { files = new ArrayList<Long>(); Iterator<FileAnnotationData> i = measurements.iterator(); while (i.hasNext()) files.add(i.next().getId()); } state = MeasurementViewer.LOADING_ROI; ExperimenterData exp = (ExperimenterData) MeasurementAgent.getUserDetails(); currentLoader = new ROILoader(component, getImageID(), files, exp.getId()); currentLoader.load(); } */ /** * Returns the path to the file where the ROIs have been saved * or <code>null</code> if not previously saved. * * @return See above. */ String getFileSaved() { return fileSaved; } /** * Saves the current ROISet in the ROI component to file. * * @param fileName name of the file to be saved. * @param post Pass <code>true</code> to post an event, * <code>false</code> otherwise. * @throws ParsingException */ void saveROI(String fileName, boolean post) throws ParsingException { OutputStream stream = null; try { stream = IOUtil.writeFile(fileName); } catch (Exception e) { Logger log = MeasurementAgent.getRegistry().getLogger(); log.warn(this, "Cannot save the ROI "+e.getMessage()); } roiComponent.saveROI(stream); try { if (stream != null) stream.close(); FileMap.setSavedFile(getServerName(), getUserName(), getPixelsID(), fileName); if (!post) event = null; notifyDataChanged(false); } catch (Exception e) { Logger log = MeasurementAgent.getRegistry().getLogger(); log.warn(this, "Cannot close the stream "+e.getMessage()); } } /** * Saves the current ROISet in the ROI component to server. * * @param async Pass <code>true</code> to save the ROI asynchronously, * <code>false</code> otherwise. * @param close Indicates to close the component if <code>true</code>. */ void saveROIToServer(boolean async, boolean close) { try { List<ROIData> roiList = getROIData(); ExperimenterData exp = (ExperimenterData) MeasurementAgent.getUserDetails(); if (roiList.size() == 0) return; roiComponent.reset(); if (async) { currentSaver = new ROISaver(component, getSecurityContext(), getImageID(), exp.getId(), roiList, close); currentSaver.load(); state = MeasurementViewer.SAVING_ROI; notifyDataChanged(false); } else { OmeroImageService svc = MeasurementAgent.getRegistry().getImageService(); svc.saveROI(getSecurityContext(), getImageID(), exp.getId(), roiList); state = MeasurementViewer.READY; event = null; } checkIfHasROIToDelete(); } catch (Exception e) { Logger log = MeasurementAgent.getRegistry().getLogger(); log.warn(this, "Cannot save to server "+e.getMessage()); UserNotifier un = MeasurementAgent.getRegistry().getUserNotifier(); un.notifyInfo("Save ROI", "Unable to save the ROIs"); } } /** * Returns the collection of ROI on the image owned by the user currently * logged in * * @return See above. */ List<ROIData> getROIData() { try { long userID = getCurrentUser().getId(); return roiComponent.saveROI(getImage(), ROIComponent.EDIT, userID); } catch (Exception e) { Logger log = MeasurementAgent.getRegistry().getLogger(); log.warn(this, "Cannot transform the ROI: "+e.getMessage()); } return new ArrayList<ROIData>(); } /** * Returns the collection of ROI on the image owned by the user currently * logged in * * @param level One of the constants defined by the * <code>MeasurementViewer</code>. * @return See above. */ List<ROIData> getROIData(int level) { try { long userID = getCurrentUser().getId(); switch (level) { case MeasurementViewer.ALL: return roiComponent.saveROI(getImage(), ROIComponent.DELETE, userID); case MeasurementViewer.ME: return roiComponent.saveROI(getImage(), ROIComponent.DELETE_MINE, userID); case MeasurementViewer.OTHER: return roiComponent.saveROI(getImage(), ROIComponent.DELETE_OTHERS, userID); } } catch (Exception e) { Logger log = MeasurementAgent.getRegistry().getLogger(); log.warn(this, "Cannot transform the ROI: "+e.getMessage()); } return new ArrayList<ROIData>(); } /** * Returns the image the pixels set is linked to. * * @return See above. */ ImageData getImage() { return pixels.getImage(); } /** * Propagates the selected shape in the roi model. * * @param shape The ROIShape to propagate. * @param timePoint The timepoint to propagate to. * @param zSection The z-section to propagate to. * @return A list of the newly added shapes. * @throws NoSuchROIException Thrown if ROI with id does not exist. * @throws ROICreationException Thrown if the ROI cannot be created. */ List<ROIShape> propagateShape(ROIShape shape, int timePoint, int zSection) throws ROICreationException, NoSuchROIException { notifyDataChanged(true); Coord3D coord = new Coord3D(zSection, timePoint); return roiComponent.propagateShape(shape.getID(), shape.getCoord3D(), shape.getCoord3D(),coord); } /** * Deletes the selected shape from current coord to timepoint and z-section. * * @param shape The ROIShape to propagate. * @param timePoint The timepoint to propagate to. * @param zSection The z-section to propagate to. * @throws NoSuchROIException Thrown if no such ROI exists. */ void deleteShape(ROIShape shape, int timePoint, int zSection) throws NoSuchROIException { if (drawingComponent.contains(shape.getFigure())) drawingComponent.getDrawing().remove(shape.getFigure()); else { notifyDataChanged(true); roiComponent.deleteShape( shape.getID(), shape.getCoord3D(), new Coord3D(zSection, timePoint)); } } /** * Show the measurements in the ROIFigures in microns. * * @param inMicrons show the measurement in microns if true. * */ void showMeasurementsInMicrons(boolean inMicrons) { if (inMicrons) roiComponent.setPixelSizes(getPixelSizeX(), getPixelSizeY(), getPixelSizeZ()); else roiComponent.setPixelSizes(new LengthI(1, UnitsLength.PIXEL), new LengthI(1, UnitsLength.PIXEL), new LengthI(1, UnitsLength.PIXEL)); } /** * Returns the type of units. * * @return See above. */ MeasurementUnits getMeasurementUnits() { return roiComponent.getMeasurementUnits(); } /** * Sets the active channels. * * @param activeChannels The value to set. */ void setActiveChannels(Map activeChannels) { this.activeChannels = activeChannels; } /** * Fires an asynchronous call to analyze the passed shapes. * * @param shapeList The shapelist to analyze. Mustn't be <code>null</code>. */ void fireAnalyzeShape(List<ROIShape> shapeList) { if (CollectionUtils.isEmpty(shapeList)) return; state = MeasurementViewer.ANALYSE_SHAPE; if (currentLoader != null) currentLoader.cancel(); List<ROIShape> l = new ArrayList<ROIShape>(shapeList.size()); Iterator<ROIShape> i = shapeList.iterator(); ROIShape shape; int z, t; while (i.hasNext()) { shape = i.next(); z = shape.getZ(); t = shape.getT(); if (z >= 0 && t >= 0) { l.add(shape); } else if (z == -1 && t >= 0) { l.add(shape.copy(new Coord3D(currentPlane.getZSection(), t))); } else if (z >=0 && t == -1) { l.add(shape.copy(new Coord3D(z, currentPlane.getTimePoint()))); } else if (z == -1 && t == -1) { l.add(shape.copy(new Coord3D(currentPlane.getZSection(), currentPlane.getTimePoint()))); } } currentLoader = new Analyser(component, getSecurityContext(), pixels, activeChannels.keySet(), l, currentPlane); currentLoader.load(); } /** * Returns the channels metadata. * * @return See above */ List<ChannelData> getMetadata() { return metadata; } /** * Returns the metadata corresponding to the specified index or * <code>null</code> if the index is not valid. * * @param index The channel index. * @return See above. */ ChannelData getMetadata(int index) { if (index < 0 || index >= metadata.size()) return null; Iterator<ChannelData> i = metadata.iterator(); ChannelData d; while (i.hasNext()) { d = i.next(); if (d.getIndex() == index) return d; } return null; } /** * Sets the results of an analysis. * * @param analysisResults The value to set. */ void setAnalysisResults(Map analysisResults) { if (this.analysisResults != null) { this.analysisResults.clear(); } else { this.analysisResults = new LinkedHashMap(); } //sort the map. if (analysisResults != null) { List newList = sorter.sort(analysisResults.keySet()); Iterator i = newList.iterator(); ROIShape shape; while (i.hasNext()) { shape = (ROIShape) i.next(); this.analysisResults.put(shape, analysisResults.get(shape)); } analysisResults.clear(); } state = MeasurementViewer.READY; } /** * Returns the collection of stats or <code>null</code> * if no analysis run on the selected ROI shapes. * * @return See above. */ Map getAnalysisResults() { return analysisResults; } /** * Returns the active channels for the data. * * @return See above. */ Map getActiveChannels() { return activeChannels; } /** * Returns the color associated to the specified channel or * <code>null</code> if the channel is not active. * * @param index The index of the channel. * @return See above. */ Color getActiveChannelColor(int index) { return (Color) activeChannels.get(index); } /** * Returns the figures selected in the current view. * * @return See above. */ Collection<Figure> getSelectedFigures() { return getDrawingView().getSelectedFigures(); } /** * Returns <code>true</code> if the channel is active, * <code>false</code> otherwise. * * @param index The index of the channel. * @return See above. */ boolean isChannelActive(int index) { return (activeChannels.get(index) != null); } /** * Notifies listeners that the measurement tool does not have data to save * if <code>false</code>. * * @param toSave Pass <code>true</code> to save the data, <code>false</code> * otherwise. */ void notifyDataChanged(boolean toSave) { if (isHCSData()) return; if (event != null && toSave) return; EventBus bus = MeasurementAgent.getRegistry().getEventBus(); event = new SaveRelatedData(getPixelsID(), new SaveData(getPixelsID(), SaveData.MEASUREMENT_TYPE), "The ROI", toSave); checkIfHasROIToDelete(); bus.post(event); if (!toSave) event = null; } /** * Calculate the stats for the roi in the shapelist. * * @param shapeList see above. */ void calculateStats(List<ROIShape> shapeList) { component.analyseShapeList(shapeList); } /** * Returns the list of ROIs associated to that file. * * @param fileID The id of the file. * @return See above. */ List<ROI> getROIList(long fileID) { return roiComponent.getROIList(fileID); } /** * Clones the specified ROI. * * @param id The id of the ROI to clone. * @return See above. * @throws ROICreationException * @throws NoSuchROIException */ ROI cloneROI(long id) throws ROICreationException, NoSuchROIException { return roiComponent.cloneROI(id); } /** * Deletes the shapes. * * @param id * @param coord * @throws NoSuchROIException */ void deleteShape(long id, Coord3D coord) throws NoSuchROIException { roiComponent.deleteShape(id, coord); } /** * Adds a new shape to the ROI component. * * @param id * @param coord * @param shape * @throws ROICreationException * @throws NoSuchROIException */ void addShape(long id, Coord3D coord, ROIShape shape) throws ROICreationException, NoSuchROIException { roiComponent.addShape(id, coord, shape); } /** * Sets the rendered image. * * @param rndImage The value to set. */ void setRenderedImage(Object rndImage) { this.rndImage = rndImage; } /** * Returns the rendered image. * * @return See above. */ BufferedImage getRenderedImage() { if (rndImage instanceof BufferedImage) return (BufferedImage) rndImage; return null; } /** * Returns the object of reference. * * @return See above. */ Object getRefObject() { return pixels; } /** * Returns <code>true</code> if data to save, <code>false</code> * otherwise. * * @return See above. */ boolean hasROIToSave() { if (isHCSData()) return false; return event != null; } /** * Returns <code>true</code> if data to delete, <code>false</code> * otherwise. * * @return See above. */ boolean hasROIToDelete() { if (hasROIToSave()) return true; return dataToDelete; } /** * Returns <code>true</code> if the tool is for HCS data, <code>false</code> * otherwise. * * @return See above. */ boolean isHCSData() { return HCSData; } /** * Sets the flag indicating if the tool is for HCS data. * * @param value The value to set. */ void setHCSData(boolean value) { HCSData = value; } /** * Adds the passed ROI to the collection of ROIs to delete. * * @param id The id of the shape. * @param roi The ROI to add. * @param force Pass <code>true</code> when the roi is removed via key. */ void markROIForDelete(long id, ROI roi, boolean force) { if (roi == null) return; if (roiToDelete == null) roiToDelete = new ArrayList<ROI>(); if (id < 0) return; if (force) { if (!roi.isClientSide()) roiToDelete.add(roi); } else { if (!roiComponent.containsROI(id) && !roi.isClientSide()) roiToDelete.add(roi); } } /** * Returns the collection to ROI to delete. * * @return See above. */ List<ROI> getROIToDelete() { return roiToDelete; } /** * Invokes when the ROI has been deleted. * * @param imageID The image's identifier. */ void onROIDeleted(long imageID) { if (this.imageID != imageID) return; state = MeasurementViewer.READY; if (roiToDelete != null) roiToDelete.clear(); if (getROIData().size() == 0) notifyDataChanged(false); } /** * Post an event indicating to delete all the rois. * * @param list The list of objects to delete. */ void deleteAllROIs(List<DeletableObject> list) { if (list.size() == 0) return; IconManager icons = IconManager.getInstance(); DeleteActivityParam p = new DeleteActivityParam( icons.getIcon(IconManager.APPLY_22), list); p.setImageID(imageID); p.setFailureIcon(icons.getIcon(IconManager.DELETE_22)); UserNotifier un = MeasurementAgent.getRegistry().getUserNotifier(); un.notifyActivity(getSecurityContext(), p); } /** * Sets the flag indicating if the tool is for big image data. * * @param value The value to set. */ void setBigImage(boolean value) { bigImage = value; } /** * Returns <code>true</code> if big image data, <code>false</code> * otherwise. * * @return See above. */ boolean isBigImage() { return bigImage; } /** * Returns the security context. * * @return See above. */ SecurityContext getSecurityContext() { return ctx; } /** * Returns <code>true</code> if the user is not an owner nor an admin, * <code>false</code> otherwise. * * @return See above. */ boolean isMember() { if (MeasurementAgent.isAdministrator()) return false; Collection groups = MeasurementAgent.getAvailableUserGroups(); Iterator i = groups.iterator(); GroupData g, gRef = null; long gId = getImage().getGroupId(); while (i.hasNext()) { g = (GroupData) i.next(); if (g.getId() == gId) { gRef = g; break; } } if (gRef != null) return EditorUtil.isUserGroupOwner(gRef, MeasurementAgent.getUserDetails().getId()); return false; } /** * Sets the channels that have been modified. * * @param channels The value to set. */ void setChannelData(List<ChannelData> channels) { metadata = channels; } /** * Loads the annotations linked to rois. * * @param shapes The shapes */ void fireLoadROIAnnotations(List<DataObject> shapes) { ROIAnnotationLoader loader = new ROIAnnotationLoader(component, getSecurityContext(), shapes); loader.load(); } /** * Saves the annotations. * * @param toAnnotate The objects to annotate. * @param toAdd The annotation to add. * @param toRemove The annotation to remove. */ void fireAnnotationSaving(Collection<DataObject> toAnnotate, List<AnnotationData> toAdd, List<Object> toRemove) { ROIAnnotationSaver saver = new ROIAnnotationSaver(component, getSecurityContext(), toAnnotate, toAdd, toRemove); saver.load(); } /** * Sets the collection of existing tags. * * @param tags The value to set. */ void setExistingTags(Collection tags) { if (tags != null) existingTags = sorter.sort(tags); } /** * Returns the collection of existing tags. * * @return See above. */ Collection getExistingTags() { return existingTags; } /** Fires an asynchronous retrieval of existing tags. */ void fireExistingTagsLoading() { TagsLoader loader = new TagsLoader(component, getSecurityContext(), canRetrieveAll()); loader.load(); } /** * Indicates to load all annotations available if the user can annotate * and is an administrator/group owner or to only load the user's * annotation. * * @return See above */ private boolean canRetrieveAll() { if (!pixels.canAnnotate()) return false; //check the group level GroupData group = getGroup(pixels.getGroupId()); if (group == null) return false; switch (group.getPermissions().getPermissionsLevel()) { case GroupData.PERMISSIONS_GROUP_READ: if (MeasurementAgent.isAdministrator()) return true; Set leaders = group.getLeaders(); Iterator i = leaders.iterator(); long userID = getCurrentUser().getId(); ExperimenterData exp; while (i.hasNext()) { exp = (ExperimenterData) i.next(); if (exp.getId() == userID) return true; } return false; case GroupData.PERMISSIONS_PRIVATE: return false; } return true; } /** * Returns the group corresponding to the specified id or <code>null</code>. * * @param groupId The identifier of the group. * @return See above. */ private GroupData getGroup(long groupId) { Collection groups = MeasurementAgent.getAvailableUserGroups(); if (groups == null) return null; Iterator i = groups.iterator(); GroupData group; while (i.hasNext()) { group = (GroupData) i.next(); if (group.getId() == groupId) return group; } return null; } }