Adding auto zoom when result is loaded
This commit is contained in:
@@ -35,9 +35,7 @@ logger = get_logger(__name__)
|
|||||||
class ResultsTab(QWidget):
|
class ResultsTab(QWidget):
|
||||||
"""Results tab showing detection history and preview overlays."""
|
"""Results tab showing detection history and preview overlays."""
|
||||||
|
|
||||||
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
|
||||||
@@ -71,24 +69,12 @@ class ResultsTab(QWidget):
|
|||||||
left_layout.addLayout(controls_layout)
|
left_layout.addLayout(controls_layout)
|
||||||
|
|
||||||
self.results_table = QTableWidget(0, 5)
|
self.results_table = QTableWidget(0, 5)
|
||||||
self.results_table.setHorizontalHeaderLabels(
|
self.results_table.setHorizontalHeaderLabels(["Image", "Model", "Detections", "Classes", "Last Updated"])
|
||||||
["Image", "Model", "Detections", "Classes", "Last Updated"]
|
self.results_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||||
)
|
self.results_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||||
self.results_table.horizontalHeader().setSectionResizeMode(
|
self.results_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
||||||
0, QHeaderView.Stretch
|
self.results_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
|
||||||
)
|
self.results_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeToContents)
|
||||||
self.results_table.horizontalHeader().setSectionResizeMode(
|
|
||||||
1, QHeaderView.Stretch
|
|
||||||
)
|
|
||||||
self.results_table.horizontalHeader().setSectionResizeMode(
|
|
||||||
2, QHeaderView.ResizeToContents
|
|
||||||
)
|
|
||||||
self.results_table.horizontalHeader().setSectionResizeMode(
|
|
||||||
3, QHeaderView.Stretch
|
|
||||||
)
|
|
||||||
self.results_table.horizontalHeader().setSectionResizeMode(
|
|
||||||
4, QHeaderView.ResizeToContents
|
|
||||||
)
|
|
||||||
self.results_table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.results_table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
self.results_table.setSelectionMode(QAbstractItemView.SingleSelection)
|
self.results_table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
self.results_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
self.results_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||||
@@ -106,6 +92,8 @@ class ResultsTab(QWidget):
|
|||||||
preview_layout = QVBoxLayout()
|
preview_layout = QVBoxLayout()
|
||||||
|
|
||||||
self.preview_canvas = AnnotationCanvasWidget()
|
self.preview_canvas = AnnotationCanvasWidget()
|
||||||
|
# Auto-zoom so newly loaded images fill the available preview viewport.
|
||||||
|
self.preview_canvas.set_auto_fit_to_view(True)
|
||||||
self.preview_canvas.set_polyline_enabled(False)
|
self.preview_canvas.set_polyline_enabled(False)
|
||||||
self.preview_canvas.set_show_bboxes(True)
|
self.preview_canvas.set_show_bboxes(True)
|
||||||
preview_layout.addWidget(self.preview_canvas)
|
preview_layout.addWidget(self.preview_canvas)
|
||||||
@@ -119,9 +107,7 @@ class ResultsTab(QWidget):
|
|||||||
self.show_bboxes_checkbox.stateChanged.connect(self._toggle_bboxes)
|
self.show_bboxes_checkbox.stateChanged.connect(self._toggle_bboxes)
|
||||||
self.show_confidence_checkbox = QCheckBox("Show Confidence")
|
self.show_confidence_checkbox = QCheckBox("Show Confidence")
|
||||||
self.show_confidence_checkbox.setChecked(False)
|
self.show_confidence_checkbox.setChecked(False)
|
||||||
self.show_confidence_checkbox.stateChanged.connect(
|
self.show_confidence_checkbox.stateChanged.connect(self._apply_detection_overlays)
|
||||||
self._apply_detection_overlays
|
|
||||||
)
|
|
||||||
toggles_layout.addWidget(self.show_masks_checkbox)
|
toggles_layout.addWidget(self.show_masks_checkbox)
|
||||||
toggles_layout.addWidget(self.show_bboxes_checkbox)
|
toggles_layout.addWidget(self.show_bboxes_checkbox)
|
||||||
toggles_layout.addWidget(self.show_confidence_checkbox)
|
toggles_layout.addWidget(self.show_confidence_checkbox)
|
||||||
@@ -169,8 +155,7 @@ class ResultsTab(QWidget):
|
|||||||
"image_id": det["image_id"],
|
"image_id": det["image_id"],
|
||||||
"model_id": det["model_id"],
|
"model_id": det["model_id"],
|
||||||
"image_path": det.get("image_path"),
|
"image_path": det.get("image_path"),
|
||||||
"image_filename": det.get("image_filename")
|
"image_filename": det.get("image_filename") or det.get("image_path"),
|
||||||
or det.get("image_path"),
|
|
||||||
"model_name": det.get("model_name", ""),
|
"model_name": det.get("model_name", ""),
|
||||||
"model_version": det.get("model_version", ""),
|
"model_version": det.get("model_version", ""),
|
||||||
"last_detected": det.get("detected_at"),
|
"last_detected": det.get("detected_at"),
|
||||||
@@ -183,8 +168,7 @@ class ResultsTab(QWidget):
|
|||||||
|
|
||||||
entry["count"] += 1
|
entry["count"] += 1
|
||||||
if det.get("detected_at") and (
|
if det.get("detected_at") and (
|
||||||
not entry.get("last_detected")
|
not entry.get("last_detected") or str(det.get("detected_at")) > str(entry.get("last_detected"))
|
||||||
or str(det.get("detected_at")) > str(entry.get("last_detected"))
|
|
||||||
):
|
):
|
||||||
entry["last_detected"] = det.get("detected_at")
|
entry["last_detected"] = det.get("detected_at")
|
||||||
if det.get("class_name"):
|
if det.get("class_name"):
|
||||||
@@ -214,9 +198,7 @@ class ResultsTab(QWidget):
|
|||||||
|
|
||||||
for row, entry in enumerate(self.detection_summary):
|
for row, entry in enumerate(self.detection_summary):
|
||||||
model_label = f"{entry['model_name']} {entry['model_version']}".strip()
|
model_label = f"{entry['model_name']} {entry['model_version']}".strip()
|
||||||
class_list = (
|
class_list = ", ".join(sorted(entry["classes"])) if entry["classes"] else "-"
|
||||||
", ".join(sorted(entry["classes"])) if entry["classes"] else "-"
|
|
||||||
)
|
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
QTableWidgetItem(entry.get("image_filename", "")),
|
QTableWidgetItem(entry.get("image_filename", "")),
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from PySide6.QtGui import (
|
|||||||
QPaintEvent,
|
QPaintEvent,
|
||||||
QPolygonF,
|
QPolygonF,
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QEvent, Signal, QPoint, QPointF, QRect
|
from PySide6.QtCore import Qt, QEvent, Signal, QPoint, QPointF, QRect, QTimer
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from src.utils.image import Image, ImageLoadError
|
from src.utils.image import Image, ImageLoadError
|
||||||
@@ -79,9 +79,7 @@ def rdp(points: List[Tuple[float, float]], epsilon: float) -> List[Tuple[float,
|
|||||||
return [start, end]
|
return [start, end]
|
||||||
|
|
||||||
|
|
||||||
def simplify_polyline(
|
def simplify_polyline(points: List[Tuple[float, float]], epsilon: float) -> List[Tuple[float, float]]:
|
||||||
points: List[Tuple[float, float]], epsilon: float
|
|
||||||
) -> List[Tuple[float, float]]:
|
|
||||||
"""
|
"""
|
||||||
Simplify a polyline with RDP while preserving closure semantics.
|
Simplify a polyline with RDP while preserving closure semantics.
|
||||||
|
|
||||||
@@ -145,6 +143,10 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
self.zoom_step = 0.1
|
self.zoom_step = 0.1
|
||||||
self.zoom_wheel_step = 0.15
|
self.zoom_wheel_step = 0.15
|
||||||
|
|
||||||
|
# Auto-fit behavior (opt-in): when enabled, newly loaded images (and resizes)
|
||||||
|
# will scale to fill the available viewport while preserving aspect ratio.
|
||||||
|
self._auto_fit_to_view: bool = False
|
||||||
|
|
||||||
# Drawing / interaction state
|
# Drawing / interaction state
|
||||||
self.is_drawing = False
|
self.is_drawing = False
|
||||||
self.polyline_enabled = False
|
self.polyline_enabled = False
|
||||||
@@ -175,6 +177,35 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
|
|
||||||
self._setup_ui()
|
self._setup_ui()
|
||||||
|
|
||||||
|
def set_auto_fit_to_view(self, enabled: bool):
|
||||||
|
"""Enable/disable automatic zoom-to-fit behavior."""
|
||||||
|
self._auto_fit_to_view = bool(enabled)
|
||||||
|
if self._auto_fit_to_view and self.original_pixmap is not None:
|
||||||
|
QTimer.singleShot(0, self.fit_to_view)
|
||||||
|
|
||||||
|
def fit_to_view(self, padding_px: int = 6):
|
||||||
|
"""Zoom the image so it fits the scroll area's viewport (aspect preserved)."""
|
||||||
|
if self.original_pixmap is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
viewport = self.scroll_area.viewport().size()
|
||||||
|
available_w = max(1, int(viewport.width()) - int(padding_px))
|
||||||
|
available_h = max(1, int(viewport.height()) - int(padding_px))
|
||||||
|
|
||||||
|
img_w = max(1, int(self.original_pixmap.width()))
|
||||||
|
img_h = max(1, int(self.original_pixmap.height()))
|
||||||
|
|
||||||
|
scale_w = available_w / img_w
|
||||||
|
scale_h = available_h / img_h
|
||||||
|
new_scale = min(scale_w, scale_h)
|
||||||
|
new_scale = max(self.zoom_min, min(self.zoom_max, float(new_scale)))
|
||||||
|
|
||||||
|
if abs(new_scale - self.zoom_scale) < 1e-4:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.zoom_scale = new_scale
|
||||||
|
self._apply_zoom()
|
||||||
|
|
||||||
def _setup_ui(self):
|
def _setup_ui(self):
|
||||||
"""Setup user interface."""
|
"""Setup user interface."""
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
@@ -187,9 +218,7 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
|
|
||||||
self.canvas_label = QLabel("No image loaded")
|
self.canvas_label = QLabel("No image loaded")
|
||||||
self.canvas_label.setAlignment(Qt.AlignCenter)
|
self.canvas_label.setAlignment(Qt.AlignCenter)
|
||||||
self.canvas_label.setStyleSheet(
|
self.canvas_label.setStyleSheet("QLabel { background-color: #2b2b2b; color: #888; }")
|
||||||
"QLabel { background-color: #2b2b2b; color: #888; }"
|
|
||||||
)
|
|
||||||
self.canvas_label.setScaledContents(False)
|
self.canvas_label.setScaledContents(False)
|
||||||
self.canvas_label.setMouseTracking(True)
|
self.canvas_label.setMouseTracking(True)
|
||||||
|
|
||||||
@@ -212,9 +241,18 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
self.zoom_scale = 1.0
|
self.zoom_scale = 1.0
|
||||||
self.clear_annotations()
|
self.clear_annotations()
|
||||||
self._display_image()
|
self._display_image()
|
||||||
logger.debug(
|
|
||||||
f"Loaded image into annotation canvas: {image.width}x{image.height}"
|
# Defer fit-to-view until the widget has a valid viewport size.
|
||||||
)
|
if self._auto_fit_to_view:
|
||||||
|
QTimer.singleShot(0, self.fit_to_view)
|
||||||
|
|
||||||
|
logger.debug(f"Loaded image into annotation canvas: {image.width}x{image.height}")
|
||||||
|
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
"""Optionally keep the image fitted when the widget is resized."""
|
||||||
|
super().resizeEvent(event)
|
||||||
|
if self._auto_fit_to_view and self.original_pixmap is not None:
|
||||||
|
QTimer.singleShot(0, self.fit_to_view)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Clear the displayed image and all annotations."""
|
"""Clear the displayed image and all annotations."""
|
||||||
@@ -289,22 +327,14 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
scaled_width,
|
scaled_width,
|
||||||
scaled_height,
|
scaled_height,
|
||||||
Qt.KeepAspectRatio,
|
Qt.KeepAspectRatio,
|
||||||
(
|
(Qt.SmoothTransformation if self.zoom_scale >= 1.0 else Qt.FastTransformation),
|
||||||
Qt.SmoothTransformation
|
|
||||||
if self.zoom_scale >= 1.0
|
|
||||||
else Qt.FastTransformation
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
scaled_annotations = self.annotation_pixmap.scaled(
|
scaled_annotations = self.annotation_pixmap.scaled(
|
||||||
scaled_width,
|
scaled_width,
|
||||||
scaled_height,
|
scaled_height,
|
||||||
Qt.KeepAspectRatio,
|
Qt.KeepAspectRatio,
|
||||||
(
|
(Qt.SmoothTransformation if self.zoom_scale >= 1.0 else Qt.FastTransformation),
|
||||||
Qt.SmoothTransformation
|
|
||||||
if self.zoom_scale >= 1.0
|
|
||||||
else Qt.FastTransformation
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Composite image and annotations
|
# Composite image and annotations
|
||||||
@@ -390,16 +420,11 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
y = (pos.y() - offset_y) / self.zoom_scale
|
y = (pos.y() - offset_y) / self.zoom_scale
|
||||||
|
|
||||||
# Check bounds
|
# Check bounds
|
||||||
if (
|
if 0 <= x < self.original_pixmap.width() and 0 <= y < self.original_pixmap.height():
|
||||||
0 <= x < self.original_pixmap.width()
|
|
||||||
and 0 <= y < self.original_pixmap.height()
|
|
||||||
):
|
|
||||||
return (int(x), int(y))
|
return (int(x), int(y))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _find_polyline_at(
|
def _find_polyline_at(self, img_x: float, img_y: float, threshold_px: float = 5.0) -> Optional[int]:
|
||||||
self, img_x: float, img_y: float, threshold_px: float = 5.0
|
|
||||||
) -> Optional[int]:
|
|
||||||
"""
|
"""
|
||||||
Find index of polyline whose geometry is within threshold_px of (img_x, img_y).
|
Find index of polyline whose geometry is within threshold_px of (img_x, img_y).
|
||||||
Returns the index in self.polylines, or None if none is close enough.
|
Returns the index in self.polylines, or None if none is close enough.
|
||||||
@@ -421,9 +446,7 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
|
|
||||||
# Precise distance to all segments
|
# Precise distance to all segments
|
||||||
for (x1, y1), (x2, y2) in zip(polyline[:-1], polyline[1:]):
|
for (x1, y1), (x2, y2) in zip(polyline[:-1], polyline[1:]):
|
||||||
d = perpendicular_distance(
|
d = perpendicular_distance((img_x, img_y), (float(x1), float(y1)), (float(x2), float(y2)))
|
||||||
(img_x, img_y), (float(x1), float(y1)), (float(x2), float(y2))
|
|
||||||
)
|
|
||||||
if d < best_dist:
|
if d < best_dist:
|
||||||
best_dist = d
|
best_dist = d
|
||||||
best_index = idx
|
best_index = idx
|
||||||
@@ -624,11 +647,7 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
|
|
||||||
def mouseMoveEvent(self, event: QMouseEvent):
|
def mouseMoveEvent(self, event: QMouseEvent):
|
||||||
"""Handle mouse move events for drawing."""
|
"""Handle mouse move events for drawing."""
|
||||||
if (
|
if not self.is_drawing or not self.polyline_enabled or self.annotation_pixmap is None:
|
||||||
not self.is_drawing
|
|
||||||
or not self.polyline_enabled
|
|
||||||
or self.annotation_pixmap is None
|
|
||||||
):
|
|
||||||
super().mouseMoveEvent(event)
|
super().mouseMoveEvent(event)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -688,15 +707,10 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
|
|
||||||
if len(simplified) >= 2:
|
if len(simplified) >= 2:
|
||||||
# Store polyline and redraw all annotations
|
# Store polyline and redraw all annotations
|
||||||
self._add_polyline(
|
self._add_polyline(simplified, self.polyline_pen_color, self.polyline_pen_width)
|
||||||
simplified, self.polyline_pen_color, self.polyline_pen_width
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert to normalized coordinates for metadata + signal
|
# Convert to normalized coordinates for metadata + signal
|
||||||
normalized_stroke = [
|
normalized_stroke = [self._image_to_normalized_coords(int(x), int(y)) for (x, y) in simplified]
|
||||||
self._image_to_normalized_coords(int(x), int(y))
|
|
||||||
for (x, y) in simplified
|
|
||||||
]
|
|
||||||
self.all_strokes.append(
|
self.all_strokes.append(
|
||||||
{
|
{
|
||||||
"points": normalized_stroke,
|
"points": normalized_stroke,
|
||||||
@@ -709,8 +723,7 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
# Emit signal with normalized coordinates
|
# Emit signal with normalized coordinates
|
||||||
self.annotation_drawn.emit(normalized_stroke)
|
self.annotation_drawn.emit(normalized_stroke)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Completed stroke with {len(simplified)} points "
|
f"Completed stroke with {len(simplified)} points " f"(normalized len={len(normalized_stroke)})"
|
||||||
f"(normalized len={len(normalized_stroke)})"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.current_stroke = []
|
self.current_stroke = []
|
||||||
@@ -750,9 +763,7 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
|
|
||||||
# Store polyline as [y_norm, x_norm] to match DB convention and
|
# Store polyline as [y_norm, x_norm] to match DB convention and
|
||||||
# the expectations of draw_saved_polyline().
|
# the expectations of draw_saved_polyline().
|
||||||
normalized_polyline = [
|
normalized_polyline = [[y / img_height, x / img_width] for (x, y) in polyline]
|
||||||
[y / img_height, x / img_width] for (x, y) in polyline
|
|
||||||
]
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Polyline {idx}: {len(polyline)} points, "
|
f"Polyline {idx}: {len(polyline)} points, "
|
||||||
@@ -772,7 +783,7 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
self,
|
self,
|
||||||
polyline: List[List[float]],
|
polyline: List[List[float]],
|
||||||
color: str,
|
color: str,
|
||||||
width: int = 3,
|
width: int = 1,
|
||||||
annotation_id: Optional[int] = None,
|
annotation_id: Optional[int] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -810,17 +821,13 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
|
|
||||||
# Store and redraw using common pipeline
|
# Store and redraw using common pipeline
|
||||||
pen_color = QColor(color)
|
pen_color = QColor(color)
|
||||||
pen_color.setAlpha(128) # Add semi-transparency
|
pen_color.setAlpha(255) # Add semi-transparency
|
||||||
self._add_polyline(img_coords, pen_color, width, annotation_id=annotation_id)
|
self._add_polyline(img_coords, pen_color, width, annotation_id=annotation_id)
|
||||||
|
|
||||||
# Store in all_strokes for consistency (uses normalized coordinates)
|
# Store in all_strokes for consistency (uses normalized coordinates)
|
||||||
self.all_strokes.append(
|
self.all_strokes.append({"points": polyline, "color": color, "alpha": 128, "width": width})
|
||||||
{"points": polyline, "color": color, "alpha": 128, "width": width}
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(f"Drew saved polyline with {len(polyline)} points in color {color}")
|
||||||
f"Drew saved polyline with {len(polyline)} points in color {color}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def draw_saved_bbox(
|
def draw_saved_bbox(
|
||||||
self,
|
self,
|
||||||
@@ -844,9 +851,7 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if len(bbox) != 4:
|
if len(bbox) != 4:
|
||||||
logger.warning(
|
logger.warning(f"Invalid bounding box format: expected 4 values, got {len(bbox)}")
|
||||||
f"Invalid bounding box format: expected 4 values, got {len(bbox)}"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Convert normalized coordinates to image coordinates (for logging/debug)
|
# Convert normalized coordinates to image coordinates (for logging/debug)
|
||||||
@@ -867,15 +872,11 @@ class AnnotationCanvasWidget(QWidget):
|
|||||||
# in _redraw_annotations() together with all polylines.
|
# in _redraw_annotations() together with all polylines.
|
||||||
pen_color = QColor(color)
|
pen_color = QColor(color)
|
||||||
pen_color.setAlpha(128) # Add semi-transparency
|
pen_color.setAlpha(128) # Add semi-transparency
|
||||||
self.bboxes.append(
|
self.bboxes.append([float(x_min_norm), float(y_min_norm), float(x_max_norm), float(y_max_norm)])
|
||||||
[float(x_min_norm), float(y_min_norm), float(x_max_norm), float(y_max_norm)]
|
|
||||||
)
|
|
||||||
self.bbox_meta.append({"color": pen_color, "width": int(width), "label": label})
|
self.bbox_meta.append({"color": pen_color, "width": int(width), "label": label})
|
||||||
|
|
||||||
# Store in all_strokes for consistency
|
# Store in all_strokes for consistency
|
||||||
self.all_strokes.append(
|
self.all_strokes.append({"bbox": bbox, "color": color, "alpha": 128, "width": width, "label": label})
|
||||||
{"bbox": bbox, "color": color, "alpha": 128, "width": width, "label": label}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Redraw overlay (polylines + all bounding boxes)
|
# Redraw overlay (polylines + all bounding boxes)
|
||||||
self._redraw_annotations()
|
self._redraw_annotations()
|
||||||
|
|||||||
Reference in New Issue
Block a user