我正在编写一个用于图像傅里叶滤波的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