@camstack/addon-pipeline 1.2.327 → 1.2.329
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 +35 -27
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-CBIYdAJ5.js → default-detection-model-ChH7Y219.js} +130 -61
- package/dist/{default-detection-model-hql6BX9-.mjs → default-detection-model-j0nBpRoB.mjs} +130 -61
- package/dist/detection-pipeline/index.js +458 -16
- package/dist/detection-pipeline/index.mjs +458 -16
- package/dist/{dist-B9LnBPz9.mjs → dist-0HoA3wAi.mjs} +503 -370
- package/dist/{dist-BBZwYfSN.js → dist-BlIWhjnu.js} +550 -369
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-mJQBUiEc.js → node-BPpVMnZy.js} +1 -1
- package/dist/{node-Ci0E-0VP.mjs → node-HOMWRPzW.mjs} +1 -1
- package/dist/pipeline-runner/index.js +215 -78
- package/dist/pipeline-runner/index.mjs +215 -78
- package/dist/{process-memory-DgEfKdtC.js → process-memory-BW06vOIQ.js} +1 -1
- package/dist/{process-memory-B63Jlpev.mjs → process-memory-DuF8pa2w.mjs} +1 -1
- package/dist/recorder/index.js +2 -2
- package/dist/recorder/index.mjs +2 -2
- package/dist/{segment-demux-js-DzwHW4Ey.mjs → segment-demux-js-BU2MXpln.mjs} +1 -1
- package/dist/{segment-demux-js-C2ufKMu0.js → segment-demux-js-Dl52Zz8w.js} +1 -1
- package/dist/session-decode/decode-worker-main.js +13 -21
- package/dist/session-decode/decode-worker-main.mjs +13 -21
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B4fPzS-8.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-vNfS65In.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DfeHvW4k.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-Cg4IdtE5.mjs → hostInit-Ciacp783.mjs} +3 -3
- package/dist/stream-broker/index.js +4 -3
- package/dist/stream-broker/index.mjs +4 -3
- 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 +69 -138
- package/python/postprocessors/test_bird_edgetpu.py +124 -0
- package/python/postprocessors/test_yolox_plate_edgetpu.py +126 -0
- package/python/postprocessors/test_yunet_edgetpu.py +149 -0
- package/python/postprocessors/testdata/bird_classifier_int8_outputs.json +1 -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/python/test_inference_pool_static_batch.py +2 -4
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BDGzcgLP.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as LABEL_LINK_POLICIES, Ct as inferModelProvider, E as FILLABLE_STEP_VOCABULARIES, St as hfModelUrl, X as audioKindId, b as DEVICE_BACKEND_TO_FORMAT, f as COCO_80_LABELS, i as AUDIO_MACRO_LABELS, j as LABEL_LINK_SETTING_KEY, p as COCO_TO_MACRO, rn as stepModelRunsOnFormat, v as DEFAULT_LABEL_LINK_POLICY } from "./dist-0HoA3wAi.mjs";
|
|
2
2
|
import { t as isWorkerReply } from "./worker-protocol-D_NzPcnh.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/detection-pipeline/registry/effective-confidence.ts
|
|
@@ -90,7 +90,7 @@ var YUNET_MIT = {
|
|
|
90
90
|
},
|
|
91
91
|
url: "https://github.com/opencv/opencv_zoo/blob/main/models/face_detection_yunet/LICENSE",
|
|
92
92
|
attribution: "YuNet weights Copyright (c) 2020 Shiqi Yu, MIT. Training code: ShiqiYu/libfacedetection.train, BSD-3-Clause.",
|
|
93
|
-
modifications: "input contract baked into the graph (the pool RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather); internal tensor names cleared in the OpenVINO IR; ONNX fp32, OpenVINO IR fp32, CoreML fp16 (scripts/build-yunet-model.py)",
|
|
93
|
+
modifications: "input contract baked into the graph (the pool RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather); internal tensor names cleared in the OpenVINO IR; ONNX fp32, OpenVINO IR fp32, CoreML fp16 (scripts/build-yunet-model.py); Edge TPU build: the bake folded into the first Conv, full-integer int8 post-training quantisation calibrated on CamStack person crops, compiled with edgetpu_compiler 14 (scripts/build-yunet-edgetpu.py)",
|
|
94
94
|
hosting: "camstack-hf"
|
|
95
95
|
};
|
|
96
96
|
var AURAFACE_APACHE = {
|
|
@@ -104,6 +104,30 @@ var AURAFACE_APACHE = {
|
|
|
104
104
|
modifications: "input scaling y = 2x - 1 baked ahead of the first Conv (scripts/build-auraface-model.py); converted to OpenVINO IR and CoreML FP16",
|
|
105
105
|
hosting: "camstack-hf"
|
|
106
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* open-lpr's YOLOX-tiny plate detector (D719). Weights Apache-2.0 by the
|
|
109
|
+
* open-lpr repository licence and NOTICE (YOLOX code and its same-repository
|
|
110
|
+
* COCO weights are Apache-2.0; no Ultralytics weights used) — the weights'
|
|
111
|
+
* status is inferred from that statement, not from a per-file licence. The
|
|
112
|
+
* NOTICE travels verbatim (Apache-2.0 §4(d)): licenses/models/NOTICE-open-lpr.txt
|
|
113
|
+
* and the mirror folder's own NOTICE.
|
|
114
|
+
*/
|
|
115
|
+
var OPEN_LPR_YOLOX_APACHE = {
|
|
116
|
+
weights: "Apache-2.0",
|
|
117
|
+
code: "Apache-2.0",
|
|
118
|
+
data: {
|
|
119
|
+
name: "Simanno annotated plate corpus plus community-contributed photographs, pseudo-labelled by a teacher model (not published; the upstream NOTICE says the photographs were uploaded without consent obtained)",
|
|
120
|
+
terms: "UNKNOWN"
|
|
121
|
+
},
|
|
122
|
+
upstream: {
|
|
123
|
+
name: "open-lpr models (faisalthaheem/open-lpr-models), YOLOX-tiny",
|
|
124
|
+
url: "https://huggingface.co/faisalthaheem/open-lpr-models"
|
|
125
|
+
},
|
|
126
|
+
url: "https://huggingface.co/faisalthaheem/open-lpr-models/blob/aed0df35ec2891c5a90437041ebeadb592cc516b/LICENSE",
|
|
127
|
+
attribution: "open-lpr models, Copyright the open-lpr contributors, Apache-2.0 (https://huggingface.co/faisalthaheem/open-lpr-models, tag detector-2026.10.1). YOLOX architecture and COCO pretraining by Megvii (https://github.com/Megvii-BaseDetection/YOLOX), Apache-2.0. The upstream NOTICE is reproduced verbatim (`NOTICE-open-lpr.txt`).",
|
|
128
|
+
modifications: "input contract baked into the graph (static 1x3x640x640 RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather) and the output reshaped to the YOLOv8 layout (cx, cy, w, h, obj*cls); ONNX fp32, OpenVINO IR fp16, CoreML fp16 (scripts/build-yolox-plate-model.py); Edge TPU build: the Focus stem rewritten as the equivalent 6x6 stride-2 Conv with the input bake folded into it, the graph cut before the grid decode (raw per-stride head), full-integer int8 post-training quantisation calibrated on CamStack vehicle crops, compiled with edgetpu_compiler 14 (scripts/build-yolox-plate-edgetpu.py)",
|
|
129
|
+
hosting: "camstack-hf"
|
|
130
|
+
};
|
|
107
131
|
var FAST_PLATE_OCR_MIT = {
|
|
108
132
|
weights: "MIT",
|
|
109
133
|
code: "MIT",
|
|
@@ -145,7 +169,7 @@ var AIY_BIRDS_APACHE = {
|
|
|
145
169
|
},
|
|
146
170
|
url: "https://github.com/google-coral/test_data/blob/master/LICENSE",
|
|
147
171
|
attribution: "Coral Edge TPU models and AIY Vision Birds V1 © Google LLC, Apache-2.0.",
|
|
148
|
-
modifications: "converted from TensorFlow with tf2onnx; OpenVINO IR and CoreML derived from it",
|
|
172
|
+
modifications: "converted from TensorFlow with tf2onnx; OpenVINO IR and CoreML derived from it; the Edge TPU build is the Google full-integer quantised TFLite of the same model, compiled with edgetpu_compiler 14 (scripts/build-bird-classifier-edgetpu.py)",
|
|
149
173
|
hosting: "camstack-hf"
|
|
150
174
|
};
|
|
151
175
|
var VEHICLE_TYPE_V1_NC = {
|
|
@@ -728,7 +752,12 @@ var FACE_DETECTION_MODELS = [{
|
|
|
728
752
|
files: [...MLPACKAGE_FILES],
|
|
729
753
|
runtimes: ["python"]
|
|
730
754
|
},
|
|
731
|
-
openvino: ovFormat(hf("faceDetection/yunet/openvino/camstack-yunet-2023mar.xml"), .36)
|
|
755
|
+
openvino: ovFormat(hf("faceDetection/yunet/openvino/camstack-yunet-2023mar.xml"), .36),
|
|
756
|
+
tflite: {
|
|
757
|
+
url: hf("faceDetection/yunet/tflite/camstack-yunet-2023mar_edgetpu.tflite"),
|
|
758
|
+
sizeMB: .7,
|
|
759
|
+
runtimes: ["python"]
|
|
760
|
+
}
|
|
732
761
|
}
|
|
733
762
|
}];
|
|
734
763
|
var FACE_EMBEDDING_MODELS = [{
|
|
@@ -762,17 +791,10 @@ var FACE_EMBEDDING_MODELS = [{
|
|
|
762
791
|
}
|
|
763
792
|
}];
|
|
764
793
|
var PLATE_DETECTION_MODELS = [{
|
|
765
|
-
id: "
|
|
766
|
-
license:
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
terms: "UNKNOWN"
|
|
770
|
-
},
|
|
771
|
-
attribution: "© Ultralytics, AGPL-3.0 (YOLOv8n base, fine-tuned for plates by an unidentified third party). CamStack holds no training checkpoint; the ONNX file is the only form it has.",
|
|
772
|
-
modifications: "converted to OpenVINO IR and CoreML"
|
|
773
|
-
}),
|
|
774
|
-
name: "YOLOv8 Nano — License Plate",
|
|
775
|
-
description: "YOLOv8 Nano fine-tuned for license plate detection",
|
|
794
|
+
id: "yolox-tiny-plate",
|
|
795
|
+
license: OPEN_LPR_YOLOX_APACHE,
|
|
796
|
+
name: "YOLOX-tiny — License Plate",
|
|
797
|
+
description: "YOLOX-tiny licence-plate detector trained by the open-lpr project (Apache-2.0)",
|
|
776
798
|
inputSize: {
|
|
777
799
|
width: 640,
|
|
778
800
|
height: 640
|
|
@@ -782,19 +804,25 @@ var PLATE_DETECTION_MODELS = [{
|
|
|
782
804
|
name: "License Plate"
|
|
783
805
|
}],
|
|
784
806
|
preprocessMode: "letterbox",
|
|
807
|
+
postprocessor: "yolo",
|
|
785
808
|
formats: {
|
|
786
809
|
onnx: {
|
|
787
|
-
url: hf("plateDetection/
|
|
788
|
-
sizeMB:
|
|
810
|
+
url: hf("plateDetection/yolox-tiny-plate/onnx/camstack-yolox-tiny-plate.onnx"),
|
|
811
|
+
sizeMB: 19.3
|
|
789
812
|
},
|
|
790
813
|
coreml: {
|
|
791
|
-
url: hf("plateDetection/
|
|
792
|
-
sizeMB:
|
|
814
|
+
url: hf("plateDetection/yolox-tiny-plate/coreml/camstack-yolox-tiny-plate.mlpackage"),
|
|
815
|
+
sizeMB: 9.8,
|
|
793
816
|
isDirectory: true,
|
|
794
817
|
files: [...MLPACKAGE_FILES],
|
|
795
818
|
runtimes: ["python"]
|
|
796
819
|
},
|
|
797
|
-
openvino: ovFormat(hf("plateDetection/
|
|
820
|
+
openvino: ovFormat(hf("plateDetection/yolox-tiny-plate/openvino/camstack-yolox-tiny-plate.xml"), 10),
|
|
821
|
+
tflite: {
|
|
822
|
+
url: hf("plateDetection/yolox-tiny-plate/tflite/camstack-yolox-tiny-plate_edgetpu.tflite"),
|
|
823
|
+
sizeMB: 6.9,
|
|
824
|
+
runtimes: ["python"]
|
|
825
|
+
}
|
|
798
826
|
}
|
|
799
827
|
}];
|
|
800
828
|
var PACKAGE_DETECTION_MODELS = [{
|
|
@@ -924,7 +952,12 @@ var BIRD_CLASSIFIER_MODELS = [{
|
|
|
924
952
|
files: [...MLPACKAGE_FILES],
|
|
925
953
|
runtimes: ["python"]
|
|
926
954
|
},
|
|
927
|
-
openvino: ovFormat(hf("animalClassification/bird-classifier/openvino/bird-classifier.xml"), 14)
|
|
955
|
+
openvino: ovFormat(hf("animalClassification/bird-classifier/openvino/bird-classifier.xml"), 14),
|
|
956
|
+
tflite: {
|
|
957
|
+
url: hf("animalClassification/bird-classifier/tflite/bird-classifier_edgetpu.tflite"),
|
|
958
|
+
sizeMB: 3.9,
|
|
959
|
+
runtimes: ["python"]
|
|
960
|
+
}
|
|
928
961
|
},
|
|
929
962
|
extraFiles: [{
|
|
930
963
|
url: hf("animalClassification/bird-classifier/onnx/bird-classifier-labels.json"),
|
|
@@ -1151,16 +1184,27 @@ var ANIMAL_10_LABELS = [
|
|
|
1151
1184
|
"sheep",
|
|
1152
1185
|
"spider"
|
|
1153
1186
|
];
|
|
1154
|
-
var ANIMAL_CLASSIFIER_LABELS = [
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1187
|
+
var ANIMAL_CLASSIFIER_LABELS = FILLABLE_STEP_VOCABULARIES["animal-classifier"];
|
|
1188
|
+
var VEHICLE_CLASSIFIER_LABELS = FILLABLE_STEP_VOCABULARIES["vehicle-classifier"];
|
|
1189
|
+
/**
|
|
1190
|
+
* The `labelLink` policy field (D715) — offered on every step a root detector
|
|
1191
|
+
* can fill. Stored in the step's own settings (`DeviceStepConfig.settings` /
|
|
1192
|
+
* `CameraStepOverridePatch.settings`), so it has ONE authority. It changes
|
|
1193
|
+
* nothing unless the camera's root model declares it fills this step.
|
|
1194
|
+
*/
|
|
1195
|
+
function labelLinkField(stepName) {
|
|
1196
|
+
return {
|
|
1197
|
+
type: "select",
|
|
1198
|
+
key: LABEL_LINK_SETTING_KEY,
|
|
1199
|
+
label: "When the detector already fills this step",
|
|
1200
|
+
description: `Only matters when the root detection model declares it fills ${stepName} (e.g. a Frigate+ model). "Skip" does not run ${stepName} for a subject the detector already named, and makes the detector's label the one answer — but only once every class the detector cannot produce is switched off in this step's class list. "Run" always runs ${stepName}; the detector's label is then a fallback any answer of the step outranks.`,
|
|
1201
|
+
options: LABEL_LINK_POLICIES.map((p) => ({
|
|
1202
|
+
value: p,
|
|
1203
|
+
label: p === "run" ? "Run anyway" : "Skip when filled"
|
|
1204
|
+
})),
|
|
1205
|
+
default: DEFAULT_LABEL_LINK_POLICY
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1164
1208
|
var VGG_G2_CHARSET = [
|
|
1165
1209
|
"",
|
|
1166
1210
|
..."0123456789".split(""),
|
|
@@ -1376,27 +1420,31 @@ var AnimalClassifierStep = class {
|
|
|
1376
1420
|
}
|
|
1377
1421
|
};
|
|
1378
1422
|
getConfigSchema(model) {
|
|
1379
|
-
return [
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1423
|
+
return [
|
|
1424
|
+
{
|
|
1425
|
+
type: "multiselect",
|
|
1426
|
+
key: "enabledClasses",
|
|
1427
|
+
label: "Species",
|
|
1428
|
+
description: "Which animal species to keep (empty = all). The classifier still scores every label but only the selected classes are returned on the detection.",
|
|
1429
|
+
options: ANIMAL_CLASSIFIER_LABELS.map((l) => ({
|
|
1430
|
+
value: l,
|
|
1431
|
+
label: l
|
|
1432
|
+
})),
|
|
1433
|
+
default: []
|
|
1434
|
+
},
|
|
1435
|
+
labelLinkField("the animal classifier"),
|
|
1436
|
+
{
|
|
1437
|
+
type: "slider",
|
|
1438
|
+
key: "minConfidence",
|
|
1439
|
+
label: "Min Confidence",
|
|
1440
|
+
description: "Drop classifications whose score is below this threshold.",
|
|
1441
|
+
default: resolveModelDefaultConfidence(this.definition, model),
|
|
1442
|
+
min: 0,
|
|
1443
|
+
max: 1,
|
|
1444
|
+
step: .05,
|
|
1445
|
+
showValue: true
|
|
1446
|
+
}
|
|
1447
|
+
];
|
|
1400
1448
|
}
|
|
1401
1449
|
};
|
|
1402
1450
|
/**
|
|
@@ -1434,7 +1482,7 @@ var PackageDetectionStep = class extends PipelineStepBase {
|
|
|
1434
1482
|
step: .05,
|
|
1435
1483
|
default: resolveModelDefaultConfidence(this.definition, model),
|
|
1436
1484
|
showValue: true
|
|
1437
|
-
}];
|
|
1485
|
+
}, labelLinkField("package detection")];
|
|
1438
1486
|
}
|
|
1439
1487
|
};
|
|
1440
1488
|
var ClassifierWithMinConfidence = class extends PipelineStepBase {
|
|
@@ -1582,7 +1630,7 @@ var STEP_PLATE_DETECTION = new PipelineStepBase({
|
|
|
1582
1630
|
inputClasses: ["vehicle"],
|
|
1583
1631
|
outputClasses: ["plate"],
|
|
1584
1632
|
models: [...PLATE_DETECTION_MODELS],
|
|
1585
|
-
defaultModelId: "
|
|
1633
|
+
defaultModelId: "yolox-tiny-plate",
|
|
1586
1634
|
defaultConfidence: .5,
|
|
1587
1635
|
defaultMinParentScore: .7,
|
|
1588
1636
|
labels: ["plate"],
|
|
@@ -1649,7 +1697,32 @@ var STEP_BIRD_CLASSIFIER = new ClassifierWithMinConfidence({
|
|
|
1649
1697
|
stickyOnConfidence: .7
|
|
1650
1698
|
}
|
|
1651
1699
|
});
|
|
1652
|
-
|
|
1700
|
+
/**
|
|
1701
|
+
* `vehicle-classifier` — the `minConfidence` slider plus the two fields a
|
|
1702
|
+
* root-detector fill needs (D715): the class allow-list (a coverage gap the
|
|
1703
|
+
* detector cannot produce, e.g. `Van` for Frigate+, is switched off HERE before
|
|
1704
|
+
* the skip applies) and the `labelLink` policy.
|
|
1705
|
+
*/
|
|
1706
|
+
var VehicleClassifierStep = class extends ClassifierWithMinConfidence {
|
|
1707
|
+
getConfigSchema(model) {
|
|
1708
|
+
return [
|
|
1709
|
+
{
|
|
1710
|
+
type: "multiselect",
|
|
1711
|
+
key: "enabledClasses",
|
|
1712
|
+
label: "Vehicle types",
|
|
1713
|
+
description: "Which vehicle types to keep (empty = all). The classifier still scores every type but only the selected ones are returned on the detection.",
|
|
1714
|
+
options: VEHICLE_CLASSIFIER_LABELS.map((l) => ({
|
|
1715
|
+
value: l,
|
|
1716
|
+
label: l
|
|
1717
|
+
})),
|
|
1718
|
+
default: []
|
|
1719
|
+
},
|
|
1720
|
+
...super.getConfigSchema(model),
|
|
1721
|
+
labelLinkField("the vehicle classifier")
|
|
1722
|
+
];
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
var STEP_VEHICLE_CLASSIFIER = new VehicleClassifierStep({
|
|
1653
1726
|
id: "vehicle-classifier",
|
|
1654
1727
|
name: "Vehicle Classifier",
|
|
1655
1728
|
slot: "classifier",
|
|
@@ -2879,14 +2952,10 @@ var LocalFrameRegistry = class {
|
|
|
2879
2952
|
if (entry.releaseNativeOnClose) entry.image.releaseNativeLease?.();
|
|
2880
2953
|
}
|
|
2881
2954
|
};
|
|
2882
|
-
function envPositiveInt(name, fallback) {
|
|
2883
|
-
const parsed = Number(process.env[name]);
|
|
2884
|
-
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
2885
|
-
}
|
|
2886
2955
|
/** Shared only by addons in the same grouped runner process. */
|
|
2887
2956
|
var localFrameRegistry = new LocalFrameRegistry({
|
|
2888
|
-
maxFrames:
|
|
2889
|
-
maxBytes:
|
|
2957
|
+
maxFrames: DEFAULT_MAX_FRAMES,
|
|
2958
|
+
maxBytes: DEFAULT_MAX_BYTES
|
|
2890
2959
|
});
|
|
2891
2960
|
//#endregion
|
|
2892
2961
|
//#region src/detection-pipeline/pipeline/macro-gate.ts
|