@camstack/addon-pipeline 1.1.56 → 1.1.58

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.
Files changed (42) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/detection-pipeline/index.js +71 -74
  4. package/dist/detection-pipeline/index.mjs +70 -73
  5. package/dist/{dist-ClE6_i0p.js → dist-Bn6Uq4vq.js} +4750 -4644
  6. package/dist/{dist-CrjLGrkj.mjs → dist-CnwwDzX3.mjs} +4751 -4645
  7. package/dist/motion-wasm/index.js +9 -41
  8. package/dist/motion-wasm/index.mjs +9 -41
  9. package/dist/pipeline-runner/index.js +238 -656
  10. package/dist/pipeline-runner/index.mjs +227 -647
  11. package/dist/recorder/index.js +2 -2
  12. package/dist/recorder/index.mjs +2 -2
  13. package/dist/remote-restream-BeHi78PZ.mjs +25 -0
  14. package/dist/remote-restream-CO36Sr30.js +36 -0
  15. package/dist/session-decode/decode-worker-child.js +97 -2
  16. package/dist/session-decode/decode-worker-child.mjs +97 -2
  17. package/dist/{step-definitions-C9kUXgC7.js → step-definitions-D7NOrnzz.js} +44 -2
  18. package/dist/{step-definitions-dCghpdRo.mjs → step-definitions-DhEe57Wn.mjs} +44 -2
  19. package/dist/stream-broker/_stub.js +282 -222
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D4xH4Tm-.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B8FQ1e0a.mjs} +3 -3
  21. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D2z_vRMH.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Ba7Eyd47.mjs} +1 -1
  22. package/dist/stream-broker/{hostInit-Di9WUNlQ.mjs → hostInit-D1Vdyu-4.mjs} +3 -3
  23. package/dist/stream-broker/index.js +881 -40
  24. package/dist/stream-broker/index.mjs +878 -37
  25. package/dist/stream-broker/remoteEntry.js +1 -1
  26. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Ca93F1js.js → MaskShapeCanvas-DI4BY7W2-CG5EE0W4.js} +1 -1
  27. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CDqgb-WA.js → MotionZonesSettings-NcxxQN8r-BFeLUNOl.js} +1 -1
  28. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BlxPhY_w.js → PrivacyMaskSettings-APgPLF7p-pQQunq1F.js} +1 -1
  29. package/embed-dist/assets/index-BxqQ931j.js +81 -0
  30. package/embed-dist/index.html +1 -1
  31. package/package.json +7 -5
  32. package/python/inference_pool.py +172 -28
  33. package/python/postprocessors/__init__.py +2 -0
  34. package/python/postprocessors/ssd.py +128 -0
  35. package/python/requirements-edgetpu.txt +23 -0
  36. package/python/test_inference_pool_layout.py +16 -1
  37. package/python/test_inference_pool_preprocess.py +65 -0
  38. package/dist/remote-source-plane-B28iGi7h.mjs +0 -1001
  39. package/dist/remote-source-plane-BAqEHSgm.js +0 -1030
  40. package/embed-dist/assets/index-B23riUw8.js +0 -81
  41. package/dist/{model-download-service-C-IHWnXx-BPy6aoAx.mjs → model-download-service-C-IHWnXx-3Mmeob3l.mjs} +1 -1
  42. package/dist/{model-download-service-C-IHWnXx-BLXzxpRB.js → model-download-service-C-IHWnXx-D326YNnt.js} +1 -1
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-D6vf50IK.js");
6
- const require_dist = require("../dist-ClE6_i0p.js");
6
+ const require_dist = require("../dist-Bn6Uq4vq.js");
7
7
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
8
- const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BLXzxpRB.js");
8
+ const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-D326YNnt.js");
9
9
  let node_fs = require("node:fs");
10
10
  node_fs = require_chunk.__toESM(node_fs);
11
11
  let node_path = require("node:path");
@@ -1,7 +1,7 @@
1
1
  import { t as __require } from "../chunk-BdkLduGY.mjs";
2
- import { C as mapAudioLabelToMacro, I as errMsg, L as BaseAddon, U as hydrateSchema, h as audioAnalyzerCapability, l as HF_BASE_URL, m as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG } from "../dist-CrjLGrkj.mjs";
2
+ import { C as mapAudioLabelToMacro, H as hydrateSchema, I as errMsg, L as BaseAddon, h as audioAnalyzerCapability, l as HF_BASE_URL, m as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG } from "../dist-CnwwDzX3.mjs";
3
3
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
4
- import { r as downloadFile } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
4
+ import { r as downloadFile } from "../model-download-service-C-IHWnXx-3Mmeob3l.mjs";
5
5
  import * as fs from "node:fs";
6
6
  import * as path$1 from "node:path";
7
7
  //#region src/audio-analyzer/audio-pipeline.ts
@@ -3,20 +3,19 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-D6vf50IK.js");
6
- const require_dist = require("../dist-ClE6_i0p.js");
7
- const require_step_definitions = require("../step-definitions-C9kUXgC7.js");
6
+ const require_dist = require("../dist-Bn6Uq4vq.js");
7
+ const require_step_definitions = require("../step-definitions-D7NOrnzz.js");
8
8
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
9
- const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BLXzxpRB.js");
10
- let _camstack_shm_ring = require("@camstack/shm-ring");
9
+ const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-D326YNnt.js");
11
10
  let node_child_process = require("node:child_process");
11
+ let sharp = require("sharp");
12
+ sharp = require_chunk.__toESM(sharp);
12
13
  let node_fs = require("node:fs");
13
14
  node_fs = require_chunk.__toESM(node_fs);
14
15
  let node_path = require("node:path");
15
16
  node_path = require_chunk.__toESM(node_path);
16
17
  let node_os = require("node:os");
17
18
  node_os = require_chunk.__toESM(node_os);
18
- let sharp = require("sharp");
19
- sharp = require_chunk.__toESM(sharp);
20
19
  //#region src/detection-pipeline/runtimes.ts
21
20
  var KNOWN_PLATFORMS = [
22
21
  "darwin",
@@ -52,6 +51,9 @@ function npuInfoFrom(hw) {
52
51
  if (!type) return null;
53
52
  return { type };
54
53
  }
54
+ function coralInfoFrom(hw) {
55
+ return hw.coral?.type === "coral-edgetpu" ? { type: "coral-edgetpu" } : null;
56
+ }
55
57
  function envToHardwareInfo(env) {
56
58
  return {
57
59
  platform: toKnownPlatform(env.platform),
@@ -61,7 +63,8 @@ function envToHardwareInfo(env) {
61
63
  totalRAM_MB: 0,
62
64
  availableRAM_MB: 0,
63
65
  gpu: env.hardware ? gpuInfoFrom(env.hardware) : null,
64
- npu: env.hardware ? npuInfoFrom(env.hardware) : null
66
+ npu: env.hardware ? npuInfoFrom(env.hardware) : null,
67
+ coral: env.hardware ? coralInfoFrom(env.hardware) : null
65
68
  };
66
69
  }
67
70
  function probedToHardwareInfo(hw) {
@@ -74,7 +77,8 @@ function probedToHardwareInfo(hw) {
74
77
  totalRAM_MB: 0,
75
78
  availableRAM_MB: 0,
76
79
  gpu: gpuInfoFrom(hw),
77
- npu: npuInfoFrom(hw)
80
+ npu: npuInfoFrom(hw),
81
+ coral: coralInfoFrom(hw)
78
82
  };
79
83
  }
80
84
  var RUNTIME_DETAIL = {
@@ -107,6 +111,15 @@ var RUNTIME_DETAIL = {
107
111
  maxBatchSize: 8,
108
112
  numWorkers: 1
109
113
  }
114
+ },
115
+ edgetpu: {
116
+ label: "Coral Edge TPU",
117
+ pythonRequirements: ["requirements.txt", "requirements-edgetpu.txt"],
118
+ tuning: {
119
+ concurrency: 1,
120
+ batchMode: "none",
121
+ numWorkers: 1
122
+ }
110
123
  }
111
124
  };
112
125
  /**
@@ -134,11 +147,12 @@ function defaultDeviceFor(id) {
134
147
  var RUNTIME_FORMAT = {
135
148
  onnx: "onnx",
136
149
  openvino: "openvino",
137
- coreml: "coreml"
150
+ coreml: "coreml",
151
+ edgetpu: "tflite"
138
152
  };
139
153
  /**
140
154
  * Returns the model format required for a given runtime.
141
- * Returns the locally-typed format string ('onnx' | 'openvino' | 'coreml')
155
+ * Returns the locally-typed format string ('onnx' | 'openvino' | 'coreml' | 'tflite')
142
156
  * matching the engine-provisioner's own ModelFormat type.
143
157
  */
144
158
  function modelFormatFor(id) {
@@ -167,6 +181,7 @@ function shouldInstallOpenvino(env) {
167
181
  var PREFERENCE = [
168
182
  "coreml",
169
183
  "openvino",
184
+ "edgetpu",
170
185
  "onnx"
171
186
  ];
172
187
  /**
@@ -936,6 +951,9 @@ function serializeModelConfig(config) {
936
951
  postprocessor: config.postprocessor,
937
952
  confidence: config.confidence
938
953
  };
954
+ if (config.inputWidth !== void 0) result["inputWidth"] = config.inputWidth;
955
+ if (config.inputHeight !== void 0) result["inputHeight"] = config.inputHeight;
956
+ if (config.inputChannels !== void 0) result["inputChannels"] = config.inputChannels;
939
957
  if (config.labels) result["labels"] = config.labels;
940
958
  if (config.charset) result["charset"] = config.charset;
941
959
  if (config.numClasses) result["numClasses"] = config.numClasses;
@@ -1231,12 +1249,14 @@ var BACKEND_TO_POOL_RUNTIME = {
1231
1249
  openvino: "openvino",
1232
1250
  onnx: "onnxruntime",
1233
1251
  cpu: "onnxruntime",
1234
- cuda: "onnxruntime"
1252
+ cuda: "onnxruntime",
1253
+ edgetpu: "edgetpu"
1235
1254
  };
1236
1255
  var RUNTIME_TO_FORMAT = {
1237
1256
  coreml: "coreml",
1238
1257
  openvino: "openvino",
1239
- onnxruntime: "onnx"
1258
+ onnxruntime: "onnx",
1259
+ edgetpu: "tflite"
1240
1260
  };
1241
1261
  var EngineFactory = class {
1242
1262
  pool = null;
@@ -1355,13 +1375,15 @@ var EngineFactory = class {
1355
1375
  const concurrency = this.opts.concurrency ?? {
1356
1376
  coreml: 1,
1357
1377
  openvino: 1,
1358
- onnxruntime: 4
1378
+ onnxruntime: 4,
1379
+ edgetpu: 1
1359
1380
  }[poolRuntime];
1360
1381
  const isAutoDefault = this.opts.concurrency === void 0;
1361
1382
  const defaultBatchMode = {
1362
1383
  coreml: "window",
1363
1384
  openvino: "none",
1364
- onnxruntime: "list"
1385
+ onnxruntime: "list",
1386
+ edgetpu: "none"
1365
1387
  };
1366
1388
  const tuning = this.opts.tuning ?? {};
1367
1389
  const resolvedTuning = {
@@ -1399,7 +1421,10 @@ var EngineFactory = class {
1399
1421
  const urlParts = formatEntry.url.split("/");
1400
1422
  const filename = urlParts[urlParts.length - 1] ?? `${modelId}.${format}`;
1401
1423
  const modelPath = `${this.opts.modelsDir}/${filename}`;
1402
- const inputSize = Math.max(modelEntry.inputSize.width, modelEntry.inputSize.height);
1424
+ const inputWidth = modelEntry.inputSize.width;
1425
+ const inputHeight = modelEntry.inputSize.height;
1426
+ const inputSize = Math.max(inputWidth, inputHeight);
1427
+ const inputChannels = modelEntry.inputChannels ?? 3;
1403
1428
  let labels = def.labels;
1404
1429
  if (!labels && modelEntry.extraFiles) {
1405
1430
  const labelsFile = modelEntry.extraFiles.find((f) => f.filename.endsWith("-labels.json"));
@@ -1411,8 +1436,11 @@ var EngineFactory = class {
1411
1436
  return {
1412
1437
  path: modelPath,
1413
1438
  inputSize,
1439
+ inputWidth,
1440
+ inputHeight,
1441
+ inputChannels,
1414
1442
  preprocessMode: modelEntry.preprocessMode ?? "letterbox",
1415
- postprocessor: def.postprocessor,
1443
+ postprocessor: modelEntry.postprocessor ?? def.postprocessor,
1416
1444
  confidence: def.defaultConfidence,
1417
1445
  labels,
1418
1446
  charset: def.charset,
@@ -1611,6 +1639,11 @@ function postprocessYolo(output, stepDef) {
1611
1639
  detections: simpleNms(dets, .45)
1612
1640
  };
1613
1641
  }
1642
+ function postprocessSsd(output, _stepDef) {
1643
+ const structured = tryStructured(output);
1644
+ if (structured) return structured;
1645
+ throw new Error("SSD postprocessing runs in the Python inference pool (ssd.py); no TypeScript raw-tensor path.");
1646
+ }
1614
1647
  function postprocessScrfd(output, stepDef) {
1615
1648
  const structured = tryStructured(output);
1616
1649
  if (structured) return structured;
@@ -2033,6 +2066,7 @@ function postprocessYamnet(output, _stepDef) {
2033
2066
  }
2034
2067
  var DISPATCH = {
2035
2068
  yolo: postprocessYolo,
2069
+ ssd: postprocessSsd,
2036
2070
  "yolo-seg": postprocessYoloSeg,
2037
2071
  scrfd: postprocessScrfd,
2038
2072
  arcface: postprocessArcface,
@@ -2843,14 +2877,17 @@ var MIN_FACE_DETECTION_FLOOR_PX = 6;
2843
2877
  var NATIVE_FACE_CROP_MAX_WIDTH = 320;
2844
2878
  /**
2845
2879
  * StepOutput kinds that synthesize a full-frame detection when they run at
2846
- * the tree ROOT (see `synthesizeRootDetection`) — classifier/embedding steps
2847
- * promoted to root by `pipelineRunner.runDetailSubtree` (task 6b), e.g.
2848
- * `clip-embedding`, `animal-classifier`. Detector (`'detections'`) roots take
2849
- * the normal path; `'text'`/`'mask'` roots are out of scope and still
2850
- * `continue` with no output, matching pre-existing behaviour.
2880
+ * the tree ROOT (see `synthesizeRootDetection`) — classifier/embedding/OCR
2881
+ * steps promoted to root by `pipelineRunner.runDetailSubtree` (task 6b), e.g.
2882
+ * `clip-embedding`, `animal-classifier`, `plate-ocr`. Detector (`'detections'`)
2883
+ * roots take the normal path. `'text'` is included so a `plate-ocr` step run
2884
+ * standalone on an already-cut plate crop (parent class `plate`) synthesizes a
2885
+ * full-frame detection carrying the recognized text as a label, instead of
2886
+ * silently dropping — the OCR chain otherwise only surfaces text when nested
2887
+ * under `plate-detection`. `'mask'` roots remain out of scope.
2851
2888
  */
2852
2889
  function isEnrichmentOutput(output) {
2853
- return output.kind === "classifications" || output.kind === "embedding";
2890
+ return output.kind === "classifications" || output.kind === "embedding" || output.kind === "text";
2854
2891
  }
2855
2892
  var PipelineExecutor = class {
2856
2893
  opts;
@@ -3633,7 +3670,8 @@ function toProbedHardware(hw) {
3633
3670
  if (!hw) return null;
3634
3671
  return {
3635
3672
  npu: hw.npu ? { type: hw.npu.type } : null,
3636
- gpu: hw.gpu ? { type: hw.gpu.type } : null
3673
+ gpu: hw.gpu ? { type: hw.gpu.type } : null,
3674
+ coral: hw.coral ? { type: hw.coral.type } : null
3637
3675
  };
3638
3676
  }
3639
3677
  var ONNX_FLOOR = {
@@ -3739,16 +3777,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
3739
3777
  */
3740
3778
  loggedLiveDispatchIssues = /* @__PURE__ */ new Set();
3741
3779
  /**
3742
- * CB5 shm passthrough — the process-local, LONG-LIVED reader cache for
3743
- * `runPipeline({ frameHandle })`. Opens each named shm segment ONCE and
3744
- * reuses the mapping for every later handle on the same ring (never
3745
- * open/close per-call). Lazily created on first handle, unmapped in
3746
- * {@link shutdown}. The executor and the runner are both hub-local
3747
- * processes sharing `/dev/shm`, so a LOCAL handle resolves to a zero-copy
3748
- * read here.
3749
- */
3750
- frameHandleReaders = null;
3751
- /**
3752
3780
  * Lazy detection-engine runtime provisioner (Phase 2). Owns the
3753
3781
  * idle → installing → verifying → ready state machine for the
3754
3782
  * currently-selected engine and is the SOLE authority the inference
@@ -3945,14 +3973,15 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
3945
3973
  */
3946
3974
  resolvedEngineBackend() {
3947
3975
  const backend = this.currentEngine.backend;
3948
- return backend === "openvino" || backend === "coreml" ? backend : "onnx";
3976
+ return backend === "openvino" || backend === "coreml" || backend === "edgetpu" ? backend : "onnx";
3949
3977
  }
3950
3978
  /** Map a backend string to a known RuntimeId, flooring to onnx. */
3951
3979
  toRuntimeId(backend) {
3952
3980
  return [
3953
3981
  "onnx",
3954
3982
  "openvino",
3955
- "coreml"
3983
+ "coreml",
3984
+ "edgetpu"
3956
3985
  ].find((id) => id === backend) ?? "onnx";
3957
3986
  }
3958
3987
  /**
@@ -4615,35 +4644,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4615
4644
  } });
4616
4645
  return { success: true };
4617
4646
  }
4618
- /** This executor's cluster node id (suffix-stripped, matching the runner). */
4619
- ownNodeId() {
4620
- const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
4621
- return raw.includes("/") ? raw.split("/")[0] : raw;
4622
- }
4623
- /**
4624
- * CB5 — resolve a shm `FrameHandle` to pixels zero-copy. Returns `null`
4625
- * when:
4626
- * - the handle is FOREIGN (its segment lives on another node — a local
4627
- * `shm_open` would flood ENOENT; the runner only sends LOCAL handles, so
4628
- * this is a defensive guard against an unexpected topology);
4629
- * - the ring slot was recycled before the read (latest-wins drop) or the
4630
- * segment could not be mapped.
4631
- * A `null` degrades the caller to an empty `FrameResult` — the runner ships
4632
- * pixels via `frame` as the fallback.
4633
- */
4634
- resolveFrameHandle(handle) {
4635
- if (handle.nodeId !== this.ownNodeId()) return null;
4636
- if (!this.frameHandleReaders) this.frameHandleReaders = new _camstack_shm_ring.FrameRingReaderCache(this.log);
4637
- const decoded = this.frameHandleReaders.read(handle);
4638
- if (!decoded) return null;
4639
- return {
4640
- data: decoded.data,
4641
- width: decoded.width,
4642
- height: decoded.height,
4643
- format: decoded.format,
4644
- timestamp: decoded.timestamp
4645
- };
4646
- }
4647
4647
  /**
4648
4648
  * Minimal detection-free `FrameResult` returned when a `frameHandle` read
4649
4649
  * misses (recycled slot / foreign / unmappable). Shape-identical to a
@@ -4736,12 +4736,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4736
4736
  ].filter((s) => s !== null);
4737
4737
  if (sources.length === 0) throw new Error("runPipeline requires exactly one of: frame, frameHandle, image, imageBase64, referenceImage");
4738
4738
  if (sources.length > 1) throw new Error(`runPipeline received conflicting image sources: ${sources.join(", ")}`);
4739
- let handleFrame = null;
4740
- if (input.frameHandle) {
4741
- handleFrame = this.resolveFrameHandle(input.frameHandle);
4742
- if (!handleFrame) return this.emptyFrameHandleResult(input);
4743
- }
4744
- const runtimeFrame = input.frame ?? handleFrame ?? void 0;
4739
+ if (input.frameHandle) return this.emptyFrameHandleResult(input);
4740
+ const runtimeFrame = input.frame ?? void 0;
4745
4741
  const decodeT0 = performance.now();
4746
4742
  if (runtimeFrame) {
4747
4743
  const frame = runtimeFrame;
@@ -5496,8 +5492,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5496
5492
  this.executor = null;
5497
5493
  }
5498
5494
  for (const id of Array.from(this.deviceProxies.keys())) this.releaseDeviceProxy(id);
5499
- this.frameHandleReaders?.close();
5500
- this.frameHandleReaders = null;
5501
5495
  }
5502
5496
  /**
5503
5497
  * Resolve and cache a {@link DeviceProxy} for the given camera. Pins
@@ -6233,7 +6227,8 @@ var BACKEND_TO_FORMAT = {
6233
6227
  cpu: "onnx",
6234
6228
  coreml: "coreml",
6235
6229
  openvino: "openvino",
6236
- onnx: "onnx"
6230
+ onnx: "onnx",
6231
+ edgetpu: "tflite"
6237
6232
  };
6238
6233
  /**
6239
6234
  * Every runtime the registry knows about, in offer order. Used only to build
@@ -6244,7 +6239,8 @@ var BACKEND_TO_FORMAT = {
6244
6239
  var ALL_RUNTIME_IDS = [
6245
6240
  "onnx",
6246
6241
  "openvino",
6247
- "coreml"
6242
+ "coreml",
6243
+ "edgetpu"
6248
6244
  ];
6249
6245
  /**
6250
6246
  * Narrow an arbitrary stored backend string to a known `RuntimeId`. Legacy or
@@ -6712,6 +6708,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.B
6712
6708
  return {
6713
6709
  npu: hw.npu ? { type: hw.npu.type } : null,
6714
6710
  gpu: hw.gpu ? { type: hw.gpu.type } : null,
6711
+ coral: hw.coral ? { type: hw.coral.type } : null,
6715
6712
  platform: hw.platform,
6716
6713
  arch: hw.arch
6717
6714
  };