/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2014 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.util.roi.io;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.geom.BezierPath;
import org.openmicroscopy.shoola.util.roi.ROIComponent;
import org.openmicroscopy.shoola.util.roi.exception.ParsingException;
import org.openmicroscopy.shoola.util.roi.figures.MeasureBezierFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureEllipseFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasurePointFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureRectangleFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureLineFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureMaskFigure;
import org.openmicroscopy.shoola.util.roi.figures.MeasureTextFigure;
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.annotation.AnnotationKeys;
import org.openmicroscopy.shoola.util.roi.model.annotation.MeasurementAttributes;
import org.openmicroscopy.shoola.util.roi.model.util.Coord3D;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import omero.gateway.model.EllipseData;
import omero.gateway.model.ImageData;
import omero.gateway.model.LineData;
import omero.gateway.model.MaskData;
import omero.gateway.model.PointData;
import omero.gateway.model.PolygonData;
import omero.gateway.model.PolylineData;
import omero.gateway.model.ROIData;
import omero.gateway.model.ShapeData;
import omero.gateway.model.ShapeSettingsData;
import omero.gateway.model.TextData;
import omero.gateway.model.RectangleData;
import omero.model.LengthI;
import omero.model.enums.UnitsLength;
/**
* Handles ROI from server.
*
* @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 3.0-Beta4
*/
class OutputServerStrategy
{
/** The ROIComponent to serialize. */
private ROIComponent component;
/** The list of ROI to be supplied to the server. */
private List<ROIData> ROIList;
/**
* Parses the ROI in the ROIComponent to create the appropriate ROIData
* object to supply to the server.
*
* @param image The image the ROI is on.
* @param index One of the constants defined by {@link ROIComponent} class.
* @param userID The id of the user currently logged in.
* @throws Exception
*/
private void parseROI(ImageData image, int index, long userID)
throws Exception
{
TreeMap<Long, ROI> map = component.getROIMap();
Iterator<ROI> i = map.values().iterator();
ROI roi;
switch (index) {
case ROIComponent.ANNOTATE:
while (i.hasNext())
{
roi = i.next();
if (roi.canAnnotate())
ROIList.add(createServerROI(roi, image));
}
break;
case ROIComponent.EDIT:
while (i.hasNext())
{
roi = i.next();
if (roi.canEdit())
ROIList.add(createServerROI(roi, image));
}
break;
case ROIComponent.DELETE:
while (i.hasNext())
{
roi = i.next();
if (roi.canDelete())
ROIList.add(createServerROI(roi, image));
}
break;
case ROIComponent.DELETE_MINE:
while (i.hasNext())
{
roi = i.next();
if (roi.canDelete()) {
if (roi.getOwnerID() < 0 || roi.getOwnerID() == userID)
ROIList.add(createServerROI(roi, image));
}
}
break;
case ROIComponent.DELETE_OTHERS:
while (i.hasNext())
{
roi = i.next();
if (roi.canDelete() && roi.getOwnerID() >= 0 &&
roi.getOwnerID() != userID)
ROIList.add(createServerROI(roi, image));
}
break;
case ROIComponent.ALL:
while (i.hasNext())
ROIList.add(createServerROI(i.next(), image));
}
}
/**
* Creates the Shape object for the ROIShape figure object.
* @param clientShape See above.
* @return See above.
* @throws Exception If an error occurred while creating the shape.
*/
private ShapeData createShapeData(ROIShape clientShape)
throws Exception
{
ROIFigure fig = clientShape.getFigure();
ShapeData shape = null;
if (fig instanceof MeasureBezierFigure)
shape = createBezierFigure(clientShape);
else if (fig instanceof MeasureEllipseFigure)
shape = createEllipseFigure(clientShape);
else if (fig instanceof MeasureLineFigure)
shape = createLineFigure(clientShape);
else if (fig instanceof MeasureMaskFigure)
shape = createMaskFigure(clientShape);
else if (fig instanceof MeasurePointFigure)
shape = createPointFigure(clientShape);
else if (fig instanceof MeasureRectangleFigure)
shape = createRectangleFigure(clientShape);
else if (fig instanceof MeasureTextFigure)
shape = createTextFigure(clientShape);
if (shape == null)
throw new Exception("ROIShape not supported : " +
clientShape.getClass().toString());
if (clientShape.getT() >= 0)
shape.setT(clientShape.getT());
if (clientShape.getZ() >= 0)
shape.setZ(clientShape.getZ());
shape.setDirty(fig.isDirty());
if (!fig.isClientObject())
shape.setId(clientShape.getROIShapeID());
return shape;
}
/**
* Creates an ROIData object from an ROI.
*
* @param roi The ROI to handle.
* @param image The image the ROI is on.
* @return See above.
* @throws Exception If an error occurred while parsing the ROI.
*/
private ROIData createServerROI(ROI roi, ImageData image)
throws Exception
{
ROIData roiData = new ROIData();
roiData.setClientSide(roi.isClientSide());
if (!roi.isClientSide())
roiData.setId(roi.getID());
roiData.setImage(image.asImage());
TreeMap<Coord3D, ROIShape> shapes = roi.getShapes();
Iterator<ROIShape> shapeIterator = shapes.values().iterator();
ROIShape roiShape;
ShapeData shape;
while (shapeIterator.hasNext())
{
roiShape = shapeIterator.next();
shape = createShapeData(roiShape);
addShapeAttributes(roiShape.getFigure(), shape);
roiData.addShapeData(shape);
}
return roiData;
}
/**
* Creates a Bezier figure server side object from a MeasureBezierFigure
* client side object.
*
* @param shape See above.
* @return See above.
* @throws ParsingException If an error occurred while parsing.
*/
private ShapeData createBezierFigure(ROIShape shape)
throws ParsingException
{
MeasureBezierFigure fig = (MeasureBezierFigure) shape.getFigure();
if (fig.isClosed()) return createPolygonFigure(shape);
return createPolylineFigure(shape);
}
/**
* Creates an ellipse figure server side object from a MeasureEllipseFigure
* client side object.
*
* @param shape See above.
* @return See above.
* @throws ParsingException If an error occurred while parsing.
*/
private EllipseData createEllipseFigure(ROIShape shape)
throws ParsingException
{
MeasureEllipseFigure fig = (MeasureEllipseFigure) shape.getFigure();
double rx = fig.getEllipse().getWidth()/2d;
double ry = fig.getEllipse().getHeight()/2d;
double cx = fig.getEllipse().getCenterX();
double cy = fig.getEllipse().getCenterY();
EllipseData ellipse = new EllipseData(cx, cy, rx, ry);
ellipse.setVisible(fig.isVisible());
String text = fig.getText();
if (text != null && text.trim().length() > 0 &&
!text.equals(ROIFigure.DEFAULT_TEXT))
ellipse.setText(text);
AffineTransform t = AttributeKeys.TRANSFORM.get(fig);
if (t != null)
ellipse.setTransform(toTransform(t));
return ellipse;
}
/**
* Creates a mask figure server side object from a MeasureMaskFigure
* client side object.
*
* @param shape See above.
* @return See above.
*/
private MaskData createMaskFigure(ROIShape shape)
{
return null;
}
/**
* Creates a Bezier figure server side object from a MeasurePointFigure
* client side object.
*
* @param shape See above.
* @return See above.
* @throws ParsingException If an error occurred while parsing.
*/
private PointData createPointFigure(ROIShape shape)
throws ParsingException
{
MeasurePointFigure fig = (MeasurePointFigure)shape.getFigure();
double cx = fig.getCentre().getX();
double cy = fig.getCentre().getY();
PointData point = new PointData(cx, cy);
point.setVisible(fig.isVisible());
String text = fig.getText();
if (text != null && text.trim().length() > 0 &&
!text.equals(ROIFigure.DEFAULT_TEXT))
point.setText(fig.getText());
AffineTransform t = AttributeKeys.TRANSFORM.get(fig);
if (t != null)
point.setTransform(toTransform(t));
return point;
}
/**
* Creates a text figure server side object from a MeasureTextFigure
* client side object.
*
* @param shape See above.
* @return See above.
* @throws ParsingException If an error occurred while parsing.
*/
private TextData createTextFigure(ROIShape shape)
throws ParsingException
{
MeasureTextFigure fig = (MeasureTextFigure)shape.getFigure();
double x = fig.getBounds().getX();
double y = fig.getBounds().getY();
String text = fig.getText();
if (text != null && text.trim().length() > 0 &&
text.equals(ROIFigure.DEFAULT_TEXT))
text = "";
TextData data = new TextData(text, x, y);
data.setDirty(fig.isDirty());
if (shape.getT() >=0) data.setT(shape.getT());
if (shape.getZ() >=0) data.setZ(shape.getZ());
AffineTransform t = AttributeKeys.TRANSFORM.get(fig);
if (t != null)
data.setTransform(toTransform(t));
if (!fig.isClientObject())
data.setId(shape.getROIShapeID());
return data;
}
/**
* Creates a rectangle figure server side object from a
* MeasureRectangleFigure client side object.
*
* @param shape See above.
* @return See above.
* @throws ParsingException If an error occurred while parsing.
*/
private RectangleData createRectangleFigure(ROIShape shape)
throws ParsingException
{
MeasureRectangleFigure fig = (MeasureRectangleFigure) shape.getFigure();
double x = fig.getX();
double y = fig.getY();
double width = fig.getWidth();
double height = fig.getHeight();
RectangleData rectangle = new RectangleData(x, y, width, height);
String text = fig.getText();
if (text != null && text.trim().length() > 0 &&
!text.equals(ROIFigure.DEFAULT_TEXT))
rectangle.setText(fig.getText());
rectangle.setVisible(fig.isVisible());
AffineTransform t = AttributeKeys.TRANSFORM.get(fig);
if (t != null)
rectangle.setTransform(toTransform(t));
return rectangle;
}
/**
* Creates a polygon figure server side object from a MeasureBezierFigure
* client side object.
*
* @param shape See above.
* @return See above.
* @throws ParsingException If an error occurred while parsing.
*/
private PolygonData createPolygonFigure(ROIShape shape)
throws ParsingException
{
MeasureBezierFigure fig = (MeasureBezierFigure) shape.getFigure();
AffineTransform t = AttributeKeys.TRANSFORM.get(fig);
List<Point2D.Double> points = new LinkedList<Point2D.Double>();
List<Point2D.Double> points1 = new LinkedList<Point2D.Double>();
List<Point2D.Double> points2 = new LinkedList<Point2D.Double>();
List<Integer> maskList = new LinkedList<Integer>();
BezierPath bezier = fig.getBezierPath();
for (BezierPath.Node node : bezier)
{
points.add(new Point2D.Double(node.x[0], node.y[0]));
points1.add(new Point2D.Double(node.x[1], node.y[1]));
points2.add(new Point2D.Double(node.x[2], node.y[2]));
maskList.add(Integer.valueOf(node.getMask()));
}
PolygonData poly = new PolygonData();
poly.setVisible(fig.isVisible());
poly.setPoints(points, points1, points2, maskList);
if (t != null)
poly.setTransform(toTransform(t));
String text = fig.getText();
if (text != null && text.trim().length() > 0 &&
!text.equals(ROIFigure.DEFAULT_TEXT))
poly.setText(fig.getText());
return poly;
}
/**
* Creates a line figure server side object from a MeasureLineFigure
* client side object.
*
* @param shape See above.
* @return See above.
* @throws ParsingException If an error occurred while parsing.
*/
private ShapeData createLineFigure(ROIShape shape)
throws ParsingException
{
MeasureLineFigure fig = (MeasureLineFigure) shape.getFigure();
BezierPath bezier = fig.getBezierPath();
AffineTransform t = AttributeKeys.TRANSFORM.get(fig);
int n = bezier.size();
if (n == 2) { //it is a line.
BezierPath.Node start = bezier.get(0);
BezierPath.Node end = bezier.get(1);
LineData line = new LineData(start.x[0], start.y[0],
end.x[0], end.y[0]);
line.setVisible(fig.isVisible());
if (t != null) line.setTransform(toTransform(t));
String text = fig.getText();
if (text != null && text.trim().length() > 0 &&
!text.equals(ROIFigure.DEFAULT_TEXT))
line.setText(fig.getText());
return line;
}
List<Point2D.Double> points = new LinkedList<Point2D.Double>();
List<Point2D.Double> points1 = new LinkedList<Point2D.Double>();
List<Point2D.Double> points2 = new LinkedList<Point2D.Double>();
List<Integer> maskList = new LinkedList<Integer>();
for (BezierPath.Node node : bezier)
{
points.add(new Point2D.Double(node.x[0], node.y[0]));
points1.add(new Point2D.Double(node.x[1], node.y[1]));
points2.add(new Point2D.Double(node.x[2], node.y[2]));
maskList.add(Integer.valueOf(node.getMask()));
}
PolylineData line = new PolylineData();
line.setVisible(fig.isVisible());
line.setPoints(points, points1, points2, maskList);
if (t != null)
line.setTransform(toTransform(t));
String text = fig.getText();
if (text != null && text.trim().length() > 0 &&
!text.equals(ROIFigure.DEFAULT_TEXT))
line.setText(fig.getText());
return line;
}
/**
* Creates a PolyLine figure server side object from a MeasureBezierFigure
* client side object.
*
* @param shape See above.
* @return See above.
* @throws ParsingException If an error occurred while parsing.
*/
private PolylineData createPolylineFigure(ROIShape shape)
throws ParsingException
{
MeasureBezierFigure fig = (MeasureBezierFigure)shape.getFigure();
AffineTransform t = AttributeKeys.TRANSFORM.get(fig);
List<Point2D.Double> points = new LinkedList<Point2D.Double>();
List<Point2D.Double> points1 = new LinkedList<Point2D.Double>();
List<Point2D.Double> points2 = new LinkedList<Point2D.Double>();
List<Integer> maskList=new LinkedList<Integer>();
BezierPath bezier = fig.getBezierPath();
for (BezierPath.Node node : bezier)
{
points.add(new Point2D.Double(node.x[0], node.y[0]));
points1.add(new Point2D.Double(node.x[1], node.y[1]));
points2.add(new Point2D.Double(node.x[2], node.y[2]));
maskList.add(Integer.valueOf(node.getMask()));
}
PolylineData poly = new PolylineData();
poly.setVisible(fig.isVisible());
poly.setPoints(points, points1, points2, maskList);
if (t != null)
poly.setTransform(toTransform(t));
String text = fig.getText();
if (text != null && text.trim().length() > 0 &&
!text.equals(ROIFigure.DEFAULT_TEXT))
poly.setText(fig.getText());
return poly;
}
/**
* Adds the ShapeSettings attributes to the shape.
*
* @param fig The figure in the measurement tool.
* @param shape The shape to add setting to.
*/
private void addShapeAttributes(ROIFigure fig, ShapeData shape)
{
ShapeSettingsData settings = shape.getShapeSettings();
Boolean bold;
Boolean italic;
Coord3D coord = fig.getROIShape().getCoord3D();
int channel = coord.getChannel();
if (channel >= 0) shape.setC(channel);
if (AttributeKeys.FILL_COLOR.get(fig) != null)
{
Color c = AttributeKeys.FILL_COLOR.get(fig);
settings.setFill(c);
}
if (MeasurementAttributes.STROKE_COLOR.get(fig) != null)
settings.setStroke(
MeasurementAttributes.STROKE_COLOR.get(fig));
if (MeasurementAttributes.STROKE_WIDTH.get(fig) != null)
settings.setStrokeWidth(
new LengthI(MeasurementAttributes.STROKE_WIDTH.get(fig), UnitsLength.PIXEL));
if (MeasurementAttributes.FONT_FACE.get(fig) != null) {
settings.setFontFamily(UIUtilities.convertFont(
MeasurementAttributes.FONT_FACE.get(fig).getName()));
} else
settings.setFontFamily(ShapeSettingsData.DEFAULT_FONT_FAMILY);
if (MeasurementAttributes.FONT_SIZE.get(fig) != null)
settings.setFontSize(
new LengthI(MeasurementAttributes.FONT_SIZE.get(fig), UnitsLength.POINT));
else
settings.setFontSize(new LengthI(ShapeSettingsData.DEFAULT_FONT_SIZE, UnitsLength.POINT));
bold = MeasurementAttributes.FONT_BOLD.get(fig);
italic = MeasurementAttributes.FONT_ITALIC.get(fig);
if (bold != null) {
if (bold.booleanValue()) {
if (italic != null && italic.booleanValue()) {
settings.setFontStyle(ShapeSettingsData.FONT_BOLD_ITALIC);
} else settings.setFontStyle(ShapeSettingsData.FONT_BOLD);
} else {
if (italic != null && italic.booleanValue()) {
settings.setFontStyle(ShapeSettingsData.FONT_ITALIC);
} else settings.setFontStyle(ShapeSettingsData.FONT_REGULAR);
}
} else if (italic != null) {
if (italic.booleanValue()) {
if (bold != null && bold.booleanValue()) {
settings.setFontStyle(ShapeSettingsData.FONT_BOLD_ITALIC);
} else settings.setFontStyle(ShapeSettingsData.FONT_ITALIC);
} else {
if (bold != null && bold.booleanValue()) {
settings.setFontStyle(ShapeSettingsData.FONT_BOLD);
} else settings.setFontStyle(ShapeSettingsData.FONT_REGULAR);
}
} else settings.setFontStyle(ShapeSettingsData.FONT_REGULAR);
}
/**
* Converts an AffineTransform into an SVG transform attribute value as
* specified in
* http://www.w3.org/TR/SVGMobile12/coords.html#TransformAttribute
*/
private static String toTransform(AffineTransform t)
throws ParsingException
{
StringBuilder buf=new StringBuilder();
switch (t.getType())
{
case AffineTransform.TYPE_IDENTITY:
buf.append("none");
break;
case AffineTransform.TYPE_TRANSLATION:
// translate(<tx> [<ty>]), specifies a translation by tx and ty.
// If <ty> is not provided, it is assumed to be zero.
buf.append("translate(");
buf.append(toNumber(t.getTranslateX()));
if (t.getTranslateY()!=0d)
{
buf.append(' ');
buf.append(toNumber(t.getTranslateY()));
}
buf.append(')');
break;
/*
* case AffineTransform.TYPE_GENERAL_ROTATION : case
* AffineTransform.TYPE_QUADRANT_ROTATION : case
* AffineTransform.TYPE_MASK_ROTATION : // rotate(<rotate-angle> [<cx>
* <cy>]), specifies a rotation by // <rotate-angle> degrees about a
* given point. // If optional parameters <cx> and <cy> are not
* supplied, the // rotate is about the origin of the current user
* coordinate // system. The operation corresponds to the matrix //
* [cos(a) sin(a) -sin(a) cos(a) 0 0]. // If optional parameters
* <cx> and <cy> are supplied, the rotate // is about the point (<cx>,
* <cy>). The operation represents the // equivalent of the
* following specification: // translate(<cx>, <cy>) rotate(<rotate-angle>) //
* translate(-<cx>, -<cy>). buf.append("rotate(");
* buf.append(toNumber(t.getScaleX())); buf.append(')'); break;
*/
case AffineTransform.TYPE_UNIFORM_SCALE:
// scale(<sx> [<sy>]), specifies a scale operation by sx
// and sy. If <sy> is not provided, it is assumed to be equal
// to <sx>.
buf.append("scale(");
buf.append(toNumber(t.getScaleX()));
buf.append(')');
break;
case AffineTransform.TYPE_GENERAL_SCALE:
case AffineTransform.TYPE_MASK_SCALE:
// scale(<sx> [<sy>]), specifies a scale operation by sx
// and sy. If <sy> is not provided, it is assumed to be equal
// to <sx>.
buf.append("scale(");
buf.append(toNumber(t.getScaleX()));
buf.append(' ');
buf.append(toNumber(t.getScaleY()));
buf.append(')');
break;
default:
// matrix(<a> <b> <c> <d> <e> <f>), specifies a transformation
// in the form of a transformation matrix of six values.
// matrix(a,b,c,d,e,f) is equivalent to applying the
// transformation matrix [a b c d e f].
buf.append("matrix(");
double[] matrix = new double[6];
t.getMatrix(matrix);
for (int i = 0; i < matrix.length; i++)
{
if (i != 0)
{
buf.append(' ');
}
buf.append(toNumber(matrix[i]));
}
buf.append(')');
break;
}
return buf.toString();
}
/**
* Returns a double array as a number attribute value.
*
* @param number the number to convert.
* @return See above.
*/
private static String toNumber(double number)
{
String str = Double.toString(number);
if (str.endsWith(".0"))
str = str.substring(0, str.length()-2);
return str;
}
/** Creates a new instance. */
OutputServerStrategy() {}
/**
* Writes the ROI from the ROI component to the server.
*
* @param component See above.
* @param image The image the ROI is on.
* @param index One of the constants defined by {@link ROIComponent} class.
* @param userID The id of the user currently logged in.
* @throws Exception If an error occurred while parsing the ROI.
*/
List<ROIData> writeROI(ROIComponent component, ImageData image, int index,
long userID)
throws Exception
{
this.component = component;
ROIList = new ArrayList<ROIData>();
parseROI(image, index, userID);
return ROIList;
}
}