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