A modern representation of the aging DICOM medical imaging standard

Encoding DICOM into Google’s Protocol Buffers

Photo by rawpixel on Unsplash

The DICOM (Digital Imaging and Communications in Medicine) standard has been a been the de facto representation of virtually all medical images created since the early 1990s.

This is generally a very good thing.

The adoption of an open and well-documented standard like DICOM is a remarkable feat in the otherwise siloed medical IT community (and showed a lot of foresight) —allowing medical device manufacturers, researchers, software vendors, and patients to work together with a single representation of a medical image!

However DICOM doesn’t come without some caveats:

  • it’s a long, custom, and complex specification (1000s of pages long) that continues to grow as modern use cases are reconciled with the standard. This makes it difficult and complicated for developers to start working with DICOM binary data from scratch, even though developers are used to working with other equally complex data in more modern and conventional formats (more on this later).
  • it has a fragmented developer tooling ecosystem. There are several different open source parsing implementations for some software languages, but they are not always consistent with one another and are in differing stages of completion, maintenance, and compliance. (PS — we’re working on a high performance golang DICOM parser too)

This can make it challenging to work with DICOMs consistently in microservice ecosystems, where services in different languages and using different libraries may need to work with data embedded in a given DICOM.

We also think it’ll be easier for us and our developer partners to build services to work with medical imaging data if dealing with DICOMs is abstracted away and if we could just work with a simple, familiar, and schema-based data structure.

Protocol Buffers to the Rescue

Interface Definition Languages (IDLs) like Apache’s Thrift and Google’s Protocol Buffers are very common ways to represent APIs and the structure of data that are passed around between programs today.

In fact, Google already uses the protocol buffer IDL in its tensorflow machine learning framework and has built protocol buffer representations for electronic health record APIs (but incidentally, nothing for medical images).

IDLs are great for many reasons:

  • they’re language agnostic — which means you can define a data structure as a protocol buffer and work with it natively in nearly any language (Python, Go, C++, Java, Javascript, etc)
  • they can be serialized to a compact binary representations that are efficient to transfer over the wire
  • they’re reusable and composable, so it’s easy to generate higher order data structures from existing IDL definitions.

This way of working with data representations is powerful, because it gives us an efficient machine-readable and language agnostic way to work with and pass around structured data (without worrying about parsing issues, fragmented libraries, or the DICOM spec).

Sample of what a protocol buffer data schema might look like

At Gradient Health, we’re already using gRPC and protocol buffers for structured service-to-service communication, so we decided to attempt generation of protocol buffer schemas for the entire DICOM specification. That way our microservices (and those that our developer partners integrate into our ecosystem) never have to work directly with DICOM, and can keep working with protocol buffers only.

You can see what we initially came up with here. Some example generated protocol buffers are below. All of the generator code and the protocol buffers themselves have been released under an MIT license so feel free to use them in your projects (or better yet — contribute back to the repo). This is still a work in progress.

The goal is to soon adapt our open source Golang DICOM parser into a microservice that will parse all DICOMs that enter our ecosystem into these convenient and familiar protocol buffers that will then be consumed by downstream microservices in our ecosystem as needed.

We can also take advantage of other gRPC capabilities by using protocol buffers — such as client and server side streaming RPCs to stream logical components of DICOMs around (like Frames encoded in the PixelData).

For any machine learning microservices we use, we expect that we will also be able to generate consistent tf.train.example tensorflow protocol buffers directly from these DICOMs as well.

Thanks for reading (and check out the protocol buffer examples below or on Github)! We strongly believe in open source here at Gradient Health, so we welcome your thoughts and contributions.

Who we are

At Gradient Health, we’re a passionate team of software engineers who love healthcare and modern software engineering. We’re on a mission to make it simple for researchers and developers to translate their technology into the clinic. Contact me at suyash@gradienthealth.ai if you want to learn more or just chat. Shoutout to Ouwen Huang for being an amazing friend, co-founder, and partner on this (and many other) projects.

Examples of generated protocol buffers

Below you can see some example generated protocol buffers. Find the initial full set here.

The attributes that are in DICOM modules are currently stored in one big Attributes.proto file that modules import, a snippet of which is below:

Certain attributes have the “SQ” sequence value representation, which essentially means that they can have children attributes. Such attribute sequence protocol buffers are stored in a Sequences.proto file for now, which looks something like this:

All “leaf” attributes live in Attributes.proto for now.