diff --git a/.gitignore b/.gitignore index 3d47f986c41db29ec6dc0d5036bf760b3a1cf366..57f16fb67c1b1589981416b323d7a9debc728665 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 c87f039f6ba90873d278f98dcaf48f816197047b..cff32f46eb3e85b84872f2f0ddde035faf5c6f7e 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 462a5052e9994c91a15a127813fc07b7a494acc6..4d9beaeaa46d9c7efed2e5561bb576f9ad9d9522 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 Binary files /dev/null and b/src/main/resources/path-intensity-profiler.png differ