Python PyGObject pixbuf memory leak - python

I'm retrieving a JPEG from gphoto2, creating a Gio stream from the data and then creating a Pixbuf from the stream:
import gphoto2 as gp
from gi.repository import Gio, GdkPixbuf
camera = gp.Camera()
context = gp.Context
camera.init(context)
file = gp.CameraFile()
camera.capture_preview(file, context)
data = memoryview(file.get_data_and_size())
stream = Gio.MemoryInputStream.new_from_data(data)
pixbuf = GtkPixbuf.Pixbuf.new_from_stream(stream)
# display pixbuf in GtkImage
The function doing this is attached to the Gtk idle event using GLib.idle_add(...). It works, but it leaks memory. The process's memory use climbs continually. It leaks even when the line constructing the pixbuf is commented out, but not when the line constructing the stream is also commented out, so it seems that it is the stream itself that is leaking. Adding stream.close() after constructing the pixbuf doesn't help.
What's the right way to release the memory here?

I wouldn't call it an answer as such, and if someone knows a direct answer to the question I'm happy to mark it as the right answer, but here's a workaround for anyone else in the same position:
import gphoto2 as gp
from gi.repository import Gio, GdkPixbuf
camera = gp.Camera()
context = gp.Context
camera.init(context)
file = gp.CameraFile()
camera.capture_preview(file, context)
data = memoryview(file.get_data_and_size())
loader = GdkPixbuf.PixbufLoader.new()
loader.write(data)
pixbuf = loader.get_pixbuf()
# use the pixbuf
loader.close()
This no longer leaks memory.

Related

Buffer function for python 3+

I'm trying to open a vtk window using vtk_show, but my Ipython console crashes every time i do this, apparently this is because Ipython can't display an external window, which is exactly what vtk_show does. I searched on google for a solution, but it's written for python2 (i'm using python 3.6.3). Here's the solution i found:
import vtk
from IPython.display import Image
def vtk_show(renderer, width=400, height=300):
"""
Takes vtkRenderer instance and returns an IPython Image with the
rendering.
"""
renderWindow = vtk.vtkRenderWindow()
renderWindow.SetOffScreenRendering(1)
renderWindow.AddRenderer(renderer)
renderWindow.SetSize(width, height)
renderWindow.Render()
windowToImageFilter = vtk.vtkWindowToImageFilter()
windowToImageFilter.SetInput(renderWindow)
windowToImageFilter.Update()
writer = vtk.vtkPNGWriter()
writer.SetWriteToMemory(1)
writer.SetInputConnection(windowToImageFilter.GetOutputPort())
writer.Write()
data = str(buffer(writer.GetResult()))
return Image(data)
I'm getting an error while trying to use the buffer built-in function of python2, but as this function doesn't exist on python3+ i'm stuck.. If anyone could help me with this i would be very appreciated. Thanks in advance!
At least these two points must be modified on your code to have the same behavior with Python 3:
The buffer(...) built-in function in Python 2 has been replaced by memoryview(...) in Python 3: What is Python buffer type for?. Replace the buffer call by memoryview
the str(...) built-in function has to replaced by a bytes(...) call to get a bytes object: https://docs.python.org/2/howto/pyporting.html#text-versus-binary-data
So the data = ... line should read:
data = bytes(memoryview(writer.GetResult()))
To clarify, I believe this example was an adaptation of a very informative blog example showing how to extract surfaces from medical images using VTK's marching cubes algorithm. The accompanying Jupyter notebook was intended for Python 2.7, and as mentioned for it to be used in Python 3.6+, the data=... portion needs to be changed.
import vtk
from IPython.display import Image
def vtk_show(renderer, width=400, height=300):
"""
Takes vtkRenderer instance and returns an IPython Image with the
rendering.
"""
renderWindow = vtk.vtkRenderWindow()
renderWindow.SetOffScreenRendering(1)
renderWindow.AddRenderer(renderer)
renderWindow.SetSize(width, height)
renderWindow.Render()
windowToImageFilter = vtk.vtkWindowToImageFilter()
windowToImageFilter.SetInput(renderWindow)
windowToImageFilter.Update()
writer = vtk.vtkPNGWriter()
writer.SetWriteToMemory(1)
writer.SetInputConnection(windowToImageFilter.GetOutputPort())
writer.Write()
data = memoryview(writer.GetResults()).tobytes()
return Image(data)
Credit for the solution definitely goes to #MafiaSkafia and #jcgiret, but I wanted to post a full and final solution.

Memory leak with PySide QGraphicsPixmapItem in Python 3.4

In my PySide app I repeatedly update a QGraphicsPixmapItem with a new pixmap that I create from a (always differently scaled) numpy array:
# Once:
from PySide import QtGui
self._image_pixmap_item = self._scene.addPixmap(QtGui.QPixmap(width, height))
# Repeated:
image = QtGui.QImage(numpy_array, width, height)
pixmap = QtGui.QPixmap.fromImage(image)
self._image_pixmap_item.setPixmap(pixmap)
This code works fine with Python 2.7 but memory usage is constantly increasing under Python 3.4. I could fix this by manually invoking the garbage collector in each loop:
import gc
gc.collect()
but performance is (of cause) quite bad. I use Python 3.4.3 with PySide 1.2.4 and numpy 1.11.2.
Is this a bug in the (relatively new) Python 3.x support of PySide or am I missing something? Also, is there a way to directly fill the the pixmap buffer without creating a new QImage every time?
Thanks
Alex
UPDATE:
As workaround, using qimage2ndarray (https://github.com/hmeine/qimage2ndarray) to convert the numpy array to a QImage works perfectly well.

PySide crash when displaying pixmaps

I am programming a GUI application for Data visualization using Python and Qt via PySide.
I experience occasional crashes ('python.exe has stopped working') which I think I narrowed down to the following problem:
When creating a pixmap from a numpy array, somehow the memory is freed by python (?) even when the pixmap already exists. This does not happen if the image format used is QImage.Format_ARGB32. (Why not?). Check out the code example below, I hope you can reproduce the problem.
EDIT: To clarify - If the numpy array is not deleted by python, everything works just as expected. However, in my application, new data is generated constantly and I would have to find a good way to track which dataset is currently displayed as a pixmap, and delete it as soon as it is not displayed anymore. I would like to find the correct way for Qt to take care of the (image-) data and store it in memory until not required anymore.
As far as I understood the documentation of Qt and PySide, the pixmap should hold all the data of the image, thus Qt should be responsible for the memory management.
Is this a bug in Qt, Pyside, or did I not understand something? I could not find any details on the memory management in the regular documentation.
Background: I need to regularly update the data to display, thus it may happen that between creating the pixmap and displaying it, the numpy data array is already overwritten by python (as there are some CPU intensive threads involved that sometimes slow the GUI). Thus, storing the numpy array forever is not an option.
Here is a code example, the interesting bits happen in the display_image method:
import numpy as np
from PySide import QtCore, QtGui
import sys
class displaywidget(QtGui.QWidget):
def __init__(self,parent = None):
super(displaywidget, self).__init__(parent)
## set up the GUI elements
self.setLayout(QtGui.QGridLayout())
self.view = QtGui.QGraphicsView()
self.layout().addWidget(self.view)
self.scene = QtGui.QGraphicsScene()
self.view.setScene(self.scene)
# create a pixmap and display it on the graphicsview
self.display_image()
def display_image(self):
# create image data in numpy array
size = 1024
r = np.linspace(0,255, num = size**2, dtype = np.uint32)
argb = (r<<16) +(255<<24)
# image should display a black to red shading
image = QtGui.QImage(argb, size,size, size*4, QtGui.QImage.Format_RGB32)
### using ARGB format option does not cause the problem
# image = QtGui.QImage(argb, size,size, size*4, QtGui.QImage.Format_RGB32)
pixmap = QtGui.QPixmap.fromImage(image)
self.scene.addPixmap(pixmap)
### when the image data is stored, everything works fine, too
# self.cache = argb
### if only the pixmap and image is stored, the problem still exists
# self.cache = [pixmap, image]
def main(argv):
## create application and main window
try:
app = QtGui.QApplication(argv)
new_qtapp = True
except:
new_qtapp = False
mainwindow = QtGui.QMainWindow()
mainwindow.setCentralWidget(displaywidget())
mainwindow.show()
if new_qtapp:
sys.exit(app.exec_())
return mainwindow
if __name__=="__main__":
w = main(sys.argv)
I am using 32 bit Python 2.7.6 and PySide 1.2.2 on a generic Windows7 Office PC.
Thanks for your help!
This simple change keeps the image from being garbage collected when the function is done. Which seems to be what caused the problem
self.argb = (r<<16) +(255<<24)
# image should display a black to red shading
image = QtGui.QImage(self.argb, size,size, size*4, QtGui.QImage.Format_RGB32)

How to create a PixBuf from file with Gdk3?

Environment: Python3
Libraries:
from gi.repository import Gtk, Gdk
import cairo
I want to create a 'pixbuf from file' but the method does not longer exist in Gdk3.
pb = Gdk.pixbuf_new_from_file('sunshine.png')
Gdk.cairo_set_source_pixbuf(cr, pb, 0, 0)
Result in: AttributeError: 'gi.repository.Gdk' object has no attribute 'pixbuf_new_from_file'
Does anybody know how to do this with Gdk3?
Seems that Gdk3 only support these methods:
gdk_pixbuf_get_from_window
gdk_pixbuf_get_from_surface
Update
Found it out myself:
image = Gtk.Image()
image.set_from_file('sunshine.png')
pixbuf = image.get_pixbuf()
Gdk.cairo_set_source_pixbuf(cr, pixbuf, 10, 10)
The question is old, but maybe it helps someone:
from gi.repository import GdkPixbuf
pixbuf = GdkPixbuf.Pixbuf.new_from_file('sunshine.png')
Tested on GdkPixbuf._version == '2.0'
I dont know much about python, but in general you should not using gtk image directly to load file. Use GdkPixbuf instead because of error handling. The reason there is no attribute (in python) "pixbuf new from file", is because for much of the GdkPixbuf handling, still using (in C) gdk-pixbuf-2.0. Why they haven't port or still keeping it, I have no idea. In python you have to find the "gdk pixbuf" module instead of using GDK3, as Schcriher pointed out.

ReportLab and Python Imaging Library images from memory issue

I've run into an issue I can't seem to figure out with PIL and reportlab. Specifically, I would like to use drawImage on a canvas in reportlab using a PIL Image object.
In the past I've inserted images into reportlab documents from the web using raw data, StringIO and reportlab's ImageReader class. Unfortunately, ImageReader takes a file name or a file buffer like object.
The ultimate goal is to be able to put QR codes, (which are PIL objects) into the reportlab PDFs. One thing that does work is the following:
size, qrcode = PyQrcodec.encode('http://www.google.com')
qrcode.save("img.jpeg")
self.pdf.drawImage(ImageReader("img.jpeg"), 25, 25, width=125, height=125)
self.pdf.showPage()
This saves the image and then reads it into the pdf. Obviously doing it like this doesn't make sense.
My efforts are compounded by the relatively long development history of reportlab which makes finding the answers relevant to the latest version (2.4).
Thanks for the help.
(By the way, I'm using 1.1.6 PIL)
Although it does look like it should work, it really doesn't. I finally was able to track down the problem, and it was in the _isPILImage() function. The problem is that "Image.Image" is actually "from PIL import Image" whereas my object is actually just from Image. I would have assumed they were the same, but in any case isinstance doesn't evaluate them as the same. My hack solution was to change _isPILImage(fileName): ... to
519 def _isPILImage(im):
520 import Image as PIL_Image
521 try:
522 return isinstance(im,Image.Image) or isinstance(im, PIL_Image.Image)
523 except ImportError:
524 return 0
That solves my error. Since you pointed me in the right direction I originally tried to post this as a comment then accept your answer, but it doesn't allow enough characters.
Thank you for the input! If you can think of a more elegant way to fix this... (I tried to wrap the Image.Image object in a PIL Image object) let me know!
Looking at the source for ReportLab 2.4, it seems that ImageReader will accept a PIL Image object as "filename".
def _isPILImage(im):
try:
return isinstance(im,Image.Image)
except ImportError:
return 0
class ImageReader(object):
"Wraps up either PIL or Java to get data from bitmaps"
_cache={}
def __init__(self, fileName):
if isinstance(fileName,ImageReader):
self.__dict__ = fileName.__dict__ #borgize
return
#start wih lots of null private fields, to be populated by
#the relevant engine.
self.fileName = fileName
self._image = None
self._width = None
self._height = None
self._transparent = None
self._data = None
if _isPILImage(fileName):
self._image = fileName
self.fp = getattr(fileName,'fp',None)
try:
self.fileName = self._image.fileName
except AttributeError:
self.fileName = 'PILIMAGE_%d' % id(self)
weired
the documentation claims that drawImage and drawInlineImage work the same way, but it works with drawInlineImage out of the box, and do not work in drawImage
This is what I did, using plotly, BytesIO and reportlab. It puts an image in the pdf from memory, without having to save it on disk first.
import io
import plotly.graph_objects as go
from reportlab.platypus import SimpleDocTemplate, Image
image_in_memory = io.BytesIO()
figure = go.Figure(data=data) # as it's not part of the question I'm leaving data out
figure.write_image(image_in_memory, scale=5, width=1000, height=400)
pdf_image = Image(image_in_memory, width=400, height=160)
pdf_document = SimpleDocTemplate(path, title='Title')
pdf_document.multiBuild([pdf_image])

Categories

Resources