@camstack/addon-post-analysis 1.1.22 → 1.1.24
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/{dist-DbgDsnRE.js → dist-B0VgBdBN.js} +573 -251
- package/dist/{dist-DiIUcFdN.mjs → dist-DRK78NMq.mjs} +573 -251
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/enrichment-engine/index.js +28 -16
- package/dist/enrichment-engine/index.mjs +27 -15
- package/dist/{node-CdzrYoKs.js → node-Cx0LM0Sq.js} +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BaXPYx2C.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BsRL9Bh-.mjs} +3 -3
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DtOrjE2U.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-CZ8URdDr.mjs → hostInit-sO1Drmtf.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +424 -166
- package/dist/pipeline-analytics/index.mjs +422 -164
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/dist/{resolve-frame-nQMd6Z8B.js → resolve-frame-DBXwF5fk.js} +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BEL-L4wR.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as object, S as number, c as pipelineAnalyticsCapability, d as zoneAnalyticsCapability, f as errMsg, h as EventCategory, i as cosineSimilarity, l as plateGalleryCapability, m as DeviceType, n as addonWidgetsSourceCapability, o as faceGalleryCapability, p as BaseAddon, r as audioMetricsCapability, t as EVENT_PAD_MS, u as videoclipsCapability, v as hydrateSchema, w as string, x as boolean } from "../dist-
|
|
1
|
+
import { C as object, S as number, c as pipelineAnalyticsCapability, d as zoneAnalyticsCapability, f as errMsg, h as EventCategory, i as cosineSimilarity, l as plateGalleryCapability, m as DeviceType, n as addonWidgetsSourceCapability, o as faceGalleryCapability, p as BaseAddon, r as audioMetricsCapability, t as EVENT_PAD_MS, u as videoclipsCapability, v as hydrateSchema, w as string, x as boolean } from "../dist-DRK78NMq.mjs";
|
|
2
2
|
import { n as extractCrop, t as resolveFrame } from "../resolve-frame-CT1T1tWy.mjs";
|
|
3
3
|
import { FrameRingReaderCache } from "@camstack/shm-ring";
|
|
4
4
|
import sharp from "sharp";
|
|
@@ -694,17 +694,24 @@ function normalizeToPixel(p, width, height) {
|
|
|
694
694
|
//#region src/pipeline-analytics/pipeline/tracker/sort-tracker.ts
|
|
695
695
|
var DEFAULT_TRACKER_CONFIG = {
|
|
696
696
|
iouThreshold: .3,
|
|
697
|
-
|
|
697
|
+
maxMissedMs: 4e3,
|
|
698
698
|
minHits: 3,
|
|
699
699
|
predictiveCoasting: true,
|
|
700
700
|
classGating: true,
|
|
701
701
|
occlusionEnabled: true,
|
|
702
702
|
occlusionContainment: .6,
|
|
703
|
-
|
|
704
|
-
maxTrackLifetimeMs: 3e5
|
|
703
|
+
occlusionMaxMissedMs: 8e3,
|
|
704
|
+
maxTrackLifetimeMs: 3e5,
|
|
705
|
+
rescueIouThreshold: .1,
|
|
706
|
+
rescueCentroidFactor: .75,
|
|
707
|
+
resurrectionWindowMs: 8e3,
|
|
708
|
+
stationarySpeedPx: 2
|
|
705
709
|
};
|
|
706
710
|
var MAX_PATH_LENGTH = 300;
|
|
707
711
|
var nextTrackId = 1;
|
|
712
|
+
function clamp(value, min, max) {
|
|
713
|
+
return Math.max(min, Math.min(max, value));
|
|
714
|
+
}
|
|
708
715
|
function iou$1(a, b) {
|
|
709
716
|
const ax1 = a.x, ay1 = a.y, ax2 = a.x + a.w, ay2 = a.y + a.h;
|
|
710
717
|
const bx1 = b.x, by1 = b.y, bx2 = b.x + b.w, by2 = b.y + b.h;
|
|
@@ -734,26 +741,51 @@ var SortTracker = class {
|
|
|
734
741
|
};
|
|
735
742
|
}
|
|
736
743
|
/** Where a track is expected this frame — extrapolated by velocity while
|
|
737
|
-
* coasting (predictiveCoasting), else its last known bbox.
|
|
744
|
+
* coasting (predictiveCoasting), else its last known bbox. A stationary
|
|
745
|
+
* track (speed < stationarySpeedPx) is frozen so bbox jitter can't walk the
|
|
746
|
+
* box off a sitting object; otherwise the drift is capped at one bbox size
|
|
747
|
+
* so a stale velocity can't fling the box across the frame. Public so the
|
|
748
|
+
* clamp is directly unit-testable. */
|
|
738
749
|
predicted(t) {
|
|
739
750
|
if (!this.config.predictiveCoasting) return t.bbox;
|
|
751
|
+
if (Math.hypot(t.velocity.dx, t.velocity.dy) < this.config.stationarySpeedPx) return t.bbox;
|
|
740
752
|
const f = t.age + 1;
|
|
753
|
+
const driftX = clamp(t.velocity.dx * f, -t.bbox.w, t.bbox.w);
|
|
754
|
+
const driftY = clamp(t.velocity.dy * f, -t.bbox.h, t.bbox.h);
|
|
741
755
|
return {
|
|
742
|
-
x: t.bbox.x +
|
|
743
|
-
y: t.bbox.y +
|
|
756
|
+
x: t.bbox.x + driftX,
|
|
757
|
+
y: t.bbox.y + driftY,
|
|
744
758
|
w: t.bbox.w,
|
|
745
759
|
h: t.bbox.h
|
|
746
760
|
};
|
|
747
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Loose same-class gate used by the rescue and resurrection passes: accept
|
|
764
|
+
* when the detection overlaps the track's LAST-KNOWN bbox by `rescueIou`, or
|
|
765
|
+
* its centroid is within `rescueCentroidFactor × bbox-diagonal`. Always class-
|
|
766
|
+
* gated (a rescue must never cross classes), independent of `classGating`.
|
|
767
|
+
*/
|
|
768
|
+
looseMatch(track, det) {
|
|
769
|
+
if (track.class !== det.class) return false;
|
|
770
|
+
if (iou$1(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return true;
|
|
771
|
+
const tc = bboxCentroid(track.bbox);
|
|
772
|
+
const dc = bboxCentroid(det.bbox);
|
|
773
|
+
const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
|
|
774
|
+
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
775
|
+
return dist <= this.config.rescueCentroidFactor * diag;
|
|
776
|
+
}
|
|
748
777
|
update(detections, timestamp) {
|
|
749
778
|
if (this.config.maxTrackLifetimeMs > 0) {
|
|
750
779
|
const alive = [];
|
|
751
780
|
for (const track of this.tracks) if (timestamp - track.firstSeen > this.config.maxTrackLifetimeMs) {
|
|
752
781
|
track.lost = true;
|
|
782
|
+
track.lostAt = timestamp;
|
|
783
|
+
track.resurrectable = false;
|
|
753
784
|
this.lostTracks.push(track);
|
|
754
785
|
} else alive.push(track);
|
|
755
786
|
this.tracks = alive;
|
|
756
787
|
}
|
|
788
|
+
this.lostTracks = this.lostTracks.filter((t) => timestamp - t.lostAt <= this.config.resurrectionWindowMs);
|
|
757
789
|
const used = /* @__PURE__ */ new Set();
|
|
758
790
|
const matchedTracks = /* @__PURE__ */ new Set();
|
|
759
791
|
const matched = /* @__PURE__ */ new Map();
|
|
@@ -778,6 +810,27 @@ var SortTracker = class {
|
|
|
778
810
|
matchedTracks.add(pair.track);
|
|
779
811
|
used.add(pair.detIdx);
|
|
780
812
|
}
|
|
813
|
+
const rescuePairs = [];
|
|
814
|
+
for (const track of this.tracks) {
|
|
815
|
+
if (matchedTracks.has(track)) continue;
|
|
816
|
+
for (let di = 0; di < detections.length; di++) {
|
|
817
|
+
if (used.has(di)) continue;
|
|
818
|
+
const det = detections[di];
|
|
819
|
+
if (!this.looseMatch(track, det)) continue;
|
|
820
|
+
rescuePairs.push({
|
|
821
|
+
track,
|
|
822
|
+
detIdx: di,
|
|
823
|
+
score: iou$1(track.bbox, det.bbox)
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
rescuePairs.sort((a, b) => b.score - a.score);
|
|
828
|
+
for (const pair of rescuePairs) {
|
|
829
|
+
if (matchedTracks.has(pair.track) || used.has(pair.detIdx)) continue;
|
|
830
|
+
matched.set(pair.track, detections[pair.detIdx]);
|
|
831
|
+
matchedTracks.add(pair.track);
|
|
832
|
+
used.add(pair.detIdx);
|
|
833
|
+
}
|
|
781
834
|
for (const [track, det] of matched) {
|
|
782
835
|
const prevCenter = bboxCentroid({
|
|
783
836
|
x: track.bbox.x,
|
|
@@ -816,12 +869,47 @@ var SortTracker = class {
|
|
|
816
869
|
}
|
|
817
870
|
const occluded = this.config.occlusionEnabled && occluderBoxes.some((ob) => containment(this.predicted(track), ob) >= this.config.occlusionContainment);
|
|
818
871
|
track.age++;
|
|
819
|
-
|
|
820
|
-
if (track.age > limit) {
|
|
872
|
+
if (timestamp - track.lastSeen > (occluded ? this.config.occlusionMaxMissedMs : this.config.maxMissedMs)) {
|
|
821
873
|
track.lost = true;
|
|
874
|
+
track.lostAt = timestamp;
|
|
875
|
+
track.resurrectable = true;
|
|
822
876
|
this.lostTracks.push(track);
|
|
823
877
|
} else surviving.push(track);
|
|
824
878
|
}
|
|
879
|
+
for (let di = 0; di < detections.length; di++) {
|
|
880
|
+
if (used.has(di)) continue;
|
|
881
|
+
const det = detections[di];
|
|
882
|
+
let best;
|
|
883
|
+
let bestScore = -1;
|
|
884
|
+
for (const lost of this.lostTracks) {
|
|
885
|
+
if (!lost.resurrectable) continue;
|
|
886
|
+
if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
|
|
887
|
+
if (!this.looseMatch(lost, det)) continue;
|
|
888
|
+
const score = iou$1(lost.bbox, det.bbox);
|
|
889
|
+
if (score > bestScore) {
|
|
890
|
+
best = lost;
|
|
891
|
+
bestScore = score;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (!best) continue;
|
|
895
|
+
this.lostTracks = this.lostTracks.filter((t) => t !== best);
|
|
896
|
+
best.bbox = det.bbox;
|
|
897
|
+
best.class = det.class;
|
|
898
|
+
best.originalClass = det.originalClass;
|
|
899
|
+
best.score = det.score;
|
|
900
|
+
best.age = 0;
|
|
901
|
+
best.hits++;
|
|
902
|
+
best.lost = false;
|
|
903
|
+
best.lastSeen = timestamp;
|
|
904
|
+
best.velocity = {
|
|
905
|
+
dx: 0,
|
|
906
|
+
dy: 0
|
|
907
|
+
};
|
|
908
|
+
best.path.push(det.bbox);
|
|
909
|
+
if (best.path.length > MAX_PATH_LENGTH) best.path.shift();
|
|
910
|
+
surviving.push(best);
|
|
911
|
+
used.add(di);
|
|
912
|
+
}
|
|
825
913
|
for (let di = 0; di < detections.length; di++) {
|
|
826
914
|
if (used.has(di)) continue;
|
|
827
915
|
const det = detections[di];
|
|
@@ -841,7 +929,9 @@ var SortTracker = class {
|
|
|
841
929
|
dx: 0,
|
|
842
930
|
dy: 0
|
|
843
931
|
},
|
|
844
|
-
lost: false
|
|
932
|
+
lost: false,
|
|
933
|
+
lostAt: 0,
|
|
934
|
+
resurrectable: true
|
|
845
935
|
});
|
|
846
936
|
}
|
|
847
937
|
this.tracks = surviving;
|
|
@@ -956,6 +1046,7 @@ var StateAnalyzer = class {
|
|
|
956
1046
|
//#region src/pipeline-analytics/pipeline/events/event-filter.ts
|
|
957
1047
|
var DEFAULT_EVENT_EMITTER_CONFIG = {
|
|
958
1048
|
minTrackAge: 3,
|
|
1049
|
+
minTrackAgeMs: 0,
|
|
959
1050
|
cooldownSec: 5,
|
|
960
1051
|
enabledTypes: [
|
|
961
1052
|
"object.entering",
|
|
@@ -986,6 +1077,13 @@ var DetectionEventEmitter = class {
|
|
|
986
1077
|
config;
|
|
987
1078
|
previousStates = /* @__PURE__ */ new Map();
|
|
988
1079
|
lastEmitted = /* @__PURE__ */ new Map();
|
|
1080
|
+
/**
|
|
1081
|
+
* Last-known track per id. A `leaving` state fires for a track that is ABSENT
|
|
1082
|
+
* from the current frame, so it can't be resolved from `trackMap` — it is
|
|
1083
|
+
* resolved from this snapshot instead (without it, leaving events never
|
|
1084
|
+
* fired: the latent "0 `left` events in 24h" bug).
|
|
1085
|
+
*/
|
|
1086
|
+
lastKnownTracks = /* @__PURE__ */ new Map();
|
|
989
1087
|
constructor(config = {}) {
|
|
990
1088
|
this.config = {
|
|
991
1089
|
...DEFAULT_EVENT_EMITTER_CONFIG,
|
|
@@ -997,10 +1095,12 @@ var DetectionEventEmitter = class {
|
|
|
997
1095
|
const now = Date.now();
|
|
998
1096
|
const stateMap = new Map(states.map((s) => [s.trackId, s]));
|
|
999
1097
|
const trackMap = new Map(tracks.map((t) => [t.trackId, t]));
|
|
1098
|
+
for (const t of tracks) this.lastKnownTracks.set(t.trackId, t);
|
|
1000
1099
|
for (const state of states) {
|
|
1001
|
-
const track = trackMap.get(state.trackId);
|
|
1100
|
+
const track = trackMap.get(state.trackId) ?? this.lastKnownTracks.get(state.trackId);
|
|
1002
1101
|
if (!track) continue;
|
|
1003
1102
|
if (track.trackAge < this.config.minTrackAge) continue;
|
|
1103
|
+
if (state.dwellTimeMs < this.config.minTrackAgeMs) continue;
|
|
1004
1104
|
const eventType = STATE_TO_EVENT[state.state];
|
|
1005
1105
|
if (!eventType) continue;
|
|
1006
1106
|
if (!this.config.enabledTypes.includes(eventType)) continue;
|
|
@@ -1046,12 +1146,16 @@ var DetectionEventEmitter = class {
|
|
|
1046
1146
|
trackPath: [...track.path]
|
|
1047
1147
|
});
|
|
1048
1148
|
}
|
|
1049
|
-
for (const state of states) if (state.state === "leaving")
|
|
1149
|
+
for (const state of states) if (state.state === "leaving") {
|
|
1150
|
+
this.previousStates.delete(state.trackId);
|
|
1151
|
+
this.lastKnownTracks.delete(state.trackId);
|
|
1152
|
+
}
|
|
1050
1153
|
return events;
|
|
1051
1154
|
}
|
|
1052
1155
|
reset() {
|
|
1053
1156
|
this.previousStates.clear();
|
|
1054
1157
|
this.lastEmitted.clear();
|
|
1158
|
+
this.lastKnownTracks.clear();
|
|
1055
1159
|
}
|
|
1056
1160
|
};
|
|
1057
1161
|
//#endregion
|
|
@@ -1712,10 +1816,13 @@ var TrackStore = class {
|
|
|
1712
1816
|
try {
|
|
1713
1817
|
await this.persistCompleted(record);
|
|
1714
1818
|
} catch (err) {
|
|
1715
|
-
this.logger.warn("persist completed track failed", {
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1819
|
+
this.logger.warn("persist completed track failed", {
|
|
1820
|
+
tags: { deviceId: t.deviceId },
|
|
1821
|
+
meta: {
|
|
1822
|
+
trackId,
|
|
1823
|
+
error: String(err)
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1719
1826
|
}
|
|
1720
1827
|
this.active.delete(trackId);
|
|
1721
1828
|
expired.push(record);
|
|
@@ -1942,10 +2049,13 @@ var MediaStore = class {
|
|
|
1942
2049
|
});
|
|
1943
2050
|
return key;
|
|
1944
2051
|
} catch (err) {
|
|
1945
|
-
this.logger.warn("media put failed", {
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2052
|
+
this.logger.warn("media put failed", {
|
|
2053
|
+
tags: { deviceId: params.deviceId },
|
|
2054
|
+
meta: {
|
|
2055
|
+
key,
|
|
2056
|
+
error: String(err)
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
1949
2059
|
throw err;
|
|
1950
2060
|
}
|
|
1951
2061
|
}
|
|
@@ -2134,11 +2244,14 @@ var MediaStore = class {
|
|
|
2134
2244
|
relativePath: oldPath
|
|
2135
2245
|
});
|
|
2136
2246
|
} catch (err) {
|
|
2137
|
-
this.logger.warn("MediaStore.reown: old blob delete failed (best-effort)", {
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2247
|
+
this.logger.warn("MediaStore.reown: old blob delete failed (best-effort)", {
|
|
2248
|
+
tags: { deviceId },
|
|
2249
|
+
meta: {
|
|
2250
|
+
key,
|
|
2251
|
+
oldPath,
|
|
2252
|
+
error: String(err)
|
|
2253
|
+
}
|
|
2254
|
+
});
|
|
2142
2255
|
}
|
|
2143
2256
|
try {
|
|
2144
2257
|
await this.store.delete.mutate({
|
|
@@ -2146,10 +2259,13 @@ var MediaStore = class {
|
|
|
2146
2259
|
key
|
|
2147
2260
|
});
|
|
2148
2261
|
} catch (err) {
|
|
2149
|
-
this.logger.warn("MediaStore.reown: old index row delete failed (best-effort)", {
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2262
|
+
this.logger.warn("MediaStore.reown: old index row delete failed (best-effort)", {
|
|
2263
|
+
tags: { deviceId },
|
|
2264
|
+
meta: {
|
|
2265
|
+
key,
|
|
2266
|
+
error: String(err)
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2153
2269
|
}
|
|
2154
2270
|
return newKey;
|
|
2155
2271
|
}
|
|
@@ -2350,10 +2466,13 @@ var EventStore = class {
|
|
|
2350
2466
|
}
|
|
2351
2467
|
});
|
|
2352
2468
|
} catch (err) {
|
|
2353
|
-
this.logger.warn("insertMotion failed", {
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2469
|
+
this.logger.warn("insertMotion failed", {
|
|
2470
|
+
tags: { deviceId: ev.deviceId },
|
|
2471
|
+
meta: {
|
|
2472
|
+
eventId: ev.id,
|
|
2473
|
+
error: String(err)
|
|
2474
|
+
}
|
|
2475
|
+
});
|
|
2357
2476
|
}
|
|
2358
2477
|
}
|
|
2359
2478
|
async insertObject(ev) {
|
|
@@ -2367,10 +2486,13 @@ var EventStore = class {
|
|
|
2367
2486
|
}
|
|
2368
2487
|
});
|
|
2369
2488
|
} catch (err) {
|
|
2370
|
-
this.logger.warn("insertObject failed", {
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2489
|
+
this.logger.warn("insertObject failed", {
|
|
2490
|
+
tags: { deviceId: ev.deviceId },
|
|
2491
|
+
meta: {
|
|
2492
|
+
eventId: ev.id,
|
|
2493
|
+
error: String(err)
|
|
2494
|
+
}
|
|
2495
|
+
});
|
|
2374
2496
|
}
|
|
2375
2497
|
}
|
|
2376
2498
|
async insertAudio(ev) {
|
|
@@ -2384,10 +2506,13 @@ var EventStore = class {
|
|
|
2384
2506
|
}
|
|
2385
2507
|
});
|
|
2386
2508
|
} catch (err) {
|
|
2387
|
-
this.logger.warn("insertAudio failed", {
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2509
|
+
this.logger.warn("insertAudio failed", {
|
|
2510
|
+
tags: { deviceId: ev.deviceId },
|
|
2511
|
+
meta: {
|
|
2512
|
+
eventId: ev.id,
|
|
2513
|
+
error: String(err)
|
|
2514
|
+
}
|
|
2515
|
+
});
|
|
2391
2516
|
}
|
|
2392
2517
|
}
|
|
2393
2518
|
buildFilter(q) {
|
|
@@ -2856,25 +2981,34 @@ var EventMediaDispatcher = class {
|
|
|
2856
2981
|
getRemoteFrame: this.deps.getRemoteFrame
|
|
2857
2982
|
});
|
|
2858
2983
|
} catch (err) {
|
|
2859
|
-
this.deps.logger.debug("event media: resolveFrame threw", {
|
|
2860
|
-
deviceId,
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2984
|
+
this.deps.logger.debug("event media: resolveFrame threw", {
|
|
2985
|
+
tags: { deviceId },
|
|
2986
|
+
meta: {
|
|
2987
|
+
deviceId,
|
|
2988
|
+
shmId: frameHandle.shmId,
|
|
2989
|
+
error: String(err)
|
|
2990
|
+
}
|
|
2991
|
+
});
|
|
2864
2992
|
return;
|
|
2865
2993
|
}
|
|
2866
2994
|
if (!decoded) {
|
|
2867
|
-
this.deps.logger.debug("event media: frame recycled before resolve", {
|
|
2868
|
-
deviceId,
|
|
2869
|
-
|
|
2870
|
-
|
|
2995
|
+
this.deps.logger.debug("event media: frame recycled before resolve", {
|
|
2996
|
+
tags: { deviceId },
|
|
2997
|
+
meta: {
|
|
2998
|
+
deviceId,
|
|
2999
|
+
shmId: frameHandle.shmId
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
2871
3002
|
return;
|
|
2872
3003
|
}
|
|
2873
3004
|
if (decoded.format !== "rgb") {
|
|
2874
|
-
this.deps.logger.debug("event media: resolved frame is not RGB", {
|
|
2875
|
-
deviceId,
|
|
2876
|
-
|
|
2877
|
-
|
|
3005
|
+
this.deps.logger.debug("event media: resolved frame is not RGB", {
|
|
3006
|
+
tags: { deviceId },
|
|
3007
|
+
meta: {
|
|
3008
|
+
deviceId,
|
|
3009
|
+
format: decoded.format
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
2878
3012
|
return;
|
|
2879
3013
|
}
|
|
2880
3014
|
const frameData = Buffer.isBuffer(decoded.data) ? decoded.data : Buffer.from(decoded.data);
|
|
@@ -2916,11 +3050,14 @@ var EventMediaDispatcher = class {
|
|
|
2916
3050
|
data: crop
|
|
2917
3051
|
});
|
|
2918
3052
|
} catch (err) {
|
|
2919
|
-
this.deps.logger.warn("event media: crop failed", {
|
|
2920
|
-
deviceId,
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
3053
|
+
this.deps.logger.warn("event media: crop failed", {
|
|
3054
|
+
tags: { deviceId },
|
|
3055
|
+
meta: {
|
|
3056
|
+
deviceId,
|
|
3057
|
+
eventId: ev.eventId,
|
|
3058
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3059
|
+
}
|
|
3060
|
+
});
|
|
2924
3061
|
}
|
|
2925
3062
|
try {
|
|
2926
3063
|
const fullFrame = await drawBoxedFrame(frameData, fw, fh, [], { quality: MEDIA_QUALITY });
|
|
@@ -2933,11 +3070,14 @@ var EventMediaDispatcher = class {
|
|
|
2933
3070
|
data: fullFrame
|
|
2934
3071
|
});
|
|
2935
3072
|
} catch (err) {
|
|
2936
|
-
this.deps.logger.warn("event media: clear full frame failed", {
|
|
2937
|
-
deviceId,
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
3073
|
+
this.deps.logger.warn("event media: clear full frame failed", {
|
|
3074
|
+
tags: { deviceId },
|
|
3075
|
+
meta: {
|
|
3076
|
+
deviceId,
|
|
3077
|
+
eventId: ev.eventId,
|
|
3078
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3079
|
+
}
|
|
3080
|
+
});
|
|
2941
3081
|
}
|
|
2942
3082
|
try {
|
|
2943
3083
|
const fullFrameBoxed = await drawBoxedFrame(frameData, fw, fh, [box], { quality: MEDIA_QUALITY });
|
|
@@ -2950,11 +3090,14 @@ var EventMediaDispatcher = class {
|
|
|
2950
3090
|
data: fullFrameBoxed
|
|
2951
3091
|
});
|
|
2952
3092
|
} catch (err) {
|
|
2953
|
-
this.deps.logger.warn("event media: boxed full frame failed", {
|
|
2954
|
-
deviceId,
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
3093
|
+
this.deps.logger.warn("event media: boxed full frame failed", {
|
|
3094
|
+
tags: { deviceId },
|
|
3095
|
+
meta: {
|
|
3096
|
+
deviceId,
|
|
3097
|
+
eventId: ev.eventId,
|
|
3098
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3099
|
+
}
|
|
3100
|
+
});
|
|
2958
3101
|
}
|
|
2959
3102
|
if (ev.childCrops) for (const child of ev.childCrops) try {
|
|
2960
3103
|
const childRegion = squareSafeCropRegion(child.bbox, {
|
|
@@ -2984,11 +3127,14 @@ var EventMediaDispatcher = class {
|
|
|
2984
3127
|
data: childCropData
|
|
2985
3128
|
});
|
|
2986
3129
|
} catch (err) {
|
|
2987
|
-
this.deps.logger.warn(`event media: ${child.kind} failed`, {
|
|
2988
|
-
deviceId,
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3130
|
+
this.deps.logger.warn(`event media: ${child.kind} failed`, {
|
|
3131
|
+
tags: { deviceId },
|
|
3132
|
+
meta: {
|
|
3133
|
+
deviceId,
|
|
3134
|
+
eventId: ev.eventId,
|
|
3135
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3136
|
+
}
|
|
3137
|
+
});
|
|
2992
3138
|
}
|
|
2993
3139
|
}
|
|
2994
3140
|
async writeTrackFrame(deviceId, frameData, fw, fh, tf) {
|
|
@@ -3007,11 +3153,14 @@ var EventMediaDispatcher = class {
|
|
|
3007
3153
|
data: boxed
|
|
3008
3154
|
});
|
|
3009
3155
|
} catch (err) {
|
|
3010
|
-
this.deps.logger.warn("event media: track frame failed", {
|
|
3011
|
-
deviceId,
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3156
|
+
this.deps.logger.warn("event media: track frame failed", {
|
|
3157
|
+
tags: { deviceId },
|
|
3158
|
+
meta: {
|
|
3159
|
+
deviceId,
|
|
3160
|
+
trackId: tf.trackId,
|
|
3161
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3162
|
+
}
|
|
3163
|
+
});
|
|
3015
3164
|
}
|
|
3016
3165
|
}
|
|
3017
3166
|
};
|
|
@@ -3727,22 +3876,22 @@ function resolveDetectionSensitivitySettings(raw) {
|
|
|
3727
3876
|
stationaryThresholdSec: pick("stationaryThresholdSec")
|
|
3728
3877
|
};
|
|
3729
3878
|
}
|
|
3730
|
-
//#endregion
|
|
3731
|
-
//#region src/pipeline-analytics/tracking-settings.ts
|
|
3732
|
-
/**
|
|
3733
|
-
* Per-device tracker tuning. These drive the SORT tracker (association,
|
|
3734
|
-
* coasting, occlusion, lifetime) and the detector-dropout frame skip. Every
|
|
3735
|
-
* field is independently overridable per camera from the admin UI; unknown or
|
|
3736
|
-
* invalid values fall back to the field default (never throws on a bad blob).
|
|
3737
|
-
*
|
|
3738
|
-
* Note: `minHits` lives in detection-sensitivity-settings (it predates this
|
|
3739
|
-
* file and already flows to the tracker) — it is NOT duplicated here.
|
|
3740
|
-
*/
|
|
3741
3879
|
var TrackingSettingsSchema = object({
|
|
3742
3880
|
/** IoU required to match a (predicted) track to a detection. */
|
|
3743
3881
|
iouThreshold: number().min(0).max(1).default(.3),
|
|
3744
|
-
/**
|
|
3882
|
+
/** Wall-clock coasting budget (ms) before a missed track is dropped. Time-
|
|
3883
|
+
* based so behaviour is identical across camera frame rates. */
|
|
3884
|
+
maxMissedMs: number().min(0).default(4e3),
|
|
3885
|
+
/** Minimum wall-clock track age (ms) before it may produce events. */
|
|
3886
|
+
minTrackAgeMs: number().min(0).default(1e3),
|
|
3887
|
+
/** Extended coasting budget (ms) while a track is occluded. */
|
|
3888
|
+
occlusionMaxMissedMs: number().min(0).default(8e3),
|
|
3889
|
+
/** @deprecated frame-based alias for {@link maxMissedMs}. Retained so
|
|
3890
|
+
* existing per-device blobs keep working — resolved into `maxMissedMs`
|
|
3891
|
+
* (× {@link NOMINAL_FRAME_MS}) only when `maxMissedMs` is unset. */
|
|
3745
3892
|
maxMissedFrames: number().int().min(0).default(30),
|
|
3893
|
+
/** @deprecated frame-based alias for {@link occlusionMaxMissedMs}. */
|
|
3894
|
+
occlusionMaxMissedFrames: number().int().min(0).default(60),
|
|
3746
3895
|
/** Hard cap on total track lifetime — retire+restart beyond this to bound
|
|
3747
3896
|
* drift / long-lived false positives. 0 = unlimited. */
|
|
3748
3897
|
maxTrackLifetimeSec: number().min(0).default(300),
|
|
@@ -3756,8 +3905,17 @@ var TrackingSettingsSchema = object({
|
|
|
3756
3905
|
/** Containment ratio (intersection / occluded-area) above which a missed
|
|
3757
3906
|
* track is considered hidden behind another track. */
|
|
3758
3907
|
occlusionContainment: number().min(0).max(1).default(.6),
|
|
3759
|
-
/**
|
|
3760
|
-
|
|
3908
|
+
/** Loose IoU gate (vs. last-known bbox) for the second (rescue) association
|
|
3909
|
+
* pass + graveyard resurrection. */
|
|
3910
|
+
rescueIouThreshold: number().min(0).max(1).default(.1),
|
|
3911
|
+
/** Centroid gate for rescue/resurrection: fraction of the bbox diagonal. */
|
|
3912
|
+
rescueCentroidFactor: number().min(0).default(.75),
|
|
3913
|
+
/** Wall-clock graveyard retention (ms): a dropped track re-attaches to a new
|
|
3914
|
+
* same-class detection (keeping its id) for this long. */
|
|
3915
|
+
resurrectionWindowMs: number().min(0).default(8e3),
|
|
3916
|
+
/** Speed (px/frame) below which a track's prediction is frozen (stationary
|
|
3917
|
+
* jitter can't drift the box off a sitting object). */
|
|
3918
|
+
stationarySpeedPx: number().min(0).default(2),
|
|
3761
3919
|
/** Skip frames where detections suddenly drop to zero (detector glitch). */
|
|
3762
3920
|
dropoutSkipEnabled: boolean().default(true),
|
|
3763
3921
|
/** Max consecutive all-zero frames absorbed as a glitch before the scene is
|
|
@@ -3772,15 +3930,26 @@ var TRACKING_DEFAULTS = TrackingSettingsSchema.parse({});
|
|
|
3772
3930
|
*/
|
|
3773
3931
|
function resolveTrackingSettings(raw) {
|
|
3774
3932
|
const s = TrackingSettingsSchema.shape;
|
|
3933
|
+
const maxMissedFrames = s.maxMissedFrames.catch(TRACKING_DEFAULTS.maxMissedFrames).parse(raw.maxMissedFrames);
|
|
3934
|
+
const occlusionMaxMissedFrames = s.occlusionMaxMissedFrames.catch(TRACKING_DEFAULTS.occlusionMaxMissedFrames).parse(raw.occlusionMaxMissedFrames);
|
|
3935
|
+
const maxMissedMs = raw.maxMissedMs !== void 0 ? s.maxMissedMs.catch(TRACKING_DEFAULTS.maxMissedMs).parse(raw.maxMissedMs) : raw.maxMissedFrames !== void 0 ? Math.round(maxMissedFrames * 133) : TRACKING_DEFAULTS.maxMissedMs;
|
|
3936
|
+
const occlusionMaxMissedMs = raw.occlusionMaxMissedMs !== void 0 ? s.occlusionMaxMissedMs.catch(TRACKING_DEFAULTS.occlusionMaxMissedMs).parse(raw.occlusionMaxMissedMs) : raw.occlusionMaxMissedFrames !== void 0 ? Math.round(occlusionMaxMissedFrames * 133) : TRACKING_DEFAULTS.occlusionMaxMissedMs;
|
|
3775
3937
|
return {
|
|
3776
3938
|
iouThreshold: s.iouThreshold.catch(TRACKING_DEFAULTS.iouThreshold).parse(raw.iouThreshold),
|
|
3777
|
-
|
|
3939
|
+
maxMissedMs,
|
|
3940
|
+
minTrackAgeMs: s.minTrackAgeMs.catch(TRACKING_DEFAULTS.minTrackAgeMs).parse(raw.minTrackAgeMs),
|
|
3941
|
+
occlusionMaxMissedMs,
|
|
3942
|
+
maxMissedFrames,
|
|
3943
|
+
occlusionMaxMissedFrames,
|
|
3778
3944
|
maxTrackLifetimeSec: s.maxTrackLifetimeSec.catch(TRACKING_DEFAULTS.maxTrackLifetimeSec).parse(raw.maxTrackLifetimeSec),
|
|
3779
3945
|
predictiveCoasting: s.predictiveCoasting.catch(TRACKING_DEFAULTS.predictiveCoasting).parse(raw.predictiveCoasting),
|
|
3780
3946
|
classGating: s.classGating.catch(TRACKING_DEFAULTS.classGating).parse(raw.classGating),
|
|
3781
3947
|
occlusionEnabled: s.occlusionEnabled.catch(TRACKING_DEFAULTS.occlusionEnabled).parse(raw.occlusionEnabled),
|
|
3782
3948
|
occlusionContainment: s.occlusionContainment.catch(TRACKING_DEFAULTS.occlusionContainment).parse(raw.occlusionContainment),
|
|
3783
|
-
|
|
3949
|
+
rescueIouThreshold: s.rescueIouThreshold.catch(TRACKING_DEFAULTS.rescueIouThreshold).parse(raw.rescueIouThreshold),
|
|
3950
|
+
rescueCentroidFactor: s.rescueCentroidFactor.catch(TRACKING_DEFAULTS.rescueCentroidFactor).parse(raw.rescueCentroidFactor),
|
|
3951
|
+
resurrectionWindowMs: s.resurrectionWindowMs.catch(TRACKING_DEFAULTS.resurrectionWindowMs).parse(raw.resurrectionWindowMs),
|
|
3952
|
+
stationarySpeedPx: s.stationarySpeedPx.catch(TRACKING_DEFAULTS.stationarySpeedPx).parse(raw.stationarySpeedPx),
|
|
3784
3953
|
dropoutSkipEnabled: s.dropoutSkipEnabled.catch(TRACKING_DEFAULTS.dropoutSkipEnabled).parse(raw.dropoutSkipEnabled),
|
|
3785
3954
|
dropoutMaxSkipFrames: s.dropoutMaxSkipFrames.catch(TRACKING_DEFAULTS.dropoutMaxSkipFrames).parse(raw.dropoutMaxSkipFrames)
|
|
3786
3955
|
};
|
|
@@ -4223,10 +4392,13 @@ var FaceStore = class {
|
|
|
4223
4392
|
value: rest
|
|
4224
4393
|
});
|
|
4225
4394
|
} catch (err) {
|
|
4226
|
-
this.logger.warn("FaceStore.insert failed", {
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4395
|
+
this.logger.warn("FaceStore.insert failed", {
|
|
4396
|
+
tags: { deviceId: face.deviceId },
|
|
4397
|
+
meta: {
|
|
4398
|
+
faceId: id,
|
|
4399
|
+
error: String(err)
|
|
4400
|
+
}
|
|
4401
|
+
});
|
|
4230
4402
|
}
|
|
4231
4403
|
}
|
|
4232
4404
|
/**
|
|
@@ -4293,10 +4465,13 @@ var FaceStore = class {
|
|
|
4293
4465
|
});
|
|
4294
4466
|
deleted.push(id);
|
|
4295
4467
|
} catch (err) {
|
|
4296
|
-
this.logger.warn("FaceStore.prune delete failed", {
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4468
|
+
this.logger.warn("FaceStore.prune delete failed", {
|
|
4469
|
+
tags: { deviceId: input.deviceId },
|
|
4470
|
+
meta: {
|
|
4471
|
+
faceId: id,
|
|
4472
|
+
error: String(err)
|
|
4473
|
+
}
|
|
4474
|
+
});
|
|
4300
4475
|
}
|
|
4301
4476
|
return deleted;
|
|
4302
4477
|
}
|
|
@@ -4582,10 +4757,13 @@ var ObjectEmbeddingStore = class {
|
|
|
4582
4757
|
value: record
|
|
4583
4758
|
});
|
|
4584
4759
|
} catch (err) {
|
|
4585
|
-
this.logger.warn("ObjectEmbeddingStore.upsertIfBetter failed", {
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4760
|
+
this.logger.warn("ObjectEmbeddingStore.upsertIfBetter failed", {
|
|
4761
|
+
tags: { deviceId: input.deviceId },
|
|
4762
|
+
meta: {
|
|
4763
|
+
trackId: input.trackId,
|
|
4764
|
+
error: String(err)
|
|
4765
|
+
}
|
|
4766
|
+
});
|
|
4589
4767
|
}
|
|
4590
4768
|
}
|
|
4591
4769
|
/**
|
|
@@ -4797,18 +4975,24 @@ var FaceRecognizer = class {
|
|
|
4797
4975
|
try {
|
|
4798
4976
|
await this.deps.trackStore.setLabel(c.trackId, name);
|
|
4799
4977
|
} catch (err) {
|
|
4800
|
-
this.deps.logger.warn("setLabel failed", {
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4978
|
+
this.deps.logger.warn("setLabel failed", {
|
|
4979
|
+
tags: { deviceId: input.deviceId },
|
|
4980
|
+
meta: {
|
|
4981
|
+
trackId: c.trackId,
|
|
4982
|
+
error: String(err)
|
|
4983
|
+
}
|
|
4984
|
+
});
|
|
4804
4985
|
}
|
|
4805
4986
|
try {
|
|
4806
4987
|
await this.deps.eventStore.setLabelForTrack(c.trackId, name);
|
|
4807
4988
|
} catch (err) {
|
|
4808
|
-
this.deps.logger.warn("setLabelForTrack failed", {
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4989
|
+
this.deps.logger.warn("setLabelForTrack failed", {
|
|
4990
|
+
tags: { deviceId: input.deviceId },
|
|
4991
|
+
meta: {
|
|
4992
|
+
trackId: c.trackId,
|
|
4993
|
+
error: String(err)
|
|
4994
|
+
}
|
|
4995
|
+
});
|
|
4812
4996
|
}
|
|
4813
4997
|
}
|
|
4814
4998
|
}
|
|
@@ -4820,10 +5004,13 @@ var FaceRecognizer = class {
|
|
|
4820
5004
|
if (input.frameHandle !== void 0) try {
|
|
4821
5005
|
crop = await this.deps.captureCrop(input.frameHandle, cropBbox, input.frameWidth, input.frameHeight, input.cropPadding) ?? void 0;
|
|
4822
5006
|
} catch (err) {
|
|
4823
|
-
this.deps.logger.debug("FaceRecognizer crop capture failed", {
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
5007
|
+
this.deps.logger.debug("FaceRecognizer crop capture failed", {
|
|
5008
|
+
tags: { deviceId: input.deviceId },
|
|
5009
|
+
meta: {
|
|
5010
|
+
trackId: c.trackId,
|
|
5011
|
+
error: String(err)
|
|
5012
|
+
}
|
|
5013
|
+
});
|
|
4827
5014
|
}
|
|
4828
5015
|
this.bestFace.set(c.trackId, {
|
|
4829
5016
|
score: c.confidence,
|
|
@@ -4879,10 +5066,13 @@ var FaceRecognizer = class {
|
|
|
4879
5066
|
data: held.crop
|
|
4880
5067
|
});
|
|
4881
5068
|
} catch (err) {
|
|
4882
|
-
this.deps.logger.warn("FaceRecognizer face crop put failed", {
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
5069
|
+
this.deps.logger.warn("FaceRecognizer face crop put failed", {
|
|
5070
|
+
tags: { deviceId },
|
|
5071
|
+
meta: {
|
|
5072
|
+
faceId,
|
|
5073
|
+
error: String(err)
|
|
5074
|
+
}
|
|
5075
|
+
});
|
|
4886
5076
|
}
|
|
4887
5077
|
try {
|
|
4888
5078
|
await this.deps.faceStore.insert({
|
|
@@ -4908,10 +5098,13 @@ var FaceRecognizer = class {
|
|
|
4908
5098
|
}
|
|
4909
5099
|
});
|
|
4910
5100
|
} catch (err) {
|
|
4911
|
-
this.deps.logger.warn("FaceRecognizer faceStore insert failed", {
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
5101
|
+
this.deps.logger.warn("FaceRecognizer faceStore insert failed", {
|
|
5102
|
+
tags: { deviceId },
|
|
5103
|
+
meta: {
|
|
5104
|
+
faceId,
|
|
5105
|
+
error: String(err)
|
|
5106
|
+
}
|
|
5107
|
+
});
|
|
4915
5108
|
}
|
|
4916
5109
|
}
|
|
4917
5110
|
};
|
|
@@ -4991,10 +5184,13 @@ var PlateStore = class {
|
|
|
4991
5184
|
value: rest
|
|
4992
5185
|
});
|
|
4993
5186
|
} catch (err) {
|
|
4994
|
-
this.logger.warn("PlateStore.insert failed", {
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
5187
|
+
this.logger.warn("PlateStore.insert failed", {
|
|
5188
|
+
tags: { deviceId: plate.deviceId },
|
|
5189
|
+
meta: {
|
|
5190
|
+
plateId: id,
|
|
5191
|
+
error: String(err)
|
|
5192
|
+
}
|
|
5193
|
+
});
|
|
4998
5194
|
}
|
|
4999
5195
|
}
|
|
5000
5196
|
normalizeRow(r) {
|
|
@@ -5150,10 +5346,13 @@ var PlateRecognizer = class {
|
|
|
5150
5346
|
if (input.frameHandle !== void 0) try {
|
|
5151
5347
|
crop = await this.deps.captureCrop(input.frameHandle, c.plateBbox, input.frameWidth, input.frameHeight, input.cropPadding) ?? void 0;
|
|
5152
5348
|
} catch (err) {
|
|
5153
|
-
this.deps.logger.debug("PlateRecognizer crop capture failed", {
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5349
|
+
this.deps.logger.debug("PlateRecognizer crop capture failed", {
|
|
5350
|
+
tags: { deviceId: input.deviceId },
|
|
5351
|
+
meta: {
|
|
5352
|
+
trackId: c.trackId,
|
|
5353
|
+
error: String(err)
|
|
5354
|
+
}
|
|
5355
|
+
});
|
|
5157
5356
|
}
|
|
5158
5357
|
this.bestPlate.set(c.trackId, {
|
|
5159
5358
|
text: c.plateText,
|
|
@@ -5182,10 +5381,13 @@ var PlateRecognizer = class {
|
|
|
5182
5381
|
data: held.crop
|
|
5183
5382
|
});
|
|
5184
5383
|
} catch (err) {
|
|
5185
|
-
this.deps.logger.warn("PlateRecognizer plate crop put failed", {
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5384
|
+
this.deps.logger.warn("PlateRecognizer plate crop put failed", {
|
|
5385
|
+
tags: { deviceId },
|
|
5386
|
+
meta: {
|
|
5387
|
+
plateId,
|
|
5388
|
+
error: String(err)
|
|
5389
|
+
}
|
|
5390
|
+
});
|
|
5189
5391
|
}
|
|
5190
5392
|
try {
|
|
5191
5393
|
await this.deps.plateStore.insert({
|
|
@@ -5211,10 +5413,13 @@ var PlateRecognizer = class {
|
|
|
5211
5413
|
}
|
|
5212
5414
|
});
|
|
5213
5415
|
} catch (err) {
|
|
5214
|
-
this.deps.logger.warn("PlateRecognizer plateStore insert failed", {
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5416
|
+
this.deps.logger.warn("PlateRecognizer plateStore insert failed", {
|
|
5417
|
+
tags: { deviceId },
|
|
5418
|
+
meta: {
|
|
5419
|
+
plateId,
|
|
5420
|
+
error: String(err)
|
|
5421
|
+
}
|
|
5422
|
+
});
|
|
5218
5423
|
}
|
|
5219
5424
|
}
|
|
5220
5425
|
};
|
|
@@ -6525,7 +6730,10 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
6525
6730
|
} : void 0, { classificationMinScore: settings.classificationMinScore });
|
|
6526
6731
|
if (route.kind === "classification") {
|
|
6527
6732
|
if (!topClassification) {
|
|
6528
|
-
this.ctx.logger.warn("classifyAudioFrame returned classification but topClassification is null", {
|
|
6733
|
+
this.ctx.logger.warn("classifyAudioFrame returned classification but topClassification is null", {
|
|
6734
|
+
tags: { deviceId },
|
|
6735
|
+
meta: { deviceId }
|
|
6736
|
+
});
|
|
6529
6737
|
return;
|
|
6530
6738
|
}
|
|
6531
6739
|
const last = this.lastAudioInsertByDevice.get(deviceId);
|
|
@@ -6886,16 +7094,21 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
6886
7094
|
p = new FrameProcessor(deviceId, {
|
|
6887
7095
|
minHits,
|
|
6888
7096
|
iouThreshold: trk.iouThreshold,
|
|
6889
|
-
|
|
7097
|
+
maxMissedMs: trk.maxMissedMs,
|
|
6890
7098
|
predictiveCoasting: trk.predictiveCoasting,
|
|
6891
7099
|
classGating: trk.classGating,
|
|
6892
7100
|
occlusionEnabled: trk.occlusionEnabled,
|
|
6893
7101
|
occlusionContainment: trk.occlusionContainment,
|
|
6894
|
-
|
|
7102
|
+
occlusionMaxMissedMs: trk.occlusionMaxMissedMs,
|
|
7103
|
+
rescueIouThreshold: trk.rescueIouThreshold,
|
|
7104
|
+
rescueCentroidFactor: trk.rescueCentroidFactor,
|
|
7105
|
+
resurrectionWindowMs: trk.resurrectionWindowMs,
|
|
7106
|
+
stationarySpeedPx: trk.stationarySpeedPx,
|
|
6895
7107
|
maxTrackLifetimeMs: trk.maxTrackLifetimeSec * 1e3
|
|
6896
7108
|
}, { stationaryThresholdSec }, {
|
|
6897
7109
|
minTrackAge,
|
|
6898
|
-
cooldownSec
|
|
7110
|
+
cooldownSec,
|
|
7111
|
+
minTrackAgeMs: trk.minTrackAgeMs
|
|
6899
7112
|
}, source);
|
|
6900
7113
|
this.processors.set(key, p);
|
|
6901
7114
|
}
|
|
@@ -7075,13 +7288,16 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
7075
7288
|
const { counts, ids } = await this.eventStore.pruneBefore(input);
|
|
7076
7289
|
if (ids.length > 0 && this.mediaStore) await this.mediaStore.deleteForEvents([...ids]);
|
|
7077
7290
|
const { motion, object, audio } = counts;
|
|
7078
|
-
if (motion + object + audio > 0) this.ctx.logger.info("analytics event eviction (floor)", {
|
|
7079
|
-
deviceId: input.deviceId,
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7291
|
+
if (motion + object + audio > 0) this.ctx.logger.info("analytics event eviction (floor)", {
|
|
7292
|
+
tags: { deviceId: input.deviceId },
|
|
7293
|
+
meta: {
|
|
7294
|
+
deviceId: input.deviceId,
|
|
7295
|
+
cutoffMs: input.cutoffMs,
|
|
7296
|
+
motion,
|
|
7297
|
+
object,
|
|
7298
|
+
audio
|
|
7299
|
+
}
|
|
7300
|
+
});
|
|
7085
7301
|
return counts;
|
|
7086
7302
|
}
|
|
7087
7303
|
/**
|
|
@@ -7453,12 +7669,53 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
7453
7669
|
},
|
|
7454
7670
|
{
|
|
7455
7671
|
type: "number",
|
|
7456
|
-
key: "
|
|
7457
|
-
label: "Max
|
|
7458
|
-
description: "
|
|
7672
|
+
key: "maxMissedMs",
|
|
7673
|
+
label: "Max coasting time",
|
|
7674
|
+
description: "How long (ms) a track may miss detections before it is dropped. Time-based so it behaves the same on a 5fps and a 23fps camera.",
|
|
7459
7675
|
min: 0,
|
|
7460
|
-
step:
|
|
7461
|
-
default: TRACKING_DEFAULTS.
|
|
7676
|
+
step: 100,
|
|
7677
|
+
default: TRACKING_DEFAULTS.maxMissedMs,
|
|
7678
|
+
unit: "ms"
|
|
7679
|
+
},
|
|
7680
|
+
{
|
|
7681
|
+
type: "number",
|
|
7682
|
+
key: "minTrackAgeMs",
|
|
7683
|
+
label: "Min track age",
|
|
7684
|
+
description: "A track must exist for at least this long (ms) before it can produce events — stops sub-second flicker fragments from becoming events.",
|
|
7685
|
+
min: 0,
|
|
7686
|
+
step: 100,
|
|
7687
|
+
default: TRACKING_DEFAULTS.minTrackAgeMs,
|
|
7688
|
+
unit: "ms"
|
|
7689
|
+
},
|
|
7690
|
+
{
|
|
7691
|
+
type: "number",
|
|
7692
|
+
key: "resurrectionWindowMs",
|
|
7693
|
+
label: "Resurrection window",
|
|
7694
|
+
description: "How long (ms) a dropped track can be re-attached to a re-appearing same-class detection (keeping its id) — the main fix for a stationary object fragmenting into many tracks.",
|
|
7695
|
+
min: 0,
|
|
7696
|
+
step: 500,
|
|
7697
|
+
default: TRACKING_DEFAULTS.resurrectionWindowMs,
|
|
7698
|
+
unit: "ms"
|
|
7699
|
+
},
|
|
7700
|
+
{
|
|
7701
|
+
type: "number",
|
|
7702
|
+
key: "rescueIouThreshold",
|
|
7703
|
+
label: "Rescue IoU threshold",
|
|
7704
|
+
description: "Looser overlap (vs. a track’s last-known box) for the second association + resurrection passes. Lower = more aggressive re-attachment.",
|
|
7705
|
+
min: 0,
|
|
7706
|
+
max: 1,
|
|
7707
|
+
step: .05,
|
|
7708
|
+
default: TRACKING_DEFAULTS.rescueIouThreshold
|
|
7709
|
+
},
|
|
7710
|
+
{
|
|
7711
|
+
type: "number",
|
|
7712
|
+
key: "stationarySpeedPx",
|
|
7713
|
+
label: "Stationary speed",
|
|
7714
|
+
description: "Speed (px/frame) below which a track is treated as stationary and its prediction is frozen, so bbox jitter cannot walk the box off a sitting object.",
|
|
7715
|
+
min: 0,
|
|
7716
|
+
step: .5,
|
|
7717
|
+
default: TRACKING_DEFAULTS.stationarySpeedPx,
|
|
7718
|
+
unit: "px"
|
|
7462
7719
|
},
|
|
7463
7720
|
{
|
|
7464
7721
|
type: "number",
|
|
@@ -7503,12 +7760,13 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
7503
7760
|
},
|
|
7504
7761
|
{
|
|
7505
7762
|
type: "number",
|
|
7506
|
-
key: "
|
|
7507
|
-
label: "Occlusion max
|
|
7508
|
-
description: "Extended coasting
|
|
7763
|
+
key: "occlusionMaxMissedMs",
|
|
7764
|
+
label: "Occlusion max coasting time",
|
|
7765
|
+
description: "Extended coasting time (ms) while a track is hidden behind another.",
|
|
7509
7766
|
min: 0,
|
|
7510
|
-
step:
|
|
7511
|
-
default: TRACKING_DEFAULTS.
|
|
7767
|
+
step: 100,
|
|
7768
|
+
default: TRACKING_DEFAULTS.occlusionMaxMissedMs,
|
|
7769
|
+
unit: "ms"
|
|
7512
7770
|
},
|
|
7513
7771
|
{
|
|
7514
7772
|
type: "boolean",
|