wsi-rs is a Rust whole-slide image reader. It opens TIFF-family WSI,
including ARGOS and Huron, DICOM VL WSI, Zeiss CZI/ZVI, MIRAX, Hamamatsu VMS/VMU, Olympus VSI/ETS, raw
JPEG 2000 codestream fixtures, and .svcache containers. JPEG, JPEG 2000,
and HTJ2K decode is delegated to the
J2K pure-Rust JPEG 2000 codec
crates. JPEG XR decoding uses the separate JXR codec.
The main crate denies unsafe code by default, with a narrowly scoped,
audited exception for Metal interoperability.
Unsupported or incomplete sources return WsiError; they should not silently
produce black or partial pixels.
cargo add wsi-rsSupported architectures are x86_64 and aarch64. The JPEG backend in the required j2k 0.10 series does not support 32-bit targets.
use wsi_rs::{RegionRequest, Slide, TileRequest};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let slide = Slide::open("sample.svs")?;
let region = RegionRequest::builder(0usize, 0usize, 0u32)
.origin_px((0, 0))
.size_px((1024, 1024))
.build()?;
slide.read_region_rgba(®ion)?.save("region.png")?;
let tile = TileRequest::builder(0usize, 0usize, 0u32).tile(0, 0).build()?;
let cpu_tile = slide.read_tile(&tile)?;
println!("{}x{}", cpu_tile.width(), cpu_tile.height());
Ok(())
}Use SlideOpenOptions for explicit cache budgets, read-through .svcache
lookup, custom registries, region limits, or decode execution settings.
Every slide also has checked hostile-input limits. The defaults are 128 MiB
aggregate metadata, 16 MiB per metadata value, 128 MiB each for tile indexes,
encoded units, and decoded outputs, 33,554,432 pixels/128 MiB RGBA per region,
384 MiB transient work per operation, 512 MiB in flight per slide, and a
256 MiB internal batch-chunk target. Oversized batches are processed in order;
only an individually oversized tile, associated image, or region is rejected.
Use SlideLimits through SlideOpenOptions::with_limits to choose stricter
checked limits.
wsi-rs presents a format-independent Slide façade backed by a
SlideReader. Format modules own metadata validation, tile or frame lookup,
and source I/O; the shared core owns typed requests, byte-bounded caches,
decode policy, and CPU output types. Unsupported or invalid input
returns a typed WsiError instead of silently producing a partial tile.
The detailed internal ownership map is in
docs/architecture.md.
Batch reads preserve request order and cardinality. The default controlled-read
adapter checks cancellation, submits the complete request slice once, validates
the result count with WsiError::BackendContract, and checks cancellation again
before returning. Format-specific implementations may group internal I/O, but
they restore results to the original request slots. Cancellation is terminal for
that attempt and is not reinterpreted as a codec error or a reason to fall back
from device output to CPU. JPEG and JPEG 2000 kernels already running remain
non-preemptive.
Encapsulated DICOM images lazily share one validated frame index between reads
and prepare_level_controlled. The normal indexer seeks over compressed
payloads, prefers a valid Extended Offset Table, and otherwise validates the
Basic Offset Table and Item headers. Unusual supported layouts retain a token
parser fallback. Index construction checks counts, monotonic offsets, arithmetic,
file bounds, frame lengths, and NumberOfFrames; it publishes only complete
indexes and creates no sidecar files.
SlideOpenOptions owns cache configuration. The shared source-tile and display
composition caches are byte-bounded LRUs, while narrowly scoped format caches
cover source-specific data such as DICOM frame bytes. Those private caches share
one aggregate allocation derived from the configured source-tile budget; excess
per-image or per-shard caches remain disabled instead of preallocating outside
that policy. Cache effects completed by a legacy reader are not rolled back when
a controlled read is cancelled, but a cancelled result is not returned to the
caller. .svcache freshness policies stream the complete source through SHA-256,
so opting into cache resolution performs a full source-file read; implicit
.svcache resolution remains disabled by default.
Default retained caches are capped at 128 MiB per slide: 64 MiB for decoded source tiles, 32 MiB for display tiles, and 32 MiB across private and full-decode caches. Explicit cache settings may choose a different budget.
Metal and CUDA features accelerate JP2K/HTJ2K CPU reads automatically when a
measured device route, including readback, wins by at least 15%. The ordinary
API always returns CpuTile and falls back safely. Expert
read_tile(s)_metal and read_tile(s)_cuda methods are strict: they support
only JP2K/HTJ2K and either return typed resident tiles or an error. Both tile
types provide checked, pitch-aware download_cpu() staging.
Controlled-read diagnostics are opt-in and delivered outside internal locks.
The library emits operational events through tracing, but installs no
subscriber and owns no application UI or JSONL output.
For viewer zoom/pan debugging on tiled SVS inputs:
RUST_LOG=wsi_rs=debug WSI_RS_TILE_CACHE_BYTES=134217728 \
WSI_RS_DISPLAY_TILE_CACHE_BYTES=67108864 your-viewerWSI_RS_TILE_CACHE_BYTES controls the shared decoded source-tile cache and
WSI_RS_DISPLAY_TILE_CACHE_BYTES controls display-tile composition cache
capacity. The debug logs include cache hit/miss summaries for region/display
tiles and timing for TIFF/SVS JPEG tile batches when the host application
installs a tracing subscriber.
Build cache files with:
cargo run --release --bin svcache -- build sample.svs --out sample.svs.svcache| Input family | Typical paths |
|---|---|
| TIFF-family WSI (including ARGOS and Huron) and uncompressed RGB TIFF | .svs, .tif, .tiff, .ndpi, .scn, .bif, .avs |
| DICOM VL WSI | .dcm files or a DICOM series directory |
| Zeiss CZI (single-plane brightfield) and ZVI | .czi, .zvi |
| MIRAX | .mrxs plus sibling data files |
| Hamamatsu VMS/VMU | .vms, .vmu plus sibling JPEG (VMS) or NGR (VMU) files |
| Olympus VSI | .vsi plus matching ETS companion data |
| Raw JPEG 2000 codestream | .j2k, .j2c |
.svcache |
.svcache |
CZI supports single-plane Bgr24 whole-slide images with uncompressed, JPEG, or JPEG XR subblocks. Scenes share a canvas and expose common native pyramid resolutions. JPEG XR Bgr48 preview attachments retain 16-bit samples. Other CZI pixel types, multi-plane datasets, and unsupported compression return errors. Sakura remains excluded until a redistributable real sample is available.
JPEG XR TIFF support covers tiled, contiguous unsigned 8-bit grayscale/RGB images with top-left orientation, no predictor, and no alpha. Physical edge tiles are decoded before cropping. Compressed generic TIFF strips are not newly supported.
Generic strip-based TIFF support is intentionally limited to one top-level, uncompressed 8-bit RGB image with top-left orientation, no predictor, and either interleaved or separate sample planes. Other ordinary TIFF variants remain unsupported unless they use a registered WSI layout.
VMU reads the base and map NGR images at focal plane zero, plus an optional
JPEG macro image. NGR pixels retain all stored RGB samples as interleaved
Uint16 (12 significant bits); native reads do not discard the low four bits.
Use read_region_rgba_windowed with DisplayWindow::new(0.0, 4095.0)? for
an explicit display conversion. The OpenSlide C shim applies OpenSlide's
separate display rule, truncating each sample with sample >> 4 before
fractional region composition.
VMU validation currently uses generated fixtures based on OpenSlide's NGR layout, with native sample assertions and independent OpenSlide comparisons. A real scanner-generated VMU fixture has not yet been validated.
| Feature | Default | Description |
|---|---|---|
metal |
off | Metal-backed device payloads on macOS. |
cuda |
off | CUDA-backed payload surface. |
parity-openslide |
off | OpenSlide oracle parity tests. |
parity-metal |
off | CPU-vs-Metal pixel parity checks on macOS. |
The workspace includes wsi-rs-openslide-shim, a C ABI library that exports
OpenSlide-compatible symbols and routes reads through wsi-rs.
cargo build -p wsi-rs-openslide-shim --release
cargo run -p wsi-rs-openslide-shim --bin wsi-rs-openslide-install -- \
install --shim target/release/libwsi_rs_openslide_shim.dylib \
--prefix /tmp/wsi-rs-openslideUse .so instead of .dylib on Linux. Test in a private prefix before
replacing any system OpenSlide library.
cargo xtask validate
cargo xtask rc-preflight
cargo xtask fuzz-checkcargo xtask validate runs the default local gate.
cargo xtask rc-preflight runs API and supply-chain checks, fuzz builds and
five-minute campaigns for each target, feature-combination checks, validation,
release and corpus tests, coverage, performance acceptance, and package dry-run
checks. The four-platform RC workflow also verifies the C artifacts.
Performance acceptance requires reviewed capture JSON files in
WSI_RS_RC_OPENSLIDE_CAPTURE, WSI_RS_RC_PREVIOUS_CAPTURE, and
WSI_RS_RC_CURRENT_CAPTURE. Collect the OpenSlide/current pair with
cargo xtask perf-capture-pair <label> using the default five alternating
repetitions and worker matrix; collect the previous-release capture with the
same corpus, workloads, cache budget, and thread limits. Missing captures or
failed comparisons block preflight. Temporary dependency exceptions and their
expiry dates are recorded in
SUPPLY_CHAIN.md.
Releases that include the cuda feature also require the fail-closed
CUDA validation workflow on the self-hosted CUDA runner.
Report vulnerabilities privately through GitHub private vulnerability reporting or the repository owner profile.
Dual-licensed under either MIT or Apache-2.0, at your option.