Reputation: 183
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
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
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