GRASS logo

NAME

t.crop.season - Derives crop development stage and irrigation status from Sentinel-1/Sentinel-2 time series (STRDS) over a period of interest.

KEYWORDS

imagery, temporal, satellite, Sentinel, Sentinel-1, Sentinel-2, crop, phenology, irrigation, agriculture

SYNOPSIS

t.crop.season
t.crop.season --help
t.crop.season [-cfm] red=name nir=name swir=name vv=name vh=name [precip=name] start=string end=string output=name [theta=float] [wcm_a=float] [wcm_b=float] [anomaly_threshold=float] [rain_threshold=float] [sos_fraction=float] [baseline_percentile=float] [peak_percentile=float] [min_amplitude=float] [min_cycle_days=float] [max_gap_days=float] [max_cycle_days=float] [smooth_window=integer] [max_cycles=integer] [sar_gap_days=float] [harvest_snap_days=float] [training=name] [training_column=name] [--overwrite] [--help] [--verbose] [--quiet] [--ui]

Flags:

-c
Train and apply a Random Forest classifier using the training vector (requires training= and training_column=)
-f
Fast mode - use amplitude-threshold phenology only, skip the per-pixel double-tanh curve fit
-m
Multi-cycle mode - detect ALL per-pixel planting(SOS)/harvest(EOS) cycles over the period (not just one whole-record fit), fusing Sentinel-2 NDVI (primary) with Sentinel-1 cross-ratio (cloud-gap-fill + harvest-date snapping) and with noise-merging and a minimum cycle length, suitable for annual crops with several cycles in the record AND for perennial/ratoon crops (e.g. sugarcane) with one long cycle. Writes _cycleN_sos/_eos, _ncycles, _last_sos/_last_eos/_last_still_growing (the last-cycle triplet t.crop.yield's season_sos=/season_eos=/season_still_growing= options expect).
--overwrite
Allow output files to overwrite existing files
--help
Print usage summary
--verbose
Verbose module output
--quiet
Quiet module output
--ui
Force launching GUI dialog

Parameters:

red=name [required]
STRDS of Sentinel-2 red reflectance (e.g. B04)
nir=name [required]
STRDS of Sentinel-2 NIR reflectance (e.g. B08)
swir=name [required]
STRDS of Sentinel-2 SWIR reflectance (e.g. B11), used for NDWI
vv=name [required]
STRDS of Sentinel-1 VV backscatter (dB)
vh=name [required]
STRDS of Sentinel-1 VH backscatter (dB)
precip=name
Optional STRDS of precipitation (e.g. from t.in.era5), used to separate rain from irrigation
start=string [required]
Start date of period of interest (YYYY-MM-DD)
end=string [required]
End date of period of interest (YYYY-MM-DD)
output=name [required]
Prefix for output raster maps and STRDS
theta=float
Sentinel-1 incidence angle in degrees, used by the Water Cloud Model
Default: 31.0
wcm_a=float
Water Cloud Model vegetation coefficient A (Bindlish & Barros)
Default: 0.0012
wcm_b=float
Water Cloud Model vegetation coefficient B (Bindlish & Barros)
Default: 0.091
anomaly_threshold=float
Soil backscatter anomaly threshold (dB above dry baseline) to flag a wetting event
Default: 1.5
rain_threshold=float
Precipitation threshold (mm, summed over the 3 days before a SAR date) below which a wetting event is attributed to irrigation rather than rain
Default: 2.0
sos_fraction=float
Fraction of seasonal amplitude used to define start/end of season crossings
Default: 0.5
baseline_percentile=float
Percentile of the NDVI time series used as the season baseline (background/bare-soil level)
Default: 5.0
peak_percentile=float
Percentile of the NDVI time series used as the season peak level
Default: 95.0
min_amplitude=float
Minimum NDVI amplitude (peak_percentile - baseline_percentile, over the whole period) for a pixel to be considered to have any detectable crop cycle at all, used by multi-cycle detection (-m)
Default: 0.1
min_cycle_days=float
Minimum SOS-to-EOS duration (days) for a detected cycle to be kept (rejects noise-driven blips); raise for perennial crops (e.g. 270 for sugarcane)
Default: 60
max_gap_days=float
Below-threshold gaps shorter than this (days) are merged into the surrounding cycle instead of ending it - needed so a brief cloud-noise dip or a perennial crop's inter-ratoon canopy dip doesn't fragment one real cycle into several. Do NOT set this very large (e.g. 120) to compensate for a suspected over-fragmentation problem - a large value bridges almost any sequence of scattered short dips into one mega-cycle instead; use max_cycle_days= as the ceiling on cycle length instead
Default: 45
max_cycle_days=float
Hard ceiling (days) on a single cycle's SOS-to-EOS span - cycles longer than this are dropped as unresolved rather than reported as a (likely inflated) single cycle. Needed because some low-contrast/noisy pixels never show one clean gap long enough for max_gap_days to split them, however tuned; default 730 (~24 months) suits most annual/perennial crops including sugarcane, raise only if a real single cycle in your crop legitimately exceeds it
Default: 730
smooth_window=integer
Rolling-median smoothing window (in samples) applied to each pixel's NDVI series before multi-cycle threshold-crossing detection (-m); 1 disables smoothing
Default: 3
max_cycles=integer
Maximum number of per-pixel cycles to keep/write with -m (a pixel with more detected cycles keeps only the most recent max_cycles)
Default: 5
sar_gap_days=float
With -m, a Sentinel-1 (cross-ratio) observation is only added to the crossing-detection series if no NDVI observation exists within this many days of it - lets SAR fill cloud gaps without displacing optical data where both are available
Default: 10
harvest_snap_days=float
With -m, each detected harvest (EOS) date is refined to the sharpest Sentinel-1 cross-ratio drop within this many days of the NDVI-based estimate (a ratoon cut's bare-soil exposure is a cleaner SAR signal than NDVI's gradual senescence); 0 disables snapping
Default: 20
training=name
Name of input vector map
Optional vector map of training points/polygons for Random Forest classification
training_column=name
Attribute column in the training vector holding class labels

Table of contents

DESCRIPTION

A GRASS GIS addon that derives crop development stage and irrigation status from fused Sentinel-1 SAR and Sentinel-2 optical time series (Space-Time Raster Datasets, STRDS) over a user-defined period of interest.

t.crop.season red=s2_B04 nir=s2_B08 swir=s2_B11 \
              vv=s1_VV vh=s1_VH \
              start=2023-03-01 end=2023-10-01 \
              output=field2023

It is designed to sit downstream of r.in.sentinel, which imports Sentinel-1/2 scenes and can register them directly as per-band STRDS (strds= option) with the naming this module expects.

Why

Individually, optical and SAR time series each carry partial information about a crop’s growth: NDVI tracks chlorophyll/canopy cover but is blind on cloudy days; VV/VH backscatter is cloud-independent and structurally sensitive but noisier and confounded by soil moisture. Fusing both, as demonstrated across a body of recent remote-sensing literature (see References), gives more complete phenology timing and opens the door to detecting irrigation events from the SAR soil-moisture signal that optical data cannot see. t.crop.season implements that fusion as a single GRASS temporal-framework tool.

What it computes

1. Indices (per acquisition date)

Each is registered as its own output STRDS (<output>_ndvi, <output>_ndwi, <output>_cr).

2. Phenology (per pixel, over the whole period)

By default, t.crop.season fits the double hyperbolic tangent phenology model of Meroni et al. (2021) to each pixel’s NDVI time series:

VI(t) = a0 + a1*(tanh((t-a2)*a3)+1)/2 + a4*(tanh((t-a5)*a6)+1)/2 - a4

where a0 is the background/bare-soil level, a1/a4 are the green-up/decay amplitudes, a2/a5 the inflection timings and a3/a6 the steepness of green-up/senescence. The fit is fed the same amplitude-threshold estimate used as its initial guess (see below), so a converged fit and its fallback agree closely near the crossing points; the fit mainly resolves the noise the raw threshold crossing is sensitive to.

From the fitted (or fallback) curve, three phenometrics are derived using a configurable fraction of the seasonal amplitude (sos_fraction=, default 50%, matching Meroni et al.’s SOS50/EOS50):

If a pixel has too few valid observations, too little seasonal amplitude, or the nonlinear fit does not converge, the module falls back to the same threshold crossing computed directly on the raw (unfitted) percentile-normalised series, rather than leaving the pixel empty.

Pass -f (fast mode) to skip the per-pixel curve fit everywhere and use the vectorised amplitude-threshold method only — much faster on large regions, at some cost in noise robustness.

3. Per-date development-stage classification

Each optical acquisition date is classified, per pixel, into one of six stages based on its position relative to that pixel’s SOS/POS/EOS:

code stage
0 bare / pre-emergence
1 emergence / early vegetative
2 vegetative
3 reproductive / peak
4 senescence
5 post-harvest / bare

Written as the <output>_stage STRDS.

4. Irrigation status (Water Cloud Model soil anomaly)

For each SAR acquisition date, t.crop.season estimates vegetation water content from NDVI (interpolated onto the SAR date axis), following Ma, Johansen & McCabe (2022):

VWC = 0.098 * exp(4.225 * NDVI)

and inverts the Water Cloud Model (Attema & Ulaby 1978; Bindlish & Barros parameterisation wcm_a=/wcm_b=, default A=0.0012, B=0.091) to isolate the soil-driven backscatter component from the observed VV (and VH) backscatter:

tau2       = exp(-2 * wcm_b * VWC / cos(theta))
sigma_veg  = wcm_a * VWC * cos(theta) * (1 - tau2)
sigma_soil = (sigma_obs_linear - sigma_veg) / tau2

Each pixel’s driest observed soil-backscatter level (10th percentile over the period) is taken as a dry-soil baseline; the anomaly above that baseline, in dB, is written as the <output>_vvanomaly STRDS. Where the anomaly exceeds anomaly_threshold= (default 1.5 dB), a wetting event is flagged in <output>_wetflag:

code meaning
0 no wetting event
1 irrigation event
2 rain event

Rain and irrigation produce the same backscatter signature and cannot be told apart from SAR alone. If an optional precip= STRDS is supplied (e.g. from t.in.era5), wetting events that coincide with more than rain_threshold= mm (default 2 mm, summed over the 3 days before the SAR date) of precipitation are reclassified as rain (code 2) rather than irrigation (code 1). Without precip=, all wetting events are reported as irrigation and this caveat is logged as a warning.

5. Optional Random Forest classification (-c)

If a labeled training= vector (points or polygons; polygons are converted to centroids) with a training_column= attribute is supplied, -c trains a scikit-learn RandomForestClassifier on a feature stack built from this module’s own outputs — cumulative NDVI/NDWI over the period (Pageot et al. 2020 style), the phenology metrics (SOS/POS/EOS/amplitude), and summary statistics of the irrigation anomaly signal (mean/max anomaly, wetting-event count) — and applies it to the full raster to produce <output>_classified. This mirrors the irrigated/rainfed classification approach of Pageot et al. (2020) without hard-coding it to that one use case: the same mechanism works for any two-or-more-class label set (e.g. crop type, growth-stage anomaly, water-stress class) the user’s training data defines.

Output naming

Given output=field2023:

Notes and limitations

References

See also

SOURCE CODE

Available at: t.crop.season source code (history)

Accessed: Monday Jul 27 12:30:05 2026


Main index | Temporal index | Topics index | Keywords index | Graphical index | Full index

© 2003-2026 GRASS Development Team, GRASS 8.5.1dev Reference Manual