@camstack/addon-post-analysis 1.2.1 → 1.2.3
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-DMg7kQfI.js → dist-Ck79MtSp.js} +68 -7
- package/dist/{dist-D4KAS857.mjs → dist-gQ5DHTYd.mjs} +68 -7
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node--uX7wh6R.js → node-C4bKtLou.js} +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-ddQyIsuX.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-C4dWm6ZL.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-DGlQ_y2f.mjs → hostInit-3T15bdHA.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +181 -23
- package/dist/pipeline-analytics/index.mjs +180 -22
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -11168,7 +11168,8 @@ var PipelineStepInputSchema = lazy(() => object({
|
|
|
11168
11168
|
modelId: string().optional(),
|
|
11169
11169
|
enabled: boolean().default(true),
|
|
11170
11170
|
children: array(PipelineStepInputSchema).optional(),
|
|
11171
|
-
settings: record(string(), unknown()).optional()
|
|
11171
|
+
settings: record(string(), unknown()).optional(),
|
|
11172
|
+
jumpDeviceKey: string().optional()
|
|
11172
11173
|
}));
|
|
11173
11174
|
var ModelSubstitutionSchema = object({
|
|
11174
11175
|
addonId: string(),
|
|
@@ -11474,8 +11475,24 @@ var NativeCropBboxSchema = object({
|
|
|
11474
11475
|
});
|
|
11475
11476
|
/** Result of a best-effort native-resolution crop (`getNativeCrop`). */
|
|
11476
11477
|
var NativeCropResultSchema = object({
|
|
11477
|
-
/**
|
|
11478
|
-
|
|
11478
|
+
/**
|
|
11479
|
+
* Packed rgb (24-bit) pixels of the crop. Present on the DEFAULT (raw) path —
|
|
11480
|
+
* same-node (in-process / UDS) callers get zero-copy RGB and encode locally.
|
|
11481
|
+
* OMITTED when the caller requested `encodeJpeg` (the cross-node compressed
|
|
11482
|
+
* path below), where shipping raw RGB is both an invariant violation and, for
|
|
11483
|
+
* a native full frame (~26 MB at 4K), larger than Moleculer's 10 MB
|
|
11484
|
+
* `maxPacketSize` → the packet is dropped and the call 60s-times-out. That is
|
|
11485
|
+
* the cross-node native-media miss: `bytes` is replaced by `jpeg`.
|
|
11486
|
+
*/
|
|
11487
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
11488
|
+
/**
|
|
11489
|
+
* Base64 JPEG of the crop — the COMPRESSED cross-node payload. The OWNING node
|
|
11490
|
+
* (which holds the native surface) encodes it in-process, so a native 4K frame
|
|
11491
|
+
* ships as ~0.5–2 MB (well under `maxPacketSize`) and NO raw pixels ever cross
|
|
11492
|
+
* a process boundary (CLAUDE.md invariant #21). Present ONLY when the request
|
|
11493
|
+
* set `encodeJpeg: true`; `bytes` is then absent.
|
|
11494
|
+
*/
|
|
11495
|
+
jpeg: string().optional(),
|
|
11479
11496
|
width: number().int().positive(),
|
|
11480
11497
|
height: number().int().positive(),
|
|
11481
11498
|
/**
|
|
@@ -11621,6 +11638,20 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
|
|
|
11621
11638
|
hubHostnameOverride: string().optional()
|
|
11622
11639
|
})]).describe("Per-camera frame-source mode for the runner (P2c)");
|
|
11623
11640
|
/**
|
|
11641
|
+
* One ENABLED inference device on the runner's node, as the step-tree
|
|
11642
|
+
* device-jump resolver sees it (phase 1). The orchestrator populates this
|
|
11643
|
+
* roster on the attach payload whenever the camera is elected onto a specific
|
|
11644
|
+
* `deviceKey` and the node has ≥2 enabled devices — it is the candidate set the
|
|
11645
|
+
* runner auto-jumps an enrichment step to when the elected device's format
|
|
11646
|
+
* cannot run that step's model. `weight`/`maxSessions` mirror the balancer's
|
|
11647
|
+
* per-device knobs so the auto choice is weighted-least-loaded.
|
|
11648
|
+
*/
|
|
11649
|
+
var RunnerInferenceDeviceSchema = object({
|
|
11650
|
+
deviceKey: string(),
|
|
11651
|
+
weight: number().positive().default(1),
|
|
11652
|
+
maxSessions: number().int().positive().nullable().default(null)
|
|
11653
|
+
});
|
|
11654
|
+
/**
|
|
11624
11655
|
* Camera assignment payload sent by `addon-pipeline-orchestrator` to a
|
|
11625
11656
|
* specific runner instance via `attachCamera`. Carries everything the
|
|
11626
11657
|
* runner needs to subscribe to the local broker and execute inference.
|
|
@@ -11738,7 +11769,16 @@ var RunnerCameraConfigSchema = object({
|
|
|
11738
11769
|
* omitted ⇒ the runner's default device. The engine itself stays node-local —
|
|
11739
11770
|
* this only selects WHICH device pool of that node runs the session.
|
|
11740
11771
|
*/
|
|
11741
|
-
deviceKey: string().optional()
|
|
11772
|
+
deviceKey: string().optional(),
|
|
11773
|
+
/**
|
|
11774
|
+
* Step-tree device-jump roster (phase 1): the node's ENABLED inference
|
|
11775
|
+
* devices, populated by the orchestrator ONLY when `deviceKey` is set and the
|
|
11776
|
+
* node has ≥2 enabled devices. The runner uses it to AUTO-jump an enrichment
|
|
11777
|
+
* step whose model has no build for the elected device's format onto another
|
|
11778
|
+
* enabled device of the SAME node (weighted-least-loaded). Absent/single-entry
|
|
11779
|
+
* ⇒ no jump possible; the step runs on `deviceKey`.
|
|
11780
|
+
*/
|
|
11781
|
+
inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
|
|
11742
11782
|
});
|
|
11743
11783
|
motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
|
|
11744
11784
|
/**
|
|
@@ -11794,7 +11834,18 @@ var RunnerLocalMetricsSchema = object({
|
|
|
11794
11834
|
method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
|
|
11795
11835
|
handle: FrameHandleSchema,
|
|
11796
11836
|
bbox: NativeCropBboxSchema,
|
|
11797
|
-
maxWidth: number().int().positive().optional()
|
|
11837
|
+
maxWidth: number().int().positive().optional(),
|
|
11838
|
+
/**
|
|
11839
|
+
* When `true`, the runner encodes the resolved crop to JPEG ON THE
|
|
11840
|
+
* OWNING NODE and returns it in `jpeg` (base64) INSTEAD of raw `bytes`.
|
|
11841
|
+
* Callers set this for CROSS-NODE fetches (`handle.nodeId` is a remote
|
|
11842
|
+
* agent) so the compressed payload fits Moleculer's `maxPacketSize` and
|
|
11843
|
+
* no raw pixels cross the process boundary. Same-node callers omit it
|
|
11844
|
+
* and keep the zero-copy raw `bytes` path. Additive + optional: a
|
|
11845
|
+
* pre-encode runner (version skew) ignores it and returns `bytes`, so
|
|
11846
|
+
* the caller falls back to encoding locally.
|
|
11847
|
+
*/
|
|
11848
|
+
encodeJpeg: boolean().optional()
|
|
11798
11849
|
}), NativeCropResultSchema.nullable()), method(object({
|
|
11799
11850
|
deviceId: number(),
|
|
11800
11851
|
frameHandle: FrameHandleSchema.optional(),
|
|
@@ -16657,7 +16708,8 @@ var MediaFileKindEnum = _enum([
|
|
|
16657
16708
|
"faceCrop",
|
|
16658
16709
|
"plateCrop",
|
|
16659
16710
|
"keyFrame",
|
|
16660
|
-
"keyFrameSmall"
|
|
16711
|
+
"keyFrameSmall",
|
|
16712
|
+
"thumbnailSmall"
|
|
16661
16713
|
]);
|
|
16662
16714
|
var MediaFileSchema = object({
|
|
16663
16715
|
key: string(),
|
|
@@ -17169,7 +17221,16 @@ var PipelineTemplateSchema = object({
|
|
|
17169
17221
|
});
|
|
17170
17222
|
var DeviceStepConfigSchema = object({
|
|
17171
17223
|
modelId: string().optional(),
|
|
17172
|
-
settings: record(string(), unknown()).optional()
|
|
17224
|
+
settings: record(string(), unknown()).optional(),
|
|
17225
|
+
/**
|
|
17226
|
+
* Step-tree device jump (same-node, phase 1) — OPTIONAL manual override.
|
|
17227
|
+
* When set, this step runs on the named enabled device of the SAME node
|
|
17228
|
+
* (`<backend>:<device>`) instead of the camera's elected device, bypassing
|
|
17229
|
+
* the runtime AUTO-jump. The orchestrator rejects a `jumpDeviceKey` pointing
|
|
17230
|
+
* at a disabled/absent device on that node at save time. Absent ⇒ the runner
|
|
17231
|
+
* auto-jumps only when the effective device's format cannot run the step.
|
|
17232
|
+
*/
|
|
17233
|
+
jumpDeviceKey: string().optional()
|
|
17173
17234
|
});
|
|
17174
17235
|
var AgentPipelineSettingsSchema = object({
|
|
17175
17236
|
maxCameras: number().int().nonnegative().nullable().default(null),
|
|
@@ -11146,7 +11146,8 @@ var PipelineStepInputSchema = lazy(() => object({
|
|
|
11146
11146
|
modelId: string().optional(),
|
|
11147
11147
|
enabled: boolean().default(true),
|
|
11148
11148
|
children: array(PipelineStepInputSchema).optional(),
|
|
11149
|
-
settings: record(string(), unknown()).optional()
|
|
11149
|
+
settings: record(string(), unknown()).optional(),
|
|
11150
|
+
jumpDeviceKey: string().optional()
|
|
11150
11151
|
}));
|
|
11151
11152
|
var ModelSubstitutionSchema = object({
|
|
11152
11153
|
addonId: string(),
|
|
@@ -11452,8 +11453,24 @@ var NativeCropBboxSchema = object({
|
|
|
11452
11453
|
});
|
|
11453
11454
|
/** Result of a best-effort native-resolution crop (`getNativeCrop`). */
|
|
11454
11455
|
var NativeCropResultSchema = object({
|
|
11455
|
-
/**
|
|
11456
|
-
|
|
11456
|
+
/**
|
|
11457
|
+
* Packed rgb (24-bit) pixels of the crop. Present on the DEFAULT (raw) path —
|
|
11458
|
+
* same-node (in-process / UDS) callers get zero-copy RGB and encode locally.
|
|
11459
|
+
* OMITTED when the caller requested `encodeJpeg` (the cross-node compressed
|
|
11460
|
+
* path below), where shipping raw RGB is both an invariant violation and, for
|
|
11461
|
+
* a native full frame (~26 MB at 4K), larger than Moleculer's 10 MB
|
|
11462
|
+
* `maxPacketSize` → the packet is dropped and the call 60s-times-out. That is
|
|
11463
|
+
* the cross-node native-media miss: `bytes` is replaced by `jpeg`.
|
|
11464
|
+
*/
|
|
11465
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
11466
|
+
/**
|
|
11467
|
+
* Base64 JPEG of the crop — the COMPRESSED cross-node payload. The OWNING node
|
|
11468
|
+
* (which holds the native surface) encodes it in-process, so a native 4K frame
|
|
11469
|
+
* ships as ~0.5–2 MB (well under `maxPacketSize`) and NO raw pixels ever cross
|
|
11470
|
+
* a process boundary (CLAUDE.md invariant #21). Present ONLY when the request
|
|
11471
|
+
* set `encodeJpeg: true`; `bytes` is then absent.
|
|
11472
|
+
*/
|
|
11473
|
+
jpeg: string().optional(),
|
|
11457
11474
|
width: number().int().positive(),
|
|
11458
11475
|
height: number().int().positive(),
|
|
11459
11476
|
/**
|
|
@@ -11599,6 +11616,20 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
|
|
|
11599
11616
|
hubHostnameOverride: string().optional()
|
|
11600
11617
|
})]).describe("Per-camera frame-source mode for the runner (P2c)");
|
|
11601
11618
|
/**
|
|
11619
|
+
* One ENABLED inference device on the runner's node, as the step-tree
|
|
11620
|
+
* device-jump resolver sees it (phase 1). The orchestrator populates this
|
|
11621
|
+
* roster on the attach payload whenever the camera is elected onto a specific
|
|
11622
|
+
* `deviceKey` and the node has ≥2 enabled devices — it is the candidate set the
|
|
11623
|
+
* runner auto-jumps an enrichment step to when the elected device's format
|
|
11624
|
+
* cannot run that step's model. `weight`/`maxSessions` mirror the balancer's
|
|
11625
|
+
* per-device knobs so the auto choice is weighted-least-loaded.
|
|
11626
|
+
*/
|
|
11627
|
+
var RunnerInferenceDeviceSchema = object({
|
|
11628
|
+
deviceKey: string(),
|
|
11629
|
+
weight: number().positive().default(1),
|
|
11630
|
+
maxSessions: number().int().positive().nullable().default(null)
|
|
11631
|
+
});
|
|
11632
|
+
/**
|
|
11602
11633
|
* Camera assignment payload sent by `addon-pipeline-orchestrator` to a
|
|
11603
11634
|
* specific runner instance via `attachCamera`. Carries everything the
|
|
11604
11635
|
* runner needs to subscribe to the local broker and execute inference.
|
|
@@ -11716,7 +11747,16 @@ var RunnerCameraConfigSchema = object({
|
|
|
11716
11747
|
* omitted ⇒ the runner's default device. The engine itself stays node-local —
|
|
11717
11748
|
* this only selects WHICH device pool of that node runs the session.
|
|
11718
11749
|
*/
|
|
11719
|
-
deviceKey: string().optional()
|
|
11750
|
+
deviceKey: string().optional(),
|
|
11751
|
+
/**
|
|
11752
|
+
* Step-tree device-jump roster (phase 1): the node's ENABLED inference
|
|
11753
|
+
* devices, populated by the orchestrator ONLY when `deviceKey` is set and the
|
|
11754
|
+
* node has ≥2 enabled devices. The runner uses it to AUTO-jump an enrichment
|
|
11755
|
+
* step whose model has no build for the elected device's format onto another
|
|
11756
|
+
* enabled device of the SAME node (weighted-least-loaded). Absent/single-entry
|
|
11757
|
+
* ⇒ no jump possible; the step runs on `deviceKey`.
|
|
11758
|
+
*/
|
|
11759
|
+
inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
|
|
11720
11760
|
});
|
|
11721
11761
|
motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
|
|
11722
11762
|
/**
|
|
@@ -11772,7 +11812,18 @@ var RunnerLocalMetricsSchema = object({
|
|
|
11772
11812
|
method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
|
|
11773
11813
|
handle: FrameHandleSchema,
|
|
11774
11814
|
bbox: NativeCropBboxSchema,
|
|
11775
|
-
maxWidth: number().int().positive().optional()
|
|
11815
|
+
maxWidth: number().int().positive().optional(),
|
|
11816
|
+
/**
|
|
11817
|
+
* When `true`, the runner encodes the resolved crop to JPEG ON THE
|
|
11818
|
+
* OWNING NODE and returns it in `jpeg` (base64) INSTEAD of raw `bytes`.
|
|
11819
|
+
* Callers set this for CROSS-NODE fetches (`handle.nodeId` is a remote
|
|
11820
|
+
* agent) so the compressed payload fits Moleculer's `maxPacketSize` and
|
|
11821
|
+
* no raw pixels cross the process boundary. Same-node callers omit it
|
|
11822
|
+
* and keep the zero-copy raw `bytes` path. Additive + optional: a
|
|
11823
|
+
* pre-encode runner (version skew) ignores it and returns `bytes`, so
|
|
11824
|
+
* the caller falls back to encoding locally.
|
|
11825
|
+
*/
|
|
11826
|
+
encodeJpeg: boolean().optional()
|
|
11776
11827
|
}), NativeCropResultSchema.nullable()), method(object({
|
|
11777
11828
|
deviceId: number(),
|
|
11778
11829
|
frameHandle: FrameHandleSchema.optional(),
|
|
@@ -16635,7 +16686,8 @@ var MediaFileKindEnum = _enum([
|
|
|
16635
16686
|
"faceCrop",
|
|
16636
16687
|
"plateCrop",
|
|
16637
16688
|
"keyFrame",
|
|
16638
|
-
"keyFrameSmall"
|
|
16689
|
+
"keyFrameSmall",
|
|
16690
|
+
"thumbnailSmall"
|
|
16639
16691
|
]);
|
|
16640
16692
|
var MediaFileSchema = object({
|
|
16641
16693
|
key: string(),
|
|
@@ -17147,7 +17199,16 @@ var PipelineTemplateSchema = object({
|
|
|
17147
17199
|
});
|
|
17148
17200
|
var DeviceStepConfigSchema = object({
|
|
17149
17201
|
modelId: string().optional(),
|
|
17150
|
-
settings: record(string(), unknown()).optional()
|
|
17202
|
+
settings: record(string(), unknown()).optional(),
|
|
17203
|
+
/**
|
|
17204
|
+
* Step-tree device jump (same-node, phase 1) — OPTIONAL manual override.
|
|
17205
|
+
* When set, this step runs on the named enabled device of the SAME node
|
|
17206
|
+
* (`<backend>:<device>`) instead of the camera's elected device, bypassing
|
|
17207
|
+
* the runtime AUTO-jump. The orchestrator rejects a `jumpDeviceKey` pointing
|
|
17208
|
+
* at a disabled/absent device on that node at save time. Absent ⇒ the runner
|
|
17209
|
+
* auto-jumps only when the effective device's format cannot run the step.
|
|
17210
|
+
*/
|
|
17211
|
+
jumpDeviceKey: string().optional()
|
|
17151
17212
|
});
|
|
17152
17213
|
var AgentPipelineSettingsSchema = object({
|
|
17153
17214
|
maxCameras: number().int().nonnegative().nullable().default(null),
|
|
@@ -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-Ck79MtSp.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_fs$1 = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
node_fs = require_dist.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as BaseAddon, f as hfModelUrl, u as embeddingEncoderCapability } from "../dist-
|
|
1
|
+
import { b as BaseAddon, f as hfModelUrl, u as embeddingEncoderCapability } from "../dist-gQ5DHTYd.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.3",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.3",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.3",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.3",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.2.
|
|
48
|
+
version: "1.2.3",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.3",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -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-Ck79MtSp.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
let sharp = require("sharp");
|
|
8
8
|
sharp = require_dist.__toESM(sharp);
|
|
@@ -4497,6 +4497,7 @@ var SINGLE_INSTANCE_KINDS = new Set([
|
|
|
4497
4497
|
"keyFrame",
|
|
4498
4498
|
"keyFrameSmall",
|
|
4499
4499
|
"thumbnail",
|
|
4500
|
+
"thumbnailSmall",
|
|
4500
4501
|
"firstFrame",
|
|
4501
4502
|
"lastFrame"
|
|
4502
4503
|
]);
|
|
@@ -6477,6 +6478,76 @@ function squareSafeCropRegionNormalized(bbox, frame, padding) {
|
|
|
6477
6478
|
};
|
|
6478
6479
|
}
|
|
6479
6480
|
//#endregion
|
|
6481
|
+
//#region src/shared/frame/square-subject-crop.ts
|
|
6482
|
+
/** Expansion factor applied to the bbox long side to frame the subject with a
|
|
6483
|
+
* little breathing room (operator choice: ×1.2). */
|
|
6484
|
+
var SQUARE_SUBJECT_CROP_EXPANSION = 1.2;
|
|
6485
|
+
/**
|
|
6486
|
+
* Compute a SQUARE crop region in PIXEL space around the subject bbox.
|
|
6487
|
+
*
|
|
6488
|
+
* Algorithm:
|
|
6489
|
+
* 1. center: cx = x + w/2, cy = y + h/2
|
|
6490
|
+
* 2. side = max(w, h) × SQUARE_SUBJECT_CROP_EXPANSION
|
|
6491
|
+
* 3. clamp side to fit the frame: side = min(side, W, H) — a square can never
|
|
6492
|
+
* exceed the frame's SHORT edge (this is the "subject bigger than the
|
|
6493
|
+
* frame's short side" case)
|
|
6494
|
+
* 4. center-and-clamp the origin so the square stays fully inside the frame
|
|
6495
|
+
* 5. round to integer pixels
|
|
6496
|
+
*
|
|
6497
|
+
* The returned region fully contains the bbox whenever the bbox itself fits in a
|
|
6498
|
+
* square of the frame's short side (always true for real detections).
|
|
6499
|
+
*/
|
|
6500
|
+
function squareSubjectCropRegion(bbox, frame) {
|
|
6501
|
+
const { W, H } = frame;
|
|
6502
|
+
const cx = bbox.x + bbox.w / 2;
|
|
6503
|
+
const cy = bbox.y + bbox.h / 2;
|
|
6504
|
+
const longerSide = Math.max(bbox.w, bbox.h);
|
|
6505
|
+
const side = Math.min(longerSide * SQUARE_SUBJECT_CROP_EXPANSION, W, H);
|
|
6506
|
+
const rawX0 = cx - side / 2;
|
|
6507
|
+
const rawY0 = cy - side / 2;
|
|
6508
|
+
const x0 = Math.max(0, Math.min(rawX0, W - side));
|
|
6509
|
+
const y0 = Math.max(0, Math.min(rawY0, H - side));
|
|
6510
|
+
return {
|
|
6511
|
+
x: Math.round(x0),
|
|
6512
|
+
y: Math.round(y0),
|
|
6513
|
+
w: Math.round(side),
|
|
6514
|
+
h: Math.round(side)
|
|
6515
|
+
};
|
|
6516
|
+
}
|
|
6517
|
+
/**
|
|
6518
|
+
* The same SQUARE region as {@link squareSubjectCropRegion}, expressed in
|
|
6519
|
+
* NORMALIZED [0,1]×[0,1] coordinates. A normalized box maps DIRECTLY onto a
|
|
6520
|
+
* native-resolution surface of the SAME aspect ratio, so the region computed
|
|
6521
|
+
* from the detection-frame dimensions addresses the exact same ROI on the
|
|
6522
|
+
* runner's retained native frame. Reuses the pixel geometry verbatim (single
|
|
6523
|
+
* source of truth) and divides by the frame dimensions.
|
|
6524
|
+
*/
|
|
6525
|
+
function squareSubjectCropRegionNormalized(bbox, frame) {
|
|
6526
|
+
const region = squareSubjectCropRegion(bbox, frame);
|
|
6527
|
+
return {
|
|
6528
|
+
x: region.x / frame.W,
|
|
6529
|
+
y: region.y / frame.H,
|
|
6530
|
+
w: region.w / frame.W,
|
|
6531
|
+
h: region.h / frame.H
|
|
6532
|
+
};
|
|
6533
|
+
}
|
|
6534
|
+
/**
|
|
6535
|
+
* Derive the `thumbnailSmall` JPEG from an already-encoded native subject-crop
|
|
6536
|
+
* JPEG. Downscales to at most {@link THUMBNAIL_SMALL_MAX_WIDTH} on the long side
|
|
6537
|
+
* (aspect preserved). If the native crop's long side is already ≤ the cap, the
|
|
6538
|
+
* ORIGINAL buffer is returned unchanged (store as-is, never upscale, no wasted
|
|
6539
|
+
* re-encode).
|
|
6540
|
+
*/
|
|
6541
|
+
async function deriveThumbnailSmall(nativeJpeg) {
|
|
6542
|
+
const meta = await (0, sharp.default)(nativeJpeg).metadata();
|
|
6543
|
+
const longSide = Math.max(meta.width ?? 0, meta.height ?? 0);
|
|
6544
|
+
if (longSide > 0 && longSide <= 480) return nativeJpeg;
|
|
6545
|
+
return (0, sharp.default)(nativeJpeg).resize(480, 480, {
|
|
6546
|
+
fit: "inside",
|
|
6547
|
+
withoutEnlargement: true
|
|
6548
|
+
}).jpeg({ quality: 88 }).toBuffer();
|
|
6549
|
+
}
|
|
6550
|
+
//#endregion
|
|
6480
6551
|
//#region src/shared/frame/box-drawer.ts
|
|
6481
6552
|
var DEFAULT_COLOR = require_dist.DEFAULT_EVENT_COLOR;
|
|
6482
6553
|
var DEFAULT_QUALITY = 80;
|
|
@@ -6702,7 +6773,7 @@ var EventMediaDispatcher = class {
|
|
|
6702
6773
|
const storedSnapshots = [];
|
|
6703
6774
|
const thumbnailTrackIds = [];
|
|
6704
6775
|
for (const sn of snapshots) {
|
|
6705
|
-
const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn
|
|
6776
|
+
const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn);
|
|
6706
6777
|
if (res.storedSnapshot) storedSnapshots.push(res.storedSnapshot);
|
|
6707
6778
|
if (res.thumbnailWritten) thumbnailTrackIds.push(sn.trackId);
|
|
6708
6779
|
}
|
|
@@ -6723,7 +6794,7 @@ var EventMediaDispatcher = class {
|
|
|
6723
6794
|
* failed). `thumbnailWritten` reports whether a best `thumbnail` actually
|
|
6724
6795
|
* landed this frame (#27-A) so the caller can stop forcing retries.
|
|
6725
6796
|
*/
|
|
6726
|
-
async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn
|
|
6797
|
+
async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn) {
|
|
6727
6798
|
if (!sn.appendSnapshot && !sn.rollingLastFrame && !sn.bestThumbnail) return {
|
|
6728
6799
|
storedSnapshot: null,
|
|
6729
6800
|
thumbnailWritten: false
|
|
@@ -6750,8 +6821,11 @@ var EventMediaDispatcher = class {
|
|
|
6750
6821
|
if (sn.rollingLastFrame && boxed) await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
6751
6822
|
let thumbnailWritten = false;
|
|
6752
6823
|
if (sn.bestThumbnail) {
|
|
6753
|
-
const
|
|
6754
|
-
if (
|
|
6824
|
+
const variants = await this.cropSubjectVariants(frameHandle, fw, fh, sn.bbox);
|
|
6825
|
+
if (variants) {
|
|
6826
|
+
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", sn.timestamp, variants.thumbnail);
|
|
6827
|
+
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", sn.timestamp, variants.thumbnailSmall);
|
|
6828
|
+
}
|
|
6755
6829
|
}
|
|
6756
6830
|
return {
|
|
6757
6831
|
storedSnapshot: stored,
|
|
@@ -6801,6 +6875,54 @@ var EventMediaDispatcher = class {
|
|
|
6801
6875
|
}
|
|
6802
6876
|
}
|
|
6803
6877
|
/**
|
|
6878
|
+
* The best-shot subject crop as its TWO persisted variants (best-crop
|
|
6879
|
+
* fast-load, 2026-07-21). ONE native fetch: the SQUARE-framed subject ROI
|
|
6880
|
+
* (side = max(w,h)×1.2, clamped to the frame — {@link squareSubjectCropRegionNormalized})
|
|
6881
|
+
* is requested from the runner's retained native surface with NO `maxWidth`
|
|
6882
|
+
* (uncapped TRUE native, decision #3) → the `thumbnail`. The `thumbnailSmall`
|
|
6883
|
+
* is DERIVED by downscaling that SAME JPEG to the 480 long-side cap — never a
|
|
6884
|
+
* second `getNativeCropJpeg`, never upscaled ({@link deriveThumbnailSmall}
|
|
6885
|
+
* returns the native buffer as-is when it is already ≤ 480).
|
|
6886
|
+
*
|
|
6887
|
+
* NATIVE-OR-NOTHING: any miss/error (or a runner without the method) returns
|
|
6888
|
+
* `null` after a loud `logger.warn`; the caller SKIPS the write and the
|
|
6889
|
+
* per-frame retry lands a real native crop later. Never a local resize
|
|
6890
|
+
* upscale of a ≤640 tile (a blurred lie).
|
|
6891
|
+
*/
|
|
6892
|
+
async cropSubjectVariants(frameHandle, fw, fh, bbox) {
|
|
6893
|
+
if (!this.deps.getNativeCropJpeg) {
|
|
6894
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
6895
|
+
shmId: frameHandle.shmId,
|
|
6896
|
+
reason: "no-native-cap"
|
|
6897
|
+
} });
|
|
6898
|
+
return null;
|
|
6899
|
+
}
|
|
6900
|
+
try {
|
|
6901
|
+
const norm = squareSubjectCropRegionNormalized(bbox, {
|
|
6902
|
+
W: fw,
|
|
6903
|
+
H: fh
|
|
6904
|
+
});
|
|
6905
|
+
const native = await this.deps.getNativeCropJpeg(frameHandle, norm);
|
|
6906
|
+
if (!native) {
|
|
6907
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
6908
|
+
shmId: frameHandle.shmId,
|
|
6909
|
+
reason: "native-miss"
|
|
6910
|
+
} });
|
|
6911
|
+
return null;
|
|
6912
|
+
}
|
|
6913
|
+
return {
|
|
6914
|
+
thumbnail: native,
|
|
6915
|
+
thumbnailSmall: await deriveThumbnailSmall(native)
|
|
6916
|
+
};
|
|
6917
|
+
} catch (err) {
|
|
6918
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
6919
|
+
shmId: frameHandle.shmId,
|
|
6920
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6921
|
+
} });
|
|
6922
|
+
return null;
|
|
6923
|
+
}
|
|
6924
|
+
}
|
|
6925
|
+
/**
|
|
6804
6926
|
* The boxed timeline tile (`firstFrame`/`snapshot`/`lastFrame`): the track's
|
|
6805
6927
|
* OWN box burned onto the NATIVE full frame downscaled to
|
|
6806
6928
|
* {@link SNAPSHOT_MAX_WIDTH}. On a native miss it falls back to the ≤640
|
|
@@ -11608,6 +11730,21 @@ async function encodeRgbCropToJpeg(bytes, width, height) {
|
|
|
11608
11730
|
channels: 3
|
|
11609
11731
|
} }).jpeg({ quality: 90 }).toBuffer();
|
|
11610
11732
|
}
|
|
11733
|
+
/**
|
|
11734
|
+
* Decode a base64 JPEG (the COMPRESSED cross-node crop the owning runner
|
|
11735
|
+
* returns) back into raw RGB (24-bit) pixels, so every downstream consumer
|
|
11736
|
+
* keeps its existing RGB contract regardless of whether the crop was fetched
|
|
11737
|
+
* same-node (raw) or cross-node (JPEG). The decode happens IN-PROCESS on the
|
|
11738
|
+
* post-processing node — no raw pixels ever cross a process boundary.
|
|
11739
|
+
*/
|
|
11740
|
+
async function decodeJpegToRgb(base64Jpeg) {
|
|
11741
|
+
const { data, info } = await (0, sharp.default)(Buffer.from(base64Jpeg, "base64")).raw().toBuffer({ resolveWithObject: true });
|
|
11742
|
+
return {
|
|
11743
|
+
bytes: data,
|
|
11744
|
+
width: info.width,
|
|
11745
|
+
height: info.height
|
|
11746
|
+
};
|
|
11747
|
+
}
|
|
11611
11748
|
//#endregion
|
|
11612
11749
|
//#region src/pipeline-analytics/pipeline/event-child-crops.ts
|
|
11613
11750
|
/**
|
|
@@ -11982,10 +12119,11 @@ function pickEventOwnedMedia(files) {
|
|
|
11982
12119
|
return files.find((f) => f.kind === "crop") ?? files.find((f) => f.kind === "fullFrameBoxed") ?? files[0];
|
|
11983
12120
|
}
|
|
11984
12121
|
/** Pick a track's fallback media in the shared cadence-preference order the
|
|
11985
|
-
* KeyEvent path uses:
|
|
11986
|
-
*
|
|
12122
|
+
* KeyEvent path uses: `thumbnailSmall` (480 fast-load) → best `thumbnail` →
|
|
12123
|
+
* rolling `lastFrame` → `firstFrame` → newest `snapshot` → any track blob.
|
|
12124
|
+
* Undefined when the track owns no media. */
|
|
11987
12125
|
function pickTrackFallbackMedia(files) {
|
|
11988
|
-
return files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
12126
|
+
return files.find((f) => f.kind === "thumbnailSmall") ?? files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
11989
12127
|
}
|
|
11990
12128
|
/**
|
|
11991
12129
|
* Resolve the default-path media for an event id: the event's own media when it
|
|
@@ -12401,7 +12539,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12401
12539
|
let storage = this.ctx.kernel.storage;
|
|
12402
12540
|
const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
|
|
12403
12541
|
if (mediaRoot) {
|
|
12404
|
-
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node
|
|
12542
|
+
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-C4bKtLou.js"));
|
|
12405
12543
|
storage = new FilesystemStorageProvider(mediaRoot);
|
|
12406
12544
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
12407
12545
|
}
|
|
@@ -12548,6 +12686,23 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12548
12686
|
} });
|
|
12549
12687
|
}
|
|
12550
12688
|
const pipelineRunnerApi = api.pipelineRunner;
|
|
12689
|
+
const isRemoteHandle = (handle) => handle.nodeId !== ownNodeId;
|
|
12690
|
+
const cropReplyToRgb = async (reply) => {
|
|
12691
|
+
if (reply.jpeg !== void 0) {
|
|
12692
|
+
const rgb = await decodeJpegToRgb(reply.jpeg);
|
|
12693
|
+
return {
|
|
12694
|
+
bytes: Buffer.from(rgb.bytes),
|
|
12695
|
+
width: rgb.width,
|
|
12696
|
+
height: rgb.height
|
|
12697
|
+
};
|
|
12698
|
+
}
|
|
12699
|
+
if (reply.bytes !== void 0) return {
|
|
12700
|
+
bytes: Buffer.from(reply.bytes),
|
|
12701
|
+
width: reply.width,
|
|
12702
|
+
height: reply.height
|
|
12703
|
+
};
|
|
12704
|
+
return null;
|
|
12705
|
+
};
|
|
12551
12706
|
const getRemoteFrame = async (handle) => {
|
|
12552
12707
|
if (!pipelineRunnerApi?.getNativeCrop) return null;
|
|
12553
12708
|
const full = await pipelineRunnerApi.getNativeCrop.query({
|
|
@@ -12558,13 +12713,16 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12558
12713
|
w: 1,
|
|
12559
12714
|
h: 1
|
|
12560
12715
|
},
|
|
12561
|
-
maxWidth: handle.width
|
|
12716
|
+
maxWidth: handle.width,
|
|
12717
|
+
encodeJpeg: isRemoteHandle(handle)
|
|
12562
12718
|
}, require_dist.nodePin(handle.nodeId));
|
|
12563
12719
|
if (!full || full.width <= 0 || full.height <= 0) return null;
|
|
12720
|
+
const rgb = await cropReplyToRgb(full);
|
|
12721
|
+
if (!rgb) return null;
|
|
12564
12722
|
return {
|
|
12565
|
-
data:
|
|
12566
|
-
width:
|
|
12567
|
-
height:
|
|
12723
|
+
data: rgb.bytes,
|
|
12724
|
+
width: rgb.width,
|
|
12725
|
+
height: rgb.height,
|
|
12568
12726
|
format: "rgb",
|
|
12569
12727
|
timestamp: 0
|
|
12570
12728
|
};
|
|
@@ -12579,15 +12737,18 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12579
12737
|
w: 1,
|
|
12580
12738
|
h: 1
|
|
12581
12739
|
},
|
|
12582
|
-
maxWidth
|
|
12740
|
+
maxWidth,
|
|
12741
|
+
encodeJpeg: isRemoteHandle(handle)
|
|
12583
12742
|
}, require_dist.nodePin(handle.nodeId));
|
|
12584
12743
|
if (!full || full.width <= 0 || full.height <= 0) return null;
|
|
12744
|
+
const rgb = await cropReplyToRgb(full);
|
|
12745
|
+
if (!rgb) return null;
|
|
12585
12746
|
const tier = full.tier === "ram-fullframe" ? "ram-fullframe" : "native";
|
|
12586
12747
|
return {
|
|
12587
12748
|
frame: {
|
|
12588
|
-
data:
|
|
12589
|
-
width:
|
|
12590
|
-
height:
|
|
12749
|
+
data: rgb.bytes,
|
|
12750
|
+
width: rgb.width,
|
|
12751
|
+
height: rgb.height,
|
|
12591
12752
|
format: "rgb",
|
|
12592
12753
|
timestamp: 0
|
|
12593
12754
|
},
|
|
@@ -12618,14 +12779,11 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12618
12779
|
const native = await pipelineRunnerApi.getNativeCrop.query({
|
|
12619
12780
|
handle: frameHandle,
|
|
12620
12781
|
bbox: paddedNorm,
|
|
12621
|
-
...maxWidth !== void 0 ? { maxWidth } : {}
|
|
12782
|
+
...maxWidth !== void 0 ? { maxWidth } : {},
|
|
12783
|
+
encodeJpeg: isRemoteHandle(frameHandle)
|
|
12622
12784
|
}, require_dist.nodePin(frameHandle.nodeId));
|
|
12623
12785
|
if (!native || native.width <= 0 || native.height <= 0) return null;
|
|
12624
|
-
return
|
|
12625
|
-
bytes: Buffer.from(native.bytes),
|
|
12626
|
-
width: native.width,
|
|
12627
|
-
height: native.height
|
|
12628
|
-
};
|
|
12786
|
+
return await cropReplyToRgb(native);
|
|
12629
12787
|
} catch (err) {
|
|
12630
12788
|
cropMetricLogger.debug("native crop request failed — falling back", { meta: { error: require_dist.errMsg(err) } });
|
|
12631
12789
|
return null;
|
|
@@ -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-gQ5DHTYd.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import sharp from "sharp";
|
|
4
4
|
//#region src/pipeline-analytics/videoclips-provider.ts
|
|
@@ -4492,6 +4492,7 @@ var SINGLE_INSTANCE_KINDS = new Set([
|
|
|
4492
4492
|
"keyFrame",
|
|
4493
4493
|
"keyFrameSmall",
|
|
4494
4494
|
"thumbnail",
|
|
4495
|
+
"thumbnailSmall",
|
|
4495
4496
|
"firstFrame",
|
|
4496
4497
|
"lastFrame"
|
|
4497
4498
|
]);
|
|
@@ -6472,6 +6473,76 @@ function squareSafeCropRegionNormalized(bbox, frame, padding) {
|
|
|
6472
6473
|
};
|
|
6473
6474
|
}
|
|
6474
6475
|
//#endregion
|
|
6476
|
+
//#region src/shared/frame/square-subject-crop.ts
|
|
6477
|
+
/** Expansion factor applied to the bbox long side to frame the subject with a
|
|
6478
|
+
* little breathing room (operator choice: ×1.2). */
|
|
6479
|
+
var SQUARE_SUBJECT_CROP_EXPANSION = 1.2;
|
|
6480
|
+
/**
|
|
6481
|
+
* Compute a SQUARE crop region in PIXEL space around the subject bbox.
|
|
6482
|
+
*
|
|
6483
|
+
* Algorithm:
|
|
6484
|
+
* 1. center: cx = x + w/2, cy = y + h/2
|
|
6485
|
+
* 2. side = max(w, h) × SQUARE_SUBJECT_CROP_EXPANSION
|
|
6486
|
+
* 3. clamp side to fit the frame: side = min(side, W, H) — a square can never
|
|
6487
|
+
* exceed the frame's SHORT edge (this is the "subject bigger than the
|
|
6488
|
+
* frame's short side" case)
|
|
6489
|
+
* 4. center-and-clamp the origin so the square stays fully inside the frame
|
|
6490
|
+
* 5. round to integer pixels
|
|
6491
|
+
*
|
|
6492
|
+
* The returned region fully contains the bbox whenever the bbox itself fits in a
|
|
6493
|
+
* square of the frame's short side (always true for real detections).
|
|
6494
|
+
*/
|
|
6495
|
+
function squareSubjectCropRegion(bbox, frame) {
|
|
6496
|
+
const { W, H } = frame;
|
|
6497
|
+
const cx = bbox.x + bbox.w / 2;
|
|
6498
|
+
const cy = bbox.y + bbox.h / 2;
|
|
6499
|
+
const longerSide = Math.max(bbox.w, bbox.h);
|
|
6500
|
+
const side = Math.min(longerSide * SQUARE_SUBJECT_CROP_EXPANSION, W, H);
|
|
6501
|
+
const rawX0 = cx - side / 2;
|
|
6502
|
+
const rawY0 = cy - side / 2;
|
|
6503
|
+
const x0 = Math.max(0, Math.min(rawX0, W - side));
|
|
6504
|
+
const y0 = Math.max(0, Math.min(rawY0, H - side));
|
|
6505
|
+
return {
|
|
6506
|
+
x: Math.round(x0),
|
|
6507
|
+
y: Math.round(y0),
|
|
6508
|
+
w: Math.round(side),
|
|
6509
|
+
h: Math.round(side)
|
|
6510
|
+
};
|
|
6511
|
+
}
|
|
6512
|
+
/**
|
|
6513
|
+
* The same SQUARE region as {@link squareSubjectCropRegion}, expressed in
|
|
6514
|
+
* NORMALIZED [0,1]×[0,1] coordinates. A normalized box maps DIRECTLY onto a
|
|
6515
|
+
* native-resolution surface of the SAME aspect ratio, so the region computed
|
|
6516
|
+
* from the detection-frame dimensions addresses the exact same ROI on the
|
|
6517
|
+
* runner's retained native frame. Reuses the pixel geometry verbatim (single
|
|
6518
|
+
* source of truth) and divides by the frame dimensions.
|
|
6519
|
+
*/
|
|
6520
|
+
function squareSubjectCropRegionNormalized(bbox, frame) {
|
|
6521
|
+
const region = squareSubjectCropRegion(bbox, frame);
|
|
6522
|
+
return {
|
|
6523
|
+
x: region.x / frame.W,
|
|
6524
|
+
y: region.y / frame.H,
|
|
6525
|
+
w: region.w / frame.W,
|
|
6526
|
+
h: region.h / frame.H
|
|
6527
|
+
};
|
|
6528
|
+
}
|
|
6529
|
+
/**
|
|
6530
|
+
* Derive the `thumbnailSmall` JPEG from an already-encoded native subject-crop
|
|
6531
|
+
* JPEG. Downscales to at most {@link THUMBNAIL_SMALL_MAX_WIDTH} on the long side
|
|
6532
|
+
* (aspect preserved). If the native crop's long side is already ≤ the cap, the
|
|
6533
|
+
* ORIGINAL buffer is returned unchanged (store as-is, never upscale, no wasted
|
|
6534
|
+
* re-encode).
|
|
6535
|
+
*/
|
|
6536
|
+
async function deriveThumbnailSmall(nativeJpeg) {
|
|
6537
|
+
const meta = await sharp(nativeJpeg).metadata();
|
|
6538
|
+
const longSide = Math.max(meta.width ?? 0, meta.height ?? 0);
|
|
6539
|
+
if (longSide > 0 && longSide <= 480) return nativeJpeg;
|
|
6540
|
+
return sharp(nativeJpeg).resize(480, 480, {
|
|
6541
|
+
fit: "inside",
|
|
6542
|
+
withoutEnlargement: true
|
|
6543
|
+
}).jpeg({ quality: 88 }).toBuffer();
|
|
6544
|
+
}
|
|
6545
|
+
//#endregion
|
|
6475
6546
|
//#region src/shared/frame/box-drawer.ts
|
|
6476
6547
|
var DEFAULT_COLOR = DEFAULT_EVENT_COLOR;
|
|
6477
6548
|
var DEFAULT_QUALITY = 80;
|
|
@@ -6697,7 +6768,7 @@ var EventMediaDispatcher = class {
|
|
|
6697
6768
|
const storedSnapshots = [];
|
|
6698
6769
|
const thumbnailTrackIds = [];
|
|
6699
6770
|
for (const sn of snapshots) {
|
|
6700
|
-
const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn
|
|
6771
|
+
const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn);
|
|
6701
6772
|
if (res.storedSnapshot) storedSnapshots.push(res.storedSnapshot);
|
|
6702
6773
|
if (res.thumbnailWritten) thumbnailTrackIds.push(sn.trackId);
|
|
6703
6774
|
}
|
|
@@ -6718,7 +6789,7 @@ var EventMediaDispatcher = class {
|
|
|
6718
6789
|
* failed). `thumbnailWritten` reports whether a best `thumbnail` actually
|
|
6719
6790
|
* landed this frame (#27-A) so the caller can stop forcing retries.
|
|
6720
6791
|
*/
|
|
6721
|
-
async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn
|
|
6792
|
+
async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn) {
|
|
6722
6793
|
if (!sn.appendSnapshot && !sn.rollingLastFrame && !sn.bestThumbnail) return {
|
|
6723
6794
|
storedSnapshot: null,
|
|
6724
6795
|
thumbnailWritten: false
|
|
@@ -6745,8 +6816,11 @@ var EventMediaDispatcher = class {
|
|
|
6745
6816
|
if (sn.rollingLastFrame && boxed) await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
6746
6817
|
let thumbnailWritten = false;
|
|
6747
6818
|
if (sn.bestThumbnail) {
|
|
6748
|
-
const
|
|
6749
|
-
if (
|
|
6819
|
+
const variants = await this.cropSubjectVariants(frameHandle, fw, fh, sn.bbox);
|
|
6820
|
+
if (variants) {
|
|
6821
|
+
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", sn.timestamp, variants.thumbnail);
|
|
6822
|
+
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", sn.timestamp, variants.thumbnailSmall);
|
|
6823
|
+
}
|
|
6750
6824
|
}
|
|
6751
6825
|
return {
|
|
6752
6826
|
storedSnapshot: stored,
|
|
@@ -6796,6 +6870,54 @@ var EventMediaDispatcher = class {
|
|
|
6796
6870
|
}
|
|
6797
6871
|
}
|
|
6798
6872
|
/**
|
|
6873
|
+
* The best-shot subject crop as its TWO persisted variants (best-crop
|
|
6874
|
+
* fast-load, 2026-07-21). ONE native fetch: the SQUARE-framed subject ROI
|
|
6875
|
+
* (side = max(w,h)×1.2, clamped to the frame — {@link squareSubjectCropRegionNormalized})
|
|
6876
|
+
* is requested from the runner's retained native surface with NO `maxWidth`
|
|
6877
|
+
* (uncapped TRUE native, decision #3) → the `thumbnail`. The `thumbnailSmall`
|
|
6878
|
+
* is DERIVED by downscaling that SAME JPEG to the 480 long-side cap — never a
|
|
6879
|
+
* second `getNativeCropJpeg`, never upscaled ({@link deriveThumbnailSmall}
|
|
6880
|
+
* returns the native buffer as-is when it is already ≤ 480).
|
|
6881
|
+
*
|
|
6882
|
+
* NATIVE-OR-NOTHING: any miss/error (or a runner without the method) returns
|
|
6883
|
+
* `null` after a loud `logger.warn`; the caller SKIPS the write and the
|
|
6884
|
+
* per-frame retry lands a real native crop later. Never a local resize
|
|
6885
|
+
* upscale of a ≤640 tile (a blurred lie).
|
|
6886
|
+
*/
|
|
6887
|
+
async cropSubjectVariants(frameHandle, fw, fh, bbox) {
|
|
6888
|
+
if (!this.deps.getNativeCropJpeg) {
|
|
6889
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
6890
|
+
shmId: frameHandle.shmId,
|
|
6891
|
+
reason: "no-native-cap"
|
|
6892
|
+
} });
|
|
6893
|
+
return null;
|
|
6894
|
+
}
|
|
6895
|
+
try {
|
|
6896
|
+
const norm = squareSubjectCropRegionNormalized(bbox, {
|
|
6897
|
+
W: fw,
|
|
6898
|
+
H: fh
|
|
6899
|
+
});
|
|
6900
|
+
const native = await this.deps.getNativeCropJpeg(frameHandle, norm);
|
|
6901
|
+
if (!native) {
|
|
6902
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
6903
|
+
shmId: frameHandle.shmId,
|
|
6904
|
+
reason: "native-miss"
|
|
6905
|
+
} });
|
|
6906
|
+
return null;
|
|
6907
|
+
}
|
|
6908
|
+
return {
|
|
6909
|
+
thumbnail: native,
|
|
6910
|
+
thumbnailSmall: await deriveThumbnailSmall(native)
|
|
6911
|
+
};
|
|
6912
|
+
} catch (err) {
|
|
6913
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
6914
|
+
shmId: frameHandle.shmId,
|
|
6915
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6916
|
+
} });
|
|
6917
|
+
return null;
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
/**
|
|
6799
6921
|
* The boxed timeline tile (`firstFrame`/`snapshot`/`lastFrame`): the track's
|
|
6800
6922
|
* OWN box burned onto the NATIVE full frame downscaled to
|
|
6801
6923
|
* {@link SNAPSHOT_MAX_WIDTH}. On a native miss it falls back to the ≤640
|
|
@@ -11603,6 +11725,21 @@ async function encodeRgbCropToJpeg(bytes, width, height) {
|
|
|
11603
11725
|
channels: 3
|
|
11604
11726
|
} }).jpeg({ quality: 90 }).toBuffer();
|
|
11605
11727
|
}
|
|
11728
|
+
/**
|
|
11729
|
+
* Decode a base64 JPEG (the COMPRESSED cross-node crop the owning runner
|
|
11730
|
+
* returns) back into raw RGB (24-bit) pixels, so every downstream consumer
|
|
11731
|
+
* keeps its existing RGB contract regardless of whether the crop was fetched
|
|
11732
|
+
* same-node (raw) or cross-node (JPEG). The decode happens IN-PROCESS on the
|
|
11733
|
+
* post-processing node — no raw pixels ever cross a process boundary.
|
|
11734
|
+
*/
|
|
11735
|
+
async function decodeJpegToRgb(base64Jpeg) {
|
|
11736
|
+
const { data, info } = await sharp(Buffer.from(base64Jpeg, "base64")).raw().toBuffer({ resolveWithObject: true });
|
|
11737
|
+
return {
|
|
11738
|
+
bytes: data,
|
|
11739
|
+
width: info.width,
|
|
11740
|
+
height: info.height
|
|
11741
|
+
};
|
|
11742
|
+
}
|
|
11606
11743
|
//#endregion
|
|
11607
11744
|
//#region src/pipeline-analytics/pipeline/event-child-crops.ts
|
|
11608
11745
|
/**
|
|
@@ -11977,10 +12114,11 @@ function pickEventOwnedMedia(files) {
|
|
|
11977
12114
|
return files.find((f) => f.kind === "crop") ?? files.find((f) => f.kind === "fullFrameBoxed") ?? files[0];
|
|
11978
12115
|
}
|
|
11979
12116
|
/** Pick a track's fallback media in the shared cadence-preference order the
|
|
11980
|
-
* KeyEvent path uses:
|
|
11981
|
-
*
|
|
12117
|
+
* KeyEvent path uses: `thumbnailSmall` (480 fast-load) → best `thumbnail` →
|
|
12118
|
+
* rolling `lastFrame` → `firstFrame` → newest `snapshot` → any track blob.
|
|
12119
|
+
* Undefined when the track owns no media. */
|
|
11982
12120
|
function pickTrackFallbackMedia(files) {
|
|
11983
|
-
return files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
12121
|
+
return files.find((f) => f.kind === "thumbnailSmall") ?? files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
11984
12122
|
}
|
|
11985
12123
|
/**
|
|
11986
12124
|
* Resolve the default-path media for an event id: the event's own media when it
|
|
@@ -12543,6 +12681,23 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12543
12681
|
} });
|
|
12544
12682
|
}
|
|
12545
12683
|
const pipelineRunnerApi = api.pipelineRunner;
|
|
12684
|
+
const isRemoteHandle = (handle) => handle.nodeId !== ownNodeId;
|
|
12685
|
+
const cropReplyToRgb = async (reply) => {
|
|
12686
|
+
if (reply.jpeg !== void 0) {
|
|
12687
|
+
const rgb = await decodeJpegToRgb(reply.jpeg);
|
|
12688
|
+
return {
|
|
12689
|
+
bytes: Buffer.from(rgb.bytes),
|
|
12690
|
+
width: rgb.width,
|
|
12691
|
+
height: rgb.height
|
|
12692
|
+
};
|
|
12693
|
+
}
|
|
12694
|
+
if (reply.bytes !== void 0) return {
|
|
12695
|
+
bytes: Buffer.from(reply.bytes),
|
|
12696
|
+
width: reply.width,
|
|
12697
|
+
height: reply.height
|
|
12698
|
+
};
|
|
12699
|
+
return null;
|
|
12700
|
+
};
|
|
12546
12701
|
const getRemoteFrame = async (handle) => {
|
|
12547
12702
|
if (!pipelineRunnerApi?.getNativeCrop) return null;
|
|
12548
12703
|
const full = await pipelineRunnerApi.getNativeCrop.query({
|
|
@@ -12553,13 +12708,16 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12553
12708
|
w: 1,
|
|
12554
12709
|
h: 1
|
|
12555
12710
|
},
|
|
12556
|
-
maxWidth: handle.width
|
|
12711
|
+
maxWidth: handle.width,
|
|
12712
|
+
encodeJpeg: isRemoteHandle(handle)
|
|
12557
12713
|
}, nodePin(handle.nodeId));
|
|
12558
12714
|
if (!full || full.width <= 0 || full.height <= 0) return null;
|
|
12715
|
+
const rgb = await cropReplyToRgb(full);
|
|
12716
|
+
if (!rgb) return null;
|
|
12559
12717
|
return {
|
|
12560
|
-
data:
|
|
12561
|
-
width:
|
|
12562
|
-
height:
|
|
12718
|
+
data: rgb.bytes,
|
|
12719
|
+
width: rgb.width,
|
|
12720
|
+
height: rgb.height,
|
|
12563
12721
|
format: "rgb",
|
|
12564
12722
|
timestamp: 0
|
|
12565
12723
|
};
|
|
@@ -12574,15 +12732,18 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12574
12732
|
w: 1,
|
|
12575
12733
|
h: 1
|
|
12576
12734
|
},
|
|
12577
|
-
maxWidth
|
|
12735
|
+
maxWidth,
|
|
12736
|
+
encodeJpeg: isRemoteHandle(handle)
|
|
12578
12737
|
}, nodePin(handle.nodeId));
|
|
12579
12738
|
if (!full || full.width <= 0 || full.height <= 0) return null;
|
|
12739
|
+
const rgb = await cropReplyToRgb(full);
|
|
12740
|
+
if (!rgb) return null;
|
|
12580
12741
|
const tier = full.tier === "ram-fullframe" ? "ram-fullframe" : "native";
|
|
12581
12742
|
return {
|
|
12582
12743
|
frame: {
|
|
12583
|
-
data:
|
|
12584
|
-
width:
|
|
12585
|
-
height:
|
|
12744
|
+
data: rgb.bytes,
|
|
12745
|
+
width: rgb.width,
|
|
12746
|
+
height: rgb.height,
|
|
12586
12747
|
format: "rgb",
|
|
12587
12748
|
timestamp: 0
|
|
12588
12749
|
},
|
|
@@ -12613,14 +12774,11 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12613
12774
|
const native = await pipelineRunnerApi.getNativeCrop.query({
|
|
12614
12775
|
handle: frameHandle,
|
|
12615
12776
|
bbox: paddedNorm,
|
|
12616
|
-
...maxWidth !== void 0 ? { maxWidth } : {}
|
|
12777
|
+
...maxWidth !== void 0 ? { maxWidth } : {},
|
|
12778
|
+
encodeJpeg: isRemoteHandle(frameHandle)
|
|
12617
12779
|
}, nodePin(frameHandle.nodeId));
|
|
12618
12780
|
if (!native || native.width <= 0 || native.height <= 0) return null;
|
|
12619
|
-
return
|
|
12620
|
-
bytes: Buffer.from(native.bytes),
|
|
12621
|
-
width: native.width,
|
|
12622
|
-
height: native.height
|
|
12623
|
-
};
|
|
12781
|
+
return await cropReplyToRgb(native);
|
|
12624
12782
|
} catch (err) {
|
|
12625
12783
|
cropMetricLogger.debug("native crop request failed — falling back", { meta: { error: errMsg(err) } });
|
|
12626
12784
|
return null;
|
|
@@ -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-C4dWm6ZL.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.3",
|
|
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",
|