{
  "format": "odb-inspection/1",
  "executed": false,
  "source": {
    "path": "components/ultrasound/convex-open-image/0.1.0/src/lib.rs",
    "sha256": "499ac1bc5020b389c0185711335204949dceb09726656ee9e8eecda853565aa9",
    "build": "node scripts/build-open-image-wasm.ts; npm run components:build",
    "files": [
      {
        "path": "components/ultrasound/convex-open-image/0.1.0/Cargo.toml",
        "sha256": "2a9d33c0c2ce37af7164c815fde16f1944105f34112e123b1217cc8cf1da4807",
        "text": "[package]\nname = \"probe-open-image\"\nversion = \"0.1.0\"\nedition = \"2024\"\nlicense = \"AGPL-3.0-or-later\"\npublish = false\n[lib]\ncrate-type = [\"rlib\", \"cdylib\"]\n[dependencies]\nodb-convex-decoder = { path = \"../../convex-decoder/1.0.0\", default-features = false }\nserde_json = \"1\"\nsha2 = \"0.10\"\n[profile.release]\nopt-level = 3\nlto = true\nstrip = true\npanic = \"abort\"\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.0/Cargo.lock",
        "sha256": "7b6a9a3bb1fa2d432dbacfefe926fed0383e80f536fec5a8b00da4fd65e11e24",
        "text": "# This file is automatically @generated by Cargo.\n# It is not intended for manual editing.\nversion = 4\n\n[[package]]\nname = \"block-buffer\"\nversion = \"0.10.4\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71\"\ndependencies = [\n \"generic-array\",\n]\n\n[[package]]\nname = \"cfg-if\"\nversion = \"1.0.5\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600\"\n\n[[package]]\nname = \"cpufeatures\"\nversion = \"0.2.17\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280\"\ndependencies = [\n \"libc\",\n]\n\n[[package]]\nname = \"crypto-common\"\nversion = \"0.1.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a\"\ndependencies = [\n \"generic-array\",\n \"typenum\",\n]\n\n[[package]]\nname = \"digest\"\nversion = \"0.10.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292\"\ndependencies = [\n \"block-buffer\",\n \"crypto-common\",\n]\n\n[[package]]\nname = \"generic-array\"\nversion = \"0.14.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a\"\ndependencies = [\n \"typenum\",\n \"version_check\",\n]\n\n[[package]]\nname = \"itoa\"\nversion = \"1.0.18\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682\"\n\n[[package]]\nname = \"libc\"\nversion = \"0.2.191\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a\"\n\n[[package]]\nname = \"memchr\"\nversion = \"2.8.3\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98\"\n\n[[package]]\nname = \"odb-convex-decoder\"\nversion = \"1.0.0\"\ndependencies = [\n \"serde_json\",\n \"sha2\",\n]\n\n[[package]]\nname = \"probe-open-image\"\nversion = \"0.1.0\"\ndependencies = [\n \"odb-convex-decoder\",\n \"serde_json\",\n \"sha2\",\n]\n\n[[package]]\nname = \"proc-macro2\"\nversion = \"1.0.107\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9\"\ndependencies = [\n \"unicode-ident\",\n]\n\n[[package]]\nname = \"quote\"\nversion = \"1.0.47\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001\"\ndependencies = [\n \"proc-macro2\",\n]\n\n[[package]]\nname = \"serde\"\nversion = \"1.0.229\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba\"\ndependencies = [\n \"serde_core\",\n]\n\n[[package]]\nname = \"serde_core\"\nversion = \"1.0.229\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48\"\ndependencies = [\n \"serde_derive\",\n]\n\n[[package]]\nname = \"serde_derive\"\nversion = \"1.0.229\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348\"\ndependencies = [\n \"proc-macro2\",\n \"quote\",\n \"syn\",\n]\n\n[[package]]\nname = \"serde_json\"\nversion = \"1.0.154\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6\"\ndependencies = [\n \"itoa\",\n \"memchr\",\n \"serde\",\n \"serde_core\",\n \"zmij\",\n]\n\n[[package]]\nname = \"sha2\"\nversion = \"0.10.9\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283\"\ndependencies = [\n \"cfg-if\",\n \"cpufeatures\",\n \"digest\",\n]\n\n[[package]]\nname = \"syn\"\nversion = \"3.0.9\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b\"\ndependencies = [\n \"proc-macro2\",\n \"quote\",\n \"unicode-ident\",\n]\n\n[[package]]\nname = \"typenum\"\nversion = \"1.20.1\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20\"\n\n[[package]]\nname = \"unicode-ident\"\nversion = \"1.0.27\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563\"\n\n[[package]]\nname = \"version_check\"\nversion = \"0.9.5\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a\"\n\n[[package]]\nname = \"zmij\"\nversion = \"1.0.23\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b\"\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.0/README.md",
        "sha256": "a9d336cd9e773549c816c1c293930498d74286cb21d93f16344e867bb31b581b",
        "text": "# Original UC5-2S reconstruction profile\n\nAn experimental, resource-free image profile implemented in Rust under\nAGPL-3.0-or-later. Native Linux and browser WASM use the same `OpenSession`\nand the shared `odb-convex-decoder` processing library. All processing inputs\nare byte slices in memory. Hosts provide transport, storage and display.\n\nThis component reconstructs already acquired DATA streams. It does not\ninitialize or activate a probe. A complete autonomous acquisition profile\nremains separate work; this release is not a plug-and-scan application.\n\n## Generated profile\n\nThe symmetric 16-tap filter is the binomial polynomial\n`(1 + z^-1)^15 / 2^15`, generated using integer recurrence. Display defaults\nare -8 dB gain, 48 dB dynamic range, flat gain versus sample index,\ntemporal persistence 0.25 and generated identity grayscale. Adaptive compound\nmixing and spatial enhancement are disabled. These are original display\nchoices, not calibrated tissue or transducer parameters.\n\nRendering uses a chosen normalized 60-degree sector with unit radius and\n512 × 512 opaque RGBA pixels. This is not a measurement of the probe's angle\nor depth. The decoder accepts the observed mode0/P896/N32 DATA format,\n1800-word lines and four u16 lanes; no physical sampling rate is inferred.\nThere is no claim of equivalence to another image profile or metric calibration.\n\n## Build and native use\n\nFrom the repository root, with Rust 1.99.0 and its `wasm32-wasip1` target installed:\n\n```sh\nnode scripts/build-open-image-wasm.ts\nnode scripts/build-open-image-wasm.ts --check\nnpm run components:build\ncargo test --locked --manifest-path components/ultrasound/convex-open-image/0.1.0/Cargo.toml\n```\n\nThe optional `replay_open` native binary accepts a recorded capture directory\nand a new output directory. It reads `receive/data/manifest.json` and the\nreferenced raw reads, then writes PNGs and their hashes. No configuration,\nbootstrap responses or device access are needed for reconstruction.\nBoth WASM builds pin Rust 1.99.0 and normalize repository and Cargo-cache paths.\nThe locked Rust compiler adapter also normalizes Cargo's metadata for the two\nlocal crates, retaining their names, versions, targets and enabled features.\nOther dependency identities and all numerical compiler options are preserved.\nIts source hash selects a separate build cache so an adapter change cannot reuse\nan older compilation. This avoids host-dependent function ordering in the WASM.\nCI tests the shared library and original profile and rebuilds both artifacts.\nBinary identity is\nchecked with the same locked dependencies and Rust toolchain; it is not a claim\nof identical output across different compiler versions.\n\nNative library callers use `OpenSession::new(Display::default())`,\n`push(&[u8])`, `advance(&[u8])` and `finish()`. `Display` accepts bounded finite\ngain, dynamic range, sample-index gain slope and persistence settings.\n\n## Browser use\n\n```ts\nconst decoder = await createProbeDecoder(wasmBytes, undefined, {\n  purpose: \"reconstruction\",\n  continuous: true,\n});\nconst frames = decoder.pushRgba(recordedDataBytes);\n// Feed remaining bytes in order, then call decoder.finishRgba().\n```\n\nInitialization requires explicit reconstruction purpose. Acquisition/default\ninitialization and hardware command binding fail. ABI operations 2/5 return\nPNG batches, 9/10 RGBA batches, 11 encodes an RGBA snapshot and 12 advances\nprocessing histories without rasterizing. No resource file, mounted directory,\nnetwork access or USB import is required. Maximum WASM memory is 256 MiB.\nThe `ultrasound/convex-replay` application selects reconstruction explicitly and\nreads local `.bin`, `.odb-capture` and `.odb-stream` files. The recording source\ndoes not expose USB or execute archived journal commands. Continuous JSONL\nrecordings are read incrementally; legacy JSON containers are limited to96MiB.\nShared processing sources are pinned in `shared-sources.json` and checked before\nbuilding the WASM. The registry also pins this lock file with the component.\n\n## Validation scope\n\nTwo preserved phantom acquisitions provide 45 complete reference frames.\nReplay counts remain 9 and 36; native/WASM RGB differences are bounded by\n2 intensity levels out of 255. Image appearance intentionally differs from\nthe earlier reference profile. Those measurements validate reconstruction\ncontinuity and cross-target consistency, not acoustic safety, calibration or\ndiagnostic performance. No new physical capture was required.\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.0/src/bin/replay_open.rs",
        "sha256": "5e0aaa40cbdeb706426a55677be33bbfcef5c027003ced94daab684a22a4425d",
        "text": "// SPDX-License-Identifier: AGPL-3.0-or-later\nuse probe_open_image::{Display, HEIGHT, OpenSession, WIDTH, manifest};\nuse serde_json::{Value, json};\nuse sha2::{Digest, Sha256};\nuse std::{env, fs, path::Path};\nfn main() -> Result<(), Box<dyn std::error::Error>> {\n    let a: Vec<_> = env::args().skip(1).collect();\n    if a.len() != 2 {\n        return Err(\"Usage: replay_open archived-capture new-output-dir\".into());\n    }\n    let input = Path::new(&a[0]).join(\"receive/data\");\n    let out = Path::new(&a[1]);\n    fs::create_dir(out)?;\n    let cfg = Display::default();\n    let mut session = OpenSession::new(cfg)?;\n    let reads: Value = serde_json::from_slice(&fs::read(input.join(\"manifest.json\"))?)?;\n    let mut rows = Vec::new();\n    let mut save =\n        |frames: Vec<odb_convex_decoder::RgbaFrame>| -> Result<(), Box<dyn std::error::Error>> {\n            for f in frames {\n                let name = format!(\"{:06}.png\", f.sequence);\n                let png = odb_convex_decoder::encode_rgba_png(WIDTH, HEIGHT, &f.rgba)?;\n                fs::write(out.join(&name), &png)?;\n                rows.push(json!({\"sequence\":f.sequence,\"file\":name,\"sha256\":format!(\"{:x}\",Sha256::digest(&png)),\"rgba_sha256\":format!(\"{:x}\",Sha256::digest(&f.rgba))}));\n            }\n            Ok(())\n        };\n    for row in reads[\"reads\"].as_array().ok_or(\"Missing read manifest\")? {\n        let name = row[\"raw_file\"].as_str().ok_or(\"Missing raw filename\")?;\n        if Path::new(name).components().count() != 1 {\n            return Err(\"Invalid raw filename\".into());\n        }\n        save(session.push(&fs::read(input.join(name))?)?)?;\n    }\n    save(session.finish()?)?;\n    fs::write(out.join(\"images.json\"), serde_json::to_vec_pretty(&rows)?)?;\n    fs::write(\n        out.join(\"profile.json\"),\n        serde_json::to_vec_pretty(&manifest(cfg)?)?,\n    )?;\n    println!(\n        \"{} images; original generated display profile; no configuration or USB\",\n        rows.len()\n    );\n    Ok(())\n}\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.0/shared-sources.json",
        "sha256": "5d3b280d009c7cb2ddea2f923a36877636a69b03c93b50e2a84d6c2f54212fca",
        "text": "{\n  \"format\": \"odb-source-lock/1\",\n  \"component\": \"ultrasound/convex-decoder@1.0.0\",\n  \"files\": [\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/lib.rs\",\n      \"sha256\": \"93a7d80de814a9fdde3bdc131a8deb3980b3ff7fe399e2e5143bf8064228cfda\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/Cargo.lock\",\n      \"sha256\": \"a89f9d40168858def7e4ee35a1bba63208b8322e3a7ce64270157c2730f44165\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/Cargo.toml\",\n      \"sha256\": \"7f927acd5159c79e9e5cd6e86da164fb52b8ecc0ff9f3676186c7e99899ce91c\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/source-origin.json\",\n      \"sha256\": \"2105fe63c1c9ad1f565f0343e16abc23636a0e5a47b621979de8f140ff78d516\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/admission.rs\",\n      \"sha256\": \"5aba1902cdaa2a8aeed10737896ee186ca8dcbc69d57b4cff92977c093557269\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/archive.rs\",\n      \"sha256\": \"098ebf2b90a99e35d507fc8765184fc1f68631afb96382a2a2138f5104b409f6\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_bfsp.rs\",\n      \"sha256\": \"9c078685c2ba936c640ca1f62d1cf45107e78e7d3040a284f2f60a20ae8d4c4f\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_command.rs\",\n      \"sha256\": \"480fbc33846874ae68b0396cb2913f69f7ec199b90b873060af4946a09cb01bd\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_compound.rs\",\n      \"sha256\": \"a87d04c92d51de21aec911267eb73c4ecccdcac75bef13c362b3b3e4f7967359\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_configuration.rs\",\n      \"sha256\": \"dbdc8df42ad13c65de76e15ca08f767bdce12b79e4667845f94f58902cd61ef8\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_dsc.rs\",\n      \"sha256\": \"1cfa44b12ba6b8b7d8b8f768460ca6e04a475444e0d58c14cae797aa49461e0d\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_dtgc.rs\",\n      \"sha256\": \"dcd7fc2a0aaecdbf6c724689343b99fa00ad32f7d1eb7346023133ff32310e18\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_group.rs\",\n      \"sha256\": \"d88d35ce2c5d7b1821f9f305379c11805c0dfb7c3dbdda64da80a07f5c75a7d3\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_lifecycle.rs\",\n      \"sha256\": \"3e0b28199d0e95074fd454d06dbd9f83f309bab0f1a3fab38d7fe703b069175e\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_messages.rs\",\n      \"sha256\": \"aa00a808c67b7daec150fb1fddf6121e9e38a3bf69f9d407b265d2b892bc4db2\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_parameters.rs\",\n      \"sha256\": \"27dcc979d239197a2767d16783450c6c78aef4e89744e14167b965a8c74ea961\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_pipeline.rs\",\n      \"sha256\": \"4fb4925ede9fc66f4fb0292fae144b25822a515b17c60f8567dcb339f33fb11e\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_producers.rs\",\n      \"sha256\": \"41a50d9b9d549088c2caf6e1eff19531ae443df986427bee824190522a08e9f8\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_sri.rs\",\n      \"sha256\": \"74057b9a8cdc0c1012ae40390bb832f05866a07fff2f90234e8ac5c73e9a6d4d\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_sri_plan.rs\",\n      \"sha256\": \"fabc74491f2b537dcfb50bc9c66045e4d962b2ca0d1391d596e32b9e1bdea57d\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/framing.rs\",\n      \"sha256\": \"74c4e02ba50a5035517f7b1a7f4023a8ea259af7b92cb0b139e5853d707d653a\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/known_convex_assets.json\",\n      \"sha256\": \"700e1c948dfb673980c38fba239bed9a3b01bff558f0d56ee17f1e10343fbd0f\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/known_convex_producer_assets.json\",\n      \"sha256\": \"207ec2321a7c01de1b5462a3e61c0b46f09c9dc9e2d9dbcb3db748186fbdbd0a\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/known_convex_sri_layout.json\",\n      \"sha256\": \"4cafbc69a11b93c19d04d390aa1989d5a241df0d928d027ebe7632bf1921f396\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/linear_dsp.rs\",\n      \"sha256\": \"d711bf2d2221c23f4213a260e2429d2fac3a086d8fc3078d1acad0f8982cca38\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/linear_palette.rs\",\n      \"sha256\": \"0b05f3f2d247222a0f730ee3f361646231c3cb351606bf62adf864788fdf4bc2\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/linear_post.rs\",\n      \"sha256\": \"55bc8d05a021a34ff7da6d9f0aaab3e658a0018e875c78f914d0c8bcbaa069e6\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/rgba_png.rs\",\n      \"sha256\": \"4d91a880687db02ebdaad20b63e28e653af8b15f943a577c34961ef70d3ff8ad\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/resources.rs\",\n      \"sha256\": \"c8dcb773217ae1a6391a6092b902ebedb94bb19691202f24b07e307804aef3e4\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/examples/replay_memory.rs\",\n      \"sha256\": \"2409ba64d2a8470976030b020075799aea4782834104567bd6cdf12c1b17edb3\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/selected_profile.rs\",\n      \"sha256\": \"4c0c5b3e451a42a0ce930bdf51d31f031c318f39b66ffd19d8c194b28cb9bf3b\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/build/reproducible-rustc.rs\",\n      \"sha256\": \"35ad737748a656197b08b1033df4432bd9a0c444955275e4f9ec68d3c09344c1\"\n    }\n  ]\n}\n"
      }
    ],
    "text": "// SPDX-License-Identifier: AGPL-3.0-or-later\n//! Original display profile for the observed UC5-2S DATA format.\n//! No device configuration, TX, ADC or HV data. No calibration or acoustic units.\nuse odb_convex_decoder::{ImageProfile, Result, RgbaFrame, Session};\nuse serde_json::{Value, json};\nuse std::cell::RefCell;\npub const PROFILE: &str = \"uc5-normalized-binomial-v1\";\npub const WIDTH: usize = 512;\npub const HEIGHT: usize = 512;\n#[derive(Clone, Copy)]\npub struct Display {\n    pub gain_db: f32,\n    pub dynamic_range_db: f32,\n    pub tgc_slope_db: f32,\n    pub persistence: f32,\n}\nimpl Default for Display {\n    fn default() -> Self {\n        // Display choices, not measured tissue attenuation or probe calibration.\n        Self {\n            gain_db: -8.,\n            dynamic_range_db: 48.,\n            tgc_slope_db: 0.,\n            persistence: 0.25,\n        }\n    }\n}\nimpl Display {\n    fn validate(self) -> Result<()> {\n        if !self.gain_db.is_finite()\n            || !(-60. ..=60.).contains(&self.gain_db)\n            || !self.dynamic_range_db.is_finite()\n            || !(12. ..=120.).contains(&self.dynamic_range_db)\n            || !self.tgc_slope_db.is_finite()\n            || !(-60. ..=60.).contains(&self.tgc_slope_db)\n            || !self.persistence.is_finite()\n            || !(0. ..=0.9).contains(&self.persistence)\n        {\n            return Err(\"Display settings out of bounded finite domain\".into());\n        }\n        Ok(())\n    }\n}\n/// Half of the symmetric length16 binomial FIR: coefficients of (1+z^-1)^15/2^15.\n/// Integer construction is exact on native and WASM, with unity DC gain.\npub fn binomial16_half() -> [f32; 8] {\n    let mut c = 1u32;\n    std::array::from_fn(|i| {\n        if i > 0 {\n            c = c * (16 - i as u32) / i as u32;\n        }\n        c as f32 / 32768.\n    })\n}\npub fn configuration() -> Value {\n    // These are observed DATA-format facts; they do not request hardware settings.\n    json!({\"parameters\":{\"A\":2,\"B\":0,\"C\":4,\"D\":4,\"E\":4},\n      \"grouping\":{\"schema_version\":\"convex-b-grouping-settings-v1\",\"b_capacity_bytes\":2097152,\"m_capacity_bytes\":2097152,\"m_line_count\":1,\"bm_header_26\":0,\"bm_header_28\":0,\"epoch_policy\":\"new-organizer-per-observed-epoch-v1\"},\n      \"settings\":{\"schema_version\":\"convex-pipeline-settings-v1\",\n        \"bfsp\":{\"schema_version\":\"convex-bfsp-settings-v1\",\"depth_index\":0,\"selector_index\":0,\"line_words\":1800},\n        \"dtgc\":{\"schema_version\":\"convex-dtgc-settings-v1\",\"bank_selector_0x48\":0,\"gain\":0.,\"dynamic_range\":48.,\"tgc_controls\":[0.,0.,0.,0.,0.,0.]},\n        \"sri\":{\"enhancement\":0,\"correlation_index\":0},\n        \"dsc\":{\"schema_version\":\"convex-dsc-settings-v1\",\"depth_index\":0,\"width\":WIDTH,\"height\":HEIGHT,\"gray_map\":0,\"tint_map\":0},\"max_groups\":128}})\n}\npub fn image_profile(display: Display) -> Result<ImageProfile> {\n    display.validate()?;\n    let mut settings = configuration()[\"settings\"].clone();\n    settings[\"dtgc\"][\"dynamic_range\"] = json!(display.dynamic_range_db);\n    // The rendering sector is a chosen normalized view, NOT a measured scan angle.\n    let theta = std::f32::consts::PI / 3.;\n    let mut parameters = [0u8; 40];\n    for (i, v) in [\n        (2, WIDTH as u32),\n        (3, HEIGHT as u32),\n        (6, 1f32.to_bits()),\n        (7, theta.to_bits()),\n        (8, 1f32.to_bits()),\n        (9, (theta / 2.).cos().to_bits()),\n    ] {\n        parameters[4 * i..4 * i + 4].copy_from_slice(&v.to_le_bytes());\n    }\n    let tgc = std::array::from_fn(|sample| display.tgc_slope_db * (sample as f32 / 1023.));\n    Ok(ImageProfile {\n        settings,\n        bfsp_coefficients: binomial16_half(),\n        // 1800words ->900 ushort4 vectors ->768 valid FIR positions ->3072 upsampled values.\n        // Reduce those3072 values to the core's1024 samples. No physical sampling clock inferred.\n        bfsp_downsample: 3,\n        dtgc_tgc: tgc,\n        // Required core bookkeeping range; this scalar is unused by pixel evaluation.\n        dtgc_depth: 2.,\n        dtgc_gain: display.gain_db,\n        dtgc_dr: 255. / display.dynamic_range_db,\n        // Disable adaptive compound mixing; retain the shared processing/lifecycle implementation.\n        compound_window: 1,\n        compound_coef0: 0.,\n        compound_threshold_a: 0.,\n        compound_threshold_b: 1.,\n        temporal_correlation: display.persistence,\n        dsc_parameters: parameters,\n        display_map: std::array::from_fn(|i| (i / 4) as u8),\n    })\n}\npub fn manifest(display: Display) -> Result<Value> {\n    display.validate()?;\n    Ok(\n        json!({\"profile\":PROFILE,\"license\":\"AGPL-3.0-or-later\",\"purpose\":\"reconstruction-only\",\n        \"input_format\":\"observed mode0/P896/N32, line_words1800, four u16 envelope lanes\",\n        \"filter\":\"(1+z^-1)^15 / 2^15; 16tap binomial, generated by integer recurrence\",\n        \"gain_db\":display.gain_db,\"dynamic_range_db\":display.dynamic_range_db,\n        \"tgc\":\"slope_db * sample_index /1023; default flat\",\"tgc_slope_db\":display.tgc_slope_db,\n        \"temporal_persistence\":display.persistence,\"palette\":\"identity grayscale, generated\",\n        \"geometry\":\"chosen normalized60degree sector, unit radius; no metric scale\",\n        \"alpha\":\"output is opaque; core's fourth LUT component is an intensity index\",\n        \"width\":WIDTH,\"height\":HEIGHT,\"transmit_commands\":0,\"external_resource_files\":0,\n        \"calibrated\":false,\"vendor_profile_equivalence_claimed\":false}),\n    )\n}\npub struct OpenSession {\n    engine: Session,\n}\nimpl OpenSession {\n    pub fn new(display: Display) -> Result<Self> {\n        let profile = image_profile(display)?;\n        let mut conf = configuration();\n        conf[\"settings\"] = profile.settings.clone();\n        let mut engine = Session::from_profile(&conf, profile)?;\n        engine.allow_continuous();\n        Ok(Self { engine })\n    }\n    pub fn push(&mut self, bytes: &[u8]) -> Result<Vec<RgbaFrame>> {\n        let frames = self.engine.push_rgba(bytes)?;\n        Ok(opaque(frames))\n    }\n    pub fn finish(&mut self) -> Result<Vec<RgbaFrame>> {\n        let frames = self.engine.finish_rgba()?;\n        Ok(opaque(frames))\n    }\n    /// Update all processing histories without rasterizing discarded frames.\n    pub fn advance(&mut self, bytes: &[u8]) -> Result<()> {\n        self.engine.advance(bytes)\n    }\n}\nfn opaque(mut frames: Vec<RgbaFrame>) -> Vec<RgbaFrame> {\n    for f in &mut frames {\n        for pixel in f.rgba.as_chunks_mut::<4>().0 {\n            pixel[3] = 255;\n        }\n    }\n    frames\n}\nthread_local! {static ENGINE:RefCell<Option<OpenSession>>=const{RefCell::new(None)};static OUTPUT:RefCell<Vec<u8>>=const{RefCell::new(Vec::new())};}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_builtin_profile() -> u32 {\n    1\n}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_live_api() -> u32 {\n    1\n}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_alloc(n: usize) -> *mut u8 {\n    if n > 4194304 {\n        return std::ptr::null_mut();\n    }\n    Box::into_raw(vec![0u8; n].into_boxed_slice()) as *mut u8\n}\n/// # Safety\n/// Pointer/length must describe the exact live allocation returned by odb_alloc.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn odb_free(p: *mut u8, n: usize) {\n    if !p.is_null() {\n        unsafe {\n            drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(p, n)));\n        }\n    }\n}\nfn encode(frames: Vec<RgbaFrame>, png: bool) -> Result<Vec<u8>> {\n    let mut out = (frames.len() as u32).to_le_bytes().to_vec();\n    for f in frames {\n        let b = if png {\n            odb_convex_decoder::encode_rgba_png(WIDTH, HEIGHT, &f.rgba)?\n        } else {\n            f.rgba\n        };\n        out.extend(f.sequence.to_le_bytes());\n        out.extend((b.len() as u32).to_le_bytes());\n        out.extend(b);\n    }\n    Ok(out)\n}\nfn initialize(bytes: &[u8]) -> Result<Vec<u8>> {\n    let opts: Value = serde_json::from_slice(bytes).map_err(|e| e.to_string())?;\n    let obj = opts\n        .as_object()\n        .ok_or(\"Require explicit reconstruction options\")?;\n    if obj.len() != 1 || obj.get(\"purpose\") != Some(&json!(\"reconstruction\")) {\n        return Err(\"This original profile supports reconstruction only; USB acquisition is not implemented\".into());\n    }\n    let session = OpenSession::new(Display::default())?;\n    ENGINE.with(|s| *s.borrow_mut() = Some(session));\n    let mut v = manifest(Display::default())?;\n    v[\"ready\"] = json!(true);\n    Ok(serde_json::to_vec(&v).unwrap())\n}\n/// # Safety\n/// Pointer/length must identify initialized readable bytes for the duration of the call.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn odb_call(op: u32, p: *const u8, n: usize) -> i32 {\n    if p.is_null() || n > 4194304 {\n        return -1;\n    }\n    let bytes = unsafe { std::slice::from_raw_parts(p, n) };\n    let result = match op {\n        7 | 8 => initialize(bytes),\n        2 | 9 => ENGINE\n            .with(|s| {\n                s.borrow_mut()\n                    .as_mut()\n                    .ok_or(\"Initialize reconstruction first\")?\n                    .push(bytes)\n            })\n            .and_then(|f| encode(f, op == 2)),\n        5 | 10 => ENGINE\n            .with(|s| {\n                s.borrow_mut()\n                    .as_mut()\n                    .ok_or(\"Initialize reconstruction first\")?\n                    .finish()\n            })\n            .and_then(|f| encode(f, op == 5)),\n        11 => odb_convex_decoder::encode_rgba_png(WIDTH, HEIGHT, bytes),\n        12 => ENGINE\n            .with(|s| {\n                s.borrow_mut()\n                    .as_mut()\n                    .ok_or(\"Initialize reconstruction first\")?\n                    .advance(bytes)\n            })\n            .and_then(|()| encode(Vec::new(), false)),\n        4 => Err(\n            \"This original profile cannot produce probe configuration or transmit commands\".into(),\n        ),\n        _ => Err(\"Unsupported reconstruction operation\".into()),\n    };\n    let (status, b) = match result {\n        Ok(b) => (0, b),\n        Err(e) => (-1, e.into_bytes()),\n    };\n    OUTPUT.with(|o| *o.borrow_mut() = b);\n    status\n}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_output_ptr() -> *const u8 {\n    OUTPUT.with(|o| o.borrow().as_ptr())\n}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_output_len() -> usize {\n    OUTPUT.with(|o| o.borrow().len())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    #[test]\n    fn binomial_filter_has_unit_dc_gain_and_zero_nyquist_response() {\n        let half = binomial16_half();\n        let full: Vec<_> = half.iter().chain(half.iter().rev()).copied().collect();\n        assert_eq!(full.iter().sum::<f32>(), 1.);\n        assert_eq!(\n            full.iter()\n                .enumerate()\n                .map(|(i, v)| if i % 2 == 0 { *v } else { -*v })\n                .sum::<f32>(),\n            0.\n        );\n        assert!(full.iter().all(|c| *c > 0.));\n        // Analytic binomial magnitude at omega=pi/2: cos(pi/4)^15.\n        let (re, im) = full\n            .iter()\n            .enumerate()\n            .fold((0f64, 0f64), |(re, im), (n, &c)| {\n                let a = n as f64 * std::f64::consts::FRAC_PI_2;\n                (re + c as f64 * a.cos(), im - c as f64 * a.sin())\n            });\n        assert!((re.hypot(im) - 2f64.powf(-7.5)).abs() < 1e-12);\n    }\n    #[test]\n    fn display_inputs_are_finite_bounded_and_never_activate_hardware() {\n        for invalid in [\n            Display {\n                gain_db: f32::NAN,\n                ..Display::default()\n            },\n            Display {\n                dynamic_range_db: 0.,\n                ..Display::default()\n            },\n            Display {\n                persistence: 1.,\n                ..Display::default()\n            },\n            Display {\n                tgc_slope_db: f32::INFINITY,\n                ..Display::default()\n            },\n        ] {\n            assert!(image_profile(invalid).is_err());\n        }\n        assert!(initialize(br#\"{}\"#).is_err());\n        assert!(initialize(br#\"{\"purpose\":\"acquisition\"}\"#).is_err());\n        assert!(initialize(br#\"{\"purpose\":\"reconstruction\",\"gain\":0}\"#).is_err());\n        assert!(initialize(br#\"{\"purpose\":\"reconstruction\"}\"#).is_ok());\n        let b = [0u8; 512];\n        let status = unsafe { odb_call(4, b.as_ptr(), b.len()) };\n        assert_eq!(status, -1);\n        OUTPUT.with(|o| assert!(String::from_utf8_lossy(&o.borrow()).contains(\"cannot produce\")));\n    }\n}\n"
  },
  "behavior": "Explicit reconstruction-only initialization; binomial FIR, adjustable native display settings, generated grayscale and normalized sector. No external resource files, hardware configuration, USB or network access. Shared Rust processing library for native/WASM. Recording host requires explicit reconstruction purpose. Shared processing sources are pinned by shared-sources.json. No metric calibration or clinical equivalence claim.",
  "ports": {
    "bytes": {
      "direction": "in",
      "kind": "stream",
      "contract": "odb.usb.bytes/1"
    },
    "frames": {
      "direction": "out",
      "kind": "stream",
      "contract": "odb.video.frame/1"
    }
  },
  "parameters": {},
  "fixtures": [],
  "evidence": [],
  "wasm": {
    "imports": [
      {
        "module": "wasi_snapshot_preview1",
        "name": "environ_get",
        "kind": "function"
      },
      {
        "module": "wasi_snapshot_preview1",
        "name": "environ_sizes_get",
        "kind": "function"
      },
      {
        "module": "wasi_snapshot_preview1",
        "name": "fd_write",
        "kind": "function"
      },
      {
        "module": "wasi_snapshot_preview1",
        "name": "proc_exit",
        "kind": "function"
      }
    ],
    "exports": [
      {
        "name": "memory",
        "kind": "memory"
      },
      {
        "name": "odb_alloc",
        "kind": "function"
      },
      {
        "name": "odb_builtin_profile",
        "kind": "function"
      },
      {
        "name": "odb_call",
        "kind": "function"
      },
      {
        "name": "odb_free",
        "kind": "function"
      },
      {
        "name": "odb_output_len",
        "kind": "function"
      },
      {
        "name": "odb_output_ptr",
        "kind": "function"
      },
      {
        "name": "odb_live_api",
        "kind": "function"
      }
    ],
    "signature": "odb_call(i32,i32,i32) -> i32",
    "maximum_memory_bytes": 268435456,
    "bytes": 505744,
    "executed": false
  }
}