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
« 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
9from components.file_selector_widget import FileSelectorWidget
10from ui.dl_execution_page import DlExecutionPage
11from page import Page
12from logger import get_logger
14log = get_logger()
17class DlNiftiSelectionPage(Page):
18 """
19 GUI page that allows the user to select NIfTI files for Deep Learning-based segmentation.
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 """
27 def __init__(self, context=None, previous_page=None):
28 """
29 Initialize the patient selection page.
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
39 self._setup_ui()
40 self._translate_ui()
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)
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)
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)
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)
68 # Pushes everything upward visually
69 self.layout.addStretch()
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)
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.
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)
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
94 # If no subject identifier found, no segmentation can be associated
95 if not subject_id:
96 return False
98 # Expected directory where segmentations are stored
99 seg_dir = os.path.join(workspace_path, 'derivatives', 'deep_learning_seg', subject_id, 'anat')
101 # If the segmentation directory does not exist, return False
102 if not os.path.exists(seg_dir):
103 return False
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
110 return False
112 def back(self):
113 """
114 Navigate back to the previous page in the workflow.
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
123 def next(self, context):
124 """
125 Save the selected files in the context and move to the next page (DlExecutionPage).
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()
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)
140 # Prepare the next page before displaying
141 self.next_page.on_enter()
142 return self.next_page
144 def on_enter(self):
145 """Called when the page is shown — resets transient UI elements."""
146 self.status_label.setText("")
148 def is_ready_to_advance(self):
149 """
150 Check if the user can proceed to the next page.
152 :return: True if one or more files are selected.
153 """
154 return self.file_selector_widget.get_selected_files()
156 def is_ready_to_go_back(self):
157 """
158 Check if the user can navigate backward.
160 :return: Always True for this page.
161 """
162 return True
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 = []
170 self.status_label.setText("")
172 # Clear any saved data in the context
173 if self.context:
174 self.context["selected_segmentation_files"] = []
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 ))