Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display 3D mutlichannel images #313

Merged
merged 1 commit into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Loading