@camstack/addon-pipeline 1.2.6 → 1.2.8
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/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +2 -3
- package/dist/{chunk-D6vf50IK.js → chunk-emK7D4bc.js} +7 -0
- package/dist/detection-pipeline/index.js +104 -8
- package/dist/detection-pipeline/index.mjs +103 -8
- package/dist/{dist-Bh0H8jM6.js → dist-DNjy_sr4.js} +1374 -925
- package/dist/{dist-Bw_V5L0r.mjs → dist-owmpaeIY.mjs} +1379 -924
- package/dist/fmp4-keyframes-BzTPoPVn.js +6676 -0
- package/dist/fmp4-keyframes-qOgJBjMO.mjs +6669 -0
- package/dist/{sheet-geometry-DcpuGTCm.mjs → hub-hostname-DAJXlOgV.js} +6 -39
- package/dist/{sheet-geometry-BR-ip0CP.js → hub-hostname-cCknRYKj.mjs} +1 -56
- package/dist/{model-download-service-Cp9f4dk6-CwUb5v3Y.mjs → model-download-service-Cp9f4dk6-WO-foD7W.mjs} +26 -1
- package/dist/{model-download-service-Cp9f4dk6-Dv-oFwjN.js → model-download-service-Cp9f4dk6-fsdDExML.js} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +51 -132
- package/dist/pipeline-runner/index.mjs +49 -130
- package/dist/recorder/index.js +695 -974
- package/dist/recorder/index.mjs +694 -972
- package/dist/session-decode/decode-worker-child.js +9 -5
- package/dist/session-decode/decode-worker-child.mjs +8 -4
- package/dist/{step-definitions-xxdpJX9-.js → step-definitions-C2bNlww3.js} +1 -1
- package/dist/{step-definitions-aY4D9Loy.mjs → step-definitions-CkvctwuS.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DX-Ef8CP.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-XdI0RgvI.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DSfbOjdi.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BdxY9zld.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js--zEZtV2N.mjs} +1 -1
- package/dist/stream-broker/{hostInit-DUL71Vjq.mjs → hostInit-DsMIdlwP.mjs} +2 -2
- package/dist/stream-broker/index.js +680 -15
- package/dist/stream-broker/index.mjs +679 -15
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CGWEeRkz.js → MaskShapeCanvas-DI4BY7W2-CftzR7Pj.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-4YbTa3Wp.js → MotionZonesSettings-NcxxQN8r-DPgVIdEn.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-DL_tAs9-.js → PrivacyMaskSettings-APgPLF7p-D0HqiXHW.js} +1 -1
- package/embed-dist/assets/{index-CvBwcYl_.js → index-DRtbV_en.js} +17 -15
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/python/postprocessors/ctc.py +14 -5
- package/python/postprocessors/test_ctc.py +35 -0
- package/dist/chunk-BdkLduGY.mjs +0 -5
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-gchR4WlX.mjs +0 -26
|
@@ -2,10 +2,10 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_chunk = require("../chunk-
|
|
6
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
+
const require_dist = require("../dist-DNjy_sr4.js");
|
|
7
7
|
const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
|
|
8
|
-
const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-
|
|
8
|
+
const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-fsdDExML.js");
|
|
9
9
|
let node_fs = require("node:fs");
|
|
10
10
|
node_fs = require_chunk.__toESM(node_fs);
|
|
11
11
|
let node_path = require("node:path");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { D as mapAudioLabelToMacro, H as
|
|
1
|
+
import { i as downloadFile, l as __require } from "../model-download-service-Cp9f4dk6-WO-foD7W.mjs";
|
|
2
|
+
import { D as mapAudioLabelToMacro, H as BaseAddon, V as errMsg, _ as audioAnalysisCapability, d as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, q as hydrateSchema, v as audioAnalyzerCapability } from "../dist-owmpaeIY.mjs";
|
|
3
3
|
import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
|
|
4
|
-
import { i as downloadFile } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
|
|
5
4
|
import * as fs from "node:fs";
|
|
6
5
|
import * as path$1 from "node:path";
|
|
7
6
|
//#region src/audio-analyzer/audio-pipeline.ts
|
|
@@ -5,6 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
8
9
|
var __copyProps = (to, from, except, desc) => {
|
|
9
10
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
11
|
key = keys[i];
|
|
@@ -20,6 +21,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
20
21
|
enumerable: true
|
|
21
22
|
}) : target, mod));
|
|
22
23
|
//#endregion
|
|
24
|
+
Object.defineProperty(exports, "__commonJSMin", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function() {
|
|
27
|
+
return __commonJSMin;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
23
30
|
Object.defineProperty(exports, "__toESM", {
|
|
24
31
|
enumerable: true,
|
|
25
32
|
get: function() {
|
|
@@ -2,11 +2,11 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_chunk = require("../chunk-
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_step_definitions = require("../step-definitions-
|
|
5
|
+
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
+
const require_dist = require("../dist-DNjy_sr4.js");
|
|
7
|
+
const require_step_definitions = require("../step-definitions-C2bNlww3.js");
|
|
8
8
|
const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
|
|
9
|
-
const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-
|
|
9
|
+
const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-fsdDExML.js");
|
|
10
10
|
let node_child_process = require("node:child_process");
|
|
11
11
|
let sharp = require("sharp");
|
|
12
12
|
sharp = require_chunk.__toESM(sharp);
|
|
@@ -2556,7 +2556,13 @@ async function buildAlignedFaceCrop(args) {
|
|
|
2556
2556
|
if (native && native.width >= 2 && native.height >= 2 && native.bytes.length >= native.width * native.height * 3) return {
|
|
2557
2557
|
...await alignFaceCropFromRgb(native, region, args.landmarksImageSpace, { outSize: args.outSize }),
|
|
2558
2558
|
source: "native",
|
|
2559
|
-
nativeFaceShortSidePx: nativeFaceShortSide(args.faceBbox, region, native)
|
|
2559
|
+
nativeFaceShortSidePx: nativeFaceShortSide(args.faceBbox, region, native),
|
|
2560
|
+
nativeDiag: {
|
|
2561
|
+
nativeCropW: native.width,
|
|
2562
|
+
nativeCropH: native.height,
|
|
2563
|
+
regionW: region.right - region.left,
|
|
2564
|
+
regionH: region.bottom - region.top
|
|
2565
|
+
}
|
|
2560
2566
|
};
|
|
2561
2567
|
} catch {}
|
|
2562
2568
|
return {
|
|
@@ -2686,6 +2692,51 @@ async function nativeChildCrop(provider, bbox, imageWidth, imageHeight, maxWidth
|
|
|
2686
2692
|
height: native.height
|
|
2687
2693
|
};
|
|
2688
2694
|
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Detection score at/above which a near-full-frame box is trusted as a REAL
|
|
2697
|
+
* close subject rather than a phantom. Below this bar a full-frame box is
|
|
2698
|
+
* dropped; at/above it survives (a full-frame crop of a genuine close subject
|
|
2699
|
+
* beats a phantom occupancy chip). The observed cam-615 phantom scored 0.52,
|
|
2700
|
+
* comfortably under this 0.70 bar; a genuine object right under the lens
|
|
2701
|
+
* (doorbell close-ups) commonly scores 0.65-0.85 — 0.70 keeps the legit band
|
|
2702
|
+
* above the bar while holding a wide margin over observed phantoms (~0.52).
|
|
2703
|
+
* Score-aware like the media guard (whose thumbnail-only bar is 0.5; this one
|
|
2704
|
+
* DROPS detections outright, so it sits between that and a hard cut).
|
|
2705
|
+
*/
|
|
2706
|
+
var FULL_FRAME_MAX_CONFIDENCE = .7;
|
|
2707
|
+
/**
|
|
2708
|
+
* True when `bbox` is a near-full-frame LOW-confidence phantom that should be
|
|
2709
|
+
* DROPPED from the frame's detections (overlay / tracker / events / motion-wake).
|
|
2710
|
+
*
|
|
2711
|
+
* Rule (drop ⇔ returns true):
|
|
2712
|
+
* (areaRatio ≥ {@link FULL_FRAME_AREA_RATIO}
|
|
2713
|
+
* OR width ≥ {@link FULL_FRAME_DIMENSION_RATIO} × frameWidth
|
|
2714
|
+
* OR height ≥ {@link FULL_FRAME_DIMENSION_RATIO} × frameHeight)
|
|
2715
|
+
* AND score < `maxConfidence`
|
|
2716
|
+
*
|
|
2717
|
+
* A high-confidence detection (`score ≥ maxConfidence`) is trusted as a real
|
|
2718
|
+
* subject and always survives. Degenerate frame dimensions (≤0) carry no info
|
|
2719
|
+
* to reject on → not a phantom.
|
|
2720
|
+
*
|
|
2721
|
+
* @param bbox absolute-pixel `[x1, y1, x2, y2]` box in source-frame coords.
|
|
2722
|
+
* @param frameWidth source frame width in px.
|
|
2723
|
+
* @param frameHeight source frame height in px.
|
|
2724
|
+
* @param score detector confidence for this detection (0..1).
|
|
2725
|
+
* @param maxConfidence confidence bar below which a full-frame box is a phantom
|
|
2726
|
+
* (defaults to {@link FULL_FRAME_MAX_CONFIDENCE}; overridable by the caller so
|
|
2727
|
+
* the bar can be threaded from detection settings without new cap methods).
|
|
2728
|
+
*/
|
|
2729
|
+
function isFullFramePhantomDetection(bbox, frameWidth, frameHeight, score, maxConfidence = FULL_FRAME_MAX_CONFIDENCE) {
|
|
2730
|
+
if (frameWidth <= 0 || frameHeight <= 0) return false;
|
|
2731
|
+
if (score >= maxConfidence) return false;
|
|
2732
|
+
const [x1, y1, x2, y2] = bbox;
|
|
2733
|
+
const w = x2 - x1;
|
|
2734
|
+
const h = y2 - y1;
|
|
2735
|
+
if (w * h / (frameWidth * frameHeight) >= .9) return true;
|
|
2736
|
+
if (w >= frameWidth * .98) return true;
|
|
2737
|
+
if (h >= frameHeight * .98) return true;
|
|
2738
|
+
return false;
|
|
2739
|
+
}
|
|
2689
2740
|
//#endregion
|
|
2690
2741
|
//#region src/detection-pipeline/pipeline/plate-deskew.ts
|
|
2691
2742
|
/**
|
|
@@ -3375,6 +3426,17 @@ var MIN_FACE_DETECTION_FLOOR_PX = 6;
|
|
|
3375
3426
|
*/
|
|
3376
3427
|
var NATIVE_FACE_CROP_MAX_WIDTH = 320;
|
|
3377
3428
|
/**
|
|
3429
|
+
* Minimum short side (px) of the ACTUAL crop fed to the plate-OCR (`ctc`) leaf
|
|
3430
|
+
* — the plate analogue of the face path's min-face-size gate. Below ~20px the
|
|
3431
|
+
* CTC logit map collapses toward the blank and the surviving timesteps emit
|
|
3432
|
+
* symbol junk ("[miv4o)", "6ou.2" — live-observed 2026-07-22). Measured on the
|
|
3433
|
+
* NATIVE crop when the native ROI fetch succeeded (its dimensions ARE the
|
|
3434
|
+
* plate's real native size — `nativeChildCrop`'s 1920 cap never binds for a
|
|
3435
|
+
* plate), else on the fallback detection-space tile — which IS the input OCR
|
|
3436
|
+
* would read, so the same floor applies honestly in both cases.
|
|
3437
|
+
*/
|
|
3438
|
+
var MIN_PLATE_OCR_SHORT_SIDE_PX = 20;
|
|
3439
|
+
/**
|
|
3378
3440
|
* StepOutput kinds that synthesize a full-frame detection when they run at
|
|
3379
3441
|
* the tree ROOT (see `synthesizeRootDetection`) — classifier/embedding/OCR
|
|
3380
3442
|
* steps promoted to root by `pipelineRunner.runDetailSubtree` (task 6b), e.g.
|
|
@@ -3438,14 +3500,15 @@ var PipelineExecutor = class {
|
|
|
3438
3500
|
* native crop, the NATIVE face size so an operator sees the size that actually
|
|
3439
3501
|
* gated. Throttled so it isn't per-frame spam.
|
|
3440
3502
|
*/
|
|
3441
|
-
warnFaceTooSmall(child, detShortSidePx, minFaceSizePx, nativeFaceShortSidePx, deviceId) {
|
|
3503
|
+
warnFaceTooSmall(child, detShortSidePx, minFaceSizePx, nativeFaceShortSidePx, deviceId, diag) {
|
|
3442
3504
|
this.throttledWarn(`face-small:${child.stepId}:${deviceId}`, 3e4, "face detected but too small to embed — skipping", {
|
|
3443
3505
|
step: child.stepId,
|
|
3444
3506
|
model: child.modelId,
|
|
3445
3507
|
faceShortSidePx: Math.round(detShortSidePx),
|
|
3446
3508
|
minFaceSizePx,
|
|
3447
3509
|
gatedOn: nativeFaceShortSidePx === void 0 ? "detection" : "native",
|
|
3448
|
-
...nativeFaceShortSidePx === void 0 ? {} : { nativeFaceShortSidePx: Math.round(nativeFaceShortSidePx) }
|
|
3510
|
+
...nativeFaceShortSidePx === void 0 ? {} : { nativeFaceShortSidePx: Math.round(nativeFaceShortSidePx) },
|
|
3511
|
+
...diag
|
|
3449
3512
|
}, deviceId);
|
|
3450
3513
|
}
|
|
3451
3514
|
/**
|
|
@@ -3520,6 +3583,11 @@ var PipelineExecutor = class {
|
|
|
3520
3583
|
if (debug) console.log(`[executor] drop det macro=${mutable.macroClass} score=${mutable.score} (class or confidence filter)`);
|
|
3521
3584
|
continue;
|
|
3522
3585
|
}
|
|
3586
|
+
const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
|
|
3587
|
+
if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0)) {
|
|
3588
|
+
if (debug) console.log(`[executor] drop det macro=${mutable.macroClass} score=${mutable.score} bbox=${JSON.stringify(mutable.bbox)} (full-frame phantom guard)`);
|
|
3589
|
+
continue;
|
|
3590
|
+
}
|
|
3523
3591
|
try {
|
|
3524
3592
|
await this.executeChildren(rootStep.children, mutable, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
3525
3593
|
} catch (err) {
|
|
@@ -3719,10 +3787,22 @@ var PipelineExecutor = class {
|
|
|
3719
3787
|
} : {}
|
|
3720
3788
|
});
|
|
3721
3789
|
const gateShortSide = aligned.nativeFaceShortSidePx ?? detShortSide;
|
|
3790
|
+
const faceDiag = {
|
|
3791
|
+
tileW: imageWidth,
|
|
3792
|
+
tileH: imageHeight,
|
|
3793
|
+
...aligned.nativeDiag
|
|
3794
|
+
};
|
|
3722
3795
|
if (gateShortSide < minFaceSize) {
|
|
3723
|
-
this.warnFaceTooSmall(child, detShortSide, minFaceSize, aligned.source === "native" ? gateShortSide : void 0, deviceId);
|
|
3796
|
+
this.warnFaceTooSmall(child, detShortSide, minFaceSize, aligned.source === "native" ? gateShortSide : void 0, deviceId, faceDiag);
|
|
3724
3797
|
continue;
|
|
3725
3798
|
}
|
|
3799
|
+
this.throttledWarn(`face-embed-diag:${deviceId}`, 3e4, "face embed accepted (scale diagnostic)", {
|
|
3800
|
+
step: child.stepId,
|
|
3801
|
+
detShortSidePx: Math.round(detShortSide),
|
|
3802
|
+
gateShortSidePx: Math.round(gateShortSide),
|
|
3803
|
+
source: aligned.source,
|
|
3804
|
+
...faceDiag
|
|
3805
|
+
}, deviceId);
|
|
3726
3806
|
cropJpegBuf = aligned.jpeg;
|
|
3727
3807
|
cropW = aligned.width;
|
|
3728
3808
|
cropH = aligned.height;
|
|
@@ -3750,6 +3830,22 @@ var PipelineExecutor = class {
|
|
|
3750
3830
|
cropW = crop.width;
|
|
3751
3831
|
cropH = crop.height;
|
|
3752
3832
|
}
|
|
3833
|
+
if (child.definition.postprocessor === "ctc") {
|
|
3834
|
+
const plateShortSidePx = Math.min(cropW, cropH);
|
|
3835
|
+
if (plateShortSidePx < MIN_PLATE_OCR_SHORT_SIDE_PX) {
|
|
3836
|
+
this.opts.logger?.debug("plate ROI too small for OCR — skipping", {
|
|
3837
|
+
tags: { deviceId },
|
|
3838
|
+
meta: {
|
|
3839
|
+
step: child.stepId,
|
|
3840
|
+
model: child.modelId,
|
|
3841
|
+
plateShortSidePx: Math.round(plateShortSidePx),
|
|
3842
|
+
minPlateShortSidePx: MIN_PLATE_OCR_SHORT_SIDE_PX,
|
|
3843
|
+
source: nativeChild ? "native" : "detection-tile"
|
|
3844
|
+
}
|
|
3845
|
+
});
|
|
3846
|
+
continue;
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3753
3849
|
if (child.definition.postprocessor === "ctc" && child.settings?.["rectify"] !== false) try {
|
|
3754
3850
|
const rectified = await rectifyPlateJpeg(cropJpegBuf);
|
|
3755
3851
|
if (rectified) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-
|
|
1
|
+
import { a as ensureModel, l as __require, o as isModelDownloaded, r as deleteModelFromDisk } from "../model-download-service-Cp9f4dk6-WO-foD7W.mjs";
|
|
2
|
+
import { C as detectionPipelineCapability, H as BaseAddon, I as runtimeDevices$1, K as createEvent, M as pipelineExecutorCapability, T as evaluateZoneRules, V as errMsg, Z as parseJsonUnknown, ct as object, dt as union, et as sleep, ft as EventCategory, h as YAMNET_TO_MACRO, j as nodePin, nt as array, q as hydrateSchema, s as DEVICE_BACKEND_TO_FORMAT, t as APPLE_SA_TO_MACRO, ut as string, w as enumerateInferenceDevices, x as defaultDeviceFor$1, z as supportedRuntimes$1 } from "../dist-owmpaeIY.mjs";
|
|
3
|
+
import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-CkvctwuS.mjs";
|
|
4
4
|
import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
|
|
5
|
-
import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
|
|
6
5
|
import { spawn } from "node:child_process";
|
|
7
6
|
import sharp from "sharp";
|
|
8
7
|
import * as fs from "node:fs";
|
|
@@ -2548,7 +2547,13 @@ async function buildAlignedFaceCrop(args) {
|
|
|
2548
2547
|
if (native && native.width >= 2 && native.height >= 2 && native.bytes.length >= native.width * native.height * 3) return {
|
|
2549
2548
|
...await alignFaceCropFromRgb(native, region, args.landmarksImageSpace, { outSize: args.outSize }),
|
|
2550
2549
|
source: "native",
|
|
2551
|
-
nativeFaceShortSidePx: nativeFaceShortSide(args.faceBbox, region, native)
|
|
2550
|
+
nativeFaceShortSidePx: nativeFaceShortSide(args.faceBbox, region, native),
|
|
2551
|
+
nativeDiag: {
|
|
2552
|
+
nativeCropW: native.width,
|
|
2553
|
+
nativeCropH: native.height,
|
|
2554
|
+
regionW: region.right - region.left,
|
|
2555
|
+
regionH: region.bottom - region.top
|
|
2556
|
+
}
|
|
2552
2557
|
};
|
|
2553
2558
|
} catch {}
|
|
2554
2559
|
return {
|
|
@@ -2678,6 +2683,51 @@ async function nativeChildCrop(provider, bbox, imageWidth, imageHeight, maxWidth
|
|
|
2678
2683
|
height: native.height
|
|
2679
2684
|
};
|
|
2680
2685
|
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Detection score at/above which a near-full-frame box is trusted as a REAL
|
|
2688
|
+
* close subject rather than a phantom. Below this bar a full-frame box is
|
|
2689
|
+
* dropped; at/above it survives (a full-frame crop of a genuine close subject
|
|
2690
|
+
* beats a phantom occupancy chip). The observed cam-615 phantom scored 0.52,
|
|
2691
|
+
* comfortably under this 0.70 bar; a genuine object right under the lens
|
|
2692
|
+
* (doorbell close-ups) commonly scores 0.65-0.85 — 0.70 keeps the legit band
|
|
2693
|
+
* above the bar while holding a wide margin over observed phantoms (~0.52).
|
|
2694
|
+
* Score-aware like the media guard (whose thumbnail-only bar is 0.5; this one
|
|
2695
|
+
* DROPS detections outright, so it sits between that and a hard cut).
|
|
2696
|
+
*/
|
|
2697
|
+
var FULL_FRAME_MAX_CONFIDENCE = .7;
|
|
2698
|
+
/**
|
|
2699
|
+
* True when `bbox` is a near-full-frame LOW-confidence phantom that should be
|
|
2700
|
+
* DROPPED from the frame's detections (overlay / tracker / events / motion-wake).
|
|
2701
|
+
*
|
|
2702
|
+
* Rule (drop ⇔ returns true):
|
|
2703
|
+
* (areaRatio ≥ {@link FULL_FRAME_AREA_RATIO}
|
|
2704
|
+
* OR width ≥ {@link FULL_FRAME_DIMENSION_RATIO} × frameWidth
|
|
2705
|
+
* OR height ≥ {@link FULL_FRAME_DIMENSION_RATIO} × frameHeight)
|
|
2706
|
+
* AND score < `maxConfidence`
|
|
2707
|
+
*
|
|
2708
|
+
* A high-confidence detection (`score ≥ maxConfidence`) is trusted as a real
|
|
2709
|
+
* subject and always survives. Degenerate frame dimensions (≤0) carry no info
|
|
2710
|
+
* to reject on → not a phantom.
|
|
2711
|
+
*
|
|
2712
|
+
* @param bbox absolute-pixel `[x1, y1, x2, y2]` box in source-frame coords.
|
|
2713
|
+
* @param frameWidth source frame width in px.
|
|
2714
|
+
* @param frameHeight source frame height in px.
|
|
2715
|
+
* @param score detector confidence for this detection (0..1).
|
|
2716
|
+
* @param maxConfidence confidence bar below which a full-frame box is a phantom
|
|
2717
|
+
* (defaults to {@link FULL_FRAME_MAX_CONFIDENCE}; overridable by the caller so
|
|
2718
|
+
* the bar can be threaded from detection settings without new cap methods).
|
|
2719
|
+
*/
|
|
2720
|
+
function isFullFramePhantomDetection(bbox, frameWidth, frameHeight, score, maxConfidence = FULL_FRAME_MAX_CONFIDENCE) {
|
|
2721
|
+
if (frameWidth <= 0 || frameHeight <= 0) return false;
|
|
2722
|
+
if (score >= maxConfidence) return false;
|
|
2723
|
+
const [x1, y1, x2, y2] = bbox;
|
|
2724
|
+
const w = x2 - x1;
|
|
2725
|
+
const h = y2 - y1;
|
|
2726
|
+
if (w * h / (frameWidth * frameHeight) >= .9) return true;
|
|
2727
|
+
if (w >= frameWidth * .98) return true;
|
|
2728
|
+
if (h >= frameHeight * .98) return true;
|
|
2729
|
+
return false;
|
|
2730
|
+
}
|
|
2681
2731
|
//#endregion
|
|
2682
2732
|
//#region src/detection-pipeline/pipeline/plate-deskew.ts
|
|
2683
2733
|
/**
|
|
@@ -3367,6 +3417,17 @@ var MIN_FACE_DETECTION_FLOOR_PX = 6;
|
|
|
3367
3417
|
*/
|
|
3368
3418
|
var NATIVE_FACE_CROP_MAX_WIDTH = 320;
|
|
3369
3419
|
/**
|
|
3420
|
+
* Minimum short side (px) of the ACTUAL crop fed to the plate-OCR (`ctc`) leaf
|
|
3421
|
+
* — the plate analogue of the face path's min-face-size gate. Below ~20px the
|
|
3422
|
+
* CTC logit map collapses toward the blank and the surviving timesteps emit
|
|
3423
|
+
* symbol junk ("[miv4o)", "6ou.2" — live-observed 2026-07-22). Measured on the
|
|
3424
|
+
* NATIVE crop when the native ROI fetch succeeded (its dimensions ARE the
|
|
3425
|
+
* plate's real native size — `nativeChildCrop`'s 1920 cap never binds for a
|
|
3426
|
+
* plate), else on the fallback detection-space tile — which IS the input OCR
|
|
3427
|
+
* would read, so the same floor applies honestly in both cases.
|
|
3428
|
+
*/
|
|
3429
|
+
var MIN_PLATE_OCR_SHORT_SIDE_PX = 20;
|
|
3430
|
+
/**
|
|
3370
3431
|
* StepOutput kinds that synthesize a full-frame detection when they run at
|
|
3371
3432
|
* the tree ROOT (see `synthesizeRootDetection`) — classifier/embedding/OCR
|
|
3372
3433
|
* steps promoted to root by `pipelineRunner.runDetailSubtree` (task 6b), e.g.
|
|
@@ -3430,14 +3491,15 @@ var PipelineExecutor = class {
|
|
|
3430
3491
|
* native crop, the NATIVE face size so an operator sees the size that actually
|
|
3431
3492
|
* gated. Throttled so it isn't per-frame spam.
|
|
3432
3493
|
*/
|
|
3433
|
-
warnFaceTooSmall(child, detShortSidePx, minFaceSizePx, nativeFaceShortSidePx, deviceId) {
|
|
3494
|
+
warnFaceTooSmall(child, detShortSidePx, minFaceSizePx, nativeFaceShortSidePx, deviceId, diag) {
|
|
3434
3495
|
this.throttledWarn(`face-small:${child.stepId}:${deviceId}`, 3e4, "face detected but too small to embed — skipping", {
|
|
3435
3496
|
step: child.stepId,
|
|
3436
3497
|
model: child.modelId,
|
|
3437
3498
|
faceShortSidePx: Math.round(detShortSidePx),
|
|
3438
3499
|
minFaceSizePx,
|
|
3439
3500
|
gatedOn: nativeFaceShortSidePx === void 0 ? "detection" : "native",
|
|
3440
|
-
...nativeFaceShortSidePx === void 0 ? {} : { nativeFaceShortSidePx: Math.round(nativeFaceShortSidePx) }
|
|
3501
|
+
...nativeFaceShortSidePx === void 0 ? {} : { nativeFaceShortSidePx: Math.round(nativeFaceShortSidePx) },
|
|
3502
|
+
...diag
|
|
3441
3503
|
}, deviceId);
|
|
3442
3504
|
}
|
|
3443
3505
|
/**
|
|
@@ -3512,6 +3574,11 @@ var PipelineExecutor = class {
|
|
|
3512
3574
|
if (debug) console.log(`[executor] drop det macro=${mutable.macroClass} score=${mutable.score} (class or confidence filter)`);
|
|
3513
3575
|
continue;
|
|
3514
3576
|
}
|
|
3577
|
+
const fullFrameBar = rootStep.settings?.["fullFrameGuardMaxConfidence"];
|
|
3578
|
+
if (isFullFramePhantomDetection(mutable.bbox, imageWidth, imageHeight, mutable.score, typeof fullFrameBar === "number" ? fullFrameBar : void 0)) {
|
|
3579
|
+
if (debug) console.log(`[executor] drop det macro=${mutable.macroClass} score=${mutable.score} bbox=${JSON.stringify(mutable.bbox)} (full-frame phantom guard)`);
|
|
3580
|
+
continue;
|
|
3581
|
+
}
|
|
3515
3582
|
try {
|
|
3516
3583
|
await this.executeChildren(rootStep.children, mutable, fullFrameJpegProvider, imageWidth, imageHeight, traceBuilder, stepTimings, ctx, poolAgg, runOpts?.plane, deviceId, nativeCropProvider);
|
|
3517
3584
|
} catch (err) {
|
|
@@ -3711,10 +3778,22 @@ var PipelineExecutor = class {
|
|
|
3711
3778
|
} : {}
|
|
3712
3779
|
});
|
|
3713
3780
|
const gateShortSide = aligned.nativeFaceShortSidePx ?? detShortSide;
|
|
3781
|
+
const faceDiag = {
|
|
3782
|
+
tileW: imageWidth,
|
|
3783
|
+
tileH: imageHeight,
|
|
3784
|
+
...aligned.nativeDiag
|
|
3785
|
+
};
|
|
3714
3786
|
if (gateShortSide < minFaceSize) {
|
|
3715
|
-
this.warnFaceTooSmall(child, detShortSide, minFaceSize, aligned.source === "native" ? gateShortSide : void 0, deviceId);
|
|
3787
|
+
this.warnFaceTooSmall(child, detShortSide, minFaceSize, aligned.source === "native" ? gateShortSide : void 0, deviceId, faceDiag);
|
|
3716
3788
|
continue;
|
|
3717
3789
|
}
|
|
3790
|
+
this.throttledWarn(`face-embed-diag:${deviceId}`, 3e4, "face embed accepted (scale diagnostic)", {
|
|
3791
|
+
step: child.stepId,
|
|
3792
|
+
detShortSidePx: Math.round(detShortSide),
|
|
3793
|
+
gateShortSidePx: Math.round(gateShortSide),
|
|
3794
|
+
source: aligned.source,
|
|
3795
|
+
...faceDiag
|
|
3796
|
+
}, deviceId);
|
|
3718
3797
|
cropJpegBuf = aligned.jpeg;
|
|
3719
3798
|
cropW = aligned.width;
|
|
3720
3799
|
cropH = aligned.height;
|
|
@@ -3742,6 +3821,22 @@ var PipelineExecutor = class {
|
|
|
3742
3821
|
cropW = crop.width;
|
|
3743
3822
|
cropH = crop.height;
|
|
3744
3823
|
}
|
|
3824
|
+
if (child.definition.postprocessor === "ctc") {
|
|
3825
|
+
const plateShortSidePx = Math.min(cropW, cropH);
|
|
3826
|
+
if (plateShortSidePx < MIN_PLATE_OCR_SHORT_SIDE_PX) {
|
|
3827
|
+
this.opts.logger?.debug("plate ROI too small for OCR — skipping", {
|
|
3828
|
+
tags: { deviceId },
|
|
3829
|
+
meta: {
|
|
3830
|
+
step: child.stepId,
|
|
3831
|
+
model: child.modelId,
|
|
3832
|
+
plateShortSidePx: Math.round(plateShortSidePx),
|
|
3833
|
+
minPlateShortSidePx: MIN_PLATE_OCR_SHORT_SIDE_PX,
|
|
3834
|
+
source: nativeChild ? "native" : "detection-tile"
|
|
3835
|
+
}
|
|
3836
|
+
});
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3745
3840
|
if (child.definition.postprocessor === "ctc" && child.settings?.["rectify"] !== false) try {
|
|
3746
3841
|
const rectified = await rectifyPlateJpeg(cropJpegBuf);
|
|
3747
3842
|
if (rectified) {
|