← Lillie AcademyCourse contentslillieearthintelligence.com

Module 9.7: Agriculture and Food Security from Space

Phase: III elective (Applications) · Level: Advanced · Optional Estimated time: ~15 to 18 hours (about 6 h concepts, 9 h lab, 1 to 2 h write-up). The foundation-model and soil-moisture steps are marked stretch; the core path is closer to 13 hours. Prerequisites: Modules 1 (cubes), 4 (classical ML and spatial cross-validation), 6 (SAR time series), and 7 (segmentation). Phase 0. Portfolio thread: a crop-type map over a real agricultural area with spatial cross-validation and an area-of-applicability check, plus a short food-security note. An optional application elective.


Why this module

Agriculture is the largest and most fundable application of Earth observation, and for a Nigeria and Africa audience it is also the most consequential: crop mapping, yield forecasting, drought and irrigation monitoring, and food-security early warning all run on satellites. The good news is that it needs no new craft. Crops are defined by how a pixel changes across a season, so this module is your Module 1 cubes, Module 4 classical ML and spatial cross-validation, Module 6 SAR time series, and Module 7 segmentation, pointed at a new target. Employers (Planet, Regrow, Bayer, Corteva, EOS Data Analytics, NASA Harvest, and Digital Earth Africa) hire for exactly this combination of time-series feature engineering and honest, spatially validated maps.

Learning outcomes

After this module you can:

  1. Explain what optical and SAR see in a field, and why crop mapping is a time-series problem, not a single-date one.
  2. Compute vegetation indices and phenology features (greenup, peak, senescence, amplitude) from a Sentinel-1 and Sentinel-2 season.
  3. Distinguish cropland masking from crop-type classification, and use the right open label sources for each.
  4. Handle the smallholder reality of African agriculture: small mixed plots, cloud, mixed pixels, and scarce labels.
  5. Use soil moisture and irrigation signals (SMAP at coarse scale, Sentinel-1 at field scale) and connect them to drought and food-security monitoring.
  6. Ship a tested agri.py and a spatially validated crop-type map with an area-of-applicability mask.

Concept lessons (about 6 hours)

Lesson 1: What Earth observation sees in a field

A crop is not a colour, it is a trajectory. Bare soil, greenup, peak canopy, senescence, and harvest trace a curve over the season, and different crops trace different curves, which is why a single image rarely separates maize from rice but a time series does.

The practical stance is fusion: build a stacked S1 and S2 season, and let the model use whichever is available and informative.

Lesson 2: Crop calendars and phenology

Because the signal is the season, phenology features are the heart of crop mapping:

Align every field to the same seasonal clock (days since planting or a fixed agricultural year) before comparing, or the features will not line up.

Lesson 3: Cropland masking versus crop-type mapping

These are two different jobs, done in order:

Labels are the bottleneck, especially in Africa, so lean on these open sets, WorldCereal, and Digital Earth Africa training data, and validate against independent ground truth where you can.

Lesson 4: The smallholder reality

Most African farms are small, mixed, and rain-fed, which breaks assumptions built for large monoculture fields:

Naming these constraints, and designing for them, is exactly what separates a credible smallholder-agriculture practitioner from someone who ran a European pipeline on African data.

Lesson 5: Soil moisture, water, and irrigation

Lesson 6: Yield, food security, and the frontier


Guided lab (about 9 hours): a crop-type map you can defend

Open in Colab Open in GitHub Codespaces

A starter notebook notebooks/m9-7_agriculture.ipynb scaffolds the season build and the feature extraction so you write the analysis.

Step 1: Build a season and phenology features

Over a Nigerian or West African agricultural area, build a Sentinel-1 and Sentinel-2 season (reuse cube.py from Module 1), cloud-mask and composite the optical, and gap-fill. Then compute the features with agri.py:

"""agri.py: agriculture time-series helpers (pure, testable)."""
import numpy as np


def ndvi(nir, red, eps=1e-9):
    """Normalized difference vegetation index."""
    nir = np.asarray(nir, float); red = np.asarray(red, float)
    return (nir - red) / (nir + red + eps)


def evi(nir, red, blue):
    """Enhanced vegetation index (less saturation over dense canopy)."""
    nir = np.asarray(nir, float); red = np.asarray(red, float); blue = np.asarray(blue, float)
    return 2.5 * (nir - red) / (nir + 6.0 * red - 7.5 * blue + 1.0)


def gap_fill(y):
    """Linearly interpolate NaN gaps in a 1-D series; leading and trailing NaNs
    take the nearest valid value."""
    y = np.asarray(y, float).copy()
    idx = np.arange(y.size)
    good = ~np.isnan(y)
    if good.sum() == 0:
        return y
    y[~good] = np.interp(idx[~good], idx[good], y[good])
    return y


def harmonic_fit(t_years, y):
    """Fit y = a + b*sin(2 pi t) + c*cos(2 pi t); return (mean, amplitude, phase).

    A one-cycle annual harmonic captures crop phenology; amplitude separates a
    strongly seasonal crop from near-constant bare soil or evergreen cover.
    """
    t = np.asarray(t_years, float); y = np.asarray(y, float)
    m = ~(np.isnan(t) | np.isnan(y))
    if m.sum() < 3:
        return float("nan"), float("nan"), float("nan")
    w = 2.0 * np.pi
    a_mat = np.vstack([np.ones(m.sum()), np.sin(w * t[m]), np.cos(w * t[m])]).T
    a, b, c = np.linalg.lstsq(a_mat, y[m], rcond=None)[0]
    return float(a), float(np.hypot(b, c)), float(np.arctan2(c, b))


def season_amplitude(y):
    """Peak-to-trough amplitude of a (gap-filled) series; a simple crop-vs-bare cue."""
    y = np.asarray(y, float)
    if np.all(np.isnan(y)):
        return float("nan")
    return float(np.nanmax(y) - np.nanmin(y))


def peak_time(t_years, y):
    """Time of maximum greenness (peak of season) from a series."""
    t = np.asarray(t_years, float); y = np.asarray(y, float)
    if np.all(np.isnan(y)):
        return float("nan")
    return float(t[int(np.nanargmax(y))])
# tests/test_agri.py
import numpy as np
from eo_portfolio.agri import evi, gap_fill, harmonic_fit, ndvi, peak_time, season_amplitude


def test_ndvi_known():
    assert abs(float(ndvi(np.array(0.4), np.array(0.1))) - 0.6) < 1e-6


def test_evi_known():
    assert abs(float(evi(np.array(0.4), np.array(0.1), np.array(0.05))) - 0.46154) < 1e-4


def test_gap_fill_interpolates():
    y = np.array([0.0, np.nan, 2.0, np.nan, 4.0])
    assert np.allclose(gap_fill(y), [0.0, 1.0, 2.0, 3.0, 4.0])


def test_harmonic_recovers_amplitude():
    t = np.linspace(0, 1, 48, endpoint=False)
    y = 0.4 + 0.5 * np.sin(2 * np.pi * t)
    _, amp, _ = harmonic_fit(t, y)
    assert abs(amp - 0.5) < 0.02


def test_season_amplitude_and_peak():
    t = np.linspace(0, 1, 48, endpoint=False)
    y = 0.4 + 0.5 * np.sin(2 * np.pi * t)
    assert abs(season_amplitude(y) - 1.0) < 0.05
    assert abs(peak_time(t, y) - 0.25) < 0.05

The tests encode the physical intuition: NDVI and EVI have known values, gaps interpolate, a harmonic fit recovers a known seasonal amplitude, and the peak of season lands where the curve peaks.

Step 2: Cropland mask, then crop type

Restrict to cropland using the WorldCereal temporary-crop extent or Digital Earth Africa cropland, then assemble labels from CropHarvest (and any local ground truth) for the crops in your area. Build a feature stack of per-pixel S1 and S2 phenology features and train a random forest, evaluated with spatial block cross-validation from Module 4.

Step 3: Honest map with an applicability mask

Produce the crop-type map, report random versus spatial accuracy and the gap, and mask the map to its area of applicability (the dissimilarity check from Module 9.6) so you never present confident classes on ground unlike your training data.

Step 4 (optional stretch): soil moisture or a foundation model

Either add a Sentinel-1 field-scale soil-moisture or irrigation layer, or swap the engineered features for a satellite time-series foundation model or AlphaEarth embeddings and benchmark it against the random forest on full and low-label regimes.

Step 5: Write-up and commit

A short note, "Crop-type mapping over [area], [season]," with the map, the applicability mask, the honest accuracy table, and a paragraph on the smallholder limitations. Commit agri.py, its tests, and the notebook.

git add src/eo_portfolio/agri.py tests/test_agri.py notebooks/m9-7_agriculture.ipynb writeups/crop_type_map.md
git commit -m "Module 9.7: crop-type map with phenology features, spatial CV, and an applicability mask"
git push

Checkpoint

Multiple choice (answers below).

  1. Why is a single-date image usually poor for separating crop types? (a) resolution is too coarse; (b) crops are defined by their trajectory across a season, not one date; (c) optical bands cannot see vegetation; (d) SAR is required.
  2. In the cloudy tropics, the sensor that keeps the season going is: (a) Landsat; (b) MODIS; (c) Sentinel-1 SAR; (d) SMAP.
  3. You must find where crops exist before classifying which crop. That first step is: (a) crop-type mapping; (b) a cropland mask; (c) yield modelling; (d) irrigation detection.
  4. SMAP soil moisture is best described as: (a) field-scale from active radar; (b) coarse-scale from a passive L-band radiometer (radar failed in 2015); (c) 10 m optical; (d) unavailable.
  5. For smallholder African fields, the most important honesty step is: (a) using the biggest model; (b) a random train/test split; (c) spatial cross-validation plus an area-of-applicability mask; (d) ignoring cloud.

Short answer (answers below).

  1. What does the amplitude of an annual harmonic fit tell you about a pixel?
  2. Which open sources would you use for a cropland mask and for crop-type labels over Africa?

Interview-style questions (practice out loud).

Answers. (1) b: a crop is a seasonal trajectory, so a time series separates types a single date cannot.
(2) c: Sentinel-1 SAR sees through cloud and carries the season where optical has gaps.
(3) b: mask cropland first, then classify crop type only where crops exist.
(4) b: SMAP is a coarse passive L-band radiometer; its radar failed in 2015, so it is regional, not field scale.
(5) c: fields cluster, so spatial cross-validation plus an applicability mask is the honest bar.
(6) A large amplitude means a strongly seasonal signal (a growing and harvested crop); a small amplitude means near-constant cover such as bare soil or evergreen vegetation.
(7) Cropland mask: ESA WorldCereal temporary-crop extent or Digital Earth Africa cropland extent. Crop-type labels: CropHarvest, WorldCereal reference data, EuroCrops or EuroCropsML for method practice, plus any local ground truth.


Deliverable

A tested agri.py (NDVI, EVI, gap-fill, harmonic phenology fit, season amplitude, peak of season), a spatially validated crop-type map over your area with random-versus-spatial accuracy and an area-of-applicability mask, and a short note on the smallholder limitations. Optionally a soil-moisture or irrigation layer, or a foundation-model benchmark.

What a hiring manager sees

Agriculture teams see endless crop maps and very few with honest spatial validation and a clear statement of where the map applies. Showing that you build phenology features from an S1 and S2 season, mask cropland before classifying, validate spatially, bound the map with an applicability mask, and speak plainly about smallholder and cloud limitations is exactly their bar. Naming the current landscape correctly (WorldCereal, Digital Earth Africa, CropHarvest, SMAP versus Sentinel-1 for soil moisture, and foundation models for the low-label regime) signals you track the field, which agriculture and food-security employers reward.

Currency note

Verified 23 August 2026. ESA WorldCereal is an open, dynamic 10 m system for global cropland, seasonal maize and cereal crop-type, and irrigation maps with confidence layers, plus open global crop calendars; Phase II runs 2023 to 2026, the current collection centres on 2021 with more products due by end 2026, so check coverage for your season. Digital Earth Africa's continental cropland extent is a provisional 2019 10 m product (Sentinel-2, random forest, CC-BY, on the Registry of Open Data on AWS), and DE Africa also provides crop-type workflows and a sandbox. SMAP (launched 2015) operates on its passive L-band radiometer at coarse scale; its radar failed in 2015 and its nominal end of life is around September 2026, so treat SMAP as regional and use Sentinel-1 for field-scale soil moisture. Open crop-label sets: CropHarvest (global pixel time series on Zenodo and GitHub), EuroCrops and EuroCropsML (Europe, on Zenodo), and Fields of the World (open field-boundary benchmark). Sentinel-1 and Sentinel-2 and HLS are the primary imagery; confirm STAC endpoints as in Module 1. Foundation models for crop time series (for example Presto) and AlphaEarth embeddings move quickly, so confirm the current release before use. Pin all versions in your Module 0.1 environment file (agri uses only NumPy).

Figures this module needs

  1. A crop phenology curve (NDVI over a season) with greenup, peak, and senescence marked, and two crops overlaid to show they differ.
  2. An annual harmonic fit over a noisy NDVI time series, showing the recovered seasonal curve and amplitude.
  3. A Sentinel-1 VH and VV season next to the optical NDVI season, showing SAR filling cloud gaps.
  4. Cropland mask then crop-type map, side by side (mask first, classify within).
  5. A crop-type map shown with and without its area-of-applicability mask.
  6. A smallholder scene at 10 m versus very high resolution, showing the mixed-pixel problem.
  7. A soil-moisture comparison: coarse SMAP versus field-scale Sentinel-1.
Previous9.6 Resources, Minerals, and EnergyNext10 Wildfire Systems End to End