@camstack/addon-post-analysis 1.2.205 → 1.2.206
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{dist-C6gK_SDX.mjs → dist-WtZuLM5F.mjs} +36 -6
- package/dist/{dist-CJ0RJC87.js → dist-sREZj0iq.js} +36 -6
- 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-Vdk1rrss.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Di0Bams-.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-OI-5yBsz.mjs → hostInit-WRyV_amV.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +733 -122
- package/dist/pipeline-analytics/index.mjs +733 -122
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as audioModeOf, $t as EventCategory, A as NcSnoozeSchema, At as vectorDimFromBase64, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as nodePin, C as NcConditionDescriptorSchema, Ct as readTimelapseGeneratedAt, D as NcRuleTargetSchema, Dt as storageOccupancyCapability, E as NcRuleSchema, Et as sceneMonitorCapability, Ft as CamProfileSchema, G as TimelapseRulePatchSchema, Gt as discriminatedUnion, H as SceneMonitorSchema, Ht as _enum, I as RECORDING_EXPORT_MAX_READ_BYTES, It as DeviceType, J as VISIT_MERGE_GAP_MS, Jt as object, K as TimelapseRuleSchema, Kt as literal, L as RetrainStatusSchema, Lt as createEvent, M as NcSystemEventKindSchema, Mt as zoneAnalyticsCapability, N as NcTaxonomySchema, Nt as errMsg$1, O as NcScheduleSchema, Ot as subKindsOf, P as OpsLogEntrySchema, Pt as BaseAddon, Q as audioMetricsCapability, Qt as unknown, Rt as hydrateSchema, S as NC_TAXONOMY, St as readDeviceStateFrom, T as NcRulePatchSchema, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as array, V as SCENE_DIVERGED, Vt as sleep$1, W as TimelapseRuleInputSchema, Wt as boolean, X as alarmPanelCapability, Xt as record, Y as addonWidgetsSourceCapability, Yt as partialRecord, Z as assertTimelapseCadences, Zt as string, _ as MediaFileKindEnum, _t as notificationRulesCapability, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as pipelineAnalyticsCapability, c as DETECTION_MACRO_CLASSES, ct as evictionPolicyOfLocation, d as EVENT_KIND_BY_CAP, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isDetectionMacroClass, g as MACRO_LABELS, gt as mayWriteToLocation, h as LabelAttributionSchema, ht as kebabToCamel, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as videoclipsCapability, k as NcSnoozeInputSchema, kt as systemEventFilterApplies, lt as faceGalleryCapability, m as FailureCounters, mt as isSourceCap, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isScheduleActive, q as TrackSourceSchema, qt as number, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, ut as failureContributionCapability, v as NC_ALARM_SYSTEM_EVENT_KINDS, w as NcRuleInputSchema, wt as resolveLocationMode, xt as plateGalleryCapability, y as NC_CONDITION_CATALOG, yt as pickClusterStepModels, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as isDeviceScopedCap } from "../dist-
|
|
1
|
+
import { $ as audioModeOf, $t as EventCategory, A as NcSnoozeSchema, At as vectorDimFromBase64, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as nodePin, C as NcConditionDescriptorSchema, Ct as readTimelapseGeneratedAt, D as NcRuleTargetSchema, Dt as storageOccupancyCapability, E as NcRuleSchema, Et as sceneMonitorCapability, Ft as CamProfileSchema, G as TimelapseRulePatchSchema, Gt as discriminatedUnion, H as SceneMonitorSchema, Ht as _enum, I as RECORDING_EXPORT_MAX_READ_BYTES, It as DeviceType, J as VISIT_MERGE_GAP_MS, Jt as object, K as TimelapseRuleSchema, Kt as literal, L as RetrainStatusSchema, Lt as createEvent, M as NcSystemEventKindSchema, Mt as zoneAnalyticsCapability, N as NcTaxonomySchema, Nt as errMsg$1, O as NcScheduleSchema, Ot as subKindsOf, P as OpsLogEntrySchema, Pt as BaseAddon, Q as audioMetricsCapability, Qt as unknown, Rt as hydrateSchema, S as NC_TAXONOMY, St as readDeviceStateFrom, T as NcRulePatchSchema, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as array, V as SCENE_DIVERGED, Vt as sleep$1, W as TimelapseRuleInputSchema, Wt as boolean, X as alarmPanelCapability, Xt as record, Y as addonWidgetsSourceCapability, Yt as partialRecord, Z as assertTimelapseCadences, Zt as string, _ as MediaFileKindEnum, _t as notificationRulesCapability, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as pipelineAnalyticsCapability, c as DETECTION_MACRO_CLASSES, ct as evictionPolicyOfLocation, d as EVENT_KIND_BY_CAP, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isDetectionMacroClass, g as MACRO_LABELS, gt as mayWriteToLocation, h as LabelAttributionSchema, ht as kebabToCamel, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as videoclipsCapability, k as NcSnoozeInputSchema, kt as systemEventFilterApplies, lt as faceGalleryCapability, m as FailureCounters, mt as isSourceCap, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isScheduleActive, q as TrackSourceSchema, qt as number, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, ut as failureContributionCapability, v as NC_ALARM_SYSTEM_EVENT_KINDS, w as NcRuleInputSchema, wt as resolveLocationMode, xt as plateGalleryCapability, y as NC_CONDITION_CATALOG, yt as pickClusterStepModels, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as isDeviceScopedCap } from "../dist-WtZuLM5F.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";
|
|
@@ -27646,6 +27646,89 @@ function deliveredWideCentralSquareLayout(bbox, frame) {
|
|
|
27646
27646
|
if (centralSquareContainmentFraction(bbox, frame, layout) >= .5) return layout;
|
|
27647
27647
|
return wideCentralSquareLayout(bbox, frame, { containSubject: true });
|
|
27648
27648
|
}
|
|
27649
|
+
/**
|
|
27650
|
+
* Fraction of the DELIVERED 16:9 canvas that is NOT real pixels — the lateral
|
|
27651
|
+
* fill the composer synthesizes because the window ran off a frame edge. 0 =
|
|
27652
|
+
* the whole canvas is scene; 0.21 = a fifth of the delivered image is fill.
|
|
27653
|
+
*
|
|
27654
|
+
* Measured on {@link deliveredWideCentralSquareLayout} — the layout the
|
|
27655
|
+
* persisted `thumbnail` is actually cut with — because the operator complains
|
|
27656
|
+
* about the image they were SHOWN, not about a hypothetical centred window.
|
|
27657
|
+
* The two differ exactly where it matters: the default layout slides fully
|
|
27658
|
+
* in-frame and carries no fill at all, while the delivery fallback re-imposes
|
|
27659
|
+
* the subject's slack bound (so the square still shows the subject) and pays
|
|
27660
|
+
* for it in padding.
|
|
27661
|
+
*
|
|
27662
|
+
* Upper bound: a window that can only show its central square delivers
|
|
27663
|
+
* `1 − 9/16 = 0.4375` fill; nothing can exceed that while the square itself is
|
|
27664
|
+
* in frame.
|
|
27665
|
+
*
|
|
27666
|
+
* Pure geometry (no I/O). Feeds the best-frame ranking as a BOUNDED
|
|
27667
|
+
* within-tier factor — see `edge-clear.ts#CANVAS_FILL_MAX_PENALTY` for why it
|
|
27668
|
+
* is deliberately not a tier.
|
|
27669
|
+
*/
|
|
27670
|
+
function canvasFillFraction(bbox, frame, layout = deliveredWideCentralSquareLayout(bbox, frame)) {
|
|
27671
|
+
if (layout.canvasW <= 0) return 0;
|
|
27672
|
+
return Math.max(0, Math.min(1, (layout.canvasW - layout.slabW) / layout.canvasW));
|
|
27673
|
+
}
|
|
27674
|
+
//#endregion
|
|
27675
|
+
//#region src/pipeline-analytics/pipeline/best-shot-promotion-log.ts
|
|
27676
|
+
function r3(value) {
|
|
27677
|
+
return Math.round(value * 1e3) / 1e3;
|
|
27678
|
+
}
|
|
27679
|
+
/**
|
|
27680
|
+
* Build the attempt line's meta. Pure.
|
|
27681
|
+
*
|
|
27682
|
+
* `decidedBy` is `'first-sighting'` when there was no held peak: the track's
|
|
27683
|
+
* first observation is accepted unconditionally and nothing was compared, and
|
|
27684
|
+
* saying `'effective-score'` there would invent a comparison that never ran.
|
|
27685
|
+
*/
|
|
27686
|
+
function bestShotPromotionMeta(attempt) {
|
|
27687
|
+
const { candidate, held, comparison } = attempt;
|
|
27688
|
+
return {
|
|
27689
|
+
trackId: attempt.trackId,
|
|
27690
|
+
className: attempt.className,
|
|
27691
|
+
timestamp: attempt.timestamp,
|
|
27692
|
+
accepted: attempt.accepted,
|
|
27693
|
+
decidedBy: comparison?.decidedBy ?? "first-sighting",
|
|
27694
|
+
...comparison === void 0 ? {} : {
|
|
27695
|
+
heldScore: r3(comparison.currentScore),
|
|
27696
|
+
candidateScore: r3(comparison.candidateScore)
|
|
27697
|
+
},
|
|
27698
|
+
candConfidence: r3(candidate.confidence),
|
|
27699
|
+
candEdgeClear: candidate.edgeClear,
|
|
27700
|
+
...candidate.containment === void 0 ? {} : { candContainment: r3(candidate.containment) },
|
|
27701
|
+
...candidate.sizeScore === void 0 ? {} : { candSizeScore: r3(candidate.sizeScore) },
|
|
27702
|
+
...candidate.squareFraction === void 0 ? {} : { candSquareFraction: r3(candidate.squareFraction) },
|
|
27703
|
+
...candidate.canvasFill === void 0 ? {} : { candCanvasFill: r3(candidate.canvasFill) },
|
|
27704
|
+
candSemantic: candidate.semantic === true,
|
|
27705
|
+
...held === void 0 ? {} : {
|
|
27706
|
+
heldConfidence: r3(held.confidence),
|
|
27707
|
+
heldEdgeClear: held.edgeClear,
|
|
27708
|
+
...held.canvasFill === void 0 ? {} : { heldCanvasFill: r3(held.canvasFill) }
|
|
27709
|
+
},
|
|
27710
|
+
thumbnailRequested: attempt.thumbnailRequested,
|
|
27711
|
+
keyFrameRequested: attempt.keyFrameRequested,
|
|
27712
|
+
...attempt.suppressedBy === void 0 ? {} : { suppressedBy: attempt.suppressedBy }
|
|
27713
|
+
};
|
|
27714
|
+
}
|
|
27715
|
+
/**
|
|
27716
|
+
* Is this attempt worth a line?
|
|
27717
|
+
*
|
|
27718
|
+
* Every ACCEPTED attempt is — it is the moment the operator's cover image
|
|
27719
|
+
* changes, or fails to. A REFUSED attempt is logged only while the track still
|
|
27720
|
+
* has NO picture at all, and then at most one line per
|
|
27721
|
+
* {@link PROMOTION_REFUSAL_LOG_GAP_MS}: "the best shot never moved off the birth
|
|
27722
|
+
* frame" is exactly the shape `5f5f0f6e` presented and could not be diagnosed,
|
|
27723
|
+
* while a track whose thumbnail already landed is refusing candidates normally
|
|
27724
|
+
* and has nothing to explain.
|
|
27725
|
+
*/
|
|
27726
|
+
function shouldLogPromotionAttempt(attempt, gate) {
|
|
27727
|
+
if (attempt.accepted) return true;
|
|
27728
|
+
if (gate.thumbnailLanded) return false;
|
|
27729
|
+
const last = gate.lastRefusalLogAtMs;
|
|
27730
|
+
return last === void 0 || attempt.timestamp - last >= 2e3;
|
|
27731
|
+
}
|
|
27649
27732
|
var defaultScheduler = (tick, intervalMs) => {
|
|
27650
27733
|
const handle = setInterval(tick, intervalMs);
|
|
27651
27734
|
handle.unref?.();
|
|
@@ -30313,11 +30396,16 @@ function analyseDebugTrack(inventory) {
|
|
|
30313
30396
|
const out = [];
|
|
30314
30397
|
if (media.length === 0) out.push(finding("MEDIA_EVICTED_OR_NEVER", "error", "No media at all — either nothing was captured, or it was evicted (a debug mark does not pin retention)."));
|
|
30315
30398
|
else if (closed) {
|
|
30316
|
-
|
|
30399
|
+
const keyFrameMissing = !hasKind(media, "keyFrame");
|
|
30400
|
+
const lastFrameCopySourceMissing = keyFrameMissing || !hasKind(media, "keyFrameSmall");
|
|
30401
|
+
const noSnapshot = !hasKind(media, "snapshot");
|
|
30402
|
+
if (keyFrameMissing) out.push(finding("MISSING_KEYFRAME", "error", "No keyFrame — the best-shot frame is absent."));
|
|
30317
30403
|
if (!hasKind(media, "firstFrame")) out.push(finding("MISSING_FIRSTFRAME", "warn", "No firstFrame for a closed track."));
|
|
30318
|
-
if (!hasKind(media, "lastFrame")) out.push(finding("MISSING_LASTFRAME", "warn", "No lastFrame for a closed track."));
|
|
30319
|
-
if (!hasKind(media, "thumbnail")) out.push(finding("MISSING_THUMBNAIL", "warn", "No thumbnail — every list surface renders blank."));
|
|
30320
|
-
|
|
30404
|
+
if (!hasKind(media, "lastFrame")) out.push(finding("MISSING_LASTFRAME", "warn", noSnapshot && lastFrameCopySourceMissing ? `No lastFrame for a closed track — a CONSEQUENCE of ${keyFrameMissing ? "the missing keyFrame" : "the missing keyFrameSmall"}: with no snapshot to promote, the close can only copy keyFrameSmall, and there is none.` : "No lastFrame for a closed track."));
|
|
30405
|
+
if (!hasKind(media, "thumbnail")) out.push(finding("MISSING_THUMBNAIL", "warn", keyFrameMissing ? "No thumbnail — every list surface renders blank; with no keyFrame the close-time derive had nothing to cut from." : "No thumbnail — every list surface renders blank."));
|
|
30406
|
+
const cropExpected = facts.hasFace === true || facts.hasEmbeddedFace === true;
|
|
30407
|
+
const noCrop = !hasKind(media, "crop") && !hasKind(media, "faceCrop") && !hasKind(media, "plateCrop");
|
|
30408
|
+
if (cropExpected && noCrop) out.push(finding("MISSING_BEST_CROP", "warn", facts.hasEmbeddedFace === true ? "A gallery face row was recorded for this track but no crop of any kind survives — the row promises an image the media index does not have." : "A face detail ran on this track but no crop of any kind was cut for it."));
|
|
30321
30409
|
}
|
|
30322
30410
|
if (durationMs < 2e3) out.push(finding("SHORT_TRACK", "warn", `Track lasted ${String(durationMs)} ms, under the ${String(SHORT_TRACK_MS)} ms floor — likely a fragment.`));
|
|
30323
30411
|
if (eventCount === 0) out.push(finding("ZERO_EVENTS", "error", "No object events — the track exists but recorded nothing."));
|
|
@@ -30401,7 +30489,9 @@ function toFacts(track) {
|
|
|
30401
30489
|
snapshotCount: track.snapshots.length,
|
|
30402
30490
|
...track.importance === void 0 ? {} : { importance: track.importance },
|
|
30403
30491
|
...track.bestEventId === void 0 ? {} : { bestEventId: track.bestEventId },
|
|
30404
|
-
...track.previewMissReason === void 0 ? {} : { previewMissReason: track.previewMissReason }
|
|
30492
|
+
...track.previewMissReason === void 0 ? {} : { previewMissReason: track.previewMissReason },
|
|
30493
|
+
...track.hasFace === void 0 ? {} : { hasFace: track.hasFace },
|
|
30494
|
+
...track.hasEmbeddedFace === void 0 ? {} : { hasEmbeddedFace: track.hasEmbeddedFace }
|
|
30405
30495
|
};
|
|
30406
30496
|
}
|
|
30407
30497
|
/**
|
|
@@ -33487,6 +33577,33 @@ var CONTAINMENT_MAX_SIDE_PENALTY = .5;
|
|
|
33487
33577
|
* NO best frame at all). */
|
|
33488
33578
|
var CONTAINMENT_MIN = .1;
|
|
33489
33579
|
/**
|
|
33580
|
+
* Aspect at or above which a clipped subject reads as INTACT — no penalty. 2.5
|
|
33581
|
+
* = the box is two-and-a-half times longer along the clipped axis than across
|
|
33582
|
+
* it, which is what a whole upright person standing with their feet on the
|
|
33583
|
+
* bottom border looks like.
|
|
33584
|
+
*/
|
|
33585
|
+
var CLIP_ASPECT_NO_PENALTY = 2.5;
|
|
33586
|
+
/**
|
|
33587
|
+
* How much of a clipped subject is missing, from the SUBJECT'S OWN geometry.
|
|
33588
|
+
*
|
|
33589
|
+
* `alongClip` is the bbox's extent along the axis the border cut (height for a
|
|
33590
|
+
* top/bottom clip, width for a left/right clip); `acrossClip` is its extent on
|
|
33591
|
+
* the other axis. Returns the share of {@link CONTAINMENT_MAX_SIDE_PENALTY} to
|
|
33592
|
+
* apply, ramped linearly between {@link CLIP_ASPECT_FULL_PENALTY} and
|
|
33593
|
+
* {@link CLIP_ASPECT_NO_PENALTY}.
|
|
33594
|
+
*
|
|
33595
|
+
* Truncation COMPRESSES a box along the axis it was cut on, so the surviving
|
|
33596
|
+
* aspect is the signal: it is intrinsic to the subject, scale-invariant and
|
|
33597
|
+
* frame-invariant. Degenerate (`acrossClip <= 0`) reads as fully truncated.
|
|
33598
|
+
*/
|
|
33599
|
+
function clipPenaltyScale(alongClip, acrossClip) {
|
|
33600
|
+
if (acrossClip <= 0) return 1;
|
|
33601
|
+
const aspect = alongClip / acrossClip;
|
|
33602
|
+
if (aspect <= 1) return 1;
|
|
33603
|
+
if (aspect >= 2.5) return 0;
|
|
33604
|
+
return 1 - (aspect - 1) / (CLIP_ASPECT_NO_PENALTY - 1);
|
|
33605
|
+
}
|
|
33606
|
+
/**
|
|
33490
33607
|
* Edge-CONTAINMENT factor for a (clamped) bbox: an estimate in
|
|
33491
33608
|
* `[CONTAINMENT_MIN, 1]` of the fraction of the subject that is actually in
|
|
33492
33609
|
* frame. 1 = fully contained (no border clipping); lower = the subject is
|
|
@@ -33494,15 +33611,26 @@ var CONTAINMENT_MIN = .1;
|
|
|
33494
33611
|
* walking out of view).
|
|
33495
33612
|
*
|
|
33496
33613
|
* Because detection bboxes are already CLAMPED to the frame, the true off-screen
|
|
33497
|
-
* extent is unknown, so this is a purely-geometric proxy
|
|
33498
|
-
* bbox touches (within the {@link DEFAULT_EDGE_TOLERANCE} band)
|
|
33499
|
-
* scaled by
|
|
33500
|
-
*
|
|
33501
|
-
*
|
|
33502
|
-
*
|
|
33503
|
-
*
|
|
33504
|
-
*
|
|
33505
|
-
*
|
|
33614
|
+
* extent is unknown, so this is a purely-geometric proxy. For each border the
|
|
33615
|
+
* bbox touches (within the {@link DEFAULT_EDGE_TOLERANCE} band) the penalty is
|
|
33616
|
+
* scaled by {@link clipPenaltyScale} — the surviving remnant's own aspect
|
|
33617
|
+
* ALONG the cut axis. A wide bbox flush to the top/bottom (a wide subject cut
|
|
33618
|
+
* off top/bottom) or a tall bbox flush to the left/right (a tall subject cut
|
|
33619
|
+
* off at the side) is heavily penalised, whereas a narrow subject whose feet
|
|
33620
|
+
* merely graze the bottom border keeps its score. Degenerate/unknown dims
|
|
33621
|
+
* (≤ 0) return 1 (neutral — matches {@link isEdgeClear}).
|
|
33622
|
+
*
|
|
33623
|
+
* **Why the basis changed (2026-09-08, D400, track `841f9b8e`).** The penalty
|
|
33624
|
+
* used to be scaled by the bbox's SPAN across the touched border as a fraction
|
|
33625
|
+
* of the FRAME. That calibration was written for a wide subject cut off top or
|
|
33626
|
+
* bottom, and it is meaningless for an upright person: a 4K frame is 3840 px
|
|
33627
|
+
* wide, a person clipped at the head is ~400 px wide, so `spanX = 0.104` and a
|
|
33628
|
+
* decapitated person lost **5.2 %** of its score — the operator's "questi tagli
|
|
33629
|
+
* hanno uno score troppo alto, non si vedono il dorso e testa", with the number
|
|
33630
|
+
* attached. The frame's width is not a fact about the subject. The remnant's
|
|
33631
|
+
* own aspect is: the same person now loses 47.9 %, and the wide-subject case the
|
|
33632
|
+
* old shape existed for is penalised MORE than before (a 900×400 vehicle flush
|
|
33633
|
+
* to the bottom went from 0.88 to 0.50), not less.
|
|
33506
33634
|
*/
|
|
33507
33635
|
function bboxContainment(bbox, frameWidth, frameHeight, tolerance = DEFAULT_EDGE_TOLERANCE) {
|
|
33508
33636
|
if (frameWidth <= 0 || frameHeight <= 0) return 1;
|
|
@@ -33512,16 +33640,16 @@ function bboxContainment(bbox, frameWidth, frameHeight, tolerance = DEFAULT_EDGE
|
|
|
33512
33640
|
const top = bbox.y;
|
|
33513
33641
|
const right = bbox.x + bbox.w;
|
|
33514
33642
|
const bottom = bbox.y + bbox.h;
|
|
33515
|
-
const
|
|
33516
|
-
const
|
|
33643
|
+
const verticalBorderScale = clipPenaltyScale(bbox.w, bbox.h);
|
|
33644
|
+
const horizontalBorderScale = clipPenaltyScale(bbox.h, bbox.w);
|
|
33517
33645
|
let containment = 1;
|
|
33518
|
-
const penalize = (
|
|
33519
|
-
containment *= 1 - CONTAINMENT_MAX_SIDE_PENALTY *
|
|
33646
|
+
const penalize = (scale) => {
|
|
33647
|
+
containment *= 1 - CONTAINMENT_MAX_SIDE_PENALTY * scale;
|
|
33520
33648
|
};
|
|
33521
|
-
if (left <= tolX) penalize(
|
|
33522
|
-
if (right >= frameWidth - tolX) penalize(
|
|
33523
|
-
if (top <= tolY) penalize(
|
|
33524
|
-
if (bottom >= frameHeight - tolY) penalize(
|
|
33649
|
+
if (left <= tolX) penalize(verticalBorderScale);
|
|
33650
|
+
if (right >= frameWidth - tolX) penalize(verticalBorderScale);
|
|
33651
|
+
if (top <= tolY) penalize(horizontalBorderScale);
|
|
33652
|
+
if (bottom >= frameHeight - tolY) penalize(horizontalBorderScale);
|
|
33525
33653
|
return Math.max(CONTAINMENT_MIN, Math.min(1, containment));
|
|
33526
33654
|
}
|
|
33527
33655
|
/** Fraction of the frame area at which a subject's size score saturates to 1.
|
|
@@ -33568,13 +33696,38 @@ var SQUARE_EVICTION_SCORE_FLOOR = .05;
|
|
|
33568
33696
|
* raw confidence — the ABSOLUTE hysteresis margin stays meaningful — while a
|
|
33569
33697
|
* bigger view still earns up to a 2× relative preference. */
|
|
33570
33698
|
var SIZE_FACTOR_FLOOR = .5;
|
|
33699
|
+
/**
|
|
33700
|
+
* Maximum fraction of a candidate's effective score the DELIVERED-CANVAS fill
|
|
33701
|
+
* can subtract, at 100 % fill (D400).
|
|
33702
|
+
*
|
|
33703
|
+
* **Why this is a factor and not a tier.** Fill is anti-correlated with subject
|
|
33704
|
+
* size by construction: the 16:9 window is `side × 16/9` of the subject's
|
|
33705
|
+
* central square, so a big, close subject is far likelier to overflow the frame
|
|
33706
|
+
* than a small distant one — a small subject near the centre has zero fill
|
|
33707
|
+
* ALWAYS. A hard "no-fill beats fill" tier would therefore systematically
|
|
33708
|
+
* promote distant, tiny subjects over close ones, which is the exact defect
|
|
33709
|
+
* `bboxSizeScore` was added to fix (BEST_SHOT_STALE, 2026-07-22 / 2026-09-03).
|
|
33710
|
+
* So it enters as a bounded multiplicative factor in the same family as
|
|
33711
|
+
* `containment` and `sizeFactor`, strictly BELOW every tier, and as a near-
|
|
33712
|
+
* equal tie-break ranked ABOVE centeredness (a measurement of the delivered
|
|
33713
|
+
* image beats a proxy for it).
|
|
33714
|
+
*
|
|
33715
|
+
* **Bounded so it can only ever tie-break.** The real fill ceiling is 0.4375
|
|
33716
|
+
* (a window that can show no more than its own central square), so the largest
|
|
33717
|
+
* move this term can make is `0.15 × 0.4375 = 6.6 %` of the effective score. It
|
|
33718
|
+
* cannot overturn a lead of 7.1 % or more — and the metric that would say it
|
|
33719
|
+
* has stopped tie-breaking and started dominating is the share of accepted
|
|
33720
|
+
* best-shot promotions logged with `decidedBy: 'canvas-fill'`.
|
|
33721
|
+
*/
|
|
33722
|
+
var CANVAS_FILL_MAX_PENALTY = .15;
|
|
33571
33723
|
/** The effective within-tier score: detector confidence scaled by the
|
|
33572
|
-
* edge-containment
|
|
33573
|
-
* when absent. */
|
|
33724
|
+
* edge-containment, (floored) subject-size and delivered-canvas-fill factors —
|
|
33725
|
+
* each neutral at 1 when absent. */
|
|
33574
33726
|
function effectiveScore(c) {
|
|
33575
33727
|
const sizeFactor = c.sizeScore === void 0 ? 1 : SIZE_FACTOR_FLOOR + (1 - SIZE_FACTOR_FLOOR) * c.sizeScore;
|
|
33576
33728
|
const squareFactor = isSquareContained(c) ? 1 : Math.max(c.squareFraction ?? 0, SQUARE_EVICTION_SCORE_FLOOR);
|
|
33577
|
-
|
|
33729
|
+
const fillFactor = 1 - CANVAS_FILL_MAX_PENALTY * Math.max(0, Math.min(1, c.canvasFill ?? 0));
|
|
33730
|
+
return c.confidence * (c.containment ?? 1) * sizeFactor * squareFactor * fillFactor;
|
|
33578
33731
|
}
|
|
33579
33732
|
/**
|
|
33580
33733
|
* True when `candidate` beats `current` DECISIVELY — same-or-better edge tier
|
|
@@ -33592,48 +33745,51 @@ function isDecisiveImprovement(current, candidate) {
|
|
|
33592
33745
|
return effectiveScore(candidate) >= effectiveScore(current) * DECISIVE_IMPROVEMENT_RATIO;
|
|
33593
33746
|
}
|
|
33594
33747
|
/**
|
|
33595
|
-
*
|
|
33596
|
-
*
|
|
33597
|
-
*
|
|
33598
|
-
*
|
|
33599
|
-
*
|
|
33600
|
-
*
|
|
33601
|
-
*
|
|
33602
|
-
*
|
|
33603
|
-
*
|
|
33604
|
-
*
|
|
33605
|
-
*
|
|
33606
|
-
*
|
|
33607
|
-
*
|
|
33608
|
-
*
|
|
33609
|
-
*
|
|
33610
|
-
*
|
|
33611
|
-
*
|
|
33612
|
-
|
|
33613
|
-
|
|
33614
|
-
|
|
33615
|
-
|
|
33616
|
-
|
|
33617
|
-
|
|
33618
|
-
|
|
33619
|
-
|
|
33620
|
-
|
|
33621
|
-
|
|
33622
|
-
|
|
33623
|
-
|
|
33624
|
-
|
|
33625
|
-
if (
|
|
33626
|
-
if (
|
|
33627
|
-
if (
|
|
33628
|
-
if (candidate
|
|
33629
|
-
if (
|
|
33630
|
-
|
|
33631
|
-
|
|
33632
|
-
|
|
33748
|
+
* The ONE best-frame comparison, decomposed. {@link isEdgeAwareNewBest} is this
|
|
33749
|
+
* function's `accepted` bit and nothing else — there is no second copy of the
|
|
33750
|
+
* ordering to drift.
|
|
33751
|
+
*
|
|
33752
|
+
* The tier order, highest first, and where the delivered-canvas term sits:
|
|
33753
|
+
* 1. **square-containment** — does the delivered SQUARE show the subject at all?
|
|
33754
|
+
* 2. **semantic** — did a recognition succeed on this frame?
|
|
33755
|
+
* 3. **edge-clear** — is the whole subject inside the frame?
|
|
33756
|
+
* 4. **effective-score** — `confidence × containment × size × square × canvasFill`.
|
|
33757
|
+
* 5. **canvas-fill** — near-equal tie-break: less synthesized lateral fill wins.
|
|
33758
|
+
* 6. **centeredness** — near-equal tie-break of last resort.
|
|
33759
|
+
*
|
|
33760
|
+
* Canvas fill enters at 4 (bounded, see {@link CANVAS_FILL_MAX_PENALTY}) and 5,
|
|
33761
|
+
* never as a tier: fill is anti-correlated with subject size, so a tier would
|
|
33762
|
+
* hand every camera's best shot to its most distant subject. It ranks ABOVE
|
|
33763
|
+
* centeredness because both answer "is this a well-framed delivery?" and fill
|
|
33764
|
+
* MEASURES the delivered canvas while centeredness only proxies it.
|
|
33765
|
+
*/
|
|
33766
|
+
function compareBestFrame(current, candidate, hysteresis) {
|
|
33767
|
+
const currentScore = effectiveScore(current);
|
|
33768
|
+
const candidateScore = effectiveScore(candidate);
|
|
33769
|
+
const verdict = (accepted, decidedBy) => ({
|
|
33770
|
+
accepted,
|
|
33771
|
+
decidedBy,
|
|
33772
|
+
currentScore,
|
|
33773
|
+
candidateScore
|
|
33774
|
+
});
|
|
33775
|
+
return compareBestFrameTiers(current, candidate, hysteresis, currentScore, candidateScore, verdict);
|
|
33776
|
+
}
|
|
33777
|
+
function compareBestFrameTiers(current, candidate, hysteresis, curEffective, candEffective, verdict) {
|
|
33778
|
+
if (isSquareContained(candidate) && !isSquareContained(current)) return verdict(true, "square-containment");
|
|
33779
|
+
if (!isSquareContained(candidate) && isSquareContained(current)) return verdict(false, "square-containment");
|
|
33780
|
+
if (isSemantic(candidate) && !isSemantic(current)) return verdict(true, "semantic");
|
|
33781
|
+
if (!isSemantic(candidate) && isSemantic(current)) return verdict(false, "semantic");
|
|
33782
|
+
if (candidate.edgeClear && !current.edgeClear) return verdict(true, "edge-clear");
|
|
33783
|
+
if (!candidate.edgeClear && current.edgeClear) return verdict(false, "edge-clear");
|
|
33784
|
+
if (candEffective > curEffective + hysteresis) return verdict(true, "effective-score");
|
|
33785
|
+
const nearEqual = Math.abs(candEffective - curEffective) <= hysteresis;
|
|
33786
|
+
if (candidate.canvasFill !== void 0 && current.canvasFill !== void 0) {
|
|
33787
|
+
if (nearEqual && candidate.canvasFill + .05 < current.canvasFill) return verdict(true, "canvas-fill");
|
|
33788
|
+
}
|
|
33633
33789
|
if (candidate.centerScore !== void 0 && current.centerScore !== void 0) {
|
|
33634
|
-
if (
|
|
33790
|
+
if (nearEqual && candidate.centerScore > current.centerScore + .1) return verdict(true, "centeredness");
|
|
33635
33791
|
}
|
|
33636
|
-
return false;
|
|
33792
|
+
return verdict(false, nearEqual ? "centeredness" : "effective-score");
|
|
33637
33793
|
}
|
|
33638
33794
|
//#endregion
|
|
33639
33795
|
//#region src/pipeline-analytics/pipeline/first-frame-retry-bbox.ts
|
|
@@ -34052,6 +34208,74 @@ var LooseBindSampler = class {
|
|
|
34052
34208
|
this.counts.clear();
|
|
34053
34209
|
}
|
|
34054
34210
|
};
|
|
34211
|
+
/**
|
|
34212
|
+
* Floor on the predictive-drift clamp, in box sides.
|
|
34213
|
+
*
|
|
34214
|
+
* The scaled term collapses at short intervals — at the 56 ms minimum measured
|
|
34215
|
+
* on camera 615 it is 0.11 sides — and a clamp that tight turns ordinary
|
|
34216
|
+
* detector box jitter into a refused match. A quarter of a side is slack, not
|
|
34217
|
+
* travel.
|
|
34218
|
+
*/
|
|
34219
|
+
var DRIFT_MIN_SIDES = .25;
|
|
34220
|
+
/**
|
|
34221
|
+
* Re-acquisition reach, in box DIAGONALS per second.
|
|
34222
|
+
*
|
|
34223
|
+
* Answers `267840a1` again, on the gate that actually refused it: 908.4 px of
|
|
34224
|
+
* centroid travel in 880 ms against a `hypot(513.6, 721.2)` = 885.5 px diagonal
|
|
34225
|
+
* is 1.166 diagonals per second. 1.5 clears it with ~29 % margin and is
|
|
34226
|
+
* physically bounded — 1.5 of a subject's own body-diagonal per second is a
|
|
34227
|
+
* brisk run, and being scale-free it means the same thing for a far-field
|
|
34228
|
+
* person with a 90-px box as for a near one with a 900-px box.
|
|
34229
|
+
*
|
|
34230
|
+
* The number it replaces (`rescueCentroidFactor` 0.75 × diagonal, flat) was
|
|
34231
|
+
* doing two jobs at once: at a 2135 ms poll gap it was far too tight to bridge
|
|
34232
|
+
* a walking subject, and at a 150 ms burst interval it was loose enough to hand
|
|
34233
|
+
* one subject's id to another 452 px away (track `27855a56`, a second car, at
|
|
34234
|
+
* IoU 0.058). One number, two opposite failures.
|
|
34235
|
+
*/
|
|
34236
|
+
var REACH_DIAGONALS_PER_SEC = 1.5;
|
|
34237
|
+
/**
|
|
34238
|
+
* Floor on the re-acquisition reach, in box diagonals.
|
|
34239
|
+
*
|
|
34240
|
+
* Deliberately TIGHTER than the 0.75 flat factor it replaces. At a 150 ms burst
|
|
34241
|
+
* interval the scaled term is 0.225 diagonals; the floor lifts it to 0.3, which
|
|
34242
|
+
* is 221 px on `27855a56`'s 738-px diagonal — comfortably below the 452 px that
|
|
34243
|
+
* bought that track a second car. A short gap is evidence that the subject
|
|
34244
|
+
* cannot have gone far, and the gate now says so.
|
|
34245
|
+
*/
|
|
34246
|
+
var REACH_MIN_DIAGONALS = .3;
|
|
34247
|
+
/**
|
|
34248
|
+
* Ceiling on the re-acquisition reach, in box diagonals.
|
|
34249
|
+
*
|
|
34250
|
+
* `REACH_DIAGONALS_PER_SEC × BUDGET_CEILING_MS`. A subject unseen for the full
|
|
34251
|
+
* `maxMissedMs` gets 1.5 diagonals of reach, not 6.
|
|
34252
|
+
*/
|
|
34253
|
+
var REACH_MAX_DIAGONALS = 1.5;
|
|
34254
|
+
function clampRange(value, min, max) {
|
|
34255
|
+
return Math.max(min, Math.min(max, value));
|
|
34256
|
+
}
|
|
34257
|
+
/**
|
|
34258
|
+
* How far a coasting box's velocity extrapolation may carry it on one axis.
|
|
34259
|
+
*
|
|
34260
|
+
* `side` is the box's width for the x drift and its height for the y drift, so
|
|
34261
|
+
* the allowance keeps the same meaning for a tall person and a wide car.
|
|
34262
|
+
*/
|
|
34263
|
+
function driftAllowancePx(side, elapsedMs) {
|
|
34264
|
+
const elapsed = Math.max(0, elapsedMs);
|
|
34265
|
+
return clampRange(side * 2 * (elapsed / 1e3), side * DRIFT_MIN_SIDES, side * 2);
|
|
34266
|
+
}
|
|
34267
|
+
/**
|
|
34268
|
+
* How far from where a track is EXPECTED a detection may sit and still be
|
|
34269
|
+
* claimed as that track by the loose (rescue / resurrection) reach clause.
|
|
34270
|
+
*
|
|
34271
|
+
* Scale-free: the budget is a multiple of the track's own diagonal, so it does
|
|
34272
|
+
* not have to know the frame size, the camera, or how far away the subject is.
|
|
34273
|
+
*/
|
|
34274
|
+
function reacquireReachPx(bbox, elapsedMs) {
|
|
34275
|
+
const diag = Math.hypot(bbox.w, bbox.h);
|
|
34276
|
+
const elapsed = Math.max(0, elapsedMs);
|
|
34277
|
+
return clampRange(diag * REACH_DIAGONALS_PER_SEC * (elapsed / 1e3), diag * REACH_MIN_DIAGONALS, diag * REACH_MAX_DIAGONALS);
|
|
34278
|
+
}
|
|
34055
34279
|
//#endregion
|
|
34056
34280
|
//#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
|
|
34057
34281
|
/**
|
|
@@ -34111,6 +34335,21 @@ function growthAdjacentMatch(track, det, ctx) {
|
|
|
34111
34335
|
}
|
|
34112
34336
|
/** Per-reason counter + sampling decision. One instance per tracker, so the
|
|
34113
34337
|
* counts are per camera and per detection source. */
|
|
34338
|
+
var SpawnBesideCoastingSampler = class {
|
|
34339
|
+
counts = /* @__PURE__ */ new Map();
|
|
34340
|
+
/** The running count when this report should be emitted, else `undefined`. */
|
|
34341
|
+
next(reason) {
|
|
34342
|
+
const count = (this.counts.get(reason) ?? 0) + 1;
|
|
34343
|
+
this.counts.set(reason, count);
|
|
34344
|
+
if (count <= 10) return count;
|
|
34345
|
+
return count % 10 === 0 ? count : void 0;
|
|
34346
|
+
}
|
|
34347
|
+
reset() {
|
|
34348
|
+
this.counts.clear();
|
|
34349
|
+
}
|
|
34350
|
+
};
|
|
34351
|
+
/** Per-reason counter + sampling decision. One instance per tracker, so the
|
|
34352
|
+
* counts are per camera and per detection source. */
|
|
34114
34353
|
var SpawnRefusalSampler = class {
|
|
34115
34354
|
counts = /* @__PURE__ */ new Map();
|
|
34116
34355
|
/** The running count when this refusal should be reported, else `undefined`. */
|
|
@@ -34135,6 +34374,7 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
34135
34374
|
maxTrackLifetimeMs: 3e5,
|
|
34136
34375
|
rescueIouThreshold: .1,
|
|
34137
34376
|
rescueCentroidFactor: .75,
|
|
34377
|
+
reacquireTimeScaled: true,
|
|
34138
34378
|
resurrectionWindowMs: 8e3,
|
|
34139
34379
|
resurrectionCrowdGuard: true,
|
|
34140
34380
|
resurrectionCrowdedIouThreshold: .3,
|
|
@@ -34209,6 +34449,10 @@ var SortTracker = class SortTracker {
|
|
|
34209
34449
|
refusalSampler = new SpawnRefusalSampler();
|
|
34210
34450
|
/** Sink for dropped-detection diagnostics — see {@link setSpawnRefusalObserver}. */
|
|
34211
34451
|
spawnRefusalObserver = null;
|
|
34452
|
+
/** Per-reason counters behind the sampled spawn-beside-coasting diagnostics. */
|
|
34453
|
+
spawnBesideSampler = new SpawnBesideCoastingSampler();
|
|
34454
|
+
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
34455
|
+
spawnBesideObserver = null;
|
|
34212
34456
|
constructor(config = {}) {
|
|
34213
34457
|
this.config = {
|
|
34214
34458
|
...DEFAULT_TRACKER_CONFIG,
|
|
@@ -34233,6 +34477,88 @@ var SortTracker = class SortTracker {
|
|
|
34233
34477
|
setSpawnRefusalObserver(observer) {
|
|
34234
34478
|
this.spawnRefusalObserver = observer;
|
|
34235
34479
|
}
|
|
34480
|
+
/**
|
|
34481
|
+
* Wire the diagnostics sink for the case the tracker does NOT act on: a new id
|
|
34482
|
+
* minted beside a live, compatible, coasting track that could have claimed it.
|
|
34483
|
+
* Same contract as the other two sinks — the tracker has no `deviceId`, the
|
|
34484
|
+
* caller stamps it, and nothing is computed when no observer is wired.
|
|
34485
|
+
*/
|
|
34486
|
+
setSpawnBesideCoastingObserver(observer) {
|
|
34487
|
+
this.spawnBesideObserver = observer;
|
|
34488
|
+
}
|
|
34489
|
+
/**
|
|
34490
|
+
* Report a fresh id minted beside the nearest live coasting track of its
|
|
34491
|
+
* association group, when there was one within
|
|
34492
|
+
* {@link SPAWN_BESIDE_REPORT_REACH_FACTOR} × the reach the gate used.
|
|
34493
|
+
*
|
|
34494
|
+
* `coasting` is every track surviving this frame that did NOT match a real
|
|
34495
|
+
* detection on it — the only ones a spawn can be the continuation of.
|
|
34496
|
+
*/
|
|
34497
|
+
reportSpawnBesideCoasting(spawnedId, det, coasting, detections, frameArea, timestamp) {
|
|
34498
|
+
const observer = this.spawnBesideObserver;
|
|
34499
|
+
if (!observer) return;
|
|
34500
|
+
const group = this.assocGroup(det.class);
|
|
34501
|
+
const dc = bboxCentroid(det.bbox);
|
|
34502
|
+
let best;
|
|
34503
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
34504
|
+
let bestReach = 0;
|
|
34505
|
+
for (const track of coasting) {
|
|
34506
|
+
if (this.assocGroup(track.class) !== group) continue;
|
|
34507
|
+
const tc = bboxCentroid(track.bbox);
|
|
34508
|
+
const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
|
|
34509
|
+
const reach = this.reacquireReach(track, timestamp, true);
|
|
34510
|
+
if (dist > reach * 2) continue;
|
|
34511
|
+
if (dist < bestDist) {
|
|
34512
|
+
best = track;
|
|
34513
|
+
bestDist = dist;
|
|
34514
|
+
bestReach = reach;
|
|
34515
|
+
}
|
|
34516
|
+
}
|
|
34517
|
+
if (!best) return;
|
|
34518
|
+
const declinedBy = this.explainDecline(best, det, detections, frameArea, timestamp, bestDist);
|
|
34519
|
+
const spawnCount = this.spawnBesideSampler.next(declinedBy);
|
|
34520
|
+
if (spawnCount === void 0) return;
|
|
34521
|
+
observer({
|
|
34522
|
+
spawnedTrackId: spawnedId,
|
|
34523
|
+
spawnedClass: det.class,
|
|
34524
|
+
spawnedScore: det.score,
|
|
34525
|
+
spawnedBbox: det.bbox,
|
|
34526
|
+
spawnedNativeRecovery: det.nativeRecovery !== void 0,
|
|
34527
|
+
coastingTrackId: best.id,
|
|
34528
|
+
coastingClass: best.class,
|
|
34529
|
+
iou: iou$3(best.bbox, det.bbox),
|
|
34530
|
+
predictedIou: iou$3(this.predicted(best, timestamp), det.bbox),
|
|
34531
|
+
centroidDistPx: bestDist,
|
|
34532
|
+
reachPx: bestReach,
|
|
34533
|
+
trackDiagPx: Math.hypot(best.bbox.w, best.bbox.h),
|
|
34534
|
+
gapMs: timestamp - best.lastSeen,
|
|
34535
|
+
trackAgeMs: timestamp - best.firstSeen,
|
|
34536
|
+
declinedBy,
|
|
34537
|
+
spawnCount,
|
|
34538
|
+
sampledEvery: 10
|
|
34539
|
+
});
|
|
34540
|
+
}
|
|
34541
|
+
/**
|
|
34542
|
+
* Which gate turned `track` away from `det`, evaluated in the order the
|
|
34543
|
+
* tracker itself evaluates them — the FIRST one that says no is the answer.
|
|
34544
|
+
*
|
|
34545
|
+
* Every clause here re-reads a fact about the frame rather than about the
|
|
34546
|
+
* greedy pass's claim order, so the verdict is reproducible: the one case that
|
|
34547
|
+
* cannot be re-derived is a pair nothing refused, which is exactly what
|
|
34548
|
+
* `already-claimed` means.
|
|
34549
|
+
*/
|
|
34550
|
+
explainDecline(track, det, detections, frameArea, timestamp, centroidDist) {
|
|
34551
|
+
if (!this.sameGroupAsMeasured(track.class, det)) return "native-measured-class";
|
|
34552
|
+
if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return "assoc-group";
|
|
34553
|
+
if (iou$3(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "already-claimed";
|
|
34554
|
+
if (centroidDist <= this.reacquireReach(track, timestamp, true)) {
|
|
34555
|
+
if (!this.isPlausibleBirth(det, frameArea)) return "already-claimed";
|
|
34556
|
+
if (!this.config.reacquireTimeScaled) return "plausible-birth-veto";
|
|
34557
|
+
return this.isUnambiguousReach(track, det, detections) ? "already-claimed" : "crowd-guard";
|
|
34558
|
+
}
|
|
34559
|
+
if (growthAdjacentMatch(track.bbox, det.bbox, SortTracker.growthContext(track, timestamp))) return "already-claimed";
|
|
34560
|
+
return "geometry";
|
|
34561
|
+
}
|
|
34236
34562
|
/** Emit one dropped-detection record, subject to the refusal sampler. */
|
|
34237
34563
|
reportRefusal(reason, det, extra = {}) {
|
|
34238
34564
|
const observer = this.spawnRefusalObserver;
|
|
@@ -34501,21 +34827,39 @@ var SortTracker = class SortTracker {
|
|
|
34501
34827
|
if (a !== void 0 && dropIds.has(a) || b !== void 0 && dropIds.has(b)) this.dupStreaks.delete(key);
|
|
34502
34828
|
}
|
|
34503
34829
|
}
|
|
34504
|
-
/**
|
|
34505
|
-
*
|
|
34506
|
-
*
|
|
34507
|
-
*
|
|
34508
|
-
*
|
|
34509
|
-
*
|
|
34510
|
-
|
|
34830
|
+
/**
|
|
34831
|
+
* Where a track is expected at `nowMs` — extrapolated by velocity while
|
|
34832
|
+
* coasting (`predictiveCoasting`), else its last known bbox.
|
|
34833
|
+
*
|
|
34834
|
+
* A stationary track (speed below `stationarySpeedPx`, measured in the
|
|
34835
|
+
* px-per-update unit that setting is calibrated in) is frozen so bbox jitter
|
|
34836
|
+
* cannot walk the box off a sitting object. Otherwise the drift is the
|
|
34837
|
+
* velocity carried over the ELAPSED interval, capped by
|
|
34838
|
+
* {@link driftAllowancePx} — which is itself a function of that interval, so
|
|
34839
|
+
* a stale velocity cannot fling the box across the frame and a long, honest
|
|
34840
|
+
* gap is no longer capped at the one box width a single frame would get
|
|
34841
|
+
* (D399). Public so the clamp is directly unit-testable.
|
|
34842
|
+
*/
|
|
34843
|
+
predicted(t, nowMs) {
|
|
34511
34844
|
if (!this.config.predictiveCoasting) return t.bbox;
|
|
34512
|
-
if (Math.hypot(t.velocity.dx, t.velocity.dy) < this.config.stationarySpeedPx) return t.bbox;
|
|
34513
|
-
|
|
34514
|
-
|
|
34515
|
-
|
|
34845
|
+
if (Math.hypot(t.velocity.dx, t.velocity.dy) * t.emitIntervalMs < this.config.stationarySpeedPx) return t.bbox;
|
|
34846
|
+
if (!this.config.reacquireTimeScaled) {
|
|
34847
|
+
const f = t.age + 1;
|
|
34848
|
+
const perFrameX = t.velocity.dx * t.emitIntervalMs;
|
|
34849
|
+
const perFrameY = t.velocity.dy * t.emitIntervalMs;
|
|
34850
|
+
return {
|
|
34851
|
+
x: t.bbox.x + clamp(perFrameX * f, -t.bbox.w, t.bbox.w),
|
|
34852
|
+
y: t.bbox.y + clamp(perFrameY * f, -t.bbox.h, t.bbox.h),
|
|
34853
|
+
w: t.bbox.w,
|
|
34854
|
+
h: t.bbox.h
|
|
34855
|
+
};
|
|
34856
|
+
}
|
|
34857
|
+
const elapsedMs = Math.max(0, nowMs - t.lastSeen);
|
|
34858
|
+
const allowX = driftAllowancePx(t.bbox.w, elapsedMs);
|
|
34859
|
+
const allowY = driftAllowancePx(t.bbox.h, elapsedMs);
|
|
34516
34860
|
return {
|
|
34517
|
-
x: t.bbox.x +
|
|
34518
|
-
y: t.bbox.y +
|
|
34861
|
+
x: t.bbox.x + clamp(t.velocity.dx * elapsedMs, -allowX, allowX),
|
|
34862
|
+
y: t.bbox.y + clamp(t.velocity.dy * elapsedMs, -allowY, allowY),
|
|
34519
34863
|
w: t.bbox.w,
|
|
34520
34864
|
h: t.bbox.h
|
|
34521
34865
|
};
|
|
@@ -34558,19 +34902,64 @@ var SortTracker = class SortTracker {
|
|
|
34558
34902
|
* crouch→animal flip when `classGroupAssoc` is on, but never by an unrelated
|
|
34559
34903
|
* class.
|
|
34560
34904
|
*/
|
|
34561
|
-
looseMatch(track, det, timestamp) {
|
|
34905
|
+
looseMatch(track, det, timestamp, timeScaled = true) {
|
|
34562
34906
|
if (!this.sameGroupAsMeasured(track.class, det)) return null;
|
|
34563
34907
|
if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return null;
|
|
34564
34908
|
if (iou$3(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
|
|
34565
34909
|
const tc = bboxCentroid(track.bbox);
|
|
34566
34910
|
const dc = bboxCentroid(det.bbox);
|
|
34567
|
-
|
|
34568
|
-
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
34569
|
-
if (dist <= this.config.rescueCentroidFactor * diag) return "centroid";
|
|
34911
|
+
if (Math.hypot(tc.x - dc.x, tc.y - dc.y) <= this.reacquireReach(track, timestamp, timeScaled)) return "centroid";
|
|
34570
34912
|
if (growthAdjacentMatch(track.bbox, det.bbox, SortTracker.growthContext(track, timestamp))) return "growth";
|
|
34571
34913
|
return null;
|
|
34572
34914
|
}
|
|
34573
34915
|
/**
|
|
34916
|
+
* The proximity budget the `centroid` clause is measured against.
|
|
34917
|
+
*
|
|
34918
|
+
* On the RESCUE pass (`timeScaled`) it is {@link reacquireReachPx} — a
|
|
34919
|
+
* multiple of the track's diagonal that grows with the elapsed gap and stops
|
|
34920
|
+
* at a ceiling, because a live coasting track's whereabouts genuinely become
|
|
34921
|
+
* less certain the longer it has gone unseen (D399).
|
|
34922
|
+
*
|
|
34923
|
+
* On the RESURRECTION pass it stays the flat `rescueCentroidFactor × diagonal`
|
|
34924
|
+
* it always was. That pass re-attaches a CORPSE, and it exists for one thing:
|
|
34925
|
+
* "a flickering stationary object keeps its identity". Elapsed time is not the
|
|
34926
|
+
* variable there — a stationary subject does not travel further because it
|
|
34927
|
+
* blinked for longer — and the corpse is strictly weaker evidence than a live
|
|
34928
|
+
* track, which is the same reasoning `resurrectionCrowdedIouThreshold` already
|
|
34929
|
+
* encodes.
|
|
34930
|
+
*/
|
|
34931
|
+
reacquireReach(track, timestamp, timeScaled) {
|
|
34932
|
+
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
34933
|
+
if (!timeScaled || !this.config.reacquireTimeScaled) return this.config.rescueCentroidFactor * diag;
|
|
34934
|
+
return reacquireReachPx(track.bbox, timestamp - track.lastSeen);
|
|
34935
|
+
}
|
|
34936
|
+
/**
|
|
34937
|
+
* Whether a REACH bind would be unambiguous: this coasting track is the only
|
|
34938
|
+
* subject of its association group the scene holds, and `det` is the only box
|
|
34939
|
+
* of that group on the frame.
|
|
34940
|
+
*
|
|
34941
|
+
* This is what lets a reach bind survive {@link isPlausibleBirth} (D399). That
|
|
34942
|
+
* veto exists because "a spare track costs media; a stolen track corrupts the
|
|
34943
|
+
* plate, the travelled distance and the last frame of the track it was taken
|
|
34944
|
+
* from" — every word of which is about a scene with something to steal FROM.
|
|
34945
|
+
* With one track and one box there is no second subject the id could be wrong
|
|
34946
|
+
* about, and refusing the bind does not buy safety, it buys a fragment.
|
|
34947
|
+
*
|
|
34948
|
+
* Deliberately computed over the whole frame rather than over what is still
|
|
34949
|
+
* unclaimed, so the answer is a fact about the scene and reads the same at
|
|
34950
|
+
* bind time and at report time — the spawn-beside-coasting line has to be able
|
|
34951
|
+
* to say which gate declined without re-running the greedy competition.
|
|
34952
|
+
*/
|
|
34953
|
+
isUnambiguousReach(track, det, detections) {
|
|
34954
|
+
if (this.sameGroupPeerCount(track, this.tracks) > 0) return false;
|
|
34955
|
+
const group = this.assocGroup(track.class);
|
|
34956
|
+
for (const other of detections) {
|
|
34957
|
+
if (other === det) continue;
|
|
34958
|
+
if (this.assocGroup(other.class) === group) return false;
|
|
34959
|
+
}
|
|
34960
|
+
return true;
|
|
34961
|
+
}
|
|
34962
|
+
/**
|
|
34574
34963
|
* Whether `det` would mint a track of its own if no salvage branch claimed it:
|
|
34575
34964
|
* it clears BOTH spawn floors (per-class score, per-class bbox-area fraction).
|
|
34576
34965
|
*
|
|
@@ -34638,11 +35027,11 @@ var SortTracker = class SortTracker {
|
|
|
34638
35027
|
* gates) so the two-tier ByteTrack association stays a single implementation.
|
|
34639
35028
|
* Mutates `used` / `matchedTracks` / `matched` in place.
|
|
34640
35029
|
*/
|
|
34641
|
-
greedyAssociate(detections, candidateIdxs, iouThreshold, used, matchedTracks, matched) {
|
|
35030
|
+
greedyAssociate(detections, candidateIdxs, iouThreshold, timestamp, used, matchedTracks, matched) {
|
|
34642
35031
|
const pairs = [];
|
|
34643
35032
|
for (const track of this.tracks) {
|
|
34644
35033
|
if (matchedTracks.has(track)) continue;
|
|
34645
|
-
const pbox = this.predicted(track);
|
|
35034
|
+
const pbox = this.predicted(track, timestamp);
|
|
34646
35035
|
for (const di of candidateIdxs) {
|
|
34647
35036
|
if (used.has(di)) continue;
|
|
34648
35037
|
const det = detections[di];
|
|
@@ -34701,7 +35090,7 @@ var SortTracker = class SortTracker {
|
|
|
34701
35090
|
for (let di = 0; di < detections.length; di++) if (this.config.byteTrackEnabled && detections[di].score < this.config.byteTrackHighThreshold) lowIdxs.push(di);
|
|
34702
35091
|
else highIdxs.push(di);
|
|
34703
35092
|
const lowSet = new Set(lowIdxs);
|
|
34704
|
-
this.greedyAssociate(detections, highIdxs, this.config.iouThreshold, used, matchedTracks, matched);
|
|
35093
|
+
this.greedyAssociate(detections, highIdxs, this.config.iouThreshold, timestamp, used, matchedTracks, matched);
|
|
34705
35094
|
const rescuePairs = [];
|
|
34706
35095
|
for (const track of this.tracks) {
|
|
34707
35096
|
if (matchedTracks.has(track)) continue;
|
|
@@ -34710,7 +35099,7 @@ var SortTracker = class SortTracker {
|
|
|
34710
35099
|
const det = detections[di];
|
|
34711
35100
|
const gate = this.looseMatch(track, det, timestamp);
|
|
34712
35101
|
if (gate === null) continue;
|
|
34713
|
-
if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
|
|
35102
|
+
if (gate === "centroid" && this.isPlausibleBirth(det, frameArea) && !(this.config.reacquireTimeScaled && this.isUnambiguousReach(track, det, detections))) continue;
|
|
34714
35103
|
rescuePairs.push({
|
|
34715
35104
|
track,
|
|
34716
35105
|
detIdx: di,
|
|
@@ -34728,7 +35117,7 @@ var SortTracker = class SortTracker {
|
|
|
34728
35117
|
used.add(pair.detIdx);
|
|
34729
35118
|
this.reportBind("rescue", pair.gate, pair.track, det, timestamp);
|
|
34730
35119
|
}
|
|
34731
|
-
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, used, matchedTracks, matched);
|
|
35120
|
+
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, timestamp, used, matchedTracks, matched);
|
|
34732
35121
|
for (const [track, det] of matched) {
|
|
34733
35122
|
if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, { against: track });
|
|
34734
35123
|
const prevCenter = bboxCentroid({
|
|
@@ -34744,6 +35133,7 @@ var SortTracker = class SortTracker {
|
|
|
34744
35133
|
h: det.bbox.h
|
|
34745
35134
|
});
|
|
34746
35135
|
const frames = track.age + 1;
|
|
35136
|
+
const dtMs = timestamp - track.lastSeen;
|
|
34747
35137
|
track.bbox = det.bbox;
|
|
34748
35138
|
track.class = det.class;
|
|
34749
35139
|
track.originalClass = det.originalClass;
|
|
@@ -34751,10 +35141,13 @@ var SortTracker = class SortTracker {
|
|
|
34751
35141
|
track.age = 0;
|
|
34752
35142
|
track.hits++;
|
|
34753
35143
|
track.lastSeen = timestamp;
|
|
34754
|
-
|
|
34755
|
-
|
|
34756
|
-
|
|
34757
|
-
|
|
35144
|
+
if (dtMs > 0) {
|
|
35145
|
+
track.velocity = {
|
|
35146
|
+
dx: (newCenter.x - prevCenter.x) / dtMs,
|
|
35147
|
+
dy: (newCenter.y - prevCenter.y) / dtMs
|
|
35148
|
+
};
|
|
35149
|
+
track.emitIntervalMs = dtMs / frames;
|
|
35150
|
+
}
|
|
34758
35151
|
track.path.push(det.bbox);
|
|
34759
35152
|
if (track.path.length > MAX_PATH_LENGTH) track.path.shift();
|
|
34760
35153
|
this.addClassVote(track, det);
|
|
@@ -34770,7 +35163,7 @@ var SortTracker = class SortTracker {
|
|
|
34770
35163
|
surviving.push(track);
|
|
34771
35164
|
continue;
|
|
34772
35165
|
}
|
|
34773
|
-
const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track), ob.bbox) >= this.config.occlusionContainment);
|
|
35166
|
+
const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track, timestamp), ob.bbox) >= this.config.occlusionContainment);
|
|
34774
35167
|
track.age++;
|
|
34775
35168
|
if (timestamp - track.lastSeen > (occluded ? this.config.occlusionMaxMissedMs : this.config.maxMissedMs)) {
|
|
34776
35169
|
track.lost = true;
|
|
@@ -34792,7 +35185,7 @@ var SortTracker = class SortTracker {
|
|
|
34792
35185
|
for (const lost of this.lostTracks) {
|
|
34793
35186
|
if (!lost.resurrectable) continue;
|
|
34794
35187
|
if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
|
|
34795
|
-
const gate = this.looseMatch(lost, det, timestamp);
|
|
35188
|
+
const gate = this.looseMatch(lost, det, timestamp, false);
|
|
34796
35189
|
if (gate === null) continue;
|
|
34797
35190
|
if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
|
|
34798
35191
|
const score = iou$3(lost.bbox, det.bbox);
|
|
@@ -34872,8 +35265,10 @@ var SortTracker = class SortTracker {
|
|
|
34872
35265
|
used.add(di);
|
|
34873
35266
|
continue;
|
|
34874
35267
|
}
|
|
35268
|
+
const spawnedId = randomUUID();
|
|
35269
|
+
this.reportSpawnBesideCoasting(spawnedId, det, surviving.filter((t) => t.lastSeen !== timestamp), detections, frameArea, timestamp);
|
|
34875
35270
|
surviving.push({
|
|
34876
|
-
id:
|
|
35271
|
+
id: spawnedId,
|
|
34877
35272
|
bbox: det.bbox,
|
|
34878
35273
|
class: det.class,
|
|
34879
35274
|
originalClass: det.originalClass,
|
|
@@ -34887,6 +35282,7 @@ var SortTracker = class SortTracker {
|
|
|
34887
35282
|
dx: 0,
|
|
34888
35283
|
dy: 0
|
|
34889
35284
|
},
|
|
35285
|
+
emitIntervalMs: 0,
|
|
34890
35286
|
lost: false,
|
|
34891
35287
|
lostAt: 0,
|
|
34892
35288
|
resurrectable: true,
|
|
@@ -34907,7 +35303,10 @@ var SortTracker = class SortTracker {
|
|
|
34907
35303
|
bbox: t.bbox,
|
|
34908
35304
|
trackId: t.id,
|
|
34909
35305
|
trackAge: t.hits,
|
|
34910
|
-
velocity:
|
|
35306
|
+
velocity: {
|
|
35307
|
+
dx: t.velocity.dx * t.emitIntervalMs,
|
|
35308
|
+
dy: t.velocity.dy * t.emitIntervalMs
|
|
35309
|
+
},
|
|
34911
35310
|
path: [...t.path],
|
|
34912
35311
|
matchedThisFrame: t.lastSeen === timestamp,
|
|
34913
35312
|
...scene !== void 0 ? { resurrectionScene: scene } : {}
|
|
@@ -34935,6 +35334,7 @@ var SortTracker = class SortTracker {
|
|
|
34935
35334
|
this.lostTracks = [];
|
|
34936
35335
|
this.dupStreaks.clear();
|
|
34937
35336
|
this.bindSampler.reset();
|
|
35337
|
+
this.spawnBesideSampler.reset();
|
|
34938
35338
|
}
|
|
34939
35339
|
};
|
|
34940
35340
|
//#endregion
|
|
@@ -35369,6 +35769,12 @@ var FrameProcessor = class {
|
|
|
35369
35769
|
setBindObserver(observer) {
|
|
35370
35770
|
this.tracker.setBindObserver(observer);
|
|
35371
35771
|
}
|
|
35772
|
+
/** Wire (or clear) the tracker's fragmentation sink — a new id minted beside a
|
|
35773
|
+
* live coasting track that could have claimed it (D399). Same `deviceId`
|
|
35774
|
+
* contract as the two sinks above. */
|
|
35775
|
+
setSpawnBesideCoastingObserver(observer) {
|
|
35776
|
+
this.tracker.setSpawnBesideCoastingObserver(observer);
|
|
35777
|
+
}
|
|
35372
35778
|
/** Forget a track in the underlying tracker (used when the addon promotes it
|
|
35373
35779
|
* to a stationary-object registry entry). */
|
|
35374
35780
|
dropTrack(trackId) {
|
|
@@ -35965,7 +36371,11 @@ function promoteSemanticBestFrame(deps, input) {
|
|
|
35965
36371
|
W: frameWidth,
|
|
35966
36372
|
H: frameHeight
|
|
35967
36373
|
}),
|
|
35968
|
-
semantic: true
|
|
36374
|
+
semantic: true,
|
|
36375
|
+
canvasFill: canvasFillFraction(bbox, {
|
|
36376
|
+
W: frameWidth,
|
|
36377
|
+
H: frameHeight
|
|
36378
|
+
})
|
|
35969
36379
|
})) {
|
|
35970
36380
|
deps.logger.debug("best-shot: semantic frame did not beat the held peak", {
|
|
35971
36381
|
tags: { deviceId },
|
|
@@ -36509,9 +36919,15 @@ function decideLastFramePromotion(media) {
|
|
|
36509
36919
|
snapshotKey: newestKeyFrameSmall.key,
|
|
36510
36920
|
snapshotTimestamp: newestKeyFrameSmall.timestamp
|
|
36511
36921
|
};
|
|
36512
|
-
return {
|
|
36922
|
+
return {
|
|
36923
|
+
promote: false,
|
|
36924
|
+
declinedBecause: lastFrame === void 0 ? "no-key-frame" : "no-source"
|
|
36925
|
+
};
|
|
36513
36926
|
}
|
|
36514
|
-
if (lastFrame !== void 0 && newestSnapshot.timestamp <= lastFrame.timestamp) return {
|
|
36927
|
+
if (lastFrame !== void 0 && newestSnapshot.timestamp <= lastFrame.timestamp) return {
|
|
36928
|
+
promote: false,
|
|
36929
|
+
declinedBecause: "already-last"
|
|
36930
|
+
};
|
|
36515
36931
|
return {
|
|
36516
36932
|
promote: true,
|
|
36517
36933
|
mode: "move",
|
|
@@ -36558,17 +36974,28 @@ function averageBboxDiagonal(boxes) {
|
|
|
36558
36974
|
//#endregion
|
|
36559
36975
|
//#region src/pipeline-analytics/pipeline/track-close.ts
|
|
36560
36976
|
/**
|
|
36561
|
-
* Compose the persisted `previewMissReason` (D276/B2). One
|
|
36562
|
-
* tokens: the derive's own miss branch (or what tile stands
|
|
36563
|
-
* capture-side failure
|
|
36564
|
-
*
|
|
36977
|
+
* Compose the persisted `previewMissReason` (D276/B2, extended by D400). One
|
|
36978
|
+
* line, greppable tokens: the derive's own miss branch (or what tile stands
|
|
36979
|
+
* in), then whether the NATIVE KEY FRAME landed, then the capture-side failure
|
|
36980
|
+
* with its count. Examples:
|
|
36981
|
+
* `no-key-frame no-key-frame-at-close capture=keyframe:native-missx4`
|
|
36565
36982
|
* `derive-returned-null tile=standin`
|
|
36566
|
-
* `no-key-frame
|
|
36567
|
-
|
|
36568
|
-
|
|
36983
|
+
* `native-tile tile=native no-key-frame-at-close`
|
|
36984
|
+
*
|
|
36985
|
+
* **Why the key-frame token (D400).** The reason used to be written ONLY when
|
|
36986
|
+
* the best-shot TILE ranked below native, so a track whose thumbnail landed
|
|
36987
|
+
* natively while its key frame missed carried no reason at all — three of the
|
|
36988
|
+
* four `MISSING_KEYFRAME` tracks in the 2026-09-07 audit were exactly that, and
|
|
36989
|
+
* the operator's "niente ultimo" had no cause anywhere on the row. The key
|
|
36990
|
+
* frame IS the native best-shot FRAME, and its absence is the head of the
|
|
36991
|
+
* cascade (`keyFrame ⇒ keyFrameSmall ⇒ the lastFrame copy source`), so it
|
|
36992
|
+
* belongs on this line whatever the tile ended up being.
|
|
36993
|
+
*/
|
|
36994
|
+
function composePreviewMissReason(tile, deriveMiss, captureMiss, keyFrameLanded = true) {
|
|
36569
36995
|
const parts = [];
|
|
36570
36996
|
parts.push(deriveMiss ?? (tile === "none" ? "no-thumbnail" : `${tile}-tile`));
|
|
36571
36997
|
if (tile !== "none") parts.push(`tile=${tile}`);
|
|
36998
|
+
if (!keyFrameLanded && deriveMiss !== "no-key-frame") parts.push("no-key-frame-at-close");
|
|
36572
36999
|
if (captureMiss !== void 0) parts.push(`capture=${captureMiss.reason}x${captureMiss.count}`);
|
|
36573
37000
|
return parts.join(" ");
|
|
36574
37001
|
}
|
|
@@ -36664,9 +37091,10 @@ var TrackCloser = class {
|
|
|
36664
37091
|
await this.commitRasterFallback(t, closure.rasterFallback.jpeg, closure.rasterFallback.timestamp);
|
|
36665
37092
|
tile = "raster";
|
|
36666
37093
|
}
|
|
36667
|
-
|
|
37094
|
+
const keyFrameLanded = ownedMedia.some((m) => m.kind === "keyFrame");
|
|
37095
|
+
if (tile !== "native" || !keyFrameLanded) {
|
|
36668
37096
|
const captureMiss = this.deps.previewCaptureMiss?.(t.trackId);
|
|
36669
|
-
const reason = composePreviewMissReason(tile, deriveMiss, captureMiss);
|
|
37097
|
+
const reason = composePreviewMissReason(tile, deriveMiss, captureMiss, keyFrameLanded);
|
|
36670
37098
|
if (this.deps.setPreviewMiss !== void 0) try {
|
|
36671
37099
|
await this.deps.setPreviewMiss(t.deviceId, t.trackId, reason);
|
|
36672
37100
|
} catch (err) {
|
|
@@ -36679,8 +37107,8 @@ var TrackCloser = class {
|
|
|
36679
37107
|
}
|
|
36680
37108
|
});
|
|
36681
37109
|
}
|
|
36682
|
-
const bestSeen = this.deps.residents.bestSeenBbox(t.trackId);
|
|
36683
|
-
this.deps.recordPendingPreviewUpgrade?.({
|
|
37110
|
+
const bestSeen = tile === "native" ? void 0 : this.deps.residents.bestSeenBbox(t.trackId);
|
|
37111
|
+
if (tile !== "native") this.deps.recordPendingPreviewUpgrade?.({
|
|
36684
37112
|
deviceId: t.deviceId,
|
|
36685
37113
|
trackId: t.trackId,
|
|
36686
37114
|
...bestSeen !== void 0 ? { bestSeen: {
|
|
@@ -36949,9 +37377,32 @@ var TrackCloser = class {
|
|
|
36949
37377
|
*/
|
|
36950
37378
|
async maybePromoteLastFrame(t, ownedMedia) {
|
|
36951
37379
|
const promotion = decideLastFramePromotion(ownedMedia);
|
|
36952
|
-
if (!promotion.promote)
|
|
37380
|
+
if (!promotion.promote) {
|
|
37381
|
+
if (promotion.declinedBecause !== "already-last") this.deps.logger.warn("no lastFrame at close", {
|
|
37382
|
+
tags: { deviceId: t.deviceId },
|
|
37383
|
+
meta: {
|
|
37384
|
+
trackId: t.trackId,
|
|
37385
|
+
declinedBecause: promotion.declinedBecause,
|
|
37386
|
+
hasKeyFrame: ownedMedia.some((m) => m.kind === "keyFrame"),
|
|
37387
|
+
hasKeyFrameSmall: ownedMedia.some((m) => m.kind === "keyFrameSmall"),
|
|
37388
|
+
snapshots: ownedMedia.filter((m) => m.kind === "snapshot").length
|
|
37389
|
+
}
|
|
37390
|
+
});
|
|
37391
|
+
return;
|
|
37392
|
+
}
|
|
36953
37393
|
const snapshot = ownedMedia.find((m) => m.key === promotion.snapshotKey);
|
|
36954
|
-
if (!snapshot)
|
|
37394
|
+
if (!snapshot) {
|
|
37395
|
+
this.deps.logger.warn("no lastFrame at close", {
|
|
37396
|
+
tags: { deviceId: t.deviceId },
|
|
37397
|
+
meta: {
|
|
37398
|
+
trackId: t.trackId,
|
|
37399
|
+
declinedBecause: "promotion-source-vanished",
|
|
37400
|
+
sourceKey: promotion.snapshotKey,
|
|
37401
|
+
mode: promotion.mode
|
|
37402
|
+
}
|
|
37403
|
+
});
|
|
37404
|
+
return;
|
|
37405
|
+
}
|
|
36955
37406
|
try {
|
|
36956
37407
|
await this.deps.mediaStore()?.promoteToLastFrame({
|
|
36957
37408
|
deviceId: t.deviceId,
|
|
@@ -37119,9 +37570,15 @@ var BestDetectionTracker = class {
|
|
|
37119
37570
|
* `best`. Absent = the caller does not measure it → contained tier
|
|
37120
37571
|
* (legacy behaviour). */
|
|
37121
37572
|
squareFraction = /* @__PURE__ */ new Map();
|
|
37573
|
+
/** Held peak's DELIVERED-CANVAS fill fraction (0..1), PARALLEL to `best`.
|
|
37574
|
+
* Absent = the caller does not measure it → neutral (no fill). */
|
|
37575
|
+
canvasFill = /* @__PURE__ */ new Map();
|
|
37122
37576
|
/** Held peak's SEMANTIC-tier bit (a recognition succeeded on that frame),
|
|
37123
37577
|
* PARALLEL to `best`. Absent = non-semantic (legacy behaviour). */
|
|
37124
37578
|
semantic = /* @__PURE__ */ new Map();
|
|
37579
|
+
/** The last comparison run for a track (D400 promotion log). Never read by
|
|
37580
|
+
* the ranking — a pure observation seam. */
|
|
37581
|
+
lastComparison = /* @__PURE__ */ new Map();
|
|
37125
37582
|
constructor(options = {}) {
|
|
37126
37583
|
this.hysteresis = options.hysteresis ?? 0;
|
|
37127
37584
|
this.minGapMs = options.minGapMs ?? 0;
|
|
@@ -37141,12 +37598,13 @@ var BestDetectionTracker = class {
|
|
|
37141
37598
|
* the classic policy holds: a confidence past the `hysteresis` margin that also
|
|
37142
37599
|
* respects `minGapMs` wins. On acceptance the held peak advances.
|
|
37143
37600
|
*/
|
|
37144
|
-
observe(trackId, confidence, timestamp, edgeClearIn, centerScoreIn, containmentIn, sizeScoreIn, squareFractionIn, semanticIn) {
|
|
37601
|
+
observe(trackId, confidence, timestamp, edgeClearIn, centerScoreIn, containmentIn, sizeScoreIn, squareFractionIn, semanticIn, canvasFillIn) {
|
|
37145
37602
|
const edgeClear = this.edgeAware ? edgeClearIn : void 0;
|
|
37146
37603
|
const centerScore = this.edgeAware ? centerScoreIn : void 0;
|
|
37147
37604
|
const containment = this.edgeAware ? containmentIn : void 0;
|
|
37148
37605
|
const sizeScore = this.sizeAware ? sizeScoreIn : void 0;
|
|
37149
37606
|
const squareFraction = this.edgeAware ? squareFractionIn : void 0;
|
|
37607
|
+
const canvasFill = this.edgeAware ? canvasFillIn : void 0;
|
|
37150
37608
|
const semanticBit = this.semanticAware ? semanticIn : void 0;
|
|
37151
37609
|
const cur = this.best.get(trackId);
|
|
37152
37610
|
if (cur === void 0) {
|
|
@@ -37159,6 +37617,7 @@ var BestDetectionTracker = class {
|
|
|
37159
37617
|
if (containment !== void 0) this.containment.set(trackId, containment);
|
|
37160
37618
|
if (sizeScore !== void 0) this.sizeScore.set(trackId, sizeScore);
|
|
37161
37619
|
if (squareFraction !== void 0) this.squareFraction.set(trackId, squareFraction);
|
|
37620
|
+
if (canvasFill !== void 0) this.canvasFill.set(trackId, canvasFill);
|
|
37162
37621
|
if (semanticBit !== void 0) this.semantic.set(trackId, semanticBit);
|
|
37163
37622
|
return true;
|
|
37164
37623
|
}
|
|
@@ -37168,6 +37627,7 @@ var BestDetectionTracker = class {
|
|
|
37168
37627
|
const curContainment = this.containment.get(trackId);
|
|
37169
37628
|
const curSize = this.sizeScore.get(trackId);
|
|
37170
37629
|
const curSquareFraction = this.squareFraction.get(trackId);
|
|
37630
|
+
const curCanvasFill = this.canvasFill.get(trackId);
|
|
37171
37631
|
const curSemantic = this.semantic.get(trackId) ?? false;
|
|
37172
37632
|
const candSemantic = semanticBit ?? false;
|
|
37173
37633
|
const held = {
|
|
@@ -37177,6 +37637,7 @@ var BestDetectionTracker = class {
|
|
|
37177
37637
|
...curContainment !== void 0 ? { containment: curContainment } : {},
|
|
37178
37638
|
...curSize !== void 0 ? { sizeScore: curSize } : {},
|
|
37179
37639
|
...curSquareFraction !== void 0 ? { squareFraction: curSquareFraction } : {},
|
|
37640
|
+
...curCanvasFill !== void 0 ? { canvasFill: curCanvasFill } : {},
|
|
37180
37641
|
semantic: curSemantic
|
|
37181
37642
|
};
|
|
37182
37643
|
const cand = {
|
|
@@ -37186,6 +37647,7 @@ var BestDetectionTracker = class {
|
|
|
37186
37647
|
...containment !== void 0 ? { containment } : {},
|
|
37187
37648
|
...sizeScore !== void 0 ? { sizeScore } : {},
|
|
37188
37649
|
...squareFraction !== void 0 ? { squareFraction } : {},
|
|
37650
|
+
...canvasFill !== void 0 ? { canvasFill } : {},
|
|
37189
37651
|
semantic: candSemantic
|
|
37190
37652
|
};
|
|
37191
37653
|
const squareTierUpgrade = isSquareContained(cand) && !isSquareContained(held);
|
|
@@ -37194,7 +37656,9 @@ var BestDetectionTracker = class {
|
|
|
37194
37656
|
const sameSemanticTier = isSemantic(cand) === isSemantic(held);
|
|
37195
37657
|
const tierUpgrade = squareTierUpgrade || semanticTierUpgrade || sameSquareTier && sameSemanticTier && candClear && !curClear;
|
|
37196
37658
|
const gapOk = timestamp - cur.atMs >= this.minGapMs || sizeScore !== void 0 && isDecisiveImprovement(held, cand);
|
|
37197
|
-
const
|
|
37659
|
+
const comparison = compareBestFrame(held, cand, this.hysteresis);
|
|
37660
|
+
this.lastComparison.set(trackId, comparison);
|
|
37661
|
+
const isNewBest = tierUpgrade ? true : comparison.accepted && gapOk;
|
|
37198
37662
|
if (isNewBest) {
|
|
37199
37663
|
this.best.set(trackId, {
|
|
37200
37664
|
confidence,
|
|
@@ -37205,11 +37669,46 @@ var BestDetectionTracker = class {
|
|
|
37205
37669
|
if (containment !== void 0) this.containment.set(trackId, containment);
|
|
37206
37670
|
if (sizeScore !== void 0) this.sizeScore.set(trackId, sizeScore);
|
|
37207
37671
|
if (squareFraction !== void 0) this.squareFraction.set(trackId, squareFraction);
|
|
37672
|
+
if (canvasFill !== void 0) this.canvasFill.set(trackId, canvasFill);
|
|
37208
37673
|
this.semantic.set(trackId, candSemantic);
|
|
37209
37674
|
}
|
|
37210
37675
|
return isNewBest;
|
|
37211
37676
|
}
|
|
37212
37677
|
/**
|
|
37678
|
+
* The FULL candidate shape of the track's held peak — everything the ranking
|
|
37679
|
+
* saw, not just `{confidence, atMs}`. Read BEFORE `observe` so a promotion
|
|
37680
|
+
* attempt can be logged against the peak it actually competed with (D400).
|
|
37681
|
+
* Undefined when the track holds no peak (the first sighting).
|
|
37682
|
+
*/
|
|
37683
|
+
heldCandidate(trackId) {
|
|
37684
|
+
const cur = this.best.get(trackId);
|
|
37685
|
+
if (cur === void 0) return void 0;
|
|
37686
|
+
const centerScore = this.centerScore.get(trackId);
|
|
37687
|
+
const containment = this.containment.get(trackId);
|
|
37688
|
+
const sizeScore = this.sizeScore.get(trackId);
|
|
37689
|
+
const squareFraction = this.squareFraction.get(trackId);
|
|
37690
|
+
const canvasFill = this.canvasFill.get(trackId);
|
|
37691
|
+
return {
|
|
37692
|
+
confidence: cur.confidence,
|
|
37693
|
+
edgeClear: this.edgeClear.get(trackId) ?? true,
|
|
37694
|
+
...centerScore !== void 0 ? { centerScore } : {},
|
|
37695
|
+
...containment !== void 0 ? { containment } : {},
|
|
37696
|
+
...sizeScore !== void 0 ? { sizeScore } : {},
|
|
37697
|
+
...squareFraction !== void 0 ? { squareFraction } : {},
|
|
37698
|
+
...canvasFill !== void 0 ? { canvasFill } : {},
|
|
37699
|
+
semantic: this.semantic.get(trackId) ?? false
|
|
37700
|
+
};
|
|
37701
|
+
}
|
|
37702
|
+
/**
|
|
37703
|
+
* The comparison the LAST {@link observe} ran for this track — which term
|
|
37704
|
+
* decided, and both effective scores. Undefined on a first sighting (nothing
|
|
37705
|
+
* was compared) and after {@link delete}. Feeds the promotion log line; it is
|
|
37706
|
+
* never a ranking input.
|
|
37707
|
+
*/
|
|
37708
|
+
lastComparisonFor(trackId) {
|
|
37709
|
+
return this.lastComparison.get(trackId);
|
|
37710
|
+
}
|
|
37711
|
+
/**
|
|
37213
37712
|
* Stamp the HELD peak as semantic — the attribution-time re-score for a
|
|
37214
37713
|
* recognition that arrived AFTER the frame was ranked (detail results are
|
|
37215
37714
|
* async). Use when the recognition's source frame IS the held peak: the
|
|
@@ -37235,7 +37734,9 @@ var BestDetectionTracker = class {
|
|
|
37235
37734
|
this.containment.delete(trackId);
|
|
37236
37735
|
this.sizeScore.delete(trackId);
|
|
37237
37736
|
this.squareFraction.delete(trackId);
|
|
37737
|
+
this.canvasFill.delete(trackId);
|
|
37238
37738
|
this.semantic.delete(trackId);
|
|
37739
|
+
this.lastComparison.delete(trackId);
|
|
37239
37740
|
}
|
|
37240
37741
|
clear() {
|
|
37241
37742
|
this.best.clear();
|
|
@@ -37244,7 +37745,9 @@ var BestDetectionTracker = class {
|
|
|
37244
37745
|
this.containment.clear();
|
|
37245
37746
|
this.sizeScore.clear();
|
|
37246
37747
|
this.squareFraction.clear();
|
|
37748
|
+
this.canvasFill.clear();
|
|
37247
37749
|
this.semantic.clear();
|
|
37750
|
+
this.lastComparison.clear();
|
|
37248
37751
|
}
|
|
37249
37752
|
};
|
|
37250
37753
|
//#endregion
|
|
@@ -37506,9 +38009,29 @@ var TrackResidentState = class {
|
|
|
37506
38009
|
}
|
|
37507
38010
|
/** Record a detection for the track's best-FRAME ranking (edge-aware §5
|
|
37508
38011
|
* policy). Returns true when it becomes the track's new best. */
|
|
37509
|
-
observeBestFrame(deviceId, trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, semantic) {
|
|
38012
|
+
observeBestFrame(deviceId, trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, semantic, canvasFill) {
|
|
37510
38013
|
this.open(deviceId, trackId);
|
|
37511
|
-
return this.bestFrameTracker.observe(trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, semantic);
|
|
38014
|
+
return this.bestFrameTracker.observe(trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, semantic, canvasFill);
|
|
38015
|
+
}
|
|
38016
|
+
/** D400: frame instant of the track's last logged promotion REFUSAL
|
|
38017
|
+
* (undefined = none yet). */
|
|
38018
|
+
bestShotRefusalLoggedAt(trackId) {
|
|
38019
|
+
return this.residents.get(trackId)?.bestShotRefusalLoggedAt;
|
|
38020
|
+
}
|
|
38021
|
+
/** Stamp the track's last promotion-refusal log instant. */
|
|
38022
|
+
noteBestShotRefusalLogged(deviceId, trackId, timestamp) {
|
|
38023
|
+
this.ensure(deviceId, trackId).bestShotRefusalLoggedAt = timestamp;
|
|
38024
|
+
}
|
|
38025
|
+
/** The FULL held best-frame candidate (everything the ranking saw). Read
|
|
38026
|
+
* BEFORE `observeBestFrame` to log a promotion attempt against the peak it
|
|
38027
|
+
* competed with (D400). */
|
|
38028
|
+
bestFrameHeld(trackId) {
|
|
38029
|
+
return this.bestFrameTracker.heldCandidate(trackId);
|
|
38030
|
+
}
|
|
38031
|
+
/** Which term decided the LAST best-frame comparison for the track (D400).
|
|
38032
|
+
* Undefined on a first sighting. */
|
|
38033
|
+
bestFrameLastComparison(trackId) {
|
|
38034
|
+
return this.bestFrameTracker.lastComparisonFor(trackId);
|
|
37512
38035
|
}
|
|
37513
38036
|
/** The held best-frame peak for a track (undefined if never observed). */
|
|
37514
38037
|
bestFramePeak(trackId) {
|
|
@@ -39702,6 +40225,24 @@ var TRACK_CLOSE_REASONS = ["stationary-promotion"];
|
|
|
39702
40225
|
function parseCloseReason(value) {
|
|
39703
40226
|
return TRACK_CLOSE_REASONS.find((reason) => reason === value);
|
|
39704
40227
|
}
|
|
40228
|
+
/**
|
|
40229
|
+
* `positions` in TIME order (D399).
|
|
40230
|
+
*
|
|
40231
|
+
* The array is APPENDED in arrival order and the two detector passes interleave,
|
|
40232
|
+
* so it is not the same thing: seven of the fourteen tracks the 2026-09-08 audit
|
|
40233
|
+
* read had at least one position whose timestamp preceded its predecessor's, and
|
|
40234
|
+
* a consumer that draws the array as a polyline draws a path crossing back on
|
|
40235
|
+
* itself. Everything that reasons about a first, a last, or a velocity was
|
|
40236
|
+
* reading them as stored.
|
|
40237
|
+
*
|
|
40238
|
+
* Immutable — returns a new array; `toSorted` is stable, so positions sharing a
|
|
40239
|
+
* timestamp keep their arrival order relative to each other. Applied at the READ
|
|
40240
|
+
* boundaries only: the write path keeps arrival order on purpose, because that
|
|
40241
|
+
* is a fact about delivery and `totalDistance` has always been accumulated on it.
|
|
40242
|
+
*/
|
|
40243
|
+
function positionsInTimeOrder(positions) {
|
|
40244
|
+
return positions.toSorted((a, b) => a.timestamp - b.timestamp);
|
|
40245
|
+
}
|
|
39705
40246
|
var DEFAULT_CONFIG = {
|
|
39706
40247
|
ttlMs: 3e4,
|
|
39707
40248
|
maxPositionHistory: 300,
|
|
@@ -40068,7 +40609,7 @@ function cloneTrack(t) {
|
|
|
40068
40609
|
subLabelMeta: t.subLabelMeta,
|
|
40069
40610
|
firstSeen: t.firstSeen,
|
|
40070
40611
|
lastSeen: t.lastSeen,
|
|
40071
|
-
positions: t.positions.map((p) => ({
|
|
40612
|
+
positions: positionsInTimeOrder(t.positions).map((p) => ({
|
|
40072
40613
|
...p,
|
|
40073
40614
|
bbox: { ...p.bbox }
|
|
40074
40615
|
})),
|
|
@@ -41661,7 +42202,7 @@ var TrackStore = class {
|
|
|
41661
42202
|
*/
|
|
41662
42203
|
const storedObservations = data["observations"];
|
|
41663
42204
|
const observations = typeof storedObservations === "number" ? storedObservations : slim ? void 0 : storedPositions.length;
|
|
41664
|
-
const positions = storedPositions;
|
|
42205
|
+
const positions = positionsInTimeOrder(storedPositions);
|
|
41665
42206
|
const snapshots = slim ? [] : data["snapshots"] ?? [];
|
|
41666
42207
|
const zones = data["zonesVisited"] ?? [];
|
|
41667
42208
|
const classes = data["classes"];
|
|
@@ -71373,7 +71914,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71373
71914
|
x: center.x,
|
|
71374
71915
|
y: center.y,
|
|
71375
71916
|
timestamp: result.timestamp,
|
|
71376
|
-
bbox: { ...t.bbox }
|
|
71917
|
+
bbox: { ...t.bbox },
|
|
71918
|
+
source: t.nativeRecovery !== void 0 ? "recovery" : "frame",
|
|
71919
|
+
score: t.confidence
|
|
71377
71920
|
},
|
|
71378
71921
|
zones: t.zones,
|
|
71379
71922
|
state: t.state
|
|
@@ -71866,6 +72409,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71866
72409
|
mediaKey: s.mediaKey
|
|
71867
72410
|
});
|
|
71868
72411
|
for (const trackId of res.thumbnailTrackIds) this.residents.markThumbnailLanded(deviceId, trackId);
|
|
72412
|
+
const landedThumbnails = new Set(res.thumbnailTrackIds);
|
|
72413
|
+
for (const target of snapshotTargets) {
|
|
72414
|
+
if (!target.bestThumbnail || landedThumbnails.has(target.trackId)) continue;
|
|
72415
|
+
log.warn("best shot capture did not land", {
|
|
72416
|
+
tags: { deviceId },
|
|
72417
|
+
meta: {
|
|
72418
|
+
trackId: target.trackId,
|
|
72419
|
+
timestamp: target.timestamp,
|
|
72420
|
+
keyFrameRequested: target.keyFrame !== "skip"
|
|
72421
|
+
}
|
|
72422
|
+
});
|
|
72423
|
+
}
|
|
71869
72424
|
for (const trackId of res.firstFrameTrackIds) {
|
|
71870
72425
|
this.residents.clearFirstFramePending(trackId);
|
|
71871
72426
|
this.residents.markFirstFrameLanded(deviceId, trackId);
|
|
@@ -72906,7 +73461,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72906
73461
|
const ctx = this.ctxIfReady;
|
|
72907
73462
|
if (ctx === null) return;
|
|
72908
73463
|
promoteSemanticBestFrame({
|
|
72909
|
-
observeBestFrame: (o) => this.residents.observeBestFrame(o.deviceId, o.trackId, o.confidence, o.timestamp, o.edgeClear, o.centerScore, o.containment, o.sizeScore, o.squareFraction, o.semantic),
|
|
73464
|
+
observeBestFrame: (o) => this.residents.observeBestFrame(o.deviceId, o.trackId, o.confidence, o.timestamp, o.edgeClear, o.centerScore, o.containment, o.sizeScore, o.squareFraction, o.semantic, o.canvasFill),
|
|
72910
73465
|
recordBestSeenBbox: (deviceId, trackId, bbox) => this.residents.recordBestSeenBbox(deviceId, trackId, bbox),
|
|
72911
73466
|
captureBestShot: (request) => this.captureSemanticBestShot(request),
|
|
72912
73467
|
logger: ctx.logger
|
|
@@ -73111,13 +73666,20 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73111
73666
|
W: frameWidth,
|
|
73112
73667
|
H: frameHeight
|
|
73113
73668
|
});
|
|
73669
|
+
const canvasFill = canvasFillFraction(t.bbox, {
|
|
73670
|
+
W: frameWidth,
|
|
73671
|
+
H: frameHeight
|
|
73672
|
+
});
|
|
73114
73673
|
const firstBox = this.trackStore?.peekActive(t.trackId)?.positions[0]?.bbox;
|
|
73115
|
-
const
|
|
73674
|
+
const skipParkedThumb = firstBox !== void 0 && thumbnailWouldShowParkedNotSubject({
|
|
73116
73675
|
currentBbox: t.bbox,
|
|
73117
73676
|
firstBbox: firstBox,
|
|
73118
73677
|
className: t.className,
|
|
73119
73678
|
entries: this.stationaryRegistry?.listViews(deviceId) ?? []
|
|
73120
|
-
})
|
|
73679
|
+
});
|
|
73680
|
+
const heldBest = this.residents.bestFrameHeld(t.trackId);
|
|
73681
|
+
const isNewBest = skipParkedThumb ? false : this.residents.observeBestFrame(deviceId, t.trackId, t.confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, false, canvasFill);
|
|
73682
|
+
const comparison = skipParkedThumb ? void 0 : this.residents.bestFrameLastComparison(t.trackId);
|
|
73121
73683
|
this.maybeEmitTrackLifecycleUpdate(deviceId, t, timestamp, isNewBest);
|
|
73122
73684
|
const plan = planPeriodicMedia({
|
|
73123
73685
|
debugMediaEnabled: media.debugMediaEnabled,
|
|
@@ -73150,6 +73712,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73150
73712
|
state: this.residents.keyFrameCapture(t.trackId),
|
|
73151
73713
|
now: timestamp
|
|
73152
73714
|
}).kind;
|
|
73715
|
+
const suppressedBy = (() => {
|
|
73716
|
+
if (skipParkedThumb) return "parked-thumbnail";
|
|
73717
|
+
if (!isNewBest || bestThumbnail) return void 0;
|
|
73718
|
+
if (!plan.bestThumbnail) return "not-planned";
|
|
73719
|
+
if (!plausibleBox) return "implausible-box";
|
|
73720
|
+
if (!retryBoxPlausible) return "retry-box-collapsed";
|
|
73721
|
+
return "capture-in-flight";
|
|
73722
|
+
})();
|
|
73723
|
+
const promotionAttempt = {
|
|
73724
|
+
trackId: t.trackId,
|
|
73725
|
+
className: t.className,
|
|
73726
|
+
timestamp,
|
|
73727
|
+
candidate: {
|
|
73728
|
+
confidence: t.confidence,
|
|
73729
|
+
edgeClear,
|
|
73730
|
+
centerScore,
|
|
73731
|
+
containment,
|
|
73732
|
+
sizeScore,
|
|
73733
|
+
squareFraction,
|
|
73734
|
+
canvasFill,
|
|
73735
|
+
semantic: false
|
|
73736
|
+
},
|
|
73737
|
+
...heldBest !== void 0 ? { held: heldBest } : {},
|
|
73738
|
+
...comparison !== void 0 ? { comparison } : {},
|
|
73739
|
+
accepted: isNewBest,
|
|
73740
|
+
...suppressedBy !== void 0 ? { suppressedBy } : {},
|
|
73741
|
+
thumbnailRequested: bestThumbnail,
|
|
73742
|
+
keyFrameRequested: keyFrame !== "skip"
|
|
73743
|
+
};
|
|
73744
|
+
const refusalLoggedAt = this.residents.bestShotRefusalLoggedAt(t.trackId);
|
|
73745
|
+
if (shouldLogPromotionAttempt(promotionAttempt, {
|
|
73746
|
+
thumbnailLanded: this.residents.isThumbnailLanded(t.trackId),
|
|
73747
|
+
...refusalLoggedAt !== void 0 ? { lastRefusalLogAtMs: refusalLoggedAt } : {}
|
|
73748
|
+
})) {
|
|
73749
|
+
if (!isNewBest) this.residents.noteBestShotRefusalLogged(deviceId, t.trackId, timestamp);
|
|
73750
|
+
this.ctx.logger.info("best shot promotion", {
|
|
73751
|
+
tags: { deviceId },
|
|
73752
|
+
meta: { ...bestShotPromotionMeta(promotionAttempt) }
|
|
73753
|
+
});
|
|
73754
|
+
}
|
|
73153
73755
|
if (!plan.appendSnapshot && !rollingLastFrame && !bestThumbnail && keyFrame === "skip") continue;
|
|
73154
73756
|
targets.push({
|
|
73155
73757
|
trackId: t.trackId,
|
|
@@ -74501,6 +75103,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
74501
75103
|
}
|
|
74502
75104
|
});
|
|
74503
75105
|
});
|
|
75106
|
+
p.setSpawnBesideCoastingObserver((record) => {
|
|
75107
|
+
this.ctx.logger.info("tracker: new id minted beside a live coasting track", {
|
|
75108
|
+
tags: { deviceId },
|
|
75109
|
+
meta: {
|
|
75110
|
+
source,
|
|
75111
|
+
...record
|
|
75112
|
+
}
|
|
75113
|
+
});
|
|
75114
|
+
});
|
|
74504
75115
|
p.setTrackLabelState((trackId) => {
|
|
74505
75116
|
const active = this.trackStore?.getActiveByTrack(trackId);
|
|
74506
75117
|
return active !== null && active !== void 0 ? {
|