A facial recognition login service for Linux.
2

Configure Feed

Select the types of activity you want to include in your feed.

core,nix: Add OnnxSession + pinned model weights behind inference feature

The real Detector and Embedder implementations need ONNX Runtime; this
commit wires up the dependency, the Nix devShell, the pinned model
weights, and a thin session wrapper they'll build on top. The actual
SCRFD detector and ArcFace embedder land in subsequent sub-milestones
(M3.3, M3.5).

Feature shape mirrors v4l2: `pareidolia-core` keeps `inference` opt-in
(default = []); the daemon enables it via its own default-on `inference`
feature that proxies to core. CI's --no-default-features keeps libv4l +
libonnxruntime out of the Nixery image while normal builds get the full
pipeline.

`ort`'s `load-dynamic` feature is the cleanest fit for Nix: the crate
dlopens libonnxruntime at runtime via ORT_DYLIB_PATH rather than linking
against it at build time. The devShell wires ORT_DYLIB_PATH to nixpkgs's
`.so` so both build and run work without bundling C++ libs.

`OnnxSession` exposes the underlying `ort::Session` directly rather
than wrapping every ort API behind a Pareidolia-flavoured alternative —
chasing ort's API as it evolves is more churn than the abstraction is
worth. The boundary is at error types: `ort::Error` is mapped to
`PipelineError::Backend` so the rest of the pipeline stays
backend-agnostic.

Model weights: the M6 plan had Nix-driven model fetching land alongside
the NixOS module, but the M3 sub-milestones need real ONNX files for
their hardware tests, so the fetching infrastructure moves forward.
`nix/models.nix` pins SCRFD-10G and ArcFace glintr100 from the
`immich-app/buffalo_l` HuggingFace mirror (a maintained mirror of the
InsightFace bundle, stable since 2023). The devShell exports
PAREIDOLIA_TEST_{MODEL,SCRFD,ARCFACE} pointing at the materialised store
paths so `just test-hardware` runs end-to-end with no manual setup.

The pinned weights are also exposed as flake packages
(`nix build .#scrfd-10g`) for ad-hoc use outside the devShell.

Isaac Corbrey (May 27, 2026, 12:29 PM EDT) 69bfbdd7 f1eda04a

+380 -25
+36 -11
CONTRIBUTING.md
··· 122 122 runner skips them. CI never runs them — it has no hardware. Locally: 123 123 124 124 ```sh 125 - just test-v4l2 # runs v4l2 module's hardware-free tests 125 + just test-features # runs v4l2 + inference unit tests (no hardware) 126 126 just test-hardware # runs only the ignored / hardware-gated tests 127 127 ``` 128 128 129 129 If you touch a hardware-facing module (`pareidolia_core::camera::v4l2`, 130 - later the inference backends, etc.), run `just test-hardware` on your 131 - machine before requesting review. The PR description should call out 132 - which physical devices the tests were exercised against. 130 + the inference backends, etc.), run `just test-hardware` on your machine 131 + before requesting review. The PR description should call out which 132 + physical devices and model files the tests were exercised against. 133 + 134 + #### Model weights 135 + 136 + ONNX model weights are pinned in `nix/models.nix` and fetched on demand 137 + by Nix (fixed-sha256, sandbox-safe). The devShell exports 138 + `PAREIDOLIA_TEST_MODEL` / `PAREIDOLIA_TEST_SCRFD` / `PAREIDOLIA_TEST_ARCFACE` 139 + pointing at the materialised files, so `just test-hardware` inside 140 + `nix develop` runs the model-loading tests with no manual setup. 141 + 142 + Outside Nix, fetch the files manually (see `nix/models.nix` for the 143 + upstream URLs) and set the env vars yourself. 144 + 145 + The pinned models can also be materialised standalone: 146 + 147 + ```sh 148 + nix build .#scrfd-10g # → ./result symlink to the .onnx 149 + nix build .#arcface-glintr100 150 + ``` 133 151 134 152 ### Feature flags 135 153 136 - `pareidolia-daemon` defaults to the `v4l2` feature so a normal 137 - `cargo build` (or `nix build`) produces a working binary with camera 138 - capture wired in. `cargo build --no-default-features` opts out — what CI 139 - uses, since its Nixery image doesn't ship libv4l / libclang / kernel 154 + `pareidolia-daemon` and `pareidolia-cli` default to a set of features that 155 + make a normal `cargo build` (or `nix build`) produce working binaries with 156 + camera capture and inference wired in: 157 + 158 + - `v4l2` — Linux V4L2 capture (needs libv4l + bindgen prerequisites in 159 + the build environment). 160 + - `inference` — ONNX Runtime inference (needs libonnxruntime; the devShell 161 + sets `ORT_DYLIB_PATH` to nixpkgs's `.so`). 162 + 163 + `cargo build --no-default-features` opts out of both — what CI uses, since 164 + its Nixery image doesn't ship libv4l, libonnxruntime, libclang, or kernel 140 165 headers. CI threads this through via the `CARGO_ARGS` env var that the 141 166 justfile recipes read. 142 167 143 - `pareidolia-core` itself keeps `v4l2` opt-in (default features are empty) 144 - so consumers that only need the pipeline (mocks, format conversions, IPC 145 - types) don't pay the build cost. 168 + `pareidolia-core` itself keeps both features opt-in (default features are 169 + empty) so consumers that only need the pipeline abstractions (mocks, format 170 + conversions, IPC types) don't pay the build cost. 146 171 147 172 Note that `[workspace.dependencies]` sets `default-features = false` on 148 173 every internal crate. This is load-bearing: `cargo --workspace` unifies
+125
Cargo.lock
··· 379 379 checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" 380 380 381 381 [[package]] 382 + name = "matrixmultiply" 383 + version = "0.3.10" 384 + source = "registry+https://github.com/rust-lang/crates.io-index" 385 + checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" 386 + dependencies = [ 387 + "autocfg", 388 + "rawpointer", 389 + ] 390 + 391 + [[package]] 382 392 name = "memchr" 383 393 version = "2.8.1" 384 394 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 391 401 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 392 402 393 403 [[package]] 404 + name = "ndarray" 405 + version = "0.16.1" 406 + source = "registry+https://github.com/rust-lang/crates.io-index" 407 + checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" 408 + dependencies = [ 409 + "matrixmultiply", 410 + "num-complex", 411 + "num-integer", 412 + "num-traits", 413 + "portable-atomic", 414 + "portable-atomic-util", 415 + "rawpointer", 416 + ] 417 + 418 + [[package]] 394 419 name = "nom" 395 420 version = "7.1.3" 396 421 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 401 426 ] 402 427 403 428 [[package]] 429 + name = "num-complex" 430 + version = "0.4.6" 431 + source = "registry+https://github.com/rust-lang/crates.io-index" 432 + checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 433 + dependencies = [ 434 + "num-traits", 435 + ] 436 + 437 + [[package]] 438 + name = "num-integer" 439 + version = "0.1.46" 440 + source = "registry+https://github.com/rust-lang/crates.io-index" 441 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 442 + dependencies = [ 443 + "num-traits", 444 + ] 445 + 446 + [[package]] 404 447 name = "num-traits" 405 448 version = "0.2.19" 406 449 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 422 465 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 423 466 424 467 [[package]] 468 + name = "ort" 469 + version = "2.0.0-rc.10" 470 + source = "registry+https://github.com/rust-lang/crates.io-index" 471 + checksum = "1fa7e49bd669d32d7bc2a15ec540a527e7764aec722a45467814005725bcd721" 472 + dependencies = [ 473 + "libloading", 474 + "ndarray", 475 + "ort-sys", 476 + "smallvec", 477 + "tracing", 478 + ] 479 + 480 + [[package]] 481 + name = "ort-sys" 482 + version = "2.0.0-rc.10" 483 + source = "registry+https://github.com/rust-lang/crates.io-index" 484 + checksum = "e2aba9f5c7c479925205799216e7e5d07cc1d4fa76ea8058c60a9a30f6a4e890" 485 + dependencies = [ 486 + "pkg-config", 487 + ] 488 + 489 + [[package]] 425 490 name = "pareidolia-cli" 426 491 version = "0.0.0" 427 492 dependencies = [ ··· 434 499 name = "pareidolia-core" 435 500 version = "0.0.0" 436 501 dependencies = [ 502 + "ndarray", 503 + "ort", 437 504 "proptest", 438 505 "self_cell", 439 506 "thiserror", ··· 469 536 version = "0.1.2" 470 537 source = "registry+https://github.com/rust-lang/crates.io-index" 471 538 checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 539 + 540 + [[package]] 541 + name = "pin-project-lite" 542 + version = "0.2.17" 543 + source = "registry+https://github.com/rust-lang/crates.io-index" 544 + checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 545 + 546 + [[package]] 547 + name = "pkg-config" 548 + version = "0.3.33" 549 + source = "registry+https://github.com/rust-lang/crates.io-index" 550 + checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" 551 + 552 + [[package]] 553 + name = "portable-atomic" 554 + version = "1.13.1" 555 + source = "registry+https://github.com/rust-lang/crates.io-index" 556 + checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" 557 + 558 + [[package]] 559 + name = "portable-atomic-util" 560 + version = "0.2.7" 561 + source = "registry+https://github.com/rust-lang/crates.io-index" 562 + checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" 563 + dependencies = [ 564 + "portable-atomic", 565 + ] 472 566 473 567 [[package]] 474 568 name = "ppv-lite86" ··· 583 677 ] 584 678 585 679 [[package]] 680 + name = "rawpointer" 681 + version = "0.2.1" 682 + source = "registry+https://github.com/rust-lang/crates.io-index" 683 + checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" 684 + 685 + [[package]] 586 686 name = "regex" 587 687 version = "1.12.3" 588 688 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 716 816 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 717 817 718 818 [[package]] 819 + name = "smallvec" 820 + version = "2.0.0-alpha.10" 821 + source = "registry+https://github.com/rust-lang/crates.io-index" 822 + checksum = "51d44cfb396c3caf6fbfd0ab422af02631b69ddd96d2eff0b0f0724f9024051b" 823 + 824 + [[package]] 719 825 name = "strsim" 720 826 version = "0.11.1" 721 827 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 763 869 "proc-macro2", 764 870 "quote", 765 871 "syn", 872 + ] 873 + 874 + [[package]] 875 + name = "tracing" 876 + version = "0.1.44" 877 + source = "registry+https://github.com/rust-lang/crates.io-index" 878 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 879 + dependencies = [ 880 + "pin-project-lite", 881 + "tracing-core", 882 + ] 883 + 884 + [[package]] 885 + name = "tracing-core" 886 + version = "0.1.36" 887 + source = "registry+https://github.com/rust-lang/crates.io-index" 888 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 889 + dependencies = [ 890 + "once_cell", 766 891 ] 767 892 768 893 [[package]]
+7
Cargo.toml
··· 46 46 # CLI argument parsing for `pareidolia`. `derive` is what gives us the 47 47 # `#[derive(Parser)]` / `#[derive(Subcommand)]` ergonomics. 48 48 clap = { version = "4", features = ["derive"] } 49 + # ONNX Runtime bindings. `load-dynamic` makes ort dlopen libonnxruntime at 50 + # runtime via ORT_DYLIB_PATH rather than linking against it at build time; 51 + # the Nix devShell sets that env var to point at nixpkgs's onnxruntime so 52 + # both build and run work without bundling the C++ library. CI opts out of 53 + # the inference feature entirely via --no-default-features. 54 + ort = { version = "=2.0.0-rc.10", default-features = false, features = ["load-dynamic"] } 55 + ndarray = "0.16" 49 56 50 57 # Test-only deps. These never appear in the binaries we ship; only in the 51 58 # dev-dependencies of crates that own tests.
+8
crates/core/Cargo.toml
··· 23 23 # need capture (cli, pam, integration-tests) don't pay the libv4l / bindgen 24 24 # build cost. 25 25 v4l2 = ["dep:v4l", "dep:self_cell"] 26 + # Enables the ONNX Runtime-backed inference modules. Same shape as v4l2: 27 + # off by default in pareidolia-core; the daemon enables it via its own 28 + # default-on `inference` feature. Disabling at the cargo command line 29 + # (--no-default-features) skips ort entirely so CI doesn't need 30 + # libonnxruntime in its Nixery image. 31 + inference = ["dep:ort", "dep:ndarray"] 26 32 27 33 [dependencies] 28 34 thiserror.workspace = true 29 35 v4l = { workspace = true, optional = true } 30 36 self_cell = { workspace = true, optional = true } 37 + ort = { workspace = true, optional = true } 38 + ndarray = { workspace = true, optional = true } 31 39 32 40 [dev-dependencies] 33 41 proptest.workspace = true
+3
crates/core/src/pipeline.rs
··· 22 22 23 23 pub mod mock; 24 24 25 + #[cfg(feature = "inference")] 26 + pub mod onnx; 27 + 25 28 /// A 2D point in image-space pixel coordinates. Floating-point because 26 29 /// detector outputs are subpixel-accurate; we round only at the alignment 27 30 /// stage where we sample from the source image.
+129
crates/core/src/pipeline/onnx.rs
··· 1 + //! Thin wrapper around an [`ort::Session`]. 2 + //! 3 + //! [`OnnxSession`] is what the real [`Detector`] and [`Embedder`] 4 + //! implementations build on top of — it owns the loaded model, exposes the 5 + //! input/output metadata our pipeline needs to validate shapes, and maps 6 + //! `ort`'s error types into [`PipelineError`] so the rest of the pipeline 7 + //! stays backend-agnostic. 8 + //! 9 + //! The model file is loaded from a local path. In production, that path 10 + //! resolves to a fixed-hash Nix derivation output (M6); in development and 11 + //! tests, callers point it at a hand-managed file. Loading is synchronous 12 + //! and can take hundreds of milliseconds for the larger face models; the 13 + //! daemon should do this at startup, not per request. 14 + //! 15 + //! [`Detector`]: super::Detector 16 + //! [`Embedder`]: super::Embedder 17 + 18 + use std::path::Path; 19 + 20 + use ort::session::Session; 21 + 22 + use crate::pipeline::PipelineError; 23 + 24 + /// An owned ONNX Runtime session backed by a model file on disk. 25 + /// 26 + /// Sessions are `Send` but not `Sync` (their internal state is mutated on 27 + /// every `run`); the daemon's intended pattern is one session per model 28 + /// per worker, not shared across threads concurrently. 29 + #[derive(Debug)] 30 + pub struct OnnxSession { 31 + session: Session, 32 + } 33 + 34 + impl OnnxSession { 35 + /// Load an ONNX model from disk. 36 + /// 37 + /// Returns [`PipelineError::Backend`] for any failure — file not found, 38 + /// invalid ONNX, missing operators in the runtime, etc. The message is 39 + /// the underlying `ort` error formatted as a string; we deliberately 40 + /// don't expose `ort`'s concrete error type so the rest of the 41 + /// pipeline can stay backend-agnostic. 42 + pub fn from_file(path: impl AsRef<Path>) -> Result<Self, PipelineError> { 43 + let path = path.as_ref(); 44 + let session = Session::builder() 45 + .map_err(map_ort_err)? 46 + .commit_from_file(path) 47 + .map_err(map_ort_err)?; 48 + Ok(Self { session }) 49 + } 50 + 51 + /// Names of the model's input tensors in declaration order. Useful for 52 + /// validating that a caller is providing the inputs the model actually 53 + /// expects rather than silently feeding the wrong tensor to the wrong 54 + /// slot. 55 + pub fn input_names(&self) -> Vec<String> { 56 + self.session.inputs.iter().map(|i| i.name.clone()).collect() 57 + } 58 + 59 + /// Names of the model's output tensors in declaration order. 60 + pub fn output_names(&self) -> Vec<String> { 61 + self.session 62 + .outputs 63 + .iter() 64 + .map(|o| o.name.clone()) 65 + .collect() 66 + } 67 + 68 + /// Borrow the underlying [`ort::session::Session`]. 69 + /// 70 + /// Real detector / embedder implementations need direct access to run 71 + /// inference with their specific input shapes and decode their specific 72 + /// output layouts. Keeping this `pub` rather than wrapping every ort 73 + /// API behind a Pareidolia-flavoured alternative means we don't have to 74 + /// chase ort's API surface as it evolves. 75 + pub fn session(&self) -> &Session { 76 + &self.session 77 + } 78 + 79 + /// Mutable borrow of the session. `Session::run` takes `&mut self` so 80 + /// callers running inference need this. 81 + pub fn session_mut(&mut self) -> &mut Session { 82 + &mut self.session 83 + } 84 + } 85 + 86 + /// Map any `ort::Error` (and the `ort::Error` shape varies by version) 87 + /// into [`PipelineError::Backend`] with the original message preserved. 88 + fn map_ort_err<E: std::fmt::Display>(e: E) -> PipelineError { 89 + PipelineError::Backend(e.to_string()) 90 + } 91 + 92 + #[cfg(test)] 93 + mod tests { 94 + use super::*; 95 + 96 + #[test] 97 + fn missing_model_file_returns_backend_error_not_panic() { 98 + let err = OnnxSession::from_file("/nonexistent/model.onnx") 99 + .expect_err("nonexistent file should error"); 100 + assert!( 101 + matches!(err, PipelineError::Backend(_)), 102 + "wrong error variant: {err:?}", 103 + ); 104 + } 105 + 106 + /// Loads a real ONNX model. Requires `PAREIDOLIA_TEST_MODEL` env var 107 + /// pointing at an .onnx file. Skipped by default; opt in via 108 + /// `just test-hardware` or 109 + /// `PAREIDOLIA_TEST_MODEL=/path/to/model.onnx cargo test ... -- --ignored`. 110 + #[test] 111 + #[ignore = "requires a real ONNX model file via PAREIDOLIA_TEST_MODEL"] 112 + fn loads_real_model_and_exposes_io_names() { 113 + let Ok(path) = std::env::var("PAREIDOLIA_TEST_MODEL") else { 114 + panic!( 115 + "PAREIDOLIA_TEST_MODEL not set; should be skipped via #[ignore] \ 116 + unless an env-provided model path is available" 117 + ); 118 + }; 119 + let session = OnnxSession::from_file(&path).expect("load real model"); 120 + assert!( 121 + !session.input_names().is_empty(), 122 + "real model should have at least one input", 123 + ); 124 + assert!( 125 + !session.output_names().is_empty(), 126 + "real model should have at least one output", 127 + ); 128 + } 129 + }
+5 -1
crates/daemon/Cargo.toml
··· 19 19 path = "src/main.rs" 20 20 21 21 [features] 22 - default = ["v4l2"] 22 + default = ["v4l2", "inference"] 23 23 # Re-exports pareidolia-core's `v4l2` feature so building the daemon 24 24 # defaults to including the V4L2 backend. CI passes `--no-default-features` 25 25 # to skip it (its Nixery image doesn't ship libv4l). 26 26 v4l2 = ["pareidolia-core/v4l2"] 27 + # Re-exports pareidolia-core's `inference` feature so the daemon ships 28 + # with the ONNX-backed inference pipeline. CI's --no-default-features 29 + # disables it (Nixery image doesn't ship libonnxruntime). 30 + inference = ["pareidolia-core/inference"] 27 31 28 32 [dependencies] 29 33 pareidolia-core = { workspace = true, default-features = false }
+26 -3
flake.nix
··· 22 22 extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; 23 23 }; 24 24 25 + models = import ./nix/models.nix { inherit (pkgs) fetchurl; }; 26 + 25 27 # Native build inputs needed at compile time. M1 has no third-party 26 28 # crate deps; this list will grow as milestones land (onnxruntime, 27 29 # v4l, pam, etc). ··· 42 44 # capture; without this in buildInputs the crate's build script 43 45 # can't find its system headers. 44 46 v4l-utils 47 + 48 + # ONNX Runtime. The `ort` crate uses `load-dynamic` so it dlopens 49 + # libonnxruntime via ORT_DYLIB_PATH at runtime; we wire that in 50 + # shellHook below. 51 + onnxruntime 45 52 ]; 46 53 in 47 54 { ··· 59 66 60 67 # Local builds keep incremental compilation for fast iteration. 61 68 # CI sets CARGO_INCREMENTAL=0 to avoid bloating runner caches. 69 + # 70 + # ORT_DYLIB_PATH tells the `ort` crate's load-dynamic mode where 71 + # to find libonnxruntime at runtime. CI doesn't set it because 72 + # CI builds with --no-default-features (no ort dep at all). 73 + # 74 + # PAREIDOLIA_TEST_* point at the Nix-fetched model weights so 75 + # `just test-hardware` runs without manual model setup. 62 76 shellHook = '' 63 77 export RUST_BACKTRACE=1 78 + export ORT_DYLIB_PATH=${pkgs.onnxruntime}/lib/libonnxruntime.so 79 + export PAREIDOLIA_TEST_MODEL=${models.scrfd_10g} 80 + export PAREIDOLIA_TEST_SCRFD=${models.scrfd_10g} 81 + export PAREIDOLIA_TEST_ARCFACE=${models.arcface_glintr100} 64 82 ''; 65 83 }; 66 84 67 - # Package outputs land in M6 alongside the NixOS module. The M1 flake 68 - # only exposes the devShell so `nix develop` works. 69 - packages = { }; 85 + # Package outputs. The pareidolia binaries themselves land alongside 86 + # the NixOS module in M6; for now we expose the pinned model weights 87 + # so `nix build .#scrfd-10g` (etc.) materialises the file into the 88 + # store on demand for ad-hoc use outside the devShell. 89 + packages = { 90 + scrfd-10g = models.scrfd_10g; 91 + arcface-glintr100 = models.arcface_glintr100; 92 + }; 70 93 71 94 checks = { 72 95 # `nix flake check` is wired up properly in M1 by re-exporting the
+9 -10
justfile
··· 37 37 cargo nextest run --workspace --lib {{cargo_args}} 38 38 39 39 # Hardware-dependent tests (marked #[ignore]). Requires real devices on the 40 - # host: a V4L2 camera at /dev/video*, eventually a NIC for IPC, etc. Enables 41 - # every hardware-touching feature (currently just `v4l2`) so the gated 42 - # modules actually compile. 40 + # host: a V4L2 camera at /dev/video*, and for the inference tests a model 41 + # file path in PAREIDOLIA_TEST_MODEL. 43 42 # 44 43 # CI never runs this; it's the developer's responsibility to run locally 45 44 # before merging anything that touches a hardware-facing surface. 46 45 test-hardware: 47 - cargo nextest run -p pareidolia-core --features v4l2 --run-ignored only 46 + cargo nextest run -p pareidolia-core --features v4l2,inference --run-ignored only 48 47 49 - # Unit tests with the v4l2 feature enabled. Runs the FourCC mapping tests and 50 - # the always-on portions of the V4L2 backend without touching real hardware. 51 - # Locally only; CI doesn't have libv4l / libclang configured in its Nixery 52 - # image. 53 - test-v4l2: 54 - cargo nextest run -p pareidolia-core --features v4l2 --lib 48 + # Unit tests with all default-on features enabled. Runs the always-on 49 + # portions of the v4l2 and inference modules without touching hardware. 50 + # Locally only; CI doesn't have libv4l / libonnxruntime configured in its 51 + # Nixery image. 52 + test-features: 53 + cargo nextest run -p pareidolia-core --features v4l2,inference --lib 55 54 56 55 # Everything the per-push CI job runs. 57 56 ci-fast: fmt-check lint test-unit
+32
nix/models.nix
··· 1 + { fetchurl }: 2 + 3 + # Pretrained ONNX model weights consumed by the inference pipeline. 4 + # 5 + # Source: https://huggingface.co/immich-app/buffalo_l — a maintained mirror 6 + # of the InsightFace buffalo_l bundle (SCRFD-10G for detection, ArcFace 7 + # glintr100 for recognition) packaged as standalone ONNX files. Immich is a 8 + # popular self-hosted photo manager and the mirror has been stable since 9 + # 2023, so the host is reasonable to depend on. 10 + # 11 + # Hashes are pinned (sha256, hex). Bump them in lockstep with any URL 12 + # change; Nix's sandbox will refuse a fetch that doesn't match. 13 + 14 + rec { 15 + # Face detector. SCRFD-10G in ONNX, ~16 MB. Outputs 9 tensors across 3 16 + # stride levels (8/16/32), each with a score, bbox, and 5-point landmark 17 + # head. M3.3 implements the post-processing. 18 + scrfd_10g = fetchurl { 19 + name = "scrfd-10g-buffalo_l.onnx"; 20 + url = "https://huggingface.co/immich-app/buffalo_l/resolve/main/detection/model.onnx"; 21 + sha256 = "5838f7fe053675b1c7a08b633df49e7af5495cee0493c7dcf6697200b85b5b91"; 22 + }; 23 + 24 + # Face embedder. ArcFace glintr100 in ONNX, ~166 MB. Input is a 112×112 25 + # RGB face chip; output is a 512-dimensional float vector. M3.5 wires it 26 + # in. 27 + arcface_glintr100 = fetchurl { 28 + name = "arcface-glintr100-buffalo_l.onnx"; 29 + url = "https://huggingface.co/immich-app/buffalo_l/resolve/main/recognition/model.onnx"; 30 + sha256 = "4c06341c33c2ca1f86781dab0e829f88ad5b64be9fba56e56bc9ebdefc619e43"; 31 + }; 32 + }