Skip to content

Commit 9581b2b

Browse files
Merge pull request #47 from computational-cell-analytics/45-user-interface
Add first version of user interface
2 parents 74c3e69 + 9622f88 commit 9581b2b

File tree

9 files changed

+872
-1
lines changed

9 files changed

+872
-1
lines changed

environment.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@ dependencies:
88
- pip
99
- pyqt
1010
- magicgui
11+
- pytorch
12+
- bioimageio.core
13+
- kornia
14+
- tensorboard
1115
- pip:
1216
- napari-skimage-regionprops

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"console_scripts": [
1616
"sr_tools.correct_segmentation = synaptic_reconstruction.tools.segmentation_correction:main",
1717
"sr_tools.measure_distances = synaptic_reconstruction.tools.distance_measurement:main",
18-
]
18+
],
19+
"napari.manifest": [
20+
"synaptic_reconstruction = synaptic_reconstruction:napari.yaml",
21+
],
1922
},
2023
)

synaptic_reconstruction/napari.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: synaptic_reconstruction
2+
display_name: Synaptic Reconstruction
3+
# see https://napari.org/stable/plugins/manifest.html for valid categories
4+
categories: ["Image Processing", "Annotation"]
5+
contributions:
6+
commands:
7+
- id: synaptic_reconstruction.segment
8+
python_name: synaptic_reconstruction.tools.synaptic_plugin.segmentation_widget:get_segmentation_widget
9+
title: Segment
10+
- id: synaptic_reconstruction.distance_measure
11+
python_name: synaptic_reconstruction.tools.synaptic_plugin.distance_measure_widget:get_distance_measure_widget
12+
title: Distance Measurement
13+
- id: synaptic_reconstruction.file_reader
14+
title: Read ".mrc, .rec" files
15+
python_name: synaptic_reconstruction.tools.file_reader_plugin.elf_reader:get_reader
16+
17+
readers:
18+
- command: synaptic_reconstruction.file_reader
19+
filename_patterns:
20+
- '*.mrc'
21+
accepts_directories: false
22+
23+
widgets:
24+
- command: synaptic_reconstruction.segment
25+
display_name: Segmentation
26+
- command: synaptic_reconstruction.distance_measure
27+
display_name: Distance Measurement
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from typing import Callable, List, Optional, Sequence, Union
2+
from napari.types import LayerData
3+
from elf.io import open_file
4+
5+
PathLike = str
6+
PathOrPaths = Union[PathLike, Sequence[PathLike]]
7+
ReaderFunction = Callable[[PathOrPaths], List[LayerData]]
8+
9+
10+
def get_reader(path: PathOrPaths) -> Optional[ReaderFunction]:
11+
# If we recognize the format, we return the actual reader function
12+
if isinstance(path, str) and path.endswith(".mrc"):
13+
return elf_read_file
14+
# otherwise we return None.
15+
return None
16+
17+
18+
def elf_read_file(path: PathOrPaths) -> List[LayerData]:
19+
try:
20+
with open_file(path, mode="r") as f:
21+
data = f["data"][:]
22+
layer_attributes = {
23+
"name": "Raw",
24+
"colormap": "gray",
25+
"blending": "additive"
26+
}
27+
return [(data, layer_attributes)]
28+
except Exception as e:
29+
print(f"Failed to read file: {e}")
30+
return

synaptic_reconstruction/tools/synaptic_plugin/__init__.py

Whitespace-only changes.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
from pathlib import Path
2+
import napari
3+
from qtpy.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QLabel, QSpinBox, QLineEdit, QGroupBox, QFormLayout, QFrame, QComboBox, QCheckBox
4+
import qtpy.QtWidgets as QtWidgets
5+
from superqt import QCollapsible
6+
7+
8+
class BaseWidget(QWidget):
9+
def __init__(self):
10+
super().__init__()
11+
self.viewer = napari.current_viewer()
12+
self.attribute_dict = {}
13+
14+
def _add_string_param(self, name, value, title=None, placeholder=None, layout=None, tooltip=None):
15+
if layout is None:
16+
layout = QtWidgets.QHBoxLayout()
17+
label = QtWidgets.QLabel(title or name)
18+
if tooltip:
19+
label.setToolTip(tooltip)
20+
layout.addWidget(label)
21+
param = QtWidgets.QLineEdit()
22+
param.setText(value)
23+
if placeholder is not None:
24+
param.setPlaceholderText(placeholder)
25+
param.textChanged.connect(lambda val: setattr(self, name, val))
26+
if tooltip:
27+
param.setToolTip(tooltip)
28+
layout.addWidget(param)
29+
return param, layout
30+
31+
def _add_float_param(self, name, value, title=None, min_val=0.0, max_val=1.0, decimals=2,
32+
step=0.01, layout=None, tooltip=None):
33+
if layout is None:
34+
layout = QtWidgets.QHBoxLayout()
35+
label = QtWidgets.QLabel(title or name)
36+
if tooltip:
37+
label.setToolTip(tooltip)
38+
layout.addWidget(label)
39+
param = QtWidgets.QDoubleSpinBox()
40+
param.setRange(min_val, max_val)
41+
param.setDecimals(decimals)
42+
param.setValue(value)
43+
param.setSingleStep(step)
44+
param.valueChanged.connect(lambda val: setattr(self, name, val))
45+
if tooltip:
46+
param.setToolTip(tooltip)
47+
layout.addWidget(param)
48+
return param, layout
49+
50+
def _add_int_param(self, name, value, min_val, max_val, title=None, step=1, layout=None, tooltip=None):
51+
if layout is None:
52+
layout = QHBoxLayout()
53+
label = QLabel(title or name)
54+
if tooltip:
55+
label.setToolTip(tooltip)
56+
layout.addWidget(label)
57+
param = QSpinBox()
58+
param.setRange(min_val, max_val)
59+
param.setValue(value)
60+
param.setSingleStep(step)
61+
param.valueChanged.connect(lambda val: setattr(self, name, val))
62+
if tooltip:
63+
param.setToolTip(tooltip)
64+
layout.addWidget(param)
65+
return param, layout
66+
67+
def _add_choice_param(self, name, value, options, title=None, layout=None, update=None, tooltip=None):
68+
if layout is None:
69+
layout = QHBoxLayout()
70+
label = QLabel(title or name)
71+
if tooltip:
72+
label.setToolTip(tooltip)
73+
layout.addWidget(label)
74+
75+
# Create the dropdown menu via QComboBox, set the available values.
76+
dropdown = QComboBox()
77+
dropdown.addItems(options)
78+
if update is None:
79+
dropdown.currentIndexChanged.connect(lambda index: setattr(self, name, options[index]))
80+
else:
81+
dropdown.currentIndexChanged.connect(update)
82+
83+
# Set the correct value for the value.
84+
dropdown.setCurrentIndex(dropdown.findText(value))
85+
86+
if tooltip:
87+
dropdown.setToolTip(tooltip)
88+
89+
layout.addWidget(dropdown)
90+
return dropdown, layout
91+
92+
def _add_shape_param(self, names, values, min_val, max_val, step=1, title=None, tooltip=None):
93+
layout = QHBoxLayout()
94+
95+
x_layout = QVBoxLayout()
96+
x_param, _ = self._add_int_param(
97+
names[0], values[0], min_val=min_val, max_val=max_val, layout=x_layout, step=step,
98+
title=title[0] if title is not None else title, tooltip=tooltip
99+
)
100+
layout.addLayout(x_layout)
101+
102+
y_layout = QVBoxLayout()
103+
y_param, _ = self._add_int_param(
104+
names[1], values[1], min_val=min_val, max_val=max_val, layout=y_layout, step=step,
105+
title=title[1] if title is not None else title, tooltip=tooltip
106+
)
107+
layout.addLayout(y_layout)
108+
109+
return x_param, y_param, layout
110+
111+
def _make_collapsible(self, widget, title):
112+
parent_widget = QWidget()
113+
parent_widget.setLayout(QVBoxLayout())
114+
collapsible = QCollapsible(title, parent_widget)
115+
collapsible.addWidget(widget)
116+
parent_widget.layout().addWidget(collapsible)
117+
return parent_widget
118+
119+
def _add_boolean_param(self, name, value, title=None, tooltip=None):
120+
checkbox = QCheckBox(name if title is None else title)
121+
checkbox.setChecked(value)
122+
checkbox.stateChanged.connect(lambda val: setattr(self, name, val))
123+
if tooltip:
124+
checkbox.setToolTip(tooltip)
125+
return checkbox
126+
127+
def _add_path_param(self, name, value, select_type, title=None, placeholder=None, tooltip=None):
128+
assert select_type in ("directory", "file", "both")
129+
130+
layout = QtWidgets.QHBoxLayout()
131+
label = QtWidgets.QLabel(title or name)
132+
if tooltip:
133+
label.setToolTip(tooltip)
134+
layout.addWidget(label)
135+
136+
path_textbox = QtWidgets.QLineEdit()
137+
path_textbox.setText(str(value))
138+
if placeholder is not None:
139+
path_textbox.setPlaceholderText(placeholder)
140+
path_textbox.textChanged.connect(lambda val: setattr(self, name, val))
141+
if tooltip:
142+
path_textbox.setToolTip(tooltip)
143+
144+
layout.addWidget(path_textbox)
145+
146+
def add_path_button(select_type, tooltip=None):
147+
# Adjust button text.
148+
button_text = f"Select {select_type.capitalize()}"
149+
path_button = QtWidgets.QPushButton(button_text)
150+
151+
# Call appropriate function based on select_type.
152+
path_button.clicked.connect(lambda: getattr(self, f"_get_{select_type}_path")(name, path_textbox))
153+
if tooltip:
154+
path_button.setToolTip(tooltip)
155+
layout.addWidget(path_button)
156+
157+
if select_type == "both":
158+
add_path_button("file")
159+
add_path_button("directory")
160+
161+
else:
162+
add_path_button(select_type)
163+
164+
return path_textbox, layout
165+
166+
def _get_directory_path(self, name, textbox, tooltip=None):
167+
directory = QtWidgets.QFileDialog.getExistingDirectory(
168+
self, "Select Directory", "", QtWidgets.QFileDialog.ShowDirsOnly
169+
)
170+
if tooltip:
171+
directory.setToolTip(tooltip)
172+
if directory and Path(directory).is_dir():
173+
textbox.setText(str(directory))
174+
else:
175+
# Handle the case where the selected path is not a directory
176+
print("Invalid directory selected. Please try again.")
177+
178+
def _get_file_path(self, name, textbox, tooltip=None):
179+
file_path, _ = QtWidgets.QFileDialog.getOpenFileName(
180+
self, "Select File", "", "All Files (*)"
181+
)
182+
if tooltip:
183+
file_path.setToolTip(tooltip)
184+
if file_path and Path(file_path).is_file():
185+
textbox.setText(str(file_path))
186+
else:
187+
# Handle the case where the selected path is not a file
188+
print("Invalid file selected. Please try again.")

0 commit comments

Comments
 (0)