Built-in types
This guide provides a comprehensive overview of utilizing SpectralIndices.jl with Julia's built-in types and data structures. By exploring these foundational elements, you'll gain valuable insights into the package's functionality and its application in calculating spectral indices like NDVI and SAVI.
Introduction to Indices Calculation
Let's begin with an example involving two data points representing the near-infrared (NIR) and red reflectances of vegetation, stored as Int
values:
nir = 6723
red = 1243
1243
Our goal is to calculate the Normalized Difference Vegetation Index (NDVI). NDVI is a widely used spectral index for monitoring vegetation health, calculated using NIR and red reflectances. The formula for NDVI is:
\[NDVI = \frac{NIR-Red}{NIR+Red}\]
Direct Calculation with NDVI Struct
SpectralIndices.jl provides a straightforward method for computing NDVI:
using SpectralIndices
NDVI
NDVI: Normalized Difference Vegetation Index
* Application Domain: vegetation
* Bands/Parameters: Any["N", "R"]
* Formula: (N-R)/(N+R)
* Reference: https://ntrs.nasa.gov/citations/19740022614
This outputs the NDVI struct, containing all necessary information. The struct can also be used as a function to compute NDVI:
NDVI(Float64, nir, red)
0.6879236756213909
This method is direct but not the recommended approach for computing indices. When using this method, ensure the parameter order matches the bands
field of the SpectralIndex
:
NDVI.bands
2-element Vector{Any}:
"N"
"R"
Using the compute
Function
A more flexible way to calculate indices is through the compute
function. This function accepts the SpectralIndex
struct and parameters as either a dictionary or keyword arguments:
params = Dict(
"N" => nir,
"R" => red
)
ndvi = compute(NDVI, params)
0.6879236756213909
Please ensure dictionary keys match the band names specified in the bands
field.
Additionally you can pass the values as kwargs as follows:
ndvi = compute(NDVI; N=nir, R=red)
0.6879236756213909
Order of keyword arguments does not affect the outcome:
ndvi1 = compute(NDVI; N=nir, R=red)
ndvi2 = compute(NDVI; R=red, N=nir)
ndvi1 == ndvi2
true
Using compute_index
Lastly, you can use compute_index
to compute it. The precedure is identical to what has been shown so far for compute
, but the specification of the index is done by passing its name in a String
:
params = Dict(
"N" => nir,
"R" => red
)
ndvi = compute_index("NDVI", params)
0.6879236756213909
Or, using the kwargs:
ndvi = compute_index("NDVI"; N=nir, R=red)
0.6879236756213909
Handling Floats
For Float
s the procedure is similar. We will illustrate the example with the SAVI
index
SAVI
SAVI: Soil-Adjusted Vegetation Index
* Application Domain: vegetation
* Bands/Parameters: Any["L", "N", "R"]
* Formula: (1.0+L)*(N-R)/(N+R+L)
* Reference: https://doi.org/10.1016/0034-4257(88)90106-X
This index needs the following bands:
SAVI.bands
3-element Vector{Any}:
"L"
"N"
"R"
The L
parameter is new in this example. Thankfully, SpectralIndices.jl provides a list of constant values handy that we can leverage in this situation:
constants["L"]
L: Canopy background adjustment
* Description: Canopy background adjustment
* Standard: L
* Default value: 1.0
* Current value: 1.0
So now that we know what L
is or does, we can use it in our calculation of the SAVI index. But first we are going to redefine the values to be Float
s since we want to showcase some properties of SpectralIndices.jl with that data type. Additionally, SAVI needs imput values to be between -1 and 1:
nir /= 10000
red /= 10000
0.1243
Now we can proceed as before. Either using a Dict
to built our parameters:
params = Dict(
"N" => nir,
"R" => red,
"L" => 0.5
)
savi = compute(SAVI, params)
0.6339657565941694
or by passing them as kwargs:
savi = compute(SAVI; N=nir, R=red, L=0.5)
0.6339657565941694
And the same holds true for compute_index
as well:
params = Dict(
"N" => nir,
"R" => red,
"L" => 0.5
)
savi = compute_index("SAVI", params)
0.6339657565941694
savi = compute_index("SAVI"; N=nir, R=red, L=0.5)
0.6339657565941694
Float32, Float16
The package can compute indices at custom precision
T = Float32
savi = compute_index("SAVI"; N=T(nir), R=T(red), L=T(0.5))
0.63396573f0
T = Float16
savi = compute_index("SAVI"; N=T(nir), R=T(red), L=T(0.5))
Float16(0.634)
Computing Multiple Indices
Now that we have added more indices we can explore how to compute multiple indices at the same time. All is needed is to pass a Vector of String
s to the compute_index
function with the chosen spectral indices inside, as well as the chosen parameters of course:
params = Dict(
"N" => nir,
"R" => red,
"L" => 0.5
)
ndvi, savi = compute_index(["NDVI", "SAVI"], params)
2-element Vector{Any}:
0.687923675621391
0.6339657565941694
Alternatively, using kwargs:
ndvi, savi = compute_index(["NDVI", "SAVI"]; N=nir, R=red, L=0.5)
2-element Vector{Any}:
0.687923675621391
0.6339657565941694
Extension to Vectors
The extension to Vector
s is relatively straightforward. We follow the same procedure as before, defining our parameters, only this time they are arrays:
params = Dict(
"N" => fill(nir, 10),
"R" => fill(red, 10),
"L" => fill(0.5, 10)
)
Dict{String, Vector{Float64}} with 3 entries:
"N" => [0.6723, 0.6723, 0.6723, 0.6723, 0.6723, 0.6723, 0.6723, 0.6723, 0.672…
"L" => [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
"R" => [0.1243, 0.1243, 0.1243, 0.1243, 0.1243, 0.1243, 0.1243, 0.1243, 0.124…
After that we can compute either one, or both indices:
ndvi, savi = compute_index(["NDVI", "SAVI"], params)
2-element Vector{Any}:
[0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391]
[0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694]
We can use the same params to calculate single indices. The additional bands are just going to be ignored:
ndvi = compute_index("NDVI", params)
10-element Vector{Float64}:
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
savi = compute_index("SAVI", params)
10-element Vector{Float64}:
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
And as always, we can also pass them as kwargs:
ndvi, savi = compute_index(["NDVI", "SAVI"];
N=fill(nir, 10),
R=fill(red, 10),
L=fill(0.5, 10))
2-element Vector{Any}:
[0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391, 0.687923675621391]
[0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694, 0.6339657565941694]
ndvi = compute_index("NDVI";
N=fill(nir, 10),
R=fill(red, 10),
L=fill(0.5, 10))
10-element Vector{Float64}:
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
0.687923675621391
savi = compute_index("SAVI";
N=fill(nir, 10),
R=fill(red, 10),
L=fill(0.5, 10))
10-element Vector{Float64}:
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
0.6339657565941694
Extension to NamedTuples
SpectralIndices.jl allows you to also create indices from NamedTuples
:
params = (N=fill(0.2, 10), R=fill(0.1, 10), L=fill(0.5, 10))
compute_index("NDVI", params)
(NDVI = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333],)
compute_index(["NDVI", "SAVI"], params)
(NDVI = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333], SAVI = [0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003])
You can also pass the NamedTuple
as kwargs splatting them, but the output will not be a NamedTuple
compute_index("NDVI"; params...)
10-element Vector{Float64}:
0.3333333333333333
0.3333333333333333
0.3333333333333333
0.3333333333333333
0.3333333333333333
0.3333333333333333
0.3333333333333333
0.3333333333333333
0.3333333333333333
0.3333333333333333
compute_index(["NDVI", "SAVI"]; params...)
2-element Vector{Any}:
[0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
[0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003, 0.18750000000000003]