Real time plotting (with Makie)

Currently I’m trying to plot real-time measured values (force values from a robot controller). By real-time I mean, at a 125Hz frequency as the values arrive. I’m working with Makie and concluded to this code based on this issue.
I will just comment the above code:
I got the values over TCP as a string in a format of "[1.1, 2.2, 3.3, 4.4, 5.5, 6.6]\n".
Then I update a string Node to process it with readdlm(). Right after processing I push the new values into a “fix size” array. This function is used to make the “fix size behaviour”:

function pushTo!(A, newX, maxSize)
    if size(A, 1) < maxSize
        push!(A, newX)
    else
        popfirst!(A)
        push!(A, newX)
    end
end

After updating the arrays I use the lastUpdate node to update the plot. I throttle it down:

        if (current_time-lastUpdate[])/1000000 > UPDATE_LAT
            lastUpdate[] = current_time
        end

where UPDATE_LAT is around 200 miliseconds. If that time is up, the next function is called to update the plot:

function updatePlot(val)
    fxNode[] = FxV
    fyNode[] = FyV
    fzNode[] = FzV
    txNode[] = TxV
    tyNode[] = TyV
    tzNode[] = TzV
    # update limits:
    for i in 1:6
        AbstractPlotting.update_limits!(sArr[i])
    end
    AbstractPlotting.update!(scene)
end

Here’s an example picture what I’ve created (with some random noise):

I tried to lower the UPDATE_LAT value to like 100 ms, but then plotting freezes out.
Overall I’m happy that it works, but could it be improved? And is this the way to do this kind of plotting, or should I do something completely different?

Some version infos:

julia> versioninfo()
Julia Version 1.0.2
Commit d789231e99 (2018-11-08 20:11 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-3632QM CPU @ 2.20GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.0 (ORCJIT, ivybridge)
Environment:
  JULIA_EDITOR = "C:\Users\cstamas\AppData\Local\atom\app-1.34.0\atom.exe" -a
  JULIA_NUM_THREADS = 4

(julia-RTPlot) pkg> st
    Status `C:\Users\cstamas\Documents\Coding\julia-RTPlot\Project.toml`
  [537997a7] AbstractPlotting v0.9.4 #master (https://github.com/JuliaPlots/AbstractPlotting.jl.git)  [6e4b80f9] BenchmarkTools v0.4.2
  [5789e2e9] FileIO v1.0.5
  [e9467ef8] GLMakie v0.0.4 #master (https://github.com/JuliaPlots/GLMakie.jl.git)
  [ee78f7c6] Makie v0.9.1 #master (https://github.com/JuliaPlots/Makie.jl.git)
  [510215fc] Observables v0.2.3

(I feel this post a bit unnecessary as my code is working, but I’m curious, if I made some mistake and/or how could I improve it.)

3 Likes

One thing you could do to optimize drawing is to initially define a very large timescale, and then plot on that. This will avoid the update_limits!, which causes the plot to be redrawn.

1 Like

Could you paste a complete working example (e.g. with random data)? I can take a look at it!

2 Likes

Here I created a working example. There’s also a longer description to describe the problem and the solution. The code is more or less commented. (I tested it only on windows, but I don’t think that anything that I use is platform dependent.)
I have two concerns (besides that I don’t know if my approach is good):

  • Almost everywhere I use global variables, which I find fragile (and the only way I know to do this).
  • Don’t know if I should use other datastructure (for example CircularBuffer).

I would be pleased, if you could review it.

This will avoid the update_limits! , which causes the plot to be redrawn.

I think I need the call update_limits!, because it’s possible that the (y) values are outside of the current range. The x range is fixed. (I also updated the code since the original post).

That’s not true… update_limits just causes the scene graph to get layouted, it has nothing to do with drawing the plot! Depending on your scene, update_limit! might still be the bottleneck though, since drawing is usually very fast.

That looks a bit like you turned your initial example into the opposite of a minimal example :smiley:
Do you also have something that shows the gist of your plotting code in one file with only a couple of lines? Your initial code looked like that, but was incomplete.

Yeah, it’s possible… :smiley: I tried to give a working example, therefore I needed to implement the other side of the TCP connection too.

Not sure how to compress it to a couple lines (I need to learn this too), tried to do it in this gist. It’s possible that it is still too verbose. :smiley:

1 Like

That’s more like it - although it doesn’t run, so it’s still not a minimal working example (MWE)… Also, you could just use random data instead of including server code :wink:

Yeah, I can do a working and a minimal example, but not a MWE.
Here’s the next try as gist and also an src/mwe.jl in the git repo.
(It’s considered minimal as it implements the things I need: JSON parsing and buttons for pause/restart).
newvalues(number, sleep_time) updates the input_string variable number times with sleep(sleep_times) between.

julia> include("src/mwe.jl")
0x000238a0fd5d4d40

julia> scene

julia> newvalues(1, 0.1)

julia> newvalues(100, 0.01)

Maybe you need to start an approach of divide and conquer.
Where is the real bottleneck?

  • Check the parts that run at 125Hz first:
    • Are you happy that you can read from the socket and parse the JSON fast enough with no display?
    • Is the FIFO buffer mechanism your using able to run fast enough?
  • If your sure it isn’t those aspects, then prototype the part running at the lower rate of 10-20Hz, i.e. the plotting part (or inter task issues, if your using separate tasks), in which can you can use a MWE which just plots random values, where you don’t need the JSON parsing etc., you probably don’t need buttons for this as well, it just needs to run for a minute, plotting at 10 frames a second. Heck in principle what your plotting should with proper GPU support be able to run at the 125Hz, consider people claim over 100 fps in games like quake (now I don’t say you need to aim for that rate, anything above 25 is unnecesary, since TVs ran in many parts of the world ran at 25Hz with no complains from viewers, and I would suggest from other scientific displays I have done in the past outside the Julia environment, that frame rates above 10Hz mostly becomes wasted).
  • If your plotting MWE works good enough, it should just be a case of putting the parts together and making sure you don’t create some bottleneck in the way the parts work together.

In any case, I’m interested in following to here if there are other revelations, since in the near future I might also need to do something similar.

Hello,

I myself have been looking to acquire high frequency signals and display them with a good framerate. The MWE below runs with either static axes or “chart” style time scrolling. For simplicity I’m just filling up an array and using the system clock, but in practice data would come in chunks and plotting will be triggered by a hardware clock’s callback function (rather than the sleep timer)…

Anyhow: The bottom line is (in my hands) plotting via Observables is insanely quick, but if you want dynamic axes it really costs you. Granted there’s some overhead with timing but I am getting frame render times of ~0.1ms with static axes and 20ish ms with “updating”.

@sdanisch Is there a better way to handle this? I noticed the double updating I’ve used is also how you made the animation example in the Makie gallery. For most time signals you could get away with a static or “on event” scaling Y-axis and then a regularly changing X-axis. I’m guessing this could be much simpler and quicker than min maxing each frame???

MWE of faux streaming data w/Makie plots:

using Makie

scene = Scene()
data = Node(rand(1000))
t = lift(c -> length(c), data)
y = lift(a -> to_value(data)[max(1, a-999):max(a, 1000)], t)
#x = lift(b -> collect(max(b-999, 1):max(b, 1000)), t)
x = 1:1000 # for static axis
lines!(scene, x, y)

@time for i in 1:500
frametime = @elapsed begin
    push!(data, append!(to_value(data), rand(10)))
    #AbstractPlotting.update_limits!(scene) #comment out update calls for fixed axis
    #AbstractPlotting.update!(scene)
    end
    sleep(max(0.020-frametime, 0))
    println(frametime)
end
9 Likes

Since it’s been some time I’d like to ask if there’s any new development or best practices how to do (very) fast axis limits updates. My application is also a real time plot of sensor values with moving time axes.

However, the length of data shown could be static, e.g. 30 seconds. But it has to move like a sliding window. Does that help to increase performance?

Thanks a lot :slight_smile:

3 Likes

I was curious about this too so here’s an updated version of @wsphillips’s script that let’s you pick between using autolimits!(), explicitly setting fixed limits, or not updating them at all:

using Printf
using Statistics
using GLMakie

function benchmark_plot(update_method)
    f = Figure()
    ax = Axis(f[1, 1]; title="Random data")
    data = Observable([Point2f(i, rand()) for i in 1:1000])
    lines!(ax, data)

    display(f)

    times::Vector{Float64} = Float64[]

    @time for i in 1:500
        new_points = [Point2f(length(data[]) + i, rand()) for i in 1:10]
        frametime = @elapsed begin
            data[] = append!(data[], new_points)

            if update_method == :explicit
                xlims!(ax, length(data[]) - 1000, length(data[]))
            elseif update_method == :auto
                autolimits!(ax)
            end
        end

        push!(times, frametime)
        sleep(max(0.020-frametime, 0))
    end

    @printf("Frame update time: %.5fs ± %.5f", mean(times), std(times))
end

And results on my machine, plotting fullscreen on a 2K monitor with integrated graphics:

julia> benchmark_plot(:auto)
 11.174539 seconds (1.03 M allocations: 155.969 MiB, 1.08% gc time)
Frame update time: 0.01011s ± 0.00760
julia> benchmark_plot(:explicit)
 11.040334 seconds (1.37 M allocations: 185.142 MiB, 0.97% gc time)
Frame update time: 0.00763s ± 0.00561
julia> benchmark_plot(:none)
 11.080176 seconds (246.17 k allocations: 108.857 MiB, 0.48% gc time)
Frame update time: 0.00407s ± 0.00494

The numbers vary significantly depending on how big the plot is, but TL;DR autolimits!() is in the ballpark of ~2.5x slower on my system. Though that’s still ~10ms so not terrible I’d say.

1 Like