I am trying to work with Chaco and pyqt in plotting a real-time data acquisition task for laboratory hardware. I was previously using matplotlib, however it proved to be too slow (I even tried animation). The following code works fine when I embedded a matplotlib plot in a pyqt window, but with chaco, nothing happens when I emit my update signal from inside a thread. This code will work if you do not use a thread for the simulated acquisition. I have tried using qthreads to no avail either (including something like this: Threading and Signals problem in PyQt). Is there anyone out there who has used pyqt + chaco + threading that could help me find where I am going wrong, or what is happening?
import sys
import threading, time
import numpy as np
from enthought.etsconfig.etsconfig import ETSConfig
ETSConfig.toolkit = "qt4"
from enthought.enable.api import Window
from enthought.chaco.api import ArrayPlotData, Plot
from PyQt4 import QtGui, QtCore
class Signals(QtCore.QObject):
done_collecting = QtCore.pyqtSignal(np.ndarray, np.ndarray)
class PlotWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
x = np.linspace(0,2*np.pi,200)
y = np.sin(x)
plotdata = ArrayPlotData(x=x, y=y)
plot = Plot(plotdata, padding=50, border_visible=True)
plot.plot(('x', 'y'))
window = Window(self,-1, component=plot)
self.setCentralWidget(window.control)
self.resize(500,500)
self.pd = plotdata
def update_display(self, x, y):
print 'updating'
self.pd.set_data('x', x)
self.pd.set_data('y', y)
def run_collection(signal):
# this is where I would start and stop my hardware,
# but I will just call the read function myself here
for i in range(1,10):
every_n_collected(i, signal)
time.sleep(0.5)
def every_n_collected(frequency, signal):
# dummy data to take place of device read
x = np.linspace(0,2*np.pi,200)
y = np.sin(x*frequency)
print 'emitting'
signal.emit(x, y)
QtGui.QApplication.processEvents()
def main():
plt = PlotWindow()
plt.show()
QtGui.QApplication.processEvents()
signals = Signals()
signals.done_collecting.connect(plt.update_display)
t = threading.Thread(target=run_collection, args=(signals.done_collecting,))
t.start()
t.join()
QtGui.QApplication.processEvents()
# it works without threads though...
# run_collection(signals.done_collecting)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main()
Your call to join on the mainthread (which is the UI thread) is blocking that thread and prevents the events to be processed by the UI. If you started the app/GUI event loop in the main function and wait for the app to be closed without calling t.join(), it should work fine.
This is the way to do it with regular Traits/TraitsUI/Chaco apps.
import time
import threading
import numpy as np
from traits.etsconfig.etsconfig import ETSConfig
ETSConfig.toolkit = "qt4"
from enable.api import ComponentEditor
from chaco.api import ArrayPlotData, Plot
from traits.api import Event, HasTraits, Instance
from traitsui.api import View, Item
class PlotWindow(HasTraits):
dataset = Instance(ArrayPlotData)
plot = Instance(Plot)
def _dataset_default(self):
x = np.linspace(0,2*np.pi,200)
y = np.sin(x)
plotdata = ArrayPlotData(x=x, y=y)
return plotdata
def _plot_default(self):
plot = Plot(self.dataset, padding=50, border_visible=True)
plot.plot(('x', 'y'))
return plot
def update_display(self, x, y):
print 'updating', threading.current_thread()
self.dataset.set_data('x', x)
self.dataset.set_data('y', y)
traits_view = View(
Item('plot', editor=ComponentEditor(size=(400, 400)), show_label=False)
)
def run_collection(datamodel):
# this is where I would start and stop my hardware,
# but I will just call the read function myself here
for i in range(1,10):
x = np.linspace(0,2*np.pi,200)
y = np.sin(x*i)
datamodel.update_display(x, y)
time.sleep(0.5)
def main():
plot = PlotWindow()
t = threading.Thread(target=run_collection, args=(plot,))
t.start()
# Starts the UI and the GUI mainloop
plot.configure_traits()
# don't call t.join() as it blocks the current thread...
if __name__ == "__main__":
main()
Related
I am trying to add a ROI in real-time with a button click. Once it is added, the ROI is blocking the line plot to scroll. I tried adding the line Plot widget as a parent to the ROI, that didn't work. Is there any way to make the ROI scroll outside of the view along with line plot? Also, how do I retain the historical data and ROIs, currently, the data is replaced by the new data.
Thanks.
import signal
import time
from math import sin
from threading import Thread
from time import sleep
import pyqtgraph as pg
from pglive.kwargs import Axis
from pglive.sources.data_connector import DataConnector
from pglive.sources.live_axis import LiveAxis
from pglive.sources.live_plot import LiveLinePlot
from pglive.sources.live_plot_widget import LivePlotWidget
"""
In this example Line plot is displayed.
"""
import random
import signal
import sys
from math import sin
from time import sleep
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QApplication,QPushButton
running = True
app = QApplication(sys.argv)
button = QPushButton('Add Rect')
def addRect(button):
print('Added')
roi= pg.ROI((time.time()-3,-1.5),(2,3),pen='g',movable=False)
roi.setZValue(10)
time_axis_plot_widget.addItem(roi)
button.clicked.connect(addRect)
def stop():
"""Stop current QApplication"""
global running
running = False
app.exit(0)
# Connect SIGINT with stop function
signal.signal(signal.SIGINT, lambda sig, frame: stop())
connectors = []
layout = pg.LayoutWidget()
# Define Time plot
left_axis = LiveAxis("left", axisPen="red", textPen="red")
bottom_axis = LiveAxis("bottom", axisPen="green", textPen="green", **{Axis.TICK_FORMAT: Axis.TIME})
time_axis_plot_widget = LivePlotWidget(title="Time Line Plot # 100Hz",
axisItems={'bottom': bottom_axis, 'left': left_axis})
plot = LiveLinePlot()
time_axis_plot_widget.addItem(plot)
connectors.append(DataConnector(plot, max_points=600))
layout.addWidget(time_axis_plot_widget)
layout.addWidget(button)
layout.show()
def sin_wave_generator(*data_connectors):
"""Sinus wave generator"""
x = 0
while running:
x += 1
for data_connector in data_connectors:
data_connector.cb_append_data_point(sin(x * 0.01), time.time())
sleep(0.01)
Thread(target=sin_wave_generator, args=connectors).start()
signal.signal(signal.SIGINT, lambda sig, frame: stop())
app.exec()
stop()
Im trying to make a gui where sensor data will be displayed on real time. My program has 4 processes and a main that gets everything going. 2 processes (lets call them sensor processes) just generate random numbers and send them through a pipe to a third process. This processes makes a matplotlib animation on a figure that then is sent to the last process who shows the figure on a tkinter gui. This is just a test to see if it is posible, the end game is to have a bunch of processes that make other animations using the data from the sensors and send them all to the gui where they can be shown when the user decides so in the gui. The problem i have is that when i execute the code the gui will show an empty figure with no animation. I dont know if this is the correct way to doit or if there is a better one all help is welcomed.
I will add the code bellow:
################### main ######################
import multiprocessing
from multiprocessing import freeze_support
from process_1 import Process_1
from process_2 import Process_2
from process_both import Figure_both
from process_GUI import Figure_distrib
if __name__ == "__main__":
freeze_support()
pipe_end_1, pipe_end_2 = multiprocessing.Pipe()
pipe_end_3, pipe_end_4 = multiprocessing.Pipe()
pipe_end_5, pipe_end_6 = multiprocessing.Pipe()
p1 = Process_1(pipe1=pipe_end_1)
p1.start()
p2 = Process_2(pipe2=pipe_end_3)
p2.start()
fb = Figure_both(pipe1=pipe_end_2, pipe2=pipe_end_4, pipe3 = pipe_end_5)
fb.start()
gui = Figure_distrib(pipe3 = pipe_end_6)
gui.start()
############### process_1 ##################
import multiprocessing
import random
class Process_1(multiprocessing.Process):
def __init__(self, pipe1=None):
multiprocessing.Process.__init__(self)
self.pipe1 = pipe1
def run(self):
while True:
y1 = random.randint(0,50)
self.pipe1.send(y1)
################### process_2 ###########################
import multiprocessing
import random
import time
class Process_2(multiprocessing.Process):
def __init__(self, pipe2=None):
multiprocessing.Process.__init__(self)
self.pipe2 = pipe2
def run(self):
while True:
y2 = random.randint(0,50)
self.pipe2.send(y2)
############## process_both ####################
import multiprocessing
import time
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
both = plt.figure()
b1 = both.add_subplot(1, 2, 1)
b2 = both.add_subplot(1, 2, 2)
class Figure_both(multiprocessing.Process):
def __init__(self, pipe1=None, pipe2=None, pipe3=None):
multiprocessing.Process.__init__(self)
self.pipe1 = pipe1
self.pipe2 = pipe2
self.pipe3 = pipe3
def Get_data(self):
if self.pipe1.poll():
y1 = self.pipe1.recv()
if self.pipe2.poll():
y2 = self.pipe2.recv()
values = (y1,y2)
return y1,y2
def animateboth(self,i):
y1, y2 = self.Get_data()
b1.clear()
b1.bar(1, y1, width = 0.4)
b1.hlines(xmin = 0, xmax = 2, y = [5,10,15,20,25,30,35,40,45,50], linestyles= '--', color = 'gray')
b2.clear()
b2.bar(1, y2, width = 0.4)
b2.hlines(xmin = 0, xmax = 2, y = [5,10,15,20,25,30,35,40,45,50], linestyles= '--', color = 'gray')
### this code sends the figure
def run(self):
ani1 = animation.FuncAnimation(both, self.animateboth, interval= 100)
self.pipe3.send(both)
###
### This code shows the figure that should be sent
# def run(self):
# gui = GUI()
#
# ani1 = animation.FuncAnimation(both, self.animateboth, interval=100)
#
# gui.mainloop()
#
#
# class GUI(tk.Tk):
# def __init__(self):
# tk.Tk.__init__(self)
#
# canvas = FigureCanvasTkAgg(both,self)
# canvas.get_tk_widget().pack()
###
############################### process_GUI ################################
import multiprocessing
import time
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
import threading
class Figure_distrib(multiprocessing.Process):
def __init__(self, pipe3=None):
multiprocessing.Process.__init__(self)
self.pipe3 = pipe3
def update_fig(self):
both = self.pipe3.recv()
return both
def run(self):
both = self.update_fig()
gui = GUI(both)
gui.mainloop()
class GUI(tk.Tk):
def __init__(self,both):
tk.Tk.__init__(self)
self.both = both
canvas = FigureCanvasTkAgg(self.both,self)
canvas.get_tk_widget().pack()
I am not an experienced programmer and i am trying to create a sort of datalogger program in python using Qt for python (PySide2) to build the GUI. I was able to create a gui using Designer and the load it inside python. The gui is only a blank window for now. Then i created a function that launch MatplotLib in a window showing a graph, and i updtate data in each loop of the main program, using a Qt timer.
Everithing works, but the redraw time of MatPlotLib slow down the gui refresh too much. So I tried to put MatPlotLib inside a separe Thread, and after a lot of trials i understood it cannot run in a separate Thread..
At the end i decided to try with Multiprocessing. Now the MatPlotLib runs fine in a separate Process ( i use queue to send data to MatPlotLib) and exit properly after the process is finished, but when i close the main window the program newer close completely, and also typing Ctrl+C the prompt is blocked.
This is my code:
#!/usr/bin/env python3
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import QFile, QTimer
import matplotlib.pyplot as plt
from multiprocessing import Process, Queue, freeze_support
import random
class DSL(QWidget):
def __init__(self):
# LOAD HMI
QWidget.__init__(self)
designer_file = QFile('userInterface.ui')
designer_file.open(QFile.ReadOnly)
loader = QUiLoader()
self.ui = loader.load(designer_file, self)
designer_file.close()
self.ui.show()
# Data to be visualized
self.data = []
def mainLoop(self):
self.data = []
for i in range(10):
self.data.append(random.randint(0, 10))
# Send data to graph process
queue.put(self.data)
# LOOP repeater
QTimer.singleShot(10, self.mainLoop)
def graphProcess(queue):
for i in range(10):
# Get data
data = queue.get()
# MatPlotLib
plt.ion()
plt.clf()
plt.plot(data)
plt.show()
plt.pause(0.1)
print('process end')
if __name__ == '__main__':
# MatPlotLib Process
queue = Queue()
freeze_support()
p = Process(target=graphProcess, args=(queue,))
p.daemon = True
p.start()
# PySide2 Process
app = QApplication(sys.argv)
dsl = DSL()
dsl.mainLoop()
sys.exit(app.exec_())
Instead of using matplotlib in a secondary process it is better to embed a canvas in a QWidget that allows it to run in the same PySide2 processes:
#!/usr/bin/env python3
import sys
from PySide2.QtCore import QFile, QObject, Signal, Slot, QTimer
from PySide2.QtWidgets import QApplication, QVBoxLayout, QWidget
from PySide2.QtUiTools import QUiLoader
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import random
class DSL(QObject):
dataChanged = Signal(list)
def __init__(self, parent=None):
# LOAD HMI
super().__init__(parent)
designer_file = QFile("userInterface.ui")
if designer_file.open(QFile.ReadOnly):
loader = QUiLoader()
self.ui = loader.load(designer_file)
designer_file.close()
self.ui.show()
# Data to be visualized
self.data = []
def mainLoop(self):
self.data = []
for i in range(10):
self.data.append(random.randint(0, 10))
# Send data to graph
self.dataChanged.emit(self.data)
# LOOP repeater
QTimer.singleShot(10, self.mainLoop)
class MatplotlibWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
fig = Figure(figsize=(7, 5), dpi=65, facecolor=(1, 1, 1), edgecolor=(0, 0, 0))
self.canvas = FigureCanvas(fig)
self.toolbar = NavigationToolbar(self.canvas, self)
lay = QVBoxLayout(self)
lay.addWidget(self.toolbar)
lay.addWidget(self.canvas)
self.ax = fig.add_subplot(111)
self.line, *_ = self.ax.plot([])
#Slot(list)
def update_plot(self, data):
self.line.set_data(range(len(data)), data)
self.ax.set_xlim(0, len(data))
self.ax.set_ylim(min(data), max(data))
self.canvas.draw()
if __name__ == "__main__":
app = QApplication(sys.argv)
dsl = DSL()
dsl.mainLoop()
matplotlib_widget = MatplotlibWidget()
matplotlib_widget.show()
dsl.dataChanged.connect(matplotlib_widget.update_plot)
sys.exit(app.exec_())
I am interested in real time graph. My aim is to update graph with another definition callback. I tried to debug but I don't see anythink after exec_() command. I tried to call update insteaded of Qtimer.
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from multiprocessing import Process, Manager,Queue
def f(name):
app2 = QtGui.QApplication([])
win2 = pg.GraphicsWindow(title="Basic plotting examples")
win2.resize(1000,600)
win2.setWindowTitle('pyqtgraph example: Plotting')
p2 = win2.addPlot(title="Updating plot")
curve = p2.plot(pen='y')
def updateInProc(curve):
t = np.arange(0,3.0,0.01)
s = np.sin(2 * np.pi * t + updateInProc.i)
curve.setData(t,s)
updateInProc.i += 0.1
QtGui.QApplication.instance().exec_()
updateInProc.i = 0
timer = QtCore.QTimer()
timer.timeout.connect(lambda: updateInProc(curve))
timer.start(50)
if __name__ == '__main__':
m=f()
m
I want to use another definition like
def UpdateCallback():
for x in range(1,100):
m.updateInProc(x,time)
I deleted Qtimer then I tried to send data but I did not see at graph
I did the following example code just to test how to integrate an animated matplotlib plot with pygtk. However, I get some unexpected behaviors when I run it.
First, when I run my program and click on the button (referred to as button1 in the code), there is another external blank window which shows up and the animated plot starts only after closing this window.
Secondly, when I click on the button many times, it seems that there is more animations which are created on top of each other (which gives the impression that the animated plot speeds up). I have tried to call animation.FuncAnimation inside a thread (as you can see in the comment at end of the function on_button1_clicked), but the problem still the same.
Thirdly, is it a good practice to call animation.FuncAnimation in a thread to allow the user to use the other functions of the gui ? Or should I rather create a thread inside the method animate (I guess this will create too many threads quickly) ? I am not sure how to proceed.
Here is my code:
import gtk
from random import random
import numpy as np
from multiprocessing.pool import ThreadPool
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
#from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas
class HelloWorld:
def __init__(self):
interface = gtk.Builder()
interface.add_from_file('interface.glade')
self.dialog1 = interface.get_object("dialog1")
self.label1 = interface.get_object("label1")
self.entry1 = interface.get_object("entry1")
self.button1 = interface.get_object("button1")
self.hbox1 = interface.get_object("hbox1")
self.fig, self.ax = plt.subplots()
self.X = [random() for x in range(10)]
self.Y = [random() for x in range(10)]
self.line, = self.ax.plot(self.X, self.Y)
self.canvas = FigureCanvas(self.fig)
# self.hbox1.add(self.canvas)
self.hbox1.pack_start(self.canvas)
interface.connect_signals(self)
self.dialog1.show_all()
def gtk_widget_destroy(self, widget):
gtk.main_quit()
def on_button1_clicked(self, widget):
name = self.entry1.get_text()
self.label1.set_text("Hello " + name)
self.ani = animation.FuncAnimation(self.fig, self.animate, np.arange(1, 200), init_func=self.init, interval=25, blit=True)
'''
pool = ThreadPool(processes=1)
async_result = pool.apply_async(animation.FuncAnimation, args=(self.fig, self.animate, np.arange(1, 200)), kwds={'init_func':self.init, 'interval':25, 'blit':True} )
self.ani = async_result.get()
'''
plt.show()
def animate(self, i):
# Read XX and YY from a file or whateve
XX = [random() for x in range(10)] # just as an example
YY = [random() for x in range(10)] # just as an example
self.line.set_xdata( XX )
self.line.set_ydata( YY )
return self.line,
def init(self):
self.line.set_ydata(np.ma.array(self.X, mask=True))
return self.line,
if __name__ == "__main__":
HelloWorld()
gtk.main()