Compute vegetation indices (NDVI, NDRE, SAVI, GNDVI, EVI, MSAVI2, OSAVI, NDWI) from multi-band drone and satellite GeoTIFFs from the command line.
Built and maintained by ag-gis — Precision Ag Python & GIS Automation.
Calculating a vegetation index from a drone orthomosaic should be one command,
not a notebook. agindex is a small, focused CLI for that workflow:
- Reads any GDAL-supported multi-band raster (GeoTIFF, COG, …).
- Preserves CRS, transform, and nodata in the output.
- Emits a tiled, deflate-compressed float32 GeoTIFF ready for QGIS / ArcGIS.
- Optional PNG preview with a colormap for quick visual QA.
- Sensor presets for common drone payloads (Micasense RedEdge, DJI P4 MS,
Parrot Sequoia) plus explicit
--*-bandoverrides.
pip install agindex # core CLI
pip install "agindex[viz]" # adds matplotlib for --png previewsRequires Python 3.10+, numpy, rasterio, click.
The repo ships with a tiny synthetic 5-band GeoTIFF you can run on immediately:
git clone https://github.com/ag-gis/agindex.git
cd agindex
pip install -e ".[viz]"
agindex compute samples/field_5band.tif \
--index ndvi \
--preset micasense-rededge \
-o out/ndvi.tif \
--png out/ndvi.pngOutput:
wrote out/ndvi.tif (NDVI, 192x128)
count= 24576 min=-0.0432 max=+0.8910 mean=+0.5023 std=0.3072
wrote out/ndvi.png (PNG preview, colormap=RdYlGn)
agindex list # show supported indices and formulas
agindex presets # show sensor band-ordering presets
agindex compute INPUT.tif --index NDVI --out result.tif [options]
| Option | Purpose |
|---|---|
--index |
One of: ndvi, ndre, gndvi, savi, osavi, evi, msavi2, ndwi |
--preset |
micasense-rededge, dji-p4-multispectral, parrot-sequoia, rgb |
--red-band etc. |
Per-band 1-indexed override (wins over the preset) |
--png |
Also write a PNG preview |
--colormap |
Matplotlib colormap for the PNG (default RdYlGn) |
--stats/--no-stats |
Print min/max/mean/std after writing (default on) |
NDVI from a DJI P4 Multispectral orthomosaic:
agindex compute ortho.tif --index ndvi --preset dji-p4-multispectral -o ndvi.tifNDRE with explicit bands (e.g. a custom stack):
agindex compute stack.tif --index ndre --nir-band 5 --rededge-band 4 -o ndre.tifEVI with a quick PNG for the field report:
agindex compute ortho.tif --index evi --preset micasense-rededge \
-o evi.tif --png evi.png --colormap viridis| Short | Name | Formula | Uses |
|---|---|---|---|
| ndvi | NDVI | (NIR − RED) / (NIR + RED) | General vigor |
| ndre | NDRE | (NIR − REDEDGE) / (NIR + REDEDGE) | Canopy N, mid-late season |
| gndvi | GNDVI | (NIR − GREEN) / (NIR + GREEN) | Chlorophyll at high LAI |
| savi | SAVI | (1+L)(NIR−RED) / (NIR+RED+L), L=0.5 | Low-LAI / bare-soil scenes |
| osavi | OSAVI | (1+0.16)(NIR−RED) / (NIR+RED+0.16) | Robust across canopies |
| evi | EVI | 2.5·(NIR−RED) / (NIR + 6·RED − 7.5·BLUE + 1) | Atmospheric / soil resistant |
| msavi2 | MSAVI2 | 0.5·(2·NIR+1 − √((2·NIR+1)² − 8·(NIR−RED))) | Self-adjusting SAVI |
| ndwi | NDWI | (GREEN − NIR) / (GREEN + NIR) | Surface water (drone form) |
Run agindex list for the canonical list at runtime.
Inputs are read as float64 and pixels equal to the source nodata become NaN.
Indices propagate NaN through divisions and sqrt. On write, NaNs are encoded
as -9999.0 with nodata=-9999.0 in the GeoTIFF profile so downstream GIS
software treats them correctly.
import numpy as np
import rasterio
from agindex import compute_index
with rasterio.open("ortho.tif") as src:
red = src.read(3).astype("float64")
nir = src.read(5).astype("float64")
ndvi = compute_index("ndvi", {"red": red, "nir": nir})git clone https://github.com/ag-gis/agindex.git
cd agindex
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
python samples/generate.py # regenerate the sample GeoTIFFMIT — see LICENSE.