Matplotlib не может отображать несколько контурных графиков на Django

Всякий раз, когда (по крайней мере) 2 человека пытаются создать контурный график в моем приложении, по крайней мере, один из них получит случайную ошибку в зависимости от того, насколько далеко удался первый человек. («Неизвестный элемент o», «ContourSet должен быть в текущих топорах "- это всего лишь две возможности)

Ниже приведен вырезанный тест, который может привести к ошибке, если вы попытаетесь загрузить эту страницу на 2 или более вкладках сразу, первая будет отображаться правильно, а вторая приведет к ошибке. (Самый простой способ, которым я нашел это, – нажать кнопку обновления страницы в хроме с помощью средней кнопки мыши пару раз)

views.py

def home(request): return render(request, 'home.html', {'chart': _test_chart()}) def _test_chart(): import base64 import cStringIO import matplotlib matplotlib.use('agg') from matplotlib.mlab import bivariate_normal import matplotlib.pyplot as plt import numpy as np from numpy.core.multiarray import arange delta = 0.5 x = arange(-3.0, 4.001, delta) y = arange(-4.0, 3.001, delta) X, Y = np.meshgrid(x, y) Z1 = bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0) Z2 = bivariate_normal(X, Y, 1.5, 0.5, 1, 1) Z = (Z1 - Z2) * 10 fig = plt.figure(figsize=(10, 5)) plt.contour(X, Y, Z, 10, colors='k') jpg_image_buffer = cStringIO.StringIO() fig.savefig(jpg_image_buffer) array = base64.b64encode(jpg_image_buffer.getvalue()) jpg_image_buffer.close() return array 

home.html (достаточно одной строки)

 <img src="data:image/png;base64,{{ chart }}" /> 

Я попытался использовать mpld3 вместо этого для обработки генерации изображения, и это по-прежнему вызывает разные ошибки, поэтому я знаю, что это определенно не спасение фигуры, а больше ее поколение. Я также пытался использовать ThreadPool и Threading безрезультатно, из того, что я могу сказать, похоже, что создание контура в matplotlib не может поддерживать несколько экземпляров, которые никогда не будут работать на веб-сайте …

Мое единственное ясное решение, о котором я сейчас думаю, это заменить matplotlib на что-то еще, чего я действительно не хочу делать.

Есть ли способ генерировать контурные графики с matplotlib, которые будут работать для меня?

Во-первых, позвольте мне начать с того, что это намного проще воспроизвести, вызвав _test_chart в пару потоков

 from threading import Thread for i in xrange(2): Thread(target=_test_chart).start() 

Выполняя вышеизложенное, каждый будет работать по желанию, а второй – сбой.


Простая причина этого заключается в том, что модуль pyplot не предназначен для многопоточности, и поэтому две диаграммы смешивают свои данные по мере их попытки.

Это лучше объяснить mdboom

… pyplot используется для удобного построения графика в командной строке и поддерживает глобальное состояние. Например, когда вы скажете plt.figure (), он добавляет фигуру в глобальный список, а затем устанавливает указатель «Текущая цифра» на последний созданный рисунок. Затем последующие команды построения автоматически записываются на эту цифру. Очевидно, что это не потокобезопасное …

Есть два способа исправить эту проблему,

  1. Заставить эти диаграммы рисовать в разных процессах.
 for i in xrange(2): pool = Pool(processes=1) pool.apply(_test_chart) 

В то время как это будет работать, вы обнаружите, что заметное снижение производительности, поскольку часто требуется столько же времени, чтобы создать процесс, как он будет генерировать диаграмму (что я не считал приемлемым!)

  1. Реальное решение заключается в использовании интерфейсных модулей OO Matplotlib, которые затем позволят вам работать с правильными объектами – по сути, это работает вплоть до работы с подзаголовками, а не с графиками. Для данного примера в вопросе это будет выглядеть следующим образом:
 def _test_chart2(): delta = 0.5 x = arange(-3.0, 4.001, delta) y = arange(-4.0, 3.001, delta) X, Y = np.meshgrid(x, y) Z1 = bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0) Z2 = bivariate_normal(X, Y, 1.5, 0.5, 1, 1) Z = (Z1 - Z2) * 10 fig = figure(figsize=(10, 5)) ax1 = fig.add_subplot(111) extents = [x.min(), x.max(), y.min(), y.max()] im = ax1.imshow(Z, interpolation='spline36', extent=extents, origin='lower', aspect='auto', cmap=cm.jet) ax1.contour(X, Y, Z, 10, colors='k') jpg_image_buffer = cStringIO.StringIO() fig.savefig(jpg_image_buffer) array = base64.b64encode(jpg_image_buffer.getvalue()) jpg_image_buffer.close() return array