From 44f3d5f7e4a2ff15cba79e3a22468fd66017aa98 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 17 Mar 2025 12:57:11 -0500 Subject: [PATCH 1/2] Improve labels transfer to ImageJ --- dev-environment.yml | 2 +- environment.yml | 2 +- pyproject.toml | 2 +- src/napari_imagej/widgets/widget_utils.py | 11 +++++++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dev-environment.yml b/dev-environment.yml index 633eb8b2..7ac631ea 100644 --- a/dev-environment.yml +++ b/dev-environment.yml @@ -29,7 +29,7 @@ dependencies: - numpy - openjdk=11 - pandas - - pyimagej >= 1.4.1 + - pyimagej >= 1.5.0 - scyjava >= 1.9.1 - superqt >= 0.7.0 - xarray < 2024.10.0 diff --git a/environment.yml b/environment.yml index 75743119..6a2ead38 100644 --- a/environment.yml +++ b/environment.yml @@ -29,7 +29,7 @@ dependencies: - numpy - openjdk=11 - pandas - - pyimagej >= 1.4.1 + - pyimagej >= 1.5.0 - scyjava >= 1.9.1 - superqt >= 0.7.0 - xarray < 2024.10.0 diff --git a/pyproject.toml b/pyproject.toml index b4d10c92..52c0c590 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [ "napari >= 0.4.17", "numpy", "pandas", - "pyimagej >= 1.4.1", + "pyimagej >= 1.5.0", "scyjava >= 1.9.1", "superqt >= 0.7.0", "xarray < 2024.10.0", diff --git a/src/napari_imagej/widgets/widget_utils.py b/src/napari_imagej/widgets/widget_utils.py index e759f190..80c0dfe2 100644 --- a/src/napari_imagej/widgets/widget_utils.py +++ b/src/napari_imagej/widgets/widget_utils.py @@ -2,6 +2,7 @@ from logging import getLogger from typing import List +from imagej.convert import index_img_to_roi_tree from jpype import JArray, JByte from scyjava import jvm_started from magicgui import magicgui @@ -156,7 +157,7 @@ def __init__(self, title: str, error_message: str, *args, **kwargs): _IMAGE_LAYER_TYPES = (Image, Labels) -_ROI_LAYER_TYPES = (Points, Shapes) +_ROI_LAYER_TYPES = (Labels, Points, Shapes) class LayerComboBox(QWidget): @@ -261,7 +262,7 @@ def __init__(self, viewer: Viewer): for layer in viewer.layers: if isinstance(layer, _IMAGE_LAYER_TYPES): self.imgs.append(layer) - elif isinstance(layer, _ROI_LAYER_TYPES): + if isinstance(layer, _ROI_LAYER_TYPES): self.rois.append(layer) # Add combo boxes @@ -298,11 +299,17 @@ def pass_to_ij(): j_img = nij.ij.py.to_java( img, dim_order=self.dims_container.provided_labels() ) + # Labels layers should display the index image + if isinstance(j_img, jc.ImgLabeling): + j_img = j_img.getIndexImg() if roi: if isinstance(roi, Points): j_point = nij.ij.py.to_java(roi) j_roi = jc.DefaultROITree() j_roi.addROIs(jc.ArrayList([j_point])) + elif isinstance(roi, Labels): + j_label = nij.ij.py.to_java(roi) + j_roi = index_img_to_roi_tree(nij.ij, j_label.getIndexImg()) else: j_roi = nij.ij.py.to_java(roi) j_img.getProperties().put("rois", j_roi) From cf3647aeeb0b54491a4ba1494503c6ca0e8104d8 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 31 Mar 2025 14:16:50 -0500 Subject: [PATCH 2/2] Test labels transfer --- src/napari_imagej/widgets/widget_utils.py | 3 +- tests/widgets/test_menu.py | 116 +++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src/napari_imagej/widgets/widget_utils.py b/src/napari_imagej/widgets/widget_utils.py index 80c0dfe2..0f0c53e9 100644 --- a/src/napari_imagej/widgets/widget_utils.py +++ b/src/napari_imagej/widgets/widget_utils.py @@ -301,7 +301,8 @@ def pass_to_ij(): ) # Labels layers should display the index image if isinstance(j_img, jc.ImgLabeling): - j_img = j_img.getIndexImg() + j_img = nij.ij.py.to_dataset(j_img.getIndexImg()) + j_img.setName(img.name) if roi: if isinstance(roi, Points): j_point = nij.ij.py.to_java(roi) diff --git a/tests/widgets/test_menu.py b/tests/widgets/test_menu.py index b9c3b1e1..401789e4 100644 --- a/tests/widgets/test_menu.py +++ b/tests/widgets/test_menu.py @@ -7,7 +7,7 @@ import numpy import pytest from napari import Viewer -from napari.layers import Image, Layer, Shapes +from napari.layers import Image, Labels, Layer, Shapes from napari.viewer import current_viewer from qtpy.QtCore import QRunnable, Qt, QThreadPool from qtpy.QtGui import QPixmap @@ -395,6 +395,120 @@ def check_active_display(): asserter(check_active_display) +def test_labels_transfer_as_data( + popup_handler, asserter, ij, gui_widget: NapariImageJMenu +): + """Tests the detailed image exporter""" + if settings.headless(): + pytest.skip("Only applies when not running headlessly") + + button: ToIJDetailedButton = gui_widget.to_ij_detail + assert not button.isEnabled() + + # Add some labels to the viewer + sample_data = numpy.ones((50, 100), dtype=numpy.uint8) + labels = Labels(data=sample_data, name="test_labels") + current_viewer().add_layer(labels) + asserter(lambda: button.isEnabled()) + + def handle_transfer(widget: QDialog) -> bool: + if not isinstance(widget, DetailExportDialog): + print("Not an AdvancedExportDialog") + return False + if not widget.img_container.combo.currentText() == "test_labels": + print(widget.img_container.combo.currentText()) + print("Unexpected starting image text") + return False + if not widget.roi_container.combo.currentText() == "--------": + print("Unexpected starting roi text") + return False + if labels not in widget.roi_container.choices: + print("Labels layer not available") + return False + + ok_button = widget.buttons.button(QDialogButtonBox.Ok) + ok_button.clicked.emit() + return True + + popup_handler(button.clicked.emit, handle_transfer) + + # Assert that the data is now in Fiji + def check_active_display(): + if not ij.display().getActiveDisplay(): + return False + dataset = ij.display().getActiveDisplay().getActiveView().getData() + if not dataset.getName() == "test_labels": + return False + + return True + + asserter(check_active_display) + + +def test_labels_transfer_as_rois( + popup_handler, asserter, ij, gui_widget: NapariImageJMenu +): + """Tests the detailed image exporter""" + if settings.headless(): + pytest.skip("Only applies when not running headlessly") + + button: ToIJDetailedButton = gui_widget.to_ij_detail + assert not button.isEnabled() + + # Add an image to the viewer + sample_data = numpy.ones((50, 100, 3), dtype=numpy.uint8) + # NB: Unnatural data order used for testing in handler + dims = ("X", "Y", "Z") + sample_data = DataArray(data=sample_data, dims=dims) + image: Image = Image(data=sample_data, name="test_to") + current_viewer().add_layer(image) + asserter(lambda: button.isEnabled()) + + # Add some labels to the viewer + sample_data = numpy.ones((50, 100), dtype=numpy.uint8) + labels = Labels(data=sample_data, name="test_labels") + current_viewer().add_layer(labels) + + def handle_transfer(widget: QDialog) -> bool: + if not isinstance(widget, DetailExportDialog): + print("Not an AdvancedExportDialog") + return False + # Note test_labels is the active layer - it will be the current text + if not widget.img_container.combo.currentText() == "test_labels": + print(widget.img_container.combo.currentText()) + print("Unexpected starting image text") + return False + if not widget.roi_container.combo.currentText() == "--------": + print("Unexpected starting roi text") + return False + if labels not in widget.roi_container.choices: + print("Labels layer not available") + return False + + widget.img_container.combo.setCurrentText("test_to") + widget.roi_container.combo.setCurrentText("test_labels") + + ok_button = widget.buttons.button(QDialogButtonBox.Ok) + ok_button.clicked.emit() + return True + + popup_handler(button.clicked.emit, handle_transfer) + + # Assert that the data is now in Fiji + def check_active_display(): + if not ij.display().getActiveDisplay(): + return False + dataset = ij.display().getActiveDisplay().getActiveView().getData() + if not dataset.getName() == "test_to": + return False + if dataset.getProperties().get("rois") is None: + return False + + return True + + asserter(check_active_display) + + def test_settings_no_change(gui_widget: NapariImageJMenu): """Ensure that no changes are made when there is no change from the defaults""" button: SettingsButton = gui_widget.settings_button