Right now I'm using Tkinter as a front-end framework to display data coming in through a queue. This data is then drawn to the screen using my draw() function, etc.
The looping part is using Tkinters after function that calls a specific function after n milliseconds. I have a feeling that when calling destroy or just closing the window, this loop halts? Causing some back-end process to not be satisfied.
I've posted the code below, as you'll be missing the abstract class. Removing the (Updater) and the super within init will satisfy it, as the abstract class is more so like an interface.
Replicating the issue: During run-time of the script, if the Tkinter window is closed by any means available before plotting all the data from the queue onto the plot. The script never returns to command line and is forever stuck. This situation is unfavorable as being able to exit the window and expect the process to be destroyed is what I'm seeking.
Further tracking: Upon failing to pass oscy.run(), the program promptly exits but doesn't return to the command line; as it's seen by running and swiftly exiting the program before completion. This begins to hint towards something happening in init?
from __future__ import division
import logging
import atexit
import matplotlib
import numpy as np
matplotlib.use('TkAgg')
from functools import wraps
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from Tkinter import Scale, Button, Tk, TOP, BOTTOM, BOTH, HORIZONTAL
class Oscilloscope():
""" Displays the Oscilloscope provided the data streaming in. """
def __init__(
self,
data_queue,
closed_callback,
title="DRP",
xlabel="Range Cells",
ylabel="Magnitude"
):
"""
Initialization function for the Osc oscilloscope.
:param data_queue: List data representative of magnitudes per update.
:type data_queue: Queue
:param closed_callback: When the figure is closed, callback should be used to remove the figure.
:type closed_callback: Function
:param title: Title of the plot being drawn for the Oscilloscope.
:type title: String
:param xlabel: X-axis of the plot being drawn, should either be Range or Doppler.
:type xlabel: String
:param ylabel: Y-axis of the plot being drawn, should always be Magnitude.
:type ylabel: String
"""
self.data_queue = data_queue
self.closed_callback = closed_callback
self.window = Tk()
atexit.register(self.closed_callback)
self.title = title
self.xlabel = xlabel
self.ylabel = ylabel
self.y_limits = np.array([0, np.finfo(np.float).eps])
def _adjust_ylim_if_req(self, magnitude):
"""
Changes the limits based on magnitudes.
:param magnitude: Size of the magnitude being plotted.
:type magnitude: Float
"""
if magnitude < self.y_limits[0]:
self.y_limits[0] = magnitude
elif magnitude > self.y_limits[1]:
self.y_limits[1] = magnitude
self.ax.set_ylim(self.y_limits[0], self.y_limits[1])
def draw(self):
"""
Draws the main line plot.
"""
try:
magnitudes = self.data_queue.get_nowait()
except:
pass
else:
# Adjust y limits
for magnitude in magnitudes:
self._adjust_ylim_if_req(magnitude)
# Plot the graph
self.ax.cla()
self.ax.set_title(self.title, fontdict={'fontsize': 16, 'fontweight': 'medium'})
self.ax.set_xlabel(self.xlabel, fontdict={'fontsize': 12, 'fontweight': 'medium'})
self.ax.set_ylabel(self.ylabel, fontdict={'fontsize': 12, 'fontweight': 'medium'})
self.ax.plot([n for n in range(len(magnitudes))], magnitudes, '-bo')
def run(self):
"""
Sets up and runs the main logic of the Window
"""
self.plot()
self.updateplot()
self.window.mainloop()
def plot(self):
"""
Creates the initial base plot
"""
figure = matplotlib.figure.Figure()
self.ax = figure.add_subplot(1,1,1)
self.ax.set_title(self.title, fontdict={'fontsize': 16, 'fontweight': 'medium'})
self.ax.set_xlabel(self.xlabel, fontdict={'fontsize': 12, 'fontweight': 'medium'})
self.ax.set_ylabel(self.ylabel, fontdict={'fontsize': 12, 'fontweight': 'medium'})
self.canvas = FigureCanvasTkAgg(figure, master=self.window)
self.canvas.draw()
self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
self.canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)
self.draw()
def close_fig():
self.window.destroy()
self.closed_callback
button = Button(self.window, text='Close', command=close_fig)
button.pack()
def updateplot(self):
"""
Updates the plot by gathering more data from the Queue.
"""
print('Start')
self.draw()
self.canvas.draw()
self.window.after(1, self.updateplot)
print('End')
if __name__ == '__main__':
from threading import Thread
from multiprocessing import Queue
q = Queue()
for i in xrange(100):
l = []
for i in xrange(1000):
l.append(np.random.randint(0, 100))
q.put(l)
def cb():
print('Closed')
oscy = Oscilloscope(q, cb)
oscy.run()