Buckets:

Rishik001's picture
download
raw
7.01 kB
"""Extract (T, 17, 3) skeletons + dense frame labels for the 371 FS-Spot clips.
Streams each clip's frames out of its parent broadcast, runs YOLO pose, picks the
skater with a continuity tracker, and writes one npz per clip:
skeleton (T, 17, 3) COCO-17, x/width and y/height normalized, + confidence
frame_labels (T,) dense int labels over SPOT_TAXONOMY (0 = NONE)
box (T, 4) the tracked skater's box, xyxy in pixels
det_count (T,) persons detected per frame -- crowd/pan diagnostic
Run: python3 -m temporal_scripts.fs_spot_extract [--limit N] [--broadcast NAME]
Only broadcasts already present in data/fs_spot/videos are processed, so this can
run alongside the download and be re-run to pick up newly-arrived videos. Clips
whose npz already exists are skipped.
"""
from __future__ import annotations
import argparse
import sys
import time
from pathlib import Path
import numpy as np
from temporal_scripts.fs_spot_data import (
OUT_DIR,
VIDEO_DIR,
Clip,
available_broadcasts,
build_index,
find_video,
pick_skater,
stream_window,
)
# ffmpeg pre-scales to this, matching IMGSZ's aspect so YOLO's letterbox is a
# no-op and the frame is resampled once rather than twice. Keypoints are
# normalized by these dims, so [0,1] coords are resolution-independent.
#
# IMGSZ tracks config.py's yolo_imgsz (384). Measured recall ON labelled
# jump/spin frames (the ones that carry the signal), 2 clips, this data:
# yolo11n @ 384 192 fps 85.98% <- current: fast PoC setting
# yolo11m @ 384 191 fps 88.41% (bigger model is ~free -- GPU isn't saturated)
# yolo11n @ 640 148 fps 92.07%
# yolo11m @ 640 130 fps 96.34%
# Resolution dominates model size here. At 384 roughly 1 in 7 element frames has
# no detection at all and lands as a zero row for interpolate_missing to fill --
# concentrated in takeoff/landing motion blur, i.e. exactly the boundaries a TAS
# model must learn. Raise IMGSZ to 640 (and WIDTH/HEIGHT to 640x360) before
# trusting any final number.
WIDTH, HEIGHT = 384, 216
IMGSZ = 384
BATCH = 64
def extract_clip(clip: Clip, model, chunk: int = 128) -> dict:
"""Run pose over one clip and return arrays to save."""
skel = np.zeros((clip.num_frames, 17, 3), dtype=np.float32)
boxes = np.zeros((clip.num_frames, 4), dtype=np.float32)
dets = np.zeros(clip.num_frames, dtype=np.int16)
video = find_video(clip.broadcast)
if video is None:
raise FileNotFoundError(f"no video on disk for broadcast {clip.broadcast}")
prev_center: np.ndarray | None = None
t = 0
for frames in stream_window(
video, clip.start, clip.num_frames, WIDTH, HEIGHT, chunk=chunk, scale=(WIDTH, HEIGHT)
):
results = []
for i in range(0, len(frames), BATCH):
results.extend(
model.predict(
list(frames[i : i + BATCH]),
device=0, verbose=False, imgsz=IMGSZ, half=True, batch=BATCH,
)
)
for r in results:
if t >= clip.num_frames:
break
if r.boxes is None or len(r.boxes) == 0 or r.keypoints is None:
# No person found: leave zeros and drop the track, so the next
# detection isn't yanked toward a stale centre from before a cut.
prev_center = None
t += 1
continue
b = r.boxes.xyxy.cpu().numpy()
cf = r.boxes.conf.cpu().numpy()
idx = pick_skater(b, cf, prev_center)
prev_center = np.array([(b[idx, 0] + b[idx, 2]) / 2, (b[idx, 1] + b[idx, 3]) / 2])
k = r.keypoints.data[idx].cpu().numpy().astype(np.float32) # (17,3) px x, px y, conf
k[:, 0] /= WIDTH
k[:, 1] /= HEIGHT
skel[t] = k
boxes[t] = b[idx]
dets[t] = len(b)
t += 1
if t != clip.num_frames:
raise RuntimeError(f"{clip.name}: decoded {t} frames, expected {clip.num_frames}")
return {
"skeleton": skel,
"frame_labels": clip.frame_labels(),
"box": boxes,
"det_count": dets,
"transcript": np.array(clip.transcript, dtype=object),
"element_takeoff": np.array([e.takeoff for e in clip.elements], dtype=np.int32),
"element_landing": np.array([e.landing for e in clip.elements], dtype=np.int32),
"element_label": np.array([e.label for e in clip.elements], dtype=object),
"fps": 25.0,
"broadcast": clip.broadcast,
"abs_start": clip.start,
}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--limit", type=int, default=None, help="process at most N clips")
ap.add_argument("--broadcast", type=str, default=None, help="only this broadcast")
ap.add_argument("--weights", type=str, default="yolo11n-pose.pt")
ap.add_argument("--shard", type=str, default=None, help="i/N -- process only shard i of N")
args = ap.parse_args()
from ultralytics import YOLO
OUT_DIR.mkdir(parents=True, exist_ok=True)
clips = build_index()
have = available_broadcasts()
todo = [
c for c in clips.values()
if c.broadcast in have
and (args.broadcast is None or c.broadcast == args.broadcast)
and not (OUT_DIR / f"{c.name}.npz").exists()
]
todo.sort(key=lambda c: (c.broadcast, c.start))
if args.shard:
i, n = (int(x) for x in args.shard.split("/"))
todo = [c for j, c in enumerate(todo) if j % n == i]
if args.limit:
todo = todo[: args.limit]
print(f"broadcasts downloaded : {len(have)}/11 {sorted(have)}")
print(f"clips to process : {len(todo)} (done: {len(list(OUT_DIR.glob('*.npz')))}/371)")
if not todo:
print("nothing to do")
return 0
model = YOLO(args.weights)
model.to("cuda:0")
t0 = time.time()
frames_done = 0
for i, clip in enumerate(todo, 1):
ts = time.time()
try:
out = extract_clip(clip, model)
except Exception as exc:
print(f"[{i}/{len(todo)}] FAIL {clip.name}: {exc}", flush=True)
continue
np.savez_compressed(OUT_DIR / f"{clip.name}.npz", **out)
frames_done += clip.num_frames
dt = time.time() - ts
found = float((out["skeleton"][:, :, 2] > 0).any(axis=1).mean())
elapsed = time.time() - t0
eta = (sum(c.num_frames for c in todo[i:]) / (frames_done / elapsed)) / 60 if frames_done else 0
print(
f"[{i}/{len(todo)}] {clip.name[:52]:52} T={clip.num_frames:5d} "
f"{clip.num_frames/dt:6.1f} fps pose_found={found*100:5.1f}% "
f"elems={len(clip.elements)} ETA {eta:5.1f}m",
flush=True,
)
print(f"\ndone in {(time.time()-t0)/60:.1f} min | {len(list(OUT_DIR.glob('*.npz')))}/371 clips extracted")
return 0
if __name__ == "__main__":
sys.exit(main())

Xet Storage Details

Size:
7.01 kB
·
Xet hash:
366fd19348478106f3a46549dcc46b8776fe9de472eb1fbde12f4c6c9c4e9f33

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.