Build an Image Classification API Using Django Rest Framework.

Build an Image Classification API Using Django Rest Framework.

Overview:

Machine Learning (ML) and data science applications are in high demand. When ML algorithms offer information before it is known, the benefits for business are significant. Integrating machine learning algorithms for inference into production systems is a technological barrier hence the need for deploying ML models as API'S.

Introduction

In this short article, we implement AI based models to detect COVID-19 in Chest X-rays and CT Scans using four Deep Learning Algorithms: VGG16, ResNet50, InceptionV3 and Xception. Note, we shall focus mainly on implementing the api and not the model creation.

To test my live endpoint, send a POST request to the following URL with an x-ray image appended to the body of the request. You will get the following sample output if request was successful:

https://alienx.tech/api/v1/xray # for the x-ray images
https://alienx.tech/api/v1/ct # for CT scans
{
    "status": "success",
    "data": {
        "asset_id": "3e978ba830fb266978af20f2bf816f5e",
        "public_id": "vacpxfywfohgfprwhrso",
        "version": 1637699139,
        "version_id": "c9017c7d3b28ce797edefec0b0d72796",
        "signature": "e9c632b832e773cbbcb8906f93aba1d9e859d4bf",
        "width": 1205,
        "height": 1395,
        "format": "png",
        "resource_type": "image",
        "created_at": "2021-11-23T20:25:39Z",
        "tags": [],
        "bytes": 1325222,
        "type": "upload",
        "etag": "86005d3c34202b10949db5569570cd16",
        "placeholder": false,
        "url": "http://res.cloudinary.com/prometheusapi/image/upload/v1637699139/vacpxfywfohgfprwhrso.png",
        "secure_url": "https://res.cloudinary.com/prometheusapi/image/upload/v1637699139/vacpxfywfohgfprwhrso.png",
        "original_filename": "covid-19-pneumonia-22",
        "api_key": "138196782467569"
    },
    "url": "http://res.cloudinary.com/prometheusapi/image/upload/v1637699139/vacpxfywfohgfprwhrso.png",
    "xception_chest_pred": "100.00% COVID",
    "inception_chest_pred": "100.00% COVID",
    "vgg_chest_pred": "100.00% COVID",
    "resnet_chest_pred": "100.00% COVID"
}

ML Model Building

The dataset for the project was gathered from two open source Github repositories:

Four algorithms: VGG16, ResNet50, InceptionV3 and Xception were trained separately on Chest X-rays and CT Scans, giving us a total of 8 deep learning models. 80% of the images were used for training the models and the remaining 20% for testing the accuracy of the models.

The code for training the 8 models is available on the creators github or my repository. The model for the project can be found on the following google drive.

Turning the Model into an RESTFUL API

Following Python best practices, we will create a virtual environment for our project, and install the required packages.

First, create the project directory.

$ mkdir djangoapp
$ cd djangoapp

Now, create a virtual environment and install the required packages.

For macOS and Unix systems:

$ python3 -m venv myenv
$ source myenv/bin/activate
(myenv) $ pip install django requests djangorestframework tensorflow cloudinary opencv-python

For Windows:

$ python3 -m venv myenv
$ myenv\Scripts\activate
(myenv) $ pip install django requests djangorestframework tensorflow cloudinary opencv-python

Setting Up Your Django Application

First, navigate to the directory djangoapp we created and establish a Django project.

(myenv) $ django-admin startproject mainapp

This will auto-generate some files for your project skeleton:

mainapp/
    manage.py
    mainapp/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

Now, navigate to the directory you just created (make sure you are in the same directory as manage.py) and create your app directory.

(myenv) $ python manage.py startapp monitor

This will create the following:

monitor/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

On the mainapp/settings.py file, look for the following line and add the app we just created above.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',#new line
    'monitor', #new line
]

Ensure you are in the monitor directory then create a new directory called templates and a new file called urls.py. Your directory structure of monitor application should look like this

monitor/
    __init__.py
    admin.py
    apps.py
    migrations/
    templates/
        __init__.py
    models.py
    tests.py
    urls.py
    views.py

Ensure your mainapp/urls.py file, add our monitor app URL to include the URLs we shall create next on the monitor app:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    #path('admin/', admin.site.urls),
    path('', include('monitor.urls')),#monitor app url
]

Now, on the monitor/urls.py file, add our website there:

from django.urls import path
from .views import *

urlpatterns = [
    path('api/upload/xray', UploadView.as_view(), name = 'prediction'),
    path('api/upload/ct', CTUploadView.as_view(), name = 'ct_prediction'),
]

Let’s create another directory to store our machine learning model. I’ll also add the dataset to the project for those who want to achieve the whole dataset. (It is not compulsory to create a data folder.)

(venv)$ mkdir ml
(venv)$ mkdir ml/models
(venv)$ mkdir ml/data

We also need to tell Django where our machine learning model is located and also add our cloudinary configuration there. Add these lines to settings.py file:

import os
import cloudinary

cloudinary.config( 
  cloud_name = "prometheusapi", 
  api_key = "GETYOURAPIKEY", 
  api_secret = "GETYOURAPIKEY" 
)

MODELS = os.path.join(BASE_DIR, 'ml/models')

Load Keras Model through apps.py

Load your machine learning models in apps.py so that when the application starts, the trained model is loaded only once. Otherwise, the trained model is loaded each time an endpoint is called, and then the response time will be slower.

Let’s update apps.py

import os
from django.apps import AppConfig
from django.conf import settings
from tensorflow.keras.models import load_model
from tensorflow import keras


class ResNetModelConfig(AppConfig):
    name = 'resnetAPI'
    MODEL_FILE = os.path.join(settings.MODELS, "resnet_chest.h5")
    model = keras.models.load_model(MODEL_FILE)

class ResNetCTModelConfig(AppConfig):
    name = 'resnetCTAPI'
    MODEL_FILE = os.path.join(settings.MODELS, "resnet_ct.h5")
    model = keras.models.load_model(MODEL_FILE)

class VGGModelConfig(AppConfig):
    name = 'vggAPI'
    MODEL_FILE = os.path.join(settings.MODELS, "vgg_chest.h5")
    model = keras.models.load_model(MODEL_FILE)

class VGGCTModelConfig(AppConfig):
    name = 'vggCTAPI'
    MODEL_FILE = os.path.join(settings.MODELS, "vgg_ct.h5")
    model = keras.models.load_model(MODEL_FILE)    

class InceptionModelConfig(AppConfig):
    name = 'inceptionv3_chestAPI'
    MODEL_FILE = os.path.join(settings.MODELS, "inceptionv3_chest.h5")    
    model = keras.models.load_model(MODEL_FILE)

class InceptionCTModelConfig(AppConfig):
    name = 'inceptionv3_chestCTAPI'
    MODEL_FILE = os.path.join(settings.MODELS, "inception_ct.h5")    
    model = keras.models.load_model(MODEL_FILE)    

class ExceptionModelConfig(AppConfig):
    name = 'xception_chestAPI'
    MODEL_FILE = os.path.join(settings.MODELS, "xception_chest.h5")    
    model = keras.models.load_model(MODEL_FILE)

class ExceptionCTModelConfig(AppConfig):
    name = 'xception_chestCTAPI'
    MODEL_FILE = os.path.join(settings.MODELS, "xception_ct.h5")    
    model = keras.models.load_model(MODEL_FILE)

Edit views.py

The last step is to update views.py. The views will be mainly responsible for two tasks:

  • Process incoming POST requests.
  • Make a prediction with the incoming data and give the result as a Response.
import urllib
from django.shortcuts import render
import numpy as np
from .apps import *
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, JSONParser
import cloudinary.uploader
import matplotlib.pyplot as plt
import cv2

# Create your views here.
class UploadView(APIView):
    parser_classes = (
        MultiPartParser,
        JSONParser,
    )

    @staticmethod
    def post(request):
        file = request.data.get('picture')
        upload_data = cloudinary.uploader.upload(file)
        #print(upload_data)
        img = upload_data['url']


        #load models
        resnet_chest = ResNetModelConfig.model
        vgg_chest = VGGModelConfig.model
        inception_chest = InceptionModelConfig.model
        xception_chest = ExceptionModelConfig.model

        req = urllib.request.urlopen(img)
        arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
        image = cv2.imdecode(arr, -1) # 'Load it as it is'
        #image = cv2.imread('upload_chest.jpg') # read file 
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # arrange format as per keras
        image = cv2.resize(image,(224,224))
        image = np.array(image) / 255
        image = np.expand_dims(image, axis=0)

        resnet_pred = resnet_chest.predict(image)
        probability = resnet_pred[0]
        #print("Resnet Predictions:")
        if probability[0] > 0.5:
            resnet_chest_pred = str('%.2f' % (probability[0]*100) + '% COVID') 
        else:
            resnet_chest_pred = str('%.2f' % ((1-probability[0])*100) + '% NonCOVID')
        #print(resnet_chest_pred)

        vgg_pred = vgg_chest.predict(image)
        probability = vgg_pred[0]
        #print("VGG Predictions:")
        if probability[0] > 0.5:
            vgg_chest_pred = str('%.2f' % (probability[0]*100) + '% COVID') 
        else:
            vgg_chest_pred = str('%.2f' % ((1-probability[0])*100) + '% NonCOVID')
        #print(vgg_chest_pred)

        inception_pred = inception_chest.predict(image)
        probability = inception_pred[0]
        #print("Inception Predictions:")
        if probability[0] > 0.5:
            inception_chest_pred = str('%.2f' % (probability[0]*100) + '% COVID') 
        else:
            inception_chest_pred = str('%.2f' % ((1-probability[0])*100) + '% NonCOVID')
        #print(inception_chest_pred)

        xception_pred = xception_chest.predict(image)
        probability = xception_pred[0]
        #print("Xception Predictions:")
        if probability[0] > 0.5:
            xception_chest_pred = str('%.2f' % (probability[0]*100) + '% COVID') 
        else:
            xception_chest_pred = str('%.2f' % ((1-probability[0])*100) + '% NonCOVID')
        #print(xception_chest_pred)
        return Response({
            'status': 'success',
            'data': upload_data,
            'url':img,
            'xception_chest_pred':xception_chest_pred,
            'inception_chest_pred':inception_chest_pred,
            'vgg_chest_pred':vgg_chest_pred,
            'resnet_chest_pred':resnet_chest_pred,
        }, status=201)


class CTUploadView(APIView):
    parser_classes = (
        MultiPartParser,
        JSONParser,
    )

    @staticmethod
    def post(request):
        file = request.data.get('picture')
        upload_data = cloudinary.uploader.upload(file)
        #print(upload_data)
        img = upload_data['url']


        #load models
        resnet_chest = ResNetCTModelConfig.model
        vgg_chest = VGGCTModelConfig.model
        inception_chest = InceptionCTModelConfig.model
        xception_chest = ExceptionCTModelConfig.model

        req = urllib.request.urlopen(img)
        arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
        image = cv2.imdecode(arr, -1) # 'Load it as it is'
        #image = cv2.imread('upload_chest.jpg') # read file 
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # arrange format as per keras
        image = cv2.resize(image,(224,224))
        image = np.array(image) / 255
        image = np.expand_dims(image, axis=0)

        resnet_pred = resnet_chest.predict(image)
        probability = resnet_pred[0]
        #print("Resnet Predictions:")
        if probability[0] > 0.5:
            resnet_chest_pred = str('%.2f' % (probability[0]*100) + '% COVID') 
        else:
            resnet_chest_pred = str('%.2f' % ((1-probability[0])*100) + '% NonCOVID')
        #print(resnet_chest_pred)

        vgg_pred = vgg_chest.predict(image)
        probability = vgg_pred[0]
        #print("VGG Predictions:")
        if probability[0] > 0.5:
            vgg_chest_pred = str('%.2f' % (probability[0]*100) + '% COVID') 
        else:
            vgg_chest_pred = str('%.2f' % ((1-probability[0])*100) + '% NonCOVID')
        #print(vgg_chest_pred)

        inception_pred = inception_chest.predict(image)
        probability = inception_pred[0]
        #print("Inception Predictions:")
        if probability[0] > 0.5:
            inception_chest_pred = str('%.2f' % (probability[0]*100) + '% COVID') 
        else:
            inception_chest_pred = str('%.2f' % ((1-probability[0])*100) + '% NonCOVID')
        #print(inception_chest_pred)

        xception_pred = xception_chest.predict(image)
        probability = xception_pred[0]
        #print("Xception Predictions:")
        if probability[0] > 0.5:
            xception_chest_pred = str('%.2f' % (probability[0]*100) + '% COVID') 
        else:
            xception_chest_pred = str('%.2f' % ((1-probability[0])*100) + '% NonCOVID')
        #print(xception_chest_pred)
        return Response({
            'status': 'success',
            'data': upload_data,
            'url':img,
            'xceptionCT_chest_pred':xception_chest_pred,
            'inceptionCT_chest_pred':inception_chest_pred,
            'vggCT_chest_pred':vgg_chest_pred,
            'resnetCT_chest_pred':resnet_chest_pred,
        }, status=201)

Testing our API

Create the necessary migrations then run the server:

(myenv) $ python manage.py makemigrations
(myenv) $ python manage.py migrate
(myenv) $ python manage.py runserver

Fire up Postman and make a POST request with an image appended to the body.

polo.png

Thanks for staying tuned!