From 6eae5605b03428cae0141feb87164c56f9f6919e Mon Sep 17 00:00:00 2001
From: Amandine Tournay <amandine.tournay@pasteur.fr>
Date: Tue, 9 Mar 2021 18:58:55 +0100
Subject: [PATCH] Added project

---
 .gitignore                                    |   6 +
 pom.xml                                       |  98 ++++
 .../roi/quantify/PathIntensityProfiler.java   | 446 ++++++++++++++++++
 3 files changed, 550 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 pom.xml
 create mode 100644 src/main/java/plugins/stef/roi/quantify/PathIntensityProfiler.java

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3d47f98
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.idea/
+target/
+.settings/
+*.iml
+.project
+.classpath
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..7900d80
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <!-- Inherited Icy Parent POM -->
+    <parent>
+        <groupId>org.bioimageanalysis.icy</groupId>
+        <artifactId>parent-pom-plugin</artifactId>
+        <version>1.0.3</version>
+    </parent>
+
+    <!-- Project Information -->
+    <artifactId>path-intensity-profiler</artifactId>
+    <version>1.2.1.</version>
+
+    <packaging>jar</packaging>
+
+    <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>
+    <inceptionYear>2020</inceptionYear>
+
+    <organization>
+        <name>Institut Pasteur</name>
+        <url>https://pasteur.fr</url>
+    </organization>
+
+    <licenses>
+        <license>
+            <name>GNU GPLv3</name>
+            <url>https://www.gnu.org/licenses/gpl-3.0.en.html</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <developers>
+        <developer>
+            <id>sdallongeville</id>
+            <name>Stéphane Dallongeville</name>
+            <url>https://research.pasteur.fr/fr/member/stephane-dallongeville/</url>
+            <roles>
+                <role>founder</role>
+                <role>lead</role>
+                <role>architect</role>
+                <role>developer</role>
+                <role>debugger</role>
+                <role>tester</role>
+                <role>maintainer</role>
+                <role>support</role>
+            </roles>
+        </developer>
+    </developers>
+
+    <!-- Project properties -->
+    <properties>
+
+    </properties>
+
+    <!-- Project build configuration -->
+    <build>
+
+    </build>
+
+    <!-- List of project's dependencies -->
+    <dependencies>
+        <!-- The core of Icy -->
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>icy-kernel</artifactId>
+        </dependency>
+
+        <!-- The EzPlug library, simplifies writing UI for Icy plugins. -->
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>ezplug</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>workbooks</artifactId>
+            <version>3.4.10</version>
+        </dependency>
+    </dependencies>
+
+    <!-- Icy Maven repository (to find parent POM) -->
+    <repositories>
+        <repository>
+            <id>icy</id>
+            <name>Icy's Nexus</name>
+            <url>https://icy-nexus.pasteur.fr/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
new file mode 100644
index 0000000..8e07b21
--- /dev/null
+++ b/src/main/java/plugins/stef/roi/quantify/PathIntensityProfiler.java
@@ -0,0 +1,446 @@
+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;
+
+import javax.swing.JComponent;
+
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import icy.gui.dialog.MessageDialog;
+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.type.collection.CollectionUtil;
+import icy.type.geom.Line3D;
+import icy.type.point.Point3D;
+import icy.type.point.Point4D;
+import icy.type.rectangle.Rectangle5D;
+import plugins.adufour.blocks.lang.Block;
+import plugins.adufour.blocks.util.VarList;
+import plugins.adufour.ezplug.EzGUI;
+import plugins.adufour.ezplug.EzPlug;
+import plugins.adufour.ezplug.EzStoppable;
+import plugins.adufour.ezplug.EzVarBoolean;
+import plugins.adufour.vars.gui.swing.WorkbookEditor;
+import plugins.adufour.vars.lang.VarROIArray;
+import plugins.adufour.vars.lang.VarSequence;
+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;
+
+/**
+ * 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
+ */
+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()
+    {
+        super();
+
+        varSequence = new VarSequence("Sequence", null);
+        varRois = new VarROIArray("Roi(s)");
+        varRealUnit = new EzVarBoolean("Use real unit", false);
+        varWorkbook = new VarWorkbook("WorkBook", Workbooks.createEmptyWorkbook(WorkbookFormat.XLSX));
+    }
+
+    @Override
+    protected void initialize()
+    {
+        final WorkbookEditor viewer = (WorkbookEditor) varWorkbook.createVarViewer();
+
+        viewer.setEnabled(true);
+        viewer.setFirstRowAsHeader(true);
+
+        final JComponent jc = viewer.getEditorComponent();
+        jc.setPreferredSize(new Dimension(400, 300));
+
+        addComponent(jc);
+        addEzComponent(varRealUnit);
+    }
+
+    @Override
+    public void declareInput(VarList inputMap)
+    {
+        inputMap.add("sequence", varSequence);
+        inputMap.add("rois", varRois);
+        inputMap.add("realUnit", varRealUnit.getVariable());
+    }
+
+    @Override
+    public void declareOutput(VarList outputMap)
+    {
+        outputMap.add("workbook", varWorkbook);
+    }
+
+    @Override
+    public void clean()
+    {
+        // set empty workbook to release resources
+        varWorkbook.setValue(Workbooks.createEmptyWorkbook(WorkbookFormat.XLSX));
+    }
+
+    @Override
+    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())
+        {
+            final Sequence seq = getActiveSequence();
+
+            // set variables
+            if (seq != null)
+            {
+                varSequence.setValue(seq);
+                varRois.setValue(seq.getROIs().toArray(new ROI[0]));
+            }
+            else
+            {
+                // inform user
+                MessageDialog.showDialog("You need to open an image containing ROI(s).",
+                        MessageDialog.INFORMATION_MESSAGE);
+                return;
+            }
+        }
+
+        final Sequence sequence = varSequence.getValue();
+
+        if (sequence != null)
+        {
+            final ROI[] rois = varRois.getValue();
+            final List<ROI> validRois = new ArrayList<ROI>();
+
+            // we can only compute path intensity on path ROI or 2D ROI
+            for (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 ROI3D)
+                    validRois.add(roi);
+            }
+
+            if (validRois.size() == 0)
+            {
+                // inform user
+                if (!isHeadLess())
+                    MessageDialog.showDialog(
+                            "The selected Sequence doesn't contain any ROI where we can compute intensity profil.",
+                            MessageDialog.INFORMATION_MESSAGE);
+                return;
+            }
+
+            // interactive mode
+            if (!isHeadLess())
+                getUI().setProgressBarMessage("Computing...");
+
+            try
+            {
+                // set result
+                varWorkbook.setValue(
+                        getPathIntensityProfil(sequence, validRois, varRealUnit.getValue().booleanValue(), getUI()));
+            }
+            finally
+            {
+                // interactive mode
+                if (!isHeadLess())
+                {
+                    getUI().setProgressBarMessage("Done");
+                    getUI().setProgressBarValue(0);
+                }
+            }
+        }
+    }
+
+    /**
+     * 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.
+     */
+    private static XSSFWorkbook getPathIntensityProfil(Sequence sequence, List<ROI> rois, boolean useRealUnit, EzGUI ui)
+    {
+        final XSSFWorkbook result = (XSSFWorkbook) Workbooks.createEmptyWorkbook(WorkbookFormat.XLSX);
+
+        // nothing to do
+        if (rois.isEmpty())
+            return result;
+
+        final int sizeT = sequence.getSizeT();
+        final int sizeZ = sequence.getSizeZ();
+        final int sizeC = sequence.getSizeC();
+
+        final Point4D.Double unitScale = new Point4D.Double();
+
+        if (useRealUnit)
+        {
+            unitScale.x = sequence.getPixelSizeX();
+            unitScale.y = sequence.getPixelSizeY();
+            unitScale.z = sequence.getPixelSizeZ();
+            unitScale.t = sequence.getTimeInterval();
+        }
+        else
+        {
+            unitScale.x = 1d;
+            unitScale.y = 1d;
+            unitScale.z = 1d;
+            unitScale.t = 1d;
+        }
+
+        final double progressRatio = rois.size() * sizeT * sizeZ * sizeC;
+
+        int roiIndex = 0;
+        // can process now
+        for (ROI roi : rois)
+        {
+            // create a sheet for each ROI
+            final IcySpreadSheet sh = new IcySpreadSheet(
+                    result.createSheet(roi.getName() + String.format(" - S%04d", Integer.valueOf(roiIndex))));
+
+            // write columns headers
+            int indCol = 0;
+            sh.setValue(0, indCol++, "Point #");
+            sh.setValue(0, indCol++, "X" + (useRealUnit ? " (um)" : ""));
+            sh.setValue(0, indCol++, "Y" + (useRealUnit ? " (um)" : ""));
+            sh.setValue(0, indCol++, "Z" + (useRealUnit ? " (um)" : ""));
+            sh.setValue(0, indCol++, "T" + (useRealUnit ? " (s)" : ""));
+            for (int c = 0; c < sizeC; c++)
+                sh.setValue(0, indCol++, "ch " + c + " (" + sequence.getChannelName(c) + ")");
+
+            // ROI bounds
+            final Rectangle5D roiBounds = roi.getBounds5D();
+            // start
+            int row = 1;
+
+            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)
+                {
+                    final int roiT = ((ROI2D) roi).getT();
+
+                    // ROI not on current T position ? --> next
+                    if ((roiT != -1) && (roiT != t))
+                        continue;
+                }
+                // in case of 3D ROI, we need to handle the specific T = -1 (ALL)
+                else if (roi instanceof ROI3D)
+                {
+                    final int roiT = ((ROI3D) roi).getT();
+
+                    // ROI not on current T position ? --> next
+                    if ((roiT != -1) && (roiT != t))
+                        continue;
+                }
+                // ROI does not contain current T position ? --> next
+                else if ((roiBounds.getMinT() > t) || (roiBounds.getMaxT() < t))
+                    continue;
+
+                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)
+                    {
+                        final int roiZ = ((ROI2D) roi).getZ();
+
+                        // ROI not on current Z position ? --> next
+                        if ((roiZ != -1) && (roiZ != z))
+                            continue;
+                    }
+                    // otherwise we just need 1 iteration
+                    else if (z > 0)
+                        continue;
+
+                    // interrupt processing if wanted
+                    if (Thread.currentThread().isInterrupted())
+                        return result;
+
+                    // point index
+                    int ptIndex = 0;
+
+                    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)
+                        {
+                            // compute progress bar avoid integer type loss
+                            double progress = roiIndex;
+                            progress *= sizeT * sizeZ * sizeC;
+                            progress += t * sizeZ * sizeC;
+                            progress += z * sizeC;
+                            progress += c;
+                            progress /= progressRatio;
+
+                            ui.setProgressBarValue(progress);
+                        }
+
+                        // rewind to original Z start row when multi channel
+                        if (c > 0)
+                            row = zStartRow;
+
+                        final List pts;
+                        boolean interpolate = true;
+
+                        // 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) ?
+                        else if ((roi instanceof ROI2DPoint) || (roi instanceof ROI2DLine)
+                                || (roi instanceof ROI2DPolyLine) || (roi instanceof ROI2DPolygon)
+                                || (roi instanceof ROI2DRectangle))
+                            pts = ((ROI2DShape) roi).getPoints();
+                        else
+                        {
+                            // initialize
+                            pts = new ArrayList();
+                            // no link here
+                            interpolate = false;
+
+                            // 2D ROI so we can use the contour of the mask
+                            if (roi instanceof ROI2D)
+                            {
+                                /// iterate over all component (separate object if any)
+                                for (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)
+                            {
+                                // add points in whatever order
+                                pts.addAll(
+                                        CollectionUtil.asList(((ROI3D) roi).getBooleanMask(true).getContourPoints()));
+                            }
+                            // not supported
+                            else
+                            {
+
+                            }
+                        }
+
+                        // control points that need interpolation in-between ?
+                        if (interpolate)
+                        {
+                            // get the first obj
+                            Point3D startPt = getPoint3D(pts.get(0), z);
+
+                            // specific case where we have only a single point
+                            if (pts.size() == 1)
+                                writeRow(sh, row++, ptIndex++, t, c, sequence, startPt, unitScale);
+                            else
+                            {
+                                // otherwise we use a special way to interpolate through 2 controls points
+                                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);
+
+                                    // iterate over line points
+                                    while (lineIt.hasNext())
+                                        writeRow(sh, row++, ptIndex++, t, c, sequence, lineIt.next(), unitScale);
+
+                                    // done --> go next control point
+                                    startPt = endPt;
+                                }
+                            }
+                        }
+                        // contour points ?
+                        else
+                        {
+                            for (int i = 0; i < pts.size(); i++)
+                                writeRow(sh, row++, ptIndex++, t, c, sequence, getPoint3D(pts.get(i), z), unitScale);
+                        }
+                    }
+                }
+            }
+
+            roiIndex++;
+        }
+
+        return result;
+
+    }
+
+    /**
+     * 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.
+     */
+    public static XSSFWorkbook getPathIntensityProfil(Sequence sequence, List<ROI> rois, boolean useRealUnit)
+    {
+        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 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)));
+        if (unitScale.getT() == 1d)
+            sh.setValue(row, 4, Integer.valueOf(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)));
+    }
+}
-- 
GitLab