From 0b2ea33e6b1c0fc18f45b78ac0afd543c4e357f8 Mon Sep 17 00:00:00 2001 From: Thomas <thomas.musset@pasteur.fr> Date: Wed, 3 Jul 2024 16:28:54 +0200 Subject: [PATCH] updated pom to v6.0.0-a.1, fix classes accordingly to new architecture, added icon, updated .gitignore --- .gitignore | 46 +++++-- pom.xml | 46 ++++++- .../adufour/roi/ROIConvexHullDescriptor.java | 46 +++---- .../roi/ROIEllipsoidFittingDescriptor.java | 62 ++++----- .../roi/ROIFeretDiameterDescriptor.java | 37 +++--- .../roi/ROIHaralickTextureDescriptor.java | 43 +++--- .../java/plugins/adufour/roi/ROIMeasures.java | 123 ++++++++++-------- .../adufour/roi/ROIRoundnessDescriptor.java | 36 +++-- .../adufour/roi/ROISphericityDescriptor.java | 31 +++-- .../roi/ROIStatisticsTrackProcessor.java | 39 +++--- .../adufour/roi/ROITrackStatistics.java | 52 ++++---- .../ROIIntensityCenterDescriptorsPlugin.java | 32 +++-- .../ROIIntensityCenterXDescriptor.java | 18 +-- .../ROIIntensityCenterYDescriptor.java | 18 +-- .../ROIIntensityCenterZDescriptor.java | 18 +-- src/main/resources/roi-stat.png | Bin 0 -> 19128 bytes 16 files changed, 363 insertions(+), 284 deletions(-) create mode 100644 src/main/resources/roi-stat.png diff --git a/.gitignore b/.gitignore index 0f33221..57f16fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,41 @@ -.idea/ -.settings/ -bin/ -build/ +/build* +/workspace +setting.xml +release/ target/ -workspace/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +icy.log + +### IntelliJ IDEA ### +.idea/ +*.iws *.iml -*.jar +*.ipr + +### Eclipse ### +.apt_generated .classpath +.factorypath .project -export.jardesc -setting.xml -**/.DS_Store \ No newline at end of file +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +**/.DS_Store +Icon? \ No newline at end of file diff --git a/pom.xml b/pom.xml index ead22e3..02ef00b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ <parent> <groupId>org.bioimageanalysis.icy</groupId> <artifactId>pom-icy</artifactId> - <version>2.2.0</version> + <version>3.0.0-a.1</version> </parent> <artifactId>roi-statistics</artifactId> - <version>6.0.0</version> + <version>6.0.0-a.1</version> <name>ROI Statistics</name> <description> @@ -18,36 +18,68 @@ </description> <dependencies> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>kernel-extensions</artifactId> + </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>quickhull</artifactId> + </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>3d-mesh-roi</artifactId> + </dependency> <dependency> <groupId>org.bioimageanalysis.icy</groupId> <artifactId>vecmath</artifactId> </dependency> <dependency> <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>active-contours</artifactId> + <artifactId>jama</artifactId> </dependency> <dependency> <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>convexify</artifactId> + <artifactId>ezplug</artifactId> </dependency> <dependency> <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>sequence-blocks</artifactId> + <artifactId>convexify</artifactId> </dependency> <dependency> <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>spot-tracking</artifactId> + <artifactId>protocols</artifactId> </dependency> <dependency> <groupId>org.bioimageanalysis.icy</groupId> <artifactId>workbooks</artifactId> </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>icy-jmath</artifactId> + </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>track-manager</artifactId> + </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>active-contours</artifactId> + </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>spot-detection-utilities</artifactId> + </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>spot-tracking</artifactId> + </dependency> </dependencies> <repositories> <repository> <id>icy</id> - <url>https://icy-nexus.pasteur.fr/repository/Icy/</url> + <url>https://nexus-icy.pasteur.cloud/repository/icy/</url> </repository> </repositories> </project> \ No newline at end of file diff --git a/src/main/java/plugins/adufour/roi/ROIConvexHullDescriptor.java b/src/main/java/plugins/adufour/roi/ROIConvexHullDescriptor.java index cae4e71..13791ba 100644 --- a/src/main/java/plugins/adufour/roi/ROIConvexHullDescriptor.java +++ b/src/main/java/plugins/adufour/roi/ROIConvexHullDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,20 +18,22 @@ package plugins.adufour.roi; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginBundled; -import icy.plugin.interface_.PluginROIDescriptor; -import icy.roi.ROI; -import icy.roi.ROI2D; -import icy.roi.ROI3D; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.type.point.Point3D; +import org.bioimageanalysis.extension.kernel.roi.roi2d.ROI2DRectShape; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginROIDescriptor; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROI2D; +import org.bioimageanalysis.icy.model.roi.ROI3D; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.system.logging.IcyLogger; import plugins.adufour.quickhull.QuickHull2D; import plugins.adufour.quickhull.QuickHull3D; import plugins.adufour.roi.mesh.Vertex3D; import plugins.adufour.roi.mesh.polygon.ROI3DPolygonalMesh; -import plugins.kernel.roi.roi2d.ROI2DRectShape; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; @@ -42,7 +44,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class ROIConvexHullDescriptor extends Plugin implements PluginROIDescriptor, PluginBundled { +@IcyPluginName("ROI Convex Hull Descriptor") +@IcyPluginIcon(path = "/roi-stat.png") +public class ROIConvexHullDescriptor extends Plugin implements PluginROIDescriptor { public static class ROIConvexity extends ROIDescriptor { protected ROIConvexity() { super("Convexity", Double.class); @@ -85,9 +89,7 @@ public class ROIConvexHullDescriptor extends Plugin implements PluginROIDescript if (roi.getNumberOfPoints() < 4) return 100; - if (roi instanceof ROI2D) { - final ROI2D r2 = (ROI2D) roi; - + if (roi instanceof final ROI2D r2) { final Point[] pts = r2.getBooleanMask(true).getContourPoints(); if (pts.length < 4) @@ -105,7 +107,7 @@ public class ROIConvexHullDescriptor extends Plugin implements PluginROIDescript // contour = sum( sqrt[ (x[i] - x[i-1])^2 + (y[i] - y[i-1])^2 ] ) // area = 0.5 * sum( (x[i-1] * y[i]) - (y[i-1] * x[i]) ) - Point2D p1 = points.get(points.size() - 1), p2; + Point2D p1 = points.getLast(), p2; for (final Point2D point : points) { p2 = point; @@ -118,8 +120,7 @@ public class ROIConvexHullDescriptor extends Plugin implements PluginROIDescript else if (roi instanceof ROI3D) { final Point3d[] points; - if (roi instanceof ROI3DPolygonalMesh) { - final ROI3DPolygonalMesh mesh = (ROI3DPolygonalMesh) roi; + if (roi instanceof final ROI3DPolygonalMesh mesh) { // Tuple3d res = mesh.getPixelSize(); points = new Point3d[mesh.getNumberOfVertices(true)]; @@ -174,11 +175,11 @@ public class ROIConvexHullDescriptor extends Plugin implements PluginROIDescript } } catch (final RuntimeException e) { - System.err.println("Warning while computing the convexity of " + roi.getName() + ": " + e.getMessage()); + IcyLogger.warn(ROIConvexity.class, e, "Warning while computing the convexity of " + roi.getName()); } } else { - System.err.println("WARNING: cannot compute the convexity of a " + roi.getClassName()); + IcyLogger.warn(ROIConvexity.class, "WARNING: cannot compute the convexity of a " + roi.getClassName()); return Double.NaN; } @@ -202,9 +203,4 @@ public class ROIConvexHullDescriptor extends Plugin implements PluginROIDescript map.put(convexity, convexity.compute(roi, sequence)); return map; } - - @Override - public String getMainPluginClassName() { - return ROIMeasures.class.getName(); - } } diff --git a/src/main/java/plugins/adufour/roi/ROIEllipsoidFittingDescriptor.java b/src/main/java/plugins/adufour/roi/ROIEllipsoidFittingDescriptor.java index 7a0b1b9..0bc83ab 100644 --- a/src/main/java/plugins/adufour/roi/ROIEllipsoidFittingDescriptor.java +++ b/src/main/java/plugins/adufour/roi/ROIEllipsoidFittingDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -20,19 +20,19 @@ package plugins.adufour.roi; import Jama.EigenvalueDecomposition; import Jama.Matrix; -import icy.math.MathUtil; -import icy.math.UnitUtil; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginBundled; -import icy.plugin.interface_.PluginROIDescriptor; -import icy.roi.ROI; -import icy.roi.ROI2D; -import icy.roi.ROI3D; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.type.point.Point3D; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.common.math.MathUtil; +import org.bioimageanalysis.icy.common.math.UnitUtil; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginROIDescriptor; +import org.bioimageanalysis.icy.model.roi.*; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.system.logging.IcyLogger; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import plugins.adufour.vars.lang.VarDouble; -import plugins.kernel.roi.descriptor.measure.ROIMassCenterDescriptorsPlugin; import javax.vecmath.*; import java.awt.*; @@ -40,7 +40,9 @@ import java.awt.geom.Point2D; import java.util.List; import java.util.*; -public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDescriptor, PluginBundled { +@IcyPluginName("ROI Ellipsoid Fitting Descriptor") +@IcyPluginIcon(path = "/roi-stat.png") +public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDescriptor { public static class ROIFirstDiameter extends ROIDescriptor { protected ROIFirstDiameter() { super("1st Diameter", Double.class); @@ -175,7 +177,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe * @return The first (or major) principle axis of the best fitting ellipse * @throws InterruptedException if thread was interrupted */ - public static Vector3d computeFirstAxis(final ROI roi, final Sequence sequence) throws InterruptedException { + public static @NotNull Vector3d computeFirstAxis(final ROI roi, final Sequence sequence) throws InterruptedException { final double[] ellipse = computeOrientation(roi, sequence); return new Vector3d(ellipse[3], ellipse[4], ellipse[5]); } @@ -210,7 +212,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe * @return The second principle axis of the best fitting ellipse * @throws InterruptedException if thread was interrupted */ - public static Vector3d computeSecondAxis(final ROI roi, final Sequence sequence) throws InterruptedException { + public static @NotNull Vector3d computeSecondAxis(final ROI roi, final Sequence sequence) throws InterruptedException { final double[] ellipse = computeOrientation(roi, sequence); return new Vector3d(ellipse[6], ellipse[7], ellipse[8]); } @@ -245,7 +247,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe * @return The third principle axis of the best fitting ellipse * @throws InterruptedException if thread was interrupted */ - public static Vector3d computeThirdAxis(final ROI roi, final Sequence sequence) throws InterruptedException { + public static @NotNull Vector3d computeThirdAxis(final ROI roi, final Sequence sequence) throws InterruptedException { if (roi instanceof ROI2D) return new Vector3d(); @@ -457,6 +459,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe * @param ellipse ellipse orientation * @return The elongation ratio of the X-Y plane */ + @Contract(pure = true) public static double computeElongation(final double[] ellipse) { if (ellipse == null) return Double.NaN; @@ -503,6 +506,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe * @param ellipse ellipse orientation * @return The 3D flatness ratio of the Y-Z plane */ + @Contract(pure = true) public static double computeFlatness3D(final double[] ellipse) { if (ellipse == null) return Double.NaN; @@ -567,7 +571,8 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe return map; } - private static String getUnit(final Sequence sequence) { + @Contract(pure = true) + private static @NotNull String getUnit(final Sequence sequence) { if (sequence == null) return "px"; @@ -596,7 +601,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe * </ul> * @throws InterruptedException if thread was interrupted */ - public static double[] computeOrientation(final ROI roi, final Sequence sequence) throws InterruptedException { + public static double @NotNull [] computeOrientation(final ROI roi, final Sequence sequence) throws InterruptedException { final double[] ellipse = new double[12]; if (roi instanceof ROI2D) { @@ -661,7 +666,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe } } else { - System.err.println("Cannot compute ellipse dimensions for ROI of type: " + roi.getClassName()); + IcyLogger.error(ROIEllipsoidFittingDescriptor.class, "Cannot compute ellipse dimensions for ROI of type: " + roi.getClassName()); Arrays.fill(ellipse, Double.NaN); } @@ -695,7 +700,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe * @throws InterruptedException * @throws SingularMatrixException if the component is flat (i.e. lies in a 2D plane) */ - private static void fitEllipse(final ROI3D cc, final Point3d radii, final Vector3d[] eigenVectors, final double[] equation) throws IllegalArgumentException, InterruptedException { + private static void fitEllipse(final @NotNull ROI3D cc, final Point3d radii, final Vector3d[] eigenVectors, final double[] equation) throws IllegalArgumentException, InterruptedException { final Point3D.Integer[] points = cc.getBooleanMask(true).getContourPoints(); if (points.length < 9) { @@ -789,7 +794,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe * @throws InterruptedException */ private static void fitEllipse( - final ROI2D roi, + final @NotNull ROI2D roi, final Point2d center, final Point2d radii, final VarDouble angle, @@ -801,7 +806,7 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe if (points.length < 4) return; - final Point2D ccenter = ROIMassCenterDescriptorsPlugin.computeMassCenter(roi).toPoint2D(); + final Point2D ccenter = ROIUtil.computeMassCenter(roi).toPoint2D(); final double cx = ccenter.getX(); final double cy = ccenter.getY(); @@ -924,7 +929,8 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe } } - private static Matrix diag(final Matrix matrix) { + @Contract("_ -> new") + private static @NotNull Matrix diag(final @NotNull 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++) { @@ -933,15 +939,11 @@ public class ROIEllipsoidFittingDescriptor extends Plugin implements PluginROIDe return new Matrix(diag); } - private static Matrix ones(final int m, final int n) { + @Contract("_, _ -> new") + private static @NotNull 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); } - - @Override - public String getMainPluginClassName() { - return ROIMeasures.class.getName(); - } } diff --git a/src/main/java/plugins/adufour/roi/ROIFeretDiameterDescriptor.java b/src/main/java/plugins/adufour/roi/ROIFeretDiameterDescriptor.java index 859adbb..49960e3 100644 --- a/src/main/java/plugins/adufour/roi/ROIFeretDiameterDescriptor.java +++ b/src/main/java/plugins/adufour/roi/ROIFeretDiameterDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,19 +18,21 @@ package plugins.adufour.roi; -import icy.math.UnitUtil; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginBundled; -import icy.plugin.interface_.PluginROIDescriptor; -import icy.roi.ROI; -import icy.roi.ROI2D; -import icy.roi.ROI3D; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.type.point.Point3D; +import org.bioimageanalysis.extension.kernel.roi.roi2d.ROI2DRectShape; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.common.math.UnitUtil; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginROIDescriptor; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROI2D; +import org.bioimageanalysis.icy.model.roi.ROI3D; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.system.logging.IcyLogger; import plugins.adufour.roi.mesh.Vertex3D; import plugins.adufour.roi.mesh.polygon.ROI3DPolygonalMesh; -import plugins.kernel.roi.roi2d.ROI2DRectShape; import javax.vecmath.Point3d; import java.awt.*; @@ -41,7 +43,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class ROIFeretDiameterDescriptor extends Plugin implements PluginROIDescriptor, PluginBundled { +@IcyPluginName("ROI Feret Diameter Descriptor") +@IcyPluginIcon(path = "/roi-stat.png") +public class ROIFeretDiameterDescriptor extends Plugin implements PluginROIDescriptor { public static class ROIFeretDiameter extends ROIDescriptor { protected ROIFeretDiameter() { super("Max Feret diameter", Double.class); @@ -153,7 +157,7 @@ public class ROIFeretDiameterDescriptor extends Plugin implements PluginROIDescr maxDistance = Math.sqrt(maxDistance); } else { - System.err.println("Cannot compute Max. Feret diameter for ROI of type: " + roi.getClassName()); + IcyLogger.error(ROIFeretDiameter.class, "Cannot compute Max. Feret diameter for ROI of type: " + roi.getClassName()); maxDistance = 0.0d; } @@ -176,9 +180,4 @@ public class ROIFeretDiameterDescriptor extends Plugin implements PluginROIDescr map.put(feretDiameter, feretDiameter.compute(roi, sequence)); return map; } - - @Override - public String getMainPluginClassName() { - return ROIMeasures.class.getName(); - } } diff --git a/src/main/java/plugins/adufour/roi/ROIHaralickTextureDescriptor.java b/src/main/java/plugins/adufour/roi/ROIHaralickTextureDescriptor.java index cb2273e..514218d 100644 --- a/src/main/java/plugins/adufour/roi/ROIHaralickTextureDescriptor.java +++ b/src/main/java/plugins/adufour/roi/ROIHaralickTextureDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,25 +18,29 @@ package plugins.adufour.roi; -import icy.image.IcyBufferedImage; -import icy.image.IcyBufferedImageUtil; -import icy.math.ArrayMath; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginBundled; -import icy.plugin.interface_.PluginROIDescriptor; -import icy.roi.BooleanMask2D; -import icy.roi.ROI; -import icy.roi.ROI2D; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.type.DataType; +import org.bioimageanalysis.icy.common.math.ArrayMath; +import org.bioimageanalysis.icy.common.type.DataType; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginROIDescriptor; +import org.bioimageanalysis.icy.model.image.IcyBufferedImage; +import org.bioimageanalysis.icy.model.image.IcyBufferedImageUtil; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROI2D; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.roi.mask.BooleanMask2D; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class ROIHaralickTextureDescriptor extends Plugin implements PluginROIDescriptor, PluginBundled { +@IcyPluginName("ROI Haralick Texture Descriptor") +@IcyPluginIcon(path = "/roi-stat.png") +public class ROIHaralickTextureDescriptor extends Plugin implements PluginROIDescriptor { private static abstract class ROIHaralickDescriptor extends ROIDescriptor { protected final int step; @@ -211,7 +215,7 @@ public class ROIHaralickTextureDescriptor extends Plugin implements PluginROIDes * (see {@link #getDescriptors()}) for a full list * @throws InterruptedException */ - public static Map<ROIDescriptor, Object> computeHaralickFeatures(final Sequence sequence, final ROI2D roi, final int step) throws InterruptedException { + public static @NotNull Map<ROIDescriptor, Object> computeHaralickFeatures(final Sequence sequence, final @NotNull ROI2D roi, final int step) throws InterruptedException { final int c = roi.getC(); if (c == -1) throw new UnsupportedOperationException("Texture can only be calculated on a single channel"); @@ -295,10 +299,10 @@ public class ROIHaralickTextureDescriptor extends Plugin implements PluginROIDes * input region of interest (ROI) * @throws InterruptedException */ - private static IcyBufferedImage buildGLCM(final IcyBufferedImage image, final ROI2D roi, final int step) throws InterruptedException { + private static @NotNull IcyBufferedImage buildGLCM(final @NotNull IcyBufferedImage image, final ROI2D roi, final int step) throws InterruptedException { // Rescale to [0-255] if necessary IcyBufferedImage imByte = null; - if (image.getDataType_() == DataType.UBYTE) { + if (image.getDataType() == DataType.UBYTE) { imByte = image; } else { @@ -372,9 +376,4 @@ public class ROIHaralickTextureDescriptor extends Plugin implements PluginROIDes return coocImage; } - - @Override - public String getMainPluginClassName() { - return ROIMeasures.class.getName(); - } } diff --git a/src/main/java/plugins/adufour/roi/ROIMeasures.java b/src/main/java/plugins/adufour/roi/ROIMeasures.java index 15a2b8d..aeb14a0 100644 --- a/src/main/java/plugins/adufour/roi/ROIMeasures.java +++ b/src/main/java/plugins/adufour/roi/ROIMeasures.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,31 +18,35 @@ package plugins.adufour.roi; -import icy.file.FileUtil; -import icy.gui.main.GlobalSequenceListener; -import icy.main.Icy; -import icy.plugin.PluginLauncher; -import icy.plugin.PluginLoader; -import icy.preferences.XMLPreferences; -import icy.roi.*; -import icy.roi.ROIEvent.ROIEventType; -import icy.sequence.Sequence; -import icy.sequence.SequenceDataIterator; -import icy.sequence.SequenceEvent; -import icy.sequence.SequenceEvent.SequenceEventSourceType; -import icy.sequence.SequenceListener; -import icy.system.IcyExceptionHandler; -import icy.system.SystemUtil; -import icy.system.thread.Processor; -import icy.system.thread.ThreadUtil; -import icy.type.point.Point3D; -import icy.type.point.Point5D; -import icy.type.rectangle.Rectangle5D; -import icy.util.StringUtil; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.WorkbookUtil; +import org.bioimageanalysis.extension.kernel.roi.descriptor.measure.ROIContourDescriptor; +import org.bioimageanalysis.extension.kernel.roi.descriptor.measure.ROIInteriorDescriptor; +import org.bioimageanalysis.icy.Icy; +import org.bioimageanalysis.icy.common.geom.point.Point5D; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.common.geom.rectangle.Rectangle5D; +import org.bioimageanalysis.icy.common.string.StringUtil; +import org.bioimageanalysis.icy.extension.plugin.PluginLauncher; +import org.bioimageanalysis.icy.extension.plugin.PluginLoader; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.gui.listener.GlobalSequenceListener; +import org.bioimageanalysis.icy.io.FileUtil; +import org.bioimageanalysis.icy.model.roi.*; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.model.sequence.SequenceDataIterator; +import org.bioimageanalysis.icy.model.sequence.SequenceEvent; +import org.bioimageanalysis.icy.model.sequence.SequenceListener; +import org.bioimageanalysis.icy.system.SystemUtil; +import org.bioimageanalysis.icy.system.logging.IcyLogger; +import org.bioimageanalysis.icy.system.preferences.XMLPreferences; +import org.bioimageanalysis.icy.system.thread.Processor; +import org.bioimageanalysis.icy.system.thread.ThreadUtil; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import plugins.adufour.blocks.tools.roi.ROIBlock; import plugins.adufour.blocks.util.VarList; import plugins.adufour.ezplug.EzDialog; @@ -65,8 +69,6 @@ import plugins.adufour.vars.util.VarReferencingPolicy; import plugins.adufour.workbooks.IcySpreadSheet; import plugins.adufour.workbooks.Workbooks; 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 javax.swing.*; @@ -85,8 +87,9 @@ import java.util.concurrent.Future; * @author Alexandre Dufour * @author Daniel Gonzalez */ +@IcyPluginName("ROI Measures") +@IcyPluginIcon(path = "/roi-stat.png") public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListener, SequenceListener, ROIListener { - /** * Measures available for this plugin. * <b>Note:</b> The id is used when loading and saving parameters. @@ -121,6 +124,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe final int id; final String unit; + @Contract(pure = true) Measures(final String name, final int id, final String unit) { this.name = name; this.id = id; @@ -129,31 +133,39 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe boolean selected = true; + @SuppressWarnings("UnstableApiUsage") + @Contract(mutates = "this") public void toggleSelection() { selected = !selected; } + @SuppressWarnings("UnstableApiUsage") + @Contract(mutates = "this") public void setSelected(final boolean value) { selected = value; } + @Contract(pure = true) @Override public String toString() { return name; } - public String toHeader() { + public @NotNull String toHeader() { return name + (hasUnit() ? " (" + getUnit() + ")" : ""); } + @Contract(pure = true) public boolean hasUnit() { return unit != null && !unit.isEmpty(); } + @Contract(pure = true) public String getUnit() { return unit; } + @Contract(pure = true) public boolean isSelected() { return selected; } @@ -226,7 +238,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe } @Override - public void declareInput(final VarList inputMap) { + public void declareInput(final @NotNull VarList inputMap) { measureSelector.setReferencingPolicy(VarReferencingPolicy.NONE); inputMap.add("measures", measureSelector); inputMap.add("Regions of interest", rois); @@ -234,7 +246,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe } @Override - public void declareOutput(final VarList outputMap) { + public void declareOutput(final @NotNull VarList outputMap) { outputMap.add("Workbook", book); } @@ -271,7 +283,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe book.setValue(wbs); } catch (final IOException e) { - IcyExceptionHandler.showErrorMessage(e, true); + IcyLogger.error(this.getClass(), e); } //book.setValue(wb = Workbooks.createEmptyWorkbook()); } @@ -293,7 +305,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe // try retrieving the sequence attached to the first ROI final List<Sequence> sequences = rois.getValue()[0].getSequences(); if (!sequences.isEmpty()) - sequenceOfInterest = sequences.get(0); + sequenceOfInterest = sequences.getFirst(); } if (sequenceOfInterest == null) { @@ -418,7 +430,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe return; } catch (final Exception e) { - IcyExceptionHandler.showErrorMessage(e, true); + IcyLogger.error(this.getClass(), e); return; } @@ -433,7 +445,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe } } - private void updateStatistics(final ROI roi) { + private void updateStatistics(final @NotNull ROI roi) { final Workbook wb = book.getValue(); for (final Sequence sequenceOfInterest : roi.getSequences()) @@ -452,13 +464,13 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe } } - private static String colorToString(final Color color) { + private static @NotNull String colorToString(final @NotNull Color color) { return (StringUtil.toHexaString(color.getAlpha(), 2) + StringUtil.toHexaString(color.getRed(), 2) + StringUtil.toHexaString(color.getGreen(), 2) + StringUtil.toHexaString(color.getBlue(), 2)) .toUpperCase(); } - private void updateWorkbook(final Workbook wb, final IcySpreadSheet sheet, final int rowID, final List<Object> measures, final boolean updateInterface) { + private void updateWorkbook(final Workbook wb, final IcySpreadSheet sheet, final int rowID, final @NotNull List<Object> measures, final boolean updateInterface) { for (int colID = 0; colID < measures.size(); colID++) { final Object value = measures.get(colID); @@ -474,7 +486,9 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe book.valueChanged(book, null, book.getValue()); } - private Callable<List<Object>> createUpdater(final Sequence sequenceOfInterest, final ROI roi2Update) { + @SuppressWarnings("resource") + @Contract(pure = true) + private @NotNull Callable<List<Object>> createUpdater(final Sequence sequenceOfInterest, final ROI roi2Update) { return () -> { final List<Object> measures = new ArrayList<>(); final List<Measures> measuresToCalculate = new ArrayList<>(); @@ -493,7 +507,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe || Measures.T_CENTER.isSelected() || Measures.C_CENTER.isSelected() || Measures.X_GLOBAL_CENTER.isSelected() || Measures.Y_GLOBAL_CENTER.isSelected() || Measures.Z_GLOBAL_CENTER.isSelected()) { - center = ROIMassCenterDescriptorsPlugin.computeMassCenter(roi); + center = ROIUtil.computeMassCenter(roi); globalCenter = center.toPoint3D(); if (sequenceOfInterest != null) { globalCenter.setX(sequenceOfInterest.getPositionX() @@ -596,7 +610,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe // we don't want to spam output with these errors if (!haralickErrors.contains(mess)) { haralickErrors.add(mess); - System.err.println(mess); + IcyLogger.error(this.getClass(), ex); } } } @@ -866,8 +880,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe if (sequenceOfInterest == null) measures.add("NA"); else { - final double mul = ROIBasicMeasureDescriptorsPlugin - .getMultiplierFactor(sequenceOfInterest, roi, 2); + final double mul = ROIUtil.getMultiplierFactor(sequenceOfInterest, roi, 2); // 0 means the operation is not supported for this ROI if (mul == 0d) @@ -888,8 +901,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe if (sequenceOfInterest == null) measures.add("NA"); else { - final double mul1 = ROIBasicMeasureDescriptorsPlugin - .getMultiplierFactor(sequenceOfInterest, roi, 3); + final double mul1 = ROIUtil.getMultiplierFactor(sequenceOfInterest, roi, 3); // 0 means the operation is not supported for this ROI if (mul1 == 0d) @@ -902,16 +914,19 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe } break; case INTENSITY_X: + Objects.requireNonNull(intensityCenters); for (int c = 0; c < sizeC; c++) { measures.add(intensityCenters[c].getX()); } break; case INTENSITY_Y: + Objects.requireNonNull(intensityCenters); for (int c = 0; c < sizeC; c++) { measures.add(intensityCenters[c].getY()); } break; case INTENSITY_Z: + Objects.requireNonNull(intensityCenters); for (int c = 0; c < sizeC; c++) { measures.add(intensityCenters[c].getZ()); } @@ -931,7 +946,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe // MAIN LISTENER // @Override - public void sequenceOpened(final Sequence openedSequence) { + public void sequenceOpened(final @NotNull Sequence openedSequence) { openedSequence.addListener(this); for (final ROI roi : openedSequence.getROIs()) roi.addListener(this); @@ -944,7 +959,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe } @Override - public void sequenceClosed(final Sequence closedSequence) { + public void sequenceClosed(final @NotNull Sequence closedSequence) { closedSequence.removeListener(this); for (final ROI roi : closedSequence.getROIs()) roi.removeListener(this); @@ -963,12 +978,12 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe // SEQUENCE LISTENER // @Override - public void sequenceChanged(final SequenceEvent sequenceEvent) { - if (sequenceEvent.getSourceType() == SequenceEventSourceType.SEQUENCE_DATA) { + public void sequenceChanged(final @NotNull SequenceEvent sequenceEvent) { + if (sequenceEvent.getSourceType() == SequenceEvent.SequenceEventSourceType.SEQUENCE_DATA) { updateStatistics(sequenceEvent.getSequence()); } - else if (sequenceEvent.getSourceType() == SequenceEventSourceType.SEQUENCE_ROI) { + else if (sequenceEvent.getSourceType() == SequenceEvent.SequenceEventSourceType.SEQUENCE_ROI) { final ROI roi = (ROI) sequenceEvent.getSource(); switch (sequenceEvent.getType()) { @@ -991,7 +1006,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe if (roi == null) { // multiple ROIs have been removed - System.err.println("[ROI Statistics] Warning: potential memory leak"); + IcyLogger.warn(this.getClass(), "Warning: potential memory leak"); } else { roi.removeListener(this); @@ -1021,9 +1036,9 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe } @Override - public void roiChanged(final ROIEvent event) { - if (event.getType() == ROIEventType.ROI_CHANGED - || event.getType() == ROIEventType.PROPERTY_CHANGED && (event.getPropertyName().equalsIgnoreCase("name") + public void roiChanged(final @NotNull ROIEvent event) { + if (event.getType() == ROIEvent.ROIEventType.ROI_CHANGED + || event.getType() == ROIEvent.ROIEventType.PROPERTY_CHANGED && (event.getPropertyName().equalsIgnoreCase("name") || event.getPropertyName().equalsIgnoreCase("color"))) { updateStatistics(event.getSource()); // this is mandatory since sheet modification cannot be detected @@ -1049,8 +1064,9 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe setNameVisible(false); } + @Contract(" -> new") @Override - protected JButton createEditorComponent() { + protected @NotNull JButton createEditorComponent() { if (getEditorComponent() != null) deactivateListeners(); @@ -1074,6 +1090,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe getEditorComponent().removeActionListener(listener); } + @Contract(pure = true) @Override protected void updateInterfaceValue() { // nothing to do (it's just a button with a name) @@ -1103,8 +1120,9 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe addListener(gui); } + @Contract(" -> new") @Override - public VarEditor<Long> createVarEditor() { + public @NotNull VarEditor<Long> createVarEditor() { return new ButtonVarEditor(this); } @@ -1241,6 +1259,7 @@ public class ROIMeasures extends EzPlug implements ROIBlock, GlobalSequenceListe } } + @SuppressWarnings("resource") public static void main(final String[] args) { Icy.main(args); diff --git a/src/main/java/plugins/adufour/roi/ROIRoundnessDescriptor.java b/src/main/java/plugins/adufour/roi/ROIRoundnessDescriptor.java index 2da0b4c..ab3da3b 100644 --- a/src/main/java/plugins/adufour/roi/ROIRoundnessDescriptor.java +++ b/src/main/java/plugins/adufour/roi/ROIRoundnessDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,19 +18,18 @@ package plugins.adufour.roi; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginBundled; -import icy.plugin.interface_.PluginROIDescriptor; -import icy.roi.ROI; -import icy.roi.ROI2D; -import icy.roi.ROI3D; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.type.point.Point3D; -import icy.type.point.Point5D; +import org.bioimageanalysis.extension.kernel.roi.roi2d.ROI2DEllipse; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.common.geom.point.Point5D; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginROIDescriptor; +import org.bioimageanalysis.icy.model.roi.*; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.system.logging.IcyLogger; import plugins.adufour.roi.ROISphericityDescriptor.ROISphericity; import plugins.kernel.roi.descriptor.measure.ROIMassCenterDescriptorsPlugin; -import plugins.kernel.roi.roi2d.ROI2DEllipse; import javax.vecmath.Point3d; import java.awt.*; @@ -41,7 +40,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class ROIRoundnessDescriptor extends Plugin implements PluginROIDescriptor, PluginBundled { +@IcyPluginName("ROI Roundness Descriptor") +@IcyPluginIcon(path = "/roi-stat.png") +public class ROIRoundnessDescriptor extends Plugin implements PluginROIDescriptor { public static class ROIRoundness extends ROIDescriptor { public ROIRoundness() { super("Roundness", Double.class); @@ -111,7 +112,7 @@ public class ROIRoundnessDescriptor extends Plugin implements PluginROIDescripto public static double computeRoundness(final ROI roi) throws InterruptedException { double minDistance = Double.MAX_VALUE, maxDistance = 0.0d; - final Point5D center = ROIMassCenterDescriptorsPlugin.computeMassCenter(roi); + final Point5D center = ROIUtil.computeMassCenter(roi); if (roi instanceof ROI2D) { if (roi instanceof ROI2DEllipse) { @@ -146,7 +147,7 @@ public class ROIRoundnessDescriptor extends Plugin implements PluginROIDescripto } } else { - System.err.println("Cannot compute roundness for ROI of type: " + roi.getClassName()); + IcyLogger.error(ROIRoundness.class, "Cannot compute roundness for ROI of type: " + roi.getClassName()); return Double.NaN; } @@ -171,9 +172,4 @@ public class ROIRoundnessDescriptor extends Plugin implements PluginROIDescripto map.put(roundness, roundness.compute(roi, sequence)); return map; } - - @Override - public String getMainPluginClassName() { - return ROIMeasures.class.getName(); - } } diff --git a/src/main/java/plugins/adufour/roi/ROISphericityDescriptor.java b/src/main/java/plugins/adufour/roi/ROISphericityDescriptor.java index ce81dae..a3edb22 100644 --- a/src/main/java/plugins/adufour/roi/ROISphericityDescriptor.java +++ b/src/main/java/plugins/adufour/roi/ROISphericityDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,21 +18,25 @@ package plugins.adufour.roi; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginBundled; -import icy.plugin.interface_.PluginROIDescriptor; -import icy.roi.ROI; -import icy.roi.ROI2D; -import icy.roi.ROI3D; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginROIDescriptor; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROI2D; +import org.bioimageanalysis.icy.model.roi.ROI3D; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.system.logging.IcyLogger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class ROISphericityDescriptor extends Plugin implements PluginROIDescriptor, PluginBundled { +@IcyPluginName("ROI Sphericity Descriptor") +@IcyPluginIcon(path = "/roi-stat.png") +public class ROISphericityDescriptor extends Plugin implements PluginROIDescriptor { public static class ROISphericity extends ROIDescriptor { public ROISphericity() { super("Sphericity", Double.class); @@ -101,7 +105,7 @@ public class ROISphericityDescriptor extends Plugin implements PluginROIDescript dim = 3.0d; } else { - System.err.println("Cannot compute sphericity for ROI of type: " + roi.getClassName()); + IcyLogger.error(ROISphericity.class, "Cannot compute sphericity for ROI of type: " + roi.getClassName()); return Double.NaN; } @@ -147,9 +151,4 @@ public class ROISphericityDescriptor extends Plugin implements PluginROIDescript map.put(sphericity, sphericity.compute(roi, sequence)); return map; } - - @Override - public String getMainPluginClassName() { - return ROIMeasures.class.getName(); - } } diff --git a/src/main/java/plugins/adufour/roi/ROIStatisticsTrackProcessor.java b/src/main/java/plugins/adufour/roi/ROIStatisticsTrackProcessor.java index 67bab6d..0b79970 100644 --- a/src/main/java/plugins/adufour/roi/ROIStatisticsTrackProcessor.java +++ b/src/main/java/plugins/adufour/roi/ROIStatisticsTrackProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,15 +18,14 @@ package plugins.adufour.roi; -import icy.file.FileUtil; -import icy.gui.dialog.SaveDialog; -import icy.gui.frame.progress.AnnounceFrame; -import icy.gui.util.GuiUtil; -import icy.plugin.interface_.PluginBundled; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.system.IcyExceptionHandler; import org.apache.poi.ss.usermodel.Workbook; +import org.bioimageanalysis.icy.gui.GuiUtil; +import org.bioimageanalysis.icy.gui.dialog.SaveDialog; +import org.bioimageanalysis.icy.gui.frame.progress.AnnounceFrame; +import org.bioimageanalysis.icy.io.FileUtil; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.system.logging.IcyLogger; import org.math.plot.Plot2DPanel; import plugins.adufour.blocks.tools.io.WorkbookToFile; import plugins.adufour.blocks.tools.io.WorkbookToFile.MergePolicy; @@ -42,12 +41,7 @@ import javax.swing.*; import java.awt.*; import java.io.IOException; -public class ROIStatisticsTrackProcessor extends PluginTrackManagerProcessor implements PluginBundled { - @Override - public String getMainPluginClassName() { - return ROIMeasures.class.getName(); - } - +public class ROIStatisticsTrackProcessor extends PluginTrackManagerProcessor { private final JButton exportButton = new JButton("Export to XLS..."); private final VarSequence sequence = new VarSequence("Sequence", null); @@ -134,8 +128,13 @@ public class ROIStatisticsTrackProcessor extends PluginTrackManagerProcessor imp final Sequence seq = trackPool.getDisplaySequence(); try { - ROITrackStatistics.buildPlot(trackPool.getTrackGroupList(), seq, channel.getValue(), - ROIDescriptor.getDescriptor(roiDescriptor.getValue()), plotPanel); + ROITrackStatistics.buildPlot( + trackPool.getTrackGroupList(), + seq, + channel.getValue(), + ROIDescriptor.getDescriptor(roiDescriptor.getValue()), + plotPanel + ); if (!plotPanel.getPlots().isEmpty()) { chartPanel.add(plotPanel); @@ -148,7 +147,7 @@ public class ROIStatisticsTrackProcessor extends PluginTrackManagerProcessor imp } } catch (final Exception e) { - IcyExceptionHandler.showErrorMessage(e, true); + IcyLogger.error(this.getClass(), e, "Cannot compute descriptor."); chartPanel.add(new JLabel("Cannot compute descriptor: " + e.getMessage())); exportButton.setEnabled(false); } @@ -172,7 +171,7 @@ public class ROIStatisticsTrackProcessor extends PluginTrackManagerProcessor imp // Restore last used folder final String xlsFolder = getPreferencesRoot().get("xlsFolder", null); - final String path = SaveDialog.chooseFile("Export statistics", xlsFolder, name, ".xls"); + final String path = SaveDialog.chooseFile("Export statistics", xlsFolder, name, ".xlsx"); // canceled if (path == null) @@ -191,7 +190,7 @@ public class ROIStatisticsTrackProcessor extends PluginTrackManagerProcessor imp } } catch (final IOException e) { - IcyExceptionHandler.showErrorMessage(e, true); + IcyLogger.error(this.getClass(), e, "Cannot export statistics."); } finally { message.close(); diff --git a/src/main/java/plugins/adufour/roi/ROITrackStatistics.java b/src/main/java/plugins/adufour/roi/ROITrackStatistics.java index 6cf4613..0e49661 100644 --- a/src/main/java/plugins/adufour/roi/ROITrackStatistics.java +++ b/src/main/java/plugins/adufour/roi/ROITrackStatistics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,15 +18,21 @@ package plugins.adufour.roi; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginBundled; -import icy.roi.ROI; -import icy.roi.ROI2D; -import icy.roi.ROI3D; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.type.collection.CollectionUtil; import org.apache.poi.ss.usermodel.Workbook; +import org.bioimageanalysis.extension.kernel.roi.roi2d.ROI2DArea; +import org.bioimageanalysis.extension.kernel.roi.roi2d.ROI2DPoint; +import org.bioimageanalysis.extension.kernel.roi.roi3d.ROI3DArea; +import org.bioimageanalysis.extension.kernel.roi.roi3d.ROI3DPoint; +import org.bioimageanalysis.icy.common.collection.CollectionUtil; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROI2D; +import org.bioimageanalysis.icy.model.roi.ROI3D; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.jetbrains.annotations.NotNull; import org.math.plot.Plot2DPanel; import org.math.plot.plots.Plot; import plugins.adufour.activecontours.ActiveContour; @@ -39,10 +45,6 @@ import plugins.adufour.workbooks.IcySpreadSheet; import plugins.adufour.workbooks.Workbooks; import plugins.fab.trackmanager.TrackGroup; import plugins.fab.trackmanager.TrackSegment; -import plugins.kernel.roi.roi2d.ROI2DArea; -import plugins.kernel.roi.roi2d.ROI2DPoint; -import plugins.kernel.roi.roi3d.ROI3DArea; -import plugins.kernel.roi.roi3d.ROI3DPoint; import plugins.nchenouard.particletracking.DetectionSpotTrack; import plugins.nchenouard.particletracking.legacytracker.SpotTrack; import plugins.nchenouard.spot.Detection; @@ -55,15 +57,22 @@ import java.util.List; * * @author Stephane Dallongeville */ -public class ROITrackStatistics extends Plugin implements ROIBlock, PluginBundled { +@IcyPluginName("ROI Track Statistics") +@IcyPluginIcon(path = "/roi-stat.png") +public class ROITrackStatistics extends Plugin implements ROIBlock { /** * Build the plot describing the {@link ROIDescriptor} evolution along given input tracks * * @throws InterruptedException * @throws UnsupportedOperationException */ - public static void buildPlot(final List<TrackGroup> trackGroups, final Sequence sequence, final int channel, final ROIDescriptor descriptor, final Plot2DPanel plotPanel) - throws UnsupportedOperationException, InterruptedException { + public static void buildPlot( + final @NotNull List<TrackGroup> trackGroups, + final Sequence sequence, + final int channel, + final @NotNull ROIDescriptor descriptor, + final @NotNull Plot2DPanel plotPanel + ) throws UnsupportedOperationException, InterruptedException { final int sizeZ = (sequence != null) ? sequence.getSizeZ() : 1; final double timeInterval = (sequence != null) ? sequence.getTimeInterval() : 1d; @@ -178,7 +187,7 @@ public class ROITrackStatistics extends Plugin implements ROIBlock, PluginBundle /** * Build and return the workbook describing the {@link ROIDescriptor} evolution along given input tracks */ - public static Workbook getWorkBook(final Sequence sequence, final String name, final Plot2DPanel plotPanel) { + public static @NotNull Workbook getWorkBook(final Sequence sequence, final String name, final Plot2DPanel plotPanel) { final Workbook wb = Workbooks.createEmptyWorkbook(); final IcySpreadSheet sheet = Workbooks.getSheet(wb, name); @@ -225,7 +234,7 @@ public class ROITrackStatistics extends Plugin implements ROIBlock, PluginBundle } @Override - public void declareInput(final VarList inputMap) { + public void declareInput(final @NotNull VarList inputMap) { inputMap.add("trackgroup", tracks); inputMap.add("sequence", sequence); inputMap.add("channel", channel); @@ -233,7 +242,7 @@ public class ROITrackStatistics extends Plugin implements ROIBlock, PluginBundle } @Override - public void declareOutput(final VarList outputMap) { + public void declareOutput(final @NotNull VarList outputMap) { outputMap.add("workbook", workbook); } @@ -268,9 +277,4 @@ public class ROITrackStatistics extends Plugin implements ROIBlock, PluginBundle Thread.currentThread().interrupt(); } } - - @Override - public String getMainPluginClassName() { - return ROIMeasures.class.getName(); - } } diff --git a/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterDescriptorsPlugin.java b/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterDescriptorsPlugin.java index ea06b98..b60eb67 100644 --- a/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterDescriptorsPlugin.java +++ b/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterDescriptorsPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,16 +18,20 @@ package plugins.adufour.roi.intensitycenter; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginROIDescriptor; -import icy.roi.ROI; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.sequence.SequenceDataIterator; -import icy.type.point.Point3D; -import icy.type.rectangle.Rectangle5D; -import plugins.kernel.roi.roi2d.ROI2DPoint; -import plugins.kernel.roi.roi3d.ROI3DPoint; +import org.bioimageanalysis.extension.kernel.roi.roi2d.ROI2DPoint; +import org.bioimageanalysis.extension.kernel.roi.roi3d.ROI3DPoint; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.common.geom.rectangle.Rectangle5D; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginROIDescriptor; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.model.sequence.SequenceDataIterator; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.HashMap; @@ -41,6 +45,8 @@ import java.util.Map; * * @author Daniel Felipe Gonzalez Obando */ +@IcyPluginName("ROI Intensity Center Descriptor") +@IcyPluginIcon(path = "/roi-stat.png") public class ROIIntensityCenterDescriptorsPlugin extends Plugin implements PluginROIDescriptor { public static final String ID_MASS_CENTER_X = ROIIntensityCenterXDescriptor.ID; public static final String ID_MASS_CENTER_Y = ROIIntensityCenterYDescriptor.ID; @@ -58,7 +64,8 @@ public class ROIIntensityCenterDescriptorsPlugin extends Plugin implements Plugi * @return Position of the intensity center. * @throws InterruptedException If the thread is interrupted during the descriptors computation. */ - public static Point3D computeIntensityCenter(final ROI roi, final Sequence sequence) throws InterruptedException { + @Contract("_, _ -> new") + public static @NotNull Point3D computeIntensityCenter(final @NotNull ROI roi, final Sequence sequence) throws InterruptedException { final Rectangle5D bounds = roi.getBounds5D(); // special case of empty bounds ? --> return position @@ -142,5 +149,4 @@ public class ROIIntensityCenterDescriptorsPlugin extends Plugin implements Plugi return result; } - } diff --git a/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterXDescriptor.java b/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterXDescriptor.java index f97178f..fd462e2 100644 --- a/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterXDescriptor.java +++ b/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterXDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,12 +18,12 @@ package plugins.adufour.roi.intensitycenter; -import icy.roi.ROI; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.sequence.SequenceEvent; -import icy.sequence.SequenceEvent.SequenceEventSourceType; -import icy.type.point.Point3D; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.model.sequence.SequenceEvent; +import org.jetbrains.annotations.NotNull; public class ROIIntensityCenterXDescriptor extends ROIDescriptor { public static final String ID = "Intensity center X"; @@ -48,8 +48,8 @@ public class ROIIntensityCenterXDescriptor extends ROIDescriptor { } @Override - public boolean needRecompute(final SequenceEvent change) { - return (change.getSourceType() == SequenceEventSourceType.SEQUENCE_DATA); + public boolean needRecompute(final @NotNull SequenceEvent change) { + return (change.getSourceType() == SequenceEvent.SequenceEventSourceType.SEQUENCE_DATA); } @Override diff --git a/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterYDescriptor.java b/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterYDescriptor.java index 8e55be6..b6eab03 100644 --- a/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterYDescriptor.java +++ b/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterYDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,12 +18,12 @@ package plugins.adufour.roi.intensitycenter; -import icy.roi.ROI; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.sequence.SequenceEvent; -import icy.sequence.SequenceEvent.SequenceEventSourceType; -import icy.type.point.Point3D; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.model.sequence.SequenceEvent; +import org.jetbrains.annotations.NotNull; public class ROIIntensityCenterYDescriptor extends ROIDescriptor { public static final String ID = "Intensity center Y"; @@ -48,8 +48,8 @@ public class ROIIntensityCenterYDescriptor extends ROIDescriptor { } @Override - public boolean needRecompute(final SequenceEvent change) { - return (change.getSourceType() == SequenceEventSourceType.SEQUENCE_DATA); + public boolean needRecompute(final @NotNull SequenceEvent change) { + return (change.getSourceType() == SequenceEvent.SequenceEventSourceType.SEQUENCE_DATA); } @Override diff --git a/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterZDescriptor.java b/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterZDescriptor.java index 55ad853..ecae5f7 100644 --- a/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterZDescriptor.java +++ b/src/main/java/plugins/adufour/roi/intensitycenter/ROIIntensityCenterZDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,12 +18,12 @@ package plugins.adufour.roi.intensitycenter; -import icy.roi.ROI; -import icy.roi.ROIDescriptor; -import icy.sequence.Sequence; -import icy.sequence.SequenceEvent; -import icy.sequence.SequenceEvent.SequenceEventSourceType; -import icy.type.point.Point3D; +import org.bioimageanalysis.icy.common.geom.point.Point3D; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.roi.ROIDescriptor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.model.sequence.SequenceEvent; +import org.jetbrains.annotations.NotNull; public class ROIIntensityCenterZDescriptor extends ROIDescriptor { public static final String ID = "Intensity center Z"; @@ -48,8 +48,8 @@ public class ROIIntensityCenterZDescriptor extends ROIDescriptor { } @Override - public boolean needRecompute(final SequenceEvent change) { - return (change.getSourceType() == SequenceEventSourceType.SEQUENCE_DATA); + public boolean needRecompute(final @NotNull SequenceEvent change) { + return (change.getSourceType() == SequenceEvent.SequenceEventSourceType.SEQUENCE_DATA); } @Override diff --git a/src/main/resources/roi-stat.png b/src/main/resources/roi-stat.png new file mode 100644 index 0000000000000000000000000000000000000000..cefa2397373f100d897d68f81231284f3e2aaccc GIT binary patch literal 19128 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 literal 0 HcmV?d00001 -- GitLab