Registering thousands of extracted frames one-by-one via POST /frames doesn't scale, so add `spelunkai-labeling-backend ingest-session`: it writes Frame rows directly against the database (no server needs to be running) for all frame_*.png files under a directory already placed under FRAMES_ROOT. Idempotent per (session_name, frame_index), so re-running after copying more frames only inserts the new ones. Restructured the CLI into subcommands (serve / ingest-session) while keeping `spelunkai-labeling-backend` with no arguments working exactly as before (defaults to serve), verified against a live run. 22/22 backend tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Labeling Tool — Backend
API + data model for the bounding-box labeling tool. See CLAUDE.md §3.2 for the full requirements this is built against.
Status: core data model + CRUD API implemented (sets, hierarchical classes,
frames, labels, per-frame/per-set status), frame image serving, and bulk frame
ingest from a recording session (see labeling/frontend/ for the UI). Not yet
implemented: dataset versioning/promotion (enemy-v1, enemy-v2, ...) —
deferred since it needs its own design pass (snapshot semantics: labels are
editable at any time, but a promoted dataset version must stay reproducible).
Also not yet implemented: auth/login (see below) or the active-learning
auto-label workflow.
Stack
- FastAPI + SQLAlchemy (2.0), SQLite by default (
./labeling.db), swappable via theDATABASE_URLenv var (e.g. to Postgres later without code changes — one Postgres-compatible ORM). - Multi-user, no auth yet: labels/status changes take a plain
created_by/updated_byusername string, resolved via get-or-create (users.py). There's no login flow — attribution only, since there's no UI yet that would need real auth.
Data model
LabelSet— a label set (Enemy,Items,Traps, ...), one per detector model.MainClass/SubClass— the per-set Main → Sub class hierarchy (e.g.Enemy→Bat,Snake), created ad hoc via the API, no migration needed to add classes.Frame— one labelable image, identified by(session_name, frame_index)— matches the recording tool's frame-extraction output 1:1.Label— one bounding box (x, y, width, heightin pixel space), scoped to a frame + set + sub-class.FrameSetStatus— per-frame, per-set label state (unlabeled/auto_labeled/reviewed).
Frame images are served from FRAMES_ROOT (env var, default ./frames) at
/images/<image_path>; Frame.image_path is always relative to that root.
Setup
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Run
spelunkai-labeling-backend
# or: uvicorn spelunkai_labeling_backend.main:create_app --factory --reload
Interactive API docs at http://127.0.0.1:8000/docs once running.
Bulk-ingesting a recording session
After a session's extracted frames (recording/'s extract-frames output) have
been copied/rsynced onto this machine under FRAMES_ROOT, register them all in
one shot (writes directly to the database, no server needs to be running):
spelunkai-labeling-backend ingest-session \
--frames-dir /path/under/FRAMES_ROOT/run01_frames \
--session-name run01
--image-path-prefix defaults to the frames directory's own name (here
run01_frames) — override it if the directory was copied under a different name.
Safe to re-run: frames already registered for that session (by frame index) are
skipped. --width/--height default to 1280x720 (Spelunky Classic HD's fixed
capture resolution).
Testing
pytest
Each test gets a fully isolated app + SQLite file via create_app(database_url=...)
(see tests/conftest.py) — no shared state between tests, no real server needed.