@camstack/addon-pipeline 1.2.101 → 1.2.102
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/{addon-utils-eUS6n_Zj.js → addon-utils-CLc6yHCN.js} +1 -1
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +163 -42
- package/dist/detection-pipeline/index.mjs +161 -40
- package/dist/{dist-dm3t4BOt.js → dist-C11WuNUP.js} +87 -51
- package/dist/{dist-gXdWP96z.mjs → dist-Ccmt3fGJ.mjs} +82 -52
- package/dist/{event-loop-stall-monitor-Lki2_mYY.js → event-loop-stall-monitor-Cq_NeC4o.js} +175 -132
- package/dist/{event-loop-stall-monitor-TnQ21mu0.mjs → event-loop-stall-monitor-OJrOMeuu.mjs} +170 -133
- package/dist/{lazy-sharp-oYppDIZR.js → lazy-sharp-RxUs6on_.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +5 -4
- package/dist/pipeline-runner/index.mjs +4 -3
- package/dist/{process-memory-CKWh609B.mjs → process-memory-D0zDmXLI.mjs} +1 -1
- package/dist/{process-memory-vkFVOdkV.js → process-memory-DOjQ3MgC.js} +1 -1
- package/dist/recorder/index.js +2 -2
- package/dist/recorder/index.mjs +1 -1
- package/dist/session-decode/decode-worker-child.js +2 -2
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C4fRkYcj.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BdgcF1lL.mjs} +2 -2
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-zT-cYUdF.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DY31bUCj.mjs} +1 -1
- package/dist/stream-broker/{hostInit-CS6xpCdb.mjs → hostInit-Da9wVA2r.mjs} +2 -2
- package/dist/stream-broker/index.js +1 -1
- package/dist/stream-broker/index.mjs +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-BrlrMxXu.js → worker-protocol-BePduZVV.js} +1 -1
- package/dist/{worker-protocol-DJWdPLKF.mjs → worker-protocol-DDpliBIW.mjs} +1 -1
- package/package.json +1 -1
- package/python/postprocessors/ctc.py +56 -3
- package/python/postprocessors/test_ctc.py +37 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-
|
|
2
|
+
import { At as array, Bt as EventCategory, H as enumerateInferenceDevices, It as object, Ot as sleep, P as defaultDeviceFor$1, Rt as string, T as YAMNET_TO_MACRO, Tt as parseJsonUnknown, U as evaluateZoneRules, Z as pipelineExecutorCapability, bt as hydrateSchema, d as DEVICE_BACKEND_TO_FORMAT, ht as BaseAddon, it as runtimeDevices$1, lt as errMsg, nt as resolvePoolMemoryPolicy, st as supportedRuntimes$1, t as APPLE_SA_TO_MACRO, wt as nodePin, y as PoolMemoryWatchdog, yt as createEvent, z as detectionPipelineCapability, zt as union } from "../dist-Ccmt3fGJ.mjs";
|
|
3
|
+
import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep, u as landmarkPrecisionVerdict } from "../event-loop-stall-monitor-OJrOMeuu.mjs";
|
|
4
4
|
import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
5
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
5
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-D0zDmXLI.mjs";
|
|
6
6
|
import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-A2S9D7pu.mjs";
|
|
7
7
|
import sharp from "sharp";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
@@ -3033,7 +3033,9 @@ async function nativeChildCrop(provider, bbox, imageWidth, imageHeight, maxWidth
|
|
|
3033
3033
|
* peaky (a few high rows) rather than smeared across many rows.
|
|
3034
3034
|
* 3. pick the angle maximising the score (coarse 2° sweep + a 0.5° local
|
|
3035
3035
|
* refinement); apply it to the full-resolution RGB tile only when it is
|
|
3036
|
-
* meaningfully non-zero
|
|
3036
|
+
* meaningfully non-zero AND the optimum was BRACKETED by the sweep — an
|
|
3037
|
+
* argmax sitting on the boundary means the profile never turned over and
|
|
3038
|
+
* the "angle" is an artefact of where the search stopped.
|
|
3037
3039
|
*
|
|
3038
3040
|
* All of the geometry is pure functions over raw buffers, so the estimator is
|
|
3039
3041
|
* fully deterministic and unit-testable without any image I/O.
|
|
@@ -3212,22 +3214,28 @@ function scoreAngle(gradient, angleRad) {
|
|
|
3212
3214
|
height: rotated.height
|
|
3213
3215
|
});
|
|
3214
3216
|
}
|
|
3217
|
+
var DEFAULT_COARSE_STEP_DEG = 2;
|
|
3218
|
+
var NO_SKEW = {
|
|
3219
|
+
angleDeg: 0,
|
|
3220
|
+
score: 0
|
|
3221
|
+
};
|
|
3215
3222
|
/**
|
|
3216
3223
|
* Estimate the dominant text-baseline skew of a grayscale plate crop and return
|
|
3217
3224
|
* the CORRECTION angle (degrees) that levels it. A coarse sweep over
|
|
3218
3225
|
* ±`maxAngleDeg` in `coarseStepDeg` steps finds the best bucket; a `fineStepDeg`
|
|
3219
3226
|
* local search refines it. Returns `0` when the flat (no-rotation) profile is
|
|
3220
|
-
* already the strongest response
|
|
3227
|
+
* already the strongest response, and — critically — also when the coarse
|
|
3228
|
+
* winner is not BRACKETED by the sweep (see {@link BOUNDARY_REJECT_DEG}): an
|
|
3229
|
+
* optimum sitting on the edge of the searched interval is not a measurement,
|
|
3230
|
+
* it is the place the search ran out of road.
|
|
3221
3231
|
*/
|
|
3222
3232
|
function estimateSkewAngle(gray, opts = {}) {
|
|
3223
|
-
const maxAngleDeg = opts.maxAngleDeg ??
|
|
3224
|
-
const coarseStepDeg = opts.coarseStepDeg ??
|
|
3233
|
+
const maxAngleDeg = opts.maxAngleDeg ?? 16;
|
|
3234
|
+
const coarseStepDeg = opts.coarseStepDeg ?? DEFAULT_COARSE_STEP_DEG;
|
|
3225
3235
|
const fineStepDeg = opts.fineStepDeg ?? .5;
|
|
3226
3236
|
const scoreMaxDim = opts.scoreMaxDim ?? 480;
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
score: 0
|
|
3230
|
-
};
|
|
3237
|
+
const boundaryRejectDeg = Math.max(0, maxAngleDeg - coarseStepDeg);
|
|
3238
|
+
if (gray.width < 4 || gray.height < 4) return NO_SKEW;
|
|
3231
3239
|
const gradient = sobelMagnitude(downscaleGray(gray, scoreMaxDim));
|
|
3232
3240
|
let bestAngle = 0;
|
|
3233
3241
|
let bestScore = scoreAngle(gradient, 0);
|
|
@@ -3239,6 +3247,7 @@ function estimateSkewAngle(gray, opts = {}) {
|
|
|
3239
3247
|
bestAngle = deg;
|
|
3240
3248
|
}
|
|
3241
3249
|
}
|
|
3250
|
+
if (Math.abs(bestAngle) >= boundaryRejectDeg) return NO_SKEW;
|
|
3242
3251
|
const lo = bestAngle - coarseStepDeg;
|
|
3243
3252
|
const hi = bestAngle + coarseStepDeg;
|
|
3244
3253
|
for (let deg = lo; deg <= hi + 1e-9; deg += fineStepDeg) {
|
|
@@ -3254,6 +3263,50 @@ function estimateSkewAngle(gray, opts = {}) {
|
|
|
3254
3263
|
score: bestScore
|
|
3255
3264
|
};
|
|
3256
3265
|
}
|
|
3266
|
+
function toSharpChannels(channels) {
|
|
3267
|
+
switch (channels) {
|
|
3268
|
+
case 1: return 1;
|
|
3269
|
+
case 2: return 2;
|
|
3270
|
+
case 4: return 4;
|
|
3271
|
+
default: return 3;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3275
|
+
* Cut an expand-rotated canvas back to the LEVELLED SOURCE BAND.
|
|
3276
|
+
*
|
|
3277
|
+
* `rotateRaw(expand: true)` grows the canvas to `w·|cos| + h·|sin|` ×
|
|
3278
|
+
* `w·|sin| + h·|cos|` so no corner is clipped — but the `|sin|` terms are pure
|
|
3279
|
+
* corner spread: flat fill above/below (and left/right of) the plate. Handing
|
|
3280
|
+
* that to OCR is not free. The recognizer letterboxes the whole tile into a
|
|
3281
|
+
* fixed 64px-tall input, so a 155px crop returned at 194px shrinks every glyph
|
|
3282
|
+
* by ~20% before a single character is read — the warp paying back part of what
|
|
3283
|
+
* it just won.
|
|
3284
|
+
*
|
|
3285
|
+
* The band kept is the source rectangle scaled by `|cos angle|`, centred: a
|
|
3286
|
+
* source edge of length L, once levelled, spans `L·|cos|` along its own axis.
|
|
3287
|
+
* Everything outside is the fill the rotation introduced.
|
|
3288
|
+
*/
|
|
3289
|
+
function cropLevelledBand(rotated, srcWidth, srcHeight, angleRad) {
|
|
3290
|
+
const c = Math.abs(Math.cos(angleRad));
|
|
3291
|
+
const bw = Math.max(1, Math.min(rotated.width, Math.round(srcWidth * c)));
|
|
3292
|
+
const bh = Math.max(1, Math.min(rotated.height, Math.round(srcHeight * c)));
|
|
3293
|
+
if (bw === rotated.width && bh === rotated.height) return rotated;
|
|
3294
|
+
const ch = rotated.channels;
|
|
3295
|
+
const x0 = Math.max(0, Math.floor((rotated.width - bw) / 2));
|
|
3296
|
+
const y0 = Math.max(0, Math.floor((rotated.height - bh) / 2));
|
|
3297
|
+
const out = new Uint8Array(bw * bh * ch);
|
|
3298
|
+
const rowBytes = bw * ch;
|
|
3299
|
+
for (let y = 0; y < bh; y++) {
|
|
3300
|
+
const srcOff = ((y0 + y) * rotated.width + x0) * ch;
|
|
3301
|
+
out.set(rotated.data.subarray(srcOff, srcOff + rowBytes), y * rowBytes);
|
|
3302
|
+
}
|
|
3303
|
+
return {
|
|
3304
|
+
data: out,
|
|
3305
|
+
width: bw,
|
|
3306
|
+
height: bh,
|
|
3307
|
+
channels: ch
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3257
3310
|
/**
|
|
3258
3311
|
* Rectify an oblique plate crop: decode the JPEG, estimate the baseline skew,
|
|
3259
3312
|
* and — only when it exceeds {@link MIN_RECTIFY_ANGLE_DEG} — rotate the
|
|
@@ -3281,19 +3334,20 @@ async function rectifyPlateJpeg(jpeg, opts = {}) {
|
|
|
3281
3334
|
let mean = 0;
|
|
3282
3335
|
for (let i = 0; i < gray.data.length; i++) mean += gray.data[i];
|
|
3283
3336
|
const fill = gray.data.length > 0 ? mean / gray.data.length | 0 : 0;
|
|
3284
|
-
const
|
|
3337
|
+
const angleRad = angleDeg * DEG2RAD;
|
|
3338
|
+
const band = cropLevelledBand(rotateRaw(rgb, angleRad, {
|
|
3285
3339
|
expand: true,
|
|
3286
3340
|
fill
|
|
3287
|
-
});
|
|
3341
|
+
}), rgb.width, rgb.height, angleRad);
|
|
3288
3342
|
try {
|
|
3289
3343
|
return {
|
|
3290
|
-
jpeg: await sharp(Buffer.from(
|
|
3291
|
-
width:
|
|
3292
|
-
height:
|
|
3293
|
-
channels:
|
|
3344
|
+
jpeg: await sharp(Buffer.from(band.data), { raw: {
|
|
3345
|
+
width: band.width,
|
|
3346
|
+
height: band.height,
|
|
3347
|
+
channels: toSharpChannels(band.channels)
|
|
3294
3348
|
} }).jpeg({ quality: opts.quality ?? 90 }).toBuffer(),
|
|
3295
|
-
width:
|
|
3296
|
-
height:
|
|
3349
|
+
width: band.width,
|
|
3350
|
+
height: band.height,
|
|
3297
3351
|
angleDeg
|
|
3298
3352
|
};
|
|
3299
3353
|
} catch {
|
|
@@ -3302,6 +3356,12 @@ async function rectifyPlateJpeg(jpeg, opts = {}) {
|
|
|
3302
3356
|
}
|
|
3303
3357
|
//#endregion
|
|
3304
3358
|
//#region src/detection-pipeline/pipeline/result-assembler.ts
|
|
3359
|
+
/** L2 norm of a raw embedding vector. */
|
|
3360
|
+
function vectorMagnitude(v) {
|
|
3361
|
+
let sum = 0;
|
|
3362
|
+
for (const x of v) sum += x * x;
|
|
3363
|
+
return Math.sqrt(sum);
|
|
3364
|
+
}
|
|
3305
3365
|
/**
|
|
3306
3366
|
* Small deterministic id generator scoped to a single frame. Each call
|
|
3307
3367
|
* returns the next `d{n}` id so detail parentId references can be set
|
|
@@ -3395,6 +3455,7 @@ function applyChildOutput(parent, childStep, output, stepLatencyMs, ctx) {
|
|
|
3395
3455
|
case "embedding":
|
|
3396
3456
|
parent.embedding = output.embeddingNorm;
|
|
3397
3457
|
parent.embeddingModelId = childStep.modelId;
|
|
3458
|
+
parent.embeddingMagnitude = vectorMagnitude(output.embedding);
|
|
3398
3459
|
parent.ownLabels.push({
|
|
3399
3460
|
label: `embedding:${output.embedding.length}d`,
|
|
3400
3461
|
score: 1
|
|
@@ -3617,20 +3678,50 @@ var MIN_FACE_DETECTION_FLOOR_PX = 6;
|
|
|
3617
3678
|
*/
|
|
3618
3679
|
var NATIVE_FACE_CROP_MAX_WIDTH = 320;
|
|
3619
3680
|
/**
|
|
3620
|
-
* Minimum
|
|
3621
|
-
*
|
|
3622
|
-
*
|
|
3623
|
-
*
|
|
3624
|
-
*
|
|
3625
|
-
*
|
|
3626
|
-
*
|
|
3627
|
-
*
|
|
3681
|
+
* Minimum LONG side (px) of the ACTUAL crop fed to the plate-OCR (`ctc`) leaf —
|
|
3682
|
+
* the plate analogue of the face path's min-face-size gate.
|
|
3683
|
+
*
|
|
3684
|
+
* The long side, not the short one. A plate's information lives along its text
|
|
3685
|
+
* run: the recognizer resizes the tile to a fixed 64px height and reads left to
|
|
3686
|
+
* right, so what decides whether a glyph survives is how many horizontal pixels
|
|
3687
|
+
* each character got. The previous 20px SHORT-side floor measured the wrong
|
|
3688
|
+
* axis and passed essentially everything.
|
|
3689
|
+
*
|
|
3690
|
+
* The number comes from the 2026-08 audit of 11 cam-617 plate tracks with
|
|
3691
|
+
* ground truth. Native long side vs read:
|
|
3692
|
+
*
|
|
3693
|
+
* 117px → "DNRO3O9" (truth DNRO309 — one glyph out, the only near-miss)
|
|
3694
|
+
* 82px → "VONND9592" (truth DNDS597)
|
|
3695
|
+
* 77px → "DEPNOQ1" (truth DNP1091)
|
|
3696
|
+
* 76px → "VDHPXOE8" (truth DNPK838)
|
|
3697
|
+
* 73px → "DAEOR2D8" (truth DNDR298)
|
|
3698
|
+
* ≤62px → "I0IAZ", "EUSM9", "0IA3", "DYAGI2", "I0AA", "IC1E"
|
|
3699
|
+
*
|
|
3700
|
+
* Not one read under 100px resembled its plate, and the gap between 82 and 117
|
|
3701
|
+
* is empty — 100 sits in it. Below this floor the OCR call is pure cost: it
|
|
3702
|
+
* produces a label that is wrong, and a wrong plate label is worse than none.
|
|
3703
|
+
*
|
|
3704
|
+
* Measured on the NATIVE crop when the native ROI fetch succeeded (its
|
|
3705
|
+
* dimensions ARE the plate's real native size — `nativeChildCrop`'s 1920 cap
|
|
3706
|
+
* never binds for a plate), else on the fallback detection-space tile — which
|
|
3707
|
+
* IS the input OCR would read, so the same floor applies honestly in both cases.
|
|
3628
3708
|
*/
|
|
3629
|
-
var
|
|
3709
|
+
var MIN_PLATE_OCR_LONG_SIDE_PX = 100;
|
|
3630
3710
|
function isEnrichmentOutput(output) {
|
|
3631
3711
|
return output.kind === "classifications" || output.kind === "embedding" || output.kind === "text";
|
|
3632
3712
|
}
|
|
3633
3713
|
/**
|
|
3714
|
+
* Read a numeric step setting, falling back to `fallback` when the key is
|
|
3715
|
+
* absent or not a finite number. A type guard rather than a cast — a step's
|
|
3716
|
+
* `settings` bag is `Record<string, unknown>` because it is operator data, and
|
|
3717
|
+
* asserting `as number` over it is how a string from a form becomes a silent
|
|
3718
|
+
* NaN comparison that admits everything.
|
|
3719
|
+
*/
|
|
3720
|
+
function numericSetting(settings, key, fallback) {
|
|
3721
|
+
const raw = settings?.[key];
|
|
3722
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : fallback;
|
|
3723
|
+
}
|
|
3724
|
+
/**
|
|
3634
3725
|
* Stamp the camera onto an inference input so a failure inside the SHARED pool
|
|
3635
3726
|
* can name it (`InferenceInput.deviceId` — diagnostic only, it selects
|
|
3636
3727
|
* nothing). Immutable, and a no-op for a caller with no real camera: the
|
|
@@ -4071,12 +4162,27 @@ var PipelineExecutor = class {
|
|
|
4071
4162
|
let cropW;
|
|
4072
4163
|
let cropH;
|
|
4073
4164
|
if (modelEntry?.faceAlignment) {
|
|
4074
|
-
const minFaceSize =
|
|
4165
|
+
const minFaceSize = numericSetting(child.settings, "minFaceSize", DEFAULT_MIN_FACE_SIZE_PX);
|
|
4075
4166
|
const detShortSide = bboxShortSide(parentDetection.bbox);
|
|
4076
4167
|
if (detShortSide < MIN_FACE_DETECTION_FLOOR_PX) {
|
|
4077
4168
|
this.warnFaceTooSmall(child, detShortSide, minFaceSize, void 0, deviceId);
|
|
4078
4169
|
continue;
|
|
4079
4170
|
}
|
|
4171
|
+
const precision = landmarkPrecisionVerdict({
|
|
4172
|
+
detectionShortSidePx: detShortSide,
|
|
4173
|
+
floorPx: numericSetting(child.settings, "minLandmarkFaceSize", 24)
|
|
4174
|
+
});
|
|
4175
|
+
if (precision !== null) {
|
|
4176
|
+
this.throttledWarn(`face-landmark-floor:${child.stepId}:${deviceId}`, 3e4, "face detected but its landmarks are below the precision floor — no embedding", {
|
|
4177
|
+
step: child.stepId,
|
|
4178
|
+
model: child.modelId,
|
|
4179
|
+
detectionShortSidePx: Math.round(precision.detectionShortSidePx),
|
|
4180
|
+
minLandmarkFaceSizePx: precision.floorPx,
|
|
4181
|
+
tileW: imageWidth,
|
|
4182
|
+
tileH: imageHeight
|
|
4183
|
+
}, deviceId);
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4080
4186
|
const lms = parentDetection.landmarks;
|
|
4081
4187
|
if (lms && lms.length >= 5) {
|
|
4082
4188
|
if (!nativeCropProvider && detShortSide < minFaceSize) {
|
|
@@ -4140,15 +4246,16 @@ var PipelineExecutor = class {
|
|
|
4140
4246
|
cropH = crop.height;
|
|
4141
4247
|
}
|
|
4142
4248
|
if (child.definition.postprocessor === "ctc") {
|
|
4143
|
-
const
|
|
4144
|
-
if (
|
|
4145
|
-
this.opts.logger?.
|
|
4249
|
+
const plateLongSidePx = Math.max(cropW, cropH);
|
|
4250
|
+
if (plateLongSidePx < MIN_PLATE_OCR_LONG_SIDE_PX) {
|
|
4251
|
+
this.opts.logger?.info("plate ROI too small for OCR — skipping", {
|
|
4146
4252
|
tags: { deviceId },
|
|
4147
4253
|
meta: {
|
|
4148
4254
|
step: child.stepId,
|
|
4149
4255
|
model: child.modelId,
|
|
4150
|
-
|
|
4151
|
-
|
|
4256
|
+
plateLongSidePx: Math.round(plateLongSidePx),
|
|
4257
|
+
plateShortSidePx: Math.round(Math.min(cropW, cropH)),
|
|
4258
|
+
minPlateLongSidePx: MIN_PLATE_OCR_LONG_SIDE_PX,
|
|
4152
4259
|
source: nativeChild ? "native" : "detection-tile"
|
|
4153
4260
|
}
|
|
4154
4261
|
});
|
|
@@ -4161,12 +4268,15 @@ var PipelineExecutor = class {
|
|
|
4161
4268
|
cropJpegBuf = rectified.jpeg;
|
|
4162
4269
|
cropW = rectified.width;
|
|
4163
4270
|
cropH = rectified.height;
|
|
4164
|
-
this.opts.logger?.
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4271
|
+
this.opts.logger?.info("plate crop deskewed before OCR", {
|
|
4272
|
+
tags: { deviceId },
|
|
4273
|
+
meta: {
|
|
4274
|
+
step: child.stepId,
|
|
4275
|
+
angleDeg: Number(rectified.angleDeg.toFixed(2)),
|
|
4276
|
+
width: cropW,
|
|
4277
|
+
height: cropH
|
|
4278
|
+
}
|
|
4279
|
+
});
|
|
4170
4280
|
}
|
|
4171
4281
|
} catch {}
|
|
4172
4282
|
}
|
|
@@ -4176,6 +4286,17 @@ var PipelineExecutor = class {
|
|
|
4176
4286
|
}, deviceId), cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
|
|
4177
4287
|
const childMs = Date.now() - childStart;
|
|
4178
4288
|
if (childOutput === null) continue;
|
|
4289
|
+
if (child.definition.postprocessor === "ctc" && childOutput.kind === "text") this.opts.logger?.info("plate OCR read", {
|
|
4290
|
+
tags: { deviceId },
|
|
4291
|
+
meta: {
|
|
4292
|
+
step: child.stepId,
|
|
4293
|
+
model: child.modelId,
|
|
4294
|
+
text: childOutput.text,
|
|
4295
|
+
confidence: Number(childOutput.confidence.toFixed(3)),
|
|
4296
|
+
formatValid: childOutput.formatValid ?? null,
|
|
4297
|
+
tilePx: `${cropW}x${cropH}`
|
|
4298
|
+
}
|
|
4299
|
+
});
|
|
4179
4300
|
const detailsBefore = ctx.details.length;
|
|
4180
4301
|
applyChildOutput(parentDetection, child, childOutput, childMs, ctx);
|
|
4181
4302
|
if (childOutput.kind === "detections" && child.children.length > 0) {
|
|
@@ -15342,128 +15342,103 @@ var COCO_TO_MACRO = {
|
|
|
15342
15342
|
var AUDIO_MACRO_LABELS = [
|
|
15343
15343
|
{
|
|
15344
15344
|
id: "speech",
|
|
15345
|
-
name: "Speech"
|
|
15346
|
-
icon: "🗣️"
|
|
15345
|
+
name: "Speech"
|
|
15347
15346
|
},
|
|
15348
15347
|
{
|
|
15349
15348
|
id: "scream",
|
|
15350
|
-
name: "Scream / Shout"
|
|
15351
|
-
icon: "😱"
|
|
15349
|
+
name: "Scream / Shout"
|
|
15352
15350
|
},
|
|
15353
15351
|
{
|
|
15354
15352
|
id: "crying",
|
|
15355
|
-
name: "Crying / Baby"
|
|
15356
|
-
icon: "😢"
|
|
15353
|
+
name: "Crying / Baby"
|
|
15357
15354
|
},
|
|
15358
15355
|
{
|
|
15359
15356
|
id: "laughter",
|
|
15360
|
-
name: "Laughter"
|
|
15361
|
-
icon: "😂"
|
|
15357
|
+
name: "Laughter"
|
|
15362
15358
|
},
|
|
15363
15359
|
{
|
|
15364
15360
|
id: "music",
|
|
15365
|
-
name: "Music"
|
|
15366
|
-
icon: "🎵"
|
|
15361
|
+
name: "Music"
|
|
15367
15362
|
},
|
|
15368
15363
|
{
|
|
15369
15364
|
id: "dog",
|
|
15370
|
-
name: "Dog"
|
|
15371
|
-
icon: "🐕"
|
|
15365
|
+
name: "Dog"
|
|
15372
15366
|
},
|
|
15373
15367
|
{
|
|
15374
15368
|
id: "cat",
|
|
15375
|
-
name: "Cat"
|
|
15376
|
-
icon: "🐈"
|
|
15369
|
+
name: "Cat"
|
|
15377
15370
|
},
|
|
15378
15371
|
{
|
|
15379
15372
|
id: "bird",
|
|
15380
|
-
name: "Bird"
|
|
15381
|
-
icon: "🐦"
|
|
15373
|
+
name: "Bird"
|
|
15382
15374
|
},
|
|
15383
15375
|
{
|
|
15384
15376
|
id: "animal",
|
|
15385
|
-
name: "Animal (other)"
|
|
15386
|
-
icon: "🐾"
|
|
15377
|
+
name: "Animal (other)"
|
|
15387
15378
|
},
|
|
15388
15379
|
{
|
|
15389
15380
|
id: "alarm",
|
|
15390
|
-
name: "Alarm / Siren"
|
|
15391
|
-
icon: "🚨"
|
|
15381
|
+
name: "Alarm / Siren"
|
|
15392
15382
|
},
|
|
15393
15383
|
{
|
|
15394
15384
|
id: "doorbell",
|
|
15395
|
-
name: "Doorbell / Knock"
|
|
15396
|
-
icon: "🔔"
|
|
15385
|
+
name: "Doorbell / Knock"
|
|
15397
15386
|
},
|
|
15398
15387
|
{
|
|
15399
15388
|
id: "glass_breaking",
|
|
15400
|
-
name: "Glass Breaking"
|
|
15401
|
-
icon: "💥"
|
|
15389
|
+
name: "Glass Breaking"
|
|
15402
15390
|
},
|
|
15403
15391
|
{
|
|
15404
15392
|
id: "gunshot",
|
|
15405
|
-
name: "Gunshot / Explosion"
|
|
15406
|
-
icon: "💣"
|
|
15393
|
+
name: "Gunshot / Explosion"
|
|
15407
15394
|
},
|
|
15408
15395
|
{
|
|
15409
15396
|
id: "vehicle",
|
|
15410
|
-
name: "Vehicle"
|
|
15411
|
-
icon: "🚗"
|
|
15397
|
+
name: "Vehicle"
|
|
15412
15398
|
},
|
|
15413
15399
|
{
|
|
15414
15400
|
id: "siren",
|
|
15415
|
-
name: "Emergency Siren"
|
|
15416
|
-
icon: "🚑"
|
|
15401
|
+
name: "Emergency Siren"
|
|
15417
15402
|
},
|
|
15418
15403
|
{
|
|
15419
15404
|
id: "fire",
|
|
15420
|
-
name: "Fire / Smoke"
|
|
15421
|
-
icon: "🔥"
|
|
15405
|
+
name: "Fire / Smoke"
|
|
15422
15406
|
},
|
|
15423
15407
|
{
|
|
15424
15408
|
id: "water",
|
|
15425
|
-
name: "Water"
|
|
15426
|
-
icon: "💧"
|
|
15409
|
+
name: "Water"
|
|
15427
15410
|
},
|
|
15428
15411
|
{
|
|
15429
15412
|
id: "wind",
|
|
15430
|
-
name: "Wind / Weather"
|
|
15431
|
-
icon: "🌬️"
|
|
15413
|
+
name: "Wind / Weather"
|
|
15432
15414
|
},
|
|
15433
15415
|
{
|
|
15434
15416
|
id: "door",
|
|
15435
|
-
name: "Door"
|
|
15436
|
-
icon: "🚪"
|
|
15417
|
+
name: "Door"
|
|
15437
15418
|
},
|
|
15438
15419
|
{
|
|
15439
15420
|
id: "footsteps",
|
|
15440
|
-
name: "Footsteps"
|
|
15441
|
-
icon: "👣"
|
|
15421
|
+
name: "Footsteps"
|
|
15442
15422
|
},
|
|
15443
15423
|
{
|
|
15444
15424
|
id: "crowd",
|
|
15445
|
-
name: "Crowd / Chatter"
|
|
15446
|
-
icon: "👥"
|
|
15425
|
+
name: "Crowd / Chatter"
|
|
15447
15426
|
},
|
|
15448
15427
|
{
|
|
15449
15428
|
id: "telephone",
|
|
15450
|
-
name: "Telephone"
|
|
15451
|
-
icon: "📞"
|
|
15429
|
+
name: "Telephone"
|
|
15452
15430
|
},
|
|
15453
15431
|
{
|
|
15454
15432
|
id: "engine",
|
|
15455
|
-
name: "Engine / Motor"
|
|
15456
|
-
icon: "⚙️"
|
|
15433
|
+
name: "Engine / Motor"
|
|
15457
15434
|
},
|
|
15458
15435
|
{
|
|
15459
15436
|
id: "tools",
|
|
15460
|
-
name: "Tools / Construction"
|
|
15461
|
-
icon: "🔨"
|
|
15437
|
+
name: "Tools / Construction"
|
|
15462
15438
|
},
|
|
15463
15439
|
{
|
|
15464
15440
|
id: "silence",
|
|
15465
|
-
name: "Silence"
|
|
15466
|
-
icon: "🤫"
|
|
15441
|
+
name: "Silence"
|
|
15467
15442
|
}
|
|
15468
15443
|
];
|
|
15469
15444
|
var YAMNET_TO_MACRO = {
|
|
@@ -19727,6 +19702,12 @@ var DetailResultSchema = object({
|
|
|
19727
19702
|
/** FRAME-space bbox (already mapped back from crop space). */
|
|
19728
19703
|
bbox: NativeCropBboxSchema.optional(),
|
|
19729
19704
|
embedding: string().optional(),
|
|
19705
|
+
/**
|
|
19706
|
+
* L2 magnitude of the RAW pre-normalization embedding. Additive and optional
|
|
19707
|
+
* so an older runner that does not send it degrades to "unmeasurable", which
|
|
19708
|
+
* the consuming gate treats as accept — never as reject.
|
|
19709
|
+
*/
|
|
19710
|
+
embeddingMagnitude: number().optional(),
|
|
19730
19711
|
label: string().optional(),
|
|
19731
19712
|
/**
|
|
19732
19713
|
* The tier `label` occupies, copied VERBATIM from the producing step's
|
|
@@ -35841,12 +35822,61 @@ Object.freeze({
|
|
|
35841
35822
|
"network-access": "ingress",
|
|
35842
35823
|
"smtp-provider": "email"
|
|
35843
35824
|
});
|
|
35825
|
+
/**
|
|
35826
|
+
* audio-condition — the authoring logic behind the sustained-sound condition
|
|
35827
|
+
* (`NcConditions.audio`), pure and UI-framework-free.
|
|
35828
|
+
*
|
|
35829
|
+
* WHY IT LIVES HERE
|
|
35830
|
+
* ─────────────────
|
|
35831
|
+
* Same reason as `condition-taxonomy.ts` next door: two editors author this
|
|
35832
|
+
* condition (the admin UI and the viewer), the vocabulary it offers is already
|
|
35833
|
+
* in this package (`AUDIO_MACRO_LABELS`), and a second copy of "what a cleared
|
|
35834
|
+
* threshold writes" is a mirror free to drift. The viewer keeps its own copy
|
|
35835
|
+
* because it is a separate app with its own toolchain; every surface that CAN
|
|
35836
|
+
* import `@camstack/types` uses this one.
|
|
35837
|
+
*
|
|
35838
|
+
* Four behaviours here are not cosmetic:
|
|
35839
|
+
*
|
|
35840
|
+
* 1. **dBFS is negative-going.** `0` is full scale; digital silence reads
|
|
35841
|
+
* {@link NC_AUDIO_DBFS_FLOOR}. A control that let an operator author `60`
|
|
35842
|
+
* meaning "loud" would write a threshold no sample can reach, and the rule
|
|
35843
|
+
* would look broken rather than mis-configured. Every write is clamped.
|
|
35844
|
+
* 2. **Neither filter given = the engine REFUSES.** With no level floor and no
|
|
35845
|
+
* labels every sample is trivially a hit, so the window would fire on
|
|
35846
|
+
* silence. {@link audioIsFailClosed} is how an editor says that BEFORE the
|
|
35847
|
+
* save instead of shipping a rule that never notifies and never says why.
|
|
35848
|
+
* 3. **Both label spellings are the same label.** The hub's taxonomy serves
|
|
35849
|
+
* `audio-dog`; the macro catalog and an operator both say `dog`, and the
|
|
35850
|
+
* matcher normalizes the prefix away on both sides. Selection therefore
|
|
35851
|
+
* compares NORMALIZED ids — a rule authored in the admin UI must not read
|
|
35852
|
+
* as "nothing selected" on the phone.
|
|
35853
|
+
* 4. **A stored label the build does not know stays visible.** It is offered
|
|
35854
|
+
* as an explicitly-unknown, removable choice, never silently dropped:
|
|
35855
|
+
* rewriting the rule the operator is looking at is worse than showing a
|
|
35856
|
+
* stale id.
|
|
35857
|
+
*/
|
|
35858
|
+
/** The `audio-` namespace the NC taxonomy uses for these same macro ids. */
|
|
35859
|
+
var AUDIO_KIND_PREFIX = "audio-";
|
|
35860
|
+
/**
|
|
35861
|
+
* The inverse: a macro id in the taxonomy's namespace (`dog` → `audio-dog`),
|
|
35862
|
+
* which is the `iconId` every UI resolves to a glyph (`EVENT_KIND_ICONS` in
|
|
35863
|
+
* `@camstack/ui-library`, `event-kind-icons.ts` in the viewer). Idempotent, so
|
|
35864
|
+
* a value that already carries the namespace is passed through unchanged.
|
|
35865
|
+
*
|
|
35866
|
+
* This lives here — beside `normalizeAudioLabel` and next to the vocabulary
|
|
35867
|
+
* itself — rather than in a UI package, because the *id* is data and the *glyph*
|
|
35868
|
+
* is presentation: the server puts the id on a config option, and whichever
|
|
35869
|
+
* renderer picks it up (lucide-react on the web, lucide-react-native in the
|
|
35870
|
+
* viewer) supplies its own component for it.
|
|
35871
|
+
*/
|
|
35872
|
+
function audioKindId(macroId) {
|
|
35873
|
+
return macroId.startsWith(AUDIO_KIND_PREFIX) ? macroId : `${AUDIO_KIND_PREFIX}${macroId}`;
|
|
35874
|
+
}
|
|
35844
35875
|
/** Schema defaults — an untouched sub-field must author exactly these. */
|
|
35845
35876
|
var NC_AUDIO_DEFAULTS = {
|
|
35846
35877
|
hitPercent: 60,
|
|
35847
35878
|
samplingSeconds: 10
|
|
35848
35879
|
};
|
|
35849
|
-
new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
|
|
35850
35880
|
new Set(["devices", "classes"]);
|
|
35851
35881
|
NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
35852
35882
|
/**
|
|
@@ -37367,6 +37397,12 @@ Object.defineProperty(exports, "audioAnalyzerCapability", {
|
|
|
37367
37397
|
return audioAnalyzerCapability;
|
|
37368
37398
|
}
|
|
37369
37399
|
});
|
|
37400
|
+
Object.defineProperty(exports, "audioKindId", {
|
|
37401
|
+
enumerable: true,
|
|
37402
|
+
get: function() {
|
|
37403
|
+
return audioKindId;
|
|
37404
|
+
}
|
|
37405
|
+
});
|
|
37370
37406
|
Object.defineProperty(exports, "batteryCapability", {
|
|
37371
37407
|
enumerable: true,
|
|
37372
37408
|
get: function() {
|