dicom 0.9.3 → 0.9.4

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