/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy 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 3 of the License, or * (at your option) any later version. * * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.roi; import icy.image.IcyBufferedImage; import icy.image.IntensityInfo; import icy.math.DataIteratorMath; import icy.math.MathUtil; import icy.plugin.interface_.PluginROIDescriptor; import icy.sequence.Sequence; import icy.sequence.SequenceDataIterator; import icy.type.DataIteratorUtil; import icy.type.DataType; import icy.type.collection.CollectionUtil; import icy.type.dimension.Dimension5D; import icy.type.geom.Line2DUtil; import icy.type.geom.Polygon2D; import icy.type.point.Point3D; import icy.type.point.Point4D; import icy.type.point.Point5D; import icy.type.rectangle.Rectangle2DUtil; import icy.type.rectangle.Rectangle3D; import icy.type.rectangle.Rectangle4D; import icy.type.rectangle.Rectangle5D; import icy.util.ShapeUtil.BooleanOperator; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import plugins.kernel.roi.descriptor.intensity.ROIIntensityDescriptorsPlugin; import plugins.kernel.roi.descriptor.intensity.ROIMaxIntensityDescriptor; import plugins.kernel.roi.descriptor.intensity.ROIMeanIntensityDescriptor; import plugins.kernel.roi.descriptor.intensity.ROIMinIntensityDescriptor; import plugins.kernel.roi.descriptor.intensity.ROIStandardDeviationDescriptor; import plugins.kernel.roi.descriptor.intensity.ROISumIntensityDescriptor; import plugins.kernel.roi.descriptor.measure.ROIAreaDescriptor; import plugins.kernel.roi.descriptor.measure.ROIBasicMeasureDescriptorsPlugin; import plugins.kernel.roi.descriptor.measure.ROIContourDescriptor; import plugins.kernel.roi.descriptor.measure.ROIInteriorDescriptor; import plugins.kernel.roi.descriptor.measure.ROIMassCenterDescriptorsPlugin; import plugins.kernel.roi.descriptor.measure.ROIPerimeterDescriptor; import plugins.kernel.roi.descriptor.measure.ROISurfaceAreaDescriptor; import plugins.kernel.roi.descriptor.measure.ROIVolumeDescriptor; import plugins.kernel.roi.roi2d.ROI2DArea; import plugins.kernel.roi.roi2d.ROI2DEllipse; import plugins.kernel.roi.roi2d.ROI2DPolygon; import plugins.kernel.roi.roi2d.ROI2DRectangle; import plugins.kernel.roi.roi2d.ROI2DShape; import plugins.kernel.roi.roi3d.ROI3DArea; import plugins.kernel.roi.roi3d.ROI3DStackEllipse; import plugins.kernel.roi.roi3d.ROI3DStackPolygon; import plugins.kernel.roi.roi3d.ROI3DStackRectangle; import plugins.kernel.roi.roi4d.ROI4DArea; import plugins.kernel.roi.roi5d.ROI5DArea; /** * ROI utilities class. * * @author Stephane */ public class ROIUtil { final public static String STACK_SUFFIX = " stack"; final public static String MASK_SUFFIX = " mask"; final public static String SHAPE_SUFFIX = " shape"; final public static String OBJECT_SUFFIX = " object"; final public static String PART_SUFFIX = " part"; /** * Returns all available ROI descriptors (see {@link ROIDescriptor}) and their attached plugin * (see {@link PluginROIDescriptor}).<br/> * This list can be extended by installing new plugin(s) implementing the {@link PluginROIDescriptor} interface.<br/> * This method is an alias of {@link ROIDescriptor#getDescriptors()} * * @see ROIDescriptor#compute(ROI, Sequence) * @see PluginROIDescriptor#compute(ROI, Sequence) */ public static Map<ROIDescriptor, PluginROIDescriptor> getROIDescriptors() { return ROIDescriptor.getDescriptors(); } /** * Computes the specified descriptor from the input {@link ROIDescriptor} set on given ROI * and returns the result (or <code>null</code> if the descriptor is not found).<br/> * This method is an alias of {@link ROIDescriptor#computeDescriptor(Set, String, ROI, Sequence)} * * @param roiDescriptors * the input {@link ROIDescriptor} set (see {@link #getROIDescriptors()} method) * @param descriptorId * the id of the descriptor we want to compute ({@link ROIBasicMeasureDescriptorsPlugin#ID_VOLUME} for * instance) * @param roi * the ROI on which the descriptor(s) should be computed * @param sequence * an optional sequence where the pixel size can be retrieved * @return the computed descriptor or <code>null</code> if the descriptor if not found in the * specified set * @throws UnsupportedOperationException * if the type of the given ROI is not supported by this descriptor, or if <code>sequence</code> is * <code>null</code> while the calculation requires it, or if * the specified Z, T or C position are not supported by the descriptor */ public static Object computeDescriptor(Set<ROIDescriptor> roiDescriptors, String descriptorId, ROI roi, Sequence sequence) { return ROIDescriptor.computeDescriptor(roiDescriptors, descriptorId, roi, sequence); } /** * Computes the specified descriptor on given ROI and returns the result (or <code>null</code> if the descriptor is * not found).<br/> * This method is an alias of {@link ROIDescriptor#computeDescriptor(String, ROI, Sequence)} * * @param descriptorId * the id of the descriptor we want to compute ({@link ROIBasicMeasureDescriptorsPlugin#ID_VOLUME} for * instance) * @param roi * the ROI on which the descriptor(s) should be computed * @param sequence * an optional sequence where the pixel size can be retrieved * @return the computed descriptor or <code>null</code> if the descriptor if not found in the * specified set * @throws UnsupportedOperationException * if the type of the given ROI is not supported by this descriptor, or if <code>sequence</code> is * <code>null</code> while the calculation requires it, or if * the specified Z, T or C position are not supported by the descriptor */ public static Object computeDescriptor(String descriptorId, ROI roi, Sequence sequence) { return ROIDescriptor.computeDescriptor(descriptorId, roi, sequence); } /** * @deprecated Use {@link ROIStandardDeviationDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} * method instead. */ @Deprecated public static double getStandardDeviation(Sequence sequence, ROI roi, int z, int t, int c) { try { final SequenceDataIterator it = new SequenceDataIterator(sequence, roi, false, z, t, c); long numPixels = 0; double sum = 0; double sum2 = 0; // faster to do all calculation in a single iteration run while (!it.done()) { final double value = it.get(); sum += value; sum2 += value * value; numPixels++; it.next(); } if (numPixels > 0) { double x1 = (sum2 / numPixels); double x2 = sum / numPixels; x2 *= x2; return Math.sqrt(x1 - x2); } } catch (Exception e) { // we can have exception as the process can be really long // and size modified during this period } return 0d; } /** * @deprecated Use {@link ROIIntensityDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static IntensityInfo getIntensityInfo(Sequence sequence, ROI roi, int z, int t, int c) { try { final IntensityInfo result = new IntensityInfo(); final SequenceDataIterator it = new SequenceDataIterator(sequence, roi, false, z, t, c); long numPixels = 0; double min = Double.MAX_VALUE; double max = -Double.MAX_VALUE; double sum = 0; // faster to do all calculation in a single iteration run while (!it.done()) { final double value = it.get(); if (value < min) min = value; if (value > max) max = value; sum += value; numPixels++; it.next(); } if (numPixels > 0) { result.minIntensity = min; result.maxIntensity = max; result.meanIntensity = sum / numPixels; } else { result.minIntensity = 0d; result.maxIntensity = 0d; result.meanIntensity = 0d; } return result; } catch (Exception e) { // we can have exception as the process can be really long // and size modified during this period return null; } } /** * Returns the number of sequence pixels contained in the specified ROI. * * @param sequence * The sequence we want to get the number of pixel. * @param roi * The ROI define the region where we want to compute the number of pixel. * @param z * The specific Z position (slice) where we want to compute the number of pixel or <code>-1</code> to use the * ROI Z dimension information. * @param t * The specific T position (frame) where we want to compute the number of pixel or <code>-1</code> to use the * ROI T dimension information. * @param c * The specific C position (channel) where we want to compute the number of pixel or <code>-1</code> to use * the ROI C dimension information. */ public static long getNumPixel(Sequence sequence, ROI roi, int z, int t, int c) { return DataIteratorUtil.count(new SequenceDataIterator(sequence, roi, false, z, t, c)); } /** * @deprecated Use {@link ROIMinIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static double getMinIntensity(Sequence sequence, ROI roi, int z, int t, int c) { return DataIteratorMath.min(new SequenceDataIterator(sequence, roi, false, z, t, c)); } /** * @deprecated Use {@link ROIMaxIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static double getMaxIntensity(Sequence sequence, ROI roi, int z, int t, int c) { return DataIteratorMath.max(new SequenceDataIterator(sequence, roi, false, z, t, c)); } /** * @deprecated Use {@link ROIMeanIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static double getMeanIntensity(Sequence sequence, ROI roi, int z, int t, int c) { return DataIteratorMath.mean(new SequenceDataIterator(sequence, roi, false, z, t, c)); } /** * @deprecated Use {@link ROISumIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static double getSumIntensity(Sequence sequence, ROI roi, int z, int t, int c) { return DataIteratorMath.sum(new SequenceDataIterator(sequence, roi, false, z, t, c)); } /** * @deprecated Use {@link ROIStandardDeviationDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} * method instead. */ @Deprecated public static double getStandardDeviation(Sequence sequence, ROI roi) { return getStandardDeviation(sequence, roi, -1, -1, -1); } /** * @deprecated Use {@link ROIIntensityDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static IntensityInfo getIntensityInfo(Sequence sequence, ROI roi) { return getIntensityInfo(sequence, roi, -1, -1, -1); } /** * Returns the number of sequence pixels contained in the specified ROI. */ public static long getNumPixel(Sequence sequence, ROI roi) { return getNumPixel(sequence, roi, -1, -1, -1); } /** * @deprecated Use {@link ROIMinIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static double getMinIntensity(Sequence sequence, ROI roi) { return getMinIntensity(sequence, roi, -1, -1, -1); } /** * @deprecated Use {@link ROIMaxIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static double getMaxIntensity(Sequence sequence, ROI roi) { return getMaxIntensity(sequence, roi, -1, -1, -1); } /** * @deprecated Use {@link ROIMeanIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static double getMeanIntensity(Sequence sequence, ROI roi) { return getMeanIntensity(sequence, roi, -1, -1, -1); } /** * @deprecated Use {@link ROISumIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static double getSumIntensity(Sequence sequence, ROI roi) { return getSumIntensity(sequence, roi, -1, -1, -1); } /** * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} * method instead. */ @Deprecated public static Point5D getMassCenter(ROI roi) { switch (roi.getDimension()) { case 2: final ROI2D roi2d = (ROI2D) roi; final Point2D pt2d = getMassCenter(roi2d); return new Point5D.Double(pt2d.getX(), pt2d.getY(), roi2d.getZ(), roi2d.getT(), roi2d.getC()); case 3: final ROI3D roi3d = (ROI3D) roi; final Point3D pt3d = getMassCenter(roi3d); return new Point5D.Double(pt3d.getX(), pt3d.getY(), pt3d.getZ(), roi3d.getT(), roi3d.getC()); case 4: final ROI4D roi4d = (ROI4D) roi; final Point4D pt4d = getMassCenter(roi4d); return new Point5D.Double(pt4d.getX(), pt4d.getY(), pt4d.getZ(), pt4d.getT(), roi4d.getC()); case 5: return getMassCenter((ROI5D) roi); default: return null; } } /** * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} * method instead. */ @Deprecated public static Point2D getMassCenter(ROI2D roi) { double x = 0, y = 0; long len = 0; final BooleanMask2D mask = roi.getBooleanMask(true); final boolean m[] = mask.mask; final int h = mask.bounds.height; final int w = mask.bounds.width; int off = 0; for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { if (m[off++]) { x += i; y += j; len++; } } } // get bounds final Rectangle2D bounds2D = roi.getBounds2D(); // empty roi --> use bounds center if (len == 0) return new Point2D.Double(bounds2D.getCenterX(), bounds2D.getCenterY()); return new Point2D.Double(bounds2D.getX() + (x / len), bounds2D.getY() + (y / len)); } /** * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} * method instead. */ @Deprecated public static Point3D getMassCenter(ROI3D roi) { double x = 0, y = 0, z = 0; long len = 0; final BooleanMask3D mask3d = roi.getBooleanMask(true); for (Integer zSlice : mask3d.mask.keySet()) { final int zi = zSlice.intValue(); final double zd = zi; final BooleanMask2D mask = mask3d.getMask2D(zi); final boolean m[] = mask.mask; final double bx = mask.bounds.x; final double by = mask.bounds.y; final int h = mask.bounds.height; final int w = mask.bounds.width; int off = 0; for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { if (m[off++]) { x += bx + i; y += by + j; z += zd; len++; } } } } // get bounds final Rectangle3D bounds3D = roi.getBounds3D(); // empty roi --> use bounds center if (len == 0) return new Point3D.Double(bounds3D.getCenterX(), bounds3D.getCenterY(), bounds3D.getCenterZ()); return new Point3D.Double((x / len), (y / len), (z / len)); } /** * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} * method instead. */ @Deprecated public static Point4D getMassCenter(ROI4D roi) { final BooleanMask4D mask4d = roi.getBooleanMask(true); double x = 0, y = 0, z = 0, t = 0; long len = 0; for (Integer tFrame : mask4d.mask.keySet()) { final int ti = tFrame.intValue(); final double td = ti; final BooleanMask3D mask3d = mask4d.getMask3D(ti); for (Integer zSlice : mask3d.mask.keySet()) { final int zi = zSlice.intValue(); final double zd = zi; final BooleanMask2D mask = mask3d.getMask2D(zi); final boolean m[] = mask.mask; final double bx = mask.bounds.x; final double by = mask.bounds.y; final int h = mask.bounds.height; final int w = mask.bounds.width; int off = 0; for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { if (m[off++]) { x += bx + i; y += by + j; z += zd; t += td; len++; } } } } } // get bounds final Rectangle4D bounds4D = roi.getBounds4D(); // empty roi --> use bounds center if (len == 0) return new Point4D.Double(bounds4D.getCenterX(), bounds4D.getCenterY(), bounds4D.getCenterZ(), bounds4D.getCenterT()); return new Point4D.Double((x / len), (y / len), (z / len), (t / len)); } /** * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} * method instead. */ @Deprecated public static Point5D getMassCenter(ROI5D roi) { final BooleanMask5D mask5d = roi.getBooleanMask(true); double x = 0, y = 0, z = 0, t = 0, c = 0; long len = 0; for (Integer cChannel : mask5d.mask.keySet()) { final int ci = cChannel.intValue(); final double cd = ci; final BooleanMask4D mask4d = mask5d.getMask4D(ci); for (Integer tFrame : mask4d.mask.keySet()) { final int ti = tFrame.intValue(); final double td = ti; final BooleanMask3D mask3d = mask4d.getMask3D(ti); for (Integer zSlice : mask3d.mask.keySet()) { final int zi = zSlice.intValue(); final double zd = zi; final BooleanMask2D mask = mask3d.getMask2D(zi); final boolean m[] = mask.mask; final double bx = mask.bounds.x; final double by = mask.bounds.y; final int h = mask.bounds.height; final int w = mask.bounds.width; int off = 0; for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { if (m[off++]) { x += bx + i; y += by + j; z += zd; t += td; c += cd; len++; } } } } } } // get bounds final Rectangle5D bounds5D = roi.getBounds5D(); // empty roi --> use bounds center if (len == 0) return new Point5D.Double(bounds5D.getCenterX(), bounds5D.getCenterY(), bounds5D.getCenterZ(), bounds5D.getCenterT(), bounds5D.getCenterC()); return new Point5D.Double((x / len), (y / len), (z / len), (t / len), (c / len)); } /** * @deprecated */ @Deprecated private static double getMultiplier(Sequence sequence, ROI roi, int dim) { final int dimRoi = roi.getDimension(); // cannot give this information for this roi if (dimRoi > dim) return 0d; final Rectangle5D boundsRoi = roi.getBounds5D(); double mul = 1d; switch (dim) { case 5: if (dimRoi == 4) { final int sizeC = sequence.getSizeC(); if ((boundsRoi.getSizeC() == Double.POSITIVE_INFINITY) && (sizeC > 1)) mul *= sizeC; // cannot give this information for this roi else mul = 0d; } case 4: if (dimRoi == 3) { final int sizeT = sequence.getSizeT(); if ((boundsRoi.getSizeT() == Double.POSITIVE_INFINITY) && (sizeT > 1)) mul *= sizeT; // cannot give this information for this roi else mul = 0d; } case 3: if (dimRoi == 2) { final int sizeZ = sequence.getSizeZ(); if ((boundsRoi.getSizeZ() == Double.POSITIVE_INFINITY) && (sizeZ > 1)) mul *= sizeZ; // cannot give this information for this roi else mul = 0d; } case 2: if (dimRoi == 1) { final int sizeY = sequence.getSizeY(); if ((boundsRoi.getSizeY() == Double.POSITIVE_INFINITY) && (sizeY > 1)) mul *= sizeY; // cannot give this information for this roi else mul = 0d; } } return mul; } /** * @deprecated Use {@link ROIContourDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead. */ @Deprecated public static String getContourSize(Sequence sequence, double contourPoints, ROI roi, int dim, int roundSignificant) { final double mul = getMultiplier(sequence, roi, dim); // 0 means the operation is not supported for this ROI if (mul != 0d) return sequence.calculateSize(MathUtil.roundSignificant(contourPoints, roundSignificant) * mul, dim, dim - 1, 5); return ""; } /** * @deprecated Use {@link ROIContourDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead. */ @Deprecated public static String getContourSize(Sequence sequence, ROI roi, int dim, int roundSignificant) { return getContourSize(sequence, roi.getNumberOfContourPoints(), roi, dim, roundSignificant); } /** * @deprecated Use {@link ROIContourDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead. */ @Deprecated public static String getContourSize(Sequence sequence, ROI roi, int dim) { return getContourSize(sequence, roi, dim, 0); } /** * @deprecated Use {@link ROIInteriorDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static String getInteriorSize(Sequence sequence, double interiorPoints, ROI roi, int dim, int roundSignificant) { final double mul = getMultiplier(sequence, roi, dim); // 0 means the operation is not supported for this ROI if (mul != 0d) return sequence.calculateSize(MathUtil.roundSignificant(interiorPoints, roundSignificant) * mul, dim, dim, 5); return ""; } /** * @deprecated Use {@link ROIInteriorDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static String getInteriorSize(Sequence sequence, ROI roi, int dim, int roundSignificant) { return getInteriorSize(sequence, roi.getNumberOfPoints(), roi, dim, roundSignificant); } /** * @deprecated Use {@link ROIInteriorDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static String getInteriorSize(Sequence sequence, ROI roi, int dim) { return getInteriorSize(sequence, roi, dim, 0); } /** * @deprecated Use {@link ROIPerimeterDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static String getPerimeter(Sequence sequence, ROI roi, int roundSignificant) { return getContourSize(sequence, roi, 2, roundSignificant); } /** * @deprecated Use {@link ROIPerimeterDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static String getPerimeter(Sequence sequence, ROI roi) { return getPerimeter(sequence, roi, 0); } /** * @deprecated Use {@link ROIAreaDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead. */ @Deprecated public static String getArea(Sequence sequence, ROI roi, int roundSignificant) { return getInteriorSize(sequence, roi, 2, roundSignificant); } /** * @deprecated Use {@link ROIAreaDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead. */ @Deprecated public static String getArea(Sequence sequence, ROI roi) { return getArea(sequence, roi, 0); } /** * @deprecated Use {@link ROISurfaceAreaDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static String getSurfaceArea(Sequence sequence, ROI roi, int roundSignificant) { return getContourSize(sequence, roi, 3, roundSignificant); } /** * @deprecated Use {@link ROISurfaceAreaDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method * instead. */ @Deprecated public static String getSurfaceArea(Sequence sequence, ROI roi) { return getSurfaceArea(sequence, roi, 0); } /** * @deprecated Use {@link ROIVolumeDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead. */ @Deprecated public static String getVolume(Sequence sequence, ROI roi, int roundSignificant) { return getInteriorSize(sequence, roi, 3, roundSignificant); } /** * @deprecated Use {@link ROIVolumeDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead. */ @Deprecated public static String getVolume(Sequence sequence, ROI roi) { return getVolume(sequence, roi, 0); } /** * Returns the effective ROI number of dimension needed for the specified bounds. */ public static int getEffectiveDimension(Rectangle5D bounds) { int result = 5; if (bounds.isInfiniteC() || (bounds.getSizeC() <= 1d)) { result--; if (bounds.isInfiniteT() || (bounds.getSizeT() <= 1d)) { result--; if (bounds.isInfiniteZ() || (bounds.getSizeZ() <= 1d)) result--; } } return result; } /** * Return 5D dimension for specified operation dimension */ private static Dimension5D.Integer getOpDim(int dim, Rectangle5D.Integer bounds) { final Dimension5D.Integer result = new Dimension5D.Integer(); switch (dim) { case 2: // XY ROI with fixed ZTC result.sizeZ = 1; result.sizeT = 1; result.sizeC = 1; break; case 3: // XYZ ROI with fixed TC result.sizeZ = bounds.sizeZ; result.sizeT = 1; result.sizeC = 1; break; case 4: // XYZT ROI with fixed C result.sizeZ = bounds.sizeZ; result.sizeT = bounds.sizeT; result.sizeC = 1; break; default: // XYZTC ROI result.sizeZ = bounds.sizeZ; result.sizeT = bounds.sizeT; result.sizeC = bounds.sizeC; break; } return result; } /** * Get ROI result for specified 5D mask and operation dimension. */ private static ROI getOpResult(int dim, BooleanMask5D mask, Rectangle5D.Integer bounds) { final ROI result; switch (dim) { case 2: // XY ROI with fixed ZTC result = new ROI2DArea(mask.getMask2D(bounds.z, bounds.t, bounds.c)); // set ZTC position result.beginUpdate(); try { ((ROI2D) result).setZ(bounds.z); ((ROI2D) result).setT(bounds.t); ((ROI2D) result).setC(bounds.c); } finally { result.endUpdate(); } break; case 3: // XYZ ROI with fixed TC result = new ROI3DArea(mask.getMask3D(bounds.t, bounds.c)); // set TC position result.beginUpdate(); try { ((ROI3D) result).setT(bounds.t); ((ROI3D) result).setC(bounds.c); } finally { result.endUpdate(); } break; case 4: // XYZT ROI with fixed C result = new ROI4DArea(mask.getMask4D(bounds.c)); // set C position ((ROI4D) result).setC(bounds.c); break; case 5: // XYZTC ROI result = new ROI5DArea(mask); break; default: throw new UnsupportedOperationException( "Can't process boolean operation on a ROI with unknown dimension."); } return result; } /** * Compute the resulting bounds for <i>union</i> operation between specified ROIs.<br> * It throws an exception if the <i>union</i> operation cannot be done (incompatible dimension). */ public static Rectangle5D getUnionBounds(ROI roi1, ROI roi2) throws UnsupportedOperationException { // null checking if (roi1 == null) { if (roi2 == null) return new Rectangle5D.Double(); return roi2.getBounds5D(); } else if (roi2 == null) return roi1.getBounds5D(); final Rectangle5D bounds1 = roi1.getBounds5D(); final Rectangle5D bounds2 = roi2.getBounds5D(); // init infinite dim infos final boolean ic1 = bounds1.isInfiniteC(); final boolean ic2 = bounds2.isInfiniteC(); final boolean it1 = bounds1.isInfiniteT(); final boolean it2 = bounds2.isInfiniteT(); final boolean iz1 = bounds1.isInfiniteZ(); final boolean iz2 = bounds2.isInfiniteZ(); // cannot process union when we have an infinite dimension with a finite one if ((ic1 ^ ic2) || (it1 ^ it2) || (iz1 ^ iz2)) throw new UnsupportedOperationException("Can't process union on ROI with different infinite dimension"); // do union Rectangle5D.union(bounds1, bounds2, bounds1); // init infinite dim infos on result final boolean ic = bounds1.isInfiniteC() || (bounds1.getSizeC() <= 1d); final boolean it = bounds1.isInfiniteT() || (bounds1.getSizeT() <= 1d); final boolean iz = bounds1.isInfiniteZ() || (bounds1.getSizeZ() <= 1d); // cannot process union if C dimension is finite but T or Z is infinite if (!ic && (it || iz)) throw new UnsupportedOperationException( "Can't process union on ROI with a finite C dimension and infinite T or Z dimension"); // cannot process union if T dimension is finite but Z is infinite if (!it && iz) throw new UnsupportedOperationException( "Can't process union on ROI with a finite T dimension and infinite Z dimension"); return bounds1; } /** * Compute the resulting bounds for <i>intersection</i> operation between specified ROIs.<br> * It throws an exception if the <i>intersection</i> operation cannot be done (incompatible dimension). */ protected static Rectangle5D getIntersectionBounds(ROI roi1, ROI roi2) throws UnsupportedOperationException { // null checking if ((roi1 == null) || (roi2 == null)) return new Rectangle5D.Double(); final Rectangle5D bounds1 = roi1.getBounds5D(); final Rectangle5D bounds2 = roi2.getBounds5D(); // do intersection Rectangle5D.intersect(bounds1, bounds2, bounds1); // init infinite dim infos final boolean ic = bounds1.isInfiniteC() || (bounds1.getSizeC() <= 1d); final boolean it = bounds1.isInfiniteT() || (bounds1.getSizeT() <= 1d); final boolean iz = bounds1.isInfiniteZ() || (bounds1.getSizeZ() <= 1d); // cannot process intersection if C dimension is finite but T or Z is infinite if (!ic && (it || iz)) throw new UnsupportedOperationException( "Can't process intersection on ROI with a finite C dimension and infinite T or Z dimension"); // cannot process intersection if T dimension is finite but Z is infinite if (!it && iz) throw new UnsupportedOperationException( "Can't process intersection on ROI with a finite T dimension and infinite Z dimension"); return bounds1; } /** * Compute the resulting bounds for <i>subtraction</i> of (roi1 - roi2).<br> * It throws an exception if the <i>subtraction</i> operation cannot be done (incompatible dimension). */ protected static Rectangle5D getSubtractionBounds(ROI roi1, ROI roi2) throws UnsupportedOperationException { // null checking if (roi1 == null) return new Rectangle5D.Double(); if (roi2 == null) return roi1.getBounds5D(); final Rectangle5D bounds1 = roi1.getBounds5D(); final Rectangle5D bounds2 = roi2.getBounds5D(); // init infinite dim infos final boolean ic1 = bounds1.isInfiniteC(); final boolean ic2 = bounds2.isInfiniteC(); final boolean it1 = bounds1.isInfiniteT(); final boolean it2 = bounds2.isInfiniteT(); final boolean iz1 = bounds1.isInfiniteZ(); final boolean iz2 = bounds2.isInfiniteZ(); // cannot process subtraction when we have an finite dimension on second ROI // while having a infinite one on the first ROI if (ic1 && !ic2) throw new UnsupportedOperationException( "Can't process subtraction: ROI 1 has infinite C dimension while ROI 2 has a finite one"); if (it1 && !it2) throw new UnsupportedOperationException( "Can't process subtraction: ROI 1 has infinite T dimension while ROI 2 has a finite one"); if (iz1 && !iz2) throw new UnsupportedOperationException( "Can't process subtraction: ROI 1 has infinite Z dimension while ROI 2 has a finite one"); return bounds1; } /** * Computes union of specified <code>ROI</code> and return result in a new <code>ROI</code>. */ public static ROI getUnion(ROI roi1, ROI roi2) throws UnsupportedOperationException { // null checking if (roi1 == null) { // return empty ROI if (roi2 == null) return new ROI2DArea(); // return simple copy return roi2.getCopy(); } else if (roi2 == null) return roi1.getCopy(); final Rectangle5D bounds5D = getUnionBounds(roi1, roi2); final int dim = getEffectiveDimension(bounds5D); // we want integer bounds now final Rectangle5D.Integer bounds = bounds5D.toInteger(); final Dimension5D.Integer roiSize = getOpDim(dim, bounds); // get 3D and 4D bounds final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D(); final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D(); final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC]; for (int c = 0; c < roiSize.sizeC; c++) { final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT]; for (int t = 0; t < roiSize.sizeT; t++) { final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ]; for (int z = 0; z < roiSize.sizeZ; z++) { mask3D[z] = BooleanMask2D.getUnion( roi1.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true), roi2.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true)); } mask4D[t] = new BooleanMask3D(bounds3D, mask3D); } mask5D[c] = new BooleanMask4D(bounds4D, mask4D); } // build the 5D result ROI final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D); // optimize bounds of the new created mask mask.optimizeBounds(); // get result final ROI result = getOpResult(dim, mask, bounds); // set name result.setName("Union"); return result; } /** * Computes intersection of specified <code>ROI</code> and return result in a new <code>ROI</code>. */ public static ROI getIntersection(ROI roi1, ROI roi2) throws UnsupportedOperationException { // null checking if ((roi1 == null) || (roi2 == null)) // return empty ROI return new ROI2DArea(); final Rectangle5D bounds5D = getIntersectionBounds(roi1, roi2); final int dim = getEffectiveDimension(bounds5D); // we want integer bounds now final Rectangle5D.Integer bounds = bounds5D.toInteger(); final Dimension5D.Integer roiSize = getOpDim(dim, bounds); // get 2D, 3D and 4D bounds final Rectangle bounds2D = (Rectangle) bounds.toRectangle2D(); final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D(); final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D(); final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC]; for (int c = 0; c < roiSize.sizeC; c++) { final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT]; for (int t = 0; t < roiSize.sizeT; t++) { final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ]; for (int z = 0; z < roiSize.sizeZ; z++) { final BooleanMask2D roi1Mask2D = new BooleanMask2D(bounds2D, roi1.getBooleanMask2D(bounds2D, bounds.z + z, bounds.t + t, bounds.c + c, true)); final BooleanMask2D roi2Mask2D = new BooleanMask2D(bounds2D, roi2.getBooleanMask2D(bounds2D, bounds.z + z, bounds.t + t, bounds.c + c, true)); mask3D[z] = BooleanMask2D.getIntersection(roi1Mask2D, roi2Mask2D); } mask4D[t] = new BooleanMask3D(bounds3D, mask3D); } mask5D[c] = new BooleanMask4D(bounds4D, mask4D); } // build the 5D result ROI final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D); // optimize bounds of the new created mask mask.optimizeBounds(); // get result final ROI result = getOpResult(dim, mask, bounds); // set name result.setName("Intersection"); return result; } /** * Compute exclusive union of specified <code>ROI</code> and return result in a new <code>ROI</code>. */ public static ROI getExclusiveUnion(ROI roi1, ROI roi2) throws UnsupportedOperationException { // null checking if (roi1 == null) { // return empty ROI if (roi2 == null) return new ROI2DArea(); // return simple copy return roi2.getCopy(); } else if (roi2 == null) return roi1.getCopy(); final Rectangle5D bounds5D = getUnionBounds(roi1, roi2); final int dim = getEffectiveDimension(bounds5D); // we want integer bounds now final Rectangle5D.Integer bounds = bounds5D.toInteger(); final Dimension5D.Integer roiSize = getOpDim(dim, bounds); // get 3D and 4D bounds final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D(); final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D(); final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC]; for (int c = 0; c < roiSize.sizeC; c++) { final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT]; for (int t = 0; t < roiSize.sizeT; t++) { final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ]; for (int z = 0; z < roiSize.sizeZ; z++) { mask3D[z] = BooleanMask2D.getExclusiveUnion( roi1.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true), roi2.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true)); } mask4D[t] = new BooleanMask3D(bounds3D, mask3D); } mask5D[c] = new BooleanMask4D(bounds4D, mask4D); } // build the 5D result ROI final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D); // optimize bounds of the new created mask mask.optimizeBounds(); // get result final ROI result = getOpResult(dim, mask, bounds); // set name result.setName("Exclusive union"); return result; } /** * Computes the subtraction of roi1 - roi2 and returns result in a new <code>ROI</code>. */ public static ROI getSubtraction(ROI roi1, ROI roi2) throws UnsupportedOperationException { // return empty ROI if (roi1 == null) return new ROI2DArea(); // return copy of ROI1 if (roi2 == null) return roi1.getCopy(); final Rectangle5D bounds5D = getSubtractionBounds(roi1, roi2); final int dim = getEffectiveDimension(bounds5D); // we want integer bounds now final Rectangle5D.Integer bounds = bounds5D.toInteger(); final Dimension5D.Integer roiSize = getOpDim(dim, bounds); // get 3D and 4D bounds final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D(); final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D(); final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC]; for (int c = 0; c < roiSize.sizeC; c++) { final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT]; for (int t = 0; t < roiSize.sizeT; t++) { final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ]; for (int z = 0; z < roiSize.sizeZ; z++) { mask3D[z] = BooleanMask2D.getSubtraction( roi1.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true), roi2.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true)); } mask4D[t] = new BooleanMask3D(bounds3D, mask3D); } mask5D[c] = new BooleanMask4D(bounds4D, mask4D); } // build the 5D result ROI final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D); // optimize bounds of the new created mask mask.optimizeBounds(); // get result final ROI result = getOpResult(dim, mask, bounds); // set name result.setName("Substraction"); return result; } /** * Merge the specified array of {@link ROI} with the given {@link BooleanOperator}.<br> * * @param rois * ROIs we want to merge. * @param operator * {@link BooleanOperator} to apply. * @return {@link ROI} representing the result of the merge operation. */ public static ROI merge(List<? extends ROI> rois, BooleanOperator operator) throws UnsupportedOperationException { if (rois.size() == 0) return null; ROI result = rois.get(0).getCopy(); // copy can fail... if (result != null) { switch (operator) { case AND: for (int i = 1; i < rois.size(); i++) result = result.intersect(rois.get(i), true); break; case OR: for (int i = 1; i < rois.size(); i++) result = result.add(rois.get(i), true); break; case XOR: for (int i = 1; i < rois.size(); i++) result = result.exclusiveAdd(rois.get(i), true); break; } } // for (int i = 1; i < rois.size(); i++) // { // final ROI roi = rois.get(i); // // switch (operator) // { // case AND: // result = result.getIntersection(roi); // break; // case OR: // result = result.getUnion(roi); // break; // case XOR: // result = result.getExclusiveUnion(roi); // break; // } // } return result; } /** * Builds and returns a ROI corresponding to the union of the specified ROI list. */ public static ROI getUnion(List<? extends ROI> rois) throws UnsupportedOperationException { return merge(rois, BooleanOperator.OR); } /** * Builds and returns a ROI corresponding to the exclusive union of the specified ROI list. */ public static ROI getExclusiveUnion(List<? extends ROI> rois) throws UnsupportedOperationException { return merge(rois, BooleanOperator.XOR); } /** * Builds and returns a ROI corresponding to the intersection of the specified ROI list. */ public static ROI getIntersection(List<? extends ROI> rois) throws UnsupportedOperationException { return merge(rois, BooleanOperator.AND); } /** * Subtract the content of the roi2 from the roi1 and return the result as a new {@link ROI}.<br> * This is equivalent to: <code>roi1.getSubtraction(roi2)</code> * * @return {@link ROI} representing the result of subtraction. */ public static ROI subtract(ROI roi1, ROI roi2) throws UnsupportedOperationException { return roi1.getSubtraction(roi2); } /** * Converts the specified 2D ROI to 3D Stack ROI (ROI3DStack) by stacking it along the Z axis given zMin and zMax * (inclusive) parameters. * * @return the converted 3D stack ROI or <code>null</code> if the ROI */ public static ROI convertToStack(ROI2D roi, int zMin, int zMax) { ROI result = null; if (roi instanceof ROI2DRectangle) result = new ROI3DStackRectangle(((ROI2DRectangle) roi).getRectangle(), zMin, zMax); else if (roi instanceof ROI2DEllipse) result = new ROI3DStackEllipse(((ROI2DEllipse) roi).getEllipse(), zMin, zMax); else if (roi instanceof ROI2DPolygon) result = new ROI3DStackPolygon(((ROI2DPolygon) roi).getPolygon2D(), zMin, zMax); else if (roi instanceof ROI2DArea) result = new ROI3DArea(((ROI2DArea) roi).getBooleanMask(true), zMin, zMax); else if (roi != null) result = new ROI3DArea(roi.getBooleanMask2D(roi.getZ(), roi.getT(), roi.getC(), true), zMin, zMax); if ((roi != null) && (result != null)) { // unselect all control points result.unselectAllPoints(); // keep original ROI informations result.setName(roi.getName() + STACK_SUFFIX); copyROIProperties(roi, result, false); } return result; } /** * Converts the specified ROI to a boolean mask type ROI (ROI Area). * * @return the ROI Area corresponding to the input ROI.<br> * If the ROI is already of boolean mask type then it's directly returned without any conversion. */ public static ROI convertToMask(ROI roi) { // no conversion needed if ((roi instanceof ROI2DArea) || (roi instanceof ROI3DArea) || (roi instanceof ROI4DArea) || (roi instanceof ROI5DArea)) return roi; final Rectangle5D bounds5D = roi.getBounds5D(); final int dim = getEffectiveDimension(bounds5D); // we want integer bounds now final Rectangle5D.Integer bounds = bounds5D.toInteger(); final Dimension5D.Integer roiSize = getOpDim(dim, bounds); // get 2D, 3D and 4D bounds final Rectangle bounds2D = (Rectangle) bounds.toRectangle2D(); final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D(); final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D(); // build 5D mask result final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC]; for (int c = 0; c < roiSize.sizeC; c++) { final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT]; for (int t = 0; t < roiSize.sizeT; t++) { final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ]; for (int z = 0; z < roiSize.sizeZ; z++) mask3D[z] = new BooleanMask2D(bounds2D, roi.getBooleanMask2D(bounds2D, bounds.z + z, bounds.t + t, bounds.c + c, true)); mask4D[t] = new BooleanMask3D(bounds3D, mask3D); } mask5D[c] = new BooleanMask4D(bounds4D, mask4D); } // build the 5D result ROI final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D); // optimize bounds of the new created mask mask.optimizeBounds(); // get result final ROI result = getOpResult(dim, mask, bounds); // keep original ROI informations String newName = roi.getName() + MASK_SUFFIX; // check if we can shorter name final String cancelableSuffix = SHAPE_SUFFIX + MASK_SUFFIX; if (newName.endsWith(cancelableSuffix)) newName = newName.substring(0, newName.length() - cancelableSuffix.length()); // set name result.setName(newName); // copy properties copyROIProperties(roi, result, false); return result; } /** * Converts the specified ROI to a shape type ROI (ROI Polygon or ROI Mesh). * * @param roi * the roi to convert to shape type ROI * @param maxDeviation * maximum allowed deviation/distance of resulting ROI polygon from the input ROI contour (in pixel). * Use <code>-1</code> for automatic maximum deviation calculation. * @return the ROI Polygon or ROI Mesh corresponding to the input ROI.<br> * If the ROI is already of shape type then it's directly returned without any conversion. */ public static ROI convertToShape(ROI roi, double maxDeviation) throws UnsupportedOperationException { if (roi instanceof ROI2DShape) return roi; if (roi instanceof ROI2D) { final ROI2D roi2d = (ROI2D) roi; // get contour points in connected order final List<Point> points = roi2d.getBooleanMask(true).getConnectedContourPoints(); // convert to point2D and center points in observed pixel. final List<Point2D> points2D = new ArrayList<Point2D>(points.size()); for (Point pt : points) points2D.add(new Point2D.Double(pt.x + 0.5d, pt.y + 0.5d)); final double dev; // auto deviation if (maxDeviation < 0) { // compute it from ROI size final Rectangle2D bnd = roi2d.getBounds2D(); dev = Math.log10(Math.sqrt(bnd.getWidth() * bnd.getHeight())) / Math.log10(3); } else dev = maxDeviation; // convert to ROI polygon final ROI2DPolygon result = new ROI2DPolygon(Polygon2D.getPolygon2D(points2D, dev)); // keep original ROI informations String newName = roi.getName() + SHAPE_SUFFIX; // check if we can shorter name final String cancelableSuffix = MASK_SUFFIX + SHAPE_SUFFIX; if (newName.endsWith(cancelableSuffix)) newName = newName.substring(0, newName.length() - cancelableSuffix.length()); // set name result.setName(newName); // copy properties copyROIProperties(roi, result, false); return result; } if (roi instanceof ROI3D) { // not yet supported throw new UnsupportedOperationException("ROIUtil.convertToShape(ROI): Operation not supported for 3D ROI."); } throw new UnsupportedOperationException("ROIUtil.convertToShape(ROI): Operation not supported for this ROI: " + roi.getName()); } /** * Returns connected component from specified ROI as a list of ROI (Area type). */ public static List<ROI> getConnectedComponents(ROI roi) throws UnsupportedOperationException { final List<ROI> result = new ArrayList<ROI>(); if (roi instanceof ROI2D) { final ROI2D roi2d = (ROI2D) roi; int ind = 0; for (BooleanMask2D component : roi2d.getBooleanMask(true).getComponents()) { final ROI2DArea componentRoi = new ROI2DArea(component); if (!componentRoi.isEmpty()) { // keep original ROI informations componentRoi.setName(roi.getName() + OBJECT_SUFFIX + " #" + ind++); copyROIProperties(roi, componentRoi, false); result.add(componentRoi); } } return result; } if (roi instanceof ROI3D) { // TODO: add label extractor implementation here // final ROI3D roi3d = (ROI3D) roi; // int ind = 0; // // for (BooleanMask3D component : roi3d.getBooleanMask(true).getComponents()) // { // final ROI2DArea componentRoi = new ROI2DArea(component); // // if (!componentRoi.isEmpty()) // { // // keep original ROI informations // componentRoi.setName(roi.getName() + " object #" + ind++); // copyROIProperties(roi, componentRoi, false); // // result.add(componentRoi); // } // } // not yet supported throw new UnsupportedOperationException( "ROIUtil.getConnectedComponents(ROI): Operation not supported yet for 3D ROI."); } throw new UnsupportedOperationException( "ROIUtil.getConnectedComponents(ROI): Operation not supported for this ROI: " + roi.getName()); } static boolean computePolysFromLine(Line2D line, Point2D edgePt1, Point2D edgePt2, Polygon2D poly1, Polygon2D poly2, boolean inner) { final Line2D edgeLine = new Line2D.Double(edgePt1, edgePt2); // they intersect ? if (edgeLine.intersectsLine(line)) { final Point2D intersection = Line2DUtil.getIntersection(edgeLine, line); // are we inside poly2 ? if (inner) { // add intersection to poly2 poly2.addPoint(intersection); // add intersection and pt2 to poly1 poly1.addPoint(intersection); poly1.addPoint(edgePt2); } else { // add intersection to poly1 poly1.addPoint(intersection); // add intersection and pt2 to poly2 poly2.addPoint(intersection); poly2.addPoint(edgePt2); } // we changed region return !inner; } // inside poly2 --> add point to poly2 if (inner) poly2.addPoint(edgePt2); else poly1.addPoint(edgePt2); // same region return inner; } /** * Cut the specified ROI with the given Line2D (extended to ROI bounds) and return the 2 resulting ROI in a list.<br> * If the specified ROI cannot be cut by the given Line2D then <code>null</code> is returned. */ public static List<ROI> split(ROI roi, Line2D line) { final Rectangle2D bounds2d = roi.getBounds5D().toRectangle2D(); // need to enlarge bounds a bit to avoid roundness issues on line intersection final Rectangle2D extendedBounds2d = Rectangle2DUtil.getScaledRectangle(bounds2d, 1.1d, true); // enlarge line to ROI bounds final Line2D extendedLine = Rectangle2DUtil.getIntersectionLine(extendedBounds2d, line); // if the extended line intersects the ROI bounds if ((extendedLine != null) && bounds2d.intersectsLine(extendedLine)) { final List<ROI> result = new ArrayList<ROI>(); final Point2D topLeft = new Point2D.Double(bounds2d.getMinX(), bounds2d.getMinY()); final Point2D topRight = new Point2D.Double(bounds2d.getMaxX(), bounds2d.getMinY()); final Point2D bottomRight = new Point2D.Double(bounds2d.getMaxX(), bounds2d.getMaxY()); final Point2D bottomLeft = new Point2D.Double(bounds2d.getMinX(), bounds2d.getMaxY()); final Polygon2D poly1 = new Polygon2D(); final Polygon2D poly2 = new Polygon2D(); boolean inner; // add first point to poly1 poly1.addPoint(topLeft); // we are inside poly1 for now inner = false; // compute the 2 rectangle part (polygon) from top, right, bottom and left lines inner = computePolysFromLine(extendedLine, topLeft, topRight, poly1, poly2, inner); inner = computePolysFromLine(extendedLine, topRight, bottomRight, poly1, poly2, inner); inner = computePolysFromLine(extendedLine, bottomRight, bottomLeft, poly1, poly2, inner); inner = computePolysFromLine(extendedLine, bottomLeft, topLeft, poly1, poly2, inner); // get intersection result from both polygon final ROI roiPart1 = new ROI2DPolygon(poly1).getIntersection(roi); final ROI roiPart2 = new ROI2DPolygon(poly2).getIntersection(roi); // keep original ROI informations roiPart1.setName(roi.getName() + PART_SUFFIX + " #1"); copyROIProperties(roi, roiPart1, false); roiPart2.setName(roi.getName() + PART_SUFFIX + " #2"); copyROIProperties(roi, roiPart2, false); // add to result list result.add(roiPart1); result.add(roiPart2); return result; } return null; } /** * Convert a list of ROI into a binary / labeled Sequence. * * @param inputRois * list of ROI to convert * @param sizeX * the wanted size X of output Sequence, if set to <code>0</code> then Sequence size X is computed * automatically from * the global ROI bounds. * @param sizeY * the wanted size Y of output Sequence, if set to <code>0</code> then Sequence size Y is computed * automatically from * the global ROI bounds. * @param sizeC * the wanted size C of output Sequence, if set to <code>0</code> then Sequence size C is computed * automatically from * the global ROI bounds. * @param sizeZ * the wanted size Z of output Sequence, if set to <code>0</code> then Sequence size Z is computed * automatically from * the global ROI bounds. * @param sizeT * the wanted size T of output Sequence, if set to <code>0</code> then Sequence size T is computed * automatically from * the global ROI bounds. * @param dataType * the wanted dataType of output Sequence * @param label * if set to <code>true</code> then each ROI will be draw as a separate label (value) in the sequence * starting from 1. */ public static Sequence convertToSequence(List<ROI> inputRois, int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT, DataType dataType, boolean label) { final List<ROI> rois = new ArrayList<ROI>(); final Rectangle5D bounds = new Rectangle5D.Double(); try { // compute the union of all ROI final ROI roi = ROIUtil.merge(inputRois, BooleanOperator.OR); // get bounds of result bounds.add(roi.getBounds5D()); // add this single ROI to list rois.add(roi); } catch (Exception e) { for (ROI roi : inputRois) { // compute global bounds if (roi != null) { bounds.add(roi.getBounds5D()); rois.add(roi); } } } int sX = sizeX; int sY = sizeY; int sC = sizeC; int sZ = sizeZ; int sT = sizeT; if (sX == 0) sX = (int) bounds.getSizeX(); if (sY == 0) sY = (int) bounds.getSizeY(); if (sC == 0) sC = (bounds.isInfiniteC() ? 1 : (int) bounds.getSizeC()); if (sZ == 0) sZ = (bounds.isInfiniteZ() ? 1 : (int) bounds.getSizeZ()); if (sT == 0) sT = (bounds.isInfiniteT() ? 1 : (int) bounds.getSizeT()); // empty base dimension and empty result --> generate a empty 320x240 image if (sX == 0) sX = 320; if (sY == 0) sY = 240; if (sC == 0) sC = 1; if (sZ == 0) sZ = 1; if (sT == 0) sT = 1; final Sequence out = new Sequence("ROI conversion"); out.beginUpdate(); try { for (int t = 0; t < sT; t++) for (int z = 0; z < sZ; z++) out.setImage(t, z, new IcyBufferedImage(sX, sY, sC, dataType)); double fillValue = 1d; // set value from ROI(s) for (ROI roi : rois) { if (!roi.getBounds5D().isEmpty()) DataIteratorUtil.set(new SequenceDataIterator(out, roi), fillValue); if (label) fillValue += 1d; } // notify data changed out.dataChanged(); } finally { out.endUpdate(); } return out; } /** * Convert a list of ROI into a binary / labeled Sequence. * * @param inputRois * list of ROI to convert * @param sequence * the sequence used to define the wanted sequence dimension in return.<br> * If this field is <code>null</code> then the global ROI bounds will be used to define the Sequence * dimension * @param label * if set to <code>true</code> then each ROI will be draw as a separate label (value) in the sequence * starting from 1. */ public static Sequence convertToSequence(List<ROI> inputRois, Sequence sequence, boolean label) { if (sequence == null) return convertToSequence(inputRois, 0, 0, 0, 0, 0, label ? ((inputRois.size() > 255) ? DataType.USHORT : DataType.UBYTE) : DataType.UBYTE, label); return convertToSequence(inputRois, sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(), sequence.getSizeZ(), sequence.getSizeT(), sequence.getDataType_(), label); } /** * Convert a single ROI into a binary / labeled Sequence. * * @param inputRoi * ROI to convert * @param sequence * the sequence used to define the wanted sequence dimension in return.<br> * If this field is <code>null</code> then the global ROI bounds will be used to define the Sequence * dimension */ public static Sequence convertToSequence(ROI inputRoi, Sequence sequence) { return convertToSequence(CollectionUtil.createArrayList(inputRoi), sequence, false); } /** * Copy properties (name, color...) from <code>source</code> ROI and apply it to <code>destination</code> ROI. */ public static void copyROIProperties(ROI source, ROI destination, boolean copyName) { if ((source == null) || (destination == null)) return; if (copyName) destination.setName(source.getName()); destination.setColor(source.getColor()); destination.setOpacity(source.getOpacity()); destination.setStroke(source.getStroke()); destination.setReadOnly(source.isReadOnly()); destination.setSelected(source.isSelected()); } }