AMS-BP 0.3.0__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 +32 -18
- 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/sim_microscopy.py +2 -2
- {ams_bp-0.3.0.dist-info → ams_bp-0.4.0.dist-info}/METADATA +59 -34
- 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.0.dist-info/RECORD +0 -55
- ams_bp-0.3.0.dist-info/entry_points.txt +0 -2
- {ams_bp-0.3.0.dist-info → ams_bp-0.4.0.dist-info}/WHEEL +0 -0
- {ams_bp-0.3.0.dist-info → ams_bp-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,259 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
from PyQt6.QtWidgets import (
|
5
|
+
QComboBox,
|
6
|
+
QDoubleSpinBox,
|
7
|
+
QFormLayout,
|
8
|
+
QHBoxLayout,
|
9
|
+
QLabel,
|
10
|
+
QLineEdit,
|
11
|
+
QMessageBox,
|
12
|
+
QPushButton,
|
13
|
+
QSpinBox,
|
14
|
+
QTabWidget,
|
15
|
+
QVBoxLayout,
|
16
|
+
QWidget,
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
class ExperimentConfigWidget(QWidget):
|
21
|
+
def __init__(self):
|
22
|
+
super().__init__()
|
23
|
+
|
24
|
+
self.laser_power_widgets = {}
|
25
|
+
self.laser_position_widgets = {}
|
26
|
+
|
27
|
+
layout = QVBoxLayout()
|
28
|
+
form = QFormLayout()
|
29
|
+
|
30
|
+
# Experiment Info
|
31
|
+
self.name_field = QLineEdit()
|
32
|
+
form.addRow("Experiment Name:", self.name_field)
|
33
|
+
|
34
|
+
self.desc_field = QLineEdit()
|
35
|
+
form.addRow("Description:", self.desc_field)
|
36
|
+
|
37
|
+
self.type_field = QComboBox()
|
38
|
+
self.type_field.addItems(["time-series", "z-stack"])
|
39
|
+
form.addRow("Experiment Type:", self.type_field)
|
40
|
+
|
41
|
+
# Z Position (just one for time-series)
|
42
|
+
self.z_position_inputs: List[QDoubleSpinBox] = []
|
43
|
+
|
44
|
+
self.z_position_container = QWidget()
|
45
|
+
self.z_position_layout = QVBoxLayout()
|
46
|
+
self.z_position_container.setLayout(self.z_position_layout)
|
47
|
+
form.addRow("Z Position(s):", self.z_position_container)
|
48
|
+
|
49
|
+
self.add_z_button = QPushButton("Add Z-Position")
|
50
|
+
self.remove_z_button = QPushButton("Remove Z-Position")
|
51
|
+
self.remove_z_button.clicked.connect(self.remove_z_position_field)
|
52
|
+
self.add_z_button.clicked.connect(self.add_z_position_field)
|
53
|
+
self.type_field.currentTextChanged.connect(self.update_z_position_mode)
|
54
|
+
z_button_row = QHBoxLayout()
|
55
|
+
z_button_row.addWidget(self.add_z_button)
|
56
|
+
z_button_row.addWidget(self.remove_z_button)
|
57
|
+
layout.addLayout(z_button_row)
|
58
|
+
# XY Offset
|
59
|
+
self.xyoffset = [QDoubleSpinBox() for _ in range(2)]
|
60
|
+
for box in self.xyoffset:
|
61
|
+
box.setRange(-1e5, 1e5)
|
62
|
+
form.addRow("XY Offset (x, y):", self._hbox(self.xyoffset))
|
63
|
+
|
64
|
+
# Exposure and Interval wrapped in a QWidget
|
65
|
+
self.timing_widget = QWidget()
|
66
|
+
timing_layout = QFormLayout(self.timing_widget)
|
67
|
+
|
68
|
+
self.exposure = QSpinBox()
|
69
|
+
self.exposure.setRange(0, 100000000)
|
70
|
+
timing_layout.addRow("Exposure Time (ms):", self.exposure)
|
71
|
+
|
72
|
+
self.interval = QSpinBox()
|
73
|
+
self.interval.setRange(0, 100000000)
|
74
|
+
timing_layout.addRow("Interval Time (ms):", self.interval)
|
75
|
+
|
76
|
+
form.addRow(self.timing_widget)
|
77
|
+
|
78
|
+
self.update_z_position_mode(self.type_field.currentText())
|
79
|
+
layout.addLayout(form)
|
80
|
+
|
81
|
+
# Laser Tabs
|
82
|
+
self.laser_tabs = QTabWidget()
|
83
|
+
layout.addWidget(QLabel("Active Laser Parameters:"))
|
84
|
+
layout.addWidget(self.laser_tabs)
|
85
|
+
|
86
|
+
# Validate Button
|
87
|
+
self.validate_button = QPushButton("Validate")
|
88
|
+
self.validate_button.clicked.connect(self.validate)
|
89
|
+
layout.addWidget(self.validate_button)
|
90
|
+
|
91
|
+
self.setLayout(layout)
|
92
|
+
|
93
|
+
def update_z_position_mode(self, mode: str):
|
94
|
+
# Clear existing
|
95
|
+
for i in reversed(range(self.z_position_layout.count())):
|
96
|
+
item = self.z_position_layout.itemAt(i).widget()
|
97
|
+
if item:
|
98
|
+
item.setParent(None)
|
99
|
+
self.z_position_inputs.clear()
|
100
|
+
|
101
|
+
if mode == "time-series":
|
102
|
+
# One input only
|
103
|
+
z_input = QDoubleSpinBox()
|
104
|
+
z_input.setRange(-1e5, 1e5)
|
105
|
+
self.z_position_inputs.append(z_input)
|
106
|
+
self.z_position_layout.addWidget(z_input)
|
107
|
+
self.add_z_button.setVisible(False)
|
108
|
+
self.add_z_button.setVisible(False)
|
109
|
+
self.remove_z_button.setVisible(False)
|
110
|
+
self.timing_widget.setVisible(False)
|
111
|
+
else:
|
112
|
+
# Start with two for z-stack
|
113
|
+
for _ in range(2):
|
114
|
+
self.add_z_position_field()
|
115
|
+
self.add_z_button.setVisible(True)
|
116
|
+
self.add_z_button.setVisible(True)
|
117
|
+
self.remove_z_button.setVisible(True)
|
118
|
+
self.timing_widget.setVisible(True)
|
119
|
+
|
120
|
+
def remove_z_position_field(self):
|
121
|
+
if len(self.z_position_inputs) > 1:
|
122
|
+
z_widget = self.z_position_inputs.pop()
|
123
|
+
self.z_position_layout.removeWidget(z_widget)
|
124
|
+
z_widget.setParent(None)
|
125
|
+
else:
|
126
|
+
QMessageBox.warning(
|
127
|
+
self, "Cannot Remove", "At least one Z-position is required."
|
128
|
+
)
|
129
|
+
|
130
|
+
def add_z_position_field(self):
|
131
|
+
z_input = QDoubleSpinBox()
|
132
|
+
z_input.setRange(-1e5, 1e5)
|
133
|
+
self.z_position_inputs.append(z_input)
|
134
|
+
self.z_position_layout.addWidget(z_input)
|
135
|
+
|
136
|
+
def _hbox(self, widgets):
|
137
|
+
box = QHBoxLayout()
|
138
|
+
for w in widgets:
|
139
|
+
box.addWidget(w)
|
140
|
+
container = QWidget()
|
141
|
+
container.setLayout(box)
|
142
|
+
return container
|
143
|
+
|
144
|
+
def set_active_lasers(self, laser_names: List[str], powers=None, positions=None):
|
145
|
+
self.laser_tabs.clear()
|
146
|
+
self.laser_power_widgets.clear()
|
147
|
+
self.laser_position_widgets.clear()
|
148
|
+
|
149
|
+
powers = powers or [0.0] * len(laser_names)
|
150
|
+
positions = positions or [[0.0, 0.0, 0.0] for _ in laser_names]
|
151
|
+
|
152
|
+
for i, name in enumerate(laser_names):
|
153
|
+
tab = QWidget()
|
154
|
+
form = QFormLayout()
|
155
|
+
|
156
|
+
# Power
|
157
|
+
power = QDoubleSpinBox()
|
158
|
+
power.setValue(powers[i] if i < len(powers) else 0.0)
|
159
|
+
form.addRow("Power (W):", power)
|
160
|
+
|
161
|
+
# Position
|
162
|
+
pos_spins = [QDoubleSpinBox() for _ in range(3)]
|
163
|
+
for j, s in enumerate(pos_spins):
|
164
|
+
s.setValue(
|
165
|
+
positions[i][j]
|
166
|
+
if i < len(positions) and j < len(positions[i])
|
167
|
+
else 0.0
|
168
|
+
)
|
169
|
+
form.addRow("Position (x, y, z):", self._hbox(pos_spins))
|
170
|
+
|
171
|
+
tab.setLayout(form)
|
172
|
+
self.laser_tabs.addTab(tab, name)
|
173
|
+
|
174
|
+
self.laser_power_widgets[name] = power
|
175
|
+
self.laser_position_widgets[name] = pos_spins
|
176
|
+
|
177
|
+
def get_data(self):
|
178
|
+
data = {
|
179
|
+
"name": self.name_field.text(),
|
180
|
+
"description": self.desc_field.text(),
|
181
|
+
"experiment_type": self.type_field.currentText(),
|
182
|
+
"z_position": [z.value() for z in self.z_position_inputs],
|
183
|
+
"laser_names_active": list(self.laser_power_widgets.keys()),
|
184
|
+
"laser_powers_active": [
|
185
|
+
w.value() for w in self.laser_power_widgets.values()
|
186
|
+
],
|
187
|
+
"laser_positions_active": [
|
188
|
+
[w.value() for w in self.laser_position_widgets[name]]
|
189
|
+
for name in self.laser_position_widgets
|
190
|
+
],
|
191
|
+
"xyoffset": [w.value() for w in self.xyoffset],
|
192
|
+
}
|
193
|
+
|
194
|
+
if data["experiment_type"] == "z-stack":
|
195
|
+
data["exposure_time"] = self.exposure.value()
|
196
|
+
data["interval_time"] = self.interval.value()
|
197
|
+
|
198
|
+
return data
|
199
|
+
|
200
|
+
def set_data(self, data: dict):
|
201
|
+
self.name_field.setText(data.get("name", ""))
|
202
|
+
self.desc_field.setText(data.get("description", ""))
|
203
|
+
|
204
|
+
experiment_type = data.get("experiment_type", "time-series")
|
205
|
+
idx = self.type_field.findText(experiment_type)
|
206
|
+
if idx >= 0:
|
207
|
+
self.type_field.setCurrentIndex(idx)
|
208
|
+
|
209
|
+
self.update_z_position_mode(experiment_type)
|
210
|
+
z_positions = data.get("z_position", [])
|
211
|
+
if experiment_type == "time-series" and z_positions:
|
212
|
+
self.z_position_inputs[0].setValue(z_positions[0])
|
213
|
+
elif experiment_type == "z-stack":
|
214
|
+
for i in range(len(self.z_position_inputs), len(z_positions)):
|
215
|
+
self.add_z_position_field()
|
216
|
+
for i, val in enumerate(z_positions):
|
217
|
+
if i < len(self.z_position_inputs):
|
218
|
+
self.z_position_inputs[i].setValue(val)
|
219
|
+
|
220
|
+
# XY Offset
|
221
|
+
xyoffset = data.get("xyoffset", [0, 0])
|
222
|
+
for i in range(min(2, len(xyoffset))):
|
223
|
+
self.xyoffset[i].setValue(xyoffset[i])
|
224
|
+
|
225
|
+
# Exposure and Interval only for z-stack
|
226
|
+
if experiment_type == "z-stack":
|
227
|
+
self.exposure.setValue(data.get("exposure_time", 100))
|
228
|
+
self.interval.setValue(data.get("interval_time", 0))
|
229
|
+
else:
|
230
|
+
self.exposure.setValue(0)
|
231
|
+
self.interval.setValue(0)
|
232
|
+
|
233
|
+
# Laser data in one call
|
234
|
+
laser_names = data.get("laser_names_active", [])
|
235
|
+
powers = data.get("laser_powers_active", [])
|
236
|
+
positions = data.get("laser_positions_active", [])
|
237
|
+
self.set_active_lasers(laser_names, powers=powers, positions=positions)
|
238
|
+
|
239
|
+
def validate(self) -> bool:
|
240
|
+
try:
|
241
|
+
from ...configio.convertconfig import create_experiment_from_config
|
242
|
+
|
243
|
+
data = self.get_data()
|
244
|
+
config_dict = {"experiment": data}
|
245
|
+
|
246
|
+
# This function will raise if the dataclass constructor or __post_init__ fails
|
247
|
+
configEXP, funcEXP = create_experiment_from_config(config_dict)
|
248
|
+
|
249
|
+
QMessageBox.information(
|
250
|
+
self, "Validation Successful", "Experiment parameters are valid."
|
251
|
+
)
|
252
|
+
return True
|
253
|
+
|
254
|
+
except Exception as e:
|
255
|
+
QMessageBox.critical(self, "Validation Error", str(e))
|
256
|
+
return False
|
257
|
+
|
258
|
+
def get_help_path(self) -> Path:
|
259
|
+
return Path(__file__).parent.parent / "help_docs" / "experiment_help.md"
|