@camstack/addon-post-analysis 1.2.41 → 1.2.42
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-CrCaHNC2.mjs → dist-Bd7HioQT.mjs} +31 -6
- package/dist/{dist-C09kySqI.js → dist-CUnWKs0D.js} +36 -5
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-DL2FM8_E.js → node-ByXsH-Ce.js} +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Dt_25eeS.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DX4bQCYL.mjs} +2 -2
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DbdKLyhB.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Da6ggNiB.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-BfGl-Bnl.mjs → hostInit-BPLslJdB.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +341 -84
- package/dist/pipeline-analytics/index.mjs +340 -83
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as string, A as kebabToCamel, B as DeviceType, C as cosineSimilarity, D as encodeVectorBase64, F as subKindsOf, G as _enum, H as hydrateSchema, I as videoclipsCapability, J as boolean, K as _void, L as zoneAnalyticsCapability, M as pipelineAnalyticsCapability, N as plateGalleryCapability, O as faceGalleryCapability, P as readDeviceStateFrom, Q as record, R as errMsg, S as buildEventKindDescriptor, T as defineCustomActions, U as isDeviceScopedCap, V as createEvent, W as nodePin, X as number, Y as literal, Z as object, _ as TimelapseRuleInputSchema, a as MACRO_LABELS, b as alarmPanelCapability, c as NcConditionDescriptorSchema, d as NcRuleSchema, et as unknown, f as NcSnoozeInputSchema, g as OpsLogEntrySchema, h as NcTaxonomySchema, i as EVENT_PAD_MS, j as notificationRulesCapability, l as NcRuleInputSchema, m as NcSnoozeSuppressedSchema, n as DEFAULT_EVENT_COLOR, o as NC_CONDITION_CATALOG, p as NcSnoozeSchema, q as array, r as EVENT_KIND_BY_CAP, s as NC_TAXONOMY, t as BaseDevice, tt as EventCategory, u as NcRulePatchSchema, v as TimelapseRuleSchema, w as customAction, x as audioMetricsCapability, y as addonWidgetsSourceCapability, z as BaseAddon } from "../dist-Bd7HioQT.mjs";
|
|
2
2
|
import { promises } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -5815,6 +5815,66 @@ function makeNcActionHandlers(deps) {
|
|
|
5815
5815
|
};
|
|
5816
5816
|
}
|
|
5817
5817
|
//#endregion
|
|
5818
|
+
//#region src/pipeline-analytics/embedding-actions.ts
|
|
5819
|
+
/**
|
|
5820
|
+
* embedding-actions — operator buttons for the CLIP semantic-search index.
|
|
5821
|
+
*
|
|
5822
|
+
* These ride the generic `addons.custom` bridge rather than adding cap methods,
|
|
5823
|
+
* because the settings form already knows how to render an
|
|
5824
|
+
* `addon-action-button` and call it. The three operations they expose are the
|
|
5825
|
+
* ones an operator needs when the index and the world disagree:
|
|
5826
|
+
*
|
|
5827
|
+
* - **Rebuild** re-embeds tracks from their key frames. It is the only way to
|
|
5828
|
+
* change what a vector MEANS — crop margin, squaring, encoder model — since
|
|
5829
|
+
* mixing feature spaces in one index makes cosine scores incomparable.
|
|
5830
|
+
* - **Status** is what makes rebuild usable at all: a pass is ~1s per track,
|
|
5831
|
+
* 45 minutes for a fleet, so it runs detached and this is how progress is
|
|
5832
|
+
* seen.
|
|
5833
|
+
* - **Wipe** empties the index, for the clean slate before a policy change.
|
|
5834
|
+
*
|
|
5835
|
+
* Wipe is `danger` and carries a confirm prompt. It has already destroyed a
|
|
5836
|
+
* live index once when it was called casually — the button must not make that
|
|
5837
|
+
* easier than it needs to be.
|
|
5838
|
+
*/
|
|
5839
|
+
var RebuildInputSchema = object({
|
|
5840
|
+
/** Restrict to one camera; omit for the whole fleet. */
|
|
5841
|
+
deviceId: number().optional(),
|
|
5842
|
+
/**
|
|
5843
|
+
* Fraction added on EACH side of the detection box before cropping.
|
|
5844
|
+
* ~0.2 (a 1.4x window) gives CLIP the surroundings it is trained on; 0 is
|
|
5845
|
+
* the pixel-tight crop.
|
|
5846
|
+
*/
|
|
5847
|
+
cropMargin: number().min(0).max(4).optional(),
|
|
5848
|
+
/** Square the window so a tall subject is not squashed into CLIP's input. */
|
|
5849
|
+
square: boolean().optional(),
|
|
5850
|
+
/** Stop after this many tracks; the status reports whether more remain. */
|
|
5851
|
+
maxTracks: number().int().positive().optional()
|
|
5852
|
+
});
|
|
5853
|
+
var RebuildStartedSchema = object({
|
|
5854
|
+
started: boolean(),
|
|
5855
|
+
alreadyRunning: boolean()
|
|
5856
|
+
});
|
|
5857
|
+
var RebuildStatusSchema = object({
|
|
5858
|
+
running: boolean(),
|
|
5859
|
+
scanned: number(),
|
|
5860
|
+
rebuilt: number(),
|
|
5861
|
+
missingKeyFrame: number(),
|
|
5862
|
+
missingBbox: number(),
|
|
5863
|
+
failed: number(),
|
|
5864
|
+
complete: boolean().nullable(),
|
|
5865
|
+
startedAtMs: number().nullable(),
|
|
5866
|
+
finishedAtMs: number().nullable(),
|
|
5867
|
+
error: string().nullable(),
|
|
5868
|
+
/** Rows currently in the index — the number the operator is watching. */
|
|
5869
|
+
indexed: number()
|
|
5870
|
+
});
|
|
5871
|
+
var WipeResultSchema = object({ deleted: number() });
|
|
5872
|
+
var embeddingActions = defineCustomActions({
|
|
5873
|
+
"embedding.rebuild": customAction(RebuildInputSchema, RebuildStartedSchema, { auth: "admin" }),
|
|
5874
|
+
"embedding.rebuildStatus": customAction(_void(), RebuildStatusSchema),
|
|
5875
|
+
"embedding.wipe": customAction(_void(), WipeResultSchema, { auth: "admin" })
|
|
5876
|
+
});
|
|
5877
|
+
//#endregion
|
|
5818
5878
|
//#region src/pipeline-analytics/pipeline/face-clustering.ts
|
|
5819
5879
|
/**
|
|
5820
5880
|
* face-clustering — pure greedy clustering of face embeddings by cosine
|
|
@@ -12041,57 +12101,84 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
12041
12101
|
const margin = options.cropMargin ?? .2;
|
|
12042
12102
|
const square = options.square ?? true;
|
|
12043
12103
|
const maxTracks = options.maxTracks ?? Number.POSITIVE_INFINITY;
|
|
12044
|
-
const trackIds = await deps.listTrackIds();
|
|
12045
|
-
const considered = trackIds.slice(0, Number.isFinite(maxTracks) ? maxTracks : void 0);
|
|
12046
12104
|
let rebuilt = 0;
|
|
12047
12105
|
let missingKeyFrame = 0;
|
|
12048
12106
|
let missingBbox = 0;
|
|
12049
12107
|
let failed = 0;
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
if (
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
}
|
|
12060
|
-
if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
|
|
12061
|
-
missingBbox += 1;
|
|
12062
|
-
continue;
|
|
12063
|
-
}
|
|
12064
|
-
const meta = await sharp(source.keyFrame).metadata();
|
|
12065
|
-
if (meta.width === void 0 || meta.height === void 0) {
|
|
12066
|
-
failed += 1;
|
|
12067
|
-
continue;
|
|
12108
|
+
let scanned = 0;
|
|
12109
|
+
let cursor = null;
|
|
12110
|
+
let exhausted = false;
|
|
12111
|
+
let budgetExhausted = false;
|
|
12112
|
+
while (scanned < maxTracks) {
|
|
12113
|
+
const page = await deps.listTrackIdPage(cursor);
|
|
12114
|
+
if (page.trackIds.length === 0) {
|
|
12115
|
+
exhausted = true;
|
|
12116
|
+
break;
|
|
12068
12117
|
}
|
|
12069
|
-
const
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12118
|
+
const room = maxTracks - scanned;
|
|
12119
|
+
const batch = page.trackIds.slice(0, Number.isFinite(room) ? room : void 0);
|
|
12120
|
+
if (batch.length < page.trackIds.length) budgetExhausted = true;
|
|
12121
|
+
for (const trackId of batch) {
|
|
12122
|
+
scanned += 1;
|
|
12123
|
+
try {
|
|
12124
|
+
const source = await deps.loadSource(trackId);
|
|
12125
|
+
if (source === null) {
|
|
12126
|
+
missingKeyFrame += 1;
|
|
12127
|
+
continue;
|
|
12128
|
+
}
|
|
12129
|
+
if (source.keyFrame === null) {
|
|
12130
|
+
missingKeyFrame += 1;
|
|
12131
|
+
continue;
|
|
12132
|
+
}
|
|
12133
|
+
if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
|
|
12134
|
+
missingBbox += 1;
|
|
12135
|
+
continue;
|
|
12136
|
+
}
|
|
12137
|
+
const meta = await sharp(source.keyFrame).metadata();
|
|
12138
|
+
if (meta.width === void 0 || meta.height === void 0) {
|
|
12139
|
+
failed += 1;
|
|
12140
|
+
continue;
|
|
12141
|
+
}
|
|
12142
|
+
const window = cropWindow(source.bbox, meta.width, meta.height, {
|
|
12143
|
+
margin,
|
|
12144
|
+
square
|
|
12145
|
+
});
|
|
12146
|
+
const cut = await cropFromJpeg(source.keyFrame, window);
|
|
12147
|
+
if (cut === null) {
|
|
12148
|
+
failed += 1;
|
|
12149
|
+
continue;
|
|
12150
|
+
}
|
|
12151
|
+
const embedding = await deps.encode(cut.crop, cut.width, cut.height);
|
|
12152
|
+
if (embedding.length === 0) {
|
|
12153
|
+
failed += 1;
|
|
12154
|
+
continue;
|
|
12155
|
+
}
|
|
12156
|
+
await deps.store(source, embedding);
|
|
12157
|
+
rebuilt += 1;
|
|
12158
|
+
} catch (err) {
|
|
12159
|
+
failed += 1;
|
|
12160
|
+
deps.logger.warn("embedding rebuild failed for a track", { meta: {
|
|
12161
|
+
trackId,
|
|
12162
|
+
error: String(err)
|
|
12163
|
+
} });
|
|
12164
|
+
}
|
|
12165
|
+
if (scanned % 25 === 0) options.onProgress?.({
|
|
12166
|
+
scanned,
|
|
12167
|
+
rebuilt,
|
|
12168
|
+
missingKeyFrame,
|
|
12169
|
+
missingBbox,
|
|
12170
|
+
failed
|
|
12171
|
+
});
|
|
12077
12172
|
}
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
continue;
|
|
12173
|
+
if (page.nextCursor === null) {
|
|
12174
|
+
exhausted = true;
|
|
12175
|
+
break;
|
|
12082
12176
|
}
|
|
12083
|
-
|
|
12084
|
-
rebuilt += 1;
|
|
12085
|
-
} catch (err) {
|
|
12086
|
-
failed += 1;
|
|
12087
|
-
deps.logger.warn("embedding rebuild failed for a track", { meta: {
|
|
12088
|
-
trackId,
|
|
12089
|
-
error: String(err)
|
|
12090
|
-
} });
|
|
12177
|
+
cursor = page.nextCursor;
|
|
12091
12178
|
}
|
|
12092
|
-
const complete =
|
|
12179
|
+
const complete = exhausted && !budgetExhausted;
|
|
12093
12180
|
deps.logger.info("object embedding rebuild finished", { meta: {
|
|
12094
|
-
scanned
|
|
12181
|
+
scanned,
|
|
12095
12182
|
rebuilt,
|
|
12096
12183
|
missingKeyFrame,
|
|
12097
12184
|
missingBbox,
|
|
@@ -12101,7 +12188,7 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
12101
12188
|
square
|
|
12102
12189
|
} });
|
|
12103
12190
|
return {
|
|
12104
|
-
scanned
|
|
12191
|
+
scanned,
|
|
12105
12192
|
rebuilt,
|
|
12106
12193
|
missingKeyFrame,
|
|
12107
12194
|
missingBbox,
|
|
@@ -17524,26 +17611,6 @@ var SyntheticSensorTrackMaterializer = class {
|
|
|
17524
17611
|
return track;
|
|
17525
17612
|
}
|
|
17526
17613
|
};
|
|
17527
|
-
var TRACK_RETENTION_DEFAULT_DAYS = object({ trackRetentionDays: number().min(0).default(7) }).parse({}).trackRetentionDays;
|
|
17528
|
-
/**
|
|
17529
|
-
* Sweep every device with persisted tracks: skip retention-disabled devices,
|
|
17530
|
-
* prune the rest at `now − retentionMs`. Per-device ISOLATED — one bad device
|
|
17531
|
-
* never aborts the sweep. Returns the total tracks pruned across devices.
|
|
17532
|
-
*/
|
|
17533
|
-
async function sweepTrackRetention(deps) {
|
|
17534
|
-
const devices = await deps.listDeviceIds();
|
|
17535
|
-
const nowMs = deps.now();
|
|
17536
|
-
let totalTracks = 0;
|
|
17537
|
-
for (const deviceId of devices) try {
|
|
17538
|
-
const cutoffMs = await deps.resolveCutoffMs(deviceId, nowMs);
|
|
17539
|
-
if (cutoffMs === null) continue;
|
|
17540
|
-
const counts = await deps.pruneTracksBefore(deviceId, cutoffMs);
|
|
17541
|
-
totalTracks += counts.tracks;
|
|
17542
|
-
} catch (err) {
|
|
17543
|
-
deps.onError?.(deviceId, err);
|
|
17544
|
-
}
|
|
17545
|
-
return totalTracks;
|
|
17546
|
-
}
|
|
17547
17614
|
var TrackingSettingsSchema = object({
|
|
17548
17615
|
/**
|
|
17549
17616
|
* How much of a detection's box must lie inside a zone (0-1 fraction of the
|
|
@@ -18339,6 +18406,26 @@ function buildDetectionSettingsSections() {
|
|
|
18339
18406
|
}
|
|
18340
18407
|
];
|
|
18341
18408
|
}
|
|
18409
|
+
var TRACK_RETENTION_DEFAULT_DAYS = object({ trackRetentionDays: number().min(0).default(7) }).parse({}).trackRetentionDays;
|
|
18410
|
+
/**
|
|
18411
|
+
* Sweep every device with persisted tracks: skip retention-disabled devices,
|
|
18412
|
+
* prune the rest at `now − retentionMs`. Per-device ISOLATED — one bad device
|
|
18413
|
+
* never aborts the sweep. Returns the total tracks pruned across devices.
|
|
18414
|
+
*/
|
|
18415
|
+
async function sweepTrackRetention(deps) {
|
|
18416
|
+
const devices = await deps.listDeviceIds();
|
|
18417
|
+
const nowMs = deps.now();
|
|
18418
|
+
let totalTracks = 0;
|
|
18419
|
+
for (const deviceId of devices) try {
|
|
18420
|
+
const cutoffMs = await deps.resolveCutoffMs(deviceId, nowMs);
|
|
18421
|
+
if (cutoffMs === null) continue;
|
|
18422
|
+
const counts = await deps.pruneTracksBefore(deviceId, cutoffMs);
|
|
18423
|
+
totalTracks += counts.tracks;
|
|
18424
|
+
} catch (err) {
|
|
18425
|
+
deps.onError?.(deviceId, err);
|
|
18426
|
+
}
|
|
18427
|
+
return totalTracks;
|
|
18428
|
+
}
|
|
18342
18429
|
/**
|
|
18343
18430
|
* Re-home the global analytics sections into the per-device `Analytics`
|
|
18344
18431
|
* top-tab. Every section defaults to the `Analytics` tab (so the
|
|
@@ -18449,6 +18536,59 @@ function buildGlobalSettingsSchema() {
|
|
|
18449
18536
|
default: true
|
|
18450
18537
|
}]
|
|
18451
18538
|
},
|
|
18539
|
+
{
|
|
18540
|
+
id: "embedding-index",
|
|
18541
|
+
title: "Semantic search index",
|
|
18542
|
+
description: "The CLIP vector index behind text search. Rebuild re-embeds stored tracks from their native key frames — the only way to change what a vector means, since a new crop margin or encoder model leaves two incomparable feature spaces in one index. Vectors live in the vector-store capability, alongside the events they are shown with.",
|
|
18543
|
+
columns: 1,
|
|
18544
|
+
fields: [
|
|
18545
|
+
{
|
|
18546
|
+
type: "number",
|
|
18547
|
+
key: "rebuildCropMargin",
|
|
18548
|
+
label: "Rebuild crop margin",
|
|
18549
|
+
description: "Fraction added on EACH side of the detection box when re-embedding. 0.2 gives a 1.4x window: CLIP is trained on natural images WITH surroundings, and a pixel-tight crop removes what it is strongest on. 0 reproduces the tight crop. Only affects Rebuild — it does not change live capture, and the thumbnail is never re-cropped.",
|
|
18550
|
+
default: .2,
|
|
18551
|
+
min: 0,
|
|
18552
|
+
max: 2,
|
|
18553
|
+
step: .1
|
|
18554
|
+
},
|
|
18555
|
+
{
|
|
18556
|
+
type: "addon-action-button",
|
|
18557
|
+
key: "rebuildEmbeddingsButton",
|
|
18558
|
+
label: "Rebuild index",
|
|
18559
|
+
description: "Re-embed every stored track from its key frame, using the margin above. Runs in the background — press Progress to watch it. Tracks whose key frame has aged out cannot be rebuilt and are reported as skipped.",
|
|
18560
|
+
addonId: "pipeline-analytics",
|
|
18561
|
+
action: "embedding.rebuild",
|
|
18562
|
+
buttonLabel: "Rebuild",
|
|
18563
|
+
variant: "primary",
|
|
18564
|
+
paramsFromForm: { cropMargin: "rebuildCropMargin" },
|
|
18565
|
+
paramsStatic: { square: true },
|
|
18566
|
+
successMessage: "Rebuild started — press Progress to follow it."
|
|
18567
|
+
},
|
|
18568
|
+
{
|
|
18569
|
+
type: "addon-action-button",
|
|
18570
|
+
key: "rebuildStatusButton",
|
|
18571
|
+
label: "Progress",
|
|
18572
|
+
description: "Rows currently indexed, plus the running pass: scanned, rebuilt, and what it had to skip.",
|
|
18573
|
+
addonId: "pipeline-analytics",
|
|
18574
|
+
action: "embedding.rebuildStatus",
|
|
18575
|
+
buttonLabel: "Check progress",
|
|
18576
|
+
successMessage: "Indexed {indexed} · scanned {scanned} · rebuilt {rebuilt} · no key frame {missingKeyFrame} · failed {failed}"
|
|
18577
|
+
},
|
|
18578
|
+
{
|
|
18579
|
+
type: "addon-action-button",
|
|
18580
|
+
key: "wipeEmbeddingsButton",
|
|
18581
|
+
label: "Empty the index",
|
|
18582
|
+
description: "Deletes every vector. Search returns nothing until a rebuild repopulates it, and only tracks still inside the retention window can be rebuilt — anything older is gone for good.",
|
|
18583
|
+
addonId: "pipeline-analytics",
|
|
18584
|
+
action: "embedding.wipe",
|
|
18585
|
+
buttonLabel: "Empty index",
|
|
18586
|
+
variant: "danger",
|
|
18587
|
+
confirmMessage: "Delete every semantic-search vector? Search stops working until a rebuild finishes, and vectors whose tracks have aged out cannot be recovered.",
|
|
18588
|
+
successMessage: "Removed {deleted} vectors."
|
|
18589
|
+
}
|
|
18590
|
+
]
|
|
18591
|
+
},
|
|
18452
18592
|
{
|
|
18453
18593
|
id: "tracking-core",
|
|
18454
18594
|
title: "Tracking",
|
|
@@ -22943,10 +23083,30 @@ var ENCODER_INFO_TTL_MS = 6e4;
|
|
|
22943
23083
|
var EMBEDDING_ORPHAN_SCAN_LIMIT = 2e4;
|
|
22944
23084
|
/** Dimension of the zero probe vector used to enumerate the index. */
|
|
22945
23085
|
var EMBEDDING_ORPHAN_PROBE_DIM = 512;
|
|
23086
|
+
/**
|
|
23087
|
+
* Tracks fetched per page while rebuilding.
|
|
23088
|
+
*
|
|
23089
|
+
* The page size is NOT the caller's `maxTracks`: that bounds the whole pass,
|
|
23090
|
+
* this bounds one round trip. Conflating them is what made the rebuild
|
|
23091
|
+
* re-embed the same newest N tracks on every call.
|
|
23092
|
+
*/
|
|
23093
|
+
var REBUILD_PAGE_SIZE = 200;
|
|
22946
23094
|
var PIPELINE_ANALYTICS_ADDON_ID = "pipeline-analytics";
|
|
22947
23095
|
/** Operator-visible name of the fixed integration. */
|
|
22948
23096
|
var NC_INTEGRATION_NAME = "Notification Center";
|
|
22949
23097
|
/**
|
|
23098
|
+
* The FULL action catalog for this addon.
|
|
23099
|
+
*
|
|
23100
|
+
* Must contain every action, not just the notification-center ones: the hub
|
|
23101
|
+
* harvests THIS named export, so an action registered only in what
|
|
23102
|
+
* `onInitialize` returns is invisible to the bridge and answers 404. That is
|
|
23103
|
+
* exactly what the embedding buttons did on their first deploy.
|
|
23104
|
+
*/
|
|
23105
|
+
var customActions = {
|
|
23106
|
+
...ncActions,
|
|
23107
|
+
...embeddingActions
|
|
23108
|
+
};
|
|
23109
|
+
/**
|
|
22950
23110
|
* Pipeline Analytics addon — subscribes to `PipelineInferenceResult` +
|
|
22951
23111
|
* `PipelineAudioInferenceResult` on the bus, refines raw detections
|
|
22952
23112
|
* into tracks + per-kind events + persisted media, and emits canonical
|
|
@@ -23130,6 +23290,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
23130
23290
|
plateStore = null;
|
|
23131
23291
|
plateRecognizer = null;
|
|
23132
23292
|
objectEmbeddingStore = null;
|
|
23293
|
+
/** Live state of the detached embedding rebuild — see `rebuildObjectEmbeddings`. */
|
|
23294
|
+
rebuildState = { running: false };
|
|
23133
23295
|
/** Cached active-encoder model id — see `resolveEncoderModelId`. */
|
|
23134
23296
|
encoderInfoCache = null;
|
|
23135
23297
|
/** Two-plane detail scheduler/dispatcher: per-track on-demand face/clip/
|
|
@@ -23604,8 +23766,14 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
23604
23766
|
return {
|
|
23605
23767
|
providers,
|
|
23606
23768
|
...isHub && ncHandlers !== void 0 ? {
|
|
23607
|
-
customActions:
|
|
23608
|
-
|
|
23769
|
+
customActions: {
|
|
23770
|
+
...ncActions,
|
|
23771
|
+
...embeddingActions
|
|
23772
|
+
},
|
|
23773
|
+
actionHandlers: {
|
|
23774
|
+
...ncHandlers,
|
|
23775
|
+
...this.buildEmbeddingActionHandlers()
|
|
23776
|
+
}
|
|
23609
23777
|
} : {}
|
|
23610
23778
|
};
|
|
23611
23779
|
}
|
|
@@ -26858,33 +27026,40 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
26858
27026
|
* widened after the fact.
|
|
26859
27027
|
*/
|
|
26860
27028
|
async rebuildObjectEmbeddings(input) {
|
|
27029
|
+
if (this.rebuildState.running) {
|
|
27030
|
+
this.ctx.logger.info("object embedding rebuild already running — request ignored");
|
|
27031
|
+
return {
|
|
27032
|
+
started: false,
|
|
27033
|
+
alreadyRunning: true
|
|
27034
|
+
};
|
|
27035
|
+
}
|
|
26861
27036
|
const store = this.objectEmbeddingStore;
|
|
26862
27037
|
const trackStore = this.trackStore;
|
|
26863
27038
|
const mediaStore = this.mediaStore;
|
|
26864
27039
|
const eventStore = this.eventStore;
|
|
26865
27040
|
const api = this.ctx.api;
|
|
26866
|
-
const empty = {
|
|
26867
|
-
scanned: 0,
|
|
26868
|
-
rebuilt: 0,
|
|
26869
|
-
missingKeyFrame: 0,
|
|
26870
|
-
missingBbox: 0,
|
|
26871
|
-
failed: 0,
|
|
26872
|
-
complete: true
|
|
26873
|
-
};
|
|
26874
27041
|
if (!store || !trackStore || !mediaStore || !eventStore || !api) {
|
|
26875
27042
|
this.ctx.logger.warn("rebuildObjectEmbeddings: analytics stores unavailable");
|
|
26876
|
-
return
|
|
27043
|
+
return {
|
|
27044
|
+
started: false,
|
|
27045
|
+
alreadyRunning: false
|
|
27046
|
+
};
|
|
26877
27047
|
}
|
|
26878
27048
|
const modelId = await this.resolveEncoderModelId(api) ?? "unknown";
|
|
26879
27049
|
const deviceIds = input.deviceId !== void 0 ? [input.deviceId] : await trackStore.listDeviceIds();
|
|
26880
|
-
|
|
26881
|
-
|
|
26882
|
-
|
|
27050
|
+
const deps = {
|
|
27051
|
+
listTrackIdPage: async (cursor) => {
|
|
27052
|
+
const page = await trackStore.queryRecent({
|
|
26883
27053
|
deviceIds,
|
|
26884
27054
|
...input.since !== void 0 ? { since: input.since } : {},
|
|
26885
27055
|
...input.until !== void 0 ? { until: input.until } : {},
|
|
26886
|
-
|
|
26887
|
-
|
|
27056
|
+
...cursor !== null ? { cursor } : {},
|
|
27057
|
+
limit: REBUILD_PAGE_SIZE
|
|
27058
|
+
});
|
|
27059
|
+
return {
|
|
27060
|
+
trackIds: page.tracks.map((t) => t.trackId),
|
|
27061
|
+
nextCursor: page.nextCursor
|
|
27062
|
+
};
|
|
26888
27063
|
},
|
|
26889
27064
|
loadSource: async (trackId) => {
|
|
26890
27065
|
const media = await mediaStore.listByOwner("track", trackId);
|
|
@@ -26930,11 +27105,93 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
26930
27105
|
info: (message, extras) => this.ctx.logger.info(message, extras),
|
|
26931
27106
|
warn: (message, extras) => this.ctx.logger.warn(message, extras)
|
|
26932
27107
|
}
|
|
26933
|
-
}
|
|
27108
|
+
};
|
|
27109
|
+
this.rebuildState = {
|
|
27110
|
+
running: true,
|
|
27111
|
+
startedAtMs: Date.now(),
|
|
27112
|
+
progress: {
|
|
27113
|
+
scanned: 0,
|
|
27114
|
+
rebuilt: 0,
|
|
27115
|
+
missingKeyFrame: 0,
|
|
27116
|
+
missingBbox: 0,
|
|
27117
|
+
failed: 0
|
|
27118
|
+
}
|
|
27119
|
+
};
|
|
27120
|
+
rebuildObjectEmbeddings(deps, {
|
|
26934
27121
|
...input.cropMargin !== void 0 ? { cropMargin: input.cropMargin } : {},
|
|
26935
27122
|
...input.square !== void 0 ? { square: input.square } : {},
|
|
26936
|
-
...input.maxTracks !== void 0 ? { maxTracks: input.maxTracks } : {}
|
|
27123
|
+
...input.maxTracks !== void 0 ? { maxTracks: input.maxTracks } : {},
|
|
27124
|
+
onProgress: (progress) => {
|
|
27125
|
+
if (this.rebuildState.running) this.rebuildState.progress = progress;
|
|
27126
|
+
this.ctx.logger.info("object embedding rebuild progress", { meta: { ...progress } });
|
|
27127
|
+
}
|
|
27128
|
+
}).then((outcome) => {
|
|
27129
|
+
this.rebuildState = {
|
|
27130
|
+
running: false,
|
|
27131
|
+
startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
|
|
27132
|
+
finishedAtMs: Date.now(),
|
|
27133
|
+
progress: outcome,
|
|
27134
|
+
complete: outcome.complete
|
|
27135
|
+
};
|
|
27136
|
+
}).catch((err) => {
|
|
27137
|
+
this.rebuildState = {
|
|
27138
|
+
running: false,
|
|
27139
|
+
startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
|
|
27140
|
+
finishedAtMs: Date.now(),
|
|
27141
|
+
error: String(err)
|
|
27142
|
+
};
|
|
27143
|
+
this.ctx.logger.warn("object embedding rebuild failed", { meta: { error: String(err) } });
|
|
26937
27144
|
});
|
|
27145
|
+
this.ctx.logger.info("object embedding rebuild started", { meta: {
|
|
27146
|
+
deviceId: input.deviceId ?? "all",
|
|
27147
|
+
cropMargin: input.cropMargin ?? "default",
|
|
27148
|
+
maxTracks: input.maxTracks ?? "unbounded"
|
|
27149
|
+
} });
|
|
27150
|
+
return {
|
|
27151
|
+
started: true,
|
|
27152
|
+
alreadyRunning: false
|
|
27153
|
+
};
|
|
27154
|
+
}
|
|
27155
|
+
/**
|
|
27156
|
+
* Handlers behind the embedding buttons in the settings form.
|
|
27157
|
+
*
|
|
27158
|
+
* They are thin wrappers over the cap methods on purpose: the button and the
|
|
27159
|
+
* API must not be able to drift into two behaviours.
|
|
27160
|
+
*/
|
|
27161
|
+
buildEmbeddingActionHandlers() {
|
|
27162
|
+
return {
|
|
27163
|
+
"embedding.rebuild": async (input) => this.rebuildObjectEmbeddings(input),
|
|
27164
|
+
"embedding.rebuildStatus": async () => {
|
|
27165
|
+
const status = await this.getObjectEmbeddingRebuildStatus();
|
|
27166
|
+
let indexed = 0;
|
|
27167
|
+
try {
|
|
27168
|
+
const api = this.ctx.api;
|
|
27169
|
+
if (api) indexed = (await api.vectorStore.stats.query({ index: OBJECT_CLIP_INDEX })).count;
|
|
27170
|
+
} catch {}
|
|
27171
|
+
return {
|
|
27172
|
+
...status,
|
|
27173
|
+
indexed
|
|
27174
|
+
};
|
|
27175
|
+
},
|
|
27176
|
+
"embedding.wipe": async () => this.wipeObjectEmbeddings()
|
|
27177
|
+
};
|
|
27178
|
+
}
|
|
27179
|
+
/** Progress of the detached rebuild — the only way to observe a long pass. */
|
|
27180
|
+
async getObjectEmbeddingRebuildStatus() {
|
|
27181
|
+
const state = this.rebuildState;
|
|
27182
|
+
const p = state.progress;
|
|
27183
|
+
return {
|
|
27184
|
+
running: state.running,
|
|
27185
|
+
scanned: p?.scanned ?? 0,
|
|
27186
|
+
rebuilt: p?.rebuilt ?? 0,
|
|
27187
|
+
missingKeyFrame: p?.missingKeyFrame ?? 0,
|
|
27188
|
+
missingBbox: p?.missingBbox ?? 0,
|
|
27189
|
+
failed: p?.failed ?? 0,
|
|
27190
|
+
complete: state.complete ?? null,
|
|
27191
|
+
startedAtMs: state.startedAtMs ?? null,
|
|
27192
|
+
finishedAtMs: state.finishedAtMs ?? null,
|
|
27193
|
+
error: state.error ?? null
|
|
27194
|
+
};
|
|
26938
27195
|
}
|
|
26939
27196
|
async searchObjectEvents(input) {
|
|
26940
27197
|
const store = this.objectEmbeddingStore;
|
|
@@ -27347,4 +27604,4 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
27347
27604
|
}
|
|
27348
27605
|
};
|
|
27349
27606
|
//#endregion
|
|
27350
|
-
export { DETECTION_PIPELINE_SECTION_IDS,
|
|
27607
|
+
export { DETECTION_PIPELINE_SECTION_IDS, customActions, PipelineAnalyticsAddon as default, ncActions, pickCleanMedia, retagDetectionSections, retagRetentionSection, stripGlobalOnlyFields, toAnalyticsDeviceSections };
|
|
@@ -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-DX4bQCYL.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.42",
|
|
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",
|