Skip to content
Back to skills

Event Anchored Frame Sync

ASecurity

Align low-Hz sensor data to high-fps video by anchoring a named event (e.g. ball_snap) to a known frame index and converting time offsets via fps

  • 61 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 12, 2026
toolspython

Works with

  • cli

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add wenmin-wu/ds-skills --skill event-anchored-frame-sync --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Event Anchored Frame Sync?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Event Anchored Frame Sync
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/wenmin-wu-event-anchored-frame-sync/badge)](https://www.skillsdirectory.com/skills/wenmin-wu-event-anchored-frame-sync)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: timeseries-event-anchored-frame-sync
description: Align low-Hz sensor data to high-fps video by anchoring a named event (e.g. ball_snap) to a known frame index and converting time offsets via fps
---

## Overview

Sensor and video streams are rarely recorded with perfectly synchronized clocks. In the NFL Helmet Assignment competition, player tracking (NGS) is 10Hz with wall-clock timestamps while video is ~60fps counted from frame 0 — you cannot join them by timestamp. The trick is that both streams share a discrete labeled event: the `ball_snap`. If you know which video frame contains the snap (a constant per dataset), you can anchor the NGS row for `event == 'ball_snap'` to that frame, compute a `snap_offset` in seconds, multiply by fps, and derive `est_frame` for every NGS row. Result: a deterministic join key between the two streams with millisecond-level accuracy.

## Quick Start

```python
def add_est_frame(tracks, fps=59.94, snap_frame=10):
    tracks = tracks.copy()
    tracks['game_play'] = (tracks['gameKey'].astype(str) + '_'
                           + tracks['playID'].astype(str).str.zfill(6))
    tracks['time'] = pd.to_datetime(tracks['time'])

    # Anchor: for each play, find the wall-clock time of the ball_snap row
    snap_time = (tracks.query('event == "ball_snap"')
                 .groupby('game_play')['time'].first().to_dict())
    tracks['snap'] = tracks['game_play'].map(snap_time)

    # Offset from snap in seconds, then convert to frame index
    tracks['snap_offset'] = (
        (tracks['time'] - tracks['snap']).dt.total_seconds()
    )
    tracks['est_frame'] = (
        (tracks['snap_offset'] * fps) + snap_frame
    ).round().astype(int)
    return tracks
```

## Workflow

1. Identify a discrete event that appears in both streams (ball_snap, first-touch, shutter release)
2. Pick one row per session where `event == <anchor>` in the sensor stream
3. Find the known video frame index of the anchor event (often a constant from dataset metadata)
4. Compute `offset_sec = sensor_time - anchor_time` for every sensor row
5. Convert to `est_frame = round(offset_sec * fps + anchor_frame)`

## Key Decisions

- **Anchor event must be discrete and unique**: events that repeat (e.g. "whistle") break the alignment.
- **Watch for fps fractions**: NTSC video is 59.94 not 60; using integer fps drifts ~0.1% over long clips.
- **Nearest-neighbor lookup downstream**: even after sync, multiple sensor rows may fall between frames — use `find_nearest(est_frames, video_frame)` to pick one.
- **vs. timestamp-only join**: wall-clock timestamps drift between recorders; event anchoring cancels the absolute offset entirely.

## References

- [NFL Helmet Assignment - Getting Started Guide](https://www.kaggle.com/code/robikscube/nfl-helmet-assignment-getting-started-guide)
- [NFL Baseline - Simple Helmet Mapping](https://www.kaggle.com/code/its7171/nfl-baseline-simple-helmet-mapping)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…