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()
‘’’