#!/usr/bin/python # # # Copyright 2018 David Talkin. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """This demonstrates that Tkinter.PhotoImage leaks memory with each data update. To see the leak, run this as a main program and watch memory usage on the system monitor. The leak occurs in LeakDemo.ShowImage. """ import sys import math import Tkinter as tk import numpy as np __author__ = 'dtalkin@gmail.com (David Talkin)' class LeakDemo(object): """Demonstrates the memory leak due to calls to TkApp._createbytearray. """ def __init__(self, height=600, width=1000): self.height = height self.width = width self.root = tk.Tk() self.frame = tk.Frame(self.root) self.frame.pack() self.canvas = tk.Canvas(self.frame, height=self.height, width=self.width) self.canvas.pack(side=tk.TOP, expand=tk.YES, fill=tk.BOTH) self.image = None self.photo = None self.phase = 0.0 def MakeSignals(self): """Create signals that will modulate the red, blue and green intensities in the image. Return them as a three-element list of numpy arrays. """ freqs = [5.0, 7.0, 9.0] sigs = [] amp = 128.0 for freq in freqs: arg = math.pi * 2.0 * freq / self.width ara = np.zeros(self.width, dtype=np.float32) for i in range(self.width): ara[i] = amp * math.sin(self.phase + (arg * i)) sigs.append(ara) self.phase += math.pi / 100.0 return sigs def CreateImage(self, sigs): """Accept a list of three arrays that represent the red, blue and green intensities across the canvas. Return a PPN-format image to be displayed. """ odata = np.array(sigs).transpose().flatten() amax = 128 if len(odata) > self.width * 3: odata = odata[:self.width * 3] head = 'P6\n%d %d\n%d\n' % (len(odata)/3, self.height, amax) bdata = head + (np.array([min(amax, max(0, (128 + v) / 2)) for v in odata], dtype=np.uint8).tostring() * (self.height)) return bdata def ShowImage(self, image_data): """The leak happens here when PhotoImage is instantiated an after each call to configure. """ if not self.photo: self.photo = tk.PhotoImage(master=self.root, data=image_data, format='PPM') self.image = self.canvas.create_image((1 + self.width) / 2, (1 + self.height) / 2, image=self.photo) else: self.photo.configure(data=image_data) def RunDemo(self): sigs = self.MakeSignals() image = self.CreateImage(sigs) self.ShowImage(image) self.root.after(33, self.RunDemo) # About 30 frames/second. def Main(unused_args): demo = LeakDemo() demo.RunDemo() tk.mainloop() if __name__ == '__main__': Main(sys.argv)