Skip to content

Commit bcee212

Browse files
author
Felix Cremer
authored
Merge branch 'main' into fg/multidim
2 parents 9f5faf0 + 85be5d9 commit bcee212

File tree

6 files changed

+195
-56
lines changed

6 files changed

+195
-56
lines changed

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
File filter
3+
# Changelog
4+
5+
All notable changes to this project will be documented in this file.
6+
7+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
8+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9+
10+
Links are updated with Changelog.jl using the command:
11+
12+
```julia
13+
Changelog.generate(
14+
Changelog.CommonMark(), # output type
15+
"CHANGELOG.md"; # input and output file
16+
repo = "JuliaDataCubes/PyramidScheme.jl", # default repository for links
17+
)
18+
```
19+
20+
## [0.1.2]
21+
22+
### Added
23+
24+
- Implemented proper MakieRecipies this should make Observable(::Pyramid) work
25+
- Added `cat` of Pyramids to concatenate on all levels
26+
27+
### Fixed
28+
29+
- Improved computation of the dimensions of the aggregated levels

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "PyramidScheme"
22
uuid = "ec211b67-1c2c-4319-878f-eaee078ee145"
33
authors = ["Felix Cremer <[email protected]> and contributors"]
4-
version = "0.1.1"
4+
version = "0.1.2"
55

66
[deps]
77
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
@@ -42,7 +42,7 @@ Extents = "0.1.3"
4242
FillArrays = "1.11"
4343
Flatten = "0.4.3"
4444
GeoInterface = "1.3.4"
45-
Makie = "0.21.2, 0.22, 0.23, 0.24"
45+
Makie = "0.24"
4646
OffsetArrays = "1.14"
4747
Proj = "1.7.1"
4848
Rasters = "0.11.8, 0.12, 0.13, 0.14"

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# PyramidScheme
2+
[![codecov](https://codecov.io/gh/JuliaDataCubes/PyramidScheme.jl/graph/badge.svg?token=EBhwEBXlnx)](https://codecov.io/gh/JuliaDataCubes/PyramidScheme.jl)
3+
24

35
PyramidScheme.jl is a package to easily and efficiently compute pyramids of a given datacube which might be larger than RAM.
46
It uses DiskArrayEngine.jl as the computational backend.

ext/PyramidSchemeMakieExt.jl

Lines changed: 136 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,150 @@
11
module PyramidSchemeMakieExt
22
using Makie: Axis, Colorbar, DataAspect, Figure, FigureAxisPlot, Observable, Relative
3-
using Makie: on, heatmap!, image!
4-
import Makie: plot, plot!
3+
using Makie: on, heatmap!, map!
4+
using Makie.ComputePipeline: add_input!
5+
using Makie
6+
57
using PyramidScheme: Pyramid, switchkeys, levels, selectlevel, xkey, ykey
68
using DimensionalData: DimensionalData as DD
79
using DimensionalData.Dimensions: XDim, YDim
810
using Extents: Extent, extent, intersects
911
miss2nan(x) = ismissing(x) ? NaN : x
10-
"""
11-
plot(pyramids)
12-
Plot a Pyramid.
13-
This will plot the coarsest resolution level at the beginning and will plot higher resolutions after zooming into the plot.
14-
This is expected to be used with interactive Makie backends.
15-
"""
16-
function plot(pyramid::Pyramid;colorbar=true, size=(1155, 1043), kwargs...)
17-
#This should be converted into a proper recipe for Makie but this would depend on a pyramid type.
18-
fig = Figure(;size)
19-
lon, lat = DD.dims(DD.parent(pyramid))
20-
ax = Axis(fig[1,1], limits=(extrema(lon), extrema(lat)), aspect=DataAspect())
21-
hmap = plot!(ax, pyramid;kwargs...)
22-
if colorbar
23-
Colorbar(fig[1,2], hmap, height = Relative(3.5 / 4))
12+
13+
# hacks to get around DD hacks that get around Makie issues
14+
for p in (Heatmap, Image, Contour, Contourf, Contour3d, Spy, Surface)
15+
f = Makie.plotkey(p)
16+
@eval begin
17+
function Makie.$f(A::Pyramid; kwargs...)
18+
invoke(Makie.$f, Tuple{AbstractMatrix{<: Any}}, A; kwargs...)
19+
end
20+
function Makie.$f(A::Observable{<: Pyramid}; kwargs...)
21+
invoke(Makie.$f, Tuple{<:Union{<:AbstractMatrix{<: Any}, <:Observable{<: AbstractMatrix{<:Any}}}}, A; kwargs...)
22+
end
23+
Makie.expand_dimensions(::Type{$p}, p::Pyramid) = (p,)
24+
Makie.convert_arguments(::Type{$p}, p::Pyramid) = (p,)
2425
end
25-
ax.autolimitaspect = 1
26-
FigureAxisPlot(fig, ax, hmap)
2726
end
2827

29-
function plot!(ax, pyramid::Pyramid;interp=false, kwargs...)#; rastercrs=crs(parent(pyramid)),plotcrs=EPSG(3857), kwargs...)
30-
tip = levels(pyramid)[end-2][:,:]
31-
#@show typeof(tip)
32-
subtypes = Union{typeof.(pyramid.levels)..., typeof(parent(pyramid))}
33-
data = Observable{DD.AbstractDimMatrix}(tip)
34-
xval = only(values(extent(pyramid, XDim)))
35-
yval = only(values(extent(pyramid, YDim)))
36-
rasdataext = Extent(X=xval, Y=yval)
37-
rasext = extent(pyramid)
38-
on(ax.scene.viewport) do viewport
39-
limext = extent(ax.finallimits[])
40-
41-
datalimit = switchkeys(limext, rasext)
42-
data.val = miss2nan.(selectlevel(pyramid, datalimit, target_imsize=viewport.widths))
43-
notify(data)
44-
end
45-
on(ax.finallimits) do limits
46-
limext = extent(limits)
47-
# Compute limit in raster projection
48-
#trans = Proj.Transformation(plotcrs, rastercrs, always_xy=true)
49-
#datalimit = trans_bounds(trans, limext)
50-
datalimit = switchkeys(limext, rasext)
51-
if intersects(rasdataext, limext)
52-
rasdata = selectlevel(pyramid, datalimit, target_imsize=ax.scene.viewport[].widths)
53-
# Project selected data to plotcrs
54-
#data.val = Rasters.resample(rasdata, crs=plotcrs, method=:bilinear )
55-
data.val = miss2nan.(rasdata)
28+
struct PyramidConversion <: Makie.ConversionTrait end
29+
30+
function Makie.conversion_trait(::Type{<:Heatmap}, ::Pyramid)
31+
return PyramidConversion()
32+
end
33+
34+
function Makie.types_for_plot_arguments(::Type{<:Heatmap}, ::PyramidConversion)
35+
return Tuple{<:Pyramid}
36+
end
37+
38+
Makie.expand_dimensions(::PyramidConversion, p::Pyramid) = (p,)
39+
Makie.convert_arguments(::PyramidConversion, p::Pyramid) = (p,)
40+
41+
42+
43+
Makie.plottype(::Pyramid) = Makie.Heatmap
44+
45+
function Makie.plot!(plot::Heatmap{<: Tuple{<: Pyramid}})
46+
#=
47+
Go from a relative space rectangle
48+
to the rectangle in data space and pixel space,
49+
thus getting `ax.finallimits` and `ax.viewport`
50+
respectively.
51+
52+
This is a bit arcane but probably the simplest thing in the long run.
53+
=#
54+
inputpositions = [Point2f(0, 0), Point2f(1, 1)]
55+
add_input!(plot.attributes, :__pyramid_input_positions, inputpositions)
56+
Makie.register_positions_projected!(
57+
plot; input_space = :relative, output_space = :space,
58+
input_name = :__pyramid_input_positions,
59+
output_name = :__pyramid_dataspace_positions,
60+
)
61+
Makie.register_positions_projected!(
62+
plot; input_space = :relative, output_space = :pixel,
63+
input_name = :__pyramid_input_positions,
64+
output_name = :__pyramid_pixelspace_positions,
65+
)
66+
67+
data = Observable{DD.AbstractDimMatrix}(levels(plot.arg1[])[end-2][:, :])
68+
onany(plot, plot.arg1, plot.__pyramid_dataspace_positions, plot.__pyramid_pixelspace_positions) do pyramid, datapos, pixelpos
69+
xyext = values.(extent(pyramid, (XDim, YDim)))
70+
xval, yval = first(xyext), last(xyext)
71+
pyramid_data_ext = Extent(X=xval, Y=yval)
72+
pyramid_ext = extent(pyramid)
73+
74+
data_limits_ext = Extent(X = extrema(first, datapos), Y = extrema(x -> x[2], datapos))
75+
pixel_widths = Point2f(abs.(pixelpos[2] .- pixelpos[1]))
76+
77+
datalimit = switchkeys(data_limits_ext, pyramid_ext)
78+
79+
if intersects(pyramid_data_ext, data_limits_ext)
80+
data[] = miss2nan.(
81+
selectlevel(pyramid, datalimit, target_imsize = pixel_widths)
82+
)
5683
end
57-
notify(data)
84+
nothing
5885
end
59-
#@show typeof(data)
60-
hmap = heatmap!(ax, data; interpolate=interp, kwargs...)
86+
87+
heatmap!(plot, plot.attributes, data)
6188
end
89+
90+
function Makie.data_limits(p::Heatmap{<: Tuple{<: Pyramid}})
91+
extX, extY = extent(p.arg1[], (XDim, YDim))
92+
rect = Rect3f((extX[1], extY[1],0), (extX[2] - extX[1], extY[2] - extY[1], 0))
93+
return rect
94+
end
95+
Makie.boundingbox(p::Heatmap{<: Tuple{<: Pyramid}}, space::Symbol = :data) = Makie.apply_transform_and_model(p, Makie.data_limits(p))
96+
97+
98+
# """
99+
# plot(pyramids)
100+
# Plot a Pyramid.
101+
# This will plot the coarsest resolution level at the beginning and will plot higher resolutions after zooming into the plot.
102+
# This is expected to be used with interactive Makie backends.
103+
# """
104+
# function plot(pyramid::Pyramid;colorbar=true, size=(1155, 1043), kwargs...)
105+
# #This should be converted into a proper recipe for Makie but this would depend on a pyramid type.
106+
# fig = Figure(;size)
107+
# lon, lat = DD.dims(DD.parent(pyramid))
108+
# ax = Axis(fig[1,1], limits=(extrema(lon), extrema(lat)), aspect=DataAspect())
109+
# hmap = plot!(ax, pyramid;kwargs...)
110+
# if colorbar
111+
# Colorbar(fig[1,2], hmap, height = Relative(3.5 / 4))
112+
# end
113+
# ax.autolimitaspect = 1
114+
# FigureAxisPlot(fig, ax, hmap)
115+
# end
116+
117+
# function plot!(ax, pyramid::Pyramid;interp=false, kwargs...)#; rastercrs=crs(parent(pyramid)),plotcrs=EPSG(3857), kwargs...)
118+
# tip = levels(pyramid)[end-2][:,:]
119+
# #@show typeof(tip)
120+
# subtypes = Union{typeof.(pyramid.levels)..., typeof(parent(pyramid))}
121+
# data = Observable{DD.AbstractDimMatrix}(tip)
122+
# xyext = values.(extent(pyramid, (XDim, YDim)))
123+
# xval = first(xyext)
124+
# yval = last(xyext)
125+
# rasdataext = Extent(X=xval, Y=yval)
126+
# rasext = extent(pyramid)
127+
# on(ax.scene.viewport) do viewport
128+
# limext = extent(ax.finallimits[])
129+
130+
# datalimit = switchkeys(limext, rasext)
131+
# data[] = miss2nan.(selectlevel(pyramid, datalimit, target_imsize=viewport.widths))
132+
# end
133+
# on(ax.finallimits) do limits
134+
# limext = extent(limits)
135+
# # Compute limit in raster projection
136+
# #trans = Proj.Transformation(plotcrs, rastercrs, always_xy=true)
137+
# #datalimit = trans_bounds(trans, limext)
138+
# datalimit = switchkeys(limext, rasext)
139+
# if intersects(rasdataext, limext)
140+
# rasdata = selectlevel(pyramid, datalimit, target_imsize=ax.scene.viewport[].widths)
141+
# # Project selected data to plotcrs
142+
# #data.val = Rasters.resample(rasdata, crs=plotcrs, method=:bilinear )
143+
# data.val = miss2nan.(rasdata)
144+
# end
145+
# notify(data)
146+
# end
147+
# #@show typeof(data)
148+
# hmap = heatmap!(ax, data; interpolate=interp, kwargs...)
149+
# end
62150
end

src/PyramidScheme.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,10 @@ end
124124
Pyramid(newbase, newlevels, DD.metadata(A))
125125
end
126126

127-
function Base.map(f, A::Pyramid)
128-
newbase = map(f, parent(A))
129-
newlevels = [map(f, levels(A,i)) for i in 1:nlevels(A)]
130-
Pyramid(newbase, newlevels, DD.metadata(A)) # This should handle metadata better.
127+
function Base.map(f, A1::Pyramid, As::Pyramid...)
128+
newbase = map(f, parent(A1), parent.(As)...)
129+
newlevels = [map(f, levels(A1,i), levels.(As, i)...) for i in 1:nlevels(A1)]
130+
Pyramid(newbase, newlevels, DD.metadata(A1)) # This should handle metadata better.
131131
end
132132

133133
function DD.show_after(io::IO, mime, A::Pyramid)
@@ -458,8 +458,8 @@ end
458458

459459

460460

461-
xkey(keyext) = DD.dim2key(DD.dims(keyext, XDim))
462-
ykey(keyext) = DD.dim2key(DD.dims(keyext, YDim))
461+
xkey(keyext) = DD.name(DD.dims(keyext, XDim))
462+
ykey(keyext) = DD.name(DD.dims(keyext, YDim))
463463
#TODO write test, move to utils.jl
464464

465465
"""

test/runtests.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ end
4242
@test size(subpyramid) == (10,10)
4343
@test parent(subpyramid) == data[1:10,1:10]
4444
fig, axis, h = plot(pyramid)
45+
@test length(axis.scene.plots) == 1
46+
plot!(axis, pyramid)
47+
@test length(axis.scene.plots) == 2
4548
end
4649

4750
#= building an RGB pyramid doesn't work, need to think more about it.
@@ -161,6 +164,23 @@ end
161164
end
162165

163166
end
167+
@testitem "Map of pyramids" begin
168+
using DimensionalData
169+
pyr1 = Pyramid(fill(1,X(1:128),Y(1:128)), tilesize=16)
170+
pyr2 = Pyramid(fill(1, X(1:128),Y(1:128)), tilesize=16, resampling_method=sum)
171+
pyr1_neg = map(x-> x-1, pyr1)
172+
@test all(all.(iszero, pyr1_neg.levels))
173+
@test iszero(pyr1_neg.base)
174+
pyr2_neg = map(x-> x-1, pyr2)
175+
@test pyr2_neg.levels[1][1,1] == 3
176+
177+
pyrsum = map((x,y) -> x + y, pyr1, pyr2)
178+
@test pyrsum[100,30] == 2
179+
@test pyrsum.levels[1][10,10] == 5
180+
@test pyrsum.levels[2][10,10] == 17
181+
end
182+
183+
164184
#=
165185
@testitem "Comparing zarr pyramid with tif pyramid" begin
166186
using PyramidScheme: PyramidScheme as PS

0 commit comments

Comments
 (0)