Coverage for main/ui/nifti_mask_selection_page.py: 95%

80 statements  

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

1from PyQt6.QtWidgets import (QVBoxLayout, QLabel, QPushButton) 

2from PyQt6.QtCore import Qt, QCoreApplication 

3import os 

4 

5from components.file_selector_widget import FileSelectorWidget 

6from page import Page 

7from logger import get_logger 

8 

9log = get_logger() 

10 

11 

12class MaskNiftiSelectionPage(Page): 

13 """ 

14 A page that allows the user to select a NIfTI file for automatic drawing (mask creation). 

15 

16 This page provides an interface for selecting a NIfTI file and checking 

17 if a corresponding mask already exists in the workspace directory. 

18 """ 

19 

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

21 """ 

22 Initialize the NiftiMaskSelectionPage. 

23 

24 Args: 

25 context (dict, optional): Shared context object used for communication 

26 between pages and the main controller. 

27 previous_page (Page, optional): The previous page in the navigation flow. 

28 """ 

29 super().__init__() 

30 self.context = context 

31 self.previous_page = previous_page 

32 self.next_page = None 

33 self.selected_file = None 

34 

35 # Set up the main layout 

36 self.layout = QVBoxLayout(self) 

37 self.setLayout(self.layout) 

38 

39 # Title label 

40 self.title = QLabel("Select a NIfTI file for Automatic Drawing") 

41 self.title.setStyleSheet("font-size: 18px; font-weight: bold;") 

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

43 self.layout.addWidget(self.title) 

44 

45 # File selection widget 

46 self.file_selector_widget = FileSelectorWidget( 

47 parent=self, 

48 context=self.context, 

49 has_existing_function=self.has_existing_mask, 

50 label="mask", 

51 allow_multiple=False 

52 ) 

53 self.layout.addWidget(self.file_selector_widget) 

54 

55 # Push everything to the top 

56 self.layout.addStretch() 

57 

58 # Button to open the NIfTI viewer 

59 self.viewer_button = QPushButton("Open NIfTI file") 

60 self.file_selector_widget.has_file.connect(self.viewer_button.setEnabled) 

61 self.viewer_button.setEnabled(False) 

62 self.viewer_button.clicked.connect(self.open_nifti_viewer) 

63 self.layout.addWidget(self.viewer_button) 

64 

65 # Translation setup for UI text 

66 self._translate_ui() 

67 if context and "language_changed" in context: 

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

69 

70 def has_existing_mask(self, nifti_file_path, workspace_path): 

71 """ 

72 Check if a manual or deep learning mask already exists for the selected NIfTI file. 

73 

74 Args: 

75 nifti_file_path (str): Full path to the NIfTI file. 

76 workspace_path (str): Root workspace directory. 

77 

78 Returns: 

79 bool: True if a mask file exists, False otherwise. 

80 """ 

81 # Extract the subject ID (assuming a path structure like .../sub-XX/anat/sub-XX_modality.nii.gz) 

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

83 

84 subject_id = None 

85 for part in path_parts: 

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

87 subject_id = part 

88 break 

89 

90 # If no subject ID is found, assume no mask exists 

91 if not subject_id: 

92 return False 

93 

94 # Build expected paths for manual and deep learning masks 

95 manual_mask_dir = os.path.join(workspace_path, 'derivatives', 'manual_masks', subject_id, 'anat') 

96 deep_learning_mask_dir = os.path.join(workspace_path, 'derivatives', 'deep_learning_masks', subject_id, 'anat') 

97 

98 # If neither directory exists, no mask can exist 

99 if not os.path.exists(manual_mask_dir) and not os.path.exists(deep_learning_mask_dir): 

100 return False 

101 

102 # Check for existing NIfTI mask files (.nii.gz) 

103 has_nii = False 

104 

105 if os.path.exists(manual_mask_dir): 

106 for file in os.listdir(manual_mask_dir): 

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

108 has_nii = True 

109 break 

110 

111 if os.path.exists(deep_learning_mask_dir): 

112 for file in os.listdir(deep_learning_mask_dir): 

113 if file.endswith('.nii.gz'): 

114 has_nii = True 

115 break 

116 

117 return has_nii 

118 

119 def is_ready_to_advance(self): 

120 """Return False because this page does not support advancing to the next page.""" 

121 return False 

122 

123 def is_ready_to_go_back(self): 

124 """Always allow navigating back to the previous page.""" 

125 return True 

126 

127 def on_enter(self): 

128 """Hook called when entering the page (currently does nothing).""" 

129 pass 

130 

131 def open_nifti_viewer(self): 

132 """ 

133 Open the selected NIfTI file in the external viewer defined in the context. 

134 

135 This method retrieves the selected file and invokes the 

136 'open_nifti_viewer' function stored in the shared context. 

137 """ 

138 try: 

139 file = self.file_selector_widget.get_selected_files()[-1] 

140 self.context["open_nifti_viewer"](file) 

141 except Exception: 

142 log.exception("Error while opening NIfTI viewer") 

143 

144 def back(self): 

145 """ 

146 Navigate back to the previous page in the workflow. 

147 

148 Returns: 

149 Page | None: The previous page if available, otherwise None. 

150 """ 

151 if self.previous_page: 

152 self.previous_page.on_enter() 

153 return self.previous_page 

154 return None 

155 

156 def reset_page(self): 

157 """ 

158 Reset the page to its initial state. 

159 

160 Clears all file selections and disables the viewer button. 

161 """ 

162 self.selected_file = None 

163 self.file_selector_widget.clear_selected_files() 

164 self.viewer_button.setEnabled(False) 

165 

166 def _translate_ui(self): 

167 """ 

168 Update all user-facing text for language changes. 

169 

170 This method uses Qt’s translation system to localize labels and buttons. 

171 """ 

172 self.title.setText(QCoreApplication.translate( 

173 "NiftiMaskSelectionPage", "Select a NIfTI file for Automatic Drawing" 

174 )) 

175 self.viewer_button.setText(QCoreApplication.translate( 

176 "NiftiMaskSelectionPage", "Open NIfTI file" 

177 ))