Tristan
Tristan

Reputation: 127

wxPython images taking up lots of memory

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

Answers (1)

RobinDunn
RobinDunn

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

Related Questions