YAXArrays.jl
This tutorial will illustrate how to use SpectralIndices.jl using YAXArrays.jl as input data.
First we need to download the data, like in the previous tutorial. Only this time the data is going to be higher dimensional and slightly more complex, hence the need for YAXArrays.jl. In order to do so we are going to use the load_dataset
function:
using YAXArrays, DimensionalData
using SpectralIndices
yaxa = load_dataset("sentinel", YAXArray)
╭─────────────────────────────╮
│ 300×300×4 YAXArray{Int64,3} │
├─────────────────────────────┴───────────────────────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points,
↗ bands Categorical{String} ["B02", "B03", "B04", "B08"] ForwardOrdered
├─────────────────────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────────────────────── file size ┤
file size: 2.75 MB
└─────────────────────────────────────────────────────────────────────────┘
As it is possible to observe we have a YAXArray
object with three dimensions: bands
, x
and y
. Each band is one of the 10 m spectral bands of a Sentinel-2 image.
The data is stored as Int64
, so let us convert it to Float
and rescale it:
yaxa = yaxa./10000
╭───────────────────────────────╮
│ 300×300×4 YAXArray{Float64,3} │
├───────────────────────────────┴─────────────────────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points,
↗ bands Categorical{String} ["B02", "B03", "B04", "B08"] ForwardOrdered
├─────────────────────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────────────────────── file size ┤
file size: 2.75 MB
└─────────────────────────────────────────────────────────────────────────┘
Now let's compute the NDVI for this dataset!
First, let's define the bands to be used:
b8 = yaxa[bands = At("B08")]
b4 = yaxa[bands = At("B04")]
now, let's compute the index
ndvi_compute = compute_index("NDVI"; N=b8, R=b4)
╭─────────────────────────────╮
│ 300×300 YAXArray{Float64,2} │
├─────────────────────────────┴───────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points
├─────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────── file size ┤
file size: 703.12 KB
└─────────────────────────────────────────────────────────┘
map
Due to the type
design, in order to use map
we will use the PartialFunctions
package to specify the first initial type
on each function, namely
using PartialFunctions
ndvi_p = NDVI.compute $Float64
NDVI_func(Float64, ...)
now, we can compute the index
ndvi_map = map(ndvi_p, b8, b4)
╭─────────────────────────────╮
│ 300×300 YAXArray{Float64,2} │
├─────────────────────────────┴───────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points
├─────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────── file size ┤
file size: 703.12 KB
└─────────────────────────────────────────────────────────┘
Let's check that we have the same output:
ndvi_compute.data == ndvi_map.data
true
mapCube
For out of memory calculations then using mapCube
is the way to go. This can be implemented as follows. First, we wrap our function of interest into a function compatible with mapCube
, namely
function ndvi_out(xout, x1, x2)
xout .= NDVI.(Float64, x1, x2) # note the .
end
ndvi_out (generic function with 1 method)
next, define the input and output dimensions of your YAXArray
's.
in_dims = InDims("x") # the second one will be inferred
out_dims = OutDims("x") # dito
ndvi_cube = mapCube(ndvi_out, (b8, b4), indims=(in_dims, in_dims),
outdims=OutDims("x", outtype=Float64))
╭─────────────────────────────────────────────╮
│ 300×300 YAXArray{Union{Missing, Float64},2} │
├─────────────────────────────────────────────┴───── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points
├─────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────── file size ┤
file size: 703.12 KB
└─────────────────────────────────────────────────────────┘
and we check again the data output matches
ndvi_compute.data == ndvi_cube.data
true
Computing index by named dims
As usual we can also just feed a properly constructed YAXArray
to the compute_index
function. Let's built the array:
index_R = findfirst(yaxa.bands.val .== "B04")
index_N = findfirst(yaxa.bands.val .== "B08")
new_bands_dim = Dim{:Variables}(["R", "N"])
nr_data = cat(yaxa[:, :, index_R], yaxa[:, :, index_N], dims=3)
new_yaxa = YAXArray((yaxa.x, yaxa.y, new_bands_dim), nr_data)
╭───────────────────────────────╮
│ 300×300×2 YAXArray{Float64,3} │
├───────────────────────────────┴─────────────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points,
↗ Variables Categorical{String} ["R", "N"] ReverseOrdered
├─────────────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────────────── file size ┤
file size: 1.37 MB
└─────────────────────────────────────────────────────────────────┘
Please notice how the Dim
is called Variables
. This is needed for the internal computation to work properly. Also, note that this does not work for out of memory datasets.
Now that we have our YAXArray
with the correctly names Dim
s we can use it direcly into compute_index
:
ndvi = compute_index(
"NDVI", new_yaxa
)
╭─────────────────────────────╮
│ 300×300 YAXArray{Float64,2} │
├─────────────────────────────┴───────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points
├─────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────── file size ┤
file size: 703.12 KB
└─────────────────────────────────────────────────────────┘
Computing Kernels for kNDVI
We want to compute the kNDVI for a YAXArray
.
kNDVI
kNDVI: Kernel Normalized Difference Vegetation Index
* Application Domain: kernel
* Bands/Parameters: Any["kNN", "kNR"]
* Formula: (kNN-kNR)/(kNN+kNR)
* Reference: https://doi.org/10.1126/sciadv.abc7447
As we see from bands
we need the kNN
and kNR
. In order to compute these values SpectralIndices.jl provides a compute_kernel
function. If you are curious about the kNDVI
remember that you always have the source of the index in the reference
field:
kNDVI.reference
"https://doi.org/10.1126/sciadv.abc7447"
Onto the calculations:
knn = YAXArray((yaxa.x, yaxa.y), fill(1.0, 300, 300));
knr = compute_kernel(
RBF;
a = Float64.(yaxa[bands = At("B08")]),
b = Float64.(yaxa[bands = At("B04")]),
sigma = yaxa[bands = At("B08")].+yaxa[bands = At("B04")] ./ 2
)
╭─────────────────────────────╮
│ 300×300 YAXArray{Float64,2} │
├─────────────────────────────┴───────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points
├─────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────── file size ┤
file size: 703.12 KB
└─────────────────────────────────────────────────────────┘
As always, you can decide to build a YAXArray
and feed that to the compute_kernel
function if you prefer:
a = Float64.(yaxa[bands = At("B08")])
b = Float64.(yaxa[bands = At("B04")])
sigma = yaxa[bands = At("B08")].+yaxa[bands = At("B04")] ./ 2
kernel_dims = Dim{:Variables}(["a", "b", "sigma"])
params = concatenatecubes([a, b, sigma], kernel_dims)
knr = compute_kernel(RBF, params)
╭─────────────────────────────╮
│ 300×300 YAXArray{Float64,2} │
├─────────────────────────────┴───────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points
├─────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────── file size ┤
file size: 703.12 KB
└─────────────────────────────────────────────────────────┘
We can finally compute the kNDVI:
kndvi = compute_index("kNDVI"; kNN = knn, kNR=knr)
╭─────────────────────────────╮
│ 300×300 YAXArray{Float64,2} │
├─────────────────────────────┴───────────────────── dims ┐
↓ x Sampled{Int64} 1:300 ForwardOrdered Regular Points,
→ y Sampled{Int64} 1:300 ForwardOrdered Regular Points
├─────────────────────────────────────────────── metadata ┤
Dict{String, Any}()
├────────────────────────────────────────────── file size ┤
file size: 703.12 KB
└─────────────────────────────────────────────────────────┘
Let's plot it!
using CairoMakie
fig, ax, plt = heatmap(kndvi; colormap=:haline,
axis = (; aspect=DataAspect()),
figure = (; size=(600, 400)))
Colorbar(fig[1,2], plt)
colsize!(fig.layout, 1, Aspect(1, 1.0))
fig
![Example block output](5d35e646.png)