Using imageJ functions like type conversion and setting pixel size via pyimagej

I am using pyimagej to stitch images using the Grid/Collection stitching plugin. Afterwards, I want to convert the image to 8-bit and set the pixel size to a known value (0.5nm), then save the image to disk. My current workflow is like this:

ij = imagej.init('/Applications/Fiji.app')
plugin = 'Grid/Collection stitching'
            args = {'type': '[Filename defined position]', 'order': '[Defined by filename         ]',
                    'grid_size_x': '3', 'grid_size_y': '3', 'tile_overlap': '8', 'first_file_index_x': str(index_x),
                    'first_file_index_y': str(index_y), 'directory': img_path,
                    'file_names': 'Tile_{yyy}-{xxx}-000000_0-000.tif', 'output_textfile_name': 'TileConfiguration.txt',
                    'fusion_method': '[Intensity of random input tile]', 'regression_threshold': '0.15',
                    'max/avg_displacement_threshold': '0.20', 'absolute_displacement_threshold': '0.30',
                    'compute_overlap': True, 'computation_parameters': '[Save memory (but be slower)]',
                    'image_output': '[Fuse and display]'}

ij.py.run_plugin(plugin, args)
WindowManager = autoclass('ij.WindowManager')
stitched_img = WindowManager.getCurrentImage()
# Convert it to an ImageJ2 dataset. It was an imageJ1 ImagePlus before and the save function can't handle
# that. See: https://github.com/imagej/pyimagej/issues/35
stitched_img_dataset = ij.py.to_dataset(stitched_img)

# TODO: Change to 8-bit
# In imagej, I would do this by going to Type/8bit, with the macro call: run("8-bit"); or with the Java macro call IJ.run(imp, "8-bit", "");

# TODO: Set the pixel size
# In imagej, I would go to Image/Properties and adjust the parameters, use the macro run("Properties...", "channels=1 slices=1 frames=1 unit=nm pixel_width=0.5 pixel_height=0.5 voxel_depth=0.5"); or run the Java macro IJ.run(imp, "Properties...", "channels=1 slices=1 frames=1 unit=nm pixel_width=0.5000000 pixel_height=0.5000000 voxel_depth=0.5000000");

ij.io().save(stitched_img_dataset, '/Users/Joel/Desktop/test2.png')

Can I do the two ToDos via pyimagej? How can I change the image to 8-bit and change the pixel size?
Generally, is there a way to see what API calls are available in pyimagej and what parameters they would take?

1 Like

Welcome to the forum, @jluethi! And glad you are finding pyimagej useful.

Your instinct to record Java (or maybe better, JavaScript) code via the Recorder is a good one. The code generated there should work as written (minus the semicolon) from pyimagej—at least when initialized in non-headless mode.

Did you try:

from jnius import autoclass
IJ = autoclass('ij.IJ')
imp = IJ.getImage()
IJ.run(imp, "8-bit", "")

?

Similarly:

IJ.run(imp, "Properties...", "channels=1 slices=1 frames=1 unit=nm pixel_width=0.5000000 pixel_height=0.5000000 voxel_depth=0.5000000")

The API of ImageJ is absolutely enormous; there is not really a comprehensive way to see everything. Nearly anything you can do in Java, you can do from Python via pyjnius as shown above.

To see what the Java API is capable of, you can browse the javadocs at https://javadoc.scijava.org/, but again, there is an avalanche of information there.

To learn the ImageJ API in general, you can browse the ImageJ tutorial notebooks at https://imagej.github.io/tutorials/. Most of the notebooks are in Groovy (so Jupyter is backed by the JVM), but the API from Python via pyimagej will be extremely similar if not identical.

Your other resource is this forum: just post here if you get stuck, and we’ll help! :+1:

4 Likes

Instead of calling the Grid/Collection Stitching plugin in this way and having to rely on the active image being the stitched image, you can also perform the stitching by calling the lower level API.

Following up on our discussion in imagej/pyimagej#35, I now released version 0.0.1 of the faim-ij2-visiview-processing component, so you now should be able to initialize with Maven as suggested by @ctrueden in that linked discussion:

ij = imagej.init('sc.fiji:fiji:2.0.0-pre-10+ch.fmi:faim-ij2-visiview-processing:0.0.1')

… and then call the static methods of the StitchingUtils class as follows (pseudo-code, untested):

# load your images, e.g.
# imps = BF.open('path/to/your/bio-formats/image.czi')
# define starting positions as list of float[] (if applicable)
# positions = [[0, 0], [0, 100], [100, 0], [100, 100]]
dimensionality = 2
computeOverlap = True

StitchingUtils = autoclass('ch.fmi.visiview.StitchingUtils')
models = StitchingUtils.computeStitching( imps, positions, dimensionality, computeOverlap)

resultImp = StitchingUtils.fuseTiles(imps, models, dimensionality)

IJ.run(resultImp, "8-bit")
...

3 Likes

Awesome, @imagejan!

@jluethi Another advantage of @imagejan’s way is that it avoids the ImageJ1 plugin execution mechanism, and thus can invoke the stitching code without needing to wrap a local Fiji installation with plugins.dir set up.

Awesome, thanks a lot @ctrueden & @imagejan!
I could get those calls for 8bit images and pixel size working as you described @ctrueden. Thanks a lot! I can create a small tutorial of my stitching example into once I’ve managed to do it with the lower level APIs as well. Maybe this will help someone who has a similar problem in the future.

Following up on our discussion in imagej/pyimagej#35 , I now released version 0.0.1 of the faim-ij2-visiview-processing component, so you now should be able to initialize with Maven as suggested by @ctrueden in that linked discussion:

ij = imagej.init('sc.fiji:fiji:2.0.0-pre-10+ch.fmi:faim-ij2-visiview-processing:0.0.1')

@imagejan Is your 0.0.1 version of faim-fij2-visiview-processing deployed to SciJava Maven? When I’m trying to initialize via Maven as you describe above, I get the following error message:

Traceback (most recent call last):
  File "/Users/Joel/ForkStitcher/stitch_MAPS_annotations.py", line 220, in <module>
    main()
  File "/Users/Joel/ForkStitcher/stitch_MAPS_annotations.py", line 212, in main
    eight_bit=True)
  File "/Users/Joel/ForkStitcher/stitch_MAPS_annotations.py", line 25, in stitch_annotated_tiles
    ij = imagej.init('sc.fiji:fiji:2.0.0-pre-10+ch.fmi:faim-ij2-visiview-processing:0.0.1')
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/site-packages/imagej/imagej.py", line 104, in init
    import imglyb
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/site-packages/imglyb/__init__.py", line 41, in <module>
    config, _ = _init_jvm_options()
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/site-packages/imglyb/__init__.py", line 36, in _init_jvm_options
    import scyjava
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/site-packages/scyjava/__init__.py", line 122, in <module>
    jnius = _init_jvm()
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/site-packages/scyjava/__init__.py", line 108, in _init_jvm
    verbose=scyjava_config.get_verbose()
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/site-packages/jgo/jgo.py", line 457, in resolve_dependencies
    raise e
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/site-packages/jgo/jgo.py", line 448, in resolve_dependencies
    mvn_out = run_and_combine_outputs(mvn, *mvn_args)
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/site-packages/jgo/jgo.py", line 205, in run_and_combine_outputs
    return subprocess.check_output((command,) + args, stderr=subprocess.STDOUT)
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/subprocess.py", line 395, in check_output
    **kwargs).stdout
  File "/miniconda3/envs/fork_stitcher/lib/python3.7/subprocess.py", line 487, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '('/miniconda3/envs/fork_stitcher/bin/mvn', '-B', '-f', '/Users/Joel/.jgo/sc/fiji/fiji/2.0.0-pre-10+ch.fmi-faim-ij2-visiview-processing-0.0.1+net.imglib-imglib2-imglyb-0.3.0/pom.xml', 'dependency:resolve')' returned non-zero exit status 1.

This sounds like it could not get the faim-fij2-visiview-processing from Maven or failed when getting one of its dependencies.
Maybe it’s related to the fact that the cached imagej that is in /Users/Joel/.jgo/sc/fiji/fiji is 2.0.0-pre-10+ch.fmi-faim-ij2-visiview-processing-0.0.1+net.imglib-imglib2-imglyb-0.3.0, thus with an additional +net.imglib-imglib2-imglyb-0.3.0?
Or should I be installing something else locally to make this work?


For people stumbling on this later, here’s the code that worked for me (while using the imageJ1 plugin execution mechanism). Basically as described above, just adding the global to the Properties call.

plugin = 'Grid/Collection stitching'
args = {'type': '[Filename defined position]', 'order': '[Defined by filename         ]',
        'grid_size_x': '3', 'grid_size_y': '3', 'tile_overlap': '8', 'first_file_index_x': str(index_x),
        'first_file_index_y': str(index_y), 'directory': img_path,
        'file_names': 'Tile_{yyy}-{xxx}-000000_0-000.tif', 'output_textfile_name': 'TileConfiguration.txt',
        'fusion_method': '[Intensity of random input tile]', 'regression_threshold': '0.15',
        'max/avg_displacement_threshold': '0.20', 'absolute_displacement_threshold': '0.30',
        'compute_overlap': True, 'computation_parameters': '[Save memory (but be slower)]',
        'image_output': '[Fuse and display]'}
ij.py.run_plugin(plugin, args)

# Use imageJ to set bit depth, pixel size & save the image.
from jnius import autoclass
IJ = autoclass('ij.IJ')

# Get the open window with the stitched image
WindowManager = autoclass('ij.WindowManager')
stitched_img = WindowManager.getCurrentImage()

# Set the pixel size
pixel_size_nm = str(pixel_size * 10e8)
pixel_size_command = "channels=1 slices=1 frames=1 unit=nm pixel_width=" + pixel_size_nm + \
                     " pixel_height=" + pixel_size_nm + " voxel_depth=1.0 global"
IJ.run(stitched_img, "Properties...", pixel_size_command)

# Convert to 8 bit
if eight_bit:
    IJ.run(stitched_img, "8-bit", "")

# Convert it to an ImageJ2 dataset. It was an imageJ1 ImagePlus before and the save function can't handle
# that. See: https://github.com/imagej/pyimagej/issues/35
stitched_img_dataset = ij.py.to_dataset(stitched_img)
ij.io().save(stitched_img_dataset, '/Users/Joel/Desktop/test.png')
1 Like

Yes, it is here:

https://maven.scijava.org/#nexus-search;gav~ch.fmi~faim-ij2-visiview-processing~0.0.1~~

What’s the content of the pom.xml file on your system?

Does running mvn dependency:resolve -B -f pom.xml on that file work?

@imagejan
This is the content of the pom.xml file:

<project>
	<modelVersion>4.0.0</modelVersion>
	<groupId>sc.fiji-BOOTSTRAPPER</groupId>
	<artifactId>fiji-BOOTSTRAPPER</artifactId>
	<version>0</version>
	<dependencyManagement>
		<dependencies></dependencies>
	</dependencyManagement>
	<dependencies><dependency><groupId>sc.fiji</groupId><artifactId>fiji</artifactId><version>2.0.0-pre-10</version></dependency><dependency><groupId>ch.fmi</groupId><artifactId>faim-ij2-visiview-processing</artifactId><version>0.0.1</version></dependency><dependency><groupId>net.imglib</groupId><artifactId>imglib2-imglyb</artifactId><version>0.3.0</version></dependency></dependencies>
	<repositories><repository><id>imagej.public</id><url>https://maven.imagej.net/content/groups/public</url></repository><repository><id>saalfeldlab</id><url>https://saalfeldlab.github.io/maven</url></repository></repositories>
</project>``` 

And if I run mvn dependency:resolve -B -f pom.xml on it, I get the following error:

[INFO] Scanning for projects...

[INFO]

[INFO] ---------------< sc.fiji-BOOTSTRAPPER:fiji-BOOTSTRAPPER >---------------

[INFO] Building fiji-BOOTSTRAPPER 0

[INFO] --------------------------------[ jar ]---------------------------------

[INFO] Downloading from imagej.public: https://maven.imagej.net/content/groups/public/ch/fmi/faim-ij2-visiview-processing/0.0.1/faim-ij2-visiview-processing-0.0.1.pom

[INFO] ------------------------------------------------------------------------

[INFO] BUILD FAILURE

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 6.014 s

[INFO] Finished at: 2019-05-20T12:38:31+02:00

[INFO] ------------------------------------------------------------------------

[ERROR] Failed to execute goal on project fiji-BOOTSTRAPPER: Could not resolve dependencies for project sc.fiji-BOOTSTRAPPER:fiji-BOOTSTRAPPER:jar:0: Failed to collect dependencies at ch.fmi:faim-ij2-visiview-processing:jar:0.0.1: Failed to read artifact descriptor for ch.fmi:faim-ij2-visiview-processing:jar:0.0.1: Could not transfer artifact ch.fmi:faim-ij2-visiview-processing:pom:0.0.1 from/to imagej.public (https://maven.imagej.net/content/groups/public): Certificate for <maven.imagej.net> doesn't match any of the subject alternative names: [maven.scijava.org] -> [Help 1]

[ERROR]

[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.

[ERROR] Re-run Maven using the -X switch to enable full debug logging.

[ERROR]

[ERROR] For more information about the errors and possible solutions, please read the following articles:

[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException

Aha, that might be related to the recent migration of maven.imagej.net to maven.scijava.org by @ctrueden. Maybe some certificates are still carrying the now-obsolete domain?

I got the same error locally when running on your pom.xml, but it occurred with the sc.fiji:fiji:pom:2.0.0-pre-10 artifact:

[ERROR] Failed to execute goal on project fiji-BOOTSTRAPPER: Could not resolve dependencies for project sc.fiji-BOOTSTRAPPER:fiji-BOOTSTRAPPER:jar:0: Failed to collect dependencies at sc.fiji:fiji:jar:2.0.0-pre-10: Failed to read artifact descriptor for sc.fiji:fiji:jar:2.0.0-pre-10: Could not transfer artifact sc.fiji:fiji:pom:2.0.0-pre-10 from/to imagej.public (https://maven.imagej.net/content/groups/public): Certificate for <maven.imagej.net> doesn't match any of the subject alternative names: [maven.scijava.org] -> [Help 1]

Edit:
It seems in the auto-generated pom.xml, the old repository URL maven.imagej.net is still used. @jluethi would you mind trying to replace:

<repository><id>imagej.public</id><url>https://maven.imagej.net/content/groups/public</url></repository>

with:

<repository><id>scijava.public</id><url>https://maven.scijava.org/content/groups/public</url></repository>

and trying again? We might be able to fix this upstream then.

1 Like

We likely just need a new release of scijava/jgo that includes this commit by @ctrueden:

:wink:

1 Like

@imagejan Thanks! When I make that change manually, I get a successful build via mvn dependency:resolve -B -f pom.xml. But when I use ij = imagej.init('sc.fiji:fiji:2.0.0-pre-10+ch.fmi:faim-ij2-visiview-processing:0.0.1') in my script afterwards, it changes the pom.xml file back to the imagej.public paths and fails again.

Is a new release of scijava/jgo something that will come up soon? If so, I can wait for that. Or is there a workaround to using the manually adapted version of the pom.xml file I can run locally?

I accidentally broke HTTPS support for maven.imagej.net a few days ago, when I fixed an unrelated problem. Sorry about that. I have now remedied the issue; the https://maven.imagej.net/ URLs should now work again, including with existing versions of jgo. So a new release of jgo should not be needed urgently. @jluethi Let me know whether things work better now?

1 Like

Thank you @ctrueden, now it works fine :tada:

I have another question for @imagejan though, sorry for taking so much of your time. You described above that one needs to open the images and save them into the imps variable like this:

If I have 4-9 distinct TIFF files instead that are being stitched instead of files in a container, how can I open multiple TIFF files into the imps variable? Is there an API I can use to add/append additional files to the imps variable? I’ve seen that I can load a single image via imps = ij.io().open(str(img_path / center_filename)), how do I add to this ArrayList?

1 Like

I just chose the bio-formats example because it opens multi-series files as a list of ij.ImagePlus objects, and that’s what is consumed by the stitching plugin as well.

To create a list of images from distinct ImagePlus objects, it should be as easy as creating a python list (if I understand correctly), as this would get translated to an ArrayList on the java side.

The issue with this one is that ij.io().open() returns a Dataset (the ImageJ2 representation of an image), but the Grid/Collection Stitching plugin still entirely relies on ImageJ 1.x data structures, so you likely need to convert these objects first. In Java, I’d call ConvertService.convert() as follows:

// Dataset ds = ij.io().open(...)
ImagePlus imp = ij.convert().convert(dataset, ImagePlus.class);

I suggest you try in a similar way from Python, all the services are available from the ij gateway.
Once you converted to ImagePlus, it should be sufficient to create a python list from them:

imps = [imp1, imp2, imp3]

Let me know if that works for you.

1 Like

@imagejan Wow, those different image formats get me every time :smile:
I can load all the images into ImagePlus images and combine them to a list, but the computeStitching function does not take this list of ImagePlus as an input. It seems to want a ‘java/util/ArrayList’ and the list of ImagePlus (or its content) is seen as ij/ImagePlus

ImagePlusClass = autoclass('ij.ImagePlus')
imagej2_img = ij.io().open(str(img_path / center_filename))
imps = [ij.convert().convert(imagej2_img, ImagePlusClass)]

for neighbor in annotation_tiles[annotation_name]['surrounding_tile_names']:
    print(neighbor)
    imagej2_img = ij.io().open(str(img_path / neighbor))
    imps.append(ij.convert().convert(imagej2_img, ImagePlusClass))

# Define starting positions
positions = [[3686, 3686], [0, 0], [3686, 0], [7373, 0], [0, 3686], [7373, 3686], [0, 7373],
             [3686, 7373], [7373, 7373]]

dimensionality = 2
computeOverlap = True
StitchingUtils = autoclass('ch.fmi.visiview.StitchingUtils')
models = StitchingUtils.computeStitching(imps, positions, dimensionality, computeOverlap)

Results in this error message:

Traceback (most recent call last):
  File "/Users/Joel/ForkStitcher/stitch_MAPS_annotations.py", line 240, in <module>
    main()
  File "/Users/Joel/ForkStitcher/stitch_MAPS_annotations.py", line 232, in main
    eight_bit=True)
  File "/Users/Joel/ForkStitcher/stitch_MAPS_annotations.py", line 67, in stitch_annotated_tiles
    models = StitchingUtils.computeStitching(imps, positions, dimensionality, computeOverlap)
  File "jnius/jnius_export_class.pxi", line 760, in jnius.JavaMethod.__call__
  File "jnius/jnius_conversion.pxi", line 106, in jnius.populate_args
  File "jnius/jnius_conversion.pxi", line 678, in jnius.convert_pyarray_to_java
  File "jnius/jnius_utils.pxi", line 205, in jnius.check_assignable_from
jnius.JavaException: Invalid instance of 'ij/ImagePlus' passed for a 'java/util/ArrayList'

Any idea how I could convert my list of images to a Java ArrayList?

1 Like

This is where the magic of scyjava comes in handy. Try this:

java_list = ij.py.to_java(imps)
models = StitchingUtils.computeStitching(java_list, positions, dimensionality, computeOverlap)

Alternately, it can also be done manually:

ArrayList = autoclass('java.util.ArrayList')
java_list = ArrayList()
for imp in imps:
    java_list.add(imp)

But the to_java method is supposed to avoid the need for that.

2 Likes

@ctrueden Thank you for the hint, I think the to_java part works for the ArrayList. Do all input elements need to be converted to Java? I first only converted the imps to a Java array and got this error:

Traceback (most recent call last):
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 240, in <module>
    main()
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 232, in main
    eight_bit=True)
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 67, in stitch_annotated_tiles
    models = StitchingUtils.computeStitching(java_imgs, positions, dimensionality, computeOverlap)
  File "jnius/jnius_export_class.pxi", line 760, in jnius.JavaMethod.__call__
  File "jnius/jnius_conversion.pxi", line 106, in jnius.populate_args
  File "jnius/jnius_conversion.pxi", line 667, in jnius.convert_pyarray_to_java
  File "jnius/jnius_conversion.pxi", line 696, in jnius.convert_pyarray_to_java
jnius.JavaException: Invalid variable [0, 0] used for L array 'Ljava/util/ArrayList;'

For reference, this is the code I’m using:

from jnius import autoclass
image_plus_class = autoclass('ij.ImagePlus')
imps = []

for neighbor in annotation_tiles[annotation_name]['surrounding_tile_names']:
    print(neighbor)
    imagej2_img = ij.io().open(str(img_path / neighbor))
    imps.append(ij.convert().convert(imagej2_img, image_plus_class))
# Define starting positions
positions = [[0, 0], [3686, 0], [7373, 0], [0, 3686], [3686, 3686], [7373, 3686], [0, 7373], [3686, 7373], [7373, 7373]]
java_imgs = ij.py.to_java(imps)

dimensionality = 2
computeOverlap = True
StitchingUtils = autoclass('ch.fmi.visiview.StitchingUtils')
models = StitchingUtils.computeStitching(java_imgs, positions, dimensionality, computeOverlap)

resultImp = StitchingUtils.fuseTiles(imps, models, dimensionality)

I assume that this means that the positions list also needs to be converted to a Java array, so I changed that line to:

positions = ij.py.to_java([[0, 0], [3686, 0], [7373, 0], [0, 3686], [3686, 3686], [7373, 3686], [0, 7373], [3686, 7373], [7373, 7373]])

Then, that issue seems solved, but I get the following error:

Traceback (most recent call last):
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 240, in <module>
    main()
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 232, in main
    eight_bit=True)
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 67, in stitch_annotated_tiles
    models = StitchingUtils.computeStitching(java_imgs, positions, dimensionality, computeOverlap)
  File "jnius/jnius_export_class.pxi", line 765, in jnius.JavaMethod.__call__
  File "jnius/jnius_export_class.pxi", line 931, in jnius.JavaMethod.call_staticmethod
  File "jnius/jnius_utils.pxi", line 91, in jnius.check_exception
jnius.JavaException: JVM exception occurred: java.util.ArrayList cannot be cast to [F

I’m not sure if the output is somehow cut off here or if it really wants to cast to [F. I thought maybe I also need to cast dimensionality & computeOverlap to some Java format, as they are also inputs to that function call. But if I change them to:

dimensionality = ij.py.to_java(2)
computeOverlap = ij.py.to_java(True)

I get the following error:

Traceback (most recent call last):
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 240, in <module>
    main()
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 232, in main
    eight_bit=True)
  File "/Users/Joel/Dropbox/Joel/Zivildienst/ForkStitcher/fork-stitcher/stitch_MAPS_annotations.py", line 67, in stitch_annotated_tiles
    models = StitchingUtils.computeStitching(java_imgs, positions, dimensionality, computeOverlap)
  File "jnius/jnius_export_class.pxi", line 760, in jnius.JavaMethod.__call__
  File "jnius/jnius_jvm_dlopen.pxi", line 50, in jnius.create_jnienv
TypeError: an integer is required

@ctrueden Can you help me out with how I need to cast my inputs to the correct Java classes? I assume it’s just the last two that are not cast correctly, but maybe it’s something different related to java.util.ArrayList cannot be cast to [F?

Yes, except for simple types (if they are not already), but to_java does it recursively. So a single invocation is typically all you need. However, for this case, it is trickier; see below.

Just in case it’s not already clear to you: in Java parlance, [F means float[]—i.e., an array of primitive floats. So that error implies you are trying to pass an ArrayList as an argument to a method that wants a float[] parameter.

You shouldn’t have to explicitly convert basic data types. The pyjnius library should take care of all the primitive types (and in limited circumstances, arrays thereof), as well as String objects.

The method you are trying to call has this signature:

computeStitching(ArrayList<ImagePlus> images, ArrayList<float[]> positions, int dimensionality, boolean computeOverlap)

So you need an ArrayList<float[]>. This ends up being tricky:

  • The thing you are creating is a Python list populated with elements that are themselves Python lists, each with two elements of type int.
  • After calling ij.py.to_java on that thing, you end with with an ArrayList<ArrayList<Integer>>:
    >>> x = [[1, 2], [3, 4], [5, 6]]
    >>> jx = ij.py.to_java(x)
    >>> type(jx)
    <class 'jnius.reflect.java.util.ArrayList'>
    >>> type(jx[0])
    <class 'jnius.reflect.java.util.ArrayList'>
    >>> type(jx[0][0])
    <class 'int'>
    
    In other words: not an ArrayList<float[]>.

This happens because the to_java method is recursive; whenever it comes across a Python list it transforms it into a Java ArrayList. And when it comes across your Python ints, it makes them into Java Integers (not Floats).

One way forward is to add your float pairs manually, after converting:

 >>> jlist = ij.py.to_java([])
>>> jlist.add([1.0, 2.0])
True
>>> jlist.add([3.4, 5.6])
True
>>> jlist.toString()
'[[F@648d0e6d, [F@79e66b2f]'
>>> type(jlist)
<class 'jnius.reflect.java.util.ArrayList'>
>>> type(jlist[0])
<class 'list'>
>>> type(jlist[0][0])
<class 'float'>

Note the use of 1.0 instead of 1, so the numbers are float rather than int.

As you can see from the above, we end up with a Java ArrayList object that on the Java side sees its elements as float[] objects. On the Python side, the type of each pair is still seen as list, but I think this is an implicit conversion done by pyjnius. I think it will work to pass that thing to the StitchingUtils method. Give it a try and let us know how it goes.

1 Like

@ctrueden Cool, that works! :slight_smile: Thanks a lot!

Now part of the reason to call those lower level APIs was to get the stitching parameters out directly instead of reading them in from a file (that could be overwritten by another process stitching in parallel). Those values are in my models variable after the following call:

models = StitchingUtils.computeStitching(java_imgs, positions_jlist, dimensionality, computeOverlap)

Unfortunately, this models variable is an object of class jnius.reflect.mpicbg.models.TranslationModel2D, so ij.py.from_java() doesn’t work with it (TypeError: Unsupported data type).
I thought I could import the TranslationModel2D class and somehow use its toDataString function. But when I try any of the following, I get a jnius.JavaException: Class not found b'ij/TranslationModel2D' error:

translation_model = autoclass('ij.TranslationModel2D')
translation_model = autoclass('ij.reflect.mpicbg.models.TranslationModel2D')
translation_model = autoclass('ij.mpicbg.models.TranslationModel2D')
translation_model = autoclass('ij.mpicbg.trakem2.transform')

@ctrueden Is this the right approach to get the data out of model? How would I autoclass that correctly? And how do I call the toDataString function afterwards from python on that object?


@imagejan Also, I have a question / feature request for the faim-ij2-visiview-processing. Is it possible to expose some fusion options? Via the ImageJ1 plugin, I can choose to fuse by overlap (Intensity of random input tile). I saw that there is a subclass of Fusion called OverlapFusion, which seems to implement that. Is it possible to have this as an option in faim-ij2-visiview-processing for the StitchingUtils.fuseTiles function?
I’m not that familiar with Java, but I can also try and see if I can build it myself, if you don’t have time for this at the moment.

Ignore the jnius.reflect. prefix—that’s only the Python-adapted class name. The Java class name is mpicbg.models.TranslationModel2D. One way to learn about a class is to browse its javadoc (see here for TranslationModel2D).

You typically won’t need to autoclass much if anything when extracting state from a Java object—just call the getters that return what you want. In the case of TranslationModel2D though it is tricky: there is no getter to return the affine transformation parameters. There is instead a method void toArray(double[] data) where you pass a preallocated double[] array of length 6, and it writes the affine parameters into it. I didn’t test, but it might work to write something like:

params = [0.0] * 6
models.toArray(params)
print(params)

Or if that has typing issues, you can allocate the Java array using Java calls like:

>>> from jnius import autoclass
>>> Array = autoclass('java.lang.reflect.Array')
>>> Double = autoclass('java.lang.Double')
>>> params = Array.newInstance(Double.TYPE, 6)
>>> params
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
>>> type(params)
<class 'list'>

But as you can see, pyjnius coerces it from double[] back to list anyway on the Python side, so I’m not sure if it will make a difference or not. But hopefully the first way works.

Of course we are all busy; the further you can get on your own, the better. Building from source is not so hard. Here is documentation to get you started:

Including links to various popular IDEs and how to use them with these projects.

Happy to continue helping if you get stuck, but if you can develop a little proficiency hacking on the Java side, I expect you’ll feel very empowered. :grin:

1 Like

Thanks for the suggestion, I’ll look into adding this to my StitchingUtils class.


You can of course always call the Fusion.fuse() method directly, with an appropriate argument for fusionType.

The implementation takes a raw int in the range from 0 to 5, see here in the source:

1 Like