Update pyqtgraph plot from RxPy observable - python

In the following script, a plot window is created and values are correctly passed to plot.update. However, the plot is not updating. What am I doing wrong?
import sys
import time
import numpy
from numpy import pi
import rx
from rx.concurrency import QtScheduler
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
class DemoData:
def __init__(self, window, steps):
def signal(t):
omega = 5
return numpy.sin(2*pi*omega*t) + numpy.random.random()
self.stream = rx.Observable.range(
0, steps
).map(
signal
).buffer_with_count(
window, 1
)
class UpdatingPlot:
def __init__(self):
self.plot = pg.plot()
self.curve = self.plot.plot()
def update(self, values):
self.curve.setData(values)
def main():
app = QtGui.QApplication([])
scheduler = QtScheduler(QtCore)
window, steps = 50, 100
data = DemoData(window, steps)
plot = UpdatingPlot()
data.stream.subscribe_on(scheduler=scheduler).subscribe(plot.update)
sys.exit(app.exec_())
if __name__ == '__main__':
main()

This doesn't fix your issue, but it might fix your need. You can use the package joystick, install it using pip install joystick. It is designed to provide a real-time and interactive plotting framework.
Here is an example using it. It creates a graph-frame that 'listens' and displays your sine function as a function of time; a new data point is added every 0.2 sec.
import joystick as jk
import numpy as np
import time
class DemoData(jk.Joystick):
# initialize the infinite loop and callit decorators so they can auto-
# register methods they decorate
_infinite_loop = jk.deco_infinite_loop()
_callit = jk.deco_callit()
#_callit('before', 'init')
def _init_data(self, *args, **kwargs):
# Function automatically called at initialization, thanks to the
# decorator
self.tdata = np.array([]) # time x-axis
self.ydata = np.array([]) # data y-axis
self.omega = 5
#_callit('after', 'init')
def _build_frames(self, *args, **kwargs):
# Function automatically called at initialization, thanks to the
# decorator.
# Creates a graph frame
self.mygraph = self.add_frame(
jk.Graph(name="DemoData", size=(500, 500), pos=(50, 50),
fmt="go-", xnpts=50, freq_up=7, bgcol="w",
xylim=(None,None,-1.1,2.1), xlabel='t', ylabel='sine'))
#_callit('before', 'start')
def _set_t0(self):
# initialize t0 at start-up
self._t0 = time.time()
#_infinite_loop(wait_time=0.2)
def _get_data(self):
# This method will automatically be called with simulation start
# (t.start()), and looped every 0.2 in a separate thread as long as
# the simulation runs (running == True)
# It generates new data and pushes it to the frame.
# concatenate data on the time x-axis
timestamp = time.time() - self._t0
self.tdata = jk.add_datapoint(self.tdata,
timestamp,
xnptsmax=self.mygraph.xnptsmax)
new_y_data = np.sin(2*np.pi*self.omega*timestamp) + np.random.random()
self.ydata = jk.add_datapoint(self.ydata,
new_y_data,
xnptsmax=self.mygraph.xnptsmax)
# push new data to the graph
self.mygraph.set_xydata(np.round(self.tdata, 1), self.ydata)
d = DemoData()
d.start()
...
d.stop()

Related

Data acquisition and plotting in separate threads

I have a class that does data acquisition, i.e. PySession.scope in a separate thread. I want to plot each scope in a ScopeGUI, which is based on PyQt5. Obviously, the GUI's need to be running in the main thread, however I want to be able to invoke another PySession.scope at any time, that is, the console has to be free to submit new commands.
To conclude: Let's say I start two scopes in parallel, while the data is being collected it should be visualized in two separate instances of the ScopeGUI. Simultaneously, the console should be free to start an additional scope. What's the best practice so achieve this behavior in Python?
Here's a reproducible example of what I have so far:
.
├── main.py
├── scope.py
├── alosaclient.py
main.py
import os
import time
import asyncio
from concurrent.futures import ThreadPoolExecutor
from alosaclient import PySession
import matplotlib.pyplot as plt
if __name__ == '__main__':
session = PySession(
"C:/ProjTMS/alosa1-client/test/config/linkcommand.toml")
variables = "speedcpu U_Z"
future1 = session.scope(
varnames=variables, nsamples=20, ndiv=1, realtime=True)
future2 = session.scope("m_1 m_u", nsamples=20, ndiv=1, realtime=True)
print("----------------------------------------------MAIN THREAD----------------------------------------------")
session.work()
result1 = future1.result()
print(result1.data[1].time)
print(result1.data[1].values)
result2 = future2.result()
print(result2.data[1].time)
print(result2.data[1].values)
scope.py
import numpy as np
import sys
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import (FigureCanvas,
NavigationToolbar2QT as
NavigationToolbar)
from matplotlib.figure import Figure
class ScopeGUI(QtWidgets.QMainWindow):
def __init__(self, varnames, nsamples):
super().__init__()
self.varnames = varnames
self.nsamples = nsamples
self.setEnabled(True)
self.setGeometry(0, 0, 800, 600)
self.setMinimumSize(800, 600)
self.main_widget = QtWidgets.QWidget()
self.setCentralWidget(self.main_widget)
self.main_layout = QtWidgets.QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
self._fig = Figure(figsize=(8, 6))
self._canvas = FigureCanvas(self._fig)
self.main_layout.addWidget(self._canvas)
self._axes = self._fig.subplots()
self._axes.grid(True, which="both")
self._axes.set_xlabel('Time (s)')
self._axes.set_ylabel('Data (DSPu)')
self._axes.set_xlim(left=0, right=6.3)
self._axes.set_ylim(bottom=-1.5, top=1.5)
self.lines = []
self.initialize_lines()
self.addToolBar(NavigationToolbar(self._canvas, self))
self.show()
def initialize_lines(self):
variables = self.varnames.split()
for var in variables:
line, = self._axes.plot([], [])
line.set_marker('.')
self.lines.append(line)
def plot(self, scope_data):
print("plotting")
for signal, line in zip(scope_data, self.lines):
x = signal.time
y = signal.values
common_length = min(len(x), len(y))
line.set_xdata(x[:common_length])
line.set_ydata(y[:common_length])
self._canvas.draw()
self._canvas.flush_events()
alosaclient.py
import sys
import time
from concurrent.futures import ThreadPoolExecutor
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import random
from PyQt5 import QtWidgets
from scope import ScopeGUI
import asyncio
app = QtWidgets.QApplication(sys.argv)
class ScopeDataElement:
def __init__(self, variable_index, time, values):
self.variable_index = variable_index
self.time = time
self.values = values
class Scope:
def __init__(self, data):
self.data = data
class PySession:
def __init__(self, lcmdfile):
self.pool = ThreadPoolExecutor(8)
self.loop = asyncio.get_event_loop()
self.tasks = list()
self.scope_buffer = list()
def work(self):
self.loop.run_until_complete(asyncio.wait(self.tasks))
def scope(self, varnames, nsamples=1, ndiv=1, realtime=False):
future = self.pool.submit(
self.dummy_scope, varnames, nsamples, ndiv)
if realtime:
scope_gui = ScopeGUI(varnames, nsamples)
task = self.loop.create_task(self.update(scope_gui, varnames))
self.tasks.append(task)
return future
async def update(self, scope_gui, varnames):
variable_indices = [self.name_to_index(
var) for var in varnames.split()]
# find corresponding scope_buffer is it may potentially grow dynamically
scope_index = self.find_scope_index(variable_indices)
# as long as empty, wait
while not self.scope_buffer:
await asyncio.sleep(25e-3)
# while the data is not complete, update to GUI
while not all([len(signal.time) == scope_gui.nsamples for signal in self.scope_buffer[scope_index].data]):
scope_gui.plot(self.scope_buffer[scope_index].data)
await asyncio.sleep(25e-3)
#staticmethod
def name_to_index(varname):
# dummy cross reference table: get index from variable name
varnames = ["speedcpu", "U_Z", "m_1", "m_u"]
return varnames.index(varname)
def find_scope_index(self, variable_indices):
# get scope index from variable_indices, may change if scopes run parallel
result = filter(lambda pair: all([signal.variable_index == varindex for varindex, signal in zip(
variable_indices, pair[1].data)]), enumerate(self.scope_buffer))
index = list(result)[0][0]
return index
def find_data_index(self, scope, varname):
result = filter(lambda pair: self.name_to_index(varname) ==
pair[1].variable_index, enumerate(scope.data))
index = list(result)[0][0]
return index
def dummy_scope(self, varnames, nsamples, ndiv):
variables = varnames.split()
variable_indices = [self.name_to_index(
var) for var in variables]
content = [ScopeDataElement(self.name_to_index(
var), list(), list()) for var in variables]
scope = Scope(content)
self.scope_buffer.append(scope)
for var in variables:
scope_index = self.find_scope_index(variable_indices)
data_index = self.find_data_index(
self.scope_buffer[scope_index], var)
linspace = np.linspace(0, 2*np.pi, nsamples)
for arrayidx, point in enumerate(linspace):
print(f"scope index is {scope_index}")
print(f"data index is {data_index}")
self.scope_buffer[scope_index].data[data_index].time.append(
point)
self.scope_buffer[scope_index].data[data_index].values.append(
np.sin(point) + random.uniform(0, 0.2))
time.sleep(10e-3)
return self.scope_buffer[scope_index]
Essentially, this code does the following:
Start scope (PySession.scope)
trigger two dummy scope functions to gather some dummy data
push back according GUI update task into the event loop
Process event loop (PySession.work)
As soon as all desired scopes have been initialized, the event loop gets processes, i.e. the GUI's are updated
Issues:
As long as the event loop runs, the console is blocked and no more commands can be submitted.
IMPORTANT
I'm working with the Python interactive console, that's why there is no app.exec_() command. The reproducible example has the be started with python3 -i main.py to start on the same page as I.
I am fully aware that the way I'm trying to do this is probably dead wrong, that's why I'm asking you guys for help.
Thanks in advance!
Here's how it's done, no more asyncio needed:
The update function needs to be adapted as follows. Move the canvas.draw() from the GUI to the update routine to avoid flickering of the figure.
def update(self, scope_gui, varnames):
try:
variable_indices = [self.name_to_index(
var) for var in varnames.split()]
# find corresponding scope_buffer is it may potentially grow dynamically
scope_index = self.find_scope_index(variable_indices)
done = False
while not self.scope_buffer:
time.sleep(10e-3)
# while the data is not complete, update to GUI
while not all([len(signal.time) == scope_gui.nsamples for signal in self.scope_buffer[scope_index].data]):
scope_gui.plot(self.scope_buffer[scope_index].data)
scope_gui._canvas.draw()
time.sleep(25e-3)
except Exception as e:
print(f"EXCEPTION OCURRED: {e}")
raise(e)
Accordingly, the scope gets adapted as well.
def scope(self, varnames, nsamples=1, ndiv=1, realtime=False):
future = self.pool.submit(
self.dummy_scope, varnames, nsamples, ndiv)
if realtime:
scope_gui = ScopeGUI(varnames, nsamples)
self.pool.submit(self.update, scope_gui, varnames)
return future
This ensures that the GUI runs in the main thread, whereas the updating task is running in a separate thread and therefore does not block the console.

In QThread: calls a function again 1 second after the function finishes

I want to acquire a spectrum of a spectrometer every 1s, and below is the code I wrote for this.
However, when the spectrometer is triggered at less than 1 Hz,
the function asking for a spectrum will wait for a trigger and take longer than 1s to process, while the timer has already timed out.
This cause the thread to be very laggy and eventually freeze.
What is a better way to write it so that it calls the function 'acquire_spectrum' 1s after the function finishes itself, instead of just calling it every 1s?
class Spec_Thread(QThread):
def __init__(self):
QThread.__init__(self)
self.signals = Signals()
self.specth = Spectrometer.from_first_available() #connect to the spectrometer
self.threadtimer = QTimer()
self.threadtimer.moveToThread(self)
self.threadtimer.timeout.connect(self.acquire_spectrum)
def acquire_spectrum(self): #acquire the current spectrum from the spectrometer
print('in thread,', device_running)
if device_running == True:
self.specth.open() #open the usb portal
self.wavelengths = self.specth.wavelengths() #acquire wavelengths (will wait for a trigger)
self.intensities = self.specth.intensities() #acquire intensities (will wait for a trigger)
self.specth.close() #close usb portal
self.signals.new_spectrum.emit(self.wavelengths, self.intensities)
else:
print('Device stopped')
return
def run(self):
self.threadtimer.start(10000)
loop = QEventLoop()
loop.exec_()
You have to start a timer after finishing the task execution, and for this you can threading.Timer():
import threading
from PyQt5 import QtCore
import numpy as np
from seabreeze.spectrometers import Spectrometer
class QSpectrometer(QtCore.QObject):
dataChanged = QtCore.pyqtSignal(np.ndarray, np.ndarray)
def __init__(self, parent=None):
super().__init__(parent)
self.specth = Spectrometer.from_first_available()
def start(self, repeat=False):
threading.Thread(target=self._read, args=(repeat,), daemon=True).start()
def _read(self, repeat):
self.specth.open()
wavelengths = self.specth.wavelengths()
intensities = self.specth.intensities()
self.specth.close()
self.dataChanged.emit(wavelengths, intensities)
if repeat:
threading.Timer(1, self._read, (repeat,)).start()
if __name__ == "__main__":
import sys
app = QtCore.QCoreApplication(sys.argv)
spectometer = QSpectrometer()
spectometer.start(True)
spectometer.dataChanged.connect(print)
sys.exit(app.exec_())

AttributeError: 'PyQt4.QtCore.pyqtBoundSignal' object has no attribute 'Emt'

I've been trying to get a controller to work with signals (pyqtSignal), but I get this message when interacting with the GUI:
Traceback (most recent call last):
File "main_signals.py", line 102, in <module>
helper.Sinal.Emt.connect(PID.Imprime, QtCore.Qt.QueuedConnection)
AttributeError: 'PyQt4.QtCore.pyqtBoundSignal' object has no attribute 'Emt'
Would someone please tell me what I'm doing wrong, here's the stripped down version of the code (main_signals.py):
# Import 3rd party libraries
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import QObject, pyqtSignal
import time
import pyqtgraph
# Import python standard modules
import sys
# This file holds the MainWindow
import Plotter
# Variables
T = [0]
R = [0]
# Disregard this function
def ReadChannel(channel):
# -----------------------------------------------------------------
class Ctrldr(QtGui.QMainWindow, Plotter.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.QOutput.clicked.connect(self.Calculo)
# Plotting
#QtCore.pyqtSlot(str, tuple)
def Imprime(self, name, ptm):
global T, R, W
x, y = ptm
R.append(y)
self.graphicsView.plotItem.plot(T, R, pen='b')
# Calculations
def Calculo(self):
global T
t = time.clock()
T.append(t)
Read = ReadChannel(0)
Helper.Sinal.emit("Sensor", (t, Read))
Read = str(Read)
self.QResult.setText(Read)
# -----------------------------------------------------------------
class Helper(QtCore.QObject):
Sinal = QtCore.pyqtSignal(str, tuple)
def __init__(self):
super(self.__class__, self).__init__()
def Emt(self, str, tuple):
self.Sinal.emit(str, tuple)
# -----------------------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
PID = Ctrldr()
helper = Helper()
helper.Sinal.connect(PID.Imprime)
PID.show()
app.exec_()
Tried to follow the examples in these 2 pages:
Plotting with pyqtgraph using external data
Tutorial- Creating your own signals an slots
The one that emits the signal is an object, in your Helper it is not an object, but the name of a class, so the solution is to create an object. For another str and tuple are reserved words that indicate data types, in the case of pyqtSignal() its use is correct since pyqtSignal () requires as parameters the types of data it will handle, but do not use it as arguments in Emt() since you overlapped his name.
# Import 3rd party libraries
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import QObject, pyqtSignal
import time
import pyqtgraph
# Import python standard modules
import sys
# This file holds the MainWindow
import Plotter
# Variables
T = [0]
R = [0]
# Disregard this function
def ReadChannel(channel):
# some process
return channel
# -----------------------------------------------------------------
# -----------------------------------------------------------------
class Helper(QtCore.QObject):
Sinal = QtCore.pyqtSignal(str, tuple)
def Emt(self, arg1, arg2):
self.Sinal.emit(arg1, arg2)
class Ctrldr(QtGui.QMainWindow, Plotter.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.QOutput.clicked.connect(self.Calculo)
self.helper = Helper(self) # <--- object
# Plotting
#QtCore.pyqtSlot(str, tuple)
def Imprime(self, name, ptm):
global T, R, W
x, y = ptm
R.append(y)
self.graphicsView.plotItem.plot(T, R, pen='b')
# Calculations
def Calculo(self):
global T
t = time.clock()
T.append(t)
Read = ReadChannel(0)
self.helper.Sinal.emit("Sensor", (t, Read))
Read = str(Read)
self.QResult.setText(Read)
# -----------------------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
PID = Ctrldr()
helper = Helper()
helper.Sinal.connect(PID.Imprime)
PID.show()
app.exec_()

Python update Matplotlib from threads

I'm pretty new to the python world and unfortunately I could not find any solution to this yet.
I'm running python 3.6 with matplotlib==1.3.1 on Mac OS X 10.13.2
Currently I'm trying to build a small software which fetches data at 4Hz and displays the fetched data in a plot at 1Hz. Therefore I created a class which runs in a thread to fetch the data and another class to update the actual plots. In-between there is a data class which will hold the data and is used as interface inbetween the two classes.
import matplotlib.pyplot as plt
import numpy as np
import threading
import random
import time
class MyDataClass():
def __init__(self):
self.XData = [0]
self.YData = [0]
class MyPlotClass(threading.Thread):
def __init__(self, dataClass):
threading.Thread.__init__(self)
self._dataClass = dataClass
self._period = 1
self._nextCall = time.time()
self.hLine, = plt.plot(0, 0)
plt.ion()
def run(self):
while True:
self.hLine.set_data(self._dataClass.XData, self._dataClass.YData)
plt.draw()
print("updated %i datapoints" % len(self._dataClass.XData))
# sleep until next execution
self._nextCall = self._nextCall + self._period
time.sleep(self._nextCall - time.time())
class MyDataFetchClass(threading.Thread):
def __init__(self, dataClass):
threading.Thread.__init__(self)
self._dataClass = dataClass
self._period = 0.25
self._nextCall = time.time()
def run(self):
while True:
# add data to data class
self._dataClass.XData.append(self._dataClass.XData[-1] + 1)
self._dataClass.YData.append(random.randint(0, 256))
print("Added (%i, %i)" % (self._dataClass.XData[-1], self._dataClass.YData[-1]))
# sleep until next execution
self._nextCall = self._nextCall + self._period
time.sleep(self._nextCall - time.time())
data = MyDataClass()
fetcher = MyDataFetchClass(data)
plotter = MyPlotClass(data)
fetcher.start()
plotter.start()
fetcher.join()
plotter.join()
I can see that the threads are running due to the command line output. But for some reason the figure holding the plots won't show up.
The rocket symbol will just bounce up and down instead of showing up. See attached Screenshot.
Simple examples where the plot is only created ones and the plt.show() command is used works fine.
I can't figure out what I'm doing wrong. I hope anyone of you might have an idea.
Thanks!
Edit: Solutions presented here How to update a plot in matplotlib? will not work for me because I don't want to be limited to a certain number of frames (using the animation framework of Matplotlib). I need to update the plot with 1Hz continiously.
Figure not showing up
I don't think you can run matplotlib GUI outside the main thread. So keeping the plotting in the main thread and using a FuncAnimation to steer the plotting, the following seems to work fine.
Due to the while True loop it will run forever, even after closing the window, so for any real world application this should still be adjusted.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
import threading
import random
import time
class MyDataClass():
def __init__(self):
self.XData = [0]
self.YData = [0]
class MyPlotClass():
def __init__(self, dataClass):
self._dataClass = dataClass
self.hLine, = plt.plot(0, 0)
self.ani = FuncAnimation(plt.gcf(), self.run, interval = 1000, repeat=True)
def run(self, i):
print("plotting data")
self.hLine.set_data(self._dataClass.XData, self._dataClass.YData)
self.hLine.axes.relim()
self.hLine.axes.autoscale_view()
class MyDataFetchClass(threading.Thread):
def __init__(self, dataClass):
threading.Thread.__init__(self)
self._dataClass = dataClass
self._period = 0.25
self._nextCall = time.time()
def run(self):
while True:
print("updating data")
# add data to data class
self._dataClass.XData.append(self._dataClass.XData[-1] + 1)
self._dataClass.YData.append(random.randint(0, 256))
# sleep until next execution
self._nextCall = self._nextCall + self._period;
time.sleep(self._nextCall - time.time())
data = MyDataClass()
plotter = MyPlotClass(data)
fetcher = MyDataFetchClass(data)
fetcher.start()
plt.show()
#fetcher.join()

GUI with pyqtgraph never refresh

I have been working on a GUI for the beagle bone black that launch a thread when a button is clicked and starts to get data through the SPI.
This function is inside a class called Scanner(QObject) and runs in a different thread when the Start button is clicked.
def scan (self):
thread_name = QThread.currentThread().objectName()
self.sig_msg.emit('Scanning '+thread_name)
for step in range(nsamples):
data = self.read_reg(reg[thread_name])
self.sig_data.emit(step, data)
QThread.currentThread().msleep(50)
app.processEvents()
if self.__abort:
self.sig_msg.emit('scan stopped by user')
break
self.sig_done.emit(thread_name)
sig_msg is a pyqtsignal connected to the following function inside the GUI thread.
#pyqtSlot(int, int)
def on_scaner_data(self, t: int, y: int):
app.processEvents()
self.debugBox.insertPlainText('t: '+str(t)+'y: '+str(y)+'\n')
self.debugBox.ensureCursorVisible()
self.MainGraph.update_fig(t,y)
And finally the MainGraph.update_fig() is called. Inside that function i have used setData(self.datat,self.datay) and app.processEvents() for update the graph, but nothing changes. If i run plot(self.datat,self.datay) instead it redraws the graph but causes a huge performance hit.
class DynamicPlotter(PlotWidget):
def __init__(self,parent=None):
PlotWidget.__init__(self)
self.setParent(parent)
# Use getPlotItem() to get the PlotItem inside PlotWidget.
self.pitem = self.getPlotItem()
#now pitem is our PlotItem
self.pitem.curve=self.pitem.plot()
#curve is a new PlotDataItem added by PlotItem.plot()
self.datat = [1,2]
self.datay = [1,2]
self.pitem.curve.setData(self.datat,self.datay)
#this graph works fine
self.datat = []
self.datay = []
def update_fig(self,t:int,y:int):
self.datat.append(t)
self.datay.append(y)
#it works
self.pitem.curve=self.pitem.plot(self.datat,self.datay)
#it doesn't
self.pitem.curve.setData(self.datat,self.datay)
app.processEvents()
print (self.datat)
log.debug(str(t)+str(y))
def reset_figure(self):
log.debug('clean graph')
self.clear()
I have been following this example from the pyqtplot and my idea was do something similar inside my GUI.
import initExample
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
app = QtGui.QApplication([])
p = pg.plot()
p.setWindowTitle('pyqtgraph example: PlotSpeedTest')
p.setRange(QtCore.QRectF(0, -10, 5000, 20))
p.setLabel('bottom', 'Index', units='B')
curve = p.plot()
data = np.random.normal(size=(50,5000))
ptr = 0
lastTime = time()
fps = None
def update():
global curve, data, ptr, p, lastTime, fps
curve.setData(data[ptr%10])
ptr += 1
now = time()
dt = now - lastTime
lastTime = now
if fps is None:
fps = 1.0/dt
else:
s = np.clip(dt*3., 0, 1)
fps = fps * (1-s) + (1.0/dt) * s
p.setTitle('%0.2f fps' % fps)
app.processEvents() ## force complete redraw
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
I have been reading the documentation and right know I'm not sure where is/are the problems. I bet for the threads or the event loop handler but i don't know.
Which are the critical points that i have to review?
Any clue?
Thank you.
After a while i have found the problem by myself.
I fixed the problem changing the way that i was using to reset the graph and stopping the scan thread until elements are plot.
Changes on reset function. self.clear() remove the traces on the PlotWidget and that wasn't what i needed.
def reset_figure(self):
log.debug('clean graph')
self.datat =[]
self.datay=[]
self.pitem.curve.setData(self.datat,self.datay)
Scan was modified to stop while the data is plotted in the other thread. sync_to_plot stop the thread execution until self._wait=False. This value is changed by wait_state.
def scan (self):
thread_name = QThread.currentThread().objectName()
#thread_id = str(QThread.currentThreadId())#review
self.sig_msg.emit('Scanning '+thread_name)
for step in range(nsamples):
data = self.read_reg(reg[thread_name])
self.sig_data.emit(step, data)
#pause while plot values
self.sync_to_plot()
if step % refrate == 0:
log.debug("%5d : %d" % (step, data) )
if self.__abort:
self.sig_msg.emit('scan stoped by user')
break
self.sig_done.emit(thread_name)
def sync_to_plot(self):
self._wait=True
while self._wait:
log.debug("waiting")
QThread.currentThread().msleep(1)
app.processEvents()
def wait_state(self, stat):
self._wait=stat
With that done the last change was on the on_scaner_data that unblocks the thread that is waiting on sync_to_plot.
#pyqtSlot(int, int)
def on_scaner_data(self, t: int, y: int):
app.processEvents()
self.debugBox.insertPlainText('t: '+str(t)+'y: '+str(y)+'\n')
self.debugBox.ensureCursorVisible()
self.MainGraph.update_fig(t,y)
for thread, scaner in self.__threads:
scaner.wait_state(False)
log.debug("scanner false")

Categories