Commit Graph

3236 Commits (cf0881aa79a226288d6b880fed537b7082c0187f)

Author SHA1 Message Date
Moe 8e3ddd43f7 Update Plugin Manager with command buttons
- Add Command buttons for additional script launches.
2022-12-11 12:27:32 -08:00
Moe 7770c812cd prettify plugin install output in UI 2022-12-11 09:40:04 -08:00
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
Elad Bar aa2a43e79a prevent same event source (groupId + monitorId) to run in parallel in the same plugin, aligned DeepStack object detection plugin 2022-12-11 08:52:51 +02:00
Elad Bar 95de2f6348 Merge branch 'dev' into generic-face-manager
# Conflicts:
#	libs/system/utils.js
#	libs/webServerSuperPaths.js
#	tools/modifyConfiguration.js
2022-12-10 18:11:47 +02:00
Moe b87ff353e7 Merge branch 'docker-configuration' into 'dev'
fix configuration get/set for docker

See merge request Shinobi-Systems/Shinobi!423
2022-12-10 16:04:35 +00:00
Elad Bar ea304dc5f2 fix configuration get/set for docker 2022-12-10 11:31:46 +02:00
Moe c0f9d0f45b Update en_CA.json 2022-12-09 23:07:39 -08:00
Moe f9487adb7b Update cuda-11.sh 2022-12-09 23:06:53 -08:00
Moe e6276f8dd6 add default options for downloadable plugins 2022-12-09 23:00:01 -08:00
Moe a691a50023 cleanup plugin install handler in ui 2022-12-09 22:59:31 -08:00
Moe f0529b978c fix plugins auto load 2022-12-09 22:58:35 -08:00
Moe a08a0654c9 Update plugins .gitignore 2022-12-09 19:36:46 -08:00
Moe ffd50c1d1f parse input_maps in builder 2022-12-09 15:42:14 -08:00
Elad Bar 3bbd0d30db reload DeepStack / CodeProject AI data after registering / unregistering 2022-12-09 22:42:11 +02:00
Elad Bar 57d2e3879d Fix plugin configuration 2022-12-09 22:17:27 +02:00
Elad Bar a7f70803d6 Fix register / unregister 2022-12-09 21:18:10 +02:00
Elad Bar 330c6b751a Update configuration save / restore for docker 2022-12-09 21:17:49 +02:00
Elad Bar 335d2598ec fix submit client side failure 2022-12-09 16:39:32 +02:00
Elad Bar 38a6b47f11 Add enableFaceManager configuration parameter, fix missing fields in configuration editor, removed those are available in configuration but without definition in schema 2022-12-09 12:44:42 +02:00
Moe e8dec1c0c1 Merge branch 'dev' of https://gitlab.com/Shinobi-Systems/Shinobi into dev 2022-12-08 08:50:55 -08:00
Moe 53ab4841fc Merge branch 'fix-pipeline' into 'dev'
Add CI/CD rules to reduce the number of pipelines running

See merge request Shinobi-Systems/Shinobi!422
2022-12-08 16:46:50 +00:00
Moe e0a3523c67 reverse "Immediate exit requested" operations 2022-12-08 08:46:36 -08:00
Moe 4f523cce92 parse stream_channels in builder 2022-12-08 08:45:48 -08:00
Elad Bar abe366080c Add default Dockerfile to the rule 2022-12-08 17:36:54 +02:00
Elad Bar b868e3dc8e Add CI/CD rules to reduce the number of pipelines running 2022-12-08 17:35:25 +02:00
Elad Bar be7132ae23 Merge branch 'dev' into 'generic-face-manager'
# Conflicts:
#   plugins/deepstack-face/shinobi-deepstack-face.js
#   plugins/deepstack-object/shinobi-deepstack-object.js
2022-12-08 12:42:02 +00:00
Elad Bar f2fa2c1832 Add support for DeepStack / CodeProject AI to work with the face manager UI 2022-12-08 14:02:56 +02:00
Elad Bar 837bae92cc removed unused parameter 2022-12-07 23:28:54 +02:00
Elad Bar 300624f61f Face manager is full working 2022-12-07 23:26:16 +02:00
Moe 37fab5f635 minor cleanup 2022-12-06 22:45:35 -08:00
Moe 9191eb8a08 Merge branch 'dev' of https://gitlab.com/Shinobi-Systems/Shinobi into dev 2022-12-06 20:53:19 -08:00
Moe b69af2b66d remove old 777 chmods 2022-12-06 15:24:16 -08:00
Moe a5d04b5d17 Read status for videos re-added to Videos Table 2022-12-06 15:00:23 -08:00
Moe 888e0380c7 Fix GUI bugs with Videos Table
Thanks Nocona!
2022-12-06 12:11:48 -08:00
Moe 8553cbfa03 Fix mobile not scrolling over sideMenu monitor list 2022-12-06 12:00:32 -08:00
Moe 33146b4272 Update LICENSE.md 2022-12-06 12:00:14 -08:00
Moe 507955f2f4 Merge branch 'fix-docker-plugins' into 'dev'
Removed docker volume of plugins

See merge request Shinobi-Systems/Shinobi!420
2022-12-06 14:40:39 +00:00
Moe 8bb3d81935 Merge branch 'deeptack-plugins-use-buffer' into 'dev'
Aligned both plugins to use frameBuffer, not b64 encoded image

See merge request Shinobi-Systems/Shinobi!419
2022-12-06 14:40:25 +00:00
Elad Bar 1158902e1d removed docker volume of plugins 2022-12-06 12:34:59 +02:00
Elad Bar 3cf2852f05 Aligned both plugins to use frameBuffer, not b64 encoded image 2022-12-06 11:49:18 +02:00
Moe 302c87f186 Highlight Previewed Video in Videos Table and FileBin 2022-12-05 15:23:52 -08:00
Moe 006fa2d9cc Fix Master Mute for Live Grid 2022-12-05 14:55:14 -08:00
Moe 2086f3554b Add Zip and Download to Videos, FileBin, and Time-lapse Frames 2022-12-04 19:24:04 -08:00
Moe e2c867cc76 fix protocol check for embed api 2022-12-04 10:00:02 -08:00
Moe 1043a2b71c Fix #237 #66 2022-12-04 08:27:46 -08:00
Moe 971c1f045a Fix #130 2022-12-03 18:23:41 -08:00
Moe a028f8684b Fix Input Type JPEG snap grabber 2022-12-03 17:33:29 -08:00
Moe f42be60135 Update monitor.js 2022-12-03 12:11:53 -08:00
Moe 67f08b0246 Fix #312 2022-12-03 12:01:55 -08:00