go-openslide: Modern Go Bindings for OpenSlide 4.0

I recently shipped v0.1.0 of go-openslide — a CGO binding library for OpenSlide 4.0, the standard C library for reading whole slide images (WSI) in digital pathology. Here’s what it does and why I built it.

The Problem

Whole slide images are the gigapixel TIFF-family files produced by pathology scanners — the kind used in hospitals and research labs to digitize tissue sections for analysis. OpenSlide is the de facto open-source C library for reading them across all the major vendor formats (Aperio SVS, Hamamatsu NDPI, MIRAX, and several others).

Go has no good bindings for it. The handful of community libraries that exist all target OpenSlide 3.4.x, expose raw C pointers without any Go idioms, and haven’t been touched in years. More importantly, OpenSlide 4.0 shipped meaningful new API surface — ICC color profile support and a proper tile cache API — that none of the old bindings cover.

go-openslide is the 4.0-native replacement.

What’s in v0.1.0

Full OpenSlide 4.0 API coverage. Every C function is wrapped: level count and dimensions, region reading, associated images (label, macro, thumbnail), properties, ICC profiles, cache management, vendor detection.

Thread safety. The Slide type wraps the C handle behind a sync.RWMutex. Multiple goroutines can call ReadRegion concurrently — which matters a lot in a tile server context where you’re fielding many simultaneous HTTP requests against the same open handle.

Correct pixel handling. OpenSlide returns pre-multiplied ARGB pixels. The library converts these to straight-alpha image.NRGBA before handing them back to Go, so you get correct colors without having to think about it.

ICC color profiles. slide.ICCProfile() returns the embedded ICC profile bytes for slides that carry them, enabling color-accurate rendering on calibrated displays. This is an OpenSlide 4.0 exclusive feature and the main reason to care about the version bump.

Tile cache management. The library exposes the native OpenSlide tile pixel cache via NewCache / SetCache — a shared memory budget that keeps decoded tile pixels hot across multiple open slide handles, avoiding redundant disk I/O and decompression on repeated reads of the same region. A single cache instance can be attached to multiple slides, making it practical for a tile server that keeps several slides open simultaneously.

Deep Zoom generation. DeepZoomGenerator handles all the tile coordinate math and produces DZI XML manifests plus 256×256 JPEG or PNG tiles ready to serve to web viewers like OpenSeadragon. This is the layer that turns a raw slide file into something a browser can consume.

pkg-config integration. The CGO preamble uses #cgo pkg-config: openslide instead of hardcoded paths, so there’s no manual CGO_CFLAGS / CGO_LDFLAGS configuration required. Install OpenSlide via Homebrew or apt, and go build just works.

GitHub Actions CI. Every push builds OpenSlide 4.0 from source via Meson on Ubuntu, downloads a real CMU-1.tiff test slide, and runs the full test suite with the -race detector.

A Few Things I Learned Building It

CGO memory ownership is less complicated than it looks, but you have to be deliberate about it. The rule I kept coming back to: Go-managed buffers passed to synchronous C calls are fine; anything asynchronous or stored-for-later needs to be C-allocated. OpenSlide’s API is synchronous throughout, so make([]uint32, w*h) with &buf[0] passed to openslide_read_region is safe, but every Go string passed to C still needs C.CString + defer C.free.

The OpenSlide 4.0 include path change is subtle and breaks old code silently. The library used to be included as <openslide/openslide.h>; in 4.0 the pkg-config Cflags entry points directly into the openslide/ subdirectory, so the correct include is now just <openslide.h>. Worth calling out explicitly because every example on the internet still shows the old path.

Premultiplied alpha is a real footgun. image.RGBA in Go expects pre-multiplied data (which is why it exists), but most downstream uses — PNG encoding, compositing, passing to display APIs — want straight alpha. Using image.NRGBA and undoing the premultiplication in the conversion step sidesteps the entire class of washed-out-color bugs.

A Note on DICOM Support

OpenSlide 4.0 added DICOM WSI reading at the C library level, and since go-openslide calls straight through to openslide_open(), it may work with DICOM files if your OpenSlide installation was compiled with libdicom support. However, v0.1.0 makes no claim about DICOM compatibility — we don’t install libdicom in CI, we have no DICOM test slide in the test suite, and I haven’t validated it end to end. Claiming support without test coverage would be dishonest. Adding libdicom to the build environment, downloading a real DICOM WSI for integration testing, and explicitly verifying the full read path is on the roadmap for v0.2.0. If you need DICOM support today and want to help test it, contributions are welcome.

Github Link: https://github.com/mrmushfiq/go-openslide

Leave a Reply

Your email address will not be published. Required fields are marked *