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 b5f3b6e30928b9a4f9aa2d3dcf0095fcd13d3217..1c5d3b62b5f89f4dc74573706dae1925f87abdc0 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 473bb089b15fb290630d515f3c79d9a1e762c6e5..9ac4a1e801b80446fe52947fc1a4e7b19a23b1c1 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 0e3f23435333891c6c8715c4eff9d07de013e1bc..5fa6d22ab6e092dcdd7e6092137587837b1df0ea 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
Binary files /dev/null and b/src/main/resources/plugins/adufour/opencv/icon.png differ