skills/pathml/references/image_loading.md
This reference targets the PyPI-stable PathML 3.0.5 API. All sources were checked on 2026-07-23 against the v3.0.5 tag and stable ReadTheDocs build.
Never infer authorization from the fact that a file is readable. Whole-slide images and DICOM objects can carry identifiers in pixels, labels, filenames, and metadata. Keep the original on approved storage, use a pseudonymous working name, and do not print arbitrary metadata. The bundled inspector emits only an allowlist of technical fields:
python scripts/slide_manifest.py inspect \
--slide data/pseudonymous_slide.svs \
--root .
It rejects URLs and symlinks. PathML itself accepts paths more broadly, so validate before constructing a slide object.
from pathml.core import (
CODEXSlide,
HESlide,
IHCSlide,
MultiparametricSlide,
SlideData,
SlideDataset,
VectraSlide,
types,
)
Convenience classes pass a stable SlideType:
HESlide(...) → types.HEIHCSlide(...) → types.IHCMultiparametricSlide(...) → types.IF, Bio-Formats by defaultVectraSlide(...) → types.Vectra, Bio-Formats by defaultCODEXSlide(...) → types.CODEX, Bio-Formats by defaultThe generic constructor is:
slide = SlideData(
"data/pseudonymous_slide.svs",
name="slide-001",
backend="openslide",
slide_type=types.HE,
)
SlideData.from_slide(), read_region(), level_dimensions, and
level_downsamples are not stable SlideData APIs. Use the constructor,
extract_region(), shape, and backend-specific objects where necessary.
For a local cohort, instantiate slides first:
from pathlib import Path
from pathml.core import HESlide, SlideDataset
root = Path("data/slides")
paths = sorted(root.glob("*.svs"))
slides = [HESlide(path, backend="openslide", name=path.stem) for path in paths]
dataset = SlideDataset(slides)
Do not recursively accept arbitrary user-controlled paths. Validate a manifest, freeze the patient split, and then build this list.
Use backend="openslide" for common brightfield pyramid formats. Stable PathML
lists:
.svs, .tif, .tiff, .bif, .ndpi, .vms, .vmu, .scn, .mrxs,
and .svslide.
The complete capability depends on the installed OpenSlide build and the vendor subtype, not only the suffix. Some generic TIFFs are not valid WSIs, and some files with a supported suffix use unsupported compression.
Native OpenSlide is required. Official PathML guidance uses
openslide-tools on Debian/Ubuntu, Homebrew openslide on macOS, and vcpkg or
official prebuilt binaries on Windows.
Use backend="bioformats" for multidimensional microscopy, OME-TIFF, QPTIFF, and
formats OpenSlide cannot read. Bio-Formats supports a large catalogue (the
upstream examples describe 160+ formats), including .ome.tif, .ome.tiff,
.qptiff, .czi, .vsi, .zvi, and many laboratory formats.
This backend requires Java, python-bioformats, and python-javabridge. It
starts a JVM and stable source configures a large maximum heap, so isolate and
resource-limit untrusted images. Java has an approximately 2 GB array limit in
the backend. A listed extension is not proof that every variant loads.
Bio-Formats returns five-dimensional arrays in PathML order:
(i, j, z, channel, time) = (row, column, z, c, t).
Even singleton z and time dimensions are retained until a transform such as
CollapseRunsCODEX or CollapseRunsVectra changes the layout.
Use backend="dicom" for .dcm or .dicom. Stable PathML treats DICOM frames as
tiles. DICOM metadata is especially likely to contain PHI; de-identify with an
approved DICOM process before PathML, preserve required UIDs consistently, and
never dump the full dataset to logs.
.h5 and .h5path inputs are inferred as PathML's processed HDF5 format:
from pathml.core import SlideData
processed = SlideData("derived/slide-001.h5path")
There is no stable from_hdf5() constructor. See data_management.md.
If backend=None, PathML infers a backend from the suffix. Prefer an explicit
backend in reproducible work:
from pathml.core import HESlide
slide = HESlide("data/slide-001.svs", backend="openslide")
Reasons to be explicit:
.tif can mean a brightfield pyramid, OME-TIFF, or a plain raster.slide.shape returns (height, width) for the backend's default level.
height, width = slide.shape
region = slide.extract_region(
location=(2_000, 3_000), # (i, j) = (row, column)
size=(512, 768), # (height, width)
level=1,
)
tiles = slide.generate_tiles(
shape=(512, 512),
stride=(256, 256),
pad=False,
level=1,
)
generate_tiles() is lazy. Do not materialize all tiles just to count them.
Use the bounded planner first:
python scripts/plan_pipeline.py \
--width 100000 --height 80000 \
--tile-size 512 --stride 256 \
--level-downsample 4
SlideData.run() uses different parameter names: tile_size, tile_stride,
tile_pad, and level.
PathML's Tile.coords is the top-left (i, j):
i: row / vertical / image yj: column / horizontal / image x(0, 0)For OpenSlide, stable PathML multiplies (i, j) by that level's downsample and
swaps the order before calling OpenSlide's level-0 (x, y) API. Therefore:
row_level0 = i_level * downsample_level
col_level0 = j_level * downsample_level
y_um = row_level0 * mpp_y
x_um = col_level0 * mpp_x
Use scanner-provided level-0 MPP when reliable. Do not silently derive MPP from objective power. Record:
ij or xy)QuantifyMIF later writes obsm["spatial"] in (x, y) order, so a conversion is
required when joining it to Tile.coords.
For OpenSlide, level 0 is highest resolution. Later levels are downsampled, but
the factors are slide-specific; do not assume 4x, 16x, or a particular
magnification sequence.
Backend-level inspection:
level_count = slide.slide.level_count
level0_shape = slide.slide.get_image_shape(level=0) # (height, width)
# OpenSlide-specific internals, not a backend-neutral PathML contract:
downsamples = tuple(slide.slide.slide.level_downsamples)
dimensions_xy = tuple(slide.slide.slide.level_dimensions)
Guard backend-specific access and record it as such. Bio-Formats maps image series to levels; those series are not necessarily an optical pyramid.
For one dimension D, tile extent T, and stride S, pad=False yields:
0 if D < T
floor((D - T) / S) + 1 otherwise
With pad=True, stable PathML follows its backend implementation, which is not
identical to a generic ceil(D / S) rule for every overlapping configuration.
Use the bundled planner and verify a small synthetic case. Padded pixels are zero,
which can bias tissue/stain/QC transforms.
Important stable limitation: SlideData.generate_tiles() does not slice
slide-level masks into padded tiles. Do not combine a slide-level mask with
pad=True without an explicit, tested padding policy.
PathML 3.0.5 has no backend-neutral slide.metadata mapping. Technical metadata
is backend-specific:
metadata.Default to a strict allowlist such as:
Do not emit patient name/ID, accession, dates, institution, free text, UIDs, or file paths. Even technical fields can be identifying in a small cohort; minimize what is retained.
Before large-scale processing:
slide_data.py):
https://github.com/Dana-Farber-AIOS/pathml/blob/v3.0.5/pathml/core/slide_data.pyslide_backends.py):
https://github.com/Dana-Farber-AIOS/pathml/blob/v3.0.5/pathml/core/slide_backends.pytile.py):
https://github.com/Dana-Farber-AIOS/pathml/blob/v3.0.5/pathml/core/tile.py