El trazado en tiempo real con Matplotlib, PyQt y Threading termina con el locking de Python

He estado luchando con mi aplicación Python y no puedo encontrar ninguna respuesta.

Tengo una aplicación GUI de PyQT que usa el widget Matplotlib. La GUI comienza un nuevo hilo que maneja el trazado en el widget mpl. Me temo que corro ahora a una condición de carrera accediendo a los componentes de dibujo de matplotlib desde otro hilo que provoca un locking.

Esto es básicamente, como se ve mi código:

class Analyzer(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): self.timer = QTimer() super(Analyzer, self).__init__(parent) self.setupUi(self) self.background = self.mpl.canvas.copy_from_bbox(self.mpl.canvas.ax.bbox) self.plotQueue = Queue.Queue() self.plotterStarted = False self.plotter = Plotter(self.mpl, self.plotQueue) self.cam = Cam(self.plotQueue, self.textEdit) ... class Ui_MainWindow(object): def setupUi(self, MainWindow): ... self.mpl = MplWidget(self.centralWidget) ... class MplWidget(QtGui.QWidget): """Widget defined in Qt Designer""" def __init__(self, parent = None): QtGui.QWidget.__init__(self, parent) self.canvas = MplCanvas() ... class MplCanvas(FigureCanvas): """Class to represent the FigureCanvas widget""" def __init__(self): # setup Matplotlib Figure and Axis self.fig = Figure() self.ax = self.fig.add_subplot(111) # initialization of the canvas FigureCanvas.__init__(self, self.fig) FigureCanvas.updateGeometry(self) 

Y clase de plotter:

 class Plotter(): def __init__(self, mpl="", plotQueue=""): self.mpl = mpl self.background = self.mpl.canvas.copy_from_bbox(self.mpl.canvas.ax.bbox) self.plotQueue = plotQueue ... def start(self): threading.Thread(target=self.run).start() ''' Real time plotting ''' def run(self): while True: try: inputData = self.plotQueue.get(timeout=1) # Go through samples for samples in inputData: self.line, = self.mpl.canvas.ax.plot(x, y, animated=True, label='Jee') for sample in samples: x.append(sample['tick']) y.append(sample['linear']) self.line.set_data(x,y) self.mpl.canvas.ax.draw_artist(self.line) self.mpl.canvas.blit(self.mpl.canvas.ax.bbox) ... 

Entonces paso mpl y plotQueue al objeto de la clase Plotter. PlotQueue se rellena en la clase Cam que procesa los datos entrantes de hw externos. Plotter lee el plotQueue, lo procesa y llama al dibujo para mpl.

Pero, ¿es este un método seguro para acceder a mpl? Si no, ¿cómo debo hacerlo? Cualquier consejo sobre esto es apreciado.


Editar 1.

Agregué QTimer en el hilo principal para manejar el dibujo, como se sugiere en los comentarios. Después de un pequeño ajuste, lo conseguí trabajando bastante bien.

 class Analyzer(...): def __init__(self, parent=None): QObject.connect(self.timer, SIGNAL("timeout()"), self.periodicCall) def periodicCall(self): self.plotter.draw() def startButton(self): self.timer.start(10) 

Muchas gracias por los comentarios útiles.

Si matplotlib en su progtwig usa el backend QT (que asumo que lo es, ya que lo está incorporando en una aplicación Qt), entonces el dibujo se realizará en el hilo del que se llaman los comandos matplotlib. Esto va a ser un problema porque Qt requiere que todos los dibujos se realicen desde el hilo principal. Así que estoy bastante seguro de que no puedes arreglarlo simplemente. (Si estuviera usando GTK, podría usar el locking gtk para evitar que el proceso principal interactúe con la GUI mientras hacía cosas relacionadas con la GUI de su hilo, pero Qt se deshizo de su locking similar en v4 y superior).

Tienes pocas opciones:

  1. Intente y separe las partes de dibujo de matplotlib (¿quizás no sea posible?) Y haga que se ejecuten en el hilo principal enviando eventos con QApplication.postEvent()

  2. En lugar de usar un subproceso, solo use devoluciones de llamada en el subproceso principal (puede que se llame periódicamente usando un QTimer o cuando el progtwig esté inactivo). Este problema no afectará el rendimiento de su aplicación, ya que el GIL de Python evita el verdadero comportamiento de subprocesos múltiples.

  3. Utilice una biblioteca de trazado diferente. Eché un vistazo a PyQtGraph el otro día, y parece que va muy bien. Desde mi breve vistazo, creo que tiene la capacidad de manejar todo esto detrás de la escena para usted, utilizando un RemoteGraphicsView . Esto lanzaría un segundo proceso para realizar tareas de trazado intensivo de CPU, que soluciona el problema de Python GIL mencionado anteriormente. Echa un vistazo a los ejemplos que proporcionan si estás interesado