I’m working on a simulation and was using QML before, but since it is not well maintained, I was looking into using GLMakie.
I have this code snippet where I update data in a matrix from a thread, and visualize it using heatmap from GLMakie. The updates are too fast to call a screen update every time the data (in the snippet just a while loop generating a random matrix), so I’m updating the screen on a timer with 60fps. The following code works fine:
using GLMakie, Observables
function update(obs)
obs[] = obs[]
return
end
const m = rand(500,500)
const obm = Observable(m)
f = Figure()
ax = Axis(f[1,1])
scene = heatmap!(ax, obm, colormap = :thermal)
display(f)
tm = Timer((timer) -> update(obm), 0., interval = 1/60)
Threads.@spawn while true
m .= rand(500,500)
GC.safepoint()
end
However, when I have data in a struct, suddenly the screen doesn’t update anymore when assigning the observable. The following code displays a static image, even though the data in the Observable is being updated:
using GLMakie, Observables
struct Container
m::Matrix{Float32}
end
function update(obs)
obs[] = obs[]
return
end
const c = Container(rand(500,500))
const obc = Observable(c.m)
f = Figure()
ax = Axis(f[1,1])
scene = heatmap!(ax, obc, colormap = :thermal)
display(f)
tm = Timer((timer) -> update(obc), 0., interval = 1/60)
Threads.@spawn while true
c.m .= rand(500,500)
GC.safepoint()
end
I don’t see why this shouldn’t work, can anyone explain to me?
You’re making an observable that stores the same array as your container in it. So mutations using the container field will affect the observable as well, because the content object is one and the same. However, the mutation of the inner array is nothing that the observable knows about. So once you’ve mutated you have to call notify(observable) to signal the change to all listeners.
I think he does that with the Timer!
Could just be notify(obc) though
Either I’m missing something here, or it’s a genuine bug!
But I can’t actually reproduce it with neither GLMakie@0.8.6 nor master (pretty close to 0.8.7)…
Maybe there’s something going on with the array in the container?
At least this works perfectly fine:
using GLMakie
cm = rand(500, 500)
const obc = Observable(cm)
f = Figure()
ax = Axis(f[1,1])
scene = heatmap!(ax, obc, colormap = :thermal)
display(f)
tm = Timer((timer) -> notify(obc), 0.0, interval=1 / 10)
Threads.@spawn while true
cm .= rand(500, 500)
GC.safepoint()
end
Btw, image! should work perfectly fine as a drop in replacement for this example and should run a lot faster!
Ah another thing: if you start julia with only one thread, this will hang and never yield to the rendering thread.
So, this could also be a problem you may run into!
Thanks for the replies. I’m aware I the observable doesn’t know when the data is mutated, which is why I defined the update function, I didn’t know about the notify function.
I tried this as well now and indeed it seems to be a problem with 0.8.7. I don’t understand how this can happen thought, because I thought the Observable shouldn’t really be aware where the vector comes from, so how can this have an effect on how the updates are pushed?
Yep, I’m running exactly the same thing. Just removing GLMakie and adding 0.8.6 makes the screen interactive again. Also, I’m running Julia with 8 threads. Going back and forth between 0.8.6 and 0.8.7 the behavior is consistent; it always works with 0.8.6. I’m having another problem that prevents me from using 0.8.6, though which seems to be fixed with 0.8.7 (I’m using a reshape of a view of an Vector, which gives an error when running the Texture function on 0.8.6).
Sorry I meant it doesn’t update. I can interact with it in that way, e.g. zooming moving the image etc. I just tried the master branch and there I’m observing the same problem.
I can’t see the video…
But seems like this is an m1 issue somehow…
Could you try to reduce the issue to something smaller?
E.g. switch out timer with @async while true or things like that
Could also check if obs updates on(x-> println("updating"), obs), or if it somethings deeper in the stack... Would also be nice to check if the intensityobservable here actually updates: https://github.com/MakieOrg/Makie.jl/blob/master/GLMakie/src/drawing_primitives.jl#L448 Also, did you try usingimage!` instead? That could help pin down the problem as well!
Actually, I’m currently on Windows. Also the whole while loop on a thread and timer where similar to how I would setup my program, but not necessary to show the problem. This is sufficient:
using GLMakie
struct Container
m::Matrix{Float32}
end
const c = Container(rand(500,500))
const obc = Observable(c.m)
on(obc) do m
println("Observable changed")
end
f = Figure()
ax = Axis(f[1,1])
scene = image!(ax, obc, colormap = :thermal)
GLMakie.activate!(inline=false)
display(f)
c.m .= rand(500,500)
notify(obc)
When calling notify, the line is printed but the screen is not updated. The same problem is also observed with image.
How do I check wether the intensity observable is being updated?
Still my question remains, how can this problem even exist? I’m really confused, because as far as the observable and everything in Makie are concerned, they’re just being passed a regular old vector, right?
If I could reproduce it, I’d say it’s related to the observable feature of ignore_equal_values which will skip updates if you mutate in place… but it should then just always fail, unless we somehow managed to run slightly different code in the end. I added it in a few places which should only be internal, so user facing mutation should still work, but maybe I didn’t do it carefully enough.
Otherwise I’m a bit clueless and would suspect something wonky with the tasks somewhere…
I also ran into this problem. It seems to be related to matrix m being Float32. If you define it as Float64, it will update. Here’s a modified example that shows this behavior. It will try to flash random arrays, but only the 64-bit one will update.
using GLMakie
GLMakie.activate!(inline=false)
GLMakie.set_window_config!(float=true)
struct Container
m32::Matrix{Float32}
m64::Matrix{Float64}
end
const c = Container(rand(20,20),rand(20,20))
const obc32 = Observable(c.m32)
const obc64 = Observable(c.m64)
on(obc32) do m
println("Observable changed")
end
f = Figure()
ax = Axis(f[1,1],title="Float32")
scene = heatmap!(ax, obc32, colormap = :thermal)
ax1=Axis(f[1,2], title="Float64")
scene = heatmap!(ax1, obc64)
display(f)
for n in 1:10
c.m32 .= rand(20,20)
c.m64 .= rand(20,20)
notify(obc32)
notify(obc64)
sleep(0.1)
end
Well, the latest release gives segfaults, which is fixed already for 5 months in main, but despite request to make a new release, this hasn’t been done already for quite a while.
Thanks a lot for the heads-up. I did comment on the GitHub issue about the topic, and the maintainer explained that he tried to fix another issue, that turned out to be much more complex than initially assumed.
That stopped him from releasing a fixed version.
I do think, that a segfauling release should not happen to begin with, and a hotfix is suitable in such a case.
To all our benefit, will the QML.jl project now join the bigger JuliaGraphics organization.
This allows a dedicated group of people to commit and release, see the surrounding discussion here.
I see, thanks for the explanation. It was actually a big headache for me because I was including it in a package I’m building, not just using it for a personal project where I can just install the main branch. Anyway, Makie is a bit more flexible for my use, so I’m happy with the switch, it just came at a bit of an unfortunate time.