Inspiration

A camera trap that fails does not go offline. It fogs its lens, grows a spider web across the sensor, loses its infrared illuminator, browns out its battery, or gets nudged so its field of view no longer covers the trail. It keeps triggering. It keeps uploading. Every dashboard stays green.

What changes is its detection probability, and it goes to nearly zero. Ecologists then read that silence as absence: the animal wasn't there. That is how a dead illuminator quietly turns into a data point in a population trend.

We wanted a monitor that treats "this camera is still returning images" and "this camera is still seeing" as two different questions.

What it does

Silent Camera watches a 24-station, 120-day deployment and answers three things per camera: is it healthy, when did it stop working, and which way did it break.

  • Each camera is its own control. The health model is fitted to a station's own baseline period rather than the array's, because a genuinely quiet site and a broken camera look identical if you compare against the array average. The reference pool is then rebuilt iteratively from the cameras the previous pass still believes, so already-broken stations stop dragging the reference.
  • A CUSUM changepoint detector runs over a multivariate health score built from trigger rate, day/night trigger ratio, background-difference, contrast, edge density and inter-trigger intervals.
  • Failure mode classification matches the post-onset feature signature against five physical prototypes: lens fogging, web/vegetation occlusion, battery brownout, IR illuminator death, and field-of-view shift.
  • A replay control runs the deployment day by day so you can watch a failure emerge and the detector catch it, with the estimated onset and the true injected onset both marked. The detector is causal: it only ever sees days up to the current one.
  • A bias panel then prices the damage against a single-season occupancy model.

How we built it

Vanilla JavaScript and canvas, no framework and no runtime CDN. The dataset generator simulates each station's true occupancy, visitation rate and nocturnality, then injects failure episodes whose image statistics are derived from the physics of each failure rather than sampled independently — an IR death kills night detections while leaving day untouched, a brownout drops trigger counts with night going first, fogging collapses daytime contrast gradually.

The occupancy model is a MacKenzie-style single-season MLE over detection histories, fitted three ways: with every camera-day counted, with the detector's flagged days censored, and with an oracle censoring using the injected onsets.

Challenges we ran into

The first detector had recall 0.857 but precision 0.632, and seven of ten healthy cameras raised an alarm. That over-censoring was worse than useless: it threw away good camera-days and made the occupancy correction move in the wrong direction.

The fix was not a bigger threshold, it was a clearer operating point. Post-onset deviation separates cleanly — healthy stations sat at 0.6 to 2.7, real failures at 9.6 to 49.8 — so the detector now distinguishes degraded (a watch-list state) from failed (a claim), and only a FAILED verdict alarms or censors. We also split the false-alarm rate, which previously conflated two different errors, into healthy-camera alarms and early alarms on cameras that really did fail.

Accomplishments we're proud of

Measured against the injected ground truth, not asserted:

  • precision 0.857, recall 0.857, F1 0.857
  • failure mode classified correctly on 100% of true positives
  • median detection latency 3 days, max 4
  • 0 of 10 never-broken cameras raised an alarm
  • 1,112 camera-days censored against 1,092 truly compromised, a 1.8% error on how much survey effort was silently worthless

And the headline finding: counting dead camera-days as valid effort underestimates per-day detection probability by 31.2% (0.0209 against 0.0304). That is the number survey design runs on — it decides how many camera-nights a study buys and propagates into every trend estimate built on the array.

Honest limits

The deployment is synthetic. The image statistics are physically derived, but no real camera-trap imagery was processed, and nothing here has been validated against a field deployment.

More important, the occupancy point estimate was not biased downward in this simulation, and we show that on screen rather than hiding it. The naive proportion detected (0.750) does sit below true occupancy (0.792), but the detection-corrected MLE (0.815) already brackets truth, and censoring moves it to 0.705. Oracle censoring using the injected onsets lands at 0.728, which tells us this is a property of this deployment rather than a failure of the detector. Censoring is a clear win for estimating p and for knowing how much of your effort was real; on this simulation it is not a rescue of psi. We would rather ship that sentence than a bar chart that implies otherwise.

Two of the fourteen real failures are flagged before their injected onset. The generator ramps some failures gradually, so an early flag may be legitimate, but we refuse to take credit for an alarm that fires before the failure was injected and count those as errors.

What we learned

The useful output of a failure detector is not a red light. It is a censoring decision, and the value of that decision is measured downstream in the estimate it protects. Once we started scoring the detector by how accurately it recovered the number of compromised camera-days rather than by raw alarm accuracy, the design questions got much sharper.

What's next

Run it against a real archive with maintenance logs as ground truth, add a per-station power/duty-cycle channel, and let the censoring feed a multi-season dynamic occupancy model where the effort correction compounds across years.

Built With

  • camera-trap
  • canvas
  • changepoint-detection
  • conservation
  • cusum
  • data-visualization
  • ecology
  • javascript
  • occupancy-modeling
  • statistics
Share this project:

Updates