diff --git a/croppa/main.py b/croppa/main.py index d85ada6..6f5a426 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -860,9 +860,6 @@ class VideoEditor: self.previous_frame_for_flow = None # Template matching tracking - self.template_matching_enabled = False - self.tracking_template = None - self.template_region = None self.template_match_history = [] # Store recent match confidences for adaptive thresholding # (x, y, w, h) in rotated frame coordinates self.template_selection_start = None @@ -922,8 +919,6 @@ class VideoEditor: 'tracking_enabled': self.tracking_enabled, 'tracking_points': {str(k): v for k, v in self.tracking_points.items()}, 'feature_tracker': self.feature_tracker.get_state_dict(), - 'template_matching_enabled': self.template_matching_enabled, - 'template_region': self.template_region, 'template_matching_full_frame': self.template_matching_full_frame, 'templates': {str(k): { 'template': None, # Don't save template images (too large) @@ -1021,12 +1016,6 @@ class VideoEditor: print(f"Loaded feature tracker state") # Load template matching state - if 'template_matching_enabled' in state: - self.template_matching_enabled = state['template_matching_enabled'] - if 'template_region' in state and state['template_region'] is not None: - self.template_region = state['template_region'] - # Recreate template from region when needed - self.tracking_template = None if 'template_matching_full_frame' in state: self.template_matching_full_frame = state['template_matching_full_frame'] @@ -1666,7 +1655,7 @@ class VideoEditor: # Calculate offset from template matching if enabled template_offset = None - if self.template_matching_enabled and self.tracking_template is not None: + if self.templates and self.current_template_id is not None: if self.current_display_frame is not None: if self.template_matching_full_frame: # Full frame mode - use the entire original frame @@ -1836,7 +1825,7 @@ class VideoEditor: def _get_template_matching_position(self, frame_number): """Get template matching position and confidence for a frame""" - if not self.template_matching_enabled or self.tracking_template is None: + if not self.templates or self.current_template_id is None: return None if self.current_display_frame is not None: @@ -1948,8 +1937,6 @@ class VideoEditor: def _map_screen_to_rotated(self, sx, sy): """Map a point on canvas screen coords back to ROTATED frame coords (pre-crop).""" - frame_number = getattr(self, 'current_frame', 0) - angle = self.rotation_angle # Use unified display params params = self._get_display_params() # Back to processed (zoomed+cropped) space @@ -2293,12 +2280,18 @@ class VideoEditor: def track_template(self, frame): """Track the template in the current frame""" - if self.tracking_template is None: + if self.current_template_id is None or self.current_template_id not in self.templates: + return None + + template_data = self.templates[self.current_template_id] + tracking_template = template_data['template'] + + if tracking_template is None: return None try: # Apply image preprocessing for better template matching - gray_frame, gray_template = self._improve_template_matching(frame, self.tracking_template) + gray_frame, gray_template = self._improve_template_matching(frame, tracking_template) # Single-scale template matching (faster) result = cv2.matchTemplate(gray_frame, gray_template, cv2.TM_CCOEFF_NORMED) @@ -2391,7 +2384,7 @@ class VideoEditor: # Extract template from raw frame template = self.current_display_frame[raw_y:raw_y+raw_h, raw_x:raw_x+raw_w] if template.size > 0: - # Add template to collection instead of setting single template + # Add template to collection template_id = self.add_template(template, (raw_x, raw_y, raw_w, raw_h)) self.show_feedback_message(f"Template {template_id} set from region ({raw_w}x{raw_h})") print(f"DEBUG: Template {template_id} set with size {template.shape}") @@ -2410,16 +2403,19 @@ class VideoEditor: if end_frame is None: end_frame = self.total_frames - 1 - # End any templates that start after the current frame + # Only end the current template if it exists and starts at or before the current frame + if self.current_template_id is not None and self.current_template_id in self.templates: + current_template = self.templates[self.current_template_id] + if current_template['start_frame'] <= self.current_frame: + # End the current template at the previous frame + self.templates[self.current_template_id]['end_frame'] = self.current_frame - 1 + print(f"DEBUG: Ended current template {self.current_template_id} at frame {self.current_frame - 1}") + + # Remove any templates that start after the current frame (they shouldn't exist yet) for template_id, template_data in list(self.templates.items()): if template_data['start_frame'] > self.current_frame: - # This template starts after the current frame, so remove it del self.templates[template_id] print(f"DEBUG: Removed future template {template_id} that started at frame {template_data['start_frame']}") - elif template_data['start_frame'] == self.current_frame: - # This template starts at the same frame, end it at the previous frame - self.templates[template_id]['end_frame'] = self.current_frame - 1 - print(f"DEBUG: Ended template {template_id} at frame {self.current_frame - 1}") self.templates[template_id] = { 'template': template.copy(), @@ -2428,11 +2424,8 @@ class VideoEditor: 'end_frame': end_frame } - # Set as current template and enable template matching + # Set as current template self.current_template_id = template_id - self.tracking_template = template.copy() - self.template_region = region - self.template_matching_enabled = True # Enable template matching when template is created self.show_feedback_message(f"Template {template_id} added (frames {start_frame}-{end_frame})") return template_id @@ -2446,11 +2439,8 @@ class VideoEditor: if self.current_template_id == template_id: self._select_best_template_for_frame(self.current_frame) - # If no templates left, disable template matching + # If no templates left, clear current template if not self.templates: - self.template_matching_enabled = False - self.tracking_template = None - self.template_region = None self.current_template_id = None self.show_feedback_message(f"Template {template_id} removed") @@ -2491,13 +2481,10 @@ class VideoEditor: else: return False - self.tracking_template = template_data['template'].copy() - self.template_region = template_data['region'] + # Template is already stored in the templates dict return True else: self.current_template_id = None - self.tracking_template = None - self.template_region = None return False def _recreate_template_from_template_data(self, template_id, frame): @@ -3168,7 +3155,7 @@ class VideoEditor: if self.optical_flow_enabled: feature_text += " (OPTICAL FLOW)" template_text = "" - if self.template_matching_enabled: + if self.templates: mode = "Full Frame" if self.template_matching_full_frame else "Cropped" template_text = f" | Template: {mode}" autorepeat_text = ( @@ -3284,8 +3271,8 @@ class VideoEditor: # Draw template matching point (blue circle with confidence) if (not self.is_image_mode and - self.template_matching_enabled and - self.tracking_template is not None): + self.templates and + self.current_template_id is not None): # Get template matching position for current frame template_pos = self._get_template_matching_position(self.current_frame) if template_pos: @@ -3911,13 +3898,10 @@ class VideoEditor: def _render_video_worker(self, output_path: str): """Worker method that runs in the render thread""" - render_cap = None try: if not output_path.endswith(".mp4"): output_path += ".mp4" - start_time = time.time() - # Send progress update to main thread self.render_progress_queue.put(("init", "Initializing render...", 0.0, 0.0)) @@ -4728,10 +4712,15 @@ class VideoEditor: self.show_feedback_message(f"Optical flow {'ON' if self.optical_flow_enabled else 'OFF'}") self.save_state() elif key == ord("m"): - # Toggle template matching tracking - self.template_matching_enabled = not self.template_matching_enabled - print(f"DEBUG: Template matching toggled to {self.template_matching_enabled}") - self.show_feedback_message(f"Template matching {'ON' if self.template_matching_enabled else 'OFF'}") + # Clear all templates + if self.templates: + self.templates.clear() + self.current_template_id = None + print("DEBUG: All templates cleared") + self.show_feedback_message("All templates cleared") + else: + print("DEBUG: No templates to clear") + self.show_feedback_message("No templates to clear") self.save_state() elif key == ord("M"): # Shift+M - Toggle multi-scale template matching self.template_matching_full_frame = not self.template_matching_full_frame