From 83546a77be2ef1e065491e211cf7b8399a84988c Mon Sep 17 00:00:00 2001 From: niksirbi Date: Thu, 11 Jan 2024 15:36:48 +0000 Subject: [PATCH] visually group existing widgets using groupboxes --- brainglobe_template_builder/napari/_widget.py | 23 +++++ .../napari/mask_widget.py | 29 ++++-- .../napari/midline_widget.py | 93 +++++++++++++------ 3 files changed, 109 insertions(+), 36 deletions(-) diff --git a/brainglobe_template_builder/napari/_widget.py b/brainglobe_template_builder/napari/_widget.py index 29288db..9296014 100644 --- a/brainglobe_template_builder/napari/_widget.py +++ b/brainglobe_template_builder/napari/_widget.py @@ -14,9 +14,32 @@ def __init__(self, napari_viewer: Viewer, parent=None): collapsible=True, widget_title="Create mask", ) + self._expand_mask_widget() self.add_widget( FindMidline(napari_viewer, parent=self), collapsible=True, widget_title="Find midline", ) + self._connect_midline_widget_toggle() + + def get_widgets(self): + """Get all widgets in the container.""" + return [ + self.layout().itemAt(i).widget() + for i in range(self.layout().count()) + ] + + def _expand_mask_widget(self): + """Expand the mask widget.""" + mask_widget = self.get_widgets()[0] + mask_widget.expand() + + def _connect_midline_widget_toggle(self): + """Connect the toggle of the midline widget to the refresh of its + dropdowns. + """ + midline_widget = self.get_widgets()[1] + midline_widget.toggled.connect( + midline_widget.content().refresh_dropdowns + ) diff --git a/brainglobe_template_builder/napari/mask_widget.py b/brainglobe_template_builder/napari/mask_widget.py index 20e7ba2..dc3729e 100644 --- a/brainglobe_template_builder/napari/mask_widget.py +++ b/brainglobe_template_builder/napari/mask_widget.py @@ -4,6 +4,7 @@ from qtpy.QtWidgets import ( QComboBox, QFormLayout, + QGroupBox, QPushButton, QSpinBox, QWidget, @@ -20,23 +21,33 @@ def __init__(self, napari_viewer: Viewer, parent=None): self.viewer = napari_viewer self.setLayout(QFormLayout()) - self.gauss_sigma = QSpinBox(parent=self) + self._create_mask_group() + + def _create_mask_group(self): + """Create the group of widgets concerned with creating a mask.""" + self.mask_groupbox = QGroupBox("Create mask to exclude background") + self.mask_groupbox.setLayout(QFormLayout()) + self.layout().addRow(self.mask_groupbox) + + self.gauss_sigma = QSpinBox(parent=self.mask_groupbox) self.gauss_sigma.setRange(0, 20) self.gauss_sigma.setValue(3) - self.layout().addRow("gauss sigma:", self.gauss_sigma) + self.mask_groupbox.layout().addRow("gaussian sigma:", self.gauss_sigma) - self.threshold_method = QComboBox(parent=self) + self.threshold_method = QComboBox(parent=self.mask_groupbox) self.threshold_method.addItems(["triangle", "otsu", "isodata"]) - self.layout().addRow("threshold method:", self.threshold_method) + self.mask_groupbox.layout().addRow( + "threshold method:", self.threshold_method + ) - self.closing_size = QSpinBox(parent=self) + self.closing_size = QSpinBox(parent=self.mask_groupbox) self.closing_size.setRange(0, 20) self.closing_size.setValue(5) - self.layout().addRow("closing size:", self.closing_size) + self.mask_groupbox.layout().addRow("closing size:", self.closing_size) - self.generate_mask_button = QPushButton("Create mask", parent=self) - self.layout().addRow(self.generate_mask_button) - self.generate_mask_button.clicked.connect(self._on_button_click) + self.create_mask_button = QPushButton("Create mask", parent=self) + self.mask_groupbox.layout().addRow(self.create_mask_button) + self.create_mask_button.clicked.connect(self._on_button_click) def _on_button_click(self): """Create a mask from the selected image layer, using the parameters diff --git a/brainglobe_template_builder/napari/midline_widget.py b/brainglobe_template_builder/napari/midline_widget.py index d4aac50..0defd7d 100644 --- a/brainglobe_template_builder/napari/midline_widget.py +++ b/brainglobe_template_builder/napari/midline_widget.py @@ -4,6 +4,7 @@ from qtpy.QtWidgets import ( QComboBox, QFormLayout, + QGroupBox, QPushButton, QWidget, ) @@ -21,44 +22,76 @@ def __init__(self, napari_viewer: Viewer, parent=None): super().__init__(parent=parent) self.viewer = napari_viewer self.setLayout(QFormLayout()) + self._create_estimate_group() + self._create_align_group() + + def _create_estimate_group(self): + """Create the group of widgets concerned with estimating midline + points.""" + self.estimate_groupbox = QGroupBox("Estimate points along midline") + self.estimate_groupbox.setLayout(QFormLayout()) + self.layout().addRow(self.estimate_groupbox) + + # Add dropdown to select labels layer (mask) + self.select_mask_dropdown = QComboBox(parent=self.estimate_groupbox) + self.select_mask_dropdown.addItems(self._get_layers_by_type(Labels)) + self.select_mask_dropdown.currentTextChanged.connect( + self._on_dropdown_selection_change + ) + self.estimate_groupbox.layout().addRow( + "mask:", self.select_mask_dropdown + ) # Initialise button to estimate midline points self.estimate_points_button = QPushButton( - "Estimate midline points", parent=self + "Estimate points", parent=self.estimate_groupbox ) - self.layout().addRow(self.estimate_points_button) + self.estimate_points_button.setEnabled(False) self.estimate_points_button.clicked.connect( self._on_estimate_button_click ) + self.estimate_groupbox.layout().addRow(self.estimate_points_button) + + def _create_align_group(self): + """Create the group of widgets concerned with aligning the image to + the midline.""" + + self.align_groupbox = QGroupBox("Align image to midline") + self.align_groupbox.setLayout(QFormLayout()) + self.layout().addRow(self.align_groupbox) # Add dropdown to select image layer - self.select_image_dropdown = QComboBox(parent=self) + self.select_image_dropdown = QComboBox(parent=self.align_groupbox) self.select_image_dropdown.addItems(self._get_layers_by_type(Image)) self.select_image_dropdown.currentTextChanged.connect( self._on_dropdown_selection_change ) - self.layout().addRow("image:", self.select_image_dropdown) + self.align_groupbox.layout().addRow( + "image:", self.select_image_dropdown + ) # Add dropdown to select points layer - self.select_points_dropdown = QComboBox(parent=self) + self.select_points_dropdown = QComboBox(parent=self.align_groupbox) self.select_points_dropdown.addItems(self._get_layers_by_type(Points)) self.select_points_dropdown.currentTextChanged.connect( self._on_dropdown_selection_change ) - self.layout().addRow("points:", self.select_points_dropdown) + self.align_groupbox.layout().addRow( + "points:", self.select_points_dropdown + ) # Add dropdown to select axis - self.select_axis_dropdown = QComboBox(parent=self) + self.select_axis_dropdown = QComboBox(parent=self.align_groupbox) self.select_axis_dropdown.addItems(["x", "y", "z"]) - self.layout().addRow("axis:", self.select_axis_dropdown) + self.align_groupbox.layout().addRow("axis:", self.select_axis_dropdown) # Add button to align image to midline self.align_image_button = QPushButton( - "Align image to midline", parent=self + "Align image", parent=self.align_groupbox ) - self.layout().addRow(self.align_image_button) - self.align_image_button.clicked.connect(self._on_align_button_click) self.align_image_button.setEnabled(False) + self.align_image_button.clicked.connect(self._on_align_button_click) + self.align_groupbox.layout().addRow(self.align_image_button) # 9 colors taken from ColorBrewer2.org Set3 palette self.point_colors = [ @@ -81,28 +114,25 @@ def _get_layers_by_type(self, layer_type: Layer) -> list: if isinstance(layer, layer_type) ] - def _refresh_layer_dropdowns(self): + def refresh_dropdowns(self): """Refresh the dropdowns to reflect the current layers.""" for layer_type, dropdown in zip( - [Image, Points], - [self.select_image_dropdown, self.select_points_dropdown], + [Labels, Image, Points], + [ + self.select_mask_dropdown, + self.select_image_dropdown, + self.select_points_dropdown, + ], ): dropdown.clear() dropdown.addItems(self._get_layers_by_type(layer_type)) def _on_estimate_button_click(self): """Estimate midline points and add them to the viewer.""" - if len(self.viewer.layers.selection) != 1: - show_info("Please select exactly one Labels layer") - return None - - mask = list(self.viewer.layers.selection)[0] - - if not isinstance(mask, Labels): - show_info("The selected layer is not a Labels layer") - return None - # Estimate 9 midline points + # Estimate 9 midline points based on the selected mask + mask_name = self.select_mask_dropdown.currentText() + mask = self.viewer.layers[mask_name] points = get_midline_points(mask.data) # Point layer attributes point_attrs = { @@ -119,7 +149,11 @@ def _on_estimate_button_click(self): mask.visible = False self.viewer.add_points(points, **point_attrs) - self._refresh_layer_dropdowns() + self.refresh_dropdowns() + show_info( + "Please move the estimated points so that they sit exactly " + "on the mid-sagittal plane." + ) def _on_align_button_click(self): """Align image and add the transformed image to the viewer.""" @@ -138,8 +172,13 @@ def _on_align_button_click(self): self.viewer.add_image(aligned_image, name="aligned image") def _on_dropdown_selection_change(self): - """Enable align button if both image and points dropdowns - have a selection.""" + # Enable estimate button if mask dropdown has a value + if self.select_mask_dropdown.currentText() == "": + self.estimate_points_button.setEnabled(False) + else: + self.estimate_points_button.setEnabled(True) + + # Enable align button if both image and points dropdowns have a value if ( self.select_image_dropdown.currentText() == "" or self.select_points_dropdown.currentText() == ""