I tried to create a 2D plot using QGraphicsItem, I was successful in doing that but when I drag the QGraphicsItem there is a delay and the view is distorted.
Searching for a solution, I came across this QGraphicsItem paint delay. I applied the mouseMoveEvent to my QGraphicsView but it did not resolve the problem.
Could someone tell me what is causing the problem and how can I fix it?
Here is my code:
from PyQt4 import QtGui, QtCore
import sys
import numpy as np
class MyGraphicsItem(QtGui.QGraphicsItem):
def __init__(self,dataX,dataY):
super(MyGraphicsItem,self).__init__()
self.Xval = dataX
self.Yval = dataY
self.Xvalmin = np.min(self.Xval)
self.Xvalmax = np.max(self.Xval)
self.Yvalmin = np.min(self.Yval)
self.Yvalmax = np.max(self.Yval)
self.rect = QtCore.QRectF(0,0,100,2)
self.points = []
self._picture = None
def paint(self, QPainter, QStyleOptionGraphicsItem, QWidget_widget=None):
if self._picture is None:
self._picture = QtGui.QPicture()
QPainter.begin(self._picture)
startPoint = QtCore.QPointF(0, 0)
cubicPath = QtGui.QPainterPath()
cubicPath.moveTo(startPoint)
for i in range(len(self.points) - 2):
points_ = self.points[i:i+3]
cubicPath.cubicTo(*points_)
QPainter.setPen(QtGui.QPen(QtCore.Qt.red))
QPainter.drawLine(0,10,100,10)
QPainter.drawLine(0,-10,0,10)
QPainter.setPen(QtGui.QPen(QtCore.Qt.black))
QPainter.drawPath(cubicPath)
QPainter.end()
else:
self._picture.play(QPainter)
def boundingRect(self):
return self.rect
class mygraphicsview(QtGui.QGraphicsView):
def __init__(self):
super(mygraphicsview,self).__init__()
def mouseMoveEvent(self, event):
QtGui.QGraphicsView.mouseMoveEvent(self,event)
if self.scene().selectedItems():
self.update()
class Mainwindow(QtGui.QMainWindow):
def __init__(self):
super(Mainwindow,self).__init__()
self.main_widget = QtGui.QWidget()
self.vl = QtGui.QVBoxLayout()
self.scene = QtGui.QGraphicsScene()
self.view = mygraphicsview()
self.Xval = np.linspace(0,100,1000)
self.Yval = np.sin(self.Xval)
self.painter = QtGui.QPainter()
self.style = QtGui.QStyleOptionGraphicsItem()
self.item = MyGraphicsItem(self.Xval, self.Yval)
self.item.paint(self.painter, self.style,self.main_widget)
self.item.setFlag(QtGui.QGraphicsItem.ItemIsMovable,True)
self.trans = QtGui.QTransform()
self.trans.scale(5,5)
self.item.setTransform(self.trans)
self.scene = QtGui.QGraphicsScene()
self.scene.addItem(self.item)
self.view.setScene(self.scene)
self.vl.addWidget(self.view)
self.main_widget.setLayout(self.vl)
self.setCentralWidget(self.main_widget)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Mainwindow()
window.show()
sys.exit(app.exec_())
I fixed the issue of dragging delay.
The reason for the occurrence of such a delay is due to the boundinRect() function. The boudingRect was too tight around the item designed.
Adding some marigin to the boundingRect(), solved the problem.
self.rect = QtCore.QRectF(-10,-10,120,25)
Related
Is there an attribute to position subwindows in qmdiarea? I’m trying to center subwindow in middle of mainwindow on startup (mdiarea)
I’m working on a mcve but haven’t finished it, wanted to see if anyone has tried doing this, and how they did it
Subwindows are randomly placed on startup when initialized
class App(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent=parent)
self.setupUi(self)
self.screenShape = QDesktopWidget().screenGeometry()
self.width = self.screenShape.width()
self.height = self.screenShape.height()
self.resize(self.width * .6, self.height * .6)
self.new = []
#calls GUI's in other modules
self.lw = Login()
self.vs = VS()
self.ms = MS()
self.hw = HomeWindow()
self.mw = MainWindow()
self.ga = GA()
self.sGUI = Settings()
# shows subwindow
self.CreateLogin()
self.CreateVS()
self.CreateMS()
self.CreateGA()
self.CreateSettings()
def CreateLogin(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.lw)
self.subwindow.setAttribute(Qt.WA_DeleteOnClose, True)
self.mdiArea.addSubWindow(self.subwindow)
self.subwindow.setMaximumSize(520, 300)
self.subwindow.setMinimumSize(520, 300)
self.lw.showNormal()
def CreateVS(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.vs)
self.mdiArea.addSubWindow(self.subwindow)
self.vs.showMinimized()
def CreateMS(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.ms)
self.mdiArea.addSubWindow(self.subwindow)
self.ms.showMinimized()
self.ms.tabWidget.setCurrentIndex(0)
def CreateGA(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.ga)
self.mdiArea.addSubWindow(self.subwindow)
self.ga.showMinimized()
self.subwindow.setMaximumSize(820, 650)
def CreateSettings(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.sGUI)
self.mdiArea.addSubWindow(self.subwindow)
self.sGUI.showMinimized()
def CreateWindow(self):
self.hw.pushButton.clicked.connect(self.vs.showNormal)
self.hw.pushButton_2.clicked.connect(self.Moduleprogram)
self.hw.pushButton_3.clicked.connect(self.ms.showNormal)
self.hw.pushButton_4.clicked.connect(self.ga.showNormal)
self.subwindow = QMdiSubWindow()
self.subwindow.setWindowFlags(Qt.CustomizeWindowHint | Qt.Tool)
self.subwindow.setWidget(self.hw)
self.subwindow.setMaximumSize(258, 264)
self.subwindow.move(self.newwidth*.35, self.newheight*.25)
self.mdiArea.addSubWindow(self.subwindow)
In Qt the geometry is only effective when the window is visible so if you want to center something it must be in the showEvent method. On the other hand to center the QMdiSubWindow you must first get the center of the viewport of the QMdiArea, and according to that modify the geometry of the QMdiSubWindow.
Because the code you provide is complicated to execute, I have created my own code
from PyQt5 import QtCore, QtGui, QtWidgets
import random
def create_widget():
widget = QtWidgets.QLabel(
str(random.randint(0, 100)), alignment=QtCore.Qt.AlignCenter
)
widget.setStyleSheet(
"""background-color: {};""".format(
QtGui.QColor(*random.sample(range(255), 3)).name()
)
)
widget.setMinimumSize(*random.sample(range(100, 300), 2))
return widget
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
add_button = QtWidgets.QPushButton(
"Add subwindow", clicked=self.add_subwindow
)
self._mdiarea = QtWidgets.QMdiArea()
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QVBoxLayout(central_widget)
lay.addWidget(add_button)
lay.addWidget(self._mdiarea)
self._is_first_time = True
for _ in range(4):
self.add_subwindow()
#QtCore.pyqtSlot()
def add_subwindow(self):
widget = create_widget()
subwindow = QtWidgets.QMdiSubWindow(self._mdiarea)
subwindow.setWidget(widget)
subwindow.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
subwindow.show()
self._mdiarea.addSubWindow(subwindow)
# self.center_subwindow(subwindow)
def showEvent(self, event):
if self.isVisible() and self._is_first_time:
for subwindow in self._mdiarea.subWindowList():
self.center_subwindow(subwindow)
self._is_first_time = False
def center_subwindow(self, subwindow):
center = self._mdiarea.viewport().rect().center()
geo = subwindow.geometry()
geo.moveCenter(center)
subwindow.setGeometry(geo)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Update:
If you want the subwindow to be centered then with the following code you have to create a property center to True:
def add_subwindow(self):
widget = create_widget()
subwindow = QtWidgets.QMdiSubWindow(self._mdiarea)
subwindow.setWidget(widget)
subwindow.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
subwindow.show()
subwindow.setProperty("center", True) # <----
self._mdiarea.addSubWindow(subwindow)
def showEvent(self, event):
if self.isVisible() and self._is_first_time:
for subwindow in self._mdiarea.subWindowList():
if subwindow.property("center"): # <---
self.center_subwindow(subwindow)
self._is_first_time = False
My QGraphicsView should show an image of a large resolution. The size should fit inside a resizable window. Currently, the image is viewed in a way that I want it to but only by providing some manually adjusted values to the initial view geometry. This doe not look neat. I also tried to refer to the solutions posted here: Graphics View and Pixmap Size
My current Window looks like this:
class ImageCheck(Ui_ImageCheck.Ui_MainWindow, QMainWindow):
def __init__(self, parent=None):
super(ImageCheck, self).__init__()
self.setupUi(self)
self.setWindowTitle("Image Analyzer")
self.crop_ratio_w = 1
self.crop_ratio_h = 1
self.path = None
self.scene = QGraphicsScene()
self.scene.clear()
self.image_item = QGraphicsPixmapItem()
# This is the approximate shift in coordinates of my initial view from the window
self.view.setGeometry(self.geometry().x()+ 10, self.geometry().y()+ 39,
self.geometry().width()- 55, self.geometry().height()- 110)
self.view.setAlignment(Qt.AlignCenter)
self.view.setFrameShape(QFrame.NoFrame)
def setImage(self, path):
self.path = path
self.crop_ratio_w = self.pixmap.width() / self.view.width()
self.crop_ratio_h = self.pixmap.height() / self.view.height()
pixmap = QPixmap(path)
smaller_pixmap = pixmap.scaled(self.view.width(), self.view.height(),
Qt.IgnoreAspectRatio, t.FastTransformation)
self.image_item.setPixmap(smaller_pixmap)
self.scene.addItem(self.image_item)
self.scene.setSceneRect(0, 0, self.view.width(), self.view.height())
self.view.setGeometry(0, 0, self.view.width(), self.view.height())
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.view.setScene(self.scene)
self.view.setSceneSize()
def resizeEvent(self, event):
self.view.setGeometry(self.geometry().x()+ 10, self.geometry().y()+ 39,
self.geometry().width()- 55, self.geometry().height()- 110)
self.setImage(self.path)
My manual override was probably not a good idea when I tried to determine distances between two points. Even the scaled distance gives me a slightly wrong value.
I can not use your code because there are many hidden things so I will propose the next solution that is to rescale the view based on the scene each time the window changes its size. I have also implemented a signal that transports the clicked information in the image based on the coordinates of the image.
from PyQt5 import QtCore, QtGui, QtWidgets
class ClickableGraphicsView(QtWidgets.QGraphicsView):
clicked = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, parent=None):
super(ClickableGraphicsView, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
self.pixmap_item = None
def setImage(self, path):
pixmap = QtGui.QPixmap(path)
self.pixmap_item = self.scene().addPixmap(pixmap)
self.pixmap_item.setShapeMode(
QtWidgets.QGraphicsPixmapItem.BoundingRectShape
)
def mousePressEvent(self, event):
if self.pixmap_item is not None:
if self.pixmap_item == self.itemAt(event.pos()):
sp = self.mapToScene(event.pos())
lp = self.pixmap_item.mapToItem(self.pixmap_item, sp)
p = lp.toPoint()
if self.pixmap_item.pixmap().rect().contains(p):
self.clicked.emit(p)
super(ClickableGraphicsView, self).mousePressEvent(event)
def resizeEvent(self, event):
self.fitInView(self.sceneRect(), QtCore.Qt.IgnoreAspectRatio)
super(ClickableGraphicsView, self).resizeEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowTitle("Image Analyzer")
view = ClickableGraphicsView()
view.clicked.connect(print)
view.setImage("image.jpg")
label = QtWidgets.QLabel("Distance")
display = QtWidgets.QLCDNumber()
buttonbox = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
)
widget = QtWidgets.QWidget()
self.setCentralWidget(widget)
lay = QtWidgets.QGridLayout(widget)
lay.addWidget(view, 0, 0, 1, 2)
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(label)
hlay.addWidget(display)
hlay.addStretch()
lay.addLayout(hlay, 1, 0)
lay.addWidget(buttonbox, 1, 1)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
What I'm trying to do is add splitter to a QGridLayout in order to resize the layout with the mouse. So for instance with this :
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.setFixedWidth(300)
self.setFixedHeight(100)
self.wid = QWidget()
self.setCentralWidget(self.wid)
self.grid = QGridLayout()
l_a = QLabel('A')
l_b = QLabel('B')
l_c = QLabel('C')
l_d = QLabel('D')
l_e = QLabel('E')
l_f = QLabel('F')
l_g = QLabel('G')
l_h = QLabel('H')
l_i = QLabel('I')
self.grid.addWidget(l_a, 0, 0)
self.grid.addWidget(l_b, 0, 1)
self.grid.addWidget(l_c, 0, 2)
self.grid.addWidget(l_d, 1, 0)
self.grid.addWidget(l_e, 1, 1)
self.grid.addWidget(l_f, 1, 2)
self.grid.addWidget(l_g, 2, 0)
self.grid.addWidget(l_h, 2, 1)
self.grid.addWidget(l_i, 2, 2)
self.wid.setLayout(self.grid)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
I get this:
What I would like is instead of the colored line, have the possibility to click and drag vertically (for green lines) and horizontally (for red lines) the grid borders.
I tried something with QSplitter directly, but I end up with:
The Horizontal splits are okay, but the vertical ones are not aligned any more:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.setFixedWidth(300)
self.setFixedHeight(100)
self.wid = QWidget()
self.setCentralWidget(self.wid)
# self.grid = QGridLayout()
self.globallayout = QVBoxLayout()
self.split_V = QSplitter(Qt.Vertical)
l_a = QLabel('A')
l_b = QLabel('B')
l_c = QLabel('C')
l_d = QLabel('D')
l_e = QLabel('E')
l_f = QLabel('F')
l_g = QLabel('G')
l_h = QLabel('H')
l_i = QLabel('I')
split_H = QSplitter(Qt.Horizontal)
split_H.addWidget(l_a)
split_H.addWidget(l_b)
split_H.addWidget(l_c)
self.split_V.addWidget(split_H)
split_H = QSplitter(Qt.Horizontal)
split_H.addWidget(l_d)
split_H.addWidget(l_e)
split_H.addWidget(l_f)
self.split_V.addWidget(split_H)
split_H = QSplitter(Qt.Horizontal)
split_H.addWidget(l_g)
split_H.addWidget(l_h)
split_H.addWidget(l_i)
self.split_V.addWidget(split_H)
self.globallayout.addWidget(self.split_V)
self.wid.setLayout(self.globallayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
Update
I think I almost found a solution where a function is used so that whenever the vertical splits are changed, it re-aligns them:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.setFixedWidth(300)
self.setFixedHeight(100)
self.wid = QWidget()
self.setCentralWidget(self.wid)
# self.grid = QGridLayout()
self.globallayout = QVBoxLayout()
self.split_V = QSplitter(Qt.Vertical)
l_a = QLabel('A')
l_b = QLabel('B')
l_c = QLabel('C')
l_d = QLabel('D')
l_e = QLabel('E')
l_f = QLabel('F')
l_g = QLabel('G')
l_h = QLabel('H')
l_i = QLabel('I')
self.split_H1 = QSplitter(Qt.Horizontal)
self.split_H1.addWidget(l_a)
self.split_H1.addWidget(l_b)
self.split_H1.addWidget(l_c)
self.split_V.addWidget(self.split_H1)
self.split_H2 = QSplitter(Qt.Horizontal)
self.split_H2.addWidget(l_d)
self.split_H2.addWidget(l_e)
self.split_H2.addWidget(l_f)
self.split_V.addWidget(self.split_H2)
self.split_H3 = QSplitter(Qt.Horizontal)
self.split_H3.addWidget(l_g)
self.split_H3.addWidget(l_h)
self.split_H3.addWidget(l_i)
self.split_V.addWidget(self.split_H3)
self.globallayout.addWidget(self.split_V)
self.wid.setLayout(self.globallayout)
self.split_H1.splitterMoved.connect(self.moveSplitter)
self.split_H2.splitterMoved.connect(self.moveSplitter)
self.split_H3.splitterMoved.connect(self.moveSplitter)
# self.split_H1.splitterMoved
# self.moveSplitter(0,self.split_H1.at )
def moveSplitter( self, index, pos ):
# splt = self._spltA if self.sender() == self._spltB else self._spltB
self.split_H1.blockSignals(True)
self.split_H2.blockSignals(True)
self.split_H3.blockSignals(True)
self.split_H1.moveSplitter(index, pos)
self.split_H2.moveSplitter(index, pos)
self.split_H3.moveSplitter(index, pos)
self.split_H1.blockSignals(False)
self.split_H2.blockSignals(False)
self.split_H3.blockSignals(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
However, I still have an issue at the begining - the alignment is not correct :
I don't know How call the function moveSplitter in the __init__
It seems that directly calling moveSplitter (which is a protected method) may be problematic. Using Qt-5.10.1 with PyQt-5.10.1 on Linux, I found that it can often result in a core dump when called during __init__. There is probably a good reason why Qt provides setSizes as a public method for changing the position of the splitters, so it may be wise to prefer it over moveSplitter.
With that in mind, I arrived at the following implementation:
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
...
self.split_H1.splitterMoved.connect(self.moveSplitter)
self.split_H2.splitterMoved.connect(self.moveSplitter)
self.split_H3.splitterMoved.connect(self.moveSplitter)
QTimer.singleShot(0, lambda: self.split_H1.splitterMoved.emit(0, 0))
def moveSplitter(self, index, pos):
sizes = self.sender().sizes()
for index in range(self.split_V.count()):
self.split_V.widget(index).setSizes(sizes)
The single-shot timer is needed because on some platforms the geometry of the window may not be fully initialized before it is shown on screen. And note that setSizes does not trigger splitterMoved, so there is no need to block signals when using it.
Following up on this Question and the solution provided by tcaswell I tried to adopt the code for imshow() to generate a non-freezing window with a slider for image processing, such as gaussian blur filter. (I plotted two images on top of each other, because I want to display a partly transparent mask at a later stage.)
I hope some of you might find this useful, although I could still use some help.
EDIT: You can find the current state in section THIRD CODE below. I am keeping the old versions for other users who would like to dig into the details.
I derived two different working codes, each having some (minor) issues and I would really appreciate some advice.
First code:
As long as the QSlider is dragged around the thread is running. However, you can not simply click the slider bar. Any suggestion?
The image axes are not properly plotted, i.e. they disappear again. Why?
The plot updating is not what I would call fast, although it is faster than calling imshow() everytime. How can I speed this up even more?
The window is still frozen for the very short time during which the plot is updated. (The window dragging while the loop is running is stuttering.) Can this be improved?
To not run into QThread: Destroyed while thread is still running I have put a time.sleep(1) in closeEvent(). I know this is really bad, but how can I avoid it without a new flag?
import time, sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from scipy import misc
from scipy import ndimage
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QtGui.QMainWindow):
get_data = QtCore.pyqtSignal()
close_request = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.thread = QtCore.QThread(parent=self)
self.worker = Worker(parent=None)
self.worker.moveToThread(self.thread)
self.create_main_frame()
self.close_request.connect(self.thread.quit)
self.startButton.clicked.connect(self.start_calculation)
self.stopButton.clicked.connect(self.stop_calculation)
self.worker.started.connect(self.thread.start)
self.worker.new_pixel_array.connect(self.update_figure)
self.slider.sliderPressed.connect(self.start_calculation)
self.slider.valueChanged.connect(self.slider_value_changed)
self.slider.sliderReleased.connect(self.stop_calculation)
self.get_data.connect(self.worker.get_data)
self.thread.start()
def create_main_frame(self):
self.main_frame = QtGui.QWidget()
self.dpi = 100
self.width = 5
self.height = 5
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,512))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.im1 = self.axes.imshow(misc.ascent(), cmap='bone', interpolation='lanczos', extent=[0,512,0,512], aspect=(1), animated=True)
self.im2 = self.axes.imshow(misc.lena(), cmap='afmhot', interpolation='lanczos', extent=[0,265,0,256], aspect=(1), animated=True)
self.startButton = QtGui.QPushButton(self.tr("Keep Calculating"))
self.stopButton = QtGui.QPushButton(self.tr("Stop Calculation"))
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
self.slider.setRange(0, 100)
self.slider.setValue(50)
self.slider.setTracking(True)
self.slider.setTickPosition(QtGui.QSlider.TicksBothSides)
layout = QtGui.QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.slider, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 3, 0)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("Gaussian Filter - Slider not clickable"))
def slider_value_changed(self):
#self.worker.blockSignals(False)
self.worker.slider = self.slider.value()
def start_calculation(self):
self.worker.exiting = False
self.worker.slider = self.slider.value()
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.get_data.emit()
def stop_calculation(self):
self.worker.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, im1_data,im2_data):
self.canvas.restore_region(self.background)
self.im1.set_array(im1_data)
self.im2.set_array(im2_data)
self.axes.draw_artist(self.im1)
self.axes.draw_artist(self.im2)
self.canvas.blit(self.axes.bbox)
def cleanup_UI(self):
self.background = None
self.canvas.draw()
def closeEvent(self, event):
self.stop_calculation()
self.close_request.emit()
time.sleep(1)
## ugly workaround to prevent window from closing before thread is closed. (calculation takes time) How can this be avoided without additional flag?
event.accept()
class Worker(QtCore.QObject):
new_pixel_array = QtCore.pyqtSignal(np.ndarray,np.ndarray)
started = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtCore.QObject.__init__(self, parent)
self.exiting = True
self.slider = 0
#QtCore.pyqtSlot()
def get_data(self):
while self.exiting == False:
self.started.emit()
im1_data = self.gauss(misc.ascent(),self.slider)
im2_data = self.gauss(misc.lena(),self.slider)
self.new_pixel_array.emit(im1_data, im2_data)
print 'Slider Value: ', self.slider
def gauss(self,im,radius):
gaussed = ndimage.gaussian_filter(im, radius)
return gaussed
def main():
app = QtGui.QApplication(sys.argv)
form = ApplicationWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
Second code:
You can now also click the slider bar.
Background (axes) reconstruction is still not working. Of course calling self.canvas.draw() in cleanup_UI() fixes this somehow.
When the slider bar is clicked, the calculation is performed once, but if the slider is dragged around and released, the calculation is performed twice at the same value. Why? I tried to catch this with blockSignals but then sometimes (when the slider is dragged around really fast and released) the second image in the plot is not updated properly. You recognize it by two different amounts of blur.
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from scipy import misc
from scipy import ndimage
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QtGui.QMainWindow):
get_data = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.thread = QtCore.QThread(parent=self)
self.worker = Worker(parent=None)
self.worker.moveToThread(self.thread)
self.create_main_frame()
self.startButton.clicked.connect(self.start_calculation)
self.worker.new_pixel_array.connect(self.update_figure)
self.worker.done.connect(self.stop_calculation)
self.slider.sliderPressed.connect(self.start_calculation)
self.slider.valueChanged.connect(self.slider_value_changed)
self.slider.actionTriggered.connect(self.start_calculation)
self.get_data.connect(self.worker.get_data)
self.thread.start()
def create_main_frame(self):
self.main_frame = QtGui.QWidget()
self.dpi = 100
self.width = 5
self.height = 5
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,512))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.im1 = self.axes.imshow(misc.ascent(), cmap='bone', interpolation='lanczos', extent=[0,512,0,512], aspect=(1), animated=True)
self.im2 = self.axes.imshow(misc.lena(), cmap='afmhot', interpolation='lanczos', extent=[0,265,0,256], aspect=(1), animated=True)
self.startButton = QtGui.QPushButton(self.tr("Do a Calculation"))
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
self.slider.setRange(0, 100)
self.slider.setValue(50)
self.slider.setTracking(True)
self.slider.setTickPosition(QtGui.QSlider.TicksBothSides)
layout = QtGui.QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.slider, 1, 0)
layout.addWidget(self.startButton, 2, 0)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("Gaussian Filter"))
def slider_value_changed(self):
#self.worker.blockSignals(False)
self.worker.slider = self.slider.value()
def start_calculation(self):
self.slider_value_changed()
self.worker.exiting = False
self.startButton.setEnabled(False)
self.get_data.emit()
def stop_calculation(self):
self.worker.exiting = True
self.startButton.setEnabled(True)
self.cleanup_UI()
def update_figure(self, im1_data,im2_data):
self.im1.set_array(im1_data)
self.im2.set_array(im2_data)
self.axes.draw_artist(self.im1)
self.axes.draw_artist(self.im2)
self.canvas.blit(self.axes.bbox)
def cleanup_UI(self):
self.canvas.restore_region(self.background)
#self.canvas.draw()
#self.worker.blockSignals(True)
class Worker(QtCore.QObject):
new_pixel_array = QtCore.pyqtSignal(np.ndarray,np.ndarray)
done = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtCore.QObject.__init__(self, parent)
self.exiting = True
self.slider = 0
#QtCore.pyqtSlot()
def get_data(self):
if self.exiting == False:
im1_data = self.gauss(misc.ascent(),self.slider)
im2_data = self.gauss(misc.lena(),self.slider)
self.new_pixel_array.emit(im1_data,im2_data)
print 'Calculation performed, Slider Value: ', self.slider
self.done.emit()
else: None
def gauss(self,im,radius):
gaussed = ndimage.gaussian_filter(im, radius)
return gaussed
def main():
app = QtGui.QApplication(sys.argv)
form = ApplicationWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
EDIT: Third Code (Major issues resolved and update rate limited)
The slider is now only starting a new thread when the calculation of the previous one has finished. That was acheived by disconnect.
The Plotting is still slow, (the blur function too).
restore_region still seems to have no effect at all.
I have now put the calculation of both images into threads and return the result via a Queue(). If you see some possibility for improvements, plese let me know.
I once tried to switch to the multiprocessing module and put the calculation inside a Pool(), but it throws me an Can't pickle... error. As I am totally new to multiprocessing, I would very much like to learn more about it.
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from scipy import misc
from scipy import ndimage
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from threading import Thread
from Queue import Queue
class ApplicationWindow(QtGui.QMainWindow):
get_data = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.thread = QtCore.QThread(parent=self)
self.worker = Worker(parent=None)
self.worker.moveToThread(self.thread)
self.create_main_frame()
self.startButton.clicked.connect(self.start_calculation)
self.stopButton.clicked.connect(self.stop_calculation)
self.worker.started.connect(self.thread.start)
self.worker.new_pixel_array.connect(self.update_figure)
self.slider.actionTriggered.connect(self.start_calculation)
self.slider.valueChanged.connect(self.slider_value_changed)
self.worker.done.connect(self.stop_calculation)
self.get_data.connect(self.worker.get_data)
self.thread.start()
def create_main_frame(self):
self.main_frame = QtGui.QWidget()
self.dpi = 100
self.width = 5
self.height = 5
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,512))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.background = None
self.canvas.draw()
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.im1 = self.axes.imshow(misc.ascent(), cmap='bone', interpolation='lanczos', extent=[0,512,0,512], aspect=(1), animated=True)
self.im2 = self.axes.imshow(misc.lena(), cmap='afmhot', interpolation='lanczos', extent=[0,265,0,256], aspect=(1), animated=True)
self.startButton = QtGui.QPushButton(self.tr("Start Calculation"))
self.stopButton = QtGui.QPushButton(self.tr("Stop Calculation"))
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
self.slider.setRange(0, 100)
self.slider.setValue(50)
self.slider.setTracking(True)
self.slider.setTickPosition(QtGui.QSlider.TicksBothSides)
layout = QtGui.QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.slider, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 3, 0)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("Gaussian Filter"))
def slider_value_changed(self):
self.worker.slider = self.slider.value()
def start_calculation(self):
if self.worker.exiting:
self.slider.actionTriggered.disconnect(self.start_calculation)
self.worker.slider = self.slider.value()
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.get_data.emit()
self.worker.exiting = False
def stop_calculation(self):
if not self.worker.exiting:
self.slider.actionTriggered.connect(self.start_calculation)
self.worker.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, im1_data,im2_data):
#self.canvas.restore_region(self.background)
self.im1.set_array(im1_data)
self.im2.set_array(im2_data)
self.axes.draw_artist(self.im1)
self.axes.draw_artist(self.im2)
self.canvas.blit(self.axes.bbox)
def cleanup_UI(self):
self.background = None
self.canvas.draw()
class Worker(QtCore.QObject):
new_pixel_array = QtCore.pyqtSignal(np.ndarray,np.ndarray)
started = QtCore.pyqtSignal()
done = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtCore.QObject.__init__(self, parent)
self.exiting = True
self.slider = 0
#QtCore.pyqtSlot()
def get_data(self):
while self.exiting == False:
self.started.emit()
queue1 = Queue()
queue2 = Queue()
im1T = Thread(target=self.gauss, args=(misc.ascent(),queue1))
im2T = Thread(target=self.gauss, args=(misc.lena(),queue2))
slider_val = self.slider
im1T.start()
im2T.start()
im1T.join()
im2T.join()
im1_data = queue1.get()
im2_data = queue2.get()
self.new_pixel_array.emit(im1_data, im2_data)
if slider_val == self.slider:
self.done.emit()
print 'Slider Value: ', self.slider
break
def gauss(self,im,output_queue):
gaussed = ndimage.gaussian_filter(im,self.slider)
output_queue.put(gaussed)
def main():
app = QtGui.QApplication(sys.argv)
form = ApplicationWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
I'm trying to draw vector segments on top of a QGraphicsPixmapItem, i used the mouse events to draw a first "red-dash-line" to positioning the segment. When the mouse is released the segment vertex are stored in a list wich is then appended to an other list. The metalist containing all the segments vertex is then drawn with a green-solid-line.
Once the user finished to draw segments i'm looking for a way to remove the segments from the scene and start over.
I'm not able to find a way to clean-up the scene removing the segments.
An ideal way will be to have each segment listed with a proper segment.identifier and then connect the "clear" push button to a self.scene.removeItem(segment.identifier) to remove it.
#!/usr/bin/env python
import sys
from PyQt4 import QtCore, QtGui
class MainWidget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.scene = QtGui.QGraphicsScene()
self.view = QtGui.QGraphicsView(self.scene)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.view)
self.setLayout(layout)
self.bt = QtGui.QPushButton("Clear lines")
layout.addWidget(self.bt)
self.pixmap_item = QtGui.QGraphicsPixmapItem(QtGui.QPixmap('image.png'), None, self.scene)
self.pixmap_item.mousePressEvent = self.startLine
self.pixmap_item.mouseMoveEvent = self.mouseMoveEvent
self.pixmap_item.mouseReleaseEvent = self.updateProfile
self.startPoint = QtCore.QPointF()
self.profiles = []
self.bt.clicked.connect(self.clean)
self.pp = []
def clean(self):
#self.myItemGroup = self.scene.createItemGroup([])
self.myItemGroup.hide
print(dir(self.myItemGroup))
def startLine(self, event):
pen = QtGui.QPen(QtCore.Qt.red, 2, QtCore.Qt.DashDotLine)
self.sline = QtGui.QGraphicsLineItem(QtCore.QLineF(0,0,0,0))
self.sline.setPen(pen)
self.scene.addItem(self.sline)
print self.profiles
if (QtCore.Qt.LeftButton):
self.startPoint = QtCore.QPointF(event.pos())
def updateProfile(self, event):
self.profiles.append([self.startPoint.x(),self.startPoint.y(), event.pos().x(), event.pos().y()])
#print self.profiles
items = []
pen = QtGui.QPen(QtCore.Qt.green, 2, QtCore.Qt.SolidLine)
for i in self.profiles:
self.pline = QtGui.QGraphicsLineItem(QtCore.QLineF(i[0],i[1],i[2],i[3]))
self.pline.setPen(pen)
#self.scene.addItem(self.pline)
#self.pline.setGroup(self.myItemGroup)
items.append(self.pline)
self.myItemGroup = self.scene.createItemGroup(items)
self.lastPoint = self.startPoint
self.startPoint = QtCore.QPointF(self.profiles[-1][-2],self.profiles[-1][-1])
self.scene.removeItem(self.sline)
print self.startPoint, self.lastPoint
def mouseMoveEvent(self, event):
self.sline.setLine(self.startPoint.x(),self.startPoint.y(), event.pos().x(), event.pos().y())
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
widget = MainWidget()
widget.resize(640, 480)
widget.show()
sys.exit(app.exec_())