diff --git a/croppa/main.py b/croppa/main.py index 776f233..fe76bf1 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -2534,56 +2534,62 @@ class VideoEditor: return False def navigate_to_next_template(self): - """Navigate to next template (; key)""" + """Navigate to next template (: key) - jump to template's start frame""" if not self.templates: return template_ids = sorted(self.templates.keys()) if self.current_template_id is None: - # Find first template that starts after current frame - for template_id in template_ids: - if self.templates[template_id]['start_frame'] > self.current_frame: - self.current_template_id = template_id - self._select_best_template_for_frame(self.current_frame) - self.show_feedback_message(f"Template {template_id} selected") - return + # Find first template + if template_ids: + template_id = template_ids[0] + start_frame = self.templates[template_id]['start_frame'] + self.current_frame = start_frame + self.current_template_id = template_id + self._select_best_template_for_frame(self.current_frame) + self.show_feedback_message(f"Template {template_id} (frame {start_frame})") + return else: # Find next template current_idx = template_ids.index(self.current_template_id) - for i in range(current_idx + 1, len(template_ids)): - template_id = template_ids[i] - if self.templates[template_id]['start_frame'] > self.current_frame: - self.current_template_id = template_id - self._select_best_template_for_frame(self.current_frame) - self.show_feedback_message(f"Template {template_id} selected") - return + if current_idx + 1 < len(template_ids): + template_id = template_ids[current_idx + 1] + start_frame = self.templates[template_id]['start_frame'] + self.current_frame = start_frame + self.current_template_id = template_id + self._select_best_template_for_frame(self.current_frame) + self.show_feedback_message(f"Template {template_id} (frame {start_frame})") + return self.show_feedback_message("No next template found") def navigate_to_previous_template(self): - """Navigate to previous template (: key)""" + """Navigate to previous template (; key) - jump to template's start frame""" if not self.templates: return template_ids = sorted(self.templates.keys()) if self.current_template_id is None: - # Find last template that starts before current frame - for template_id in reversed(template_ids): - if self.templates[template_id]['start_frame'] < self.current_frame: - self.current_template_id = template_id - self._select_best_template_for_frame(self.current_frame) - self.show_feedback_message(f"Template {template_id} selected") - return + # Find last template + if template_ids: + template_id = template_ids[-1] + start_frame = self.templates[template_id]['start_frame'] + self.current_frame = start_frame + self.current_template_id = template_id + self._select_best_template_for_frame(self.current_frame) + self.show_feedback_message(f"Template {template_id} (frame {start_frame})") + return else: # Find previous template current_idx = template_ids.index(self.current_template_id) - for i in range(current_idx - 1, -1, -1): - template_id = template_ids[i] - if self.templates[template_id]['start_frame'] < self.current_frame: - self.current_template_id = template_id - self._select_best_template_for_frame(self.current_frame) - self.show_feedback_message(f"Template {template_id} selected") - return + if current_idx - 1 >= 0: + template_id = template_ids[current_idx - 1] + start_frame = self.templates[template_id]['start_frame'] + self.current_frame = start_frame + self.current_template_id = template_id + self._select_best_template_for_frame(self.current_frame) + self.show_feedback_message(f"Template {template_id} (frame {start_frame})") + return self.show_feedback_message("No previous template found") @@ -3323,6 +3329,32 @@ class VideoEditor: conf_text = f"{confidence:.2f}" cv2.putText(canvas, conf_text, (sx + 10, sy - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) + # Draw template dots for all templates + if not self.is_image_mode and self.templates: + for template_id, template_data in self.templates.items(): + # Only draw if this template covers the current frame + if (template_data['start_frame'] <= self.current_frame <= template_data['end_frame']): + # Get template center position from region + x, y, w, h = template_data['region'] + center_x = x + w // 2 + center_y = y + h // 2 + + # Map to screen coordinates + sx, sy = self._map_rotated_to_screen(center_x, center_y) + + # Draw template dot (different colors for active/inactive) + is_active = (self.current_template_id == template_id) + if is_active: + cv2.circle(canvas, (sx, sy), 6, (0, 255, 0), -1) # Green for active + cv2.circle(canvas, (sx, sy), 6, (255, 255, 255), 2) + else: + cv2.circle(canvas, (sx, sy), 4, (0, 0, 255), -1) # Red for inactive + cv2.circle(canvas, (sx, sy), 4, (255, 255, 255), 1) + + # Draw template ID + cv2.putText(canvas, str(template_id), (sx + 8, sy - 8), + cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) + # Draw selection rectangles for feature extraction/deletion if self.selective_feature_extraction_rect: x, y, w, h = self.selective_feature_extraction_rect @@ -3541,17 +3573,23 @@ class VideoEditor: # Handle Ctrl+Right-click for template removal if event == cv2.EVENT_RBUTTONDOWN and (flags & cv2.EVENT_FLAG_CTRLKEY) and not (flags & cv2.EVENT_FLAG_SHIFTKEY): if not self.is_image_mode and self.templates: - # Check if click is near any template region + # Check if click is near any template dot (center of template region) screen_x, screen_y = x, y raw_x, raw_y = self._map_screen_to_rotated(screen_x, screen_y) for template_id, template_data in self.templates.items(): - tx, ty, tw, th = template_data['region'] - # Check if click is within template region - if (tx <= raw_x <= tx + tw and ty <= raw_y <= ty + th): - self.remove_template(template_id) - self.save_state() - return + # Only check templates that cover current frame + if (template_data['start_frame'] <= self.current_frame <= template_data['end_frame']): + tx, ty, tw, th = template_data['region'] + center_x = tx + tw // 2 + center_y = ty + th // 2 + + # Check if click is within 10px of template center + distance = ((raw_x - center_x) ** 2 + (raw_y - center_y) ** 2) ** 0.5 + if distance <= 10: + self.remove_template(template_id) + self.save_state() + return # Handle right-click for tracking points (no modifiers) if event == cv2.EVENT_RBUTTONDOWN and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY)): @@ -4757,10 +4795,10 @@ class VideoEditor: print(f"DEBUG: Multi-scale template matching toggled to {self.multi_scale_template_matching}") self.show_feedback_message(f"Multi-scale template matching {'ON' if self.multi_scale_template_matching else 'OFF'}") self.save_state() - elif key == ord(";"): # Semicolon - Navigate to next template - self.navigate_to_next_template() - elif key == ord(":"): # Colon - Navigate to previous template + elif key == ord(";"): # Semicolon - Navigate to previous template self.navigate_to_previous_template() + elif key == ord(":"): # Colon - Navigate to next template + self.navigate_to_next_template() elif key == ord("t"): # Marker looping only for videos if not self.is_image_mode: