From 077f44a17ce5b700d68a232e2f0451e156e10993 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 23 Jan 2024 19:24:00 +0100 Subject: [PATCH] initial commit --- .gitignore | 2 + Project.toml | 4 ++ examples/generic.jl | 13 ++++++ examples/hdf5.jl | 83 ++++++++++++++++++++++++++++++++++++ examples/ncdata.jl | 11 +++++ src/NDViewer.jl | 5 ++- src/layers.jl | 93 ++++++++++++++++++++++++++++++++++++++++ src/widgets.jl | 100 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 examples/generic.jl create mode 100644 examples/hdf5.jl create mode 100644 examples/ncdata.jl create mode 100644 src/layers.jl create mode 100644 src/widgets.jl diff --git a/.gitignore b/.gitignore index b067edd..8da5fc2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /Manifest.toml +*.hdf5 +*.nc diff --git a/Project.toml b/Project.toml index 79637ff..83739c5 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,10 @@ uuid = "e1a03473-7fd2-4259-81e1-ed5183eae3a5" authors = ["Makie contributors"] version = "1.0.0-DEV" +[deps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +Observables = "510215fc-4207-5dde-b226-833fc4488ee2" + [compat] julia = "1" diff --git a/examples/generic.jl b/examples/generic.jl new file mode 100644 index 0000000..be4abc8 --- /dev/null +++ b/examples/generic.jl @@ -0,0 +1,13 @@ +( + data=tempdata, + names=["x", "y", "z", "time"], +) +Dict( + "type" => "volume", + "data" => (1, 2, 3), +) + +Dict( + "type" => "heatmap", + "data" => (1, 2), +) diff --git a/examples/hdf5.jl b/examples/hdf5.jl new file mode 100644 index 0000000..7a2cbcc --- /dev/null +++ b/examples/hdf5.jl @@ -0,0 +1,83 @@ +using HDF5, GLMakie +using LinearAlgebra + + +hydrodynamics = h5open("Hydrodynamic.hdf5", "r") +water_properties = h5open("WaterProperties.hdf5", "r") + + +wp_results = water_properties["Results"] + +arr = map(["temperature_0000$i" for i in 1:9]) do name + wp_results["temperature"][name][] +end +arr[1] +with_time = cat(arr...; dims=4) +x = with_time[1] +filtered = map(y -> x ≈ y ? NaN : y, with_time) +filtered0 = map(y -> x ≈ y ? 0f0 : y, tmp1) +create_plot(filtered) + +grid = hydrodynamics["Grid"] +bathymetry = grid["Bathymetry"] +bathymetry["Maximum"][] +results = hydrodynamics["Results"] +velocity_u = results["velocity U"] +velocity_v = results["velocity V"] +velocity_w = results["velocity W"] +velocity_u_1 = velocity_u["velocity U_00001"] +velocity_v_1 = velocity_v["velocity V_00001"] +velocity_w_1 = velocity_w["velocity W_00001"] + + +data = Vec3f.(velocity_u_1[], velocity_v_1[], velocity_w_1[])[1:5:end, 1:5:end, 1:5:end] +points = Point3f.(Tuple.(CartesianIndices(data))) +arrows(vec(points), vec(data), arrowsize=0.5, color=norm.(vec(data))) + +vec(points) +vec(points)[1:5:end] + +f = Figure() +s = Slider(f[1, 1], range=range(mini, maxi, length=100)) +volume(f[2, 1], data, algorithm=:iso, isovalue=s.value) + +img = map(x-> RGBf.(x...), data) +volume(img, colormap=nothing, color=nothing) +heatmap(norm.(data[:, :, 5])) +begin + f = Figure() + uv = Vec2f.(velocity_u_1[], velocity_v_1[]) + s = Slider(f[2, 1], range=1:size(uv, 3)) + swidth = Slider(f[3, 1], range=1:10) + uv_mat = map(s.value, swidth.value) do idx, w + vec(uv[1:w:end, 1:w:end, idx]) + end + uv_vec = map(vec, uv_mat) + color = map(x-> norm.(x), uv_vec) + points = map(uv_mat) do uv + x = LinRange(1, 100, size(uv, 1)); y = LinRange(1, 100, size(uv, 2)) + vec(Point2f.(x, y')) + end + arrows(f[1, 1], points, uv_vec, color=color) + f +end + +begin + f = Figure() + uv = Vec3f.(velocity_u_1[], velocity_v_1[], velocity_w_1[]) + s = Slider(f[2, 1], range=1:size(uv, 3)) + swidth = Slider(f[3, 1], range=1:10) + uv_mat = map(s.value, swidth.value) do idx, w + vec(uv[1:w:end, 1:w:end, idx]) + end + uv_vec = map(vec, uv_mat) + color = map(x -> norm.(x), uv_vec) + points = map(uv_mat) do uv + x = LinRange(1, 100, size(uv, 1)) + y = LinRange(1, 100, size(uv, 2)) + z = LinRange(1, 100, size(uv, 3)) + vec(uv) + end + arrows(f[1, 1], points, uv_vec, color=color) + f +end diff --git a/examples/ncdata.jl b/examples/ncdata.jl new file mode 100644 index 0000000..d085158 --- /dev/null +++ b/examples/ncdata.jl @@ -0,0 +1,11 @@ +using YAXArrays, GLMakie, NDViewer +using YAXArrays, NetCDF +using DimensionalData + +data = Cube(joinpath(@__DIR__, "speedyweather.nc")) +vars = collect(data.Variable) + +temp = data[Variable=At("temp")] +tempdata = convert(Array{Float32}, temp.data) + +NDViewer.create_plot(tempdata) diff --git a/src/NDViewer.jl b/src/NDViewer.jl index e61a791..b53f13e 100644 --- a/src/NDViewer.jl +++ b/src/NDViewer.jl @@ -1,5 +1,8 @@ module NDViewer -# Write your package code here. +using Makie + +include("widgets.jl") +include("layers.jl") end diff --git a/src/layers.jl b/src/layers.jl new file mode 100644 index 0000000..dce3c17 --- /dev/null +++ b/src/layers.jl @@ -0,0 +1,93 @@ + +function arrows_layer!(ax::Makie.AbstractAxis, positions, directions, colormap) + +end + +function heatmap_layer!(ax::Makie.AbstractAxis, x, y, image, colormap) + +end + +function surface_layer!(ax::Makie.AbstractAxis, x, y, image, colormap) + +end + +function volume_layer!(ax::Makie.AbstractAxis, x, y, z, volume, colormap) + +end + +function create_plot(tempdata) + f = Figure() + fcolor = f[1, 1] + fslider = f[2, 1] + fplots = f[3, 1] + fcbar = f[4, 1] + time_slice = slice_dim(fslider[1, 1], tempdata, 4, "time") + slice_2d = slice_dim(fslider[2, 1], time_slice, 3, "height") + colormaps = colormap_widget(fcolor, tempdata) + grid = SliderGrid(fslider[3, 1], + (label="absorption", range=LinRange(0, 100, 100), startvalue=50), + ) + absorption = grid.sliders[1] + + ax, hp = heatmap(fplots[1, 1], slice_2d; axis=(; aspect=DataAspect()), colormaps...) + ax, cp = volume(fplots[1, 2], time_slice; axis=(; type=Axis3), + shading=NoShading, levels=10, algorithm=:absorption, absorption=map(Float32, absorption.value), + colormaps... + ) + cmaps = Base.structdiff(colormaps, (; nan_color=0, alpha=0)) + Colorbar(fcbar[1, 1]; vertical=false, tellheight=true, tellwidth=true, cmaps...) + + f +end + + +function match_dims(a, b) + # same + a == b && return -1000 + na = length(a); nb = length(b) + # permutation + na == nb && all(x-> x in a, b) && return -900 + # ready to slice + nb == na-1 && a[1:nb] == b && return -800 + if all(x-> (x in a), b) + i = 0 + for (x, y) in zip(a, b) + if x == y + i -= 1 + else + i += 1 + end + end + return i + end + # no match + return 1000 +end + +function get_dims!(fig, target_dims, names, gridpos, result::Dict) + available = collect(keys(result)) + if target_dims in available + return result[target_dims] + end + sort!(available, by=x->match_dims(x, target_dims)) + closest = first(available) + input_data = result[closest] + if any(x -> x > ndims(input_data[]), target_dims) + throw(ArgumentError("target_dims must be a subset of the dimensions of data")) + elseif length(closest) == length(target_dims) + data = map(x -> permutedims(x, (target_dims...,)), input_data) + result[target_dims] = data + return result + elseif length(closest) == length(target_dims) + 1 + dim_to_slice = only(setdiff(closest, target_dims)) + data = slice_dim(fig[gridpos, 1], input_data, dim_to_slice, names[dim_to_slice]) + new_dims = filter(x-> x != dim_to_slice, target_dims) + result[new_dims] = data + return result + else + missing_dims = setdiff(closest, target_dims) + new_target_dims = filter(x-> x != missing_dims[1], target_dims) + get_dims!(fig, new_target_dims, names, gridpos, result) + return get_dims!(fig, target_dims, names, gridpos + 1, result) + end +end diff --git a/src/widgets.jl b/src/widgets.jl new file mode 100644 index 0000000..23f60c5 --- /dev/null +++ b/src/widgets.jl @@ -0,0 +1,100 @@ + +function play_slider(figure, label, range) + l = Label(figure[1, 1], label, halign=:left) + button = Button(figure[1, 2]; label=">") + slider = Slider(figure[1, 3]; range=range) + vl = Label(figure[1, 4], map(string, slider.value), halign=:right) + sgrid = figure[1, 5] = GridLayout() + speed_button = [ + Button(sgrid[1, 1]; label="1") => 1, + Button(sgrid[1, 2]; label="10") => 10, + Button(sgrid[1, 3]; label="24") => 24, + Button(sgrid[1, 4]; label="60") => 60, + ] + playing = Threads.Atomic{Bool}(false) + fps = Threads.Atomic{Int}(24) + + for (b, s) in speed_button + on(b.clicks) do _ + fps[] = s + end + end + colgap!(sgrid, 2) + rowgap!(sgrid, 2) + scene = Makie.parent_scene(figure) + was_opened = isopen(scene) + Base.errormonitor(@async let i = first(range) + while !was_opened || isopen(scene) + was_opened = isopen(scene) + t = time() + if playing[] + i = mod1(i + 1, last(range)) + Makie.set_close_to!(slider, i) + end + elapsed = time() - t + sleep(max(0.001, (1 / fps[]) - elapsed)) + end + end) + on(button.clicks) do _ + if playing[] + button.label[] = ">" + playing[] = false + else + button.label[] = "||" + playing[] = true + end + end + return slider.value +end + +function slice_dim(figure, arr, dim, dim_name) + arr_obs = convert(Observable, arr) + idx_obs = play_slider(figure, dim_name, collect(axes(arr_obs[], dim))) + return map(arr_obs, idx_obs) do arr, idx + return view(arr, ntuple(i -> i == dim ? idx : (:), ndims(arr))...) + end +end + +function colormap_widget(f, limits, colorrange, lowclip, highclip, nan_color, alpha, colormap, colormaps=COLORMAPS) + current_crange = colorrange[] + rs_h = IntervalSlider(f[1, 1], + range=LinRange(limits..., 100), + startvalues=(current_crange...,)) + labeltext1 = lift(rs_h.interval) do int + string(round.(int, digits=2)) + end + Label(f[1, 2], labeltext1) + on(rs_h.interval) do v + colorrange[] = v + end + cmap_menu = Menu(f[:, 3]; options=colormaps, default=string(colormap[])) + on(cmap_menu.selection) do val + colormap[] = val + end +end + +const COLORMAPS = [ + :viridis, + :autumn1, + :balance, + :matter, + :turbid, + :bam50, + :berlin25, + :buda25, + :lipari25 +] + +function colormap_widget(f, data, colormaps=COLORMAPS) + limits = Makie.extrema_nan(data) + kw = ( + colorrange=Observable(Vec2f(limits)), + lowclip=Observable(:black), + highclip=Observable(:red), + nan_color=Observable(:transparent), + alpha=Observable(1.0), + colormap=Observable(:autumn1), + ) + colormap_widget(f, limits, kw.colorrange, kw.lowclip, kw.highclip, kw.nan_color, kw.alpha, kw.colormap, colormaps) + return kw +end