dicom 0.4 → 0.5

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.
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