AMS-BP 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- AMS_BP/__init__.py +1 -1
- AMS_BP/configio/configmodels.py +32 -18
- AMS_BP/configio/convertconfig.py +508 -632
- AMS_BP/gui/README.md +77 -0
- AMS_BP/gui/__init__.py +0 -0
- AMS_BP/gui/assets/__init__.py +0 -0
- AMS_BP/gui/assets/drawing.svg +107 -0
- AMS_BP/gui/configuration_window.py +333 -0
- AMS_BP/gui/help_docs/__init__.py +0 -0
- AMS_BP/gui/help_docs/cell_help.md +45 -0
- AMS_BP/gui/help_docs/channels_help.md +78 -0
- AMS_BP/gui/help_docs/condensate_help.md +59 -0
- AMS_BP/gui/help_docs/detector_help.md +57 -0
- AMS_BP/gui/help_docs/experiment_help.md +92 -0
- AMS_BP/gui/help_docs/fluorophore_help.md +128 -0
- AMS_BP/gui/help_docs/general_help.md +43 -0
- AMS_BP/gui/help_docs/global_help.md +47 -0
- AMS_BP/gui/help_docs/laser_help.md +76 -0
- AMS_BP/gui/help_docs/molecule_help.md +78 -0
- AMS_BP/gui/help_docs/output_help.md +5 -0
- AMS_BP/gui/help_docs/psf_help.md +51 -0
- AMS_BP/gui/help_window.py +26 -0
- AMS_BP/gui/logging_window.py +93 -0
- AMS_BP/gui/main.py +255 -0
- AMS_BP/gui/sim_worker.py +58 -0
- AMS_BP/gui/template_window_selection.py +100 -0
- AMS_BP/gui/widgets/__init__.py +0 -0
- AMS_BP/gui/widgets/camera_config_widget.py +213 -0
- AMS_BP/gui/widgets/cell_config_widget.py +225 -0
- AMS_BP/gui/widgets/channel_config_widget.py +307 -0
- AMS_BP/gui/widgets/condensate_config_widget.py +341 -0
- AMS_BP/gui/widgets/experiment_config_widget.py +259 -0
- AMS_BP/gui/widgets/flurophore_config_widget.py +513 -0
- AMS_BP/gui/widgets/general_config_widget.py +47 -0
- AMS_BP/gui/widgets/global_config_widget.py +142 -0
- AMS_BP/gui/widgets/laser_config_widget.py +255 -0
- AMS_BP/gui/widgets/molecule_config_widget.py +714 -0
- AMS_BP/gui/widgets/output_config_widget.py +61 -0
- AMS_BP/gui/widgets/psf_config_widget.py +128 -0
- AMS_BP/gui/widgets/utility_widgets/__init__.py +0 -0
- AMS_BP/gui/widgets/utility_widgets/scinotation_widget.py +21 -0
- AMS_BP/gui/widgets/utility_widgets/spectrum_widget.py +115 -0
- AMS_BP/logging/__init__.py +0 -0
- AMS_BP/logging/logutil.py +83 -0
- AMS_BP/logging/setup_run_directory.py +35 -0
- AMS_BP/{run_cell_simulation.py → main_cli.py} +27 -72
- AMS_BP/optics/filters/filters.py +2 -0
- AMS_BP/resources/template_configs/metadata_configs.json +20 -0
- AMS_BP/resources/template_configs/sim_config.toml +408 -0
- AMS_BP/resources/template_configs/twocolor_widefield_timeseries_live.toml +399 -0
- AMS_BP/resources/template_configs/twocolor_widefield_zstack_fixed.toml +406 -0
- AMS_BP/resources/template_configs/twocolor_widefield_zstack_live.toml +408 -0
- AMS_BP/run_sim_util.py +76 -0
- AMS_BP/sim_microscopy.py +2 -2
- {ams_bp-0.3.0.dist-info → ams_bp-0.4.0.dist-info}/METADATA +59 -34
- ams_bp-0.4.0.dist-info/RECORD +103 -0
- ams_bp-0.4.0.dist-info/entry_points.txt +2 -0
- ams_bp-0.3.0.dist-info/RECORD +0 -55
- ams_bp-0.3.0.dist-info/entry_points.txt +0 -2
- {ams_bp-0.3.0.dist-info → ams_bp-0.4.0.dist-info}/WHEEL +0 -0
- {ams_bp-0.3.0.dist-info → ams_bp-0.4.0.dist-info}/licenses/LICENSE +0 -0
AMS_BP/configio/convertconfig.py
CHANGED
@@ -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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
""
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
131
|
-
|
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
|
-
|
134
|
-
|
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
|
-
|
137
|
-
|
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
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
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
|
290
|
-
|
291
|
-
)
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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
|
-
|
322
|
-
|
323
|
-
)
|
324
|
-
|
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
|
-
|
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
|
-
|
345
|
-
|
346
|
-
|
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
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
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
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
""
|
470
|
-
|
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
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
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
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
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
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
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
|
-
|
642
|
-
|
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
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
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
|
-
|
683
|
-
|
684
|
-
|
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
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
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
|
-
|
696
|
-
|
697
|
-
|
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
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
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
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
),
|
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
|
+
}
|