#!/usr/bin/env python3 """ Debug Enhanced Positioning System for Video Background Replacement Key debugging areas: 1. UI parameter flow validation 2. Coordinate system debugging 3. Alpha/video alignment verification 4. Canvas placement mathematics 5. Temporal synchronization checks """ import cv2 import numpy as np import os import time import random from pathlib import Path from moviepy.editor import VideoFileClip import logging # Enhanced logging for debugging logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") debug_logger = logging.getLogger("positioning_debug") class PositioningDebugger: """Comprehensive debugging for positioning system""" def __init__(self, debug_dir="debug_positioning"): self.debug_dir = Path(debug_dir) self.debug_dir.mkdir(exist_ok=True, parents=True) self.frame_count = 0 def log_parameters(self, placement_dict): """Log all positioning parameters""" debug_logger.info("=== POSITIONING PARAMETERS ===") debug_logger.info(f"Raw placement dict: {placement_dict}") px = float(placement_dict.get("x", 0.5)) py = float(placement_dict.get("y", 0.75)) ps = float(placement_dict.get("scale", 1.0)) feather_px = int(placement_dict.get("feather", 3)) debug_logger.info(f"Parsed - px: {px}, py: {py}, ps: {ps}, feather: {feather_px}") debug_logger.info(f"Clamped - px: {max(0.0, min(1.0, px))}, py: {max(0.0, min(1.0, py))}") debug_logger.info(f"Scale range - ps: {max(0.3, min(2.0, ps))}") return px, py, ps, feather_px def debug_coordinate_calculation(self, frame_dims, alpha_dims, placement): """Debug coordinate calculations step by step""" hh, ww = frame_dims # Original video dimensions alpha_h, alpha_w = alpha_dims # Alpha video dimensions px, py, ps, feather_px = self.log_parameters(placement) debug_logger.info("=== COORDINATE CALCULATIONS ===") debug_logger.info(f"Original video dims: {ww}x{hh}") debug_logger.info(f"Alpha video dims: {alpha_w}x{alpha_h}") # Scale calculations sw = max(1, int(ww * ps)) sh = max(1, int(hh * ps)) debug_logger.info(f"Scaled subject size: {sw}x{sh} (scale factor: {ps})") # Center calculations cx = int(px * ww) cy = int(py * hh) debug_logger.info(f"Target center: ({cx}, {cy})") # Top-left corner calculations x0 = int(cx - sw // 2) y0 = int(cy - sh // 2) debug_logger.info(f"Top-left corner: ({x0}, {y0})") # Clipping calculations xs0, ys0 = max(0, x0), max(0, y0) xs1, ys1 = min(ww, x0 + sw), min(hh, y0 + sh) debug_logger.info(f"Clipped bounds: ({xs0}, {ys0}) to ({xs1}, {ys1})") if xs1 <= xs0 or ys1 <= ys0: debug_logger.warning("INVALID BOUNDS - Subject will be clipped completely!") # Source region calculations src_x0 = xs0 - x0 src_y0 = ys0 - y0 src_x1 = src_x0 + (xs1 - xs0) src_y1 = src_y0 + (ys1 - ys0) debug_logger.info(f"Source region: ({src_x0}, {src_y0}) to ({src_x1}, {src_y1})") return { 'scaled_size': (sw, sh), 'center': (cx, cy), 'top_left': (x0, y0), 'clipped_dest': (xs0, ys0, xs1, ys1), 'source_region': (src_x0, src_y0, src_x1, src_y1) } def save_debug_frame(self, frame, alpha, composite, frame_idx, coords): """Save debug visualization of frame processing""" debug_path = self.debug_dir / f"frame_{frame_idx:04d}_debug.png" # Create debug visualization h, w = frame.shape[:2] debug_viz = np.zeros((h * 2, w * 2, 3), dtype=np.uint8) # Top-left: Original frame debug_viz[0:h, 0:w] = cv2.cvtColor((frame * 255).astype(np.uint8), cv2.COLOR_RGB2BGR) # Top-right: Alpha mask (as RGB) alpha_viz = cv2.cvtColor((alpha * 255).astype(np.uint8), cv2.COLOR_GRAY2BGR) debug_viz[0:h, w:w*2] = alpha_viz # Bottom-left: Composite debug_viz[h:h*2, 0:w] = cv2.cvtColor((composite * 255).astype(np.uint8), cv2.COLOR_RGB2BGR) # Bottom-right: Coordinate overlay coord_viz = np.zeros((h, w, 3), dtype=np.uint8) # Draw placement indicators cx, cy = coords['center'] sw, sh = coords['scaled_size'] x0, y0 = coords['top_left'] # Draw center cross cv2.line(coord_viz, (cx-10, cy), (cx+10, cy), (0, 255, 0), 2) cv2.line(coord_viz, (cx, cy-10), (cx, cy+10), (0, 255, 0), 2) # Draw scaled subject bounds cv2.rectangle(coord_viz, (x0, y0), (x0 + sw, y0 + sh), (255, 0, 0), 2) # Draw frame bounds cv2.rectangle(coord_viz, (0, 0), (w-1, h-1), (255, 255, 255), 1) debug_viz[h:h*2, w:w*2] = coord_viz # Add text annotations font = cv2.FONT_HERSHEY_SIMPLEX cv2.putText(debug_viz, "Original", (10, 30), font, 0.7, (255, 255, 255), 2) cv2.putText(debug_viz, "Alpha", (w + 10, 30), font, 0.7, (255, 255, 255), 2) cv2.putText(debug_viz, "Composite", (10, h + 30), font, 0.7, (255, 255, 255), 2) cv2.putText(debug_viz, "Coordinates", (w + 10, h + 30), font, 0.7, (255, 255, 255), 2) cv2.imwrite(str(debug_path), debug_viz) debug_logger.info(f"Debug frame saved: {debug_path}") def create_enhanced_composite_function(debugger=None): """Create composite function with enhanced debugging""" def composite_frame_debug(get_frame, t, original_clip, alpha_clip, bg_rgb, placement, feather_px): """Enhanced composite function with comprehensive debugging""" if debugger: debugger.frame_count += 1 debug_logger.info(f"=== PROCESSING FRAME {debugger.frame_count} at t={t:.3f}s ===") # Get original frame frame = get_frame(t).astype(np.float32) / 255.0 hh, ww = frame.shape[:2] # Get alpha with temporal bounds checking alpha_duration = alpha_clip.duration or 0 if alpha_duration > 0: alpha_t = min(t, max(0.0, alpha_duration - 0.01)) else: alpha_t = 0.0 debug_logger.info(f"Alpha lookup: t={t:.3f}, alpha_t={alpha_t:.3f}, alpha_duration={alpha_duration:.3f}") try: a = alpha_clip.get_frame(alpha_t) if a.ndim == 3: a = a[:, :, 0] # Take first channel if RGB a = a.astype(np.float32) / 255.0 debug_logger.info(f"Alpha frame shape: {a.shape}, range: [{a.min():.3f}, {a.max():.3f}]") except Exception as e: debug_logger.error(f"Alpha frame error: {e}") return (bg_rgb * 255).astype(np.uint8) # Extract placement parameters px = max(0.0, min(1.0, float(placement.get("x", 0.5)))) py = max(0.0, min(1.0, float(placement.get("y", 0.75)))) ps = max(0.3, min(2.0, float(placement.get("scale", 1.0)))) # Debug coordinate calculations coords = None if debugger: coords = debugger.debug_coordinate_calculation((hh, ww), a.shape, placement) # Scale subject and alpha sw = max(1, int(ww * ps)) sh = max(1, int(hh * ps)) debug_logger.info(f"Scaling: {ww}x{hh} -> {sw}x{sh} (factor: {ps})") fg_scaled = cv2.resize(frame, (sw, sh), interpolation=cv2.INTER_LINEAR) a_scaled = cv2.resize(a, (sw, sh), interpolation=cv2.INTER_LINEAR) # Create canvas fg_canvas = np.zeros_like(frame, dtype=np.float32) a_canvas = np.zeros((hh, ww), dtype=np.float32) # Calculate placement cx = int(px * ww) cy = int(py * hh) x0 = int(cx - sw // 2) y0 = int(cy - sh // 2) # Bounds checking xs0, ys0 = max(0, x0), max(0, y0) xs1, ys1 = min(ww, x0 + sw), min(hh, y0 + sh) if xs1 <= xs0 or ys1 <= ys0: debug_logger.warning("Subject completely outside frame bounds!") if debugger: debugger.save_debug_frame(frame, a, bg_rgb, debugger.frame_count, coords or {}) return (bg_rgb * 255).astype(np.uint8) # Place scaled subject src_x0 = xs0 - x0 src_y0 = ys0 - y0 src_x1 = src_x0 + (xs1 - xs0) src_y1 = src_y0 + (ys1 - ys0) try: fg_canvas[ys0:ys1, xs0:xs1, :] = fg_scaled[src_y0:src_y1, src_x0:src_x1, :] a_canvas[ys0:ys1, xs0:xs1] = a_scaled[src_y0:src_y1, src_x0:src_x1] except Exception as e: debug_logger.error(f"Canvas placement error: {e}") debug_logger.error(f"Canvas region: [{ys0}:{ys1}, {xs0}:{xs1}]") debug_logger.error(f"Source region: [{src_y0}:{src_y1}, {src_x0}:{src_x1}]") if debugger: debugger.save_debug_frame(frame, a, bg_rgb, debugger.frame_count, coords or {}) return (bg_rgb * 255).astype(np.uint8) # Apply feathering if feather_px > 0: k = (feather_px * 2 + 1) a_canvas = cv2.GaussianBlur(a_canvas, (k, k), feather_px) # Composite a3 = a_canvas[:, :, None] comp = a3 * fg_canvas + (1.0 - a3) * bg_rgb result = np.clip(comp * 255, 0, 255).astype(np.uint8) # Save debug frame periodically if debugger and debugger.frame_count % 30 == 1: # Every ~1 second at 30fps debugger.save_debug_frame(frame, a_canvas, comp, debugger.frame_count, coords or {}) return result return composite_frame_debug def validate_ui_parameter_flow(): """Test function to validate UI parameters reach processing correctly""" # Simulate UI parameter values test_placements = [ {"x": 0.5, "y": 0.5, "scale": 1.0, "feather": 3}, # Center {"x": 0.2, "y": 0.8, "scale": 0.7, "feather": 5}, # Bottom-left, smaller {"x": 0.8, "y": 0.3, "scale": 1.5, "feather": 1}, # Top-right, larger ] debugger = PositioningDebugger() for i, placement in enumerate(test_placements): print(f"\n=== TEST CASE {i+1} ===") # Simulate frame dimensions frame_dims = (720, 1280) # H, W alpha_dims = (720, 1280) coords = debugger.debug_coordinate_calculation(frame_dims, alpha_dims, placement) print(f"Expected center: {coords['center']}") print(f"Scaled size: {coords['scaled_size']}") print(f"Placement bounds: {coords['clipped_dest']}") def create_debug_enhanced_process_video_main(): """Enhanced version of process_video_main with debugging""" def process_video_main_debug( video_path: str, background_path: str = None, trim_duration: float = None, crf: int = 18, preserve_audio_flag: bool = True, placement: dict = None, use_chunked_processing: bool = False, enable_debug: bool = True, progress=None, ): """ Enhanced process_video_main with comprehensive positioning debugging """ debugger = PositioningDebugger() if enable_debug else None messages = [] try: if debugger: debug_logger.info("=== STARTING DEBUG SESSION ===") debug_logger.info(f"Video: {video_path}") debug_logger.info(f"Background: {background_path}") debug_logger.info(f"Placement: {placement}") # Validate placement parameters early placement = placement or {} if debugger: debugger.log_parameters(placement) # ... [Previous initialization code remains the same] ... # Enhanced composite function with debugging def composite_frame(get_frame, t): return create_enhanced_composite_function(debugger)( get_frame, t, original_clip, alpha_clip, bg_rgb, placement, int(placement.get("feather", 3)) ) # ... [Rest of processing remains the same] ... if debugger: debug_logger.info("=== DEBUG SESSION COMPLETE ===") debug_logger.info(f"Debug files saved to: {debugger.debug_dir}") return result_path, "\n".join(messages) except Exception as e: if debugger: debug_logger.error(f"Processing failed: {e}") raise return process_video_main_debug # Quick fix suggestions for immediate testing def quick_positioning_fixes(): """ Quick fixes to test for common positioning issues """ fixes = { "coordinate_system_flip": { "description": "Test if Y coordinate should be flipped", "change": "cy = int((1.0 - py) * hh) # Flip Y coordinate", "original": "cy = int(py * hh)" }, "anchor_point_adjustment": { "description": "Test different anchor points for scaling", "change": """ # Try bottom-center anchor instead of center-center x0 = int(cx - sw // 2) # Keep X centered y0 = int(cy - sh) # Anchor at bottom """, "original": """ x0 = int(cx - sw // 2) y0 = int(cy - sh // 2) """ }, "alpha_scaling_sync": { "description": "Ensure alpha and frame scaling are synchronized", "change": """ # Force exact same dimensions if a.shape != (hh, ww): a = cv2.resize(a, (ww, hh), interpolation=cv2.INTER_LINEAR) """, "original": "# No explicit resize check" }, "ui_parameter_validation": { "description": "Add parameter validation and logging", "change": """ px = float(placement.get("x", 0.5)) py = float(placement.get("y", 0.75)) ps = float(placement.get("scale", 1.0)) print(f"DEBUG: px={px}, py={py}, ps={ps}") print(f"DEBUG: cx={px*ww}, cy={py*hh}") """, "original": "# No debug logging" } } return fixes if __name__ == "__main__": print("=== Video Background Replacement - Positioning Debug ===") print("\n1. Running UI parameter validation...") validate_ui_parameter_flow() print("\n2. Available quick fixes:") fixes = quick_positioning_fixes() for fix_name, fix_info in fixes.items(): print(f"\n{fix_name.upper()}:") print(f" Description: {fix_info['description']}") print(f" Change: {fix_info['change']}") print("\n3. Debug system ready!") print(" - Use PositioningDebugger class in your main pipeline") print(" - Enable debug mode: enable_debug=True") print(" - Check debug_positioning/ folder for output")