I have two problems actually. The first is that I am running a background task in my api that is taking an image and predicting on it. . For some reason I cannot store the background task in a variable and return it. I need to do this for the second part of my problem.
API Code:
from starlette.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
from fastapi import FastAPI, File, UploadFile, BackgroundTasks
from tensorflow.keras import preprocessing
from fastapi.staticfiles import StaticFiles
from keras.models import load_model
from PIL import Image
import numpy as np
import uvicorn
app = FastAPI()
app.mount("/Templates", StaticFiles(directory="Templates"), name="Templates")
templates = Jinja2Templates(directory="Templates")
model_dir = 'F:\\Saved-Models\\Dog-Cat-Models\\json_function_test_dog_cat_optuna.h5'
model = load_model(model_dir)
def predict_image(image):
pp_dogcat_image = Image.open(image.file).resize((150, 150), Image.NEAREST).convert("RGB")
pp_dogcat_image_arr = preprocessing.image.img_to_array(pp_dogcat_image)
input_arr = np.array([pp_dogcat_image_arr])
prediction = np.argmax(model.predict(input_arr), axis=-1)
if str(prediction) == '[1]':
answer = "It's a Dog"
else:
answer = "It's a Cat"
return answer
@app.get('/')
async def index():
return RedirectResponse(url="/Templates/index.html")
# Background tasks are so that we can return a response regardless how long it takes to process image data
@app.post('/prediction_page')
async def prediction_form(background_tasks: BackgroundTasks, dogcat_img: UploadFile = File(...)):
answer = background_tasks.add_task(predict_image, image=dogcat_img)
return answer
if __name__ == '__main__':
uvicorn.run(app, host='localhost', port=8000)
The second issue is that I am trying to pass this back into my html file in the form of a Jinja tag. If I can get to the point of storing my background task, I would like to return it to the actual html template. I have looked far and wide and FastAPI has useless information to do exactly this with a get.post.
HTML Code:
<div class="prediction_box"><p>Select an image of a dog or a cat and the AI will print out a prediction of what he
thinks
it is.....</p><br>
<!--enctype="multipart/form-data" enables UploadFile data to pass through-->
<form action="/prediction_page" enctype="multipart/form-data" method="post">
<label for="image-upload" class="custom-file-upload">Select Image:</label>
<input type="file" id="image-upload" name="dogcat_img"><br>
<input class="custom-submit-button" type="submit">
</form>
<p>{{answer}}</p>
</div>
CodePudding user response:
I agree with @MatsLindh's comment, you probably need a task queue system like celery to schedule your image prediction tasks. This will also help with separation of concerns, so the application serving HTTP won't have to deal with ML tasks.
So, a background task runs after returning a response first:
...
from fastapi import BackgroundTasks
...
def predict_image(image):
...
@app.post('/prediction_page')
async def prediction_form(background_tasks: BackgroundTasks, dogcat_img: UploadFile = File(...)):
background_tasks.add_task(predict_image, image=dogcat_img)
return {"message": "Processing image in the background"}
But in your case you want to return the prediction results to the user immediately and since your task is CPU bound, ideally it should run in another process so it's not blocking other requests.
You can take two approaches here:
Implement the PRG (Post/Redirect/Get) deign pattern so you have an index page
/
with the HTML form, images are send to/prediction_page
endpoint and the request is redirected to/result
page to show the results. You'll probably have to store the results in a database or find some other way to pass and display them on the/results
page.Build a JavaScript SPA app (Single Page App) that will use JS to make the HTTP requests to the backend.
With that being said, here is a simple example that will use ProcessPoolExecutor to make the prediction and JS Fetch method to get and display the results:
main.py:
import asyncio
import random
import uvicorn
from concurrent.futures import ProcessPoolExecutor
from fastapi import FastAPI, File, Request, UploadFile
from fastapi.concurrency import run_in_threadpool
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory=".")
executor = None
@app.on_event("startup")
async def startup():
global executor
executor = ProcessPoolExecutor()
@app.on_event("shutdown")
async def shutdown():
global executor
executor.shutdown()
def predict_image(image):
return random.choice(("It's a Dog", "It's a Cat"))
@app.get("/", response_class=HTMLResponse)
def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/prediction_page")
async def prediction_form(dogcat_img: UploadFile = File(...)):
loop = asyncio.get_running_loop()
# For images larger than 1MB the contents are written to disk and
# a true file-like object is returned that can't be pickled,
# use run_in_threadpool instead in that case
result = await loop.run_in_executor(executor, predict_image, dogcat_img)
# result = await run_in_threadpool(predict_image, dogcat_img)
return {"prediction": result}
if __name__ == "__main__":
uvicorn.run("main:app", host="localhost", port=8000, reload=True)
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<div class="prediction_box">
<p>Select an image of a dog or a cat and the AI will print out a prediction of what he thinks it is.....
</p>
<br>
<!--enctype="multipart/form-data" enables UploadFile data to pass through-->
<form id="uploadform" action="/prediction_page" enctype="multipart/form-data" method="post">
<label for="image-upload" class="custom-file-upload">Select Image:</label>
<input type="file" id="image-upload" name="dogcat_img"><br>
<input class="custom-submit-button" type="submit">
</form>
<p id="result"></p>
</div>
<script>
const p = document.getElementById("result");
uploadform.onsubmit = async (e) => {
e.preventDefault();
p.innerHTML = "Processing image...";
let res = await fetch("/prediction_page", {
method: "POST",
body: new FormData(uploadform),
});
if (res.ok) {
let result = await res.json();
p.innerHTML = result["prediction"];
} else {
p.innerHTML = `Response error: ${res.status}`;
};
};
</script>
</body>
</html>