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
« 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
5from components.file_selector_widget import FileSelectorWidget
6from page import Page
7from logger import get_logger
9log = get_logger()
12class MaskNiftiSelectionPage(Page):
13 """
14 A page that allows the user to select a NIfTI file for automatic drawing (mask creation).
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 """
20 def __init__(self, context=None, previous_page=None):
21 """
22 Initialize the NiftiMaskSelectionPage.
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
35 # Set up the main layout
36 self.layout = QVBoxLayout(self)
37 self.setLayout(self.layout)
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)
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)
55 # Push everything to the top
56 self.layout.addStretch()
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)
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)
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.
74 Args:
75 nifti_file_path (str): Full path to the NIfTI file.
76 workspace_path (str): Root workspace directory.
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)
84 subject_id = None
85 for part in path_parts:
86 if part.startswith('sub-'):
87 subject_id = part
88 break
90 # If no subject ID is found, assume no mask exists
91 if not subject_id:
92 return False
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')
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
102 # Check for existing NIfTI mask files (.nii.gz)
103 has_nii = False
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
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
117 return has_nii
119 def is_ready_to_advance(self):
120 """Return False because this page does not support advancing to the next page."""
121 return False
123 def is_ready_to_go_back(self):
124 """Always allow navigating back to the previous page."""
125 return True
127 def on_enter(self):
128 """Hook called when entering the page (currently does nothing)."""
129 pass
131 def open_nifti_viewer(self):
132 """
133 Open the selected NIfTI file in the external viewer defined in the context.
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")
144 def back(self):
145 """
146 Navigate back to the previous page in the workflow.
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
156 def reset_page(self):
157 """
158 Reset the page to its initial state.
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)
166 def _translate_ui(self):
167 """
168 Update all user-facing text for language changes.
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 ))