Skip to content

Commit 38d6edf

Browse files
Merge pull request #50 from computational-cell-analytics/49-plugin-updates
Updates to the plugins
2 parents ff5be12 + 9980c2b commit 38d6edf

File tree

5 files changed

+203
-182
lines changed

5 files changed

+203
-182
lines changed

synaptic_reconstruction/distance_measurements.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,21 @@ def _compute_seg_object_distances(segmentation, segmented_object, resolution, ve
158158
n = len(seg_ids)
159159

160160
distances = np.zeros(n)
161-
endpoints1 = np.zeros((n, 3), dtype="int")
162-
endpoints2 = np.zeros((n, 3), dtype="int")
161+
ndim = segmentation.ndim
162+
endpoints1 = np.zeros((n, ndim), dtype="int")
163+
endpoints2 = np.zeros((n, ndim), dtype="int")
163164

164165
object_ids = []
165166
# We use this so often, it should be refactored.
166167
props = regionprops(segmentation)
167168
for prop in tqdm(props, disable=not verbose):
168169
bb = prop.bbox
169-
offset = np.array(bb[:3])
170-
bb = np.s_[bb[0]:bb[3], bb[1]:bb[4], bb[2]:bb[5]]
170+
offset = np.array(bb[:ndim])
171+
# bb = np.s_[bb[0]:bb[3], bb[1]:bb[4], bb[2]:bb[5]]
172+
if len(bb) == 4: # 2D bounding box
173+
bb = np.s_[bb[0]:bb[2], bb[1]:bb[3]]
174+
elif len(bb) == 6: # 3D bounding box
175+
bb = np.s_[bb[0]:bb[3], bb[1]:bb[4], bb[2]:bb[5]]
171176

172177
label = prop.label
173178
mask = segmentation[bb] == label

synaptic_reconstruction/tools/synaptic_plugin/base_widget.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from qtpy.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QLabel, QSpinBox, QLineEdit, QGroupBox, QFormLayout, QFrame, QComboBox, QCheckBox
44
import qtpy.QtWidgets as QtWidgets
55
from superqt import QCollapsible
6+
from magicgui.widgets import create_widget
67

78

89
class BaseWidget(QWidget):
@@ -11,6 +12,66 @@ def __init__(self):
1112
self.viewer = napari.current_viewer()
1213
self.attribute_dict = {}
1314

15+
def _create_layer_selector(self, selector_name, layer_type="Image"):
16+
"""
17+
Create a layer selector for an image or labels and store it in a dictionary.
18+
19+
Parameters:
20+
- selector_name (str): The name of the selector, used as a key in the dictionary.
21+
- layer_type (str): The type of layer to filter for ("Image" or "Labels").
22+
"""
23+
if not hasattr(self, "layer_selectors"):
24+
self.layer_selectors = {}
25+
26+
# Determine the annotation type for the widget
27+
if layer_type == "Image":
28+
layer_filter = napari.layers.Image
29+
elif layer_type == "Labels":
30+
layer_filter = napari.layers.Labels
31+
else:
32+
raise ValueError("layer_type must be either 'Image' or 'Labels'.")
33+
34+
selector_widget = QtWidgets.QWidget()
35+
image_selector = QtWidgets.QComboBox()
36+
layer_label = QtWidgets.QLabel(f"{selector_name} Layer:")
37+
38+
# Populate initial options
39+
self._update_selector(selector=image_selector, layer_filter=layer_filter)
40+
41+
# Update selector on layer events
42+
self.viewer.layers.events.inserted.connect(lambda event: self._update_selector(image_selector, layer_filter))
43+
self.viewer.layers.events.removed.connect(lambda event: self._update_selector(image_selector, layer_filter))
44+
45+
# Store the selector in the dictionary
46+
self.layer_selectors[selector_name] = selector_widget
47+
48+
# Set up layout
49+
layout = QVBoxLayout()
50+
layout.addWidget(layer_label)
51+
layout.addWidget(image_selector)
52+
selector_widget.setLayout(layout)
53+
return selector_widget
54+
55+
def _update_selector(self, selector, layer_filter):
56+
"""Update a single selector with the current image layers in the viewer."""
57+
selector.clear()
58+
image_layers = [layer.name for layer in self.viewer.layers if isinstance(layer, layer_filter)] # if isinstance(layer, napari.layers.Image)
59+
selector.addItems(image_layers)
60+
61+
def _get_layer_selector_data(self, selector_name):
62+
"""Return the data for the layer currently selected in a given selector."""
63+
if selector_name in self.layer_selectors:
64+
selector_widget = self.layer_selectors[selector_name]
65+
66+
# Retrieve the QComboBox from the QWidget's layout
67+
image_selector = selector_widget.layout().itemAt(1).widget()
68+
69+
if isinstance(image_selector, QComboBox):
70+
selected_layer_name = image_selector.currentText()
71+
if selected_layer_name in self.viewer.layers:
72+
return self.viewer.layers[selected_layer_name].data
73+
return None # Return None if layer not found
74+
1475
def _add_string_param(self, name, value, title=None, placeholder=None, layout=None, tooltip=None):
1576
if layout is None:
1677
layout = QtWidgets.QHBoxLayout()

synaptic_reconstruction/tools/synaptic_plugin/distance_measure_widget.py

Lines changed: 47 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ def __init__(self):
1717
self.viewer = napari.current_viewer()
1818
layout = QVBoxLayout()
1919

20-
self.selectors = {}
2120
self.image_selector_name1 = "Segmentation 1"
2221
self.image_selector_name2 = "Segmentation 2"
2322
# Create the image selection dropdown
24-
self.segmentation1_selector_widget = self.create_image_selector(selector_name=self.image_selector_name1)
25-
self.segmentation2_selector_widget = self.create_image_selector(selector_name=self.image_selector_name2)
23+
self.segmentation1_selector_widget = self._create_layer_selector(self.image_selector_name1, layer_type="Labels")
24+
self.segmentation2_selector_widget = self._create_layer_selector(self.image_selector_name2, layer_type="Labels")
2625

2726
# create save path
2827
self.settings = self._create_settings_widget()
@@ -45,17 +44,9 @@ def __init__(self):
4544

4645
self.setLayout(layout)
4746

48-
def get_selected_layer_data(self, selector_name):
49-
"""Return the data for the layer currently selected in a given selector."""
50-
if selector_name in self.selectors:
51-
selected_layer_name = self.selectors[selector_name].currentText()
52-
if selected_layer_name in self.viewer.layers:
53-
return self.viewer.layers[selected_layer_name].data
54-
return None # Return None if layer not found
55-
5647
def on_measure_segmentation_to_object(self):
57-
segmentation1_data = self.get_selected_layer_data(self.image_selector_name1)
58-
segmentation2_data = self.get_selected_layer_data(self.image_selector_name2)
48+
segmentation1_data = self._get_layer_selector_data(self.image_selector_name1)
49+
segmentation2_data = self._get_layer_selector_data(self.image_selector_name2)
5950
if segmentation1_data is None or segmentation2_data is None:
6051
show_info("Please choose both segmentation layers.")
6152
return
@@ -64,144 +55,79 @@ def on_measure_segmentation_to_object(self):
6455
(distances,
6556
endpoints1,
6657
endpoints2,
67-
seg_ids,
68-
object_ids) = distance_measurements.measure_segmentation_to_object_distances(
58+
seg_ids) = distance_measurements.measure_segmentation_to_object_distances(
6959
segmentation=segmentation1_data,
7060
segmented_object=segmentation2_data,
7161
distance_type="boundary",
7262
# save_path=self.save_path
7363
)
74-
if self.save_path is not None:
75-
file_path = save_to_csv(self.save_path, data=(distances, endpoints1, endpoints2, seg_ids, object_ids))
64+
if self.save_path.text() != "":
65+
# save to csv
66+
header = "distances endpoints1 endpoints2 seg_ids"
67+
header_list = header.split(" ")
68+
file_path = save_to_csv(
69+
self.save_path.text(),
70+
data=(distances, endpoints1, endpoints2, seg_ids),
71+
header=header_list
72+
)
7673
show_info(f"Measurements saved to {file_path}")
74+
lines, properties = distance_measurements.create_object_distance_lines(
75+
distances=distances,
76+
endpoints1=endpoints1,
77+
endpoints2=endpoints2,
78+
seg_ids=seg_ids
79+
)
7780

78-
show_info("Not implemented yet.")
81+
# Add the lines layer
82+
self.viewer.add_shapes(
83+
lines,
84+
name="Distance Lines",
85+
shape_type="line", # Specify the shape type as 'line'
86+
edge_width=2,
87+
edge_color="red",
88+
blending="additive", # Use 'additive' for blending if needed
89+
)
90+
if self.save_path.text() != "":
91+
show_info(f"Added distance lines and saved file to {file_path}.")
92+
else:
93+
show_info("Added distance lines.")
7994
return
8095

8196
def on_measure_pairwise(self):
8297
if self.image is None:
8398
show_info("Please choose a segmentation.")
8499
return
85-
if self.save_path is None:
100+
if self.save_path.value() is None:
86101
show_info("Please choose a save path.")
87102
return
88-
# get segmentation
89-
segmentation = self.image
90-
# run measurements
91103
show_info("Not implemented yet.")
92104
return
93-
distance_measurements.measure_pairwise_object_distances(
94-
segmentation=segmentation, distance_type="boundary",
95-
save_path=self.save_path
96-
)
97-
lines, properties = distance_measurements.create_distance_lines(
98-
measurement_path=self.save_path
99-
)
100-
101-
# Add the lines layer
102-
self.viewer.add_lines(
103-
lines, name="Distance Lines", visible=True, edge_width=2, edge_color="red", edge_blend="additive"
104-
)
105-
106-
# layer_kwargs = {"colormap": "inferno", "blending": "additive"}
107-
# return segmentation, layer_kwargs
108-
109-
def create_image_selector(self, selector_name):
110-
attribute_dict = {}
111-
viewer = self.viewer
112-
"""Create an image selector widget for a specific layer attribute."""
113-
selector_widget = QWidget()
114-
image_selector = QComboBox()
115-
title_label = QLabel(f"Select Layer for {selector_name}:")
116-
117-
# Populate initial options
118-
self.update_selector(viewer, image_selector)
119-
120-
# Connect selection change to update image data in attribute_dict
121-
image_selector.currentIndexChanged.connect(
122-
lambda: self.update_image_data(viewer, image_selector, attribute_dict, selector_name)
123-
)
124-
125-
# Update selector on layer events
126-
viewer.layers.events.inserted.connect(lambda event: self.update_selector(viewer, image_selector))
127-
viewer.layers.events.removed.connect(lambda event: self.update_selector(viewer, image_selector))
128-
129-
# Store this combo box in the selectors dictionary
130-
self.selectors[selector_name] = image_selector
131-
132-
# Set up layout
133-
layout = QVBoxLayout()
134-
layout.addWidget(title_label)
135-
layout.addWidget(image_selector)
136-
selector_widget.setLayout(layout)
137-
138-
return selector_widget
139-
140-
def update_selector(self, viewer, selector):
141-
"""Update a single selector with the current image layers in the viewer."""
142-
selector.clear()
143-
image_layers = [layer.name for layer in viewer.layers] # if isinstance(layer, napari.layers.Image)
144-
selector.addItems(image_layers)
145-
146-
def update_image_data(self, viewer, selector, attribute_dict, attribute_name):
147-
"""Update the specified attribute in the attribute_dict with selected layer data."""
148-
selected_layer_name = selector.currentText()
149-
if selected_layer_name in viewer.layers:
150-
attribute_dict[attribute_name] = viewer.layers[selected_layer_name].data
151-
else:
152-
attribute_dict[attribute_name] = None # Reset if no valid selection
105+
# distance_measurements.measure_pairwise_object_distances(
106+
# segmentation=segmentation, distance_type="boundary",
107+
# save_path=self.save_path
108+
# )
109+
# lines, properties = distance_measurements.create_distance_lines(
110+
# measurement_path=self.save_path
111+
# )
112+
113+
# # Add the lines layer
114+
# self.viewer.add_lines(
115+
# lines, name="Distance Lines", visible=True, edge_width=2, edge_color="red", edge_blend="additive"
116+
# )
153117

154118
def _create_settings_widget(self):
155119
setting_values = QWidget()
156120
# setting_values.setToolTip(get_tooltip("embedding", "settings"))
157121
setting_values.setLayout(QVBoxLayout())
158122

159123
self.save_path, layout = self._add_path_param(
160-
name="Save Directory", select_type="directory", value=None
124+
name="Save Directory", select_type="directory", value=""
161125
)
162126
setting_values.layout().addLayout(layout)
163127

164128
settings = self._make_collapsible(widget=setting_values, title="Advanced Settings")
165129
return settings
166130

167-
# def create_image_selector(self):
168-
# selector_widget = QWidget()
169-
# self.image_selector = QComboBox()
170-
171-
# title_label = QLabel("Select Image Layer:")
172-
173-
# # Populate initial options
174-
# self.update_image_selector()
175-
176-
# # Connect selection change to update self.image
177-
# self.image_selector.currentIndexChanged.connect(self.update_image_data)
178-
179-
# # Connect to Napari layer events to update the list
180-
# self.viewer.layers.events.inserted.connect(self.update_image_selector)
181-
# self.viewer.layers.events.removed.connect(self.update_image_selector)
182-
183-
# layout = QVBoxLayout()
184-
# layout.addWidget(title_label)
185-
# layout.addWidget(self.image_selector)
186-
# selector_widget.setLayout(layout)
187-
# return selector_widget
188-
189-
# def update_image_selector(self, event=None):
190-
# """Update dropdown options with current image layers in the viewer."""
191-
# self.image_selector.clear()
192-
193-
# # Add each image layer's name to the dropdown
194-
# image_layers = [layer.name for layer in self.viewer.layers if isinstance(layer, napari.layers.Image)]
195-
# self.image_selector.addItems(image_layers)
196-
197-
# def update_image_data(self):
198-
# """Update the self.image attribute with data from the selected layer."""
199-
# selected_layer_name = self.image_selector.currentText()
200-
# if selected_layer_name in self.viewer.layers:
201-
# self.image = self.viewer.layers[selected_layer_name].data
202-
# else:
203-
# self.image = None # Reset if no valid selection
204-
205131

206132
def get_distance_measure_widget():
207133
return DistanceMeasureWidget()

0 commit comments

Comments
 (0)