generated from LouisMazin/PythonApplicationTemplate
338 lines
13 KiB
Python
338 lines
13 KiB
Python
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
|
QFileDialog
|
|
)
|
|
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
|
from os import walk, path as Path
|
|
import os
|
|
from app.core.main_manager import MainManager, NotificationType
|
|
from app.utils.paths import get_current_dir
|
|
from app.ui.widgets.drag_drop_frame import DragDropFrame
|
|
from app.ui.widgets.loading_bar import LoadingBar
|
|
|
|
|
|
|
|
class ImportWorker(QThread):
|
|
progress_changed = pyqtSignal(int)
|
|
phase_changed = pyqtSignal(str)
|
|
import_finished = pyqtSignal()
|
|
error_occurred = pyqtSignal(str)
|
|
dicoms_imported = pyqtSignal(int) # Signal for number of imported DICOMs
|
|
|
|
def __init__(self, dicom_manager, dicom_paths):
|
|
super().__init__()
|
|
self.dicom_manager = dicom_manager
|
|
self.dicom_paths = dicom_paths
|
|
self.current_progress = 0
|
|
|
|
def run(self):
|
|
total_paths = len(self.dicom_paths)
|
|
|
|
# Phase 1: Path processing (0-20%)
|
|
self.phase_changed.emit("processing_paths")
|
|
for i, dicom_path in enumerate(self.dicom_paths):
|
|
self.current_progress = int((i / total_paths) * 20)
|
|
self.progress_changed.emit(self.current_progress)
|
|
|
|
# Phase 2: DICOM object creation (20-70%) - Now much faster
|
|
self.phase_changed.emit("creating_dicoms")
|
|
|
|
self.dicom_manager.error_occurred.connect(self.error_occurred.emit)
|
|
|
|
successfully_added = 0
|
|
for i, dicom_path in enumerate(self.dicom_paths):
|
|
try:
|
|
dicom = self.dicom_manager.process_dicomdir(dicom_path)
|
|
|
|
if dicom is not None and not dicom.is_empty():
|
|
self.dicom_manager.dicoms.append(dicom)
|
|
successfully_added += 1
|
|
|
|
except Exception as e:
|
|
self.error_occurred.emit("load_error")
|
|
|
|
self.current_progress = 20 + int((i / total_paths) * 50)
|
|
self.progress_changed.emit(self.current_progress)
|
|
|
|
try:
|
|
self.dicom_manager.error_occurred.disconnect()
|
|
except:
|
|
pass
|
|
|
|
self.dicoms_imported.emit(successfully_added)
|
|
|
|
# Phase 3: Background validation (70-90%)
|
|
self.phase_changed.emit("validating_files")
|
|
self._validate_files_in_background()
|
|
|
|
# Phase 4: UI preparation (90-100%)
|
|
self.phase_changed.emit("preparing_display")
|
|
for i in range(10):
|
|
self.current_progress = 90 + i + 1
|
|
self.progress_changed.emit(self.current_progress)
|
|
self.msleep(10)
|
|
|
|
self.phase_changed.emit("import_complete")
|
|
self.import_finished.emit()
|
|
|
|
def _validate_files_in_background(self):
|
|
"""Validate DICOM files in background without blocking UI"""
|
|
all_files = []
|
|
|
|
# Collect all DicomFile objects
|
|
for dicom in self.dicom_manager.dicoms:
|
|
if dicom is None:
|
|
continue
|
|
all_files.extend(dicom.get_files())
|
|
for subfolder in dicom.get_subfolders():
|
|
all_files.extend(subfolder.get_files())
|
|
|
|
total_files = len(all_files)
|
|
invalid_count = 0
|
|
|
|
for i, dicom_file in enumerate(all_files):
|
|
# Trigger lazy loading/validation
|
|
dicom_file._ensure_loaded()
|
|
if not dicom_file.is_valid:
|
|
invalid_count += 1
|
|
|
|
# Update progress
|
|
if i % 10 == 0:
|
|
progress = 70 + int((i / total_files) * 20)
|
|
self.progress_changed.emit(progress)
|
|
|
|
# Log validation results if needed
|
|
if invalid_count > 0:
|
|
print(f"Warning: {invalid_count} invalid DICOM files found during validation")
|
|
|
|
class ImportWindow(QWidget):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.main_manager = MainManager.get_instance()
|
|
self.language_manager = self.main_manager.get_language_manager()
|
|
self.dicom_manager = self.main_manager.get_dicom_manager()
|
|
self.alert_manager = self.main_manager.get_alert_manager()
|
|
self.observer_manager = self.main_manager.get_observer_manager()
|
|
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_texts)
|
|
|
|
self.dicom_paths = None
|
|
self.setup_ui()
|
|
self.update_texts()
|
|
|
|
self.import_worker = None
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
|
|
self.drag_area = DragDropFrame(self)
|
|
self.drag_area.setMinimumHeight(100)
|
|
self.drag_area.setMaximumHeight(200)
|
|
layout.addWidget(self.drag_area)
|
|
|
|
|
|
self.file_btn = QPushButton()
|
|
self.file_btn.clicked.connect(self.select_file)
|
|
self.folder_btn = QPushButton()
|
|
self.folder_btn.clicked.connect(self.select_folder)
|
|
file_layout = QHBoxLayout()
|
|
file_layout.addWidget(self.file_btn)
|
|
file_layout.addWidget(self.folder_btn)
|
|
layout.addLayout(file_layout)
|
|
|
|
self.input_entry = QLabel()
|
|
self.input_entry.setText(self.language_manager.get_text("path_placeholder"))
|
|
layout.addWidget(self.input_entry)
|
|
|
|
layout.addStretch()
|
|
|
|
self.progress_bar = LoadingBar()
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
layout.addStretch()
|
|
|
|
self.import_btn = QPushButton()
|
|
self.import_btn.clicked.connect(self.start_import)
|
|
layout.addWidget(self.import_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
def update_texts(self):
|
|
self.drag_area.setDragText(self.language_manager.get_text("drag_drop"))
|
|
if ":" not in self.input_entry.text():
|
|
self.input_entry.setText(self.language_manager.get_text("path_placeholder"))
|
|
self.file_btn.setText(self.language_manager.get_text("select_image"))
|
|
self.folder_btn.setText(self.language_manager.get_text("select_folder"))
|
|
self.import_btn.setText(self.language_manager.get_text("import"))
|
|
|
|
def start_import(self):
|
|
self.find_dicoms_from_files(self.input_entry.text())
|
|
self.import_dicoms()
|
|
|
|
def import_dicoms(self):
|
|
if not self.dicom_paths:
|
|
self.alert_manager.show_error("no_dicom_found")
|
|
return
|
|
|
|
# Build a comprehensive set of all existing file paths (normalized)
|
|
existing_file_paths = set()
|
|
existing_dicom_paths = set()
|
|
|
|
for dicom in self.dicom_manager.get_dicoms():
|
|
if dicom is None:
|
|
continue
|
|
|
|
# Add the DICOM's main path (normalized)
|
|
if dicom.get_path():
|
|
existing_dicom_paths.add(os.path.normpath(dicom.get_path()))
|
|
|
|
# Add all individual file paths within this DICOM (normalized)
|
|
files = dicom.get_files()
|
|
for file in files:
|
|
if hasattr(file, 'file_path') and file.file_path:
|
|
existing_file_paths.add(os.path.normpath(file.file_path))
|
|
|
|
# Add files from subfolders
|
|
subfolders = dicom.get_subfolders()
|
|
for subfolder in subfolders:
|
|
subfolder_files = subfolder.get_files()
|
|
for file in subfolder_files:
|
|
if hasattr(file, 'file_path') and file.file_path:
|
|
existing_file_paths.add(os.path.normpath(file.file_path))
|
|
|
|
# Filter out duplicates
|
|
dicom_paths_filtered = []
|
|
for path in self.dicom_paths:
|
|
normalized_path = os.path.normpath(path)
|
|
should_skip = False
|
|
|
|
# Check if this exact path is already imported
|
|
if normalized_path in existing_dicom_paths:
|
|
continue
|
|
|
|
# For single DCM files, check if already part of existing DICOM
|
|
if path.endswith('.dcm'):
|
|
if normalized_path in existing_file_paths:
|
|
continue
|
|
# Also check if parent directory is already imported as DICOM
|
|
parent_dir = os.path.normpath(os.path.dirname(path))
|
|
if parent_dir in existing_dicom_paths:
|
|
continue
|
|
|
|
# For directories, check more thoroughly
|
|
elif Path.isdir(path):
|
|
# Check if this directory path is already imported
|
|
if normalized_path in existing_dicom_paths:
|
|
continue
|
|
|
|
# Check if any parent directory is already imported
|
|
current_path = normalized_path
|
|
while current_path != os.path.dirname(current_path): # Until we reach root
|
|
if current_path in existing_dicom_paths:
|
|
should_skip = True
|
|
break
|
|
current_path = os.path.dirname(current_path)
|
|
|
|
if should_skip:
|
|
continue
|
|
|
|
# Check if any child directories are already imported
|
|
for existing_path in existing_dicom_paths:
|
|
if existing_path.startswith(normalized_path + os.sep):
|
|
should_skip = True
|
|
break
|
|
|
|
if should_skip:
|
|
continue
|
|
|
|
# If we get here, the path is not a duplicate
|
|
dicom_paths_filtered.append(path)
|
|
|
|
if not dicom_paths_filtered:
|
|
self.alert_manager.show_info("Tous les DICOMs sont déjà importés")
|
|
return
|
|
|
|
# Show loading bar and start import
|
|
self.progress_bar.set_label(self.language_manager.get_text("importing"))
|
|
self.progress_bar.set_progress(0)
|
|
|
|
# Disable buttons during import
|
|
self.file_btn.setEnabled(False)
|
|
self.folder_btn.setEnabled(False)
|
|
self.import_btn.setEnabled(False)
|
|
|
|
# Start import worker
|
|
self.import_worker = ImportWorker(self.dicom_manager, dicom_paths_filtered)
|
|
self.import_worker.progress_changed.connect(self.progress_bar.set_progress)
|
|
self.import_worker.phase_changed.connect(self.update_phase_label)
|
|
self.import_worker.error_occurred.connect(self.handle_import_error)
|
|
self.import_worker.import_finished.connect(self.on_import_finished)
|
|
self.import_worker.start()
|
|
|
|
def handle_import_error(self, error_key):
|
|
"""Handle errors during import"""
|
|
self.alert_manager.show_error(error_key)
|
|
|
|
def update_phase_label(self, phase):
|
|
"""Update the loading bar label based on current phase"""
|
|
self.progress_bar.set_label(self.language_manager.get_text(phase))
|
|
|
|
def on_import_finished(self):
|
|
|
|
# Re-enable buttons
|
|
self.import_btn.setEnabled(True)
|
|
self.file_btn.setEnabled(True)
|
|
self.folder_btn.setEnabled(True)
|
|
|
|
# Reset paths
|
|
self.dicom_paths = None
|
|
self.input_entry.setText(self.language_manager.get_text("path_placeholder"))
|
|
|
|
# Clean up worker
|
|
if self.import_worker:
|
|
self.import_worker.deleteLater()
|
|
self.import_worker = None
|
|
|
|
# Notify observers
|
|
self.observer_manager.notify(NotificationType.DICOM)
|
|
|
|
def select_file(self):
|
|
path = QFileDialog.getOpenFileName(self, self.language_manager.get_text("select_image"), get_current_dir(), "DICOMDIR Files (DICOMDIR) ;; DICOM Files (*.dcm)")[0]
|
|
if path:
|
|
self.input_entry.setText(path)
|
|
else:
|
|
self.input_entry.setText(self.language_manager.get_text("path_placeholder"))
|
|
|
|
def select_folder(self):
|
|
path = QFileDialog.getExistingDirectory(self, self.language_manager.get_text("select_folder"), get_current_dir())
|
|
if path:
|
|
self.input_entry.setText(path)
|
|
else:
|
|
self.input_entry.setText(self.language_manager.get_text("path_placeholder"))
|
|
|
|
def find_dicoms_from_files(self, path):
|
|
if isinstance(path, tuple):
|
|
path = path[0] # Extract the file path from the tuple if needed
|
|
if not path:
|
|
return
|
|
if Path.isdir(path):
|
|
if path:
|
|
fichiers_dicomdir = []
|
|
for racine, _, fichiers in walk(path):
|
|
for fichier in fichiers:
|
|
if fichier == 'DICOMDIR':
|
|
chemin_complet = Path.join(racine, fichier)
|
|
fichiers_dicomdir.append(chemin_complet)
|
|
if fichier.endswith('.dcm'):
|
|
chemin_complet = Path.join(racine, fichier)
|
|
parent_folder = Path.dirname(chemin_complet)
|
|
if parent_folder not in fichiers_dicomdir:
|
|
fichiers_dicomdir.append(parent_folder)
|
|
if fichiers_dicomdir:
|
|
self.dicom_paths = fichiers_dicomdir
|
|
self.input_entry.setText(path)
|
|
else:
|
|
if path.endswith('DICOMDIR'):
|
|
self.dicom_paths = [path]
|
|
self.input_entry.setText(path)
|
|
elif path.endswith('.dcm'):
|
|
# For single DCM file, use the file path directly, not the parent folder
|
|
self.dicom_paths = [path]
|
|
self.input_entry.setText(path) |