dicom 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (8) hide show
  1. data/CHANGELOG +24 -0
  2. data/COPYING +674 -0
  3. data/DOCUMENTATION +66 -0
  4. data/README +57 -0
  5. data/lib/DObject.rb +439 -0
  6. data/lib/DRead.rb +1204 -0
  7. data/lib/dicom.rb +2 -0
  8. metadata +52 -0
@@ -0,0 +1,1204 @@
1
+ module DICOM
2
+ # Class for reading the data from a DICOM file:
3
+ class DRead
4
+
5
+ # Initialize the DRead instance.
6
+ def initialize(file_name)
7
+ # Variables that hold data that will be returned to the person/procedure using this class:
8
+ # Arrays that will hold information from the DICOM file:
9
+ @names = Array.new()
10
+ @labels = Array.new()
11
+ @types = Array.new()
12
+ @lengths = Array.new()
13
+ @values = Array.new()
14
+ @raw = Array.new()
15
+ # Explicitness (explicit (true) by default):
16
+ @explicit = true
17
+ # Explicitness of the remaining groups after the first group:
18
+ @rest_explicit = true
19
+ # Variable to keep track of whether the current sequence being read have length specified or not:
20
+ @sq_length = false
21
+ # Variable to keep track of whether the image pixel data in this file are compressed or not, and if it exists at all:
22
+ @compression = false
23
+ # Pixel data is color or greyscale?
24
+ @color = false
25
+ # Default endianness of start of DICOM files is little endian:
26
+ @file_endian=false
27
+
28
+ # Variables used internally when reading the dicom file:
29
+ # If tag does not exist in the library it is unknown:
30
+ @unknown = false
31
+ # Does the particular tag contain any information?
32
+ @content = true
33
+ # Check endianness of the system (false if little endian):
34
+ @sys_endian=check_sys_endian()
35
+ # Endianness of the remaining groups after the first group:
36
+ @rest_endian=false
37
+ # Use a "relationship endian" variable to guide reading of file (true if they are equal):
38
+ if @sys_endian == @file_endian
39
+ @endian = true
40
+ else
41
+ @endian = false
42
+ end
43
+
44
+ # Open file for binary reading:
45
+ @file = File.new(file_name, "rb")
46
+
47
+ # Establish relationship between tag adress and name:
48
+ load_library()
49
+
50
+ # Read the initial header of the file:
51
+ header=check_header()
52
+ if header == false
53
+ @file.close()
54
+ @file = File.new(file_name, "rb")
55
+ end
56
+
57
+ # Initiate the process to read tags:
58
+ tag = true
59
+ temp_check=true
60
+ while tag != false and temp_check== true do
61
+ tag=process_tag()
62
+ # Store the tag information in arrays:
63
+ if tag != false and @content == true
64
+ @names+=[tag[0]]
65
+ @labels+=[tag[1]]
66
+ @types+=[tag[2]]
67
+ @lengths+=[tag[3]]
68
+ @values+=[tag[4]]
69
+ @raw+=[tag[5]]
70
+ end
71
+ end
72
+ # Check the status of the pixel data:
73
+ check_pixel_status()
74
+ # Index of last element in tag arrays:
75
+ @lastIndex=@names.length-1
76
+ # Close the file as we are finished reading it:
77
+ @file.close()
78
+ end
79
+
80
+
81
+ # Returns the relevant information gathered from the read dicom procedure.
82
+ def return_data()
83
+ return [@names,@labels,@types,@lengths,@values,@raw,@compression,@color,@explicit, @file_endian]
84
+ end
85
+
86
+
87
+ # Checks the initial header of the DICOM file.
88
+ def check_header()
89
+ # According to the official DICOM standard, a DICOM file shall contain 128
90
+ # consequtive zero bytes followed by 4 bytes that spell the string 'DICM'.
91
+ # Apparently, some providers seems to skip this in their DICOM files.
92
+ # First 128 bytes should be zeroes:
93
+ bin1=@file.read(128)
94
+ str_header1=bin1.unpack('a' * 128).to_s
95
+ # Next 4 bytes should spell 'DICM':
96
+ bin2=@file.read(4)
97
+ str_header2=bin2.unpack('a' * 4).to_s
98
+ # If we dont have this expected header, we will still try to read it is a DICOM file.
99
+ if str_header2 != 'DICM' then
100
+ puts "Warning: The specified file does not contain the official DICOM header."
101
+ puts "Will try to read the file anyway, as some sources are known to skip the formal DICOM header."
102
+ # Some DICOM files skips group 2, which defines the structure of the DICOM file.
103
+ # This has only been observed in files that also skips the above part of the DICOM header.
104
+ # Check for skipped group 0002:
105
+ group_label=bin1.unpack('h4').to_s.reverse.upcase
106
+ if (group_label.include? "2")
107
+ #Assume the file starts with a group 0002 tag, as "normal".
108
+ # Assume a default transfer syntax: Implicit, Little Endian.
109
+ @explicit = false
110
+ @rest_explicit = false
111
+ @file_endian = false
112
+ @rest_endian = false
113
+ @compression = false
114
+ else
115
+ # Assume a default transfer syntax: Implicit, Little Endian.
116
+ # (Turns out I use the same settings as above, which makes this somewhat silly, but I'll leave it like this for now in case of any changes later)
117
+ @explicit = false
118
+ @rest_explicit = false
119
+ @file_endian = false
120
+ @rest_endian = false
121
+ @compression = false
122
+ puts "Warning: Group '0002' Transfer Syntax does not exist. Assuming Implicit, Little Endian."
123
+ end
124
+ return false
125
+ else
126
+ return true
127
+ end
128
+ end
129
+
130
+
131
+ # Checks the status of the pixel data that has been read from the DICOM file: whether it exists at all and if its greyscale or color.
132
+ # Modifies instance variable @color if color image is detected and instance variable @compression if no pixel data is detected.
133
+ def check_pixel_status()
134
+ # Check if pixel data is present:
135
+ pixel_pos = @labels.index("7FE0.0010")
136
+ if pixel_pos == nil
137
+ @compression = nil
138
+ return
139
+ end
140
+ # Check for color image:
141
+ col_string = get_value("0028.0004")
142
+ if col_string != false
143
+ if (col_string.include? "RGB") or (col_string.include? "COLOR") or (col_string.include? "COLOUR")
144
+ @color = true
145
+ end
146
+ end
147
+ end
148
+
149
+
150
+ # Governs the process of reading tags in the DICOM file.
151
+ def process_tag()
152
+ #STEP 1:
153
+ # Read the tag label, but do stop reading if indicated by this method.
154
+ @content = true
155
+ res=read_label()
156
+ if res == false
157
+ return false
158
+ end
159
+ if @content == false
160
+ return
161
+ end
162
+ # As we have a valid tag, extract the two pieces of information:
163
+ label=res[0]
164
+ pos=res[1]
165
+ # STEP 2:
166
+ # Continue reading the tag information: Byte type and length.
167
+ res=read_type_length(pos,label)
168
+ type=res[0]
169
+ length=res[1]
170
+ # For sequence type tag, check if the tag have length specified:
171
+ if type == "SQ"
172
+ if length == "UNDEFINED" or length.to_i == 0
173
+ @sq_length = false
174
+ else
175
+ @sq_length = true
176
+ end
177
+ end
178
+ # If length is undefined, do not continue to read tag data:
179
+ if length == "UNDEFINED"
180
+ if label == "7FE0.0010"
181
+ data = "(Encapsulated pixel data)"
182
+ name = "Encapsulated image"
183
+ else
184
+ data = "(Encapsulated data)"
185
+ name = "Encapsulated information"
186
+ end
187
+ return [name,label,type,length,data]
188
+ end
189
+ # Item related tags:
190
+ if type == "()"
191
+ # Get name ("Item"):
192
+ name = get_name(pos)
193
+ # If length is zero, just return:
194
+ if length == 0
195
+ type = ""
196
+ data = nil
197
+ return [name,label,type,length,data]
198
+ else
199
+ # If there is content, this may, in the case of an image, be the image data.
200
+ # Must insert the image's type here.
201
+ # Some times when this tag has a length, it does not have content in itself, but instead
202
+ # have content in a number of subtags.
203
+ if @sq_length == true
204
+ # Do nothing (keep the type as "()")
205
+ else
206
+ # Treat the item as containing image data:
207
+ type = "OW" # A more general approach should be implemented here.
208
+ end
209
+ end
210
+ end
211
+ # STEP 3:
212
+ # Finally read the tag data.
213
+ if @content == true
214
+ res=read_data(type,length,pos)
215
+ value = res[0]
216
+ raw = res[1]
217
+ name=get_name(pos)
218
+ # Check for the Transfer Syntax UID tag, and process it:
219
+ if label == "0002.0010"
220
+ process_syntax(value)
221
+ end
222
+ return [name,label,type,length,value,raw]
223
+ end
224
+ end
225
+ # END READ TAG
226
+
227
+
228
+ # Reads and returns TAG LABEL (4 first bytes of tag).
229
+ def read_label()
230
+ bin1=@file.read(2)
231
+ bin2=@file.read(2)
232
+ # Check if we have reached end of file before proceeding:
233
+ if bin1 == nil
234
+ return false
235
+ end
236
+ label1=bin1.unpack('h*').to_s.reverse.upcase
237
+ label2=bin2.unpack('h*').to_s.reverse.upcase
238
+ # Special treatment of tags that are of the first "0002" group:
239
+ if @sys_endian
240
+ # Rearrange the numbers:
241
+ label1 = label1[2..3]+label1[0..1]
242
+ label2 = label2[2..3]+label2[0..1]
243
+ # Has this been verified? Suspect unintended consequence.
244
+ end
245
+ # Process the label, by considering the endian-ness relationship, if are past the initial "0002" group:
246
+ if label1 != "0002"
247
+ # As we are past the initial little endian part of the file, update the file properties:
248
+ @file_endian = @rest_endian
249
+ @explicit = @rest_explicit
250
+ #Update the endian-relationship variable:
251
+ if @sys_endian == @file_endian
252
+ @endian = true
253
+ else
254
+ @endian = false
255
+ end
256
+ # Do we need to rearrange?
257
+ if @endian
258
+ # No action needed
259
+ else
260
+ # Need to rearrange the first and second part of each string:
261
+ label1 = label1[2..3]+label1[0..1]
262
+ label2 = label2[2..3]+label2[0..1]
263
+ end
264
+ end
265
+ # Join the label group and label element together to the final string:
266
+ label=label1+'.'+label2
267
+ # Find the position of this label in the array of library labels:
268
+ pos=@lib_labels.index(label)
269
+ # If no match found, this is an unknown header type. Inform the user:
270
+ if pos == nil
271
+ @unknown = true
272
+ # For identifying additions needed in the library:
273
+ #puts "Unknown header element: "+label
274
+ else
275
+ @unknown = false
276
+ end
277
+ # Return the label as well as the position of this label in the library:
278
+ return [label,pos]
279
+ end
280
+ # END TAG LABEL
281
+
282
+
283
+ # Reads and returns TAG TYPE (2 bytes) and TAG LENGTH (Varying length).
284
+ def read_type_length(pos,label)
285
+ # Structure will differ, dependent on whether we have explicit or implicit type of file:
286
+ # Explicit:
287
+ if @explicit == true
288
+ # Unfortunately, it seems we need to have a special case for item labels in the explicit scenario:
289
+ if label == "FFFE.E000" or label == "FFFE.E00D" or label == "FFFE.E0DD"
290
+ # For the item related tag, get the special type from library, then read the 4 byte length:
291
+ type = @lib_types[pos]
292
+ bin=@file.read(4)
293
+ length = get_SL(bin)
294
+ else
295
+ # Read tag type field (2 bytes), as long as we are not dealing with an item related tag:
296
+ bin=@file.read(2)
297
+ type=bin.unpack('a*').to_s
298
+ end
299
+ # Two (three) possible structures for value length here, dependent on tag type:
300
+ case type
301
+ when "OB","OW","SQ","UN"
302
+ # Two empty bytes should occur here, according to the standard:
303
+ bin=@file.read(2)
304
+ #length=bin.unpack('S*')[0]
305
+ # However, it may occur (??) that the length appears in this slot, and subsequently,
306
+ # the following 4 byte slot is not utilized:
307
+ #if length != 0
308
+ # Read value length (4 bytes):
309
+ bin=@file.read(4)
310
+ length=get_SL(bin)
311
+ #end
312
+ when "()"
313
+ #An empty entry for the item related tags (As it has already been processed).
314
+ # Do nothing.
315
+ # For all the other tag types:
316
+ else
317
+ # Read value length (2 bytes):
318
+ bin=@file.read(2)
319
+ length=get_US(bin)
320
+ end
321
+ else
322
+ #Implicit:
323
+ # If it is unknown, use the identifier "UN":
324
+ if pos == nil
325
+ type = "UN"
326
+ else
327
+ # Tag type is not specified in the file, try to retrieve it from the library:
328
+ type = @lib_types[pos]
329
+ end
330
+ # Read value length (4 bytes):
331
+ bin=@file.read(4)
332
+ length = get_SL(bin)
333
+ end
334
+ # For encapsulated data, the tag length will not be defined. To convey this,
335
+ # the hex sequence 'ff ff ff ff' is used (-1 converted to signed long).
336
+ if length == -1
337
+ length = "UNDEFINED"
338
+ elsif length%2 >0
339
+ # According to the DICOM standard, all tag lengths should be an even number.
340
+ # If it is not, there has probably been an error in the readout:
341
+ puts "Warning: Odd number of bytes occured. This is a violation of the DICOM standard."
342
+ end
343
+ return [type,length]
344
+ end
345
+ # END BYTE TYPE and TAG LENGTH
346
+
347
+
348
+ # Reads and returns TAG DATA (Varying length - determined at an earlier stage).
349
+ def read_data(type, length, pos)
350
+ # Treatment dependent on what type of information we are dealing with.
351
+ case type
352
+
353
+ # Normally these numbers tags will contain just one number, but in some cases,
354
+ # they contain multiple numbers. In such cases we will read each number and store
355
+ # them all in a string separated by "/".
356
+ # Unsigned long: (4 bytes)
357
+ when "UL"
358
+ if length <= 4
359
+ bin = @file.read(length)
360
+ data = get_UL(bin)
361
+ else
362
+ data = process_numbers(length, type, 4)
363
+ end
364
+
365
+ # Signed long: (4 bytes)
366
+ when "SL"
367
+ if length <= 4
368
+ bin = @file.read(length)
369
+ data = get_SL(bin)
370
+ else
371
+ data = process_numbers(length, type, 4)
372
+ end
373
+
374
+ # Unsigned short: (2 bytes)
375
+ when 'US'
376
+ if length <= 2
377
+ bin = @file.read(length)
378
+ data = get_US(bin)
379
+ else
380
+ data = process_numbers(length, type, 2)
381
+ end
382
+
383
+ # Signed short: (2 bytes)
384
+ when 'SS'
385
+ if length <= 2
386
+ bin = @file.read(length)
387
+ data = get_SS(bin)
388
+ else
389
+ data = process_numbers(length, type, 2)
390
+ end
391
+
392
+ # Floating point double: (8 bytes)
393
+ when "FD"
394
+ if length <= 8
395
+ bin = @file.read(length)
396
+ data = get_FD(bin)
397
+ else
398
+ data = process_numbers(length, type, 8)
399
+ end
400
+
401
+ # Unknown information, header element is not recognised from local database:
402
+ when 'UN'
403
+ bin=@file.read(length)
404
+ data=bin.unpack('H*')[0]
405
+
406
+ # A tag that contains items/elements (sequence of elements):
407
+ when 'SQ'
408
+ # The tag has no content in itself, the file starts directly on a new tag adress.
409
+ data="(sequence of elements)"
410
+
411
+ # Item tag:
412
+ when '()'
413
+ # Tag may have a length, but no content belonging to this tag itself. They are to be read
414
+ # for this item's subtags.
415
+ data = "(Sequence of tags)"
416
+
417
+ # The tag contains a tag adress (4 bytes):
418
+ when 'AT'
419
+ if length != 4
420
+ puts "warning: Unexpected tag length, expected 4 bytes for tag type 'AT'!"
421
+ end
422
+ temp=Array.new(4)
423
+ 4.times do |i|
424
+ bin=@file.read(1)
425
+ temp[i]=bin.unpack('H*')[0]
426
+ end
427
+ # Put together, mix up the order to get it correct:
428
+ data=temp[1].to_s+temp[0].to_s+"."+temp[3].to_s+temp[2].to_s
429
+ # This has not been tested with other than Little endian system/file:
430
+ if @file_endian or @system_endian
431
+ puts "Warning: Handling for tag type 'AT' has not been verified for other than default endianness."
432
+ end
433
+
434
+ # Binary data, used sometimes when we have encapsulated images:
435
+ when 'OB'
436
+ bin=@file.read(length)
437
+ data=bin.unpack('H*')[0]
438
+
439
+ # Image data:
440
+ when 'OW'
441
+ # We need to know what kind of bith depth the pixel data is saved with:
442
+ bit_depth=get_value('0028.0100')
443
+ # Proceed to read the image binary data:
444
+ bin=@file.read(length)
445
+ # Number of bytes used per pixel will determine how to unpack this:
446
+ case bit_depth
447
+ when 8
448
+ data=get_BYTE(bin) # Byte/Character/Fixnum (1 byte)
449
+ when 16
450
+ data=get_US(bin) # Unsigned short (2 bytes)
451
+ when 12
452
+ # 12 BIT SIMPLY NOT WORKING YET!
453
+ # This one is a bit more tricky to extract.
454
+ # Unknown if really working.
455
+ puts "Warning: Bit depth 12 is not working correctly at this time!"
456
+ data=Array.new(length)
457
+ (length).times do |i|
458
+ hex=bin.unpack('H3')
459
+ hex4="0"+hex[0]
460
+ num=hex[0].unpack('v')
461
+ data[i]=num
462
+ end
463
+ else
464
+ raise "Bit depth "+bit_depth.to_s+" has not received implementation in this procedure yet."
465
+ end
466
+
467
+ # For everything else, assume string type information:
468
+ when 'AE','AS','CS','DA','DS','IS','LO','LT','PN','SH','ST','TM','UI','VR'
469
+ bin=@file.read(length)
470
+ data=bin.unpack('a*').to_s
471
+ else
472
+ puts "Warning: Tag type "+type+" does not have a reading method assigned to it. Please update the source code."
473
+ bin=@file.read(length)
474
+ data=bin.unpack('H*')[0]
475
+ end
476
+ return [data,bin]
477
+ end
478
+ # END TAG DATA
479
+
480
+
481
+ # Returns tag name from library if tag is recognised, else returns 'Unknown Name'.
482
+ def get_name(pos)
483
+ if not @unknown
484
+ str_name=@lib_names[pos]
485
+ else
486
+ str_name='Unknown Name'
487
+ end
488
+ return str_name
489
+ end
490
+
491
+
492
+ # Returns the (processed) value of a DICOM tag based on an input tag label, category name or array index.
493
+ def get_value(id)
494
+ # Assume we have been fed a tag label:
495
+ pos=@labels.index(id)
496
+ # If this does not give a hit, assume we have been fed a tag name:
497
+ if pos==nil
498
+ pos=@names.index(id)
499
+ end
500
+ # If we still dont have a hit, check if it is a valid number within the array range:
501
+ if pos == nil
502
+ if (id.is_a? Integer)
503
+ if id >= 0 and id <= @lastIndex
504
+ # The id supplied is a valid position, return its corresponding value:
505
+ return @values[id]
506
+ else
507
+ return false
508
+ end
509
+ else
510
+ return false
511
+ end
512
+ else
513
+ # We have a valid position, return the value:
514
+ return @values[pos]
515
+ end
516
+ end
517
+
518
+
519
+ # Loads the library which links tag label with tag name and tag type.
520
+ # (Note that this library is not complete, it is based on information from the DICOM files I have available.)
521
+ # (TNV means type not verified with external source for the particular tag that these letters appear behind)
522
+ def load_library()
523
+ a=Array.new()
524
+ b=Array.new()
525
+ c=Array.new()
526
+ a+=['0002.0000'] and b+=['UL'] and c+=['Group Length']
527
+ a+=['0002.0001'] and b+=['OB'] and c+=['File Meta Information']
528
+ a+=['0002.0002'] and b+=['UI'] and c+=['Media Storage SOP Class UID']
529
+ a+=['0002.0003'] and b+=['UI'] and c+=['Media Storage SOP Instance UID']
530
+ a+=['0002.0010'] and b+=['UI'] and c+=['Transfer Syntax UID']
531
+ a+=['0002.0012'] and b+=['UI'] and c+=['Implementation Class UID']
532
+ a+=['0002.0013'] and b+=['SH'] and c+=['Implementation Version Name']
533
+ a+=['0002.0016'] and b+=['AE'] and c+=['Source Application Entity Title']
534
+ a+=['0008.0000'] and b+=['UL'] and c+=['Group Length']
535
+ a+=['0008.0005'] and b+=['CS'] and c+=['Specific Character Set']
536
+ a+=['0008.0008'] and b+=['CS'] and c+=['Image Type']
537
+ a+=['0008.0010'] and b+=['LO'] and c+=['Recognition Code'] #TNV
538
+ a+=['0008.0012'] and b+=['DA'] and c+=['Instance Creation Date']
539
+ a+=['0008.0013'] and b+=['TM'] and c+=['Instance Creation Time']
540
+ a+=['0008.0014'] and b+=['UI'] and c+=['Instance Creator UID']
541
+ a+=['0008.0016'] and b+=['UI'] and c+=['SOP Class UID']
542
+ a+=['0008.0018'] and b+=['UI'] and c+=['SOP Instance UID']
543
+ a+=['0008.0020'] and b+=['DA'] and c+=['Study Date']
544
+ a+=['0008.0021'] and b+=['DA'] and c+=['Series Date']
545
+ a+=['0008.0022'] and b+=['DA'] and c+=['Acquisition Date']
546
+ a+=['0008.0023'] and b+=['DA'] and c+=['Image Date']
547
+ a+=['0008.0030'] and b+=['TM'] and c+=['Study Time']
548
+ a+=['0008.0031'] and b+=['TM'] and c+=['Series Time']
549
+ a+=['0008.0032'] and b+=['TM'] and c+=['Acquisition Time']
550
+ a+=['0008.0033'] and b+=['TM'] and c+=['Image Time']
551
+ a+=['0008.0040'] and b+=['US'] and c+=['Data Set Type'] #TNV
552
+ a+=['0008.0041'] and b+=['LO'] and c+=['Data Set Subtype'] #TNV
553
+ a+=['0008.0050'] and b+=['SH'] and c+=['Accession Number']
554
+ a+=['0008.0060'] and b+=['CS'] and c+=['Modality']
555
+ a+=['0008.0064'] and b+=['CS'] and c+=['Conversion Type']
556
+ a+=['0008.0070'] and b+=['LO'] and c+=['Manufacturer']
557
+ a+=['0008.0080'] and b+=['LO'] and c+=['Institution Name']
558
+ a+=['0008.0081'] and b+=['ST'] and c+=['Institution Address']
559
+ a+=['0008.0090'] and b+=['PN'] and c+=['Referring Physician`s name']
560
+ a+=['0008.0201'] and b+=['SH'] and c+=['Timezone Offset From UTC']
561
+ a+=['0008.1010'] and b+=['SH'] and c+=['Station Name']
562
+ a+=['0008.1030'] and b+=['LO'] and c+=['Study Description']
563
+ a+=['0008.103E'] and b+=['LO'] and c+=['Series Description']
564
+ a+=['0008.1040'] and b+=['LO'] and c+=['Institutional Department Name']
565
+ a+=['0008.1050'] and b+=['PN'] and c+=['Performing Physician`s Name']
566
+ a+=['0008.1060'] and b+=['PN'] and c+=['Name of Physician(s) Reading Study']
567
+ a+=['0008.1070'] and b+=['PN'] and c+=['Operator`s name']
568
+ a+=['0008.1080'] and b+=['LO'] and c+=['Admitting Diagnoses Description']
569
+ a+=['0008.1090'] and b+=['LO'] and c+=['Manufacturer`s Model Name']
570
+ a+=['0008.1140'] and b+=['SQ'] and c+=['Referenced Image Sequence']
571
+ a+=['0008.1150'] and b+=['UI'] and c+=['Referenced SOP Class UID']
572
+ a+=['0008.1155'] and b+=['UI'] and c+=['Referenced SOP Instance UID']
573
+ a+=['0008.2120'] and b+=['SH'] and c+=['Stage Name']
574
+ a+=['0008.2122'] and b+=['IS'] and c+=['Stage Number']
575
+ a+=['0008.2124'] and b+=['IS'] and c+=['Number of Stages']
576
+ a+=['0008.2128'] and b+=['IS'] and c+=['View Number']
577
+ a+=['0008.212A'] and b+=['IS'] and c+=['Number of Views in Stage']
578
+ a+=['0008.2204'] and b+=['CS'] and c+=['Transducer Orientation']
579
+ a+=['0009.0000'] and b+=['UL'] and c+=['Group Length']
580
+ a+=['0010.0000'] and b+=['UL'] and c+=['Group Length']
581
+ a+=['0010.0010'] and b+=['PN'] and c+=['Patient`s Name']
582
+ a+=['0010.0020'] and b+=['LO'] and c+=['Patient ID']
583
+ a+=['0010.0030'] and b+=['DA'] and c+=['Patient`s Birth Date']
584
+ a+=['0010.0040'] and b+=['CS'] and c+=['Patient`s Sex']
585
+ a+=['0010.1000'] and b+=['LO'] and c+=['Other Patient IDs']
586
+ a+=['0010.1005'] and b+=['PN'] and c+=['Patient`s Birth Name']
587
+ a+=['0010.1010'] and b+=['AS'] and c+=['Patient`s Age']
588
+ a+=['0010.1020'] and b+=['DS'] and c+=['Patient`s Size']
589
+ a+=['0010.1030'] and b+=['DS'] and c+=['Patient`s Weight']
590
+ a+=['0010.2160'] and b+=['SH'] and c+=['Ethnic Group']
591
+ a+=['0010.21B0'] and b+=['LT'] and c+=['Additional Patient History']
592
+ a+=['0010.4000'] and b+=['LT'] and c+=['Patient Comments']
593
+ a+=['0018.0000'] and b+=['UL'] and c+=['Group Length']
594
+ a+=['0018.0010'] and b+=['LO'] and c+=['Contrast/Bolus Agent']
595
+ a+=['0018.0015'] and b+=['CS'] and c+=['Body Part Examined']
596
+ a+=['0018.0020'] and b+=['CS'] and c+=['Scanning Sequence']
597
+ a+=['0018.0021'] and b+=['CS'] and c+=['Sequence Variant']
598
+ a+=['0018.0022'] and b+=['CS'] and c+=['Scan Options']
599
+ a+=['0018.0023'] and b+=['CS'] and c+=['MR Acquisition Type']
600
+ a+=['0018.0024'] and b+=['SH'] and c+=['Sequence Name']
601
+ a+=['0018.0025'] and b+=['CS'] and c+=['Angio Flag']
602
+ a+=['0018.0050'] and b+=['DS'] and c+=['Slice Thickness']
603
+ a+=['0018.0060'] and b+=['DS'] and c+=['KVP']
604
+ a+=['0018.0070'] and b+=['IS'] and c+=['Counts Accumulated']
605
+ a+=['0018.0071'] and b+=['CS'] and c+=['Acquisition Termination Condition']
606
+ a+=['0018.0080'] and b+=['DS'] and c+=['Repetition Time']
607
+ a+=['0018.0081'] and b+=['DS'] and c+=['Echo Time']
608
+ a+=['0018.0082'] and b+=['DS'] and c+=['Inversion Time']
609
+ a+=['0018.0083'] and b+=['DS'] and c+=['Number of Averages']
610
+ a+=['0018.0084'] and b+=['DS'] and c+=['Imaging Frequency']
611
+ a+=['0018.0085'] and b+=['SH'] and c+=['Imaged Nucleus']
612
+ a+=['0018.0086'] and b+=['IS'] and c+=['Echo Number(s)']
613
+ a+=['0018.0087'] and b+=['DS'] and c+=['Magnetic Field Strength']
614
+ a+=['0018.0088'] and b+=['DS'] and c+=['Spacing Between Slices']
615
+ a+=['0018.0089'] and b+=['IS'] and c+=['Number of Phase Encoding Steps']
616
+ a+=['0018.0090'] and b+=['DS'] and c+=['Data Collection Diameter']
617
+ a+=['0018.0091'] and b+=['IS'] and c+=['Echo Train Length']
618
+ a+=['0018.0093'] and b+=['DS'] and c+=['Percent Sampling']
619
+ a+=['0018.0094'] and b+=['DS'] and c+=['Percent Phase Field of View']
620
+ a+=['0018.0095'] and b+=['DS'] and c+=['Pixel Bandwidth']
621
+ a+=['0018.1000'] and b+=['LO'] and c+=['Device Serial Number']
622
+ a+=['0018.1004'] and b+=['LO'] and c+=['Plate ID']
623
+ a+=['0018.1010'] and b+=['LO'] and c+=['Secondary Capture Device ID']
624
+ a+=['0018.1012'] and b+=['DA'] and c+=['Date of Secondary Capture']
625
+ a+=['0018.1014'] and b+=['TM'] and c+=['Time of Secondary Capture']
626
+ a+=['0018.1018'] and b+=['LO'] and c+=['Secondary Capture Device Manufacturer`s Model Name']
627
+ a+=['0018.1019'] and b+=['LO'] and c+=['Secondary Capture Device Software Version(s)']
628
+ a+=['0018.1020'] and b+=['LO'] and c+=['Software Version(s)']
629
+ a+=['0018.1030'] and b+=['LO'] and c+=['Protocol Name']
630
+ a+=['0018.1041'] and b+=['DS'] and c+=['Contrast/Bolus Volume']
631
+ a+=['0018.1044'] and b+=['DS'] and c+=['Contrast/Bolus Total Dose']
632
+ a+=['0018.1050'] and b+=['DS'] and c+=['Spatial Resolution']
633
+ a+=['0018.1062'] and b+=['IS'] and c+=['Nominal Interval']
634
+ a+=['0018.1063'] and b+=['DS'] and c+=['Frame Time']
635
+ a+=['0018.1081'] and b+=['IS'] and c+=['Low R-R Value']
636
+ a+=['0018.1082'] and b+=['IS'] and c+=['High R-R Value']
637
+ a+=['0018.1083'] and b+=['IS'] and c+=['Intervals Acquired']
638
+ a+=['0018.1084'] and b+=['IS'] and c+=['Intervals Rejected']
639
+ a+=['0018.1088'] and b+=['IS'] and c+=['Heart Rate']
640
+ a+=['0018.1090'] and b+=['IS'] and c+=['Cardiac Number of Images']
641
+ a+=['0018.1094'] and b+=['IS'] and c+=['Trigger Window']
642
+ a+=['0018.1100'] and b+=['DS'] and c+=['Reconstruction Diameter']
643
+ a+=['0018.1110'] and b+=['DS'] and c+=['Distance Source to Detector']
644
+ a+=['0018.1111'] and b+=['DS'] and c+=['Distance Source to Patient']
645
+ a+=['0018.1120'] and b+=['DS'] and c+=['Gantry/Detector Tilt']
646
+ a+=['0018.1130'] and b+=['DS'] and c+=['Table Height']
647
+ a+=['0018.1140'] and b+=['CS'] and c+=['Rotation Direction']
648
+ a+=['0018.1149'] and b+=['IS'] and c+=['Field of View Dimension(s)']
649
+ a+=['0018.1150'] and b+=['IS'] and c+=['Exposure Time']
650
+ a+=['0018.1151'] and b+=['IS'] and c+=['X-ray Tube Current']
651
+ a+=['0018.1152'] and b+=['IS'] and c+=['Exposure']
652
+ a+=['0018.1155'] and b+=['CS'] and c+=['Radiation Setting']
653
+ a+=['0018.1160'] and b+=['SH'] and c+=['Filter Type']
654
+ a+=['0018.1164'] and b+=['DS'] and c+=['Image Pixel Spacing']
655
+ a+=['0018.1170'] and b+=['IS'] and c+=['Generator Power']
656
+ a+=['0018.1190'] and b+=['DS'] and c+=['Focal Spot(s)']
657
+ a+=['0018.1200'] and b+=['DA'] and c+=['Date of Last Calibration']
658
+ a+=['0018.1201'] and b+=['TM'] and c+=['Time of Last Calibration']
659
+ a+=['0018.1210'] and b+=['SH'] and c+=['Convolution Kernel']
660
+ a+=['0018.1250'] and b+=['SH'] and c+=['Receiving Coil']
661
+ a+=['0018.1251'] and b+=['SH'] and c+=['Transmitting Coil']
662
+ a+=['0018.1260'] and b+=['SH'] and c+=['Plate Type']
663
+ a+=['0018.1261'] and b+=['LO'] and c+=['Phosphor Type']
664
+ a+=['0018.1310'] and b+=['US'] and c+=['Acquisition Matrix']
665
+ a+=['0018.1312'] and b+=['CS'] and c+=['Phase Encoding Direction']
666
+ a+=['0018.1314'] and b+=['DS'] and c+=['Flip Angle']
667
+ a+=['0018.1315'] and b+=['CS'] and c+=['Variable Flip Angle Flag']
668
+ a+=['0018.1316'] and b+=['DS'] and c+=['SAR']
669
+ a+=['0018.1318'] and b+=['DS'] and c+=['dB/dt']
670
+ a+=['0018.1400'] and b+=['LO'] and c+=['Acquisition Device Processing Description']
671
+ a+=['0018.1401'] and b+=['LO'] and c+=['Acquisition Device Processing Code']
672
+ a+=['0018.1402'] and b+=['CS'] and c+=['Cassette Orientation']
673
+ a+=['0018.1403'] and b+=['CS'] and c+=['Cassette Size']
674
+ a+=['0018.1404'] and b+=['CS'] and c+=['Exposures on Plate']
675
+ a+=['0018.1500'] and b+=['CS'] and c+=['Positioner Motion']
676
+ a+=['0018.1510'] and b+=['DS'] and c+=['Positioner Primary Angle']
677
+ a+=['0018.1511'] and b+=['DS'] and c+=['Positioner Secondary Angle']
678
+ a+=['0018.5020'] and b+=['LO'] and c+=['Processing Function']
679
+ a+=['0018.5100'] and b+=['CS'] and c+=['Patient Position']
680
+ a+=['0018.5101'] and b+=['CS'] and c+=['View Position']
681
+ a+=['0018.6000'] and b+=['DS'] and c+=['Sensitivity']
682
+ a+=['0019.0000'] and b+=['UL'] and c+=['Group Length']
683
+ a+=['0020.0000'] and b+=['UL'] and c+=['Group Length']
684
+ a+=['0020.000D'] and b+=['UI'] and c+=['Study Instance UID']
685
+ a+=['0020.000E'] and b+=['UI'] and c+=['Series Instance UID']
686
+ a+=['0020.0010'] and b+=['SH'] and c+=['Study ID']
687
+ a+=['0020.0011'] and b+=['IS'] and c+=['Series Number']
688
+ a+=['0020.0012'] and b+=['IS'] and c+=['Acquisition Number']
689
+ a+=['0020.0013'] and b+=['IS'] and c+=['Instance Number']
690
+ a+=['0020.0020'] and b+=['CS'] and c+=['Patient Orientation']
691
+ a+=['0020.0030'] and b+=['LO'] and c+=['Image Position'] #TNV
692
+ a+=['0020.0035'] and b+=['LO'] and c+=['Image Orientation'] #TNV
693
+ a+=['0020.0032'] and b+=['DS'] and c+=['Image Position (Patient)']
694
+ a+=['0020.0037'] and b+=['DS'] and c+=['Image Orientation (Patient)']
695
+ a+=['0020.0050'] and b+=['LO'] and c+=['Location'] #TNV
696
+ a+=['0020.0052'] and b+=['UI'] and c+=['Frame of Reference UID']
697
+ a+=['0020.0060'] and b+=['CS'] and c+=['Laterality']
698
+ a+=['0020.0070'] and b+=['LO'] and c+=['Image Geometry Type'] #TNV
699
+ a+=['0020.1001'] and b+=['LO'] and c+=['Acquisitions in Series'] #TNV
700
+ a+=['0020.1002'] and b+=['IS'] and c+=['Images in Acquisition']
701
+ a+=['0020.1020'] and b+=['LO'] and c+=['Reference'] #TNV
702
+ a+=['0020.1040'] and b+=['LO'] and c+=['Position Reference Indicator']
703
+ a+=['0020.1041'] and b+=['DS'] and c+=['Slice Location']
704
+ a+=['0020.4000'] and b+=['LT'] and c+=['Image Comments']
705
+ a+=['0020.5000'] and b+=['UL'] and c+=['Original Image Identification'] #TNV
706
+ a+=['0020.5002'] and b+=['LO'] and c+=['Original Image Identification Nomenclature'] #TNV
707
+ a+=['0021.0000'] and b+=['UL'] and c+=['Group Length']
708
+ a+=['0023.0000'] and b+=['UL'] and c+=['Group Length']
709
+ a+=['0027.0000'] and b+=['UL'] and c+=['Group Length']
710
+ a+=['0028.0000'] and b+=['UL'] and c+=['Group Length']
711
+ a+=['0028.0002'] and b+=['US'] and c+=['Samples per Pixel']
712
+ a+=['0028.0004'] and b+=['CS'] and c+=['Photometric Interpretation']
713
+ a+=['0028.0006'] and b+=['US'] and c+=['Planar Configuration']
714
+ a+=['0028.0005'] and b+=['US'] and c+=['Image Dimensions'] #TNV
715
+ a+=['0028.0008'] and b+=['IS'] and c+=['Number of Frames']
716
+ a+=['0028.0009'] and b+=['AT'] and c+=['Frame Increment Pointer']
717
+ a+=['0028.0010'] and b+=['US'] and c+=['Rows']
718
+ a+=['0028.0011'] and b+=['US'] and c+=['Columns']
719
+ a+=['0028.0030'] and b+=['DS'] and c+=['Pixel Spacing']
720
+ a+=['0028.0034'] and b+=['IS'] and c+=['Pixel Aspect Ratio']
721
+ a+=['0028.0040'] and b+=['LO'] and c+=['Image Format'] #TNV
722
+ a+=['0028.0060'] and b+=['LO'] and c+=['Compression Code'] #TNV
723
+ a+=['0028.0100'] and b+=['US'] and c+=['Bits Allocated']
724
+ a+=['0028.0101'] and b+=['US'] and c+=['Bits Stored']
725
+ a+=['0028.0102'] and b+=['US'] and c+=['High Bit']
726
+ a+=['0028.0103'] and b+=['US'] and c+=['Pixel Representation']
727
+ a+=['0028.0104'] and b+=['US'] and c+=['Smallest Valid Pixel Value'] #TNV
728
+ a+=['0028.0105'] and b+=['US'] and c+=['Largest Valid Pixel Value'] #TNV
729
+ a+=['0028.0106'] and b+=['US'] and c+=['Smallest Image Pixel Value'] # 'SS' also possible
730
+ a+=['0028.0107'] and b+=['US'] and c+=['Largest Image Pixel Value'] # 'SS' also possible
731
+ a+=['0028.0108'] and b+=['US'] and c+=['Smallest Pixel Value in Series']
732
+ a+=['0028.0109'] and b+=['US'] and c+=['Largest Pixel Value in Series']
733
+ a+=['0028.0120'] and b+=['US'] and c+=['Pixel Padding Value'] # 'SS' also possible
734
+ a+=['0028.0200'] and b+=['US'] and c+=['Image Location'] #TNV
735
+ a+=['0028.0300'] and b+=['CS'] and c+=['Quality Control Image']
736
+ a+=['0028.0301'] and b+=['CS'] and c+=['Burned In Annotation']
737
+ a+=['0028.1040'] and b+=['CS'] and c+=['Pixel Intensity Relationship']
738
+ a+=['0028.1050'] and b+=['DS'] and c+=['Window Center']
739
+ a+=['0028.1051'] and b+=['DS'] and c+=['Window Width']
740
+ a+=['0028.1052'] and b+=['DS'] and c+=['Rescale Intercept']
741
+ a+=['0028.1053'] and b+=['US'] and c+=['Rescale Slope']
742
+ a+=['0028.1054'] and b+=['LO'] and c+=['Rescale Type']
743
+ a+=['0028.1055'] and b+=['LO'] and c+=['Window Center & Width Explanation']
744
+ a+=['0028.1101'] and b+=['US'] and c+=['Red Palette Color Lookup Table Descriptor']
745
+ a+=['0028.1102'] and b+=['US'] and c+=['Green Palette Color Lookup Table Descriptor']
746
+ a+=['0028.1103'] and b+=['US'] and c+=['Blue Palette Color Lookup Table Descriptor']
747
+ a+=['0028.1199'] and b+=['UI'] and c+=['Palette Color Lookup Table UID']
748
+ a+=['0028.1201'] and b+=['US'] and c+=['Red Palette Color Lookup Table Data']
749
+ a+=['0028.1202'] and b+=['US'] and c+=['Green Palette Color Lookup Table Data']
750
+ a+=['0028.1203'] and b+=['US'] and c+=['Blue Palette Color Lookup Table Data']
751
+ a+=['0028.2110'] and b+=['CS'] and c+=['Lossy Image Compression']
752
+ a+=['0028.3003'] and b+=['LO'] and c+=['LUT Explanation']
753
+ a+=['0029.0000'] and b+=['UL'] and c+=['Group Length']
754
+ a+=['0032.0000'] and b+=['UL'] and c+=['Group Length']
755
+ a+=['0032.1060'] and b+=['LO'] and c+=['Requested Procedure Description']
756
+ a+=['0040.0000'] and b+=['UL'] and c+=['Group Length']
757
+ a+=['0040.0244'] and b+=['DA'] and c+=['Performed Procedure Step Start Date']
758
+ a+=['0040.0245'] and b+=['TM'] and c+=['Performed Procedure Step Start Time']
759
+ a+=['0040.0253'] and b+=['SH'] and c+=['Performed Procedure Step ID']
760
+ a+=['0040.0254'] and b+=['LO'] and c+=['Performed Procedure Step Description']
761
+ a+=['0043.0000'] and b+=['UL'] and c+=['Group Length']
762
+ a+=['0045.0000'] and b+=['UL'] and c+=['Group Length']
763
+ a+=['0051.0000'] and b+=['UL'] and c+=['Group Length']
764
+ a+=['0054.0000'] and b+=['UL'] and c+=['Group Length']
765
+ a+=['0054.0011'] and b+=['US'] and c+=['Number of Energy Windows']
766
+ a+=['0054.0021'] and b+=['US'] and c+=['Number of Detectors']
767
+ a+=['0054.0051'] and b+=['US'] and c+=['Number of Rotations']
768
+ a+=['0054.0080'] and b+=['US'] and c+=['Slice Vector']
769
+ a+=['0054.0081'] and b+=['US'] and c+=['Number of Slices']
770
+ a+=['0054.0202'] and b+=['CS'] and c+=['Type of Detector Motion']
771
+ a+=['0054.0400'] and b+=['SH'] and c+=['Image ID']
772
+ a+=['0088.0000'] and b+=['UL'] and c+=['Group Length']
773
+ a+=['0088.0140'] and b+=['UI'] and c+=['Storage Media File-set UID']
774
+ a+=['0088.0200'] and b+=['SQ'] and c+=['Icon Image Sequence']
775
+ a+=['2010.0000'] and b+=['UL'] and c+=['Group Length']
776
+ a+=['2010.0100'] and b+=['CS'] and c+=['Border Density']
777
+ a+=['2020.0000'] and b+=['UL'] and c+=['Group Length']
778
+ a+=['2020.0020'] and b+=['CS'] and c+=['Polarity']
779
+ a+=['3002.0000'] and b+=['UL'] and c+=['Group Length']
780
+ a+=['3002.0002'] and b+=['SH'] and c+=['RT Image Label']
781
+ a+=['3002.0003'] and b+=['LO'] and c+=['RT Image Name']
782
+ a+=['3002.0004'] and b+=['ST'] and c+=['RT Image Description']
783
+ a+=['3002.000A'] and b+=['CS'] and c+=['Reported Values Origin']
784
+ a+=['3002.000C'] and b+=['CS'] and c+=['RT Image Plane']
785
+ a+=['3002.000D'] and b+=['DS'] and c+=['X-Ray Image Receptor Translation']
786
+ a+=['3002.000E'] and b+=['DS'] and c+=['X-Ray Image Receptor Angle']
787
+ a+=['3002.0011'] and b+=['DS'] and c+=['Image Plane Pixel Spacing']
788
+ a+=['3002.0012'] and b+=['DS'] and c+=['RT Image Position']
789
+ a+=['3002.0020'] and b+=['SH'] and c+=['Radiation Machine Name']
790
+ a+=['3002.0022'] and b+=['DS'] and c+=['Radiation Machine SAD']
791
+ a+=['3002.0026'] and b+=['DS'] and c+=['RT Image SID']
792
+ a+=['3002.0030'] and b+=['SQ'] and c+=['Exposure Sequence']
793
+ a+=['3002.0032'] and b+=['DS'] and c+=['Meterset Exposure']
794
+ a+=['3002.0034'] and b+=['DS'] and c+=['Diaphragm Position']
795
+ a+=['3004.0000'] and b+=['UL'] and c+=['Group Length']
796
+ a+=['3004.0002'] and b+=['CS'] and c+=['Dose Units']
797
+ a+=['3004.0004'] and b+=['CS'] and c+=['Dose Type']
798
+ a+=['3004.0006'] and b+=['LO'] and c+=['Dose Comment']
799
+ a+=['3004.000A'] and b+=['CS'] and c+=['Dose Summation Type']
800
+ a+=['3004.000C'] and b+=['DS'] and c+=['Grid Frame Offset Vector']
801
+ a+=['3004.000E'] and b+=['DS'] and c+=['Dose Grid Scaling']
802
+ a+=['3004.0010'] and b+=['SQ'] and c+=['RT Dose ROI Sequence']
803
+ a+=['3004.0012'] and b+=['DS'] and c+=['Dose Value']
804
+ a+=['3005.0000'] and b+=['UL'] and c+=['Group Length']
805
+ a+=['3006.0000'] and b+=['UL'] and c+=['Group Length']
806
+ a+=['3006.0002'] and b+=['SH'] and c+=['Structure Set Label']
807
+ a+=['3006.0004'] and b+=['LO'] and c+=['Structure Set Name']
808
+ a+=['3006.0008'] and b+=['DA'] and c+=['Structure Set Date']
809
+ a+=['3006.0009'] and b+=['TM'] and c+=['Structure Set Time']
810
+ a+=['3006.0010'] and b+=['SQ'] and c+=['Referenced Frame of Reference Sequence']
811
+ a+=['3006.0012'] and b+=['SQ'] and c+=['RT Referenced Study Sequence']
812
+ a+=['3006.0014'] and b+=['SQ'] and c+=['RT Referenced Series Sequence']
813
+ a+=['3006.0020'] and b+=['SQ'] and c+=['Structure Set ROI Sequence']
814
+ a+=['3006.0022'] and b+=['IS'] and c+=['ROI Number']
815
+ a+=['3006.0024'] and b+=['UI'] and c+=['Referenced Frame of Reference UID']
816
+ a+=['3006.0026'] and b+=['LO'] and c+=['ROI Name']
817
+ a+=['3006.002A'] and b+=['IS'] and c+=['ROI Display Color']
818
+ a+=['3006.0016'] and b+=['SQ'] and c+=['Contour Image Sequence']
819
+ a+=['3006.0036'] and b+=['CS'] and c+=['ROI Generation Algorithm']
820
+ a+=['3006.0039'] and b+=['SQ'] and c+=['ROI Contour Sequence']
821
+ a+=['3006.0040'] and b+=['SQ'] and c+=['Contour Sequence']
822
+ a+=['3006.0042'] and b+=['CS'] and c+=['Contour Geometric Type']
823
+ a+=['3006.0046'] and b+=['IS'] and c+=['Number of Contour Points']
824
+ a+=['3006.0048'] and b+=['IS'] and c+=['Contour Number']
825
+ a+=['3006.0050'] and b+=['DS'] and c+=['Contour Data']
826
+ a+=['3006.0080'] and b+=['SQ'] and c+=['RT ROI Observations Sequence']
827
+ a+=['3006.0082'] and b+=['IS'] and c+=['Observation Number']
828
+ a+=['3006.0084'] and b+=['IS'] and c+=['Referenced ROI Number']
829
+ a+=['3006.00A4'] and b+=['CS'] and c+=['RT ROI Interpreted Type']
830
+ a+=['3006.00A6'] and b+=['PN'] and c+=['ROI Interpreter']
831
+ a+=['3006.00C0'] and b+=['SQ'] and c+=['Frame of Reference Relationship Sequence']
832
+ a+=['3006.00C2'] and b+=['UI'] and c+=['Related Frame of Reference UID']
833
+ a+=['3006.00C4'] and b+=['CS'] and c+=['Frame of Reference Transformation Type']
834
+ a+=['3006.00C6'] and b+=['DS'] and c+=['Frame of Reference Transformation Matrix']
835
+ a+=['3006.00C8'] and b+=['LO'] and c+=['Frame of Reference Transformation Comment']
836
+ a+=['3007.0000'] and b+=['UL'] and c+=['Group Length']
837
+ a+=['300A.0000'] and b+=['UL'] and c+=['Group Length']
838
+ a+=['300A.0002'] and b+=['SH'] and c+=['RT Plan Label']
839
+ a+=['300A.0003'] and b+=['LO'] and c+=['RT Plan Name']
840
+ a+=['300A.0004'] and b+=['ST'] and c+=['RT Plan Description']
841
+ a+=['300A.0006'] and b+=['DA'] and c+=['RT Plan Date']
842
+ a+=['300A.0007'] and b+=['TM'] and c+=['RT Plan Time']
843
+ a+=['300A.000C'] and b+=['CS'] and c+=['RT Plan Geometry']
844
+ a+=['300A.0070'] and b+=['SQ'] and c+=['Fraction Group Sequence']
845
+ a+=['300A.0071'] and b+=['IS'] and c+=['Fraction Group Number']
846
+ a+=['300A.0078'] and b+=['IS'] and c+=['Number of Fractions Planned']
847
+ a+=['300A.0080'] and b+=['IS'] and c+=['Number of Beams']
848
+ a+=['300A.0086'] and b+=['DS'] and c+=['Beam Meterset']
849
+ a+=['300A.00A0'] and b+=['IS'] and c+=['Number of Brachy Application Setups']
850
+ a+=['300A.00B0'] and b+=['SQ'] and c+=['Beam Sequence']
851
+ a+=['300A.00B2'] and b+=['SH'] and c+=['Treatment Machine Name']
852
+ a+=['300A.00B3'] and b+=['CS'] and c+=['Primary Dosimeter Unit']
853
+ a+=['300A.00B4'] and b+=['DS'] and c+=['Source-Axis Distance']
854
+ a+=['300A.00B6'] and b+=['SQ'] and c+=['Beam Limiting Device Sequence']
855
+ a+=['300A.00B8'] and b+=['CS'] and c+=['RT Beam Limiting Device Type']
856
+ a+=['300A.00BC'] and b+=['IS'] and c+=['Number of Leaf/Jaw Pairs']
857
+ a+=['300A.00BE'] and b+=['DS'] and c+=['Leaf Position Boundaries']
858
+ a+=['300A.00C0'] and b+=['IS'] and c+=['Beam Number']
859
+ a+=['300A.00C2'] and b+=['LO'] and c+=['Beam Name']
860
+ a+=['300A.00C3'] and b+=['ST'] and c+=['Beam Description']
861
+ a+=['300A.00C4'] and b+=['CS'] and c+=['Beam Type']
862
+ a+=['300A.00C6'] and b+=['CS'] and c+=['Radiation Type']
863
+ a+=['300A.00C8'] and b+=['IS'] and c+=['Reference Image Number']
864
+ a+=['300A.00CE'] and b+=['CS'] and c+=['Treatment Delivery Type']
865
+ a+=['300A.00D0'] and b+=['IS'] and c+=['Number of Wedges']
866
+ a+=['300A.00D1'] and b+=['SQ'] and c+=['Wedge Sequence']
867
+ a+=['300A.00D2'] and b+=['IS'] and c+=['Wedge Number']
868
+ a+=['300A.00D3'] and b+=['CS'] and c+=['Wedge Type']
869
+ a+=['300A.00D4'] and b+=['SH'] and c+=['Wedge Id']
870
+ a+=['300A.00D5'] and b+=['IS'] and c+=['Wedge Angle']
871
+ a+=['300A.00D6'] and b+=['DS'] and c+=['Wedge Factor']
872
+ a+=['300A.00D8'] and b+=['DS'] and c+=['Wedge Orientation']
873
+ a+=['300A.00E0'] and b+=['IS'] and c+=['Number of Compensators']
874
+ a+=['300A.00E1'] and b+=['SH'] and c+=['Material Id']
875
+ a+=['300A.00ED'] and b+=['IS'] and c+=['Number of Boli']
876
+ a+=['300A.00F0'] and b+=['IS'] and c+=['Number of Blocks']
877
+ a+=['300A.00F2'] and b+=['DS'] and c+=['Total Block Tray Factor']
878
+ a+=['300A.00F4'] and b+=['SQ'] and c+=['Block Sequence']
879
+ a+=['300A.00F5'] and b+=['SH'] and c+=['Block Tray Id']
880
+ a+=['300A.00F6'] and b+=['DS'] and c+=['Source to Block Tray Distance']
881
+ a+=['300A.00F8'] and b+=['CS'] and c+=['Block Type']
882
+ a+=['300A.00FA'] and b+=['CS'] and c+=['Block Divergence']
883
+ a+=['300A.00FC'] and b+=['IS'] and c+=['Block Number']
884
+ a+=['300A.00FE'] and b+=['LO'] and c+=['Block Name']
885
+ a+=['300A.0100'] and b+=['DS'] and c+=['Block Thickness']
886
+ a+=['300A.0102'] and b+=['DS'] and c+=['Block Transmission']
887
+ a+=['300A.0104'] and b+=['IS'] and c+=['Block Number of Points']
888
+ a+=['300A.0106'] and b+=['DS'] and c+=['Block Data']
889
+ a+=['300A.010E'] and b+=['DS'] and c+=['Final Cumulative Meterset Weight']
890
+ a+=['300A.0110'] and b+=['IS'] and c+=['Number of Control Points']
891
+ a+=['300A.0111'] and b+=['SQ'] and c+=['Control Point Sequence']
892
+ a+=['300A.0112'] and b+=['IS'] and c+=['Control Point Index']
893
+ a+=['300A.0114'] and b+=['DS'] and c+=['Nominal Beam Energy']
894
+ a+=['300A.011A'] and b+=['SQ'] and c+=['Beam Limiting Device Position Sequence']
895
+ a+=['300A.011C'] and b+=['DS'] and c+=['Leaf/Jaw Positions']
896
+ a+=['300A.011E'] and b+=['DS'] and c+=['Gantry Angle']
897
+ a+=['300A.011F'] and b+=['CS'] and c+=['Gantry Rotation Direction']
898
+ a+=['300A.0120'] and b+=['DS'] and c+=['Beam Limiting Device Angle']
899
+ a+=['300A.0121'] and b+=['CS'] and c+=['Beam Limiting Device Rotation Direction']
900
+ a+=['300A.0122'] and b+=['DS'] and c+=['Patient Support Angle']
901
+ a+=['300A.0123'] and b+=['CS'] and c+=['Patient Support Rotation Direction']
902
+ a+=['300A.0125'] and b+=['DS'] and c+=['Table Top Eccentric Angle']
903
+ a+=['300A.0126'] and b+=['CS'] and c+=['Table Top Eccentric Rotation Direction']
904
+ a+=['300A.0128'] and b+=['DS'] and c+=['Table Top Vertical Position']
905
+ a+=['300A.0129'] and b+=['DS'] and c+=['Table Top Longitudinal Position']
906
+ a+=['300A.012A'] and b+=['DS'] and c+=['Table Top Lateral Position']
907
+ a+=['300A.012C'] and b+=['DS'] and c+=['Isocenter Position']
908
+ a+=['300A.0130'] and b+=['DS'] and c+=['Source to Surface Distance']
909
+ a+=['300A.0134'] and b+=['DS'] and c+=['Cumulative Meterset Weight']
910
+ a+=['300A.0180'] and b+=['SQ'] and c+=['Patient Setup Sequence']
911
+ a+=['300A.0182'] and b+=['IS'] and c+=['Patient Setup Number']
912
+ a+=['300A.01D2'] and b+=['DS'] and c+=['Table Top Vertical Setup Displacement']
913
+ a+=['300A.01D4'] and b+=['DS'] and c+=['Table Top Longitudinal Setup Displacement']
914
+ a+=['300A.01D6'] and b+=['DS'] and c+=['Table Top Lateral Setup Displacement']
915
+ a+=['300A.0212'] and b+=['IS'] and c+=['Source Number']
916
+ a+=['300A.0214'] and b+=['CS'] and c+=['Source Type']
917
+ a+=['300C.0000'] and b+=['UL'] and c+=['Group Length']
918
+ a+=['300C.0002'] and b+=['SQ'] and c+=['Referenced RT Plan Sequence']
919
+ a+=['300C.0004'] and b+=['SQ'] and c+=['Referenced Beam Sequence']
920
+ a+=['300C.0006'] and b+=['IS'] and c+=['Referenced Beam Number']
921
+ a+=['300C.0020'] and b+=['SQ'] and c+=['Referenced Fraction Group Sequence']
922
+ a+=['300C.0022'] and b+=['IS'] and c+=['Referenced Fraction Group Number']
923
+ a+=['300C.0042'] and b+=['SQ'] and c+=['Referenced Reference Image Sequence']
924
+ a+=['300C.0060'] and b+=['SQ'] and c+=['Referenced Structure Set Sequence']
925
+ a+=['300C.006A'] and b+=['IS'] and c+=['Referenced Patient Setup Number']
926
+ a+=['300C.0080'] and b+=['SQ'] and c+=['Referenced Dose Sequence']
927
+ a+=['300E.0000'] and b+=['UL'] and c+=['Group Length']
928
+ a+=['300E.0002'] and b+=['CS'] and c+=['Approval Status']
929
+ a+=['3241.0000'] and b+=['UL'] and c+=['Group Length']
930
+ a+=['5000.0000'] and b+=['UL'] and c+=['Group Length']
931
+ a+=['5000.0005'] and b+=['US'] and c+=['Curve Dimensions']
932
+ a+=['5000.0010'] and b+=['US'] and c+=['Number of Points']
933
+ a+=['5000.0020'] and b+=['CS'] and c+=['Type of Data']
934
+ a+=['5000.0030'] and b+=['SH'] and c+=['Axis Units']
935
+ a+=['5000.0103'] and b+=['US'] and c+=['Data Value Representation']
936
+ a+=['5000.3000'] and b+=['OB'] and c+=['Curve Data']
937
+ a+=['5001.0000'] and b+=['UL'] and c+=['Group Length']
938
+ a+=['5001.0010'] and b+=['US'] and c+=['Number of Points'] # ?? check with other sources
939
+ a+=['5002.0000'] and b+=['UL'] and c+=['Group Length']
940
+ a+=['5002.0005'] and b+=['US'] and c+=['Curve Dimensions']
941
+ a+=['5002.0010'] and b+=['US'] and c+=['Number of Points']
942
+ a+=['5002.0020'] and b+=['CS'] and c+=['Type of Data']
943
+ a+=['5002.0030'] and b+=['SH'] and c+=['Axis Units']
944
+ a+=['5002.0103'] and b+=['US'] and c+=['Data Value Representation']
945
+ a+=['5002.3000'] and b+=['OB'] and c+=['Curve Data']
946
+ a+=['7FE0.0000'] and b+=['UL'] and c+=['Group Length']
947
+ a+=['7FE0.0010'] and b+=['OW'] and c+=['Pixel Data']
948
+ a+=['FFFC.FFFC'] and b+=['OB'] and c+=['Data Set Trailing Padding']
949
+ a+=['FFFE.E000'] and b+=['()'] and c+=['Item']
950
+ a+=['FFFE.E00D'] and b+=['()'] and c+=['Sequence Delimitation Item']
951
+ a+=['FFFE.E0DD'] and b+=['()'] and c+=['Sequence Delimitation Item']
952
+ a+=['dumm.dumm'] and b+=['SS'] and c+=['Dummy entry 1'] # To get the 'SS' type in the list.
953
+
954
+ @lib_labels=a
955
+ @lib_types=b
956
+ @lib_names=c
957
+ end
958
+
959
+
960
+ # Process a series of numbers to return a string containing all the numbers separated with the separator "/" between the numbers.
961
+ def process_numbers(length, type, size)
962
+ bin = @file.read(size)
963
+ data=""
964
+ case type
965
+ when "UL"
966
+ temp1 = get_UL(bin)
967
+ when "SL"
968
+ temp1 = get_SL(bin)
969
+ when "US"
970
+ temp1 = get_US(bin)
971
+ when "SS"
972
+ temp1 = get_SS(bin)
973
+ when "FD"
974
+ temp1 = get_FD(bin)
975
+ else
976
+ puts "Warning: Type "+type+"not supported in method process_numbers()."
977
+ end
978
+ remain = (length-size)/size
979
+ remain.times do
980
+ bin = @file.read(size)
981
+ case type
982
+ when "UL"
983
+ temp2 = get_UL(bin)
984
+ when "SL"
985
+ temp2 = get_SL(bin)
986
+ when "US"
987
+ temp2 = get_US(bin)
988
+ when "SS"
989
+ temp2 = get_SS(bin)
990
+ when "FD"
991
+ temp2 = get_FD(bin)
992
+ else
993
+ puts "Warning: Type "+type+"not supported in method process_numbers()."
994
+ end
995
+ data = temp1.to_s+"/"+temp2.to_s
996
+ temp1 = data
997
+ end
998
+ return data
999
+ end
1000
+
1001
+ # Returns a byte integer (1 byte), from the supplied variable.
1002
+ def get_BYTE(bin)
1003
+ # If bin contains several numbers, unpack and return in an array. If just one number, return the number:
1004
+ elements = bin.size
1005
+ if @endian
1006
+ # Native byte order:
1007
+ if elements > 1
1008
+ num=bin.unpack('C*')
1009
+ else
1010
+ num=bin.unpack('C*')[0]
1011
+ end
1012
+ else
1013
+ # Network byte order: (Unknown what to use here)
1014
+ puts "Warning: Method get_BYTE not tested with this endian yet!"
1015
+ if elements > 1
1016
+ num=bin.unpack('C*')
1017
+ else
1018
+ num=bin.unpack('C*')[0]
1019
+ end
1020
+ end
1021
+ return num
1022
+ end
1023
+
1024
+
1025
+ # Returns a unsigned short (2 bytes), from the supplied variable.
1026
+ def get_US(bin)
1027
+ # If bin contains several numbers, unpack and return in an array. If just one number, return the number:
1028
+ elements = bin.size/2
1029
+ if @endian
1030
+ # Native byte order:
1031
+ if elements > 1
1032
+ num=bin.unpack('S*') # or v (little endian (?))
1033
+ else
1034
+ num=bin.unpack('S*')[0]
1035
+ end
1036
+ else
1037
+ # Network byte order:
1038
+ if elements > 1
1039
+ num=bin.unpack('n*')
1040
+ else
1041
+ num=bin.unpack('n*')[0]
1042
+ end
1043
+ end
1044
+ return num
1045
+ end
1046
+
1047
+
1048
+ # Returns a signed short (2 bytes), from the supplied variable.
1049
+ def get_SS(bin)
1050
+ elements = bin.size/2
1051
+ # If bin contains several numbers, unpack and return in an array. If just one number, return the number:
1052
+ if @endian
1053
+ # Native byte order
1054
+ if elements > 1
1055
+ num=bin.unpack('s*')
1056
+ else
1057
+ num=bin.unpack('s*')[0]
1058
+ end
1059
+ else
1060
+ # Unknown what unpack code to use here:
1061
+ if elements > 1
1062
+ num=bin.unpack('s*')
1063
+ else
1064
+ num=bin.unpack('s*')[0]
1065
+ end
1066
+ puts "Warning: Oppositve endian for signed short is not working yet!"
1067
+ end
1068
+ return num
1069
+ end
1070
+
1071
+
1072
+ # Returns an unsigned long (4 bytes), from the supplied variable.
1073
+ def get_UL(bin)
1074
+ elements = bin.size/4
1075
+ # If bin contains several numbers, unpack and return in an array. If just one number, return the number:
1076
+ if @endian
1077
+ # Unsigned native integer:
1078
+ if elements > 1
1079
+ num=bin.unpack('I*')
1080
+ else
1081
+ num=bin.unpack('I*')[0]
1082
+ end
1083
+ else
1084
+ # Unsigned long in network byte order:
1085
+ if elements > 1
1086
+ num=bin.unpack('N*')
1087
+ else
1088
+ num=bin.unpack('N*')[0]
1089
+ end
1090
+ end
1091
+ return num
1092
+ end
1093
+
1094
+
1095
+ # Returns a signed long (4 bytes), from the supplied variable.
1096
+ def get_SL(bin)
1097
+ elements = bin.size/4
1098
+ # If bin contains several numbers, unpack and return in an array. If just one number, return the number:
1099
+ if @endian
1100
+ # Signed native long integer:
1101
+ if elements > 1
1102
+ num=bin.unpack('l*')
1103
+ else
1104
+ num=bin.unpack('l*')[0]
1105
+ end
1106
+ else
1107
+ puts "Warning: Oppositve endian for signed long is not working yet!"
1108
+ if elements > 1
1109
+ num=bin.unpack('l*')
1110
+ else
1111
+ num=bin.unpack('l*')[0]
1112
+ end
1113
+ end
1114
+ return num
1115
+ end
1116
+
1117
+
1118
+ # Returns a floating point double (8 bytes), from the supplied variable.
1119
+ def get_FD(bin)
1120
+ elements = bin.size/8
1121
+ # If bin contains several numbers, unpack and return in an array. If just one number, return the number:
1122
+ if @endian
1123
+ # Double in little-endian byte order:
1124
+ if elements > 1
1125
+ num=bin.unpack('E*')
1126
+ else
1127
+ num=bin.unpack('E*')[0]
1128
+ end
1129
+ else
1130
+ # Double in network byte order:
1131
+ if elements > 1
1132
+ num=bin.unpack('G*')
1133
+ else
1134
+ num=bin.unpack('G*')[0]
1135
+ end
1136
+ end
1137
+ return num
1138
+ end
1139
+
1140
+
1141
+ # Checks the Transfer Syntax UID tag and updates class variables to prepare for correct reading of DICOM file.
1142
+ def process_syntax(value)
1143
+ case value.rstrip
1144
+ # Some variations with uncompressed pixel data:
1145
+ when "1.2.840.10008.1.2"
1146
+ # Implicit VR, Little Endian
1147
+ @rest_explicit = false
1148
+ @rest_endian = false
1149
+ @compression = false
1150
+ when "1.2.840.10008.1.2.1"
1151
+ # Explicit VR, Little Endian
1152
+ @rest_explicit = true
1153
+ @rest_endian = false
1154
+ @compression = false
1155
+ when "1.2.840.10008.1.2.2"
1156
+ # Explicit VR, Big Endian
1157
+ @rest_explicit = true
1158
+ @rest_endian = true
1159
+ @compression = false
1160
+ # Compressed pixel data, using various compression algorithms:
1161
+ when "1.2.840.10008.1.2.4.50"
1162
+ @rest_explicit = true
1163
+ @rest_endian = false
1164
+ @compression = "JPEG Baseline"
1165
+ when "1.2.840.10008.1.2.4.51"
1166
+ @rest_explicit = true
1167
+ @rest_endian = false
1168
+ @compression = "JPEG Extended (Process 2 & 4)"
1169
+ when "1.2.840.10008.1.2.4.70"
1170
+ @rest_explicit = true
1171
+ @rest_endian = false
1172
+ @compression = "JPEG Lossless, Non-Hierarchical"
1173
+ when "1.2.840.10008.1.2.4.90"
1174
+ @rest_explicit = true
1175
+ @rest_endian = false
1176
+ @compression = "JPEG 2000, Lossless Only"
1177
+ when "1.2.840.10008.1.2.4.91"
1178
+ @rest_explicit = true
1179
+ @rest_endian = false
1180
+ @compression = "JPEG 2000, Lossy"
1181
+ else
1182
+ # For everything else, assume unknown compression algorithm, with Explicit VR, Little Endian:
1183
+ puts "Warning: Unknown Transfer Syntax UID: "+ value.to_s
1184
+ puts "Handling for this data type has not been implemented, errors may occur."
1185
+ @rest_explicit = true
1186
+ @rest_endian = false
1187
+ @compression = "Unknown"
1188
+ end
1189
+ end
1190
+
1191
+
1192
+ # Checks the endianness of the system. Returns false if little endian, true if big endian.
1193
+ def check_sys_endian()
1194
+ x = 0xdeadbeef
1195
+ endian_type = {
1196
+ Array(x).pack("V*") => false, #:little
1197
+ Array(x).pack("N*") => true #:big
1198
+ }
1199
+ return endian_type[Array(x).pack("L*")]
1200
+ end
1201
+
1202
+
1203
+ end # End of class.
1204
+ end # End of module.