@camstack/addon-post-analysis 1.2.193 → 1.2.195
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-CJKdyCag.js → dist-C15zOWA2.js} +71 -40
- package/dist/{dist-CoI1xhsw.mjs → dist-DE64ld3c.mjs} +71 -40
- 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-BmbhUxPs.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-if_wK4QI.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-qRtMpIyP.mjs → hostInit-D-5Zg6MO.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +218 -80
- package/dist/pipeline-analytics/index.mjs +218 -80
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg$1, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-
|
|
1
|
+
import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg$1, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-DE64ld3c.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";
|
|
@@ -35216,6 +35216,7 @@ var FrameProcessor = class {
|
|
|
35216
35216
|
const plateByBbox = /* @__PURE__ */ new Map();
|
|
35217
35217
|
const maskByBbox = /* @__PURE__ */ new Map();
|
|
35218
35218
|
const nativeRecoveryByBbox = /* @__PURE__ */ new Map();
|
|
35219
|
+
const birthEvidenceByBbox = /* @__PURE__ */ new Map();
|
|
35219
35220
|
const flatDetections = frame.detections.filter((d) => d.kind === "first-level").map((det) => {
|
|
35220
35221
|
const bbox = {
|
|
35221
35222
|
x: det.bbox.x,
|
|
@@ -35231,6 +35232,7 @@ var FrameProcessor = class {
|
|
|
35231
35232
|
...det.nativeRecovery !== void 0 ? { nativeRecovery: det.nativeRecovery } : {}
|
|
35232
35233
|
};
|
|
35233
35234
|
if (det.nativeRecovery !== void 0) nativeRecoveryByBbox.set(bbox, det.nativeRecovery);
|
|
35235
|
+
if (det.birthEvidence !== void 0) birthEvidenceByBbox.set(bbox, det.birthEvidence);
|
|
35234
35236
|
if (det.labels.length > 0) labelsByBbox.set(bbox, det.labels);
|
|
35235
35237
|
if (det.debug?.originalClassTier !== void 0) originalClassTierByBbox.set(bbox, det.debug.originalClassTier);
|
|
35236
35238
|
if (det.embedding !== void 0) embeddingByBbox.set(bbox, {
|
|
@@ -35456,6 +35458,7 @@ var FrameProcessor = class {
|
|
|
35456
35458
|
const plate = plateByBbox.get(td.bbox);
|
|
35457
35459
|
const sourceDetectionId = sourceIdByBbox.get(td.bbox);
|
|
35458
35460
|
const nativeRecovery = td.matchedThisFrame !== false ? nativeRecoveryByBbox.get(td.bbox) : void 0;
|
|
35461
|
+
const birthEvidence = td.matchedThisFrame !== false ? birthEvidenceByBbox.get(td.bbox) : void 0;
|
|
35459
35462
|
return {
|
|
35460
35463
|
trackId: td.trackId,
|
|
35461
35464
|
...sourceDetectionId !== void 0 ? { sourceDetectionId } : {},
|
|
@@ -35479,7 +35482,8 @@ var FrameProcessor = class {
|
|
|
35479
35482
|
plateScore: plate.score,
|
|
35480
35483
|
plateBbox: plate.bbox
|
|
35481
35484
|
} : {},
|
|
35482
|
-
...nativeRecovery !== void 0 ? { nativeRecovery } : {}
|
|
35485
|
+
...nativeRecovery !== void 0 ? { nativeRecovery } : {},
|
|
35486
|
+
...birthEvidence !== void 0 ? { birthEvidence } : {}
|
|
35483
35487
|
};
|
|
35484
35488
|
});
|
|
35485
35489
|
this.lastLabelWriteRefusals = refusals;
|
|
@@ -45281,9 +45285,28 @@ function withTimeout$2(promise, timeoutMs) {
|
|
|
45281
45285
|
});
|
|
45282
45286
|
}
|
|
45283
45287
|
async function runConfirmation(candidate, config, deps, exhausted) {
|
|
45288
|
+
const carried = candidate.evidenceCrop ?? null;
|
|
45289
|
+
if (carried) {
|
|
45290
|
+
const verdict = await measure(candidate, carried, config, deps, "carried");
|
|
45291
|
+
if (verdict.verdict === "confirmed" || verdict.reason === "redetect-error") return verdict;
|
|
45292
|
+
}
|
|
45284
45293
|
let crop = await deps.fetchCrop(candidate);
|
|
45285
|
-
|
|
45294
|
+
let cropSource = "native";
|
|
45295
|
+
if (!crop && exhausted && deps.fetchFallbackCrop) {
|
|
45296
|
+
crop = await deps.fetchFallbackCrop(candidate);
|
|
45297
|
+
cropSource = "fallback";
|
|
45298
|
+
}
|
|
45286
45299
|
if (!crop) return undecided(candidate, "no-crop");
|
|
45300
|
+
return measure(candidate, crop, config, deps, cropSource);
|
|
45301
|
+
}
|
|
45302
|
+
/**
|
|
45303
|
+
* Re-detect on ONE crop and turn the result into a verdict.
|
|
45304
|
+
*
|
|
45305
|
+
* Shared by all three sources so a crop can never be judged by two slightly
|
|
45306
|
+
* different rules depending on where it came from — the `cropSource` argument
|
|
45307
|
+
* is the ONLY thing that varies, and it varies in exactly one place below.
|
|
45308
|
+
*/
|
|
45309
|
+
async function measure(candidate, crop, config, deps, cropSource) {
|
|
45287
45310
|
const detections = await deps.redetect(crop);
|
|
45288
45311
|
if (detections === null) return undecided(candidate, "redetect-error");
|
|
45289
45312
|
let best;
|
|
@@ -45293,6 +45316,7 @@ async function runConfirmation(candidate, config, deps, exhausted) {
|
|
|
45293
45316
|
} else if (!bestIncompatible || d.score > bestIncompatible.score) bestIncompatible = d;
|
|
45294
45317
|
const bar = barFor(candidate, config, deps);
|
|
45295
45318
|
const confirmed = best !== void 0 && best.score >= bar;
|
|
45319
|
+
if (!confirmed && cropSource !== "native") return undecided(candidate, cropSource === "fallback" ? "fallback-inconclusive" : "carried-inconclusive");
|
|
45296
45320
|
const secondary = bestIncompatible ? promoteSecondary(candidate, bestIncompatible, bar, deps) : null;
|
|
45297
45321
|
return {
|
|
45298
45322
|
trackId: candidate.trackId,
|
|
@@ -45300,6 +45324,7 @@ async function runConfirmation(candidate, config, deps, exhausted) {
|
|
|
45300
45324
|
reason: confirmed ? "confirmed" : "suppressed",
|
|
45301
45325
|
className: candidate.className,
|
|
45302
45326
|
appliedMinConfidence: bar,
|
|
45327
|
+
cropSource,
|
|
45303
45328
|
...best ? { bestScore: best.score } : {},
|
|
45304
45329
|
...bestIncompatible ? {
|
|
45305
45330
|
bestIncompatibleClass: bestIncompatible.macroClass,
|
|
@@ -51812,6 +51837,97 @@ async function normalizePhotoOrientation(photo) {
|
|
|
51812
51837
|
if (orientation === void 0 || orientation === EXIF_UPRIGHT) return photo;
|
|
51813
51838
|
return sharp(photo).rotate().jpeg({ quality: 95 }).toBuffer();
|
|
51814
51839
|
}
|
|
51840
|
+
/**
|
|
51841
|
+
* How long one birth's evidence is kept. Twice `confirmationGateMaxDeferralMs`
|
|
51842
|
+
* (2 000 ms default) — the longest a deferred birth can still be re-offered.
|
|
51843
|
+
*/
|
|
51844
|
+
var BIRTH_EVIDENCE_TTL_MS = 4e3;
|
|
51845
|
+
var BirthEvidenceStore = class {
|
|
51846
|
+
slotsPerCamera;
|
|
51847
|
+
ttlMs;
|
|
51848
|
+
byDevice = /* @__PURE__ */ new Map();
|
|
51849
|
+
constructor(slotsPerCamera = 8, ttlMs = BIRTH_EVIDENCE_TTL_MS) {
|
|
51850
|
+
this.slotsPerCamera = slotsPerCamera;
|
|
51851
|
+
this.ttlMs = ttlMs;
|
|
51852
|
+
}
|
|
51853
|
+
/**
|
|
51854
|
+
* Keep this birth's crop.
|
|
51855
|
+
*
|
|
51856
|
+
* The FIRST crop for an id wins. A later frame's crop of the same track is a
|
|
51857
|
+
* different instant, and this store exists precisely to preserve the instant
|
|
51858
|
+
* the birth was measured on — overwriting it would quietly reintroduce the
|
|
51859
|
+
* defect the fallback asymmetry exists to distrust.
|
|
51860
|
+
*/
|
|
51861
|
+
remember(deviceKey, trackId, crop, nowMs) {
|
|
51862
|
+
let slots = this.byDevice.get(deviceKey);
|
|
51863
|
+
if (!slots) {
|
|
51864
|
+
slots = /* @__PURE__ */ new Map();
|
|
51865
|
+
this.byDevice.set(deviceKey, slots);
|
|
51866
|
+
}
|
|
51867
|
+
if (slots.has(trackId)) return;
|
|
51868
|
+
slots.set(trackId, {
|
|
51869
|
+
crop,
|
|
51870
|
+
atMs: nowMs
|
|
51871
|
+
});
|
|
51872
|
+
this.evict(deviceKey, slots, nowMs);
|
|
51873
|
+
}
|
|
51874
|
+
/** This birth's crop, or `null` when it was never kept or has aged out. */
|
|
51875
|
+
get(deviceKey, trackId, nowMs) {
|
|
51876
|
+
const entry = this.byDevice.get(deviceKey)?.get(trackId);
|
|
51877
|
+
if (!entry) return null;
|
|
51878
|
+
if (nowMs - entry.atMs > this.ttlMs) {
|
|
51879
|
+
this.resolve(deviceKey, trackId);
|
|
51880
|
+
return null;
|
|
51881
|
+
}
|
|
51882
|
+
return entry.crop;
|
|
51883
|
+
}
|
|
51884
|
+
/** A verdict finally arrived — the crop is dead weight from here. */
|
|
51885
|
+
resolve(deviceKey, trackId) {
|
|
51886
|
+
const slots = this.byDevice.get(deviceKey);
|
|
51887
|
+
if (!slots) return;
|
|
51888
|
+
slots.delete(trackId);
|
|
51889
|
+
if (slots.size === 0) this.byDevice.delete(deviceKey);
|
|
51890
|
+
}
|
|
51891
|
+
/**
|
|
51892
|
+
* Forget every id the tracker is no longer carrying — the same per-frame
|
|
51893
|
+
* contract as `DeferredBirthRegistry.retain`. An id the tracker dropped can
|
|
51894
|
+
* never be decided, so holding its pixels only leaks.
|
|
51895
|
+
*/
|
|
51896
|
+
retain(deviceKey, currentTrackIds) {
|
|
51897
|
+
const slots = this.byDevice.get(deviceKey);
|
|
51898
|
+
if (!slots) return;
|
|
51899
|
+
for (const id of [...slots.keys()]) if (!currentTrackIds.has(id)) slots.delete(id);
|
|
51900
|
+
if (slots.size === 0) this.byDevice.delete(deviceKey);
|
|
51901
|
+
}
|
|
51902
|
+
/** Crops held for one camera (diagnostics and tests). */
|
|
51903
|
+
size(deviceKey) {
|
|
51904
|
+
return this.byDevice.get(deviceKey)?.size ?? 0;
|
|
51905
|
+
}
|
|
51906
|
+
/** Bytes held across every camera — the declared budget, checkable. */
|
|
51907
|
+
totalBytes() {
|
|
51908
|
+
let total = 0;
|
|
51909
|
+
for (const slots of this.byDevice.values()) for (const entry of slots.values()) total += entry.crop.byteLength;
|
|
51910
|
+
return total;
|
|
51911
|
+
}
|
|
51912
|
+
/** Drop a camera's reserve wholesale (device removed / pipeline reset). */
|
|
51913
|
+
clearDevice(deviceKey) {
|
|
51914
|
+
this.byDevice.delete(deviceKey);
|
|
51915
|
+
}
|
|
51916
|
+
/**
|
|
51917
|
+
* Age first, then the quota — and only ever inside THIS camera's slots.
|
|
51918
|
+
* `Map` keeps insertion order and a crop is inserted once, so the first key
|
|
51919
|
+
* is always the oldest.
|
|
51920
|
+
*/
|
|
51921
|
+
evict(deviceKey, slots, nowMs) {
|
|
51922
|
+
for (const [id, entry] of [...slots]) if (nowMs - entry.atMs > this.ttlMs) slots.delete(id);
|
|
51923
|
+
while (slots.size > this.slotsPerCamera) {
|
|
51924
|
+
const oldest = slots.keys().next().value;
|
|
51925
|
+
if (oldest === void 0) break;
|
|
51926
|
+
slots.delete(oldest);
|
|
51927
|
+
}
|
|
51928
|
+
if (slots.size === 0) this.byDevice.delete(deviceKey);
|
|
51929
|
+
}
|
|
51930
|
+
};
|
|
51815
51931
|
//#endregion
|
|
51816
51932
|
//#region src/pipeline-analytics/pipeline/deferred-births.ts
|
|
51817
51933
|
var DeferredBirthRegistry = class {
|
|
@@ -53968,6 +54084,77 @@ function personOverVehicle(person, neighbours, threshold = CONTAINMENT_THRESHOLD
|
|
|
53968
54084
|
return best >= threshold ? best : null;
|
|
53969
54085
|
}
|
|
53970
54086
|
//#endregion
|
|
54087
|
+
//#region src/pipeline-analytics/pipeline/started-births.ts
|
|
54088
|
+
/**
|
|
54089
|
+
* Memory of births whose ritual has already RUN, per device+source.
|
|
54090
|
+
*
|
|
54091
|
+
* A birth is a decision, and a decision is taken once. Until 2026-09-07 it
|
|
54092
|
+
* could be taken twice, on camera 615 it routinely was, and the second vote
|
|
54093
|
+
* won:
|
|
54094
|
+
*
|
|
54095
|
+
* 04:59:38.335Z confirmation gate: birth confirmed {trackId=f585b2ee…
|
|
54096
|
+
* className=animal bestScore=0.7068}
|
|
54097
|
+
* 04:59:38.335Z track started {trackId=f585b2ee…}
|
|
54098
|
+
* 04:59:38.821Z confirmation gate: birth suppressed (false positive)
|
|
54099
|
+
* {trackId=f585b2ee… className=animal} ← no bestScore:
|
|
54100
|
+
* the second crop held no compatible detection AT ALL
|
|
54101
|
+
* 04:59:38.821Z birth suppressed — track record retracted
|
|
54102
|
+
*
|
|
54103
|
+
* TWO conditions have to meet, and both are structural:
|
|
54104
|
+
*
|
|
54105
|
+
* 1. `processFrame` reads `lastActiveTrackIds` at the top and writes it at the
|
|
54106
|
+
* bottom, and AWAITS the confirmation gate in between (a crop round trip to
|
|
54107
|
+
* the frame's node plus a re-detection: 130–490 ms measured on this camera,
|
|
54108
|
+
* against 100 ms frame spacing). Several frames are therefore in flight at
|
|
54109
|
+
* once and each of them sees the same fresh id as unjudged.
|
|
54110
|
+
* 2. The other birth discriminator, `positionsCount > 1`, does not close the
|
|
54111
|
+
* gap either: a COASTED frame deliberately appends no position
|
|
54112
|
+
* (`TrackStore.upsert`, `matched: false`), so a track the full-frame pass
|
|
54113
|
+
* cannot see — which is the entire premise of the small-subject recovery —
|
|
54114
|
+
* keeps exactly one position for as long as it coasts, and
|
|
54115
|
+
* `classifyTrackAppearance` keeps answering `birth`.
|
|
54116
|
+
*
|
|
54117
|
+
* So the id that has already been born is remembered HERE, and both halves of
|
|
54118
|
+
* the birth path consult it: it is not offered to the gate again, and a verdict
|
|
54119
|
+
* that arrives for it anyway cannot retract it. Same lifetime discipline as
|
|
54120
|
+
* {@link SuppressedBirthRegistry} — held while the tracker still carries the
|
|
54121
|
+
* id, forgotten the frame it stops.
|
|
54122
|
+
*/
|
|
54123
|
+
var StartedBirthRegistry = class {
|
|
54124
|
+
byDevice = /* @__PURE__ */ new Map();
|
|
54125
|
+
/** Record that this id's birth ritual ran — `start`, firstFrame, the lot. */
|
|
54126
|
+
markStarted(deviceKey, trackId) {
|
|
54127
|
+
let ids = this.byDevice.get(deviceKey);
|
|
54128
|
+
if (!ids) {
|
|
54129
|
+
ids = /* @__PURE__ */ new Set();
|
|
54130
|
+
this.byDevice.set(deviceKey, ids);
|
|
54131
|
+
}
|
|
54132
|
+
ids.add(trackId);
|
|
54133
|
+
}
|
|
54134
|
+
hasStarted(deviceKey, trackId) {
|
|
54135
|
+
return this.byDevice.get(deviceKey)?.has(trackId) === true;
|
|
54136
|
+
}
|
|
54137
|
+
/**
|
|
54138
|
+
* Forget every started id the tracker is no longer carrying. Called once per
|
|
54139
|
+
* frame with the ids present THIS frame — an id the tracker has dropped can
|
|
54140
|
+
* never be re-offered as a birth, so holding it serves nothing.
|
|
54141
|
+
*/
|
|
54142
|
+
retain(deviceKey, currentTrackIds) {
|
|
54143
|
+
const ids = this.byDevice.get(deviceKey);
|
|
54144
|
+
if (!ids) return;
|
|
54145
|
+
for (const id of ids) if (!currentTrackIds.has(id)) ids.delete(id);
|
|
54146
|
+
if (ids.size === 0) this.byDevice.delete(deviceKey);
|
|
54147
|
+
}
|
|
54148
|
+
/** Started ids currently held for a device — diagnostics and tests. */
|
|
54149
|
+
size(deviceKey) {
|
|
54150
|
+
return this.byDevice.get(deviceKey)?.size ?? 0;
|
|
54151
|
+
}
|
|
54152
|
+
/** Drop a device's memory wholesale (device removed / pipeline reset). */
|
|
54153
|
+
clearDevice(deviceKey) {
|
|
54154
|
+
this.byDevice.delete(deviceKey);
|
|
54155
|
+
}
|
|
54156
|
+
};
|
|
54157
|
+
//#endregion
|
|
53971
54158
|
//#region src/pipeline-analytics/pipeline/stationary/stationary-evidence.ts
|
|
53972
54159
|
/**
|
|
53973
54160
|
* stationary-evidence — how a parked object EARNS a registry entry.
|
|
@@ -55240,77 +55427,6 @@ function rowToEntry(id, data) {
|
|
|
55240
55427
|
};
|
|
55241
55428
|
}
|
|
55242
55429
|
//#endregion
|
|
55243
|
-
//#region src/pipeline-analytics/pipeline/started-births.ts
|
|
55244
|
-
/**
|
|
55245
|
-
* Memory of births whose ritual has already RUN, per device+source.
|
|
55246
|
-
*
|
|
55247
|
-
* A birth is a decision, and a decision is taken once. Until 2026-09-07 it
|
|
55248
|
-
* could be taken twice, on camera 615 it routinely was, and the second vote
|
|
55249
|
-
* won:
|
|
55250
|
-
*
|
|
55251
|
-
* 04:59:38.335Z confirmation gate: birth confirmed {trackId=f585b2ee…
|
|
55252
|
-
* className=animal bestScore=0.7068}
|
|
55253
|
-
* 04:59:38.335Z track started {trackId=f585b2ee…}
|
|
55254
|
-
* 04:59:38.821Z confirmation gate: birth suppressed (false positive)
|
|
55255
|
-
* {trackId=f585b2ee… className=animal} ← no bestScore:
|
|
55256
|
-
* the second crop held no compatible detection AT ALL
|
|
55257
|
-
* 04:59:38.821Z birth suppressed — track record retracted
|
|
55258
|
-
*
|
|
55259
|
-
* TWO conditions have to meet, and both are structural:
|
|
55260
|
-
*
|
|
55261
|
-
* 1. `processFrame` reads `lastActiveTrackIds` at the top and writes it at the
|
|
55262
|
-
* bottom, and AWAITS the confirmation gate in between (a crop round trip to
|
|
55263
|
-
* the frame's node plus a re-detection: 130–490 ms measured on this camera,
|
|
55264
|
-
* against 100 ms frame spacing). Several frames are therefore in flight at
|
|
55265
|
-
* once and each of them sees the same fresh id as unjudged.
|
|
55266
|
-
* 2. The other birth discriminator, `positionsCount > 1`, does not close the
|
|
55267
|
-
* gap either: a COASTED frame deliberately appends no position
|
|
55268
|
-
* (`TrackStore.upsert`, `matched: false`), so a track the full-frame pass
|
|
55269
|
-
* cannot see — which is the entire premise of the small-subject recovery —
|
|
55270
|
-
* keeps exactly one position for as long as it coasts, and
|
|
55271
|
-
* `classifyTrackAppearance` keeps answering `birth`.
|
|
55272
|
-
*
|
|
55273
|
-
* So the id that has already been born is remembered HERE, and both halves of
|
|
55274
|
-
* the birth path consult it: it is not offered to the gate again, and a verdict
|
|
55275
|
-
* that arrives for it anyway cannot retract it. Same lifetime discipline as
|
|
55276
|
-
* {@link SuppressedBirthRegistry} — held while the tracker still carries the
|
|
55277
|
-
* id, forgotten the frame it stops.
|
|
55278
|
-
*/
|
|
55279
|
-
var StartedBirthRegistry = class {
|
|
55280
|
-
byDevice = /* @__PURE__ */ new Map();
|
|
55281
|
-
/** Record that this id's birth ritual ran — `start`, firstFrame, the lot. */
|
|
55282
|
-
markStarted(deviceKey, trackId) {
|
|
55283
|
-
let ids = this.byDevice.get(deviceKey);
|
|
55284
|
-
if (!ids) {
|
|
55285
|
-
ids = /* @__PURE__ */ new Set();
|
|
55286
|
-
this.byDevice.set(deviceKey, ids);
|
|
55287
|
-
}
|
|
55288
|
-
ids.add(trackId);
|
|
55289
|
-
}
|
|
55290
|
-
hasStarted(deviceKey, trackId) {
|
|
55291
|
-
return this.byDevice.get(deviceKey)?.has(trackId) === true;
|
|
55292
|
-
}
|
|
55293
|
-
/**
|
|
55294
|
-
* Forget every started id the tracker is no longer carrying. Called once per
|
|
55295
|
-
* frame with the ids present THIS frame — an id the tracker has dropped can
|
|
55296
|
-
* never be re-offered as a birth, so holding it serves nothing.
|
|
55297
|
-
*/
|
|
55298
|
-
retain(deviceKey, currentTrackIds) {
|
|
55299
|
-
const ids = this.byDevice.get(deviceKey);
|
|
55300
|
-
if (!ids) return;
|
|
55301
|
-
for (const id of ids) if (!currentTrackIds.has(id)) ids.delete(id);
|
|
55302
|
-
if (ids.size === 0) this.byDevice.delete(deviceKey);
|
|
55303
|
-
}
|
|
55304
|
-
/** Started ids currently held for a device — diagnostics and tests. */
|
|
55305
|
-
size(deviceKey) {
|
|
55306
|
-
return this.byDevice.get(deviceKey)?.size ?? 0;
|
|
55307
|
-
}
|
|
55308
|
-
/** Drop a device's memory wholesale (device removed / pipeline reset). */
|
|
55309
|
-
clearDevice(deviceKey) {
|
|
55310
|
-
this.byDevice.delete(deviceKey);
|
|
55311
|
-
}
|
|
55312
|
-
};
|
|
55313
|
-
//#endregion
|
|
55314
55430
|
//#region src/pipeline-analytics/pipeline/suppressed-births.ts
|
|
55315
55431
|
/**
|
|
55316
55432
|
* Memory of births the confirmation gate rejected, per device.
|
|
@@ -67676,6 +67792,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67676
67792
|
/** Births the gate could not MEASURE, awaiting another look on a later frame. */
|
|
67677
67793
|
deferredBirths = new DeferredBirthRegistry();
|
|
67678
67794
|
/**
|
|
67795
|
+
* The pixels each deferred birth arrived with — a RESERVE per camera, evicted
|
|
67796
|
+
* by age inside that camera's own quota (D379).
|
|
67797
|
+
*
|
|
67798
|
+
* Not a shared cap. The path this replaces was one node-wide 4 096-entry LRU
|
|
67799
|
+
* measured pinned full in 6 814 of 6 849 samples over three hours, in which a
|
|
67800
|
+
* busy camera evicted a quiet one's proof and every camera added shortened
|
|
67801
|
+
* every other camera's window. See `pipeline/birth-evidence-store.ts` for the
|
|
67802
|
+
* two measurements the slot count and the TTL are taken from.
|
|
67803
|
+
*/
|
|
67804
|
+
birthEvidence = new BirthEvidenceStore();
|
|
67805
|
+
/**
|
|
67679
67806
|
* Subjects the confirmation gate found in a birth's crop BESIDES the birth
|
|
67680
67807
|
* itself (a dog in a person's crop), waiting to ride the next frame of the
|
|
67681
67808
|
* same (device, source) as detections — `pipeline/secondary-subjects.ts`.
|
|
@@ -70929,12 +71056,20 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70929
71056
|
const exhaustionNowMs = Date.now();
|
|
70930
71057
|
const exhaustedIds = /* @__PURE__ */ new Set();
|
|
70931
71058
|
for (const { id } of bornCandidates) if (this.deferredBirths.exhausted(key, id, exhaustionNowMs, gateSettings.maxAttempts, gateSettings.maxDeferralMs)) exhaustedIds.add(id);
|
|
70932
|
-
const
|
|
70933
|
-
|
|
70934
|
-
|
|
70935
|
-
|
|
70936
|
-
|
|
70937
|
-
|
|
71059
|
+
for (const { id, t } of bornCandidates) {
|
|
71060
|
+
if (t.birthEvidence === void 0) continue;
|
|
71061
|
+
this.birthEvidence.remember(key, id, Buffer.from(t.birthEvidence.jpegBase64, "base64"), exhaustionNowMs);
|
|
71062
|
+
}
|
|
71063
|
+
const outcome = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => {
|
|
71064
|
+
const evidenceCrop = this.birthEvidence.get(key, id, exhaustionNowMs);
|
|
71065
|
+
return {
|
|
71066
|
+
trackId: id,
|
|
71067
|
+
className: t.className,
|
|
71068
|
+
bbox: t.bbox,
|
|
71069
|
+
...t.nativeRecovery !== void 0 ? { nativeRecovery: t.nativeRecovery } : {},
|
|
71070
|
+
...evidenceCrop !== null ? { evidenceCrop } : {}
|
|
71071
|
+
};
|
|
71072
|
+
}), {
|
|
70938
71073
|
deviceId,
|
|
70939
71074
|
source,
|
|
70940
71075
|
frameHandle,
|
|
@@ -70958,6 +71093,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70958
71093
|
const deferredForMs = this.deferredBirths.elapsedMs(key, id, gateNowMs);
|
|
70959
71094
|
const deferredAttempts = this.deferredBirths.attemptsFor(key, id);
|
|
70960
71095
|
this.deferredBirths.resolve(key, id);
|
|
71096
|
+
this.birthEvidence.resolve(key, id);
|
|
70961
71097
|
if (this.startedBirths.hasStarted(key, id)) {
|
|
70962
71098
|
this.deferredBirths.resolve(key, id);
|
|
70963
71099
|
log.info("late birth verdict ignored — the track was already born", { meta: {
|
|
@@ -71075,6 +71211,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71075
71211
|
this.suppressedBirths.retain(key, currentTrackIds);
|
|
71076
71212
|
this.startedBirths.retain(key, currentTrackIds);
|
|
71077
71213
|
this.deferredBirths.retain(key, currentTrackIds);
|
|
71214
|
+
this.birthEvidence.retain(key, currentTrackIds);
|
|
71078
71215
|
this.lastActiveTrackIds.set(key, currentTrackIds);
|
|
71079
71216
|
if (currentTrackIds.size > 0) this.lastTrackActivityMs.set(deviceId, Date.now());
|
|
71080
71217
|
const stationarySettings = this.stationarySettingsFromCache(deviceId);
|
|
@@ -71704,6 +71841,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71704
71841
|
bestIncompatibleScore: decision.bestIncompatibleScore
|
|
71705
71842
|
} : {},
|
|
71706
71843
|
minConfidence: config.minConfidence,
|
|
71844
|
+
...decision.cropSource !== void 0 ? { cropSource: decision.cropSource } : {},
|
|
71707
71845
|
...decision.reason === "native-pass" ? {
|
|
71708
71846
|
nativeRecoveryCropSidePx: candidateNativeRecovery.get(decision.trackId)?.cropSidePx,
|
|
71709
71847
|
nativeRecoveryViewPx: candidateNativeRecovery.get(decision.trackId)?.viewPx
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-if_wK4QI.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-post-analysis",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.195",
|
|
4
4
|
"description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|