diff --git a/environment.yml b/environment.yml index cf5de899..5bd97373 100644 --- a/environment.yml +++ b/environment.yml @@ -18,6 +18,7 @@ dependencies: - labeling >= 0.1.12 - magicgui >= 0.5.1 - napari + - numpy - openjdk=8 - pyimagej >= 1.2.0 # Project from source diff --git a/setup.cfg b/setup.cfg index ed2138f2..7075979c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,6 +40,7 @@ install_requires = confuse labeling >= 0.1.12 napari + numpy magicgui >= 0.5.1 pyimagej >= 1.2.0 diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index db8116f7..7467707e 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -442,6 +442,10 @@ def SuperEllipsoid(self): def Dataset(self): return "net.imagej.Dataset" + @blocking_import + def DatasetView(self): + return "net.imagej.display.DatasetView" + @blocking_import def DefaultROITree(self): return "net.imagej.roi.DefaultROITree" @@ -450,6 +454,10 @@ def DefaultROITree(self): def ImageDisplay(self): return "net.imagej.display.ImageDisplay" + @blocking_import + def ImgPlus(self): + return "net.imagej.ImgPlus" + @blocking_import def Mesh(self): return "net.imagej.mesh.Mesh" diff --git a/src/napari_imagej/types/converters/images.py b/src/napari_imagej/types/converters/images.py index 15709fc5..769a2e2a 100644 --- a/src/napari_imagej/types/converters/images.py +++ b/src/napari_imagej/types/converters/images.py @@ -3,13 +3,50 @@ and napari Images """ +from typing import Any + +from imagej.dims import _has_axis from jpype import JArray, JByte from napari.layers import Image from napari.utils.colormaps import Colormap +from numpy import ndarray, ones from scyjava import Priority from napari_imagej.java import ij, jc -from napari_imagej.types.converters import py_to_java_converter +from napari_imagej.types.converters import java_to_py_converter, py_to_java_converter + + +def _can_convert_img_plus(obj: Any): + can_convert = ij().convert().supports(obj, jc.ImgPlus) + has_axis = _has_axis(obj) + return can_convert and has_axis + + +@java_to_py_converter( + predicate=lambda obj: ij().convert().supports(obj, jc.DatasetView), + priority=Priority.VERY_HIGH + 1, +) +def _dataset_view_to_image(image: Any) -> Image: + view = ij().convert().convert(image, jc.DatasetView) + # Construct a dataset from the data + data = ij().py.from_java(view.getData().getImgPlus().getImg()) + name = view.getData().getName() + cmap = _color_table_to_colormap(view.getColorTables().get(0)) + return _new_image(data, name, cmap) + + +@java_to_py_converter(predicate=_can_convert_img_plus, priority=Priority.VERY_HIGH) +def _dataset_to_image(image: Any) -> Image: + imp = ij().convert().convert(image, jc.ImgPlus) + # Construct a dataset from the data + data = ij().py.from_java(imp.getImg()) + name = imp.getName() + cmap = imp.getColorTable(0) + return _new_image(data, name, cmap) + + +def _new_image(data: ndarray, name=None, cmap="gray") -> Image: + return Image(data=data, name=name, colormap=cmap) @py_to_java_converter( @@ -23,13 +60,13 @@ def _image_to_dataset(image: Image) -> "jc.Dataset": dataset.setName(image.name) # Add color table, if the image uses a custom colormap if image.colormap.name != "gray": - color_table = _colormap_to_colorTable(image.colormap) + color_table = _colormap_to_color_table(image.colormap) dataset.initializeColorTables(1) dataset.setColorTable(color_table, 0) return dataset -def _colormap_to_colorTable(cmap: Colormap): +def _colormap_to_color_table(cmap: Colormap): """ Converts a napari Colormap into a SciJava ColorTable. :param cmap: The napari Colormap @@ -47,3 +84,18 @@ def _colormap_to_colorTable(cmap: Colormap): j_values[i][j] = value if value < 128 else value - 256 return jc.ColorTable8(j_values) + + +def _color_table_to_colormap(ctable: "jc.ColorTable"): + """ + Converts a SciJava ColorTable into a napari Colormap. + :param ctable: The SciJava ColorTable + :return: An "equivalent" napari Colormap + """ + 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) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index ad5cf87c..541dd3d7 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -205,36 +205,30 @@ def get_chosen_layer(self) -> None: # grab the chosen name name = choices["data"] # grab the chosen image - i = names.index(name) - image = ij().py.from_java(images[i]) - # if the conversion is already a layer, add it directly - if isinstance(image, Layer): - image.name = name - self.viewer.add_layer(image) - # otherwise, try to coerce it into an Image layer - elif ij().py._is_arraylike(image): - self.viewer.add_image(data=image, name=name) - # if we can't coerce it, give up - else: - raise ValueError(f"{image} cannot be displayed in napari!") + view = ( + ij().get("org.scijava.display.DisplayService").getDisplay(name).get(0) + ) + self._add_image_from_display_view(view) def get_active_layer(self) -> None: - # Choose the active Dataset - image = ij().get("net.imagej.display.ImageDisplayService").getActiveDataset() - if image is None: + # Choose the active DatasetView + view = ij().get("net.imagej.display.ImageDisplayService").getActiveDatasetView() + if view is None: log_debug("There is no active window to export to napari") return + self._add_image_from_display_view(view) + + def _add_image_from_display_view(self, view: "jc.DatasetView"): # Get the stuff needed for a new layer - py_image = ij().py.from_java(image) - name = ij().object().getName(image) + py_image = ij().py.from_java(view) # Create and add the layer if isinstance(py_image, Layer): - py_image.name = name self.viewer.add_layer(py_image) elif ij().py._is_arraylike(py_image): + name = ij().object().getName(view) self.viewer.add_image(data=py_image, name=name) else: - raise ValueError(f"{image} cannot be displayed in napari!") + raise ValueError(f"{view} cannot be displayed in napari!") class GUIButton(QPushButton):