From 8f0bcdfe88252758a60ab0bf014eb4d3ea16a961 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Tue, 25 Jul 2023 13:44:39 +0200 Subject: [PATCH 01/17] Utils improvements - Set new results path now remains across utils - Utils generate one layer and update it on subsequent runs - Layer choice tries to be conserved across utils --- napari_cellseg3d/code_plugins/plugin_base.py | 38 +++++++- .../code_plugins/plugin_convert.py | 90 ++++++++++--------- napari_cellseg3d/code_plugins/plugin_crf.py | 17 ++-- napari_cellseg3d/code_plugins/plugin_crop.py | 27 +++--- .../code_plugins/plugin_utilities.py | 88 ++++++++++++++++-- napari_cellseg3d/interface.py | 48 +++++----- napari_cellseg3d/utils.py | 36 +++++--- 7 files changed, 245 insertions(+), 99 deletions(-) diff --git a/napari_cellseg3d/code_plugins/plugin_base.py b/napari_cellseg3d/code_plugins/plugin_base.py index f369320b..63c7bfa9 100644 --- a/napari_cellseg3d/code_plugins/plugin_base.py +++ b/napari_cellseg3d/code_plugins/plugin_base.py @@ -446,9 +446,10 @@ def _update_default_paths(self, path=None): self.extract_dataset_paths(self.labels_filepaths), self.results_path, ] - return + return utils.parse_default_path(self._default_path) if Path(path).is_dir(): self._default_path.append(path) + return utils.parse_default_path(self._default_path) @staticmethod def extract_dataset_paths(paths): @@ -458,3 +459,38 @@ def extract_dataset_paths(paths): if paths[0] is None: return None return str(Path(paths[0]).parent) + + +class BasePluginUtils(BasePluginFolder): + """Small subclass used to have centralized widgets in utilities""" + + needs_extra_layer = True + save_path = None + utils_default_paths = [Path.home() / "cellseg3d"] + + def __init__( + self, + viewer: napari.viewer.Viewer, + parent=None, + loads_images=True, + loads_labels=True, + ): + super().__init__( + viewer=viewer, + loads_images=loads_images, + loads_labels=loads_labels, + parent=parent, + ) + if parent is not None: + self.setParent(parent) + self.parent = parent + + self.layer = None + """Should contain the layer associated with the results of the utility""" + + def _update_default_paths(self, path=None): + """Override to also update utilities' pool of default paths""" + default_path = super()._update_default_paths(path) + logger.debug(f"Trying to update default with {default_path}") + if default_path is not None: + self.utils_default_paths.append(default_path) diff --git a/napari_cellseg3d/code_plugins/plugin_convert.py b/napari_cellseg3d/code_plugins/plugin_convert.py index aa70bc73..eacfba80 100644 --- a/napari_cellseg3d/code_plugins/plugin_convert.py +++ b/napari_cellseg3d/code_plugins/plugin_convert.py @@ -13,7 +13,7 @@ threshold, to_semantic, ) -from napari_cellseg3d.code_plugins.plugin_base import BasePluginFolder +from napari_cellseg3d.code_plugins.plugin_base import BasePluginUtils MAX_W = ui.UTILS_MAX_WIDTH MAX_H = ui.UTILS_MAX_HEIGHT @@ -21,9 +21,11 @@ logger = utils.LOGGER -class AnisoUtils(BasePluginFolder): +class AnisoUtils(BasePluginUtils): """Class to correct anisotropy in images""" + save_path = Path.home() / Path("cellseg3d/anisotropy") + def __init__(self, viewer: "napari.Viewer.viewer", parent=None): """ Creates a AnisoUtils widget @@ -46,7 +48,7 @@ def __init__(self, viewer: "napari.Viewer.viewer", parent=None): self.aniso_widgets = ui.AnisotropyWidgets(self, always_visible=True) self.start_btn = ui.Button("Start", self._start) - self.results_path = str(Path.home() / Path("cellseg3d/anisotropy")) + self.results_path = str(self.save_path) self.results_filewidget.text_field.setText(str(self.results_path)) self.results_filewidget.check_ready() @@ -91,11 +93,12 @@ def _start(self): f"isotropic_{layer.name}_{utils.get_date_time()}.tif", isotropic_image, ) - utils.show_result( + self.layer = utils.show_result( self._viewer, layer, isotropic_image, f"isotropic_{layer.name}", + existing_layer=self.layer, ) elif ( @@ -113,7 +116,8 @@ def _start(self): ) -class RemoveSmallUtils(BasePluginFolder): +class RemoveSmallUtils(BasePluginUtils): + save_path = Path.home() / Path("cellseg3d/small_removed") """ Widget to remove small objects """ @@ -128,14 +132,14 @@ def __init__(self, viewer: "napari.viewer.Viewer", parent=None): """ super().__init__( viewer, - parent, - loads_labels=False, + parent=parent, + loads_images=False, ) self.data_panel = self._build_io_panel() - self.image_layer_loader.layer_list.label.setText("Layer :") - self.image_layer_loader.set_layer_type(napari.layers.Layer) + self.label_layer_loader.layer_list.label.setText("Layer :") + # self.label_layer_loader.set_layer_type(napari.layers.Layer) self.start_btn = ui.Button("Start", self._start) self.size_for_removal_counter = ui.IntIncrementCounter( @@ -145,8 +149,9 @@ def __init__(self, viewer: "napari.viewer.Viewer", parent=None): text_label="Remove all smaller than (pxs):", ) - self.results_path = Path.home() / Path("cellseg3d/small_removed") - self.results_filewidget.text_field.setText(str(self.results_path)) + self.results_path = str(self.save_path) + self.results_filewidget.text_field.setText(self.results_path) + self.results_filewidget.check_ready() self.container = self._build() @@ -180,8 +185,8 @@ def _start(self): remove_size = self.size_for_removal_counter.value() if self.layer_choice.isChecked(): - if self.image_layer_loader.layer_data() is not None: - layer = self.image_layer_loader.layer() + if self.label_layer_loader.layer_data() is not None: + layer = self.label_layer_loader.layer() data = np.array(layer.data) removed = self.function(data, remove_size) @@ -191,8 +196,12 @@ def _start(self): f"cleared_{layer.name}_{utils.get_date_time()}.tif", removed, ) - utils.show_result( - self._viewer, layer, removed, f"cleared_{layer.name}" + self.layer = utils.show_result( + self._viewer, + layer, + removed, + f"cleared_{layer.name}", + existing_layer=self.layer, ) elif ( self.folder_choice.isChecked() and len(self.images_filepaths) != 0 @@ -210,7 +219,8 @@ def _start(self): return -class ToSemanticUtils(BasePluginFolder): +class ToSemanticUtils(BasePluginUtils): + save_path = Path.home() / Path("cellseg3d/semantic_labels") """ Widget to create semantic labels from instance labels """ @@ -225,20 +235,15 @@ def __init__(self, viewer: "napari.viewer.Viewer", parent=None): """ super().__init__( viewer, - parent, + parent=parent, loads_images=False, ) self.data_panel = self._build_io_panel() - self.start_btn = ui.Button("Start", self._start) - - self.results_path = str( - Path.home() / Path("cellseg3d/instance_labels") - ) + self.results_path = str(self.save_path) self.results_filewidget.text_field.setText(self.results_path) self.results_filewidget.check_ready() - self._build() def _build(self): @@ -280,7 +285,7 @@ def _start(self): f"semantic_{layer.name}_{utils.get_date_time()}.tif", semantic, ) - utils.show_result( + self.layer = utils.show_result( self._viewer, layer, semantic, f"semantic_{layer.name}" ) elif ( @@ -300,7 +305,8 @@ def _start(self): logger.warning("Please specify a layer or a folder") -class ToInstanceUtils(BasePluginFolder): +class ToInstanceUtils(BasePluginUtils): + save_path = Path.home() / Path("cellseg3d/instance_labels") """ Widget to convert semantic labels to instance labels """ @@ -315,18 +321,16 @@ def __init__(self, viewer: "napari.viewer.Viewer", parent=None): """ super().__init__( viewer, - parent, + parent=parent, loads_images=False, ) self.data_panel = self._build_io_panel() - self.label_layer_loader.set_layer_type(napari.layers.Layer) - + self.image_layer_loader.set_layer_type(napari.layers.Layer) self.instance_widgets = InstanceWidgets(parent=self) - self.start_btn = ui.Button("Start", self._start) - self.results_path = Path.home() / Path("cellseg3d/instance") + self.results_path = str(self.save_path) self.results_filewidget.text_field.setText(str(self.results_path)) self.results_filewidget.check_ready() @@ -368,8 +372,12 @@ def _start(self): f"instance_{layer.name}_{utils.get_date_time()}.tif", instance, ) - self._viewer.add_labels( - instance, name=f"instance_{layer.name}" + self.layer = utils.show_result( + self._viewer, + layer, + instance, + f"instance_{layer.name}", + existing_layer=self.layer, ) elif ( @@ -387,7 +395,8 @@ def _start(self): ) -class ThresholdUtils(BasePluginFolder): +class ThresholdUtils(BasePluginUtils): + save_path = Path.home() / Path("cellseg3d/threshold") """ Creates a ThresholdUtils widget Args: @@ -398,16 +407,14 @@ class ThresholdUtils(BasePluginFolder): def __init__(self, viewer: "napari.viewer.Viewer", parent=None): super().__init__( viewer, - parent, + parent=parent, loads_labels=False, ) self.data_panel = self._build_io_panel() self._set_io_visibility() - self.image_layer_loader.layer_list.label.setText("Layer :") self.image_layer_loader.set_layer_type(napari.layers.Layer) - self.start_btn = ui.Button("Start", self._start) self.binarize_counter = ui.DoubleIncrementCounter( lower=0.0, @@ -417,12 +424,11 @@ def __init__(self, viewer: "napari.viewer.Viewer", parent=None): text_label="Remove all smaller than (value):", ) - self.results_path = str(Path.home() / Path("cellseg3d/threshold")) + self.results_path = str(self.save_path) self.results_filewidget.text_field.setText(self.results_path) self.results_filewidget.check_ready() self.container = self._build() - self.function = threshold def _build(self): @@ -464,8 +470,12 @@ def _start(self): f"threshold_{layer.name}_{utils.get_date_time()}.tif", removed, ) - utils.show_result( - self._viewer, layer, removed, f"threshold{layer.name}" + self.layer = utils.show_result( + self._viewer, + layer, + removed, + f"threshold{layer.name}", + existing_layer=self.layer, ) elif ( self.folder_choice.isChecked() and len(self.images_filepaths) != 0 diff --git a/napari_cellseg3d/code_plugins/plugin_crf.py b/napari_cellseg3d/code_plugins/plugin_crf.py index 76194e87..fb6bddfc 100644 --- a/napari_cellseg3d/code_plugins/plugin_crf.py +++ b/napari_cellseg3d/code_plugins/plugin_crf.py @@ -13,7 +13,7 @@ CRFWorker, crf_with_config, ) -from napari_cellseg3d.code_plugins.plugin_base import BasePluginSingleImage +from napari_cellseg3d.code_plugins.plugin_base import BasePluginUtils from napari_cellseg3d.utils import LOGGER as logger @@ -107,7 +107,11 @@ def make_config(self): ) -class CRFWidget(BasePluginSingleImage): +class CRFWidget(BasePluginUtils): + """Widget to run CRF post-processing""" + + save_path = Path.home() / Path("cellseg3d/crf") + def __init__(self, viewer, parent=None): """ Create a widget for CRF post-processing. @@ -115,7 +119,7 @@ def __init__(self, viewer, parent=None): viewer: napari viewer to display the widget parent: parent widget. Defaults to None. """ - super().__init__(viewer, parent) + super().__init__(viewer, parent=parent) self._viewer = viewer self.start_button = ui.Button("Start", self._start, parent=self) @@ -129,6 +133,8 @@ def __init__(self, viewer, parent=None): napari.layers.Image ) # to load all crf-compatible inputs, not int only self.image_layer_loader.setVisible(True) + self.label_layer_loader.layer_list.label.setText("Model output :") + if CRF_INSTALLED: self.start_button.setVisible(True) else: @@ -138,8 +144,9 @@ def __init__(self, viewer, parent=None): self.result_name = None self.crf_results = [] - self.results_path = Path.home() / Path("cellseg3d/crf") - self.results_filewidget.text_field.setText(str(self.results_path)) + self.results_path = str(self.save_path) + self.results_filewidget.text_field.setText(self.results_path) + self.results_filewidget.check_ready() self._container = ui.ContainerWidget(parent=self, l=11, t=11, r=11) diff --git a/napari_cellseg3d/code_plugins/plugin_crop.py b/napari_cellseg3d/code_plugins/plugin_crop.py index c6e822d4..a083e4af 100644 --- a/napari_cellseg3d/code_plugins/plugin_crop.py +++ b/napari_cellseg3d/code_plugins/plugin_crop.py @@ -17,9 +17,14 @@ logger = utils.LOGGER -class Cropping(BasePluginSingleImage): +class Cropping( + BasePluginSingleImage +): # not a BasePLuginUtils since it's not runnning on folders """A utility plugin for cropping 3D volumes.""" + save_path = Path.home() / Path("cellseg3d/cropped") + utils_default_paths = [] + def __init__(self, viewer: "napari.viewer.Viewer", parent=None): """Creates a Cropping plugin with several buttons : @@ -37,13 +42,21 @@ def __init__(self, viewer: "napari.viewer.Viewer", parent=None): """ super().__init__(viewer) + + if parent is not None: + self.setParent(parent) + self.docked_widgets = [] - self.results_path = Path.home() / Path("cellseg3d/cropped") + self.results_path = str(self.save_path) self.btn_start = ui.Button("Start", self._start) self.image_layer_loader.set_layer_type(napari.layers.Layer) - self.image_layer_loader.layer_list.label.setText("Image 1") + self.image_layer_loader.layer_list.label.setText("Image") + # self.image_layer_loader.layer_list.currentIndexChanged.connect( + # self.auto_set_dims + # ) + self.image_layer_loader.layer_list.currentIndexChanged.connect( self.auto_set_dims ) @@ -73,14 +86,8 @@ def __init__(self, viewer: "napari.viewer.Viewer", parent=None): self.layer_choice.clicked.connect( self._toggle_second_image_io_visibility ) + self.results_filewidget.text_field.setText(str(self.save_path)) - # self.results_filewidget = ui.FilePathWidget( - # "Results path", - # self._load_results_path, - # default=str(self.results_path), - # ) - # self.results_filewidget.tooltips = str(self.results_path) - self.results_filewidget.text_field.setText(str(self.results_path)) self.results_filewidget.check_ready() self.crop_size_widgets = ui.IntIncrementCounter.make_n( diff --git a/napari_cellseg3d/code_plugins/plugin_utilities.py b/napari_cellseg3d/code_plugins/plugin_utilities.py index 6e1a606a..f79d3ef3 100644 --- a/napari_cellseg3d/code_plugins/plugin_utilities.py +++ b/napari_cellseg3d/code_plugins/plugin_utilities.py @@ -9,6 +9,8 @@ # local import napari_cellseg3d.interface as ui +from napari_cellseg3d import utils +from napari_cellseg3d.code_plugins.plugin_base import BasePluginUtils from napari_cellseg3d.code_plugins.plugin_convert import ( AnisoUtils, RemoveSmallUtils, @@ -18,6 +20,7 @@ ) from napari_cellseg3d.code_plugins.plugin_crf import CRFWidget from napari_cellseg3d.code_plugins.plugin_crop import Cropping +from napari_cellseg3d.utils import LOGGER as logger UTILITIES_WIDGETS = { "Crop": Cropping, @@ -34,17 +37,10 @@ class Utilities(QWidget, metaclass=ui.QWidgetSingleton): def __init__(self, viewer: "napari.viewer.Viewer"): super().__init__() self._viewer = viewer + self.current_widget = None attr_names = ["crop", "aniso", "small", "inst", "sem", "thresh", "crf"] self._create_utils_widgets(attr_names) - - # self.crop = Cropping(self._viewer) - # self.sem = ToSemanticUtils(self._viewer) - # self.aniso = AnisoUtils(self._viewer) - # self.inst = ToInstanceUtils(self._viewer) - # self.thresh = ThresholdUtils(self._viewer) - # self.small = RemoveSmallUtils(self._viewer) - self.utils_choice = ui.DropdownMenu( UTILITIES_WIDGETS.keys(), text_label="Utilities" ) @@ -52,10 +48,21 @@ def __init__(self, viewer: "napari.viewer.Viewer"): self._build() self.utils_choice.currentIndexChanged.connect(self._update_visibility) + self.utils_choice.currentIndexChanged.connect( + self._update_current_widget + ) # self._dock_util() self._update_visibility() qInstallMessageHandler(ui.handle_adjust_errors_wrapper(self)) + def _update_current_widget(self): + self.current_widget = self.utils_widgets[ + self.utils_choice.currentIndex() + ] + + def _update_results_path(self, widget): + self.results_filewidget.text_field.setText(str(widget.save_path)) + def _build(self): layout = QVBoxLayout() ui.add_widgets(layout, self.utils_widgets) @@ -72,11 +79,14 @@ def _build(self): # layout.setSizeConstraint(QLayout.SetFixedSize) self.setLayout(layout) # self.setMinimumHeight(2000) - self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) + self.setSizePolicy( + QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding + ) self._update_visibility() def _create_utils_widgets(self, names): for key, name in zip(UTILITIES_WIDGETS, names): + logger.debug(f"Creating {name} widget") setattr(self, name, UTILITIES_WIDGETS[key](self._viewer)) self.utils_widgets = [] @@ -84,22 +94,82 @@ def _create_utils_widgets(self, names): wid = getattr(self, n) self.utils_widgets.append(wid) + self.current_widget = self.utils_widgets[0] if len(self.utils_widgets) != len(UTILITIES_WIDGETS.keys()): raise RuntimeError( "One or several utility widgets are missing/erroneous" ) + def _update_layers(self, current_loader, new_loader): + current_layer = current_loader.layer() + if not isinstance(current_layer, new_loader.layer_type): + return + if ( + current_layer is not None + and current_layer.name in new_loader.layer_list.get_items() + ): + index = new_loader.layer_list.get_items().index(current_layer.name) + logger.debug( + f"Index of layer {current_layer.name} in new loader : {index}" + ) + new_loader.layer_list.setCurrentIndex(index) + + def _update_fields(self, widget: BasePluginUtils): + try: + # checks all combinations to find if a layer could be recovered across widgets + # correctness is ensured by the types of the layer loaders + self._update_layers( + self.current_widget.image_layer_loader, + widget.image_layer_loader, + ) + self._update_layers( + self.current_widget.image_layer_loader, + widget.label_layer_loader, + ) + self._update_layers( + self.current_widget.label_layer_loader, + widget.image_layer_loader, + ) + self._update_layers( + self.current_widget.label_layer_loader, + widget.label_layer_loader, + ) + except KeyError: + pass + + logger.debug( + f"Current widget save path : {self.current_widget.save_path}" + ) + logger.debug( + f"Current widget text field : {self.current_widget.results_filewidget.text_field.text()}" + ) + logger.debug( + f"Matching : {self.current_widget.results_filewidget.text_field.text() == self.current_widget.results_path}" + ) + if len(self.current_widget.utils_default_paths) > 1: + try: + path = self.current_widget.utils_default_paths + default = utils.parse_default_path(path) + widget.results_filewidget.text_field.setText(default) + widget.utils_default_paths.append(default) + except AttributeError: + pass + def _update_visibility(self): widget_class = UTILITIES_WIDGETS[self.utils_choice.currentText()] # print("vis. updated") # print(self.utils_widgets) self._hide_all() + widget = None for _i, w in enumerate(self.utils_widgets): if isinstance(w, widget_class): w.setVisible(True) w.adjustSize() + widget = w # else: # self.utils_widgets[i].setVisible(False) + self._update_fields(widget) + self.current_widget = widget def _hide_all(self): for w in self.utils_widgets: diff --git a/napari_cellseg3d/interface.py b/napari_cellseg3d/interface.py index 22f6a4a3..b6e02012 100644 --- a/napari_cellseg3d/interface.py +++ b/napari_cellseg3d/interface.py @@ -4,13 +4,10 @@ from typing import List, Optional import napari - -# Qt -# from qtpy.QtCore import QtWarningMsg from qtpy import QtCore -# from qtpy.QtCore import QtWarningMsg -from qtpy.QtCore import QObject, Qt, QUrl +# Qt +from qtpy.QtCore import QObject, Qt, QtWarningMsg, QUrl from qtpy.QtGui import QCursor, QDesktopServices, QTextCursor from qtpy.QtWidgets import ( QAbstractSpinBox, @@ -110,25 +107,25 @@ def __call__(cls, *args, **kwargs): def handle_adjust_errors(widget, warning_type, context, msg: str): """Qt message handler that attempts to react to errors when setting the window size and resizes the main window""" - pass - # head = msg.split(": ")[0] - # if warning_type == QtWarningMsg and head == "QWindowsWindow::setGeometry": - # logger.warning( - # f"Qt resize error : {msg}\nhas been handled by attempting to resize the window" - # ) - # try: - # if widget.parent() is not None: - # state = int(widget.parent().parent().windowState()) - # if state == 0: # normal state - # widget.parent().parent().adjustSize() - # logger.debug("Non-max. size adjust attempt") - # logger.debug(f"{widget.parent().parent()}") - # elif state == 2: # maximized state - # widget.parent().parent().showNormal() - # widget.parent().parent().showMaximized() - # logger.debug("Maximized size adjust attempt") - # except RuntimeError: - # pass + # pass + head = msg.split(": ")[0] + if warning_type == QtWarningMsg and head == "QWindowsWindow::setGeometry": + logger.warning( + f"Qt resize error : {msg}\nhas been handled by attempting to resize the window" + ) + try: + if widget.parent() is not None: + state = int(widget.parent().parent().windowState()) + if state == 0: # normal state + widget.parent().parent().adjustSize() + logger.debug("Non-max. size adjust attempt") + logger.debug(f"{widget.parent().parent()}") + elif state == 2: # maximized state + widget.parent().parent().showNormal() + widget.parent().parent().showMaximized() + logger.debug("Maximized size adjust attempt") + except RuntimeError: + pass def handle_adjust_errors_wrapper(widget): @@ -430,6 +427,9 @@ def __init__( if fixed: self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + def get_items(self): + return [self.itemText(i) for i in range(self.count())] + class CheckBox(QCheckBox): """Shortcut class for creating QCheckBox with a title and a function""" diff --git a/napari_cellseg3d/utils.py b/napari_cellseg3d/utils.py index c6a8bbac..d1eb541d 100644 --- a/napari_cellseg3d/utils.py +++ b/napari_cellseg3d/utils.py @@ -70,7 +70,9 @@ def save_layer(results_path, image_name, image): imwrite(path, image) -def show_result(viewer, layer, image, name): +def show_result( + viewer, layer, image, name, existing_layer: napari.layers.Layer = None +): """ Adds layers to a viewer to show result to user @@ -83,16 +85,30 @@ def show_result(viewer, layer, image, name): Returns: """ - if isinstance(layer, napari.layers.Image): - LOGGER.debug("Added resulting image layer") - viewer.add_image(image, name=name) - elif isinstance(layer, napari.layers.Labels): - LOGGER.debug("Added resulting label layer") - viewer.add_labels(image, name=name) + if existing_layer is None: + if isinstance(layer, napari.layers.Image): + LOGGER.info("Added resulting image layer") + results_layer = viewer.add_image(image, name=name) + elif isinstance(layer, napari.layers.Labels): + LOGGER.info("Added resulting label layer") + results_layer = viewer.add_labels(image, name=name) + else: + LOGGER.warning( + f"Results not shown, unsupported layer type {type(layer)}" + ) else: - LOGGER.warning( - f"Results not shown, unsupported layer type {type(layer)}" - ) + try: + viewer.layers[existing_layer.name].data = image + results_layer = viewer.layers[existing_layer.name] + except KeyError: + LOGGER.warning( + f"Results not shown, layer {existing_layer.name} not found" + "Showing new layer instead" + ) + results_layer = show_result( + viewer, layer, image, name, existing_layer=None + ) + return results_layer class Singleton(type): From 002e43beb33a0a3f9e6234412ba8416b6d811601 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Tue, 25 Jul 2023 14:00:22 +0200 Subject: [PATCH 02/17] Cleanup + CRF fixes --- napari_cellseg3d/code_plugins/plugin_base.py | 5 ++--- napari_cellseg3d/code_plugins/plugin_convert.py | 8 ++++++-- napari_cellseg3d/code_plugins/plugin_crf.py | 6 +++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/napari_cellseg3d/code_plugins/plugin_base.py b/napari_cellseg3d/code_plugins/plugin_base.py index 63c7bfa9..3b3a9926 100644 --- a/napari_cellseg3d/code_plugins/plugin_base.py +++ b/napari_cellseg3d/code_plugins/plugin_base.py @@ -462,9 +462,8 @@ def extract_dataset_paths(paths): class BasePluginUtils(BasePluginFolder): - """Small subclass used to have centralized widgets in utilities""" + """Small subclass used to have centralized widgets layer and result path selection in utilities""" - needs_extra_layer = True save_path = None utils_default_paths = [Path.home() / "cellseg3d"] @@ -486,7 +485,7 @@ def __init__( self.parent = parent self.layer = None - """Should contain the layer associated with the results of the utility""" + """Should contain the layer associated with the results of the utility widget""" def _update_default_paths(self, path=None): """Override to also update utilities' pool of default paths""" diff --git a/napari_cellseg3d/code_plugins/plugin_convert.py b/napari_cellseg3d/code_plugins/plugin_convert.py index eacfba80..09021f39 100644 --- a/napari_cellseg3d/code_plugins/plugin_convert.py +++ b/napari_cellseg3d/code_plugins/plugin_convert.py @@ -286,7 +286,11 @@ def _start(self): semantic, ) self.layer = utils.show_result( - self._viewer, layer, semantic, f"semantic_{layer.name}" + self._viewer, + layer, + semantic, + f"semantic_{layer.name}", + existing_layer=self.layer, ) elif ( self.folder_choice.isChecked() and len(self.labels_filepaths) != 0 @@ -474,7 +478,7 @@ def _start(self): self._viewer, layer, removed, - f"threshold{layer.name}", + f"threshold_{layer.name}", existing_layer=self.layer, ) elif ( diff --git a/napari_cellseg3d/code_plugins/plugin_crf.py b/napari_cellseg3d/code_plugins/plugin_crf.py index fb6bddfc..eb7368af 100644 --- a/napari_cellseg3d/code_plugins/plugin_crf.py +++ b/napari_cellseg3d/code_plugins/plugin_crf.py @@ -156,6 +156,7 @@ def __init__(self, viewer, parent=None): self.worker = None self.log = None + self.layer = None def _build(self): self.setMinimumWidth(100) @@ -275,9 +276,12 @@ def _on_yield(self, result): str(self.result_name + "_crf.tif"), result, ) - self._viewer.add_image( + self.layer = utils.show_result( + self._viewer, + self.result_layer, result, name="crf_" + self.result_name, + existing_layer=self.layer, ) def _on_start(self): From c31f8d99b8eb8c22123c833b9ffcd1553e30c231 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Tue, 25 Jul 2023 14:38:49 +0200 Subject: [PATCH 03/17] Fragmenting utility --- .../code_plugins/plugin_convert.py | 93 +++++++++++++++++++ .../code_plugins/plugin_utilities.py | 13 ++- napari_cellseg3d/interface.py | 12 ++- napari_cellseg3d/utils.py | 6 +- 4 files changed, 116 insertions(+), 8 deletions(-) diff --git a/napari_cellseg3d/code_plugins/plugin_convert.py b/napari_cellseg3d/code_plugins/plugin_convert.py index 09021f39..3d701c67 100644 --- a/napari_cellseg3d/code_plugins/plugin_convert.py +++ b/napari_cellseg3d/code_plugins/plugin_convert.py @@ -14,6 +14,7 @@ to_semantic, ) from napari_cellseg3d.code_plugins.plugin_base import BasePluginUtils +from napari_cellseg3d.dev_scripts.crop_data import crop_3d_image MAX_W = ui.UTILS_MAX_WIDTH MAX_H = ui.UTILS_MAX_HEIGHT @@ -21,6 +22,98 @@ logger = utils.LOGGER +class FragmentUtils(BasePluginUtils): + """Class to crop large 3D volumes into smaller fragments""" + + save_path = Path.home() / Path("cellseg3d/fragmented") + + def __init__(self, viewer: "napari.Viewer.viewer", parent=None): + """Creates a FragmentUtils widget + + Args: + viewer: viewer in which to process data + parent: parent widget + """ + super().__init__( + viewer, + parent, + loads_labels=False, + ) + self.data_panel = self._build_io_panel() + self.start_btn = ui.Button("Start", self._start) + self.size_selection = ui.AnisotropyWidgets( + parent=self, + default_x=64, + default_y=64, + default_z=64, + always_visible=True, + use_integer_counter=True, + ) + [ + lbl.setText(f"Size in {ax} (pixels):") + for lbl, ax in zip(self.size_selection.box_widgets_lbl, "xyz") + ] + [ + w.setToolTip(f"Size of crop for {dim} axis") + for w, dim in zip(self.size_selection.box_widgets, "xyz") + ] + + self.image_layer_loader.layer_list.label.setText("Layer :") + self.image_layer_loader.set_layer_type(napari.layers.Layer) + + self.results_path = str(self.save_path) + self.results_filewidget.text_field.setText(str(self.results_path)) + self.results_filewidget.check_ready() + + self._build() + + def _build(self): + container = ui.ContainerWidget() + + ui.add_widgets( + container.layout, + [ + self.data_panel, + self.size_selection, + self.start_btn, + ], + ) + + ui.ScrollArea.make_scrollable( + container.layout, + self, + max_wh=[MAX_W, MAX_H], + ) + + self._set_io_visibility() + self.setSizePolicy( + QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding + ) + + def _fragment(self, crops, name): + dir_name = f"/{name}_fragmented_{utils.get_time_filepath()}" + Path(self.results_path + dir_name).mkdir(parents=True, exist_ok=False) + for i, crop in enumerate(crops): + utils.save_layer( + self.results_path + dir_name, + f"{name}_fragmented_{i}.tif", + crop, + ) + + def _start(self): + sizes = self.size_selection.resolution_zyx() + if self.layer_choice.isChecked(): + layer = self.image_layer_loader.layer() + crops = crop_3d_image(layer.data, sizes) + self._fragment(crops, layer.name) + elif self.folder_choice.isChecked(): + paths = self.images_filepaths + for path in paths: + data = imread(path) + crops = crop_3d_image(data, sizes) + self._fragment(crops, Path(path).stem) + + class AnisoUtils(BasePluginUtils): """Class to correct anisotropy in images""" diff --git a/napari_cellseg3d/code_plugins/plugin_utilities.py b/napari_cellseg3d/code_plugins/plugin_utilities.py index f79d3ef3..abe56699 100644 --- a/napari_cellseg3d/code_plugins/plugin_utilities.py +++ b/napari_cellseg3d/code_plugins/plugin_utilities.py @@ -13,6 +13,7 @@ from napari_cellseg3d.code_plugins.plugin_base import BasePluginUtils from napari_cellseg3d.code_plugins.plugin_convert import ( AnisoUtils, + FragmentUtils, RemoveSmallUtils, ThresholdUtils, ToInstanceUtils, @@ -24,6 +25,7 @@ UTILITIES_WIDGETS = { "Crop": Cropping, + "Fragment 3D volume": FragmentUtils, "Correct anisotropy": AnisoUtils, "Remove small objects": RemoveSmallUtils, "Convert to instance labels": ToInstanceUtils, @@ -39,7 +41,16 @@ def __init__(self, viewer: "napari.viewer.Viewer"): self._viewer = viewer self.current_widget = None - attr_names = ["crop", "aniso", "small", "inst", "sem", "thresh", "crf"] + attr_names = [ + "crop", + "frag", + "aniso", + "small", + "inst", + "sem", + "thresh", + "crf", + ] self._create_utils_widgets(attr_names) self.utils_choice = ui.DropdownMenu( UTILITIES_WIDGETS.keys(), text_label="Utilities" diff --git a/napari_cellseg3d/interface.py b/napari_cellseg3d/interface.py index b6e02012..e6cc3f27 100644 --- a/napari_cellseg3d/interface.py +++ b/napari_cellseg3d/interface.py @@ -640,6 +640,7 @@ def __init__( default_y: Optional[float] = 1.0, default_z: Optional[float] = 1.0, always_visible: Optional[bool] = False, + use_integer_counter: Optional[bool] = False, ): """Creates an instance of AnisotropyWidgets Args: @@ -659,9 +660,14 @@ def __init__( "Anisotropic data", self._toggle_display_aniso, parent ) - self.box_widgets = DoubleIncrementCounter.make_n( - n=3, lower=1.0, upper=1000.0, default=1.0, step=0.5 - ) + if use_integer_counter: + self.box_widgets = IntIncrementCounter.make_n( + n=3, lower=1, upper=9999, default=64, step=1 + ) + else: + self.box_widgets = DoubleIncrementCounter.make_n( + n=3, lower=1.0, upper=1000.0, default=1.0, step=0.5 + ) self.box_widgets[0].setValue(default_x) self.box_widgets[1].setValue(default_y) self.box_widgets[2].setValue(default_z) diff --git a/napari_cellseg3d/utils.py b/napari_cellseg3d/utils.py index d1eb541d..bf54228c 100644 --- a/napari_cellseg3d/utils.py +++ b/napari_cellseg3d/utils.py @@ -61,13 +61,11 @@ def save_layer(results_path, image_name, image): results_path: path to folder containing result image_name: image name for saving image: data array containing image - - Returns: - """ path = str(results_path / Path(image_name)) # TODO flexible filetype LOGGER.info(f"Saved as : {path}") - imwrite(path, image) + image = image.astype(np.float32) + imwrite(path, image, dtype="float32") def show_result( From 4c9079d04499e7e1028190982f6314a95a4a20e8 Mon Sep 17 00:00:00 2001 From: Cyril Achard <94955160+C-Achard@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:06:55 +0200 Subject: [PATCH 04/17] Temp fix for CRF (#46) --- .github/workflows/test_and_deploy.yml | 2 +- napari_cellseg3d/code_models/crf.py | 2 +- pyproject.toml | 4 ++-- tox.ini | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 406bf4f5..fafb1719 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -51,7 +51,7 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install setuptools tox tox-gh-actions -# pip install git+https://github.com/lucasb-eyer/pydensecrf.git@master#egg=pydensecrf +# pip install git+https://github.com/kodalli/pydensecrf.git@master#egg=pydensecrf # this runs the platform-specific tests declared in tox.ini - name: Test with tox diff --git a/napari_cellseg3d/code_models/crf.py b/napari_cellseg3d/code_models/crf.py index b362246a..79951fc5 100644 --- a/napari_cellseg3d/code_models/crf.py +++ b/napari_cellseg3d/code_models/crf.py @@ -7,7 +7,7 @@ Philipp Krähenbühl and Vladlen Koltun NIPS 2011 -Implemented using the pydense libary available at https://github.com/lucasb-eyer/pydensecrf. +Implemented using the pydense libary available at https://github.com/kodalli/pydensecrf. """ from warnings import warn diff --git a/pyproject.toml b/pyproject.toml index 40450f9b..5d5be93b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ line_length = 79 [project.optional-dependencies] crf = [ - "pydensecrf@git+https://github.com/lucasb-eyer/pydensecrf.git#egg=master", + "pydensecrf@git+https://github.com/kodalli/pydensecrf.git#egg=master", ] dev = [ "isort", @@ -142,7 +142,7 @@ test = [ "coverage", "tox", "twine", - "pydensecrf@git+https://github.com/lucasb-eyer/pydensecrf.git#egg=master", + "pydensecrf@git+https://github.com/kodalli/pydensecrf.git#egg=master", ] onnx-cpu = [ "onnx", diff --git a/tox.ini b/tox.ini index b8c76091..195b0dff 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ deps = magicgui pytest-qt qtpy - git+https://github.com/lucasb-eyer/pydensecrf.git@master#egg=pydensecrf + git+https://github.com/kodalli/pydensecrf.git@master#egg=pydensecrf onnx onnxruntime ; pyopencl[pocl] From d37d56fb102daa90bf9409064691c8c3f9c1a373 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 28 Jul 2023 13:18:22 +0200 Subject: [PATCH 05/17] Update plugin_convert.py --- napari_cellseg3d/code_plugins/plugin_convert.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/napari_cellseg3d/code_plugins/plugin_convert.py b/napari_cellseg3d/code_plugins/plugin_convert.py index 3d701c67..e990ce64 100644 --- a/napari_cellseg3d/code_plugins/plugin_convert.py +++ b/napari_cellseg3d/code_plugins/plugin_convert.py @@ -186,6 +186,10 @@ def _start(self): f"isotropic_{layer.name}_{utils.get_date_time()}.tif", isotropic_image, ) + if isinstance(layer, napari.layers.Image): + isotropic_image = isotropic_image.astype(layer.data.dtype) + else: + isotropic_image = isotropic_image.astype(np.uint16) self.layer = utils.show_result( self._viewer, layer, From e0201d1f0a058287636d7ab184fa1b6becc6007f Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 28 Jul 2023 15:54:15 +0200 Subject: [PATCH 06/17] Update test_and_deploy.yml --- .github/workflows/test_and_deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index fafb1719..488d3190 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -7,6 +7,7 @@ on: push: branches: - main + - cy/utils tags: - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 pull_request: From d7f5ef1fdbcadd265b36b74307c996fcac77bc4b Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 28 Jul 2023 16:03:07 +0200 Subject: [PATCH 07/17] Update test_inference.py --- napari_cellseg3d/_tests/test_inference.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/napari_cellseg3d/_tests/test_inference.py b/napari_cellseg3d/_tests/test_inference.py index 336630f5..3497d97c 100644 --- a/napari_cellseg3d/_tests/test_inference.py +++ b/napari_cellseg3d/_tests/test_inference.py @@ -67,15 +67,22 @@ def test_inference_on_folder(): str(Path(__file__).resolve().parent / "res/test.tif") ] - def mock_work(x): - return x + class mock_work: + def __call__(self, x): + return x + + def eval(self): + return None worker = InferenceWorker(worker_config=config) - worker.aniso_transform = mock_work + worker.aniso_transform = mock_work() image = torch.Tensor(rand_gen.random((1, 1, 64, 64, 64))) res = worker.inference_on_folder( - {"image": image}, 0, model=mock_work, post_process_transforms=mock_work + {"image": image}, + 0, + model=mock_work(), + post_process_transforms=mock_work(), ) assert isinstance(res, InferenceResult) From 36293d95acbd444dd0648f5c784bf9f78bb05904 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 28 Jul 2023 14:14:10 +0200 Subject: [PATCH 08/17] Fix tests + new weights --- napari_cellseg3d/_tests/test_inference.py | 2 ++ napari_cellseg3d/code_models/models/model_SegResNet.py | 2 +- napari_cellseg3d/code_models/models/model_SwinUNetR.py | 2 +- napari_cellseg3d/code_models/models/model_TRAILMAP_MS.py | 2 +- napari_cellseg3d/code_models/models/model_VNet.py | 2 +- .../models/pretrained/pretrained_model_urls.json | 8 ++++---- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/napari_cellseg3d/_tests/test_inference.py b/napari_cellseg3d/_tests/test_inference.py index 3497d97c..2fe996bf 100644 --- a/napari_cellseg3d/_tests/test_inference.py +++ b/napari_cellseg3d/_tests/test_inference.py @@ -66,6 +66,7 @@ def test_inference_on_folder(): config.images_filepaths = [ str(Path(__file__).resolve().parent / "res/test.tif") ] + config.sliding_window_config.window_size = 64 class mock_work: def __call__(self, x): @@ -85,6 +86,7 @@ def eval(self): post_process_transforms=mock_work(), ) assert isinstance(res, InferenceResult) + assert res.result is not None def test_post_processing(): diff --git a/napari_cellseg3d/code_models/models/model_SegResNet.py b/napari_cellseg3d/code_models/models/model_SegResNet.py index 60b74d64..99f8cbfc 100644 --- a/napari_cellseg3d/code_models/models/model_SegResNet.py +++ b/napari_cellseg3d/code_models/models/model_SegResNet.py @@ -3,7 +3,7 @@ class SegResNet_(SegResNetVAE): use_default_training = True - weights_file = "SegResNet.pth" + weights_file = "SegResNet_latest.pth" def __init__( self, input_img_size, out_channels=1, dropout_prob=0.3, **kwargs diff --git a/napari_cellseg3d/code_models/models/model_SwinUNetR.py b/napari_cellseg3d/code_models/models/model_SwinUNetR.py index 0dbf0be5..bce316e8 100644 --- a/napari_cellseg3d/code_models/models/model_SwinUNetR.py +++ b/napari_cellseg3d/code_models/models/model_SwinUNetR.py @@ -7,7 +7,7 @@ class SwinUNETR_(SwinUNETR): use_default_training = True - weights_file = "Swin64_best_metric.pth" + weights_file = "SwinUNetR_latest.pth" def __init__( self, diff --git a/napari_cellseg3d/code_models/models/model_TRAILMAP_MS.py b/napari_cellseg3d/code_models/models/model_TRAILMAP_MS.py index bc8e43d5..4ee971e2 100644 --- a/napari_cellseg3d/code_models/models/model_TRAILMAP_MS.py +++ b/napari_cellseg3d/code_models/models/model_TRAILMAP_MS.py @@ -4,7 +4,7 @@ class TRAILMAP_MS_(UNet3D): use_default_training = True - weights_file = "TRAILMAP_MS_best_metric_epoch_26.pth" + weights_file = "TRAILMAP_MS_best_metric.pth" # original model from Liqun Luo lab, transferred to pytorch and trained on mesoSPIM-acquired data (mostly TPH2 as of July 2022) diff --git a/napari_cellseg3d/code_models/models/model_VNet.py b/napari_cellseg3d/code_models/models/model_VNet.py index 4e375a11..8fe18e2b 100644 --- a/napari_cellseg3d/code_models/models/model_VNet.py +++ b/napari_cellseg3d/code_models/models/model_VNet.py @@ -3,7 +3,7 @@ class VNet_(VNet): use_default_training = True - weights_file = "VNet_40e.pth" + weights_file = "VNet_latest.pth" def __init__(self, in_channels=1, out_channels=1, **kwargs): try: diff --git a/napari_cellseg3d/code_models/models/pretrained/pretrained_model_urls.json b/napari_cellseg3d/code_models/models/pretrained/pretrained_model_urls.json index b235a550..3c393d47 100644 --- a/napari_cellseg3d/code_models/models/pretrained/pretrained_model_urls.json +++ b/napari_cellseg3d/code_models/models/pretrained/pretrained_model_urls.json @@ -1,8 +1,8 @@ { - "TRAILMAP_MS": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/TRAILMAP_MS.tar.gz", - "SegResNet": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/SegResNet.tar.gz", - "VNet": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/VNet.tar.gz", - "SwinUNetR": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/Swin64.tar.gz", + "TRAILMAP_MS": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/TRAILMAP_latest.tar.gz", + "SegResNet": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/SegResNet_latest.tar.gz", + "VNet": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/VNet_latest.tar.gz", + "SwinUNetR": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/SwinUNetR_latest.tar.gz", "WNet": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/wnet.tar.gz", "WNet_ONNX": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/wnet_onnx.tar.gz", "test": "https://huggingface.co/C-Achard/cellseg3d/resolve/main/test.tar.gz" From bc2f0f6cb2de74ed9181ab10fb31543c9800c7e6 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 28 Jul 2023 16:16:22 +0200 Subject: [PATCH 09/17] Update test_models.py --- napari_cellseg3d/_tests/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napari_cellseg3d/_tests/test_models.py b/napari_cellseg3d/_tests/test_models.py index a9176fa4..89043ba9 100644 --- a/napari_cellseg3d/_tests/test_models.py +++ b/napari_cellseg3d/_tests/test_models.py @@ -115,7 +115,7 @@ def test_pretrained_weights_compatibility(): for model_name in MODEL_LIST: file_name = MODEL_LIST[model_name].weights_file WeightsDownloader().download_weights(model_name, file_name) - model = MODEL_LIST[model_name](input_img_size=[128, 128, 128]) + model = MODEL_LIST[model_name](input_img_size=[64, 64, 64]) try: model.load_state_dict( torch.load( From e95cbbe0316c415b5f19e43079712c8475ed2937 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 28 Jul 2023 15:30:01 +0200 Subject: [PATCH 10/17] Fix dir for saving in tests --- napari_cellseg3d/code_models/worker_inference.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/napari_cellseg3d/code_models/worker_inference.py b/napari_cellseg3d/code_models/worker_inference.py index b66647c3..70edda6b 100644 --- a/napari_cellseg3d/code_models/worker_inference.py +++ b/napari_cellseg3d/code_models/worker_inference.py @@ -436,6 +436,8 @@ def save_image( + f"_{time}" + filetype ) + if not Path(self.config.results_path).exists(): + Path(self.config.results_path).mkdir(parents=True, exist_ok=True) try: imwrite(file_path, image) except ValueError as e: From fc3565b23f8b30df0069f11ce19e5a2967366447 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 31 Jul 2023 09:14:37 +0200 Subject: [PATCH 11/17] Docstring update --- napari_cellseg3d/code_models/workers_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/napari_cellseg3d/code_models/workers_utils.py b/napari_cellseg3d/code_models/workers_utils.py index 5efb93a0..22f4e30d 100644 --- a/napari_cellseg3d/code_models/workers_utils.py +++ b/napari_cellseg3d/code_models/workers_utils.py @@ -151,7 +151,7 @@ def __init__(self, file_location): except ImportError as e: logger.error("ONNX is not installed but ONNX model was loaded") logger.error(e) - msg = "PLEASE INSTALL ONNX CPU OR GPU USING pip install napari-cellseg3d[onnx-cpu] OR napari-cellseg3d[onnx-gpu]" + msg = "PLEASE INSTALL ONNX CPU OR GPU USING: pip install napari-cellseg3d[onnx-cpu] OR pip install napari-cellseg3d[onnx-gpu]" logger.error(msg) raise ImportError(msg) from e @@ -177,6 +177,8 @@ def to(self, device): class QuantileNormalizationd(MapTransform): + """MONAI-style dict transform to normalize each image in a batch individually by quantile normalization.""" + def __init__(self, keys, allow_missing_keys: bool = False): super().__init__(keys, allow_missing_keys) @@ -199,6 +201,8 @@ def normalizer(self, image: torch.Tensor): class QuantileNormalization(Transform): + """MONAI-style transform to normalize each image in a batch individually by quantile normalization.""" + def __call__(self, img): return utils.quantile_normalization(img) From 5957b463c473f381b52a668127c1eff86589ab5e Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 31 Jul 2023 13:48:39 +0200 Subject: [PATCH 12/17] CRF and utils colormap adjustment --- napari_cellseg3d/code_plugins/plugin_crf.py | 1 + napari_cellseg3d/utils.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/napari_cellseg3d/code_plugins/plugin_crf.py b/napari_cellseg3d/code_plugins/plugin_crf.py index eb7368af..5225787f 100644 --- a/napari_cellseg3d/code_plugins/plugin_crf.py +++ b/napari_cellseg3d/code_plugins/plugin_crf.py @@ -282,6 +282,7 @@ def _on_yield(self, result): result, name="crf_" + self.result_name, existing_layer=self.layer, + colormap="bop orange", ) def _on_start(self): diff --git a/napari_cellseg3d/utils.py b/napari_cellseg3d/utils.py index bf54228c..45bed204 100644 --- a/napari_cellseg3d/utils.py +++ b/napari_cellseg3d/utils.py @@ -69,8 +69,13 @@ def save_layer(results_path, image_name, image): def show_result( - viewer, layer, image, name, existing_layer: napari.layers.Layer = None -): + viewer, + layer, + image, + name, + existing_layer: napari.layers.Layer = None, + colormap="bop orange", +) -> napari.layers.Layer: """ Adds layers to a viewer to show result to user @@ -81,12 +86,15 @@ def show_result( name: name of the added layer Returns: - + napari.layers.Layer: the layer added to the viewer """ + colormap = colormap if colormap is not None else "gray" if existing_layer is None: if isinstance(layer, napari.layers.Image): LOGGER.info("Added resulting image layer") - results_layer = viewer.add_image(image, name=name) + results_layer = viewer.add_image( + image, name=name, colormap=colormap + ) elif isinstance(layer, napari.layers.Labels): LOGGER.info("Added resulting label layer") results_layer = viewer.add_labels(image, name=name) From a7a00b3be8e05ee75f344fde7ec37e85cf2a8690 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 31 Jul 2023 15:14:29 +0200 Subject: [PATCH 13/17] Update plugin_crf.py --- napari_cellseg3d/code_plugins/plugin_crf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/napari_cellseg3d/code_plugins/plugin_crf.py b/napari_cellseg3d/code_plugins/plugin_crf.py index 5225787f..04caae02 100644 --- a/napari_cellseg3d/code_plugins/plugin_crf.py +++ b/napari_cellseg3d/code_plugins/plugin_crf.py @@ -270,7 +270,6 @@ def _start(self): def _on_yield(self, result): self.crf_results.append(result) - utils.save_layer( self.results_filewidget.text_field.text(), str(self.result_name + "_crf.tif"), From 469af40907c14fb6833abe4485c524601d36af53 Mon Sep 17 00:00:00 2001 From: Cyril Achard <94955160+C-Achard@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:32:45 +0200 Subject: [PATCH 14/17] Fix typo in crf.py Co-authored-by: Jessy Lauer <30733203+jeylau@users.noreply.github.com> --- napari_cellseg3d/code_models/crf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napari_cellseg3d/code_models/crf.py b/napari_cellseg3d/code_models/crf.py index 79951fc5..10e04aa8 100644 --- a/napari_cellseg3d/code_models/crf.py +++ b/napari_cellseg3d/code_models/crf.py @@ -7,7 +7,7 @@ Philipp Krähenbühl and Vladlen Koltun NIPS 2011 -Implemented using the pydense libary available at https://github.com/kodalli/pydensecrf. +Implemented using the pydense library available at https://github.com/kodalli/pydensecrf. """ from warnings import warn From 1b28f3d2fc71cd0d5059995d47310e3684aa71f9 Mon Sep 17 00:00:00 2001 From: Cyril Achard <94955160+C-Achard@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:33:27 +0200 Subject: [PATCH 15/17] Fix Pathlib typo in plugin_convert.py Co-authored-by: Jessy Lauer <30733203+jeylau@users.noreply.github.com> --- napari_cellseg3d/code_plugins/plugin_convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napari_cellseg3d/code_plugins/plugin_convert.py b/napari_cellseg3d/code_plugins/plugin_convert.py index e990ce64..df1a5bb0 100644 --- a/napari_cellseg3d/code_plugins/plugin_convert.py +++ b/napari_cellseg3d/code_plugins/plugin_convert.py @@ -117,7 +117,7 @@ def _start(self): class AnisoUtils(BasePluginUtils): """Class to correct anisotropy in images""" - save_path = Path.home() / Path("cellseg3d/anisotropy") + save_path = Path.home() / "cellseg3d" / "anisotropy" def __init__(self, viewer: "napari.Viewer.viewer", parent=None): """ From 2dac8f5535ed2154768ac2ff5389bb9878e929c8 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Wed, 9 Aug 2023 16:47:57 +0200 Subject: [PATCH 16/17] Fixed filepaths + comments cleanup --- .../code_plugins/plugin_convert.py | 31 +++---------------- napari_cellseg3d/code_plugins/plugin_crf.py | 2 +- napari_cellseg3d/code_plugins/plugin_crop.py | 2 +- .../code_plugins/plugin_review.py | 2 +- napari_cellseg3d/config.py | 6 ++-- 5 files changed, 11 insertions(+), 32 deletions(-) diff --git a/napari_cellseg3d/code_plugins/plugin_convert.py b/napari_cellseg3d/code_plugins/plugin_convert.py index df1a5bb0..d1878cdf 100644 --- a/napari_cellseg3d/code_plugins/plugin_convert.py +++ b/napari_cellseg3d/code_plugins/plugin_convert.py @@ -25,7 +25,7 @@ class FragmentUtils(BasePluginUtils): """Class to crop large 3D volumes into smaller fragments""" - save_path = Path.home() / Path("cellseg3d/fragmented") + save_path = Path.home() / "cellseg3d" / "fragmented" def __init__(self, viewer: "napari.Viewer.viewer", parent=None): """Creates a FragmentUtils widget @@ -214,7 +214,7 @@ def _start(self): class RemoveSmallUtils(BasePluginUtils): - save_path = Path.home() / Path("cellseg3d/small_removed") + save_path = Path.home() / "cellseg3d" / "small_removed" """ Widget to remove small objects """ @@ -236,7 +236,6 @@ def __init__(self, viewer: "napari.viewer.Viewer", parent=None): self.data_panel = self._build_io_panel() self.label_layer_loader.layer_list.label.setText("Layer :") - # self.label_layer_loader.set_layer_type(napari.layers.Layer) self.start_btn = ui.Button("Start", self._start) self.size_for_removal_counter = ui.IntIncrementCounter( @@ -317,7 +316,7 @@ def _start(self): class ToSemanticUtils(BasePluginUtils): - save_path = Path.home() / Path("cellseg3d/semantic_labels") + save_path = Path.home() / "cellseg3d" / "semantic_labels" """ Widget to create semantic labels from instance labels """ @@ -407,7 +406,7 @@ def _start(self): class ToInstanceUtils(BasePluginUtils): - save_path = Path.home() / Path("cellseg3d/instance_labels") + save_path = Path.home() / "cellseg3d" / "instance_labels" """ Widget to convert semantic labels to instance labels """ @@ -497,7 +496,7 @@ def _start(self): class ThresholdUtils(BasePluginUtils): - save_path = Path.home() / Path("cellseg3d/threshold") + save_path = Path.home() / "cellseg3d" / "threshold" """ Creates a ThresholdUtils widget Args: @@ -591,23 +590,3 @@ def _start(self): images, self.images_filepaths, ) - - -# class ConvertUtils(BasePluginFolder): -# """Utility widget that allows to convert labels from instance to semantic and the reverse.""" -# -# def __init__(self, viewer: "napari.viewer.Viewer", parent): -# """Builds a ConvertUtils widget with the following buttons: -# -# * A button to convert a folder of labels to semantic labels -# -# * A button to convert a folder of labels to instance labels -# -# * A button to convert a currently selected layer to semantic labels -# -# * A button to convert a currently selected layer to instance labels -# """ -# -# super().__init__(viewer, parent) -# self._viewer = viewer -# pass diff --git a/napari_cellseg3d/code_plugins/plugin_crf.py b/napari_cellseg3d/code_plugins/plugin_crf.py index 04caae02..64503e10 100644 --- a/napari_cellseg3d/code_plugins/plugin_crf.py +++ b/napari_cellseg3d/code_plugins/plugin_crf.py @@ -110,7 +110,7 @@ def make_config(self): class CRFWidget(BasePluginUtils): """Widget to run CRF post-processing""" - save_path = Path.home() / Path("cellseg3d/crf") + save_path = Path.home() / "cellseg3d" / "crf" def __init__(self, viewer, parent=None): """ diff --git a/napari_cellseg3d/code_plugins/plugin_crop.py b/napari_cellseg3d/code_plugins/plugin_crop.py index a083e4af..c4889249 100644 --- a/napari_cellseg3d/code_plugins/plugin_crop.py +++ b/napari_cellseg3d/code_plugins/plugin_crop.py @@ -22,7 +22,7 @@ class Cropping( ): # not a BasePLuginUtils since it's not runnning on folders """A utility plugin for cropping 3D volumes.""" - save_path = Path.home() / Path("cellseg3d/cropped") + save_path = Path.home() / "cellseg3d" / "cropped" utils_default_paths = [] def __init__(self, viewer: "napari.viewer.Viewer", parent=None): diff --git a/napari_cellseg3d/code_plugins/plugin_review.py b/napari_cellseg3d/code_plugins/plugin_review.py index d3216436..712b3193 100644 --- a/napari_cellseg3d/code_plugins/plugin_review.py +++ b/napari_cellseg3d/code_plugins/plugin_review.py @@ -144,7 +144,7 @@ def _build(self): # self._show_io_element(self.results_filewidget) self.results_filewidget.text_field.setText( - str(Path.home() / Path("cellseg3d/review")) + str(Path.home() / "cellseg3d" / "review") ) csv_param_w.setLayout(csv_param_l) diff --git a/napari_cellseg3d/config.py b/napari_cellseg3d/config.py index b05f7ac7..027aef2b 100644 --- a/napari_cellseg3d/config.py +++ b/napari_cellseg3d/config.py @@ -44,7 +44,7 @@ class ReviewConfig: image: np.array = None labels: np.array = None - csv_path: str = Path.home() / Path("cellseg3d/review") + csv_path: str = Path.home() / "cellseg3d" / "review" model_name: str = "" new_csv: bool = True filetype: str = ".tif" @@ -206,7 +206,7 @@ class InferenceWorkerConfig: device: str = "cpu" model_info: ModelInfo = ModelInfo() weights_config: WeightsInfo = WeightsInfo() - results_path: str = str(Path.home() / Path("cellseg3d/inference")) + results_path: str = str(Path.home() / "cellseg3d" / "inference") filetype: str = ".tif" keep_on_cpu: bool = False compute_stats: bool = False @@ -254,7 +254,7 @@ class TrainingWorkerConfig: scheduler_factor: float = 0.5 validation_interval: int = 2 batch_size: int = 1 - results_path_folder: str = str(Path.home() / Path("cellseg3d/training")) + results_path_folder: str = str(Path.home() / "cellseg3d" / "training") sampling: bool = False num_samples: int = 2 sample_size: List[int] = None From 95dfe80735d054f9d602aa1ec66c8161706efecf Mon Sep 17 00:00:00 2001 From: C-Achard Date: Tue, 19 Sep 2023 10:56:46 +0200 Subject: [PATCH 17/17] Fixed merge comment deletion --- napari_cellseg3d/code_models/crf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/napari_cellseg3d/code_models/crf.py b/napari_cellseg3d/code_models/crf.py index ddd5176a..e90529ce 100644 --- a/napari_cellseg3d/code_models/crf.py +++ b/napari_cellseg3d/code_models/crf.py @@ -8,6 +8,7 @@ NIPS 2011 Implemented using the pydense library available at https://github.com/kodalli/pydensecrf. +""" from warnings import warn