Predict crop yield from multispectral reflectance bands and the vegetation indices derived from them. The project ships a synthetic field generator, a small set of spectral indices including NDVI, and a regressor that reads the bands to estimate yield in tonnes per hectare.
The idea is the one remote sensing people rely on every season: a healthy canopy reflects strongly in the near infrared and absorbs in the red, so the contrast between those two bands tells you something real about how much the field will produce. NDVI is that contrast written as a single number.
src/
indices.py NDVI, GNDVI, SAVI and a helper that appends them as features
data.py synthetic multispectral field generator with a known yield law
model.py YieldRegressor (bands plus indices into gradient boosted trees)
tests/
test_indices.py NDVI matches its formula, stays bounded, handles zeros
test_model.py the model beats the mean baseline and tracks the truth
Every field plot carries a hidden canopy vigour value. Vigour pushes the near infrared band up and the red band down, and it also lifts the true yield. Because the bands and the yield share that one driver, the bands genuinely carry yield information rather than noise. A soil brightness term is mixed in as a nuisance so the model has to separate canopy signal from background.
Reflectance is clipped to the valid [0, 1] range. Yield follows a monotone curve in vigour with a mild saturating term, then a small amount of Gaussian noise so no model can fit it perfectly.
The normalized difference vegetation index is
NDVI = (NIR - RED) / (NIR + RED)
For non negative reflectance this sits in [-1, 1]. Positive values mean live vegetation, values near zero mean bare soil or rock, and negative values come from water or cloud. When both bands are essentially zero the ratio is undefined, and the implementation returns 0.0 there so the output stays finite.
Features are the four raw bands plus NDVI, GNDVI and SAVI. Those go through a
standardization step and then a HistGradientBoostingRegressor from
scikit-learn. Gradient boosted trees handle the saturating yield curve without
any manual feature engineering beyond the indices.
On a held out split of 800 synthetic plots a single run produced a mean absolute error of about 0.37 tonnes per hectare against a mean baseline of about 1.84, so the model lands near one fifth of the baseline error. The correlation between predicted and true yield on that split was about 0.98. These are the numbers from the synthetic generator at its default seed and will shift if you change the seed, the noise level, or the sample count.
Install the dependencies and run the tests:
pip install -r requirements.txt
python -m pytest tests/ -q
The tests are behavior checks. They confirm that NDVI reproduces its formula to floating point tolerance, stays inside its bounds, and is safe on zero bands, and they confirm that the trained model beats the mean baseline in mean absolute error and correlates strongly with the true yield. Everything runs on CPU in a couple of seconds with no downloads and no API keys.