Matplotlib no puede representar múltiples gráficos de contorno en Django

Siempre que (al menos) 2 personas intenten generar un trazado de contorno en mi aplicación, al menos una de ellas recibirá un error aleatorio según la distancia que haya logrado dibujar la primera persona … (“elemento desconocido o”, “ContourSet debe estar en los ejes actuales “son solo dos de las posibilidades)

La siguiente es una prueba de reducción que puede producir el error. Si intenta cargar esta página en 2 o más tabs a la vez, la primera se procesará correctamente mientras que la segunda producirá un error. (La forma más fácil que encontré para hacer esto fue hacer clic un par de veces en el botón de actualización de la página en Chrome)

vistas.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 (solo esta línea es suficiente)

  

He intentado usar mpld3 en lugar de manejar la generación de la imagen y esto todavía produce diferentes errores, así que sé que definitivamente no es el ahorro de la figura sino más su generación. También he intentado usar ThreadPool y Threading sin éxito, por lo que puedo decir, parece que crear un gráfico de contorno en matplotlib no puede admitir varias instancias que nunca funcionarán para un sitio web …

Mi única solución clara en la que puedo pensar ahora es reemplazar matplotlib con otra cosa que realmente no quiero hacer.

¿Hay alguna forma de generar gráficos de contorno con matplotlib que funcione para mí?

Primero, permítanme comenzar diciendo que esto es mucho más fácil de reproducir llamando a _test_chart en un par de hilos.

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

Al hacer lo anterior, uno funcionará como se desea, mientras que el segundo se bloqueará.


La razón simple de esto es que el módulo pyplot no está diseñado para multiproceso y, por lo tanto, los dos gráficos están mezclando sus datos cuando intentan dibujar.

Esto puede explicarse mejor por mdboom

… pyplot se utiliza para trazar cómodamente en la línea de comandos y se mantiene en todo el estado global. Por ejemplo, cuando dice plt.figure (), agrega la figura a una lista global y luego establece el puntero de “figura actual” a la figura creada más recientemente. Luego, los comandos de trazado subsiguientes escriben automáticamente en esa figura. Obviamente, eso no es seguro para hilos …

Hay dos formas de solucionar este problema,

  1. Forzar estos gráficos para que sean dibujados en diferentes procesos.
 for i in xrange(2): pool = Pool(processes=1) pool.apply(_test_chart) 

Si bien esto funcionará, verás que hay una disminución notable en el rendimiento, ya que a menudo demorará tanto en crear el proceso como en generar el gráfico (¡lo cual no me pareció aceptable!)

  1. La solución real es utilizar los módulos de interfaz OO de Matplotlib, que le permitirán trabajar con los objetos correctos; esencialmente, esto funciona hasta trabajar con subplots en lugar de plots. Para el ejemplo dado en la pregunta, esto se vería como el siguiente
 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