Skip to content

Commit

Permalink
Merge pull request #264 from imagej/boolean-type-img-conversion
Browse files Browse the repository at this point in the history
Add conversion for BitType and BoolType imgs
  • Loading branch information
elevans authored Jan 14, 2025
2 parents b89ef68 + 2b947aa commit 348284d
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 10 deletions.
79 changes: 69 additions & 10 deletions src/imagej/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import numpy as np
import scyjava as sj
from jpype import JException

from imagej._java import jc

Expand All @@ -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",
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
22 changes: 22 additions & 0 deletions tests/test_image_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 348284d

Please sign in to comment.