@camstack/addon-post-analysis 1.2.192 → 1.2.194
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-CoI1xhsw.mjs → dist-DLVNGF8I.mjs} +52 -36
- package/dist/{dist-CJKdyCag.js → dist-DMnyp081.js} +52 -36
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Gc-d7G7c.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DAc8SUDf.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BK6J3Mhb.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CJb6-kIP.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-Bprfn5kJ.mjs → hostInit-B4lsosaS.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +410 -22
- package/dist/pipeline-analytics/index.mjs +410 -22
- 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-DMnyp081.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");
|
|
@@ -34017,6 +34017,18 @@ var LooseBindSampler = class {
|
|
|
34017
34017
|
this.counts.clear();
|
|
34018
34018
|
}
|
|
34019
34019
|
};
|
|
34020
|
+
/** Per-reason counter + sampling decision. One instance per tracker, so the
|
|
34021
|
+
* counts are per camera and per detection source. */
|
|
34022
|
+
var SpawnRefusalSampler = class {
|
|
34023
|
+
counts = /* @__PURE__ */ new Map();
|
|
34024
|
+
/** The running count when this refusal should be reported, else `undefined`. */
|
|
34025
|
+
next(reason) {
|
|
34026
|
+
const count = (this.counts.get(reason) ?? 0) + 1;
|
|
34027
|
+
this.counts.set(reason, count);
|
|
34028
|
+
if (count <= 10) return count;
|
|
34029
|
+
return count % 10 === 0 ? count : void 0;
|
|
34030
|
+
}
|
|
34031
|
+
};
|
|
34020
34032
|
//#endregion
|
|
34021
34033
|
//#region src/pipeline-analytics/pipeline/tracker/sort-tracker.ts
|
|
34022
34034
|
var DEFAULT_TRACKER_CONFIG = {
|
|
@@ -34101,6 +34113,10 @@ var SortTracker = class SortTracker {
|
|
|
34101
34113
|
/** Sink for salvage-bind diagnostics. Null until an owner that knows the
|
|
34102
34114
|
* `deviceId` wires one — see {@link setBindObserver}. */
|
|
34103
34115
|
bindObserver = null;
|
|
34116
|
+
/** Per-reason counters behind the sampled dropped-detection diagnostics. */
|
|
34117
|
+
refusalSampler = new SpawnRefusalSampler();
|
|
34118
|
+
/** Sink for dropped-detection diagnostics — see {@link setSpawnRefusalObserver}. */
|
|
34119
|
+
spawnRefusalObserver = null;
|
|
34104
34120
|
constructor(config = {}) {
|
|
34105
34121
|
this.config = {
|
|
34106
34122
|
...DEFAULT_TRACKER_CONFIG,
|
|
@@ -34116,6 +34132,37 @@ var SortTracker = class SortTracker {
|
|
|
34116
34132
|
setBindObserver(observer) {
|
|
34117
34133
|
this.bindObserver = observer;
|
|
34118
34134
|
}
|
|
34135
|
+
/**
|
|
34136
|
+
* Wire the diagnostics sink for detections the tracker DROPS — a spawn a
|
|
34137
|
+
* gate refused, or a box a track of another class absorbed. Same contract as
|
|
34138
|
+
* {@link setBindObserver}: the tracker has no `deviceId`, the caller stamps
|
|
34139
|
+
* it. Nothing is computed when no observer is wired.
|
|
34140
|
+
*/
|
|
34141
|
+
setSpawnRefusalObserver(observer) {
|
|
34142
|
+
this.spawnRefusalObserver = observer;
|
|
34143
|
+
}
|
|
34144
|
+
/** Emit one dropped-detection record, subject to the refusal sampler. */
|
|
34145
|
+
reportRefusal(reason, det, extra = {}) {
|
|
34146
|
+
const observer = this.spawnRefusalObserver;
|
|
34147
|
+
if (!observer) return;
|
|
34148
|
+
const refusalCount = this.refusalSampler.next(reason);
|
|
34149
|
+
if (refusalCount === void 0) return;
|
|
34150
|
+
observer({
|
|
34151
|
+
reason,
|
|
34152
|
+
detClass: det.class,
|
|
34153
|
+
detScore: det.score,
|
|
34154
|
+
bbox: det.bbox,
|
|
34155
|
+
...det.nativeRecovery !== void 0 ? { nativeRecoveryScore: det.nativeRecovery.score } : {},
|
|
34156
|
+
...extra.threshold !== void 0 ? { threshold: extra.threshold } : {},
|
|
34157
|
+
...extra.measured !== void 0 ? { measured: extra.measured } : {},
|
|
34158
|
+
...extra.against !== void 0 ? {
|
|
34159
|
+
againstTrackId: extra.against.id,
|
|
34160
|
+
againstTrackClass: extra.against.class
|
|
34161
|
+
} : {},
|
|
34162
|
+
refusalCount,
|
|
34163
|
+
sampledEvery: 10
|
|
34164
|
+
});
|
|
34165
|
+
}
|
|
34119
34166
|
/** Emit one salvage decision, subject to {@link LOOSE_BIND_LOG_SAMPLE_EVERY}.
|
|
34120
34167
|
* Nothing is computed when no observer is wired. */
|
|
34121
34168
|
reportBind(stage, gate, track, det, timestamp) {
|
|
@@ -34175,9 +34222,49 @@ var SortTracker = class SortTracker {
|
|
|
34175
34222
|
* exact class when `classGating` is off (no gate at all) — matching the legacy
|
|
34176
34223
|
* contract — otherwise same association group (group == exact class unless
|
|
34177
34224
|
* `classGroupAssoc` widened it to {person,animal}). */
|
|
34178
|
-
associable(trackClass,
|
|
34225
|
+
associable(trackClass, det) {
|
|
34226
|
+
if (!this.sameGroupAsMeasured(trackClass, det)) return false;
|
|
34179
34227
|
if (!this.config.classGating) return true;
|
|
34180
|
-
return this.assocGroup(trackClass) === this.assocGroup(
|
|
34228
|
+
return this.assocGroup(trackClass) === this.assocGroup(det.class);
|
|
34229
|
+
}
|
|
34230
|
+
/**
|
|
34231
|
+
* A NATIVE-MEASURED BOX IS ITS OWN SUBJECT ACROSS CLASSES (D378).
|
|
34232
|
+
*
|
|
34233
|
+
* `classGroupAssoc` collapses {person, animal} into one association group for
|
|
34234
|
+
* one failure of the COARSE full-frame pass: a bending person re-labelled
|
|
34235
|
+
* `animal` for a frame, over the person's own box. Letting that box re-match
|
|
34236
|
+
* the person track is strictly better than minting a concurrent id.
|
|
34237
|
+
*
|
|
34238
|
+
* The runner's second pass makes the opposite measurement — the same detector
|
|
34239
|
+
* on a 640-800 px native square around a motion region the full-frame boxes
|
|
34240
|
+
* do NOT account for. Such a box is, by construction, not the subject the
|
|
34241
|
+
* full pass already has; absorbing it into a person track means the animal is
|
|
34242
|
+
* never seen at all, which on 2026-09-07 07:51:33 is exactly what happened to
|
|
34243
|
+
* an `animal` at `nativeRecoveryScore=0.833`.
|
|
34244
|
+
*
|
|
34245
|
+
* So the widening does not apply to a detection carrying `nativeRecovery`:
|
|
34246
|
+
* it associates to its OWN class or it spawns. Every other guard on that
|
|
34247
|
+
* spawn still runs — the per-class score floor, the area floor, the
|
|
34248
|
+
* `dedupSpawnIou` overlap, and `resolveDuplicates` afterwards, where a person
|
|
34249
|
+
* still beats a cross-class animal.
|
|
34250
|
+
*/
|
|
34251
|
+
sameGroupAsMeasured(trackClass, det) {
|
|
34252
|
+
if (det.nativeRecovery === void 0) return true;
|
|
34253
|
+
return trackClass === det.class;
|
|
34254
|
+
}
|
|
34255
|
+
/**
|
|
34256
|
+
* Whether a matched track's box refuses `det` a birth. The gate exists for a
|
|
34257
|
+
* detector double-firing INSIDE a subject it already has, so it is a
|
|
34258
|
+
* same-subject claim: for a box the native pass measured (D378), a
|
|
34259
|
+
* cross-class occluder is not that claim — a dog at its owner's knee is 100%
|
|
34260
|
+
* contained in the person's box and is not the person. 2026-09-07 07:56:37,
|
|
34261
|
+
* an `animal` at `nativeRecoveryScore=0.5143` refused on `threshold=0.6`.
|
|
34262
|
+
* A SAME-class occluder still refuses it: that is the torso the crop cut out
|
|
34263
|
+
* of a person the full pass already has.
|
|
34264
|
+
*/
|
|
34265
|
+
occludesSpawn(det, occluder) {
|
|
34266
|
+
if (!this.sameGroupAsMeasured(occluder.class, det)) return false;
|
|
34267
|
+
return containment(det.bbox, occluder.bbox) >= this.config.occlusionContainment;
|
|
34181
34268
|
}
|
|
34182
34269
|
/** Whether a spawn candidate clears its per-class minimum bbox-area floor
|
|
34183
34270
|
* (fraction of frame area). Ungated when disabled, when the class has no floor,
|
|
@@ -34199,14 +34286,30 @@ var SortTracker = class SortTracker {
|
|
|
34199
34286
|
isDuplicateSpawn(det, live, timestamp) {
|
|
34200
34287
|
for (const t of live) {
|
|
34201
34288
|
if (!this.dedupCompatible(t.class, det.class)) continue;
|
|
34202
|
-
|
|
34289
|
+
const overlap = iou$3(det.bbox, t.bbox);
|
|
34290
|
+
if (overlap >= this.config.dedupSpawnIou) {
|
|
34291
|
+
this.reportRefusal("dedup-iou", det, {
|
|
34292
|
+
threshold: this.config.dedupSpawnIou,
|
|
34293
|
+
measured: overlap,
|
|
34294
|
+
against: t
|
|
34295
|
+
});
|
|
34296
|
+
return true;
|
|
34297
|
+
}
|
|
34203
34298
|
if (t.class === det.class && growthAdjacentMatch(t.bbox, det.bbox, SortTracker.growthContext(t, timestamp))) {
|
|
34204
34299
|
this.reportBind("spawn-dedup", "growth", t, det, timestamp);
|
|
34205
34300
|
return true;
|
|
34206
34301
|
}
|
|
34207
34302
|
}
|
|
34208
|
-
if (this.config.personAnimalDedup && det.class === ANIMAL_CLASS && det.score < this.config.animalOverPersonMaxScore) {
|
|
34209
|
-
|
|
34303
|
+
if (this.config.personAnimalDedup && det.class === ANIMAL_CLASS && det.nativeRecovery === void 0 && det.score < this.config.animalOverPersonMaxScore) for (const t of live) {
|
|
34304
|
+
const overlap = iou$3(det.bbox, t.bbox);
|
|
34305
|
+
if (t.class === PERSON_CLASS && overlap >= this.config.animalOverPersonIou) {
|
|
34306
|
+
this.reportRefusal("animal-over-person", det, {
|
|
34307
|
+
threshold: this.config.animalOverPersonMaxScore,
|
|
34308
|
+
measured: overlap,
|
|
34309
|
+
against: t
|
|
34310
|
+
});
|
|
34311
|
+
return true;
|
|
34312
|
+
}
|
|
34210
34313
|
}
|
|
34211
34314
|
return false;
|
|
34212
34315
|
}
|
|
@@ -34321,6 +34424,7 @@ var SortTracker = class SortTracker {
|
|
|
34321
34424
|
* class.
|
|
34322
34425
|
*/
|
|
34323
34426
|
looseMatch(track, det, timestamp) {
|
|
34427
|
+
if (!this.sameGroupAsMeasured(track.class, det)) return null;
|
|
34324
34428
|
if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return null;
|
|
34325
34429
|
if (iou$3(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
|
|
34326
34430
|
const tc = bboxCentroid(track.bbox);
|
|
@@ -34407,7 +34511,7 @@ var SortTracker = class SortTracker {
|
|
|
34407
34511
|
for (const di of candidateIdxs) {
|
|
34408
34512
|
if (used.has(di)) continue;
|
|
34409
34513
|
const det = detections[di];
|
|
34410
|
-
if (!this.associable(track.class, det
|
|
34514
|
+
if (!this.associable(track.class, det)) continue;
|
|
34411
34515
|
const score = iou$3(pbox, det.bbox);
|
|
34412
34516
|
if (score >= iouThreshold) pairs.push({
|
|
34413
34517
|
track,
|
|
@@ -34486,6 +34590,7 @@ var SortTracker = class SortTracker {
|
|
|
34486
34590
|
}
|
|
34487
34591
|
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, used, matchedTracks, matched);
|
|
34488
34592
|
for (const [track, det] of matched) {
|
|
34593
|
+
if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, { against: track });
|
|
34489
34594
|
const prevCenter = bboxCentroid({
|
|
34490
34595
|
x: track.bbox.x,
|
|
34491
34596
|
y: track.bbox.y,
|
|
@@ -34514,15 +34619,18 @@ var SortTracker = class SortTracker {
|
|
|
34514
34619
|
if (track.path.length > MAX_PATH_LENGTH) track.path.shift();
|
|
34515
34620
|
this.addClassVote(track, det);
|
|
34516
34621
|
}
|
|
34517
|
-
const
|
|
34518
|
-
for (const track of matchedTracks)
|
|
34622
|
+
const occluders = [];
|
|
34623
|
+
for (const track of matchedTracks) occluders.push({
|
|
34624
|
+
bbox: track.bbox,
|
|
34625
|
+
class: track.class
|
|
34626
|
+
});
|
|
34519
34627
|
const surviving = [];
|
|
34520
34628
|
for (const track of this.tracks) {
|
|
34521
34629
|
if (matchedTracks.has(track)) {
|
|
34522
34630
|
surviving.push(track);
|
|
34523
34631
|
continue;
|
|
34524
34632
|
}
|
|
34525
|
-
const occluded = this.config.occlusionEnabled &&
|
|
34633
|
+
const occluded = this.config.occlusionEnabled && occluders.some((ob) => containment(this.predicted(track), ob.bbox) >= this.config.occlusionContainment);
|
|
34526
34634
|
track.age++;
|
|
34527
34635
|
if (timestamp - track.lastSeen > (occluded ? this.config.occlusionMaxMissedMs : this.config.maxMissedMs)) {
|
|
34528
34636
|
track.lost = true;
|
|
@@ -34601,14 +34709,25 @@ var SortTracker = class SortTracker {
|
|
|
34601
34709
|
for (const di of unmatchedIdx) {
|
|
34602
34710
|
const det = detections[di];
|
|
34603
34711
|
if (!this.meetsClassScoreFloor(det)) {
|
|
34712
|
+
this.reportRefusal("score-floor", det, {
|
|
34713
|
+
threshold: this.config.classMinScores[det.class] ?? 0,
|
|
34714
|
+
measured: det.score
|
|
34715
|
+
});
|
|
34604
34716
|
used.add(di);
|
|
34605
34717
|
continue;
|
|
34606
34718
|
}
|
|
34607
34719
|
if (!this.meetsSpawnAreaFloor(det, frameArea)) {
|
|
34720
|
+
this.reportRefusal("area-floor", det, {
|
|
34721
|
+
threshold: this.config.classSpawnAreaFracs[det.class] ?? 0,
|
|
34722
|
+
measured: frameArea > 0 ? det.bbox.w * det.bbox.h / frameArea : 0
|
|
34723
|
+
});
|
|
34608
34724
|
used.add(di);
|
|
34609
34725
|
continue;
|
|
34610
34726
|
}
|
|
34611
|
-
if (this.config.occlusionEnabled &&
|
|
34727
|
+
if (this.config.occlusionEnabled && occluders.some((ob) => this.occludesSpawn(det, ob))) {
|
|
34728
|
+
this.reportRefusal("occlusion", det, { threshold: this.config.occlusionContainment });
|
|
34729
|
+
continue;
|
|
34730
|
+
}
|
|
34612
34731
|
if (this.config.dedupEnabled && this.isDuplicateSpawn(det, surviving, timestamp)) {
|
|
34613
34732
|
used.add(di);
|
|
34614
34733
|
continue;
|
|
@@ -35096,6 +35215,9 @@ var FrameProcessor = class {
|
|
|
35096
35215
|
* Only the rescue / resurrection / growth branches report, and they report
|
|
35097
35216
|
* sampled: stage 1 runs per track per frame at frame-rate.
|
|
35098
35217
|
*/
|
|
35218
|
+
setSpawnRefusalObserver(observer) {
|
|
35219
|
+
this.tracker.setSpawnRefusalObserver(observer);
|
|
35220
|
+
}
|
|
35099
35221
|
setBindObserver(observer) {
|
|
35100
35222
|
this.tracker.setBindObserver(observer);
|
|
35101
35223
|
}
|
|
@@ -35118,6 +35240,8 @@ var FrameProcessor = class {
|
|
|
35118
35240
|
const faceAlignedCropByBbox = /* @__PURE__ */ new Map();
|
|
35119
35241
|
const plateByBbox = /* @__PURE__ */ new Map();
|
|
35120
35242
|
const maskByBbox = /* @__PURE__ */ new Map();
|
|
35243
|
+
const nativeRecoveryByBbox = /* @__PURE__ */ new Map();
|
|
35244
|
+
const birthEvidenceByBbox = /* @__PURE__ */ new Map();
|
|
35121
35245
|
const flatDetections = frame.detections.filter((d) => d.kind === "first-level").map((det) => {
|
|
35122
35246
|
const bbox = {
|
|
35123
35247
|
x: det.bbox.x,
|
|
@@ -35129,8 +35253,11 @@ var FrameProcessor = class {
|
|
|
35129
35253
|
class: det.macroClass,
|
|
35130
35254
|
originalClass: det.debug?.originalClass ?? det.macroClass,
|
|
35131
35255
|
score: det.score,
|
|
35132
|
-
bbox
|
|
35256
|
+
bbox,
|
|
35257
|
+
...det.nativeRecovery !== void 0 ? { nativeRecovery: det.nativeRecovery } : {}
|
|
35133
35258
|
};
|
|
35259
|
+
if (det.nativeRecovery !== void 0) nativeRecoveryByBbox.set(bbox, det.nativeRecovery);
|
|
35260
|
+
if (det.birthEvidence !== void 0) birthEvidenceByBbox.set(bbox, det.birthEvidence);
|
|
35134
35261
|
if (det.labels.length > 0) labelsByBbox.set(bbox, det.labels);
|
|
35135
35262
|
if (det.debug?.originalClassTier !== void 0) originalClassTierByBbox.set(bbox, det.debug.originalClassTier);
|
|
35136
35263
|
if (det.embedding !== void 0) embeddingByBbox.set(bbox, {
|
|
@@ -35355,6 +35482,8 @@ var FrameProcessor = class {
|
|
|
35355
35482
|
const faceAlignedCrop = faceAlignedCropByBbox.get(td.bbox);
|
|
35356
35483
|
const plate = plateByBbox.get(td.bbox);
|
|
35357
35484
|
const sourceDetectionId = sourceIdByBbox.get(td.bbox);
|
|
35485
|
+
const nativeRecovery = td.matchedThisFrame !== false ? nativeRecoveryByBbox.get(td.bbox) : void 0;
|
|
35486
|
+
const birthEvidence = td.matchedThisFrame !== false ? birthEvidenceByBbox.get(td.bbox) : void 0;
|
|
35358
35487
|
return {
|
|
35359
35488
|
trackId: td.trackId,
|
|
35360
35489
|
...sourceDetectionId !== void 0 ? { sourceDetectionId } : {},
|
|
@@ -35377,7 +35506,9 @@ var FrameProcessor = class {
|
|
|
35377
35506
|
plateText: plate.text,
|
|
35378
35507
|
plateScore: plate.score,
|
|
35379
35508
|
plateBbox: plate.bbox
|
|
35380
|
-
} : {}
|
|
35509
|
+
} : {},
|
|
35510
|
+
...nativeRecovery !== void 0 ? { nativeRecovery } : {},
|
|
35511
|
+
...birthEvidence !== void 0 ? { birthEvidence } : {}
|
|
35381
35512
|
};
|
|
35382
35513
|
});
|
|
35383
35514
|
this.lastLabelWriteRefusals = refusals;
|
|
@@ -45228,9 +45359,28 @@ function withTimeout$2(promise, timeoutMs) {
|
|
|
45228
45359
|
});
|
|
45229
45360
|
}
|
|
45230
45361
|
async function runConfirmation(candidate, config, deps, exhausted) {
|
|
45362
|
+
const carried = candidate.evidenceCrop ?? null;
|
|
45363
|
+
if (carried) {
|
|
45364
|
+
const verdict = await measure(candidate, carried, config, deps, "carried");
|
|
45365
|
+
if (verdict.verdict === "confirmed" || verdict.reason === "redetect-error") return verdict;
|
|
45366
|
+
}
|
|
45231
45367
|
let crop = await deps.fetchCrop(candidate);
|
|
45232
|
-
|
|
45368
|
+
let cropSource = "native";
|
|
45369
|
+
if (!crop && exhausted && deps.fetchFallbackCrop) {
|
|
45370
|
+
crop = await deps.fetchFallbackCrop(candidate);
|
|
45371
|
+
cropSource = "fallback";
|
|
45372
|
+
}
|
|
45233
45373
|
if (!crop) return undecided(candidate, "no-crop");
|
|
45374
|
+
return measure(candidate, crop, config, deps, cropSource);
|
|
45375
|
+
}
|
|
45376
|
+
/**
|
|
45377
|
+
* Re-detect on ONE crop and turn the result into a verdict.
|
|
45378
|
+
*
|
|
45379
|
+
* Shared by all three sources so a crop can never be judged by two slightly
|
|
45380
|
+
* different rules depending on where it came from — the `cropSource` argument
|
|
45381
|
+
* is the ONLY thing that varies, and it varies in exactly one place below.
|
|
45382
|
+
*/
|
|
45383
|
+
async function measure(candidate, crop, config, deps, cropSource) {
|
|
45234
45384
|
const detections = await deps.redetect(crop);
|
|
45235
45385
|
if (detections === null) return undecided(candidate, "redetect-error");
|
|
45236
45386
|
let best;
|
|
@@ -45238,8 +45388,9 @@ async function runConfirmation(candidate, config, deps, exhausted) {
|
|
|
45238
45388
|
for (const d of detections) if (isConfirmationCompatible(candidate.className, d.macroClass)) {
|
|
45239
45389
|
if (!best || d.score > best.score) best = d;
|
|
45240
45390
|
} else if (!bestIncompatible || d.score > bestIncompatible.score) bestIncompatible = d;
|
|
45241
|
-
const bar =
|
|
45391
|
+
const bar = barFor(candidate, config, deps);
|
|
45242
45392
|
const confirmed = best !== void 0 && best.score >= bar;
|
|
45393
|
+
if (!confirmed && cropSource !== "native") return undecided(candidate, cropSource === "fallback" ? "fallback-inconclusive" : "carried-inconclusive");
|
|
45243
45394
|
const secondary = bestIncompatible ? promoteSecondary(candidate, bestIncompatible, bar, deps) : null;
|
|
45244
45395
|
return {
|
|
45245
45396
|
trackId: candidate.trackId,
|
|
@@ -45247,6 +45398,7 @@ async function runConfirmation(candidate, config, deps, exhausted) {
|
|
|
45247
45398
|
reason: confirmed ? "confirmed" : "suppressed",
|
|
45248
45399
|
className: candidate.className,
|
|
45249
45400
|
appliedMinConfidence: bar,
|
|
45401
|
+
cropSource,
|
|
45250
45402
|
...best ? { bestScore: best.score } : {},
|
|
45251
45403
|
...bestIncompatible ? {
|
|
45252
45404
|
bestIncompatibleClass: bestIncompatible.macroClass,
|
|
@@ -45277,7 +45429,27 @@ function promoteSecondary(candidate, detection, bar, deps) {
|
|
|
45277
45429
|
}
|
|
45278
45430
|
};
|
|
45279
45431
|
}
|
|
45432
|
+
/**
|
|
45433
|
+
* The bar this candidate faces. Never below the configured one: the
|
|
45434
|
+
* `minConfidenceFor` hook is monotonic by construction, and clamping here says
|
|
45435
|
+
* so at the point of use rather than trusting the caller to have been careful.
|
|
45436
|
+
*/
|
|
45437
|
+
function barFor(candidate, config, deps) {
|
|
45438
|
+
return Math.max(config.minConfidence, deps.minConfidenceFor?.(candidate) ?? 0);
|
|
45439
|
+
}
|
|
45280
45440
|
async function confirmOne(candidate, config, deps, exhausted) {
|
|
45441
|
+
const native = candidate.nativeRecovery;
|
|
45442
|
+
if (native !== void 0) {
|
|
45443
|
+
const bar = barFor(candidate, config, deps);
|
|
45444
|
+
if (native.score >= bar) return {
|
|
45445
|
+
trackId: candidate.trackId,
|
|
45446
|
+
verdict: "confirmed",
|
|
45447
|
+
reason: "native-pass",
|
|
45448
|
+
className: candidate.className,
|
|
45449
|
+
bestScore: native.score,
|
|
45450
|
+
appliedMinConfidence: bar
|
|
45451
|
+
};
|
|
45452
|
+
}
|
|
45281
45453
|
const cropPx = Math.max(candidate.bbox.w, candidate.bbox.h);
|
|
45282
45454
|
if (config.minCropPx > 0 && cropPx < config.minCropPx) return undecided(candidate, "below-min-crop");
|
|
45283
45455
|
try {
|
|
@@ -51739,6 +51911,97 @@ async function normalizePhotoOrientation(photo) {
|
|
|
51739
51911
|
if (orientation === void 0 || orientation === EXIF_UPRIGHT) return photo;
|
|
51740
51912
|
return (0, sharp.default)(photo).rotate().jpeg({ quality: 95 }).toBuffer();
|
|
51741
51913
|
}
|
|
51914
|
+
/**
|
|
51915
|
+
* How long one birth's evidence is kept. Twice `confirmationGateMaxDeferralMs`
|
|
51916
|
+
* (2 000 ms default) — the longest a deferred birth can still be re-offered.
|
|
51917
|
+
*/
|
|
51918
|
+
var BIRTH_EVIDENCE_TTL_MS = 4e3;
|
|
51919
|
+
var BirthEvidenceStore = class {
|
|
51920
|
+
slotsPerCamera;
|
|
51921
|
+
ttlMs;
|
|
51922
|
+
byDevice = /* @__PURE__ */ new Map();
|
|
51923
|
+
constructor(slotsPerCamera = 8, ttlMs = BIRTH_EVIDENCE_TTL_MS) {
|
|
51924
|
+
this.slotsPerCamera = slotsPerCamera;
|
|
51925
|
+
this.ttlMs = ttlMs;
|
|
51926
|
+
}
|
|
51927
|
+
/**
|
|
51928
|
+
* Keep this birth's crop.
|
|
51929
|
+
*
|
|
51930
|
+
* The FIRST crop for an id wins. A later frame's crop of the same track is a
|
|
51931
|
+
* different instant, and this store exists precisely to preserve the instant
|
|
51932
|
+
* the birth was measured on — overwriting it would quietly reintroduce the
|
|
51933
|
+
* defect the fallback asymmetry exists to distrust.
|
|
51934
|
+
*/
|
|
51935
|
+
remember(deviceKey, trackId, crop, nowMs) {
|
|
51936
|
+
let slots = this.byDevice.get(deviceKey);
|
|
51937
|
+
if (!slots) {
|
|
51938
|
+
slots = /* @__PURE__ */ new Map();
|
|
51939
|
+
this.byDevice.set(deviceKey, slots);
|
|
51940
|
+
}
|
|
51941
|
+
if (slots.has(trackId)) return;
|
|
51942
|
+
slots.set(trackId, {
|
|
51943
|
+
crop,
|
|
51944
|
+
atMs: nowMs
|
|
51945
|
+
});
|
|
51946
|
+
this.evict(deviceKey, slots, nowMs);
|
|
51947
|
+
}
|
|
51948
|
+
/** This birth's crop, or `null` when it was never kept or has aged out. */
|
|
51949
|
+
get(deviceKey, trackId, nowMs) {
|
|
51950
|
+
const entry = this.byDevice.get(deviceKey)?.get(trackId);
|
|
51951
|
+
if (!entry) return null;
|
|
51952
|
+
if (nowMs - entry.atMs > this.ttlMs) {
|
|
51953
|
+
this.resolve(deviceKey, trackId);
|
|
51954
|
+
return null;
|
|
51955
|
+
}
|
|
51956
|
+
return entry.crop;
|
|
51957
|
+
}
|
|
51958
|
+
/** A verdict finally arrived — the crop is dead weight from here. */
|
|
51959
|
+
resolve(deviceKey, trackId) {
|
|
51960
|
+
const slots = this.byDevice.get(deviceKey);
|
|
51961
|
+
if (!slots) return;
|
|
51962
|
+
slots.delete(trackId);
|
|
51963
|
+
if (slots.size === 0) this.byDevice.delete(deviceKey);
|
|
51964
|
+
}
|
|
51965
|
+
/**
|
|
51966
|
+
* Forget every id the tracker is no longer carrying — the same per-frame
|
|
51967
|
+
* contract as `DeferredBirthRegistry.retain`. An id the tracker dropped can
|
|
51968
|
+
* never be decided, so holding its pixels only leaks.
|
|
51969
|
+
*/
|
|
51970
|
+
retain(deviceKey, currentTrackIds) {
|
|
51971
|
+
const slots = this.byDevice.get(deviceKey);
|
|
51972
|
+
if (!slots) return;
|
|
51973
|
+
for (const id of [...slots.keys()]) if (!currentTrackIds.has(id)) slots.delete(id);
|
|
51974
|
+
if (slots.size === 0) this.byDevice.delete(deviceKey);
|
|
51975
|
+
}
|
|
51976
|
+
/** Crops held for one camera (diagnostics and tests). */
|
|
51977
|
+
size(deviceKey) {
|
|
51978
|
+
return this.byDevice.get(deviceKey)?.size ?? 0;
|
|
51979
|
+
}
|
|
51980
|
+
/** Bytes held across every camera — the declared budget, checkable. */
|
|
51981
|
+
totalBytes() {
|
|
51982
|
+
let total = 0;
|
|
51983
|
+
for (const slots of this.byDevice.values()) for (const entry of slots.values()) total += entry.crop.byteLength;
|
|
51984
|
+
return total;
|
|
51985
|
+
}
|
|
51986
|
+
/** Drop a camera's reserve wholesale (device removed / pipeline reset). */
|
|
51987
|
+
clearDevice(deviceKey) {
|
|
51988
|
+
this.byDevice.delete(deviceKey);
|
|
51989
|
+
}
|
|
51990
|
+
/**
|
|
51991
|
+
* Age first, then the quota — and only ever inside THIS camera's slots.
|
|
51992
|
+
* `Map` keeps insertion order and a crop is inserted once, so the first key
|
|
51993
|
+
* is always the oldest.
|
|
51994
|
+
*/
|
|
51995
|
+
evict(deviceKey, slots, nowMs) {
|
|
51996
|
+
for (const [id, entry] of [...slots]) if (nowMs - entry.atMs > this.ttlMs) slots.delete(id);
|
|
51997
|
+
while (slots.size > this.slotsPerCamera) {
|
|
51998
|
+
const oldest = slots.keys().next().value;
|
|
51999
|
+
if (oldest === void 0) break;
|
|
52000
|
+
slots.delete(oldest);
|
|
52001
|
+
}
|
|
52002
|
+
if (slots.size === 0) this.byDevice.delete(deviceKey);
|
|
52003
|
+
}
|
|
52004
|
+
};
|
|
51742
52005
|
//#endregion
|
|
51743
52006
|
//#region src/pipeline-analytics/pipeline/deferred-births.ts
|
|
51744
52007
|
var DeferredBirthRegistry = class {
|
|
@@ -53895,6 +54158,77 @@ function personOverVehicle(person, neighbours, threshold = CONTAINMENT_THRESHOLD
|
|
|
53895
54158
|
return best >= threshold ? best : null;
|
|
53896
54159
|
}
|
|
53897
54160
|
//#endregion
|
|
54161
|
+
//#region src/pipeline-analytics/pipeline/started-births.ts
|
|
54162
|
+
/**
|
|
54163
|
+
* Memory of births whose ritual has already RUN, per device+source.
|
|
54164
|
+
*
|
|
54165
|
+
* A birth is a decision, and a decision is taken once. Until 2026-09-07 it
|
|
54166
|
+
* could be taken twice, on camera 615 it routinely was, and the second vote
|
|
54167
|
+
* won:
|
|
54168
|
+
*
|
|
54169
|
+
* 04:59:38.335Z confirmation gate: birth confirmed {trackId=f585b2ee…
|
|
54170
|
+
* className=animal bestScore=0.7068}
|
|
54171
|
+
* 04:59:38.335Z track started {trackId=f585b2ee…}
|
|
54172
|
+
* 04:59:38.821Z confirmation gate: birth suppressed (false positive)
|
|
54173
|
+
* {trackId=f585b2ee… className=animal} ← no bestScore:
|
|
54174
|
+
* the second crop held no compatible detection AT ALL
|
|
54175
|
+
* 04:59:38.821Z birth suppressed — track record retracted
|
|
54176
|
+
*
|
|
54177
|
+
* TWO conditions have to meet, and both are structural:
|
|
54178
|
+
*
|
|
54179
|
+
* 1. `processFrame` reads `lastActiveTrackIds` at the top and writes it at the
|
|
54180
|
+
* bottom, and AWAITS the confirmation gate in between (a crop round trip to
|
|
54181
|
+
* the frame's node plus a re-detection: 130–490 ms measured on this camera,
|
|
54182
|
+
* against 100 ms frame spacing). Several frames are therefore in flight at
|
|
54183
|
+
* once and each of them sees the same fresh id as unjudged.
|
|
54184
|
+
* 2. The other birth discriminator, `positionsCount > 1`, does not close the
|
|
54185
|
+
* gap either: a COASTED frame deliberately appends no position
|
|
54186
|
+
* (`TrackStore.upsert`, `matched: false`), so a track the full-frame pass
|
|
54187
|
+
* cannot see — which is the entire premise of the small-subject recovery —
|
|
54188
|
+
* keeps exactly one position for as long as it coasts, and
|
|
54189
|
+
* `classifyTrackAppearance` keeps answering `birth`.
|
|
54190
|
+
*
|
|
54191
|
+
* So the id that has already been born is remembered HERE, and both halves of
|
|
54192
|
+
* the birth path consult it: it is not offered to the gate again, and a verdict
|
|
54193
|
+
* that arrives for it anyway cannot retract it. Same lifetime discipline as
|
|
54194
|
+
* {@link SuppressedBirthRegistry} — held while the tracker still carries the
|
|
54195
|
+
* id, forgotten the frame it stops.
|
|
54196
|
+
*/
|
|
54197
|
+
var StartedBirthRegistry = class {
|
|
54198
|
+
byDevice = /* @__PURE__ */ new Map();
|
|
54199
|
+
/** Record that this id's birth ritual ran — `start`, firstFrame, the lot. */
|
|
54200
|
+
markStarted(deviceKey, trackId) {
|
|
54201
|
+
let ids = this.byDevice.get(deviceKey);
|
|
54202
|
+
if (!ids) {
|
|
54203
|
+
ids = /* @__PURE__ */ new Set();
|
|
54204
|
+
this.byDevice.set(deviceKey, ids);
|
|
54205
|
+
}
|
|
54206
|
+
ids.add(trackId);
|
|
54207
|
+
}
|
|
54208
|
+
hasStarted(deviceKey, trackId) {
|
|
54209
|
+
return this.byDevice.get(deviceKey)?.has(trackId) === true;
|
|
54210
|
+
}
|
|
54211
|
+
/**
|
|
54212
|
+
* Forget every started id the tracker is no longer carrying. Called once per
|
|
54213
|
+
* frame with the ids present THIS frame — an id the tracker has dropped can
|
|
54214
|
+
* never be re-offered as a birth, so holding it serves nothing.
|
|
54215
|
+
*/
|
|
54216
|
+
retain(deviceKey, currentTrackIds) {
|
|
54217
|
+
const ids = this.byDevice.get(deviceKey);
|
|
54218
|
+
if (!ids) return;
|
|
54219
|
+
for (const id of ids) if (!currentTrackIds.has(id)) ids.delete(id);
|
|
54220
|
+
if (ids.size === 0) this.byDevice.delete(deviceKey);
|
|
54221
|
+
}
|
|
54222
|
+
/** Started ids currently held for a device — diagnostics and tests. */
|
|
54223
|
+
size(deviceKey) {
|
|
54224
|
+
return this.byDevice.get(deviceKey)?.size ?? 0;
|
|
54225
|
+
}
|
|
54226
|
+
/** Drop a device's memory wholesale (device removed / pipeline reset). */
|
|
54227
|
+
clearDevice(deviceKey) {
|
|
54228
|
+
this.byDevice.delete(deviceKey);
|
|
54229
|
+
}
|
|
54230
|
+
};
|
|
54231
|
+
//#endregion
|
|
53898
54232
|
//#region src/pipeline-analytics/pipeline/stationary/stationary-evidence.ts
|
|
53899
54233
|
/**
|
|
53900
54234
|
* stationary-evidence — how a parked object EARNS a registry entry.
|
|
@@ -67527,9 +67861,22 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
67527
67861
|
/** See `pipeline/suppressed-births.ts` — rejected births must not be
|
|
67528
67862
|
* re-upserted, and must be forgotten when the tracker drops the id. */
|
|
67529
67863
|
suppressedBirths = new SuppressedBirthRegistry();
|
|
67864
|
+
/** Ids whose birth ritual has already run — a birth is decided ONCE (D377). */
|
|
67865
|
+
startedBirths = new StartedBirthRegistry();
|
|
67530
67866
|
/** Births the gate could not MEASURE, awaiting another look on a later frame. */
|
|
67531
67867
|
deferredBirths = new DeferredBirthRegistry();
|
|
67532
67868
|
/**
|
|
67869
|
+
* The pixels each deferred birth arrived with — a RESERVE per camera, evicted
|
|
67870
|
+
* by age inside that camera's own quota (D379).
|
|
67871
|
+
*
|
|
67872
|
+
* Not a shared cap. The path this replaces was one node-wide 4 096-entry LRU
|
|
67873
|
+
* measured pinned full in 6 814 of 6 849 samples over three hours, in which a
|
|
67874
|
+
* busy camera evicted a quiet one's proof and every camera added shortened
|
|
67875
|
+
* every other camera's window. See `pipeline/birth-evidence-store.ts` for the
|
|
67876
|
+
* two measurements the slot count and the TTL are taken from.
|
|
67877
|
+
*/
|
|
67878
|
+
birthEvidence = new BirthEvidenceStore();
|
|
67879
|
+
/**
|
|
67533
67880
|
* Subjects the confirmation gate found in a birth's crop BESIDES the birth
|
|
67534
67881
|
* itself (a dog in a person's crop), waiting to ride the next frame of the
|
|
67535
67882
|
* same (device, source) as detections — `pipeline/secondary-subjects.ts`.
|
|
@@ -70745,10 +71092,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70745
71092
|
if (prevIds.has(id)) continue;
|
|
70746
71093
|
const t = result.tracked.find((x) => x.trackId === id);
|
|
70747
71094
|
if (!t) continue;
|
|
70748
|
-
|
|
71095
|
+
const appearance = classifyTrackAppearance({
|
|
70749
71096
|
inPrevActive: false,
|
|
70750
71097
|
positionsCount: positionsCountById.get(id) ?? 1
|
|
70751
|
-
})
|
|
71098
|
+
});
|
|
71099
|
+
if (this.startedBirths.hasStarted(key, id)) continue;
|
|
71100
|
+
if (appearance === "resurrection") {
|
|
70752
71101
|
log.info("track resumed", {
|
|
70753
71102
|
tags: { deviceId },
|
|
70754
71103
|
meta: {
|
|
@@ -70781,11 +71130,20 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70781
71130
|
const exhaustionNowMs = Date.now();
|
|
70782
71131
|
const exhaustedIds = /* @__PURE__ */ new Set();
|
|
70783
71132
|
for (const { id } of bornCandidates) if (this.deferredBirths.exhausted(key, id, exhaustionNowMs, gateSettings.maxAttempts, gateSettings.maxDeferralMs)) exhaustedIds.add(id);
|
|
70784
|
-
const
|
|
70785
|
-
|
|
70786
|
-
|
|
70787
|
-
|
|
70788
|
-
})
|
|
71133
|
+
for (const { id, t } of bornCandidates) {
|
|
71134
|
+
if (t.birthEvidence === void 0) continue;
|
|
71135
|
+
this.birthEvidence.remember(key, id, Buffer.from(t.birthEvidence.jpegBase64, "base64"), exhaustionNowMs);
|
|
71136
|
+
}
|
|
71137
|
+
const outcome = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => {
|
|
71138
|
+
const evidenceCrop = this.birthEvidence.get(key, id, exhaustionNowMs);
|
|
71139
|
+
return {
|
|
71140
|
+
trackId: id,
|
|
71141
|
+
className: t.className,
|
|
71142
|
+
bbox: t.bbox,
|
|
71143
|
+
...t.nativeRecovery !== void 0 ? { nativeRecovery: t.nativeRecovery } : {},
|
|
71144
|
+
...evidenceCrop !== null ? { evidenceCrop } : {}
|
|
71145
|
+
};
|
|
71146
|
+
}), {
|
|
70789
71147
|
deviceId,
|
|
70790
71148
|
source,
|
|
70791
71149
|
frameHandle,
|
|
@@ -70809,6 +71167,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70809
71167
|
const deferredForMs = this.deferredBirths.elapsedMs(key, id, gateNowMs);
|
|
70810
71168
|
const deferredAttempts = this.deferredBirths.attemptsFor(key, id);
|
|
70811
71169
|
this.deferredBirths.resolve(key, id);
|
|
71170
|
+
this.birthEvidence.resolve(key, id);
|
|
71171
|
+
if (this.startedBirths.hasStarted(key, id)) {
|
|
71172
|
+
this.deferredBirths.resolve(key, id);
|
|
71173
|
+
log.info("late birth verdict ignored — the track was already born", { meta: {
|
|
71174
|
+
trackId: id,
|
|
71175
|
+
className: t.className,
|
|
71176
|
+
source,
|
|
71177
|
+
verdict: outcome.confirmed.has(id) ? "confirmed" : "suppressed"
|
|
71178
|
+
} });
|
|
71179
|
+
continue;
|
|
71180
|
+
}
|
|
70812
71181
|
if (!outcome.confirmed.has(id)) {
|
|
70813
71182
|
this.suppressedBirths.reject(key, id);
|
|
70814
71183
|
this.trackStore?.dropActive(id);
|
|
@@ -70846,6 +71215,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70846
71215
|
})));
|
|
70847
71216
|
this.failureReport.notePersonBirth(deviceId, overlap === null ? void 0 : REASON_PERSON_OVER_VEHICLE);
|
|
70848
71217
|
}
|
|
71218
|
+
this.startedBirths.markStarted(key, id);
|
|
70849
71219
|
log.info("track started", { meta: {
|
|
70850
71220
|
trackId: id,
|
|
70851
71221
|
className: t.className,
|
|
@@ -70913,7 +71283,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70913
71283
|
});
|
|
70914
71284
|
}
|
|
70915
71285
|
this.suppressedBirths.retain(key, currentTrackIds);
|
|
71286
|
+
this.startedBirths.retain(key, currentTrackIds);
|
|
70916
71287
|
this.deferredBirths.retain(key, currentTrackIds);
|
|
71288
|
+
this.birthEvidence.retain(key, currentTrackIds);
|
|
70917
71289
|
this.lastActiveTrackIds.set(key, currentTrackIds);
|
|
70918
71290
|
if (currentTrackIds.size > 0) this.lastTrackActivityMs.set(deviceId, Date.now());
|
|
70919
71291
|
const stationarySettings = this.stationarySettingsFromCache(deviceId);
|
|
@@ -71492,6 +71864,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71492
71864
|
return allConfirmed();
|
|
71493
71865
|
}
|
|
71494
71866
|
const nodeId = frameHandle.nodeId;
|
|
71867
|
+
const candidateNativeRecovery = /* @__PURE__ */ new Map();
|
|
71868
|
+
for (const c of candidates) if (c.nativeRecovery !== void 0) candidateNativeRecovery.set(c.trackId, c.nativeRecovery);
|
|
71495
71869
|
return confirmBirths(candidates, config, {
|
|
71496
71870
|
fetchCrop: (candidate) => this.captureScheduler.request({
|
|
71497
71871
|
deviceId,
|
|
@@ -71541,6 +71915,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71541
71915
|
bestIncompatibleScore: decision.bestIncompatibleScore
|
|
71542
71916
|
} : {},
|
|
71543
71917
|
minConfidence: config.minConfidence,
|
|
71918
|
+
...decision.cropSource !== void 0 ? { cropSource: decision.cropSource } : {},
|
|
71919
|
+
...decision.reason === "native-pass" ? {
|
|
71920
|
+
nativeRecoveryCropSidePx: candidateNativeRecovery.get(decision.trackId)?.cropSidePx,
|
|
71921
|
+
nativeRecoveryViewPx: candidateNativeRecovery.get(decision.trackId)?.viewPx
|
|
71922
|
+
} : {},
|
|
71544
71923
|
...decision.appliedMinConfidence !== void 0 && decision.appliedMinConfidence > config.minConfidence ? {
|
|
71545
71924
|
appliedMinConfidence: decision.appliedMinConfidence,
|
|
71546
71925
|
barRaisedByPhantomCell: true
|
|
@@ -73802,6 +74181,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73802
74181
|
}
|
|
73803
74182
|
});
|
|
73804
74183
|
});
|
|
74184
|
+
p.setSpawnRefusalObserver((record) => {
|
|
74185
|
+
this.ctx.logger.info("tracker: detection dropped — no track was born from it", {
|
|
74186
|
+
tags: { deviceId },
|
|
74187
|
+
meta: {
|
|
74188
|
+
source,
|
|
74189
|
+
...record
|
|
74190
|
+
}
|
|
74191
|
+
});
|
|
74192
|
+
});
|
|
73805
74193
|
p.setTrackLabelState((trackId) => {
|
|
73806
74194
|
const active = this.trackStore?.getActiveByTrack(trackId);
|
|
73807
74195
|
return active !== null && active !== void 0 ? {
|