Coverage for main/ui/dl_selection_page.py: 100%

75 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-03 15:51 +0000

1from PyQt6.QtGui import QIcon, QFont 

2from PyQt6.QtWidgets import ( 

3 QWidget, QVBoxLayout, QLabel, QPushButton, QGridLayout, QHBoxLayout, 

4 QMessageBox, QGroupBox, QListWidget, QDialog, QLineEdit, QDialogButtonBox, QListWidgetItem 

5) 

6from PyQt6.QtCore import Qt, QCoreApplication 

7import os 

8 

9from components.file_selector_widget import FileSelectorWidget 

10from ui.dl_execution_page import DlExecutionPage 

11from page import Page 

12from logger import get_logger 

13 

14log = get_logger() 

15 

16 

17class DlNiftiSelectionPage(Page): 

18 """ 

19 GUI page that allows the user to select NIfTI files for Deep Learning-based segmentation. 

20 

21 This page is part of a multi-step workflow: 

22 - Displays a title and a FileSelectorWidget to pick one or more NIfTI files. 

23 - Checks if a segmentation already exists for a given subject. 

24 - Saves selections in a shared context and navigates to the next page (DlExecutionPage). 

25 """ 

26 

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

28 """ 

29 Initialize the patient selection page. 

30 

31 :param context: Shared application context dictionary (used for data persistence between pages) 

32 :param previous_page: Reference to the previous Page object (for navigation) 

33 """ 

34 super().__init__() 

35 self.context = context 

36 self.previous_page = previous_page 

37 self.next_page = None # Will be created when advancing 

38 

39 self._setup_ui() 

40 self._translate_ui() 

41 

42 # Re-translate the UI dynamically if the language changes in the context 

43 if context and "language_changed" in context: 

44 context["language_changed"].connect(self._translate_ui) 

45 

46 def _setup_ui(self): 

47 """Create and organize all UI elements for this page.""" 

48 self.layout = QVBoxLayout(self) 

49 self.setLayout(self.layout) 

50 

51 # Title label 

52 self.title = QLabel("Select NIfTI files for Deep Learning Segmentation") 

53 self.title.setFont(QFont("Arial", 18, QFont.Weight.Bold)) 

54 self.title.setAlignment(Qt.AlignmentFlag.AlignCenter) 

55 self.layout.addWidget(self.title) 

56 

57 # Widget for selecting NIfTI files 

58 # - `has_existing_function` allows pre-checking if a segmentation already exists 

59 self.file_selector_widget = FileSelectorWidget( 

60 parent=self, 

61 context=self.context, 

62 has_existing_function=self.has_existing_segmentation, 

63 label="seg", 

64 allow_multiple=True 

65 ) 

66 self.layout.addWidget(self.file_selector_widget) 

67 

68 # Pushes everything upward visually 

69 self.layout.addStretch() 

70 

71 # Label for displaying temporary status or messages 

72 self.status_label = QLabel("") 

73 self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) 

74 self.layout.addWidget(self.status_label) 

75 

76 def has_existing_segmentation(self, nifti_file_path, workspace_path): 

77 """ 

78 Check if a deep learning segmentation already exists for the patient corresponding to this NIfTI file. 

79 

80 :param nifti_file_path: Full path to the selected NIfTI file. 

81 :param workspace_path: Root path of the working directory. 

82 :return: True if a segmentation file is already present, otherwise False. 

83 """ 

84 # Extract patient ID (subject ID) from the NIfTI file path 

85 path_parts = nifti_file_path.replace(workspace_path, '').strip(os.sep).split(os.sep) 

86 

87 # Look for a directory segment starting with 'sub-' (BIDS convention) 

88 subject_id = None 

89 for part in path_parts: 

90 if part.startswith('sub-'): 

91 subject_id = part 

92 break 

93 

94 # If no subject identifier found, no segmentation can be associated 

95 if not subject_id: 

96 return False 

97 

98 # Expected directory where segmentations are stored 

99 seg_dir = os.path.join(workspace_path, 'derivatives', 'deep_learning_seg', subject_id, 'anat') 

100 

101 # If the segmentation directory does not exist, return False 

102 if not os.path.exists(seg_dir): 

103 return False 

104 

105 # Check for any segmentation file in the directory 

106 for file in os.listdir(seg_dir): 

107 if file.endswith('_seg.nii.gz') or file.endswith('_seg.nii'): 

108 return True 

109 

110 return False 

111 

112 def back(self): 

113 """ 

114 Navigate back to the previous page in the workflow. 

115 

116 :return: The previous Page instance, or None if there is none. 

117 """ 

118 if self.previous_page: 

119 self.previous_page.on_enter() 

120 return self.previous_page 

121 return None 

122 

123 def next(self, context): 

124 """ 

125 Save the selected files in the context and move to the next page (DlExecutionPage). 

126 

127 :param context: The shared application context dictionary. 

128 :return: The next Page instance. 

129 """ 

130 # Store selected files in the context 

131 if self.context is not None: 

132 self.context["selected_segmentation_files"] = self.file_selector_widget.get_selected_files() 

133 

134 # Instantiate the next page only once 

135 if not self.next_page: 

136 self.next_page = DlExecutionPage(self.context, self) 

137 if "history" in self.context: 

138 self.context["history"].append(self.next_page) 

139 

140 # Prepare the next page before displaying 

141 self.next_page.on_enter() 

142 return self.next_page 

143 

144 def on_enter(self): 

145 """Called when the page is shown — resets transient UI elements.""" 

146 self.status_label.setText("") 

147 

148 def is_ready_to_advance(self): 

149 """ 

150 Check if the user can proceed to the next page. 

151 

152 :return: True if one or more files are selected. 

153 """ 

154 return self.file_selector_widget.get_selected_files() 

155 

156 def is_ready_to_go_back(self): 

157 """ 

158 Check if the user can navigate backward. 

159 

160 :return: Always True for this page. 

161 """ 

162 return True 

163 

164 def reset_page(self): 

165 """ 

166 Reset the page to its initial state, clearing all selections and messages. 

167 """ 

168 self.file_selector_widget._selected_files = [] 

169 

170 self.status_label.setText("") 

171 

172 # Clear any saved data in the context 

173 if self.context: 

174 self.context["selected_segmentation_files"] = [] 

175 

176 def _translate_ui(self): 

177 """Apply translations to all text elements in the UI.""" 

178 self.title.setText(QCoreApplication.translate( 

179 "DlSelectionPage", 

180 "Select NIfTI files for Deep Learning Segmentation" 

181 ))