diff --git a/src/imagej/images.py b/src/imagej/images.py index b774eb52..208dc097 100644 --- a/src/imagej/images.py +++ b/src/imagej/images.py @@ -6,6 +6,7 @@ import numpy as np import scyjava as sj +from jpype import JException from imagej._java import jc @@ -15,6 +16,8 @@ # fmt: off _imglib2_types = { "net.imglib2.type.logic.NativeBoolType": "bool_", + "net.imglib2.type.logic.BitType": "bool_", + "net.imglib2.type.logic.BoolType": "bool_", "net.imglib2.type.numeric.integer.ByteType": "int8", "net.imglib2.type.numeric.integer.ByteLongAccessType": "int8", "net.imglib2.type.numeric.integer.ShortType": "int16", @@ -137,28 +140,57 @@ def copy_rai_into_ndarray( if not is_arraylike(narr): raise TypeError("narr is not arraylike") + # Suppose all mechanisms fail. Any one of these might be the one that was + # "supposed" to work. + failure_exceptions = [] + # Check imglib2 version for fast copy availability. imglib2_version = sj.get_version(jc.RandomAccessibleInterval) if sj.is_version_at_least(imglib2_version, "5.9.0"): - # ImgLib2 is new enough to use net.imglib2.util.ImgUtil.copy. - ImgUtil = sj.jimport("net.imglib2.util.ImgUtil") - ImgUtil.copy(rai, sj.to_java(narr)) - return narr + try: + # ImgLib2 is new enough to use net.imglib2.util.ImgUtil.copy. + ImgUtil = sj.jimport("net.imglib2.util.ImgUtil") + ImgUtil.copy(rai, sj.to_java(narr)) + return narr + except JException as exc: + # Try another method + failure_exceptions.append( + _format_copy_exception(exc.toString(), "net.imglib2.util.ImgUtil.copy") + ) # Check imagej-common version for fast copy availability. imagej_common_version = sj.get_version(jc.Dataset) if sj.is_version_at_least(imagej_common_version, "0.30.0"): - # ImageJ Common is new enough to use (deprecated) - # net.imagej.util.Images.copy. - Images = sj.jimport("net.imagej.util.Images") - Images.copy(rai, sj.to_java(narr)) - return narr + try: + # ImageJ Common is new enough to use (deprecated) + # net.imagej.util.Images.copy. + Images = sj.jimport("net.imagej.util.Images") + Images.copy(rai, sj.to_java(narr)) + return narr + except JException as exc: + # Try another method + failure_exceptions.append( + _format_copy_exception(exc.toString(), "net.imglib2.util.Images.copy") + ) # Fall back to copying with ImageJ Ops's copy.rai op. In theory, Ops # should always be faster. But in practice, the copy.rai operation is # slower than the hardcoded ones above. If we were to fix Ops to be # fast always, we could eliminate the above special casing. - ij.op().run("copy.rai", sj.to_java(narr), rai) + try: + ij.op().run("copy.rai", sj.to_java(narr), rai) + return + except JException as exc: + # Try another method + failure_exceptions.append( + _format_copy_exception( + exc.toString(), "net.imagej.ops.copy.CopyNamespace.rai" + ) + ) + + # Failed + failure_msg = "\n".join(failure_exceptions) + raise Exception("\n" + failure_msg) def dtype(image_or_type) -> np.dtype: @@ -215,3 +247,30 @@ def dtype(image_or_type) -> np.dtype: raise TypeError(f"Unsupported original ImageJ type: {imagej_type}") raise TypeError("Unsupported Java type: " + str(sj.jclass(image_or_type).getName())) + + +def _format_copy_exception(exc: str, fun_name: str) -> str: + """Format copy exceptions strings. + + :param exc: Exception as a String. + :param fun_name: Name of the function producing the exception. + :return: The formatted exception. + """ + # format cast exception + exc = str(exc) + if "cannot be cast to" in exc: + m = exc.split(" ") + from_class = m[m.index("cannot") - 1] + # special case if "class" is present or not + ci = m.index("cast") + if m[ci + 2] == "class": + to_class = m[ci + 3] + else: + to_class = m[ci + 2] + return ( + f"Error: Unsupported type cast via {fun_name}\n" + f" Source type: {from_class}\n" + f" Target type: {to_class}" + ) + else: + return exc diff --git a/tests/test_image_conversion.py b/tests/test_image_conversion.py index 664e1b10..93f97069 100644 --- a/tests/test_image_conversion.py +++ b/tests/test_image_conversion.py @@ -376,6 +376,28 @@ def test_dataset_converts_to_xarray(ij): assert_inverted_xarr_equal_to_xarr(dataset, ij, xarr) +def test_bittype_img_to_ndarray(ij): + ops_version = sj.get_version(sj.jimport("net.imagej.ops.OpService")) + if not sj.is_version_at_least(ops_version, "2.1.0"): + pytest.skip("Fails without ImageJ Ops >= 2.1.0") + + ArrayImgs = sj.jimport("net.imglib2.img.array.ArrayImgs") + # NB ArrayImgs requires a long[] - construct long[] {10, 10, 10} + dims = sj.jarray("j", 3) + dims[:] = [10] * 3 + j_img = ArrayImgs.bits(dims) + + p_img = ij.py.from_java(j_img) + assert p_img.dtype == np.bool_ + + +def test_boolean_ndarray_to_img(ij): + narr = np.ones((10, 10), dtype=np.bool_) + j_img = ij.py.to_java(narr) + BooleanType = sj.jimport("net.imglib2.type.BooleanType") + assert isinstance(j_img.firstElement(), BooleanType) + + def test_image_metadata_conversion(ij): # Create a ImageMetadata DefaultImageMetadata = sj.jimport("io.scif.DefaultImageMetadata")