AMS-BP 0.4.31__py3-none-any.whl → 0.4.41__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 → core/configio}/experiments.py +5 -0
- AMS_BP/{motion → core/motion}/condensate_movement.py +1 -1
- AMS_BP/{motion → core/motion}/movement/boundary_conditions.py +1 -1
- AMS_BP/{photophysics → core/photophysics}/photon_physics.py +1 -1
- AMS_BP/{sample → core/sample}/flurophores/flurophore_schema.py +1 -1
- AMS_BP/{sim_microscopy.py → core/sim_microscopy.py} +1 -1
- AMS_BP/gui/main.py +4 -4
- AMS_BP/gui/sim_worker.py +3 -4
- AMS_BP/gui/widgets/camera_config_widget.py +4 -5
- AMS_BP/gui/widgets/cell_config_widget.py +3 -3
- AMS_BP/gui/widgets/channel_config_widget.py +2 -2
- AMS_BP/gui/widgets/condensate_config_widget.py +4 -4
- AMS_BP/gui/widgets/experiment_config_widget.py +53 -2
- AMS_BP/gui/widgets/flurophore_config_widget.py +1 -2
- AMS_BP/gui/widgets/global_config_widget.py +2 -2
- AMS_BP/gui/widgets/laser_config_widget.py +58 -13
- AMS_BP/gui/widgets/molecule_config_widget.py +3 -6
- AMS_BP/gui/windows/__init__.py +0 -0
- AMS_BP/gui/{configuration_window.py → windows/configuration_window.py} +15 -12
- AMS_BP/gui/{template_window_selection.py → windows/template_window_selection.py} +35 -5
- AMS_BP/main_cli.py +2 -2
- AMS_BP/resources/template_configs/metadata_configs.json +21 -3
- AMS_BP/resources/template_configs/twocolor_confocal_timeseries_live.toml +399 -0
- AMS_BP/resources/template_configs/twocolor_confocal_zstack_fixed.toml +406 -0
- AMS_BP/{sim_config.toml → resources/template_configs/twocolor_confocal_zstack_live.toml} +111 -111
- AMS_BP/tools/logging/__init__.py +0 -0
- {ams_bp-0.4.31.dist-info → ams_bp-0.4.41.dist-info}/METADATA +7 -2
- ams_bp-0.4.41.dist-info/RECORD +110 -0
- ams_bp-0.4.31.dist-info/RECORD +0 -106
- /AMS_BP/{configio → core}/__init__.py +0 -0
- /AMS_BP/{cells → core/cells}/__init__.py +0 -0
- /AMS_BP/{cells → core/cells}/budding_yeast_cell.py +0 -0
- /AMS_BP/{cells → core/cells}/cell_factory.py +0 -0
- /AMS_BP/{logging → core/configio}/__init__.py +0 -0
- /AMS_BP/{configio → core/configio}/configmodels.py +0 -0
- /AMS_BP/{configio → core/configio}/convertconfig.py +0 -0
- /AMS_BP/{configio → core/configio}/saving.py +0 -0
- /AMS_BP/{groundtruth_generators → core/groundtruth_generators}/__init__.py +0 -0
- /AMS_BP/{groundtruth_generators → core/groundtruth_generators}/nuclearporecomplexes.py +0 -0
- /AMS_BP/{metadata → core/metadata}/__init__.py +0 -0
- /AMS_BP/{metadata → core/metadata}/metadata.py +0 -0
- /AMS_BP/{motion → core/motion}/__init__.py +0 -0
- /AMS_BP/{motion → core/motion}/movement/__init__.py +0 -0
- /AMS_BP/{motion → core/motion}/track_gen.py +0 -0
- /AMS_BP/{optics → core/optics}/__init__.py +0 -0
- /AMS_BP/{optics → core/optics}/camera/__init__.py +0 -0
- /AMS_BP/{optics → core/optics}/camera/detectors.py +0 -0
- /AMS_BP/{optics → core/optics}/camera/quantum_eff.py +0 -0
- /AMS_BP/{optics → core/optics}/filters/__init__.py +0 -0
- /AMS_BP/{optics → core/optics}/filters/channels/__init__.py +0 -0
- /AMS_BP/{optics → core/optics}/filters/channels/channelschema.py +0 -0
- /AMS_BP/{optics → core/optics}/filters/filters.py +0 -0
- /AMS_BP/{optics → core/optics}/lasers/__init__.py +0 -0
- /AMS_BP/{optics → core/optics}/lasers/laser_profiles.py +0 -0
- /AMS_BP/{optics → core/optics}/lasers/scanning_patterns.py +0 -0
- /AMS_BP/{optics → core/optics}/psf/__init__.py +0 -0
- /AMS_BP/{optics → core/optics}/psf/psf_engine.py +0 -0
- /AMS_BP/{photophysics → core/photophysics}/__init__.py +0 -0
- /AMS_BP/{photophysics → core/photophysics}/state_kinetics.py +0 -0
- /AMS_BP/{probabilityfuncs → core/probabilityfuncs}/__init__.py +0 -0
- /AMS_BP/{probabilityfuncs → core/probabilityfuncs}/markov_chain.py +0 -0
- /AMS_BP/{probabilityfuncs → core/probabilityfuncs}/probability_functions.py +0 -0
- /AMS_BP/{run_sim_util.py → core/run_sim_util.py} +0 -0
- /AMS_BP/{sample → core/sample}/__init__.py +0 -0
- /AMS_BP/{sample → core/sample}/flurophores/__init__.py +0 -0
- /AMS_BP/{sample → core/sample}/sim_sampleplane.py +0 -0
- /AMS_BP/gui/{help_window.py → windows/help_window.py} +0 -0
- /AMS_BP/gui/{logging_window.py → windows/logging_window.py} +0 -0
- /AMS_BP/{logging → tools/logging}/logutil.py +0 -0
- /AMS_BP/{logging → tools/logging}/setup_run_directory.py +0 -0
- {ams_bp-0.4.31.dist-info → ams_bp-0.4.41.dist-info}/WHEEL +0 -0
- {ams_bp-0.4.31.dist-info → ams_bp-0.4.41.dist-info}/entry_points.txt +0 -0
- {ams_bp-0.4.31.dist-info → ams_bp-0.4.41.dist-info}/licenses/LICENSE +0 -0
AMS_BP/__init__.py
CHANGED
@@ -20,6 +20,7 @@ class TimeSeriesExpConfig(BaseExpConfig):
|
|
20
20
|
laser_powers_active: List[float]
|
21
21
|
laser_positions_active: List
|
22
22
|
xyoffset: Tuple[float, float]
|
23
|
+
scanning: bool = False
|
23
24
|
|
24
25
|
exposure_time: Optional[int] = None
|
25
26
|
interval_time: Optional[int] = None
|
@@ -61,6 +62,8 @@ class zStackExpConfig(BaseExpConfig):
|
|
61
62
|
exposure_time: int
|
62
63
|
interval_time: int
|
63
64
|
|
65
|
+
scanning: bool = False
|
66
|
+
|
64
67
|
def __post_init__(self):
|
65
68
|
len_ln = len(self.laser_names_active)
|
66
69
|
len_lpow = len(self.laser_powers_active)
|
@@ -94,6 +97,7 @@ def timeseriesEXP(
|
|
94
97
|
duration_total=config.duration_time,
|
95
98
|
exposure_time=config.exposure_time,
|
96
99
|
interval_time=config.interval_time,
|
100
|
+
scanning=config.scanning,
|
97
101
|
)
|
98
102
|
return np.array([frames]), metadata
|
99
103
|
|
@@ -112,6 +116,7 @@ def zseriesEXP(
|
|
112
116
|
duration_total=config.exposure_time + config.interval_time,
|
113
117
|
exposure_time=config.exposure_time,
|
114
118
|
interval_time=config.interval_time,
|
119
|
+
scanning=config.scanning,
|
115
120
|
)
|
116
121
|
frames.append(f)
|
117
122
|
# m.Channel = {"name": microscope.channels.names}
|
@@ -5,7 +5,7 @@ Removal Time: NDY (not determined yet)
|
|
5
5
|
|
6
6
|
import numpy as np
|
7
7
|
|
8
|
-
from
|
8
|
+
from ....utils.decorators import _catch_recursion_error, deprecated
|
9
9
|
|
10
10
|
# Reflecting boundary condition which is a recursive function so that even if the first candidate
|
11
11
|
# is out of the space limit, the function will keep calling itself until the candidate is within the space limit
|
@@ -3,6 +3,7 @@ from typing import Callable, List, Optional, Tuple
|
|
3
3
|
|
4
4
|
import numpy as np
|
5
5
|
|
6
|
+
from ...utils.constants import H_C_COM
|
6
7
|
from ..optics.camera.detectors import photon_noise
|
7
8
|
from ..optics.camera.quantum_eff import QuantumEfficiency
|
8
9
|
from ..optics.filters.filters import FilterSpectrum
|
@@ -11,7 +12,6 @@ from ..sample.flurophores.flurophore_schema import (
|
|
11
12
|
SpectralData,
|
12
13
|
WavelengthDependentProperty,
|
13
14
|
)
|
14
|
-
from ..utils.constants import H_C_COM
|
15
15
|
|
16
16
|
|
17
17
|
@dataclass
|
@@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, List, Optional, TypeVar
|
|
4
4
|
import numpy as np
|
5
5
|
from pydantic import BaseModel, Field, field_validator
|
6
6
|
|
7
|
-
from
|
7
|
+
from ....utils.constants import H_C_COM, N_A
|
8
8
|
|
9
9
|
NumericType = TypeVar("NumericType", float, np.ndarray, List[float])
|
10
10
|
|
@@ -3,6 +3,7 @@ from typing import Callable, Dict, List, Literal, Optional, Tuple, Union
|
|
3
3
|
|
4
4
|
import numpy as np
|
5
5
|
|
6
|
+
from ..utils.util_functions import ms_to_seconds
|
6
7
|
from .configio.configmodels import ConfigList
|
7
8
|
from .metadata.metadata import MetaData
|
8
9
|
from .optics.camera.detectors import Detector
|
@@ -18,7 +19,6 @@ from .photophysics.photon_physics import (
|
|
18
19
|
from .photophysics.state_kinetics import StateTransitionCalculator
|
19
20
|
from .sample.flurophores.flurophore_schema import StateType, WavelengthDependentProperty
|
20
21
|
from .sample.sim_sampleplane import EMPTY_STATE_HISTORY_DICT, SamplePlane
|
21
|
-
from .utils.util_functions import ms_to_seconds
|
22
22
|
|
23
23
|
|
24
24
|
class VirtualMicroscope:
|
AMS_BP/gui/main.py
CHANGED
@@ -18,12 +18,12 @@ from PyQt6.QtWidgets import (
|
|
18
18
|
QWidget,
|
19
19
|
)
|
20
20
|
|
21
|
-
from ..logging.logutil import LoggerManager
|
22
|
-
from ..logging.setup_run_directory import setup_run_directory
|
23
|
-
from .logging_window import LogWindow
|
21
|
+
from ..tools.logging.logutil import LoggerManager
|
22
|
+
from ..tools.logging.setup_run_directory import setup_run_directory
|
24
23
|
from .sim_worker import SimulationWorker
|
25
|
-
from .template_window_selection import TemplateSelectionWindow
|
26
24
|
from .widgets.utility_widgets.toggleswitch_widget import ToggleSwitch
|
25
|
+
from .windows.logging_window import LogWindow
|
26
|
+
from .windows.template_window_selection import TemplateSelectionWindow
|
27
27
|
|
28
28
|
LOGO_PATH = str(Path(__file__).parent / "assets" / "drawing.svg")
|
29
29
|
|
AMS_BP/gui/sim_worker.py
CHANGED
@@ -2,7 +2,9 @@ from pathlib import Path
|
|
2
2
|
|
3
3
|
from PyQt6.QtCore import QObject, pyqtSignal
|
4
4
|
|
5
|
-
from ..
|
5
|
+
from ..core.configio.convertconfig import load_config, setup_microscope
|
6
|
+
from ..core.configio.saving import save_config_frames
|
7
|
+
from ..tools.logging.logutil import LogEmitter
|
6
8
|
|
7
9
|
|
8
10
|
class SimulationWorker(QObject):
|
@@ -20,9 +22,6 @@ class SimulationWorker(QObject):
|
|
20
22
|
try:
|
21
23
|
self.emitter.message.emit(f"Starting simulation for {self.config_path}")
|
22
24
|
|
23
|
-
from ..configio.convertconfig import load_config, setup_microscope
|
24
|
-
from ..configio.saving import save_config_frames
|
25
|
-
|
26
25
|
loadedconfig = load_config(self.config_path)
|
27
26
|
|
28
27
|
if "version" in loadedconfig:
|
@@ -14,6 +14,10 @@ from PyQt6.QtWidgets import (
|
|
14
14
|
QWidget,
|
15
15
|
)
|
16
16
|
|
17
|
+
from ...core.configio.convertconfig import create_quantum_efficiency_from_config
|
18
|
+
from ...core.optics.camera.detectors import (
|
19
|
+
CMOSDetector,
|
20
|
+
)
|
17
21
|
from .utility_widgets.spectrum_widget import SpectrumEditorDialog
|
18
22
|
|
19
23
|
|
@@ -122,11 +126,6 @@ class CameraConfigWidget(QWidget):
|
|
122
126
|
# You can now use this data wherever needed, e.g., saving or validation
|
123
127
|
|
124
128
|
def validate(self) -> bool:
|
125
|
-
from ...configio.convertconfig import create_quantum_efficiency_from_config
|
126
|
-
from ...optics.camera.detectors import (
|
127
|
-
CMOSDetector,
|
128
|
-
)
|
129
|
-
|
130
129
|
try:
|
131
130
|
data = self.get_data()
|
132
131
|
|
@@ -14,6 +14,9 @@ from PyQt6.QtWidgets import (
|
|
14
14
|
QWidget,
|
15
15
|
)
|
16
16
|
|
17
|
+
from ...core.cells import create_cell
|
18
|
+
from ...core.configio.configmodels import CellParameters
|
19
|
+
|
17
20
|
|
18
21
|
class CellConfigWidget(QWidget):
|
19
22
|
def __init__(self):
|
@@ -53,9 +56,6 @@ class CellConfigWidget(QWidget):
|
|
53
56
|
layout.addWidget(self.validate_button)
|
54
57
|
|
55
58
|
def validate(self) -> bool:
|
56
|
-
from ...cells import create_cell
|
57
|
-
from ...configio.configmodels import CellParameters
|
58
|
-
|
59
59
|
try:
|
60
60
|
data = self.get_data()
|
61
61
|
|
@@ -15,6 +15,8 @@ from PyQt6.QtWidgets import (
|
|
15
15
|
QWidget,
|
16
16
|
)
|
17
17
|
|
18
|
+
from ...core.configio.convertconfig import create_channels
|
19
|
+
|
18
20
|
|
19
21
|
class ChannelConfigWidget(QWidget):
|
20
22
|
def __init__(self):
|
@@ -48,8 +50,6 @@ class ChannelConfigWidget(QWidget):
|
|
48
50
|
|
49
51
|
def validate(self) -> bool:
|
50
52
|
try:
|
51
|
-
from ...configio.convertconfig import create_channels
|
52
|
-
|
53
53
|
data = self.get_data()
|
54
54
|
|
55
55
|
# Full simulation-level validation
|
@@ -18,6 +18,10 @@ from PyQt6.QtWidgets import (
|
|
18
18
|
QWidget,
|
19
19
|
)
|
20
20
|
|
21
|
+
from ...core.cells import create_cell
|
22
|
+
from ...core.configio.configmodels import CondensateParameters
|
23
|
+
from ...core.motion import create_condensate_dict
|
24
|
+
|
21
25
|
|
22
26
|
class CondensateConfigWidget(QWidget):
|
23
27
|
# Signal to notify when molecule count changes
|
@@ -300,10 +304,6 @@ class CondensateConfigWidget(QWidget):
|
|
300
304
|
}
|
301
305
|
|
302
306
|
def validate(self) -> bool:
|
303
|
-
from ...cells import create_cell
|
304
|
-
from ...configio.configmodels import CondensateParameters
|
305
|
-
from ...motion import create_condensate_dict
|
306
|
-
|
307
307
|
try:
|
308
308
|
data = self.get_data()
|
309
309
|
|
@@ -1,10 +1,12 @@
|
|
1
1
|
from pathlib import Path
|
2
2
|
from typing import List
|
3
3
|
|
4
|
+
from PyQt6.QtCore import Qt, QTimer
|
4
5
|
from PyQt6.QtWidgets import (
|
5
6
|
QComboBox,
|
6
7
|
QDoubleSpinBox,
|
7
8
|
QFormLayout,
|
9
|
+
QGraphicsOpacityEffect,
|
8
10
|
QHBoxLayout,
|
9
11
|
QLabel,
|
10
12
|
QLineEdit,
|
@@ -18,6 +20,8 @@ from PyQt6.QtWidgets import (
|
|
18
20
|
QWidget,
|
19
21
|
)
|
20
22
|
|
23
|
+
from ...core.configio.convertconfig import create_experiment_from_config
|
24
|
+
|
21
25
|
|
22
26
|
class ExperimentConfigWidget(QWidget):
|
23
27
|
def __init__(self):
|
@@ -31,6 +35,32 @@ class ExperimentConfigWidget(QWidget):
|
|
31
35
|
layout.setSpacing(10)
|
32
36
|
self.setLayout(layout)
|
33
37
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
38
|
+
|
39
|
+
self.scanning_area = QWidget()
|
40
|
+
self.scanning_area_layout = QVBoxLayout()
|
41
|
+
self.scanning_area_layout.setContentsMargins(0, 0, 0, 0)
|
42
|
+
self.scanning_area_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
43
|
+
self.scanning_area.setLayout(self.scanning_area_layout)
|
44
|
+
layout.addWidget(self.scanning_area)
|
45
|
+
# Setup scanning label
|
46
|
+
self.scanning_label = QLabel("Scanning Confocal Selected")
|
47
|
+
self.scanning_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
48
|
+
self.scanning_label.setStyleSheet(
|
49
|
+
"color: green; font-weight: bold; font-size: 16px;"
|
50
|
+
)
|
51
|
+
layout.addWidget(self.scanning_label)
|
52
|
+
# Reserve space by always having label active, even if invisible
|
53
|
+
self.opacity_effect = QGraphicsOpacityEffect(self.scanning_label)
|
54
|
+
self.scanning_label.setGraphicsEffect(self.opacity_effect)
|
55
|
+
self.opacity_effect.setOpacity(0.0) # start invisible but reserved
|
56
|
+
|
57
|
+
# Blinking mode control
|
58
|
+
self._scanning_mode = False
|
59
|
+
self._blinking = False
|
60
|
+
self.blink_timer = QTimer(self)
|
61
|
+
self.blink_timer.setInterval(500) # ms
|
62
|
+
self.blink_timer.timeout.connect(self._toggle_scanning_label_visibility)
|
63
|
+
|
34
64
|
form = QFormLayout()
|
35
65
|
|
36
66
|
# Experiment Info
|
@@ -103,6 +133,24 @@ class ExperimentConfigWidget(QWidget):
|
|
103
133
|
self.validate_button.clicked.connect(self.validate)
|
104
134
|
layout.addWidget(self.validate_button)
|
105
135
|
|
136
|
+
def set_scanning_mode(self, enabled: bool):
|
137
|
+
self._scanning_mode = enabled
|
138
|
+
if enabled:
|
139
|
+
self.opacity_effect.setOpacity(1.0)
|
140
|
+
self.blink_timer.start()
|
141
|
+
else:
|
142
|
+
self.opacity_effect.setOpacity(0.0)
|
143
|
+
self.blink_timer.stop()
|
144
|
+
|
145
|
+
def _toggle_scanning_label_visibility(self):
|
146
|
+
"""Toggles label opacity to create blinking without layout shifting."""
|
147
|
+
if self._scanning_mode:
|
148
|
+
current_opacity = self.opacity_effect.opacity()
|
149
|
+
new_opacity = 0.0 if current_opacity > 0.5 else 1.0
|
150
|
+
self.opacity_effect.setOpacity(new_opacity)
|
151
|
+
else:
|
152
|
+
self.opacity_effect.setOpacity(0.0)
|
153
|
+
|
106
154
|
def update_z_position_mode(self, mode: str):
|
107
155
|
# Clear existing
|
108
156
|
for i in reversed(range(self.z_position_layout.count())):
|
@@ -202,6 +250,7 @@ class ExperimentConfigWidget(QWidget):
|
|
202
250
|
for name in self.laser_position_widgets
|
203
251
|
],
|
204
252
|
"xyoffset": [w.value() for w in self.xyoffset],
|
253
|
+
"scanning": self._scanning_mode,
|
205
254
|
}
|
206
255
|
|
207
256
|
if data["experiment_type"] == "z-stack":
|
@@ -249,10 +298,12 @@ class ExperimentConfigWidget(QWidget):
|
|
249
298
|
positions = data.get("laser_positions_active", [])
|
250
299
|
self.set_active_lasers(laser_names, powers=powers, positions=positions)
|
251
300
|
|
301
|
+
# Scanning Confocal
|
302
|
+
scanning_mode = data.get("scanning", False)
|
303
|
+
self.set_scanning_mode(scanning_mode)
|
304
|
+
|
252
305
|
def validate(self) -> bool:
|
253
306
|
try:
|
254
|
-
from ...configio.convertconfig import create_experiment_from_config
|
255
|
-
|
256
307
|
data = self.get_data()
|
257
308
|
config_dict = {"experiment": data}
|
258
309
|
|
@@ -18,6 +18,7 @@ from PyQt6.QtWidgets import (
|
|
18
18
|
QWidget,
|
19
19
|
)
|
20
20
|
|
21
|
+
from ...core.configio.convertconfig import create_fluorophores_from_config
|
21
22
|
from .utility_widgets.scinotation_widget import scientific_input_field
|
22
23
|
from .utility_widgets.spectrum_widget import SpectrumEditorDialog
|
23
24
|
|
@@ -495,8 +496,6 @@ class FluorophoreConfigWidget(QWidget):
|
|
495
496
|
|
496
497
|
def validate(self) -> bool:
|
497
498
|
try:
|
498
|
-
from ...configio.convertconfig import create_fluorophores_from_config
|
499
|
-
|
500
499
|
data = self.get_data()
|
501
500
|
# Try to build fluorophores with the backend logic
|
502
501
|
|
@@ -11,6 +11,8 @@ from PyQt6.QtWidgets import (
|
|
11
11
|
QWidget,
|
12
12
|
)
|
13
13
|
|
14
|
+
from ...core.configio.configmodels import GlobalParameters
|
15
|
+
|
14
16
|
|
15
17
|
class GlobalConfigWidget(QWidget):
|
16
18
|
def __init__(self):
|
@@ -122,8 +124,6 @@ class GlobalConfigWidget(QWidget):
|
|
122
124
|
|
123
125
|
def validate(self) -> bool:
|
124
126
|
try:
|
125
|
-
from ...configio.configmodels import GlobalParameters
|
126
|
-
|
127
127
|
data = self.get_data()
|
128
128
|
|
129
129
|
GlobalParameters(**data)
|
@@ -1,10 +1,12 @@
|
|
1
1
|
from pathlib import Path
|
2
2
|
|
3
|
-
from PyQt6.QtCore import pyqtSignal
|
3
|
+
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
4
4
|
from PyQt6.QtWidgets import (
|
5
5
|
QComboBox,
|
6
6
|
QDoubleSpinBox,
|
7
7
|
QFormLayout,
|
8
|
+
QGraphicsOpacityEffect,
|
9
|
+
QLabel,
|
8
10
|
QLineEdit,
|
9
11
|
QMessageBox,
|
10
12
|
QPushButton,
|
@@ -15,12 +17,15 @@ from PyQt6.QtWidgets import (
|
|
15
17
|
QWidget,
|
16
18
|
)
|
17
19
|
|
20
|
+
from ...core.configio.convertconfig import create_lasers_from_config
|
21
|
+
|
18
22
|
|
19
23
|
class LaserConfigWidget(QWidget):
|
20
24
|
laser_names_updated = pyqtSignal(list)
|
21
25
|
|
22
26
|
def __init__(self):
|
23
27
|
super().__init__()
|
28
|
+
self._confocal_mode = False
|
24
29
|
self.laser_name_widgets = []
|
25
30
|
layout = QVBoxLayout()
|
26
31
|
layout.setContentsMargins(10, 10, 10, 10)
|
@@ -28,6 +33,25 @@ class LaserConfigWidget(QWidget):
|
|
28
33
|
self.setLayout(layout)
|
29
34
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
30
35
|
|
36
|
+
self.scanning_label = QLabel(
|
37
|
+
"Scanning Confocal Selected \n Only Gaussian Laser Allowed"
|
38
|
+
)
|
39
|
+
self.scanning_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
40
|
+
self.scanning_label.setStyleSheet(
|
41
|
+
"color: green; font-weight: bold; font-size: 14px;"
|
42
|
+
)
|
43
|
+
self.scanning_label.setVisible(
|
44
|
+
True
|
45
|
+
) # Always visible — will control opacity instead
|
46
|
+
# blinking effect
|
47
|
+
self.opacity_effect = QGraphicsOpacityEffect(self.scanning_label)
|
48
|
+
self.scanning_label.setGraphicsEffect(self.opacity_effect)
|
49
|
+
self.opacity_effect.setOpacity(0.0)
|
50
|
+
self.blink_timer = QTimer(self)
|
51
|
+
self.blink_timer.setInterval(500)
|
52
|
+
self.blink_timer.timeout.connect(self._toggle_scanning_label_opacity)
|
53
|
+
layout.addWidget(self.scanning_label)
|
54
|
+
|
31
55
|
form = QFormLayout()
|
32
56
|
|
33
57
|
self.num_lasers = QSpinBox()
|
@@ -50,39 +74,51 @@ class LaserConfigWidget(QWidget):
|
|
50
74
|
self.validate_button.clicked.connect(self.validate)
|
51
75
|
layout.addWidget(self.validate_button)
|
52
76
|
|
77
|
+
def _toggle_scanning_label_opacity(self):
|
78
|
+
if self._confocal_mode:
|
79
|
+
current_opacity = self.opacity_effect.opacity()
|
80
|
+
new_opacity = 0.0 if current_opacity > 0.5 else 1.0
|
81
|
+
self.opacity_effect.setOpacity(new_opacity)
|
82
|
+
else:
|
83
|
+
self.opacity_effect.setOpacity(0.0)
|
84
|
+
|
53
85
|
def set_confocal_mode(self, enabled: bool):
|
86
|
+
self._confocal_mode = enabled
|
87
|
+
|
54
88
|
for i in range(self.laser_tabs.count()):
|
55
89
|
tab = self.laser_tabs.widget(i)
|
56
90
|
|
57
91
|
laser_type: QComboBox = tab.findChild(QComboBox)
|
58
92
|
beam_width: QDoubleSpinBox = tab.findChildren(QDoubleSpinBox)[
|
59
93
|
1
|
60
|
-
] #
|
94
|
+
] # second spinbox is beam_width
|
61
95
|
|
62
96
|
if enabled:
|
63
|
-
# Force laser type to "gaussian" and disable editing
|
64
97
|
laser_type.setCurrentText("gaussian")
|
65
98
|
laser_type.setEnabled(False)
|
66
99
|
|
67
|
-
# Hide beam width
|
68
100
|
beam_width.hide()
|
69
101
|
label = tab.layout().labelForField(beam_width)
|
70
102
|
if label:
|
71
103
|
label.hide()
|
72
104
|
else:
|
73
|
-
# Enable laser type editing
|
74
105
|
laser_type.setEnabled(True)
|
75
106
|
|
76
|
-
# Show beam width
|
77
107
|
beam_width.show()
|
78
108
|
label = tab.layout().labelForField(beam_width)
|
79
109
|
if label:
|
80
110
|
label.show()
|
81
111
|
|
112
|
+
# Handle blinking label
|
113
|
+
if enabled:
|
114
|
+
self.scanning_label.setVisible(True)
|
115
|
+
self.blink_timer.start()
|
116
|
+
else:
|
117
|
+
self.scanning_label.setVisible(False)
|
118
|
+
self.blink_timer.stop()
|
119
|
+
|
82
120
|
def validate(self) -> bool:
|
83
121
|
try:
|
84
|
-
from ...configio.convertconfig import create_lasers_from_config
|
85
|
-
|
86
122
|
data = self.get_data()
|
87
123
|
create_lasers_from_config({"lasers": data})
|
88
124
|
|
@@ -148,13 +184,22 @@ class LaserConfigWidget(QWidget):
|
|
148
184
|
layout.addRow(f"Laser {index + 1} Beam Width (µm):", beam_width)
|
149
185
|
layout.addRow(f"Laser {index + 1} Numerical Aperture:", numerical_aperture)
|
150
186
|
layout.addRow(f"Laser {index + 1} Refractive Index:", refractive_index)
|
151
|
-
|
187
|
+
inclination_label = QLabel(f"Laser {index + 1} Inclination Angle (°):")
|
188
|
+
layout.addRow(inclination_label, inclination_angle)
|
152
189
|
|
153
190
|
# Logic for hilo
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
191
|
+
def handle_inclination_visibility(selected_type):
|
192
|
+
if selected_type == "hilo":
|
193
|
+
inclination_angle.show()
|
194
|
+
inclination_label.show()
|
195
|
+
else:
|
196
|
+
inclination_angle.hide()
|
197
|
+
inclination_label.hide()
|
198
|
+
|
199
|
+
laser_type.currentTextChanged.connect(handle_inclination_visibility)
|
200
|
+
|
201
|
+
# Initial state
|
202
|
+
handle_inclination_visibility(laser_type.currentText())
|
158
203
|
|
159
204
|
tab.setLayout(layout)
|
160
205
|
self.laser_tabs.addTab(tab, f"Laser {index + 1}")
|
@@ -20,6 +20,9 @@ from PyQt6.QtWidgets import (
|
|
20
20
|
QWidget,
|
21
21
|
)
|
22
22
|
|
23
|
+
from ...core.configio.configmodels import MoleculeParameters
|
24
|
+
from ...core.configio.convertconfig import create_dataclass_schema
|
25
|
+
|
23
26
|
|
24
27
|
class MoleculeConfigWidget(QWidget):
|
25
28
|
# Signal to notify when molecule count changes
|
@@ -99,8 +102,6 @@ class MoleculeConfigWidget(QWidget):
|
|
99
102
|
data = self.get_data()
|
100
103
|
|
101
104
|
# This will validate the schema using the backend logic
|
102
|
-
from ...configio.configmodels import MoleculeParameters
|
103
|
-
from ...configio.convertconfig import create_dataclass_schema
|
104
105
|
|
105
106
|
_ = create_dataclass_schema(MoleculeParameters, data)
|
106
107
|
|
@@ -120,10 +121,6 @@ class MoleculeConfigWidget(QWidget):
|
|
120
121
|
Load molecule configuration from TOML config format.
|
121
122
|
"""
|
122
123
|
try:
|
123
|
-
# Validate format first
|
124
|
-
from ...configio.configmodels import MoleculeParameters
|
125
|
-
from ...configio.convertconfig import create_dataclass_schema
|
126
|
-
|
127
124
|
validated = create_dataclass_schema(MoleculeParameters, config)
|
128
125
|
|
129
126
|
# Determine number of types
|
File without changes
|
@@ -18,19 +18,19 @@ from PyQt6.QtWidgets import (
|
|
18
18
|
QWidget,
|
19
19
|
)
|
20
20
|
|
21
|
+
from ..widgets.camera_config_widget import CameraConfigWidget
|
22
|
+
from ..widgets.cell_config_widget import CellConfigWidget
|
23
|
+
from ..widgets.channel_config_widget import ChannelConfigWidget
|
24
|
+
from ..widgets.condensate_config_widget import CondensateConfigWidget
|
25
|
+
from ..widgets.experiment_config_widget import ExperimentConfigWidget
|
26
|
+
from ..widgets.flurophore_config_widget import FluorophoreConfigWidget
|
27
|
+
from ..widgets.general_config_widget import GeneralConfigWidget
|
28
|
+
from ..widgets.global_config_widget import GlobalConfigWidget
|
29
|
+
from ..widgets.laser_config_widget import LaserConfigWidget
|
30
|
+
from ..widgets.molecule_config_widget import MoleculeConfigWidget
|
31
|
+
from ..widgets.output_config_widget import OutputConfigWidget
|
32
|
+
from ..widgets.psf_config_widget import PSFConfigWidget
|
21
33
|
from .help_window import HelpWindow
|
22
|
-
from .widgets.camera_config_widget import CameraConfigWidget
|
23
|
-
from .widgets.cell_config_widget import CellConfigWidget
|
24
|
-
from .widgets.channel_config_widget import ChannelConfigWidget
|
25
|
-
from .widgets.condensate_config_widget import CondensateConfigWidget
|
26
|
-
from .widgets.experiment_config_widget import ExperimentConfigWidget
|
27
|
-
from .widgets.flurophore_config_widget import FluorophoreConfigWidget
|
28
|
-
from .widgets.general_config_widget import GeneralConfigWidget
|
29
|
-
from .widgets.global_config_widget import GlobalConfigWidget
|
30
|
-
from .widgets.laser_config_widget import LaserConfigWidget
|
31
|
-
from .widgets.molecule_config_widget import MoleculeConfigWidget
|
32
|
-
from .widgets.output_config_widget import OutputConfigWidget
|
33
|
-
from .widgets.psf_config_widget import PSFConfigWidget
|
34
34
|
|
35
35
|
|
36
36
|
class ConfigEditor(QWidget):
|
@@ -110,6 +110,9 @@ class ConfigEditor(QWidget):
|
|
110
110
|
|
111
111
|
# === Widget interconnections ===
|
112
112
|
self.psf_tab.confocal_mode_changed.connect(self.laser_tab.set_confocal_mode)
|
113
|
+
self.psf_tab.confocal_mode_changed.connect(
|
114
|
+
self.experiment_tab.set_scanning_mode
|
115
|
+
)
|
113
116
|
|
114
117
|
self.molecule_tab.molecule_count_changed.connect(
|
115
118
|
self.fluorophore_tab.set_mfluorophore_count
|
@@ -11,13 +11,14 @@ from PyQt6.QtWidgets import (
|
|
11
11
|
QMessageBox,
|
12
12
|
QPushButton,
|
13
13
|
QScrollArea,
|
14
|
+
QSizePolicy,
|
14
15
|
QVBoxLayout,
|
15
16
|
QWidget,
|
16
17
|
)
|
17
18
|
|
18
19
|
from .configuration_window import ConfigEditor
|
19
20
|
|
20
|
-
TEMPLATE_DIR = Path(__file__).parent.parent / "resources" / "template_configs"
|
21
|
+
TEMPLATE_DIR = Path(__file__).parent.parent.parent / "resources" / "template_configs"
|
21
22
|
METADATA_PATH = TEMPLATE_DIR / "metadata_configs.json"
|
22
23
|
|
23
24
|
|
@@ -52,32 +53,61 @@ class TemplateSelectionWindow(QWidget):
|
|
52
53
|
|
53
54
|
def create_template_card(self, entry: dict) -> QWidget:
|
54
55
|
group = QGroupBox(entry["label"])
|
56
|
+
group.setMinimumHeight(150)
|
57
|
+
group.setMaximumHeight(200)
|
55
58
|
layout = QHBoxLayout()
|
59
|
+
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
60
|
+
layout.setSpacing(15)
|
56
61
|
|
57
62
|
# Image
|
58
63
|
img_label = QLabel()
|
59
64
|
img_path = TEMPLATE_DIR / entry["image"]
|
60
65
|
if img_path.exists():
|
61
66
|
pixmap = QPixmap(str(img_path)).scaled(
|
62
|
-
|
67
|
+
150,
|
68
|
+
150,
|
69
|
+
Qt.AspectRatioMode.KeepAspectRatio,
|
70
|
+
Qt.TransformationMode.SmoothTransformation,
|
63
71
|
)
|
64
72
|
img_label.setPixmap(pixmap)
|
65
73
|
else:
|
66
74
|
img_label.setText("[Missing image assets]")
|
75
|
+
img_label.setFixedSize(150, 150)
|
76
|
+
img_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
67
77
|
layout.addWidget(img_label)
|
68
78
|
|
69
|
-
# Description + Button
|
79
|
+
# Description + Button (in a VBox)
|
70
80
|
vbox = QVBoxLayout()
|
81
|
+
vbox.setAlignment(Qt.AlignmentFlag.AlignTop)
|
82
|
+
|
83
|
+
# Scrollable description
|
84
|
+
desc_scroll = QScrollArea()
|
85
|
+
desc_scroll.setFixedHeight(100) # Height for the scroll area
|
86
|
+
desc_scroll.setWidgetResizable(True)
|
87
|
+
|
88
|
+
desc_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
89
|
+
desc_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
71
90
|
|
72
91
|
description_label = QLabel(entry.get("description", ""))
|
73
|
-
description_label.setWordWrap(True)
|
74
|
-
|
92
|
+
description_label.setWordWrap(True)
|
93
|
+
description_label.setAlignment(
|
94
|
+
Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft
|
95
|
+
)
|
96
|
+
description_label.setContentsMargins(5, 5, 5, 5)
|
97
|
+
description_label.setSizePolicy(
|
98
|
+
description_label.sizePolicy().horizontalPolicy(),
|
99
|
+
QSizePolicy.Policy.Maximum,
|
100
|
+
)
|
101
|
+
|
102
|
+
desc_scroll.setWidget(description_label)
|
103
|
+
vbox.addWidget(desc_scroll)
|
75
104
|
|
76
105
|
btn = QPushButton("Use This Template")
|
77
106
|
btn.clicked.connect(
|
78
107
|
lambda _, config=entry["config"]: self.load_template(config)
|
79
108
|
)
|
80
109
|
vbox.addWidget(btn)
|
110
|
+
|
81
111
|
layout.addLayout(vbox)
|
82
112
|
|
83
113
|
group.setLayout(layout)
|
AMS_BP/main_cli.py
CHANGED
@@ -34,9 +34,9 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
34
34
|
from typing_extensions import Annotated
|
35
35
|
|
36
36
|
from . import __version__
|
37
|
+
from .core.run_sim_util import run_simulation_from_file
|
37
38
|
from .gui.main import MainWindow
|
38
|
-
from .logging.logutil import cleanup_old_logs
|
39
|
-
from .run_sim_util import run_simulation_from_file
|
39
|
+
from .tools.logging.logutil import cleanup_old_logs
|
40
40
|
|
41
41
|
cli_help_doc = str(
|
42
42
|
"""
|