@blueharford/scrypted-spatial-awareness 0.6.12 → 0.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/main.nodejs.js.map +1 -1
- package/dist/plugin.zip +0 -0
- package/out/main.nodejs.js +96 -30
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/core/tracking-engine.ts +109 -43
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/out/main.nodejs.js
CHANGED
|
@@ -36943,6 +36943,11 @@ class TrackingEngine {
|
|
|
36943
36943
|
trainingConfig = training_1.DEFAULT_TRAINING_CONFIG;
|
|
36944
36944
|
/** Callback for training status updates */
|
|
36945
36945
|
onTrainingStatusUpdate;
|
|
36946
|
+
// ==================== Snapshot Cache ====================
|
|
36947
|
+
/** Cached snapshots for tracked objects (for faster notifications) */
|
|
36948
|
+
snapshotCache = new Map();
|
|
36949
|
+
/** Pending LLM description promises (started when snapshot is captured) */
|
|
36950
|
+
pendingDescriptions = new Map();
|
|
36946
36951
|
constructor(topology, state, alertManager, config, console) {
|
|
36947
36952
|
this.topology = topology;
|
|
36948
36953
|
this.state = state;
|
|
@@ -37166,6 +37171,12 @@ class TrackingEngine {
|
|
|
37166
37171
|
const lastSighting = (0, tracked_object_1.getLastSighting)(tracked);
|
|
37167
37172
|
if (lastSighting && lastSighting.cameraId !== sighting.cameraId) {
|
|
37168
37173
|
const transitDuration = sighting.timestamp - lastSighting.timestamp;
|
|
37174
|
+
// Update cached snapshot from new camera (object is now visible here)
|
|
37175
|
+
if (this.config.useLlmDescriptions) {
|
|
37176
|
+
this.captureAndCacheSnapshot(tracked.globalId, sighting.cameraId).catch(e => {
|
|
37177
|
+
this.console.warn(`[Transition Snapshot] Failed to update snapshot: ${e}`);
|
|
37178
|
+
});
|
|
37179
|
+
}
|
|
37169
37180
|
// Add journey segment
|
|
37170
37181
|
this.state.addJourney(tracked.globalId, {
|
|
37171
37182
|
fromCameraId: lastSighting.cameraId,
|
|
@@ -37232,6 +37243,13 @@ class TrackingEngine {
|
|
|
37232
37243
|
}
|
|
37233
37244
|
/** Schedule an alert after loitering threshold passes */
|
|
37234
37245
|
scheduleLoiteringAlert(globalId, sighting, isEntryPoint) {
|
|
37246
|
+
// Capture snapshot IMMEDIATELY when object is first detected (don't wait for loitering threshold)
|
|
37247
|
+
// This ensures we have a good image while the person/object is still in frame
|
|
37248
|
+
if (this.config.useLlmDescriptions) {
|
|
37249
|
+
this.captureAndCacheSnapshot(globalId, sighting.cameraId).catch(e => {
|
|
37250
|
+
this.console.warn(`[Snapshot] Failed to cache initial snapshot: ${e}`);
|
|
37251
|
+
});
|
|
37252
|
+
}
|
|
37235
37253
|
// Check after loitering threshold if object is still being tracked
|
|
37236
37254
|
setTimeout(async () => {
|
|
37237
37255
|
const tracked = this.state.getObject(globalId);
|
|
@@ -37240,26 +37258,28 @@ class TrackingEngine {
|
|
|
37240
37258
|
// Check if we've already alerted for this object
|
|
37241
37259
|
if (this.isInAlertCooldown(globalId))
|
|
37242
37260
|
return;
|
|
37243
|
-
//
|
|
37244
|
-
let
|
|
37245
|
-
|
|
37246
|
-
if (
|
|
37261
|
+
// Use prefetched LLM result if available (started when snapshot was captured)
|
|
37262
|
+
let spatialResult;
|
|
37263
|
+
const pendingDescription = this.pendingDescriptions.get(globalId);
|
|
37264
|
+
if (pendingDescription) {
|
|
37265
|
+
this.console.log(`[Entry Alert] Using prefetched LLM result for ${globalId.slice(0, 8)}`);
|
|
37247
37266
|
try {
|
|
37248
|
-
|
|
37249
|
-
this.console.log(`[Entry Alert]
|
|
37250
|
-
if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
|
|
37251
|
-
mediaObject = await camera.takePicture();
|
|
37252
|
-
this.console.log(`[Entry Alert] Got snapshot: ${!!mediaObject}`);
|
|
37253
|
-
}
|
|
37267
|
+
spatialResult = await pendingDescription;
|
|
37268
|
+
this.console.log(`[Entry Alert] Prefetch result: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37254
37269
|
}
|
|
37255
37270
|
catch (e) {
|
|
37256
|
-
this.console.warn(
|
|
37271
|
+
this.console.warn(`[Entry Alert] Prefetch failed, generating fallback: ${e}`);
|
|
37272
|
+
spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId);
|
|
37257
37273
|
}
|
|
37274
|
+
this.pendingDescriptions.delete(globalId);
|
|
37275
|
+
}
|
|
37276
|
+
else {
|
|
37277
|
+
// Fallback: generate description now (slower path)
|
|
37278
|
+
this.console.log(`[Entry Alert] No prefetch available, generating now`);
|
|
37279
|
+
const mediaObject = this.snapshotCache.get(globalId);
|
|
37280
|
+
spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
|
|
37281
|
+
this.console.log(`[Entry Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37258
37282
|
}
|
|
37259
|
-
// Generate spatial description (now async with LLM support)
|
|
37260
|
-
this.console.log(`[Entry Alert] Calling generateEntryDescription with mediaObject=${!!mediaObject}`);
|
|
37261
|
-
const spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
|
|
37262
|
-
this.console.log(`[Entry Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37263
37283
|
if (isEntryPoint) {
|
|
37264
37284
|
// Entry point - generate property entry alert
|
|
37265
37285
|
await this.alertManager.checkAndAlert('property_entry', tracked, {
|
|
@@ -37290,6 +37310,37 @@ class TrackingEngine {
|
|
|
37290
37310
|
this.recordAlertTime(globalId);
|
|
37291
37311
|
}, this.config.loiteringThreshold);
|
|
37292
37312
|
}
|
|
37313
|
+
/** Capture and cache a snapshot for a tracked object, and start LLM analysis immediately */
|
|
37314
|
+
async captureAndCacheSnapshot(globalId, cameraId, eventType = 'entry') {
|
|
37315
|
+
try {
|
|
37316
|
+
const camera = systemManager.getDeviceById(cameraId);
|
|
37317
|
+
if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
|
|
37318
|
+
const mediaObject = await camera.takePicture();
|
|
37319
|
+
if (mediaObject) {
|
|
37320
|
+
this.snapshotCache.set(globalId, mediaObject);
|
|
37321
|
+
this.console.log(`[Snapshot] Cached snapshot for ${globalId.slice(0, 8)} from ${cameraId}`);
|
|
37322
|
+
// Start LLM analysis immediately in parallel (don't await)
|
|
37323
|
+
const tracked = this.state.getObject(globalId);
|
|
37324
|
+
if (tracked && this.config.useLlmDescriptions) {
|
|
37325
|
+
this.console.log(`[LLM Prefetch] Starting ${eventType} analysis for ${globalId.slice(0, 8)}`);
|
|
37326
|
+
const descriptionPromise = eventType === 'exit'
|
|
37327
|
+
? this.spatialReasoning.generateExitDescription(tracked, cameraId, mediaObject)
|
|
37328
|
+
: this.spatialReasoning.generateEntryDescription(tracked, cameraId, mediaObject);
|
|
37329
|
+
this.pendingDescriptions.set(globalId, descriptionPromise);
|
|
37330
|
+
// Log when complete (for debugging)
|
|
37331
|
+
descriptionPromise.then(result => {
|
|
37332
|
+
this.console.log(`[LLM Prefetch] ${eventType} analysis ready for ${globalId.slice(0, 8)}: "${result.description.substring(0, 40)}..."`);
|
|
37333
|
+
}).catch(e => {
|
|
37334
|
+
this.console.warn(`[LLM Prefetch] Failed for ${globalId.slice(0, 8)}: ${e}`);
|
|
37335
|
+
});
|
|
37336
|
+
}
|
|
37337
|
+
}
|
|
37338
|
+
}
|
|
37339
|
+
}
|
|
37340
|
+
catch (e) {
|
|
37341
|
+
this.console.warn(`[Snapshot] Failed to capture snapshot: ${e}`);
|
|
37342
|
+
}
|
|
37343
|
+
}
|
|
37293
37344
|
/** Attempt to correlate a sighting with existing tracked objects */
|
|
37294
37345
|
async correlateDetection(sighting) {
|
|
37295
37346
|
const activeObjects = this.state.getActiveObjects();
|
|
@@ -37329,31 +37380,40 @@ class TrackingEngine {
|
|
|
37329
37380
|
handlePotentialExit(tracked, sighting) {
|
|
37330
37381
|
// Mark as pending and set timer
|
|
37331
37382
|
this.state.markPending(tracked.globalId);
|
|
37383
|
+
// Capture a fresh snapshot now while object is still visible (before they leave)
|
|
37384
|
+
// Also starts LLM analysis immediately in parallel
|
|
37385
|
+
if (this.config.useLlmDescriptions) {
|
|
37386
|
+
this.captureAndCacheSnapshot(tracked.globalId, sighting.cameraId, 'exit').catch(e => {
|
|
37387
|
+
this.console.warn(`[Exit Snapshot] Failed to update snapshot: ${e}`);
|
|
37388
|
+
});
|
|
37389
|
+
}
|
|
37332
37390
|
// Wait for correlation window before marking as exited
|
|
37333
37391
|
const timer = setTimeout(async () => {
|
|
37334
37392
|
const current = this.state.getObject(tracked.globalId);
|
|
37335
37393
|
if (current && current.state === 'pending') {
|
|
37336
37394
|
this.state.markExited(tracked.globalId, sighting.cameraId, sighting.cameraName);
|
|
37337
|
-
//
|
|
37338
|
-
let
|
|
37339
|
-
this.
|
|
37340
|
-
if (
|
|
37395
|
+
// Use prefetched LLM result if available (started when exit was first detected)
|
|
37396
|
+
let spatialResult;
|
|
37397
|
+
const pendingDescription = this.pendingDescriptions.get(tracked.globalId);
|
|
37398
|
+
if (pendingDescription) {
|
|
37399
|
+
this.console.log(`[Exit Alert] Using prefetched LLM result for ${tracked.globalId.slice(0, 8)}`);
|
|
37341
37400
|
try {
|
|
37342
|
-
|
|
37343
|
-
this.console.log(`[Exit Alert]
|
|
37344
|
-
if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
|
|
37345
|
-
mediaObject = await camera.takePicture();
|
|
37346
|
-
this.console.log(`[Exit Alert] Got snapshot: ${!!mediaObject}`);
|
|
37347
|
-
}
|
|
37401
|
+
spatialResult = await pendingDescription;
|
|
37402
|
+
this.console.log(`[Exit Alert] Prefetch result: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37348
37403
|
}
|
|
37349
37404
|
catch (e) {
|
|
37350
|
-
this.console.warn(
|
|
37405
|
+
this.console.warn(`[Exit Alert] Prefetch failed, generating fallback: ${e}`);
|
|
37406
|
+
spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId);
|
|
37351
37407
|
}
|
|
37408
|
+
this.pendingDescriptions.delete(tracked.globalId);
|
|
37409
|
+
}
|
|
37410
|
+
else {
|
|
37411
|
+
// Fallback: generate description now (slower path)
|
|
37412
|
+
this.console.log(`[Exit Alert] No prefetch available, generating now`);
|
|
37413
|
+
const mediaObject = this.snapshotCache.get(tracked.globalId);
|
|
37414
|
+
spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
|
|
37415
|
+
this.console.log(`[Exit Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37352
37416
|
}
|
|
37353
|
-
// Generate rich exit description using topology context (now async with LLM support)
|
|
37354
|
-
this.console.log(`[Exit Alert] Calling generateExitDescription with mediaObject=${!!mediaObject}`);
|
|
37355
|
-
const spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
|
|
37356
|
-
this.console.log(`[Exit Alert] Object ${tracked.globalId.slice(0, 8)} exited: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37357
37417
|
await this.alertManager.checkAndAlert('property_exit', current, {
|
|
37358
37418
|
cameraId: sighting.cameraId,
|
|
37359
37419
|
cameraName: sighting.cameraName,
|
|
@@ -37362,6 +37422,9 @@ class TrackingEngine {
|
|
|
37362
37422
|
involvedLandmarks: spatialResult.involvedLandmarks?.map(l => l.name),
|
|
37363
37423
|
usedLlm: spatialResult.usedLlm,
|
|
37364
37424
|
});
|
|
37425
|
+
// Clean up cached snapshot and pending descriptions after exit alert
|
|
37426
|
+
this.snapshotCache.delete(tracked.globalId);
|
|
37427
|
+
this.pendingDescriptions.delete(tracked.globalId);
|
|
37365
37428
|
}
|
|
37366
37429
|
this.pendingTimers.delete(tracked.globalId);
|
|
37367
37430
|
}, this.config.correlationWindow);
|
|
@@ -37377,6 +37440,9 @@ class TrackingEngine {
|
|
|
37377
37440
|
this.state.markLost(tracked.globalId);
|
|
37378
37441
|
this.console.log(`Object ${tracked.globalId.slice(0, 8)} marked as lost ` +
|
|
37379
37442
|
`(not seen for ${Math.round(timeSinceSeen / 1000)}s)`);
|
|
37443
|
+
// Clean up cached snapshot and pending descriptions
|
|
37444
|
+
this.snapshotCache.delete(tracked.globalId);
|
|
37445
|
+
this.pendingDescriptions.delete(tracked.globalId);
|
|
37380
37446
|
this.alertManager.checkAndAlert('lost_tracking', tracked, {
|
|
37381
37447
|
objectClass: tracked.className,
|
|
37382
37448
|
objectLabel: tracked.label,
|