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.
Files changed (61) hide show
  1. AMS_BP/__init__.py +1 -1
  2. AMS_BP/configio/configmodels.py +32 -18
  3. AMS_BP/configio/convertconfig.py +508 -632
  4. AMS_BP/gui/README.md +77 -0
  5. AMS_BP/gui/__init__.py +0 -0
  6. AMS_BP/gui/assets/__init__.py +0 -0
  7. AMS_BP/gui/assets/drawing.svg +107 -0
  8. AMS_BP/gui/configuration_window.py +333 -0
  9. AMS_BP/gui/help_docs/__init__.py +0 -0
  10. AMS_BP/gui/help_docs/cell_help.md +45 -0
  11. AMS_BP/gui/help_docs/channels_help.md +78 -0
  12. AMS_BP/gui/help_docs/condensate_help.md +59 -0
  13. AMS_BP/gui/help_docs/detector_help.md +57 -0
  14. AMS_BP/gui/help_docs/experiment_help.md +92 -0
  15. AMS_BP/gui/help_docs/fluorophore_help.md +128 -0
  16. AMS_BP/gui/help_docs/general_help.md +43 -0
  17. AMS_BP/gui/help_docs/global_help.md +47 -0
  18. AMS_BP/gui/help_docs/laser_help.md +76 -0
  19. AMS_BP/gui/help_docs/molecule_help.md +78 -0
  20. AMS_BP/gui/help_docs/output_help.md +5 -0
  21. AMS_BP/gui/help_docs/psf_help.md +51 -0
  22. AMS_BP/gui/help_window.py +26 -0
  23. AMS_BP/gui/logging_window.py +93 -0
  24. AMS_BP/gui/main.py +255 -0
  25. AMS_BP/gui/sim_worker.py +58 -0
  26. AMS_BP/gui/template_window_selection.py +100 -0
  27. AMS_BP/gui/widgets/__init__.py +0 -0
  28. AMS_BP/gui/widgets/camera_config_widget.py +213 -0
  29. AMS_BP/gui/widgets/cell_config_widget.py +225 -0
  30. AMS_BP/gui/widgets/channel_config_widget.py +307 -0
  31. AMS_BP/gui/widgets/condensate_config_widget.py +341 -0
  32. AMS_BP/gui/widgets/experiment_config_widget.py +259 -0
  33. AMS_BP/gui/widgets/flurophore_config_widget.py +513 -0
  34. AMS_BP/gui/widgets/general_config_widget.py +47 -0
  35. AMS_BP/gui/widgets/global_config_widget.py +142 -0
  36. AMS_BP/gui/widgets/laser_config_widget.py +255 -0
  37. AMS_BP/gui/widgets/molecule_config_widget.py +714 -0
  38. AMS_BP/gui/widgets/output_config_widget.py +61 -0
  39. AMS_BP/gui/widgets/psf_config_widget.py +128 -0
  40. AMS_BP/gui/widgets/utility_widgets/__init__.py +0 -0
  41. AMS_BP/gui/widgets/utility_widgets/scinotation_widget.py +21 -0
  42. AMS_BP/gui/widgets/utility_widgets/spectrum_widget.py +115 -0
  43. AMS_BP/logging/__init__.py +0 -0
  44. AMS_BP/logging/logutil.py +83 -0
  45. AMS_BP/logging/setup_run_directory.py +35 -0
  46. AMS_BP/{run_cell_simulation.py → main_cli.py} +27 -72
  47. AMS_BP/optics/filters/filters.py +2 -0
  48. AMS_BP/resources/template_configs/metadata_configs.json +20 -0
  49. AMS_BP/resources/template_configs/sim_config.toml +408 -0
  50. AMS_BP/resources/template_configs/twocolor_widefield_timeseries_live.toml +399 -0
  51. AMS_BP/resources/template_configs/twocolor_widefield_zstack_fixed.toml +406 -0
  52. AMS_BP/resources/template_configs/twocolor_widefield_zstack_live.toml +408 -0
  53. AMS_BP/run_sim_util.py +76 -0
  54. AMS_BP/sim_microscopy.py +2 -2
  55. {ams_bp-0.3.0.dist-info → ams_bp-0.4.0.dist-info}/METADATA +59 -34
  56. ams_bp-0.4.0.dist-info/RECORD +103 -0
  57. ams_bp-0.4.0.dist-info/entry_points.txt +2 -0
  58. ams_bp-0.3.0.dist-info/RECORD +0 -55
  59. ams_bp-0.3.0.dist-info/entry_points.txt +0 -2
  60. {ams_bp-0.3.0.dist-info → ams_bp-0.4.0.dist-info}/WHEEL +0 -0
  61. {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"