12-14-2021 06:54 PM
TLDR: matplotlib animation crashing and not continuously plotting data from NIDAQmx read_many_samples.
Note: I am a new programmer and I've only been programming using Python for about 2 months so please excuse me if this is a basic question.
I am creating a control system in Python by combining PyQT5, Matplotlib, NIDAQmx, and multiprocessing.
I want to continuously read data from an NI DAQ and then plot the data as an animation. I have separated this onto separate process from my user IO and opened up a new window, separate from the main GUI. However, when I run the code, the animation window opens and I begin to see some plots for about half a second before the window crashes. The main GUI stays open though, because it is on a separate process.
I have included the problem file here for reference. This class gets called in from a main function in a different file.
I believe the problem originates from the animate function. Specifically:
self.y1data.append(get_data.values_read(6)[1])
self.y2data.append(get_data.values_read(6)[2])
self.y3data.append(get_data.values_read(6)[3])
self.time = datetime.datetime.now()
self.target_time_new = self.time + datetime.timedelta(seconds=self.t)
self.x = self.target_time_new
self.xdata.append(self.x)
I wanted to just access the first row of data in the values_read numpy array get_data.values_read[0,:]
but unfortunately, I was getting a "builtin_function_or_method' object is not subscriptable error" so I changed it. I put the "(6)" because I figure that I have 6 sensors so the shape of the array was going to be 6 rows? Feel free to tell me if that is incorrect. I was not able to find much information on the subscriptable error and numpy arrays. Mostly because I am not sure if this is because I don't understand buffers properly and how they store data or if I don't understand how numpy arrays work. Most likely both.
This code is a very large project that is still a work-in-progress so excuse the unfinished comments and sloppy work.
import numpy as np
import nidaqmx
from nidaqmx import constants
from nidaqmx import stream_readers, stream_writers
from nidaqmx.stream_writers import AnalogSingleChannelWriter
from nidaqmx.stream_readers import AnalogMultiChannelReader
from PyQt5 import QtCore
import datetime
from time import sleep
import sys
# INPUT globals and constants
NUM_CHAN = 2
FS_ACQ = 1651 # sample frequency
T_MED = 2 # time to acquire data
# OUTPUT globals and constants
BUFSIZE = 200
BUFSIZE_CLK = 200
Nu = 1E-06
TOL = 1E-3 # to compare for setpoint currently. This will be
# removed when the control system is implemented
RATE_OUTCFG = 1000
RATE_CALLBACK = 200
global STREAM
global COUNTER
COUNTER = 0
class get_data():
""" A class used to receive and plot data from sensors
. . .
Description:
Attributes
----------
Methods
-------
data_gen():
Generates simulated data to use as an example for animated chart
animate(data):
plots the data as an animation (moving chart)
TODO: Fix Doc String
"""
in_task = nidaqmx.Task()
#Add a Current Analog Input channel from ai0
in_task.ai_channels.add_ai_current_chan('cDAQ1Mod1/ai0:5', min_val=0, max_val=0.02)
in_task.timing.cfg_samp_clk_timing(rate=FS_ACQ, sample_mode=constants.AcquisitionType.CONTINUOUS,samps_per_chan=(T_MED * FS_ACQ),)
samples_per_buffer = int(FS_ACQ // 30) # 30 Hz update
values_read = np.empty
def __init__(self):
"""
TODO: Fix Doc String
"""
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# interactive(True)
#used to set ASCR to in_stream to allow for reading data from a
#single channel
self.reader = AnalogMultiChannelReader(self.in_task.in_stream)
get_data.in_task.register_every_n_samples_acquired_into_buffer_event(1000, self.input_callback)
get_data.in_task.start()
#Initialize 'data' variable as an empty array
self.data = np.empty
self.inputs = []
# Create a figure and initialize 2 rows, 3 columns
self.fig, ((self.ax1, self.ax2, self.ax3),
(self.ax4, self.ax5, self.ax6)) = plt.subplots(figsize=(12,8), nrows=2, ncols=3)
# line1 and line2 initialized to be plot on subplots
self.line1, = self.ax1.plot([], [])
self.line2, = self.ax2.plot([], [], color='r')
self.line3, = self.ax3.plot([], [], color='b')
self.line4, = self.ax4.plot([], [], color='g')
self.line5, = self.ax5.plot([], [], color='c')
self.line6, = self.ax6.plot([], [], color='m')
# concatenate the two data lines into one variable
self.line = [self.line1, self.line2, self.line3, self.line4, self.line5,self.line6]
self.time = datetime.datetime.now()
self.target_time = self.time + datetime.timedelta(seconds=60)
#self.x = self.target_time
# set axes for both plots
for ax in [self.ax1, self.ax2, self.ax3,
self.ax4, self.ax5, self.ax6]:
ax.set_ylim(-1.1, 1.1)
ax.set_xlim(self.time, self.target_time)
ax.grid() # Display gridlines on plot
# Data point for the 'line' variable
self.xdata, self.y1data, self.y2data, self.y3data = [], [], [], []
# animation for a dynamic chart
anim = animation.FuncAnimation(self.fig, self.animate, self.data_gen, blit=True, interval=0.1, repeat=False)
# Show the animation plot
plt.show()
def input_callback(self, task_idx, event_type, num_samples, callback_data = float):
get_data.values_read = np.zeros((6,1000), dtype= np.float64)
self.reader.read_many_sample(get_data.values_read, num_samples, timeout=constants.WAIT_INFINITELY)
self.inputs.append(get_data.values_read)
#print(get_data.values_read[0,:])
return 0
# function to generate simulated data
def data_gen(self):
"""
TODO: Fix Doc String
"""
self.t = 0
cnt = 0
while True: #cnt < 1000:
#cnt+=1
self.t += 0.1
# self.y1 = self.inputs[0,:] #* np.exp(-self.t/10.)
# self.y2 = self.inputs[1,:] #* np.exp(-self.t/10.)
# self.y3 = np.exp(-self.t/10.)
# adapted the data generator to yield both sin and cos
yield self.t # self.y1, self.y2, self.y3
def animate(self, data):
"""
TODO: Fix Doc String
"""
self.t = data
# self.t, self.y1, self.y2, self.y3 = data
self.y1data.append(get_data.values_read(6)[1])
self.y2data.append(get_data.values_read(6)[2])
self.y3data.append(get_data.values_read(6)[3])
self.time = datetime.datetime.now()
self.target_time_new = self.time + datetime.timedelta(seconds=self.t)
self.x = self.target_time_new
self.xdata.append(self.x)
# axis limits checking. Same as before, just for both axes
for ax in [self.ax1, self.ax2, self.ax3,
self.ax4, self.ax5, self.ax6]:
xmin, xmax = ax.get_xlim()
if self.t >= xmax:
ax.set_xlim(xmin, xmax+datetime.timedelta(seconds=60))
ax.figure.canvas.draw()
# update the data of both line objects
self.line[0].set_data(self.xdata, self.y1data)
self.line[1].set_data(self.xdata, self.y2data)
self.line[2].set_data(self.xdata, self.y3data)
self.line[3].set_data(self.xdata, self.y2data)
self.line[4].set_data(self.xdata, self.y2data)
self.line[5].set_data(self.xdata, self.y2data)
return self.line # self.line,