I'm using django-stdimage for creating variations of the image.
class Photo(models.Model):
photo = StdImageField(upload_to='photos', verbose_name=_("photo"),
variations={'large': (600, 600), 'thumbnail': (100, 100)}
StdImageField does it's own operations on the image, subclassing ImageField and having attr_class = StdImageFieldFile
StdImageFieldFile does the actual save operation
class StdImageFieldFile(ImageFieldFile):
"""
Like ImageFieldFile but handles variations.
"""
def save(self, name, content, save=True):
super(StdImageFieldFile, self).save(name, content, save)
render_variations = self.field.render_variations
if callable(render_variations):
render_variations = render_variations(
file_name=self.name,
variations=self.field.variations,
storage=self.storage,
)
if not isinstance(render_variations, bool):
msg = (
'"render_variations" callable expects a boolean return value,'
' but got %s'
) % type(render_variations)
raise TypeError(msg)
if render_variations:
self.render_variations()
However, I want to do some manipulation of the image before StdImageFieldFile does it (rotating).
So I created my custom field, to catch the image before it's passed to stdimage
class Rotate(ImageFieldFile):
def save(self, name, content, save=True):
save = False
return super(Rotate, self).save(name, content, save)
class StdImageFieldFileRotateMixin(Rotate, StdImageFieldFile):
pass
class StdImageFieldRotate(StdImageField):
attr_class = StdImageFieldFileRotateMixin
I have the image in the content property of the Rotate class and I can manipulate the image using PIL, but after it's done, I don't know how to assign this image back to the content property. It seems that it's generated on the lower level. Is there a method to generate this content property and then MRO will handle the rest (i.e. pass it to StdImageFieldFile and it will do the rest)?
You can pass a callable to the argument render_variations of the StdImageField class and pre-process the image there.
For example assume you want to save disk space and resize the original image to a smaller version, taking only the smaller image together with variations created by django-stdimage. You can write a function like this:
from io import BytesIO
from PIL import Image
from django.core.files.base import ContentFile
from stdimage.utils import render_variations
def preprocess(file_name, variations, storage):
with storage.open(file_name) as f:
with Image.open(f) as image:
file_format = image.format
# resize to a maximum of 1000x1000 keeping aspect ratio
image.thumbnail((1000, 1000), resample=Image.ANTIALIAS)
with BytesIO() as file_buffer:
image.save(file_buffer, file_format)
f = ContentFile(file_buffer.getvalue())
# delete the original big image
storage.delete(file_name)
# save the resized version with the same filename and format
storage.save(file_name, f)
# render stdimage variations
render_variations(file_name, variations, replace=True, storage=storage)
return False # prevent default rendering
You can then pass the function to the render_variations argument of the StdImageField class, like this:
class Photo(models.Model):
photo = StdImageField(upload_to='photos', verbose_name=_("photo"),
variations={'large': (600, 600), 'thumbnail': (100, 100)},
render_variations=preprocess)
This technique is inspired from the Async image processing example in the README of the django-stdimage project on Github.
Also the way in which the image is handled in the preprocess function is inspired from the stdimage.models.StdImageFieldFile.render_variation method and uses Django Storage objects to deal with different storage types for image files (think Amazon S3).
Related
Using the python module fastAPI, I can't figure out how to return an image. In flask I would do something like this:
#app.route("/vector_image", methods=["POST"])
def image_endpoint():
# img = ... # Create the image here
return Response(img, mimetype="image/png")
what's the corresponding call in this module?
If you already have the bytes of the image in memory
Return a fastapi.responses.Response with your custom content and media_type.
You'll also need to muck with the endpoint decorator to get FastAPI to put the correct media type in the OpenAPI specification.
#app.get(
"/image",
# Set what the media type will be in the autogenerated OpenAPI specification.
# fastapi.tiangolo.com/advanced/additional-responses/#additional-media-types-for-the-main-response
responses = {
200: {
"content": {"image/png": {}}
}
}
# Prevent FastAPI from adding "application/json" as an additional
# response media type in the autogenerated OpenAPI specification.
# https://github.com/tiangolo/fastapi/issues/3258
response_class=Response,
)
def get_image()
image_bytes: bytes = generate_cat_picture()
# media_type here sets the media type of the actual response sent to the client.
return Response(content=image_bytes, media_type="image/png")
See the Response documentation.
If your image exists only on the filesystem
Return a fastapi.responses.FileResponse.
See the FileResponse documentation.
Be careful with StreamingResponse
Other answers suggest StreamingResponse. StreamingResponse is harder to use correctly, so I don't recommend it unless you're sure you can't use Response or FileResponse.
In particular, code like this is pointless. It will not "stream" the image in any useful way.
#app.get("/image")
def get_image()
image_bytes: bytes = generate_cat_picture()
# ❌ Don't do this.
image_stream = io.BytesIO(image_bytes)
return StreamingResponse(content=image_stream, media_type="image/png")
First of all, StreamingResponse(content=my_iterable) streams by iterating over the chunks provided by my_iterable. But when that iterable is a BytesIO, the chunks will be \n-terminated lines, which won't make sense for a binary image.
And even if the chunk divisions made sense, chunking is pointless here because we had the whole image_bytes bytes object available from the start. We may as well have just passed the whole thing into a Response from the beginning. We don't gain anything by holding data back from FastAPI.
Second, StreamingResponse corresponds to HTTP chunked transfer encoding. (This might depend on your ASGI server, but it's the case for Uvicorn, at least.) And this isn't a good use case for chunked transfer encoding.
Chunked transfer encoding makes sense when you don't know the size of your output ahead of time, and you don't want to wait to collect it all to find out before you start sending it to the client. That can apply to stuff like serving the results of slow database queries, but it doesn't generally apply to serving images.
Unnecessary chunked transfer encoding can be harmful. For example, it means clients can't show progress bars when they're downloading the file. See:
Content-Length header versus chunked encoding
Is it a good idea to use Transfer-Encoding: chunked on static files?
I had a similar issue but with a cv2 image. This may be useful for others. Uses the StreamingResponse.
import io
from starlette.responses import StreamingResponse
app = FastAPI()
#app.post("/vector_image")
def image_endpoint(*, vector):
# Returns a cv2 image array from the document vector
cv2img = my_function(vector)
res, im_png = cv2.imencode(".png", cv2img)
return StreamingResponse(io.BytesIO(im_png.tobytes()), media_type="image/png")
All the other answer(s) is on point, but now it's so easy to return an image
from fastapi.responses import FileResponse
#app.get("/")
async def main():
return FileResponse("your_image.jpeg")
It's not properly documented yet, but you can use anything from Starlette.
So, you can use a FileResponse if it's a file in disk with a path: https://www.starlette.io/responses/#fileresponse
If it's a file-like object created in your path operation, in the next stable release of Starlette (used internally by FastAPI) you will also be able to return it in a StreamingResponse.
Thanks to #biophetik's answer, with an important reminder that caused me confusion: If you're using BytesIO especially with PIL/skimage, make sure to also do img.seek(0) before returning!
#app.get("/generate")
def generate(data: str):
img = generate_image(data)
print('img=%s' % (img.shape,))
buf = BytesIO()
imsave(buf, img, format='JPEG', quality=100)
buf.seek(0) # important here!
return StreamingResponse(buf, media_type="image/jpeg",
headers={'Content-Disposition': 'inline; filename="%s.jpg"' %(data,)})
The answer from #SebastiánRamírez pointed me in the right direction, but for those looking to solve the problem, I needed a few lines of code to make it work. I needed to import FileResponse from starlette (not fastAPI?), add CORS support, and return from a temporary file. Perhaps there is a better way, but I couldn't get streaming to work:
from starlette.responses import FileResponse
from starlette.middleware.cors import CORSMiddleware
import tempfile
app = FastAPI()
app.add_middleware(
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
#app.post("/vector_image")
def image_endpoint(*, vector):
# Returns a raw PNG from the document vector (define here)
img = my_function(vector)
with tempfile.NamedTemporaryFile(mode="w+b", suffix=".png", delete=False) as FOUT:
FOUT.write(img)
return FileResponse(FOUT.name, media_type="image/png")
My needs weren't quite met from the above because my image was built with PIL. My fastapi endpoint takes an image file name, reads it as a PIL image, and generates a thumbnail jpeg in memory that can be used in HTML like:
<img src="http://localhost:8000/images/thumbnail/bigimage.jpg">
import io
from PIL import Image
from fastapi.responses import StreamingResponse
#app.get('/images/thumbnail/{filename}',
response_description="Returns a thumbnail image from a larger image",
response_class="StreamingResponse",
responses= {200: {"description": "an image", "content": {"image/jpeg": {}}}})
def thumbnail_image (filename: str):
# read the high-res image file
image = Image.open(filename)
# create a thumbnail image
image.thumbnail((100, 100))
imgio = io.BytesIO()
image.save(imgio, 'JPEG')
imgio.seek(0)
return StreamingResponse(content=imgio, media_type="image/jpeg")
You can use a FileResponse if it's a file in disk with a path:
import os
from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()
path = "/path/to/files"
#app.get("/")
def index():
return {"Hello": "World"}
#app.get("/vector_image", responses={200: {"description": "A picture of a vector image.", "content" : {"image/jpeg" : {"example" : "No example available. Just imagine a picture of a vector image."}}}})
def image_endpoint():
file_path = os.path.join(path, "files/vector_image.jpg")
if os.path.exists(file_path):
return FileResponse(file_path, media_type="image/jpeg", filename="vector_image_for_you.jpg")
return {"error" : "File not found!"}
If when following the top answer and you are attempting to return a BytesIO object like this in your Response
buffer = BytesIO(my_data)
# Return file
return Response(content=buffer, media_type="image/jpg")
You may receive an error that looks like this (as described in this comment)
AttributeError: '_io.BytesIO' object has no attribute 'encode'
This is caused by the render function in Response which explicitly checks for a bytes type here. Since BytesIO != bytes it attempts to encode the value and fails.
The solution is to get the bytes value from the BytesIO object with getvalue()
buffer = BytesIO(my_data)
# Return file
return Response(content=buffer.getvalue(), media_type="image/jpg")
You can do something very similar in FastAPI
from fastapi import FastAPI, Response
app = FastAPI()
#app.post("/vector_image/")
async def image_endpoint():
# img = ... # Create the image here
return Response(content=img, media_type="image/png")
I have a function that accepts a post object and an image form-set (made with Django's modelformset_factory). I expect it to create a cover photo for the post using the first image in the image form-set (instead it points post.cover_image.path to the same path). It successfully saves the post images and post cover-image, but the problem is that it also makes the first image in the post's image set blurry as the cover-photo after saving.
from PIL import Image as PIL_Image
def handle_uploaded_post_images(post, post_image_formset):
for index, form in enumerate(post_image_formset.cleaned_data):
try:
image = form['image']
post_image = Images(post=post, image=image)
post_image.save()
if index == 0 or not post.cover_image:
post.cover_image = post_image.image
post.save()
cover_image = PIL_Image.open(post.cover_image.path)
cover_image.thumbnail((480, 480))
cover_image.save(post.cover_image.path)
except (KeyError, FileNotFoundError) as e:
pass
I just want to make a cover-photo without affecting the post's images.
I am trying to resize an image on upload and also create a thumbnail out of the same resized image. I previously was able to resize the image without issues, however, when I tried adding in the thumbnail feature I was unable to get it working. I have looked at all the other related questions and all of them are either outdated or haven't worked in my case.
models.py
class Project(models.Model):
cover_image=models.ImageField(max_length=150, upload_to='project-covers/', default='Default.png', null=True)
thumbnail=models.ImageField(max_length=150, upload_to='project-thumbnails/', null=True)
def save(self, *args, **kwargs):
# pdb.set_trace()
if self.cover_image:
fname = self.title + '_cover.'
tname = self.title + '_thumbnail.'
self.resizeUploadedImage(fname)
self.createThumbnail(tname)
super(Project, self).save(*args, **
def resizeUploadedImage(self, fname):
'''Resize the image being uploaded.'''
try:
im = Image.open(self.cover_image)
if im.width > IMAGE_SIZE[0] or im.heght > IMAGE_SIZE[1]:
im.resize(IMAGE_SIZE, Image.ANTIALIAS)
image_io = BytesIO()
im.save(image_io, im.format)
# pdb.set_trace()
fname = fname + im.format
self.cover_image.save(fname, ContentFile(image_io.read(), False))
im.close()
image_io.close()
except IOError as e:
print("Could not resize image for", self.image)
print(e)
def createThumbnail(self, fname):
'''Create thumbnail of the image.'''
try:
if self.thumbnail is None:
im = Image.open(self.cover_image)
im.thumbnail(THUMB_SIZE)
image_io = BytesIO()
im.save(image_io, im.format)
fname = fname + im.format
self.thumbnail.save(fname, ContentFile(image_io.getvalue(), False))
im.close()
except IOError as e:
print('Could not create a thumbnail for', self.image)
print(e)
Originally I was using the resizeUploadedImage and createThumbnail methods and was successful with the resizing however the thumbnail was always empty on my admin page and db. Before I had 'editable=False' on the thumbnail as I wanted it to be created automatically behind the scenes. I thought that might be preventing the change so I took it out but it didn't change the results.
Then I tried moving both into the save method (as I had previously run into issues when I moved my resize outside the save) but it still doesn't work properly.
I see on several documents that it's best to put the super() call at the end of the save method, but when I do that I get
UNIQUE constraint failed: projects_project.id
How can I create a thumbnail out of the cover_image and save it to the thumbnail field?
P.S. While running ./manage.py test projects it seems to be actually saving files to my disk which I thought wasn't supposed to happen so I assume its some kind of issue with my Pillow code. I end up with '1_cover.PNG', '1Project0_cover.PNG', '1Project1_cover.PNG', '2_cover.PNG' etc, etc, etc counting up to 9 even though my setuptestdata only has 'Project 1'.
P.P.S. I read somewhere that it's better to use Pillow's thumbnail function for resizing as it'll keep the aspect ratio whereas resize won't. Does anyone have any insight on this?
from PIL import Image
def save(self):
super().save()
img = Image.open(self.cover_image.path)
if img.height > 250 or img.width > 250:
output_size = (250, 250)
img.thumbnail(output_size)
img.save(self.cover_image.path)
try this way with the size you wanted instead of 250
In my project a user can upload an image and once it is saved I can run a post_save signal to modify the image using Pillow. And after modifying the image I need to replace the existing image with the new image. Now the issue is if I run the save method in the post_save signal it will lead to maximum recursion error.So instead of this I am thinking of using update method but it throws an error regarding the characters in the file.
Below is the code:
def menuImageLocation(instance,filename):
return "%s/%s"%(instance,filename)
class restaurantMenuImage(models.Model):
restaurant = models.ForeignKey(restaurantCreateUpdate)
title = models.CharField(max_length=100,null=True,blank=True)
menuImage = models.ImageField(upload_to=menuImageLocation,null=True,blank=True,verbose_name="Menu Image")
def __unicode__(self):
return str(self.restaurant)
#receiver(post_save,sender=restaurantMenuImage)
def modifyMenuImage(sender,instance,**kwargs):
getRestaurant = restaurantCreateUpdate.objects.get(restaurantName=instance.restaurant)
image_path = instance.menuImage.path
filename = os.path.basename(image_path)
image_hold = Image.open(image_path)
image = image_hold.resize((300,300),Image.ANTIALIAS)
temp_loc = "%s/%s/%s/tmp"%(settings.MEDIA_ROOT,"menu",getRestaurant.uniqueID)
if not os.path.exists(temp_loc):
os.makedirs(temp_loc)
temp_file_path = os.path.join(temp_loc,filename)
if os.path.exists(temp_file_path):
temp_path = os.path.join(temp_loc,"%s" %(random.random()))
os.makedirs(temp_path)
temp_file_path = os.path.join(temp_path,filename)
temp_image = open(temp_file_path,"w")
image.save(temp_image,quality=80)
temp_data = open(temp_file_path,"r")
image_file = File(temp_data)
instance.menuImage.save(filename, image_file)
So if I run the last line of the code, this will result into maximum recursion error, and instead of using save method I am trying to use update method the file path but for that I am getting the error that the characters exceed the max_length of the "image_file". Will appreciate any help.
Before you run the last line, add a check if the image path of the instance is not the same as the new image path. Then the code will only run once. Your problem occurs, because the code in your post_save signal is always run when you call save on your model, which creates an endless loop. So you need to be careful when you call save in your post_save signal method.
Something like this should help:
if instance.menuImage != image_file:
instance.menuImage.save(filename, image_file)
I'd like to figure out how to embed binary content in a python script. For instance, I don't want to have any external files around (images, sound, ... ), I want all this content living inside of my python scripts.
Little example to clarify, let's say I got this small snippet:
from StringIO import StringIO
from PIL import Image, ImageFilter
embedded_resource = StringIO(open("Lenna.png", "rb").read())
im = Image.open(embedded_resource)
im.show()
im_sharp = im.filter(ImageFilter.SHARPEN)
im_sharp.show()
As you can see, the example is reading the external file 'Lenna.png'
Question
How to proceed to embed "Lenna.png" as a resource (variable) into my python script. What's the fastest way to achieve this simple task using python?
You might find the following class rather useful for embedding resources in your program. To use it, call the package method with paths to the files that you want to embed. The class will print out a DATA attribute that should be used to replace the one already found in the class. If you want to add files to your pre-built data, use the add method instead. To use the class in your program, make calls to the load method using context manager syntax. The returned value is a Path object that can be used as a filename argument to other functions or for the purpose of directly loading the reconstituted file. See this SMTP Client for example usage.
import base64
import contextlib
import pathlib
import pickle
import pickletools
import sys
import zlib
class Resource:
"""Manager for resources that would normally be held externally."""
WIDTH = 76
__CACHE = None
DATA = b''
#classmethod
def package(cls, *paths):
"""Creates a resource string to be copied into the class."""
cls.__generate_data(paths, {})
#classmethod
def add(cls, *paths):
"""Include paths in the pre-generated DATA block up above."""
cls.__preload()
cls.__generate_data(paths, cls.__CACHE.copy())
#classmethod
def __generate_data(cls, paths, buffer):
"""Load paths into buffer and output DATA code for the class."""
for path in map(pathlib.Path, paths):
if not path.is_file():
raise ValueError('{!r} is not a file'.format(path))
key = path.name
if key in buffer:
raise KeyError('{!r} has already been included'.format(key))
with path.open('rb') as file:
buffer[key] = file.read()
pickled = pickle.dumps(buffer, pickle.HIGHEST_PROTOCOL)
optimized = pickletools.optimize(pickled)
compressed = zlib.compress(optimized, zlib.Z_BEST_COMPRESSION)
encoded = base64.b85encode(compressed)
cls.__print(" DATA = b'''")
for offset in range(0, len(encoded), cls.WIDTH):
cls.__print("\\\n" + encoded[
slice(offset, offset + cls.WIDTH)].decode('ascii'))
cls.__print("'''")
#staticmethod
def __print(line):
"""Provides alternative printing interface for simplicity."""
sys.stdout.write(line)
sys.stdout.flush()
#classmethod
#contextlib.contextmanager
def load(cls, name, delete=True):
"""Dynamically loads resources and makes them usable while needed."""
cls.__preload()
if name not in cls.__CACHE:
raise KeyError('{!r} cannot be found'.format(name))
path = pathlib.Path(name)
with path.open('wb') as file:
file.write(cls.__CACHE[name])
yield path
if delete:
path.unlink()
#classmethod
def __preload(cls):
"""Warm up the cache if it does not exist in a ready state yet."""
if cls.__CACHE is None:
decoded = base64.b85decode(cls.DATA)
decompressed = zlib.decompress(decoded)
cls.__CACHE = pickle.loads(decompressed)
def __init__(self):
"""Creates an error explaining class was used improperly."""
raise NotImplementedError('class was not designed for instantiation')
The best way to go about this is converting your picture into a python string, and have it in a separate file called something like resources.py, then you simply parse it.
If you are looking to embed the whole thing inside a single binary, then you're looking at something like py2exe. Here is an example embedding external files
In the first scenario, you could even use base64 to (de)code the picture, something like this:
import base64
file = open('yourImage.png');
encoded = base64.b64encode(file.read())
data = base64.b64decode(encoded) # Don't forget to file.close() !