dicom 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,230 +1,249 @@
1
- # encoding: UTF-8
2
-
3
- # This file contains extensions to the Ruby library which are used by Ruby DICOM.
4
-
5
- # Extension to the String class. These extensions are focused on processing/analysing Data Element tags.
6
- # A tag string (as used by the Ruby DICOM library) is 9 characters long and of the form "GGGG,EEEE"
7
- # (where G represents a group hexadecimal, and E represents an element hexadecimal).
8
- #
9
- class String
10
-
11
- # Renames the original unpack method.
12
- #
13
- alias __original_unpack__ unpack
14
-
15
- # Returns true for all values that LOOK like a DICOM name - they may not be valid.
16
- #
17
- def dicom_name?
18
- self==self.dicom_titleize
19
- end
20
-
21
- # Returns true for all strings that LOOK like a DICOM method name - they may not be valid.
22
- #
23
- def dicom_method?
24
- self == self.dicom_underscore
25
- end
26
-
27
- # Returns a proper DICOM method name string.
28
- #
29
- def dicom_methodize
30
- self.gsub(/^3/,'three_').gsub(/[#*?!]/,' ').gsub(', ',' ').gsub('&','and').gsub(' - ','_').gsub(' / ','_').gsub(/[\s\-\.\,\/\\]/,'_').gsub(/[\(\)\']/,'').gsub(/\_+/, '_').downcase
31
- end
32
-
33
- # Capitalizes all the words and replaces some characters in the string to make a nicer looking title.
34
- #
35
- def dicom_titleize
36
- self.dicom_underscore.gsub(/_/, " ").gsub(/\b('?[a-z])/) { $1.capitalize }
37
- end
38
-
39
- # Makes an underscored, lowercase form from the string expression.
40
- #
41
- def dicom_underscore
42
- word = self.dup
43
- word.tr!("-", "_")
44
- word.downcase!
45
- word
46
- end
47
-
48
- # Divides a string into a number of sub-strings of exactly equal length, and returns these in an array.
49
- # The length of self must be a multiple of parts, or an error will be raised.
50
- #
51
- def divide(parts)
52
- raise ArgumentError, "Expected an integer (Fixnum). Got #{parts.class}." unless parts.is_a?(Fixnum)
53
- raise ArgumentError, "Argument must be in the range <1 - self.length (#{self.length})>. Got #{parts}." if parts < 1 or parts > self.length
54
- 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
55
- if parts > 1
56
- sub_strings = Array.new
57
- sub_length = self.length/parts
58
- parts.times { sub_strings << self.slice!(0..(sub_length-1)) }
59
- return sub_strings
60
- else
61
- return [self]
62
- end
63
- end
64
-
65
- # Returns the element part of the tag string: The last 4 characters.
66
- #
67
- def element
68
- return self[5..8]
69
- end
70
-
71
- # Returns the group part of the tag string: The first 4 characters.
72
- #
73
- def group
74
- return self[0..3]
75
- end
76
-
77
- # Returns the "Group Length" ("GGGG,0000") tag which corresponds to the original tag/group string.
78
- # This string may either be a 4 character group string, or a 9 character custom tag.
79
- #
80
- def group_length
81
- if self.length == 4
82
- return self + ",0000"
83
- else
84
- return self.group + ",0000"
85
- end
86
- end
87
-
88
- # Checks if the string is a "Group Length" tag (its element part is "0000").
89
- # Returns true if it is and false if not.
90
- #
91
- def group_length?
92
- return (self.element == "0000" ? true : false)
93
- end
94
-
95
- # Checks if the string is a private tag (has an odd group number).
96
- # Returns true if it is, false if not.
97
- #
98
- def private?
99
- return ((self.upcase =~ /\A\h{3}[1,3,5,7,9,B,D,F],\h{4}\z/) == nil ? false : true)
100
- end
101
-
102
- # Checks if the string is a valid tag (as defined by Ruby DICOM: "GGGG,EEEE").
103
- # Returns true if it is a valid tag, false if not.
104
- #
105
- def tag?
106
- # Test that the string is composed of exactly 4 HEX characters, followed by a comma, then 4 more HEX characters:
107
- return ((self.upcase =~ /\A\h{4},\h{4}\z/) == nil ? false : true)
108
- end
109
-
110
- # Redefines the old unpack method, adding the ability to decode signed integers in big endian.
111
- #
112
- # === Parameters
113
- #
114
- # * <tt>string</tt> -- A template string which decides the decoding scheme to use.
115
- #
116
- def unpack(string)
117
- # Check for some custom unpack strings that we've invented:
118
- case string
119
- when "k*" # SS
120
- # Unpack BE US, repack LE US, then finally unpack LE SS:
121
- wrongly_unpacked = self.__original_unpack__("n*")
122
- repacked = wrongly_unpacked.__original_pack__("S*")
123
- correct = repacked.__original_unpack__("s*")
124
- when "r*" # SL
125
- # Unpack BE UL, repack LE UL, then finally unpack LE SL:
126
- wrongly_unpacked = self.__original_unpack__("N*")
127
- repacked = wrongly_unpacked.__original_pack__("I*")
128
- correct = repacked.__original_unpack__("l*")
129
- else
130
- # Call the original method for all other (normal) cases:
131
- self.__original_unpack__(string)
132
- end
133
- end
134
-
135
- end
136
-
137
-
138
- # Extensions to the Array class.
139
- # These methods deal with encoding Integer arrays as well as conversion between signed and unsigned integers.
140
- #
141
- class Array
142
-
143
- # Renames the original pack method.
144
- #
145
- alias __original_pack__ pack
146
-
147
- # Redefines the old pack method, adding the ability to encode signed integers in big endian
148
- # (which surprisingly has not been supported out of the box in Ruby).
149
- #
150
- # === Parameters
151
- #
152
- # * <tt>string</tt> -- A template string which decides the encoding scheme to use.
153
- #
154
- def pack(string)
155
- # FIXME: At some time in the future, when Ruby 1.9.3 can be set as required ruby version,
156
- # this custom pack (as well as unpack) method can be discarded, and the desired endian
157
- # encodings can probably be achieved with the new template strings introduced in 1.9.3.
158
- #
159
- # Check for some custom pack strings that we've invented:
160
- case string
161
- when "k*" # SS
162
- # Pack LE SS, re-unpack as LE US, then finally pack BE US:
163
- wrongly_packed = self.__original_pack__("s*")
164
- reunpacked = wrongly_packed.__original_unpack__("S*")
165
- correct = reunpacked.__original_pack__("n*")
166
- when "r*" # SL
167
- # Pack LE SL, re-unpack as LE UL, then finally pack BE UL:
168
- wrongly_packed = self.__original_pack__("l*")
169
- reunpacked = wrongly_packed.__original_unpack__("I*")
170
- correct = reunpacked.__original_pack__("N*")
171
- else
172
- # Call the original method for all other (normal) cases:
173
- self.__original_pack__(string)
174
- end
175
- end
176
-
177
- # Packs an array of (unsigned) integers to a binary string (blob).
178
- #
179
- # === Parameters
180
- #
181
- # * <tt>depth</tt> -- The bith depth to be used when encoding the unsigned integers.
182
- #
183
- def to_blob(depth)
184
- raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
185
- raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
186
- case depth
187
- when 8
188
- return self.pack("C*") # Unsigned char
189
- when 16
190
- return self.pack("S*") # Unsigned short, native byte order
191
- end
192
- end
193
-
194
- # Shifts the integer values of the array to make a signed data set.
195
- # The size of the shift is determined by the bit depth.
196
- #
197
- # === Parameters
198
- #
199
- # * <tt>depth</tt> -- The bith depth of the integers.
200
- #
201
- def to_signed(depth)
202
- raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
203
- raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
204
- case depth
205
- when 8
206
- return self.collect {|i| i - 128}
207
- when 16
208
- return self.collect {|i| i - 32768}
209
- end
210
- end
211
-
212
- # Shifts the integer values of the array to make an unsigned data set.
213
- # The size of the shift is determined by the bit depth.
214
- #
215
- # === Parameters
216
- #
217
- # * <tt>depth</tt> -- The bith depth of the integers.
218
- #
219
- def to_unsigned(depth)
220
- raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
221
- raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
222
- case depth
223
- when 8
224
- return self.collect {|i| i + 128}
225
- when 16
226
- return self.collect {|i| i + 32768}
227
- end
228
- end
229
-
1
+ # encoding: UTF-8
2
+
3
+ # This file contains extensions to the Ruby library which are used by Ruby DICOM.
4
+
5
+ # Extension to the String class. These mainly facilitate the processing and analysis of element tags.
6
+ # A tag string (as used by the ruby-dicom library) is 9 characters long and of the form 'GGGG,EEEE'
7
+ # (where G represents a group hexadecimal, and E represents an element hexadecimal).
8
+ #
9
+ class String
10
+
11
+ # Renames the original unpack method.
12
+ #
13
+ alias __original_unpack__ unpack
14
+
15
+ # Checks if a string value LOOKS like a DICOM name - it may still not be valid one.
16
+ #
17
+ # @return [Boolean] true if a string looks like a DICOM name, and false if not
18
+ #
19
+ def dicom_name?
20
+ self == self.dicom_titleize
21
+ end
22
+
23
+ # Checks if a string value LOOKS like a DICOM method name - it may still not be valid one.
24
+ #
25
+ # @return [Boolean] true if a string looks like a DICOM method name, and false if not
26
+ #
27
+ def dicom_method?
28
+ self == self.dicom_underscore
29
+ end
30
+
31
+ # Capitalizes all the words in the string and replaces some characters to make a nicer looking title.
32
+ #
33
+ # @return [String] a formatted, capitalized string
34
+ #
35
+ def dicom_titleize
36
+ self.dicom_underscore.gsub(/_/, ' ').gsub(/\b('?[a-z])/) { $1.capitalize }
37
+ end
38
+
39
+ # Makes an underscored, lowercased version of the string.
40
+ #
41
+ # @return [String] an underscored, lower case string
42
+ #
43
+ def dicom_underscore
44
+ word = self.dup
45
+ word.tr!('-', '_')
46
+ word.downcase!
47
+ word
48
+ end
49
+
50
+ # Divides a string into a number of sub-strings of exactly equal length.
51
+ #
52
+ # @note The length of self must be a multiple of parts, or an exception will be raised.
53
+ # @param [Integer] parts the number of sub-strings to create
54
+ # @return [Array<String>] the divided sub-strings
55
+ #
56
+ def divide(parts)
57
+ raise ArgumentError, "Expected an integer (Fixnum). Got #{parts.class}." unless parts.is_a?(Fixnum)
58
+ raise ArgumentError, "Argument must be in the range <1 - self.length (#{self.length})>. Got #{parts}." if parts < 1 or parts > self.length
59
+ 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
60
+ if parts > 1
61
+ sub_strings = Array.new
62
+ sub_length = self.length/parts
63
+ parts.times { sub_strings << self.slice!(0..(sub_length-1)) }
64
+ return sub_strings
65
+ else
66
+ return [self]
67
+ end
68
+ end
69
+
70
+ # Extracts the element part of the tag string: The last 4 characters.
71
+ #
72
+ # @return [String] the element part of the tag
73
+ #
74
+ def element
75
+ return self[5..8]
76
+ end
77
+
78
+ # Returns the group part of the tag string: The first 4 characters.
79
+ #
80
+ # @return [String] the group part of the tag
81
+ #
82
+ def group
83
+ return self[0..3]
84
+ end
85
+
86
+ # Returns the "Group Length" ('GGGG,0000') tag which corresponds to the original tag/group string.
87
+ # The original string may either be a 4 character group string, or a 9 character custom tag.
88
+ #
89
+ # @return [String] a group length tag
90
+ #
91
+ def group_length
92
+ if self.length == 4
93
+ return self + ',0000'
94
+ else
95
+ return self.group + ',0000'
96
+ end
97
+ end
98
+
99
+ # Checks if the string is a "Group Length" tag (its element part is '0000').
100
+ #
101
+ # @return [Boolean] true if it is a group length tag, and false if not
102
+ #
103
+ def group_length?
104
+ return (self.element == '0000' ? true : false)
105
+ end
106
+
107
+ # Checks if the string is a private tag (has an odd group number).
108
+ #
109
+ # @return [Boolean] true if it is a private tag, and false if not
110
+ #
111
+ def private?
112
+ return ((self.upcase =~ /\A\h{3}[1,3,5,7,9,B,D,F],\h{4}\z/) == nil ? false : true)
113
+ end
114
+
115
+ # Checks if the string is a valid tag (as defined by ruby-dicom: 'GGGG,EEEE').
116
+ #
117
+ # @return [Boolean] true if it is a valid tag, and false if not
118
+ #
119
+ def tag?
120
+ # Test that the string is composed of exactly 4 HEX characters, followed by a comma, then 4 more HEX characters:
121
+ return ((self.upcase =~ /\A\h{4},\h{4}\z/) == nil ? false : true)
122
+ end
123
+
124
+ # Converts the string to a proper DICOM element method name symbol.
125
+ #
126
+ # @return [Symbol] a DICOM element method name
127
+ #
128
+ def to_element_method
129
+ self.gsub(/^3/,'three_').gsub(/[#*?!]/,' ').gsub(', ',' ').gsub('&','and').gsub(' - ','_').gsub(' / ','_').gsub(/[\s\-\.\,\/\\]/,'_').gsub(/[\(\)\']/,'').gsub(/\_+/, '_').downcase.to_sym
130
+ end
131
+
132
+ # Redefines the core library unpack method, adding
133
+ # the ability to decode signed integers in big endian.
134
+ #
135
+ # @param [String] format a format string which decides the decoding scheme to use
136
+ # @return [Array<String, Integer, Float>] the decoded values
137
+ #
138
+ def unpack(format)
139
+ # Check for some custom unpack strings that we've invented:
140
+ case format
141
+ when "k*" # SS
142
+ # Unpack BE US, repack LE US, then finally unpack LE SS:
143
+ wrongly_unpacked = self.__original_unpack__('n*')
144
+ repacked = wrongly_unpacked.__original_pack__('S*')
145
+ correct = repacked.__original_unpack__('s*')
146
+ when 'r*' # SL
147
+ # Unpack BE UL, repack LE UL, then finally unpack LE SL:
148
+ wrongly_unpacked = self.__original_unpack__('N*')
149
+ repacked = wrongly_unpacked.__original_pack__('I*')
150
+ correct = repacked.__original_unpack__('l*')
151
+ else
152
+ # Call the original method for all other (normal) cases:
153
+ self.__original_unpack__(format)
154
+ end
155
+ end
156
+
157
+ end
158
+
159
+
160
+ # Extensions to the Array class.
161
+ # These mainly deal with encoding integer arrays as well as conversion between
162
+ # signed and unsigned integers.
163
+ #
164
+ class Array
165
+
166
+ # Renames the original pack method.
167
+ #
168
+ alias __original_pack__ pack
169
+
170
+ # Redefines the old pack method, adding the ability to encode signed integers in big endian
171
+ # (which surprisingly has not been supported out of the box in Ruby until version 1.9.3).
172
+ #
173
+ # @param [String] format a format string which decides the encoding scheme to use
174
+ # @return [String] the encoded binary string
175
+ #
176
+ def pack(format)
177
+ # FIXME: At some time in the future, when Ruby 1.9.3 can be set as required ruby version,
178
+ # this custom pack (as well as unpack) method can be discarded, and the desired endian
179
+ # encodings can probably be achieved with the new template strings introduced in 1.9.3.
180
+ #
181
+ # Check for some custom pack strings that we've invented:
182
+ case format
183
+ when 'k*' # SS
184
+ # Pack LE SS, re-unpack as LE US, then finally pack BE US:
185
+ wrongly_packed = self.__original_pack__('s*')
186
+ reunpacked = wrongly_packed.__original_unpack__('S*')
187
+ correct = reunpacked.__original_pack__('n*')
188
+ when 'r*' # SL
189
+ # Pack LE SL, re-unpack as LE UL, then finally pack BE UL:
190
+ wrongly_packed = self.__original_pack__('l*')
191
+ reunpacked = wrongly_packed.__original_unpack__('I*')
192
+ correct = reunpacked.__original_pack__('N*')
193
+ else
194
+ # Call the original method for all other (normal) cases:
195
+ self.__original_pack__(format)
196
+ end
197
+ end
198
+
199
+ # Packs an array of (unsigned) integers to a binary string (blob).
200
+ #
201
+ # @param [Integer] depth the bit depth to be used when encoding the unsigned integers
202
+ # @return [String] an encoded binary string
203
+ #
204
+ def to_blob(depth)
205
+ raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
206
+ raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
207
+ case depth
208
+ when 8
209
+ return self.pack('C*') # Unsigned char
210
+ when 16
211
+ return self.pack('S*') # Unsigned short, native byte order
212
+ end
213
+ end
214
+
215
+ # Shifts the integer values of the array to make a signed data set.
216
+ # The size of the shift is determined by the given bit depth.
217
+ #
218
+ # @param [Integer] depth the bit depth of the integers
219
+ # @return [Array<Integer>] an array of signed integers
220
+ #
221
+ def to_signed(depth)
222
+ raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
223
+ raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
224
+ case depth
225
+ when 8
226
+ return self.collect {|i| i - 128}
227
+ when 16
228
+ return self.collect {|i| i - 32768}
229
+ end
230
+ end
231
+
232
+ # Shifts the integer values of the array to make an unsigned data set.
233
+ # The size of the shift is determined by the given bit depth.
234
+ #
235
+ # @param [Integer] depth the bit depth of the integers
236
+ # @return [Array<Integer>] an array of unsigned integers
237
+ #
238
+ def to_unsigned(depth)
239
+ raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
240
+ raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
241
+ case depth
242
+ when 8
243
+ return self.collect {|i| i + 128}
244
+ when 16
245
+ return self.collect {|i| i + 32768}
246
+ end
247
+ end
248
+
230
249
  end