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
AMS_BP/__init__.py CHANGED
@@ -10,4 +10,4 @@ Last updated: 2024-12-16
10
10
 
11
11
  """
12
12
 
13
- __version__ = "0.4.22"
13
+ __version__ = "0.4.40"
@@ -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}
@@ -25,8 +25,8 @@ from typing import Optional
25
25
 
26
26
  import numpy as np
27
27
 
28
+ from ...utils.decorators import cache
28
29
  from ..cells import BaseCell
29
- from ..utils.decorators import cache
30
30
  from .track_gen import Track_generator
31
31
 
32
32
 
@@ -5,7 +5,7 @@ Removal Time: NDY (not determined yet)
5
5
 
6
6
  import numpy as np
7
7
 
8
- from ...utils.decorators import _catch_recursion_error, deprecated
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 ...utils.constants import H_C_COM, N_A
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
@@ -4,10 +4,11 @@ from zipfile import ZipFile
4
4
 
5
5
  import napari
6
6
  import tifffile
7
- from PyQt6.QtCore import Qt, QThread
7
+ from PyQt6.QtCore import QSettings, Qt, QThread
8
8
  from PyQt6.QtGui import QPainter, QPixmap
9
9
  from PyQt6.QtSvg import QSvgRenderer
10
10
  from PyQt6.QtWidgets import (
11
+ QApplication,
11
12
  QFileDialog,
12
13
  QLabel,
13
14
  QMainWindow,
@@ -17,11 +18,12 @@ from PyQt6.QtWidgets import (
17
18
  QWidget,
18
19
  )
19
20
 
20
- from ..logging.logutil import LoggerManager
21
- from ..logging.setup_run_directory import setup_run_directory
22
- from .logging_window import LogWindow
21
+ from ..tools.logging.logutil import LoggerManager
22
+ from ..tools.logging.setup_run_directory import setup_run_directory
23
23
  from .sim_worker import SimulationWorker
24
- from .template_window_selection import TemplateSelectionWindow
24
+ from .widgets.utility_widgets.toggleswitch_widget import ToggleSwitch
25
+ from .windows.logging_window import LogWindow
26
+ from .windows.template_window_selection import TemplateSelectionWindow
25
27
 
26
28
  LOGO_PATH = str(Path(__file__).parent / "assets" / "drawing.svg")
27
29
 
@@ -77,6 +79,20 @@ class MainWindow(QMainWindow):
77
79
  self.package_logs_button.clicked.connect(self.package_logs)
78
80
  layout.addWidget(self.package_logs_button)
79
81
 
82
+ # Load theme preference
83
+ self.settings = QSettings("AMS", "AMSConfig")
84
+ theme_pref = self.settings.value("theme", "light")
85
+
86
+ # Add toggle switch with label
87
+ self.theme_toggle = ToggleSwitch(checked=(theme_pref == "dark"))
88
+ self.theme_toggle.toggled.connect(self.toggle_theme)
89
+ self.theme_label = QLabel("Dark Mode" if theme_pref == "dark" else "Light Mode")
90
+ layout.addWidget(self.theme_label)
91
+ layout.addWidget(self.theme_toggle, alignment=Qt.AlignmentFlag.AlignCenter)
92
+
93
+ # Apply initial theme
94
+ self.apply_theme(theme_pref)
95
+
80
96
  def package_logs(self):
81
97
  log_dir = Path.home() / "AMS_runs"
82
98
 
@@ -245,6 +261,21 @@ class MainWindow(QMainWindow):
245
261
  else:
246
262
  print("Failed to load SVG file.")
247
263
 
264
+ def toggle_theme(self, is_dark: bool):
265
+ theme = "dark" if is_dark else "light"
266
+ self.settings.setValue("theme", theme)
267
+ self.apply_theme(theme)
268
+ self.theme_label.setText("Dark Mode" if is_dark else "Light Mode")
269
+
270
+ def apply_theme(self, theme: str):
271
+ theme_file = Path(__file__).parent / "themes" / f"{theme}_theme.qss"
272
+ try:
273
+ with open(theme_file, "r") as f:
274
+ stylesheet = f.read()
275
+ QApplication.instance().setStyleSheet(stylesheet)
276
+ except Exception as e:
277
+ QMessageBox.warning(self, "Theme Error", f"Failed to load theme:\n{e}")
278
+
248
279
  def open_config_editor(self):
249
280
  """Launch template selection first, then open ConfigEditor."""
250
281
  self.template_window = TemplateSelectionWindow()
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 ..logging.logutil import LogEmitter
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:
@@ -0,0 +1,86 @@
1
+ /* Dark Theme */
2
+ * {
3
+ font-family: "Segoe UI", "Arial", sans-serif;
4
+ font-size: 14px;
5
+ color: #e0e0e0;
6
+ }
7
+
8
+ QWidget {
9
+ background-color: #2b2b2b;
10
+ }
11
+
12
+ QLabel {
13
+ color: #dddddd;
14
+ }
15
+
16
+ QPushButton {
17
+ background-color: #3daee9;
18
+ color: #ffffff;
19
+ border-radius: 6px;
20
+ padding: 6px 12px;
21
+ }
22
+ QPushButton:hover {
23
+ background-color: #5dbff0;
24
+ }
25
+ QPushButton:pressed {
26
+ background-color: #2a97cc;
27
+ }
28
+
29
+ QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox, QTextEdit {
30
+ background-color: #3c3f41;
31
+ border: 1px solid #555;
32
+ border-radius: 4px;
33
+ padding: 4px;
34
+ color: #eeeeee;
35
+ }
36
+
37
+ QGroupBox {
38
+ border: 1px solid #444;
39
+ border-radius: 6px;
40
+ margin-top: 10px;
41
+ }
42
+ QGroupBox:title {
43
+ subcontrol-origin: margin;
44
+ subcontrol-position: top left;
45
+ padding: 0 6px;
46
+ font-weight: bold;
47
+ color: #aaaaaa;
48
+ }
49
+
50
+ QTabWidget::pane {
51
+ border: 1px solid #444;
52
+ background: #3c3f41;
53
+ border-radius: 6px;
54
+ }
55
+
56
+ QTabBar::tab {
57
+ background: #444;
58
+ border: 1px solid #555;
59
+ padding: 6px 12px;
60
+ border-top-left-radius: 6px;
61
+ border-top-right-radius: 6px;
62
+ }
63
+ QTabBar::tab:selected {
64
+ background: #2b2b2b;
65
+ border-bottom-color: #2b2b2b;
66
+ }
67
+
68
+ QScrollArea {
69
+ border: none;
70
+ }
71
+
72
+ QListWidget {
73
+ background-color: #2f2f2f;
74
+ border-right: 1px solid #444;
75
+ }
76
+
77
+ QListWidget::item {
78
+ padding: 6px;
79
+ border-radius: 4px;
80
+ color: #dddddd;
81
+ }
82
+
83
+ QListWidget::item:selected {
84
+ background-color: #3daee9;
85
+ color: #ffffff;
86
+ }
@@ -0,0 +1,85 @@
1
+ /* Light Theme */
2
+ * {
3
+ font-family: "Segoe UI", "Arial", sans-serif;
4
+ font-size: 14px;
5
+ color: #2b2b2b;
6
+ }
7
+
8
+ QWidget {
9
+ background-color: #f5f7fa;
10
+ }
11
+
12
+ QLabel {
13
+ color: #333;
14
+ }
15
+
16
+ QPushButton {
17
+ background-color: #007acc;
18
+ color: white;
19
+ border-radius: 6px;
20
+ padding: 6px 12px;
21
+ }
22
+ QPushButton:hover {
23
+ background-color: #005f9e;
24
+ }
25
+ QPushButton:pressed {
26
+ background-color: #004d80;
27
+ }
28
+
29
+ QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox, QTextEdit {
30
+ background-color: white;
31
+ border: 1px solid #ccc;
32
+ border-radius: 4px;
33
+ padding: 4px;
34
+ }
35
+
36
+ QGroupBox {
37
+ border: 1px solid #d6d9dc;
38
+ border-radius: 6px;
39
+ margin-top: 10px;
40
+ }
41
+ QGroupBox:title {
42
+ subcontrol-origin: margin;
43
+ subcontrol-position: top left;
44
+ padding: 0 6px;
45
+ font-weight: bold;
46
+ color: #555;
47
+ }
48
+
49
+ QTabWidget::pane {
50
+ border: 1px solid #d1dbe3;
51
+ background: #ffffff;
52
+ border-radius: 6px;
53
+ }
54
+
55
+ QTabBar::tab {
56
+ background: #e6ebf1;
57
+ border: 1px solid #cfd7df;
58
+ padding: 6px 12px;
59
+ border-top-left-radius: 6px;
60
+ border-top-right-radius: 6px;
61
+ }
62
+ QTabBar::tab:selected {
63
+ background: #ffffff;
64
+ border-bottom-color: #ffffff;
65
+ }
66
+
67
+ QScrollArea {
68
+ border: none;
69
+ }
70
+
71
+ QListWidget {
72
+ background-color: #f0f0f0;
73
+ border-right: 1px solid #ccc;
74
+ }
75
+
76
+ QListWidget::item {
77
+ padding: 6px;
78
+ border-radius: 4px;
79
+ }
80
+
81
+ QListWidget::item:selected {
82
+ background-color: #007acc;
83
+ color: white;
84
+ }
85
+
@@ -8,23 +8,30 @@ from PyQt6.QtWidgets import (
8
8
  QHBoxLayout,
9
9
  QMessageBox,
10
10
  QPushButton,
11
+ QSizePolicy,
11
12
  QSpinBox,
12
13
  QVBoxLayout,
13
14
  QWidget,
14
15
  )
15
16
 
17
+ from ...core.configio.convertconfig import create_quantum_efficiency_from_config
18
+ from ...core.optics.camera.detectors import (
19
+ CMOSDetector,
20
+ )
16
21
  from .utility_widgets.spectrum_widget import SpectrumEditorDialog
17
22
 
18
23
 
19
24
  class CameraConfigWidget(QWidget):
20
25
  def __init__(self):
21
26
  super().__init__()
27
+
22
28
  layout = QVBoxLayout()
23
- form = QFormLayout()
29
+ layout.setContentsMargins(10, 10, 10, 10)
30
+ layout.setSpacing(10)
31
+ self.setLayout(layout)
32
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
24
33
 
25
- self.validate_button = QPushButton("Validate")
26
- self.validate_button.clicked.connect(self.validate)
27
- layout.addWidget(self.validate_button)
34
+ form = QFormLayout()
28
35
 
29
36
  # Camera type (Only "CMOS" is available)
30
37
  self.camera_type = QComboBox()
@@ -89,6 +96,10 @@ class CameraConfigWidget(QWidget):
89
96
  layout.addLayout(form)
90
97
  self.setLayout(layout)
91
98
 
99
+ self.validate_button = QPushButton("Validate Parameters")
100
+ self.validate_button.clicked.connect(self.validate)
101
+ layout.addWidget(self.validate_button)
102
+
92
103
  def _hbox(self, widgets):
93
104
  box = QHBoxLayout()
94
105
  for w in widgets:
@@ -115,11 +126,6 @@ class CameraConfigWidget(QWidget):
115
126
  # You can now use this data wherever needed, e.g., saving or validation
116
127
 
117
128
  def validate(self) -> bool:
118
- from ...configio.convertconfig import create_quantum_efficiency_from_config
119
- from ...optics.camera.detectors import (
120
- CMOSDetector,
121
- )
122
-
123
129
  try:
124
130
  data = self.get_data()
125
131
 
@@ -8,21 +8,26 @@ from PyQt6.QtWidgets import (
8
8
  QHBoxLayout,
9
9
  QMessageBox,
10
10
  QPushButton,
11
+ QSizePolicy,
11
12
  QStackedWidget,
12
13
  QVBoxLayout,
13
14
  QWidget,
14
15
  )
15
16
 
17
+ from ...core.cells import create_cell
18
+ from ...core.configio.configmodels import CellParameters
19
+
16
20
 
17
21
  class CellConfigWidget(QWidget):
18
22
  def __init__(self):
19
23
  super().__init__()
20
24
  layout = QVBoxLayout()
21
- form = QFormLayout()
25
+ layout.setContentsMargins(10, 10, 10, 10)
26
+ layout.setSpacing(10)
27
+ self.setLayout(layout)
28
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
22
29
 
23
- self.validate_button = QPushButton("Validate")
24
- self.validate_button.clicked.connect(self.validate)
25
- layout.addWidget(self.validate_button)
30
+ form = QFormLayout()
26
31
 
27
32
  self.cell_type = QComboBox()
28
33
  self.cell_type.addItems(
@@ -46,10 +51,11 @@ class CellConfigWidget(QWidget):
46
51
  layout.addWidget(self.param_stack)
47
52
  self.setLayout(layout)
48
53
 
49
- def validate(self) -> bool:
50
- from ...cells import create_cell
51
- from ...configio.configmodels import CellParameters
54
+ self.validate_button = QPushButton("Validate Parameters")
55
+ self.validate_button.clicked.connect(self.validate)
56
+ layout.addWidget(self.validate_button)
52
57
 
58
+ def validate(self) -> bool:
53
59
  try:
54
60
  data = self.get_data()
55
61
 
@@ -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_channels
19
+
17
20
 
18
21
  class ChannelConfigWidget(QWidget):
19
22
  def __init__(self):
@@ -21,11 +24,12 @@ class ChannelConfigWidget(QWidget):
21
24
  self.channel_widgets = []
22
25
 
23
26
  layout = QVBoxLayout()
24
- form = QFormLayout()
27
+ layout.setContentsMargins(10, 10, 10, 10)
28
+ layout.setSpacing(10)
29
+ self.setLayout(layout)
30
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
25
31
 
26
- self.validate_button = QPushButton("Validate")
27
- self.validate_button.clicked.connect(self.validate)
28
- layout.addWidget(self.validate_button)
32
+ form = QFormLayout()
29
33
 
30
34
  self.num_channels = QSpinBox()
31
35
  self.num_channels.setRange(1, 10)
@@ -40,10 +44,12 @@ class ChannelConfigWidget(QWidget):
40
44
  layout.addWidget(self.channel_tabs)
41
45
  self.setLayout(layout)
42
46
 
47
+ self.validate_button = QPushButton("Validate Parameters")
48
+ self.validate_button.clicked.connect(self.validate)
49
+ layout.addWidget(self.validate_button)
50
+
43
51
  def validate(self) -> bool:
44
52
  try:
45
- from ...configio.convertconfig import create_channels
46
-
47
53
  data = self.get_data()
48
54
 
49
55
  # Full simulation-level validation
@@ -11,12 +11,17 @@ 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.cells import create_cell
22
+ from ...core.configio.configmodels import CondensateParameters
23
+ from ...core.motion import create_condensate_dict
24
+
20
25
 
21
26
  class CondensateConfigWidget(QWidget):
22
27
  # Signal to notify when molecule count changes
@@ -31,6 +36,10 @@ class CondensateConfigWidget(QWidget):
31
36
 
32
37
  def setup_ui(self):
33
38
  layout = QVBoxLayout()
39
+ layout.setContentsMargins(10, 10, 10, 10)
40
+ layout.setSpacing(10)
41
+ self.setLayout(layout)
42
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
34
43
 
35
44
  # Instructions label
36
45
  instructions = QLabel(
@@ -113,6 +122,18 @@ class CondensateConfigWidget(QWidget):
113
122
  condensate_controls.addWidget(condensate_count)
114
123
  layout.addLayout(condensate_controls)
115
124
 
125
+ # === Density Difference field RIGHT AFTER the condensate count ===
126
+ density_layout = QHBoxLayout()
127
+ density_layout.addWidget(QLabel("Density Difference:"))
128
+
129
+ density_spin = QDoubleSpinBox()
130
+ density_spin.setRange(0, 100)
131
+ density_spin.setValue(1.0)
132
+ density_spin.setDecimals(3)
133
+ density_layout.addWidget(density_spin)
134
+ layout.addLayout(density_layout)
135
+
136
+ # === Condensate containers BELOW this point ===
116
137
  condensate_container = QVBoxLayout()
117
138
  layout.addLayout(condensate_container)
118
139
 
@@ -126,25 +147,16 @@ class CondensateConfigWidget(QWidget):
126
147
  )
127
148
  )
128
149
 
129
- # Density Difference per molecule type
130
- density_layout = QHBoxLayout()
131
- density_layout.addWidget(QLabel("Density Difference:"))
132
-
133
- density_spin = QDoubleSpinBox()
134
- density_spin.setRange(0, 100)
135
- density_spin.setValue(1.0)
136
- density_spin.setDecimals(3)
137
- density_layout.addWidget(density_spin)
138
- layout.addLayout(density_layout)
139
150
  self.condensate_widgets.append(
140
151
  {
141
152
  "condensates": condensate_widgets,
142
153
  "density_widget": density_spin,
154
+ "condensate_count_spinner": condensate_count,
143
155
  }
144
156
  )
157
+
145
158
  molecule_widget.setLayout(layout)
146
159
  scroll_area.setWidget(molecule_widget)
147
-
148
160
  self.tab_widget.addTab(scroll_area, f"Molecule Type {index + 1}")
149
161
 
150
162
  def add_condensate_group(self, index, condensate_widgets, condensate_container):
@@ -242,6 +254,7 @@ class CondensateConfigWidget(QWidget):
242
254
  molecule_group["condensates"],
243
255
  molecule_group_layout,
244
256
  )
257
+ molecule_group["condensate_count_spinner"].setValue(num_condensates)
245
258
 
246
259
  for j in range(num_condensates):
247
260
  condensate = molecule_group["condensates"][j]
@@ -291,10 +304,6 @@ class CondensateConfigWidget(QWidget):
291
304
  }
292
305
 
293
306
  def validate(self) -> bool:
294
- from ...cells import create_cell
295
- from ...configio.configmodels import CondensateParameters
296
- from ...motion import create_condensate_dict
297
-
298
307
  try:
299
308
  data = self.get_data()
300
309