File: Attributes.md

package info (click to toggle)
vtk-dicom 0.8.17-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,176 kB
  • sloc: cpp: 113,811; python: 2,041; makefile: 43; tcl: 10
file content (367 lines) | stat: -rw-r--r-- 14,578 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
## Attributes {#attributes}

@brief Managing DICOM meta data.

## Overview

The meta data can be loaded from a DICOM file in two ways: either via
the vtkDICOMReader class (which also reads the image data), or via the
vtkDICOMParser class (which reads only the meta data).  These classes
store the meta data in a vtkDICOMMetaData object, which provides storage
for all of the files in the DICOM series. The Get() method for this class,
which returns the value of a DICOM attribute, accepts a file index as a
parameter.  This allows one to choose the file in the series for which
to get the attribute.

~~~~~~~~{.cpp}
  // Get meta data from a vtkDICOMReader
  reader->UpdateInformation();
  vtkDICOMMetaData *meta = reader->GetMetaData();

  // Get the number of files for which "meta" holds meta-data
  int n = meta->GetNumberOfInstances();

  // Get EchoTime attribute for first file in series.
  if (meta->Has(DC::EchoTime))
  {
    int fileIndex = 0;
    double t = meta->Get(fileIndex, DC::EchoTime).AsDouble();
  }

  // If fileIndex is not given, attribute is retrieved from first file.
  std::string str = meta->Get(DC::SeriesDescription).AsString();
~~~~~~~~

Attributes are returned as a vtkDICOMValue object, which has methods
such as AsDouble(), AsInt() or AsString() that allow conversion of the
value to various C++ types.  If the DICOM file does not have the
requested attribute, then the returned value will be empty and
"value.IsValid()" will return "false".  Calling AsString() on an invalid
value will return the empty string, and likewise calling AsInt() or
AsDouble() on an invalid value will return 0.

## Per-slice meta data

As discussed in the Overview, a "file index" can be used when retrieving
an attribute from vtkDICOMMetaData.  It is important to note that this
is a file index and not a slice index.  The slices are sorted by spatial
location, which might be different from the file order.  Furthermore, a
single DICOM file might actually contain multiple slices, with each
slice stored in a different frame within the file.

The reader provides an array, the FileIndexArray, that can be used to
convert a "slice index" to a file index.  It also provides a
FrameIndexArray that can be used to convert a slice index to a frame
number.  Together, these can be used regardless of whether the there was
one slice per file, or one slice per frame in a multi-frame file: the
vtkDICOMMetaData object provides a Get() method that takes
both a file index and a frame index, along with the tag of the attribute
to be inspected.

~~~~~~~~{.cpp}
  // Read the files and get the meta data.
  reader->SetFileNames(fileNameArray);
  reader->Update();
  vtkDICOMMetaData *meta = reader->GetMetaData();

  // Get the arrays that map slice to file and frame.
  vtkIntArray *fileMap = reader->GetFileIndexArray();
  vtkIntArray *frameMap = reader->GetFrameIndexArray();

  // Get the file and frame for a particular slice.
  int sliceIndex = 6;
  int fileIndex = fileMap->GetComponent(sliceIndex, 0);
  int frameIndex = frameMap->GetComponent(sliceIndex, 0);

  // Get the position for that slice.
  vtkDICOMValue pv = meta->Get(fileIdx, frameIdx, DC::ImagePositionPatient);
  double position[3] = { 0.0, 0.0, 0.0 };
  if (pv.IsValid() && pv.GetNumberOfValues() == 3)
  {
    pv.GetValues(position, 3);
  }
~~~~~~~~

As a caveat, for multi-frame files, the example given above assumes
that the meta data contains a per-frame ImagePositionPatient attribute.
This is the case for enhanced multi-frame CT and MRI files, but not
for multi-frame nuclear medicine files.  Whenever retrieving meta data
from a DICOM image, it is wise to consult the DICOM standard to see
how the attributes are defined for the various modality-specific IODs
(information object descriptions).

## Per-component meta data

In the example in the previous section, the fileMap->GetComponent()
method was called with two arguments, but the second argument was set
to zero.
If the vtkDICOMReader assigned a vector dimension to the data, then
the the vtkImageData will have multiple scalar values in each voxel.
For instance, the first component in each voxel may have come from a
file that provided the real component of a complex-valued image, while
the second component from a file that provided the imaginary
component.  In this case, one would do the following to retrieve the
meta data from the "imaginary" file:

~~~~~~~~{.cpp}
  // Read the files and get the meta data.
  reader->SetFileNames(fileNameArray);
  reader->Update();
  vtkDICOMMetaData *metaData = reader->GetMetaData();

  // Get the arrays that map slice to file and frame.
  vtkIntArray *fileMap = reader->GetFileIndexArray();
  vtkIntArray *frameMap = reader->GetFrameIndexArray();

  // Get the file and frame for a particular slice and component.
  int sliceIndex = 6;
  int vectorIndex = 1; // 2nd component is the imaginary component
  int fileIndex = fileMap->GetComponent(sliceIndex, vectorIndex);
  int frameIndex = frameMap->GetComponent(sliceIndex, vectorIndex);

  // Get an attribute from the meta data.
  vtkDICOMValue v = metaData->Get(fileIdx, frameIdx, tag);
~~~~~~~~

If the data has a time dimension and the reader's TimeAsVectorOn() method
was called, then the components of each voxel can correspond both to a
specific time slot, and to a specific vector component.  To make the
situation even more complicated, each pixel in the DICOM files might
be an RGB pixel and therefore have three components as given
by the SamplesPerPixel attribute in the meta data.

The number of components in the FileIndexArray and FrameIndexArray is
equal to the vector dimension, and if TimeAsVectorOn() was called, then
the vector dimension will include the time dimension.  The FileIndexArray
and FrameIndexArray do not have components that correspond to the individual
R,G,B components in RGB images, since the R, G, and B components will always
have the same meta data because they always come from the same file and
frame.

The following strategy is recommended for accessing per-component meta
data in multi-dimensional images:

~~~~~~~~{.cpp}
  // Get the arrays that map slice to file and frame.
  vtkIntArray *fileMap = reader->GetFileIndexArray();
  vtkIntArray *frameMap = reader->GetFrameIndexArray();

  // Get the image data and meta data.
  vtkImageData *image = reader->GetOutput();
  vtkDICOMMetaData *meta = reader->GetMetaData();

  // Get the number of components in the data.
  int numComponents = image->GetNumberOfScalarComponents();

  // Get the full vector dimension for the DICOM data.
  int vectorDimension = fileMap->GetNumberOfComponents();

  // Compute the samples per pixel in original files.
  int samplesPerPixel = numComponents/vectorDimension;


  // Check for time dimension
  int timeDimension = reader->GetTimeDimension();
  if (timeDimension == 0)
  {
    timeDimension = 1;
  }

  // Get all attributes for a specific time.
  int vectorIndex = timeIndex*vectorDimension/timeDimension;
  int vectorEndIndex = (timeIndex + 1)*vectorDimension/timeDimension;

  for (int i = vectorIndex; i < vectorEndIndex; i++)
  {
    int fileIndex = fileMap->GetComponent(sliceIndex, i);
    int frameIndex = frameMap->GetComponent(sliceIndex, i);
    vtkDICOMValue v = meta->Get(fileIdx, frameIdx, tag);
    // print or display the value
  }

  // Extract an image at the desired time slot (e.g. for display).
  int componentIndex = timeIndex*vectorDimension/timeDimension*samplesPerPixel;
  vtkNew<vtkImageExtractComponents> extractor;
  extractor->SetInputConnection(reader->GetOutputPort());
  if (samplesPerPixel == 1)
  {
    extractor->SetComponents(componentIndex);
  }
  else if (samplesPerPixel == 2) // rare/nonexistent in DICOM images
  {
    extractor->SetComponents(componentIndex, componentIndex + 1);
  }
  else
  {
    extractor->SetComponents(componentIndex, componentIndex + 1, componentIndex + 2);
  }
  extractor->Update();
~~~~~~~~
## Nested data, tag paths, and multi-frame files

DICOM meta data can be nested.  For example, nesting is used to store
per-frame meta data for enhanced multi-frame DICOM files.  In order to
make it easy to access nested attributes, the vtkDICOMTagPath
class describes the full path to a nested attribute.

~~~~~~~~{.cpp}
  // Get an attribute for frame 3 of a multi-frame file.
  int frameIdx = 3;
  double echoTime = meta->Get(
    vtkDICOMTagPath(DC::PerFrameFunctionalGroupSequence, frameIdx,
                    DC::CardiacSynchronizationSequence, 0,
                    DC::NominalCardiacTriggerDelayTime)).AsDouble();
~~~~~~~~

This is rather verbose, so a more convenient method for accessing per-frame
data is provided for enhanced multi-frame files.  You can give the frame
index after the file index, in which case the Get() method
will perform a search for the attribute without requiring a full path.

~~~~~~~~{.cpp}
  // Get an attribute for frame 3 of an enhanced multi-frame file.
  int fileIdx = 0;
  int frameIdx = 3;
  vtkDICOMValue vw = meta->Get(fileIdx, frameIdx, DC::WindowWidth);
  vtkDICOMValue vc = meta->Get(fileIdx, frameIdx, DC::WindowCenter);
  if (vw.IsValid() && vc.IsValid())
  {
    // set the window for the image
  }
~~~~~~~~

The vtkDICOMMetaDataAdapter class can also be used to access enhanced
multi-frame files as if each frame was a separate file.

## Iterating over data elements

The vtkDICOMMetaData object also provides iterator-style access to the
data elements.  This is useful, for instance, when you want to iterate
through all of the elements in the meta data in sequential order.  It is
also useful if you want to check which attributes vary between files in
the series.

~~~~~~~~{.cpp}
  // Iterate through all data elements in the meta data.
  for (vtkDICOMDataElementIterator iter = meta->Begin(); iter != meta->End(); ++iter)
  {
    vtkDICOMTag tag = iter->GetTag();
    std::cout << "tag: " << tag << std::endl;
    // Crucial step: check for values that vary across the series.
    if (iter->IsPerInstance())
    {
      int n = iter->GetNumberOfInstances();
      for (int i = 0; i < n; i++)
      {
        std::cout << "instance " << i << ": " << iter->GetValue(i) << std::endl;
      }
    }
    else
    {
      // Not PerInstance: value is the same for all files in series.
      std::cout << "all instances: " << iter->GetValue() << std::endl;
    }
  }

  // Get the iterator to a specific element (hash table lookup).
  vtkDICOMDataElementIterator iter = meta->Find(DC::ImageOrientationPatient);
  if (iter != meta->End())
  {
    // do something
  }
~~~~~~~~

You might be surprised by the PerInstance check, but it is necessary due
to the fact that vtkDICOMMetaData holds the meta data for an entire series
of DICOM files.  Most attributes are the same across the series, but a
few attributes vary from one file to the next.  These per-file attributes
are identified when the file is read by vtkDICOMReader.

## Dictionaries

When iterating through the data elements in the meta data, as described
in the previous section, it can be useful to get information about
the meaning of the data elements that are encountered.  Complete information
can only be provided by the DICOM standards documents themselves, but the
vtkDICOMDictionary can at least provide a summary of what kind of data to
expect for a given attribute.

~~~~~~~~{.cpp}
  // do a dictionary lookup on a tag
  vtkDICOMDictEntry entry;
  entry = vtkDICOMDictionary::FindDictEntry(vtkDICOMTag(0x0008,0x0020));
  // check if entry was found in dictionary
  if (entry.IsValid())
  {
    std::cout << entry.GetName() << std::endl; // prints "StudyDate"
    std::cout << entry.GetVR() << std::endl; // prints "DA"
    std::cout << entry.GetVM() << std::endl; // prints "1"
  }

  // do a dictionary lookup by name
  entry = vtkDICOMDictionary::FindDictEntry("StudyDate");
  if (entry.IsValid())
  {
    std::cout << entry.GetTag() << std::endl; // prints "0008,0020"
  }
~~~~~~~~

The vtkDICOMDictionary class provides information for attributes that are
described in the DICOM standard, as well as information for private
attributes defined by medical device manufacturers.
Every DICOM file is likely to have a mix of standard attributes and private
attributes.  Fortunately, it is easy to tell the difference between the
two: private attributes always use a tag with with an odd group number,
while the DICOM standard only uses even group numbers.  The lookup of
private tags requires the name of the private dictionary.

~~~~~~~~{.cpp}
  // do a private dictionary lookup in GE dictionary GEMS_ACQU_01
  vtkDICOMDictEntry entry;
  entry = vtkDICOMDictionary::FindDictEntry("CellSpacing", "GEMS_ACQU_01");
  if (entry.IsValid())
  {
    std::cout << entry.GetTag() << std::endl; // prints "0019,0004"
    std::cout << entry.GetName() << std::endl; // prints "CellSpacing"
    std::cout << entry.GetVR() << std::endl; // prints "DS"
    std::cout << entry.GetVM() << std::endl; // prints "1"
  }
~~~~~~~~

## Private meta data

Because private tags are not registered with any central authority, there
is no guarantee that they are unique.  Instead, each private group within
a DICOM file contains 240 blocks (each with 256 elements) that can be
be individually reserved for elements belonging to a specific private
dictionary.  The details of how this is done are described in Part 5,
Section 7.8 of the DICOM standard.

The result of this is that private tags are of the form (gggg,xxee)
where "xx" is a hexadecimal value between `10` and `ff`
that identifies the block that was used to store the attribute.  The
tricky thing is that this value can vary from one DICOM file to the next,
though it is usually consistent within a single series.
Some people are surprised by this, because the first block (i.e. `10`)
is the only block that is used in most files.

To ensure that you are looking for private attributes in the correct
location (i.e. within the correct block), you must resolve each
private tag before using it.

~~~~~~~~{.cpp}
  // start with the tag in its "dictionary" form, with xx=00
  vtkDICOMTag ptag = vtkDICOMTag(0019,0004);
  // resolve the private tag (find out what block was reserved)
  ptag = meta->ResolvePrivateTag(ptag, "GEMS_ACQU_01");
  if (ptag == vtkDICOMTag(0xffff,0xffff))
  {
    // the special tag value ffff,ffff indicates that the tag could not be
    // resolved: no private block was reserved for dictionary GEMS_ACQU_01
  }
  else
  {
    // ptag will now be 0019,xx04 where "xx" is usually 10 (first block)
    double spacing = meta->Get(ptag).AsDouble();
  }
~~~~~~~~