FIX AFRI formula and reconcile the bare soil index with its reference - #51
Merged
Merged
Conversation
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.
This was referenced Aug 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #50 — review that first; this PR targets
hotfix/glcm-quantization.AFRI
AFRICalculator.process()computed:That subtracts a bare dimensionless constant from a reflectance, then multiplies by a band ratio. Karnieli et al. (2001) define:
The coefficient derives from the empirical relation
ρ_0.645 ≈ 0.66 · ρ_1.6and 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.66is negative whereverNIR < 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
swir2band. Onexample/data/:BI
BICalculatorcomputed(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_pathandblue_pathare supplied: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_pathinstead, so existing results remain reproducible. It emits aDeprecationWarning. The EBBI citation is replaced with Rikimaru et al. (2002) inpaper.bibandpaper.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/: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.pycarries 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.