Prepare a search-and-rescue drone-image incident for visual triage. Use when the user asks to "ingest", "prepare", or "set up" an SAR incident directory of RGB drone frames — e.g. "get incident013 ready for search", "ingest the new footage", "prep this folder for triage". Enumerates 4056x3040 RGB JPGs in the directory, extracts GPS/timestamp from EXIF, tiles each frame into a 4x3 grid of 1164x1147 overlapping tiles (200px overlap), runs a cheap ImageMagick Stage-1 filter that drops uniform/sky/overexposed tiles while flagging blaze-orange clusters, and writes runs/<incident_id>/manifest.json.
Prepare one SAR incident directory of drone imagery for visual triage.
Two values, both required. Ask the user if missing.
/home/sarocu/Projects/nova/assets/incident013incident013Invoke the orchestrator once. It enumerates, extracts GPS, tiles, Stage-1 filters, and writes manifest.json:
python3 /home/sarocu/Projects/nova/.claude/skills/sar-ingest/scripts/build_manifest.py \
--incident-dir <ABSOLUTE_PATH_TO_INCIDENT_DIR> \
--incident-id <INCIDENT_ID>
Expected runtime on a 250-frame incident: 5–20 min depending on core count. Progress prints to stderr. The manifest path prints to stdout.
/home/sarocu/Projects/nova/runs/<incident_id>/manifest.json — canonical manifest. Schema version 1./home/sarocu/Projects/nova/runs/<incident_id>/tiles/<FRAME_ID>/tile_NN.jpg — 12 tiles per frame.(frame_id, tile_index, pixel_count). Orange-clad humans in wilderness produce 50–500 px clusters. Surface these prominently; they may be immediate leads.sar-triage on this incident.identify -format "%wx%h\n" /path/to/DJI_xxxx.JPG. If the drone model isn't 4056×3040, the tiler needs retuning (edit XS, YS, TW, TH in tile_frame.sh / .py).gps carries {"error": "..."} and downstream report shows "no coordinates" for those frames.tile_frame.sh). Slower but identical output.Applied to every tile. Any single rule matching → skip:
| Reason | Rule (normalized 0–1 ImageMagick fx values) |
|---|---|
uniform_gray | stddev < 0.04 AND mean_sat < 0.15 |
sky_blue | mean_hue ∈ [0.55, 0.68] AND mean_sat ∈ [0.10, 0.55] AND mean > 0.55 AND stddev < 0.10 |
overexposed | mean > 0.93 AND stddev < 0.06 |
Blaze-orange detection runs independently on a 400×400 downsample of the tile, counting pixels with r>0.75 AND 0.2≤g≤0.6 AND b<0.3. Count is scaled back to full-tile pixels and stored in stage1.blaze_orange_pixels. Does NOT cause skip — it flags for priority review in the report.
frames[].frame_id "DJI_0401"
frames[].source_path "/abs/path/to/DJI_0401.JPG"
frames[].parent_w / parent_h 4056, 3040
frames[].gps.lat/lon/alt_m floats (decimal degrees, meters)
frames[].gps.timestamp_utc "YYYY-MM-DDTHH:MM:SSZ"
frames[].tiles[].tile_index 0..11
frames[].tiles[].tile_path absolute path to tile JPG
frames[].tiles[].crop_box {x, y, w, h} in parent-frame pixels
frames[].tiles[].stage1.verdict "keep" | "skip"
frames[].tiles[].stage1.reason "texture_ok" | "uniform_gray" | "sky_blue" | "overexposed" | "stage1_error"
frames[].tiles[].stage1.blaze_orange_pixels int (scaled from downsample)
frames[].tiles[].stage1.stats {mean, stddev, mean_hue, mean_sat}