alex eckert
alex eckert

Reputation: 183

pyplot crashing on plt.figure() in macOS

I repeatedly get RuntimeError: main thread is not in main loop when I call plt.figure().

This is in a charting function that creates charts based on user data in a django webapp.

I've seen a warning on IDLE in python 3.4 that my version of Tcl/Tk may be unstable which links to http://www.python.org/download/mac/tcltk/ for more information, but this did not provide any guidance on how to determine what version I was running in a venv, or how to update the version in the venv.

This error only happens in my mac OS environment.

Not sure if I'm using matplotlib wrong, or if I need to update my environment. If I need to update, I have no idea how to go about this with a virtual environment.

Code:

def visualize(frictionloss):
    """
    Input: an instance of a FrictionLoss model,
    Return: a bar chart of the losses in b64 encoded image
    """
    # 6 bars
    ind = np.arange(6)
    width = .65

    # load psi lost in each section to a bar to show
    losses1 = (frictionloss.ug_1_loss,
              frictionloss.ug_2_loss,
              frictionloss.riser_loss,
              frictionloss.bulk_main_loss,
              frictionloss.cross_main_loss,
              frictionloss.head_1_loss)

    # additionally, show each head loss to later stack on top
    losses2 = (0, 0, 0, 0, 0, frictionloss.head_2_loss)
    losses3 = (0, 0, 0, 0, 0, frictionloss.head_3_loss)
    losses4 = (0, 0, 0, 0, 0, frictionloss.head_4_loss)
    losses5 = (0, 0, 0, 0, 0, frictionloss.head_5_loss)
    losses6 = (0, 0, 0, 0, 0, frictionloss.head_6_loss)

    # backend here has to be forced to one thread, otherwise it misbehaves
    lock = Lock()
    lock.acquire()
    frictionFig = plt.figure()
    lock.release()

    # build a stack of bar charts on top of each other,
    # the first 5 bars only get used in chart 1,
    # the last bar gets used in all 6 to show each head in the branch line
    ax = frictionFig.add_subplot(111)
    rects1 = ax.bar(ind, losses1, width, color='#e05757')
    rects2 = ax.bar(ind, losses2, width, color='#a38080', bottom=losses1)
    rects3 = ax.bar(ind, losses3, width, color='#efcece', bottom=losses2)
    rects4 = ax.bar(ind, losses4, width, color='#d69393', bottom=losses3)
    rects5 = ax.bar(ind, losses5, width, color='#fc6767', bottom=losses4)
    rects6 = ax.bar(ind, losses6, width, color='#a38080', bottom=losses5)

    # set axes and labels
    ax.set_ylabel('Lost Pressure (psi)')
    ax.set_xticklabels(('',
                       'UG 1',
                       'UG 2',
                       'Riser',
                       'Bulk Main',
                       'Cross Main',
                       'Heads'))

    # remove the frame lines
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['left'].set_visible(False)
    ax.spines['bottom'].set_visible(False)

    # Converts the graph into a string to be sent as a context variable
    buffer = BytesIO()
    frictionFig.savefig(buffer, format='png')
    buffer.seek(0)
    graph = quote(b64encode(buffer.getvalue()))

    return graph

Error:

Environment:


Request Method: GET
Request URL: http://127.0.0.1:8000/densitycurve/

Django Version: 1.11
Python Version: 3.6.1
Installed Applications:
['django.contrib.admin',
 'django.contrib.sites',
 'registration',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'home',
 'hydrograph',
 'densitycurve',
 'storage',
 'frictionloss',
 'flowtest',
 'pipeweight',
 'result',
 'useraccount',
 'watersupply',
 'seismichanger',
 'widget_tweaks',
 'crispy_forms']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback:

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/ENV/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
  41.             response = get_response(request)

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/ENV/lib/python3.6/site-packages/django/core/handlers/base.py" in _legacy_get_response
  249.             response = self._get_response(request)

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/ENV/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/ENV/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/ENV/lib/python3.6/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  23.                 return view_func(request, *args, **kwargs)

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/densitycurve/views.py" in densitycurve
  160.          (graph, outputText) = processData(densityInstance, request)

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/densitycurve/views.py" in processData
  204.  densityFigure = plt.figure()

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/ENV/lib/python3.6/site-packages/matplotlib/pyplot.py" in figure
  535.                                         **kwargs)

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/ENV/lib/python3.6/site-packages/matplotlib/backends/backend_tkagg.py" in new_figure_manager
  81.     return new_figure_manager_given_figure(num, figure)

File "/Users/Mark/Desktop/Professional/FSC/water/WATER/ENV/lib/python3.6/site-packages/matplotlib/backends/backend_tkagg.py" in new_figure_manager_given_figure
  98.         icon_img = Tk.PhotoImage(file=icon_fname)

File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py" in __init__
  3539.         Image.__init__(self, 'photo', name, cnf, master, **kw)

File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py" in __init__
  3495.         self.tk.call(('image', 'create', imgtype, name,) + options)

Exception Type: RuntimeError at /densitycurve/
Exception Value: main thread is not in main loop

Upvotes: 1

Views: 891

Answers (2)

alex eckert
alex eckert

Reputation: 183

UPDATE:

This issue can be solved by using a non-interactive backend (reference). I didn't discover this answer because it is a little tricky to change backends in django.

The standard way is to write at the top of your module:

models.py:

import matplotlib  
matplotlib.use('Agg')  
from matplotlib import pyplot as plt  

However in django, matplotlib may get imported before your module imports it, and the backend can only bet set once! Thus, to change the backend reliably, you must do so in your django settings file by importing matplotlib:

settings.py:

import matplotlib
matplotlib.use('Agg')

Upvotes: 4

Donal Fellows
Donal Fellows

Reputation: 137557

The error message itself tells you what is wrong: the main loop of the GUI (used for handling lots of events from the OS; GUIs are complex that way) has to be in the main thread of the program. Internally, this is because of the way it manages thread-specific resources, but the programming API you use is single threaded.

You can do any non-GUI processing in other threads if you wish (e.g., getting the data into the right format) but the UI calls need to be from the main thread only.

Upvotes: 0

Related Questions