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,213 @@
1
+ from pathlib import Path
2
+
3
+ from pydantic import ValidationError
4
+ from PyQt6.QtWidgets import (
5
+ QComboBox,
6
+ QDoubleSpinBox,
7
+ QFormLayout,
8
+ QHBoxLayout,
9
+ QMessageBox,
10
+ QPushButton,
11
+ QSpinBox,
12
+ QVBoxLayout,
13
+ QWidget,
14
+ )
15
+
16
+ from .utility_widgets.spectrum_widget import SpectrumEditorDialog
17
+
18
+
19
+ class CameraConfigWidget(QWidget):
20
+ def __init__(self):
21
+ super().__init__()
22
+ layout = QVBoxLayout()
23
+ form = QFormLayout()
24
+
25
+ self.validate_button = QPushButton("Validate")
26
+ self.validate_button.clicked.connect(self.validate)
27
+ layout.addWidget(self.validate_button)
28
+
29
+ # Camera type (Only "CMOS" is available)
30
+ self.camera_type = QComboBox()
31
+ self.camera_type.addItems(["CMOS"])
32
+ form.addRow("Camera Type:", self.camera_type)
33
+
34
+ # Pixel count (width and height)
35
+ self.pixel_width = QSpinBox()
36
+ self.pixel_width.setRange(1, 10000)
37
+ self.pixel_height = QSpinBox()
38
+ self.pixel_height.setRange(1, 10000)
39
+ form.addRow(
40
+ "Pixel Count (Width x Height):",
41
+ self._hbox([self.pixel_width, self.pixel_height]),
42
+ )
43
+
44
+ # Pixel detector size
45
+ self.pixel_detector_size = QDoubleSpinBox()
46
+ self.pixel_detector_size.setRange(0, 100)
47
+ form.addRow("Pixel Detector Size (µm):", self.pixel_detector_size)
48
+
49
+ # Magnification
50
+ self.magnification = QSpinBox()
51
+ self.magnification.setRange(1, 100)
52
+ form.addRow("Magnification:", self.magnification)
53
+
54
+ # Dark current
55
+ self.dark_current = QDoubleSpinBox()
56
+ self.dark_current.setRange(0, 1000)
57
+ form.addRow("Dark Current (electrons/pixel/sec):", self.dark_current)
58
+
59
+ # Readout noise
60
+ self.readout_noise = QDoubleSpinBox()
61
+ self.readout_noise.setRange(0, 1000)
62
+ form.addRow("Readout Noise (electrons RMS):", self.readout_noise)
63
+
64
+ # Bit depth
65
+ self.bit_depth = QSpinBox()
66
+ self.bit_depth.setRange(8, 16)
67
+ form.addRow("Bit Depth:", self.bit_depth)
68
+
69
+ # Sensitivity
70
+ self.sensitivity = QDoubleSpinBox()
71
+ self.sensitivity.setRange(0, 100)
72
+ form.addRow("Sensitivity (electrons/ADU):", self.sensitivity)
73
+
74
+ # Base ADU
75
+ self.base_adu = QSpinBox()
76
+ self.base_adu.setRange(0, 65535)
77
+ form.addRow("Base ADU:", self.base_adu)
78
+
79
+ # Binning size
80
+ self.binning_size = QSpinBox()
81
+ self.binning_size.setRange(1, 10)
82
+ form.addRow("Binning Size:", self.binning_size)
83
+
84
+ # Quantum efficiency
85
+ self.quantum_efficiency_button = QPushButton("Edit Quantum Efficiency")
86
+ self.quantum_efficiency_button.clicked.connect(self.edit_quantum_efficiency)
87
+ form.addRow("Quantum Efficiency:", self.quantum_efficiency_button)
88
+
89
+ layout.addLayout(form)
90
+ self.setLayout(layout)
91
+
92
+ def _hbox(self, widgets):
93
+ box = QHBoxLayout()
94
+ for w in widgets:
95
+ box.addWidget(w)
96
+ container = QWidget()
97
+ container.setLayout(box)
98
+ return container
99
+
100
+ def edit_quantum_efficiency(self):
101
+ # Open the SpectrumEditorDialog for editing quantum efficiency
102
+ dialog = SpectrumEditorDialog(
103
+ parent=self,
104
+ wavelengths=[], # Pass an empty list or preloaded wavelengths
105
+ intensities=[], # Pass an empty list or preloaded intensities
106
+ intensity_name="QE",
107
+ )
108
+
109
+ if dialog.exec():
110
+ # Handle the updated quantum efficiency data (wavelengths, intensities)
111
+ self.quantum_efficiency_data = {
112
+ "wavelengths": dialog.wavelengths,
113
+ "quantum_efficiency": dialog.intensities,
114
+ }
115
+ # You can now use this data wherever needed, e.g., saving or validation
116
+
117
+ def validate(self) -> bool:
118
+ from ...configio.convertconfig import create_quantum_efficiency_from_config
119
+ from ...optics.camera.detectors import (
120
+ CMOSDetector,
121
+ )
122
+
123
+ try:
124
+ data = self.get_data()
125
+
126
+ # Convert QE before passing
127
+ qe = create_quantum_efficiency_from_config(data["quantum_efficiency"])
128
+
129
+ # Prepare CMOS-specific parameters only (drop "type" and "quantum_efficiency")
130
+ camera_data = {
131
+ k: v for k, v in data.items() if k not in {"type", "quantum_efficiency"}
132
+ }
133
+ camera_data["pixel_size"] = (
134
+ camera_data["pixel_detector_size"] / camera_data["magnification"]
135
+ )
136
+
137
+ # Attempt to construct the detector
138
+ detector = CMOSDetector(**camera_data)
139
+
140
+ # If no exception, validation is successful
141
+ QMessageBox.information(
142
+ self, "Validation Successful", "Camera parameters are valid."
143
+ )
144
+ return True
145
+
146
+ except ValidationError as e:
147
+ QMessageBox.critical(self, "Validation Error", str(e))
148
+ return False
149
+ except Exception as e:
150
+ QMessageBox.critical(self, "Validation Error", str(e))
151
+ return False
152
+
153
+ def get_data(self):
154
+ camera_data = {
155
+ "type": self.camera_type.currentText(),
156
+ "pixel_count": [self.pixel_width.value(), self.pixel_height.value()],
157
+ "pixel_detector_size": self.pixel_detector_size.value(),
158
+ "magnification": self.magnification.value(),
159
+ "dark_current": self.dark_current.value(),
160
+ "readout_noise": self.readout_noise.value(),
161
+ "bit_depth": self.bit_depth.value(),
162
+ "sensitivity": self.sensitivity.value(),
163
+ "base_adu": self.base_adu.value(),
164
+ "binning_size": self.binning_size.value(),
165
+ "quantum_efficiency": convert_dict_to_2_list(
166
+ self.quantum_efficiency_data
167
+ ), # Use edited data here
168
+ }
169
+ return camera_data
170
+
171
+ def set_data(self, data: dict):
172
+ # Camera type
173
+ camera_type = data.get("type", "CMOS")
174
+ idx = self.camera_type.findText(camera_type)
175
+ if idx >= 0:
176
+ self.camera_type.setCurrentIndex(idx)
177
+
178
+ # Pixel count
179
+ pixel_count = data.get("pixel_count", [512, 512])
180
+ if len(pixel_count) == 2:
181
+ self.pixel_width.setValue(pixel_count[0])
182
+ self.pixel_height.setValue(pixel_count[1])
183
+
184
+ self.pixel_detector_size.setValue(data.get("pixel_detector_size", 6.5))
185
+ self.magnification.setValue(data.get("magnification", 60))
186
+ self.dark_current.setValue(data.get("dark_current", 1.0))
187
+ self.readout_noise.setValue(data.get("readout_noise", 1.0))
188
+ self.bit_depth.setValue(data.get("bit_depth", 16))
189
+ self.sensitivity.setValue(data.get("sensitivity", 0.5))
190
+ self.base_adu.setValue(data.get("base_adu", 100))
191
+ self.binning_size.setValue(data.get("binning_size", 1))
192
+
193
+ # Quantum efficiency
194
+ raw_qe = data.get("quantum_efficiency", [])
195
+ wavelengths = [pair[0] for pair in raw_qe]
196
+ efficiencies = [pair[1] for pair in raw_qe]
197
+ self.quantum_efficiency_data = {
198
+ "wavelengths": wavelengths,
199
+ "quantum_efficiency": efficiencies,
200
+ }
201
+
202
+ def get_help_path(self) -> Path:
203
+ return Path(__file__).parent.parent / "help_docs" / "detector_help.md"
204
+
205
+
206
+ def convert_dict_to_2_list(dict_c):
207
+ wl_qe = []
208
+ wl = dict_c["wavelengths"]
209
+ qe = dict_c["quantum_efficiency"]
210
+ for i in range(len(wl)):
211
+ lista = [wl[i], qe[i]]
212
+ wl_qe.append(lista)
213
+ return wl_qe
@@ -0,0 +1,225 @@
1
+ from pathlib import Path
2
+
3
+ from pydantic import ValidationError
4
+ from PyQt6.QtWidgets import (
5
+ QComboBox,
6
+ QDoubleSpinBox,
7
+ QFormLayout,
8
+ QHBoxLayout,
9
+ QMessageBox,
10
+ QPushButton,
11
+ QStackedWidget,
12
+ QVBoxLayout,
13
+ QWidget,
14
+ )
15
+
16
+
17
+ class CellConfigWidget(QWidget):
18
+ def __init__(self):
19
+ super().__init__()
20
+ layout = QVBoxLayout()
21
+ form = QFormLayout()
22
+
23
+ self.validate_button = QPushButton("Validate")
24
+ self.validate_button.clicked.connect(self.validate)
25
+ layout.addWidget(self.validate_button)
26
+
27
+ self.cell_type = QComboBox()
28
+ self.cell_type.addItems(
29
+ ["SphericalCell", "RodCell", "RectangularCell", "OvoidCell"]
30
+ )
31
+ self.cell_type.currentIndexChanged.connect(self.update_params)
32
+ form.addRow("Cell Type:", self.cell_type)
33
+
34
+ self.param_stack = QStackedWidget()
35
+ self.param_widgets = {
36
+ "SphericalCell": self.make_spherical_widget(),
37
+ "RodCell": self.make_rod_widget(),
38
+ "RectangularCell": self.make_rect_widget(),
39
+ "OvoidCell": self.make_ovoid_widget(),
40
+ }
41
+
42
+ for widget in self.param_widgets.values():
43
+ self.param_stack.addWidget(widget)
44
+
45
+ layout.addLayout(form)
46
+ layout.addWidget(self.param_stack)
47
+ self.setLayout(layout)
48
+
49
+ def validate(self) -> bool:
50
+ from ...cells import create_cell
51
+ from ...configio.configmodels import CellParameters
52
+
53
+ try:
54
+ data = self.get_data()
55
+
56
+ # Validate the Pydantic model first
57
+ cell_params = CellParameters(**data)
58
+
59
+ # Try creating the cell with backend logic
60
+ create_cell(cell_params.cell_type, cell_params.params)
61
+
62
+ # Success
63
+ QMessageBox.information(
64
+ self, "Validation Successful", "Cell parameters are valid."
65
+ )
66
+ return True
67
+
68
+ except ValidationError as e:
69
+ QMessageBox.critical(self, "Validation Error", str(e))
70
+ return False
71
+ except Exception as e:
72
+ QMessageBox.critical(self, "Validation Error", str(e))
73
+ return False
74
+
75
+ def update_params(self, index):
76
+ self.param_stack.setCurrentIndex(index)
77
+
78
+ def make_spherical_widget(self):
79
+ widget = QWidget()
80
+ form = QFormLayout()
81
+ self.center_s = [QDoubleSpinBox() for _ in range(3)]
82
+ for c in self.center_s:
83
+ c.setRange(0, 1e5)
84
+ self.radius_s = QDoubleSpinBox()
85
+ self.radius_s.setRange(0, 1e4)
86
+ form.addRow("Center (x,y,z):", self._hbox(self.center_s))
87
+ form.addRow("Radius:", self.radius_s)
88
+ widget.setLayout(form)
89
+ return widget
90
+
91
+ def make_rod_widget(self):
92
+ widget = QWidget()
93
+ form = QFormLayout()
94
+ self.center_r = [QDoubleSpinBox() for _ in range(3)]
95
+ self.direction_r = [QDoubleSpinBox() for _ in range(3)]
96
+ self.height_r = QDoubleSpinBox()
97
+ self.radius_r = QDoubleSpinBox()
98
+ for w in self.center_r + self.direction_r:
99
+ w.setRange(0, 1e5)
100
+ form.addRow("Center:", self._hbox(self.center_r))
101
+ form.addRow("Direction:", self._hbox(self.direction_r))
102
+ form.addRow("Height:", self.height_r)
103
+ form.addRow("Radius:", self.radius_r)
104
+ widget.setLayout(form)
105
+ return widget
106
+
107
+ def make_rect_widget(self):
108
+ widget = QWidget()
109
+ form = QFormLayout()
110
+ self.bounds = [QDoubleSpinBox() for _ in range(6)]
111
+ for b in self.bounds:
112
+ b.setRange(0, 1e5)
113
+ form.addRow("Bounds [xmin,xmax,ymin,ymax,zmin,zmax]:", self._hbox(self.bounds))
114
+ widget.setLayout(form)
115
+ return widget
116
+
117
+ def make_ovoid_widget(self):
118
+ widget = QWidget()
119
+ form = QFormLayout()
120
+ self.center_o = [QDoubleSpinBox() for _ in range(3)]
121
+ self.xradius = QDoubleSpinBox()
122
+ self.yradius = QDoubleSpinBox()
123
+ self.zradius = QDoubleSpinBox()
124
+ for c in self.center_o:
125
+ c.setRange(0, 1e5)
126
+ form.addRow("Center:", self._hbox(self.center_o))
127
+ form.addRow("X Radius:", self.xradius)
128
+ form.addRow("Y Radius:", self.yradius)
129
+ form.addRow("Z Radius:", self.zradius)
130
+ widget.setLayout(form)
131
+ return widget
132
+
133
+ def _hbox(self, widgets):
134
+ box = QHBoxLayout()
135
+ for w in widgets:
136
+ box.addWidget(w)
137
+ container = QWidget()
138
+ container.setLayout(box)
139
+ return container
140
+
141
+ def get_data(self):
142
+ ctype = self.cell_type.currentText()
143
+ if ctype == "SphericalCell":
144
+ return {
145
+ "cell_type": ctype,
146
+ "params": {
147
+ "center": [s.value() for s in self.center_s],
148
+ "radius": self.radius_s.value(),
149
+ },
150
+ }
151
+ elif ctype == "RodCell":
152
+ return {
153
+ "cell_type": ctype,
154
+ "params": {
155
+ "center": [s.value() for s in self.center_r],
156
+ "direction": [s.value() for s in self.direction_r],
157
+ "height": self.height_r.value(),
158
+ "radius": self.radius_r.value(),
159
+ },
160
+ }
161
+ elif ctype == "RectangularCell":
162
+ return {
163
+ "cell_type": ctype,
164
+ "params": {"bounds": [b.value() for b in self.bounds]},
165
+ }
166
+ elif ctype == "OvoidCell":
167
+ return {
168
+ "cell_type": ctype,
169
+ "params": {
170
+ "center": [s.value() for s in self.center_o],
171
+ "xradius": self.xradius.value(),
172
+ "yradius": self.yradius.value(),
173
+ "zradius": self.zradius.value(),
174
+ },
175
+ }
176
+ else:
177
+ return {"cell_type": ctype, "params": {}}
178
+
179
+ def set_data(self, data: dict):
180
+ try:
181
+ cell_type = data.get("cell_type", "RodCell")
182
+ self.cell_type.setCurrentText(cell_type)
183
+
184
+ params = data.get("params", {})
185
+
186
+ if cell_type == "SphericalCell":
187
+ center = params.get("center", [0, 0, 0])
188
+ radius = params.get("radius", 1.0)
189
+ for i, val in enumerate(center):
190
+ self.center_s[i].setValue(val)
191
+ self.radius_s.setValue(radius)
192
+
193
+ elif cell_type == "RodCell":
194
+ center = params.get("center", [0, 0, 0])
195
+ direction = params.get("direction", [1, 0, 0])
196
+ height = params.get("height", 1.0)
197
+ radius = params.get("radius", 0.5)
198
+ for i, val in enumerate(center):
199
+ self.center_r[i].setValue(val)
200
+ for i, val in enumerate(direction):
201
+ self.direction_r[i].setValue(val)
202
+ self.height_r.setValue(height)
203
+ self.radius_r.setValue(radius)
204
+
205
+ elif cell_type == "RectangularCell":
206
+ bounds = params.get("bounds", [0, 10, 0, 10, 0, 10])
207
+ for i, val in enumerate(bounds):
208
+ self.bounds[i].setValue(val)
209
+
210
+ elif cell_type == "OvoidCell":
211
+ center = params.get("center", [0, 0, 0])
212
+ xr = params.get("xradius", 1.0)
213
+ yr = params.get("yradius", 1.0)
214
+ zr = params.get("zradius", 1.0)
215
+ for i, val in enumerate(center):
216
+ self.center_o[i].setValue(val)
217
+ self.xradius.setValue(xr)
218
+ self.yradius.setValue(yr)
219
+ self.zradius.setValue(zr)
220
+
221
+ except Exception as e:
222
+ print(f"[CellConfigWidget] Failed to load data: {e}")
223
+
224
+ def get_help_path(self) -> Path:
225
+ return Path(__file__).parent.parent / "help_docs" / "cell_help.md"