AMS-BP 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- AMS_BP/__init__.py +1 -1
- AMS_BP/configio/configmodels.py +26 -12
- AMS_BP/configio/convertconfig.py +508 -632
- AMS_BP/gui/README.md +77 -0
- AMS_BP/gui/__init__.py +0 -0
- AMS_BP/gui/assets/__init__.py +0 -0
- AMS_BP/gui/assets/drawing.svg +107 -0
- AMS_BP/gui/configuration_window.py +333 -0
- AMS_BP/gui/help_docs/__init__.py +0 -0
- AMS_BP/gui/help_docs/cell_help.md +45 -0
- AMS_BP/gui/help_docs/channels_help.md +78 -0
- AMS_BP/gui/help_docs/condensate_help.md +59 -0
- AMS_BP/gui/help_docs/detector_help.md +57 -0
- AMS_BP/gui/help_docs/experiment_help.md +92 -0
- AMS_BP/gui/help_docs/fluorophore_help.md +128 -0
- AMS_BP/gui/help_docs/general_help.md +43 -0
- AMS_BP/gui/help_docs/global_help.md +47 -0
- AMS_BP/gui/help_docs/laser_help.md +76 -0
- AMS_BP/gui/help_docs/molecule_help.md +78 -0
- AMS_BP/gui/help_docs/output_help.md +5 -0
- AMS_BP/gui/help_docs/psf_help.md +51 -0
- AMS_BP/gui/help_window.py +26 -0
- AMS_BP/gui/logging_window.py +93 -0
- AMS_BP/gui/main.py +255 -0
- AMS_BP/gui/sim_worker.py +58 -0
- AMS_BP/gui/template_window_selection.py +100 -0
- AMS_BP/gui/widgets/__init__.py +0 -0
- AMS_BP/gui/widgets/camera_config_widget.py +213 -0
- AMS_BP/gui/widgets/cell_config_widget.py +225 -0
- AMS_BP/gui/widgets/channel_config_widget.py +307 -0
- AMS_BP/gui/widgets/condensate_config_widget.py +341 -0
- AMS_BP/gui/widgets/experiment_config_widget.py +259 -0
- AMS_BP/gui/widgets/flurophore_config_widget.py +513 -0
- AMS_BP/gui/widgets/general_config_widget.py +47 -0
- AMS_BP/gui/widgets/global_config_widget.py +142 -0
- AMS_BP/gui/widgets/laser_config_widget.py +255 -0
- AMS_BP/gui/widgets/molecule_config_widget.py +714 -0
- AMS_BP/gui/widgets/output_config_widget.py +61 -0
- AMS_BP/gui/widgets/psf_config_widget.py +128 -0
- AMS_BP/gui/widgets/utility_widgets/__init__.py +0 -0
- AMS_BP/gui/widgets/utility_widgets/scinotation_widget.py +21 -0
- AMS_BP/gui/widgets/utility_widgets/spectrum_widget.py +115 -0
- AMS_BP/logging/__init__.py +0 -0
- AMS_BP/logging/logutil.py +83 -0
- AMS_BP/logging/setup_run_directory.py +35 -0
- AMS_BP/{run_cell_simulation.py → main_cli.py} +27 -72
- AMS_BP/optics/filters/filters.py +2 -0
- AMS_BP/resources/template_configs/metadata_configs.json +20 -0
- AMS_BP/resources/template_configs/sim_config.toml +408 -0
- AMS_BP/resources/template_configs/twocolor_widefield_timeseries_live.toml +399 -0
- AMS_BP/resources/template_configs/twocolor_widefield_zstack_fixed.toml +406 -0
- AMS_BP/resources/template_configs/twocolor_widefield_zstack_live.toml +408 -0
- AMS_BP/run_sim_util.py +76 -0
- {ams_bp-0.3.1.dist-info → ams_bp-0.4.0.dist-info}/METADATA +46 -27
- ams_bp-0.4.0.dist-info/RECORD +103 -0
- ams_bp-0.4.0.dist-info/entry_points.txt +2 -0
- ams_bp-0.3.1.dist-info/RECORD +0 -55
- ams_bp-0.3.1.dist-info/entry_points.txt +0 -2
- {ams_bp-0.3.1.dist-info → ams_bp-0.4.0.dist-info}/WHEEL +0 -0
- {ams_bp-0.3.1.dist-info → ams_bp-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,255 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from PyQt6.QtCore import pyqtSignal
|
4
|
+
from PyQt6.QtWidgets import (
|
5
|
+
QComboBox,
|
6
|
+
QDoubleSpinBox,
|
7
|
+
QFormLayout,
|
8
|
+
QLineEdit,
|
9
|
+
QMessageBox,
|
10
|
+
QPushButton,
|
11
|
+
QSpinBox,
|
12
|
+
QTabWidget,
|
13
|
+
QVBoxLayout,
|
14
|
+
QWidget,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
class LaserConfigWidget(QWidget):
|
19
|
+
laser_names_updated = pyqtSignal(list)
|
20
|
+
|
21
|
+
def __init__(self):
|
22
|
+
super().__init__()
|
23
|
+
self.laser_name_widgets = []
|
24
|
+
layout = QVBoxLayout()
|
25
|
+
form = QFormLayout()
|
26
|
+
|
27
|
+
self.validate_button = QPushButton("Validate")
|
28
|
+
self.validate_button.clicked.connect(self.validate)
|
29
|
+
layout.addWidget(self.validate_button)
|
30
|
+
|
31
|
+
self.num_lasers = QSpinBox()
|
32
|
+
self.num_lasers.setRange(1, 10)
|
33
|
+
self.num_lasers.setValue(2)
|
34
|
+
self.num_lasers.valueChanged.connect(self.update_laser_tabs)
|
35
|
+
form.addRow("Number of Lasers:", self.num_lasers)
|
36
|
+
|
37
|
+
self.laser_widgets = []
|
38
|
+
|
39
|
+
self.laser_tabs = QTabWidget()
|
40
|
+
self.update_laser_tabs()
|
41
|
+
self.emit_active_lasers()
|
42
|
+
|
43
|
+
layout.addLayout(form)
|
44
|
+
layout.addWidget(self.laser_tabs)
|
45
|
+
self.setLayout(layout)
|
46
|
+
|
47
|
+
def set_confocal_mode(self, enabled: bool):
|
48
|
+
for i in range(self.laser_tabs.count()):
|
49
|
+
tab = self.laser_tabs.widget(i)
|
50
|
+
|
51
|
+
laser_type: QComboBox = tab.findChild(QComboBox)
|
52
|
+
beam_width: QDoubleSpinBox = tab.findChildren(QDoubleSpinBox)[
|
53
|
+
1
|
54
|
+
] # Assumes 2nd spinbox is beam_width
|
55
|
+
|
56
|
+
if enabled:
|
57
|
+
# Force laser type to "gaussian" and disable editing
|
58
|
+
laser_type.setCurrentText("gaussian")
|
59
|
+
laser_type.setEnabled(False)
|
60
|
+
|
61
|
+
# Hide beam width
|
62
|
+
beam_width.hide()
|
63
|
+
label = tab.layout().labelForField(beam_width)
|
64
|
+
if label:
|
65
|
+
label.hide()
|
66
|
+
else:
|
67
|
+
# Enable laser type editing
|
68
|
+
laser_type.setEnabled(True)
|
69
|
+
|
70
|
+
# Show beam width
|
71
|
+
beam_width.show()
|
72
|
+
label = tab.layout().labelForField(beam_width)
|
73
|
+
if label:
|
74
|
+
label.show()
|
75
|
+
|
76
|
+
def validate(self) -> bool:
|
77
|
+
try:
|
78
|
+
from ...configio.convertconfig import create_lasers_from_config
|
79
|
+
|
80
|
+
data = self.get_data()
|
81
|
+
create_lasers_from_config({"lasers": data})
|
82
|
+
|
83
|
+
QMessageBox.information(
|
84
|
+
self, "Validation Successful", "Laser parameters are valid."
|
85
|
+
)
|
86
|
+
return True
|
87
|
+
|
88
|
+
except ValueError as e:
|
89
|
+
QMessageBox.critical(self, "Validation Error", str(e))
|
90
|
+
return False
|
91
|
+
except Exception as e:
|
92
|
+
QMessageBox.critical(self, "Unexpected Error", str(e))
|
93
|
+
return False
|
94
|
+
|
95
|
+
def emit_active_lasers(self):
|
96
|
+
names = []
|
97
|
+
for i in range(self.laser_tabs.count()):
|
98
|
+
tab = self.laser_tabs.widget(i)
|
99
|
+
name_field = tab.findChild(QLineEdit)
|
100
|
+
if name_field:
|
101
|
+
names.append(name_field.text())
|
102
|
+
self.laser_names_updated.emit([n for n in names if n])
|
103
|
+
|
104
|
+
def update_laser_tabs(self):
|
105
|
+
self.laser_tabs.clear()
|
106
|
+
self.laser_widgets = []
|
107
|
+
|
108
|
+
for i in range(self.num_lasers.value()):
|
109
|
+
self.add_laser_tab(i)
|
110
|
+
|
111
|
+
self.emit_active_lasers()
|
112
|
+
|
113
|
+
def add_laser_tab(self, index):
|
114
|
+
tab = QWidget()
|
115
|
+
layout = QFormLayout()
|
116
|
+
|
117
|
+
# Create widgets
|
118
|
+
laser_name = QLineEdit()
|
119
|
+
laser_type = QComboBox()
|
120
|
+
laser_type.addItems(["widefield", "gaussian", "hilo"])
|
121
|
+
laser_preset = QLineEdit()
|
122
|
+
power = QDoubleSpinBox()
|
123
|
+
power.setRange(0, 100000)
|
124
|
+
wavelength = QSpinBox()
|
125
|
+
wavelength.setRange(100, 10000)
|
126
|
+
beam_width = QDoubleSpinBox()
|
127
|
+
beam_width.setRange(0, 1000)
|
128
|
+
numerical_aperture = QDoubleSpinBox()
|
129
|
+
numerical_aperture.setRange(0, 2)
|
130
|
+
refractive_index = QDoubleSpinBox()
|
131
|
+
refractive_index.setRange(1, 2)
|
132
|
+
inclination_angle = QDoubleSpinBox()
|
133
|
+
inclination_angle.setRange(0, 90)
|
134
|
+
inclination_angle.setEnabled(False)
|
135
|
+
|
136
|
+
# Form layout
|
137
|
+
layout.addRow(f"Laser {index + 1} Name:", laser_name)
|
138
|
+
layout.addRow(f"Laser {index + 1} Type:", laser_type)
|
139
|
+
layout.addRow(f"Laser {index + 1} Preset:", laser_preset)
|
140
|
+
layout.addRow(f"Laser {index + 1} Power (W):", power)
|
141
|
+
layout.addRow(f"Laser {index + 1} Wavelength (nm):", wavelength)
|
142
|
+
layout.addRow(f"Laser {index + 1} Beam Width (µm):", beam_width)
|
143
|
+
layout.addRow(f"Laser {index + 1} Numerical Aperture:", numerical_aperture)
|
144
|
+
layout.addRow(f"Laser {index + 1} Refractive Index:", refractive_index)
|
145
|
+
layout.addRow(f"Laser {index + 1} Inclination Angle (°):", inclination_angle)
|
146
|
+
|
147
|
+
# Logic for hilo
|
148
|
+
laser_type.currentTextChanged.connect(
|
149
|
+
lambda val: inclination_angle.setEnabled(val == "hilo")
|
150
|
+
)
|
151
|
+
inclination_angle.setEnabled(laser_type.currentText() == "hilo")
|
152
|
+
|
153
|
+
tab.setLayout(layout)
|
154
|
+
self.laser_tabs.addTab(tab, f"Laser {index + 1}")
|
155
|
+
|
156
|
+
# Track widgets
|
157
|
+
self.laser_widgets.append(
|
158
|
+
{
|
159
|
+
"name": laser_name,
|
160
|
+
"type": laser_type,
|
161
|
+
"preset": laser_preset,
|
162
|
+
"power": power,
|
163
|
+
"wavelength": wavelength,
|
164
|
+
"beam_width": beam_width,
|
165
|
+
"numerical_aperture": numerical_aperture,
|
166
|
+
"refractive_index": refractive_index,
|
167
|
+
"inclination_angle": inclination_angle,
|
168
|
+
}
|
169
|
+
)
|
170
|
+
|
171
|
+
# Update signal
|
172
|
+
laser_name.textChanged.connect(self.emit_active_lasers)
|
173
|
+
|
174
|
+
def toggle_inclination(self, laser_type, inclination_angle):
|
175
|
+
"""Enable or disable inclination angle field based on laser type."""
|
176
|
+
if laser_type.currentText() == "hilo":
|
177
|
+
inclination_angle.setEnabled(True)
|
178
|
+
else:
|
179
|
+
inclination_angle.setEnabled(False)
|
180
|
+
|
181
|
+
def set_data(self, data: dict):
|
182
|
+
# Determine how many lasers we need to configure
|
183
|
+
active_names = data.get("active", [])
|
184
|
+
self.num_lasers.setValue(len(active_names))
|
185
|
+
self.update_laser_tabs()
|
186
|
+
|
187
|
+
for i, name in enumerate(active_names):
|
188
|
+
if i >= len(self.laser_widgets):
|
189
|
+
break # Extra safety
|
190
|
+
|
191
|
+
widgets = self.laser_widgets[i]
|
192
|
+
widgets["name"].setText(name)
|
193
|
+
|
194
|
+
laser_info = data.get(name, {})
|
195
|
+
widgets["type"].setCurrentText(laser_info.get("type", "widefield"))
|
196
|
+
widgets["preset"].setText(laser_info.get("preset", ""))
|
197
|
+
|
198
|
+
params = laser_info.get("parameters", {})
|
199
|
+
|
200
|
+
widgets["power"].setValue(params.get("power", 0.0))
|
201
|
+
widgets["wavelength"].setValue(params.get("wavelength", 488))
|
202
|
+
widgets["beam_width"].setValue(params.get("beam_width", 1.0))
|
203
|
+
widgets["numerical_aperture"].setValue(
|
204
|
+
params.get("numerical_aperture", 1.4)
|
205
|
+
)
|
206
|
+
widgets["refractive_index"].setValue(params.get("refractive_index", 1.518))
|
207
|
+
|
208
|
+
if laser_info.get("type") == "hilo":
|
209
|
+
widgets["inclination_angle"].setValue(
|
210
|
+
params.get("inclination_angle", 45.0)
|
211
|
+
)
|
212
|
+
widgets["inclination_angle"].setEnabled(True)
|
213
|
+
else:
|
214
|
+
widgets["inclination_angle"].setEnabled(False)
|
215
|
+
|
216
|
+
self.emit_active_lasers()
|
217
|
+
|
218
|
+
def get_data(self) -> dict:
|
219
|
+
lasers_section = {}
|
220
|
+
active_names = []
|
221
|
+
|
222
|
+
for widgets in self.laser_widgets:
|
223
|
+
name = widgets["name"].text().strip()
|
224
|
+
if not name:
|
225
|
+
raise ValueError("All lasers must have a name.")
|
226
|
+
|
227
|
+
active_names.append(name)
|
228
|
+
|
229
|
+
laser_type = widgets["type"].currentText()
|
230
|
+
preset = widgets["preset"].text().strip()
|
231
|
+
|
232
|
+
params = {
|
233
|
+
"power": widgets["power"].value(),
|
234
|
+
"wavelength": widgets["wavelength"].value(),
|
235
|
+
"beam_width": widgets["beam_width"].value(),
|
236
|
+
"numerical_aperture": widgets["numerical_aperture"].value(),
|
237
|
+
"refractive_index": widgets["refractive_index"].value(),
|
238
|
+
}
|
239
|
+
|
240
|
+
if laser_type == "hilo":
|
241
|
+
params["inclination_angle"] = widgets["inclination_angle"].value()
|
242
|
+
|
243
|
+
lasers_section[name] = {
|
244
|
+
"type": laser_type,
|
245
|
+
"preset": preset,
|
246
|
+
"parameters": params,
|
247
|
+
}
|
248
|
+
|
249
|
+
return {
|
250
|
+
"active": active_names,
|
251
|
+
**lasers_section,
|
252
|
+
}
|
253
|
+
|
254
|
+
def get_help_path(self) -> Path:
|
255
|
+
return Path(__file__).parent.parent / "help_docs" / "laser_help.md"
|