Skip to content

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:

julia
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=NIRRedNIR+Red

Direct Calculation with NDVI Struct

SpectralIndices.jl provides a straightforward method for computing NDVI:

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

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

julia
NDVI.bands
2-element Vector{Any}:
 "N"
 "R"

Using compute_index

A more flexible way to calculate indices is through the compute_index function. This function accepts the SpectralIndex struct and parameters as either a dictionary or keyword arguments:

julia
params = Dict(
    "N" => nir,
    "R" => red
)
ndvi = compute_index(NDVI, params)
0.6879236756213909

Warning

Please ensure dictionary keys match the band names specified in the bands field.

Additionally you can pass the values as kwargs as follows:

julia
ndvi = compute_index(NDVI; N=nir, R=red)
0.6879236756213909

Order of keyword arguments does not affect the outcome:

julia
ndvi1 = compute_index(NDVI; N=nir, R=red)
ndvi2 = compute_index(NDVI; R=red, N=nir)
ndvi1 == ndvi2
true

Additionally, you can also pass the indexas a String:

julia
params = Dict(
    "N" => nir,
    "R" => red
)
ndvi = compute_index("NDVI", params)
0.6879236756213909

Or, using the kwargs:

julia
ndvi = compute_index("NDVI"; N=nir, R=red)
0.6879236756213909

Handling Floats

For Floats the procedure is similar. We will illustrate the example with the SAVI index

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

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

julia
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 Floats 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:

julia
nir /= 10000
red /= 10000
0.1243

Now we can proceed as before. Either using a Dict to built our parameters:

julia
params = Dict(
    "N" => nir,
    "R" => red,
    "L" => 0.5
)
savi = compute(SAVI, params)
0.6339657565941694

or by passing them as kwargs:

julia
savi = compute(SAVI; N=nir, R=red, L=0.5)
0.6339657565941694

And the same holds true for compute_index as well:

julia
params = Dict(
    "N" => nir,
    "R" => red,
    "L" => 0.5
)
savi = compute_index("SAVI", params)
0.6339657565941694
julia
savi = compute_index("SAVI"; N=nir, R=red, L=0.5)
0.6339657565941694

Float32, Float16

The package can compute indices at custom precision

julia
T = Float32
savi = compute_index("SAVI"; N=T(nir), R=T(red), L=T(0.5))
0.63396573f0
julia
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 Strings to the compute_index function with the chosen spectral indices inside, as well as the chosen parameters of course:

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

julia
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 Vectors is relatively straightforward. We follow the same procedure as before, defining our parameters, only this time they are arrays:

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

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

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

julia
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]
julia
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
julia
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:

julia
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],)
julia
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

julia
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
julia
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]