Processing DICOM Files

Introduction

The DICOM module described here allows Iguana the ability to read and modify DICOM files.  If you have a DICOM requirement then contact us – we’re able to extend this support quickly to handle a real world problem with DICOM.

Installation (Windows)

The DICOM module is contained in two files: dicom.lua and dicom_raw.dll.  The DLL must be copied into Iguana’s installation folder (where iguana.exe is located).  The Lua module is added to your Translator projects as per usual.

Download: dicom-r3-win32.zip or dicom-r3-win64.zip.

In rare cases, you may get a “system error 14001” while the dicom_raw.dll is being loaded.  In such cases, you may need to install Microsoft’s VS2008 Redistributable for win32 or win64.

Installation (GNU/Linux)

The DICOM module for GNU/Linux is also contained in two files: dicom.lua and dicom_raw.so.  The .so file must be copied into Iguana’s working directory, which may differ from its installation directory if you use the –working_dir flag.  The Lua module is added to your Translator projects as per usual.

The module is currently only available for Ubuntu and those platforms that work with our Ubuntu Iguana builds: dicom-r3-linux32.tar.gz or dicom-r3-linux64.tar.gz.

Tutorial

If you wish, you can import this tutorial directly into Iguana: DICOM_Example_From_Translator.zip.  See also: How do I export and import code for a Translator instance from one Iguana to another?

require 'dicom'

This tutorial assumes you are familiar with DICOM—Wikipedia provides a good, quick overview of DICOM.  The DICOM module exposes one function, dicom.load(), that loads DICOM files.  It has many options to control loading when auto-detection does not work correctly, but for the most part is used like so:

local d = dicom.load('d:sample.dcm')

If you don’t already have some DICOM data, you can use this sample file: sample.dcm.

The resulting value, d, represents the DICOM file’s top-level dataset as a tree.  Each element is named according to the DICOM standard.  E.g., to extract the name of a patient, you would write:

local pn = d.PatientName.value

Values and Value Representations (VRs)

All values are returned as strings, even for numbers.  Lua will convert them to numbers if you try to do math with them or if you use tonumber().  You are free to assign numbers to DICOM values, however.

d.AcquisitionNumber.value = 1

This sort of assignment, to a .value field, will ensure the VR of the element is preserved, but it requires that the element already be present.  Assigning directly to the element will create the element if needed, and ensure the standard VR for it is used.

d.AcquisitionNumber = 1

In unusual cases where you wish to use a non-standard VR for a field, you can specify that during assignment, as follows.

d.AcquisitionNumber = { value=1, vr='SS' }

You can also change the VR of an existing element by simply changing the .vr field.  The old value will be reinterpreted for the new VR.

d.AcquisitionNumber.vr = 'SS'

Value Multiplicity (VM)

When an element has a VM greater than 1, the .value field will contain each value separated by backslashes.  E.g., “123” (the backslashes will appear doubled in the Translator’s annotations).

for x in d.Allergies.value:gmatch('[^]+') do
  check_allergy(x)
end

A .vm field is also available, but cannot be assigned to directly.  Assigning a value with embedded backslashes automatically adjusts the VM value for an element (except for some VRs like OB and OW where each byte or word is separated from the next with a backslash; for these types, VM is always 1 anyway).

local n_allergies = d.Allergies and d.Allergies.vm or 0

Sequences

DICOM sequences are very similar to arrays: they are indexed with integers inside brackets (e.g., x.SourceImageSequence[1]), but you cannot use # to get their length, or ipairs() to iterate over them.  Instead they have a :length() method and an :ipairs() method.

for i,v in x.SourceImageSequence:ipairs() do
  check_id(i, v.ReferencedSOPClassUID.value)
end

To insert new items into a sequence, use the :insert() method.  A single new item is created at the index supplied; existing items are shifted if need be.  More than one item can be appended in a single call, simply by specifying a location larger than :length()+1.

local n = x.SourceImageSequence:length()
x.SourceImageSequence:insert(n+2)  -- append two new items.

To remove an item from a sequence, use the :remove() method.  Any items after the item removed will be shifted to fill in the space.

x.SourceImageSequence:remove(1)

File Meta-data and Saving

You can use the :meta() method to access a DICOM file’s meta-data, the data describing the storage of the file itself.

local m = x:meta()
local ts = m.TransferSyntaxUID.value

Finally, if you want to store a modified DICOM file, you can use the :save() method.  There are several options to control how the file is written, but usually you just want to give it a file name:

x:save('d:modified.dcm')

Module Change Log

This section outlines the changes made in each revision.  It’s important to keep up-to-date, but I want to outline what changed so you can decide for yourself if an update is necessary.  Updating the DLL (or .so library in Linux) often means restarting Iguana, which may be difficult.  And changes to the Lua file need to be applied manually.  Oftentimes, however, if it works for you, you probably don’t need to update.

First Revision (r1)

  • dicom_raw.dll: Fixed issues with external library dependencies on Windows platforms.
  • dicom.lua: Fixed issue when inserting items into sequences.
  • dicom.lua: Iteration over an item, with :pairs(), will no longer include the :pairs() method itself.

Second Revision (r2) — Linux Only

  • dicom_raw.so: Rebuilt to work on Ubuntu releases as old as 8.04 LTS.

Third Revision (r3)

  • dicom_raw.*: Fixed issue with empty OB/OW/UN values not being retrievable.

Leave A Comment?