Skip to content

Commit 3257781

Browse files
juliohmeliascarv
andauthored
Add SpectralIndex transform (#300)
* Add SpectralIndex transform * Use _assert utility * Add tests for auxiliary functions * Update src/transforms/spectralindex.jl Co-authored-by: Elias Carvalho <[email protected]> --------- Co-authored-by: Elias Carvalho <[email protected]>
1 parent db15486 commit 3257781

File tree

7 files changed

+168
-1
lines changed

7 files changed

+168
-1
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1616
NelderMead = "2f6b4ddb-b4ff-44c0-b59b-2ab99302f970"
1717
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
1818
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
19+
SpectralIndices = "df0093a1-273d-40bc-819a-796ec3476907"
1920
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
2021
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
2122
TableDistances = "e5d66e97-8c70-46bb-8b66-04a2d73ad782"
@@ -36,6 +37,7 @@ LinearAlgebra = "1.9"
3637
NelderMead = "0.4"
3738
PrettyTables = "2"
3839
Random = "1.9"
40+
SpectralIndices = "0.2"
3941
Statistics = "1.9"
4042
StatsBase = "0.33, 0.34"
4143
TableDistances = "1.0"

docs/src/transforms.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,12 @@ SDS
242242
ProjectionPursuit
243243
```
244244

245+
## SpectralIndex
246+
247+
```@docs
248+
SpectralIndex
249+
```
250+
245251
## KMedoids
246252

247253
```@docs

src/TableTransforms.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ using Distributions: ContinuousUnivariateDistribution, Normal
2525
using InverseFunctions: NoInverse, inverse as invfun
2626
using StatsBase: AbstractWeights, Weights, sample
2727
using Distributed: CachingPool, pmap, workers
28+
using SpectralIndices: indices, bands, compute
2829
using NelderMead: optimise
2930

3031
import Distributions: quantile, cdf
@@ -91,6 +92,7 @@ export
9192
DRS,
9293
SDS,
9394
ProjectionPursuit,
95+
SpectralIndex,
9496
KMedoids,
9597
Closure,
9698
Remainder,
@@ -101,6 +103,10 @@ export
101103
RowTable,
102104
ColTable,
103105
,
104-
106+
,
107+
108+
# utilities
109+
spectralindices,
110+
spectralbands
105111

106112
end

src/transforms.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ include("transforms/quantile.jl")
286286
include("transforms/functional.jl")
287287
include("transforms/eigenanalysis.jl")
288288
include("transforms/projectionpursuit.jl")
289+
include("transforms/spectralindex.jl")
289290
include("transforms/kmedoids.jl")
290291
include("transforms/closure.jl")
291292
include("transforms/remainder.jl")

src/transforms/spectralindex.jl

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# ------------------------------------------------------------------
2+
# Licensed under the MIT License. See LICENSE in the project root.
3+
# ------------------------------------------------------------------
4+
5+
"""
6+
SpectralIndex(name, [bands...])
7+
8+
Compute the spectral index of given `name` from SpectralIndices.jl.
9+
Optionally, specify the column names corresponding to spectral `bands`
10+
as keyword arguments.
11+
12+
# Examples
13+
14+
```julia
15+
# vegetation index
16+
SpectralIndex("NDVI")
17+
18+
# water index
19+
SpectralIndex("NDWI")
20+
21+
# specify "R" (red) and "N" (near infra red) columns
22+
SpectralIndex("NDVI", R="col1", N="col4")
23+
```
24+
25+
### Notes
26+
27+
The list of supported indices can be obtained with `spectralindices()`
28+
and the list of spectral bands for a given index can be obtained with
29+
`spectralbands(name)`.
30+
"""
31+
struct SpectralIndex{B} <: StatelessFeatureTransform
32+
name::String
33+
bands::B
34+
35+
function SpectralIndex(name, bands)
36+
sname = string(name)
37+
_assert(sname keys(indices), "$sname not found in SpectralIndices.jl")
38+
sbands = if isempty(bands)
39+
nothing
40+
else
41+
skeys = string.(keys(bands))
42+
vkeys = Tuple(indices[sname].bands)
43+
_assert(skeys vkeys, "bands $skeys are not valid for spectral index $sname, please choose from $vkeys")
44+
svals = string.(values(values(bands)))
45+
(; zip(Symbol.(skeys), svals)...)
46+
end
47+
new{typeof(sbands)}(sname, sbands)
48+
end
49+
end
50+
51+
SpectralIndex(name; bands...) = SpectralIndex(name, bands)
52+
53+
function applyfeat(transform::SpectralIndex, feat, prep)
54+
# retrieve spectral index
55+
iname = transform.name
56+
index = indices[iname]
57+
58+
# extract band names from feature table
59+
cols = Tables.columns(feat)
60+
names = Tables.columnnames(cols)
61+
bnames = Symbol.(index.bands)
62+
tbands = transform.bands
63+
snames = map(bnames) do b
64+
if !isnothing(tbands) && b keys(tbands)
65+
Symbol(tbands[b])
66+
else
67+
b
68+
end
69+
end
70+
71+
# throw helpful error message in case of invalid names
72+
if !(snames names)
73+
notfound = setdiff(snames, names)
74+
required = ["$(b.short_name): $(b.long_name)" for b in spectralbands(iname)]
75+
pprint(names) = "\"" * join(string.(names), "\", \"", "\" and \"") * "\""
76+
throw(ArgumentError("""columns $(pprint(notfound)) not found in table.
77+
78+
Please specify valid columns names for the spectral bands as keyword arguments.
79+
80+
Required bands for $iname:
81+
82+
$(join(required, "\n "))
83+
84+
Available column names: $(pprint(names))
85+
"""))
86+
end
87+
88+
# compute index for all locations
89+
icols = [b => Tables.getcolumn(cols, n) for (b, n) in zip(bnames, snames)]
90+
ivals = compute(index; icols...)
91+
92+
# new table with index feature
93+
newfeat = (; Symbol(iname) => ivals) |> Tables.materializer(feat)
94+
95+
newfeat, nothing
96+
end
97+
98+
"""
99+
spectralindices()
100+
101+
List of spectral indices supported by SpectralIndices.jl.
102+
"""
103+
spectralindices() = indices
104+
105+
"""
106+
spectralbands(name)
107+
108+
List of spectral bands for spectral index of given `name`.
109+
"""
110+
spectralbands(name) = [bands[b] for b in indices[name].bands]

test/transforms.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ transformfiles = [
3131
"functional.jl",
3232
"eigenanalysis.jl",
3333
"projectionpursuit.jl",
34+
"spectralindex.jl",
3435
"kmedoids.jl",
3536
"closure.jl",
3637
"remainder.jl",

test/transforms/spectralindex.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@testset "SpectralIndex" begin
2+
@test !isrevertible(SpectralIndex("NDVI"))
3+
4+
# standard names
5+
t = Table(R=[1,2,3], N=[4,5,6])
6+
T = SpectralIndex("NDVI")
7+
@test T(t).NDVI [0.6, 0.42857142857142855, 0.3333333333333333]
8+
T = SpectralIndex("NDVI", R="R")
9+
@test T(t).NDVI [0.6, 0.42857142857142855, 0.3333333333333333]
10+
T = SpectralIndex("NDVI", N="N")
11+
@test T(t).NDVI [0.6, 0.42857142857142855, 0.3333333333333333]
12+
T = SpectralIndex("NDVI", R="R", N="N")
13+
@test T(t).NDVI [0.6, 0.42857142857142855, 0.3333333333333333]
14+
15+
# some non-standard names
16+
t = Table(R=[1,2,3], NIR=[4,5,6])
17+
T = SpectralIndex("NDVI", N="NIR")
18+
@test T(t).NDVI [0.6, 0.42857142857142855, 0.3333333333333333]
19+
T = SpectralIndex("NDVI")
20+
@test_throws ArgumentError T(t)
21+
T = SpectralIndex("NDVI", R="RED")
22+
@test_throws ArgumentError T(t)
23+
24+
# all non-standard names
25+
t = Table(RED=[1,2,3], NIR=[4,5,6])
26+
T = SpectralIndex("NDVI", R="RED", N="NIR")
27+
@test T(t).NDVI [0.6, 0.42857142857142855, 0.3333333333333333]
28+
T = SpectralIndex("NDVI", R="RED")
29+
@test_throws ArgumentError T(t)
30+
T = SpectralIndex("NDVI", N="NIR")
31+
@test_throws ArgumentError T(t)
32+
33+
# bands as symbols
34+
t = Table(RED=[1,2,3], NIR=[4,5,6])
35+
T = SpectralIndex("NDVI", R=:RED, N=:NIR)
36+
@test T(t).NDVI [0.6, 0.42857142857142855, 0.3333333333333333]
37+
38+
# auxiliary functions
39+
@test spectralindices() isa Dict
40+
@test spectralbands("NDVI") isa Vector
41+
end

0 commit comments

Comments
 (0)