From b8761a0275eb83afb7fa193fdffc0f208e5b738c Mon Sep 17 00:00:00 2001 From: danecreekphotography Date: Mon, 18 Jan 2021 11:12:17 -0800 Subject: [PATCH] Add support for custom Deepstack models (#417) * Initial implementation * Schema update * Fix name of property in schema * More schema experiments * Update changelog --- CHANGELOG.md | 4 ++++ src/DeepStack.ts | 8 ++++++-- src/Trigger.ts | 13 ++++++++++++- src/TriggerManager.ts | 1 + src/schemas/triggerConfiguration.schema.json | 12 ++++++++++-- src/types/ITriggerJson.ts | 1 + 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b243841..988e571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## Unreleased + +- Add support for `customEndpoint` on a trigger to support custom Deepstack models. Resolves [issue 416](https://github.com/danecreekphotography/node-deepstackai-trigger/issues/416). + ## 5.6.1 - Address a minor security vulnerability in a dependency. Resolves [issue 407](https://github.com/danecreekphotography/node-deepstackai-trigger/issues/407). diff --git a/src/DeepStack.ts b/src/DeepStack.ts index c174000..4933976 100644 --- a/src/DeepStack.ts +++ b/src/DeepStack.ts @@ -9,15 +9,19 @@ import * as Settings from "./Settings"; import request from "request-promise-native"; import IDeepStackResponse from "./types/IDeepStackResponse"; -export default async function analyzeImage(fileName: string): Promise { +export default async function analyzeImage(fileName: string, endpoint?: string): Promise { // This method of calling DeepStack comes from https://nodejs.deepstack.cc/ const imageStream = fs.createReadStream(fileName); const form = { image: imageStream }; + const deepstackUri = endpoint + ? new URL(endpoint, Settings.deepstackUri) + : new URL("/v1/vision/detection", Settings.deepstackUri); + const rawResponse = await request .post({ formData: form, - uri: new URL("/v1/vision/detection", Settings.deepstackUri).toString(), + uri: deepstackUri.toString(), }) .catch(e => { throw Error(`Failed to call DeepStack at ${Settings.deepstackUri}: ${e.error}`); diff --git a/src/Trigger.ts b/src/Trigger.ts index 1580ad0..72eb398 100644 --- a/src/Trigger.ts +++ b/src/Trigger.ts @@ -37,6 +37,7 @@ export default class Trigger { * Use the incrementAnalyzedFiles() method to update the total. */ public analyzedFilesCount = 0; + public customEndpoint?: string; public cooldownTime: number; public enabled = true; public name: string; @@ -76,7 +77,7 @@ export default class Trigger { private async analyzeImage(fileName: string): Promise { log.verbose(`Trigger ${this.name}`, `${fileName}: Analyzing`); const startTime = new Date(); - const analysis = await analyzeImage(fileName).catch(e => { + const analysis = await analyzeImage(fileName, this.customEndpoint).catch(e => { log.warn(`Trigger ${this.name}`, e); return undefined; }); @@ -283,6 +284,16 @@ export default class Trigger { * @returns True if the trigger is activated by the label */ public isRegisteredForObject(fileName: string, label: string): boolean { + // If a custom endpoint is specified then watchObjects don't apply and it is assumed + // the trigger is always registered for the detected object. + if (this.customEndpoint) { + log.verbose(`Trigger ${this.name}`, `${fileName}: Custom endpoint matched triggering object ${label}`); + return true; + } + + // In the far more common, normal, case where no custom endpoint is specified + // check and see if the detected object is in the list of registered objects + // to watch. const isRegistered = this.watchObjects?.some(watchLabel => { return watchLabel.toLowerCase() === label?.toLowerCase(); }); diff --git a/src/TriggerManager.ts b/src/TriggerManager.ts index cb94819..720f7ce 100644 --- a/src/TriggerManager.ts +++ b/src/TriggerManager.ts @@ -78,6 +78,7 @@ export function loadConfiguration(configurations: IConfiguration[]): IConfigurat triggers = triggerConfigJson.triggers.map(triggerJson => { log.info("Triggers", `Loaded configuration for ${triggerJson.name}`); const configuredTrigger = new Trigger({ + customEndpoint: triggerJson.customEndpoint, cooldownTime: triggerJson.cooldownTime, enabled: triggerJson.enabled ?? true, // If it isn't specified then enable the camera name: triggerJson.name, diff --git a/src/schemas/triggerConfiguration.schema.json b/src/schemas/triggerConfiguration.schema.json index 3e5e146..d4f5633 100644 --- a/src/schemas/triggerConfiguration.schema.json +++ b/src/schemas/triggerConfiguration.schema.json @@ -24,8 +24,11 @@ "title": "Trigger", "description": "A trigger that responds to DeepStack predictions.", "type": "object", - "anyOf": [{ "required": ["watchPattern"] }, { "required": ["snapshotUri"] }], - "required": ["handlers", "name", "watchObjects"], + "allOf": [ + { "anyOf": [{ "required": ["watchPattern"] }, { "required": ["snapshotUri"] }] }, + { "oneOf": [{ "required": ["watchObjects"] }, { "required": ["customEndpoint"] }] }, + { "required": ["handlers", "name"] } + ], "additionalProperties": false, "properties": { "name": { @@ -45,6 +48,11 @@ "maximum": 600, "type": "integer" }, + "customEndpoint": { + "description": "Custom endpoint for DeepStack, used to call a custom model. When specified watchObjects is ignored.", + "examples": ["/v1/vision/custom/my_custom_model_name?image"], + "type": "string" + }, "enabled": { "description": "Set to true to enable the trigger. If false the trigger will be ignored.", "examples": [true], diff --git a/src/types/ITriggerJson.ts b/src/types/ITriggerJson.ts index 2c19d75..a8585e5 100644 --- a/src/types/ITriggerJson.ts +++ b/src/types/ITriggerJson.ts @@ -11,6 +11,7 @@ import IPushbulletConfigJson from "../handlers/pushbulletManager/IPushoverConfig export default interface ITriggerJson { cooldownTime: number; + customEndpoint?: string; enabled: boolean; name: string; snapshotUri: string;