dicom 0.8 → 0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,34 @@
1
- # This file contains extensions to the Ruby library which are used by Ruby-DICOM.
1
+ # encoding: UTF-8
2
+
3
+ # This file contains extensions to the Ruby library which are used by Ruby DICOM.
2
4
 
3
5
  # Extension to the String class. These extensions are focused on processing/analysing Data Element tags.
4
- # A tag string (as used by the Ruby-DICOM library) is 9 characters long and of the form "GGGG,EEEE"
6
+ # A tag string (as used by the Ruby DICOM library) is 9 characters long and of the form "GGGG,EEEE"
5
7
  # (where G represents a group hexadecimal, and E represents an element hexadecimal).
6
8
  #
7
9
  class String
8
10
 
11
+ # Renames the original unpack method.
12
+ #
13
+ alias __original_unpack__ unpack
14
+
15
+ # Divides a string into a number of sub-strings of exactly equal length, and returns these in an array.
16
+ # The length of self must be a multiple of parts, or an error will be raised.
17
+ #
18
+ def divide(parts)
19
+ raise ArgumentError, "Expected an integer (Fixnum). Got #{parts.class}." unless parts.is_a?(Fixnum)
20
+ raise ArgumentError, "Argument must be in the range <1 - self.length (#{self.length})>. Got #{parts}." if parts < 1 or parts > self.length
21
+ raise ArgumentError, "Length of self (#{self.length}) must be a multiple of parts (#{parts})." unless (self.length/parts).to_f == self.length/parts.to_f
22
+ if parts > 1
23
+ sub_strings = Array.new
24
+ sub_length = self.length/parts
25
+ parts.times { sub_strings << self.slice!(0..(sub_length-1)) }
26
+ return sub_strings
27
+ else
28
+ return [self]
29
+ end
30
+ end
31
+
9
32
  # Returns the element part of the tag string: The last 4 characters.
10
33
  #
11
34
  def element
@@ -44,7 +67,7 @@ class String
44
67
  return ((self.upcase =~ /\A[a-fA-F\d]{3}[1,3,5,7,9,B,D,F],[a-fA-F\d]{4}\z/) == nil ? false : true)
45
68
  end
46
69
 
47
- # Checks if the string is a valid tag (as defined by Ruby-DICOM: "GGGG,EEEE").
70
+ # Checks if the string is a valid tag (as defined by Ruby DICOM: "GGGG,EEEE").
48
71
  # Returns true if it is a valid tag, false if not.
49
72
  #
50
73
  def tag?
@@ -53,4 +76,153 @@ class String
53
76
  return ((self.upcase =~ /\A[a-fA-F\d]{4},[a-fA-F\d]{4}\z/) == nil ? false : true)
54
77
  end
55
78
 
79
+ # Redefines the old unpack method, adding the ability to decode signed integers in big endian.
80
+ #
81
+ # === Parameters
82
+ #
83
+ # * <tt>string</tt> -- A template string which decides the decoding scheme to use.
84
+ #
85
+ def unpack(string)
86
+ # Check for some custom unpack strings that we've invented:
87
+ case string
88
+ when "k*" # SS
89
+ # Unpack BE US, repack LE US, then finally unpack LE SS:
90
+ wrongly_unpacked = self.__original_unpack__("n*")
91
+ repacked = wrongly_unpacked.__original_pack__("S*")
92
+ correct = repacked.__original_unpack__("s*")
93
+ when "r*" # SL
94
+ # Unpack BE UL, repack LE UL, then finally unpack LE SL:
95
+ wrongly_unpacked = self.__original_unpack__("N*")
96
+ repacked = wrongly_unpacked.__original_pack__("I*")
97
+ correct = repacked.__original_unpack__("l*")
98
+ else
99
+ # Call the original method for all other (normal) cases:
100
+ self.__original_unpack__(string)
101
+ end
102
+ end
103
+
104
+ # Returns true for all values that LOOK like a DICOM name - they may not be valid.
105
+ #
106
+ def dicom_name?
107
+ self==self.titleize
108
+ end
109
+
110
+ # Returns true for all strings that LOOK like a DICOM method name - they may not be valid.
111
+ #
112
+ def dicom_method?
113
+ self == self.underscore
114
+ end
115
+
116
+ # Returns a proper DICOM method name string.
117
+ #
118
+ def dicom_methodize
119
+ self.gsub(/^3/,'three_').gsub(/[#*?!]/,' ').gsub(', ',' ').gsub('&','and').gsub(' - ','_').gsub(' / ','_').gsub(/[\s\-\.\,\/\\]/,'_').gsub(/[\(\)\']/,'').gsub(/\_+/, '_').downcase
120
+ end
121
+
122
+ # Capitalizes all the words and replaces some characters in the string to make a nicer looking title.
123
+ #
124
+ def titleize
125
+ self.underscore.gsub(/_/, " ").gsub(/\b('?[a-z])/) { $1.capitalize }
126
+ end
127
+
128
+ # Makes an underscored, lowercase form from the string expression.
129
+ #
130
+ def underscore
131
+ word = self.dup
132
+ word.tr!("-", "_")
133
+ word.downcase!
134
+ word
135
+ end
136
+
137
+ end
138
+
139
+
140
+ # Extensions to the Array class.
141
+ # These methods deal with encoding Integer arrays as well as conversion between signed and unsigned integers.
142
+ #
143
+ class Array
144
+
145
+ # Renames the original pack method.
146
+ #
147
+ alias __original_pack__ pack
148
+
149
+ # Redefines the old pack method, adding the ability to encode signed integers in big endian
150
+ # (which surprisingly has not been supported out of the box in Ruby).
151
+ #
152
+ # === Parameters
153
+ #
154
+ # * <tt>string</tt> -- A template string which decides the encoding scheme to use.
155
+ #
156
+ def pack(string)
157
+ # Check for some custom pack strings that we've invented:
158
+ case string
159
+ when "k*" # SS
160
+ # Pack LE SS, re-unpack as LE US, then finally pack BE US:
161
+ wrongly_packed = self.__original_pack__("s*")
162
+ reunpacked = wrongly_packed.__original_unpack__("S*")
163
+ correct = reunpacked.__original_pack__("n*")
164
+ when "r*" # SL
165
+ # Pack LE SL, re-unpack as LE UL, then finally pack BE UL:
166
+ wrongly_packed = self.__original_pack__("l*")
167
+ reunpacked = wrongly_packed.__original_unpack__("I*")
168
+ correct = reunpacked.__original_pack__("N*")
169
+ else
170
+ # Call the original method for all other (normal) cases:
171
+ self.__original_pack__(string)
172
+ end
173
+ end
174
+
175
+ # Packs an array of (unsigned) integers to a binary string (blob).
176
+ #
177
+ # === Parameters
178
+ #
179
+ # * <tt>depth</tt> -- The bith depth to be used when encoding the unsigned integers.
180
+ #
181
+ def to_blob(depth)
182
+ raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
183
+ raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
184
+ case depth
185
+ when 8
186
+ return self.pack("C*") # Unsigned char
187
+ when 16
188
+ return self.pack("S*") # Unsigned short, native byte order
189
+ end
190
+ end
191
+
192
+ # Shifts the integer values of the array to make a signed data set.
193
+ # The size of the shift is determined by the bit depth.
194
+ #
195
+ # === Parameters
196
+ #
197
+ # * <tt>depth</tt> -- The bith depth of the integers.
198
+ #
199
+ def to_signed(depth)
200
+ raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
201
+ raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
202
+ case depth
203
+ when 8
204
+ return self.collect {|i| i - 128}
205
+ when 16
206
+ return self.collect {|i| i - 32768}
207
+ end
208
+ end
209
+
210
+ # Shifts the integer values of the array to make an unsigned data set.
211
+ # The size of the shift is determined by the bit depth.
212
+ #
213
+ # === Parameters
214
+ #
215
+ # * <tt>depth</tt> -- The bith depth of the integers.
216
+ #
217
+ def to_unsigned(depth)
218
+ raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
219
+ raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
220
+ case depth
221
+ when 8
222
+ return self.collect {|i| i + 128}
223
+ when 16
224
+ return self.collect {|i| i + 32768}
225
+ end
226
+ end
227
+
56
228
  end
@@ -1,13 +1,11 @@
1
- # Copyright 2010 Christoffer Lervag
2
-
3
1
  module DICOM
4
2
 
5
3
  # The Sequence class handles information related to Sequence elements.
6
4
  #
7
- class Sequence < SuperParent
5
+ class Sequence < Parent
8
6
 
9
- # Include the Elements mix-in module:
10
- include Elements
7
+ # Include the Elemental mix-in module:
8
+ include Elemental
11
9
 
12
10
  # Creates a Sequence instance.
13
11
  #
@@ -36,6 +34,7 @@ module DICOM
36
34
  # encapsulated_pixel_data = Sequence.new("7FE0,0010", :name => "Encapsulated Pixel Data", :parent => obj, :vr => "OW")
37
35
  #
38
36
  def initialize(tag, options={})
37
+ raise ArgumentError, "The supplied tag (#{tag}) is not valid. The tag must be a string of the form 'GGGG,EEEE'." unless tag.is_a?(String) && tag.tag?
39
38
  # Set common parent variables:
40
39
  initialize_parent
41
40
  # Set instance variables:
@@ -54,7 +53,7 @@ module DICOM
54
53
  @length = options[:length] || -1
55
54
  if options[:parent]
56
55
  @parent = options[:parent]
57
- @parent.add(self)
56
+ @parent.add(self, :no_follow => true)
58
57
  end
59
58
  end
60
59
 
@@ -1,5 +1,3 @@
1
- # Copyright 2009-2010 Christoffer Lervag
2
-
3
1
  module DICOM
4
2
 
5
3
  # The Stream class handles string operations (encoding to and decoding from binary strings).
@@ -14,6 +12,8 @@ module DICOM
14
12
  attr_accessor :index
15
13
  # The instance string.
16
14
  attr_accessor :string
15
+ # The endianness of the instance string.
16
+ attr_reader :str_endian
17
17
  # An array of warning/error messages that (may) have been accumulated.
18
18
  attr_reader :errors
19
19
  # A hash with vr as key and its corresponding pad byte as value.
@@ -71,23 +71,29 @@ module DICOM
71
71
  # * <tt>type</tt> -- String. The type (vr) of data to decode.
72
72
  #
73
73
  def decode(length, type)
74
+ raise ArgumentError, "Invalid argument length. Expected Fixnum, got #{length.class}" unless length.is_a?(Fixnum)
75
+ raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
74
76
  # Check if values are valid:
75
77
  if (@index + length) > @string.length
76
78
  # The index number is bigger then the length of the binary string.
77
79
  # We have reached the end and will return nil.
78
80
  value = nil
79
81
  else
80
- # Decode the binary string and return value:
81
- value = @string.slice(@index, length).unpack(vr_to_str(type))
82
- # If the result is an array of one element, return the element instead of the array.
83
- # If result is contained in a multi-element array, the original array is returned.
84
- if value.length == 1
85
- value = value[0]
86
- # If value is a string, strip away possible trailing whitespace:
87
- value = value.rstrip if value.is_a?(String)
82
+ if type == "AT"
83
+ value = decode_tag
84
+ else
85
+ # Decode the binary string and return value:
86
+ value = @string.slice(@index, length).unpack(vr_to_str(type))
87
+ # If the result is an array of one element, return the element instead of the array.
88
+ # If result is contained in a multi-element array, the original array is returned.
89
+ if value.length == 1
90
+ value = value[0]
91
+ # If value is a string, strip away possible trailing whitespace:
92
+ value = value.rstrip if value.is_a?(String)
93
+ end
94
+ # Update our position in the string:
95
+ skip(length)
88
96
  end
89
- # Update our position in the string:
90
- skip(length)
91
97
  end
92
98
  return value
93
99
  end
@@ -143,6 +149,7 @@ module DICOM
143
149
  # * <tt>type</tt> -- String. The type (vr) of data to encode.
144
150
  #
145
151
  def encode(value, type)
152
+ raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
146
153
  value = [value] unless value.is_a?(Array)
147
154
  return value.pack(vr_to_str(type))
148
155
  end
@@ -216,14 +223,18 @@ module DICOM
216
223
  # * <tt>vr</tt> -- String. The type of data to encode.
217
224
  #
218
225
  def encode_value(value, vr)
219
- # Make sure the value is in an array:
220
- value = [value] unless value.is_a?(Array)
221
- # Get the proper pack string:
222
- type = vr_to_str(vr)
223
- # Encode:
224
- bin = value.pack(type)
225
- # Add an empty byte if the resulting binary has an odd length:
226
- bin = bin + @pad_byte[vr] if bin.length[0] == 1
226
+ if vr == "AT"
227
+ bin = encode_tag(value)
228
+ else
229
+ # Make sure the value is in an array:
230
+ value = [value] unless value.is_a?(Array)
231
+ # Get the proper pack string:
232
+ type = vr_to_str(vr)
233
+ # Encode:
234
+ bin = value.pack(type)
235
+ # Add an empty byte if the resulting binary has an odd length:
236
+ bin = bin + @pad_byte[vr] if bin.length[0] == 1
237
+ end
227
238
  return bin
228
239
  end
229
240
 
@@ -398,7 +409,7 @@ module DICOM
398
409
  "OB" => @by, # Other byte string (1-byte integers)
399
410
  "OF" => @fs, # Other float string (4-byte floating point numbers)
400
411
  "OW" => @us, # Other word string (2-byte integers)
401
- "AT" => @hex, # Tag reference (4 bytes) NB: This may need to be revisited at some point...
412
+ "AT" => @hex, # Tag reference (4 bytes) NB: For tags the spesialized encode_tag/decode_tag methods are used instead of this lookup table.
402
413
  "UN" => @hex, # Unknown information (header element is not recognized from local database)
403
414
  "HEX" => @hex, # HEX
404
415
  # We have a number of VRs that are decoded as string:
@@ -462,8 +473,9 @@ module DICOM
462
473
  # Some of these depends on the endianness of the system and the String.
463
474
  #
464
475
  #--
465
- # FIXME: Apparently the Ruby pack/unpack methods lacks a format for signed short
466
- # and signed long in the network byte order. A solution needs to be found for this.
476
+ # Note: Surprisingly the Ruby pack/unpack methods lack a format for signed short
477
+ # and signed long in the network byte order. A hack has been implemented to to ensure
478
+ # correct behaviour in this case, but it is slower (~4 times slower than a normal pack/unpack).
467
479
  #
468
480
  def set_string_formats
469
481
  if @equal_endian
@@ -477,9 +489,9 @@ module DICOM
477
489
  else
478
490
  # Network byte order:
479
491
  @us = "n*"
480
- @ss = "n*" # Not correct (gives US)
492
+ @ss = CUSTOM_SS # Custom string for our redefined pack/unpack.
481
493
  @ul = "N*"
482
- @sl = "N*" # Not correct (gives UL)
494
+ @sl = CUSTOM_SL # Custom string for our redefined pack/unpack.
483
495
  @fs = "g*"
484
496
  @fd = "G*"
485
497
  end
@@ -0,0 +1,51 @@
1
+ module DICOM
2
+
3
+ class << self
4
+
5
+ #--
6
+ # Module attributes:
7
+ #++
8
+
9
+ # The ruby-dicom image processor to be used.
10
+ attr_accessor :image_processor
11
+ # The key representation for hashes, json, yaml.
12
+ attr_accessor :key_representation
13
+ # Source Application Entity Title (gets written to the DICOM header in files where it is undefined).
14
+ attr_accessor :source_app_title
15
+
16
+ #--
17
+ # Module methods:
18
+ #++
19
+
20
+ # Use tags as key. Example: "0010,0010"
21
+ #
22
+ def key_use_tags
23
+ @key_representation = :tag
24
+ end
25
+
26
+ # Use names as key. Example: "Patient's Name"
27
+ #
28
+ def key_use_names
29
+ @key_representation = :name
30
+ end
31
+
32
+ # Use method names as key. Example: :patients_name
33
+ #
34
+ def key_use_method_names
35
+ @key_representation = :name_as_method
36
+ end
37
+
38
+ end
39
+
40
+ #--
41
+ # Default variable settings:
42
+ #++
43
+
44
+ # The default image processor.
45
+ self.image_processor = :rmagick
46
+ # The default key representation.
47
+ self.key_representation = :name
48
+ # The default source application entity title.
49
+ self.source_app_title = "RUBY_DICOM"
50
+
51
+ end
@@ -0,0 +1,6 @@
1
+ module DICOM
2
+
3
+ # The ruby-dicom version string.
4
+ VERSION = "0.9"
5
+
6
+ end
metadata CHANGED
@@ -1,7 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dicom
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.8"
4
+ prerelease:
5
+ version: "0.9"
5
6
  platform: ruby
6
7
  authors:
7
8
  - Christoffer Lervag
@@ -9,10 +10,74 @@ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
12
 
12
- date: 2010-08-01 00:00:00 +02:00
13
- default_executable:
14
- dependencies: []
15
-
13
+ date: 2011-05-17 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.0
24
+ type: :development
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 2.5.0
35
+ type: :development
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: mocha
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.12
46
+ type: :development
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: narray
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: rmagick
61
+ prerelease: false
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ type: :development
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: mini_magick
72
+ prerelease: false
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 3.2.1
79
+ type: :development
80
+ version_requirements: *id006
16
81
  description: DICOM is a standard widely used throughout the world to store and transfer medical image data. This library enables efficient and powerful handling of DICOM in Ruby, to the benefit of any student or professional who would like to use their favorite language to process DICOM files and communicate across the network.
17
82
  email: chris.lervag@gmail.com
18
83
  executables: []
@@ -22,31 +87,34 @@ extensions: []
22
87
  extra_rdoc_files: []
23
88
 
24
89
  files:
25
- - lib/dicom.rb
26
- - lib/dicom/dictionary.rb
27
- - lib/dicom/constants.rb
28
- - lib/dicom/item.rb
29
90
  - lib/dicom/anonymizer.rb
91
+ - lib/dicom/constants.rb
92
+ - lib/dicom/d_client.rb
93
+ - lib/dicom/d_library.rb
30
94
  - lib/dicom/d_object.rb
31
- - lib/dicom/stream.rb
32
- - lib/dicom/sequence.rb
33
- - lib/dicom/super_item.rb
34
- - lib/dicom/link.rb
35
- - lib/dicom/file_handler.rb
36
- - lib/dicom/d_write.rb
95
+ - lib/dicom/d_read.rb
37
96
  - lib/dicom/d_server.rb
38
- - lib/dicom/super_parent.rb
97
+ - lib/dicom/d_write.rb
98
+ - lib/dicom/dictionary.rb
99
+ - lib/dicom/element.rb
100
+ - lib/dicom/elemental.rb
101
+ - lib/dicom/file_handler.rb
102
+ - lib/dicom/image_item.rb
103
+ - lib/dicom/image_processor.rb
104
+ - lib/dicom/image_processor_mini_magick.rb
105
+ - lib/dicom/image_processor_r_magick.rb
106
+ - lib/dicom/item.rb
107
+ - lib/dicom/link.rb
108
+ - lib/dicom/parent.rb
39
109
  - lib/dicom/ruby_extensions.rb
40
- - lib/dicom/d_read.rb
41
- - lib/dicom/d_client.rb
42
- - lib/dicom/elements.rb
43
- - lib/dicom/d_library.rb
44
- - lib/dicom/data_element.rb
45
- - CHANGELOG
46
- - README
110
+ - lib/dicom/sequence.rb
111
+ - lib/dicom/stream.rb
112
+ - lib/dicom/variables.rb
113
+ - lib/dicom/version.rb
114
+ - lib/dicom.rb
115
+ - CHANGELOG.rdoc
47
116
  - COPYING
48
- - init.rb
49
- has_rdoc: true
117
+ - README.rdoc
50
118
  homepage: http://dicom.rubyforge.org/
51
119
  licenses: []
52
120
 
@@ -56,21 +124,21 @@ rdoc_options: []
56
124
  require_paths:
57
125
  - lib
58
126
  required_ruby_version: !ruby/object:Gem::Requirement
127
+ none: false
59
128
  requirements:
60
129
  - - ">="
61
130
  - !ruby/object:Gem::Version
62
131
  version: 1.8.6
63
- version:
64
132
  required_rubygems_version: !ruby/object:Gem::Requirement
133
+ none: false
65
134
  requirements:
66
135
  - - ">="
67
136
  - !ruby/object:Gem::Version
68
- version: 1.3.4
69
- version:
137
+ version: 1.3.6
70
138
  requirements: []
71
139
 
72
140
  rubyforge_project: dicom
73
- rubygems_version: 1.3.5
141
+ rubygems_version: 1.8.2
74
142
  signing_key:
75
143
  specification_version: 3
76
144
  summary: Library for handling DICOM files and DICOM network communication.