概要
画像を入力とし, 画像を返すAPIをFastAPIで実装する. (FastAPIでAPI部分を実装するより, jsでStream APIを使用するほうが大変であった)
実装
準備
$pip install pillow fastapi jinja2 aiofiles python-multipart uvicorn
API側の実装
画像処理の内容は入力された画像をタイル状に並べるだけのかんたんな処理
UploadFile
のリストとして受け取り, StreamingResponse
として返す.
データの読み書きはByteIO
を使用して行う.
ByteIO
を使用してpillow
の画像を返す場合は, 返す前にseek
で先頭に戻す必要がある.
@app.post('/api/image-processing') async def create_image_processing(files: List[UploadFile] = File(...)): # open image bytes_io = BytesIO(files[0].file.read()) image = Image.open(bytes_io).convert('RGB') # image processing data = np.array(image) h, w, _ = data.shape h = int(h // 2) * 2 w = int(w // 2) * 2 data = data[:h, :w, :] \ .reshape(h // 2, 2, w // 2, 2, -1) \ .transpose(1, 0, 3, 2, 4) \ .reshape(h, w, -1) content = BytesIO() Image.fromarray(data).save(content, format='png') content.seek(0) # response return StreamingResponse(content, media_type='image/png')
フロント側の実装
画像を送信して, そのレスポンスのReadableStream
を処理して表示する処理.
MDN Web Docsのサンプルをそのまま使用している.
postBtn.addEventListener("click", () => { // if (!imageFile) { console.error("no image file"); return; } const body = new FormData(); body.append("files", imageFile); fetch("/api/image-processing", { method: "POST", body: body, }) .then((resp) => { const reader = resp.body.getReader(); return new ReadableStream({ start(controller) { return pump(); function pump() { return reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } controller.enqueue(value); return pump(); }); } }, }); }) .then((stream) => new Response(stream)) .then((resp) => resp.blob()) .then((blob) => { document.getElementById("output-img").src = URL.createObjectURL(blob); }); });