@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NcRuleSchema, $n as EventCategory, $t as isSourceCap, A as LabelAttributionSchema, An as
|
|
1
|
+
import { $ as NcRuleSchema, $n as EventCategory, $t as isSourceCap, A as LabelAttributionSchema, An as DeviceType, At as audioMetricsCapability, B as NC_DEFAULT_SNOOZE_MINUTES, Bt as embeddingRefusalOf, C as EMBEDDING_REQUIREMENTS_FAILED, Cn as systemEventFilterApplies, Ct as TimelapseRuleInputSchema, D as FIRST_LEVEL_MACRO_CLASSES, Dn as vectorDimFromBase64, Dt as addonWidgetsSourceCapability, E as EVENT_OWNER_TYPES, En as templateVarsFor, Et as TrackSourceSchema, F as MOTION_CLOSE_AFTER_MS, Fn as customAction, Ft as deriveRecordingMode, G as NC_TAXONOMY, Gn as discriminatedUnion, Gt as evictionPolicyOfLocation, Hn as _enum, I as MediaFileKindEnum, In as defineCustomActions, It as describeSystemEventDeliveryProblem, J as NcAlarmSettingsPatchSchema, Jn as object, Jt as isDetectionMacroClass, K as NC_TEMPLATE_VARS, Kn as literal, Kt as faceGalleryCapability, L as NC_ALARM_SYSTEM_EVENT_KINDS, Ln as hydrateSchema, Lt as describeSystemEventFamilyProblem, M as MAX_ARCHIVED_DEBUG_NOTES, Mn as BaseAddon, Mt as buildEventKindDescriptor, N as MAX_BIRTH_DECISION_RECORDS, Nn as CamProfileSchema, Nt as consumableLowPercentOf, O as FULL_IMAGE_BBOX, On as zoneAnalyticsCapability, Ot as alarmPanelCapability, Pn as createEvent, Pt as cosineSimilarity$1, Q as NcRulePatchSchema, Qn as unknown, Qt as isScheduleActive, R as NC_CONDITION_CATALOG, Rn as isDeviceScopedCap, S as EMBEDDING_MODEL_FAILED, Sn as systemEventFamilyProblems, St as TIMELAPSE_DENSE_FLOOR_SEC, T as EVENT_KIND_BY_CAP, Tn as templateCarriesSummary, Tt as TimelapseRuleSchema, U as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, Un as array, Ut as encodeVectorBase64, V as NC_SENSOR_TRANSITION_KINDS, Vn as sleep$1, W as NC_TANK_SYSTEM_EVENT_KINDS, Wn as boolean, Wt as evaluateSensorEdge, X as NcConditionsSchema, Xn as record, Xt as isKnownTemplateVar, Y as NcConditionDescriptorSchema, Yn as partialRecord, Yt as isEventOwnerType, Z as NcRuleInputSchema, Zn as string, Zt as isMediaOwnerType, _n as sensorRaisedOf, a as CLIP_EMBEDDING_STEP_ID, an as notificationRulesCapability, at as NcSnoozeSuppressedSchema, bn as subKindsOf, bt as SCENE_DIVERGED, c as resolveClusterModelPin, cn as pipelineAnalyticsCapability, ct as NcTemplatePreviewInputSchema, d as ArchivedDebugNoteSchema, dn as readTimelapseGeneratedAt, dt as OpsLogEntrySchema, en as isSystemDelivery, et as NcRuleTargetSchema, f as BaseDevice, fn as resolveClipSearchMinScore, g as DEFAULT_EVENT_COLOR, gn as sceneMonitorCapability, gt as RetrainStatusSchema, h as CoverStateSchema, hn as ruleBelongsToAlarm$1, ht as RebuildObjectEmbeddingsRefusalSchema, in as mayWriteToLocation, it as NcSnoozeSchema, j as LockStateSchema, jn as errMsg$1, jt as audioModeOf, k as FailureCounters, kn as canonicalHash, kt as assertTimelapseCadences, l as AUDIO_MACRO_LABELS, ln as plateGalleryCapability, lt as NcTemplatePreviewSchema, m as COCO_TO_MACRO, mt as RECORDING_SOURCE_CAMSTACK_ADDON, nn as isVectorIndexDimConflict, nt as NcScheduleSchema, o as FACE_EMBEDDING_STEP_ID, on as occupancyScope, ot as NcSystemEventKindSchema, p as BirthDecisionRecordSchema, pn as resolveLocationMode, pt as RECORDING_EXPORT_MAX_READ_BYTES, q as NcAlarmConfigSchema, qn as number, qt as failureContributionCapability, rn as kebabToCamel, rt as NcSnoozeInputSchema, s as readClusterModelPinStrict, st as NcTaxonomySchema, t as BUILTIN_CLIP_MODELS, tn as isSystemEventSummaryRule, tt as NcRuleUpdateInputSchema, u as AlarmArmModeSchema, un as readDeviceStateFrom, ut as NcTemplateVarDescriptorSchema, vn as servedTemplateVars, vt as SCENE_DEFAULT_ANCHOR_THRESHOLD, w as EMBEDDING_REQUIREMENTS_UNAVAILABLE, wn as systemEventSummaryScheduleOf, wt as TimelapseRulePatchSchema, x as DeclaredDevices, xn as systemEventDeliveryProblems, xt as SceneMonitorSchema, y as DETECTION_MACRO_CLASSES, yn as storageOccupancyCapability, yt as SCENE_DEFAULT_UNCOVERED_POLICY, z as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, zn as nodePin } from "../clip-model-registry-D2zqXamW.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -47029,6 +47029,42 @@ function pairRiders(detections) {
|
|
|
47029
47029
|
return pairs;
|
|
47030
47030
|
}
|
|
47031
47031
|
//#endregion
|
|
47032
|
+
//#region src/pipeline-analytics/pipeline/tracker/low-score-continuation.ts
|
|
47033
|
+
var LOW_SCORE_CONTINUATION_FLOOR_DEFAULT = .35;
|
|
47034
|
+
var LOW_SCORE_CONTINUATION_CLASSES = new Set(["person", "animal"]);
|
|
47035
|
+
/** `[x1, y1, x2, y2]` (the discard trail's tuple) → the tracker's rectangle. */
|
|
47036
|
+
function toRect(bbox) {
|
|
47037
|
+
const [x1, y1, x2, y2] = bbox;
|
|
47038
|
+
return {
|
|
47039
|
+
x: x1,
|
|
47040
|
+
y: y1,
|
|
47041
|
+
w: x2 - x1,
|
|
47042
|
+
h: y2 - y1
|
|
47043
|
+
};
|
|
47044
|
+
}
|
|
47045
|
+
/**
|
|
47046
|
+
* The continuation candidates of one frame. `floor === null` means the tier is
|
|
47047
|
+
* off for this camera and nothing is selected.
|
|
47048
|
+
*/
|
|
47049
|
+
function selectLowScoreContinuations(discarded, floor) {
|
|
47050
|
+
if (floor === null || discarded === void 0) return [];
|
|
47051
|
+
const out = [];
|
|
47052
|
+
for (const d of discarded) {
|
|
47053
|
+
if (d.reason !== "below-threshold") continue;
|
|
47054
|
+
if (d.score < floor) continue;
|
|
47055
|
+
if (!LOW_SCORE_CONTINUATION_CLASSES.has(d.macroClass)) continue;
|
|
47056
|
+
const bbox = toRect(d.bbox);
|
|
47057
|
+
if (bbox.w <= 0 || bbox.h <= 0) continue;
|
|
47058
|
+
out.push({
|
|
47059
|
+
class: d.macroClass,
|
|
47060
|
+
originalClass: d.macroClass,
|
|
47061
|
+
score: d.score,
|
|
47062
|
+
bbox
|
|
47063
|
+
});
|
|
47064
|
+
}
|
|
47065
|
+
return out;
|
|
47066
|
+
}
|
|
47067
|
+
//#endregion
|
|
47032
47068
|
//#region src/pipeline-analytics/pipeline/track-id.ts
|
|
47033
47069
|
/**
|
|
47034
47070
|
* A track's identity: short, random, and mintable inside a frame.
|
|
@@ -47177,6 +47213,30 @@ function reacquireReachPx(bbox, elapsedMs) {
|
|
|
47177
47213
|
const elapsed = Math.max(0, elapsedMs);
|
|
47178
47214
|
return clampRange(diag * REACH_DIAGONALS_PER_SEC * (elapsed / 1e3), diag * REACH_MIN_DIAGONALS, diag * REACH_MAX_DIAGONALS);
|
|
47179
47215
|
}
|
|
47216
|
+
/**
|
|
47217
|
+
* Resurrection reach, in box DIAGONALS per second of gap (D665).
|
|
47218
|
+
*
|
|
47219
|
+
* A corpse is reached only after `maxMissedMs` (4 s) without a box, so the
|
|
47220
|
+
* rescue rate above — which is at its 1.5-diagonal ceiling by 1 s — says
|
|
47221
|
+
* nothing useful here. A subject the detector has not boxed for four seconds
|
|
47222
|
+
* is, on this fleet's evidence, one that stood still or stood half-hidden,
|
|
47223
|
+
* not one striding across the frame (a striding person is detected): 0.25
|
|
47224
|
+
* diagonals per second is a slow walk for a mid-field person and a pause for
|
|
47225
|
+
* a near one. At the 4 s floor of the graveyard band it gives 1.0 diagonal;
|
|
47226
|
+
* camera 590's refused returns sat at 1.05–1.82× the old flat 0.75 reach,
|
|
47227
|
+
* i.e. 0.79–1.37 diagonals, which this covers from about 5.5 s of gap.
|
|
47228
|
+
*/
|
|
47229
|
+
var RESURRECTION_REACH_DIAGONALS_PER_SEC = .25;
|
|
47230
|
+
/** Never tighter than the flat `rescueCentroidFactor` default it replaces. */
|
|
47231
|
+
var RESURRECTION_REACH_MIN_DIAGONALS = .75;
|
|
47232
|
+
/**
|
|
47233
|
+
* How far from a corpse's last box a detection may sit and still resurrect it
|
|
47234
|
+
* on the proximity clause, for a corpse last seen `gapMs` ago.
|
|
47235
|
+
*/
|
|
47236
|
+
function resurrectionReachPx(bbox, gapMs) {
|
|
47237
|
+
const diag = Math.hypot(bbox.w, bbox.h);
|
|
47238
|
+
return clampRange(diag * RESURRECTION_REACH_DIAGONALS_PER_SEC * (Math.max(0, gapMs) / 1e3), diag * RESURRECTION_REACH_MIN_DIAGONALS, diag * 2);
|
|
47239
|
+
}
|
|
47180
47240
|
//#endregion
|
|
47181
47241
|
//#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
|
|
47182
47242
|
/**
|
|
@@ -47325,7 +47385,10 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
47325
47385
|
byteTrackLowIouThreshold: .2,
|
|
47326
47386
|
minSpawnAreaEnabled: true,
|
|
47327
47387
|
classSpawnAreaFracs: { person: 5e-4 },
|
|
47328
|
-
classGroupAssoc: true
|
|
47388
|
+
classGroupAssoc: true,
|
|
47389
|
+
lowScoreContinuationEnabled: true,
|
|
47390
|
+
lowScoreContinuationMaxMs: 8e3,
|
|
47391
|
+
resurrectionTimeScaled: true
|
|
47329
47392
|
};
|
|
47330
47393
|
/** Macro-classes the duplicate resolver may collapse across (people in
|
|
47331
47394
|
* non-upright poses misdetected as animals). Any other cross-class pair is
|
|
@@ -47333,6 +47396,15 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
47333
47396
|
var PERSON_CLASS = "person";
|
|
47334
47397
|
var ANIMAL_CLASS = "animal";
|
|
47335
47398
|
var MAX_PATH_LENGTH = 300;
|
|
47399
|
+
function emptyLowScoreStats() {
|
|
47400
|
+
return {
|
|
47401
|
+
offered: 0,
|
|
47402
|
+
continued: 0,
|
|
47403
|
+
resumed: 0,
|
|
47404
|
+
sustainCapDrops: 0,
|
|
47405
|
+
shadowed: 0
|
|
47406
|
+
};
|
|
47407
|
+
}
|
|
47336
47408
|
/** No stationary-held detections on this frame — the default for every caller
|
|
47337
47409
|
* that has no gate (replay, tests, the non-pipeline planes). */
|
|
47338
47410
|
var EMPTY_INDEX_SET = /* @__PURE__ */ new Set();
|
|
@@ -47404,6 +47476,8 @@ var SortTracker = class SortTracker {
|
|
|
47404
47476
|
trackLossSampler = new TrackLossSampler();
|
|
47405
47477
|
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
47406
47478
|
spawnBesideObserver = null;
|
|
47479
|
+
/** Running counts behind {@link drainLowScoreStats} (D665). */
|
|
47480
|
+
lowScoreStats = emptyLowScoreStats();
|
|
47407
47481
|
constructor(config = {}) {
|
|
47408
47482
|
this.config = {
|
|
47409
47483
|
...DEFAULT_TRACKER_CONFIG,
|
|
@@ -47464,7 +47538,7 @@ var SortTracker = class SortTracker {
|
|
|
47464
47538
|
if (SortTracker.spawnBesideFamily(track.class) !== family) continue;
|
|
47465
47539
|
const tc = bboxCentroid(track.bbox);
|
|
47466
47540
|
const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
|
|
47467
|
-
const reach = this.reacquireReach(track, timestamp,
|
|
47541
|
+
const reach = this.reacquireReach(track, timestamp, "rescue");
|
|
47468
47542
|
if (dist > reach * 2) continue;
|
|
47469
47543
|
if (dist < bestDist) {
|
|
47470
47544
|
best = track;
|
|
@@ -47511,7 +47585,7 @@ var SortTracker = class SortTracker {
|
|
|
47511
47585
|
explainDecline(track, det, detections, frameArea, timestamp, centroidDist) {
|
|
47512
47586
|
if (track.class !== det.class) return det.nativeRecovery !== void 0 ? "native-measured-class" : "cross-class";
|
|
47513
47587
|
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "already-claimed";
|
|
47514
|
-
if (centroidDist <= this.reacquireReach(track, timestamp,
|
|
47588
|
+
if (centroidDist <= this.reacquireReach(track, timestamp, "rescue")) {
|
|
47515
47589
|
if (!this.isPlausibleBirth(det, frameArea)) return "already-claimed";
|
|
47516
47590
|
if (!this.config.reacquireTimeScaled) return "plausible-birth-veto";
|
|
47517
47591
|
return this.isUnambiguousReach(track, det, detections) ? "already-claimed" : "crowd-guard";
|
|
@@ -47966,34 +48040,38 @@ var SortTracker = class SortTracker {
|
|
|
47966
48040
|
* this gate crossed the group, and it is where a `person` at 0.86 took over
|
|
47967
48041
|
* an animal's id (camera 615, 17:11:01).
|
|
47968
48042
|
*/
|
|
47969
|
-
looseMatch(track, det, timestamp,
|
|
48043
|
+
looseMatch(track, det, timestamp, pass = "rescue") {
|
|
47970
48044
|
if (track.class !== det.class) return null;
|
|
47971
48045
|
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
|
|
47972
48046
|
const tc = bboxCentroid(track.bbox);
|
|
47973
48047
|
const dc = bboxCentroid(det.bbox);
|
|
47974
|
-
if (Math.hypot(tc.x - dc.x, tc.y - dc.y) <= this.reacquireReach(track, timestamp,
|
|
48048
|
+
if (Math.hypot(tc.x - dc.x, tc.y - dc.y) <= this.reacquireReach(track, timestamp, pass)) return "centroid";
|
|
47975
48049
|
if (growthAdjacentMatch(track.bbox, det.bbox, this.growthContext(track, timestamp))) return "growth";
|
|
47976
48050
|
return null;
|
|
47977
48051
|
}
|
|
47978
48052
|
/**
|
|
47979
48053
|
* The proximity budget the `centroid` clause is measured against.
|
|
47980
48054
|
*
|
|
47981
|
-
* On the RESCUE pass
|
|
47982
|
-
*
|
|
47983
|
-
*
|
|
47984
|
-
*
|
|
47985
|
-
*
|
|
47986
|
-
* On the RESURRECTION pass it
|
|
47987
|
-
*
|
|
47988
|
-
*
|
|
47989
|
-
*
|
|
47990
|
-
*
|
|
47991
|
-
*
|
|
47992
|
-
*
|
|
47993
|
-
|
|
47994
|
-
|
|
48055
|
+
* On the RESCUE pass it is {@link reacquireReachPx} — a multiple of the
|
|
48056
|
+
* track's diagonal that grows with the elapsed gap and stops at a ceiling,
|
|
48057
|
+
* because a live coasting track's whereabouts genuinely become less certain
|
|
48058
|
+
* the longer it has gone unseen (D399).
|
|
48059
|
+
*
|
|
48060
|
+
* On the RESURRECTION pass it is {@link resurrectionReachPx} (D665): the same
|
|
48061
|
+
* idea on the graveyard's own time scale — 0.25 diagonals per second of gap,
|
|
48062
|
+
* never below the flat 0.75 it replaces, never above 2. Until D665 this pass
|
|
48063
|
+
* kept the flat `rescueCentroidFactor × diagonal` on the argument that it
|
|
48064
|
+
* exists for "a flickering stationary object", and a stationary subject does
|
|
48065
|
+
* not travel further because it blinked for longer. True of the object it
|
|
48066
|
+
* was written for; not of the pass as it runs: on camera 590 (2026-09-27)
|
|
48067
|
+
* 38% of all re-splits were PEOPLE returning after 4–12 s, further than the
|
|
48068
|
+
* flat gate, with frames flowing the whole time. `resurrectionTimeScaled`
|
|
48069
|
+
* off restores the flat gate.
|
|
48070
|
+
*/
|
|
48071
|
+
reacquireReach(track, timestamp, pass) {
|
|
47995
48072
|
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
47996
|
-
if (
|
|
48073
|
+
if (pass === "resurrection") return this.config.resurrectionTimeScaled ? resurrectionReachPx(track.bbox, timestamp - track.lastSeen) : this.config.rescueCentroidFactor * diag;
|
|
48074
|
+
if (!this.config.reacquireTimeScaled) return this.config.rescueCentroidFactor * diag;
|
|
47997
48075
|
return reacquireReachPx(track.bbox, timestamp - track.lastSeen);
|
|
47998
48076
|
}
|
|
47999
48077
|
/**
|
|
@@ -48043,6 +48121,23 @@ var SortTracker = class SortTracker {
|
|
|
48043
48121
|
nearestPx: nearest
|
|
48044
48122
|
};
|
|
48045
48123
|
}
|
|
48124
|
+
/**
|
|
48125
|
+
* The resurrection counterpart of {@link isUnambiguousReach}: no other
|
|
48126
|
+
* same-class subject is live or resurrectable beside `corpse`
|
|
48127
|
+
* ({@link sameClassPeerCount} over the SURVIVING set, which is what the crowd
|
|
48128
|
+
* guard counts), and `det` is the only box of that class on the frame —
|
|
48129
|
+
* the sub-floor `lowScore` boxes included.
|
|
48130
|
+
*/
|
|
48131
|
+
isSoleSubjectResurrection(corpse, det, detections, surviving, lowScore) {
|
|
48132
|
+
if (this.sameClassPeerCount(corpse, surviving) > 0) return false;
|
|
48133
|
+
for (const other of detections) if (other !== det && other.class === corpse.class) return false;
|
|
48134
|
+
for (const other of lowScore) {
|
|
48135
|
+
if (other.class !== corpse.class) continue;
|
|
48136
|
+
if (iou$2(other.bbox, det.bbox) >= this.config.byteTrackLowIouThreshold) continue;
|
|
48137
|
+
return false;
|
|
48138
|
+
}
|
|
48139
|
+
return true;
|
|
48140
|
+
}
|
|
48046
48141
|
isUnambiguousReach(track, det, detections) {
|
|
48047
48142
|
if (this.sameClassPeerCount(track, this.tracks) > 0) return false;
|
|
48048
48143
|
for (const other of detections) {
|
|
@@ -48197,6 +48292,64 @@ var SortTracker = class SortTracker {
|
|
|
48197
48292
|
used.add(pair.detIdx);
|
|
48198
48293
|
}
|
|
48199
48294
|
}
|
|
48295
|
+
/** A CONFIDENT box fed `track` (match, resurrection): the emitted box
|
|
48296
|
+
* follows it again, and a hold that ends this way is an id the tier kept. */
|
|
48297
|
+
markConfident(track, bbox, timestamp) {
|
|
48298
|
+
track.confidentBbox = bbox;
|
|
48299
|
+
track.confidentSeenAt = timestamp;
|
|
48300
|
+
if (track.lowScoreHeld) {
|
|
48301
|
+
track.lowScoreHeld = false;
|
|
48302
|
+
this.lowScoreStats.resumed++;
|
|
48303
|
+
}
|
|
48304
|
+
}
|
|
48305
|
+
/**
|
|
48306
|
+
* The low-score continuation stage (D665): greedy, highest IoU first, of the
|
|
48307
|
+
* sub-floor boxes against the CONFIRMED tracks nothing claimed this frame,
|
|
48308
|
+
* same class, on the predicted box at `byteTrackLowIouThreshold`.
|
|
48309
|
+
*
|
|
48310
|
+
* A bound box moves the track's association state only — `bbox`, `lastSeen`,
|
|
48311
|
+
* `age` — so the next confident box meets the subject where it is. Not
|
|
48312
|
+
* `hits` (it confirms nothing), not `score`, `class`, the vote, the path or
|
|
48313
|
+
* the velocity (it describes nothing downstream may act on). A track past
|
|
48314
|
+
* `lowScoreContinuationMaxMs` since its last confident box is not offered.
|
|
48315
|
+
*/
|
|
48316
|
+
continueOnLowScore(established, lowScore, timestamp, matchedTracks, matched) {
|
|
48317
|
+
const claimed = [...matched.values()].map((d) => d.bbox);
|
|
48318
|
+
const candidates = lowScore.filter((det) => {
|
|
48319
|
+
const shadow = claimed.some((b) => iou$2(b, det.bbox) >= this.config.byteTrackLowIouThreshold);
|
|
48320
|
+
if (shadow) this.lowScoreStats.shadowed++;
|
|
48321
|
+
return !shadow;
|
|
48322
|
+
});
|
|
48323
|
+
const pairs = [];
|
|
48324
|
+
for (const track of established) {
|
|
48325
|
+
if (matchedTracks.has(track)) continue;
|
|
48326
|
+
if (timestamp - track.confidentSeenAt > this.config.lowScoreContinuationMaxMs) continue;
|
|
48327
|
+
const pbox = this.predicted(track, timestamp);
|
|
48328
|
+
for (const det of candidates) {
|
|
48329
|
+
if (det.class !== track.class) continue;
|
|
48330
|
+
const score = iou$2(pbox, det.bbox);
|
|
48331
|
+
if (score >= this.config.byteTrackLowIouThreshold) pairs.push({
|
|
48332
|
+
track,
|
|
48333
|
+
det,
|
|
48334
|
+
score
|
|
48335
|
+
});
|
|
48336
|
+
}
|
|
48337
|
+
}
|
|
48338
|
+
pairs.sort((a, b) => b.score - a.score);
|
|
48339
|
+
const usedTracks = /* @__PURE__ */ new Set();
|
|
48340
|
+
const usedDets = /* @__PURE__ */ new Set();
|
|
48341
|
+
for (const pair of pairs) {
|
|
48342
|
+
if (usedTracks.has(pair.track) || usedDets.has(pair.det)) continue;
|
|
48343
|
+
usedTracks.add(pair.track);
|
|
48344
|
+
usedDets.add(pair.det);
|
|
48345
|
+
pair.track.bbox = pair.det.bbox;
|
|
48346
|
+
pair.track.lastSeen = timestamp;
|
|
48347
|
+
pair.track.lastSeenFrameSeq = this.frameSeq;
|
|
48348
|
+
pair.track.age = 0;
|
|
48349
|
+
pair.track.lowScoreHeld = true;
|
|
48350
|
+
this.lowScoreStats.continued++;
|
|
48351
|
+
}
|
|
48352
|
+
}
|
|
48200
48353
|
update(detections, timestamp, frameContext) {
|
|
48201
48354
|
this.frameSeq++;
|
|
48202
48355
|
const frameArea = frameContext ? frameContext.frameWidth * frameContext.frameHeight : 0;
|
|
@@ -48254,6 +48407,9 @@ var SortTracker = class SortTracker {
|
|
|
48254
48407
|
this.rescueAssociate(established, PROXIMITY, detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
48255
48408
|
this.rescueAssociate(provisional, new Set([...OVERLAP, ...PROXIMITY]), detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
48256
48409
|
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);
|
|
48410
|
+
const lowScore = frameContext?.lowScore ?? [];
|
|
48411
|
+
this.lowScoreStats.offered += lowScore.length;
|
|
48412
|
+
if (this.config.lowScoreContinuationEnabled && lowScore.length > 0) this.continueOnLowScore(established, lowScore, timestamp, matchedTracks, matched);
|
|
48257
48413
|
for (const [track, det] of matched) {
|
|
48258
48414
|
if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, {
|
|
48259
48415
|
against: track,
|
|
@@ -48284,6 +48440,7 @@ var SortTracker = class SortTracker {
|
|
|
48284
48440
|
track.hits++;
|
|
48285
48441
|
track.lastSeen = timestamp;
|
|
48286
48442
|
track.lastSeenFrameSeq = this.frameSeq;
|
|
48443
|
+
this.markConfident(track, det.bbox, timestamp);
|
|
48287
48444
|
if (dtMs > 0) {
|
|
48288
48445
|
track.velocity = {
|
|
48289
48446
|
dx: (newCenter.x - prevCenter.x) / dtMs,
|
|
@@ -48308,10 +48465,15 @@ var SortTracker = class SortTracker {
|
|
|
48308
48465
|
}
|
|
48309
48466
|
const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track, timestamp), ob.bbox) >= this.config.occlusionContainment);
|
|
48310
48467
|
track.age++;
|
|
48311
|
-
|
|
48468
|
+
const coastMs = timestamp - track.lastSeen;
|
|
48469
|
+
const limitMs = occluded ? this.config.occlusionMaxMissedMs : this.config.maxMissedMs;
|
|
48470
|
+
const heldTooLong = track.lowScoreHeld && timestamp - track.confidentSeenAt > this.config.lowScoreContinuationMaxMs;
|
|
48471
|
+
if (heldTooLong) this.lowScoreStats.sustainCapDrops++;
|
|
48472
|
+
if (coastMs > limitMs || heldTooLong) {
|
|
48312
48473
|
track.lost = true;
|
|
48313
48474
|
track.lostAt = timestamp;
|
|
48314
48475
|
track.resurrectable = true;
|
|
48476
|
+
track.lowScoreHeld = false;
|
|
48315
48477
|
this.lostTracks.push(track);
|
|
48316
48478
|
} else surviving.push(track);
|
|
48317
48479
|
}
|
|
@@ -48322,15 +48484,17 @@ var SortTracker = class SortTracker {
|
|
|
48322
48484
|
let bestScore = -1;
|
|
48323
48485
|
let bestGate = "iou";
|
|
48324
48486
|
let bestPeers = 0;
|
|
48487
|
+
let bestVetoLifted = false;
|
|
48325
48488
|
let refused;
|
|
48326
48489
|
let refusedGate = "iou";
|
|
48327
48490
|
let refusedScore = -1;
|
|
48328
48491
|
for (const lost of this.lostTracks) {
|
|
48329
48492
|
if (!lost.resurrectable) continue;
|
|
48330
48493
|
if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
|
|
48331
|
-
const gate = this.looseMatch(lost, det, timestamp,
|
|
48494
|
+
const gate = this.looseMatch(lost, det, timestamp, "resurrection");
|
|
48332
48495
|
if (gate === null) continue;
|
|
48333
|
-
|
|
48496
|
+
const vetoLifted = gate === "centroid" && this.isPlausibleBirth(det, frameArea);
|
|
48497
|
+
if (vetoLifted && !(this.config.resurrectionTimeScaled && this.isSoleSubjectResurrection(lost, det, detections, surviving, lowScore))) continue;
|
|
48334
48498
|
const score = iou$2(lost.bbox, det.bbox);
|
|
48335
48499
|
const peers = this.sameClassPeerCount(lost, surviving);
|
|
48336
48500
|
if (this.config.resurrectionCrowdGuard && peers > 0 && score < this.config.resurrectionCrowdedIouThreshold) {
|
|
@@ -48346,13 +48510,14 @@ var SortTracker = class SortTracker {
|
|
|
48346
48510
|
bestScore = score;
|
|
48347
48511
|
bestGate = gate;
|
|
48348
48512
|
bestPeers = peers;
|
|
48513
|
+
bestVetoLifted = vetoLifted;
|
|
48349
48514
|
}
|
|
48350
48515
|
}
|
|
48351
48516
|
if (!best) {
|
|
48352
48517
|
if (refused !== void 0) this.reportBind("resurrection-refused", refusedGate, refused, det, timestamp);
|
|
48353
48518
|
continue;
|
|
48354
48519
|
}
|
|
48355
|
-
resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : "sole-subject");
|
|
48520
|
+
resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : bestVetoLifted ? "sole-subject-veto-lifted" : "sole-subject");
|
|
48356
48521
|
this.reportBind("resurrection", bestGate, best, det, timestamp);
|
|
48357
48522
|
this.lostTracks = this.lostTracks.filter((t) => t !== best);
|
|
48358
48523
|
best.bbox = det.bbox;
|
|
@@ -48366,6 +48531,7 @@ var SortTracker = class SortTracker {
|
|
|
48366
48531
|
best.lost = false;
|
|
48367
48532
|
best.lastSeen = timestamp;
|
|
48368
48533
|
best.lastSeenFrameSeq = this.frameSeq;
|
|
48534
|
+
this.markConfident(best, det.bbox, timestamp);
|
|
48369
48535
|
best.velocity = {
|
|
48370
48536
|
dx: 0,
|
|
48371
48537
|
dy: 0
|
|
@@ -48442,7 +48608,10 @@ var SortTracker = class SortTracker {
|
|
|
48442
48608
|
classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]]),
|
|
48443
48609
|
emitted: false,
|
|
48444
48610
|
lossReported: false,
|
|
48445
|
-
lastNativeRecovery: det.nativeRecovery
|
|
48611
|
+
lastNativeRecovery: det.nativeRecovery,
|
|
48612
|
+
confidentBbox: det.bbox,
|
|
48613
|
+
confidentSeenAt: timestamp,
|
|
48614
|
+
lowScoreHeld: false
|
|
48446
48615
|
});
|
|
48447
48616
|
}
|
|
48448
48617
|
this.tracks = surviving;
|
|
@@ -48450,11 +48619,12 @@ var SortTracker = class SortTracker {
|
|
|
48450
48619
|
return this.tracks.filter((t) => this.isConfirmedForEmit(t)).map((t) => {
|
|
48451
48620
|
t.emitted = true;
|
|
48452
48621
|
const scene = resurrectedScene.get(t);
|
|
48622
|
+
const held = t.lowScoreHeld;
|
|
48453
48623
|
return {
|
|
48454
48624
|
class: this.resolveReportedClass(t),
|
|
48455
48625
|
originalClass: t.originalClass,
|
|
48456
48626
|
score: t.score,
|
|
48457
|
-
bbox: t.bbox,
|
|
48627
|
+
bbox: held ? t.confidentBbox : t.bbox,
|
|
48458
48628
|
trackId: t.id,
|
|
48459
48629
|
trackAge: t.hits,
|
|
48460
48630
|
velocity: {
|
|
@@ -48462,8 +48632,9 @@ var SortTracker = class SortTracker {
|
|
|
48462
48632
|
dy: t.velocity.dy * t.emitIntervalMs
|
|
48463
48633
|
},
|
|
48464
48634
|
path: [...t.path],
|
|
48465
|
-
matchedThisFrame: t.lastSeen === timestamp,
|
|
48466
|
-
...scene !== void 0 ? { resurrectionScene: scene } : {}
|
|
48635
|
+
matchedThisFrame: t.lastSeen === timestamp && !held,
|
|
48636
|
+
...scene !== void 0 ? { resurrectionScene: scene } : {},
|
|
48637
|
+
...held ? { heldByLowScore: true } : {}
|
|
48467
48638
|
};
|
|
48468
48639
|
});
|
|
48469
48640
|
}
|
|
@@ -48477,6 +48648,16 @@ var SortTracker = class SortTracker {
|
|
|
48477
48648
|
this.tracks = this.tracks.filter((t) => t.id !== trackId);
|
|
48478
48649
|
this.lostTracks = this.lostTracks.filter((t) => t.id !== trackId);
|
|
48479
48650
|
}
|
|
48651
|
+
/**
|
|
48652
|
+
* What the low-score continuation stage did since the last call, and reset
|
|
48653
|
+
* (D665). The tracker has no `deviceId` and no logger; its owner reads this
|
|
48654
|
+
* on its own cadence and logs it per camera.
|
|
48655
|
+
*/
|
|
48656
|
+
drainLowScoreStats() {
|
|
48657
|
+
const out = this.lowScoreStats;
|
|
48658
|
+
this.lowScoreStats = emptyLowScoreStats();
|
|
48659
|
+
return out;
|
|
48660
|
+
}
|
|
48480
48661
|
getActiveTracks() {
|
|
48481
48662
|
return this.tracks;
|
|
48482
48663
|
}
|
|
@@ -48851,6 +49032,8 @@ var FrameProcessor = class {
|
|
|
48851
49032
|
detectionRules;
|
|
48852
49033
|
/** See {@link setZoneMembershipMinOverlap}. 0 = any positive overlap. */
|
|
48853
49034
|
zoneMembershipMinOverlap;
|
|
49035
|
+
/** See {@link setLowScoreContinuationFloor}. `null` = tier off. */
|
|
49036
|
+
lowScoreFloor = LOW_SCORE_CONTINUATION_FLOOR_DEFAULT;
|
|
48854
49037
|
/** See {@link getLastZoneOverlaps}. */
|
|
48855
49038
|
lastZoneOverlaps;
|
|
48856
49039
|
/** See {@link getLastZoneRejections}. */
|
|
@@ -48963,6 +49146,18 @@ var FrameProcessor = class {
|
|
|
48963
49146
|
* failure mode this whole area keeps producing. {@link lastZoneOverlaps}
|
|
48964
49147
|
* exists so the distribution can be read before a number is picked.
|
|
48965
49148
|
*/
|
|
49149
|
+
/**
|
|
49150
|
+
* The score at or above which a box the operator's class floor dropped is
|
|
49151
|
+
* handed to the tracker as low-score continuation evidence (D665); `null`
|
|
49152
|
+
* turns the tier off for this camera. Pushed per frame, like the zones.
|
|
49153
|
+
*/
|
|
49154
|
+
setLowScoreContinuationFloor(floor) {
|
|
49155
|
+
this.lowScoreFloor = floor !== null && Number.isFinite(floor) && floor >= 0 && floor <= 1 ? floor : null;
|
|
49156
|
+
}
|
|
49157
|
+
/** The tracker's low-score continuation counts since the last call (D665). */
|
|
49158
|
+
drainLowScoreStats() {
|
|
49159
|
+
return this.tracker.drainLowScoreStats();
|
|
49160
|
+
}
|
|
48966
49161
|
setZoneMembershipMinOverlap(minOverlap) {
|
|
48967
49162
|
this.zoneMembershipMinOverlap = Number.isFinite(minOverlap) && minOverlap >= 0 && minOverlap <= 1 ? minOverlap : 0;
|
|
48968
49163
|
}
|
|
@@ -49355,10 +49550,16 @@ var FrameProcessor = class {
|
|
|
49355
49550
|
}
|
|
49356
49551
|
trackerInput.push(det);
|
|
49357
49552
|
}
|
|
49553
|
+
const lowCandidates = selectLowScoreContinuations(frame.discarded, this.lowScoreFloor);
|
|
49554
|
+
const lowScore = lowCandidates.length === 0 ? lowCandidates : this.zoneEngine.filterDetections(lowCandidates.map((d) => ({
|
|
49555
|
+
...d.bbox,
|
|
49556
|
+
detection: d
|
|
49557
|
+
})), this.zones, this.detectionRules, frameWidth, frameHeight, (fd) => fd.detection.class).passed.map((fd) => fd.detection);
|
|
49358
49558
|
const trackedDetections = this.tracker.update(trackerInput, timestamp, {
|
|
49359
49559
|
frameWidth,
|
|
49360
49560
|
frameHeight,
|
|
49361
|
-
continuationOnly
|
|
49561
|
+
continuationOnly,
|
|
49562
|
+
lowScore
|
|
49362
49563
|
});
|
|
49363
49564
|
const stationaryContinued = continuableBoxes.size === 0 ? 0 : trackedDetections.filter((t) => t.matchedThisFrame !== false && continuableBoxes.has(t.bbox)).length;
|
|
49364
49565
|
const riderFolds = foldByVehicleBbox.size === 0 ? EMPTY_RIDER_FOLDS : [...foldByVehicleBbox].map(([bbox, fold]) => {
|
|
@@ -49600,6 +49801,19 @@ var FrameProcessor = class {
|
|
|
49600
49801
|
}
|
|
49601
49802
|
};
|
|
49602
49803
|
//#endregion
|
|
49804
|
+
//#region src/pipeline-analytics/face-identity-retraction.ts
|
|
49805
|
+
/** The recognizer's step id — the attribution a face identity label carries. */
|
|
49806
|
+
var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
|
|
49807
|
+
async function retractFaceIdentityLabel(stores, trackId) {
|
|
49808
|
+
const meta = stores.trackStore.getActiveByTrack(trackId)?.subLabelMeta;
|
|
49809
|
+
if (meta === void 0) return "no-label";
|
|
49810
|
+
if (meta.stepId !== "face-recognizer") return "not-face-recognizer";
|
|
49811
|
+
await stores.eventStore.clearLabelForTrack(trackId, 2);
|
|
49812
|
+
await stores.trackStore.clearLabel(trackId, 2);
|
|
49813
|
+
await stores.recomputeImportance?.(trackId);
|
|
49814
|
+
return "retracted";
|
|
49815
|
+
}
|
|
49816
|
+
//#endregion
|
|
49603
49817
|
//#region src/pipeline-analytics/pipeline/track-provenance.ts
|
|
49604
49818
|
function trackProvenanceOf(frame) {
|
|
49605
49819
|
if (!(frame.width > 0 && frame.height > 0)) return void 0;
|
|
@@ -63204,8 +63418,6 @@ function faceSizeScore(detectionFrameShortSidePx) {
|
|
|
63204
63418
|
}
|
|
63205
63419
|
//#endregion
|
|
63206
63420
|
//#region src/pipeline-analytics/face-recognizer.ts
|
|
63207
|
-
/** Attribution of an identity resolved by the in-process ArcFace matcher. */
|
|
63208
|
-
var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
|
|
63209
63421
|
/** At most one "dropping imageless track" log per this interval, per recognizer. */
|
|
63210
63422
|
var FACE_IMAGELESS_LOG_THROTTLE_MS = 6e4;
|
|
63211
63423
|
/** At most one 'embedding magnitude' sample line per device per this interval. */
|
|
@@ -63231,6 +63443,9 @@ var FaceRecognizer = class {
|
|
|
63231
63443
|
gallery = [];
|
|
63232
63444
|
names = /* @__PURE__ */ new Map();
|
|
63233
63445
|
aggregates = /* @__PURE__ */ new Map();
|
|
63446
|
+
/** The aggregate a veto-lifted quarantine set aside, restored if the first
|
|
63447
|
+
* face proves the same person (D665). */
|
|
63448
|
+
stashedIdentity = /* @__PURE__ */ new Map();
|
|
63234
63449
|
bestFace = /* @__PURE__ */ new Map();
|
|
63235
63450
|
/** Frame instant of each live track's last source park (D644). */
|
|
63236
63451
|
lastSourceParkAt = /* @__PURE__ */ new Map();
|
|
@@ -63422,9 +63637,35 @@ var FaceRecognizer = class {
|
|
|
63422
63637
|
* • the face becomes non-promotable: {@link onTrackEnd} will not write it to
|
|
63423
63638
|
* the gallery while the flag stands.
|
|
63424
63639
|
*
|
|
63425
|
-
* The
|
|
63426
|
-
|
|
63427
|
-
|
|
63640
|
+
* The identity AGGREGATE depends on the scene (D665):
|
|
63641
|
+
*
|
|
63642
|
+
* • `multi-subject` — a bind that needed real overlap (IoU ≥ 0.3) with the
|
|
63643
|
+
* corpse's last box. The aggregate is LEFT ALONE: the next face either
|
|
63644
|
+
* confirms (same person, name intact at no cost — the common case, a
|
|
63645
|
+
* seated person occluded for a few seconds) or is rejected by cosine, and
|
|
63646
|
+
* a rejection is a PROVEN subject change: the aggregate is deleted and the
|
|
63647
|
+
* written identity label retracted then.
|
|
63648
|
+
* • `sole-subject-veto-lifted` — a bind on proximity alone. The aggregate is
|
|
63649
|
+
* STASHED and reset, and the written identity label retracted at once:
|
|
63650
|
+
* `updateTrackAggregate` keeps an assignment through every unmatched frame,
|
|
63651
|
+
* so without the reset a stranger who never matched anybody would carry
|
|
63652
|
+
* the name. The first face-bearing frame decides — `confirmed` restores the
|
|
63653
|
+
* stash and rewrites the label (the same person, back immediately);
|
|
63654
|
+
* `rejected`, or no face before the track ends, discards it.
|
|
63655
|
+
*/
|
|
63656
|
+
async quarantineHeldFace(deviceId, trackId, scene) {
|
|
63657
|
+
if (scene === "sole-subject-veto-lifted") {
|
|
63658
|
+
const aggregate = this.aggregates.get(trackId);
|
|
63659
|
+
if (aggregate?.assignedIdentityId != null) {
|
|
63660
|
+
const peak = this.bestMatchScore.get(trackId);
|
|
63661
|
+
this.stashedIdentity.set(trackId, {
|
|
63662
|
+
aggregate,
|
|
63663
|
+
...peak !== void 0 ? { bestMatchScore: peak } : {}
|
|
63664
|
+
});
|
|
63665
|
+
this.aggregates.delete(trackId);
|
|
63666
|
+
await this.retractIdentity(deviceId, trackId, scene, aggregate.assignedIdentityId);
|
|
63667
|
+
} else this.aggregates.delete(trackId);
|
|
63668
|
+
}
|
|
63428
63669
|
const held = this.bestFace.get(trackId);
|
|
63429
63670
|
if (held === void 0 || held.unverified === true) return;
|
|
63430
63671
|
const quarantined = {
|
|
@@ -63447,6 +63688,7 @@ var FaceRecognizer = class {
|
|
|
63447
63688
|
trackId
|
|
63448
63689
|
},
|
|
63449
63690
|
meta: {
|
|
63691
|
+
scene,
|
|
63450
63692
|
heldScore: held.score,
|
|
63451
63693
|
heldAt: held.timestamp,
|
|
63452
63694
|
strippedIdentityId: held.recognizedIdentityId ?? null,
|
|
@@ -63455,6 +63697,40 @@ var FaceRecognizer = class {
|
|
|
63455
63697
|
});
|
|
63456
63698
|
}
|
|
63457
63699
|
/**
|
|
63700
|
+
* Take the identity label back from the track and its events — only when the
|
|
63701
|
+
* face recognizer wrote it (`retractFaceIdentityLabel`). Stated either way.
|
|
63702
|
+
*/
|
|
63703
|
+
async retractIdentity(deviceId, trackId, reason, identityId) {
|
|
63704
|
+
const retract = this.deps.retractIdentityLabel;
|
|
63705
|
+
if (retract === void 0) return;
|
|
63706
|
+
try {
|
|
63707
|
+
const outcome = await retract(trackId);
|
|
63708
|
+
this.deps.logger.info("face: identity label retracted after an unverified re-attach", {
|
|
63709
|
+
tags: {
|
|
63710
|
+
deviceId,
|
|
63711
|
+
trackId
|
|
63712
|
+
},
|
|
63713
|
+
meta: {
|
|
63714
|
+
reason,
|
|
63715
|
+
identityId,
|
|
63716
|
+
outcome
|
|
63717
|
+
}
|
|
63718
|
+
});
|
|
63719
|
+
} catch (err) {
|
|
63720
|
+
this.deps.logger.warn("face: identity label retraction failed", {
|
|
63721
|
+
tags: {
|
|
63722
|
+
deviceId,
|
|
63723
|
+
trackId
|
|
63724
|
+
},
|
|
63725
|
+
meta: {
|
|
63726
|
+
reason,
|
|
63727
|
+
identityId,
|
|
63728
|
+
error: String(err)
|
|
63729
|
+
}
|
|
63730
|
+
});
|
|
63731
|
+
}
|
|
63732
|
+
}
|
|
63733
|
+
/**
|
|
63458
63734
|
* Ask a FRESH face whether the quarantined held face is still about the same
|
|
63459
63735
|
* person, at the same bar the gallery uses to say two vectors are one person
|
|
63460
63736
|
* (`similarityThreshold` — one knob over one band, D222).
|
|
@@ -63476,7 +63752,7 @@ var FaceRecognizer = class {
|
|
|
63476
63752
|
}
|
|
63477
63753
|
async processFrame(input) {
|
|
63478
63754
|
const { settings } = input;
|
|
63479
|
-
for (const t of input.tracked) if (t.resurrectionScene
|
|
63755
|
+
for (const t of input.tracked) if (t.resurrectionScene !== void 0 && t.resurrectionScene !== "sole-subject") await this.quarantineHeldFace(input.deviceId, t.trackId, t.resurrectionScene);
|
|
63480
63756
|
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);
|
|
63481
63757
|
if (candidates.length === 0) return;
|
|
63482
63758
|
this.deps.logger.debug("face: frame candidates", {
|
|
@@ -63543,11 +63819,26 @@ var FaceRecognizer = class {
|
|
|
63543
63819
|
embedding: c.embedding,
|
|
63544
63820
|
embeddingModelId: c.embeddingModelId
|
|
63545
63821
|
}, settings.similarityThreshold);
|
|
63546
|
-
|
|
63547
|
-
|
|
63548
|
-
|
|
63549
|
-
|
|
63550
|
-
|
|
63822
|
+
const stashed = this.stashedIdentity.get(c.trackId);
|
|
63823
|
+
this.stashedIdentity.delete(c.trackId);
|
|
63824
|
+
if (verdict === "confirmed") {
|
|
63825
|
+
this.bestFace.set(c.trackId, {
|
|
63826
|
+
...quarantined,
|
|
63827
|
+
unverified: false
|
|
63828
|
+
});
|
|
63829
|
+
const name = stashed?.aggregate.assignedIdentityId;
|
|
63830
|
+
if (stashed !== void 0 && name != null) {
|
|
63831
|
+
this.aggregates.set(c.trackId, stashed.aggregate);
|
|
63832
|
+
if (stashed.bestMatchScore !== void 0) this.bestMatchScore.set(c.trackId, stashed.bestMatchScore);
|
|
63833
|
+
labelWork.push({
|
|
63834
|
+
trackId: c.trackId,
|
|
63835
|
+
assignedIdentityId: name,
|
|
63836
|
+
matchScore: stashed.bestMatchScore ?? null
|
|
63837
|
+
});
|
|
63838
|
+
}
|
|
63839
|
+
} else if (verdict === "rejected") {
|
|
63840
|
+
const named = this.aggregates.get(c.trackId)?.assignedIdentityId;
|
|
63841
|
+
if (named != null) await this.retractIdentity(input.deviceId, c.trackId, "rejected", named);
|
|
63551
63842
|
this.bestFace.delete(c.trackId);
|
|
63552
63843
|
this.bestTracker.delete(c.trackId);
|
|
63553
63844
|
this.aggregates.delete(c.trackId);
|
|
@@ -63762,6 +64053,7 @@ var FaceRecognizer = class {
|
|
|
63762
64053
|
const bestMatchScore = this.bestMatchScore.get(trackId);
|
|
63763
64054
|
const bestSuggestion = this.bestSuggestion.get(trackId);
|
|
63764
64055
|
this.aggregates.delete(trackId);
|
|
64056
|
+
this.stashedIdentity.delete(trackId);
|
|
63765
64057
|
this.bestFace.delete(trackId);
|
|
63766
64058
|
this.bestTracker.delete(trackId);
|
|
63767
64059
|
this.bestMatchScore.delete(trackId);
|
|
@@ -67923,6 +68215,19 @@ function shouldSkipDropoutFrame(activeTrackCount, detectionCount, consecutiveSki
|
|
|
67923
68215
|
if (activeTrackCount <= 0) return false;
|
|
67924
68216
|
return consecutiveSkips < cfg.maxSkipFrames;
|
|
67925
68217
|
}
|
|
68218
|
+
/**
|
|
68219
|
+
* How much evidence a frame carries for the dropout decision: its detections,
|
|
68220
|
+
* plus the sub-floor boxes the tracker may continue a track on (D665).
|
|
68221
|
+
*
|
|
68222
|
+
* Until D665 the skip counted `frame.detections` only, so a frame whose sole
|
|
68223
|
+
* box was a person at 0.5 read as a detector glitch and never reached the
|
|
68224
|
+
* tracker — on camera 590 more than half the frames of a re-split gap were
|
|
68225
|
+
* dropout-skipped. A frame with a continuable box is not empty; `floor` is the
|
|
68226
|
+
* camera's low-score floor, `null` when the tier is off.
|
|
68227
|
+
*/
|
|
68228
|
+
function dropoutEvidenceCount(frame, floor) {
|
|
68229
|
+
return frame.detections.length + selectLowScoreContinuations(frame.discarded, floor).length;
|
|
68230
|
+
}
|
|
67926
68231
|
//#endregion
|
|
67927
68232
|
//#region src/pipeline-analytics/pipeline/key-event-query.ts
|
|
67928
68233
|
async function rankKeyEvents(candidates, options, peakLookup) {
|
|
@@ -82876,7 +83181,21 @@ var TrackingSettingsSchema = object({
|
|
|
82876
83181
|
* passes, only for a coarse box below `animalOverPersonMaxScore`, never on
|
|
82877
83182
|
* the rescue / resurrection passes. Reported label stays via the class
|
|
82878
83183
|
* vote. Off = exact per-frame class gating. */
|
|
82879
|
-
classGroupAssoc: boolean().default(true)
|
|
83184
|
+
classGroupAssoc: boolean().default(true),
|
|
83185
|
+
/** Hand the boxes the per-class confidence floor dropped to the tracker as
|
|
83186
|
+
* CONTINUATION-ONLY evidence (D665): they may keep a confirmed track alive,
|
|
83187
|
+
* never open one, never produce media, events or notifications. */
|
|
83188
|
+
lowScoreContinuationEnabled: boolean().default(true),
|
|
83189
|
+
/** Score at/above which a floor-dropped box counts as continuation evidence
|
|
83190
|
+
* (D665 — justified in `low-score-continuation.ts`). */
|
|
83191
|
+
lowScoreContinuationFloor: number().min(0).max(1).default(.35),
|
|
83192
|
+
/** Longest a track may be held by floor-dropped boxes alone, from its last
|
|
83193
|
+
* confident box (ms). */
|
|
83194
|
+
lowScoreContinuationMaxMs: number().min(0).default(8e3),
|
|
83195
|
+
/** Graveyard reach scaled by the gap, and the plausible-birth veto lifted for
|
|
83196
|
+
* a sole subject (D665). Off = the flat pre-D665 resurrection gate — the
|
|
83197
|
+
* per-camera rollback that needs no redeploy. */
|
|
83198
|
+
resurrectionTimeScaled: boolean().default(true)
|
|
82880
83199
|
});
|
|
82881
83200
|
var TRACKING_DEFAULTS = TrackingSettingsSchema.parse({});
|
|
82882
83201
|
/**
|
|
@@ -82931,7 +83250,11 @@ function resolveTrackingSettings(raw) {
|
|
|
82931
83250
|
minSpawnAreaFracPerson: s.minSpawnAreaFracPerson.catch(TRACKING_DEFAULTS.minSpawnAreaFracPerson).parse(raw.minSpawnAreaFracPerson),
|
|
82932
83251
|
minSpawnAreaFracAnimal: s.minSpawnAreaFracAnimal.catch(TRACKING_DEFAULTS.minSpawnAreaFracAnimal).parse(raw.minSpawnAreaFracAnimal),
|
|
82933
83252
|
minSpawnAreaFracVehicle: s.minSpawnAreaFracVehicle.catch(TRACKING_DEFAULTS.minSpawnAreaFracVehicle).parse(raw.minSpawnAreaFracVehicle),
|
|
82934
|
-
classGroupAssoc: s.classGroupAssoc.catch(TRACKING_DEFAULTS.classGroupAssoc).parse(raw.classGroupAssoc)
|
|
83253
|
+
classGroupAssoc: s.classGroupAssoc.catch(TRACKING_DEFAULTS.classGroupAssoc).parse(raw.classGroupAssoc),
|
|
83254
|
+
lowScoreContinuationEnabled: s.lowScoreContinuationEnabled.catch(TRACKING_DEFAULTS.lowScoreContinuationEnabled).parse(raw.lowScoreContinuationEnabled),
|
|
83255
|
+
lowScoreContinuationFloor: s.lowScoreContinuationFloor.catch(TRACKING_DEFAULTS.lowScoreContinuationFloor).parse(raw.lowScoreContinuationFloor),
|
|
83256
|
+
lowScoreContinuationMaxMs: s.lowScoreContinuationMaxMs.catch(TRACKING_DEFAULTS.lowScoreContinuationMaxMs).parse(raw.lowScoreContinuationMaxMs),
|
|
83257
|
+
resurrectionTimeScaled: s.resurrectionTimeScaled.catch(TRACKING_DEFAULTS.resurrectionTimeScaled).parse(raw.resurrectionTimeScaled)
|
|
82935
83258
|
};
|
|
82936
83259
|
}
|
|
82937
83260
|
//#endregion
|
|
@@ -83361,6 +83684,40 @@ function buildDetectionSettingsSections() {
|
|
|
83361
83684
|
step: .05,
|
|
83362
83685
|
default: TRACKING_DEFAULTS.byteTrackLowIouThreshold
|
|
83363
83686
|
},
|
|
83687
|
+
{
|
|
83688
|
+
type: "boolean",
|
|
83689
|
+
key: "lowScoreContinuationEnabled",
|
|
83690
|
+
label: "Keep tracks through low-confidence frames",
|
|
83691
|
+
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.",
|
|
83692
|
+
default: TRACKING_DEFAULTS.lowScoreContinuationEnabled
|
|
83693
|
+
},
|
|
83694
|
+
{
|
|
83695
|
+
type: "number",
|
|
83696
|
+
key: "lowScoreContinuationFloor",
|
|
83697
|
+
label: "Low-confidence continuation floor",
|
|
83698
|
+
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.",
|
|
83699
|
+
min: 0,
|
|
83700
|
+
max: 1,
|
|
83701
|
+
step: .05,
|
|
83702
|
+
default: TRACKING_DEFAULTS.lowScoreContinuationFloor
|
|
83703
|
+
},
|
|
83704
|
+
{
|
|
83705
|
+
type: "number",
|
|
83706
|
+
key: "lowScoreContinuationMaxMs",
|
|
83707
|
+
label: "Low-confidence hold limit (ms)",
|
|
83708
|
+
description: "Longest a track may be kept alive by low-confidence boxes alone, counted from its last confident box.",
|
|
83709
|
+
min: 0,
|
|
83710
|
+
max: 3e4,
|
|
83711
|
+
step: 500,
|
|
83712
|
+
default: TRACKING_DEFAULTS.lowScoreContinuationMaxMs
|
|
83713
|
+
},
|
|
83714
|
+
{
|
|
83715
|
+
type: "boolean",
|
|
83716
|
+
key: "resurrectionTimeScaled",
|
|
83717
|
+
label: "Wider re-attach after a long gap",
|
|
83718
|
+
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.",
|
|
83719
|
+
default: TRACKING_DEFAULTS.resurrectionTimeScaled
|
|
83720
|
+
},
|
|
83364
83721
|
{
|
|
83365
83722
|
type: "boolean",
|
|
83366
83723
|
key: "minSpawnAreaEnabled",
|
|
@@ -87861,6 +88218,8 @@ function decodeEmbeddingBase64(base64) {
|
|
|
87861
88218
|
var PHANTOM_CELL_PX = 32;
|
|
87862
88219
|
/** How long a cell remembers its closes. */
|
|
87863
88220
|
var PHANTOM_CELL_WINDOW_MS = 360 * 6e4;
|
|
88221
|
+
/** Cadence of the per-camera low-score continuation line (D665). */
|
|
88222
|
+
var LOW_SCORE_REPORT_INTERVAL_MS = 6e4;
|
|
87864
88223
|
var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
87865
88224
|
/** Recent SHORT+MOTIONLESS track closes per `<device>:<class>:<cell>` —
|
|
87866
88225
|
* see {@link noteShortMotionlessTrack}. Measurement only; each entry is
|
|
@@ -88528,6 +88887,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
88528
88887
|
packageBackground = null;
|
|
88529
88888
|
/** Consecutive detector-dropout frames skipped per (deviceId, source) key. */
|
|
88530
88889
|
dropoutSkipsByKey = /* @__PURE__ */ new Map();
|
|
88890
|
+
/** Last time each processor's low-score continuation counts were logged (D665). */
|
|
88891
|
+
lowScoreReportAtByKey = /* @__PURE__ */ new Map();
|
|
88531
88892
|
/** The per-track close-path orchestrator (S2, refactor spec 2026-07-22):
|
|
88532
88893
|
* owns the single-flight sweep latch + the FULL per-track close sequence
|
|
88533
88894
|
* (zero-media policy, lastFrame promotion, recognizer ends with the
|
|
@@ -90086,6 +90447,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
90086
90447
|
mediaStore: stores.mediaStore,
|
|
90087
90448
|
trackStore: stores.trackStore,
|
|
90088
90449
|
eventStore: stores.eventStore,
|
|
90450
|
+
retractIdentityLabel: (trackId) => retractFaceIdentityLabel({
|
|
90451
|
+
trackStore: stores.trackStore,
|
|
90452
|
+
eventStore: stores.eventStore,
|
|
90453
|
+
recomputeImportance: (id) => recomputeTrackImportance({
|
|
90454
|
+
trackStore: stores.trackStore,
|
|
90455
|
+
eventStore: stores.eventStore
|
|
90456
|
+
}, id)
|
|
90457
|
+
}, trackId),
|
|
90089
90458
|
captureCrop: (frameHandle, bbox, frameWidth, frameHeight, padding, ownerId, deviceId) => this.captureScheduler.request({
|
|
90090
90459
|
deviceId,
|
|
90091
90460
|
trackId: ownerId,
|
|
@@ -92040,6 +92409,31 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
92040
92409
|
* tracking/zone/event state never crosses between sources. Emits the
|
|
92041
92410
|
* SAME canonical events, distinguished only by `source`.
|
|
92042
92411
|
*/
|
|
92412
|
+
/**
|
|
92413
|
+
* One line per camera per minute with what the low-score continuation tier
|
|
92414
|
+
* did (D665): boxes offered, tracks continued, holds that ended in a
|
|
92415
|
+
* confident box again (`resumed` — an id the tier kept), holds cut at the
|
|
92416
|
+
* cap. Never one line per box. Silent while nothing was offered.
|
|
92417
|
+
*/
|
|
92418
|
+
reportLowScoreContinuation(deviceId, key, processor) {
|
|
92419
|
+
const now = Date.now();
|
|
92420
|
+
const last = this.lowScoreReportAtByKey.get(key);
|
|
92421
|
+
if (last === void 0) {
|
|
92422
|
+
this.lowScoreReportAtByKey.set(key, now);
|
|
92423
|
+
return;
|
|
92424
|
+
}
|
|
92425
|
+
if (now - last < LOW_SCORE_REPORT_INTERVAL_MS) return;
|
|
92426
|
+
this.lowScoreReportAtByKey.set(key, now);
|
|
92427
|
+
const stats = processor.drainLowScoreStats();
|
|
92428
|
+
if (stats.offered === 0 && stats.sustainCapDrops === 0) return;
|
|
92429
|
+
this.ctx.logger.info("tracker: low-score continuation", {
|
|
92430
|
+
tags: { deviceId },
|
|
92431
|
+
meta: {
|
|
92432
|
+
...stats,
|
|
92433
|
+
windowMs: now - last
|
|
92434
|
+
}
|
|
92435
|
+
});
|
|
92436
|
+
}
|
|
92043
92437
|
async processFrame(deviceId, frame, source, rawFrameHandle, detailSteps, occasion = "session") {
|
|
92044
92438
|
if (this.shuttingDown) return;
|
|
92045
92439
|
if (!await this.bindingCache.isActive(deviceId)) {
|
|
@@ -92064,7 +92458,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
92064
92458
|
const trk = await this.resolveDeviceTrackingSettings(deviceId);
|
|
92065
92459
|
const activeCount = this.lastActiveTrackIds.get(key)?.size ?? 0;
|
|
92066
92460
|
const skips = this.dropoutSkipsByKey.get(key) ?? 0;
|
|
92067
|
-
|
|
92461
|
+
const lowScoreFloor = trk.lowScoreContinuationEnabled ? trk.lowScoreContinuationFloor : null;
|
|
92462
|
+
if (shouldSkipDropoutFrame(activeCount, dropoutEvidenceCount(frame, lowScoreFloor), skips, {
|
|
92068
92463
|
enabled: trk.dropoutSkipEnabled,
|
|
92069
92464
|
maxSkipFrames: trk.dropoutMaxSkipFrames
|
|
92070
92465
|
})) {
|
|
@@ -92092,6 +92487,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
92092
92487
|
processor.setZones(liveZones);
|
|
92093
92488
|
processor.setDetectionRules(liveRules);
|
|
92094
92489
|
processor.setZoneMembershipMinOverlap(trk.zoneMembershipMinOverlap);
|
|
92490
|
+
processor.setLowScoreContinuationFloor(lowScoreFloor);
|
|
92095
92491
|
const injection = injectSecondarySubjects(frame, this.secondarySubjects.take(key, Date.now()));
|
|
92096
92492
|
for (const id of injection.covered) this.secondarySubjects.drop(key, id);
|
|
92097
92493
|
const trackerFrame = injection.frame;
|
|
@@ -92107,6 +92503,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
92107
92503
|
timestamp: frame.timestamp,
|
|
92108
92504
|
frame: trackerFrame
|
|
92109
92505
|
});
|
|
92506
|
+
this.reportLowScoreContinuation(deviceId, key, processor);
|
|
92110
92507
|
for (const r of processor.getLastLabelWriteRefusals()) {
|
|
92111
92508
|
const message = r.reason.kind === "macro-class" ? "label tier collapse refused" : "label write refused";
|
|
92112
92509
|
this.ctx.logger.info(message, {
|
|
@@ -95794,7 +96191,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
95794
96191
|
animal: trk.minSpawnAreaFracAnimal,
|
|
95795
96192
|
vehicle: trk.minSpawnAreaFracVehicle
|
|
95796
96193
|
},
|
|
95797
|
-
classGroupAssoc: trk.classGroupAssoc
|
|
96194
|
+
classGroupAssoc: trk.classGroupAssoc,
|
|
96195
|
+
lowScoreContinuationEnabled: trk.lowScoreContinuationEnabled,
|
|
96196
|
+
lowScoreContinuationMaxMs: trk.lowScoreContinuationMaxMs,
|
|
96197
|
+
resurrectionTimeScaled: trk.resurrectionTimeScaled
|
|
95798
96198
|
}, {
|
|
95799
96199
|
stationaryThresholdSec,
|
|
95800
96200
|
...stillSpeedFracPerSec !== void 0 ? { stillSpeedFracPerSec } : {}
|