@camstack/addon-post-analysis 1.2.291 → 1.2.293
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/{clip-model-registry-CTY5co6P.js → clip-model-registry-BAuTHS_s.js} +2323 -2033
- package/dist/{clip-model-registry-Cudz--Ql.mjs → clip-model-registry-D2zqXamW.mjs} +2324 -2034
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CBHxwCSj.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DW6-zerK.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Boh18P08.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Bs5QFgmh.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-DSKT2dE4.mjs → hostInit-CAJh3aX8.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +445 -45
- package/dist/pipeline-analytics/index.mjs +445 -45
- package/dist/pipeline-analytics/remoteEntry.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_types__loadShare__.js-2OwTV_D2.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DAAKQ3sC.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_clip_model_registry = require("../clip-model-registry-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-BAuTHS_s.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -47054,6 +47054,42 @@ function pairRiders(detections) {
|
|
|
47054
47054
|
return pairs;
|
|
47055
47055
|
}
|
|
47056
47056
|
//#endregion
|
|
47057
|
+
//#region src/pipeline-analytics/pipeline/tracker/low-score-continuation.ts
|
|
47058
|
+
var LOW_SCORE_CONTINUATION_FLOOR_DEFAULT = .35;
|
|
47059
|
+
var LOW_SCORE_CONTINUATION_CLASSES = new Set(["person", "animal"]);
|
|
47060
|
+
/** `[x1, y1, x2, y2]` (the discard trail's tuple) → the tracker's rectangle. */
|
|
47061
|
+
function toRect(bbox) {
|
|
47062
|
+
const [x1, y1, x2, y2] = bbox;
|
|
47063
|
+
return {
|
|
47064
|
+
x: x1,
|
|
47065
|
+
y: y1,
|
|
47066
|
+
w: x2 - x1,
|
|
47067
|
+
h: y2 - y1
|
|
47068
|
+
};
|
|
47069
|
+
}
|
|
47070
|
+
/**
|
|
47071
|
+
* The continuation candidates of one frame. `floor === null` means the tier is
|
|
47072
|
+
* off for this camera and nothing is selected.
|
|
47073
|
+
*/
|
|
47074
|
+
function selectLowScoreContinuations(discarded, floor) {
|
|
47075
|
+
if (floor === null || discarded === void 0) return [];
|
|
47076
|
+
const out = [];
|
|
47077
|
+
for (const d of discarded) {
|
|
47078
|
+
if (d.reason !== "below-threshold") continue;
|
|
47079
|
+
if (d.score < floor) continue;
|
|
47080
|
+
if (!LOW_SCORE_CONTINUATION_CLASSES.has(d.macroClass)) continue;
|
|
47081
|
+
const bbox = toRect(d.bbox);
|
|
47082
|
+
if (bbox.w <= 0 || bbox.h <= 0) continue;
|
|
47083
|
+
out.push({
|
|
47084
|
+
class: d.macroClass,
|
|
47085
|
+
originalClass: d.macroClass,
|
|
47086
|
+
score: d.score,
|
|
47087
|
+
bbox
|
|
47088
|
+
});
|
|
47089
|
+
}
|
|
47090
|
+
return out;
|
|
47091
|
+
}
|
|
47092
|
+
//#endregion
|
|
47057
47093
|
//#region src/pipeline-analytics/pipeline/track-id.ts
|
|
47058
47094
|
/**
|
|
47059
47095
|
* A track's identity: short, random, and mintable inside a frame.
|
|
@@ -47202,6 +47238,30 @@ function reacquireReachPx(bbox, elapsedMs) {
|
|
|
47202
47238
|
const elapsed = Math.max(0, elapsedMs);
|
|
47203
47239
|
return clampRange(diag * REACH_DIAGONALS_PER_SEC * (elapsed / 1e3), diag * REACH_MIN_DIAGONALS, diag * REACH_MAX_DIAGONALS);
|
|
47204
47240
|
}
|
|
47241
|
+
/**
|
|
47242
|
+
* Resurrection reach, in box DIAGONALS per second of gap (D665).
|
|
47243
|
+
*
|
|
47244
|
+
* A corpse is reached only after `maxMissedMs` (4 s) without a box, so the
|
|
47245
|
+
* rescue rate above — which is at its 1.5-diagonal ceiling by 1 s — says
|
|
47246
|
+
* nothing useful here. A subject the detector has not boxed for four seconds
|
|
47247
|
+
* is, on this fleet's evidence, one that stood still or stood half-hidden,
|
|
47248
|
+
* not one striding across the frame (a striding person is detected): 0.25
|
|
47249
|
+
* diagonals per second is a slow walk for a mid-field person and a pause for
|
|
47250
|
+
* a near one. At the 4 s floor of the graveyard band it gives 1.0 diagonal;
|
|
47251
|
+
* camera 590's refused returns sat at 1.05–1.82× the old flat 0.75 reach,
|
|
47252
|
+
* i.e. 0.79–1.37 diagonals, which this covers from about 5.5 s of gap.
|
|
47253
|
+
*/
|
|
47254
|
+
var RESURRECTION_REACH_DIAGONALS_PER_SEC = .25;
|
|
47255
|
+
/** Never tighter than the flat `rescueCentroidFactor` default it replaces. */
|
|
47256
|
+
var RESURRECTION_REACH_MIN_DIAGONALS = .75;
|
|
47257
|
+
/**
|
|
47258
|
+
* How far from a corpse's last box a detection may sit and still resurrect it
|
|
47259
|
+
* on the proximity clause, for a corpse last seen `gapMs` ago.
|
|
47260
|
+
*/
|
|
47261
|
+
function resurrectionReachPx(bbox, gapMs) {
|
|
47262
|
+
const diag = Math.hypot(bbox.w, bbox.h);
|
|
47263
|
+
return clampRange(diag * RESURRECTION_REACH_DIAGONALS_PER_SEC * (Math.max(0, gapMs) / 1e3), diag * RESURRECTION_REACH_MIN_DIAGONALS, diag * 2);
|
|
47264
|
+
}
|
|
47205
47265
|
//#endregion
|
|
47206
47266
|
//#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
|
|
47207
47267
|
/**
|
|
@@ -47350,7 +47410,10 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
47350
47410
|
byteTrackLowIouThreshold: .2,
|
|
47351
47411
|
minSpawnAreaEnabled: true,
|
|
47352
47412
|
classSpawnAreaFracs: { person: 5e-4 },
|
|
47353
|
-
classGroupAssoc: true
|
|
47413
|
+
classGroupAssoc: true,
|
|
47414
|
+
lowScoreContinuationEnabled: true,
|
|
47415
|
+
lowScoreContinuationMaxMs: 8e3,
|
|
47416
|
+
resurrectionTimeScaled: true
|
|
47354
47417
|
};
|
|
47355
47418
|
/** Macro-classes the duplicate resolver may collapse across (people in
|
|
47356
47419
|
* non-upright poses misdetected as animals). Any other cross-class pair is
|
|
@@ -47358,6 +47421,15 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
47358
47421
|
var PERSON_CLASS = "person";
|
|
47359
47422
|
var ANIMAL_CLASS = "animal";
|
|
47360
47423
|
var MAX_PATH_LENGTH = 300;
|
|
47424
|
+
function emptyLowScoreStats() {
|
|
47425
|
+
return {
|
|
47426
|
+
offered: 0,
|
|
47427
|
+
continued: 0,
|
|
47428
|
+
resumed: 0,
|
|
47429
|
+
sustainCapDrops: 0,
|
|
47430
|
+
shadowed: 0
|
|
47431
|
+
};
|
|
47432
|
+
}
|
|
47361
47433
|
/** No stationary-held detections on this frame — the default for every caller
|
|
47362
47434
|
* that has no gate (replay, tests, the non-pipeline planes). */
|
|
47363
47435
|
var EMPTY_INDEX_SET = /* @__PURE__ */ new Set();
|
|
@@ -47429,6 +47501,8 @@ var SortTracker = class SortTracker {
|
|
|
47429
47501
|
trackLossSampler = new TrackLossSampler();
|
|
47430
47502
|
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
47431
47503
|
spawnBesideObserver = null;
|
|
47504
|
+
/** Running counts behind {@link drainLowScoreStats} (D665). */
|
|
47505
|
+
lowScoreStats = emptyLowScoreStats();
|
|
47432
47506
|
constructor(config = {}) {
|
|
47433
47507
|
this.config = {
|
|
47434
47508
|
...DEFAULT_TRACKER_CONFIG,
|
|
@@ -47489,7 +47563,7 @@ var SortTracker = class SortTracker {
|
|
|
47489
47563
|
if (SortTracker.spawnBesideFamily(track.class) !== family) continue;
|
|
47490
47564
|
const tc = bboxCentroid(track.bbox);
|
|
47491
47565
|
const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
|
|
47492
|
-
const reach = this.reacquireReach(track, timestamp,
|
|
47566
|
+
const reach = this.reacquireReach(track, timestamp, "rescue");
|
|
47493
47567
|
if (dist > reach * 2) continue;
|
|
47494
47568
|
if (dist < bestDist) {
|
|
47495
47569
|
best = track;
|
|
@@ -47536,7 +47610,7 @@ var SortTracker = class SortTracker {
|
|
|
47536
47610
|
explainDecline(track, det, detections, frameArea, timestamp, centroidDist) {
|
|
47537
47611
|
if (track.class !== det.class) return det.nativeRecovery !== void 0 ? "native-measured-class" : "cross-class";
|
|
47538
47612
|
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "already-claimed";
|
|
47539
|
-
if (centroidDist <= this.reacquireReach(track, timestamp,
|
|
47613
|
+
if (centroidDist <= this.reacquireReach(track, timestamp, "rescue")) {
|
|
47540
47614
|
if (!this.isPlausibleBirth(det, frameArea)) return "already-claimed";
|
|
47541
47615
|
if (!this.config.reacquireTimeScaled) return "plausible-birth-veto";
|
|
47542
47616
|
return this.isUnambiguousReach(track, det, detections) ? "already-claimed" : "crowd-guard";
|
|
@@ -47991,34 +48065,38 @@ var SortTracker = class SortTracker {
|
|
|
47991
48065
|
* this gate crossed the group, and it is where a `person` at 0.86 took over
|
|
47992
48066
|
* an animal's id (camera 615, 17:11:01).
|
|
47993
48067
|
*/
|
|
47994
|
-
looseMatch(track, det, timestamp,
|
|
48068
|
+
looseMatch(track, det, timestamp, pass = "rescue") {
|
|
47995
48069
|
if (track.class !== det.class) return null;
|
|
47996
48070
|
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
|
|
47997
48071
|
const tc = bboxCentroid(track.bbox);
|
|
47998
48072
|
const dc = bboxCentroid(det.bbox);
|
|
47999
|
-
if (Math.hypot(tc.x - dc.x, tc.y - dc.y) <= this.reacquireReach(track, timestamp,
|
|
48073
|
+
if (Math.hypot(tc.x - dc.x, tc.y - dc.y) <= this.reacquireReach(track, timestamp, pass)) return "centroid";
|
|
48000
48074
|
if (growthAdjacentMatch(track.bbox, det.bbox, this.growthContext(track, timestamp))) return "growth";
|
|
48001
48075
|
return null;
|
|
48002
48076
|
}
|
|
48003
48077
|
/**
|
|
48004
48078
|
* The proximity budget the `centroid` clause is measured against.
|
|
48005
48079
|
*
|
|
48006
|
-
* On the RESCUE pass
|
|
48007
|
-
*
|
|
48008
|
-
*
|
|
48009
|
-
*
|
|
48010
|
-
*
|
|
48011
|
-
* On the RESURRECTION pass it
|
|
48012
|
-
*
|
|
48013
|
-
*
|
|
48014
|
-
*
|
|
48015
|
-
*
|
|
48016
|
-
*
|
|
48017
|
-
*
|
|
48018
|
-
|
|
48019
|
-
|
|
48080
|
+
* On the RESCUE pass it is {@link reacquireReachPx} — a multiple of the
|
|
48081
|
+
* track's diagonal that grows with the elapsed gap and stops at a ceiling,
|
|
48082
|
+
* because a live coasting track's whereabouts genuinely become less certain
|
|
48083
|
+
* the longer it has gone unseen (D399).
|
|
48084
|
+
*
|
|
48085
|
+
* On the RESURRECTION pass it is {@link resurrectionReachPx} (D665): the same
|
|
48086
|
+
* idea on the graveyard's own time scale — 0.25 diagonals per second of gap,
|
|
48087
|
+
* never below the flat 0.75 it replaces, never above 2. Until D665 this pass
|
|
48088
|
+
* kept the flat `rescueCentroidFactor × diagonal` on the argument that it
|
|
48089
|
+
* exists for "a flickering stationary object", and a stationary subject does
|
|
48090
|
+
* not travel further because it blinked for longer. True of the object it
|
|
48091
|
+
* was written for; not of the pass as it runs: on camera 590 (2026-09-27)
|
|
48092
|
+
* 38% of all re-splits were PEOPLE returning after 4–12 s, further than the
|
|
48093
|
+
* flat gate, with frames flowing the whole time. `resurrectionTimeScaled`
|
|
48094
|
+
* off restores the flat gate.
|
|
48095
|
+
*/
|
|
48096
|
+
reacquireReach(track, timestamp, pass) {
|
|
48020
48097
|
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
48021
|
-
if (
|
|
48098
|
+
if (pass === "resurrection") return this.config.resurrectionTimeScaled ? resurrectionReachPx(track.bbox, timestamp - track.lastSeen) : this.config.rescueCentroidFactor * diag;
|
|
48099
|
+
if (!this.config.reacquireTimeScaled) return this.config.rescueCentroidFactor * diag;
|
|
48022
48100
|
return reacquireReachPx(track.bbox, timestamp - track.lastSeen);
|
|
48023
48101
|
}
|
|
48024
48102
|
/**
|
|
@@ -48068,6 +48146,23 @@ var SortTracker = class SortTracker {
|
|
|
48068
48146
|
nearestPx: nearest
|
|
48069
48147
|
};
|
|
48070
48148
|
}
|
|
48149
|
+
/**
|
|
48150
|
+
* The resurrection counterpart of {@link isUnambiguousReach}: no other
|
|
48151
|
+
* same-class subject is live or resurrectable beside `corpse`
|
|
48152
|
+
* ({@link sameClassPeerCount} over the SURVIVING set, which is what the crowd
|
|
48153
|
+
* guard counts), and `det` is the only box of that class on the frame —
|
|
48154
|
+
* the sub-floor `lowScore` boxes included.
|
|
48155
|
+
*/
|
|
48156
|
+
isSoleSubjectResurrection(corpse, det, detections, surviving, lowScore) {
|
|
48157
|
+
if (this.sameClassPeerCount(corpse, surviving) > 0) return false;
|
|
48158
|
+
for (const other of detections) if (other !== det && other.class === corpse.class) return false;
|
|
48159
|
+
for (const other of lowScore) {
|
|
48160
|
+
if (other.class !== corpse.class) continue;
|
|
48161
|
+
if (iou$2(other.bbox, det.bbox) >= this.config.byteTrackLowIouThreshold) continue;
|
|
48162
|
+
return false;
|
|
48163
|
+
}
|
|
48164
|
+
return true;
|
|
48165
|
+
}
|
|
48071
48166
|
isUnambiguousReach(track, det, detections) {
|
|
48072
48167
|
if (this.sameClassPeerCount(track, this.tracks) > 0) return false;
|
|
48073
48168
|
for (const other of detections) {
|
|
@@ -48222,6 +48317,64 @@ var SortTracker = class SortTracker {
|
|
|
48222
48317
|
used.add(pair.detIdx);
|
|
48223
48318
|
}
|
|
48224
48319
|
}
|
|
48320
|
+
/** A CONFIDENT box fed `track` (match, resurrection): the emitted box
|
|
48321
|
+
* follows it again, and a hold that ends this way is an id the tier kept. */
|
|
48322
|
+
markConfident(track, bbox, timestamp) {
|
|
48323
|
+
track.confidentBbox = bbox;
|
|
48324
|
+
track.confidentSeenAt = timestamp;
|
|
48325
|
+
if (track.lowScoreHeld) {
|
|
48326
|
+
track.lowScoreHeld = false;
|
|
48327
|
+
this.lowScoreStats.resumed++;
|
|
48328
|
+
}
|
|
48329
|
+
}
|
|
48330
|
+
/**
|
|
48331
|
+
* The low-score continuation stage (D665): greedy, highest IoU first, of the
|
|
48332
|
+
* sub-floor boxes against the CONFIRMED tracks nothing claimed this frame,
|
|
48333
|
+
* same class, on the predicted box at `byteTrackLowIouThreshold`.
|
|
48334
|
+
*
|
|
48335
|
+
* A bound box moves the track's association state only — `bbox`, `lastSeen`,
|
|
48336
|
+
* `age` — so the next confident box meets the subject where it is. Not
|
|
48337
|
+
* `hits` (it confirms nothing), not `score`, `class`, the vote, the path or
|
|
48338
|
+
* the velocity (it describes nothing downstream may act on). A track past
|
|
48339
|
+
* `lowScoreContinuationMaxMs` since its last confident box is not offered.
|
|
48340
|
+
*/
|
|
48341
|
+
continueOnLowScore(established, lowScore, timestamp, matchedTracks, matched) {
|
|
48342
|
+
const claimed = [...matched.values()].map((d) => d.bbox);
|
|
48343
|
+
const candidates = lowScore.filter((det) => {
|
|
48344
|
+
const shadow = claimed.some((b) => iou$2(b, det.bbox) >= this.config.byteTrackLowIouThreshold);
|
|
48345
|
+
if (shadow) this.lowScoreStats.shadowed++;
|
|
48346
|
+
return !shadow;
|
|
48347
|
+
});
|
|
48348
|
+
const pairs = [];
|
|
48349
|
+
for (const track of established) {
|
|
48350
|
+
if (matchedTracks.has(track)) continue;
|
|
48351
|
+
if (timestamp - track.confidentSeenAt > this.config.lowScoreContinuationMaxMs) continue;
|
|
48352
|
+
const pbox = this.predicted(track, timestamp);
|
|
48353
|
+
for (const det of candidates) {
|
|
48354
|
+
if (det.class !== track.class) continue;
|
|
48355
|
+
const score = iou$2(pbox, det.bbox);
|
|
48356
|
+
if (score >= this.config.byteTrackLowIouThreshold) pairs.push({
|
|
48357
|
+
track,
|
|
48358
|
+
det,
|
|
48359
|
+
score
|
|
48360
|
+
});
|
|
48361
|
+
}
|
|
48362
|
+
}
|
|
48363
|
+
pairs.sort((a, b) => b.score - a.score);
|
|
48364
|
+
const usedTracks = /* @__PURE__ */ new Set();
|
|
48365
|
+
const usedDets = /* @__PURE__ */ new Set();
|
|
48366
|
+
for (const pair of pairs) {
|
|
48367
|
+
if (usedTracks.has(pair.track) || usedDets.has(pair.det)) continue;
|
|
48368
|
+
usedTracks.add(pair.track);
|
|
48369
|
+
usedDets.add(pair.det);
|
|
48370
|
+
pair.track.bbox = pair.det.bbox;
|
|
48371
|
+
pair.track.lastSeen = timestamp;
|
|
48372
|
+
pair.track.lastSeenFrameSeq = this.frameSeq;
|
|
48373
|
+
pair.track.age = 0;
|
|
48374
|
+
pair.track.lowScoreHeld = true;
|
|
48375
|
+
this.lowScoreStats.continued++;
|
|
48376
|
+
}
|
|
48377
|
+
}
|
|
48225
48378
|
update(detections, timestamp, frameContext) {
|
|
48226
48379
|
this.frameSeq++;
|
|
48227
48380
|
const frameArea = frameContext ? frameContext.frameWidth * frameContext.frameHeight : 0;
|
|
@@ -48279,6 +48432,9 @@ var SortTracker = class SortTracker {
|
|
|
48279
48432
|
this.rescueAssociate(established, PROXIMITY, detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
48280
48433
|
this.rescueAssociate(provisional, new Set([...OVERLAP, ...PROXIMITY]), detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
48281
48434
|
if (this.config.byteTrackEnabled && lowIdxs.length > 0) for (const tier of [established, provisional]) this.greedyAssociate(tier, detections, lowIdxs, this.config.byteTrackLowIouThreshold, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
48435
|
+
const lowScore = frameContext?.lowScore ?? [];
|
|
48436
|
+
this.lowScoreStats.offered += lowScore.length;
|
|
48437
|
+
if (this.config.lowScoreContinuationEnabled && lowScore.length > 0) this.continueOnLowScore(established, lowScore, timestamp, matchedTracks, matched);
|
|
48282
48438
|
for (const [track, det] of matched) {
|
|
48283
48439
|
if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, {
|
|
48284
48440
|
against: track,
|
|
@@ -48309,6 +48465,7 @@ var SortTracker = class SortTracker {
|
|
|
48309
48465
|
track.hits++;
|
|
48310
48466
|
track.lastSeen = timestamp;
|
|
48311
48467
|
track.lastSeenFrameSeq = this.frameSeq;
|
|
48468
|
+
this.markConfident(track, det.bbox, timestamp);
|
|
48312
48469
|
if (dtMs > 0) {
|
|
48313
48470
|
track.velocity = {
|
|
48314
48471
|
dx: (newCenter.x - prevCenter.x) / dtMs,
|
|
@@ -48333,10 +48490,15 @@ var SortTracker = class SortTracker {
|
|
|
48333
48490
|
}
|
|
48334
48491
|
const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track, timestamp), ob.bbox) >= this.config.occlusionContainment);
|
|
48335
48492
|
track.age++;
|
|
48336
|
-
|
|
48493
|
+
const coastMs = timestamp - track.lastSeen;
|
|
48494
|
+
const limitMs = occluded ? this.config.occlusionMaxMissedMs : this.config.maxMissedMs;
|
|
48495
|
+
const heldTooLong = track.lowScoreHeld && timestamp - track.confidentSeenAt > this.config.lowScoreContinuationMaxMs;
|
|
48496
|
+
if (heldTooLong) this.lowScoreStats.sustainCapDrops++;
|
|
48497
|
+
if (coastMs > limitMs || heldTooLong) {
|
|
48337
48498
|
track.lost = true;
|
|
48338
48499
|
track.lostAt = timestamp;
|
|
48339
48500
|
track.resurrectable = true;
|
|
48501
|
+
track.lowScoreHeld = false;
|
|
48340
48502
|
this.lostTracks.push(track);
|
|
48341
48503
|
} else surviving.push(track);
|
|
48342
48504
|
}
|
|
@@ -48347,15 +48509,17 @@ var SortTracker = class SortTracker {
|
|
|
48347
48509
|
let bestScore = -1;
|
|
48348
48510
|
let bestGate = "iou";
|
|
48349
48511
|
let bestPeers = 0;
|
|
48512
|
+
let bestVetoLifted = false;
|
|
48350
48513
|
let refused;
|
|
48351
48514
|
let refusedGate = "iou";
|
|
48352
48515
|
let refusedScore = -1;
|
|
48353
48516
|
for (const lost of this.lostTracks) {
|
|
48354
48517
|
if (!lost.resurrectable) continue;
|
|
48355
48518
|
if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
|
|
48356
|
-
const gate = this.looseMatch(lost, det, timestamp,
|
|
48519
|
+
const gate = this.looseMatch(lost, det, timestamp, "resurrection");
|
|
48357
48520
|
if (gate === null) continue;
|
|
48358
|
-
|
|
48521
|
+
const vetoLifted = gate === "centroid" && this.isPlausibleBirth(det, frameArea);
|
|
48522
|
+
if (vetoLifted && !(this.config.resurrectionTimeScaled && this.isSoleSubjectResurrection(lost, det, detections, surviving, lowScore))) continue;
|
|
48359
48523
|
const score = iou$2(lost.bbox, det.bbox);
|
|
48360
48524
|
const peers = this.sameClassPeerCount(lost, surviving);
|
|
48361
48525
|
if (this.config.resurrectionCrowdGuard && peers > 0 && score < this.config.resurrectionCrowdedIouThreshold) {
|
|
@@ -48371,13 +48535,14 @@ var SortTracker = class SortTracker {
|
|
|
48371
48535
|
bestScore = score;
|
|
48372
48536
|
bestGate = gate;
|
|
48373
48537
|
bestPeers = peers;
|
|
48538
|
+
bestVetoLifted = vetoLifted;
|
|
48374
48539
|
}
|
|
48375
48540
|
}
|
|
48376
48541
|
if (!best) {
|
|
48377
48542
|
if (refused !== void 0) this.reportBind("resurrection-refused", refusedGate, refused, det, timestamp);
|
|
48378
48543
|
continue;
|
|
48379
48544
|
}
|
|
48380
|
-
resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : "sole-subject");
|
|
48545
|
+
resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : bestVetoLifted ? "sole-subject-veto-lifted" : "sole-subject");
|
|
48381
48546
|
this.reportBind("resurrection", bestGate, best, det, timestamp);
|
|
48382
48547
|
this.lostTracks = this.lostTracks.filter((t) => t !== best);
|
|
48383
48548
|
best.bbox = det.bbox;
|
|
@@ -48391,6 +48556,7 @@ var SortTracker = class SortTracker {
|
|
|
48391
48556
|
best.lost = false;
|
|
48392
48557
|
best.lastSeen = timestamp;
|
|
48393
48558
|
best.lastSeenFrameSeq = this.frameSeq;
|
|
48559
|
+
this.markConfident(best, det.bbox, timestamp);
|
|
48394
48560
|
best.velocity = {
|
|
48395
48561
|
dx: 0,
|
|
48396
48562
|
dy: 0
|
|
@@ -48467,7 +48633,10 @@ var SortTracker = class SortTracker {
|
|
|
48467
48633
|
classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]]),
|
|
48468
48634
|
emitted: false,
|
|
48469
48635
|
lossReported: false,
|
|
48470
|
-
lastNativeRecovery: det.nativeRecovery
|
|
48636
|
+
lastNativeRecovery: det.nativeRecovery,
|
|
48637
|
+
confidentBbox: det.bbox,
|
|
48638
|
+
confidentSeenAt: timestamp,
|
|
48639
|
+
lowScoreHeld: false
|
|
48471
48640
|
});
|
|
48472
48641
|
}
|
|
48473
48642
|
this.tracks = surviving;
|
|
@@ -48475,11 +48644,12 @@ var SortTracker = class SortTracker {
|
|
|
48475
48644
|
return this.tracks.filter((t) => this.isConfirmedForEmit(t)).map((t) => {
|
|
48476
48645
|
t.emitted = true;
|
|
48477
48646
|
const scene = resurrectedScene.get(t);
|
|
48647
|
+
const held = t.lowScoreHeld;
|
|
48478
48648
|
return {
|
|
48479
48649
|
class: this.resolveReportedClass(t),
|
|
48480
48650
|
originalClass: t.originalClass,
|
|
48481
48651
|
score: t.score,
|
|
48482
|
-
bbox: t.bbox,
|
|
48652
|
+
bbox: held ? t.confidentBbox : t.bbox,
|
|
48483
48653
|
trackId: t.id,
|
|
48484
48654
|
trackAge: t.hits,
|
|
48485
48655
|
velocity: {
|
|
@@ -48487,8 +48657,9 @@ var SortTracker = class SortTracker {
|
|
|
48487
48657
|
dy: t.velocity.dy * t.emitIntervalMs
|
|
48488
48658
|
},
|
|
48489
48659
|
path: [...t.path],
|
|
48490
|
-
matchedThisFrame: t.lastSeen === timestamp,
|
|
48491
|
-
...scene !== void 0 ? { resurrectionScene: scene } : {}
|
|
48660
|
+
matchedThisFrame: t.lastSeen === timestamp && !held,
|
|
48661
|
+
...scene !== void 0 ? { resurrectionScene: scene } : {},
|
|
48662
|
+
...held ? { heldByLowScore: true } : {}
|
|
48492
48663
|
};
|
|
48493
48664
|
});
|
|
48494
48665
|
}
|
|
@@ -48502,6 +48673,16 @@ var SortTracker = class SortTracker {
|
|
|
48502
48673
|
this.tracks = this.tracks.filter((t) => t.id !== trackId);
|
|
48503
48674
|
this.lostTracks = this.lostTracks.filter((t) => t.id !== trackId);
|
|
48504
48675
|
}
|
|
48676
|
+
/**
|
|
48677
|
+
* What the low-score continuation stage did since the last call, and reset
|
|
48678
|
+
* (D665). The tracker has no `deviceId` and no logger; its owner reads this
|
|
48679
|
+
* on its own cadence and logs it per camera.
|
|
48680
|
+
*/
|
|
48681
|
+
drainLowScoreStats() {
|
|
48682
|
+
const out = this.lowScoreStats;
|
|
48683
|
+
this.lowScoreStats = emptyLowScoreStats();
|
|
48684
|
+
return out;
|
|
48685
|
+
}
|
|
48505
48686
|
getActiveTracks() {
|
|
48506
48687
|
return this.tracks;
|
|
48507
48688
|
}
|
|
@@ -48876,6 +49057,8 @@ var FrameProcessor = class {
|
|
|
48876
49057
|
detectionRules;
|
|
48877
49058
|
/** See {@link setZoneMembershipMinOverlap}. 0 = any positive overlap. */
|
|
48878
49059
|
zoneMembershipMinOverlap;
|
|
49060
|
+
/** See {@link setLowScoreContinuationFloor}. `null` = tier off. */
|
|
49061
|
+
lowScoreFloor = LOW_SCORE_CONTINUATION_FLOOR_DEFAULT;
|
|
48879
49062
|
/** See {@link getLastZoneOverlaps}. */
|
|
48880
49063
|
lastZoneOverlaps;
|
|
48881
49064
|
/** See {@link getLastZoneRejections}. */
|
|
@@ -48988,6 +49171,18 @@ var FrameProcessor = class {
|
|
|
48988
49171
|
* failure mode this whole area keeps producing. {@link lastZoneOverlaps}
|
|
48989
49172
|
* exists so the distribution can be read before a number is picked.
|
|
48990
49173
|
*/
|
|
49174
|
+
/**
|
|
49175
|
+
* The score at or above which a box the operator's class floor dropped is
|
|
49176
|
+
* handed to the tracker as low-score continuation evidence (D665); `null`
|
|
49177
|
+
* turns the tier off for this camera. Pushed per frame, like the zones.
|
|
49178
|
+
*/
|
|
49179
|
+
setLowScoreContinuationFloor(floor) {
|
|
49180
|
+
this.lowScoreFloor = floor !== null && Number.isFinite(floor) && floor >= 0 && floor <= 1 ? floor : null;
|
|
49181
|
+
}
|
|
49182
|
+
/** The tracker's low-score continuation counts since the last call (D665). */
|
|
49183
|
+
drainLowScoreStats() {
|
|
49184
|
+
return this.tracker.drainLowScoreStats();
|
|
49185
|
+
}
|
|
48991
49186
|
setZoneMembershipMinOverlap(minOverlap) {
|
|
48992
49187
|
this.zoneMembershipMinOverlap = Number.isFinite(minOverlap) && minOverlap >= 0 && minOverlap <= 1 ? minOverlap : 0;
|
|
48993
49188
|
}
|
|
@@ -49380,10 +49575,16 @@ var FrameProcessor = class {
|
|
|
49380
49575
|
}
|
|
49381
49576
|
trackerInput.push(det);
|
|
49382
49577
|
}
|
|
49578
|
+
const lowCandidates = selectLowScoreContinuations(frame.discarded, this.lowScoreFloor);
|
|
49579
|
+
const lowScore = lowCandidates.length === 0 ? lowCandidates : this.zoneEngine.filterDetections(lowCandidates.map((d) => ({
|
|
49580
|
+
...d.bbox,
|
|
49581
|
+
detection: d
|
|
49582
|
+
})), this.zones, this.detectionRules, frameWidth, frameHeight, (fd) => fd.detection.class).passed.map((fd) => fd.detection);
|
|
49383
49583
|
const trackedDetections = this.tracker.update(trackerInput, timestamp, {
|
|
49384
49584
|
frameWidth,
|
|
49385
49585
|
frameHeight,
|
|
49386
|
-
continuationOnly
|
|
49586
|
+
continuationOnly,
|
|
49587
|
+
lowScore
|
|
49387
49588
|
});
|
|
49388
49589
|
const stationaryContinued = continuableBoxes.size === 0 ? 0 : trackedDetections.filter((t) => t.matchedThisFrame !== false && continuableBoxes.has(t.bbox)).length;
|
|
49389
49590
|
const riderFolds = foldByVehicleBbox.size === 0 ? EMPTY_RIDER_FOLDS : [...foldByVehicleBbox].map(([bbox, fold]) => {
|
|
@@ -49625,6 +49826,19 @@ var FrameProcessor = class {
|
|
|
49625
49826
|
}
|
|
49626
49827
|
};
|
|
49627
49828
|
//#endregion
|
|
49829
|
+
//#region src/pipeline-analytics/face-identity-retraction.ts
|
|
49830
|
+
/** The recognizer's step id — the attribution a face identity label carries. */
|
|
49831
|
+
var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
|
|
49832
|
+
async function retractFaceIdentityLabel(stores, trackId) {
|
|
49833
|
+
const meta = stores.trackStore.getActiveByTrack(trackId)?.subLabelMeta;
|
|
49834
|
+
if (meta === void 0) return "no-label";
|
|
49835
|
+
if (meta.stepId !== "face-recognizer") return "not-face-recognizer";
|
|
49836
|
+
await stores.eventStore.clearLabelForTrack(trackId, 2);
|
|
49837
|
+
await stores.trackStore.clearLabel(trackId, 2);
|
|
49838
|
+
await stores.recomputeImportance?.(trackId);
|
|
49839
|
+
return "retracted";
|
|
49840
|
+
}
|
|
49841
|
+
//#endregion
|
|
49628
49842
|
//#region src/pipeline-analytics/pipeline/track-provenance.ts
|
|
49629
49843
|
function trackProvenanceOf(frame) {
|
|
49630
49844
|
if (!(frame.width > 0 && frame.height > 0)) return void 0;
|
|
@@ -63280,8 +63494,6 @@ function faceSizeScore(detectionFrameShortSidePx) {
|
|
|
63280
63494
|
}
|
|
63281
63495
|
//#endregion
|
|
63282
63496
|
//#region src/pipeline-analytics/face-recognizer.ts
|
|
63283
|
-
/** Attribution of an identity resolved by the in-process ArcFace matcher. */
|
|
63284
|
-
var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
|
|
63285
63497
|
/** At most one "dropping imageless track" log per this interval, per recognizer. */
|
|
63286
63498
|
var FACE_IMAGELESS_LOG_THROTTLE_MS = 6e4;
|
|
63287
63499
|
/** At most one 'embedding magnitude' sample line per device per this interval. */
|
|
@@ -63307,6 +63519,9 @@ var FaceRecognizer = class {
|
|
|
63307
63519
|
gallery = [];
|
|
63308
63520
|
names = /* @__PURE__ */ new Map();
|
|
63309
63521
|
aggregates = /* @__PURE__ */ new Map();
|
|
63522
|
+
/** The aggregate a veto-lifted quarantine set aside, restored if the first
|
|
63523
|
+
* face proves the same person (D665). */
|
|
63524
|
+
stashedIdentity = /* @__PURE__ */ new Map();
|
|
63310
63525
|
bestFace = /* @__PURE__ */ new Map();
|
|
63311
63526
|
/** Frame instant of each live track's last source park (D644). */
|
|
63312
63527
|
lastSourceParkAt = /* @__PURE__ */ new Map();
|
|
@@ -63498,9 +63713,35 @@ var FaceRecognizer = class {
|
|
|
63498
63713
|
* • the face becomes non-promotable: {@link onTrackEnd} will not write it to
|
|
63499
63714
|
* the gallery while the flag stands.
|
|
63500
63715
|
*
|
|
63501
|
-
* The
|
|
63502
|
-
|
|
63503
|
-
|
|
63716
|
+
* The identity AGGREGATE depends on the scene (D665):
|
|
63717
|
+
*
|
|
63718
|
+
* • `multi-subject` — a bind that needed real overlap (IoU ≥ 0.3) with the
|
|
63719
|
+
* corpse's last box. The aggregate is LEFT ALONE: the next face either
|
|
63720
|
+
* confirms (same person, name intact at no cost — the common case, a
|
|
63721
|
+
* seated person occluded for a few seconds) or is rejected by cosine, and
|
|
63722
|
+
* a rejection is a PROVEN subject change: the aggregate is deleted and the
|
|
63723
|
+
* written identity label retracted then.
|
|
63724
|
+
* • `sole-subject-veto-lifted` — a bind on proximity alone. The aggregate is
|
|
63725
|
+
* STASHED and reset, and the written identity label retracted at once:
|
|
63726
|
+
* `updateTrackAggregate` keeps an assignment through every unmatched frame,
|
|
63727
|
+
* so without the reset a stranger who never matched anybody would carry
|
|
63728
|
+
* the name. The first face-bearing frame decides — `confirmed` restores the
|
|
63729
|
+
* stash and rewrites the label (the same person, back immediately);
|
|
63730
|
+
* `rejected`, or no face before the track ends, discards it.
|
|
63731
|
+
*/
|
|
63732
|
+
async quarantineHeldFace(deviceId, trackId, scene) {
|
|
63733
|
+
if (scene === "sole-subject-veto-lifted") {
|
|
63734
|
+
const aggregate = this.aggregates.get(trackId);
|
|
63735
|
+
if (aggregate?.assignedIdentityId != null) {
|
|
63736
|
+
const peak = this.bestMatchScore.get(trackId);
|
|
63737
|
+
this.stashedIdentity.set(trackId, {
|
|
63738
|
+
aggregate,
|
|
63739
|
+
...peak !== void 0 ? { bestMatchScore: peak } : {}
|
|
63740
|
+
});
|
|
63741
|
+
this.aggregates.delete(trackId);
|
|
63742
|
+
await this.retractIdentity(deviceId, trackId, scene, aggregate.assignedIdentityId);
|
|
63743
|
+
} else this.aggregates.delete(trackId);
|
|
63744
|
+
}
|
|
63504
63745
|
const held = this.bestFace.get(trackId);
|
|
63505
63746
|
if (held === void 0 || held.unverified === true) return;
|
|
63506
63747
|
const quarantined = {
|
|
@@ -63523,6 +63764,7 @@ var FaceRecognizer = class {
|
|
|
63523
63764
|
trackId
|
|
63524
63765
|
},
|
|
63525
63766
|
meta: {
|
|
63767
|
+
scene,
|
|
63526
63768
|
heldScore: held.score,
|
|
63527
63769
|
heldAt: held.timestamp,
|
|
63528
63770
|
strippedIdentityId: held.recognizedIdentityId ?? null,
|
|
@@ -63531,6 +63773,40 @@ var FaceRecognizer = class {
|
|
|
63531
63773
|
});
|
|
63532
63774
|
}
|
|
63533
63775
|
/**
|
|
63776
|
+
* Take the identity label back from the track and its events — only when the
|
|
63777
|
+
* face recognizer wrote it (`retractFaceIdentityLabel`). Stated either way.
|
|
63778
|
+
*/
|
|
63779
|
+
async retractIdentity(deviceId, trackId, reason, identityId) {
|
|
63780
|
+
const retract = this.deps.retractIdentityLabel;
|
|
63781
|
+
if (retract === void 0) return;
|
|
63782
|
+
try {
|
|
63783
|
+
const outcome = await retract(trackId);
|
|
63784
|
+
this.deps.logger.info("face: identity label retracted after an unverified re-attach", {
|
|
63785
|
+
tags: {
|
|
63786
|
+
deviceId,
|
|
63787
|
+
trackId
|
|
63788
|
+
},
|
|
63789
|
+
meta: {
|
|
63790
|
+
reason,
|
|
63791
|
+
identityId,
|
|
63792
|
+
outcome
|
|
63793
|
+
}
|
|
63794
|
+
});
|
|
63795
|
+
} catch (err) {
|
|
63796
|
+
this.deps.logger.warn("face: identity label retraction failed", {
|
|
63797
|
+
tags: {
|
|
63798
|
+
deviceId,
|
|
63799
|
+
trackId
|
|
63800
|
+
},
|
|
63801
|
+
meta: {
|
|
63802
|
+
reason,
|
|
63803
|
+
identityId,
|
|
63804
|
+
error: String(err)
|
|
63805
|
+
}
|
|
63806
|
+
});
|
|
63807
|
+
}
|
|
63808
|
+
}
|
|
63809
|
+
/**
|
|
63534
63810
|
* Ask a FRESH face whether the quarantined held face is still about the same
|
|
63535
63811
|
* person, at the same bar the gallery uses to say two vectors are one person
|
|
63536
63812
|
* (`similarityThreshold` — one knob over one band, D222).
|
|
@@ -63552,7 +63828,7 @@ var FaceRecognizer = class {
|
|
|
63552
63828
|
}
|
|
63553
63829
|
async processFrame(input) {
|
|
63554
63830
|
const { settings } = input;
|
|
63555
|
-
for (const t of input.tracked) if (t.resurrectionScene
|
|
63831
|
+
for (const t of input.tracked) if (t.resurrectionScene !== void 0 && t.resurrectionScene !== "sole-subject") await this.quarantineHeldFace(input.deviceId, t.trackId, t.resurrectionScene);
|
|
63556
63832
|
const candidates = input.tracked.filter((t) => Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && (t.nativeFaceShortSidePx ?? (t.faceBbox ? Math.min(t.faceBbox.w, t.faceBbox.h) : void 0) ?? Number.POSITIVE_INFINITY) >= settings.minFacePx);
|
|
63557
63833
|
if (candidates.length === 0) return;
|
|
63558
63834
|
this.deps.logger.debug("face: frame candidates", {
|
|
@@ -63619,11 +63895,26 @@ var FaceRecognizer = class {
|
|
|
63619
63895
|
embedding: c.embedding,
|
|
63620
63896
|
embeddingModelId: c.embeddingModelId
|
|
63621
63897
|
}, settings.similarityThreshold);
|
|
63622
|
-
|
|
63623
|
-
|
|
63624
|
-
|
|
63625
|
-
|
|
63626
|
-
|
|
63898
|
+
const stashed = this.stashedIdentity.get(c.trackId);
|
|
63899
|
+
this.stashedIdentity.delete(c.trackId);
|
|
63900
|
+
if (verdict === "confirmed") {
|
|
63901
|
+
this.bestFace.set(c.trackId, {
|
|
63902
|
+
...quarantined,
|
|
63903
|
+
unverified: false
|
|
63904
|
+
});
|
|
63905
|
+
const name = stashed?.aggregate.assignedIdentityId;
|
|
63906
|
+
if (stashed !== void 0 && name != null) {
|
|
63907
|
+
this.aggregates.set(c.trackId, stashed.aggregate);
|
|
63908
|
+
if (stashed.bestMatchScore !== void 0) this.bestMatchScore.set(c.trackId, stashed.bestMatchScore);
|
|
63909
|
+
labelWork.push({
|
|
63910
|
+
trackId: c.trackId,
|
|
63911
|
+
assignedIdentityId: name,
|
|
63912
|
+
matchScore: stashed.bestMatchScore ?? null
|
|
63913
|
+
});
|
|
63914
|
+
}
|
|
63915
|
+
} else if (verdict === "rejected") {
|
|
63916
|
+
const named = this.aggregates.get(c.trackId)?.assignedIdentityId;
|
|
63917
|
+
if (named != null) await this.retractIdentity(input.deviceId, c.trackId, "rejected", named);
|
|
63627
63918
|
this.bestFace.delete(c.trackId);
|
|
63628
63919
|
this.bestTracker.delete(c.trackId);
|
|
63629
63920
|
this.aggregates.delete(c.trackId);
|
|
@@ -63838,6 +64129,7 @@ var FaceRecognizer = class {
|
|
|
63838
64129
|
const bestMatchScore = this.bestMatchScore.get(trackId);
|
|
63839
64130
|
const bestSuggestion = this.bestSuggestion.get(trackId);
|
|
63840
64131
|
this.aggregates.delete(trackId);
|
|
64132
|
+
this.stashedIdentity.delete(trackId);
|
|
63841
64133
|
this.bestFace.delete(trackId);
|
|
63842
64134
|
this.bestTracker.delete(trackId);
|
|
63843
64135
|
this.bestMatchScore.delete(trackId);
|
|
@@ -67999,6 +68291,19 @@ function shouldSkipDropoutFrame(activeTrackCount, detectionCount, consecutiveSki
|
|
|
67999
68291
|
if (activeTrackCount <= 0) return false;
|
|
68000
68292
|
return consecutiveSkips < cfg.maxSkipFrames;
|
|
68001
68293
|
}
|
|
68294
|
+
/**
|
|
68295
|
+
* How much evidence a frame carries for the dropout decision: its detections,
|
|
68296
|
+
* plus the sub-floor boxes the tracker may continue a track on (D665).
|
|
68297
|
+
*
|
|
68298
|
+
* Until D665 the skip counted `frame.detections` only, so a frame whose sole
|
|
68299
|
+
* box was a person at 0.5 read as a detector glitch and never reached the
|
|
68300
|
+
* tracker — on camera 590 more than half the frames of a re-split gap were
|
|
68301
|
+
* dropout-skipped. A frame with a continuable box is not empty; `floor` is the
|
|
68302
|
+
* camera's low-score floor, `null` when the tier is off.
|
|
68303
|
+
*/
|
|
68304
|
+
function dropoutEvidenceCount(frame, floor) {
|
|
68305
|
+
return frame.detections.length + selectLowScoreContinuations(frame.discarded, floor).length;
|
|
68306
|
+
}
|
|
68002
68307
|
//#endregion
|
|
68003
68308
|
//#region src/pipeline-analytics/pipeline/key-event-query.ts
|
|
68004
68309
|
async function rankKeyEvents(candidates, options, peakLookup) {
|
|
@@ -82952,7 +83257,21 @@ var TrackingSettingsSchema = require_clip_model_registry.object({
|
|
|
82952
83257
|
* passes, only for a coarse box below `animalOverPersonMaxScore`, never on
|
|
82953
83258
|
* the rescue / resurrection passes. Reported label stays via the class
|
|
82954
83259
|
* vote. Off = exact per-frame class gating. */
|
|
82955
|
-
classGroupAssoc: require_clip_model_registry.boolean().default(true)
|
|
83260
|
+
classGroupAssoc: require_clip_model_registry.boolean().default(true),
|
|
83261
|
+
/** Hand the boxes the per-class confidence floor dropped to the tracker as
|
|
83262
|
+
* CONTINUATION-ONLY evidence (D665): they may keep a confirmed track alive,
|
|
83263
|
+
* never open one, never produce media, events or notifications. */
|
|
83264
|
+
lowScoreContinuationEnabled: require_clip_model_registry.boolean().default(true),
|
|
83265
|
+
/** Score at/above which a floor-dropped box counts as continuation evidence
|
|
83266
|
+
* (D665 — justified in `low-score-continuation.ts`). */
|
|
83267
|
+
lowScoreContinuationFloor: require_clip_model_registry.number().min(0).max(1).default(.35),
|
|
83268
|
+
/** Longest a track may be held by floor-dropped boxes alone, from its last
|
|
83269
|
+
* confident box (ms). */
|
|
83270
|
+
lowScoreContinuationMaxMs: require_clip_model_registry.number().min(0).default(8e3),
|
|
83271
|
+
/** Graveyard reach scaled by the gap, and the plausible-birth veto lifted for
|
|
83272
|
+
* a sole subject (D665). Off = the flat pre-D665 resurrection gate — the
|
|
83273
|
+
* per-camera rollback that needs no redeploy. */
|
|
83274
|
+
resurrectionTimeScaled: require_clip_model_registry.boolean().default(true)
|
|
82956
83275
|
});
|
|
82957
83276
|
var TRACKING_DEFAULTS = TrackingSettingsSchema.parse({});
|
|
82958
83277
|
/**
|
|
@@ -83007,7 +83326,11 @@ function resolveTrackingSettings(raw) {
|
|
|
83007
83326
|
minSpawnAreaFracPerson: s.minSpawnAreaFracPerson.catch(TRACKING_DEFAULTS.minSpawnAreaFracPerson).parse(raw.minSpawnAreaFracPerson),
|
|
83008
83327
|
minSpawnAreaFracAnimal: s.minSpawnAreaFracAnimal.catch(TRACKING_DEFAULTS.minSpawnAreaFracAnimal).parse(raw.minSpawnAreaFracAnimal),
|
|
83009
83328
|
minSpawnAreaFracVehicle: s.minSpawnAreaFracVehicle.catch(TRACKING_DEFAULTS.minSpawnAreaFracVehicle).parse(raw.minSpawnAreaFracVehicle),
|
|
83010
|
-
classGroupAssoc: s.classGroupAssoc.catch(TRACKING_DEFAULTS.classGroupAssoc).parse(raw.classGroupAssoc)
|
|
83329
|
+
classGroupAssoc: s.classGroupAssoc.catch(TRACKING_DEFAULTS.classGroupAssoc).parse(raw.classGroupAssoc),
|
|
83330
|
+
lowScoreContinuationEnabled: s.lowScoreContinuationEnabled.catch(TRACKING_DEFAULTS.lowScoreContinuationEnabled).parse(raw.lowScoreContinuationEnabled),
|
|
83331
|
+
lowScoreContinuationFloor: s.lowScoreContinuationFloor.catch(TRACKING_DEFAULTS.lowScoreContinuationFloor).parse(raw.lowScoreContinuationFloor),
|
|
83332
|
+
lowScoreContinuationMaxMs: s.lowScoreContinuationMaxMs.catch(TRACKING_DEFAULTS.lowScoreContinuationMaxMs).parse(raw.lowScoreContinuationMaxMs),
|
|
83333
|
+
resurrectionTimeScaled: s.resurrectionTimeScaled.catch(TRACKING_DEFAULTS.resurrectionTimeScaled).parse(raw.resurrectionTimeScaled)
|
|
83011
83334
|
};
|
|
83012
83335
|
}
|
|
83013
83336
|
//#endregion
|
|
@@ -83437,6 +83760,40 @@ function buildDetectionSettingsSections() {
|
|
|
83437
83760
|
step: .05,
|
|
83438
83761
|
default: TRACKING_DEFAULTS.byteTrackLowIouThreshold
|
|
83439
83762
|
},
|
|
83763
|
+
{
|
|
83764
|
+
type: "boolean",
|
|
83765
|
+
key: "lowScoreContinuationEnabled",
|
|
83766
|
+
label: "Keep tracks through low-confidence frames",
|
|
83767
|
+
description: "Boxes below the class confidence floor may keep an EXISTING track alive while the detector is unsure, so a person read at 0.5 for a few seconds keeps one id instead of splitting. They never start a track and never produce events, notifications or snapshots.",
|
|
83768
|
+
default: TRACKING_DEFAULTS.lowScoreContinuationEnabled
|
|
83769
|
+
},
|
|
83770
|
+
{
|
|
83771
|
+
type: "number",
|
|
83772
|
+
key: "lowScoreContinuationFloor",
|
|
83773
|
+
label: "Low-confidence continuation floor",
|
|
83774
|
+
description: "Score at or above which a box the class floor dropped still counts as evidence that a tracked subject is there. Only used with the option above.",
|
|
83775
|
+
min: 0,
|
|
83776
|
+
max: 1,
|
|
83777
|
+
step: .05,
|
|
83778
|
+
default: TRACKING_DEFAULTS.lowScoreContinuationFloor
|
|
83779
|
+
},
|
|
83780
|
+
{
|
|
83781
|
+
type: "number",
|
|
83782
|
+
key: "lowScoreContinuationMaxMs",
|
|
83783
|
+
label: "Low-confidence hold limit (ms)",
|
|
83784
|
+
description: "Longest a track may be kept alive by low-confidence boxes alone, counted from its last confident box.",
|
|
83785
|
+
min: 0,
|
|
83786
|
+
max: 3e4,
|
|
83787
|
+
step: 500,
|
|
83788
|
+
default: TRACKING_DEFAULTS.lowScoreContinuationMaxMs
|
|
83789
|
+
},
|
|
83790
|
+
{
|
|
83791
|
+
type: "boolean",
|
|
83792
|
+
key: "resurrectionTimeScaled",
|
|
83793
|
+
label: "Wider re-attach after a long gap",
|
|
83794
|
+
description: "Let a track that was lost for a few seconds be re-attached to a subject that reappears further away, the longer the gap (up to two box diagonals) — only when nobody else of that class is in view. A recognised face is re-checked after such a re-attach. Off = the fixed short re-attach distance.",
|
|
83795
|
+
default: TRACKING_DEFAULTS.resurrectionTimeScaled
|
|
83796
|
+
},
|
|
83440
83797
|
{
|
|
83441
83798
|
type: "boolean",
|
|
83442
83799
|
key: "minSpawnAreaEnabled",
|
|
@@ -87937,6 +88294,8 @@ function decodeEmbeddingBase64(base64) {
|
|
|
87937
88294
|
var PHANTOM_CELL_PX = 32;
|
|
87938
88295
|
/** How long a cell remembers its closes. */
|
|
87939
88296
|
var PHANTOM_CELL_WINDOW_MS = 360 * 6e4;
|
|
88297
|
+
/** Cadence of the per-camera low-score continuation line (D665). */
|
|
88298
|
+
var LOW_SCORE_REPORT_INTERVAL_MS = 6e4;
|
|
87940
88299
|
var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_model_registry.BaseAddon {
|
|
87941
88300
|
/** Recent SHORT+MOTIONLESS track closes per `<device>:<class>:<cell>` —
|
|
87942
88301
|
* see {@link noteShortMotionlessTrack}. Measurement only; each entry is
|
|
@@ -88604,6 +88963,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
88604
88963
|
packageBackground = null;
|
|
88605
88964
|
/** Consecutive detector-dropout frames skipped per (deviceId, source) key. */
|
|
88606
88965
|
dropoutSkipsByKey = /* @__PURE__ */ new Map();
|
|
88966
|
+
/** Last time each processor's low-score continuation counts were logged (D665). */
|
|
88967
|
+
lowScoreReportAtByKey = /* @__PURE__ */ new Map();
|
|
88607
88968
|
/** The per-track close-path orchestrator (S2, refactor spec 2026-07-22):
|
|
88608
88969
|
* owns the single-flight sweep latch + the FULL per-track close sequence
|
|
88609
88970
|
* (zero-media policy, lastFrame promotion, recognizer ends with the
|
|
@@ -90162,6 +90523,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
90162
90523
|
mediaStore: stores.mediaStore,
|
|
90163
90524
|
trackStore: stores.trackStore,
|
|
90164
90525
|
eventStore: stores.eventStore,
|
|
90526
|
+
retractIdentityLabel: (trackId) => retractFaceIdentityLabel({
|
|
90527
|
+
trackStore: stores.trackStore,
|
|
90528
|
+
eventStore: stores.eventStore,
|
|
90529
|
+
recomputeImportance: (id) => recomputeTrackImportance({
|
|
90530
|
+
trackStore: stores.trackStore,
|
|
90531
|
+
eventStore: stores.eventStore
|
|
90532
|
+
}, id)
|
|
90533
|
+
}, trackId),
|
|
90165
90534
|
captureCrop: (frameHandle, bbox, frameWidth, frameHeight, padding, ownerId, deviceId) => this.captureScheduler.request({
|
|
90166
90535
|
deviceId,
|
|
90167
90536
|
trackId: ownerId,
|
|
@@ -92116,6 +92485,31 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
92116
92485
|
* tracking/zone/event state never crosses between sources. Emits the
|
|
92117
92486
|
* SAME canonical events, distinguished only by `source`.
|
|
92118
92487
|
*/
|
|
92488
|
+
/**
|
|
92489
|
+
* One line per camera per minute with what the low-score continuation tier
|
|
92490
|
+
* did (D665): boxes offered, tracks continued, holds that ended in a
|
|
92491
|
+
* confident box again (`resumed` — an id the tier kept), holds cut at the
|
|
92492
|
+
* cap. Never one line per box. Silent while nothing was offered.
|
|
92493
|
+
*/
|
|
92494
|
+
reportLowScoreContinuation(deviceId, key, processor) {
|
|
92495
|
+
const now = Date.now();
|
|
92496
|
+
const last = this.lowScoreReportAtByKey.get(key);
|
|
92497
|
+
if (last === void 0) {
|
|
92498
|
+
this.lowScoreReportAtByKey.set(key, now);
|
|
92499
|
+
return;
|
|
92500
|
+
}
|
|
92501
|
+
if (now - last < LOW_SCORE_REPORT_INTERVAL_MS) return;
|
|
92502
|
+
this.lowScoreReportAtByKey.set(key, now);
|
|
92503
|
+
const stats = processor.drainLowScoreStats();
|
|
92504
|
+
if (stats.offered === 0 && stats.sustainCapDrops === 0) return;
|
|
92505
|
+
this.ctx.logger.info("tracker: low-score continuation", {
|
|
92506
|
+
tags: { deviceId },
|
|
92507
|
+
meta: {
|
|
92508
|
+
...stats,
|
|
92509
|
+
windowMs: now - last
|
|
92510
|
+
}
|
|
92511
|
+
});
|
|
92512
|
+
}
|
|
92119
92513
|
async processFrame(deviceId, frame, source, rawFrameHandle, detailSteps, occasion = "session") {
|
|
92120
92514
|
if (this.shuttingDown) return;
|
|
92121
92515
|
if (!await this.bindingCache.isActive(deviceId)) {
|
|
@@ -92140,7 +92534,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
92140
92534
|
const trk = await this.resolveDeviceTrackingSettings(deviceId);
|
|
92141
92535
|
const activeCount = this.lastActiveTrackIds.get(key)?.size ?? 0;
|
|
92142
92536
|
const skips = this.dropoutSkipsByKey.get(key) ?? 0;
|
|
92143
|
-
|
|
92537
|
+
const lowScoreFloor = trk.lowScoreContinuationEnabled ? trk.lowScoreContinuationFloor : null;
|
|
92538
|
+
if (shouldSkipDropoutFrame(activeCount, dropoutEvidenceCount(frame, lowScoreFloor), skips, {
|
|
92144
92539
|
enabled: trk.dropoutSkipEnabled,
|
|
92145
92540
|
maxSkipFrames: trk.dropoutMaxSkipFrames
|
|
92146
92541
|
})) {
|
|
@@ -92168,6 +92563,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
92168
92563
|
processor.setZones(liveZones);
|
|
92169
92564
|
processor.setDetectionRules(liveRules);
|
|
92170
92565
|
processor.setZoneMembershipMinOverlap(trk.zoneMembershipMinOverlap);
|
|
92566
|
+
processor.setLowScoreContinuationFloor(lowScoreFloor);
|
|
92171
92567
|
const injection = injectSecondarySubjects(frame, this.secondarySubjects.take(key, Date.now()));
|
|
92172
92568
|
for (const id of injection.covered) this.secondarySubjects.drop(key, id);
|
|
92173
92569
|
const trackerFrame = injection.frame;
|
|
@@ -92183,6 +92579,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
92183
92579
|
timestamp: frame.timestamp,
|
|
92184
92580
|
frame: trackerFrame
|
|
92185
92581
|
});
|
|
92582
|
+
this.reportLowScoreContinuation(deviceId, key, processor);
|
|
92186
92583
|
for (const r of processor.getLastLabelWriteRefusals()) {
|
|
92187
92584
|
const message = r.reason.kind === "macro-class" ? "label tier collapse refused" : "label write refused";
|
|
92188
92585
|
this.ctx.logger.info(message, {
|
|
@@ -95870,7 +96267,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
95870
96267
|
animal: trk.minSpawnAreaFracAnimal,
|
|
95871
96268
|
vehicle: trk.minSpawnAreaFracVehicle
|
|
95872
96269
|
},
|
|
95873
|
-
classGroupAssoc: trk.classGroupAssoc
|
|
96270
|
+
classGroupAssoc: trk.classGroupAssoc,
|
|
96271
|
+
lowScoreContinuationEnabled: trk.lowScoreContinuationEnabled,
|
|
96272
|
+
lowScoreContinuationMaxMs: trk.lowScoreContinuationMaxMs,
|
|
96273
|
+
resurrectionTimeScaled: trk.resurrectionTimeScaled
|
|
95874
96274
|
}, {
|
|
95875
96275
|
stationaryThresholdSec,
|
|
95876
96276
|
...stillSpeedFracPerSec !== void 0 ? { stillSpeedFracPerSec } : {}
|