@camstack/addon-pipeline 1.2.328 → 1.2.330
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/THIRD_PARTY_MODELS.md +2 -2
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-1SadEKsW.js → default-detection-model-B6gKGnfx.js} +53 -71
- package/dist/{default-detection-model-CMeVTcPZ.mjs → default-detection-model-CPDT_eFT.mjs} +53 -71
- package/dist/detection-pipeline/index.js +607 -39
- package/dist/detection-pipeline/index.mjs +607 -39
- package/dist/{dist-wJO7mXeO.js → dist-CMeICL07.js} +781 -465
- package/dist/{dist-Bpub2VcJ.mjs → dist-CuPEyZY2.mjs} +736 -438
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-BjCSgD4M.mjs → node-USq5jYwq.mjs} +1 -1
- package/dist/{node-BCrqaejt.js → node-X_0Tka7-.js} +1 -1
- package/dist/pipeline-runner/index.js +355 -104
- package/dist/pipeline-runner/index.mjs +355 -104
- package/dist/{process-memory-BaKQ8u2G.mjs → process-memory-07XYts8V.mjs} +1 -1
- package/dist/{process-memory-iUJuTpXs.js → process-memory-o4LFWbBN.js} +1 -1
- package/dist/recorder/index.js +7 -7
- package/dist/recorder/index.mjs +7 -7
- package/dist/{segment-demux-js-C5ez9LYG.mjs → segment-demux-js-Bq8DrH0b.mjs} +2 -2
- package/dist/{segment-demux-js-Opx6Hv9G.js → segment-demux-js-FqaQjG7n.js} +2 -2
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B94LgsV0.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-EYRUYv_r.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BnP7C_HO.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-CR0--kRv.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-xOx7AvgW.mjs → hostInit-iGdBpvGc.mjs} +3 -3
- package/dist/stream-broker/index.js +4 -4
- package/dist/stream-broker/index.mjs +4 -4
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/edgetpu_dequant.py +38 -1
- package/python/inference_pool.py +31 -17
- package/python/postprocessors/test_yolox_plate_edgetpu.py +126 -0
- package/python/postprocessors/test_yunet_edgetpu.py +149 -0
- package/python/postprocessors/testdata/yolox_plate_int8_outputs.json +1 -0
- package/python/postprocessors/testdata/yunet_int8_outputs.json +1 -0
- package/python/postprocessors/yolo.py +44 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BPWYf4_O.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BpXPNINd.mjs +0 -26
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { s as readProcessCost } from "../node-
|
|
4
|
-
import { _ as getStep, a as buildSubtreeCanRun, b as landmarkPrecisionVerdict, c as macroGateVerdict, g as getDefaultModelForFormat, h as ALL_STEPS, i as mergeCustomModels, m as ALL_PIPELINE_STEPS, n as resolveDefaultDetectionModel, r as CustomModelCatalog, u as localFrameRegistry, v as getStepDefinition, x as resolveModelDefaultConfidence, y as resolveModelForFormat } from "../default-detection-model-
|
|
2
|
+
import { $n as union, Cn as errMsg, Dt as linkedLabelStepId, G as YAMNET_TO_MACRO, Ln as nodePin, N as PoolMemoryWatchdog, Nn as hydrateSchema, O as LABEL_SUPPLY_MACRO, Qn as string, Rn as parseJsonUnknown, Tn as BaseAddon, Un as array, Vn as sleep, Vt as pickClusterStepSettings, Wt as pipelineExecutorCapability, Yn as object, Zt as resolveClusterStepModelId, an as runtimeDevices$1, b as DEVICE_BACKEND_TO_FORMAT, d as CLUSTER_MODEL_SCOPED_STEPS, dn as supportedRuntimes$1, dt as detectionPipelineCapability, er as EventCategory, g as DEFAULT_CLUSTER_STEP_SETTINGS, ht as evaluateZoneRules, in as rootSuppliesOf, kn as createEvent, kt as loadContributionCapability, mt as enumerateInferenceDevices, n as APPLE_SA_TO_MACRO, nt as containingParentIndex, on as stepModelRunsOnFormat, ot as defaultDeviceFor$1, tn as resolvePoolMemoryPolicy, wt as hfModelUrl, zt as overlayClusterStepSettings } from "../dist-CuPEyZY2.mjs";
|
|
3
|
+
import { s as readProcessCost } from "../node-USq5jYwq.mjs";
|
|
4
|
+
import { _ as getStep, a as buildSubtreeCanRun, b as landmarkPrecisionVerdict, c as macroGateVerdict, g as getDefaultModelForFormat, h as ALL_STEPS, i as mergeCustomModels, m as ALL_PIPELINE_STEPS, n as resolveDefaultDetectionModel, r as CustomModelCatalog, u as localFrameRegistry, v as getStepDefinition, x as resolveModelDefaultConfidence, y as resolveModelForFormat } from "../default-detection-model-CPDT_eFT.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
|
|
6
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
6
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-07XYts8V.mjs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as path$1 from "node:path";
|
|
@@ -752,7 +752,7 @@ var POOL_MODEL_LOAD_BOUNDS = {
|
|
|
752
752
|
hardMs: 6e5,
|
|
753
753
|
why: "A cold iGPU compile of the WHOLE model set measured ~25 s on the N100; one model is 1-3 s (YuNet 1.4 s on a fresh worker, 2026-09-26). 120 s is ~5x the worst measured set. No evidence of a legitimate compile past it; the hard bound gives one 5x more before the worker is recycled.",
|
|
754
754
|
gilMayBeHeldDuringCompile: false,
|
|
755
|
-
gilWhy: "The OpenVINO Python binding is believed to release the GIL around compile_model (gil_scoped_release). Not measured on
|
|
755
|
+
gilWhy: "The OpenVINO Python binding is believed to release the GIL around compile_model (gil_scoped_release). Not yet measured on this node."
|
|
756
756
|
},
|
|
757
757
|
coreml: {
|
|
758
758
|
softMs: 3e5,
|
|
@@ -2809,6 +2809,8 @@ var PipelineModelManager = class {
|
|
|
2809
2809
|
stepId,
|
|
2810
2810
|
modelId,
|
|
2811
2811
|
poolIndex: index,
|
|
2812
|
+
path: config.path,
|
|
2813
|
+
...settings !== void 0 ? { settings } : {},
|
|
2812
2814
|
lruTick: ++this.lruClock,
|
|
2813
2815
|
decode
|
|
2814
2816
|
};
|
|
@@ -2816,6 +2818,56 @@ var PipelineModelManager = class {
|
|
|
2816
2818
|
return entry;
|
|
2817
2819
|
}
|
|
2818
2820
|
/**
|
|
2821
|
+
* Reload every resident slot whose model FILE is no longer the one
|
|
2822
|
+
* `buildConfig` names — a Coral pool that switched co-compiled set (D724).
|
|
2823
|
+
* The new slot is loaded BEFORE the old one is unloaded and the entry is
|
|
2824
|
+
* replaced in between, so a lookup always finds a loaded index. Returns the
|
|
2825
|
+
* `(step, model)` pairs reloaded. A failed reload keeps the old slot (it
|
|
2826
|
+
* still serves, from the file it was loaded from) and is logged.
|
|
2827
|
+
*/
|
|
2828
|
+
async reloadChangedPaths() {
|
|
2829
|
+
const reloaded = [];
|
|
2830
|
+
for (const [stepId, perStep] of this.loaded) for (const entry of perStep.values()) {
|
|
2831
|
+
const config = this.source.buildConfig(stepId, entry.modelId, entry.settings);
|
|
2832
|
+
if (config.path === entry.path) continue;
|
|
2833
|
+
const index = this.pool.allocateIndex();
|
|
2834
|
+
try {
|
|
2835
|
+
await this.pool.loadModel(index, config);
|
|
2836
|
+
} catch (err) {
|
|
2837
|
+
this.pool.releaseIndex(index);
|
|
2838
|
+
this.log.error("Step variant reload from another model file failed — the old file keeps serving", { meta: {
|
|
2839
|
+
step: stepId,
|
|
2840
|
+
modelId: entry.modelId,
|
|
2841
|
+
deviceKey: this.deviceKey,
|
|
2842
|
+
from: entry.path,
|
|
2843
|
+
to: config.path,
|
|
2844
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2845
|
+
} });
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
perStep.set(entry.modelId, {
|
|
2849
|
+
...entry,
|
|
2850
|
+
poolIndex: index,
|
|
2851
|
+
path: config.path,
|
|
2852
|
+
decode: poolDecodeSettingsOf(config)
|
|
2853
|
+
});
|
|
2854
|
+
await this.pool.unloadModel(entry.poolIndex);
|
|
2855
|
+
this.log.info("Step variant reloaded from another model file", { meta: {
|
|
2856
|
+
step: stepId,
|
|
2857
|
+
modelId: entry.modelId,
|
|
2858
|
+
deviceKey: this.deviceKey,
|
|
2859
|
+
from: entry.path,
|
|
2860
|
+
to: config.path,
|
|
2861
|
+
poolIndex: index
|
|
2862
|
+
} });
|
|
2863
|
+
reloaded.push({
|
|
2864
|
+
stepId,
|
|
2865
|
+
modelId: entry.modelId
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
return reloaded;
|
|
2869
|
+
}
|
|
2870
|
+
/**
|
|
2819
2871
|
* Push a retuned decode setting into ONE already-loaded slot.
|
|
2820
2872
|
*
|
|
2821
2873
|
* Scope is the point: `reconfigure` names a single pool index, so a slider
|
|
@@ -2974,6 +3026,251 @@ function deviceKeyOf(engine) {
|
|
|
2974
3026
|
return engine.device ? `${engine.backend}:${engine.device}` : engine.backend;
|
|
2975
3027
|
}
|
|
2976
3028
|
//#endregion
|
|
3029
|
+
//#region src/detection-pipeline/engine/coral-cocompiled-set.ts
|
|
3030
|
+
var ROOT_DETECTOR_STEP_ID = "object-detection";
|
|
3031
|
+
/** The models a dispatch places on the pool that runs it: every enabled video step. */
|
|
3032
|
+
function placedModelsOf(steps) {
|
|
3033
|
+
return flattenEnabledVideoSteps(steps).map((s) => ({
|
|
3034
|
+
stepId: s.addonId,
|
|
3035
|
+
modelId: s.modelId
|
|
3036
|
+
}));
|
|
3037
|
+
}
|
|
3038
|
+
var sameModel = (a, b) => a.stepId === b.stepId && a.modelId === b.modelId;
|
|
3039
|
+
/**
|
|
3040
|
+
* The set for `placed`, or why there is none.
|
|
3041
|
+
*
|
|
3042
|
+
* 1. A set that reserves the cache for the root detector is eligible only when
|
|
3043
|
+
* the root is placed here, and a rootless set only when it is not: a root
|
|
3044
|
+
* set on a pool without the root wastes the cache the root would have had,
|
|
3045
|
+
* and a rootless set beside a root makes the root swap.
|
|
3046
|
+
* 2. A set must stand for at least two placed models — one model alone is
|
|
3047
|
+
* better off with the whole cache (its separately compiled file).
|
|
3048
|
+
* 3. Most placed models covered wins; then the set with fewest members nobody
|
|
3049
|
+
* placed (they hold cache for nothing); then declaration order.
|
|
3050
|
+
*/
|
|
3051
|
+
function selectCoCompiledSet(placed, sets) {
|
|
3052
|
+
if (placed.length === 0) return {
|
|
3053
|
+
kind: "separate",
|
|
3054
|
+
reason: "nothing-placed"
|
|
3055
|
+
};
|
|
3056
|
+
if (placed.length === 1) return {
|
|
3057
|
+
kind: "separate",
|
|
3058
|
+
reason: "single-model"
|
|
3059
|
+
};
|
|
3060
|
+
const hostsRoot = placed.some((p) => p.stepId === ROOT_DETECTOR_STEP_ID);
|
|
3061
|
+
let best = null;
|
|
3062
|
+
for (const set of sets) {
|
|
3063
|
+
if (set.hostsRoot !== hostsRoot) continue;
|
|
3064
|
+
const covered = placed.filter((p) => set.members.some((m) => sameModel(m, p)));
|
|
3065
|
+
if (covered.length < 2) continue;
|
|
3066
|
+
const idle = set.members.length - covered.length;
|
|
3067
|
+
if (best === null || covered.length > best.covered.length || covered.length === best.covered.length && idle < best.idle) best = {
|
|
3068
|
+
set,
|
|
3069
|
+
covered,
|
|
3070
|
+
idle
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
if (best === null) return {
|
|
3074
|
+
kind: "separate",
|
|
3075
|
+
reason: "no-set-covers-two"
|
|
3076
|
+
};
|
|
3077
|
+
const { set, covered } = best;
|
|
3078
|
+
return {
|
|
3079
|
+
kind: "set",
|
|
3080
|
+
set,
|
|
3081
|
+
covered,
|
|
3082
|
+
uncovered: placed.filter((p) => !covered.some((c) => sameModel(c, p)))
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
/** The member a selection loads `(stepId, modelId)` from, or `null` (its own `formats.tflite`). */
|
|
3086
|
+
function setMemberFor(selection, stepId, modelId) {
|
|
3087
|
+
if (selection.kind !== "set") return null;
|
|
3088
|
+
return selection.set.members.find((m) => m.stepId === stepId && m.modelId === modelId) ?? null;
|
|
3089
|
+
}
|
|
3090
|
+
/**
|
|
3091
|
+
* `entry` with its `tflite` build pointed at the set's file. Everything else —
|
|
3092
|
+
* labels, postprocessor, input contract, licence — is the entry's own: a
|
|
3093
|
+
* member is the same model, compiled together with others.
|
|
3094
|
+
*/
|
|
3095
|
+
function withSetBuild(entry, member) {
|
|
3096
|
+
return {
|
|
3097
|
+
...entry,
|
|
3098
|
+
formats: {
|
|
3099
|
+
...entry.formats,
|
|
3100
|
+
tflite: {
|
|
3101
|
+
url: member.url,
|
|
3102
|
+
sizeMB: member.sizeMB,
|
|
3103
|
+
runtimes: ["python"]
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
/** Identity of a selection for change detection: the set AND what it leaves out. */
|
|
3109
|
+
var selectionKey = (s) => s.kind === "set" ? `${s.set.id}|${s.uncovered.map((u) => `${u.stepId}/${u.modelId}`).toSorted().join(",")}` : `separate:${s.reason}`;
|
|
3110
|
+
/**
|
|
3111
|
+
* The placement of ONE Coral pool and the set it loads. Lives exactly as long
|
|
3112
|
+
* as the pool (the factory owns it).
|
|
3113
|
+
*/
|
|
3114
|
+
var CoralSetPlanner = class {
|
|
3115
|
+
sets;
|
|
3116
|
+
placed = [];
|
|
3117
|
+
current = {
|
|
3118
|
+
kind: "separate",
|
|
3119
|
+
reason: "nothing-placed"
|
|
3120
|
+
};
|
|
3121
|
+
constructor(sets) {
|
|
3122
|
+
this.sets = sets;
|
|
3123
|
+
}
|
|
3124
|
+
/** Add the models a dispatch places on this pool; say whether the set changed. */
|
|
3125
|
+
note(models) {
|
|
3126
|
+
let grew = false;
|
|
3127
|
+
for (const m of models) {
|
|
3128
|
+
if (this.placed.some((p) => sameModel(p, m))) continue;
|
|
3129
|
+
this.placed.push({
|
|
3130
|
+
stepId: m.stepId,
|
|
3131
|
+
modelId: m.modelId
|
|
3132
|
+
});
|
|
3133
|
+
grew = true;
|
|
3134
|
+
}
|
|
3135
|
+
if (!grew) return { kind: "unchanged" };
|
|
3136
|
+
const next = selectCoCompiledSet(this.placed, this.sets);
|
|
3137
|
+
if (selectionKey(next) === selectionKey(this.current)) {
|
|
3138
|
+
this.current = next;
|
|
3139
|
+
return { kind: "unchanged" };
|
|
3140
|
+
}
|
|
3141
|
+
const from = this.current;
|
|
3142
|
+
this.current = next;
|
|
3143
|
+
return {
|
|
3144
|
+
kind: "changed",
|
|
3145
|
+
from,
|
|
3146
|
+
to: next,
|
|
3147
|
+
placed: [...this.placed]
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
selection() {
|
|
3151
|
+
return this.current;
|
|
3152
|
+
}
|
|
3153
|
+
/** `entry` as this pool must load it: from the set when the set stands for it. */
|
|
3154
|
+
entryFor(stepId, entry) {
|
|
3155
|
+
const member = setMemberFor(this.current, stepId, entry.id);
|
|
3156
|
+
return member === null ? entry : withSetBuild(entry, member);
|
|
3157
|
+
}
|
|
3158
|
+
};
|
|
3159
|
+
//#endregion
|
|
3160
|
+
//#region src/detection-pipeline/registry/edgetpu-cocompiled-sets.ts
|
|
3161
|
+
/**
|
|
3162
|
+
* Co-compiled Edge TPU sets — pool-level artifacts, not models (D724).
|
|
3163
|
+
*
|
|
3164
|
+
* A Coral holds ~6.9 MiB of parameter cache. Two separately compiled models
|
|
3165
|
+
* carry two different cache tokens, so every alternation between them on one
|
|
3166
|
+
* Coral flushes the cache and re-streams the incoming model's parameters over
|
|
3167
|
+
* USB (D710). `edgetpu_compiler` given several models at once allocates the
|
|
3168
|
+
* cache between them, in command-line order, under ONE token: loaded from the
|
|
3169
|
+
* same set, they never evict each other — whatever does not fit is streamed on
|
|
3170
|
+
* every invoke of THAT model instead.
|
|
3171
|
+
*
|
|
3172
|
+
* Each member is the SAME model as its catalog entry's `formats.tflite` (same
|
|
3173
|
+
* `modelId`, same int8 graph, same labels and licence record), compiled
|
|
3174
|
+
* together with the other members (D710: a Coral build is a format of the
|
|
3175
|
+
* survivor; the set is where it is placed, not a new model). A Coral pool picks
|
|
3176
|
+
* at most one set from the models placed on it (`coral-cocompiled-set.ts`) and
|
|
3177
|
+
* falls back to the separately compiled `formats.tflite` for everything else.
|
|
3178
|
+
*
|
|
3179
|
+
* Built by `scripts/build-edgetpu-sets.py` (compile order = `members` order =
|
|
3180
|
+
* cache priority) and hosted under `edgetpuSets/<id>/`. Only sets whose
|
|
3181
|
+
* measured cost is acceptable are listed here: a set that would make the root
|
|
3182
|
+
* detector stream parameters on every frame (root + YuNet at 640: the root
|
|
3183
|
+
* keeps 3.34 of its 6.39 MiB, YuNet's activations take the rest) is
|
|
3184
|
+
* deliberately absent.
|
|
3185
|
+
*/
|
|
3186
|
+
var HF_REPO = "camstack/camstack-models";
|
|
3187
|
+
var setFile = (setId, file) => hfModelUrl(HF_REPO, `edgetpuSets/${setId}/${setId}__${file}`);
|
|
3188
|
+
var SSD = "ssd-mobilenet-v2-coco-edgetpu";
|
|
3189
|
+
var YUNET = "yunet-2023mar";
|
|
3190
|
+
var YOLOX = "yolox-tiny-plate";
|
|
3191
|
+
var BIRD = "bird-classifier";
|
|
3192
|
+
var EDGETPU_COCOMPILED_SETS = [
|
|
3193
|
+
{
|
|
3194
|
+
id: "root-plate-bird",
|
|
3195
|
+
hostsRoot: true,
|
|
3196
|
+
members: [
|
|
3197
|
+
{
|
|
3198
|
+
stepId: "object-detection",
|
|
3199
|
+
modelId: SSD,
|
|
3200
|
+
url: setFile("root-plate-bird", "ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite"),
|
|
3201
|
+
sizeMB: 6.9,
|
|
3202
|
+
onChipMiB: 6.39,
|
|
3203
|
+
streamedMiB: 0
|
|
3204
|
+
},
|
|
3205
|
+
{
|
|
3206
|
+
stepId: "plate-detection",
|
|
3207
|
+
modelId: YOLOX,
|
|
3208
|
+
url: setFile("root-plate-bird", "camstack-yolox-tiny-plate_int8_edgetpu.tflite"),
|
|
3209
|
+
sizeMB: 6.9,
|
|
3210
|
+
onChipMiB: .08,
|
|
3211
|
+
streamedMiB: 5.41
|
|
3212
|
+
},
|
|
3213
|
+
{
|
|
3214
|
+
stepId: "bird-classifier",
|
|
3215
|
+
modelId: BIRD,
|
|
3216
|
+
url: setFile("root-plate-bird", "mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite"),
|
|
3217
|
+
sizeMB: 4.9,
|
|
3218
|
+
onChipMiB: 0,
|
|
3219
|
+
streamedMiB: 4.54
|
|
3220
|
+
}
|
|
3221
|
+
]
|
|
3222
|
+
},
|
|
3223
|
+
{
|
|
3224
|
+
id: "face-plate-bird",
|
|
3225
|
+
hostsRoot: false,
|
|
3226
|
+
members: [
|
|
3227
|
+
{
|
|
3228
|
+
stepId: "face-detection",
|
|
3229
|
+
modelId: YUNET,
|
|
3230
|
+
url: setFile("face-plate-bird", "camstack-yunet-2023mar_int8_edgetpu.tflite"),
|
|
3231
|
+
sizeMB: .7,
|
|
3232
|
+
onChipMiB: .11,
|
|
3233
|
+
streamedMiB: 0
|
|
3234
|
+
},
|
|
3235
|
+
{
|
|
3236
|
+
stepId: "plate-detection",
|
|
3237
|
+
modelId: YOLOX,
|
|
3238
|
+
url: setFile("face-plate-bird", "camstack-yolox-tiny-plate_int8_edgetpu.tflite"),
|
|
3239
|
+
sizeMB: 6.9,
|
|
3240
|
+
onChipMiB: 3.23,
|
|
3241
|
+
streamedMiB: 2.27
|
|
3242
|
+
},
|
|
3243
|
+
{
|
|
3244
|
+
stepId: "bird-classifier",
|
|
3245
|
+
modelId: BIRD,
|
|
3246
|
+
url: setFile("face-plate-bird", "mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite"),
|
|
3247
|
+
sizeMB: 4.9,
|
|
3248
|
+
onChipMiB: 0,
|
|
3249
|
+
streamedMiB: 4.54
|
|
3250
|
+
}
|
|
3251
|
+
]
|
|
3252
|
+
},
|
|
3253
|
+
{
|
|
3254
|
+
id: "plate-bird",
|
|
3255
|
+
hostsRoot: false,
|
|
3256
|
+
members: [{
|
|
3257
|
+
stepId: "plate-detection",
|
|
3258
|
+
modelId: YOLOX,
|
|
3259
|
+
url: setFile("plate-bird", "camstack-yolox-tiny-plate_int8_edgetpu.tflite"),
|
|
3260
|
+
sizeMB: 6.9,
|
|
3261
|
+
onChipMiB: 5.55,
|
|
3262
|
+
streamedMiB: 0
|
|
3263
|
+
}, {
|
|
3264
|
+
stepId: "bird-classifier",
|
|
3265
|
+
modelId: BIRD,
|
|
3266
|
+
url: setFile("plate-bird", "mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite"),
|
|
3267
|
+
sizeMB: 4.1,
|
|
3268
|
+
onChipMiB: .92,
|
|
3269
|
+
streamedMiB: 2.8
|
|
3270
|
+
}]
|
|
3271
|
+
}
|
|
3272
|
+
];
|
|
3273
|
+
//#endregion
|
|
2977
3274
|
//#region src/detection-pipeline/engine/engine-factory.ts
|
|
2978
3275
|
var BACKEND_TO_POOL_RUNTIME = {
|
|
2979
3276
|
coreml: "coreml",
|
|
@@ -3067,11 +3364,45 @@ var EngineFactory = class {
|
|
|
3067
3364
|
* trying to be.
|
|
3068
3365
|
*/
|
|
3069
3366
|
resolvedProvisioning;
|
|
3367
|
+
/**
|
|
3368
|
+
* A Coral pool's placement and the co-compiled set it loads (D724); `null`
|
|
3369
|
+
* on every other runtime. Lives exactly as long as this factory — a new
|
|
3370
|
+
* pool starts from nothing placed.
|
|
3371
|
+
*/
|
|
3372
|
+
coralSets;
|
|
3070
3373
|
constructor(opts) {
|
|
3071
3374
|
this.opts = opts;
|
|
3072
3375
|
this.log = opts.logger;
|
|
3073
3376
|
this.deviceKey = deviceKeyOf(opts.engine);
|
|
3074
3377
|
this.resolvedProvisioning = resolvePoolProvisioning(opts.engine, opts.provisioning);
|
|
3378
|
+
this.coralSets = BACKEND_TO_POOL_RUNTIME[opts.engine.backend] === "edgetpu" ? new CoralSetPlanner(opts.coCompiledSets ?? EDGETPU_COCOMPILED_SETS) : null;
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3381
|
+
* Record the models a dispatch places on this pool. On a Coral pool the
|
|
3382
|
+
* answer says whether the co-compiled set it loads from changed; on any
|
|
3383
|
+
* other pool nothing is placed and nothing changes.
|
|
3384
|
+
*/
|
|
3385
|
+
notePlacement(models) {
|
|
3386
|
+
return this.coralSets?.note(models) ?? { kind: "unchanged" };
|
|
3387
|
+
}
|
|
3388
|
+
/** The co-compiled set this pool loads from, or why none (`null`: not a Coral pool). */
|
|
3389
|
+
coralSetSelection() {
|
|
3390
|
+
return this.coralSets?.selection() ?? null;
|
|
3391
|
+
}
|
|
3392
|
+
/**
|
|
3393
|
+
* The catalog entry as THIS pool must download and load it: on a Coral pool
|
|
3394
|
+
* whose set stands for the model, the `tflite` build is the set's file.
|
|
3395
|
+
* Every other entry is returned unchanged.
|
|
3396
|
+
*/
|
|
3397
|
+
entryForPool(stepId, entry) {
|
|
3398
|
+
return this.coralSets?.entryFor(stepId, entry) ?? entry;
|
|
3399
|
+
}
|
|
3400
|
+
/**
|
|
3401
|
+
* Reload every resident model whose file this pool no longer names — after
|
|
3402
|
+
* the Coral set changed. The caller has already downloaded the new files.
|
|
3403
|
+
*/
|
|
3404
|
+
async reloadChangedModelFiles() {
|
|
3405
|
+
return this.poolManager?.reloadChangedPaths() ?? [];
|
|
3075
3406
|
}
|
|
3076
3407
|
/**
|
|
3077
3408
|
* The provisioning this factory's pool runs with — resolved from the pool's
|
|
@@ -3333,6 +3664,7 @@ var EngineFactory = class {
|
|
|
3333
3664
|
stepSettings,
|
|
3334
3665
|
device: this.opts.engine.device,
|
|
3335
3666
|
resolveCustomModel: this.opts.resolveCustomModel,
|
|
3667
|
+
entryForPool: (sid, entry) => this.entryForPool(sid, entry),
|
|
3336
3668
|
onClassMapDrop: ({ stepId: sid, modelId: mid, unmappedLabels }) => {
|
|
3337
3669
|
const key = `${sid}::${mid}`;
|
|
3338
3670
|
if (this.classMapDropLogged.has(key)) return;
|
|
@@ -3358,8 +3690,9 @@ var EngineFactory = class {
|
|
|
3358
3690
|
function buildPoolModelConfigForStep(inputs) {
|
|
3359
3691
|
const { stepId, modelId, poolRuntime, modelsDir } = inputs;
|
|
3360
3692
|
const def = getStepDefinition(stepId);
|
|
3361
|
-
const
|
|
3362
|
-
if (!
|
|
3693
|
+
const resolvedEntry = def.models.find((m) => m.id === modelId) ?? inputs.resolveCustomModel?.(stepId, modelId);
|
|
3694
|
+
if (!resolvedEntry) throw new Error(`Model "${modelId}" not found in step "${stepId}" catalog (static or custom registry)`);
|
|
3695
|
+
const modelEntry = inputs.entryForPool?.(stepId, resolvedEntry) ?? resolvedEntry;
|
|
3363
3696
|
const format = RUNTIME_TO_FORMAT[poolRuntime];
|
|
3364
3697
|
if (!format) throw new Error(`No format mapping for pool runtime "${poolRuntime}"`);
|
|
3365
3698
|
const dropWarning = collectClassMapDropWarning(def, modelEntry);
|
|
@@ -5443,6 +5776,26 @@ var PLATE_OCR_CROP_POLICIES = {
|
|
|
5443
5776
|
}
|
|
5444
5777
|
};
|
|
5445
5778
|
/**
|
|
5779
|
+
* The margin around a plate box drawn by the ROOT model (D730) — a Frigate+
|
|
5780
|
+
* `license_plate` read in place of `plate-detection`'s own box.
|
|
5781
|
+
*
|
|
5782
|
+
* The margin is a property of the box's detector (see above), and this
|
|
5783
|
+
* detector's boxes have never been measured against the plate readers: 0.15 is
|
|
5784
|
+
* the value that served a TIGHT detector box (the deleted `yolov8n-plate`,
|
|
5785
|
+
* 7/7 Kia reads at 0.13–0.16), on the assumption that Frigate+ boxes are
|
|
5786
|
+
* tight as well. It is uncalibrated and needs measuring on a ground-truth set
|
|
5787
|
+
* of root-supplied plates before it is trusted; until then it applies to every
|
|
5788
|
+
* reader alike, replacing the reader's own detector-specific margin.
|
|
5789
|
+
*/
|
|
5790
|
+
var ROOT_SUPPLIED_PLATE_MARGIN_FRACTION = .15;
|
|
5791
|
+
/**
|
|
5792
|
+
* The margin a reader cuts around its plate box: the reader's own, unless the
|
|
5793
|
+
* box was drawn by the root model.
|
|
5794
|
+
*/
|
|
5795
|
+
function plateOcrMarginFraction(policy, boxFromRoot) {
|
|
5796
|
+
return boxFromRoot ? ROOT_SUPPLIED_PLATE_MARGIN_FRACTION : policy.marginFraction;
|
|
5797
|
+
}
|
|
5798
|
+
/**
|
|
5446
5799
|
* The crop policy for a step child, or `null` when the child is not a plate
|
|
5447
5800
|
* reader at all (a face embedder, a classifier — nothing here applies to them).
|
|
5448
5801
|
*/
|
|
@@ -5472,6 +5825,79 @@ function expandBbox(bbox, marginFraction, imageWidth, imageHeight) {
|
|
|
5472
5825
|
];
|
|
5473
5826
|
}
|
|
5474
5827
|
//#endregion
|
|
5828
|
+
//#region src/detection-pipeline/pipeline/root-child-boxes.ts
|
|
5829
|
+
/**
|
|
5830
|
+
* Root child boxes (D730) — a root model's `license_plate` / `face` box made
|
|
5831
|
+
* into a DETAIL of the vehicle / person that contains it.
|
|
5832
|
+
*
|
|
5833
|
+
* The labels are deliberately absent from the root's classMap, so they never
|
|
5834
|
+
* make a first-level detection and therefore never a track (D683). When the
|
|
5835
|
+
* root declares a CROP supply for them, the executor keeps the boxes aside
|
|
5836
|
+
* instead of discarding them, and once the frame's first-level detections are
|
|
5837
|
+
* final this attaches each to its containing parent (`containingParentIndex`).
|
|
5838
|
+
* Whether the camera USES them is the runner's decision
|
|
5839
|
+
* (`pipeline-runner/root-supply.ts`); a box nothing contains is discarded.
|
|
5840
|
+
*
|
|
5841
|
+
* Pure.
|
|
5842
|
+
*/
|
|
5843
|
+
/** The crop supply a raw root class belongs to, if the root declares one. */
|
|
5844
|
+
function cropSupplyForLabel(supplies, rawClass) {
|
|
5845
|
+
for (const supply of supplies ?? []) if (supply.kind === "crop" && supply.rootLabels.includes(rawClass)) return supply;
|
|
5846
|
+
}
|
|
5847
|
+
function toBox(b) {
|
|
5848
|
+
return {
|
|
5849
|
+
x: b[0],
|
|
5850
|
+
y: b[1],
|
|
5851
|
+
w: b[2] - b[0],
|
|
5852
|
+
h: b[3] - b[1]
|
|
5853
|
+
};
|
|
5854
|
+
}
|
|
5855
|
+
/**
|
|
5856
|
+
* Attach each candidate to the smallest first-level parent of its supply's
|
|
5857
|
+
* macro that contains it. Never mutates the parents.
|
|
5858
|
+
*/
|
|
5859
|
+
function attachRootChildBoxes(candidates, parents, modelId, idGen) {
|
|
5860
|
+
const details = [];
|
|
5861
|
+
const uncontained = [];
|
|
5862
|
+
for (const candidate of candidates) {
|
|
5863
|
+
const eligible = parents.filter((p) => p.kind === "first-level" && p.macroClass === candidate.supply.parentMacro);
|
|
5864
|
+
const index = containingParentIndex(toBox(candidate.bbox), eligible.map((p) => toBox(p.bbox)));
|
|
5865
|
+
const parent = index >= 0 ? eligible[index] : void 0;
|
|
5866
|
+
if (parent === void 0) {
|
|
5867
|
+
uncontained.push(candidate);
|
|
5868
|
+
continue;
|
|
5869
|
+
}
|
|
5870
|
+
const stepId = candidate.supply.stepId;
|
|
5871
|
+
details.push({
|
|
5872
|
+
id: idGen.next(),
|
|
5873
|
+
kind: "detail",
|
|
5874
|
+
macroClass: candidate.supply.childClass,
|
|
5875
|
+
originalClass: candidate.rawClass,
|
|
5876
|
+
score: candidate.score,
|
|
5877
|
+
bbox: [
|
|
5878
|
+
candidate.bbox[0],
|
|
5879
|
+
candidate.bbox[1],
|
|
5880
|
+
candidate.bbox[2],
|
|
5881
|
+
candidate.bbox[3]
|
|
5882
|
+
],
|
|
5883
|
+
parentId: parent.id,
|
|
5884
|
+
rootSupplied: {
|
|
5885
|
+
stepId,
|
|
5886
|
+
modelId
|
|
5887
|
+
},
|
|
5888
|
+
ownLabels: [],
|
|
5889
|
+
alternateLabels: {},
|
|
5890
|
+
stepLatencyMs: {},
|
|
5891
|
+
modelIds: { [stepId]: modelId },
|
|
5892
|
+
extractMode: "full-frame"
|
|
5893
|
+
});
|
|
5894
|
+
}
|
|
5895
|
+
return {
|
|
5896
|
+
details,
|
|
5897
|
+
uncontained
|
|
5898
|
+
};
|
|
5899
|
+
}
|
|
5900
|
+
//#endregion
|
|
5475
5901
|
//#region src/detection-pipeline/pipeline/plate-warp.ts
|
|
5476
5902
|
/**
|
|
5477
5903
|
* Plate-crop WARP — perspective rectification of a licence-plate ROI to a
|
|
@@ -6028,6 +6454,7 @@ function buildFrameResult(input) {
|
|
|
6028
6454
|
} : {},
|
|
6029
6455
|
labels,
|
|
6030
6456
|
...m.parentId !== void 0 ? { parentId: m.parentId } : {},
|
|
6457
|
+
...m.rootSupplied !== void 0 ? { rootSupplied: m.rootSupplied } : {},
|
|
6031
6458
|
...embedding !== void 0 ? {
|
|
6032
6459
|
embedding,
|
|
6033
6460
|
...embeddingModelId !== void 0 ? { embeddingModelId } : {}
|
|
@@ -6129,33 +6556,35 @@ function toMutableRootDetection(det, rootStep, idGen, rootStepLatencyMs) {
|
|
|
6129
6556
|
//#endregion
|
|
6130
6557
|
//#region src/detection-pipeline/pipeline/linked-label.ts
|
|
6131
6558
|
/**
|
|
6132
|
-
* The detector's linked label (D715): when the model that RAN declares
|
|
6133
|
-
*
|
|
6134
|
-
* the effect as a `ScoredLabel` attributed to `detector-link:<stepId>`
|
|
6135
|
-
* the step's own vocabulary and tier, so tier 1 means one thing whichever
|
|
6559
|
+
* The detector's linked label (D715, D730): when the model that RAN declares a
|
|
6560
|
+
* LABEL supply for a step, a detection whose raw class names that step's effect
|
|
6561
|
+
* carries the effect as a `ScoredLabel` attributed to `detector-link:<stepId>`
|
|
6562
|
+
* — in the step's own vocabulary and tier, so tier 1 means one thing whichever
|
|
6136
6563
|
* writer produced it.
|
|
6137
6564
|
*
|
|
6138
6565
|
* Written at score 0 here, always: the executor cannot see the camera's whole
|
|
6139
6566
|
* step tree (a detail step placed on another pool is pruned from the dispatch),
|
|
6140
|
-
* so it cannot know
|
|
6141
|
-
*
|
|
6142
|
-
*
|
|
6143
|
-
*
|
|
6567
|
+
* so it cannot know whether the camera USES the supply. The runner, which holds
|
|
6568
|
+
* the attach config, decides with `resolveRootSupply` (`pipeline-runner/root-supply.ts`):
|
|
6569
|
+
* effective ⇒ the label is raised to the detection's score, or dropped when the
|
|
6570
|
+
* step's class list switched it off; otherwise it stays the score-0 fallback
|
|
6571
|
+
* any scored answer of the step outranks.
|
|
6144
6572
|
*/
|
|
6145
6573
|
/** The linked labels one mapped root detection carries. Pure. */
|
|
6146
|
-
function linkedLabelsFor(
|
|
6147
|
-
if (
|
|
6574
|
+
function linkedLabelsFor(supplies, rawClass, macroClass, modelId) {
|
|
6575
|
+
if (supplies === void 0 || supplies.length === 0) return [];
|
|
6148
6576
|
const out = [];
|
|
6149
|
-
for (const
|
|
6150
|
-
if (
|
|
6151
|
-
|
|
6577
|
+
for (const supply of supplies) {
|
|
6578
|
+
if (supply.kind !== "label") continue;
|
|
6579
|
+
if (LABEL_SUPPLY_MACRO[supply.stepId] !== macroClass) continue;
|
|
6580
|
+
const effect = supply.labelToEffect[rawClass];
|
|
6152
6581
|
if (effect === void 0 || effect === macroClass) continue;
|
|
6153
|
-
const tier = getStepDefinition(
|
|
6582
|
+
const tier = getStepDefinition(supply.stepId).labelTier;
|
|
6154
6583
|
out.push({
|
|
6155
6584
|
label: effect,
|
|
6156
6585
|
score: 0,
|
|
6157
6586
|
...tier !== void 0 ? { tier } : {},
|
|
6158
|
-
stepId: linkedLabelStepId(
|
|
6587
|
+
stepId: linkedLabelStepId(supply.stepId),
|
|
6159
6588
|
modelId
|
|
6160
6589
|
});
|
|
6161
6590
|
}
|
|
@@ -6384,10 +6813,19 @@ var PipelineExecutor = class {
|
|
|
6384
6813
|
const rootView = rootInputViewProvider ? await rootInputViewProvider(rootStep, rootCrop) : null;
|
|
6385
6814
|
const cropPrep = !rootView && rootCrop !== void 0 ? await this.prepareCropZoneInput(rootCrop, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) : null;
|
|
6386
6815
|
const rootStart = Date.now();
|
|
6387
|
-
const
|
|
6816
|
+
const supplied = runOpts?.suppliedRoot?.stepId === rootStep.stepId ? runOpts.suppliedRoot : void 0;
|
|
6817
|
+
const rawRootOutput = supplied ? {
|
|
6818
|
+
kind: "detections",
|
|
6819
|
+
detections: supplied.detections.map((d) => ({
|
|
6820
|
+
class: rootStep.definition.outputClasses[0] ?? rootStep.stepId,
|
|
6821
|
+
score: d.score,
|
|
6822
|
+
bbox: d.bbox,
|
|
6823
|
+
...d.landmarks !== void 0 ? { landmarks: d.landmarks } : {}
|
|
6824
|
+
}))
|
|
6825
|
+
} : await this.executeStep(rootStep, rootView?.input ?? cropPrep?.input ?? withInferenceDeviceId(rootInput, deviceId), rootView?.width ?? cropPrep?.width ?? imageWidth, rootView?.height ?? cropPrep?.height ?? imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg, rootShed);
|
|
6388
6826
|
const rootMs = Date.now() - rootStart;
|
|
6389
6827
|
if (rawRootOutput === null) continue;
|
|
6390
|
-
const rootOutput = rootView && rawRootOutput.kind === "detections" ? reprojectViewDetections(rawRootOutput, rootView.geometry) : cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
|
|
6828
|
+
const rootOutput = supplied ? rawRootOutput : rootView && rawRootOutput.kind === "detections" ? reprojectViewDetections(rawRootOutput, rootView.geometry) : cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
|
|
6391
6829
|
if (rootOutput.kind !== "detections") {
|
|
6392
6830
|
if (isEnrichmentOutput(rootOutput)) {
|
|
6393
6831
|
const mutable = this.synthesizeRootDetection(rootOutput, rootStep, idGen, rootMs, imageWidth, imageHeight);
|
|
@@ -6409,8 +6847,33 @@ var PipelineExecutor = class {
|
|
|
6409
6847
|
}
|
|
6410
6848
|
continue;
|
|
6411
6849
|
}
|
|
6850
|
+
const rootChildCandidates = [];
|
|
6851
|
+
const firstLevelStart = firstLevel.length;
|
|
6412
6852
|
for (const det of rootOutput.detections) {
|
|
6413
|
-
const
|
|
6853
|
+
const cropSupply = cropSupplyForLabel(rootStep.rootSupplies, det.class);
|
|
6854
|
+
if (cropSupply !== void 0) {
|
|
6855
|
+
rootChildCandidates.push({
|
|
6856
|
+
rawClass: det.class,
|
|
6857
|
+
score: det.score,
|
|
6858
|
+
bbox: [
|
|
6859
|
+
det.bbox[0],
|
|
6860
|
+
det.bbox[1],
|
|
6861
|
+
det.bbox[2],
|
|
6862
|
+
det.bbox[3]
|
|
6863
|
+
],
|
|
6864
|
+
supply: cropSupply
|
|
6865
|
+
});
|
|
6866
|
+
continue;
|
|
6867
|
+
}
|
|
6868
|
+
const seeded = toMutableRootDetection(det, rootStep, idGen, rootMs);
|
|
6869
|
+
const mutable = supplied ? {
|
|
6870
|
+
...seeded,
|
|
6871
|
+
rootSupplied: {
|
|
6872
|
+
stepId: rootStep.stepId,
|
|
6873
|
+
modelId: supplied.modelId
|
|
6874
|
+
},
|
|
6875
|
+
modelIds: { [rootStep.stepId]: supplied.modelId }
|
|
6876
|
+
} : seeded;
|
|
6414
6877
|
if (rootStep.effectiveClassMap) {
|
|
6415
6878
|
const mapped = rootStep.effectiveClassMap.mapping[det.class];
|
|
6416
6879
|
if (mapped) {
|
|
@@ -6431,7 +6894,7 @@ var PipelineExecutor = class {
|
|
|
6431
6894
|
continue;
|
|
6432
6895
|
}
|
|
6433
6896
|
}
|
|
6434
|
-
for (const linked of linkedLabelsFor(rootStep.
|
|
6897
|
+
for (const linked of linkedLabelsFor(rootStep.rootSupplies, det.class, mutable.macroClass, rootStep.modelId)) mutable.ownLabels.push(linked);
|
|
6435
6898
|
const filterVerdict = this.macroFilterVerdict(mutable.macroClass, mutable.score, rootStep.settings);
|
|
6436
6899
|
if (filterVerdict !== "pass") {
|
|
6437
6900
|
(discarded ??= []).push({
|
|
@@ -6513,6 +6976,21 @@ var PipelineExecutor = class {
|
|
|
6513
6976
|
}
|
|
6514
6977
|
firstLevel.push(mutable);
|
|
6515
6978
|
}
|
|
6979
|
+
if (rootChildCandidates.length > 0) {
|
|
6980
|
+
const attached = attachRootChildBoxes(rootChildCandidates, firstLevel.slice(firstLevelStart), rootStep.modelId, idGen);
|
|
6981
|
+
details.push(...attached.details);
|
|
6982
|
+
for (const c of attached.uncontained) (discarded ??= []).push({
|
|
6983
|
+
bbox: [
|
|
6984
|
+
c.bbox[0],
|
|
6985
|
+
c.bbox[1],
|
|
6986
|
+
c.bbox[2],
|
|
6987
|
+
c.bbox[3]
|
|
6988
|
+
],
|
|
6989
|
+
macroClass: c.supply.childClass,
|
|
6990
|
+
score: c.score,
|
|
6991
|
+
reason: "class-filter"
|
|
6992
|
+
});
|
|
6993
|
+
}
|
|
6516
6994
|
}
|
|
6517
6995
|
const totalMs = Date.now() - startMs;
|
|
6518
6996
|
return {
|
|
@@ -6780,7 +7258,8 @@ var PipelineExecutor = class {
|
|
|
6780
7258
|
}
|
|
6781
7259
|
} else {
|
|
6782
7260
|
const cropPolicy = resolvePlateOcrCropPolicy(resolveEffectivePostprocessor(child.definition, child.modelId));
|
|
6783
|
-
const
|
|
7261
|
+
const marginFraction = cropPolicy ? plateOcrMarginFraction(cropPolicy, parentDetection.rootSupplied !== void 0) : 0;
|
|
7262
|
+
const cropBbox = cropPolicy ? expandBbox(parentDetection.bbox, marginFraction, imageWidth, imageHeight) : parentDetection.bbox;
|
|
6784
7263
|
const nativeChild = nativeCropProvider && child.children.length === 0 ? await nativeChildCrop(nativeCropProvider, cropBbox, imageWidth, imageHeight, NATIVE_CHILD_CROP_MAX_WIDTH) : null;
|
|
6785
7264
|
if (nativeChild) {
|
|
6786
7265
|
cropJpegBuf = nativeChild.jpeg;
|
|
@@ -6803,7 +7282,8 @@ var PipelineExecutor = class {
|
|
|
6803
7282
|
plateLongSidePx: Math.round(plateLongSidePx),
|
|
6804
7283
|
plateShortSidePx: Math.round(Math.min(cropW, cropH)),
|
|
6805
7284
|
minPlateLongSidePx: cropPolicy.minLongSidePx,
|
|
6806
|
-
marginFraction
|
|
7285
|
+
marginFraction,
|
|
7286
|
+
boxFrom: parentDetection.rootSupplied !== void 0 ? "root-model" : "step",
|
|
6807
7287
|
source: nativeChild ? "native" : "detection-tile"
|
|
6808
7288
|
}
|
|
6809
7289
|
});
|
|
@@ -7001,9 +7481,18 @@ function buildNode(step, getEngine, resolveModel, onMissingVariant, resolved) {
|
|
|
7001
7481
|
children,
|
|
7002
7482
|
...Object.keys(mergedSettings).length > 0 ? { settings: mergedSettings } : {},
|
|
7003
7483
|
...effectiveClassMap !== void 0 ? { effectiveClassMap } : {},
|
|
7004
|
-
...
|
|
7484
|
+
...rootSuppliesField(definition, modelEntry)
|
|
7005
7485
|
};
|
|
7006
7486
|
}
|
|
7487
|
+
/** A root step's supplies, omitted when it declares none (and for every child step). */
|
|
7488
|
+
function rootSuppliesField(definition, modelEntry) {
|
|
7489
|
+
if (definition.inputClasses !== null) return {};
|
|
7490
|
+
const supplies = rootSuppliesOf(modelEntry, {
|
|
7491
|
+
labels: definition.labels ?? [],
|
|
7492
|
+
...definition.classMap !== void 0 ? { classMap: definition.classMap } : {}
|
|
7493
|
+
});
|
|
7494
|
+
return supplies.length > 0 ? { rootSupplies: supplies } : {};
|
|
7495
|
+
}
|
|
7007
7496
|
/**
|
|
7008
7497
|
* Walk the step's declared `getConfigSchema()` and collect every field
|
|
7009
7498
|
* with a primitive `default` value into a plain record. Group fields
|
|
@@ -9149,7 +9638,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9149
9638
|
execution = await executor.run(effectiveTree, rootInput, jpegProvider, imageWidth, imageHeight, effectiveDeviceId, {
|
|
9150
9639
|
traceVerbosity: isRuntime ? this.eventBus ? "summary" : "off" : "full",
|
|
9151
9640
|
plane: input.plane,
|
|
9152
|
-
occasion: input.replay === true ? "replay" : "live"
|
|
9641
|
+
occasion: input.replay === true ? "replay" : "live",
|
|
9642
|
+
...input.suppliedRoot !== void 0 ? { suppliedRoot: input.suppliedRoot } : {}
|
|
9153
9643
|
}, nativeCropProvider, cropZoneBbox, rootInputViewProvider);
|
|
9154
9644
|
executionSucceeded = true;
|
|
9155
9645
|
} finally {
|
|
@@ -9425,6 +9915,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9425
9915
|
*/
|
|
9426
9916
|
async loadDispatchModels(steps, factory, format, context, policy) {
|
|
9427
9917
|
const failures = [];
|
|
9918
|
+
if (factory !== null) await this.noteCoralPlacement(steps, factory, format, context);
|
|
9428
9919
|
const walk = async (nodes, root) => {
|
|
9429
9920
|
for (const step of nodes) {
|
|
9430
9921
|
if (!step.enabled || step.slot === "audio-classifier") continue;
|
|
@@ -9449,6 +9940,69 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9449
9940
|
await walk(steps, true);
|
|
9450
9941
|
return failures;
|
|
9451
9942
|
}
|
|
9943
|
+
/**
|
|
9944
|
+
* Tell a Coral pool which models this dispatch places on it, and act on a
|
|
9945
|
+
* change of the co-compiled set it loads from (D724): download the set's
|
|
9946
|
+
* files for the models already resident and reload them — queued behind the
|
|
9947
|
+
* pool's other loads, so nothing loads from the old file meanwhile. Logged
|
|
9948
|
+
* once per change, tagged with the camera whose dispatch moved it.
|
|
9949
|
+
*/
|
|
9950
|
+
async noteCoralPlacement(steps, factory, format, context) {
|
|
9951
|
+
const change = factory.notePlacement(placedModelsOf(steps));
|
|
9952
|
+
if (change.kind === "unchanged") return;
|
|
9953
|
+
const deviceKey = context.deviceKey ?? deviceKeyOf(this.currentEngine);
|
|
9954
|
+
const tags = context.deviceId !== void 0 ? { tags: { deviceId: context.deviceId } } : {};
|
|
9955
|
+
const placed = change.placed.map((p) => `${p.stepId}/${p.modelId}`);
|
|
9956
|
+
if (change.to.kind === "set") this.log.info("Coral pool loads a co-compiled set — the models placed on it share the parameter cache", {
|
|
9957
|
+
...tags,
|
|
9958
|
+
meta: {
|
|
9959
|
+
deviceKey,
|
|
9960
|
+
set: change.to.set.id,
|
|
9961
|
+
placed,
|
|
9962
|
+
fromSet: change.to.covered.map((p) => `${p.stepId}/${p.modelId}`),
|
|
9963
|
+
separatelyCompiled: change.to.uncovered.map((p) => `${p.stepId}/${p.modelId}`),
|
|
9964
|
+
previous: change.from.kind === "set" ? change.from.set.id : null
|
|
9965
|
+
}
|
|
9966
|
+
});
|
|
9967
|
+
else if (change.to.reason === "no-set-covers-two") this.log.warn("Coral pool has no co-compiled set for the models placed on it — each is compiled alone, so they evict each other from the parameter cache on every alternation", {
|
|
9968
|
+
...tags,
|
|
9969
|
+
meta: {
|
|
9970
|
+
deviceKey,
|
|
9971
|
+
placed,
|
|
9972
|
+
previous: change.from.kind === "set" ? change.from.set.id : null,
|
|
9973
|
+
hint: "keep the extra step off this Coral with its jumpDeviceKey, or place a combination a published set covers"
|
|
9974
|
+
}
|
|
9975
|
+
});
|
|
9976
|
+
const pool = factory;
|
|
9977
|
+
const previous = this.modelLoadTail.get(pool) ?? Promise.resolve();
|
|
9978
|
+
const tail = (async () => {
|
|
9979
|
+
await previous;
|
|
9980
|
+
for (const loaded of factory.listLoaded()) {
|
|
9981
|
+
const resolved = await this.resolveModelEntry(loaded.stepId, loaded.modelId);
|
|
9982
|
+
if (resolved === void 0) continue;
|
|
9983
|
+
const entry = factory.entryForPool(loaded.stepId, resolved);
|
|
9984
|
+
if (!isModelDownloaded(this.modelsDir, entry, format)) await this.downloadWithRetry(entry, format, 3);
|
|
9985
|
+
}
|
|
9986
|
+
const reloaded = await factory.reloadChangedModelFiles();
|
|
9987
|
+
if (reloaded.length > 0) this.log.info("Coral pool reloaded its resident models from the new set", {
|
|
9988
|
+
...tags,
|
|
9989
|
+
meta: {
|
|
9990
|
+
deviceKey,
|
|
9991
|
+
reloaded: reloaded.map((r) => `${r.stepId}/${r.modelId}`)
|
|
9992
|
+
}
|
|
9993
|
+
});
|
|
9994
|
+
})().catch((err) => {
|
|
9995
|
+
this.log.error("Coral pool could not move its resident models to the new set — they keep serving from the old files", {
|
|
9996
|
+
...tags,
|
|
9997
|
+
meta: {
|
|
9998
|
+
deviceKey,
|
|
9999
|
+
error: errMsg(err)
|
|
10000
|
+
}
|
|
10001
|
+
});
|
|
10002
|
+
});
|
|
10003
|
+
this.modelLoadTail.set(pool, tail);
|
|
10004
|
+
await tail;
|
|
10005
|
+
}
|
|
9452
10006
|
/** Ensure all models needed by steps are downloaded and loaded in the engine pool.
|
|
9453
10007
|
* `factory`/`format` default to the node's engine; a per-device dispatch passes
|
|
9454
10008
|
* that device's factory + format (Phase 2 multi-device). `context` names the
|
|
@@ -9546,7 +10100,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9546
10100
|
const refusal = this.loadRefusal(pool, deviceKey, models);
|
|
9547
10101
|
if (refusal !== null) throw refusal;
|
|
9548
10102
|
try {
|
|
9549
|
-
await this.downloadNeededModels(needed, format);
|
|
10103
|
+
await this.downloadNeededModels(needed, format, factory);
|
|
9550
10104
|
this.log.info("Loading additional models for benchmark", { meta: {
|
|
9551
10105
|
count: needed.length,
|
|
9552
10106
|
models,
|
|
@@ -9610,16 +10164,21 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9610
10164
|
});
|
|
9611
10165
|
}
|
|
9612
10166
|
/** Download any model of `needed` missing in `format` (the device's). */
|
|
9613
|
-
async downloadNeededModels(needed, format) {
|
|
10167
|
+
async downloadNeededModels(needed, format, factory = null) {
|
|
9614
10168
|
for (const step of needed) try {
|
|
9615
|
-
await this.ensureStepModelArtifact(step, format);
|
|
10169
|
+
await this.ensureStepModelArtifact(step, format, factory);
|
|
9616
10170
|
} catch (err) {
|
|
9617
10171
|
throw new StepModelArtifactError(step.addonId, step.modelId, err);
|
|
9618
10172
|
}
|
|
9619
10173
|
}
|
|
9620
|
-
/**
|
|
9621
|
-
|
|
9622
|
-
|
|
10174
|
+
/**
|
|
10175
|
+
* Make one step's model artifact present on disk for `format`, or say why it
|
|
10176
|
+
* cannot be. The entry is the one `factory`'s pool will LOAD — on a Coral
|
|
10177
|
+
* pool, a co-compiled set's file when the set stands for the model (D724).
|
|
10178
|
+
*/
|
|
10179
|
+
async ensureStepModelArtifact(step, format, factory = null) {
|
|
10180
|
+
const resolved = await this.resolveModelEntry(step.addonId, step.modelId);
|
|
10181
|
+
const modelEntry = resolved && factory !== null ? factory.entryForPool(step.addonId, resolved) : resolved;
|
|
9623
10182
|
if (modelEntry && !isModelDownloaded(this.modelsDir, modelEntry, format)) {
|
|
9624
10183
|
if (modelEntry.formats[format] === void 0) throw new Error(`Model "${modelEntry.id}" has no ${format} format build (available: ${Object.keys(modelEntry.formats).join(", ") || "none"}) — not retrying a permanent format mismatch`);
|
|
9625
10184
|
if (modelEntry.formats[format]?.url.startsWith("camstack-local://") === true) throw new Error(`Custom model "${modelEntry.id}" (${format}) is not present in this node's models directory — distribute it to this node from Model Studio before selecting it here`);
|
|
@@ -10641,7 +11200,7 @@ function buildSchemaSlots(formats, modelsDir, customByStep) {
|
|
|
10641
11200
|
...m.legacy !== void 0 ? { legacy: m.legacy } : {},
|
|
10642
11201
|
provider: m.provider ?? "camstack",
|
|
10643
11202
|
defaultConfidence: resolveModelDefaultConfidence(step, m),
|
|
10644
|
-
...
|
|
11203
|
+
...modelSuppliesField(step, m)
|
|
10645
11204
|
})),
|
|
10646
11205
|
defaultModelId: step.defaultModelId,
|
|
10647
11206
|
...step.defaultModelIdByFormat ? { defaultModelIdByFormat: { ...step.defaultModelIdByFormat } } : {},
|
|
@@ -10777,6 +11336,15 @@ function isDetailPlaneStep(addonId) {
|
|
|
10777
11336
|
return false;
|
|
10778
11337
|
}
|
|
10779
11338
|
}
|
|
11339
|
+
/** A root model's supplies for the schema, omitted when it supplies nothing. */
|
|
11340
|
+
function modelSuppliesField(step, model) {
|
|
11341
|
+
if (step.inputClasses !== null) return {};
|
|
11342
|
+
const supplies = rootSuppliesOf(model, {
|
|
11343
|
+
labels: step.labels ?? [],
|
|
11344
|
+
...step.classMap !== void 0 ? { classMap: step.classMap } : {}
|
|
11345
|
+
});
|
|
11346
|
+
return supplies.length > 0 ? { suppliesSteps: supplies } : {};
|
|
11347
|
+
}
|
|
10780
11348
|
/**
|
|
10781
11349
|
* What a dispatch runs after its per-step loads (D657).
|
|
10782
11350
|
*
|
|
@@ -11025,7 +11593,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
11025
11593
|
type: "select",
|
|
11026
11594
|
key: "batchMode",
|
|
11027
11595
|
label: "Batch dispatch mode",
|
|
11028
|
-
description: "How the Python pool GROUPS predict requests — it never changes a model's compiled batch SHAPE, which is derived from (engine, device) at load and is not configurable
|
|
11596
|
+
description: "How the Python pool GROUPS predict requests — it never changes a model's compiled batch SHAPE, which is derived from (engine, device) at load and is not configurable. `none` = one call per frame. `window` = per-model accumulator coalesces concurrent calls within Window (ms). Auto uses the matrix-sweep default for the current backend. Applies to THIS node only.",
|
|
11029
11597
|
options: [
|
|
11030
11598
|
{
|
|
11031
11599
|
value: "",
|
|
@@ -11069,7 +11637,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
11069
11637
|
type: "slider",
|
|
11070
11638
|
key: "maxBatchSize",
|
|
11071
11639
|
label: "Max batch size",
|
|
11072
|
-
description: "Cap on how many requests are GROUPED into one batched predict call. Not the model's compiled batch shape — that is derived from (engine, device) at load and is not configurable
|
|
11640
|
+
description: "Cap on how many requests are GROUPED into one batched predict call. Not the model's compiled batch shape — that is derived from (engine, device) at load and is not configurable; a stacked tensor is only ever built for an export that declares a flexible batch axis itself. Auto = backend default. Applies to THIS node only.",
|
|
11073
11641
|
min: 1,
|
|
11074
11642
|
max: 16,
|
|
11075
11643
|
step: 1,
|