Create debug_positioning.py
Browse files- debug_positioning.py +237 -0
debug_positioning.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Quick test script to validate positioning fixes before full video processing.
|
| 4 |
+
Run this to verify coordinate calculations are working correctly.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import cv2
|
| 9 |
+
import os
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
def test_coordinate_calculations():
|
| 13 |
+
"""Test coordinate calculations with various placement scenarios"""
|
| 14 |
+
|
| 15 |
+
print("=== COORDINATE CALCULATION TESTS ===\n")
|
| 16 |
+
|
| 17 |
+
# Simulate video dimensions
|
| 18 |
+
video_dims = [(720, 1280), (1080, 1920), (480, 640)] # (H, W)
|
| 19 |
+
|
| 20 |
+
test_placements = [
|
| 21 |
+
{"name": "Center", "x": 0.5, "y": 0.5, "scale": 1.0},
|
| 22 |
+
{"name": "Bottom Center", "x": 0.5, "y": 0.75, "scale": 1.0},
|
| 23 |
+
{"name": "Top Right", "x": 0.8, "y": 0.2, "scale": 0.7},
|
| 24 |
+
{"name": "Bottom Left Small", "x": 0.2, "y": 0.9, "scale": 0.5},
|
| 25 |
+
{"name": "Large Center", "x": 0.5, "y": 0.5, "scale": 1.5},
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
for h, w in video_dims:
|
| 29 |
+
print(f"Video Resolution: {w}x{h}")
|
| 30 |
+
print("-" * 40)
|
| 31 |
+
|
| 32 |
+
for test in test_placements:
|
| 33 |
+
px, py, ps = test["x"], test["y"], test["scale"]
|
| 34 |
+
|
| 35 |
+
# Calculate coordinates (same logic as app.py)
|
| 36 |
+
sw = max(1, round(w * ps))
|
| 37 |
+
sh = max(1, round(h * ps))
|
| 38 |
+
cx = round(px * w)
|
| 39 |
+
cy = round(py * h)
|
| 40 |
+
x0 = cx - sw // 2
|
| 41 |
+
y0 = cy - sh // 2
|
| 42 |
+
|
| 43 |
+
# Bounds checking
|
| 44 |
+
xs0 = max(0, x0)
|
| 45 |
+
ys0 = max(0, y0)
|
| 46 |
+
xs1 = min(w, x0 + sw)
|
| 47 |
+
ys1 = min(h, y0 + sh)
|
| 48 |
+
|
| 49 |
+
# Check validity
|
| 50 |
+
valid = xs1 > xs0 and ys1 > ys0
|
| 51 |
+
clipped = x0 < 0 or y0 < 0 or (x0 + sw) > w or (y0 + sh) > h
|
| 52 |
+
|
| 53 |
+
print(f" {test['name']:15} | Center: ({cx:4d}, {cy:4d}) | Size: {sw:4d}x{sh:4d} | Valid: {valid} | Clipped: {clipped}")
|
| 54 |
+
|
| 55 |
+
if not valid:
|
| 56 |
+
print(f" ERROR: Invalid bounds ({xs0}, {ys0}) to ({xs1}, {ys1})")
|
| 57 |
+
|
| 58 |
+
print()
|
| 59 |
+
|
| 60 |
+
def create_test_visualization():
|
| 61 |
+
"""Create visual test to verify positioning logic"""
|
| 62 |
+
|
| 63 |
+
print("=== CREATING POSITIONING VISUALIZATION ===\n")
|
| 64 |
+
|
| 65 |
+
# Test canvas
|
| 66 |
+
canvas_w, canvas_h = 800, 600
|
| 67 |
+
canvas = np.zeros((canvas_h, canvas_w, 3), dtype=np.uint8)
|
| 68 |
+
canvas.fill(50) # Dark gray background
|
| 69 |
+
|
| 70 |
+
# Test subject dimensions (simulating scaled subject)
|
| 71 |
+
subject_w, subject_h = 200, 300
|
| 72 |
+
|
| 73 |
+
test_positions = [
|
| 74 |
+
{"x": 0.2, "y": 0.3, "color": (255, 0, 0)}, # Red - top left
|
| 75 |
+
{"x": 0.8, "y": 0.3, "color": (0, 255, 0)}, # Green - top right
|
| 76 |
+
{"x": 0.5, "y": 0.5, "color": (0, 0, 255)}, # Blue - center
|
| 77 |
+
{"x": 0.3, "y": 0.8, "color": (255, 255, 0)}, # Cyan - bottom left
|
| 78 |
+
{"x": 0.7, "y": 0.8, "color": (255, 0, 255)}, # Magenta - bottom right
|
| 79 |
+
]
|
| 80 |
+
|
| 81 |
+
for i, pos in enumerate(test_positions):
|
| 82 |
+
px, py = pos["x"], pos["y"]
|
| 83 |
+
color = pos["color"]
|
| 84 |
+
|
| 85 |
+
# Calculate placement (same as app.py logic)
|
| 86 |
+
cx = round(px * canvas_w)
|
| 87 |
+
cy = round(py * canvas_h)
|
| 88 |
+
x0 = cx - subject_w // 2
|
| 89 |
+
y0 = cy - subject_h // 2
|
| 90 |
+
|
| 91 |
+
# Bounds
|
| 92 |
+
xs0 = max(0, x0)
|
| 93 |
+
ys0 = max(0, y0)
|
| 94 |
+
xs1 = min(canvas_w, x0 + subject_w)
|
| 95 |
+
ys1 = min(canvas_h, y0 + subject_h)
|
| 96 |
+
|
| 97 |
+
if xs1 > xs0 and ys1 > ys0:
|
| 98 |
+
# Draw subject rectangle
|
| 99 |
+
cv2.rectangle(canvas, (xs0, ys0), (xs1-1, ys1-1), color, 2)
|
| 100 |
+
|
| 101 |
+
# Draw center cross
|
| 102 |
+
cv2.line(canvas, (cx-10, cy), (cx+10, cy), color, 2)
|
| 103 |
+
cv2.line(canvas, (cx, cy-10), (cx, cy+10), color, 2)
|
| 104 |
+
|
| 105 |
+
# Add label
|
| 106 |
+
cv2.putText(canvas, f"({px:.1f}, {py:.1f})", (cx-30, cy-20),
|
| 107 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.4, color, 1)
|
| 108 |
+
|
| 109 |
+
print(f"Position {i+1}: ({px:.1f}, {py:.1f}) -> Center ({cx}, {cy}) -> Rect ({xs0}, {ys0}) to ({xs1}, {ys1})")
|
| 110 |
+
else:
|
| 111 |
+
print(f"Position {i+1}: ({px:.1f}, {py:.1f}) -> OUT OF BOUNDS")
|
| 112 |
+
|
| 113 |
+
# Save test image
|
| 114 |
+
output_path = "positioning_test_visualization.png"
|
| 115 |
+
cv2.imwrite(output_path, canvas)
|
| 116 |
+
print(f"\nVisualization saved: {output_path}")
|
| 117 |
+
|
| 118 |
+
return output_path
|
| 119 |
+
|
| 120 |
+
def test_ui_parameter_flow():
|
| 121 |
+
"""Test that UI slider values correctly flow through to positioning"""
|
| 122 |
+
|
| 123 |
+
print("=== UI PARAMETER FLOW TEST ===\n")
|
| 124 |
+
|
| 125 |
+
# Simulate Gradio slider values
|
| 126 |
+
gradio_values = {
|
| 127 |
+
"place_x": 0.7, # 70% from left
|
| 128 |
+
"place_y": 0.3, # 30% from top
|
| 129 |
+
"place_scale": 1.2, # 120% scale
|
| 130 |
+
"place_feather": 5 # 5px feather
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
# Simulate placement dict creation (from Gradio interface)
|
| 134 |
+
placement = {
|
| 135 |
+
"x": gradio_values["place_x"],
|
| 136 |
+
"y": gradio_values["place_y"],
|
| 137 |
+
"scale": gradio_values["place_scale"],
|
| 138 |
+
"feather": gradio_values["place_feather"]
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
print("Gradio slider values:")
|
| 142 |
+
for key, val in gradio_values.items():
|
| 143 |
+
print(f" {key}: {val}")
|
| 144 |
+
|
| 145 |
+
print(f"\nPlacement dict: {placement}")
|
| 146 |
+
|
| 147 |
+
# Test parameter extraction (same as app.py)
|
| 148 |
+
px = max(0.0, min(1.0, float(placement.get("x", 0.5))))
|
| 149 |
+
py = max(0.0, min(1.0, float(placement.get("y", 0.75))))
|
| 150 |
+
ps = max(0.3, min(2.0, float(placement.get("scale", 1.0))))
|
| 151 |
+
feather_px = max(0, min(50, int(placement.get("feather", 3))))
|
| 152 |
+
|
| 153 |
+
print("\nExtracted and clamped values:")
|
| 154 |
+
print(f" px (x): {px}")
|
| 155 |
+
print(f" py (y): {py}")
|
| 156 |
+
print(f" ps (scale): {ps}")
|
| 157 |
+
print(f" feather_px: {feather_px}")
|
| 158 |
+
|
| 159 |
+
# Verify no unexpected transformations
|
| 160 |
+
expected_x = gradio_values["place_x"]
|
| 161 |
+
expected_y = gradio_values["place_y"]
|
| 162 |
+
expected_scale = gradio_values["place_scale"]
|
| 163 |
+
expected_feather = gradio_values["place_feather"]
|
| 164 |
+
|
| 165 |
+
if abs(px - expected_x) < 0.001 and abs(py - expected_y) < 0.001:
|
| 166 |
+
print("\n✅ Parameter flow is correct!")
|
| 167 |
+
else:
|
| 168 |
+
print(f"\n❌ Parameter flow issue!")
|
| 169 |
+
print(f" Expected x: {expected_x}, got: {px}")
|
| 170 |
+
print(f" Expected y: {expected_y}, got: {py}")
|
| 171 |
+
|
| 172 |
+
return px == expected_x and py == expected_y
|
| 173 |
+
|
| 174 |
+
def test_scaling_calculations():
|
| 175 |
+
"""Test scaling calculations for different scenarios"""
|
| 176 |
+
|
| 177 |
+
print("=== SCALING CALCULATION TESTS ===\n")
|
| 178 |
+
|
| 179 |
+
base_dims = [(720, 1280), (1080, 1920)]
|
| 180 |
+
scale_factors = [0.5, 0.75, 1.0, 1.25, 1.5]
|
| 181 |
+
|
| 182 |
+
for h, w in base_dims:
|
| 183 |
+
print(f"Base dimensions: {w}x{h}")
|
| 184 |
+
print("-" * 30)
|
| 185 |
+
|
| 186 |
+
for scale in scale_factors:
|
| 187 |
+
# Test both int() and round() approaches
|
| 188 |
+
sw_int = max(1, int(w * scale))
|
| 189 |
+
sh_int = max(1, int(h * scale))
|
| 190 |
+
sw_round = max(1, round(w * scale))
|
| 191 |
+
sh_round = max(1, round(h * scale))
|
| 192 |
+
|
| 193 |
+
print(f" Scale {scale:4.2f} | int(): {sw_int:4d}x{sh_int:4d} | round(): {sw_round:4d}x{sh_round:4d}")
|
| 194 |
+
|
| 195 |
+
if sw_int != sw_round or sh_int != sh_round:
|
| 196 |
+
print(f" ^ Different results! Using round() is more accurate.")
|
| 197 |
+
|
| 198 |
+
print()
|
| 199 |
+
|
| 200 |
+
def main():
|
| 201 |
+
"""Run all positioning tests"""
|
| 202 |
+
|
| 203 |
+
print("🎬 VIDEO BACKGROUND REPLACEMENT - POSITIONING TESTS")
|
| 204 |
+
print("=" * 60)
|
| 205 |
+
|
| 206 |
+
# Test 1: Coordinate calculations
|
| 207 |
+
test_coordinate_calculations()
|
| 208 |
+
|
| 209 |
+
# Test 2: UI parameter flow
|
| 210 |
+
ui_flow_ok = test_ui_parameter_flow()
|
| 211 |
+
|
| 212 |
+
# Test 3: Scaling calculations
|
| 213 |
+
test_scaling_calculations()
|
| 214 |
+
|
| 215 |
+
# Test 4: Visual verification
|
| 216 |
+
viz_path = create_test_visualization()
|
| 217 |
+
|
| 218 |
+
# Summary
|
| 219 |
+
print("=" * 60)
|
| 220 |
+
print("TEST SUMMARY:")
|
| 221 |
+
print(f"✅ Coordinate calculations: Tested")
|
| 222 |
+
print(f"{'✅' if ui_flow_ok else '❌'} UI parameter flow: {'OK' if ui_flow_ok else 'Issues detected'}")
|
| 223 |
+
print(f"✅ Scaling calculations: Tested")
|
| 224 |
+
print(f"✅ Visual verification: {viz_path}")
|
| 225 |
+
|
| 226 |
+
if not ui_flow_ok:
|
| 227 |
+
print("\n⚠️ UI parameter flow issues detected!")
|
| 228 |
+
print(" Check that Gradio slider values are correctly passed to placement dict.")
|
| 229 |
+
|
| 230 |
+
print(f"\n🔧 Next steps:")
|
| 231 |
+
print("1. Review coordinate calculation results above")
|
| 232 |
+
print("2. Check the generated visualization image")
|
| 233 |
+
print("3. Apply the enhanced composite_frame function to your app.py")
|
| 234 |
+
print("4. Test with a small video file first")
|
| 235 |
+
|
| 236 |
+
if __name__ == "__main__":
|
| 237 |
+
main()
|