pythonpython-3.xpython-requestsbase64fastapi

How to send base64 image using Python requests and FastAPI?


I am trying to implement a code for image style transfer based on FastAPI. I found it effective to convert the byte of the image into base64 and transmit it.

So, I designed my client codeto encode the image into a base64 string and send it to the server, which received it succesfully. However, I face some difficulties in restoring the image bytes to ndarray.

I get the following this errors:

image_array = np.frombuffer(base64.b64decode(image_byte)).reshape(image_shape)

ValueError: cannot reshape array of size 524288 into shape (512,512,4)

This is my client code :

import base64
import requests
import numpy as np
import json
from matplotlib.pyplot import imread
from skimage.transform import resize


if __name__ == '__main__':
    path_to_img = "my image path"

    image = imread(path_to_img)
    image = resize(image, (512, 512))

    image_byte = base64.b64encode(image.tobytes())
    data = {"shape": image.shape, "image": image_byte.decode()}

    response = requests.get('http://127.0.0.1:8000/myapp/v1/filter/a', data=json.dumps(data))

and this is my server code:

import json
import base64
import uvicorn
import model_loader
import numpy as np

from fastapi import FastAPI
from typing import Optional


app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/myapp/v1/filter/a")
async def style_transfer(data: dict):
    image_byte = data.get('image').encode()
    image_shape = tuple(data.get('shape'))
    image_array = np.frombuffer(base64.b64decode(image_byte)).reshape(image_shape)

if __name__ == '__main__':
    uvicorn.run(app, port='8000', host="127.0.0.1")

Solution

  • Option 1

    As previously mentioned here, as well as here and here, one should use UploadFile, in order to upload files from client apps (for async read/write have a look at this answer). For example:

    server side:

    @app.post("/upload")
    def upload(file: UploadFile = File(...)):
        try:
            contents = file.file.read()
            with open(file.filename, 'wb') as f:
                f.write(contents)
        except Exception:
            return {"message": "There was an error uploading the file"}
        finally:
            file.file.close()
            
        return {"message": f"Successfuly uploaded {file.filename}"}
    

    client side:

    import requests
    
    url = 'http://127.0.0.1:8000/upload'
    file = {'file': open('images/1.png', 'rb')}
    resp = requests.post(url=url, files=file) 
    print(resp.json())
    

    Option 2

    If, however, you still need to send a base64 encoded image, you can do it as previously described here (Option 2). On client side, you can encode the image to base64 and send it using a POST request as follows:

    client side:

    import base64
    import requests
    
    url = 'http://127.0.0.1:8000/upload'
    with open("photo.png", "rb") as image_file:
        encoded_string = base64.b64encode(image_file.read())
        
    payload ={"filename": "photo.png", "filedata": encoded_string}
    resp = requests.post(url=url, data=payload) 
    

    On server side you can receive the image using a Form field, and decode the image as follows:

    server side:

    @app.post("/upload")
    def upload(filename: str = Form(...), filedata: str = Form(...)):
        image_as_bytes = str.encode(filedata)  # convert string to bytes
        img_recovered = base64.b64decode(image_as_bytes)  # decode base64string
        try:
            with open("uploaded_" + filename, "wb") as f:
                f.write(img_recovered)
        except Exception:
            return {"message": "There was an error uploading the file"}
            
        return {"message": f"Successfuly uploaded {filename}"}