I am working on a Python program for displaying photos on the Raspberry Pi (Model B Revision 2.0 with 512MB RAM). It uses Tk for displaying the images. The program is mostly finished, but I ran into an issue where the program is terminated by the kernel because of low memory. This seems to happen randomly.
I do not understand why this happens. I have noticed that during image switching, the CPU spikes up significantly (up to 90%). I therefore thought that it might be an issue with the CPU not keeping up between two images and then falling behind and running out of memory. To test this I increased the timeout between showing images to 1 minute, but that did not help.
My question is, whether I am doing something wrong/inefficiently in the code (see below)? If not: I am considering switching to PyQt, because it seems to accelerate graphics with OpenGL (from what I read). Is this true and/or do you think that this might help with the issue I am facing?
This is my current Python code:
# From: https://stackoverflow.com/questions/19838972/how-to-update-an-image-on-a-canvas
import os
from pathlib import Path
from tkinter import *
from PIL import Image, ExifTags, ImageTk
import ipdb
class MainWindow():
def __init__(self, main):
self.my_images = []
self._imageDirectory = str(Path.home().joinpath("./Pictures/rpictureframe"))
self.main = main
w, h = main.winfo_screenwidth(), root.winfo_screenheight()
self.w, self.h = w, h
main.attributes("-fullscreen", True) # REF: https://stackoverflow.com/questions/45136287/python-tkinter-toggle-quit-fullscreen-image-with-double-mouse-click
main.focus_set()
self.canvas = Canvas(main, width=w, height=h)
self.canvas.configure(background="black", highlightthickness=0)
self.canvas.pack()
self.firstCall = True
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(w/2, h/2, image = self.getNextImage()) ### replacing getNextImage instead of getNextImageV1 here fails
@property
def imageDirectory(self):
return self._imageDirectory
@imageDirectory.setter
def setImageDirectory(self,imageDirectory):
self._imageDirectory = imageDirectory
def getNextImage(self):
if self.my_images == []:
self.my_images = os.listdir(self.imageDirectory)
currentImagePath = self.imageDirectory + "/" + self.my_images.pop()
self.currentImage = self.readImage(currentImagePath, self.w, self.h)
return self.currentImage
def readImage(self,imagePath,w,h):
pilImage = Image.open(imagePath)
pilImage = self.rotateImage(pilImage)
pilImage = self.resizeImage(pilImage,w,h)
return ImageTk.PhotoImage(pilImage)
def rotateImage(self,image):
# REF: https://stackoverflow.com/a/26928142/653770
try:
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation]=='Orientation':
break
exif=dict(image._getexif().items())
if exif[orientation] == 3:
image=image.rotate(180, expand=True)
elif exif[orientation] == 6:
image=image.rotate(270, expand=True)
elif exif[orientation] == 8:
image=image.rotate(90, expand=True)
except (AttributeError, KeyError, IndexError):
# cases: image don't have getexif
pass
return image
def resizeImage(self,pilImage,w,h):
imgWidth, imgHeight = pilImage.size
if imgWidth > w or imgHeight > h:
ratio = min(w/imgWidth, h/imgHeight)
imgWidth = int(imgWidth*ratio)
imgHeight = int(imgHeight*ratio)
pilImage = pilImage.resize((imgWidth,imgHeight), Image.ANTIALIAS)
return pilImage
def update_image(self):
# REF: https://stackoverflow.com/questions/7573031/when-i-use-update-with-tkinter-my-label-writes-another-line-instead-of-rewriti/7582458#
self.canvas.itemconfig(self.image_on_canvas, image = self.getNextImage()) ### replacing getNextImage instead of getNextImageV1 here fails
self.main.after(5000, self.update_image)
root = Tk()
app = MainWindow(root)
app.update_image()
root.mainloop()
UPDATE:
Below you will find the current code that still produces the out-of-memory issue.
You can find the dmesg out-of-memory error here: https://pastebin.com/feTFLSxq
Furthermore this is the periodic (every second) output from top
: https://pastebin.com/PX99VqX0
I have plotted the columns 6 and 7 (memory usage) of the top
output:
As you can see, there does not appear to be a continues increase in memory usage as I would expect from a memory leak.
This is my current code:
# From: https://stackoverflow.com/questions/19838972/how-to-update-an-image-on-a-canvas
import glob
from pathlib import Path
from tkinter import *
from PIL import Image, ExifTags, ImageTk
class MainWindow():
def __init__(self, main):
self.my_images = []
self._imageDirectory = str(Path.home().joinpath("Pictures/rpictureframe"))
self.main = main
w, h = main.winfo_screenwidth(), root.winfo_screenheight()
self.w, self.h = w, h
# main.attributes("-fullscreen", True) # REF: https://stackoverflow.com/questions/45136287/python-tkinter-toggle-quit-fullscreen-image-with-double-mouse-click
main.focus_set()
self.canvas = Canvas(main, width=w, height=h)
self.canvas.configure(background="black", highlightthickness=0)
self.canvas.pack()
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(w / 2, h / 2,
image=self.getNextImage()) ### replacing getNextImage instead of getNextImageV1 here fails
@property
def imageDirectory(self):
return self._imageDirectory
@imageDirectory.setter
def setImageDirectory(self, imageDirectory):
self._imageDirectory = imageDirectory
def getNextImage(self):
if self.my_images == []:
# self.my_images = os.listdir(self.imageDirectory)
self.my_images = glob.glob(f"{self.imageDirectory}/*.jpg")
currentImagePath = self.my_images.pop()
self.currentImage = self.readImage(currentImagePath, self.w, self.h)
return self.currentImage
def readImage(self, imagePath, w, h):
with Image.open(imagePath) as pilImage:
pilImage = self.rotateImage(pilImage)
pilImage = self.resizeImage(pilImage, w, h)
return ImageTk.PhotoImage(pilImage)
def rotateImage(self, image):
# REF: https://stackoverflow.com/a/26928142/653770
try:
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = dict(image._getexif().items())
if exif[orientation] == 3:
image = image.rotate(180, expand=True)
elif exif[orientation] == 6:
image = image.rotate(270, expand=True)
elif exif[orientation] == 8:
image = image.rotate(90, expand=True)
except (AttributeError, KeyError, IndexError):
# cases: image don't have getexif
pass
return image
def resizeImage(self, pilImage, w, h):
imgWidth, imgHeight = pilImage.size
if imgWidth > w or imgHeight > h:
ratio = min(w / imgWidth, h / imgHeight)
imgWidth = int(imgWidth * ratio)
imgHeight = int(imgHeight * ratio)
pilImage = pilImage.resize((imgWidth, imgHeight), Image.ANTIALIAS)
return pilImage
def update_image(self):
# REF: https://stackoverflow.com/questions/7573031/when-i-use-update-with-tkinter-my-label-writes-another-line-instead-of-rewriti/7582458#
self.canvas.itemconfig(self.image_on_canvas,
image=self.getNextImage()) ### replacing getNextImage instead of getNextImageV1 here fails
self.main.after(30000, self.update_image)
root = Tk()
app = MainWindow(root)
app.update_image()
root.mainloop()