ui.nifti_mask_selection_page
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 ))
13class MaskNiftiSelectionPage(Page): 14 """ 15 A page that allows the user to select a NIfTI file for automatic drawing (mask creation). 16 17 This page provides an interface for selecting a NIfTI file and checking 18 if a corresponding mask already exists in the workspace directory. 19 """ 20 21 def __init__(self, context=None, previous_page=None): 22 """ 23 Initialize the NiftiMaskSelectionPage. 24 25 Args: 26 context (dict, optional): Shared context object used for communication 27 between pages and the main controller. 28 previous_page (Page, optional): The previous page in the navigation flow. 29 """ 30 super().__init__() 31 self.context = context 32 self.previous_page = previous_page 33 self.next_page = None 34 self.selected_file = None 35 36 # Set up the main layout 37 self.layout = QVBoxLayout(self) 38 self.setLayout(self.layout) 39 40 # Title label 41 self.title = QLabel("Select a NIfTI file for Automatic Drawing") 42 self.title.setStyleSheet("font-size: 18px; font-weight: bold;") 43 self.title.setAlignment(Qt.AlignmentFlag.AlignCenter) 44 self.layout.addWidget(self.title) 45 46 # File selection widget 47 self.file_selector_widget = FileSelectorWidget( 48 parent=self, 49 context=self.context, 50 has_existing_function=self.has_existing_mask, 51 label="mask", 52 allow_multiple=False 53 ) 54 self.layout.addWidget(self.file_selector_widget) 55 56 # Push everything to the top 57 self.layout.addStretch() 58 59 # Button to open the NIfTI viewer 60 self.viewer_button = QPushButton("Open NIfTI file") 61 self.file_selector_widget.has_file.connect(self.viewer_button.setEnabled) 62 self.viewer_button.setEnabled(False) 63 self.viewer_button.clicked.connect(self.open_nifti_viewer) 64 self.layout.addWidget(self.viewer_button) 65 66 # Translation setup for UI text 67 self._translate_ui() 68 if context and "language_changed" in context: 69 context["language_changed"].connect(self._translate_ui) 70 71 def has_existing_mask(self, nifti_file_path, workspace_path): 72 """ 73 Check if a manual or deep learning mask already exists for the selected NIfTI file. 74 75 Args: 76 nifti_file_path (str): Full path to the NIfTI file. 77 workspace_path (str): Root workspace directory. 78 79 Returns: 80 bool: True if a mask file exists, False otherwise. 81 """ 82 # Extract the subject ID (assuming a path structure like .../sub-XX/anat/sub-XX_modality.nii.gz) 83 path_parts = nifti_file_path.replace(workspace_path, '').strip(os.sep).split(os.sep) 84 85 subject_id = None 86 for part in path_parts: 87 if part.startswith('sub-'): 88 subject_id = part 89 break 90 91 # If no subject ID is found, assume no mask exists 92 if not subject_id: 93 return False 94 95 # Build expected paths for manual and deep learning masks 96 manual_mask_dir = os.path.join(workspace_path, 'derivatives', 'manual_masks', subject_id, 'anat') 97 deep_learning_mask_dir = os.path.join(workspace_path, 'derivatives', 'deep_learning_masks', subject_id, 'anat') 98 99 # If neither directory exists, no mask can exist 100 if not os.path.exists(manual_mask_dir) and not os.path.exists(deep_learning_mask_dir): 101 return False 102 103 # Check for existing NIfTI mask files (.nii.gz) 104 has_nii = False 105 106 if os.path.exists(manual_mask_dir): 107 for file in os.listdir(manual_mask_dir): 108 if file.endswith('.nii.gz'): 109 has_nii = True 110 break 111 112 if os.path.exists(deep_learning_mask_dir): 113 for file in os.listdir(deep_learning_mask_dir): 114 if file.endswith('.nii.gz'): 115 has_nii = True 116 break 117 118 return has_nii 119 120 def is_ready_to_advance(self): 121 """Return False because this page does not support advancing to the next page.""" 122 return False 123 124 def is_ready_to_go_back(self): 125 """Always allow navigating back to the previous page.""" 126 return True 127 128 def on_enter(self): 129 """Hook called when entering the page (currently does nothing).""" 130 pass 131 132 def open_nifti_viewer(self): 133 """ 134 Open the selected NIfTI file in the external viewer defined in the context. 135 136 This method retrieves the selected file and invokes the 137 'open_nifti_viewer' function stored in the shared context. 138 """ 139 try: 140 file = self.file_selector_widget.get_selected_files()[-1] 141 self.context["open_nifti_viewer"](file) 142 except Exception: 143 log.exception("Error while opening NIfTI viewer") 144 145 def back(self): 146 """ 147 Navigate back to the previous page in the workflow. 148 149 Returns: 150 Page | None: The previous page if available, otherwise None. 151 """ 152 if self.previous_page: 153 self.previous_page.on_enter() 154 return self.previous_page 155 return None 156 157 def reset_page(self): 158 """ 159 Reset the page to its initial state. 160 161 Clears all file selections and disables the viewer button. 162 """ 163 self.selected_file = None 164 self.file_selector_widget.clear_selected_files() 165 self.viewer_button.setEnabled(False) 166 167 def _translate_ui(self): 168 """ 169 Update all user-facing text for language changes. 170 171 This method uses Qt’s translation system to localize labels and buttons. 172 """ 173 self.title.setText(QCoreApplication.translate( 174 "NiftiMaskSelectionPage", "Select a NIfTI file for Automatic Drawing" 175 )) 176 self.viewer_button.setText(QCoreApplication.translate( 177 "NiftiMaskSelectionPage", "Open NIfTI file" 178 ))
A page that allows the user to select a NIfTI file for automatic drawing (mask creation).
This page provides an interface for selecting a NIfTI file and checking if a corresponding mask already exists in the workspace directory.
21 def __init__(self, context=None, previous_page=None): 22 """ 23 Initialize the NiftiMaskSelectionPage. 24 25 Args: 26 context (dict, optional): Shared context object used for communication 27 between pages and the main controller. 28 previous_page (Page, optional): The previous page in the navigation flow. 29 """ 30 super().__init__() 31 self.context = context 32 self.previous_page = previous_page 33 self.next_page = None 34 self.selected_file = None 35 36 # Set up the main layout 37 self.layout = QVBoxLayout(self) 38 self.setLayout(self.layout) 39 40 # Title label 41 self.title = QLabel("Select a NIfTI file for Automatic Drawing") 42 self.title.setStyleSheet("font-size: 18px; font-weight: bold;") 43 self.title.setAlignment(Qt.AlignmentFlag.AlignCenter) 44 self.layout.addWidget(self.title) 45 46 # File selection widget 47 self.file_selector_widget = FileSelectorWidget( 48 parent=self, 49 context=self.context, 50 has_existing_function=self.has_existing_mask, 51 label="mask", 52 allow_multiple=False 53 ) 54 self.layout.addWidget(self.file_selector_widget) 55 56 # Push everything to the top 57 self.layout.addStretch() 58 59 # Button to open the NIfTI viewer 60 self.viewer_button = QPushButton("Open NIfTI file") 61 self.file_selector_widget.has_file.connect(self.viewer_button.setEnabled) 62 self.viewer_button.setEnabled(False) 63 self.viewer_button.clicked.connect(self.open_nifti_viewer) 64 self.layout.addWidget(self.viewer_button) 65 66 # Translation setup for UI text 67 self._translate_ui() 68 if context and "language_changed" in context: 69 context["language_changed"].connect(self._translate_ui)
Initialize the NiftiMaskSelectionPage.
Args: context (dict, optional): Shared context object used for communication between pages and the main controller. previous_page (Page, optional): The previous page in the navigation flow.
71 def has_existing_mask(self, nifti_file_path, workspace_path): 72 """ 73 Check if a manual or deep learning mask already exists for the selected NIfTI file. 74 75 Args: 76 nifti_file_path (str): Full path to the NIfTI file. 77 workspace_path (str): Root workspace directory. 78 79 Returns: 80 bool: True if a mask file exists, False otherwise. 81 """ 82 # Extract the subject ID (assuming a path structure like .../sub-XX/anat/sub-XX_modality.nii.gz) 83 path_parts = nifti_file_path.replace(workspace_path, '').strip(os.sep).split(os.sep) 84 85 subject_id = None 86 for part in path_parts: 87 if part.startswith('sub-'): 88 subject_id = part 89 break 90 91 # If no subject ID is found, assume no mask exists 92 if not subject_id: 93 return False 94 95 # Build expected paths for manual and deep learning masks 96 manual_mask_dir = os.path.join(workspace_path, 'derivatives', 'manual_masks', subject_id, 'anat') 97 deep_learning_mask_dir = os.path.join(workspace_path, 'derivatives', 'deep_learning_masks', subject_id, 'anat') 98 99 # If neither directory exists, no mask can exist 100 if not os.path.exists(manual_mask_dir) and not os.path.exists(deep_learning_mask_dir): 101 return False 102 103 # Check for existing NIfTI mask files (.nii.gz) 104 has_nii = False 105 106 if os.path.exists(manual_mask_dir): 107 for file in os.listdir(manual_mask_dir): 108 if file.endswith('.nii.gz'): 109 has_nii = True 110 break 111 112 if os.path.exists(deep_learning_mask_dir): 113 for file in os.listdir(deep_learning_mask_dir): 114 if file.endswith('.nii.gz'): 115 has_nii = True 116 break 117 118 return has_nii
Check if a manual or deep learning mask already exists for the selected NIfTI file.
Args: nifti_file_path (str): Full path to the NIfTI file. workspace_path (str): Root workspace directory.
Returns: bool: True if a mask file exists, False otherwise.
120 def is_ready_to_advance(self): 121 """Return False because this page does not support advancing to the next page.""" 122 return False
Return False because this page does not support advancing to the next page.
124 def is_ready_to_go_back(self): 125 """Always allow navigating back to the previous page.""" 126 return True
Always allow navigating back to the previous page.
128 def on_enter(self): 129 """Hook called when entering the page (currently does nothing).""" 130 pass
Hook called when entering the page (currently does nothing).
132 def open_nifti_viewer(self): 133 """ 134 Open the selected NIfTI file in the external viewer defined in the context. 135 136 This method retrieves the selected file and invokes the 137 'open_nifti_viewer' function stored in the shared context. 138 """ 139 try: 140 file = self.file_selector_widget.get_selected_files()[-1] 141 self.context["open_nifti_viewer"](file) 142 except Exception: 143 log.exception("Error while opening NIfTI viewer")
Open the selected NIfTI file in the external viewer defined in the context.
This method retrieves the selected file and invokes the 'open_nifti_viewer' function stored in the shared context.
145 def back(self): 146 """ 147 Navigate back to the previous page in the workflow. 148 149 Returns: 150 Page | None: The previous page if available, otherwise None. 151 """ 152 if self.previous_page: 153 self.previous_page.on_enter() 154 return self.previous_page 155 return None
Navigate back to the previous page in the workflow.
Returns: Page | None: The previous page if available, otherwise None.
157 def reset_page(self): 158 """ 159 Reset the page to its initial state. 160 161 Clears all file selections and disables the viewer button. 162 """ 163 self.selected_file = None 164 self.file_selector_widget.clear_selected_files() 165 self.viewer_button.setEnabled(False)
Reset the page to its initial state.
Clears all file selections and disables the viewer button.