Skip to content

jgrieb/object-detection-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

object-detection-service

This project demonstrates how to provide object detection as a microservice based on a trained deep learning model using the detectron2 framework. Furthermore the microservice

An example implementation is made for the use case of plant organ detection, using the trained model for this purpose from @2younis and his repo 2younis/plant-organ-detection. His work is described in the following publication:

Younis et al. (2020): Detection and Annotation of Plant Organs from Digitized Herbarium Scans using Deep Learning https://arxiv.org/abs/2007.13106

The idea is that an image can be sent to the microservice via http, which then applies the trained model on the image and returns the detected plant organ instances in JSON format. Also an example is given of this service could interact with a Cordra instance.

Setup

Requirement: Python3.8 (!),(higher Python versions would require a change of the current dependency config) with the venv module installed

# copy this repository
git clone https://github.com/jgrieb/object-detection-service
# create a virtual environment
python3 -m venv object-detection-service
cd object-detection-service && source bin/activate
# install dependencies
pip install -r requirements.txt
# if you run into an error during installation of the pycocotools
# try running the following (depending on the Python version on the machine):
# sudo apt-get install python3.8-dev python3-opencv
# Then try again: pip install -r requirements.txt

Afterwards download a trained .pth model for the object detection and save it in the data folder as model_final.pth. For this example we use the model from @2younis mentioned above for plant organ detection.

wget https://github.com/2younis/plant-organ-detection/releases/download/v1.0/model_final.pth -P data/

Finally customize the config/custom_model_config.yaml according to the trained model and set the CLASS_LIST parameter and the configuration loading in main.py accordingly. This is already preconfigured for the plant organ detection example.

For interaction with Cordra you will also need to cp config/cordraConfig.json.template config/cordraConfig.json and configure, see below Interaction with Cordra for further information.

Run

Run the service with bin/uvicorn main:app (you might want to add --host 0.0.0.0 --port 8000 or another configuration)

You can then either access it by passing a url to an image: curl -XPOST localhost:8000/object-detection -H "Content-Type: application/json" -d '{"url":"http://oxalis.br.fgov.be/images/BR0/000/013/701/604/BR0000013701604.jpg"}'

Or by uploading the image directly: curl -XPOST localhost:8000/object-detection/image-upload -F 'payload=@images/BR0000013701604.jpg'

Return type

By default JSON is returned, but you can change this so that the service returns the image with the detected bounding boxes drawn on top (in this case you should specify the output file with the -o flag)

curl -XPOST localhost:8000/object-detection -H "Content-Type: application/json" -d '{"url":"http://oxalis.br.fgov.be/images/BR0/000/013/701/604/BR0000013701604.jpg", "returnType":"image"}' -o output.jpg

curl -XPOST localhost:8000/object-detection/image-upload -F 'payload=@images/BR0000013701604.jpg' -F 'returnType=image' -o output.jpg

Returned JSON data has the following format:

{
  "success":true,
  "instances":[
    {
      "class":"leaf",
      "score":0.9999428987503052,
      "boundingBox":[2470,3957,3229,4348]
    }
  ]
}

where the boundingBox are pixel coordinates of [xmin, ymin, xmax, ymax].

Documentation

Thanks to the fastapi framework an OpenApi-conformant documentation of the REST Api is autogenerated when running the service and can be accessed under localhost:8000/docs. See here for further explanation of the parameters.

Interaction with Cordra

This microservice is desigend for interaction with Digital Objects managed in Cordra. The lifecycle hooks of the Digital Object can be used to automatically trigger the object detection when a new image is uploaded as a payload to a Digital Object. Currently the setup is in that way that this microservice uploads new Digital Objects of type AnnotationList when objects are detected and links them to the object of origin. For this a service account with Cordra access is required: cp config/cordraConfig.json.template config/cordraConfig.json to copy the template and then put in the credentials there. An example lifecycle configuration for the Digital Object type is given in the following

// Nashorn JDK cannot use XmlHttpRequest, therefore we must use Java Http requests
// see for reference: https://gist.github.com/billybong/a462152889b6616deb02

var cordra = require("cordra");
var cordraBaseUri = cordra.get('design').content.handleMintingConfig.baseUri;
// ensure that ends with a slash
if (cordraBaseUri[cordraBaseUri.length - 1] !== '/'){
    cordraBaseUri += '/'
}
var imageServiceUri = 'http://localhost:8000/object-detection';

exports.afterCreateOrUpdate = afterCreateOrUpdate;

exports.methods = {};

exports.methods.getAnnotations = getAnnotations;
exports.methods.getAnnotations.allowGet = true;

function getAnnotations(object, context){
    if (Array.isArray(object.payloads) && object.payloads.length > 0){
        var query = 'type:AnnotationList AND /ods\\:linkedDigitalObject:' + object.id + ' AND (';
        for(var i=0; i<object.payloads.length; i++){
            var payloadName = object.payloads[i].name;
            var payloadUrl = cordraBaseUri + 'objects/' + object.id + '?payload=' + payloadName;
            var payloadUrlEscaped = payloadUrl.replace(/([\!\*\+\&\|\(\)\[\]\{\}\^\~\?\:\"])/g, "\\$1");
            if(i>0){
                query += ' OR ';
            }
            query += '/ods\\:imageURI:"' + payloadUrlEscaped + '"';
        }
        query += ')';
        var queryResult = cordra.search(query);
        return queryResult.results;
    }
    return [];
}

function afterCreateOrUpdate(object, context) {
    // todo: need validation of uploaded payload type and appropriate handling
    if (Array.isArray(object.payloads) && object.payloads.length > 0){
        for(var i=0; i<object.payloads.length; i++){
            var payloadName = object.payloads[i].name;
            var payloadUrl = cordraBaseUri + 'objects/' + object.id + '?payload=' + payloadName;
            var payloadUrlEscaped = payloadUrl.replace(/([\!\*\+\&\|\(\)\[\]\{\}\^\~\?\:\"])/g, "\\$1");
            var query = 'type:AnnotationList AND /ods\\:linkedDigitalObject:' + object.id;
            query += ' AND /ods\\:imageURI:"' + payloadUrlEscaped + '"';
            // search if AnnotationLists for the image exist already in Cordra
            var queryResult = cordra.search(query);
            // if not send to the microservice for object detection
            if (queryResult.results.length === 0){
                var data = JSON.stringify({
                    "linkedDigitalObjectId": object.id,
                    "url": payloadUrl,
                    "returnType": "cordra",
                    "runAsync": true
                });
                var con = new java.net.URL(imageServiceUri).openConnection();
                con.setConnectTimeout(10000); // 10 seconds
                con.setReadTimeout(10000); // 10 seconds
                var response = {};
                try {
                    write(con, data);
                    response = JSON.parse(read(con.inputStream));
                } catch (e){
                    console.log("HTTP Error: ", e);
                }
            }
        }
    }
}

function read(inputStream){
    var inReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));
    var inputLine;
    var response = new java.lang.StringBuffer();

    while ((inputLine = inReader.readLine()) !== null) {
           response.append(inputLine);
    }
    inReader.close();
    return response.toString();
}

function write(con, data){
    con.requestMethod = "POST";
    con.setRequestProperty( "Content-Type", "application/json; charset=utf-8");
    con.doOutput=true;
    var wr = new java.io.DataOutputStream(con.outputStream);
    wr.writeBytes(data);
    wr.flush();
    wr.close();
}

Outlook

The generated AnnotationList currently does not make use of a controlled RDF vocabulary, this should be changed in the future and when decided uploaded in json-ld format.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages