Skip to content

Commit f74e5cd

Browse files
Update plugins WIP
1 parent 38d6edf commit f74e5cd

File tree

9 files changed

+85
-133
lines changed

9 files changed

+85
-133
lines changed

run_correction.sh

Lines changed: 0 additions & 1 deletion
This file was deleted.

synaptic_reconstruction/napari.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ categories: ["Image Processing", "Annotation"]
55
contributions:
66
commands:
77
- id: synaptic_reconstruction.segment
8-
python_name: synaptic_reconstruction.tools.synaptic_plugin.segmentation_widget:get_segmentation_widget
8+
python_name: synaptic_reconstruction.tools.segmentation_widget:SegmentationWidget
99
title: Segment
1010
- id: synaptic_reconstruction.distance_measure
11-
python_name: synaptic_reconstruction.tools.synaptic_plugin.distance_measure_widget:get_distance_measure_widget
11+
python_name: synaptic_reconstruction.tools.distance_measure_widget:DistanceMeasureWidget
1212
title: Distance Measurement
1313
- id: synaptic_reconstruction.file_reader
1414
title: Read volumetric data

synaptic_reconstruction/tools/synaptic_plugin/base_widget.py renamed to synaptic_reconstruction/tools/base_widget.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from pathlib import Path
2+
23
import napari
3-
from qtpy.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QLabel, QSpinBox, QLineEdit, QGroupBox, QFormLayout, QFrame, QComboBox, QCheckBox
44
import qtpy.QtWidgets as QtWidgets
5+
6+
from qtpy.QtWidgets import (
7+
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSpinBox, QComboBox, QCheckBox
8+
)
59
from superqt import QCollapsible
6-
from magicgui.widgets import create_widget
710

811

912
class BaseWidget(QWidget):
@@ -15,7 +18,7 @@ def __init__(self):
1518
def _create_layer_selector(self, selector_name, layer_type="Image"):
1619
"""
1720
Create a layer selector for an image or labels and store it in a dictionary.
18-
21+
1922
Parameters:
2023
- selector_name (str): The name of the selector, used as a key in the dictionary.
2124
- layer_type (str): The type of layer to filter for ("Image" or "Labels").
@@ -34,24 +37,24 @@ def _create_layer_selector(self, selector_name, layer_type="Image"):
3437
selector_widget = QtWidgets.QWidget()
3538
image_selector = QtWidgets.QComboBox()
3639
layer_label = QtWidgets.QLabel(f"{selector_name} Layer:")
37-
40+
3841
# Populate initial options
3942
self._update_selector(selector=image_selector, layer_filter=layer_filter)
40-
43+
4144
# Update selector on layer events
4245
self.viewer.layers.events.inserted.connect(lambda event: self._update_selector(image_selector, layer_filter))
4346
self.viewer.layers.events.removed.connect(lambda event: self._update_selector(image_selector, layer_filter))
4447

4548
# Store the selector in the dictionary
4649
self.layer_selectors[selector_name] = selector_widget
47-
50+
4851
# Set up layout
4952
layout = QVBoxLayout()
5053
layout.addWidget(layer_label)
5154
layout.addWidget(image_selector)
5255
selector_widget.setLayout(layout)
5356
return selector_widget
54-
57+
5558
def _update_selector(self, selector, layer_filter):
5659
"""Update a single selector with the current image layers in the viewer."""
5760
selector.clear()
@@ -62,10 +65,10 @@ def _get_layer_selector_data(self, selector_name):
6265
"""Return the data for the layer currently selected in a given selector."""
6366
if selector_name in self.layer_selectors:
6467
selector_widget = self.layer_selectors[selector_name]
65-
68+
6669
# Retrieve the QComboBox from the QWidget's layout
6770
image_selector = selector_widget.layout().itemAt(1).widget()
68-
71+
6972
if isinstance(image_selector, QComboBox):
7073
selected_layer_name = image_selector.currentText()
7174
if selected_layer_name in self.viewer.layers:
@@ -176,7 +179,7 @@ def _make_collapsible(self, widget, title):
176179
collapsible.addWidget(widget)
177180
parent_widget.layout().addWidget(collapsible)
178181
return parent_widget
179-
182+
180183
def _add_boolean_param(self, name, value, title=None, tooltip=None):
181184
checkbox = QCheckBox(name if title is None else title)
182185
checkbox.setChecked(value)

synaptic_reconstruction/tools/synaptic_plugin/distance_measure_widget.py renamed to synaptic_reconstruction/tools/distance_measure_widget.py

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
1+
import os
2+
13
import napari
24
import napari.layers
5+
import pandas as pd
6+
37
from napari.utils.notifications import show_info
4-
from qtpy.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QComboBox
8+
from qtpy.QtWidgets import QWidget, QVBoxLayout, QPushButton
59

610
from .base_widget import BaseWidget
7-
8-
# Custom imports for model and prediction utilities
9-
from synaptic_reconstruction import distance_measurements
10-
from ..util import save_to_csv
11+
from .. import distance_measurements
12+
13+
try:
14+
from napari_skimage_regionprops import add_table
15+
except ImportError:
16+
add_table = None
17+
18+
19+
def _save_distance_table(save_path, data):
20+
ext = os.path.splitext(save_path)[1]
21+
if ext == "": # No file extension given, By default we save to CSV.
22+
file_path = f"{save_path}.csv"
23+
data.to_csv(file_path, index=False)
24+
elif ext == ".csv": # Extension was specified as csv
25+
file_path = save_path
26+
data.to_csv(file_path, index=False)
27+
elif ext == ".xlsx": # We also support excel.
28+
file_path = save_path
29+
data.to_excel(file_path, index=False)
30+
else:
31+
raise ValueError("Invalid extension for table: {ext}. We support .csv or .xlsx.")
32+
return file_path
1133

1234

1335
class DistanceMeasureWidget(BaseWidget):
@@ -20,15 +42,16 @@ def __init__(self):
2042
self.image_selector_name1 = "Segmentation 1"
2143
self.image_selector_name2 = "Segmentation 2"
2244
# Create the image selection dropdown
45+
# TODO: update the names to make it easier to distinguish what is what.
2346
self.segmentation1_selector_widget = self._create_layer_selector(self.image_selector_name1, layer_type="Labels")
2447
self.segmentation2_selector_widget = self._create_layer_selector(self.image_selector_name2, layer_type="Labels")
2548

2649
# create save path
2750
self.settings = self._create_settings_widget()
2851

2952
# create buttons
30-
self.measure_pairwise_button = QPushButton('Measure Distance Pairwise')
31-
self.measure_segmentation_to_object_button = QPushButton('Measure Distance Segmentation to Object')
53+
self.measure_pairwise_button = QPushButton("Measure Distance Pairwise")
54+
self.measure_segmentation_to_object_button = QPushButton("Measure Distance Segmentation to Object")
3255

3356
# Connect buttons to functions
3457
self.measure_pairwise_button.clicked.connect(self.on_measure_pairwise)
@@ -50,7 +73,6 @@ def on_measure_segmentation_to_object(self):
5073
if segmentation1_data is None or segmentation2_data is None:
5174
show_info("Please choose both segmentation layers.")
5275
return
53-
# get save_path
5476

5577
(distances,
5678
endpoints1,
@@ -59,18 +81,16 @@ def on_measure_segmentation_to_object(self):
5981
segmentation=segmentation1_data,
6082
segmented_object=segmentation2_data,
6183
distance_type="boundary",
62-
# save_path=self.save_path
6384
)
85+
6486
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-
)
73-
show_info(f"Measurements saved to {file_path}")
87+
data = {"label": seg_ids, "distance": distances}
88+
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+
7494
lines, properties = distance_measurements.create_object_distance_lines(
7595
distances=distances,
7696
endpoints1=endpoints1,
@@ -79,19 +99,23 @@ def on_measure_segmentation_to_object(self):
7999
)
80100

81101
# Add the lines layer
82-
self.viewer.add_shapes(
102+
line_layer = self.viewer.add_shapes(
83103
lines,
84104
name="Distance Lines",
85105
shape_type="line", # Specify the shape type as 'line'
86106
edge_width=2,
87107
edge_color="red",
88108
blending="additive", # Use 'additive' for blending if needed
89109
)
110+
111+
# FIXME: this doesn't work yet
112+
if add_table is not None:
113+
add_table(line_layer, self.viewer)
114+
90115
if self.save_path.text() != "":
91116
show_info(f"Added distance lines and saved file to {file_path}.")
92117
else:
93118
show_info("Added distance lines.")
94-
return
95119

96120
def on_measure_pairwise(self):
97121
if self.image is None:
@@ -120,14 +144,8 @@ def _create_settings_widget(self):
120144
# setting_values.setToolTip(get_tooltip("embedding", "settings"))
121145
setting_values.setLayout(QVBoxLayout())
122146

123-
self.save_path, layout = self._add_path_param(
124-
name="Save Directory", select_type="directory", value=""
125-
)
147+
self.save_path, layout = self._add_path_param(name="Save Table", select_type="file", value="")
126148
setting_values.layout().addLayout(layout)
127149

128150
settings = self._make_collapsible(widget=setting_values, title="Advanced Settings")
129151
return settings
130-
131-
132-
def get_distance_measure_widget():
133-
return DistanceMeasureWidget()

synaptic_reconstruction/tools/synaptic_plugin/segmentation_widget.py renamed to synaptic_reconstruction/tools/segmentation_widget.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
from qtpy.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QComboBox
55

66
from .base_widget import BaseWidget
7-
from synaptic_reconstruction.training.supervised_training import get_2d_model
8-
9-
# Custom imports for model and prediction utilities
10-
from ..util import run_segmentation, get_model_registry, _available_devices
7+
from .util import run_segmentation, get_model_registry, _available_devices
118

129

1310
class SegmentationWidget(BaseWidget):
@@ -23,7 +20,7 @@ def __init__(self):
2320
self.image_selector_widget = self._create_layer_selector(self.image_selector_name, layer_type="Image")
2421

2522
# create buttons
26-
self.predict_button = QPushButton('Run Segmentation')
23+
self.predict_button = QPushButton("Run Segmentation")
2724

2825
# Connect buttons to functions
2926
self.predict_button.clicked.connect(self.on_predict)
@@ -78,6 +75,7 @@ def on_predict(self):
7875
show_info("Please choose an image.")
7976
return
8077

78+
# FIXME: don't hard-code tiling here, but figure it out centrally in the prediction function.
8179
# get tile shape and halo from the viewer
8280
tiling = {
8381
"tile": {
@@ -111,9 +109,6 @@ def on_predict(self):
111109
# Add the segmentation layer
112110
self.viewer.add_labels(segmentation, name=f"{model_key}-segmentation")
113111
show_info(f"Segmentation of {model_key} added to layers.")
114-
# alternatively return the segmentation and layer_kwargs
115-
# layer_kwargs = {"colormap": "inferno", "blending": "additive"}
116-
# return segmentation, layer_kwargs
117112

118113
def _create_settings_widget(self):
119114
setting_values = QWidget()
@@ -142,15 +137,11 @@ def _create_settings_widget(self):
142137
# tooltip=get_tooltip("embedding", "halo")
143138
)
144139
setting_values.layout().addLayout(layout)
145-
140+
146141
self.scale_param, layout = self._add_float_param(
147142
"scale", 0.5, min_val=0.0, max_val=8.0,
148143
)
149144
setting_values.layout().addLayout(layout)
150145

151146
settings = self._make_collapsible(widget=setting_values, title="Advanced Settings")
152147
return settings
153-
154-
155-
def get_segmentation_widget():
156-
return SegmentationWidget()

synaptic_reconstruction/tools/synaptic_plugin/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)