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:
- Explain what optical and SAR see in a field, and why crop mapping is a time-series problem, not a single-date one.
- Compute vegetation indices and phenology features (greenup, peak, senescence, amplitude) from a Sentinel-1 and Sentinel-2 season.
- Distinguish cropland masking from crop-type classification, and use the right open label sources for each.
- Handle the smallholder reality of African agriculture: small mixed plots, cloud, mixed pixels, and scarce labels.
- Use soil moisture and irrigation signals (SMAP at coarse scale, Sentinel-1 at field scale) and connect them to drought and food-security monitoring.
- Ship a tested
agri.pyand 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.
- Optical (Sentinel-2, HLS): vegetation indices track greenness and structure. NDVI is the workhorse; EVI reduces saturation over dense canopy; the red-edge bands (Sentinel-2 has three) are unusually informative for crop stress and type. Cloud is the enemy, so you composite and gap-fill.
- SAR (Sentinel-1): VV and VH backscatter respond to canopy structure and moisture and work through cloud, which matters enormously in the cloudy tropics. SAR often carries the season where optical has gaps, and the VH/VV ratio and its temporal shape are strong crop features.
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:
- Greenup, peak, and senescence timing, the length of the growing season, and the amplitude of the NDVI curve.
- A one-cycle harmonic fit (mean plus a sine and cosine at the annual frequency) compresses a noisy series into a few robust numbers; the amplitude alone separates a strongly seasonal crop from near-constant bare soil or evergreen cover.
- Crop calendars tell you when each crop is green in your region, so you sample the right months. ESA WorldCereal publishes open global crop calendars for wheat and maize.
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:
- Cropland mask: is this pixel cropped at all? A binary map that removes forest, water, and built-up land so you only classify crops where crops exist. Open products: ESA WorldCereal temporary-crop extent (10 m, global) and Digital Earth Africa's continental cropland extent (10 m, provisional 2019, CC-BY).
- Crop type: given cropland, which crop? A multi-class classification on the phenology and backscatter features. Open labels: CropHarvest (global, pixel time series with crop labels), EuroCrops and EuroCropsML (Europe, many classes), and WorldCereal reference data. For the field outlines themselves, Fields of the World is an open field-boundary benchmark.
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:
- Small mixed plots fall below or near the pixel size, so mixed pixels are the norm; 10 m Sentinel data is often the practical floor, and very-high-resolution imagery helps for boundaries.
- Persistent cloud in the growing season makes SAR essential, not optional.
- Scarce, biased labels mean you must be honest about where the model applies (carry the area-of-applicability idea from Module 9.6) and validate spatially (Module 4), because fields cluster and a random split will flatter you.
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
- Soil moisture: SMAP provides an open, decade-long L-band record, but only from its passive radiometer (the radar failed in 2015) and at coarse scale (tens of kilometres), so it is a regional drought and water-balance tool, not field scale. For field-scale soil moisture, Sentinel-1 backscatter (with careful vegetation and roughness handling) is the practical route.
- Irrigation: irrigated fields stay green through the dry season and show a distinct backscatter and NDVI signature against rain-fed neighbours; WorldCereal publishes seasonal irrigation maps.
- Drought and water balance: NDVI and evapotranspiration anomalies against a multi-year baseline drive drought indices and feed food-security early warning (FEWS NET, GEOGLAM).
Lesson 6: Yield, food security, and the frontier
- Yield: peak or integrated NDVI over the season, combined with weather and a crop model, predicts yield; the honest version reports uncertainty and validates against reported statistics.
- Food-security early warning: anomalies in greenness, rainfall, and soil moisture against a baseline flag areas of concern; this is the operational backbone of FEWS NET and GEOGLAM, and it is where public-good and humanitarian roles sit.
- Frontier: geospatial foundation models and satellite time-series transformers (for example Presto for pixel time series, plus the AlphaEarth embeddings from Module 8) increasingly replace hand-built phenology features, especially in the low-label regime that dominates African agriculture. Treat them as one more feature source and benchmark them honestly against your engineered features, as in Module 8.
Guided lab (about 9 hours): a crop-type map you can defend
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).
- 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.
- In the cloudy tropics, the sensor that keeps the season going is: (a) Landsat; (b) MODIS; (c) Sentinel-1 SAR; (d) SMAP.
- 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.
- 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.
- 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).
- What does the amplitude of an annual harmonic fit tell you about a pixel?
- Which open sources would you use for a cropland mask and for crop-type labels over Africa?
Interview-style questions (practice out loud).
- Walk me through building a crop-type map for a smallholder region, and how you would prove where it should not be trusted.
- Optical is cloudy for most of your growing season. How do you still deliver a crop map?
- How would you estimate yield from satellites, and how would you report uncertainty?
- When would a foundation model or embeddings beat hand-built phenology features, and how would you show it?
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
- A crop phenology curve (NDVI over a season) with greenup, peak, and senescence marked, and two crops overlaid to show they differ.
- An annual harmonic fit over a noisy NDVI time series, showing the recovered seasonal curve and amplitude.
- A Sentinel-1 VH and VV season next to the optical NDVI season, showing SAR filling cloud gaps.
- Cropland mask then crop-type map, side by side (mask first, classify within).
- A crop-type map shown with and without its area-of-applicability mask.
- A smallholder scene at 10 m versus very high resolution, showing the mixed-pixel problem.
- A soil-moisture comparison: coarse SMAP versus field-scale Sentinel-1.