Repository navigation
Releases: frames-sg/wsi-rs
Release list
wsi-rs 0.9.2
wsi-rs 0.9.2 is an internal cleanup of the tile-location and lossless raw-read code added in 0.9.0 (#27, #32). There are no API or behavior changes; 0.9.1 users get identical output. It still uses J2K 0.13.1 and JXR 0.4.0 and needs Rust 1.99 or newer.
The Python bindings are on PyPI as wsi-rs (pip install -U wsi-rs). The abi3 wheels support Python 3.10 and newer on:
- Linux x86_64 and aarch64;
- macOS arm64 and x86_64;
- Windows x64.
The wheels and sdist are attached to this release.
[0.9.2] - 2026-11-01
Internal cleanup of the tile-location and lossless raw-read code added in
0.9.0. No API or behavior changes.
Changed
- Readers share one implementation for cutting a raw tile from a stored
JPEG, for returning 8-bit RGB samples as an uncompressed raw tile, for
finding the stored tile behind a Ventana subtile, and for placing and
ordering CZI subblocks, instead of each keeping its own copy. - The
tile_locationsexample is now a short listing of a level's tile
locations that assembles tiles from their byte ranges and checks them
againstread_raw_compressed_tile.
wsi-rs 0.9.1
wsi-rs 0.9.1 is a patch release with fixes to the tile byte locations and lossless raw reads added in 0.9.0 (#27, #32). There are no API changes. It still uses J2K 0.13.1 and JXR 0.4.0 and needs Rust 1.99 or newer.
The Python bindings are on PyPI as wsi-rs (pip install -U wsi-rs). The abi3 wheels support Python 3.10 and newer on:
- Linux x86_64 and aarch64;
- macOS arm64 and x86_64;
- Windows x64.
The wheels and sdist are attached to this release.
[0.9.1] - 2026-11-01
Fixes to the tile locations and lossless raw reads added in 0.9.0. No API
changes.
Fixed
- Raw reads of clipped right and bottom edge tiles now report the stored
tile's size, withRawCompressedTile::tile_regionlocating the tile inside
it, astile_locationdoes, for TIFF JPEG XR tiles and NDPI tiles read
through restart markers. JPEG XR edge tiles reported the clipped size for a
codestream holding the whole tile, and NDPI edge tiles reported no region. - A raw read cut from a baseline or progressive JPEG that the lossless crop
cannot cut, such as a single component sampled 2x2, now returns the whole
stored JPEG withtile_regionlocating the tile, as a crop over its memory
limit does, instead of failing. A crop source that breaks the JPEG
standard (a zero quantizer, repeated component IDs, sampling factors
outside 1 to 4 or more than 10 blocks per MCU) now fails with
WsiError::Jpegrather thanWsiError::Unsupported. EncodedUnit::assembleand TIFF JPEG raw reads mergeJPEGTablesinto a
tile whenever the tile references a quantization or Huffman table it does
not define. Before, a tile carrying any table of each kind was returned
unchanged, so a tile with its own table 0 that needed table 1 from
JPEGTablescould not be decoded on its own. Tables the tile defines still
take precedence.- DICOM
tile_locationnow checks that the file is the one opened, as tile
reads do, and returns the "source changed" error for a replaced file
instead of byte ranges computed against the old one.
wsi-rs 0.9.0
wsi-rs 0.9.0 uses J2K 0.13.1 and JXR 0.4.0, and needs Rust 1.99 or newer. The previous crates.io release was 0.8.0. This release also includes the changes listed under 0.8.1 in the changelog.
The Python bindings are now on PyPI as wsi-rs, so pip install wsi-rs works. Its abi3 wheels support Python 3.10 and newer on:
- Linux x86_64 and aarch64;
- macOS arm64 and x86_64;
- Windows x64.
The wheels and sdist are attached to this release.
[0.9.0] - 2026-10-29
Added
- Opens TIFFs stored in strips when the largest image is 8-bit grayscale or
RGB, uncompressed or compressed with LZW, Deflate, Zstd or JPEG, including
YCbCr JPEG and the horizontal predictor. Before, only a single-image
uncompressed 8-bit RGB TIFF opened. A tile read decodes only the strips it
covers, so images over the 128 MiB decoded-image limit open; each compressed
strip must fit it. Other images in the file become associated images.
16-bit, palette, CMYK and alpha strip images still return an error, and a
strip-only.vsifile without its companion folder is not opened as a
plain TIFF. Slide::tile_locationandSlide::level_tile_locationsreport where tiles'
encoded bytes are stored: the file, byte ranges, framing (Standalone,
JpegTables,BarePayloadorPrefixed) and the codec parameters a decoder
outside wsi-rs needs, or that a tile isAbsentorNotAddressableand why.
EncodedUnit::assembleturns the bytes at those ranges into a standalone
stream; for a unit that covers its whole tile it equals what
read_raw_compressed_tilereturns, except for bare payloads that raw reads
return as reordered or decoded samples. A unit a tile is cut from assembles
to the whole stored image. Every range is checked against the file size,
and a level listing stays within the tile-index limit. Every slide format
reports locations;.svcachefiles, which hold wsi-rs's own decoded tiles,
do not. (#27)read_raw_compressed_tilecovers tiles it used to reject. A tile cut from a
larger stored JPEG (Ventana subtiles, NDPI levels without restart markers,
MIRAX and CZI crops, strip-based TIFFs, ZVI windows) is cropped in the
coefficient domain, asjpegtran -cropdoes: quantized coefficients are kept
and only the entropy coding is redone. When the tile origin is not on the
JPEG's MCU grid, the crop starts at the previous MCU boundary and the new
RawCompressedTile::tile_regionlocates the tile. A crop holds the whole
source unit's DCT coefficients, about 12 bytes per pixel for a 4:4:4 JPEG,
and its working set must fit the newSlideLimits::lossless_crop_bytes
(256 MiB by default). A crop that does not fit returns the whole stored
JPEG unchanged, made standalone astile_locationdescribes it, with
tile_regionlocating the tile, as JPEG 2000 and JPEG XR crops do;
jpegtran -cropcan cut it. So does a crop whose output would exceed the
encoded-unit limit. Apart from the usual admission limits, only a stored
JPEG over the encoded-unit limit, or a tile spanning several JPEG strips
whose crop does not fit, returnsWsiError::ResourceLimit. TIFF LZW,
Deflate, Zstd, uncompressed and JPEG XR tiles, DICOM native and RLE frames, MIRAX PNG and
BMP records, CZI subblocks, Hamamatsu VMU and Zeiss ZVI planes now pass
through. Tiles of lossless codecs that cannot be returned as stored come
back as exact uncompressed samples.SlideLimits::lossless_crop_bytesandwith_lossless_crop_bytesset the
working set of a lossless JPEG crop in a raw tile read, 256 MiB by default,
so 4096x4096 4:4:4 sources and large NDPI levels crop. Raw reads that may
crop reserve it as per-operation transient work, so it must stay within
operation_transient_bytes(384 MiB); decoded reads and other raw reads
do not reserve it.encoded_unit_bytesstill caps every stored unit.
CustomSlideReaders admitted at the encoded-unit limit can ask for more
for a raw read withSlideReader::raw_compressed_tile_reservation_hint.- Python:
Slide.tile_location,Slide.level_tile_locationsand
CompressedTile.tile_region, with a kerchunk-style reference example in the
Python README. Thetile_locationsexample checks reported ranges against
read_raw_compressed_tile. Compression::Png,Compression::Bmpand
EncodedTilePhotometricInterpretation::Monochrome1.- Python wheels: publishing a GitHub release builds abi3 wheels for Python
3.10 and later (Linux x86_64 and aarch64 on glibc 2.28 or newer, macOS
arm64 and x86_64, Windows x64) and an sdist. Each wheel is tested, then all
of them are attached to the release and published to PyPI aswsi-rs
through trusted publishing, sopip install wsi-rsworks. Pull requests
that touch the bindings build and test the same wheels.
Changed
- Raw DICOM
MONOCHROME1tiles and raw grayscale TIFF JPEG tiles tagged
MinIsWhite reportEncodedTilePhotometricInterpretation::Monochrome1
instead ofMonochrome2. - Raw TIFF JPEG and JPEG 2000 edge tiles report the size of the encoded
image, which usually keeps the whole stored tile, withtile_region
marking the clipped tile in it. They used to report the clipped size, which
did not match the image the bytes decode to. A JPEG or codestream smaller
than its tile returns an error. - Raw Hamamatsu VMS tiles carry a JPEG header with the restart interval
cleared, the same headertile_locationreports. Only that header field
changes; decoded pixels are unchanged. - J2K dependencies follow the J2K
mainbranch instead of the 0.8.1
maintenance revision. Main carries the CUDA packet-header fix along with
later decoder fixes and speedups. - JXR follows its
mainbranch, which builds on J2Kmain, so wsi-rs compiles
one J2K instead of also J2K 0.12 for JXR. It requires JXR 0.4, the release
line built on J2K 0.13. - Builds take the newest J2K and JXR
maincommits:cargo latestupdates
them, and every CI job but packaging runs it before building; packaging
checks the committedCargo.lock. cargo-vet treats J2K and JXR
as first-party code, reviewed in their own repositories, instead of
requiring an audit for each revision. - The J2K dependencies require 0.13.1, so a published wsi-rs resolves at
least J2K 0.13.1, with its CPU and Metal batch region fixes, instead of
accepting 0.13.0. - Raw
.j2kand.j2clevels with a side over 4096 pixels are read as
1024 x 1024 tiles, each a region decode, instead of one tile covering the
level, so a read decodes only the tiles it covers. A tile batch decodes its
tiles serially side by side, where J2K decodes Tier-1 in bands as the inverse
DWT reads them. Smaller levels still decode whole. Metal decodes tile batches
at reductions up to 3 as one batch of regions sharing the codestream's bytes;
deeper levels decode on the CPU. CUDA decoding is offered only when the full
resolution is one tile. A raw read of a full-resolution tile returns the
whole codestream withtile_regionlocating the tile; reduced resolutions
have no raw read.
Fixed
- NDPI DCT retiling returns an error for an AC coefficient outside the
8-bit baseline range, which it encoded wrongly, and for corrupt block grids,
where it copied blocks wrapped from the next row. - Merging TIFF
JPEGTablesinto a tile shorter than its SOI marker returns
an error instead of panicking. - Raw
.j2kand.j2cfiles whose decoded image exceeds 128 MiB, about 44.7
megapixels of RGB, open. The decoded-image limit now bounds each decoded
tile; it rejected the whole image at open. Reduced levels of such files
decode, where they failed against the full-resolution size. - A tile read from an uncompressed strip-based RGB TIFF reads only the rows it
covers. It read the whole image, and read it again for every tile when the
image was larger than the format cache. - Some complete LZW-compressed TIFF tiles and strips failed to decode as
truncated: J2K's LZW decoder stopped when the input ran out, before reading
the end code in its last bytes. J2Kmainreads to the end code.
wsi-rs 0.8.0
wsi-rs 0.8.0 uses J2K 0.12.0 and JXR 0.3.0 and requires Rust 1.99 or newer. This release includes the changes prepared for 0.7.0; the previous published version was 0.6.0.
The Rust crate supports CPU reads plus optional Metal and CUDA JPEG 2000 decoding. The attached unsigned OpenSlide-compatible libraries enable Metal on macOS and CUDA on Linux. The Windows library provides CPU decoding; J2K currently generates CUDA kernels on Linux hosts. Each platform archive contains the library and its build metadata and SBOMs.
[0.8.0] - 2026-10-06
Added
MetalBackendSessions::prewarmcompiles a session's JPEG 2000 GPU kernels
ahead of time, so the first slide read doesn't wait for them.
Changed
- Requires Rust 1.99 or newer.
- Uses J2K 0.12.0 and JXR 0.3.0, including the updated CUDA and Metal batch
decode paths. - CUDA JPEG 2000 tile reads use bounded encoded-image batches for parallel
planning and pooled uploads. Results keep their request order and logical
crop; a recoverable tile failure does not discard neighboring results. - Metal JPEG 2000 batches share Tier-1 dispatch across compatible prepared
groups while retaining the existing 4 MiB execution window. .svcachefiles are looked up in~/.cache/wsi-rs/svcache/instead of
~/.cache/slideviewer/svcache/. Move existing cache files there or rebuild
them.- The OpenSlide shim opens slides with a 32 MiB tile cache, matching OpenSlide
4.0.1, and no display cache; format-specific caches scale down with it. - The OpenSlide shim composes small MIRAX reads in one pass and writes cached
regular-grid regions directly into the caller's pixel buffer. - Faster XML metadata parsing when opening ARGOS, Leica, Philips and Ventana
slides. - With the
route-telemetryfeature, route tile counts are reported only in
the per-device sections; theexecutionsection no longer repeats them.
Fixed
- Concurrent single-tile reads of one DICOM slide no longer queue behind each
other under the default memory limits, and Metal decodes DICOM frames in
groups instead of one at a time. - DICOM slides that store progressive JPEG frames under the baseline JPEG
transfer syntax, as some 3DHISTECH scanners do, now read in full. - Multi-tile JPEG reads also use CPU cores that free up while the read is
running, so concurrent reads finish sooner. - CPU batch helpers yield cores when new callers need them. Concurrent tiled
TIFF JPEG reads avoid adding caller threads to a pool that already uses
every CPU core. - MIRAX JPEG reads reserve memory from indexed source sizes, so small tiles
can share a bounded batch instead of each reserving the maximum input size. - MIRAX source batches decode on the caller with help from the shared CPU
pool. JPEG tiles with known dimensions use J2K's direct output API and
reusable decode scratch, including TIFF tiles whose color hints match the
JPEG header. Larger MIRAX reads use wider bounded row bands to reduce
repeated tile planning. - The first GPU calibration read no longer includes Metal kernel compilation,
which could make a decode route settle on the CPU.
Security
- Tiny raw JPEG 2000 files with impossible tile grids are rejected before
decoding can spend excessive time processing absent tiles. - Malformed DICOM file-meta headers using
SVorUVno longer bypass length
checks and trigger oversized allocations.
[0.7.0] - 2026-09-28
Added
- 12-bit JPEG DICOM slides (JPEG Extended and progressive JPEG). Reads return
16-bit RGB samples holding the 12-bit values; grayscale expands to RGB.
12-bit decoding runs on the CPU. The OpenSlide shim rejects 12-bit DICOM, as
OpenSlide does. - Hamamatsu VMU slides, with full 16-bit samples, metadata and macro images.
The OpenSlide shim returns them as 8-bit RGB, as OpenSlide does. - ARGOS and Huron TIFF slides, including ARGOS sparse tiles and z-planes, and
associated images for both. - Zeiss CZI slides: single-plane brightfield scans stored uncompressed, as JPEG
or as JPEG XR. 16-bit embedded preview images keep all 16 bits. - JPEG XR tiles in tiled TIFF files.
- DICOM JPEG Extended, progressive JPEG and lossless JPEG transfer syntaxes.
- Raw JPEG 2000 codestreams (
.j2k,.j2c) expose each resolution as a
pyramid level, matching OpenJPEG's reduced-resolution decodes exactly. - Raw compressed JPEG tile access for CZI, MIRAX, full-resolution VMS and
ordinary tiled TIFF. - Olympus VSI raw JPEG 2000 tile access and GPU-resident Metal and CUDA tile
reads. - Per-slide resource limits (
SlideLimits). - ICC profiles for associated images, also through the OpenSlide shim.
- GPU-resident JPEG 2000 and HTJ2K tile reads on Metal and CUDA
(read_tile_metal,read_tiles_metal,read_tile_cuda,read_tiles_cuda).
Changed
- All normal reads (tiles, batches, regions, display tiles and associated
images) returnCpuTile. CustomSlideReaderimplementations provide one CPU
tile method, and batch reads keep request order by default. DecodeAcceleration::{Auto, CpuOnly}replaces the output-routing and sampling
controls.Autouses the GPU for JPEG 2000 only when it measures at least 15%
faster, including copying the result back, and falls back to the CPU on any
GPU error.- The GPU is timed on a background thread instead of during the read, except
for batches too large to keep after the read. The CPU is timed on the read
that needs the tiles. If GPU startup is much slower than CPU decoding, that
kind of tile stays on the CPU. - All JPEG 2000 CPU decoding shares one thread pool for the process.
Per-slide thread-pool settings are gone. - Default caches are 64 MiB for decoded tiles, 32 MiB for display tiles and
32 MiB shared by format-specific caches. Older per-cache settings are scaled
to fit that total. - Every built-in format counts the metadata and indexes it parses against the
slide's limits. Custom readers are trusted while opening, and their reads
reserve memory conservatively. - Ventana reduced levels are slower than in 0.6 because they are now stitched
correctly (see Fixed). They are still faster than OpenSlide. - On Apple Silicon Macs, slide fingerprints use the CPU's SHA-256 instructions.
Removed
TileOutputPreference,DeviceOutputContext,OutputBackendRequest,
TilePixels,DeviceTile, the public route-decision types, the route-sample
setting and GPU decoding of ordinary JPEG. UseDecodeAccelerationand the
GPU-resident tile APIs instead.SlideReader::recommended_shared_cache_bytes. Cache sizes are set per slide.
Fixed
Pixels that differed from OpenSlide:
- NDPI and VMS levels that OpenSlide derives by JPEG scaling (1/2, 1/4, 1/8)
now match OpenSlide 4.0.1 exactly. They differed by up to 3 levels, or up to
about 160 for slides with subsampled color. - Ventana BIF reduced levels are built from the stitched full-resolution tile
map, as OpenSlide does. They used the unstitched overview images, which put
tissue 36 to 60 µm out of place and showed blank filler. - Overlapping tiles in Ventana level 0 and MIRAX are painted in OpenSlide's
order and arithmetic, so overlaps look the same as in OpenSlide. - MIRAX levels whose images split into fractional sub-tiles are resampled at
their exact offsets, and RGB sources use Pixman's rounding, as in OpenSlide. - The OpenSlide shim keeps partly covered edge pixels on Ventana and MIRAX
reduced levels. - Metal YCbCr-to-RGB conversion matches the CPU exactly; non-neutral colors
were off. - Sparse Philips and generic TIFF tiles stay transparent in single and batched
reads. - DICOM honors RGB and YBR color-transform metadata.
- Invalid levels, missing associated images, the shim's error state,
zero-length ICC reads, Leica barcodes and slide bounds behave as in OpenSlide. - Raw JPEG 2000 slides report
openslide.vendorandopenslide.quickhash-1
like other formats. - J2K 0.11.3 fixes JPEG 2000 decoding of subsampled images whose origin is not
zero.
Crashes, hangs and bad files:
- Fixed a deadlock when several readers read display tiles from the same
generated NDPI level. - Concurrent MIRAX reads no longer interfere with each other.
- Generated NDPI levels use virtual tiles, so zooming and fractional regions
don't decode an entire level. - Short TIFF payloads, malformed CZI payload lengths and invalid CZI geometry
are rejected before decoding. - Aperio, Ventana, DICOM, NDPI and MIRAX reject bad geometry, bad sparse data,
out-of-range offsets and non-finite physical metadata. - Corrupt JPEG 2000 codestreams fail immediately instead of retrying, and no
longer build excessive coding trees (J2K 0.11.3). - Corrupt MIRAX slides are recognized as MIRAX, so opening reports the real
error. - CZI keeps planes separate, respects resource limits and keeps mosaic overlap
order. Requests for missing native levels return errors. - Generic TIFF and Philips associated images use each image's own JPEG tables
and decode one strip at a time. - VMS reads keep complete index records when the file ends with an incomplete
row. - The shim installer no longer creates incorrect
libopenslide.4aliases.
Speed and memory:
- Multi-tile JPEG reads from tiled TIFF slides (SVS, Leica and similar) decode
in parallel again. - NDPI region reads no longer queue behind other readers' work.
- MIRAX handles opened on the same files share one parsed index.
- OpenSlide-compatible region reads use row bands to cap memory, compose cached
regions straight into the output, and skip empty areas of sparse slides. - Ventana reduced levels decode each stored tile once per batch and no longer
re-decode tiles while panning. - Aperio reduced-level, zoom and thumbnail reads are back to 0.6 speed.
- Cached region reads from MIRAX, VMS, CZI and ZVI are faster with many
concurrent readers. - CZI reuses decoded source blocks across neighboring output tiles.
- Region reads stream large requests in batches sized to the...
WSI 0.8.0 preflight inputs
Benchmark capture inputs for the wsi-rs 0.8.0 release preflight.
wsi-rs 0.6.0
Added
- Added reproducible OpenSlide comparison tooling, changed-line and component
coverage gates, deterministic workload checksums, and CPU/host metadata for
performance captures. - Added focused concurrency, geometry, compositor, cache, parser, and device
regression coverage while moving test-only modules out of production LCOV.
Changed
- Migrated the optional Metal backend from
metal-rsto the J2K 0.10.0
objc2-metalownership model.MetalBackendSessions::system_defaultis the
new common constructor; expert raw-buffer adoption now accepts
MetalBuffer, and the deprecated unsynchronizedMetalDeviceStorage::Buffer
variant was removed. - Reworked region composition, JPEG 2000 decoding, and DICOM frame access around
explicit planning, validation, I/O, cache, and backend ownership boundaries.
Decode runtime selection is now passed explicitly at internal operation
boundaries instead of relying on thread-local state; the publicSlideReader
interface remains unchanged. - Consolidated parity-corpus and OpenSlide test/performance support. Performance
capture schema 6 removes metadata duplicated by the run records and declared
capture plan; schema 5 captures remain readable by the checksum-enforcing
comparator. CpuTile::pixels_arcnow returnsOption<Arc<Vec<u8>>>and clones the tile's
existingArcwithout copying pixels. Callers migrating fromArc<[u8]>
should change the stored type and usepixels.as_slice()when they need a
byte slice; constructing a newArc<[u8]>remains possible but copies.- The OpenSlide compatibility shim now reports
OpenSlide 4.0.1+wsi-rs-0.6.0, matching the pinned comparison ABI version
while retaining the shim package version in the compatibility string. - Consolidated decoded-cache single-flight behavior and split format,
composition, codec, and test modules along existing ownership boundaries.
Public APIs remain unchanged except for the plannedpixels_arcmigration.
Removed
- Removed obsolete JP2K parsing/conversion code, self-only XML helpers, and
unreachable public visibility identified by the 0.6 source audit.
This GitHub release records the existing crates.io 0.6.0 distribution. The published crate identifies the same source revision as the existing v0.6.0 tag.