-1

I am trying to run an infinite task (until killed) in a tkinter GUI. I have attempted to use threads to stop the GUI freezing and infinitely hanging after executing this task. My expectation was that threading would stop the GUI (mainloop) hanging but this is not the case. My code is attached. Any ideas? There is only one line in the code I call the threading function.

Regards,

Jordan.

import nidaqmx.system
import nidaqmx
import numpy as np
import matplotlib.pyplot as plt
#from nidaqmx.stream_readers import AnalogMultiChannelReader, DigitalMultiChannelReader
#from nidaqmx.stream_writers import AnalogSingleChannelWriter
Volts = nidaqmx.constants.VoltageUnits.VOLTS
system = nidaqmx.system.System.local()
system.driver_version
from nidaqmx import stream_writers
#from nidaqmx.constants import LineGrouping
#from nidaqmx.constants import TerminalConfiguration
import time
import os
from PIL import Image
import tkinter as tk
from PIL import ImageTk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import threading


dirpath = os.getcwd()
os.chdir(dirpath)
filename = r'Cutera_2.jpg'
img = Image.open(filename)
img.save('Cutera_logo.ico')
logo = dirpath + '\Cutera_logo.ico'
Astrum = dirpath + '\Astrum_board.png' 

img = Image.open(Astrum) # image extension *.png,*.jpg
width, height = img.size
enlarge = 1.75
new_width  = int(width*enlarge)
new_height = int(height*enlarge)
img = img.resize((new_width, new_height), Image.ANTIALIAS)
img.save('Astrum2.png')
Astrum2 = dirpath + '\Astrum2.png' 


system = nidaqmx.system.System.local()
print(system.driver_version)
for device in system.devices:
    dev = str(device).split('=')[1].split(')')[0]
    print(device)
    print(dev)

sample_clock = '/'+dev+'/ai/SampleClock'

def arm_laser(dev,state):
    if state == 'on':
        state = True
    elif state == 'off':
        state = False
    with nidaqmx.Task("arm") as arm:
        arm.do_channels.add_do_chan(dev+"/port1/line0")
        arm.write(state, auto_start=True)


def current_to_voltage(current): #gives command voltage for desired current 50mV = 1A
    voltage = current/20
    return float(voltage)


#fire_astrum(dev,ana_out,ana_in,N,pulse_count,pulse_on,pulse_off,command,mode)

def fire_single_shot(dev,channel,pulse_duration,N,command):
    channel = dev +'/' + channel
    
    arm_laser(dev,'on') # Sends 5V to Astrum to arm laser diode
    time.sleep(0.2) # wait for 100ms before data stream
    
    with nidaqmx.Task() as task:
        samples = np.append(command*np.ones(1),np.zeros(1))
        task.ao_channels.add_ao_voltage_chan(channel)
        task.timing.cfg_samp_clk_timing(rate=1/pulse_duration*1000, sample_mode= nidaqmx.constants.AcquisitionType.FINITE, samps_per_chan = N)
        Writer = stream_writers.AnalogSingleChannelWriter(task.out_stream, auto_start=True)
        Writer.write_many_sample(samples)
        task.wait_until_done(timeout=10)
        time_vec = np.linspace(0, (pulse_duration+pulse_duration)*int(1), num=N, endpoint=True)
        print('length of time vec = ' + str(len(time_vec)))
        print('length of trigger = ' + str(len(samples)))
        return time_vec, samples
    
    arm_laser(dev,'off')

def fire_burst(dev,channel,pulse_on,pulse_off,pulse_count,N,command):
    channel = dev +'/' + channel
    
    arm_laser(dev,'on') # Sends 5V to Astrum to arm laser diode
    time.sleep(0.2) # wait for 100ms before data stream
    
    with nidaqmx.Task() as task:
        duty = pulse_on/(pulse_on+pulse_off) # duty cycle in %
        #N = 2**8 # no of points in stream array (8-bit)
        array_on = int(N*duty) # on values in array
        array_off = int(N-array_on) # off values in array
        samples = np.append(command*np.ones(array_on),np.zeros(array_off))
        task.ao_channels.add_ao_voltage_chan(channel)
        task.timing.cfg_samp_clk_timing(rate=(array_on/N)*1/pulse_on*1000*N, sample_mode= nidaqmx.constants.AcquisitionType.FINITE , samps_per_chan= pulse_count*len(samples))
        Writer = stream_writers.AnalogSingleChannelWriter(task.out_stream, auto_start=True)
        Writer.write_many_sample(samples)
        task.wait_until_done(timeout=60) # Alternative command lokk at API
        trigger = np.tile(samples,pulse_count)
        time_vec = np.linspace(0, (pulse_on+pulse_off)*pulse_count, num=len(trigger), endpoint=True)
        print('length of time vec = ' + str(len(time_vec)))
        print('length of trigger = ' + str(len(trigger)))
        return time_vec, trigger
    
    arm_laser(dev,'off')

def fire_continuous(dev,channel,pulse_on,pulse_off,N,command):
    channel = dev +'/' + channel
    
    arm_laser(dev,'on') # Sends 5V to Astrum to arm laser diode
    time.sleep(0.2) # wait for 100ms before data stream
    
    with nidaqmx.Task() as task:
        duty = pulse_on/(pulse_on+pulse_off) # duty cycle in %
        #N = 2**8 # no of points in stream array (8-bit)
        array_on = int(N*duty) # on values in array
        array_off = int(N-array_on) # off values in array
        samples = np.append(command*np.ones(array_on),np.zeros(array_off))
        task.ao_channels.add_ao_voltage_chan(channel)
        task.timing.cfg_samp_clk_timing(rate=(array_on/N)*1/pulse_on*1000*N, sample_mode= nidaqmx.constants.AcquisitionType.CONTINUOUS)
        Writer = stream_writers.AnalogSingleChannelWriter(task.out_stream, auto_start=True)
        Writer.write_many_sample(samples)
        task.wait_until_done(timeout=nidaqmx.constants.WAIT_INFINITELY) # Alternative command lokk at API
 
    

#def fire_continuous(dev,channel,pulse_on,pulse_off,N,command): # Fire continuous has to be threaded
#    channel = dev +'/' + channel
#    
#    arm_laser(dev,'on') # Sends 5V to Astrum to arm laser diode
#    time.sleep(0.2) # wait for 100ms before data stream
#    
#    task = nidaqmx.Task()
#    duty = pulse_on/(pulse_on+pulse_off) # duty cycle in %
#    #N = 2**8 # no of points in stream array
#    array_on = int(N*duty) # on values in array
#    array_off = int(N-array_on) # off values in array
#    samples = np.append(command*np.ones(array_on),np.zeros(array_off))
#    task.ao_channels.add_ao_voltage_chan(channel)
#    task.timing.cfg_samp_clk_timing(rate=(array_on/N)*1/pulse_on*1000*N, source=sample_clock, sample_mode= nidaqmx.constants.AcquisitionType.CONTINUOUS)
#    Writer = stream_writers.AnalogSingleChannelWriter(task.out_stream, auto_start=True)
#    Writer.write_many_sample(samples)
##    task.start()


#root = tk.Toplevel
window = tk.Tk()
window.resizable(width=True, height=True)
#window.withdraw()
window.iconbitmap(logo)
window.configure(background='white')
window.geometry("1700x1500") # This sets the Window size to work with
#window.geometry("600x500") # This sets the Window size to work with
window.title('Astrum laser pulser')
defaultbg = window.cget('bg')
img = ImageTk.PhotoImage(Image.open(Astrum))
panel = tk.Label(window, image = img,background="white")
panel.place(relx=.7, rely=.65)


fig = plt.Figure(figsize=(5,4), dpi=100)
fig.tight_layout(True)
ax1 = fig.add_subplot(111)
canvas = FigureCanvasTkAgg(fig, window)
fig.canvas.draw()
canvas.get_tk_widget().place(relx=0.55, rely=0.35, anchor=tk.W)

trig, = ax1.plot([],[], color = 'g')
ax1.set_title('Pulse trigger')
ax1.set(xlabel='time (ms)', ylabel='current (A)')
ax1.grid(linewidth=0.7, linestyle=':')


def update_plot(x,y):
    trig.set_data(x,y)
    ax1.set_xlim(0, max(x))
    ax1.set_ylim(0, 1.1*max(y))
    fig.canvas.draw()
    fig.canvas.flush_events()
    

def get_trigger(pulse_count,pulse_on,pulse_off,N,command):
    duty = pulse_on/(pulse_on+pulse_off) # duty cycle in %
    array_on = int(N*duty) # on values in array
    array_off = int(N-array_on)
    samples = np.append(command*np.ones(array_on),np.zeros(array_off))
    trigger = np.tile(samples,pulse_count)
    return trigger

l = tk.Label(window, bg='white', fg='black', width=20, text='Diode current = 0.0A')
l.pack()
  
def print_selection(v):
    l.config(text='Diode current = ' + v + "A")
    global variable
    #print(v)
    variable = v

def savecurrent():
    print("current selcted was = " + variable + "A")
    return float(variable)
    
s = tk.Scale(window, label='Diode current (A)', from_=0, to=70, orient=tk.HORIZONTAL, length=1400, showvalue=2,tickinterval=5, resolution=0.5, command=print_selection)
s.pack()

def selected():
    print(var.get())


def fire_laser():
    #global final
    print(var.get(),get_pulse_on(),get_pulse_off(),get_pulse_count(),get_trig_delay())
    print("Enter function to get configuration, write configuration then fire laser...")
    diode_curr = savecurrent()
    print(diode_curr)
  
    
    if var.get() == 'SINGLE SHOT':
        data = fire_burst(dev,'ao0',float(get_pulse_on()),float(get_pulse_off()),int(1),1000,current_to_voltage(diode_curr))
        mode = get_trigger(int(1),float(get_pulse_on()),float(get_pulse_off()),1000,float(diode_curr))
        t = data[0]
        #mode = data[1]
        print('single shot fired')
   
    elif var.get() == 'CONTINUOUS':    
        #fire_continuous(dev,channel,pulse_on,pulse_off,N,command)
        mode = get_trigger(int(get_pulse_count()),float(get_pulse_on()),float(get_pulse_off()),1000,float(diode_curr)) 
        threading.Thread(target=fire_continuous(dev,'ao0',float(get_pulse_on()),float(get_pulse_off()),1000,current_to_voltage(diode_curr))).start()
        t = np.linspace(0, (float(get_pulse_on())+float(get_pulse_off()))*int(get_pulse_count()), num=len(mode), endpoint=True)
        #t = data[0]
        print('continuous pulse modulation')
    
    elif var.get() == 'PULSE TRAIN':    
        data = fire_burst(dev,'ao0',float(get_pulse_on()),float(get_pulse_off()),int(get_pulse_count()),1000,current_to_voltage(diode_curr))
        mode = get_trigger(int(get_pulse_count()),float(get_pulse_on()),float(get_pulse_off()),1000,float(diode_curr))
        t = data[0]
        #mode = data[1]
        print('A burst of ' + str(int(get_pulse_count())) + ' pulses fired')
    
    update_plot(t,mode)
    #final = [var.get(),var1.get(),var2.get(),get_noise_scan(),get_power_scan(), get_delay(), get_cycle(), get_dt(), get_temp(), get_step()]
#    window.quit()  
#    window.destroy()
#    return final

def stop_laser():
    #global final
    #fire_astrum(dev,'ao0','ai0:1',1000,int(1),float(get_pulse_on()),float(get_pulse_off()),current_to_voltage(0),'train')
    print("Enter function here to kill the laser")
    look = get_trigger(int(1),float(get_pulse_on()),float(get_pulse_off()),1000,float(0))
    t = list(range(len(look)))
    print('laser stopped')
    update_plot(t,look)
    arm_laser(dev,'off')
#    final = [var.get(),var1.get(),var2.get(),get_noise_scan(),get_power_scan(), get_delay(), get_cycle(), get_dt(), get_temp(), get_step()]
#    window.quit()  
#    window.destroy()
#    return final

def get_pulse_on():
    on = entry1.get()
    print(on)
    return on

def get_pulse_off():
    off = entry2.get()
    print(off)
    return off

def get_pulse_count():
    count = entry3.get()
    print(count)
    return count

def get_trig_delay():
    delay = entry4.get()
    print(delay)
    return delay

def get_power_scan():
    p = entry4.get()
    print(p)
    return p
    
#    if var1.get() == "BRF" or var.get() == "single" or (var1.get() == "BRF" and var2.get() == "BRF"):
#        brf.config(state=tk.DISABLED, selectcolor = "snow")
#        
#    elif var1.get() == "SHG" or var1.get() == "THG" or var1.get() == "ETA" or var1.get() == "MAIN TEC":    
#        brf.config(state=tk.ACTIVE, selectcolor = "peach puff")
#    
#    if var1.get() == "SHG" or var.get() == "single" or (var1.get() == "SHG" and var2.get() == "SHG"):
#        shg.config(state=tk.DISABLED, selectcolor = "snow")
#       
#    elif var1.get() == "BRF" or var1.get() == "THG" or var1.get() == "ETA" or var1.get() == "MAIN TEC":    
#        shg.config(state=tk.ACTIVE, selectcolor = "spring green")
#    
#    if var1.get() == "THG" or var.get() == "single" or (var1.get() == "THG" and var2.get() == "THG"):
#        thg.config(state=tk.DISABLED, selectcolor = "snow")
#       
#    elif var1.get() == "BRF" or var1.get() == "SHG" or var1.get() == "ETA" or var1.get() == "MAIN TEC":    
#        thg.config(state=tk.ACTIVE, selectcolor = "thistle1")
#    
#    if var1.get() == "ETA" or var.get() == "single" or (var1.get() == "ETA" and var2.get() == "ETA"):
#        eta.config(state=tk.DISABLED, selectcolor = "snow")
#      
#    elif var1.get() == "BRF" or var1.get() == "SHG" or var1.get() == "THG" or var1.get() == "MAIN TEC":    
#        eta.config(state=tk.ACTIVE, selectcolor = "sky blue")
#    
#    if var1.get() == "MAIN TEC" or var.get() == "single" or (var1.get() == "MAIN TEC" and var2.get() == "MAIN TEC"):
#        maint.config(state=tk.DISABLED, selectcolor = "snow")
#       
#    elif var1.get() == "BRF" or var1.get() == "SHG" or var1.get() == "THG" or var1.get() == "ETA":    
#        maint.config(state=tk.ACTIVE, selectcolor = "salmon")      

############
trigg = tk.StringVar()
check1 = tk.Checkbutton(window, text='Trigger', 
        command=get_power_scan, variable = trigg,
        onvalue="Yes", offvalue="No")
#
check1.place(x=200,y=100+15*20*2)
check1.deselect()
#############

mode = ["SINGLE SHOT", "CONTINUOUS", "PULSE TRAIN"]
#get_scan
#  button = tk.Button(window, font="Heltavica",text ="PROCEED", command=get_scan)
#    button.config(bd=8, font="Ariel", justify="center")
#    button.place(relx=.39, rely=0.9)
#used to get the 'value' property of a tkinter.Radiobutton
var = tk.StringVar() #MODE
var2 = tk.StringVar() #FIRE
var3 = tk.StringVar() #STOP

fire = tk.Button(window, width=8 , bd=4, text="FIRE", command = fire_laser, activebackground="red")#, onvalue=1, offvalue=0)
fire.place(x=200,y=100+15*35*1)
#fire.deselect()
#fire.config(selectcolor = "snow")
#fire.pack()
#fire.update()

stop = tk.Button(window, width=8 , bd=4, text="STOP", command = stop_laser, activebackground="green")#, onvalue=1, offvalue=0)
stop.place(x=600,y=100+15*35*1)
#stop.deselect()
#stop.config(selectcolor = "snow")
#stop.pack()
#stop.update()   
           
ss = tk.Radiobutton(window, selectcolor = "peach puff", width=12, bd=4, text="SINGLE SHOT", variable=var, value = mode[0], command = selected , indicatoron = 0)#, onvalue=1, offvalue=0)
ss.place(x=200,y=100+15*5*1)
ss.select()
#ss.config(selectcolor = "snow")
#ss.pack
ss.update()

c = tk.Radiobutton(window, selectcolor = "spring green", width=12 , bd=4, text="CONTINUOUS", variable=var, value = mode[1], command = selected , indicatoron = 0)#, onvalue=1, offvalue=0)
c.place(x=200,y=100+15*10*1)        
c.deselect()
#c.config(selectcolor = "snow")
#c.pack
c.update()

pt = tk.Radiobutton(window, selectcolor = "thistle1" , width=12 , bd=4, text="PULSE TRAIN", variable=var, value = mode[2], command = selected , indicatoron = 0)#, onvalue=1, offvalue=0)
pt.place(x=200,y=100+15*15*1)
pt.deselect()
#pt.config(selectcolor = "snow")
#pt.pack
pt.update()

entry1 = tk.Entry(window, width=4)
entry1.insert(0, "40")
entry1.place(x=500,y=30+100+15*5*1)
entry1.config(bd=2, font="Ariel", justify="center",state=tk.NORMAL)

entry2 = tk.Entry(window, width=4)
entry2.insert(0, "15")
entry2.place(x=500,y=30+100+15*10*1)
entry2.config(bd=2, font="Ariel", justify="center",state=tk.NORMAL)

entry3 = tk.Entry(window, width=4)
entry3.insert(0, "10")
entry3.place(x=500,y=30+100+15*15*1)
entry3.config(bd=2, font="Ariel", justify="center",state=tk.NORMAL)

entry4 = tk.Entry(window, width=4)
entry4.insert(0, "100")
entry4.place(x=500,y=30+100+15*20*1)
entry4.config(bd=2, font="Ariel", justify="center",state=tk.NORMAL)

L = tk.Label(window, text = "Pulse on (ms) ", font="Ariel", width=22, height = 1 , background = defaultbg)
L.place(x=400,y=100+15*5*1)

L2 = tk.Label(window, text = "Pulse off (ms) " , font="Ariel", width=22, height = 1 , background = defaultbg)
L2.place(x=400,y=100+15*10*1)

L3 = tk.Label(window, text = "Pulse count " , font="Ariel", width=22, height = 1 , background = defaultbg)
L3.place(x=400,y=100+15*15*1)

L4 = tk.Label(window, text = "Delay (ms) " , font="Ariel", width=22, height = 1 , background = defaultbg)
L4.place(x=400,y=100+15*20*1)


window.mainloop()
  • You seem to have posted more code than what would be reasonable for your issue. Please read [ask] and how to make a [mre]; providing a MRE helps users answer your question and future users relate to your issue. – rizerphe Jun 25 '20 at 08:29
  • `target=fire_continuous(dev,'ao0',float(get_pulse_on())` _immediately_ runs the function in the main thread. This is a very common problem. You're the second person to ask this today. See https://stackoverflow.com/q/62571697/7432 – Bryan Oakley Jun 25 '20 at 13:46

1 Answers1

0

You want to replace:

threading.Thread(target=fire_continuous(dev,channel,pulse_on,pulse_off,N,command)).start()

with (the actual arguments, not these placeholders)

threading.Thread(target=fire_continuous, args=(dev,channel,pulse_on,pulse_off,N,command)).start()

Notice the args= parameter in the second version.

Calling fire_continuous with the parentheses as it is in the first version calls the function immediately (inside the main thread). the target parameter of threading.Thread takes a callable (yet to be called)

Tresdon
  • 1,211
  • 8
  • 18