@camstack/addon-post-analysis 1.1.38 → 1.1.39
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-CSMfGdnz.mjs → dist-CO07v2sR.mjs} +43 -5
- package/dist/{dist-_h-RM5Lr.js → dist-DUcGHr9E.js} +43 -5
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-BiZGDArd.js → node-B7HfyyIy.js} +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B2MjF8pA.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DrIydlxk.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-Ckp6XV52.mjs → hostInit-DnOjDm_e.mjs} +1 -1
- package/dist/pipeline-analytics/index.js +331 -56
- package/dist/pipeline-analytics/index.mjs +330 -55
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as hydrateSchema, D as object, E as number, O as string, S as createEvent, T as boolean, _ as videoclipsCapability, a as OpsLogEntrySchema, b as BaseAddon, c as buildEventKindDescriptor, d as faceGalleryCapability, g as subKindsOf, h as plateGalleryCapability, i as MACRO_LABELS, k as EventCategory, l as cosineSimilarity, m as pipelineAnalyticsCapability, n as EVENT_KIND_BY_CAP, o as addonWidgetsSourceCapability, p as nodePin, r as EVENT_PAD_MS, s as audioMetricsCapability, t as DEFAULT_EVENT_COLOR, v as zoneAnalyticsCapability, w as array, x as DeviceType, y as errMsg } from "../dist-
|
|
1
|
+
import { C as hydrateSchema, D as object, E as number, O as string, S as createEvent, T as boolean, _ as videoclipsCapability, a as OpsLogEntrySchema, b as BaseAddon, c as buildEventKindDescriptor, d as faceGalleryCapability, g as subKindsOf, h as plateGalleryCapability, i as MACRO_LABELS, k as EventCategory, l as cosineSimilarity, m as pipelineAnalyticsCapability, n as EVENT_KIND_BY_CAP, o as addonWidgetsSourceCapability, p as nodePin, r as EVENT_PAD_MS, s as audioMetricsCapability, t as DEFAULT_EVENT_COLOR, v as zoneAnalyticsCapability, w as array, x as DeviceType, y as errMsg } from "../dist-CO07v2sR.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import sharp from "sharp";
|
|
4
4
|
//#region src/pipeline-analytics/videoclips-provider.ts
|
|
@@ -1136,7 +1136,13 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
1136
1136
|
vehicle: .45
|
|
1137
1137
|
},
|
|
1138
1138
|
confirmBypassEnabled: false,
|
|
1139
|
-
confirmBypassScore: .9
|
|
1139
|
+
confirmBypassScore: .9,
|
|
1140
|
+
byteTrackEnabled: true,
|
|
1141
|
+
byteTrackHighThreshold: .4,
|
|
1142
|
+
byteTrackLowIouThreshold: .2,
|
|
1143
|
+
minSpawnAreaEnabled: true,
|
|
1144
|
+
classSpawnAreaFracs: { person: 5e-4 },
|
|
1145
|
+
classGroupAssoc: true
|
|
1140
1146
|
};
|
|
1141
1147
|
/** Macro-classes the duplicate resolver may collapse across (people in
|
|
1142
1148
|
* non-upright poses misdetected as animals). Any other cross-class pair is
|
|
@@ -1193,6 +1199,31 @@ var SortTracker = class SortTracker {
|
|
|
1193
1199
|
static pairKey(a, b) {
|
|
1194
1200
|
return a < b ? `${a}|${b}` : `${b}|${a}`;
|
|
1195
1201
|
}
|
|
1202
|
+
/** Association-gating group of a class. When `classGroupAssoc` is on the
|
|
1203
|
+
* {person,animal} pair collapses to one group so a per-frame class flip
|
|
1204
|
+
* (crouch person → animal) re-matches the existing track instead of spawning a
|
|
1205
|
+
* concurrent id. Every other class is its own group. */
|
|
1206
|
+
assocGroup(cls) {
|
|
1207
|
+
if (this.config.classGroupAssoc && (cls === PERSON_CLASS || cls === ANIMAL_CLASS)) return `${PERSON_CLASS}|${ANIMAL_CLASS}`;
|
|
1208
|
+
return cls;
|
|
1209
|
+
}
|
|
1210
|
+
/** Whether a detection may associate to a track under the current class gate:
|
|
1211
|
+
* exact class when `classGating` is off (no gate at all) — matching the legacy
|
|
1212
|
+
* contract — otherwise same association group (group == exact class unless
|
|
1213
|
+
* `classGroupAssoc` widened it to {person,animal}). */
|
|
1214
|
+
associable(trackClass, detClass) {
|
|
1215
|
+
if (!this.config.classGating) return true;
|
|
1216
|
+
return this.assocGroup(trackClass) === this.assocGroup(detClass);
|
|
1217
|
+
}
|
|
1218
|
+
/** Whether a spawn candidate clears its per-class minimum bbox-area floor
|
|
1219
|
+
* (fraction of frame area). Ungated when disabled, when the class has no floor,
|
|
1220
|
+
* or when the caller supplied no frame dimensions (fraction is uncomputable). */
|
|
1221
|
+
meetsSpawnAreaFloor(det, frameArea) {
|
|
1222
|
+
if (!this.config.minSpawnAreaEnabled) return true;
|
|
1223
|
+
const floor = this.config.classSpawnAreaFracs[det.class] ?? 0;
|
|
1224
|
+
if (floor <= 0 || frameArea <= 0) return true;
|
|
1225
|
+
return det.bbox.w * det.bbox.h / frameArea >= floor;
|
|
1226
|
+
}
|
|
1196
1227
|
/**
|
|
1197
1228
|
* True when spawning a fresh track for `det` would duplicate an already-live
|
|
1198
1229
|
* track: (a) it overlaps a concurrent compatible-class track by
|
|
@@ -1281,13 +1312,16 @@ var SortTracker = class SortTracker {
|
|
|
1281
1312
|
};
|
|
1282
1313
|
}
|
|
1283
1314
|
/**
|
|
1284
|
-
* Loose same-
|
|
1315
|
+
* Loose same-group gate used by the rescue and resurrection passes: accept
|
|
1285
1316
|
* when the detection overlaps the track's LAST-KNOWN bbox by `rescueIou`, or
|
|
1286
|
-
* its centroid is within `rescueCentroidFactor × bbox-diagonal`. Always
|
|
1287
|
-
* gated (a rescue must never cross
|
|
1317
|
+
* its centroid is within `rescueCentroidFactor × bbox-diagonal`. Always group-
|
|
1318
|
+
* gated (a rescue must never cross association groups) independent of
|
|
1319
|
+
* `classGating`, so a coasting person track can be rescued by its own
|
|
1320
|
+
* crouch→animal flip when `classGroupAssoc` is on, but never by an unrelated
|
|
1321
|
+
* class.
|
|
1288
1322
|
*/
|
|
1289
1323
|
looseMatch(track, det) {
|
|
1290
|
-
if (track.class !== det.class) return false;
|
|
1324
|
+
if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return false;
|
|
1291
1325
|
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return true;
|
|
1292
1326
|
const tc = bboxCentroid(track.bbox);
|
|
1293
1327
|
const dc = bboxCentroid(det.bbox);
|
|
@@ -1335,29 +1369,25 @@ var SortTracker = class SortTracker {
|
|
|
1335
1369
|
if (t.hits >= this.config.minHits) return true;
|
|
1336
1370
|
return this.config.confirmBypassEnabled && t.score >= this.config.confirmBypassScore;
|
|
1337
1371
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
this.tracks = alive;
|
|
1348
|
-
}
|
|
1349
|
-
this.lostTracks = this.lostTracks.filter((t) => timestamp - t.lostAt <= this.config.resurrectionWindowMs);
|
|
1350
|
-
const used = /* @__PURE__ */ new Set();
|
|
1351
|
-
const matchedTracks = /* @__PURE__ */ new Set();
|
|
1352
|
-
const matched = /* @__PURE__ */ new Map();
|
|
1372
|
+
/**
|
|
1373
|
+
* One greedy IoU association pass over a candidate detection subset against the
|
|
1374
|
+
* currently-UNMATCHED tracks, on each track's PREDICTED box. Highest-IoU pairs
|
|
1375
|
+
* bind first; a track/detection binds at most once. Shared by the high-score
|
|
1376
|
+
* first stage and the low-score second stage (different candidate sets + IoU
|
|
1377
|
+
* gates) so the two-tier ByteTrack association stays a single implementation.
|
|
1378
|
+
* Mutates `used` / `matchedTracks` / `matched` in place.
|
|
1379
|
+
*/
|
|
1380
|
+
greedyAssociate(detections, candidateIdxs, iouThreshold, used, matchedTracks, matched) {
|
|
1353
1381
|
const pairs = [];
|
|
1354
1382
|
for (const track of this.tracks) {
|
|
1383
|
+
if (matchedTracks.has(track)) continue;
|
|
1355
1384
|
const pbox = this.predicted(track);
|
|
1356
|
-
for (
|
|
1385
|
+
for (const di of candidateIdxs) {
|
|
1386
|
+
if (used.has(di)) continue;
|
|
1357
1387
|
const det = detections[di];
|
|
1358
|
-
if (this.
|
|
1388
|
+
if (!this.associable(track.class, det.class)) continue;
|
|
1359
1389
|
const score = iou$2(pbox, det.bbox);
|
|
1360
|
-
if (score >=
|
|
1390
|
+
if (score >= iouThreshold) pairs.push({
|
|
1361
1391
|
track,
|
|
1362
1392
|
detIdx: di,
|
|
1363
1393
|
score
|
|
@@ -1371,11 +1401,34 @@ var SortTracker = class SortTracker {
|
|
|
1371
1401
|
matchedTracks.add(pair.track);
|
|
1372
1402
|
used.add(pair.detIdx);
|
|
1373
1403
|
}
|
|
1404
|
+
}
|
|
1405
|
+
update(detections, timestamp, frameContext) {
|
|
1406
|
+
const frameArea = frameContext ? frameContext.frameWidth * frameContext.frameHeight : 0;
|
|
1407
|
+
if (this.config.maxTrackLifetimeMs > 0) {
|
|
1408
|
+
const alive = [];
|
|
1409
|
+
for (const track of this.tracks) if (timestamp - track.firstSeen > this.config.maxTrackLifetimeMs) {
|
|
1410
|
+
track.lost = true;
|
|
1411
|
+
track.lostAt = timestamp;
|
|
1412
|
+
track.resurrectable = false;
|
|
1413
|
+
this.lostTracks.push(track);
|
|
1414
|
+
} else alive.push(track);
|
|
1415
|
+
this.tracks = alive;
|
|
1416
|
+
}
|
|
1417
|
+
this.lostTracks = this.lostTracks.filter((t) => timestamp - t.lostAt <= this.config.resurrectionWindowMs);
|
|
1418
|
+
const used = /* @__PURE__ */ new Set();
|
|
1419
|
+
const matchedTracks = /* @__PURE__ */ new Set();
|
|
1420
|
+
const matched = /* @__PURE__ */ new Map();
|
|
1421
|
+
const highIdxs = [];
|
|
1422
|
+
const lowIdxs = [];
|
|
1423
|
+
for (let di = 0; di < detections.length; di++) if (this.config.byteTrackEnabled && detections[di].score < this.config.byteTrackHighThreshold) lowIdxs.push(di);
|
|
1424
|
+
else highIdxs.push(di);
|
|
1425
|
+
const lowSet = new Set(lowIdxs);
|
|
1426
|
+
this.greedyAssociate(detections, highIdxs, this.config.iouThreshold, used, matchedTracks, matched);
|
|
1374
1427
|
const rescuePairs = [];
|
|
1375
1428
|
for (const track of this.tracks) {
|
|
1376
1429
|
if (matchedTracks.has(track)) continue;
|
|
1377
1430
|
for (let di = 0; di < detections.length; di++) {
|
|
1378
|
-
if (used.has(di)) continue;
|
|
1431
|
+
if (used.has(di) || lowSet.has(di)) continue;
|
|
1379
1432
|
const det = detections[di];
|
|
1380
1433
|
if (!this.looseMatch(track, det)) continue;
|
|
1381
1434
|
rescuePairs.push({
|
|
@@ -1392,6 +1445,7 @@ var SortTracker = class SortTracker {
|
|
|
1392
1445
|
matchedTracks.add(pair.track);
|
|
1393
1446
|
used.add(pair.detIdx);
|
|
1394
1447
|
}
|
|
1448
|
+
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, used, matchedTracks, matched);
|
|
1395
1449
|
for (const [track, det] of matched) {
|
|
1396
1450
|
const prevCenter = bboxCentroid({
|
|
1397
1451
|
x: track.bbox.x,
|
|
@@ -1488,6 +1542,10 @@ var SortTracker = class SortTracker {
|
|
|
1488
1542
|
used.add(di);
|
|
1489
1543
|
continue;
|
|
1490
1544
|
}
|
|
1545
|
+
if (!this.meetsSpawnAreaFloor(det, frameArea)) {
|
|
1546
|
+
used.add(di);
|
|
1547
|
+
continue;
|
|
1548
|
+
}
|
|
1491
1549
|
if (this.config.occlusionEnabled && occluderBoxes.some((ob) => containment(det.bbox, ob) >= this.config.occlusionContainment)) continue;
|
|
1492
1550
|
if (this.config.dedupEnabled && this.isDuplicateSpawn(det, surviving)) {
|
|
1493
1551
|
used.add(di);
|
|
@@ -1524,7 +1582,8 @@ var SortTracker = class SortTracker {
|
|
|
1524
1582
|
trackId: t.id,
|
|
1525
1583
|
trackAge: t.hits,
|
|
1526
1584
|
velocity: t.velocity,
|
|
1527
|
-
path: [...t.path]
|
|
1585
|
+
path: [...t.path],
|
|
1586
|
+
matchedThisFrame: t.lastSeen === timestamp
|
|
1528
1587
|
}));
|
|
1529
1588
|
}
|
|
1530
1589
|
/**
|
|
@@ -2106,7 +2165,10 @@ var FrameProcessor = class {
|
|
|
2106
2165
|
wokenEntryIds: []
|
|
2107
2166
|
};
|
|
2108
2167
|
const trackerInput = gate.suppressedIndices.size > 0 ? filteredDetections.filter((_, i) => !gate.suppressedIndices.has(i)) : filteredDetections;
|
|
2109
|
-
const trackedDetections = this.tracker.update(trackerInput, timestamp
|
|
2168
|
+
const trackedDetections = this.tracker.update(trackerInput, timestamp, {
|
|
2169
|
+
frameWidth,
|
|
2170
|
+
frameHeight
|
|
2171
|
+
});
|
|
2110
2172
|
const objectStates = this.stateAnalyzer.analyze(trackedDetections, timestamp);
|
|
2111
2173
|
const rawEvents = this.eventEmitter.emit(trackedDetections, objectStates, [], [], String(this.deviceId));
|
|
2112
2174
|
const zonesByTrack = /* @__PURE__ */ new Map();
|
|
@@ -2135,6 +2197,7 @@ var FrameProcessor = class {
|
|
|
2135
2197
|
bbox: { ...td.bbox },
|
|
2136
2198
|
zones: zonesByTrack.get(td.trackId) ?? [],
|
|
2137
2199
|
state,
|
|
2200
|
+
matchedThisFrame: td.matchedThisFrame !== false,
|
|
2138
2201
|
...label ? { label } : {},
|
|
2139
2202
|
...emb !== void 0 ? {
|
|
2140
2203
|
embedding: emb.embedding,
|
|
@@ -4407,6 +4470,26 @@ var FACE_MEDIA_OWNER_PREFIX = "face-";
|
|
|
4407
4470
|
* `plate-<trackId>`. Used by `deleteByTracks` to derive the plate crop owners
|
|
4408
4471
|
* of a set of tracks without a `trackId` column on media rows. */
|
|
4409
4472
|
var PLATE_MEDIA_OWNER_PREFIX = "plate-";
|
|
4473
|
+
/**
|
|
4474
|
+
* Kinds that hold exactly ONE row per `(ownerKind, ownerId)` — the current
|
|
4475
|
+
* "best"/rolling/first artefact, never a filmstrip. Their row id + blob path are
|
|
4476
|
+
* DETERMINISTIC (`owner:kind`, NO timestamp) and written via an UPSERT
|
|
4477
|
+
* (`store.set`, atomic `INSERT … ON CONFLICT DO UPDATE`), so N concurrent
|
|
4478
|
+
* captures for the same `(track, kind)` all address the SAME row/blob — one can
|
|
4479
|
+
* never become thirteen regardless of interleaving (RC-1). Accumulating kinds
|
|
4480
|
+
* (`snapshot`, event `crop`/`fullFrame*`, `faceCrop`/`plateCrop`) keep their
|
|
4481
|
+
* per-instance timestamped id so the filmstrip / per-event / per-enrollment rows
|
|
4482
|
+
* still accumulate.
|
|
4483
|
+
*/
|
|
4484
|
+
var SINGLE_INSTANCE_KINDS = new Set([
|
|
4485
|
+
"keyFrame",
|
|
4486
|
+
"thumbnail",
|
|
4487
|
+
"firstFrame",
|
|
4488
|
+
"lastFrame"
|
|
4489
|
+
]);
|
|
4490
|
+
function isSingleInstanceKind(kind) {
|
|
4491
|
+
return SINGLE_INSTANCE_KINDS.has(kind);
|
|
4492
|
+
}
|
|
4410
4493
|
var MEDIA_COLUMNS = [
|
|
4411
4494
|
{
|
|
4412
4495
|
name: "id",
|
|
@@ -4458,10 +4541,11 @@ var MEDIA_INDEXES = [{
|
|
|
4458
4541
|
columns: ["deviceId", "timestamp"]
|
|
4459
4542
|
}];
|
|
4460
4543
|
function buildKey(params) {
|
|
4461
|
-
return `${params.ownerKind}:${params.ownerId}:${params.kind}:${params.timestamp}`;
|
|
4544
|
+
return isSingleInstanceKind(params.kind) ? `${params.ownerKind}:${params.ownerId}:${params.kind}` : `${params.ownerKind}:${params.ownerId}:${params.kind}:${params.timestamp}`;
|
|
4462
4545
|
}
|
|
4463
4546
|
function buildPath(params) {
|
|
4464
|
-
|
|
4547
|
+
const base = `pipeline-analytics/${params.deviceId}/${params.ownerKind}/${params.ownerId}`;
|
|
4548
|
+
return isSingleInstanceKind(params.kind) ? `${base}/${params.kind}.jpg` : `${base}/${params.kind}-${params.timestamp}.jpg`;
|
|
4465
4549
|
}
|
|
4466
4550
|
var MediaStore = class {
|
|
4467
4551
|
storage;
|
|
@@ -4484,25 +4568,31 @@ var MediaStore = class {
|
|
|
4484
4568
|
async put(params) {
|
|
4485
4569
|
const key = buildKey(params);
|
|
4486
4570
|
const path = buildPath(params);
|
|
4571
|
+
const record = {
|
|
4572
|
+
deviceId: params.deviceId,
|
|
4573
|
+
ownerKind: params.ownerKind,
|
|
4574
|
+
ownerId: params.ownerId,
|
|
4575
|
+
kind: params.kind,
|
|
4576
|
+
timestamp: params.timestamp,
|
|
4577
|
+
path,
|
|
4578
|
+
sizeBytes: params.data.length
|
|
4579
|
+
};
|
|
4487
4580
|
try {
|
|
4488
4581
|
await this.storage.write({
|
|
4489
4582
|
location: "eventMedia",
|
|
4490
4583
|
relativePath: path,
|
|
4491
4584
|
data: params.data
|
|
4492
4585
|
});
|
|
4493
|
-
await this.store.
|
|
4586
|
+
if (isSingleInstanceKind(params.kind)) await this.store.set.mutate({
|
|
4587
|
+
collection: MEDIA_COLLECTION,
|
|
4588
|
+
key,
|
|
4589
|
+
value: record
|
|
4590
|
+
});
|
|
4591
|
+
else await this.store.insert.mutate({
|
|
4494
4592
|
collection: MEDIA_COLLECTION,
|
|
4495
4593
|
record: {
|
|
4496
4594
|
id: key,
|
|
4497
|
-
data:
|
|
4498
|
-
deviceId: params.deviceId,
|
|
4499
|
-
ownerKind: params.ownerKind,
|
|
4500
|
-
ownerId: params.ownerId,
|
|
4501
|
-
kind: params.kind,
|
|
4502
|
-
timestamp: params.timestamp,
|
|
4503
|
-
path,
|
|
4504
|
-
sizeBytes: params.data.length
|
|
4505
|
-
}
|
|
4595
|
+
data: record
|
|
4506
4596
|
}
|
|
4507
4597
|
});
|
|
4508
4598
|
return key;
|
|
@@ -4521,8 +4611,14 @@ var MediaStore = class {
|
|
|
4521
4611
|
* Overwrite semantics: keep exactly ONE media entry per (ownerKind, ownerId,
|
|
4522
4612
|
* kind). Used for the rolling `lastFrame` and the best `thumbnail` per track —
|
|
4523
4613
|
* each new capture replaces the previous one (blob + index row) rather than
|
|
4524
|
-
* accumulating a filmstrip the way `put` does.
|
|
4525
|
-
*
|
|
4614
|
+
* accumulating a filmstrip the way `put` does.
|
|
4615
|
+
*
|
|
4616
|
+
* These kinds are {@link SINGLE_INSTANCE_KINDS}, so `put` writes them to a
|
|
4617
|
+
* DETERMINISTIC `owner:kind` row/blob via an UPSERT — N concurrent racing
|
|
4618
|
+
* calls therefore all collapse onto the SAME row (RC-1), no query/insert/delete
|
|
4619
|
+
* interleaving can leave survivors. The query+delete pass below is retained
|
|
4620
|
+
* ONLY to reap LEGACY timestamped rows/blobs written before the deterministic
|
|
4621
|
+
* scheme (transition back-compat); once migrated it is a no-op.
|
|
4526
4622
|
*/
|
|
4527
4623
|
async putReplacing(params) {
|
|
4528
4624
|
const existing = await this.store.query.query({
|
|
@@ -7921,7 +8017,35 @@ var TrackingSettingsSchema = object({
|
|
|
7921
8017
|
dropoutSkipEnabled: boolean().default(true),
|
|
7922
8018
|
/** Max consecutive all-zero frames absorbed as a glitch before the scene is
|
|
7923
8019
|
* treated as genuinely empty. */
|
|
7924
|
-
dropoutMaxSkipFrames: number().int().min(0).default(5)
|
|
8020
|
+
dropoutMaxSkipFrames: number().int().min(0).default(5),
|
|
8021
|
+
/** Two-stage (ByteTrack) association: match high-confidence detections first,
|
|
8022
|
+
* then recover coasting tracks with the low-confidence leftovers before they
|
|
8023
|
+
* die. The single biggest fragmentation reducer. Off = legacy single pass. */
|
|
8024
|
+
byteTrackEnabled: boolean().default(true),
|
|
8025
|
+
/** Score at/above which a detection is HIGH (matched first); below it a detection
|
|
8026
|
+
* is LOW and only gets the second-stage recovery match. Does not gate spawning
|
|
8027
|
+
* (the per-class score/area floors do). */
|
|
8028
|
+
byteTrackHighThreshold: number().min(0).max(1).default(.4),
|
|
8029
|
+
/** Looser IoU gate for the low-score second (recovery) association stage — a
|
|
8030
|
+
* coasting box is stale, and the match only re-attaches an existing track. */
|
|
8031
|
+
byteTrackLowIouThreshold: number().min(0).max(1).default(.2),
|
|
8032
|
+
/** Enforce a per-class minimum bbox-area (fraction of frame) at track SPAWN —
|
|
8033
|
+
* kills tiny far-field / vanishing-point noise tracks. An established track
|
|
8034
|
+
* still matches a shrinking box; only spawns are gated. */
|
|
8035
|
+
minSpawnAreaEnabled: boolean().default(true),
|
|
8036
|
+
/** Min spawn bbox area for `person` as a fraction of the frame (0 = ungated).
|
|
8037
|
+
* Person is the only spawn-score-ungated class, so far-field person noise is
|
|
8038
|
+
* the worst offender; a conservative 0.05% rejects only vanishing-point blips. */
|
|
8039
|
+
minSpawnAreaFracPerson: number().min(0).max(1).default(5e-4),
|
|
8040
|
+
/** Min spawn bbox area for `animal` as a fraction of the frame (0 = ungated). */
|
|
8041
|
+
minSpawnAreaFracAnimal: number().min(0).max(1).default(0),
|
|
8042
|
+
/** Min spawn bbox area for `vehicle` as a fraction of the frame (0 = ungated). */
|
|
8043
|
+
minSpawnAreaFracVehicle: number().min(0).max(1).default(0),
|
|
8044
|
+
/** Gate association on a class GROUP ({person,animal}) instead of the exact
|
|
8045
|
+
* per-frame class, so a person that flips to `animal` for a frame re-matches
|
|
8046
|
+
* its existing track (no concurrent animal id). Reported label stays via the
|
|
8047
|
+
* class vote. Off = exact per-frame class gating. */
|
|
8048
|
+
classGroupAssoc: boolean().default(true)
|
|
7925
8049
|
});
|
|
7926
8050
|
var TRACKING_DEFAULTS = TrackingSettingsSchema.parse({});
|
|
7927
8051
|
/**
|
|
@@ -7967,7 +8091,15 @@ function resolveTrackingSettings(raw) {
|
|
|
7967
8091
|
confirmBypassEnabled: s.confirmBypassEnabled.catch(TRACKING_DEFAULTS.confirmBypassEnabled).parse(raw.confirmBypassEnabled),
|
|
7968
8092
|
confirmBypassScore: s.confirmBypassScore.catch(TRACKING_DEFAULTS.confirmBypassScore).parse(raw.confirmBypassScore),
|
|
7969
8093
|
dropoutSkipEnabled: s.dropoutSkipEnabled.catch(TRACKING_DEFAULTS.dropoutSkipEnabled).parse(raw.dropoutSkipEnabled),
|
|
7970
|
-
dropoutMaxSkipFrames: s.dropoutMaxSkipFrames.catch(TRACKING_DEFAULTS.dropoutMaxSkipFrames).parse(raw.dropoutMaxSkipFrames)
|
|
8094
|
+
dropoutMaxSkipFrames: s.dropoutMaxSkipFrames.catch(TRACKING_DEFAULTS.dropoutMaxSkipFrames).parse(raw.dropoutMaxSkipFrames),
|
|
8095
|
+
byteTrackEnabled: s.byteTrackEnabled.catch(TRACKING_DEFAULTS.byteTrackEnabled).parse(raw.byteTrackEnabled),
|
|
8096
|
+
byteTrackHighThreshold: s.byteTrackHighThreshold.catch(TRACKING_DEFAULTS.byteTrackHighThreshold).parse(raw.byteTrackHighThreshold),
|
|
8097
|
+
byteTrackLowIouThreshold: s.byteTrackLowIouThreshold.catch(TRACKING_DEFAULTS.byteTrackLowIouThreshold).parse(raw.byteTrackLowIouThreshold),
|
|
8098
|
+
minSpawnAreaEnabled: s.minSpawnAreaEnabled.catch(TRACKING_DEFAULTS.minSpawnAreaEnabled).parse(raw.minSpawnAreaEnabled),
|
|
8099
|
+
minSpawnAreaFracPerson: s.minSpawnAreaFracPerson.catch(TRACKING_DEFAULTS.minSpawnAreaFracPerson).parse(raw.minSpawnAreaFracPerson),
|
|
8100
|
+
minSpawnAreaFracAnimal: s.minSpawnAreaFracAnimal.catch(TRACKING_DEFAULTS.minSpawnAreaFracAnimal).parse(raw.minSpawnAreaFracAnimal),
|
|
8101
|
+
minSpawnAreaFracVehicle: s.minSpawnAreaFracVehicle.catch(TRACKING_DEFAULTS.minSpawnAreaFracVehicle).parse(raw.minSpawnAreaFracVehicle),
|
|
8102
|
+
classGroupAssoc: s.classGroupAssoc.catch(TRACKING_DEFAULTS.classGroupAssoc).parse(raw.classGroupAssoc)
|
|
7971
8103
|
};
|
|
7972
8104
|
}
|
|
7973
8105
|
//#endregion
|
|
@@ -8439,11 +8571,12 @@ function isPlausibleThumbnailBox(bbox, frameWidth, frameHeight) {
|
|
|
8439
8571
|
var KEYFRAME_NATIVE_MAX_WIDTH = 1920;
|
|
8440
8572
|
/**
|
|
8441
8573
|
* The tracks whose native `keyFrame` should be (re)captured THIS frame: exactly
|
|
8442
|
-
* the tracks that hit a new best-frame moment (`
|
|
8443
|
-
* downstream keeps one `keyFrame`
|
|
8574
|
+
* the tracks that hit a GENUINE new best-frame moment (`keyFrame`), NOT the
|
|
8575
|
+
* per-frame best-thumbnail retry. `putReplacing` downstream keeps one `keyFrame`
|
|
8576
|
+
* per track (the current peak).
|
|
8444
8577
|
*/
|
|
8445
8578
|
function selectKeyFrameTrackIds(targets) {
|
|
8446
|
-
return targets.filter((t) => t.
|
|
8579
|
+
return targets.filter((t) => t.keyFrame).map((t) => t.trackId);
|
|
8447
8580
|
}
|
|
8448
8581
|
/**
|
|
8449
8582
|
* Build the `captureCrop` request for a track's native `keyFrame`: the FULL
|
|
@@ -8463,6 +8596,15 @@ function buildKeyFrameCaptureRequest(frameWidth, frameHeight) {
|
|
|
8463
8596
|
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
|
|
8464
8597
|
};
|
|
8465
8598
|
}
|
|
8599
|
+
/**
|
|
8600
|
+
* True when a native subject crop of `width` px is a plausible native hit worth
|
|
8601
|
+
* keeping, rather than a sub-threshold fallback stamp. Clamps the floor to the
|
|
8602
|
+
* requested `maxWidth` so a caller that legitimately asked for a narrow crop
|
|
8603
|
+
* (`maxWidth < MIN`) is not rejected for honouring its own cap.
|
|
8604
|
+
*/
|
|
8605
|
+
function nativeSubjectCropMeetsFloor(width, maxWidth) {
|
|
8606
|
+
return width >= Math.min(320, maxWidth);
|
|
8607
|
+
}
|
|
8466
8608
|
//#endregion
|
|
8467
8609
|
//#region src/pipeline-analytics/track-retention-sweep.ts
|
|
8468
8610
|
/**
|
|
@@ -9545,6 +9687,7 @@ var FaceRecognizer = class {
|
|
|
9545
9687
|
bbox: input.parentBbox,
|
|
9546
9688
|
zones: [],
|
|
9547
9689
|
state: "moving",
|
|
9690
|
+
matchedThisFrame: true,
|
|
9548
9691
|
embedding: input.embedding,
|
|
9549
9692
|
embeddingModelId: modelId,
|
|
9550
9693
|
...input.faceBbox !== void 0 ? { faceBbox: input.faceBbox } : {},
|
|
@@ -11900,6 +12043,19 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
11900
12043
|
* (recycled/blank live frame) still gets a subject crop for the gallery
|
|
11901
12044
|
* instead of degrading to a full-scene tile. Cleared on track end + reset. */
|
|
11902
12045
|
thumbnailLandedTracks = /* @__PURE__ */ new Set();
|
|
12046
|
+
/** Tracks with a best-`thumbnail` capture CURRENTLY in flight (RC-1). The
|
|
12047
|
+
* #27-A retry re-fired a fresh best-thumbnail capture every frame while one
|
|
12048
|
+
* was still resolving (capture latency stacks 0.1–3s under the native path),
|
|
12049
|
+
* so a short track issued N overlapping captures. While a track sits here
|
|
12050
|
+
* `buildSnapshotTargets` suppresses a new best-thumbnail request; the pending
|
|
12051
|
+
* result clears the flag (and, if it landed, sets `thumbnailLandedTracks`).
|
|
12052
|
+
* Cleared on track end + reset. */
|
|
12053
|
+
thumbnailInFlight = /* @__PURE__ */ new Set();
|
|
12054
|
+
/** Tracks with a native `keyFrame` capture CURRENTLY in flight (RC-1). Guards
|
|
12055
|
+
* the fire-and-forget `persistKeyFrames` so a burst of new-best frames issues
|
|
12056
|
+
* at most ONE outstanding 1920px native capture per track instead of one per
|
|
12057
|
+
* frame. Cleared when the capture settles (+ on track end / reset). */
|
|
12058
|
+
keyFrameInFlight = /* @__PURE__ */ new Set();
|
|
11903
12059
|
/** Per-track update-gate memory for `PipelineAnalyticsTrackLifecycle`
|
|
11904
12060
|
* `phase:'update'` — the last-emitted best (confidence / label / crop
|
|
11905
12061
|
* area) + emit time, so a material improvement is measured against the
|
|
@@ -12141,7 +12297,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12141
12297
|
detectionFrameFallbacks: nativeFallbacks
|
|
12142
12298
|
} });
|
|
12143
12299
|
};
|
|
12144
|
-
const
|
|
12300
|
+
const fetchNativeCropRgb = async (frameHandle, paddedNorm, maxWidth) => {
|
|
12145
12301
|
if (!pipelineRunnerApi?.getNativeCrop) return null;
|
|
12146
12302
|
try {
|
|
12147
12303
|
const native = await pipelineRunnerApi.getNativeCrop.query({
|
|
@@ -12150,14 +12306,33 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12150
12306
|
...maxWidth !== void 0 ? { maxWidth } : {}
|
|
12151
12307
|
}, nodePin(frameHandle.nodeId));
|
|
12152
12308
|
if (!native || native.width <= 0 || native.height <= 0) return null;
|
|
12153
|
-
return
|
|
12309
|
+
return {
|
|
12310
|
+
bytes: Buffer.from(native.bytes),
|
|
12311
|
+
width: native.width,
|
|
12312
|
+
height: native.height
|
|
12313
|
+
};
|
|
12154
12314
|
} catch (err) {
|
|
12155
12315
|
cropMetricLogger.debug("native crop request failed — falling back", { meta: { error: errMsg(err) } });
|
|
12156
12316
|
return null;
|
|
12157
12317
|
}
|
|
12158
12318
|
};
|
|
12319
|
+
const tryNativeCrop = async (frameHandle, paddedNorm, maxWidth) => {
|
|
12320
|
+
const native = await fetchNativeCropRgb(frameHandle, paddedNorm, maxWidth);
|
|
12321
|
+
if (!native) return null;
|
|
12322
|
+
return await encodeRgbCropToJpeg(native.bytes, native.width, native.height);
|
|
12323
|
+
};
|
|
12159
12324
|
const getNativeCropJpeg = async (frameHandle, normalizedBbox, maxWidth) => {
|
|
12160
|
-
const
|
|
12325
|
+
const native = await fetchNativeCropRgb(frameHandle, normalizedBbox, maxWidth);
|
|
12326
|
+
if (!native || !nativeSubjectCropMeetsFloor(native.width, maxWidth)) {
|
|
12327
|
+
if (native) cropMetricLogger.debug("native subject crop below floor — local fallback", { meta: {
|
|
12328
|
+
width: native.width,
|
|
12329
|
+
height: native.height,
|
|
12330
|
+
maxWidth
|
|
12331
|
+
} });
|
|
12332
|
+
bumpCropMetric(false);
|
|
12333
|
+
return null;
|
|
12334
|
+
}
|
|
12335
|
+
const jpeg = await encodeRgbCropToJpeg(native.bytes, native.width, native.height);
|
|
12161
12336
|
bumpCropMetric(jpeg !== null);
|
|
12162
12337
|
return jpeg;
|
|
12163
12338
|
};
|
|
@@ -12710,6 +12885,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12710
12885
|
this.bestFrameTracker.clear();
|
|
12711
12886
|
this.lastFrameAtByTrack.clear();
|
|
12712
12887
|
this.thumbnailLandedTracks.clear();
|
|
12888
|
+
this.thumbnailInFlight.clear();
|
|
12889
|
+
this.keyFrameInFlight.clear();
|
|
12713
12890
|
this.trackLifecycleUpdateMem.clear();
|
|
12714
12891
|
this.objectEmbeddingBestSelector.clear();
|
|
12715
12892
|
this.levelStateByDevice.clear();
|
|
@@ -13030,6 +13207,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13030
13207
|
frames: captureAgg.frames,
|
|
13031
13208
|
...captureAgg.sums
|
|
13032
13209
|
} });
|
|
13210
|
+
const thumbInFlightTrackIds = snapshotTargets.filter((t) => t.bestThumbnail).map((t) => t.trackId);
|
|
13211
|
+
for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.add(trackId);
|
|
13033
13212
|
this.eventMediaDispatcher.captureForFrame({
|
|
13034
13213
|
deviceId,
|
|
13035
13214
|
frameHandle,
|
|
@@ -13049,7 +13228,9 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13049
13228
|
mediaKey: s.mediaKey
|
|
13050
13229
|
});
|
|
13051
13230
|
for (const trackId of res.thumbnailTrackIds) this.thumbnailLandedTracks.add(trackId);
|
|
13052
|
-
}).catch(() => {})
|
|
13231
|
+
}).catch(() => {}).finally(() => {
|
|
13232
|
+
for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.delete(trackId);
|
|
13233
|
+
});
|
|
13053
13234
|
}
|
|
13054
13235
|
}
|
|
13055
13236
|
if (this.faceRecognizer && faceGloballyEnabled && faceSettings) this.faceRecognizer.processFrame({
|
|
@@ -13494,7 +13675,10 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13494
13675
|
const mediaStore = this.mediaStore;
|
|
13495
13676
|
if (!capture || !mediaStore) return;
|
|
13496
13677
|
const req = buildKeyFrameCaptureRequest(frameWidth, frameHeight);
|
|
13497
|
-
|
|
13678
|
+
const pending = trackIds.filter((id) => !this.keyFrameInFlight.has(id));
|
|
13679
|
+
if (pending.length === 0) return;
|
|
13680
|
+
for (const id of pending) this.keyFrameInFlight.add(id);
|
|
13681
|
+
await Promise.all(pending.map(async (trackId) => {
|
|
13498
13682
|
try {
|
|
13499
13683
|
const keyFrame = await capture(frameHandle, req.bbox, frameWidth, frameHeight, req.padding, req.maxWidth);
|
|
13500
13684
|
if (!keyFrame) return;
|
|
@@ -13515,6 +13699,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13515
13699
|
error: errMsg(err)
|
|
13516
13700
|
}
|
|
13517
13701
|
});
|
|
13702
|
+
} finally {
|
|
13703
|
+
this.keyFrameInFlight.delete(trackId);
|
|
13518
13704
|
}
|
|
13519
13705
|
}));
|
|
13520
13706
|
}
|
|
@@ -13576,6 +13762,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13576
13762
|
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
|
|
13577
13763
|
const targets = [];
|
|
13578
13764
|
for (const t of tracked) {
|
|
13765
|
+
if (t.matchedThisFrame === false) continue;
|
|
13579
13766
|
const lastSnap = this.trackStore.lastSnapshotAt(t.trackId);
|
|
13580
13767
|
const dueSnapshot = media.saveThumbnails && evaluatePeriodicSnapshot({
|
|
13581
13768
|
lastSnapshotAt: lastSnap,
|
|
@@ -13607,8 +13794,10 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13607
13794
|
});
|
|
13608
13795
|
if (plan.rollingLastFrame) this.lastFrameAtByTrack.set(t.trackId, timestamp);
|
|
13609
13796
|
if (plan.appendSnapshot) this.trackStore.markSnapshotPending(t.trackId, timestamp, t.bbox);
|
|
13610
|
-
const
|
|
13611
|
-
|
|
13797
|
+
const plausibleBox = isPlausibleThumbnailBox(t.bbox, frameWidth, frameHeight);
|
|
13798
|
+
const bestThumbnail = plan.bestThumbnail && plausibleBox && !this.thumbnailInFlight.has(t.trackId);
|
|
13799
|
+
const keyFrame = isNewBest && plausibleBox;
|
|
13800
|
+
if (!plan.appendSnapshot && !plan.rollingLastFrame && !bestThumbnail && !keyFrame) continue;
|
|
13612
13801
|
targets.push({
|
|
13613
13802
|
trackId: t.trackId,
|
|
13614
13803
|
timestamp,
|
|
@@ -13616,7 +13805,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13616
13805
|
...t.label ? { label: t.label } : {},
|
|
13617
13806
|
appendSnapshot: plan.appendSnapshot,
|
|
13618
13807
|
rollingLastFrame: plan.rollingLastFrame,
|
|
13619
|
-
bestThumbnail
|
|
13808
|
+
bestThumbnail,
|
|
13809
|
+
keyFrame
|
|
13620
13810
|
});
|
|
13621
13811
|
}
|
|
13622
13812
|
return targets;
|
|
@@ -13923,6 +14113,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13923
14113
|
this.objectEmbeddingBestSelector.delete(t.trackId);
|
|
13924
14114
|
this.lastFrameAtByTrack.delete(t.trackId);
|
|
13925
14115
|
this.thumbnailLandedTracks.delete(t.trackId);
|
|
14116
|
+
this.thumbnailInFlight.delete(t.trackId);
|
|
14117
|
+
this.keyFrameInFlight.delete(t.trackId);
|
|
13926
14118
|
this.detailDispatcher?.onTrackEnded(t.deviceId, t.trackId);
|
|
13927
14119
|
this.overlayState.onTrackEnded(t.deviceId, t.trackId);
|
|
13928
14120
|
if ((this.trackStore?.getActive(t.deviceId).length ?? 0) === 0) {
|
|
@@ -14185,6 +14377,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
14185
14377
|
this.objectEmbeddingBestSelector.delete(track.trackId);
|
|
14186
14378
|
this.lastFrameAtByTrack.delete(track.trackId);
|
|
14187
14379
|
this.thumbnailLandedTracks.delete(track.trackId);
|
|
14380
|
+
this.thumbnailInFlight.delete(track.trackId);
|
|
14381
|
+
this.keyFrameInFlight.delete(track.trackId);
|
|
14188
14382
|
this.trackLifecycleUpdateMem.delete(track.trackId);
|
|
14189
14383
|
this.detailDispatcher?.onTrackEnded(deviceId, track.trackId);
|
|
14190
14384
|
this.overlayState.onTrackEnded(deviceId, track.trackId);
|
|
@@ -14235,7 +14429,17 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
14235
14429
|
vehicle: trk.minScoreVehicle
|
|
14236
14430
|
},
|
|
14237
14431
|
confirmBypassEnabled: trk.confirmBypassEnabled,
|
|
14238
|
-
confirmBypassScore: trk.confirmBypassScore
|
|
14432
|
+
confirmBypassScore: trk.confirmBypassScore,
|
|
14433
|
+
byteTrackEnabled: trk.byteTrackEnabled,
|
|
14434
|
+
byteTrackHighThreshold: trk.byteTrackHighThreshold,
|
|
14435
|
+
byteTrackLowIouThreshold: trk.byteTrackLowIouThreshold,
|
|
14436
|
+
minSpawnAreaEnabled: trk.minSpawnAreaEnabled,
|
|
14437
|
+
classSpawnAreaFracs: {
|
|
14438
|
+
person: trk.minSpawnAreaFracPerson,
|
|
14439
|
+
animal: trk.minSpawnAreaFracAnimal,
|
|
14440
|
+
vehicle: trk.minSpawnAreaFracVehicle
|
|
14441
|
+
},
|
|
14442
|
+
classGroupAssoc: trk.classGroupAssoc
|
|
14239
14443
|
}, { stationaryThresholdSec }, {
|
|
14240
14444
|
minTrackAge,
|
|
14241
14445
|
cooldownSec,
|
|
@@ -15687,6 +15891,77 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
15687
15891
|
max: 1,
|
|
15688
15892
|
step: .05,
|
|
15689
15893
|
default: TRACKING_DEFAULTS.confirmBypassScore
|
|
15894
|
+
},
|
|
15895
|
+
{
|
|
15896
|
+
type: "boolean",
|
|
15897
|
+
key: "byteTrackEnabled",
|
|
15898
|
+
label: "Two-stage association (ByteTrack)",
|
|
15899
|
+
description: "Match high-confidence detections first, then recover coasting tracks with the low-confidence leftovers before they die. The single biggest fix for a subject fragmenting into many tracks. Off = legacy single pass.",
|
|
15900
|
+
default: TRACKING_DEFAULTS.byteTrackEnabled
|
|
15901
|
+
},
|
|
15902
|
+
{
|
|
15903
|
+
type: "number",
|
|
15904
|
+
key: "byteTrackHighThreshold",
|
|
15905
|
+
label: "Two-stage high threshold",
|
|
15906
|
+
description: "Score at/above which a detection is matched in the first stage. Below it a detection is LOW and gets a second, looser recovery match against still-unmatched (coasting) tracks. This does not change which detections can start a track (the spawn confidence/size floors do). Only used with two-stage association.",
|
|
15907
|
+
min: 0,
|
|
15908
|
+
max: 1,
|
|
15909
|
+
step: .05,
|
|
15910
|
+
default: TRACKING_DEFAULTS.byteTrackHighThreshold
|
|
15911
|
+
},
|
|
15912
|
+
{
|
|
15913
|
+
type: "number",
|
|
15914
|
+
key: "byteTrackLowIouThreshold",
|
|
15915
|
+
label: "Two-stage low IoU",
|
|
15916
|
+
description: "Looser overlap gate for re-attaching a low-confidence detection to a coasting track in the second stage. Only used with two-stage association.",
|
|
15917
|
+
min: 0,
|
|
15918
|
+
max: 1,
|
|
15919
|
+
step: .05,
|
|
15920
|
+
default: TRACKING_DEFAULTS.byteTrackLowIouThreshold
|
|
15921
|
+
},
|
|
15922
|
+
{
|
|
15923
|
+
type: "boolean",
|
|
15924
|
+
key: "minSpawnAreaEnabled",
|
|
15925
|
+
label: "Minimum spawn size",
|
|
15926
|
+
description: "Require a new track’s box to cover at least a minimum fraction of the frame before it is created — stops tiny far-field / vanishing-point blips from spawning full tracks. An already-tracked subject that shrinks into the distance is unaffected.",
|
|
15927
|
+
default: TRACKING_DEFAULTS.minSpawnAreaEnabled
|
|
15928
|
+
},
|
|
15929
|
+
{
|
|
15930
|
+
type: "number",
|
|
15931
|
+
key: "minSpawnAreaFracPerson",
|
|
15932
|
+
label: "Min spawn area — person",
|
|
15933
|
+
description: "Smallest person box (as a fraction of the frame area) allowed to start a NEW track. 0 = ungated. Conservative default (0.0005 = 0.05% of frame) drops only vanishing-point noise. Raise on cameras with a distant road / horizon.",
|
|
15934
|
+
min: 0,
|
|
15935
|
+
max: 1,
|
|
15936
|
+
step: 5e-4,
|
|
15937
|
+
default: TRACKING_DEFAULTS.minSpawnAreaFracPerson
|
|
15938
|
+
},
|
|
15939
|
+
{
|
|
15940
|
+
type: "number",
|
|
15941
|
+
key: "minSpawnAreaFracAnimal",
|
|
15942
|
+
label: "Min spawn area — animal",
|
|
15943
|
+
description: "Smallest animal box (fraction of frame area) allowed to start a NEW track. 0 = ungated (animal already has a spawn-score floor).",
|
|
15944
|
+
min: 0,
|
|
15945
|
+
max: 1,
|
|
15946
|
+
step: 5e-4,
|
|
15947
|
+
default: TRACKING_DEFAULTS.minSpawnAreaFracAnimal
|
|
15948
|
+
},
|
|
15949
|
+
{
|
|
15950
|
+
type: "number",
|
|
15951
|
+
key: "minSpawnAreaFracVehicle",
|
|
15952
|
+
label: "Min spawn area — vehicle",
|
|
15953
|
+
description: "Smallest vehicle box (fraction of frame area) allowed to start a NEW track. 0 = ungated (vehicle already has a spawn-score floor).",
|
|
15954
|
+
min: 0,
|
|
15955
|
+
max: 1,
|
|
15956
|
+
step: 5e-4,
|
|
15957
|
+
default: TRACKING_DEFAULTS.minSpawnAreaFracVehicle
|
|
15958
|
+
},
|
|
15959
|
+
{
|
|
15960
|
+
type: "boolean",
|
|
15961
|
+
key: "classGroupAssoc",
|
|
15962
|
+
label: "Person/animal group matching",
|
|
15963
|
+
description: "Let a detection that flips between person and animal for a frame (a crouching / bending person is often read as an animal) re-match its existing track instead of spawning a concurrent animal track. The reported class is still decided by the per-track class vote.",
|
|
15964
|
+
default: TRACKING_DEFAULTS.classGroupAssoc
|
|
15690
15965
|
}
|
|
15691
15966
|
]
|
|
15692
15967
|
},
|
|
@@ -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-DrIydlxk.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.1.
|
|
3
|
+
"version": "1.1.39",
|
|
4
4
|
"description": "CamStack Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|