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.
Files changed (77) hide show
  1. AMS_BP/__init__.py +1 -1
  2. AMS_BP/{configio → core/configio}/experiments.py +5 -0
  3. AMS_BP/{motion → core/motion}/condensate_movement.py +1 -1
  4. AMS_BP/{motion → core/motion}/movement/boundary_conditions.py +1 -1
  5. AMS_BP/{photophysics → core/photophysics}/photon_physics.py +1 -1
  6. AMS_BP/{sample → core/sample}/flurophores/flurophore_schema.py +1 -1
  7. AMS_BP/{sim_microscopy.py → core/sim_microscopy.py} +1 -1
  8. AMS_BP/gui/main.py +36 -5
  9. AMS_BP/gui/sim_worker.py +3 -4
  10. AMS_BP/gui/themes/dark_theme.qss +86 -0
  11. AMS_BP/gui/themes/light_theme.qss +85 -0
  12. AMS_BP/gui/widgets/camera_config_widget.py +15 -9
  13. AMS_BP/gui/widgets/cell_config_widget.py +13 -7
  14. AMS_BP/gui/widgets/channel_config_widget.py +12 -6
  15. AMS_BP/gui/widgets/condensate_config_widget.py +24 -15
  16. AMS_BP/gui/widgets/experiment_config_widget.py +71 -7
  17. AMS_BP/gui/widgets/flurophore_config_widget.py +6 -2
  18. AMS_BP/gui/widgets/global_config_widget.py +13 -6
  19. AMS_BP/gui/widgets/laser_config_widget.py +12 -6
  20. AMS_BP/gui/widgets/molecule_config_widget.py +14 -11
  21. AMS_BP/gui/widgets/utility_widgets/toggleswitch_widget.py +60 -0
  22. AMS_BP/gui/windows/__init__.py +0 -0
  23. AMS_BP/gui/{configuration_window.py → windows/configuration_window.py} +86 -89
  24. AMS_BP/gui/{template_window_selection.py → windows/template_window_selection.py} +35 -5
  25. AMS_BP/main_cli.py +2 -2
  26. AMS_BP/resources/template_configs/metadata_configs.json +21 -3
  27. AMS_BP/resources/template_configs/twocolor_confocal_timeseries_live.toml +399 -0
  28. AMS_BP/resources/template_configs/twocolor_confocal_zstack_fixed.toml +406 -0
  29. AMS_BP/{sim_config.toml → resources/template_configs/twocolor_confocal_zstack_live.toml} +111 -111
  30. AMS_BP/tools/logging/__init__.py +0 -0
  31. {ams_bp-0.4.22.dist-info → ams_bp-0.4.40.dist-info}/METADATA +7 -3
  32. ams_bp-0.4.40.dist-info/RECORD +110 -0
  33. ams_bp-0.4.22.dist-info/RECORD +0 -103
  34. /AMS_BP/{configio → core}/__init__.py +0 -0
  35. /AMS_BP/{cells → core/cells}/__init__.py +0 -0
  36. /AMS_BP/{cells → core/cells}/budding_yeast_cell.py +0 -0
  37. /AMS_BP/{cells → core/cells}/cell_factory.py +0 -0
  38. /AMS_BP/{logging → core/configio}/__init__.py +0 -0
  39. /AMS_BP/{configio → core/configio}/configmodels.py +0 -0
  40. /AMS_BP/{configio → core/configio}/convertconfig.py +0 -0
  41. /AMS_BP/{configio → core/configio}/saving.py +0 -0
  42. /AMS_BP/{groundtruth_generators → core/groundtruth_generators}/__init__.py +0 -0
  43. /AMS_BP/{groundtruth_generators → core/groundtruth_generators}/nuclearporecomplexes.py +0 -0
  44. /AMS_BP/{metadata → core/metadata}/__init__.py +0 -0
  45. /AMS_BP/{metadata → core/metadata}/metadata.py +0 -0
  46. /AMS_BP/{motion → core/motion}/__init__.py +0 -0
  47. /AMS_BP/{motion → core/motion}/movement/__init__.py +0 -0
  48. /AMS_BP/{motion → core/motion}/track_gen.py +0 -0
  49. /AMS_BP/{optics → core/optics}/__init__.py +0 -0
  50. /AMS_BP/{optics → core/optics}/camera/__init__.py +0 -0
  51. /AMS_BP/{optics → core/optics}/camera/detectors.py +0 -0
  52. /AMS_BP/{optics → core/optics}/camera/quantum_eff.py +0 -0
  53. /AMS_BP/{optics → core/optics}/filters/__init__.py +0 -0
  54. /AMS_BP/{optics → core/optics}/filters/channels/__init__.py +0 -0
  55. /AMS_BP/{optics → core/optics}/filters/channels/channelschema.py +0 -0
  56. /AMS_BP/{optics → core/optics}/filters/filters.py +0 -0
  57. /AMS_BP/{optics → core/optics}/lasers/__init__.py +0 -0
  58. /AMS_BP/{optics → core/optics}/lasers/laser_profiles.py +0 -0
  59. /AMS_BP/{optics → core/optics}/lasers/scanning_patterns.py +0 -0
  60. /AMS_BP/{optics → core/optics}/psf/__init__.py +0 -0
  61. /AMS_BP/{optics → core/optics}/psf/psf_engine.py +0 -0
  62. /AMS_BP/{photophysics → core/photophysics}/__init__.py +0 -0
  63. /AMS_BP/{photophysics → core/photophysics}/state_kinetics.py +0 -0
  64. /AMS_BP/{probabilityfuncs → core/probabilityfuncs}/__init__.py +0 -0
  65. /AMS_BP/{probabilityfuncs → core/probabilityfuncs}/markov_chain.py +0 -0
  66. /AMS_BP/{probabilityfuncs → core/probabilityfuncs}/probability_functions.py +0 -0
  67. /AMS_BP/{run_sim_util.py → core/run_sim_util.py} +0 -0
  68. /AMS_BP/{sample → core/sample}/__init__.py +0 -0
  69. /AMS_BP/{sample → core/sample}/flurophores/__init__.py +0 -0
  70. /AMS_BP/{sample → core/sample}/sim_sampleplane.py +0 -0
  71. /AMS_BP/gui/{help_window.py → windows/help_window.py} +0 -0
  72. /AMS_BP/gui/{logging_window.py → windows/logging_window.py} +0 -0
  73. /AMS_BP/{logging → tools/logging}/logutil.py +0 -0
  74. /AMS_BP/{logging → tools/logging}/setup_run_directory.py +0 -0
  75. {ams_bp-0.4.22.dist-info → ams_bp-0.4.40.dist-info}/WHEEL +0 -0
  76. {ams_bp-0.4.22.dist-info → ams_bp-0.4.40.dist-info}/entry_points.txt +0 -0
  77. {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 (just one for time-series)
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
- form.addRow("Z Position(s):", self.z_position_container)
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
- self.setLayout(layout)
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
- form = QFormLayout()
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
- self.validate_button = QPushButton("Validate")
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
- # Create the main layout for the window
40
- layout = QVBoxLayout()
41
-
42
- # Create a horizontal layout for the dropdown and the tab index label
43
- dropdown_layout = QHBoxLayout()
44
-
45
- # Add a QLabel for the instruction/title about the dropdown
46
- dropdown_title = QLabel(
47
- "Use the dropdown below to set the parameters for each tab:"
48
- )
49
- dropdown_layout.addWidget(dropdown_title)
50
-
51
- # Create a QComboBox (dropdown menu) for selecting tabs
52
- self.dropdown = QComboBox()
53
- self.dropdown.addItems(
54
- [
55
- "General",
56
- "Global Parameters",
57
- "Cell Parameters",
58
- "Molecule Parameters",
59
- "Condensate Parameters",
60
- "Define fluorophores",
61
- "Camera Parameters",
62
- "PSF Parameters",
63
- "Laser Parameters",
64
- "Channels Parameters",
65
- "Saving Instructions",
66
- "Experiment Builder",
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.dropdown.currentIndexChanged.connect(
70
- self.on_dropdown_change
71
- ) # Connect to the change event
80
+ right_panel.addWidget(self.stacked_widget)
72
81
 
73
- # Create a QLabel for displaying the current tab index
74
- self.tab_index_label = QLabel("1/10")
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
- # Add the dropdown and label to the layout
77
- dropdown_layout.addWidget(self.dropdown)
78
- dropdown_layout.addWidget(self.tab_index_label)
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
- # Add the dropdown layout to the main layout
81
- layout.addLayout(dropdown_layout)
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
- # Create a QStackedWidget to hold the content for each "tab"
84
- self.stacked_widget = QStackedWidget()
95
+ main_layout.addLayout(right_panel)
85
96
 
86
- # Initialize the widgets for each "tab"
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
- # connections
101
- # PSF -> confocal -> lasers
111
+ # === Widget interconnections ===
102
112
  self.psf_tab.confocal_mode_changed.connect(self.laser_tab.set_confocal_mode)
103
- # === Molecule -> Fluorophore & Condensate ===
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
- # === Fluorophore -> Molecule & Condensate ===
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
- # === Condensate -> Molecule & Fluorophore ===
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
- # === Laser -> Experiment
137
+
125
138
  self.laser_tab.laser_names_updated.connect(
126
139
  self.experiment_tab.set_active_lasers
127
140
  )
128
141
 
129
- # Add each tab's widget to the stacked widget
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
- # Set the stacked widget as the central widget
144
- layout.addWidget(self.stacked_widget)
145
-
146
- # Create and add the save and help buttons at the bottom
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
- # Set initial display
163
- self.on_dropdown_change(0) # Show the first tab (index 0)
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 on_dropdown_change(self, index):
240
- """Change the displayed widget based on the dropdown selection."""
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
- # Update the tab index label (1-based index)
243
- total_tabs = (
244
- self.dropdown.count()
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(