# This file deals with tests of imagej's ijopencv solution to use opencv in imagej # https://imagej.net/Jython_Scripting#Using_openCV_in_Jython and https://github.com/joheras/IJ-OpenCV/wiki provides a necessary but (too) basic documentation, # https://github.com/bytedeco/javacv-examples/blob/master/OpenCV_Cookbook/README.md says : # "OpenCV (Open Source Computer Vision) is a library of several hundred algorithms for computer vision and video analysis. OpenCV can be us on JVM using two approaches. First are Java wrappers provided by OpenCV. Second are are wrappers based on JavaCPP (C++ wrapper engine for JVM) called OpenCV JavaCPP Presets. There are also JavaCPP presets for other computer vision related libraries like: FFmpeg, libdc1394, PGR FlyCapture, OpenKinect, videoInput, ARToolKitPlus, flandmark, and others. JavaCV combines libraries in JavaCPP Presets and add some additional functionality that makes them easier use on JVM." # Here's what I've worked out by testing ijopencv : # 1. ijopencv is a solution based on javacpp (http://bytedeco.org/) # 2. javacpp provides java wrappers for a lot of c++ libraries such as opencv. A javacpp version is tied with a specific opencv version : # - javacpp 1.4.2 uses opencv 3.4.2 (https://github.com/bytedeco/javacpp-presets/tree/1.4.2/opencv) # - javacpp 1.5 uses opencv 4.0.1 (https://github.com/bytedeco/javacpp-presets/tree/1.5/opencv) # 3. javacpp is not responsible for the python binding # 4. jython exposes java classes and functions as python classes and functions. As a result, the java api of javacpp is exposed by jython as the python api org.bytedeco.javacpp.* (eg org.bytedeco.javacpp.opencv_core) # 5. as aresult of 4, javacpp's python api for opencv is not as practical as opencv's python api: for example, it doesn't seem possible to access pixel data using image[x, y]. As a result, a python code written using opencv's javacpp api is not compatible with a python code using opencv vi opencv's python wrappers. # 6. as a result of 4, the documentation for org.bytedeco.javacpp's functions and arguments can be found in http://bytedeco.org/javacpp/apidocs/ in the form of java functions. As this url only shows the latest version (javacpp 1.5.1), here's a way to find the functions that are available with your javacpp : # [OK]graffy@pr079234:~/toto[19:20:37]>git clone https://github.com/bytedeco/javacpp-presets.git # [OK]graffy@pr079234:~/toto[19:21:17]>cd javacpp-presets # [OK]graffy@pr079234:~/toto/javacpp-presets[19:21:37]>git checkout tags/1.4.2 # [OK]graffy@pr079234:~/toto/javacpp-presets[19:21:57]>git branch # * (HEAD detached at 1.4.2) # [OK]graffy@pr079234:~/toto/javacpp-presets[19:18:33]>grep -A 2 "void cvCalcHist" ./opencv/src/main/java/org/bytedeco/javacpp/opencv_imgproc.java # public static native void cvCalcHist( @Cast("IplImage**") PointerPointer image, CvHistogram hist, # int accumulate/*=0*/, # @Const CvArr mask/*=NULL*/ ); # -- # public static native void cvCalcHist( @ByPtrPtr IplImage image, CvHistogram hist ); # public static native void cvCalcHist( @ByPtrPtr IplImage image, CvHistogram hist, # int accumulate/*=0*/, # @Const CvArr mask/*=NULL*/ ); # [OK]graffy@pr079234:~/toto/javacpp-presets[19:18:41]>grep -A 2 "void cvCalcHist" ./opencv/src/main/java/org/bytedeco/javacpp/helper/opencv_imgproc.java # public static void cvCalcHist(IplImage[] arr, CvHistogram hist, int accumulate/*=0*/, CvArr mask/*=null*/) { # org.bytedeco.javacpp.opencv_imgproc.cvCalcHist(new IplImageArray(arr), hist, accumulate, mask); # } # -- # public static void cvCalcHist(IplImageArray arr, CvHistogram hist, # int accumulate/*=0*/, CvArr mask/*=null*/) { # org.bytedeco.javacpp.opencv_imgproc.cvCalcArrHist(arr, hist, accumulate, mask); # 7 javacpp's python api doesn't seem to support keyword arguments # 8 as org.bytedeco.javacpp.opencv* knows nothing about imagej, the package ijopencv provides conversion functions that convert images from imagej's ImageProcessor to org.bytedeco.javacpp.opencv_core.Mat and vice versa # there are 2 calcHist functions in /Applications/Fiji.app/jars/opencv-3.4.2-1.4.2.jar, and they take 6 or 7 arguments : # jar -xvf /Applications/Fiji.app/jars/opencv-3.4.2-1.4.2.jar # [1]graffy@pr079234:~/toto/jython[15:18:17]>javap ./org/opencv/imgproc/Imgproc.class | grep calcHist # public static void calcHist(java.util.List, org.opencv.core.MatOfInt, org.opencv.core.Mat, org.opencv.core.Mat, org.opencv.core.MatOfInt, org.opencv.core.MatOfFloat, boolean); # public static void calcHist(java.util.List, org.opencv.core.MatOfInt, org.opencv.core.Mat, org.opencv.core.Mat, org.opencv.core.MatOfInt, org.opencv.core.MatOfFloat); # # jar -xvf /Applications/Fiji.app/jars/opencv-3.4.2-1.4.2-macosx-x86_64.jar # [OK]graffy@pr079234:~/toto/jython[19:02:28]>grep -A 5 "calcHist(" org/bytedeco/javacpp/macosx-x86_64/include/opencv2/imgproc.hpp # CV_EXPORTS void calcHist( const Mat* images, int nimages, # const int* channels, InputArray mask, # OutputArray hist, int dims, const int* histSize, # const float** ranges, bool uniform = true, bool accumulate = false ); # /** @overload # -- # CV_EXPORTS void calcHist( const Mat* images, int nimages, # const int* channels, InputArray mask, # SparseMat& hist, int dims, # const int* histSize, const float** ranges, # bool uniform = true, bool accumulate = false ); # -- # CV_EXPORTS_W void calcHist( InputArrayOfArrays images, # const std::vector& channels, # InputArray mask, OutputArray hist, # const std::vector& histSize, # const std::vector& ranges, # bool accumulate = false ); from ij import IJ # pylint: disable=import-error from ijopencv.ij import ImagePlusMatConverter # pylint: disable=import-error from ijopencv.opencv import MatImagePlusConverter # pylint: disable=import-error import org.bytedeco.javacpp.opencv_core as opencv_core # pylint: disable=import-error import org.bytedeco.javacpp.opencv_imgproc as opencv_imgproc # pylint: disable=import-error from org.bytedeco.javacpp.opencv_imgcodecs import imread, imwrite # pylint: disable=import-error # from org.bytedeco.javacpp.opencv_core import CvMat def test_opencv_calc_hist1(): src_image = opencv_core.Mat([128, 128]) histogram = opencv_imgproc.cvCalcHist(src_image) # pylint: disable=unused-variable # TypeError: cvCalcHist(): expected 2 or 4 args; got 1 def test_opencv_calc_hist2(): src_image = opencv_core.Mat([128, 128]) histogram = opencv_core.Mat() opencv_imgproc.cvCalcHist(src_image, histogram) # TypeError: cvCalcHist(): 1st arg can't be coerced to org.bytedeco.javacpp.opencv_core$IplImage def test_opencv_calc_hist3(): src_image = opencv_core.Mat([128, 128]) histogram = opencv_imgproc.calcHist(src_image) # pylint: disable=unused-variable # TypeError: calcHist(): expected 6-8 or 10 args; got 1 # ah: ./opencv/src/main/java/org/bytedeco/javacpp/opencv_imgproc.java seems to contain the list of 57 allowed calcHist sets of arguments, as the number of arguments match # [OK]graffy@pr079234:~/toto/javacpp-presets[19:45:41]>grep -A 5 calcHist ./opencv/src/main/java/org/bytedeco/javacpp/opencv_imgproc.java | sed -E 's/^[ ]*//g' | tr '\n' '`' | sed 's/,`/, /g' | tr '`' '\n' | grep '^@Namespace' | wc -l # 57 # [OK]graffy@pr079234:~/toto/javacpp-presets[19:45:56]>grep -A 5 calcHist ./opencv/src/main/java/org/bytedeco/javacpp/opencv_imgproc.java | sed -E 's/^[ ]*//g' | tr '\n' '`' | sed 's/,`/, /g' | tr '`' '\n' | grep '^@Namespace' | head -2 # @Namespace("cv") public static native void calcHist( @Const Mat images, int nimages, @Const IntPointer channels, @ByVal Mat mask, @ByVal Mat hist, int dims, @Const IntPointer histSize, @Cast("const float**") PointerPointer ranges, @Cast("bool") boolean uniform/*=true*/, @Cast("bool") boolean accumulate/*=false*/ ); # @Namespace("cv") public static native void calcHist( @Const Mat images, int nimages, @Const IntPointer channels, @ByVal Mat mask, @ByVal Mat hist, int dims, @Const IntPointer histSize, @Const @ByPtrPtr FloatPointer ranges ); # [OK]graffy@pr079234:~/toto/javacpp-presets[19:38:39]>grep -A 5 calcHist ./opencv/src/main/java/org/bytedeco/javacpp/opencv_imgproc.java | sed -E 's/^[ ]*//g' | tr '\n' '`' | sed 's/,`/, /g' | tr '`' '\n' | grep '^@Namespace' | awk '-F,' '{print NF}' | sort --unique -n # 6 # 7 # 8 # 10 def test_opencv_calc_hist4(): src_image = opencv_core.Mat([128, 128]) src_images = opencv_core.MatVector() # print(dir(src_images)) src_images.push_back(src_image) print(type(src_images)) # print(src_images) # [org.bytedeco.javacpp.opencv_core$Mat[width=1024,height=1024,depth=8,channels=3]] opencv_imgproc.calcHist(src_images, [0], None, None, [256], [0.0, 256.0]) # TypeError: calcHist(): 1st arg can't be coerced to org.bytedeco.javacpp.opencv_core$UMatVector, org.bytedeco.javacpp.opencv_core$GpuMatVector, org.bytedeco.javacpp.opencv_core$MatVector def test_opencv_calc_hist5(): src_image = opencv_core.Mat([128, 128]) histogram = opencv_core.Mat() # @Namespace("cv") public static native void calcHist( @Const Mat images, int nimages, @Const int[] channels, @ByVal Mat mask, @ByVal Mat hist, int dims, @Const int[] histSize, @Const @ByPtrPtr float[] ranges ); opencv_imgproc.calcHist(src_image, 1, [0], None, histogram, 1, [256], [0.0, 256.0]) # at org.bytedeco.javacpp.opencv_imgproc.calcHist(Native Method) # at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) # at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) # at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) # at java.lang.reflect.Method.invoke(Method.java:497) # java.lang.NullPointerException: java.lang.NullPointerException: Pointer address of argument 3 is NULL. def test_opencv_calc_hist6(): src_image = opencv_core.Mat([128, 128]) histogram = opencv_core.Mat() mask = opencv_core.Mat() # @Namespace("cv") public static native void calcHist( @Const Mat images, int nimages, @Const int[] channels, @ByVal Mat mask, @ByVal Mat hist, int dims, @Const int[] histSize, @Const @ByPtrPtr float[] ranges ); opencv_imgproc.calcHist(src_image, 1, [0], mask, histogram, 1, [256], [0.0, 256.0]) # java.lang.RuntimeException: java.lang.RuntimeException: OpenCV(3.4.2) /Users/travis/build/bytedeco/javacpp-presets/opencv/cppbuild/macosx-x86_64/opencv-3.4.2/modules/imgproc/src/histogram.cpp:918: error: (-210:Unsupported format or combination of formats) in function 'calcHist' def test_opencv_calc_hist7(): # 0 : depth=8 # 1 : depth=-2147483640 # 2 : depth=16 # 3 : depth=-2147483640 # 4 : depth=-2147483640 # 8 : depth=8, channels=2 print('opencv_core.CV_8U', opencv_core.CV_8U) print('opencv_core.CV_16U', opencv_core.CV_16U) print('opencv_core.CV_32F', opencv_core.CV_32F) print('opencv_core.CV_8UC3', opencv_core.CV_8UC3) src_image = opencv_core.Mat().zeros(128, 128, opencv_core.CV_8U).asMat() byte_pointer = src_image.data() print('byte_pointer', byte_pointer) # byte_pointer.putChar(128 * 3 + 7, 5) byte_pointer.put(128 * 3 + 7, 5) # print(dir(opencv_core)) print('src_image', src_image) histogram = opencv_core.Mat() print('histogram', histogram) # create an empty mask. From https://docs.opencv.org/3.4.1/d6/dc7/group__imgproc__hist.html#ga4b2b5fd75503ff9e6844cc4dcdaed35d : # Optional mask. If the matrix is not empty, it must be an 8-bit array of the same size as images[i] . The non-zero mask elements mark the array elements counted in the histogram. empty_mask = opencv_core.Mat() print('empty_mask', empty_mask) # @Namespace("cv") public static native void calcHist( @Const Mat images, int nimages, @Const int[] channels, @ByVal Mat mask, @ByVal Mat hist, int dims, @Const int[] histSize, @Const @ByPtrPtr float[] ranges ); opencv_imgproc.calcHist(src_image, 1, [0], empty_mask, histogram, 1, [256], [0.0, 256.0]) print('histogram', histogram, opencv_core.CvMat(histogram)) def test_opencv_calc_hist(): ''' ''' # https://forum.image.sc/t/use-opencv-within-jython-macro/10320/4 test_opencv_calc_hist7() def create_test_image_file(image_file_path): image = opencv_core.Mat().zeros(128, 128, opencv_core.CV_8U).asMat() imwrite(image_file_path, image) def test_ijopencv_converter(): image_file_path = '/tmp/toto.png' create_test_image_file(image_file_path) image = IJ.openImage(image_file_path) imp2mat = ImagePlusMatConverter() mat = imp2mat.toMat(image.getProcessor()) # white_mat = imread(white_image_file_path) print('mat', mat) def test_opencv(): test_ijopencv_converter() test_opencv_calc_hist() # If a Jython script is run, the variable __name__ contains the string '__main__'. # If a script is loaded as module, __name__ has a different value. if __name__ in ['__builtin__', '__main__']: # run_script() # test_get_image_median_value() test_opencv()