如何在pyqt窗口中重绘matplotlib图像时保持缩放/平移状态?

我正在编写一个用于图像傅里叶滤波的QT应用程序 . 对于过滤步骤I:

  • 在matplotlib画布中显示FFT,该画布链接到其工具栏

  • 使用工具栏可放大图像的有趣部分

  • 通过捕获鼠标和键盘事件来定义蒙版

  • 显示屏蔽的FFT ...

但是每次重绘画布时我都会松开缩放/平移状态 . 所以我需要为每个动作重复缩放和平移(定义矩形的角或选择一个像素 . )有没有办法存储工具栏的缩放/平移状态并在重绘时应用它?

顺便提一下,第二个问题:当使用QMainWindow时缩放平移期间显示“颤抖”(轻微的尺寸变化和移位)(这实际上并没有帮助精确地做到这一点)有没有办法避免颤抖? (当使用QWidget时,没有颤抖 . )

这是代码的简短版本和要加载here的图像 . 加载图像后,请勾选两个复选框(至少第二个) . 然后通过首先单击图像(给它焦点)定义一个蒙版,然后在图像上的一个点上移动鼠标并按下键'h'(定义蒙版的左上角)然后移动鼠标并按下键'n'(定义掩码的右下角),'b'添加单个像素 .

在此先感谢任何帮助和评论 .

# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget,
              QGridLayout, QHBoxLayout, QButtonGroup, QRadioButton, 
              QPushButton, QCheckBox, QLabel, QFileDialog, QLineEdit)
from PyQt5.QtCore import Qt # for the focus from keyboeard

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure # better do not use pyplot (but it's possible)
import matplotlib.pyplot as plt # for imread

import numpy as np

class mainWindow(QWidget):
    def __init__(self, parent=None):
        super(mainWindow, self).__init__(parent)

        ############      Define window size, position and title.
        self.setGeometry(0, 50, 900, 500)
        self.title = 'FFT test v1'
        self.setWindowTitle(self.title)

        ############      defining the widgets
        ### For plotting the images
        self.figureFFT = Figure()
        self.canvasFFT = FigureCanvas(self.figureFFT)

        # Connect a function that gets the key that is pressed
        self.canvasFFT.mpl_connect('key_press_event',self.GetKey)
        # Configure the canvas widget to process keyboard events
        self.canvasFFT.setFocusPolicy(Qt.StrongFocus) # listen to keyboard too!

        self.toolbarFFT = NavigationToolbar(self.canvasFFT, self)

        ### For masking
        self.ckMaskActivateIt = QCheckBox('Use mask')
        self.ckMaskActivateIt.clicked.connect(self.Mask_changed)

        rdBtLayoutMsk = QHBoxLayout()  # layout for the radio button widget
        self.rdBtwidgetMsk = QWidget(self)  # radio button widget
        self.rdBtwidgetMsk.setLayout(rdBtLayoutMsk)

        self.rdbtgroupMsk = QButtonGroup(self.rdBtwidgetMsk) 
        rdbtAdd=QRadioButton("Add pixels")
        self.rdbtgroupMsk.addButton(rdbtAdd,0) # 0 is the id in the group
        rdbtRemove=QRadioButton("Remove pixels")
        self.rdbtgroupMsk.addButton(rdbtRemove,1)
        rdbtAdd.setChecked(True)   # check one button on creation
        rdBtLayoutMsk.addWidget(rdbtAdd)
        rdBtLayoutMsk.addWidget(rdbtRemove)
        # self.rdbtgroupMsk.buttonClicked[int].connect(self.rdbtMskTog)

        ### for using saturation limits in the plot
        self.ckSatDisplayFFT = QCheckBox('Define display z-limits')
        self.ckSatDisplayFFT.clicked.connect(self.DisplayFFT)
        self.LbVminFFT = QLabel('Vmin = ')  # label (passif)
        self.LbVminFFT.setAlignment(Qt.AlignRight)
        self.EdVminFFT = QLineEdit('0')  # edit
        self.LbVmaxFFT = QLabel('Vmax = ')  # label (passif)
        self.LbVmaxFFT.setAlignment(Qt.AlignRight)
        self.EdVmaxFFT = QLineEdit('5000')  # edit        

        # for loading a grayscale image
        self.BtLoadFile1 = QPushButton('1. Load file')
        self.BtLoadFile1.clicked.connect(self.Load_File1)

        ############  setting the layout of the 2nd tab
        grid2 = QGridLayout(self)
        grid2.setSpacing(5) #defines the spacing between widgets

        # (object, row, col, rowSpan, colSpan)
        grid2.addWidget(self.canvasFFT, 0, 0, 12, 6)
        grid2.addWidget(self.toolbarFFT, 13, 0, 1, 6)

        grid2.addWidget(self.BtLoadFile1, 0,7) 

        grid2.addWidget(self.ckMaskActivateIt,3,7)
        grid2.addWidget(self.rdBtwidgetMsk, 3, 8, 1, 3)

        # for using saturation limits in the plot
        grid2.addWidget(self.ckSatDisplayFFT,1,7)
        grid2.addWidget(self.LbVminFFT,2,7)  # label (passif)
        grid2.addWidget(self.EdVminFFT,2,8)  # edit
        grid2.addWidget(self.LbVmaxFFT,2,9)  # label (passif)
        grid2.addWidget(self.EdVmaxFFT,2,10)  # edit

        self.setLayout(grid2)
        grid2.setRowStretch(2, 1)   
        self.show()


    def Load_File1(self):
        global fftOri
        global MaskList

        '''load the image file and show it in the left pane'''
        # choose the image with a dialog
        fname = QFileDialog.getOpenFileName(
                self, 
                'Open file',
                '',
                "All files (*.*) ;; image files (*.tif *.tiff *.png)")

        # load the image
        if fname[0]: # a file was chosen
            fftOri = plt.imread(fname[0])

        ### Display the image
        self.DisplayFFT()
        # initialze MaskList
        MaskList = []


    def DisplayFFT(self):
        # fix z display range
        Vmi = fftOri.min()
        Vma = fftOri.max()
        if self.ckSatDisplayFFT.isChecked():
            Vmi = float(self.EdVminFFT.text())
            Vma = float(self.EdVmaxFFT.text())

        # Clear the figure from earlier uses
        self.figureFFT.clear()
        axFFT = self.figureFFT.add_subplot(111)
        axFFT.axis("off")
        cax = axFFT.imshow(fftOri, cmap='jet',vmin = Vmi, vmax = Vma)
        self.figureFFT.colorbar(cax, orientation='horizontal')                
        self.canvasFFT.draw()
        return


    def GetKey(self, event):
        # Manipulates the Mask 
        # h : define upper left corner of rectangle
        # n : define lower right corner
        # b : add/remove single pixel
        global MaskList
        global ulx # for keeping the memory between two calls
        global uly # upper left
        global lrx # lower right
        global lry

        print(event.key)

        if (self.ckMaskActivateIt.isChecked() 
                  and event.xdata != None and event.ydata != None) :
            self.showMask()

            Letter = str(event.key).upper()
            newPoints =[]
            if Letter == 'B':
                # attacher si le point n'existe pas encore dans la liste
                newPoints=[( int(np.round(event.xdata)) , 
                                 int(np.round(event.ydata)) )]
            elif Letter == 'H':
                # memorize first point
                ulx = int(np.round(event.xdata))
                uly = int(np.round(event.ydata))
                # reset second point
                lrx = -50
                lry = -50
            elif Letter == 'N':
                # memorize first point
                lrx = int(np.round(event.xdata))
                lry = int(np.round(event.ydata))

                if (ulx > -1) and (uly > -1) : # the rectagle gets finished
                    # Add rectangle points to newPoints
                    # should work with inverted points too

                    # flip points if necessary
                    if lrx < ulx: # right x should be larger than left x
                        buffer = ulx 
                        ulx = lrx
                        lrx = buffer
                    if lry < uly: # lower y should be larger than upper y
                        buffer = uly 
                        uly = lry
                        lry = buffer

                    # fill columnwise
                    for xCoo in range(ulx,lrx+1): # from left to right
                        # column loop
                        for yCoo in range(uly,lry+1): # from upper to lower
                            # line loop
                            newPoints.append((xCoo, yCoo))
                    # reset choices
                    lrx = -50
                    lry = -50
                    ulx = -50
                    uly = -50
            # end letter 'N'

            # add or remove newPoints to MaskList
            if self.rdbtgroupMsk.checkedId() == 0:
                # add really new points to MaskList
                MaskList = list(set( MaskList + newPoints )) # sorts at the same time
            elif self.rdbtgroupMsk.checkedId() == 1:
                # remove entries of newPoints that existed in MaskList
                MaskList = list(set(MaskList) - set(newPoints)) # sorts at the same time

            if Letter == 'B' or Letter == 'N' :
                # btID = self.rdbtgroup3.checkedId() 
                self.showMask()
            #print(len(MaskList))   
        # end checkbox activated and mouse inside during key-press


    def showMask(self):
        global MaskList

        # Make a copy of the displayed image
        MaskedImage = fftOri.copy()
        putVal = MaskedImage.min()

        for count in range(len(MaskList)):
            MaskedImage[MaskList[count][1],MaskList[count][0]] = putVal

        Vmi = MaskedImage.min()
        Vma = MaskedImage.max()
        if self.ckSatDisplayFFT.isChecked():
            Vmi = float(self.EdVminFFT.text())
            Vma = float(self.EdVmaxFFT.text())

        # Clear the figure from earlier uses
        self.figureFFT.clear()
        axFFT = self.figureFFT.add_subplot(111)
        axFFT.axis("off")
        cax = axFFT.imshow(MaskedImage, cmap='jet',vmin = Vmi, vmax = Vma)
        self.figureFFT.colorbar(cax, orientation='horizontal')
        self.canvasFFT.draw()


    def Mask_changed(self):
        global MaskList
        global ulx # for keeping the memory between two calls
        global uly # upper left
        global lrx # lower right
        global lry

        if self.ckMaskActivateIt.isChecked():
            self.showMask()
        else:
            ulx = -50 # for keeping the memory between two calls
            uly = -50 # upper left
            lrx = -50 # lower right
            lry = -50
            # btID = self.rdbtgroup3.checkedId() 
            self.rdbt3Tog(0)

####################################################
if __name__ == '__main__':  # for use in Spyder
    # app = QApplication(sys.argv) # std : create QApplication
    app = QApplication.instance() # checks if QApplication already exists
    if not app: # create QApplication if it doesnt exist
        app = QApplication(sys.argv)
    app.aboutToQuit.connect(app.deleteLater)

    main = mainWindow()
    main.show()

    # sys.exit(app.exec_()) # std : exits python when the app finishes
    app.exec_() #do not exit Ipython when the app finishes

回答(0)