Files
object-segmentation/src/gui/tabs/annotation_tab.py

177 lines
5.8 KiB
Python
Raw Normal View History

2025-12-05 09:50:50 +02:00
"""
Annotation tab for the microscopy object detection application.
Future feature for manual annotation.
"""
2025-12-08 16:28:58 +02:00
from PySide6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QLabel,
QGroupBox,
QPushButton,
QFileDialog,
QMessageBox,
)
2025-12-08 17:33:32 +02:00
from PySide6.QtCore import Qt, QSettings
2025-12-08 16:28:58 +02:00
from pathlib import Path
2025-12-05 09:50:50 +02:00
from src.database.db_manager import DatabaseManager
from src.utils.config_manager import ConfigManager
2025-12-08 16:28:58 +02:00
from src.utils.image import Image, ImageLoadError
from src.utils.logger import get_logger
2025-12-08 17:33:32 +02:00
from src.gui.widgets import ImageDisplayWidget
2025-12-08 16:28:58 +02:00
logger = get_logger(__name__)
2025-12-05 09:50:50 +02:00
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
2025-12-08 16:28:58 +02:00
self.current_image = None
self.current_image_path = None
2025-12-05 09:50:50 +02:00
self._setup_ui()
def _setup_ui(self):
"""Setup user interface."""
layout = QVBoxLayout()
2025-12-08 16:28:58 +02:00
# 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()
2025-12-08 17:33:32 +02:00
# 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)
2025-12-08 16:28:58 +02:00
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"
2025-12-05 09:50:50 +02:00
"Planned Features:\n"
"- Drawing tools for bounding boxes\n"
"- Class label assignment\n"
"- Export annotations to YOLO format\n"
"- Annotation verification"
)
2025-12-08 16:28:58 +02:00
info_layout.addWidget(info_label)
info_group.setLayout(info_layout)
2025-12-05 09:50:50 +02:00
2025-12-08 16:28:58 +02:00
layout.addWidget(info_group)
2025-12-08 17:33:32 +02:00
# 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)
2025-12-05 09:50:50 +02:00
self.setLayout(layout)
2025-12-08 16:28:58 +02:00
def _load_image(self):
"""Load and display an image file."""
2025-12-08 17:33:32 +02:00
# 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())
2025-12-08 16:28:58 +02:00
# 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
2025-12-08 17:33:32 +02:00
# Store the directory for next time
settings.setValue(
"annotation_tab/last_directory", str(Path(file_path).parent)
2025-12-08 16:28:58 +02:00
)
2025-12-08 17:33:32 +02:00
# Display image using the ImageDisplayWidget
self.image_display_widget.load_image(self.current_image)
# Update info label
self._update_image_info()
2025-12-08 16:28:58 +02:00
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)}")
2025-12-08 17:33:32 +02:00
def _update_image_info(self):
"""Update the image info label with current image details."""
2025-12-08 16:28:58 +02:00
if self.current_image is None:
2025-12-08 17:33:32 +02:00
self.image_info_label.setText("No image loaded")
2025-12-08 16:28:58 +02:00
return
2025-12-08 17:33:32 +02:00
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)
2025-12-08 16:28:58 +02:00
2025-12-08 17:33:32 +02:00
def _on_zoom_changed(self, zoom_scale: float):
"""Handle zoom level changes from the image display widget."""
self._update_image_info()
2025-12-08 16:28:58 +02:00
2025-12-05 09:50:50 +02:00
def refresh(self):
"""Refresh the tab."""
pass