0

I'm running a Tkinter script that updates a plot every 5 seconds. It calls the function that plots it every 5 seconds. After not that long python starts using a lot of memory, I checked in task manager. The memory usage keeps increasing really fast. It starts a new file every 24 hours so there is a limit to the number of lines in the file. The file starts empty.

I tried increasing the 5s time span but it does the same thing. Maybe a little slower, also tried tried plotting every 3 rows or so but the same thing happened again.

Any idea what is causing such high memory usage and how to fix?

Thanks!

data = np.genfromtxt(filename)

time_data = data[:,0]
room_temp_data_celsius = data[:,1]
rad_temp_data_celsius = data[:,2]
fan_state_data = data[:,3]
threshold_data = data[:,4]
hysteresis_data = data[:,5]

threshold_up = [] #empty array
threshold_down = []#empty array

for i in range(0,len(threshold_data)):
    threshold_up.append(threshold_data[i]+hysteresis_data[i])
    threshold_down.append(threshold_data[i]-hysteresis_data[i])

# Time formatting
dts = map(datetime.datetime.fromtimestamp, time_data)

fds = matplotlib.dates.date2num(dts)

hfmt = matplotlib.dates.DateFormatter('%H:%M')

# Temperature conversion
room_temp_data_fahrenheit = map(celsius_to_fahrenheit, room_temp_data_celsius)
rad_temp_data_fahrenheit = map(celsius_to_fahrenheit, rad_temp_data_celsius)
threshold_data_fahrenheit = map(celsius_to_fahrenheit, threshold_data)
threshold_up_fahrenheit = map(celsius_to_fahrenheit, threshold_up)
threshold_down_fahrenheit = map(celsius_to_fahrenheit, threshold_down)


f = plt.figure()
a = f.add_subplot(111)

a.plot(fds,room_temp_data_fahrenheit, fds, rad_temp_data_fahrenheit, 'r')
a.plot(fds,fan_state_data*(max(rad_temp_data_fahrenheit)+4),'g_')
a.plot(fds, threshold_up_fahrenheit, 'y--') 
a.plot(fds, threshold_down_fahrenheit, 'y--')

plt.xlabel('Time (min)')
plt.ylabel('Temperature '+unichr(176)+'F')
plt.legend(["Room Temperature","Radiator","Fan State","Threshold Region"], loc="upper center", ncol=2)
plt.ylim([min(room_temp_data_fahrenheit)-5, max(rad_temp_data_fahrenheit)+5])
plt.grid()


a.xaxis.set_major_formatter(hfmt)


data_graph = FigureCanvasTkAgg(f, master=root)
data_graph.show()
data_graph.get_tk_widget().grid(row=6,column=0, columnspan=3)    
root.after(WAIT_TIME, control)
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
rigs
  • 101
  • 4
  • Fixed this. I was not running Tkinter as part of a class and that was doing it. I wrote another script where it runs the GUI as an object similar to what Unutbu wrote and it's working fine. – rigs Mar 14 '13 at 03:44

1 Answers1

0

It's not clear to me from your code how your plots are changing with time. So I don't have any specific suggestion for your existing code. However, here is a basic example of how to embed an animated matplotlib figure in a Tkinter app. Once you grok how it works, you should be able to adapt it to your situation.

import matplotlib.pyplot as plt
import numpy as np
import Tkinter as tk
import matplotlib.figure as mplfig
import matplotlib.backends.backend_tkagg as tkagg
pi = np.pi
sin = np.sin

class App(object):
    def __init__(self, master):
        self.master = master
        self.fig = mplfig.Figure(figsize = (5, 4), dpi = 100)
        self.ax = self.fig.add_subplot(111)
        self.canvas = canvas = tkagg.FigureCanvasTkAgg(self.fig, master)
        canvas.get_tk_widget().pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
        self.toolbar = toolbar = tkagg.NavigationToolbar2TkAgg(canvas, master)
        toolbar.update()
        self.update = self.animate().next
        master.after(10, self.update) 
        canvas.show()

    def animate(self):
        x = np.linspace(0, 6*pi, 100)
        y = sin(x)
        line1, = self.ax.plot(x, y, 'r-')
        phase = 0
        while True:
            phase += 0.1
            line1.set_ydata(sin(x + phase))
            newx = x+phase
            line1.set_xdata(newx)
            self.ax.set_xlim(newx.min(), newx.max())
            self.ax.relim()
            self.ax.autoscale_view(True, True, True) 
            self.fig.canvas.draw()
            self.master.after(10, self.update) 
            yield

def main():
    root = tk.Tk()
    app = App(root)
    tk.mainloop()

if __name__ == '__main__':
    main()

The main idea here is that plt.plot should only be called once. It returns a Line2D object, line1. You can then manipulate the plot by calling line1.set_xdata and/or line1.set_ydata. This "technique" for animation comes from the Matplotlib Cookbook.

Technical note:

The generator function, animate was used here to allow the state of the plot to be saved and updated without having to save state information in instance attributes. Note that it is the generator function's next method (not the generator self.animate) which is being called repeatedly:

    self.update = self.animate().next
    master.after(10, self.update) 

So we are advancing the plot frame-by-frame by calling the generator, self.animate()'s, next method.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 1
    This seems like a very strange way to solve the problem. I don't understand why you think you need an infinite loop and a yield statement. Why can't you just put the one-time code in one method, and the code to run periodically in another? – Bryan Oakley Dec 22 '12 at 01:43
  • 1
    If you do it that way, you will need to make a `self.line` attribute, a `self.phase` attribute, a `self.x` attribute, and a `setup_animation` method. If the animation were more complicated, there could be even more attributes you'd have to define. I think in the end a generator is simpler. – unutbu Dec 22 '12 at 02:03
  • Every time that function gets called it reads the file and plots the whole thing, over and over again. So essentially every 5 seconds the graph is being replotted completely. Hope this helps. – rigs Dec 23 '12 at 18:12