1

I am trying to deploy an image classification model on server using FastAPI.

As such, I have two issues related to my code.

The first issue is that in the original code (without using FastAPI), I would read an image using OpenCV and then convert it from BGR to RGB. Not doing this conversion would give me inaccurate results at test time.

Using FastAPI, the image is being read as follows:

def read_image(payload):
    stream=BytesIO(payload)
    image=np.asarray(bytearray(stream.read()),dtype="uint8")
    image=cv2.imdecode(image,cv2.IMREAD_COLOR)
    if isinstance(image,np.ndarray):
        img=Image.fromarray(image)
    return img

The second issue I am facing is with the POST method when I run the server, and accessing the URL http:127.0.0.1:9999/, the GET method is running, which prints the following message:

Welcome to classification server

However, when I execute the post method shown below:

@app.post("/classify/")
async def classify_image(file:UploadFile=File(...)):
    #return "File Uploaded."
    image_byte=await file.read()
    return classify(image_byte)

When I go to the link http:127.0.0.1:9999/classify/ I end up recieving the error:

method not allowed

Any reasons on why this is happening and what can be done to fix the error? The full code is listed below. If there are any errors that I am missing in this, please let me know. I am new to FastAPI and as such, I am really confused about this.

from fastapi import FastAPI, UploadFile, File
import uvicorn
import torch
import torchvision
from torchvision import transforms as T
from PIL import Image
from build_effnet import build_model
import torch.nn.functional as F
import io
from io import BytesIO
import numpy as np
import cv2

app = FastAPI()

class_name = ['F_AF', 'F_AS', 'F_CA', 'F_LA', 'M_AF', 'M_AS', 'M_CA', 'M_LA']
idx_to_class = {i: j for i, j in enumerate(class_name)}
class_to_idx = {value: key for key, value in idx_to_class.items()}

test_transform = T.Compose([
                             
                             #T.Resize(size=(224,224)), # Resizing the image to be 224 by 224
                             #T.RandomRotation(degrees=(-20,+20)), #NO need for validation
                             T.ToTensor(), #converting the dimension from (height,weight,channel) to (channel,height,weight) convention of PyTorch
                             T.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]) # Normalize by 3 means 3 StD's of the image net, 3 channels
])

#Load model
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = build_model()
model.load_state_dict(torch.load(
    'CharacterClass_effnet_SGD.pt', map_location='cpu'))
model.eval()
model.to(device)

def read_image(payload):
    stream=BytesIO(payload)
    image=np.asarray(bytearray(stream.read()),dtype="uint8")
    image=cv2.imdecode(image,cv2.IMREAD_COLOR)
    if isinstance(image,np.ndarray):
        img=Image.fromarray(image)
    return img

def classify(payload):
    img=read_image(payload)
    img=test_transform(img)
    with torch.no_grad():
        ps=model(img.unsqueeze(0))
        ps=F.softmax(ps,dim=1)
        topk,topclass=ps.topk(1,dim=1)
    x=topclass.view(-1).cpu().view()
    return idx_to_class[x[0]]
        
@app.get("/")
def get():
    return "Welcome to classification server."

@app.post("/classify/")
async def classify_image(file:UploadFile=File(...)):
    #return "File Uploaded."
    image_byte=await file.read()
    return classify(image_byte)
Chris
  • 18,724
  • 6
  • 46
  • 80
  • What do you mean by _when I go to the link_? You open this link in browser? – sudden_appearance Apr 13 '22 at 08:08
  • yes i am opening the link in browser – Sparsh Garg Apr 13 '22 at 08:21
  • Please have a look at [here](https://stackoverflow.com/a/70657621/17865804) and [here](https://stackoverflow.com/a/70640522/17865804) (**Method 3** - **Test with Fetch or Axios**) on how to upload a file, as well as [here](https://stackoverflow.com/a/71639658/17865804) on how to send back a numpy array / image bytes. – Chris Apr 13 '22 at 08:39
  • Ok I will @Chris,is it okay to reach out to you if I run into issues. – Sparsh Garg Apr 13 '22 at 10:04

1 Answers1

0

Your code has defined the classify route on POST requests. Your browser will only perform GET requests from the url

If you expect the browser to work, use @app.get("/classify/"). However, you'll need a different way to provide the file argument, which as a query parameter and a file path (not recommended for security reasons).

If you want POST requests to work, test your code with curl or Postman

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
  • i tried doing @app.get("/classify/"),that gives me the following errror {"detail":[{"loc":["body","file"],"msg":"field required","type":"value_error.missing"}]} Also,any suggestions on how I can send image to this via curl/postman let's say my image is img_1.jpg – Sparsh Garg Apr 13 '22 at 08:18
  • I tried using curl -L -F "file=@model_2_0.jpg" http://127.0.0.1:9999/classify/ that ended up giving an internal server error.Any ideas why that happened – Sparsh Garg Apr 13 '22 at 08:23
  • Well, you'd need to look at the logs of the server rather than its response to the client, but you're still using a GET method, there – OneCricketeer Apr 13 '22 at 08:28
  • I'm not sure what `-F` flag does. Perhaps you wanted `--data` or `-d`? – OneCricketeer Apr 13 '22 at 08:30
  • I changed the method back to post and then used curl to test the code. The model's prediction come out to be {},which is strange because when I test the code without fastapi,the prediction usually comes out to be a class – Sparsh Garg Apr 13 '22 at 08:30
  • Sorry, not really sure. Haven't used these prediction libraries, or fastapi, really. This answer is exclusively for "method not allowed" response – OneCricketeer Apr 13 '22 at 08:31
  • 1
    Don't change `@app.post(...)` to `@app.get(...)`, create another function for that. You have file field required inside post request, so you are asked to pass it – sudden_appearance Apr 13 '22 at 08:32
  • using the flag --data gives me the following error {"detail":[{"loc":["body","file"],"msg":"Expected UploadFile, received: ","type":"value_error"}]} – Sparsh Garg Apr 13 '22 at 08:32
  • ok thanks OneCricketeer,atleast now I am able to get something.I just need to look into how to get the correct prediction.If anyone can give anymore suggestions on this that will be helpful – Sparsh Garg Apr 13 '22 at 08:34
  • tried passing the file through curl ,but it seems that the file is not being read because file.filename comes out to be null. I was passing -L which was causing the issue. – Sparsh Garg Apr 13 '22 at 08:37
  • Reading the Fastapi docs, it expects a form request, and that's what -F flag is for, but you'll also need to add `-X POST` https://stackoverflow.com/a/28045514/2308683 – OneCricketeer Apr 13 '22 at 08:39