Makie - connecting Slider to update a 2D slice [matrix] heatmap display of a 3D array

Hello,

I would like to connect a Slider to an heatmap display of a 2D slice, i.e, matrix, of a 3D array:
a3DArray[:, :, sliderValue]. such that as the Slider is moved, a new 2D slice is selected/indexed and the 2D heatmap is updated to display it.

My understanding from the documentation is that I should use “lift” to make the Slider - heatmap connection. However, it is not clear to me how I should implement it. Any insight would be appreciated.

The current code fragment:

module QntImageDisplay

using ModernGL
using GLMakie
using Observables
using PerceptualColourMaps

export static_image_display
function static_image_display(
            imageModality::String,
            imageVolume::Array{Float32, 3}, # the 3D image array
            spatialExtent::Vector{Float32},
            windowCentre::Float32,
            windowWidth::Float32)

    # . . . 

    #
    # 2D displays
    #
    windowMin::Float32 = windowCentre - 0.5 * windowWidth
    windowMax::Float32 = windowCentre + 0.5 * windowWidth

    iLinRange = LinRange(1, size(imageVolume, 1), size(imageVolume, 1))
    jLinRange = LinRange(1, size(imageVolume, 2), size(imageVolume, 2))
    kLinRange = LinRange(1, size(imageVolume, 3), size(imageVolume, 3))

    # 2D axial display
    axis2DAxial = Axis(
                     figure[1, 2][1, 1],
                     aspect = AxisAspect(spatialExtent[2]/spatialExtent[1]),
                     backgroundcolor = :gray10)

    hidedecorations!.(axis2DAxial)

    # Slider to set the number/index of the 2D slice of the 3D array
    slider2DAxial = Slider( 
                        figure[1, 2][1, 2],
                        horizontal = false,
                        linewidth = 10.0,
                        range = 1:1:size(imageVolume, 3), 
                        startvalue = size(imageVolume, 3)//2)

    axialSliceNumber = to_value(slider2DAxial.value)

    # 2D heatmap display of a slice [matrix] of the 3D array
    axialHeatMap = heatmap!(
                       iLinRange, 
                       jLinRange, 
                       imageVolume[:,  :,  axialSliceNumber], # axialSliceNumber to be set by the Slider
                       color = :gray10,
                       colormap = colourMap,
                       colorrange = (windowMin, windowMax),
                       interpolate = true,
                       ssao = false,
                       transparency = false)

    # I think I need to use lift here. If so, then how do I implement it?

    # . . .

end

end
1 Like

Here’s a small example

data = randn(100, 100, 100)
z_index = Node(1)

slice = @lift(data[:, :, $z_index])

f = Figure()

heatmap(f[1, 1], slice)

sl = Slider(f[1, 2], horizontal = false, range = 1:size(data, 3))
connect!(z_index, sl.value)

f

You can also make the slider first and avoid the z_index observable, but that can be quite confusing in terms of code ordering. Usually small helper observables with clear names are better, and you can easily connect! them.

3 Likes

Your elegant method works well. Thank you.