diff --git a/src/napari_imagej/types/converters/images.py b/src/napari_imagej/types/converters/images.py index e91e0ed8..ddbce78b 100644 --- a/src/napari_imagej/types/converters/images.py +++ b/src/napari_imagej/types/converters/images.py @@ -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 @@ -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. @@ -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( @@ -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 diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index 381494ab..a7b40ae8 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -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 @@ -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) diff --git a/tests/types/test_converters.py b/tests/types/test_converters.py index afc11a40..4312a0df 100644 --- a/tests/types/test_converters.py +++ b/tests/types/test_converters.py @@ -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( @@ -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) @@ -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)