Data Acquisition Interval and Slowness

Hello guys. I am pretty new on Python, and I have been doing a project where I am collecting some data from a NI DAQ system, and getting some outputs. Within my code, I have two graphic user interface windows: the first one is to start the data acquisition (after pressing the button, it closes and I go to the second GUI) and the second one has the live outputs I am generating (plus a Figure window where I am able to see the graphs being updated). The issue that I have with my code is that the second GUI window (and the Figure window) appear in my screen pretty slow (it take around 15 - 20 seconds for the Figure window to appear an another 10 - 20 seconds to start recording and updating data). I tried to incorporate a time.sleep function since I want my data to have a uniform interval among recorded points (instead of being random), but this did not work out. At this point, I just want to optimize and make my code more time efficient (aka faster). I will leave my code below to see if you have any suggestions:

‘’’
import os
import time
import openpyxl
import math
import nidaqmx
import matplotlib.pyplot as plt
from datetime import datetime
import tkinter as tk
from tkinter import ttk

class StopDataCollection(Exception):
pass

Constants (you can modify these values if needed)

sampling_and_refresh_interval = 5.0 # Update data every 5.0 seconds
max_time = 5400
voltage_high = 5.0
voltage_low_E = 3.0
voltage_low_O = 0.0
pressure_high = 25.0
pressure_low = 0
max_flowrate = 8.0
min_flowrate = 0.5

Initialize the output task and send 5V signal at the beginning

output_task = nidaqmx.Task()
output_task.ao_channels.add_ao_voltage_chan(‘Dev1/ao0’, ‘Voltage_Regulator’, voltage_low_E, voltage_high)
output_task.write(voltage_high)

Function to control the output voltage

def control_output(value):
output_task.write(value)

Function to read voltage and calculate pressure for both devices

def readdaq():
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan(“Dev1/ai0”) # Edwards pressure voltage channel
voltage_edwards = task.read()

with nidaqmx.Task() as task:
    task.ai_channels.add_ai_voltage_chan("Dev1/ai1")  # Omega pressure voltage channel
    voltage_omega = task.read()

pressure_omega = (12.5 * voltage_omega) - 37.5
pressure_edwards = (5.3652 * voltage_edwards) + 0.0145

return voltage_omega, pressure_omega, voltage_edwards, pressure_edwards

Dictionary to store device-specific values

device_values = {
‘Omega’: {
‘max_voltage’: voltage_high,
‘min_voltage’: voltage_low_O,
‘max_pressure’: pressure_high,
‘min_pressure’: pressure_low,
},
‘Edwards’: {
‘max_voltage’: voltage_high,
‘min_voltage’: voltage_low_E,
‘max_pressure’: pressure_high,
‘min_pressure’: pressure_low,
‘max_flowrate’: max_flowrate,
‘min_flowrate’: min_flowrate,
}
}

Function to get the device-specific values based on the user’s choice

def get_device_specific_values(device_choice):
return device_values[device_choice][‘max_voltage’], device_values[device_choice][‘min_voltage’],
device_values[device_choice][‘max_pressure’], device_values[device_choice][‘min_pressure’]

Function to create or load an Excel sheet for data storage

def create_or_load_excel_sheet():
if os.path.exists(‘output_data.xlsx’):
workbook = openpyxl.load_workbook(‘output_data.xlsx’)
sheet_name = f’Sheet_{datetime.now().strftime(“%Y%m%d_%H%M%S”)}’
data_sheet = workbook.create_sheet(title=sheet_name)
else:
workbook = openpyxl.Workbook()
data_sheet = workbook.active
data_sheet.title = f’Sheet_{datetime.now().strftime(“%Y%m%d_%H%M%S”)}’
return workbook, data_sheet

Function to append data to the Excel sheet

def append_data_to_sheet(data_sheet, timestamp, elapsed_time, pressure_omega, voltage_omega, pressure_edwards, voltage_edwards, volumetric_flow_rate):
data_sheet.append([timestamp, elapsed_time, pressure_omega, voltage_omega, pressure_edwards, voltage_edwards, volumetric_flow_rate])

Function to save the Excel file

def save_excel_file(workbook):
workbook.save(‘output_data.xlsx’)

Function to update the pressure plot

def update_pressure_plot(ax1, time_data, pressure_data_omega, pressure_data_edwards):
ax1.clear()
ax1.plot(time_data, pressure_data_omega, ‘r’, label=‘Pressure Omega’)
ax1.plot(time_data, pressure_data_edwards, ‘b’, label=‘Pressure Edwards’)
ax1.set_title(‘Pressure across Flowmeter’)
ax1.set_xlabel(‘Time (seconds)’)
ax1.set_ylabel(‘Pressure (in H2O)’)
ax1.grid()
ax1.legend()

Function to update the voltage plot

def update_voltage_plot(ax2, time_data, voltage_data_omega, voltage_data_edwards):
ax2.clear()
ax2.plot(time_data, voltage_data_omega, ‘r’, label=‘Voltage Omega’)
ax2.plot(time_data, voltage_data_edwards, ‘b’, label=‘Voltage Edwards’)
ax2.set_title(‘Voltage across Flowmeter’)
ax2.set_xlabel(‘Time (seconds)’)
ax2.set_ylabel(‘Voltage (V)’)
ax2.grid()
ax2.legend()

Function to update the volumetric flow rate plot

def update_flowrate_plot(ax3, time_data, volumetric_flow_rate_data):
ax3.clear()
ax3.plot(time_data, volumetric_flow_rate_data, ‘g’)
ax3.set_title(‘Volumetric Flowrate across Flowmeter’)
ax3.set_xlabel(‘Time (seconds)’)
ax3.set_ylabel(‘Volumetric Flowrate (cfm)’)
ax3.grid()

plt.tight_layout()

Global variables

data_acquisition_running = False
second_gui = None # Initialize second_gui as None

Function to stop data acquisition and quit the root window

def stop_data_acquisition():
global data_acquisition_running, btn_stop, root
data_acquisition_running = False
btn_stop.config(state=tk.DISABLED) # Disable the button to prevent multiple clicks
root.quit()

Function to update the second GUI with elapsed time

def update_second_gui(start_time):
global entry_seconds

# Calculate the elapsed time since the start of data acquisition
current_time = time.time()
elapsed_time = current_time - start_time

# Update the time (in seconds) label in the second GUI
entry_seconds.delete(0, tk.END)
entry_seconds.insert(0, f"{elapsed_time:.2f}")
entry_seconds.grid(row=5, column=1, padx=5, pady=5)

# Schedule the update of the GUI again after 100 milliseconds
second_gui.after(100, update_second_gui, start_time)

Function to calculate volumetric flow rate based on Edwards sensor’s pressure

def calculate_volumetric_flow_rate(pressure_edwards):
volumetric_flow_rate = 0.1038*(pressure_edwards**2) + 0.0034*(pressure_edwards)
return volumetric_flow_rate

Function to update the second GUI with pressure, voltage, and volumetric flow rate values

def update_second_gui_values(voltage_edwards, voltage_omega, pressure_edwards, pressure_omega, volumetric_flow_rate, time_seconds):
global entry_edwards_voltage, entry_omega_voltage, entry_edwards_pressure, entry_omega_pressure, entry_volumetric_flow_rate, entry_seconds

entry_edwards_voltage.delete(0, tk.END)
entry_edwards_voltage.insert(0, f"{voltage_edwards:.3f} V")

entry_omega_voltage.delete(0, tk.END)
entry_omega_voltage.insert(0, f"{voltage_omega:.3f} V")

entry_edwards_pressure.delete(0, tk.END)
entry_edwards_pressure.insert(0, f"{pressure_edwards:.3f} in H2O")

entry_omega_pressure.delete(0, tk.END)
entry_omega_pressure.insert(0, f"{pressure_omega:.3f} in H2O")

entry_volumetric_flow_rate.delete(0, tk.END)
entry_volumetric_flow_rate.insert(0, f"{volumetric_flow_rate:.4f} cfm")

entry_seconds.delete(0, tk.END)
entry_seconds.insert(0, f"{time_seconds:.2f} s")

Main function for data acquisition and plotting

def main():
global root, entry_edwards_voltage, entry_omega_voltage, entry_edwards_pressure, entry_omega_pressure, entry_volumetric_flow_rate, entry_seconds, btn_stop, data_acquisition_running

data_acquisition_running = True
last_refresh_time = time.time()  # Initialize the time for the first refresh
last_acquisition_time = last_refresh_time  # Initialize the time for the first acquisition

workbook = None  # Initialize workbook outside the try-except block

try:
    print("Data acquisition started.")  # Debug print
    max_voltage, min_voltage, max_pressure, min_pressure = get_device_specific_values('Omega')
    workbook, data_sheet = create_or_load_excel_sheet()

    data_sheet.append(['Timestamp', 'Time (seconds)', 'Pressure Omega (in H2O)', 'Voltage Omega (V)',
                       'Pressure Edwards (in H2O)', 'Voltage Edwards (V)', 'Volumetric Flowrate (cfm)'])

    time_data = []
    pressure_data_omega = []
    pressure_data_edwards = []
    voltage_data_omega = []
    voltage_data_edwards = []
    volumetric_flow_rate_data = []

    # Update the figsize parameter to make the plots larger
    fig, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex=True, figsize=(9, 9))

    start_time = time.time()

    while data_acquisition_running:
        current_time = time.time()

        # Check if we have reached the max_time
        elapsed_time = current_time - start_time
        if elapsed_time >= max_time:
            print("Reached max_time.")
            data_acquisition_running = False
            break

        if not plt.get_fignums():
            print("Figure window closed. Stopping.")
            data_acquisition_running = False
            break

        # Only acquire data and update plots every 5 seconds
        if current_time - last_acquisition_time >= sampling_and_refresh_interval:
            voltage_omega, pressure_omega, voltage_edwards, pressure_edwards = readdaq()

            timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]

            if voltage_omega < min_voltage or voltage_omega > max_voltage:
                control_output(0.0)
                print("Omega Voltage out of range. Stopping.")
                data_acquisition_running = False
                break

            if pressure_omega < min_pressure or pressure_omega > max_pressure:
                control_output(0.0)
                print("Omega Pressure out of range. Stopping.")
                data_acquisition_running = False
                break

            if voltage_edwards < min_voltage or voltage_edwards > max_voltage:
                control_output(0.0)
                print("Edwards Voltage out of range. Stopping.")
                data_acquisition_running = False
                break

            if pressure_edwards < min_pressure or pressure_edwards > max_pressure:
                control_output(0.0)
                print("Edwards Pressure out of range. Stopping.")
                data_acquisition_running = False
                break

            volumetric_flow_rate = calculate_volumetric_flow_rate(pressure_edwards)

            # Check volumetric flow rate range for the selected device
            if volumetric_flow_rate < min_flowrate or volumetric_flow_rate > max_flowrate:
                control_output(0.0)
                print("Volumetric Flow Rate out of range. Stopping.")
                data_acquisition_running = False
                break

            append_data_to_sheet(data_sheet, timestamp, elapsed_time, pressure_omega, voltage_omega, pressure_edwards, voltage_edwards, volumetric_flow_rate)
            save_excel_file(workbook)

            time_data.append(elapsed_time)
            pressure_data_omega.append(pressure_omega)
            pressure_data_edwards.append(pressure_edwards)
            voltage_data_omega.append(voltage_omega)
            voltage_data_edwards.append(voltage_edwards)
            volumetric_flow_rate_data.append(volumetric_flow_rate)

            last_acquisition_time = current_time  # Update the last acquisition time

        # Update plots and refresh the GUI every 5 seconds
        if current_time - last_refresh_time >= sampling_and_refresh_interval:
            update_pressure_plot(ax1, time_data, pressure_data_omega, pressure_data_edwards)
            update_voltage_plot(ax2, time_data, voltage_data_omega, voltage_data_edwards)
            update_flowrate_plot(ax3, time_data, volumetric_flow_rate_data)

            root.update()
            plt.pause(0.01)  # Small pause to allow GUI updates

            last_refresh_time = current_time  # Update the last refresh time

        print("Timestamp:", timestamp)
        print("T =", round(elapsed_time, 2), "(s)")
        print("Pressure Omega =", round(pressure_omega, 3), "(in H2O)")
        print("Voltage Omega =", round(voltage_omega, 3), "(V)")
        print("Pressure Edwards =", round(pressure_edwards, 3), "(in H2O)")
        print("Voltage Edwards =", round(voltage_edwards, 3), "(V)")
        print("Volumetric Flow Rate =", round(volumetric_flow_rate, 3), "(cfm)")

        second_gui.after(100, update_second_gui_values, voltage_edwards, voltage_omega, pressure_edwards, pressure_omega, volumetric_flow_rate, elapsed_time)

    print("Data acquisition loop ended.")  # Debug print

except KeyboardInterrupt:
    print("KeyboardInterrupt: Stopping the data collection.")
except StopDataCollection:
    print("Data collection stopped.")
except Exception as e:
    print("An error occurred:", str(e))

finally:
    control_output(0.0)
    if output_task is not None:
        output_task.close()
    if workbook is not None:
        save_excel_file(workbook)  # Save the workbook before closing
        workbook.close()  # Close the workbook
    print("Data collection stopped.")  # Debug print
    if second_gui:
        second_gui.destroy()

    os._exit(0)

Function to create the second GUI window

def create_second_gui():
global root, entry_edwards_voltage, entry_omega_voltage, entry_edwards_pressure, entry_omega_pressure, entry_volumetric_flow_rate, entry_seconds, btn_stop, second_gui

print("Creating second GUI")  # Debug print

root.withdraw()

second_gui = tk.Tk()
second_gui.title("Data Acquisition")
second_gui.geometry("500x300")  # Set the size to 500x300

lbl_edwards_voltage = ttk.Label(second_gui, text="Edwards Voltage:")
lbl_edwards_voltage.grid(row=0, column=0, padx=5, pady=5)

entry_edwards_voltage = ttk.Entry(second_gui)
entry_edwards_voltage.grid(row=0, column=1, padx=5, pady=5)

lbl_omega_voltage = ttk.Label(second_gui, text="Omega Voltage:")
lbl_omega_voltage.grid(row=1, column=0, padx=5, pady=5)

entry_omega_voltage = ttk.Entry(second_gui)
entry_omega_voltage.grid(row=1, column=1, padx=5, pady=5)

lbl_edwards_pressure = ttk.Label(second_gui, text="Edwards Pressure:")
lbl_edwards_pressure.grid(row=2, column=0, padx=5, pady=5)

entry_edwards_pressure = ttk.Entry(second_gui)
entry_edwards_pressure.grid(row=2, column=1, padx=5, pady=5)

lbl_omega_pressure = ttk.Label(second_gui, text="Omega Pressure:")
lbl_omega_pressure.grid(row=3, column=0, padx=5, pady=5)

entry_omega_pressure = ttk.Entry(second_gui)
entry_omega_pressure.grid(row=3, column=1, padx=5, pady=5)

lbl_volumetric_flow_rate = ttk.Label(second_gui, text="Volumetric Flow Rate:")
lbl_volumetric_flow_rate.grid(row=4, column=0, padx=5, pady=5)

entry_volumetric_flow_rate = ttk.Entry(second_gui)
entry_volumetric_flow_rate.grid(row=4, column=1, padx=5, pady=5)

lbl_seconds = ttk.Label(second_gui, text="Time:")
lbl_seconds.grid(row=5, column=0, padx=5, pady=5)

entry_seconds = ttk.Entry(second_gui)
entry_seconds.grid(row=5, column=1, padx=5, pady=5)

# Add the "Stop Data Acquisition" button
btn_stop = ttk.Button(second_gui, text="Stop Data Acquisition", command=stop_data_acquisition)
btn_stop.grid(row=6, column=0, columnspan=2, padx=5, pady=5)

# Handle the WM_DELETE_WINDOW event of the second GUI window
second_gui.protocol("WM_DELETE_WINDOW", stop_data_acquisition)

second_gui.after(100, main)  # Start the data acquisition

second_gui.after(100, update_second_gui, time.time())  # Start the time update loop

second_gui.mainloop()

Function to start data acquisition

def start_data_acquisition():
global root
print(“Starting data acquisition”) # Debug print
create_second_gui()

First GUI window to choose the pressure device

root = tk.Tk()
root.title(“Data Acquisition”)
root.geometry(“400x150”)

label_instruction = ttk.Label(root, text=“Click the button to start data acquisition.”)
label_instruction.pack()

Handle the WM_DELETE_WINDOW event of the first GUI window

root.protocol(“WM_DELETE_WINDOW”, stop_data_acquisition)

Add the “Start Data Acquisition” button

btn_start_acquisition = ttk.Button(root, text=“Start Data Acquisition”, command=start_data_acquisition)
btn_start_acquisition.pack()

root.mainloop()
‘’’

Did you try removing parts of the functionality, to try to figure out which parts are causing the slowdown?

No. The only thing I tried out was to stop printing the output values in each iteration. How can I do what you are suggesting?