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.
Files changed (60) hide show
  1. AMS_BP/__init__.py +1 -1
  2. AMS_BP/configio/configmodels.py +26 -12
  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-0.3.1.dist-info → ams_bp-0.4.0.dist-info}/METADATA +46 -27
  55. ams_bp-0.4.0.dist-info/RECORD +103 -0
  56. ams_bp-0.4.0.dist-info/entry_points.txt +2 -0
  57. ams_bp-0.3.1.dist-info/RECORD +0 -55
  58. ams_bp-0.3.1.dist-info/entry_points.txt +0 -2
  59. {ams_bp-0.3.1.dist-info → ams_bp-0.4.0.dist-info}/WHEEL +0 -0
  60. {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"