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
--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
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.
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.
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):
SOS — start of season (rising crossing)
POS — peak of season (curve maximum)
EOS — end of season (falling crossing)
amplitude and season_length (= EOS − SOS) are also written
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:
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.
With -c: field2023_classified (plus intermediate
field2023_feat_* feature rasters)
Notes and limitations
Requires numpy and scipy; -c additionally requires
scikit-learn.
Reads each input raster fully into memory via
grass.script.array, so processing time and memory scale with
region size × number of dates; use -f and/or a coarser region for
quick exploration before running the full fit.
The incidence angle (theta=) is treated as a single scalar for the
whole region; for large regions spanning a wide swath, consider
running per-tile with a locally appropriate value.
red/nir/swir must share acquisition dates (as produced by
r.in.sentinel, which imports one raster per band per date); vv
and vh likewise.
References
Meroni, M. et al. (2021). Comparing land surface phenology of major
European crops as derived from SAR and multispectral data of
Sentinel-1 and -2. Remote Sensing of Environment, 253.
(double hyperbolic tangent model, SOS50/EOS50/PS90)
Ma, H., Johansen, K. & McCabe, M.F. (2022). Monitoring Irrigation
Events and Crop Dynamics Using Sentinel-1 and Sentinel-2 Time
Series. Remote Sensing, 14(5), 1205. (Water Cloud Model
parameterisation, VWC-from-NDVI formula, irrigation-event detection
logic)
Pageot, Y. et al. (2020). Detection of Irrigated and Rainfed Crops
in Temperate Areas Using Sentinel-1 and Sentinel-2 Time Series.
Remote Sensing, 12(18), 3044. (cumulative-index feature engineering
for irrigation classification)
d’Andrimont, R. et al. (2021). From parcel to continental scale — A
first European crop type map based on Sentinel-1 and LUCAS
Copernicus in-situ observations. Remote Sensing of Environment, 266.
(Random Forest crop classification from VV/VH time series)
Veloso, A. et al. (2017). Understanding the temporal behavior of
crops using Sentinel-1 and Sentinel-2-like data for agricultural
applications. Remote Sensing of Environment, 199.
(crop-specific VV/VH/NDVI correlation behaviour by phenological
stage)
Harfenmeister, K. et al. (2021). Detecting Phenological Development
of Winter Wheat and Winter Barley Using Time Series of Sentinel-1
and Sentinel-2. Remote Sensing, 13(24), 5036. (breakpoint-based
phenology detection, BBCH linkage)
Xie, Q. & Niculescu, S. (2022). Mapping Crop Types and Monitoring
Their Phenology Using Sentinel-1 and Sentinel-2. Remote Sensing,
14(18), 4437. (backscatter-shape based germination/heading/harvest
detection)
See also
r.in.sentinel —
imports the Sentinel-1/2 STRDS this module consumes
t.in.era5 — a source of
the optional precip= STRDS