@blueharford/scrypted-spatial-awareness 0.5.7 → 0.5.9
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 +127 -28
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/core/spatial-reasoning.ts +33 -24
- package/src/core/topology-discovery.ts +2 -2
- package/src/main.ts +96 -5
- package/src/ui/editor-html.ts +6 -0
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/out/main.nodejs.js
CHANGED
|
@@ -35076,35 +35076,48 @@ const { systemManager, mediaManager } = sdk_1.default;
|
|
|
35076
35076
|
*/
|
|
35077
35077
|
async function mediaObjectToBase64(mediaObject) {
|
|
35078
35078
|
try {
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
|
|
35082
|
-
|
|
35083
|
-
|
|
35084
|
-
|
|
35085
|
-
|
|
35086
|
-
|
|
35087
|
-
|
|
35088
|
-
|
|
35089
|
-
|
|
35090
|
-
|
|
35091
|
-
// Try alternate approach: get raw data using any type
|
|
35079
|
+
const mimeType = mediaObject?.mimeType || 'image/jpeg';
|
|
35080
|
+
console.log(`[Image] Converting MediaObject, mimeType=${mimeType}`);
|
|
35081
|
+
// Use createMediaObject to ensure we have a proper MediaObject with mimeType
|
|
35082
|
+
// Then convert to buffer - this should handle the conversion internally
|
|
35083
|
+
let buffer;
|
|
35084
|
+
try {
|
|
35085
|
+
// Try direct conversion with the source mime type
|
|
35086
|
+
buffer = await mediaManager.convertMediaObjectToBuffer(mediaObject, mimeType);
|
|
35087
|
+
}
|
|
35088
|
+
catch (convErr) {
|
|
35089
|
+
console.warn(`[Image] Direct conversion failed, trying with explicit JPEG:`, convErr);
|
|
35090
|
+
// Try creating a new MediaObject with explicit mimeType
|
|
35092
35091
|
try {
|
|
35092
|
+
// Get raw data if available
|
|
35093
35093
|
const anyMedia = mediaObject;
|
|
35094
35094
|
if (typeof anyMedia.getData === 'function') {
|
|
35095
|
-
const
|
|
35096
|
-
if (
|
|
35097
|
-
console.log(`[Image] Got data
|
|
35098
|
-
|
|
35099
|
-
|
|
35100
|
-
|
|
35101
|
-
|
|
35095
|
+
const rawData = await anyMedia.getData();
|
|
35096
|
+
if (rawData && Buffer.isBuffer(rawData) && rawData.length > 1000) {
|
|
35097
|
+
console.log(`[Image] Got raw data: ${rawData.length} bytes`);
|
|
35098
|
+
buffer = rawData;
|
|
35099
|
+
}
|
|
35100
|
+
else {
|
|
35101
|
+
console.warn(`[Image] getData returned invalid data`);
|
|
35102
|
+
return null;
|
|
35102
35103
|
}
|
|
35103
35104
|
}
|
|
35105
|
+
else {
|
|
35106
|
+
console.warn('[Image] No getData method available');
|
|
35107
|
+
return null;
|
|
35108
|
+
}
|
|
35104
35109
|
}
|
|
35105
35110
|
catch (dataErr) {
|
|
35106
|
-
console.warn('[Image]
|
|
35111
|
+
console.warn('[Image] Alternate data fetch failed:', dataErr);
|
|
35112
|
+
return null;
|
|
35107
35113
|
}
|
|
35114
|
+
}
|
|
35115
|
+
// Check if we got an actual Buffer (not a proxy)
|
|
35116
|
+
const isRealBuffer = Buffer.isBuffer(buffer);
|
|
35117
|
+
const bufferLength = isRealBuffer ? buffer.length : 0;
|
|
35118
|
+
console.log(`[Image] Buffer: isBuffer=${isRealBuffer}, length=${bufferLength}`);
|
|
35119
|
+
if (!isRealBuffer || bufferLength === 0) {
|
|
35120
|
+
console.warn('[Image] Did not receive a valid Buffer');
|
|
35108
35121
|
return null;
|
|
35109
35122
|
}
|
|
35110
35123
|
// Check if buffer is too small to be a valid image (< 1KB is suspicious)
|
|
@@ -36262,7 +36275,7 @@ class TopologyDiscoveryEngine {
|
|
|
36262
36275
|
],
|
|
36263
36276
|
},
|
|
36264
36277
|
],
|
|
36265
|
-
max_tokens:
|
|
36278
|
+
max_tokens: 1500,
|
|
36266
36279
|
temperature: 0.3,
|
|
36267
36280
|
});
|
|
36268
36281
|
const content = result?.choices?.[0]?.message?.content;
|
|
@@ -36471,7 +36484,7 @@ class TopologyDiscoveryEngine {
|
|
|
36471
36484
|
const prompt = CORRELATION_PROMPT.replace('{scenes}', scenesText);
|
|
36472
36485
|
const result = await llm.getChatCompletion({
|
|
36473
36486
|
messages: [{ role: 'user', content: prompt }],
|
|
36474
|
-
max_tokens:
|
|
36487
|
+
max_tokens: 2000,
|
|
36475
36488
|
temperature: 0.4,
|
|
36476
36489
|
});
|
|
36477
36490
|
const content = result?.choices?.[0]?.message?.content;
|
|
@@ -40211,12 +40224,38 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
40211
40224
|
const topology = this.trackingEngine.getTopology();
|
|
40212
40225
|
let updated = false;
|
|
40213
40226
|
if (suggestion.type === 'landmark' && suggestion.landmark) {
|
|
40227
|
+
// Calculate a reasonable position for the landmark
|
|
40228
|
+
// Use the first visible camera's position as a starting point, or canvas center
|
|
40229
|
+
let position = suggestion.landmark.position;
|
|
40230
|
+
if (!position || (position.x === 0 && position.y === 0)) {
|
|
40231
|
+
// Find a camera that can see this landmark
|
|
40232
|
+
const visibleCameraId = suggestion.landmark.visibleFromCameras?.[0];
|
|
40233
|
+
const camera = visibleCameraId ? topology.cameras.find(c => c.deviceId === visibleCameraId) : null;
|
|
40234
|
+
if (camera?.floorPlanPosition) {
|
|
40235
|
+
// Position near the camera with some offset
|
|
40236
|
+
const offset = (topology.landmarks?.length || 0) * 30;
|
|
40237
|
+
position = {
|
|
40238
|
+
x: camera.floorPlanPosition.x + 50 + (offset % 100),
|
|
40239
|
+
y: camera.floorPlanPosition.y + 50 + Math.floor(offset / 100) * 30,
|
|
40240
|
+
};
|
|
40241
|
+
}
|
|
40242
|
+
else {
|
|
40243
|
+
// Position in a grid pattern starting from center
|
|
40244
|
+
const landmarkCount = topology.landmarks?.length || 0;
|
|
40245
|
+
const gridSize = 80;
|
|
40246
|
+
const cols = 5;
|
|
40247
|
+
position = {
|
|
40248
|
+
x: 200 + (landmarkCount % cols) * gridSize,
|
|
40249
|
+
y: 100 + Math.floor(landmarkCount / cols) * gridSize,
|
|
40250
|
+
};
|
|
40251
|
+
}
|
|
40252
|
+
}
|
|
40214
40253
|
// Add new landmark to topology
|
|
40215
40254
|
const landmark = {
|
|
40216
40255
|
id: `landmark_${Date.now()}`,
|
|
40217
40256
|
name: suggestion.landmark.name,
|
|
40218
40257
|
type: suggestion.landmark.type,
|
|
40219
|
-
position
|
|
40258
|
+
position,
|
|
40220
40259
|
description: suggestion.landmark.description,
|
|
40221
40260
|
visibleFromCameras: suggestion.landmark.visibleFromCameras,
|
|
40222
40261
|
aiSuggested: true,
|
|
@@ -40227,15 +40266,69 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
40227
40266
|
}
|
|
40228
40267
|
topology.landmarks.push(landmark);
|
|
40229
40268
|
updated = true;
|
|
40230
|
-
this.console.log(`[Discovery] Added landmark: ${landmark.name}`);
|
|
40269
|
+
this.console.log(`[Discovery] Added landmark: ${landmark.name} at (${position.x}, ${position.y})`);
|
|
40270
|
+
}
|
|
40271
|
+
if (suggestion.type === 'zone' && suggestion.zone) {
|
|
40272
|
+
// Create a drawn zone from the discovery zone
|
|
40273
|
+
const zone = suggestion.zone;
|
|
40274
|
+
// Find cameras that see this zone type to determine position
|
|
40275
|
+
const cameraWithZone = suggestion.sourceCameras?.[0];
|
|
40276
|
+
const camera = cameraWithZone ? topology.cameras.find(c => c.deviceId === cameraWithZone || c.name === cameraWithZone) : null;
|
|
40277
|
+
// Create a default polygon near the camera or at a default location
|
|
40278
|
+
let centerX = 300;
|
|
40279
|
+
let centerY = 200;
|
|
40280
|
+
if (camera?.floorPlanPosition) {
|
|
40281
|
+
centerX = camera.floorPlanPosition.x;
|
|
40282
|
+
centerY = camera.floorPlanPosition.y + 80;
|
|
40283
|
+
}
|
|
40284
|
+
// Create a rectangular zone (user can edit later)
|
|
40285
|
+
const size = 100;
|
|
40286
|
+
const drawnZone = {
|
|
40287
|
+
id: `zone_${Date.now()}`,
|
|
40288
|
+
name: zone.name,
|
|
40289
|
+
type: (zone.type || 'custom'),
|
|
40290
|
+
description: zone.description,
|
|
40291
|
+
polygon: [
|
|
40292
|
+
{ x: centerX - size / 2, y: centerY - size / 2 },
|
|
40293
|
+
{ x: centerX + size / 2, y: centerY - size / 2 },
|
|
40294
|
+
{ x: centerX + size / 2, y: centerY + size / 2 },
|
|
40295
|
+
{ x: centerX - size / 2, y: centerY + size / 2 },
|
|
40296
|
+
],
|
|
40297
|
+
};
|
|
40298
|
+
if (!topology.drawnZones) {
|
|
40299
|
+
topology.drawnZones = [];
|
|
40300
|
+
}
|
|
40301
|
+
topology.drawnZones.push(drawnZone);
|
|
40302
|
+
updated = true;
|
|
40303
|
+
this.console.log(`[Discovery] Added zone: ${zone.name} (${zone.type})`);
|
|
40231
40304
|
}
|
|
40232
40305
|
if (suggestion.type === 'connection' && suggestion.connection) {
|
|
40233
40306
|
// Add new connection to topology
|
|
40234
40307
|
const conn = suggestion.connection;
|
|
40308
|
+
// Ensure cameras have floor plan positions for visibility
|
|
40309
|
+
const fromCamera = topology.cameras.find(c => c.deviceId === conn.fromCameraId || c.name === conn.fromCameraId);
|
|
40310
|
+
const toCamera = topology.cameras.find(c => c.deviceId === conn.toCameraId || c.name === conn.toCameraId);
|
|
40311
|
+
// Auto-assign floor plan positions if missing
|
|
40312
|
+
if (fromCamera && !fromCamera.floorPlanPosition) {
|
|
40313
|
+
const idx = topology.cameras.indexOf(fromCamera);
|
|
40314
|
+
fromCamera.floorPlanPosition = {
|
|
40315
|
+
x: 150 + (idx % 3) * 200,
|
|
40316
|
+
y: 150 + Math.floor(idx / 3) * 150,
|
|
40317
|
+
};
|
|
40318
|
+
this.console.log(`[Discovery] Auto-positioned camera: ${fromCamera.name}`);
|
|
40319
|
+
}
|
|
40320
|
+
if (toCamera && !toCamera.floorPlanPosition) {
|
|
40321
|
+
const idx = topology.cameras.indexOf(toCamera);
|
|
40322
|
+
toCamera.floorPlanPosition = {
|
|
40323
|
+
x: 150 + (idx % 3) * 200,
|
|
40324
|
+
y: 150 + Math.floor(idx / 3) * 150,
|
|
40325
|
+
};
|
|
40326
|
+
this.console.log(`[Discovery] Auto-positioned camera: ${toCamera.name}`);
|
|
40327
|
+
}
|
|
40235
40328
|
const newConnection = {
|
|
40236
40329
|
id: `conn_${Date.now()}`,
|
|
40237
|
-
fromCameraId: conn.fromCameraId,
|
|
40238
|
-
toCameraId: conn.toCameraId,
|
|
40330
|
+
fromCameraId: fromCamera?.deviceId || conn.fromCameraId,
|
|
40331
|
+
toCameraId: toCamera?.deviceId || conn.toCameraId,
|
|
40239
40332
|
bidirectional: conn.bidirectional,
|
|
40240
40333
|
// Default exit/entry zones covering full frame
|
|
40241
40334
|
exitZone: [[0, 0], [100, 0], [100, 100], [0, 100]],
|
|
@@ -40249,7 +40342,7 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
40249
40342
|
};
|
|
40250
40343
|
topology.connections.push(newConnection);
|
|
40251
40344
|
updated = true;
|
|
40252
|
-
this.console.log(`[Discovery] Added connection: ${conn.fromCameraId} -> ${conn.toCameraId}`);
|
|
40345
|
+
this.console.log(`[Discovery] Added connection: ${fromCamera?.name || conn.fromCameraId} -> ${toCamera?.name || conn.toCameraId}`);
|
|
40253
40346
|
}
|
|
40254
40347
|
if (updated) {
|
|
40255
40348
|
// Save updated topology
|
|
@@ -42298,6 +42391,12 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
|
|
|
42298
42391
|
for (let i = 1; i < currentZonePoints.length; i++) {
|
|
42299
42392
|
ctx.lineTo(currentZonePoints[i].x, currentZonePoints[i].y);
|
|
42300
42393
|
}
|
|
42394
|
+
// Close the polygon if we have 3+ points
|
|
42395
|
+
if (currentZonePoints.length >= 3) {
|
|
42396
|
+
ctx.closePath();
|
|
42397
|
+
ctx.fillStyle = color;
|
|
42398
|
+
ctx.fill();
|
|
42399
|
+
}
|
|
42301
42400
|
ctx.strokeStyle = strokeColor;
|
|
42302
42401
|
ctx.lineWidth = 2;
|
|
42303
42402
|
ctx.stroke();
|