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:

ModuleContents
(root)FaceRect, SaliencyMap, AnalysisOutput, ImageRef, PixelFormat, and the FaceDetector / SaliencyDetector traits
preprocessBilinear resize to NCHW RGB f32 with Letterbox/Stretch modes and CenterScale/UnitScale/MeanSubtract normalization; returns LetterboxInfo for coordinate reversal
nmsIoU and greedy non-maximum suppression over raw detections
decodeTurn raw model output tensors into typed results (decode_ultraface, decode_microsalnet)
bridgeFrom 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

FeatureDefaultEffect
stdyesStandard library support
serdenoSerialize / Deserialize on the result types
zenlayoutnoThe 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:

CrateInferenceNotes
zensally-tracttract (pure-Rust ONNX, compiled in)Models embedded as gzip'd bytes; no C dependency; #![forbid(unsafe_code)]
zensally-zentractzentract 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)

DetectorFeatureTask
UltraFaceDetectorultraface (default)Faces — UltraFace RFB-320, ~1 MB model, the recommended general-purpose detector
MicroSalNetmicrosalnetSaliency — compact MobileNetV3-style encoder/decoder
ContentAnalyzeranalyzerFaces + saliency in one pass (UltraFace + MicroSalNet)
BlazeFaceDetectorblazeface320Faces — BlazeFace-320 (heavier RetinaFace-style)
MediaPipeBlazeFaceDetectormediapipeFaces — MediaPipe BlazeFace
YuNetDetectoryunetFaces — YuNet (anchor-free)
U2NetpDetectoru2netpSaliency — U²-Netp
SelfieSegselfie_segPerson 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:

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 internalszenjxl-decoder · jxl-encoder · zenrav1e · rav1d-safe · zenavif-parse · zenavif-serialize
Compressionzenflate · zenzop · zenzstd
Processingzenresize · zenquant · zenblend · zenfilters · zensally · zentone
Pixels & colorzenpixels · zenpixels-convert · linear-srgb · garb
Pipeline & frameworkzenpipe · zencodec · zencodecs · zenlayout · zennode · zenwasm · zentract
Metricszensim · fast-ssim2 · butteraugli · zenmetrics · resamplescope-rs
Pickers & MLzenanalyze · zenpredict · zenpicker
ProductsImageflow 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

Open source · @imazen · @lilith · lib.rs/~lilith