Skip to content

Commit 9622f88

Browse files
committed
pooch loading with state_dict works.. layer_selector_widgets should be refactored to be in BaseWidget
1 parent 7af1511 commit 9622f88

File tree

5 files changed

+349
-70
lines changed

5 files changed

+349
-70
lines changed

synaptic_reconstruction/napari.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ categories: ["Image Processing", "Annotation"]
55
contributions:
66
commands:
77
- id: synaptic_reconstruction.segment
8-
python_name: synaptic_reconstruction.tools.synaptic_plugin.segmentation:segmentation_widget
8+
python_name: synaptic_reconstruction.tools.synaptic_plugin.segmentation_widget:get_segmentation_widget
99
title: Segment
10+
- id: synaptic_reconstruction.distance_measure
11+
python_name: synaptic_reconstruction.tools.synaptic_plugin.distance_measure_widget:get_distance_measure_widget
12+
title: Distance Measurement
1013
- id: synaptic_reconstruction.file_reader
1114
title: Read ".mrc, .rec" files
1215
python_name: synaptic_reconstruction.tools.file_reader_plugin.elf_reader:get_reader
@@ -20,3 +23,5 @@ contributions:
2023
widgets:
2124
- command: synaptic_reconstruction.segment
2225
display_name: Segmentation
26+
- command: synaptic_reconstruction.distance_measure
27+
display_name: Distance Measurement

synaptic_reconstruction/tools/synaptic_plugin/base_widget.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,51 @@
1+
from pathlib import Path
12
import napari
2-
from qtpy.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QLabel, QSpinBox, QLineEdit, QGroupBox, QFormLayout, QFrame, QComboBox
3+
from qtpy.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QLabel, QSpinBox, QLineEdit, QGroupBox, QFormLayout, QFrame, QComboBox, QCheckBox
4+
import qtpy.QtWidgets as QtWidgets
35
from superqt import QCollapsible
46

57

68
class BaseWidget(QWidget):
79
def __init__(self):
810
super().__init__()
911
self.viewer = napari.current_viewer()
12+
self.attribute_dict = {}
13+
14+
def _add_string_param(self, name, value, title=None, placeholder=None, layout=None, tooltip=None):
15+
if layout is None:
16+
layout = QtWidgets.QHBoxLayout()
17+
label = QtWidgets.QLabel(title or name)
18+
if tooltip:
19+
label.setToolTip(tooltip)
20+
layout.addWidget(label)
21+
param = QtWidgets.QLineEdit()
22+
param.setText(value)
23+
if placeholder is not None:
24+
param.setPlaceholderText(placeholder)
25+
param.textChanged.connect(lambda val: setattr(self, name, val))
26+
if tooltip:
27+
param.setToolTip(tooltip)
28+
layout.addWidget(param)
29+
return param, layout
30+
31+
def _add_float_param(self, name, value, title=None, min_val=0.0, max_val=1.0, decimals=2,
32+
step=0.01, layout=None, tooltip=None):
33+
if layout is None:
34+
layout = QtWidgets.QHBoxLayout()
35+
label = QtWidgets.QLabel(title or name)
36+
if tooltip:
37+
label.setToolTip(tooltip)
38+
layout.addWidget(label)
39+
param = QtWidgets.QDoubleSpinBox()
40+
param.setRange(min_val, max_val)
41+
param.setDecimals(decimals)
42+
param.setValue(value)
43+
param.setSingleStep(step)
44+
param.valueChanged.connect(lambda val: setattr(self, name, val))
45+
if tooltip:
46+
param.setToolTip(tooltip)
47+
layout.addWidget(param)
48+
return param, layout
1049

1150
def _add_int_param(self, name, value, min_val, max_val, title=None, step=1, layout=None, tooltip=None):
1251
if layout is None:
@@ -76,3 +115,74 @@ def _make_collapsible(self, widget, title):
76115
collapsible.addWidget(widget)
77116
parent_widget.layout().addWidget(collapsible)
78117
return parent_widget
118+
119+
def _add_boolean_param(self, name, value, title=None, tooltip=None):
120+
checkbox = QCheckBox(name if title is None else title)
121+
checkbox.setChecked(value)
122+
checkbox.stateChanged.connect(lambda val: setattr(self, name, val))
123+
if tooltip:
124+
checkbox.setToolTip(tooltip)
125+
return checkbox
126+
127+
def _add_path_param(self, name, value, select_type, title=None, placeholder=None, tooltip=None):
128+
assert select_type in ("directory", "file", "both")
129+
130+
layout = QtWidgets.QHBoxLayout()
131+
label = QtWidgets.QLabel(title or name)
132+
if tooltip:
133+
label.setToolTip(tooltip)
134+
layout.addWidget(label)
135+
136+
path_textbox = QtWidgets.QLineEdit()
137+
path_textbox.setText(str(value))
138+
if placeholder is not None:
139+
path_textbox.setPlaceholderText(placeholder)
140+
path_textbox.textChanged.connect(lambda val: setattr(self, name, val))
141+
if tooltip:
142+
path_textbox.setToolTip(tooltip)
143+
144+
layout.addWidget(path_textbox)
145+
146+
def add_path_button(select_type, tooltip=None):
147+
# Adjust button text.
148+
button_text = f"Select {select_type.capitalize()}"
149+
path_button = QtWidgets.QPushButton(button_text)
150+
151+
# Call appropriate function based on select_type.
152+
path_button.clicked.connect(lambda: getattr(self, f"_get_{select_type}_path")(name, path_textbox))
153+
if tooltip:
154+
path_button.setToolTip(tooltip)
155+
layout.addWidget(path_button)
156+
157+
if select_type == "both":
158+
add_path_button("file")
159+
add_path_button("directory")
160+
161+
else:
162+
add_path_button(select_type)
163+
164+
return path_textbox, layout
165+
166+
def _get_directory_path(self, name, textbox, tooltip=None):
167+
directory = QtWidgets.QFileDialog.getExistingDirectory(
168+
self, "Select Directory", "", QtWidgets.QFileDialog.ShowDirsOnly
169+
)
170+
if tooltip:
171+
directory.setToolTip(tooltip)
172+
if directory and Path(directory).is_dir():
173+
textbox.setText(str(directory))
174+
else:
175+
# Handle the case where the selected path is not a directory
176+
print("Invalid directory selected. Please try again.")
177+
178+
def _get_file_path(self, name, textbox, tooltip=None):
179+
file_path, _ = QtWidgets.QFileDialog.getOpenFileName(
180+
self, "Select File", "", "All Files (*)"
181+
)
182+
if tooltip:
183+
file_path.setToolTip(tooltip)
184+
if file_path and Path(file_path).is_file():
185+
textbox.setText(str(file_path))
186+
else:
187+
# Handle the case where the selected path is not a file
188+
print("Invalid file selected. Please try again.")
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import napari
2+
import napari.layers
3+
from napari.utils.notifications import show_info
4+
from qtpy.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QComboBox
5+
6+
from .base_widget import BaseWidget
7+
from synaptic_reconstruction.training.supervised_training import get_2d_model
8+
9+
# Custom imports for model and prediction utilities
10+
from synaptic_reconstruction import distance_measurements
11+
from ..util import save_to_csv
12+
13+
14+
class DistanceMeasureWidget(BaseWidget):
15+
def __init__(self):
16+
super().__init__()
17+
18+
self.viewer = napari.current_viewer()
19+
layout = QVBoxLayout()
20+
21+
self.selectors = {}
22+
self.image_selector_name1 = "Segmentation 1"
23+
self.image_selector_name2 = "Segmentation 2"
24+
# Create the image selection dropdown
25+
self.segmentation1_selector_widget = self.create_image_selector(selector_name=self.image_selector_name1)
26+
self.segmentation2_selector_widget = self.create_image_selector(selector_name=self.image_selector_name2)
27+
28+
# create save path
29+
self.settings = self._create_settings_widget()
30+
31+
# create buttons
32+
self.measure_pairwise_button = QPushButton('Measure Distance Pairwise')
33+
self.measure_segmentation_to_object_button = QPushButton('Measure Distance Segmentation to Object')
34+
35+
# Connect buttons to functions
36+
self.measure_pairwise_button.clicked.connect(self.on_measure_pairwise)
37+
self.measure_segmentation_to_object_button.clicked.connect(self.on_measure_segmentation_to_object)
38+
# self.load_model_button.clicked.connect(self.on_load_model)
39+
40+
# Add the widgets to the layout
41+
layout.addWidget(self.segmentation1_selector_widget)
42+
layout.addWidget(self.segmentation2_selector_widget)
43+
layout.addWidget(self.settings)
44+
# layout.addWidget(self.measure_pairwise_button)
45+
layout.addWidget(self.measure_segmentation_to_object_button)
46+
47+
self.setLayout(layout)
48+
49+
def get_selected_layer_data(self, selector_name):
50+
"""Return the data for the layer currently selected in a given selector."""
51+
if selector_name in self.selectors:
52+
selected_layer_name = self.selectors[selector_name].currentText()
53+
if selected_layer_name in self.viewer.layers:
54+
return self.viewer.layers[selected_layer_name].data
55+
return None # Return None if layer not found
56+
57+
def on_measure_segmentation_to_object(self):
58+
segmentation1_data = self.get_selected_layer_data(self.image_selector_name1)
59+
segmentation2_data = self.get_selected_layer_data(self.image_selector_name2)
60+
if segmentation1_data is None or segmentation2_data is None:
61+
show_info("Please choose both segmentation layers.")
62+
return
63+
# get save_path
64+
65+
distances, endpoints1, endpoints2, seg_ids, object_ids = distance_measurements.measure_segmentation_to_object_distances(
66+
segmentation=segmentation1_data,
67+
segmented_object=segmentation2_data,
68+
distance_type="boundary",
69+
#save_path=self.save_path
70+
)
71+
if self.save_path is not None:
72+
file_path = save_to_csv(self.save_path, data=(distances, endpoints1, endpoints2, seg_ids, object_ids))
73+
show_info(f"Measurements saved to {file_path}")
74+
75+
show_info("Not implemented yet.")
76+
return
77+
78+
def on_measure_pairwise(self):
79+
if self.image is None:
80+
show_info("Please choose a segmentation.")
81+
return
82+
if self.save_path is None:
83+
show_info("Please choose a save path.")
84+
return
85+
# get segmentation
86+
segmentation = self.image
87+
# run measurements
88+
show_info("Not implemented yet.")
89+
return
90+
distance_measurements.measure_pairwise_object_distances(
91+
segmentation=segmentation, distance_type="boundary",
92+
save_path=self.save_path
93+
)
94+
lines, properties = distance_measurements.create_distance_lines(
95+
measurement_path=self.save_path
96+
)
97+
98+
# Add the lines layer
99+
self.viewer.add_lines(lines, name="Distance Lines", visible=True, edge_width=2, edge_color="red", edge_blend="additive")
100+
# Add the segmentation layer
101+
# self.viewer.add_image(segmentation, name="Segmentation", colormap="inferno", blending="additive")
102+
103+
# layer_kwargs = {"colormap": "inferno", "blending": "additive"}
104+
# return segmentation, layer_kwargs
105+
106+
def create_image_selector(self, selector_name):
107+
attribute_dict = {}
108+
viewer = self.viewer
109+
"""Create an image selector widget for a specific layer attribute."""
110+
selector_widget = QWidget()
111+
image_selector = QComboBox()
112+
title_label = QLabel(f"Select Layer for {selector_name}:")
113+
114+
# Populate initial options
115+
self.update_selector(viewer, image_selector)
116+
117+
# Connect selection change to update image data in attribute_dict
118+
image_selector.currentIndexChanged.connect(
119+
lambda: self.update_image_data(viewer, image_selector, attribute_dict, selector_name)
120+
)
121+
122+
# Update selector on layer events
123+
viewer.layers.events.inserted.connect(lambda event: self.update_selector(viewer, image_selector))
124+
viewer.layers.events.removed.connect(lambda event: self.update_selector(viewer, image_selector))
125+
126+
# Store this combo box in the selectors dictionary
127+
self.selectors[selector_name] = image_selector
128+
129+
# Set up layout
130+
layout = QVBoxLayout()
131+
layout.addWidget(title_label)
132+
layout.addWidget(image_selector)
133+
selector_widget.setLayout(layout)
134+
135+
return selector_widget
136+
137+
def update_selector(self, viewer, selector):
138+
"""Update a single selector with the current image layers in the viewer."""
139+
selector.clear()
140+
image_layers = [layer.name for layer in viewer.layers] #if isinstance(layer, napari.layers.Image)
141+
selector.addItems(image_layers)
142+
143+
def update_image_data(self, viewer, selector, attribute_dict, attribute_name):
144+
"""Update the specified attribute in the attribute_dict with selected layer data."""
145+
selected_layer_name = selector.currentText()
146+
if selected_layer_name in viewer.layers:
147+
attribute_dict[attribute_name] = viewer.layers[selected_layer_name].data
148+
else:
149+
attribute_dict[attribute_name] = None # Reset if no valid selection
150+
151+
def _create_settings_widget(self):
152+
setting_values = QWidget()
153+
# setting_values.setToolTip(get_tooltip("embedding", "settings"))
154+
setting_values.setLayout(QVBoxLayout())
155+
156+
self.save_path, layout = self._add_path_param(
157+
name="Save Directory", select_type="directory", value=None
158+
)
159+
setting_values.layout().addLayout(layout)
160+
161+
settings = self._make_collapsible(widget=setting_values, title="Advanced Settings")
162+
return settings
163+
164+
# def create_image_selector(self):
165+
# selector_widget = QWidget()
166+
# self.image_selector = QComboBox()
167+
168+
# title_label = QLabel("Select Image Layer:")
169+
170+
# # Populate initial options
171+
# self.update_image_selector()
172+
173+
# # Connect selection change to update self.image
174+
# self.image_selector.currentIndexChanged.connect(self.update_image_data)
175+
176+
# # Connect to Napari layer events to update the list
177+
# self.viewer.layers.events.inserted.connect(self.update_image_selector)
178+
# self.viewer.layers.events.removed.connect(self.update_image_selector)
179+
180+
# layout = QVBoxLayout()
181+
# layout.addWidget(title_label)
182+
# layout.addWidget(self.image_selector)
183+
# selector_widget.setLayout(layout)
184+
# return selector_widget
185+
186+
# def update_image_selector(self, event=None):
187+
# """Update dropdown options with current image layers in the viewer."""
188+
# self.image_selector.clear()
189+
190+
# # Add each image layer's name to the dropdown
191+
# image_layers = [layer.name for layer in self.viewer.layers if isinstance(layer, napari.layers.Image)]
192+
# self.image_selector.addItems(image_layers)
193+
194+
# def update_image_data(self):
195+
# """Update the self.image attribute with data from the selected layer."""
196+
# selected_layer_name = self.image_selector.currentText()
197+
# if selected_layer_name in self.viewer.layers:
198+
# self.image = self.viewer.layers[selected_layer_name].data
199+
# else:
200+
# self.image = None # Reset if no valid selection
201+
202+
203+
def get_distance_measure_widget():
204+
return DistanceMeasureWidget()

0 commit comments

Comments
 (0)