MakieOrg / BeautifulMakie

Web page with basic examples showing how to use makie in julia
https://beautiful.makie.org
MIT License
374 stars 27 forks source link

Heatmap examples - center versus edge coordinates #9

Closed walra356 closed 3 years ago

walra356 commented 3 years ago

Is the following example of interest to you? I have more if you like.

using CairoMakie
CairoMakie.activate!()

x = [1,4,1,4,2]                        # specify x steplength
y = [0.5,2,1,4.5,1.5]                  # specify y steplength
σ = rand(length(x), length(y))         # beware of dims

theme = Theme(fontsize = 10, colormap = :gist_earth, resolution = (600,400) ); set_theme!(theme)

attr1 = (xticks=(stepcenters(x),string.(1:5)), yticks=(stepcenters(y),string.(1:5)), xlabel="x", )
attr2 = (xticks=(stepedges(x),string.(0:5)), yticks=(stepedges(y),string.(0:5)), xlabel="x", )

f = Figure()
ax = Axis(f[1,1]; attr1...); heatmap!(ax, steps(x), steps(y), σ)
ax = Axis(f[1,2]; attr2...); heatmap!(ax, steps(x), steps(y), σ)
f

image

for this you need the following convenience functions:

function steps(x::Vector{T} where T<:Real)

    sum(x .< 0) == 0 || error("Error: $x - nagative step length not allowed")

    return (s = append!(eltype(x)[0],x); [Base.sum(s[1:i]) for i ∈ Base.eachindex(s)])

end

function stepcenters(x::Vector{T} where T<:Real)

    δ = x .* 0.5
    s = append!(eltype(x)[0],x)

    return [Base.sum(s[1:i]) for i ∈ Base.eachindex(x)] .+ δ

end

stepedges(x::Vector{T} where T<:Real) = steps(x)
lazarusA commented 3 years ago

sure! I will add it to the gallery. The idea is to have a diverse site from all fields or points of view. Please fill free to add more suggestions.

walra356 commented 3 years ago

OK, please have a look. Here some comparisons between centers and edges for various range types. (At some point I have to learn how to make a pull request...)

For these one needs the convenience function edges:

using CairoMakie
using IntervalSets
CairoMakie.activate!()

function edges(x)

    xt = typeof(x); Δx = 0   

    xt <: Base.OneTo   ? (x1 = 0.5; x2 = x[end]-0.5; Δx = 1) :
    xt <: UnitRange    ? (x1 = x[1]-0.5; x2 = x[end]-0.5; Δx = 1) : 
    xt <: StepRange    ? (x1 = x[1]-0.5; x2 = x[end]-0.5; Δx = x.step) :  
    xt <: StepRangeLen ? (x1 = x[1]; x2 = x[end]; Δx = x.step) : 
    xt <: LinRange     ? (x1 = x[1]-0.5; x2 = x[end]-0.5; Δx = round.((x[end]-x[1])/x.len) ) : 
    xt <: IntervalSets.ClosedInterval ? (x = (x.left-0.5)..(x.right-0.5)) : error("RangeType $xtype not implemented")            

    return Δx == 0 ? x : Δx == 1 ? (x1:Δx:x2) : append!(eltype(x)[x[1]-Δx],x)

end

Example 1: Base.OneTo(n)

x = Base.OneTo(10)
y = Base.OneTo(7)
σ = rand(length(x), length(y))

theme = Theme(fontsize = 11, colormap = :gist_earth, resolution = (600,400) )
set_theme!(theme)

attr1 = (xlabel="x", xticks=[1,2,5,10], yticks=[1,2,5,y.stop],)
attr2 = (xlabel="x", xticks=[1,2,5,10], yticks=[0,1,2,5,y.stop],)

f = Figure()
ax = Axis(f[1,1]; attr1...); heatmap!(ax, x, y, σ)
ax = Axis(f[1,2]; attr2...); heatmap!(ax, edges(x), edges(y), σ)
f

image

Example 2: UnitRange(n1:n2)

x = -8:0
y = 1:7
σ = rand(length(x), length(y))

theme = Theme(fontsize = 11, colormap = :gist_earth, resolution = (600,400) )
set_theme!(theme)

attr1 = (xlabel="x", xticks=[-8,-5,-2,0], yticks=[1,2,5,y.stop],)
attr2 = (xlabel="x", xticks=[-8,-5,-2,0], yticks=[0,7],)

f = Figure()
ax = Axis(f[1,1]; attr1...); heatmap!(ax, x, y, σ)
ax = Axis(f[1,2]; attr2...); heatmap!(ax, edges(x), edges(y), σ)
f

image

Example 3: LinRange(n1:n2)

x = LinRange(-8:0)
y = 1:7
σ = rand(length(x), length(y))

theme = Theme(fontsize = 11, colormap = :gist_earth, resolution = (600,400) ); set_theme!(theme)

attr1 = (xlabel="x", xticks=[-8,-5,-2,0], yticks=[1,2,5,7],)
attr2 = (xlabel="x", xticks=[-8,-5,-2,0], yticks=[0,1,2,5,7],)

f = Figure()
ax = Axis(f[1,1]; attr1...); heatmap!(ax, x, y, σ)
ax = Axis(f[1,2]; attr2...); heatmap!(ax, edges(x), edges(y), σ)
f

image

Example 4: StepRangeLen(n1:step:n2)

x = -8:2:0
y = 1:7
σ = rand(length(x), length(y))

theme = Theme(fontsize = 11, colormap = :gist_earth, resolution = (600,400) ); set_theme!(theme)

attr1 = (xlabel="x", xticks=x, yticks=[1,2,5,7],)
attr2 = (xlabel="x", xticks=x, yticks=[0,1,2,5,7],)

f = Figure()
ax = Axis(f[1,1]; attr1...); heatmap!(ax, x, y, σ)
ax = Axis(f[1,2]; attr2...); heatmap!(ax, edges(x), edges(y), σ)
f

image

Example 5: IntervalSets.ClosedInterval(n1..n2)

x = -9..0
y = 1..11
σ = rand(10, 11)

theme = Theme(fontsize = 10, colormap = :gist_earth, resolution = (600,400) ); set_theme!(theme)

attr1 = (xlabel="x", xticks=[-9,-5,-2,0], yticks=[0,7],)
attr2 = (xlabel="x", xticks=[-9,-5,-2,0], yticks=[0,7],)

f = Figure()
ax = Axis(f[1,1]; attr1...); heatmap!(ax, x, y, σ)
ax = Axis(f[1,2]; attr2...); heatmap!(ax, edges(x), edges(y), σ)
f

image

lazarusA commented 3 years ago

Your previous example now lives here: https://lazarusa.github.io/BeautifulMakie/heatmaps/heatmapIrregularCategories/ please feel free to add your new examples using edges in a pull request, since that one works with negative values. Or, I could do that over the weekend. I named categorical ticks, or? what other use could you make of them?

walra356 commented 3 years ago

Great, fast and thank you! I will rework the above examples into a display scene and will try to make a pull request but I did not do this before on a cloned repository... We shall see. I think I can put another .jl file into your assets but the testing is not cleat to me. may well I put the stuf here.

lazarusA commented 3 years ago

for now I think its ok to put things into _assets/scripts/ I can take it from there more easily. The point is not to rework that much the .jl file. Plus it will be great to know use cases (where could this be applied in order to have a descriptive name for the script and people can find them more easily).

walra356 commented 3 years ago

OK, I will give you a preview before uploading the .jl file.

walra356 commented 3 years ago

What about the following heatmap range specification guide? Please comment before I do the upload.

using CairoMakie
CairoMakie.activate!()

theme = Theme(fontsize = 10, colormap = :gist_earth, resolution = (800,1600) )

n = 10   # matrix x dimension
m = n-1  # matrix y dimension

set_theme!(theme)
attr0 =(xlabelsize = 13, ylabelsize = 13, titlesize = 15)

# .............................. Base.OneTo ...................................
x1 = Base.OneTo(n)  # range specification x axis
y1 = Base.OneTo(m)  # range specification y axis
σ1 = rand(length(x1), length(y1))
attr1a = (attr0..., xticks = [1, 2, x1.stop], yticks = [1, 2, y1.stop], 
                xlabel = "$x1", ylabel = "$y1", title = "default (1a)", )
attr1b = (attr0..., xticks = [0, 1, 2, x1.stop], yticks = [0, 1, 2, y1.stop], 
                xlabel = "$x1", ylabel = "$y1", title = "default shifted to edges (1b)", )

#................................ UnitRange ............................
x2 = UnitRange(-m:0)
y2 = UnitRange(1:m)
σ2 = rand(length(x2), length(y2))
attr2a = (attr0..., xticks = [x2.start, -2, x2.stop], yticks = [y2.start, 2, y2.stop], 
                xlabel = "UnitRange($x2)", ylabel ="UnitRange($y2)", 
                title = "UnitRange on centers (2a)", )
attr2b = (attr0..., xticks=[x2.start-1, x2.start, -2, x2.stop], yticks=[y2.start-1, y2.start, 2, y2.stop], 
                xlabel = "UnitRange($x2)", ylabel = "UnitRange($y2)", 
                title = "UnitRange shifted to edges (2b)", )

#................................ StepRangeLen ............................
x3 = range(-21.1, 0, length=n) 
y3 = range(1, 5, length=m)
σ3 = rand(length(x3), length(y3))
attr3a = (attr0..., xticks = [x3[1], x3[end]], yticks = [y3[1], y3[end]], 
                xlabel = "range(-21.1, 0, length=$n)", ylabel =  "range(1, 5, length=$m)", 
                title = "StepRangeLen on centers (3a)", )
attr3b = (attr0..., xticks = [x3[1], x3[end]], yticks = [y3[1], y3[end]], 
                xlabel = "range(-21.1, 0, length=$n)", ylabel = "range(1, 5, length=$m)", 
                title = "StepRangeLen shifted to edges (3b)", )

#................................ LinRange ............................
x4 = LinRange(-21.1,0,n)
y4 = LinRange(1,5,m)
σ4 = rand(length(x2), length(y2))
attr4a = (attr0..., xticks = [x4[1], x4[end]], yticks = [y4[1], y4[end]], 
                xlabel = "LinRange(-21.1,0,$n)", ylabel =  "LinRange(1,5,$m))", 
                title = "LinRange on centers (4a)", )
attr4b = (attr0..., xticks = [x4[1], x4[end]], yticks = [y3[1], y3[end]], 
                xlabel = "LinRange(-21.1,0,$n)", ylabel = "LinRange(1,5,$m)", 
                title = "LinRange shifted to edges (4b)", )

#................................ ClosedInterval ............................
x5 = -21.1..0
y5 = 1..10
σ5 = rand(n,m)
attr5a = (attr0..., xticks = [x5.left, x5.right], yticks = [y5.left, y5.right], 
                xlabel = "ClosedInterval($x5)", ylabel =  "ClosedInterval($y5)", 
                title = "ClosedInterval on centers (5a)",)
attr5b = (attr0..., xticks = [x5.left, x5.right], yticks = [y5.left, y5.right], 
                xlabel = "ClosedInterval($x5)", ylabel= "ClosedInterval($y5)", 
                title = "ClosedInterval shifed to edges (5b)",)

fig = Figure()
ax = Axis(fig[1,1]; attr1a...); heatmap!(ax, x1, y1, σ1)
ax = Axis(fig[1,2]; attr1b...); heatmap!(ax, edges(x1), edges(y1), σ1)
ax = Axis(fig[2,1]; attr2a...); heatmap!(ax, x2, y2, σ2)
ax = Axis(fig[2,2]; attr2b...); heatmap!(ax, edges(x2), edges(y2), σ2)
ax = Axis(fig[3,1]; attr3a...); heatmap!(ax, x3, y3, σ3)
ax = Axis(fig[3,2]; attr3b...); heatmap!(ax, edges(x3), edges(y3), σ3)
ax = Axis(fig[4,1]; attr4a...); heatmap!(ax, x4, y4, σ4)
ax = Axis(fig[4,2]; attr4b...); heatmap!(ax, edges(x4), edges(y4), σ4)
ax = Axis(fig[5,1]; attr5a...); heatmap!(ax, x5, y5, σ5)
ax = Axis(fig[5,2]; attr5b...); heatmap!(ax, edges(x5; dim = size(σ5,1)), edges(y5; dim = size(σ5,2)), σ5)
st = Label(fig[0,:], text = "Heatmap range specification guide for 'center' and 'edge' formats
                            \n (with demo of manual ticks)", textsize = 20)
fig

image

This requires the function 'edges':

using IntervalSets

function edges(x; dim = 0)

    T = typeof(x)
    E = eltype(x)

    strErr = "RangeType $T not implemented"

    T <: Base.OneTo   ? Δx = 1 :
    T <: UnitRange    ? Δx = 1 : 
    T <: StepRange    ? Δx = x.step :  
    T <: StepRangeLen ? Δx = x.step : 
    T <: LinRange     ? Δx = (x.stop-x.start)/(x.len-1) : 
    T <: IntervalSets.ClosedInterval ? Δx = (x.right-x.left)/(dim-1) : error(strErr)

    return dim > 0 ? (x.left-0.5Δx)..(x.right-0.5Δx) : Δx == 1 ? x .- 0.5Δx : x .- E[0.5Δx]

end
lazarusA commented 3 years ago

As I said before, where could one used this kind of plots? (a reference maybe with some use cases). It's nice to have custom ticks, but I don't see a practical use for them. The first, yeah kind of, if I have some categorical data where each variable expands a certain value. However, for these new examples its not clear to me what could they represent or in what kind of data visualisation scenario I will need them. So, before including them I will like to see use cases out there (links to similar plots).

walra356 commented 3 years ago

In physics, biology and astronomy heatmaps are used to analyze images of pixel cameras in false colors (starting from matrices representing the exposure of pixel arrays). In many cases pixel resolution is an issue.

At some point one has to relate the image to some physical scale (e.g., position on the chip or the celestial position); i.e., we have to move from integers (pixel indices) to reals (physical position on the chip). But how to introduce this scale? First of all, the pixel shape and pixel period varies with camera type. Furthermore, one has to choose an origin of reference to enable interpolation. One common choice of origin is to reference to a corner of the optically exposed area (conveniently defined by the left-bottom edge of the left-bottom pixel). In other cases the center of a given pixel may serve this purpose.

So, what users look are examples of how to put their favorite scale on the heatmap; i.e, a choice between ‘centers’ and ‘edges’ as well as a free choice of range in x and y directions.

As an practical example I show the heatmaps that triggered me to suggest the examples (astronomers will have other examples). Below you find 6 cross-correlated optical speckle patterns (for 6 different physical parameters). Note that I show raw data using default pixel coordinates.

R1-R6

To relate these images I need an absolute reference frame for which I can choose one of the options demonstrated in detail in the examples; e.g, to monitor displacement as a function of the physical parameter.

In my opinion anyone doing precision measurements with pixel cameras has to address these issues to obtain quantitative results.

Is this helpful?

If you don't like it, you are the boss! I do not insist (my problem is solved). Maybe it is to much to demonstrate that the function 'edges' is robust for anyone's favorite choice of range specification. On the other hand in'ts nice to show the various options the Makie offers?

lazarusA commented 3 years ago

On the contrary, I like it. And now we can link your answer to the example. I was not aware of this issue per se(pixel resolution&reference) and probably more people will be in the same situation. So having this discussion might help, or even motivate others to give a different approach. Go ahead with the pull request. Let me know if you have any problems.

walra356 commented 3 years ago

OK, but I get your point. My panels are rather abstract and detached from the application. Before learning the pull request procedure I will work out some pixel camera related appetizers. It may take a little while.

lazarusA commented 3 years ago

Sound good. Let know is you have any problems afterwards with the pull request.

walra356 commented 3 years ago

With emphasis on the application I can provide stand-alone examples of the figures shown below. This may be more practical than the demonstation of the function edges as given above. In the new examples I use an inline function for edges. If you agree I will try and do the pull request tomorrow. image image image image

lazarusA commented 3 years ago

just go for it. The easiest way is to put the scripts into /_assets/scripts/ and if you have data that could go into /_assets/data/. I can take it from there. Thanks!

walra356 commented 3 years ago

I tried to create and push to a remote branch 'walra356' and got the following message: remote: Permission to lazarusA/BeautifulMakie.git denied to walra356. fatal: unable to access 'https://github.com/lazarusA/BeautifulMakie.git/': The requested URL returned error: 403

lazarusA commented 3 years ago

In order to do a pull request you need to do the following steps: