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 | 
            +
                }
         |