AMS-BP 0.4.0__py3-none-any.whl → 0.4.3__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/gui/README.md +40 -36
- AMS_BP/gui/configuration_window.py +71 -77
- AMS_BP/gui/main.py +32 -1
- 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 +11 -4
- AMS_BP/gui/widgets/cell_config_widget.py +10 -4
- AMS_BP/gui/widgets/channel_config_widget.py +10 -4
- AMS_BP/gui/widgets/condensate_config_widget.py +20 -11
- AMS_BP/gui/widgets/experiment_config_widget.py +19 -6
- AMS_BP/gui/widgets/flurophore_config_widget.py +5 -0
- AMS_BP/gui/widgets/global_config_widget.py +11 -4
- AMS_BP/gui/widgets/laser_config_widget.py +10 -4
- AMS_BP/gui/widgets/molecule_config_widget.py +14 -8
- AMS_BP/gui/widgets/utility_widgets/toggleswitch_widget.py +60 -0
- {ams_bp-0.4.0.dist-info → ams_bp-0.4.3.dist-info}/METADATA +12 -30
- {ams_bp-0.4.0.dist-info → ams_bp-0.4.3.dist-info}/RECORD +21 -18
- {ams_bp-0.4.0.dist-info → ams_bp-0.4.3.dist-info}/WHEEL +0 -0
- {ams_bp-0.4.0.dist-info → ams_bp-0.4.3.dist-info}/entry_points.txt +0 -0
- {ams_bp-0.4.0.dist-info → ams_bp-0.4.3.dist-info}/licenses/LICENSE +0 -0
AMS_BP/__init__.py
CHANGED
AMS_BP/gui/README.md
CHANGED
@@ -6,12 +6,12 @@ The AMS-BP GUI provides a user-friendly interface for constructing and validatin
|
|
6
6
|
|
7
7
|
This GUI supports:
|
8
8
|
|
9
|
-
A template-based configuration builder for quick setup.
|
10
|
-
A visual editor for each simulation parameter block (cells, molecules, fluorophores, lasers, etc.).
|
11
|
-
Interactive help sections for each tab.
|
12
|
-
Simulation execution using prebuilt configs.
|
13
|
-
Napari integration for visualization of resulting microscopy data.
|
14
|
-
Packaging of simulation logs for easy sharing.
|
9
|
+
- A template-based configuration builder for quick setup.
|
10
|
+
- A visual editor for each simulation parameter block (cells, molecules, fluorophores, lasers, etc.).
|
11
|
+
- Interactive help sections for each tab.
|
12
|
+
- Simulation execution using prebuilt configs.
|
13
|
+
- Napari integration for visualization of resulting microscopy data.
|
14
|
+
- Packaging of simulation logs for easy sharing.
|
15
15
|
|
16
16
|
## Launching the GUI
|
17
17
|
|
@@ -23,55 +23,59 @@ run_AMS_BP gui
|
|
23
23
|
### Main Window
|
24
24
|
The main window (MainWindow) acts as the launchpad for:
|
25
25
|
|
26
|
-
Configuration Builder — Launches a template selector and opens a full editor.
|
27
|
-
Run Simulation from Config — Lets you pick a .toml file and start the simulation.
|
28
|
-
Visualize Microscopy Data — Opens TIFF and other images in a Napari viewer.
|
29
|
-
Package Logs for Sharing — Archives a run_* folder into a .zip.
|
26
|
+
- Configuration Builder — Launches a template selector and opens a full editor.
|
27
|
+
- Run Simulation from Config — Lets you pick a .toml file and start the simulation.
|
28
|
+
- Visualize Microscopy Data — Opens TIFF and other images in a Napari viewer.
|
29
|
+
- Package Logs for Sharing — Archives a run_* folder into a .zip.
|
30
|
+
|
30
31
|
#### Configuration Editor
|
31
32
|
Once a template is selected, the ConfigEditor is launched. It contains:
|
32
33
|
|
33
|
-
A dropdown navigation system replacing traditional tabs.
|
34
|
-
A floating tab counter and preview/save buttons.
|
35
|
-
A help button per section (reads *.md files from help_docs/).
|
36
|
-
Live validation using internal config models (convertconfig.py).
|
34
|
+
- A dropdown navigation system replacing traditional tabs.
|
35
|
+
- A floating tab counter and preview/save buttons.
|
36
|
+
- A help button per section (reads *.md files from help_docs/).
|
37
|
+
- Live validation using internal config models (convertconfig.py).
|
38
|
+
|
37
39
|
##### Tabs & Widgets
|
38
40
|
Each configuration section is implemented as a modular PyQt widget, including:
|
39
41
|
|
40
|
-
GlobalConfigWidget
|
41
|
-
CellConfigWidget
|
42
|
-
MoleculeConfigWidget
|
43
|
-
FluorophoreConfigWidget
|
44
|
-
CondensateConfigWidget
|
45
|
-
LaserConfigWidget
|
46
|
-
ExperimentConfigWidget
|
47
|
-
...and more
|
42
|
+
- GlobalConfigWidget
|
43
|
+
- CellConfigWidget
|
44
|
+
- MoleculeConfigWidget
|
45
|
+
- FluorophoreConfigWidget
|
46
|
+
- CondensateConfigWidget
|
47
|
+
- LaserConfigWidget
|
48
|
+
- ExperimentConfigWidget
|
49
|
+
- ...and more
|
50
|
+
|
48
51
|
Each widget supports:
|
49
52
|
|
50
|
-
get_data() → Extract validated dictionary data
|
51
|
-
set_data(data: dict) → Load existing config into the UI
|
52
|
-
validate() → Validate using backend logic
|
53
|
-
get_help_path() → Load the corresponding markdown help page
|
53
|
+
- get_data() → Extract validated dictionary data
|
54
|
+
- set_data(data: dict) → Load existing config into the UI
|
55
|
+
- validate() → Validate using backend logic
|
56
|
+
- get_help_path() → Load the corresponding markdown help page
|
54
57
|
|
55
58
|
#### Running a Simulation
|
56
59
|
|
57
60
|
Once you've completed the config file setup via the GUI:
|
58
61
|
|
59
|
-
Click "Preview Configuration TOML" to confirm contents.
|
60
|
-
Click "Ready to Save Configuration" and choose a .toml path.
|
61
|
-
Return to the main window, click "Run Simulation from Config".
|
62
|
-
Simulation will launch in a background thread and print logs to a live window.
|
63
|
-
Once done, results will be saved in a run_*/ folder.
|
62
|
+
- Click "Preview Configuration TOML" to confirm contents.
|
63
|
+
- Click "Ready to Save Configuration" and choose a .toml path.
|
64
|
+
- Return to the main window, click "Run Simulation from Config".
|
65
|
+
- Simulation will launch in a background thread and print logs to a live window.
|
66
|
+
- Once done, results will be saved in a run_*/ folder.
|
64
67
|
|
65
68
|
#### Viewing Results
|
66
69
|
|
67
|
-
Click "Visualize Microscopy Data (Napari)"
|
68
|
-
Select any .tif, .tiff, .nd2, or .zarr file
|
70
|
+
- Click "Visualize Microscopy Data (Napari)"
|
71
|
+
- Select any .tif, .tiff, .nd2, or .zarr file
|
72
|
+
|
69
73
|
The data will be loaded into a new Napari viewer session
|
70
74
|
|
71
75
|
#### Packaging Logs
|
72
76
|
|
73
77
|
To share or archive a completed simulation:
|
74
78
|
|
75
|
-
Click "Package Logs for Sharing".
|
76
|
-
Select the run_* folder you want.
|
77
|
-
Choose a destination for the .zip file.
|
79
|
+
- Click "Package Logs for Sharing".
|
80
|
+
- Select the run_* folder you want.
|
81
|
+
- Choose a destination for the .zip file.
|
@@ -2,14 +2,16 @@ 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,
|
@@ -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,35 @@ 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
|
+
|
104
114
|
self.molecule_tab.molecule_count_changed.connect(
|
105
115
|
self.fluorophore_tab.set_mfluorophore_count
|
106
116
|
)
|
107
117
|
self.molecule_tab.molecule_count_changed.connect(
|
108
118
|
self.condensate_tab.set_molecule_count
|
109
119
|
)
|
110
|
-
|
120
|
+
|
111
121
|
self.fluorophore_tab.mfluorophore_count_changed.connect(
|
112
122
|
self.molecule_tab.set_molecule_count
|
113
123
|
)
|
114
124
|
self.fluorophore_tab.mfluorophore_count_changed.connect(
|
115
125
|
self.condensate_tab.set_molecule_count
|
116
126
|
)
|
117
|
-
|
127
|
+
|
118
128
|
self.condensate_tab.molecule_count_changed.connect(
|
119
129
|
self.molecule_tab.set_molecule_count
|
120
130
|
)
|
121
131
|
self.condensate_tab.molecule_count_changed.connect(
|
122
132
|
self.fluorophore_tab.set_mfluorophore_count
|
123
133
|
)
|
124
|
-
|
134
|
+
|
125
135
|
self.laser_tab.laser_names_updated.connect(
|
126
136
|
self.experiment_tab.set_active_lasers
|
127
137
|
)
|
128
138
|
|
129
|
-
# Add
|
139
|
+
# === Add tab widgets to the stack ===
|
130
140
|
self.stacked_widget.addWidget(self.general_tab)
|
131
141
|
self.stacked_widget.addWidget(self.global_tab)
|
132
142
|
self.stacked_widget.addWidget(self.cell_tab)
|
@@ -140,27 +150,13 @@ class ConfigEditor(QWidget):
|
|
140
150
|
self.stacked_widget.addWidget(self.output_tab)
|
141
151
|
self.stacked_widget.addWidget(self.experiment_tab)
|
142
152
|
|
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)
|
153
|
+
# Final layout and window size
|
154
|
+
self.setLayout(main_layout)
|
155
|
+
self.setMinimumSize(1100, 750)
|
156
|
+
self.resize(1250, 850)
|
161
157
|
|
162
|
-
#
|
163
|
-
self.
|
158
|
+
# Initial tab display
|
159
|
+
self.on_tab_selected(0)
|
164
160
|
|
165
161
|
def set_data(self, config: dict):
|
166
162
|
if "Cell_Parameters" in config:
|
@@ -236,14 +232,12 @@ class ConfigEditor(QWidget):
|
|
236
232
|
doc[key] = val
|
237
233
|
return doc
|
238
234
|
|
239
|
-
def
|
240
|
-
"""Change the displayed widget
|
235
|
+
def on_tab_selected(self, index: int):
|
236
|
+
"""Change the displayed widget and update breadcrumb/step label."""
|
241
237
|
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}")
|
238
|
+
total = self.nav_list.count()
|
239
|
+
current = self.nav_list.item(index).text()
|
240
|
+
self.step_label.setText(f"Step {index + 1}/{total} — {current}")
|
247
241
|
|
248
242
|
def validate_all_tabs(self) -> bool:
|
249
243
|
return all(
|
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,
|
@@ -22,6 +23,7 @@ from ..logging.setup_run_directory import setup_run_directory
|
|
22
23
|
from .logging_window import LogWindow
|
23
24
|
from .sim_worker import SimulationWorker
|
24
25
|
from .template_window_selection import TemplateSelectionWindow
|
26
|
+
from .widgets.utility_widgets.toggleswitch_widget import ToggleSwitch
|
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()
|
@@ -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,6 +8,7 @@ from PyQt6.QtWidgets import (
|
|
8
8
|
QHBoxLayout,
|
9
9
|
QMessageBox,
|
10
10
|
QPushButton,
|
11
|
+
QSizePolicy,
|
11
12
|
QSpinBox,
|
12
13
|
QVBoxLayout,
|
13
14
|
QWidget,
|
@@ -19,12 +20,14 @@ from .utility_widgets.spectrum_widget import SpectrumEditorDialog
|
|
19
20
|
class CameraConfigWidget(QWidget):
|
20
21
|
def __init__(self):
|
21
22
|
super().__init__()
|
23
|
+
|
22
24
|
layout = QVBoxLayout()
|
23
|
-
|
25
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
26
|
+
layout.setSpacing(10)
|
27
|
+
self.setLayout(layout)
|
28
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
24
29
|
|
25
|
-
|
26
|
-
self.validate_button.clicked.connect(self.validate)
|
27
|
-
layout.addWidget(self.validate_button)
|
30
|
+
form = QFormLayout()
|
28
31
|
|
29
32
|
# Camera type (Only "CMOS" is available)
|
30
33
|
self.camera_type = QComboBox()
|
@@ -89,6 +92,10 @@ class CameraConfigWidget(QWidget):
|
|
89
92
|
layout.addLayout(form)
|
90
93
|
self.setLayout(layout)
|
91
94
|
|
95
|
+
self.validate_button = QPushButton("Validate Parameters")
|
96
|
+
self.validate_button.clicked.connect(self.validate)
|
97
|
+
layout.addWidget(self.validate_button)
|
98
|
+
|
92
99
|
def _hbox(self, widgets):
|
93
100
|
box = QHBoxLayout()
|
94
101
|
for w in widgets:
|
@@ -8,6 +8,7 @@ from PyQt6.QtWidgets import (
|
|
8
8
|
QHBoxLayout,
|
9
9
|
QMessageBox,
|
10
10
|
QPushButton,
|
11
|
+
QSizePolicy,
|
11
12
|
QStackedWidget,
|
12
13
|
QVBoxLayout,
|
13
14
|
QWidget,
|
@@ -18,11 +19,12 @@ class CellConfigWidget(QWidget):
|
|
18
19
|
def __init__(self):
|
19
20
|
super().__init__()
|
20
21
|
layout = QVBoxLayout()
|
21
|
-
|
22
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
23
|
+
layout.setSpacing(10)
|
24
|
+
self.setLayout(layout)
|
25
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
22
26
|
|
23
|
-
|
24
|
-
self.validate_button.clicked.connect(self.validate)
|
25
|
-
layout.addWidget(self.validate_button)
|
27
|
+
form = QFormLayout()
|
26
28
|
|
27
29
|
self.cell_type = QComboBox()
|
28
30
|
self.cell_type.addItems(
|
@@ -46,6 +48,10 @@ class CellConfigWidget(QWidget):
|
|
46
48
|
layout.addWidget(self.param_stack)
|
47
49
|
self.setLayout(layout)
|
48
50
|
|
51
|
+
self.validate_button = QPushButton("Validate Parameters")
|
52
|
+
self.validate_button.clicked.connect(self.validate)
|
53
|
+
layout.addWidget(self.validate_button)
|
54
|
+
|
49
55
|
def validate(self) -> bool:
|
50
56
|
from ...cells import create_cell
|
51
57
|
from ...configio.configmodels import CellParameters
|
@@ -8,6 +8,7 @@ from PyQt6.QtWidgets import (
|
|
8
8
|
QLineEdit,
|
9
9
|
QMessageBox,
|
10
10
|
QPushButton,
|
11
|
+
QSizePolicy,
|
11
12
|
QSpinBox,
|
12
13
|
QTabWidget,
|
13
14
|
QVBoxLayout,
|
@@ -21,11 +22,12 @@ class ChannelConfigWidget(QWidget):
|
|
21
22
|
self.channel_widgets = []
|
22
23
|
|
23
24
|
layout = QVBoxLayout()
|
24
|
-
|
25
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
26
|
+
layout.setSpacing(10)
|
27
|
+
self.setLayout(layout)
|
28
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
25
29
|
|
26
|
-
|
27
|
-
self.validate_button.clicked.connect(self.validate)
|
28
|
-
layout.addWidget(self.validate_button)
|
30
|
+
form = QFormLayout()
|
29
31
|
|
30
32
|
self.num_channels = QSpinBox()
|
31
33
|
self.num_channels.setRange(1, 10)
|
@@ -40,6 +42,10 @@ class ChannelConfigWidget(QWidget):
|
|
40
42
|
layout.addWidget(self.channel_tabs)
|
41
43
|
self.setLayout(layout)
|
42
44
|
|
45
|
+
self.validate_button = QPushButton("Validate Parameters")
|
46
|
+
self.validate_button.clicked.connect(self.validate)
|
47
|
+
layout.addWidget(self.validate_button)
|
48
|
+
|
43
49
|
def validate(self) -> bool:
|
44
50
|
try:
|
45
51
|
from ...configio.convertconfig import create_channels
|
@@ -11,6 +11,7 @@ from PyQt6.QtWidgets import (
|
|
11
11
|
QMessageBox,
|
12
12
|
QPushButton,
|
13
13
|
QScrollArea,
|
14
|
+
QSizePolicy,
|
14
15
|
QSpinBox,
|
15
16
|
QTabWidget,
|
16
17
|
QVBoxLayout,
|
@@ -31,6 +32,10 @@ class CondensateConfigWidget(QWidget):
|
|
31
32
|
|
32
33
|
def setup_ui(self):
|
33
34
|
layout = QVBoxLayout()
|
35
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
36
|
+
layout.setSpacing(10)
|
37
|
+
self.setLayout(layout)
|
38
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
34
39
|
|
35
40
|
# Instructions label
|
36
41
|
instructions = QLabel(
|
@@ -113,6 +118,18 @@ class CondensateConfigWidget(QWidget):
|
|
113
118
|
condensate_controls.addWidget(condensate_count)
|
114
119
|
layout.addLayout(condensate_controls)
|
115
120
|
|
121
|
+
# === Density Difference field RIGHT AFTER the condensate count ===
|
122
|
+
density_layout = QHBoxLayout()
|
123
|
+
density_layout.addWidget(QLabel("Density Difference:"))
|
124
|
+
|
125
|
+
density_spin = QDoubleSpinBox()
|
126
|
+
density_spin.setRange(0, 100)
|
127
|
+
density_spin.setValue(1.0)
|
128
|
+
density_spin.setDecimals(3)
|
129
|
+
density_layout.addWidget(density_spin)
|
130
|
+
layout.addLayout(density_layout)
|
131
|
+
|
132
|
+
# === Condensate containers BELOW this point ===
|
116
133
|
condensate_container = QVBoxLayout()
|
117
134
|
layout.addLayout(condensate_container)
|
118
135
|
|
@@ -126,25 +143,16 @@ class CondensateConfigWidget(QWidget):
|
|
126
143
|
)
|
127
144
|
)
|
128
145
|
|
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
146
|
self.condensate_widgets.append(
|
140
147
|
{
|
141
148
|
"condensates": condensate_widgets,
|
142
149
|
"density_widget": density_spin,
|
150
|
+
"condensate_count_spinner": condensate_count,
|
143
151
|
}
|
144
152
|
)
|
153
|
+
|
145
154
|
molecule_widget.setLayout(layout)
|
146
155
|
scroll_area.setWidget(molecule_widget)
|
147
|
-
|
148
156
|
self.tab_widget.addTab(scroll_area, f"Molecule Type {index + 1}")
|
149
157
|
|
150
158
|
def add_condensate_group(self, index, condensate_widgets, condensate_container):
|
@@ -242,6 +250,7 @@ class CondensateConfigWidget(QWidget):
|
|
242
250
|
molecule_group["condensates"],
|
243
251
|
molecule_group_layout,
|
244
252
|
)
|
253
|
+
molecule_group["condensate_count_spinner"].setValue(num_condensates)
|
245
254
|
|
246
255
|
for j in range(num_condensates):
|
247
256
|
condensate = molecule_group["condensates"][j]
|
@@ -10,6 +10,8 @@ from PyQt6.QtWidgets import (
|
|
10
10
|
QLineEdit,
|
11
11
|
QMessageBox,
|
12
12
|
QPushButton,
|
13
|
+
QScrollArea,
|
14
|
+
QSizePolicy,
|
13
15
|
QSpinBox,
|
14
16
|
QTabWidget,
|
15
17
|
QVBoxLayout,
|
@@ -25,6 +27,10 @@ class ExperimentConfigWidget(QWidget):
|
|
25
27
|
self.laser_position_widgets = {}
|
26
28
|
|
27
29
|
layout = QVBoxLayout()
|
30
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
31
|
+
layout.setSpacing(10)
|
32
|
+
self.setLayout(layout)
|
33
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
28
34
|
form = QFormLayout()
|
29
35
|
|
30
36
|
# Experiment Info
|
@@ -38,13 +44,20 @@ class ExperimentConfigWidget(QWidget):
|
|
38
44
|
self.type_field.addItems(["time-series", "z-stack"])
|
39
45
|
form.addRow("Experiment Type:", self.type_field)
|
40
46
|
|
41
|
-
# Z Position
|
47
|
+
# Z Position inputs
|
42
48
|
self.z_position_inputs: List[QDoubleSpinBox] = []
|
43
49
|
|
50
|
+
# Scrollable container for z-position inputs
|
51
|
+
self.z_scroll_area = QScrollArea()
|
52
|
+
self.z_scroll_area.setWidgetResizable(True)
|
53
|
+
self.z_scroll_area.setFixedHeight(150) # Adjust height as needed
|
54
|
+
|
44
55
|
self.z_position_container = QWidget()
|
45
|
-
self.z_position_layout = QVBoxLayout()
|
56
|
+
self.z_position_layout = QVBoxLayout(self.z_position_container)
|
46
57
|
self.z_position_container.setLayout(self.z_position_layout)
|
47
|
-
|
58
|
+
|
59
|
+
self.z_scroll_area.setWidget(self.z_position_container)
|
60
|
+
form.addRow("Z Position(s):", self.z_scroll_area)
|
48
61
|
|
49
62
|
self.add_z_button = QPushButton("Add Z-Position")
|
50
63
|
self.remove_z_button = QPushButton("Remove Z-Position")
|
@@ -83,13 +96,13 @@ class ExperimentConfigWidget(QWidget):
|
|
83
96
|
layout.addWidget(QLabel("Active Laser Parameters:"))
|
84
97
|
layout.addWidget(self.laser_tabs)
|
85
98
|
|
99
|
+
self.setLayout(layout)
|
100
|
+
|
86
101
|
# Validate Button
|
87
|
-
self.validate_button = QPushButton("Validate")
|
102
|
+
self.validate_button = QPushButton("Validate Parameters")
|
88
103
|
self.validate_button.clicked.connect(self.validate)
|
89
104
|
layout.addWidget(self.validate_button)
|
90
105
|
|
91
|
-
self.setLayout(layout)
|
92
|
-
|
93
106
|
def update_z_position_mode(self, mode: str):
|
94
107
|
# Clear existing
|
95
108
|
for i in reversed(range(self.z_position_layout.count())):
|
@@ -11,6 +11,7 @@ from PyQt6.QtWidgets import (
|
|
11
11
|
QMessageBox,
|
12
12
|
QPushButton,
|
13
13
|
QScrollArea,
|
14
|
+
QSizePolicy,
|
14
15
|
QSpinBox,
|
15
16
|
QTabWidget,
|
16
17
|
QVBoxLayout,
|
@@ -33,6 +34,10 @@ class FluorophoreConfigWidget(QWidget):
|
|
33
34
|
|
34
35
|
def setup_ui(self):
|
35
36
|
layout = QVBoxLayout()
|
37
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
38
|
+
layout.setSpacing(10)
|
39
|
+
self.setLayout(layout)
|
40
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
36
41
|
|
37
42
|
instructions = QLabel(
|
38
43
|
"Configure fluorophores and their respective states and transitions."
|
@@ -5,6 +5,7 @@ from PyQt6.QtWidgets import (
|
|
5
5
|
QHBoxLayout,
|
6
6
|
QMessageBox,
|
7
7
|
QPushButton,
|
8
|
+
QSizePolicy,
|
8
9
|
QSpinBox,
|
9
10
|
QVBoxLayout,
|
10
11
|
QWidget,
|
@@ -18,11 +19,12 @@ class GlobalConfigWidget(QWidget):
|
|
18
19
|
|
19
20
|
def setup_ui(self):
|
20
21
|
layout = QVBoxLayout()
|
22
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
23
|
+
layout.setSpacing(10)
|
24
|
+
self.setLayout(layout)
|
25
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
26
|
+
|
21
27
|
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
28
|
|
27
29
|
# Sample plane dimensions
|
28
30
|
self.sample_plane_width = QSpinBox()
|
@@ -71,6 +73,11 @@ class GlobalConfigWidget(QWidget):
|
|
71
73
|
|
72
74
|
self.setLayout(layout)
|
73
75
|
|
76
|
+
# Validation button
|
77
|
+
self.validate_button = QPushButton("Validate Parameters")
|
78
|
+
self.validate_button.clicked.connect(self.validate)
|
79
|
+
layout.addWidget(self.validate_button)
|
80
|
+
|
74
81
|
def set_defaults(self):
|
75
82
|
"""Set default values for the form fields"""
|
76
83
|
self.sample_plane_width.setValue(50) # 1000 μm
|
@@ -8,6 +8,7 @@ from PyQt6.QtWidgets import (
|
|
8
8
|
QLineEdit,
|
9
9
|
QMessageBox,
|
10
10
|
QPushButton,
|
11
|
+
QSizePolicy,
|
11
12
|
QSpinBox,
|
12
13
|
QTabWidget,
|
13
14
|
QVBoxLayout,
|
@@ -22,11 +23,12 @@ class LaserConfigWidget(QWidget):
|
|
22
23
|
super().__init__()
|
23
24
|
self.laser_name_widgets = []
|
24
25
|
layout = QVBoxLayout()
|
25
|
-
|
26
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
27
|
+
layout.setSpacing(10)
|
28
|
+
self.setLayout(layout)
|
29
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
26
30
|
|
27
|
-
|
28
|
-
self.validate_button.clicked.connect(self.validate)
|
29
|
-
layout.addWidget(self.validate_button)
|
31
|
+
form = QFormLayout()
|
30
32
|
|
31
33
|
self.num_lasers = QSpinBox()
|
32
34
|
self.num_lasers.setRange(1, 10)
|
@@ -44,6 +46,10 @@ class LaserConfigWidget(QWidget):
|
|
44
46
|
layout.addWidget(self.laser_tabs)
|
45
47
|
self.setLayout(layout)
|
46
48
|
|
49
|
+
self.validate_button = QPushButton("Validate Parameters")
|
50
|
+
self.validate_button.clicked.connect(self.validate)
|
51
|
+
layout.addWidget(self.validate_button)
|
52
|
+
|
47
53
|
def set_confocal_mode(self, enabled: bool):
|
48
54
|
for i in range(self.laser_tabs.count()):
|
49
55
|
tab = self.laser_tabs.widget(i)
|
@@ -13,6 +13,7 @@ from PyQt6.QtWidgets import (
|
|
13
13
|
QMessageBox,
|
14
14
|
QPushButton,
|
15
15
|
QScrollArea,
|
16
|
+
QSizePolicy,
|
16
17
|
QSpinBox,
|
17
18
|
QTabWidget,
|
18
19
|
QVBoxLayout,
|
@@ -28,6 +29,11 @@ class MoleculeConfigWidget(QWidget):
|
|
28
29
|
super().__init__()
|
29
30
|
|
30
31
|
self.main_layout = QVBoxLayout()
|
32
|
+
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
33
|
+
self.main_layout.setSpacing(10)
|
34
|
+
self.setLayout(self.main_layout)
|
35
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
36
|
+
|
31
37
|
self.setLayout(self.main_layout)
|
32
38
|
|
33
39
|
# Number of molecule types spinner
|
@@ -41,11 +47,6 @@ class MoleculeConfigWidget(QWidget):
|
|
41
47
|
self.num_types_layout.addWidget(self.num_types_spinner)
|
42
48
|
self.num_types_layout.addStretch()
|
43
49
|
|
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
50
|
self.main_layout.addLayout(self.num_types_layout)
|
50
51
|
|
51
52
|
# Create tab widget to hold molecule type configs
|
@@ -56,6 +57,11 @@ class MoleculeConfigWidget(QWidget):
|
|
56
57
|
self.molecule_type_widgets = []
|
57
58
|
self.update_molecule_types(1)
|
58
59
|
|
60
|
+
# Add the validate button at the bottom
|
61
|
+
self.validate_button = QPushButton("Validate Parameters")
|
62
|
+
self.validate_button.clicked.connect(self.validate)
|
63
|
+
self.main_layout.addWidget(self.validate_button)
|
64
|
+
|
59
65
|
def _on_molecule_count_changed(self, count):
|
60
66
|
self.update_molecule_types(count)
|
61
67
|
self.molecule_count_changed.emit(count)
|
@@ -235,6 +241,9 @@ class MoleculeConfigWidget(QWidget):
|
|
235
241
|
}
|
236
242
|
self.molecule_type_widgets[i].set_data(type_data)
|
237
243
|
|
244
|
+
def get_help_path(self) -> Path:
|
245
|
+
return Path(__file__).parent.parent / "help_docs" / "molecule_help.md"
|
246
|
+
|
238
247
|
|
239
248
|
class MoleculeTypeWidget(QWidget):
|
240
249
|
def __init__(self, type_index):
|
@@ -709,6 +718,3 @@ class TransitionMatrixWidget(QWidget):
|
|
709
718
|
for i in range(size):
|
710
719
|
for j in range(min(size, len(matrix[i]))):
|
711
720
|
self.spinboxes[i][j].setValue(matrix[i][j])
|
712
|
-
|
713
|
-
def get_help_path(self) -> Path:
|
714
|
-
return Path(__file__).parent.parent / "help_docs" / "molecule_help.md"
|
@@ -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()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: AMS_BP
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.3
|
4
4
|
Summary: Advanced Microscopy Simulations developed for the Weber Lab by Baljyot Singh Parmar
|
5
5
|
Project-URL: Documentation, https://joemans3.github.io/AMS_BP/
|
6
6
|
Project-URL: Source code, https://github.com/joemans3/AMS_BP
|
@@ -46,7 +46,7 @@ Find detailed API references for the library at: [joemans3/github.io/AMS_BP](htt
|
|
46
46
|
|
47
47
|
> !!ATTENTION!! - Please note that you NEED to install the developmental dependencies to run the examples in full. This is mainly for installing the Jupyter notebook extensions, matplotlib and other visualization packages.
|
48
48
|
|
49
|
-
[<img src="./docs/assets/buttons/ButtonFigure_FRAP.svg" width="300" height="120"/>](./examples/QuantitativeExperiments/FRAP/FRAP_methods.ipynb) [<img src="./docs/assets/buttons/ButtonFigure_fPALM_NPC.svg" width="300"/>](./examples/QuantitativeExperiments/
|
49
|
+
[<img src="./docs/assets/buttons/ButtonFigure_FRAP.svg" width="300" height="120"/>](./examples/QuantitativeExperiments/FRAP/FRAP_methods.ipynb) [<img src="./docs/assets/buttons/ButtonFigure_fPALM_NPC.svg" width="300"/>](./examples/QuantitativeExperiments/PALM/fPALM/npc_palm.ipynb)
|
50
50
|
|
51
51
|
[<img src="./docs/assets/buttons/ButtonFigure_zstack_twocolor_widefield.svg" width="300"/>](./examples/QuantitativeExperiments/TwoColor/Widefield/widefield_twocolor.ipynb) [<img src="./docs/assets/buttons/ButtonFigure_zstack_twocolor_confocal.svg" width="300"/>](./examples/QuantitativeExperiments/TwoColor/Confocal/confocal_twocolor.ipynb)
|
52
52
|
|
@@ -63,31 +63,29 @@ Find detailed API references for the library at: [joemans3/github.io/AMS_BP](htt
|
|
63
63
|
|
64
64
|
|
65
65
|
### ***Installing the CLI tool using UV***
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
66
|
1. [Install UV](https://docs.astral.sh/uv/getting-started/installation/).
|
71
67
|
2. Run the command:
|
72
68
|
```bash
|
73
69
|
uv tool install AMS_BP
|
74
70
|
```
|
75
|
-
3. You will have access to
|
71
|
+
3. You will have access to three CLI commands (using the uv interface):
|
76
72
|
- `run_AMS_BP runsim` : This is the main entry point for the simulation. (see `run_AMS_BP runsim --help` for more details)
|
77
73
|
- `run_AMS_BP config` : This is a helper tool to generate a template config file for the simulation. (see `run_AMS_BP config --help` for more details)
|
74
|
+
- `run_AMS_BP gui` : to start the GUI. See [GUI Documentation](./src/AMS_BP/gui/README.md)
|
78
75
|
- Note: using `run_AMS_BP --help` will show you all the available commands.
|
79
76
|
4. You can now use these tools (they are isolated in their own env created by uv, which is cool).
|
80
77
|
|
81
78
|
### ***PyPi***
|
82
79
|
|
83
|
-
1.
|
80
|
+
1. If using pip, make sure the environment is python >= 3.12
|
81
|
+
2. Run:
|
84
82
|
```bash
|
85
83
|
pip install AMS_BP
|
86
84
|
```
|
87
85
|
|
88
86
|
## Command Line Interface
|
89
87
|
|
90
|
-
AMS-BP provides a command-line interface with
|
88
|
+
AMS-BP provides a command-line interface with three main commands:
|
91
89
|
|
92
90
|
```bash
|
93
91
|
# Generate a default configuration file
|
@@ -111,10 +109,10 @@ In addition to the CLI and programmatic API, AMS-BP comes with a graphical inter
|
|
111
109
|
### Main GUI Features
|
112
110
|
The GUI provides the following tools from a single interface:
|
113
111
|
|
114
|
-
**Create Configuration File** — Launches the visual configuration builder
|
115
|
-
**Run Simulation from Config** — Select a .toml file and run the simulation with logging and progress tracking
|
116
|
-
**Visualize Microscopy Data (Napari)** — Open TIFF, PNG, ND2, or Zarr image files and view with the Napari viewer
|
117
|
-
**Package Logs for Sharing** — Package run directories (e.g., run_2024_04_20_001) into a .zip file for archival or collaboration
|
112
|
+
- **Create Configuration File** — Launches the visual configuration builder
|
113
|
+
- **Run Simulation from Config** — Select a .toml file and run the simulation with logging and progress tracking
|
114
|
+
- **Visualize Microscopy Data (Napari)** — Open TIFF, PNG, ND2, or Zarr image files and view with the Napari viewer
|
115
|
+
- **Package Logs for Sharing** — Package run directories (e.g., run_2024_04_20_001) into a .zip file for archival or collaboration
|
118
116
|
|
119
117
|
### Launch the GUI
|
120
118
|
To start the GUI, run:
|
@@ -123,23 +121,7 @@ To start the GUI, run:
|
|
123
121
|
|
124
122
|
run_AMS_BP gui
|
125
123
|
```
|
126
|
-
|
127
|
-
Clicking "Create Configuration File" opens a template selector where you can choose a preconfigured simulation (with preview images), and then visually edit all configuration options through dedicated tabs.
|
128
|
-
|
129
|
-
Each section of the configuration is editable via structured UI forms, with contextual help and validation. Tabs include:
|
130
|
-
|
131
|
-
- Global/Cell/Molecule/Condensate/Fluorophore parameters
|
132
|
-
- Laser and optical configuration
|
133
|
-
- Camera and channel settings
|
134
|
-
- Experiment setup (e.g., z-stack vs time-series)
|
135
|
-
Once ready, click "Preview Configuration TOML" to inspect the final file, and "Save" to export.
|
136
|
-
|
137
|
-
#### Running Simulations from GUI
|
138
|
-
Clicking "Run Simulation from Config" lets you select any .toml configuration file. A real-time log viewer shows progress, and outputs are saved in a structured AMS_runs/run_*/ directory.
|
139
|
-
|
140
|
-
#### Sharing Logs
|
141
|
-
Run into an issue? Use the packge logs button to select the logs corresponding to the simulation you just ran, save them and send them over to us! It will help you diagnose the issue!
|
142
|
-
|
124
|
+
For detailed walkthrough see the [GUI Documentation](./src/AMS_BP/gui/README.md).
|
143
125
|
## Configuration File
|
144
126
|
|
145
127
|
The configuration file (sim_config.toml) is divided into several key sections:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
AMS_BP/__init__.py,sha256=
|
1
|
+
AMS_BP/__init__.py,sha256=HU_BdGVyL7jcOOv7vIsvzAcl1MzpHKqDjt9B36Nrd0g,326
|
2
2
|
AMS_BP/main_cli.py,sha256=DpRzWDsmI0nJvrzJj58F9k1k5ShSj8pCT7N0oH-_8BU,5786
|
3
3
|
AMS_BP/run_sim_util.py,sha256=UQr0l9rbtTmzuuWXjkIaTQV-ETO5AqyXRwflF6U2WYs,2415
|
4
4
|
AMS_BP/sim_config.toml,sha256=G0wW9qsxciZA1Y0Zm6YgZ00ZDBEBBLw_XXq4gsVRjSY,11778
|
@@ -13,12 +13,12 @@ AMS_BP/configio/experiments.py,sha256=HdfaSi0gPPJ_wLF87XcW5ICja19Uezx7-ygFEwNzi3
|
|
13
13
|
AMS_BP/configio/saving.py,sha256=596QgAadV32rzsN4B2FngGFcBWCzCDnLFN-qtQsv3bM,857
|
14
14
|
AMS_BP/groundtruth_generators/__init__.py,sha256=UPVmhiB81OfyqAes5LoN-n6XgQuBCYCqRPAGd2jpMfc,99
|
15
15
|
AMS_BP/groundtruth_generators/nuclearporecomplexes.py,sha256=1aBcWltsKo0OGd7A9GfuEZ3azQBx5SzpjrSLLMXuYn8,2518
|
16
|
-
AMS_BP/gui/README.md,sha256=
|
16
|
+
AMS_BP/gui/README.md,sha256=z2XreAPQeeFmKSXbhsa2kznHbk-YbLD55-VPWikNQp4,2778
|
17
17
|
AMS_BP/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
-
AMS_BP/gui/configuration_window.py,sha256=
|
18
|
+
AMS_BP/gui/configuration_window.py,sha256=BNlJvwKKAVPMh7cXxOSl9DGvWVwSc6sPwLlZAGGdGes,12101
|
19
19
|
AMS_BP/gui/help_window.py,sha256=Jn844Vez7ULC1VGKb4iGgRvuw3wPTd0pcyRv7VWbeV4,790
|
20
20
|
AMS_BP/gui/logging_window.py,sha256=a7HbmfYP3YavLuyT317YRE1xWXJtB4i8EoK9QAstOv8,2938
|
21
|
-
AMS_BP/gui/main.py,sha256=
|
21
|
+
AMS_BP/gui/main.py,sha256=O2FPkyIjCcEbn3dPBefowXQx4kfuB1oUkTyIDI5Me8M,10653
|
22
22
|
AMS_BP/gui/sim_worker.py,sha256=SYKbHGQNd9laL2YMjPfm755eRTyub434fSTQ5mGS9iM,1989
|
23
23
|
AMS_BP/gui/template_window_selection.py,sha256=GgLfApuTa4qNiek0Ksiqy15aqhdOIZyaReFYAS9NDoc,3034
|
24
24
|
AMS_BP/gui/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -36,22 +36,25 @@ AMS_BP/gui/help_docs/laser_help.md,sha256=wzloXV_1WCT6zRTrf1jKC-cMlepntacZV7jbLL
|
|
36
36
|
AMS_BP/gui/help_docs/molecule_help.md,sha256=5wnyNlCTWjCsgwzRFG7XhnlGXao6qXj-2ZL0yfnHQng,2793
|
37
37
|
AMS_BP/gui/help_docs/output_help.md,sha256=Rg0dV0N4ClrJvPTt5HaJHuQgHE5nmgKwiQCAHhV0A24,225
|
38
38
|
AMS_BP/gui/help_docs/psf_help.md,sha256=onH-suSxocfj9mD6WCE6lG7yxov1yCoGeau82oG-zmo,1854
|
39
|
+
AMS_BP/gui/themes/dark_theme.qss,sha256=6MUyaaZnmZCnXTNNJuqi7NN8Om8P8P9pSfAffv4ampA,1462
|
40
|
+
AMS_BP/gui/themes/light_theme.qss,sha256=wVEIcnFzGVZN7ohLHrus6zX5ybGnyfTwihAPSvhrDL8,1424
|
39
41
|
AMS_BP/gui/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
AMS_BP/gui/widgets/camera_config_widget.py,sha256=
|
41
|
-
AMS_BP/gui/widgets/cell_config_widget.py,sha256=
|
42
|
-
AMS_BP/gui/widgets/channel_config_widget.py,sha256=
|
43
|
-
AMS_BP/gui/widgets/condensate_config_widget.py,sha256=
|
44
|
-
AMS_BP/gui/widgets/experiment_config_widget.py,sha256=
|
45
|
-
AMS_BP/gui/widgets/flurophore_config_widget.py,sha256=
|
42
|
+
AMS_BP/gui/widgets/camera_config_widget.py,sha256=pYzSSNUBzo2TFzw3qwSW-PuduD6M3q4XqDCGZJpdUWs,7787
|
43
|
+
AMS_BP/gui/widgets/cell_config_widget.py,sha256=rNQljIob3oUGa02bKiG9kxHWlE2-o30gEH4oUv43Msg,8062
|
44
|
+
AMS_BP/gui/widgets/channel_config_widget.py,sha256=zBfP9xySSqBQWSyXyaGqPViTHJhQKCen6gMcLl9Tgx0,11007
|
45
|
+
AMS_BP/gui/widgets/condensate_config_widget.py,sha256=K2sYIHhXbqmpDf6edWaxnYVYEjDYffciucN7XivJc8M,12573
|
46
|
+
AMS_BP/gui/widgets/experiment_config_widget.py,sha256=Oa_3XvzOk3TmFdQ0LmOLdq8P8kRb6HVwc-ypw7YFq78,9815
|
47
|
+
AMS_BP/gui/widgets/flurophore_config_widget.py,sha256=jOWwo7hmX7b_fNcbaTbHLfaoKTivvdiq8-yHs2b8198,19897
|
46
48
|
AMS_BP/gui/widgets/general_config_widget.py,sha256=YbnE11TnWZBgb-xqKWM00p8RdSp-9eTbwTZ9mikPvZc,1321
|
47
|
-
AMS_BP/gui/widgets/global_config_widget.py,sha256=
|
48
|
-
AMS_BP/gui/widgets/laser_config_widget.py,sha256=
|
49
|
-
AMS_BP/gui/widgets/molecule_config_widget.py,sha256=
|
49
|
+
AMS_BP/gui/widgets/global_config_widget.py,sha256=QqkJiPFZfe73a1fbgf6oCmLacbp3Cda7UH4lr0A3rXo,5098
|
50
|
+
AMS_BP/gui/widgets/laser_config_widget.py,sha256=oqINXtRUhVzwW4HE9jsHcFWVp3q5n4RBlXumdZZoHjM,8964
|
51
|
+
AMS_BP/gui/widgets/molecule_config_widget.py,sha256=Bog31_wjTJ0uyw4qpL_3m8EK7jlc3GNMgn-7EOS-k0g,28063
|
50
52
|
AMS_BP/gui/widgets/output_config_widget.py,sha256=pExiAHIHs0-J-dY2lZtfB0ei9oNjZIJKDQtWDSjFZOY,2105
|
51
53
|
AMS_BP/gui/widgets/psf_config_widget.py,sha256=75ggeolYI_Sbn5LD_hUGY3S4Z9uRkJGtzZhb79NgsY4,4534
|
52
54
|
AMS_BP/gui/widgets/utility_widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
53
55
|
AMS_BP/gui/widgets/utility_widgets/scinotation_widget.py,sha256=sLrQqueRJ0wEy4aF1I8UJO0Bolnm7yTsAyfNqrHiAKI,553
|
54
56
|
AMS_BP/gui/widgets/utility_widgets/spectrum_widget.py,sha256=QQjYV-aErjyI1HdOZMIPaddHpi0sag6r4V8nBSdgEn8,3659
|
57
|
+
AMS_BP/gui/widgets/utility_widgets/toggleswitch_widget.py,sha256=vldANnsLcoqwmM8PNQc6PZAMtPN2h6A-JCCx9etBLKU,1945
|
55
58
|
AMS_BP/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
59
|
AMS_BP/logging/logutil.py,sha256=HkkB0mF8SL68gmJcOngBppNi6InX18Goxro5LJR-inA,2420
|
57
60
|
AMS_BP/logging/setup_run_directory.py,sha256=B9sG_N3lOya_gVaJTs7GDVXpTY1Ov3jMPGTj2ttydM0,897
|
@@ -96,8 +99,8 @@ AMS_BP/utils/decorators.py,sha256=4qFdvzPJne0dhkhD1znPxRln1Rfr5NX8rdcCDcbATRU,62
|
|
96
99
|
AMS_BP/utils/errors.py,sha256=7BOd-L4_YeKmWn3Q4EOdTnNF3Bj_exDa3eg5X0yCZrc,759
|
97
100
|
AMS_BP/utils/maskMaker.py,sha256=2ca3n2nc8rFtUh1LurKXOJJsUmhrOpWbRnVX7fjRVvs,335
|
98
101
|
AMS_BP/utils/util_functions.py,sha256=9Qlr4kjY04fObktR8TrzB0IgoG1yXtcmxPRX9AN34mM,9671
|
99
|
-
ams_bp-0.4.
|
100
|
-
ams_bp-0.4.
|
101
|
-
ams_bp-0.4.
|
102
|
-
ams_bp-0.4.
|
103
|
-
ams_bp-0.4.
|
102
|
+
ams_bp-0.4.3.dist-info/METADATA,sha256=tsHlRpGj25zeFtHI46UbcCRm73MRIiBXIU8MpD2ogxA,9593
|
103
|
+
ams_bp-0.4.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
104
|
+
ams_bp-0.4.3.dist-info/entry_points.txt,sha256=06NS85P4dz6vosMOKXHfx8l7gK9WXBZxuwjl55XfT2c,65
|
105
|
+
ams_bp-0.4.3.dist-info/licenses/LICENSE,sha256=k_-JV1DQKvO0FR8WjvOisqdTl0kp6VJ7RFM3YZhao0c,1071
|
106
|
+
ams_bp-0.4.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|