Skip to content

Fix race condition during matplotlib rendering#91

Open
Tom-Willemsen wants to merge 1 commit into
mainfrom
genie_90
Open

Fix race condition during matplotlib rendering#91
Tom-Willemsen wants to merge 1 commit into
mainfrom
genie_90

Conversation

@Tom-Willemsen

@Tom-Willemsen Tom-Willemsen commented Jul 16, 2026

Copy link
Copy Markdown
Member

Description of work

This fixes a race/conflict which I believe occurs between calls to draw() and print_figure().

I believe the two things racing against each other are:

  • Updates triggered from the python side (possible because our backend is non-blocking)
  • Updates triggered by websocket messages sent from the GUI

To reproduce blank files being saved

  • Put the following script (based on MPL example which draws a 2d heatmap in similar way to refl alignment bluesky plans) in c:\scripts\mpl_stress.py
Details
import numpy as np


def mandelbrot_set(xmin, xmax, ymin, ymax, xn, yn, maxiter, horizon=2.0):
    X = np.linspace(xmin, xmax, xn).astype(np.float32)
    Y = np.linspace(ymin, ymax, yn).astype(np.float32)
    C = X + Y[:, None] * 1j
    N = np.zeros_like(C, dtype=int)
    Z = np.zeros_like(C)
    for n in range(maxiter):
        I = abs(Z) < horizon
        N[I] = n
        Z[I] = Z[I]**2 + C[I]
    N[N == maxiter-1] = 0
    return Z, N


def stress():
    import time

    import matplotlib.pyplot as plt

    import matplotlib
    from matplotlib import colors

    xmin, xmax, xn = -2.25, +0.75, 3000 // 2
    ymin, ymax, yn = -1.25, +1.25, 2500 // 2
    maxiter = 200
    horizon = 2.0 ** 40
    log_horizon = np.log2(np.log(horizon))
    Z, N = mandelbrot_set(xmin, xmax, ymin, ymax, xn, yn, maxiter, horizon)

    # Normalized recount as explained in:
    # https://linas.org/art-gallery/escape/smooth.html
    # https://web.archive.org/web/20160331171238/https://www.ibm.com/developerworks/community/blogs/jfp/entry/My_Christmas_Gift?lang=en

    # This line will generate warnings for null values, but it is faster to
    # process them afterwards using the nan_to_num
    with np.errstate(invalid='ignore'):
        M = np.nan_to_num(N + 1 - np.log2(np.log(abs(Z))) + log_horizon)

    dpi = 72
    width = 10
    height = 10*yn/xn
    fig = plt.figure(figsize=(width, height), dpi=dpi)
    ax = fig.add_axes((0, 0, 1, 1), frameon=False, aspect=1)

    # Shaded rendering
    light = colors.LightSource(azdeg=315, altdeg=10)
    M = light.shade(M, cmap=plt.colormaps["hot"], vert_exag=1.5,
                    norm=colors.PowerNorm(0.3), blend_mode='hsv')
    ax.imshow(M, extent=[xmin, xmax, ymin, ymax], interpolation="bicubic")
    ax.set_xticks([])
    ax.set_yticks([])

    # Some advertisement for matplotlib
    year = time.strftime("%Y")
    text = ("The Mandelbrot fractal set\n"
            "Rendered with matplotlib %s, %s - https://matplotlib.org"
            % (matplotlib.__version__, year))
    ax.text(xmin+.025, ymin+.025, text, color="white", fontsize=12, alpha=0.5)

    plt.show()
    from time import time
    plt.savefig("c:/instrument/mpl/" + str(time()) + ".png")
  • Load the script with g.load_script("mpl_stress.py", check_script=False)
  • Create directory c:\instrument\mpl
  • In PyDEV scripting console in GUI run for _ in range(500): stress()
  • Check for difference in behaviour between this branch and main:
    • On main some of the generated .png files in C:\Instrument\mpl will be blank / misrendered. On my machine, this occured for around 5% of files on average.
    • On this branch, all generated .png files should be identical and should all show exactly the same image. None should be blank/misrendered.
  • Tip: look at file sizes or thumbnails in windows explorer to easily spot 'blank' images

To reproduce plot not appearing until resized

  • Run in the python scripting console inside the GUI:
import matplotlib.pyplot as plt
for i in range(1000):
    plt.plot(range(i, i*2))
    plt.show()
    plt.savefig("c:/instrument/mpl/" + str(i))
  • On main, you may get a blank plot appearing in the GUI (until the plot is resized)
    • You may also see partially-rendered plots in saved files, for example.
  • On this branch, you should always see some plot in the GUI, and save files should be rendered consistently correctly.

To test

Closes #90

Acceptance criteria

List the acceptance criteria for the PR


Code Review

  • Is the code of an acceptable quality?
  • Are there unit tests in place? Are the unit tests small and test the a class in isolation?
  • Have the changes been documented in the release notes. If so, do they describe the changes appropriately?

Functional Tests

  • Do changes function as described? Add comments below that describe the tests performed.
  • How do the changes handle unexpected situations, e.g. bad input?
  • Has developer documentation been updated if required?

@Tom-Willemsen Tom-Willemsen changed the title Avoid plot-drawing conflict/race Fix race condition during matplotlib rendering Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plots not always rendering reliably

1 participant