@camstack/addon-post-analysis 1.2.290 → 1.2.292
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--sFeijO7.js → clip-model-registry-CTY5co6P.js} +35 -4
- package/dist/{clip-model-registry-CoYBCvtr.mjs → clip-model-registry-Cudz--Ql.mjs} +35 -4
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-r3ZZOSzj.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CBHxwCSj.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-PoeIZAFx.mjs → hostInit-DSKT2dE4.mjs} +1 -1
- package/dist/pipeline-analytics/index.js +670 -83
- package/dist/pipeline-analytics/index.mjs +670 -83
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NcRuleSchema, $n as EventCategory, $t as isSourceCap, A as LabelAttributionSchema, An as canonicalHash, 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 CamProfileSchema, Mt as buildEventKindDescriptor, N as MAX_BIRTH_DECISION_RECORDS, Nn as DeviceType, 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 BaseAddon, jt as audioModeOf, k as FailureCounters, kn as errMsg$1, 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-
|
|
1
|
+
import { $ as NcRuleSchema, $n as EventCategory, $t as isSourceCap, A as LabelAttributionSchema, An as canonicalHash, 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 CamProfileSchema, Mt as buildEventKindDescriptor, N as MAX_BIRTH_DECISION_RECORDS, Nn as DeviceType, 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 BaseAddon, jt as audioModeOf, k as FailureCounters, kn as errMsg$1, 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-Cudz--Ql.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";
|
|
@@ -39654,7 +39654,12 @@ var RebuildInputSchema = object({
|
|
|
39654
39654
|
* Milliseconds between tracks; omit for the default, `0` to run flat out.
|
|
39655
39655
|
* A rebuild shares hub-main's only thread with every live request.
|
|
39656
39656
|
*/
|
|
39657
|
-
pacingMs: number().int().nonnegative().optional()
|
|
39657
|
+
pacingMs: number().int().nonnegative().optional(),
|
|
39658
|
+
/**
|
|
39659
|
+
* Re-embed ONLY tracks already in the pinned space — to redo vectors cut
|
|
39660
|
+
* from the wrong box (D664). Requires `maxTracks`.
|
|
39661
|
+
*/
|
|
39662
|
+
force: boolean().optional()
|
|
39658
39663
|
});
|
|
39659
39664
|
var RebuildStartedSchema = object({
|
|
39660
39665
|
started: boolean(),
|
|
@@ -39674,7 +39679,20 @@ var RebuildStatusSchema = object({
|
|
|
39674
39679
|
scanned: number(),
|
|
39675
39680
|
rebuilt: number(),
|
|
39676
39681
|
missingKeyFrame: number(),
|
|
39682
|
+
/** No object event at all — its own number since D664. */
|
|
39683
|
+
missingEvent: number(),
|
|
39677
39684
|
missingBbox: number(),
|
|
39685
|
+
/** Which rule placed each judged track's box (D664). */
|
|
39686
|
+
boxSource: object({
|
|
39687
|
+
subjectBox: number(),
|
|
39688
|
+
positions: number(),
|
|
39689
|
+
event: number(),
|
|
39690
|
+
refused: number()
|
|
39691
|
+
}),
|
|
39692
|
+
alreadyInSpace: number(),
|
|
39693
|
+
/** Whether the current / last pass was a `force` pass (D664). */
|
|
39694
|
+
force: boolean().nullable(),
|
|
39695
|
+
forcedInSpace: number(),
|
|
39678
39696
|
/** Refused by the executing node (unreadable key frame / bad box), not broken. */
|
|
39679
39697
|
notRunnable: number(),
|
|
39680
39698
|
/** The pass STOPPED: no node in the cluster could serve the pinned model. */
|
|
@@ -40949,7 +40967,7 @@ function toPixelBox(b) {
|
|
|
40949
40967
|
h: b.height
|
|
40950
40968
|
};
|
|
40951
40969
|
}
|
|
40952
|
-
function refused(reason, detail) {
|
|
40970
|
+
function refused$1(reason, detail) {
|
|
40953
40971
|
return {
|
|
40954
40972
|
kind: "refused",
|
|
40955
40973
|
reason,
|
|
@@ -40977,8 +40995,8 @@ function comparableFaces(input) {
|
|
|
40977
40995
|
/** Refuse before choosing when nothing in the photo can become the sample. */
|
|
40978
40996
|
function precondition(input) {
|
|
40979
40997
|
const enrollable = input.faces.filter((f) => f.enrollable && f.embedding !== void 0);
|
|
40980
|
-
if (enrollable.length === 0) return refused("no-enrollable-face", `${input.faces.length} face(s) found, none enrollable (${input.faces.map((f) => f.refusedReason ?? "unknown").join(", ")})`);
|
|
40981
|
-
if (!enrollable.some((f) => isEnrollableIn(f, input.modelId))) return refused("model-mismatch", `faces were embedded by ${[...new Set(enrollable.map((f) => f.modelId))].join(", ")}, the old sample by ${input.modelId} — re-embed the gallery first`);
|
|
40998
|
+
if (enrollable.length === 0) return refused$1("no-enrollable-face", `${input.faces.length} face(s) found, none enrollable (${input.faces.map((f) => f.refusedReason ?? "unknown").join(", ")})`);
|
|
40999
|
+
if (!enrollable.some((f) => isEnrollableIn(f, input.modelId))) return refused$1("model-mismatch", `faces were embedded by ${[...new Set(enrollable.map((f) => f.modelId))].join(", ")}, the old sample by ${input.modelId} — re-embed the gallery first`);
|
|
40982
41000
|
return null;
|
|
40983
41001
|
}
|
|
40984
41002
|
function chooseByIou(input, oldBox, scored) {
|
|
@@ -40987,13 +41005,13 @@ function chooseByIou(input, oldBox, scored) {
|
|
|
40987
41005
|
face,
|
|
40988
41006
|
iou: iou$3(box, toPixelBox(face.bbox))
|
|
40989
41007
|
})).filter((c) => c.iou >= PHOTO_REENROL_MIN_IOU);
|
|
40990
|
-
if (overlapping.length === 0) return refused("no-iou-match", `no face overlaps the old box at IoU ≥ ${PHOTO_REENROL_MIN_IOU}`);
|
|
40991
|
-
if (overlapping.length > 1) return refused("ambiguous", `${overlapping.length} faces overlap the old box (IoU ${overlapping.map((c) => c.iou.toFixed(2)).join(", ")})`);
|
|
41008
|
+
if (overlapping.length === 0) return refused$1("no-iou-match", `no face overlaps the old box at IoU ≥ ${PHOTO_REENROL_MIN_IOU}`);
|
|
41009
|
+
if (overlapping.length > 1) return refused$1("ambiguous", `${overlapping.length} faces overlap the old box (IoU ${overlapping.map((c) => c.iou.toFixed(2)).join(", ")})`);
|
|
40992
41010
|
const [only] = overlapping;
|
|
40993
|
-
if (only === void 0) return refused("no-iou-match", "no overlapping face");
|
|
40994
|
-
if (!isEnrollableIn(only.face, input.modelId)) return refused("no-enrollable-face", `the face on the old box is not enrollable (${only.face.refusedReason ?? `model ${only.face.modelId ?? "none"}`})`);
|
|
41011
|
+
if (only === void 0) return refused$1("no-iou-match", "no overlapping face");
|
|
41012
|
+
if (!isEnrollableIn(only.face, input.modelId)) return refused$1("no-enrollable-face", `the face on the old box is not enrollable (${only.face.refusedReason ?? `model ${only.face.modelId ?? "none"}`})`);
|
|
40995
41013
|
const cosine = scored.find((s) => s.face === only.face)?.cosine;
|
|
40996
|
-
if (cosine === void 0 || cosine < .5) return refused("below-floor", `the overlapping face scores cosine ${cosine?.toFixed(3) ?? "n/a"} < ${PHOTO_REENROL_COSINE_FLOOR}`);
|
|
41014
|
+
if (cosine === void 0 || cosine < .5) return refused$1("below-floor", `the overlapping face scores cosine ${cosine?.toFixed(3) ?? "n/a"} < ${PHOTO_REENROL_COSINE_FLOOR}`);
|
|
40997
41015
|
return {
|
|
40998
41016
|
kind: "chosen",
|
|
40999
41017
|
face: only.face,
|
|
@@ -41004,10 +41022,10 @@ function chooseByIou(input, oldBox, scored) {
|
|
|
41004
41022
|
}
|
|
41005
41023
|
function chooseByCosine(input, scored) {
|
|
41006
41024
|
const [best, runnerUp] = [...scored].toSorted((a, b) => b.cosine - a.cosine);
|
|
41007
|
-
if (best === void 0) return refused("model-mismatch", "no face has a comparable vector");
|
|
41008
|
-
if (best.cosine < .5) return refused("below-floor", `best cosine ${best.cosine.toFixed(3)} < ${PHOTO_REENROL_COSINE_FLOOR}`);
|
|
41009
|
-
if (runnerUp !== void 0 && runnerUp.cosine >= .5 && best.cosine - runnerUp.cosine < .1) return refused("ambiguous", `two faces clear the floor within ${PHOTO_REENROL_AMBIGUITY_MARGIN}: ${best.cosine.toFixed(3)} vs ${runnerUp.cosine.toFixed(3)}`);
|
|
41010
|
-
if (!isEnrollableIn(best.face, input.modelId)) return refused("ambiguous", `the closest face (cosine ${best.cosine.toFixed(3)}) is not enrollable (${best.face.refusedReason ?? "unknown"}) — the enrollable ones may be someone else`);
|
|
41025
|
+
if (best === void 0) return refused$1("model-mismatch", "no face has a comparable vector");
|
|
41026
|
+
if (best.cosine < .5) return refused$1("below-floor", `best cosine ${best.cosine.toFixed(3)} < ${PHOTO_REENROL_COSINE_FLOOR}`);
|
|
41027
|
+
if (runnerUp !== void 0 && runnerUp.cosine >= .5 && best.cosine - runnerUp.cosine < .1) return refused$1("ambiguous", `two faces clear the floor within ${PHOTO_REENROL_AMBIGUITY_MARGIN}: ${best.cosine.toFixed(3)} vs ${runnerUp.cosine.toFixed(3)}`);
|
|
41028
|
+
if (!isEnrollableIn(best.face, input.modelId)) return refused$1("ambiguous", `the closest face (cosine ${best.cosine.toFixed(3)}) is not enrollable (${best.face.refusedReason ?? "unknown"}) — the enrollable ones may be someone else`);
|
|
41011
41029
|
return {
|
|
41012
41030
|
kind: "chosen",
|
|
41013
41031
|
face: best.face,
|
|
@@ -47011,6 +47029,42 @@ function pairRiders(detections) {
|
|
|
47011
47029
|
return pairs;
|
|
47012
47030
|
}
|
|
47013
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
|
|
47014
47068
|
//#region src/pipeline-analytics/pipeline/track-id.ts
|
|
47015
47069
|
/**
|
|
47016
47070
|
* A track's identity: short, random, and mintable inside a frame.
|
|
@@ -47159,6 +47213,30 @@ function reacquireReachPx(bbox, elapsedMs) {
|
|
|
47159
47213
|
const elapsed = Math.max(0, elapsedMs);
|
|
47160
47214
|
return clampRange(diag * REACH_DIAGONALS_PER_SEC * (elapsed / 1e3), diag * REACH_MIN_DIAGONALS, diag * REACH_MAX_DIAGONALS);
|
|
47161
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
|
+
}
|
|
47162
47240
|
//#endregion
|
|
47163
47241
|
//#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
|
|
47164
47242
|
/**
|
|
@@ -47307,7 +47385,10 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
47307
47385
|
byteTrackLowIouThreshold: .2,
|
|
47308
47386
|
minSpawnAreaEnabled: true,
|
|
47309
47387
|
classSpawnAreaFracs: { person: 5e-4 },
|
|
47310
|
-
classGroupAssoc: true
|
|
47388
|
+
classGroupAssoc: true,
|
|
47389
|
+
lowScoreContinuationEnabled: true,
|
|
47390
|
+
lowScoreContinuationMaxMs: 8e3,
|
|
47391
|
+
resurrectionTimeScaled: true
|
|
47311
47392
|
};
|
|
47312
47393
|
/** Macro-classes the duplicate resolver may collapse across (people in
|
|
47313
47394
|
* non-upright poses misdetected as animals). Any other cross-class pair is
|
|
@@ -47315,6 +47396,15 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
47315
47396
|
var PERSON_CLASS = "person";
|
|
47316
47397
|
var ANIMAL_CLASS = "animal";
|
|
47317
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
|
+
}
|
|
47318
47408
|
/** No stationary-held detections on this frame — the default for every caller
|
|
47319
47409
|
* that has no gate (replay, tests, the non-pipeline planes). */
|
|
47320
47410
|
var EMPTY_INDEX_SET = /* @__PURE__ */ new Set();
|
|
@@ -47386,6 +47476,8 @@ var SortTracker = class SortTracker {
|
|
|
47386
47476
|
trackLossSampler = new TrackLossSampler();
|
|
47387
47477
|
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
47388
47478
|
spawnBesideObserver = null;
|
|
47479
|
+
/** Running counts behind {@link drainLowScoreStats} (D665). */
|
|
47480
|
+
lowScoreStats = emptyLowScoreStats();
|
|
47389
47481
|
constructor(config = {}) {
|
|
47390
47482
|
this.config = {
|
|
47391
47483
|
...DEFAULT_TRACKER_CONFIG,
|
|
@@ -47446,7 +47538,7 @@ var SortTracker = class SortTracker {
|
|
|
47446
47538
|
if (SortTracker.spawnBesideFamily(track.class) !== family) continue;
|
|
47447
47539
|
const tc = bboxCentroid(track.bbox);
|
|
47448
47540
|
const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
|
|
47449
|
-
const reach = this.reacquireReach(track, timestamp,
|
|
47541
|
+
const reach = this.reacquireReach(track, timestamp, "rescue");
|
|
47450
47542
|
if (dist > reach * 2) continue;
|
|
47451
47543
|
if (dist < bestDist) {
|
|
47452
47544
|
best = track;
|
|
@@ -47493,7 +47585,7 @@ var SortTracker = class SortTracker {
|
|
|
47493
47585
|
explainDecline(track, det, detections, frameArea, timestamp, centroidDist) {
|
|
47494
47586
|
if (track.class !== det.class) return det.nativeRecovery !== void 0 ? "native-measured-class" : "cross-class";
|
|
47495
47587
|
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "already-claimed";
|
|
47496
|
-
if (centroidDist <= this.reacquireReach(track, timestamp,
|
|
47588
|
+
if (centroidDist <= this.reacquireReach(track, timestamp, "rescue")) {
|
|
47497
47589
|
if (!this.isPlausibleBirth(det, frameArea)) return "already-claimed";
|
|
47498
47590
|
if (!this.config.reacquireTimeScaled) return "plausible-birth-veto";
|
|
47499
47591
|
return this.isUnambiguousReach(track, det, detections) ? "already-claimed" : "crowd-guard";
|
|
@@ -47948,34 +48040,38 @@ var SortTracker = class SortTracker {
|
|
|
47948
48040
|
* this gate crossed the group, and it is where a `person` at 0.86 took over
|
|
47949
48041
|
* an animal's id (camera 615, 17:11:01).
|
|
47950
48042
|
*/
|
|
47951
|
-
looseMatch(track, det, timestamp,
|
|
48043
|
+
looseMatch(track, det, timestamp, pass = "rescue") {
|
|
47952
48044
|
if (track.class !== det.class) return null;
|
|
47953
48045
|
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
|
|
47954
48046
|
const tc = bboxCentroid(track.bbox);
|
|
47955
48047
|
const dc = bboxCentroid(det.bbox);
|
|
47956
|
-
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";
|
|
47957
48049
|
if (growthAdjacentMatch(track.bbox, det.bbox, this.growthContext(track, timestamp))) return "growth";
|
|
47958
48050
|
return null;
|
|
47959
48051
|
}
|
|
47960
48052
|
/**
|
|
47961
48053
|
* The proximity budget the `centroid` clause is measured against.
|
|
47962
48054
|
*
|
|
47963
|
-
* On the RESCUE pass
|
|
47964
|
-
*
|
|
47965
|
-
*
|
|
47966
|
-
*
|
|
47967
|
-
*
|
|
47968
|
-
* On the RESURRECTION pass it
|
|
47969
|
-
*
|
|
47970
|
-
*
|
|
47971
|
-
*
|
|
47972
|
-
*
|
|
47973
|
-
*
|
|
47974
|
-
*
|
|
47975
|
-
|
|
47976
|
-
|
|
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) {
|
|
47977
48072
|
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
47978
|
-
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;
|
|
47979
48075
|
return reacquireReachPx(track.bbox, timestamp - track.lastSeen);
|
|
47980
48076
|
}
|
|
47981
48077
|
/**
|
|
@@ -48025,6 +48121,23 @@ var SortTracker = class SortTracker {
|
|
|
48025
48121
|
nearestPx: nearest
|
|
48026
48122
|
};
|
|
48027
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
|
+
}
|
|
48028
48141
|
isUnambiguousReach(track, det, detections) {
|
|
48029
48142
|
if (this.sameClassPeerCount(track, this.tracks) > 0) return false;
|
|
48030
48143
|
for (const other of detections) {
|
|
@@ -48179,6 +48292,64 @@ var SortTracker = class SortTracker {
|
|
|
48179
48292
|
used.add(pair.detIdx);
|
|
48180
48293
|
}
|
|
48181
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
|
+
}
|
|
48182
48353
|
update(detections, timestamp, frameContext) {
|
|
48183
48354
|
this.frameSeq++;
|
|
48184
48355
|
const frameArea = frameContext ? frameContext.frameWidth * frameContext.frameHeight : 0;
|
|
@@ -48236,6 +48407,9 @@ var SortTracker = class SortTracker {
|
|
|
48236
48407
|
this.rescueAssociate(established, PROXIMITY, detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
48237
48408
|
this.rescueAssociate(provisional, new Set([...OVERLAP, ...PROXIMITY]), detections, lowSet, frameArea, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
48238
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);
|
|
48239
48413
|
for (const [track, det] of matched) {
|
|
48240
48414
|
if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, {
|
|
48241
48415
|
against: track,
|
|
@@ -48266,6 +48440,7 @@ var SortTracker = class SortTracker {
|
|
|
48266
48440
|
track.hits++;
|
|
48267
48441
|
track.lastSeen = timestamp;
|
|
48268
48442
|
track.lastSeenFrameSeq = this.frameSeq;
|
|
48443
|
+
this.markConfident(track, det.bbox, timestamp);
|
|
48269
48444
|
if (dtMs > 0) {
|
|
48270
48445
|
track.velocity = {
|
|
48271
48446
|
dx: (newCenter.x - prevCenter.x) / dtMs,
|
|
@@ -48290,10 +48465,15 @@ var SortTracker = class SortTracker {
|
|
|
48290
48465
|
}
|
|
48291
48466
|
const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track, timestamp), ob.bbox) >= this.config.occlusionContainment);
|
|
48292
48467
|
track.age++;
|
|
48293
|
-
|
|
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) {
|
|
48294
48473
|
track.lost = true;
|
|
48295
48474
|
track.lostAt = timestamp;
|
|
48296
48475
|
track.resurrectable = true;
|
|
48476
|
+
track.lowScoreHeld = false;
|
|
48297
48477
|
this.lostTracks.push(track);
|
|
48298
48478
|
} else surviving.push(track);
|
|
48299
48479
|
}
|
|
@@ -48304,15 +48484,17 @@ var SortTracker = class SortTracker {
|
|
|
48304
48484
|
let bestScore = -1;
|
|
48305
48485
|
let bestGate = "iou";
|
|
48306
48486
|
let bestPeers = 0;
|
|
48487
|
+
let bestVetoLifted = false;
|
|
48307
48488
|
let refused;
|
|
48308
48489
|
let refusedGate = "iou";
|
|
48309
48490
|
let refusedScore = -1;
|
|
48310
48491
|
for (const lost of this.lostTracks) {
|
|
48311
48492
|
if (!lost.resurrectable) continue;
|
|
48312
48493
|
if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
|
|
48313
|
-
const gate = this.looseMatch(lost, det, timestamp,
|
|
48494
|
+
const gate = this.looseMatch(lost, det, timestamp, "resurrection");
|
|
48314
48495
|
if (gate === null) continue;
|
|
48315
|
-
|
|
48496
|
+
const vetoLifted = gate === "centroid" && this.isPlausibleBirth(det, frameArea);
|
|
48497
|
+
if (vetoLifted && !(this.config.resurrectionTimeScaled && this.isSoleSubjectResurrection(lost, det, detections, surviving, lowScore))) continue;
|
|
48316
48498
|
const score = iou$2(lost.bbox, det.bbox);
|
|
48317
48499
|
const peers = this.sameClassPeerCount(lost, surviving);
|
|
48318
48500
|
if (this.config.resurrectionCrowdGuard && peers > 0 && score < this.config.resurrectionCrowdedIouThreshold) {
|
|
@@ -48328,13 +48510,14 @@ var SortTracker = class SortTracker {
|
|
|
48328
48510
|
bestScore = score;
|
|
48329
48511
|
bestGate = gate;
|
|
48330
48512
|
bestPeers = peers;
|
|
48513
|
+
bestVetoLifted = vetoLifted;
|
|
48331
48514
|
}
|
|
48332
48515
|
}
|
|
48333
48516
|
if (!best) {
|
|
48334
48517
|
if (refused !== void 0) this.reportBind("resurrection-refused", refusedGate, refused, det, timestamp);
|
|
48335
48518
|
continue;
|
|
48336
48519
|
}
|
|
48337
|
-
resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : "sole-subject");
|
|
48520
|
+
resurrectedScene.set(best, bestPeers > 0 ? "multi-subject" : bestVetoLifted ? "sole-subject-veto-lifted" : "sole-subject");
|
|
48338
48521
|
this.reportBind("resurrection", bestGate, best, det, timestamp);
|
|
48339
48522
|
this.lostTracks = this.lostTracks.filter((t) => t !== best);
|
|
48340
48523
|
best.bbox = det.bbox;
|
|
@@ -48348,6 +48531,7 @@ var SortTracker = class SortTracker {
|
|
|
48348
48531
|
best.lost = false;
|
|
48349
48532
|
best.lastSeen = timestamp;
|
|
48350
48533
|
best.lastSeenFrameSeq = this.frameSeq;
|
|
48534
|
+
this.markConfident(best, det.bbox, timestamp);
|
|
48351
48535
|
best.velocity = {
|
|
48352
48536
|
dx: 0,
|
|
48353
48537
|
dy: 0
|
|
@@ -48424,7 +48608,10 @@ var SortTracker = class SortTracker {
|
|
|
48424
48608
|
classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]]),
|
|
48425
48609
|
emitted: false,
|
|
48426
48610
|
lossReported: false,
|
|
48427
|
-
lastNativeRecovery: det.nativeRecovery
|
|
48611
|
+
lastNativeRecovery: det.nativeRecovery,
|
|
48612
|
+
confidentBbox: det.bbox,
|
|
48613
|
+
confidentSeenAt: timestamp,
|
|
48614
|
+
lowScoreHeld: false
|
|
48428
48615
|
});
|
|
48429
48616
|
}
|
|
48430
48617
|
this.tracks = surviving;
|
|
@@ -48432,11 +48619,12 @@ var SortTracker = class SortTracker {
|
|
|
48432
48619
|
return this.tracks.filter((t) => this.isConfirmedForEmit(t)).map((t) => {
|
|
48433
48620
|
t.emitted = true;
|
|
48434
48621
|
const scene = resurrectedScene.get(t);
|
|
48622
|
+
const held = t.lowScoreHeld;
|
|
48435
48623
|
return {
|
|
48436
48624
|
class: this.resolveReportedClass(t),
|
|
48437
48625
|
originalClass: t.originalClass,
|
|
48438
48626
|
score: t.score,
|
|
48439
|
-
bbox: t.bbox,
|
|
48627
|
+
bbox: held ? t.confidentBbox : t.bbox,
|
|
48440
48628
|
trackId: t.id,
|
|
48441
48629
|
trackAge: t.hits,
|
|
48442
48630
|
velocity: {
|
|
@@ -48444,8 +48632,9 @@ var SortTracker = class SortTracker {
|
|
|
48444
48632
|
dy: t.velocity.dy * t.emitIntervalMs
|
|
48445
48633
|
},
|
|
48446
48634
|
path: [...t.path],
|
|
48447
|
-
matchedThisFrame: t.lastSeen === timestamp,
|
|
48448
|
-
...scene !== void 0 ? { resurrectionScene: scene } : {}
|
|
48635
|
+
matchedThisFrame: t.lastSeen === timestamp && !held,
|
|
48636
|
+
...scene !== void 0 ? { resurrectionScene: scene } : {},
|
|
48637
|
+
...held ? { heldByLowScore: true } : {}
|
|
48449
48638
|
};
|
|
48450
48639
|
});
|
|
48451
48640
|
}
|
|
@@ -48459,6 +48648,16 @@ var SortTracker = class SortTracker {
|
|
|
48459
48648
|
this.tracks = this.tracks.filter((t) => t.id !== trackId);
|
|
48460
48649
|
this.lostTracks = this.lostTracks.filter((t) => t.id !== trackId);
|
|
48461
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
|
+
}
|
|
48462
48661
|
getActiveTracks() {
|
|
48463
48662
|
return this.tracks;
|
|
48464
48663
|
}
|
|
@@ -48833,6 +49032,8 @@ var FrameProcessor = class {
|
|
|
48833
49032
|
detectionRules;
|
|
48834
49033
|
/** See {@link setZoneMembershipMinOverlap}. 0 = any positive overlap. */
|
|
48835
49034
|
zoneMembershipMinOverlap;
|
|
49035
|
+
/** See {@link setLowScoreContinuationFloor}. `null` = tier off. */
|
|
49036
|
+
lowScoreFloor = LOW_SCORE_CONTINUATION_FLOOR_DEFAULT;
|
|
48836
49037
|
/** See {@link getLastZoneOverlaps}. */
|
|
48837
49038
|
lastZoneOverlaps;
|
|
48838
49039
|
/** See {@link getLastZoneRejections}. */
|
|
@@ -48945,6 +49146,18 @@ var FrameProcessor = class {
|
|
|
48945
49146
|
* failure mode this whole area keeps producing. {@link lastZoneOverlaps}
|
|
48946
49147
|
* exists so the distribution can be read before a number is picked.
|
|
48947
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
|
+
}
|
|
48948
49161
|
setZoneMembershipMinOverlap(minOverlap) {
|
|
48949
49162
|
this.zoneMembershipMinOverlap = Number.isFinite(minOverlap) && minOverlap >= 0 && minOverlap <= 1 ? minOverlap : 0;
|
|
48950
49163
|
}
|
|
@@ -49337,10 +49550,16 @@ var FrameProcessor = class {
|
|
|
49337
49550
|
}
|
|
49338
49551
|
trackerInput.push(det);
|
|
49339
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);
|
|
49340
49558
|
const trackedDetections = this.tracker.update(trackerInput, timestamp, {
|
|
49341
49559
|
frameWidth,
|
|
49342
49560
|
frameHeight,
|
|
49343
|
-
continuationOnly
|
|
49561
|
+
continuationOnly,
|
|
49562
|
+
lowScore
|
|
49344
49563
|
});
|
|
49345
49564
|
const stationaryContinued = continuableBoxes.size === 0 ? 0 : trackedDetections.filter((t) => t.matchedThisFrame !== false && continuableBoxes.has(t.bbox)).length;
|
|
49346
49565
|
const riderFolds = foldByVehicleBbox.size === 0 ? EMPTY_RIDER_FOLDS : [...foldByVehicleBbox].map(([bbox, fold]) => {
|
|
@@ -49582,6 +49801,19 @@ var FrameProcessor = class {
|
|
|
49582
49801
|
}
|
|
49583
49802
|
};
|
|
49584
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
|
|
49585
49817
|
//#region src/pipeline-analytics/pipeline/track-provenance.ts
|
|
49586
49818
|
function trackProvenanceOf(frame) {
|
|
49587
49819
|
if (!(frame.width > 0 && frame.height > 0)) return void 0;
|
|
@@ -53721,11 +53953,10 @@ var EventStore = class {
|
|
|
53721
53953
|
let entryId;
|
|
53722
53954
|
for (const row of rows) {
|
|
53723
53955
|
const bbox = row.data["bbox"];
|
|
53724
|
-
if (bbox
|
|
53725
|
-
|
|
53726
|
-
|
|
53727
|
-
|
|
53728
|
-
}
|
|
53956
|
+
if (bbox === null || typeof bbox !== "object") continue;
|
|
53957
|
+
const bw = "w" in bbox && typeof bbox.w === "number" ? bbox.w : 0;
|
|
53958
|
+
const bh = "h" in bbox && typeof bbox.h === "number" ? bbox.h : 0;
|
|
53959
|
+
if (bw <= 0 || bh <= 0) continue;
|
|
53729
53960
|
const conf = typeof row.data["confidence"] === "number" ? row.data["confidence"] : 0;
|
|
53730
53961
|
if (conf > bestConf) {
|
|
53731
53962
|
bestConf = conf;
|
|
@@ -62377,6 +62608,26 @@ var EmbeddingDispatcher = class {
|
|
|
62377
62608
|
return this.pendingCrops.size;
|
|
62378
62609
|
}
|
|
62379
62610
|
};
|
|
62611
|
+
//#endregion
|
|
62612
|
+
//#region src/pipeline-analytics/embedding-rebuild.ts
|
|
62613
|
+
/**
|
|
62614
|
+
* Track sources a rebuild never lists (D664). An audio track and a synthetic
|
|
62615
|
+
* sensor track have no object event and no subject to embed — on the live hub
|
|
62616
|
+
* 6,726 + 180 of 42,254 tracks — and walking them only inflated the skip
|
|
62617
|
+
* counters an operator reads to judge media coverage.
|
|
62618
|
+
*/
|
|
62619
|
+
var REBUILD_EXCLUDED_SOURCES = ["audio", "sensor"];
|
|
62620
|
+
/**
|
|
62621
|
+
* One page of the rebuild listing as a `queryRecentIds` query. Pure, so the
|
|
62622
|
+
* exclusion is assertable: dropping it would look identical in every result
|
|
62623
|
+
* except the skip counters.
|
|
62624
|
+
*/
|
|
62625
|
+
function rebuildTrackPageQuery(window) {
|
|
62626
|
+
return {
|
|
62627
|
+
...window,
|
|
62628
|
+
excludeSources: REBUILD_EXCLUDED_SOURCES
|
|
62629
|
+
};
|
|
62630
|
+
}
|
|
62380
62631
|
/**
|
|
62381
62632
|
* Re-embed every listed track from its key frame.
|
|
62382
62633
|
*
|
|
@@ -62392,6 +62643,14 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62392
62643
|
let recoveredKeyFrame = 0;
|
|
62393
62644
|
let missingEvent = 0;
|
|
62394
62645
|
let missingBbox = 0;
|
|
62646
|
+
let boxSubject = 0;
|
|
62647
|
+
let boxPositions = 0;
|
|
62648
|
+
let boxEvent = 0;
|
|
62649
|
+
let boxRefused = 0;
|
|
62650
|
+
let forcedInSpace = 0;
|
|
62651
|
+
let skippedOutOfSpace = 0;
|
|
62652
|
+
const refusalsByDevice = /* @__PURE__ */ new Map();
|
|
62653
|
+
const force = options.force === true;
|
|
62395
62654
|
let alreadyInSpace = 0;
|
|
62396
62655
|
let notRunnable = 0;
|
|
62397
62656
|
let noCapableNode = 0;
|
|
@@ -62402,6 +62661,12 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62402
62661
|
let budgetExhausted = false;
|
|
62403
62662
|
/** Set when the cluster ran out of nodes — the pass stops, `complete` is false. */
|
|
62404
62663
|
let abandoned = null;
|
|
62664
|
+
const boxSourceCounts = () => ({
|
|
62665
|
+
subjectBox: boxSubject,
|
|
62666
|
+
positions: boxPositions,
|
|
62667
|
+
event: boxEvent,
|
|
62668
|
+
refused: boxRefused
|
|
62669
|
+
});
|
|
62405
62670
|
while (scanned < maxTracks && abandoned === null) {
|
|
62406
62671
|
const page = await deps.listTrackIdPage(cursor);
|
|
62407
62672
|
if (page.trackIds.length === 0) {
|
|
@@ -62409,9 +62674,14 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62409
62674
|
break;
|
|
62410
62675
|
}
|
|
62411
62676
|
let pageIds = page.trackIds;
|
|
62677
|
+
let forcedPage = false;
|
|
62412
62678
|
if (deps.filterOutOfSpace) {
|
|
62413
62679
|
const split = await deps.filterOutOfSpace(page.trackIds);
|
|
62414
|
-
if (split !== null) {
|
|
62680
|
+
if (split !== null && force) {
|
|
62681
|
+
skippedOutOfSpace += split.outOfSpace.length;
|
|
62682
|
+
pageIds = split.inSpace;
|
|
62683
|
+
forcedPage = true;
|
|
62684
|
+
} else if (split !== null) {
|
|
62415
62685
|
alreadyInSpace += split.alreadyInSpace;
|
|
62416
62686
|
pageIds = split.outOfSpace;
|
|
62417
62687
|
}
|
|
@@ -62427,6 +62697,7 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62427
62697
|
const room = maxTracks - scanned;
|
|
62428
62698
|
const batch = pageIds.slice(0, Number.isFinite(room) ? room : void 0);
|
|
62429
62699
|
if (batch.length < pageIds.length) budgetExhausted = true;
|
|
62700
|
+
if (forcedPage) forcedInSpace += batch.length;
|
|
62430
62701
|
for (const trackId of batch) {
|
|
62431
62702
|
if (!deps.canDispatch()) {
|
|
62432
62703
|
abandoned = "no node left in the rotation can serve the pinned model — stopped before loading any further media";
|
|
@@ -62445,6 +62716,25 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62445
62716
|
missingEvent += 1;
|
|
62446
62717
|
return;
|
|
62447
62718
|
}
|
|
62719
|
+
if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
|
|
62720
|
+
missingBbox += 1;
|
|
62721
|
+
boxRefused += 1;
|
|
62722
|
+
const reason = source.boxSource.kind === "refused" ? source.boxSource.reason : "degenerate-box";
|
|
62723
|
+
const n = (refusalsByDevice.get(source.deviceId) ?? 0) + 1;
|
|
62724
|
+
refusalsByDevice.set(source.deviceId, n);
|
|
62725
|
+
if (n === 1 || n % 100 === 0) deps.logger.warn("embedding rebuild: no box measured on the key frame — track refused (D303)", {
|
|
62726
|
+
tags: { deviceId: source.deviceId },
|
|
62727
|
+
meta: {
|
|
62728
|
+
trackId,
|
|
62729
|
+
reason,
|
|
62730
|
+
refusedOnThisCamera: n
|
|
62731
|
+
}
|
|
62732
|
+
});
|
|
62733
|
+
return;
|
|
62734
|
+
}
|
|
62735
|
+
if (source.boxSource.kind === "subjectBox") boxSubject += 1;
|
|
62736
|
+
else if (source.boxSource.kind === "positions") boxPositions += 1;
|
|
62737
|
+
else boxEvent += 1;
|
|
62448
62738
|
let keyFrame = source.keyFrame;
|
|
62449
62739
|
let recovered = false;
|
|
62450
62740
|
if (keyFrame === null && deps.recoverKeyFrame) {
|
|
@@ -62456,10 +62746,6 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62456
62746
|
return;
|
|
62457
62747
|
}
|
|
62458
62748
|
if (recovered) recoveredKeyFrame += 1;
|
|
62459
|
-
if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
|
|
62460
|
-
missingBbox += 1;
|
|
62461
|
-
return;
|
|
62462
|
-
}
|
|
62463
62749
|
const outcome = await deps.embed(recovered ? {
|
|
62464
62750
|
...source,
|
|
62465
62751
|
keyFrame
|
|
@@ -62527,6 +62813,9 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62527
62813
|
recoveredKeyFrame,
|
|
62528
62814
|
missingEvent,
|
|
62529
62815
|
missingBbox,
|
|
62816
|
+
boxSource: boxSourceCounts(),
|
|
62817
|
+
forcedInSpace,
|
|
62818
|
+
skippedOutOfSpace,
|
|
62530
62819
|
alreadyInSpace,
|
|
62531
62820
|
notRunnable,
|
|
62532
62821
|
noCapableNode,
|
|
@@ -62550,6 +62839,10 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62550
62839
|
recoveredKeyFrame,
|
|
62551
62840
|
missingEvent,
|
|
62552
62841
|
missingBbox,
|
|
62842
|
+
boxSource: boxSourceCounts(),
|
|
62843
|
+
force,
|
|
62844
|
+
forcedInSpace,
|
|
62845
|
+
skippedOutOfSpace,
|
|
62553
62846
|
alreadyInSpace,
|
|
62554
62847
|
notRunnable,
|
|
62555
62848
|
noCapableNode,
|
|
@@ -62564,6 +62857,9 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
62564
62857
|
recoveredKeyFrame,
|
|
62565
62858
|
missingEvent,
|
|
62566
62859
|
missingBbox,
|
|
62860
|
+
boxSource: boxSourceCounts(),
|
|
62861
|
+
forcedInSpace,
|
|
62862
|
+
skippedOutOfSpace,
|
|
62567
62863
|
alreadyInSpace,
|
|
62568
62864
|
notRunnable,
|
|
62569
62865
|
noCapableNode,
|
|
@@ -63122,8 +63418,6 @@ function faceSizeScore(detectionFrameShortSidePx) {
|
|
|
63122
63418
|
}
|
|
63123
63419
|
//#endregion
|
|
63124
63420
|
//#region src/pipeline-analytics/face-recognizer.ts
|
|
63125
|
-
/** Attribution of an identity resolved by the in-process ArcFace matcher. */
|
|
63126
|
-
var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
|
|
63127
63421
|
/** At most one "dropping imageless track" log per this interval, per recognizer. */
|
|
63128
63422
|
var FACE_IMAGELESS_LOG_THROTTLE_MS = 6e4;
|
|
63129
63423
|
/** At most one 'embedding magnitude' sample line per device per this interval. */
|
|
@@ -63149,6 +63443,9 @@ var FaceRecognizer = class {
|
|
|
63149
63443
|
gallery = [];
|
|
63150
63444
|
names = /* @__PURE__ */ new Map();
|
|
63151
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();
|
|
63152
63449
|
bestFace = /* @__PURE__ */ new Map();
|
|
63153
63450
|
/** Frame instant of each live track's last source park (D644). */
|
|
63154
63451
|
lastSourceParkAt = /* @__PURE__ */ new Map();
|
|
@@ -63340,9 +63637,35 @@ var FaceRecognizer = class {
|
|
|
63340
63637
|
* • the face becomes non-promotable: {@link onTrackEnd} will not write it to
|
|
63341
63638
|
* the gallery while the flag stands.
|
|
63342
63639
|
*
|
|
63343
|
-
* The
|
|
63344
|
-
|
|
63345
|
-
|
|
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
|
+
}
|
|
63346
63669
|
const held = this.bestFace.get(trackId);
|
|
63347
63670
|
if (held === void 0 || held.unverified === true) return;
|
|
63348
63671
|
const quarantined = {
|
|
@@ -63365,6 +63688,7 @@ var FaceRecognizer = class {
|
|
|
63365
63688
|
trackId
|
|
63366
63689
|
},
|
|
63367
63690
|
meta: {
|
|
63691
|
+
scene,
|
|
63368
63692
|
heldScore: held.score,
|
|
63369
63693
|
heldAt: held.timestamp,
|
|
63370
63694
|
strippedIdentityId: held.recognizedIdentityId ?? null,
|
|
@@ -63373,6 +63697,40 @@ var FaceRecognizer = class {
|
|
|
63373
63697
|
});
|
|
63374
63698
|
}
|
|
63375
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
|
+
/**
|
|
63376
63734
|
* Ask a FRESH face whether the quarantined held face is still about the same
|
|
63377
63735
|
* person, at the same bar the gallery uses to say two vectors are one person
|
|
63378
63736
|
* (`similarityThreshold` — one knob over one band, D222).
|
|
@@ -63394,7 +63752,7 @@ var FaceRecognizer = class {
|
|
|
63394
63752
|
}
|
|
63395
63753
|
async processFrame(input) {
|
|
63396
63754
|
const { settings } = input;
|
|
63397
|
-
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);
|
|
63398
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);
|
|
63399
63757
|
if (candidates.length === 0) return;
|
|
63400
63758
|
this.deps.logger.debug("face: frame candidates", {
|
|
@@ -63461,11 +63819,26 @@ var FaceRecognizer = class {
|
|
|
63461
63819
|
embedding: c.embedding,
|
|
63462
63820
|
embeddingModelId: c.embeddingModelId
|
|
63463
63821
|
}, settings.similarityThreshold);
|
|
63464
|
-
|
|
63465
|
-
|
|
63466
|
-
|
|
63467
|
-
|
|
63468
|
-
|
|
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);
|
|
63469
63842
|
this.bestFace.delete(c.trackId);
|
|
63470
63843
|
this.bestTracker.delete(c.trackId);
|
|
63471
63844
|
this.aggregates.delete(c.trackId);
|
|
@@ -63680,6 +64053,7 @@ var FaceRecognizer = class {
|
|
|
63680
64053
|
const bestMatchScore = this.bestMatchScore.get(trackId);
|
|
63681
64054
|
const bestSuggestion = this.bestSuggestion.get(trackId);
|
|
63682
64055
|
this.aggregates.delete(trackId);
|
|
64056
|
+
this.stashedIdentity.delete(trackId);
|
|
63683
64057
|
this.bestFace.delete(trackId);
|
|
63684
64058
|
this.bestTracker.delete(trackId);
|
|
63685
64059
|
this.bestMatchScore.delete(trackId);
|
|
@@ -67841,6 +68215,19 @@ function shouldSkipDropoutFrame(activeTrackCount, detectionCount, consecutiveSki
|
|
|
67841
68215
|
if (activeTrackCount <= 0) return false;
|
|
67842
68216
|
return consecutiveSkips < cfg.maxSkipFrames;
|
|
67843
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
|
+
}
|
|
67844
68231
|
//#endregion
|
|
67845
68232
|
//#region src/pipeline-analytics/pipeline/key-event-query.ts
|
|
67846
68233
|
async function rankKeyEvents(candidates, options, peakLookup) {
|
|
@@ -72962,6 +73349,58 @@ function hasKeyFrame(source) {
|
|
|
72962
73349
|
}
|
|
72963
73350
|
//#endregion
|
|
72964
73351
|
//#region src/pipeline-analytics/rebuild-source-loader.ts
|
|
73352
|
+
function refused(reason) {
|
|
73353
|
+
return {
|
|
73354
|
+
bbox: null,
|
|
73355
|
+
boxSource: {
|
|
73356
|
+
kind: "refused",
|
|
73357
|
+
reason
|
|
73358
|
+
}
|
|
73359
|
+
};
|
|
73360
|
+
}
|
|
73361
|
+
function normaliseMeasured(box) {
|
|
73362
|
+
if (box.frameWidth <= 0 || box.frameHeight <= 0 || box.w <= 0 || box.h <= 0) return null;
|
|
73363
|
+
return {
|
|
73364
|
+
x: box.x / box.frameWidth,
|
|
73365
|
+
y: box.y / box.frameHeight,
|
|
73366
|
+
w: box.w / box.frameWidth,
|
|
73367
|
+
h: box.h / box.frameHeight
|
|
73368
|
+
};
|
|
73369
|
+
}
|
|
73370
|
+
/**
|
|
73371
|
+
* The box measured on THIS key frame: its own `subjectBox`, else the track
|
|
73372
|
+
* position at exactly its instant, else a named refusal. Never another
|
|
73373
|
+
* frame's box (D303).
|
|
73374
|
+
*/
|
|
73375
|
+
async function keyFrameBox(deps, trackId, keyFrame) {
|
|
73376
|
+
const own = (await deps.media.listSubjectBoxesByOwner("track", trackId)).find((row) => row.mediaId === keyFrame.mediaId);
|
|
73377
|
+
if (own !== void 0) {
|
|
73378
|
+
const bbox = normaliseMeasured(own.subjectBox);
|
|
73379
|
+
if (bbox !== null) return {
|
|
73380
|
+
bbox,
|
|
73381
|
+
boxSource: { kind: "subjectBox" }
|
|
73382
|
+
};
|
|
73383
|
+
}
|
|
73384
|
+
const track = await deps.tracks.getPersistedByTrackId(trackId);
|
|
73385
|
+
if (track === null) return refused("track-row-missing");
|
|
73386
|
+
const { frameWidth, frameHeight } = track;
|
|
73387
|
+
if (frameWidth === void 0 || frameHeight === void 0) return refused("track-frame-size-unknown");
|
|
73388
|
+
const at = track.positions.find((p) => p.timestamp === keyFrame.timestamp);
|
|
73389
|
+
if (at === void 0) return refused("no-position-at-key-frame");
|
|
73390
|
+
const bbox = normaliseMeasured({
|
|
73391
|
+
...at.bbox,
|
|
73392
|
+
frameWidth,
|
|
73393
|
+
frameHeight
|
|
73394
|
+
});
|
|
73395
|
+
if (bbox === null) return refused("no-position-at-key-frame");
|
|
73396
|
+
return {
|
|
73397
|
+
bbox,
|
|
73398
|
+
boxSource: { kind: "positions" }
|
|
73399
|
+
};
|
|
73400
|
+
}
|
|
73401
|
+
function hasBox(event) {
|
|
73402
|
+
return event.bbox !== void 0 && event.bbox.w > 0 && event.bbox.h > 0;
|
|
73403
|
+
}
|
|
72965
73404
|
/**
|
|
72966
73405
|
* Assemble one track's rebuild inputs, reading at most ONE blob.
|
|
72967
73406
|
*
|
|
@@ -72969,7 +73408,8 @@ function hasKeyFrame(source) {
|
|
|
72969
73408
|
* to describe, and the caller counts it. A track WITH an event but WITHOUT a
|
|
72970
73409
|
* key frame still returns a source (with `keyFrame: null`) so the caller's
|
|
72971
73410
|
* `missingKeyFrame` counter stays the one place that decision is made; no blob
|
|
72972
|
-
* is read in that case either.
|
|
73411
|
+
* is read in that case either. A key frame with no box measured on it returns
|
|
73412
|
+
* `bbox: null` and a named refusal, also without reading the blob.
|
|
72973
73413
|
*/
|
|
72974
73414
|
async function loadRebuildSource(deps, trackId) {
|
|
72975
73415
|
const media = await deps.media.listInfoByOwner("track", trackId);
|
|
@@ -72977,26 +73417,44 @@ async function loadRebuildSource(deps, trackId) {
|
|
|
72977
73417
|
const cropInfo = media.find((m) => m.kind === "thumbnail");
|
|
72978
73418
|
const peak = await deps.events.peakForTrack(trackId);
|
|
72979
73419
|
const events = await deps.events.queryObjectByTrackIds([trackId]);
|
|
72980
|
-
const best = events.find((e) => String(e.id) === peak.bestEventId) ?? events[0];
|
|
73420
|
+
const best = events.find((e) => String(e.id) === peak.bestEventId) ?? events.find(hasBox) ?? events[0];
|
|
72981
73421
|
if (best === void 0) return null;
|
|
72982
73422
|
const common = {
|
|
72983
73423
|
trackId,
|
|
72984
73424
|
deviceId: best.deviceId,
|
|
72985
|
-
timestamp: best.timestamp,
|
|
72986
73425
|
className: best.className,
|
|
72987
73426
|
confidence: best.confidence ?? 0,
|
|
72988
|
-
bbox: normaliseEventBbox(best),
|
|
72989
73427
|
...cropInfo !== void 0 ? { mediaId: cropInfo.mediaId } : {}
|
|
72990
73428
|
};
|
|
72991
|
-
if (keyFrameInfo === void 0)
|
|
73429
|
+
if (keyFrameInfo === void 0) {
|
|
73430
|
+
const bbox = normaliseEventBbox(best);
|
|
73431
|
+
return {
|
|
73432
|
+
...common,
|
|
73433
|
+
timestamp: best.timestamp,
|
|
73434
|
+
bbox,
|
|
73435
|
+
boxSource: bbox !== null ? { kind: "event" } : {
|
|
73436
|
+
kind: "refused",
|
|
73437
|
+
reason: "event-without-box"
|
|
73438
|
+
},
|
|
73439
|
+
keyFrame: null
|
|
73440
|
+
};
|
|
73441
|
+
}
|
|
73442
|
+
const box = await keyFrameBox(deps, trackId, keyFrameInfo);
|
|
73443
|
+
const keyed = {
|
|
72992
73444
|
...common,
|
|
73445
|
+
timestamp: keyFrameInfo.timestamp,
|
|
73446
|
+
bbox: box.bbox,
|
|
73447
|
+
boxSource: box.boxSource,
|
|
73448
|
+
keyFrameMediaId: keyFrameInfo.mediaId
|
|
73449
|
+
};
|
|
73450
|
+
if (box.bbox === null) return {
|
|
73451
|
+
...keyed,
|
|
72993
73452
|
keyFrame: null
|
|
72994
73453
|
};
|
|
72995
73454
|
const keyFrameFile = await deps.media.getById(keyFrameInfo.mediaId);
|
|
72996
73455
|
return {
|
|
72997
|
-
...
|
|
72998
|
-
keyFrame: keyFrameFile ? Buffer.from(keyFrameFile.base64, "base64") : null
|
|
72999
|
-
keyFrameMediaId: keyFrameInfo.mediaId
|
|
73456
|
+
...keyed,
|
|
73457
|
+
keyFrame: keyFrameFile ? Buffer.from(keyFrameFile.base64, "base64") : null
|
|
73000
73458
|
};
|
|
73001
73459
|
}
|
|
73002
73460
|
//#endregion
|
|
@@ -82723,7 +83181,21 @@ var TrackingSettingsSchema = object({
|
|
|
82723
83181
|
* passes, only for a coarse box below `animalOverPersonMaxScore`, never on
|
|
82724
83182
|
* the rescue / resurrection passes. Reported label stays via the class
|
|
82725
83183
|
* vote. Off = exact per-frame class gating. */
|
|
82726
|
-
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)
|
|
82727
83199
|
});
|
|
82728
83200
|
var TRACKING_DEFAULTS = TrackingSettingsSchema.parse({});
|
|
82729
83201
|
/**
|
|
@@ -82778,7 +83250,11 @@ function resolveTrackingSettings(raw) {
|
|
|
82778
83250
|
minSpawnAreaFracPerson: s.minSpawnAreaFracPerson.catch(TRACKING_DEFAULTS.minSpawnAreaFracPerson).parse(raw.minSpawnAreaFracPerson),
|
|
82779
83251
|
minSpawnAreaFracAnimal: s.minSpawnAreaFracAnimal.catch(TRACKING_DEFAULTS.minSpawnAreaFracAnimal).parse(raw.minSpawnAreaFracAnimal),
|
|
82780
83252
|
minSpawnAreaFracVehicle: s.minSpawnAreaFracVehicle.catch(TRACKING_DEFAULTS.minSpawnAreaFracVehicle).parse(raw.minSpawnAreaFracVehicle),
|
|
82781
|
-
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)
|
|
82782
83258
|
};
|
|
82783
83259
|
}
|
|
82784
83260
|
//#endregion
|
|
@@ -83208,6 +83684,40 @@ function buildDetectionSettingsSections() {
|
|
|
83208
83684
|
step: .05,
|
|
83209
83685
|
default: TRACKING_DEFAULTS.byteTrackLowIouThreshold
|
|
83210
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
|
+
},
|
|
83211
83721
|
{
|
|
83212
83722
|
type: "boolean",
|
|
83213
83723
|
key: "minSpawnAreaEnabled",
|
|
@@ -83545,7 +84055,7 @@ function buildGlobalSettingsSchema(cap) {
|
|
|
83545
84055
|
addonId: "pipeline-analytics",
|
|
83546
84056
|
action: "embedding.rebuildStatus",
|
|
83547
84057
|
buttonLabel: "Check progress",
|
|
83548
|
-
successMessage: "{modelId} → {index}: indexed {indexed} · scanned {scanned} · rebuilt {rebuilt} · no key frame {missingKeyFrame} · not runnable {notRunnable} · failed {failed}"
|
|
84058
|
+
successMessage: "{modelId} → {index}: indexed {indexed} · scanned {scanned} · rebuilt {rebuilt} · no key frame {missingKeyFrame} · no box {missingBbox} · no event {missingEvent} · not runnable {notRunnable} · failed {failed}"
|
|
83549
84059
|
},
|
|
83550
84060
|
{
|
|
83551
84061
|
type: "addon-action-button",
|
|
@@ -87708,6 +88218,8 @@ function decodeEmbeddingBase64(base64) {
|
|
|
87708
88218
|
var PHANTOM_CELL_PX = 32;
|
|
87709
88219
|
/** How long a cell remembers its closes. */
|
|
87710
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;
|
|
87711
88223
|
var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
87712
88224
|
/** Recent SHORT+MOTIONLESS track closes per `<device>:<class>:<cell>` —
|
|
87713
88225
|
* see {@link noteShortMotionlessTrack}. Measurement only; each entry is
|
|
@@ -88375,6 +88887,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
88375
88887
|
packageBackground = null;
|
|
88376
88888
|
/** Consecutive detector-dropout frames skipped per (deviceId, source) key. */
|
|
88377
88889
|
dropoutSkipsByKey = /* @__PURE__ */ new Map();
|
|
88890
|
+
/** Last time each processor's low-score continuation counts were logged (D665). */
|
|
88891
|
+
lowScoreReportAtByKey = /* @__PURE__ */ new Map();
|
|
88378
88892
|
/** The per-track close-path orchestrator (S2, refactor spec 2026-07-22):
|
|
88379
88893
|
* owns the single-flight sweep latch + the FULL per-track close sequence
|
|
88380
88894
|
* (zero-media policy, lastFrame promotion, recognizer ends with the
|
|
@@ -89933,6 +90447,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
89933
90447
|
mediaStore: stores.mediaStore,
|
|
89934
90448
|
trackStore: stores.trackStore,
|
|
89935
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),
|
|
89936
90458
|
captureCrop: (frameHandle, bbox, frameWidth, frameHeight, padding, ownerId, deviceId) => this.captureScheduler.request({
|
|
89937
90459
|
deviceId,
|
|
89938
90460
|
trackId: ownerId,
|
|
@@ -91887,6 +92409,31 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
91887
92409
|
* tracking/zone/event state never crosses between sources. Emits the
|
|
91888
92410
|
* SAME canonical events, distinguished only by `source`.
|
|
91889
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
|
+
}
|
|
91890
92437
|
async processFrame(deviceId, frame, source, rawFrameHandle, detailSteps, occasion = "session") {
|
|
91891
92438
|
if (this.shuttingDown) return;
|
|
91892
92439
|
if (!await this.bindingCache.isActive(deviceId)) {
|
|
@@ -91911,7 +92458,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
91911
92458
|
const trk = await this.resolveDeviceTrackingSettings(deviceId);
|
|
91912
92459
|
const activeCount = this.lastActiveTrackIds.get(key)?.size ?? 0;
|
|
91913
92460
|
const skips = this.dropoutSkipsByKey.get(key) ?? 0;
|
|
91914
|
-
|
|
92461
|
+
const lowScoreFloor = trk.lowScoreContinuationEnabled ? trk.lowScoreContinuationFloor : null;
|
|
92462
|
+
if (shouldSkipDropoutFrame(activeCount, dropoutEvidenceCount(frame, lowScoreFloor), skips, {
|
|
91915
92463
|
enabled: trk.dropoutSkipEnabled,
|
|
91916
92464
|
maxSkipFrames: trk.dropoutMaxSkipFrames
|
|
91917
92465
|
})) {
|
|
@@ -91939,6 +92487,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
91939
92487
|
processor.setZones(liveZones);
|
|
91940
92488
|
processor.setDetectionRules(liveRules);
|
|
91941
92489
|
processor.setZoneMembershipMinOverlap(trk.zoneMembershipMinOverlap);
|
|
92490
|
+
processor.setLowScoreContinuationFloor(lowScoreFloor);
|
|
91942
92491
|
const injection = injectSecondarySubjects(frame, this.secondarySubjects.take(key, Date.now()));
|
|
91943
92492
|
for (const id of injection.covered) this.secondarySubjects.drop(key, id);
|
|
91944
92493
|
const trackerFrame = injection.frame;
|
|
@@ -91954,6 +92503,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
91954
92503
|
timestamp: frame.timestamp,
|
|
91955
92504
|
frame: trackerFrame
|
|
91956
92505
|
});
|
|
92506
|
+
this.reportLowScoreContinuation(deviceId, key, processor);
|
|
91957
92507
|
for (const r of processor.getLastLabelWriteRefusals()) {
|
|
91958
92508
|
const message = r.reason.kind === "macro-class" ? "label tier collapse refused" : "label write refused";
|
|
91959
92509
|
this.ctx.logger.info(message, {
|
|
@@ -95641,7 +96191,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
95641
96191
|
animal: trk.minSpawnAreaFracAnimal,
|
|
95642
96192
|
vehicle: trk.minSpawnAreaFracVehicle
|
|
95643
96193
|
},
|
|
95644
|
-
classGroupAssoc: trk.classGroupAssoc
|
|
96194
|
+
classGroupAssoc: trk.classGroupAssoc,
|
|
96195
|
+
lowScoreContinuationEnabled: trk.lowScoreContinuationEnabled,
|
|
96196
|
+
lowScoreContinuationMaxMs: trk.lowScoreContinuationMaxMs,
|
|
96197
|
+
resurrectionTimeScaled: trk.resurrectionTimeScaled
|
|
95645
96198
|
}, {
|
|
95646
96199
|
stationaryThresholdSec,
|
|
95647
96200
|
...stillSpeedFracPerSec !== void 0 ? { stillSpeedFracPerSec } : {}
|
|
@@ -97132,6 +97685,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97132
97685
|
refusal: "stores-unavailable"
|
|
97133
97686
|
};
|
|
97134
97687
|
}
|
|
97688
|
+
const force = input.force === true;
|
|
97689
|
+
if (force && input.maxTracks === void 0) {
|
|
97690
|
+
this.ctx.logger.warn("rebuildObjectEmbeddings: refusing a force pass with no maxTracks", { meta: { refusal: "force-requires-max-tracks" } });
|
|
97691
|
+
return {
|
|
97692
|
+
started: false,
|
|
97693
|
+
alreadyRunning: false,
|
|
97694
|
+
refusal: "force-requires-max-tracks"
|
|
97695
|
+
};
|
|
97696
|
+
}
|
|
97135
97697
|
const confirmation = await this.getClusterPinConfirmer(api, "clip-rebuild").confirm();
|
|
97136
97698
|
if (confirmation.kind === "refused") {
|
|
97137
97699
|
this.ctx.logger.warn("rebuildObjectEmbeddings: the cluster CLIP model is not confirmed — refusing to start a pass that cannot pin one", { meta: { refusal: confirmation.reason } });
|
|
@@ -97175,16 +97737,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97175
97737
|
logger: this.ctx.logger
|
|
97176
97738
|
}));
|
|
97177
97739
|
const deps = {
|
|
97178
|
-
listTrackIdPage: async (cursor) => trackStore.queryRecentIds({
|
|
97740
|
+
listTrackIdPage: async (cursor) => trackStore.queryRecentIds(rebuildTrackPageQuery({
|
|
97179
97741
|
deviceIds,
|
|
97180
97742
|
...input.since !== void 0 ? { since: input.since } : {},
|
|
97181
97743
|
...input.until !== void 0 ? { until: input.until } : {},
|
|
97182
97744
|
...cursor !== null ? { cursor } : {},
|
|
97183
97745
|
limit: REBUILD_PAGE_SIZE
|
|
97184
|
-
}),
|
|
97746
|
+
})),
|
|
97185
97747
|
loadSource: (trackId) => loadRebuildSource({
|
|
97186
97748
|
media: mediaStore,
|
|
97187
|
-
events: eventStore
|
|
97749
|
+
events: eventStore,
|
|
97750
|
+
tracks: trackStore
|
|
97188
97751
|
}, trackId),
|
|
97189
97752
|
recoverKeyFrame: async (source) => {
|
|
97190
97753
|
const recovered = await recordedStills.recover(source.deviceId, source.timestamp, RECOVERED_KEY_FRAME_MAX_WIDTH);
|
|
@@ -97210,7 +97773,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97210
97773
|
if (split === null) return null;
|
|
97211
97774
|
return {
|
|
97212
97775
|
outOfSpace: split.outOfSpaceIds,
|
|
97213
|
-
alreadyInSpace: split.inSpace
|
|
97776
|
+
alreadyInSpace: split.inSpace,
|
|
97777
|
+
inSpace: split.inSpaceIds
|
|
97214
97778
|
};
|
|
97215
97779
|
},
|
|
97216
97780
|
logger: {
|
|
@@ -97222,6 +97786,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97222
97786
|
running: true,
|
|
97223
97787
|
modelId,
|
|
97224
97788
|
index,
|
|
97789
|
+
force,
|
|
97225
97790
|
startedAtMs: Date.now(),
|
|
97226
97791
|
progress: {
|
|
97227
97792
|
scanned: 0,
|
|
@@ -97230,6 +97795,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97230
97795
|
recoveredKeyFrame: 0,
|
|
97231
97796
|
missingEvent: 0,
|
|
97232
97797
|
missingBbox: 0,
|
|
97798
|
+
boxSource: {
|
|
97799
|
+
subjectBox: 0,
|
|
97800
|
+
positions: 0,
|
|
97801
|
+
event: 0,
|
|
97802
|
+
refused: 0
|
|
97803
|
+
},
|
|
97804
|
+
forcedInSpace: 0,
|
|
97805
|
+
skippedOutOfSpace: 0,
|
|
97233
97806
|
alreadyInSpace: 0,
|
|
97234
97807
|
notRunnable: 0,
|
|
97235
97808
|
noCapableNode: 0,
|
|
@@ -97237,6 +97810,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97237
97810
|
}
|
|
97238
97811
|
};
|
|
97239
97812
|
rebuildObjectEmbeddings(deps, {
|
|
97813
|
+
force,
|
|
97240
97814
|
...input.maxTracks !== void 0 ? { maxTracks: input.maxTracks } : {},
|
|
97241
97815
|
...input.pacingMs !== void 0 ? { pacingMs: input.pacingMs } : {},
|
|
97242
97816
|
onProgress: (progress) => {
|
|
@@ -97248,6 +97822,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97248
97822
|
running: false,
|
|
97249
97823
|
modelId,
|
|
97250
97824
|
index,
|
|
97825
|
+
force,
|
|
97251
97826
|
startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
|
|
97252
97827
|
finishedAtMs: Date.now(),
|
|
97253
97828
|
progress: outcome,
|
|
@@ -97263,6 +97838,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97263
97838
|
running: false,
|
|
97264
97839
|
modelId,
|
|
97265
97840
|
index,
|
|
97841
|
+
force,
|
|
97266
97842
|
startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
|
|
97267
97843
|
finishedAtMs: Date.now(),
|
|
97268
97844
|
error: String(err)
|
|
@@ -97277,7 +97853,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
97277
97853
|
modelId,
|
|
97278
97854
|
index,
|
|
97279
97855
|
deviceId: input.deviceId ?? "all",
|
|
97280
|
-
maxTracks: input.maxTracks ?? "unbounded"
|
|
97856
|
+
maxTracks: input.maxTracks ?? "unbounded",
|
|
97857
|
+
force
|
|
97281
97858
|
} });
|
|
97282
97859
|
return {
|
|
97283
97860
|
started: true,
|
|
@@ -98279,8 +98856,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
98279
98856
|
running: state.running,
|
|
98280
98857
|
scanned: p?.scanned ?? 0,
|
|
98281
98858
|
rebuilt: p?.rebuilt ?? 0,
|
|
98282
|
-
missingKeyFrame:
|
|
98859
|
+
missingKeyFrame: p?.missingKeyFrame ?? 0,
|
|
98860
|
+
missingEvent: p?.missingEvent ?? 0,
|
|
98283
98861
|
missingBbox: p?.missingBbox ?? 0,
|
|
98862
|
+
boxSource: p?.boxSource ?? {
|
|
98863
|
+
subjectBox: 0,
|
|
98864
|
+
positions: 0,
|
|
98865
|
+
event: 0,
|
|
98866
|
+
refused: 0
|
|
98867
|
+
},
|
|
98868
|
+
alreadyInSpace: p?.alreadyInSpace ?? 0,
|
|
98869
|
+
force: state.force ?? null,
|
|
98870
|
+
forcedInSpace: p?.forcedInSpace ?? 0,
|
|
98284
98871
|
notRunnable: p?.notRunnable ?? 0,
|
|
98285
98872
|
noCapableNode: p?.noCapableNode ?? 0,
|
|
98286
98873
|
failed: p?.failed ?? 0,
|