@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
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-CUnWKs0D.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_dist.__toESM(node_path);
|
|
@@ -5821,6 +5821,66 @@ function makeNcActionHandlers(deps) {
|
|
|
5821
5821
|
};
|
|
5822
5822
|
}
|
|
5823
5823
|
//#endregion
|
|
5824
|
+
//#region src/pipeline-analytics/embedding-actions.ts
|
|
5825
|
+
/**
|
|
5826
|
+
* embedding-actions — operator buttons for the CLIP semantic-search index.
|
|
5827
|
+
*
|
|
5828
|
+
* These ride the generic `addons.custom` bridge rather than adding cap methods,
|
|
5829
|
+
* because the settings form already knows how to render an
|
|
5830
|
+
* `addon-action-button` and call it. The three operations they expose are the
|
|
5831
|
+
* ones an operator needs when the index and the world disagree:
|
|
5832
|
+
*
|
|
5833
|
+
* - **Rebuild** re-embeds tracks from their key frames. It is the only way to
|
|
5834
|
+
* change what a vector MEANS — crop margin, squaring, encoder model — since
|
|
5835
|
+
* mixing feature spaces in one index makes cosine scores incomparable.
|
|
5836
|
+
* - **Status** is what makes rebuild usable at all: a pass is ~1s per track,
|
|
5837
|
+
* 45 minutes for a fleet, so it runs detached and this is how progress is
|
|
5838
|
+
* seen.
|
|
5839
|
+
* - **Wipe** empties the index, for the clean slate before a policy change.
|
|
5840
|
+
*
|
|
5841
|
+
* Wipe is `danger` and carries a confirm prompt. It has already destroyed a
|
|
5842
|
+
* live index once when it was called casually — the button must not make that
|
|
5843
|
+
* easier than it needs to be.
|
|
5844
|
+
*/
|
|
5845
|
+
var RebuildInputSchema = require_dist.object({
|
|
5846
|
+
/** Restrict to one camera; omit for the whole fleet. */
|
|
5847
|
+
deviceId: require_dist.number().optional(),
|
|
5848
|
+
/**
|
|
5849
|
+
* Fraction added on EACH side of the detection box before cropping.
|
|
5850
|
+
* ~0.2 (a 1.4x window) gives CLIP the surroundings it is trained on; 0 is
|
|
5851
|
+
* the pixel-tight crop.
|
|
5852
|
+
*/
|
|
5853
|
+
cropMargin: require_dist.number().min(0).max(4).optional(),
|
|
5854
|
+
/** Square the window so a tall subject is not squashed into CLIP's input. */
|
|
5855
|
+
square: require_dist.boolean().optional(),
|
|
5856
|
+
/** Stop after this many tracks; the status reports whether more remain. */
|
|
5857
|
+
maxTracks: require_dist.number().int().positive().optional()
|
|
5858
|
+
});
|
|
5859
|
+
var RebuildStartedSchema = require_dist.object({
|
|
5860
|
+
started: require_dist.boolean(),
|
|
5861
|
+
alreadyRunning: require_dist.boolean()
|
|
5862
|
+
});
|
|
5863
|
+
var RebuildStatusSchema = require_dist.object({
|
|
5864
|
+
running: require_dist.boolean(),
|
|
5865
|
+
scanned: require_dist.number(),
|
|
5866
|
+
rebuilt: require_dist.number(),
|
|
5867
|
+
missingKeyFrame: require_dist.number(),
|
|
5868
|
+
missingBbox: require_dist.number(),
|
|
5869
|
+
failed: require_dist.number(),
|
|
5870
|
+
complete: require_dist.boolean().nullable(),
|
|
5871
|
+
startedAtMs: require_dist.number().nullable(),
|
|
5872
|
+
finishedAtMs: require_dist.number().nullable(),
|
|
5873
|
+
error: require_dist.string().nullable(),
|
|
5874
|
+
/** Rows currently in the index — the number the operator is watching. */
|
|
5875
|
+
indexed: require_dist.number()
|
|
5876
|
+
});
|
|
5877
|
+
var WipeResultSchema = require_dist.object({ deleted: require_dist.number() });
|
|
5878
|
+
var embeddingActions = require_dist.defineCustomActions({
|
|
5879
|
+
"embedding.rebuild": require_dist.customAction(RebuildInputSchema, RebuildStartedSchema, { auth: "admin" }),
|
|
5880
|
+
"embedding.rebuildStatus": require_dist.customAction(require_dist._void(), RebuildStatusSchema),
|
|
5881
|
+
"embedding.wipe": require_dist.customAction(require_dist._void(), WipeResultSchema, { auth: "admin" })
|
|
5882
|
+
});
|
|
5883
|
+
//#endregion
|
|
5824
5884
|
//#region src/pipeline-analytics/pipeline/face-clustering.ts
|
|
5825
5885
|
/**
|
|
5826
5886
|
* face-clustering — pure greedy clustering of face embeddings by cosine
|
|
@@ -12047,57 +12107,84 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
12047
12107
|
const margin = options.cropMargin ?? .2;
|
|
12048
12108
|
const square = options.square ?? true;
|
|
12049
12109
|
const maxTracks = options.maxTracks ?? Number.POSITIVE_INFINITY;
|
|
12050
|
-
const trackIds = await deps.listTrackIds();
|
|
12051
|
-
const considered = trackIds.slice(0, Number.isFinite(maxTracks) ? maxTracks : void 0);
|
|
12052
12110
|
let rebuilt = 0;
|
|
12053
12111
|
let missingKeyFrame = 0;
|
|
12054
12112
|
let missingBbox = 0;
|
|
12055
12113
|
let failed = 0;
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
if (
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
}
|
|
12066
|
-
if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
|
|
12067
|
-
missingBbox += 1;
|
|
12068
|
-
continue;
|
|
12069
|
-
}
|
|
12070
|
-
const meta = await (0, sharp.default)(source.keyFrame).metadata();
|
|
12071
|
-
if (meta.width === void 0 || meta.height === void 0) {
|
|
12072
|
-
failed += 1;
|
|
12073
|
-
continue;
|
|
12114
|
+
let scanned = 0;
|
|
12115
|
+
let cursor = null;
|
|
12116
|
+
let exhausted = false;
|
|
12117
|
+
let budgetExhausted = false;
|
|
12118
|
+
while (scanned < maxTracks) {
|
|
12119
|
+
const page = await deps.listTrackIdPage(cursor);
|
|
12120
|
+
if (page.trackIds.length === 0) {
|
|
12121
|
+
exhausted = true;
|
|
12122
|
+
break;
|
|
12074
12123
|
}
|
|
12075
|
-
const
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12124
|
+
const room = maxTracks - scanned;
|
|
12125
|
+
const batch = page.trackIds.slice(0, Number.isFinite(room) ? room : void 0);
|
|
12126
|
+
if (batch.length < page.trackIds.length) budgetExhausted = true;
|
|
12127
|
+
for (const trackId of batch) {
|
|
12128
|
+
scanned += 1;
|
|
12129
|
+
try {
|
|
12130
|
+
const source = await deps.loadSource(trackId);
|
|
12131
|
+
if (source === null) {
|
|
12132
|
+
missingKeyFrame += 1;
|
|
12133
|
+
continue;
|
|
12134
|
+
}
|
|
12135
|
+
if (source.keyFrame === null) {
|
|
12136
|
+
missingKeyFrame += 1;
|
|
12137
|
+
continue;
|
|
12138
|
+
}
|
|
12139
|
+
if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
|
|
12140
|
+
missingBbox += 1;
|
|
12141
|
+
continue;
|
|
12142
|
+
}
|
|
12143
|
+
const meta = await (0, sharp.default)(source.keyFrame).metadata();
|
|
12144
|
+
if (meta.width === void 0 || meta.height === void 0) {
|
|
12145
|
+
failed += 1;
|
|
12146
|
+
continue;
|
|
12147
|
+
}
|
|
12148
|
+
const window = cropWindow(source.bbox, meta.width, meta.height, {
|
|
12149
|
+
margin,
|
|
12150
|
+
square
|
|
12151
|
+
});
|
|
12152
|
+
const cut = await cropFromJpeg(source.keyFrame, window);
|
|
12153
|
+
if (cut === null) {
|
|
12154
|
+
failed += 1;
|
|
12155
|
+
continue;
|
|
12156
|
+
}
|
|
12157
|
+
const embedding = await deps.encode(cut.crop, cut.width, cut.height);
|
|
12158
|
+
if (embedding.length === 0) {
|
|
12159
|
+
failed += 1;
|
|
12160
|
+
continue;
|
|
12161
|
+
}
|
|
12162
|
+
await deps.store(source, embedding);
|
|
12163
|
+
rebuilt += 1;
|
|
12164
|
+
} catch (err) {
|
|
12165
|
+
failed += 1;
|
|
12166
|
+
deps.logger.warn("embedding rebuild failed for a track", { meta: {
|
|
12167
|
+
trackId,
|
|
12168
|
+
error: String(err)
|
|
12169
|
+
} });
|
|
12170
|
+
}
|
|
12171
|
+
if (scanned % 25 === 0) options.onProgress?.({
|
|
12172
|
+
scanned,
|
|
12173
|
+
rebuilt,
|
|
12174
|
+
missingKeyFrame,
|
|
12175
|
+
missingBbox,
|
|
12176
|
+
failed
|
|
12177
|
+
});
|
|
12083
12178
|
}
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
continue;
|
|
12179
|
+
if (page.nextCursor === null) {
|
|
12180
|
+
exhausted = true;
|
|
12181
|
+
break;
|
|
12088
12182
|
}
|
|
12089
|
-
|
|
12090
|
-
rebuilt += 1;
|
|
12091
|
-
} catch (err) {
|
|
12092
|
-
failed += 1;
|
|
12093
|
-
deps.logger.warn("embedding rebuild failed for a track", { meta: {
|
|
12094
|
-
trackId,
|
|
12095
|
-
error: String(err)
|
|
12096
|
-
} });
|
|
12183
|
+
cursor = page.nextCursor;
|
|
12097
12184
|
}
|
|
12098
|
-
const complete =
|
|
12185
|
+
const complete = exhausted && !budgetExhausted;
|
|
12099
12186
|
deps.logger.info("object embedding rebuild finished", { meta: {
|
|
12100
|
-
scanned
|
|
12187
|
+
scanned,
|
|
12101
12188
|
rebuilt,
|
|
12102
12189
|
missingKeyFrame,
|
|
12103
12190
|
missingBbox,
|
|
@@ -12107,7 +12194,7 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
|
|
|
12107
12194
|
square
|
|
12108
12195
|
} });
|
|
12109
12196
|
return {
|
|
12110
|
-
scanned
|
|
12197
|
+
scanned,
|
|
12111
12198
|
rebuilt,
|
|
12112
12199
|
missingKeyFrame,
|
|
12113
12200
|
missingBbox,
|
|
@@ -17530,26 +17617,6 @@ var SyntheticSensorTrackMaterializer = class {
|
|
|
17530
17617
|
return track;
|
|
17531
17618
|
}
|
|
17532
17619
|
};
|
|
17533
|
-
var TRACK_RETENTION_DEFAULT_DAYS = require_dist.object({ trackRetentionDays: require_dist.number().min(0).default(7) }).parse({}).trackRetentionDays;
|
|
17534
|
-
/**
|
|
17535
|
-
* Sweep every device with persisted tracks: skip retention-disabled devices,
|
|
17536
|
-
* prune the rest at `now − retentionMs`. Per-device ISOLATED — one bad device
|
|
17537
|
-
* never aborts the sweep. Returns the total tracks pruned across devices.
|
|
17538
|
-
*/
|
|
17539
|
-
async function sweepTrackRetention(deps) {
|
|
17540
|
-
const devices = await deps.listDeviceIds();
|
|
17541
|
-
const nowMs = deps.now();
|
|
17542
|
-
let totalTracks = 0;
|
|
17543
|
-
for (const deviceId of devices) try {
|
|
17544
|
-
const cutoffMs = await deps.resolveCutoffMs(deviceId, nowMs);
|
|
17545
|
-
if (cutoffMs === null) continue;
|
|
17546
|
-
const counts = await deps.pruneTracksBefore(deviceId, cutoffMs);
|
|
17547
|
-
totalTracks += counts.tracks;
|
|
17548
|
-
} catch (err) {
|
|
17549
|
-
deps.onError?.(deviceId, err);
|
|
17550
|
-
}
|
|
17551
|
-
return totalTracks;
|
|
17552
|
-
}
|
|
17553
17620
|
var TrackingSettingsSchema = require_dist.object({
|
|
17554
17621
|
/**
|
|
17555
17622
|
* How much of a detection's box must lie inside a zone (0-1 fraction of the
|
|
@@ -18345,6 +18412,26 @@ function buildDetectionSettingsSections() {
|
|
|
18345
18412
|
}
|
|
18346
18413
|
];
|
|
18347
18414
|
}
|
|
18415
|
+
var TRACK_RETENTION_DEFAULT_DAYS = require_dist.object({ trackRetentionDays: require_dist.number().min(0).default(7) }).parse({}).trackRetentionDays;
|
|
18416
|
+
/**
|
|
18417
|
+
* Sweep every device with persisted tracks: skip retention-disabled devices,
|
|
18418
|
+
* prune the rest at `now − retentionMs`. Per-device ISOLATED — one bad device
|
|
18419
|
+
* never aborts the sweep. Returns the total tracks pruned across devices.
|
|
18420
|
+
*/
|
|
18421
|
+
async function sweepTrackRetention(deps) {
|
|
18422
|
+
const devices = await deps.listDeviceIds();
|
|
18423
|
+
const nowMs = deps.now();
|
|
18424
|
+
let totalTracks = 0;
|
|
18425
|
+
for (const deviceId of devices) try {
|
|
18426
|
+
const cutoffMs = await deps.resolveCutoffMs(deviceId, nowMs);
|
|
18427
|
+
if (cutoffMs === null) continue;
|
|
18428
|
+
const counts = await deps.pruneTracksBefore(deviceId, cutoffMs);
|
|
18429
|
+
totalTracks += counts.tracks;
|
|
18430
|
+
} catch (err) {
|
|
18431
|
+
deps.onError?.(deviceId, err);
|
|
18432
|
+
}
|
|
18433
|
+
return totalTracks;
|
|
18434
|
+
}
|
|
18348
18435
|
/**
|
|
18349
18436
|
* Re-home the global analytics sections into the per-device `Analytics`
|
|
18350
18437
|
* top-tab. Every section defaults to the `Analytics` tab (so the
|
|
@@ -18455,6 +18542,59 @@ function buildGlobalSettingsSchema() {
|
|
|
18455
18542
|
default: true
|
|
18456
18543
|
}]
|
|
18457
18544
|
},
|
|
18545
|
+
{
|
|
18546
|
+
id: "embedding-index",
|
|
18547
|
+
title: "Semantic search index",
|
|
18548
|
+
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.",
|
|
18549
|
+
columns: 1,
|
|
18550
|
+
fields: [
|
|
18551
|
+
{
|
|
18552
|
+
type: "number",
|
|
18553
|
+
key: "rebuildCropMargin",
|
|
18554
|
+
label: "Rebuild crop margin",
|
|
18555
|
+
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.",
|
|
18556
|
+
default: .2,
|
|
18557
|
+
min: 0,
|
|
18558
|
+
max: 2,
|
|
18559
|
+
step: .1
|
|
18560
|
+
},
|
|
18561
|
+
{
|
|
18562
|
+
type: "addon-action-button",
|
|
18563
|
+
key: "rebuildEmbeddingsButton",
|
|
18564
|
+
label: "Rebuild index",
|
|
18565
|
+
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.",
|
|
18566
|
+
addonId: "pipeline-analytics",
|
|
18567
|
+
action: "embedding.rebuild",
|
|
18568
|
+
buttonLabel: "Rebuild",
|
|
18569
|
+
variant: "primary",
|
|
18570
|
+
paramsFromForm: { cropMargin: "rebuildCropMargin" },
|
|
18571
|
+
paramsStatic: { square: true },
|
|
18572
|
+
successMessage: "Rebuild started — press Progress to follow it."
|
|
18573
|
+
},
|
|
18574
|
+
{
|
|
18575
|
+
type: "addon-action-button",
|
|
18576
|
+
key: "rebuildStatusButton",
|
|
18577
|
+
label: "Progress",
|
|
18578
|
+
description: "Rows currently indexed, plus the running pass: scanned, rebuilt, and what it had to skip.",
|
|
18579
|
+
addonId: "pipeline-analytics",
|
|
18580
|
+
action: "embedding.rebuildStatus",
|
|
18581
|
+
buttonLabel: "Check progress",
|
|
18582
|
+
successMessage: "Indexed {indexed} · scanned {scanned} · rebuilt {rebuilt} · no key frame {missingKeyFrame} · failed {failed}"
|
|
18583
|
+
},
|
|
18584
|
+
{
|
|
18585
|
+
type: "addon-action-button",
|
|
18586
|
+
key: "wipeEmbeddingsButton",
|
|
18587
|
+
label: "Empty the index",
|
|
18588
|
+
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.",
|
|
18589
|
+
addonId: "pipeline-analytics",
|
|
18590
|
+
action: "embedding.wipe",
|
|
18591
|
+
buttonLabel: "Empty index",
|
|
18592
|
+
variant: "danger",
|
|
18593
|
+
confirmMessage: "Delete every semantic-search vector? Search stops working until a rebuild finishes, and vectors whose tracks have aged out cannot be recovered.",
|
|
18594
|
+
successMessage: "Removed {deleted} vectors."
|
|
18595
|
+
}
|
|
18596
|
+
]
|
|
18597
|
+
},
|
|
18458
18598
|
{
|
|
18459
18599
|
id: "tracking-core",
|
|
18460
18600
|
title: "Tracking",
|
|
@@ -22949,10 +23089,30 @@ var ENCODER_INFO_TTL_MS = 6e4;
|
|
|
22949
23089
|
var EMBEDDING_ORPHAN_SCAN_LIMIT = 2e4;
|
|
22950
23090
|
/** Dimension of the zero probe vector used to enumerate the index. */
|
|
22951
23091
|
var EMBEDDING_ORPHAN_PROBE_DIM = 512;
|
|
23092
|
+
/**
|
|
23093
|
+
* Tracks fetched per page while rebuilding.
|
|
23094
|
+
*
|
|
23095
|
+
* The page size is NOT the caller's `maxTracks`: that bounds the whole pass,
|
|
23096
|
+
* this bounds one round trip. Conflating them is what made the rebuild
|
|
23097
|
+
* re-embed the same newest N tracks on every call.
|
|
23098
|
+
*/
|
|
23099
|
+
var REBUILD_PAGE_SIZE = 200;
|
|
22952
23100
|
var PIPELINE_ANALYTICS_ADDON_ID = "pipeline-analytics";
|
|
22953
23101
|
/** Operator-visible name of the fixed integration. */
|
|
22954
23102
|
var NC_INTEGRATION_NAME = "Notification Center";
|
|
22955
23103
|
/**
|
|
23104
|
+
* The FULL action catalog for this addon.
|
|
23105
|
+
*
|
|
23106
|
+
* Must contain every action, not just the notification-center ones: the hub
|
|
23107
|
+
* harvests THIS named export, so an action registered only in what
|
|
23108
|
+
* `onInitialize` returns is invisible to the bridge and answers 404. That is
|
|
23109
|
+
* exactly what the embedding buttons did on their first deploy.
|
|
23110
|
+
*/
|
|
23111
|
+
var customActions = {
|
|
23112
|
+
...ncActions,
|
|
23113
|
+
...embeddingActions
|
|
23114
|
+
};
|
|
23115
|
+
/**
|
|
22956
23116
|
* Pipeline Analytics addon — subscribes to `PipelineInferenceResult` +
|
|
22957
23117
|
* `PipelineAudioInferenceResult` on the bus, refines raw detections
|
|
22958
23118
|
* into tracks + per-kind events + persisted media, and emits canonical
|
|
@@ -23136,6 +23296,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
23136
23296
|
plateStore = null;
|
|
23137
23297
|
plateRecognizer = null;
|
|
23138
23298
|
objectEmbeddingStore = null;
|
|
23299
|
+
/** Live state of the detached embedding rebuild — see `rebuildObjectEmbeddings`. */
|
|
23300
|
+
rebuildState = { running: false };
|
|
23139
23301
|
/** Cached active-encoder model id — see `resolveEncoderModelId`. */
|
|
23140
23302
|
encoderInfoCache = null;
|
|
23141
23303
|
/** Two-plane detail scheduler/dispatcher: per-track on-demand face/clip/
|
|
@@ -23610,8 +23772,14 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
23610
23772
|
return {
|
|
23611
23773
|
providers,
|
|
23612
23774
|
...isHub && ncHandlers !== void 0 ? {
|
|
23613
|
-
customActions:
|
|
23614
|
-
|
|
23775
|
+
customActions: {
|
|
23776
|
+
...ncActions,
|
|
23777
|
+
...embeddingActions
|
|
23778
|
+
},
|
|
23779
|
+
actionHandlers: {
|
|
23780
|
+
...ncHandlers,
|
|
23781
|
+
...this.buildEmbeddingActionHandlers()
|
|
23782
|
+
}
|
|
23615
23783
|
} : {}
|
|
23616
23784
|
};
|
|
23617
23785
|
}
|
|
@@ -23760,7 +23928,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
23760
23928
|
let storage = this.ctx.kernel.storage;
|
|
23761
23929
|
const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
|
|
23762
23930
|
if (mediaRoot) {
|
|
23763
|
-
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-
|
|
23931
|
+
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-ByXsH-Ce.js"));
|
|
23764
23932
|
storage = new FilesystemStorageProvider(mediaRoot, { eventMedia: mediaRoot });
|
|
23765
23933
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
23766
23934
|
}
|
|
@@ -26864,33 +27032,40 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
26864
27032
|
* widened after the fact.
|
|
26865
27033
|
*/
|
|
26866
27034
|
async rebuildObjectEmbeddings(input) {
|
|
27035
|
+
if (this.rebuildState.running) {
|
|
27036
|
+
this.ctx.logger.info("object embedding rebuild already running — request ignored");
|
|
27037
|
+
return {
|
|
27038
|
+
started: false,
|
|
27039
|
+
alreadyRunning: true
|
|
27040
|
+
};
|
|
27041
|
+
}
|
|
26867
27042
|
const store = this.objectEmbeddingStore;
|
|
26868
27043
|
const trackStore = this.trackStore;
|
|
26869
27044
|
const mediaStore = this.mediaStore;
|
|
26870
27045
|
const eventStore = this.eventStore;
|
|
26871
27046
|
const api = this.ctx.api;
|
|
26872
|
-
const empty = {
|
|
26873
|
-
scanned: 0,
|
|
26874
|
-
rebuilt: 0,
|
|
26875
|
-
missingKeyFrame: 0,
|
|
26876
|
-
missingBbox: 0,
|
|
26877
|
-
failed: 0,
|
|
26878
|
-
complete: true
|
|
26879
|
-
};
|
|
26880
27047
|
if (!store || !trackStore || !mediaStore || !eventStore || !api) {
|
|
26881
27048
|
this.ctx.logger.warn("rebuildObjectEmbeddings: analytics stores unavailable");
|
|
26882
|
-
return
|
|
27049
|
+
return {
|
|
27050
|
+
started: false,
|
|
27051
|
+
alreadyRunning: false
|
|
27052
|
+
};
|
|
26883
27053
|
}
|
|
26884
27054
|
const modelId = await this.resolveEncoderModelId(api) ?? "unknown";
|
|
26885
27055
|
const deviceIds = input.deviceId !== void 0 ? [input.deviceId] : await trackStore.listDeviceIds();
|
|
26886
|
-
|
|
26887
|
-
|
|
26888
|
-
|
|
27056
|
+
const deps = {
|
|
27057
|
+
listTrackIdPage: async (cursor) => {
|
|
27058
|
+
const page = await trackStore.queryRecent({
|
|
26889
27059
|
deviceIds,
|
|
26890
27060
|
...input.since !== void 0 ? { since: input.since } : {},
|
|
26891
27061
|
...input.until !== void 0 ? { until: input.until } : {},
|
|
26892
|
-
|
|
26893
|
-
|
|
27062
|
+
...cursor !== null ? { cursor } : {},
|
|
27063
|
+
limit: REBUILD_PAGE_SIZE
|
|
27064
|
+
});
|
|
27065
|
+
return {
|
|
27066
|
+
trackIds: page.tracks.map((t) => t.trackId),
|
|
27067
|
+
nextCursor: page.nextCursor
|
|
27068
|
+
};
|
|
26894
27069
|
},
|
|
26895
27070
|
loadSource: async (trackId) => {
|
|
26896
27071
|
const media = await mediaStore.listByOwner("track", trackId);
|
|
@@ -26936,11 +27111,93 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
26936
27111
|
info: (message, extras) => this.ctx.logger.info(message, extras),
|
|
26937
27112
|
warn: (message, extras) => this.ctx.logger.warn(message, extras)
|
|
26938
27113
|
}
|
|
26939
|
-
}
|
|
27114
|
+
};
|
|
27115
|
+
this.rebuildState = {
|
|
27116
|
+
running: true,
|
|
27117
|
+
startedAtMs: Date.now(),
|
|
27118
|
+
progress: {
|
|
27119
|
+
scanned: 0,
|
|
27120
|
+
rebuilt: 0,
|
|
27121
|
+
missingKeyFrame: 0,
|
|
27122
|
+
missingBbox: 0,
|
|
27123
|
+
failed: 0
|
|
27124
|
+
}
|
|
27125
|
+
};
|
|
27126
|
+
rebuildObjectEmbeddings(deps, {
|
|
26940
27127
|
...input.cropMargin !== void 0 ? { cropMargin: input.cropMargin } : {},
|
|
26941
27128
|
...input.square !== void 0 ? { square: input.square } : {},
|
|
26942
|
-
...input.maxTracks !== void 0 ? { maxTracks: input.maxTracks } : {}
|
|
27129
|
+
...input.maxTracks !== void 0 ? { maxTracks: input.maxTracks } : {},
|
|
27130
|
+
onProgress: (progress) => {
|
|
27131
|
+
if (this.rebuildState.running) this.rebuildState.progress = progress;
|
|
27132
|
+
this.ctx.logger.info("object embedding rebuild progress", { meta: { ...progress } });
|
|
27133
|
+
}
|
|
27134
|
+
}).then((outcome) => {
|
|
27135
|
+
this.rebuildState = {
|
|
27136
|
+
running: false,
|
|
27137
|
+
startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
|
|
27138
|
+
finishedAtMs: Date.now(),
|
|
27139
|
+
progress: outcome,
|
|
27140
|
+
complete: outcome.complete
|
|
27141
|
+
};
|
|
27142
|
+
}).catch((err) => {
|
|
27143
|
+
this.rebuildState = {
|
|
27144
|
+
running: false,
|
|
27145
|
+
startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
|
|
27146
|
+
finishedAtMs: Date.now(),
|
|
27147
|
+
error: String(err)
|
|
27148
|
+
};
|
|
27149
|
+
this.ctx.logger.warn("object embedding rebuild failed", { meta: { error: String(err) } });
|
|
26943
27150
|
});
|
|
27151
|
+
this.ctx.logger.info("object embedding rebuild started", { meta: {
|
|
27152
|
+
deviceId: input.deviceId ?? "all",
|
|
27153
|
+
cropMargin: input.cropMargin ?? "default",
|
|
27154
|
+
maxTracks: input.maxTracks ?? "unbounded"
|
|
27155
|
+
} });
|
|
27156
|
+
return {
|
|
27157
|
+
started: true,
|
|
27158
|
+
alreadyRunning: false
|
|
27159
|
+
};
|
|
27160
|
+
}
|
|
27161
|
+
/**
|
|
27162
|
+
* Handlers behind the embedding buttons in the settings form.
|
|
27163
|
+
*
|
|
27164
|
+
* They are thin wrappers over the cap methods on purpose: the button and the
|
|
27165
|
+
* API must not be able to drift into two behaviours.
|
|
27166
|
+
*/
|
|
27167
|
+
buildEmbeddingActionHandlers() {
|
|
27168
|
+
return {
|
|
27169
|
+
"embedding.rebuild": async (input) => this.rebuildObjectEmbeddings(input),
|
|
27170
|
+
"embedding.rebuildStatus": async () => {
|
|
27171
|
+
const status = await this.getObjectEmbeddingRebuildStatus();
|
|
27172
|
+
let indexed = 0;
|
|
27173
|
+
try {
|
|
27174
|
+
const api = this.ctx.api;
|
|
27175
|
+
if (api) indexed = (await api.vectorStore.stats.query({ index: OBJECT_CLIP_INDEX })).count;
|
|
27176
|
+
} catch {}
|
|
27177
|
+
return {
|
|
27178
|
+
...status,
|
|
27179
|
+
indexed
|
|
27180
|
+
};
|
|
27181
|
+
},
|
|
27182
|
+
"embedding.wipe": async () => this.wipeObjectEmbeddings()
|
|
27183
|
+
};
|
|
27184
|
+
}
|
|
27185
|
+
/** Progress of the detached rebuild — the only way to observe a long pass. */
|
|
27186
|
+
async getObjectEmbeddingRebuildStatus() {
|
|
27187
|
+
const state = this.rebuildState;
|
|
27188
|
+
const p = state.progress;
|
|
27189
|
+
return {
|
|
27190
|
+
running: state.running,
|
|
27191
|
+
scanned: p?.scanned ?? 0,
|
|
27192
|
+
rebuilt: p?.rebuilt ?? 0,
|
|
27193
|
+
missingKeyFrame: p?.missingKeyFrame ?? 0,
|
|
27194
|
+
missingBbox: p?.missingBbox ?? 0,
|
|
27195
|
+
failed: p?.failed ?? 0,
|
|
27196
|
+
complete: state.complete ?? null,
|
|
27197
|
+
startedAtMs: state.startedAtMs ?? null,
|
|
27198
|
+
finishedAtMs: state.finishedAtMs ?? null,
|
|
27199
|
+
error: state.error ?? null
|
|
27200
|
+
};
|
|
26944
27201
|
}
|
|
26945
27202
|
async searchObjectEvents(input) {
|
|
26946
27203
|
const store = this.objectEmbeddingStore;
|
|
@@ -27354,7 +27611,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
27354
27611
|
};
|
|
27355
27612
|
//#endregion
|
|
27356
27613
|
exports.DETECTION_PIPELINE_SECTION_IDS = DETECTION_PIPELINE_SECTION_IDS;
|
|
27357
|
-
exports.customActions =
|
|
27614
|
+
exports.customActions = customActions;
|
|
27358
27615
|
exports.default = PipelineAnalyticsAddon;
|
|
27359
27616
|
exports.ncActions = ncActions;
|
|
27360
27617
|
exports.pickCleanMedia = pickCleanMedia;
|