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

1""" 

2tool_selection_page.py 

3 

4This module defines the `ToolChoicePage` class — a PyQt6 page that allows the user 

5to select the next processing step in the GliAAns pipeline. 

6 

7The available options include: 

8- Skull Stripping 

9- Automatic Drawing 

10- Deep Learning Segmentation (Linux + CUDA only) 

11- Full Pipeline Execution 

12 

13Depending on the selected option, the next page is dynamically loaded. 

14""" 

15 

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 

24 

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 

31 

32 

33class ToolSelectionPage(Page): 

34 """ 

35 A UI page for selecting the next processing step in the workflow. 

36 

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. 

40 

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 """ 

50 

51 def __init__(self, context=None, previous_page=None): 

52 """Initialize the ToolSelectionPage UI.""" 

53 super().__init__() 

54 

55 self.context = context 

56 self.previous_page = previous_page 

57 

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 

63 

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) 

69 

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) 

76 

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) 

90 

91 radio_layout = QVBoxLayout() 

92 radio_layout.setSpacing(20) 

93 radio_layout.setContentsMargins(10, 10, 10, 10) 

94 

95 self.radio_group = QButtonGroup(self) 

96 

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") 

102 

103 self.radio_buttons = [ 

104 self.radio_skull, 

105 self.radio_draw, 

106 self.radio_dl, 

107 self.radio_analysis, 

108 ] 

109 

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) 

131 

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) 

142 

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) 

155 

156 self.radio_group.buttonToggled.connect(lambda: self.on_selection()) 

157 

158 self.radio_group_box.setLayout(radio_layout) 

159 self.layout.addWidget(self.radio_group_box) 

160 

161 self.selected_option = None 

162 

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) 

167 

168 # ------------------------------------------------------------------------- 

169 # Navigation 

170 # ------------------------------------------------------------------------- 

171 

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"]() 

177 

178 def is_ready_to_advance(self): 

179 """Return True if an option has been selected.""" 

180 return self.radio_group.checkedId() != -1 

181 

182 def is_ready_to_go_back(self): 

183 """Always allows going back from this page.""" 

184 return True 

185 

186 def next(self, context): 

187 """ 

188 Return the next page based on the selected option. 

189 

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 } 

198 

199 if self.selected_option not in page_classes: 

200 return None 

201 

202 # Check CUDA and OS support for Deep Learning mode 

203 is_linux = platform.system() == "Linux" 

204 has_gpu = torch.cuda.is_available() 

205 

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 

216 

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) 

220 

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) 

225 

226 next_page.on_enter() 

227 return next_page 

228 

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 

235 

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 

243 

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"))