@camstack/addon-post-analysis 1.2.266 → 1.2.267
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-ZhDqWNZF.js → dist-Cgy2NW9x.js} +138 -2
- package/dist/{dist-CxMzr0uA.mjs → dist-DO_99QlZ.mjs} +138 -2
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Cpkxf2UJ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CJW84Bpj.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DtSv_IZa.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C5WB_ysd.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-BTMiHQhW.mjs → hostInit-B3uXhoOC.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +538 -38
- package/dist/pipeline-analytics/index.mjs +538 -38
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-uWlH4czX.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DFi0Tnyo.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as TimelapseRuleSchema, $t as array, A as NcConditionDescriptorSchema, At as readDeviceStateFrom, B as NcTaxonomySchema, Bt as zoneAnalyticsCapability, C as MOTION_CLOSE_AFTER_MS, Ct as mayWriteToLocation, D as NC_DEFAULT_SNOOZE_MINUTES, Dt as pickClusterStepModels, E as NC_CONDITION_CATALOG, F as NcScheduleSchema, Ft as storageOccupancyCapability, Gt as createEvent, Ht as BaseAddon, I as NcSnoozeInputSchema, It as subKindsOf, J as SCENE_DIVERGED, Jt as hydrateSchema, K as SCENE_DEFAULT_ANCHOR_THRESHOLD, Kt as customAction, L as NcSnoozeSchema, Lt as systemEventFilterApplies, M as NcRulePatchSchema, Mt as resolveLocationMode, N as NcRuleSchema, Ot as pipelineAnalyticsCapability, P as NcRuleTargetSchema, Pt as sceneMonitorCapability, Q as TimelapseRulePatchSchema, Qt as _enum, R as NcSnoozeSuppressedSchema, Rt as vectorDimFromBase64, St as kebabToCamel, T as NC_ALARM_SYSTEM_EVENT_KINDS, Tt as occupancyScope, U as RECORDING_EXPORT_MAX_READ_BYTES, Ut as CamProfileSchema, V as OpsLogEntrySchema, Vt as errMsg$1, W as RetrainStatusSchema, Wt as DeviceType, X as TIMELAPSE_DENSE_FLOOR_SEC, Xt as nodePin, Y as SceneMonitorSchema, Yt as isDeviceScopedCap, Z as TimelapseRuleInputSchema, Zt as sleep$1, _ as FULL_IMAGE_BBOX, _t as isDetectionMacroClass, a as CLUSTER_MODEL_SCOPED_STEPS, an as partialRecord, at as audioMetricsCapability, b as MAX_ARCHIVED_DEBUG_NOTES, bt as isScheduleActive, cn as unknown, ct as cosineSimilarity$1, dt as encodeVectorBase64, en as boolean, et as TrackSourceSchema, f as DeclaredDevices, ft as evaluateSensorEdge, g as FIRST_LEVEL_MACRO_CLASSES, h as EVENT_PAD_MS, ht as failureContributionCapability, i as BirthDecisionRecordSchema, in as object, it as assertTimelapseCadences, j as NcRuleInputSchema, jt as readTimelapseGeneratedAt, k as NC_TAXONOMY, kt as plateGalleryCapability, ln as EventCategory, lt as deriveRecordingMode, m as EVENT_OWNER_TYPES, mt as faceGalleryCapability, n as ArchivedDebugNoteSchema, nn as literal, nt as addonWidgetsSourceCapability, o as COCO_TO_MACRO, on as record, ot as audioModeOf, p as EVENT_KIND_BY_CAP, pt as evictionPolicyOfLocation, q as SCENE_DEFAULT_UNCOVERED_POLICY, qt as defineCustomActions, r as BaseDevice, rn as number, rt as alarmPanelCapability, s as DEFAULT_EVENT_COLOR, sn as string, st as buildEventKindDescriptor, t as AUDIO_MACRO_LABELS, tn as discriminatedUnion, tt as VISIT_MERGE_GAP_MS, u as DETECTION_MACRO_CLASSES, v as FailureCounters, vt as isEventOwnerType, w as MediaFileKindEnum, wt as notificationRulesCapability, x as MAX_BIRTH_DECISION_RECORDS, xt as isSourceCap, y as LabelAttributionSchema, yt as isMediaOwnerType, z as NcSystemEventKindSchema, zt as videoclipsCapability } from "../dist-
|
|
1
|
+
import { $ as TimelapseRuleSchema, $t as array, A as NcConditionDescriptorSchema, At as readDeviceStateFrom, B as NcTaxonomySchema, Bt as zoneAnalyticsCapability, C as MOTION_CLOSE_AFTER_MS, Ct as mayWriteToLocation, D as NC_DEFAULT_SNOOZE_MINUTES, Dt as pickClusterStepModels, E as NC_CONDITION_CATALOG, F as NcScheduleSchema, Ft as storageOccupancyCapability, Gt as createEvent, Ht as BaseAddon, I as NcSnoozeInputSchema, It as subKindsOf, J as SCENE_DIVERGED, Jt as hydrateSchema, K as SCENE_DEFAULT_ANCHOR_THRESHOLD, Kt as customAction, L as NcSnoozeSchema, Lt as systemEventFilterApplies, M as NcRulePatchSchema, Mt as resolveLocationMode, N as NcRuleSchema, Ot as pipelineAnalyticsCapability, P as NcRuleTargetSchema, Pt as sceneMonitorCapability, Q as TimelapseRulePatchSchema, Qt as _enum, R as NcSnoozeSuppressedSchema, Rt as vectorDimFromBase64, St as kebabToCamel, T as NC_ALARM_SYSTEM_EVENT_KINDS, Tt as occupancyScope, U as RECORDING_EXPORT_MAX_READ_BYTES, Ut as CamProfileSchema, V as OpsLogEntrySchema, Vt as errMsg$1, W as RetrainStatusSchema, Wt as DeviceType, X as TIMELAPSE_DENSE_FLOOR_SEC, Xt as nodePin, Y as SceneMonitorSchema, Yt as isDeviceScopedCap, Z as TimelapseRuleInputSchema, Zt as sleep$1, _ as FULL_IMAGE_BBOX, _t as isDetectionMacroClass, a as CLUSTER_MODEL_SCOPED_STEPS, an as partialRecord, at as audioMetricsCapability, b as MAX_ARCHIVED_DEBUG_NOTES, bt as isScheduleActive, cn as unknown, ct as cosineSimilarity$1, dt as encodeVectorBase64, en as boolean, et as TrackSourceSchema, f as DeclaredDevices, ft as evaluateSensorEdge, g as FIRST_LEVEL_MACRO_CLASSES, h as EVENT_PAD_MS, ht as failureContributionCapability, i as BirthDecisionRecordSchema, in as object, it as assertTimelapseCadences, j as NcRuleInputSchema, jt as readTimelapseGeneratedAt, k as NC_TAXONOMY, kt as plateGalleryCapability, ln as EventCategory, lt as deriveRecordingMode, m as EVENT_OWNER_TYPES, mt as faceGalleryCapability, n as ArchivedDebugNoteSchema, nn as literal, nt as addonWidgetsSourceCapability, o as COCO_TO_MACRO, on as record, ot as audioModeOf, p as EVENT_KIND_BY_CAP, pt as evictionPolicyOfLocation, q as SCENE_DEFAULT_UNCOVERED_POLICY, qt as defineCustomActions, r as BaseDevice, rn as number, rt as alarmPanelCapability, s as DEFAULT_EVENT_COLOR, sn as string, st as buildEventKindDescriptor, t as AUDIO_MACRO_LABELS, tn as discriminatedUnion, tt as VISIT_MERGE_GAP_MS, u as DETECTION_MACRO_CLASSES, v as FailureCounters, vt as isEventOwnerType, w as MediaFileKindEnum, wt as notificationRulesCapability, x as MAX_BIRTH_DECISION_RECORDS, xt as isSourceCap, y as LabelAttributionSchema, yt as isMediaOwnerType, z as NcSystemEventKindSchema, zt as videoclipsCapability } from "../dist-DO_99QlZ.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -37210,6 +37210,11 @@ var SortTracker = class SortTracker {
|
|
|
37210
37210
|
* three call sites reach and only one of them could have been handed it.
|
|
37211
37211
|
*/
|
|
37212
37212
|
frameArea = 0;
|
|
37213
|
+
/** The frame DIMS of the pass, for the same reason `frameArea` is held: the
|
|
37214
|
+
* salvage-bind record carries both boxes, and a box without the frame it was
|
|
37215
|
+
* measured in cannot be put back on a picture. `0` = no frame context. */
|
|
37216
|
+
frameWidthPx = 0;
|
|
37217
|
+
frameHeightPx = 0;
|
|
37213
37218
|
/** Sink for track deaths — see {@link setTrackLossObserver}. */
|
|
37214
37219
|
trackLossObserver = null;
|
|
37215
37220
|
trackLossSampler = new TrackLossSampler();
|
|
@@ -37447,6 +37452,20 @@ var SortTracker = class SortTracker {
|
|
|
37447
37452
|
centroidDistPx: Math.hypot(tc.x - dc.x, tc.y - dc.y),
|
|
37448
37453
|
trackDiagPx: Math.hypot(track.bbox.w, track.bbox.h),
|
|
37449
37454
|
areaRatio: smaller > 0 ? Math.max(trackArea, detArea) / smaller : 0,
|
|
37455
|
+
trackBbox: {
|
|
37456
|
+
x: track.bbox.x,
|
|
37457
|
+
y: track.bbox.y,
|
|
37458
|
+
w: track.bbox.w,
|
|
37459
|
+
h: track.bbox.h
|
|
37460
|
+
},
|
|
37461
|
+
detBbox: {
|
|
37462
|
+
x: det.bbox.x,
|
|
37463
|
+
y: det.bbox.y,
|
|
37464
|
+
w: det.bbox.w,
|
|
37465
|
+
h: det.bbox.h
|
|
37466
|
+
},
|
|
37467
|
+
frameWidth: this.frameWidthPx,
|
|
37468
|
+
frameHeight: this.frameHeightPx,
|
|
37450
37469
|
dtMs: timestamp - track.lastSeen,
|
|
37451
37470
|
trackAgeMs: timestamp - track.firstSeen,
|
|
37452
37471
|
bindCount,
|
|
@@ -37991,6 +38010,8 @@ var SortTracker = class SortTracker {
|
|
|
37991
38010
|
this.frameSeq++;
|
|
37992
38011
|
const frameArea = frameContext ? frameContext.frameWidth * frameContext.frameHeight : 0;
|
|
37993
38012
|
this.frameArea = frameArea;
|
|
38013
|
+
this.frameWidthPx = frameContext?.frameWidth ?? 0;
|
|
38014
|
+
this.frameHeightPx = frameContext?.frameHeight ?? 0;
|
|
37994
38015
|
const continuationOnly = frameContext?.continuationOnly ?? EMPTY_INDEX_SET;
|
|
37995
38016
|
const resurrectedScene = /* @__PURE__ */ new Map();
|
|
37996
38017
|
if (this.config.maxTrackLifetimeMs > 0) {
|
|
@@ -41235,6 +41256,17 @@ var TrackResidentState = class {
|
|
|
41235
41256
|
objectEmbeddingBestSelector = new TrackBestSelector(BEST_OBJECT_EMBED_POLICY);
|
|
41236
41257
|
/** keyFrame keys of CLOSED tracks still readable until the recognizer-end
|
|
41237
41258
|
* deferred drop settles (see module docstring). */
|
|
41259
|
+
/**
|
|
41260
|
+
* How many LIVE tracks carry the operator's `debug` flag, across every camera
|
|
41261
|
+
* this runner holds.
|
|
41262
|
+
*
|
|
41263
|
+
* A counter rather than a scan because the frame path asks it every frame:
|
|
41264
|
+
* it is what lets the host skip maintaining a per-frame handle mirror
|
|
41265
|
+
* entirely while nothing is marked, which is the state of the fleet
|
|
41266
|
+
* essentially always. Kept in step in the three places a resident's flag can
|
|
41267
|
+
* change: {@link setDebugMarked}, {@link close} and {@link clearAll}.
|
|
41268
|
+
*/
|
|
41269
|
+
debugMarkedCount = 0;
|
|
41238
41270
|
deferredKeyFrameKeys = /* @__PURE__ */ new Map();
|
|
41239
41271
|
constructor(hooks = {}) {
|
|
41240
41272
|
this.hooks = hooks;
|
|
@@ -41256,7 +41288,8 @@ var TrackResidentState = class {
|
|
|
41256
41288
|
thumbnailLanded: false,
|
|
41257
41289
|
keyFrameLanded: false,
|
|
41258
41290
|
keyFrameRetries: 0,
|
|
41259
|
-
confirmed: false
|
|
41291
|
+
confirmed: false,
|
|
41292
|
+
debugMarked: false
|
|
41260
41293
|
};
|
|
41261
41294
|
this.residents.set(trackId, created);
|
|
41262
41295
|
return created;
|
|
@@ -41270,6 +41303,7 @@ var TrackResidentState = class {
|
|
|
41270
41303
|
*/
|
|
41271
41304
|
close(trackId, mode) {
|
|
41272
41305
|
const resident = this.residents.get(trackId);
|
|
41306
|
+
if (resident?.debugMarked === true) this.debugMarkedCount -= 1;
|
|
41273
41307
|
this.residents.delete(trackId);
|
|
41274
41308
|
this.bestFrameTracker.delete(trackId);
|
|
41275
41309
|
this.objectEmbeddingBestSelector.delete(trackId);
|
|
@@ -41289,6 +41323,7 @@ var TrackResidentState = class {
|
|
|
41289
41323
|
}
|
|
41290
41324
|
/** Drop everything (addon dispose). */
|
|
41291
41325
|
clearAll() {
|
|
41326
|
+
this.debugMarkedCount = 0;
|
|
41292
41327
|
this.residents.clear();
|
|
41293
41328
|
this.bestFrameTracker.clear();
|
|
41294
41329
|
this.objectEmbeddingBestSelector.clear();
|
|
@@ -41480,6 +41515,42 @@ var TrackResidentState = class {
|
|
|
41480
41515
|
markConfirmed(deviceId, trackId) {
|
|
41481
41516
|
this.ensure(deviceId, trackId).confirmed = true;
|
|
41482
41517
|
}
|
|
41518
|
+
/**
|
|
41519
|
+
* Is this track the one the operator is asking about?
|
|
41520
|
+
*
|
|
41521
|
+
* THE gate for every debug crop, and it must stay this cheap: one map lookup,
|
|
41522
|
+
* no store read, no allocation. An unknown track is not marked — a lookup
|
|
41523
|
+
* that finds nothing is the common case on every camera in the fleet.
|
|
41524
|
+
*/
|
|
41525
|
+
isDebugMarked(trackId) {
|
|
41526
|
+
return this.residents.get(trackId)?.debugMarked ?? false;
|
|
41527
|
+
}
|
|
41528
|
+
/**
|
|
41529
|
+
* Record the operator's `debug` flag for a LIVE track.
|
|
41530
|
+
*
|
|
41531
|
+
* Called from the `setTrackFlags` cap mutation, which is the only thing that
|
|
41532
|
+
* can turn the flag on — so this runs once per press, not per frame.
|
|
41533
|
+
* `ensure` rather than a lookup: the button can be pressed on a track whose
|
|
41534
|
+
* first frame is still in flight, and a mark that silently missed would be
|
|
41535
|
+
* the operator clicking and getting nothing, which is the whole complaint.
|
|
41536
|
+
*/
|
|
41537
|
+
setDebugMarked(deviceId, trackId, marked) {
|
|
41538
|
+
const resident = this.ensure(deviceId, trackId);
|
|
41539
|
+
if (resident.debugMarked === marked) return;
|
|
41540
|
+
resident.debugMarked = marked;
|
|
41541
|
+
this.debugMarkedCount += marked ? 1 : -1;
|
|
41542
|
+
}
|
|
41543
|
+
/**
|
|
41544
|
+
* Does ANY live track on this runner carry the flag?
|
|
41545
|
+
*
|
|
41546
|
+
* The frame path's own gate, one level above {@link isDebugMarked}: it lets
|
|
41547
|
+
* the host skip the per-frame bookkeeping a debug crop needs (the frame
|
|
41548
|
+
* handle mirror the salvage-bind observer reads) while nothing is marked —
|
|
41549
|
+
* which is the fleet's normal state. A counter read, no scan, no allocation.
|
|
41550
|
+
*/
|
|
41551
|
+
anyDebugMarked() {
|
|
41552
|
+
return this.debugMarkedCount > 0;
|
|
41553
|
+
}
|
|
41483
41554
|
/** Retain the FIRST detection-raster crop per track (zero-media fallback);
|
|
41484
41555
|
* a later frame never overwrites an existing one. */
|
|
41485
41556
|
retainRasterFallback(deviceId, trackId, fallback) {
|
|
@@ -48691,6 +48762,298 @@ async function extractCropRaw(frameData, frameWidth, frameHeight, bbox) {
|
|
|
48691
48762
|
};
|
|
48692
48763
|
}
|
|
48693
48764
|
//#endregion
|
|
48765
|
+
//#region src/pipeline-analytics/pipeline/bbox-pad.ts
|
|
48766
|
+
/**
|
|
48767
|
+
* Pad a normalized bounding box by a fractional amount relative to its own
|
|
48768
|
+
* dimensions. All coordinates remain in the [0, 1] unit-square.
|
|
48769
|
+
*
|
|
48770
|
+
* Formula (before clamping):
|
|
48771
|
+
* newX = x - padding * w
|
|
48772
|
+
* newW = w + 2 * padding * w (i.e. w * (1 + 2 * padding))
|
|
48773
|
+
* newY = y - padding * h
|
|
48774
|
+
* newH = h + 2 * padding * h
|
|
48775
|
+
*
|
|
48776
|
+
* padding === 0 is the identity operation. Pure, immutable — always returns a
|
|
48777
|
+
* new object.
|
|
48778
|
+
*
|
|
48779
|
+
* NOTE: clamping SHIFTS, it does not truncate — a window that overflows a
|
|
48780
|
+
* bound slides inward and keeps the extent that was asked for, on either side.
|
|
48781
|
+
* It shrinks only when the padded window is larger than the frame itself
|
|
48782
|
+
* would (e.g. a left-hugging box gains a bit more right-side context). This is
|
|
48783
|
+
* acceptable for detection crops (more context, never out of [0,1]); the
|
|
48784
|
+
* downstream `extractCrop` clamps to pixel bounds again regardless.
|
|
48785
|
+
*/
|
|
48786
|
+
function padBbox(bbox, padding) {
|
|
48787
|
+
const rawX = bbox.x - padding * bbox.w;
|
|
48788
|
+
const rawY = bbox.y - padding * bbox.h;
|
|
48789
|
+
const rawW = bbox.w * (1 + 2 * padding);
|
|
48790
|
+
const rawH = bbox.h * (1 + 2 * padding);
|
|
48791
|
+
const w = Math.min(rawW, 1);
|
|
48792
|
+
const h = Math.min(rawH, 1);
|
|
48793
|
+
return {
|
|
48794
|
+
x: Math.min(Math.max(0, rawX), 1 - w),
|
|
48795
|
+
y: Math.min(Math.max(0, rawY), 1 - h),
|
|
48796
|
+
w,
|
|
48797
|
+
h
|
|
48798
|
+
};
|
|
48799
|
+
}
|
|
48800
|
+
/**
|
|
48801
|
+
* Legibility floor: the window's SHORT side is grown until it is at least this
|
|
48802
|
+
* fraction of the frame's short side.
|
|
48803
|
+
*
|
|
48804
|
+
* The 2026-09-18 traces included a 24x18 px subject. At the context ratio alone
|
|
48805
|
+
* that is a 72x54 window — and 72x54 source pixels are 72x54 delivered pixels,
|
|
48806
|
+
* because nothing in this repo may upscale. Growing the RECTANGLE is the only
|
|
48807
|
+
* honest way to make the picture bigger: more real pixels arrive, the subject
|
|
48808
|
+
* occupies less of the frame, and its drawn box says which part of it is the
|
|
48809
|
+
* subject. 0.18 of 1080 = 194 px, which renders a 24 px subject recognisably in
|
|
48810
|
+
* its surroundings.
|
|
48811
|
+
*/
|
|
48812
|
+
var DEBUG_CROP_MIN_SIDE_FRAC = .18;
|
|
48813
|
+
function unionBox(a, b) {
|
|
48814
|
+
const x = Math.min(a.x, b.x);
|
|
48815
|
+
const y = Math.min(a.y, b.y);
|
|
48816
|
+
const right = Math.max(a.x + a.w, b.x + b.w);
|
|
48817
|
+
const bottom = Math.max(a.y + a.h, b.y + b.h);
|
|
48818
|
+
return {
|
|
48819
|
+
x,
|
|
48820
|
+
y,
|
|
48821
|
+
w: right - x,
|
|
48822
|
+
h: bottom - y
|
|
48823
|
+
};
|
|
48824
|
+
}
|
|
48825
|
+
/**
|
|
48826
|
+
* Which borders a box touches, on `edge-clear.ts`'s tolerance band — the SAME
|
|
48827
|
+
* band `isEdgeClear` uses, so "clipped" here and "not edge-clear" there can
|
|
48828
|
+
* never disagree about one frame.
|
|
48829
|
+
*/
|
|
48830
|
+
function debugCropClipSides(bbox, frameWidth, frameHeight) {
|
|
48831
|
+
if (frameWidth <= 0 || frameHeight <= 0) return [];
|
|
48832
|
+
const tolX = DEFAULT_EDGE_TOLERANCE * frameWidth;
|
|
48833
|
+
const tolY = DEFAULT_EDGE_TOLERANCE * frameHeight;
|
|
48834
|
+
const sides = [];
|
|
48835
|
+
if (bbox.x <= tolX) sides.push("left");
|
|
48836
|
+
if (bbox.y <= tolY) sides.push("top");
|
|
48837
|
+
if (bbox.x + bbox.w >= frameWidth - tolX) sides.push("right");
|
|
48838
|
+
if (bbox.y + bbox.h >= frameHeight - tolY) sides.push("bottom");
|
|
48839
|
+
return sides;
|
|
48840
|
+
}
|
|
48841
|
+
/**
|
|
48842
|
+
* The ratio that makes `base` reach the legibility floor, or
|
|
48843
|
+
* {@link DEBUG_CROP_CONTEXT_RATIO} when it already does.
|
|
48844
|
+
*
|
|
48845
|
+
* Expressed as a PADDING RATIO rather than as a second rectangle so the window
|
|
48846
|
+
* still comes out of one {@link padBbox} call: there is exactly one place in
|
|
48847
|
+
* this file where a rectangle is grown, and it is not here.
|
|
48848
|
+
*/
|
|
48849
|
+
function contextRatioFor(base, frameWidth, frameHeight) {
|
|
48850
|
+
const minSide = DEBUG_CROP_MIN_SIDE_FRAC * Math.min(frameWidth, frameHeight);
|
|
48851
|
+
const ratioFor = (extent) => extent <= 0 ? 1 : (minSide / extent - 1) / 2;
|
|
48852
|
+
const needed = Math.max(ratioFor(base.w), ratioFor(base.h));
|
|
48853
|
+
return Math.max(1, needed);
|
|
48854
|
+
}
|
|
48855
|
+
/**
|
|
48856
|
+
* The one debug-crop rectangle. Pure.
|
|
48857
|
+
*
|
|
48858
|
+
* A refusal is a VALUE, not a `null`: the caller logs the reason, because a
|
|
48859
|
+
* branch that stores nothing and says nothing reads as "never happened".
|
|
48860
|
+
*/
|
|
48861
|
+
function planDebugCrop(input) {
|
|
48862
|
+
const { frameWidth, frameHeight, subject } = input;
|
|
48863
|
+
if (frameWidth <= 0 || frameHeight <= 0) return {
|
|
48864
|
+
kind: "refused",
|
|
48865
|
+
reason: "degenerate-frame"
|
|
48866
|
+
};
|
|
48867
|
+
if (subject.w <= 0 || subject.h <= 0) return {
|
|
48868
|
+
kind: "refused",
|
|
48869
|
+
reason: "degenerate-subject"
|
|
48870
|
+
};
|
|
48871
|
+
const base = input.rival === void 0 ? subject : unionBox(subject, input.rival);
|
|
48872
|
+
const contextRatio = contextRatioFor(base, frameWidth, frameHeight);
|
|
48873
|
+
const norm = padBbox({
|
|
48874
|
+
x: base.x / frameWidth,
|
|
48875
|
+
y: base.y / frameHeight,
|
|
48876
|
+
w: base.w / frameWidth,
|
|
48877
|
+
h: base.h / frameHeight
|
|
48878
|
+
}, contextRatio);
|
|
48879
|
+
const rect = {
|
|
48880
|
+
x: norm.x * frameWidth,
|
|
48881
|
+
y: norm.y * frameHeight,
|
|
48882
|
+
w: norm.w * frameWidth,
|
|
48883
|
+
h: norm.h * frameHeight
|
|
48884
|
+
};
|
|
48885
|
+
const relative = (role, box) => ({
|
|
48886
|
+
role,
|
|
48887
|
+
x: box.x - rect.x,
|
|
48888
|
+
y: box.y - rect.y,
|
|
48889
|
+
w: box.w,
|
|
48890
|
+
h: box.h
|
|
48891
|
+
});
|
|
48892
|
+
const boxes = input.rival === void 0 ? [relative("subject", subject)] : [relative("subject", subject), relative("rival", input.rival)];
|
|
48893
|
+
return {
|
|
48894
|
+
kind: "plan",
|
|
48895
|
+
decision: input.decision,
|
|
48896
|
+
rect,
|
|
48897
|
+
norm,
|
|
48898
|
+
boxes,
|
|
48899
|
+
contextRatio,
|
|
48900
|
+
floorApplied: contextRatio > 1,
|
|
48901
|
+
clippedSides: debugCropClipSides(subject, frameWidth, frameHeight),
|
|
48902
|
+
subjectCenterNorm: {
|
|
48903
|
+
x: (subject.x + subject.w / 2) / frameWidth,
|
|
48904
|
+
y: (subject.y + subject.h / 2) / frameHeight
|
|
48905
|
+
}
|
|
48906
|
+
};
|
|
48907
|
+
}
|
|
48908
|
+
function formatRect(rect) {
|
|
48909
|
+
return `${Math.round(rect.x)},${Math.round(rect.y)} ${Math.round(rect.w)}x${Math.round(rect.h)}`;
|
|
48910
|
+
}
|
|
48911
|
+
/**
|
|
48912
|
+
* The attribution bag: the deciding site's evidence FIRST, then the structural
|
|
48913
|
+
* facts — so no evidence key can rename the decision, the track or the
|
|
48914
|
+
* rectangle the crop belongs to. That ordering is the whole point: a crop whose
|
|
48915
|
+
* own label can be overwritten by a caller is back to being unattributable.
|
|
48916
|
+
*/
|
|
48917
|
+
function debugCropMeta(plan, attribution) {
|
|
48918
|
+
return {
|
|
48919
|
+
...attribution.evidence,
|
|
48920
|
+
decision: attribution.decision,
|
|
48921
|
+
trackId: attribution.trackId,
|
|
48922
|
+
...attribution.rivalTrackId === void 0 ? {} : { rivalTrackId: attribution.rivalTrackId },
|
|
48923
|
+
timestamp: attribution.timestamp,
|
|
48924
|
+
cropRect: formatRect(plan.rect),
|
|
48925
|
+
contextRatio: plan.contextRatio,
|
|
48926
|
+
floorApplied: plan.floorApplied,
|
|
48927
|
+
clippedSides: plan.clippedSides.join("|"),
|
|
48928
|
+
subjectCenter: `${plan.subjectCenterNorm.x.toFixed(3)},${plan.subjectCenterNorm.y.toFixed(3)}`
|
|
48929
|
+
};
|
|
48930
|
+
}
|
|
48931
|
+
/**
|
|
48932
|
+
* The caption burnt into the crop's own pixels.
|
|
48933
|
+
*
|
|
48934
|
+
* Burnt in, not stored beside it, because a debug crop travels: it is exported,
|
|
48935
|
+
* pasted into a note, and looked at months later next to five others. A blob
|
|
48936
|
+
* that cannot say what it is, on its own, is the thing that made the last
|
|
48937
|
+
* investigation slow — and this store has no column to say it in.
|
|
48938
|
+
*/
|
|
48939
|
+
function debugCropLabel(plan, attribution) {
|
|
48940
|
+
return `${`${attribution.decision} ${attribution.trackId}`}${attribution.rivalTrackId === void 0 ? "" : ` vs ${attribution.rivalTrackId}`}${plan.clippedSides.length === 0 ? "" : ` clipped:${plan.clippedSides.join("+")}`} ${Object.entries(attribution.evidence).map(([k, v]) => `${k}=${typeof v === "number" ? Math.round(v * 1e3) / 1e3 : String(v)}`).join(" ")}`.trim().slice(0, 160);
|
|
48941
|
+
}
|
|
48942
|
+
//#endregion
|
|
48943
|
+
//#region src/pipeline-analytics/pipeline/debug-crop-capture.ts
|
|
48944
|
+
/** Stroke colour of the box the decision was ABOUT. */
|
|
48945
|
+
var SUBJECT_COLOR = "#00e5ff";
|
|
48946
|
+
/** Stroke colour of the box it was weighed AGAINST — a different colour,
|
|
48947
|
+
* because "which one is which" is the first question a two-box crop raises. */
|
|
48948
|
+
var RIVAL_COLOR = "#ff9500";
|
|
48949
|
+
/** Encode quality: a debug crop is read by a person looking for a defect, so it
|
|
48950
|
+
* is encoded above the timeline-tile default (80) — a JPEG artefact that reads
|
|
48951
|
+
* as a mirror edge would waste the very investigation this exists for. */
|
|
48952
|
+
var DEBUG_CROP_QUALITY = 92;
|
|
48953
|
+
/** Bound the per-track counters so a long-lived process never leaks them. */
|
|
48954
|
+
var MAX_TRACKED_TRACKS = 2e3;
|
|
48955
|
+
function createDebugCropCapture(deps) {
|
|
48956
|
+
const storedPerTrack = /* @__PURE__ */ new Map();
|
|
48957
|
+
const cappedAnnounced = /* @__PURE__ */ new Set();
|
|
48958
|
+
const skip = (input, reason, extra) => {
|
|
48959
|
+
deps.logger.info("debug crop not stored", {
|
|
48960
|
+
tags: { deviceId: input.deviceId },
|
|
48961
|
+
meta: {
|
|
48962
|
+
reason,
|
|
48963
|
+
decision: input.decision,
|
|
48964
|
+
trackId: input.trackId,
|
|
48965
|
+
timestamp: input.timestamp,
|
|
48966
|
+
...extra
|
|
48967
|
+
}
|
|
48968
|
+
});
|
|
48969
|
+
return null;
|
|
48970
|
+
};
|
|
48971
|
+
return async (input) => {
|
|
48972
|
+
if (!deps.isDebugMarked(input.trackId)) return null;
|
|
48973
|
+
const already = storedPerTrack.get(input.trackId) ?? 0;
|
|
48974
|
+
if (already >= 6) {
|
|
48975
|
+
if (cappedAnnounced.has(input.trackId)) return null;
|
|
48976
|
+
if (cappedAnnounced.size >= MAX_TRACKED_TRACKS) cappedAnnounced.clear();
|
|
48977
|
+
cappedAnnounced.add(input.trackId);
|
|
48978
|
+
return skip(input, "per-track-cap", {
|
|
48979
|
+
stored: already,
|
|
48980
|
+
cap: 6
|
|
48981
|
+
});
|
|
48982
|
+
}
|
|
48983
|
+
const plan = planDebugCrop({
|
|
48984
|
+
decision: input.decision,
|
|
48985
|
+
subject: input.subject,
|
|
48986
|
+
...input.rival === void 0 ? {} : { rival: input.rival },
|
|
48987
|
+
frameWidth: input.frameWidth,
|
|
48988
|
+
frameHeight: input.frameHeight
|
|
48989
|
+
});
|
|
48990
|
+
if (plan.kind === "refused") return skip(input, plan.reason, {});
|
|
48991
|
+
const attribution = {
|
|
48992
|
+
decision: input.decision,
|
|
48993
|
+
trackId: input.trackId,
|
|
48994
|
+
...input.rivalTrackId === void 0 ? {} : { rivalTrackId: input.rivalTrackId },
|
|
48995
|
+
timestamp: input.timestamp,
|
|
48996
|
+
evidence: input.evidence
|
|
48997
|
+
};
|
|
48998
|
+
const meta = debugCropMeta(plan, attribution);
|
|
48999
|
+
const frame = await deps.fetchFrameRgb(input.frameHandle, input.deviceId, input.trackId);
|
|
49000
|
+
if (frame === null || frame.width <= 0 || frame.height <= 0) return skip(input, "frame-unavailable", { nodeId: input.frameHandle.nodeId });
|
|
49001
|
+
let jpeg;
|
|
49002
|
+
try {
|
|
49003
|
+
const cut = await deps.extractCropRaw(frame.bytes, frame.width, frame.height, plan.norm);
|
|
49004
|
+
const scale = plan.rect.w > 0 ? cut.width / plan.rect.w : 1;
|
|
49005
|
+
const boxes = plan.boxes.map((b) => {
|
|
49006
|
+
const geometry = {
|
|
49007
|
+
x: b.x * scale,
|
|
49008
|
+
y: b.y * scale,
|
|
49009
|
+
w: b.w * scale,
|
|
49010
|
+
h: b.h * scale
|
|
49011
|
+
};
|
|
49012
|
+
return b.role === "subject" ? {
|
|
49013
|
+
...geometry,
|
|
49014
|
+
color: SUBJECT_COLOR,
|
|
49015
|
+
label: debugCropLabel(plan, attribution)
|
|
49016
|
+
} : {
|
|
49017
|
+
...geometry,
|
|
49018
|
+
color: RIVAL_COLOR,
|
|
49019
|
+
label: "rival"
|
|
49020
|
+
};
|
|
49021
|
+
});
|
|
49022
|
+
jpeg = await deps.drawBoxedFrame(cut.data, cut.width, cut.height, boxes, { quality: DEBUG_CROP_QUALITY });
|
|
49023
|
+
} catch (err) {
|
|
49024
|
+
return skip(input, "render-failed", { error: err instanceof Error ? err.message : String(err) });
|
|
49025
|
+
}
|
|
49026
|
+
try {
|
|
49027
|
+
const mediaId = await deps.putDebugCrop({
|
|
49028
|
+
deviceId: input.deviceId,
|
|
49029
|
+
trackId: input.trackId,
|
|
49030
|
+
timestamp: input.timestamp,
|
|
49031
|
+
data: jpeg
|
|
49032
|
+
});
|
|
49033
|
+
if (storedPerTrack.size >= MAX_TRACKED_TRACKS) storedPerTrack.clear();
|
|
49034
|
+
storedPerTrack.set(input.trackId, already + 1);
|
|
49035
|
+
deps.logger.info("debug crop stored", {
|
|
49036
|
+
tags: { deviceId: input.deviceId },
|
|
49037
|
+
meta: {
|
|
49038
|
+
...meta,
|
|
49039
|
+
mediaId,
|
|
49040
|
+
deviceId: input.deviceId
|
|
49041
|
+
}
|
|
49042
|
+
});
|
|
49043
|
+
return mediaId;
|
|
49044
|
+
} catch (err) {
|
|
49045
|
+
deps.logger.warn("debug crop store failed", {
|
|
49046
|
+
tags: { deviceId: input.deviceId },
|
|
49047
|
+
meta: {
|
|
49048
|
+
...meta,
|
|
49049
|
+
error: err instanceof Error ? err.message : String(err)
|
|
49050
|
+
}
|
|
49051
|
+
});
|
|
49052
|
+
return null;
|
|
49053
|
+
}
|
|
49054
|
+
};
|
|
49055
|
+
}
|
|
49056
|
+
//#endregion
|
|
48694
49057
|
//#region src/shared/frame/encode-crop.ts
|
|
48695
49058
|
/**
|
|
48696
49059
|
* JPEG-encode an already-cropped raw RGB (24-bit) buffer. Used for the
|
|
@@ -53482,42 +53845,6 @@ var AnalyticsFailureReport = class {
|
|
|
53482
53845
|
}
|
|
53483
53846
|
};
|
|
53484
53847
|
//#endregion
|
|
53485
|
-
//#region src/pipeline-analytics/pipeline/bbox-pad.ts
|
|
53486
|
-
/**
|
|
53487
|
-
* Pad a normalized bounding box by a fractional amount relative to its own
|
|
53488
|
-
* dimensions. All coordinates remain in the [0, 1] unit-square.
|
|
53489
|
-
*
|
|
53490
|
-
* Formula (before clamping):
|
|
53491
|
-
* newX = x - padding * w
|
|
53492
|
-
* newW = w + 2 * padding * w (i.e. w * (1 + 2 * padding))
|
|
53493
|
-
* newY = y - padding * h
|
|
53494
|
-
* newH = h + 2 * padding * h
|
|
53495
|
-
*
|
|
53496
|
-
* padding === 0 is the identity operation. Pure, immutable — always returns a
|
|
53497
|
-
* new object.
|
|
53498
|
-
*
|
|
53499
|
-
* NOTE: clamping SHIFTS, it does not truncate — a window that overflows a
|
|
53500
|
-
* bound slides inward and keeps the extent that was asked for, on either side.
|
|
53501
|
-
* It shrinks only when the padded window is larger than the frame itself
|
|
53502
|
-
* would (e.g. a left-hugging box gains a bit more right-side context). This is
|
|
53503
|
-
* acceptable for detection crops (more context, never out of [0,1]); the
|
|
53504
|
-
* downstream `extractCrop` clamps to pixel bounds again regardless.
|
|
53505
|
-
*/
|
|
53506
|
-
function padBbox(bbox, padding) {
|
|
53507
|
-
const rawX = bbox.x - padding * bbox.w;
|
|
53508
|
-
const rawY = bbox.y - padding * bbox.h;
|
|
53509
|
-
const rawW = bbox.w * (1 + 2 * padding);
|
|
53510
|
-
const rawH = bbox.h * (1 + 2 * padding);
|
|
53511
|
-
const w = Math.min(rawW, 1);
|
|
53512
|
-
const h = Math.min(rawH, 1);
|
|
53513
|
-
return {
|
|
53514
|
-
x: Math.min(Math.max(0, rawX), 1 - w),
|
|
53515
|
-
y: Math.min(Math.max(0, rawY), 1 - h),
|
|
53516
|
-
w,
|
|
53517
|
-
h
|
|
53518
|
-
};
|
|
53519
|
-
}
|
|
53520
|
-
//#endregion
|
|
53521
53848
|
//#region src/pipeline-analytics/pipeline/capture-crop.ts
|
|
53522
53849
|
/**
|
|
53523
53850
|
* The normalised window a capture asks the runner for: the box padded by
|
|
@@ -75910,6 +76237,19 @@ var customActions = {
|
|
|
75910
76237
|
...faceRescoreActions
|
|
75911
76238
|
};
|
|
75912
76239
|
/**
|
|
76240
|
+
* Width cap on the ONE full frame a debug crop is cut from.
|
|
76241
|
+
*
|
|
76242
|
+
* A debug crop is a picture for a person, and the plan has already widened the
|
|
76243
|
+
* window to a neighbourhood — past this width the extra pixels buy nothing and
|
|
76244
|
+
* a 4K native surface would cross the transport for every marked track.
|
|
76245
|
+
*/
|
|
76246
|
+
var DEBUG_CROP_FETCH_MAX_WIDTH = 1280;
|
|
76247
|
+
/** How long a queued debug-crop frame fetch is still worth executing. Past
|
|
76248
|
+
* this the handle's frame has aged out of the runner's retention and the next
|
|
76249
|
+
* decision will ask again with a young one, so the queued entry is dead
|
|
76250
|
+
* weight in a lane real media is waiting in. */
|
|
76251
|
+
var DEBUG_CROP_FETCH_STALE_MS = 2e3;
|
|
76252
|
+
/**
|
|
75913
76253
|
* Assist threshold when the caller does not pin one. Packages have their own
|
|
75914
76254
|
* (0.35, on the zone crop only — `retrain/retrain-assist.ts`); everything else
|
|
75915
76255
|
* starts here and the operator drops the ones they do not want.
|
|
@@ -76943,7 +77283,51 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
76943
77283
|
* `docs/decisions/` — the gate is a model input, so this is a deliberate
|
|
76944
77284
|
* exception to "model-input crops keep captureCrop".
|
|
76945
77285
|
*/
|
|
77286
|
+
/**
|
|
77287
|
+
* The operator-debug crop capture (`pipeline/debug-crop-capture.ts`).
|
|
77288
|
+
*
|
|
77289
|
+
* Null until the transport and the media store exist. Reached from three
|
|
77290
|
+
* decision sites and gated INSIDE the capture on the track's `debug` flag, so
|
|
77291
|
+
* a camera with no marked track pays one map lookup per decision — see the
|
|
77292
|
+
* capture's module doc for why the gate lives there and not at each site.
|
|
77293
|
+
*/
|
|
77294
|
+
captureDebugCropFn = null;
|
|
76946
77295
|
captureDisplayCropFn = null;
|
|
77296
|
+
/**
|
|
77297
|
+
* Offer one decision's pixels to the operator-debug crop capture.
|
|
77298
|
+
*
|
|
77299
|
+
* Fire-and-forget and NEVER throws: this is the addon's own bookkeeping, and
|
|
77300
|
+
* a frame path that can be slowed or broken by a diagnostic is worse than no
|
|
77301
|
+
* diagnostic. The gate, the per-track cap and all six refusal reasons live in
|
|
77302
|
+
* the capture (`pipeline/debug-crop-capture.ts`); this is only the seam.
|
|
77303
|
+
*/
|
|
77304
|
+
/**
|
|
77305
|
+
* The CURRENT frame's handle and dims, per camera — the one thing the
|
|
77306
|
+
* salvage-bind observer lacks.
|
|
77307
|
+
*
|
|
77308
|
+
* The tracker knows no `deviceId`, holds no handle and writes no media (that
|
|
77309
|
+
* is the point of `pipeline/tracker/**`), so a growth bind reports its two
|
|
77310
|
+
* boxes and stops. This mirror is what lets the host put those boxes back on
|
|
77311
|
+
* a picture. Written ONLY while some track on this runner is marked
|
|
77312
|
+
* (`residents.anyDebugMarked()`), so an ordinary fleet does not allocate one
|
|
77313
|
+
* object per frame per camera for a diagnostic nobody asked for.
|
|
77314
|
+
*/
|
|
77315
|
+
debugFrameContext = /* @__PURE__ */ new Map();
|
|
77316
|
+
offerDebugCrop(input) {
|
|
77317
|
+
const capture = this.captureDebugCropFn;
|
|
77318
|
+
if (capture === null) return;
|
|
77319
|
+
capture(input).catch((err) => {
|
|
77320
|
+
this.ctx.logger.debug("debug crop offer threw", {
|
|
77321
|
+
tags: { deviceId: input.deviceId },
|
|
77322
|
+
meta: {
|
|
77323
|
+
deviceId: input.deviceId,
|
|
77324
|
+
trackId: input.trackId,
|
|
77325
|
+
decision: input.decision,
|
|
77326
|
+
error: err instanceof Error ? err.message : String(err)
|
|
77327
|
+
}
|
|
77328
|
+
});
|
|
77329
|
+
});
|
|
77330
|
+
}
|
|
76947
77331
|
captureCrop = null;
|
|
76948
77332
|
/** Full NATIVE frame by handle (downscaled worker-side to `maxWidth`), routed
|
|
76949
77333
|
* to the owning runner, PLUS the source `tier`. Captured in the async init
|
|
@@ -77308,6 +77692,36 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
77308
77692
|
isNodeOffline: (nodeId) => this.nodeLiveness.isKnownOffline(nodeId, Date.now())
|
|
77309
77693
|
});
|
|
77310
77694
|
this.captureCrop = transport.captureCrop;
|
|
77695
|
+
const fetchDebugFullFrame = transport.getNativeFullFrameRgb;
|
|
77696
|
+
this.captureDebugCropFn = createDebugCropCapture({
|
|
77697
|
+
isDebugMarked: (trackId) => this.residents.isDebugMarked(trackId),
|
|
77698
|
+
fetchFrameRgb: async (handle, deviceId, trackId) => {
|
|
77699
|
+
const frame = await this.captureScheduler.request({
|
|
77700
|
+
deviceId,
|
|
77701
|
+
trackId,
|
|
77702
|
+
kind: "other",
|
|
77703
|
+
coalesceKey: captureCoalesceKey(deviceId, "other", trackId),
|
|
77704
|
+
staleAfterMs: DEBUG_CROP_FETCH_STALE_MS,
|
|
77705
|
+
exec: () => fetchDebugFullFrame(handle, DEBUG_CROP_FETCH_MAX_WIDTH)
|
|
77706
|
+
});
|
|
77707
|
+
return frame === null || frame === void 0 ? null : {
|
|
77708
|
+
bytes: frame.data,
|
|
77709
|
+
width: frame.width,
|
|
77710
|
+
height: frame.height
|
|
77711
|
+
};
|
|
77712
|
+
},
|
|
77713
|
+
extractCropRaw: (data, w, h, box) => extractCropRaw(data, w, h, box),
|
|
77714
|
+
drawBoxedFrame: (data, w, h, boxes, options) => drawBoxedFrame(data, w, h, boxes, options ?? {}),
|
|
77715
|
+
putDebugCrop: (put) => stores.mediaStore.put({
|
|
77716
|
+
deviceId: put.deviceId,
|
|
77717
|
+
ownerKind: "track",
|
|
77718
|
+
ownerId: put.trackId,
|
|
77719
|
+
kind: "debugCrop",
|
|
77720
|
+
timestamp: put.timestamp,
|
|
77721
|
+
data: put.data
|
|
77722
|
+
}),
|
|
77723
|
+
logger: logger.child("DebugCrop")
|
|
77724
|
+
});
|
|
77311
77725
|
this.eventMediaDispatcher = new EventMediaDispatcher({
|
|
77312
77726
|
getRemoteFrame: transport.getRemoteFrame,
|
|
77313
77727
|
getNativeCropJpeg: transport.getNativeCropJpeg,
|
|
@@ -80308,6 +80722,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
80308
80722
|
frameHeight: result.frameHeight
|
|
80309
80723
|
}, exhaustedIds);
|
|
80310
80724
|
const outcome = run.outcome;
|
|
80725
|
+
if (frameHandle !== void 0 && this.residents.anyDebugMarked()) this.debugFrameContext.set(deviceId, {
|
|
80726
|
+
handle: frameHandle,
|
|
80727
|
+
width: result.frameWidth,
|
|
80728
|
+
height: result.frameHeight,
|
|
80729
|
+
timestamp: result.timestamp
|
|
80730
|
+
});
|
|
80311
80731
|
const gateNowMs = Date.now();
|
|
80312
80732
|
for (const { id, t } of bornCandidates) {
|
|
80313
80733
|
if (outcome.undecided.has(id)) {
|
|
@@ -80366,6 +80786,32 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
80366
80786
|
error: String(err)
|
|
80367
80787
|
} });
|
|
80368
80788
|
});
|
|
80789
|
+
if (frameHandle !== void 0) {
|
|
80790
|
+
const decision = run.decisions.get(id);
|
|
80791
|
+
this.offerDebugCrop({
|
|
80792
|
+
deviceId,
|
|
80793
|
+
trackId: id,
|
|
80794
|
+
decision: "birth-confirmation",
|
|
80795
|
+
subject: {
|
|
80796
|
+
x: t.bbox.x,
|
|
80797
|
+
y: t.bbox.y,
|
|
80798
|
+
w: t.bbox.w,
|
|
80799
|
+
h: t.bbox.h
|
|
80800
|
+
},
|
|
80801
|
+
frameHandle,
|
|
80802
|
+
frameWidth: result.frameWidth,
|
|
80803
|
+
frameHeight: result.frameHeight,
|
|
80804
|
+
timestamp: gateNowMs,
|
|
80805
|
+
evidence: {
|
|
80806
|
+
className: t.className,
|
|
80807
|
+
source,
|
|
80808
|
+
verdict: "suppression-held",
|
|
80809
|
+
...decision?.cropSource !== void 0 ? { cropSource: decision.cropSource } : {},
|
|
80810
|
+
...decision?.bestScore !== void 0 ? { bestScore: decision.bestScore } : {},
|
|
80811
|
+
judgedCropKept: judged !== void 0
|
|
80812
|
+
}
|
|
80813
|
+
});
|
|
80814
|
+
}
|
|
80369
80815
|
log.info("birth suppression held — the gate is not this root detector", {
|
|
80370
80816
|
tags: { deviceId },
|
|
80371
80817
|
meta: {
|
|
@@ -82394,6 +82840,32 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
82394
82840
|
meta: { ...bestShotPromotionMeta(promotionAttempt) }
|
|
82395
82841
|
});
|
|
82396
82842
|
}
|
|
82843
|
+
if (frameHandle !== void 0) {
|
|
82844
|
+
const heldBbox = this.residents.bestSeenBbox(t.trackId);
|
|
82845
|
+
const rival = heldBbox !== void 0 && heldBbox.frameWidth === frameWidth && heldBbox.frameHeight === frameHeight ? {
|
|
82846
|
+
x: heldBbox.x,
|
|
82847
|
+
y: heldBbox.y,
|
|
82848
|
+
w: heldBbox.w,
|
|
82849
|
+
h: heldBbox.h
|
|
82850
|
+
} : void 0;
|
|
82851
|
+
this.offerDebugCrop({
|
|
82852
|
+
deviceId,
|
|
82853
|
+
trackId: t.trackId,
|
|
82854
|
+
decision: "best-shot",
|
|
82855
|
+
subject: {
|
|
82856
|
+
x: t.bbox.x,
|
|
82857
|
+
y: t.bbox.y,
|
|
82858
|
+
w: t.bbox.w,
|
|
82859
|
+
h: t.bbox.h
|
|
82860
|
+
},
|
|
82861
|
+
...rival !== void 0 ? { rival } : {},
|
|
82862
|
+
frameHandle,
|
|
82863
|
+
frameWidth,
|
|
82864
|
+
frameHeight,
|
|
82865
|
+
timestamp,
|
|
82866
|
+
evidence: { ...bestShotPromotionMeta(promotionAttempt) }
|
|
82867
|
+
});
|
|
82868
|
+
}
|
|
82397
82869
|
if (!plan.appendSnapshot && !rollingLastFrame && !bestThumbnail && keyFrame === "skip") continue;
|
|
82398
82870
|
targets.push({
|
|
82399
82871
|
trackId: t.trackId,
|
|
@@ -83747,6 +84219,33 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
83747
84219
|
...record
|
|
83748
84220
|
}
|
|
83749
84221
|
});
|
|
84222
|
+
if (record.gate !== "growth") return;
|
|
84223
|
+
const frame = this.debugFrameContext.get(deviceId);
|
|
84224
|
+
if (frame === void 0 || frame.width !== record.frameWidth) return;
|
|
84225
|
+
this.offerDebugCrop({
|
|
84226
|
+
deviceId,
|
|
84227
|
+
trackId: record.trackId,
|
|
84228
|
+
decision: "growth-rescue",
|
|
84229
|
+
subject: record.trackBbox,
|
|
84230
|
+
rival: record.detBbox,
|
|
84231
|
+
frameHandle: frame.handle,
|
|
84232
|
+
frameWidth: record.frameWidth,
|
|
84233
|
+
frameHeight: record.frameHeight,
|
|
84234
|
+
timestamp: frame.timestamp,
|
|
84235
|
+
evidence: {
|
|
84236
|
+
stage: record.stage,
|
|
84237
|
+
trackClass: record.trackClass,
|
|
84238
|
+
detClass: record.detClass,
|
|
84239
|
+
detScore: record.detScore,
|
|
84240
|
+
iou: record.iou,
|
|
84241
|
+
centroidDistPx: record.centroidDistPx,
|
|
84242
|
+
trackDiagPx: record.trackDiagPx,
|
|
84243
|
+
areaRatio: record.areaRatio,
|
|
84244
|
+
dtMs: record.dtMs,
|
|
84245
|
+
trackAgeMs: record.trackAgeMs,
|
|
84246
|
+
source
|
|
84247
|
+
}
|
|
84248
|
+
});
|
|
83750
84249
|
});
|
|
83751
84250
|
p.setSpawnRefusalObserver((record) => {
|
|
83752
84251
|
this.ctx.logger.info("tracker: detection dropped — nothing downstream received it", {
|
|
@@ -85893,6 +86392,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
85893
86392
|
logger: this.ctx.logger,
|
|
85894
86393
|
...this.debugNoteArchive !== null ? { noteArchive: this.debugNoteArchive } : {}
|
|
85895
86394
|
}, input);
|
|
86395
|
+
this.residents.setDebugMarked(input.deviceId, input.trackId, out.debug);
|
|
85896
86396
|
this.ctx.logger.info("track operator flags set", {
|
|
85897
86397
|
tags: { deviceId: input.deviceId },
|
|
85898
86398
|
meta: {
|