From cb097c55f1c23292576acb3bb7f0684417f479da Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Tue, 16 Sep 2025 20:32:33 +0200 Subject: [PATCH] Enhance VideoEditor with improved point transformation, bounds checking, and debugging features This commit refines the point transformation methods in the VideoEditor class, ensuring coordinates are validated against frame dimensions and crop areas. It adds detailed logging for point transformations and mouse interactions, improving visibility into the state of tracking points. Additionally, it introduces debug features for visualizing crop rectangles and point indices, enhancing the debugging experience during video editing. --- croppa/main.py | 98 ++++++++++++++++++++++++++++++++++++++-------- croppa/tracking.py | 2 - 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/croppa/main.py b/croppa/main.py index df65f8c..6ed0dab 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -1200,11 +1200,22 @@ class VideoEditor: if point is None: return None - x, y = float(point[0]), float(point[1]) + orig_x, orig_y = float(point[0]), float(point[1]) + print(f"transform_point: original point ({orig_x}, {orig_y})") - print(f"transform_point: original point ({x}, {y})") + # Get frame dimensions + if self.current_display_frame is None: + return None + + frame_height, frame_width = self.current_display_frame.shape[:2] + + # Step 1: Check if point is within frame bounds + if not (0 <= orig_x < frame_width and 0 <= orig_y < frame_height): + print(f"transform_point: point is outside frame bounds ({frame_width}x{frame_height})") + # We'll still transform it, but it might not be visible # Step 1: Apply crop (adjust point relative to crop origin) + x, y = orig_x, orig_y if self.crop_rect: crop_x, crop_y, crop_w, crop_h = self.crop_rect print(f"transform_point: crop_rect = {self.crop_rect}") @@ -1224,12 +1235,8 @@ class VideoEditor: if self.crop_rect: crop_w, crop_h = float(self.crop_rect[2]), float(self.crop_rect[3]) else: - if self.current_display_frame is not None: - crop_h, crop_w = self.current_display_frame.shape[:2] - crop_h, crop_w = float(crop_h), float(crop_w) - else: - return None - + crop_h, crop_w = float(frame_height), float(frame_width) + print(f"transform_point: rotation_angle = {self.rotation_angle}, dimensions = ({crop_w}, {crop_h})") # Apply rotation to coordinates @@ -1270,10 +1277,14 @@ class VideoEditor: if point is None or self.current_display_frame is None: return None - x, y = float(point[0]), float(point[1]) - print(f"untransform_point: original display point ({x}, {y})") + display_x, display_y = float(point[0]), float(point[1]) + print(f"untransform_point: original display point ({display_x}, {display_y})") + + # Get frame dimensions + frame_height, frame_width = self.current_display_frame.shape[:2] # Step 1: Reverse zoom + x, y = display_x, display_y if self.zoom_factor != 1.0: x /= self.zoom_factor y /= self.zoom_factor @@ -1285,8 +1296,7 @@ class VideoEditor: if self.crop_rect: crop_w, crop_h = float(self.crop_rect[2]), float(self.crop_rect[3]) else: - crop_h, crop_w = self.current_display_frame.shape[:2] - crop_h, crop_w = float(crop_h), float(crop_w) + crop_h, crop_w = float(frame_height), float(frame_width) print(f"untransform_point: rotation_angle = {self.rotation_angle}, dimensions = ({crop_w}, {crop_h})") @@ -1314,9 +1324,11 @@ class VideoEditor: x += crop_x y += crop_y print(f"untransform_point: after reverse crop ({x}, {y}), crop_rect = {self.crop_rect}") - - # Don't clamp coordinates - allow points outside frame bounds - # This is important for tracking points that may be outside the current crop + + # Check if the resulting point is within frame bounds + if not (0 <= x < frame_width and 0 <= y < frame_height): + print(f"untransform_point: result is outside frame bounds ({frame_width}x{frame_height})") + # We'll still return it, but it might not be visible print(f"untransform_point: final result = ({x}, {y})") return (x, y) @@ -1494,18 +1506,46 @@ class VideoEditor: print(f"draw_tracking_points: offset=({offset_x},{offset_y}), scale={scale}") - # Draw tracking points for the current frame (green circles with white border) + # Draw tracking points for the current frame tracking_points = self.motion_tracker.get_tracking_points_for_frame(self.current_frame) print(f"draw_tracking_points: found {len(tracking_points)} tracking points for frame {self.current_frame}") # Get current frame dimensions for bounds checking frame_height, frame_width = self.current_display_frame.shape[:2] + # Draw coordinate axes for debugging (if in debug mode) + debug_mode = True + if debug_mode and self.crop_rect: + # Draw crop rectangle outline on the canvas + crop_x, crop_y, crop_w, crop_h = self.crop_rect + + # Transform the crop corners to display coordinates + top_left = self.transform_point((crop_x, crop_y)) + top_right = self.transform_point((crop_x + crop_w, crop_y)) + bottom_left = self.transform_point((crop_x, crop_y + crop_h)) + bottom_right = self.transform_point((crop_x + crop_w, crop_y + crop_h)) + + # Draw crop outline if all corners are visible + if all([top_left, top_right, bottom_left, bottom_right]): + # Convert to canvas coordinates + tl_x, tl_y = int(offset_x + top_left[0] * scale), int(offset_y + top_left[1] * scale) + tr_x, tr_y = int(offset_x + top_right[0] * scale), int(offset_y + top_right[1] * scale) + bl_x, bl_y = int(offset_x + bottom_left[0] * scale), int(offset_y + bottom_left[1] * scale) + br_x, br_y = int(offset_x + bottom_right[0] * scale), int(offset_y + bottom_right[1] * scale) + + # Draw crop outline + cv2.line(canvas, (tl_x, tl_y), (tr_x, tr_y), (255, 0, 255), 1) + cv2.line(canvas, (tr_x, tr_y), (br_x, br_y), (255, 0, 255), 1) + cv2.line(canvas, (br_x, br_y), (bl_x, bl_y), (255, 0, 255), 1) + cv2.line(canvas, (bl_x, bl_y), (tl_x, tl_y), (255, 0, 255), 1) + + # Process each tracking point for i, point in enumerate(tracking_points): print(f"draw_tracking_points: processing point {i}: {point}") # Check if the point is within the frame bounds is_in_frame = (0 <= point[0] < frame_width and 0 <= point[1] < frame_height) + print(f"draw_tracking_points: point {i} is {'inside' if is_in_frame else 'outside'} frame bounds") # Check if the point is within the crop area (if cropping is active) is_in_crop = True @@ -1513,6 +1553,7 @@ class VideoEditor: crop_x, crop_y, crop_w, crop_h = self.crop_rect is_in_crop = (crop_x <= point[0] < crop_x + crop_w and crop_y <= point[1] < crop_y + crop_h) + print(f"draw_tracking_points: point {i} is {'inside' if is_in_crop else 'outside'} crop area") # Transform point from original frame coordinates to display coordinates display_point = self.transform_point(point) @@ -1537,12 +1578,16 @@ class VideoEditor: cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (255, 255, 255), 2) # Draw green circle cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 0), -1) + # Draw point index for identification + cv2.putText(canvas, str(i), (x + 15, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) else: # Point is outside crop area - draw with different color # Draw gray border cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (128, 128, 128), 2) # Draw yellow circle cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 255), -1) + # Draw point index for identification + cv2.putText(canvas, str(i), (x + 15, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) else: print(f"draw_tracking_points: point {i} is outside canvas bounds") else: @@ -2127,7 +2172,8 @@ class VideoEditor: start_x = (self.window_width - final_display_width) // 2 start_y = (available_height - final_display_height) // 2 - print(f"mouse_callback: right-click at ({x}, {y}), display frame at ({start_x}, {start_y}), scale={scale}") + print(f"mouse_callback: right-click at ({x}, {y}), canvas dimensions: {self.window_width}x{self.window_height}") + print(f"mouse_callback: display frame at ({start_x}, {start_y}), size: {final_display_width}x{final_display_height}, scale={scale}") # Check if click is within the frame area if (start_x <= x < start_x + final_display_width and @@ -2145,6 +2191,17 @@ class VideoEditor: print(f"mouse_callback: untransformed to original coords {original_point}") if original_point: + # Get original frame dimensions for validation + frame_height, frame_width = self.current_display_frame.shape[:2] + + # Ensure point is within the original frame bounds + x_clamped = max(0, min(frame_width - 1, original_point[0])) + y_clamped = max(0, min(frame_height - 1, original_point[1])) + + if x_clamped != original_point[0] or y_clamped != original_point[1]: + print(f"mouse_callback: clamped point from {original_point} to ({x_clamped}, {y_clamped})") + original_point = (x_clamped, y_clamped) + # Check if clicking on an existing tracking point to remove it removed = self.motion_tracker.remove_tracking_point( self.current_frame, @@ -2164,6 +2221,13 @@ class VideoEditor: ) print(f"mouse_callback: added tracking point at {original_point}") + # Draw a debug marker at the exact point for visualization + if display_frame is not None: + # Transform the point back to display coordinates for verification + verification_point = self.transform_point(original_point) + if verification_point: + print(f"mouse_callback: verification - point transforms back to {verification_point}") + # Save state when tracking points change self.save_state() self.display_needs_update = True diff --git a/croppa/tracking.py b/croppa/tracking.py index 46bb899..3400b5f 100644 --- a/croppa/tracking.py +++ b/croppa/tracking.py @@ -108,8 +108,6 @@ class MotionTracker: def get_tracking_offset(self, frame_number: int) -> Tuple[float, float]: """Get the offset to center the crop on the tracked point""" - import logging - logger = logging.getLogger('croppa') if not self.tracking_enabled: print(f"get_tracking_offset: tracking not enabled, returning (0,0)")