dicom 0.9.4 → 0.9.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.
@@ -14,6 +14,27 @@ module DICOM
14
14
 
15
15
  include ImageProcessor
16
16
 
17
+ # Creates an Element with the given arguments and connects it to self.
18
+ #
19
+ # @param [String] tag an element tag
20
+ # @param [String, Integer, Float, Array, NilClass] value an element value
21
+ # @param [Hash] options any options used for creating the element (see Element.new documentation)
22
+ #
23
+ def add_element(tag, value, options={})
24
+ add(e = Element.new(tag, value, options))
25
+ e
26
+ end
27
+
28
+ # Creates a Sequence with the given arguments and connects it to self.
29
+ #
30
+ # @param [String] tag a sequence tag
31
+ # @param [Hash] options any options used for creating the sequence (see Sequence.new documentation)
32
+ #
33
+ def add_sequence(tag, options={})
34
+ add(s = Sequence.new(tag, options))
35
+ s
36
+ end
37
+
17
38
  # Checks if colored pixel data is present.
18
39
  #
19
40
  # @return [Boolean] true if the object contains colored pixels, and false if not
@@ -22,7 +43,7 @@ module DICOM
22
43
  # "Photometric Interpretation" is contained in the data element "0028,0004":
23
44
  begin
24
45
  photometric = photometry
25
- if photometric.include?("COLOR") or photometric.include?("RGB") or photometric.include?("YBR")
46
+ if photometric.include?('COLOR') or photometric.include?('RGB') or photometric.include?('YBR')
26
47
  return true
27
48
  else
28
49
  return false
@@ -56,8 +77,8 @@ module DICOM
56
77
  raise ArgumentError, "Expected String, got #{bin.class}." unless bin.is_a?(String)
57
78
  pixels = false
58
79
  # We need to know what kind of bith depth and integer type the pixel data is saved with:
59
- bit_depth_element = self["0028,0100"]
60
- pixel_representation_element = self["0028,0103"]
80
+ bit_depth_element = self['0028,0100']
81
+ pixel_representation_element = self['0028,0103']
61
82
  if bit_depth_element and pixel_representation_element
62
83
  # Load the binary pixel data to the Stream instance:
63
84
  stream.set_string(bin)
@@ -81,8 +102,8 @@ module DICOM
81
102
  raise ArgumentError, "Expected Array, got #{pixels.class}." unless pixels.is_a?(Array)
82
103
  bin = false
83
104
  # We need to know what kind of bith depth and integer type the pixel data is saved with:
84
- bit_depth_element = self["0028,0100"]
85
- pixel_representation_element = self["0028,0103"]
105
+ bit_depth_element = self['0028,0100']
106
+ pixel_representation_element = self['0028,0103']
86
107
  if bit_depth_element and pixel_representation_element
87
108
  template = template_string(bit_depth_element.value.to_i)
88
109
  bin = stream.encode(pixels, template) if template
@@ -187,7 +208,7 @@ module DICOM
187
208
  #
188
209
  def image_from_file(file)
189
210
  raise ArgumentError, "Expected #{String}, got #{file.class}." unless file.is_a?(String)
190
- f = File.new(file, "rb")
211
+ f = File.new(file, 'rb')
191
212
  bin = f.read(f.stat.size)
192
213
  if bin.length > 0
193
214
  # Write the binary data to the Pixel Data Element:
@@ -235,18 +256,18 @@ module DICOM
235
256
  parts = file.split('.')
236
257
  if parts.length > 1
237
258
  base = parts[0..-2].join
238
- extension = "." + parts.last
259
+ extension = '.' + parts.last
239
260
  else
240
261
  base = file
241
- extension = ""
262
+ extension = ''
242
263
  end
243
264
  # Get the binary image strings and dump them to the file(s):
244
265
  images = image_strings
245
266
  images.each_index do |i|
246
267
  if images.length == 1
247
- f = File.new(file, "wb")
268
+ f = File.new(file, 'wb')
248
269
  else
249
- f = File.new("#{base}-#{i}#{extension}", "wb")
270
+ f = File.new("#{base}-#{i}#{extension}", 'wb')
250
271
  end
251
272
  f.write(images[i])
252
273
  f.close
@@ -265,7 +286,7 @@ module DICOM
265
286
  # @param [MagickImage] image the image to be assigned to the pixel data element
266
287
  #
267
288
  def image=(image)
268
- raise ArgumentError, "Expected one of the supported image objects (#{valid_image_objects}), got #{image.class}." unless valid_image_objects.include?(image.class)
289
+ raise ArgumentError, "Expected one of the supported image classes: #{valid_image_objects} (got #{image.class})" unless valid_image_objects.include?(image.class.to_s)
269
290
  # Export to pixels using the proper image processor:
270
291
  pixels = export_pixels(image, photometry)
271
292
  # Encode and write to the Pixel Data Element:
@@ -277,7 +298,7 @@ module DICOM
277
298
  # @return [Integer, NilClass] the number of columns, or nil (if the columns value is undefined)
278
299
  #
279
300
  def num_cols
280
- self["0028,0011"].value rescue nil
301
+ self['0028,0011'].value rescue nil
281
302
  end
282
303
 
283
304
  # Gives the number of frames in the pixel data.
@@ -286,7 +307,7 @@ module DICOM
286
307
  # @return [Integer] the number of rows
287
308
  #
288
309
  def num_frames
289
- (self["0028,0008"].is_a?(Element) == true ? self["0028,0008"].value.to_i : 1)
310
+ (self['0028,0008'].is_a?(Element) == true ? self['0028,0008'].value.to_i : 1)
290
311
  end
291
312
 
292
313
  # Gives the number of rows in the pixel data.
@@ -294,7 +315,7 @@ module DICOM
294
315
  # @return [Integer, NilClass] the number of rows, or nil (if the rows value is undefined)
295
316
  #
296
317
  def num_rows
297
- self["0028,0010"].value rescue nil
318
+ self['0028,0010'].value rescue nil
298
319
  end
299
320
 
300
321
  # Creates an NArray containing the pixel data. If the pixel data is an image
@@ -402,22 +423,21 @@ module DICOM
402
423
  # @param [Array<Integer>, NArray] values an Array (or NArray) containing integer pixel values
403
424
  #
404
425
  def pixels=(values)
405
- raise ArgumentError, "Expected Array or NArray, got #{values.class}." unless [Array, NArray].include?(values.class)
406
- if values.is_a?(NArray)
407
- # When NArray, convert to a Ruby Array:
426
+ raise ArgumentError, "The given argument does not respond to #to_a (got an argument of class #{values.class})" unless values.respond_to?(:to_a)
427
+ if values.class.ancestors.to_s.include?('NArray')
428
+ # With an NArray argument, make sure that it gets properly converted to an Array:
408
429
  if values.shape.length > 2
409
- # We need to take care when reshaping this 3D array so that the pixel values falls properly into place:
430
+ # For a 3D NArray we need to rearrange to ensure that the pixels get their
431
+ # proper order when converting to an ordinary Array instance:
410
432
  narr = NArray.int(values.shape[1] * values.shape[2], values.shape[0])
411
433
  values.shape[0].times do |i|
412
434
  narr[true, i] = values[i, true, true].reshape(values.shape[1] * values.shape[2])
413
435
  end
414
- values = narr.to_a
415
- else
416
- values = values.to_a
436
+ values = narr
417
437
  end
418
438
  end
419
439
  # Encode the pixel data:
420
- bin = encode_pixels(values.flatten)
440
+ bin = encode_pixels(values.to_a.flatten)
421
441
  # Write the binary data to the Pixel Data Element:
422
442
  write_pixels(bin)
423
443
  end
@@ -444,7 +464,7 @@ module DICOM
444
464
  raise "The 'Bits Allocated' Element is missing from this DICOM instance. Unable to encode/decode pixel data." unless exists?("0028,0100")
445
465
  if photometry == PI_PALETTE_COLOR
446
466
  # Only one channel is checked and it is assumed that all channels have the same number of bits.
447
- return self["0028,1101"].value.split("\\").last.to_i
467
+ return self['0028,1101'].value.split("\\").last.to_i
448
468
  else
449
469
  return bit_depth
450
470
  end
@@ -458,7 +478,7 @@ module DICOM
458
478
  #
459
479
  def bit_depth
460
480
  raise "The 'Bits Allocated' Element is missing from this DICOM instance. Unable to encode/decode pixel data." unless exists?("0028,0100")
461
- return value("0028,0100")
481
+ return value('0028,0100')
462
482
  end
463
483
 
464
484
  # Performs a run length decoding on the input stream.
@@ -524,7 +544,7 @@ module DICOM
524
544
  #
525
545
  def photometry
526
546
  raise "The 'Photometric Interpretation' Element is missing from this DICOM instance. Unable to encode/decode pixel data." unless exists?("0028,0004")
527
- return value("0028,0004").upcase
547
+ return value('0028,0004').upcase
528
548
  end
529
549
 
530
550
  # Processes the pixel array based on attributes defined in the DICOM object,
@@ -539,16 +559,16 @@ module DICOM
539
559
  proper_rgb = false
540
560
  photometric = photometry()
541
561
  # (With RLE COLOR PALETTE the Planar Configuration is not set)
542
- planar = self["0028,0006"].is_a?(Element) ? self["0028,0006"].value : 0
562
+ planar = self['0028,0006'].is_a?(Element) ? self['0028,0006'].value : 0
543
563
  # Step 1: Produce an array with RGB values. At this time, YBR is not supported in ruby-dicom,
544
564
  # so this leaves us with a possible conversion from PALETTE COLOR:
545
- if photometric.include?("COLOR")
565
+ if photometric.include?('COLOR')
546
566
  # Pseudo colors (rgb values grabbed from a lookup table):
547
567
  rgb = Array.new(pixels.length*3)
548
568
  # Prepare the lookup data arrays:
549
- lookup_binaries = [self["0028,1201"].bin, self["0028,1202"].bin, self["0028,1203"].bin]
569
+ lookup_binaries = [self['0028,1201'].bin, self['0028,1202'].bin, self['0028,1203'].bin]
550
570
  lookup_values = Array.new
551
- nr_bits = self["0028,1101"].value.split("\\").last.to_i
571
+ nr_bits = self['0028,1101'].value.split("\\").last.to_i
552
572
  template = template_string(nr_bits)
553
573
  lookup_binaries.each do |bin|
554
574
  stream.set_string(bin)
@@ -561,7 +581,7 @@ module DICOM
561
581
  end
562
582
  # As we have now ordered the pixels in RGB order, modify planar configuration to reflect this:
563
583
  planar = 0
564
- elsif photometric.include?("YBR")
584
+ elsif photometric.include?('YBR')
565
585
  rgb = false
566
586
  else
567
587
  rgb = pixels
@@ -739,13 +759,13 @@ module DICOM
739
759
  #
740
760
  def signed_pixels?
741
761
  raise "The 'Pixel Representation' data element is missing from this DICOM instance. Unable to process pixel data." unless exists?("0028,0103")
742
- case value("0028,0103")
762
+ case value('0028,0103')
743
763
  when 1
744
764
  return true
745
765
  when 0
746
766
  return false
747
767
  else
748
- raise "Invalid value encountered (#{value("0028,0103")}) in the 'Pixel Representation' data element. Expected 0 or 1."
768
+ raise "Invalid value encountered (#{value('0028,0103')}) in the 'Pixel Representation' data element. Expected 0 or 1."
749
769
  end
750
770
  end
751
771
 
@@ -757,22 +777,22 @@ module DICOM
757
777
  #
758
778
  def template_string(depth)
759
779
  template = false
760
- pixel_representation = self["0028,0103"].value.to_i
780
+ pixel_representation = self['0028,0103'].value.to_i
761
781
  # Number of bytes used per pixel will determine how to unpack this:
762
782
  case depth
763
783
  when 8 # (1 byte)
764
- template = "BY" # Byte/Character/Fixnum
784
+ template = 'BY' # Byte/Character/Fixnum
765
785
  when 16 # (2 bytes)
766
786
  if pixel_representation == 1
767
- template = "SS" # Signed short
787
+ template = 'SS' # Signed short
768
788
  else
769
- template = "US" # Unsigned short
789
+ template = 'US' # Unsigned short
770
790
  end
771
791
  when 32 # (4 bytes)
772
792
  if pixel_representation == 1
773
- template = "SL" # Signed long
793
+ template = 'SL' # Signed long
774
794
  else
775
- template = "UL" # Unsigned long
795
+ template = 'UL' # Unsigned long
776
796
  end
777
797
  when 12
778
798
  # 12 BIT SIMPLY NOT IMPLEMENTED YET!
@@ -793,10 +813,10 @@ module DICOM
793
813
  # @return [Array<Integer, NilClass>] center, width, intercept and slope
794
814
  #
795
815
  def window_level_values
796
- center = (self["0028,1050"].is_a?(Element) == true ? self["0028,1050"].value.to_i : nil)
797
- width = (self["0028,1051"].is_a?(Element) == true ? self["0028,1051"].value.to_i : nil)
798
- intercept = (self["0028,1052"].is_a?(Element) == true ? self["0028,1052"].value.to_i : 0)
799
- slope = (self["0028,1053"].is_a?(Element) == true ? self["0028,1053"].value.to_i : 1)
816
+ center = (self['0028,1050'].is_a?(Element) == true ? self['0028,1050'].value.to_i : nil)
817
+ width = (self['0028,1051'].is_a?(Element) == true ? self['0028,1051'].value.to_i : nil)
818
+ intercept = (self['0028,1052'].is_a?(Element) == true ? self['0028,1052'].value.to_i : 0)
819
+ slope = (self['0028,1053'].is_a?(Element) == true ? self['0028,1053'].value.to_i : 1)
800
820
  return center, width, intercept, slope
801
821
  end
802
822
 
@@ -47,10 +47,10 @@ module DICOM
47
47
 
48
48
  # Gives an array containing the image objects that are supported by the image processor.
49
49
  #
50
- # @return [Array] the valid image classes
50
+ # @return [Array<String>] the valid image classes
51
51
  #
52
52
  def valid_image_objects
53
- return [Magick::Image, MiniMagick::Image]
53
+ return ['Magick::Image', 'MiniMagick::Image']
54
54
  end
55
55
 
56
56
 
@@ -45,7 +45,7 @@ module DICOM
45
45
  # @param [String] format the image format to use
46
46
  # @return [Magick::Image] a mini_magick image object
47
47
  #
48
- def import_pixels(blob, columns, rows, depth, photometry, format="png")
48
+ def import_pixels(blob, columns, rows, depth, photometry, format='png')
49
49
  image = MiniMagick::Image.import_pixels(blob, columns, rows, depth, im_map(photometry), format)
50
50
  end
51
51
 
@@ -56,12 +56,12 @@ module DICOM
56
56
  #
57
57
  def im_map(photometry)
58
58
  raise ArgumentError, "Expected String, got #{photometry.class}." unless photometry.is_a?(String)
59
- if photometry.include?("COLOR") or photometry.include?("RGB")
60
- return "rgb"
61
- elsif photometry.include?("YBR")
62
- return "ybr"
59
+ if photometry.include?('COLOR') or photometry.include?('RGB')
60
+ return 'rgb'
61
+ elsif photometry.include?('YBR')
62
+ return 'ybr'
63
63
  else
64
- return "gray" # (Assuming monochromeX - greyscale)
64
+ return 'gray' # (Assuming monochromeX - greyscale)
65
65
  end
66
66
  end
67
67
 
@@ -63,7 +63,7 @@ module DICOM
63
63
  # @param [String] format the image format to use
64
64
  # @return [Magick::Image] an RMagick image object
65
65
  #
66
- def import_pixels(blob, columns, rows, depth, photometry, format="png")
66
+ def import_pixels(blob, columns, rows, depth, photometry, format='png')
67
67
  image = Magick::Image.new(columns,rows).import_pixels(0, 0, columns, rows, rm_map(photometry), blob, rm_data_type(depth))
68
68
  end
69
69
 
@@ -91,12 +91,12 @@ module DICOM
91
91
  #
92
92
  def rm_map(photometry)
93
93
  raise ArgumentError, "Expected String, got #{photometry.class}." unless photometry.is_a?(String)
94
- if photometry.include?("COLOR") or photometry.include?("RGB")
95
- return "RGB"
96
- elsif photometry.include?("YBR")
97
- return "YBR"
94
+ if photometry.include?('COLOR') or photometry.include?('RGB')
95
+ return 'RGB'
96
+ elsif photometry.include?('YBR')
97
+ return 'YBR'
98
98
  else
99
- return "I" # (Assuming monochromeX - greyscale)
99
+ return 'I' # (Assuming monochromeX - greyscale)
100
100
  end
101
101
  end
102
102
 
@@ -1415,7 +1415,7 @@ module DICOM
1415
1415
  # Convert the variable to an empty string.
1416
1416
  data = ""
1417
1417
  end
1418
- return data
1418
+ data
1419
1419
  end
1420
1420
 
1421
1421
  # Receives the data from an incoming network transmission.
@@ -1423,17 +1423,13 @@ module DICOM
1423
1423
  #
1424
1424
  def receive_transmission_data
1425
1425
  data = false
1426
- t1 = Time.now.to_f
1427
- @receive = true
1428
- thr = Thread.new{ data=@session.recv(@max_receive_size); @receive=false }
1429
- while @receive
1430
- if (Time.now.to_f - t1) > @timeout
1431
- Thread.kill(thr)
1432
- logger.error("No answer was received within the specified timeout period. Aborting.")
1433
- stop_receiving
1434
- end
1426
+ response = IO.select([@session], nil, nil, @timeout)
1427
+ if response.nil?
1428
+ logger.error("No answer was received within the specified timeout period. Aborting.")
1429
+ else
1430
+ data = @session.recv(@max_receive_size)
1435
1431
  end
1436
- return data
1432
+ data
1437
1433
  end
1438
1434
 
1439
1435
  # Sets some default values related to encoding.
@@ -122,6 +122,7 @@ module DICOM
122
122
  end
123
123
 
124
124
  # The logger object setter.
125
+ #
125
126
  # This method is used to replace the default logger instance with
126
127
  # a custom logger of your own.
127
128
  #
@@ -152,4 +153,4 @@ module DICOM
152
153
  # Include the Logging module so we can use DICOM.logger.
153
154
  include Logging
154
155
 
155
- end
156
+ end
@@ -36,6 +36,41 @@ module DICOM
36
36
  return uid
37
37
  end
38
38
 
39
+ # Loads DICOM data to DObject instances and returns them in an array.
40
+ # Invalid DICOM sources (files) are ignored.
41
+ # If no valid DICOM source is given, an empty array is returned.
42
+ #
43
+ # @param [String, DObject, Array<String, DObject>] data single or multiple DICOM data (directories, file paths, binary strings, DICOM objects)
44
+ # @return [Array<DObject>] an array of successfully loaded DICOM objects
45
+ #
46
+ def load(data)
47
+ data = Array[data] unless data.respond_to?(:to_ary)
48
+ ary = Array.new
49
+ data.each do |element|
50
+ if element.is_a?(String)
51
+ begin
52
+ if File.directory?(element)
53
+ files = Dir[File.join(element, '**/*')].reject {|f| File.directory?(f) }
54
+ dcms = files.collect {|f| DObject.read(f)}
55
+ elsif File.file?(element)
56
+ dcms = [DObject.read(element)]
57
+ else
58
+ dcms = [DObject.parse(element)]
59
+ end
60
+ rescue
61
+ dcms = [DObject.parse(element)]
62
+ end
63
+ ary += dcms.keep_if {|dcm| dcm.read?}
64
+ else
65
+ # The element was not a string, and the only remaining valid element type is a DICOM object:
66
+ raise ArgumentError, "Invalid element (#{element.class}) given. Expected string or DObject." unless element.respond_to?(:to_dcm)
67
+ element.was_dcm_on_input = true
68
+ ary << element.to_dcm
69
+ end
70
+ end
71
+ ary
72
+ end
73
+
39
74
  # Use tags as key. Example: '0010,0010'
40
75
  #
41
76
  def key_use_tags
@@ -65,6 +100,6 @@ module DICOM
65
100
  # The default key representation.
66
101
  self.key_representation = :name
67
102
  # The default source application entity title.
68
- self.source_app_title = 'RUBY_DICOM'
103
+ self.source_app_title = 'RUBY-DICOM'
69
104
 
70
105
  end
@@ -1,6 +1,6 @@
1
1
  module DICOM
2
2
 
3
3
  # The ruby-dicom version string.
4
- VERSION = '0.9.4'
4
+ VERSION = '0.9.5'
5
5
 
6
6
  end
@@ -1,8 +1,10 @@
1
1
  # Available commands:
2
2
  # Testing the specification:
3
3
  # bundle exec rake spec
4
- # Building a gem package from source:
4
+ # Building a gem from source with rake:
5
5
  # bundle exec rake package
6
+ # Building a gem from source with rubygems:
7
+ # bundle exec gem build dicom.gemspec
6
8
  # Create html documentation files:
7
9
  # bundle exec rake yard
8
10