developers, dicom

Hacking classical DICOM : A Hello World primer

This weekend, I am at PennApps, a fantastic hackathon event in Philadelphia. It got me thinking about sharing some simple steps to hit the ground running, with classic DICOM (notwithstanding all of the amazing advances of #DICOMweb).

DISCLAIMER: This information is provided as-is, the content might be incorrect or out of date, and could harm kittens or even worse if used improperly.

I will also continue to update this post as necessary.

Step 0 : Understanding enough about DICOM to start

Skip this section and come back to it as you follow the steps below. Below you’ll find some basic explainers of DICOM concepts. These are over-simplified to the point of technical inaccuracy – but I’ve added some DICOM-geek-spoilers to help technically clarify statements. If you’re just starting out, ignore those notes in red.

  • A medical image file can be referred to as a DICOM file.
    • Technically, the fact that we have actual files is just a matter of convenience / an implementation detail – DICOM is more about specifying the file structure and the transportation layer, and leave “how you save the files to your disk” up to you. 
  • A DICOM file is very much similar to a JPG or PNG, that you take from your camera. Unlike most image files, double-clicking them won’t open up those images – there isn’t a viewer available by default on computers today.
  • A DICOM file has headers and pixel data.
    • Headers are made up of a series of “parameters” (or tags, or attributes), a “parameter type” (like string, or integer), and a value / set of values. See here for a general list of tags (easy to use browser find to look at tag names).
  • A piece of medical equipment that captures medical images is sometimes called a “modality”. In most modalities, multiple images are taken as part of a medical exam. Here are a few examples:
    • X-rays are taken when you have fractured a bone. There’s usually a few different views taken.
    • Ultrasounds are taken when looking at softer tissue. Sometimes they are a series of snapshots taken over the course of the exam; other times they are a series of images in “real-time” (like watching the heart).
    • CTs and MRIs are used to see inside the body – and they are stored as “slices”. Think of your body being cut horizontally, millimetre by millimetre – each one of these is an image. There could be 64 slices, or there could be 10,000 slices or more.
  • Individual images that were captured as a group are collected into a “series”, for example all the CT slices that make up a brain volume. A number of series can then grouped into a study, which is what a doctor orders, for example a PET series and a CT series in a combined PET/CT scan of a tumor.

Step 1 : Get some images

There are a lot of places to grab images from. Google terms like “DICOM Sample Images” to find some. Here’s a couple of places to get you started:

These sites typically package DICOM images into a ZIP file for download, containing a set of folders (which represent each series), and each of those folders containing a set of DCM files. If the ZIP is a set of studies, there might be an additional layer (directory of studies, each containing directories of series, each containing directories of DCM files).

So, let’s assume you have some DICOM. Now, we need to have a look inside. Unzip them somewhere and remember where you saved them.

Step 2: Have a look at the images

For this step, you need an image viewer. There are a number of open-source viewers out there – here’s a couple :

Once you have installed viewer software, you can open up the DCM files you have saved from the previous step. Typically, viewers allow you to open up a “directory”, which will discover all related images in a series or study.

Once you’ve opened up a study, have a look around! Medical imaging is a fascinating and beautiful thing to behold.

Okay – so, now you have the data – but how do you unlock that information?

Step 3: Getting at the header and pixel data

Now we get to the programming aspect. For this, you need a library. There are a number of libraries available, for all sorts of languages. Server-side, I’ve been brought up on Java, so I use a library called DCM4CHE (start with the BIN download). It also has command line tools, so it is worthwhile even if ultimately you don’t want to use Java.

With DCM4CHE, there are a number of tools with command line apps pre-compiled with it. Two very quick wins with these tools will allow you to get header data (in XML or JSON), and getting the image housed within it.

Getting the header data

Use the command dcm2xml. You can pass in a DICOM file and it will spit out XML of all of the DICOM tags. It works very simply:

dcm2xml <path-to-dicom-file>/<file-name>

And this will write to the console the XML file. There is a lot of very useful information that can be gleaned by exploring the header data, such as learning about the patient and about the study being performed.

Although not documented on the site, I have seen a JSON rendition of this tool as well.

Getting the image (pixel data)

Use the command dcm2jpg, you can pass in a DICOM file and it will spit out a JPG for all your viewing needs. It also works very simply:

dcm2jpg <path-to-dicom-file>/<dicom-file-name> <path-to-jpg-file>/<dicom-jpg-name>

And this will drop the file as an image. Now, depending on your version of Java, you may need a JPG library that can actually encode the out as JPG.

Other DCM4CHE notes

  • When you actually use the image library inside of Java, you can use buffers and streams to more efficiently work with the pixel data (and header data, for what it’s worth).
  • There are many other tools to explore from here, including creating DICOM files, manipulating them, and transmitting them.

Other library options

There are other libraries like DCM4CHE depending on your needs and preferences. DVTk is another example.

Step 4: Do Awesome

Now that you have the basic building blocks of a DICOM image file, you can now begin to create imaging magic. It only gets better from here. Some resources to keep you moving forward:

dicom

Quick and dirty JavaScript DICOMweb UID generator

I thought I’d share this tidbit code, to create UIDs (Study Instance UID, SOP Insuance UID, etc) using Javascript, primarily for use with standards like DICOMweb STOW-RS and UPS-RS.

WARNING: This is not a proper implementation, and could result in UID collisions in your repositories. Do not use in production – you should use proper UID management procedures defined in  DICOM PS3.5 B.1.

var dicomUID = '2.25.xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
   var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
   return v.toString(10);
});

I put a functional example on jsFiddle.

See DICOM PS3.5 B.2 for the specification reference. The JavaScript inspiration came from an answer on StackOverflow.

dicom, healthcare API, interoperability

DICOMweb QIDO-RS vs FHIR ImagingStudy

Over the last year or two, there has been fantastic synergy between the DICOM and HL7 working groups, to harmonize the RESTful services being developed out of both standards organizations. DICOM has the DICOMweb services (of WADO-RS, QIDO-RS, and STOW-RS), and HL7 has FHIR. They both have representations of the diagnostic imaging study. They have been harmonized – but remain un-unified and distinct. To some, this may seem like a peculiar concept – why not just have one standard that works for everybody? Why not develop one interface, one method, one response, one object type?

To me, this approach of objects in both standards – is logical and perfectly justifiable. My personal, perhaps biased take comes down to one main thing – target audience. Following are the two extremes.
For the Electronic Medical Record (EMR) system, their bread and butter interoperability method is HL7. They are interested in “events”, and “observations” – i.e., patient is admitted, or patient had a diagnostic test. They know about available lab work, and happily render that information (procedure + value + unit + abnormal flag). They know about and happily render reports (straight text). They know orders. But what they don’t do, is imaging. Peering into DICOM is an entirely different beast compared to other clinical data object types. Knowing how to hang images within a study is a science (and art) all its own. EMRs want to know what studies are available, and then they can have a dedicated buddy (an image display application, such as an enterprise viewer or PACS) to render for them. EMRs may want to show thumbnails, but that’s likely about as deep as they want to go. FHIR ImagingStudy is for them – they get a high level overview in a standard they know, and let someone else dig deeper if necessary.

For the PACS Radiology system, their bread and butter interoperability method is DICOM. They work at the instance level, and build up from there. They break apart and digest DICOM headers, and render complicated multi-frame instances with thousands of parts, without even breaking a sweat. They may have knowledge about orders, but that information is tertiary and linked, at best. They have massive repositories filled with DICOM, the standard that has worked for decades. Knowing the basic metadata about the instances is simply not enough for all of the use cases – depending on the modality, the specialty, the vendor, the task – the full header information and the complete instances are needed. DICOMweb is for them – querying headers of choice via QIDO-RS and retrieving via WADO-RS – they get in-depth detail in a standard they already speak. Modalities, who typically only deal with classic DICOM, can even leverage DICOMweb with the benefit of a helping go-between service that can map their classic queries onto DICOMweb ones (i.e., via a proxy).

So those are the black and white cases. There’s a vast middle-ground, where either standard (or both) is entirely appropriate. It depends a lot on their “imaging use maturity”; software that already has an established imaging connection might choose to stick to DICOM and augment themselves with DICOMweb services. Software that is ready to be image-enabled (nurse call, patient portals, rounds) might choose FHIR. To a large extent, this is an undecided, under-discussion, case-by-case type of thing.

It is important to keep in mind – both QIDO-RS and FHIR’s ImagingStudy resource both point to WADO-RS (DICOMweb’s retrieval service) to retrieve the images or a specific rendering of them. So, for the “provider”, they will either need to implement the necessary DICOMweb services anyway, or have a buddy that already has this available to talk to. It is also important that, since both standards are built on top of technologies such as JSON and XML, can gracefully be augmented with additional data outside the standards (just add new tags; existing systems will gracefully ignore them).

Whether it is top-to-bottom or bottom-to-top, there’s value having both available for the necessary constituents. Making a PACS or VNA implement the breadth and depth of FHIR is quite heavy-handed and provides little benefit in the near future; for example, the FHIR Patient sub-resource requires information that is not available in DICOM headers and so makes it more complicated to obtain. If the information is truly needed, systems can ask the appropriate source of truth for this. Similarly, finding EMR systems that speak DICOM has historically been quite challenging and other solutions to this problem (i.e., having a image viewing application buddy) have already taken hold.

It is hard for me to say what the future will hold – I’d check out Don Dennison’s article, entitled PACS 2018: An Autopsy, as a start. If one will eventually “win”, it surely won’t happen anytime soon. What I can say, though – the very fact that we can have these conversations – it truly is an exciting time for the integrator, and a transformative one for healthcare.

dicom

A DICOMweb Quick Start Guide

I wrote this example on my unofficial DICOMweb documentation page, but I felt it would be warranted to include it here after my last post. As an integrator, I find that this one-pager sort of example, with relevant use case, is very helpful. Granted, when one considers conformance and adherence to standards, referring to just an example like mine won’t be very helpful – but in order to “get into” the mindset of RESTful image access, to level set what will be required for integration – this is immensely helpful, and I’d argue to be a requirement in order to drive developer adoption. This sort of “quick start” documentation is prevalent in many of the most successful APIs – for example, have a look at Facebook’s quick start guide. It is a differentiator. Anyhow, on to the example.

By the way, at the bottom I will include references to the RESTful standards in DICOM. Some of the content (especially the actual data responses) come from that text before I modified them for the example.

DICOMweb Quick Start Guide

An oncologist, Karen Smith, is seeing patients in her clinic, and would like background on the patients she is seeing today. Her first patient of the day, Alex Thompson. has arrived. She launches her imaging software, and makes a query on Alex using his last name. Her imaging software makes a QIDO-RS query at the study level, to retrieve a list of studies for Alex. She finds one result, a CT of the abdomen, and decides she would like to view it. She launches it in her imaging software, which makes a WADO-RS query to download the images for the study. Once downloaded, Karen views the images, and decides to photograph a suspicious lesion. She captures a picture, and uploads it into her imaging software. The imaging software then makes aSTOW-RS request to store the image against the imaging repository.

QIDO-RS Query

Corresponding to “Karen’s imaging software makes a QIDO-RS query at the study level, to retrieve a list of studies for Alex”, this step demonstrates how the Imaging Software constructs a URL for this query:

GET https://dicomweb.myhospital.com/studies/?00100010=THOMPSON&includefield=00081030
Accept: application/json

Which returns the following JSON response:

[
	{
	    "00080005": {
	        "vr": "CS",
	        "Value": [
	            "ISO_IR192"
	        ]
	    },
	    "00080020": {
	        "vr": "DT",
	        "Value": [
	            "20130409"
	        ]
	    },
	    "00080030": {
	        "vr": "TM",
	        "Value": [
	            "131600.0000"
	        ]
	    },
	    "00080050": {
	        "vr": "SH",
	        "Value": [
	            "11235813"
	        ]
	    },
	    "00080056": {
	        "vr": "CS",
	        "Value": [
	            "ONLINE"
	        ]
	    },
	    "00080061": {
	        "vr": "CS",
	        "Value": [
	            "CT"
	        ]
	    },
	    "00080130": {
	        "vr": "LO",
	        "Value": [
	            "Abdomen CT"
	        ]
	    },
	    "00080090": {
	        "vr": "PN",
	        "Value": [
	            {
	                "Alphabetic": {
	                    "Family": [
	                        "SMITH"
	                    ],
	                    "Given": [
	                        "KAREN"
	                    ]
	                }
	            }
	        ]
	    },
	    "00100010": {
	        "vr": "PN",
	        "Value": [
	            {
	                "Alphabetic": {
	                    "Family": [
	                        "THOMPSON"
	                    ],
	                    "Given": [
	                        "ALEX"
	                    ]
	                }
	            }
	        ]
	    },
	    "00100020": {
	        "vr": "LO",
	        "Value": [
	            "12345"
	        ]
	    },
	    "00100030": {
	        "vr": "DT",
	        "Value": [
	            "19670701"
	        ]
	    },
	    "00100040": {
	        "vr": "CS",
	        "Value": [
	            "M"
	        ]
	    },
	    "0020000D": {
	        "vr": "UI",
	        "Value": [
	            "1.2.392.200036.9116.2.2.2.1762893313.1029997326.945873"
	        ]
	    },
	    "00200010": {
	        "vr": "SH",
	        "Value": [
	            "11235813"
	        ]
	    },
	    "00201206": {
	        "vr": "IS",
	        "Value": [
	            4
	        ]
	    },
	    "00201208": {
	        "vr": "IS",
	        "Value": [
	            942
	        ]
	    }
	    "00081190": {
	        "vr": "UT",
	        "Value": [
	            "https://dicomweb.myhospital.com/studies/1.2.392.200036.9116.2.2.2.1762893313.1029997326.945873"
	        ]
	    }
	}
]

By the way, you’ll note that the JSON tag names are in a numbered format. This is called a DICOM attribute tag, and it is in hexadecimal. I created a handy JSON lookup object here.

WADO-RS Query

Corresponding to “Karen launches it in her imaging software, which makes a WADO-RS query to download the images for the study”, this step demonstrates how the Imaging Software constructs a URL for this query: (using the URL passed from the QIDO-RS query):

GET https://dicomweb.myhospital.com/studies/1.2.392.200036.9116.2.2.2.1762893313.1029997326.945873
Accept: multipart/related; type=image/jpeg

Which returns a multipart response of JPEGs representing each image in the study.

STOW-RS Query

Corresponding to “the imaging software then makes a STOW-RS request to store the image against the imaging repository.”, this step demonstrates how the Imaging Software constructs a URL for this query.

POST https://dicomweb.myhospital.com/studies/2.25.329800735698586629295641978511506172918 
Content-Type: multipart/related; type=application/dicom+xml; boundary=MESSAGEBOUNDARY

--MESSAGEBOUNDARY
Content-Type: application/dicom+xml

<?xml version="1.0" encoding="UTF-8"?>
<NativeDicomModel>
	<DicomAttribute Tag="00080020" VR="DT" Keyword="StudyDate">
		<Value number="1">20130409</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080030" VR="TM" Keyword="StudyTime">
		<Value number="1">131600.0000</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080050" VR="CS" Keyword="AccessionNumber">
		<Value number="1">98765</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080056" VR="CS" Keyword="InstanceAvailability">
		<Value number="1"ONLINE</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080061" VR="CS" Keyword="ModalitiesInStudy">
		<Value number="1">XC</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080090" VR="PN" Keyword="ReferringPhysiciansName">
		<PersonName number="1">
			<SingleByte>
				<FamilyName>SMITH</FamilyName> 
				<GivenName>KAREN</GivenName>
			</SingleByte>
		</PersonName>
	</DicomAttribute>
	<DicomAttribute Tag="00100010" VR="PN" Keyword="PatientName">
		<PersonName number="1">
			<SingleByte>
				<FamilyName>THOMPSON</FamilyName> 
				<GivenName>ALEX</GivenName>
			</SingleByte>
		</PersonName>
	</DicomAttribute>
	<DicomAttribute Tag="00100020" VR="CS" Keyword="PatientID">
		<Value number="1">12345</value>
	</DicomAttribute>
	<DicomAttribute Tag="00100030" VR="DT" Keyword="PatientsBirthDate">
		<Value number="1">19670701</value>
	</DicomAttribute>
	<DicomAttribute Tag="00100040" VR="CS" Keyword="PatientsSex">
		<Value number="1">MALE</value>
	</DicomAttribute>
	<DicomAttribute Tag="00200010" VR="SH" Keyword="StudyID">
		<Value number="1">98765</value>
	</DicomAttribute>
	<DicomAttribute Tag="0020000D" VR="UI" Keyword="StudyInstanceUID">
		<Value number="1">2.25.329800735698586629295641978511506172918</value>
	</DicomAttribute>
	<DicomAttribute Tag="0020000E" VR="UI" Keyword="SeriesInstanceUID">
		<Value number="1">2.25.444800735698586629295641978511506172918</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080018" VR="UI" Keyword="SOPInstanceUID">
		<Value number="1">2.25.555800735698586629295641978511506172918</value>
	</DicomAttribute>
	<DicomAttribute tag="00081150" vr="UI" keyword="ReferencedSOPClassUID">
		<Value number="1">1.2.840.10008.1.2.4.50</Value>
	</DicomAttribute>
	<DicomAttribute Tag="7FE00010" VR="OB" Keyword="PixelData">
		<Value number="1">329800735</value>
	</DicomAttribute>
</NativeDicomModel>

--MESSAGEBOUNDARY
Content-Type: image/jpeg
Content-Location: 329800735

<binary JPG image data>

--MESSAGEBOUNDARY--

Which returns an XML response reporting on the result of storing this image:

200 OK
<?xml version="1.0" encoding="UTF-8"?>
<NativeDicomModel>
	<DicomAttribute Tag="00081199" VR="SQ" keyword="ReferencedSOPSequence">
		<Item number="1">

			<DicomAttribute tag="00081150" vr="UI" keyword="ReferencedSOPClassUID">
				<Value number="1">1.2.840.10008.1.2.4.50</Value>
			</DicomAttribute>
			<DicomAttribute tag="00081155" vr="UI" keyword="ReferencedSOPInstanceUID">
				<Value number="1">2.25.555800735698586629295641978511506172918</Value>
			</DicomAttribute>
			<DicomAttribute tag="00081190" vr="UT" keyword="RetrieveURL">
				<Value number="1">https://dicomweb.myhospital.com/studies/2.25.329800735698586629295641978511506172918/series/2.25.444800735698586629295641978511506172918/instances/2.25.555800735698586629295641978511506172918</Value>
			</DicomAttribute>
		</Item>
	</DicomAttribute>
	<DicomAttribute Tag="00081190" VR="UT" Keyword="RetrieveURL">
		<Value number="1">https://dicomweb.myhospital.com/studies/2.25.329800735698586629295641978511506172918</value>
	</DicomAttribute>
</NativeDicomModel>

References