feat(export): QA overlay renderer for exported alert datasets - #381
Merged
Conversation
Renders contact sheets from an alerts_export dataset so annotations can be eyeballed against the UI: one sheet per object, plus a combined sheet per multi-object alert where every lane draws on the same image in its own colour. Each cell pairs the full frame with a magnified crop of the box region -- exported boxes are typically ~0.05% of the frame area and vanish once a 1280x720 frame is scaled to a grid cell. Sibling lanes hold their own copies of one capture, so multi-object sheets align frames by recorded_at and give each lane its own zoom panel rather than a union crop.
Review follow-ups on the overlay renderer: - A capture is now rendered from whichever lane's copy actually decodes. Previously a null image_path (which export_alerts writes for a download it could not complete) raised TypeError, and a truncated file -- which the exporter never re-downloads, its idempotency check being mere existence -- raised UnidentifiedImageError. Either killed the whole run, losing index.csv for every sheet already rendered. - render_sheet reports whether it wrote anything, so index.csv no longer lists sheets that were skipped for want of a usable image. - Cells are built in a single decode pass; the grid is sized from the captures that survived rather than from the manifest. - Warn when an alert has more lanes than distinct colours. - Document the tool: make render-overlays target, root CLAUDE.md section, and README now points at the shipped export + overlay commands instead of calling the pull script "planned".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
annotation_api/scripts/data_transfer/export/render_overlays.py(+make render-overlays), a QA tool that draws the exported boxes back onto the exported frames so annotation work can be eyeballed against the annotator UI.It reads a dataset written by
make export-alerts(manifest.jsonl+images/) and writes<dataset>/overlays/:Each sheet lays an object's frames out in time order. A cell stacks the full frame with its boxes drawn, a magnified crop of the box region, and a caption naming the detection id. The crop row is what makes the sheets usable: exported boxes are typically ~0.05% of frame area, invisible once a 1280x720 frame is scaled into a grid cell.
For a multi-object alert every lane draws on the same image in its own colour, and each lane gets its own zoom panel — a crop around the union of two objects at opposite ends of the frame just reproduces the frame. Sibling lanes hold their own copies of a capture (distinct detection ids, usually distinct
bucket_keys), so frames are aligned across lanes by exactrecorded_at.Why
Verifying an export numerically (counts,
xyxynbounds, file presence) says nothing about whether a box actually sits on the plume. This makes that check cheap and repeatable, and the sheet filenames carry the UI's own identifiers (alert{platform_alert_id}_seq{sequence_id}_{camera}.png) so a reviewer can jump straight to the matching lane.Usage
Every smoke lane is rendered, plus
FP_SAMPLEfalse-positive lanes chosen round-robin across false-positive type combinations so the sample spans all of them before deepening any one.FP_SAMPLEcaps only the per-object sheets — the multi-object pass covers every multi-lane alert;--alertsis what bounds a run.Robustness
The renderer never assumes an image is there or readable. A capture is drawn from whichever lane's copy actually decodes, so on a multi-object alert a sibling rescues a capture the first lane lost. Two failure modes it tolerates, both reachable from a real export:
image_path: null— whatexport_alerts.pywrites for a download it could not complete..jpg— never re-downloaded, since the exporter's idempotency check is mere file existence.Unrenderable captures are skipped with a warning, an alert with no usable image yields no sheet, and
index.csvlists only sheets that were actually written.Verification
index.csvmatches the PNGs on disk exactly.uv run ruff check .(what the Ruff CI job runs),ruff format --check, andmypyall clean.No application code is touched — this is scripts, Makefile and docs only.