Skip to content

Add near-real-time burn severity from CIMSS BRISK - #1

Merged
scottwmccoy merged 1 commit into
mainfrom
burn-severity
Aug 15, 2026
Merged

Add near-real-time burn severity from CIMSS BRISK#1
scottwmccoy merged 1 commit into
mainfrom
burn-severity

Conversation

@scottwmccoy

Copy link
Copy Markdown
Owner

Adds a stormscape.burn module and a burn subcommand: screen the CIMSS BRISK archive for fires intersecting an AOI, cache and mosaic the scenes, classify severity, and drape it over the hillshade.

Why BRISK

BRISK maps every large US fire daily from a nine-satellite dNBR composite, which is what makes it usable while a fire is still burning. The authoritative products are not: BAER soil burn severity lands days-to-weeks after containment and only for assessed fires, MTBS a year or more later.

The portal's WMTS serves rendered PNG capped at zoom 7, so this reads the per-fire GeoTIFFs from the open archive behind it. Finding which fires touch an AOI costs no pixels — GDAL range-reads the headers (~60 fires in 1.4 s) and footprints are memoised, so repeat screens are instant.

Colours match BAER

Maps draw in the BAER class palette, banded into severity classes with class names on the colour bar, so a stormscape map can sit beside a BAER product. The palette is read from the products themselves: all 77 of the 2025 BAER soil-burn-severity rasters embed the same four class colours — and they are the four BRISK publishes.

Validation

examples/brisk_vs_baer.py compares BRISK against the BAER teams' own dNBR on the 39 fires carrying both. BRISK computes the same quantity: median slope 1.013, bias +0.003, and r = 0.938 over 1.24 M cells.

The disagreements are compositing latency, not a different algorithm — every poor performer read low on the BAER date and recovered to r = 0.80–0.96 given a scene 5–21 days later. A fixed +14 d rule takes fires at r ≥ 0.90 from 14/39 to 24/39. So scenes now carry an age_days, immature composites are flagged on every run, and --min-age DAYS turns that into a hard filter.

BAER products from Sentinel-2 (20 m) agree better than Landsat-derived ones (30 m) — median r 0.923 vs 0.814, p = 0.0005.

Caveat, stated everywhere it matters

dNBR is a vegetation index, not soil burn severity, and the USGS post-fire debris-flow models are calibrated on the latter. BRISK is interim: act early, supersede with --product sbs. Flagged in the README, the CLI help and the module docstring.

Incidental

  • drape_i15 gains norm / cbar_ticks / cbar_ticklabels for classed maps (general, reusable)
  • export learns severity is categorical, and scales burn fields on their own range instead of the rainfall autoscale floor
  • brisk_cache/ joins the reserved event-root directories

Tests

66 new offline tests (398 total, ~3 s). They pin the documented invariants: dNBR breaks match the published ×1000 thresholds, unburned stays distinct from unobserved, a scene's NaN surround never erases a neighbouring fire, class mosaics are never interpolated, the palette stays identical to BAER's, and a past-year archive index caches forever while today's expires.

🤖 Generated with Claude Code

New stormscape.burn module and burn subcommand: screen the BRISK archive
for fires intersecting an AOI, cache and mosaic the scenes, classify
severity, and drape it over the hillshade.

BRISK maps every large US fire daily from a nine-satellite dNBR composite,
which is what makes it usable while a fire is still burning -- BAER soil
burn severity lands days to weeks after containment, MTBS a year later.
The portal's WMTS serves rendered PNG capped at zoom 7, so this reads the
per-fire GeoTIFFs from the open archive behind it. Finding which fires
touch an AOI costs no pixels: GDAL range-reads the headers (~60 fires in
1.4 s) and the footprints are memoised.

Maps draw in the BAER class palette, banded into severity classes with the
class names on the colour bar, so a stormscape map can sit beside a BAER
product. That palette is read from the products themselves -- all 77 of
the 2025 BAER soil-burn-severity rasters embed the same four class
colours, and they are the four BRISK publishes.

Validated against the BAER teams' own dNBR on the 39 fires carrying both
(examples/brisk_vs_baer.py): BRISK computes the same quantity -- median
slope 1.013, bias +0.003, r = 0.938 over 1.24 M cells. The disagreements
are compositing latency, not a different algorithm: every poor performer
read low on the BAER date and recovered to r = 0.80-0.96 given a scene
5-21 days later. So scenes now carry an age, immature composites are
flagged, and --min-age turns that into a filter.

dNBR is a vegetation index, not soil burn severity, and the USGS
debris-flow models are calibrated on the latter -- flagged in the README,
the CLI help and the module docstring.

Also: drape_i15 gains norm/cbar_ticks/cbar_ticklabels for classed maps;
export learns that severity is categorical and scales burn fields on their
own range; brisk_cache/ joins the reserved event-root directories.

66 offline tests (398 total).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@scottwmccoy
scottwmccoy merged commit 998af2d into main Aug 15, 2026
2 checks passed
@scottwmccoy
scottwmccoy deleted the burn-severity branch August 15, 2026 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant