Siemens format DICOM with CSA header

Recent Siemens DICOM images have useful information stored in a private header. We’ll call this the CSA header.

CSA header

See this Siemens Syngo DICOM conformance statement, and a GDCM Siemens header dump.

The CSA header is stored in DICOM private tags. In the images we are looking at, there are several relevant tags:

(0029, 1008) [CSA Image Header Type]             OB: 'IMAGE NUM 4 '
(0029, 1009) [CSA Image Header Version]          OB: '20100114'
(0029, 1010) [CSA Image Header Info]             OB: Array of 11560 bytes
(0029, 1018) [CSA Series Header Type]            OB: 'MR'
(0029, 1019) [CSA Series Header Version]         OB: '20100114'
(0029, 1020) [CSA Series Header Info]            OB: Array of 80248 bytes

In our case we want to read the ‘CSAImageHeaderInfo’.

From the SPM (SPM8) code spm_dicom_headers.m

The CSAImageHeaderInfo and the CSA Series Header Info fields are of the same format. The fields can be of two types, CSA1 and CSA2.

Both are always little-endian, whatever the machine endian is.

The CSA2 format begins with the string ‘SV10’, the CSA1 format does not.

The code below keeps track of the position within the CSA header stream. We’ll call this csa_position. At this point (after reading the 8 bytes of the header), csa_position == 8. There’s a variable that sets the last byte position in the file that is sensibly still CSA header, and we’ll call that csa_max_pos.

CSA1

Start header

  1. n_tags, uint32, number of tags. Number of tags should apparently be between 1 and 128. If this is not true we just abort and move to csa_max_pos.

  2. unused, uint32, apparently has value 77

Each tag

  1. name : S64, null terminated string 64 bytes

  2. vm : int32

  3. vr : S4, first 3 characters only

  4. syngodt : int32

  5. nitems : int32

  6. xx : int32 - apparently either 77 or 205

nitems gives the number of items in the tag. The items follow directly after the tag.

Each item

  1. xx : int32 * 4 . The first of these seems to be the length of the item in bytes, modified as below.

At this point SPM does a check, by calculating the length of this item item_len with xx[0] - the nitems of the first read tag. If item_len is less than 0 or greater than csa_max_pos-csa_position (the remaining number of bytes to read in the whole header) then we break from the item reading loop, setting the value below to ‘’.

Then we calculate item_len rounded up to the nearest 4 byte boundary tp get next_item_pos.

  1. value : uint8, item_len.

We set the stream position to next_item_pos.

CSA2

Start header

  1. hdr_id : S4 == ‘SV10’

  2. unused1 : uint8, 4

  3. n_tags, uint32, number of tags. Number of tags should apparently be between 1 and 128. If this is not true we just abort and move to csa_max_pos.

  4. unused2, uint32, apparently has value 77

Each tag

  1. name : S64, null terminated string 64 bytes

  2. vm : int32

  3. vr : S4, first 3 characters only

  4. syngodt : int32

  5. nitems : int32

  6. xx : int32 - apparently either 77 or 205

nitems gives the number of items in the tag. The items follow directly after the tag.

Each item

  1. xx : int32 * 4 . The first of these seems to be the length of the item in bytes, modified as below.

Now there’s a different length check from CSA1. item_len is given just by xx[1]. If item_len > csa_max_pos - csa_position (the remaining bytes in the header), then we just read the remaining bytes in the header (as above) into value below, as uint8, move the filepointer to the next 4 byte boundary, and give up reading.

  1. value : uint8, item_len.

We set the stream position to the next 4 byte boundary.