Version 2.10.0 – Parallel Reads, Colour Management and Binary Distributions

We are pleased to announce the release of SlideIO version 2.10.0, an update to our open-source library for pathology image analysis.

This version makes block reads of a single scene run in parallel on ten of the twelve supported formats, adds a colour management API built on ICC profiles, reports acquisition time, per-plane timestamps and channel significant bits across the drivers, and ships prebuilt binary packages for Windows, macOS and Linux for the first time.

Highlights

Parallel Block Reads

Reads of one scene were always thread-safe, but they were fully serialised: no two block reads of the same scene ever ran at the same time. A tile server or a training pipeline reading one slide from several threads got no benefit from them.

Reads of one scene now run concurrently for SVS, Philips TIFF, AFI, PKE, SCN, NDPI, CZI, VSI, OME-TIFF and ZVI — ten of the twelve formats. DICOM and GDAL are unchanged and remain serialised; CVScene::supportsConcurrentReads() reports which applies to a given scene.

Getting there meant removing shared mutable state from every read path. Two pieces of machinery carry that:

  • FileReader, a cursor-free positional reader — ReadFile with an OVERLAPPED offset on Windows, pread elsewhere — so two threads reading one file cannot disturb each other’s position.
  • ContextPool, a bounded free list that hands each thread its own read context (a TIFF handle, a scratch buffer) and takes it back afterwards, rather than tying state to a thread.

ZVI needed the same treatment inside pole, the OLE compound-file reader it uses, which gained a positional read path of its own. Streams there now issue one read per run of consecutively numbered blocks instead of one per block, so a sequentially written stream costs a single read.

No signatures changed, and there is no source or binary incompatibility. The change is behavioural, and worth one moment’s attention when upgrading: code that relied on reads of one scene being mutually exclusive in order to protect its own state must now take its own lock. Reads themselves remain safe.

Correctness was gated on byte-exactness harnesses that read the same regions concurrently and serially and compare the results, plus ThreadSanitizer coverage of FileReader and ContextPool in CI.

Colour Management and ICC Profiles

A scene can now report the ICC profile embedded in the slide, and rasters can be converted through it into a known colour space. This matters for any pipeline that compares images from different scanners: the same tissue photographed on two devices does not produce the same numbers until the profiles are applied.

Reading the profile:

import slideio

slide = slideio.open_slide("image.svs")
scene = slide.get_scene(0)

icc = scene.get_color_profile()          # raw ICC bytes, or None
info = scene.get_color_profile_info()    # parsed header
print(info.present, info.source)         # e.g. True, ColorProfileSource.EMBEDDED

ColorProfileSource distinguishes a real correction from a guess — EMBEDDED, ASSUMED, SUPPLIED or NONE — so a caller can tell whether the numbers rest on something the file actually stated.

Converting through it is a scene transformation:

cm = slideio.ColorManagement()
cm.target = slideio.ColorTarget.LAB
cm.intent = slideio.RenderingIntent.PERCEPTUAL
cm.black_point_compensation = True
cm.missing_profile_policy = slideio.MissingProfilePolicy.ASSUME_SRGB

converted = slideio.transform_scene(scene, cm)
raster = converted.read_block()

Targets are SRGB, LINEAR_RGB, LAB and XYZ. missing_profile_policy decides what happens to a slide that embeds no profile — ASSUME_SRGB, PASS_THROUGH or FAIL — and source_profile_override supplies profile bytes for a slide that carries none, which is what makes an unprofiled scene convertible at all.

The colour engine is Little-CMS (lcms2). It is linked privately into slideio-imagetools and included from a single translation unit, so no consumer of an installed header needs lcms2 on its include path. ICC profiles are read by the TIFF-based drivers (SVS and Philips TIFF), NDPI, DICOM and GDAL.

Acquisition Time, Plane Timestamps and Significant Bits

Three pieces of acquisition metadata are now reported through a uniform API, each read from whatever the format actually states and left at a documented default where it states nothing:

scene.acquisition_time                      # seconds since the epoch, 0 if unknown
scene.has_plane_timestamps                  # True if every plane carries one
scene.get_plane_timestamp(t_frame, channel, z_slice)
scene.get_channel_significant_bits(channel) # 0 when the format does not say

acquisition_time is the origin get_plane_timestamp() measures from, so the absolute instant of a plane is the sum of the two. Where a file records no start, the origin is the scene’s earliest plane — differences between planes stay meaningful either way.

get_channel_significant_bits() reports the bits the acquisition actually filled, which is often narrower than the channel data type: 16-bit samples carrying 10 bits of camera data report 10.

Scan and acquisition times are read by the SVS, Philips TIFF, AFI, SCN, PKE, NDPI, DICOM, CZI, ZVI, OME-TIFF, VSI and GDAL drivers. Per-plane timestamps are available where the format records them — CZI, DICOM, ZVI, OME-TIFF and VSI.

In C++ these are getAcquisitionTime(), hasPlaneTimestamps(), getPlaneTimestamp() and getChannelSignificantBits() on Scene.

Binary Distributions and Command Line Tools

SlideIO previously had to be built from source. Release builds now produce prebuilt packages for each platform:

  • Windows — a .zip with the libraries and headers, plus a separate -pdb.zip of debug symbols.
  • macOS — a .tar.gz.
  • Linux — Debian packages where dpkg exists (libslideio2.10, libslideio-dev and slideio-tools), and a .tar.gz otherwise, including a manylinux_2_28 x86_64 archive built against a glibc 2.28 floor.

The Linux shared libraries carry an SONAME for the first time, so two minor releases can be installed side by side.

Every package also ships two command line tools, slideio-converter and slideio-tiffinspector. Each release is verified by building a standalone find_package(slideio) consumer against the package just produced, which is what catches a header missing from the development package or a driver left out of an archive.

Reading a Transformed Scene by Level

The level-addressed reading introduced in 2.9.0 now works through transformations. A transformed scene exposes its origin’s zoom pyramid unchanged and reads the origin at the level it was asked for, so a filtered or colour-managed scene can be browsed level by level like any other.

The rule that settles what a transformation means at a level: its parameters are in the pixels of the level being read. A blur radius covers the same number of pixels at every level, and therefore more tissue at coarser ones. A level read agrees with the equivalent scaled read bit for bit.

Wrapping a concurrent scene in a transformation no longer downgrades it to serialised reads either — a transformed scene now reports the concurrency of the scene it wraps.

Build and Dependency Changes

  • Every dependency now resolves from conan center. The private Conan remote is gone, so a fresh machine needs no credentials and no packages built in advance — conan install -b missing is the whole story.
  • The JPEG XR codec, pole, and the NDPI forks of libjpeg-turbo and libtiff are git submodules rather than Conan packages. Clone with --recurse-submodules.
  • glog has been removed, replaced by a logging seam owned by the library over spdlog. The slideio-base module has been merged into slideio-core.
  • OpenCV moved to 4.14.0 and ICU to 78.2, both from conan center; s390x support goes with the ICU move.
  • The public headers are now separated from the internal ones: 37 headers ship, each listed by the module that owns it, and every one is compiled the way a consumer would compile it as part of the build. OpenCV-based interfaces such as CVScene are internal and no longer appear in the signatures of public classes.
  • The C++17 requirement is now enforced rather than assumed, and the declared CMake minimum is 3.15 — which is what the build already required in practice.

Exported-API changes that affect out-of-tree callers are recorded in software-docs/BREAKING_CHANGES.md.

Bug Fixes and Improvements

  • CZI: a scene whose Z blocks start above zero reported one time frame too many.
  • ZVI: item skip sizes could desynchronise the tag stream, making a file unreadable; image items are now derived from the document structure. A structure-only probe was added for diagnosing unreadable files.
  • VSI: the unit stated for X, Y and Z is now read rather than assumed to be micrometres, and getTFrameResolution() is no longer scaled by the Z unit.
  • OME-TIFF: the converter writes the time interval as TimeIncrement rather than PhysicalSizeT.
  • SVS/Philips TIFF: an Aperio reading can no longer reach a Philips scene.
  • Driver file-pattern matching is now case-insensitive on Linux and macOS, matching Windows behaviour.
  • Significant bits wider than the sample that describes them are refused rather than reported.

Getting Started

A comprehensive tutorial with step-by-step instructions and code samples is available in our GitHub repository:

https://github.com/Booritas/slideio-tutorial

We welcome your feedback and issue reports on GitHub and appreciate your continued support of the SlideIO project.