← Lillie AcademyCourse contentslillieearthintelligence.com

Module 6: SAR Fundamentals II, Time Series and Change

Phase: II, Core analytics
Level: Advanced
Estimated time: ~12 hours (about 4 h concepts, 7 h lab, 1 h write-up)
Prerequisites: Modules 3 (SAR), 4 (ML and spatial CV), 5, Phase I.
Portfolio thread: builds sar_change.py and produces Public write-up #1, "Detecting burned area through cloud with Sentinel-1."


Why this module

Cloud-free change detection is the whole reason SAR matters in fire and forest monitoring. When smoke or a three-week monsoon hides the ground from optical sensors, radar still sees it, and nearly every operational deforestation and disturbance alert (RADD, the OPERA DIST-ALERT products, and most commercial systems) is built on the time-series change methods in this module. Learning to turn a stack of Sentinel-1 scenes into a defensible change signal, and to know where it fails, is a core operational skill and the heart of your first public write-up.

Learning outcomes

After this module you can:

  1. Build a dense Sentinel-1 RTC time-series cube, handling ascending and descending orbits separately.
  2. Implement log-ratio and CuSum change detection and choose operating points with an ROC curve.
  3. Explain probabilistic and Bayesian change detection (the RADD method) and the OPERA DIST-ALERT lineage.
  4. Detect burned area from Sentinel-1 and validate it against optical dNBR and MTBS.
  5. Implement a simplified RADD-style deforestation alert and compare it to official products.
  6. Build a Sentinel-1 water mask with an Otsu threshold, the basis of flood and DSWx-style mapping.

Concept lessons (about 4 hours)

Lesson 1: SAR time series

Stack many RTC scenes into a time cube (Zarr, from Module 1). Two rules keep it honest:

Lesson 2: Log-ratio and CuSum

Pick thresholds with an ROC curve against reference labels, and report the operating point you chose and why.

Lesson 3: Probabilistic and operational alerts

Single-date thresholds are noisy, so operational systems accumulate evidence:

The lesson to carry: persistence and confirmation over multiple observations beat any clever single-date detector for operational reliability.

Lesson 4: Burned area from Sentinel-1

After fire, canopy volume scattering collapses, so VH backscatter usually drops, which a log-ratio or CuSum on the VH series can detect through cloud. The caveats matter: the response is terrain-dependent (slopes complicate it), and low-severity or grass fires may barely register. You will compare your Sentinel-1 burned-area map to the optical dNBR map from Module 2 and to MTBS, and honestly describe where radar wins (cloudy periods, rapid response) and where it struggles.

Lesson 5: Water with Sentinel-1

Smooth water reflects radar away from the sensor (specular reflection), so it appears very dark. A histogram of VV over a scene with water is bimodal (water and land), and an Otsu threshold separates them automatically. This simple, robust idea underlies flood mapping and the OPERA DSWx-S1 surface-water product.


Guided lab (about 7 hours): two detectors and a water mask

Open in Colab Open in GitHub Codespaces

Step 1: Dense RTC time cubes

Build OPERA RTC-S1 stacks (from ASF) over your fire AOI and tropical AOI as Zarr time cubes, ascending and descending separated. Convert to dB for display, but do ratio math in linear power.

Step 2: sar_change.py with tests

"""sar_change.py: SAR time-series change detection (pure, testable)."""
from __future__ import annotations
import numpy as np

def log_ratio(a, b, eps=1e-6):
    """Change between two dates in dB (positive = a brighter than b). Ratio handles speckle."""
    return 10.0 * np.log10((a + eps) / (b + eps))

def cusum(series):
    """Cumulative sum of deviations from the series mean (over time, axis 0)."""
    x = np.asarray(series, float)
    return np.cumsum(x - x.mean(axis=0), axis=0)

def cusum_change_magnitude(series):
    """Range of the cumulative sum: larger means a stronger sustained change."""
    s = cusum(series)
    return float(s.max() - s.min())

def otsu_threshold(values, bins=256):
    """Otsu threshold that separates a bimodal distribution (for example water vs land)."""
    x = np.asarray(values, float).ravel()
    hist, edges = np.histogram(x, bins=bins)
    p = hist / hist.sum()
    centers = (edges[:-1] + edges[1:]) / 2.0
    omega = np.cumsum(p)
    mu = np.cumsum(p * centers)
    mu_t = mu[-1]
    sigma_b2 = (mu_t * omega - mu) ** 2 / (omega * (1 - omega) + 1e-12)
    return float(centers[int(np.nanargmax(sigma_b2))])
# tests/test_sar_change.py
import numpy as np
from eo_portfolio.sar_change import log_ratio, cusum_change_magnitude, otsu_threshold

def test_log_ratio():
    assert abs(float(log_ratio(np.array(10.0), np.array(1.0))) - 10.0) < 1e-6
    assert abs(float(log_ratio(np.array(3.0), np.array(3.0)))) < 1e-6

def test_cusum_detects_step():
    step = [0, 0, 0, 5, 5, 5]
    flat = [2, 2, 2, 2, 2, 2]
    assert cusum_change_magnitude(step) > cusum_change_magnitude(flat)

def test_otsu_between_modes():
    rng = np.random.default_rng(0)
    v = np.concatenate([rng.normal(0, 0.3, 500), rng.normal(5, 0.3, 500)])
    assert 1.0 < otsu_threshold(v) < 4.0

The tests encode the physics: a ratio of equal values is zero change, a sustained step raises the CuSum range, and Otsu lands between two modes.

Step 3: Burned area, validated

On the fire AOI, run log-ratio and CuSum on the VH series across the fire date and threshold to a burned-area map. Compare against your Module 2 dNBR map and MTBS, and draw an ROC curve to choose and justify a threshold.

Step 4: A RADD-style deforestation alert

On the tropical AOI, implement a simplified probabilistic alert: characterize forest backscatter, score each new observation, accumulate evidence, and confirm an alert after persistence. Compare your alerts to official RADD and to DIST-ALERT where available.

Step 5: Water mask

Compute an Otsu threshold on VV to produce a water mask, and sanity-check it against a known water body. This is your reusable flood and water layer.

Step 6: Public write-up #1 and commit

Write a clear, blog-style piece: "Detecting burned area through cloud with Sentinel-1, a baseline and where it fails," with the ROC, the comparison to MTBS, and honest limitations. Commit sar_change.py, tests, notebook, and the write-up.

git add src/eo_portfolio/sar_change.py tests/test_sar_change.py notebooks/08_sar_change.ipynb writeups/01_s1_burned_area.md
git commit -m "Module 6: SAR change detectors (log-ratio, CuSum), water mask, write-up 1"
git push

Checkpoint

Self-check (answers below).

  1. Why must ascending and descending Sentinel-1 orbits be handled separately in a change time series?
  2. Why compare two dates as a ratio (log-ratio) rather than a linear difference?
  3. What does the range of a CuSum series tell you, and what does its turning point give?
  4. Why do operational alerts like RADD confirm over multiple observations instead of flagging single-date change?

Interview-style questions (practice out loud).

Answers. (1) They image from opposite look geometries, so the same surface has different backscatter; mixing orbits injects apparent change that is only a geometry difference.
(2) Speckle is multiplicative, so the ratio is the statistically correct, speckle-consistent comparison and is symmetric to brightening and darkening; a linear difference is biased by scene brightness.
(3) The range measures the magnitude of a sustained change, and the point where the cumulative sum turns marks the onset date.
(4) Single observations are noisy from speckle and moisture, so confirming a probability across multiple looks is what keeps false-alarm rates low enough to be operational.


Deliverable

A tested sar_change.py (log-ratio, CuSum, Otsu water threshold), a notebooks/08_sar_change.ipynb with a Sentinel-1 burned-area map validated against MTBS (with ROC) and a simplified RADD-style deforestation alert, plus Public write-up #1. This closes Phase II.

What a hiring manager sees

Change detection is where SAR earns its keep, and doing it credibly (orbits separated, ratios in linear power, thresholds chosen by ROC, confirmation over time, honest failure analysis) is exactly what disturbance-monitoring teams need. A public write-up that shows both a working Sentinel-1 burned-area baseline and a clear-eyed account of where it fails signals maturity that a flawless-sounding result never would.

Currency note

Verified August 2026. OPERA RTC-S1 (analysis-ready backscatter) is available from ASF. OPERA DSWx-S1 (Sentinel-1 surface water) is available from PO.DAAC; note a processing anomaly affected products from May to December 2025 (Sentinel-1A and 1C mixing), fixed on 9 December 2025 with reprocessing of the affected window, so verify you are using corrected data for that period. OPERA DIST-ALERT-HLS (optical vegetation disturbance) is the mature disturbance-alert product; the Sentinel-1 DIST-ALERT-S1 validated product is rolling out, so confirm availability for your AOI before relying on it. RADD alerts (Wageningen) cover the tropics via Global Forest Watch. Read the current OPERA algorithm documents and pin your Python versions in the Module 0.1 environment file.

Previous5 Forestry, Lidar, and BiomassNext7 Segmentation Networks