imglib / imglyb

Connecting Java/ImgLib2 + Python/NumPy
https://pypi.org/project/imglyb/
BSD 2-Clause "Simplified" License
31 stars 5 forks source link

Unable to convert ImgLib2 BooleanType image to numpy/xarray bool #13

Open elevans opened 3 years ago

elevans commented 3 years ago

imglyb does not support converting bool type images to numpy. This means that users are unable to convert masks/thresholded images into python numpy/xarray objects.

Here is a minimal example:

import imagej
import scyjava as sj

# initialize
ij = imagej.init()

# load blobs
img = ij.io().open('blobs.tif')

# threshold image w/ ops
CreateNamespace = sj.jimport('net.imagej.ops.create.CreateNamespace')

img_invert = ij.op().namespace(CreateNamespace).img(img)
ij.op().image().invert(img_invert, img)
threshold = ij.op().threshold().otsu(img_invert)

# get threshold from java
py_threshold = ij.py.from_java(threshold)

Returns the traceback:

File "/home/edward/Documents/repos/loci/imglyb/imglyb/util.py", line 149, in _to_imglib
    raise NotImplementedError("Cannot convert dtype to ImgLib2 type yet: {}".format(source.dtype))
NotImplementedError: Cannot convert dtype to ImgLib2 type yet: bool
elevans commented 3 years ago

With @hinerm we determined that imglib2-unsafe needs to support BitType or BooleanType types.

To resolve this bug we need to:

hinerm commented 3 years ago

Technically this is an imglib2 BitType.

Adding support for this conversion requires:

This runs into several challenges including:

At the moment we have logic to catch upsupported types and default them to float64. In the short term I think we should comment out the BitType support since it is not actually supported, and rely on the fallback support.

ctrueden commented 3 years ago

@hinerm It looks like NumPy bool typed images are booleans each stored as a byte, same as in Java. I.e. each element ends up being 8 bits under the hood. Therefore, as you say, NativeBoolType would indeed be the direct mapping.

For converting a BitType image to NumPy, we can use net.imglib2.converter.RealTypeConverters.copyFromTo(bitImg, nativeBoolImg), since it uses getReal/setRealDouble to do the copy, rather than get/set directly like net.imagej.util.Images.copy does.

So then the plan to implement this initially would be:

  1. Augment imglyb to support conversion between Python bool and ImgLib2 NativeBoolType images.
  2. Augment pyimagej's rai_to_numpy function to be smarter about types, with a new case covering conversion of BitType RAIs by allocating the new_numpy_image result as a NativeBoolType instead, and using RealTypeConverters rather than Images to do the copy.
  3. Later down the line, consider doing something like what this SO post describes to create a NumPy image backed by 1-bit elements, and figure out if we can directly go between that and a BitType ImgLib2 image with zero copying.
imagesc-bot commented 3 years ago

This issue has been mentioned on Image.sc Forum. There might be relevant details there:

https://forum.image.sc/t/show-a-binary-image-in-pyimagej/58031/2

gselzer commented 2 years ago

I noticed this issue completely ignorant of this one, and just filed imglib/imglib2-unsafe#8

@hinerm It looks like NumPy bool typed images are booleans each stored as a byte, same as in Java. I.e. each element ends up being 8 bits under the hood. Therefore, as you say, NativeBoolType would indeed be the direct mapping.

But, now seeing this, I wonder if that is the correct fix... I don't know that it is an issue with the code, but I do think that it doesn't help with this issue.

Anyways, I'm going to try and fix this, as https://github.com/imagej/napari-imagej/issues/69 doesn't do much for us without this feature. The only way to get a Mesh in stock ImageJ Ops is via a BooleanType 3D image, which we cannot get due to this.