Skip to content

Commit

Permalink
Display 3D mutlichannel images
Browse files Browse the repository at this point in the history
  • Loading branch information
gselzer committed Nov 19, 2024
1 parent 9718380 commit 612b210
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 21 deletions.
63 changes: 49 additions & 14 deletions src/napari_imagej/types/converters/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from logging import getLogger
from typing import Any
from typing import Any, List, Union

from imagej.convert import java_to_xarray
from jpype import JArray, JByte
Expand All @@ -23,7 +23,7 @@
predicate=lambda obj: nij.ij.convert().supports(obj, jc.DatasetView),
priority=Priority.VERY_HIGH + 1,
)
def _java_image_to_image_layer(image: Any) -> Image:
def _java_image_to_image_layer(image: Any) -> Union[Image, List[Image]]:
"""
Converts a java image (i.e. something that can be converted into a DatasetView)
into a napari Image layer.
Expand All @@ -35,18 +35,37 @@ def _java_image_to_image_layer(image: Any) -> Image:
"""
# Construct a DatasetView from the Java image
view = nij.ij.convert().convert(image, jc.DatasetView)
existing_ctables = view.getColorTables() and view.getColorTables().size() > 0
data = view.getData()
# Construct an xarray from the DatasetView
xarr: DataArray = java_to_xarray(nij.ij, view.getData())
# Construct a map of Image layer parameters
kwargs = dict(
data=xarr,
metadata=getattr(xarr, "attrs", {}),
name=view.getData().getName(),
)
if view.getColorTables() and view.getColorTables().size() > 0:
if not jc.ColorTables.isGrayColorTable(view.getColorTables().get(0)):
kwargs["colormap"] = _color_table_to_colormap(view.getColorTables().get(0))
return Image(**kwargs)
xarr: DataArray = java_to_xarray(nij.ij, data)
# General layer parameters
kwargs = dict()
kwargs["name"] = data.getName()
kwargs["metadata"] = getattr(xarr, "attrs", {})

# Channel-less data
if "ch" not in xarr.dims:
if existing_ctables:
cmap = _color_table_to_colormap(view.getColorTables().get(0))
kwargs["colormap"] = cmap
pass
# RGB data - set RGB flag
elif xarr.sizes["ch"] in [3, 4]:
kwargs["rgb"] = True
# Channel data - but not RGB - need one layer per channel
else:
kwargs["blending"] = "additive"
channels = []
for d in range(xarr.sizes["ch"]):
kw = kwargs.copy()
kw["name"] = f"{kwargs['name']}[{d}]"
if existing_ctables:
cmap = _color_table_to_colormap(view.getColorTables().get(d))
kw["colormap"] = cmap
channels.append(Image(data=xarr.sel(ch=d), **kw))
return channels
return Image(data=xarr, **kwargs)


@py_to_java_converter(
Expand Down Expand Up @@ -147,10 +166,26 @@ def _color_table_to_colormap(ctable: "jc.ColorTable"):
:param ctable: The SciJava ColorTable
:return: An "equivalent" napari Colormap
"""
builtins = {
jc.ColorTables.RED: "red",
jc.ColorTables.GREEN: "green",
jc.ColorTables.BLUE: "blue",
jc.ColorTables.CYAN: "cyan",
jc.ColorTables.MAGENTA: "magenta",
jc.ColorTables.YELLOW: "yellow",
jc.ColorTables.GRAYS: "gray",
}
if ctable in builtins:
return builtins[ctable]

components = ctable.getComponentCount()
bins = ctable.getLength()
data = ones((bins, 4), dtype=float)
for component in range(components):
for bin in range(bins):
data[bin, component] = float(ctable.get(component, bin)) / 255.0
return Colormap(colors=data)
cmap = Colormap(colors=data)
# NB prevents napari from using cached colormaps
cmap.name = str(ctable)

return cmap
18 changes: 13 additions & 5 deletions src/napari_imagej/widgets/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

from pathlib import Path
from typing import Iterable, Optional
from typing import Iterable, List, Optional, Tuple

from magicgui.widgets import request_values
from napari import Viewer
Expand Down Expand Up @@ -209,18 +209,26 @@ def get_active_layer(self) -> None:
def _add_layer(self, view):
# Convert the object into Python
py_image = nij.ij.py.from_java(view)
# Create and add the layer
if isinstance(py_image, Layer):
self.viewer.add_layer(py_image)

def add_layer(layer: Layer) -> None:
self.viewer.add_layer(layer)
# Check the metadata for additonal layers, like
# Shapes/Tracks/Points
for _, v in py_image.metadata.items():
for _, v in layer.metadata.items():
if isinstance(v, Layer):
self.viewer.add_layer(v)
elif isinstance(v, Iterable):
for itm in v:
if isinstance(itm, Layer):
self.viewer.add_layer(itm)

# Create and add the layer
if isinstance(py_image, Layer):
add_layer(py_image)
elif isinstance(py_image, (Tuple, List)):
for image in py_image:
if isinstance(image, Layer):
add_layer(image)
# Other
elif is_arraylike(py_image):
name = nij.ij.object().getName(view)
Expand Down
37 changes: 35 additions & 2 deletions tests/types/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,20 @@ def test_dataset(ij) -> "jc.Dataset":
return dataset


@pytest.fixture
def test_multichannel_dataset(ij) -> "jc.Dataset":
dataset: jc.Dataset = ij.dataset().create(ij.py.to_java(np.ones((2, 10, 10))))
dataset.setAxis(jc.DefaultLinearAxis(jc.Axes.CHANNEL, 1, 0), 2)
return dataset


@pytest.fixture
def test_rgb_dataset(ij) -> "jc.Dataset":
dataset: jc.Dataset = ij.dataset().create(ij.py.to_java(np.ones((3, 10, 10))))
dataset.setAxis(jc.DefaultLinearAxis(jc.Axes.CHANNEL, 1, 0), 2)
return dataset


@pytest.fixture
def test_dataset_view(ij, test_dataset) -> "jc.DatasetView":
view: jc.DatasetView = ij.get(
Expand Down Expand Up @@ -840,10 +854,29 @@ def test_colormap_dataset_to_image_layer(ij, test_dataset):
p_img = ij.py.from_java(test_dataset)
assert isinstance(p_img, Image)
assert test_dataset.getName() == p_img.name
assert "gray" != p_img.colormap.name
assert "cyan" == p_img.colormap.name
_assert_equal_color_maps(test_dataset.getColorTable(0), p_img.colormap)


def test_multichannel_dataset_to_image_layers(ij, test_multichannel_dataset):
test_multichannel_dataset.initializeColorTables(2)
test_multichannel_dataset.setColorTable(jc.ColorTables.CYAN, 0)
test_multichannel_dataset.setColorTable(jc.ColorTables.MAGENTA, 1)
p_imgs = ij.py.from_java(test_multichannel_dataset)
assert isinstance(p_imgs, List)
assert isinstance(p_imgs[0], Image)
assert "cyan" == p_imgs[0].colormap.name
assert isinstance(p_imgs[1], Image)
assert "magenta" == p_imgs[1].colormap.name


def test_dataset_rgb_to_image_layer(ij, test_rgb_dataset):
"""Test conversion of a Dataset with no colormap"""
p_img = ij.py.from_java(test_rgb_dataset)
assert isinstance(p_img, Image)
assert p_img.rgb


def test_dataset_view_to_image_layer(ij, test_dataset_view):
"""Test conversion of a Dataset with no colormap"""
p_img = ij.py.from_java(test_dataset_view)
Expand All @@ -858,5 +891,5 @@ def test_colormap_dataset_view_to_image_layer(ij, test_dataset_view):
p_img = ij.py.from_java(test_dataset_view)
assert isinstance(p_img, Image)
assert test_dataset_view.getData().getName() == p_img.name
assert "gray" != p_img.colormap.name
assert "cyan" == p_img.colormap.name
_assert_equal_color_maps(test_dataset_view.getColorTables().get(0), p_img.colormap)

0 comments on commit 612b210

Please sign in to comment.