canns 0.13.1__py3-none-any.whl → 0.14.0__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 (99) hide show
  1. canns/analyzer/data/__init__.py +5 -1
  2. canns/analyzer/data/asa/__init__.py +27 -12
  3. canns/analyzer/data/asa/cohospace.py +336 -10
  4. canns/analyzer/data/asa/config.py +3 -0
  5. canns/analyzer/data/asa/embedding.py +48 -45
  6. canns/analyzer/data/asa/path.py +104 -2
  7. canns/analyzer/data/asa/plotting.py +88 -19
  8. canns/analyzer/data/asa/tda.py +11 -4
  9. canns/analyzer/data/cell_classification/__init__.py +97 -0
  10. canns/analyzer/data/cell_classification/core/__init__.py +26 -0
  11. canns/analyzer/data/cell_classification/core/grid_cells.py +633 -0
  12. canns/analyzer/data/cell_classification/core/grid_modules_leiden.py +288 -0
  13. canns/analyzer/data/cell_classification/core/head_direction.py +347 -0
  14. canns/analyzer/data/cell_classification/core/spatial_analysis.py +431 -0
  15. canns/analyzer/data/cell_classification/io/__init__.py +5 -0
  16. canns/analyzer/data/cell_classification/io/matlab_loader.py +417 -0
  17. canns/analyzer/data/cell_classification/utils/__init__.py +39 -0
  18. canns/analyzer/data/cell_classification/utils/circular_stats.py +383 -0
  19. canns/analyzer/data/cell_classification/utils/correlation.py +318 -0
  20. canns/analyzer/data/cell_classification/utils/geometry.py +442 -0
  21. canns/analyzer/data/cell_classification/utils/image_processing.py +416 -0
  22. canns/analyzer/data/cell_classification/visualization/__init__.py +19 -0
  23. canns/analyzer/data/cell_classification/visualization/grid_plots.py +292 -0
  24. canns/analyzer/data/cell_classification/visualization/hd_plots.py +200 -0
  25. canns/analyzer/metrics/__init__.py +2 -1
  26. canns/analyzer/visualization/core/config.py +46 -4
  27. canns/data/__init__.py +6 -1
  28. canns/data/datasets.py +154 -1
  29. canns/data/loaders.py +37 -0
  30. canns/pipeline/__init__.py +13 -9
  31. canns/pipeline/__main__.py +6 -0
  32. canns/pipeline/asa/runner.py +105 -41
  33. canns/pipeline/asa_gui/__init__.py +68 -0
  34. canns/pipeline/asa_gui/__main__.py +6 -0
  35. canns/pipeline/asa_gui/analysis_modes/__init__.py +42 -0
  36. canns/pipeline/asa_gui/analysis_modes/base.py +39 -0
  37. canns/pipeline/asa_gui/analysis_modes/batch_mode.py +21 -0
  38. canns/pipeline/asa_gui/analysis_modes/cohomap_mode.py +56 -0
  39. canns/pipeline/asa_gui/analysis_modes/cohospace_mode.py +194 -0
  40. canns/pipeline/asa_gui/analysis_modes/decode_mode.py +52 -0
  41. canns/pipeline/asa_gui/analysis_modes/fr_mode.py +81 -0
  42. canns/pipeline/asa_gui/analysis_modes/frm_mode.py +92 -0
  43. canns/pipeline/asa_gui/analysis_modes/gridscore_mode.py +123 -0
  44. canns/pipeline/asa_gui/analysis_modes/pathcompare_mode.py +199 -0
  45. canns/pipeline/asa_gui/analysis_modes/tda_mode.py +112 -0
  46. canns/pipeline/asa_gui/app.py +29 -0
  47. canns/pipeline/asa_gui/controllers/__init__.py +6 -0
  48. canns/pipeline/asa_gui/controllers/analysis_controller.py +59 -0
  49. canns/pipeline/asa_gui/controllers/preprocess_controller.py +89 -0
  50. canns/pipeline/asa_gui/core/__init__.py +15 -0
  51. canns/pipeline/asa_gui/core/cache.py +14 -0
  52. canns/pipeline/asa_gui/core/runner.py +1936 -0
  53. canns/pipeline/asa_gui/core/state.py +324 -0
  54. canns/pipeline/asa_gui/core/worker.py +260 -0
  55. canns/pipeline/asa_gui/main_window.py +184 -0
  56. canns/pipeline/asa_gui/models/__init__.py +7 -0
  57. canns/pipeline/asa_gui/models/config.py +14 -0
  58. canns/pipeline/asa_gui/models/job.py +31 -0
  59. canns/pipeline/asa_gui/models/presets.py +21 -0
  60. canns/pipeline/asa_gui/resources/__init__.py +16 -0
  61. canns/pipeline/asa_gui/resources/dark.qss +167 -0
  62. canns/pipeline/asa_gui/resources/light.qss +163 -0
  63. canns/pipeline/asa_gui/resources/styles.qss +130 -0
  64. canns/pipeline/asa_gui/utils/__init__.py +1 -0
  65. canns/pipeline/asa_gui/utils/formatters.py +15 -0
  66. canns/pipeline/asa_gui/utils/io_adapters.py +40 -0
  67. canns/pipeline/asa_gui/utils/validators.py +41 -0
  68. canns/pipeline/asa_gui/views/__init__.py +1 -0
  69. canns/pipeline/asa_gui/views/help_content.py +171 -0
  70. canns/pipeline/asa_gui/views/pages/__init__.py +6 -0
  71. canns/pipeline/asa_gui/views/pages/analysis_page.py +565 -0
  72. canns/pipeline/asa_gui/views/pages/preprocess_page.py +492 -0
  73. canns/pipeline/asa_gui/views/panels/__init__.py +1 -0
  74. canns/pipeline/asa_gui/views/widgets/__init__.py +21 -0
  75. canns/pipeline/asa_gui/views/widgets/artifacts_tab.py +44 -0
  76. canns/pipeline/asa_gui/views/widgets/drop_zone.py +80 -0
  77. canns/pipeline/asa_gui/views/widgets/file_list.py +27 -0
  78. canns/pipeline/asa_gui/views/widgets/gridscore_tab.py +308 -0
  79. canns/pipeline/asa_gui/views/widgets/help_dialog.py +27 -0
  80. canns/pipeline/asa_gui/views/widgets/image_tab.py +50 -0
  81. canns/pipeline/asa_gui/views/widgets/image_viewer.py +97 -0
  82. canns/pipeline/asa_gui/views/widgets/log_box.py +16 -0
  83. canns/pipeline/asa_gui/views/widgets/pathcompare_tab.py +200 -0
  84. canns/pipeline/asa_gui/views/widgets/popup_combo.py +25 -0
  85. canns/pipeline/gallery/__init__.py +15 -5
  86. canns/pipeline/gallery/__main__.py +11 -0
  87. canns/pipeline/gallery/app.py +705 -0
  88. canns/pipeline/gallery/runner.py +790 -0
  89. canns/pipeline/gallery/state.py +51 -0
  90. canns/pipeline/gallery/styles.tcss +123 -0
  91. canns/pipeline/launcher.py +81 -0
  92. {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/METADATA +11 -1
  93. canns-0.14.0.dist-info/RECORD +163 -0
  94. canns-0.14.0.dist-info/entry_points.txt +5 -0
  95. canns/pipeline/_base.py +0 -50
  96. canns-0.13.1.dist-info/RECORD +0 -89
  97. canns-0.13.1.dist-info/entry_points.txt +0 -3
  98. {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/WHEEL +0 -0
  99. {canns-0.13.1.dist-info → canns-0.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,184 @@
1
+ """Main window for ASA GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from PySide6.QtCore import QSettings, Qt, QUrl, Signal
8
+ from PySide6.QtGui import QDesktopServices, QIcon
9
+ from PySide6.QtWidgets import (
10
+ QApplication,
11
+ QComboBox,
12
+ QHBoxLayout,
13
+ QLabel,
14
+ QMainWindow,
15
+ QPushButton,
16
+ QStackedWidget,
17
+ QVBoxLayout,
18
+ QWidget,
19
+ )
20
+
21
+ from .controllers import AnalysisController, PreprocessController
22
+ from .core import PipelineRunner, StateManager, WorkerManager
23
+ from .resources import load_theme_qss
24
+ from .views.pages.analysis_page import AnalysisPage
25
+ from .views.pages.preprocess_page import PreprocessPage
26
+ from .views.widgets.popup_combo import PopupComboBox
27
+
28
+
29
+ class LogoLabel(QLabel):
30
+ clicked = Signal()
31
+
32
+ def mousePressEvent(self, event) -> None: # pragma: no cover - UI callback
33
+ if event.button() == Qt.LeftButton:
34
+ self.clicked.emit()
35
+ super().mousePressEvent(event)
36
+
37
+
38
+ class MainWindow(QMainWindow):
39
+ def __init__(self, parent=None) -> None:
40
+ super().__init__(parent)
41
+ self.setWindowTitle("ASA GUI")
42
+ self.resize(1200, 800)
43
+
44
+ self._settings = QSettings("canns", "asa_gui")
45
+
46
+ self._state_manager = StateManager()
47
+ self._runner = PipelineRunner()
48
+ self._workers = WorkerManager()
49
+
50
+ self._preprocess_controller = PreprocessController(self._state_manager, self._runner)
51
+ self._analysis_controller = AnalysisController(self._state_manager, self._runner)
52
+
53
+ self._build_ui()
54
+
55
+ def _build_ui(self) -> None:
56
+ root = QWidget()
57
+ layout = QVBoxLayout(root)
58
+
59
+ nav = QHBoxLayout()
60
+ self.logo_label = LogoLabel()
61
+ self.logo_label.setCursor(Qt.PointingHandCursor)
62
+ logo_height = 32 if sys.platform == "darwin" else 40
63
+ logo_pixmap = self._load_logo_pixmap(logo_height)
64
+ if logo_pixmap is not None:
65
+ self.logo_label.setPixmap(logo_pixmap)
66
+ self.logo_label.setFixedSize(logo_pixmap.size())
67
+ self.logo_label.setAlignment(Qt.AlignCenter)
68
+ nav.addWidget(self.logo_label)
69
+ self.logo_label.clicked.connect(self._open_homepage)
70
+ self.btn_preprocess = QPushButton("Preprocess")
71
+ self.btn_preprocess.setObjectName("navButton")
72
+ self.btn_preprocess.setCheckable(True)
73
+ self.btn_analysis = QPushButton("Analysis")
74
+ self.btn_analysis.setObjectName("navButton")
75
+ self.btn_analysis.setCheckable(True)
76
+ self.btn_preprocess.clicked.connect(lambda: self._stack.setCurrentIndex(0))
77
+ self.btn_analysis.clicked.connect(lambda: self._stack.setCurrentIndex(1))
78
+ nav.addWidget(self.btn_preprocess)
79
+ nav.addWidget(self.btn_analysis)
80
+ nav.addStretch(1)
81
+ self.theme_switch = PopupComboBox()
82
+ self.theme_switch.addItem("Light", userData="Light")
83
+ self.theme_switch.addItem("Dark", userData="Dark")
84
+ self.theme_switch.currentIndexChanged.connect(self._apply_theme)
85
+ self.theme_switch.setSizeAdjustPolicy(QComboBox.AdjustToContents)
86
+ self.theme_switch.setMinimumWidth(90)
87
+ nav.addWidget(self.theme_switch)
88
+ self.lang_switch = PopupComboBox()
89
+ self.lang_switch.addItem("EN", userData="en")
90
+ self.lang_switch.addItem("中文", userData="zh")
91
+ self.lang_switch.currentIndexChanged.connect(self._apply_language)
92
+ self.lang_switch.setSizeAdjustPolicy(QComboBox.AdjustToContents)
93
+ self.lang_switch.setMinimumWidth(90)
94
+ nav.addWidget(self.lang_switch)
95
+ layout.addLayout(nav)
96
+
97
+ self._stack = QStackedWidget()
98
+ self.preprocess_page = PreprocessPage(self._preprocess_controller, self._workers)
99
+ self.analysis_page = AnalysisPage(self._analysis_controller, self._workers)
100
+
101
+ self._stack.addWidget(self.preprocess_page)
102
+ self._stack.addWidget(self.analysis_page)
103
+ layout.addWidget(self._stack, 1)
104
+
105
+ self.preprocess_page.preprocess_completed.connect(self._on_preprocess_completed)
106
+ self._stack.currentChanged.connect(self._sync_nav)
107
+
108
+ self.setCentralWidget(root)
109
+ self._init_theme()
110
+ self._init_language()
111
+ self._init_icons(str(self.theme_switch.currentData() or "Light"))
112
+ self._sync_nav(self._stack.currentIndex())
113
+
114
+ def _go_analysis(self) -> None:
115
+ self._stack.setCurrentIndex(1)
116
+
117
+ def _on_preprocess_completed(self) -> None:
118
+ self._stack.setCurrentIndex(1)
119
+ self.analysis_page.load_state(self._state_manager.state)
120
+
121
+ def _sync_nav(self, index: int) -> None:
122
+ self.btn_preprocess.setChecked(index == 0)
123
+ self.btn_analysis.setChecked(index == 1)
124
+
125
+ def _init_theme(self) -> None:
126
+ theme = str(self._settings.value("theme", "Light"))
127
+ idx = 0 if theme.lower().startswith("light") else 1
128
+ self.theme_switch.setCurrentIndex(idx)
129
+ self._apply_theme()
130
+
131
+ def _init_language(self) -> None:
132
+ lang = str(self._settings.value("lang", "en")).lower()
133
+ idx = 0 if lang.startswith("en") else 1
134
+ self.lang_switch.setCurrentIndex(idx)
135
+ self._apply_language()
136
+
137
+ def _apply_theme(self) -> None:
138
+ try:
139
+ theme = str(self.theme_switch.currentData() or "Light")
140
+ qss = load_theme_qss(theme)
141
+ QApplication.instance().setStyleSheet(qss)
142
+ self._settings.setValue("theme", theme)
143
+ self._init_icons(theme)
144
+ except Exception:
145
+ pass
146
+
147
+ def _apply_language(self) -> None:
148
+ lang = self.lang_switch.currentData() or "en"
149
+ self._settings.setValue("lang", str(lang))
150
+ is_zh = str(lang).lower().startswith("zh")
151
+ self.btn_preprocess.setText("预处理" if is_zh else "Preprocess")
152
+ self.btn_analysis.setText("分析" if is_zh else "Analysis")
153
+ self.preprocess_page.apply_language(str(lang))
154
+ self.analysis_page.apply_language(str(lang))
155
+
156
+ def _init_icons(self, theme: str) -> None:
157
+ try:
158
+ import qtawesome as qta
159
+
160
+ color = "#34d399" if str(theme).lower().startswith("dark") else "#10b981"
161
+ self.btn_preprocess.setIcon(qta.icon("fa5s.sliders-h", color=color))
162
+ self.btn_analysis.setIcon(qta.icon("fa5s.chart-area", color=color))
163
+ except Exception:
164
+ pass
165
+
166
+ def _load_logo_pixmap(self, height: int):
167
+ from pathlib import Path
168
+
169
+ base = Path(__file__).resolve().parents[4] / "images"
170
+ logo_path = base / "logo.svg"
171
+ if not logo_path.exists():
172
+ return None
173
+ icon = QIcon(str(logo_path))
174
+ if icon.isNull():
175
+ return None
176
+ size = max(64, int(height) * 4)
177
+ pixmap = icon.pixmap(size, size)
178
+ return pixmap.scaledToHeight(int(height), Qt.SmoothTransformation)
179
+
180
+ def _open_homepage(self) -> None:
181
+ QDesktopServices.openUrl(QUrl("https://github.com/Routhleck/canns"))
182
+
183
+
184
+ ASAGuiApp = MainWindow
@@ -0,0 +1,7 @@
1
+ """Data models for ASA GUI."""
2
+
3
+ from .config import AnalysisConfig
4
+ from .job import JobResult, JobSpec
5
+ from .presets import get_preset_params
6
+
7
+ __all__ = ["AnalysisConfig", "JobSpec", "JobResult", "get_preset_params"]
@@ -0,0 +1,14 @@
1
+ """Lightweight config helpers for ASA GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+
9
+ @dataclass
10
+ class AnalysisConfig:
11
+ """Simple container for analysis parameters."""
12
+
13
+ mode: str = "tda"
14
+ params: dict[str, Any] = field(default_factory=dict)
@@ -0,0 +1,31 @@
1
+ """Job specification and results for ASA GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+
10
+ @dataclass
11
+ class JobSpec:
12
+ """Inputs and parameters for a single analysis run."""
13
+
14
+ input_mode: str # "asa" | "batch" | "neuron_traj"
15
+ preset: str # "grid" | "hd" | "none"
16
+ asa_file: Path | None = None
17
+ neuron_file: Path | None = None
18
+ traj_file: Path | None = None
19
+ out_dir: Path = Path("Results/asa_gui_job")
20
+ params: dict[str, Any] = field(default_factory=dict)
21
+
22
+
23
+ @dataclass
24
+ class JobResult:
25
+ """Result container for pipeline execution."""
26
+
27
+ ok: bool
28
+ out_dir: Path
29
+ artifacts: dict[str, Path] = field(default_factory=dict)
30
+ summary: dict[str, Any] = field(default_factory=dict)
31
+ errors: list[str] = field(default_factory=list)
@@ -0,0 +1,21 @@
1
+ """Preset parameter hints for ASA GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def get_preset_params(preset: str) -> dict[str, Any]:
9
+ """Return default parameter overrides for a preset."""
10
+ preset = (preset or "none").lower()
11
+ if preset == "grid":
12
+ return {
13
+ "tda": {"maxdim": 2},
14
+ "decode": {"num_circ": 2},
15
+ }
16
+ if preset == "hd":
17
+ return {
18
+ "tda": {"maxdim": 1},
19
+ "decode": {"num_circ": 1},
20
+ }
21
+ return {}
@@ -0,0 +1,16 @@
1
+ """Static resources for ASA GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def load_theme_qss(theme: str) -> str:
9
+ """Load the QSS for a given theme name."""
10
+ name = (theme or "Light").strip().lower()
11
+ if name in {"dark", "dark mode", "darkmode"}:
12
+ fname = "dark.qss"
13
+ else:
14
+ fname = "light.qss"
15
+ path = Path(__file__).parent / fname
16
+ return path.read_text(encoding="utf-8")
@@ -0,0 +1,167 @@
1
+ /* Dark theme */
2
+ QWidget {
3
+ font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", "Arial", sans-serif;
4
+ font-size: 11pt;
5
+ color: #e5e7eb;
6
+ background-color: #111827;
7
+ }
8
+
9
+ QLabel {
10
+ background: transparent;
11
+ }
12
+
13
+ QLabel#muted {
14
+ color: #9ca3af;
15
+ }
16
+
17
+ QFrame#card, QGroupBox#card {
18
+ background-color: #1f2937;
19
+ border: 1px solid #374151;
20
+ border-radius: 12px;
21
+ }
22
+
23
+ QGroupBox {
24
+ border: 1px solid #374151;
25
+ border-radius: 10px;
26
+ margin-top: 18px;
27
+ background-color: #1f2937;
28
+ }
29
+
30
+ QGroupBox::title {
31
+ subcontrol-origin: margin;
32
+ left: 10px;
33
+ padding: 0 6px;
34
+ color: #34d399;
35
+ font-weight: 600;
36
+ }
37
+
38
+ QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox {
39
+ background-color: #374151;
40
+ color: #e5e7eb;
41
+ border: 1px solid #4b5563;
42
+ border-radius: 6px;
43
+ padding: 4px 8px;
44
+ min-width: 120px;
45
+ selection-background-color: #34d399;
46
+ }
47
+
48
+ QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QComboBox:focus {
49
+ border: 1px solid #34d399;
50
+ }
51
+
52
+
53
+ QPushButton {
54
+ background-color: #374151;
55
+ color: #e5e7eb;
56
+ border: 1px solid #4b5563;
57
+ border-radius: 6px;
58
+ padding: 6px 12px;
59
+ font-weight: 600;
60
+ }
61
+
62
+ QPushButton:hover {
63
+ background-color: #4b5563;
64
+ border-color: #6b7280;
65
+ }
66
+
67
+ QPushButton#btn_run {
68
+ background-color: #059669;
69
+ color: #ffffff;
70
+ border: none;
71
+ }
72
+
73
+ QPushButton#btn_run:hover {
74
+ background-color: #10b981;
75
+ }
76
+
77
+ QPushButton#btn_stop {
78
+ background-color: #991b1b;
79
+ color: #ffffff;
80
+ border: none;
81
+ }
82
+
83
+ QPushButton#btn_stop:hover {
84
+ background-color: #b91c1c;
85
+ }
86
+
87
+ QPushButton#navButton {
88
+ background-color: transparent;
89
+ border: none;
90
+ color: #9ca3af;
91
+ font-weight: 600;
92
+ padding: 6px 10px;
93
+ }
94
+
95
+ QPushButton#navButton:checked {
96
+ color: #34d399;
97
+ border-bottom: 2px solid #34d399;
98
+ }
99
+
100
+ QTabWidget::pane {
101
+ border: 1px solid #374151;
102
+ background: #1f2937;
103
+ border-radius: 8px;
104
+ }
105
+
106
+ QTabBar::tab {
107
+ background: transparent;
108
+ border: none;
109
+ padding: 8px 14px;
110
+ margin-right: 4px;
111
+ color: #9ca3af;
112
+ font-weight: 600;
113
+ }
114
+
115
+ QTabBar::tab:selected {
116
+ color: #34d399;
117
+ border-bottom: 2px solid #34d399;
118
+ }
119
+
120
+ QProgressBar {
121
+ border: none;
122
+ background-color: #374151;
123
+ border-radius: 4px;
124
+ text-align: center;
125
+ color: #e5e7eb;
126
+ }
127
+
128
+ QProgressBar::chunk {
129
+ background-color: #059669;
130
+ border-radius: 4px;
131
+ }
132
+
133
+ QTextEdit, QListWidget {
134
+ background-color: #0b0f14;
135
+ color: #a7f3d0;
136
+ border: 1px solid #374151;
137
+ border-radius: 8px;
138
+ font-family: "Consolas", "Menlo", "Monaco", monospace;
139
+ }
140
+
141
+ QSplitter::handle {
142
+ background: #374151;
143
+ }
144
+
145
+ QScrollArea {
146
+ background: transparent;
147
+ border: none;
148
+ }
149
+
150
+ QGraphicsView {
151
+ background: #0f172a;
152
+ border: 1px solid #374151;
153
+ border-radius: 8px;
154
+ }
155
+
156
+ QFrame#DropZone {
157
+ border: 2px dashed #6b7280;
158
+ border-radius: 12px;
159
+ background-color: #111827;
160
+ color: #9ca3af;
161
+ }
162
+
163
+ QFrame#DropZone[drag="true"] {
164
+ background-color: #0f172a;
165
+ border-color: #34d399;
166
+ color: #34d399;
167
+ }
@@ -0,0 +1,163 @@
1
+ /* Light theme */
2
+ QWidget {
3
+ font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", "Arial", sans-serif;
4
+ font-size: 11pt;
5
+ color: #374151;
6
+ background-color: #f3f4f6;
7
+ }
8
+
9
+ QLabel {
10
+ background: transparent;
11
+ }
12
+
13
+ QLabel#muted {
14
+ color: #6b7280;
15
+ }
16
+
17
+ QFrame#card, QGroupBox#card {
18
+ background-color: #ffffff;
19
+ border: 1px solid #e5e7eb;
20
+ border-radius: 12px;
21
+ }
22
+
23
+ QGroupBox {
24
+ border: 1px solid #e5e7eb;
25
+ border-radius: 10px;
26
+ margin-top: 18px;
27
+ background-color: #ffffff;
28
+ }
29
+
30
+ QGroupBox::title {
31
+ subcontrol-origin: margin;
32
+ left: 10px;
33
+ padding: 0 6px;
34
+ color: #10b981;
35
+ font-weight: 600;
36
+ }
37
+
38
+ QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox {
39
+ background-color: #ffffff;
40
+ border: 1px solid #d1d5db;
41
+ border-radius: 6px;
42
+ padding: 4px 8px;
43
+ min-width: 120px;
44
+ selection-background-color: #10b981;
45
+ }
46
+
47
+ QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QComboBox:focus {
48
+ border: 1px solid #10b981;
49
+ background-color: #f0fdf4;
50
+ }
51
+
52
+
53
+ QPushButton {
54
+ background-color: #ffffff;
55
+ border: 1px solid #d1d5db;
56
+ border-radius: 6px;
57
+ padding: 6px 12px;
58
+ font-weight: 600;
59
+ }
60
+
61
+ QPushButton:hover {
62
+ background-color: #f9fafb;
63
+ border-color: #9ca3af;
64
+ }
65
+
66
+ QPushButton#btn_run {
67
+ background-color: #10b981;
68
+ color: #ffffff;
69
+ border: none;
70
+ }
71
+
72
+ QPushButton#btn_run:hover {
73
+ background-color: #059669;
74
+ }
75
+
76
+ QPushButton#btn_stop {
77
+ background-color: #ef4444;
78
+ color: #ffffff;
79
+ border: none;
80
+ }
81
+
82
+ QPushButton#btn_stop:hover {
83
+ background-color: #dc2626;
84
+ }
85
+
86
+ QPushButton#navButton {
87
+ background-color: transparent;
88
+ border: none;
89
+ color: #6b7280;
90
+ font-weight: 600;
91
+ padding: 6px 10px;
92
+ }
93
+
94
+ QPushButton#navButton:checked {
95
+ color: #10b981;
96
+ border-bottom: 2px solid #10b981;
97
+ }
98
+
99
+ QTabWidget::pane {
100
+ border: 1px solid #e5e7eb;
101
+ background: #ffffff;
102
+ border-radius: 8px;
103
+ }
104
+
105
+ QTabBar::tab {
106
+ background: transparent;
107
+ border: none;
108
+ padding: 8px 14px;
109
+ margin-right: 4px;
110
+ color: #6b7280;
111
+ font-weight: 600;
112
+ }
113
+
114
+ QTabBar::tab:selected {
115
+ color: #10b981;
116
+ border-bottom: 2px solid #10b981;
117
+ }
118
+
119
+ QProgressBar {
120
+ border: none;
121
+ background-color: #e5e7eb;
122
+ border-radius: 4px;
123
+ text-align: center;
124
+ }
125
+
126
+ QProgressBar::chunk {
127
+ background-color: #10b981;
128
+ border-radius: 4px;
129
+ }
130
+
131
+ QTextEdit, QListWidget {
132
+ background-color: #ffffff;
133
+ border: 1px solid #e5e7eb;
134
+ border-radius: 8px;
135
+ }
136
+
137
+ QSplitter::handle {
138
+ background: #e5e7eb;
139
+ }
140
+
141
+ QScrollArea {
142
+ background: transparent;
143
+ border: none;
144
+ }
145
+
146
+ QGraphicsView {
147
+ background: #ffffff;
148
+ border: 1px solid #e5e7eb;
149
+ border-radius: 8px;
150
+ }
151
+
152
+ QFrame#DropZone {
153
+ border: 2px dashed #9ca3af;
154
+ border-radius: 12px;
155
+ background-color: #f9fafb;
156
+ color: #6b7280;
157
+ }
158
+
159
+ QFrame#DropZone[drag="true"] {
160
+ background-color: #f0fdf4;
161
+ border-color: #10b981;
162
+ color: #10b981;
163
+ }