Skip to content

Releases: frames-sg/wsi-rs

wsi-rs 0.9.2

Choose a tag to compare

@jcwal1516 jcwal1516 released this 01 Nov 06:37

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_locations example is now a short listing of a level's tile
    locations that assembles tiles from their byte ranges and checks them
    against read_raw_compressed_tile.

wsi-rs 0.9.1

Choose a tag to compare

@jcwal1516 jcwal1516 released this 01 Nov 05:51

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, with RawCompressedTile::tile_region locating the tile inside
    it, as tile_location does, 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 with tile_region locating 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::Jpeg rather than WsiError::Unsupported.
  • EncodedUnit::assemble and TIFF JPEG raw reads merge JPEGTables into 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
    JPEGTables could not be decoded on its own. Tables the tile defines still
    take precedence.
  • DICOM tile_location now 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

Choose a tag to compare

@jcwal1516 jcwal1516 released this 30 Oct 01:19
59eefd7

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 .vsi file without its companion folder is not opened as a
    plain TIFF.
  • Slide::tile_location and Slide::level_tile_locations report where tiles'
    encoded bytes are stored: the file, byte ranges, framing (Standalone,
    JpegTables, BarePayload or Prefixed) and the codec parameters a decoder
    outside wsi-rs needs, or that a tile is Absent or NotAddressable and why.
    EncodedUnit::assemble turns the bytes at those ranges into a standalone
    stream; for a unit that covers its whole tile it equals what
    read_raw_compressed_tile returns, 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; .svcache files, which hold wsi-rs's own decoded tiles,
    do not. (#27)
  • read_raw_compressed_tile covers 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, as jpegtran -crop does: 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_region locates 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 new SlideLimits::lossless_crop_bytes
    (256 MiB by default). A crop that does not fit returns the whole stored
    JPEG unchanged, made standalone as tile_location describes it, with
    tile_region locating the tile, as JPEG 2000 and JPEG XR crops do;
    jpegtran -crop can 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, returns WsiError::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_bytes and with_lossless_crop_bytes set 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_bytes still caps every stored unit.
    Custom SlideReaders admitted at the encoded-unit limit can ask for more
    for a raw read with SlideReader::raw_compressed_tile_reservation_hint.
  • Python: Slide.tile_location, Slide.level_tile_locations and
    CompressedTile.tile_region, with a kerchunk-style reference example in the
    Python README. The tile_locations example checks reported ranges against
    read_raw_compressed_tile.
  • Compression::Png, Compression::Bmp and
    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 as wsi-rs
    through trusted publishing, so pip install wsi-rs works. Pull requests
    that touch the bindings build and test the same wheels.

Changed

  • Raw DICOM MONOCHROME1 tiles and raw grayscale TIFF JPEG tiles tagged
    MinIsWhite report EncodedTilePhotometricInterpretation::Monochrome1
    instead of Monochrome2.
  • Raw TIFF JPEG and JPEG 2000 edge tiles report the size of the encoded
    image, which usually keeps the whole stored tile, with tile_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 header tile_location reports. Only that header field
    changes; decoded pixels are unchanged.
  • J2K dependencies follow the J2K main branch 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 main branch, which builds on J2K main, 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 main commits: cargo latest updates
    them, and every CI job but packaging runs it before building; packaging
    checks the committed Cargo.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 .j2k and .j2c levels 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 with tile_region locating 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 JPEGTables into a tile shorter than its SOI marker returns
    an error instead of panicking.
  • Raw .j2k and .j2c files 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. J2K main reads to the end code.

wsi-rs 0.8.0

Choose a tag to compare

@jcwal1516 jcwal1516 released this 06 Oct 19:53

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::prewarm compiles 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.
  • .svcache files 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-telemetry feature, route tile counts are reported only in
    the per-device sections; the execution section 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 SV or UV no 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) return CpuTile. Custom SlideReader implementations provide one CPU
    tile method, and batch reads keep request order by default.
  • DecodeAcceleration::{Auto, CpuOnly} replaces the output-routing and sampling
    controls. Auto uses 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. Use DecodeAcceleration and 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.vendor and openslide.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.4 aliases.

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...
Read more

WSI 0.8.0 preflight inputs

Pre-release

Choose a tag to compare

@jcwal1516 jcwal1516 released this 06 Oct 17:56

Benchmark capture inputs for the wsi-rs 0.8.0 release preflight.

wsi-rs 0.6.0

Choose a tag to compare

@jcwal1516 jcwal1516 released this 06 Sep 20:18

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-rs to the J2K 0.10.0
    objc2-metal ownership model. MetalBackendSessions::system_default is the
    new common constructor; expert raw-buffer adoption now accepts
    MetalBuffer, and the deprecated unsynchronized MetalDeviceStorage::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 public SlideReader
    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_arc now returns Option<Arc<Vec<u8>>> and clones the tile's
    existing Arc without copying pixels. Callers migrating from Arc<[u8]>
    should change the stored type and use pixels.as_slice() when they need a
    byte slice; constructing a new Arc<[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 planned pixels_arc migration.

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.