Converting images to OME-TIFF#

spatialrefinery.convert_to_ometiff converts a whole-slide or microscopy image into a pyramidal OME-TIFF: a tiled, multi-resolution TIFF that viewers (and SpatialData) can read efficiently at any zoom level without loading the full-resolution image into memory.

It dispatches on the input file’s suffix to a registered converter – OpenSlideImageConverter for .svs, .ndpi, .tif, .tiff, .mrxs, .scn, .bif, .vms, .svslide, and BioioImageConverter for .czi (which needs the optional czi extra). What you need: one whole-slide image file. Runtime: proportional to image size; pyramid generation is the dominant cost.

from pathlib import Path

from spatialrefinery import convert_to_ometiff
from spatialrefinery.core.registry import RegistryError, get_converter_for, list_converters

Registered converters#

list_converters() shows every suffix with a registered converter, and which class handles it:

list_converters()
{'.bif': 'OpenSlideImageConverter',
 '.czi': 'BioioImageConverter',
 '.mrxs': 'OpenSlideImageConverter',
 '.ndpi': 'OpenSlideImageConverter',
 '.scn': 'OpenSlideImageConverter',
 '.svs': 'OpenSlideImageConverter',
 '.svslide': 'OpenSlideImageConverter',
 '.tif': 'OpenSlideImageConverter',
 '.tiff': 'OpenSlideImageConverter',
 '.vms': 'OpenSlideImageConverter'}

Converting a single file#

subresolutions controls how many pyramid levels are generated below full resolution (each level halves the resolution); tile_size is the TIFF tile edge length in pixels. The defaults (4 subresolutions, 1024px tiles) work well for typical whole-slide images.

SOURCE = Path("/Users/rushin.gindra/Documents/Research/SpatialProjects/Phoenix/datasets/svs_files/example.svs")
OUTPUT_DIR = Path("/Users/rushin.gindra/Documents/Research/SpatialProjects/Phoenix/datasets/ometiff_output/")

ometiff_paths = convert_to_ometiff(
    source=SOURCE,
    output_dir=OUTPUT_DIR,
    subresolutions=4,
    tile_size=1024,
    overwrite=False,
)
ometiff_paths
INFO     Writing pyramid level 0 (shape: (44928, 99840, 3))
INFO     Writing pyramid level 1 (shape: (22464, 49920, 3))
INFO     Writing pyramid level 2 (shape: (11232, 24960, 3))
INFO     Writing pyramid level 3 (shape: (5616, 12480, 3))
INFO     Writing pyramid level 4 (shape: (2808, 6240, 3))
[PosixPath('/Users/rushin.gindra/Documents/Research/SpatialProjects/Phoenix/datasets/ometiff_output/example.ome.tif')]

A source file can contain more than one scene/plane (common for .czi), in which case convert_to_ometiff writes one .ome.tif per scene and returns every path it wrote.

Batch converting a directory#

Discover every convertible file under a directory by matching the registered suffixes, then convert each one – skipping (rather than aborting on) files with no registered converter or that fail to convert:

def find_convertible_files(input_path: Path) -> list[Path]:
    """Return every file under `input_path` with a registered converter suffix."""
    suffixes = list_converters().keys()
    files = []
    for suffix in suffixes:
        files.extend(input_path.glob(f"*{suffix}"))
        files.extend(input_path.glob(f"*{suffix.upper()}"))
    return sorted(set(files))


WSI_DIR = Path("wsi_dir")
all_outputs: list[Path] = []

for file in find_convertible_files(WSI_DIR):
    try:
        get_converter_for(file)  # fail fast with a clear message before doing any work
    except RegistryError as e:
        print(f"Skipping {file}: {e}")
        continue

    try:
        all_outputs.extend(convert_to_ometiff(file, OUTPUT_DIR, overwrite=False))
    except Exception as e:  # noqa: BLE001 - one file's failure must not abort the batch
        print(f"Failed to convert {file}: {e}")

print(f"Created {len(all_outputs)} OME-TIFF file(s)")
Created 0 OME-TIFF file(s)

Handling unsupported files#

get_converter_for (and therefore convert_to_ometiff) raises RegistryError for two cases: a suffix with no registered converter, and a source that is itself already an .ome.tif/.ome.tiff – re-running a conversion over a directory that already contains outputs will not re-ingest them as inputs.

try:
    convert_to_ometiff(Path("unsupported.xyz"), OUTPUT_DIR)
except RegistryError as e:
    print(e)
"No converter registered for suffix '.xyz'. Known suffixes: .bif, .czi, .mrxs, .ndpi, .scn, .svs, .svslide, .tif, .tiff, .vms"

CZI support#

.czi files require the optional czi extra:

pip install "spatialrefinery[czi]"

Without it, .czi sources raise RegistryError just like any other unregistered suffix.

Running this as a script#

python scripts/convert_to_ometiff.py --input_path wsi_dir/ --output_dir ometiff_output/ -p 4

What’s next#

That’s the full pipeline – see the API reference for the complete parameter list of each function used across these tutorials.