@camstack/addon-pipeline 1.2.609 → 1.2.611
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 +3 -1
- package/dist/audio-analyzer/index.js +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/{custom-model-catalog-Ci_-yU1S.js → custom-model-catalog-CPStyC-O.js} +1 -1
- package/dist/{custom-model-catalog-D4nyEGYU.mjs → custom-model-catalog-Cw5freGh.mjs} +1 -1
- package/dist/detection-pipeline/index.js +1 -1
- package/dist/detection-pipeline/index.mjs +1 -1
- package/dist/{model-catalogs-B4udr4Fp.mjs → model-catalogs-CPr2weQc.mjs} +80 -1
- package/dist/{model-catalogs-CqlA8ThV.js → model-catalogs-D5v2-Wue.js} +79 -0
- package/dist/pipeline-runner/index.js +1 -1
- package/dist/pipeline-runner/index.mjs +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DtSn_bza.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-8eIi2CRB.mjs} +3 -3
- package/dist/stream-broker/{hostInit-DTCC4mJ-.mjs → hostInit-BJKx9YvB.mjs} +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/python/edgetpu_device.py +32 -1
- package/python/inference_pool.py +4 -4
- package/python/postprocessors/test_yolov9_edgetpu_concat.py +145 -0
- package/python/postprocessors/yolo_split_dfl.py +58 -9
- package/python/requirements-edgetpu.txt +13 -10
- package/python/test_edgetpu_device.py +30 -1
package/THIRD_PARTY_MODELS.md
CHANGED
|
@@ -32,6 +32,7 @@ under [Non-commercial models](#non-commercial-models).
|
|
|
32
32
|
|
|
33
33
|
- © Ultralytics, AGPL-3.0. Corresponding source: the upstream Ultralytics checkpoint and build-camstack-models.py (with convert_lib.py) published at https://huggingface.co/camstack/camstack-models/tree/main/source. https://github.com/ultralytics/ultralytics
|
|
34
34
|
- Coral Edge TPU models and AIY Vision Birds V1 © Google LLC, Apache-2.0. https://github.com/google-coral/test_data
|
|
35
|
+
- YOLOv9-s EdgeTPU weights © 2025 Dan Brown (fine-tuned to 17 COCO classes, INT8, compiled for the Edge TPU; https://github.com/dbro/frigate-detector-edgetpu-yolo9, repository MIT). Derived from the YOLOv9 code and yolov9-s checkpoint of Chien-Yao Wang, I-Hau Yeh, Hong-Yuan Mark Liao, GPL-3.0 (https://github.com/WongKinYiu/yolov9). Corresponding source: the fine-tuning checkpoint (yolov9-s-relu6-best.pt), the fine-tuning and conversion notebooks and the publisher LICENSE, mirrored at https://huggingface.co/camstack/camstack-models/tree/main/source/yolov9-edgetpu-dbro. https://github.com/dbro/frigate-detector-edgetpu-yolo9
|
|
35
36
|
- © Ultralytics, AGPL-3.0 (Ultralytics YOLOv9 weights). YOLOv9 architecture: Chien-Yao Wang, I-Hau Yeh, Hong-Yuan Mark Liao, GPL-3.0 (https://github.com/WongKinYiu/yolov9). Corresponding source: the upstream Ultralytics checkpoint and build-camstack-models.py (with convert_lib.py) published at https://huggingface.co/camstack/camstack-models/tree/main/source. https://github.com/ultralytics/ultralytics
|
|
36
37
|
- RF-DETR © Roboflow, Apache-2.0. https://github.com/roboflow/rf-detr
|
|
37
38
|
- YuNet weights Copyright (c) 2020 Shiqi Yu, MIT. Training code: ShiqiYu/libfacedetection.train, BSD-3-Clause. https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet
|
|
@@ -48,7 +49,7 @@ under [Non-commercial models](#non-commercial-models).
|
|
|
48
49
|
|
|
49
50
|
Training data under attribution terms:
|
|
50
51
|
|
|
51
|
-
- **COCO 2017** (training data of `yolo26n-seg` and
|
|
52
|
+
- **COCO 2017** (training data of `yolo26n-seg` and 52 more): annotations CC-BY-4.0; images under their Flickr owners' terms. https://cocodataset.org/#termsofuse
|
|
52
53
|
- **WIDER FACE** (training data of `yunet-2023mar`): research-oriented terms; images CC BY-NC-ND. http://shuoyang1213.me/WIDERFACE/
|
|
53
54
|
- **Open Images V7** (training data of `animal-classifier`): annotations CC-BY-4.0 (Google LLC); images listed as CC-BY-2.0, no warranty. https://storage.googleapis.com/openimages/web/factsfigures_v7.html
|
|
54
55
|
- **DrBimmer/vehicle-classification** (training data of `vehicle-type-v1`): CC-BY-NC-4.0 — treated as binding the trained weights (conservative reading). https://huggingface.co/datasets/DrBimmer/vehicle-classification
|
|
@@ -123,6 +124,7 @@ entry kept resolvable for a saved selection but no longer offered.
|
|
|
123
124
|
| `yolo26n-seg`, `yolo26s-seg`, `yolo26m-seg` | [Ultralytics](https://github.com/ultralytics/ultralytics) | AGPL-3.0 / AGPL-3.0 (`AGPL-3.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | exported to ONNX, OpenVINO IR and CoreML | mirror `segmentation/yolo26-seg` |
|
|
124
125
|
| `yolo26n` (default), `yolo26s`, `yolo26m`, `yolo26l`, `yolo26x` (hidden), `yolo26n-fp16` (hidden), `yolo26n-int8`, `yolo26s-fp16` (hidden), `yolo26s-int8`, `yolo26m-fp16` (hidden), `yolo26m-int8`, `yolo26l-fp16` (hidden), `yolo26l-int8`, `yolo26n-320`, `yolo26n-320-int8`, `yolo26n-256`, `yolo26n-256-int8`, `yolo26s-320`, `yolo26s-320-int8`, `yolo26s-256`, `yolo26s-256-int8`, `yolo26m-320`, `yolo26m-320-int8`, `yolo26m-256`, `yolo26m-256-int8`, `yolo26l-320`, `yolo26l-320-int8`, `yolo26l-256`, `yolo26l-256-int8`, `yolo26x-fp16` (hidden), `yolo26x-int8` (hidden) | [Ultralytics](https://github.com/ultralytics/ultralytics) | AGPL-3.0 / AGPL-3.0 (`AGPL-3.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | exported to ONNX, OpenVINO IR and CoreML (scripts/build-camstack-models.py); OpenVINO FP16 export; exported to OpenVINO IR, then OpenVINO NNCF INT8 post-training quantisation; re-exported at 320×320 to ONNX, OpenVINO IR and CoreML; re-exported at 320×320 to OpenVINO IR; OpenVINO NNCF INT8 post-training quantisation; re-exported at 256×256 to ONNX, OpenVINO IR and CoreML; re-exported at 256×256 to OpenVINO IR; OpenVINO NNCF INT8 post-training quantisation | mirror `objectDetection/yolo26` |
|
|
125
126
|
| `ssd-mobilenet-v2-coco-edgetpu` (default) | [Google Coral test_data](https://github.com/google-coral/test_data) | Apache-2.0 / Apache-2.0 (`Apache-2.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | none | https://github.com/google-coral/test_data |
|
|
127
|
+
| `yolov9s-320-edgetpu`, `yolov9s-512-edgetpu` | [YOLOv9-s EdgeTPU (Dan Brown)](https://github.com/dbro/frigate-detector-edgetpu-yolo9) | GPL-3.0 / GPL-3.0 (`GPL-3.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | none: the publisher release files are mirrored byte-identical | mirror `objectDetection/yolov9` |
|
|
126
128
|
| `yolov9t-320`, `yolov9t-320-int8`, `yolov9t-640`, `yolov9t-640-int8`, `yolov9s-320`, `yolov9s-320-int8`, `yolov9s-640`, `yolov9s-640-int8`, `yolov9m-320`, `yolov9m-320-int8`, `yolov9m-640`, `yolov9m-640-int8`, `yolov9c-320`, `yolov9c-320-int8`, `yolov9c-640`, `yolov9c-640-int8` | [Ultralytics](https://github.com/ultralytics/ultralytics) | AGPL-3.0 / AGPL-3.0 (`AGPL-3.0.txt`) | COCO 2017: annotations CC-BY-4.0; images under their Flickr owners' terms | exported at 320×320 to ONNX, OpenVINO IR FP16 and CoreML; exported at 320×320 to OpenVINO IR; OpenVINO NNCF INT8 post-training quantisation; exported at 640×640 to ONNX, OpenVINO IR FP16 and CoreML; exported at 640×640 to OpenVINO IR; OpenVINO NNCF INT8 post-training quantisation | mirror `objectDetection/yolov9` |
|
|
127
129
|
|
|
128
130
|
### Package detection
|
|
@@ -4,7 +4,7 @@ Object.defineProperties(exports, {
|
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
6
|
const require_dist = require("../dist-DkfPNxQw.js");
|
|
7
|
-
const require_model_catalogs = require("../model-catalogs-
|
|
7
|
+
const require_model_catalogs = require("../model-catalogs-D5v2-Wue.js");
|
|
8
8
|
const require_process_memory = require("../process-memory-BF_zHntD.js");
|
|
9
9
|
let node_fs = require("node:fs");
|
|
10
10
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
2
|
import { $ as audioAnalyzerCapability, F as PoolMemoryWatchdog, Kn as hydrateSchema, Mn as errMsg, Pn as BaseAddon, Q as audioAnalysisCapability, Qn as nodePin, Rt as mapAudioLabelToMacro, Wn as expandAudioChunkToF32le, h as DEFAULT_AUDIO_ANALYZER_CONFIG, hr as EventCategory, ln as resolvePoolMemoryPolicy, r as AUDIO_BACKEND_CHOICES, zn as audioChunkBytesPerSample } from "../dist-0D-dLBnm.mjs";
|
|
3
|
-
import { n as AUDIO_CLASSIFIER_MODELS } from "../model-catalogs-
|
|
3
|
+
import { n as AUDIO_CLASSIFIER_MODELS } from "../model-catalogs-CPr2weQc.mjs";
|
|
4
4
|
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-B5YqxUdg.mjs";
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path$1 from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_dist = require("./dist-DkfPNxQw.js");
|
|
2
|
-
const require_model_catalogs = require("./model-catalogs-
|
|
2
|
+
const require_model_catalogs = require("./model-catalogs-D5v2-Wue.js");
|
|
3
3
|
const require_worker_protocol = require("./worker-protocol-C-G8qmye.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
//#region src/detection-pipeline/registry/effective-confidence.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Mt as inferModelProvider, b as DEVICE_BACKEND_TO_FORMAT, et as audioKindId, f as COCO_80_LABELS, hn as stepModelRunsOnFormat, i as AUDIO_MACRO_LABELS, k as LABEL_SUPPLY_VOCABULARIES, p as COCO_TO_MACRO } from "./dist-0D-dLBnm.mjs";
|
|
2
|
-
import { a as FACE_DETECTION_MODELS, c as OBJECT_DETECTION_MODELS, d as PLATE_OCR_MODELS, f as SEGMENTATION_REFINER_MODELS, i as CLIP_EMBEDDING_MODELS, l as PACKAGE_DETECTION_MODELS, n as AUDIO_CLASSIFIER_MODELS, o as FACE_EMBEDDING_MODELS, p as VEHICLE_CLASSIFIER_MODELS, r as BIRD_CLASSIFIER_MODELS, s as INSTANCE_SEGMENTATION_MODELS, t as ANIMAL_CLASSIFIER_MODELS, u as PLATE_DETECTION_MODELS } from "./model-catalogs-
|
|
2
|
+
import { a as FACE_DETECTION_MODELS, c as OBJECT_DETECTION_MODELS, d as PLATE_OCR_MODELS, f as SEGMENTATION_REFINER_MODELS, i as CLIP_EMBEDDING_MODELS, l as PACKAGE_DETECTION_MODELS, n as AUDIO_CLASSIFIER_MODELS, o as FACE_EMBEDDING_MODELS, p as VEHICLE_CLASSIFIER_MODELS, r as BIRD_CLASSIFIER_MODELS, s as INSTANCE_SEGMENTATION_MODELS, t as ANIMAL_CLASSIFIER_MODELS, u as PLATE_DETECTION_MODELS } from "./model-catalogs-CPr2weQc.mjs";
|
|
3
3
|
import { t as isWorkerReply } from "./worker-protocol-D_NzPcnh.mjs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
//#region src/detection-pipeline/registry/effective-confidence.ts
|
|
@@ -5,7 +5,7 @@ Object.defineProperties(exports, {
|
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
6
|
const require_dist = require("../dist-DkfPNxQw.js");
|
|
7
7
|
const require_node = require("../node-CpUZKfZC.js");
|
|
8
|
-
const require_custom_model_catalog = require("../custom-model-catalog-
|
|
8
|
+
const require_custom_model_catalog = require("../custom-model-catalog-CPStyC-O.js");
|
|
9
9
|
const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
|
|
10
10
|
const require_process_memory = require("../process-memory-BF_zHntD.js");
|
|
11
11
|
let node_os = require("node:os");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
2
|
import { Bn as createEvent, Ct as evaluateZoneRules, F as PoolMemoryWatchdog, Gn as hfModelUrl, It as loadContributionCapability, Jt as pickClusterStepSettings, Kn as hydrateSchema, Kt as overlayClusterStepSettings, Mn as errMsg$1, O as LABEL_SUPPLY_MACRO, Pn as BaseAddon, Pt as linkedLabelStepId, Qn as nodePin, St as enumerateInferenceDevices, Wt as modelBindingKey, X as applyModelBoundSettings, Zt as pipelineExecutorCapability, _t as detectionPipelineCapability, bn as supportedRuntimes$1, ct as containingParentIndex, d as CLUSTER_MODEL_SCOPED_STEPS, fn as rootSuppliesOf, ft as defaultDeviceFor$1, g as DEFAULT_CLUSTER_STEP_SETTINGS, hn as stepModelRunsOnFormat, hr as EventCategory, in as resolveClusterStepModelId, ir as array, ln as resolvePoolMemoryPolicy, mr as union, n as APPLE_SA_TO_MACRO, pn as runtimeDevices$1, pr as string, q as YAMNET_TO_MACRO, st as collectModelBoundKeys, tr as sleep, ur as object, yt as edgetpuDelegateDevice } from "../dist-0D-dLBnm.mjs";
|
|
3
3
|
import { c as readProcessCost } from "../node-CXQrV8Tv.mjs";
|
|
4
|
-
import { C as resolveModelDefaultConfidence, S as landmarkPrecisionVerdict, _ as ALL_STEPS, b as getStepDefinition, f as localFrameRegistry, g as ALL_PIPELINE_STEPS, l as resolveDefaultDetectionModel, n as mergeCustomModels, o as deviceKeyOf, r as buildSubtreeCanRun, s as resolveDeviceEngine, t as CustomModelCatalog, u as macroGateVerdict, v as getDefaultModelForFormat, x as resolveModelForFormat, y as getStep } from "../custom-model-catalog-
|
|
4
|
+
import { C as resolveModelDefaultConfidence, S as landmarkPrecisionVerdict, _ as ALL_STEPS, b as getStepDefinition, f as localFrameRegistry, g as ALL_PIPELINE_STEPS, l as resolveDefaultDetectionModel, n as mergeCustomModels, o as deviceKeyOf, r as buildSubtreeCanRun, s as resolveDeviceEngine, t as CustomModelCatalog, u as macroGateVerdict, v as getDefaultModelForFormat, x as resolveModelForFormat, y as getStep } from "../custom-model-catalog-Cw5freGh.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
|
|
6
6
|
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-B5YqxUdg.mjs";
|
|
7
7
|
import * as os$1 from "node:os";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gn as hfModelUrl } from "./dist-0D-dLBnm.mjs";
|
|
1
|
+
import { Gn as hfModelUrl, f as COCO_80_LABELS } from "./dist-0D-dLBnm.mjs";
|
|
2
2
|
//#region src/detection-pipeline/registry/model-licenses.ts
|
|
3
3
|
var COCO_DATA = {
|
|
4
4
|
name: "COCO 2017",
|
|
@@ -27,6 +27,30 @@ var ULTRALYTICS_YOLOV9_AGPL = {
|
|
|
27
27
|
...ULTRALYTICS_AGPL,
|
|
28
28
|
attribution: "© Ultralytics, AGPL-3.0 (Ultralytics YOLOv9 weights). YOLOv9 architecture: Chien-Yao Wang, I-Hau Yeh, Hong-Yuan Mark Liao, GPL-3.0 (https://github.com/WongKinYiu/yolov9). Corresponding source: the upstream Ultralytics checkpoint and build-camstack-models.py (with convert_lib.py) published at https://huggingface.co/camstack/camstack-models/tree/main/source."
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* YOLOv9-s fine-tuned to 17 COCO classes and compiled for the Coral by Dan
|
|
32
|
+
* Brown (verified 2026-10-22 from the publisher's repository): the training
|
|
33
|
+
* notebook clones WongKinYiu/yolov9 (GPL-3.0, licence API) and starts from its
|
|
34
|
+
* `yolov9-s-converted.pt` release; the data is COCO 2017 filtered to 17
|
|
35
|
+
* classes. The repository's LICENSE.md is MIT and covers the publisher's own
|
|
36
|
+
* plugin and notebooks; no separate licence is stated for the release weights,
|
|
37
|
+
* so the record carries the terms of the GPL-3.0 checkpoint they derive from.
|
|
38
|
+
* CamStack does not alter or re-host the files: a node downloads them from the
|
|
39
|
+
* publisher's GitHub release, pinned by sha256 (the format's `revision`).
|
|
40
|
+
*/
|
|
41
|
+
var DBRO_YOLOV9_EDGETPU_GPL = {
|
|
42
|
+
weights: "GPL-3.0",
|
|
43
|
+
code: "GPL-3.0",
|
|
44
|
+
data: COCO_DATA,
|
|
45
|
+
upstream: {
|
|
46
|
+
name: "YOLOv9-s EdgeTPU (Dan Brown)",
|
|
47
|
+
url: "https://github.com/dbro/frigate-detector-edgetpu-yolo9"
|
|
48
|
+
},
|
|
49
|
+
url: "https://github.com/WongKinYiu/yolov9/blob/main/LICENSE.md",
|
|
50
|
+
attribution: "YOLOv9-s EdgeTPU weights © 2025 Dan Brown (fine-tuned to 17 COCO classes, INT8, compiled for the Edge TPU; https://github.com/dbro/frigate-detector-edgetpu-yolo9, repository MIT). Derived from the YOLOv9 code and yolov9-s checkpoint of Chien-Yao Wang, I-Hau Yeh, Hong-Yuan Mark Liao, GPL-3.0 (https://github.com/WongKinYiu/yolov9). Corresponding source: the fine-tuning checkpoint (yolov9-s-relu6-best.pt), the fine-tuning and conversion notebooks and the publisher LICENSE, mirrored at https://huggingface.co/camstack/camstack-models/tree/main/source/yolov9-edgetpu-dbro.",
|
|
51
|
+
modifications: "none: the publisher release files are mirrored byte-identical",
|
|
52
|
+
hosting: "camstack-hf"
|
|
53
|
+
};
|
|
30
54
|
/** The CamStack Coral COCO detector (SSD MobileNet V2). */
|
|
31
55
|
var CORAL_COCO_APACHE = {
|
|
32
56
|
weights: "Apache-2.0",
|
|
@@ -370,6 +394,60 @@ var yolov9Reduced = (tier, res, sizes) => {
|
|
|
370
394
|
formats: { openvino: ovFormat(hf(`objectDetection/yolov9/openvino/camstack-yolov9${tier}-${res}-int8.xml`), sizes.ovInt8) }
|
|
371
395
|
}];
|
|
372
396
|
};
|
|
397
|
+
var COCO17_LABELS = [
|
|
398
|
+
"person",
|
|
399
|
+
"bicycle",
|
|
400
|
+
"car",
|
|
401
|
+
"motorcycle",
|
|
402
|
+
"airplane",
|
|
403
|
+
"bus",
|
|
404
|
+
"train",
|
|
405
|
+
"truck",
|
|
406
|
+
"boat",
|
|
407
|
+
"bird",
|
|
408
|
+
"cat",
|
|
409
|
+
"dog",
|
|
410
|
+
"horse",
|
|
411
|
+
"sheep",
|
|
412
|
+
"cow",
|
|
413
|
+
"elephant",
|
|
414
|
+
"bear"
|
|
415
|
+
].map((id) => {
|
|
416
|
+
const label = COCO_80_LABELS.find((l) => l.id === id);
|
|
417
|
+
if (label === void 0) throw new Error(`COCO17 label "${id}" is not a COCO-80 id`);
|
|
418
|
+
return label;
|
|
419
|
+
});
|
|
420
|
+
/**
|
|
421
|
+
* YOLOv9-s compiled for the Coral Edge TPU (community build, record
|
|
422
|
+
* `DBRO_YOLOV9_EDGETPU_GPL`): INT8, uint8 input, int8 class logits `[1, N, 17]`
|
|
423
|
+
* + DFL boxes `[1, N, 64]` with the strides 8/16/32 concatenated (the v1.5
|
|
424
|
+
* build adds a `[1, N, 1]` max-score tensor) — decoded by `yolo-split-dfl`.
|
|
425
|
+
* Mirrored unchanged on our Hugging Face repo from the publisher's release
|
|
426
|
+
* (`release`); the revision comes from the mirror's table (D744), and the
|
|
427
|
+
* corresponding source sits in
|
|
428
|
+
* `source/yolov9-edgetpu-dbro/`. Selectable only: the tflite default stays the Coral SSD, and
|
|
429
|
+
* no hardware rule names these (D39). Floors are the stock defaults until
|
|
430
|
+
* measured.
|
|
431
|
+
*/
|
|
432
|
+
var yolov9EdgeTpuEntry = (res, release, file, sizeMB, note) => ({
|
|
433
|
+
id: `yolov9s-${res}-edgetpu`,
|
|
434
|
+
name: `YOLOv9 Small @${res} (Coral)`,
|
|
435
|
+
description: `YOLOv9-s @${res}×${res}, fine-tuned to 17 COCO classes, INT8 compiled for the Coral Edge TPU (community release ${release}). ${note}`,
|
|
436
|
+
inputSize: {
|
|
437
|
+
width: res,
|
|
438
|
+
height: res
|
|
439
|
+
},
|
|
440
|
+
labels: COCO17_LABELS,
|
|
441
|
+
preprocessMode: "letterbox",
|
|
442
|
+
postprocessor: "yolo-split-dfl",
|
|
443
|
+
license: DBRO_YOLOV9_EDGETPU_GPL,
|
|
444
|
+
formats: { tflite: {
|
|
445
|
+
url: hf(`objectDetection/yolov9/tflite/${file}`),
|
|
446
|
+
sizeMB,
|
|
447
|
+
runtimes: ["python"]
|
|
448
|
+
} }
|
|
449
|
+
});
|
|
450
|
+
var yolov9EdgeTpu = [yolov9EdgeTpuEntry(320, "v1.0", "yolov9-s-relu6-best_320_int8_edgetpu.tflite", 7.8, "Every op on the TPU; ~10 ms/frame as published."), yolov9EdgeTpuEntry(512, "v1.5", "yolov9-s-relu6-tpumax_512_int8_edgetpu.tflite", 7.8, "Finds smaller subjects than @320; ~21 ms/frame as published.")];
|
|
373
451
|
/**
|
|
374
452
|
* Reduced-input YOLO26 variants (2026-07-11). A lower square input (320 / 256)
|
|
375
453
|
* is the biggest, cheapest latency lever measured across every runtime — Mac
|
|
@@ -610,6 +688,7 @@ var OBJECT_DETECTION_MODEL_ENTRIES = [
|
|
|
610
688
|
runtimes: ["python"]
|
|
611
689
|
} }
|
|
612
690
|
},
|
|
691
|
+
...yolov9EdgeTpu,
|
|
613
692
|
{
|
|
614
693
|
id: "yolo26n-seg",
|
|
615
694
|
license: licensed(ULTRALYTICS_AGPL, { modifications: "exported to ONNX, OpenVINO IR and CoreML" }),
|
|
@@ -27,6 +27,30 @@ var ULTRALYTICS_YOLOV9_AGPL = {
|
|
|
27
27
|
...ULTRALYTICS_AGPL,
|
|
28
28
|
attribution: "© Ultralytics, AGPL-3.0 (Ultralytics YOLOv9 weights). YOLOv9 architecture: Chien-Yao Wang, I-Hau Yeh, Hong-Yuan Mark Liao, GPL-3.0 (https://github.com/WongKinYiu/yolov9). Corresponding source: the upstream Ultralytics checkpoint and build-camstack-models.py (with convert_lib.py) published at https://huggingface.co/camstack/camstack-models/tree/main/source."
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* YOLOv9-s fine-tuned to 17 COCO classes and compiled for the Coral by Dan
|
|
32
|
+
* Brown (verified 2026-10-22 from the publisher's repository): the training
|
|
33
|
+
* notebook clones WongKinYiu/yolov9 (GPL-3.0, licence API) and starts from its
|
|
34
|
+
* `yolov9-s-converted.pt` release; the data is COCO 2017 filtered to 17
|
|
35
|
+
* classes. The repository's LICENSE.md is MIT and covers the publisher's own
|
|
36
|
+
* plugin and notebooks; no separate licence is stated for the release weights,
|
|
37
|
+
* so the record carries the terms of the GPL-3.0 checkpoint they derive from.
|
|
38
|
+
* CamStack does not alter or re-host the files: a node downloads them from the
|
|
39
|
+
* publisher's GitHub release, pinned by sha256 (the format's `revision`).
|
|
40
|
+
*/
|
|
41
|
+
var DBRO_YOLOV9_EDGETPU_GPL = {
|
|
42
|
+
weights: "GPL-3.0",
|
|
43
|
+
code: "GPL-3.0",
|
|
44
|
+
data: COCO_DATA,
|
|
45
|
+
upstream: {
|
|
46
|
+
name: "YOLOv9-s EdgeTPU (Dan Brown)",
|
|
47
|
+
url: "https://github.com/dbro/frigate-detector-edgetpu-yolo9"
|
|
48
|
+
},
|
|
49
|
+
url: "https://github.com/WongKinYiu/yolov9/blob/main/LICENSE.md",
|
|
50
|
+
attribution: "YOLOv9-s EdgeTPU weights © 2025 Dan Brown (fine-tuned to 17 COCO classes, INT8, compiled for the Edge TPU; https://github.com/dbro/frigate-detector-edgetpu-yolo9, repository MIT). Derived from the YOLOv9 code and yolov9-s checkpoint of Chien-Yao Wang, I-Hau Yeh, Hong-Yuan Mark Liao, GPL-3.0 (https://github.com/WongKinYiu/yolov9). Corresponding source: the fine-tuning checkpoint (yolov9-s-relu6-best.pt), the fine-tuning and conversion notebooks and the publisher LICENSE, mirrored at https://huggingface.co/camstack/camstack-models/tree/main/source/yolov9-edgetpu-dbro.",
|
|
51
|
+
modifications: "none: the publisher release files are mirrored byte-identical",
|
|
52
|
+
hosting: "camstack-hf"
|
|
53
|
+
};
|
|
30
54
|
/** The CamStack Coral COCO detector (SSD MobileNet V2). */
|
|
31
55
|
var CORAL_COCO_APACHE = {
|
|
32
56
|
weights: "Apache-2.0",
|
|
@@ -370,6 +394,60 @@ var yolov9Reduced = (tier, res, sizes) => {
|
|
|
370
394
|
formats: { openvino: ovFormat(hf(`objectDetection/yolov9/openvino/camstack-yolov9${tier}-${res}-int8.xml`), sizes.ovInt8) }
|
|
371
395
|
}];
|
|
372
396
|
};
|
|
397
|
+
var COCO17_LABELS = [
|
|
398
|
+
"person",
|
|
399
|
+
"bicycle",
|
|
400
|
+
"car",
|
|
401
|
+
"motorcycle",
|
|
402
|
+
"airplane",
|
|
403
|
+
"bus",
|
|
404
|
+
"train",
|
|
405
|
+
"truck",
|
|
406
|
+
"boat",
|
|
407
|
+
"bird",
|
|
408
|
+
"cat",
|
|
409
|
+
"dog",
|
|
410
|
+
"horse",
|
|
411
|
+
"sheep",
|
|
412
|
+
"cow",
|
|
413
|
+
"elephant",
|
|
414
|
+
"bear"
|
|
415
|
+
].map((id) => {
|
|
416
|
+
const label = require_dist.COCO_80_LABELS.find((l) => l.id === id);
|
|
417
|
+
if (label === void 0) throw new Error(`COCO17 label "${id}" is not a COCO-80 id`);
|
|
418
|
+
return label;
|
|
419
|
+
});
|
|
420
|
+
/**
|
|
421
|
+
* YOLOv9-s compiled for the Coral Edge TPU (community build, record
|
|
422
|
+
* `DBRO_YOLOV9_EDGETPU_GPL`): INT8, uint8 input, int8 class logits `[1, N, 17]`
|
|
423
|
+
* + DFL boxes `[1, N, 64]` with the strides 8/16/32 concatenated (the v1.5
|
|
424
|
+
* build adds a `[1, N, 1]` max-score tensor) — decoded by `yolo-split-dfl`.
|
|
425
|
+
* Mirrored unchanged on our Hugging Face repo from the publisher's release
|
|
426
|
+
* (`release`); the revision comes from the mirror's table (D744), and the
|
|
427
|
+
* corresponding source sits in
|
|
428
|
+
* `source/yolov9-edgetpu-dbro/`. Selectable only: the tflite default stays the Coral SSD, and
|
|
429
|
+
* no hardware rule names these (D39). Floors are the stock defaults until
|
|
430
|
+
* measured.
|
|
431
|
+
*/
|
|
432
|
+
var yolov9EdgeTpuEntry = (res, release, file, sizeMB, note) => ({
|
|
433
|
+
id: `yolov9s-${res}-edgetpu`,
|
|
434
|
+
name: `YOLOv9 Small @${res} (Coral)`,
|
|
435
|
+
description: `YOLOv9-s @${res}×${res}, fine-tuned to 17 COCO classes, INT8 compiled for the Coral Edge TPU (community release ${release}). ${note}`,
|
|
436
|
+
inputSize: {
|
|
437
|
+
width: res,
|
|
438
|
+
height: res
|
|
439
|
+
},
|
|
440
|
+
labels: COCO17_LABELS,
|
|
441
|
+
preprocessMode: "letterbox",
|
|
442
|
+
postprocessor: "yolo-split-dfl",
|
|
443
|
+
license: DBRO_YOLOV9_EDGETPU_GPL,
|
|
444
|
+
formats: { tflite: {
|
|
445
|
+
url: hf(`objectDetection/yolov9/tflite/${file}`),
|
|
446
|
+
sizeMB,
|
|
447
|
+
runtimes: ["python"]
|
|
448
|
+
} }
|
|
449
|
+
});
|
|
450
|
+
var yolov9EdgeTpu = [yolov9EdgeTpuEntry(320, "v1.0", "yolov9-s-relu6-best_320_int8_edgetpu.tflite", 7.8, "Every op on the TPU; ~10 ms/frame as published."), yolov9EdgeTpuEntry(512, "v1.5", "yolov9-s-relu6-tpumax_512_int8_edgetpu.tflite", 7.8, "Finds smaller subjects than @320; ~21 ms/frame as published.")];
|
|
373
451
|
/**
|
|
374
452
|
* Reduced-input YOLO26 variants (2026-07-11). A lower square input (320 / 256)
|
|
375
453
|
* is the biggest, cheapest latency lever measured across every runtime — Mac
|
|
@@ -610,6 +688,7 @@ var OBJECT_DETECTION_MODEL_ENTRIES = [
|
|
|
610
688
|
runtimes: ["python"]
|
|
611
689
|
} }
|
|
612
690
|
},
|
|
691
|
+
...yolov9EdgeTpu,
|
|
613
692
|
{
|
|
614
693
|
id: "yolo26n-seg",
|
|
615
694
|
license: licensed(ULTRALYTICS_AGPL, { modifications: "exported to ONNX, OpenVINO IR and CoreML" }),
|
|
@@ -5,7 +5,7 @@ Object.defineProperties(exports, {
|
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
6
|
const require_dist = require("../dist-DkfPNxQw.js");
|
|
7
7
|
const require_node = require("../node-CpUZKfZC.js");
|
|
8
|
-
const require_custom_model_catalog = require("../custom-model-catalog-
|
|
8
|
+
const require_custom_model_catalog = require("../custom-model-catalog-CPStyC-O.js");
|
|
9
9
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
10
10
|
const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
|
|
11
11
|
const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { A as NativeLeaseAdmissionSchema, Bn as createEvent, Dt as failureContributionCapability, E as FailureCounters, Ft as linkedStepOf, Hn as defineCustomActions, It as loadContributionCapability, M as NodeBenchRunnerInputSchema, Mn as errMsg, P as PIPELINE_RAMP, Pn as BaseAddon, Qn as nodePin, Qt as pipelineRunnerCapability, Rn as MODEL_FORMATS, V as ROOT_SUPPLIABLE_STEP_IDS, Vn as customAction, Xt as pickNativeLeaseOverride, Y as allowedSuppliedEffect, Yt as pickDetailCropConvention, _ as DEFAULT_DETAIL_CROP_CONVENTION, a as BackgroundRunRegistry, ar as boolean, cn as resolveModelBoundSetting, cr as literal, dn as resolveRootSupply, fn as rootSuppliesOf, gt as deriveRootFaceRefineRect, hr as EventCategory, ir as array, j as NativeLeaseSettingsSchema, lr as number, m as CROP_SUPPLY_SOURCES, mt as deriveDetailCropRect, nt as buildBenchStepTree, pr as string, rr as _enum, rt as cameraOwnsMotionViaSensor, sr as lazy, ur as object, x as DEVICE_RAMP, y as DEFAULT_NATIVE_LEASE_SETTINGS } from "../dist-0D-dLBnm.mjs";
|
|
2
2
|
import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-CXQrV8Tv.mjs";
|
|
3
|
-
import { a as stepsForElectedPool, b as getStepDefinition, d as rootObjectGate, f as localFrameRegistry, h as resolveFrameViewGeometry, i as placeDetailStep, l as resolveDefaultDetectionModel, m as createSessionDecodeCoordinator, p as isSessionDecodeReadGone, s as resolveDeviceEngine, t as CustomModelCatalog, u as macroGateVerdict, y as getStep } from "../custom-model-catalog-
|
|
3
|
+
import { a as stepsForElectedPool, b as getStepDefinition, d as rootObjectGate, f as localFrameRegistry, h as resolveFrameViewGeometry, i as placeDetailStep, l as resolveDefaultDetectionModel, m as createSessionDecodeCoordinator, p as isSessionDecodeReadGone, s as resolveDeviceEngine, t as CustomModelCatalog, u as macroGateVerdict, y as getStep } from "../custom-model-catalog-Cw5freGh.mjs";
|
|
4
4
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
|
|
6
6
|
import { c as withDetectionIntent, o as preRollRequestOf, t as DETECTION_PRE_ROLL_REQUEST_MS } from "../restream-intent-DyGmLAU9.mjs";
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.143",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_stream_broker_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.393",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_stream_broker_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.310",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_stream_broker_widgets",
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.393",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.2.
|
|
48
|
+
version: "1.2.143",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.310",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-8eIi2CRB.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.611",
|
|
4
4
|
"description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
package/python/edgetpu_device.py
CHANGED
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import ctypes
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
|
-
from typing import Callable, Optional
|
|
13
|
+
from typing import Callable, Mapping, Optional
|
|
14
14
|
|
|
15
15
|
_DEVICE = re.compile(r"^(usb|pci)(:\d+)?$")
|
|
16
16
|
|
|
@@ -32,6 +32,37 @@ def edgetpu_delegate_options(device: Optional[str]) -> Optional[dict[str, str]]:
|
|
|
32
32
|
return {"device": dev}
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
# Where the hub image's installer has always put the delegate.
|
|
36
|
+
LEGACY_EDGETPU_LIB = "/data/deps/edgetpu/lib/libedgetpu.so.1"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def edgetpu_library_path(
|
|
40
|
+
env: "Mapping[str, str]",
|
|
41
|
+
platform: str,
|
|
42
|
+
exists: Optional[Callable[[str], bool]] = None,
|
|
43
|
+
) -> str:
|
|
44
|
+
"""The libedgetpu file this pool loads.
|
|
45
|
+
|
|
46
|
+
The same lookup as the TS probe (``chooseEdgetpuDelegate`` in
|
|
47
|
+
``@camstack/types`` ``deps/edgetpu-delegate.ts``): ``CAMSTACK_EDGETPU_LIB``
|
|
48
|
+
when set (followed even if missing — ``load_delegate`` then names it); the
|
|
49
|
+
copy the deps downloader staged under ``<CAMSTACK_DATA>/deps/edgetpu/lib/``
|
|
50
|
+
(``libedgetpu.1.dylib`` on macOS, ``libedgetpu.so.1`` elsewhere); else the
|
|
51
|
+
hub's historical path, which on the hub image is the same file.
|
|
52
|
+
"""
|
|
53
|
+
check = exists or os.path.exists
|
|
54
|
+
override = (env.get("CAMSTACK_EDGETPU_LIB") or "").strip()
|
|
55
|
+
if override:
|
|
56
|
+
return override
|
|
57
|
+
data_root = env.get("CAMSTACK_DATA") or env.get("CAMSTACK_DATA_DIR") or ""
|
|
58
|
+
if data_root:
|
|
59
|
+
name = "libedgetpu.1.dylib" if platform == "darwin" else "libedgetpu.so.1"
|
|
60
|
+
staged = os.path.join(data_root, "deps", "edgetpu", "lib", name)
|
|
61
|
+
if check(staged):
|
|
62
|
+
return staged
|
|
63
|
+
return LEGACY_EDGETPU_LIB
|
|
64
|
+
|
|
65
|
+
|
|
35
66
|
# The libedgetpu "std" delegate links libusb even when it drives a PCIe Coral.
|
|
36
67
|
# The installer (edgetpu-install.sh) drops this copy beside the delegate for an
|
|
37
68
|
# image that lacks the system package.
|
package/python/inference_pool.py
CHANGED
|
@@ -830,12 +830,12 @@ def _init_runtime(runtime: str, device: str = "") -> None:
|
|
|
830
830
|
# The delegate is created ONCE here and shared across all model slots (the
|
|
831
831
|
# USB device is a single hardware context). See docs: the delegate's TF
|
|
832
832
|
# version MUST match the interpreter — feranick libedgetpu tf2.17.1 pairs
|
|
833
|
-
# with ai-edge-litert 2.1.6.
|
|
833
|
+
# with ai-edge-litert 2.1.6. Staged by the deps downloader beside the
|
|
834
|
+
# edgetpu requirements (`requirements-edgetpu.txt`), or edgetpu-install.sh.
|
|
834
835
|
import threading
|
|
835
836
|
from ai_edge_litert.interpreter import Interpreter, load_delegate
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
)
|
|
837
|
+
from edgetpu_device import edgetpu_library_path
|
|
838
|
+
lib_path = edgetpu_library_path(os.environ, sys.platform)
|
|
839
839
|
# Multiple Corals (USB and/or PCIe) coexist and run concurrently, one
|
|
840
840
|
# pool each. libedgetpu addresses a unit by device string; the TS side
|
|
841
841
|
# sends the explicit ordinal ("usb:0", "usb:1", "pci:0", "pci:1") as the
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""YOLOv9 Edge TPU build with ALL strides concatenated (the community Frigate export).
|
|
2
|
+
|
|
3
|
+
The EdgeTPU-compiled YOLOv9 detectors the Frigate community publishes
|
|
4
|
+
(`dbro/frigate-detector-edgetpu-yolo9`, releases v1.0 and v1.5) were read with
|
|
5
|
+
a flatbuffer dump of the files themselves (2026-10-22):
|
|
6
|
+
|
|
7
|
+
yolov9-s-relu6-best_320_int8_edgetpu.tflite (v1.0)
|
|
8
|
+
in : serving_default_images:0 [1, 320, 320, 3] uint8 scale 1/255 zp 0
|
|
9
|
+
out: PartitionedCall:1 [1, 2100, 17] int8 scale 0.027195 zp 19 (class LOGITS)
|
|
10
|
+
PartitionedCall:0 [1, 2100, 64] int8 scale 0.079690 zp -45 (DFL l,t,r,b × 16)
|
|
11
|
+
yolov9-s-relu6-tpumax_512_int8_edgetpu.tflite (v1.5)
|
|
12
|
+
out: [1, 5376, 64] int8, [1, 5376, 17] int8, and a THIRD [1, 5376, 1] int8
|
|
13
|
+
|
|
14
|
+
N is the three strides CONCATENATED, 8 then 16 then 32, each row-major
|
|
15
|
+
(2100 = 40² + 20² + 10²; 5376 = 64² + 32² + 16²) — not one tensor pair per
|
|
16
|
+
stride like the Scrypted export `test_yolo_split_dfl.py` pins. The anchor of
|
|
17
|
+
cell (gx, gy) is ((gx + 0.5) · s, (gy + 0.5) · s); a box is anchor ∓ ltrb · s.
|
|
18
|
+
|
|
19
|
+
Every fixture below is QUANTISED with the real file's scale/zero-point and
|
|
20
|
+
dequantised through `edgetpu_dequant` exactly as the pool's edgetpu predict
|
|
21
|
+
closure does; the expected boxes are computed by hand in the comments.
|
|
22
|
+
|
|
23
|
+
Pure lists — runs on CI's bare interpreter (no numpy).
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import math
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
import unittest
|
|
31
|
+
|
|
32
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
33
|
+
sys.path.insert(0, os.path.dirname(_HERE)) # edgetpu_dequant lives one level up
|
|
34
|
+
|
|
35
|
+
from edgetpu_dequant import dequant_edgetpu_output # noqa: E402
|
|
36
|
+
from yolo_split_dfl import postprocess_yolo_split_dfl # noqa: E402
|
|
37
|
+
|
|
38
|
+
# The real quantisation of yolov9-s-relu6-best_320_int8_edgetpu.tflite.
|
|
39
|
+
CLS_SCALE, CLS_ZP = 0.027195103466510773, 19
|
|
40
|
+
BOX_SCALE, BOX_ZP = 0.07969039678573608, -45
|
|
41
|
+
|
|
42
|
+
COCO17 = [
|
|
43
|
+
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck",
|
|
44
|
+
"boat", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
Q_HOT, Q_COLD = 127, -128 # int8 extremes: DFL bin logits 13.7 / -6.6 after dequant
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _dfl(l: int, t: int, r: int, b: int) -> list[int]:
|
|
51
|
+
out: list[int] = []
|
|
52
|
+
for bin_index in (l, t, r, b):
|
|
53
|
+
out += [Q_HOT if i == bin_index else Q_COLD for i in range(16)]
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _sigmoid(q: int) -> float:
|
|
58
|
+
return 1.0 / (1.0 + math.exp(-((q - CLS_ZP) * CLS_SCALE)))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _quantised_outputs(cells: dict[int, tuple[tuple[int, int, int, int], int, int]], n: int) -> tuple[list, list]:
|
|
62
|
+
"""cells: candidate index → (ltrb bins, class id, quantised class logit)."""
|
|
63
|
+
box = [[_dfl(0, 0, 0, 0) for _ in range(n)]]
|
|
64
|
+
cls = [[[Q_COLD] * len(COCO17) for _ in range(n)]]
|
|
65
|
+
for idx, (ltrb, class_id, q) in cells.items():
|
|
66
|
+
box[0][idx] = _dfl(*ltrb)
|
|
67
|
+
cls[0][idx][class_id] = q
|
|
68
|
+
return box, cls
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _dequant_cls(cls: list) -> list:
|
|
72
|
+
return dequant_edgetpu_output(cls, dtype_name="int8", scale=CLS_SCALE, zero_point=CLS_ZP)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _dequant_box(box: list) -> list:
|
|
76
|
+
return dequant_edgetpu_output(box, dtype_name="int8", scale=BOX_SCALE, zero_point=BOX_ZP)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
CONFIG = {
|
|
80
|
+
"labels": COCO17,
|
|
81
|
+
"confidence": 0.5,
|
|
82
|
+
"preprocessMode": "letterbox",
|
|
83
|
+
"inputSize": 320,
|
|
84
|
+
"inputWidth": 320,
|
|
85
|
+
"inputHeight": 320,
|
|
86
|
+
"nmsIouThreshold": 0.45,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# 640×480 frame letterboxed to 320: scale 0.5, content 320×240, pad (0, 40).
|
|
90
|
+
ORIG_W, ORIG_H, SCALE, PAD = 640, 480, 0.5, (0, 40)
|
|
91
|
+
|
|
92
|
+
# Stride 16 (grid 20, offset 40² = 1600), cell (gx 5, gy 7) → idx 1600 + 7·20 + 5 = 1745.
|
|
93
|
+
# anchor (5.5·16, 7.5·16) = (88, 120); ltrb (2, 3, 4, 1) → (56, 72)-(152, 136) input px
|
|
94
|
+
# → original ((56-0)/0.5, (72-40)/0.5, (152-0)/0.5, (136-40)/0.5) = (112, 64, 304, 192). car.
|
|
95
|
+
CAR_IDX, CAR_Q = 1745, 99
|
|
96
|
+
# Its neighbour (gx 6) → idx 1746, anchor (104, 120); ltrb (3, 3, 3, 1) → the SAME box,
|
|
97
|
+
# a weaker score: NMS must keep only the car above.
|
|
98
|
+
DUP_IDX, DUP_Q = 1746, 60
|
|
99
|
+
# Stride 32 (grid 10, offset 1600 + 400 = 2000), cell (gx 3, gy 2) → idx 2000 + 2·10 + 3 = 2023.
|
|
100
|
+
# anchor (3.5·32, 2.5·32) = (112, 80); ltrb (1, 1, 2, 2) → (80, 48)-(176, 144) input px
|
|
101
|
+
# → original (160, 16, 352, 208). person.
|
|
102
|
+
PERSON_IDX, PERSON_Q = 2023, 127
|
|
103
|
+
|
|
104
|
+
CELLS = {
|
|
105
|
+
CAR_IDX: ((2, 3, 4, 1), 2, CAR_Q),
|
|
106
|
+
DUP_IDX: ((3, 3, 3, 1), 2, DUP_Q),
|
|
107
|
+
PERSON_IDX: ((1, 1, 2, 2), 0, PERSON_Q),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class YoloV9EdgeTpuConcatenatedStrides(unittest.TestCase):
|
|
112
|
+
def _decode(self, predictions: dict) -> list[dict]:
|
|
113
|
+
out = postprocess_yolo_split_dfl(predictions, CONFIG, ORIG_W, ORIG_H, SCALE, PAD)
|
|
114
|
+
self.assertEqual(out["kind"], "detections")
|
|
115
|
+
return sorted(out["detections"], key=lambda d: d["class"])
|
|
116
|
+
|
|
117
|
+
def _assert_expected(self, dets: list[dict]) -> None:
|
|
118
|
+
self.assertEqual([d["class"] for d in dets], ["car", "person"])
|
|
119
|
+
car, person = dets
|
|
120
|
+
self.assertEqual(car["bbox"], [112.0, 64.0, 304.0, 192.0])
|
|
121
|
+
self.assertEqual(person["bbox"], [160.0, 16.0, 352.0, 208.0])
|
|
122
|
+
self.assertEqual(car["score"], round(_sigmoid(CAR_Q), 4))
|
|
123
|
+
self.assertEqual(person["score"], round(_sigmoid(PERSON_Q), 4))
|
|
124
|
+
|
|
125
|
+
def test_v1_0_two_tensors_scores_first(self) -> None:
|
|
126
|
+
box, cls = _quantised_outputs(CELLS, 2100)
|
|
127
|
+
# Positional keys in the file's output order: scores, then boxes.
|
|
128
|
+
self._assert_expected(self._decode({"0": _dequant_cls(cls), "1": _dequant_box(box)}))
|
|
129
|
+
|
|
130
|
+
def test_v1_5_third_single_channel_tensor_is_not_the_class_tensor(self) -> None:
|
|
131
|
+
box, cls = _quantised_outputs(CELLS, 2100)
|
|
132
|
+
# The v1.5 files add a [1, N, 1] output AFTER the class tensor. Read as the
|
|
133
|
+
# class tensor it would score every candidate on ONE cold channel.
|
|
134
|
+
aux = [[[Q_COLD] for _ in range(2100)]]
|
|
135
|
+
preds = {"0": _dequant_box(box), "1": _dequant_cls(cls), "2": _dequant_cls(aux)}
|
|
136
|
+
self._assert_expected(self._decode(preds))
|
|
137
|
+
|
|
138
|
+
def test_a_concatenated_n_that_matches_no_input_size_is_refused(self) -> None:
|
|
139
|
+
box, cls = _quantised_outputs({}, 2101)
|
|
140
|
+
with self.assertRaises(ValueError):
|
|
141
|
+
self._decode({"0": _dequant_cls(cls), "1": _dequant_box(box)})
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
unittest.main()
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
"""YOLO split-head DFL postprocessor (
|
|
1
|
+
"""YOLO split-head DFL postprocessor (YOLOv9 TFLite / EdgeTPU).
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
ONNX ``[1, 7, 2100]`` packed layout:
|
|
3
|
+
Two split layouts reach it, neither the ONNX ``[1, 7, 2100]`` packed one:
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
* Scrypted's TFLite builds emit SIX tensors (3 strides × box+cls):
|
|
6
|
+
|
|
7
|
+
box: [1, H·W, 4·reg_max] with ``reg_max=16`` (DFL distances l,t,r,b)
|
|
8
|
+
cls: [1, H·W, C] logits over person/vehicle/animal (C=3)
|
|
9
|
+
|
|
10
|
+
* The community Frigate EdgeTPU export (``dbro/frigate-detector-edgetpu-yolo9``,
|
|
11
|
+
read from the published files) emits ONE pair with the strides
|
|
12
|
+
CONCATENATED, 8 then 16 then 32, each row-major:
|
|
13
|
+
|
|
14
|
+
box: [1, N, 64] cls: [1, N, C] N = Σ (H/s)·(W/s) — 2100 at 320², 5376 at 512²
|
|
15
|
+
|
|
16
|
+
and its v1.5 files add a third ``[1, N, 1]`` tensor that is NOT the class
|
|
17
|
+
tensor: the widest non-box tensor of a given ``N`` is.
|
|
8
18
|
|
|
9
19
|
Pairs are matched by shared spatial length ``N`` (1600/400/100 for 320²), not
|
|
10
20
|
by positional key order — TFLite output order has drifted between exports.
|
|
@@ -110,7 +120,10 @@ def _pair_box_cls(predictions: dict) -> list[tuple[Any, Any]]:
|
|
|
110
120
|
n, width = shape
|
|
111
121
|
if width == 4 * REG_MAX:
|
|
112
122
|
boxes[n] = tensor
|
|
113
|
-
elif width > 0 and
|
|
123
|
+
elif width > 0 and (n not in classes or width > _shape(classes[n])[1]):
|
|
124
|
+
# The WIDEST non-box tensor of this N is the class tensor: a
|
|
125
|
+
# one-channel sibling (the v1.5 Frigate export's [1, N, 1]) never
|
|
126
|
+
# displaces it, whatever the output order.
|
|
114
127
|
classes[n] = tensor
|
|
115
128
|
pairs: list[tuple[Any, Any]] = []
|
|
116
129
|
for n, box in boxes.items():
|
|
@@ -128,6 +141,37 @@ def _pair_box_cls(predictions: dict) -> list[tuple[Any, Any]]:
|
|
|
128
141
|
return pairs
|
|
129
142
|
|
|
130
143
|
|
|
144
|
+
CONCAT_STRIDES: tuple[int, ...] = (8, 16, 32)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _concat_stride_cells(n: int, in_w: int, in_h: int) -> "list[tuple[int, int, int]] | None":
|
|
148
|
+
"""``[(offset, stride, grid_w), …]`` when ``N`` is every stride of the input
|
|
149
|
+
concatenated (8, 16, 32 in that order), else ``None``."""
|
|
150
|
+
cells: list[tuple[int, int, int]] = []
|
|
151
|
+
offset = 0
|
|
152
|
+
for stride in CONCAT_STRIDES:
|
|
153
|
+
if in_w % stride or in_h % stride:
|
|
154
|
+
return None
|
|
155
|
+
cells.append((offset, stride, in_w // stride))
|
|
156
|
+
offset += (in_w // stride) * (in_h // stride)
|
|
157
|
+
return cells if offset == n else None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _segments(
|
|
161
|
+
box: Any, cls: Any, in_w: int, in_h: int, input_size: int
|
|
162
|
+
) -> list[tuple[Any, Any, int, int]]:
|
|
163
|
+
"""``(box rows, cls rows, stride, grid_w)`` per stride of one box/cls pair."""
|
|
164
|
+
n = _shape(box)[0]
|
|
165
|
+
concat = _concat_stride_cells(n, in_w, in_h)
|
|
166
|
+
if concat is None:
|
|
167
|
+
return [(box, cls, _infer_stride(n, input_size), int(round(math.sqrt(n))))]
|
|
168
|
+
ends = [offset for offset, _, _ in concat[1:]] + [n]
|
|
169
|
+
return [
|
|
170
|
+
(box[offset:end], cls[offset:end], stride, grid_w)
|
|
171
|
+
for (offset, stride, grid_w), end in zip(concat, ends)
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
|
|
131
175
|
def _infer_stride(n: int, input_size: int) -> int:
|
|
132
176
|
"""N = (input_size / stride)² → stride = input_size / sqrt(N)."""
|
|
133
177
|
side = int(round(math.sqrt(n)))
|
|
@@ -191,12 +235,17 @@ def postprocess_yolo_split_dfl(
|
|
|
191
235
|
labels = config.get("labels") or ["person", "vehicle", "animal"]
|
|
192
236
|
nms_iou = float(config.get("nmsIouThreshold", 0.45))
|
|
193
237
|
input_size = int(config.get("inputSize") or config.get("inputWidth") or 320)
|
|
238
|
+
in_w = int(config.get("inputWidth") or input_size)
|
|
239
|
+
in_h = int(config.get("inputHeight") or input_size)
|
|
194
240
|
|
|
241
|
+
segments = [
|
|
242
|
+
seg
|
|
243
|
+
for box_pair, cls_pair in _pair_box_cls(predictions)
|
|
244
|
+
for seg in _segments(box_pair, cls_pair, in_w, in_h, input_size)
|
|
245
|
+
]
|
|
195
246
|
candidates: list[dict] = []
|
|
196
|
-
for box_tensor, cls_tensor in
|
|
247
|
+
for box_tensor, cls_tensor, stride, grid in segments:
|
|
197
248
|
n = _shape(box_tensor)[0]
|
|
198
|
-
stride = _infer_stride(n, input_size)
|
|
199
|
-
grid = int(round(math.sqrt(n)))
|
|
200
249
|
num_classes = _shape(cls_tensor)[1]
|
|
201
250
|
|
|
202
251
|
for i in range(n):
|
|
@@ -9,16 +9,19 @@
|
|
|
9
9
|
# and is numpy-2 native.
|
|
10
10
|
ai-edge-litert==2.1.6
|
|
11
11
|
|
|
12
|
-
# libedgetpu
|
|
13
|
-
# install it
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
12
|
+
# libedgetpu (the Coral delegate) is a native shared library — pip cannot
|
|
13
|
+
# install it — and its TensorFlow version MUST match the interpreter (stock
|
|
14
|
+
# Coral libedgetpu 16.0/grouper is built against old TFLite and FAILS at invoke:
|
|
15
|
+
# "EdgeTpuDelegateForCustomOp failed to invoke"). The line below asks the deps
|
|
16
|
+
# downloader to stage feranick's libedgetpu rebuilt against TF 2.17.1 right
|
|
17
|
+
# after this file installs (`@camstack/types` `deps/edgetpu-delegate.ts`:
|
|
18
|
+
# pinned, SHA-256 verified; on Linux libusb-1.0.so.0 beside it, because the std
|
|
19
|
+
# delegate links libusb even for a PCIe Coral and the pool preloads that copy).
|
|
20
|
+
# It lands in <CAMSTACK_DATA>/deps/edgetpu/lib/ — /data/deps/edgetpu/lib on the
|
|
21
|
+
# hub image, where `edgetpu-install.sh` (beside this file) has always put it.
|
|
22
|
+
# Linux x64/arm64 and Apple Silicon only; Windows has no TF 2.17.1 build.
|
|
23
|
+
# CAMSTACK_EDGETPU_LIB overrides the path the pool loads.
|
|
24
|
+
# camstack-native-dep: edgetpu-delegate
|
|
22
25
|
#
|
|
23
26
|
# Host prerequisite, USB: /dev/bus/usb passed through plus a
|
|
24
27
|
# `--device-cgroup-rule 'c 189:* rmw'` rule (the Coral re-enumerates its USB
|
|
@@ -14,7 +14,7 @@ import tempfile
|
|
|
14
14
|
import unittest
|
|
15
15
|
from unittest import mock
|
|
16
16
|
|
|
17
|
-
from edgetpu_device import edgetpu_delegate_options, preload_libusb
|
|
17
|
+
from edgetpu_device import edgetpu_delegate_options, edgetpu_library_path, preload_libusb
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class EdgetpuDelegateOptions(unittest.TestCase):
|
|
@@ -70,5 +70,34 @@ class PreloadLibusb(unittest.TestCase):
|
|
|
70
70
|
cdll.assert_called_once_with(path, mode=ctypes.RTLD_GLOBAL)
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
class EdgetpuLibraryPath(unittest.TestCase):
|
|
74
|
+
"""The delegate the pool loads — the same lookup as the TS probe
|
|
75
|
+
(`chooseEdgetpuDelegate` in `@camstack/types`): override, then the copy
|
|
76
|
+
staged under the node data root, then the hub's historical path."""
|
|
77
|
+
|
|
78
|
+
LEGACY = "/data/deps/edgetpu/lib/libedgetpu.so.1"
|
|
79
|
+
|
|
80
|
+
def test_a_desktop_loads_the_dylib_staged_under_its_data_root(self) -> None:
|
|
81
|
+
staged = "/Users/me/CamStack/data/deps/edgetpu/lib/libedgetpu.1.dylib"
|
|
82
|
+
path = edgetpu_library_path(
|
|
83
|
+
{"CAMSTACK_DATA": "/Users/me/CamStack/data"}, "darwin", lambda p: p == staged
|
|
84
|
+
)
|
|
85
|
+
self.assertEqual(path, staged)
|
|
86
|
+
|
|
87
|
+
def test_a_linux_agent_with_another_data_root_loads_its_staged_copy(self) -> None:
|
|
88
|
+
staged = "/srv/cs/deps/edgetpu/lib/libedgetpu.so.1"
|
|
89
|
+
env = {"CAMSTACK_DATA_DIR": "/srv/cs"}
|
|
90
|
+
self.assertEqual(edgetpu_library_path(env, "linux", lambda p: p in (staged, self.LEGACY)), staged)
|
|
91
|
+
|
|
92
|
+
def test_the_override_wins_even_when_missing(self) -> None:
|
|
93
|
+
# The operator named a file: loading another build instead would hide it.
|
|
94
|
+
env = {"CAMSTACK_EDGETPU_LIB": "/opt/x.so", "CAMSTACK_DATA": "/d"}
|
|
95
|
+
self.assertEqual(edgetpu_library_path(env, "linux", lambda p: False), "/opt/x.so")
|
|
96
|
+
|
|
97
|
+
def test_nothing_staged_falls_back_to_the_historical_hub_path(self) -> None:
|
|
98
|
+
self.assertEqual(edgetpu_library_path({}, "linux", lambda p: False), self.LEGACY)
|
|
99
|
+
self.assertEqual(edgetpu_library_path({"CAMSTACK_DATA": "/d"}, "linux", lambda p: False), self.LEGACY)
|
|
100
|
+
|
|
101
|
+
|
|
73
102
|
if __name__ == "__main__":
|
|
74
103
|
unittest.main()
|