Skip to content

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, (x−xmin⁡)/(xmax⁡−xmin⁡)(x - x_{\min}) / (x_{\max} - x_{\min}), 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 ​

IndexConstant in reflectance units?Safe on raw DN?
NDVI, NDWI, UI, BSInoneYes — a normalized difference is invariant to a gain applied to all bands equally
SAVIsoil adjustment L=0.5L = 0.5No
AFRIcoefficients 0.660.66 / 0.500.50 on SWIRNo

Adding L=0.5L = 0.5 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 ρ=DN×s+o\rho = \text{DN} \times s + o. Published values for the common analysis-ready products ship as RADIOMETRIC_PRESETS:

Python
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/")
PresetScaleOffset
landsat-c2-l22.75×10−52.75 \times 10^{-5}−0.2-0.2
sentinel2-l2a10−410^{-4}0.00.0
sentinel2-l2a-baseline410−410^{-4}−0.1-0.1
reflectance1.01.00.00.0

Sentinel-2 processing baseline 04.00 and later carries a BOA_ADD_OFFSET of −1000-1000, 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()
FormatPNGGeoTIFF
Valuesquantized to 256 levels per channelfull precision (float32 / int32)
Pixel gridresampled by dpi and bbox_inchesidentical to the input
CRS / transformnonecopied from the source band
Useinspection, reporting, figuresGIS 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:

Python
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:

  • float32 for continuous results — well beyond reflectance precision, half the size of float64 — and int32 for integer label maps such as classifications.
  • nodata = NaN for floating point output.
  • tiled=True, compress="deflate" with predictor=3 for float data, and BIGTIFF="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:

NDVI=NIR−RedNIR+Red\text{NDVI} = \frac{\text{NIR} - \text{Red}}{\text{NIR} + \text{Red}}

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 Red\text{Red} spectrum (0.02−0.080.02 - 0.08). Concurrently, the internal spongy mesophyll tissue strongly scatters near-infrared radiation back into space to prevent cellular overheating, driving high NIR\text{NIR} reflectance (0.40−0.700.40 - 0.70). This divergence causes the NDVI to approach its upper limit:

NDVIveg⟶+1.0\text{NDVI}_{\text{veg}} \longrightarrow +1.0

  • 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 NIR≈Red\text{NIR} \approx \text{Red}, 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 NIR\text{NIR} 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 (LL):

SAVI=NIR−RedNIR+Red+L×(1+L)\text{SAVI} = \frac{\text{NIR} - \text{Red}}{\text{NIR} + \text{Red} + L} \times (1 + L)

To optimize the index for intermediate or variable canopy covers, the module hardcodes the scaling factor to L=0.5L = 0.5:

SAVI=NIR−RedNIR+Red+0.5×1.5\text{SAVI} = \frac{\text{NIR} - \text{Red}}{\text{NIR} + \text{Red} + 0.5} \times 1.5

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 L=0.5L = 0.5 adjustment factor shifts the intersection of the soil line back to the coordinate origin, minimizing the effect of background soil brightness. The multiplicative scaler (1+L)=1.5(1 + L) = 1.5 ensures the final output remains comparable to the standard [−1.0,1.0][-1.0, 1.0] 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:

AFRI1.6=NIR−0.66×SWIR1.6NIR+0.66×SWIR1.6AFRI2.1=NIR−0.50×SWIR2.1NIR+0.50×SWIR2.1\text{AFRI}_{1.6} = \frac{\text{NIR} - 0.66 \times \text{SWIR1.6}}{\text{NIR} + 0.66 \times \text{SWIR1.6}} \qquad \text{AFRI}_{2.1} = \frac{\text{NIR} - 0.50 \times \text{SWIR2.1}}{\text{NIR} + 0.50 \times \text{SWIR2.1}}

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 (0.660.66 and 0.500.50): These are scaling factors applied to the SWIR band, not thresholds. They come from the empirical reflectance relationships ρ0.645≈0.66×ρ1.6\rho_{0.645} \approx 0.66 \times \rho_{1.6} and ρ0.469≈0.50×ρ2.1\rho_{0.469} \approx 0.50 \times \rho_{2.1} 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 (NIR−0.66)×SWIR1/(NIR+0.66×SWIR1)(\text{NIR} - 0.66) \times \text{SWIR1} / (\text{NIR} + 0.66 \times \text{SWIR1}), 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 [0,+1][0, +1] 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:

NDWI=Green−NIRGreen+NIR\text{NDWI} = \frac{\text{Green} - \text{NIR}}{\text{Green} + \text{NIR}}

Biophysical Interaction Properties ​

Clear surface water reflects slightly in the visible spectrum—peaking near the Green\text{Green} wavelength—but absorbs almost all incident light in the near-infrared (NIR\text{NIR}) band. This characteristic behavior yields a positive index value for water bodies:

NDWIwater⟶+1.0\text{NDWI}_{\text{water}} \longrightarrow +1.0

In contrast, land features like healthy vegetation or dry soils reflect much more strongly in the NIR\text{NIR} than in the Green\text{Green} 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:

BSI=(SWIR1+Red)−(NIR+Blue)(SWIR1+Red)+(NIR+Blue)\text{BSI} = \frac{(\text{SWIR1} + \text{Red}) - (\text{NIR} + \text{Blue})}{(\text{SWIR1} + \text{Red}) + (\text{NIR} + \text{Blue})}

Biophysical Interaction Properties ​

  • Four-Band Contrast: Bare soil and exposed rock are bright in SWIR1\text{SWIR1} and Red\text{Red} and comparatively dark in NIR\text{NIR} and Blue\text{Blue}. Vegetation is the exact inverse — a strong NIR\text{NIR} 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 SWIR1\text{SWIR1} 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:

BIlegacy=NIR−Green−RedNIR+Green+Red\text{BI}_{\text{legacy}} = \frac{\text{NIR} - \text{Green} - \text{Red}}{\text{NIR} + \text{Green} + \text{Red}}

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, (SWIR−NIR)/(10SWIR+TIR)(\text{SWIR} - \text{NIR}) / (10\sqrt{\text{SWIR} + \text{TIR}}) — 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:

UI=SWIR2−NIRSWIR2+NIR\text{UI} = \frac{\text{SWIR2} - \text{NIR}}{\text{SWIR2} + \text{NIR}}

Biophysical Interaction Properties ​

Man-made materials like concrete, asphalt, and stone maintain high reflectance in the longer short-wave infrared band (SWIR2\text{SWIR2}) but lack the internal cell structures that cause high near-infrared (NIR\text{NIR}) scattering. This gives urban features a positive index value:

UIurban⟶+1.0\text{UI}_{\text{urban}} \longrightarrow +1.0

Vegetated regions show the opposite response, strongly absorbing SWIR2\text{SWIR2} energy via leaf moisture while scattering NIR\text{NIR} 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 ClassTarget Mapping FeatureMathematical Equation MatrixOperational Value RangeLandsat 8/9 ChannelsSentinel-2 ChannelsOptimal Colormap
NDVICalculatorCanopy Health & DensityNIR−RedNIR+Red\frac{\text{NIR} - \text{Red}}{\text{NIR} + \text{Red}}[−1.0,  +1.0][-1.0, \,\, +1.0]B5, B4B8, B4'RdYlGn' / 'YlGn'
SAVICalculatorSparse / Arid ShrublandsNIR−RedNIR+Red+0.5×1.5\frac{\text{NIR} - \text{Red}}{\text{NIR} + \text{Red} + 0.5} \times 1.5[−1.0,  +1.0][-1.0, \,\, +1.0]B5, B4B8, B4'RdYlGn' / 'YlGn'
AFRICalculatorDense / Woody Forests, Hazy ScenesNIR−0.66⋅SWIR1NIR+0.66⋅SWIR1\frac{\text{NIR} - 0.66 \cdot \text{SWIR1}}{\text{NIR} + 0.66 \cdot \text{SWIR1}}[−1.0,  +1.0][-1.0, \,\, +1.0]B5, B6B8, B11'YlGn'
NDWICalculatorWater Bodies & HydrologyGreen−NIRGreen+NIR\frac{\text{Green} - \text{NIR}}{\text{Green} + \text{NIR}}[−1.0,  +1.0][-1.0, \,\, +1.0]B3, B5B3, B8'Blues'
BICalculatorBare Soil & Exposed Rock(SWIR1+Red)−(NIR+Blue)(SWIR1+Red)+(NIR+Blue)\frac{(\text{SWIR1} + \text{Red}) - (\text{NIR} + \text{Blue})}{(\text{SWIR1} + \text{Red}) + (\text{NIR} + \text{Blue})}[−1.0,  +1.0][-1.0, \,\, +1.0]B6, B4, B5, B2B11, B4, B8, B2'inferno' / 'hot'
UICalculatorBuilt-up Urban AreasSWIR2−NIRSWIR2+NIR\frac{\text{SWIR2} - \text{NIR}}{\text{SWIR2} + \text{NIR}}[−1.0,  +1.0][-1.0, \,\, +1.0]B7, B5B12, B8'coolwarm'

Operational Implementation Examples ​

Visualizing Vegetation Density (NDVI) ​

Python
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) ​

Python
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()

Built with VitePress