@blueharford/scrypted-spatial-awareness 0.6.4 → 0.6.5

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/out/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueharford/scrypted-spatial-awareness",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "Cross-camera object tracking for Scrypted NVR with spatial awareness",
5
5
  "author": "Joshua Seidel <blueharford>",
6
6
  "license": "Apache-2.0",
@@ -659,7 +659,9 @@ export class TopologyDiscoveryEngine {
659
659
  type: landmark.type,
660
660
  description: landmark.description,
661
661
  visibleFromCameras: [analysis.cameraId],
662
- },
662
+ // Include bounding box for positioning (will be used by applyDiscoverySuggestion)
663
+ boundingBox: landmark.boundingBox,
664
+ } as any, // boundingBox is extra metadata not in Landmark interface
663
665
  };
664
666
  this.suggestions.set(suggestion.id, suggestion);
665
667
  }
package/src/main.ts CHANGED
@@ -1861,21 +1861,47 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1861
1861
  const range = fov.range || 80;
1862
1862
  const fovAngle = fov.angle || 90;
1863
1863
 
1864
- // Count existing landmarks from this camera to spread them out
1865
- const cameraDeviceId = camera.deviceId;
1866
- const existingFromCamera = (topology.landmarks || []).filter(l =>
1867
- l.visibleFromCameras?.includes(cameraDeviceId) ||
1868
- l.visibleFromCameras?.includes(camera.name)
1869
- ).length;
1870
-
1871
1864
  // Calculate position in front of camera within its FOV
1872
1865
  // Convert direction to radians (0 = up/north, 90 = right/east)
1873
1866
  const dirRad = (direction - 90) * Math.PI / 180;
1874
1867
  const halfFov = (fovAngle / 2) * Math.PI / 180;
1875
1868
 
1876
- // Spread landmarks across the FOV cone at varying distances
1877
- const angleOffset = (existingFromCamera % 3 - 1) * halfFov * 0.6; // -0.6, 0, +0.6 of half FOV
1878
- const distanceMultiplier = 0.5 + (existingFromCamera % 2) * 0.3; // 50% or 80% of range
1869
+ // Use bounding box if available to position landmark accurately within FOV
1870
+ // boundingBox format: [x, y, width, height] normalized 0-1
1871
+ const bbox = (suggestion.landmark as any).boundingBox as [number, number, number, number] | undefined;
1872
+
1873
+ let angleOffset: number;
1874
+ let distanceMultiplier: number;
1875
+
1876
+ if (bbox && bbox.length >= 2) {
1877
+ // Use bounding box center to determine position in FOV
1878
+ const bboxCenterX = bbox[0] + (bbox[2] || 0) / 2; // 0 = left edge, 1 = right edge
1879
+ const bboxCenterY = bbox[1] + (bbox[3] || 0) / 2; // 0 = top (far), 1 = bottom (close)
1880
+
1881
+ // Map X position to angle within FOV
1882
+ // bboxCenterX 0 → left side of FOV (-halfFov)
1883
+ // bboxCenterX 0.5 → center of FOV (0)
1884
+ // bboxCenterX 1 → right side of FOV (+halfFov)
1885
+ angleOffset = (bboxCenterX - 0.5) * 2 * halfFov;
1886
+
1887
+ // Map Y position to distance from camera
1888
+ // bboxCenterY 0 (top of frame) → far from camera (90% of range)
1889
+ // bboxCenterY 1 (bottom of frame) → close to camera (30% of range)
1890
+ distanceMultiplier = 0.9 - (bboxCenterY * 0.6);
1891
+
1892
+ this.console.log(`[Discovery] Using bounding box [${bbox.join(',')}] → center (${bboxCenterX.toFixed(2)}, ${bboxCenterY.toFixed(2)})`);
1893
+ } else {
1894
+ // Fallback: spread landmarks across FOV if no bounding box
1895
+ const cameraDeviceId = camera.deviceId;
1896
+ const existingFromCamera = (topology.landmarks || []).filter(l =>
1897
+ l.visibleFromCameras?.includes(cameraDeviceId) ||
1898
+ l.visibleFromCameras?.includes(camera.name)
1899
+ ).length;
1900
+
1901
+ angleOffset = (existingFromCamera % 3 - 1) * halfFov * 0.6;
1902
+ distanceMultiplier = 0.5 + (existingFromCamera % 2) * 0.3;
1903
+ this.console.log(`[Discovery] No bounding box, using fallback spread (existing: ${existingFromCamera})`);
1904
+ }
1879
1905
 
1880
1906
  const finalAngle = dirRad + angleOffset;
1881
1907
  const distance = range * distanceMultiplier;
@@ -1885,7 +1911,7 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1885
1911
  y: camera.floorPlanPosition.y + Math.sin(finalAngle) * distance,
1886
1912
  };
1887
1913
 
1888
- this.console.log(`[Discovery] Placing landmark "${suggestion.landmark.name}" in ${camera.name}'s FOV: dir=${direction}°, dist=${distance.toFixed(0)}px`);
1914
+ this.console.log(`[Discovery] Placing landmark "${suggestion.landmark.name}" in ${camera.name}'s FOV: dir=${direction}°, angle=${(angleOffset * 180 / Math.PI).toFixed(1)}°, dist=${distance.toFixed(0)}px`);
1889
1915
  } else {
1890
1916
  // Position in a grid pattern starting from center
1891
1917
  const landmarkCount = topology.landmarks?.length || 0;
@@ -1956,35 +1982,65 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1956
1982
  const dirRad = (direction - 90) * Math.PI / 180;
1957
1983
  const halfFov = (fovAngle / 2) * Math.PI / 180;
1958
1984
 
1959
- // Count existing zones from this camera to offset new ones
1960
- const existingFromCamera = (topology.drawnZones || []).filter((z: any) =>
1961
- z.linkedCameras?.includes(sourceCameras[0])
1962
- ).length;
1985
+ const camX = camera.floorPlanPosition.x;
1986
+ const camY = camera.floorPlanPosition.y;
1963
1987
 
1964
- // Create a wedge-shaped zone within the camera's FOV
1965
- // Offset based on existing zones to avoid overlap
1966
- const innerRadius = range * 0.3 + existingFromCamera * 20;
1967
- const outerRadius = range * 0.8 + existingFromCamera * 20;
1988
+ // Use bounding box if available to position zone accurately within FOV
1989
+ const bbox = zone.boundingBox; // [x, y, width, height] normalized 0-1
1968
1990
 
1969
- // Use a portion of the FOV for each zone
1970
- const zoneSpread = halfFov * 0.7; // 70% of half FOV
1991
+ let innerRadius: number;
1992
+ let outerRadius: number;
1993
+ let angleStart: number;
1994
+ let angleEnd: number;
1971
1995
 
1972
- const camX = camera.floorPlanPosition.x;
1973
- const camY = camera.floorPlanPosition.y;
1996
+ if (bbox && bbox.length >= 4) {
1997
+ // Map bounding box to position within FOV
1998
+ const bboxLeft = bbox[0];
1999
+ const bboxRight = bbox[0] + bbox[2];
2000
+ const bboxTop = bbox[1];
2001
+ const bboxBottom = bbox[1] + bbox[3];
2002
+
2003
+ // Map X to angle within FOV (0 = left edge, 1 = right edge)
2004
+ angleStart = dirRad + (bboxLeft - 0.5) * 2 * halfFov;
2005
+ angleEnd = dirRad + (bboxRight - 0.5) * 2 * halfFov;
2006
+
2007
+ // Map Y to distance (0 = far, 1 = close)
2008
+ innerRadius = range * (0.9 - bboxBottom * 0.6);
2009
+ outerRadius = range * (0.9 - bboxTop * 0.6);
2010
+
2011
+ // Ensure min size
2012
+ if (outerRadius - innerRadius < 20) {
2013
+ outerRadius = innerRadius + 20;
2014
+ }
2015
+
2016
+ this.console.log(`[Discovery] Zone "${zone.name}" using bbox [${bbox.join(',')}] → angles ${(angleStart * 180 / Math.PI).toFixed(1)}° to ${(angleEnd * 180 / Math.PI).toFixed(1)}°`);
2017
+ } else {
2018
+ // Fallback: wedge-shaped zone offset by existing count
2019
+ const existingFromCamera = (topology.drawnZones || []).filter((z: any) =>
2020
+ z.linkedCameras?.includes(sourceCameras[0])
2021
+ ).length;
2022
+
2023
+ innerRadius = range * 0.3 + existingFromCamera * 20;
2024
+ outerRadius = range * 0.8 + existingFromCamera * 20;
2025
+ angleStart = dirRad - halfFov * 0.7;
2026
+ angleEnd = dirRad + halfFov * 0.7;
2027
+
2028
+ this.console.log(`[Discovery] Zone "${zone.name}" using fallback spread (existing: ${existingFromCamera})`);
2029
+ }
1974
2030
 
1975
- // Create arc polygon (wedge shape)
2031
+ // Create arc polygon
1976
2032
  const steps = 8;
1977
- // Inner arc (from left to right)
2033
+ // Inner arc (from start angle to end angle)
1978
2034
  for (let i = 0; i <= steps; i++) {
1979
- const angle = dirRad - zoneSpread + (zoneSpread * 2 * i / steps);
2035
+ const angle = angleStart + (angleEnd - angleStart) * i / steps;
1980
2036
  polygon.push({
1981
2037
  x: camX + Math.cos(angle) * innerRadius,
1982
2038
  y: camY + Math.sin(angle) * innerRadius,
1983
2039
  });
1984
2040
  }
1985
- // Outer arc (from right to left)
2041
+ // Outer arc (from end angle to start angle)
1986
2042
  for (let i = steps; i >= 0; i--) {
1987
- const angle = dirRad - zoneSpread + (zoneSpread * 2 * i / steps);
2043
+ const angle = angleStart + (angleEnd - angleStart) * i / steps;
1988
2044
  polygon.push({
1989
2045
  x: camX + Math.cos(angle) * outerRadius,
1990
2046
  y: camY + Math.sin(angle) * outerRadius,