Live plotting of many subplots using pyqtgraph - python

I used this question and tried to use it to do fast live plotting of many subplots.
Unfortunately it is very difficult for me to understand the code and thus I have problems changing it for my needs.
I wanted to create a subplot of 2x2 matrices with 10x10 pixels. Right now I am getting the following:
The code looks like that:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import numpy as np
import time
import sys
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
#### Create Gui Elements ###########
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.mainbox.setLayout(QtGui.QVBoxLayout())
self.canvas = pg.GraphicsLayoutWidget()
self.mainbox.layout().addWidget(self.canvas)
self.label = QtGui.QLabel()
self.mainbox.layout().addWidget(self.label)
self.view = self.canvas.addViewBox()
self.view.setAspectLocked(True)
self.view.setRange(QtCore.QRectF(0, 0, 50, 50))
self.img = []
for i in range(4):
self.img.append(pg.ImageItem(None, border="w"))
self.canvas.nextRow()
self.view.addItem(self.img[i])
self._update()
def _update(self):
for i in range(4):
self.data = np.random.rand(10,10)
self.img[i].setImage(self.data)
QtCore.QTimer.singleShot(1, self._update)
def sensor_data(n_sensors, x_res, y_res):
return np.random.rand(n_sensors, x_res, y_res)
if __name__ == '__main__':
while True:
# Get sensor data
data = sensor_data(4, 10, 10)
# Pass data to live plot function?
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())
Can someone please show me what I'm doing wrong?

You have only built a single ViewBox and in it you are adding the items and that causes the problem, what you must do is create several ViewBox and add an item as I show you next:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import numpy as np
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.canvas = pg.GraphicsLayoutWidget()
self.label = QtGui.QLabel()
lay = QtGui.QVBoxLayout(self.mainbox)
lay.addWidget(self.canvas)
lay.addWidget(self.label)
self.img_items = []
for i in range(4):
view = self.canvas.addViewBox()
view.setAspectLocked(True)
view.setRange(QtCore.QRectF(0, 0, 10, 10))
it = pg.ImageItem(None, border="w")
view.addItem(it)
self.img_items.append(it)
self.canvas.nextRow()
timer = QtCore.QTimer(self, interval=1)
timer.timeout.connect(self._update)
timer.start()
def _update(self):
for item in self.img_items:
data = np.random.rand(10, 10)
item.setImage(data)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())
Update:
NxN
n = 2
for i in range(n):
for j in range(n):
view = self.canvas.addViewBox(i, j)
view.setAspectLocked(True)
view.setRange(QtCore.QRectF(0, 0, 10, 10))
it = pg.ImageItem(None, border="w")
view.addItem(it)
self.img_items.append(it)
Update:
If you want to obtain data within a while True you must do it in a new thread to avoid the GUI being blocked, you must also give a small sleep so that the GUI can be updated:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import numpy as np
def sensor_data(n_sensors, x_res, y_res):
return np.random.rand(n_sensors, x_res, y_res)
class Thread(QtCore.QThread):
dataChanged = QtCore.pyqtSignal(np.ndarray)
def run(self):
while True:
data = sensor_data(4, 10, 10)
self.dataChanged.emit(data)
QtCore.QThread.msleep(10)
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.canvas = pg.GraphicsLayoutWidget()
self.label = QtGui.QLabel()
lay = QtGui.QVBoxLayout(self.mainbox)
lay.addWidget(self.canvas)
lay.addWidget(self.label)
self.img_items = []
n = 2
for i in range(n):
for j in range(n):
view = self.canvas.addViewBox(i, j)
view.setAspectLocked(True)
view.setRange(QtCore.QRectF(0, 0, 10, 10))
it = pg.ImageItem(None, border="w")
view.addItem(it)
self.img_items.append(it)
#QtCore.pyqtSlot(np.ndarray)
def update_data(self, data):
for i, v in enumerate(data):
self.img_items[i].setImage(v)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
thisapp = App()
thread = Thread()
thread.dataChanged.connect(thisapp.update_data)
thread.start()
thisapp.show()
sys.exit(app.exec_())

Related

How can I clip a curve in pyqtgraph?

I am trying to work with pyqt and create a "UI". The main idea is to plot a graph/curve and clip the selected area with placing the widget (blue area) and clicking on the button: clip.
I tried to write a function def clicked(self):, which should change the data frame and update the curve. The idea: I am selecting an area, which creates a high (self.hi) and low value (self.lo). I am creating a new list filteredto select the index, which I want to drop later from my data frame. Is there a way to write a better function? How should I approach to this problem?
The .csv Date is online:
https://gigamove.rwth-aachen.de/de/download/ba5527c1f92270bc3235bfbd05c3a5d9
Best
import pandas as pd
import numpy as np
import glob
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
class MyWindow(QMainWindow):
Liste = ["BetaPinen4MSAlleParikel.csv"]#glob.glob("*.csv")
df = pd.read_csv(Liste[0])
def __init__(self):
super(MyWindow, self).__init__()
#self. setGeometry(200, 200, 300, 300)
self.setWindowTitle("Spectrum analysis")
self.initUI()
self.hi = 0
self.setCentralWidget(self.cw)
self.l.addWidget(self.pw3)
#Plot###########
#self.graphWidget.setBackground('w')
self.curve_plot()
self.lr.sigRegionChanged.connect(self.regionUpdate)
self.pw3.addItem(self.lr)
self.l.addWidget(self.b1)
self.cw.setLayout(self.l)
self.show()
def curve_plot(self):
self.curve = self.pw3.plot(MyWindow.df["Energy"],MyWindow.df["df1.1"], clickable=True)
self.curve.curve.setClickable(True)
self.curve.setPen('w') ## white pen
self.curve.setShadowPen(pg.mkPen((70,70,30), width=6, cosmetic=True))
def regionUpdate(self,regionItem):
self.lo,self.hi = regionItem.getRegion()
print(round(self.lo,2), round(self.hi,2))
def initUI(self): #All the stuff on the window in this function
self.label = QtWidgets.QLabel(self)
self.label.setText("my first label!")
self.label.move(50,50)
self.b1 = QtWidgets.QPushButton(self)
self.b1.setText("Clip")
self.b1.clicked.connect(self.clicked)
self.cw = QtWidgets.QWidget()
self.proxy = QtWidgets.QGraphicsProxyWidget()
self.l = QtWidgets.QVBoxLayout()
self.pw3 = pg.PlotWidget()
self.lr = pg.LinearRegionItem([250, 300], movable=True)
def clicked(self):
print(self.lo, self.hi)
List = list(MyWindow.df.index.values)
filtered = [x for x in List if x > self.lo and x < self.hi]
print(filtered)
MyWindow.df.index = MyWindow.df["Energy"]
print(MyWindow.df)
MyWindow.df = MyWindow.df.drop(filtered)
print(MyWindow.df)
self.curve.setData(MyWindow.df["Energy"],MyWindow.df["df1.1"])
self.curve.sigPlotChanged(MyWindow.df)
def window() :
app=pg.mkQApp()
win= MyWindow()
sys.exit(app.exec_())
window()
It took me some time, but I found a solution.
First: I created a new instance with an attribute:
win.curve_plot(name_df="BetaPinen4MSAlleParikel.csv")
Second: I change the function def clicked(self) a bit.
def clicked(self):
self.df = self.df[(self.df.Energy < self.lo) | (self.df.Energy > self.hi)]
self.df = self.df.reset_index()
self.df = self.df.drop(["index"],axis=1)
self.curve.setData(self.df["Energy"],self.df["df1.1"])

PyQt5 Pyqtgraph plot discret

At present, for this project, I'm working with matplolib to trace my plot.
Nevertheless, I wish to convert at pyqtgraph ( Pyqt5).
I didn't found how trace a discret plot of FFT with pyqtgraph, same the picture.
Have you an idea ?
You can create an item in which the vertical lines are drawn:
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
class LinePlot(pg.GraphicsObject):
def __init__(self, data):
super(LinePlot, self).__init__()
self._data = []
self.setData(data)
def setData(self, data):
self._data = data
self.generate()
def generate(self):
self.picture = QtGui.QPicture()
p = QtGui.QPainter(self.picture)
p.setPen(pg.mkPen('r', width=2))
for (t, v) in self._data:
if v != 0:
p.drawLine(QtCore.QPointF(t, 0), QtCore.QPointF(t, v))
def paint(self, p, *args):
p.drawPicture(0, 0, self.picture)
def boundingRect(self):
return QtCore.QRectF(self.picture.boundingRect())
if __name__ == '__main__':
import sys
import random
app = QtGui.QApplication(sys.argv)
w = QtGui.QMainWindow()
view = pg.GraphicsLayoutWidget()
w.setCentralWidget(view)
plot = view.addPlot()
vals = [(i, random.randint(0, 255)) for i in range(30)]
plot.addItem(LinePlot(vals))
w.show()
sys.exit(app.exec_())

Add combobox to horizontal header in QTableWidget

I want to create a table in PyQt5 that has a combobox in each column header. When I try to do it, the following error is returned:
TypeError: setHorizontalHeaderItem(self, int, QTableWidgetItem): argument 2 has unexpected type 'QComboBox'
Apparently the function setHorizontalHeaderItem() doesn't accept widgets as items. So is there a way to achieve this? If not, I would settle with putting the comboboxes above the headers, but they should be aligned with the size of each column, even if the user changes the width with the mouse. I don't know if this is possible either.
My code:
from PyQt5 import QtWidgets
import numpy as np
class App(QtWidgets.QWidget):
def __init__(self):
super(App,self).__init__()
self.data = np.random.rand(5,5)
self.createTable()
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.table)
self.setLayout(self.layout)
self.showMaximized()
def createTable(self):
self.header = []
self.table = QtWidgets.QTableWidget(len(self.data), len(self.data[0]))
for i in range(len(self.data[0])):
self.header.append(QtWidgets.QComboBox())
self.header[-1].addItem('Variable')
self.header[-1].addItem('Timestamp')
self.table.setHorizontalHeaderItem(i,self.header[-1])
for i in range(len(self.data)):
for j in range(len(self.data[0])):
self.table.setItem(i,j,QtWidgets.QTableWidgetItem(str(self.data[i][j])))
if __name__ == '__main__':
app = QtWidgets.QApplication([])
ex = App()
app.exec_()
QHeaderView does not support widgets as items so you must create a custom header as I show below:
from PyQt5 import QtCore, QtWidgets
import numpy as np
class HorizontalHeader(QtWidgets.QHeaderView):
def __init__(self, values, parent=None):
super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent)
self.setSectionsMovable(True)
self.comboboxes = []
self.sectionResized.connect(self.handleSectionResized)
self.sectionMoved.connect(self.handleSectionMoved)
def showEvent(self, event):
for i in range(self.count()):
if i < len(self.comboboxes):
combo = self.comboboxes[i]
combo.clear()
combo.addItems(["Variable", "Timestamp"])
else:
combo = QtWidgets.QComboBox(self)
combo.addItems(["Variable", "Timestamp"])
self.comboboxes.append(combo)
combo.setGeometry(self.sectionViewportPosition(i), 0, self.sectionSize(i)-4, self.height())
combo.show()
if len(self.comboboxes) > self.count():
for i in range(self.count(), len(self.comboboxes)):
self.comboboxes[i].deleteLater()
super(HorizontalHeader, self).showEvent(event)
def handleSectionResized(self, i):
for i in range(self.count()):
j = self.visualIndex(i)
logical = self.logicalIndex(j)
self.comboboxes[i].setGeometry(self.sectionViewportPosition(logical), 0, self.sectionSize(logical)-4, self.height())
def handleSectionMoved(self, i, oldVisualIndex, newVisualIndex):
for i in range(min(oldVisualIndex, newVisualIndex), self.count()):
logical = self.logicalIndex(i)
self.comboboxes[i].setGeometry(self.ectionViewportPosition(logical), 0, self.sectionSize(logical) - 5, height())
def fixComboPositions(self):
for i in range(self.count()):
self.comboboxes[i].setGeometry(self.sectionViewportPosition(i), 0, self.sectionSize(i) - 5, self.height())
class TableWidget(QtWidgets.QTableWidget):
def __init__(self, *args, **kwargs):
super(TableWidget, self).__init__(*args, **kwargs)
header = HorizontalHeader(self)
self.setHorizontalHeader(header)
def scrollContentsBy(self, dx, dy):
super(TableWidget, self).scrollContentsBy(dx, dy)
if dx != 0:
self.horizontalHeader().fixComboPositions()
class App(QtWidgets.QWidget):
def __init__(self):
super(App,self).__init__()
self.data = np.random.rand(10, 10)
self.createTable()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.table)
self.showMaximized()
def createTable(self):
self.header = []
self.table = TableWidget(*self.data.shape)
for i, row_values in enumerate(self.data):
for j, value in enumerate(row_values):
self.table.setItem(i, j, QtWidgets.QTableWidgetItem(str(value)))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())

Create several popup plots with FigureCanvas in PyQt4

Following code creates a Widget with a Button and a Progressbar. When Button is pressed and Progressbar reaches 100%, 3 plots are shown (by Matplotlib.pyplot):
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import matplotlib.pyplot as plt
import pandas as pd
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.setGeometry(500, 300, 820, 350)
self.setWindowTitle("Program")
#Buttons
btnposx = 30
btnposy = 50
self.btn4 = QPushButton('Load', self)
self.btn4.move(btnposx,btnposy+220)
self.btn4.released.connect(self.thread)
#ProgressBar
self.pb = QProgressBar(self)
self.pb.move(btnposx+150,btnposy+220)
self.pb.resize(470,27)
self.show()
#pyqtSlot(float)
def load(self, val):
self.pb.setValue(val)
#pyqtSlot(object)
def plot(self, pq):
pq.plot(grid = 1)
plt.show()
def thread(self):
self.thread_ = Thread()
self.thread_.pb_signal.connect(self.load, Qt.QueuedConnection)
self.thread_.plot_signal.connect(self.plot, Qt.QueuedConnection)
self.thread_.start()
class Thread(QThread):
pb_signal = pyqtSignal(float)
plot_signal = pyqtSignal(object)
def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
def __del__(self):
self.wait()
#pyqtSlot()
def run(self):
val = 0
self.pb_signal.emit(20)
l = range(50000000)
for i in l:
val += 1
self.pb_signal.emit(60)
self.pb_signal.emit(100)
pq = pd.DataFrame(data = {'col1':[1,2,3,4,5,6], 'col2':[6,5,4,3,2,1]})
self.plot_signal.emit(pq)
self.plot_signal.emit(pq)
self.plot_signal.emit(pq)
return
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
How do I do the exact same Thing with FigureCanvas? I don't want to make one window, in that 3 plots are embedded but 3 seperate figures. The Information online about how to use FigureCanvas this way is very scarce.
FigureCanvas is a specialized QWidget that can contain a Figure of matplotlib, and in that Figure you can make the plots, so the solution is to create one for each emission, in addition to that the plotting functions have an additional argument that receives the axes where it will be drawn, so you must pass the axes created inside Figure:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import pandas as pd
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.setGeometry(500, 300, 820, 350)
self.setWindowTitle("Program")
#Buttons
btnposx = 30
btnposy = 50
self.btn4 = QPushButton('Load', self)
self.btn4.move(btnposx,btnposy+220)
self.btn4.released.connect(self.thread)
#ProgressBar
self.pb = QProgressBar(self)
self.pb.move(btnposx+150,btnposy+220)
self.pb.resize(470,27)
self.canvas = []
self.show()
#pyqtSlot(float)
def load(self, val):
self.pb.setValue(val)
#pyqtSlot(object)
def plot(self, pq):
cv = FigureCanvas(Figure(figsize=(5, 3)))
ax = cv.figure.subplots()
pq.plot(grid = 1, ax=ax)
cv.show()
# avoid garbage collector
self.canvas.append(cv)
def thread(self):
self.thread_ = Thread()
self.thread_.pb_signal.connect(self.load, Qt.QueuedConnection)
self.thread_.plot_signal.connect(self.plot, Qt.QueuedConnection)
self.thread_.start()
class Thread(QThread):
pb_signal = pyqtSignal(float)
plot_signal = pyqtSignal(object)
def __del__(self):
self.wait()
#pyqtSlot()
def run(self):
val = 0
self.pb_signal.emit(20)
l = range(50000000)
for i in l:
val += 1
self.pb_signal.emit(60)
self.pb_signal.emit(100)
pq = pd.DataFrame(data = {'col1':[1,2,3,4,5,6], 'col2':[6,5,4,3,2,1]})
self.plot_signal.emit(pq)
self.plot_signal.emit(pq)
self.plot_signal.emit(pq)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
Note: only the list has been created to prevent the garbage collector from deleting the plots

draw matplotlib graph with QThread issue

Beside main class Window, I have defined new class (test_graph_Data) which will draw graph with matplotlib. In order to avoid problem with alarm "QPixmap: It is not safe to use pixmaps outside the GUI thread", I have defined new class test_graph_Data, which will emit signal to main class for drawing the graph, but there are some problems to emit the signals... Look at the code below:
When I run the code, I got the warning: AttributeError: "PyQt4.QtCore.pyqtSignal" object has no attribute 'connect'!
import sys, time
from PyQt4 import QtGui, QtCore
import matplotlib.pyplot as plt
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 120, 90)
self.home()
def home(self):
self.test = QtGui.QPushButton("Test", self)
self.test.clicked.connect(self.test1_function)
self.test.move(10,20)
self.show()
def test1_function(self):
self.get_thread = test_graph_Data()
self.connect(self.get_thread, QtCore.SIGNAL("finished()"),self.done_test1_function)
self.get_thread.start()
def done_test1_function(self):
print 'Graph is displayed!'
class test_graph_Data(QtCore.QThread) :
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def graph_data(self):
start = time.time()
b = [1,0,1,0,1,0,1,1,1,1,0,1,0,1]
plt.ion()
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
ax1.plot(b, 'b')
end = time.time()
print end - start
def run(self):
top_post = self.graph_data()
def main():
app = QtGui.QApplication(sys.argv)
GUI = Window()
GUI.show()
sys.exit(app.exec_())
if __name__ == '__main__' :
main()
You must use the thread to manipulate the data, not to graph. In this example I have made your list rotate. You must create a signal that communicates to the main thread that the data is ready to be updated. In the GUI you must connect that signal with a function that updates the graph.
import sys
import time
import matplotlib as mpl
mpl.use("QT4Agg")
import matplotlib.pyplot as plt
from PyQt4 import QtGui, QtCore
class test_graph_Data(QtCore.QThread):
updated = QtCore.pyqtSignal(list)
running = True
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.b = [1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1]
def run(self):
while self.running:
# rotate list
self.b = self.b[1:] + [self.b[0]]
self.updated.emit(self.b)
time.sleep(0.1)
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 120, 90)
self.home()
def home(self):
self.test = QtGui.QPushButton("Test", self)
self.test.clicked.connect(self.test1_function)
self.test.move(10, 20)
self.show()
def test1_function(self):
self.get_thread = test_graph_Data(self)
self.get_thread.finished.connect(self.done_test1_function)
self.get_thread.updated.connect(self.graph_data)
plt.ion()
fig1 = plt.figure()
self.ax1 = fig1.add_subplot(111)
self.get_thread.start()
def done_test1_function(self):
print('Graph is displayed!')
def graph_data(self, data):
self.ax1.clear()
self.ax1.plot(data, 'b')
def closeEvent(self, event):
self.get_thread.running = False
self.get_thread.wait()
def main():
app = QtGui.QApplication(sys.argv)
GUI = Window()
GUI.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Categories

Resources