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 # Add tabs to widget
self.tab_widget.addTab(self.detection_tab, "Detection") 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.training_tab, "Training")
self.tab_widget.addTab(self.validation_tab, "Validation") 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 # Connect tab change signal
self.tab_widget.currentChanged.connect(self._on_tab_changed) self.tab_widget.currentChanged.connect(self._on_tab_changed)

View File

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