diff --git a/pom.xml b/pom.xml index 07534cb08f3c819c6ce972a35d7a8b3c738d2bcb..061aebae96da6f0c541782c16fff159f0be21308 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ </parent> <artifactId>connected-components</artifactId> - <version>4.8.9</version> + <version>5.0.0</version> <packaging>jar</packaging> diff --git a/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponent.java b/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponent.java index fe242f983ab038d1eb57080429822b3ac956a2ef..dfa7b47030215f8e49c7c3faae7119e5b189cbdd 100644 --- a/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponent.java +++ b/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponent.java @@ -1,12 +1,22 @@ -package plugins.adufour.connectedcomponents; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; +/* + * Copyright (c) 2010-2023. 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 <https://www.gnu.org/licenses/>. + */ -import javax.vecmath.Point3d; -import javax.vecmath.Point3i; -import javax.vecmath.Vector3d; +package plugins.adufour.connectedcomponents; import icy.image.IcyBufferedImage; import icy.roi.ROI; @@ -19,6 +29,13 @@ import plugins.kernel.roi.roi2d.ROI2DArea; import plugins.kernel.roi.roi3d.ROI3DArea; import plugins.nchenouard.spot.Detection; +import javax.vecmath.Point3d; +import javax.vecmath.Point3i; +import javax.vecmath.Vector3d; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; + public class ConnectedComponent extends Detection implements Iterable<Point3i> { private int c = -1; @@ -45,7 +62,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { private final ArrayList<Point3i> points; /** - * the array of contour points of this object + * the array of this object contour points */ private Point3i[] contourPoints; @@ -62,13 +79,13 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * * @param initialCapacity int */ - public ConnectedComponent(int initialCapacity) { + public ConnectedComponent(final int initialCapacity) { super(0, 0, 0, 0); this.points = new ArrayList<>(initialCapacity); coordsSum = new Point3d(); } - void addPointInternal(Point3i point) { + void addPointInternal(final Point3i point) { points.add(point); // accumulate coordinates to compute the mass center @@ -84,7 +101,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * * @param point the point to add */ - public void addPoint(Point3i point) { + public void addPoint(final Point3i point) { addPointInternal(point); updateDetectionCoords(); } @@ -95,7 +112,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * * @param point the point to add */ - void removePointInternal(Point3i point) { + void removePointInternal(final Point3i point) { if (!points.remove(point)) return; @@ -116,39 +133,26 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { } /** - * @return true is the object touches the image edge along X + * @return true, if the object touches the image edge along X */ public boolean isOnEdgeX() { return onEdgeX; } /** - * @return true is the object touches the image edge along Y + * @return true, if the object touches the image edge along Y */ public boolean isOnEdgeY() { return onEdgeY; } /** - * @return true is the object touches the image edge along Z (always true for 2D images) + * @return true, if the object touches the image edge along Z (always true for 2D images) */ public boolean isOnEdgeZ() { return onEdgeZ; } - /** - * Computes the bounding box of this component, and stores the result into the given arguments - * - * @param start the first corner of the bounding box in X-Y-Z order (Upper-Left hand-Top) - * @param end the second corner of the bounding box in X-Y-Z order (Lower-Right hand-Bottom) - * @deprecated Use {@link ConnectedComponentDescriptor#computeBoundingBox(ConnectedComponent, Point3i, Point3i)} - * instead - */ - @Deprecated - public void computeBoundingBox(Point3i start, Point3i end) { - shapeDescriptor.computeBoundingBox(this, start, end); - } - /** * Computes the average intensity of the component on each channel of the given sequence, at the * time point where this component was found @@ -156,7 +160,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param sequence Sequence * @return an array containing the average intensity of the component in each band */ - public double[] computeMeanIntensity(Sequence sequence) { + public double[] computeMeanIntensity(final Sequence sequence) { return computeMeanIntensity(sequence, t); } @@ -168,13 +172,13 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param t int * @return an array containing the average intensity of the component in each band */ - public double[] computeMeanIntensity(Sequence sequence, int t) { - double[] intensitySum = new double[sequence.getSizeC()]; + public double[] computeMeanIntensity(final Sequence sequence, final int t) { + final double[] intensitySum = new double[sequence.getSizeC()]; - for (Point3i point : points) { - int offsetXY = point.x + point.y * sequence.getSizeX(); + for (final Point3i point : points) { + final int offsetXY = point.x + point.y * sequence.getSizeX(); - Object dataCXY = sequence.getDataXYC(t, point.z); + final Object dataCXY = sequence.getDataXYC(t, point.z); for (int c = 0; c < intensitySum.length; c++) intensitySum[c] += Array1DUtil.getValue(((Object[]) dataCXY)[c], offsetXY, @@ -193,7 +197,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param sequence Sequence * @return an array containing the average intensity of the component in each band */ - public double[] computeMinIntensity(Sequence sequence) { + public double[] computeMinIntensity(final Sequence sequence) { return computeMinIntensity(sequence, t); } @@ -205,17 +209,17 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param t int * @return an array containing the average intensity of the component in each band */ - public double[] computeMinIntensity(Sequence sequence, int t) { - double[] minIntensity = new double[sequence.getSizeC()]; + public double[] computeMinIntensity(final Sequence sequence, final int t) { + final double[] minIntensity = new double[sequence.getSizeC()]; Arrays.fill(minIntensity, Double.MAX_VALUE); - for (Point3i point : points) { - int offsetXY = point.x + point.y * sequence.getSizeX(); + for (final Point3i point : points) { + final int offsetXY = point.x + point.y * sequence.getSizeX(); - Object dataCXY = sequence.getImage(t, point.z).getDataXYC(); + final Object dataCXY = sequence.getImage(t, point.z).getDataXYC(); for (int c = 0; c < minIntensity.length; c++) { - double val = Array1DUtil.getValue(((Object[]) dataCXY)[c], offsetXY, + final double val = Array1DUtil.getValue(((Object[]) dataCXY)[c], offsetXY, sequence.getDataType_().isSigned()); if (val < minIntensity[c]) minIntensity[c] = val; @@ -231,7 +235,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param sequence Sequence * @return an array containing the average intensity of the component in each band */ - public double[] computeMaxIntensity(Sequence sequence) { + public double[] computeMaxIntensity(final Sequence sequence) { return computeMaxIntensity(sequence, t); } @@ -243,16 +247,16 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param t int * @return an array containing the average intensity of the component in each band */ - public double[] computeMaxIntensity(Sequence sequence, int t) { - double[] maxIntensity = new double[sequence.getSizeC()]; + public double[] computeMaxIntensity(final Sequence sequence, final int t) { + final double[] maxIntensity = new double[sequence.getSizeC()]; - for (Point3i point : points) { - int offsetXY = point.x + point.y * sequence.getSizeX(); + for (final Point3i point : points) { + final int offsetXY = point.x + point.y * sequence.getSizeX(); - Object dataCXY = sequence.getImage(t, point.z).getDataXYC(); + final Object dataCXY = sequence.getImage(t, point.z).getDataXYC(); for (int c = 0; c < maxIntensity.length; c++) { - double val = Array1DUtil.getValue(((Object[]) dataCXY)[c], offsetXY, + final double val = Array1DUtil.getValue(((Object[]) dataCXY)[c], offsetXY, sequence.getDataType_().isSigned()); if (val > maxIntensity[c]) maxIntensity[c] = val; @@ -262,20 +266,20 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { } /** - * Computes the closest euclidean distance between any two points of this component and the + * Computes the closest Euclidean distance between any two points of this component and the * given component * * @param component the component to compute the distance to * @return double */ - public double distanceTo(ConnectedComponent component) { + public double distanceTo(final ConnectedComponent component) { double distance = Double.MAX_VALUE; - for (Point3i srcPt : this) { - Vector3d srcV = new Vector3d(srcPt.x, srcPt.y, srcPt.z); - for (Point3i dstPt : component) { - Vector3d dstV = new Vector3d(dstPt.x, dstPt.y, dstPt.z); + for (final Point3i srcPt : this) { + final Vector3d srcV = new Vector3d(srcPt.x, srcPt.y, srcPt.z); + for (final Point3i dstPt : component) { + final Vector3d dstV = new Vector3d(dstPt.x, dstPt.y, dstPt.z); dstV.sub(srcV); - double length = dstV.length(); + final double length = dstV.length(); if (length < distance) distance = length; } @@ -297,27 +301,28 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * Computes the major axis of the object, which is the vector between the two most distant * points of this component. Note that the result vector orientation is meaningless if the * component is spherical.<br> - * NOTE: this is an potentially expensive operation: O(N log N) + * NOTE: this is a potentially expensive operation: O(N log N) * * @return 3D Vector */ public Vector3d getMajorAxis() { double maxDist = 0; - Vector3d vector = new Vector3d(); - Point3d pid = new Point3d(), pjd = new Point3d(); + final Vector3d vector = new Vector3d(); + final Point3d pid = new Point3d(); + final Point3d pjd = new Point3d(); - int n = points.size(); + final int n = points.size(); for (int i = 0; i < n - 1; i++) { - Point3i pi = points.get(i); + final Point3i pi = points.get(i); pid.set(pi.x, pi.y, pi.z); for (int j = i + 1; j < n; j++) { - Point3i pj = points.get(j); + final Point3i pj = points.get(j); pjd.set(pj.x, pj.y, pj.z); // compare the squared distance to save the square root operation - double dist = pid.distanceSquared(pjd); + final double dist = pid.distanceSquared(pjd); if (dist > maxDist) { maxDist = dist; @@ -330,7 +335,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { } void updateDetectionCoords() { - double factor = 1.0 / getSize(); + final double factor = 1.0 / getSize(); x = coordsSum.x * factor; y = coordsSum.y * factor; z = coordsSum.z * factor; @@ -382,11 +387,11 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param point 3D Point * @return double */ - public double getMaxDistanceTo(Point3d point) { + public double getMaxDistanceTo(final Point3d point) { double maxDist = 0; - for (Point3i p : this) { - double dist = point.distance(new Point3d(p.x, p.y, p.z)); + for (final Point3i p : this) { + final double dist = point.distance(new Point3d(p.x, p.y, p.z)); if (dist > maxDist) maxDist = dist; } @@ -414,18 +419,19 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param outputSequence (set to null if not wanted) an output sequence to receive the extracted contour * @return An array containing all the contour pixels of this component */ - public Point3i[] getContourPoints(Sequence outputSequence) { + public Point3i[] getContourPoints(final Sequence outputSequence) { if (contourPoints == null) { - ArrayList<Point3i> list = new ArrayList<>(getSize() / 2); + final ArrayList<Point3i> list = new ArrayList<>(getSize() / 2); - Point3i min = new Point3i(), max = new Point3i(); + final Point3i min = new Point3i(); + final Point3i max = new Point3i(); shapeDescriptor.computeBoundingBox(this, min, max); - Sequence mask = toSequence(); + final Sequence mask = toSequence(); final boolean outputVolatile; - int w = mask.getSizeX(); - int h = mask.getSizeY(); - int d = mask.getSizeZ(); + final int w = mask.getSizeX(); + final int h = mask.getSizeY(); + final int d = mask.getSizeZ(); byte[][] outputMask = null; @@ -443,16 +449,16 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { else outputVolatile = false; - byte[][] mask_z_xy = mask.getDataXYZAsByte(0, 0); + final byte[][] mask_z_xy = mask.getDataXYZAsByte(0, 0); - Point3i localP = new Point3i(); + final Point3i localP = new Point3i(); if (min.z != max.z) { mainLoop: - for (Point3i p : points) { + for (final Point3i p : points) { localP.sub(p, min); - int xy = localP.y * w + localP.x; + final int xy = localP.y * w + localP.x; if (localP.x == 0 || localP.y == 0 || localP.x == w - 1 || localP.y == h - 1 || localP.z == 0 || localP.z == d - 1) { @@ -462,7 +468,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { continue; } - for (byte[] z : new byte[][]{mask_z_xy[localP.z - 1], mask_z_xy[localP.z], + for (final byte[] z : new byte[][]{mask_z_xy[localP.z - 1], mask_z_xy[localP.z], mask_z_xy[localP.z + 1]}) if (z[xy - w] == 0 || z[xy - 1] == 0 || z[xy + 1] == 0 || z[xy + w] == 0 || z[xy - w - 1] == 0 || z[xy - w + 1] == 0 || z[xy + w - 1] == 0 || z[xy + w + 1] == 0) { @@ -479,10 +485,10 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { } } else { - for (Point3i p : points) { + for (final Point3i p : points) { localP.sub(p, min); - int xy = localP.y * w + localP.x; + final int xy = localP.y * w + localP.x; if (localP.x == 0 || localP.y == 0 || localP.x == w - 1 || localP.y == h - 1) { list.add(p); @@ -491,7 +497,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { continue; } - byte[] z = mask_z_xy[localP.z]; + final byte[] z = mask_z_xy[localP.z]; if (z[xy - w] == 0 || z[xy - 1] == 0 || z[xy + 1] == 0 || z[xy + w] == 0) // || z[xy - w - 1] == 0 @@ -510,7 +516,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { if ((outputSequence != null) && outputVolatile) outputSequence.setVolatile(true); - contourPoints = list.toArray(new Point3i[list.size()]); + contourPoints = list.toArray(new Point3i[0]); } return contourPoints; @@ -521,12 +527,12 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @return true if this component intersects (e.g. has at least one voxel overlapping with) the * specified component */ - public boolean intersects(ConnectedComponent component) { - int thisSize = getSize(); - int componentSize = component.getSize(); + public boolean intersects(final ConnectedComponent component) { + final int thisSize = getSize(); + final int componentSize = component.getSize(); - Point3i thisPt = new Point3i(); - Point3i componentPt = new Point3i(); + final Point3i thisPt = new Point3i(); + final Point3i componentPt = new Point3i(); for (int i = 0; i < thisSize; i++) { thisPt.set(points.get(i)); @@ -546,12 +552,12 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param component ConnectedComponent * @return ConnectedComponent */ - public ConnectedComponent intersection(ConnectedComponent component) { - ConnectedComponent intersection = new ConnectedComponent(0); + public ConnectedComponent intersection(final ConnectedComponent component) { + final ConnectedComponent intersection = new ConnectedComponent(0); // construct the intersection - for (Point3i srcPt : this) - for (Point3i dstPt : component) { + for (final Point3i srcPt : this) + for (final Point3i dstPt : component) { if (srcPt.equals(dstPt)) { intersection.addPointInternal(srcPt); break; @@ -574,23 +580,23 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @param c int * @param value double */ - public void paintOnSequence(Sequence s, int t, int c, double value) throws InterruptedException { + public void paintOnSequence(final Sequence s, final int t, final int c, final double value) throws InterruptedException { DataIteratorUtil.set(new SequenceDataIterator(s, toROI(), true, -1, t, c), value); } - public void setC(int c) { + public void setC(final int c) { this.c = c; } /** - * @return a region of interest of type area representing this connected component + * @return a ROI of type area representing this connected component */ public ROI toROI() { int z = 0; - ROI3DArea area3D = new ROI3DArea(); + final ROI3DArea area3D = new ROI3DArea(); area3D.beginUpdate(); - for (Point3i pt : this) { + for (final Point3i pt : this) { z = pt.z; area3D.addPoint(pt.x, pt.y, pt.z); } @@ -601,7 +607,7 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { return area3D; // if there is only a single slice, z will tell us - ROI2DArea area2D = area3D.getSlice(z); + final ROI2DArea area2D = area3D.getSlice(z); area2D.setZ(z); return area2D; @@ -614,33 +620,35 @@ public class ConnectedComponent extends Detection implements Iterable<Point3i> { * @return Sequence */ public Sequence toSequence() { - Sequence seq = new Sequence(); + final Sequence seq = new Sequence(); // get the bounding box first - Point3i start = new Point3i(), end = new Point3i(); + final Point3i start = new Point3i(); + final Point3i end = new Point3i(); - computeBoundingBox(start, end); + shapeDescriptor.computeBoundingBox(this, start, end); // initialize the sequence - int depth = 1 + end.z - start.z; - int height = 1 + end.y - start.y; - int width = 1 + end.x - start.x; + final int depth = 1 + end.z - start.z; + final int height = 1 + end.y - start.y; + final int width = 1 + end.x - start.x; for (int z = 0; z < depth; z++) seq.setImage(0, z, new IcyBufferedImage(width, height, 1, DataType.UBYTE)); // force non volatile - boolean v = seq.isVolatile(); + final boolean v = seq.isVolatile(); seq.setVolatile(false); try { // fill up the created sequence - byte[][] z_xy = seq.getDataXYZAsByte(0, 0); + final byte[][] z_xy = seq.getDataXYZAsByte(0, 0); - for (Point3i point : points) + for (final Point3i point : points) z_xy[point.z - start.z][(point.y - start.y) * width + (point.x - start.x)] = (byte) 1; - } finally { + } + finally { // restore volatile state seq.setVolatile(v); } diff --git a/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponentDescriptor.java b/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponentDescriptor.java index 21b6d2aaa1f58f944c366b5b3acf02743a432e35..6c7a7fde7cf2bfed3917cbc817302d7245bd9537 100644 --- a/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponentDescriptor.java +++ b/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponentDescriptor.java @@ -1,15 +1,22 @@ -package plugins.adufour.connectedcomponents; - -import java.awt.geom.Point2D; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +/* + * Copyright (c) 2010-2023. 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 <https://www.gnu.org/licenses/>. + */ -import javax.vecmath.Point2d; -import javax.vecmath.Point3d; -import javax.vecmath.Point3i; -import javax.vecmath.SingularMatrixException; -import javax.vecmath.Vector3d; +package plugins.adufour.connectedcomponents; import Jama.EigenvalueDecomposition; import Jama.Matrix; @@ -25,6 +32,12 @@ import plugins.adufour.quickhull.QuickHull3D; import plugins.adufour.vars.lang.Var; import plugins.adufour.vars.lang.VarDouble; +import javax.vecmath.*; +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class ConnectedComponentDescriptor extends Plugin implements PluginBundled, Block { Var<ConnectedComponent> varCC = new Var<>("Connected component", ConnectedComponent.class); @@ -46,12 +59,12 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle } @Override - public void declareInput(VarList inputMap) { + public void declareInput(final VarList inputMap) { inputMap.add("component", varCC); } @Override - public void declareOutput(VarList outputMap) { + public void declareOutput(final VarList outputMap) { outputMap.add("perimeter", perimeter); outputMap.add("long diameter", longAxis); outputMap.add("short diameter", shortAxis); @@ -62,14 +75,14 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle @Override public void run() { - ConnectedComponent cc = varCC.getValue(); + final ConnectedComponent cc = varCC.getValue(); if (cc == null) return; perimeter.setValue(computePerimeter(cc, null, null)); - double[] ellipseDimensions = computeEllipseDimensions(cc); + final double[] ellipseDimensions = computeEllipseDimensions(cc); longAxis.setValue(ellipseDimensions[0] * 2); shortAxis.setValue(ellipseDimensions[1] * 2); @@ -83,12 +96,12 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * @return a triplet representing the radius of the best fitting ellipse (the third value is 0 * for 2D objects) */ - public double[] computeEllipseDimensions(ConnectedComponent cc) { - double[] axes = new double[3]; + public double[] computeEllipseDimensions(final ConnectedComponent cc) { + final double[] axes = new double[3]; try { if (is2D(cc)) { - Point2d radii = new Point2d(); + final Point2d radii = new Point2d(); computeEllipse(cc, null, radii, null, null); if (radii.x > radii.y) { axes[0] = radii.x; @@ -100,14 +113,14 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle } } else { - Point3d radii = new Point3d(); + final Point3d radii = new Point3d(); computeEllipse(cc, null, radii, null, null); axes[0] = radii.x; axes[1] = radii.y; axes[2] = radii.z; } } - catch (Exception e) { + catch (final Exception e) { // Do nothing } @@ -121,7 +134,7 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * @param minBounds the first corner of the bounding box in X-Y-Z order (Upper-Left hand-Top) * @param maxBounds the second corner of the bounding box in X-Y-Z order (Lower-Right hand-Bottom) */ - public void computeBoundingBox(ConnectedComponent cc, Point3i minBounds, Point3i maxBounds) { + public void computeBoundingBox(final ConnectedComponent cc, final Point3i minBounds, final Point3i maxBounds) { if (minBounds != null) { minBounds.x = Integer.MAX_VALUE; minBounds.y = Integer.MAX_VALUE; @@ -134,7 +147,7 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle maxBounds.z = 0; } - for (Point3i point : cc) { + for (final Point3i point : cc) { if (minBounds != null) { minBounds.x = Math.min(minBounds.x, point.x); minBounds.y = Math.min(minBounds.y, point.y); @@ -155,7 +168,7 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * @param bsCenter the computed center of the bounding sphere * @param bsRadius the computed radius of the bounding sphere */ - public void computeBoundingSphere(ConnectedComponent cc, Point3d bsCenter, VarDouble bsRadius) { + public void computeBoundingSphere(final ConnectedComponent cc, final Point3d bsCenter, final VarDouble bsRadius) { bsCenter.set(cc.getMassCenter()); bsRadius.setValue(cc.getMaxDistanceTo(bsCenter)); } @@ -168,20 +181,21 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * extracted contour * @return The 3D perimeter (or 3D surface) of this component */ - public double computePerimeter(ConnectedComponent cc, ArrayList<Point3i> contourPoints, Sequence outputSequence) { + public double computePerimeter(final ConnectedComponent cc, final ArrayList<Point3i> contourPoints, final Sequence outputSequence) { double perimeter = 0; if (contourPoints != null) contourPoints.ensureCapacity(cc.getSize() / 2); - Point3i min = new Point3i(), max = new Point3i(); + final Point3i min = new Point3i(); + final Point3i max = new Point3i(); computeBoundingBox(cc, min, max); - Sequence mask = cc.toSequence(); + final Sequence mask = cc.toSequence(); final boolean outputVolatile; - int w = mask.getSizeX(); - int h = mask.getSizeY(); - int d = mask.getSizeZ(); + final int w = mask.getSizeX(); + final int h = mask.getSizeY(); + final int d = mask.getSizeZ(); byte[][] outputMask = null; @@ -199,19 +213,19 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle else outputVolatile = false; - byte[][] mask_z_xy = mask.getDataXYZAsByte(0, 0); + final byte[][] mask_z_xy = mask.getDataXYZAsByte(0, 0); - Point3i localP = new Point3i(); + final Point3i localP = new Point3i(); // count the edges and corners in 2D/3D double a = 0, b = 0; - for (Point3i p : cc) { + for (final Point3i p : cc) { localP.sub(p, min); - int xy = localP.y * w + localP.x; + final int xy = localP.y * w + localP.x; - byte[] z = mask_z_xy[localP.z]; + final byte[] z = mask_z_xy[localP.z]; int nbEdges = 0; @@ -277,11 +291,11 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * @param cc the input component * @return 1 for a perfect circle (or sphere), and lower than 1 otherwise */ - public double computeSphericity(ConnectedComponent cc) { - double dim = is2D(cc) ? 2.0 : 3.0; + public double computeSphericity(final ConnectedComponent cc) { + final double dim = is2D(cc) ? 2.0 : 3.0; - double area = cc.getSize(); - double peri = computePerimeter(cc, null, null); + final double area = cc.getSize(); + final double peri = computePerimeter(cc, null, null); // some verification code // @@ -298,7 +312,7 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle // // end of the verification code - double sph = (Math.pow(Math.PI, 1.0 / dim) / peri) * Math.pow(area * dim * 2, (dim - 1) / dim); + final double sph = (Math.pow(Math.PI, 1.0 / dim) / peri) * Math.pow(area * dim * 2, (dim - 1) / dim); // adjust final rounding off errors (sphericity is always below 1) return Math.min(1.0, sph); @@ -313,25 +327,25 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * @return the ratio b/a, where a and b are the two first largest ellipse radii (there are only * two in 2D) */ - public double computeEccentricity(ConnectedComponent cc) { + public double computeEccentricity(final ConnectedComponent cc) { if (is2D(cc)) { try { - Point2d radii = new Point2d(); + final Point2d radii = new Point2d(); computeEllipse(cc, null, radii, null, null); return radii.x / radii.y; } - catch (RuntimeException e) { + catch (final RuntimeException e) { // error during the ellipse computation return Double.NaN; } } else { - Point3d radii = new Point3d(); + final Point3d radii = new Point3d(); try { computeEllipse(cc, null, radii, null, null); return radii.x / radii.y; } - catch (Exception e) { + catch (final Exception e) { return Double.NaN; } } @@ -342,8 +356,8 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * @return The hull ratio, measured as the ratio between the object volume and its convex hull * (envelope) */ - public double computeHullRatio(ConnectedComponent cc) { - double hull = computeConvexAreaAndVolume(cc)[1]; + public double computeHullRatio(final ConnectedComponent cc) { + final double hull = computeConvexAreaAndVolume(cc)[1]; return hull == 0.0 ? 0.0 : Math.min(1.0, cc.getSize() / hull); } @@ -354,8 +368,9 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * object. The 2 values are returned together because their computation is simultaneous * (in the 3D case only) */ - public double[] computeConvexAreaAndVolume(ConnectedComponent cc) { - int i = 0, n = cc.getSize(); + public double[] computeConvexAreaAndVolume(final ConnectedComponent cc) { + int i = 0; + final int n = cc.getSize(); if (n == 1) return new double[]{0.0, 1.0}; @@ -365,7 +380,7 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle if (is2D(cc)) { List<Point2D> points = new ArrayList<>(); - for (Point3i p : cc) + for (final Point3i p : cc) points.add(new Point2D.Double(p.x, p.y)); if (points.size() > 4) @@ -387,23 +402,23 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle } else try { - Point3d[] points = new Point3d[n]; + final Point3d[] points = new Point3d[n]; - for (Point3i p : cc) + for (final Point3i p : cc) points[i++] = new Point3d(p.x, p.y, p.z); - QuickHull3D qhull = new QuickHull3D(points); - int[][] hullFaces = qhull.getFaces(); - Point3d[] hullPoints = qhull.getVertices(); + final QuickHull3D qhull = new QuickHull3D(points); + final int[][] hullFaces = qhull.getFaces(); + final Point3d[] hullPoints = qhull.getVertices(); - Vector3d v12 = new Vector3d(); - Vector3d v13 = new Vector3d(); - Vector3d cross = new Vector3d(); + final Vector3d v12 = new Vector3d(); + final Vector3d v13 = new Vector3d(); + final Vector3d cross = new Vector3d(); - for (int[] face : hullFaces) { - Point3d p1 = hullPoints[face[0]]; - Point3d p2 = hullPoints[face[1]]; - Point3d p3 = hullPoints[face[2]]; + for (final int[] face : hullFaces) { + final Point3d p1 = hullPoints[face[0]]; + final Point3d p2 = hullPoints[face[1]]; + final Point3d p3 = hullPoints[face[2]]; v12.sub(p2, p1); v13.sub(p3, p1); @@ -415,7 +430,7 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle area += contour * cross.x * (p1.x + p2.x + p3.x); } } - catch (IllegalArgumentException e) { + catch (final IllegalArgumentException e) { // less than 4 points, or coplanarity detected return new double[]{n, n}; } @@ -432,17 +447,17 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * @param r the moment order along Z (set to 0 if the object is 2D) * @return the geometric moment */ - public double computeGeometricMoment(ConnectedComponent cc, int p, int q, int r) { + public double computeGeometricMoment(final ConnectedComponent cc, final int p, final int q, final int r) { double moment = 0; - Point3d center = cc.getMassCenter(); + final Point3d center = cc.getMassCenter(); if (is2D(cc)) { - for (Point3i point : cc) + for (final Point3i point : cc) moment += Math.pow(point.x - center.x, p) * Math.pow(point.y - center.y, q); } else { - for (Point3i point : cc) + for (final Point3i point : cc) moment += Math.pow(point.x - center.x, p) * Math.pow(point.y - center.y, q) * Math.pow(point.z - center.z, r); } @@ -461,22 +476,22 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * @param equation (set to null if not wanted) an array of size 9 containing the calculated ellipsoid * equation * @throws IllegalArgumentException if the number of points in the component is too low (minimum is 9) - * @throws SingularMatrixException if the component is flat (i.e. lies in a 2D plane) + * @throws SingularMatrixException if the component is flat (i.e., lies in a 2D plane) */ - public void computeEllipse(ConnectedComponent cc, Point3d center, Point3d radii, Vector3d[] eigenVectors, - double[] equation) throws IllegalArgumentException { - int nPoints = cc.getSize(); + public void computeEllipse(final ConnectedComponent cc, final Point3d center, final Point3d radii, final Vector3d[] eigenVectors, + final double[] equation) throws IllegalArgumentException { + final int nPoints = cc.getSize(); if (nPoints < 9) { throw new IllegalArgumentException("Too few points; need at least 9 to calculate a unique ellipsoid"); } - Point3i[] points = cc.getPoints(); + final Point3i[] points = cc.getPoints(); - double[][] d = new double[nPoints][9]; + final double[][] d = new double[nPoints][9]; for (int i = 0; i < nPoints; i++) { - double x = points[i].x; - double y = points[i].y; - double z = points[i].z; + final double x = points[i].x; + final double y = points[i].y; + final double z = points[i].z; d[i][0] = (x * x); d[i][1] = (y * y); d[i][2] = (z * z); @@ -488,32 +503,32 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle d[i][8] = (2.0D * z); } - Matrix D = new Matrix(d); - Matrix ones = ones(nPoints, 1); + final Matrix D = new Matrix(d); + final Matrix ones = ones(nPoints, 1); - Matrix V; + final Matrix V; try { V = D.transpose().times(D).inverse().times(D.transpose().times(ones)); } - catch (RuntimeException e) { + catch (final RuntimeException e) { throw new SingularMatrixException("The component is most probably flat (i.e. lies in a 2D plane)"); } - double[] v = V.getColumnPackedCopy(); + final double[] v = V.getColumnPackedCopy(); - double[][] a = {{v[0], v[3], v[4], v[6]}, {v[3], v[1], v[5], v[7]}, {v[4], v[5], v[2], v[8]}, + final double[][] a = {{v[0], v[3], v[4], v[6]}, {v[3], v[1], v[5], v[7]}, {v[4], v[5], v[2], v[8]}, {v[6], v[7], v[8], -1.0D}}; - Matrix A = new Matrix(a); - Matrix C = A.getMatrix(0, 2, 0, 2).times(-1.0D).inverse().times(V.getMatrix(6, 8, 0, 0)); - Matrix T = Matrix.identity(4, 4); + final Matrix A = new Matrix(a); + final Matrix C = A.getMatrix(0, 2, 0, 2).times(-1.0D).inverse().times(V.getMatrix(6, 8, 0, 0)); + final Matrix T = Matrix.identity(4, 4); T.setMatrix(3, 3, 0, 2, C.transpose()); - Matrix R = T.times(A.times(T.transpose())); - double r33 = R.get(3, 3); - Matrix R02 = R.getMatrix(0, 2, 0, 2); - EigenvalueDecomposition E = new EigenvalueDecomposition(R02.times(-1.0D / r33)); - Matrix eVal = E.getD(); - Matrix eVec = E.getV(); - Matrix diagonal = diag(eVal); + final Matrix R = T.times(A.times(T.transpose())); + final double r33 = R.get(3, 3); + final Matrix R02 = R.getMatrix(0, 2, 0, 2); + final EigenvalueDecomposition E = new EigenvalueDecomposition(R02.times(-1.0D / r33)); + final Matrix eVal = E.getD(); + final Matrix eVec = E.getV(); + final Matrix diagonal = diag(eVal); if (radii != null) radii.set(Math.sqrt(1.0D / diagonal.get(0, 0)), Math.sqrt(1.0D / diagonal.get(1, 0)), @@ -543,15 +558,15 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle * calculated algebraic parameters of the fitting ellipse: <i>ax</i><sup>2</sup> + 2 * <i>bxy</i> + <i>cy</i><sup>2</sup> +2<i>dx</i> + 2<i>fy</i> + <i>g</i> = 0. The * vector <b>A</b> represented in the array is normed, so that ||<b>A</b>||=1. - * @throws RuntimeException if the ellipse calculation fails (e.g. if a singular matrix is detected) + * @throws RuntimeException if the ellipse calculation fails (e.g., if a singular matrix is detected) */ - public void computeEllipse(ConnectedComponent cc, Point2d center, Point2d radii, VarDouble angle, double[] equation) + public void computeEllipse(final ConnectedComponent cc, final Point2d center, final Point2d radii, final VarDouble angle, final double[] equation) throws RuntimeException { - Point3i[] points = cc.getPoints(); - Point3d ccenter = cc.getMassCenter(); + final Point3i[] points = cc.getPoints(); + final Point3d ccenter = cc.getMassCenter(); - double[][] d1 = new double[cc.getSize()][3]; - double[][] d2 = new double[cc.getSize()][3]; + final double[][] d1 = new double[cc.getSize()][3]; + final double[][] d2 = new double[cc.getSize()][3]; for (int i = 0; i < d1.length; i++) { final double xixC = points[i].x - ccenter.x; @@ -566,33 +581,33 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle d2[i][2] = 1; } - Matrix D1 = new Matrix(d1); - Matrix D2 = new Matrix(d2); + final Matrix D1 = new Matrix(d1); + final Matrix D2 = new Matrix(d2); - Matrix S1 = D1.transpose().times(D1); + final Matrix S1 = D1.transpose().times(D1); - Matrix S2 = D1.transpose().times(D2); + final Matrix S2 = D1.transpose().times(D2); - Matrix S3 = D2.transpose().times(D2); + final Matrix S3 = D2.transpose().times(D2); - Matrix T = (S3.inverse().times(-1)).times(S2.transpose()); + final Matrix T = (S3.inverse().times(-1)).times(S2.transpose()); - Matrix M = S1.plus(S2.times(T)); + final Matrix M = S1.plus(S2.times(T)); - double[][] m = M.getArray(); - double[][] n = {{m[2][0] / 2, m[2][1] / 2, m[2][2] / 2}, {-m[1][0], -m[1][1], -m[1][2]}, + final double[][] m = M.getArray(); + final double[][] n = {{m[2][0] / 2, m[2][1] / 2, m[2][2] / 2}, {-m[1][0], -m[1][1], -m[1][2]}, {m[0][0] / 2, m[0][1] / 2, m[0][2] / 2}}; - Matrix N = new Matrix(n); + final Matrix N = new Matrix(n); - EigenvalueDecomposition E = N.eig(); - Matrix eVec = E.getV(); + final EigenvalueDecomposition E = N.eig(); + final Matrix eVec = E.getV(); - Matrix R1 = eVec.getMatrix(0, 0, 0, 2); - Matrix R2 = eVec.getMatrix(1, 1, 0, 2); - Matrix R3 = eVec.getMatrix(2, 2, 0, 2); + final Matrix R1 = eVec.getMatrix(0, 0, 0, 2); + final Matrix R2 = eVec.getMatrix(1, 1, 0, 2); + final Matrix R3 = eVec.getMatrix(2, 2, 0, 2); - Matrix cond = (R1.times(4)).arrayTimes(R3).minus(R2.arrayTimes(R2)); + final Matrix cond = (R1.times(4)).arrayTimes(R3).minus(R2.arrayTimes(R2)); int _f = 0; for (int i = 0; i < 3; i++) { @@ -601,16 +616,16 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle break; } } - Matrix A1 = eVec.getMatrix(0, 2, _f, _f); + final Matrix A1 = eVec.getMatrix(0, 2, _f, _f); Matrix A = new Matrix(6, 1); A.setMatrix(0, 2, 0, 0, A1); A.setMatrix(3, 5, 0, 0, T.times(A1)); double[] ell = A.getColumnPackedCopy(); - double a4 = ell[3] - 2 * ell[0] * ccenter.x - ell[1] * ccenter.y; - double a5 = ell[4] - 2 * ell[2] * ccenter.y - ell[1] * ccenter.x; - double a6 = ell[5] + ell[0] * ccenter.x * ccenter.x + ell[2] * ccenter.y * ccenter.y + final double a4 = ell[3] - 2 * ell[0] * ccenter.x - ell[1] * ccenter.y; + final double a5 = ell[4] - 2 * ell[2] * ccenter.y - ell[1] * ccenter.x; + final double a6 = ell[5] + ell[0] * ccenter.x * ccenter.x + ell[2] * ccenter.y * ccenter.y + ell[1] * ccenter.x * ccenter.y - ell[3] * ccenter.x - ell[4] * ccenter.y; A.set(3, 0, a4); A.set(4, 0, a5); @@ -669,26 +684,26 @@ public class ConnectedComponentDescriptor extends Plugin implements PluginBundle angle.setValue(phi); } - public boolean is2D(ConnectedComponent cc) { - Point3i minBB = new Point3i(); - Point3i maxBB = new Point3i(); + public boolean is2D(final ConnectedComponent cc) { + final Point3i minBB = new Point3i(); + final Point3i maxBB = new Point3i(); computeBoundingBox(cc, minBB, maxBB); return minBB.z == maxBB.z; } - private Matrix diag(Matrix matrix) { - int min = Math.min(matrix.getRowDimension(), matrix.getColumnDimension()); - double[][] diag = new double[min][1]; + private Matrix diag(final Matrix matrix) { + final int min = Math.min(matrix.getRowDimension(), matrix.getColumnDimension()); + final double[][] diag = new double[min][1]; for (int i = 0; i < min; i++) { diag[i][0] = matrix.get(i, i); } return new Matrix(diag); } - private Matrix ones(int m, int n) { - double[][] array = new double[m][n]; - for (double[] row : array) + private Matrix ones(final int m, final int n) { + final double[][] array = new double[m][n]; + for (final double[] row : array) Arrays.fill(row, 1.0); return new Matrix(array, m, n); } diff --git a/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponents.java b/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponents.java index cc010e02f83e48fd49ad57dd26a145412bad063a..baa1be66e19d65f8cec8b8421c9d65bfbd72f340 100644 --- a/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponents.java +++ b/src/main/java/plugins/adufour/connectedcomponents/ConnectedComponents.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2010-2023. 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 <https://www.gnu.org/licenses/>. + */ + package plugins.adufour.connectedcomponents; import java.io.File; @@ -97,7 +115,7 @@ public class ConnectedComponents extends EzPlug implements Block { */ public final Comparator<ConnectedComponent> comparator; - Sorting(Comparator<ConnectedComponent> comparator) { + Sorting(final Comparator<ConnectedComponent> comparator) { this.comparator = comparator; } } @@ -141,7 +159,7 @@ public class ConnectedComponents extends EzPlug implements Block { addEzComponent(input); input.addVarChangeListener((source, newValue) -> { if (newValue != null) { - boolean doZ = newValue.getSizeZ() > 1; + final boolean doZ = newValue.getSizeZ() > 1; discardEdgesZ.setValue(doZ); discardEdgesZ.setVisible(doZ); } @@ -192,7 +210,7 @@ public class ConnectedComponents extends EzPlug implements Block { } @Override - public void declareInput(VarList inputMap) { + public void declareInput(final VarList inputMap) { inputMap.add("input", input.getVariable()); inputMap.add("method", extractionMethod.getVariable()); inputMap.add("value", background.getVariable()); @@ -207,7 +225,7 @@ public class ConnectedComponents extends EzPlug implements Block { } @Override - public void declareOutput(VarList outputMap) { + public void declareOutput(final VarList outputMap) { outputMap.add("labeled sequence", outputSequence); outputMap.add("objects", outputCCs); outputMap.add("list of extracted ROI", outputROIs); @@ -215,34 +233,34 @@ public class ConnectedComponents extends EzPlug implements Block { @Override protected void execute() { - ArrayList<ConnectedComponent> componentsList = new ArrayList<>(); - Map<Integer, List<ConnectedComponent>> componentsMap; + final ArrayList<ConnectedComponent> componentsList = new ArrayList<>(); + final Map<Integer, List<ConnectedComponent>> componentsMap; - int min = boundSize.getValue() ? minSize.getValue() : 0; - int max = boundSize.getValue() ? maxSize.getValue() : Integer.MAX_VALUE; + final int min = boundSize.getValue() ? minSize.getValue() : 0; + final int max = boundSize.getValue() ? maxSize.getValue() : Integer.MAX_VALUE; - Sequence output = new Sequence(); + final Sequence output = new Sequence(); - Sequence inputSequence = input.getValue(true); + final Sequence inputSequence = input.getValue(true); if (inputSequence.getSizeT() == 0) throw new VarException(input.getVariable(), "Cannot extract connected components from an emtpy sequence !"); // never remove objects touching the Z edge in 2D (that would remove... everything!) - boolean discardEdgesAlongZ = (inputSequence.getSizeZ() > 1 && discardEdgesZ.getValue()); + final boolean discardEdgesAlongZ = (inputSequence.getSizeZ() > 1 && discardEdgesZ.getValue()); try { if (extractionMethod.getValue() == ExtractionType.ROI) { - int width = inputSequence.getWidth(); - int height = inputSequence.getHeight(); + final int width = inputSequence.getWidth(); + final int height = inputSequence.getHeight(); //Sequence labeledSequence = new Sequence(inputSequence.getMetadata()); - Sequence labeledSequence = new Sequence(inputSequence.getOMEXMLMetadata()); + final Sequence labeledSequence = new Sequence(inputSequence.getOMEXMLMetadata()); for (int t = 0; t < inputSequence.getSizeT(); t++) for (int z = 0; z < inputSequence.getSizeZ(); z++) labeledSequence.setImage(t, z, new IcyBufferedImage(width, height, 1, DataType.USHORT)); short cpt = 1; - for (ROI roi : inputSequence.getROIs()) + for (final ROI roi : inputSequence.getROIs()) DataIteratorUtil.set(new SequenceDataIterator(labeledSequence, roi, true), cpt++ & 0xffff); componentsMap = extractConnectedComponents(labeledSequence, 0, ExtractionType.BACKGROUND_LABELED, @@ -254,7 +272,7 @@ public class ConnectedComponents extends EzPlug implements Block { discardEdgesAlongZ, min, max, output); } } - catch (InterruptedException e) { + catch (final InterruptedException e) { new FailedAnnounceFrame(e.getMessage()); return; } @@ -262,7 +280,7 @@ public class ConnectedComponents extends EzPlug implements Block { outputSequence.setValue(output); int nbObjects = 0; - for (List<ConnectedComponent> ccs : componentsMap.values()) { + for (final List<ConnectedComponent> ccs : componentsMap.values()) { nbObjects += ccs.size(); componentsList.addAll(ccs); } @@ -280,58 +298,58 @@ public class ConnectedComponents extends EzPlug implements Block { } if (exportSwPool.getValue()) { - DetectionResult result = convertToDetectionResult(componentsMap, input.getValue()); - SwimmingObject object = new SwimmingObject(result, "Set of " + nbObjects + " connected components"); + final DetectionResult result = convertToDetectionResult(componentsMap, input.getValue()); + final SwimmingObject object = new SwimmingObject(result, "Set of " + nbObjects + " connected components"); Icy.getMainInterface().getSwimmingPool().add(object); } if (exportROI.getValue() || outputROIs.isReferenced()) { if (output.getSizeZ() > 1) { - ArrayList<ROI3DArea> rois = new ArrayList<>(componentsMap.size()); + final ArrayList<ROI3DArea> rois = new ArrayList<>(componentsMap.size()); int ccID = 1; - for (List<ConnectedComponent> ccs : componentsMap.values()) - for (ConnectedComponent cc : ccs) { - ROI3DArea area = new ROI3DArea(); + for (final List<ConnectedComponent> ccs : componentsMap.values()) + for (final ConnectedComponent cc : ccs) { + final ROI3DArea area = new ROI3DArea(); area.setName("Object #" + ccID++); area.beginUpdate(); - for (Point3i pt : cc) + for (final Point3i pt : cc) area.addPoint(pt.x, pt.y, pt.z); area.setT(cc.getT()); area.endUpdate(); rois.add(area); } - outputROIs.setValue(rois.toArray(new ROI3DArea[rois.size()])); + outputROIs.setValue(rois.toArray(new ROI3DArea[0])); } else { - ArrayList<ROI2DArea> rois = new ArrayList<>(componentsMap.size()); + final ArrayList<ROI2DArea> rois = new ArrayList<>(componentsMap.size()); - for (List<ConnectedComponent> ccs : componentsMap.values()) - for (ConnectedComponent cc : ccs) { - ROI2DArea area = new ROI2DArea(); + for (final List<ConnectedComponent> ccs : componentsMap.values()) + for (final ConnectedComponent cc : ccs) { + final ROI2DArea area = new ROI2DArea(); area.beginUpdate(); - for (Point3i pt : cc) + for (final Point3i pt : cc) area.addPoint(pt.x, pt.y); area.setT(cc.getT()); area.endUpdate(); rois.add(area); } - outputROIs.setValue(rois.toArray(new ROI2DArea[rois.size()])); + outputROIs.setValue(rois.toArray(new ROI2DArea[0])); } if (exportROI.getValue()) { // replace all ROI on the input by the new ones - Sequence in = input.getValue(); + final Sequence in = input.getValue(); in.beginUpdate(); - for (ROI roi : input.getValue().getROIs()) + for (final ROI roi : input.getValue().getROIs()) in.removeROI(roi); - for (ROI roi : outputROIs.getValue()) + for (final ROI roi : outputROIs.getValue()) in.addROI(roi); in.endUpdate(); @@ -342,7 +360,7 @@ public class ConnectedComponents extends EzPlug implements Block { int page = 1; WritableSheet sheet; - WritableWorkbook workbook; + final WritableWorkbook workbook; try { File f = exportExcelFile.getValue(true); @@ -351,14 +369,14 @@ public class ConnectedComponents extends EzPlug implements Block { workbook = XLSUtil.createWorkbook(f); sheet = XLSUtil.createNewPage(workbook, "Page " + page); } - catch (Exception e) { + catch (final Exception e) { throw new IcyHandledException(e.getMessage()); } - Sequence s = input.getValue(); - Point4d resolution = new Point4d(s.getPixelSizeX(), s.getPixelSizeY(), s.getPixelSizeZ(), + final Sequence s = input.getValue(); + final Point4d resolution = new Point4d(s.getPixelSizeX(), s.getPixelSizeY(), s.getPixelSizeZ(), s.getTimeInterval()); - double voxelSize = resolution.x * resolution.y * resolution.z; + final double voxelSize = resolution.x * resolution.y * resolution.z; String res = "Sequence resolution:"; res += " X=" + StringUtil.toStringEx(resolution.x, 5); res += ", Y=" + StringUtil.toStringEx(resolution.y, 5); @@ -397,12 +415,12 @@ public class ConnectedComponents extends EzPlug implements Block { XLSUtil.setCellString(sheet, 27, 1, "convex perimeter"); XLSUtil.setCellString(sheet, 28, 1, "convex volume"); - ConnectedComponentDescriptor shapeDescriptor = new ConnectedComponentDescriptor(); + final ConnectedComponentDescriptor shapeDescriptor = new ConnectedComponentDescriptor(); int cpt = 2; - for (Integer time : componentsMap.keySet()) - for (ConnectedComponent cc : componentsMap.get(time)) { - boolean is2D = shapeDescriptor.is2D(cc); - Point3d center = cc.getMassCenter(); + for (final Integer time : componentsMap.keySet()) + for (final ConnectedComponent cc : componentsMap.get(time)) { + final boolean is2D = shapeDescriptor.is2D(cc); + final Point3d center = cc.getMassCenter(); center.x *= resolution.x; center.y *= resolution.y; center.z *= resolution.z; @@ -414,13 +432,13 @@ public class ConnectedComponents extends EzPlug implements Block { XLSUtil.setCellNumber(sheet, 5, cpt, shapeDescriptor.computePerimeter(cc, null, null)); XLSUtil.setCellNumber(sheet, 6, cpt, cc.getSize() * voxelSize); XLSUtil.setCellNumber(sheet, 7, cpt, shapeDescriptor.computeSphericity(cc)); - double[] radiuses = shapeDescriptor.computeEllipseDimensions(cc); + final double[] radiuses = shapeDescriptor.computeEllipseDimensions(cc); XLSUtil.setCellNumber(sheet, 8, cpt, radiuses[0]); XLSUtil.setCellNumber(sheet, 9, cpt, radiuses[1]); XLSUtil.setCellNumber(sheet, 10, cpt, radiuses[2]); XLSUtil.setCellNumber(sheet, 11, cpt, shapeDescriptor.computeEccentricity(cc)); - double[] contour_area = shapeDescriptor.computeConvexAreaAndVolume(cc); + final double[] contour_area = shapeDescriptor.computeConvexAreaAndVolume(cc); XLSUtil.setCellNumber(sheet, 12, cpt, contour_area[1] == 0.0 ? 0.0 : Math.min(1.0, cc.getSize() / contour_area[1])); XLSUtil.setCellNumber(sheet, 13, cpt, shapeDescriptor.computeGeometricMoment(cc, 1, 0, 0)); XLSUtil.setCellNumber(sheet, 14, cpt, shapeDescriptor.computeGeometricMoment(cc, 0, 1, 0)); @@ -469,7 +487,7 @@ public class ConnectedComponents extends EzPlug implements Block { try { XLSUtil.saveAndClose(workbook); } - catch (Exception e) { + catch (final Exception e) { throw new IcyHandledException(e.getMessage()); } } @@ -504,10 +522,10 @@ public class ConnectedComponents extends EzPlug implements Block { * Creates a new label with the given value. If no parent is set to this label, the given * value will be the final one * - * @param value the pixel value - * @param label the label value + * @param value the pixel value + * @param label the label value */ - Label(double value, int label) { + Label(final double value, final int label) { this.imageValue = value; this.targetLabelValue = label; } @@ -532,7 +550,7 @@ public class ConnectedComponents extends EzPlug implements Block { * @see ExtractionType * @see ConnectedComponent */ - public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents(Sequence inputSequence, Sequence labeledSequence) { + public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents(final Sequence inputSequence, final Sequence labeledSequence) { return extractConnectedComponents(inputSequence, false, 0, Integer.MAX_VALUE, labeledSequence); } @@ -550,7 +568,7 @@ public class ConnectedComponents extends EzPlug implements Block { * @see ExtractionType * @see ConnectedComponent */ - public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents(Sequence inputSequence, int minSize, int maxSize, Sequence labeledSequence) { + public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents(final Sequence inputSequence, final int minSize, final int maxSize, final Sequence labeledSequence) { return extractConnectedComponents(inputSequence, false, minSize, maxSize, labeledSequence); } @@ -569,7 +587,7 @@ public class ConnectedComponents extends EzPlug implements Block { * @see ExtractionType * @see ConnectedComponent */ - public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents(Sequence inputSequence, boolean isInputLabeled, int minSize, int maxSize, Sequence labeledSequence) { + public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents(final Sequence inputSequence, final boolean isInputLabeled, final int minSize, final int maxSize, final Sequence labeledSequence) { return extractConnectedComponents(inputSequence, 0, isInputLabeled ? ExtractionType.BACKGROUND_LABELED : ExtractionType.BACKGROUND, minSize, maxSize, labeledSequence); @@ -591,7 +609,7 @@ public class ConnectedComponents extends EzPlug implements Block { * @see ExtractionType * @see ConnectedComponent */ - public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents(Sequence inputSequence, double value, ExtractionType type, int minSize, int maxSize, Sequence labeledSequence) { + public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents(final Sequence inputSequence, final double value, final ExtractionType type, final int minSize, final int maxSize, final Sequence labeledSequence) { return extractConnectedComponents(inputSequence, value, type, false, false, false, minSize, maxSize, labeledSequence); } @@ -618,31 +636,31 @@ public class ConnectedComponents extends EzPlug implements Block { * @see ConnectedComponent */ public static Map<Integer, List<ConnectedComponent>> extractConnectedComponents( - Sequence inputSequence, double value, ExtractionType type, boolean noEdgeX, boolean noEdgeY, boolean noEdgeZ, int minSize, int maxSize, Sequence labeledSequence + final Sequence inputSequence, final double value, final ExtractionType type, final boolean noEdgeX, final boolean noEdgeY, final boolean noEdgeZ, final int minSize, final int maxSize, Sequence labeledSequence ) { if (inputSequence == null || inputSequence.getSizeT() == 0) throw new IllegalArgumentException("Cannot extract connected components from an empty sequence !"); - int width = inputSequence.getSizeX(); - int height = inputSequence.getSizeY(); + final int width = inputSequence.getSizeX(); + final int height = inputSequence.getSizeY(); if (labeledSequence == null) labeledSequence = new Sequence(); - Map<Integer, List<ConnectedComponent>> componentsMap = new TreeMap<>(); + final Map<Integer, List<ConnectedComponent>> componentsMap = new TreeMap<>(); for (int t = 0; t < inputSequence.getSizeT(); t++) { for (int z = 0; z < inputSequence.getSizeZ(); z++) labeledSequence.setImage(t, z, new IcyBufferedImage(width, height, 1, DataType.UINT)); - VolumetricImage volIN = inputSequence.getVolumetricImage(t); - VolumetricImage volOUT = labeledSequence.getVolumetricImage(t); + final VolumetricImage volIN = inputSequence.getVolumetricImage(t); + final VolumetricImage volOUT = labeledSequence.getVolumetricImage(t); - List<ConnectedComponent> components = extractConnectedComponents(volIN, value, type, noEdgeX, noEdgeY, + final List<ConnectedComponent> components = extractConnectedComponents(volIN, value, type, noEdgeX, noEdgeY, noEdgeZ, minSize, maxSize, volOUT); // int cpt=1; - for (ConnectedComponent cc : components) { + for (final ConnectedComponent cc : components) { cc.setT(t); // System.out.println(t + "\t" + cpt++ + "\t" + (int)cc.getMassCenter().x + "\t" + // (int)cc.getMassCenter().y); @@ -679,7 +697,7 @@ public class ConnectedComponents extends EzPlug implements Block { * @see ConnectedComponent */ public static List<ConnectedComponent> extractConnectedComponents( - VolumetricImage stack, double value, ExtractionType type, boolean noEdgeX, boolean noEdgeY, boolean noEdgeZ, int minSize, int maxSize + final VolumetricImage stack, final double value, final ExtractionType type, final boolean noEdgeX, final boolean noEdgeY, final boolean noEdgeZ, final int minSize, final int maxSize ) { return extractConnectedComponents(stack, value, type, noEdgeX, noEdgeY, noEdgeZ, minSize, maxSize, new VolumetricImage()); } @@ -710,18 +728,18 @@ public class ConnectedComponents extends EzPlug implements Block { * @see ConnectedComponent */ public static List<ConnectedComponent> extractConnectedComponents( - VolumetricImage stack, double value, ExtractionType type, boolean noEdgeX, boolean noEdgeY, boolean noEdgeZ, int minSize, int maxSize, final VolumetricImage labeledStack + final VolumetricImage stack, final double value, final ExtractionType type, final boolean noEdgeX, final boolean noEdgeY, final boolean noEdgeZ, final int minSize, final int maxSize, final VolumetricImage labeledStack ) throws NullPointerException { - int width = stack.getFirstImage().getSizeX(); - int height = stack.getFirstImage().getSizeY(); - int depth = stack.getSize(); + final int width = stack.getFirstImage().getSizeX(); + final int height = stack.getFirstImage().getSizeY(); + final int depth = stack.getSize(); - int[] neighborLabelValues = new int[13]; + final int[] neighborLabelValues = new int[13]; int neighborhoodSize = 0; - boolean extractUserValue = (type == ExtractionType.VALUE); + final boolean extractUserValue = (type == ExtractionType.VALUE); - Label[] labels = new Label[width * height * depth]; + final Label[] labels = new Label[width * height * depth]; // first image pass: naive labeling with simple backward neighborhood @@ -736,14 +754,14 @@ public class ConnectedComponents extends EzPlug implements Block { final IcyBufferedImage currentSliceImage = labeledStack.getImage(z); // retrieve the direct pointer to the current slice - int[] _labelsInCurrentSlice = currentSliceImage.getDataXYAsInt(0); + final int[] _labelsInCurrentSlice = currentSliceImage.getDataXYAsInt(0); // retrieve a direct pointer to the previous slice - int[] _labelsInUpperSlice = (z == 0) ? null : labeledStack.getImage(z - 1).getDataXYAsInt(0); + final int[] _labelsInUpperSlice = (z == 0) ? null : labeledStack.getImage(z - 1).getDataXYAsInt(0); int voxelOffset = 0; - Object inputData = stack.getImage(z).getDataXY(0); - DataType dataType = stack.getImage(z).getDataType_(); + final Object inputData = stack.getImage(z).getDataXY(0); + final DataType dataType = stack.getImage(z).getDataType_(); for (int y = 0; y < height; y++) { onEdgeY = (y == 0 || y == height - 1); @@ -751,9 +769,9 @@ public class ConnectedComponents extends EzPlug implements Block { for (int x = 0; x < width; x++, voxelOffset++) { onEdgeX = (x == 0 || x == width - 1); - double pixelValue = Array1DUtil.getValue(inputData, voxelOffset, dataType); + final double pixelValue = Array1DUtil.getValue(inputData, voxelOffset, dataType); - boolean pixelEqualsUserValue = (pixelValue == value); + final boolean pixelEqualsUserValue = (pixelValue == value); // do not process the current pixel if: // - extractUserValue is true and pixelEqualsUserValue is false @@ -794,7 +812,7 @@ public class ConnectedComponents extends EzPlug implements Block { } } else { - int north = voxelOffset - width; + final int north = voxelOffset - width; if (x == 0) { // e n n @@ -830,7 +848,7 @@ public class ConnectedComponents extends EzPlug implements Block { } else { if (y == 0) { - int south = voxelOffset + width; + final int south = voxelOffset + width; if (x == 0) { // e e e | e e e @@ -871,7 +889,7 @@ public class ConnectedComponents extends EzPlug implements Block { } } else if (y == height - 1) { - int north = voxelOffset - width; + final int north = voxelOffset - width; if (x == 0) { // e n n | e n n @@ -919,8 +937,8 @@ public class ConnectedComponents extends EzPlug implements Block { } } else { - int north = voxelOffset - width; - int south = voxelOffset + width; + final int north = voxelOffset - width; + final int south = voxelOffset + width; if (x == 0) { // e n n | e n n @@ -938,8 +956,8 @@ public class ConnectedComponents extends EzPlug implements Block { neighborhoodSize = 8; } else if (x == width - 1) { - int northwest = north - 1; - int west = voxelOffset - 1; + final int northwest = north - 1; + final int west = voxelOffset - 1; // n n e | n n e // n n e | n x e @@ -957,11 +975,11 @@ public class ConnectedComponents extends EzPlug implements Block { neighborhoodSize = 9; } else { - int northwest = north - 1; - int west = voxelOffset - 1; - int northeast = north + 1; - int southwest = south - 1; - int southeast = south + 1; + final int northwest = north - 1; + final int west = voxelOffset - 1; + final int northeast = north + 1; + final int southwest = south - 1; + final int southeast = south + 1; // n n n | n n n // n n n | n x . @@ -994,7 +1012,7 @@ public class ConnectedComponents extends EzPlug implements Block { // and assign that minimum label right now for (int i = 0; i < neighborhoodSize; i++) { - int neighborLabelValue = neighborLabelValues[i]; + final int neighborLabelValue = neighborLabelValues[i]; // zero labels are not interesting... if (neighborLabelValue == 0) @@ -1027,19 +1045,19 @@ public class ConnectedComponents extends EzPlug implements Block { // -> change their value to currentVoxelLabelValue // -> change their target to currentVoxelLabel - Label currentVoxelLabel = labels[currentVoxelLabelValue]; + final Label currentVoxelLabel = labels[currentVoxelLabelValue]; for (int i = 0; i < neighborhoodSize; i++) { - int neighborLabelValue = neighborLabelValues[i]; + final int neighborLabelValue = neighborLabelValues[i]; if (neighborLabelValue > currentVoxelLabelValue) { - Label label = labels[neighborLabelValue]; + final Label label = labels[neighborLabelValue]; if (type == ExtractionType.BACKGROUND_LABELED && label.imageValue != pixelValue) continue; - int finalLabelValue = label.getFinalLabelValue(); - Label finalLabel = labels[finalLabelValue]; + final int finalLabelValue = label.getFinalLabelValue(); + final Label finalLabel = labels[finalLabelValue]; if (currentVoxelLabel.targetLabelValue == finalLabelValue) continue; @@ -1072,7 +1090,7 @@ public class ConnectedComponents extends EzPlug implements Block { // end of the first pass, all pixels have a label // (though might not be unique within a given component) - HashMap<Integer, ConnectedComponent> componentsMap = new HashMap<>(); + final HashMap<Integer, ConnectedComponent> componentsMap = new HashMap<>(); // fusion strategy: fuse higher labels with lower ones // "highestKnownLabel" holds the highest known label @@ -1081,14 +1099,14 @@ public class ConnectedComponents extends EzPlug implements Block { int finalLabel = 0; for (int labelValue = highestKnownLabel; labelValue > 0; labelValue--) { - Label label = labels[labelValue]; + final Label label = labels[labelValue]; - int targetLabelValue = label.targetLabelValue; + final int targetLabelValue = label.targetLabelValue; if (targetLabelValue < labelValue) { // label should be fused to targetLabel - Label targetLabel = labels[targetLabelValue]; + final Label targetLabel = labels[targetLabelValue]; // -> add label's size to targetLabel targetLabel.size += label.size; @@ -1126,7 +1144,7 @@ public class ConnectedComponents extends EzPlug implements Block { // -> add this label to the list of valid labels - ConnectedComponent component = new ConnectedComponent(label.size); + final ConnectedComponent component = new ConnectedComponent(label.size); component.onEdgeX = label.onEdgeX; component.onEdgeY = label.onEdgeY; component.onEdgeZ = label.onEdgeZ; @@ -1139,7 +1157,7 @@ public class ConnectedComponents extends EzPlug implements Block { for (int z = 0; z < depth; z++) { final IcyBufferedImage sliceImage = labeledStack.getImage(z); - int[] _outputSlice = sliceImage.getDataXYAsInt(0); + final int[] _outputSlice = sliceImage.getDataXYAsInt(0); int pixelOffset = 0; @@ -1171,13 +1189,13 @@ public class ConnectedComponents extends EzPlug implements Block { return new ArrayList<>(componentsMap.values()); } - public static DetectionResult convertToDetectionResult(Map<Integer, List<ConnectedComponent>> detections, Sequence sequence) { + public static DetectionResult convertToDetectionResult(final Map<Integer, List<ConnectedComponent>> detections, final Sequence sequence) { - DetectionResult detectionResult = new DetectionResult(); - for (Integer t : detections.keySet()) - for (ConnectedComponent cc : detections.get(t)) { + final DetectionResult detectionResult = new DetectionResult(); + for (final Integer t : detections.keySet()) + for (final ConnectedComponent cc : detections.get(t)) { // TODO: add points information - Spot spot = new Spot(cc.getMassCenter().x, cc.getMassCenter().y, cc.getMassCenter().z); + final Spot spot = new Spot(cc.getMassCenter().x, cc.getMassCenter().y, cc.getMassCenter().z); detectionResult.addDetection(t, spot); } @@ -1194,37 +1212,38 @@ public class ConnectedComponents extends EzPlug implements Block { * @param components Map of List of ConnectedComponent * @param comparator Comparator of ConnectedComponents */ - public static void createLabeledSequence(Sequence output, Map<Integer, List<ConnectedComponent>> components, Comparator<ConnectedComponent> comparator) { + public static void createLabeledSequence(final Sequence output, final Map<Integer, List<ConnectedComponent>> components, final Comparator<ConnectedComponent> comparator) { if (comparator == null) return; - int width = output.getSizeX(); - int[] ids = new int[output.getSizeC()]; + final int width = output.getSizeX(); + final int[] ids = new int[output.getSizeC()]; Arrays.fill(ids, 1); - for (Integer t : components.keySet()) { + for (final Integer t : components.keySet()) { for (int c = 0; c < output.getSizeC(); c++) { // retrieve all objects in that channel - ArrayList<ConnectedComponent> cComponents = new ArrayList<>(); - for (ConnectedComponent cc : components.get(t)) { + final ArrayList<ConnectedComponent> cComponents = new ArrayList<>(); + for (final ConnectedComponent cc : components.get(t)) { if (cc.getC() == -1 || cc.getC() == c) cComponents.add(cc); } - ConnectedComponent[] ccs = cComponents.toArray(new ConnectedComponent[cComponents.size()]); + final ConnectedComponent[] ccs = cComponents.toArray(new ConnectedComponent[0]); Arrays.sort(ccs, comparator); final boolean outputVolatile = output.isVolatile(); try { - int[][] data = output.getDataXYZAsInt(t, c); + final int[][] data = output.getDataXYZAsInt(t, c); - for (ConnectedComponent cc : ccs) { - for (Point3i pt : cc) { + for (final ConnectedComponent cc : ccs) { + for (final Point3i pt : cc) { data[pt.z][pt.y * width + pt.x] = ids[c]; } ids[c]++; } - } finally { + } + finally { // restore back volatile state output.setVolatile(outputVolatile); }