zensally [](https://github.com/imazen/zensally/actions/workflows/ci.yml) [](https://crates.io/crates/zensally) [](https://lib.rs/crates/zensally) [](https://docs.rs/zensally) [](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) [](#license)
June 28, 2026 · View on GitHub
zensally is face detection and neural saliency for content-aware image cropping — find the faces and the parts of an image people actually look at, then crop to any aspect ratio without cutting off the subject. The zensally crate is the shared, pure-Rust core: the result types, the detector traits, model preprocessing, non-maximum suppression, output decoding, and a bridge into zenlayout's smart-crop solver. Ready-to-run detectors with embedded ONNX models live in sibling backend crates that plug into those traits. Pure Rust, #![forbid(unsafe_code)].
The default detector path runs entirely in Rust via tract — no ONNX Runtime, no C dependency, models small enough to embed in the binary.
Quick start
The full smart-crop flow: detect faces and saliency, then compute crops for several aspect ratios from one analysis.
[dependencies]
zensally = { version = "0.1", features = ["zenlayout"] } # core toolkit + smart-crop bridge
# Batteries-included detectors with embedded models (workspace crate; consumed via git):
zensally-tract = { git = "https://github.com/imazen/zensally", features = ["analyzer"] }
zenlayout = { version = "0.2", features = ["smart-crop"] } # crop geometry solver
use zensally::{ImageRef, PixelFormat};
use zensally_tract::ContentAnalyzer; // UltraFace (faces) + MicroSalNet (saliency)
let mut analyzer = ContentAnalyzer::new()?; // loads the embedded ONNX models
let img = ImageRef::new(&rgba, width, height, PixelFormat::Rgba)?;
let analysis = analyzer.analyze(&img); // faces (percentage coords) + saliency heatmap
println!("found {} face(s)", analysis.faces.len());
use zensally::bridge::build_smart_crop_input;
use zenlayout::smart_crop::{AspectRatio, CropMode};
// Fold faces + saliency (and any manual focus regions) into one crop input:
let input = build_smart_crop_input(analysis, &[]);
// One crop per requested aspect ratio — Vec<Option<Rect>>:
let crops = input.compute_crops(width, height, &[
(AspectRatio { w: 1, h: 1 }, CropMode::Minimal),
(AspectRatio { w: 16, h: 9 }, CropMode::Minimal),
(AspectRatio { w: 9, h: 16 }, CropMode::Minimal),
]);
Coordinates returned in FaceRect are percentages (0–100) of image dimensions, so they survive a later resize. CropMode::Minimal keeps the subject visible at the largest crop; CropMode::Maximal zooms in tight.
What zensally provides
The published zensally crate is codec-, runtime-, and model-agnostic. It owns everything around the neural network except the network itself:
| Module | Contents |
|---|---|
| (root) | FaceRect, SaliencyMap, AnalysisOutput, ImageRef, PixelFormat, and the FaceDetector / SaliencyDetector traits |
preprocess | Bilinear resize to NCHW RGB f32 with Letterbox/Stretch modes and CenterScale/UnitScale/MeanSubtract normalization; returns LetterboxInfo for coordinate reversal |
nms | IoU and greedy non-maximum suppression over raw detections |
decode | Turn raw model output tensors into typed results (decode_ultraface, decode_microsalnet) |
bridge | From conversions and build_smart_crop_input into zenlayout's solver (feature zenlayout) |
Serializable records — DetectionSummary, SmartCropResult, WhitespaceCropResult, FocusRegion, CropRect — derive serde traits under the serde feature, handy for logs, UI overlays, and debugging.
Features
| Feature | Default | Effect |
|---|---|---|
std | yes | Standard library support |
serde | no | Serialize / Deserialize on the result types |
zenlayout | no | The bridge module and From impls into zenlayout::smart_crop |
Bring your own runtime
If you already run ONNX (or any other inference engine), use the core directly: preprocess into the model's input tensor, run your network, decode the outputs. No backend crate required.
use zensally::{ImageRef, PixelFormat};
use zensally::preprocess::{preprocess_nchw, ResizeMode, Normalization};
use zensally::decode::decode_ultraface;
// 1. Build the model's NCHW RGB f32 input (UltraFace RFB-320 is 320x240):
let mut input = vec![0.0f32; 3 * 320 * 240];
let lb = preprocess_nchw(
&rgba, width, height, PixelFormat::Rgba,
320, 240, ResizeMode::Letterbox, Normalization::CenterScale,
&mut input,
);
// 2. Run `input` through your ONNX runtime → `scores`, `boxes` output slices.
// 3. Decode to FaceRects (letterbox reversed, NMS applied):
let faces = decode_ultraface(
&scores, &boxes, 320.0, 240.0, &lb,
width as f32, height as f32,
0.7, // score threshold
0.3, // NMS IoU threshold
);
Or implement FaceDetector / SaliencyDetector over your engine and feed the results straight into the bridge.
Backends
Two crates implement the traits with embedded models so you don't have to wire up inference yourself:
| Crate | Inference | Notes |
|---|---|---|
zensally-tract | tract (pure-Rust ONNX, compiled in) | Models embedded as gzip'd bytes; no C dependency; #![forbid(unsafe_code)] |
zensally-zentract | zentract plugin (loaded at runtime) | Skips compiling tract; loads ONNX through libzentract_abi instead |
Both backends currently pull in git-only dependencies, so they're consumed via git = "…" rather than from crates.io.
Detectors (zensally-tract feature flags)
| Detector | Feature | Task |
|---|---|---|
UltraFaceDetector | ultraface (default) | Faces — UltraFace RFB-320, ~1 MB model, the recommended general-purpose detector |
MicroSalNet | microsalnet | Saliency — compact MobileNetV3-style encoder/decoder |
ContentAnalyzer | analyzer | Faces + saliency in one pass (UltraFace + MicroSalNet) |
BlazeFaceDetector | blazeface320 | Faces — BlazeFace-320 (heavier RetinaFace-style) |
MediaPipeBlazeFaceDetector | mediapipe | Faces — MediaPipe BlazeFace |
YuNetDetector | yunet | Faces — YuNet (anchor-free) |
U2NetpDetector | u2netp | Saliency — U²-Netp |
SelfieSeg | selfie_seg | Person segmentation matte |
zensally-zentract exposes the same UltraFaceDetector / MicroSalNet / ContentAnalyzer surface through the ultraface, microsalnet, and analyzer features.
Evaluation
The zensally-tract crate ships evaluation and benchmark examples. They depend on test corpora and are not part of the published package; run them from a clone:
git clone https://github.com/imazen/zensally && cd zensally
# WIDER FACE recall validation (downloads the dataset; also a CI job on main):
bash scripts/download_wider_face.sh
cargo run --release -p zensally-tract --example wider_validate
# Crop / saliency evaluation harnesses:
cargo run --release -p zensally-tract --features analyzer --example eval_crop
cargo run --release -p zensally-tract --features microsalnet --example eval_microsalnet
No benchmark numbers are quoted here — measure on your own hardware and corpus. New comparative benchmarks should follow the zen benchmarking conventions (no -C target-cpu=native, reproducible from the committed command).
License
Dual-licensed, your choice of either:
- AGPL-3.0-only — for open-source use, or
- Imazen Commercial License — for use in closed-source or proprietary products.
SPDX: AGPL-3.0-only OR LicenseRef-Imazen-Commercial. The embedded model weights originate from third-party projects and carry their own upstream terms; review them before redistribution.
Image tech I maintain
| Codecs ¹ | zenjpeg · zenpng · zenwebp · zengif · zenavif · zenjxl · zenbitmaps · heic · zentiff · zenpdf · zensvg · zenjp2 · zenraw · ultrahdr |
| Codec internals | zenjxl-decoder · jxl-encoder · zenrav1e · rav1d-safe · zenavif-parse · zenavif-serialize |
| Compression | zenflate · zenzop · zenzstd |
| Processing | zenresize · zenquant · zenblend · zenfilters · zensally · zentone |
| Pixels & color | zenpixels · zenpixels-convert · linear-srgb · garb |
| Pipeline & framework | zenpipe · zencodec · zencodecs · zenlayout · zennode · zenwasm · zentract |
| Metrics | zensim · fast-ssim2 · butteraugli · zenmetrics · resamplescope-rs |
| Pickers & ML | zenanalyze · zenpredict · zenpicker |
| Products | Imageflow image engine (.NET · Node · Go) · Imageflow Server · ImageResizer (C#) |
¹ pure-Rust, #![forbid(unsafe_code)] codecs, as of 2026
General Rust awesomeness
zenbench · archmage · magetypes · enough · whereat · cargo-copter