Reputation: 127
I have a data project that produces a number of plots with the same name only in different directories. Looking at these plots helps to see if anything funky is going on with the data, but it would be a pain try to open them all one by one or to copy them all to one directory, rename them, and then open up my standard image viewer.
Knowing some wxPython GUI programming I decided to whip up an app that would take in a list of directories to these images and I can slide show them with some arrow buttons. I can then add to this list as more plots come in.
I got the app running properly, but now when I am loading in around 23 ~3 MB images my program memory usage reaches near 10 GB.
The code is below, but in short I load them up all as wxPython Images, before displaying, and this is where the memory climbs. Switching between the images via stacks doesn't seem to make the memory climb further. I think it might be me holding onto no longer needed resources, however the usage seems disproportionate to the size and number of images.
Is someone able to point out a flaw in my method?
```
class Slide(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Slide Show")
self.display = wx.GetDisplaySize()
self.image_list = None
self.current = None # holds currently displayed bitmap
# stacks that hold the bitmaps
self.future = []
self.past = []
self.left = wx.BitmapButton(self, id=101, size=(100,-1), bitmap=wx.ArtProvider.GetBitmap(wx.ART_GO_BACK))
self.left.Enable(False)
self.right = wx.BitmapButton(self, id=102, size=(100,-1), bitmap=wx.ArtProvider.GetBitmap(wx.ART_GO_FORWARD))
self.right.Enable(False)
self.open = wx.BitmapButton(self, id=103, size=(100,-1), bitmap=wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN))
# Testing code
#png = wx.Image("set001_fit_001_1.png", wx.BITMAP_TYPE_ANY).ConvertToBitmap()
#image = wx.ImageFromBitmap(png)
#image = image.Scale(self.display[0]/1.4, self.display[1]/1.4, wx.IMAGE_QUALITY_HIGH)
#png = wx.BitmapFromImage(image)
# make empty image slot
self.img = wx.StaticBitmap(self, -1, size=(self.display[0]/1.4, self.display[1]/1.4))
## Main sizers
self.vertSizer = wx.BoxSizer(wx.VERTICAL)
# sub sizers
self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
# place buttons together
self.buttonSizer.Add(self.left, flag=wx.ALIGN_CENTER)
als.AddLinearSpacer(self.buttonSizer, 15)
self.buttonSizer.Add(self.right, flag=wx.ALIGN_CENTER)
# place arrows and open together
self.vertSizer.Add(self.img, flag=wx.ALIGN_CENTER)
self.vertSizer.Add(self.open, flag=wx.ALIGN_CENTER)
als.AddLinearSpacer(self.vertSizer, 15)
self.vertSizer.Add(self.buttonSizer, flag=wx.ALIGN_CENTER)
als.AddLinearSpacer(self.vertSizer, 15)
self.Bind(wx.EVT_BUTTON, self.onOpen, id=103)
self.Bind(wx.EVT_BUTTON, self.onLeft, id=101)
self.Bind(wx.EVT_BUTTON, self.onRight, id=102)
self.SetSizer(self.vertSizer)
self.vertSizer.Fit(self)
def onOpen(self, evt):
print("Opening")
openFileDialog = wx.FileDialog(self, "Open Image List", "", "", "List (*.ls)|*.ls", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if openFileDialog.ShowModal() == wx.ID_CANCEL:
return
fileName = str(openFileDialog.GetPath())
print(fileName)
# put image list in a python list
image_list = []
f = open(fileName, 'r')
for line in f:
image_list.append(line.rstrip())
f.close()
print(image_list)
# convert every image to wx Image
png_list = []
for image in image_list:
png = wx.Image(image, wx.BITMAP_TYPE_ANY)
png_list.append(png)
# store pngs
print(image_list[::-1])
png_list = png_list[::-1]
for png in png_list[:-1]:
self.future.append(png)
# display first png
self.current = png_list[-1] # put into current buffer
png = self.current.Scale(self.display[0]/1.4, self.display[1]/1.4, wx.IMAGE_QUALITY_HIGH)
png = wx.BitmapFromImage(png)
self.img.SetBitmap(png)
self.Refresh()
self.right.Enable(True)
def onLeft(self, evt):
# put current image into future stack and grab a new current image from past stack
self.future.append(self.current)
self.current = self.past.pop()
png = self.current.Scale(self.display[0]/1.4, self.display[1]/1.4, wx.IMAGE_QUALITY_HIGH)
png = wx.BitmapFromImage(png)
self.img.SetBitmap(png)
self.Refresh()
if len(self.past) <= 0:
self.left.Enable(False)
self.right.Enable(True)
def onRight(self, evt):
# put current image into past stack and load in a new image from future stack
self.past.append(self.current)
self.current = self.future.pop()
png = self.current.Scale(self.display[0]/1.4, self.display[1]/1.4, wx.IMAGE_QUALITY_HIGH)
png = wx.BitmapFromImage(png)
self.img.SetBitmap(png)
self.Refresh()
if len(self.future) <= 0:
self.right.Enable(False)
self.left.Enable(True)
```
Upvotes: 1
Views: 203
Reputation: 6306
Loading them as needed would probably be the best way to reduce memory usage. Or perhaps keeping the current, next and previous images in memory to help speed up switching between views. Or maybe use a caching implementation to keep the N
most recently viewed images (plus the next in the sequence) in memory. Then you can fiddle with the size of N
to see what works well and is a reasonable compromise between memory and speed.
Another possible improvement would be to not scale the image each time you need to display it, just scale it once when it is read and keep that one in memory (or the cache). You may also want to experiment with pre-converting them to wx.Bitmaps
too, as image to bitmap conversions is not a trivial operation.
Upvotes: 2