-
-
The live wall: 62 camera tiles from 50 HPWREN cameras, ranked every minute of footage
-
T+9: the Creelman Fire camera reaches rank 1 with an alert
-
The camera's own frame at that minute — a small plume on the far ridge
-
The evidence card, drawn by OpenCV on the server — the box on the plume
-
Held-out: 27 of 54 detected; median 38.0 min = the 27th detection, with ±5 min sensitivity
-
Same budget, same baseline: PlumeRank 27, CNN-on-every-frame 18, frame differencing 20
-
Model calls per camera-window (one 81-minute FIgLib sequence): 1.4 vs 3,645
-
How it works: phaseCorrelate → MOG2 → four statistics → cv2.dnn on 1.76% of frames
-
curl /build-info → OpenCV 5.0.0; make verify → MATCH byte-for-byte
-
getBuildInformation, live, and the reproduce commands
-
Our own audit: the evidence crop frames the plume on 9 of 21 development fires
-
Every limitation, published
-
The landing view of the live system
-
The 30-second path for judges
-
The 6 held-out exclusions, with their stated reasons
▶ Live system: https://live.plumerank.edycu.dev · Code: https://github.com/edycutjong/plumerank · Demo video (3:34): https://youtu.be/QqX85JuNfVM · Judge's 30-second path: https://live.plumerank.edycu.dev/judge · OpenCV 5 build string, live: https://live.plumerank.edycu.dev/build-info
Held-out result, read once after every constant was frozen: 27 of 54 held-out fire windows (41 distinct fires) detected at a hard budget of 2 alerts per camera-window (81 minutes of footage on one camera) — 14 of them missed by all four baselines — with 1.4 CNN calls per camera-window against 3,645 for the same CNN on every frame (results/bench_2026-10-10.json). Held to the same budget and the same per-camera baseline, that every-frame CNN detects 18 and frame differencing 20. Built for the OpenCV AI Competition 2026 with OpenCV 5 doing the per-frame work and AWS (one Graviton t4g.small, S3, CloudWatch) running it live. Imagery: HPWREN FIgLib, http://hpwren.ucsd.edu/ (UC San Diego).
How to read those numbers: all 54 held-out windows are aligned on ignition and ranked against each other, so "top 4 with an alert" is a fire-against-fire test for every system. The median time-to-detection, misses counted as never, is 38.0 minutes — a nearest-rank median, so with exactly half detected it is the 27th and slowest detection; the detected fires are found at a median of 10.0 and a mean of 13.3 minutes. Paired on the same windows the lead over the two like-for-like baselines is 22 vs 13 and 17 vs 10 (exact McNemar p = 0.18 and 0.25) — not yet significant at 54 windows; where both detect, the baselines are sometimes faster (3 of 5, 5 of 10). What is decisive is cost: the classical stage runs p50 6.5 ms a frame against 53.6 ms for the CNN grid.
Inspiration
Mid-afternoon on a red-flag day, one watcher at a fire-agency command centre has a wall of camera tiles — ALERTCalifornia alone runs more than 1,200 cameras — and a queue of AI smoke alerts to verify: CAL FIRE has routed such alerts to its dispatch centres since 2023. Every alert is an interruption; too many and the tool gets muted.
Published smoke detectors are scored on per-frame accuracy (F1, precision, recall), or on time-to-detection with no cap on false alarms — FIgLib's own reference model, SmokeyNet, reports a 3.12-minute average time-to-detection at 89.84% precision and 76.45% recall (Dewangan et al., 2022, arXiv:2112.08598), with no alert budget attached. Deployed systems rarely publish where they sit on that trade-off, and none of it tells a fire agency whether the system will survive a real shift before it gets muted for crying wolf. The number that matters is different: how many minutes after ignition does a human actually look at the right camera, given they will tolerate only a fixed number of interruptions per camera? That is detection delay at a fixed false-alarm rate — the classical quickest-change-detection criterion (Page, 1954; Lorden, 1971) — and PlumeRank publishes it.
What it does
PlumeRank watches a camera network and, once per minute of footage, emits a ranked watch list of which cameras a human should look at now — each entry carrying its own evidence crop, drawn by OpenCV on the server — calibrated to a fixed budget of 2 alerts per camera-window (one camera-window = one FIgLib sequence: 81 minutes of footage on one camera). It never suppresses a camera and never dispatches an engine. It reorders human attention.
On the live wall (62 camera tiles from 50 HPWREN cameras, replayed through the real pipeline at one minute of footage per second), you can watch a development-set fire — the Creelman Fire, 2026-07-22 — reach rank 1, with an alert, nine minutes after ignition, while its smoke is still a small plume on the far ridge. Before any fire starts, something is still rank 1: that is what the alert budget is for. One held-out camera on the same fire, mg-s-mobo-c, was in PlumeRank's top four with an alert at nine minutes too, where all four baselines missed it. That window was never seen before the single read, though the fire itself is in the development set from bm-s-mobo-c, and one fire-free window from mg-s-mobo-c is, by design, in the threshold's calibration set.
How we built it
OpenCV 5 is the engine, not decoration — 23 defended OpenCV 5 surfaces across 6 modules, counted from the source by scripts/check_claims.py on every test run:
- Stabilise:
phaseCorrelate+createHanningWindow(+phaseCorrelateIterative, a recent addition in OpenCV 4.13 / 5.0), with anestimateAffine2D(USAC_MAGSAC)fallback. - Find what moved:
createBackgroundSubtractorMOG2,morphologyEx,createCLAHE,connectedComponentsWithStats. - Score it: four statistics per foreground component — S1 base anchoring, S2 growth anisotropy, S3 diffuse-boundary contrast (
Sobel/Laplacian), S4 in-box drift (DISOpticalFlow_create). - Escalate the few: an ONNX tile classifier through OpenCV's own DNN engine (
dnn.readNetFromONNX,ENGINE_NEW,blobFromImage,Net.forward) — on only 1.76% of frames, so PlumeRank makes 1.4 model calls per camera-window against 3,645 for the same CNN run on every frame. - Judge it against its own recent past: each camera's hazard is scored against its own trailing 15 minutes, and the alert threshold is solved on 33 fire-free windows — never typed by hand.
AWS: one Graviton t4g.small (OpenCV 5.0.0 on arm64) runs FastAPI, the live replay and a Next.js static site behind Caddy with TLS; the replayed corpus lives in S3; the realised alert rate is published to CloudWatch with ±15% drift alarms and a heartbeat-silence alarm. No Lambda, no managed vision API, no second instance.
Evaluation discipline: three arms — DEV (tuning, 29 fire windows), CONFUSE (33 fire-free windows that calibrate the threshold), HOLD (60 fire windows, 6 excluded by a rule stated before the read) — with plumerank/constants.py sha256-frozen before the single HOLD read (results/constants.sha256, results/hold_access.log). Every published number is committed in results/ and reproducible: make verify (≤3 min: rebuilds the venv, re-runs the offline suite, then checks the micro-corpus metrics byte-for-byte) and make fetch && make bench (≈80 min corpus fetch, then the held-out evaluation: 12m29s on an arm64 laptop; not yet timed on a small instance). 833 tests pass locally (CI, without the corpus, runs 821 and skips 13), 100% line coverage gated in CI, ruff and mypy clean, CodeQL and gitleaks in CI.
Challenges we ran into
- Cloud, not fog, turned out to be the adversary. The project was designed against coastal fog; measured on the confusion set, low cloud and cloud shadow cost far more false alarms (measured under the pre-baseline pipeline). The threshold is solved on all fire-free windows together, so only per-class claims changed — and we say so.
- The statistic the design started from is anti-predictive on its own. S3 (diffuse boundary) scores AUC 0.44 on DEV frames. What lifts detection is the per-camera baseline, not any single per-frame statistic: on DEV the pre-baseline pipeline detected 9 of 29 and had no median; with the baseline it detects 17.
- Stabilisation vs a per-pixel background model. HPWREN's fixed cameras still shake in wind, and a whole-frame estimate can follow moving cloud. Refereed against the ground, 48 of DEV's 55 large warps are one genuinely shaking camera; the rest follow moving cloud. A counterfactual replay without those cloud-following warps moves the fire-free set's false alarms from 64 to 63.
- 62 live background models on 2 GiB. An unbounded optical-flow cache took the box down once; it now runs with an LRU cache, a systemd memory limit, swap and a heartbeat alarm that reboots it.
Accomplishments that we're proud of
- A headline that is out-of-sample by construction — constants frozen and hashed before the held-out read, the read logged, the exclusion rule stated first.
- The same CNN on both sides: baseline B1 is PlumeRank's own ONNX classifier run on every frame, so the comparison cannot be won by having a bigger model.
- 62 camera tiles (50 HPWREN cameras) live on one small Graviton box, with a budget meter, an evidence crop for every ranked camera, and a public
/build-infothat anyone cancurlto check the OpenCV 5 claim. - Honesty that is measured, not stated: we audited our own evidence crop — at each development fire's first rank-1 or alert, the crop contains the plume on 9 of 21; cloud on 7 — and published it.
What we learned
That the metric is the product. A detector's F1 says nothing about whether the right person looks at the right tile in time; a budget-calibrated time-to-detection does. And that every claim on a page has to be re-measured against the thing it describes — one of our own published claims was retracted after an outside architecture review, and choosing the demo's hero fire exposed an evidence-crop weakness we then measured and published (9 of 21).
What's next
- Choose the evidence crop by statistic rather than by size, so it frames the plume more often (no rank changes).
- Estimate stabilisation on the ground band only, so moving cloud cannot pull the warp.
- Night/IR cameras; live RTSP ingest; a second network (Pyronear, France) to test whether the calibration transfers.
Honest limitations
Daylight only: FIgLib is almost all daytime, and the few night windows in the held-out arm were excluded by the stated rule (5 of the 6 exclusions). It ranks; it never dispatches and never hides a camera. The median is on a knife edge: 27 of 54 is exactly half, so against an ignition clock shifted by +1…+5 minutes there is no median — the detection counts (27 vs 18 vs 20) are the steadier comparison. The demonstrated fire is a development-set fire and is labelled as such. Every limitation is listed with its evidence in the README and on /judge.
Track: Overall only. We deliberately did not opt into Best Use of COOL (the hot loop is background modelling and optical flow, not COOL's accelerated operations) or Agentic Vision (the dispatcher is human-in-command by design — that is the safety argument).
Built With
- amazon-cloudwatch
- amazon-ec2
- amazon-web-services
- aws-graviton
- boto3
- caddy
- fastapi
- github-actions
- hypothesis
- next.js
- numpy
- onnx
- opencv
- playwright
- pytest
- python
- pytorch
- react
- typescript
- uvicorn
Log in or sign up for Devpost to join the conversation.