Skip to content

FIX AFRI formula and reconcile the bare soil index with its reference - #51

Merged
aradfarahani merged 1 commit into
hotfix/glcm-quantizationfrom
hotfix/afri-formula
Aug 21, 2026
Merged

FIX AFRI formula and reconcile the bare soil index with its reference#51
aradfarahani merged 1 commit into
hotfix/glcm-quantizationfrom
hotfix/afri-formula

Conversation

@mkiani12

Copy link
Copy Markdown
Collaborator

Stacked on #50 — review that first; this PR targets hotfix/glcm-quantization.

AFRI

AFRICalculator.process() computed:

(nir - 0.66) * divide_with_nan(swir1, nir + (0.66 * swir1))

That subtracts a bare dimensionless constant from a reflectance, then multiplies by a band ratio. Karnieli et al. (2001) define:

AFRI_1.6 = (NIR - 0.66 * SWIR1.6) / (NIR + 0.66 * SWIR1.6)
AFRI_2.1 = (NIR - 0.50 * SWIR2.1) / (NIR + 0.50 * SWIR2.1)

The coefficient derives from the empirical relation ρ_0.645 ≈ 0.66 · ρ_1.6 and exists only to place the SWIR reflectance on the scale of the visible band it replaces. Applied anywhere else the expression is dimensionally incoherent — which is why the implemented version correlated with the reference index at only 0.17.

It also could not produce its own documented range: NIR - 0.66 is negative wherever NIR < 0.66, so 96.37% of output pixels were negative against a documented [0, +1].

Fixed, and the AFRI 2.1 variant added — the source paper defines it and the package already carries a swir2 band. On example/data/:

AFRI_1.6 range: -1.0000 .. 1.0000   (bounded, as a normalized difference must be)
pct negative:    0.01%              (was 96.37%)

BI

BICalculator computed (NIR - Green - Red) / (NIR + Green + Red), documented as a "Bare Soil Index" targeting Bare Soil & Exposed Rock, and cited to As-Syakur et al. (2012) — which defines EBBI, (SWIR - NIR) / (10·√(SWIR + TIR)), a different formula requiring a thermal band this tool does not accept.

The implemented expression is not a published bare-soil index. It subtracts two visible bands from NIR, so it tracks vegetation brightness — dense vegetation goes positive and bare soil clusters near zero, the opposite of what the name implies.

Default is now BSI (Rikimaru et al. 2002), selected automatically when swir1_path and blue_path are supplied:

BSI = ((SWIR1 + Red) - (NIR + Blue)) / ((SWIR1 + Red) + (NIR + Blue))

Bare soil and exposed rock are bright in SWIR1 and Red and comparatively dark in NIR and Blue; vegetation is the exact inverse. That sign reversal in the numerator is what separates lithological exposure from canopy cover, and the SWIR1 term carries the soil-moisture and clay-mineral response no visible/NIR combination reproduces.

The previous expression stays reachable by passing green_path instead, so existing results remain reproducible. It emits a DeprecationWarning. The EBBI citation is replaced with Rikimaru et al. (2002) in paper.bib and paper.md.

I did not invent a source for the legacy expression — it is documented as having no published reference and being an original formulation. If you know the source you were working from, that citation should go back in.

One thing this PR does not finish

The formulas are correct now, but the physical relationships they encode are still inverted by the per-band min–max normalization from issue #37. Measured on example/data/:

bands as stored min–max normalized
AFRI vs NDVI +0.7117 −0.6927
BSI vs NDVI −0.6507 +0.3908

Karnieli reports AFRI and NDVI as nearly identical under clear sky, and bare ground must oppose canopy — both hold on the stored values and both invert under normalization. So AFRI still correlates negatively with NDVI on this branch, and BSI still correlates positively, despite both formulas being right. That is fixed in the next PR in the stack (#37), after which these become +0.71 and −0.65.

The unit tests here inject reflectance-scale bands directly, bypassing normalization, so they assert the correct physics today.

Tests

334 pass (322 on #50, 12 new). New tests/tools/spectral_indices/reference_values_test.py carries hand-computed 2×2 expectations for all six indices against their cited definitions — NDVI (Rouse), NDWI (McFeeters), SAVI (Huete, L=0.5), UI, AFRI 1.6 and 2.1 (Karnieli), BSI (Rikimaru).

This is the test class the reviewer identified as missing. The existing per-index tests re-derive the expected value from the same expression the implementation uses, so a formula that has drifted from its reference passes — which is exactly how this defect survived. Also covered: AFRI bounded by ±1, variant validation, BSI positive over bare rock and negative over vegetation, and the legacy BI path warning while still computing its original values.

The existing AFRI test asserting the old formula was updated.

AFRICalculator computed (NIR - 0.66) * SWIR1 / (NIR + 0.66 * SWIR1), which
subtracts a bare dimensionless constant from a reflectance and multiplies the
result by a band ratio. Karnieli et al. (2001) define

    AFRI_1.6 = (NIR - 0.66 * SWIR1.6) / (NIR + 0.66 * SWIR1.6)

where 0.66 derives from the empirical relation rho_0.645 ~= 0.66 * rho_1.6 and
only has meaning as a scaling factor on the SWIR band. The implemented
expression correlated with Karnieli's index at 0.17, and produced negative
values over 96% of the bundled example while the documentation claimed a
[0, +1] range - a range the old formula cannot produce, since NIR - 0.66 is
negative wherever NIR < 0.66.

Correct the formula and add the AFRI_2.1 variant (SWIR2, coefficient 0.50),
which the source paper defines and for which the package already carries a
swir2 band. On the bundled data AFRI is now bounded by +/-1 with 0.01%
negative pixels.

BICalculator computed (NIR - Green - Red) / (NIR + Green + Red), documented as
a "Bare Soil Index" targeting bare soil and exposed rock, and cited to
As-Syakur et al. (2012) - which defines EBBI, (SWIR - NIR)/(10*sqrt(SWIR +
TIR)), a different formula needing a thermal band this tool does not accept.
The implemented expression is not a published bare-soil index and responds
mainly to vegetation brightness.

Default to the Bare Soil Index proper when SWIR1 and Blue are supplied:

    BSI = ((SWIR1 + Red) - (NIR + Blue)) / ((SWIR1 + Red) + (NIR + Blue))

Bare soil and exposed rock are bright in SWIR1 and Red and dark in NIR and
Blue, the inverse of the vegetation response, so BSI is positive over exposed
ground and negative over canopy. The previous expression stays reachable via
green_path so existing results remain reproducible, and emits a
DeprecationWarning. Replace the EBBI citation with Rikimaru et al. (2002).

Add a reference-value test module carrying hand-computed 2x2 expectations for
all six indices. The existing per-index tests re-derive the expected value from
the same expression the implementation uses, so a formula that has drifted from
its reference still passes - which is how this defect survived.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants