1

I have some noisy data which I'm trying to fit with a gaussian. The problem is that I have to do it manually. By that, I mean I have to move the point on the curve (see figure below). When I move the point I have to update the curve so the curve it self can move.

For example on this curve if I move the upper point it changes the mu of my gaussian and if I move the point in the middle it update the sigma parameter. On this example, I've plotted the two curve in a FigureCanvas of matplotlib that I've embedded in a QMainWindow.

I've seached and found no way to do that in a matplotlib figure embedded in a PyQt widget. So, I've changed and tried to use PyQtGraph with the ROI tools but it didn't work very well.

Do you have any idea how i can achieve this? Is there a simple python library to do that? Thanks

EDIT : Here is the code I've used to produce the image :

from PySide2 import QtCore, QtGui, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np


class PainterCanvas(FigureCanvas):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        FigureCanvas.__init__(self, fig)
        self.setParent(parent)
        self._instructions = []
        self.axes = self.figure.add_subplot(111)

    def paintEvent(self, event):
        super().paintEvent(event)
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
        width, height = self.get_width_height()
        for x, y, rx, ry, br_color in self._instructions:
            x_pixel, y_pixel_m = self.axes.transData.transform((x, y))
            # In matplotlib, 0,0 is the lower left corner,
            # whereas it's usually the upper right
            # for most image software, so we'll flip the y-coor
            y_pixel = height - y_pixel_m
            painter.setBrush(QtGui.QColor(br_color))
            painter.drawEllipse( QtCore.QPoint(x_pixel, y_pixel), rx, ry)

    def create_oval(self, x, y, radius_x=2, radius_y=2, brush_color="red"):
        self._instructions.append([x, y, radius_x, radius_y, brush_color])
        self.update()


class MyPaintWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        self.canvas = PainterCanvas()
        self.canvas.mpl_connect("button_press_event", self._on_left_click)
        x = np.arange(0, 10, 0.1)
        rand = [np.random.uniform(-0.1, 0.2) for _ in x]
        y0 = np.exp(- (x - 5) ** 2 / 2) + rand
        y1 = np.exp(- (x - 3) ** 2 / 0.5)
        self.canvas.axes.plot(x, y0)
        self.canvas.axes.plot(x, y1)

        layout_canvas = QtWidgets.QVBoxLayout(self)
        layout_canvas.addWidget(self.canvas)

        self.canvasMenu = QtWidgets.QMenu(self)
        self.canvasMenu.addAction("test")

        self.canvas.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.canvas.customContextMenuRequested.connect(self._on_left_click)

    def _on_left_click(self, event):
        self.canvas.create_oval(event.xdata, event.ydata, brush_color="green")


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = MyPaintWidget()
    w.show()
    sys.exit(app.exec_())

To Add point I've added them by clicking on the curve. I know that code won't work to do what I've asked but it was just to produce an image to explain my idea.

Yoann A.
  • 413
  • 4
  • 16
  • How did you manage to do this to begin with? – mkrieger1 Mar 19 '20 at 21:03
  • I've added the code I used to produce the image. But this code is just an example to produce an image to explain and illustrate my idea. – Yoann A. Mar 20 '20 at 10:30
  • I've finally obtained what I wanted using Qsliders to control parameters of my curves and I've used pyqtgraph to plot and refresh data very fast. – Yoann A. Apr 09 '20 at 09:05
  • You can add an answer explaining you solution so that this will help others in the future, too. – mkrieger1 Apr 09 '20 at 09:07

1 Answers1

1

As suggested by mkrieger1, I will briefly describe my solution :

  • First I've used PyQtGraph to plot my data to draw updates more efficiently than when I was using Matplotlib.

  • To control curves I've used QSliders (modified to support double) to control the parameters of each curve. When I move one slider it emits an event and in the function handling this event, I update my pyqtgraph plotwidget with the function setData.

I get my inspiration from to do my double cursor : Use float for QSlider and I've used the pyqtgraph documentation : http://www.pyqtgraph.org/documentation/graphicsItems/plotdataitem.html

There is too much code to put it there but the solution is on my GitHub in the spectrofit project: https://github.com/fulmen27/SpectroFit (It's UI to process and study stellar spectrums). The solution is in the spectrofit.tools.Interactive_Fit.py file.

Hope this can help!

Yoann A.
  • 413
  • 4
  • 16