Local masking needs to know where an image's regions are. The watershed spike (S15 arm A) found the boundaries but had no idea what any of them enclosed; its coarse levels were geometric accidents. This adds the other half and the thing that joins them. `core/dr-segment` is where region reasoning now lives — the hierarchy moves out of `dr-gpu`, which keeps only the pixel passes that are genuinely shaders. The new crate is device-free and, without its default features, model-free too: 20 of its tests need neither an adapter nor 11 MB of weights. Arm B runs YOLO26n-seg through `ort`. D13 framed inference as a choice between `ort`'s C++ runtime and the pure-Rust dependency policy; that was a false choice. `ort`'s `alternative-backend` feature unlinks the C entirely and `ort-tract` supplies the API from tract, which is pure Rust. Measured before committing to it: zero unsupported operators, 420 ms for 640x640, and correct masks on bus.jpg. No NDK problem to solve, so D13's largest tolerated exception is not needed. Arm C is `prior.rs`, and it ships because the two arms fail in opposite directions. Instance membership re-weights the merge saddles, so region pairs the model believes share an object merge early and pairs straddling its edge merge late. No boundary moves — only the order in which they dissolve — which is how the result stays pixel-accurate at every level while its coarse levels become named things. Two things the spec assumed that turned out to be false, both recorded in models/LICENCE.md: there is no usable ADE20K-trained YOLO, so the shipped vocabulary is COCO's 80 subjects and *stuff* like sky and foliage must come from arm A; and tract cannot parse a dynamic-shape export, so the graph's input is fixed and tiling is the only route to more semantic resolution. Weights are AGPL-3.0, which GPLv3 §13 permits and which makes the combined work effectively AGPL. Deliberate, not accidental. They live in Git LFS, and a build script fails with an instruction rather than embedding a pointer file when the clone lacks them.
59 lines
2.3 KiB
Rust
59 lines
2.3 KiB
Rust
//! Check the model is a model and not an LFS pointer.
|
|
//!
|
|
//! `models/*.onnx` is stored in Git LFS (see `.gitattributes`). A clone made
|
|
//! without git-lfs installed, or with `GIT_LFS_SKIP_SMUDGE` set, leaves a
|
|
//! ~130-byte text pointer at that path instead of the weights.
|
|
//!
|
|
//! Without this check `include_bytes!` would happily embed the pointer, the
|
|
//! crate would compile, and the failure would surface much later as an opaque
|
|
//! ONNX parse error from inside tract — at which point the connection back to
|
|
//! a missing `git lfs pull` is not one anybody would make quickly. Failing
|
|
//! here costs one file read and turns that into a sentence.
|
|
|
|
use std::path::Path;
|
|
|
|
const MODEL: &str = "models/yolo26n-seg.onnx";
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-changed={MODEL}");
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
|
|
// Only the embedded path needs the file present; a build without it is
|
|
// watershed-only by choice and should not be blocked on weights.
|
|
if std::env::var_os("CARGO_FEATURE_EMBEDDED_MODEL").is_none() {
|
|
return;
|
|
}
|
|
|
|
let path = Path::new(MODEL);
|
|
let Ok(bytes) = std::fs::read(path) else {
|
|
panic!(
|
|
"\n\n{MODEL} is missing.\n\
|
|
It ships in Git LFS. Run `git lfs install && git lfs pull`, or build \
|
|
with `--no-default-features` for a watershed-only build.\n"
|
|
);
|
|
};
|
|
|
|
// ONNX is protobuf, which has no magic number; an LFS pointer is short
|
|
// ASCII beginning with a version URL. Testing for the pointer is the
|
|
// reliable direction — it has a known shape, where "valid protobuf" does
|
|
// not, and this check only has to catch the one failure that actually
|
|
// happens in practice.
|
|
if bytes.starts_with(b"version https://git-lfs") {
|
|
panic!(
|
|
"\n\n{MODEL} is a Git LFS pointer, not the model ({} bytes).\n\
|
|
Run `git lfs install && git lfs pull` to fetch the real file.\n",
|
|
bytes.len()
|
|
);
|
|
}
|
|
|
|
// A model that parses as a pointer-sized file is not one either. The real
|
|
// export is ~11 MB; anything under a megabyte is a truncated checkout.
|
|
if bytes.len() < 1_000_000 {
|
|
panic!(
|
|
"\n\n{MODEL} is only {} bytes — expected ~11 MB.\n\
|
|
The checkout looks incomplete; try `git lfs pull`.\n",
|
|
bytes.len()
|
|
);
|
|
}
|
|
}
|