Spectral Indices
Overview
The indices module provides algebraic raster calculation pipelines designed to extract quantitative geophysical properties from multi-spectral satellite imagery. Every target surface feature exhibits a unique spectral signature—a characteristic pattern of electromagnetic radiation reflection and absorption across different wavelengths.
By calculating normalized differences, empirical scaling offsets, and non-linear band ratios, these calculators isolate specific surface materials, such as chlorophyll-heavy plant canopies, exposed mineral soils, open water bodies, and artificial urban structures.
┌────────────────────────────────┐
│ Input Spectral TIF Bands │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ self.files_handler Ingestion │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ Radiometric Scaling (optional) │ ──► $\rho = \text{DN} \times s + o$
└───────────────┬────────────────┘
│
┌──────────────────────────┴──────────────────────────┐
▼ ▼
[Single-Band Processing Arrays] [Multi-Band Raster Math Engine]
├─ NDVICalculator (NIR, Red) ├─ BICalculator / BSI
├─ NDWICalculator (Green, NIR) │ (SWIR1, Red, NIR, Blue)
├─ UICalculator (SWIR2, NIR) └─ SAVICalculator (NIR, Red, $L=0.5$)
└─ AFRICalculator (NIR, SWIR1 | SWIR2)
│
▼
┌────────────────────────────────┐
│ Dimensionless Float Output │ ──► Dynamic range: $[-1.0, 1.0]$ or $[0.0, 1.0]$
└────────────────────────────────┘Radiometric Input Requirements
Indices are computed on the band values as read. No rescaling is applied unless you ask for it.
Changed in 1.4.0. Earlier releases routed every index through a per-band min–max rescale, , applied independently to each band. Because each band received a different affine transform, this altered the relationships between bands — and those relationships are the entire physical content of a spectral index. Three consequences: published thresholds did not apply, values were not comparable across scenes or dates, and a pixel's value depended on how much of the image you loaded, since the rescale used the loaded extent's own extrema. Index values from earlier versions are not comparable with current output.
Measured on the bundled example, the rescale inverted inter-band relationships outright: AFRI's correlation with NDVI ran +0.71 on the values as stored and −0.69 after rescaling, against a source paper that reports the two as nearly identical. Min–max normalization remains in use for the enhancement, HSV, PCA and SVM modules, where rescaling is appropriate — and for SVM it is necessary, since an RBF kernel needs comparable feature scales.
Which indices need reflectance, and which do not
| Index | Constant in reflectance units? | Safe on raw DN? |
|---|---|---|
| NDVI, NDWI, UI, BSI | none | Yes — a normalized difference is invariant to a gain applied to all bands equally |
| SAVI | soil adjustment | No |
| AFRI | coefficients / on SWIR | No |
Adding to a digital number in the thousands contributes nothing, so SAVI on unscaled input is silently not SAVI. The same applies to AFRI's coefficients. Both emit a UserWarning when handed values far outside the reflectance range.
Supplying the scaling
Every index accepts scale_factor and offset, applied as . Published values for the common analysis-ready products ship as RADIOMETRIC_PRESETS:
from fezrs import SAVICalculator
from fezrs.utils.radiometry_handler import RADIOMETRIC_PRESETS
SAVICalculator(
nir_path="LC09_..._SR_B5.TIF",
red_path="LC09_..._SR_B4.TIF",
**RADIOMETRIC_PRESETS["landsat-c2-l2"], # scale 2.75e-5, offset -0.2
).execute(output_path="./exports/")| Preset | Scale | Offset |
|---|---|---|
landsat-c2-l2 | ||
sentinel2-l2a | ||
sentinel2-l2a-baseline4 | ||
reflectance |
Sentinel-2 processing baseline 04.00 and later carries a BOA_ADD_OFFSET of , hence the separate preset. If your product is already reflectance, the defaults are correct and nothing needs passing.
Output: Picture or Data Product
Every tool can write its result two ways, and the difference matters.
execute() | to_raster() | |
|---|---|---|
| Format | PNG | GeoTIFF |
| Values | quantized to 256 levels per channel | full precision (float32 / int32) |
| Pixel grid | resampled by dpi and bbox_inches | identical to the input |
| CRS / transform | none | copied from the source band |
| Use | inspection, reporting, figures | GIS overlay, zonal statistics, differencing |
execute() renders through matplotlib, with margins, colorbar and watermark baked in, so the result is a picture of the computed array rather than the array. A pixel that was 0.6237 cannot be recovered from it, which rules out thresholding, zonal statistics over mapped units, and multitemporal differencing. Use it for figures.
to_raster() writes the array itself:
from fezrs import NDVICalculator
calculator = NDVICalculator(nir_path="nir.tif", red_path="red.tif")
calculator.process()
calculator.to_raster("./exports/ndvi.tif")The output carries the CRS and affine transform of the source band, so it lands in the right place when opened over other layers in QGIS or ArcGIS. Multi-component results, such as PCA's (6, height, width), are written as multi-band rasters.
Defaults, chosen for scene-scale raster products:
float32for continuous results — well beyond reflectance precision, half the size offloat64— andint32for integer label maps such as classifications.nodata = NaNfor floating point output.tiled=True,compress="deflate"withpredictor=3for float data, andBIGTIFF="IF_SAFER".
Override any of them with dtype=, nodata= and compress=.
A source without a CRS raises. Writing an identity transform instead would produce a file that looks georeferenced while placing the scene at the coordinate origin. If the inputs carry no spatial referencing, execute() is the appropriate output.
Mathematical & Scientific Formulations
NDVICalculator (Normalized Difference Vegetation Index)
Scientific and Physical Objective
The NDVI is the standard index used to evaluate the presence, structural density, and photosynthetic health of green vegetation. It leverages the sharp transition—known as the red edge—between the strong visible light absorption of chlorophyll and the high near-infrared scattering of leaf tissues.
Mathematical Formulation
The mathematical index uses a normalized difference ratio:
Biophysical Interaction Properties
- Healthy Photosynthetic Canopies: Chlorophyll pigments in functional leaves absorb up to 90% of incident visible blue and red light to power photosynthesis, resulting in low reflectance in the spectrum (). Concurrently, the internal spongy mesophyll tissue strongly scatters near-infrared radiation back into space to prevent cellular overheating, driving high reflectance (). This divergence causes the NDVI to approach its upper limit:
Exposed Soils and Bedrock: Lacking chlorophyll structures or complex cellular scattering cavities, bare earth exhibits a steady, linear increase in reflectance across both the red and near-infrared bands. Because , the numerator shrinks toward zero, yielding baseline values between 0.1 and 0.2.
Open Water Bodies: Clear water strongly absorbs electromagnetic energy across the reflective infrared spectrum, dropping values to near zero while maintaining a slight reflectance in visible wavelengths. This results in a negative index value.
SAVICalculator (Soil-Adjusted Vegetation Index)
Scientific and Physical Objective
The Soil-Adjusted Vegetation Index modifies the standard NDVI calculation for areas with sparse vegetation, such as arid shrublands, semi-arid agricultural fields, or early-stage crops. In these environments, the underlying soil brightness introduces significant background variation that can distort standard vegetation index values.
Mathematical Formulation
Originally derived by Huete (1988), the index adds an empirical soil calibration factor ():
To optimize the index for intermediate or variable canopy covers, the module hardcodes the scaling factor to :
Biophysical Interaction Properties
In sparse landscapes, variations in soil moisture, organic matter, and roughness can shift the baseline "soil line" in spectral feature space, artificially inflating or depressing standard NDVI values.
The adjustment factor shifts the intersection of the soil line back to the coordinate origin, minimizing the effect of background soil brightness. The multiplicative scaler ensures the final output remains comparable to the standard range.
AFRICalculator (Aerosol Free Vegetation Index)
Scientific and Physical Objective
The AFRI is designed to map dense forest canopies and high-biomass woody vegetation while providing a path to bypass atmospheric aerosol scattering (like smoke, haze, or dust). It enhances structural forest signatures while reducing sensitivity to variations in solar illumination, terrain shadowing, and background soil signatures by utilizing the short-wave infrared band instead of visible red.
Mathematical Formulation
Karnieli et al. (2001) define two formulations, selected with the variant parameter:
Biophysical Interaction Properties
SWIR Substitution: AFRI is structurally NDVI with a SWIR band substituted for the visible red. Aerosol scattering is strongly wavelength dependent and falls off toward longer wavelengths, so a SWIR-based index sees through smoke, haze and dust that would depress a red-based index. This is what makes AFRI usable over biomass-burning plumes and dust-laden atmospheres where NDVI fails.
The Coefficients ( and ): These are scaling factors applied to the SWIR band, not thresholds. They come from the empirical reflectance relationships and reported in the source paper, and their role is to place the SWIR reflectance on the scale of the visible band it replaces. Applying the coefficient anywhere other than to the SWIR reflectance makes the expression dimensionally incoherent.
Consistency With NDVI: Under clear-sky conditions Karnieli et al. report that AFRI and NDVI values are almost identical. That equivalence is a practical validation check: on a haze-free scene, an AFRI implementation that does not correlate strongly and positively with NDVI is computing something else.
Changed in 1.4.0. Earlier releases computed , which subtracts a bare dimensionless constant from a reflectance and then multiplies by a band ratio. It is a different quantity from Karnieli's index, correlating with it at only 0.17, and it produced negative values over 96% of the bundled example while this page claimed a range. AFRI results from earlier versions are not comparable with current output.
NDWICalculator (Normalized Difference Water Index)
Scientific and Physical Objective
The NDWI outlines open water bodies (such as lakes, reservoirs, river channels, and wetlands) and separates them from surrounding land features based on the McFeeters (1996) specification.
Mathematical Formulation
The index calculates the normalized difference between the visible green and near-infrared bands:
Biophysical Interaction Properties
Clear surface water reflects slightly in the visible spectrum—peaking near the wavelength—but absorbs almost all incident light in the near-infrared () band. This characteristic behavior yields a positive index value for water bodies:
In contrast, land features like healthy vegetation or dry soils reflect much more strongly in the than in the band, producing negative values that clearly separate terrestrial features from open water.
BICalculator (Bare Soil Index)
Scientific and Physical Objective
The Bare Soil Index isolates exposed soil surfaces, agricultural fallow fields, mining areas, and bare rock outcroppings by contrasting short-wave infrared and red brightness against near-infrared and blue reflectance.
Mathematical Formulation
The default formulation is the Bare Soil Index (BSI), selected automatically when swir1_path and blue_path are supplied:
Biophysical Interaction Properties
Four-Band Contrast: Bare soil and exposed rock are bright in and and comparatively dark in and . Vegetation is the exact inverse — a strong plateau against low visible reflectance. Pairing the bands this way makes the numerator change sign between the two surface types, so BSI is positive over exposed ground and negative over canopy, which is what allows lithological exposure to be separated from vegetation cover.
Why SWIR Is Required: The term carries the soil-moisture and clay-mineral response that no visible-band combination reproduces. An index built only from visible and NIR bands cannot distinguish dry bare soil from a sparsely vegetated surface of similar visible brightness.
Legacy Formulation
Previous releases computed a different expression, which remains reachable by passing green_path instead of swir1_path/blue_path:
This is not a published bare-soil index. It subtracts two visible bands from NIR, so it responds primarily to vegetation brightness — dense vegetation yields positive values and bare soils cluster near zero or negative, the opposite of what the name implies. It is retained so existing results stay reproducible, and it emits a DeprecationWarning. Use the BSI formulation for new work.
Citation note. Earlier documentation attributed this module to As-syakur et al. (2012), which defines EBBI, — a different formula requiring a thermal band this tool does not accept. That citation has been removed. The legacy expression above has no published source and should be treated as an original formulation.
UICalculator (Urban Index)
Scientific and Physical Objective
The Urban Index detects and maps built-up areas, impervious surfaces, and artificial infrastructure (such as concrete, asphalt, and roofing materials) by leveraging the contrast between short-wave infrared and near-infrared reflectance.
Mathematical Formulation
The index is computed as:
Biophysical Interaction Properties
Man-made materials like concrete, asphalt, and stone maintain high reflectance in the longer short-wave infrared band () but lack the internal cell structures that cause high near-infrared () scattering. This gives urban features a positive index value:
Vegetated regions show the opposite response, strongly absorbing energy via leaf moisture while scattering light, resulting in highly negative values that separate urban areas from surrounding natural land covers.
Reference Matrix & Sensor Configurations
The following matrix cross-references the required sensor channels, target ranges, and optimal styling palettes across different satellite platforms:
| Index Class | Target Mapping Feature | Mathematical Equation Matrix | Operational Value Range | Landsat 8/9 Channels | Sentinel-2 Channels | Optimal Colormap |
|---|---|---|---|---|---|---|
NDVICalculator | Canopy Health & Density | B5, B4 | B8, B4 | 'RdYlGn' / 'YlGn' | ||
SAVICalculator | Sparse / Arid Shrublands | B5, B4 | B8, B4 | 'RdYlGn' / 'YlGn' | ||
AFRICalculator | Dense / Woody Forests, Hazy Scenes | B5, B6 | B8, B11 | 'YlGn' | ||
NDWICalculator | Water Bodies & Hydrology | B3, B5 | B3, B8 | 'Blues' | ||
BICalculator | Bare Soil & Exposed Rock | B6, B4, B5, B2 | B11, B4, B8, B2 | 'inferno' / 'hot' | ||
UICalculator | Built-up Urban Areas | B7, B5 | B12, B8 | 'coolwarm' |
Operational Implementation Examples
Visualizing Vegetation Density (NDVI)
from pathlib import Path
from fezrs.tools.spectral_indices import NDVICalculator
# Instantiate the NDVI processing engine using Landsat 8 paths
ndvi_engine = NDVICalculator(
nir_path=Path("./data/LC08_B5_NIR.tif"),
red_path=Path("./data/LC08_B4_Red.tif")
)
# Run calculations and export a stylized, color-mapped quick-look array
ndvi_engine.execute(
output_path="./exports/indices/",
title="Normalized Difference Vegetation Index (NDVI)",
colormap="RdYlGn",
show_colorbar=True
)Surface Hydrology Mapping (NDWI)
from pathlib import Path
from fezrs.tools.spectral_indices import NDWICalculator
# Instantiate McFeeters NDWI calculator using Sentinel-2 paths
ndwi_engine = NDWICalculator(
green_path=Path("./data/S2_B03_Green.tif"),
nir_path=Path("./data/S2_B08_NIR.tif")
)
# Extract water body boundaries
water_mask_raster = ndwi_engine.process()
