Implementando API de Imagens com FastAPI
Tabela de conteúdos
Transferência de Estilo com Flutter e TensorFlow - Este artigo faz parte de uma série de artigos.
Este é o quinto post da série Transferência de Estilo com Flutter, FastAPI e TensorFlow. Nesta série, estou documentando minha experiência desenvolvendo uma aplicação de Machine Learning (ML) do início ao fim (end-to-end).
- Parte 1 - Transferência de Estilo com Flutter e TensorFlow
- Parte 2 - Primeiros Passos com Flutter
- Parte 3 - Flutter e suporte ao TensorFlow Lite
- Parte 4 - Machine Learning no Device ou Servidor
Acabei sendo mais rápido para implementar a aplicação do que para publicar no blog. Se você quiser, já pode acessar o código completo no GitHub.
Introdução #
Utilizar FastAPI não estava no planejamento inicial do projeto mas, devido a falta de suporte nativo ao TensorFlow Lite no Flutter, decidi mover nosso modelo de Machine Learning do device para um servidor backend.
Vamos utilizar FastAPI para implementar o backend já que, desta forma, podemos executar nosso modelo diretamente no ambiente Python e disponilizá-lo como um simples request de uma API REST.
FastAPI #
FastAPI é um framework Python que promete entregar aplicações de alta performance e prontas para produção com uma síntaxe facil de aprender e rápido de implementar.
FastAPI framework, high performance, easy to learn, fast to code, ready for production (fastapi.tiangolo.com)
Duas características me surpreenderam logo que comecei meus testes: design e performance. Vou focar nestas duas, mas você pode encontrar a lista completa de features diretamente na documentação.
Design #
O que mais me chamou atenção quanto ao design está relacionado ao sistema de anotações do Python (Type Hints) e a biblioteca Pydantic. A partir das anotações Python, FastAPI faz a validação da API e a conversão automática dos seus dados de entrada e saída.
Isso significa que você pode receber um JSON complexo e ele será convertido automaticamente em um objeto Python, com uso de modelos Pydantic, respeitando tipos como: str, int, float, list, datetime, etc.
O inverso também é válido, você retorna um objeto Python complexo em seu código e FastAPI fará a conversão para um “tipo web” como HTML, JSON, XML, Plain Text, File, Streaming, etc.
Outra característica sensacional é a geração automática de documentação de padrão aberto OpenAPI (anteriormente conhecida como Swagger) e JSON Schema. Sem nenhum código adicional, você tem uma documentação completa e que permite testes de forma interativa.
Uma última vantagem de FastAPI e a imposição de Type Hints com Pydantic, é que sistemas de auto-completar de editores como Pycharm e VSCode sempre funcionam. Sua IDE será capaz de fornecer informações como assinaturas de metódos, atributos ou tipos de dados enquanto você está programando.
Performance #
A alta performance está diretamente relacionada às bibliotecas Starlette e Uvicorn, que FastAPI utiliza internamente.
Uvicorn é um Servidor Python ASGI - Asynchronous Server Gateway Interface (sucessor do WSGI) que utiliza uvloop para a execução de aplicações assíncronas.
Starlette é um framework baseado em Uvicorn que fornece algumas abstrações úteis para o desenvolvimento web como rotas, responses, events, CORS, Session, Cookie, etc. Além disso, Starlette suporta Web Sockets e GraphQL.
FastAPI é uma terceira camada de abstração implementada por cima de Starlette e Uvicorn. Ele tem suporte a todas as features anteriores, mais abstrações e features adicionais como validação de dados e documentação automática.
Em benchmarks, FastAPI fica em terceiro lugar em relação à performance de libs Python para web, atrás apenas das próprias libs Uvicorn e Starlette. Entretanto, é preciso lembrar que estas três bibliotecas fornecem níveis diferentes de abstrações e facilidades para o desenvolvimento web.
(fonte: TechEmpower Web Framework Benchmarks)
Utilizar apenas Uvicorn trará o máximo de performance em Python para web, mas o mínimo de features. Starlette será um meio termo entre performance e features. FastAPI será mais completo em features, porém menos performático.
Performance: Uvicorn > Starlette > FastAPI
Abstrações: FastAPI > Starlette > Uvicorn
Resumindo, quanto mais especializada uma biblioteca, mais rápida ela é, quanto mais abrangente, menos performática. Uvicorn, Starlette e FastAPI estão em uma hierarquia e não competindo entre si. Starlette estende as features de Uvicorn e FastAPI estende as features de Starlette (e Uvicorn).
FastAPI adiciona um nível excelente de abstrações ao custo de alguma performance (comparado a Uvicorn e Starlette), ainda assim, sendo mais rápido que outros frameworks Python como AIOHTTP, Bottle, Flask e Django.
Instalação #
Antes de começar com nossa implementação, vamos preparar o ambiente de desenvolvimento. É bem simples instalar FastAPI, você só precisa ter o Python instalado e do comando pip.
Algumas distribuições Linux já vêm com Python por padrão, se este não for seu caso, você pode baixar o instalador diretamente do site oficial.
Antes de instalar qualquer biblioteca no seu sistema, recomendo criar um ambiente virtual com venv, conda, pipenv ou poetry, assim você não corre o risco de bagunçar sua instalação Python principal.
Então vamos começar criando uma pasta para nosso projeto.
mkdir style_transfer
cd style_transfer
E depois criar e ativar um ambiente virtual. Eu estou usando venv
, mas você pode usar o que achar mais conveniente.
python3 -m venv env
source env/bin/activate
Se os comandos deram certo, você terá uma pasta style_transfer
e dentro outra pasta chamada env
. A pasta env
é onde está sua nova instalação Python, criada especialmente para este projeto.
Sua linha de comando também deve estar diferente, iniciando com (env)
. Rodando o comando pip freeze
, você verá que sua instalação Python está complementamente limpa, não haverá nenhuma biblioteca listada.
pip freeze
Com o ambiente virtual pronto, já podemos instalar FastAPI. É possível selecionar quais dependências serão instaladas, mas para simplificar as instruções vamos instalar todas elas com o seletor [all]
.
pip install "fastapi[all]"
Se você rodar o comando pip freeze
novamente, agora terá uma lista de pacotes instalados, entre eles uvicorn
, starlette
e pydantic
(que citei anteriormente), mais alguns pacotes bem populares como urllib3
, requests
, PyYAML
, etc.
REST API #
Com FastAPI instalado, nosso objetivo é desenvolver uma API que receba e retorne imagens. Assim, estaremos prontos para integrar nosso modelo TensorFlow nas próximas etapas.
Quando estamos implementando algo novo, é importante resolvermos um problema de cada vez, de preferência em etapas incrementais, que podem ser testadas e nos darão a sensação de progresso.
Vamos começar criando um arquivo chamado main.py
que irá conter toda a lógica relacionada à nossa API.
touch main.py
Com o arquivo criado, vamos implementar nossos dois métodos: GET e POST.
GET status #
Podemos começar programando um método GET que retorne uma mensagem qualquer. Com um editor de texto de sua preferência, adicione as seguintes linhas de código ao arquivo main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
async def root():
return {'status': 'ok'}
Explicando o código, importamos FastAPI e criamos nosso app. Após, registramos a raiz do nosso projeto '/'
à função root, que não tem parâmetros e retorna um dicionário com status ok.
Somente com essas linhas, já temos uma aplicação que retorna um JSON em um endpoint GET. Podemos testar o código servindo o app com uvicorn:
uvicorn main:app --reload
Você encontrará a mensagem de status acessando http://127.0.0.1:8000/
. Também é possível acessar a documentação OpenAPI e testar seu backend de forma interativa diretamente em http://127.0.0.1:8000/docs
.
A especificação OpenAPI também pode ser utilizada para a geração automática de código dos clientes do seu backend.
POST Image #
Agora vamos implementar um método POST que receba uma imagem do cliente e a devolve logo em seguida, sem manter nada no servidor.
Este é o endpoint que utilizaremos para nossa transferência de estilo, então já vamos chamá-lo /style
.
Vamos receber a imagem como um arquivo e devolvê-la como um Streaming. Também vamos utilizar io.BytesIO
para manter nossa imagem em memória.
import io
from fastapi import FastAPI, File
from starlette.responses import StreamingResponse
app = FastAPI()
@app.get('/')
async def root():
return {'status': 'ok'}
@app.post('/style')
async def predict(img_bytes: bytes = File(...)):
img = io.BytesIO(img_bytes)
img.seek(0)
return StreamingResponse(
img,
media_type="image/jpg",
)
Explicando o código, importamos File
e Streaming
e registramos o caminho /style
à função predict
. Isso significa que toda vez que alguém acessar a URL http://localhost:8000/style
, a função predict
será invocada.
Desta vez, a função recebe um conteúdo em bytes
que é armazenado na variável img_bytes
. Com o uso da anotação Python File(...)
, dizemos para o FastAPI que estes bytes devem vir do upload de um arquivo.
Na primeira linha da função, os bytes são carregados em memória. Logo em seguida, o ponteiro de leitura e escrita é enviado de volta ao início do lista de bytes img.seek(0)
. Precisamos fazer isso pois o StreamingResponse
irá iterar novamente por essa lista.
Finalmente, a imagem é enviada de volta ao cliente. Isto é feito retornando o conteúdo em bytes
da variável img
utilizando StreamingResponse
e especificando o retorno como image/jpg
.
Se voltarmos à documentação, agora temos dois endpoints: GET e POST. O método POST está associado à URL /style
e recebe um parâmetro img_bytes
do tipo bytes
, assim como acabamos de implementar em nosso código.
Ao clicar em Try it out
, é possível fazer o upload de uma imagem e, ao enviar a request para o servidor, o resultado é a própria imagem, com status 200
e content-type image/jpg
.
Basicamente, esta é toda a implementação que precisamos para nossa API, além de alguns testes unitários que acabamos não escrevendo no tutorial.
Próximos passos #
Os passos que precisamos seguir na próxima etapa são:
- estender o endpoint
/style
para receber duas imagens, uma para o conteúdo e uma para o estilo que será aplicado - adaptar o Notebook original integrando TensorFlow ao backend FastAPI
- utilizar as imagens recebidas na API para aplicar a transferência de estilo
- retornar a imagem estilizada
O texto já está um pouco longo, então vamos deixá-los para um novo post.
Se você estiver gostando da série pode curtir, deixar um comentário ou compartilhar com seus amigos.
Até a próxima!