Skip to content

Commit 9980c2b

Browse files
committed
added distance to object to DistanceMeasureWidget feature and saving to csv file. _compute_seg_object_distances needed to be adapted to accept 2D data (prev. only 3D data was accepted)
1 parent 514e1f1 commit 9980c2b

File tree

5 files changed

+132
-140
lines changed

5 files changed

+132
-140
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: 1 addition & 1 deletion
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):
@@ -67,7 +68,6 @@ def _get_layer_selector_data(self, selector_name):
6768

6869
if isinstance(image_selector, QComboBox):
6970
selected_layer_name = image_selector.currentText()
70-
print("selector_name", selector_name, "selected_layer_name", selected_layer_name)
7171
if selected_layer_name in self.viewer.layers:
7272
return self.viewer.layers[selected_layer_name].data
7373
return None # Return None if layer not found

synaptic_reconstruction/tools/synaptic_plugin/distance_measure_widget.py

Lines changed: 43 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,6 @@ def __init__(self):
4444

4545
self.setLayout(layout)
4646

47-
# def get_selected_layer_data(self, selector_name):
48-
# """Return the data for the layer currently selected in a given selector."""
49-
# if selector_name in self.layer_selectors:
50-
# selector_widget = self.layer_selectors[selector_name]
51-
52-
# # Retrieve the QComboBox from the QWidget's layout
53-
# image_selector = selector_widget.layout().itemAt(1).widget()
54-
55-
# if isinstance(image_selector, QComboBox):
56-
# selected_layer_name = image_selector.currentText()
57-
# if selected_layer_name in self.viewer.layers:
58-
# return self.viewer.layers[selected_layer_name].data
59-
# # selected_layer_name = self.layer_selectors[selector_name].currentText()
60-
# # if selected_layer_name in self.viewer.layers:
61-
# # return self.viewer.layers[selected_layer_name].data
62-
# return None # Return None if layer not found
63-
6447
def on_measure_segmentation_to_object(self):
6548
segmentation1_data = self._get_layer_selector_data(self.image_selector_name1)
6649
segmentation2_data = self._get_layer_selector_data(self.image_selector_name2)
@@ -72,144 +55,79 @@ def on_measure_segmentation_to_object(self):
7255
(distances,
7356
endpoints1,
7457
endpoints2,
75-
seg_ids,
76-
object_ids) = distance_measurements.measure_segmentation_to_object_distances(
58+
seg_ids) = distance_measurements.measure_segmentation_to_object_distances(
7759
segmentation=segmentation1_data,
7860
segmented_object=segmentation2_data,
7961
distance_type="boundary",
8062
# save_path=self.save_path
8163
)
82-
if self.save_path is not None:
83-
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+
)
8473
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+
)
8580

86-
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.")
8794
return
8895

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

162118
def _create_settings_widget(self):
163119
setting_values = QWidget()
164120
# setting_values.setToolTip(get_tooltip("embedding", "settings"))
165121
setting_values.setLayout(QVBoxLayout())
166122

167123
self.save_path, layout = self._add_path_param(
168-
name="Save Directory", select_type="directory", value=None
124+
name="Save Directory", select_type="directory", value=""
169125
)
170126
setting_values.layout().addLayout(layout)
171127

172128
settings = self._make_collapsible(widget=setting_values, title="Advanced Settings")
173129
return settings
174130

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

214132
def get_distance_measure_widget():
215133
return DistanceMeasureWidget()

synaptic_reconstruction/tools/synaptic_plugin/segmentation_widget.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,15 @@ def on_predict(self):
9898
if ts != 0 or h != 0: # if anything changed from default
9999
use_custom_tiling = True
100100
if use_custom_tiling:
101-
segmentation = run_segmentation(image, model_path=model_path, model_key=model_key, tiling=tiling)
101+
segmentation = run_segmentation(
102+
image, model_path=model_path, model_key=model_key,
103+
tiling=tiling, scale=self.scale_param.value()
104+
)
102105
else:
103-
segmentation = run_segmentation(image, model_path=model_path, model_key=model_key)
106+
segmentation = run_segmentation(
107+
image, model_path=model_path, model_key=model_key,
108+
scale=self.scale_param.value()
109+
)
104110

105111
# Add the segmentation layer
106112
self.viewer.add_labels(segmentation, name=f"{model_key}-segmentation")
@@ -122,20 +128,25 @@ def _create_settings_widget(self):
122128
setting_values.layout().addLayout(layout)
123129

124130
# Create UI for the tile shape.
125-
self.tile_x, self.tile_y = 0, 0 # defaults
131+
self.tile_x, self.tile_y = 512, 512 # defaults
126132
self.tile_x_param, self.tile_y_param, layout = self._add_shape_param(
127133
("tile_x", "tile_y"), (self.tile_x, self.tile_y), min_val=0, max_val=2048, step=16,
128134
# tooltip=get_tooltip("embedding", "tiling")
129135
)
130136
setting_values.layout().addLayout(layout)
131137

132138
# Create UI for the halo.
133-
self.halo_x, self.halo_y = 0, 0 # defaults
139+
self.halo_x, self.halo_y = 64, 64 # defaults
134140
self.halo_x_param, self.halo_y_param, layout = self._add_shape_param(
135141
("halo_x", "halo_y"), (self.halo_x, self.halo_y), min_val=0, max_val=512,
136142
# tooltip=get_tooltip("embedding", "halo")
137143
)
138144
setting_values.layout().addLayout(layout)
145+
146+
self.scale_param, layout = self._add_float_param(
147+
"scale", 0.5, min_val=0.0, max_val=8.0,
148+
)
149+
setting_values.layout().addLayout(layout)
139150

140151
settings = self._make_collapsible(widget=setting_values, title="Advanced Settings")
141152
return settings

synaptic_reconstruction/tools/util.py

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,78 @@
99
from torch_em.util.prediction import predict_with_halo
1010
from synaptic_reconstruction.inference.vesicles import segment_vesicles
1111
from synaptic_reconstruction.inference.mitochondria import segment_mitochondria
12+
import csv
1213

1314

14-
def save_to_csv(file_path, data, delimiter="\t"):
15+
# def save_to_csv(file_path, data, delimiter="\t"):
16+
# if not file_path.endswith(".csv"):
17+
# file_path = os.path.join(file_path, "data.csv")
18+
# np.savetxt(file_path, data, delimiter=delimiter)
19+
# return file_path
20+
# def save_to_csv(file_path, data, delimiter="\t", header=None):
21+
# if not file_path.endswith(".csv"):
22+
# file_path = os.path.join(file_path, "data.csv")
23+
24+
# # Check if the data is a tuple or list of arrays
25+
# if isinstance(data, (tuple, list)):
26+
# try:
27+
# # Combine data into a single 2D array if possible
28+
# data = np.column_stack(data)
29+
# except ValueError:
30+
# raise ValueError("The data elements cannot be combined into a 2D array. Ensure consistent shapes.")
31+
32+
# # Save the data to the file
33+
# np.savetxt(file_path, data, delimiter=delimiter, header=header)
34+
# return file_path
35+
def save_to_csv(file_path, data, delimiter="\t", header=None):
36+
"""
37+
Save structured data to a CSV file.
38+
39+
Parameters:
40+
- file_path (str): Path to save the CSV file.
41+
- data (tuple): A tuple of arrays with mixed types (distances, endpoint1, endpoint2, seg_ids).
42+
- delimiter (str): Delimiter for the CSV file (default is tab-delimited).
43+
- header (list or None): List of column headers for the CSV file.
44+
"""
1545
if not file_path.endswith(".csv"):
1646
file_path = os.path.join(file_path, "data.csv")
17-
np.savetxt(file_path, data, delimiter=delimiter)
18-
return file_path
1947

48+
# Ensure the directory exists
49+
if not os.path.isdir(os.path.dirname(file_path)):
50+
raise FileNotFoundError(f"Directory does not exist: {os.path.dirname(file_path)}")
51+
52+
# Unpack the data
53+
distances, endpoint1, endpoint2, seg_ids = data
54+
55+
# Check consistency of data lengths
56+
n = len(distances)
57+
if not all(len(arr) == n for arr in [endpoint1, endpoint2, seg_ids]):
58+
raise ValueError("All data components must have the same length.")
59+
60+
# Open the file and write the data
61+
with open(file_path, mode='w', newline='') as csvfile:
62+
writer = csv.writer(csvfile, delimiter=delimiter)
63+
64+
# Write the header if provided
65+
if header:
66+
writer.writerow(header)
67+
68+
# Write the data rows
69+
for i in range(n):
70+
writer.writerow([
71+
distances[i],
72+
f"({endpoint1[i][0]}, {endpoint1[i][1]})",
73+
f"({endpoint2[i][0]}, {endpoint2[i][1]})",
74+
seg_ids[i]
75+
])
76+
77+
return file_path
2078

21-
def run_segmentation(image, model_path, model_key, tiling=None):
79+
def run_segmentation(image, model_path, model_key, tiling=None, scale=1.0):
2280
if model_key == "vesicles":
23-
segmentation = segment_vesicles(image, model_path=model_path, tiling=tiling)
81+
segmentation = segment_vesicles(image, model_path=model_path, tiling=tiling, scale=[scale])
2482
elif model_key == "mitochondria":
25-
segmentation = segment_mitochondria(image, model_path=model_path, tiling=tiling)
83+
segmentation = segment_mitochondria(image, model_path=model_path, tiling=tiling, scale=[scale])
2684
return segmentation
2785

2886

0 commit comments

Comments
 (0)