dicom 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/DLibrary.rb CHANGED
@@ -1,10 +1,10 @@
1
- # Copyright 2008-2009 Christoffer Lerv�g
1
+ # Copyright 2008-2009 Christoffer Lervag
2
2
 
3
3
  module DICOM
4
4
  # Class which holds the methods that interact with the DICOM dictionary.
5
5
  class DLibrary
6
6
 
7
- attr_reader :de_label, :de_vr, :de_name, :uid_value, :uid_name, :uid_type, :pi_type, :pi_description
7
+ attr_reader :de_tag, :de_vr, :de_name, :uid_value, :uid_name, :uid_type, :pi_type, :pi_description
8
8
 
9
9
  # Initialize the DRead instance.
10
10
  def initialize()
@@ -12,8 +12,8 @@ module DICOM
12
12
  # Load the dictionary:
13
13
  dict = Dictionary.new()
14
14
  # Data elements:
15
- de = dict.load_tags()
16
- @de_label = de[0]
15
+ de = dict.load_data_elements()
16
+ @de_tag = de[0]
17
17
  @de_vr = de[1]
18
18
  @de_name = de[2]
19
19
  # Photometric Interpretation:
@@ -28,37 +28,37 @@ module DICOM
28
28
  end
29
29
 
30
30
 
31
- # Returns data element name and value representation from library if tag is recognised, else it returns "Unknown Name" and "UN".
32
- def get_name_vr(label)
33
- pos = get_pos(label)
31
+ # Returns data element name and value representation from library if data element is recognized, else it returns "Unknown Name" and "UN".
32
+ def get_name_vr(tag)
33
+ pos = get_pos(tag)
34
34
  if pos != nil
35
35
  name = @de_name[pos]
36
36
  vr = @de_vr[pos][0]
37
37
  else
38
- # For the labels that are not recognised, we need to do some additional testing to see if it is one of the special cases:
39
- # Split label in group and element:
40
- group = label[0..3]
41
- element = label[5..8]
38
+ # For the tags that are not recognised, we need to do some additional testing to see if it is one of the special cases:
39
+ # Split tag in group and element:
40
+ group = tag[0..3]
41
+ element = tag[5..8]
42
42
  # Check for group length:
43
43
  if element == "0000"
44
44
  name = "Group Length"
45
45
  vr = "UL"
46
46
  end
47
47
  # Source Image ID's: (Retired)
48
- if label[0..6] == "0020,31"
48
+ if tag[0..6] == "0020,31"
49
49
  pos = get_pos("0020,31xx")
50
50
  name = @de_name[pos]
51
51
  vr = @de_vr[pos][0]
52
52
  end
53
53
  # Group 50xx (retired) and 60xx:
54
- if label[0..1] == "50" or label[0..1] == "60"
55
- pos = get_pos(label[0..1]+"xx"+label[4..8])
54
+ if tag[0..1] == "50" or tag[0..1] == "60"
55
+ pos = get_pos(tag[0..1]+"xx"+tag[4..8])
56
56
  if pos != nil
57
57
  name = @de_name[pos]
58
58
  vr = @de_vr[pos][0]
59
59
  end
60
60
  end
61
- # If none of the above checks yielded a result, the label is unknown:
61
+ # If none of the above checks yielded a result, the tag is unknown:
62
62
  if name == nil
63
63
  name = "Unknown Name"
64
64
  vr = "UN"
@@ -68,17 +68,40 @@ module DICOM
68
68
  end
69
69
 
70
70
 
71
+ # Returns the tag that matches the supplied data element name,
72
+ # or if a tag is supplied, return that tag.
73
+ def get_tag(value)
74
+ tag = false
75
+ # The supplied value should be a string:
76
+ if value.is_a?(String)
77
+ # Is it a tag?
78
+ # A tag is a string with 9 characters, where the 5th character should be a comma.
79
+ if value[4..4] == ',' and value.length == 9
80
+ # This is a tag.
81
+ # (Here it is possible to have some further logic to check the validity of the string as a tag.)
82
+ tag = value
83
+ else
84
+ # We have presumably been dealt a name. Search the dictionary to see if we can identify
85
+ # it along with its corresponding tag:
86
+ pos = @de_name.index(value)
87
+ tag = @de_tag[pos] unless pos == nil
88
+ end
89
+ end
90
+ return tag
91
+ end
92
+
93
+
71
94
  # Checks whether a given string is a valid transfer syntax or not.
72
- def check_ts_validity(label)
73
- res = false
74
- pos = @uid_value.index(label)
95
+ def check_ts_validity(value)
96
+ result = false
97
+ pos = @uid_value.index(value)
75
98
  if pos != nil
76
99
  if pos >= 1 and pos <= 34
77
100
  # Proved valid:
78
- res = true
101
+ result = true
79
102
  end
80
103
  end
81
- return res
104
+ return result
82
105
  end
83
106
 
84
107
 
@@ -94,8 +117,8 @@ module DICOM
94
117
  end
95
118
  return name
96
119
  end
97
-
98
-
120
+
121
+
99
122
  # Checks if the supplied transfer syntax indicates the presence of pixel compression or not.
100
123
  def get_compression(value)
101
124
  res = false
@@ -115,8 +138,8 @@ module DICOM
115
138
  private
116
139
 
117
140
  # Returns the position of the supplied data element name in the Dictionary array.
118
- def get_pos(label)
119
- pos = @de_label.index(label)
141
+ def get_pos(tag)
142
+ pos = @de_tag.index(tag)
120
143
  return pos
121
144
  end
122
145
 
data/lib/DObject.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2008-2009 Christoffer Lerv�g
1
+ # Copyright 2008-2009 Christoffer Lervag
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
@@ -29,7 +29,8 @@ module DICOM
29
29
  # Class for handling the DICOM contents:
30
30
  class DObject
31
31
 
32
- attr_reader :read_success, :write_success, :modality
32
+ attr_reader :read_success, :write_success, :modality, :errors,
33
+ :names, :tags, :types, :lengths, :values, :raw, :levels
33
34
 
34
35
  # Initialize the DObject instance.
35
36
  def initialize(file_name=nil, opts={})
@@ -38,20 +39,20 @@ module DICOM
38
39
  @lib = opts[:lib] || DLibrary.new
39
40
  # Default verbosity is true:
40
41
  @verbose = true if @verbose == nil
41
-
42
+
42
43
  # Initialize variables that will be used for the DICOM object:
43
44
  @names = Array.new()
44
- @labels = Array.new()
45
+ @tags = Array.new()
45
46
  @types = Array.new()
46
47
  @lengths = Array.new()
47
48
  @values = Array.new()
48
49
  @raw = Array.new()
49
50
  @levels = Array.new()
50
51
  # Array that will holde any messages generated while reading the DICOM file:
51
- @msg = Array.new()
52
- # Array to keep track of sequences/structure of the dicom tags:
52
+ @errors = Array.new()
53
+ # Array to keep track of sequences/structure of the dicom elements:
53
54
  @sequence = Array.new()
54
- # Index of last element in tag arrays:
55
+ # Index of last element in data element arrays:
55
56
  @last_index=0
56
57
  # Structural information (default values):
57
58
  @compression = false
@@ -66,11 +67,11 @@ module DICOM
66
67
  @sys_endian = check_sys_endian()
67
68
  # Set format strings for packing/unpacking:
68
69
  set_format_strings()
69
-
70
+
70
71
  # If a (valid) file name string is supplied, call the method to read the DICOM file:
71
- if file_name != nil and file_name != ""
72
+ if file_name.is_a?(String) and file_name != ""
72
73
  @file = file_name
73
- read_file(file_name)
74
+ read(file_name)
74
75
  end
75
76
  end # of method initialize
76
77
 
@@ -79,13 +80,13 @@ module DICOM
79
80
  # This is accomplished by initliazing the DRead class, which loads DICOM information to arrays.
80
81
  # For the time being, this method is called automatically when initializing the DObject class,
81
82
  # but in the future, when write support is added, this method may have to be called manually.
82
- def read_file(file_name)
83
+ def read(file_name)
83
84
  dcm = DRead.new(file_name, :lib => @lib, :sys_endian => @sys_endian)
84
85
  # Store the data to the instance variables if the readout was a success:
85
86
  if dcm.success
86
87
  @read_success = true
87
88
  @names = dcm.names
88
- @labels = dcm.labels
89
+ @tags = dcm.tags
89
90
  @types = dcm.types
90
91
  @lengths = dcm.lengths
91
92
  @values = dcm.values
@@ -95,7 +96,7 @@ module DICOM
95
96
  @file_endian = dcm.file_endian
96
97
  # Set format strings for packing/unpacking:
97
98
  set_format_strings(@file_endian)
98
- # Index of last element in tag arrays:
99
+ # Index of last data element in element arrays:
99
100
  @last_index=@names.length-1
100
101
  # Update status variables for this object:
101
102
  check_properties()
@@ -109,13 +110,13 @@ module DICOM
109
110
  add_msg(dcm.msg)
110
111
  end
111
112
  end
112
-
113
-
113
+
114
+
114
115
  # Transfers necessary information from the DObject to the DWrite class, which
115
116
  # will attempt to write this information to a valid DICOM file.
116
- def write_file(file_name)
117
+ def write(file_name)
117
118
  w = DWrite.new(file_name, :lib => @lib, :sys_endian => @sys_endian)
118
- w.labels = @labels
119
+ w.tags = @tags
119
120
  w.types = @types
120
121
  w.lengths = @lengths
121
122
  w.raw = @raw
@@ -129,8 +130,8 @@ module DICOM
129
130
  add_msg(w.msg)
130
131
  end
131
132
  end
132
-
133
-
133
+
134
+
134
135
  #################################################
135
136
  # START OF METHODS FOR READING INFORMATION FROM DICOM OBJECT:#
136
137
  #################################################
@@ -140,14 +141,14 @@ module DICOM
140
141
  # Modifies instance variable @color if color image is detected and instance variable @compression if no pixel data is detected.
141
142
  def check_properties()
142
143
  # Check if pixel data is present:
143
- if @labels.index("7FE0,0010") == nil
144
+ if @tags.index("7FE0,0010") == nil
144
145
  # No pixel data in DICOM file:
145
146
  @compression = nil
146
147
  else
147
- @compression = @lib.get_compression(get_value("0002,0010"))
148
+ @compression = @lib.get_compression(get_value("0002,0010", :silent => true))
148
149
  end
149
150
  # Set color variable as true if our object contain a color image:
150
- col_string = get_value("0028,0004")
151
+ col_string = get_value("0028,0004", :silent => true)
151
152
  if col_string != false
152
153
  if (col_string.include? "RGB") or (col_string.include? "COLOR") or (col_string.include? "COLOUR")
153
154
  @color = true
@@ -156,10 +157,14 @@ module DICOM
156
157
  end
157
158
 
158
159
 
159
- # Returns image data from the provided tag index, performing decompression of data if necessary.
160
+ # Returns image data from the provided element index, performing decompression of data if necessary.
160
161
  def read_image_magick(pos, columns, rows)
162
+ if pos == false or columns == false or rows == false
163
+ add_msg("Error: Method read_image_magick() does not have enough data available to build an image object.")
164
+ return false
165
+ end
161
166
  if @compression != true
162
- # Non-compressed, just return the array contained in the particular tag:
167
+ # Non-compressed, just return the array contained on the particular element:
163
168
  image_data=get_pixels(pos)
164
169
  image = Magick::Image.new(columns,rows)
165
170
  image.import_pixels(0, 0, columns, rows, "I", image_data)
@@ -204,9 +209,9 @@ module DICOM
204
209
  image = NArray.int(frames,columns,rows)
205
210
  image_temp = NArray.int(columns,rows)
206
211
  # Handling of image data will depend on whether we have one or more frames,
207
- # and if it is located in one or more tags:
212
+ # and if it is located in one or more elements:
208
213
  if image_pos.size == 1
209
- # All of the image data is located in one tag:
214
+ # All of the image data is located in one element:
210
215
  image_data = get_pixels(image_pos[0])
211
216
  #image_data = get_image_data(image_pos[0])
212
217
  (0..frames-1).each do |i|
@@ -264,7 +269,7 @@ module DICOM
264
269
  image_arr = Array.new(frames)
265
270
  # Handling of image data will depend on whether we have one or more frames,
266
271
  if image_pos.size == 1
267
- # All of the image data is located in one tag:
272
+ # All of the image data is located in one element:
268
273
  #image_data = get_image_data(image_pos[0])
269
274
  #(0..frames-1).each do |i|
270
275
  # image = Magick::Image.new(columns,rows)
@@ -295,14 +300,14 @@ module DICOM
295
300
  def get_frames()
296
301
  frames = get_value("0028,0008")
297
302
  if frames == false
298
- # If file does not specify number of tags, assume 1 image frame.
303
+ # If the DICOM object does not specify the number of frames explicitly, assume 1 image frame.
299
304
  frames = 1
300
305
  end
301
306
  return frames.to_i
302
307
  end
303
-
304
-
305
- # Unpacks and returns pixel data from a specified tag position:
308
+
309
+
310
+ # Unpacks and returns pixel data from a specified data element array position:
306
311
  def get_pixels(pos)
307
312
  pixels = false
308
313
  # We need to know what kind of bith depth the pixel data is saved with:
@@ -332,32 +337,32 @@ module DICOM
332
337
  raise "Bit depth ["+bit_depth.to_s+"] has not received implementation in this procedure yet. Please contact the author."
333
338
  end # of case bit_depth
334
339
  else
335
- add_msg("Error: DICOM object does not contain bit depth tag (0028,0010).")
340
+ add_msg("Error: DICOM object does not contain the 'Bit Depth' data element (0028,0010).")
336
341
  end # of if bit_depth ..
337
342
  return pixels
338
343
  end # of method get_pixels
339
344
 
340
345
 
341
- # Returns the index(es) of the tag(s) that contain image data.
346
+ # Returns the index(es) of the element(s) that contain image data.
342
347
  def get_image_pos()
343
- image_tag_pos = get_pos("7FE0,0010")
348
+ image_element_pos = get_pos("7FE0,0010")
344
349
  item_pos = get_pos("FFFE,E000")
345
- # Proceed only if image tag actually exists:
346
- if image_tag_pos == false
350
+ # Proceed only if an image element actually exists:
351
+ if image_element_pos == false
347
352
  return false
348
353
  else
349
- # Check if we have item tags:
354
+ # Check if we have item elements:
350
355
  if item_pos == false
351
- return image_tag_pos
356
+ return image_element_pos
352
357
  else
353
- # Extract item positions that occur after the image tag position:
354
- late_item_pos = item_pos.select {|item| image_tag_pos[0] < item}
355
- # Check if there are items appearing after the image tag.
358
+ # Extract item positions that occur after the image element position:
359
+ late_item_pos = item_pos.select {|item| image_element_pos[0] < item}
360
+ # Check if there are items appearing after the image element.
356
361
  if late_item_pos.size == 0
357
- # None occured after the image tag position:
358
- return image_tag_pos
362
+ # None occured after the image element position:
363
+ return image_element_pos
359
364
  else
360
- # Determine which of these late item tags contain image data.
365
+ # Determine which of these late item elements contain image data.
361
366
  # Usually, there are frames+1 late items, and all except
362
367
  # the first item contain an image frame:
363
368
  frames = get_frames()
@@ -369,7 +374,7 @@ module DICOM
369
374
  return false
370
375
  end
371
376
  else
372
- add_msg("Warning: Number of frames tag not found. Method get_image_pos() will return false.")
377
+ add_msg("Warning: 'Number of Frames' data element not found. Method get_image_pos() will return false.")
373
378
  return false
374
379
  end
375
380
  end
@@ -378,58 +383,81 @@ module DICOM
378
383
  end # of method get_image_pos
379
384
 
380
385
 
381
- # Returns an array of the index(es) of the tag(s) in the DICOM file that match the supplied tag label, name or position.
386
+ # Returns an array of the index(es) of the element(s) in the DICOM file that match the supplied element position, tag or name.
382
387
  # If no match is found, the method will return false.
383
388
  # Additional options:
384
- # :array => myArray - tells the method to search for hits in this specific array of positions instead of searching
385
- # through the entire DICOM obhject. If myArray is false, so will the return of this method.
386
- # If array is nil (not specified), then the entire DICOM object will be searched.
387
- def get_pos(label, opts={})
388
- # Process option value:
389
- opt_array = opts[:array]
390
- if opt_array == false
391
- # If the supplied array option equals false, it signals that the user tries to search for a tag
392
- # in an invalid position, and as such, this method should also return false:
393
- indexes = false
394
- else
395
- # Perform search to find indexes:
396
- # Array that will contain the positions where the supplied label gives a match:
397
- indexes = Array.new()
398
- # Either use the supplied array, or we will create an array that contain the indices of the entire DICOM object:
399
- if opt_array.is_a?(Array)
400
- search_array=opt_array
389
+ # :array => myArray - tells the method to search for matches in this specific array of positions instead of searching
390
+ # through the entire DICOM object. If myArray equals false, the method will return false.
391
+ # :partial => true - get_pos will not only search for exact matches, but will search the names and tags arrays for
392
+ # strings that contain the given search string.
393
+ def get_pos(query, opts={})
394
+ # Optional keywords:
395
+ keyword_array = opts[:array]
396
+ keyword_partial = opts[:partial]
397
+ indexes = Array.new()
398
+ # For convenience, allow query to be a one-element array (its value will be extracted):
399
+ if query.is_a?(Array)
400
+ if query.length > 1 or query.length == 0
401
+ add_msg("Invalid array length supplied to method get_pos.")
402
+ return false
401
403
  else
402
- search_array = Array.new(@names.size) {|i| i}
404
+ query = query[0]
403
405
  end
404
- # Do the search:
405
- search_array.each do |i|
406
- if @labels[i] == label
407
- indexes += [i]
408
- elsif @names[i] == label
409
- indexes += [i]
410
- elsif label == i
411
- indexes += [i]
406
+ end
407
+ if keyword_array == false
408
+ # If the supplied array option equals false, it signals that the user tries to search for an element
409
+ # in an invalid position, and as such, this method will also return false:
410
+ add_msg("Warning: Attempted to call get_pos() with query #{query}, but since keyword :array is false I will return false.")
411
+ indexes = false
412
+ else
413
+ # Check if query is a number (some methods want to have the ability to call get_pos() with a number):
414
+ if query.is_a?(Integer)
415
+ # Return the position if it is valid:
416
+ indexes = [query] if query >= 0 and query < @names.length
417
+ elsif query.is_a?(String)
418
+ # Either use the supplied array, or search the entire DICOM object:
419
+ if keyword_array.is_a?(Array)
420
+ search_array = keyword_array
421
+ else
422
+ search_array = Array.new(@names.length) {|i| i}
423
+ end
424
+ # Perform search:
425
+ if keyword_partial == true
426
+ # Search for partial string matches:
427
+ partial_indexes = search_array.all_indices_partial_match(@tags, query.upcase)
428
+ if partial_indexes.length > 0
429
+ indexes = partial_indexes
430
+ else
431
+ indexes = search_array.all_indices_partial_match(@names, query)
432
+ end
433
+ else
434
+ # Search for identical matches:
435
+ if query[4..4] == ","
436
+ indexes = search_array.all_indices(@tags, query.upcase)
437
+ else
438
+ indexes = search_array.all_indices(@names, query)
439
+ end
412
440
  end
413
441
  end
414
- # If no hits occured, we will return false instead of an empty array:
415
- indexes = false if indexes.size == 0
442
+ # Policy: If no matches found, return false instead of an empty array:
443
+ indexes = false if indexes.length == 0
416
444
  end
417
445
  return indexes
418
446
  end # of method get_pos
419
-
420
-
421
- # Dumps the binary content of the Pixel Data tag to file.
447
+
448
+
449
+ # Dumps the binary content of the Pixel Data element to file.
422
450
  def image_to_file(file)
423
451
  pos = get_image_pos()
424
452
  if pos
425
453
  if pos.length == 1
426
- # Pixel data located in one tag:
454
+ # Pixel data located in one element:
427
455
  pixel_data = get_raw(pos[0])
428
456
  f = File.new(file, "wb")
429
457
  f.write(pixel_data)
430
458
  f.close()
431
459
  else
432
- # Pixel data located in several tags:
460
+ # Pixel data located in several elements:
433
461
  pos.each_index do |i|
434
462
  pixel_data = get_raw(pos[i])
435
463
  f = File.new(file + i.to_s, "wb")
@@ -441,159 +469,161 @@ module DICOM
441
469
  end # of method image_to_file
442
470
 
443
471
 
444
- # Returns the positions of all tags inside the hierarchy of a sequence or an item.
472
+ # Returns the positions of all data elements inside the hierarchy of a sequence or an item.
445
473
  # Options:
446
474
  # :next_only => true - The method will only search immediately below the specified
447
475
  # item or sequence (that is, in the level of parent + 1).
448
- def children(tag, opts={})
476
+ def children(element, opts={})
449
477
  # Process option values, setting defaults for the ones that are not specified:
450
478
  opt_next_only = opts[:next_only] || false
451
- # Retrieve position of parent tag which from which we will search:
452
- pos = get_pos(tag)
479
+ value = false
480
+ # Retrieve array position:
481
+ pos = get_pos(element)
453
482
  if pos == false
454
- return false
455
- end
456
- if pos.size > 1
457
- add_msg("Warning: The supplied parent tag gives multiple hits. Search will be applied to all hits. To avoid this behaviour, specify position instead of label.")
458
- end
459
- # First we need to establish in which positions to perform the search:
460
- below_pos = Array.new()
461
- pos.each do |p|
462
- parent_level = @levels[p]
463
- remain_array = @levels[p+1..@levels.size-1]
464
- extract = true
465
- remain_array.each_index do |i|
466
- if (remain_array[i] > parent_level) and (extract == true)
467
- # If search is targetted at any specific level, we can just add this position:
468
- if not opt_next_only == true
469
- below_pos += [p+1+i]
470
- else
471
- # As search is restricted to parent level + 1, do a test for this:
472
- if remain_array[i] == parent_level + 1
473
- below_pos += [p+1+i]
483
+ add_msg("Warning: Invalid data element provided to method children(). Returning false.")
484
+ else
485
+ if pos.size > 1
486
+ add_msg("Warning: Method children() does not allow a query which yields multiple array hits. Please use array position instead of tag/name. Returning false.")
487
+ else
488
+ # Proceed to find the value:
489
+ # First we need to establish in which positions to perform the search:
490
+ below_pos = Array.new()
491
+ pos.each do |p|
492
+ parent_level = @levels[p]
493
+ remain_array = @levels[p+1..@levels.size-1]
494
+ extract = true
495
+ remain_array.each_index do |i|
496
+ if (remain_array[i] > parent_level) and (extract == true)
497
+ # If search is targetted at any specific level, we can just add this position:
498
+ if not opt_next_only == true
499
+ below_pos += [p+1+i]
500
+ else
501
+ # As search is restricted to parent level + 1, do a test for this:
502
+ if remain_array[i] == parent_level + 1
503
+ below_pos += [p+1+i]
504
+ end
505
+ end
506
+ else
507
+ # If we encounter a position who's level is not deeper than the original level, we can not extract any more values:
508
+ extract = false
474
509
  end
475
510
  end
476
- else
477
- # If we encounter a position who's level is not deeper than the original level, we can not extract any more values:
478
- extract = false
479
- end
480
- end
481
- end
482
- # Positions to search in have been established, now we can perform the actual search:
483
- if below_pos.size == 0
484
- return false
485
- else
486
- return below_pos
511
+ end # of pos.each do..
512
+ value = below_pos if below_pos.size != 0
513
+ end # of if pos.size..else..
487
514
  end
515
+ return value
488
516
  end # of method below
489
517
 
490
518
 
491
- # Returns the value (processed raw data) of the DICOM tag that matches the supplied tag ID.
492
- # The ID may be a tag index, tag name or tag label.
493
- def get_value(id)
494
- if id == nil
495
- add_msg("A tag label, category name or index number must be specified when calling the get_value() method!")
496
- return false
519
+ # Returns the value (processed raw data) of the requested DICOM data element.
520
+ # Data element may be specified by array position, tag or name.
521
+ # Options:
522
+ # :array => true - Allows the query of the value of a tag that occurs more than one time in the
523
+ # DICOM object. Values will be returned in an array with length equal to the number
524
+ # of occurances of the tag. If keyword is not specified, the method returns false in this case.
525
+ def get_value(element, opts={})
526
+ opts_array = opts[:array]
527
+ # As this method is also used internally, we want the possibility of warnings not being raised even if verbose is set to true by the user, to avoid confusion.
528
+ silent = opts[:silent]
529
+ value = false
530
+ # Retrieve array position:
531
+ pos = get_pos(element)
532
+ if pos == false
533
+ add_msg("Warning: Invalid data element provided to method get_value(). Returning false.") unless silent
497
534
  else
498
- # Assume we have been fed a tag label:
499
- pos=@labels.index(id)
500
- # If this does not give a hit, assume we have been fed a tag name:
501
- if pos==nil
502
- pos=@names.index(id)
503
- end
504
- # If we still dont have a hit, check if it is a valid number within the array range:
505
- if pos == nil
506
- if (id.is_a? Integer)
507
- if id >= 0 and id <= @last_index
508
- # The id supplied is a valid position, return its corresponding value:
509
- return @values[id]
510
- else
511
- return false
535
+ if pos.size > 1
536
+ if opts_array == true
537
+ # Retrieve all values into an array:
538
+ value = []
539
+ pos.each do |i|
540
+ value << @values[i]
512
541
  end
513
542
  else
514
- return false
543
+ add_msg("Warning: Method get_value() does not allow a query which yields multiple array hits. Please use array position instead of tag/name, or use keyword (:array => true). Returning false.") unless silent
515
544
  end
516
545
  else
517
- # We have a valid position, return the value:
518
- return @values[pos]
546
+ value = @values[pos[0]]
519
547
  end
520
548
  end
549
+ return value
521
550
  end # of method get_value
522
551
 
523
552
 
524
- # Returns the raw data of the DICOM tag that matches the supplied tag ID.
525
- # The ID may be a tag index, tag name or tag label.
526
- def get_raw(id)
527
- if id == nil
528
- add_msg("A tag label, category name or index number must be specified when calling the get_raw() method!")
529
- return false
553
+ # Returns the raw data of the requested DICOM data element.
554
+ # Data element may be specified by array position, tag or name.
555
+ # Options:
556
+ # :array => true - Allows the query of the value of a tag that occurs more than one time in the
557
+ # DICOM object. Values will be returned in an array with length equal to the number
558
+ # of occurances of the tag. If keyword is not specified, the method returns false in this case.
559
+ def get_raw(element, opts={})
560
+ opts_array = opts[:array]
561
+ value = false
562
+ # Retrieve array position:
563
+ pos = get_pos(element)
564
+ if pos == false
565
+ add_msg("Warning: Invalid data element provided to method get_raw(). Returning false.")
530
566
  else
531
- # Assume we have been fed a tag label:
532
- pos=@labels.index(id)
533
- # If this does not give a hit, assume we have been fed a tag name:
534
- if pos==nil
535
- pos=@names.index(id)
536
- end
537
- # If we still dont have a hit, check if it is a valid number within the array range:
538
- if pos == nil
539
- if (id.is_a? Integer)
540
- if id >= 0 and id <= @last_index
541
- # The id supplied is a valid position, return its corresponding value:
542
- return @raw[id]
543
- else
544
- return false
567
+ if pos.size > 1
568
+ if opts_array == true
569
+ # Retrieve all values into an array:
570
+ value = []
571
+ pos.each do |i|
572
+ value << @raw[i]
545
573
  end
546
574
  else
547
- return false
575
+ add_msg("Warning: Method get_raw() does not allow a query which yields multiple array hits. Please use array position instead of tag/name, or use keyword (:array => true). Returning false.")
548
576
  end
549
577
  else
550
- # We have a valid position, return the value:
551
- return @raw[pos]
578
+ value = @raw[pos[0]]
552
579
  end
553
580
  end
581
+ return value
554
582
  end # of method get_raw
555
-
556
-
557
- # Returns the position of (possible) parents of the specified tag in the hierarchy structure of the DICOM object.
558
- def parents(tag)
559
- # Get array position:
560
- pos = get_pos(tag)
583
+
584
+
585
+ # Returns the position of (possible) parents of the specified data element in the hierarchy structure of the DICOM object.
586
+ def parents(element)
587
+ value = false
588
+ # Retrieve array position:
589
+ pos = get_pos(element)
561
590
  if pos == false
562
- parents = false
591
+ add_msg("Warning: Invalid data element provided to method parents(). Returning false.")
563
592
  else
564
- # Extracting first value in array pos:
565
- pos = pos[0]
566
- # Get level of our tag:
567
- level = @levels[pos]
568
- # If tag is top level it can obviously have no parents:
569
- if level == 0
570
- parents = false
593
+ if pos.length > 1
594
+ add_msg("Warning: Method parents() does not allow a query which yields multiple array hits. Please use array position instead of tag/name. Returning false.")
571
595
  else
572
- # Search backwards, and record the position every time we encounter an
573
- # upwards change in the level number.
574
- parents = Array.new()
575
- prev_level = level
576
- search_arr = @levels[0..pos-1].reverse
577
- search_arr.each_index do |i|
578
- if search_arr[i] < prev_level
579
- parents += [search_arr.length-i-1]
580
- prev_level = search_arr[i]
596
+ # Proceed to find the value:
597
+ # Get the level of our element:
598
+ level = @levels[pos[0]]
599
+ # Element can obviously only have parents if it is not a top level element:
600
+ unless level == 0
601
+ # Search backwards, and record the position every time we encounter an upwards change in the level number.
602
+ parents = Array.new()
603
+ prev_level = level
604
+ search_arr = @levels[0..pos[0]-1].reverse
605
+ search_arr.each_index do |i|
606
+ if search_arr[i] < prev_level
607
+ parents += [search_arr.length-i-1]
608
+ prev_level = search_arr[i]
609
+ end
581
610
  end
582
- end
583
- # When tag has several generations of parents, we want its top parent to be first in the returned array:
584
- parents = parents.reverse
585
- end # of if level == 0
586
- end # of if pos == false..else
587
- return parents
611
+ # When the element has several generations of parents, we want its top parent to be first in the returned array:
612
+ parents = parents.reverse
613
+ value = parents if parents.length > 0
614
+ end # of if level == 0
615
+ end # of if pos.length..else..
616
+ end
617
+ return value
588
618
  end # of method parents
589
-
590
-
619
+
620
+
591
621
  ##############################################
592
622
  ####### START OF METHODS FOR PRINTING INFORMATION:######
593
623
  ##############################################
594
624
 
595
625
 
596
- # Prints information of all tags stored in the DICOM object.
626
+ # Prints the information of all elements stored in the DICOM object.
597
627
  # This method is kept for backwards compatibility.
598
628
  # Instead of calling print_all() you may use print(true) for the same functionality.
599
629
  def print_all()
@@ -601,98 +631,86 @@ module DICOM
601
631
  end
602
632
 
603
633
 
604
- # Prints the tag information of the specified tags (index, [hierarchy level, tree visualisation,] label, name, type, length, value)
605
- # The supplied variable may be a single position, an array of positions, or true - which will make the method print all tags in object.
606
- # Tag(s) may be specified by position, label or name.
607
- # Options:
608
- # :levels => true - will make the method print the level numbers for each tag.
609
- # tree => true - will make the method print a tree structure for the tags.
610
- # :file => true - will make the method print to file instead of printing to screen.
634
+ # Prints the information of the specified elements: Index, [hierarchy level, tree visualisation,] tag, name, type, length, value
635
+ # The supplied variable may be a single position, an array of positions, or true - which will make the method print all elements.
636
+ # Optional arguments:
637
+ # :levels => true - method will print the level numbers for each element.
638
+ # :tree => true - method will print a tree structure for the elements.
639
+ # :file => true - method will print to file instead of printing to screen.
611
640
  def print(pos, opts={})
612
641
  # Process option values, setting defaults for the ones that are not specified:
613
642
  opt_levels = opts[:levels] || false
614
643
  opt_tree = opts[:tree] || false
615
- opt_file = opts[:file] || false
616
- # Convert to array if number:
644
+ opt_file = opts[:file] || false
645
+ # If pos is false, abort, and inform the user:
646
+ if pos == false
647
+ add_msg("Warning: Method print() was supplied false instead of a valid position. Aborting print.")
648
+ return
649
+ end
617
650
  if not pos.is_a?(Array) and pos != true
618
- pos_valid = get_pos(pos)
651
+ # Convert to array if number:
652
+ pos_valid = [pos]
619
653
  elsif pos == true
620
- # Create an array of positions which a
621
- pos_valid = Array.new(@names.length)
622
- # Fill in indices:
623
- pos_valid.each_index do |i|
624
- pos_valid[i]=i
625
- end
626
- else
627
- # Check that the supplied array contains valid positions:
628
- pos_valid = Array.new()
629
- pos.each_index do |i|
630
- if pos[i] >= 0 and pos[i] <= @names.length
631
- pos_valid += [pos[i]]
632
- end
633
- end
634
- end
635
- # Continue only if we have valid positions:
636
- if pos_valid == false
637
- return
638
- elsif pos_valid.size == 0
639
- return
654
+ # Create a complete array of indices:
655
+ pos_valid = Array.new(@names.length) {|i| i}
656
+ else
657
+ # Use the supplied array of numbers:
658
+ pos_valid = pos
640
659
  end
641
- # We have valid positions and are ready to start process the tags:
642
660
  # Extract the information to be printed from the object arrays:
643
661
  indices = Array.new()
644
662
  levels = Array.new()
645
- labels = Array.new()
663
+ tags = Array.new()
646
664
  names = Array.new()
647
665
  types = Array.new()
648
666
  lengths = Array.new()
649
667
  values = Array.new()
650
668
  # There may be a more elegant way to do this.
651
669
  pos_valid.each do |pos|
652
- labels += [@labels[pos]]
670
+ tags += [@tags[pos]]
653
671
  levels += [@levels[pos]]
654
672
  names += [@names[pos]]
655
673
  types += [@types[pos]]
656
674
  lengths += [@lengths[pos].to_s]
657
675
  values += [@values[pos].to_s]
658
- end
676
+ end
659
677
  # We have collected the data that is to be printed, now we need to do some string manipulation if hierarchy is to be displayed:
660
678
  if opt_tree
661
679
  # Tree structure requested.
662
680
  front_symbol = "| "
663
681
  tree_symbol = "|_"
664
- labels.each_index do |i|
682
+ tags.each_index do |i|
665
683
  if levels[i] != 0
666
- labels[i] = front_symbol*(levels[i]-1) + tree_symbol + labels[i]
684
+ tags[i] = front_symbol*(levels[i]-1) + tree_symbol + tags[i]
667
685
  end
668
686
  end
669
687
  end
670
688
  # Extract the string lengths which are needed to make the formatting nice:
671
- label_lengths = Array.new()
689
+ tag_lengths = Array.new()
672
690
  name_lengths = Array.new()
673
691
  type_lengths = Array.new()
674
692
  length_lengths = Array.new()
675
693
  names.each_index do |i|
676
- label_lengths[i] = labels[i].length
694
+ tag_lengths[i] = tags[i].length
677
695
  name_lengths[i] = names[i].length
678
696
  type_lengths[i] = types[i].length
679
697
  length_lengths[i] = lengths[i].to_s.length
680
698
  end
681
699
  # To give the printed output a nice format we need to check the string lengths of some of these arrays:
682
700
  index_maxL = pos_valid.max.to_s.length
683
- label_maxL = label_lengths.max
701
+ tag_maxL = tag_lengths.max
684
702
  name_maxL = name_lengths.max
685
703
  type_maxL = type_lengths.max
686
704
  length_maxL = length_lengths.max
687
- # Construct the strings, one for each line of output, where each line contain the information of one tag:
688
- tags = Array.new()
689
- # Start of loop which formats the tags:
705
+ # Construct the strings, one for each line of output, where each line contain the information of one data element:
706
+ elements = Array.new()
707
+ # Start of loop which formats the element data:
690
708
  # (This loop is what consumes most of the computing time of this method)
691
- labels.each_index do |i|
709
+ tags.each_index do |i|
692
710
  # Configure empty spaces:
693
711
  s = " "
694
712
  f0 = " "*(index_maxL-pos_valid[i].to_s.length)
695
- f2 = " "*(label_maxL-labels[i].length+1)
713
+ f2 = " "*(tag_maxL-tags[i].length+1)
696
714
  f3 = " "*(name_maxL-names[i].length+1)
697
715
  f4 = " "*(type_maxL-types[i].length+1)
698
716
  f5 = " "*(length_maxL-lengths[i].to_s.length)
@@ -708,23 +726,23 @@ module DICOM
708
726
  else
709
727
  value = (values[i])
710
728
  end
711
- # Insert descriptive text for tags that hold binary data:
729
+ # Insert descriptive text for elements that hold binary data:
712
730
  case types[i]
713
731
  when "OW","OB","UN"
714
732
  value = "(Binary Data)"
715
733
  when "SQ","()"
716
- value = "(Encapsulated Elements)"
734
+ value = "(Encapsulated Elements)"
717
735
  end
718
- tags += [f0 + pos_valid[i].to_s + s + lev + s + labels[i] + f2 + names[i] + f3 + types[i] + f4 + f5 + lengths[i].to_s + s + s + value.rstrip]
736
+ elements += [f0 + pos_valid[i].to_s + s + lev + s + tags[i] + f2 + names[i] + f3 + types[i] + f4 + f5 + lengths[i].to_s + s + s + value.rstrip]
719
737
  end
720
738
  # Print to either screen or file, depending on what the user requested:
721
739
  if opt_file
722
- print_file(tags)
740
+ print_file(elements)
723
741
  else
724
- print_screen(tags)
725
- end # of labels.each do |i|
742
+ print_screen(elements)
743
+ end # of tags.each do |i|
726
744
  end # of method print
727
-
745
+
728
746
 
729
747
  # Prints the key structural properties of the DICOM file.
730
748
  def print_properties()
@@ -775,14 +793,14 @@ module DICOM
775
793
  end
776
794
  puts "-------------------------------"
777
795
  end # of method print_properties
778
-
779
-
796
+
797
+
780
798
  ####################################################
781
799
  ### START OF METHODS FOR WRITING INFORMATION TO THE DICOM OBJECT:#
782
800
  ####################################################
783
-
784
-
785
- # Reads binary information from file and inserts it in the pixel data tag:
801
+
802
+
803
+ # Reads binary information from file and inserts it in the pixel data element:
786
804
  def set_image_file(file)
787
805
  # Try to read file:
788
806
  begin
@@ -791,104 +809,99 @@ module DICOM
791
809
  rescue
792
810
  # Reading file was not successful. Register an error message.
793
811
  add_msg("Reading specified file was not successful for some reason. No data has been added.")
812
+ return
794
813
  end
795
814
  if bin.length > 0
796
- pos = @labels.index("7FE0,0010")
797
- # Modify tag:
798
- set_value(bin, :label => "7FE0,0010", :create => true, :bin => true)
815
+ pos = @tags.index("7FE0,0010")
816
+ # Modify element:
817
+ set_value(bin, "7FE0,0010", :create => true, :bin => true)
799
818
  else
800
819
  add_msg("Content of file is of zero length. Nothing to store.")
801
820
  end # of if bin.length > 0
802
821
  end # of method set_image_file
803
-
804
-
805
- # Transfers pixel data from a RMagick object to the pixel data tag:
822
+
823
+
824
+ # Transfers pixel data from a RMagick object to the pixel data element:
806
825
  def set_image_magick(magick_obj)
807
826
  # Export the RMagick object to a standard Ruby array of numbers:
808
827
  pixel_array = magick_obj.export_pixels(x=0, y=0, columns=magick_obj.columns, rows=magick_obj.rows, map="I")
809
828
  # Encode this array using the standard class method:
810
- set_value(pixel_array, :label => "7FE0,0010", :create => true)
829
+ set_value(pixel_array, "7FE0,0010", :create => true)
811
830
  end
812
-
813
-
814
- # Removes a tag from the DICOM object:
815
- def remove_tag(tag)
816
- pos = get_pos(tag)
817
- if pos != nil
818
- # Extract first array number:
819
- pos = pos[0]
820
- # Update group length:
821
- if @labels[pos][5..8] != "0000"
822
- change = @lengths[pos]
823
- vr = @types[pos]
824
- update_group_length(pos, vr, change, -1)
831
+
832
+
833
+ # Removes an element from the DICOM object:
834
+ def remove(element)
835
+ pos = get_pos(element)
836
+ if pos != false
837
+ if pos.length > 1
838
+ add_msg("Warning: Method remove() does not allow an element query which yields multiple array hits. Please use array position instead of tag/name. Value NOT removed.")
839
+ else
840
+ # Extract first array number:
841
+ pos = pos[0]
842
+ # Update group length:
843
+ if @tags[pos][5..8] != "0000"
844
+ change = @lengths[pos]
845
+ vr = @types[pos]
846
+ update_group_length(pos, vr, change, -1)
847
+ end
848
+ # Remove entry from arrays:
849
+ @tags.delete_at(pos)
850
+ @levels.delete_at(pos)
851
+ @names.delete_at(pos)
852
+ @types.delete_at(pos)
853
+ @lengths.delete_at(pos)
854
+ @values.delete_at(pos)
855
+ @raw.delete_at(pos)
825
856
  end
826
- # Remove entry from arrays:
827
- @labels.delete_at(pos)
828
- @levels.delete_at(pos)
829
- @names.delete_at(pos)
830
- @types.delete_at(pos)
831
- @lengths.delete_at(pos)
832
- @values.delete_at(pos)
833
- @raw.delete_at(pos)
834
857
  else
835
- add_msg("Tag #{tag} not found in DICOM object.")
858
+ add_msg("Warning: The data element #{element} could not be found in the DICOM object. Method remove() has no data element to remove.")
836
859
  end
837
860
  end
838
-
839
-
840
- # Sets the value of a tag, by modifying an existing tag or creating a new tag.
841
- # If the supplied value is not binary, it will attempt to encode it to binary itself.
842
- def set_value(value, opts={})
861
+
862
+
863
+ # Sets the value of a data element by modifying an existing element or creating a new one.
864
+ # If the supplied value is not binary, it will attempt to encode the value to binary itself.
865
+ def set_value(value, element, opts={})
843
866
  # Options:
844
- label = opts[:label]
845
- pos = opts[:pos]
846
- create = opts[:create] # =false means no tag creation
867
+ create = opts[:create] # =false means no element creation
847
868
  bin = opts[:bin] # =true means value already encoded
848
- # Abort if neither label nor position has been specified:
849
- if label == nil and pos == nil
850
- add_msg("Valid position not provided; can not modify or create tag. Please use keyword :pos or :label to specify.")
851
- return
852
- end
853
- # If position is specified, check that it is valid.
854
- # If label is specified, check that it doesnt correspond to multiple labels:
855
- if pos != nil
856
- unless pos >= 0 and pos <= @labels.length
857
- # This is not a valid position:
858
- pos = nil
859
- end
860
- else
861
- pos = get_pos(label)
862
- if pos == false
863
- pos = nil
864
- elsif pos.size > 1
865
- pos = 'abort'
866
- add_msg("The supplied label is found at multiple locations in the DICOM object. Will not update.")
867
- end
868
- end # of if pos != nil
869
- # Create or modify?
870
- if create == false
871
- # User wants modification only. Proceed only if we have a valid position:
872
- unless pos == nil
873
- # Modify tag:
874
- modify_tag(value, :bin => bin, :pos => pos)
869
+ # Retrieve array position:
870
+ pos = get_pos(element)
871
+ # We do not support changing multiple data elements:
872
+ if pos.is_a?(Array)
873
+ if pos.length > 1
874
+ add_msg("Warning: Method set_value() does not allow an element query which yields multiple array hits. Please use array position instead of tag/name. Value NOT saved.")
875
+ return
875
876
  end
877
+ end
878
+ if pos == false and create == false
879
+ # Since user has requested an element shall only be updated, we can not do so as the element position is not valid:
880
+ add_msg("Warning: Invalid data element provided to method set_value(). Value NOT updated.")
881
+ elsif create == false
882
+ # Modify element:
883
+ modify_element(value, pos[0], :bin => bin)
876
884
  else
877
- # User wants to create (or modify if present). Only abort if multiple hits have been found.
878
- unless pos == 'abort'
879
- if pos == nil
880
- # As we wish to create new tag, we need to find out where to insert it in the tag array:
881
- # We will do this by finding the last array position of the last tag that will stay in front of this tag.
882
- if @labels.size > 0
885
+ # User wants to create an element (or modify it if it is already present).
886
+ unless pos == false
887
+ # The data element already exist, so we modify instead of creating:
888
+ modify_element(value, pos[0], :bin => bin)
889
+ else
890
+ # We need to create element:
891
+ tag = @lib.get_tag(element)
892
+ if tag == false
893
+ add_msg("Warning: Method set_value() could not create data element, either because data element name was not recognized in the library, or data element tag is invalid (Expected format of tags is 'GGGG,EEEE').")
894
+ else
895
+ # As we wish to create a new data element, we need to find out where to insert it in the element arrays:
896
+ # We will do this by finding the last array position of the last element that will (alphabetically/numerically) stay in front of this element.
897
+ if @tags.size > 0
883
898
  # Search the array:
884
899
  index = -1
885
900
  quit = false
886
901
  while quit != true do
887
- if index+1 >= @labels.length # We have reached end of array.
902
+ if index+1 >= @tags.length # We have reached end of array.
888
903
  quit = true
889
- #elsif index+1 == @labels.length
890
- #quit = true
891
- elsif label < @labels[index+1] # We are past the correct position.
904
+ elsif tag < @tags[index+1] # We are past the correct position.
892
905
  quit = true
893
906
  else # Increase index in anticipation of a 'hit'.
894
907
  index += 1
@@ -898,20 +911,11 @@ module DICOM
898
911
  # We are dealing with an empty DICOM object:
899
912
  index = nil
900
913
  end
901
- # Before we allow tag creation, do a simple check that the label seems valid:
902
- if label.length == 9
903
- # Create new tag:
904
- create_tag(value, :bin => bin, :label => label, :lastpos => index)
905
- else
906
- # Label did not pass our check:
907
- add_msg("The label you specified (#{label}) does not seem valid. Please use the format 'GGGG,EEEE'.")
908
- end
909
- else
910
- # Modify existing:
911
- modify_tag(value, :bin => bin, :pos => pos)
912
- end
913
- end
914
- end # of if create == false
914
+ # The necessary information is gathered; create new data element:
915
+ create_element(value, tag, index, :bin => bin)
916
+ end # of if tag ==..else..
917
+ end # of unless pos ==..else..
918
+ end # of if pos ==..and create ==..else..
915
919
  end # of method set_value
916
920
 
917
921
 
@@ -929,28 +933,26 @@ module DICOM
929
933
  if (msg.is_a? String)
930
934
  msg=[msg]
931
935
  end
932
- @msg += msg
936
+ @errors += msg
933
937
  end
934
-
935
-
938
+
939
+
936
940
  # Checks the endianness of the system. Returns false if little endian, true if big endian.
937
941
  def check_sys_endian()
938
942
  x = 0xdeadbeef
939
943
  endian_type = {
940
944
  Array(x).pack("V*") => false, #:little
941
- Array(x).pack("N*") => true #:big
945
+ Array(x).pack("N*") => true #:big
942
946
  }
943
- return endian_type[Array(x).pack("L*")]
947
+ return endian_type[Array(x).pack("L*")]
944
948
  end
945
-
946
-
947
- # Creates a new tag:
948
- def create_tag(value, opts={})
949
+
950
+
951
+ # Creates a new data element:
952
+ def create_element(value, tag, last_pos, opts={})
949
953
  bin_only = opts[:bin]
950
- label = opts[:label]
951
- lastpos = opts[:lastpos]
952
954
  # Fetch the VR:
953
- info = @lib.get_name_vr(label)
955
+ info = @lib.get_name_vr(tag)
954
956
  vr = info[1]
955
957
  name = info[0]
956
958
  # Encode binary (if a binary is not provided):
@@ -963,67 +965,64 @@ module DICOM
963
965
  # Encode:
964
966
  bin = encode(value, vr)
965
967
  else
966
- add_msg("Error. Unable to encode tag value of unknown type!")
968
+ add_msg("Error. Unable to encode data element value of unknown type (Value Representation)!")
967
969
  end
968
970
  end
969
- # Put this tag information into the arrays:
971
+ # Put the information of this data element into the arrays:
970
972
  if bin
971
- #if bin.length > 0
972
- # 4 different scenarios: Array is empty, or: tag is put in front, inside array, or at end of array:
973
- if lastpos == nil
974
- # We have empty DICOM object:
975
- @labels = [label]
976
- @levels = [0]
977
- @names = [name]
978
- @types = [vr]
979
- @lengths = [bin.length]
980
- @values = [value]
981
- @raw = [bin]
982
- elsif lastpos == -1
983
- # Insert in front of arrays:
984
- @labels = [label] + @labels
985
- @levels = [0] + @levels # NB! No support for hierarchy at this time!
986
- @names = [name] + @names
987
- @types = [vr] + @types
988
- @lengths = [bin.length] + @lengths
989
- @values = [value] + @values
990
- @raw = [bin] + @raw
991
- elsif lastpos == @labels.length-1
992
- # Insert at end arrays:
993
- @labels = @labels + [label]
994
- @levels = @levels + [0] # Defaulting to level = 0
995
- @names = @names + [name]
996
- @types = @types + [vr]
997
- @lengths = @lengths + [bin.length]
998
- @values = @values + [value]
999
- @raw = @raw + [bin]
1000
- else
1001
- # Insert somewhere inside the array:
1002
- @labels = @labels[0..lastpos] + [label] + @labels[(lastpos+1)..(@labels.length-1)]
1003
- @levels = @levels[0..lastpos] + [0] + @levels[(lastpos+1)..(@levels.length-1)] # Defaulting to level = 0
1004
- @names = @names[0..lastpos] + [name] + @names[(lastpos+1)..(@names.length-1)]
1005
- @types = @types[0..lastpos] + [vr] + @types[(lastpos+1)..(@types.length-1)]
1006
- @lengths = @lengths[0..lastpos] + [bin.length] + @lengths[(lastpos+1)..(@lengths.length-1)]
1007
- @values = @values[0..lastpos] + [value] + @values[(lastpos+1)..(@values.length-1)]
1008
- @raw = @raw[0..lastpos] + [bin] + @raw[(lastpos+1)..(@raw.length-1)]
1009
- end
1010
- # Update last index variable as we have added to our arrays:
1011
- @last_index += 1
1012
- # Update group length (as long as it was not a group length tag that was created):
1013
- pos = @labels.index(label)
1014
- if @labels[pos][5..8] != "0000"
1015
- change = bin.length
1016
- update_group_length(pos, vr, change, 1)
1017
- end
1018
- #else
1019
- #add_msg("Binary does not have a positive length, nothing to save.")
1020
- #end # of if bin.length > 0
973
+ # 4 different scenarios: Array is empty, or: element is put in front, inside array, or at end of array:
974
+ # NB! No support for hierarchy at this time! Defaulting to level = 0.
975
+ if last_pos == nil
976
+ # We have empty DICOM object:
977
+ @tags = [tag]
978
+ @levels = [0]
979
+ @names = [name]
980
+ @types = [vr]
981
+ @lengths = [bin.length]
982
+ @values = [value]
983
+ @raw = [bin]
984
+ elsif last_pos == -1
985
+ # Insert in front of arrays:
986
+ @tags = [tag] + @tags
987
+ @levels = [0] + @levels
988
+ @names = [name] + @names
989
+ @types = [vr] + @types
990
+ @lengths = [bin.length] + @lengths
991
+ @values = [value] + @values
992
+ @raw = [bin] + @raw
993
+ elsif last_pos == @tags.length-1
994
+ # Insert at end arrays:
995
+ @tags = @tags + [tag]
996
+ @levels = @levels + [0]
997
+ @names = @names + [name]
998
+ @types = @types + [vr]
999
+ @lengths = @lengths + [bin.length]
1000
+ @values = @values + [value]
1001
+ @raw = @raw + [bin]
1002
+ else
1003
+ # Insert somewhere inside the array:
1004
+ @tags = @tags[0..last_pos] + [tag] + @tags[(last_pos+1)..(@tags.length-1)]
1005
+ @levels = @levels[0..last_pos] + [0] + @levels[(last_pos+1)..(@levels.length-1)]
1006
+ @names = @names[0..last_pos] + [name] + @names[(last_pos+1)..(@names.length-1)]
1007
+ @types = @types[0..last_pos] + [vr] + @types[(last_pos+1)..(@types.length-1)]
1008
+ @lengths = @lengths[0..last_pos] + [bin.length] + @lengths[(last_pos+1)..(@lengths.length-1)]
1009
+ @values = @values[0..last_pos] + [value] + @values[(last_pos+1)..(@values.length-1)]
1010
+ @raw = @raw[0..last_pos] + [bin] + @raw[(last_pos+1)..(@raw.length-1)]
1011
+ end
1012
+ # Update last index variable as we have added to our arrays:
1013
+ @last_index += 1
1014
+ # Update group length (as long as it was not a group length element that was created):
1015
+ pos = @tags.index(tag)
1016
+ if @tags[pos][5..8] != "0000"
1017
+ change = bin.length
1018
+ update_group_length(pos, vr, change, 1)
1019
+ end
1021
1020
  else
1022
1021
  add_msg("Binary is nil. Nothing to save.")
1023
1022
  end
1024
- end # of method create_tag
1025
-
1026
-
1023
+ end # of method create_element
1024
+
1025
+
1027
1026
  # Encodes a value to binary (used for inserting values to a DICOM object).
1028
1027
  def encode(value, vr)
1029
1028
  # Our value needs to be inside an array to be encoded:
@@ -1042,7 +1041,7 @@ module DICOM
1042
1041
  bin = value.pack(@fs)
1043
1042
  when "FD"
1044
1043
  bin = value.pack(@fd)
1045
- when "AT" # (tag label - assumes it has the format GGGGEEEE (no comma separation))
1044
+ when "AT" # (Data element tag: Assume it has the format GGGGEEEE (no comma separation))
1046
1045
  # Encode letter pairs indexes in following order 10 3 2:
1047
1046
  # NB! This may not be encoded correctly on Big Endian files or computers.
1048
1047
  old_format=value[0]
@@ -1051,6 +1050,9 @@ module DICOM
1051
1050
 
1052
1051
  # We have a number of VRs that are encoded as string:
1053
1052
  when 'AE','AS','CS','DA','DS','DT','IS','LO','LT','PN','SH','ST','TM','UI','UT'
1053
+ # In case we are dealing with a number string element, the supplied value might be a number
1054
+ # instead of a string, and as such, we convert to string just to make sure this will work nicely:
1055
+ value[0] = value[0].to_s
1054
1056
  # Odd/even test (num[0]=1 if num is odd):
1055
1057
  if value[0].length[0] == 1
1056
1058
  # Odd (add a zero byte):
@@ -1064,8 +1066,8 @@ module DICOM
1064
1066
  # What bit depth to use when encoding the pixel data?
1065
1067
  bit_depth = get_value("0028,0100")
1066
1068
  if bit_depth == false
1067
- # Tag not specified:
1068
- add_msg("Attempted to encode pixel data, but bit depth tag is missing (0028,0100).")
1069
+ # Data element not specified:
1070
+ add_msg("Attempted to encode pixel data, but 'Bit Depth' data element is missing (0028,0100).")
1069
1071
  else
1070
1072
  # 8,12 or 16 bits?
1071
1073
  case bit_depth
@@ -1082,16 +1084,14 @@ module DICOM
1082
1084
  end # of case bit_depth
1083
1085
  end # of if bit_depth..else..
1084
1086
  else # Unsupported VR:
1085
- add_msg("Tag type #{vr} does not have a dedicated encoding option assigned. Please contact author.")
1087
+ add_msg("Element type #{vr} does not have a dedicated encoding option assigned. Please contact author.")
1086
1088
  end # of case vr
1087
1089
  return bin
1088
1090
  end # of method encode
1089
-
1090
- # Modifies existing tag:
1091
- def modify_tag(value, opts={})
1091
+
1092
+ # Modifies existing data element:
1093
+ def modify_element(value, pos, opts={})
1092
1094
  bin_only = opts[:bin]
1093
- pos = opts[:pos]
1094
- pos = pos[0] if pos.is_a?(Array)
1095
1095
  # Fetch the VR and old length:
1096
1096
  vr = @types[pos]
1097
1097
  old_length = @lengths[pos]
@@ -1105,54 +1105,50 @@ module DICOM
1105
1105
  # Encode:
1106
1106
  bin = encode(value, vr)
1107
1107
  else
1108
- add_msg("Error. Unable to encode tag value of unknown type!")
1108
+ add_msg("Error. Unable to encode data element value of unknown type (Value Representation)!")
1109
1109
  end
1110
1110
  end
1111
1111
  # Update the arrays with this new information:
1112
1112
  if bin
1113
- #if bin.length > 0
1114
- # Replace array entries for this tag:
1115
- #@types[pos] = vr # for the time being there is no logic for updating type.
1116
- @lengths[pos] = bin.length
1117
- @values[pos] = value
1118
- @raw[pos] = bin
1119
- # Update group length (as long as it was not the group length that was modified):
1120
- if @labels[pos][5..8] != "0000"
1121
- change = bin.length - old_length
1122
- update_group_length(pos, vr, change, 0)
1123
- end
1124
- #else
1125
- #add_msg("Binary does not have a positive length, nothing to save.")
1126
- #end
1113
+ # Replace array entries for this element:
1114
+ #@types[pos] = vr # for the time being there is no logic for updating type.
1115
+ @lengths[pos] = bin.length
1116
+ @values[pos] = value
1117
+ @raw[pos] = bin
1118
+ # Update group length (as long as it was not the group length that was modified):
1119
+ if @tags[pos][5..8] != "0000"
1120
+ change = bin.length - old_length
1121
+ update_group_length(pos, vr, change, 0)
1122
+ end
1127
1123
  else
1128
1124
  add_msg("Binary is nil. Nothing to save.")
1129
1125
  end
1130
- end # of method modify_tag
1126
+ end # of method modify_element
1131
1127
 
1132
1128
 
1133
- # Prints the selected tags to an ascii text file.
1129
+ # Prints the selected elements to an ascii text file.
1134
1130
  # The text file will be saved in the folder of the original DICOM file,
1135
1131
  # with the original file name plus a .txt extension.
1136
- def print_file(tags)
1132
+ def print_file(elements)
1137
1133
  File.open( @file + '.txt', 'w' ) do |output|
1138
- tags.each do | line |
1134
+ elements.each do | line |
1139
1135
  output.print line + "\n"
1140
1136
  end
1141
1137
  end
1142
1138
  end
1143
1139
 
1144
-
1145
- # Prints the selected tags to screen.
1146
- def print_screen(tags)
1147
- tags.each do | tag |
1148
- puts tag
1140
+
1141
+ # Prints the selected elements to screen.
1142
+ def print_screen(elements)
1143
+ elements.each do |element|
1144
+ puts element
1149
1145
  end
1150
1146
  end
1151
-
1152
-
1147
+
1148
+
1153
1149
  # Sets the modality variable of the current DICOM object, by querying the library with the object's SOP Class UID.
1154
1150
  def set_modality()
1155
- value = get_value("0008,0016")
1151
+ value = get_value("0008,0016", :silent => true)
1156
1152
  if value == false
1157
1153
  @modality = "Not specified"
1158
1154
  else
@@ -1160,8 +1156,8 @@ module DICOM
1160
1156
  @modality = modality
1161
1157
  end
1162
1158
  end
1163
-
1164
-
1159
+
1160
+
1165
1161
  # Sets the format strings that will be used for packing/unpacking numbers depending on endianness of file/system.
1166
1162
  def set_format_strings(file_endian=@file_endian)
1167
1163
  if @file_endian == @sys_endian
@@ -1186,50 +1182,50 @@ module DICOM
1186
1182
  @fd = "G*"
1187
1183
  end
1188
1184
  end
1189
-
1190
-
1191
- # Updates the group length value when a tag has been updated, created or removed:
1192
- # Variable change holds the change in value length for the updated tag.
1193
- # (Change should be positive when a tag is removed - it will only be negative when editing a tag to a shorter value)
1194
- # Variable existance is -1 if tag has been removed, +1 if tag has been added and 0 if it has been updated.
1185
+
1186
+
1187
+ # Updates the group length value when a data element has been updated, created or removed:
1188
+ # The variable change holds the change in value length for the updated data element.
1189
+ # (Change should be positive when a data element is removed - it will only be negative when editing an element to a shorter value)
1190
+ # The variable existance is -1 if data element has been removed, +1 if element has been added and 0 if it has been updated.
1195
1191
  # (Perhaps in the future this functionality might be moved to the DWrite class, it might give an easier implementation)
1196
1192
  def update_group_length(pos, type, change, existance)
1197
1193
  # Find position of relevant group length (if it exists):
1198
- gl_pos = @labels.index(@labels[pos][0..4] + "0000")
1194
+ gl_pos = @tags.index(@tags[pos][0..4] + "0000")
1199
1195
  existance = 0 if existance == nil
1200
1196
  # If it exists, calculate change:
1201
1197
  if gl_pos
1202
1198
  if existance == 0
1203
- # Tag has only been updated, so we only need to think about value change:
1199
+ # Element has only been updated, so we only need to think about value change:
1204
1200
  value = @values[gl_pos] + change
1205
1201
  else
1206
- # Tag has either been created or removed. This means we need to calculate the length of its other parts.
1202
+ # Element has either been created or removed. This means we need to calculate the length of its other parts.
1207
1203
  if @explicit
1208
1204
  # In the explicit scenario it is slightly complex to determine this value:
1209
- tag_length = 0
1205
+ element_length = 0
1210
1206
  # VR?:
1211
- unless @labels[pos] == "FFFE,E000" or @labels[pos] == "FFFE,E00D" or @labels[pos] == "FFFE,E0DD"
1212
- tag_length += 2
1207
+ unless @tags[pos] == "FFFE,E000" or @tags[pos] == "FFFE,E00D" or @tags[pos] == "FFFE,E0DD"
1208
+ element_length += 2
1213
1209
  end
1214
1210
  # Length value:
1215
1211
  case @types[pos]
1216
1212
  when "OB","OW","SQ","UN"
1217
- if pos > @labels.index("7FE0,0010").to_i and @labels.index("7FE0,0010").to_i != 0
1218
- tag_length += 4
1213
+ if pos > @tags.index("7FE0,0010").to_i and @tags.index("7FE0,0010").to_i != 0
1214
+ element_length += 4
1219
1215
  else
1220
- tag_length += 6
1216
+ element_length += 6
1221
1217
  end
1222
1218
  when "()"
1223
- tag_length += 4
1219
+ element_length += 4
1224
1220
  else
1225
- tag_length += 2
1221
+ element_length += 2
1226
1222
  end # of case
1227
1223
  else
1228
1224
  # In the implicit scenario it is easier:
1229
- tag_length = 4
1225
+ element_length = 4
1230
1226
  end
1231
1227
  # Update group length for creation/deletion scenario:
1232
- change = (4 + tag_length + change) * existance
1228
+ change = (4 + element_length + change) * existance
1233
1229
  value = @values[gl_pos] + change
1234
1230
  end
1235
1231
  # Write the new Group Length value:
@@ -1243,4 +1239,4 @@ module DICOM
1243
1239
 
1244
1240
 
1245
1241
  end # End of class
1246
- end # End of module
1242
+ end # End of module