""" Annotation tab for the microscopy object detection application. Future feature for manual annotation. """ from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QGroupBox, QPushButton, QFileDialog, QMessageBox, ) from PySide6.QtCore import Qt, QSettings from pathlib import Path from src.database.db_manager import DatabaseManager from src.utils.config_manager import ConfigManager from src.utils.image import Image, ImageLoadError from src.utils.logger import get_logger from src.gui.widgets import ImageDisplayWidget logger = get_logger(__name__) class AnnotationTab(QWidget): """Annotation tab placeholder (future feature).""" def __init__( self, db_manager: DatabaseManager, config_manager: ConfigManager, parent=None ): super().__init__(parent) self.db_manager = db_manager self.config_manager = config_manager self.current_image = None self.current_image_path = None self._setup_ui() def _setup_ui(self): """Setup user interface.""" layout = QVBoxLayout() # Image loading section load_group = QGroupBox("Image Loading") load_layout = QVBoxLayout() # Load image button button_layout = QHBoxLayout() self.load_image_btn = QPushButton("Load Image") self.load_image_btn.clicked.connect(self._load_image) button_layout.addWidget(self.load_image_btn) button_layout.addStretch() load_layout.addLayout(button_layout) # Image info label self.image_info_label = QLabel("No image loaded") load_layout.addWidget(self.image_info_label) load_group.setLayout(load_layout) layout.addWidget(load_group) # Image display section display_group = QGroupBox("Image Display") display_layout = QVBoxLayout() # Use the reusable ImageDisplayWidget self.image_display_widget = ImageDisplayWidget() self.image_display_widget.zoom_changed.connect(self._on_zoom_changed) display_layout.addWidget(self.image_display_widget) display_group.setLayout(display_layout) layout.addWidget(display_group) # Future features info info_group = QGroupBox("Annotation Tool (Future Feature)") info_layout = QVBoxLayout() info_label = QLabel( "Full annotation functionality will be implemented in future version.\n\n" "Planned Features:\n" "- Drawing tools for bounding boxes\n" "- Class label assignment\n" "- Export annotations to YOLO format\n" "- Annotation verification" ) info_layout.addWidget(info_label) info_group.setLayout(info_layout) layout.addWidget(info_group) # Zoom controls info zoom_info = QLabel("Zoom: Mouse wheel or +/- keys to zoom in/out") zoom_info.setStyleSheet("QLabel { color: #888; font-style: italic; }") layout.addWidget(zoom_info) self.setLayout(layout) def _load_image(self): """Load and display an image file.""" # Get last opened directory from QSettings settings = QSettings("microscopy_app", "object_detection") last_dir = settings.value("annotation_tab/last_directory", None) # Fallback to image repository path or home directory if last_dir and Path(last_dir).exists(): start_dir = last_dir else: repo_path = self.config_manager.get_image_repository_path() start_dir = repo_path if repo_path else str(Path.home()) # Open file dialog file_path, _ = QFileDialog.getOpenFileName( self, "Select Image", start_dir, "Images (*.jpg *.jpeg *.png *.tif *.tiff *.bmp)", ) if not file_path: return try: # Load image using Image class self.current_image = Image(file_path) self.current_image_path = file_path # Store the directory for next time settings.setValue( "annotation_tab/last_directory", str(Path(file_path).parent) ) # Display image using the ImageDisplayWidget self.image_display_widget.load_image(self.current_image) # Update info label self._update_image_info() logger.info(f"Loaded image: {file_path}") except ImageLoadError as e: logger.error(f"Failed to load image: {e}") QMessageBox.critical( self, "Error Loading Image", f"Failed to load image:\n{str(e)}" ) except Exception as e: logger.error(f"Unexpected error loading image: {e}") QMessageBox.critical(self, "Error", f"Unexpected error:\n{str(e)}") def _update_image_info(self): """Update the image info label with current image details.""" if self.current_image is None: self.image_info_label.setText("No image loaded") return zoom_percentage = self.image_display_widget.get_zoom_percentage() info_text = ( f"File: {Path(self.current_image_path).name}\n" f"Size: {self.current_image.width}x{self.current_image.height} pixels\n" f"Channels: {self.current_image.channels}\n" f"Data type: {self.current_image.dtype}\n" f"Format: {self.current_image.format.upper()}\n" f"File size: {self.current_image.size_mb:.2f} MB\n" f"Zoom: {zoom_percentage}%" ) self.image_info_label.setText(info_text) def _on_zoom_changed(self, zoom_scale: float): """Handle zoom level changes from the image display widget.""" self._update_image_info() def refresh(self): """Refresh the tab.""" pass