|
2 | 2 |
|
3 | 3 | import napari
|
4 | 4 | import napari.layers
|
| 5 | +import numpy as np |
5 | 6 | import pandas as pd
|
6 | 7 |
|
7 | 8 | from napari.utils.notifications import show_info
|
@@ -39,109 +40,103 @@ def __init__(self):
|
39 | 40 | self.viewer = napari.current_viewer()
|
40 | 41 | layout = QVBoxLayout()
|
41 | 42 |
|
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. |
46 | 46 | self.segmentation1_selector_widget = self._create_layer_selector(self.image_selector_name1, layer_type="Labels")
|
47 | 47 | self.segmentation2_selector_widget = self._create_layer_selector(self.image_selector_name2, layer_type="Labels")
|
48 | 48 |
|
49 |
| - # create save path |
| 49 | + # Create advanced settings. |
50 | 50 | self.settings = self._create_settings_widget()
|
51 | 51 |
|
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) |
55 | 55 |
|
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) |
60 | 58 |
|
61 |
| - # Add the widgets to the layout |
| 59 | + # Add the widgets to the layout. |
62 | 60 | layout.addWidget(self.segmentation1_selector_widget)
|
63 | 61 | layout.addWidget(self.segmentation2_selector_widget)
|
64 | 62 | 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) |
67 | 65 |
|
68 | 66 | self.setLayout(layout)
|
69 | 67 |
|
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: |
88 | 75 | 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) |
100 | 79 |
|
101 |
| - # Add the lines layer |
| 80 | + def _add_lines_and_table(self, lines, properties, table_data, name): |
102 | 81 | line_layer = self.viewer.add_shapes(
|
103 | 82 | lines,
|
104 |
| - name="Distance Lines", |
105 |
| - shape_type="line", # Specify the shape type as 'line' |
| 83 | + name=name, |
| 84 | + shape_type="line", |
106 | 85 | edge_width=2,
|
107 | 86 | edge_color="red",
|
108 |
| - blending="additive", # Use 'additive' for blending if needed |
| 87 | + blending="additive", |
| 88 | + properties=properties, |
109 | 89 | )
|
110 |
| - |
111 |
| - # FIXME: this doesn't work yet |
112 | 90 | if add_table is not None:
|
113 | 91 | add_table(line_layer, self.viewer)
|
114 | 92 |
|
| 93 | + if self.save_path.text() != "": |
| 94 | + file_path = _save_distance_table(self.save_path.text(), table_data) |
| 95 | + |
115 | 96 | if self.save_path.text() != "":
|
116 | 97 | show_info(f"Added distance lines and saved file to {file_path}.")
|
117 | 98 | else:
|
118 | 99 | show_info("Added distance lines.")
|
119 | 100 |
|
| 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 | + |
120 | 120 | 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") |
141 | 137 |
|
142 | 138 | def _create_settings_widget(self):
|
143 | 139 | setting_values = QWidget()
|
144 |
| - # setting_values.setToolTip(get_tooltip("embedding", "settings")) |
145 | 140 | setting_values.setLayout(QVBoxLayout())
|
146 | 141 |
|
147 | 142 | self.save_path, layout = self._add_path_param(name="Save Table", select_type="file", value="")
|
|
0 commit comments