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