Coverage for main/ui/tool_selection_page.py: 98%
110 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
1"""
2tool_selection_page.py
4This module defines the `ToolChoicePage` class — a PyQt6 page that allows the user
5to select the next processing step in the GliAAns pipeline.
7The available options include:
8- Skull Stripping
9- Automatic Drawing
10- Deep Learning Segmentation (Linux + CUDA only)
11- Full Pipeline Execution
13Depending on the selected option, the next page is dynamically loaded.
14"""
16import platform
17import torch
18from PyQt6.QtWidgets import (
19 QVBoxLayout, QLabel, QGroupBox, QRadioButton, QButtonGroup, QSizePolicy,
20 QWidget, QHBoxLayout, QMessageBox, QApplication
21)
22from PyQt6.QtGui import QFont
23from PyQt6.QtCore import Qt, QCoreApplication
25from components.info_label import InfoLabel
26from ui.nifti_mask_selection_page import MaskNiftiSelectionPage
27from ui.dl_selection_page import DlNiftiSelectionPage
28from ui.pipeline_patient_selection_page import PipelinePatientSelectionPage
29from ui.skull_stripping_page import SkullStrippingPage
30from page import Page
33class ToolSelectionPage(Page):
34 """
35 A UI page for selecting the next processing step in the workflow.
37 This class extends `Page` and displays a set of radio buttons representing
38 different data processing operations. The user’s selection determines which
39 next page in the pipeline will be opened.
41 Attributes
42 ----------
43 context : dict
44 Shared application state, including navigation and configuration.
45 previous_page : Page
46 Reference to the previous page for backward navigation.
47 selected_option : int or None
48 The currently selected radio button ID.
49 """
51 def __init__(self, context=None, previous_page=None):
52 """Initialize the ToolSelectionPage UI."""
53 super().__init__()
55 self.context = context
56 self.previous_page = previous_page
58 # References to next pages for each option
59 self.next_skull_stripping = None
60 self.next_manual_draw = None
61 self.next_deep_learning = None
62 self.next_pipeline = None
64 # === Layout setup ===
65 self.layout = QVBoxLayout(self)
66 self.layout.setContentsMargins(40, 40, 40, 40)
67 self.layout.setSpacing(30)
68 self.setLayout(self.layout)
70 # --- Title ---
71 self.title = QLabel("Select the Next Processing Step")
72 self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
73 self.title.setFont(QFont("Arial", 18, QFont.Weight.Bold))
74 self.title.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
75 self.layout.addWidget(self.title)
77 # --- GroupBox for options ---
78 self.radio_group_box = QGroupBox("Available Processes")
79 self.radio_group_box.setStyleSheet("""
80 QGroupBox {
81 font-size: 13px;
82 border: 1px solid #bdc3c7;
83 border-radius: 10px;
84 padding: 20px;
85 background-color: #e5e5e5;
86 }
87 """)
88 self.radio_group_box.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
89 self.radio_group_box.setMaximumHeight(400)
91 radio_layout = QVBoxLayout()
92 radio_layout.setSpacing(20)
93 radio_layout.setContentsMargins(10, 10, 10, 10)
95 self.radio_group = QButtonGroup(self)
97 # --- Radio Buttons for each processing option ---
98 self.radio_skull = QRadioButton("Skull Stripping")
99 self.radio_draw = QRadioButton("Automatic Drawing")
100 self.radio_dl = QRadioButton("Deep Learning Segmentation")
101 self.radio_analysis = QRadioButton("Full Pipeline")
103 self.radio_buttons = [
104 self.radio_skull,
105 self.radio_draw,
106 self.radio_dl,
107 self.radio_analysis,
108 ]
110 # Apply consistent style to all buttons
111 for btn in self.radio_buttons:
112 btn.setFont(QFont("Arial", 14))
113 btn.setStyleSheet("""
114 QRadioButton {
115 padding: 5px;
116 border: 1px solid #bdc3c7;
117 border-radius: 8px;
118 background-color: white;
119 spacing: 10px;
120 }
121 QRadioButton::hover {
122 background-color: #dff9fb;
123 border: 1px solid #3498db;
124 }
125 QRadioButton::checked {
126 background-color: #d6eaf8;
127 border: 2px solid #2980b9;
128 }
129 """)
130 btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
132 # Add buttons to layout and group
133 for i, btn in enumerate(self.radio_buttons):
134 self.radio_group.addButton(btn, id=i)
135 if btn == self.radio_dl:
136 # Add info tooltip for Deep Learning option
137 dl_widget = QWidget()
138 dl_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
139 dl_layout = QHBoxLayout(dl_widget)
140 dl_layout.setContentsMargins(0, 0, 0, 0)
141 dl_layout.setSpacing(5)
143 self.dl_info_label = InfoLabel(
144 text="i",
145 tooltip_text=QCoreApplication.translate(
146 "ToolSelectionPage",
147 "To use this function you need Linux and a CUDA capable GPU"
148 )
149 )
150 dl_layout.addWidget(self.radio_dl)
151 dl_layout.addWidget(self.dl_info_label)
152 radio_layout.addWidget(dl_widget)
153 else:
154 radio_layout.addWidget(btn)
156 self.radio_group.buttonToggled.connect(lambda: self.on_selection())
158 self.radio_group_box.setLayout(radio_layout)
159 self.layout.addWidget(self.radio_group_box)
161 self.selected_option = None
163 # Connect language change signal if available
164 self._translate_ui()
165 if context and "language_changed" in context:
166 context["language_changed"].connect(self._translate_ui)
168 # -------------------------------------------------------------------------
169 # Navigation
170 # -------------------------------------------------------------------------
172 def on_selection(self):
173 """Triggered when a radio button is toggled."""
174 self.selected_option = self.radio_group.checkedId()
175 if self.context and "update_main_buttons" in self.context:
176 self.context["update_main_buttons"]()
178 def is_ready_to_advance(self):
179 """Return True if an option has been selected."""
180 return self.radio_group.checkedId() != -1
182 def is_ready_to_go_back(self):
183 """Always allows going back from this page."""
184 return True
186 def next(self, context):
187 """
188 Return the next page based on the selected option.
190 Handles platform and CUDA availability checks for Deep Learning mode.
191 """
192 page_classes = {
193 0: ("next_skull_stripping", SkullStrippingPage),
194 1: ("next_manual_draw", MaskNiftiSelectionPage),
195 2: ("next_deep_learning", DlNiftiSelectionPage),
196 3: ("next_pipeline", PipelinePatientSelectionPage),
197 }
199 if self.selected_option not in page_classes:
200 return None
202 # Check CUDA and OS support for Deep Learning mode
203 is_linux = platform.system() == "Linux"
204 has_gpu = torch.cuda.is_available()
206 if self.selected_option == 2 and (not has_gpu or not is_linux):
207 QMessageBox.warning(
208 self,
209 QCoreApplication.translate("ToolSelectionPage", "Not available for this platform"),
210 QCoreApplication.translate(
211 "ToolSelectionPage",
212 "The deep learning segmentation is not available for this platform: {0}"
213 ).format(platform.system()),
214 )
215 return self
217 # Create or reuse the next page instance
218 attr_name, page_class = page_classes[self.selected_option]
219 next_page = getattr(self, attr_name, None)
221 if not next_page:
222 next_page = page_class(context, self)
223 setattr(self, attr_name, next_page)
224 self.context["history"].append(next_page)
226 next_page.on_enter()
227 return next_page
229 def back(self):
230 """Navigate back to the previous page if available."""
231 if self.previous_page:
232 self.previous_page.on_enter()
233 return self.previous_page
234 return None
236 def reset_page(self):
237 """Reset selection state when the page is revisited."""
238 self.radio_group.setExclusive(False)
239 for button in self.radio_group.buttons():
240 button.setChecked(False)
241 self.radio_group.setExclusive(True)
242 self.selected_option = None
244 def _translate_ui(self):
245 """Translate all text elements (supports multi-language UI)."""
246 self.title.setText(QApplication.translate("ToolSelectionPage", "Select the Next Processing Step"))
247 self.radio_group_box.setTitle(QApplication.translate("ToolSelectionPage", "Available Processes"))
248 self.radio_skull.setText(QApplication.translate("ToolSelectionPage", "Skull Stripping"))
249 self.radio_draw.setText(QApplication.translate("ToolSelectionPage", "Automatic Drawing"))
250 self.radio_dl.setText(QApplication.translate("ToolSelectionPage", "Deep Learning Segmentation"))
251 self.radio_analysis.setText(QApplication.translate("ToolSelectionPage", "Full Pipeline"))
252 self.dl_info_label.setToolTip(QApplication.translate("ToolSelectionPage", "To use this function you need Linux and a CUDA capable GPU"))