I am attempting to put pyqtgraph ROI widgets (information here) on top of a .PNG image. When I import the image into the program, it comes out rotated and flipped the wrong way. I assume this is a bug. To attempt to fix it, I have rotated the image BUT when I do so, my ROI widget goes off of the image. How do I fix this?
Without image rotation:
i = Image.open("del.png")
a = array(i) #converting to numpy array
img1a = pg.ImageItem(a)
v1a.addItem(img1a)
Once I add img1a.rotate(90) to the code above, the ROI widget goes off the screen. How do I position the image the correct way and have my ROI widget appear normally on top of the image?
The entire code is found below (edited from this example found here.)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from numpy import array
from PIL import Image
## create GUI
app = QtGui.QApplication([])
w = pg.GraphicsWindow(size=(1000,800), border=True)
w.setWindowTitle('pyqtgraph example: ROI Examples')
text = """text"""
w1 = w.addLayout(row=0, col=0)
label1 = w1.addLabel(text, row=0, col=0)
v1a = w1.addViewBox(row=1, col=0, lockAspect=True)
#img1a = pg.ImageItem(arr)
i = Image.open("del.png")
a = array(i)
img1a = pg.ImageItem(a)
v1a.addItem(img1a)
img1a.rotate(90)
v1a.disableAutoRange('xy')
v1a.autoRange()
rois = []
rois.append(pg.EllipseROI([150, 150], [1, 1], pen=(4,9)))
rois.append(pg.EllipseROI([0, 0], [300, 300], pen=(4,9)))
for roi in rois:
v1a.addItem(roi)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
You do not have to rotate the item, but you must rotate the image for it you can use numpy.rot90:
i = Image.open("del.png")
a = array(i)
a = np.rot90(a, -1)
img1a = pg.ImageItem(a)
v1a.addItem(img1a)
v1a.disableAutoRange('xy')
v1a.autoRange()
Related
Goal:
I am trying to display an image using PyQt5 edited using OpenCV.
The above image shows a side by side comparison of my expected output (Shown from a openCV Window) and the actual output displayed in a PyQt5 Label Pixmap.
The picture shows that the image is successfully resized, but not being displayed correctly.
About the QLabel (used to display the image):
The QLabel is within a Frame. Here's how it is defined:
self.ImageDisplayerLB = QtWidgets.QLabel(self.topFrame) #topFrame is a QFrame
self.ImageDisplayerLB.setEnabled(True)
self.ImageDisplayerLB.setText("")
self.ImageDisplayerLB.setPixmap(QtGui.QPixmap("./<image>.jpg"))
self.ImageDisplayerLB.setAlignment(QtCore.Qt.AlignCenter)
self.ImageDisplayerLB.setObjectName("ImageDisplayerLB")
self.gridLayout_2.addWidget(self.ImageDisplayerLB, 0, 0, 1, 1)
About the QFrame used in QLabel:
The QFrame does have a minimum height and width (Size) set so it doesn't look too small while displaying the image.
self.topFrame = QtWidgets.QFrame(self.frame)
self.topFrame.setMinimumSize(QtCore.QSize(831, 409))
self.topFrame.setStyleSheet("background-color: rgb(1,1,1);")
self.topFrame.setObjectName("topFrame")
self.topFrame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.topFrame.setFrameShadow(QtWidgets.QFrame.Raised)
The Pixmap is being set again with a different function call while handling an event. The below code snippet is where the error seem to be occuring.
if hw := self.__check_oversized_image(image): # Returns height, width if image is larger than the QLabel size, else returns None.
w, h = self.ImageDisplayerLB.width(), self.ImageDisplayerLB.height()
self.ImageDisplayerLB.pixmap().detach() # Tried the same without it, makes no difference
thresh = min((self.ImageDisplayerLB.width(), self.ImageDisplayerLB.height()))
r = thresh / image.shape[1]
dim = (thresh, int(image.shape[0] * r))
image = cv2.resize(image, dim, interpolation=cv2.INTER_AREA) # Resize Image maintaining the ratio
self.ImageDisplayerLB.setScaledContents(True) # Makes no difference with or without this
# End of if block
frame = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
self.qimage = QtGui.QImage(
frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888
)
try:
self.pimage = QtGui.QPixmap.fromImage(self.qimage, QtCore.Qt.AutoColor)
self.ImageDisplayerLB.setPixmap(self.pimage)
except Exception as e:
print(e)
When does this occur?
This issue is only when the image is found oversized and I am resizing the image. It works fine without the image being oversized.
Any help to fix the issue where the image is grayscale and tilted.
Minimal Reproducible Code:
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QMainWindow
import cv2 as cv
class main(QMainWindow):
def __init__(self):
super().__init__()
self.mainFrame = QtWidgets.QFrame(self)
self.grid = QtWidgets.QGridLayout(self.mainFrame)
self.label = QtWidgets.QLabel(self.mainFrame)
img = cv.imread("cert_template.png") # Image from -> https://simplecert.net/certificate-templates/
frame = img.copy()
self.label.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(frame.data,frame.shape[1],frame.shape[0],QtGui.QImage.Format_RGB888,))) # Not sure why this is grayscale and tilted
self.mainFrame.setMinimumSize(QtCore.QSize(831, 409))
self.label.setScaledContents(True)
self.grid.addWidget(self.label, 0, 0, 1, 1)
cv.imshow("image", img) # Displays the predicted output
cv.waitKey(0)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = main()
window.show()
app.exec_()
Much appreciated.
Using pyQt5 I am continuously updating a plot with data using the self.graphicsView.clear() followed by self.graphicsView.plot() functions.
I changed the background color with the command pyqtgraph.setConfigOption('background', '#f0f0f0') before the widget is created, however, this does not apply to the legend items. The background is grey, and the legend appears black.
How do I change the style of this legend item?
I think I am implementing this wrong based upon how I reference each new plot item. I believe these need to be instantiated somehow, but the instance = self.graphicsView.plot(title = "example title") then referencing with instance.LegendItem and then access it with an HTML like tag. (Unable to find the reference anymore)
def plotGraph(self, value):
"""
plots value to graph
"""
self.graphQueue(self.plotDataBuffer, value) #buffered data input, max vals = value
self.graphicsView.clear() #clear data for continuous plot
self.graphicsView.addLegend()
self.graphicsView.plot(self.plotDataBuffer, pen='r', name='Data') #plot item
Note: This function is called in a loop
The addLengend() command creates a new legend each time it is called, however, my understanding is that this is only created once and if it is called again it only references the legend that was already created?
So, how do i properly initialize the legends once, and then format the style to match the background instead of black?
Simplified example:
import pyqtgraph as pg
from PyQt5 import QtGui
import numpy as np
import sys
pg.setConfigOption('background', '#f0f0f0')
plotWidget = pg.plot(title="Stackoverflow Simplified Example")
app = QtGui.QApplication(sys.argv)
while(1):
x = np.arange(50)
y = np.random.normal(size=(3, 50))
plotWidget.clear()
plotWidget.addLegend()
for i in range(3):
plotWidget.plot(x, y[i], pen=(i,3), name = "test {}".format(i))
app.processEvents()
if __name__ == '__main__':
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
app.exec_() # Start QApplication event loop ***
Checking the pyqtgraph LegendItem.py, one would find that the color is hard-coded into the paint function. You can replace that function with your user-defined function and change the color.
import pyqtgraph as pg
from PyQt5 import QtGui
import numpy as np
import sys
import types
pg.setConfigOption('background', '#f0f0f0')
plotWidget = pg.plot(title="Stackoverflow Simplified Example")
app = QtGui.QApplication(sys.argv)
leg = plotWidget.addLegend()
# replace legend paint
def paint(self, p, *args):
p.setPen(pg.mkPen(255,0,0,100))
p.setBrush(pg.mkBrush(0,200,0,50))
p.drawRect(self.boundingRect())
leg.paint = types.MethodType(paint,leg)
plotData = [0]*3
for i in range(3):
plotData[i] =plotWidget.plot([0],[0], pen=(i,3), name = "test {}".format(i))
while(1):
x = np.arange(50)
y = np.random.normal(size=(3, 50))
for i in range(3):
plotData[i].setData(x, y[i])
app.processEvents()
if __name__ == '__main__':
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
app.exec_() # Start QApplication event loop ***
If I understand correctly, you're wondering how to change the background color of a legend. Or at least that's how I stumbled upon this question.
I found that we can use the brush argument:
plotWidget.addLegend(brush=pg.mkBrush(255, 255, 255, 100))
where the first three values are your RGB color code and the last number is the alpha value that determines how transparent the background will be. In this example, the legend will be made with a white, somewhat transparent background.
I am trying to integrate the pyqtgraph example into a class.
However, since the example uses "global" to acces important methods, I am having trouble translating it into a class.
The example:
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
# Interpret image data as row-major instead of col-major
pg.setConfigOptions(imageAxisOrder='row-major')
pg.mkQApp()
win = pg.GraphicsLayoutWidget()
win.setWindowTitle('pyqtgraph example: Image Analysis')
# A plot area (ViewBox + axes) for displaying the image
p1 = win.addPlot()
# Item for displaying image data
img = pg.ImageItem()
p1.addItem(img)
# Custom ROI for selecting an image region
roi = pg.ROI([-8, 14], [6, 5])
roi.addScaleHandle([0.5, 1], [0.5, 0.5])
roi.addScaleHandle([0, 0.5], [0.5, 0.5])
p1.addItem(roi)
roi.setZValue(10) # make sure ROI is drawn above image
# Isocurve drawing
iso = pg.IsocurveItem(level=0.8, pen='g')
iso.setParentItem(img)
iso.setZValue(5)
# Contrast/color control
hist = pg.HistogramLUTItem()
hist.setImageItem(img)
win.addItem(hist)
# Draggable line for setting isocurve level
isoLine = pg.InfiniteLine(angle=0, movable=True, pen='g')
hist.vb.addItem(isoLine)
hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier
isoLine.setValue(0.8)
isoLine.setZValue(1000) # bring iso line above contrast controls
# Another plot area for displaying ROI data
win.nextRow()
p2 = win.addPlot(colspan=2)
p2.setMaximumHeight(250)
win.resize(800, 800)
win.show()
# Generate image data
data = np.random.normal(size=(200, 100))
data[20:80, 20:80] += 2.
data = pg.gaussianFilter(data, (3, 3))
data += np.random.normal(size=(200, 100)) * 0.1
img.setImage(data)
hist.setLevels(data.min(), data.max())
# build isocurves from smoothed data
iso.setData(pg.gaussianFilter(data, (2, 2)))
# set position and scale of image
img.scale(0.2, 0.2)
img.translate(-50, 0)
# zoom to fit imageo
p1.autoRange()
# Callbacks for handling user interaction
def updatePlot():
global img, roi, data, p2
selected = roi.getArrayRegion(data, img)
p2.plot(selected.mean(axis=0), clear=True)
roi.sigRegionChanged.connect(updatePlot)
updatePlot()
def updateIsocurve():
global isoLine, iso
iso.setLevel(isoLine.value())
isoLine.sigDragged.connect(updateIsocurve)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
What I tried: (only changed parts)
def updatePlot(img, roi, data, p2):
#global img, roi, data, p2
selected = roi.getArrayRegion()
p2.plot(selected.mean(axis=0), clear=True)
roi.sigRegionChanged.connect(updatePlot(img, roi, data, p2))
updatePlot(img, roi, data, p2)
def updateIsocurve(isoLine, iso):
# global isoLine, iso
so.setLevel(isoLine.value())
isoLine.sigDragged.connect(updateIsocurve(isoLine, iso))
This gives an error, since the "img" object I am giving it instead of accessing it through "global" seems to be of type None.
I don't know how to give the update function access to the necessary objects.
Make all the variables into instance variables by using self
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
class ImageWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(ImageWidget, self).__init__(parent)
# Interpret image data as row-major instead of col-major
pg.setConfigOptions(imageAxisOrder='row-major')
pg.mkQApp()
self.win = pg.GraphicsLayoutWidget()
self.win.setWindowTitle('pyqtgraph example: Image Analysis')
# A plot1 area (ViewBox + axes) for displaying the image
self.plot1 = self.win.addPlot()
# Item for displaying image data
self.item = pg.ImageItem()
self.plot1.addItem(self.item)
# Custom ROI for selecting an image region
self.ROI = pg.ROI([-8, 14], [6, 5])
self.ROI.addScaleHandle([0.5, 1], [0.5, 0.5])
self.ROI.addScaleHandle([0, 0.5], [0.5, 0.5])
self.plot1.addItem(self.ROI)
self.ROI.setZValue(10) # make sure ROI is drawn above image
# Isocurve drawing
self.iso = pg.IsocurveItem(level=0.8, pen='g')
self.iso.setParentItem(self.item)
self.iso.setZValue(5)
# Contrast/color control
self.hist = pg.HistogramLUTItem()
self.hist.setImageItem(self.item)
self.win.addItem(self.hist)
# Draggable line for setting isocurve level
self.isoLine = pg.InfiniteLine(angle=0, movable=True, pen='g')
self.hist.vb.addItem(self.isoLine)
self.hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier
self.isoLine.setValue(0.8)
self.isoLine.setZValue(1000) # bring iso line above contrast controls
# Another plot1 area for displaying ROI data
self.win.nextRow()
self.plot2 = self.win.addPlot(colspan=2)
self.plot2.setMaximumHeight(250)
self.win.resize(800, 800)
self.win.show()
# Generate image self.data
self.data = np.random.normal(size=(200, 100))
self.data[20:80, 20:80] += 2.
self.data = pg.gaussianFilter(self.data, (3, 3))
self.data += np.random.normal(size=(200, 100)) * 0.1
self.item.setImage(self.data)
self.hist.setLevels(self.data.min(), self.data.max())
# build isocurves from smoothed self.data
self.iso.setData(pg.gaussianFilter(self.data, (2, 2)))
# set position and scale of image
self.item.scale(0.2, 0.2)
self.item.translate(-50, 0)
# zoom to fit imageo
self.plot1.autoRange()
self.ROI.sigRegionChanged.connect(self.updatePlot)
self.updatePlot()
self.isoLine.sigDragged.connect(self.updateIsocurve)
# Callbacks for handling user interaction
def updatePlot(self):
selected = self.ROI.getArrayRegion(self.data, self.item)
self.plot2.plot(selected.mean(axis=0), clear=True)
def updateIsocurve(self):
self.iso.setLevel(self.isoLine.value())
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
app = QtGui.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
image_widget = ImageWidget()
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
I am new in both python and opencv and trying to develop a simple object tracking GUI by using opencv tracking API. I am using my webcam to test it. My purpose is selecting the target object in the current frame. For this, I plot a rectangle in the center of the window and select the target object with reference to the rectangle. Since the rectangular shape may fail the tracking operation I want to select the target object from the original frame. In the code I create two different frames which are called rawInp and outImage. rawInp is the input video. outImage is the final outcome and I want all the shapes are plotted in this image. I use an external function to plot the rectangle. I show also the rawInp to check it. However, I see the rectangular shape also in this output. How is it possible and how can I solve this problem? Also, how can I use only rawInp variable for both operations? Because copying the same the same variable is not a good way to handle. I am adding the related part of my code but if you want to see the whole code I can add. Thank you in advance for any of your answers.
import sys
import cv2
import numpy as np
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSlot, QTimer
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QDialog, QApplication, QFileDialog, QMainWindow
from PyQt5.uic import loadUi
cap = cv2.VideoCapture(0)
if not cap.isOpened(): print ("Could not open video") ,sys.exit()
ok, frame = cap.read(0)
height, width, channels = frame.shape
upper_left = (3*int(width/8), 3*int(height/8))
bottom_right = (5*int(width/8), 5*int(height/8))
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
class trackingApp(QMainWindow):
def __init__(self):
super(trackingApp,self).__init__()
loadUi('tracking_ui.ui',self)
self.rawInp = None
self.outImage = None
self.stBtclk = False
self.trckBtclk = False
self.startButton.clicked.connect(self.start_webcam)
self.trackingButton.clicked.connect(self.tracking_clicked)
#pyqtSlot()
def start_webcam(self):
self.stBtclk = True
self.capture = cv2.VideoCapture(0)
self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT,480)
self.capture.set(cv2.CAP_PROP_FRAME_WIDTH,620)
self.timer=QTimer(self)
self.timer.timeout.connect(self.update_frame)
self.timer.start(5)
def update_frame(self):
ret, self.rawInp = self.capture.read()
self.rawInp = cv2.flip(self.rawInp,1)
self.rawInp = cv2.cvtColor(self.rawInp, cv2.COLOR_BGR2GRAY)
self.rawInp = clahe.apply(self.rawInp)
self.rawInp = cv2.cvtColor(self.rawInp, cv2.COLOR_GRAY2BGR)
self.outImage = self.rawInp
if self.trckBtclk: self.tracker_update()
self.displayImage(self.outImage,1)
def plotCenter(self, outImage):
cv2.rectangle(outImage, upper_left, bottom_right, (0, 255, 0), 2)
# Plot the central horizontal and vertical lines
cv2.line(outImage,(50,int(height/2)),(width-50,int(height/2)),(0,255,0),1)
cv2.line(outImage,(int(width/2),50),(int(width/2),height-50),(0,255,0),1)
cv2.imshow('rawInp',self.rawInp)
#pyqtSlot()
def tracking_clicked(self):
if self.stBtclk:
self.trckBtclk = True
self.tracker = cv2.TrackerKCF_create()
bbox = (3*int(width/8), 3*int(height/8), 2*int(width/8), 2*int(height/8))
self.tracker.init(self.rawInp, bbox)
marker=self.rawInp[3*int(height/8):5*int(height/8), 3*int(width/8):5*int(width/8)]
self.surf = cv2.xfeatures2d.SURF_create(500)
kp, des = self.surf.detectAndCompute(marker,None)
marker = cv2.drawKeypoints(marker,kp,None,(0,0,255),4)
cv2.imshow("marker", marker)
else: pass
def tracker_update(self):
ok, bbox = self.tracker.update(self.outImage)
if ok:
# Tracking success
p1 = (int(bbox[0]), int(bbox[1]))
p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
cv2.rectangle(self.outImage, p1, p2, (0,255,255), 2, 1)
kp, des = self.surf.detectAndCompute(self.outImage,None)
# self.outImage = cv2.drawKeypoints(self.outImage,kp,None,(0,0,255),4)
cv2.putText(self.outImage, "Tracking", (5,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,255,0),2)
else:
# Tracking failure
cv2.putText(self.outImage, "Tracking failure detected", (5,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2)
def displayImage(self, outImage, window):
self.plotCenter(self.outImage)
qformat=QImage.Format_Indexed8
if len(outImage.shape)==3: #[0]=rows, [1]=columns, [2]=channels
if(outImage.shape[2])==4:
qformat=QImage.Format_RGBA8888
else:
qformat=QImage.Format_RGB888
outImg=QImage(outImage,outImage.shape[1],outImage.shape[0],outImage.strides[0],qformat)
outImg=outImg.rgbSwapped() #BRG>>RGB
if window==1:
self.trackingScreen.setPixmap(QPixmap.fromImage(outImg))
self.trackingScreen.setScaledContents(True)
if __name__=="__main__":
app = QApplication(sys.argv)
app.aboutToQuit.connect(app.deleteLater)
window = trackingApp()
window.show()
#sys.exit(app.exec_())
app.exec_()
And here is an example screenshot:
The first window is showing the ui and final output while the second one, named "rawInp", is showing the unprocessed input video. I do not expect to see the green rectangle in second window
I'm using pyqtgraph to plot tracks of a robot (the path that the bot drove). Now I want to add a marker to the plot to indicate the bots current position and heading. I thought ArrowItem would be the right choice, because it is scale invariant and can be rotated easily. However the local origin of the arrow is at its tip like this
but I want it to be in the center like this
How can I do that? I would also appreciate different solutions to this problem.
Update
After applying eyllansec's code I get some rendering problems. A minimal example (one has to zoom or move the view to disable the auto scaling):
import pyqtgraph as pg
import numpy as np
import time
class CenteredArrowItem(pg.ArrowItem):
def paint(self, p, *args):
p.translate(-self.boundingRect().center())
pg.ArrowItem.paint(self, p, *args)
if __name__ == '__main__':
app = pg.QtGui.QApplication([])
window = pg.GraphicsWindow(size=(1280, 720))
window.setAntialiasing(True)
tracker = window.addPlot(title='Tracker')
while True:
for i in range(300):
arrow = CenteredArrowItem(angle=i, headLen=40, tipAngle=45, baseAngle=30)
arrow.setPos(i / 300, i / 300)
tracker.addItem(arrow)
app.processEvents()
time.sleep(0.02)
tracker.removeItem(arrow)
As you may noticed I'm adding and removing the arrow each iteration. This is because arrow.setStyle(angle=i) is not working as it does not update the rotation of the arrow (probably a bug).
A possible solution is to overwrite the paint method of ArrowItem and move the QPainter:
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
class MyArrowItem(pg.ArrowItem):
def paint(self, p, *args):
p.translate(-self.boundingRect().center())
pg.ArrowItem.paint(self, p, *args)
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
p = pg.PlotWidget()
p.showGrid(x = True, y = True, alpha = 0.3)
w.show()
w.resize(640, 480)
w.setCentralWidget(p)
w.setWindowTitle('pyqtgraph example: Arrow')
a = pg.ArrowItem(angle=-160, tipAngle=60, headLen=40, tailLen=40, tailWidth=20, pen={'color': 'w', 'width': 3}, brush='r')
b = MyArrowItem(angle=-160, tipAngle=60, headLen=40, tailLen=40, tailWidth=20, pen={'color': 'w', 'width': 3})
a.setPos(10,0)
b.setPos(10,0)
p.addItem(a)
p.addItem(b)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
As shown in the following figure, the red arrow is the default ArrowItem, and the blue is the offset, both are located in the same position with respect to the plot.
Update:
The problem is caused by the method that rotates the item used as the center of coordinates using the center of transformations by default, that is to say the (0, 0), we must move it:
import pyqtgraph as pg
import numpy as np
import time
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph import functions as fn
class CenteredArrowItem(pg.ArrowItem):
def setStyle(self, **opts):
# http://www.pyqtgraph.org/documentation/_modules/pyqtgraph/graphicsItems/ArrowItem.html#ArrowItem.setStyle
self.opts.update(opts)
opt = dict([(k,self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']])
tr = QtGui.QTransform()
path = fn.makeArrowPath(**opt)
tr.rotate(self.opts['angle'])
p = -path.boundingRect().center()
tr.translate(p.x(), p.y())
self.path = tr.map(path)
self.setPath(self.path)
self.setPen(fn.mkPen(self.opts['pen']))
self.setBrush(fn.mkBrush(self.opts['brush']))
if self.opts['pxMode']:
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
else:
self.setFlags(self.flags() & ~self.ItemIgnoresTransformations)
if __name__ == '__main__':
app = pg.QtGui.QApplication([])
window = pg.GraphicsWindow(size=(1280, 720))
window.setAntialiasing(True)
tracker = window.addPlot(title='Tracker')
while True:
for i in range(300):
arrow = CenteredArrowItem(angle=i, headLen=40, tipAngle=45, baseAngle=30)
arrow.setPos(i / 300, i / 300)
tracker.addItem(arrow)
app.processEvents()
time.sleep(0.02)
tracker.removeItem(arrow)
After digging through the source code of pyqtgraph I ended up with a special function that suits my needs. I apply the translation when creating the arrow path, instead when rendering it. Fortunately this also solves the roation bug (for whatever reason).
Example:
import pyqtgraph as pg
import numpy as np
import time
import pyqtgraph.functions
class CenteredArrowItem(pg.ArrowItem):
def setData(self, x, y, angle):
self.opts['angle'] = angle
opt = dict([(k, self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']])
path = pg.functions.makeArrowPath(**opt)
b = path.boundingRect()
tr = pg.QtGui.QTransform()
tr.rotate(angle)
tr.translate(-b.x() - b.width() / 2, -b.y() - b.height() / 2)
self.path = tr.map(path)
self.setPath(self.path)
self.setPos(x, y)
if __name__ == '__main__':
app = pg.QtGui.QApplication([])
window = pg.GraphicsWindow(size=(1280, 720))
window.setAntialiasing(True)
tracker = window.addPlot(title='Tracker')
arrow = CenteredArrowItem(headLen=40, tipAngle=45, baseAngle=30)
tracker.addItem(arrow)
tracker.addItem(pg.InfiniteLine(pos=(0,0), angle=45))
center = pg.ScatterPlotItem([], [], brush='r')
tracker.addItem(center)
while True:
for i in range(300):
arrow.setData(i, i, i)
center.setData([i], [i])
app.processEvents()
time.sleep(0.02)