Commit Graph

3056 Commits (13aca4591dabbcb22bb9959efb296cb27ddbbb31)

Author SHA1 Message Date
Moe 13aca4591d mqtt outbound send snapshot as base64 2022-12-15 16:53:05 -08:00
Moe f177e8e097 Add Face Manager language vars 2022-12-15 16:52:12 -08:00
Moe a5843f1050 make config.webBlocksPreloaded modifiable by customAutoLoad 2022-12-15 16:51:45 -08:00
Moe 9cbd305654 Fix embed page over p2p+ 2022-12-15 16:51:15 -08:00
Moe b61fc21901 Fix webdav delete 2022-12-15 09:22:49 -08:00
Moe 1dac4512f0 add onEventBasedRecordingStart extender+ 2022-12-14 18:51:44 -08:00
Moe af557e4daa add onEventBasedRecordingStart extender 2022-12-14 18:50:08 -08:00
Moe a9458f5476 Fix embed page over p2p 2022-12-14 15:40:28 -08:00
Moe 1a4704268f allow setting clientId for MQTT Outbound 2022-12-14 12:40:07 -08:00
Moe 309a73fdf4 make monitor lists alphabetical+ 2022-12-14 10:14:54 -08:00
Moe 14014c65b8 clean up plugin manager row display 2022-12-14 10:14:31 -08:00
Moe 2d242e8cb5 move customAutoLoad block load in superuser 2022-12-14 10:14:05 -08:00
Moe a1ebb15f27 Update .gitignore 2022-12-14 10:13:29 -08:00
Moe 1dc5096e26 remove old plugins 2022-12-14 10:12:55 -08:00
Moe 42ee884819 Revert "Merge branch 'dev' of https://gitlab.com/Shinobi-Systems/Shinobi into dev"
This reverts commit 0a35c99af5, reversing
changes made to 7bc9f754e8.
2022-12-13 20:38:25 -08:00
Moe af41ff626c Reset Sub-Account form on creation 2022-12-13 17:02:52 -08:00
Moe 0a35c99af5 Merge branch 'dev' of https://gitlab.com/Shinobi-Systems/Shinobi into dev 2022-12-13 16:14:55 -08:00
Moe 7bc9f754e8 Update modifyConfiguration.js 2022-12-13 12:46:23 -08:00
Moe 3a24b7453f sort monitor list selectors alphabetically 2022-12-13 12:04:03 -08:00
Moe b4ed7ff086 Merge branch 'generic-face-manager' into 'dev'
Face manager as generic solution within super dashboard

See merge request Shinobi-Systems/Shinobi!421
2022-12-13 19:25:55 +00:00
Moe afadceed12 Fix !194 2022-12-13 09:37:53 -08:00
Moe 1491acab2f properly stop plugin process 2022-12-11 20:36:22 -08:00
Moe 45e33e9aa7 Update cuda-11.sh 2022-12-11 19:30:38 -08:00
Moe e6ccfd777c Plugin Manager prettified 2022-12-11 19:30:36 -08:00
Moe 87cd22e7bf Get plugin list from cdn 2022-12-11 15:41:59 -08:00
Moe f88092dd67 remove hardcoded tester for plugins 2022-12-11 14:48:16 -08:00
Moe 9309a9ca98 Merge branch 'dev' of https://gitlab.com/Shinobi-Systems/Shinobi into dev 2022-12-11 12:33:55 -08:00
Moe 04be5de1dd minor code cleanup for plugins ui 2022-12-11 12:33:53 -08:00
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