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
@@ -2,6 +2,7 @@ from copy import deepcopy
2
2
  from pathlib import Path
3
3
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
4
4
 
5
+ import numpy as np
5
6
  import tomli
6
7
  from pydantic import BaseModel
7
8
 
@@ -64,28 +65,15 @@ from .experiments import (
64
65
  FILTERSET_BASE = ["excitation", "emission", "dichroic"]
65
66
 
66
67
 
68
+ # Helper function to load config
67
69
  def load_config(config_path: Union[str, Path]) -> Dict[str, Any]:
68
70
  """
69
71
  Load and parse a TOML configuration file.
70
-
71
- Args:
72
- config_path: Path to the TOML configuration file (can be string or Path object)
73
-
74
- Returns:
75
- Dict[str, Any]: Parsed configuration dictionary
76
-
77
- Raises:
78
- FileNotFoundError: If the config file doesn't exist
79
- tomli.TOMLDecodeError: If the TOML file is invalid
80
72
  """
81
- # Convert string path to Path object if necessary
82
73
  path = Path(config_path) if isinstance(config_path, str) else config_path
83
-
84
- # Check if file exists
85
74
  if not path.exists():
86
75
  raise FileNotFoundError(f"Configuration file not found: {path}")
87
76
 
88
- # Load and parse TOML file
89
77
  try:
90
78
  with open(path, "rb") as f:
91
79
  return tomli.load(f)
@@ -93,657 +81,451 @@ def load_config(config_path: Union[str, Path]) -> Dict[str, Any]:
93
81
  raise tomli.TOMLDecodeError(f"Error parsing TOML file {path}: {str(e)}")
94
82
 
95
83
 
96
- class ConfigLoader:
97
- def __init__(self, config_path: Union[str, Path, dict]):
98
- # if exists, load config, otherwise raise error
99
- if isinstance(config_path, dict):
100
- self.config = config_path
101
- elif not Path(config_path).exists():
102
- print(f"Configuration file not found: {config_path}")
103
- self.config_path = None
104
- else:
105
- self.config_path = config_path
106
- self.config = load_config(config_path)
107
-
108
- def _reload_config(self):
109
- if self.config_path is not None:
110
- self.config = load_config(config_path=self.config_path)
111
-
112
- def create_dataclass_schema(
113
- self, dataclass_schema: type[BaseModel], config: Dict[str, Any]
114
- ) -> BaseModel:
115
- """
116
- Populate a dataclass schema with configuration data.
117
- """
118
- return dataclass_schema(**config)
119
-
120
- def populate_dataclass_schema(self) -> None:
121
- """
122
- Populate a dataclass schema with configuration data.
123
- """
124
- self.global_params = self.create_dataclass_schema(
125
- GlobalParameters, self.config["Global_Parameters"]
126
- )
127
- self.cell_params = self.create_dataclass_schema(
128
- CellParameters, self.config["Cell_Parameters"]
84
+ # Function to populate the dataclass schema
85
+ def populate_dataclass_schema(
86
+ config: Dict[str, Any],
87
+ ) -> Tuple[
88
+ GlobalParameters,
89
+ CellParameters,
90
+ MoleculeParameters,
91
+ CondensateParameters,
92
+ OutputParameters,
93
+ ]:
94
+ global_params = create_dataclass_schema(
95
+ GlobalParameters, config["Global_Parameters"]
96
+ )
97
+ cell_params = create_dataclass_schema(CellParameters, config["Cell_Parameters"])
98
+ molecule_params = create_dataclass_schema(
99
+ MoleculeParameters, config["Molecule_Parameters"]
100
+ )
101
+ condensate_params = create_dataclass_schema(
102
+ CondensateParameters, config["Condensate_Parameters"]
103
+ )
104
+ output_params = create_dataclass_schema(
105
+ OutputParameters, config["Output_Parameters"]
106
+ )
107
+
108
+ return global_params, cell_params, molecule_params, condensate_params, output_params
109
+
110
+
111
+ def create_dataclass_schema(
112
+ dataclass_schema: type[BaseModel], config: Dict[str, Any]
113
+ ) -> BaseModel:
114
+ """Populate a dataclass schema with configuration data."""
115
+ return dataclass_schema(**config)
116
+
117
+
118
+ # Function to create experiment from config
119
+ def create_experiment_from_config(
120
+ config: Dict[str, Any],
121
+ ) -> Tuple[BaseExpConfig, Callable]:
122
+ """Create experiment config and associated callable from configuration."""
123
+ configEXP = deepcopy(config["experiment"])
124
+ if configEXP.get("experiment_type") == "time-series":
125
+ del configEXP["experiment_type"]
126
+ tconfig = TimeSeriesExpConfig(**configEXP)
127
+ callableEXP = timeseriesEXP
128
+ elif configEXP.get("experiment_type") == "z-stack":
129
+ del configEXP["experiment_type"]
130
+ tconfig = zStackExpConfig(**configEXP)
131
+ callableEXP = zseriesEXP
132
+ else:
133
+ raise TypeError("Experiment is not supported")
134
+ return tconfig, callableEXP
135
+
136
+
137
+ # Function to create fluorophores from config
138
+ def create_fluorophores_from_config(config: Dict[str, Any]) -> List[Fluorophore]:
139
+ fluor_config = config.get("fluorophores", {})
140
+ if not fluor_config:
141
+ raise ValueError("No fluorophores configuration found in config")
142
+ num_fluorophores = fluor_config["num_of_fluorophores"]
143
+ fluorophore_names = fluor_config["fluorophore_names"]
144
+ return [
145
+ create_fluorophore_from_config(fluor_config[fluorophore_names[i]])
146
+ for i in range(num_fluorophores)
147
+ ]
148
+
149
+
150
+ # Function to create a single fluorophore from config
151
+ def create_fluorophore_from_config(config: Dict[str, Any]) -> Fluorophore:
152
+ fluor_config = config
153
+ states = create_states_from_config(fluor_config)
154
+ initial_state = get_initial_state(states, fluor_config)
155
+ transitions = create_transitions_from_config(fluor_config)
156
+ return Fluorophore(
157
+ name=fluor_config["name"],
158
+ states=states,
159
+ transitions=transitions,
160
+ initial_state=initial_state,
161
+ )
162
+
163
+
164
+ def create_states_from_config(fluor_config: Dict[str, Any]) -> Dict[str, State]:
165
+ """Create states from fluorophore configuration."""
166
+ states = {}
167
+ for state_name, state_data in fluor_config.get("states", {}).items():
168
+ states[state_name] = create_state(state_data)
169
+ return states
170
+
171
+
172
+ def create_state(state_data: Dict[str, Any]) -> State:
173
+ """Create a single state from configuration."""
174
+ excitation_spectrum = get_spectral_data(state_data, "excitation_spectrum")
175
+ emission_spectrum = get_spectral_data(state_data, "emission_spectrum")
176
+ return State(
177
+ name=state_data["name"],
178
+ state_type=StateType(state_data["state_type"]),
179
+ excitation_spectrum=excitation_spectrum,
180
+ emission_spectrum=emission_spectrum,
181
+ quantum_yield_lambda_val=state_data.get("quantum_yield"),
182
+ extinction_coefficient_lambda_val=state_data.get("extinction_coefficient"),
183
+ fluorescent_lifetime=state_data.get("fluorescent_lifetime"),
184
+ )
185
+
186
+
187
+ def get_spectral_data(state_data: Dict[str, Any], key: str) -> Optional[SpectralData]:
188
+ """Retrieve spectral data for excitation/emission."""
189
+ spectrum_data = state_data.get(key)
190
+ if spectrum_data:
191
+ return SpectralData(
192
+ wavelengths=spectrum_data.get("wavelengths", []),
193
+ intensities=spectrum_data.get("intensities", []),
129
194
  )
130
- self.molecule_params = self.create_dataclass_schema(
131
- MoleculeParameters, self.config["Molecule_Parameters"]
195
+ return None
196
+
197
+
198
+ def get_initial_state(states: Dict[str, State], fluor_config: Dict[str, Any]) -> State:
199
+ """Get initial state for fluorophore."""
200
+ initial_state = None
201
+ state_list = list(states.keys())
202
+ for state in states.values():
203
+ if state.name == fluor_config["initial_state"]:
204
+ initial_state = state
205
+ if initial_state is None:
206
+ raise ValueError(f"Initial state must be one of: {state_list}.")
207
+ return initial_state
208
+
209
+
210
+ def create_transitions_from_config(
211
+ fluor_config: Dict[str, Any],
212
+ ) -> Dict[str, StateTransition]:
213
+ """Create state transitions from configuration."""
214
+ transitions = {}
215
+ for _, trans_data in fluor_config.get("transitions", {}).items():
216
+ transitions[trans_data["from_state"] + trans_data["to_state"]] = (
217
+ create_transition(trans_data)
132
218
  )
133
- self.condensate_params = self.create_dataclass_schema(
134
- CondensateParameters, self.config["Condensate_Parameters"]
219
+ return transitions
220
+
221
+
222
+ def create_transition(trans_data: Dict[str, Any]) -> StateTransition:
223
+ """Create a single state transition."""
224
+ if trans_data.get("photon_dependent", False):
225
+ return StateTransition(
226
+ from_state=trans_data["from_state"],
227
+ to_state=trans_data["to_state"],
228
+ spectrum=SpectralData(
229
+ wavelengths=trans_data["spectrum"]["wavelengths"],
230
+ intensities=trans_data["spectrum"]["intensities"],
231
+ ),
232
+ extinction_coefficient_lambda_val=trans_data["spectrum"][
233
+ "extinction_coefficient"
234
+ ],
235
+ quantum_yield=trans_data["spectrum"]["quantum_yield"],
135
236
  )
136
- self.output_params = self.create_dataclass_schema(
137
- OutputParameters, self.config["Output_Parameters"]
237
+ else:
238
+ return StateTransition(
239
+ from_state=trans_data["from_state"],
240
+ to_state=trans_data["to_state"],
241
+ base_rate=trans_data.get("base_rate"),
138
242
  )
139
243
 
140
- def create_experiment_from_config(
141
- self, config: Dict[str, Any]
142
- ) -> Tuple[BaseExpConfig, Callable]:
143
- configEXP = deepcopy(config["experiment"])
144
- if configEXP.get("experiment_type") == "time-series":
145
- del configEXP["experiment_type"]
146
- tconfig = TimeSeriesExpConfig(**configEXP)
147
- callableEXP = timeseriesEXP
148
- elif configEXP.get("experiment_type") == "z-stack":
149
- del configEXP["experiment_type"]
150
- tconfig = zStackExpConfig(**configEXP)
151
- callableEXP = zseriesEXP
152
- else:
153
- raise TypeError("Experiment is not supported")
154
- return tconfig, callableEXP
155
-
156
- def create_fluorophores_from_config(
157
- self, config: Dict[str, Any]
158
- ) -> List[Fluorophore]:
159
- # Extract fluorophore section
160
- fluor_config = config.get("fluorophores", {})
161
- if not fluor_config:
162
- raise ValueError("No fluorophores configuration found in config")
163
- num_fluorophores = fluor_config["num_of_fluorophores"]
164
- fluorophore_names = fluor_config["fluorophore_names"]
165
- fluorophores = []
166
- for i in range(num_fluorophores):
167
- fluorophores.append(
168
- self.create_fluorophore_from_config(fluor_config[fluorophore_names[i]])
169
- )
170
- return fluorophores
171
-
172
- def create_fluorophore_from_config(self, config: Dict[str, Any]) -> Fluorophore:
173
- """
174
- Create a fluorophore instance from a configuration dictionary.
175
-
176
- Args:
177
- config: Dictionary containing the full configuration (typically loaded from TOML)
178
-
179
- Returns:
180
- Fluorophore: A Fluorophore instance with the loaded configuration
181
- """
182
- # Extract fluorophore section
183
- fluor_config = config
184
- if not fluor_config:
185
- raise ValueError("No fluorophore configuration found.")
186
-
187
- # Build states
188
- states = {}
189
- for state_name, state_data in fluor_config.get("states", {}).items():
190
- # Create spectral data if present
191
- excitation_spectrum = (
192
- SpectralData(
193
- wavelengths=state_data.get("excitation_spectrum", {}).get(
194
- "wavelengths", []
195
- ),
196
- intensities=state_data.get("excitation_spectrum", {}).get(
197
- "intensities", []
198
- ),
199
- )
200
- if "excitation_spectrum" in state_data
201
- else None
202
- )
203
244
 
204
- emission_spectrum = (
205
- SpectralData(
206
- wavelengths=state_data.get("emission_spectrum", {}).get(
207
- "wavelengths", []
208
- ),
209
- intensities=state_data.get("emission_spectrum", {}).get(
210
- "intensities", []
211
- ),
212
- )
213
- if "emission_spectrum" in state_data
214
- else None
215
- )
245
+ # Function to create PSF engine from config
246
+ def create_psf_from_config(config: Dict[str, Any]) -> Tuple[Callable, Dict[str, Any]]:
247
+ """Create a PSF engine instance from a configuration dictionary."""
248
+ psf_config = config.get("psf", {})
249
+ if not psf_config:
250
+ raise ValueError("No PSF configuration found in config")
216
251
 
217
- extinction_coefficient = state_data.get("extinction_coefficient")
218
- quantum_yield = state_data.get("quantum_yield")
219
- molar_cross_section = state_data.get("molar_cross_section")
220
- fluorescent_lifetime = state_data.get("fluorescent_lifetime")
221
-
222
- # Create state
223
- state = State(
224
- name=state_data["name"],
225
- state_type=StateType(state_data["state_type"]),
226
- excitation_spectrum=excitation_spectrum,
227
- emission_spectrum=emission_spectrum,
228
- quantum_yield_lambda_val=quantum_yield,
229
- extinction_coefficient_lambda_val=extinction_coefficient,
230
- molar_cross_section=molar_cross_section,
231
- quantum_yield=None,
232
- extinction_coefficient=None,
233
- fluorescent_lifetime=fluorescent_lifetime,
234
- )
235
- states[state.name] = state
236
-
237
- initial_state = None
238
- state_list = []
239
- for state in states.values():
240
- state_list.append(state.name)
241
- if state.name == fluor_config["initial_state"]:
242
- initial_state = state
243
-
244
- if initial_state is None:
245
- raise ValueError(
246
- f"Inital state must be a valid name from the provided states: {state_list}."
247
- )
252
+ params_config = psf_config.get("parameters", {})
253
+ if not params_config:
254
+ raise ValueError("No PSF parameters found in config")
248
255
 
249
- # Build transitions
250
- transitions = {}
251
- for _, trans_data in fluor_config.get("transitions", {}).items():
252
- if trans_data.get("photon_dependent", False):
253
- transition = StateTransition(
254
- from_state=trans_data["from_state"],
255
- to_state=trans_data["to_state"],
256
- spectrum=SpectralData(
257
- wavelengths=trans_data.get("spectrum")["wavelengths"],
258
- intensities=trans_data.get("spectrum")["intensities"],
259
- ),
260
- extinction_coefficient_lambda_val=trans_data.get("spectrum")[
261
- "extinction_coefficient"
262
- ],
263
- extinction_coefficient=None,
264
- cross_section=None,
265
- base_rate=None,
266
- quantum_yield=trans_data.get("spectrum")["quantum_yield"],
267
- )
268
- else:
269
- transition = StateTransition(
270
- from_state=trans_data["from_state"],
271
- to_state=trans_data["to_state"],
272
- base_rate=trans_data.get("base_rate", None),
273
- spectrum=None,
274
- extinction_coefficient_lambda_val=None,
275
- extinction_coefficient=None,
276
- cross_section=None,
277
- quantum_yield=None,
278
- )
279
- transitions[transition.from_state + transition.to_state] = transition
280
-
281
- # Create and return fluorophore
282
- return Fluorophore(
283
- name=fluor_config["name"],
284
- states=states,
285
- transitions=transitions,
286
- initial_state=initial_state,
287
- )
256
+ pixel_size = find_pixel_size(
257
+ config["camera"]["magnification"], config["camera"]["pixel_detector_size"]
258
+ )
288
259
 
289
- def create_psf_from_config(
290
- self, config: Dict[str, Any]
291
- ) -> Tuple[Callable, Dict[str, Any]]:
292
- """
293
- Create a PSF engine instance from a configuration dictionary.
294
-
295
- Args:
296
- config: Dictionary containing the full configuration (typically loaded from TOML)
297
-
298
- Returns:
299
- Tuple[Callable, Optional[Dict]]: A tuple containing:
300
- - Partial_PSFEngine partial funcion of PSFEngine. Called as f(wavelength, z_step)
301
- - Parameters:
302
- - wavelength (int, float) in nm
303
- - wavelength of the emitted light from the sample after emission filters
304
- - z_step (int, float) in um
305
- - z_step used to parameterize the psf grid.
306
- - Additional PSF-specific parameters (like custom path if specified)
307
- """
308
- # Extract PSF section
309
- psf_config = config.get("psf", {})
310
- if not psf_config:
311
- raise ValueError("No PSF configuration found in config")
312
-
313
- # Extract parameters section
314
- params_config = psf_config.get("parameters", {})
315
- if not params_config:
316
- raise ValueError("No PSF parameters found in config")
317
- pixel_size = self._find_pixel_size(
318
- config["camera"]["magnification"], config["camera"]["pixel_detector_size"]
260
+ def Partial_PSFengine(
261
+ wavelength: int | float, z_step: Optional[int | float] = None
262
+ ):
263
+ parameters = PSFParameters(
264
+ emission_wavelength=wavelength,
265
+ numerical_aperture=float(params_config["numerical_aperture"]),
266
+ pixel_size=pixel_size,
267
+ z_step=float(params_config["z_step"]) if z_step is None else z_step,
268
+ refractive_index=float(params_config.get("refractive_index", 1.0)),
269
+ pinhole_diameter=params_config.get("pinhole_diameter", None),
319
270
  )
271
+ psf_engine = PSFEngine(parameters)
272
+ return psf_engine
320
273
 
321
- def Partial_PSFengine(
322
- wavelength: int | float, z_step: Optional[int | float] = None
323
- ):
324
- # Create PSFParameters instance
325
- parameters = PSFParameters(
326
- emission_wavelength=wavelength,
327
- numerical_aperture=float(params_config["numerical_aperture"]),
328
- pixel_size=pixel_size,
329
- z_step=float(params_config["z_step"]) if z_step is None else z_step,
330
- refractive_index=float(params_config.get("refractive_index", 1.0)),
331
- pinhole_diameter=params_config.get("pinhole_diameter", None),
332
- )
274
+ additional_config = {
275
+ "type": psf_config.get("type", "gaussian"),
276
+ "custom_path": psf_config.get("custom_path", ""),
277
+ }
333
278
 
334
- # Create PSF engine
335
- psf_engine = PSFEngine(parameters)
336
- return psf_engine
279
+ return Partial_PSFengine, additional_config
337
280
 
338
- # Extract additional configuration
339
- additional_config = {
340
- "type": psf_config.get("type", "gaussian"),
341
- "custom_path": psf_config.get("custom_path", ""),
342
- }
343
281
 
344
- return Partial_PSFengine, additional_config
345
-
346
- @staticmethod
347
- def _find_pixel_size(magnification: float, pixel_detector_size: float) -> float:
348
- return pixel_detector_size / magnification
349
-
350
- def create_laser_from_config(
351
- self, laser_config: Dict[str, Any], preset: str
352
- ) -> LaserProfile:
353
- """
354
- Create a laser profile instance from a configuration dictionary.
355
-
356
- Args:
357
- laser_config: Dictionary containing the laser configuration
358
- preset: Name of the laser preset (e.g., 'blue', 'green', 'red')
359
-
360
- Returns:
361
- LaserProfile: A LaserProfile instance with the loaded configuration
362
- """
363
- # Extract laser parameters
364
- params_config = laser_config.get("parameters", {})
365
- if not params_config:
366
- raise ValueError(f"No parameters found for laser: {preset}")
367
-
368
- # Create LaserParameters instance
369
- parameters = LaserParameters(
370
- power=float(params_config["power"]),
371
- wavelength=float(params_config["wavelength"]),
372
- beam_width=float(params_config["beam_width"]),
373
- numerical_aperture=float(params_config.get("numerical_aperture")),
374
- refractive_index=float(params_config.get("refractive_index", 1.0)),
375
- )
282
+ # Helper function to find pixel size
283
+ def find_pixel_size(magnification: float, pixel_detector_size: float) -> float:
284
+ return pixel_detector_size / magnification
376
285
 
377
- # Create appropriate laser profile based on type
378
- laser_type = laser_config.get("type", "gaussian").lower()
379
-
380
- if laser_type == "gaussian":
381
- return GaussianBeam(parameters)
382
- if laser_type == "widefield":
383
- return WidefieldBeam(parameters)
384
- if laser_type == "hilo":
385
- try:
386
- params_config.get("inclination_angle")
387
- except KeyError:
388
- raise KeyError("HiLo needs inclination angle. Currently not provided")
389
- return HiLoBeam(parameters, float(params_config["inclination_angle"]))
390
- else:
391
- raise ValueError(f"Unknown laser type: {laser_type}")
392
-
393
- def create_lasers_from_config(
394
- self, config: Dict[str, Any]
395
- ) -> Dict[str, LaserProfile]:
396
- """
397
- Create multiple laser profile instances from a configuration dictionary.
398
-
399
- Args:
400
- config: Dictionary containing the full configuration (typically loaded from TOML)
401
-
402
- Returns:
403
- Dict[str, LaserProfile]: Dictionary mapping laser names to their profile instances
404
- """
405
- # Extract lasers section
406
- lasers_config = config.get("lasers", {})
407
- if not lasers_config:
408
- raise ValueError("No lasers configuration found in config")
409
-
410
- # Get active lasers
411
- active_lasers = lasers_config.get("active", [])
412
- if not active_lasers:
413
- raise ValueError("No active lasers specified in configuration")
414
-
415
- # Create laser profiles for each active laser
416
- laser_profiles = {}
417
- for laser_name in active_lasers:
418
- laser_config = lasers_config.get(laser_name)
419
- if not laser_config:
420
- raise ValueError(f"Configuration not found for laser: {laser_name}")
421
-
422
- laser_profiles[laser_name] = self.create_laser_from_config(
423
- laser_config, laser_name
424
- )
425
286
 
426
- return laser_profiles
427
-
428
- def create_filter_spectrum_from_config(
429
- self, filter_config: Dict[str, Any]
430
- ) -> FilterSpectrum:
431
- """
432
- Create a filter spectrum from configuration dictionary.
433
-
434
- Args:
435
- filter_config: Dictionary containing filter configuration
436
-
437
- Returns:
438
- FilterSpectrum: Created filter spectrum instance
439
- """
440
- filter_type = filter_config.get("type", "").lower()
441
-
442
- if filter_type == "bandpass":
443
- return create_bandpass_filter(
444
- center_wavelength=float(filter_config["center_wavelength"]),
445
- bandwidth=float(filter_config["bandwidth"]),
446
- transmission_peak=float(filter_config.get("transmission_peak", 0.95)),
447
- points=int(filter_config.get("points", 1000)),
448
- name=filter_config.get("name"),
449
- )
450
- elif filter_type == "tophat":
451
- return create_tophat_filter(
452
- center_wavelength=float(filter_config["center_wavelength"]),
453
- bandwidth=float(filter_config["bandwidth"]),
454
- transmission_peak=float(filter_config.get("transmission_peak", 0.95)),
455
- edge_steepness=float(filter_config.get("edge_steepness", 5.0)),
456
- points=int(filter_config.get("points", 1000)),
457
- name=filter_config.get("name"),
458
- )
459
- elif filter_type == "allow_all":
460
- return create_allow_all_filter(
461
- points=int(filter_config.get("points", 1000)),
462
- name=filter_config.get("name"),
463
- )
287
+ # Function to create a laser from config
288
+ def create_laser_from_config(laser_config: Dict[str, Any], preset: str) -> LaserProfile:
289
+ """Create a laser profile instance from a configuration dictionary."""
290
+ params_config = laser_config.get("parameters", {})
291
+ if not params_config:
292
+ raise ValueError(f"No parameters found for laser: {preset}")
464
293
 
465
- else:
466
- raise ValueError(f"Unsupported filter type: {filter_type}")
467
-
468
- def create_filter_set_from_config(self, config: Dict[str, Any]) -> FilterSet:
469
- """
470
- Create a filter set from configuration dictionary.
471
-
472
- Args:
473
- config: Dictionary containing the full configuration (typically loaded from TOML)
474
-
475
- Returns:
476
- FilterSet: Created filter set instance
477
- """
478
- # Extract filters section
479
- filters_config = config
480
- if not filters_config:
481
- raise ValueError("No filters configuration found in config")
482
-
483
- missing = []
484
- for base_filter in FILTERSET_BASE:
485
- if base_filter not in filters_config:
486
- print(f"Missing {base_filter} filter in filter set; using base config")
487
- missing.append(base_filter)
488
-
489
- if missing:
490
- for base_filter in missing:
491
- filters_config[base_filter] = {
492
- "type": "allow_all",
493
- "points": 1000,
494
- "name": f"{base_filter} filter",
495
- }
496
-
497
- # Create filter components
498
- excitation = self.create_filter_spectrum_from_config(
499
- filters_config["excitation"]
500
- )
501
- emission = self.create_filter_spectrum_from_config(filters_config["emission"])
502
- dichroic = self.create_filter_spectrum_from_config(filters_config["dichroic"])
503
-
504
- # Create filter set
505
- return FilterSet(
506
- name=filters_config.get("filter_set_name", "Custom Filter Set"),
507
- excitation=excitation,
508
- emission=emission,
509
- dichroic=dichroic,
510
- )
294
+ parameters = LaserParameters(
295
+ power=float(params_config["power"]),
296
+ wavelength=float(params_config["wavelength"]),
297
+ beam_width=float(params_config["beam_width"]),
298
+ numerical_aperture=float(params_config.get("numerical_aperture")),
299
+ refractive_index=float(params_config.get("refractive_index", 1.0)),
300
+ )
511
301
 
512
- def create_channels(self, config: Dict[str, Any]) -> Channels:
513
- # Extract channel section
514
- channel_config = config.get("channels", {})
515
- if not channel_config:
516
- raise ValueError("No channels configuration found in config")
517
- channel_filters = []
518
- channel_num = int(channel_config.get("num_of_channels"))
519
- channel_names = channel_config.get("channel_names")
520
- split_eff = channel_config.get("split_efficiency")
521
- for i in range(channel_num):
522
- channel_filters.append(
523
- self.create_filter_set_from_config(
524
- channel_config.get("filters").get(channel_names[i])
525
- )
526
- )
527
- channels = Channels(
528
- filtersets=channel_filters,
529
- num_channels=channel_num,
530
- splitting_efficiency=split_eff,
531
- names=channel_names,
302
+ laser_type = laser_config.get("type", "gaussian").lower()
303
+
304
+ if laser_type == "gaussian":
305
+ return GaussianBeam(parameters)
306
+ if laser_type == "widefield":
307
+ return WidefieldBeam(parameters)
308
+ if laser_type == "hilo":
309
+ try:
310
+ params_config.get("inclination_angle")
311
+ except KeyError:
312
+ raise KeyError("HiLo needs inclination angle. Currently not provided")
313
+ return HiLoBeam(parameters, float(params_config["inclination_angle"]))
314
+ else:
315
+ raise ValueError(f"Unknown laser type: {laser_type}")
316
+
317
+
318
+ # Function to create lasers from config
319
+ def create_lasers_from_config(config: Dict[str, Any]) -> Dict[str, LaserProfile]:
320
+ """Create multiple laser profile instances from a configuration dictionary."""
321
+ lasers_config = config.get("lasers", {})
322
+ if not lasers_config:
323
+ raise ValueError("No lasers configuration found in config")
324
+
325
+ active_lasers = lasers_config.get("active", [])
326
+ if not active_lasers:
327
+ raise ValueError("No active lasers specified in configuration")
328
+
329
+ laser_profiles = {}
330
+ for laser_name in active_lasers:
331
+ laser_config = lasers_config.get(laser_name)
332
+ if not laser_config:
333
+ raise ValueError(f"Configuration not found for laser: {laser_name}")
334
+
335
+ laser_profiles[laser_name] = create_laser_from_config(laser_config, laser_name)
336
+
337
+ return laser_profiles
338
+
339
+
340
+ # Function to create filter spectrum from config
341
+ def create_filter_spectrum_from_config(filter_config: Dict[str, Any]) -> FilterSpectrum:
342
+ """Create a filter spectrum from configuration dictionary."""
343
+ filter_type = filter_config.get("type", "").lower()
344
+
345
+ if filter_type == "bandpass":
346
+ return create_bandpass_filter(
347
+ center_wavelength=float(filter_config["center_wavelength"]),
348
+ bandwidth=float(filter_config["bandwidth"]),
349
+ transmission_peak=float(filter_config.get("transmission_peak", 0.95)),
350
+ points=int(filter_config.get("points", 1000)),
351
+ name=filter_config.get("name"),
532
352
  )
533
- return channels
534
-
535
- def create_quantum_efficiency_from_config(
536
- self, qe_data: List[List[float]]
537
- ) -> QuantumEfficiency:
538
- """
539
- Create a QuantumEfficiency instance from configuration data.
540
-
541
- Args:
542
- qe_data: List of [wavelength, efficiency] pairs
543
-
544
- Returns:
545
- QuantumEfficiency: Created quantum efficiency instance
546
- """
547
- # Convert list of pairs to dictionary
548
- wavelength_qe = {pair[0]: pair[1] for pair in qe_data}
549
- return QuantumEfficiency(wavelength_qe=wavelength_qe)
550
-
551
- def create_detector_from_config(
552
- self, config: Dict[str, Any]
553
- ) -> Tuple[Detector, QuantumEfficiency]:
554
- """
555
- Create a detector instance from a configuration dictionary.
556
-
557
- Args:
558
- config: Dictionary containing the full configuration (typically loaded from TOML)
559
-
560
- Returns:
561
- Tuple[Detector, QuantumEfficiency]: A tuple containing:
562
- - Detector instance with the loaded configuration
563
- - QuantumEfficiency instance for the detector
564
- """
565
- # Extract camera section
566
- camera_config = config.get("camera", {})
567
- if not camera_config:
568
- raise ValueError("No camera configuration found in config")
569
-
570
- # Create quantum efficiency curve
571
- qe_data = camera_config.get("quantum_efficiency", [])
572
- quantum_efficiency = self.create_quantum_efficiency_from_config(qe_data)
573
-
574
- pixel_size = self._find_pixel_size(
575
- camera_config["magnification"], camera_config["pixel_detector_size"]
353
+ elif filter_type == "tophat":
354
+ return create_tophat_filter(
355
+ center_wavelength=float(filter_config["center_wavelength"]),
356
+ bandwidth=float(filter_config["bandwidth"]),
357
+ transmission_peak=float(filter_config.get("transmission_peak", 0.95)),
358
+ edge_steepness=float(filter_config.get("edge_steepness", 5.0)),
359
+ points=int(filter_config.get("points", 1000)),
360
+ name=filter_config.get("name"),
361
+ )
362
+ elif filter_type == "allow_all":
363
+ return create_allow_all_filter(
364
+ points=int(filter_config.get("points", 1000)),
365
+ name=filter_config.get("name"),
576
366
  )
577
- # Extract common parameters
578
- common_params = {
579
- "pixel_size": pixel_size,
580
- "dark_current": float(camera_config["dark_current"]),
581
- "readout_noise": float(camera_config["readout_noise"]),
582
- "pixel_count": tuple([int(i) for i in camera_config["pixel_count"]]),
583
- "bit_depth": int(camera_config.get("bit_depth", 16)),
584
- "sensitivity": float(camera_config.get("sensitivity", 1.0)),
585
- "pixel_detector_size": float(camera_config["pixel_detector_size"]),
586
- "magnification": float(camera_config["magnification"]),
587
- "base_adu": int(camera_config["base_adu"]),
588
- "binning_size": int(camera_config["binning_size"]),
589
- }
590
367
 
591
- # Create appropriate detector based on type
592
- camera_type = camera_config.get("type", "").upper()
593
-
594
- if camera_type == "CMOS":
595
- detector = CMOSDetector(**common_params)
596
- elif camera_type == "EMCCD":
597
- # Extract EMCCD-specific parameters
598
- em_params = {
599
- "em_gain": float(camera_config.get("em_gain", 300)),
600
- "clock_induced_charge": float(
601
- camera_config.get("clock_induced_charge", 0.002)
602
- ),
368
+ else:
369
+ raise ValueError(f"Unsupported filter type: {filter_type}")
370
+
371
+
372
+ # Function to create filter set from config
373
+ def create_filter_set_from_config(config: Dict[str, Any]) -> FilterSet:
374
+ """Create a filter set from configuration dictionary."""
375
+ filters_config = config
376
+ if not filters_config:
377
+ raise ValueError("No filters configuration found in config")
378
+
379
+ missing = []
380
+ for base_filter in FILTERSET_BASE:
381
+ if base_filter not in filters_config:
382
+ print(f"Missing {base_filter} filter in filter set; using base config")
383
+ missing.append(base_filter)
384
+
385
+ if missing:
386
+ for base_filter in missing:
387
+ filters_config[base_filter] = {
388
+ "type": "allow_all",
389
+ "points": 1000,
390
+ "name": f"{base_filter} filter",
603
391
  }
604
- detector = EMCCDDetector(
605
- **common_params,
606
- em_gain=em_params["em_gain"],
607
- clock_induced_charge=em_params["clock_induced_charge"],
608
- )
609
- else:
610
- raise ValueError(f"Unsupported camera type: {camera_type}")
611
-
612
- return detector, quantum_efficiency
613
-
614
- def duration_time_validation_experiments(self, configEXP) -> bool:
615
- if configEXP.exposure_time:
616
- if len(configEXP.z_position) * (
617
- configEXP.exposure_time + configEXP.interval_time
618
- ) > self.config["Global_Parameters"]["cycle_count"] * (
619
- self.config["Global_Parameters"]["exposure_time"]
620
- + self.config["Global_Parameters"]["interval_time"]
621
- ):
622
- print(
623
- f"Z-series parameters overriding the set Global_parameters. cycle_count: {len(configEXP.z_position)}, exposure_time: {configEXP.exposure_time}, and interval_time: {configEXP.interval_time}."
624
- )
625
- self.config["Global_Parameters"]["cycle_count"] = len(
626
- configEXP.z_position
627
- )
628
- self.config["Global_Parameters"]["exposure_time"] = (
629
- configEXP.exposure_time
630
- )
631
- self.config["Global_Parameters"]["interval_time"] = (
632
- configEXP.interval_time
633
- )
634
-
635
- return False
636
- else:
637
- return True
638
- else:
639
- return True
640
392
 
641
- def setup_microscope(self) -> dict:
642
- # config of experiment
643
-
644
- configEXP, funcEXP = self.create_experiment_from_config(config=self.config)
645
- self.duration_time_validation_experiments(configEXP)
646
- # find the larger of the two duration times.
647
- # base config
648
- self.populate_dataclass_schema()
649
- base_config = ConfigList(
650
- CellParameters=self.cell_params,
651
- MoleculeParameters=self.molecule_params,
652
- GlobalParameters=self.global_params,
653
- CondensateParameters=self.condensate_params,
654
- OutputParameters=self.output_params,
655
- )
393
+ excitation = create_filter_spectrum_from_config(filters_config["excitation"])
394
+ emission = create_filter_spectrum_from_config(filters_config["emission"])
395
+ dichroic = create_filter_spectrum_from_config(filters_config["dichroic"])
656
396
 
657
- # fluorophore config
658
- fluorophores = self.create_fluorophores_from_config(self.config)
659
- # psf config
660
- psf, psf_config = self.create_psf_from_config(self.config)
661
- # lasers config
662
- lasers = self.create_lasers_from_config(self.config)
663
- # channels config
664
- channels = self.create_channels(self.config)
665
- # detector config
666
- detector, qe = self.create_detector_from_config(self.config)
667
-
668
- # make cell
669
- cell = make_cell(cell_params=base_config.CellParameters)
670
-
671
- # make initial sample plane
672
- sample_plane = make_sample(
673
- global_params=base_config.GlobalParameters,
674
- cell=cell,
675
- )
397
+ return FilterSet(
398
+ name=filters_config.get("filter_set_name", "Custom Filter Set"),
399
+ excitation=excitation,
400
+ emission=emission,
401
+ dichroic=dichroic,
402
+ )
676
403
 
677
- # make condensates_dict
678
- condensates_dict = make_condensatedict(
679
- condensate_params=base_config.CondensateParameters, cell=cell
680
- )
681
404
 
682
- # make sampling function
683
- sampling_functions = make_samplingfunction(
684
- condensate_params=base_config.CondensateParameters, cell=cell
685
- )
405
+ # Function to create channels from config
406
+ def create_channels(config: Dict[str, Any]) -> Channels:
407
+ """Create channels from configuration."""
408
+ channel_config = config.get("channels", {})
409
+ if not channel_config:
410
+ raise ValueError("No channels configuration found in config")
686
411
 
687
- # create initial positions
688
- initial_molecule_positions = gen_initial_positions(
689
- molecule_params=base_config.MoleculeParameters,
690
- cell=cell,
691
- condensate_params=base_config.CondensateParameters,
692
- sampling_functions=sampling_functions,
693
- )
412
+ channel_filters = []
413
+ channel_num = int(channel_config.get("num_of_channels"))
414
+ channel_names = channel_config.get("channel_names")
415
+ split_eff = channel_config.get("split_efficiency")
694
416
 
695
- # create the track generator
696
- track_generators = create_track_generator(
697
- global_params=base_config.GlobalParameters, cell=cell
417
+ for i in range(channel_num):
418
+ channel_filters.append(
419
+ create_filter_set_from_config(
420
+ channel_config.get("filters").get(channel_names[i])
421
+ )
698
422
  )
699
423
 
700
- # get all the tracks
701
- tracks, points_per_time = get_tracks(
702
- molecule_params=base_config.MoleculeParameters,
703
- global_params=base_config.GlobalParameters,
704
- initial_positions=initial_molecule_positions,
705
- track_generator=track_generators,
706
- )
424
+ channels = Channels(
425
+ filtersets=channel_filters,
426
+ num_channels=channel_num,
427
+ splitting_efficiency=split_eff,
428
+ names=channel_names,
429
+ )
430
+ return channels
707
431
 
708
- # add tracks to sample
709
- sample_plane = add_tracks_to_sample(
710
- tracks=tracks, sample_plane=sample_plane, fluorophore=fluorophores
711
- )
712
432
 
713
- vm = VirtualMicroscope(
714
- camera=(detector, qe),
715
- sample_plane=sample_plane,
716
- lasers=lasers,
717
- channels=channels,
718
- psf=psf,
719
- config=base_config,
720
- )
721
- return_dict = {
722
- "microscope": vm,
723
- "base_config": base_config,
724
- "psf": psf,
725
- "psf_config": psf_config,
726
- "channels": channels,
727
- "lasers": lasers,
728
- "sample_plane": sample_plane,
729
- "tracks": tracks,
730
- "points_per_time": points_per_time,
731
- "condensate_dict": condensates_dict,
732
- "cell": cell,
733
- "experiment_config": configEXP,
734
- "experiment_func": funcEXP,
433
+ # Function to create quantum efficiency from config
434
+ def create_quantum_efficiency_from_config(
435
+ qe_data: List[List[float]],
436
+ ) -> QuantumEfficiency:
437
+ """Create a QuantumEfficiency instance from configuration data."""
438
+ wavelength_qe = {pair[0]: pair[1] for pair in qe_data}
439
+ return QuantumEfficiency(wavelength_qe=wavelength_qe)
440
+
441
+
442
+ # Function to create detector from config
443
+ def create_detector_from_config(
444
+ config: Dict[str, Any],
445
+ ) -> Tuple[Detector, QuantumEfficiency]:
446
+ """Create a detector instance from a configuration dictionary."""
447
+ camera_config = config.get("camera", {})
448
+ if not camera_config:
449
+ raise ValueError("No camera configuration found in config")
450
+
451
+ qe_data = camera_config.get("quantum_efficiency", [])
452
+ quantum_efficiency = create_quantum_efficiency_from_config(qe_data)
453
+
454
+ pixel_size = find_pixel_size(
455
+ camera_config["magnification"], camera_config["pixel_detector_size"]
456
+ )
457
+
458
+ common_params = {
459
+ "pixel_size": pixel_size,
460
+ "dark_current": float(camera_config["dark_current"]),
461
+ "readout_noise": float(camera_config["readout_noise"]),
462
+ "pixel_count": tuple([int(i) for i in camera_config["pixel_count"]]),
463
+ "bit_depth": int(camera_config.get("bit_depth", 16)),
464
+ "sensitivity": float(camera_config.get("sensitivity", 1.0)),
465
+ "pixel_detector_size": float(camera_config["pixel_detector_size"]),
466
+ "magnification": float(camera_config["magnification"]),
467
+ "base_adu": int(camera_config["base_adu"]),
468
+ "binning_size": int(camera_config["binning_size"]),
469
+ }
470
+
471
+ camera_type = camera_config.get("type", "").upper()
472
+
473
+ if camera_type == "CMOS":
474
+ detector = CMOSDetector(**common_params)
475
+ elif camera_type == "EMCCD":
476
+ em_params = {
477
+ "em_gain": float(camera_config.get("em_gain", 300)),
478
+ "clock_induced_charge": float(
479
+ camera_config.get("clock_induced_charge", 0.002)
480
+ ),
735
481
  }
736
- return return_dict
482
+ detector = EMCCDDetector(
483
+ **common_params,
484
+ em_gain=em_params["em_gain"],
485
+ clock_induced_charge=em_params["clock_induced_charge"],
486
+ )
487
+ else:
488
+ raise ValueError(f"Unsupported camera type: {camera_type}")
737
489
 
490
+ return detector, quantum_efficiency
738
491
 
739
- def make_cell(cell_params) -> BaseCell:
740
- # make cell
741
492
 
742
- cell = create_cell(cell_params.cell_type, cell_params.params)
493
+ # Function to validate the experiment duration time
494
+ def duration_time_validation_experiments(configEXP, global_params) -> bool:
495
+ if configEXP.exposure_time:
496
+ if len(configEXP.z_position) * (
497
+ configEXP.exposure_time + configEXP.interval_time
498
+ ) > global_params.cycle_count * (
499
+ global_params.exposure_time + global_params.interval_time
500
+ ):
501
+ print(
502
+ f"Z-series parameters overriding the set Global_parameters. cycle_count: {len(configEXP.z_position)}, exposure_time: {configEXP.exposure_time}, and interval_time: {configEXP.interval_time}."
503
+ )
504
+ global_params.cycle_count = len(configEXP.z_position)
505
+ global_params.exposure_time = configEXP.exposure_time
506
+ global_params.interval_time = configEXP.interval_time
743
507
 
744
- return cell
508
+ return False
509
+ else:
510
+ return True
511
+ else:
512
+ return True
513
+
514
+
515
+ # Function to create base config from parameters
516
+ def create_base_config(
517
+ global_params, cell_params, molecule_params, condensate_params, output_params
518
+ ):
519
+ return ConfigList(
520
+ CellParameter=cell_params,
521
+ MoleculeParameter=molecule_params,
522
+ GlobalParameter=global_params,
523
+ CondensateParameter=condensate_params,
524
+ OutputParameter=output_params,
525
+ )
745
526
 
746
527
 
528
+ # Function to create sample plane
747
529
  def make_sample(global_params: GlobalParameters, cell: BaseCell) -> SamplePlane:
748
530
  bounds = cell.boundingbox
749
531
  sample_space = SampleSpace(
@@ -753,25 +535,33 @@ def make_sample(global_params: GlobalParameters, cell: BaseCell) -> SamplePlane:
753
535
  z_min=bounds[-2],
754
536
  )
755
537
 
756
- # total time
538
+ # Total time
757
539
  totaltime = int(
758
540
  global_params.cycle_count
759
541
  * (global_params.exposure_time + global_params.interval_time)
760
542
  )
761
- # initialize sample plane
543
+
544
+ # Initialize sample plane
762
545
  sample_plane = SamplePlane(
763
546
  sample_space=sample_space,
764
547
  fov=(
765
548
  (0, global_params.sample_plane_dim[0]),
766
549
  (0, global_params.sample_plane_dim[1]),
767
550
  (bounds[-2], bounds[-1]),
768
- ), # simulates the whole simulation space to avoid the issue of PSF bleeding into FOV if the molecule's location is technically outside of the FOV dictated by the camera detector size and objective magnification.
551
+ ),
769
552
  oversample_motion_time=global_params.oversample_motion_time,
770
553
  t_end=totaltime,
771
554
  )
772
555
  return sample_plane
773
556
 
774
557
 
558
+ # Function to create cell
559
+ def make_cell(cell_params) -> BaseCell:
560
+ cell = create_cell(cell_params.cell_type, cell_params.params)
561
+ return cell
562
+
563
+
564
+ # Function to create condensates dict
775
565
  def make_condensatedict(
776
566
  condensate_params: CondensateParameters, cell: BaseCell
777
567
  ) -> List[dict]:
@@ -789,6 +579,7 @@ def make_condensatedict(
789
579
  return condensates_dict
790
580
 
791
581
 
582
+ # Function to create sampling functions
792
583
  def make_samplingfunction(condensate_params, cell) -> List[Callable]:
793
584
  sampling_functions = []
794
585
  for i in range(len(condensate_params.initial_centers)):
@@ -804,6 +595,7 @@ def make_samplingfunction(condensate_params, cell) -> List[Callable]:
804
595
  return sampling_functions
805
596
 
806
597
 
598
+ # Function to generate initial positions for molecules
807
599
  def gen_initial_positions(
808
600
  molecule_params: MoleculeParameters,
809
601
  cell: BaseCell,
@@ -824,6 +616,7 @@ def gen_initial_positions(
824
616
  return initials
825
617
 
826
618
 
619
+ # Function to create track generator
827
620
  def create_track_generator(
828
621
  global_params: GlobalParameters, cell: BaseCell
829
622
  ) -> Track_generator:
@@ -831,7 +624,6 @@ def create_track_generator(
831
624
  global_params.cycle_count
832
625
  * (global_params.exposure_time + global_params.interval_time)
833
626
  )
834
- # make track generator
835
627
  track_generator = Track_generator(
836
628
  cell=cell,
837
629
  total_time=totaltime,
@@ -840,6 +632,7 @@ def create_track_generator(
840
632
  return track_generator
841
633
 
842
634
 
635
+ # Function to get tracks
843
636
  def get_tracks(
844
637
  molecule_params: MoleculeParameters,
845
638
  global_params: GlobalParameters,
@@ -870,12 +663,12 @@ def get_tracks(
870
663
  diffusion_parameters=molecule_params.diffusion_coefficient[i],
871
664
  hurst_parameters=molecule_params.hurst_exponent[i],
872
665
  diffusion_transition_matrix=change_prob_time(
873
- molecule_params.diffusion_transition_matrix[i],
666
+ np.array(molecule_params.diffusion_transition_matrix[i]),
874
667
  molecule_params.transition_matrix_time_step[i],
875
668
  global_params.oversample_motion_time,
876
669
  ),
877
670
  hurst_transition_matrix=change_prob_time(
878
- molecule_params.hurst_transition_matrix[i],
671
+ np.array(molecule_params.hurst_transition_matrix[i]),
879
672
  molecule_params.transition_matrix_time_step[i],
880
673
  global_params.oversample_motion_time,
881
674
  ),
@@ -900,6 +693,7 @@ def get_tracks(
900
693
  return tracks_collection, points_per_time_collection
901
694
 
902
695
 
696
+ # Function to add tracks to sample plane
903
697
  def add_tracks_to_sample(
904
698
  tracks: List,
905
699
  sample_plane: SamplePlane,
@@ -917,3 +711,85 @@ def add_tracks_to_sample(
917
711
  )
918
712
  counter += 1
919
713
  return sample_plane
714
+
715
+
716
+ # Function to set up the virtual microscope and return the configuration dictionary
717
+ def setup_microscope(config: Dict[str, Any]) -> dict:
718
+ global_params, cell_params, molecule_params, condensate_params, output_params = (
719
+ populate_dataclass_schema(config)
720
+ )
721
+ configEXP, funcEXP = create_experiment_from_config(config=config)
722
+ duration_time_validation_experiments(configEXP, global_params)
723
+ base_config = create_base_config(
724
+ global_params, cell_params, molecule_params, condensate_params, output_params
725
+ )
726
+
727
+ # fluorophore config
728
+ fluorophores = create_fluorophores_from_config(config)
729
+ # psf config
730
+ psf, psf_config = create_psf_from_config(config)
731
+ # lasers config
732
+ lasers = create_lasers_from_config(config)
733
+ # channels config
734
+ channels = create_channels(config)
735
+ # detector config
736
+ detector, qe = create_detector_from_config(config)
737
+
738
+ # make cell
739
+ cell = make_cell(base_config.CellParameter)
740
+
741
+ # make initial sample plane
742
+ sample_plane = make_sample(base_config.GlobalParameter, cell)
743
+
744
+ # make condensates_dict
745
+ condensates_dict = make_condensatedict(base_config.CondensateParameter, cell)
746
+
747
+ # make sampling function
748
+ sampling_functions = make_samplingfunction(base_config.CondensateParameter, cell)
749
+
750
+ # create initial positions
751
+ initial_molecule_positions = gen_initial_positions(
752
+ base_config.MoleculeParameter,
753
+ cell,
754
+ base_config.CondensateParameter,
755
+ sampling_functions,
756
+ )
757
+
758
+ # create the track generator
759
+ track_generators = create_track_generator(base_config.GlobalParameter, cell)
760
+
761
+ # get all the tracks
762
+ tracks, points_per_time = get_tracks(
763
+ base_config.MoleculeParameter,
764
+ base_config.GlobalParameter,
765
+ initial_molecule_positions,
766
+ track_generators,
767
+ )
768
+
769
+ # add tracks to sample
770
+ sample_plane = add_tracks_to_sample(tracks, sample_plane, fluorophores)
771
+
772
+ vm = VirtualMicroscope(
773
+ camera=(detector, qe),
774
+ sample_plane=sample_plane,
775
+ lasers=lasers,
776
+ channels=channels,
777
+ psf=psf,
778
+ config=base_config,
779
+ )
780
+
781
+ return {
782
+ "microscope": vm,
783
+ "base_config": base_config,
784
+ "psf": psf,
785
+ "psf_config": psf_config,
786
+ "channels": channels,
787
+ "lasers": lasers,
788
+ "sample_plane": sample_plane,
789
+ "tracks": tracks,
790
+ "points_per_time": points_per_time,
791
+ "condensate_dict": condensates_dict,
792
+ "cell": cell,
793
+ "experiment_config": configEXP,
794
+ "experiment_func": funcEXP,
795
+ }