From 8ad1ce2217e67bb9ba9faccd84593cf8eb1c21df Mon Sep 17 00:00:00 2001 From: Thomas <thomas.musset@pasteur.fr> Date: Sun, 7 Jul 2024 19:06:57 +0200 Subject: [PATCH] updated pom to v2.0.0-a.1, fix classes accordingly to new architecture, added icon, updated .gitignore --- .gitignore | 46 ++- pom.xml | 11 +- .../icy/image/projection/ProjectionAxis.java | 11 +- .../projection/ProjectionCalculator.java | 290 ++++++++++-------- .../projection/ProjectionOperationType.java | 18 +- .../util/MessageProgressListener.java | 6 +- .../image/projection/util/SequenceCursor.java | 17 +- .../util/VolumetricImageCursor.java | 21 +- .../adufour/projection/Projection.java | 145 ++++----- .../adufour/projection/icon/icon_32.png | Bin 0 -> 10473 bytes 10 files changed, 328 insertions(+), 237 deletions(-) create mode 100644 src/main/resources/plugins/adufour/projection/icon/icon_32.png diff --git a/.gitignore b/.gitignore index 30c9a54..57f16fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,41 @@ +/build* +/workspace +setting.xml +release/ target/ -workspace/ -.settings/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +icy.log + +### IntelliJ IDEA ### .idea/ -setting.xml -.project +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated .classpath -*.prefs -*.jardesc -*.jar -**/.DS_Store \ No newline at end of file +.factorypath +.project +.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 8f4dc7a..09be7c0 100644 --- a/pom.xml +++ b/pom.xml @@ -8,12 +8,12 @@ <parent> <groupId>org.bioimageanalysis.icy</groupId> <artifactId>pom-icy</artifactId> - <version>2.2.0</version> + <version>3.0.0-a.1</version> </parent> <!-- Project Information --> <artifactId>intensity-projection</artifactId> - <version>2.0.0</version> + <version>2.0.0-a.1</version> <name>Intensity Projection plugin</name> <description>Intensity projection along depth or time with multiple algorithms: mean, max, median, variance, standard deviation, saturated sum. Projection can be restricted to ROI.</description> @@ -47,15 +47,11 @@ </developer> </developers> - <!-- List of project's dependencies --> <dependencies> - <!-- The EzPlug library, simplifies writing UI for Icy plugins. --> <dependency> <groupId>org.bioimageanalysis.icy</groupId> <artifactId>ezplug</artifactId> </dependency> - - <!-- The EzPlug library, simplifies writing UI for Icy plugins. --> <dependency> <groupId>org.bioimageanalysis.icy</groupId> <artifactId>protocols</artifactId> @@ -66,8 +62,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/org/bioimageanalysis/icy/image/projection/ProjectionAxis.java b/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionAxis.java index bc9d7de..a3a3799 100644 --- a/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionAxis.java +++ b/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionAxis.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,6 +18,9 @@ package org.bioimageanalysis.icy.image.projection; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + /** * Indicates the direction on which the projection should be performed. * @@ -28,12 +31,14 @@ public enum ProjectionAxis { private final String name; - ProjectionAxis(final String name) { + @Contract(pure = true) + ProjectionAxis(final @NotNull String name) { this.name = name; } + @Contract(pure = true) @Override - public String toString() { + public @NotNull String toString() { return name; } } diff --git a/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionCalculator.java b/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionCalculator.java index d96316f..e744a89 100644 --- a/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionCalculator.java +++ b/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionCalculator.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,17 @@ package org.bioimageanalysis.icy.image.projection; -import icy.image.IcyBufferedImage; -import icy.image.IcyBufferedImageCursor; -import icy.math.ArrayMath; -import icy.roi.ROI; -import icy.sequence.Sequence; -import icy.sequence.VolumetricImage; -import icy.type.DataType; -import icy.util.OMEUtil; +import org.bioimageanalysis.icy.common.math.ArrayMath; +import org.bioimageanalysis.icy.common.type.DataType; import org.bioimageanalysis.icy.image.projection.util.MessageProgressListener; +import org.bioimageanalysis.icy.model.OMEUtil; +import org.bioimageanalysis.icy.model.image.IcyBufferedImage; +import org.bioimageanalysis.icy.model.image.IcyBufferedImageCursor; +import org.bioimageanalysis.icy.model.roi.ROI; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.model.sequence.VolumetricImage; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.concurrent.*; @@ -38,7 +40,6 @@ import java.util.concurrent.*; * @author Daniel Felipe Gonzalez Obando */ public class ProjectionCalculator implements Callable<Sequence> { - /** * Builder class for {@link ProjectionCalculator}s. This allows users to specify the sequence, ROIs, axis and operation before creating the calculator. * @@ -57,6 +58,7 @@ public class ProjectionCalculator implements Callable<Sequence> { * * @param s Target sequence for the projection. */ + @Contract("null -> fail") public Builder(final Sequence s) { if (s == null || s.isEmpty()) throw new IllegalArgumentException("Input sequence is null or empty."); @@ -138,6 +140,7 @@ public class ProjectionCalculator implements Callable<Sequence> { private final List<MessageProgressListener> progressListeners; private boolean computed; + @Contract(pure = true) private ProjectionCalculator() { progressListeners = new ArrayList<>(); computed = false; @@ -184,15 +187,17 @@ public class ProjectionCalculator implements Callable<Sequence> { private Sequence result; private void createResultSequence() throws InterruptedException { - result = new Sequence(OMEUtil.createOMEXMLMetadata(seq.getOMEXMLMetadata()), - op + " " + axis + "-projection of " + seq.getName()); + result = new Sequence( + OMEUtil.createOMEXMLMetadata(seq.getOMEXMLMetadata()), + op + " " + axis + "-projection of " + seq.getName() + ); final int width = axis == ProjectionAxis.X ? 1 : seq.getSizeX(); final int height = axis == ProjectionAxis.Y ? 1 : seq.getSizeY(); final int depth = axis == ProjectionAxis.Z ? 1 : seq.getSizeZ(); final int frames = axis == ProjectionAxis.T ? 1 : seq.getSizeT(); final int channels = axis == ProjectionAxis.C ? 1 : seq.getSizeC(); - final DataType dataType = seq.getDataType_(); + final DataType dataType = seq.getDataType(); for (int t = 0; t < frames; t++) { final VolumetricImage vol = new VolumetricImage(); @@ -209,18 +214,14 @@ public class ProjectionCalculator implements Callable<Sequence> { for (int c = 0; c < seq.getSizeC(); c++) result.getColorModel().setColorMap(c, seq.getColorMap(c), true); } - notifyProgress(0.01, "Result sequence instatiated"); + notifyProgress(0.01, "Result sequence instantiated"); } private void computeProjection() throws InterruptedException, ExecutionException { switch (axis) { case X: - //startProjectionOnPlane(); - //break; case C: - //startProjectionOnPlane(); - //break; case Y: startProjectionOnPlane(); break; @@ -252,54 +253,52 @@ public class ProjectionCalculator implements Callable<Sequence> { } } - final ForkJoinPool generalTaskPool = (ForkJoinPool) Executors - .newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 1)); - - final List<Future<IcyBufferedImage>> futures = new ArrayList<>(frames * depth); - for (t = 0; t < frames; t++) { - tOff = t * depth; - for (z = 0; z < depth; z++) { - final Callable<IcyBufferedImage> task; - switch (axis) { - case X: - task = getImageXProjectionTask(inputPlanes[tOff + z], outputPlanes[tOff + z], t, z, channels, + try (final ForkJoinPool generalTaskPool = (ForkJoinPool) Executors.newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 1))) { + final List<Future<IcyBufferedImage>> futures = new ArrayList<>(frames * depth); + for (t = 0; t < frames; t++) { + tOff = t * depth; + for (z = 0; z < depth; z++) { + final Callable<IcyBufferedImage> task = switch (axis) { + case X -> getImageXProjectionTask(inputPlanes[tOff + z], outputPlanes[tOff + z], t, z, channels, height, width); - break; - case Y: - task = getImageYProjectionTask(inputPlanes[tOff + z], outputPlanes[tOff + z], t, z, channels, + case Y -> getImageYProjectionTask(inputPlanes[tOff + z], outputPlanes[tOff + z], t, z, channels, height, width); - break; - case C: - task = getImageCProjectionTask(inputPlanes[tOff + z], outputPlanes[tOff + z], t, z, channels, + case C -> getImageCProjectionTask(inputPlanes[tOff + z], outputPlanes[tOff + z], t, z, channels, height, width); - break; - default: - throw new IllegalArgumentException("Wrong axis parameter"); + default -> throw new IllegalArgumentException("Wrong axis parameter"); + }; + futures.add(generalTaskPool.submit(task)); } - futures.add(generalTaskPool.submit(task)); } - } - generalTaskPool.shutdown(); + generalTaskPool.shutdown(); - int pos; - for (pos = 0, t = 0; t < frames; t++) { - for (z = 0; z < depth; z++) { - try { - futures.get(pos++).get(); - } - catch (final InterruptedException | ExecutionException e) { - generalTaskPool.shutdownNow(); - throw e; + int pos; + for (pos = 0, t = 0; t < frames; t++) { + for (z = 0; z < depth; z++) { + try { + futures.get(pos++).get(); + } + catch (final InterruptedException | ExecutionException e) { + generalTaskPool.shutdownNow(); + throw e; + } } + notifyProgress(0.01 + 0.99 * ((double) t / frames), axis + "-projection: Processed t=" + t); } - notifyProgress(0.01 + 0.99 * ((double) t / frames), axis + "-projection: Processed t=" + t); } } - private Callable<IcyBufferedImage> getImageXProjectionTask(final IcyBufferedImage inputPlane, - final IcyBufferedImage outputPlane, final int t, final int z, final int channels, final int height, - final int width) { + @Contract(pure = true) + private @NotNull Callable<IcyBufferedImage> getImageXProjectionTask( + final IcyBufferedImage inputPlane, + final IcyBufferedImage outputPlane, + final int t, + final int z, + final int channels, + final int height, + final int width + ) { return () -> { int channel, line, column, elementsCount; final double[] elements = new double[width]; @@ -336,9 +335,16 @@ public class ProjectionCalculator implements Callable<Sequence> { }; } - private Callable<IcyBufferedImage> getImageYProjectionTask(final IcyBufferedImage inputPlane, - final IcyBufferedImage outputPlane, final int t, final int z, final int channels, final int height, - final int width) { + @Contract(pure = true) + private @NotNull Callable<IcyBufferedImage> getImageYProjectionTask( + final IcyBufferedImage inputPlane, + final IcyBufferedImage outputPlane, + final int t, + final int z, + final int channels, + final int height, + final int width + ) { return () -> { int channel, line, column, elementsCount; final double[] elements = new double[height]; @@ -376,8 +382,16 @@ public class ProjectionCalculator implements Callable<Sequence> { }; } - private Callable<IcyBufferedImage> getImageCProjectionTask(final IcyBufferedImage inputPlane, - final IcyBufferedImage outputPlane, final int t, final int z, final int channels, final int height, final int width) { + @Contract(pure = true) + private @NotNull Callable<IcyBufferedImage> getImageCProjectionTask( + final IcyBufferedImage inputPlane, + final IcyBufferedImage outputPlane, + final int t, + final int z, + final int channels, + final int height, + final int width + ) { return () -> { int channel, line, column, elementsCount; final double[] elements = new double[height]; @@ -430,46 +444,57 @@ public class ProjectionCalculator implements Callable<Sequence> { final int frames = seq.getSizeT(); final int channels = seq.getSizeC(); - final ForkJoinPool generalTaskPool = (ForkJoinPool) Executors - .newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); - final ForkJoinPool volumeTaskPool = (ForkJoinPool) Executors - .newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 1)); - int t, c; - VolumetricImage inputVolume, resultVolume; - final List<Future<VolumetricImage>> futures = new ArrayList<>(frames * depth); - for (t = 0; t < frames; t++) { - inputVolume = seq.getVolumetricImage(t); - resultVolume = result.getVolumetricImage(t); - for (c = 0; c < channels; c++) { - futures.add(generalTaskPool.submit(getImageZProjectionTask(t, c, inputVolume, resultVolume, channels, - height, width, depth, volumeTaskPool))); + try ( + final ForkJoinPool generalTaskPool = (ForkJoinPool) Executors.newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + final ForkJoinPool volumeTaskPool = (ForkJoinPool) Executors.newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 1)) + ) { + int t, c; + VolumetricImage inputVolume, resultVolume; + final List<Future<VolumetricImage>> futures = new ArrayList<>(frames * depth); + for (t = 0; t < frames; t++) { + inputVolume = seq.getVolumetricImage(t); + resultVolume = result.getVolumetricImage(t); + for (c = 0; c < channels; c++) { + futures.add(generalTaskPool.submit(getImageZProjectionTask(t, c, inputVolume, resultVolume, channels, + height, width, depth, volumeTaskPool))); + } } - } - generalTaskPool.shutdown(); + generalTaskPool.shutdown(); - try { - int pos; - for (pos = 0, t = 0; t < frames; t++) { - for (c = 0; c < channels; c++) { - futures.get(pos++).get(); + try { + int pos; + for (pos = 0, t = 0; t < frames; t++) { + for (c = 0; c < channels; c++) { + futures.get(pos++).get(); + } + notifyProgress(0.01 + 0.99 * ((double) t / frames), axis + "-projection: Processed t=" + t); } - notifyProgress(0.01 + 0.99 * ((double) t / frames), axis + "-projection: Processed t=" + t); + } + catch (final InterruptedException | ExecutionException e) { + generalTaskPool.shutdownNow(); + throw e; + } + finally { + volumeTaskPool.shutdown(); } } - catch (final InterruptedException | ExecutionException e) { - generalTaskPool.shutdownNow(); - throw e; - } - finally { - volumeTaskPool.shutdown(); - } - } - private Callable<VolumetricImage> getImageZProjectionTask(final int t, final int c, final VolumetricImage inputVolume, - final VolumetricImage resultVolume, final int channels, final int height, final int width, final int depth, final ForkJoinPool volumeTaskPool) { + @Contract(pure = true) + @SuppressWarnings("StatementWithEmptyBody") + private @NotNull Callable<VolumetricImage> getImageZProjectionTask( + final int t, + final int c, + final VolumetricImage inputVolume, + final VolumetricImage resultVolume, + final int channels, + final int height, + final int width, + final int depth, + final ForkJoinPool volumeTaskPool + ) { return () -> { int slice, line, column; @@ -517,7 +542,10 @@ public class ProjectionCalculator implements Callable<Sequence> { try { for (line = 0; line < height; line++) { - final double[] lineElements = futureLines.get(line).get(); + notifyProgress(0.01 + 0.99 * ((double) line / height), axis + "-projection: Processed line=" + line); + final Future<double[]> future = futureLines.get(line); + while (!future.isDone()) ; + final double[] lineElements = future.get(); for (column = 0; column < width; column++) { outputCursor.setSafe(column, line, c, lineElements[column]); } @@ -539,46 +567,58 @@ public class ProjectionCalculator implements Callable<Sequence> { final int frames = seq.getSizeT(); final int channels = seq.getSizeC(); - final ForkJoinPool generalTaskPool = (ForkJoinPool) Executors - .newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); - final ForkJoinPool temporalTaskPool = (ForkJoinPool) Executors - .newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 1)); - int z, c; - Sequence inputS, resultS; - final List<Future<Sequence>> futures = new ArrayList<>(depth); - for (z = 0; z < depth; z++) { - for (c = 0; c < channels; c++) { - inputS = seq; - resultS = result; - { - futures.add(generalTaskPool.submit(getImageTProjectionTask(z, c, inputS, resultS, channels, height, - width, depth, frames, temporalTaskPool))); + try ( + final ForkJoinPool generalTaskPool = (ForkJoinPool) Executors.newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + final ForkJoinPool temporalTaskPool = (ForkJoinPool) Executors.newWorkStealingPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 1)) + ) { + int z, c; + Sequence inputS, resultS; + final List<Future<Sequence>> futures = new ArrayList<>(depth); + for (z = 0; z < depth; z++) { + for (c = 0; c < channels; c++) { + inputS = seq; + resultS = result; + { + futures.add(generalTaskPool.submit(getImageTProjectionTask(z, c, inputS, resultS, channels, height, width, depth, frames, temporalTaskPool))); + } } } - } - generalTaskPool.shutdown(); + generalTaskPool.shutdown(); - try { - int pos; - for (pos = 0, z = 0; z < depth; z++) { - for (c = 0; c < channels; c++) { - futures.get(pos++).get(); + try { + int pos; + for (pos = 0, z = 0; z < depth; z++) { + for (c = 0; c < channels; c++) { + futures.get(pos++).get(); + } + notifyProgress(0.01 + 0.99 * ((double) z / depth), axis + "-projection: Processed z=" + z); } - notifyProgress(0.01 + 0.99 * ((double) z / depth), axis + "-projection: Processed z=" + z); } - } - catch (final InterruptedException | ExecutionException e) { - generalTaskPool.shutdownNow(); - throw e; - } - finally { - temporalTaskPool.shutdown(); + catch (final InterruptedException | ExecutionException e) { + generalTaskPool.shutdownNow(); + throw e; + } + finally { + temporalTaskPool.shutdown(); + } } } - private Callable<Sequence> getImageTProjectionTask(final int z, final int c, final Sequence inputS, final Sequence resultS, final int channels, - final int height, final int width, final int depth, final int frames, final ForkJoinPool temporalTaskPool) { + @Contract(pure = true) + @SuppressWarnings("StatementWithEmptyBody") + private @NotNull Callable<Sequence> getImageTProjectionTask( + final int z, + final int c, + final Sequence inputS, + final Sequence resultS, + final int channels, + final int height, + final int width, + final int depth, + final int frames, + final ForkJoinPool temporalTaskPool + ) { return () -> { int frame, line, column; final IcyBufferedImageCursor[] inputCursors = new IcyBufferedImageCursor[frames]; @@ -624,7 +664,10 @@ public class ProjectionCalculator implements Callable<Sequence> { try { for (line = 0; line < height; line++) { - final double[] lineElements = futureLines.get(line).get(); + notifyProgress(0.01 + 0.99 * ((double) line / height), axis + "-projection: Processed line=" + line); + final Future<double[]> future = futureLines.get(line); + while (!future.isDone()) ; + final double[] lineElements = future.get(); for (column = 0; column < width; column++) { outputCursor.setSafe(column, line, c, lineElements[column]); } @@ -663,6 +706,7 @@ public class ProjectionCalculator implements Callable<Sequence> { progressListeners.forEach(l -> l.onProgress(progress, message)); } + @Contract(pure = true) private Sequence getResultSequence() { return result; } diff --git a/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionOperationType.java b/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionOperationType.java index 49f4cfd..12ba559 100644 --- a/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionOperationType.java +++ b/src/main/java/org/bioimageanalysis/icy/image/projection/ProjectionOperationType.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,22 +18,32 @@ package org.bioimageanalysis.icy.image.projection; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + /** * Represents the operation to be applied when performing an intensity projection on a sequence. * * @author Daniel Felipe Gonzalez Obando */ public enum ProjectionOperationType { - MAX("Maximum"), MEAN("Average"), MED("Median"), MIN("Minimum"), STD("Standard Deviation"), SATSUM("Saturated Sum"); + MAX("Maximum"), + MEAN("Average"), + MED("Median"), + MIN("Minimum"), + STD("Standard Deviation"), + SATSUM("Saturated Sum"); private final String description; - ProjectionOperationType(final String description) { + @Contract(pure = true) + ProjectionOperationType(final @NotNull String description) { this.description = description; } + @Contract(pure = true) @Override - public String toString() { + public @NotNull String toString() { return description; } } diff --git a/src/main/java/org/bioimageanalysis/icy/image/projection/util/MessageProgressListener.java b/src/main/java/org/bioimageanalysis/icy/image/projection/util/MessageProgressListener.java index a1e70cd..ef2088a 100644 --- a/src/main/java/org/bioimageanalysis/icy/image/projection/util/MessageProgressListener.java +++ b/src/main/java/org/bioimageanalysis/icy/image/projection/util/MessageProgressListener.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,6 +18,8 @@ package org.bioimageanalysis.icy.image.projection.util; +import org.jetbrains.annotations.Nullable; + /** * A Progress event listener that handles events associating a value and a message to them. * @@ -30,5 +32,5 @@ public interface MessageProgressListener { * @param progress The progress value from 0.0 to 1.0. {@link Double#NaN} value should be taken as no change and -1.0 as indeterminate progress. * @param message The event message. A null value should be taken as no change on the message. */ - void onProgress(double progress, String message); + void onProgress(double progress, @Nullable String message); } diff --git a/src/main/java/org/bioimageanalysis/icy/image/projection/util/SequenceCursor.java b/src/main/java/org/bioimageanalysis/icy/image/projection/util/SequenceCursor.java index 166ae99..a0255ef 100644 --- a/src/main/java/org/bioimageanalysis/icy/image/projection/util/SequenceCursor.java +++ b/src/main/java/org/bioimageanalysis/icy/image/projection/util/SequenceCursor.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,7 +18,8 @@ package org.bioimageanalysis.icy.image.projection.util; -import icy.sequence.Sequence; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.jetbrains.annotations.NotNull; import java.util.concurrent.atomic.AtomicBoolean; @@ -40,7 +41,7 @@ public class SequenceCursor { * * @param seq Sequence. */ - public SequenceCursor(final Sequence seq) { + public SequenceCursor(final @NotNull Sequence seq) { this.seq = seq; this.volumeCursors = new VolumetricImageCursor[seq.getSizeT()]; this.sequenceChanged = new AtomicBoolean(); @@ -75,8 +76,7 @@ public class SequenceCursor { * @throws IndexOutOfBoundsException If the position is not in the image. * @throws RuntimeException If the data type is not a valid format. */ - public synchronized void set(final int x, final int y, final int z, final int t, final int c, final double val) - throws IndexOutOfBoundsException, RuntimeException { + public synchronized void set(final int x, final int y, final int z, final int t, final int c, final double val) throws IndexOutOfBoundsException, RuntimeException { getVolumeCursor(t).set(x, y, z, c, val); sequenceChanged.set(true); } @@ -94,8 +94,7 @@ public class SequenceCursor { * @throws IndexOutOfBoundsException If the position is not in the image. * @throws RuntimeException If the data type is not a valid format. */ - public synchronized void setSafe(final int x, final int y, final int z, final int t, final int c, final double val) - throws IndexOutOfBoundsException, RuntimeException { + public synchronized void setSafe(final int x, final int y, final int z, final int t, final int c, final double val) throws IndexOutOfBoundsException, RuntimeException { getVolumeCursor(t).setSafe(x, y, z, c, val); sequenceChanged.set(true); } @@ -103,7 +102,7 @@ public class SequenceCursor { private int currentT; private VolumetricImageCursor currentCursor; - private synchronized VolumetricImageCursor getVolumeCursor(final int t) throws IndexOutOfBoundsException { + private synchronized @NotNull VolumetricImageCursor getVolumeCursor(final int t) throws IndexOutOfBoundsException { if (currentT != t) { if (volumeCursors[t] == null) { volumeCursors[t] = new VolumetricImageCursor(seq, t); @@ -129,7 +128,7 @@ public class SequenceCursor { } @Override - public String toString() { + public @NotNull String toString() { return "last T=" + currentT + " " + ((currentCursor != null) ? currentCursor.toString() : ""); } } diff --git a/src/main/java/org/bioimageanalysis/icy/image/projection/util/VolumetricImageCursor.java b/src/main/java/org/bioimageanalysis/icy/image/projection/util/VolumetricImageCursor.java index 7d3f108..c12d681 100644 --- a/src/main/java/org/bioimageanalysis/icy/image/projection/util/VolumetricImageCursor.java +++ b/src/main/java/org/bioimageanalysis/icy/image/projection/util/VolumetricImageCursor.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,9 +18,10 @@ package org.bioimageanalysis.icy.image.projection.util; -import icy.image.IcyBufferedImageCursor; -import icy.sequence.Sequence; -import icy.sequence.VolumetricImage; +import org.bioimageanalysis.icy.model.image.IcyBufferedImageCursor; +import org.bioimageanalysis.icy.model.sequence.Sequence; +import org.bioimageanalysis.icy.model.sequence.VolumetricImage; +import org.jetbrains.annotations.NotNull; import java.util.concurrent.atomic.AtomicBoolean; @@ -44,7 +45,7 @@ public class VolumetricImageCursor { * * @param vol Target volume. */ - public VolumetricImageCursor(final VolumetricImage vol) { + public VolumetricImageCursor(final @NotNull VolumetricImage vol) { this.vol = vol; planeCursors = new IcyBufferedImageCursor[vol.getSize()]; volumeChanged = new AtomicBoolean(false); @@ -57,7 +58,7 @@ public class VolumetricImageCursor { * @param seq Target sequence. * @param t Time position where the volume is located in {@code seq}. */ - public VolumetricImageCursor(final Sequence seq, final int t) { + public VolumetricImageCursor(final @NotNull Sequence seq, final int t) { this(seq.getVolumetricImage(t)); } @@ -88,8 +89,7 @@ public class VolumetricImageCursor { * @throws IndexOutOfBoundsException If the position is not in the image. * @throws RuntimeException If the data type is not a valid format. */ - public synchronized void set(final int x, final int y, final int z, final int c, final double val) - throws IndexOutOfBoundsException, RuntimeException { + public synchronized void set(final int x, final int y, final int z, final int c, final double val) throws IndexOutOfBoundsException, RuntimeException { getPlaneCursor(z).set(x, y, c, val); volumeChanged.set(true); } @@ -106,8 +106,7 @@ public class VolumetricImageCursor { * @throws IndexOutOfBoundsException If the position is not in the image. * @throws RuntimeException If the data type is not a valid format. */ - public synchronized void setSafe(final int x, final int y, final int z, final int c, final double val) - throws IndexOutOfBoundsException, RuntimeException { + public synchronized void setSafe(final int x, final int y, final int z, final int c, final double val) throws IndexOutOfBoundsException, RuntimeException { getPlaneCursor(z).setSafe(x, y, c, val); volumeChanged.set(true); } @@ -115,7 +114,7 @@ public class VolumetricImageCursor { private IcyBufferedImageCursor currentCursor; private int currentZ; - private synchronized IcyBufferedImageCursor getPlaneCursor(final int z) throws IndexOutOfBoundsException { + private synchronized @NotNull IcyBufferedImageCursor getPlaneCursor(final int z) throws IndexOutOfBoundsException { if (currentZ != z) { if (planeCursors[z] == null) { planeCursors[z] = new IcyBufferedImageCursor(vol.getImage(z)); diff --git a/src/main/java/plugins/adufour/projection/Projection.java b/src/main/java/plugins/adufour/projection/Projection.java index 6a5e9a0..b8640ee 100644 --- a/src/main/java/plugins/adufour/projection/Projection.java +++ b/src/main/java/plugins/adufour/projection/Projection.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,17 @@ package plugins.adufour.projection; -import icy.main.Icy; -import icy.plugin.PluginLauncher; -import icy.plugin.PluginLoader; -import icy.roi.ROI; -import icy.sequence.Sequence; -import icy.system.IcyExceptionHandler; -import icy.system.IcyHandledException; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; import org.bioimageanalysis.icy.image.projection.ProjectionAxis; import org.bioimageanalysis.icy.image.projection.ProjectionCalculator; import org.bioimageanalysis.icy.image.projection.ProjectionOperationType; +import org.bioimageanalysis.icy.model.roi.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 org.jetbrains.annotations.Nullable; import plugins.adufour.blocks.lang.Block; import plugins.adufour.blocks.util.VarList; import plugins.adufour.ezplug.*; @@ -36,21 +37,29 @@ import plugins.adufour.vars.lang.VarSequence; import java.util.Collections; import java.util.List; +@IcyPluginName("Intensity Projection") +@IcyPluginIcon(path = "/plugins/adufour/projection/icon/icon_32.png") public class Projection extends EzPlug implements Block, EzStoppable { public enum ProjectionDirection { Z, T, C, Y, X } public enum ProjectionType { - MAX("Maximum"), MEAN("Average"), MED("Median"), MIN("Minimum"), STD("Standard Deviation"), + MAX("Maximum"), + MEAN("Average"), + MED("Median"), + MIN("Minimum"), + STD("Standard Deviation"), SATSUM("Saturated Sum"); private final String description; + @Contract(pure = true) ProjectionType(final String description) { this.description = description; } + @Contract(pure = true) @Override public String toString() { return description; @@ -79,7 +88,7 @@ public class Projection extends EzPlug implements Block, EzStoppable { } @Override - public void declareInput(final VarList inputMap) { + public void declareInput(final @NotNull VarList inputMap) { initCommonVars(); inputMap.add("input", input.getVariable()); inputMap.add("projection direction", projectionDir.getVariable()); @@ -96,7 +105,7 @@ public class Projection extends EzPlug implements Block, EzStoppable { } @Override - public void declareOutput(final VarList outputMap) { + public void declareOutput(final @NotNull VarList outputMap) { output = new VarSequence("projected sequence", null); outputMap.add("projection output", output); } @@ -113,8 +122,8 @@ public class Projection extends EzPlug implements Block, EzStoppable { computeCalculator(); } catch (final Exception e) { - IcyExceptionHandler.showErrorMessage(e, true); - throw new IcyHandledException("Error while projecting: " + e.getMessage(), e); + IcyLogger.error(this.getClass(), e, "Error while projecting."); + return; } setOutput(); internalClean(); @@ -132,42 +141,27 @@ public class Projection extends EzPlug implements Block, EzStoppable { rois = restrictToROI.getValue(true) ? inputSequence.getROIs() : Collections.emptyList(); } - private static ProjectionAxis getAxis(final ProjectionDirection direction) { - switch (direction) { - case X: - return ProjectionAxis.X; - case Y: - return ProjectionAxis.Y; - case C: - return ProjectionAxis.C; - case Z: - return ProjectionAxis.Z; - case T: - return ProjectionAxis.T; - - default: - throw new IllegalArgumentException("" + direction); - } + @Contract(pure = true) + private static @NotNull ProjectionAxis getAxis(final @NotNull ProjectionDirection direction) { + return switch (direction) { + case X -> ProjectionAxis.X; + case Y -> ProjectionAxis.Y; + case C -> ProjectionAxis.C; + case Z -> ProjectionAxis.Z; + case T -> ProjectionAxis.T; + }; } - private static ProjectionOperationType getOperation(final ProjectionType type) { - switch (type) { - case MAX: - return ProjectionOperationType.MAX; - case MEAN: - return ProjectionOperationType.MEAN; - case MED: - return ProjectionOperationType.MED; - case MIN: - return ProjectionOperationType.MIN; - case SATSUM: - return ProjectionOperationType.SATSUM; - case STD: - return ProjectionOperationType.STD; - - default: - throw new IllegalArgumentException("" + type); - } + @Contract(pure = true) + private static @NotNull ProjectionOperationType getOperation(final @NotNull ProjectionType type) { + return switch (type) { + case MAX -> ProjectionOperationType.MAX; + case MEAN -> ProjectionOperationType.MEAN; + case MED -> ProjectionOperationType.MED; + case MIN -> ProjectionOperationType.MIN; + case SATSUM -> ProjectionOperationType.SATSUM; + case STD -> ProjectionOperationType.STD; + }; } private Sequence resultSequence; @@ -188,7 +182,7 @@ public class Projection extends EzPlug implements Block, EzStoppable { } } - private void onProgress(final double progress, final String message) { + private void onProgress(final double progress, final @Nullable String message) { if (!isHeadLess()) { getUI().setProgressBarValue(progress); getUI().setProgressBarMessage(message); @@ -242,7 +236,11 @@ public class Projection extends EzPlug implements Block, EzStoppable { * @throws Exception If the projection cannot be correctly done. * @see ProjectionCalculator */ - public static Sequence zProjection(final Sequence in, final ProjectionType projection, final boolean multiThread) throws Exception { + public static Sequence zProjection( + final Sequence in, + final ProjectionType projection, + final boolean multiThread + ) throws Exception { return zProjection(in, projection, multiThread, false); } @@ -259,10 +257,19 @@ public class Projection extends EzPlug implements Block, EzStoppable { * @throws Exception If the projection cannot be correctly done. * @see ProjectionCalculator */ - public static Sequence zProjection(final Sequence in, final ProjectionType projection, final boolean multiThread, final boolean restrictToROI) throws Exception { + public static Sequence zProjection( + final Sequence in, + final ProjectionType projection, + final boolean multiThread, + final boolean restrictToROI + ) throws Exception { final List<ROI> rois = restrictToROI ? in.getROIs() : Collections.emptyList(); - final ProjectionCalculator calculator = new ProjectionCalculator.Builder(in).axis(ProjectionAxis.Z) - .operation(getOperation(projection)).addRois(rois).build(); + final ProjectionCalculator calculator = new ProjectionCalculator + .Builder(in) + .axis(ProjectionAxis.Z) + .operation(getOperation(projection)) + .addRois(rois) + .build(); return calculator.call(); } @@ -277,7 +284,11 @@ public class Projection extends EzPlug implements Block, EzStoppable { * @throws Exception If the projection cannot be correctly done. * @see ProjectionCalculator */ - public static Sequence tProjection(final Sequence in, final ProjectionType projection, final boolean multiThread) throws Exception { + public static Sequence tProjection( + final Sequence in, + final ProjectionType projection, + final boolean multiThread + ) throws Exception { return tProjection(in, projection, multiThread, false); } @@ -294,23 +305,19 @@ public class Projection extends EzPlug implements Block, EzStoppable { * @throws Exception If the projection cannot be correctly done. * @see ProjectionCalculator */ - public static Sequence tProjection(final Sequence in, final ProjectionType projection, final boolean multiThread, final boolean restrictToROI) throws Exception { + public static Sequence tProjection( + final Sequence in, + final ProjectionType projection, + final boolean multiThread, + final boolean restrictToROI + ) throws Exception { final List<ROI> rois = restrictToROI ? in.getROIs() : Collections.emptyList(); - final ProjectionCalculator calculator = new ProjectionCalculator.Builder(in).axis(ProjectionAxis.T) - .operation(getOperation(projection)).addRois(rois).build(); + final ProjectionCalculator calculator = new ProjectionCalculator + .Builder(in) + .axis(ProjectionAxis.T) + .operation(getOperation(projection)) + .addRois(rois) + .build(); return calculator.call(); } - - /** - * Main method used for testing. - * - * @param args Input arguments. - */ - public static void main(final String[] args) { - // start icy - Icy.main(args); - - // then start plugin - PluginLauncher.start(PluginLoader.getPlugin(Projection.class.getName())); - } } diff --git a/src/main/resources/plugins/adufour/projection/icon/icon_32.png b/src/main/resources/plugins/adufour/projection/icon/icon_32.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc83272bac2de94fcc0cd3feba5bdb227752022 GIT binary patch literal 10473 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 literal 0 HcmV?d00001 -- GitLab