AMS-BP 0.4.22__py3-none-any.whl → 0.4.40__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 +36 -5
- AMS_BP/gui/sim_worker.py +3 -4
- AMS_BP/gui/themes/dark_theme.qss +86 -0
- AMS_BP/gui/themes/light_theme.qss +85 -0
- AMS_BP/gui/widgets/camera_config_widget.py +15 -9
- AMS_BP/gui/widgets/cell_config_widget.py +13 -7
- AMS_BP/gui/widgets/channel_config_widget.py +12 -6
- AMS_BP/gui/widgets/condensate_config_widget.py +24 -15
- AMS_BP/gui/widgets/experiment_config_widget.py +71 -7
- AMS_BP/gui/widgets/flurophore_config_widget.py +6 -2
- AMS_BP/gui/widgets/global_config_widget.py +13 -6
- AMS_BP/gui/widgets/laser_config_widget.py +12 -6
- AMS_BP/gui/widgets/molecule_config_widget.py +14 -11
- AMS_BP/gui/widgets/utility_widgets/toggleswitch_widget.py +60 -0
- AMS_BP/gui/windows/__init__.py +0 -0
- AMS_BP/gui/{configuration_window.py → windows/configuration_window.py} +86 -89
- 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.22.dist-info → ams_bp-0.4.40.dist-info}/METADATA +7 -3
- ams_bp-0.4.40.dist-info/RECORD +110 -0
- ams_bp-0.4.22.dist-info/RECORD +0 -103
- /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.22.dist-info → ams_bp-0.4.40.dist-info}/WHEEL +0 -0
- {ams_bp-0.4.22.dist-info → ams_bp-0.4.40.dist-info}/entry_points.txt +0 -0
- {ams_bp-0.4.22.dist-info → ams_bp-0.4.40.dist-info}/licenses/LICENSE +0 -0
@@ -1,21 +1,27 @@
|
|
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,
|
11
13
|
QMessageBox,
|
12
14
|
QPushButton,
|
15
|
+
QScrollArea,
|
16
|
+
QSizePolicy,
|
13
17
|
QSpinBox,
|
14
18
|
QTabWidget,
|
15
19
|
QVBoxLayout,
|
16
20
|
QWidget,
|
17
21
|
)
|
18
22
|
|
23
|
+
from ...core.configio.convertconfig import create_experiment_from_config
|
24
|
+
|
19
25
|
|
20
26
|
class ExperimentConfigWidget(QWidget):
|
21
27
|
def __init__(self):
|
@@ -25,6 +31,36 @@ class ExperimentConfigWidget(QWidget):
|
|
25
31
|
self.laser_position_widgets = {}
|
26
32
|
|
27
33
|
layout = QVBoxLayout()
|
34
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
35
|
+
layout.setSpacing(10)
|
36
|
+
self.setLayout(layout)
|
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
|
+
|
28
64
|
form = QFormLayout()
|
29
65
|
|
30
66
|
# Experiment Info
|
@@ -38,13 +74,20 @@ class ExperimentConfigWidget(QWidget):
|
|
38
74
|
self.type_field.addItems(["time-series", "z-stack"])
|
39
75
|
form.addRow("Experiment Type:", self.type_field)
|
40
76
|
|
41
|
-
# Z Position
|
77
|
+
# Z Position inputs
|
42
78
|
self.z_position_inputs: List[QDoubleSpinBox] = []
|
43
79
|
|
80
|
+
# Scrollable container for z-position inputs
|
81
|
+
self.z_scroll_area = QScrollArea()
|
82
|
+
self.z_scroll_area.setWidgetResizable(True)
|
83
|
+
self.z_scroll_area.setFixedHeight(150) # Adjust height as needed
|
84
|
+
|
44
85
|
self.z_position_container = QWidget()
|
45
|
-
self.z_position_layout = QVBoxLayout()
|
86
|
+
self.z_position_layout = QVBoxLayout(self.z_position_container)
|
46
87
|
self.z_position_container.setLayout(self.z_position_layout)
|
47
|
-
|
88
|
+
|
89
|
+
self.z_scroll_area.setWidget(self.z_position_container)
|
90
|
+
form.addRow("Z Position(s):", self.z_scroll_area)
|
48
91
|
|
49
92
|
self.add_z_button = QPushButton("Add Z-Position")
|
50
93
|
self.remove_z_button = QPushButton("Remove Z-Position")
|
@@ -83,12 +126,30 @@ class ExperimentConfigWidget(QWidget):
|
|
83
126
|
layout.addWidget(QLabel("Active Laser Parameters:"))
|
84
127
|
layout.addWidget(self.laser_tabs)
|
85
128
|
|
129
|
+
self.setLayout(layout)
|
130
|
+
|
86
131
|
# Validate Button
|
87
|
-
self.validate_button = QPushButton("Validate")
|
132
|
+
self.validate_button = QPushButton("Validate Parameters")
|
88
133
|
self.validate_button.clicked.connect(self.validate)
|
89
134
|
layout.addWidget(self.validate_button)
|
90
135
|
|
91
|
-
|
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)
|
92
153
|
|
93
154
|
def update_z_position_mode(self, mode: str):
|
94
155
|
# Clear existing
|
@@ -189,6 +250,7 @@ class ExperimentConfigWidget(QWidget):
|
|
189
250
|
for name in self.laser_position_widgets
|
190
251
|
],
|
191
252
|
"xyoffset": [w.value() for w in self.xyoffset],
|
253
|
+
"scanning": self._scanning_mode,
|
192
254
|
}
|
193
255
|
|
194
256
|
if data["experiment_type"] == "z-stack":
|
@@ -236,10 +298,12 @@ class ExperimentConfigWidget(QWidget):
|
|
236
298
|
positions = data.get("laser_positions_active", [])
|
237
299
|
self.set_active_lasers(laser_names, powers=powers, positions=positions)
|
238
300
|
|
301
|
+
# Scanning Confocal
|
302
|
+
scanning_mode = data.get("scanning", False)
|
303
|
+
self.set_scanning_mode(scanning_mode)
|
304
|
+
|
239
305
|
def validate(self) -> bool:
|
240
306
|
try:
|
241
|
-
from ...configio.convertconfig import create_experiment_from_config
|
242
|
-
|
243
307
|
data = self.get_data()
|
244
308
|
config_dict = {"experiment": data}
|
245
309
|
|
@@ -11,12 +11,14 @@ from PyQt6.QtWidgets import (
|
|
11
11
|
QMessageBox,
|
12
12
|
QPushButton,
|
13
13
|
QScrollArea,
|
14
|
+
QSizePolicy,
|
14
15
|
QSpinBox,
|
15
16
|
QTabWidget,
|
16
17
|
QVBoxLayout,
|
17
18
|
QWidget,
|
18
19
|
)
|
19
20
|
|
21
|
+
from ...core.configio.convertconfig import create_fluorophores_from_config
|
20
22
|
from .utility_widgets.scinotation_widget import scientific_input_field
|
21
23
|
from .utility_widgets.spectrum_widget import SpectrumEditorDialog
|
22
24
|
|
@@ -33,6 +35,10 @@ class FluorophoreConfigWidget(QWidget):
|
|
33
35
|
|
34
36
|
def setup_ui(self):
|
35
37
|
layout = QVBoxLayout()
|
38
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
39
|
+
layout.setSpacing(10)
|
40
|
+
self.setLayout(layout)
|
41
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
36
42
|
|
37
43
|
instructions = QLabel(
|
38
44
|
"Configure fluorophores and their respective states and transitions."
|
@@ -490,8 +496,6 @@ class FluorophoreConfigWidget(QWidget):
|
|
490
496
|
|
491
497
|
def validate(self) -> bool:
|
492
498
|
try:
|
493
|
-
from ...configio.convertconfig import create_fluorophores_from_config
|
494
|
-
|
495
499
|
data = self.get_data()
|
496
500
|
# Try to build fluorophores with the backend logic
|
497
501
|
|
@@ -5,11 +5,14 @@ from PyQt6.QtWidgets import (
|
|
5
5
|
QHBoxLayout,
|
6
6
|
QMessageBox,
|
7
7
|
QPushButton,
|
8
|
+
QSizePolicy,
|
8
9
|
QSpinBox,
|
9
10
|
QVBoxLayout,
|
10
11
|
QWidget,
|
11
12
|
)
|
12
13
|
|
14
|
+
from ...core.configio.configmodels import GlobalParameters
|
15
|
+
|
13
16
|
|
14
17
|
class GlobalConfigWidget(QWidget):
|
15
18
|
def __init__(self):
|
@@ -18,11 +21,12 @@ class GlobalConfigWidget(QWidget):
|
|
18
21
|
|
19
22
|
def setup_ui(self):
|
20
23
|
layout = QVBoxLayout()
|
24
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
25
|
+
layout.setSpacing(10)
|
26
|
+
self.setLayout(layout)
|
27
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
28
|
+
|
21
29
|
form = QFormLayout()
|
22
|
-
# Validation button
|
23
|
-
self.validate_button = QPushButton("Validate Parameters")
|
24
|
-
self.validate_button.clicked.connect(self.validate)
|
25
|
-
layout.addWidget(self.validate_button)
|
26
30
|
|
27
31
|
# Sample plane dimensions
|
28
32
|
self.sample_plane_width = QSpinBox()
|
@@ -71,6 +75,11 @@ class GlobalConfigWidget(QWidget):
|
|
71
75
|
|
72
76
|
self.setLayout(layout)
|
73
77
|
|
78
|
+
# Validation button
|
79
|
+
self.validate_button = QPushButton("Validate Parameters")
|
80
|
+
self.validate_button.clicked.connect(self.validate)
|
81
|
+
layout.addWidget(self.validate_button)
|
82
|
+
|
74
83
|
def set_defaults(self):
|
75
84
|
"""Set default values for the form fields"""
|
76
85
|
self.sample_plane_width.setValue(50) # 1000 μm
|
@@ -115,8 +124,6 @@ class GlobalConfigWidget(QWidget):
|
|
115
124
|
|
116
125
|
def validate(self) -> bool:
|
117
126
|
try:
|
118
|
-
from ...configio.configmodels import GlobalParameters
|
119
|
-
|
120
127
|
data = self.get_data()
|
121
128
|
|
122
129
|
GlobalParameters(**data)
|
@@ -8,12 +8,15 @@ from PyQt6.QtWidgets import (
|
|
8
8
|
QLineEdit,
|
9
9
|
QMessageBox,
|
10
10
|
QPushButton,
|
11
|
+
QSizePolicy,
|
11
12
|
QSpinBox,
|
12
13
|
QTabWidget,
|
13
14
|
QVBoxLayout,
|
14
15
|
QWidget,
|
15
16
|
)
|
16
17
|
|
18
|
+
from ...core.configio.convertconfig import create_lasers_from_config
|
19
|
+
|
17
20
|
|
18
21
|
class LaserConfigWidget(QWidget):
|
19
22
|
laser_names_updated = pyqtSignal(list)
|
@@ -22,11 +25,12 @@ class LaserConfigWidget(QWidget):
|
|
22
25
|
super().__init__()
|
23
26
|
self.laser_name_widgets = []
|
24
27
|
layout = QVBoxLayout()
|
25
|
-
|
28
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
29
|
+
layout.setSpacing(10)
|
30
|
+
self.setLayout(layout)
|
31
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
26
32
|
|
27
|
-
|
28
|
-
self.validate_button.clicked.connect(self.validate)
|
29
|
-
layout.addWidget(self.validate_button)
|
33
|
+
form = QFormLayout()
|
30
34
|
|
31
35
|
self.num_lasers = QSpinBox()
|
32
36
|
self.num_lasers.setRange(1, 10)
|
@@ -44,6 +48,10 @@ class LaserConfigWidget(QWidget):
|
|
44
48
|
layout.addWidget(self.laser_tabs)
|
45
49
|
self.setLayout(layout)
|
46
50
|
|
51
|
+
self.validate_button = QPushButton("Validate Parameters")
|
52
|
+
self.validate_button.clicked.connect(self.validate)
|
53
|
+
layout.addWidget(self.validate_button)
|
54
|
+
|
47
55
|
def set_confocal_mode(self, enabled: bool):
|
48
56
|
for i in range(self.laser_tabs.count()):
|
49
57
|
tab = self.laser_tabs.widget(i)
|
@@ -75,8 +83,6 @@ class LaserConfigWidget(QWidget):
|
|
75
83
|
|
76
84
|
def validate(self) -> bool:
|
77
85
|
try:
|
78
|
-
from ...configio.convertconfig import create_lasers_from_config
|
79
|
-
|
80
86
|
data = self.get_data()
|
81
87
|
create_lasers_from_config({"lasers": data})
|
82
88
|
|
@@ -13,12 +13,16 @@ from PyQt6.QtWidgets import (
|
|
13
13
|
QMessageBox,
|
14
14
|
QPushButton,
|
15
15
|
QScrollArea,
|
16
|
+
QSizePolicy,
|
16
17
|
QSpinBox,
|
17
18
|
QTabWidget,
|
18
19
|
QVBoxLayout,
|
19
20
|
QWidget,
|
20
21
|
)
|
21
22
|
|
23
|
+
from ...core.configio.configmodels import MoleculeParameters
|
24
|
+
from ...core.configio.convertconfig import create_dataclass_schema
|
25
|
+
|
22
26
|
|
23
27
|
class MoleculeConfigWidget(QWidget):
|
24
28
|
# Signal to notify when molecule count changes
|
@@ -28,6 +32,11 @@ class MoleculeConfigWidget(QWidget):
|
|
28
32
|
super().__init__()
|
29
33
|
|
30
34
|
self.main_layout = QVBoxLayout()
|
35
|
+
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
36
|
+
self.main_layout.setSpacing(10)
|
37
|
+
self.setLayout(self.main_layout)
|
38
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
39
|
+
|
31
40
|
self.setLayout(self.main_layout)
|
32
41
|
|
33
42
|
# Number of molecule types spinner
|
@@ -41,11 +50,6 @@ class MoleculeConfigWidget(QWidget):
|
|
41
50
|
self.num_types_layout.addWidget(self.num_types_spinner)
|
42
51
|
self.num_types_layout.addStretch()
|
43
52
|
|
44
|
-
# Validate button
|
45
|
-
self.validate_button = QPushButton("Validate")
|
46
|
-
self.validate_button.clicked.connect(self.validate)
|
47
|
-
self.num_types_layout.addWidget(self.validate_button)
|
48
|
-
|
49
53
|
self.main_layout.addLayout(self.num_types_layout)
|
50
54
|
|
51
55
|
# Create tab widget to hold molecule type configs
|
@@ -56,6 +60,11 @@ class MoleculeConfigWidget(QWidget):
|
|
56
60
|
self.molecule_type_widgets = []
|
57
61
|
self.update_molecule_types(1)
|
58
62
|
|
63
|
+
# Add the validate button at the bottom
|
64
|
+
self.validate_button = QPushButton("Validate Parameters")
|
65
|
+
self.validate_button.clicked.connect(self.validate)
|
66
|
+
self.main_layout.addWidget(self.validate_button)
|
67
|
+
|
59
68
|
def _on_molecule_count_changed(self, count):
|
60
69
|
self.update_molecule_types(count)
|
61
70
|
self.molecule_count_changed.emit(count)
|
@@ -93,8 +102,6 @@ class MoleculeConfigWidget(QWidget):
|
|
93
102
|
data = self.get_data()
|
94
103
|
|
95
104
|
# This will validate the schema using the backend logic
|
96
|
-
from ...configio.configmodels import MoleculeParameters
|
97
|
-
from ...configio.convertconfig import create_dataclass_schema
|
98
105
|
|
99
106
|
_ = create_dataclass_schema(MoleculeParameters, data)
|
100
107
|
|
@@ -114,10 +121,6 @@ class MoleculeConfigWidget(QWidget):
|
|
114
121
|
Load molecule configuration from TOML config format.
|
115
122
|
"""
|
116
123
|
try:
|
117
|
-
# Validate format first
|
118
|
-
from ...configio.configmodels import MoleculeParameters
|
119
|
-
from ...configio.convertconfig import create_dataclass_schema
|
120
|
-
|
121
124
|
validated = create_dataclass_schema(MoleculeParameters, config)
|
122
125
|
|
123
126
|
# Determine number of types
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from PyQt6.QtCore import QPropertyAnimation, QSize, Qt, pyqtSignal
|
2
|
+
from PyQt6.QtGui import QBrush, QColor, QPainter
|
3
|
+
from PyQt6.QtWidgets import QWidget
|
4
|
+
|
5
|
+
|
6
|
+
class ToggleSwitch(QWidget):
|
7
|
+
toggled = pyqtSignal(bool)
|
8
|
+
|
9
|
+
def __init__(self, parent=None, checked=False):
|
10
|
+
super().__init__(parent)
|
11
|
+
self.setFixedSize(50, 28)
|
12
|
+
self._checked = checked
|
13
|
+
self._circle_position = 2 if not checked else 24
|
14
|
+
|
15
|
+
self.animation = QPropertyAnimation(self, b"")
|
16
|
+
self.animation.setDuration(200)
|
17
|
+
|
18
|
+
def sizeHint(self):
|
19
|
+
return QSize(50, 28)
|
20
|
+
|
21
|
+
def mousePressEvent(self, event):
|
22
|
+
self._checked = not self._checked
|
23
|
+
self.animate_toggle()
|
24
|
+
self.toggled.emit(self._checked)
|
25
|
+
self.update()
|
26
|
+
|
27
|
+
def animate_toggle(self):
|
28
|
+
start = self._circle_position
|
29
|
+
end = 24 if self._checked else 2
|
30
|
+
self.animation.stop()
|
31
|
+
self.animation.setStartValue(start)
|
32
|
+
self.animation.setEndValue(end)
|
33
|
+
self.animation.valueChanged.connect(self.set_circle_position)
|
34
|
+
self.animation.start()
|
35
|
+
|
36
|
+
def set_circle_position(self, val):
|
37
|
+
self._circle_position = val
|
38
|
+
self.update()
|
39
|
+
|
40
|
+
def paintEvent(self, event):
|
41
|
+
painter = QPainter(self)
|
42
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
43
|
+
|
44
|
+
# NEW: Background indicates *next* theme
|
45
|
+
bg_color = QColor("#dddddd") if self._checked else QColor("#333333")
|
46
|
+
painter.setBrush(QBrush(bg_color))
|
47
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
48
|
+
painter.drawRoundedRect(0, 0, self.width(), self.height(), 14, 14)
|
49
|
+
|
50
|
+
# Handle (always green)
|
51
|
+
painter.setBrush(QBrush(QColor("#008000")))
|
52
|
+
painter.drawEllipse(int(self._circle_position), 2, 24, 24)
|
53
|
+
|
54
|
+
def isChecked(self):
|
55
|
+
return self._checked
|
56
|
+
|
57
|
+
def setChecked(self, checked: bool):
|
58
|
+
self._checked = checked
|
59
|
+
self._circle_position = 24 if checked else 2
|
60
|
+
self.update()
|
File without changes
|
@@ -2,33 +2,35 @@ from pathlib import Path
|
|
2
2
|
|
3
3
|
import tomli
|
4
4
|
import tomlkit
|
5
|
+
from PyQt6.QtCore import Qt
|
5
6
|
from PyQt6.QtWidgets import (
|
6
|
-
QComboBox,
|
7
7
|
QDialog,
|
8
8
|
QFileDialog,
|
9
9
|
QHBoxLayout,
|
10
10
|
QLabel,
|
11
|
+
QListWidget,
|
11
12
|
QMessageBox,
|
12
13
|
QPushButton,
|
14
|
+
QSizePolicy,
|
13
15
|
QStackedWidget,
|
14
16
|
QTextEdit,
|
15
17
|
QVBoxLayout,
|
16
18
|
QWidget,
|
17
19
|
)
|
18
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
|
19
33
|
from .help_window import HelpWindow
|
20
|
-
from .widgets.camera_config_widget import CameraConfigWidget
|
21
|
-
from .widgets.cell_config_widget import CellConfigWidget
|
22
|
-
from .widgets.channel_config_widget import ChannelConfigWidget
|
23
|
-
from .widgets.condensate_config_widget import CondensateConfigWidget
|
24
|
-
from .widgets.experiment_config_widget import ExperimentConfigWidget
|
25
|
-
from .widgets.flurophore_config_widget import FluorophoreConfigWidget
|
26
|
-
from .widgets.general_config_widget import GeneralConfigWidget
|
27
|
-
from .widgets.global_config_widget import GlobalConfigWidget
|
28
|
-
from .widgets.laser_config_widget import LaserConfigWidget
|
29
|
-
from .widgets.molecule_config_widget import MoleculeConfigWidget
|
30
|
-
from .widgets.output_config_widget import OutputConfigWidget
|
31
|
-
from .widgets.psf_config_widget import PSFConfigWidget
|
32
34
|
|
33
35
|
|
34
36
|
class ConfigEditor(QWidget):
|
@@ -36,54 +38,63 @@ class ConfigEditor(QWidget):
|
|
36
38
|
super().__init__()
|
37
39
|
self.setWindowTitle("Simulation Configuration Editor")
|
38
40
|
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
"
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
41
|
+
# === Main horizontal layout: [Side Navigation | Content Area] ===
|
42
|
+
main_layout = QHBoxLayout(self)
|
43
|
+
|
44
|
+
# === Sidebar: Section Navigation ===
|
45
|
+
self.nav_list = QListWidget()
|
46
|
+
self.sections = [
|
47
|
+
"General",
|
48
|
+
"Global Parameters",
|
49
|
+
"Cell Parameters",
|
50
|
+
"Molecule Parameters",
|
51
|
+
"Condensate Parameters",
|
52
|
+
"Define fluorophores",
|
53
|
+
"Camera Parameters",
|
54
|
+
"PSF Parameters",
|
55
|
+
"Laser Parameters",
|
56
|
+
"Channels Parameters",
|
57
|
+
"Saving Instructions",
|
58
|
+
"Experiment Builder",
|
59
|
+
]
|
60
|
+
self.nav_list.addItems(self.sections)
|
61
|
+
self.nav_list.setFixedWidth(220)
|
62
|
+
self.nav_list.setSpacing(4)
|
63
|
+
self.nav_list.setCurrentRow(0)
|
64
|
+
self.nav_list.currentRowChanged.connect(self.on_tab_selected)
|
65
|
+
main_layout.addWidget(self.nav_list)
|
66
|
+
|
67
|
+
# === Right panel layout ===
|
68
|
+
right_panel = QVBoxLayout()
|
69
|
+
|
70
|
+
# Step/breadcrumb label
|
71
|
+
self.step_label = QLabel()
|
72
|
+
self.step_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
73
|
+
right_panel.addWidget(self.step_label)
|
74
|
+
|
75
|
+
# === Stack of config widgets ===
|
76
|
+
self.stacked_widget = QStackedWidget()
|
77
|
+
self.stacked_widget.setSizePolicy(
|
78
|
+
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
|
68
79
|
)
|
69
|
-
self.
|
70
|
-
self.on_dropdown_change
|
71
|
-
) # Connect to the change event
|
80
|
+
right_panel.addWidget(self.stacked_widget)
|
72
81
|
|
73
|
-
#
|
74
|
-
self.
|
82
|
+
# === Buttons at the bottom ===
|
83
|
+
self.save_button = QPushButton("Ready to save configuration?")
|
84
|
+
self.save_button.clicked.connect(self.save_config)
|
85
|
+
right_panel.addWidget(self.save_button)
|
75
86
|
|
76
|
-
|
77
|
-
|
78
|
-
|
87
|
+
self.preview_button = QPushButton("Preview Configuration TOML")
|
88
|
+
self.preview_button.clicked.connect(self.preview_config)
|
89
|
+
right_panel.addWidget(self.preview_button)
|
79
90
|
|
80
|
-
|
81
|
-
|
91
|
+
self.help_button = QPushButton("Get Help on this section")
|
92
|
+
self.help_button.clicked.connect(self.show_help)
|
93
|
+
right_panel.addWidget(self.help_button)
|
82
94
|
|
83
|
-
|
84
|
-
self.stacked_widget = QStackedWidget()
|
95
|
+
main_layout.addLayout(right_panel)
|
85
96
|
|
86
|
-
#
|
97
|
+
# === Create tab content widgets ===
|
87
98
|
self.general_tab = GeneralConfigWidget()
|
88
99
|
self.global_tab = GlobalConfigWidget()
|
89
100
|
self.cell_tab = CellConfigWidget()
|
@@ -97,36 +108,38 @@ class ConfigEditor(QWidget):
|
|
97
108
|
self.detector_tab = CameraConfigWidget()
|
98
109
|
self.experiment_tab = ExperimentConfigWidget()
|
99
110
|
|
100
|
-
#
|
101
|
-
# PSF -> confocal -> lasers
|
111
|
+
# === Widget interconnections ===
|
102
112
|
self.psf_tab.confocal_mode_changed.connect(self.laser_tab.set_confocal_mode)
|
103
|
-
|
113
|
+
self.psf_tab.confocal_mode_changed.connect(
|
114
|
+
self.experiment_tab.set_scanning_mode
|
115
|
+
)
|
116
|
+
|
104
117
|
self.molecule_tab.molecule_count_changed.connect(
|
105
118
|
self.fluorophore_tab.set_mfluorophore_count
|
106
119
|
)
|
107
120
|
self.molecule_tab.molecule_count_changed.connect(
|
108
121
|
self.condensate_tab.set_molecule_count
|
109
122
|
)
|
110
|
-
|
123
|
+
|
111
124
|
self.fluorophore_tab.mfluorophore_count_changed.connect(
|
112
125
|
self.molecule_tab.set_molecule_count
|
113
126
|
)
|
114
127
|
self.fluorophore_tab.mfluorophore_count_changed.connect(
|
115
128
|
self.condensate_tab.set_molecule_count
|
116
129
|
)
|
117
|
-
|
130
|
+
|
118
131
|
self.condensate_tab.molecule_count_changed.connect(
|
119
132
|
self.molecule_tab.set_molecule_count
|
120
133
|
)
|
121
134
|
self.condensate_tab.molecule_count_changed.connect(
|
122
135
|
self.fluorophore_tab.set_mfluorophore_count
|
123
136
|
)
|
124
|
-
|
137
|
+
|
125
138
|
self.laser_tab.laser_names_updated.connect(
|
126
139
|
self.experiment_tab.set_active_lasers
|
127
140
|
)
|
128
141
|
|
129
|
-
# Add
|
142
|
+
# === Add tab widgets to the stack ===
|
130
143
|
self.stacked_widget.addWidget(self.general_tab)
|
131
144
|
self.stacked_widget.addWidget(self.global_tab)
|
132
145
|
self.stacked_widget.addWidget(self.cell_tab)
|
@@ -140,27 +153,13 @@ class ConfigEditor(QWidget):
|
|
140
153
|
self.stacked_widget.addWidget(self.output_tab)
|
141
154
|
self.stacked_widget.addWidget(self.experiment_tab)
|
142
155
|
|
143
|
-
#
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
self.save_button = QPushButton("Ready to save configuration?")
|
148
|
-
self.save_button.clicked.connect(self.save_config)
|
149
|
-
layout.addWidget(self.save_button)
|
150
|
-
|
151
|
-
self.preview_button = QPushButton("Preview Configuration TOML")
|
152
|
-
self.preview_button.clicked.connect(self.preview_config)
|
153
|
-
layout.addWidget(self.preview_button)
|
154
|
-
|
155
|
-
self.help_button = QPushButton("Get Help on this section")
|
156
|
-
self.help_button.clicked.connect(self.show_help)
|
157
|
-
layout.addWidget(self.help_button)
|
158
|
-
|
159
|
-
# Set the layout for the main window
|
160
|
-
self.setLayout(layout)
|
156
|
+
# Final layout and window size
|
157
|
+
self.setLayout(main_layout)
|
158
|
+
self.setMinimumSize(1100, 750)
|
159
|
+
self.resize(1250, 850)
|
161
160
|
|
162
|
-
#
|
163
|
-
self.
|
161
|
+
# Initial tab display
|
162
|
+
self.on_tab_selected(0)
|
164
163
|
|
165
164
|
def set_data(self, config: dict):
|
166
165
|
if "Cell_Parameters" in config:
|
@@ -236,14 +235,12 @@ class ConfigEditor(QWidget):
|
|
236
235
|
doc[key] = val
|
237
236
|
return doc
|
238
237
|
|
239
|
-
def
|
240
|
-
"""Change the displayed widget
|
238
|
+
def on_tab_selected(self, index: int):
|
239
|
+
"""Change the displayed widget and update breadcrumb/step label."""
|
241
240
|
self.stacked_widget.setCurrentIndex(index)
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
) # Corrected way to get the total number of items
|
246
|
-
self.tab_index_label.setText(f"{index + 1}/{total_tabs}")
|
241
|
+
total = self.nav_list.count()
|
242
|
+
current = self.nav_list.item(index).text()
|
243
|
+
self.step_label.setText(f"Step {index + 1}/{total} — {current}")
|
247
244
|
|
248
245
|
def validate_all_tabs(self) -> bool:
|
249
246
|
return all(
|