Paddy-field map
VH phenology features β SMOTE-balanced classifier β multi-year consensus
π English Β· Bahasa Indonesia β
The idea
Rice paddies have a distinctive VH backscatter trajectory β a deep flooding trough followed by a steep vegetative rise to a canopy peak. We turn a 7-period window of VH values into 29 engineered features (temporal values, differences, ratios, and phenology indicators) and classify each pixel as paddy / non-paddy.
Because paddy hugely outnumbers non-paddy in the training points (β92% vs 8%), a naive model becomes over-conservative. We fix this with SMOTE (synthetic minority over-sampling) to a balanced 50/50 set β the single most important step for realistic detection.
Step 1 β Train the model (runs from shipped data)
The repository ships pre-extracted features, so training needs no satellite data:
export CUDA_VISIBLE_DEVICES=-1
python train_paddy_vh_smote.pyWhat happens:
- loads
model_files_paddy_vh/training_data_*.csv(29 VH features per point), - applies SMOTE to balance the classes,
- trains an MLP with 5-fold stratified cross-validation,
- writes
model_files_paddy_vh/paddy_vh_model.keras+scaler.joblib.
Expected cross-validation performance (full study):
| Metric | Value |
|---|---|
| Accuracy | 95.4% Β± 0.2% |
| AUC-ROC | 98.3% Β± 0.1% |
A trained model already ships in model_files_paddy_vh/ β you can skip training and go straight to prediction.
Step 2 β Predict on the AOI stack
Point the predictor at the Sentinel-1 stack you fetched in Setup. Because the sample stack is a single year starting at band 1, use --year-start-band 1 and set --year-periods to the number of bands you downloaded (periods 7β24 β 18 bands).
python predict_paddy_vh_multiyear.py \
--period 15 --year 2024 \
--year-start-band 1 --year-periods 18 \
--vh-stack data/sample/klambu_vh_2024.tif \
--mask data/sample/klambu_mask.tif \
--skip-test \
--output-dir predictions_sample/2024/period_15Outputs (per period):
predictions_sample/2024/period_15/
βββ paddy_predictions.tif # 0 = non-paddy, 1 = paddy
βββ confidence.tif # 0β1 probability
βββ paddy_map.png # quick-look
βββ statistics.txt # area + confidence summary
The 29 features use the current period plus the 6 previous periods. Predict on periods where at least 7 prior bands exist (e.g. period 13+ if your stack starts at period 7). For earlier periods, fetch a stack that starts earlier.
Batch several periods to see the season evolve:
for p in 13 15 17 19 21 23; do
python predict_paddy_vh_multiyear.py --period $p --year 2024 \
--year-start-band 1 --year-periods 18 \
--vh-stack data/sample/klambu_vh_2024.tif --mask data/sample/klambu_mask.tif \
--skip-test --output-dir predictions_sample/2024/period_$p
doneStep 3 β Multi-year consensus (stable paddy)
A single period includes transient false positives. The consensus keeps only pixels detected as paddy consistently β ideally across two full years, with confidence and detection-count thresholds. With one sample year you can still build a within-year consensus:
python create_multiyear_composite_paddy.py \
--years 2024 \
--periods 2024:"13 15 17 19 21 23" \
--predictions-dir predictions_sample \
--output consensus_sample \
--min-years 1 --min-detections 3 \
--min-confidence 0.7 --min-mean-confidence 0.6The --periods flag takes one YEAR:"p1 p2 ..." argument per year and is repeatable β e.g. for the full two-year consensus: --periods 2024:"7 9 11" --periods 2025:"7 9 11" --min-years 2.
The consensus rules that matter:
--min-confidence 0.7β each detection must be confident,--min-detections 3β must recur across β₯3 periods,--min-years 2(full study) β must appear in both years β filters transient false positives.
On the full Java stack this yields the headline stable-paddy map:

From βdetectedβ to βcultivatedβ: the cycle-based map
Consensus marks persistence; it does not check that a pixel truly completed a rice cycle. The recommended end-product intersects the consensus with a β₯1 complete floodβcanopy cycle test (see the Planting index chapter) β this rejects persistent non-rice false positives (e.g. highland plantations) and yields the active-paddy map.

Next: turn the temporal signal into a planting index β Planting index.