3

I'm trying to use the 'ginput' to measure distance in a matplotlib figure by allowing the user to mouse click the locations. I am able to do this independently in the matplotlib figure, but I'm having problems when I tried to set the figure onto a matplotlib canvas and then embed it into PyQt4 widget. Below is my code, most of which were taken from the matplotlib examples. My solution will be to click a set of locations, and pass the (x,y) coordinates to the 'dist_calc' function to get the distance.

import sys
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import random
import numpy as np

class Window(QtGui.QWidget):
def __init__(self, parent=None):
    super(Window, self).__init__(parent)
    self.fig = Figure((6.5, 5.0), tight_layout=True)
    self.ax = self.fig.add_subplot(111)
    self.canvas = FigureCanvas(self.fig)
    self.toolbar = NavigationToolbar(self.canvas, self)

    self.button = QtGui.QPushButton('Plot')
    self.button.clicked.connect(self.plot)

    self.ndist = QtGui.QPushButton('Measure')
    self.ndist.clicked.connect(self.draw_line)

    self.toolbar.addWidget(self.button)
    self.toolbar.addWidget(self.ndist)
    self.fig.tight_layout()
    layout = QtGui.QVBoxLayout()
    layout.addWidget(self.toolbar)
    layout.addWidget(self.canvas)
    self.setLayout(layout)

def plot(self):
    data = [random.random() for i in range(20)]
    self.ax.hold(False)                          
    self.ax.plot(data, '*-')                     
    self.canvas.draw()                      

def draw_line(self):
    self.xy = plt.ginput(0)
    x = [p[0] for p in self.xy]
    y = [p[1] for p in self.xy]
    self.ax.plot(x,y)
    self.ax.figure.canvas.draw()
    self.get_dist(x, y)

def get_dist(self, xpts, ypts):
    npts = len(xpts)
    distArr = []
    for i in range(npts-1):
        apt = [xpts[i], ypts[i]]
        bpt = [xpts[i+1], ypts[i+1]]
        dist =self.calc_dist(apt,bpt)
        distArr.append(dist)

    tdist = np.sum(distArr)
    print(tdist)

def calc_dist(self,apt, bpt):
    apt = np.asarray(apt)
    dist = np.sum((apt - bpt)**2)
    dist = np.sqrt(dist)
    return dist

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    main = Window()
    main.show()
    sys.exit(app.exec_())

1 Answers1

0

According to this comment by one of the lead Matplotlib developers, you must not import pyplot when you're embedding Matplotlib in Qt. Pyplot sets up its own gui, mainloop and canvas, which interfere with the Qt event loop.

Changing the line self.xy = plt.ginput(0) into self.xy = self.fig.ginput(0) did not help but gave an insightful error:

AttributeError: 'FigureCanvasQTAgg' object has no attribute 'manager'
Figure.show works only for figures managed by pyplot, normally created by pyplot.figure().

In short, I don't think this is possible. ginput is a blocking function and seems only to be implemented for a Matplotlib event loop. I'm afraid that you will have to build the functionality you want using Matplotlib mouse events, which do work when embedding in PyQt. Just be sure not to use pyplot!

Edit: I just remembered, perhaps the LassoSelector is what you need.

Community
  • 1
  • 1
titusjan
  • 5,376
  • 2
  • 24
  • 43