Skip to content

Commit d991be3

Browse files
Update distance measurement widget
1 parent f74e5cd commit d991be3

File tree

3 files changed

+81
-86
lines changed

3 files changed

+81
-86
lines changed

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88
name="synaptic_reconstruction",
99
packages=find_packages(exclude=["test"]),
1010
version=__version__,
11-
author="Constantin Pape; Sarah Muth",
11+
author="Constantin Pape; Sarah Muth; Luca Freckmann",
1212
url="https://github.com/computational-cell-analytics/synaptic_reconstruction",
1313
license="MIT",
1414
entry_points={
1515
"console_scripts": [
16-
"sr_tools.correct_segmentation = synaptic_reconstruction.tools.segmentation_correction:main",
17-
"sr_tools.measure_distances = synaptic_reconstruction.tools.distance_measurement:main",
16+
# TODO add segmentation CLI
1817
],
1918
"napari.manifest": [
2019
"synaptic_reconstruction = synaptic_reconstruction:napari.yaml",

synaptic_reconstruction/distance_measurements.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import multiprocessing as mp
3-
from typing import Dict, Optional, Tuple
3+
from typing import Dict, List, Optional, Tuple
44

55
import numpy as np
66

@@ -61,8 +61,9 @@ def _compute_boundary_distances(segmentation, resolution, n_threads):
6161
n = len(seg_ids)
6262

6363
pairwise_distances = np.zeros((n, n))
64-
end_points1 = np.zeros((n, n, 3), dtype="int")
65-
end_points2 = np.zeros((n, n, 3), dtype="int")
64+
ndim = segmentation.ndim
65+
end_points1 = np.zeros((n, n, ndim), dtype="int")
66+
end_points2 = np.zeros((n, n, ndim), dtype="int")
6667

6768
properties = regionprops(segmentation)
6869
properties = {prop.label: prop for prop in properties}
@@ -80,8 +81,11 @@ def compute_distances_for_object(i):
8081
prop = properties[ngb_id]
8182

8283
bb = prop.bbox
83-
offset = np.array(bb[:3])
84-
bb = np.s_[bb[0]:bb[3], bb[1]:bb[4], bb[2]:bb[5]]
84+
offset = np.array(bb[:ndim])
85+
if ndim == 2:
86+
bb = np.s_[bb[0]:bb[2], bb[1]:bb[3]]
87+
else:
88+
bb = np.s_[bb[0]:bb[3], bb[1]:bb[4], bb[2]:bb[5]]
8589

8690
mask = segmentation[bb] == ngb_id
8791
ngb_dist, ngb_index = distances[bb].copy(), indices[(slice(None),) + bb]
@@ -168,10 +172,9 @@ def _compute_seg_object_distances(segmentation, segmented_object, resolution, ve
168172
for prop in tqdm(props, disable=not verbose):
169173
bb = prop.bbox
170174
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
175+
if ndim == 2:
173176
bb = np.s_[bb[0]:bb[2], bb[1]:bb[3]]
174-
elif len(bb) == 6: # 3D bounding box
177+
else:
175178
bb = np.s_[bb[0]:bb[3], bb[1]:bb[4], bb[2]:bb[5]]
176179

177180
label = prop.label
@@ -296,7 +299,7 @@ def create_pairwise_distance_lines(
296299
distances: np.ndarray,
297300
endpoints1: np.ndarray,
298301
endpoints2: np.ndarray,
299-
seg_ids: np.ndarray,
302+
seg_ids: List[List[int]],
300303
n_neighbors: Optional[int] = None,
301304
pairs: Optional[np.ndarray] = None,
302305
bb: Optional[Tuple[slice]] = None,
@@ -323,9 +326,7 @@ def create_pairwise_distance_lines(
323326
if pairs is None and n_neighbors is not None:
324327
pairs = _extract_nearest_neighbors(distances, seg_ids, n_neighbors, remove_duplicates=remove_duplicates)
325328
elif pairs is None:
326-
pairs = [
327-
[id1, id2] for id1 in seg_ids for id2 in seg_ids if id1 < id2
328-
]
329+
pairs = [[id1, id2] for id1 in seg_ids for id2 in seg_ids if id1 < id2]
329330

330331
assert pairs is not None
331332
pair_indices = (

synaptic_reconstruction/tools/distance_measure_widget.py

Lines changed: 66 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import napari
44
import napari.layers
5+
import numpy as np
56
import pandas as pd
67

78
from napari.utils.notifications import show_info
@@ -39,109 +40,103 @@ def __init__(self):
3940
self.viewer = napari.current_viewer()
4041
layout = QVBoxLayout()
4142

42-
self.image_selector_name1 = "Segmentation 1"
43-
self.image_selector_name2 = "Segmentation 2"
44-
# Create the image selection dropdown
45-
# TODO: update the names to make it easier to distinguish what is what.
43+
self.image_selector_name1 = "Segmentation"
44+
self.image_selector_name2 = "Object"
45+
# Create the image selection dropdown.
4646
self.segmentation1_selector_widget = self._create_layer_selector(self.image_selector_name1, layer_type="Labels")
4747
self.segmentation2_selector_widget = self._create_layer_selector(self.image_selector_name2, layer_type="Labels")
4848

49-
# create save path
49+
# Create advanced settings.
5050
self.settings = self._create_settings_widget()
5151

52-
# create buttons
53-
self.measure_pairwise_button = QPushButton("Measure Distance Pairwise")
54-
self.measure_segmentation_to_object_button = QPushButton("Measure Distance Segmentation to Object")
52+
# Create and connect buttons.
53+
self.measure_button1 = QPushButton("Measure Distances")
54+
self.measure_button1.clicked.connect(self.on_measure_seg_to_object)
5555

56-
# Connect buttons to functions
57-
self.measure_pairwise_button.clicked.connect(self.on_measure_pairwise)
58-
self.measure_segmentation_to_object_button.clicked.connect(self.on_measure_segmentation_to_object)
59-
# self.load_model_button.clicked.connect(self.on_load_model)
56+
self.measure_button2 = QPushButton("Measure Pairwise Distances")
57+
self.measure_button2.clicked.connect(self.on_measure_pairwise)
6058

61-
# Add the widgets to the layout
59+
# Add the widgets to the layout.
6260
layout.addWidget(self.segmentation1_selector_widget)
6361
layout.addWidget(self.segmentation2_selector_widget)
6462
layout.addWidget(self.settings)
65-
# layout.addWidget(self.measure_pairwise_button)
66-
layout.addWidget(self.measure_segmentation_to_object_button)
63+
layout.addWidget(self.measure_button1)
64+
layout.addWidget(self.measure_button2)
6765

6866
self.setLayout(layout)
6967

70-
def on_measure_segmentation_to_object(self):
71-
segmentation1_data = self._get_layer_selector_data(self.image_selector_name1)
72-
segmentation2_data = self._get_layer_selector_data(self.image_selector_name2)
73-
if segmentation1_data is None or segmentation2_data is None:
74-
show_info("Please choose both segmentation layers.")
75-
return
76-
77-
(distances,
78-
endpoints1,
79-
endpoints2,
80-
seg_ids) = distance_measurements.measure_segmentation_to_object_distances(
81-
segmentation=segmentation1_data,
82-
segmented_object=segmentation2_data,
83-
distance_type="boundary",
84-
)
85-
86-
if self.save_path.text() != "":
87-
data = {"label": seg_ids, "distance": distances}
68+
def _to_table_data(self, distances, seg_ids, endpoints1=None, endpoints2=None):
69+
assert len(distances) == len(seg_ids), f"{distances.shape}, {seg_ids.shape}"
70+
if seg_ids.ndim == 2:
71+
table_data = {"label1": seg_ids[:, 0], "label2": seg_ids[:, 1], "distance": distances}
72+
else:
73+
table_data = {"label": seg_ids, "distance": distances}
74+
if endpoints1 is not None:
8875
axis_names = "zyx" if endpoints1.shape[1] == 3 else "yx"
89-
data.update({f"begin-{ax}": endpoints1[:, i] for i, ax in enumerate(axis_names)})
90-
data.update({f"end-{ax}": endpoints2[:, i] for i, ax in enumerate(axis_names)})
91-
data = pd.DataFrame(data)
92-
file_path = _save_distance_table(self.save_path.text(), data)
93-
94-
lines, properties = distance_measurements.create_object_distance_lines(
95-
distances=distances,
96-
endpoints1=endpoints1,
97-
endpoints2=endpoints2,
98-
seg_ids=seg_ids
99-
)
76+
table_data.update({f"begin-{ax}": endpoints1[:, i] for i, ax in enumerate(axis_names)})
77+
table_data.update({f"end-{ax}": endpoints2[:, i] for i, ax in enumerate(axis_names)})
78+
return pd.DataFrame(table_data)
10079

101-
# Add the lines layer
80+
def _add_lines_and_table(self, lines, properties, table_data, name):
10281
line_layer = self.viewer.add_shapes(
10382
lines,
104-
name="Distance Lines",
105-
shape_type="line", # Specify the shape type as 'line'
83+
name=name,
84+
shape_type="line",
10685
edge_width=2,
10786
edge_color="red",
108-
blending="additive", # Use 'additive' for blending if needed
87+
blending="additive",
88+
properties=properties,
10989
)
110-
111-
# FIXME: this doesn't work yet
11290
if add_table is not None:
11391
add_table(line_layer, self.viewer)
11492

93+
if self.save_path.text() != "":
94+
file_path = _save_distance_table(self.save_path.text(), table_data)
95+
11596
if self.save_path.text() != "":
11697
show_info(f"Added distance lines and saved file to {file_path}.")
11798
else:
11899
show_info("Added distance lines.")
119100

101+
def on_measure_seg_to_object(self):
102+
segmentation = self._get_layer_selector_data(self.image_selector_name1)
103+
object_data = self._get_layer_selector_data(self.image_selector_name2)
104+
105+
(distances,
106+
endpoints1,
107+
endpoints2,
108+
seg_ids) = distance_measurements.measure_segmentation_to_object_distances(
109+
segmentation=segmentation, segmented_object=object_data, distance_type="boundary",
110+
)
111+
lines, properties = distance_measurements.create_object_distance_lines(
112+
distances=distances,
113+
endpoints1=endpoints1,
114+
endpoints2=endpoints2,
115+
seg_ids=seg_ids
116+
)
117+
table_data = self._to_table_data(distances, seg_ids, endpoints1, endpoints2)
118+
self._add_lines_and_table(lines, properties, table_data, name="distances")
119+
120120
def on_measure_pairwise(self):
121-
if self.image is None:
122-
show_info("Please choose a segmentation.")
123-
return
124-
if self.save_path.value() is None:
125-
show_info("Please choose a save path.")
126-
return
127-
show_info("Not implemented yet.")
128-
return
129-
# distance_measurements.measure_pairwise_object_distances(
130-
# segmentation=segmentation, distance_type="boundary",
131-
# save_path=self.save_path
132-
# )
133-
# lines, properties = distance_measurements.create_distance_lines(
134-
# measurement_path=self.save_path
135-
# )
136-
137-
# # Add the lines layer
138-
# self.viewer.add_lines(
139-
# lines, name="Distance Lines", visible=True, edge_width=2, edge_color="red", edge_blend="additive"
140-
# )
121+
segmentation = self._get_layer_selector_data(self.image_selector_name1)
122+
123+
(distances,
124+
endpoints1,
125+
endpoints2,
126+
seg_ids) = distance_measurements.measure_pairwise_object_distances(
127+
segmentation=segmentation, distance_type="boundary"
128+
)
129+
lines, properties = distance_measurements.create_pairwise_distance_lines(
130+
distances=distances, endpoints1=endpoints1, endpoints2=endpoints2, seg_ids=seg_ids.tolist()
131+
)
132+
table_data = self._to_table_data(
133+
distances=properties["distance"],
134+
seg_ids=np.concatenate([properties["id_a"][:, None], properties["id_b"][:, None]], axis=1)
135+
)
136+
self._add_lines_and_table(lines, properties, table_data, name="pairwise-distances")
141137

142138
def _create_settings_widget(self):
143139
setting_values = QWidget()
144-
# setting_values.setToolTip(get_tooltip("embedding", "settings"))
145140
setting_values.setLayout(QVBoxLayout())
146141

147142
self.save_path, layout = self._add_path_param(name="Save Table", select_type="file", value="")

0 commit comments

Comments
 (0)