From 90a52b84a7f4b378f9981331aa054f01209ab0b1 Mon Sep 17 00:00:00 2001
From: Thomas <thomas.musset@pasteur.fr>
Date: Sat, 6 Jul 2024 18:24:02 +0200
Subject: [PATCH] updated pom to v2.0.0-a.1, fix classes accordingly to new
 architecture, added icon, updated .gitignore

---
 .gitignore                                    |  41 ++-
 pom.xml                                       |  41 +--
 .../roi/quantify/PathIntensityProfiler.java   | 324 +++++++++---------
 .../resources/path-intensity-profiler.png     | Bin 0 -> 9625 bytes
 4 files changed, 206 insertions(+), 200 deletions(-)
 create mode 100644 src/main/resources/path-intensity-profiler.png

diff --git a/.gitignore b/.gitignore
index 3d47f98..57f16fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,41 @@
-.idea/
+/build*
+/workspace
+setting.xml
+release/
 target/
-.settings/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+icy.log
+
+### IntelliJ IDEA ###
+.idea/
+*.iws
 *.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
 .project
-.classpath
\ 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 c87f039..cff32f4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,23 +6,21 @@
 
     <!-- Inherited Icy Parent POM -->
     <parent>
-		<artifactId>pom-icy</artifactId>
+        <artifactId>pom-icy</artifactId>
         <groupId>org.bioimageanalysis.icy</groupId>
-        <version>2.1.0</version>
-	</parent>
+        <version>3.0.0-a.1</version>
+    </parent>
 
     <!-- Project Information -->
     <artifactId>path-intensity-profiler</artifactId>
-    <version>1.2.3</version>
-
-    <packaging>jar</packaging>
+    <version>2.0.0-a.1</version>
 
     <name>Path Intensity Profiler</name>
     <description>
         This plugin will compute the intensity profile of ROI contour along Z (3D) and T (timelaps) dimension. It generates a workbook document containing all the intensity values which can be used to display graph or be saved as XLSX or CSV format.
         The plugin is compatible with Protocols.
     </description>
-    <url>http://icy.bioimageanalysis.org/plugin/path-intensity-profil/</url>
+    <url>https://icy.bioimageanalysis.org/plugin/path-intensity-profil/</url>
     <inceptionYear>2020</inceptionYear>
 
     <organization>
@@ -56,40 +54,24 @@
         </developer>
     </developers>
 
-    <!-- Project properties -->
-    <properties>
-
-    </properties>
-
-    <profiles>
-        <profile>
-            <id>icy-plugin</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-        </profile>
-    </profiles>
-
     <!-- List of project's dependencies -->
     <dependencies>
-        <!-- The core of Icy -->
         <dependency>
             <groupId>org.bioimageanalysis.icy</groupId>
-            <artifactId>icy-kernel</artifactId>
-			<version>${icy-kernel.version}</version>
+            <artifactId>kernel-extensions</artifactId>
         </dependency>
-
-        <!-- The EzPlug library, simplifies writing UI for Icy plugins. -->
         <dependency>
             <groupId>org.bioimageanalysis.icy</groupId>
             <artifactId>ezplug</artifactId>
-			<version>${ezplug.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>protocols</artifactId>
         </dependency>
 
         <dependency>
             <groupId>org.bioimageanalysis.icy</groupId>
             <artifactId>workbooks</artifactId>
-			<version>${workbooks.version}</version>
         </dependency>
     </dependencies>
 
@@ -97,8 +79,7 @@
     <repositories>
         <repository>
             <id>icy</id>
-            <name>Icy's Nexus</name>
-            <url>https://icy-nexus.pasteur.fr/repository/Icy/</url>
+            <url>https://nexus-icy.pasteur.cloud/repository/icy/</url>
         </repository>
     </repositories>
 </project>
diff --git a/src/main/java/plugins/stef/roi/quantify/PathIntensityProfiler.java b/src/main/java/plugins/stef/roi/quantify/PathIntensityProfiler.java
index 462a505..4d9beae 100644
--- a/src/main/java/plugins/stef/roi/quantify/PathIntensityProfiler.java
+++ b/src/main/java/plugins/stef/roi/quantify/PathIntensityProfiler.java
@@ -1,30 +1,47 @@
-package plugins.stef.roi.quantify;
-
-import java.awt.Dimension;
-import java.awt.Point;
-import java.awt.geom.Point2D;
-import java.util.ArrayList;
-import java.util.List;
+/*
+ * Copyright (c) 2010-2024. Institut Pasteur.
+ *
+ * This file is part of Icy.
+ * Icy is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Icy is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Icy. If not, see <https://www.gnu.org/licenses/>.
+ */
 
-import javax.swing.JComponent;
+package plugins.stef.roi.quantify;
 
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
-
-import icy.gui.dialog.MessageDialog;
-import icy.gui.frame.progress.FailedAnnounceFrame;
-import icy.math.Line3DIterator;
-import icy.math.MathUtil;
-import icy.roi.BooleanMask2D;
-import icy.roi.ROI;
-import icy.roi.ROI2D;
-import icy.roi.ROI3D;
-import icy.sequence.Sequence;
-import icy.system.IcyExceptionHandler;
-import icy.type.collection.CollectionUtil;
-import icy.type.geom.Line3D;
-import icy.type.point.Point3D;
-import icy.type.point.Point4D;
-import icy.type.rectangle.Rectangle5D;
+import org.bioimageanalysis.extension.kernel.roi.roi2d.*;
+import org.bioimageanalysis.extension.kernel.roi.roi3d.ROI3DLine;
+import org.bioimageanalysis.extension.kernel.roi.roi3d.ROI3DPoint;
+import org.bioimageanalysis.extension.kernel.roi.roi3d.ROI3DPolyLine;
+import org.bioimageanalysis.extension.kernel.roi.roi3d.ROI3DShape;
+import org.bioimageanalysis.icy.common.collection.CollectionUtil;
+import org.bioimageanalysis.icy.common.geom.line.Line3D;
+import org.bioimageanalysis.icy.common.geom.line.Line3DIterator;
+import org.bioimageanalysis.icy.common.geom.point.Point3D;
+import org.bioimageanalysis.icy.common.geom.point.Point4D;
+import org.bioimageanalysis.icy.common.geom.rectangle.Rectangle5D;
+import org.bioimageanalysis.icy.common.math.MathUtil;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName;
+import org.bioimageanalysis.icy.gui.dialog.MessageDialog;
+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.mask.BooleanMask2D;
+import org.bioimageanalysis.icy.model.sequence.Sequence;
+import org.bioimageanalysis.icy.system.logging.IcyLogger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import plugins.adufour.blocks.lang.Block;
 import plugins.adufour.blocks.util.VarList;
 import plugins.adufour.ezplug.EzGUI;
@@ -38,34 +55,31 @@ import plugins.adufour.vars.lang.VarWorkbook;
 import plugins.adufour.workbooks.IcySpreadSheet;
 import plugins.adufour.workbooks.Workbooks;
 import plugins.adufour.workbooks.Workbooks.WorkbookFormat;
-import plugins.kernel.roi.roi2d.ROI2DLine;
-import plugins.kernel.roi.roi2d.ROI2DPoint;
-import plugins.kernel.roi.roi2d.ROI2DPolyLine;
-import plugins.kernel.roi.roi2d.ROI2DPolygon;
-import plugins.kernel.roi.roi2d.ROI2DRectangle;
-import plugins.kernel.roi.roi2d.ROI2DShape;
-import plugins.kernel.roi.roi3d.ROI3DLine;
-import plugins.kernel.roi.roi3d.ROI3DPoint;
-import plugins.kernel.roi.roi3d.ROI3DPolyLine;
-import plugins.kernel.roi.roi3d.ROI3DShape;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 
 /**
  * Path profiler plugin class.<br>
  * Computes intensity profil along path (contour for area ROI) for the given list of ROI and return result in XLSX workbook format
  * where we have one sheet per ROI.
- * 
- * @author Stephane
+ *
+ * @author Stephane Dallongeville
  */
-public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
-{
+@IcyPluginName("Path Intensity Profiler")
+@IcyPluginIcon(path = "/path-intensity-profiler.png")
+public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable {
     // VAR
     public final VarSequence varSequence;
     public final VarROIArray varRois;
     public final EzVarBoolean varRealUnit;
     public final VarWorkbook varWorkbook;
 
-    public PathIntensityProfiler()
-    {
+    public PathIntensityProfiler() {
         super();
 
         varSequence = new VarSequence("Sequence", null);
@@ -75,8 +89,7 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
     }
 
     @Override
-    protected void initialize()
-    {
+    protected void initialize() {
         final WorkbookEditor viewer = (WorkbookEditor) varWorkbook.createVarViewer();
 
         viewer.setEnabled(true);
@@ -90,71 +103,60 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
     }
 
     @Override
-    public void declareInput(VarList inputMap)
-    {
+    public void declareInput(@NotNull final VarList inputMap) {
         inputMap.add("sequence", varSequence);
         inputMap.add("rois", varRois);
         inputMap.add("realUnit", varRealUnit.getVariable());
     }
 
     @Override
-    public void declareOutput(VarList outputMap)
-    {
+    public void declareOutput(@NotNull final VarList outputMap) {
         outputMap.add("workbook", varWorkbook);
     }
 
     @Override
-    public void clean()
-    {
+    public void clean() {
         // set empty workbook to release resources
         varWorkbook.setValue(Workbooks.createEmptyWorkbook(WorkbookFormat.XLSX));
     }
 
     @Override
-    protected void execute()
-    {
+    protected void execute() {
         // set empty workbook by default (also release resources from previous run)
         varWorkbook.setValue(Workbooks.createEmptyWorkbook(WorkbookFormat.XLSX));
 
         // interactive mode (not in protocol)
-        if (!isHeadLess())
-        {
+        if (!isHeadLess()) {
             final Sequence seq = getActiveSequence();
 
             // set variables
-            if (seq != null)
-            {
+            if (seq != null) {
                 varSequence.setValue(seq);
                 varRois.setValue(seq.getROIs().toArray(new ROI[0]));
             }
-            else
-            {
+            else {
                 // inform user
-                MessageDialog.showDialog("You need to open an image containing ROI(s).",
-                        MessageDialog.INFORMATION_MESSAGE);
+                MessageDialog.showDialog("You need to open an image containing ROI(s).", MessageDialog.INFORMATION_MESSAGE);
                 return;
             }
         }
 
         final Sequence sequence = varSequence.getValue();
 
-        if (sequence != null)
-        {
+        if (sequence != null) {
             final ROI[] rois = varRois.getValue();
-            final List<ROI> validRois = new ArrayList<ROI>();
+            final List<ROI> validRois = new ArrayList<>();
 
             // we can only compute path intensity on path ROI or 2D ROI
-            for (ROI roi : rois)
-            {
+            for (final ROI roi : rois) {
                 if (roi instanceof ROI2D)
                     validRois.add(roi);
-                // else if ((roi instanceof ROI3DPoint) || (roi instanceof ROI3DLine) || (roi instanceof ROI3DPolyLine))
+                    // else if ((roi instanceof ROI3DPoint) || (roi instanceof ROI3DLine) || (roi instanceof ROI3DPolyLine))
                 else if (roi instanceof ROI3D)
                     validRois.add(roi);
             }
 
-            if (validRois.size() == 0)
-            {
+            if (validRois.isEmpty()) {
                 // inform user
                 if (!isHeadLess())
                     MessageDialog.showDialog(
@@ -167,25 +169,20 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
             if (!isHeadLess())
                 getUI().setProgressBarMessage("Computing...");
 
-            try
-            {
+            try {
                 // set result
                 varWorkbook.setValue(
-                        getPathIntensityProfil(sequence, validRois, varRealUnit.getValue().booleanValue(), getUI()));
+                        getPathIntensityProfil(sequence, validRois, varRealUnit.getValue(), getUI()));
             }
-            catch (IllegalArgumentException e)
-            {
-                IcyExceptionHandler.handleException(e, true);
+            catch (final IllegalArgumentException e) {
+                IcyLogger.error(this.getClass(), e);
             }
-            catch (InterruptedException e)
-            {
-                new FailedAnnounceFrame("Path intensity profile process interrupted..");
+            catch (final InterruptedException e) {
+                IcyLogger.error(this.getClass(), e, "Path intensity profile process interrupted.");
             }
-            finally
-            {
+            finally {
                 // interactive mode
-                if (!isHeadLess())
-                {
+                if (!isHeadLess()) {
                     getUI().setProgressBarMessage("Done");
                     getUI().setProgressBarValue(0);
                 }
@@ -195,13 +192,13 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
 
     /**
      * Computes intensity profil along path (contour for area ROI) for the given list of ROI.
-     * 
+     *
      * @return result in a XLSX workbook where we have one sheet per ROI.
      * @throws InterruptedException
      */
-    private static XSSFWorkbook getPathIntensityProfil(Sequence sequence, List<ROI> rois, boolean useRealUnit, EzGUI ui)
-            throws InterruptedException
-    {
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private static XSSFWorkbook getPathIntensityProfil(final Sequence sequence, @NotNull final List<ROI> rois, final boolean useRealUnit, final EzGUI ui)
+            throws InterruptedException {
         final XSSFWorkbook result = (XSSFWorkbook) Workbooks.createEmptyWorkbook(WorkbookFormat.XLSX);
 
         // nothing to do
@@ -214,15 +211,13 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
 
         final Point4D.Double unitScale = new Point4D.Double();
 
-        if (useRealUnit)
-        {
+        if (useRealUnit) {
             unitScale.x = sequence.getPixelSizeX();
             unitScale.y = sequence.getPixelSizeY();
             unitScale.z = sequence.getPixelSizeZ();
             unitScale.t = sequence.getTimeInterval();
         }
-        else
-        {
+        else {
             unitScale.x = 1d;
             unitScale.y = 1d;
             unitScale.z = 1d;
@@ -233,11 +228,10 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
 
         int roiIndex = 0;
         // can process now
-        for (ROI roi : rois)
-        {
+        for (final ROI roi : rois) {
             // create a sheet for each ROI
             final IcySpreadSheet sh = new IcySpreadSheet(result.createSheet(
-                    String.format("S%04d - ", Integer.valueOf(roiIndex)) + roi.getName().replace(':', '_')));
+                    String.format("S%04d - ", roiIndex) + roi.getName().replace(':', '_')));
 
             // write columns headers
             int indCol = 0;
@@ -254,11 +248,9 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
             // start
             int row = 1;
 
-            for (int t = 0; t < sizeT; t++)
-            {
+            for (int t = 0; t < sizeT; t++) {
                 // in case of 2D ROI, we need to handle the specific T = -1 (ALL)
-                if (roi instanceof ROI2D)
-                {
+                if (roi instanceof ROI2D) {
                     final int roiT = ((ROI2D) roi).getT();
 
                     // ROI not on current T position ? --> next
@@ -266,8 +258,7 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
                         continue;
                 }
                 // in case of 3D ROI, we need to handle the specific T = -1 (ALL)
-                else if (roi instanceof ROI3D)
-                {
+                else if (roi instanceof ROI3D) {
                     final int roiT = ((ROI3D) roi).getT();
 
                     // ROI not on current T position ? --> next
@@ -278,11 +269,9 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
                 else if ((roiBounds.getMinT() > t) || (roiBounds.getMaxT() < t))
                     continue;
 
-                for (int z = 0; z < sizeZ; z++)
-                {
+                for (int z = 0; z < sizeZ; z++) {
                     // in case of 2D ROI, we need to handle the specific Z = -1 (ALL)
-                    if (roi instanceof ROI2D)
-                    {
+                    if (roi instanceof ROI2D) {
                         final int roiZ = ((ROI2D) roi).getZ();
 
                         // ROI not on current Z position ? --> next
@@ -300,15 +289,13 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
                     // point index
                     int ptIndex = 0;
 
-                    for (int c = 0, zStartRow = row; c < sizeC; c++)
-                    {
+                    for (int c = 0, zStartRow = row; c < sizeC; c++) {
                         // ROI does not contain current C position ? --> next
                         if ((roiBounds.getMinC() > c) || (roiBounds.getMaxC() < c))
                             continue;
 
                         // display progress
-                        if (ui != null)
-                        {
+                        if (ui != null) {
                             // compute progress bar avoid integer type loss
                             double progress = roiIndex;
                             progress *= sizeT * sizeZ * sizeC;
@@ -330,58 +317,58 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
                         // 3D line path ROI ?
                         if ((roi instanceof ROI3DPoint) || (roi instanceof ROI3DLine) || (roi instanceof ROI3DPolyLine))
                             pts = ((ROI3DShape) roi).getPoints();
-                        // 2D straight line path / area ROI (we shouldn't consider Arc / Ellipse shape ROI here) ?
+                            // 2D straight line path / area ROI (we shouldn't consider Arc / Ellipse shape ROI here) ?
                         else if ((roi instanceof ROI2DPoint) || (roi instanceof ROI2DLine)
                                 || (roi instanceof ROI2DPolyLine) || (roi instanceof ROI2DPolygon)
                                 || (roi instanceof ROI2DRectangle))
                             pts = ((ROI2DShape) roi).getPoints();
-                        else
-                        {
+                        else {
                             // initialize
-                            pts = new ArrayList();
+                            pts = new ArrayList<>();
                             // no link here
                             interpolate = false;
 
                             // 2D ROI so we can use the contour of the mask
-                            if (roi instanceof ROI2D)
-                            {
+                            if (roi instanceof ROI2D) {
                                 /// iterate over all component (separate object if any)
-                                for (BooleanMask2D mask : ((ROI2D) roi).getBooleanMask(true).getComponents())
+                                for (final BooleanMask2D mask : ((ROI2D) roi).getBooleanMask(true).getComponents())
                                     // add points in contour order
                                     pts.addAll(mask.getConnectedContourPoints());
                             }
                             // 3D ROI so we can use the surface area points
-                            else if (roi instanceof ROI3D)
-                            {
+                            else if (roi instanceof ROI3D) {
                                 // add points in whatever order
-                                pts.addAll(
-                                        CollectionUtil.asList(((ROI3D) roi).getBooleanMask(true).getContourPoints()));
+                                pts.addAll(CollectionUtil.asList(((ROI3D) roi).getBooleanMask(true).getContourPoints()));
                             }
                             // not supported
-                            else
-                            {
-
+                            else {
+                                IcyLogger.warn(PathIntensityProfiler.class, "Not supported.");
                             }
                         }
 
                         // control points that need interpolation in-between ?
-                        if (interpolate)
-                        {
+                        if (interpolate) {
                             // get the first obj
-                            Point3D startPt = getPoint3D(pts.get(0), z);
+                            Point3D startPt = getPoint3D(pts.getFirst(), z);
 
                             // specific case where we have only a single point
                             if (pts.size() == 1)
-                                writeRow(sh, row++, ptIndex++, t, c, sequence, startPt, unitScale);
-                            else
-                            {
+                                writeRow(
+                                        sh,
+                                        row++,
+                                        ptIndex++,
+                                        t,
+                                        c,
+                                        sequence,
+                                        Objects.requireNonNull(startPt),
+                                        unitScale
+                                );
+                            else {
                                 // otherwise we use a special way to interpolate through 2 controls points
-                                for (int i = 1; i < pts.size(); i++)
-                                {
+                                for (int i = 1; i < pts.size(); i++) {
                                     final Point3D endPt = getPoint3D(pts.get(i), z);
                                     // get line iterator
-                                    final Line3DIterator lineIt = new Line3DIterator(new Line3D(startPt, endPt), 1d,
-                                            false);
+                                    final Line3DIterator lineIt = new Line3DIterator(new Line3D(startPt, endPt), 1d, false);
 
                                     // iterate over line points
                                     while (lineIt.hasNext())
@@ -393,10 +380,18 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
                             }
                         }
                         // contour points ?
-                        else
-                        {
-                            for (int i = 0; i < pts.size(); i++)
-                                writeRow(sh, row++, ptIndex++, t, c, sequence, getPoint3D(pts.get(i), z), unitScale);
+                        else {
+                            for (final Object pt : pts)
+                                writeRow(
+                                        sh,
+                                        row++,
+                                        ptIndex++,
+                                        t,
+                                        c,
+                                        sequence,
+                                        Objects.requireNonNull(getPoint3D(pt, z)),
+                                        unitScale
+                                );
                         }
                     }
                 }
@@ -411,50 +406,45 @@ public class PathIntensityProfiler extends EzPlug implements Block, EzStoppable
 
     /**
      * Computes intensity profil along path (contour for area ROI) for the given list of ROI.<br>
-     * 
+     *
      * @return result in a XLSX workbook where we have one sheet per ROI.
      * @throws InterruptedException
      */
-    public static XSSFWorkbook getPathIntensityProfil(Sequence sequence, List<ROI> rois, boolean useRealUnit)
-            throws InterruptedException
-    {
+    public static XSSFWorkbook getPathIntensityProfil(final Sequence sequence, final List<ROI> rois, final boolean useRealUnit) throws InterruptedException {
         return getPathIntensityProfil(sequence, rois, useRealUnit, null);
     }
 
-    private static Point3D getPoint3D(Object obj, int curZ)
-    {
-        final Point3D result;
-
-        if (obj instanceof Point)
-        {
-            final Point pt2d = (Point) obj;
-            result = new Point3D.Double(pt2d.getX(), pt2d.getY(), curZ);
-        }
-        else if (obj instanceof Point2D)
-        {
-            final Point2D pt2d = (Point2D) obj;
-            result = new Point3D.Double(pt2d.getX(), pt2d.getY(), curZ);
-        }
-        else if (obj instanceof Point3D)
-            result = (Point3D) obj;
-        else
-            result = null;
-
-        return result;
+    private static @Nullable Point3D getPoint3D(final Object obj, final int curZ) {
+        return switch (obj) {
+            case final Point pt2d -> new Point3D.Double(pt2d.getX(), pt2d.getY(), curZ);
+            case final Point2D pt2d -> new Point3D.Double(pt2d.getX(), pt2d.getY(), curZ);
+            case final Point3D point3D -> point3D;
+            case null, default -> null;
+        };
     }
 
-    private static void writeRow(IcySpreadSheet sh, int row, int ind, int t, int c, Sequence sequence, Point3D pt,
-            Point4D unitScale)
-    {
-        sh.setValue(row, 0, Integer.valueOf(ind));
-        sh.setValue(row, 1, Double.valueOf(MathUtil.roundSignificant(pt.getX() * unitScale.getX(), 5, true)));
-        sh.setValue(row, 2, Double.valueOf(MathUtil.roundSignificant(pt.getY() * unitScale.getY(), 5, true)));
-        sh.setValue(row, 3, Double.valueOf(MathUtil.roundSignificant(pt.getZ() * unitScale.getZ(), 5, true)));
+    private static void writeRow(
+            @NotNull final IcySpreadSheet sh,
+            final int row,
+            final int ind,
+            final int t,
+            final int c,
+            final Sequence sequence,
+            @NotNull final Point3D pt,
+            @NotNull final Point4D unitScale
+    ) {
+        sh.setValue(row, 0, ind);
+        sh.setValue(row, 1, MathUtil.roundSignificant(pt.getX() * unitScale.getX(), 5, true));
+        sh.setValue(row, 2, MathUtil.roundSignificant(pt.getY() * unitScale.getY(), 5, true));
+        sh.setValue(row, 3, MathUtil.roundSignificant(pt.getZ() * unitScale.getZ(), 5, true));
         if (unitScale.getT() == 1d)
-            sh.setValue(row, 4, Integer.valueOf(t));
+            sh.setValue(row, 4, t);
         else
-            sh.setValue(row, 4, Double.valueOf(MathUtil.roundSignificant(t * unitScale.getT(), 5, true)));
-        sh.setValue(row, 5 + c, Double.valueOf(MathUtil
-                .roundSignificant(sequence.getDataInterpolated(t, pt.getZ(), c, pt.getY(), pt.getX()), 5, true)));
+            sh.setValue(row, 4, MathUtil.roundSignificant(t * unitScale.getT(), 5, true));
+        sh.setValue(row, 5 + c, MathUtil.roundSignificant(
+                sequence.getDataInterpolated(t, pt.getZ(), c, pt.getY(), pt.getX()),
+                5,
+                true
+        ));
     }
 }
diff --git a/src/main/resources/path-intensity-profiler.png b/src/main/resources/path-intensity-profiler.png
new file mode 100644
index 0000000000000000000000000000000000000000..db2f4ef1363e1fe984098fd2c948ebba18815e8c
GIT binary patch
literal 9625
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
P00000NkvXXu0mjf_kE3u

literal 0
HcmV?d00001

-- 
GitLab