Skip to content

Commit 348284d

Browse files
authored
Merge pull request #264 from imagej/boolean-type-img-conversion
Add conversion for BitType and BoolType imgs
2 parents b89ef68 + 2b947aa commit 348284d

File tree

2 files changed

+91
-10
lines changed

2 files changed

+91
-10
lines changed

src/imagej/images.py

+69-10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import numpy as np
88
import scyjava as sj
9+
from jpype import JException
910

1011
from imagej._java import jc
1112

@@ -15,6 +16,8 @@
1516
# fmt: off
1617
_imglib2_types = {
1718
"net.imglib2.type.logic.NativeBoolType": "bool_",
19+
"net.imglib2.type.logic.BitType": "bool_",
20+
"net.imglib2.type.logic.BoolType": "bool_",
1821
"net.imglib2.type.numeric.integer.ByteType": "int8",
1922
"net.imglib2.type.numeric.integer.ByteLongAccessType": "int8",
2023
"net.imglib2.type.numeric.integer.ShortType": "int16",
@@ -137,28 +140,57 @@ def copy_rai_into_ndarray(
137140
if not is_arraylike(narr):
138141
raise TypeError("narr is not arraylike")
139142

143+
# Suppose all mechanisms fail. Any one of these might be the one that was
144+
# "supposed" to work.
145+
failure_exceptions = []
146+
140147
# Check imglib2 version for fast copy availability.
141148
imglib2_version = sj.get_version(jc.RandomAccessibleInterval)
142149
if sj.is_version_at_least(imglib2_version, "5.9.0"):
143-
# ImgLib2 is new enough to use net.imglib2.util.ImgUtil.copy.
144-
ImgUtil = sj.jimport("net.imglib2.util.ImgUtil")
145-
ImgUtil.copy(rai, sj.to_java(narr))
146-
return narr
150+
try:
151+
# ImgLib2 is new enough to use net.imglib2.util.ImgUtil.copy.
152+
ImgUtil = sj.jimport("net.imglib2.util.ImgUtil")
153+
ImgUtil.copy(rai, sj.to_java(narr))
154+
return narr
155+
except JException as exc:
156+
# Try another method
157+
failure_exceptions.append(
158+
_format_copy_exception(exc.toString(), "net.imglib2.util.ImgUtil.copy")
159+
)
147160

148161
# Check imagej-common version for fast copy availability.
149162
imagej_common_version = sj.get_version(jc.Dataset)
150163
if sj.is_version_at_least(imagej_common_version, "0.30.0"):
151-
# ImageJ Common is new enough to use (deprecated)
152-
# net.imagej.util.Images.copy.
153-
Images = sj.jimport("net.imagej.util.Images")
154-
Images.copy(rai, sj.to_java(narr))
155-
return narr
164+
try:
165+
# ImageJ Common is new enough to use (deprecated)
166+
# net.imagej.util.Images.copy.
167+
Images = sj.jimport("net.imagej.util.Images")
168+
Images.copy(rai, sj.to_java(narr))
169+
return narr
170+
except JException as exc:
171+
# Try another method
172+
failure_exceptions.append(
173+
_format_copy_exception(exc.toString(), "net.imglib2.util.Images.copy")
174+
)
156175

157176
# Fall back to copying with ImageJ Ops's copy.rai op. In theory, Ops
158177
# should always be faster. But in practice, the copy.rai operation is
159178
# slower than the hardcoded ones above. If we were to fix Ops to be
160179
# fast always, we could eliminate the above special casing.
161-
ij.op().run("copy.rai", sj.to_java(narr), rai)
180+
try:
181+
ij.op().run("copy.rai", sj.to_java(narr), rai)
182+
return
183+
except JException as exc:
184+
# Try another method
185+
failure_exceptions.append(
186+
_format_copy_exception(
187+
exc.toString(), "net.imagej.ops.copy.CopyNamespace.rai"
188+
)
189+
)
190+
191+
# Failed
192+
failure_msg = "\n".join(failure_exceptions)
193+
raise Exception("\n" + failure_msg)
162194

163195

164196
def dtype(image_or_type) -> np.dtype:
@@ -215,3 +247,30 @@ def dtype(image_or_type) -> np.dtype:
215247
raise TypeError(f"Unsupported original ImageJ type: {imagej_type}")
216248

217249
raise TypeError("Unsupported Java type: " + str(sj.jclass(image_or_type).getName()))
250+
251+
252+
def _format_copy_exception(exc: str, fun_name: str) -> str:
253+
"""Format copy exceptions strings.
254+
255+
:param exc: Exception as a String.
256+
:param fun_name: Name of the function producing the exception.
257+
:return: The formatted exception.
258+
"""
259+
# format cast exception
260+
exc = str(exc)
261+
if "cannot be cast to" in exc:
262+
m = exc.split(" ")
263+
from_class = m[m.index("cannot") - 1]
264+
# special case if "class" is present or not
265+
ci = m.index("cast")
266+
if m[ci + 2] == "class":
267+
to_class = m[ci + 3]
268+
else:
269+
to_class = m[ci + 2]
270+
return (
271+
f"Error: Unsupported type cast via {fun_name}\n"
272+
f" Source type: {from_class}\n"
273+
f" Target type: {to_class}"
274+
)
275+
else:
276+
return exc

tests/test_image_conversion.py

+22
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,28 @@ def test_dataset_converts_to_xarray(ij):
376376
assert_inverted_xarr_equal_to_xarr(dataset, ij, xarr)
377377

378378

379+
def test_bittype_img_to_ndarray(ij):
380+
ops_version = sj.get_version(sj.jimport("net.imagej.ops.OpService"))
381+
if not sj.is_version_at_least(ops_version, "2.1.0"):
382+
pytest.skip("Fails without ImageJ Ops >= 2.1.0")
383+
384+
ArrayImgs = sj.jimport("net.imglib2.img.array.ArrayImgs")
385+
# NB ArrayImgs requires a long[] - construct long[] {10, 10, 10}
386+
dims = sj.jarray("j", 3)
387+
dims[:] = [10] * 3
388+
j_img = ArrayImgs.bits(dims)
389+
390+
p_img = ij.py.from_java(j_img)
391+
assert p_img.dtype == np.bool_
392+
393+
394+
def test_boolean_ndarray_to_img(ij):
395+
narr = np.ones((10, 10), dtype=np.bool_)
396+
j_img = ij.py.to_java(narr)
397+
BooleanType = sj.jimport("net.imglib2.type.BooleanType")
398+
assert isinstance(j_img.firstElement(), BooleanType)
399+
400+
379401
def test_image_metadata_conversion(ij):
380402
# Create a ImageMetadata
381403
DefaultImageMetadata = sj.jimport("io.scif.DefaultImageMetadata")

0 commit comments

Comments
 (0)