Spaces:
Runtime error
Runtime error
File size: 12,618 Bytes
aa305e2 2db60a3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
import os
import tempfile
import subprocess
import matplotlib.pyplot as plt
import pandas as pd
import cv2
import numpy as np
from tqdm import tqdm
from persistence import load_detection_data
def create_frame_data(json_path):
"""Create frame-by-frame detection data for visualization."""
try:
data = load_detection_data(json_path)
if not data:
print("No data loaded from JSON file")
return None
if "video_metadata" not in data or "frame_detections" not in data:
print("Invalid JSON structure: missing required fields")
return None
# Extract video metadata
metadata = data["video_metadata"]
if "fps" not in metadata or "total_frames" not in metadata:
print("Invalid metadata: missing fps or total_frames")
return None
fps = metadata["fps"]
total_frames = metadata["total_frames"]
# Create frame data
frame_counts = {}
for frame_data in data["frame_detections"]:
if "frame" not in frame_data or "objects" not in frame_data:
continue # Skip invalid frame data
frame_num = frame_data["frame"]
frame_counts[frame_num] = len(frame_data["objects"])
# Fill in missing frames with 0 detections
for frame in range(total_frames):
if frame not in frame_counts:
frame_counts[frame] = 0
if not frame_counts:
print("No valid frame data found")
return None
# Convert to DataFrame
df = pd.DataFrame(list(frame_counts.items()), columns=["frame", "detections"])
df["timestamp"] = df["frame"] / fps
return df, metadata
except Exception as e:
print(f"Error creating frame data: {str(e)}")
import traceback
traceback.print_exc()
return None
def generate_frame_image(df, frame_num, temp_dir, max_y):
"""Generate and save a single frame of the visualization."""
# Set the style to dark background
plt.style.use('dark_background')
# Set global font to monospace
plt.rcParams['font.family'] = 'monospace'
plt.rcParams['font.monospace'] = ['DejaVu Sans Mono']
plt.figure(figsize=(10, 6))
# Plot data up to current frame
current_data = df[df['frame'] <= frame_num]
plt.plot(df['frame'], df['detections'], color='#1a1a1a', alpha=0.5) # Darker background line
plt.plot(current_data['frame'], current_data['detections'], color='#00ff41') # Matrix green
# Add vertical line for current position
plt.axvline(x=frame_num, color='#ff0000', linestyle='-', alpha=0.7) # Keep red for position
# Set consistent axes
plt.xlim(0, len(df) - 1)
plt.ylim(0, max_y * 1.1) # Add 10% padding
# Add labels with Matrix green color
plt.title(f'FRAME {frame_num:04d} - DETECTIONS OVER TIME', color='#00ff41', pad=20)
plt.xlabel('FRAME NUMBER', color='#00ff41')
plt.ylabel('NUMBER OF DETECTIONS', color='#00ff41')
# Add current stats in Matrix green with monospace formatting
current_detections = df[df['frame'] == frame_num]['detections'].iloc[0]
plt.text(0.02, 0.98, f'CURRENT DETECTIONS: {current_detections:02d}',
transform=plt.gca().transAxes, verticalalignment='top',
color='#00ff41', family='monospace')
# Style the grid and ticks
plt.grid(True, color='#1a1a1a', linestyle='-', alpha=0.3)
plt.tick_params(colors='#00ff41')
# Save frame
frame_path = os.path.join(temp_dir, f'frame_{frame_num:05d}.png')
plt.savefig(frame_path, bbox_inches='tight', dpi=100, facecolor='black', edgecolor='none')
plt.close()
return frame_path
def generate_gauge_frame(df, frame_num, temp_dir, detect_keyword="OBJECT"):
"""Generate a modern square-style binary gauge visualization frame."""
# Set the style to dark background
plt.style.use('dark_background')
# Set global font to monospace
plt.rcParams['font.family'] = 'monospace'
plt.rcParams['font.monospace'] = ['DejaVu Sans Mono']
# Create figure with 16:9 aspect ratio
plt.figure(figsize=(16, 9))
# Get current detection state
current_detections = df[df['frame'] == frame_num]['detections'].iloc[0]
has_detection = current_detections > 0
# Create a simple gauge visualization
plt.axis('off')
# Set colors
if has_detection:
color = '#00ff41' # Matrix green for YES
status = 'YES'
indicator_pos = 0.8 # Right position
else:
color = '#ff0000' # Red for NO
status = 'NO'
indicator_pos = 0.2 # Left position
# Draw background rectangle
background = plt.Rectangle((0.1, 0.3), 0.8, 0.2,
facecolor='#1a1a1a',
edgecolor='#333333',
linewidth=2)
plt.gca().add_patch(background)
# Draw indicator
indicator_width = 0.05
indicator = plt.Rectangle((indicator_pos - indicator_width/2, 0.25),
indicator_width, 0.3,
facecolor=color,
edgecolor=None)
plt.gca().add_patch(indicator)
# Add tick marks
tick_positions = [0.2, 0.5, 0.8] # NO, CENTER, YES
for x in tick_positions:
plt.plot([x, x], [0.3, 0.5], color='#444444', linewidth=2)
# Add YES/NO labels
plt.text(0.8, 0.2, 'YES', color='#00ff41', fontsize=14,
ha='center', va='center', family='monospace')
plt.text(0.2, 0.2, 'NO', color='#ff0000', fontsize=14,
ha='center', va='center', family='monospace')
# Add status box at top with detection keyword
plt.text(0.5, 0.8, f'{detect_keyword.upper()} DETECTED?', color=color,
fontsize=16, ha='center', va='center', family='monospace',
bbox=dict(facecolor='#1a1a1a',
edgecolor=color,
linewidth=2,
pad=10))
# Add frame counter at bottom
plt.text(0.5, 0.1, f'FRAME: {frame_num:04d}', color='#00ff41',
fontsize=14, ha='center', va='center', family='monospace')
# Add subtle grid lines for depth
for x in np.linspace(0.2, 0.8, 7):
plt.plot([x, x], [0.3, 0.5], color='#222222', linewidth=1, zorder=0)
# Add glow effect to indicator
for i in range(3):
glow = plt.Rectangle((indicator_pos - (indicator_width + i*0.01)/2,
0.25 - i*0.01),
indicator_width + i*0.01,
0.3 + i*0.02,
facecolor=color,
alpha=0.1/(i+1))
plt.gca().add_patch(glow)
# Set consistent plot limits
plt.xlim(0, 1)
plt.ylim(0, 1)
# Save frame with 16:9 aspect ratio
frame_path = os.path.join(temp_dir, f'gauge_{frame_num:05d}.png')
plt.savefig(frame_path,
bbox_inches='tight',
dpi=100,
facecolor='black',
edgecolor='none',
pad_inches=0)
plt.close()
return frame_path
def create_video_visualization(json_path, style="timeline"):
"""Create a video visualization of the detection data."""
try:
if not json_path:
return None, "No JSON file provided"
if not os.path.exists(json_path):
return None, f"File not found: {json_path}"
# Load and process data
result = create_frame_data(json_path)
if result is None:
return None, "Failed to load detection data from JSON file"
frame_data, metadata = result
if len(frame_data) == 0:
return None, "No frame data found in JSON file"
total_frames = metadata["total_frames"]
detect_keyword = metadata.get("detect_keyword", "OBJECT") # Get the detection keyword
# Create temporary directory for frames
with tempfile.TemporaryDirectory() as temp_dir:
max_y = frame_data['detections'].max()
# Generate each frame
print("Generating frames...")
frame_paths = []
with tqdm(total=total_frames, desc="Generating frames") as pbar:
for frame in range(total_frames):
try:
if style == "gauge":
frame_path = generate_gauge_frame(frame_data, frame, temp_dir, detect_keyword)
else: # default to timeline
frame_path = generate_frame_image(frame_data, frame, temp_dir, max_y)
if frame_path and os.path.exists(frame_path):
frame_paths.append(frame_path)
else:
print(f"Warning: Failed to generate frame {frame}")
pbar.update(1)
except Exception as e:
print(f"Error generating frame {frame}: {str(e)}")
continue
if not frame_paths:
return None, "Failed to generate any frames"
# Create output video path
output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "outputs")
os.makedirs(output_dir, exist_ok=True)
output_video = os.path.join(output_dir, f"detection_visualization_{style}.mp4")
# Create temp output path
base, ext = os.path.splitext(output_video)
temp_output = f"{base}_temp{ext}"
# First pass: Create video with OpenCV VideoWriter
print("Creating initial video...")
# Get frame size from first image
first_frame = cv2.imread(frame_paths[0])
height, width = first_frame.shape[:2]
out = cv2.VideoWriter(
temp_output,
cv2.VideoWriter_fourcc(*"mp4v"),
metadata["fps"],
(width, height)
)
with tqdm(total=total_frames, desc="Creating video") as pbar: # Use total_frames here too
for frame_path in frame_paths:
frame = cv2.imread(frame_path)
out.write(frame)
pbar.update(1)
out.release()
# Second pass: Convert to web-compatible format
print("Converting to web format...")
try:
subprocess.run(
[
"ffmpeg",
"-y",
"-i",
temp_output,
"-c:v",
"libx264",
"-preset",
"medium",
"-crf",
"23",
"-movflags",
"+faststart", # Better web playback
"-loglevel",
"error",
output_video,
],
check=True,
)
os.remove(temp_output) # Remove the temporary file
if not os.path.exists(output_video):
print(f"Warning: FFmpeg completed but output file not found at {output_video}")
return None, "Failed to create video"
# Return video path and stats
stats = f"""Video Stats:
FPS: {metadata['fps']}
Total Frames: {metadata['total_frames']}
Duration: {metadata['duration_sec']:.2f} seconds
Max Detections in a Frame: {frame_data['detections'].max()}
Average Detections per Frame: {frame_data['detections'].mean():.2f}"""
return output_video, stats
except subprocess.CalledProcessError as e:
print(f"Error running FFmpeg: {str(e)}")
if os.path.exists(temp_output):
os.remove(temp_output)
return None, f"Error creating visualization: {str(e)}"
except Exception as e:
print(f"Error creating video visualization: {str(e)}")
import traceback
traceback.print_exc()
return None, f"Error creating visualization: {str(e)}" |