/*
*------------------------------------------------------------------------------
* 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;
}
}