Как вернуть изображение в fastAPI?
Используя модуль python fastAPI, я не могу понять, как вернуть изображение. В колбе я бы сделал что-то вроде этого:
@app.route("/vector_image", methods=["POST"])
def image_endpoint():
# img = ... # Create the image here
return Response(img, mimetype="image/png")
какой соответствующий вызов в этом модуле?
10 ответов
Ответ @SebastiánRamírez указал мне верное направление, но для тех, кто хочет решить проблему, мне понадобилось несколько строк кода, чтобы она заработала. Мне нужен импорт edto FileResponse
из starlette (не fastAPI?), добавьте поддержку CORS и вернитесь из временного файла. Возможно, есть лучший способ, но я не смог запустить потоковую передачу:
from starlette.responses import FileResponse
app = FastAPI()
app.add_middleware(
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
def image_endpoint(*, item: Item):
# Returns a raw PNG from the document vector
with tempfile.NamedTemporaryFile(mode="w+b", suffix=".png", delete=False) as FOUT:
FOUT.write(img)
return FileResponse(FOUT.name, media_type="image/png")
Если у вас уже есть байты изображения в памяти, верните
fastapi.responses.Response
с вашим обычай
content
а также
media_type
.
@app.get(
"/image",
# Specifying the media type here is just for the OpenAPI specification. See:
# fastapi.tiangolo.com/advanced/additional-responses/#additional-media-types-for-the-main-response
responses = {
200: {
"content": {"image/png": {}}
}
}
)
def get_image()
image_bytes: bytes = generate_cat_picture()
return Response(content=image_bytes, media_type="image/png")
Если ваше изображение существует только в файловой системе, верните
fastapi.responses.FileResponse
вместо.
См. Раздел Custom Response в документации FastAPI.
Предлагают другие ответы. Я не рекомендую это делать, если вы не уверены, что не можете использовать или
FileResponse
, потому что его труднее использовать правильно.
В частности, такой код бессмысленен. Это не будет "потоковое" изображение каким-либо полезным способом.
@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")
Прежде всего,
StreamingResponse(content=my_iterable)
потоки путем итерации по кускам, предоставленным
my_iterable
. Но когда эта итерация
BytesIO
, фрагменты будут
\n
-завершенные строки , что не имеет смысла для двоичного изображения.
И даже если разделение на фрагменты имело смысл, разбиение на фрагменты здесь бессмысленно, потому что у нас был весь
image_bytes
bytes
объект доступен с самого начала. С таким же успехом мы могли просто передать все это в
Response
с самого начала. Мы ничего не получаем, удерживая данные от FastAPI.
Второй,
StreamingResponse
соответствует кодировке фрагментированной передачи HTTP . (Это может зависеть от вашего сервера ASGI, но , по крайней мере, так дело с обстоитUvicorn .) И это не лучший вариант использования кодирования фрагментированной передачи.
Кодирование с фрагментированной передачей имеет смысл, когда вы заранее не знаете размер вывода и не хотите ждать, чтобы собрать все данные, чтобы узнать, прежде чем вы начнете отправлять его клиенту. Это может относиться к таким вещам, как обслуживание результатов медленных запросов к базе данных, но обычно не относится к обслуживанию изображений.
Ненужное кодирование передачи фрагментов может быть вредным. Например, это означает, что клиенты не могут видеть индикаторы выполнения при загрузке файла. Видеть:
У меня была аналогичная проблема, но с изображением cv2. Это может быть полезно для других. Использует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")
Все остальные ответы по существу, но теперь так легко вернуть изображение
from fastapi.responses import FileResponse
...
@app.get("/")
async def main():
return FileResponse("your_image.jpeg")
Это еще не задокументировано должным образом, но вы можете использовать что угодно из Starlette.
Итак, вы можете использовать FileResponse
если это файл на диске с путем: https://www.starlette.io/responses/
Если это файлоподобный объект, созданный в вашей операции пути, в следующей стабильной версии Starlette (используемой внутри FastAPI) вы также сможете вернуть его в виде StreamingResponse
,
Благодаря ответу @biophetik с важным напоминанием, которое вызвало у меня замешательство: если вы используетеBytesIO
особенно с PIL/skimage, не забудьте также сделать img.seek(0)
перед возвращением!
@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,)})
Мои потребности не были полностью удовлетворены из вышеперечисленного, потому что мой образ был создан с помощью PIL. Моя конечная точка fastapi принимает имя файла изображения, считывает его как изображение PIL и создает в памяти миниатюру jpeg, которую можно использовать в HTML, например:
<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")
Если, следуя верхнему ответу, вы пытаетесь вернуть такой объект BytesIO в свой
buffer = BytesIO(my_data)
# Return file
return Response(content=buffer, media_type="image/jpg")
Вы можете получить сообщение об ошибке, похожее на это (как описано в этом комментарии)
AttributeError: '_io.BytesIO' object has no attribute 'encode'
Это вызваноrender
функционировать вResponse
который явно проверяет наличиеbytes
набери сюда . СBytesIO != bytes
он пытается закодировать значение и терпит неудачу.
Решение состоит в том, чтобы получить значение байтов из объекта BytesIO с помощью getvalue().
buffer = BytesIO(my_data)
# Return file
return Response(content=buffer.getvalue(), media_type="image/jpg")
Вы можете использоватьFileResponse
если это файл на диске с
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!"}
Вы можете сделать что-то очень похожее в 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")