NDVI, NDWI, SAVI, GCI and PSRI with Sentinel-2: Formulas, Value Ranges and Google Earth Engine Code

In short

On Sentinel-2, NDVI = (B8 − B4) / (B8 + B4), McFeeters NDWI = (B3 − B8) / (B3 + B8), SAVI = 1.5 × (B8 − B4) / (B8 + B4 + 0.5), GCI = B8 / B3 − 1 and PSRI = (B4 − B2) / B6. Use NDVI for general vigour, SAVI where soil is visible, GCI for chlorophyll in dense canopies, PSRI for senescence and NDWI for open water.

NDVI (Sentinel-2)
(B8 − B4) / (B8 + B4)
NDWI, McFeeters (Sentinel-2)
(B3 − B8) / (B3 + B8)
SAVI (L = 0.5)
1.5 × (B8 − B4) / (B8 + B4 + 0.5)
GCI / PSRI
B8 / B3 − 1; (B4 − B2) / B6
GEE collection
COPERNICUS/S2_SR_HARMONIZED (reflectance × 10,000)

What are NDVI, NDWI, SAVI, GCI and PSRI?

Vegetation indices are simple band arithmetic that turn raw satellite reflectance into a number that tracks something biological: green biomass, canopy water, chlorophyll or senescence. Healthy leaves absorb most red light for photosynthesis and strongly reflect near-infrared (NIR), so ratios of those bands separate vegetation from soil and water. Each index in this guide answers a slightly different question, and choosing the right one matters more than computing many of them.

These are the five indices Bishal Neupane computes in his own Sentinel-2 work in Google Earth Engine (GEE), described on the methods page and in his CV: NDVI, NDWI, SAVI, GCI and PSRI. The formulas below follow the band mapping in the open Awesome Spectral Indices catalogue, which links each index to its original paper.

Which Sentinel-2 bands do you need?

Sentinel-2 carries a 13-band multispectral instrument. In the GEE collection COPERNICUS/S2_SR_HARMONIZED (surface reflectance, Level-2A), the bands used here are:

BandNameCentre wavelength (S2A)Pixel size
B2Blue496.6 nm10 m
B3Green560 nm10 m
B4Red664.5 nm10 m
B5Red edge 1703.9 nm20 m
B6Red edge 2740.2 nm20 m
B8NIR835.1 nm10 m
B11SWIR 11613.7 nm20 m

Values and resolutions are from the Earth Engine catalogue entry. Surface reflectance is stored as integers scaled by 10,000, so divide by 10,000 before using any index that contains a constant (SAVI is the one that breaks if you forget).

Sentinel-2 formulas for each index

IndexGeneral formulaSentinel-2 bandsOriginal reference
NDVI(NIR − Red) / (NIR + Red)(B8 − B4) / (B8 + B4)Rouse et al. 1974; Tucker 1979
NDWI (McFeeters)(Green − NIR) / (Green + NIR)(B3 − B8) / (B3 + B8)McFeeters 1996
SAVI(1 + L)(NIR − Red) / (NIR + Red + L), L = 0.51.5 × (B8 − B4) / (B8 + B4 + 0.5)Huete 1988
GCI (Chlorophyll Index Green)NIR / Green − 1B8 / B3 − 1Gitelson et al. 2003
PSRI(R678 − R500) / R750(B4 − B2) / B6Merzlyak et al. 1999

Two points are easy to get wrong. First, "NDWI" names two different indices: the McFeeters version above (green and NIR) is for open water, while Gao's 1996 NDWI uses NIR and SWIR (B8 and B11 on Sentinel-2) to track water inside leaves and is often called NDMI. The NDWI water-monitoring guide covers that distinction in detail. Second, PSRI was defined from reflectance at roughly 500, 678 and 750 nm; on Sentinel-2 those map to blue (B2), red (B4) and red edge 2 (B6), which is the mapping the Awesome Spectral Indices catalogue uses.

What do the values mean?

NDVI

NDVI runs from −1 to +1. According to the USGS phenology programme, barren rock, sand or snow usually sit at about 0.1 or less, shrubs, grassland and senescing crops at roughly 0.2 to 0.5, and dense vegetation or crops at peak growth at about 0.6 to 0.9. Open water is typically negative. NDVI saturates over dense canopies, so two healthy fields can look identical even when one has more biomass.

NDWI

McFeeters NDWI is positive over open water and usually negative over vegetation and dry soil, because those reflect more NIR than green. A negative value is therefore normal for a cropped field; it does not on its own indicate drought. The methods page shows an example value of −0.15 next to an example NDVI of 0.82, a typical pairing for a vegetated pixel.

SAVI

SAVI adds a soil-brightness correction factor L to the NDVI denominator. Huete proposed L = 0.5 as a general value for intermediate vegetation cover. It is most useful early in the season, on sparse or row crops, and on terraces where bare soil fills much of each 10 m pixel. Values are on a similar scale to NDVI but are not directly interchangeable with it.

GCI

GCI has no fixed upper limit; higher values mean more chlorophyll. Because it is a simple ratio it saturates less than NDVI in dense canopies, which makes it useful for comparing nitrogen or fertiliser treatments in crops that already look uniformly green in NDVI.

PSRI

PSRI rises as the carotenoid-to-chlorophyll ratio increases, which is what happens during senescence, ripening or stress-induced yellowing. Low or negative values indicate actively growing green tissue. It is best read as a change over time on the same field rather than as an absolute threshold.

NDVI vs SAVI vs GCI: when to use which

  • General crop vigour and phenology: NDVI. It is the most widely used and easiest to compare with published work.
  • Early season, sparse canopy, visible soil: SAVI, because soil background inflates or depresses NDVI.
  • Chlorophyll or nitrogen differences in a closed canopy: GCI, which keeps responding after NDVI flattens.
  • Ripening, senescence or yellowing: PSRI, tracked across dates.
  • Open water, flooding, ponds and lakes: McFeeters NDWI. For crop water content, use the NIR–SWIR form (NDMI).

In practice, computing several indices on the same cloud-free composite and comparing them is cheap in GEE. The value comes from reading them together with field knowledge, not from any single number.

Google Earth Engine code: computing all five indices

The script below loads harmonised Sentinel-2 surface reflectance, keeps scenes under 20% cloud, masks cloud, shadow and cirrus with the Scene Classification Layer (SCL classes 3, 8, 9 and 10), builds a median composite and computes the five indices. Paste it into the GEE Code Editor and change the area and dates.

// Area of interest: 5 km buffer around Pokhara (replace with your own field or district boundary) var aoi = ee.Geometry.Point([83.9856, 28.2096]).buffer(5000); // Mask cloud, cloud shadow and cirrus using the Scene Classification Layer, then scale to reflectance function maskS2(img) { var scl = img.select('SCL'); var clear = scl.neq(3).and(scl.neq(8)).and(scl.neq(9)).and(scl.neq(10)); return img.select(['B2', 'B3', 'B4', 'B6', 'B8', 'B11']) .updateMask(clear) .divide(10000) .copyProperties(img, ['system:time_start']); } var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') .filterBounds(aoi) .filterDate('2025-11-01', '2026-02-28') .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)) .map(function (img) { return ee.Image(maskS2(img)); }); var img = s2.median().clip(aoi); var ndvi = img.normalizedDifference(['B8', 'B4']).rename('NDVI'); var ndwi = img.normalizedDifference(['B3', 'B8']).rename('NDWI'); var savi = img.expression('1.5 * (NIR - RED) / (NIR + RED + 0.5)', { NIR: img.select('B8'), RED: img.select('B4')}).rename('SAVI'); var gci = img.select('B8').divide(img.select('B3')).subtract(1).rename('GCI'); var psri = img.expression('(RED - BLUE) / RE2', { RED: img.select('B4'), BLUE: img.select('B2'), RE2: img.select('B6')}).rename('PSRI'); var indices = ndvi.addBands([ndwi, savi, gci, psri]); Map.centerObject(aoi, 12); Map.addLayer(ndvi, {min: -0.2, max: 0.9, palette: ['blue', 'white', 'green']}, 'NDVI'); print(indices.reduceRegion({reducer: ee.Reducer.mean(), geometry: aoi, scale: 10, maxPixels: 1e9}));

A few notes on the code. normalizedDifference(['B8', 'B4']) computes (first − second) / (first + second), so band order matters: swapping them flips the sign. The cloud filter uses the scene-level CLOUDY_PIXEL_PERCENTAGE property; the SCL mask then removes remaining cloudy pixels. B6 is a 20 m band, so PSRI is effectively at 20 m detail even though it is displayed on a 10 m grid. For season-to-season comparisons, build one composite per season, as Bishal did with dry- and wet-season composites in his Kaski analysis (see the GEE land-cover guide).

Common mistakes

  • Mixing scenes from before and after 25 January 2022 in the older COPERNICUS/S2_SR collection. From that date, scenes processed with baseline 04.00 or later have their values shifted by 1,000; the harmonised collection shifts them back to the older range.
  • Applying SAVI to unscaled integers, which makes L = 0.5 meaningless.
  • Treating NDWI from two different papers as the same index.
  • Reading a single-date index map as a diagnosis. Indices flag where to look; field checks confirm what is happening.

For how these indices feed into land-cover mapping and machine learning, see the post on GIS, remote sensing and ML as one system. Researchers interested in collaborating on crop monitoring with Sentinel-2 can reach Bishal through the contact page.

Frequently asked questions

Which Sentinel-2 bands are used for NDVI?

NDVI on Sentinel-2 uses band 8 (near-infrared, about 835 nm, 10 m) and band 4 (red, about 665 nm, 10 m): NDVI = (B8 − B4) / (B8 + B4). Some users substitute B8A (narrow NIR, 20 m), but B8 keeps the full 10 m resolution. In Google Earth Engine the call is image.normalizedDifference(['B8', 'B4']).

What is the difference between NDVI and SAVI?

Both use red and near-infrared reflectance, but SAVI adds a soil-brightness factor L (usually 0.5) so that bare soil between plants distorts the value less. NDVI is the standard choice for closed canopies and comparison with other studies; SAVI is better early in the season, for sparse row crops, or where soil is visible within pixels.

Why is my NDWI negative over crop fields?

McFeeters NDWI, (Green − NIR) / (Green + NIR), is designed to be positive over open water. Vegetation reflects much more near-infrared than green light, so healthy crops normally give negative NDWI. A negative value over a field is expected and is not by itself evidence of drought; for crop water status use the NIR–SWIR index (NDMI) instead.

How is PSRI calculated with Sentinel-2?

PSRI was defined by Merzlyak and colleagues in 1999 as (R678 − R500) / R750. On Sentinel-2 this maps to (B4 − B2) / B6, using red, blue and red edge 2, as listed in the Awesome Spectral Indices catalogue. PSRI rises as leaves senesce or ripen, so it is most informative tracked over time on the same field.

Do I need to scale Sentinel-2 values before computing indices?

Yes for any index with a constant. Earth Engine stores Sentinel-2 surface reflectance multiplied by 10,000. Normalized-difference indices such as NDVI give the same result either way, but SAVI's L = 0.5 assumes reflectance between 0 and 1, so divide the bands by 10,000 first, as the example script does.

Sources