I have an image of ImageFieldFile type in Django. If I do print type(image), I get <class 'django.db.models.fields.files.ImageFieldFile'>
Next, I opened this using PIL's Image.open(image), resized it via image.resize((20,20)) and closed it image.close().
After closing it, I notice image's type has changed to <class 'PIL.Image.Image'>.
How do I change it back to <class 'django.db.models.fields.files.ImageFieldFile'>? I thought .close() would suffice.
The way I got around this was to save it to a BytesIO object then stuff that into an InMemoryUploadedFile. So something like this:
from io import BytesIO
from PIL import Image
from django.core.files.uploadedfile import InMemoryUploadedFile
# Where image is your ImageFieldFile
pil_image = Image.open(image)
pil_image.resize((20, 20))
image_bytes = BytesIO()
pil_image.save(image_bytes)
new_image = InMemoryUploadedFile(
image_bytes, None, image.name, image.type, None, None, None
)
image_bytes.close()
Not terribly graceful, but it got the job done. This was done in Python 3. Not sure of Python 2 compatibility.
EDIT:
Actually, in hindsight, I like this answer better. Wish it existed when I was trying to solve this issue. :-\
Hope this helps. Cheers!
Related
I have created a simple streamlit app which has two option one is file upload another one is capturing image
while trying to open the uploaded file i am getting an error 'str' object has no attribute 'open' .
I am not sure why the issue is occurring kindly let me know how to solve this so that i can move forward
import streamlit as st
import numpy as np
import cv2
from PIL import Image
Image = st.sidebar.selectbox(
'Choose your method',
('Upload','Capture'))
if Image == 'Upload':
uploaded_file = st.file_uploader("HI",type=['jpg','png','jpeg'])
if uploaded_file is not None:
image = Image.open(uploaded_file)
st.image(image,width=300)
else:
file_image = st.camera_input(label = "Take a pic of you to be sketched out")
You have here two Image keywords, one of them is the Image library you imported from PIL, and the other is the return from your selectbox, which is a string.
Since the second one is declared later, it has overridden the first one, thus giving you this error when you try to call open.
I suggest renaming the second one to anything else and it should work properly.
I'm struggling with a memory issue on Heroku when running a Django application (with gunicorn).
I have the following code that takes a user-uploaded image, removes all EXIF data, and returns the image ready for it to be uploaded to S3. This is used both as a form data cleaner and when reading base64 data into memory.
def sanitise_image(img): # img is InMemoryUploadedFile
try:
image = Image.open(img)
except IOError:
return None
# Move all pixel data into a new PIL image
data = list(image.getdata())
image_without_exif = Image.new(image.mode, image.size)
image_without_exif.putdata(data)
# Create new file with image_without_exif instead of input image.
thumb_io = StringIO.StringIO()
image_without_exif.save(thumb_io, format=image.format)
io_len = thumb_io.len
thumb_file = InMemoryUploadedFile(thumb_io, None, strip_tags(img.name), img.content_type,
io_len, None)
# DEL AND CLOSE EVERYTHING
thumb_file.seek(0)
img.close()
del img
thumb_io.close()
image_without_exif.close()
del image_without_exif
image.close()
del image
return thumb_file
I basically take an InMemoryUploadedFile and return a new one with just the pixel data.
del and closes may be redundant, but they represent my attempt to fix the situation where Heroku memory usage keeps growing and is not released every time this function terminates, even remaining overnight:
Running this on localhost with Guppy and following the tutorial, there are no remaining InMemoryUploadedFiles nor StringIOs nor PIL Image left in the heap, leaving me puzzled.
My suspicion is Python does not release the memory back to the OS, as I've read in multiple threads on SO. Has anyone played around with InMemoryUploadedFile and can give me an explanation as to why this memory is not being released?
When I do not perform this sanitisation, the issue does not occur.
Thanks a lot!
I think the issue is creating the temporary list object:
data = list(image.getdata())
Try:
image_without_exif.putdata(image.getdata())
This is why I think that is the issue:
>>> images = [Image.new('RGBA', (100, 100)) for _ in range(100)]
Python memory usage increased ~4Mb.
>>> get_datas = [image.getdata() for image in images]
No memory increase.
>>> pixel_lists = [list(image.getdata()) for image in images]
Python memory usage increased by ~85Mb.
You probably don't want to make getdata() into a list unless you need the numbers explicitly. From the Pillow docs:
Note that the sequence object returned by this method is an internal PIL data type, which only supports certain sequence operations. To convert it to an ordinary sequence (e.g. for printing), use list(im.getdata()).
I found my own answer eventually. Huge thanks to Ryan Tran for pointing me in the right direction. list() does indeed cause the leak.
Using the equivalent split() and merge() method (docs) this is the updated code:
with Image.open(img) as image:
comp = image.split()
image_without_exif = Image.merge(image.mode, comp)
thumb_io = StringIO.StringIO()
image_without_exif.save(thumb_io, format=image.format)
io_len = thumb_io.len
clean_img = InMemoryUploadedFile(thumb_io, None, strip_tags(img.name), img.content_type,
io_len, None)
clean_img.seek(0)
return clean_img
I have a form that saves an image, and everything is working fine, but I want to be able to crop the image. However, when I used Pillow to do this, I get a strange error that doesn't really give me much to go on.
Attribute error at /userprofile/addGame/
_committed
Looking further into the traceback, the following sections were highlighted:
I'm not really sure what this error means. I think it has something to do with the form.save(committed=False), and not being able to edit files after that point, but I'm not positive. I can edit the user after that line using form.user = request.user, so I think it's the fact that I'm trying to change the image getting uploaded that is part of the issue.
This is an old post but I recently experienced a similar problem.
The information in the question is somewhat incomplete but I had the same error.
Assuming the Error being
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/PIL/Image.py", line 627, in __getattr__
raise AttributeError(name)
AttributeError: _committed
So the root problem lies with using PIL (Pillow). The function :
image = Image.open(self.cleaned_data['image'])
We wrongly assume that the object image would continue to have the
instance of image throughout the function(clean_image in my case).
So in my code structure,
def clean_image(self):
if self.cleaned_data['image']:
try:
image = Image.open(self.cleaned_data['image'])
image.verify()
except IOError:
raise forms.ValidationError(_('The file is not an image'))
return image
The error mentioned above is thrown after we execute this code. It is so because
After several of the other PIL(pillow) functions we use, we need to
open the file again. Check this out in pillow documentation
As in the code above(last line),
return image
doesn't really return anything.
To fix it
Simply add ,
image = self.cleaned_data['image']
before the return image line.
Code looks like,
except IOError:
raise forms.ValidationError(_('The file is not an image'))
image = self.cleaned_data['image']
return image
I had faced the same issue trying to save a model with an ImageFileField. My requirement was to save an image from a zip object.
Using pillow gave me the AttributeError: _committed error. Following is what worked for me:
from django.core.files.images import ImageFile
zf = zipfile.ZipFile(zip_file, mode='r')
image_list = zf.infolist()
Model.objects.create(img=ImageFile(zipObj.open(image_list[0])),
file_name=image_list[0].filename.split("/")[-1].strip(),
...)
I'm finding that in PIL I can load an image from disk substantially more quickly than I can copy it. Is there a faster way to copy an image than by calling image.copy()? (and how is this even possible?)
Sample code:
import os, PIL.Image, timeit
test_filepath = os.path.expanduser("~/Test images/C.jpg")
load_image_cmd = "PIL.Image.open('{}')".format(test_filepath)
print((PIL.Image.open(test_filepath)).__class__)
print(min(timeit.repeat(load_image_cmd, setup='import PIL.Image', number=10000)))
print(min(timeit.repeat("img.copy()", setup='import PIL.Image; img = {}'.format(load_image_cmd), number=10000)))
Produces:
PIL.JpegImagePlugin.JpegImageFile
0.916192054749
1.85366988182
Adding gc.enable to the setup for timeit doesn't change things much.
According to the PIL documentation, open() is a lazy operation, which means that it's not really doing all the work to use the image yet.
To do a copy() however, it almost certainly has to read the whole thing in and process it.
EDIT:
To test whether this is true, you should access a pixel in each image as part of your timeit.
EDIT 2:
Another glance at the doc shows that a load() after the open() ought to do the trick of making it do all its work.
I've got a wxPython app that uses the following line before creating a wx.BitmapButton:
imagePlus = wx.Image('wxPlus.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap()
Is there a way to include the image's data in the image, and so something more like this?
plusData = '...√#,›o~ño\Ķ˚fly™Ω.…Õo)Ú∞L∂W_≤Ï~˛⁄...'
imagePlus = wx.Image(plusData, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
By using the module StringIO you can create a 'file-like object' that you can pass to wx.ImageFromStream.
import StringIO
stream = StringIO.StringIO()
stream.write('...√#,›o~ño\Ķ˚fly™Ω.…Õo)Ú∞L∂W_≤Ï~˛⁄...')
image = wx.ImageFromStream(stream)
If you're using wxPython, I think img2py would be worth a look.