@camstack/addon-post-analysis 1.2.205 → 1.2.207
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
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-sREZj0iq.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -27668,6 +27668,89 @@ function deliveredWideCentralSquareLayout(bbox, frame) {
|
|
|
27668
27668
|
if (centralSquareContainmentFraction(bbox, frame, layout) >= .5) return layout;
|
|
27669
27669
|
return wideCentralSquareLayout(bbox, frame, { containSubject: true });
|
|
27670
27670
|
}
|
|
27671
|
+
/**
|
|
27672
|
+
* Fraction of the DELIVERED 16:9 canvas that is NOT real pixels — the lateral
|
|
27673
|
+
* fill the composer synthesizes because the window ran off a frame edge. 0 =
|
|
27674
|
+
* the whole canvas is scene; 0.21 = a fifth of the delivered image is fill.
|
|
27675
|
+
*
|
|
27676
|
+
* Measured on {@link deliveredWideCentralSquareLayout} — the layout the
|
|
27677
|
+
* persisted `thumbnail` is actually cut with — because the operator complains
|
|
27678
|
+
* about the image they were SHOWN, not about a hypothetical centred window.
|
|
27679
|
+
* The two differ exactly where it matters: the default layout slides fully
|
|
27680
|
+
* in-frame and carries no fill at all, while the delivery fallback re-imposes
|
|
27681
|
+
* the subject's slack bound (so the square still shows the subject) and pays
|
|
27682
|
+
* for it in padding.
|
|
27683
|
+
*
|
|
27684
|
+
* Upper bound: a window that can only show its central square delivers
|
|
27685
|
+
* `1 − 9/16 = 0.4375` fill; nothing can exceed that while the square itself is
|
|
27686
|
+
* in frame.
|
|
27687
|
+
*
|
|
27688
|
+
* Pure geometry (no I/O). Feeds the best-frame ranking as a BOUNDED
|
|
27689
|
+
* within-tier factor — see `edge-clear.ts#CANVAS_FILL_MAX_PENALTY` for why it
|
|
27690
|
+
* is deliberately not a tier.
|
|
27691
|
+
*/
|
|
27692
|
+
function canvasFillFraction(bbox, frame, layout = deliveredWideCentralSquareLayout(bbox, frame)) {
|
|
27693
|
+
if (layout.canvasW <= 0) return 0;
|
|
27694
|
+
return Math.max(0, Math.min(1, (layout.canvasW - layout.slabW) / layout.canvasW));
|
|
27695
|
+
}
|
|
27696
|
+
//#endregion
|
|
27697
|
+
//#region src/pipeline-analytics/pipeline/best-shot-promotion-log.ts
|
|
27698
|
+
function r3(value) {
|
|
27699
|
+
return Math.round(value * 1e3) / 1e3;
|
|
27700
|
+
}
|
|
27701
|
+
/**
|
|
27702
|
+
* Build the attempt line's meta. Pure.
|
|
27703
|
+
*
|
|
27704
|
+
* `decidedBy` is `'first-sighting'` when there was no held peak: the track's
|
|
27705
|
+
* first observation is accepted unconditionally and nothing was compared, and
|
|
27706
|
+
* saying `'effective-score'` there would invent a comparison that never ran.
|
|
27707
|
+
*/
|
|
27708
|
+
function bestShotPromotionMeta(attempt) {
|
|
27709
|
+
const { candidate, held, comparison } = attempt;
|
|
27710
|
+
return {
|
|
27711
|
+
trackId: attempt.trackId,
|
|
27712
|
+
className: attempt.className,
|
|
27713
|
+
timestamp: attempt.timestamp,
|
|
27714
|
+
accepted: attempt.accepted,
|
|
27715
|
+
decidedBy: comparison?.decidedBy ?? "first-sighting",
|
|
27716
|
+
...comparison === void 0 ? {} : {
|
|
27717
|
+
heldScore: r3(comparison.currentScore),
|
|
27718
|
+
candidateScore: r3(comparison.candidateScore)
|
|
27719
|
+
},
|
|
27720
|
+
candConfidence: r3(candidate.confidence),
|
|
27721
|
+
candEdgeClear: candidate.edgeClear,
|
|
27722
|
+
...candidate.containment === void 0 ? {} : { candContainment: r3(candidate.containment) },
|
|
27723
|
+
...candidate.sizeScore === void 0 ? {} : { candSizeScore: r3(candidate.sizeScore) },
|
|
27724
|
+
...candidate.squareFraction === void 0 ? {} : { candSquareFraction: r3(candidate.squareFraction) },
|
|
27725
|
+
...candidate.canvasFill === void 0 ? {} : { candCanvasFill: r3(candidate.canvasFill) },
|
|
27726
|
+
candSemantic: candidate.semantic === true,
|
|
27727
|
+
...held === void 0 ? {} : {
|
|
27728
|
+
heldConfidence: r3(held.confidence),
|
|
27729
|
+
heldEdgeClear: held.edgeClear,
|
|
27730
|
+
...held.canvasFill === void 0 ? {} : { heldCanvasFill: r3(held.canvasFill) }
|
|
27731
|
+
},
|
|
27732
|
+
thumbnailRequested: attempt.thumbnailRequested,
|
|
27733
|
+
keyFrameRequested: attempt.keyFrameRequested,
|
|
27734
|
+
...attempt.suppressedBy === void 0 ? {} : { suppressedBy: attempt.suppressedBy }
|
|
27735
|
+
};
|
|
27736
|
+
}
|
|
27737
|
+
/**
|
|
27738
|
+
* Is this attempt worth a line?
|
|
27739
|
+
*
|
|
27740
|
+
* Every ACCEPTED attempt is — it is the moment the operator's cover image
|
|
27741
|
+
* changes, or fails to. A REFUSED attempt is logged only while the track still
|
|
27742
|
+
* has NO picture at all, and then at most one line per
|
|
27743
|
+
* {@link PROMOTION_REFUSAL_LOG_GAP_MS}: "the best shot never moved off the birth
|
|
27744
|
+
* frame" is exactly the shape `5f5f0f6e` presented and could not be diagnosed,
|
|
27745
|
+
* while a track whose thumbnail already landed is refusing candidates normally
|
|
27746
|
+
* and has nothing to explain.
|
|
27747
|
+
*/
|
|
27748
|
+
function shouldLogPromotionAttempt(attempt, gate) {
|
|
27749
|
+
if (attempt.accepted) return true;
|
|
27750
|
+
if (gate.thumbnailLanded) return false;
|
|
27751
|
+
const last = gate.lastRefusalLogAtMs;
|
|
27752
|
+
return last === void 0 || attempt.timestamp - last >= 2e3;
|
|
27753
|
+
}
|
|
27671
27754
|
var defaultScheduler = (tick, intervalMs) => {
|
|
27672
27755
|
const handle = setInterval(tick, intervalMs);
|
|
27673
27756
|
handle.unref?.();
|
|
@@ -30336,11 +30419,16 @@ function analyseDebugTrack(inventory) {
|
|
|
30336
30419
|
const out = [];
|
|
30337
30420
|
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)."));
|
|
30338
30421
|
else if (closed) {
|
|
30339
|
-
|
|
30422
|
+
const keyFrameMissing = !hasKind(media, "keyFrame");
|
|
30423
|
+
const lastFrameCopySourceMissing = keyFrameMissing || !hasKind(media, "keyFrameSmall");
|
|
30424
|
+
const noSnapshot = !hasKind(media, "snapshot");
|
|
30425
|
+
if (keyFrameMissing) out.push(finding("MISSING_KEYFRAME", "error", "No keyFrame — the best-shot frame is absent."));
|
|
30340
30426
|
if (!hasKind(media, "firstFrame")) out.push(finding("MISSING_FIRSTFRAME", "warn", "No firstFrame for a closed track."));
|
|
30341
|
-
if (!hasKind(media, "lastFrame")) out.push(finding("MISSING_LASTFRAME", "warn", "No lastFrame for a closed track."));
|
|
30342
|
-
if (!hasKind(media, "thumbnail")) out.push(finding("MISSING_THUMBNAIL", "warn", "No thumbnail — every list surface renders blank."));
|
|
30343
|
-
|
|
30427
|
+
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."));
|
|
30428
|
+
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."));
|
|
30429
|
+
const cropExpected = facts.hasFace === true || facts.hasEmbeddedFace === true;
|
|
30430
|
+
const noCrop = !hasKind(media, "crop") && !hasKind(media, "faceCrop") && !hasKind(media, "plateCrop");
|
|
30431
|
+
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."));
|
|
30344
30432
|
}
|
|
30345
30433
|
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.`));
|
|
30346
30434
|
if (eventCount === 0) out.push(finding("ZERO_EVENTS", "error", "No object events — the track exists but recorded nothing."));
|
|
@@ -30424,7 +30512,9 @@ function toFacts(track) {
|
|
|
30424
30512
|
snapshotCount: track.snapshots.length,
|
|
30425
30513
|
...track.importance === void 0 ? {} : { importance: track.importance },
|
|
30426
30514
|
...track.bestEventId === void 0 ? {} : { bestEventId: track.bestEventId },
|
|
30427
|
-
...track.previewMissReason === void 0 ? {} : { previewMissReason: track.previewMissReason }
|
|
30515
|
+
...track.previewMissReason === void 0 ? {} : { previewMissReason: track.previewMissReason },
|
|
30516
|
+
...track.hasFace === void 0 ? {} : { hasFace: track.hasFace },
|
|
30517
|
+
...track.hasEmbeddedFace === void 0 ? {} : { hasEmbeddedFace: track.hasEmbeddedFace }
|
|
30428
30518
|
};
|
|
30429
30519
|
}
|
|
30430
30520
|
/**
|
|
@@ -33512,6 +33602,33 @@ var CONTAINMENT_MAX_SIDE_PENALTY = .5;
|
|
|
33512
33602
|
* NO best frame at all). */
|
|
33513
33603
|
var CONTAINMENT_MIN = .1;
|
|
33514
33604
|
/**
|
|
33605
|
+
* Aspect at or above which a clipped subject reads as INTACT — no penalty. 2.5
|
|
33606
|
+
* = the box is two-and-a-half times longer along the clipped axis than across
|
|
33607
|
+
* it, which is what a whole upright person standing with their feet on the
|
|
33608
|
+
* bottom border looks like.
|
|
33609
|
+
*/
|
|
33610
|
+
var CLIP_ASPECT_NO_PENALTY = 2.5;
|
|
33611
|
+
/**
|
|
33612
|
+
* How much of a clipped subject is missing, from the SUBJECT'S OWN geometry.
|
|
33613
|
+
*
|
|
33614
|
+
* `alongClip` is the bbox's extent along the axis the border cut (height for a
|
|
33615
|
+
* top/bottom clip, width for a left/right clip); `acrossClip` is its extent on
|
|
33616
|
+
* the other axis. Returns the share of {@link CONTAINMENT_MAX_SIDE_PENALTY} to
|
|
33617
|
+
* apply, ramped linearly between {@link CLIP_ASPECT_FULL_PENALTY} and
|
|
33618
|
+
* {@link CLIP_ASPECT_NO_PENALTY}.
|
|
33619
|
+
*
|
|
33620
|
+
* Truncation COMPRESSES a box along the axis it was cut on, so the surviving
|
|
33621
|
+
* aspect is the signal: it is intrinsic to the subject, scale-invariant and
|
|
33622
|
+
* frame-invariant. Degenerate (`acrossClip <= 0`) reads as fully truncated.
|
|
33623
|
+
*/
|
|
33624
|
+
function clipPenaltyScale(alongClip, acrossClip) {
|
|
33625
|
+
if (acrossClip <= 0) return 1;
|
|
33626
|
+
const aspect = alongClip / acrossClip;
|
|
33627
|
+
if (aspect <= 1) return 1;
|
|
33628
|
+
if (aspect >= 2.5) return 0;
|
|
33629
|
+
return 1 - (aspect - 1) / (CLIP_ASPECT_NO_PENALTY - 1);
|
|
33630
|
+
}
|
|
33631
|
+
/**
|
|
33515
33632
|
* Edge-CONTAINMENT factor for a (clamped) bbox: an estimate in
|
|
33516
33633
|
* `[CONTAINMENT_MIN, 1]` of the fraction of the subject that is actually in
|
|
33517
33634
|
* frame. 1 = fully contained (no border clipping); lower = the subject is
|
|
@@ -33519,15 +33636,26 @@ var CONTAINMENT_MIN = .1;
|
|
|
33519
33636
|
* walking out of view).
|
|
33520
33637
|
*
|
|
33521
33638
|
* Because detection bboxes are already CLAMPED to the frame, the true off-screen
|
|
33522
|
-
* extent is unknown, so this is a purely-geometric proxy
|
|
33523
|
-
* bbox touches (within the {@link DEFAULT_EDGE_TOLERANCE} band)
|
|
33524
|
-
* scaled by
|
|
33525
|
-
*
|
|
33526
|
-
*
|
|
33527
|
-
*
|
|
33528
|
-
*
|
|
33529
|
-
*
|
|
33530
|
-
*
|
|
33639
|
+
* extent is unknown, so this is a purely-geometric proxy. For each border the
|
|
33640
|
+
* bbox touches (within the {@link DEFAULT_EDGE_TOLERANCE} band) the penalty is
|
|
33641
|
+
* scaled by {@link clipPenaltyScale} — the surviving remnant's own aspect
|
|
33642
|
+
* ALONG the cut axis. A wide bbox flush to the top/bottom (a wide subject cut
|
|
33643
|
+
* off top/bottom) or a tall bbox flush to the left/right (a tall subject cut
|
|
33644
|
+
* off at the side) is heavily penalised, whereas a narrow subject whose feet
|
|
33645
|
+
* merely graze the bottom border keeps its score. Degenerate/unknown dims
|
|
33646
|
+
* (≤ 0) return 1 (neutral — matches {@link isEdgeClear}).
|
|
33647
|
+
*
|
|
33648
|
+
* **Why the basis changed (2026-09-08, D400, track `841f9b8e`).** The penalty
|
|
33649
|
+
* used to be scaled by the bbox's SPAN across the touched border as a fraction
|
|
33650
|
+
* of the FRAME. That calibration was written for a wide subject cut off top or
|
|
33651
|
+
* bottom, and it is meaningless for an upright person: a 4K frame is 3840 px
|
|
33652
|
+
* wide, a person clipped at the head is ~400 px wide, so `spanX = 0.104` and a
|
|
33653
|
+
* decapitated person lost **5.2 %** of its score — the operator's "questi tagli
|
|
33654
|
+
* hanno uno score troppo alto, non si vedono il dorso e testa", with the number
|
|
33655
|
+
* attached. The frame's width is not a fact about the subject. The remnant's
|
|
33656
|
+
* own aspect is: the same person now loses 47.9 %, and the wide-subject case the
|
|
33657
|
+
* old shape existed for is penalised MORE than before (a 900×400 vehicle flush
|
|
33658
|
+
* to the bottom went from 0.88 to 0.50), not less.
|
|
33531
33659
|
*/
|
|
33532
33660
|
function bboxContainment(bbox, frameWidth, frameHeight, tolerance = DEFAULT_EDGE_TOLERANCE) {
|
|
33533
33661
|
if (frameWidth <= 0 || frameHeight <= 0) return 1;
|
|
@@ -33537,16 +33665,16 @@ function bboxContainment(bbox, frameWidth, frameHeight, tolerance = DEFAULT_EDGE
|
|
|
33537
33665
|
const top = bbox.y;
|
|
33538
33666
|
const right = bbox.x + bbox.w;
|
|
33539
33667
|
const bottom = bbox.y + bbox.h;
|
|
33540
|
-
const
|
|
33541
|
-
const
|
|
33668
|
+
const verticalBorderScale = clipPenaltyScale(bbox.w, bbox.h);
|
|
33669
|
+
const horizontalBorderScale = clipPenaltyScale(bbox.h, bbox.w);
|
|
33542
33670
|
let containment = 1;
|
|
33543
|
-
const penalize = (
|
|
33544
|
-
containment *= 1 - CONTAINMENT_MAX_SIDE_PENALTY *
|
|
33671
|
+
const penalize = (scale) => {
|
|
33672
|
+
containment *= 1 - CONTAINMENT_MAX_SIDE_PENALTY * scale;
|
|
33545
33673
|
};
|
|
33546
|
-
if (left <= tolX) penalize(
|
|
33547
|
-
if (right >= frameWidth - tolX) penalize(
|
|
33548
|
-
if (top <= tolY) penalize(
|
|
33549
|
-
if (bottom >= frameHeight - tolY) penalize(
|
|
33674
|
+
if (left <= tolX) penalize(verticalBorderScale);
|
|
33675
|
+
if (right >= frameWidth - tolX) penalize(verticalBorderScale);
|
|
33676
|
+
if (top <= tolY) penalize(horizontalBorderScale);
|
|
33677
|
+
if (bottom >= frameHeight - tolY) penalize(horizontalBorderScale);
|
|
33550
33678
|
return Math.max(CONTAINMENT_MIN, Math.min(1, containment));
|
|
33551
33679
|
}
|
|
33552
33680
|
/** Fraction of the frame area at which a subject's size score saturates to 1.
|
|
@@ -33593,13 +33721,38 @@ var SQUARE_EVICTION_SCORE_FLOOR = .05;
|
|
|
33593
33721
|
* raw confidence — the ABSOLUTE hysteresis margin stays meaningful — while a
|
|
33594
33722
|
* bigger view still earns up to a 2× relative preference. */
|
|
33595
33723
|
var SIZE_FACTOR_FLOOR = .5;
|
|
33724
|
+
/**
|
|
33725
|
+
* Maximum fraction of a candidate's effective score the DELIVERED-CANVAS fill
|
|
33726
|
+
* can subtract, at 100 % fill (D400).
|
|
33727
|
+
*
|
|
33728
|
+
* **Why this is a factor and not a tier.** Fill is anti-correlated with subject
|
|
33729
|
+
* size by construction: the 16:9 window is `side × 16/9` of the subject's
|
|
33730
|
+
* central square, so a big, close subject is far likelier to overflow the frame
|
|
33731
|
+
* than a small distant one — a small subject near the centre has zero fill
|
|
33732
|
+
* ALWAYS. A hard "no-fill beats fill" tier would therefore systematically
|
|
33733
|
+
* promote distant, tiny subjects over close ones, which is the exact defect
|
|
33734
|
+
* `bboxSizeScore` was added to fix (BEST_SHOT_STALE, 2026-07-22 / 2026-09-03).
|
|
33735
|
+
* So it enters as a bounded multiplicative factor in the same family as
|
|
33736
|
+
* `containment` and `sizeFactor`, strictly BELOW every tier, and as a near-
|
|
33737
|
+
* equal tie-break ranked ABOVE centeredness (a measurement of the delivered
|
|
33738
|
+
* image beats a proxy for it).
|
|
33739
|
+
*
|
|
33740
|
+
* **Bounded so it can only ever tie-break.** The real fill ceiling is 0.4375
|
|
33741
|
+
* (a window that can show no more than its own central square), so the largest
|
|
33742
|
+
* move this term can make is `0.15 × 0.4375 = 6.6 %` of the effective score. It
|
|
33743
|
+
* cannot overturn a lead of 7.1 % or more — and the metric that would say it
|
|
33744
|
+
* has stopped tie-breaking and started dominating is the share of accepted
|
|
33745
|
+
* best-shot promotions logged with `decidedBy: 'canvas-fill'`.
|
|
33746
|
+
*/
|
|
33747
|
+
var CANVAS_FILL_MAX_PENALTY = .15;
|
|
33596
33748
|
/** The effective within-tier score: detector confidence scaled by the
|
|
33597
|
-
* edge-containment
|
|
33598
|
-
* when absent. */
|
|
33749
|
+
* edge-containment, (floored) subject-size and delivered-canvas-fill factors —
|
|
33750
|
+
* each neutral at 1 when absent. */
|
|
33599
33751
|
function effectiveScore(c) {
|
|
33600
33752
|
const sizeFactor = c.sizeScore === void 0 ? 1 : SIZE_FACTOR_FLOOR + (1 - SIZE_FACTOR_FLOOR) * c.sizeScore;
|
|
33601
33753
|
const squareFactor = isSquareContained(c) ? 1 : Math.max(c.squareFraction ?? 0, SQUARE_EVICTION_SCORE_FLOOR);
|
|
33602
|
-
|
|
33754
|
+
const fillFactor = 1 - CANVAS_FILL_MAX_PENALTY * Math.max(0, Math.min(1, c.canvasFill ?? 0));
|
|
33755
|
+
return c.confidence * (c.containment ?? 1) * sizeFactor * squareFactor * fillFactor;
|
|
33603
33756
|
}
|
|
33604
33757
|
/**
|
|
33605
33758
|
* True when `candidate` beats `current` DECISIVELY — same-or-better edge tier
|
|
@@ -33617,48 +33770,51 @@ function isDecisiveImprovement(current, candidate) {
|
|
|
33617
33770
|
return effectiveScore(candidate) >= effectiveScore(current) * DECISIVE_IMPROVEMENT_RATIO;
|
|
33618
33771
|
}
|
|
33619
33772
|
/**
|
|
33620
|
-
*
|
|
33621
|
-
*
|
|
33622
|
-
*
|
|
33623
|
-
*
|
|
33624
|
-
*
|
|
33625
|
-
*
|
|
33626
|
-
*
|
|
33627
|
-
*
|
|
33628
|
-
*
|
|
33629
|
-
*
|
|
33630
|
-
*
|
|
33631
|
-
*
|
|
33632
|
-
*
|
|
33633
|
-
*
|
|
33634
|
-
*
|
|
33635
|
-
*
|
|
33636
|
-
*
|
|
33637
|
-
|
|
33638
|
-
|
|
33639
|
-
|
|
33640
|
-
|
|
33641
|
-
|
|
33642
|
-
|
|
33643
|
-
|
|
33644
|
-
|
|
33645
|
-
|
|
33646
|
-
|
|
33647
|
-
|
|
33648
|
-
|
|
33649
|
-
|
|
33650
|
-
if (
|
|
33651
|
-
if (
|
|
33652
|
-
if (
|
|
33653
|
-
if (candidate
|
|
33654
|
-
if (
|
|
33655
|
-
|
|
33656
|
-
|
|
33657
|
-
|
|
33773
|
+
* The ONE best-frame comparison, decomposed. {@link isEdgeAwareNewBest} is this
|
|
33774
|
+
* function's `accepted` bit and nothing else — there is no second copy of the
|
|
33775
|
+
* ordering to drift.
|
|
33776
|
+
*
|
|
33777
|
+
* The tier order, highest first, and where the delivered-canvas term sits:
|
|
33778
|
+
* 1. **square-containment** — does the delivered SQUARE show the subject at all?
|
|
33779
|
+
* 2. **semantic** — did a recognition succeed on this frame?
|
|
33780
|
+
* 3. **edge-clear** — is the whole subject inside the frame?
|
|
33781
|
+
* 4. **effective-score** — `confidence × containment × size × square × canvasFill`.
|
|
33782
|
+
* 5. **canvas-fill** — near-equal tie-break: less synthesized lateral fill wins.
|
|
33783
|
+
* 6. **centeredness** — near-equal tie-break of last resort.
|
|
33784
|
+
*
|
|
33785
|
+
* Canvas fill enters at 4 (bounded, see {@link CANVAS_FILL_MAX_PENALTY}) and 5,
|
|
33786
|
+
* never as a tier: fill is anti-correlated with subject size, so a tier would
|
|
33787
|
+
* hand every camera's best shot to its most distant subject. It ranks ABOVE
|
|
33788
|
+
* centeredness because both answer "is this a well-framed delivery?" and fill
|
|
33789
|
+
* MEASURES the delivered canvas while centeredness only proxies it.
|
|
33790
|
+
*/
|
|
33791
|
+
function compareBestFrame(current, candidate, hysteresis) {
|
|
33792
|
+
const currentScore = effectiveScore(current);
|
|
33793
|
+
const candidateScore = effectiveScore(candidate);
|
|
33794
|
+
const verdict = (accepted, decidedBy) => ({
|
|
33795
|
+
accepted,
|
|
33796
|
+
decidedBy,
|
|
33797
|
+
currentScore,
|
|
33798
|
+
candidateScore
|
|
33799
|
+
});
|
|
33800
|
+
return compareBestFrameTiers(current, candidate, hysteresis, currentScore, candidateScore, verdict);
|
|
33801
|
+
}
|
|
33802
|
+
function compareBestFrameTiers(current, candidate, hysteresis, curEffective, candEffective, verdict) {
|
|
33803
|
+
if (isSquareContained(candidate) && !isSquareContained(current)) return verdict(true, "square-containment");
|
|
33804
|
+
if (!isSquareContained(candidate) && isSquareContained(current)) return verdict(false, "square-containment");
|
|
33805
|
+
if (isSemantic(candidate) && !isSemantic(current)) return verdict(true, "semantic");
|
|
33806
|
+
if (!isSemantic(candidate) && isSemantic(current)) return verdict(false, "semantic");
|
|
33807
|
+
if (candidate.edgeClear && !current.edgeClear) return verdict(true, "edge-clear");
|
|
33808
|
+
if (!candidate.edgeClear && current.edgeClear) return verdict(false, "edge-clear");
|
|
33809
|
+
if (candEffective > curEffective + hysteresis) return verdict(true, "effective-score");
|
|
33810
|
+
const nearEqual = Math.abs(candEffective - curEffective) <= hysteresis;
|
|
33811
|
+
if (candidate.canvasFill !== void 0 && current.canvasFill !== void 0) {
|
|
33812
|
+
if (nearEqual && candidate.canvasFill + .05 < current.canvasFill) return verdict(true, "canvas-fill");
|
|
33813
|
+
}
|
|
33658
33814
|
if (candidate.centerScore !== void 0 && current.centerScore !== void 0) {
|
|
33659
|
-
if (
|
|
33815
|
+
if (nearEqual && candidate.centerScore > current.centerScore + .1) return verdict(true, "centeredness");
|
|
33660
33816
|
}
|
|
33661
|
-
return false;
|
|
33817
|
+
return verdict(false, nearEqual ? "centeredness" : "effective-score");
|
|
33662
33818
|
}
|
|
33663
33819
|
//#endregion
|
|
33664
33820
|
//#region src/pipeline-analytics/pipeline/first-frame-retry-bbox.ts
|
|
@@ -34077,6 +34233,74 @@ var LooseBindSampler = class {
|
|
|
34077
34233
|
this.counts.clear();
|
|
34078
34234
|
}
|
|
34079
34235
|
};
|
|
34236
|
+
/**
|
|
34237
|
+
* Floor on the predictive-drift clamp, in box sides.
|
|
34238
|
+
*
|
|
34239
|
+
* The scaled term collapses at short intervals — at the 56 ms minimum measured
|
|
34240
|
+
* on camera 615 it is 0.11 sides — and a clamp that tight turns ordinary
|
|
34241
|
+
* detector box jitter into a refused match. A quarter of a side is slack, not
|
|
34242
|
+
* travel.
|
|
34243
|
+
*/
|
|
34244
|
+
var DRIFT_MIN_SIDES = .25;
|
|
34245
|
+
/**
|
|
34246
|
+
* Re-acquisition reach, in box DIAGONALS per second.
|
|
34247
|
+
*
|
|
34248
|
+
* Answers `267840a1` again, on the gate that actually refused it: 908.4 px of
|
|
34249
|
+
* centroid travel in 880 ms against a `hypot(513.6, 721.2)` = 885.5 px diagonal
|
|
34250
|
+
* is 1.166 diagonals per second. 1.5 clears it with ~29 % margin and is
|
|
34251
|
+
* physically bounded — 1.5 of a subject's own body-diagonal per second is a
|
|
34252
|
+
* brisk run, and being scale-free it means the same thing for a far-field
|
|
34253
|
+
* person with a 90-px box as for a near one with a 900-px box.
|
|
34254
|
+
*
|
|
34255
|
+
* The number it replaces (`rescueCentroidFactor` 0.75 × diagonal, flat) was
|
|
34256
|
+
* doing two jobs at once: at a 2135 ms poll gap it was far too tight to bridge
|
|
34257
|
+
* a walking subject, and at a 150 ms burst interval it was loose enough to hand
|
|
34258
|
+
* one subject's id to another 452 px away (track `27855a56`, a second car, at
|
|
34259
|
+
* IoU 0.058). One number, two opposite failures.
|
|
34260
|
+
*/
|
|
34261
|
+
var REACH_DIAGONALS_PER_SEC = 1.5;
|
|
34262
|
+
/**
|
|
34263
|
+
* Floor on the re-acquisition reach, in box diagonals.
|
|
34264
|
+
*
|
|
34265
|
+
* Deliberately TIGHTER than the 0.75 flat factor it replaces. At a 150 ms burst
|
|
34266
|
+
* interval the scaled term is 0.225 diagonals; the floor lifts it to 0.3, which
|
|
34267
|
+
* is 221 px on `27855a56`'s 738-px diagonal — comfortably below the 452 px that
|
|
34268
|
+
* bought that track a second car. A short gap is evidence that the subject
|
|
34269
|
+
* cannot have gone far, and the gate now says so.
|
|
34270
|
+
*/
|
|
34271
|
+
var REACH_MIN_DIAGONALS = .3;
|
|
34272
|
+
/**
|
|
34273
|
+
* Ceiling on the re-acquisition reach, in box diagonals.
|
|
34274
|
+
*
|
|
34275
|
+
* `REACH_DIAGONALS_PER_SEC × BUDGET_CEILING_MS`. A subject unseen for the full
|
|
34276
|
+
* `maxMissedMs` gets 1.5 diagonals of reach, not 6.
|
|
34277
|
+
*/
|
|
34278
|
+
var REACH_MAX_DIAGONALS = 1.5;
|
|
34279
|
+
function clampRange(value, min, max) {
|
|
34280
|
+
return Math.max(min, Math.min(max, value));
|
|
34281
|
+
}
|
|
34282
|
+
/**
|
|
34283
|
+
* How far a coasting box's velocity extrapolation may carry it on one axis.
|
|
34284
|
+
*
|
|
34285
|
+
* `side` is the box's width for the x drift and its height for the y drift, so
|
|
34286
|
+
* the allowance keeps the same meaning for a tall person and a wide car.
|
|
34287
|
+
*/
|
|
34288
|
+
function driftAllowancePx(side, elapsedMs) {
|
|
34289
|
+
const elapsed = Math.max(0, elapsedMs);
|
|
34290
|
+
return clampRange(side * 2 * (elapsed / 1e3), side * DRIFT_MIN_SIDES, side * 2);
|
|
34291
|
+
}
|
|
34292
|
+
/**
|
|
34293
|
+
* How far from where a track is EXPECTED a detection may sit and still be
|
|
34294
|
+
* claimed as that track by the loose (rescue / resurrection) reach clause.
|
|
34295
|
+
*
|
|
34296
|
+
* Scale-free: the budget is a multiple of the track's own diagonal, so it does
|
|
34297
|
+
* not have to know the frame size, the camera, or how far away the subject is.
|
|
34298
|
+
*/
|
|
34299
|
+
function reacquireReachPx(bbox, elapsedMs) {
|
|
34300
|
+
const diag = Math.hypot(bbox.w, bbox.h);
|
|
34301
|
+
const elapsed = Math.max(0, elapsedMs);
|
|
34302
|
+
return clampRange(diag * REACH_DIAGONALS_PER_SEC * (elapsed / 1e3), diag * REACH_MIN_DIAGONALS, diag * REACH_MAX_DIAGONALS);
|
|
34303
|
+
}
|
|
34080
34304
|
//#endregion
|
|
34081
34305
|
//#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
|
|
34082
34306
|
/**
|
|
@@ -34136,6 +34360,21 @@ function growthAdjacentMatch(track, det, ctx) {
|
|
|
34136
34360
|
}
|
|
34137
34361
|
/** Per-reason counter + sampling decision. One instance per tracker, so the
|
|
34138
34362
|
* counts are per camera and per detection source. */
|
|
34363
|
+
var SpawnBesideCoastingSampler = class {
|
|
34364
|
+
counts = /* @__PURE__ */ new Map();
|
|
34365
|
+
/** The running count when this report should be emitted, else `undefined`. */
|
|
34366
|
+
next(reason) {
|
|
34367
|
+
const count = (this.counts.get(reason) ?? 0) + 1;
|
|
34368
|
+
this.counts.set(reason, count);
|
|
34369
|
+
if (count <= 10) return count;
|
|
34370
|
+
return count % 10 === 0 ? count : void 0;
|
|
34371
|
+
}
|
|
34372
|
+
reset() {
|
|
34373
|
+
this.counts.clear();
|
|
34374
|
+
}
|
|
34375
|
+
};
|
|
34376
|
+
/** Per-reason counter + sampling decision. One instance per tracker, so the
|
|
34377
|
+
* counts are per camera and per detection source. */
|
|
34139
34378
|
var SpawnRefusalSampler = class {
|
|
34140
34379
|
counts = /* @__PURE__ */ new Map();
|
|
34141
34380
|
/** The running count when this refusal should be reported, else `undefined`. */
|
|
@@ -34160,6 +34399,7 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
34160
34399
|
maxTrackLifetimeMs: 3e5,
|
|
34161
34400
|
rescueIouThreshold: .1,
|
|
34162
34401
|
rescueCentroidFactor: .75,
|
|
34402
|
+
reacquireTimeScaled: true,
|
|
34163
34403
|
resurrectionWindowMs: 8e3,
|
|
34164
34404
|
resurrectionCrowdGuard: true,
|
|
34165
34405
|
resurrectionCrowdedIouThreshold: .3,
|
|
@@ -34234,6 +34474,10 @@ var SortTracker = class SortTracker {
|
|
|
34234
34474
|
refusalSampler = new SpawnRefusalSampler();
|
|
34235
34475
|
/** Sink for dropped-detection diagnostics — see {@link setSpawnRefusalObserver}. */
|
|
34236
34476
|
spawnRefusalObserver = null;
|
|
34477
|
+
/** Per-reason counters behind the sampled spawn-beside-coasting diagnostics. */
|
|
34478
|
+
spawnBesideSampler = new SpawnBesideCoastingSampler();
|
|
34479
|
+
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
34480
|
+
spawnBesideObserver = null;
|
|
34237
34481
|
constructor(config = {}) {
|
|
34238
34482
|
this.config = {
|
|
34239
34483
|
...DEFAULT_TRACKER_CONFIG,
|
|
@@ -34258,6 +34502,88 @@ var SortTracker = class SortTracker {
|
|
|
34258
34502
|
setSpawnRefusalObserver(observer) {
|
|
34259
34503
|
this.spawnRefusalObserver = observer;
|
|
34260
34504
|
}
|
|
34505
|
+
/**
|
|
34506
|
+
* Wire the diagnostics sink for the case the tracker does NOT act on: a new id
|
|
34507
|
+
* minted beside a live, compatible, coasting track that could have claimed it.
|
|
34508
|
+
* Same contract as the other two sinks — the tracker has no `deviceId`, the
|
|
34509
|
+
* caller stamps it, and nothing is computed when no observer is wired.
|
|
34510
|
+
*/
|
|
34511
|
+
setSpawnBesideCoastingObserver(observer) {
|
|
34512
|
+
this.spawnBesideObserver = observer;
|
|
34513
|
+
}
|
|
34514
|
+
/**
|
|
34515
|
+
* Report a fresh id minted beside the nearest live coasting track of its
|
|
34516
|
+
* association group, when there was one within
|
|
34517
|
+
* {@link SPAWN_BESIDE_REPORT_REACH_FACTOR} × the reach the gate used.
|
|
34518
|
+
*
|
|
34519
|
+
* `coasting` is every track surviving this frame that did NOT match a real
|
|
34520
|
+
* detection on it — the only ones a spawn can be the continuation of.
|
|
34521
|
+
*/
|
|
34522
|
+
reportSpawnBesideCoasting(spawnedId, det, coasting, detections, frameArea, timestamp) {
|
|
34523
|
+
const observer = this.spawnBesideObserver;
|
|
34524
|
+
if (!observer) return;
|
|
34525
|
+
const group = this.assocGroup(det.class);
|
|
34526
|
+
const dc = bboxCentroid(det.bbox);
|
|
34527
|
+
let best;
|
|
34528
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
34529
|
+
let bestReach = 0;
|
|
34530
|
+
for (const track of coasting) {
|
|
34531
|
+
if (this.assocGroup(track.class) !== group) continue;
|
|
34532
|
+
const tc = bboxCentroid(track.bbox);
|
|
34533
|
+
const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
|
|
34534
|
+
const reach = this.reacquireReach(track, timestamp, true);
|
|
34535
|
+
if (dist > reach * 2) continue;
|
|
34536
|
+
if (dist < bestDist) {
|
|
34537
|
+
best = track;
|
|
34538
|
+
bestDist = dist;
|
|
34539
|
+
bestReach = reach;
|
|
34540
|
+
}
|
|
34541
|
+
}
|
|
34542
|
+
if (!best) return;
|
|
34543
|
+
const declinedBy = this.explainDecline(best, det, detections, frameArea, timestamp, bestDist);
|
|
34544
|
+
const spawnCount = this.spawnBesideSampler.next(declinedBy);
|
|
34545
|
+
if (spawnCount === void 0) return;
|
|
34546
|
+
observer({
|
|
34547
|
+
spawnedTrackId: spawnedId,
|
|
34548
|
+
spawnedClass: det.class,
|
|
34549
|
+
spawnedScore: det.score,
|
|
34550
|
+
spawnedBbox: det.bbox,
|
|
34551
|
+
spawnedNativeRecovery: det.nativeRecovery !== void 0,
|
|
34552
|
+
coastingTrackId: best.id,
|
|
34553
|
+
coastingClass: best.class,
|
|
34554
|
+
iou: iou$3(best.bbox, det.bbox),
|
|
34555
|
+
predictedIou: iou$3(this.predicted(best, timestamp), det.bbox),
|
|
34556
|
+
centroidDistPx: bestDist,
|
|
34557
|
+
reachPx: bestReach,
|
|
34558
|
+
trackDiagPx: Math.hypot(best.bbox.w, best.bbox.h),
|
|
34559
|
+
gapMs: timestamp - best.lastSeen,
|
|
34560
|
+
trackAgeMs: timestamp - best.firstSeen,
|
|
34561
|
+
declinedBy,
|
|
34562
|
+
spawnCount,
|
|
34563
|
+
sampledEvery: 10
|
|
34564
|
+
});
|
|
34565
|
+
}
|
|
34566
|
+
/**
|
|
34567
|
+
* Which gate turned `track` away from `det`, evaluated in the order the
|
|
34568
|
+
* tracker itself evaluates them — the FIRST one that says no is the answer.
|
|
34569
|
+
*
|
|
34570
|
+
* Every clause here re-reads a fact about the frame rather than about the
|
|
34571
|
+
* greedy pass's claim order, so the verdict is reproducible: the one case that
|
|
34572
|
+
* cannot be re-derived is a pair nothing refused, which is exactly what
|
|
34573
|
+
* `already-claimed` means.
|
|
34574
|
+
*/
|
|
34575
|
+
explainDecline(track, det, detections, frameArea, timestamp, centroidDist) {
|
|
34576
|
+
if (!this.sameGroupAsMeasured(track.class, det)) return "native-measured-class";
|
|
34577
|
+
if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return "assoc-group";
|
|
34578
|
+
if (iou$3(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "already-claimed";
|
|
34579
|
+
if (centroidDist <= this.reacquireReach(track, timestamp, true)) {
|
|
34580
|
+
if (!this.isPlausibleBirth(det, frameArea)) return "already-claimed";
|
|
34581
|
+
if (!this.config.reacquireTimeScaled) return "plausible-birth-veto";
|
|
34582
|
+
return this.isUnambiguousReach(track, det, detections) ? "already-claimed" : "crowd-guard";
|
|
34583
|
+
}
|
|
34584
|
+
if (growthAdjacentMatch(track.bbox, det.bbox, SortTracker.growthContext(track, timestamp))) return "already-claimed";
|
|
34585
|
+
return "geometry";
|
|
34586
|
+
}
|
|
34261
34587
|
/** Emit one dropped-detection record, subject to the refusal sampler. */
|
|
34262
34588
|
reportRefusal(reason, det, extra = {}) {
|
|
34263
34589
|
const observer = this.spawnRefusalObserver;
|
|
@@ -34526,21 +34852,39 @@ var SortTracker = class SortTracker {
|
|
|
34526
34852
|
if (a !== void 0 && dropIds.has(a) || b !== void 0 && dropIds.has(b)) this.dupStreaks.delete(key);
|
|
34527
34853
|
}
|
|
34528
34854
|
}
|
|
34529
|
-
/**
|
|
34530
|
-
*
|
|
34531
|
-
*
|
|
34532
|
-
*
|
|
34533
|
-
*
|
|
34534
|
-
*
|
|
34535
|
-
|
|
34855
|
+
/**
|
|
34856
|
+
* Where a track is expected at `nowMs` — extrapolated by velocity while
|
|
34857
|
+
* coasting (`predictiveCoasting`), else its last known bbox.
|
|
34858
|
+
*
|
|
34859
|
+
* A stationary track (speed below `stationarySpeedPx`, measured in the
|
|
34860
|
+
* px-per-update unit that setting is calibrated in) is frozen so bbox jitter
|
|
34861
|
+
* cannot walk the box off a sitting object. Otherwise the drift is the
|
|
34862
|
+
* velocity carried over the ELAPSED interval, capped by
|
|
34863
|
+
* {@link driftAllowancePx} — which is itself a function of that interval, so
|
|
34864
|
+
* a stale velocity cannot fling the box across the frame and a long, honest
|
|
34865
|
+
* gap is no longer capped at the one box width a single frame would get
|
|
34866
|
+
* (D399). Public so the clamp is directly unit-testable.
|
|
34867
|
+
*/
|
|
34868
|
+
predicted(t, nowMs) {
|
|
34536
34869
|
if (!this.config.predictiveCoasting) return t.bbox;
|
|
34537
|
-
if (Math.hypot(t.velocity.dx, t.velocity.dy) < this.config.stationarySpeedPx) return t.bbox;
|
|
34538
|
-
|
|
34539
|
-
|
|
34540
|
-
|
|
34870
|
+
if (Math.hypot(t.velocity.dx, t.velocity.dy) * t.emitIntervalMs < this.config.stationarySpeedPx) return t.bbox;
|
|
34871
|
+
if (!this.config.reacquireTimeScaled) {
|
|
34872
|
+
const f = t.age + 1;
|
|
34873
|
+
const perFrameX = t.velocity.dx * t.emitIntervalMs;
|
|
34874
|
+
const perFrameY = t.velocity.dy * t.emitIntervalMs;
|
|
34875
|
+
return {
|
|
34876
|
+
x: t.bbox.x + clamp(perFrameX * f, -t.bbox.w, t.bbox.w),
|
|
34877
|
+
y: t.bbox.y + clamp(perFrameY * f, -t.bbox.h, t.bbox.h),
|
|
34878
|
+
w: t.bbox.w,
|
|
34879
|
+
h: t.bbox.h
|
|
34880
|
+
};
|
|
34881
|
+
}
|
|
34882
|
+
const elapsedMs = Math.max(0, nowMs - t.lastSeen);
|
|
34883
|
+
const allowX = driftAllowancePx(t.bbox.w, elapsedMs);
|
|
34884
|
+
const allowY = driftAllowancePx(t.bbox.h, elapsedMs);
|
|
34541
34885
|
return {
|
|
34542
|
-
x: t.bbox.x +
|
|
34543
|
-
y: t.bbox.y +
|
|
34886
|
+
x: t.bbox.x + clamp(t.velocity.dx * elapsedMs, -allowX, allowX),
|
|
34887
|
+
y: t.bbox.y + clamp(t.velocity.dy * elapsedMs, -allowY, allowY),
|
|
34544
34888
|
w: t.bbox.w,
|
|
34545
34889
|
h: t.bbox.h
|
|
34546
34890
|
};
|
|
@@ -34583,19 +34927,64 @@ var SortTracker = class SortTracker {
|
|
|
34583
34927
|
* crouch→animal flip when `classGroupAssoc` is on, but never by an unrelated
|
|
34584
34928
|
* class.
|
|
34585
34929
|
*/
|
|
34586
|
-
looseMatch(track, det, timestamp) {
|
|
34930
|
+
looseMatch(track, det, timestamp, timeScaled = true) {
|
|
34587
34931
|
if (!this.sameGroupAsMeasured(track.class, det)) return null;
|
|
34588
34932
|
if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return null;
|
|
34589
34933
|
if (iou$3(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
|
|
34590
34934
|
const tc = bboxCentroid(track.bbox);
|
|
34591
34935
|
const dc = bboxCentroid(det.bbox);
|
|
34592
|
-
|
|
34593
|
-
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
34594
|
-
if (dist <= this.config.rescueCentroidFactor * diag) return "centroid";
|
|
34936
|
+
if (Math.hypot(tc.x - dc.x, tc.y - dc.y) <= this.reacquireReach(track, timestamp, timeScaled)) return "centroid";
|
|
34595
34937
|
if (growthAdjacentMatch(track.bbox, det.bbox, SortTracker.growthContext(track, timestamp))) return "growth";
|
|
34596
34938
|
return null;
|
|
34597
34939
|
}
|
|
34598
34940
|
/**
|
|
34941
|
+
* The proximity budget the `centroid` clause is measured against.
|
|
34942
|
+
*
|
|
34943
|
+
* On the RESCUE pass (`timeScaled`) it is {@link reacquireReachPx} — a
|
|
34944
|
+
* multiple of the track's diagonal that grows with the elapsed gap and stops
|
|
34945
|
+
* at a ceiling, because a live coasting track's whereabouts genuinely become
|
|
34946
|
+
* less certain the longer it has gone unseen (D399).
|
|
34947
|
+
*
|
|
34948
|
+
* On the RESURRECTION pass it stays the flat `rescueCentroidFactor × diagonal`
|
|
34949
|
+
* it always was. That pass re-attaches a CORPSE, and it exists for one thing:
|
|
34950
|
+
* "a flickering stationary object keeps its identity". Elapsed time is not the
|
|
34951
|
+
* variable there — a stationary subject does not travel further because it
|
|
34952
|
+
* blinked for longer — and the corpse is strictly weaker evidence than a live
|
|
34953
|
+
* track, which is the same reasoning `resurrectionCrowdedIouThreshold` already
|
|
34954
|
+
* encodes.
|
|
34955
|
+
*/
|
|
34956
|
+
reacquireReach(track, timestamp, timeScaled) {
|
|
34957
|
+
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
34958
|
+
if (!timeScaled || !this.config.reacquireTimeScaled) return this.config.rescueCentroidFactor * diag;
|
|
34959
|
+
return reacquireReachPx(track.bbox, timestamp - track.lastSeen);
|
|
34960
|
+
}
|
|
34961
|
+
/**
|
|
34962
|
+
* Whether a REACH bind would be unambiguous: this coasting track is the only
|
|
34963
|
+
* subject of its association group the scene holds, and `det` is the only box
|
|
34964
|
+
* of that group on the frame.
|
|
34965
|
+
*
|
|
34966
|
+
* This is what lets a reach bind survive {@link isPlausibleBirth} (D399). That
|
|
34967
|
+
* veto exists because "a spare track costs media; a stolen track corrupts the
|
|
34968
|
+
* plate, the travelled distance and the last frame of the track it was taken
|
|
34969
|
+
* from" — every word of which is about a scene with something to steal FROM.
|
|
34970
|
+
* With one track and one box there is no second subject the id could be wrong
|
|
34971
|
+
* about, and refusing the bind does not buy safety, it buys a fragment.
|
|
34972
|
+
*
|
|
34973
|
+
* Deliberately computed over the whole frame rather than over what is still
|
|
34974
|
+
* unclaimed, so the answer is a fact about the scene and reads the same at
|
|
34975
|
+
* bind time and at report time — the spawn-beside-coasting line has to be able
|
|
34976
|
+
* to say which gate declined without re-running the greedy competition.
|
|
34977
|
+
*/
|
|
34978
|
+
isUnambiguousReach(track, det, detections) {
|
|
34979
|
+
if (this.sameGroupPeerCount(track, this.tracks) > 0) return false;
|
|
34980
|
+
const group = this.assocGroup(track.class);
|
|
34981
|
+
for (const other of detections) {
|
|
34982
|
+
if (other === det) continue;
|
|
34983
|
+
if (this.assocGroup(other.class) === group) return false;
|
|
34984
|
+
}
|
|
34985
|
+
return true;
|
|
34986
|
+
}
|
|
34987
|
+
/**
|
|
34599
34988
|
* Whether `det` would mint a track of its own if no salvage branch claimed it:
|
|
34600
34989
|
* it clears BOTH spawn floors (per-class score, per-class bbox-area fraction).
|
|
34601
34990
|
*
|
|
@@ -34663,11 +35052,11 @@ var SortTracker = class SortTracker {
|
|
|
34663
35052
|
* gates) so the two-tier ByteTrack association stays a single implementation.
|
|
34664
35053
|
* Mutates `used` / `matchedTracks` / `matched` in place.
|
|
34665
35054
|
*/
|
|
34666
|
-
greedyAssociate(detections, candidateIdxs, iouThreshold, used, matchedTracks, matched) {
|
|
35055
|
+
greedyAssociate(detections, candidateIdxs, iouThreshold, timestamp, used, matchedTracks, matched) {
|
|
34667
35056
|
const pairs = [];
|
|
34668
35057
|
for (const track of this.tracks) {
|
|
34669
35058
|
if (matchedTracks.has(track)) continue;
|
|
34670
|
-
const pbox = this.predicted(track);
|
|
35059
|
+
const pbox = this.predicted(track, timestamp);
|
|
34671
35060
|
for (const di of candidateIdxs) {
|
|
34672
35061
|
if (used.has(di)) continue;
|
|
34673
35062
|
const det = detections[di];
|
|
@@ -34726,7 +35115,7 @@ var SortTracker = class SortTracker {
|
|
|
34726
35115
|
for (let di = 0; di < detections.length; di++) if (this.config.byteTrackEnabled && detections[di].score < this.config.byteTrackHighThreshold) lowIdxs.push(di);
|
|
34727
35116
|
else highIdxs.push(di);
|
|
34728
35117
|
const lowSet = new Set(lowIdxs);
|
|
34729
|
-
this.greedyAssociate(detections, highIdxs, this.config.iouThreshold, used, matchedTracks, matched);
|
|
35118
|
+
this.greedyAssociate(detections, highIdxs, this.config.iouThreshold, timestamp, used, matchedTracks, matched);
|
|
34730
35119
|
const rescuePairs = [];
|
|
34731
35120
|
for (const track of this.tracks) {
|
|
34732
35121
|
if (matchedTracks.has(track)) continue;
|
|
@@ -34735,7 +35124,7 @@ var SortTracker = class SortTracker {
|
|
|
34735
35124
|
const det = detections[di];
|
|
34736
35125
|
const gate = this.looseMatch(track, det, timestamp);
|
|
34737
35126
|
if (gate === null) continue;
|
|
34738
|
-
if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
|
|
35127
|
+
if (gate === "centroid" && this.isPlausibleBirth(det, frameArea) && !(this.config.reacquireTimeScaled && this.isUnambiguousReach(track, det, detections))) continue;
|
|
34739
35128
|
rescuePairs.push({
|
|
34740
35129
|
track,
|
|
34741
35130
|
detIdx: di,
|
|
@@ -34753,7 +35142,7 @@ var SortTracker = class SortTracker {
|
|
|
34753
35142
|
used.add(pair.detIdx);
|
|
34754
35143
|
this.reportBind("rescue", pair.gate, pair.track, det, timestamp);
|
|
34755
35144
|
}
|
|
34756
|
-
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, used, matchedTracks, matched);
|
|
35145
|
+
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, timestamp, used, matchedTracks, matched);
|
|
34757
35146
|
for (const [track, det] of matched) {
|
|
34758
35147
|
if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, { against: track });
|
|
34759
35148
|
const prevCenter = bboxCentroid({
|
|
@@ -34769,6 +35158,7 @@ var SortTracker = class SortTracker {
|
|
|
34769
35158
|
h: det.bbox.h
|
|
34770
35159
|
});
|
|
34771
35160
|
const frames = track.age + 1;
|
|
35161
|
+
const dtMs = timestamp - track.lastSeen;
|
|
34772
35162
|
track.bbox = det.bbox;
|
|
34773
35163
|
track.class = det.class;
|
|
34774
35164
|
track.originalClass = det.originalClass;
|
|
@@ -34776,10 +35166,13 @@ var SortTracker = class SortTracker {
|
|
|
34776
35166
|
track.age = 0;
|
|
34777
35167
|
track.hits++;
|
|
34778
35168
|
track.lastSeen = timestamp;
|
|
34779
|
-
|
|
34780
|
-
|
|
34781
|
-
|
|
34782
|
-
|
|
35169
|
+
if (dtMs > 0) {
|
|
35170
|
+
track.velocity = {
|
|
35171
|
+
dx: (newCenter.x - prevCenter.x) / dtMs,
|
|
35172
|
+
dy: (newCenter.y - prevCenter.y) / dtMs
|
|
35173
|
+
};
|
|
35174
|
+
track.emitIntervalMs = dtMs / frames;
|
|
35175
|
+
}
|
|
34783
35176
|
track.path.push(det.bbox);
|
|
34784
35177
|
if (track.path.length > MAX_PATH_LENGTH) track.path.shift();
|
|
34785
35178
|
this.addClassVote(track, det);
|
|
@@ -34795,7 +35188,7 @@ var SortTracker = class SortTracker {
|
|
|
34795
35188
|
surviving.push(track);
|
|
34796
35189
|
continue;
|
|
34797
35190
|
}
|
|
34798
|
-
const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track), ob.bbox) >= this.config.occlusionContainment);
|
|
35191
|
+
const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track, timestamp), ob.bbox) >= this.config.occlusionContainment);
|
|
34799
35192
|
track.age++;
|
|
34800
35193
|
if (timestamp - track.lastSeen > (occluded ? this.config.occlusionMaxMissedMs : this.config.maxMissedMs)) {
|
|
34801
35194
|
track.lost = true;
|
|
@@ -34817,7 +35210,7 @@ var SortTracker = class SortTracker {
|
|
|
34817
35210
|
for (const lost of this.lostTracks) {
|
|
34818
35211
|
if (!lost.resurrectable) continue;
|
|
34819
35212
|
if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
|
|
34820
|
-
const gate = this.looseMatch(lost, det, timestamp);
|
|
35213
|
+
const gate = this.looseMatch(lost, det, timestamp, false);
|
|
34821
35214
|
if (gate === null) continue;
|
|
34822
35215
|
if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
|
|
34823
35216
|
const score = iou$3(lost.bbox, det.bbox);
|
|
@@ -34897,8 +35290,10 @@ var SortTracker = class SortTracker {
|
|
|
34897
35290
|
used.add(di);
|
|
34898
35291
|
continue;
|
|
34899
35292
|
}
|
|
35293
|
+
const spawnedId = (0, node_crypto.randomUUID)();
|
|
35294
|
+
this.reportSpawnBesideCoasting(spawnedId, det, surviving.filter((t) => t.lastSeen !== timestamp), detections, frameArea, timestamp);
|
|
34900
35295
|
surviving.push({
|
|
34901
|
-
id:
|
|
35296
|
+
id: spawnedId,
|
|
34902
35297
|
bbox: det.bbox,
|
|
34903
35298
|
class: det.class,
|
|
34904
35299
|
originalClass: det.originalClass,
|
|
@@ -34912,6 +35307,7 @@ var SortTracker = class SortTracker {
|
|
|
34912
35307
|
dx: 0,
|
|
34913
35308
|
dy: 0
|
|
34914
35309
|
},
|
|
35310
|
+
emitIntervalMs: 0,
|
|
34915
35311
|
lost: false,
|
|
34916
35312
|
lostAt: 0,
|
|
34917
35313
|
resurrectable: true,
|
|
@@ -34932,7 +35328,10 @@ var SortTracker = class SortTracker {
|
|
|
34932
35328
|
bbox: t.bbox,
|
|
34933
35329
|
trackId: t.id,
|
|
34934
35330
|
trackAge: t.hits,
|
|
34935
|
-
velocity:
|
|
35331
|
+
velocity: {
|
|
35332
|
+
dx: t.velocity.dx * t.emitIntervalMs,
|
|
35333
|
+
dy: t.velocity.dy * t.emitIntervalMs
|
|
35334
|
+
},
|
|
34936
35335
|
path: [...t.path],
|
|
34937
35336
|
matchedThisFrame: t.lastSeen === timestamp,
|
|
34938
35337
|
...scene !== void 0 ? { resurrectionScene: scene } : {}
|
|
@@ -34960,6 +35359,7 @@ var SortTracker = class SortTracker {
|
|
|
34960
35359
|
this.lostTracks = [];
|
|
34961
35360
|
this.dupStreaks.clear();
|
|
34962
35361
|
this.bindSampler.reset();
|
|
35362
|
+
this.spawnBesideSampler.reset();
|
|
34963
35363
|
}
|
|
34964
35364
|
};
|
|
34965
35365
|
//#endregion
|
|
@@ -35394,6 +35794,12 @@ var FrameProcessor = class {
|
|
|
35394
35794
|
setBindObserver(observer) {
|
|
35395
35795
|
this.tracker.setBindObserver(observer);
|
|
35396
35796
|
}
|
|
35797
|
+
/** Wire (or clear) the tracker's fragmentation sink — a new id minted beside a
|
|
35798
|
+
* live coasting track that could have claimed it (D399). Same `deviceId`
|
|
35799
|
+
* contract as the two sinks above. */
|
|
35800
|
+
setSpawnBesideCoastingObserver(observer) {
|
|
35801
|
+
this.tracker.setSpawnBesideCoastingObserver(observer);
|
|
35802
|
+
}
|
|
35397
35803
|
/** Forget a track in the underlying tracker (used when the addon promotes it
|
|
35398
35804
|
* to a stationary-object registry entry). */
|
|
35399
35805
|
dropTrack(trackId) {
|
|
@@ -35990,7 +36396,11 @@ function promoteSemanticBestFrame(deps, input) {
|
|
|
35990
36396
|
W: frameWidth,
|
|
35991
36397
|
H: frameHeight
|
|
35992
36398
|
}),
|
|
35993
|
-
semantic: true
|
|
36399
|
+
semantic: true,
|
|
36400
|
+
canvasFill: canvasFillFraction(bbox, {
|
|
36401
|
+
W: frameWidth,
|
|
36402
|
+
H: frameHeight
|
|
36403
|
+
})
|
|
35994
36404
|
})) {
|
|
35995
36405
|
deps.logger.debug("best-shot: semantic frame did not beat the held peak", {
|
|
35996
36406
|
tags: { deviceId },
|
|
@@ -36534,9 +36944,15 @@ function decideLastFramePromotion(media) {
|
|
|
36534
36944
|
snapshotKey: newestKeyFrameSmall.key,
|
|
36535
36945
|
snapshotTimestamp: newestKeyFrameSmall.timestamp
|
|
36536
36946
|
};
|
|
36537
|
-
return {
|
|
36947
|
+
return {
|
|
36948
|
+
promote: false,
|
|
36949
|
+
declinedBecause: lastFrame === void 0 ? "no-key-frame" : "no-source"
|
|
36950
|
+
};
|
|
36538
36951
|
}
|
|
36539
|
-
if (lastFrame !== void 0 && newestSnapshot.timestamp <= lastFrame.timestamp) return {
|
|
36952
|
+
if (lastFrame !== void 0 && newestSnapshot.timestamp <= lastFrame.timestamp) return {
|
|
36953
|
+
promote: false,
|
|
36954
|
+
declinedBecause: "already-last"
|
|
36955
|
+
};
|
|
36540
36956
|
return {
|
|
36541
36957
|
promote: true,
|
|
36542
36958
|
mode: "move",
|
|
@@ -36583,17 +36999,28 @@ function averageBboxDiagonal(boxes) {
|
|
|
36583
36999
|
//#endregion
|
|
36584
37000
|
//#region src/pipeline-analytics/pipeline/track-close.ts
|
|
36585
37001
|
/**
|
|
36586
|
-
* Compose the persisted `previewMissReason` (D276/B2). One
|
|
36587
|
-
* tokens: the derive's own miss branch (or what tile stands
|
|
36588
|
-
* capture-side failure
|
|
36589
|
-
*
|
|
37002
|
+
* Compose the persisted `previewMissReason` (D276/B2, extended by D400). One
|
|
37003
|
+
* line, greppable tokens: the derive's own miss branch (or what tile stands
|
|
37004
|
+
* in), then whether the NATIVE KEY FRAME landed, then the capture-side failure
|
|
37005
|
+
* with its count. Examples:
|
|
37006
|
+
* `no-key-frame no-key-frame-at-close capture=keyframe:native-missx4`
|
|
36590
37007
|
* `derive-returned-null tile=standin`
|
|
36591
|
-
* `no-key-frame
|
|
36592
|
-
|
|
36593
|
-
|
|
37008
|
+
* `native-tile tile=native no-key-frame-at-close`
|
|
37009
|
+
*
|
|
37010
|
+
* **Why the key-frame token (D400).** The reason used to be written ONLY when
|
|
37011
|
+
* the best-shot TILE ranked below native, so a track whose thumbnail landed
|
|
37012
|
+
* natively while its key frame missed carried no reason at all — three of the
|
|
37013
|
+
* four `MISSING_KEYFRAME` tracks in the 2026-09-07 audit were exactly that, and
|
|
37014
|
+
* the operator's "niente ultimo" had no cause anywhere on the row. The key
|
|
37015
|
+
* frame IS the native best-shot FRAME, and its absence is the head of the
|
|
37016
|
+
* cascade (`keyFrame ⇒ keyFrameSmall ⇒ the lastFrame copy source`), so it
|
|
37017
|
+
* belongs on this line whatever the tile ended up being.
|
|
37018
|
+
*/
|
|
37019
|
+
function composePreviewMissReason(tile, deriveMiss, captureMiss, keyFrameLanded = true) {
|
|
36594
37020
|
const parts = [];
|
|
36595
37021
|
parts.push(deriveMiss ?? (tile === "none" ? "no-thumbnail" : `${tile}-tile`));
|
|
36596
37022
|
if (tile !== "none") parts.push(`tile=${tile}`);
|
|
37023
|
+
if (!keyFrameLanded && deriveMiss !== "no-key-frame") parts.push("no-key-frame-at-close");
|
|
36597
37024
|
if (captureMiss !== void 0) parts.push(`capture=${captureMiss.reason}x${captureMiss.count}`);
|
|
36598
37025
|
return parts.join(" ");
|
|
36599
37026
|
}
|
|
@@ -36689,9 +37116,10 @@ var TrackCloser = class {
|
|
|
36689
37116
|
await this.commitRasterFallback(t, closure.rasterFallback.jpeg, closure.rasterFallback.timestamp);
|
|
36690
37117
|
tile = "raster";
|
|
36691
37118
|
}
|
|
36692
|
-
|
|
37119
|
+
const keyFrameLanded = ownedMedia.some((m) => m.kind === "keyFrame");
|
|
37120
|
+
if (tile !== "native" || !keyFrameLanded) {
|
|
36693
37121
|
const captureMiss = this.deps.previewCaptureMiss?.(t.trackId);
|
|
36694
|
-
const reason = composePreviewMissReason(tile, deriveMiss, captureMiss);
|
|
37122
|
+
const reason = composePreviewMissReason(tile, deriveMiss, captureMiss, keyFrameLanded);
|
|
36695
37123
|
if (this.deps.setPreviewMiss !== void 0) try {
|
|
36696
37124
|
await this.deps.setPreviewMiss(t.deviceId, t.trackId, reason);
|
|
36697
37125
|
} catch (err) {
|
|
@@ -36704,8 +37132,8 @@ var TrackCloser = class {
|
|
|
36704
37132
|
}
|
|
36705
37133
|
});
|
|
36706
37134
|
}
|
|
36707
|
-
const bestSeen = this.deps.residents.bestSeenBbox(t.trackId);
|
|
36708
|
-
this.deps.recordPendingPreviewUpgrade?.({
|
|
37135
|
+
const bestSeen = tile === "native" ? void 0 : this.deps.residents.bestSeenBbox(t.trackId);
|
|
37136
|
+
if (tile !== "native") this.deps.recordPendingPreviewUpgrade?.({
|
|
36709
37137
|
deviceId: t.deviceId,
|
|
36710
37138
|
trackId: t.trackId,
|
|
36711
37139
|
...bestSeen !== void 0 ? { bestSeen: {
|
|
@@ -36974,9 +37402,32 @@ var TrackCloser = class {
|
|
|
36974
37402
|
*/
|
|
36975
37403
|
async maybePromoteLastFrame(t, ownedMedia) {
|
|
36976
37404
|
const promotion = decideLastFramePromotion(ownedMedia);
|
|
36977
|
-
if (!promotion.promote)
|
|
37405
|
+
if (!promotion.promote) {
|
|
37406
|
+
if (promotion.declinedBecause !== "already-last") this.deps.logger.warn("no lastFrame at close", {
|
|
37407
|
+
tags: { deviceId: t.deviceId },
|
|
37408
|
+
meta: {
|
|
37409
|
+
trackId: t.trackId,
|
|
37410
|
+
declinedBecause: promotion.declinedBecause,
|
|
37411
|
+
hasKeyFrame: ownedMedia.some((m) => m.kind === "keyFrame"),
|
|
37412
|
+
hasKeyFrameSmall: ownedMedia.some((m) => m.kind === "keyFrameSmall"),
|
|
37413
|
+
snapshots: ownedMedia.filter((m) => m.kind === "snapshot").length
|
|
37414
|
+
}
|
|
37415
|
+
});
|
|
37416
|
+
return;
|
|
37417
|
+
}
|
|
36978
37418
|
const snapshot = ownedMedia.find((m) => m.key === promotion.snapshotKey);
|
|
36979
|
-
if (!snapshot)
|
|
37419
|
+
if (!snapshot) {
|
|
37420
|
+
this.deps.logger.warn("no lastFrame at close", {
|
|
37421
|
+
tags: { deviceId: t.deviceId },
|
|
37422
|
+
meta: {
|
|
37423
|
+
trackId: t.trackId,
|
|
37424
|
+
declinedBecause: "promotion-source-vanished",
|
|
37425
|
+
sourceKey: promotion.snapshotKey,
|
|
37426
|
+
mode: promotion.mode
|
|
37427
|
+
}
|
|
37428
|
+
});
|
|
37429
|
+
return;
|
|
37430
|
+
}
|
|
36980
37431
|
try {
|
|
36981
37432
|
await this.deps.mediaStore()?.promoteToLastFrame({
|
|
36982
37433
|
deviceId: t.deviceId,
|
|
@@ -37144,9 +37595,15 @@ var BestDetectionTracker = class {
|
|
|
37144
37595
|
* `best`. Absent = the caller does not measure it → contained tier
|
|
37145
37596
|
* (legacy behaviour). */
|
|
37146
37597
|
squareFraction = /* @__PURE__ */ new Map();
|
|
37598
|
+
/** Held peak's DELIVERED-CANVAS fill fraction (0..1), PARALLEL to `best`.
|
|
37599
|
+
* Absent = the caller does not measure it → neutral (no fill). */
|
|
37600
|
+
canvasFill = /* @__PURE__ */ new Map();
|
|
37147
37601
|
/** Held peak's SEMANTIC-tier bit (a recognition succeeded on that frame),
|
|
37148
37602
|
* PARALLEL to `best`. Absent = non-semantic (legacy behaviour). */
|
|
37149
37603
|
semantic = /* @__PURE__ */ new Map();
|
|
37604
|
+
/** The last comparison run for a track (D400 promotion log). Never read by
|
|
37605
|
+
* the ranking — a pure observation seam. */
|
|
37606
|
+
lastComparison = /* @__PURE__ */ new Map();
|
|
37150
37607
|
constructor(options = {}) {
|
|
37151
37608
|
this.hysteresis = options.hysteresis ?? 0;
|
|
37152
37609
|
this.minGapMs = options.minGapMs ?? 0;
|
|
@@ -37166,12 +37623,13 @@ var BestDetectionTracker = class {
|
|
|
37166
37623
|
* the classic policy holds: a confidence past the `hysteresis` margin that also
|
|
37167
37624
|
* respects `minGapMs` wins. On acceptance the held peak advances.
|
|
37168
37625
|
*/
|
|
37169
|
-
observe(trackId, confidence, timestamp, edgeClearIn, centerScoreIn, containmentIn, sizeScoreIn, squareFractionIn, semanticIn) {
|
|
37626
|
+
observe(trackId, confidence, timestamp, edgeClearIn, centerScoreIn, containmentIn, sizeScoreIn, squareFractionIn, semanticIn, canvasFillIn) {
|
|
37170
37627
|
const edgeClear = this.edgeAware ? edgeClearIn : void 0;
|
|
37171
37628
|
const centerScore = this.edgeAware ? centerScoreIn : void 0;
|
|
37172
37629
|
const containment = this.edgeAware ? containmentIn : void 0;
|
|
37173
37630
|
const sizeScore = this.sizeAware ? sizeScoreIn : void 0;
|
|
37174
37631
|
const squareFraction = this.edgeAware ? squareFractionIn : void 0;
|
|
37632
|
+
const canvasFill = this.edgeAware ? canvasFillIn : void 0;
|
|
37175
37633
|
const semanticBit = this.semanticAware ? semanticIn : void 0;
|
|
37176
37634
|
const cur = this.best.get(trackId);
|
|
37177
37635
|
if (cur === void 0) {
|
|
@@ -37184,6 +37642,7 @@ var BestDetectionTracker = class {
|
|
|
37184
37642
|
if (containment !== void 0) this.containment.set(trackId, containment);
|
|
37185
37643
|
if (sizeScore !== void 0) this.sizeScore.set(trackId, sizeScore);
|
|
37186
37644
|
if (squareFraction !== void 0) this.squareFraction.set(trackId, squareFraction);
|
|
37645
|
+
if (canvasFill !== void 0) this.canvasFill.set(trackId, canvasFill);
|
|
37187
37646
|
if (semanticBit !== void 0) this.semantic.set(trackId, semanticBit);
|
|
37188
37647
|
return true;
|
|
37189
37648
|
}
|
|
@@ -37193,6 +37652,7 @@ var BestDetectionTracker = class {
|
|
|
37193
37652
|
const curContainment = this.containment.get(trackId);
|
|
37194
37653
|
const curSize = this.sizeScore.get(trackId);
|
|
37195
37654
|
const curSquareFraction = this.squareFraction.get(trackId);
|
|
37655
|
+
const curCanvasFill = this.canvasFill.get(trackId);
|
|
37196
37656
|
const curSemantic = this.semantic.get(trackId) ?? false;
|
|
37197
37657
|
const candSemantic = semanticBit ?? false;
|
|
37198
37658
|
const held = {
|
|
@@ -37202,6 +37662,7 @@ var BestDetectionTracker = class {
|
|
|
37202
37662
|
...curContainment !== void 0 ? { containment: curContainment } : {},
|
|
37203
37663
|
...curSize !== void 0 ? { sizeScore: curSize } : {},
|
|
37204
37664
|
...curSquareFraction !== void 0 ? { squareFraction: curSquareFraction } : {},
|
|
37665
|
+
...curCanvasFill !== void 0 ? { canvasFill: curCanvasFill } : {},
|
|
37205
37666
|
semantic: curSemantic
|
|
37206
37667
|
};
|
|
37207
37668
|
const cand = {
|
|
@@ -37211,6 +37672,7 @@ var BestDetectionTracker = class {
|
|
|
37211
37672
|
...containment !== void 0 ? { containment } : {},
|
|
37212
37673
|
...sizeScore !== void 0 ? { sizeScore } : {},
|
|
37213
37674
|
...squareFraction !== void 0 ? { squareFraction } : {},
|
|
37675
|
+
...canvasFill !== void 0 ? { canvasFill } : {},
|
|
37214
37676
|
semantic: candSemantic
|
|
37215
37677
|
};
|
|
37216
37678
|
const squareTierUpgrade = isSquareContained(cand) && !isSquareContained(held);
|
|
@@ -37219,7 +37681,9 @@ var BestDetectionTracker = class {
|
|
|
37219
37681
|
const sameSemanticTier = isSemantic(cand) === isSemantic(held);
|
|
37220
37682
|
const tierUpgrade = squareTierUpgrade || semanticTierUpgrade || sameSquareTier && sameSemanticTier && candClear && !curClear;
|
|
37221
37683
|
const gapOk = timestamp - cur.atMs >= this.minGapMs || sizeScore !== void 0 && isDecisiveImprovement(held, cand);
|
|
37222
|
-
const
|
|
37684
|
+
const comparison = compareBestFrame(held, cand, this.hysteresis);
|
|
37685
|
+
this.lastComparison.set(trackId, comparison);
|
|
37686
|
+
const isNewBest = tierUpgrade ? true : comparison.accepted && gapOk;
|
|
37223
37687
|
if (isNewBest) {
|
|
37224
37688
|
this.best.set(trackId, {
|
|
37225
37689
|
confidence,
|
|
@@ -37230,11 +37694,46 @@ var BestDetectionTracker = class {
|
|
|
37230
37694
|
if (containment !== void 0) this.containment.set(trackId, containment);
|
|
37231
37695
|
if (sizeScore !== void 0) this.sizeScore.set(trackId, sizeScore);
|
|
37232
37696
|
if (squareFraction !== void 0) this.squareFraction.set(trackId, squareFraction);
|
|
37697
|
+
if (canvasFill !== void 0) this.canvasFill.set(trackId, canvasFill);
|
|
37233
37698
|
this.semantic.set(trackId, candSemantic);
|
|
37234
37699
|
}
|
|
37235
37700
|
return isNewBest;
|
|
37236
37701
|
}
|
|
37237
37702
|
/**
|
|
37703
|
+
* The FULL candidate shape of the track's held peak — everything the ranking
|
|
37704
|
+
* saw, not just `{confidence, atMs}`. Read BEFORE `observe` so a promotion
|
|
37705
|
+
* attempt can be logged against the peak it actually competed with (D400).
|
|
37706
|
+
* Undefined when the track holds no peak (the first sighting).
|
|
37707
|
+
*/
|
|
37708
|
+
heldCandidate(trackId) {
|
|
37709
|
+
const cur = this.best.get(trackId);
|
|
37710
|
+
if (cur === void 0) return void 0;
|
|
37711
|
+
const centerScore = this.centerScore.get(trackId);
|
|
37712
|
+
const containment = this.containment.get(trackId);
|
|
37713
|
+
const sizeScore = this.sizeScore.get(trackId);
|
|
37714
|
+
const squareFraction = this.squareFraction.get(trackId);
|
|
37715
|
+
const canvasFill = this.canvasFill.get(trackId);
|
|
37716
|
+
return {
|
|
37717
|
+
confidence: cur.confidence,
|
|
37718
|
+
edgeClear: this.edgeClear.get(trackId) ?? true,
|
|
37719
|
+
...centerScore !== void 0 ? { centerScore } : {},
|
|
37720
|
+
...containment !== void 0 ? { containment } : {},
|
|
37721
|
+
...sizeScore !== void 0 ? { sizeScore } : {},
|
|
37722
|
+
...squareFraction !== void 0 ? { squareFraction } : {},
|
|
37723
|
+
...canvasFill !== void 0 ? { canvasFill } : {},
|
|
37724
|
+
semantic: this.semantic.get(trackId) ?? false
|
|
37725
|
+
};
|
|
37726
|
+
}
|
|
37727
|
+
/**
|
|
37728
|
+
* The comparison the LAST {@link observe} ran for this track — which term
|
|
37729
|
+
* decided, and both effective scores. Undefined on a first sighting (nothing
|
|
37730
|
+
* was compared) and after {@link delete}. Feeds the promotion log line; it is
|
|
37731
|
+
* never a ranking input.
|
|
37732
|
+
*/
|
|
37733
|
+
lastComparisonFor(trackId) {
|
|
37734
|
+
return this.lastComparison.get(trackId);
|
|
37735
|
+
}
|
|
37736
|
+
/**
|
|
37238
37737
|
* Stamp the HELD peak as semantic — the attribution-time re-score for a
|
|
37239
37738
|
* recognition that arrived AFTER the frame was ranked (detail results are
|
|
37240
37739
|
* async). Use when the recognition's source frame IS the held peak: the
|
|
@@ -37260,7 +37759,9 @@ var BestDetectionTracker = class {
|
|
|
37260
37759
|
this.containment.delete(trackId);
|
|
37261
37760
|
this.sizeScore.delete(trackId);
|
|
37262
37761
|
this.squareFraction.delete(trackId);
|
|
37762
|
+
this.canvasFill.delete(trackId);
|
|
37263
37763
|
this.semantic.delete(trackId);
|
|
37764
|
+
this.lastComparison.delete(trackId);
|
|
37264
37765
|
}
|
|
37265
37766
|
clear() {
|
|
37266
37767
|
this.best.clear();
|
|
@@ -37269,7 +37770,9 @@ var BestDetectionTracker = class {
|
|
|
37269
37770
|
this.containment.clear();
|
|
37270
37771
|
this.sizeScore.clear();
|
|
37271
37772
|
this.squareFraction.clear();
|
|
37773
|
+
this.canvasFill.clear();
|
|
37272
37774
|
this.semantic.clear();
|
|
37775
|
+
this.lastComparison.clear();
|
|
37273
37776
|
}
|
|
37274
37777
|
};
|
|
37275
37778
|
//#endregion
|
|
@@ -37531,9 +38034,29 @@ var TrackResidentState = class {
|
|
|
37531
38034
|
}
|
|
37532
38035
|
/** Record a detection for the track's best-FRAME ranking (edge-aware §5
|
|
37533
38036
|
* policy). Returns true when it becomes the track's new best. */
|
|
37534
|
-
observeBestFrame(deviceId, trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, semantic) {
|
|
38037
|
+
observeBestFrame(deviceId, trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, semantic, canvasFill) {
|
|
37535
38038
|
this.open(deviceId, trackId);
|
|
37536
|
-
return this.bestFrameTracker.observe(trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, semantic);
|
|
38039
|
+
return this.bestFrameTracker.observe(trackId, confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, semantic, canvasFill);
|
|
38040
|
+
}
|
|
38041
|
+
/** D400: frame instant of the track's last logged promotion REFUSAL
|
|
38042
|
+
* (undefined = none yet). */
|
|
38043
|
+
bestShotRefusalLoggedAt(trackId) {
|
|
38044
|
+
return this.residents.get(trackId)?.bestShotRefusalLoggedAt;
|
|
38045
|
+
}
|
|
38046
|
+
/** Stamp the track's last promotion-refusal log instant. */
|
|
38047
|
+
noteBestShotRefusalLogged(deviceId, trackId, timestamp) {
|
|
38048
|
+
this.ensure(deviceId, trackId).bestShotRefusalLoggedAt = timestamp;
|
|
38049
|
+
}
|
|
38050
|
+
/** The FULL held best-frame candidate (everything the ranking saw). Read
|
|
38051
|
+
* BEFORE `observeBestFrame` to log a promotion attempt against the peak it
|
|
38052
|
+
* competed with (D400). */
|
|
38053
|
+
bestFrameHeld(trackId) {
|
|
38054
|
+
return this.bestFrameTracker.heldCandidate(trackId);
|
|
38055
|
+
}
|
|
38056
|
+
/** Which term decided the LAST best-frame comparison for the track (D400).
|
|
38057
|
+
* Undefined on a first sighting. */
|
|
38058
|
+
bestFrameLastComparison(trackId) {
|
|
38059
|
+
return this.bestFrameTracker.lastComparisonFor(trackId);
|
|
37537
38060
|
}
|
|
37538
38061
|
/** The held best-frame peak for a track (undefined if never observed). */
|
|
37539
38062
|
bestFramePeak(trackId) {
|
|
@@ -39727,6 +40250,24 @@ var TRACK_CLOSE_REASONS = ["stationary-promotion"];
|
|
|
39727
40250
|
function parseCloseReason(value) {
|
|
39728
40251
|
return TRACK_CLOSE_REASONS.find((reason) => reason === value);
|
|
39729
40252
|
}
|
|
40253
|
+
/**
|
|
40254
|
+
* `positions` in TIME order (D399).
|
|
40255
|
+
*
|
|
40256
|
+
* The array is APPENDED in arrival order and the two detector passes interleave,
|
|
40257
|
+
* so it is not the same thing: seven of the fourteen tracks the 2026-09-08 audit
|
|
40258
|
+
* read had at least one position whose timestamp preceded its predecessor's, and
|
|
40259
|
+
* a consumer that draws the array as a polyline draws a path crossing back on
|
|
40260
|
+
* itself. Everything that reasons about a first, a last, or a velocity was
|
|
40261
|
+
* reading them as stored.
|
|
40262
|
+
*
|
|
40263
|
+
* Immutable — returns a new array; `toSorted` is stable, so positions sharing a
|
|
40264
|
+
* timestamp keep their arrival order relative to each other. Applied at the READ
|
|
40265
|
+
* boundaries only: the write path keeps arrival order on purpose, because that
|
|
40266
|
+
* is a fact about delivery and `totalDistance` has always been accumulated on it.
|
|
40267
|
+
*/
|
|
40268
|
+
function positionsInTimeOrder(positions) {
|
|
40269
|
+
return positions.toSorted((a, b) => a.timestamp - b.timestamp);
|
|
40270
|
+
}
|
|
39730
40271
|
var DEFAULT_CONFIG = {
|
|
39731
40272
|
ttlMs: 3e4,
|
|
39732
40273
|
maxPositionHistory: 300,
|
|
@@ -40093,7 +40634,7 @@ function cloneTrack(t) {
|
|
|
40093
40634
|
subLabelMeta: t.subLabelMeta,
|
|
40094
40635
|
firstSeen: t.firstSeen,
|
|
40095
40636
|
lastSeen: t.lastSeen,
|
|
40096
|
-
positions: t.positions.map((p) => ({
|
|
40637
|
+
positions: positionsInTimeOrder(t.positions).map((p) => ({
|
|
40097
40638
|
...p,
|
|
40098
40639
|
bbox: { ...p.bbox }
|
|
40099
40640
|
})),
|
|
@@ -41686,7 +42227,7 @@ var TrackStore = class {
|
|
|
41686
42227
|
*/
|
|
41687
42228
|
const storedObservations = data["observations"];
|
|
41688
42229
|
const observations = typeof storedObservations === "number" ? storedObservations : slim ? void 0 : storedPositions.length;
|
|
41689
|
-
const positions = storedPositions;
|
|
42230
|
+
const positions = positionsInTimeOrder(storedPositions);
|
|
41690
42231
|
const snapshots = slim ? [] : data["snapshots"] ?? [];
|
|
41691
42232
|
const zones = data["zonesVisited"] ?? [];
|
|
41692
42233
|
const classes = data["classes"];
|
|
@@ -71447,7 +71988,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71447
71988
|
x: center.x,
|
|
71448
71989
|
y: center.y,
|
|
71449
71990
|
timestamp: result.timestamp,
|
|
71450
|
-
bbox: { ...t.bbox }
|
|
71991
|
+
bbox: { ...t.bbox },
|
|
71992
|
+
source: t.nativeRecovery !== void 0 ? "recovery" : "frame",
|
|
71993
|
+
score: t.confidence
|
|
71451
71994
|
},
|
|
71452
71995
|
zones: t.zones,
|
|
71453
71996
|
state: t.state
|
|
@@ -71940,6 +72483,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71940
72483
|
mediaKey: s.mediaKey
|
|
71941
72484
|
});
|
|
71942
72485
|
for (const trackId of res.thumbnailTrackIds) this.residents.markThumbnailLanded(deviceId, trackId);
|
|
72486
|
+
const landedThumbnails = new Set(res.thumbnailTrackIds);
|
|
72487
|
+
for (const target of snapshotTargets) {
|
|
72488
|
+
if (!target.bestThumbnail || landedThumbnails.has(target.trackId)) continue;
|
|
72489
|
+
log.warn("best shot capture did not land", {
|
|
72490
|
+
tags: { deviceId },
|
|
72491
|
+
meta: {
|
|
72492
|
+
trackId: target.trackId,
|
|
72493
|
+
timestamp: target.timestamp,
|
|
72494
|
+
keyFrameRequested: target.keyFrame !== "skip"
|
|
72495
|
+
}
|
|
72496
|
+
});
|
|
72497
|
+
}
|
|
71943
72498
|
for (const trackId of res.firstFrameTrackIds) {
|
|
71944
72499
|
this.residents.clearFirstFramePending(trackId);
|
|
71945
72500
|
this.residents.markFirstFrameLanded(deviceId, trackId);
|
|
@@ -72980,7 +73535,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72980
73535
|
const ctx = this.ctxIfReady;
|
|
72981
73536
|
if (ctx === null) return;
|
|
72982
73537
|
promoteSemanticBestFrame({
|
|
72983
|
-
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),
|
|
73538
|
+
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),
|
|
72984
73539
|
recordBestSeenBbox: (deviceId, trackId, bbox) => this.residents.recordBestSeenBbox(deviceId, trackId, bbox),
|
|
72985
73540
|
captureBestShot: (request) => this.captureSemanticBestShot(request),
|
|
72986
73541
|
logger: ctx.logger
|
|
@@ -73185,13 +73740,20 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73185
73740
|
W: frameWidth,
|
|
73186
73741
|
H: frameHeight
|
|
73187
73742
|
});
|
|
73743
|
+
const canvasFill = canvasFillFraction(t.bbox, {
|
|
73744
|
+
W: frameWidth,
|
|
73745
|
+
H: frameHeight
|
|
73746
|
+
});
|
|
73188
73747
|
const firstBox = this.trackStore?.peekActive(t.trackId)?.positions[0]?.bbox;
|
|
73189
|
-
const
|
|
73748
|
+
const skipParkedThumb = firstBox !== void 0 && thumbnailWouldShowParkedNotSubject({
|
|
73190
73749
|
currentBbox: t.bbox,
|
|
73191
73750
|
firstBbox: firstBox,
|
|
73192
73751
|
className: t.className,
|
|
73193
73752
|
entries: this.stationaryRegistry?.listViews(deviceId) ?? []
|
|
73194
|
-
})
|
|
73753
|
+
});
|
|
73754
|
+
const heldBest = this.residents.bestFrameHeld(t.trackId);
|
|
73755
|
+
const isNewBest = skipParkedThumb ? false : this.residents.observeBestFrame(deviceId, t.trackId, t.confidence, timestamp, edgeClear, centerScore, containment, sizeScore, squareFraction, false, canvasFill);
|
|
73756
|
+
const comparison = skipParkedThumb ? void 0 : this.residents.bestFrameLastComparison(t.trackId);
|
|
73195
73757
|
this.maybeEmitTrackLifecycleUpdate(deviceId, t, timestamp, isNewBest);
|
|
73196
73758
|
const plan = planPeriodicMedia({
|
|
73197
73759
|
debugMediaEnabled: media.debugMediaEnabled,
|
|
@@ -73224,6 +73786,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73224
73786
|
state: this.residents.keyFrameCapture(t.trackId),
|
|
73225
73787
|
now: timestamp
|
|
73226
73788
|
}).kind;
|
|
73789
|
+
const suppressedBy = (() => {
|
|
73790
|
+
if (skipParkedThumb) return "parked-thumbnail";
|
|
73791
|
+
if (!isNewBest || bestThumbnail) return void 0;
|
|
73792
|
+
if (!plan.bestThumbnail) return "not-planned";
|
|
73793
|
+
if (!plausibleBox) return "implausible-box";
|
|
73794
|
+
if (!retryBoxPlausible) return "retry-box-collapsed";
|
|
73795
|
+
return "capture-in-flight";
|
|
73796
|
+
})();
|
|
73797
|
+
const promotionAttempt = {
|
|
73798
|
+
trackId: t.trackId,
|
|
73799
|
+
className: t.className,
|
|
73800
|
+
timestamp,
|
|
73801
|
+
candidate: {
|
|
73802
|
+
confidence: t.confidence,
|
|
73803
|
+
edgeClear,
|
|
73804
|
+
centerScore,
|
|
73805
|
+
containment,
|
|
73806
|
+
sizeScore,
|
|
73807
|
+
squareFraction,
|
|
73808
|
+
canvasFill,
|
|
73809
|
+
semantic: false
|
|
73810
|
+
},
|
|
73811
|
+
...heldBest !== void 0 ? { held: heldBest } : {},
|
|
73812
|
+
...comparison !== void 0 ? { comparison } : {},
|
|
73813
|
+
accepted: isNewBest,
|
|
73814
|
+
...suppressedBy !== void 0 ? { suppressedBy } : {},
|
|
73815
|
+
thumbnailRequested: bestThumbnail,
|
|
73816
|
+
keyFrameRequested: keyFrame !== "skip"
|
|
73817
|
+
};
|
|
73818
|
+
const refusalLoggedAt = this.residents.bestShotRefusalLoggedAt(t.trackId);
|
|
73819
|
+
if (shouldLogPromotionAttempt(promotionAttempt, {
|
|
73820
|
+
thumbnailLanded: this.residents.isThumbnailLanded(t.trackId),
|
|
73821
|
+
...refusalLoggedAt !== void 0 ? { lastRefusalLogAtMs: refusalLoggedAt } : {}
|
|
73822
|
+
})) {
|
|
73823
|
+
if (!isNewBest) this.residents.noteBestShotRefusalLogged(deviceId, t.trackId, timestamp);
|
|
73824
|
+
this.ctx.logger.info("best shot promotion", {
|
|
73825
|
+
tags: { deviceId },
|
|
73826
|
+
meta: { ...bestShotPromotionMeta(promotionAttempt) }
|
|
73827
|
+
});
|
|
73828
|
+
}
|
|
73227
73829
|
if (!plan.appendSnapshot && !rollingLastFrame && !bestThumbnail && keyFrame === "skip") continue;
|
|
73228
73830
|
targets.push({
|
|
73229
73831
|
trackId: t.trackId,
|
|
@@ -74575,6 +75177,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
74575
75177
|
}
|
|
74576
75178
|
});
|
|
74577
75179
|
});
|
|
75180
|
+
p.setSpawnBesideCoastingObserver((record) => {
|
|
75181
|
+
this.ctx.logger.info("tracker: new id minted beside a live coasting track", {
|
|
75182
|
+
tags: { deviceId },
|
|
75183
|
+
meta: {
|
|
75184
|
+
source,
|
|
75185
|
+
...record
|
|
75186
|
+
}
|
|
75187
|
+
});
|
|
75188
|
+
});
|
|
74578
75189
|
p.setTrackLabelState((trackId) => {
|
|
74579
75190
|
const active = this.trackStore?.getActiveByTrack(trackId);
|
|
74580
75191
|
return active !== null && active !== void 0 ? {
|