@blueharford/scrypted-spatial-awareness 0.6.5 → 0.6.7

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.5",
3
+ "version": "0.6.7",
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",
package/src/main.ts CHANGED
@@ -1584,7 +1584,7 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1584
1584
  }
1585
1585
  }
1586
1586
 
1587
- private handleTrainingEndRequest(response: HttpResponse): void {
1587
+ private async handleTrainingEndRequest(response: HttpResponse): Promise<void> {
1588
1588
  if (!this.trackingEngine) {
1589
1589
  response.send(JSON.stringify({ error: 'Tracking engine not running' }), {
1590
1590
  code: 500,
@@ -1595,6 +1595,44 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1595
1595
 
1596
1596
  const session = this.trackingEngine.endTrainingSession();
1597
1597
  if (session) {
1598
+ // Get unique visited cameras
1599
+ const visitedCameraIds = [...new Set(session.visits.map(v => v.cameraId))];
1600
+
1601
+ // Auto-run discovery on visited cameras to detect landmarks and zones
1602
+ if (this.discoveryEngine && visitedCameraIds.length > 0) {
1603
+ this.console.log(`[Training] Running discovery analysis on ${visitedCameraIds.length} visited cameras...`);
1604
+
1605
+ let landmarksFound = 0;
1606
+ let zonesFound = 0;
1607
+
1608
+ for (const cameraId of visitedCameraIds) {
1609
+ try {
1610
+ const analysis = await this.discoveryEngine.analyzeScene(cameraId);
1611
+ if (analysis.isValid) {
1612
+ landmarksFound += analysis.landmarks.length;
1613
+ zonesFound += analysis.zones.length;
1614
+ this.console.log(`[Training] ${cameraId}: Found ${analysis.landmarks.length} landmarks, ${analysis.zones.length} zones`);
1615
+ }
1616
+ } catch (e) {
1617
+ this.console.warn(`[Training] Failed to analyze ${cameraId}:`, e);
1618
+ }
1619
+ }
1620
+
1621
+ // Get all pending suggestions and auto-accept them
1622
+ const suggestions = this.discoveryEngine.getPendingSuggestions();
1623
+ for (const suggestion of suggestions) {
1624
+ this.applyDiscoverySuggestion(suggestion);
1625
+ this.discoveryEngine.acceptSuggestion(suggestion.id);
1626
+ }
1627
+
1628
+ // Persist topology after applying suggestions
1629
+ if (suggestions.length > 0 && this.trackingEngine) {
1630
+ const updatedTopology = this.trackingEngine.getTopology();
1631
+ await this.storageSettings.putSetting('topology', JSON.stringify(updatedTopology));
1632
+ this.console.log(`[Training] Auto-applied ${suggestions.length} discoveries (${landmarksFound} landmarks, ${zonesFound} zones)`);
1633
+ }
1634
+ }
1635
+
1598
1636
  response.send(JSON.stringify(session), {
1599
1637
  headers: { 'Content-Type': 'application/json' },
1600
1638
  });
@@ -1866,7 +1904,13 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1866
1904
  const dirRad = (direction - 90) * Math.PI / 180;
1867
1905
  const halfFov = (fovAngle / 2) * Math.PI / 180;
1868
1906
 
1869
- // Use bounding box if available to position landmark accurately within FOV
1907
+ // Use landmark TYPE to determine distance - some things are inherently far away
1908
+ // regardless of where they appear in the camera frame
1909
+ const landmarkType = suggestion.landmark.type;
1910
+ const farTypes = ['neighbor', 'boundary', 'street']; // Always place at edge of FOV
1911
+ const isFarType = farTypes.includes(landmarkType || '');
1912
+
1913
+ // Use bounding box if available for horizontal positioning
1870
1914
  // boundingBox format: [x, y, width, height] normalized 0-1
1871
1915
  const bbox = (suggestion.landmark as any).boundingBox as [number, number, number, number] | undefined;
1872
1916
 
@@ -1874,33 +1918,46 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
1874
1918
  let distanceMultiplier: number;
1875
1919
 
1876
1920
  if (bbox && bbox.length >= 2) {
1877
- // Use bounding box center to determine position in FOV
1921
+ // Use bounding box X for horizontal position in FOV
1878
1922
  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
1923
 
1881
1924
  // 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
1925
  angleOffset = (bboxCenterX - 0.5) * 2 * halfFov;
1886
1926
 
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)})`);
1927
+ // For distance: use TYPE first, then bbox Y as hint
1928
+ if (isFarType) {
1929
+ // Neighbors, boundaries, streets are BEYOND the camera's normal range
1930
+ // Place at 150-200% of range to indicate they're distant background features
1931
+ distanceMultiplier = 1.5 + Math.random() * 0.5; // 150-200% of range
1932
+ this.console.log(`[Discovery] Far-type landmark "${landmarkType}" placed BEYOND FOV (${(distanceMultiplier * 100).toFixed(0)}% of range)`);
1933
+ } else {
1934
+ // For other types, use bbox Y as a hint (but cap minimum distance)
1935
+ const bboxCenterY = bbox[1] + (bbox[3] || 0) / 2;
1936
+ // Map Y to distance: 0 (top) = far, 1 (bottom) = closer (but not too close)
1937
+ distanceMultiplier = Math.max(0.4, 0.9 - (bboxCenterY * 0.5));
1938
+ }
1939
+
1940
+ this.console.log(`[Discovery] Using bounding box [${bbox.join(',')}] for horizontal position`);
1893
1941
  } else {
1894
- // Fallback: spread landmarks across FOV if no bounding box
1942
+ // No bounding box - use type and spread pattern
1895
1943
  const cameraDeviceId = camera.deviceId;
1896
1944
  const existingFromCamera = (topology.landmarks || []).filter(l =>
1897
1945
  l.visibleFromCameras?.includes(cameraDeviceId) ||
1898
1946
  l.visibleFromCameras?.includes(camera.name)
1899
1947
  ).length;
1900
1948
 
1949
+ // Spread horizontally across FOV
1901
1950
  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})`);
1951
+
1952
+ // Distance based on type
1953
+ if (isFarType) {
1954
+ // Neighbors, boundaries, streets are BEYOND the camera's normal range
1955
+ distanceMultiplier = 1.5 + Math.random() * 0.5; // 150-200% of range
1956
+ this.console.log(`[Discovery] Far-type landmark "${landmarkType}" (no bbox) placed BEYOND FOV`);
1957
+ } else {
1958
+ distanceMultiplier = 0.5 + (existingFromCamera % 2) * 0.3;
1959
+ this.console.log(`[Discovery] No bounding box, using fallback spread (existing: ${existingFromCamera})`);
1960
+ }
1904
1961
  }
1905
1962
 
1906
1963
  const finalAngle = dirRad + angleOffset;