- About XNAT
- News & Events
- XNAT Marketplace
- Contact Us
This documentation references a series of code repositories that are components of the XNAT build process. For quick reference these repositories are located here:
The DICOM standard includes figures describing the "DICOM model of the real-world."
This is not identical to the XNAT data model, and in fact the XNAT data model originated before XNAT support existed for DICOM. (The MR scan and session types were based on Siemens Analyze, though they've become more DICOM-ish over time; PET was based on ECAT and still hasn't drifted much DICOMward.)
Apart from the differences in the data model, the concrete representation of XNAT's data model is vastly different from how DICOM is represented. DICOM metadata storage is massively denormalized, with every image file containing all of the metadata pertaining to that image; whereas the XNAT XML is a unified, compact representation of the experiment metadata. Notes: 1. this is the last time you will hear me call XML "compact"; 2. I'm asserting that the experiment XML is the canonical representation of XNAT metadata. You could argue for other canonical representations (e.g., the SQL database) but you'd be wrong.
The project dicom-xnat, composed of the subprojects dicom-xnat-mx, dicom-xnat-sop, and dicom-xnat-util, defines our mapping from the DICOM data model to XNAT, and from individual DICOM metadata elements to fields in an XNAT experiment document. The primary interface to dicom-xnat is through the class org.nrg.dcm.xnat.DICOMSessionBuilder, defined in dicom-xnat-mx. When the DICOM session builder is called, it walks the experiment file tree and creates a temporary HSQL database with one row per DICOM file and one column per DICOM attribute that might contribute to an XNAT field. (In some cases, this is instead one column per translated XNAT field value; this is common for HCP customizations that use fields parsed from Siemens shadow headers.) This database is then used to compute the XNAT metadata fields: by series for scan-level attributes, and by study for experiment-level attributes.
The session builder is normally run by XNAT when the C-STORE session timeout expires, or when the upload applet indicates that session transfer is complete. The session builder can also be invoked on archived sessions via the REST API, by issuing a (blank entity) PUT to the session resource with the query parameter pullDataFromHeaders=true .
To add a new XNAT attribute:
The heart of the DICOM-to-XNAT metadata translation code is the class DICOMSessionBuilder, defined in the project dicom-xnat-mx. Most of the action takes place in the method
call(), which returns a modality-specific subclass of XnatImagesessiondataBean:
setValues()instead of being quietly set in the bean like all other values.
The scan builder is broadly similar to the session builder, though there are differences in the details:
As a practical matter, most metadata translation customization can be done without modifying any of this base functionality. Changing the details of file storage (e.g., by replacing the current catalog file structure) will likely require changes to at least DICOMScanBuilder.
The translation operations that build individual XNAT attributes from DICOM metadata are specified in modality- (MR, PET, CT) and level- (session, scan, image) specific classes in the package org.nrg.dcm.xnat of dicom-xnat-mx.
For example, all session-level types (mrSessionData, ctSessionData) build on a base of attributes defined in ImageSessionAttributes. The attributes defined in this class are a representative sample of attribute translation definitions.
All attribute definitions get added to a class-static container
s using the method
add(). The simplest translations, copying the text from the DICOM field to the XNAT attribute, can be made with just the XNAT field name and the DICOM tag:
This is API shorthand for
s.add(new SingleValueTextAttr("acquisition_site"), new FixedDicomAttributeIndex(Tag.InstitutionName))
More complicated translations are defined in their own classes:
XnatAttrDef.Time convert from the idiosyncratic DICOM date and time types to the somewhat-less-weird XML/XSD types.
The more common point of customization is the modality-specific class. Let's consider MRScanAttributes:
The simplest attribute definition class is XnatAttrDef.Text, which is effectively just SingleValueTextAttr<DicomAttributeIndex>. Look at the SingleValueTextAttr source in ExtAttr to see how simple attribute construction works.
foldl(), which takes as argument the aggregated value so far (a), plus a map (m) of one group of relevant native attributes, and returns a new aggregated value that takes into account the value(s) in m.
Note to functional programming aficionados: foldl is misnamed, as this method really acts as the function argument to folds. Oops. Also, there is no defined order in which values are returned, so the operation should be at least commutative.
ais provided by the method
apply()is called to turn the aggregator into a final XNAT attribute value. Here this is (nearly) trivial because the aggregator is just the text value of the attribute.
A slightly more complicated case is
XnatAttrDef.Date, which needs to translate from DICOM date format to XSD.
foldl()is similar to the Text folder, in that it verifies that there is a single non-null value
apply()converts the DICOM date string to XSD date format
OrientationAttribute does a fairly complicated conversion from a single DICOM attribute (Image Orientation Patient)
foldl()does the math to convert DICOM to Analyze, while also checking for null or conflicting values
apply()turns the Siemens-style orientation string into an ExtAttrValue
VoxelResAttribute translates multiple DICOM fields into a single XNAT attribute
start()or has keys "x" "y" and "z"
foldl()extracts the x-,y-, and z- voxel resolutions from the DICOM fields Pixel Spacing and Slice Thickness and puts them into the aggregator map
apply()turns the aggregator map into an ExtAttrValue that has no text value but has attributes "x" "y" and "z"
start()is a Set
foldl()adds each TE value to the Set
apply()verifies that there are exactly two values, and returns the absolute value of the difference