Skip to content

Commit 4534146

Browse files
authored
Merge pull request #29 from Brain-Modulation-Lab/fix/styling
Fix/styling
2 parents b6fe35b + ff383ac commit 4534146

18 files changed

Lines changed: 711 additions & 441 deletions

src/clinical_dbs_annotator/config.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@
8484

8585
# Validation limits
8686
STIMULATION_LIMITS = {
87-
"frequency": {"min": 10, "max": 200, "step1": 5, "step2": 10},
88-
"amplitude": {"min": 0.0, "max": 15.0, "decimals": 2, "step1": 1.0, "step2": 0.5},
89-
"pulse_width": {"min": 10, "max": 200, "step1": 5, "step2": 10},
87+
"frequency": {"min": 10, "max": 200, "step1": 10, "step2": 5},
88+
"amplitude": {"min": 0.0, "max": 15.0, "decimals": 2, "step1": 1, "step2": 0.5},
89+
"pulse_width": {"min": 10, "max": 200, "step1": 10, "step2": 5},
9090
}
9191

9292
SESSION_SCALE_LIMITS = {
@@ -100,8 +100,8 @@
100100
CLINICAL_SCALES_PRESETS: dict[str, list[str]] = {
101101
"OCD": [
102102
"Y-BOCS", # Yale–Brown Obsessive–Compulsive Scale
103-
"Y-BOCS-o",
104-
"Y-BOCS-c",
103+
"Y-BOCS-o", # Yale–Brown Obsessive–Compulsive Scale - obsessions
104+
"Y-BOCS-c", # Yale–Brown Obsessive–Compulsive Scale - compulsions
105105
"MADRS", # Montgomery–Åsberg Depression Rating Scale
106106
"OCI-R", # Obsessive–Compulsive Inventory – Revised
107107
],
Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
{
2+
"Dystonia": [
3+
"BFMDRS",
4+
"TWSTRS"
5+
],
6+
"ET": [
7+
"FTM-TRS",
8+
"TETRAS"
9+
],
210
"MDD": [
3-
"MADRS"
11+
"MADRS",
12+
"HAM-D",
13+
"BDI-II"
414
],
515
"OCD": [
616
"Y-BOCS",
717
"Y-BOCS-o",
818
"Y-BOCS-c",
9-
"MADRS"
19+
"MADRS",
20+
"OCI-R"
1021
],
1122
"PD": [
12-
"UPDRS-III"
23+
"MDS-UPDRS",
24+
"UPDRS-III",
25+
"PDQ-39",
26+
"UDysRS"
27+
],
28+
"TS": [
29+
"YGTSS",
30+
"PUTS",
31+
"TS-CGI",
32+
"Y-BOCS"
1333
]
1434
}

src/clinical_dbs_annotator/controllers/wizard_controller.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@
99

1010
from PySide6.QtWidgets import QMessageBox
1111

12-
from ..config import (
13-
CLINICAL_SCALES_PRESETS,
14-
SESSION_SCALES_PRESETS,
15-
)
1612
from ..config_electrode_models import ELECTRODE_MODELS
1713
from ..models import ClinicalScale, SessionData, SessionScale, StimulationParameters
1814
from ..utils import animate_button
15+
from ..utils.scale_preset_manager import get_scale_preset_manager
1916

2017

2118
class WizardController:
@@ -61,7 +58,9 @@ def apply_clinical_preset(self, preset_name: str, view) -> None:
6158
preset_name: Name of the preset (e.g., "OCD", "MDD")
6259
view: The Step1View instance to update
6360
"""
64-
preset = CLINICAL_SCALES_PRESETS.get(preset_name, [])
61+
preset_manager = get_scale_preset_manager()
62+
clinical_presets = preset_manager.get_clinical_presets()
63+
preset = clinical_presets.get(preset_name, [])
6564
view.update_clinical_scales(
6665
preset,
6766
on_add_callback=lambda: self.on_add_clinical_scale(view),
@@ -76,20 +75,20 @@ def apply_session_preset(self, preset_name: str, view) -> None:
7675
preset_name: Name of the preset (e.g., "OCD", "MDD")
7776
view: The Step2View instance to update
7877
"""
79-
preset = []
80-
if hasattr(view, "session_presets"):
81-
try:
82-
preset = view.session_presets.get(preset_name, [])
83-
except Exception:
84-
preset = []
85-
if not preset:
86-
preset = SESSION_SCALES_PRESETS.get(preset_name, [])
78+
preset_manager = get_scale_preset_manager()
79+
session_presets = preset_manager.get_session_presets()
80+
preset = session_presets.get(preset_name, [])
8781
view.update_session_scales(
8882
preset,
8983
on_add_callback=lambda: self.on_add_session_scale(view),
9084
on_remove_callback=lambda row: self.on_remove_session_scale(view, row),
9185
)
9286

87+
# Set the active preset button
88+
preset_btn = view.get_preset_button(preset_name)
89+
if preset_btn:
90+
view._set_active_preset_button(preset_btn)
91+
9392
def on_add_clinical_scale(self, view) -> None:
9493
"""
9594
Handle add button click for clinical scales.
@@ -294,6 +293,30 @@ def prepare_step2(self, view) -> None:
294293
on_remove_callback=lambda row: self.on_remove_session_scale(view, row),
295294
)
296295

296+
def auto_select_session_preset(self, view, step1_view) -> None:
297+
"""
298+
Auto-select session preset if clinical preset with same name was selected.
299+
300+
Args:
301+
view: The Step2View instance
302+
step1_view: The Step1View instance to read active preset from
303+
"""
304+
# Read the active preset name from the step1_view's active button
305+
active_preset_name = None
306+
if step1_view and step1_view.active_preset_button is not None:
307+
obj_name = step1_view.active_preset_button.objectName()
308+
# objectName is like "preset_OCD" -> extract "OCD"
309+
if obj_name.startswith("preset_"):
310+
active_preset_name = obj_name[len("preset_") :]
311+
312+
if active_preset_name:
313+
preset_manager = get_scale_preset_manager()
314+
session_presets = preset_manager.get_session_presets()
315+
316+
if active_preset_name in session_presets:
317+
# Apply the matching session preset
318+
self.apply_session_preset(active_preset_name, view)
319+
297320
def validate_step2(self, view) -> bool:
298321
"""
299322
Validate Step 2 and collect session scale names.

src/clinical_dbs_annotator/ui/widgets.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,15 @@ def _setup_ui(self) -> None:
114114
hbox.setSpacing(0)
115115
hbox.addWidget(self.line_edit)
116116

117-
# Create primary increment/decrement buttons (step1)
118-
vbox1 = self._create_button_column(self.step1, double=True)
119-
hbox.addLayout(vbox1)
120-
121117
# Create secondary buttons if step2 is provided
122118
if self.step2 is not None:
123119
vbox2 = self._create_button_column(self.step2, double=False)
124120
hbox.addLayout(vbox2)
125121

122+
# Create primary increment/decrement buttons (step1)
123+
vbox1 = self._create_button_column(self.step1, double=True)
124+
hbox.addLayout(vbox1)
125+
126126
self.setMaximumWidth(self.sizeHint().width())
127127

128128
def _create_button_column(self, step: float, double: bool = False) -> QVBoxLayout:
@@ -142,10 +142,12 @@ def _create_button_column(self, step: float, double: bool = False) -> QVBoxLayou
142142

143143
# Create up button
144144
btn_up = self._create_arrow_button("up", double)
145+
btn_up.setToolTip(f"+{step}")
145146
btn_up.clicked.connect(lambda: self._adjust_value(+step))
146147

147148
# Create down button
148149
btn_down = self._create_arrow_button("down", double)
150+
btn_down.setToolTip(f"-{step}")
149151
btn_down.clicked.connect(lambda: self._adjust_value(-step))
150152

151153
vbox.addWidget(btn_up)
@@ -242,20 +244,20 @@ def _setup_ui(self) -> None:
242244
left_layout.setContentsMargins(0, 0, 0, 0)
243245
left_layout.setSpacing(2)
244246

245-
self.left_single_btn = self._create_icon_button(
246-
self._create_lr_arrow_icon("left", False), 18, 18
247-
)
248-
self.left_single_btn.setToolTip("-0.25")
249-
self.left_single_btn.clicked.connect(lambda: self._adjust_value(-1))
250-
251247
self.left_double_btn = self._create_icon_button(
252248
self._create_lr_arrow_icon("left", True), 24, 18
253249
)
254250
self.left_double_btn.setToolTip("-0.5")
255251
self.left_double_btn.clicked.connect(lambda: self._adjust_value(-2))
256252

257-
left_layout.addWidget(self.left_single_btn)
253+
self.left_single_btn = self._create_icon_button(
254+
self._create_lr_arrow_icon("left", False), 18, 18
255+
)
256+
self.left_single_btn.setToolTip("-0.25")
257+
self.left_single_btn.clicked.connect(lambda: self._adjust_value(-1))
258+
258259
left_layout.addWidget(self.left_double_btn)
260+
left_layout.addWidget(self.left_single_btn)
259261

260262
# Progress bar
261263
self.progress_bar = QProgressBar()
@@ -271,18 +273,18 @@ def _setup_ui(self) -> None:
271273
right_layout.setContentsMargins(0, 0, 0, 0)
272274
right_layout.setSpacing(2)
273275

274-
self.right_single_btn = self._create_icon_button(
275-
self._create_lr_arrow_icon("right", False), 18, 18
276-
)
277-
self.right_single_btn.setToolTip("+0.25")
278-
self.right_single_btn.clicked.connect(lambda: self._adjust_value(1))
279-
280276
self.right_double_btn = self._create_icon_button(
281277
self._create_lr_arrow_icon("right", True), 24, 18
282278
)
283279
self.right_double_btn.setToolTip("+0.5")
284280
self.right_double_btn.clicked.connect(lambda: self._adjust_value(2))
285281

282+
self.right_single_btn = self._create_icon_button(
283+
self._create_lr_arrow_icon("right", False), 18, 18
284+
)
285+
self.right_single_btn.setToolTip("+0.25")
286+
self.right_single_btn.clicked.connect(lambda: self._adjust_value(1))
287+
286288
self.reset_btn = self._create_icon_button(self._create_x_icon(), 18, 18)
287289
self.reset_btn.setToolTip("Disable/Enable")
288290
self.reset_btn.clicked.connect(self._toggle_disabled)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
Scale preset manager for loading and saving user-modified scale presets.
3+
4+
This module handles loading and saving clinical and session scale presets
5+
to a user config file in the application log directory.
6+
"""
7+
8+
import json
9+
import sys
10+
from pathlib import Path
11+
12+
from ..config import CLINICAL_SCALES_PRESETS, SESSION_SCALES_PRESETS
13+
14+
15+
class ScalePresetManager:
16+
"""Manager for scale presets with user customization support."""
17+
18+
def __init__(self, config_dir: str | None = None):
19+
"""Initialize the scale preset manager.
20+
21+
Args:
22+
config_dir: Directory for config files. If None, uses logs in app installation directory.
23+
"""
24+
if config_dir is None:
25+
# Default to logs folder in the application installation directory
26+
# For deployed app: C:\Program Files\BML\Clinical DBS Annotator\logs
27+
# For development: uses the source directory
28+
if getattr(sys, "frozen", False):
29+
# Running as deployed executable (PyInstaller/Nuitka)
30+
app_root = Path(sys.executable).parent
31+
else:
32+
# Running in development mode
33+
app_root = Path(__file__).parent.parent.parent
34+
self.config_dir = app_root / "logs"
35+
else:
36+
self.config_dir = Path(config_dir)
37+
38+
self.config_dir.mkdir(parents=True, exist_ok=True)
39+
self.config_file = self.config_dir / "scale_presets.json"
40+
41+
def get_clinical_presets(self) -> dict[str, list[str]]:
42+
"""Get clinical scale presets, loading user modifications if available.
43+
44+
Returns:
45+
Dictionary of clinical scale presets (preset name -> list of scale names)
46+
"""
47+
user_presets = self._load_user_presets()
48+
if user_presets and "clinical" in user_presets:
49+
return user_presets["clinical"]
50+
return CLINICAL_SCALES_PRESETS
51+
52+
def get_session_presets(self) -> dict[str, list[tuple[str, str, str]]]:
53+
"""Get session scale presets, loading user modifications if available.
54+
55+
Returns:
56+
Dictionary of session scale presets (preset name -> list of (name, min, max) tuples)
57+
"""
58+
user_presets = self._load_user_presets()
59+
if user_presets and "session" in user_presets:
60+
return user_presets["session"]
61+
return SESSION_SCALES_PRESETS
62+
63+
def save_clinical_presets(self, presets: dict[str, list[str]]) -> None:
64+
"""Save clinical scale presets to user config file.
65+
66+
Args:
67+
presets: Dictionary of clinical scale presets (preset name -> list of scale names)
68+
"""
69+
user_presets = self._load_user_presets() or {}
70+
user_presets["clinical"] = presets
71+
self._save_user_presets(user_presets)
72+
73+
def save_session_presets(
74+
self, presets: dict[str, list[tuple[str, str, str]]]
75+
) -> None:
76+
"""Save session scale presets to user config file.
77+
78+
Args:
79+
presets: Dictionary of session scale presets (preset name -> list of (name, min, max) tuples)
80+
"""
81+
user_presets = self._load_user_presets() or {}
82+
user_presets["session"] = presets
83+
self._save_user_presets(user_presets)
84+
85+
def _load_user_presets(self) -> dict | None:
86+
"""Load user presets from config file.
87+
88+
Returns:
89+
Dictionary with 'clinical' and 'session' keys, or None if file doesn't exist.
90+
"""
91+
if not self.config_file.exists():
92+
return None
93+
94+
try:
95+
with open(self.config_file, encoding="utf-8") as f:
96+
return json.load(f)
97+
except OSError, json.JSONDecodeError:
98+
return None
99+
100+
def _save_user_presets(self, presets: dict) -> None:
101+
"""Save user presets to config file.
102+
103+
Args:
104+
presets: Dictionary with 'clinical' and 'session' keys
105+
"""
106+
with open(self.config_file, "w", encoding="utf-8") as f:
107+
json.dump(presets, f, indent=4)
108+
109+
110+
# Global instance
111+
_preset_manager: ScalePresetManager | None = None
112+
113+
114+
def get_scale_preset_manager() -> ScalePresetManager:
115+
"""Get the global scale preset manager instance.
116+
117+
Returns:
118+
The global ScalePresetManager instance
119+
"""
120+
global _preset_manager
121+
if _preset_manager is None:
122+
_preset_manager = ScalePresetManager()
123+
return _preset_manager

src/clinical_dbs_annotator/utils/theme_manager.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import os
99
from enum import Enum
1010

11-
from PySide6.QtWidgets import QApplication
11+
from PySide6.QtGui import QColor, QPalette
12+
from PySide6.QtWidgets import QApplication, QToolTip
1213

1314
from .resources import resource_path
1415

@@ -142,6 +143,17 @@ def apply_theme(self, theme: Theme, app: QApplication = None) -> None:
142143
# Fallback to no stylesheet
143144
app.setStyleSheet("")
144145

146+
# Set QToolTip palette explicitly — QSS QToolTip{} rules are ignored
147+
# on some platforms (e.g. Windows native style) for non-main windows
148+
tooltip_palette = QPalette()
149+
if theme == Theme.DARK:
150+
tooltip_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#334155"))
151+
tooltip_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#f1f5f9"))
152+
else:
153+
tooltip_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#ffffff"))
154+
tooltip_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#0f172a"))
155+
QToolTip.setPalette(tooltip_palette)
156+
145157
def toggle_theme(self, app: QApplication = None) -> Theme:
146158
"""
147159
Toggle between dark and light themes.

src/clinical_dbs_annotator/views/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
for different steps of the annotation wizard.
66
"""
77

8-
from .annotations_simple_view import AnnotationsFileView, AnnotationsSessionView
8+
from .annotation_only_view import AnnotationsFileView, AnnotationsSessionView
99
from .base_view import BaseStepView
10-
from .longitudinal_file_view import LongitudinalFileView
10+
from .longitudinal_report_view import LongitudinalReportView as LongitudinalFileView
1111
from .step0_view import Step0View
1212
from .step1_view import Step1View
1313
from .step2_view import Step2View

src/clinical_dbs_annotator/views/annotations_simple_view.py renamed to src/clinical_dbs_annotator/views/annotation_only_view.py

File renamed without changes.

0 commit comments

Comments
 (0)