Shinobi/plugins/deepstack-object
Elad Bar 19db81485c //
// Shinobi - DeepStack Face Recognition Plugin
// Copyright (C) 2021 Elad Bar
//
// Base Init >>
const { spawn } = require('child_process');
const fs = require('fs');
const request = require("request");
const moment = require('moment');
const config = require('./conf.json');

let s = null;

const {
  workerData
 } = require('worker_threads');

const isWorker = workerData && workerData.ok === true;
const pluginBasePath = isWorker ? "pluginWorkerBase.js" : "pluginBase.js";

for(let i = 0; i < 2; i++) {
    try {
  s = require(`../${pluginBasePath}`)(__dirname, config);

 } catch(err) {
  console.log(err);

        s = null;
 }
}

if(s === null) {
    console.log(config.plug, `Plugin start has failed. ${pluginBasePath} was not found.`);
} else {
    if(!isWorker) {
        const {
            haltMessage,
            checkStartTime,
            setStartTime,
        } = require('../pluginCheck.js');

        if(!checkStartTime()) {
            console.log(haltMessage, new Date());
            s.disconnectWebSocket();
        }

        setStartTime();
    }
}
// Base Init />>

let detectorSettings = null;

const DETECTOR_TYPE_FACE = 'face';
const DETECTOR_TYPE_OBJECT = 'object';

const FACE_UNKNOWN = 'unknown';
const DEEPSTACK_API_KEY = 'api_key';

const DETECTOR_CONFIGUTATION = {
    face: {
        detectEndpoint: '/vision/face/recognize',
        startupEndpoint: '/vision/face/list',
        key: 'userid'
    },
    object: {
        detectEndpoint: '/vision/detection',
        key: 'label'
    }
}

const PROTOCOLS = {
    true: "https",
    false: "http"
};

const log = (logger, message) => {
    logger(`${moment().format()} [${config.plug}] ${message}`);
}

const logError = (message) => {
    log(console.error, message);
};

const logWarn = (message) => {
    log(console.warn, message);
};

const logInfo = (message) => {
    log(console.info, message);
};

const logDebug = (message) => {
    log(console.debug, message);
};

const postMessage = (data) => {
    const message = JSON.stringify(data);

 logInfo(message);
};

const initialize = () => {
    const deepStackProtocol = PROTOCOLS[config.deepStack.isSSL];

    baseUrl = `${deepStackProtocol}://${config.deepStack.host}:${config.deepStack.port}/v1`;

    const detectionType = config.plug.split("-")[1].toLowerCase();
    const detectorConfig = DETECTOR_CONFIGUTATION[detectionType];
    const detectorConfigKeys = Object.keys(detectorConfig);

    detectorSettings = {
        type: detectionType,
        active: false,
        baseUrl: baseUrl,
        apiKey: config.deepStack.apiKey,
        jobs: []
 };

    if(detectionType === DETECTOR_TYPE_FACE) {
        detectorSettings["registeredPersons"] = config.persons === undefined ? [] : config.persons;
    }

 detectorConfigKeys.forEach(k => detectorSettings[k] = detectorConfig[k]);

    const testRequestData = getFormData(detectorSettings.detectEndpoint);

    request.post(testRequestData, (err, res, body) => {
        try {
            if(err) {
                throw err;
            }

            const response = JSON.parse(body);

            if(response.error) {
                detectorSettings.active = !response.error.endsWith('endpoint not activated');
            } else {
                detectorSettings.active = response.success;
            }

            const detectorSettingsKeys = Object.keys(detectorSettings);

   const pluginMessageHeader = [];
            pluginMessageHeader.push(`${config.plug} loaded`);

   const configMessage = detectorSettingsKeys.map(k => `${k}: ${detectorSettings[k]}`);

            const fullPluginMessage = pluginMessageHeader.concat(configMessage);

            const pluginMessage = fullPluginMessage.join(", ");

   logInfo(pluginMessage);

            if (detectorSettings.active) {
                s.detectObject = detectObject;

                if(detectionType === DETECTOR_TYPE_FACE) {
                    const requestData = getFormData(detectorSettings.startupEndpoint);
                    const requestTime = getCurrentTimestamp();

                    request.post(requestData, (errStartup, resStartup, bodyStartup) => {
                        if (!!resStartup) {
                            resStartup.duration = getDuration(requestTime);
                        }

                        onFaceListResult(errStartup, resStartup, bodyStartup);
                    });
                }
            }
        } catch(ex) {
            logError(`Failed to initialize ${config.plug} plugin, Error: ${ex}`)
        }
    });
};

const processImage = (imageB64, d, tx, frameLocation, callback) => {
 if(!detectorSettings.active) {
        return;
    }

    try{
        const imageStream = fs.createReadStream(frameLocation);

        const form = {
   image: imageStream,
            min_confidence: 0.7
  };

  const requestData = getFormData(detectorSettings.detectEndpoint, form);

        const requestTime = getCurrentTimestamp();

  request.post(requestData, (err, res, body) => {
            if (!!res) {
                res.duration = getDuration(requestTime);
            }

            onImageProcessed(d, tx, err, res, body, imageB64);

            fs.unlinkSync(frameLocation);

            removeJob(d.ke, d.id);
  });
 }catch(ex){
        removeJob(d.ke, d.id);

  logError(`Failed to process image, Error: ${ex}`);

        if(fs.existsSync(frameLocation)) {
            fs.unlinkSync(frameLocation);
        }
 }

 callback();
};
const getJobKey = (groupId, monitorId) => {
    const jobKey = `${groupId}_${monitorId}`;

    return jobKey;
}

const addJob = (groupId, monitorId) => {
    const jobKey = getJobKey(groupId, monitorId);
    const jobExists = detectorSettings.jobs.includes(jobKey);

    if(!jobExists) {
        detectorSettings.jobs.push(jobKey);
    }

    return !jobExists;
}

const removeJob = (groupId, monitorId) => {
    const jobKey = getJobKey(groupId, monitorId);

    detectorSettings.jobs = detectorSettings.jobs.filter(j => j !== jobKey);
}

const detectObject = (frameBuffer, d, tx, frameLocation, callback) => {
 if(!detectorSettings.active) {
        return;
    }

    const jobCreated = addJob(d.ke, d.id);

    if(!jobCreated) {
        return;
    }

    const dirCreationOptions = {
        recursive: true
    };

    d.dir = `${s.dir.streams}${d.ke}/${d.id}/`;

    frameLocation = `${d.dir}${s.gid(5)}.jpg`;

    if(!fs.existsSync(d.dir)) {
        fs.mkdirSync(d.dir, dirCreationOptions);
    }

    fs.writeFile(frameLocation, frameBuffer, function(err) {
        if(err) {
            removeJob(d.ke, d.id);

            return s.systemLog(err);
        }

        try {
            const imageB64 = frameBuffer.toString('base64');

            processImage(imageB64, d, tx, frameLocation, callback);

        } catch(ex) {
            removeJob(d.ke, d.id);

            logError(`Detector failed to parse frame, Error: ${ex}`);
        }
    });
};

const getCurrentTimestamp = () => {
    const currentTime = new Date();
    const currentTimestamp = currentTime.getTime();

    return currentTimestamp
};

const getDuration = (requestTime) => {
    const currentTime = new Date();
    const currentTimestamp = currentTime.getTime();

    const duration = currentTimestamp - requestTime;

    return duration;
};

const onFaceListResult = (err, res, body) => {
    const duration = !!res ? res.duration : 0;

    try {
        const response = JSON.parse(body);

        const success = response.success;
        const facesArr = response.faces;
        const faceStr = facesArr.join(",");

        if(success) {
            logInfo(`DeepStack loaded with the following faces: ${faceStr}, Response time: ${duration} ms`);
        } else {
            logWarn(`Failed to connect to DeepStack server, Error: ${err}, Response time: ${duration} ms`);
        }
    } catch(ex) {
        logError(`Error while connecting to DeepStack server, Error: ${ex} | ${err}, Response time: ${duration} ms`);
    }
};

const onImageProcessed = (d, tx, err, res, body, imageStream) => {
    const duration = !!res ? res.duration : 0;

    let objects = [];

    try {
        if(err) {
            throw err;
        }

        const response = JSON.parse(body);

        const success = response.success;

        if(success) {
            const predictions = response.predictions;

            if(predictions !== null && predictions.length > 0) {
                objects = predictions.map(p => getDeepStackObject(p)).filter(p => !!p);

                if(objects.length === 0) {
                    logInfo(`Processed image for ${detectorSettings.type} on monitor ${d.id} returned no results, Response time: ${duration} ms`);
                } else {
                    const identified = objects.filter(p => p.tag !== FACE_UNKNOWN);
                    const unknownCount = objects.length - identified.length;

                    if(unknownCount > 0) {
                        logInfo(`{d.id}$ detected ${unknownCount} unknown ${detectorSettings.type}s, Response time: ${duration} ms`);
                    }

                    if(identified.length > 0) {
                        const detectedObjectsStrArr = [];

                        if (detectorSettings.type === DETECTOR_TYPE_FACE) {
                            identified.forEach(f => detectedObjectsStrArr.push(`${f.tag} (${f.person}) [${(f.confidence * 100).toFixed(2)}]`));
                        } else {
                            identified.forEach(f => detectedObjectsStrArr.push(`${f.tag} [${(f.confidence * 100).toFixed(2)}]`));
                        }

                        const detectedObjectsStr = detectedObjectsStrArr.join(", ");

                        logInfo(`${d.id} detected ${detectorSettings.type}s: ${detectedObjectsStr}, Response time: ${duration} ms`);
                    }

                    const isObjectDetectionSeparate = d.mon.detector_pam === '1' && d.mon.detector_use_detect_object === '1';
                    const width = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_y_object ? d.mon.detector_scale_y_object : d.mon.detector_scale_y);
                    const height = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_x_object ? d.mon.detector_scale_x_object : d.mon.detector_scale_x);

                    const eventData = {
                        f: 'trigger',
                        id: d.id,
                        ke: d.ke,
                        details: {
                            plug: config.plug,
                            name: d.id,
                            reason: detectorSettings.type,
                            matrices: objects,
                            imgHeight: width,
                            imgWidth: height,
                            time: duration,
                            imageStream: imageStream
                        }
                    };

                    tx(eventData);
                }
            }
        } else {
            logWarn(`Processed image for ${detectorSettings.type} on monitor ${d.id} failed, Reason: ${response.error}, Response time: ${duration} ms`);
        }
    } catch(ex) {
        logError(`Error while processing image, Error: ${ex} | ${err}, Response time: ${duration} ms, Body: ${body}`);
    }

    return objects
};

const getFormData = (endpoint, additionalParameters) => {
    const formData = {};

    if(detectorSettings.apiKey) {
        formData[DEEPSTACK_API_KEY] = detectorSettings.apiKey;
    }

    if(additionalParameters !== undefined && additionalParameters !== null) {
        const keys = Object.keys(additionalParameters);

        keys.forEach(k => formData[k] = additionalParameters[k]);
    }

    const requestData = {
        url: `${detectorSettings.baseUrl}${endpoint}`,
        time: true,
        formData: formData
    };

    return requestData;
};

const getDeepStackObject = (prediction) => {
    if(prediction === undefined) {
        return null;
    }

    const tag = prediction[detectorSettings.key];

    const confidence = prediction.confidence ?? 0;
    const y_min = prediction.y_min ?? 0;
    const x_min = prediction.x_min ?? 0;
    const y_max = prediction.y_max ?? 0;
    const x_max = prediction.x_max ?? 0;
    const width = x_max - x_min;
    const height = y_max - y_min;

    const obj = {
        x: x_min,
        y: y_min,
        width: width,
        height: height,
        tag: tag,
        confidence: confidence
    };

    if (detectorSettings.type === DETECTOR_TYPE_FACE) {
        const matchingPersons = detectorSettings.registeredPersons.filter(p => tag.startsWith(p))
        const person = matchingPersons.length > 0 ? matchingPersons[0] : null;

        obj["person"] = person;
    }

    return obj;
};

initialize();
2022-12-11 08:53:04 +02:00
..
.gitignore DeepStack Object Detection Plugin by Elad Bar 2021-05-19 11:33:51 -07:00
Dockerfile Add deepstack face plugin and updated deepstack object, support also for CodeProject.AI 2022-11-24 17:34:55 +02:00
INSTALL.sh DeepStack Object Detection Plugin by Elad Bar 2021-05-19 11:33:51 -07:00
README.md Add Tensorflow plugin Docker Image 2021-05-27 00:38:20 -07:00
conf.sample.json Add deepstack face plugin and updated deepstack object, support also for CodeProject.AI 2022-11-24 17:34:55 +02:00
init.sh Docker image for DeepStack Plugin 2021-05-27 00:02:05 -07:00
package-lock.json Add deepstack face plugin and updated deepstack object, support also for CodeProject.AI 2022-11-24 17:34:55 +02:00
package.json Add deepstack face plugin and updated deepstack object, support also for CodeProject.AI 2022-11-24 17:34:55 +02:00
pm2.yml Docker image for DeepStack Plugin 2021-05-27 00:02:05 -07:00
shinobi-deepstack-object.js // 2022-12-11 08:53:04 +02:00

README.md

Shinobi Video plugin for DeepStack Object Detection

How to Install DeepStack Object Detection on GPU

This document has been rewritten over on ShinobiHub Articles.

Docker Installation

Install Shinobi Plugin with Docker

Image is based on node:12.22.1-buster-slim.

  1. Enter plugin directory. Default Shinobi installation location is /home/Shinobi.
cd /home/Shinobi/plugins/deepstack-object
  1. Build Image.
docker build --tag shinobi-deepstack-object-image:1.0 .
  1. Launch the plugin.
  • -e ADD_CONFIG='{"key":"123mypluginkey","host":"172.16.100.238","port":8080,"deepStack":{"host":"172.16.100.238","port":5000,"isSSL":false,"apiKey":"123"}}' Adds any configuration parameters to the plugin's conf.json file.
  • -p '8082:8082/tcp' is an optional flag if you decide to run the plugin in host mode.
docker run -d --name='shinobi-deepstack-object' -e ADD_CONFIG='{"key":"123mypluginkey","host":"172.16.100.238","port":8080,"deepStack":{"host":"172.16.100.238","port":5000,"isSSL":false,"apiKey":"123"}}' shinobi-deepstack-object-image:1.0

** Logs **

docker logs /shinobi-deepstack-object

** Stop and Remove **

docker stop /shinobi-deepstack-object && docker rm /shinobi-deepstack-object

Options (Environment Variables)

Option Description Default
ADD_CONFIG The plugin's name. DeepStack-Object

Additional Information

Docker - Get docker

DeepStack - Getting started

Run DeepStack CPU docker image:

sudo docker run -e VISION-FACE=True -e VISION-DETECTION=True -v localstorage:/datastore -p 80:5000 deepquestai/deepstack

GPU installation guide

More installation options

Windows (CPU / GPU support)

nVidia Jetson

Raspberry PI