Adding a New Slide Format to OpenSlide

To add a new format to OpenSlide, you will need to write a new vendor driver. When a slide is opened, the driver is responsible for parsing the slide file, loading its metadata, and locating its image tiles. At runtime, the driver receives requests for pixel data, determines which tiles to load, decodes them to a buffer in a Cairo-compatible pixel format, and renders them to a Cairo surface.

Your driver can use the grid module to map pixel coordinates to tile addresses:

Your driver should use the cache module to cache pixel data from decoded tiles. This prevents unnecessary decode operations if a region is accessed repeatedly.

OpenSlide contains support code for reading BMP, JPEG, JPEG 2000, PNG, TIFF, and TIFF-like images. You may be able to use these utilities without modification. If your driver requires additional decoding functionality, it should be added to a decoder module if it would be useful to other drivers, or implemented in your driver if not.

At runtime, your driver can receive concurrent read requests from multiple threads. Most drivers can handle this locklessly by fully initializing their data structures at open time (when they have exclusive access to the openslide_t) and then treating them as immutable at runtime. This approach requires that every read request operates on private (or thread-safe) instances of any necessary system resources, such as file handles.

Drivers are named after the vendor of the product that uses the format. This is often the manufacturer of the slide scanner.

For examples, consult the existing vendor drivers. generic-tiff is a straightforward driver for simple TIFF images. trestle is a fairly simple driver using the tilemap grid.

Opening a slide

Opening a slide occurs in two steps, implemented via function pointers in struct _openslide_format.

TIFF and TIFF-like

OpenSlide has two decoders for TIFF-derived slide formats: tiff and tifflike.

All detect and open methods receive a tifflike handle as an argument; this will be NULL if the file does not appear to be a TIFF. Because detect should be inexpensive, it should do its work using that tifflike handle, and should not use the tiff decoder. open, however, can use the tiff decoder if desired. Note that OpenSlide will close the tifflike handle when the open method completes, so the driver should copy any information it needs out of the tifflike handle during open.

Properties

The vendor driver is responsible for initializing a property map containing metadata about the slide. A property has a name (a string) and a value (another string). Property names defined by your driver should be prefixed by your driver’s name followed by a dot. You should create properties for any and all metadata supported by your slide format. If the slide format contains textual metadata formatted as key-value pairs, you may dump this metadata directly into the property map, retaining the original key names (after prepending the name of the driver). If you need to invent your own key names, you should use lowercase-with-hyphens formatting.

Drivers for TIFF-derived formats should also call _openslide_tifflike_init_properties_and_hash() to set some standard properties pertaining to TIFF files.

Your driver is also responsible for setting some standard properties, when they apply to your format:

openslide.background-color should be set with _openslide_set_background_color_prop().

The openslide.bounds-* properties should be set for formats that do not store image data for every pixel in the level. Drivers that use a single tilemap or range grid per level can set these properties with _openslide_set_bounds_props_from_grid().

openslide.mpp-x, openslide.mpp-y, and openslide.objective-power should be a copy of, or otherwise derived from, another vendor-specific property. The vendor-specific property should be the uninterpreted value from the slide file, while the openslide. property should be the validated or calculated value, often produced with _openslide_duplicate_{int,double}_prop().

The openslide.region[i].* properties should be set when a format can include image pyramids for multiple regions of the slide, and its driver handles this by combining the regions into a single main image. One group of dimension properties should be set for each region in the slide file, even if the slide contains only one region.

You should not set openslide.quickhash-1 or openslide.vendor directly.

For examples of properties produced by existing drivers, see the web demo.

quickhash-1

When opening a slide, OpenSlide calculates a “quickhash-1” which uniquely identifies that particular slide. (The quickhash-1 can be accessed via the openslide.quickhash-1 property.) It is implemented as a SHA-256 digest of a small amount of metadata and data from the slide file. It is not intended as a cryptographic hash over the entire file.

If you call _openslide_tifflike_init_properties_and_hash(), that function will calculate the quickhash-1 for you. Otherwise, you will need to handle this calculation yourself. Your open function will receive an _openslide_hash handle; you should use the _openslide_hash helper functions to add the data you wish to include in the quickhash-1.

You should carefully select the data and metadata to be included in the quickhash. Once your driver has been included in an OpenSlide release, it will be difficult or impossible to change the quickhash definition for your slide format. The quickhash should not cover too much data, so that it is quick to calculate, but should never produce the same value for two different slides. To accomplish this, it may be sufficient to hash all of the slide’s metadata; if not, you can include the (compressed) image data from the lowest-resolution pyramid level.

Associated images

In some slide formats, a slide file includes not only the primary, high-resolution slide image, but additional ancillary images. For example, there might be a thumbnail of the entire slide, or a photograph of its paper label. OpenSlide calls these “associated images” and provides an API to access them.

Where feasible, your driver should provide access to any associated images stored with the slide. Each associated image has a short name; existing names are:

label
the paper label (or barcode) glued to the slide
macro
a photograph of the entire slide, possibly extending beyond the edges of the glass
thumbnail
a low-resolution version of the primary image

Use your judgement in assigning names to the associated images supported by your slide format.

OpenSlide currently does not support pyramidal associated images; each associated image is loaded into memory in its entirety. If an associated image is pyramidal but is not too large, your driver can simply provide the highest-resolution level.

Memory allocation

OpenSlide uses the glib memory allocator, except where an external API requires the use of a different allocator. OpenSlide previously also used the glib slice allocator, but no longer does. Allocate structs with g_new0(struct some_struct, 1).

When allocating and freeing an object within the same function, use g_autoptr() or g_autofree to automatically free the object when it goes out of scope. This makes early returns simpler, since those paths don’t need to explicitly deallocate or goto a common cleanup label. g_autoptr() is suitable for structs with their own free functions, and g_autofree is for arbitrary memory that can be freed with g_free(). Variables declared with g_auto* must always be initialized on the spot (perhaps to NULL) to avoid freeing a garbage pointer; pre-commit checks include a test for this.

When allocating an object that will be returned from the function, or will be linked into data structures that outlive the function, prefer using g_auto* anyway. This can be skipped where the function cannot fail or the object cannot leak, but simplifies cleanup if the function has subsequent early returns. Use g_steal_pointer(&object) to remove object from the control of g_auto*.

There is also g_auto() for stack-allocated structs and allocated arrays of allocated strings. The latter is spelled g_auto(GStrv), and the former is used by a few OpenSlide APIs to support automatic cleanup, notably the TIFF handle cache.

Define a g_autoptr() cleanup function for any struct that would benefit from automatic cleanup. To do so, glib requires a typedef for the struct type, but OpenSlide does not use typedefs for its internal structs. The solution is to declare a typedef only for use with g_autoptr():

struct foo {
  ...
};

static void foo_free(struct foo *f) {
  ...
  g_free(f);
}

typedef struct foo foo;
G_DEFINE_AUTOPTR_CLEANUP_FUNC(foo, foo_free)

...

bool do_something(GError **err) {
  ...
  g_autoptr(foo) my_foo = g_new0(struct foo, 1);
  ...
}

Error handling

OpenSlide takes a conservative approach to error handling. Where feasible, a vendor driver should validate any slide metadata it is relying upon. If the driver finds evidence that its understanding of the slide format is incomplete – for example, required slide metadata is missing, or an enumerated field has an unknown value – it should report an error and give up rather than attempting to muddle onward.

All internal OpenSlide functions report errors using GError. GError has a strict set of rules that must be followed when producing or consuming errors.

When the external API glue receives a GError from a handler method, the openslide_t is placed into error state. No other operations can be performed on the openslide_t. The GError message is made available to the application via the openslide_get_error() API call.

When producing a GError, you may use whatever error domain and code is appropriate. If in doubt, use OPENSLIDE_ERROR_FAILED.

If you receive a GError from lower-level code and intend to propagate it, consider whether the error’s message provides enough context to diagnose the failure. If not, you should prefix the error using g_prefix_error() or g_propagate_prefixed_error().

Slow-path warnings

Sometimes a slide format will include an optional feature that allows a slide to be processed more efficiently. Files that do not contain this feature can still be read, but at a performance penalty. Similarly, image decoders may be designed with “fast paths” for common image parameters (for example, certain chroma subsampling ratios) and fallback paths for others. If it is impossible to determine from OpenSlide’s output whether the optimized or fallback path is being used – for example, by looking at a property value – the fallback code should emit a warning message so a bug in the decision-making code does not become a silent failure.

To emit a warning, use _openslide_performance_warn() (at open time) or _openslide_performance_warn_once() (at runtime). Warnings can be enabled by setting the OPENSLIDE_DEBUG environment variable to performance.

OpenSlide limitations

OpenSlide’s output is currently limited to three color channels plus an alpha channel, with a maximum of 8 bits per channel. These restrictions are partially due to Cairo limitations, but correcting them would also require changes to the OpenSlide API.

In addition, OpenSlide currently cannot return data from more than one focal plane per slide. This would also require API changes to fix.