@camera.ui/server 2.1.8 → 2.1.10
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/interface/assets/{404-vRjJVgl4.js → 404-BLNSh-sA.js} +1 -1
- package/dist/interface/assets/{About-CO9cx5Gp.js → About-ZakRzon7.js} +1 -1
- package/dist/interface/assets/Automation-DoVhkuVY.js +3 -0
- package/dist/interface/assets/AutomationImport-DVxK1R1y.js +2 -0
- package/dist/interface/assets/{AutomationStore-NyXuw3tI.js → AutomationStore-DaNO9_9S.js} +2 -2
- package/dist/interface/assets/{AutomationStoreDetail-Dz9Y8j8g.js → AutomationStoreDetail-CTDCEaIz.js} +2 -2
- package/dist/interface/assets/Automations-mdnUitWx.js +2 -0
- package/dist/interface/assets/Camera-B3cv5IVC.js +2 -0
- package/dist/interface/assets/Camera-LSq1bWmA.css +1 -0
- package/dist/interface/assets/{CameraConsole-S9Il0g0K.js → CameraConsole-C32X5I91.js} +2 -2
- package/dist/interface/assets/{Cameras-lDMBkoUW.js → Cameras-Drv48-Eb.js} +23 -23
- package/dist/interface/assets/Camview-Bvy39jSt.js +1 -0
- package/dist/interface/assets/{Camview-CS7PIBMz.css → Camview-Lp3QU0de.css} +1 -1
- package/dist/interface/assets/{Config-B0cy1RIU.js → Config-B-LDdaS6.js} +1 -1
- package/dist/interface/assets/ConfigActionCameraControl-As2qXvnd.js +1 -0
- package/dist/interface/assets/{ConfigActionDelay-DrzMP_7q.js → ConfigActionDelay-Cj3yWfEV.js} +1 -1
- package/dist/interface/assets/{ConfigActionHttp-BUANm5zh.js → ConfigActionHttp-PlS5vI0v.js} +1 -1
- package/dist/interface/assets/{ConfigActionImageInput-CKvnzs_4.js → ConfigActionImageInput-BHflOA-T.js} +1 -1
- package/dist/interface/assets/{ConfigActionMqtt-Cz-fMzXH.js → ConfigActionMqtt-DByOwVPe.js} +1 -1
- package/dist/interface/assets/{ConfigActionNotification-exxG4m-k.js → ConfigActionNotification-D_8_lp96.js} +1 -1
- package/dist/interface/assets/{ConfigActionNotificationControl-BMFbm9lJ.js → ConfigActionNotificationControl-ClFMom6m.js} +1 -1
- package/dist/interface/assets/{ConfigActionOutput-js-UUBci.js → ConfigActionOutput-YTAw-Are.js} +1 -1
- package/dist/interface/assets/{ConfigActionPlugin-B5hrC_B6.js → ConfigActionPlugin-lF2IH2Bo.js} +1 -1
- package/dist/interface/assets/{ConfigActionSensor-BESCtedA.js → ConfigActionSensor-DQZW_Jxr.js} +1 -1
- package/dist/interface/assets/{ConfigActionSnapshot-Uxz9dCqh.js → ConfigActionSnapshot-CHxIQ5J0.js} +1 -1
- package/dist/interface/assets/{ConfigActionVariable-CsiC6bgF.js → ConfigActionVariable-0VLGuhwP.js} +1 -1
- package/dist/interface/assets/{ConfigConditionIfElse-CiScJB42.js → ConfigConditionIfElse-D-Ibn8QL.js} +1 -1
- package/dist/interface/assets/{ConfigConditionSensorState-CbDfsuOl.js → ConfigConditionSensorState-BKSGYRdv.js} +1 -1
- package/dist/interface/assets/{ConfigConditionSwitch-B7E5DAoC.js → ConfigConditionSwitch-CzkEyWb1.js} +1 -1
- package/dist/interface/assets/{ConfigConditionTime-BmeGYmwH.js → ConfigConditionTime-BFKsDI_t.js} +1 -1
- package/dist/interface/assets/{ConfigSensorValueInput-CNTToTaw.js → ConfigSensorValueInput-yTiHhlXG.js} +1 -1
- package/dist/interface/assets/ConfigTriggerDetection-DFTgRaE0.js +1 -0
- package/dist/interface/assets/ConfigTriggerGeofence-B2Cqi5Id.js +1 -0
- package/dist/interface/assets/{ConfigTriggerManual-Z1_1wUUM.js → ConfigTriggerManual-BDK6OYxg.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerMqtt-BBvD0ow9.js → ConfigTriggerMqtt-BZO14RrW.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSchedule-B0rMjsZL.js → ConfigTriggerSchedule-sMkzMLgn.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSensor-B1p15KGD.js → ConfigTriggerSensor-D6W_exDW.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSystem-Du-AkCKy.js → ConfigTriggerSystem-D4cx1GqI.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerWebhook-Debqt-nw.js → ConfigTriggerWebhook-DxZziGkL.js} +1 -1
- package/dist/interface/assets/Console-DjA3ZPU_.js +1 -0
- package/dist/interface/assets/CuiAutomationRunsDialog-B4ABK72A.js +1 -0
- package/dist/interface/assets/CuiBottomSheet-D1zDWsoK.js +1 -0
- package/dist/interface/assets/{CuiBottomSheet-DECWGdrp.js → CuiBottomSheet-N9MHq4yZ.js} +1 -1
- package/dist/interface/assets/CuiCameraPipCard-CnWs2fAC.js +1 -0
- package/dist/interface/assets/CuiCameraRecordings-CahW-f8_.js +1 -0
- package/dist/interface/assets/{CuiCameraShares-D32cM63m.js → CuiCameraShares-B2RdPWWh.js} +1 -1
- package/dist/interface/assets/CuiCameraTable-4ikGv8QA.js +1 -0
- package/dist/interface/assets/{CuiCameraTable-Bk9N-Oc6.js → CuiCameraTable-BKBvhRDc.js} +2 -2
- package/dist/interface/assets/CuiChartTable-CqKo74Tf.css +1 -0
- package/dist/interface/assets/CuiChartTable-Lvz9Zq0k.js +1 -0
- package/dist/interface/assets/CuiConsole-DUswVhFC.js +5 -0
- package/dist/interface/assets/CuiDataTable-CIP95jVF.js +1 -0
- package/dist/interface/assets/CuiLightControl-CvfWo8mY.js +1 -0
- package/dist/interface/assets/{CuiList-w69dERIq.js → CuiList-CajC6jpE.js} +1 -1
- package/dist/interface/assets/CuiPluginCard-CQS8C87-.js +2 -0
- package/dist/interface/assets/CuiPluginCategoryChip-DCNG8zl1.js +1 -0
- package/dist/interface/assets/{CuiPluginOAuthButton-CNxj7cuf.js → CuiPluginOAuthButton-CvZrOWr8.js} +2 -2
- package/dist/interface/assets/{CuiPluginTrustBadge-DYsEz6WI.js → CuiPluginTrustBadge-Bc1dQgX8.js} +1 -1
- package/dist/interface/assets/CuiQRCode-CjLcB644.js +1 -0
- package/dist/interface/assets/CuiRecordingsGrid-DrpErbux.js +1 -0
- package/dist/interface/assets/{CuiRecordingsGrid-Q4C3aeLo.css → CuiRecordingsGrid-EAumRo5E.css} +1 -1
- package/dist/interface/assets/CuiSchema-BYYeh269.css +1 -0
- package/dist/interface/assets/CuiSchema-DG8peseE.js +1 -0
- package/dist/interface/assets/{CuiTopNavbarItem-7yu9c68R.js → CuiTopNavbarItem-Dq3Esl1S.js} +1 -1
- package/dist/interface/assets/Faces-D0yRgOMj.js +1 -0
- package/dist/interface/assets/FirstSteps-BW8AVmd4.js +1 -0
- package/dist/interface/assets/{Instances-Bmb-yWM-.js → Instances-BgXJvs0_.js} +1 -1
- package/dist/interface/assets/{Logs-DxHqkMpx.js → Logs-DH3rWnlV.js} +2 -2
- package/dist/interface/assets/Menu-D9FVgjwT.js +1 -0
- package/dist/interface/assets/Metrics-7bYWyGWU.js +9 -0
- package/dist/interface/assets/Metrics-DX59XXDc.css +1 -0
- package/dist/interface/assets/{OAuthDeviceFlow-CLi6l8jS.js → OAuthDeviceFlow-BS0FQbZh.js} +1 -1
- package/dist/interface/assets/Plugin-ZwkTp6C4.js +1 -0
- package/dist/interface/assets/{PluginConsole-xOY_kwz_.js → PluginConsole-Dt39TWaO.js} +2 -2
- package/dist/interface/assets/PluginDetail-D7ZtOG9Y.js +2 -0
- package/dist/interface/assets/{PluginSchema-DG3ou73v.js → PluginSchema-8T7EBq-Z.js} +1 -1
- package/dist/interface/assets/PluginSearch-DN3o0wfs.js +1 -0
- package/dist/interface/assets/PluginSearch-eRDCvu_8.js +2 -0
- package/dist/interface/assets/{PluginUpdateProgress-Bx0MXwAk.js → PluginUpdateProgress-CxjpzkGa.js} +1 -1
- package/dist/interface/assets/Plugins-DVsMpyvr.js +1 -0
- package/dist/interface/assets/Recordings-BKxSEiQo.js +1 -0
- package/dist/interface/assets/Recordings-cR4_6aGN.css +1 -0
- package/dist/interface/assets/SensorEdit-BmaMQKEa.js +1 -0
- package/dist/interface/assets/{SensorHistory-CTIUwIWN.js → SensorHistory-CmOAk-tD.js} +1 -1
- package/dist/interface/assets/Sensors-x08al7Je.js +2 -0
- package/dist/interface/assets/Settings-Cp1lzG1i.js +1 -0
- package/dist/interface/assets/SettingsAccount-BwCNddkW.js +1 -0
- package/dist/interface/assets/SettingsAccount-DC9AxqHe.js +1 -0
- package/dist/interface/assets/SettingsAppearance-CMzzDZCX.js +1 -0
- package/dist/interface/assets/{SettingsAppearance-CoTdsIwM.js → SettingsAppearance-Dr8hJloI.js} +1 -1
- package/dist/interface/assets/SettingsBackup-Dc8pDM3Y.js +1 -0
- package/dist/interface/assets/{SettingsList-W5FvbkXm.js → SettingsList-3peF3gFZ.js} +1 -1
- package/dist/interface/assets/SettingsMqtt-k-cHaOoA.js +1 -0
- package/dist/interface/assets/SettingsNotifications-BJHYlSKX.js +1 -0
- package/dist/interface/assets/SettingsPermissions-CymqvfR9.js +1 -0
- package/dist/interface/assets/{SettingsRecordings-COUi9BQg.js → SettingsRecordings-gWTD_djR.js} +1 -1
- package/dist/interface/assets/SettingsRemote-BWAhI1Gw.js +2 -0
- package/dist/interface/assets/SettingsRemote-CtP6Gz9C.css +1 -0
- package/dist/interface/assets/SettingsSystem-BGBUQvjg.js +2 -0
- package/dist/interface/assets/SettingsUsers-B_Ri5OrM.js +1 -0
- package/dist/interface/assets/Terminal-eYxvgFHE.js +4 -0
- package/dist/interface/assets/Updates-CweZekTf.js +2 -0
- package/dist/interface/assets/{VariableInput-DhAhhfsm.js → VariableInput-CM6T3pUW.js} +1 -1
- package/dist/interface/assets/{VariableSuggestions-q0Jd1DVI.js → VariableSuggestions-BGxZxS6U.js} +1 -1
- package/dist/interface/assets/{VersionsHandler-XEn3uFy7.js → VersionsHandler-BWIiBPPa.js} +2 -2
- package/dist/interface/assets/{VirtualSensorCreate-_E5E0U7E.js → VirtualSensorCreate-CFQC2-9D.js} +1 -1
- package/dist/interface/assets/Workers-B-sp43t3.js +2 -0
- package/dist/interface/assets/{Workers-7hKMBU4w.css → Workers-DH59zE1C.css} +1 -1
- package/dist/interface/assets/{api-C7Yv5Scr.js → api-CG0j1twy.js} +1 -1
- package/dist/interface/assets/api-CVOj5SYs.css +2 -0
- package/dist/interface/assets/api-C_bOaXk2.js +2 -0
- package/dist/interface/assets/{auth-BixzEnBM.js → auth-BPSOBTDX.js} +1 -1
- package/dist/interface/assets/{automations-K8XPcP3U.js → automations-BrtwUIUz.js} +1 -1
- package/dist/interface/assets/brain-7IVdhMyV.js +1 -0
- package/dist/interface/assets/cameras.schema-CoIOnGzE.js +1 -0
- package/dist/interface/assets/cloudHandoff-BiFzJEYx.js +1 -0
- package/dist/interface/assets/{config-CURuT8_k.js → config-BjldP5A6.js} +1 -1
- package/dist/interface/assets/content-copy-CLkJTA4w.js +1 -0
- package/dist/interface/assets/eventLabels-BsPoPWS9.js +1 -0
- package/dist/interface/assets/{flowSchema-szT9B0sA.js → flowSchema-B_Otn6RU.js} +1 -1
- package/dist/interface/assets/{intercomService-BjB4QQiX.js → intercomService-BpTOoSQi.js} +1 -1
- package/dist/interface/assets/lan-BP0NuDr7.js +1 -0
- package/dist/interface/assets/main-BG-VCb--.js +3 -0
- package/dist/interface/assets/main-CFQGq2Yn.css +1 -0
- package/dist/interface/assets/{mqtt-uItF3Vd7.js → mqtt-DSPPQwxJ.js} +1 -1
- package/dist/interface/assets/{notifications-CuZPzI4-.js → notifications-DEEM2oP2.js} +1 -1
- package/dist/interface/assets/{plugins-QEqfAk0G.js → plugins-DBBFdZKH.js} +1 -1
- package/dist/interface/assets/refresh-Ds2EayhZ.js +1 -0
- package/dist/interface/assets/sensorIcons-CP5EQZ60.js +1 -0
- package/dist/interface/assets/{sensors-BxvL01cB.js → sensors-CKTPsYYH.js} +1 -1
- package/dist/interface/assets/{server-CDm6FwHH.js → server-B_MuqJ5z.js} +1 -1
- package/dist/interface/assets/{theme-CRw47tnu.js → theme-FztlStyg.js} +1 -1
- package/dist/interface/assets/types-BHAzjgi3.js +1 -0
- package/dist/interface/assets/types-G1_espGV.js +1 -0
- package/dist/interface/assets/{types-DtdpTPfZ.js → types-bCt9IrFD.js} +1 -1
- package/dist/interface/assets/updates-CVapvGAI.js +1 -0
- package/dist/interface/assets/{useAppVersion-BxTTVLKg.js → useAppVersion-SrF7tGCe.js} +1 -1
- package/dist/interface/assets/useBackupRestore-Tc23Huvm.js +1 -0
- package/dist/interface/assets/{useCameraOptions-DMC2KjMS.js → useCameraOptions-zsIqf0i9.js} +1 -1
- package/dist/interface/assets/{useLogsSocket-CmyalQi8.js → useLogsSocket-DSltWBh4.js} +1 -1
- package/dist/interface/assets/{useNavLayout-BacPxpZv.js → useNavLayout-BhlxDHV7.js} +1 -1
- package/dist/interface/assets/{usePermissions-C2wQBWmH.js → usePermissions-4HqGU99G.js} +1 -1
- package/dist/interface/assets/{usePluginUpdates-CyjxFTTi.js → usePluginUpdates-CKH0Hb87.js} +1 -1
- package/dist/interface/assets/{usePushRegistration-BPSfvI_w.js → usePushRegistration-DAE3goyn.js} +1 -1
- package/dist/interface/assets/{useServerRestart-x8p2rFqn.js → useServerRestart-DoLrF47t.js} +1 -1
- package/dist/interface/assets/{useStreamStatus-PMpl519O.js → useStreamStatus-B3QIWjDE.js} +1 -1
- package/dist/interface/assets/useTabSwipe-BYFLQ9EY.js +1 -0
- package/dist/interface/assets/{useUpdateChannel-ChscS5KW.js → useUpdateChannel-DWEw-ZyB.js} +1 -1
- package/dist/interface/assets/useUpdatesSocket-CoxkS7Hr.js +1 -0
- package/dist/interface/assets/{useUserOptions-YiEj2ox6.js → useUserOptions-DCDeuz2p.js} +1 -1
- package/dist/interface/assets/vendor-cameraui-4iwjOYwz.js +2326 -0
- package/dist/interface/assets/vendor-core-gJradu21.js +1 -0
- package/dist/interface/assets/{vendor-primevue-BDu2BpXv.js → vendor-primevue-DMAuiM9b.js} +72 -72
- package/dist/interface/index.html +32 -27
- package/dist/interface/sw.js +1 -1
- package/dist/server/package.json +13 -12
- package/dist/server/requirements.txt +2 -2
- package/dist/server/src/_camera_ui_tools/camera_ui_rpc/client.py +8 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_rpc/types.py +20 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/__init__.py +8 -2
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/__init__.py +10 -2
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/config.py +9 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/detection.py +24 -10
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/enums.py +6 -7
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/notification.py +28 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/plugin/interfaces.py +24 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/__init__.py +4 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/base.py +18 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/detection.py +3 -3
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/object.py +6 -1
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/spec.py +27 -3
- package/dist/server/src/api/controllers/auth.controller.js +5 -0
- package/dist/server/src/api/controllers/auth.controller.js.map +1 -1
- package/dist/server/src/api/controllers/frameWorkers.controller.d.ts +14 -0
- package/dist/server/src/api/controllers/frameWorkers.controller.js +96 -0
- package/dist/server/src/api/controllers/frameWorkers.controller.js.map +1 -1
- package/dist/server/src/api/controllers/remote.controller.d.ts +1 -0
- package/dist/server/src/api/controllers/remote.controller.js +16 -12
- package/dist/server/src/api/controllers/remote.controller.js.map +1 -1
- package/dist/server/src/api/controllers/server.controller.js +6 -2
- package/dist/server/src/api/controllers/server.controller.js.map +1 -1
- package/dist/server/src/api/controllers/updates.controller.d.ts +8 -0
- package/dist/server/src/api/controllers/updates.controller.js +40 -0
- package/dist/server/src/api/controllers/updates.controller.js.map +1 -0
- package/dist/server/src/api/controllers/workers.controller.js +8 -2
- package/dist/server/src/api/controllers/workers.controller.js.map +1 -1
- package/dist/server/src/api/database/constants.d.ts +0 -1
- package/dist/server/src/api/database/constants.js +0 -1
- package/dist/server/src/api/database/constants.js.map +1 -1
- package/dist/server/src/api/database/index.d.ts +2 -3
- package/dist/server/src/api/database/index.js +3 -6
- package/dist/server/src/api/database/index.js.map +1 -1
- package/dist/server/src/api/database/migrations/v2-1-10.js +33 -0
- package/dist/server/src/api/database/migrations/v2-1-10.js.map +1 -0
- package/dist/server/src/api/database/migrations/v2-1-11.js +31 -0
- package/dist/server/src/api/database/migrations/v2-1-11.js.map +1 -0
- package/dist/server/src/api/database/migrations/v2-1-12.js +25 -0
- package/dist/server/src/api/database/migrations/v2-1-12.js.map +1 -0
- package/dist/server/src/api/database/migrations/v2-1-13.d.ts +3 -0
- package/dist/server/src/api/database/migrations/v2-1-13.js +51 -0
- package/dist/server/src/api/database/migrations/v2-1-13.js.map +1 -0
- package/dist/server/src/api/database/migrations/v2-1-14.d.ts +3 -0
- package/dist/server/src/api/database/migrations/v2-1-14.js +87 -0
- package/dist/server/src/api/database/migrations/v2-1-14.js.map +1 -0
- package/dist/server/src/api/database/migrations/v2-1-6.js.map +1 -1
- package/dist/server/src/api/database/migrations/v2-1-9.d.ts +3 -0
- package/dist/server/src/api/database/migrations/v2-1-9.js +38 -0
- package/dist/server/src/api/database/migrations/v2-1-9.js.map +1 -0
- package/dist/server/src/api/database/record-schemas.d.ts +18 -13
- package/dist/server/src/api/database/record-schemas.js +3 -3
- package/dist/server/src/api/database/record-schemas.js.map +1 -1
- package/dist/server/src/api/database/types.d.ts +3 -11
- package/dist/server/src/api/routes/frameWorkers.routes.js +22 -1
- package/dist/server/src/api/routes/frameWorkers.routes.js.map +1 -1
- package/dist/server/src/api/routes/index.js +3 -1
- package/dist/server/src/api/routes/index.js.map +1 -1
- package/dist/server/src/api/routes/updates.routes.d.ts +2 -0
- package/dist/server/src/api/routes/updates.routes.js +47 -0
- package/dist/server/src/api/routes/updates.routes.js.map +1 -0
- package/dist/server/src/api/routes/workers.routes.js +3 -2
- package/dist/server/src/api/routes/workers.routes.js.map +1 -1
- package/dist/server/src/api/schemas/cameras.schema.d.ts +228 -79
- package/dist/server/src/api/schemas/cameras.schema.js +58 -9
- package/dist/server/src/api/schemas/cameras.schema.js.map +1 -1
- package/dist/server/src/api/schemas/frameWorkers.schema.d.ts +6 -0
- package/dist/server/src/api/schemas/frameWorkers.schema.js +7 -0
- package/dist/server/src/api/schemas/frameWorkers.schema.js.map +1 -1
- package/dist/server/src/api/schemas/server.schema.d.ts +1 -0
- package/dist/server/src/api/schemas/server.schema.js +4 -0
- package/dist/server/src/api/schemas/server.schema.js.map +1 -1
- package/dist/server/src/api/schemas/users.schema.d.ts +119 -84
- package/dist/server/src/api/schemas/users.schema.js +3 -0
- package/dist/server/src/api/schemas/users.schema.js.map +1 -1
- package/dist/server/src/api/schemas/workers.schema.d.ts +3 -0
- package/dist/server/src/api/schemas/workers.schema.js +5 -0
- package/dist/server/src/api/schemas/workers.schema.js.map +1 -1
- package/dist/server/src/api/services/plugins.service.d.ts +4 -1
- package/dist/server/src/api/services/plugins.service.js +23 -4
- package/dist/server/src/api/services/plugins.service.js.map +1 -1
- package/dist/server/src/api/services/server.service.d.ts +3 -1
- package/dist/server/src/api/services/server.service.js +6 -1
- package/dist/server/src/api/services/server.service.js.map +1 -1
- package/dist/server/src/api/services/updates.service.d.ts +108 -0
- package/dist/server/src/api/services/updates.service.js +476 -0
- package/dist/server/src/api/services/updates.service.js.map +1 -0
- package/dist/server/src/api/types/index.d.ts +6 -0
- package/dist/server/src/api/types/index.js.map +1 -1
- package/dist/server/src/api/websocket/index.js +2 -0
- package/dist/server/src/api/websocket/index.js.map +1 -1
- package/dist/server/src/api/websocket/nsp/metrics.d.ts +3 -1
- package/dist/server/src/api/websocket/nsp/metrics.js +90 -37
- package/dist/server/src/api/websocket/nsp/metrics.js.map +1 -1
- package/dist/server/src/api/websocket/nsp/server.js +45 -9
- package/dist/server/src/api/websocket/nsp/server.js.map +1 -1
- package/dist/server/src/api/websocket/types.d.ts +23 -17
- package/dist/server/src/camera/controller.js +1 -0
- package/dist/server/src/camera/controller.js.map +1 -1
- package/dist/server/src/camera/decoder/audio-loop.d.ts +0 -1
- package/dist/server/src/camera/decoder/audio-loop.js +38 -68
- package/dist/server/src/camera/decoder/audio-loop.js.map +1 -1
- package/dist/server/src/camera/decoder/child.d.ts +4 -1
- package/dist/server/src/camera/decoder/child.js +27 -0
- package/dist/server/src/camera/decoder/child.js.map +1 -1
- package/dist/server/src/camera/decoder/debug/detection-record.d.ts +6 -0
- package/dist/server/src/camera/decoder/debug/detection-record.js +21 -0
- package/dist/server/src/camera/decoder/debug/detection-record.js.map +1 -1
- package/dist/server/src/camera/decoder/detection-coordinator.d.ts +26 -3
- package/dist/server/src/camera/decoder/detection-coordinator.js +525 -207
- package/dist/server/src/camera/decoder/detection-coordinator.js.map +1 -1
- package/dist/server/src/camera/decoder/detection-pipeline.d.ts +4 -2
- package/dist/server/src/camera/decoder/detection-pipeline.js +17 -10
- package/dist/server/src/camera/decoder/detection-pipeline.js.map +1 -1
- package/dist/server/src/camera/decoder/detection-window.d.ts +17 -0
- package/dist/server/src/camera/decoder/detection-window.js +188 -0
- package/dist/server/src/camera/decoder/detection-window.js.map +1 -0
- package/dist/server/src/camera/decoder/event-manager.d.ts +1 -0
- package/dist/server/src/camera/decoder/event-manager.js +12 -0
- package/dist/server/src/camera/decoder/event-manager.js.map +1 -1
- package/dist/server/src/camera/decoder/event-thumbnailer.d.ts +3 -3
- package/dist/server/src/camera/decoder/event-thumbnailer.js +2 -2
- package/dist/server/src/camera/decoder/event-thumbnailer.js.map +1 -1
- package/dist/server/src/camera/decoder/frame-scaler.d.ts +14 -1
- package/dist/server/src/camera/decoder/frame-scaler.js +63 -20
- package/dist/server/src/camera/decoder/frame-scaler.js.map +1 -1
- package/dist/server/src/camera/decoder/motion-localizer.d.ts +16 -0
- package/dist/server/src/camera/decoder/motion-localizer.js +49 -0
- package/dist/server/src/camera/decoder/motion-localizer.js.map +1 -0
- package/dist/server/src/camera/decoder/perf-tracker.d.ts +29 -8
- package/dist/server/src/camera/decoder/perf-tracker.js +76 -50
- package/dist/server/src/camera/decoder/perf-tracker.js.map +1 -1
- package/dist/server/src/camera/decoder/plugin-registry.d.ts +1 -0
- package/dist/server/src/camera/decoder/plugin-registry.js +8 -6
- package/dist/server/src/camera/decoder/plugin-registry.js.map +1 -1
- package/dist/server/src/camera/decoder/secondary-stage.d.ts +4 -1
- package/dist/server/src/camera/decoder/secondary-stage.js +26 -3
- package/dist/server/src/camera/decoder/secondary-stage.js.map +1 -1
- package/dist/server/src/camera/decoder/sources/analysis-source.d.ts +15 -1
- package/dist/server/src/camera/decoder/sources/audio-source.d.ts +7 -3
- package/dist/server/src/camera/decoder/sources/audio-source.js +89 -78
- package/dist/server/src/camera/decoder/sources/audio-source.js.map +1 -1
- package/dist/server/src/camera/decoder/sources/buffered-source.d.ts +27 -9
- package/dist/server/src/camera/decoder/sources/buffered-source.js +151 -65
- package/dist/server/src/camera/decoder/sources/buffered-source.js.map +1 -1
- package/dist/server/src/camera/decoder/sources/frame-handle.d.ts +13 -0
- package/dist/server/src/camera/decoder/sources/frame-handle.js +106 -0
- package/dist/server/src/camera/decoder/sources/frame-handle.js.map +1 -0
- package/dist/server/src/camera/decoder/sources/frame-source.d.ts +25 -26
- package/dist/server/src/camera/decoder/sources/frame-source.js +213 -268
- package/dist/server/src/camera/decoder/sources/frame-source.js.map +1 -1
- package/dist/server/src/camera/decoder/sources/reconnect-loop.d.ts +17 -0
- package/dist/server/src/camera/decoder/sources/reconnect-loop.js +59 -0
- package/dist/server/src/camera/decoder/sources/reconnect-loop.js.map +1 -0
- package/dist/server/src/camera/decoder/sources/snapshot-fetcher.d.ts +15 -0
- package/dist/server/src/camera/decoder/sources/snapshot-fetcher.js +58 -0
- package/dist/server/src/camera/decoder/sources/snapshot-fetcher.js.map +1 -0
- package/dist/server/src/camera/decoder/sources/video-input.d.ts +18 -0
- package/dist/server/src/camera/decoder/sources/video-input.js +55 -0
- package/dist/server/src/camera/decoder/sources/video-input.js.map +1 -0
- package/dist/server/src/camera/decoder/types.d.ts +74 -18
- package/dist/server/src/camera/decoder/types.js +2 -0
- package/dist/server/src/camera/decoder/types.js.map +1 -1
- package/dist/server/src/camera/decoder/worker.d.ts +5 -1
- package/dist/server/src/camera/decoder/worker.js +35 -5
- package/dist/server/src/camera/decoder/worker.js.map +1 -1
- package/dist/server/src/camera/index.d.ts +2 -1
- package/dist/server/src/camera/index.js +3 -0
- package/dist/server/src/camera/index.js.map +1 -1
- package/dist/server/src/camera/streaming/fmp4-session.js +1 -1
- package/dist/server/src/camera/streaming/fmp4-session.js.map +1 -1
- package/dist/server/src/camera/streaming/rtp-session.js +1 -1
- package/dist/server/src/camera/streaming/rtp-session.js.map +1 -1
- package/dist/server/src/main.js +22 -2
- package/dist/server/src/main.js.map +1 -1
- package/dist/server/src/manager/types.d.ts +1 -0
- package/dist/server/src/manager/types.js +2 -0
- package/dist/server/src/manager/types.js.map +1 -1
- package/dist/server/src/mqtt/bridge.d.ts +0 -1
- package/dist/server/src/mqtt/bridge.js +0 -33
- package/dist/server/src/mqtt/bridge.js.map +1 -1
- package/dist/server/src/mqtt/ha-discovery.js +0 -1
- package/dist/server/src/mqtt/ha-discovery.js.map +1 -1
- package/dist/server/src/plugins/index.js +0 -2
- package/dist/server/src/plugins/index.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.js +9 -0
- package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/proxy/sensorManager.js +8 -0
- package/dist/server/src/plugins/runtime/node/proxy/sensorManager.js.map +1 -1
- package/dist/server/src/plugins/runtime/python/proxy/camera_device.py +14 -0
- package/dist/server/src/plugins/runtime/python/proxy/sensor.py +1 -0
- package/dist/server/src/plugins/runtime/python/proxy/sensor_manager.py +13 -0
- package/dist/server/src/remote/services/cloudflare-managed.d.ts +2 -0
- package/dist/server/src/remote/services/cloudflare-managed.js +9 -2
- package/dist/server/src/remote/services/cloudflare-managed.js.map +1 -1
- package/dist/server/src/remote/services/cloudflare.d.ts +3 -0
- package/dist/server/src/remote/services/cloudflare.js +11 -0
- package/dist/server/src/remote/services/cloudflare.js.map +1 -1
- package/dist/server/src/remote/services/index.d.ts +2 -0
- package/dist/server/src/remote/services/index.js +60 -8
- package/dist/server/src/remote/services/index.js.map +1 -1
- package/dist/server/src/remote/services/tunnelConnections.d.ts +9 -0
- package/dist/server/src/remote/services/tunnelConnections.js +45 -0
- package/dist/server/src/remote/services/tunnelConnections.js.map +1 -0
- package/dist/server/src/remote/services/urlReadiness.d.ts +2 -0
- package/dist/server/src/remote/services/urlReadiness.js +7 -4
- package/dist/server/src/remote/services/urlReadiness.js.map +1 -1
- package/dist/server/src/remote/types.d.ts +1 -2
- package/dist/server/src/rpc/interfaces/frameworker.d.ts +4 -1
- package/dist/server/src/rpc/interfaces/sensor.d.ts +1 -0
- package/dist/server/src/rpc/leafConnection.d.ts +12 -0
- package/dist/server/src/rpc/leafConnection.js +60 -0
- package/dist/server/src/rpc/leafConnection.js.map +1 -0
- package/dist/server/src/rpc/server.d.ts +2 -0
- package/dist/server/src/rpc/server.js +7 -1
- package/dist/server/src/rpc/server.js.map +1 -1
- package/dist/server/src/sensors/registry.d.ts +2 -0
- package/dist/server/src/sensors/registry.js +16 -0
- package/dist/server/src/sensors/registry.js.map +1 -1
- package/dist/server/src/services/config/index.js +2 -0
- package/dist/server/src/services/config/index.js.map +1 -1
- package/dist/server/src/types.d.ts +10 -1
- package/dist/server/src/utils/ipc.d.ts +9 -0
- package/dist/server/src/utils/ipc.js +31 -5
- package/dist/server/src/utils/ipc.js.map +1 -1
- package/dist/server/src/utils/plugin-registry/index.js +1 -1
- package/dist/server/src/utils/plugin-registry/index.js.map +1 -1
- package/dist/server/src/utils/system-info.d.ts +9 -0
- package/dist/server/src/utils/system-info.js +48 -0
- package/dist/server/src/utils/system-info.js.map +1 -0
- package/dist/server/src/workers/agent.d.ts +3 -0
- package/dist/server/src/workers/agent.js +36 -8
- package/dist/server/src/workers/agent.js.map +1 -1
- package/dist/server/src/workers/capabilities/frame-decoding.d.ts +2 -2
- package/dist/server/src/workers/capabilities/frame-decoding.js +5 -5
- package/dist/server/src/workers/capabilities/frame-decoding.js.map +1 -1
- package/dist/server/src/workers/capabilities/handler.d.ts +2 -2
- package/dist/server/src/workers/capabilities/plugin-host.d.ts +2 -2
- package/dist/server/src/workers/capabilities/plugin-host.js +6 -6
- package/dist/server/src/workers/capabilities/plugin-host.js.map +1 -1
- package/dist/server/src/workers/manager.d.ts +9 -1
- package/dist/server/src/workers/manager.js +24 -0
- package/dist/server/src/workers/manager.js.map +1 -1
- package/dist/server/src/workers/types.d.ts +14 -0
- package/dist/server/src/workers/types.js.map +1 -1
- package/package.json +13 -12
- package/dist/interface/assets/Automation-DDNhcjDg.js +0 -3
- package/dist/interface/assets/AutomationImport-DAeFd-SZ.js +0 -2
- package/dist/interface/assets/Automations-BeGe7iQE.js +0 -2
- package/dist/interface/assets/Camera-C0sNg85L.js +0 -2
- package/dist/interface/assets/Camera-C5gF7yVN.css +0 -1
- package/dist/interface/assets/Camview-DzMmXQvW.js +0 -1
- package/dist/interface/assets/ConfigActionCameraControl-BMlRbdRM.js +0 -1
- package/dist/interface/assets/ConfigTriggerDetection-ijTkd_SS.js +0 -1
- package/dist/interface/assets/ConfigTriggerGeofence-Q7LQpVoM.js +0 -1
- package/dist/interface/assets/Console-Gu1eu82G.js +0 -1
- package/dist/interface/assets/CuiAutomationRunsDialog-CxcWpqrI.js +0 -1
- package/dist/interface/assets/CuiBottomSheet-BkmkKf1u.js +0 -1
- package/dist/interface/assets/CuiCameraPipCard-CuZZZ7Cv.js +0 -1
- package/dist/interface/assets/CuiCameraRecordings-BxEA_Vhd.js +0 -1
- package/dist/interface/assets/CuiCameraTable-67Itb7my.js +0 -1
- package/dist/interface/assets/CuiConsole-De9JpSLz.js +0 -5
- package/dist/interface/assets/CuiDataTable-P8bWN0ZK.js +0 -1
- package/dist/interface/assets/CuiLightControl-D5Z1AqWH.js +0 -1
- package/dist/interface/assets/CuiPluginCard-D3x9FPb6.js +0 -2
- package/dist/interface/assets/CuiPluginCategoryChip-CQ2FJafE.js +0 -1
- package/dist/interface/assets/CuiQRCode-CZnspFFw.js +0 -1
- package/dist/interface/assets/CuiRecordingsGrid-wEaU6-3c.js +0 -1
- package/dist/interface/assets/CuiSchema--yMBD996.css +0 -1
- package/dist/interface/assets/CuiSchema-DLV1lIiB.js +0 -1
- package/dist/interface/assets/Faces-BlPQek1-.js +0 -1
- package/dist/interface/assets/FirstSteps-C-DKv_P2.js +0 -1
- package/dist/interface/assets/Menu-DceReBlq.js +0 -1
- package/dist/interface/assets/Metrics-BstlZavQ.css +0 -1
- package/dist/interface/assets/Metrics-Cw_Z_e0C.js +0 -1
- package/dist/interface/assets/Plugin-BBVxbQeT.js +0 -1
- package/dist/interface/assets/PluginDetail-D2RncxpL.js +0 -2
- package/dist/interface/assets/PluginSearch-CBhloRm6.js +0 -1
- package/dist/interface/assets/PluginSearch-RGhSeM1T.js +0 -2
- package/dist/interface/assets/Plugins-COHwVV0A.js +0 -1
- package/dist/interface/assets/Recordings-H5qOm_rD.js +0 -1
- package/dist/interface/assets/Recordings-zdTzwv7C.css +0 -1
- package/dist/interface/assets/SensorEdit-C-H_3ap8.js +0 -1
- package/dist/interface/assets/Sensors-lPcOdHJN.js +0 -2
- package/dist/interface/assets/Settings-BUoXoFiH.js +0 -1
- package/dist/interface/assets/SettingsAccount-CudWgknL.js +0 -1
- package/dist/interface/assets/SettingsAccount-DUy_W02_.js +0 -1
- package/dist/interface/assets/SettingsAppearance-eZsdWWp3.js +0 -1
- package/dist/interface/assets/SettingsBackup-ibb5I5Jv.js +0 -1
- package/dist/interface/assets/SettingsMqtt-Ct42icAW.js +0 -1
- package/dist/interface/assets/SettingsNotifications-B4lyMlLR.js +0 -1
- package/dist/interface/assets/SettingsPermissions-Bsv293ri.js +0 -1
- package/dist/interface/assets/SettingsRemote-DYld6-1F.css +0 -1
- package/dist/interface/assets/SettingsRemote-Dq_BD0fk.js +0 -1
- package/dist/interface/assets/SettingsSystem-C5xROdA0.js +0 -2
- package/dist/interface/assets/SettingsUsers-Cjk6mevW.js +0 -1
- package/dist/interface/assets/Terminal-BIdBrL4g.js +0 -4
- package/dist/interface/assets/Workers-DVABUc39.js +0 -2
- package/dist/interface/assets/api-AVHJeggR.css +0 -2
- package/dist/interface/assets/api-CmdQlVDD.js +0 -2
- package/dist/interface/assets/cameras.schema-A_6JklLs.js +0 -1
- package/dist/interface/assets/cloudHandoff-D0ytFfHK.js +0 -1
- package/dist/interface/assets/deployed-code-update-xHE-blDN.js +0 -1
- package/dist/interface/assets/main-B1ryIyTr.css +0 -1
- package/dist/interface/assets/main-BdIdmDhs.js +0 -3
- package/dist/interface/assets/round-restart-alt-B_7HR24a.js +0 -1
- package/dist/interface/assets/round-restart-alt-CmrjVFat.css +0 -1
- package/dist/interface/assets/sensorIcons-BujySw0j.js +0 -1
- package/dist/interface/assets/types-R10eIequ.js +0 -1
- package/dist/interface/assets/types-p_uC0rfp.js +0 -1
- package/dist/interface/assets/useBackupRestore-DLieDBFC.js +0 -1
- package/dist/interface/assets/vendor-cameraui-Cf0XVNQ7.js +0 -2326
- package/dist/interface/assets/vendor-core-u4xNQ-aF.js +0 -1
- package/dist/server/src/api/database/migrations/v2-0-50.js +0 -45
- package/dist/server/src/api/database/migrations/v2-0-50.js.map +0 -1
- package/dist/server/src/api/database/migrations/v2-0-51.js +0 -43
- package/dist/server/src/api/database/migrations/v2-0-51.js.map +0 -1
- package/dist/server/src/api/database/migrations/v2-1-0.js +0 -72
- package/dist/server/src/api/database/migrations/v2-1-0.js.map +0 -1
- /package/dist/server/src/api/database/migrations/{v2-0-50.d.ts → v2-1-10.d.ts} +0 -0
- /package/dist/server/src/api/database/migrations/{v2-0-51.d.ts → v2-1-11.d.ts} +0 -0
- /package/dist/server/src/api/database/migrations/{v2-1-0.d.ts → v2-1-12.d.ts} +0 -0
|
@@ -19,19 +19,21 @@ import { AudioDetectionLoop } from './audio-loop.js';
|
|
|
19
19
|
import { CascadeManager } from './cascade-manager.js';
|
|
20
20
|
import { detectionRecord } from './debug/detection-record.js';
|
|
21
21
|
import { DetectionPipeline } from './detection-pipeline.js';
|
|
22
|
+
import { clusterBoxes, DetectionWindow, mergeWindowDetections, MOTION_PAD, planWindowsOnce, TRACK_PAD } from './detection-window.js';
|
|
22
23
|
import { DwellManager } from './dwell-manager.js';
|
|
23
24
|
import { DetectionEventManager, MOMENT_RANK_ATTRIBUTE, MOMENT_RANK_OBJECT } from './event-manager.js';
|
|
24
25
|
import { EventThumbnailer } from './event-thumbnailer.js';
|
|
25
26
|
import { FrameScaler } from './frame-scaler.js';
|
|
26
27
|
import { directionBetween, directionOf, MOMENT_FORMATS, MOMENT_QUALITY, momentWindow, unionBox } from './moment-crop.js';
|
|
28
|
+
import { MotionLocalizer } from './motion-localizer.js';
|
|
27
29
|
import { PerfTracker } from './perf-tracker.js';
|
|
28
30
|
import { normalizePlateText } from './plate-vote.js';
|
|
29
|
-
import { isVideoInputSpec, PluginRegistry } from './plugin-registry.js';
|
|
31
|
+
import { isVideoInputSpec, modelIdentity, PluginRegistry } from './plugin-registry.js';
|
|
30
32
|
import { PtzAutotracker } from './ptz/autotracker.js';
|
|
31
|
-
import { ReconnectBackoff } from './reconnect-backoff.js';
|
|
32
33
|
import { SecondaryStage } from './secondary-stage.js';
|
|
34
|
+
import { BufferedSource } from './sources/buffered-source.js';
|
|
33
35
|
import { FrameSource } from './sources/frame-source.js';
|
|
34
|
-
import { DETECT_TIMEOUT_MS, ensureDetectionBoxes, isFullFrameBox, MOTION_WIDTH_MAP } from './types.js';
|
|
36
|
+
import { DETECT_TIMEOUT_MS, DETECTOR_METRIC_TYPES, ensureDetectionBoxes, isFullFrameBox, MOTION_WIDTH_MAP } from './types.js';
|
|
35
37
|
const MOMENT_EVENTS = new Set(['objectEntered', 'objectWoke', 'objectRecovered', 'bestShotUpdated']);
|
|
36
38
|
const MOMENT_MOVING_SPEED = 0.05;
|
|
37
39
|
const MOMENT_ATTRIBUTE_MIN_AREA = 600;
|
|
@@ -46,6 +48,7 @@ const ACTIVE_TICK_MS = 100;
|
|
|
46
48
|
const MOTION_INTERVAL_MS = 200;
|
|
47
49
|
const TICK_SLACK_MS = 20;
|
|
48
50
|
const MAIN_STREAM_HOLD_MS = 5000;
|
|
51
|
+
const EXTERNAL_FRAME_MAX_AGE_MS = 1000;
|
|
49
52
|
let DetectionCoordinator = class DetectionCoordinator {
|
|
50
53
|
config;
|
|
51
54
|
proxy;
|
|
@@ -69,10 +72,17 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
69
72
|
feedingSensors = new Map();
|
|
70
73
|
dwell = new DwellManager();
|
|
71
74
|
privacy;
|
|
72
|
-
videoBackoff = new ReconnectBackoff();
|
|
73
75
|
// debugging
|
|
74
76
|
perf = new PerfTracker();
|
|
77
|
+
benchmarkRunning = false;
|
|
75
78
|
hqUpgrade;
|
|
79
|
+
localizer;
|
|
80
|
+
lastLocalizeAt = 0;
|
|
81
|
+
anchorBoxes = [];
|
|
82
|
+
trackAnchorBoxes = [];
|
|
83
|
+
farewellBoxes = [];
|
|
84
|
+
lastTrackedById = new Map();
|
|
85
|
+
detectionWindow = new DetectionWindow();
|
|
76
86
|
mainStreamActive = false;
|
|
77
87
|
idleSince = 0;
|
|
78
88
|
lastMotionAt = 0;
|
|
@@ -92,28 +102,31 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
92
102
|
this.config = config;
|
|
93
103
|
this.proxy = proxy;
|
|
94
104
|
this.logger = logger;
|
|
95
|
-
const
|
|
96
|
-
streamUrl: config.streamUrl,
|
|
97
|
-
snapshotUrl: config.snapshotUrl,
|
|
98
|
-
decoder: this.config.frameWorkerSettings.decoder,
|
|
99
|
-
};
|
|
105
|
+
const snapshot = { snapshotUrl: config.snapshotUrl };
|
|
100
106
|
if (config.controllerSnapshotSourceId) {
|
|
101
107
|
const sourceId = config.controllerSnapshotSourceId;
|
|
102
108
|
const controllerProxy = this.proxy.createProxy(NamespaceManager.cameraNamespaces(config.cameraId).cameraControllerRpc);
|
|
103
|
-
|
|
109
|
+
snapshot.snapshotProvider = async () => {
|
|
104
110
|
const jpeg = await controllerProxy.snapshot(sourceId, true, true);
|
|
105
111
|
if (!jpeg || jpeg.byteLength === 0)
|
|
106
112
|
return null;
|
|
107
113
|
return Buffer.from(jpeg);
|
|
108
114
|
};
|
|
109
115
|
}
|
|
110
|
-
this.frameSource = new FrameSource(frameSourceConfig, logger);
|
|
111
116
|
this.config.zones = normalizeZones(config.zones);
|
|
112
117
|
this.privacy = new PrivacyMask(logger);
|
|
113
118
|
this.privacy.update(this.config.zones);
|
|
119
|
+
const decoder = this.config.frameWorkerSettings.decoder;
|
|
120
|
+
this.frameSource = config.streamHot
|
|
121
|
+
? new BufferedSource({ url: config.streamUrl, decoder, privacy: this.privacy, snapshot }, logger)
|
|
122
|
+
: new FrameSource({ streamUrl: config.streamUrl, decoder, ...snapshot }, logger);
|
|
123
|
+
// a hot source buffers from the start, the loop only decides what is analysed
|
|
124
|
+
if (config.streamHot)
|
|
125
|
+
this.frameSource.start();
|
|
114
126
|
this.frameScaler = new FrameScaler(null, logger, this.privacy);
|
|
127
|
+
this.localizer = new MotionLocalizer();
|
|
115
128
|
this.pipeline = new DetectionPipeline(this.pipelineZones(), config.detectionSettings);
|
|
116
|
-
this.secondaries = new SecondaryStage(this, this.plugins, this.pipeline, this.frameScaler, this.proxy, logger);
|
|
129
|
+
this.secondaries = new SecondaryStage(this, this.plugins, this.pipeline, this.frameScaler, this.proxy, this.perf, logger);
|
|
117
130
|
if (config.zones.lines.length > 0) {
|
|
118
131
|
this.pipeline.updateLines(config.zones.lines, this.videoAspectRatio);
|
|
119
132
|
}
|
|
@@ -164,6 +177,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
164
177
|
this.cascadeUnsubscribe = this.cascade.onChange((event) => {
|
|
165
178
|
if (event.type === 'activated') {
|
|
166
179
|
this.startAdHocVideoLoopIfNeeded();
|
|
180
|
+
this.seedLocalizerReference();
|
|
167
181
|
}
|
|
168
182
|
else {
|
|
169
183
|
this.stopAdHocVideoLoopIfIdle();
|
|
@@ -191,6 +205,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
191
205
|
if (!this.dwell.hasActive() && this.worldSpans.size === 0 && !this.eventManager.hasActiveSegment()) {
|
|
192
206
|
this.eventManager.forceEndActiveEvent();
|
|
193
207
|
}
|
|
208
|
+
this.stopAdHocVideoLoopIfIdle();
|
|
194
209
|
}
|
|
195
210
|
});
|
|
196
211
|
}
|
|
@@ -201,7 +216,86 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
201
216
|
return this.config.detectionSettings;
|
|
202
217
|
}
|
|
203
218
|
getPerfSnapshot() {
|
|
204
|
-
|
|
219
|
+
const { timings, ...counters } = this.perf.snapshot();
|
|
220
|
+
const detectors = {};
|
|
221
|
+
for (const type of DETECTOR_METRIC_TYPES) {
|
|
222
|
+
const plugin = this.plugins.get(type);
|
|
223
|
+
// motion without frames is the camera's own signal, there is no detector to report
|
|
224
|
+
if (!plugin || (type === SensorType.Motion && !plugin.requiresFrames))
|
|
225
|
+
continue;
|
|
226
|
+
const input = plugin.modelSpec?.input;
|
|
227
|
+
detectors[type] = {
|
|
228
|
+
plugin: plugin.pluginId,
|
|
229
|
+
input: isVideoInputSpec(input) ? `${input.width}x${input.height}` : undefined,
|
|
230
|
+
runtime: plugin.modelSpec?.runtime,
|
|
231
|
+
models: plugin.modelSpec?.models,
|
|
232
|
+
...timings[type],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
...counters,
|
|
237
|
+
detectors,
|
|
238
|
+
mainStreamEnabled: this.mainStreamAvailable,
|
|
239
|
+
frameAnalysis: this.plugins.shouldVideoBeActive(),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
resetPerf() {
|
|
243
|
+
this.perf.reset();
|
|
244
|
+
}
|
|
245
|
+
pauseForBenchmark(paused) {
|
|
246
|
+
this.benchmarkRunning = paused;
|
|
247
|
+
}
|
|
248
|
+
async runObjectBenchmark(iterations, concurrency) {
|
|
249
|
+
const plugin = this.plugins.get(SensorType.Object);
|
|
250
|
+
const input = plugin?.modelSpec?.input;
|
|
251
|
+
if (!plugin || !isVideoInputSpec(input))
|
|
252
|
+
return null;
|
|
253
|
+
const channels = input.format === 'gray' ? 1 : input.format === 'nv12' ? 1.5 : 3;
|
|
254
|
+
const frame = {
|
|
255
|
+
id: 'benchmark',
|
|
256
|
+
data: Buffer.alloc(Math.round(input.width * input.height * channels)),
|
|
257
|
+
width: input.width,
|
|
258
|
+
height: input.height,
|
|
259
|
+
format: input.format,
|
|
260
|
+
};
|
|
261
|
+
let handlerMs = 0;
|
|
262
|
+
let completed = 0;
|
|
263
|
+
let failed = 0;
|
|
264
|
+
const timed = this.proxy.createProxy(NamespaceManager.sensorProviderNamespaces(plugin.pluginId, plugin.sensorId).sensorRpc, {
|
|
265
|
+
onTiming: (_method, timing) => {
|
|
266
|
+
handlerMs += timing.handlerMs;
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
const startedAt = Date.now();
|
|
270
|
+
const workers = Array.from({ length: Math.max(1, concurrency) }, async () => {
|
|
271
|
+
while (completed + failed < iterations) {
|
|
272
|
+
completed++;
|
|
273
|
+
try {
|
|
274
|
+
await PromiseTimeout(timed.detectObjects(frame), DETECT_TIMEOUT_MS, undefined, `Benchmark call timed out after ${DETECT_TIMEOUT_MS}ms`);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
completed--;
|
|
278
|
+
failed++;
|
|
279
|
+
this.logger.debug('Benchmark call failed:', error);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
await Promise.all(workers);
|
|
284
|
+
const totalMs = Date.now() - startedAt;
|
|
285
|
+
return {
|
|
286
|
+
camera: '',
|
|
287
|
+
plugin: plugin.pluginId,
|
|
288
|
+
sensorId: plugin.sensorId,
|
|
289
|
+
runtime: plugin.modelSpec?.runtime,
|
|
290
|
+
models: plugin.modelSpec?.models,
|
|
291
|
+
input: `${input.width}x${input.height}`,
|
|
292
|
+
iterations: completed,
|
|
293
|
+
failed,
|
|
294
|
+
concurrency,
|
|
295
|
+
totalMs,
|
|
296
|
+
perSecond: totalMs > 0 ? Math.round((completed / (totalMs / 1000)) * 10) / 10 : 0,
|
|
297
|
+
handlerMs: completed > 0 ? Math.round((handlerMs / completed) * 10) / 10 : 0,
|
|
298
|
+
};
|
|
205
299
|
}
|
|
206
300
|
updateZoneConfig(zones) {
|
|
207
301
|
zones = normalizeZones(zones);
|
|
@@ -234,6 +328,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
234
328
|
}
|
|
235
329
|
async dispose() {
|
|
236
330
|
await this.stopVideoLoop();
|
|
331
|
+
await this.frameSource.stop();
|
|
237
332
|
await this.audioLoop.stop();
|
|
238
333
|
await this.thumbnailer.stop();
|
|
239
334
|
this.ptzAutotracker.dispose();
|
|
@@ -293,6 +388,12 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
293
388
|
// the registry only announces the plugin assigned for this type, everyone else reports into the void
|
|
294
389
|
if (!this.feedingSensors.has(sensorId))
|
|
295
390
|
return;
|
|
391
|
+
// a detector reports its spec again once its models finished loading
|
|
392
|
+
if (properties.modelSpec) {
|
|
393
|
+
this.applyModelSpec(sensorId, sensorType, properties.modelSpec);
|
|
394
|
+
if (Object.keys(properties).length === 1)
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
296
397
|
if (sensorType === SensorType.Object) {
|
|
297
398
|
const filtered = this.applyExternalDetectionFilters(sensorType, properties);
|
|
298
399
|
// presence feed keeps the autotracker's return-home timer alive even while
|
|
@@ -301,6 +402,9 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
301
402
|
this.ptzAutotracker.handlePresenceDetections(externalDetections);
|
|
302
403
|
if (this.ptzAutotracker.suppressionActive)
|
|
303
404
|
return;
|
|
405
|
+
detectionRecord.tick({
|
|
406
|
+
externalObject: { detected: filtered.detected === true, count: (filtered.detections ?? []).length },
|
|
407
|
+
});
|
|
304
408
|
this.ingestDetectionResult(SensorType.Object, sensorId, filtered);
|
|
305
409
|
this.eventManager.processResults(this.buildSnapshot());
|
|
306
410
|
// re-shoot the event thumbnail: smart-camera reports arrive after the
|
|
@@ -314,78 +418,78 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
314
418
|
if (filtered.detected !== true)
|
|
315
419
|
return;
|
|
316
420
|
// assist alone is enough: its boxes upgrade zones, bboxes and thumbnails
|
|
317
|
-
// even when no face/plate/clip secondary consumes them
|
|
318
|
-
|
|
421
|
+
// even when no face/plate/clip secondary consumes them; without either,
|
|
422
|
+
// the embedded diff's anchors can still localize a moment crop
|
|
423
|
+
const hasAnchors = this.anchorBoxes.length > 0 || this.farewellBoxes.length > 0;
|
|
424
|
+
if (!this.plugins.hasFrameBasedSecondary() && !this.plugins.hasEligibleObjectAssist() && !hasAnchors)
|
|
319
425
|
return;
|
|
320
426
|
if (this.processingExternalSecondary)
|
|
321
427
|
return; // previous RPC still running
|
|
322
428
|
this.processingExternalSecondary = true;
|
|
323
429
|
try {
|
|
324
430
|
const rawExternal = filtered.detections ?? [];
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
try {
|
|
328
|
-
const results = { timestamp: Date.now() };
|
|
329
|
-
const objects = await this.runObjectAssist(peeked.frame, rawExternal);
|
|
330
|
-
this.ingestAssistedObjects(sensorId, objects);
|
|
331
|
-
// processExternal only adapts the shape, external boxes get no real tracking
|
|
332
|
-
const objectDetections = this.pipeline.processExternal(objects.detections);
|
|
333
|
-
await this.runSecondariesAndThumbnails({ frame: peeked.frame, scaler: this.frameScaler, isMainStream: false }, objectDetections, results);
|
|
334
|
-
this.ingestResultsForAllSecondaries(results);
|
|
335
|
-
const snapshot = this.buildSnapshot();
|
|
336
|
-
if (results.thumbnails && results.thumbnails.length > 0) {
|
|
337
|
-
snapshot.thumbnails = results.thumbnails;
|
|
338
|
-
}
|
|
339
|
-
this.eventManager.processResults(snapshot);
|
|
340
|
-
}
|
|
341
|
-
finally {
|
|
342
|
-
await peeked[Symbol.asyncDispose]();
|
|
343
|
-
}
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const fetched = await this.frameSource.fetchSnapshotFrame();
|
|
347
|
-
if (!fetched)
|
|
431
|
+
const handle = await this.frameSource.getFrame(EXTERNAL_FRAME_MAX_AGE_MS);
|
|
432
|
+
if (!handle)
|
|
348
433
|
return;
|
|
349
434
|
try {
|
|
350
435
|
const results = { timestamp: Date.now() };
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
results.thumbnails
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
this.
|
|
372
|
-
}
|
|
373
|
-
this.ingestResultsForAllSecondaries(results);
|
|
374
|
-
const snapshot = this.buildSnapshot();
|
|
375
|
-
if (results.thumbnails && results.thumbnails.length > 0) {
|
|
376
|
-
snapshot.thumbnails = results.thumbnails;
|
|
436
|
+
// anchors may have switched analysis to the main stream: the assist
|
|
437
|
+
// windows and secondary crops should see those native pixels
|
|
438
|
+
const analysis = await this.acquireAnalysisFrame(handle.frame);
|
|
439
|
+
try {
|
|
440
|
+
const objects = await this.runObjectAssist(analysis.frame, rawExternal, analysis.scaler);
|
|
441
|
+
this.ingestAssistedObjects(sensorId, objects);
|
|
442
|
+
if (objects.assisted) {
|
|
443
|
+
// real boxes: crop the object like the frame pipeline, so face and
|
|
444
|
+
// plate secondaries run on the subject instead of the whole scene
|
|
445
|
+
// (processExternal only adapts the shape, external boxes get no real tracking)
|
|
446
|
+
const objectDetections = this.pipeline.processExternal(objects.detections);
|
|
447
|
+
detectionRecord.tick({ assistProcessed: { count: objectDetections.length, bufferedObjects: this.currentDetectionState.object?.detections?.length ?? 0 } });
|
|
448
|
+
await this.runSecondariesAndThumbnails(analysis, objectDetections, results);
|
|
449
|
+
await this.captureExternalMoment(objectDetections, analysis, results.timestamp);
|
|
450
|
+
this.ingestResultsForAllSecondaries(results);
|
|
451
|
+
const snapshot = this.buildSnapshot();
|
|
452
|
+
detectionRecord.tick({ assistSnapshot: { objects: snapshot.objects.length } });
|
|
453
|
+
if (results.thumbnails && results.thumbnails.length > 0) {
|
|
454
|
+
snapshot.thumbnails = results.thumbnails;
|
|
455
|
+
}
|
|
456
|
+
this.eventManager.processResults(snapshot);
|
|
377
457
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
458
|
+
else {
|
|
459
|
+
// no assist boxes: the diff's motion clusters are the only
|
|
460
|
+
// localization, still better than a full-scene card
|
|
461
|
+
const anchorDetections = this.anchorMomentDetections(rawExternal);
|
|
462
|
+
if (anchorDetections.length > 0) {
|
|
463
|
+
await this.captureExternalMoment(anchorDetections, analysis, results.timestamp);
|
|
464
|
+
}
|
|
465
|
+
const sceneJpeg = await this.secondaries.detectFullFrame(analysis.frame, results);
|
|
466
|
+
try {
|
|
467
|
+
results.thumbnails = await this.secondaries.generateThumbnails(analysis.frame, analysis.scaler, results);
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
this.logger.error('Thumbnail generation error:', error);
|
|
471
|
+
}
|
|
472
|
+
this.ingestResultsForAllSecondaries(results);
|
|
473
|
+
const snapshot = this.buildSnapshot();
|
|
474
|
+
if (results.thumbnails && results.thumbnails.length > 0) {
|
|
475
|
+
snapshot.thumbnails = results.thumbnails;
|
|
476
|
+
}
|
|
477
|
+
if (sceneJpeg)
|
|
478
|
+
snapshot.eventThumbnail = sceneJpeg;
|
|
479
|
+
const wantsEventThumb = this.eventManager.needsThumbnail() || this.snapshotWillStartEvent(snapshot);
|
|
480
|
+
this.eventManager.processResults(snapshot);
|
|
481
|
+
if (wantsEventThumb) {
|
|
482
|
+
this.thumbnailer.upgradeEventThumbnailAsync();
|
|
483
|
+
}
|
|
384
484
|
}
|
|
385
485
|
}
|
|
486
|
+
finally {
|
|
487
|
+
if (analysis.frame !== handle.frame)
|
|
488
|
+
analysis.frame[Symbol.dispose]?.();
|
|
489
|
+
}
|
|
386
490
|
}
|
|
387
491
|
finally {
|
|
388
|
-
await
|
|
492
|
+
await handle[Symbol.asyncDispose]();
|
|
389
493
|
}
|
|
390
494
|
}
|
|
391
495
|
finally {
|
|
@@ -442,18 +546,28 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
442
546
|
hasPlugin(sensorType) {
|
|
443
547
|
return this.plugins.has(sensorType);
|
|
444
548
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
for
|
|
452
|
-
|
|
549
|
+
get windowAnchorsWanted() {
|
|
550
|
+
return this.plugins.get(SensorType.Object)?.requiresFrames === true || this.plugins.hasEligibleObjectAssist();
|
|
551
|
+
}
|
|
552
|
+
get localizerWanted() {
|
|
553
|
+
// the registry only holds frame-based providers; a smart-camera object
|
|
554
|
+
// sensor (Reolink AI, ONVIF) feeds externally and needs the anchors just
|
|
555
|
+
// as much — for assist zoom windows and for framing its moment pictures
|
|
556
|
+
if (this.plugins.has(SensorType.Object) || this.plugins.hasEligibleObjectAssist())
|
|
557
|
+
return true;
|
|
558
|
+
for (const sensorType of this.feedingSensors.values()) {
|
|
559
|
+
if (sensorType === SensorType.Object)
|
|
453
560
|
return true;
|
|
454
561
|
}
|
|
455
562
|
return false;
|
|
456
563
|
}
|
|
564
|
+
get windowWantsMainStream() {
|
|
565
|
+
if (!this.cascade.isActive)
|
|
566
|
+
return false;
|
|
567
|
+
if (this.anchorBoxes.length === 0 && this.trackAnchorBoxes.length === 0)
|
|
568
|
+
return false;
|
|
569
|
+
return this.windowAnchorsWanted;
|
|
570
|
+
}
|
|
457
571
|
get cascadeEnabled() {
|
|
458
572
|
return this.config.detectionSettings.cascadeDetection !== false;
|
|
459
573
|
}
|
|
@@ -470,7 +584,22 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
470
584
|
const [w, h] = this.config.interfaceSettings.aspectRatio.split(':').map(Number);
|
|
471
585
|
return w / h;
|
|
472
586
|
}
|
|
587
|
+
externalObjectSpanActive() {
|
|
588
|
+
// the registry holds frame-based providers only; a smart-camera object
|
|
589
|
+
// sensor (Reolink AI, ONVIF) lives in the feeding set, and its dwell is
|
|
590
|
+
// what keeps the span open between its report edges
|
|
591
|
+
if (this.plugins.get(SensorType.Object)?.requiresFrames)
|
|
592
|
+
return false;
|
|
593
|
+
for (const [sensorId, sensorType] of this.feedingSensors) {
|
|
594
|
+
if (sensorType === SensorType.Object && this.dwell.isActive(sensorId))
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
473
599
|
handleCascadeDeactivated() {
|
|
600
|
+
this.anchorBoxes = [];
|
|
601
|
+
this.farewellBoxes = [];
|
|
602
|
+
this.detectionWindow.reset();
|
|
474
603
|
// clear cascade-gated buffer slots, keep motion/audio
|
|
475
604
|
const cs = this.currentDetectionState;
|
|
476
605
|
cs.object = undefined;
|
|
@@ -546,7 +675,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
546
675
|
faceEmbeddingModel: cs.faceEmbeddingModel,
|
|
547
676
|
plates: cs.licensePlate?.detections ?? [],
|
|
548
677
|
plateVoting: this.plugins.get(SensorType.LicensePlate)?.requiresFrames === true,
|
|
549
|
-
plateMinConfidence: this.config.detectionSettings.licensePlate?.
|
|
678
|
+
plateMinConfidence: this.config.detectionSettings.licensePlate?.ocrConfidence,
|
|
550
679
|
plateMinLength: this.config.detectionSettings.licensePlate?.minLength,
|
|
551
680
|
faceMinConfidence: this.config.detectionSettings.face?.confidence,
|
|
552
681
|
classifiers: cs.classifiers ? Object.values(cs.classifiers).flatMap((c) => c.detections) : [],
|
|
@@ -596,12 +725,12 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
596
725
|
let boxlessLabel;
|
|
597
726
|
switch (sensorType) {
|
|
598
727
|
case SensorType.Face:
|
|
599
|
-
boxlessLabel = '
|
|
600
|
-
filtered = this.pipeline.runZoneFilterWithLabel(positioned, '
|
|
728
|
+
boxlessLabel = 'face';
|
|
729
|
+
filtered = this.pipeline.runZoneFilterWithLabel(positioned, 'face');
|
|
601
730
|
break;
|
|
602
731
|
case SensorType.LicensePlate:
|
|
603
|
-
boxlessLabel = '
|
|
604
|
-
filtered = this.pipeline.runZoneFilterWithLabel(positioned, '
|
|
732
|
+
boxlessLabel = 'license_plate';
|
|
733
|
+
filtered = this.pipeline.runZoneFilterWithLabel(positioned, 'license_plate');
|
|
605
734
|
break;
|
|
606
735
|
case SensorType.Object:
|
|
607
736
|
filtered = this.pipeline.runZoneFilter(positioned);
|
|
@@ -755,6 +884,18 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
755
884
|
break;
|
|
756
885
|
}
|
|
757
886
|
}
|
|
887
|
+
applyModelSpec(sensorId, sensorType, modelSpec) {
|
|
888
|
+
const current = this.plugins.get(sensorType);
|
|
889
|
+
if (current?.sensorId !== sensorId)
|
|
890
|
+
return;
|
|
891
|
+
// another model means other numbers, keeping the old average would mislead;
|
|
892
|
+
// load time is not part of the identity, a reload of the same model is not a change
|
|
893
|
+
if (sensorType === SensorType.Object || sensorType === SensorType.Motion) {
|
|
894
|
+
if (modelIdentity(current.modelSpec) !== modelIdentity(modelSpec))
|
|
895
|
+
this.perf.reset();
|
|
896
|
+
}
|
|
897
|
+
this.plugins.updateModelSpec(sensorId, modelSpec);
|
|
898
|
+
}
|
|
758
899
|
async registerDetectionPluginInternal(sensor) {
|
|
759
900
|
if (this.videoStopPromise)
|
|
760
901
|
await this.videoStopPromise;
|
|
@@ -762,7 +903,9 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
762
903
|
const wasVideoNeeded = this.plugins.shouldVideoBeActive();
|
|
763
904
|
const wasAudioNeeded = this.plugins.shouldAudioBeActive();
|
|
764
905
|
const namespaces = NamespaceManager.sensorProviderNamespaces(sensor.pluginId, sensor.sensorId);
|
|
765
|
-
const sensorProxy = this.proxy.createProxy(namespaces.sensorRpc
|
|
906
|
+
const sensorProxy = this.proxy.createProxy(namespaces.sensorRpc, {
|
|
907
|
+
onTiming: (_method, timing) => this.perf.trackTiming(sensor.sensorType, timing.handlerMs, timing.transportMs),
|
|
908
|
+
});
|
|
766
909
|
const registered = this.plugins.register({
|
|
767
910
|
pluginId: sensor.pluginId,
|
|
768
911
|
sensorId: sensor.sensorId,
|
|
@@ -773,7 +916,8 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
773
916
|
});
|
|
774
917
|
if (!registered) {
|
|
775
918
|
// re-push from the registry: the sensor is already live, only the spec may have changed
|
|
776
|
-
|
|
919
|
+
if (sensor.modelSpec)
|
|
920
|
+
this.applyModelSpec(sensor.sensorId, sensor.sensorType, sensor.modelSpec);
|
|
777
921
|
return;
|
|
778
922
|
}
|
|
779
923
|
this.logger.trace(`Plugin registered: ${sensor.pluginId} for ${sensor.sensorType}`);
|
|
@@ -854,7 +998,6 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
854
998
|
return;
|
|
855
999
|
this.logger.debug('Starting video detection loop');
|
|
856
1000
|
this.loopRunning = true;
|
|
857
|
-
this.videoBackoff.reset();
|
|
858
1001
|
this.loopPromise = this.runDetectionLoop();
|
|
859
1002
|
}
|
|
860
1003
|
async stopVideoLoop() {
|
|
@@ -869,7 +1012,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
869
1012
|
this.mainStreamActive = false;
|
|
870
1013
|
this.idleSince = 0;
|
|
871
1014
|
const doStop = async () => {
|
|
872
|
-
await this.frameSource.
|
|
1015
|
+
await this.frameSource.detach();
|
|
873
1016
|
await this.loopPromise;
|
|
874
1017
|
this.loopPromise = undefined;
|
|
875
1018
|
};
|
|
@@ -882,95 +1025,111 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
882
1025
|
}
|
|
883
1026
|
}
|
|
884
1027
|
startAdHocVideoLoopIfNeeded() {
|
|
885
|
-
|
|
1028
|
+
// localizerWanted covers what the registry cannot see: an object assist or
|
|
1029
|
+
// an external object sensor needs the loop for the localizer's anchors
|
|
1030
|
+
if (!this.loopRunning && (this.plugins.needsAdHocLoop() || this.localizerWanted)) {
|
|
886
1031
|
this.adHocVideoLoop = true;
|
|
887
1032
|
this.logger.debug('Starting ad-hoc video loop for sensor cascade trigger');
|
|
888
1033
|
this.startVideoLoop();
|
|
889
1034
|
}
|
|
890
1035
|
}
|
|
891
1036
|
stopAdHocVideoLoopIfIdle() {
|
|
892
|
-
if (this.adHocVideoLoop
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1037
|
+
if (!this.adHocVideoLoop || this.plugins.shouldVideoBeActive())
|
|
1038
|
+
return;
|
|
1039
|
+
// the localizer and assist still need frames while the event runs;
|
|
1040
|
+
// stopping on the cascade edge alone churned the main-stream decoder
|
|
1041
|
+
// open/closed mid-event, which starves the hw device (vaapi surface sync
|
|
1042
|
+
// failures) right when the next report arrives
|
|
1043
|
+
if (this.cascade.isActive || this.dwell.hasActive() || this.eventManager.hasActiveSegment())
|
|
1044
|
+
return;
|
|
1045
|
+
this.adHocVideoLoop = false;
|
|
1046
|
+
this.logger.debug('Stopping ad-hoc video loop — idle');
|
|
1047
|
+
this.stopVideoLoop();
|
|
897
1048
|
}
|
|
898
1049
|
async runDetectionLoop() {
|
|
1050
|
+
// the source reconnects on its own, nextFrame simply waits it out
|
|
1051
|
+
const startedAt = Date.now();
|
|
1052
|
+
await this.frameSource.start();
|
|
1053
|
+
let lastFrameId = -1;
|
|
1054
|
+
let seenGeneration = -1;
|
|
1055
|
+
let firstFrame = true;
|
|
899
1056
|
while (this.loopRunning) {
|
|
1057
|
+
const tickStart = Date.now();
|
|
1058
|
+
const snap = await this.frameSource.nextFrame(lastFrameId);
|
|
1059
|
+
if (!snap)
|
|
1060
|
+
break; // source stopped
|
|
1061
|
+
if (firstFrame) {
|
|
1062
|
+
firstFrame = false;
|
|
1063
|
+
this.logger.debug(`Analysing after ${Date.now() - startedAt}ms`);
|
|
1064
|
+
}
|
|
1065
|
+
lastFrameId = snap.id;
|
|
1066
|
+
if (this.benchmarkRunning) {
|
|
1067
|
+
// the detector is saturated on purpose, feeding it real frames would
|
|
1068
|
+
// both distort the measurement and queue up behind it
|
|
1069
|
+
snap.frame[Symbol.dispose]?.();
|
|
1070
|
+
await sleep(IDLE_TICK_MS);
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
900
1073
|
try {
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
// debugging
|
|
920
|
-
detectionRecord.setFrame(analysis.isMainStream ? 'main' : 'low', analysis.frame.width, analysis.frame.height);
|
|
1074
|
+
if (this.frameSource.generation !== seenGeneration) {
|
|
1075
|
+
seenGeneration = this.frameSource.generation;
|
|
1076
|
+
// a fresh connection brings its own hardware context and resolution
|
|
1077
|
+
this.frameScaler.updateHardwareContext(this.frameSource.hardwareContext);
|
|
1078
|
+
this.frameScaler.clearCache();
|
|
1079
|
+
this.thumbnailer.sync(this.mainStreamAnalysisWanted);
|
|
1080
|
+
this.localizer.reset();
|
|
1081
|
+
this.detectionWindow.reset();
|
|
1082
|
+
}
|
|
1083
|
+
// motion always reads the low stream: switching its input resolution
|
|
1084
|
+
// would reset the background model on every transition
|
|
1085
|
+
const analysis = await this.acquireAnalysisFrame(snap.frame);
|
|
1086
|
+
// debugging
|
|
1087
|
+
detectionRecord.setFrame(analysis.isMainStream ? 'main' : 'low', analysis.frame.width, analysis.frame.height);
|
|
1088
|
+
try {
|
|
1089
|
+
await this.processRawFrame(analysis, snap.frame);
|
|
1090
|
+
}
|
|
1091
|
+
finally {
|
|
921
1092
|
try {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
try {
|
|
926
|
-
if (this.hqUpgrade) {
|
|
927
|
-
this.hqUpgrade.frame[Symbol.dispose]?.();
|
|
928
|
-
this.hqUpgrade = undefined;
|
|
929
|
-
}
|
|
930
|
-
if (analysis.frame !== snap.frame)
|
|
931
|
-
analysis.frame[Symbol.dispose]?.();
|
|
932
|
-
snap.frame[Symbol.dispose]?.();
|
|
933
|
-
}
|
|
934
|
-
catch {
|
|
935
|
-
// best-effort
|
|
1093
|
+
if (this.hqUpgrade) {
|
|
1094
|
+
this.hqUpgrade.frame[Symbol.dispose]?.();
|
|
1095
|
+
this.hqUpgrade = undefined;
|
|
936
1096
|
}
|
|
1097
|
+
if (analysis.frame !== snap.frame)
|
|
1098
|
+
analysis.frame[Symbol.dispose]?.();
|
|
1099
|
+
}
|
|
1100
|
+
catch {
|
|
1101
|
+
// ignore
|
|
937
1102
|
}
|
|
938
|
-
this.updateStreamState();
|
|
939
|
-
// activity, not stream choice: cameras without main-stream analysis still count as active
|
|
940
|
-
if (this.worldSpans.size > 0 || this.eventManager.hasActiveSegment())
|
|
941
|
-
this.perf.activeTicks++;
|
|
942
|
-
else
|
|
943
|
-
this.perf.idleTicks++;
|
|
944
|
-
this.perf.report(this.logger);
|
|
945
|
-
const remaining = (this.mainStreamActive ? ACTIVE_TICK_MS : IDLE_TICK_MS) - (Date.now() - tickStart);
|
|
946
|
-
if (remaining > 0)
|
|
947
|
-
await sleep(remaining);
|
|
948
|
-
this.perf.loopMs += Date.now() - tickStart;
|
|
949
|
-
}
|
|
950
|
-
const streamError = this.frameSource.lastError;
|
|
951
|
-
await this.frameSource.stop();
|
|
952
|
-
this.frameScaler.clearCache();
|
|
953
|
-
if (this.loopRunning && streamError) {
|
|
954
|
-
const delay = this.videoBackoff.nextDelayMs();
|
|
955
|
-
this.logger.warn(`Stream ended with read error, reconnecting in ${delay / 1000}s: ${streamError.message}`);
|
|
956
|
-
await sleep(delay);
|
|
957
|
-
}
|
|
958
|
-
else if (this.loopRunning && frameCount === 0) {
|
|
959
|
-
this.logger.debug('Stream ended without frames, waiting before reconnect...');
|
|
960
|
-
await sleep(this.videoBackoff.idleDelayMs);
|
|
961
1103
|
}
|
|
1104
|
+
this.updateStreamState();
|
|
962
1105
|
}
|
|
963
1106
|
catch (error) {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
await this.frameSource.stop();
|
|
967
|
-
this.frameScaler.clearCache();
|
|
968
|
-
const delay = this.videoBackoff.nextDelayMs();
|
|
969
|
-
this.logger.warn(`Stream error, reconnecting in ${delay / 1000}s: ${error.message}`);
|
|
970
|
-
await sleep(delay);
|
|
1107
|
+
// the stream keeps running, a broken tick must not end the loop
|
|
1108
|
+
this.logger.error('Detection tick failed:', error);
|
|
971
1109
|
}
|
|
972
|
-
|
|
973
|
-
|
|
1110
|
+
finally {
|
|
1111
|
+
try {
|
|
1112
|
+
snap.frame[Symbol.dispose]?.();
|
|
1113
|
+
}
|
|
1114
|
+
catch {
|
|
1115
|
+
// ignore
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
const decode = this.frameSource.takeDecodeStats();
|
|
1119
|
+
this.perf.decodeMs += decode.ms;
|
|
1120
|
+
this.perf.decodedFrames += decode.frames;
|
|
1121
|
+
// activity, not stream choice: cameras without main-stream analysis still count as active
|
|
1122
|
+
if (this.worldSpans.size > 0 || this.eventManager.hasActiveSegment())
|
|
1123
|
+
this.perf.activeTicks++;
|
|
1124
|
+
else
|
|
1125
|
+
this.perf.idleTicks++;
|
|
1126
|
+
this.perf.report(this.logger);
|
|
1127
|
+
const remaining = (this.mainStreamActive ? ACTIVE_TICK_MS : IDLE_TICK_MS) - (Date.now() - tickStart);
|
|
1128
|
+
if (remaining > 0)
|
|
1129
|
+
await sleep(remaining);
|
|
1130
|
+
this.perf.loopMs += Date.now() - tickStart;
|
|
1131
|
+
}
|
|
1132
|
+
await this.frameSource.detach();
|
|
974
1133
|
this.frameScaler.clearCache();
|
|
975
1134
|
this.logger.debug('Detection loop ended');
|
|
976
1135
|
}
|
|
@@ -989,7 +1148,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
989
1148
|
const main = await this.thumbnailer.acquireHqFrame(0);
|
|
990
1149
|
if (main) {
|
|
991
1150
|
this.perf.mainFrames++;
|
|
992
|
-
this.perf.
|
|
1151
|
+
this.perf.mainDecodeMs += Date.now() - t0;
|
|
993
1152
|
return { frame: main.frame, scaler: main.scaler, isMainStream: true };
|
|
994
1153
|
}
|
|
995
1154
|
}
|
|
@@ -1007,7 +1166,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1007
1166
|
const main = await this.thumbnailer.acquireHqFrame(0);
|
|
1008
1167
|
if (main) {
|
|
1009
1168
|
this.perf.mainFrames++;
|
|
1010
|
-
this.perf.
|
|
1169
|
+
this.perf.mainDecodeMs += Date.now() - t0;
|
|
1011
1170
|
return { frame: main.frame, scaler: main.scaler, isMainStream: true };
|
|
1012
1171
|
}
|
|
1013
1172
|
}
|
|
@@ -1017,7 +1176,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1017
1176
|
return undefined;
|
|
1018
1177
|
}
|
|
1019
1178
|
updateStreamState() {
|
|
1020
|
-
if (this.worldSpans.size > 0 || this.eventManager.hasActiveSegment()) {
|
|
1179
|
+
if (this.worldSpans.size > 0 || this.eventManager.hasActiveSegment() || this.windowWantsMainStream) {
|
|
1021
1180
|
this.idleSince = 0;
|
|
1022
1181
|
if (!this.mainStreamActive && this.mainStreamAvailable) {
|
|
1023
1182
|
this.mainStreamActive = true;
|
|
@@ -1052,6 +1211,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1052
1211
|
const t0 = Date.now();
|
|
1053
1212
|
let motionDetected = false;
|
|
1054
1213
|
let objectDetections = [];
|
|
1214
|
+
let staticDetections = [];
|
|
1055
1215
|
const results = { timestamp: t0 };
|
|
1056
1216
|
// while the PTZ repositions: motion and object keep running so the
|
|
1057
1217
|
// background model and track ids survive the pan, but nothing below
|
|
@@ -1071,11 +1231,13 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1071
1231
|
return;
|
|
1072
1232
|
if (motionFrame) {
|
|
1073
1233
|
this.lastMotionAt = t0;
|
|
1234
|
+
if (this.localizerWanted)
|
|
1235
|
+
this.feedLocalizer(motionFrame, t0, ptzSuppressed);
|
|
1074
1236
|
try {
|
|
1075
1237
|
const motionInferStart = Date.now();
|
|
1076
1238
|
const result = await motionPlugin.proxy.detectMotion(motionFrame);
|
|
1077
|
-
this.perf.
|
|
1078
|
-
this.perf.
|
|
1239
|
+
this.perf.motionMs += Date.now() - motionInferStart;
|
|
1240
|
+
this.perf.motionCount++;
|
|
1079
1241
|
if (!this.loopRunning)
|
|
1080
1242
|
return;
|
|
1081
1243
|
if (ptzSuppressed) {
|
|
@@ -1104,6 +1266,17 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1104
1266
|
// no frame-based motion plugin, the cascade is the only motion-equivalent signal
|
|
1105
1267
|
motionDetected = true;
|
|
1106
1268
|
results.cascadeTriggered = true;
|
|
1269
|
+
// the localizer scales its own gray here, nobody else needed one
|
|
1270
|
+
const localizeDue = t0 - this.lastLocalizeAt >= MOTION_INTERVAL_MS - TICK_SLACK_MS;
|
|
1271
|
+
if (localizeDue && this.localizerWanted) {
|
|
1272
|
+
const scaleStart = Date.now();
|
|
1273
|
+
const gray = await this.scaleForMotionInput(motionRawFrame);
|
|
1274
|
+
this.perf.scaleMs += Date.now() - scaleStart;
|
|
1275
|
+
if (!this.loopRunning)
|
|
1276
|
+
return;
|
|
1277
|
+
if (gray)
|
|
1278
|
+
this.feedLocalizer(gray, t0, ptzSuppressed);
|
|
1279
|
+
}
|
|
1107
1280
|
}
|
|
1108
1281
|
// detected is re-derived after zone filtering, the plugin's own flag is ignored
|
|
1109
1282
|
if (results.motion && motionPlugin) {
|
|
@@ -1117,25 +1290,18 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1117
1290
|
const objectPlugin = this.plugins.get(SensorType.Object);
|
|
1118
1291
|
const shouldDetectObjects = objectPlugin?.requiresFrames === true && (ptzSuppressed || !this.cascadeEnabled || this.cascade.isActive);
|
|
1119
1292
|
if (shouldDetectObjects) {
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
if (objectFrame) {
|
|
1126
|
-
try {
|
|
1127
|
-
const inferStart = Date.now();
|
|
1128
|
-
const result = await PromiseTimeout(objectPlugin.proxy.detectObjects(objectFrame.model), DETECT_TIMEOUT_MS, undefined, `Object detection timed out after ${DETECT_TIMEOUT_MS}ms`);
|
|
1129
|
-
this.perf.inferMs += Date.now() - inferStart;
|
|
1130
|
-
this.perf.inferCount++;
|
|
1131
|
-
if (!this.loopRunning)
|
|
1132
|
-
return;
|
|
1293
|
+
try {
|
|
1294
|
+
const detected = await this.runObjectDetection(objectPlugin, analysis, results.motion?.detections ?? []);
|
|
1295
|
+
if (!this.loopRunning)
|
|
1296
|
+
return;
|
|
1297
|
+
if (detected) {
|
|
1133
1298
|
this.trackDetectionCadence();
|
|
1134
1299
|
// run the pipeline even on empty frames so Norfair advances its
|
|
1135
1300
|
// Kalman state; the pose delta keeps predictions stable across pans
|
|
1136
1301
|
const poseDelta = this.ptzAutotracker.consumePoseDelta();
|
|
1137
|
-
const
|
|
1302
|
+
const postStart = Date.now();
|
|
1138
1303
|
const pipelineResult = this.pipeline.process(detected, poseDelta);
|
|
1304
|
+
this.perf.postMs += Date.now() - postStart;
|
|
1139
1305
|
// the tick that opens a span analysed the low stream (the switch lands
|
|
1140
1306
|
// next tick), its picture work is worth a one-off HQ decode
|
|
1141
1307
|
if (this.updateWorldSpans(pipelineResult) && !analysis.isMainStream) {
|
|
@@ -1167,25 +1333,35 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1167
1333
|
return cx > -0.1 && cx < 1.1 && cy > -0.1 && cy < 1.1;
|
|
1168
1334
|
});
|
|
1169
1335
|
objectDetections = visibleTracks.filter((t) => !t.trackLost);
|
|
1170
|
-
results.object = {
|
|
1336
|
+
results.object = { detected: objectDetections.length > 0, detections: visibleTracks };
|
|
1337
|
+
staticDetections = pipelineResult.staticTracks;
|
|
1171
1338
|
if (pipelineResult.crossings.length > 0)
|
|
1172
1339
|
results.lineCrossings = pipelineResult.crossings;
|
|
1173
1340
|
// incl. extrapolated tracks, otherwise a single missed detector
|
|
1174
1341
|
// frame flips the autotracker into LOST/REACQUIRE churn
|
|
1175
1342
|
this.ptzAutotracker.handleObjectDetections(pipelineResult.tracked);
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1343
|
+
// live tracks anchor the next tick's window; a track that just died
|
|
1344
|
+
// gets one farewell look at its last position
|
|
1345
|
+
this.trackAnchorBoxes = objectDetections.map((t) => t.box);
|
|
1346
|
+
for (const id of pipelineResult.removed) {
|
|
1347
|
+
const last = this.lastTrackedById.get(id);
|
|
1348
|
+
if (last)
|
|
1349
|
+
this.farewellBoxes.push(last);
|
|
1350
|
+
}
|
|
1351
|
+
this.lastTrackedById = new Map(pipelineResult.tracked.filter((t) => t.trackId !== undefined).map((t) => [t.trackId, t.box]));
|
|
1181
1352
|
}
|
|
1182
1353
|
}
|
|
1354
|
+
catch (error) {
|
|
1355
|
+
if (!this.loopRunning || isNoRespondersError(error))
|
|
1356
|
+
return;
|
|
1357
|
+
this.logger.error('Object detection error:', error);
|
|
1358
|
+
}
|
|
1183
1359
|
}
|
|
1184
1360
|
// tracker and autotracker are fed, everything event-facing stays quiet
|
|
1185
1361
|
if (ptzSuppressed) {
|
|
1186
1362
|
// object boxes stay live in the UI through the move, bbox publish only
|
|
1187
1363
|
if (objectPlugin && results.object) {
|
|
1188
|
-
this.writeSensorProperties(objectPlugin.sensorId, { detections: results.object.detections });
|
|
1364
|
+
this.writeSensorProperties(objectPlugin.sensorId, { detections: results.object.detections, staticDetections });
|
|
1189
1365
|
}
|
|
1190
1366
|
// keep an already-running object dwell alive so a chase doesn't end its
|
|
1191
1367
|
// own event, but never activate it from suppressed frames
|
|
@@ -1204,6 +1380,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1204
1380
|
this.ingestDetectionResult(SensorType.Object, objectPlugin.sensorId, {
|
|
1205
1381
|
detected: objectDetections.length > 0,
|
|
1206
1382
|
detections: results.object.detections,
|
|
1383
|
+
staticDetections,
|
|
1207
1384
|
});
|
|
1208
1385
|
}
|
|
1209
1386
|
if (!this.loopRunning)
|
|
@@ -1259,6 +1436,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1259
1436
|
if (objectDetections.length === 0)
|
|
1260
1437
|
return;
|
|
1261
1438
|
this.perf.objects += objectDetections.length;
|
|
1439
|
+
this.perf.framesWithObjects++;
|
|
1262
1440
|
const secondaryStart = Date.now();
|
|
1263
1441
|
await this.secondaries.detect(analysis.frame, analysis.scaler, objectDetections, results);
|
|
1264
1442
|
this.perf.secondaryMs += Date.now() - secondaryStart;
|
|
@@ -1280,15 +1458,18 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1280
1458
|
}
|
|
1281
1459
|
const facePlugin = this.plugins.get(SensorType.Face);
|
|
1282
1460
|
if (facePlugin) {
|
|
1461
|
+
// the crop path reaches ingestDetectionResult directly, so the zone
|
|
1462
|
+
// filter that guards the plugin path has to be applied here too
|
|
1463
|
+
const detections = this.pipeline.runZoneFilterWithLabel(results.face.detections, 'face');
|
|
1283
1464
|
// no mid-segment clear: a turned-away head yields a no-face frame and
|
|
1284
1465
|
// would flap the list, departure is reflected when the segment ends
|
|
1285
|
-
for (const detection of
|
|
1466
|
+
for (const detection of detections) {
|
|
1286
1467
|
if (detection.identity)
|
|
1287
1468
|
this.faceIdentities.add(detection.identity);
|
|
1288
1469
|
}
|
|
1289
1470
|
this.ingestDetectionResult(SensorType.Face, facePlugin.sensorId, {
|
|
1290
|
-
detected: results.face.detected,
|
|
1291
|
-
detections
|
|
1471
|
+
detected: detections.length > 0 && results.face.detected,
|
|
1472
|
+
detections,
|
|
1292
1473
|
identities: [...this.faceIdentities].sort(),
|
|
1293
1474
|
});
|
|
1294
1475
|
}
|
|
@@ -1296,14 +1477,15 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1296
1477
|
if (results.licensePlate) {
|
|
1297
1478
|
const lpdPlugin = this.plugins.get(SensorType.LicensePlate);
|
|
1298
1479
|
if (lpdPlugin) {
|
|
1299
|
-
|
|
1480
|
+
const detections = this.pipeline.runZoneFilterWithLabel(results.licensePlate.detections, 'license_plate');
|
|
1481
|
+
for (const detection of detections) {
|
|
1300
1482
|
const plate = detection.plateText ? normalizePlateText(detection.plateText) || detection.plateText : '';
|
|
1301
1483
|
if (plate)
|
|
1302
1484
|
this.platesSeen.add(plate);
|
|
1303
1485
|
}
|
|
1304
1486
|
this.ingestDetectionResult(SensorType.LicensePlate, lpdPlugin.sensorId, {
|
|
1305
|
-
detected: results.licensePlate.detected,
|
|
1306
|
-
detections
|
|
1487
|
+
detected: detections.length > 0 && results.licensePlate.detected,
|
|
1488
|
+
detections,
|
|
1307
1489
|
plates: [...this.platesSeen].sort(),
|
|
1308
1490
|
});
|
|
1309
1491
|
}
|
|
@@ -1397,6 +1579,41 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1397
1579
|
return;
|
|
1398
1580
|
await this.captureMoment(subject, bestScore, trigger, result.tracked, analysis, at);
|
|
1399
1581
|
}
|
|
1582
|
+
anchorMomentDetections(reported) {
|
|
1583
|
+
const anchors = [...this.anchorBoxes, ...this.farewellBoxes];
|
|
1584
|
+
if (anchors.length === 0 || reported.length === 0)
|
|
1585
|
+
return [];
|
|
1586
|
+
const subject = reported.find((d) => d.label !== 'motion') ?? reported[0];
|
|
1587
|
+
const clusters = clusterBoxes(anchors);
|
|
1588
|
+
const largest = Math.max(...clusters.map((b) => b.width * b.height));
|
|
1589
|
+
// specks would drag the base union across the frame
|
|
1590
|
+
return clusters.filter((b) => b.width * b.height >= largest * 0.2).map((b) => ({ label: subject.label, confidence: subject.confidence, box: b }));
|
|
1591
|
+
}
|
|
1592
|
+
async captureExternalMoment(detections, analysis, at) {
|
|
1593
|
+
let subject;
|
|
1594
|
+
let bestScore = 0;
|
|
1595
|
+
for (const detection of detections) {
|
|
1596
|
+
const box = detection.box;
|
|
1597
|
+
if (!box)
|
|
1598
|
+
continue;
|
|
1599
|
+
const score = detection.confidence * Math.sqrt(box.width * box.height);
|
|
1600
|
+
if (score > bestScore) {
|
|
1601
|
+
bestScore = score;
|
|
1602
|
+
subject = detection;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (!subject)
|
|
1606
|
+
return;
|
|
1607
|
+
if (!this.eventManager.wantsMoment(MOMENT_RANK_OBJECT, bestScore, 'low', at))
|
|
1608
|
+
return;
|
|
1609
|
+
const target = { subject: subject.box, base: unionBox(detections.map((d) => d.box).filter(Boolean)) };
|
|
1610
|
+
const rendered = await this.renderMoment(target, analysis);
|
|
1611
|
+
if (!rendered)
|
|
1612
|
+
return;
|
|
1613
|
+
this.eventManager.offerMoment({ strip: rendered.strip, card: rendered.card, capturedAt: at, score: bestScore, rank: MOMENT_RANK_OBJECT, stream: 'low' });
|
|
1614
|
+
// debugging
|
|
1615
|
+
await this.recordMoment(target, rendered, analysis, `${subject.label}-external`, bestScore, 'external', at);
|
|
1616
|
+
}
|
|
1400
1617
|
async captureMoment(subject, score, trigger, tracked, analysis, at) {
|
|
1401
1618
|
const box = { x: subject.x, y: subject.y, width: subject.width, height: subject.height };
|
|
1402
1619
|
const moving = tracked.filter((d) => (d.trackSpeed ?? 0) >= MOMENT_MOVING_SPEED).map((d) => d.box);
|
|
@@ -1431,7 +1648,6 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1431
1648
|
// legible one frames the shot, the rest stay their own attribute tiles
|
|
1432
1649
|
let best;
|
|
1433
1650
|
let bestScore = -1;
|
|
1434
|
-
let bestCard;
|
|
1435
1651
|
for (const candidate of candidates) {
|
|
1436
1652
|
const cut = results.thumbnails?.find((t) => t.label === candidate.label);
|
|
1437
1653
|
// a subject cut off by the frame edge makes a poor card
|
|
@@ -1446,20 +1662,21 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1446
1662
|
if (score > bestScore) {
|
|
1447
1663
|
bestScore = score;
|
|
1448
1664
|
best = candidate;
|
|
1449
|
-
bestCard = cut?.jpeg;
|
|
1450
1665
|
}
|
|
1451
1666
|
}
|
|
1452
1667
|
// ask before cutting: the same face rides along on every tick of a segment
|
|
1453
1668
|
// and re-encoding for a picture that loses anyway is pure cost
|
|
1454
1669
|
if (!best || !this.eventManager.wantsMoment(MOMENT_RANK_ATTRIBUTE, bestScore, analysis.isMainStream ? 'main' : 'low', at))
|
|
1455
1670
|
return;
|
|
1671
|
+
// the card frames the whole person with the face as its center, not the
|
|
1672
|
+
// bare face box: the zoomed face already exists as an attribute tile, and
|
|
1673
|
+
// a misdetected "face" zoomed to card size ruins the event picture
|
|
1456
1674
|
const target = {
|
|
1457
1675
|
subject: best.box,
|
|
1676
|
+
base: best.parentBox ?? best.box,
|
|
1458
1677
|
hint: best.parentBox ? directionBetween(best.box, best.parentBox) : undefined,
|
|
1459
1678
|
};
|
|
1460
|
-
|
|
1461
|
-
// card is already encoded; only the 16:9 companion is still missing
|
|
1462
|
-
const rendered = await this.renderMoment(target, analysis, bestCard);
|
|
1679
|
+
const rendered = await this.renderMoment(target, analysis);
|
|
1463
1680
|
if (!rendered)
|
|
1464
1681
|
return;
|
|
1465
1682
|
const stream = analysis.isMainStream ? 'main' : 'low';
|
|
@@ -1523,10 +1740,42 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1523
1740
|
const motionPlugin = this.plugins.get(SensorType.Motion);
|
|
1524
1741
|
if (!motionPlugin?.requiresFrames)
|
|
1525
1742
|
return undefined;
|
|
1743
|
+
return this.scaleForMotionInput(rawFrame);
|
|
1744
|
+
}
|
|
1745
|
+
async scaleForMotionInput(rawFrame) {
|
|
1526
1746
|
const maxWidth = MOTION_WIDTH_MAP[this.motionResolution];
|
|
1527
1747
|
const scaled = await this.frameScaler.scaleProportional(rawFrame, maxWidth, 'gray');
|
|
1528
1748
|
return scaled ? this.frameScaler.toVideoFrameData(scaled) : undefined;
|
|
1529
1749
|
}
|
|
1750
|
+
feedLocalizer(gray, t0, ptzSuppressed) {
|
|
1751
|
+
this.lastLocalizeAt = t0;
|
|
1752
|
+
const data = Buffer.isBuffer(gray.data) ? gray.data : Buffer.from(gray.data);
|
|
1753
|
+
const boxes = this.localizer.localize({ data, width: gray.width, height: gray.height });
|
|
1754
|
+
// during ego-motion the diff is garbage, feeding keeps the reference alive
|
|
1755
|
+
this.anchorBoxes = ptzSuppressed ? [] : boxes;
|
|
1756
|
+
}
|
|
1757
|
+
async seedLocalizerReference() {
|
|
1758
|
+
if (!this.localizerWanted || !this.frameSource.decodeOldestKeyframe)
|
|
1759
|
+
return;
|
|
1760
|
+
try {
|
|
1761
|
+
const frame = await this.frameSource.decodeOldestKeyframe();
|
|
1762
|
+
if (!frame)
|
|
1763
|
+
return;
|
|
1764
|
+
try {
|
|
1765
|
+
const gray = await this.scaleForMotionInput(frame);
|
|
1766
|
+
if (gray) {
|
|
1767
|
+
const data = Buffer.isBuffer(gray.data) ? gray.data : Buffer.from(gray.data);
|
|
1768
|
+
this.localizer.seedReference({ data, width: gray.width, height: gray.height });
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
finally {
|
|
1772
|
+
frame[Symbol.dispose]?.();
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
catch (error) {
|
|
1776
|
+
this.logger.debug('Localizer reference seed failed:', error);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1530
1779
|
trackDetectionCadence() {
|
|
1531
1780
|
const now = Date.now();
|
|
1532
1781
|
const previous = this.lastObjectCallAt;
|
|
@@ -1560,6 +1809,43 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1560
1809
|
return undefined;
|
|
1561
1810
|
return this.scaleFrameForPlugin(rawFrame, objectPlugin, scaler);
|
|
1562
1811
|
}
|
|
1812
|
+
async runObjectDetection(objectPlugin, analysis, pluginMotionBoxes) {
|
|
1813
|
+
const inputSpec = objectPlugin.modelSpec?.input;
|
|
1814
|
+
const motionAnchors = [...this.anchorBoxes, ...pluginMotionBoxes.map((d) => d.box).filter((box) => box !== undefined), ...this.farewellBoxes];
|
|
1815
|
+
this.farewellBoxes = [];
|
|
1816
|
+
const windows = isVideoInputSpec(inputSpec)
|
|
1817
|
+
? this.detectionWindow.plan(motionAnchors, this.trackAnchorBoxes, analysis.frame.width, analysis.frame.height, inputSpec.width)
|
|
1818
|
+
: null;
|
|
1819
|
+
if (windows && windows.length > 0) {
|
|
1820
|
+
this.perf.zoomTicks++;
|
|
1821
|
+
this.perf.zoomWindows += windows.length;
|
|
1822
|
+
detectionRecord.zoom({ kind: 'object', anchors: motionAnchors, tracks: this.trackAnchorBoxes, windows });
|
|
1823
|
+
const inferStart = Date.now();
|
|
1824
|
+
const perWindow = await Promise.all(windows.map((window) => this.detectInWindow(objectPlugin, analysis.frame, analysis.scaler, window, inputSpec)));
|
|
1825
|
+
this.perf.objectMs += Date.now() - inferStart;
|
|
1826
|
+
this.perf.objectCount++;
|
|
1827
|
+
return mergeWindowDetections(perWindow.flat());
|
|
1828
|
+
}
|
|
1829
|
+
const scaleStart = Date.now();
|
|
1830
|
+
const objectFrame = await this.scaleForObject(analysis.frame, analysis.scaler);
|
|
1831
|
+
this.perf.scaleMs += Date.now() - scaleStart;
|
|
1832
|
+
if (!objectFrame || !this.loopRunning)
|
|
1833
|
+
return undefined;
|
|
1834
|
+
const inferStart = Date.now();
|
|
1835
|
+
const result = await PromiseTimeout(objectPlugin.proxy.detectObjects(objectFrame.model), DETECT_TIMEOUT_MS, undefined, `Object detection timed out after ${DETECT_TIMEOUT_MS}ms`);
|
|
1836
|
+
this.perf.objectMs += Date.now() - inferStart;
|
|
1837
|
+
this.perf.objectCount++;
|
|
1838
|
+
return FrameScaler.undoLetterbox(ensureDetectionBoxes(result.detections), objectFrame.geometry);
|
|
1839
|
+
}
|
|
1840
|
+
async detectInWindow(plugin, frame, scaler, window, spec) {
|
|
1841
|
+
const scaleStart = Date.now();
|
|
1842
|
+
const cropped = await scaler.cropToSpec(frame, window, spec);
|
|
1843
|
+
this.perf.scaleMs += Date.now() - scaleStart;
|
|
1844
|
+
if (!cropped)
|
|
1845
|
+
return [];
|
|
1846
|
+
const result = await PromiseTimeout(plugin.proxy.detectObjects(scaler.toVideoFrameData(cropped.padded, 'model')), DETECT_TIMEOUT_MS, undefined, `Object detection timed out after ${DETECT_TIMEOUT_MS}ms`);
|
|
1847
|
+
return FrameScaler.undoLetterbox(ensureDetectionBoxes(result.detections), cropped.geometry);
|
|
1848
|
+
}
|
|
1563
1849
|
async scaleFrameForPlugin(rawFrame, plugin, scaler = this.frameScaler) {
|
|
1564
1850
|
const inputSpec = plugin.modelSpec?.input;
|
|
1565
1851
|
if (!isVideoInputSpec(inputSpec))
|
|
@@ -1576,27 +1862,34 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1576
1862
|
ingestAssistedObjects(sensorId, objects) {
|
|
1577
1863
|
if (!objects.assisted)
|
|
1578
1864
|
return;
|
|
1865
|
+
// the assist returns raw detector output; the frame path dedupes via NMS
|
|
1866
|
+
// before ingest, so this path must merge too or near-identical candidate
|
|
1867
|
+
// boxes inflate the object count
|
|
1868
|
+
const merged = this.pipeline.runMergeAndZoneFilter(objects.detections);
|
|
1579
1869
|
const refiltered = this.applyExternalDetectionFilters(SensorType.Object, {
|
|
1580
|
-
detected:
|
|
1581
|
-
detections:
|
|
1870
|
+
detected: merged.length > 0,
|
|
1871
|
+
detections: merged,
|
|
1872
|
+
});
|
|
1873
|
+
detectionRecord.tick({
|
|
1874
|
+
assistIngest: {
|
|
1875
|
+
raw: objects.detections.map((d) => ({ label: d.label, score: d.confidence, box: d.box })),
|
|
1876
|
+
merged: merged.length,
|
|
1877
|
+
kept: (refiltered.detections ?? []).length,
|
|
1878
|
+
},
|
|
1582
1879
|
});
|
|
1583
1880
|
this.ingestDetectionResult(SensorType.Object, sensorId, refiltered);
|
|
1584
1881
|
}
|
|
1585
|
-
async runObjectAssist(frame, reported) {
|
|
1882
|
+
async runObjectAssist(frame, reported, scaler = this.frameScaler) {
|
|
1586
1883
|
if (!this.plugins.hasEligibleObjectAssist()) {
|
|
1587
1884
|
return { detections: reported, assisted: false };
|
|
1588
1885
|
}
|
|
1589
1886
|
const assist = this.plugins.get(SensorType.ObjectAssist);
|
|
1590
|
-
const scaled = await this.scaleFrameForPlugin(frame, assist);
|
|
1591
|
-
if (!scaled)
|
|
1592
|
-
return { detections: reported, assisted: false };
|
|
1593
1887
|
try {
|
|
1594
1888
|
const inferStart = Date.now();
|
|
1595
|
-
const
|
|
1596
|
-
this.perf.
|
|
1597
|
-
this.perf.
|
|
1889
|
+
const boxed = await this.assistDetections(assist, frame, scaler, reported);
|
|
1890
|
+
this.perf.assistMs += Date.now() - inferStart;
|
|
1891
|
+
this.perf.assistCount++;
|
|
1598
1892
|
const reportedLabels = new Set(reported.map((d) => d.label.toLowerCase()));
|
|
1599
|
-
const boxed = FrameScaler.undoLetterbox(ensureDetectionBoxes(result?.detections ?? []), scaled.geometry);
|
|
1600
1893
|
const found = boxed.filter((d) => reportedLabels.size === 0 || reportedLabels.has(d.label.toLowerCase()));
|
|
1601
1894
|
if (found.length === 0)
|
|
1602
1895
|
return { detections: reported, assisted: false };
|
|
@@ -1610,6 +1903,31 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1610
1903
|
return { detections: reported, assisted: false };
|
|
1611
1904
|
}
|
|
1612
1905
|
}
|
|
1906
|
+
async assistDetections(assist, frame, scaler, reported) {
|
|
1907
|
+
const inputSpec = assist.modelSpec?.input;
|
|
1908
|
+
const reportedBoxes = reported.map((d) => d.box).filter((box) => box !== undefined);
|
|
1909
|
+
// a coordinate-less report still has the localizer's diff anchors: zoom
|
|
1910
|
+
// the assist into the moving regions instead of letterboxing the whole
|
|
1911
|
+
// frame, which shrinks the subject to nothing on a main-stream frame
|
|
1912
|
+
const anchors = reportedBoxes.length > 0 ? reportedBoxes : [...this.anchorBoxes, ...this.farewellBoxes];
|
|
1913
|
+
const pad = reportedBoxes.length > 0 ? TRACK_PAD : MOTION_PAD;
|
|
1914
|
+
if (isVideoInputSpec(inputSpec) && anchors.length > 0) {
|
|
1915
|
+
const windows = planWindowsOnce(anchors, pad, frame.width, frame.height, inputSpec.width);
|
|
1916
|
+
if (windows.length > 0) {
|
|
1917
|
+
detectionRecord.zoom({ kind: 'assist', anchors, windows });
|
|
1918
|
+
const perWindow = await Promise.all(windows.map((window) => this.detectInWindow(assist, frame, scaler, window, inputSpec)));
|
|
1919
|
+
return mergeWindowDetections(perWindow.flat());
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
detectionRecord.tick({
|
|
1923
|
+
assistFullFrame: { anchors: anchors.length, videoSpec: isVideoInputSpec(inputSpec), loop: this.loopRunning, cascade: this.cascade.isActive },
|
|
1924
|
+
});
|
|
1925
|
+
const scaled = await this.scaleFrameForPlugin(frame, assist, scaler);
|
|
1926
|
+
if (!scaled)
|
|
1927
|
+
return [];
|
|
1928
|
+
const result = await PromiseTimeout(assist.proxy.detectObjects(scaled.model), DETECT_TIMEOUT_MS, undefined, `Object assist timed out after ${DETECT_TIMEOUT_MS}ms`);
|
|
1929
|
+
return FrameScaler.undoLetterbox(ensureDetectionBoxes(result?.detections ?? []), scaled.geometry);
|
|
1930
|
+
}
|
|
1613
1931
|
};
|
|
1614
1932
|
__decorate([
|
|
1615
1933
|
RPCMethod,
|