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)")