Skip to content

Commit c4bbc00

Browse files
committed
morphology in dedicated layer
1 parent e6971f5 commit c4bbc00

File tree

4 files changed

+142
-47
lines changed

4 files changed

+142
-47
lines changed

synaptic_reconstruction/tools/base_widget.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def _create_layer_selector(self, selector_name, layer_type="Image"):
3838

3939
selector_widget = QtWidgets.QWidget()
4040
image_selector = QtWidgets.QComboBox()
41-
layer_label = QtWidgets.QLabel(f"{selector_name} Layer:")
41+
layer_label = QtWidgets.QLabel(f"{selector_name}:")
4242

4343
# Populate initial options
4444
self._update_selector(selector=image_selector, layer_filter=layer_filter)

synaptic_reconstruction/tools/distance_measure_widget.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ def _add_lines_and_table(self, lines, properties, table_data, name):
8787
blending="additive",
8888
properties=properties,
8989
)
90-
if line_layer.metadata is None:
91-
line_layer.metadata = table_data
92-
else:
93-
line_layer.metadata["distances"] = table_data
90+
# if line_layer.metadata is None:
91+
# line_layer.metadata = table_data
92+
# else:
93+
line_layer.properties["distances"] = table_data
9494

9595
if add_table is not None:
9696
add_table(line_layer, self.viewer)

synaptic_reconstruction/tools/morphology_widget.py

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def _create_shapes_layer(self, table_data, name="Shapes Layer"):
6969
if 'z' not in table_data.columns
7070
else table_data[['x', 'y', 'z']].to_numpy()
7171
)
72-
radii = table_data['radii'].to_numpy()
72+
radii = table_data['radius'].to_numpy()
7373

7474
if coords.shape[1] == 2:
7575
# For 2D data, create circular outlines using trigonometric functions
@@ -131,7 +131,7 @@ def _to_table_data(self, coords, radii, props):
131131
table_data = {
132132
'label_id': [prop.label for prop in props],
133133
**{col: coords[:, i] for i, col in enumerate(col_names)},
134-
'radii': radii,
134+
'radius': radii,
135135
'intensity_max': [prop.intensity_max for prop in props],
136136
'intensity_mean': [prop.intensity_mean for prop in props],
137137
'intensity_min': [prop.intensity_min for prop in props],
@@ -158,11 +158,6 @@ def _add_table(self, coords, radii, props, name="Shapes Layer"):
158158
# Add the shapes layer
159159
layer = self._create_shapes_layer(table_data, name)
160160

161-
# Add properties to segmentation layer
162-
segmentation_layer = self._get_layer_selector_layer(self.image_selector_name1)
163-
164-
segmentation_layer.metadata["morphology"] = table_data
165-
166161
if add_table is not None:
167162
add_table(layer, self.viewer)
168163

@@ -199,7 +194,7 @@ def on_measure_vesicle_morphology(self):
199194
resolution=resolution,
200195
props=props,
201196
)
202-
self._add_table(coords, radii, props, name="Vesicles")
197+
self._add_table(coords, radii, props, name="Vesicles Morphology")
203198

204199
def on_measure_structure_morphology(self):
205200
"""add the structure measurements to the segmentation layer (via properties)
@@ -222,36 +217,22 @@ def on_measure_structure_morphology(self):
222217
self._add_table_structure(morphology)
223218

224219
def _add_table_structure(self, morphology):
225-
segmentation_layer = self._get_layer_selector_layer(self.image_selector_name1)
226-
table_data = self._to_table_data_structure(morphology)
227-
228-
segmentation_layer.metadata["morphology"] = table_data
229-
segmentation_layer.properties = table_data
230-
print("segmentation layer metadata:\n", segmentation_layer.metadata)
220+
segmentation = self._get_layer_selector_data(self.image_selector_name1)
221+
layer = self.viewer.add_labels(np.zeros(segmentation.shape, dtype=np.uint16), name="Structure Morphology")
222+
# Add properties to layer for add_table function
223+
layer.properties = morphology
231224

232225
# Add a table layer to the Napari viewer
233226
if add_table is not None:
234-
add_table(segmentation_layer, self.viewer)
227+
add_table(layer, self.viewer)
235228

236229
# Save table to file if save path is provided
237230
if self.save_path.text() != "":
238-
file_path = _save_table(self.save_path.text(), table_data)
231+
file_path = _save_table(self.save_path.text(), morphology)
239232
show_info(f"INFO: Added table and saved file to {file_path}.")
240233
else:
241234
print("INFO: Table added to viewer.")
242235

243-
def _to_table_data_structure(self, morphology):
244-
# Create table data
245-
return morphology
246-
# for k, v in morphology.items():
247-
# table_data = {k: v}
248-
# table_data = {
249-
# "Name": morphology["perimeter [pixel]"],
250-
# "area [pixel^2]": morphology["area [pixel^2]"],
251-
# "perimeter [pixel]": morphology["perimeter [pixel]"],
252-
# }
253-
return pd.DataFrame(table_data)
254-
255236
def _create_settings_widget(self):
256237
setting_values = QWidget()
257238
setting_values.setLayout(QVBoxLayout())

synaptic_reconstruction/tools/vesicle_pool_widget.py

Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ def __init__(self):
2626
self.viewer = napari.current_viewer()
2727
layout = QVBoxLayout()
2828

29-
self.image_selector_name = "Distances"
30-
self.image_selector_name1 = "Segmentation"
29+
self.image_selector_name = "Distances to Structure"
30+
self.image_selector_name1 = "Vesicles Segmentation"
31+
self.image_selector_name2 = "Vesicles Morphology"
3132
# # Create the image selection dropdown.
3233
self.image_selector_widget = self._create_layer_selector(self.image_selector_name, layer_type="Shapes")
3334
self.segmentation1_selector_widget = self._create_layer_selector(self.image_selector_name1, layer_type="Labels")
35+
self.image_selector_widget2 = self._create_layer_selector(self.image_selector_name2, layer_type="Shapes")
3436

3537
# Create new layer name
3638
self.new_layer_name_param, new_layer_name_layout = self._add_string_param(
@@ -54,12 +56,13 @@ def __init__(self):
5456
self.settings = self._create_settings_widget()
5557

5658
# Create and connect buttons.
57-
self.measure_button1 = QPushButton("Measure Vesicle Morphology")
59+
self.measure_button1 = QPushButton("Create Vesicle Pool")
5860
self.measure_button1.clicked.connect(self.on_pool_vesicles)
5961

6062

6163
# Add the widgets to the layout.
6264
layout.addWidget(self.image_selector_widget)
65+
layout.addWidget(self.image_selector_widget2)
6366
layout.addWidget(self.segmentation1_selector_widget)
6467
layout.addLayout(query_layout)
6568
layout.addLayout(new_layer_name_layout)
@@ -70,14 +73,14 @@ def __init__(self):
7073

7174
self.setLayout(layout)
7275

73-
def _create_shapes_layer(self, name, pooling):
74-
print(name, pooling)
75-
return
76-
7776
def on_pool_vesicles(self):
78-
distances = self._get_layer_selector_data(self.image_selector_name, return_metadata=True)
77+
distances_layer = self._get_layer_selector_layer(self.image_selector_name)
78+
distances = distances_layer.properties
7979
segmentation = self._get_layer_selector_data(self.image_selector_name1)
80-
morphology = self._get_layer_selector_data(self.image_selector_name1, return_metadata=True)
80+
morphology_layer = self._get_layer_selector_layer(self.image_selector_name2)
81+
morphology = morphology_layer.properties
82+
print("distances", distances)
83+
print("morphology", morphology)
8184

8285
if segmentation is None:
8386
show_info("INFO: Please choose a segmentation.")
@@ -116,11 +119,122 @@ def on_pool_vesicles(self):
116119
# resolution = segmentation.ndim * [self.voxel_size_param.value()]
117120

118121
def _compute_vesicle_pool(self, segmentation, distances, morphology, new_layer_name, pooled_group_name, query):
119-
print(segmentation, distances, morphology, new_layer_name, pooled_group_name, query)
120-
vesicle_pool = {
121-
"segmentation": segmentation,
122-
}
123-
return vesicle_pool
122+
"""
123+
Compute a vesicle pool based on the provided query parameters.
124+
125+
Args:
126+
segmentation (array): Segmentation data (e.g., labeled regions).
127+
distances (dict): Properties from the distances layer.
128+
morphology (dict): Properties from the morphology layer.
129+
new_layer_name (str): Name for the new layer to be created.
130+
pooled_group_name (str): Name for the pooled group to be assigned.
131+
query (dict): Query parameters with keys "min_radius" and "max_distance".
132+
133+
Returns:
134+
dict: Updated properties for the new vesicle pool.
135+
"""
136+
137+
distances_ids = distances.get("id", [])
138+
morphology_ids = morphology.get("label_id", [])
139+
140+
# Check if IDs are identical
141+
if set(distances_ids) != set(morphology_ids):
142+
show_info("ERROR: The IDs in distances and morphology are not identical.")
143+
return
144+
145+
distances = pd.DataFrame(distances)
146+
morphology = pd.DataFrame(morphology)
147+
148+
# Merge dataframes on the 'id' column
149+
merged_df = morphology.merge(distances, left_on="label_id", right_on="id", suffixes=("_morph", "_dist"))
150+
# Filter rows based on query parameters
151+
# Apply the query string to filter the data
152+
filtered_df = self._parse_query(query, merged_df)
153+
154+
# Extract valid vesicle IDs
155+
valid_vesicle_ids = filtered_df["label_id"].tolist()
156+
157+
# Debugging output
158+
for _, row in filtered_df.iterrows():
159+
print(f"Vesicle {row['label_id']}: Passed (Radius: {row['radius']}, Distance: {row['distance']})")
160+
161+
# Update segmentation layer with valid vesicles
162+
new_layer_data = np.zeros(segmentation.shape, dtype=np.uint8)
163+
for vesicle_id in valid_vesicle_ids:
164+
new_layer_data[segmentation == vesicle_id] = 1 # Highlight pooled vesicles
165+
166+
# Create a new layer in the viewer
167+
self.viewer.add_labels(
168+
new_layer_data,
169+
name=new_layer_name,
170+
properties={
171+
"id": valid_vesicle_ids,
172+
"radius": filtered_df["radius"].tolist(),
173+
"distance": filtered_df["distance"].tolist(),
174+
},
175+
)
176+
show_info(f"Vesicle pool created with {len(valid_vesicle_ids)} vesicles.")
177+
return {
178+
"id": valid_vesicle_ids,
179+
"radius": filtered_df["radius"].tolist(),
180+
"distance": filtered_df["distance"].tolist(),
181+
}
182+
183+
# # Filter vesicles based on the query
184+
# valid_vesicles = []
185+
# for i in distances_ids:
186+
# distance = distances.get("distance", [])[i]
187+
# radius = morphology.get("radius", [])[i]
188+
# if radius >= min_radius and distance <= max_distance:
189+
# print(f"Vesicle {i}: Passed (Radius: {radius}, Distance: {distance})")
190+
# valid_vesicles.append(i)
191+
# else:
192+
# print(f"Vesicle {i}: Failed (Radius: {radius}, Distance: {distance})")
193+
194+
# Create pooled properties
195+
# pooled_properties = {
196+
# "id": [i for i in valid_vesicles],
197+
# "radius": [morphology["radius"][i] for i in valid_vesicles],
198+
# "distance": [distances["distance"][i] for i in valid_vesicles],
199+
# }
200+
# print("pooled_properties", pooled_properties)
201+
# print("np.unique(segmenation)", np.unique(segmentation))
202+
203+
# # Create a new layer in the viewer
204+
# new_layer_data = np.zeros(segmentation.shape, dtype=np.uint8)
205+
# for vesicle_id in valid_vesicles:
206+
# new_layer_data[segmentation == vesicle_id] = 1
207+
208+
# self.viewer.add_labels(
209+
# new_layer_data,
210+
# name=new_layer_name,
211+
# properties=pooled_properties,
212+
# )
213+
214+
# show_info(f"INFO: Created pooled vesicle layer '{new_layer_name}' with {len(valid_vesicles)} vesicles.")
215+
# return pooled_properties
216+
217+
def _parse_query(self, query, data):
218+
"""
219+
Parse and apply a query string to filter data.
220+
221+
Args:
222+
query (str): Comma-separated query string (e.g., "radius > 15, distance > 250").
223+
data (pd.DataFrame): DataFrame containing the data to filter.
224+
225+
Returns:
226+
pd.DataFrame: Filtered DataFrame.
227+
"""
228+
filters = query.split(",") # Split the query into individual conditions
229+
filters = [f.strip() for f in filters] # Remove extra spaces
230+
for condition in filters:
231+
try:
232+
# Apply each condition to filter the DataFrame
233+
data = data.query(condition)
234+
except Exception as e:
235+
print(f"Failed to apply condition '{condition}': {e}")
236+
continue
237+
return data
124238

125239
def _create_settings_widget(self):
126240
setting_values = QWidget()

0 commit comments

Comments
 (0)