From 007709a17d3668a3bccdfccc83013251513910b7 Mon Sep 17 00:00:00 2001
From: Thomas <thomas.musset@pasteur.fr>
Date: Fri, 13 Sep 2024 16:27:37 +0200
Subject: [PATCH] updated pom to v5.0.0-a.1, fix classes accordingly to new
 architecture, added icon, updated .gitignore

---
 .gitignore                                    |  41 +-
 pom.xml                                       |  65 +--
 .../java/plugins/adufour/opencv/OpenCV.java   | 530 ++++++++----------
 .../plugins/adufour/opencv/OpenCVCapture.java | 165 +++---
 .../resources/plugins/adufour/opencv/icon.png | Bin 0 -> 15296 bytes
 5 files changed, 364 insertions(+), 437 deletions(-)
 create mode 100644 src/main/resources/plugins/adufour/opencv/icon.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 b5f3b6e..1c5d3b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,18 +8,16 @@
     <parent>
 		<artifactId>pom-icy</artifactId>
         <groupId>org.bioimageanalysis.icy</groupId>
-        <version>2.0.0</version>
+        <version>3.0.0-a.2</version>
 	</parent>
 
     <!-- Project Information -->
     <artifactId>opencv</artifactId>
-    <version>4.5.1-2</version>
-
-    <packaging>jar</packaging>
+    <version>5.0.0-a.1</version>
 
     <name>OpenCV</name>
     <description>OpenCV (Open Computer Vision) library for Icy. see more at http://opencv.org</description>
-    <url>http://icy.bioimageanalysis.org/plugin/opencv/</url>
+    <url>https://icy.bioimageanalysis.org/plugin/opencv/</url>
     <inceptionYear>2020</inceptionYear>
 
     <organization>
@@ -52,79 +50,26 @@
             </roles>
         </developer>
     </developers>
-
-    <!-- Project properties -->
-    <properties>
-		<artifact-to-include>opencv</artifact-to-include>
-    </properties>
-
-    <profiles>
-        <profile>
-            <id>icy-plugin-extract-library</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-        </profile>
-    </profiles>
-	
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-dependency-plugin</artifactId>
-				<executions>
-					<execution>
-						<id>${project.artifactId}-fetch</id>
-						<phase>generate-sources</phase>
-						<goals>
-							<goal>unpack-dependencies</goal>
-						</goals>
-						<configuration>
-							<includeArtifactIds>${artifact-to-include}</includeArtifactIds>
-							<outputDirectory>${project.build.outputDirectory}</outputDirectory>
-							<stripVersion>true</stripVersion>
-							<excludeTransitive>true</excludeTransitive>
-						</configuration>
-					</execution>
-				</executions>
-			</plugin>
-		</plugins>
-	</build>
 	
     <!-- List of project's dependencies -->
     <dependencies>
 		<dependency>
             <groupId>org.openpnp</groupId>
             <artifactId>opencv</artifactId>
-            <version>4.5.1-2</version>
-        </dependency>
-		
-        <!-- The core of Icy -->
-        <dependency>
-            <groupId>org.bioimageanalysis.icy</groupId>
-            <artifactId>icy-kernel</artifactId>
-			<version>${icy-kernel.version}</version>
+            <version>4.9.0-0</version>
         </dependency>
 		
 		<dependency>
             <groupId>org.bioimageanalysis.icy</groupId>
             <artifactId>ezplug</artifactId>
-			<version>${ezplug.version}</version>
         </dependency>
-		
-		<dependency>
-			<groupId>org.bioimageanalysis.icy</groupId>
-			<artifactId>icy-bioformats</artifactId>
-			<version>${icy-bioformats.version}</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>
+            <url>https://nexus-icy.pasteur.cloud/repository/icy/</url>
         </repository>
     </repositories>
 </project>
\ No newline at end of file
diff --git a/src/main/java/plugins/adufour/opencv/OpenCV.java b/src/main/java/plugins/adufour/opencv/OpenCV.java
index 473bb08..9ac4a1e 100644
--- a/src/main/java/plugins/adufour/opencv/OpenCV.java
+++ b/src/main/java/plugins/adufour/opencv/OpenCV.java
@@ -1,48 +1,68 @@
+/*
+ * 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/>.
+ */
+
 package plugins.adufour.opencv;
 
+import org.bioimageanalysis.icy.Icy;
+import org.bioimageanalysis.icy.common.Version;
+import org.bioimageanalysis.icy.common.math.FPSMeter;
+import org.bioimageanalysis.icy.common.type.DataType;
+import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName;
+import org.bioimageanalysis.icy.model.image.IcyBufferedImage;
+import org.bioimageanalysis.icy.model.sequence.Sequence;
+import org.bioimageanalysis.icy.model.sequence.SequenceAdapter;
+import org.bioimageanalysis.icy.system.SystemUtil;
+import org.bioimageanalysis.icy.system.logging.IcyLogger;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.opencv.core.Core;
+import org.opencv.core.CvType;
+import org.opencv.core.Mat;
+import org.opencv.core.Size;
+import org.opencv.imgproc.Imgproc;
+import org.opencv.videoio.VideoCapture;
+import org.opencv.videoio.Videoio;
+
+import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
-import javax.imageio.ImageIO;
-
-import org.opencv.core.Core;
-import org.opencv.core.CvType;
-import org.opencv.core.Mat;
-import org.opencv.core.Size;
-import org.opencv.imgproc.Imgproc;
-import org.opencv.videoio.VideoCapture;
-import org.opencv.videoio.Videoio;
-
-import icy.common.Version;
-import icy.image.IcyBufferedImage;
-import icy.main.Icy;
-import icy.math.FPSMeter;
-import icy.plugin.abstract_.Plugin;
-import icy.plugin.interface_.PluginLibrary;
-import icy.sequence.Sequence;
-import icy.sequence.SequenceAdapter;
-import icy.system.IcyExceptionHandler;
-import icy.system.IcyHandledException;
-import icy.system.SystemUtil;
-import icy.type.DataType;
-
 /**
  * <a href=http://opencv.org>OpenCV</a> library for Icy. This class contains additional test
  * functions as well as tools to convert between OpenCV and Icy image formats.
- * 
+ *
  * @author Alexandre Dufour
  */
-public class OpenCV extends Plugin implements PluginLibrary
-{
-    public static final Version OpenCV_Version = new Version(Core.VERSION_MAJOR, Core.VERSION_MINOR,
-            Core.VERSION_REVISION);
+@IcyPluginName("OpenCV")
+@IcyPluginIcon(path = "/plugins/adufour/opencv/icon.png")
+public class OpenCV extends Plugin {
+    public static final Version OpenCV_Version = new Version(Core.VERSION_MAJOR, Core.VERSION_MINOR, Core.VERSION_REVISION);
 
     /**
      * A local buffer used to speed up repetitive conversions between OpenCV and Icy
@@ -51,41 +71,32 @@ public class OpenCV extends Plugin implements PluginLibrary
 
     private static final ExecutorService service = Executors.newFixedThreadPool(SystemUtil.getNumberOfCPUs());
 
-    static
-    {
-        String loadingMessage = "Loading OpenCV " + OpenCV_Version + "...";
-        String successMessage = "OpenCV " + OpenCV_Version + " successfully loaded.";
+    static {
+        final String loadingMessage = "Loading OpenCV " + OpenCV_Version + "...";
+        final String successMessage = "OpenCV " + OpenCV_Version + " successfully loaded.";
 
-        try
-        {
+        try {
             // First check whether OpenCV is already installed
-            System.out.println(loadingMessage);
+            IcyLogger.info(OpenCV.class, loadingMessage);
             nu.pattern.OpenCV.loadShared();
-            System.out.println(successMessage);
+            IcyLogger.success(OpenCV.class, successMessage);
         }
-        catch (UnsatisfiedLinkError lib1)
-        {
-            IcyExceptionHandler.handleException(lib1, true);
-            System.out.println("Trying alternate method");
+        catch (final UnsatisfiedLinkError lib1) {
+            IcyLogger.warn(OpenCV.class, lib1, "Unable to load OpenCV, trying alternate method (1/2)");
 
-            try
-            {
+            try {
                 // alternate method
                 loadLibrary(OpenCV.class, Core.NATIVE_LIBRARY_NAME);
             }
-            catch (UnsatisfiedLinkError lib2)
-            {
-                IcyExceptionHandler.handleException(lib2, true);
-                System.out.println("Last chance...");
+            catch (final UnsatisfiedLinkError lib2) {
+                IcyLogger.warn(OpenCV.class, lib1, "Unable to load OpenCV, trying alternate method (2/2)");
 
-                try
-                {
+                try {
                     // last chance...
                     System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
                 }
-                catch (UnsatisfiedLinkError lib3)
-                {
-                    IcyExceptionHandler.handleException(lib3, true);
+                catch (final UnsatisfiedLinkError lib3) {
+                    IcyLogger.fatal(OpenCV.class, lib1, "Unable to load OpenCV");
                 }
             }
         }
@@ -95,22 +106,20 @@ public class OpenCV extends Plugin implements PluginLibrary
      * Initializes OpenCV by loading the native libraries for the current operating system. This
      * method should be called once before any OpenCV class is used
      */
-    public static void initialize()
-    {
+    @Contract(pure = true)
+    public static void initialize() {
         // There is no need to actually initialize anything.
         // The static block above will be executed as soon as this method is called.
     }
 
     /**
      * Converts the specified OpenCV {@link Mat} into an {@link IcyBufferedImage}
-     * 
-     * @param mat
-     *        the OpenCV {@link Mat} to convert
+     *
+     * @param mat the OpenCV {@link Mat} to convert
      * @return an {@link IcyBufferedImage}
      */
-    public static IcyBufferedImage convertToIcy(Mat mat)
-    {
-        IcyBufferedImage output = new IcyBufferedImage(mat.width(), mat.height(), mat.channels(), getIcyDataType(mat));
+    public static @NotNull IcyBufferedImage convertToIcy(final @NotNull Mat mat) {
+        final IcyBufferedImage output = new IcyBufferedImage(mat.width(), mat.height(), mat.channels(), Objects.requireNonNull(getIcyDataType(mat)));
         convertToIcy(mat, output);
         return output;
     }
@@ -120,34 +129,32 @@ public class OpenCV extends Plugin implements PluginLibrary
      * {@link IcyBufferedImage} object.<br>
      * This method requires that the output image is initialized and has the correct dimensions and
      * data type (if unsure, use {@link #convertToIcy(Mat)} instead).
-     * 
-     * @param mat  Mat
+     *
+     * @param mat    Mat
      * @param output IcyBufferedImage
-     *        throws {@link NullPointerException} if either parameter is null
+     *               throws {@link NullPointerException} if either parameter is null
      */
-    public static void convertToIcy(Mat mat, final IcyBufferedImage output)
-    {
+    @SuppressWarnings({"SuspiciousSystemArraycopy", "JavaReflectionMemberAccess"})
+    public static void convertToIcy(final @NotNull Mat mat, final IcyBufferedImage output) {
         final int width = mat.cols();
         final int height = mat.rows();
         final int nChannels = mat.channels();
-        int bufferSize = width * height * nChannels;
+        final int bufferSize = width * height * nChannels;
 
         // handle the easy case (single-channel) first
-        if (nChannels == 1)
-        {
-            try
-            {
+        if (nChannels == 1) {
+            try {
                 // Retrieve the frame data in a generic way
                 // This is equivalent to calling e.g. Mat.get(0, 0, (byte[]) buffer);
-                Object dataBuffer = output.getDataXY(0);
-                Class<?> dataType = dataBuffer.getClass();
+                final Object dataBuffer = output.getDataXY(0);
+                final Class<?> dataType = dataBuffer.getClass();
                 Mat.class.getMethod("get", int.class, int.class, dataType).invoke(mat, 0, 0, dataBuffer);
 
                 return;
             }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e);
+            catch (final Exception e) {
+                IcyLogger.error(OpenCV.class, e);
+                return;
             }
         }
 
@@ -155,152 +162,130 @@ public class OpenCV extends Plugin implements PluginLibrary
         if (Array.getLength(buffer) != bufferSize)
             buffer = null;
         final DataType dataType = getIcyDataType(mat);
-        Class<?> type = dataType.toPrimitiveClass();
+        final Class<?> type = Objects.requireNonNull(dataType).toPrimitiveClass();
         if (buffer == null || buffer.getClass().getComponentType() != type)
             buffer = Array.newInstance(type, bufferSize);
 
-        try
-        {
+        try {
             // Retrieve the frame data in a generic way
             // This is equivalent to calling e.g. Mat.get(0, 0, (byte[]) buffer);
             Mat.class.getMethod("get", int.class, int.class, buffer.getClass()).invoke(mat, 0, 0, buffer);
         }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
+        catch (final Exception e) {
+            IcyLogger.error(OpenCV.class, e);
+            return;
         }
 
-        ArrayList<Future<?>> tasks = new ArrayList<Future<?>>(nChannels);
+        final ArrayList<Future<?>> tasks = new ArrayList<>(nChannels);
 
-        for (int c = 0; c < nChannels; c++)
-        {
+        for (int c = 0; c < nChannels; c++) {
             final int channel = c;
 
-            tasks.add(service.submit(new Runnable()
-            {
-                @Override
-                public void run()
-                {
-                    switch (dataType)
-                    {
-                        case BYTE:
-                        case UBYTE:
-                        {
-                            // OpenCV reads BGR by default => revert this to conventional RGB
-                            final byte[] icyChannel = output.getDataXYAsByte(nChannels - channel - 1);
-                            for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
-                                icyChannel[out] = ((byte[]) buffer)[in];
-                            break;
-                        }
-                        case SHORT:
-                        case USHORT:
-                        {
-                            // OpenCV reads BGR by default => revert this to conventional RGB
-                            final short[] icyChannel = output.getDataXYAsShort(nChannels - channel - 1);
-                            for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
-                                icyChannel[out] = ((short[]) buffer)[in];
-                            break;
-                        }
-                        case INT:
-                        {
-                            // OpenCV reads BGR by default => revert this to conventional RGB
-                            final int[] icyChannel = output.getDataXYAsInt(nChannels - channel - 1);
-                            for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
-                                icyChannel[out] = ((int[]) buffer)[in];
-                            break;
-                        }
-                        case FLOAT:
-                        {
-                            // OpenCV reads BGR by default => revert this to conventional RGB
-                            final float[] icyChannel = output.getDataXYAsFloat(nChannels - channel - 1);
-                            for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
-                                icyChannel[out] = ((float[]) buffer)[in];
-                            break;
-                        }
-                        case DOUBLE:
-                        {
-                            // OpenCV reads BGR by default => revert this to conventional RGB
-                            final double[] icyChannel = output.getDataXYAsDouble(nChannels - channel - 1);
-                            for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
-                                icyChannel[out] = ((double[]) buffer)[in];
-                            break;
-                        }
-                        default:
-                        {
-                            // OpenCV reads BGR by default => revert this to conventional RGB
-                            final Object icyChannel = output.getDataXY(nChannels - channel - 1);
-                            int size = Array.getLength(icyChannel);
-                            for (int out = 0, in = channel; out < size; out++, in += nChannels)
-                                System.arraycopy(buffer, in, icyChannel, out, 1);
-                        }
+            tasks.add(service.submit(() -> {
+                switch (dataType) {
+                    case BYTE:
+                    case UBYTE: {
+                        // OpenCV reads BGR by default => revert this to conventional RGB
+                        final byte[] icyChannel = output.getDataXYAsByte(nChannels - channel - 1);
+                        for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
+                            icyChannel[out] = ((byte[]) buffer)[in];
+                        break;
+                    }
+                    case SHORT:
+                    case USHORT: {
+                        // OpenCV reads BGR by default => revert this to conventional RGB
+                        final short[] icyChannel = output.getDataXYAsShort(nChannels - channel - 1);
+                        for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
+                            icyChannel[out] = ((short[]) buffer)[in];
+                        break;
+                    }
+                    case INT: {
+                        // OpenCV reads BGR by default => revert this to conventional RGB
+                        final int[] icyChannel = output.getDataXYAsInt(nChannels - channel - 1);
+                        for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
+                            icyChannel[out] = ((int[]) buffer)[in];
+                        break;
+                    }
+                    case FLOAT: {
+                        // OpenCV reads BGR by default => revert this to conventional RGB
+                        final float[] icyChannel = output.getDataXYAsFloat(nChannels - channel - 1);
+                        for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
+                            icyChannel[out] = ((float[]) buffer)[in];
+                        break;
+                    }
+                    case DOUBLE: {
+                        // OpenCV reads BGR by default => revert this to conventional RGB
+                        final double[] icyChannel = output.getDataXYAsDouble(nChannels - channel - 1);
+                        for (int out = 0, in = channel; out < icyChannel.length; out++, in += nChannels)
+                            icyChannel[out] = ((double[]) buffer)[in];
+                        break;
+                    }
+                    default: {
+                        // OpenCV reads BGR by default => revert this to conventional RGB
+                        final Object icyChannel = output.getDataXY(nChannels - channel - 1);
+                        final int size = Array.getLength(icyChannel);
+                        for (int out = 0, in = channel; out < size; out++, in += nChannels)
+                            System.arraycopy(buffer, in, icyChannel, out, 1);
                     }
                 }
             }));
         }
 
-        try
-        {
-            for (Future<?> task : tasks)
+        try {
+            for (final Future<?> task : tasks)
                 task.get();
         }
-        catch (InterruptedException e)
-        {
+        catch (final InterruptedException e) {
             Thread.currentThread().interrupt();
         }
-        catch (ExecutionException e)
-        {
-            e.printStackTrace();
+        catch (final ExecutionException e) {
+            IcyLogger.error(OpenCV.class, e);
         }
     }
 
     /**
      * Converts the specified {@link IcyBufferedImage} into an OpenCV {@link Mat}
-     * 
-     * @param img
-     *        the {@link IcyBufferedImage} to convert
+     *
+     * @param img the {@link IcyBufferedImage} to convert
      * @return an OpenCV {@link Mat}
      */
-    public static Mat convertToOpenCV(IcyBufferedImage img)
-    {
-        int width = img.getWidth();
-        int height = img.getHeight();
-        int sizeC = img.getSizeC();
-        int bufferSize = width * height * sizeC;
+    @SuppressWarnings({"SuspiciousSystemArraycopy", "JavaReflectionMemberAccess"})
+    public static @Nullable Mat convertToOpenCV(final @NotNull IcyBufferedImage img) {
+        final int width = img.getWidth();
+        final int height = img.getHeight();
+        final int sizeC = img.getSizeC();
+        final int bufferSize = width * height * sizeC;
 
-        Mat mat = new Mat(new Size(width, height), getCVDataType(img));
+        final Mat mat = new Mat(new Size(width, height), getCVDataType(img));
 
-        if (sizeC == 1)
-        {
+        if (sizeC == 1) {
             buffer = img.getDataXY(0);
         }
-        else
-        {
+        else {
             // Make sure the buffer has the proper type and size
             if (Array.getLength(buffer) != bufferSize)
                 buffer = null;
-            Class<?> type = img.getDataType_().toPrimitiveClass();
+            final Class<?> type = img.getDataType().toPrimitiveClass();
             if (buffer == null || buffer.getClass().getComponentType() != type)
                 buffer = Array.newInstance(type, bufferSize);
 
-            for (int c = 0; c < sizeC; c++)
-            {
+            for (int c = 0; c < sizeC; c++) {
                 // OpenCV Mat elements are interleaved...
-                Object in = img.getDataXY(sizeC - c - 1);
+                final Object in = img.getDataXY(sizeC - c - 1);
                 for (int j = 0, offIN = 0, offOUT = c; j < height; j++)
                     for (int i = 0; i < width; i++, offIN++, offOUT += sizeC)
                         System.arraycopy(in, offIN, buffer, offOUT, 1);
             }
         }
 
-        try
-        {
+        try {
             // Retrieve the frame data in a generic way
             // This is equivalent to calling e.g. Mat.put(0, 0, (byte[]) buffer);
             Mat.class.getMethod("put", int.class, int.class, buffer.getClass()).invoke(mat, 0, 0, buffer);
         }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
+        catch (final Exception e) {
+            IcyLogger.error(OpenCV.class, e);
+            return null;
         }
 
         return mat;
@@ -309,57 +294,37 @@ public class OpenCV extends Plugin implements PluginLibrary
     /**
      * @param img Icy Buffered Image
      * @return the OpenCV data type corresponding to the specified image (see the {@link CvType}
-     *         .CV_* constants)
+     * .CV_* constants)
      */
-    public static int getCVDataType(IcyBufferedImage img)
-    {
-        switch (img.getDataType_())
-        {
-            case BYTE:
-                return CvType.CV_8SC(img.getSizeC());
-            case UBYTE:
-                return CvType.CV_8UC(img.getSizeC());
-            case SHORT:
-                return CvType.CV_16SC(img.getSizeC());
-            case USHORT:
-                return CvType.CV_16UC(img.getSizeC());
-            case UINT: // TODO ?
-            case INT:
-                return CvType.CV_32SC(img.getSizeC());
-            case FLOAT:
-                return CvType.CV_32FC(img.getSizeC());
-            case DOUBLE:
-                return CvType.CV_64FC(img.getSizeC());
-            default:
-                throw new UnsupportedOperationException("OpenCV does not support type " + img.getDataType_());
-        }
+    public static int getCVDataType(final @NotNull IcyBufferedImage img) {
+        return switch (img.getDataType()) {
+            case BYTE -> CvType.CV_8SC(img.getSizeC());
+            case UBYTE -> CvType.CV_8UC(img.getSizeC());
+            case SHORT -> CvType.CV_16SC(img.getSizeC());
+            case USHORT -> CvType.CV_16UC(img.getSizeC());
+            case UINT // TODO ?
+            , INT -> CvType.CV_32SC(img.getSizeC());
+            case FLOAT -> CvType.CV_32FC(img.getSizeC());
+            case DOUBLE -> CvType.CV_64FC(img.getSizeC());
+            default -> throw new UnsupportedOperationException("OpenCV does not support type " + img.getDataType());
+        };
     }
 
     /**
      * @param mat Mat
      * @return the {@link DataType} corresponding to the specified OpenCV {@link Mat}rix
      */
-    public static DataType getIcyDataType(Mat mat)
-    {
-        switch (CvType.depth(mat.type()))
-        {
-            case CvType.CV_8S:
-                return DataType.BYTE;
-            case CvType.CV_8U:
-                return DataType.UBYTE;
-            case CvType.CV_16S:
-                return DataType.SHORT;
-            case CvType.CV_16U:
-                return DataType.USHORT;
-            case CvType.CV_32S:
-                return DataType.INT;
-            case CvType.CV_32F:
-                return DataType.FLOAT;
-            case CvType.CV_64F:
-                return DataType.DOUBLE;
-            default:
-                return null;
-        }
+    public static @Nullable DataType getIcyDataType(final @NotNull Mat mat) {
+        return switch (CvType.depth(mat.type())) {
+            case CvType.CV_8S -> DataType.BYTE;
+            case CvType.CV_8U -> DataType.UBYTE;
+            case CvType.CV_16S -> DataType.SHORT;
+            case CvType.CV_16U -> DataType.USHORT;
+            case CvType.CV_32S -> DataType.INT;
+            case CvType.CV_32F -> DataType.FLOAT;
+            case CvType.CV_64F -> DataType.DOUBLE;
+            default -> null;
+        };
     }
 
     /**
@@ -367,8 +332,7 @@ public class OpenCV extends Plugin implements PluginLibrary
      * viewer. This method will run indefinitely unless the viewer is closed or its calling thread
      * is interrupted (see {@link Thread#interrupt()})
      */
-    public static void liveWebcam()
-    {
+    public static void liveWebcam() {
         liveWebcam(-1, -1, -1);
     }
 
@@ -377,14 +341,13 @@ public class OpenCV extends Plugin implements PluginLibrary
      * viewer. This method will run indefinitely unless the viewer is closed or its calling thread
      * is interrupted (see {@link Thread#interrupt()})
      *
-     * @param width int
+     * @param width  int
      * @param height int
-     * @param fps int
+     * @param fps    int
      */
-    public static void liveWebcam(final int width, final int height, final int fps)
-    {
+    public static void liveWebcam(final int width, final int height, final int fps) {
         // Connect to the camera
-        VideoCapture vc = new VideoCapture(0);
+        final VideoCapture vc = new VideoCapture(0);
         if (width != -1)
             vc.set(Videoio.CAP_PROP_FRAME_WIDTH, width);
         if (height != -1)
@@ -400,20 +363,17 @@ public class OpenCV extends Plugin implements PluginLibrary
      * viewer. This method will run indefinitely unless the viewer is closed or its calling thread
      * is interrupted (see {@link Thread#interrupt()})
      */
-    public static void liveWebcam(final VideoCapture camera)
-    {
-        new Thread(new Runnable()
-        {
+    public static void liveWebcam(final VideoCapture camera) {
+        new Thread(new Runnable() {
             @Override
-            public void run()
-            {
+            public void run() {
                 // We need to know which thread to interrupt to clean the camera
                 final Thread currentThread = Thread.currentThread();
 
                 // Read the first image to initialize the meta-data
-                Mat mat = new Mat();
+                final Mat mat = new Mat();
                 camera.read(mat);
-                IcyBufferedImage image = convertToIcy(mat);
+                final IcyBufferedImage image = convertToIcy(mat);
 
                 // Create the sequence
                 final Sequence s = new Sequence("Live webcam", image);
@@ -424,19 +384,16 @@ public class OpenCV extends Plugin implements PluginLibrary
                 s.setAutoUpdateChannelBounds(false);
 
                 // Closing the sequence should stop the camera
-                s.addListener(new SequenceAdapter()
-                {
+                s.addListener(new SequenceAdapter() {
                     @Override
-                    public void sequenceClosed(Sequence sequence)
-                    {
+                    public void sequenceClosed(final Sequence sequence) {
                         s.removeListener(this);
                         currentThread.interrupt();
                     }
                 });
 
-                try
-                {
-                    FPSMeter fps = new FPSMeter();
+                try {
+                    final FPSMeter fps = new FPSMeter();
                     long grabTime = 0;
                     long readTime = 0;
                     long conversionTime = 0;
@@ -444,17 +401,16 @@ public class OpenCV extends Plugin implements PluginLibrary
                     double nbIterations = 0.0;
 
                     // loop until the thread is interrupted
-                    while (!currentThread.isInterrupted())
-                    {
-                        long t0 = System.nanoTime();
+                    while (!currentThread.isInterrupted()) {
+                        final long t0 = System.nanoTime();
                         camera.grab();
-                        long t1 = System.nanoTime();
+                        final long t1 = System.nanoTime();
                         camera.retrieve(mat);
-                        long t2 = System.nanoTime();
+                        final long t2 = System.nanoTime();
                         convertToIcy(mat, image);
-                        long t3 = System.nanoTime();
+                        final long t3 = System.nanoTime();
                         image.dataChanged();
-                        long t4 = System.nanoTime();
+                        final long t4 = System.nanoTime();
                         fps.update();
 
                         nbIterations++;
@@ -463,14 +419,13 @@ public class OpenCV extends Plugin implements PluginLibrary
                         conversionTime += (t3 - t2) / 1000000;
                         dataUpdateTime += (t4 - t3) / 1000000;
                     }
-                    System.out.println("Capture frame-rate: " + fps.getFPS() + " fps");
-                    System.out.println("Camera grab time: " + grabTime / nbIterations + " ms");
-                    System.out.println("Camera read time: " + readTime / nbIterations + " ms");
-                    System.out.println("Conversion time: " + conversionTime / nbIterations + " ms");
-                    System.out.println("Data update time: " + dataUpdateTime / nbIterations + " ms");
+                    IcyLogger.info(OpenCV.class, "Capture frame-rate: " + fps.getFPS() + " fps");
+                    IcyLogger.info(OpenCV.class, "Camera grab time: " + grabTime / nbIterations + " ms");
+                    IcyLogger.info(OpenCV.class, "Camera read time: " + readTime / nbIterations + " ms");
+                    IcyLogger.info(OpenCV.class, "Conversion time: " + conversionTime / nbIterations + " ms");
+                    IcyLogger.info(OpenCV.class, "Data update time: " + dataUpdateTime / nbIterations + " ms");
                 }
-                finally
-                {
+                finally {
                     // close the camera
                     camera.release();
                 }
@@ -482,32 +437,30 @@ public class OpenCV extends Plugin implements PluginLibrary
      * OpenCV test that takes the active image and performs a Sobel filter in X and Y and combines
      * the result into a new image
      */
-    public static void testSobel()
-    {
-        new Thread(new Runnable()
-        {
-            @Override
-            public void run()
-            {
-                Sequence s = Icy.getMainInterface().getActiveSequence();
+    public static void testSobel() {
+        new Thread(() -> {
+            Sequence s = Icy.getMainInterface().getActiveSequence();
+
+            if (s == null) {
+                IcyLogger.error(OpenCV.class, "Open an image first!");
+                return;
+            }
 
-                if (s == null)
-                    throw new IcyHandledException("Open an image first!");
+            final Mat mat = convertToOpenCV(Icy.getMainInterface().getActiveImage());
 
-                Mat mat = convertToOpenCV(Icy.getMainInterface().getActiveImage());
+            final Mat x = new Mat();
+            final Mat y = new Mat();
 
-                Mat x = new Mat();
-                Mat y = new Mat();
+            Objects.requireNonNull(mat);
 
-                Imgproc.Sobel(mat, x, -1, 1, 0);
-                Imgproc.Sobel(mat, y, -1, 0, 1);
+            Imgproc.Sobel(mat, x, -1, 1, 0);
+            Imgproc.Sobel(mat, y, -1, 0, 1);
 
-                org.opencv.core.Core.addWeighted(x, 0.5, y, 0.5, 1.0, mat);
+            Core.addWeighted(x, 0.5, y, 0.5, 1.0, mat);
 
-                s = new Sequence("OpenCV: Sobel filter applied to " + s.getName(), convertToIcy(mat));
+            s = new Sequence("OpenCV: Sobel filter applied to " + s.getName(), convertToIcy(mat));
 
-                Icy.getMainInterface().addSequence(s);
-            }
+            Icy.getMainInterface().addSequence(s);
         }).start();
     }
 
@@ -518,28 +471,24 @@ public class OpenCV extends Plugin implements PluginLibrary
      * RPiCamera's encoding setting (JPEG by default).<br>
      * <br>
      * Usage Example:<br>
-     * 
+     *
      * <pre>
      * BufferedImage buffImg = piCamera.takeBufferedStill(500, 500);
      * </pre>
-     * 
-     * @param width
-     *        An int specifying width of image to take.
-     * @param height
-     *        An int specifying height of image to take.
+     *
+     * @param width  An int specifying width of image to take.
+     * @param height An int specifying height of image to take.
      * @return A BufferedImage containing the image.
-     * @throws IOException
-     *         this exception is thrown if:
-     *         <ul>
-     *         <li>The system is not a Raspberry Pi (RPi),</li>
-     *         <li>The RPi is not equipped with a camera board,</li>
-     *         <li>The <code>raspistill</code> utility is not installed,</li>
-     *         <li>Something went wrong when reading the image from the camera</li>
-     *         </ul>
+     * @throws IOException this exception is thrown if:
+     *                     <ul>
+     *                     <li>The system is not a Raspberry Pi (RPi),</li>
+     *                     <li>The RPi is not equipped with a camera board,</li>
+     *                     <li>The <code>raspistill</code> utility is not installed,</li>
+     *                     <li>Something went wrong when reading the image from the camera</li>
+     *                     </ul>
      */
-    public static BufferedImage rpi_raspistillToBufferedImage(int width, int height) throws IOException
-    {
-        List<String> command = new ArrayList<String>();
+    public static BufferedImage rpi_raspistillToBufferedImage(final int width, final int height) throws IOException {
+        final List<String> command = new ArrayList<>();
         command.add("raspistill");
         command.add("-o");
         command.add("-v");
@@ -547,7 +496,7 @@ public class OpenCV extends Plugin implements PluginLibrary
         command.add("" + width);
         command.add("-h");
         command.add("" + height);
-        ProcessBuilder pb = new ProcessBuilder(command);
+        final ProcessBuilder pb = new ProcessBuilder(command);
 
         // System.out.println("Executed this command:\n\t" + command.toString());
         // pb.redirectErrorStream(true);
@@ -555,8 +504,7 @@ public class OpenCV extends Plugin implements PluginLibrary
         // new File(System.getProperty("user.home") + File.separator +
         // "Desktop" + File.separator + "RPiCamera.out"));
 
-        Process p = pb.start();
-        BufferedImage bi = ImageIO.read(p.getInputStream());
+        final Process p = pb.start();
         // --------------------------------------------------------------------------
         // This code can be used to specify an ImageReader - perhaps for a specific
         // type of image - in place of the previous line:
@@ -568,7 +516,7 @@ public class OpenCV extends Plugin implements PluginLibrary
         // ImageReadParam param = reader.getDefaultReadParam();
         // BufferedImage bi = reader.read(0, param);
         // --------------------------------------------------------------------------
-        return bi;
+        return ImageIO.read(p.getInputStream());
     }
 
 }
diff --git a/src/main/java/plugins/adufour/opencv/OpenCVCapture.java b/src/main/java/plugins/adufour/opencv/OpenCVCapture.java
index 0e3f234..5fa6d22 100644
--- a/src/main/java/plugins/adufour/opencv/OpenCVCapture.java
+++ b/src/main/java/plugins/adufour/opencv/OpenCVCapture.java
@@ -1,40 +1,54 @@
-package plugins.adufour.opencv;
-
-import java.lang.reflect.Field;
-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.JSeparator;
+package plugins.adufour.opencv;
 
+import org.bioimageanalysis.icy.Icy;
+import org.bioimageanalysis.icy.common.math.FPSMeter;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName;
+import org.bioimageanalysis.icy.model.image.IcyBufferedImage;
+import org.bioimageanalysis.icy.model.sequence.Sequence;
+import org.bioimageanalysis.icy.model.sequence.SequenceAdapter;
+import org.bioimageanalysis.icy.system.logging.IcyLogger;
 import org.opencv.core.Mat;
 import org.opencv.videoio.VideoCapture;
 import org.opencv.videoio.Videoio;
-
-import icy.image.IcyBufferedImage;
-import icy.main.Icy;
-import icy.math.FPSMeter;
-import icy.sequence.Sequence;
-import icy.sequence.SequenceAdapter;
-import plugins.adufour.ezplug.EzPlug;
-import plugins.adufour.ezplug.EzStoppable;
-import plugins.adufour.ezplug.EzVar;
-import plugins.adufour.ezplug.EzVarInteger;
-import plugins.adufour.ezplug.EzVarListener;
+import plugins.adufour.ezplug.*;
 import plugins.adufour.vars.util.VarException;
 
-public class OpenCVCapture extends EzPlug implements EzStoppable, EzVarListener<Integer>
-{
-    List<EzVarInteger> parameters = new ArrayList<EzVarInteger>();
-    
+import javax.swing.*;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+@IcyPluginName("OpenCV Capture")
+@IcyPluginIcon(path = "/plugins/adufour/opencv/icon.png")
+public class OpenCVCapture extends EzPlug implements EzStoppable, EzVarListener<Integer> {
+    List<EzVarInteger> parameters = new ArrayList<>();
+
     VideoCapture camera = null;
-    
+
     @Override
-    protected void initialize()
-    {
+    protected void initialize() {
         OpenCV.initialize();
-        
-        for (Field f : Videoio.class.getDeclaredFields())
-        {
+
+        for (final Field f : Videoio.class.getDeclaredFields()) {
             String name = f.getName();
             if (!name.startsWith("CAP_PROP_")) continue;
             if (name.contains("_DC1394")) continue;
@@ -47,114 +61,99 @@ public class OpenCVCapture extends EzPlug implements EzStoppable, EzVarListener<
             if (name.contains("_POS")) continue;
             if (name.contains("_PVAPI")) continue;
             if (name.contains("_XI")) continue;
-            
+
             name = name.substring(9).toLowerCase();
-            EzVarInteger setting = new EzVarInteger(name);
-            setting.setValue(Integer.valueOf(-1));
+            final EzVarInteger setting = new EzVarInteger(name);
+            setting.setValue(-1);
             setting.addVarChangeListener(this);
             parameters.add(setting);
         }
-        
+
         int cpt = 0;
-        for (EzVarInteger setting : parameters)
-        {
+        for (final EzVarInteger setting : parameters) {
             addEzComponent(setting);
             cpt++;
             if (cpt % 20 == 0) addComponent(new JSeparator(JSeparator.VERTICAL));
         }
     }
-    
+
     @Override
-    protected void execute()
-    {
+    protected void execute() {
         camera = new VideoCapture(0);
-        
-        for (EzVarInteger setting : parameters)
-        {
+
+        for (final EzVarInteger setting : parameters) {
             if (setting.getValue() == -1) continue;
-            
-            String propName = "CAP_PROP_" + setting.name.toUpperCase();
-            try
-            {
-                int propID = Videoio.class.getDeclaredField(propName).getInt(null);
+
+            final String propName = "CAP_PROP_" + setting.name.toUpperCase();
+            try {
+                final int propID = Videoio.class.getDeclaredField(propName).getInt(null);
                 camera.set(propID, setting.getValue());
             }
-            catch (Exception e)
-            {
+            catch (final Exception e) {
                 throw new VarException(setting.getVariable(), e.getMessage());
             }
         }
-        
+
         // Read the first image to initialize the meta-data
-        Mat mat = new Mat();
+        final Mat mat = new Mat();
         camera.read(mat);
-        IcyBufferedImage image = OpenCV.convertToIcy(mat);
-        
+        final IcyBufferedImage image = OpenCV.convertToIcy(mat);
+
         // Create the sequence
         final Sequence s = new Sequence("Live webcam", image);
         Icy.getMainInterface().addSequence(s);
-        
+
         // don't update the channel bounds on update
         image.setAutoUpdateChannelBounds(false);
         s.setAutoUpdateChannelBounds(false);
-        
+
         // Closing the sequence should stop the camera
         final Thread thread = Thread.currentThread();
-        s.addListener(new SequenceAdapter()
-        {
+        s.addListener(new SequenceAdapter() {
             @Override
-            public void sequenceClosed(Sequence sequence)
-            {
+            public void sequenceClosed(final Sequence sequence) {
                 s.removeListener(this);
                 thread.interrupt();
             }
         });
-        
-        FPSMeter fps = new FPSMeter();
-        
-        try
-        {
-            while (camera.read(mat) && !Thread.currentThread().isInterrupted())
-            {
+
+        final FPSMeter fps = new FPSMeter();
+
+        try {
+            while (camera.read(mat) && !Thread.currentThread().isInterrupted()) {
                 OpenCV.convertToIcy(mat, image);
                 image.dataChanged();
                 fps.update();
                 getUI().setProgressBarMessage("Acquiring at " + fps.getFPS() + "fps");
             }
         }
-        catch (Exception e)
-        {
-        
+        catch (final Exception e) {
+            IcyLogger.warn(this.getClass(), e);
         }
-        finally
-        {
+        finally {
             camera.release();
         }
     }
-    
+
     @Override
-    public void clean()
-    {
-        for (EzVarInteger setting : parameters)
+    public void clean() {
+        for (final EzVarInteger setting : parameters)
             setting.removeVarChangeListener(this);
         parameters.clear();
     }
-    
+
     @Override
-    public void variableChanged(EzVar<Integer> source, Integer newValue)
-    {
+    public void variableChanged(final EzVar<Integer> source, final Integer newValue) {
         if (camera == null) return;
-        
-        String propName = "CAP_PROP_" + source.name.toUpperCase();
-        try
-        {
-            int propID = Videoio.class.getDeclaredField(propName).getInt(null);
+
+        final String propName = "CAP_PROP_" + source.name.toUpperCase();
+        try {
+            final int propID = Videoio.class.getDeclaredField(propName).getInt(null);
             camera.set(propID, newValue);
         }
-        catch (Exception e)
-        {
+        catch (final Exception e) {
             throw new VarException(source.getVariable(), e.getMessage());
         }
     }
-    
+
 }
diff --git a/src/main/resources/plugins/adufour/opencv/icon.png b/src/main/resources/plugins/adufour/opencv/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..33c18ccbabdd18ccf257a30267f4a085e64bb6f5
GIT binary patch
literal 15296
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
S{+nC?0000<MNUMnLSTZ2t$J+$

literal 0
HcmV?d00001

-- 
GitLab