Skip to content

Commit

Permalink
Merge pull request #32 from JoshuaBillson/mnf
Browse files Browse the repository at this point in the history
Made CairoMakie an Extension
  • Loading branch information
JoshuaBillson authored Aug 18, 2023
2 parents 75225ac + 71a4610 commit ff6c1e8
Show file tree
Hide file tree
Showing 16 changed files with 107 additions and 72 deletions.
10 changes: 8 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
name = "RemoteSensingToolbox"
uuid = "c88070b3-ddf6-46c7-b699-196056389566"
authors = ["Joshua Billson"]
version = "0.1.0"
version = "0.2.0"

[deps]
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand All @@ -19,6 +18,13 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
TableOperations = "ab02a1b2-a7df-11e8-156e-fb1833f50b87"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[weakdeps]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"

[extensions]
RemoteSensingToolboxMakieExt = "CairoMakie"

[compat]
ArchGDAL = "0.10"
CairoMakie = "0.10"
Expand Down
54 changes: 54 additions & 0 deletions ext/RemoteSensingToolboxMakieExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module RemoteSensingToolboxMakieExt

using RemoteSensingToolbox, CairoMakie, Rasters, Statistics
using Pipe: @pipe
import Tables

function RemoteSensingToolbox.plot_signatures(bandset::Type{<:AbstractBandset}, raster::AbstractRasterStack, shp, label::Symbol; colors=Makie.wong_colors())
# Create Figure
fig = Figure(resolution=(1000,500))

# Create Axis
ax = Axis(
fig[1,1],
xlabel="Wavelength (nm)",
ylabel="Reflectance",
xlabelfont=:bold,
ylabelfont=:bold,
xlabelpadding=10.0,
ylabelpadding=10.0,
)

# Plot Signatures
RemoteSensingToolbox.plot_signatures!(ax, bandset, raster, shp, label; colors=colors)

# Add Legend
Legend(fig[1,2], ax, "Classification")

# Return Figure
return fig
end

function RemoteSensingToolbox.plot_signatures!(ax, bandset::Type{<:AbstractBandset}, raster::AbstractRasterStack, shp, label::Symbol; colors=Makie.wong_colors())
# Extract Signatures
extracted = @pipe RemoteSensingToolbox.extract_signatures(raster, shp, label) |> RemoteSensingToolbox.fold_rows(mean, _, :label)

# Plot Signatures
RemoteSensingToolbox.plot_signatures!(ax, bandset, extracted, :label, colors)
end

function RemoteSensingToolbox.plot_signatures!(ax, bandset::Type{<:AbstractBandset}, sigs, labelcolumn::Symbol, colors)
# Extract Signatures
cols = Tables.columnnames(sigs)
bs = filter(x -> x in cols, bands(bandset))
sig_matrix = hcat([Tables.getcolumn(sigs, b) for b in bs]...)

# Plot Signatures
xs = [wavelength(bandset, b) for b in bs]
labels = Tables.getcolumn(sigs, labelcolumn)
for i in 1:size(sig_matrix,1)
lines!(ax, xs, sig_matrix[i,:], label=labels[i]; color=colors[i])
end
end

end
2 changes: 1 addition & 1 deletion src/Bandsets/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Read and decode the quality assurance mask for the given `AbstractBandset`.
- `src`: Either a directory containing the quality assurance mask named according to standard conventions or the file itself.
# Returns
The decoded quality assurance mask as a `RasterStack`. Masked values are encoded as 1, non-masked values as 0, and missing values as 255.
The decoded quality assurance mask as a `RasterStack`. Encodes masked values as 1 and non-masked values as 0.
# Example
```julia-repl
Expand Down
1 change: 0 additions & 1 deletion src/RemoteSensingToolbox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module RemoteSensingToolbox

import ArchGDAL
import ImageCore
import CairoMakie
import Tables
import TableOperations
using OrderedCollections
Expand Down
1 change: 0 additions & 1 deletion src/Spectral/Spectral.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ using Logging
using LinearAlgebra
using Pipe: @pipe

import CairoMakie: Figure, Axis, lines!, Legend, save, Makie.wong_colors, cgrad
import RemoteSensingToolbox: align_rasters, efficient_read, RasterTable, transform_column, dropmissing, fold_rows
import ..Bandsets: AbstractBandset, wavelength, bands, wavelengths

Expand Down
44 changes: 7 additions & 37 deletions src/Spectral/analysis.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""
extract_signatures(stack::AbstractRasterStack, shp, label::Symbol; drop_missing=false)
extract_signatures(stack::AbstractRasterStack, shp, label::Symbol; drop_missing=true)
Extract signatures from the given `RasterStack` within regions specified by a provided shapefile.
# Parameters
- `stack`: The `RasterStack` from which to extract spectral signatures.
- `shp`: A `Tables.jl` compatible object containing a :geometry column storing a `GeoInterface.jl` compatible geometry and a label column indicating the land cover type.
- `label`: The column in `shp` corresponding to the land cover type.
- 'drop_missing': Drop all rows with at least one missing value in either the bands or labels (default = true).
- `drop_missing`: Drop all rows with at least one missing value in either the bands or labels (default = true).
# Returns
A `RasterTable` consisting of rows for each observed signature and columns storing the respective bands and land cover type.
Expand Down Expand Up @@ -39,7 +39,7 @@ julia> extract_signatures(landsat, shp, :C_name) |> DataFrame
"""
function extract_signatures(stack::AbstractRasterStack, shp, label::Symbol; drop_missing=true)
# Prepare Labels
labels = shp[:,label]
labels = Tables.getcolumn(shp, label)
fill_to_label = Set(labels) |> enumerate |> Dict
label_to_fill = Set(labels) |> enumerate .|> reverse |> Dict

Expand Down Expand Up @@ -84,29 +84,8 @@ shp = Shapefile.Table("data/landcover/landcover.shp") |> DataFrame
plot_signatures(Landsat8, landsat, shp, :MC_name)
```
"""
function plot_signatures(bandset::Type{<:AbstractBandset}, raster::AbstractRasterStack, shp, label::Symbol; colors=wong_colors())
# Create Figure
fig = Figure(resolution=(1000,500))

# Create Axis
ax = Axis(
fig[1,1],
xlabel="Wavelength (nm)",
ylabel="Reflectance",
xlabelfont=:bold,
ylabelfont=:bold,
xlabelpadding=10.0,
ylabelpadding=10.0,
)

# Plot Signatures
plot_signatures!(ax, bandset, raster, shp, label; colors=colors)

# Add Legend
Legend(fig[1,2], ax, "Classification")

# Return Figure
return fig
function plot_signatures(args...; kwargs...)
error("`plot_signatures` requires `CairoMakie` to be activated in your environment! Run `import CairoMakie` to fix this problem.")
end

"""
Expand Down Expand Up @@ -146,15 +125,6 @@ plot_signatures!(ax2, Sentinel2, sentinel, shp, :MC_name; colors=cgrad(:tab10))
Legend(fig[:,2], ax1)
```
"""
function plot_signatures!(ax, bandset::Type{<:AbstractBandset}, raster::AbstractRasterStack, shp, label::Symbol; colors=wong_colors())
# Extract Signatures
extracted = @pipe extract_signatures(raster, shp, label) |> fold_rows(mean, _, :label)

# Prepare Signatures For Plotting
sigs = Tables.matrix(extracted)[:,2:end] .|> Float32
labels = Tables.matrix(extracted)[:,1] .|> string
bands = filter(!=(:label), Tables.columnnames(extracted))

# Plot Signatures
_plot_signatures!(ax, bandset, sigs, bands, labels; colors=colors)
function plot_signatures!(args...; kwargs...)
error("`plot_signatures!` requires `CairoMakie` to be activated in your environment! Run `import CairoMakie` to fix this problem.")
end
27 changes: 0 additions & 27 deletions src/Spectral/utils.jl
Original file line number Diff line number Diff line change
@@ -1,30 +1,3 @@
function _rasterize(shp, to, fill)
Rasters.rasterize(last, shp, to=to, fill=fill, verbose=false, progress=false)
end

function _sort_signature(bandset::Type{<:AbstractBandset}, reflectances::Vector{<:Number}, bands::Vector{Symbol})
sorted = @pipe zip(wavelength.(bandset, bands), reflectances) |> collect |> sort(_, by=first)
return (first.(sorted), last.(sorted))
end

function _plot_signatures!(ax, bandset::Type{<:AbstractBandset}, sigs::Matrix{<:AbstractFloat}, bands::Vector{Symbol}, labels; colors=wong_colors(), kwargs...)
# Check Arguments
(size(sigs, 2) != length(bands)) && throw(ArgumentError("Length of signatures ($(size(sigs, 2))) must be equal to number of bands ($(length(bands)))!"))
(size(sigs, 1) != length(labels)) && throw(ArgumentError("Number of signatures ($(size(sigs, 1))) must be equal to number of labels ($(length(labels)))"))

# Plot Signatures
for i in 1:size(sigs,1)
_plot_signature!(ax, bandset, sigs[i,:], bands, label=labels[i]; color=colors[i], kwargs...)
end
end

function _plot_signature!(ax, bandset::Type{<:AbstractBandset}, signature::Vector{<:AbstractFloat}, bands::Vector{Symbol}; kwargs...)
# Check Arguments
(length(signature) != length(bands)) && throw(ArgumentError("Length of signatures ($(length(sigs))) must be equal to number of bands ($(length(bands)))!"))

# Sort Bands In Ascending Order
x, y = _sort_signature(bandset, signature, bands)

# Plot Signature
lines!(ax, x, y; kwargs...)
end
7 changes: 7 additions & 0 deletions src/raster_table.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ mutable struct RasterTable <: Tables.AbstractColumns
cols::Vector{Vector}
end

function _dim_cols(raster)
ds = Iterators.product(dims(raster)...)
xs = @pipe map(first, ds) |> reshape(_, :)
ys = @pipe map(x -> x[2], ds) |> reshape(_, :)
return (xs, ys)
end

function _replace_missing(raster::AbstractRaster)
m = missingval(raster)
r = reshape(raster, :)
Expand Down
10 changes: 7 additions & 3 deletions src/visualization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ end

function plot_mask(mask, classes, figure=(;), legend=(;))
# Create Color Gradient
return 1
"""
colors = CairoMakie.cgrad(:viridis, length(classes), categorical=true)
# Create Plot
Expand All @@ -87,12 +89,14 @@ function plot_mask(mask, classes, figure=(;), legend=(;))
CairoMakie.Legend(fig[1,2], elements, classes, "Legend", legend...)
return fig
"""
end

function plot_image(img)
fig, ax, plt = @pipe img |> rotr90 |> CairoMakie.image(_, axis=(;aspect=CairoMakie.DataAspect()), figure=(; resolution=reverse(size(img)) .+ 64))
CairoMakie.hidedecorations!(ax)
return fig, ax, plt
return 1
#fig, ax, plt = @pipe img |> rotr90 |> CairoMakie.image(_, axis=(;aspect=CairoMakie.DataAspect()), figure=(; resolution=reverse(size(img)) .+ 64))
#CairoMakie.hidedecorations!(ax)
#return fig, ax, plt
end

"Adjust image histogram by performing a linear stretch to squeeze all values between the percentiles `lower` and `upper` into the range [0,1]."
Expand Down
3 changes: 3 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
[deps]
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
Pipe = "b98c9c47-44ae-5843-9183-064241ee97a0"
Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Binary file added test/data/landcover/landcover.dbf
Binary file not shown.
1 change: 1 addition & 0 deletions test/data/landcover/landcover.prj
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PROJCS["WGS_1984_UTM_Zone_11N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-117.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]
Binary file added test/data/landcover/landcover.shp
Binary file not shown.
Binary file added test/data/landcover/landcover.shx
Binary file not shown.
Binary file added test/data/landcover/landcover.tif
Binary file not shown.
19 changes: 19 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using RemoteSensingToolbox
using Test
using Images
using Shapefile
import ArchGDAL
import Tables
using Pipe: @pipe

@testset "Landsat" begin
Expand Down Expand Up @@ -79,3 +81,20 @@ end
@test names(sentinel) == names(recovered)
@test all(isapprox.(tocube(recovered).data, tocube(sentinel).data, atol=0.1))
end

@testset "makie" begin

# Load Sentinel
sentinel = @pipe read_bands(Sentinel2, "data/sentinel/") |> dn_to_reflectance(Sentinel2, _)

# Read Shapefile
shp = Shapefile.Table("data/landcover/landcover.shp") |> Tables.columntable

# Should Throw Error Telling Us To Load CairoMakie
@test_throws ErrorException plot_signatures(Sentinel2, sentinel, shp, :MC_name)

using CairoMakie

# Should Run Now That CairoMakie is Loaded
fig = plot_signatures(Sentinel2, sentinel, shp, :MC_name)
end

0 comments on commit ff6c1e8

Please sign in to comment.