opentile

package module
v0.22.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 24, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

README

opentile-go

A Go library for reading raw compressed tiles from whole-slide imaging (WSI) files used in digital pathology, including TIFF dialects (Aperio SVS, Hamamatsu NDPI, Philips TIFF, OME-TIFF, Ventana BIF, Leica SCN), the bleeding-edge non-TIFF Iris File Extension, and the ZIP-wrapped Microsoft Deep Zoom-based Smart Zoom Image format. Direct port of the Python opentile library for the four TIFF formats it supports, with byte-identical output. BIF (v0.7), IFE (v0.8), generic-TIFF (v0.10), Leica SCN (v0.11), and SZI (v0.16) are opentile-go's own additions beyond upstream's coverage. Memory-mapped tile reads + pool-friendly TileInto API since v0.9; bandwidth-deduplication TilePrefix / TileBodyInto API since v0.13 — see docs/perf.md.

import (
    opentile "github.com/wsilabs/opentile-go"
    _ "github.com/wsilabs/opentile-go/formats/all"
)

t, err := opentile.OpenFile("slide.svs")
if err != nil { /* ... */ }
defer t.Close()

base, _ := t.Level(0)
tile, err := base.Tile(0, 0) // raw compressed JPEG / JP2K / etc. bytes

Tile(x, y) returns the raw compressed bitstream as stored on disk — opentile-go is a tile-extraction library, not a decoder. Decode the returned bytes with whatever JPEG / JPEG 2000 / etc. library suits your downstream pipeline.

v0.22+ decoded-pixel access (preview): A decoder/ package and 9 codec subpackages are now available for callers that want decoded image.RGBA-equivalent pixels rather than raw bytes. Add a blanket side-effect import to opt in:

import (
    opentile "github.com/wsilabs/opentile-go"
    _ "github.com/wsilabs/opentile-go/formats/all"
    _ "github.com/wsilabs/opentile-go/decoder/all" // enables decoded-tile access in v1.0+
)

The decoded-tile *Slide methods that consume this layer are planned for v1.0. See decoder/ and resample/ for the current public API.

Supported formats

Format Extension Levels Associated Compression Parity bar Detail
Aperio SVS .svs tiled label, overview, thumbnail JPEG, JP2K (passthrough) byte-parity vs. Python opentile docs/formats/svs.md
Hamamatsu NDPI .ndpi tiled (striped + OneFrame) overview, synthesised label*, Map* JPEG byte-parity vs. Python opentile docs/formats/ndpi.md
Philips TIFF .tiff tiled, with sparse-tile fill label, overview, thumbnail JPEG byte-parity vs. Python opentile docs/formats/philipstiff.md
OME-TIFF .ome.tiff tiled (SubIFD) + OneFrame overview, label, thumbnail JPEG (uint8 RGB only) byte-parity vs. Python opentile + tifffile docs/formats/ometiff.md
Ventana BIF .bif tiled, serpentine remap, with overlap metadata* + ScanWhitePoint blank-tile fill overview, probability*, thumbnail JPEG tifffile (DP 200) + sampled-tile SHAs (both fixtures) docs/formats/bif.md
Iris IFE* .iris tiled (256×256, native-first inversion) with sparse-tile sentinel label, overview, thumbnail, macro, map, probability + free-form titles + ICC profile + free-form attribute map JPEG, AVIF (passthrough), Iris-proprietary (passthrough) sampled-tile SHAs + synthetic-writer + per-fixture geometry pin docs/formats/ife.md
Generic TIFF* .tiff, .tif tiled pyramidal (≥1 level, geometric scale chain) classifier-assigned: label, overview, thumbnail, or "associated" fallback JPEG, JP2K, LZW, Deflate, None, WebP, JPEG XL, AVIF, HTJ2K (all passthrough) sampled-tile SHAs + per-fixture geometry pin + cross-backing parity docs/formats/generictiff.md
Leica SCN* .scn tiled BigTIFF; multi-region "discontinuous scanning"; multi-channel fluorescence classifier-assigned: overview per auxiliary <image> JPEG sampled-tile SHAs + per-fixture geometry pin + bio-formats CLI parity oracle docs/formats/leicascn.md
Smart Zoom Image (SZI)* .szi ZIP-wrapped Microsoft Deep Zoom pyramid; per-level dim halving; sparse images not supported per spec label, overview (from macro.jpg), thumbnail JPEG / PNG (all passthrough) sampled-tile SHAs + per-fixture geometry pin docs/formats/szi.md
COG-WSI* .tiff strict GDAL Cloud Optimized GeoTIFF + WSI private tags (65080-87) + COG_WSI_VERSION ghost-area marker label, overview (from macro or overview WSIImageType), thumbnail source-format preserving (JPEG, JP2K, LZW, …) per-fixture geometry pin + cross-fixture parity vs source format + ErrNotConformantCOGWSI spec validation docs/formats/cogwsi.md

* Marks Go-side extensions beyond upstream Python opentile; see Deviations below.

Detection is automatic. opentile.OpenFile walks the registered factories — first asking each for SupportsRaw(r, size) against the raw byte stream, then falling through to TIFF-parsed Supports(file) — and dispatches the first match. The two-stage dispatch lets non-TIFF formats (IFE) short-circuit before tiff.Open. The generic-TIFF reader registers LAST so vendor format detectors get first crack at any TIFF; it activates as a catch-all only when no vendor factory claims the file. Format packages register at import time via _ "github.com/wsilabs/opentile-go/formats/all".

Format coverage: opentile-go ports the four TIFF formats Python opentile 0.20.0 supports for tile extraction. 3DHistech TIFF (the fifth upstream format) is parked at #2. Ventana BIF — the first beyond upstream's coverage — landed in v0.7. Iris IFE — the first non-TIFF format — landed in v0.8. Generic TIFF — a catch-all reader for tiled pyramidal TIFFs without vendor metadata — landed in v0.10. Leica SCN — the legacy SCN400/SCN400F format, including the first multi-channel fluorescence support — landed in v0.11. Smart Zoom Image (SZI) — a ZIP-wrapped Microsoft Deep Zoom pyramid backed by a shared internal/dzi/ core — landed in v0.16. Sakura SVSlide is parked at #3.

Prerequisites

  • Go 1.23+ (uses iter.Seq2).
  • libjpeg-turbo 2.1+ for tile-domain JPEG operations (NDPI edge-tile fill, Philips sparse-tile fill, OME OneFrame extraction).
    • macOS: brew install jpeg-turbo
    • Debian / Ubuntu: apt-get install libturbojpeg0-dev
  • pkg-config to resolve libturbojpeg at build time.

opentile-go is mostly Go with one cgo dependencyinternal/jpegturbo/ wraps libjpeg-turbo's tjTransform for lossless DCT-domain crops. Building without cgo (-tags nocgo or CGO_ENABLED=0) is supported: SVS works fully, NDPI striped levels work, but NDPI OneFrame / NDPI edge-tile fill / Philips sparse-tile fill / OME OneFrame return ErrCGORequired.

Install

go get github.com/wsilabs/opentile-go

Pin to v0.5.1 or later (v0.5.0 shipped with a wrong module path; see CHANGELOG).

API

Opening a slide
t, err := opentile.OpenFile("slide.tiff")
if err != nil { /* ErrUnsupportedFormat or open error */ }
defer t.Close()

fmt.Println("format:", t.Format())                 // "svs", "ndpi", "philips-tiff", "ome-tiff", "bif", "ife", "generic-tiff", "leica-scn", "szi", "cog-wsi"
fmt.Println("levels:", len(t.Levels()))

Pass options to override defaults:

t, err := opentile.OpenFile("slide.ndpi",
    opentile.WithTileSize(1024, 1024),                     // virtual tile size for OneFrame levels
    opentile.WithNDPISynthesizedLabel(false),              // disable the v0.2 NDPI label synthesis
)

For an io.ReaderAt source (S3, in-memory, etc.) instead of a filename:

t, err := opentile.Open(reader, size, opts...)
Reading tiles
base, _ := t.Level(0)

// Per-tile metadata.
fmt.Printf("base: %v tiles of %v pixels, compression %s, mpp %v\n",
    base.Grid(), base.TileSize(), base.Compression(), base.MPP())

// Get one tile's raw compressed bytes.
tile, err := base.Tile(0, 0)

Stream a tile via io.ReadCloser:

rc, err := base.TileReader(0, 0)
defer rc.Close()
io.Copy(dst, rc)

Iterate every tile in row-major order:

for pos, res := range base.Tiles(ctx) {
    if res.Err != nil { /* ... */ }
    process(pos.X, pos.Y, res.Bytes)
}
Multi-image files

OME-TIFF can carry multiple main pyramids in a single file. Tiler.Images() returns them all; Tiler.Levels() is a shortcut to Images()[0].Levels() for callers that don't need to distinguish.

for _, img := range t.Images() {
    fmt.Printf("Image %d (%q): %d levels, %v µm/px\n",
        img.Index(), img.Name(), len(img.Levels()), img.MPP())
    base, _ := img.Level(0)
    tile, _ := base.Tile(0, 0)
    // ...
}

For SVS, NDPI, and Philips, Images() always returns a one-element slice — Levels() / Level(i) work as before.

Associated images

Tiler.Associated() returns label / overview / thumbnail / map images where the format provides them:

for _, a := range t.Associated() {
    b, err := a.Bytes()
    if err != nil { continue }
    fmt.Printf("%s: %v, %s, %d bytes\n", a.Type(), a.Size(), a.Compression(), len(b))
}

a.Bytes() returns a self-contained, decoder-ready blob in whatever codec the source TIFF carries (typically JPEG or LZW). a.Type() is "label", "overview", "thumbnail", or "map" (NDPI only).

Format-specific metadata

Cross-format fields (manufacturer, scanner serial, acquisition datetime, magnification) are surfaced via t.Metadata(). Format-specific fields are accessible by type-asserting through a per-format helper:

import (
    svs "github.com/wsilabs/opentile-go/formats/svs"
    ndpi "github.com/wsilabs/opentile-go/formats/ndpi"
    philips "github.com/wsilabs/opentile-go/formats/philips"
    ome "github.com/wsilabs/opentile-go/formats/ome"
)

if md, ok := svs.MetadataOf(t); ok {
    fmt.Println("MPP (SVS):", md.MPP, "µm/px")
}
if md, ok := ndpi.MetadataOf(t); ok {
    fmt.Println("source lens (NDPI):", md.SourceLens, "x")
}
if md, ok := philips.MetadataOf(t); ok {
    fmt.Println("PixelSpacing (Philips):", md.PixelSpacing, "mm")
}
if md, ok := ome.MetadataOf(t); ok {
    fmt.Println("OME images:", len(md.Images))
}

MetadataOf walks any number of wrapper Tilers (e.g., *fileCloser from OpenFile) before asserting on the concrete type, so the helper works regardless of how the Tiler was obtained.

Concurrency

Level.Tile, Level.TileInto, Level.TileAt, and Level.TileReader are safe to call concurrently from multiple goroutines. SVS / Philips / OME tiled / BIF / IFE have no internal locks on the tile hot path. NDPI's striped reader takes a per-page mutex on its assembled-frame cache; concurrent reads of different pages run in parallel, concurrent reads of the same page serialize. OME OneFrame is similar.

All internal caches (parsed IFDs, per-tile offset / length tables, metadata) are populated at Open() time and then immutable — no locks on the tile hot path. Format packages with shared lazy caches use sync.Once and produce byte-deterministic output regardless of which goroutine populates them first.

Close() must not race with in-flight tile reads — drain before closing. Under the v0.9 default mmap backing, this is non-negotiable: closing unmaps the file, and subsequent reads through the mapping raise SIGBUS.

Performance

opentile-go's tile reads are designed for high-RPS HTTP serving and per-frame desktop viewers. See docs/perf.md for the full guide. Quick summary:

  • OpenFile is mmap-backed by default since v0.9. Tile reads become userspace memcpy; no pread(2) syscall per call. Opt out via opentile.WithBacking(opentile.BackingPread).
  • Use Level.TileInto(x, y, dst) (int, error) with a sync.Pool of []byte buffers sized to Level.TileMaxSize() for zero-allocation tile reads. Cervix serial: 152 ns/op, 0 allocs (vs v0.8's 22µs).
  • Tiler.WarmLevel(i) error pre-warms the page cache for predictable warm-cache latency.
  • Bandwidth deduplication (v0.13): Level.TilePrefix() returns the constant JPEG prefix; Level.TileBodyInto(x, y, dst) returns on-disk bytes without the prefix. Client-server consumers can send the prefix once per session and body bytes per tile. opentile.SpliceJPEGTile(prefix, body) reconstitutes a complete JPEG on the client side. Savings are fixture-author-dependent — see docs/perf.md for details.

Deviations from upstream Python opentile

opentile-go aims for byte-parity with Python opentile 0.20.0. A small number of deviations exist where matching upstream would encode an upstream oversight or where opentile-go provides a strictly more useful affordance:

Deviation Format Since Opt-out / API Why
Synthesised label NDPI v0.2 WithNDPISynthesizedLabel(false) Upstream doesn't surface NDPI labels at all; we crop the left 30% of the overview to provide an Aperio-style label affordance.
Map pages exposed NDPI v0.4 not opt-out-able (silent absence) tifffile already classifies them as series.name == 'Map'; surfacing matches the underlying TIFF carrying.
Multi-image OME pyramids OME v0.6 use Tiler.Levels() instead of Tiler.Images() for first-image-only behaviour Upstream's base Tiler loop silently drops 3 of 4 main pyramids in multi-image files via an unintentional last-wins assignment. We expose all of them via Tiler.Images().
Probability map exposed as kind="probability" BIF v0.7 iterate Associated() and skip the kind Upstream doesn't read BIF; openslide drops the probability map. We surface it for downstream tools that want it.
Level.TileOverlap() image.Point interface evolution BIF + all v0.7 non-BIF formats return image.Point{} (zero) — no caller change needed BIF level-0 stores tiles with horizontal overlap; consumer needs the value to position raw tile bytes correctly.
Non-strict ScannerModel acceptance BIF v0.7 not opt-out-able The BIF spec mandates rejecting any slide whose ScannerModel != "VENTANA DP 200"; we accept any iScan-tagged BigTIFF and route via HasPrefix("VENTANA DP") so legacy iScan slides aren't worse-than-openslide.
Multi-dimensional WSI API addition (TileCoord + Level.TileAt + Image.SizeZ/SizeC/SizeT/ChannelName/ZPlaneFocus) All formats v0.7 additive — 2D-only formats inherit SingleImage defaults Modern WSI consumers (fluorescence, focal-plane viewers, time series) need explicit multi-dim addressing. BIF reads multi-Z natively; OME surfaces dimensions honestly + defers TileAt(z != 0) to a future format-package milestone.
Non-TIFF dispatch path (FormatFactory.SupportsRaw + OpenRaw + RawUnsupported base) All formats v0.8 additive — TIFF factories embed RawUnsupported and inherit defaults Iris IFE is the first non-TIFF format opentile-go reads. Table-driven dispatch lets each format own its detection; future non-TIFF formats drop in additively.
TILE_TABLE.x_extent / y_extent ignored IFE v0.8 not opt-out-able The IFE v1.0 spec doc claims these fields carry image pixel dims, but the cervix fixture stores tile counts (matching LAYER_EXTENTS.x_tiles). Reader derives image dims from LAYER_EXTENTS × 256 instead — unambiguous either way.
Default mmap-backed OpenFile All formats v0.9 WithBacking(BackingPread) Universal perf win on the hot path (8–145× speedup; cervix serial Tile dropped from 22µs to 0.75µs). Auto-fallback to pread on mmap failure; SIGBUS on file truncation documented in the OpenFile docstring.
Level.TileInto + Level.TileMaxSize interface evolution All formats v0.9 additive — existing Tile() unchanged Pool-friendly tile-read API. With sync.Pool of []byte buffers sized to TileMaxSize(), the caller does zero allocations per tile on every TIFF format and IFE. NDPI / OME OneFrame still allocate internal scratch.
Tiler.WarmLevel(i) interface evolution All formats v0.9 additive — hint operation, callers can ignore Page-cache pre-warm for predictable warm-cache latency. Useful for slide-server pre-warm at startup.
Generic-TIFF reader for non-vendor tiled pyramidal TIFFs Generic TIFF v0.10 not opt-out-able once registered; any TIFF that no vendor factory claims AND that passes the validator routes here Real-world WSI authoring outside Aperio / Hamamatsu / Philips is common (Grundium, Roche legacy iScan, vendor-stripped derivatives, libtiff-encoded research outputs). A catch-all reader makes opentile-go consume any structurally valid pyramid TIFF without per-vendor reverse-engineering.
"associated" AssociatedImage Kind value addition Generic TIFF v0.10 iterate Associated() and skip the kind Generic TIFFs may carry non-pyramid IFDs the heuristic classifier can't confidently match to label / macro / thumbnail; surfacing them as "associated" lets the consumer access Bytes() / Size() without a wrong-but-plausible kind label.
Leica SCN reader for legacy SCN400 / SCN400F output Leica SCN v0.11 not opt-out-able once registered First real-fixture exercise of Image.SizeC() > 1 (Leica-Fluorescence-1.scn's separated-channel data); also the first multi-region "discontinuous scanning" reader. Architecturally valuable beyond just SCN coverage.
Level.TilePrefix / TileBodyInto / TileBodyMaxSize + opentile.SpliceJPEGTile interface evolution All formats (JPEG splice formats benefit) v0.13 additive — existing Tile() / TileInto() unchanged Bandwidth-deduplication API for client-server consumers: send the per-level prefix once, send per-tile body bytes per request, reconstitute on client. Savings fixture-author-dependent (only slides with shared JPEGTables benefit).
Smart Zoom Image (SZI) reader Smart Zoom Image v0.16 not opt-out-able once registered First ZIP-backed format opentile-go reads; first format to surface CompressionPNG. Spec-mandated uncompressed-stored ZIP entries preserve the v0.9 mmap-aliased fast path. Backed by a new shared internal/dzi/ core designed for additive bare-DZI support in v0.17+.
COG-WSI reader + integer-multiple pyramid ratio acceptance COG-WSI + generic-TIFF v0.19 not opt-out-able once registered First spec-validated COG-profile reader opentile-go ships; pairs WSI-domain private tags 65080-87 + COG_WSI_VERSION ghost-area marker with the GDAL Cloud Optimized GeoTIFF base structure. Closes Issues #5 + #6. Generic-TIFF standalone benefit: relaxed strict-drift check now accepts clean integer-multiple pyramid ratios (Aperio / Grundium SVS-style 4×/2×/2× chains).

Full reasoning + per-deviation commit references are in docs/deferred.md.

Testing

make test     # go test ./... -race -count=1
make vet      # go vet ./...
make cover    # ≥80% per package; needs OPENTILE_TESTDIR
make parity   # batched parity oracle vs Python opentile 0.20.0 + tifffile
make bench    # NDPI per-tile throughput regression gate

Integration tests and the parity oracle require real slide files at $OPENTILE_TESTDIR. Fixture JSONs (committed) are at tests/fixtures/. Slides themselves are not redistributable and are gitignored.

OPENTILE_TESTDIR="$PWD/sample_files" go test ./tests/... -v

For parity testing against Python opentile + tifffile, set the Python interpreter and run with the parity build tag:

pip install -r tests/oracle/requirements.txt
OPENTILE_ORACLE_PYTHON=$(which python) \
OPENTILE_TESTDIR="$PWD/sample_files" \
  go test ./tests/oracle/... -tags parity -v

The default run samples ~100 tile positions per level per slide. A persistent stdin / stdout protocol keeps one Python subprocess resident per slide; full sweep on the v0.6 13-slide oracle slate completes in under 10 seconds.

License + attribution

Apache 2.0. Independent Go port of the Python opentile library (Copyright 2021–2024 Sectra AB); see NOTICE for attribution. Not affiliated with or endorsed by Sectra AB or the BigPicture project.

Documentation

Overview

Package opentile provides utilities to read tiles from whole-slide imaging (WSI) TIFF files. See the repository README for a high-level overview.

Index

Constants

View Source
const (
	// PropertyCaseNumber is the clinical / specimen case identifier.
	PropertyCaseNumber = "case-number"
	// PropertyUserName is the scan operator / user name.
	PropertyUserName = "user-name"
	// PropertyScannedAreaMM2 is the physical scanned area in mm²
	// (string-formatted float; parse via strconv.ParseFloat).
	PropertyScannedAreaMM2 = "scanned-area-mm2"
	// PropertyScanDurationSec is the wall-clock scan duration in
	// seconds (string-formatted float; parse via strconv.ParseFloat).
	PropertyScanDurationSec = "scan-duration-seconds"
	// PropertyComments is free-text user comments (distinct from
	// ImageDescription, which is the structured per-format description).
	PropertyComments = "comments"
)

PropertyXxx are the canonical opentile-go cross-format keys for Metadata.Properties. Format readers use these constants to populate well-known cross-format fields that don't have typed struct positions.

Added in v0.17.

Variables

View Source
var (
	ErrUnsupportedFormat      = errors.New("opentile: unsupported format")
	ErrUnsupportedCompression = errors.New("opentile: unsupported compression")
	ErrTileOutOfBounds        = errors.New("opentile: tile position out of bounds")
	ErrCorruptTile            = errors.New("opentile: corrupt tile")
	ErrLevelOutOfRange        = errors.New("opentile: level index out of range")
	ErrInvalidTIFF            = errors.New("opentile: invalid TIFF structure")

	// ErrTooManyIFDs is returned when a TIFF IFD chain exceeds the safety cap
	// (1024 IFDs) before terminating. Either the file is corrupt, presents a
	// cycle, or is malicious. Re-exports internal/tiff.ErrTooManyIFDs so
	// callers can errors.Is(err, opentile.ErrTooManyIFDs).
	ErrTooManyIFDs = tiff.ErrTooManyIFDs

	// Returned (wrapped in TileError) when internal/jpeg cannot parse a JPEG
	// bitstream or assemble a valid one from TIFF fragments.
	ErrBadJPEGBitstream = errors.New("opentile: invalid JPEG bitstream")

	// Returned when an operation requires an MCU-aligned region and the
	// computed or requested region is not. Primarily an internal invariant
	// guard; consumers encounter it only on malformed slides.
	ErrMCUAlignment = errors.New("opentile: operation requires MCU alignment")

	// Returned from NDPI one-frame levels and NDPI label on builds compiled
	// without cgo (CGO_ENABLED=0 or -tags nocgo).
	ErrCGORequired = errors.New("opentile: operation requires cgo build with libjpeg-turbo")

	// Reserved for future use; currently unfired because v0.2 defaults the
	// NDPI tile size to 512 rather than erroring. Predefined so exporting
	// it later is not a breaking change.
	ErrTileSizeRequired = errors.New("opentile: tile size not representable for this format")

	// ErrDimensionUnavailable is returned (wrapped in TileError) when a
	// caller asks for a non-zero Z, C, or T axis on a TileCoord but the
	// underlying Image's SizeZ/SizeC/SizeT is 1 (the format / slide
	// doesn't carry that dimension at all). Distinct from
	// ErrTileOutOfBounds: that error means "this axis exists but the
	// requested index is past its size"; this means "the axis itself
	// doesn't exist on this slide / format / milestone."
	//
	// Added in v0.7 alongside Level.TileAt and the multi-dim Image
	// dimension accessors.
	ErrDimensionUnavailable = errors.New("opentile: dimension not supported on this format/image")

	// ErrSparseTile is returned (wrapped in TileError) when a tile
	// position falls within the level's grid but the underlying file
	// has no compressed bytes at that cell — the format encodes
	// "absent / blank tile" as a sentinel offset rather than empty
	// content. Iris IFE uses NULL_TILE (0xFFFFFFFFFF in the 40-bit
	// offset field); other formats may add later. Consumers typically
	// translate this into an HTTP 404 or a fixed blank image. Distinct
	// from ErrTileOutOfBounds (the position itself is past the grid).
	//
	// Added in v0.8 alongside the Iris IFE format package.
	ErrSparseTile = errors.New("opentile: sparse tile (no compressed bytes at this position)")

	// ErrMmapUnavailable is returned by [OpenFile] when called with
	// [WithBacking](BackingMmap) (the default since v0.9) but the
	// underlying memory-map operation fails — typically because the
	// file is on a filesystem that doesn't support mmap (some FUSE
	// or network mounts) or the platform lacks `golang.org/x/exp/mmap`
	// support. Wraps the underlying error from `mmap.Open`.
	//
	// Callers that want automatic fallback to the os.File / pread
	// path can retry with `opts...` extended by
	// `WithBacking(BackingPread)` on this error.
	//
	// Added in v0.9 alongside the mmap-default OpenFile change.
	ErrMmapUnavailable = errors.New("opentile: memory-map unavailable for this file")
)
View Source
var ErrBadJPEGSplice = errors.New("opentile: bad JPEG splice input")

ErrBadJPEGSplice is returned by SpliceJPEGTile when the body bytes don't conform to the expected SOS-bearing JPEG layout.

Functions

func Register

func Register(f FormatFactory)

Register adds a FormatFactory to the registry. It is safe to call concurrently but typically called from init or a main-package setup function.

func SpliceJPEGTile

func SpliceJPEGTile(prefix, body []byte) ([]byte, error)

SpliceJPEGTile reconstitutes a complete JPEG from a level's TilePrefix bytes and one tile's TileBodyInto output. Inserts the prefix at the on-disk tile's SOS boundary (the same operation opentile-go does internally during Tile/TileInto).

Returns body verbatim (defensively copied) if prefix is empty / nil — degenerate case for levels without splice (e.g., non-JPEG compressions, NDPI stripped levels, IFE).

Returns ErrBadJPEGSplice if body is empty or doesn't contain an SOS marker (0xFF 0xDA per JPEG spec).

Algorithm (documented for non-Go consumers reimplementing client-side):

  1. If prefix is empty: return body verbatim.
  2. Find offset of the first 0xFF 0xDA byte sequence in body ("Start of Scan" marker).
  3. Output = body[0:sosIdx] + prefix + body[sosIdx:]

Added in v0.13 alongside Level.TilePrefix and Level.TileBodyInto.

Types

type AssociatedImage

type AssociatedImage interface {
	Type() string
	Size() Size
	Compression() Compression
	Bytes() ([]byte, error)
}

AssociatedImage is a non-pyramidal slide-level image (label, overview, thumbnail).

Standard Type() values used across opentile-go's format readers. The choice of names follows DICOM PS3.3 / Supplement 145 (Whole Slide Microscopic Image IOD), where the Image Type attribute (0008,0008) value 3 enumerates: VOLUME / LABEL / OVERVIEW / THUMBNAIL. opentile-go uses the lowercase form, extended with format-specific kinds where the underlying file surfaces them:

"label"       — slide label / barcode
"overview"    — wide-field image of the slide. The DICOM-canonical
                term, also used by upstream Python opentile and by
                six of opentile-go's eight format readers. The
                seventh (Iris IFE) intentionally distinguishes
                "overview" from "macro" per the IFE spec.
"thumbnail"   — full-slide downsample (typically square, JPEG)
"map"         — NDPI / IFE: low-magnification map / overview-of-
                pyramid; semantically distinct from a wide-field
                slide image
"probability" — Ventana BIF / IFE: confidence / classification map
"macro"       — Iris IFE only. The IFE spec defines LABEL_MACRO
                as a kind distinct from LABEL_OVERVIEW. Other
                formats' wide-field slide images surface as
                "overview", not "macro".
"associated"  — generic-TIFF heuristic-fallback (v0.10+) when the
                classifier can't confidently match a kind above

Format readers use the string literals directly; the values above are stable and part of the public API contract from v0.15 onward.

type Backing

type Backing uint8

Backing identifies the I/O backend used to read tile bytes from a slide file. Selectable via WithBacking; defaults to BackingMmap since v0.9.

BackingMmap memory-maps the file read-only and reads tiles via userspace memcpy from the mapped region. No syscall per Tile() call once the kernel has paged in the relevant region. Recommended for high-RPS serving and warm-cache desktop use. Caveat: SIGBUS on file truncation; not suitable for storage that gets rewritten underneath open Tilers.

BackingPread keeps the v0.8 (and earlier) os.File-based path: pread(2) syscall per Level.Tile call. Use this on filesystems that don't support mmap (some FUSE / network mounts), or when you specifically need the os.File semantics around truncation.

const (
	// BackingMmap memory-maps the slide file. Default since v0.9.
	BackingMmap Backing = iota
	// BackingPread uses os.File + pread(2) per Tile().
	BackingPread
)

type Compression

type Compression uint8

Compression identifies the bitstream format of a tile as stored in a TIFF.

opentile-go returns tile bytes in the compression format of the source TIFF without decoding them. Consumers that need decoded pixels should pass the bytes to a codec appropriate for the reported compression.

The zero value is CompressionUnknown: a forgotten-to-initialize field surfaces loudly rather than masquerading as a known compression.

const (
	CompressionUnknown Compression = iota // zero value; unset or unrecognized
	CompressionNone
	CompressionJPEG
	CompressionJP2K
	CompressionLZW  // TIFF tag 259 value 5 (Aperio SVS label is commonly LZW)
	CompressionAVIF // tile bytes are an AVIF image; consumer decodes via libavif
	// CompressionIRIS is the Iris-proprietary tile codec used by IFE files
	// when written through Iris-Codec. opentile-go reports it but does not
	// decode the bytes; consumers either embed an Iris codec or 501 the
	// request. JPEG and AVIF tiles in IFE remain decodable by external
	// codecs.
	CompressionIRIS
	// CompressionDeflate identifies the Deflate (zlib) bitstream
	// commonly used by scientific imaging TIFFs and the
	// generic-TIFF catch-all reader (v0.10). TIFF tag 259 values
	// 8 (Deflate) and 32946 (Adobe Deflate) both map here; the
	// payload is identical zlib-wrapped DEFLATE either way.
	CompressionDeflate
	// CompressionWebP identifies a WebP-encoded tile (RIFF + WEBP +
	// VP8/VP8L/VP8X chunks). TIFF tag 259 value 50001 in libtiff
	// convention; same value is what the user's wsi-tools transcoder
	// emits. Tile bytes are a complete self-contained WebP file.
	// Consumer decodes via libwebp or golang.org/x/image/webp.
	//
	// Added in v0.14.
	CompressionWebP
	// CompressionJPEGXL identifies a JPEG XL codestream tile. TIFF
	// tag 259 value 50002 (wsi-tools convention; not formally
	// registered). Tile bytes are a bare JXL codestream beginning
	// with the 0xFF 0x0A marker. Consumer decodes via libjxl (cgo)
	// or stdlib image/jxl when available.
	//
	// Added in v0.14.
	CompressionJPEGXL
	// CompressionHTJ2K identifies an HTJ2K (High-Throughput JPEG
	// 2000, ISO/IEC 15444-15) codestream tile. TIFF tag 259 value
	// 60003 (wsi-tools convention). Distinct from CompressionJP2K
	// because HTJ2K uses a different entropy coder (FBCOT instead
	// of EBCOT) and a standard JP2K decoder will fail on HTJ2K
	// bytes. Consumer decodes via OpenJPEG 2.5+, OpenHTJ2K, or
	// Kakadu.
	//
	// Added in v0.14.
	CompressionHTJ2K
	// CompressionPNG identifies a PNG-encoded tile (`\x89PNG` magic).
	// DZI's Format attribute admits both jpeg and png. Tile bytes
	// are a complete self-contained PNG file. Consumer decodes via
	// `image/png` (stdlib).
	//
	// Added in v0.16.
	CompressionPNG
)

func (Compression) String

func (c Compression) String() string

type Config

type Config struct {
	// contains filtered or unexported fields
}

Config is an opaque, read-only view of the configuration passed to a FormatFactory. Format packages import opentile.Config rather than the unexported config struct.

func NewTestConfig deprecated

func NewTestConfig(tileSize Size, policy CorruptTilePolicy) *Config

NewTestConfig constructs a Config for use in tests.

Deprecated: use opentile/opentiletest.NewConfig. This wrapper remains for one release to keep external callers compiling; it will be removed in v0.4.

func (*Config) Backing

func (c *Config) Backing() Backing

Backing reports the I/O backing the caller selected via WithBacking. Defaults to BackingMmap since v0.9 if no option was passed. Format packages typically don't need this — Open is path-agnostic — but it's exposed for diagnostic accessors.

func (*Config) CorruptTilePolicy

func (c *Config) CorruptTilePolicy() CorruptTilePolicy

CorruptTilePolicy returns the configured policy.

func (*Config) NDPISynthesizedLabel

func (c *Config) NDPISynthesizedLabel() bool

NDPISynthesizedLabel reports whether NDPI Tiler.Associated() should include a synthesized label cropped from the overview. Default true.

func (*Config) TileSize

func (c *Config) TileSize() (Size, bool)

TileSize returns the requested output tile size and whether the caller set one.

  • (Size{}, false): caller did not pass WithTileSize. Format packages should use their format default (e.g. SVS reads the native tile size from the TIFF; NDPI uses 512).
  • (Size{}, true): caller explicitly passed WithTileSize(0, 0). Format packages MUST reject this as malformed input. The zero Size is distinct from "unset" because the API contract is that an explicit option overrides the default.
  • (non-zero, true): caller's requested tile size; format honors it (NDPI may snap to a stripe-multiple, SVS rejects when it doesn't match the native tile dimensions).

type CorruptTilePolicy

type CorruptTilePolicy uint8

CorruptTilePolicy controls how corrupt-edge tiles (currently: Aperio SVS) are reported. v0.1 supports only CorruptTileError.

const (
	CorruptTileError CorruptTilePolicy = iota // return ErrCorruptTile (default, v0.1)
	CorruptTileBlank                          // v0.3: return a typed blank tile
	CorruptTileFix                            // v1.0: reconstruct from parent level
)

type Format

type Format string

Format identifies the source file format.

const (
	FormatSVS  Format = "svs"
	FormatNDPI Format = "ndpi"
	// FormatPhilipsTIFF is the Philips IntelliSite Pathology Solution TIFF
	// reader. Renamed in v0.12 from FormatPhilips to FormatPhilipsTIFF to
	// align with v0.10's FormatGenericTIFF and v0.11's FormatLeicaSCN naming
	// convention; Philips has multiple WSI file formats (TIFF; iSyntax), so
	// the bare "philips" identifier was ambiguous. Reports as "philips-tiff".
	FormatPhilipsTIFF Format = "philips-tiff"
	// FormatOMETIFF is the OME-TIFF reader. Renamed in v0.12 to align
	// with v0.10/v0.11's FormatGenericTIFF / FormatLeicaSCN convention;
	// OME has multiple file formats (OME-TIFF, OME-Zarr, OME-NGFF), so
	// the bare "ome" identifier ambiguously claimed the family.
	FormatOMETIFF Format = "ome-tiff"
	FormatBIF     Format = "bif"
	FormatIFE     Format = "ife"
	// FormatGenericTIFF is the catch-all reader for tiled pyramidal TIFF
	// without vendor metadata (added in v0.10). Activates when no
	// vendor format factory claims the file. Reports as "generic-tiff"
	// to differentiate from possible future generic-non-TIFF readers.
	FormatGenericTIFF Format = "generic-tiff"
	// FormatLeicaSCN is the Leica SCN reader (added in v0.11). SCN is a
	// BigTIFF dialect produced by Leica SCN400 / SCN400F scanners;
	// scanner production stopped ~2015. Reports as "leica-scn" to
	// differentiate from other Leica-related formats (LIF, LMS) that
	// aren't SCN.
	FormatLeicaSCN Format = "leica-scn"
	// FormatSZI identifies a Smart Zoom Image file (ZIP-wrapped
	// Microsoft Deep Zoom pyramid + scan-properties.xml +
	// associated_images/, per the smartinmedia/SZI-Format spec).
	//
	// Added in v0.16.
	FormatSZI Format = "szi"
	// FormatCOGWSI identifies a Cloud Optimized GeoTIFF for WSI file —
	// a strict extension of GDAL Cloud Optimized GeoTIFF carrying
	// WSI-specific private tags + ghost-area marker. Spec at
	// docs/specs/2026-05-20-cog-wsi-format.md. Added in v0.19.
	FormatCOGWSI Format = "cog-wsi"
)

func Formats

func Formats() []Format

Formats returns the format identifiers that have been registered via Register, sorted lexicographically. Useful for diagnostics and for callers that want to enumerate compiled-in formats without importing each format package directly.

type FormatFactory

type FormatFactory interface {
	Format() Format
	SupportsRaw(r io.ReaderAt, size int64) bool
	OpenRaw(r io.ReaderAt, size int64, cfg *Config) (Tiler, error)
	Supports(file *tiff.File) bool
	Open(file *tiff.File, cfg *Config) (Tiler, error)
}

FormatFactory is implemented by format packages to register themselves with the top-level opentile package. Factories are queried in registration order; the first factory whose Supports() (TIFF path) or SupportsRaw() (non-TIFF path) returns true is used.

SupportsRaw + OpenRaw provide a non-TIFF dispatch path. Open's reader queries SupportsRaw first; if any factory takes the byte-level dispatch, the input is never handed to tiff.Open. Format packages whose files are classic/BigTIFF should embed RawUnsupported for the zero-value default impls (SupportsRaw → false, OpenRaw → ErrUnsupportedFormat). The first non-TIFF format to use this path is Iris IFE in v0.8.

type Image

type Image interface {
	// Index is the 0-based document-order index of this Image within
	// the Tiler. Always 0 for single-image formats; 0..N-1 for
	// multi-image OME.
	Index() int
	// Name is the format-specific name for this Image — for OME TIFF,
	// the <Image Name="..."> attribute (typically empty for main
	// pyramids; macro / label / thumbnail are routed to AssociatedImage
	// rather than Image). Empty string for non-OME formats.
	Name() string
	// Levels returns the pyramid levels from highest to lowest
	// resolution. Always returns a fresh slice; callers may mutate the
	// slice header without affecting the Image's internal state.
	Levels() []Level
	// Level returns the level at the given index, or
	// ErrLevelOutOfRange if i is out of bounds.
	Level(i int) (Level, error)
	// MPP returns the base-level microns/pixel for this Image. Zero
	// SizeMm when unknown.
	MPP() SizeMm

	// SizeZ returns the count of focal planes carried by this Image.
	// Returns 1 for non-Z-stack slides (every existing 2D format,
	// every BIF slide whose IMAGE_DEPTH tag is absent or 1, every
	// 2D OME slide). Added in v0.7.
	SizeZ() int

	// SizeC returns the count of separately-stored fluorescence /
	// spectral channels. Returns 1 for brightfield slides; > 1 for
	// fluorescence imaging where each channel is its own grayscale
	// image. Added in v0.7.
	//
	// IMPORTANT: this is the count of separately-stored channels,
	// NOT the per-pixel sample count. A brightfield RGB slide has
	// SizeC == 1 (one composite RGB tile per call), even though
	// each pixel decodes to 3 colour samples.
	SizeC() int

	// SizeT returns the count of time points. Returns 1 for
	// non-time-series slides. Added in v0.7.
	SizeT() int

	// ChannelName returns the human-readable name of channel c —
	// e.g., "DAPI", "FITC", "TRITC" for fluorescence; "" for
	// brightfield slides where the single channel is implicit RGB.
	//
	// c must be in [0, SizeC()); panics with index-out-of-range
	// otherwise (matching slice-access conventions). Added in v0.7.
	ChannelName(c int) string

	// ZPlaneFocus returns the focal distance (microns) of plane z
	// from the nominal focal plane. ZPlaneFocus(0) is always 0
	// (Z=0 is by convention the nominal plane). Negative values
	// indicate planes below the nominal plane (near focus); positive
	// values indicate planes above (far focus).
	//
	// z must be in [0, SizeZ()); panics with index-out-of-range
	// otherwise (matching slice-access conventions). Added in v0.7.
	ZPlaneFocus(z int) float64
}

Image represents one main pyramid in a Tiler. Single-image formats (SVS, NDPI, Philips) expose exactly one Image; OME-TIFF can expose multiple — one per OME <Image> element that isn't classified as macro / label / thumbnail.

Within an Image the Levels are ordered from highest resolution (Index 0 = baseline) downwards.

Added in v0.6 alongside Tiler.Images(). The legacy Tiler.Levels() / Tiler.Level(i) shortcut accessors continue to work, delegating to Images()[0].

type Level

type Level interface {
	Index() int
	PyramidIndex() int
	Size() Size
	TileSize() Size
	Grid() Size

	// TileOverlap returns the pixel overlap between adjacent tiles at this level.
	// Tile (c, r) is positioned in image-space at
	//   (c · (TileSize().X - TileOverlap().X),
	//    r · (TileSize().Y - TileOverlap().Y)).
	// In the overlap region, tiles further along the row/column overwrite earlier
	// tiles (no blending). Returns image.Point{} for non-overlapping levels and
	// non-BIF formats.
	TileOverlap() image.Point

	Compression() Compression
	MPP() SizeMm
	FocalPlane() float64

	// Tile returns the raw compressed tile bytes at (x, y) as stored in the
	// source TIFF. Equivalent to TileAt(TileCoord{X: x, Y: y}) — the
	// nominal-plane / first-channel / T=0 tile.
	//
	// Allocates a fresh []byte for each call. For high-RPS callers,
	// prefer [Level.TileInto] with a caller-provided buffer (typically
	// from a [sync.Pool]).
	Tile(x, y int) ([]byte, error)

	// TileInto writes the raw compressed tile bytes at (x, y) into dst
	// and returns the number of bytes written. Returns
	// [io.ErrShortBuffer] if len(dst) < [Level.TileMaxSize] for this
	// level (no I/O is performed in that case).
	//
	// Tile(x, y) is shorthand for:
	//
	//	buf := make([]byte, l.TileMaxSize())
	//	n, err := l.TileInto(x, y, buf)
	//	return buf[:n], err
	//
	// dst is caller-allocated and caller-owned; opentile-go writes into
	// it and never reads it after return. Pool-friendly callers should
	// keep a sync.Pool of []byte buffers sized at TileMaxSize() and
	// reset to that capacity before each call.
	//
	// For 2D-only Levels: non-zero Z/C/T arguments to TileAt are not
	// applicable here; use [Level.TileAt] directly when multi-dim
	// addressing is needed. TileInto is the (x, y) hot path.
	//
	// Added in v0.9 as the pool-friendly tile-read entry point.
	TileInto(x, y int, dst []byte) (int, error)

	// TileMaxSize returns the maximum byte length any tile on this
	// level may produce through [Level.Tile] or [Level.TileInto].
	// Computed at level-open time and cached; safe to call repeatedly.
	//
	// For TIFF formats: max(TileByteCounts) + len(JPEGTables splice
	// prefix), where the prefix is the (typically per-level) header
	// inserted by the format reader. For IFE: max(TileEntry.Size).
	// For NDPI striped + OME OneFrame: the assembled-frame tile size
	// (always the level's TileSize().W * TileSize().H bytes for the
	// uncompressed assembly path; the compressed output equals the
	// decoded tile region).
	//
	// Sizing a sync.Pool bucket: round up to the next power-of-two of
	// TileMaxSize() and reuse across many tiles on the same level.
	// Adjacent levels typically have similar TileMaxSize values; one
	// pool per Tiler (sized by max across levels) is usually enough.
	//
	// Added in v0.9 alongside [Level.TileInto].
	TileMaxSize() int

	// TilePrefix returns the constant per-level JPEG splice prefix bytes.
	// When non-nil, callers can ship the prefix once per level + per-tile
	// TileBodyInto output, then reconstitute the full JPEG on the client
	// side via opentile.SpliceJPEGTile. When nil, no splice prefix exists
	// for this level — TileBodyInto returns the same bytes as TileInto.
	//
	// Use case: bandwidth-efficient client-server tile transfer. SVS /
	// Philips / OME / leicascn / generictiff levels with shared JPEGTables
	// typically have ~1 KB of prefix per level applied to 100k+ tiles per
	// slide; deduplicating saves ~100 MB bandwidth per slide.
	//
	// Added in v0.13.
	TilePrefix() []byte

	// TileBodyInto writes the on-disk tile bytes (the "body" — what gets
	// spliced with TilePrefix to form a complete JPEG) into dst. Returns
	// the number of bytes written.
	//
	// For levels where TilePrefix() returns nil (non-splice path),
	// TileBodyInto is functionally equivalent to TileInto.
	//
	// Caller must size dst to at least TileBodyMaxSize().
	//
	// Added in v0.13.
	TileBodyInto(x, y int, dst []byte) (int, error)

	// TileBodyMaxSize returns the upper bound on TileBodyInto output size
	// across all tiles in this level. For levels with shared JPEGTables
	// (TilePrefix() != nil), this is strictly less than TileMaxSize() (no
	// splice prefix added). For levels without shared JPEGTables
	// (TilePrefix() == nil), equal to TileMaxSize().
	//
	// Added in v0.13.
	TileBodyMaxSize() int

	// TileAt returns the raw compressed tile bytes at the multi-dimensional
	// coord. Tile(x, y) is shorthand for TileAt(TileCoord{X: x, Y: y}).
	//
	// For 2D-only Levels (the parent Image's SizeZ/SizeC/SizeT all == 1),
	// any non-zero Z, C, or T value yields *TileError wrapping
	// ErrDimensionUnavailable. For multi-dim Levels (BIF level-0 with
	// IMAGE_DEPTH > 1; future OME multi-Z), the multi-dim coord is
	// resolved by the format's reader.
	//
	// Added in v0.7 alongside TileCoord and the Image dimension accessors.
	TileAt(coord TileCoord) ([]byte, error)

	// TileReader returns a streaming reader for the tile at (x, y). Callers
	// should Close the returned ReadCloser.
	TileReader(x, y int) (io.ReadCloser, error)

	// Tiles iterates every tile position in row-major order. Callers that need
	// parallelism goroutine on top of Tile(x, y); Tiles itself is serial.
	// Z=C=T=0 only — multi-dim iteration is consumer-driven via nested
	// loops over Image.SizeZ/SizeC/SizeT.
	Tiles(ctx context.Context) iter.Seq2[TilePos, TileResult]
}

Level is a single resolution in a pyramidal WSI.

Concurrency: Tile, TileInto, TileAt, TileReader, and the Tiles iterator are safe to call concurrently from multiple goroutines on SVS / Philips / OME tiled / BIF / IFE — those formats have no internal locks on the tile hot path. NDPI's striped Level holds a per-page mutex around the assembled-frame cache: concurrent reads of *different* pages run in parallel; concurrent reads of the *same* page serialize. OME OneFrame uses a similar per-level extended-frame cache. The underlying io.ReaderAt supplied to Open (or constructed by OpenFile) must itself be safe for concurrent use; stdlib *os.File and the v0.9 mmap-backed io.ReaderAt both satisfy this.

Bytes returned by Tile / TileAt are caller-owned. Bytes written by TileInto into the caller-provided dst remain caller-owned. opentile-go never reads them after return.

Edge-tile semantics vary by format. TIFF-based formats (SVS, NDPI, Philips, OME-TIFF, BIF, Leica SCN, generic TIFF) and IFE store all tiles at full Level.TileSize regardless of position; right-edge and bottom-edge tiles include padding bytes outside the actual image bounds — opentile-go does not add the padding, it's how the underlying file format encodes them. SZI/DZI is the exception: border tiles decode to their actual content dimensions per the DZI spec. Consumers rendering tiles at slide-image positions should clip output to the meaningful sub-rect using

contentW := min(TileSize().W, Size().W - x*TileSize().W)
contentH := min(TileSize().H, Size().H - y*TileSize().H)

On SZI/DZI the formula matches decoded dimensions exactly; on padded formats the formula identifies the meaningful sub-rect within the full-size tile. Per-format details in `docs/formats/<fmt>.md`.

Under BackingMmap (the v0.9 default), the underlying file must not be truncated or rewritten while a Tiler is open — doing so raises SIGBUS in any thread that subsequently reads through the mapping. WSI files don't get truncated under normal use; if your storage allows it, opt out via WithBacking(BackingPread).

type Metadata

type Metadata struct {
	Magnification       float64 // 0 if unknown
	ScannerManufacturer string
	ScannerModel        string
	ScannerSoftware     []string
	ScannerSerial       string
	// AcquisitionDateTime is the time the slide was scanned. Partial Date
	// or Time values that fail time.Parse yield the zero value
	// (time.Time{}); time.Time{}.IsZero() == true is the "unknown"
	// sentinel. Callers should always check IsZero rather than comparing
	// against a specific time — different scanner vendors emit dates in
	// different formats and our parsers are lenient but not exhaustive.
	AcquisitionDateTime time.Time

	// MicronsPerPixel is populated when MicronsPerPixelX and
	// MicronsPerPixelY are both set and equal (strict ==). When the
	// format reports asymmetric pixel size, MicronsPerPixel is zero
	// and consumers should consult the per-axis fields. Zero indicates
	// "unknown OR asymmetric"; check MicronsPerPixelX/Y to disambiguate.
	//
	// Added in v0.17.
	MicronsPerPixel float64

	// MicronsPerPixelX / MicronsPerPixelY are the per-axis pixel size
	// in microns. Zero indicates the format didn't report it.
	//
	// Added in v0.17.
	MicronsPerPixelX float64
	MicronsPerPixelY float64

	// ImageDescription is the structured per-format description (e.g.,
	// SVS ImageDescription TIFF tag, OME-XML <Image Description>
	// attribute). Empty when the format has no equivalent. For free-
	// text user comments, see Properties[PropertyComments].
	//
	// Added in v0.17.
	ImageDescription string

	// Properties is a flat key-value map for additional metadata
	// that doesn't fit the typed fields. Two key conventions:
	//
	//   - opentile-go-canonical keys (lowercase-with-hyphens):
	//     PropertyCaseNumber, PropertyUserName, PropertyScannedAreaMM2,
	//     PropertyScanDurationSec, PropertyComments. Populated by
	//     format readers when their format exposes the equivalent.
	//
	//   - vendor-namespaced keys (vendor.<key>): vendor-specific
	//     fields surfaced as-is. Format-prefixed: e.g., "szi.vendor.
	//     SerialNumber", "aperio.AppMag".
	//
	// Missing keys mean the format didn't expose that field. Numeric
	// values are string-formatted floats (parseable via
	// strconv.ParseFloat).
	//
	// Added in v0.17.
	Properties map[string]string

	// Writer identifies the software that wrote this file — the
	// file producer, distinct from ScannerManufacturer (scanner OEM)
	// and ScannerSoftware []string (broader software stack).
	//
	// Format-specific population:
	//   SVS Aperio canonical    "Aperio Image Library v11.2.1"
	//   SVS Grundium / other    "Grundium Ocus" (comma-suffix writer
	//                            from v0.18 detection)
	//   OME-TIFF                "OME Bio-Formats 6.0.0-rc1" (Creator
	//                            attribute; promoted from Properties)
	//   SZI                     "<SoftwareName> <SoftwareVersion>"
	//                            (e.g., "OcusScan 3.1.4")
	//   COG-WSI                 "wsitools/<WSIToolsVersion>" (file
	//                            producer; source scanner stays in
	//                            ScannerManufacturer per spec)
	//   Generic-TIFF (wsi-tools  "wsitools/<version>" from the
	//     fixtures avif/jxl/      wsi-tools ImageDescription parser
	//     htj2k/webp)
	//   NDPI / Philips / BIF /  format-specific Software field (often
	//     IFE / Leica SCN        equals ScannerSoftware[0])
	//
	// Empty when the format provides no writer indication. Consumers
	// checking presence compare against "" explicitly.
	//
	// Added in v0.20.
	Writer string
}

Metadata is the common subset of slide metadata surfaced across all formats. Format packages embed this struct to add format-specific fields exposed via type assertion on Tiler.Metadata().

func (*Metadata) SetMPPSymmetric

func (m *Metadata) SetMPPSymmetric()

SetMPPSymmetric populates MicronsPerPixel from MicronsPerPixelX and MicronsPerPixelY when they are equal (strict ==). When asymmetric, MicronsPerPixel is zeroed.

Format readers call this after setting the per-axis fields.

Added in v0.17.

func (*Metadata) SetProperty

func (m *Metadata) SetProperty(key, value string)

SetProperty is a nil-safe setter for Properties. Lazily initializes the map on first use.

Added in v0.17.

type Option

type Option func(*config)

Option mutates the opentile configuration before Open returns a Tiler.

func WithBacking

func WithBacking(b Backing) Option

WithBacking selects the I/O backend used by OpenFile. The default since v0.9 is BackingMmap; pass WithBacking(BackingPread) on the rare filesystem that doesn't support mmap or when you need os.File truncation semantics.

Has no effect on Open (which already takes a caller-provided io.ReaderAt); only the path-resolving OpenFile honors this.

When set to BackingMmap and the underlying mmap call fails (FUSE mount that doesn't support mapping, etc.), OpenFile returns ErrMmapUnavailable wrapping the underlying error rather than silently falling back. Callers that want auto-fallback should retry with WithBacking(BackingPread).

func WithCorruptTilePolicy

func WithCorruptTilePolicy(p CorruptTilePolicy) Option

WithCorruptTilePolicy sets the behavior for corrupt-edge tiles. v0.1 supports only CorruptTileError.

func WithNDPISynthesizedLabel

func WithNDPISynthesizedLabel(enable bool) Option

WithNDPISynthesizedLabel controls whether NDPI Tiler.Associated() includes a synthesized "label" image, which Go produces by cropping the left 30% of the overview page. Python opentile 0.20.0 does not expose NDPI labels; this is a Go-side extension. Default: true (matches v0.2 behavior).

func WithTileSize

func WithTileSize(w, h int) Option

WithTileSize requests output tile dimensions in pixels. If unset, the format default is used (SVS: native tile size from the TIFF). Required for formats that have no native rectangular tiles (NDPI, v0.2+).

type Point

type Point struct {
	X, Y int
}

Point is a 2D integer position measured in pixels or tile units.

func (Point) String

func (p Point) String() string

type RawUnsupported

type RawUnsupported struct{}

RawUnsupported is the zero-impl base for FormatFactory.SupportsRaw + FormatFactory.OpenRaw. Format packages whose files are classic or BigTIFF embed this struct in their Factory type to inherit the "doesn't take the non-TIFF dispatch path" defaults. Non-TIFF format packages (Iris IFE) override both methods on their own Factory.

func (RawUnsupported) OpenRaw

func (RawUnsupported) OpenRaw(io.ReaderAt, int64, *Config) (Tiler, error)

OpenRaw returns ErrUnsupportedFormat; the dispatch loop never reaches this method on a TIFF-only factory because SupportsRaw returns false first, but the explicit error keeps callers honest if they bypass Open.

func (RawUnsupported) SupportsRaw

func (RawUnsupported) SupportsRaw(io.ReaderAt, int64) bool

SupportsRaw reports false: this format doesn't recognize raw byte streams, so the dispatch loop continues to the TIFF path.

type Region

type Region struct {
	Origin Point
	Size   Size
}

Region is an axis-aligned rectangle in pixel or tile units.

func (Region) Contains

func (r Region) Contains(p Point) bool

Contains reports whether p lies inside r (inclusive of origin, exclusive of the far edge).

type SingleImage

type SingleImage struct {
	// contains filtered or unexported fields
}

SingleImage is the one-element Image wrapper used by single-pyramid formats (SVS, NDPI, Philips) to satisfy Tiler.Images(). It holds a fixed level list; Index() is always 0, Name() always empty, and MPP() returns the base level's MPP() (or the zero SizeMm when the level list is empty).

Multi-image formats (OME-TIFF) implement opentile.Image directly rather than reusing SingleImage.

func NewSingleImage

func NewSingleImage(levels []Level) *SingleImage

NewSingleImage returns an Image wrapping the supplied level slice. The slice header is copied; underlying Level pointers are shared.

func (*SingleImage) ChannelName

func (s *SingleImage) ChannelName(c int) string

ChannelName always returns "" for SingleImage — the single channel is implicit RGB on brightfield slides; consumers don't need a name to interpret it.

func (*SingleImage) Index

func (s *SingleImage) Index() int

Index always returns 0 for SingleImage.

func (*SingleImage) Level

func (s *SingleImage) Level(i int) (Level, error)

Level returns the level at i, or ErrLevelOutOfRange if i is out of bounds.

func (*SingleImage) Levels

func (s *SingleImage) Levels() []Level

Levels returns a fresh copy of the wrapped level slice.

func (*SingleImage) MPP

func (s *SingleImage) MPP() SizeMm

MPP returns the base level's MPP, or the zero SizeMm if there are no levels.

func (*SingleImage) Name

func (s *SingleImage) Name() string

Name always returns "" for SingleImage.

func (*SingleImage) SizeC

func (s *SingleImage) SizeC() int

SizeC always returns 1 for SingleImage — no fluorescence / multi-channel support on 2D pathology formats. Brightfield RGB slides return 1 (single composite RGB channel per pixel).

func (*SingleImage) SizeT

func (s *SingleImage) SizeT() int

SizeT always returns 1 for SingleImage — no time-series support on pathology formats.

func (*SingleImage) SizeZ

func (s *SingleImage) SizeZ() int

SizeZ always returns 1 for SingleImage — no Z-stack support on single-pyramid 2D formats. Multi-Z formats (BIF with IMAGE_DEPTH > 1; future OME multi-Z) implement Image directly or wrap SingleImage and override SizeZ.

func (*SingleImage) ZPlaneFocus

func (s *SingleImage) ZPlaneFocus(z int) float64

ZPlaneFocus always returns 0 for SingleImage — single Z-plane at nominal focus. Multi-Z Image impls override.

type Size

type Size struct {
	W, H int
}

Size is a 2D integer extent measured in pixels or tile units.

func (Size) Area

func (s Size) Area() int

func (Size) String

func (s Size) String() string

type SizeMm

type SizeMm struct {
	W, H float64
}

SizeMm is a 2D extent measured in millimeters. Used for pixel spacing and microns-per-pixel conversion (SizeMm scaled by 1000 equals micrometers).

func (SizeMm) IsZero

func (s SizeMm) IsZero() bool

type TileCoord

type TileCoord struct {
	X, Y int
	Z    int
	C    int
	T    int
}

TileCoord identifies a tile by its position in the multi- dimensional WSI space. X and Y are the existing 2D grid position; Z, C, and T select among focal planes (Z), fluorescence / spectral channels (C), and time points (T) respectively.

Z, C, T default to zero — a TileCoord literal {X: x, Y: y} addresses the same tile that Level.Tile(x, y) returns. Zero is the "nominal" / "first" / "T=0" plane in every dimension.

Valid ranges per axis:

0 <= X < Level.Grid().W
0 <= Y < Level.Grid().H
0 <= Z < Image.SizeZ()
0 <= C < Image.SizeC()
0 <= T < Image.SizeT()

Out-of-range values yield *TileError wrapping ErrDimensionUnavailable (axis is unsupported on this Image — SizeZ/C/T == 1) or ErrTileOutOfBounds (axis exists but the index is past its declared size).

Added in v0.7 alongside Level.TileAt and Image.SizeZ/SizeC/SizeT.

func (TileCoord) String

func (t TileCoord) String() string

type TileError

type TileError struct {
	Level int
	X, Y  int
	Err   error
}

TileError wraps a per-tile failure with the (level, x, y) that produced it. Consumers use errors.As to extract the coordinates and errors.Is against the exported sentinels to branch on the underlying cause.

func (*TileError) Error

func (e *TileError) Error() string

func (*TileError) Unwrap

func (e *TileError) Unwrap() error

type TilePos

type TilePos struct{ X, Y int }

TilePos is a (column, row) pair returned by Level.Tiles.

type TileResult

type TileResult struct {
	Bytes []byte
	Err   error
}

TileResult carries the yield from Level.Tiles.

type Tiler

type Tiler interface {
	Format() Format
	// Images returns the main pyramids carried by this file. Always
	// returns at least one Image; multi-image OME TIFF files expose
	// multiple. Index 0 corresponds to the legacy Levels() / Level(i)
	// shortcut accessors.
	//
	// Added in v0.6. Single-image formats (SVS, NDPI, Philips) return a
	// one-element slice wrapping their existing pyramid.
	Images() []Image
	// Levels is a shortcut for Images()[0].Levels(). Preserved from
	// pre-v0.6 callers; behaves identically on single-image formats.
	Levels() []Level
	// Level is a shortcut for Images()[0].Level(i).
	Level(i int) (Level, error)
	Associated() []AssociatedImage
	Metadata() Metadata
	ICCProfile() []byte

	// WarmLevel pre-warms the page cache for level i by touching one
	// byte per OS page covering this level's tile-data ranges. Under
	// mmap-backed [OpenFile] (the v0.9 default), this forces the
	// kernel to populate the page cache lazily on first call —
	// subsequent [Level.Tile] / [Level.TileInto] reads on level i hit
	// RAM at memory-bandwidth speed regardless of access pattern.
	//
	// Under [BackingPread], WarmLevel does effectively the same work
	// via a pread(1) per page. Slower, but the warm-up effect
	// (kernel page cache population) is the same.
	//
	// Returns [ErrLevelOutOfRange] if i is out of bounds. Returns
	// the first non-EOF read error encountered while touching pages
	// (typically I/O errors on the underlying file). nil on success.
	//
	// Best-effort: callers that want to ignore errors (it's a hint,
	// after all) can discard the result. Concurrent calls on
	// different levels are safe; concurrent calls on the same level
	// are safe but redundant.
	//
	// Added in v0.9 alongside the mmap-default [OpenFile] change.
	WarmLevel(i int) error

	Close() error
}

Tiler is the top-level handle returned by Open / OpenFile.

Concurrency contract:

  • All accessor methods (Format, Images, Levels, Level, Associated, Metadata, ICCProfile) are safe to call concurrently from any number of goroutines after Open. They return immutable views of state populated at Open time.

  • Tile reads via Level.Tile and Level.TileInto are safe to call concurrently. SVS / Philips / OME tiled / BIF / IFE have no internal locks on the tile hot path — concurrency is bounded only by the OS page cache (under BackingMmap, the v0.9 default) or by file-descriptor syscall throughput (under BackingPread). NDPI's striped reader takes a per-page mutex on the assembled- frame cache: concurrent reads of *different* pages run in parallel; concurrent reads of the *same* page serialize. OME OneFrame uses a similar per-level extended-frame cache.

  • Bytes returned by Tile() are caller-owned: opentile-go does not retain a reference, and callers may modify them (typical pattern is to write them straight to a network response). Bytes written by TileInto into the caller-provided dst remain caller-owned.

  • WarmLevel is a hint — safe under any concurrency, returns an error only if the underlying I/O fails or if i is out of bounds.

  • Close() must not race with in-flight tile reads. Under BackingMmap this is non-negotiable: closing unmaps the file, and subsequent reads through the mapping raise SIGBUS. Callers that need lifecycle management beyond a synchronous read loop should hold a reader lock around tile reads or sequence Close after a wait group on outstanding readers.

func Open

func Open(r io.ReaderAt, size int64, opts ...Option) (Tiler, error)

Open parses r as a WSI file and returns a Tiler for the matching format. size is the total file size in bytes.

Dispatch order: each registered factory's SupportsRaw is queried first against the raw byte stream; if any factory takes it, the input is never handed to tiff.Open. Otherwise, r is parsed as TIFF / BigTIFF and each factory's Supports is queried against the parsed *tiff.File. The first non-TIFF format using the SupportsRaw path is Iris IFE in v0.8.

func OpenFile

func OpenFile(path string, opts ...Option) (Tiler, error)

OpenFile opens path for reading and delegates to Open. The returned Tiler owns the underlying file handle (or memory map); Close releases it.

Default backing since v0.9 is BackingMmap: the file is memory-mapped read-only and tile reads become userspace memcpys from the mapped region — no `pread(2)` syscall per Level.Tile call. The kernel page cache handles paging in tile-data regions on first access; warm-cache reads hit RAM at memory-bandwidth speed.

Pass WithBacking(BackingPread) to opt out and use the v0.8 (and earlier) os.File + pread path. Required for filesystems that don't support mmap (some FUSE / network mounts) or when the caller specifically needs os.File truncation semantics.

Failure modes:

  • mmap unavailable for this file (filesystem doesn't support it, or some platform-specific failure): returns ErrMmapUnavailable wrapping the underlying error. Callers wanting automatic fallback should retry with WithBacking(BackingPread).
  • file truncated underneath an open mmap-backed Tiler: subsequent Tile() calls into the truncated region raise SIGBUS in the calling thread. WSI files don't get truncated under normal use; if your storage allows it, use BackingPread.

Directories

Path Synopsis
Package decoder defines the public Decoder interface and registry for opentile-go's pluggable codec layer.
Package decoder defines the public Decoder interface and registry for opentile-go's pluggable codec layer.
all
Package all blank-imports every decoder subpackage so all codecs register at init() time.
Package all blank-imports every decoder subpackage so all codecs register at init() time.
avif
Package avif implements the AVIF decoder via libavif.
Package avif implements the AVIF decoder via libavif.
deflate
Package deflate implements the decoder for TIFF Compression=8 (Deflate/Zip).
Package deflate implements the decoder for TIFF Compression=8 (Deflate/Zip).
htj2k
Package htj2k implements the HTJ2K (High-Throughput JPEG 2000) decoder via OpenJPH (https://github.com/aous72/OpenJPH).
Package htj2k implements the HTJ2K (High-Throughput JPEG 2000) decoder via OpenJPH (https://github.com/aous72/OpenJPH).
jpeg
Package jpeg implements the JPEG decoder via libjpeg-turbo.
Package jpeg implements the JPEG decoder via libjpeg-turbo.
jpeg2000
Package jpeg2000 implements the JPEG 2000 decoder via openjp2.
Package jpeg2000 implements the JPEG 2000 decoder via openjp2.
jpegxl
Package jpegxl implements the JPEG-XL decoder via libjxl.
Package jpegxl implements the JPEG-XL decoder via libjxl.
lzw
Package lzw implements the decoder for TIFF Compression=5 (LZW).
Package lzw implements the decoder for TIFF Compression=5 (LZW).
none
Package none implements the trivial "no-compression" decoder for TIFF Compression=1 tiles, where the on-disk bytes ARE the decoded pixels.
Package none implements the trivial "no-compression" decoder for TIFF Compression=1 tiles, where the on-disk bytes ARE the decoded pixels.
webp
Package webp implements the WebP decoder via libwebp.
Package webp implements the WebP decoder via libwebp.
formats
all
Package all registers every format implemented by opentile-go.
Package all registers every format implemented by opentile-go.
bif
Package bif implements opentile-go format support for Ventana BIF (BioImagene Image File) — a BigTIFF dialect produced by Roche's VENTANA DP scanner family (DP 200, DP 600) and predecessor iScan scanners (Coreo, HT).
Package bif implements opentile-go format support for Ventana BIF (BioImagene Image File) — a BigTIFF dialect produced by Roche's VENTANA DP scanner family (DP 200, DP 600) and predecessor iScan scanners (Coreo, HT).
cogwsi
Package cogwsi reads Cloud Optimized GeoTIFF for WSI (COG-WSI) files — a strict COG extension produced by the wsitools transcoder (cornish/wsitools).
Package cogwsi reads Cloud Optimized GeoTIFF for WSI (COG-WSI) files — a strict COG extension produced by the wsitools transcoder (cornish/wsitools).
generictiff
Package generictiff implements opentile-go format support for generic tiled pyramidal TIFF — the catch-all reader for tiled WSI TIFFs without vendor metadata.
Package generictiff implements opentile-go format support for generic tiled pyramidal TIFF — the catch-all reader for tiled WSI TIFFs without vendor metadata.
ife
Package ife implements opentile-go format support for Iris File Extension (IFE) v1.0 — the IrisDigitalPathology bleeding-edge non-TIFF WSI container.
Package ife implements opentile-go format support for Iris File Extension (IFE) v1.0 — the IrisDigitalPathology bleeding-edge non-TIFF WSI container.
leicascn
Package leicascn implements opentile-go format support for Leica SCN — a BigTIFF dialect produced by Leica SCN400 / SCN400F scanners (production discontinued ~2015).
Package leicascn implements opentile-go format support for Leica SCN — a BigTIFF dialect produced by Leica SCN400 / SCN400F scanners (production discontinued ~2015).
ndpi
Package ndpi implements opentile-go format support for Hamamatsu NDPI files.
Package ndpi implements opentile-go format support for Hamamatsu NDPI files.
ometiff
Package ometiff implements opentile-go format support for OME TIFF files — a TIFF dialect carrying OME-XML metadata in the first page's ImageDescription, with reduced-resolution pyramid levels stored as TIFF SubIFDs of the base page.
Package ometiff implements opentile-go format support for OME TIFF files — a TIFF dialect carrying OME-XML metadata in the first page's ImageDescription, with reduced-resolution pyramid levels stored as TIFF SubIFDs of the base page.
philipstiff
Package philipstiff reads tiles from Philips IntelliSite Pathology Solution TIFF whole-slide images.
Package philipstiff reads tiles from Philips IntelliSite Pathology Solution TIFF whole-slide images.
svs
Package svs implements opentile-go format support for Aperio SVS files.
Package svs implements opentile-go format support for Aperio SVS files.
szi
Package szi reads Smart Zoom Image (.szi) files — ZIP-wrapped Microsoft Deep Zoom pyramids with scan-properties.xml and an associated_images/ folder.
Package szi reads Smart Zoom Image (.szi) files — ZIP-wrapped Microsoft Deep Zoom pyramids with scan-properties.xml and an associated_images/ folder.
internal
bifxml
Package bifxml parses the XMP metadata embedded in Ventana BIF TIFF IFDs.
Package bifxml parses the XMP metadata embedded in Ventana BIF TIFF IFDs.
cog
Package cog parses the GDAL Cloud Optimized GeoTIFF ghost-area (the contiguous block of ASCII key-value metadata immediately following the TIFF header).
Package cog parses the GDAL Cloud Optimized GeoTIFF ghost-area (the contiguous block of ASCII key-value metadata immediately following the TIFF header).
dzi
Package dzi parses the Microsoft Deep Zoom Image (DZI) manifest XML format and computes per-level / per-tile coordinate information.
Package dzi parses the Microsoft Deep Zoom Image (DZI) manifest XML format and computes per-level / per-tile coordinate information.
jpeg
Package jpeg provides marker-level JPEG bitstream manipulation sufficient to assemble valid JPEGs from TIFF-embedded scan fragments.
Package jpeg provides marker-level JPEG bitstream manipulation sufficient to assemble valid JPEGs from TIFF-embedded scan fragments.
jpegturbo
Package jpegturbo provides a minimal cgo wrapper over libjpeg-turbo's tjTransform operation, scoped to the lossless MCU-aligned JPEG crop that opentile-go needs for one-frame NDPI pyramid levels and NDPI label cropping.
Package jpegturbo provides a minimal cgo wrapper over libjpeg-turbo's tjTransform operation, scoped to the lossless MCU-aligned JPEG crop that opentile-go needs for one-frame NDPI pyramid levels and NDPI label cropping.
oneframe
Package oneframe provides shared infrastructure for reading "single big JPEG, virtualised into tile cells" pyramid pages — used by NDPI's non-tiled levels (formats/ndpi/oneframe.go pre-v0.6) and OME-TIFF's reduced-resolution levels (formats/ometiff/oneframe.go in v0.6).
Package oneframe provides shared infrastructure for reading "single big JPEG, virtualised into tile cells" pyramid pages — used by NDPI's non-tiled levels (formats/ndpi/oneframe.go pre-v0.6) and OME-TIFF's reduced-resolution levels (formats/ometiff/oneframe.go in v0.6).
tiff
Package tiff parses a minimal subset of the TIFF file format sufficient to locate compressed tile byte ranges for whole-slide imaging TIFFs.
Package tiff parses a minimal subset of the TIFF file format sufficient to locate compressed tile byte ranges for whole-slide imaging TIFFs.
tifflzw
Package tifflzw implements the Lempel-Ziv-Welch compressed data format as used by the TIFF file format, including the "off by one" code-width transition that makes TIFF LZW incompatible with stdlib compress/lzw.
Package tifflzw implements the Lempel-Ziv-Welch compressed data format as used by the TIFF file format, including the "off by one" code-width transition that makes TIFF LZW incompatible with stdlib compress/lzw.
opentile
opentiletest
Package opentiletest provides test helpers for opentile consumers and the library's own internal tests.
Package opentiletest provides test helpers for opentile consumers and the library's own internal tests.
Package resample provides pure-Go pixel resamplers operating on decoder.Image.
Package resample provides pure-Go pixel resamplers operating on decoder.Image.
Package tests contains integration test helpers and fixture schemas shared between integration and fixture-generation tests.
Package tests contains integration test helpers and fixture schemas shared between integration and fixture-generation tests.
download command
download fetches openslide's public test slides into OPENTILE_TESTDIR.
download fetches openslide's public test slides into OPENTILE_TESTDIR.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL