0

Scenario

I use a formData form to upload an image via ajax which is then added to a MongoDB GridFS database.

This was working:

my_image = request.files.my_image
raw = my_image.file.read()
fs.put(raw)

Desired Behaviour

I want to resize the image with Pillow before adding to GridFS.

What I Tried

I changed the above to:

my_image = request.files.my_image
raw = Image.open(my_image.file.read())
raw_resized = raw.resize((new_dimensions))
fs.put(raw_resized)

Actual Behaviour

I am now getting 500 errors. Tail shows:

TypeError: file() argument 1 must be encoded string without NULL bytes, not str

Question

How do I properly handle the Pillow image object so that I can add it to GridFS?

Troubelshooting

This is still unresolved, but I'm just adding my attempts to understand what is happening with file types etc at different stages of the process by using the interpretor:

>>> my_image = open("my_great_image.jpg")
>>> my_image
<open file 'my_great_image.jpg', mode 'r' at 0x0259CF40>
>>> type(my_image)
<type 'file'>
>>> my_image_read = my_image.read()
>>> my_image_read
# lots of image data
>>> type(my_image_read)
<type 'str'>
>>> my_pil_image = Image.open("my_great_image.jpg")
>>> my_pil_image
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=400x267 at 0x2760CD8>
>>> type(my_pil_image)
<type 'instance'>

So from this I think I can deduce that, originally, GridFS was accepting of the string version of the image generated from the read() method.

So I think I need to somehow make the Pillow image object a string in order to get it into GridFS.

Stennie
  • 63,885
  • 14
  • 149
  • 175
user1063287
  • 10,265
  • 25
  • 122
  • 218

2 Answers2

1

Solution

Wo, check this out, it works, the logic is:

  1. Form uploads image,
  2. Pillow does three different resizes,
  3. Use StringIO to convert Pillow objects to strings
  4. Put resized images, as strings, in GridFS.

Python

from PIL import Image
import StringIO

# define three new image sizes
card_dimensions = (108,108)
main_dimensions = (42,38)
thumb_dimensions = (18,14)
# covert uploaded image to Pillow object
raw_pil = Image.open(my_image.file)
# conversion to card size
raw_card_output = StringIO.StringIO()
raw_card = raw_pil.resize((card_dimensions))
raw_card.save(raw_card_output,format=raw_pil.format)
raw_card_output_contents = raw_card_output.getvalue()
# conversion to main size
raw_main_output = StringIO.StringIO()
raw_main = raw_pil.resize((main_dimensions))
raw_main.save(raw_main_output,format=raw_pil.format)
raw_main_output_contents = raw_main_output.getvalue()
#conversion to thumb size
raw_thumb_output = StringIO.StringIO()
raw_thumb = raw_pil.resize((thumb_dimensions))
raw_thumb.save(raw_thumb_output,format=raw_pil.format)
raw_thumb_output_contents = raw_thumb_output.getvalue()
# put card image into GridFS
fs.put(raw_card_output_contents)
# put main image into GridFS
fs.put(raw_main_output_contents)
# put thumb image into GridFS
fs.put(raw_thumb_output_contents)

Further Explanation

Basically I deduced that GridFS was accepting a string, so therefore I needed to transform the Pillow object into a string.

The interpreter troubleshooting below should make some of the dynamics clearer and carries on from the troubleshooting in the original post:

>>> my_pil_image
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=400x267 at 0x2760CD8>
>>> my_pil_image_resized = my_pil_image.resize((50,50))
>>> my_pil_image_resized
<PIL.Image.Image image mode=RGB size=50x50 at 0x2898710>
>>> output = StringIO.StringIO()
>>> my_pil_image_resized.save(output,format="JPEG")
>>> contents = output.getvalue()
>>> type(contents)
<type 'str'>
>>> contents
# lots of image data

So basically the above process show the mechanics of how to convert the Pillow object into a string which can then be added to GridFS.

user1063287
  • 10,265
  • 25
  • 122
  • 218
0

I just got a simple way to do this with flask.

I am using FileStorage for file holder before uploading to gridFS. It also help me to wrap image from pillow after resize.

Document looks like this.

from mongoengine import Document ImageField


class SomeDocument(Document):
    icon = ImageField(required=False, collection_name="collection_name")

And the controller is

from io import BytesIO
from werkzeug.datastructures import FileStorage
from PIL import Image

im = Image.open(request.files.get('icon'))
im.thumbnail((400, 400))

output = BytesIO()

im.save(output, format=im.format, quality=90)

original_extension = request.files.get('icon').filename.rsplit('.', 1)[1].lower()

SomeDocument.icon.put(FileStorage(output, content_type=f"image/{original_extension"))
SomeDocument.save()
im.close()