Adding auto zoom for annotation image view and changing tab order

This commit is contained in:
2026-01-16 14:20:12 +02:00
parent f810fec4d8
commit 8d30e6bb7a
2 changed files with 28 additions and 70 deletions

View File

@@ -125,10 +125,10 @@ class MainWindow(QMainWindow):
# Add tabs to widget
self.tab_widget.addTab(self.detection_tab, "Detection")
self.tab_widget.addTab(self.results_tab, "Results")
self.tab_widget.addTab(self.annotation_tab, "Annotation")
self.tab_widget.addTab(self.training_tab, "Training")
self.tab_widget.addTab(self.validation_tab, "Validation")
self.tab_widget.addTab(self.results_tab, "Results")
self.tab_widget.addTab(self.annotation_tab, "Annotation (Future)")
# Connect tab change signal
self.tab_widget.currentChanged.connect(self._on_tab_changed)

View File

@@ -29,9 +29,7 @@ logger = get_logger(__name__)
class AnnotationTab(QWidget):
"""Annotation tab for manual image annotation."""
def __init__(
self, db_manager: DatabaseManager, config_manager: ConfigManager, parent=None
):
def __init__(self, db_manager: DatabaseManager, config_manager: ConfigManager, parent=None):
super().__init__(parent)
self.db_manager = db_manager
self.config_manager = config_manager
@@ -62,6 +60,9 @@ class AnnotationTab(QWidget):
# Use the AnnotationCanvasWidget
self.annotation_canvas = AnnotationCanvasWidget()
# Auto-zoom so newly loaded images fill the available canvas viewport.
# (Matches the behavior used in ResultsTab.)
self.annotation_canvas.set_auto_fit_to_view(True)
self.annotation_canvas.zoom_changed.connect(self._on_zoom_changed)
self.annotation_canvas.annotation_drawn.connect(self._on_annotation_drawn)
# Selection of existing polylines (when tool is not in drawing mode)
@@ -72,9 +73,7 @@ class AnnotationTab(QWidget):
self.left_splitter.addWidget(canvas_group)
# Controls info
controls_info = QLabel(
"Zoom: Mouse wheel or +/- keys | Drawing: Enable pen and drag mouse"
)
controls_info = QLabel("Zoom: Mouse wheel or +/- keys | Drawing: Enable pen and drag mouse")
controls_info.setStyleSheet("QLabel { color: #888; font-style: italic; }")
self.left_splitter.addWidget(controls_info)
# }
@@ -85,36 +84,20 @@ class AnnotationTab(QWidget):
# Annotation tools section
self.annotation_tools = AnnotationToolsWidget(self.db_manager)
self.annotation_tools.polyline_enabled_changed.connect(
self.annotation_canvas.set_polyline_enabled
)
self.annotation_tools.polyline_pen_color_changed.connect(
self.annotation_canvas.set_polyline_pen_color
)
self.annotation_tools.polyline_pen_width_changed.connect(
self.annotation_canvas.set_polyline_pen_width
)
self.annotation_tools.polyline_enabled_changed.connect(self.annotation_canvas.set_polyline_enabled)
self.annotation_tools.polyline_pen_color_changed.connect(self.annotation_canvas.set_polyline_pen_color)
self.annotation_tools.polyline_pen_width_changed.connect(self.annotation_canvas.set_polyline_pen_width)
# Show / hide bounding boxes
self.annotation_tools.show_bboxes_changed.connect(
self.annotation_canvas.set_show_bboxes
)
self.annotation_tools.show_bboxes_changed.connect(self.annotation_canvas.set_show_bboxes)
# RDP simplification controls
self.annotation_tools.simplify_on_finish_changed.connect(
self._on_simplify_on_finish_changed
)
self.annotation_tools.simplify_epsilon_changed.connect(
self._on_simplify_epsilon_changed
)
self.annotation_tools.simplify_on_finish_changed.connect(self._on_simplify_on_finish_changed)
self.annotation_tools.simplify_epsilon_changed.connect(self._on_simplify_epsilon_changed)
# Class selection and class-color changes
self.annotation_tools.class_selected.connect(self._on_class_selected)
self.annotation_tools.class_color_changed.connect(self._on_class_color_changed)
self.annotation_tools.clear_annotations_requested.connect(
self._on_clear_annotations
)
self.annotation_tools.clear_annotations_requested.connect(self._on_clear_annotations)
# Delete selected annotation on canvas
self.annotation_tools.delete_selected_annotation_requested.connect(
self._on_delete_selected_annotation
)
self.annotation_tools.delete_selected_annotation_requested.connect(self._on_delete_selected_annotation)
self.right_splitter.addWidget(self.annotation_tools)
# Image loading section
@@ -180,9 +163,7 @@ class AnnotationTab(QWidget):
self.current_image_path = file_path
# Store the directory for next time
settings.setValue(
"annotation_tab/last_directory", str(Path(file_path).parent)
)
settings.setValue("annotation_tab/last_directory", str(Path(file_path).parent))
# Get or create image in database
relative_path = str(Path(file_path).name) # Simplified for now
@@ -206,9 +187,7 @@ class AnnotationTab(QWidget):
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)}"
)
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)}")
@@ -340,9 +319,7 @@ class AnnotationTab(QWidget):
if not self.current_image_id:
return
logger.debug(
f"Class color changed; reloading annotations for image ID {self.current_image_id}"
)
logger.debug(f"Class color changed; reloading annotations for image ID {self.current_image_id}")
self._load_annotations_for_current_image()
def _on_class_selected(self, class_data):
@@ -355,9 +332,7 @@ class AnnotationTab(QWidget):
if class_data:
logger.debug(f"Object class selected: {class_data['class_name']}")
else:
logger.debug(
'No class selected ("-- Select Class --"), showing all annotations'
)
logger.debug('No class selected ("-- Select Class --"), showing all annotations')
# Changing the class filter invalidates any previous selection
self.selected_annotation_ids = []
@@ -390,9 +365,7 @@ class AnnotationTab(QWidget):
question = "Are you sure you want to delete the selected annotation?"
title = "Delete Annotation"
else:
question = (
f"Are you sure you want to delete the {count} selected annotations?"
)
question = f"Are you sure you want to delete the {count} selected annotations?"
title = "Delete Annotations"
reply = QMessageBox.question(
@@ -420,13 +393,11 @@ class AnnotationTab(QWidget):
QMessageBox.warning(
self,
"Partial Failure",
"Some annotations could not be deleted:\n"
+ ", ".join(str(a) for a in failed_ids),
"Some annotations could not be deleted:\n" + ", ".join(str(a) for a in failed_ids),
)
else:
logger.info(
f"Deleted {count} annotation(s): "
+ ", ".join(str(a) for a in self.selected_annotation_ids)
f"Deleted {count} annotation(s): " + ", ".join(str(a) for a in self.selected_annotation_ids)
)
# Clear selection and reload annotations for the current image from DB
@@ -456,17 +427,13 @@ class AnnotationTab(QWidget):
return
try:
self.current_annotations = self.db_manager.get_annotations_for_image(
self.current_image_id
)
self.current_annotations = self.db_manager.get_annotations_for_image(self.current_image_id)
# New annotations loaded; reset any selection
self.selected_annotation_ids = []
self.annotation_tools.set_has_selected_annotation(False)
self._redraw_annotations_for_current_filter()
except Exception as e:
logger.error(
f"Failed to load annotations for image {self.current_image_id}: {e}"
)
logger.error(f"Failed to load annotations for image {self.current_image_id}: {e}")
QMessageBox.critical(
self,
"Error",
@@ -490,10 +457,7 @@ class AnnotationTab(QWidget):
drawn_count = 0
for ann in self.current_annotations:
# Filter by class if one is selected
if (
selected_class_id is not None
and ann.get("class_id") != selected_class_id
):
if selected_class_id is not None and ann.get("class_id") != selected_class_id:
continue
if ann.get("segmentation_mask"):
@@ -545,19 +509,13 @@ class AnnotationTab(QWidget):
settings = QSettings("microscopy_app", "object_detection")
# Save main splitter state
settings.setValue(
"annotation_tab/main_splitter_state", self.main_splitter.saveState()
)
settings.setValue("annotation_tab/main_splitter_state", self.main_splitter.saveState())
# Save left splitter state
settings.setValue(
"annotation_tab/left_splitter_state", self.left_splitter.saveState()
)
settings.setValue("annotation_tab/left_splitter_state", self.left_splitter.saveState())
# Save right splitter state
settings.setValue(
"annotation_tab/right_splitter_state", self.right_splitter.saveState()
)
settings.setValue("annotation_tab/right_splitter_state", self.right_splitter.saveState())
logger.debug("Saved annotation tab splitter states")