Skip to content
64 changes: 64 additions & 0 deletions MPIViewers/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name = "MPIViewers"
uuid = "8bda2715-69d8-46f6-8f4f-eacbc82b6e08"
authors = ["Tobias Knopp <[email protected]>"]
version = "0.1.0"

[deps]
Cairo = "159f3aea-2a34-519c-b102-8c37f9878175"
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
Gtk4 = "9db2cae5-386f-4011-9d63-a5602296539b"
Gtk4Makie = "478199e7-b407-4926-87ea-7196203a28d8"
Graphics = "a2bd30eb-e257-5431-a919-1863eab51364"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
ImageUtils = "8ad4436d-4835-5a14-8bce-3ae014d2950b"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36"
MPIFiles = "371237a9-e6c1-5201-9adb-3d8cfa78fa9f"
MPIReco = "e4246700-6248-511e-8146-a1d1f47669d2"
MPISphericalHarmonics = "2527f7a4-0af4-4016-a944-036fbac19de9"
NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SphericalHarmonicExpansions = "504afa71-bae7-47b4-8ec9-3851161806ac"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[compat]
Cairo = "1.0"
Colors = "0.12"
FFTW = "1.3"
FileIO = "1.6"
Graphics = "1.1"
HDF5 = "0.15, 0.16"
ImageUtils = "0.2"
Images = "0.23, 0.24, 0.25"
LoggingExtras = ">= 0.4.2"
MPIFiles = "0.15, 0.16"
MPIReco = "0.7"
MPISphericalHarmonics = "0.0.10"
Reexport = "1.0"
CairoMakie = "0.12"
julia = "1.10"
Gtk4 = "0.7"
Gtk4Makie = "0.2, 0.3"


[extras]
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "HTTP"]
51 changes: 51 additions & 0 deletions MPIViewers/src/3DViewer/3DViewer.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
abstract type Abstract3DViewerMode end
modeName(m::Abstract3DViewerMode) = string(typeof(m))

#=
At the moment it is not possible to update a GtkMakieWidget with new plots.
This means we either have to create a new GtkMakieWidget to update the plot
or we hook into the observables of the plots.

The second approach breaks for certain plots, for example for isovolumes (if the isovalue changed).
=#
abstract type Abstract3DViewerModeRedrawType end
struct ObservableRedraw <: Abstract3DViewerModeRedrawType end
struct WidgetRedraw <: Abstract3DViewerModeRedrawType end
redrawType(::Abstract3DViewerMode) = ObservableRedraw()

include("3DViewerWidget.jl")
include("VolumeMode.jl")
include("SectionalMode.jl")
include("IsoSurfaceMode.jl")

function updateData!(m::Abstract3DViewerMode, arr)
# NOP
end

function showData!(gm::Gtk4Makie.GtkGLMakie, mode, data; kwargs...)
fig = Figure()
lscene = LScene(fig[1,1])
res = showData!(WidgetRedraw(), lscene, mode, data; kwargs...)
push!(gm, fig)
return res
end

export DataViewer3D, DataViewer3DWidget
mutable struct DataViewer3D
w::Gtk4.GtkWindowLeaf
dvw::DataViewer3DWidget
end

function DataViewer3D(imFG; kwargs...)
dv = DataViewer3D(; kwargs...)
updateData!(dv.dvw,imFG)
return dv
end

function DataViewer3D(; kwargs...)
w = GtkWindow("Data Viewer 3D",800,600)
dw = DataViewer3DWidget(; kwargs...)
push!(w,dw)
show(w)
return DataViewer3D(w,dw)
end
202 changes: 202 additions & 0 deletions MPIViewers/src/3DViewer/3DViewerWidget.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
mutable struct DataViewer3DWidget{A, C} <: Gtk4.GtkGrid
handle::Ptr{Gtk4.GObject}
lscene::LScene
scene::Scene
axis::A
cam::C
gm::Gtk4Makie.GtkGLMakie
# Controls
controlGrid::GtkGrid
modeCb::GtkComboBoxText
modeOpt::GtkMenuButton
frameAdj::GtkAdjustment
channelAdj::GtkAdjustment
cmapCb::GtkComboBoxText
cmin::GtkScaleButton
cmax::GtkScaleButton
# Data
data::AbstractArray
# Mode
modes::Vector{<:Abstract3DViewerMode}
end

function DataViewer3DWidget(; modes = [VolumeMode, SectionalMode, IsoSurfaceMode])
grid = GtkGrid()
handle = grid.handle

# Setup the controls
controls = GtkGrid()
controls.column_spacing = 5
controls.row_spacing = 5

# Mode selection
# We have to initialize the modeBox and the options later
# Once the viewer exits we can initialize the modes with the viewer as parent
# And then fill out our options
modeBox = GtkComboBoxText()
controls[1, 1] = GtkLabel("Mode")
controls[1, 2] = modeBox
modeOptions = GtkMenuButton()
controls[2, 1] = GtkLabel("Options")
controls[2, 2] = modeOptions
controls[3, 1:2] = GtkSeparator(:v)

# Frame/Time Selection
frameAdj = GtkAdjustment(1, 1, 1, 1, 1, 1)
frameSlider = GtkSpinButton(frameAdj, 1, 0)
controls[4, 1] = GtkLabel("Frames")
controls[4, 2] = frameSlider
channelAdj = GtkAdjustment(1, 1, 1, 1, 1, 1)
channelSlider = GtkSpinButton(channelAdj, 1, 0)
controls[5, 1] = GtkLabel("Channels")
controls[5, 2] = channelSlider
controls[6, 1:2] = GtkSeparator(:v)

# Colormap Selection
colormapBox = GtkComboBoxText()
cmaps = important_cmaps()
foreach(cm -> push!(colormapBox, cm), cmaps)
controls[7, 1] = GtkLabel("Colormap")
controls[7, 2] = colormapBox
colormapBox.active = 5 # viridis
cmin = GtkScaleButton(0, 99, 1, ["audio-volume-low"])
cmax = GtkScaleButton(1, 100, 1, ["audio-volume-high"])
cmin.value = 0
cmax.value = 100
controls[8, 1] = GtkLabel("Min")
controls[8, 2] = cmin
controls[9, 1] = GtkLabel("Max")
controls[9, 2] = cmax

grid[1, 1] = controls

# Setup the 3D viewing widget
fig = Figure()
lscene = LScene(fig[1,1])
scene = lscene.scene
cam = scene.camera_controls
axis = first(scene.plots) # Initial plot is an axis for LScene
gm = GtkMakieWidget()
push!(gm, fig)

grid[1, 2] = gm

viewer = DataViewer3DWidget(handle, lscene, scene, axis, cam, gm, controls, modeBox, modeOptions, frameAdj, channelAdj, colormapBox, cmin, cmax, [], Abstract3DViewerMode[])

# Initialize the modes
modes = map(mode -> mode(viewer), modes)
for mode in modes
push!(modeBox, modeName(mode))
end
modeBox.active = 0
modeOptions.popover = popover(first(modes))
viewer.modes = modes

initCallbacks(viewer)

return viewer
end

function initCallbacks(m::DataViewer3DWidget)
signal_connect(m.modeCb, "changed") do widget
foreach(mode -> mode.active = false, m.modes)
mode = m.modes[m.modeCb.active + 1]
mode.active = true
m.modeOpt.popover = popover(mode)
showData!(WidgetRedraw(), m)
end

signal_connect(m.frameAdj, "value_changed") do widget
showData!(m)
end
signal_connect(m.channelAdj, "value_changed") do widget
showData!(m)
end

signal_connect(m.cmapCb, "changed") do widget
showData!(m)
end

signal_connect(m.cmin, "value_changed") do widget, val
showData!(m)
end
signal_connect(m.cmax, "value_changed") do widget, val
showData!(m)
end
end


function updateData!(m::DataViewer3DWidget, file::Union{MDFFile, String})
imMeta = loadRecoData(file)
updateData!(m, imMeta)
end

# TODO handle 3 and 4 dim ImageMeta
function updateData!(m::DataViewer3DWidget, imMeta::ImageMeta{T, 5}) where T
m.data = imMeta
m.frameAdj.upper = size(m.data, 5)
m.channelAdj.upper = size(m.data, 1)
map(mode -> updateData!(mode, m.data), m.modes)
showData!(WidgetRedraw(), m)
end

function updateData!(m::DataViewer3DWidget, array::AbstractArray{T, 5}) where T
m.data = array
m.frameAdj.upper = size(m.data, 5)
m.channelAdj.upper = size(m.data, 1)
m.cmin = 0
m.cmax = 100
map(mode -> updateData!(mode, m.data), m.modes)
showData!(WidgetRedraw(), m)
end

function prepareData(m::DataViewer3DWidget)
frame = round(Int64, m.frameAdj.value)
channel = round(Int64, m.channelAdj.value)
data = ustrip.(m.data[channel, :, :, :, frame])
return data
end

function prepareDrawKwargs(m::DataViewer3DWidget)
dict = Dict{Symbol, Any}()
max = maximum(m.data)
cmin = (m.cmin.value/100) * max
cmax = (m.cmax.value/100) * max
dict[:cparams] = ColoringParams(cmin, cmax, Gtk4.active_text(m.cmapCb))
return dict
end

showData!(m::DataViewer3DWidget) = showData!(redrawType(m.modes[m.modeCb.active + 1]), m)

function showData!(::WidgetRedraw, m::DataViewer3DWidget)
# Prepare new GtkMakieWidget
delete!(m, m[1, 2])
m.gm = GtkMakieWidget()
m[1, 2] = m.gm

mode = m.modes[m.modeCb.active + 1]
data = prepareData(m)
kwargs = prepareDrawKwargs(m)
lscene = showData!(m.gm, mode, data; kwargs...)

m.lscene = lscene
m.scene = lscene.scene
m.axis = first(m.scene.plots)

# Set the camera to the old position
eyeposition = m.cam.eyeposition[]
upvector = m.cam.upvector[]
lookat = m.cam.lookat[]
m.cam = m.scene.camera_controls
update_cam!(m.scene, eyeposition, lookat, upvector)
return nothing
end

function showData!(re::ObservableRedraw, m::DataViewer3DWidget)
# TODO move these into a function
mode = m.modes[m.modeCb.active + 1]
data = prepareData(m)
kwargs = prepareDrawKwargs(m)
showData!(re, m.lscene, mode, data; kwargs...)
return nothing
end
77 changes: 77 additions & 0 deletions MPIViewers/src/3DViewer/IsoSurfaceMode.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export IsoSurfaceMode


mutable struct IsoSurfaceMode{P} <: Abstract3DViewerMode
pop::GtkPopover
parent::P
active::Bool
isoAdj::GtkAdjustment
isoMin::GtkLabel
isoMax::GtkLabel
isorange::GtkEntry
end
function IsoSurfaceMode(parent::P) where P
pop = GtkPopover()
grid = GtkGrid()
grid[1, 1] = GtkLabel("Iso Value:")
minLabel = GtkLabel("0.0")
maxLabel = GtkLabel("1.0")
adj = GtkAdjustment(0.5, 0.0, 1.0, 0.05, 0.05, 0.05)
scale = GtkScale(:h, adj)
scale.hexpand = true
grid[2, 1] = minLabel
grid[3, 1] = scale
grid[4, 1] = maxLabel

grid[1, 2] = GtkLabel("Iso Range:")
range = GtkEntry()
range.text = "0.05"
grid[2:4, 2] = range


pop.child = grid

mode = IsoSurfaceMode(pop, parent, false, adj, minLabel, maxLabel, range)

initCallbacks!(mode)

return mode
end

function initCallbacks!(mode::IsoSurfaceMode)
signal_connect(mode.isoAdj, "value_changed") do widget
if mode.active
showData!(WidgetRedraw(), mode.parent)
end
end
signal_connect(mode.isorange, "changed") do widget
if mode.active
showData!(WidgetRedraw(), mode.parent)
end
end
end

modeName(m::IsoSurfaceMode) = "Iso Surface"
popover(m::IsoSurfaceMode) = m.pop
redrawType(::IsoSurfaceMode) = WidgetRedraw()


function updateData!(m::IsoSurfaceMode, data)
min, max = extrema(data)
m.isoAdj.upper = max
m.isoAdj.lower = min
m.isoAdj.step_increment = (max - min) / 100
m.isoMin.label = string(min)
m.isoMax.label = string(max)
end

function showData!(re, scene::LScene, mode::IsoSurfaceMode, data; kwargs...)
showData!(re, scene.scene, mode, data; kwargs...)
return scene
end
function showData!(::WidgetRedraw, scene::Scene, mode::IsoSurfaceMode, data; kwargs...)
isovalue = mode.isoAdj.value
isoRange = parse(Float64, mode.isorange.text)
volume!(scene, data; algorithm=:iso, isovalue=isovalue, isorange=isoRange)
return scene
end
Loading