Threading tkinter serial reading function - python

I'm making an application that reads serial data coming from the sensors on an arduino board. Problems arise when trying to use the matplotlib.animation class to make a live graph of said data. The GUI widgets become unresponsive when the plotting is taking place. As far as i've understood, making the serial reading process run on its own thread could potentially solve the issue. I'm having trouble understanding how this could be made so that it is compatible with the FuncAnimation-subclass.
def read_serial_data(port, bauds=9600):
s = serial.Serial(port, bauds)
line = s.readline()[0:-2]
return line
def getPorts():
return [port.device for port in serial.tools.list_ports.comports(include_links=False)]
class GUI():
def __init__(self):
self.root = Tk.Tk()
self._fig = plt.figure()
self.root.title('Measurement Dashboard')
self.root.state('normal')
self.root.config(background='#ffffff')
self._canvas = FigureCanvasTkAgg(self._fig, self.root)
self._canvas.get_tk_widget().grid(column = 1, row = 1)
self._canvas.draw()
self._animate = None
self._ax = self._fig.add_subplot(111)
self._ax.yaxis.grid(True, color = 'black', linestyle='--')
self._ax.xaxis.grid(True, color = 'black', linestyle='--')
self._ax.set_xlabel('time')
self._ax.set_ylabel('CO2')
self.filename = Tk.StringVar()
self.entry = ttk.Entry(self.root, textvariable = self.filename)
self.entry.grid(column = 2, row = 2)
self.info_var = Tk.StringVar()
self.info_entry = ttk.Entry(self.root, textvariable = self.info_var)
self.info_entry.grid(column = 2, row = 3)
self.port = Tk.StringVar()
self.ports = getPorts()
self._cb = ttk.Combobox(self.root, textvariable= self.port, values = self.ports)
self._cb.grid(column = 2, row = 1)
self.start_button = Tk.Button(self.root, text = 'Start', command = self.plot)
self.start_button.grid(column = 1, row = 2)
self.save_button = Tk.Button(self.root, text = 'Save info', command = self.save_info)
self.save_button.grid(column = 2, row = 4)
def save_info(self):
global info
info = self.info_var.get()
def start(self):
self.root.mainloop()
def plot(self):
if self._animate is None:
self.scope = Scope(self._ax, self.filename.get())
self._canvas.draw_idle()
self._animate = animation.FuncAnimation(self._fig, self.scope.animate, frames = self.update, interval=2000, blit=False)
def update(self):
line = read_serial_data(self.port.get())
data = line.decode('utf-8')
yield data
time = datetime.now()
duration = time - start_time
measurement = {'time': time, 'dur': duration.seconds, 'CO2': data, 'info': info}
write_csv_line(self.filename.get(), measurement)
self.root.after(10000, self.update)
if __name__ == "__main__":
gui = GUI()
gui.start()
thread = Thread(target=read_serial_data,args=(gui.port,))
thread.start()

You don't really need another thread but can do this using non-blocking IO on th eserial port and make use of the Tkinter after call to manage the poll interval. pyserial provices inWaiting to test if the device has input waiting to be read. If there are bytes waiting, read just those.
Here is an example reader class to read lines from a serial device and post them to the application handler method once a complete line is read.
class Reader(Serial):
def __init__(self, *args, **kwargs):
self.buffer = ''
super(Reader, self).__init__(*args, **kwargs)
def monitor(self, w, interval=10):
n = self.inWaiting()
if n != 0:
data = self.read(n).decode('ascii')
self.buffer = self.buffer + data
ndx = self.buffer.find("\n")
if ndx != -1:
line = self.buffer[:ndx+1]
self.buffer = self.buffer[ndx+1:]
w.after_idle(lambda: w.parse_input(line))
w.after(interval, lambda: self.monitor(w, interval))
Used like:
app = <tkinter application class instance>
reader = Reader(port, baudrate, timeout=0)
reader.flushInput()
reader.monitor(app, 10)
app.mainloop()
In this case, it will call a parse_input method on the app instance whenever a line is read (delimited by a newline).
If you decide to use a thread, then you need a Queue to pass the data to the Tkinter UI thread and must ensure you don't call Tkinter methods from the worker thread.

Related

VLC causes buffer when embedded in Python Tkinter Frame

I have been working on a music player app that uses VLC to play songs directly from the internet and has features like seeking and a progress bar. But as the normal tkinter progress bar looks kinda old so I used customtkinter.CTkSlider widget but using this causes buffering. The song plays smoothly on using Tk.Scale.
Here, is the code:
# import external libraries
import vlc
import tkinter as Tk
from tkinter import ttk
import pyautogui
import customtkinter
import pafy
# import standard libraries
import os
from threading import Thread, Event
import time
import platform
import requests
class ttkTimer(Thread):
"""a class serving same function as wxTimer... but there may be better ways to do this
"""
def __init__(self, callback, tick):
Thread.__init__(self)
self.callback = callback
self.stopFlag = Event()
self.tick = tick
self.iters = 0
def run(self):
while not self.stopFlag.wait(self.tick):
self.iters += 1
self.callback()
def stop(self):
self.stopFlag.set()
def get(self):
return self.iters
class Player(Tk.Frame):
"""The main window has to deal with events.
"""
def __init__(self, parent, title=None):
Tk.Frame.__init__(self, parent)
self.parent = parent
if title == None:
title = "tk_vlc"
self.parent.title(title)
# Menu Bar
# File Menu
menubar = Tk.Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = Tk.Menu(menubar)
fileMenu.add_command(label="Open", underline=0, command=self.OnOpen)
fileMenu.add_command(label="Exit", underline=1, command=_quit)
menubar.add_cascade(label="File", menu=fileMenu)
# The second panel holds controls
self.player = None
self.videopanel = ttk.Frame(self.parent)
self.canvas = Tk.Canvas(self.videopanel).pack(fill=Tk.BOTH,expand=1)
self.videopanel.pack(fill=Tk.BOTH,expand=1)
ctrlpanel = ttk.Frame(self.parent)
pause = ttk.Button(ctrlpanel, text="Pause", command=self.OnPause)
play = ttk.Button(ctrlpanel, text="Play", command=self.OnPlay)
stop = ttk.Button(ctrlpanel, text="Stop", command=self.OnStop)
volume = ttk.Button(ctrlpanel, text="Volume", command=self.OnSetVolume)
pause.pack(side=Tk.LEFT)
play.pack(side=Tk.LEFT)
stop.pack(side=Tk.LEFT)
volume.pack(side=Tk.LEFT)
self.volume_var = Tk.IntVar()
self.volslider = Tk.Scale(ctrlpanel, variable=self.volume_var, command=self.volume_sel,
from_=0, to=100, orient=Tk.HORIZONTAL, length=100)
self.volslider.pack(side=Tk.LEFT)
ctrlpanel.pack(side=Tk.BOTTOM)
ctrlpanel2 = ttk.Frame(self.parent)
self.scale_var = Tk.DoubleVar()
self.timeslider_last_val = ""
self.timeslider = customtkinter.CTkSlider(ctrlpanel2, variable=self.scale_var, command=self.scale_sel,
from_=0, to=1000, orient=Tk.HORIZONTAL) # This causes buffer
self.timeslider.pack(side=Tk.BOTTOM, fill=Tk.X,expand=1)
self.timeslider_last_update = time.time()
ctrlpanel2.pack(side=Tk.BOTTOM,fill=Tk.X)
# VLC player controls
self.Instance = vlc.Instance()
self.player = self.Instance.media_player_new()
self.timer = ttkTimer(self.OnTimer, 1.0)
self.timer.start()
self.parent.update()
#self.player.set_hwnd(self.GetHandle()) # for windows, OnOpen does does this
def Extract(self,topic):
"""Will play video on following topic, takes about 10 to 15 seconds to load"""
url = 'https://www.youtube.com/results?q=' + topic
count = 0
cont = ''
try:
cont = requests.get(url)
except:
print('Error','Cannot Connect.. Internet not connected or invalid URL or id.')
cont = ''
data = cont.content
data = str(data)
lst = data.split('"')
for i in lst:
count+=1
if i == 'WEB_PAGE_TYPE_WATCH':
break
if lst[count-5] == "/results":
print("Error","No video found.")
return "https://www.youtube.com"+lst[count-5]
def OnExit(self, evt):
"""Closes the window.
"""
self.Close()
def OnOpen(self):
"""Pop up a new dialow window to choose a file, then play the selected file.
"""
# if a file is already running, then stop it.
self.OnStop()
fullname = pafy.new(self.Extract(pyautogui.password(mask="", title="Enter Song Name:", text="Enter Song Name:"))).getbest().url
self.Media = self.Instance.media_new(fullname)
self.player.set_media(self.Media)
# set the window id where to render VLC's video output
if platform.system() == 'Windows':
self.player.set_hwnd(self.GetHandle())
else:
self.player.set_xwindow(self.GetHandle()) # this line messes up windows
# FIXME: this should be made cross-platform
self.OnPlay()
# set the volume slider to the current volume
#self.volslider.SetValue(self.player.audio_get_volume() / 2)
self.volslider.set(self.player.audio_get_volume())
def OnPlay(self):
"""Toggle the status to Play/Pause.
If no file is loaded, open the dialog window.
"""
# check if there is a file to play, otherwise open a
# Tk.FileDialog to select a file
if not self.player.get_media():
self.OnOpen()
else:
# Try to launch the media, if this fails display an error message
if self.player.play() == -1:
self.errorDialog("Unable to play.")
def GetHandle(self):
return self.videopanel.winfo_id()
#def OnPause(self, evt):
def OnPause(self):
"""Pause the player.
"""
self.player.pause()
def OnStop(self):
"""Stop the player.
"""
self.player.stop()
# reset the time slider
self.timeslider.set(0)
def OnTimer(self):
"""Update the time slider according to the current movie time.
"""
if self.player == None:
return
# since the self.player.get_length can change while playing,
# re-set the timeslider to the correct range.
length = self.player.get_length()
dbl = length * 0.001
self.timeslider.config(to=dbl)
# update the time on the slider
tyme = self.player.get_time()
if tyme == -1:
tyme = 0
dbl = tyme * 0.001
self.timeslider_last_val = ("%.0f" % dbl) + ".0"
# don't want to programatically change slider while user is messing with it.
# wait 2 seconds after user lets go of slider
if time.time() > (self.timeslider_last_update + 2.0):
self.timeslider.set(dbl)
def scale_sel(self, evt):
if self.player == None:
return
nval = self.scale_var.get()
sval = str(nval)
if self.timeslider_last_val != sval:
self.timeslider_last_update = time.time()
mval = "%.0f" % (nval * 1000)
self.player.set_time(int(mval)) # expects milliseconds
def volume_sel(self, evt):
if self.player == None:
return
volume = self.volume_var.get()
if volume > 100:
volume = 100
if self.player.audio_set_volume(volume) == -1:
self.errorDialog("Failed to set volume")
def OnToggleVolume(self, evt):
"""Mute/Unmute according to the audio button.
"""
is_mute = self.player.audio_get_mute()
self.player.audio_set_mute(not is_mute)
# update the volume slider;
# since vlc volume range is in [0, 200],
# and our volume slider has range [0, 100], just divide by 2.
self.volume_var.set(self.player.audio_get_volume())
def OnSetVolume(self):
"""Set the volume according to the volume sider.
"""
volume = self.volume_var.get()
# vlc.MediaPlayer.audio_set_volume returns 0 if success, -1 otherwise
if volume > 100:
volume = 100
if self.player.audio_set_volume(volume) == -1:
self.errorDialog("Failed to set volume")
def errorDialog(self, errormessage):
"""Display a simple error dialog.
"""
Tk.tkMessageBox.showerror(self, 'Error', errormessage)
def Tk_get_root():
if not hasattr(Tk_get_root, "root"): #(1)
Tk_get_root.root= Tk.Tk() #initialization call is inside the function
return Tk_get_root.root
def _quit():
print("_quit: bye")
root = Tk_get_root()
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
os._exit(1)
if __name__ == "__main__":
# Create a Tk.App(), which handles the windowing system event loop
root = Tk_get_root()
root.protocol("WM_DELETE_WINDOW", _quit)
player = Player(root, title="tkinter vlc")
# show the player window centred and run the application
root.mainloop()
Kindly help
Regards.

Python Tkinter audio playback GUI play/pause functionality with pyaudio - can't resume from where paused

I am building a small audio GUI for processing ecological recordings. I want to be able to play a file, and pause it, and play it again from where I paused it. I can get it to play, and pause(stop), but when I hit play again it restarts the audio, not picks up from where it left off.
Pyaudio has the callback function which is what I am trying to implement (see here for working example). That example is pretty much what I want, except where this example has the 'keyboard.Listener' line in the while statement controlling the play/pause, I need to implement the play/pause button functionality from tkinter. I have also threaded the playback so that the GUI does not freeze the buttons, which has added some more complexity for me (I am an ecologist self taught in python, not a computer scientist!).
I have played around with threading.Event() for this as a way to control the stream thread, but I think that will just add additional complexity and leave me at the same problem of restarting from pause location.
Eventually I'd also like to pull out the frame number/time of file when paused, and also plot a progress bar on the tkinter canvas/matplot Figure - part of me says the pyaudio .get_time() embedded within the callback may be able to help with this (i think it returns system time).
Below is a minimum example I could make to get a gui working with where Im at.
import tkinter as tk
from tkinter import ttk
import wave
import pyaudio
import threading
import time
import numpy as np
import datetime
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# gui class
class basic_player():
def __init__(self, root):
# BUILD ROOT
self.root = root
root.title('Playback')
self.audio_file = 'C:/Data/Acoustics/Test/5D8CA5E8.WAV'
self.frame_plot()
self.frame_buttons()
# class globals
self.stream_paused = False
def frame_plot(self):
'''Frame for file plot'''
self.frame_plot = ttk.Frame(self.root, height = 100, width = 500)
self.frame_plot.grid(column = 0, row = 0, sticky = 'nsew', columnspan = 2)
self.frame_plot.grid_propagate(False)
# plot file
self.func_plot_fileplot()
def func_plot_fileplot(self):
'''Plot the main audiofile'''
# create figure to contain plot
# get frame size parameters (update frame parameters first)
self.frame_plot.update()
dpi = self.root.winfo_fpixels('1i')
plotwidth = self.frame_plot.winfo_width() / dpi
plotheight = self.frame_plot.winfo_height() / dpi
# create plot
plot_figure_fileplot_main = Figure(figsize = (plotwidth, plotheight),
dpi = dpi, frameon = False, tight_layout = True)
# get data
with wave.open(self.audio_file, mode = 'rb') as wf:
infile_audio_bytes = wf.readframes(wf.getnframes())
data = np.frombuffer(infile_audio_bytes, dtype = np.int16)
# plot x labels
lst_x_ticks = list(range(0, wf.getnframes(), int(wf.getnframes() / 8))) + [wf.getnframes()]
lst_x_label = [str(datetime.timedelta(seconds = int(sample / wf.getframerate()))) for sample in lst_x_ticks]
# add subplot
plot_figure_fileplot = plot_figure_fileplot_main.add_subplot(111)
plot_figure_fileplot.plot(data, linewidth = 0.25)
# adjust subplot visuals
plot_figure_fileplot.set_xmargin(0)
plot_figure_fileplot.yaxis.set_visible(False)
plot_figure_fileplot.spines['top'].set_visible(False)
plot_figure_fileplot.spines['right'].set_visible(False)
plot_figure_fileplot.spines['left'].set_visible(False)
# labels for plot x axis
plot_figure_fileplot.set_xticks(lst_x_ticks) # set x labels to existing to make sure they find the right spot
plot_figure_fileplot.set_xticklabels(lst_x_label, size = 8)
#create tkinter canvas
self.canvas_plot_figure_main = FigureCanvasTkAgg(plot_figure_fileplot_main, master = self.frame_plot)
self.canvas_plot_figure_main.draw()
#place canvas on tkinter window
self.canvas_plot_figure_main.get_tk_widget().grid(sticky = 'nsew')
def frame_buttons(self):
'''The main frame for the initial window'''
frame_buttons = ttk.Frame(self.root, width = 100)
frame_buttons.grid(column = 0, row = 1, sticky = 'nsew')
btn_play = tk.Button(frame_buttons,
text = 'PLAY',
command = self.buttons_command_play,
state = 'normal',
width = 10)
btn_play.grid(column = 0, row = 0, sticky = 'nsew', padx = 10, pady = 10)
btn_pause = tk.Button(frame_buttons,
text = 'PAUSE',
command = self.buttons_command_playback_pause,
state = 'normal',
width = 10)
btn_pause.grid(column = 1, row = 0, sticky = 'nsew', padx = 10, pady = 10)
def buttons_command_play(self):
''' send play audio function to thread '''
self.stream_paused = False
self.stream_thread = threading.Thread(target = self.play_audio)
self.stream_thread.start()
def play_audio(self):
'''Play audio'''
if self.stream_paused: # this doesnt work.
self.stream.start_stream()
else:
# open file
wf = wave.open(self.audio_file, mode = 'rb')
# instantiate pyaudio
self.pyaudio_init = pyaudio.PyAudio()
# define callback
def callback(in_data, frame_count, time_info, status):
data = wf.readframes(frame_count)
return (data, pyaudio.paContinue)
# open stream using callback
self.stream = self.pyaudio_init.open(format=self.pyaudio_init.get_format_from_width(wf.getsampwidth()),
input = False,
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True,
stream_callback=callback)
self.stream.start_stream()
# start the stream
while self.stream.is_active() and not self.stream_paused:
# this is where the control event needs to work i believe
time.sleep(0.1)
# stop stream
self.stream.stop_stream()
self.stream.close()
wf.close()
def buttons_command_playback_pause(self):
''' Pause the audio '''
if not self.stream_paused:
self.stream_paused = True
else:
pass
## SETUP AND RUN
root = tk.Tk()
basic_player(root)
root.mainloop()
stream_callback is unnecessary here. You could instead create a new thread and run the stream.write() in a loop.
To pause the audio set a flag, and add a condition in the loop. write the stream only if the pause condition is False
Here is an example.
import tkinter as tk
import wave
import pyaudio
import threading
class SamplePlayer:
def __init__(self, master):
frame = tk.Frame(master=master)
frame.pack(expand=True, fill="both")
self.current_lbl = tk.Label(master=frame, text="0/0")
self.current_lbl.pack()
self.pause_btn = tk.Button(master=frame, text="Pause", command=self.pause)
self.pause_btn.pack()
self.play_btn = tk.Button(master=frame, text="Play", command=self.play)
self.play_btn.pack()
self.file = r"sample_wavfile.wav"
self.paused = True
self.playing = False
self.audio_length = 0
self.current_sec = 0
self.after_id = None
def start_playing(self):
p = pyaudio.PyAudio()
chunk = 1024
with wave.open(self.file, "rb") as wf:
self.audio_length = wf.getnframes() / float(wf.getframerate())
stream = p.open(format =
p.get_format_from_width(wf.getsampwidth()),
channels = wf.getnchannels(),
rate = wf.getframerate(),
output = True)
data = wf.readframes(chunk)
chunk_total = 0
while data != b"" and self.playing:
if not self.paused:
chunk_total += chunk
stream.write(data)
data = wf.readframes(chunk)
self.current_sec = chunk_total/wf.getframerate()
self.playing=False
stream.close()
p.terminate()
def pause(self):
self.paused = True
if self.after_id:
self.current_lbl.after_cancel(self.after_id)
self.after_id = None
def play(self):
if not self.playing:
self.playing = True
threading.Thread(target=self.start_playing, daemon=True).start()
if self.after_id is None:
self.update_lbl()
self.paused = False
def stop(self):
self.playing = False
if self.after_id:
self.current_lbl.after_cancel(self.after_id)
self.after_id = None
def update_lbl(self):
self.current_lbl.config(text=f"{self.current_sec}/{self.audio_length}")
self.after_id = self.current_lbl.after(5, self.update_lbl)
def handle_close():
player.stop()
root.destroy()
## SETUP AND RUN
root = tk.Tk()
player = SamplePlayer(root)
root.protocol("WM_DELETE_WINDOW", handle_close)
root.mainloop()
This answer is the same as #Art's answer but I removed the self.after_id variable to simplify the logic a bit:
import tkinter as tk
import threading
import pyaudio
import wave
import time
class SamplePlayer:
def __init__(self, master):
frame = tk.Frame(master=master)
frame.pack(expand=True, fill="both")
self.current_lbl = tk.Label(master=frame, text="0/0")
self.current_lbl.pack()
self.pause_btn = tk.Button(master=frame, text="Pause", command=self.pause)
self.pause_btn.pack()
self.play_btn = tk.Button(master=frame, text="Play", command=self.play)
self.play_btn.pack()
# If you aren't going to use `\`s there is no need for the
# "r" before the start of the string
self.file = r"sample_wavfile.wav"
self.paused = True
self.playing = False
self.audio_length = 0
self.current_sec = 0
def start_playing(self):
""" # I don't have `pyaudio` so I used this to test my answer:
self.audio_length = 200
while self.playing:
if not self.paused:
self.current_sec += 1
time.sleep(1)
return None
# """
p = pyaudio.PyAudio()
chunk = 1024
with wave.open(self.file, "rb") as wf:
self.audio_length = wf.getnframes() / float(wf.getframerate())
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
data = wf.readframes(chunk)
chunk_total = 0
while data != b"" and self.playing:
if self.paused:
time.sleep(0.1)
else:
chunk_total += chunk
stream.write(data)
data = wf.readframes(chunk)
self.current_sec = chunk_total/wf.getframerate()
self.playing = False
stream.close()
p.terminate()
def pause(self):
self.paused = True
def play(self):
if not self.playing:
self.playing = True
threading.Thread(target=self.start_playing, daemon=True).start()
if self.paused:
self.paused = False
self.update_lbl()
def stop(self):
self.playing = False
def update_lbl(self):
if self.playing and (not self.paused):
self.current_lbl.config(text=f"{self.current_sec}/{self.audio_length}")
# There is no need to update the label more than 10 times a second.
# It changes once per second anyways.
self.current_lbl.after(100, self.update_lbl)
def handle_close():
player.stop()
root.destroy()
## SETUP AND RUN
root = tk.Tk()
player = SamplePlayer(root)
root.protocol("WM_DELETE_WINDOW", handle_close)
root.mainloop()
There is no need for a self.after_id variable if you can just add if self.playing and (not self.paused) to the code that calls .after. In this case it's the update_lbl method.

Python tkinter Doubling Checkboxes

I'm currently working on a Synthesizer inside Python for a school project and currently have a really troublesome issue. I have a Stack of Checkboxes which mark when a note is played and on which pitch inside a sequencer. My Problem is that whenever I open two Oscillators inside my synthesizer and put in the Values inside the checkboxes, the checkboxes duplicate their value over the multiple windows, but don't do it for the actual sequence, as in I have two lists with correct values, but the values aren't properly displayed inside the window.
To Replicate the Problem hit "new Oscillator" then click on "Arpeggio", do this a second time and click any Checkbox on the Arppeggio Windows
I know this might be a confusing explanation, but I'm going to link the complete code in the bottom so you can try it out and might know what I'm talking about.
The problem occurs inside the "Arpeggio" Class
import numpy # used for Waveform Calculation
from functools import partial # used for Command Combining
from tkinter import * # used for GUI
from tkinter import ttk # used for GUI
from tkinter import filedialog # used for GUI
np = numpy # Simplifying Libraries
tk = ttk # Simplifying Libraries
fd = filedialog # Simplifying Libraries
root = Tk()
#StartupFunction
def StartUp():
print("")
print("Starting Startup")
app = App(root) # Initializing GUI
print("Finished Startup")
main()
("Exiting Startup")
#Main Program Function
def main():
print("Executing Main")
root.mainloop()
print("Finished Main")
return 0
class Oscillator():
pass
class App(tk.Frame):
OscillatorWindowList = []
OscillatorList = []
SoundInputArrayList = []
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
root.title("PySynth")
root.geometry("984x300")
root.resizable(False, True)
root.maxsize(984,720)
btnNewOscillator = Button(root,text="New Oscillator",command=self.NewOscillator,relief=RIDGE,bg="#2d2d2d",fg="white")
btnNewOscillator.place(x = 8, y = 8+128)
def NewOscillator(self):
print("AddingOscillator")
self.OscillatorList.append(Oscillator())
self.SoundInputArrayList.append(SoundInputArray(root,len(self.OscillatorList)-1,len(self.OscillatorList)-1))
print(self.OscillatorList)
self.OscillatorWindowList.append(OscillatorGUI(root,self.OscillatorList[len(self.OscillatorList)-1],len(self.OscillatorList)))
def EXIT(self):
root.destroy()
#$SoundInputArray
class SoundInputArray():
actv = []
CheckbuttonList = []
CheckButtonFreq = []
i=0
ButtonCount = 32
VolumeSlider = None
btnArpeggio = None
Arpeggio = None
hasArpeggio = False
LFO = None
ArpeggioList = [(0,0)]
def __init__(self,master,oscillatorCount,number):
btnArpeggio = Button(master,text="Arpeggio",command=self.OpenArpeggio,relief=RIDGE,bg="#2d2d2d",fg="white")
btnArpeggio.place(x = 8, y = (1+oscillatorCount)*48 +128 )
def OpenArpeggio(self):
if self.Arpeggio == None:
self.Arpeggio = Arpeggio()
def GetArpeggio(self):
return self.Arpeggio
#$Arpeggio
class Arpeggio():
SoundValueList = None
def __init__(self):
GUI = Toplevel(root)
GUI.title("Arpeggio")
GUI.geometry("480x320")
GUI.resizable(False, False)
GUI.configure(bg="#171717")
self.SoundValueList = np.arange(0,16)
self.DrawUI(GUI)
self.ClearList()
def DrawUI(self,frame):
Button(frame,text="display", command= self.PrintSound, width=11,bg="#171717",).place(x = 4, y = 4)
Button(frame,text="empty", command= self.ClearList).place(x = 96, y = 4)
y = 1
x = 1
checkbuttonList = []
for y in range(1,13):
for x in range(0,16):
updatecommand = partial(self.UpdateList,x,y)
checkbuttonList.append(Checkbutton(frame, variable=self.SoundValueList[x], onvalue=y, offvalue=0,command = updatecommand))
checkbuttonList[len(checkbuttonList)-1].place(x = x*24 + 96, y= y*24 + 8)
def ClearList(self):
for i in range(0,16):
self.SoundValueList[i] = 0
def UpdateList(self,x,value):
if (self.SoundValueList[x] == value):
self.SoundValueList[x] = 0
else:
self.SoundValueList[x] = value
self.PrintSound()
def PrintSound(self):
print(self.SoundValueList)
def GetList(self):
print(self.SoundValueList)
return self.SoundValueList
StartUp() # Initiate Program

OpenCV Video with Tkinter and threading Crash

I'm trying to make a multithreaded program with Python, OpenCV, and Tkinter.
My program has some general point.
Load Video from file
Create 2 thread
1st thread to fetch frames from capture object and put it to python Queue
2nd thread to get the frames from Queue
At last, if possible, start and stop the capture object
However, my script seems to behave weirdly. Sometimes it can finish playing video until the end, but sometimes it also crash at some point of the video. Here is what I've got so far.
from Tkinter import Tk, Text
from Tkinter import PhotoImage
from ttk import Frame, Scrollbar, Button, Label
from PIL import Image, ImageTk
import cv
import time
import Queue
import threading
def writeToLog(log, msg):
numlines = log.index('end - 1 line').split('.')[0]
if log.index('end-1c')!='1.0': log.insert('end', '\n')
log.insert('end', msg)
log.see('end')
def GetIplImageMode(img):
orientation = 1 if img.origin == 0 else -1
mode_list = {(1, cv.IPL_DEPTH_8U) : ("L", "L", 1),\
(3, cv.IPL_DEPTH_8U) : ("BGR", "RGB", 3),\
(1, cv.IPL_DEPTH_32F) : ("F", "F", 4)}
key = (img.nChannels, img.depth)
modes = mode_list[key]
return [modes[0], modes[1], orientation]
def IplImage2PIL(img, mode):
return Image.fromstring(mode[1], (img.width, img.height),\
img.tostring(), "raw", mode[0],\
img.width * img.channels,\
mode[2])
def ResizePILImage(pil, width = 260, height = 180):
return pil.resize((width, height), Image.ANTIALIAS)
def PIL2TkImage(pil):
return ImageTk.PhotoImage(pil)
def setImageLabelFromIplImage(label, img_ipl):
mode = GetIplImageMode(img_ipl)
img_pil = IplImage2PIL(img_ipl, mode)
img_resized = ResizePILImage(img_pil)
img_tk = PIL2TkImage(img_resized)
label.configure(image = img_tk)
label.image = img_tk
def setImageLabelFromFile(label, szFileName):
img_ipl = cv.LoadImage(szFileName)
setImageLabelFromIplImage(label, img_ipl)
def mat_from_ipl(ipl):
return cv.GetMat(ipl)
def ipl_from_mat(mat):
ipl = cv.CreateImageHeader((mat.width, mat.height),\
cv.IPL_DEPTH_8U, mat.channels)
cv.SetData(ipl, mat.tostring())
return ipl
class asdf(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack(fill='both', expand=True)
self.parent = parent
self.variables()
self.ui()
def variables(self):
self.ctr = 0
self.fps = 0
self.video = None
self.image = None
self.putProc = None
self.getProc = None
self.isRunning = False
self.queue = Queue.Queue()
def ui(self):
f1 = Frame(self)
frm1 = Frame(f1)
self.lbl1 = Label(frm1, image=None)
setImageLabelFromFile(self.lbl1, '../image.bmp')
self.txt1 = Text(frm1, width=30, height=8)
sb1 = Scrollbar(frm1, orient='vertical', command=self.txt1.yview)
self.txt1.configure(yscrollcommand = sb1.set)
self.lbl1.pack()
self.txt1.pack(side='left')
sb1.pack(side='left', fill='y')
frm1.pack(side='left')
frm2 = Frame(f1)
self.lbl2 = Label(frm2, image=None)
setImageLabelFromFile(self.lbl2, '../image.bmp')
self.txt2 = Text(frm2, width=30, height=8)
sb2 = Scrollbar(frm2, orient='vertical', command=self.txt2.yview)
self.txt2.configure(yscrollcommand = sb2.set)
self.lbl2.pack()
self.txt2.pack(side='left')
sb2.pack(side='left', fill='y')
frm2.pack(side='left')
f1.pack()
f2 = Frame(self)
Button(f2, text='Run', command=self.run).pack(side='left')
Button(f2, text='Stop', command=self.stop).pack(side='left')
f2.pack()
def put_to_queue(self):
while self.isRunning:
self.ctr = self.ctr + 1
self.image = cv.QueryFrame(self.video)
time.sleep(1 / self.fps)
try:
writeToLog(self.txt1, '\nPut to queue .. %d' % (self.ctr))
temp1 = cv.CloneImage(self.image)
setImageLabelFromIplImage(self.lbl1, temp1)
temp2 = mat_from_ipl(temp1)
self.queue.put([self.ctr, temp2])
except:
writeToLog(self.txt1, '\nReach end of video ..')
break
def get_from_queue(self):
while self.isRunning:
from_queue = self.queue.get()
self.ctr_fr = from_queue[0]
if self.ctr_fr == self.ctr: time.sleep(30 / self.fps)
temp1 = ipl_from_mat(from_queue[1])
setImageLabelFromIplImage(self.lbl2, temp1)
writeToLog(self.txt2, '\nGet from queue .. %d' % (self.ctr_fr))
time.sleep(1 / self.fps)
def run(self):
self.isRunning = True
self.video = cv.CreateFileCapture('../video.avi')
self.fps = cv.GetCaptureProperty(self.video, cv.CV_CAP_PROP_FPS)
writeToLog(self.txt1, '\nStart put_queue ..')
self.putProc = threading.Thread(target=self.put_to_queue)
self.putProc.start()
time.sleep(1)
writeToLog(self.txt2, '\nStart get_queue ..')
self.getProc = threading.Thread(target=self.get_from_queue)
self.getProc.start()
def stop(self):
self.isRunning = False
if self.putProc.isAlive():
self.putProc._Thread__stop()
writeToLog(self.txt1, '\nputProc still alive, stopping ..')
self.putProc = None
if self.getProc.isAlive():
self.getProc._Thread__stop()
writeToLog(self.txt2, '\ngetProc still alive, stopping ..')
self.getProc = None
self.ctr_fr = 0
self.ctr = 0
if __name__ == '__main__':
root = Tk()
c = asdf(root)
root.mainloop()
Am I doing it wrong?
Any ideas will be very appreciated.
Thanks

How to open mutiple frames via thread in tkinter?

I tried to open mutiple frames by mutiple threads.
Here is my code.
'''
This is the module for test and studying.
Author:Roger
Date: 2010/10/10
Python version: 2.6.5
'''
import threading, Tkinter
class Application(Tkinter.Frame):
def __init__(self, master=None):
Tkinter.Frame.__init__(self, master)
self.columnconfigure(50)
self.rowconfigure(50)
self.grid()
self.createWidgets()
self.mainloop()
def createWidgets(self):
self.quitButton = Tkinter.Button (self, text='Quit', command=self.quit )
self.quitButton.grid()
class lab_404(threading.Thread):
'''
Is this the doc_string of lab_404?
Can there be mutiple_window?
I do know why is it like this?
Why is the button still on the frame?
'''
myWindow = None
def __init__(self, computer = 10, server = None, table = 1, chair = 1, student = 2, myWindow = None):
threading.Thread.__init__(self)
self.__computer = computer
self.__server = server
self.__table = table
self.__chair = chair
self.__student = student
self.myWindow = Application()
#self.myWindow.mainloop() #mainloop method is here, I don't where to put it.
def getComputer(self):
return self.__computer
def getServer(self):
return self.__server
def getMyWindow(self):
return self.myWindow
def setServer(self, Server):
self.__server = Server
def run(self):
print super(lab_404, self).getName(), 'This thread is starting now!'
print super(lab_404, self).getName(), 'This thread is ending.'
if __name__ == '__main__':
for n in xrange(1, 10, 1):
tn = lab_404(server = n) #Try to make a loop.
tn.start()
The code above has been running as a frame, then stop (mainloop?). It won't continue to the next frame until I close the formmer window. It's fitful.
How could I make it open new frames automatically?
I don't think you can do what you want. Generally speaking, there's no need to run multiple frames and multiple event loops. You can have multiple frames within a single thread and within a single event loop.

Categories