Adding auto zoom for annotation image view and changing tab order
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user