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
Warning

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

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 Strings 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 Vectors 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]