Skip to content

Commit 051df92

Browse files
author
Max Freudenberg
committed
introduce rgbplot
1 parent fe9f059 commit 051df92

File tree

4 files changed

+88
-5
lines changed

4 files changed

+88
-5
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Flatten = "4c728ea3-d9ee-5c9a-9642-b6f7d7dc04fa"
1818
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"
1919
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
2020
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
21+
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
2122
Missings = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
2223
Mmap = "a63ad114-7e13-5084-954f-fe012c677804"
2324
NCDatasets = "85f8d34a-cbdd-5861-8df4-14fed0d494ab"
@@ -28,6 +29,7 @@ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
2829
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
2930
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
3031
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
32+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
3133

3234
[compat]
3335
Adapt = "2, 3.0"

src/Rasters.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ import Adapt,
2020
Flatten,
2121
GeoInterface,
2222
HDF5,
23+
ImageCore,
2324
PolygonInbounds,
2425
ProgressMeter,
2526
Missings,
2627
Mmap,
2728
NCDatasets,
2829
RecipesBase,
2930
Reexport,
30-
Setfield
31+
Setfield,
32+
Statistics
3133

3234
Reexport.@reexport using DimensionalData, GeoFormatTypes, RasterDataSources
3335

@@ -40,7 +42,8 @@ using DimensionalData: Name, NoName
4042
using .Dimensions: StandardIndices, DimTuple
4143
using .LookupArrays: LookupArrayTuple
4244

43-
using RecipesBase: @recipe, @series
45+
using RecipesBase: @recipe, @series, @userplot
46+
using Statistics: quantile
4447
using Base: tail, @propagate_inbounds
4548

4649
using Setfield: @set, @set!

src/plotrecipes.jl

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,58 @@ end
193193
end
194194
end
195195

196+
# plot multispectral images as rgb
197+
@userplot RGBPlot
198+
199+
@recipe function f(p::RGBPlot; bands=1:3, low=0.02, high=0.98)
200+
r = first(p.args)
201+
if !(typeof(r) <: AbstractRaster)
202+
error("Plotting RGB images is only implemented for AbstractRasters.")
203+
end
204+
if !hasdim(r, Band)
205+
error("Raster must have a band dimension.")
206+
end
207+
if !(0 <= low <= 1) || !(0 <= high <= 1)
208+
error("'low' and 'high' have to be between 0 and 1 (inclusive).")
209+
end
210+
img = Float32.(copy(r[Band([bands...])]))
211+
normalize!(img, low, high)
212+
img = permutedims(img, (Band, X, Y))
213+
img = DD.reorder(img, X=>DD.ForwardOrdered, Y=>DD.ForwardOrdered)
214+
plot_image = ImageCore.colorview(RGB, img)
215+
216+
yguide, xguide = DD.label(dims(img, (Y,X)))
217+
218+
y, x = map(Rasters._prepare, dims(img, (Y,X)))
219+
220+
rdt = DD.refdims_title(img; issingle=true)
221+
:title --> (rdt === "" ? Rasters._maybename(img) : Rasters._maybename(img) * "\n" * rdt)
222+
:xguide --> xguide
223+
:xrotation --> 45
224+
:yguide --> yguide
225+
:tick_direction --> :out
226+
:framestyle --> :box
227+
228+
if all(d -> lookup(d) isa Mapped, (x, y))
229+
:xlims --> mappedbounds(x)
230+
:ylims --> mappedbounds(y)
231+
:aspect_ratio --> :equal
232+
else
233+
:xlims --> bounds(img, x)
234+
:ylims --> bounds(img, y)
235+
bnds = bounds(img, (X, Y))
236+
# # TODO: Rethink this....
237+
s1, s2 = map(((l, u),) -> (u - l), bnds) ./ (size(img, X), size(img, Y))
238+
square_pixels = s2 / s1
239+
:aspect_ratio --> square_pixels
240+
end
241+
242+
:seriestype --> :image
243+
:yflip --> false
244+
DD.index(x), DD.index(y), plot_image'
245+
end
246+
247+
196248
# Plots.jl heatmaps pixels are centered.
197249
# So we should center the index, and use the projected value.
198250
_prepare(d::Dimension) = d |> _maybe_shift |> _maybe_mapped
@@ -248,3 +300,26 @@ function DD.refdims_title(refdim::Band; issingle=false)
248300
end
249301
end
250302

303+
function eachband_view(r::Raster)
304+
nbands = size(r, Band)
305+
return (view(r, Band(n)) for n in 1:nbands)
306+
end
307+
308+
function normalize!(raster, low=0.1, high=0.9)
309+
if !hasdim(raster, Band)
310+
l = quantile(skipmissing(raster), low)
311+
h = quantile(skipmissing(raster), high)
312+
raster .-= l
313+
raster ./= h - l + eps(float(eltype(raster)))
314+
raster .= clamp.(raster, zero(eltype(raster)), one(eltype(raster)))
315+
else
316+
for band in eachband_view(raster)
317+
l = quantile(skipmissing(band), low)
318+
h = quantile(skipmissing(band), high)
319+
band .-= l
320+
band ./= h - l + eps(float(eltype(raster)))
321+
band .= clamp.(band, zero(eltype(raster)), one(eltype(raster)))
322+
end
323+
end
324+
return raster
325+
end

src/skipmissing.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,13 @@ end
3535

3636
_missing(x, itr) = ismissing(x) || x == missingval(itr) # mind the order, as comparison with missing returns missing
3737
function _missing(x::AbstractFloat, itr)
38-
if isnan(missingval(itr))
39-
return ismissing(x) || isnan(x)
38+
# x is an AbstractFloat here and hence cannot be nothing or missing
39+
if isnothing(missingval(itr))
40+
return false
41+
elseif isnan(missingval(itr))
42+
return isnan(x)
4043
else
41-
return ismissing(x) || x == missingval(itr)
44+
return x == missingval(itr)
4245
end
4346
end
4447

0 commit comments

Comments
 (0)