5

I am trying to use FastAPI to allow my (dockerized) server to respond to API calls returning

  • an image image, and
  • a dictionary additional_dict

(for a machine learning example, this could be classification labels from a classifier and a saliency map).

In my case, I think it makes sense to make use the same endpoint to get the two objects, because these get generated with the same computations.

I can successfully return an image as binary using something along the lines of https://stackoverflow.com/a/55905051/4240413:

from PIL import Image
from fastapi import FastAPI, File
import tempfile
from starlette.responses import FileResponse

@app.post("/getstuff")
async def get_image_and_data(file: bytes = File(...)):
    
    image, additional_dict = process_image(image)

    with tempfile.NamedTemporaryFile(mode="w+b", suffix=".png", delete=False) as outfile:
        image.save(outfile)
        return FileResponse(outfile.name, media_type="image/png")

This allows me even to see the image response when I invoke the service through the swagger UI on localhost:8000/docs.

However, I don't know how to get the image binary and the dictionary together.

I tried to replace my return statement with:

return FileResponse(outfile.name, media_type="image/png"), additional_dict

but this doesn't really work (when trying on swagger at localhost:8000/docs, I just get the json reply below, with a path to the temp file created)

[{
"path": "/tmp/tmpldwe_79d.png",
"status_code": 200,
"filename": null,
"send_header_only": false,
"media_type": "image/png",
"background": null,
"raw_headers": [
    [
    "content-type",
    "image/png"
    ]
],
"stat_result": null
},
{
"foo": 1,
"bar": 2
}]

It is possible to get in the response the binary image and the additional dict, from the same endpoint? If so, what's the best way to do it?
Is it possible to have the image rendered in the Swagger UI /docs and read the dict values there?

Davide Fiocco
  • 5,350
  • 5
  • 35
  • 72

1 Answers1

9

You can encode binary data (it would be in image in your case) with base64 and send the encoded string via dictionary.

   import base64

   with open("image.png", "rb") as image_file:
       encoded_image_string = base64.b64encode(image_file.read())

   payload = {
       "mime" : "image/png",
       "image": encoded_image_string,
       "some_other_data": None
   }

So this dictionary will contain the base64 encoded image and any other data fields.

On the front-end you may decode an image from base64 string back to bytes and present it to the user.

Andrey Korchak
  • 2,280
  • 1
  • 18
  • 15
  • OK, thanks! Still, ideally I would like to have the image visible in swagger as well, I still wonder if that's somehow possible... – Davide Fiocco Jan 20 '20 at 13:20
  • 1
    @DavideFiocco you can send the image as a traditional file and additional data in response headers. Not so convenient thaught but in this case, you will be able to see the image via Swagger. – Andrey Korchak Jan 21 '20 at 16:38