dicom 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +27 -27
- data/README.md +2 -1
- data/dicom.gemspec +8 -8
- data/lib/dicom/anonymizer.rb +3 -3
- data/lib/dicom/d_object.rb +2 -2
- data/lib/dicom/element.rb +278 -278
- data/lib/dicom/elemental.rb +121 -121
- data/lib/dicom/general/constants.rb +4 -1
- data/lib/dicom/general/version.rb +1 -1
- data/lib/dicom/image_item.rb +6 -6
- data/lib/dicom/item.rb +122 -122
- data/lib/dicom/link.rb +6 -6
- data/lib/dicom/sequence.rb +98 -98
- data/lib/dicom/stream.rb +461 -461
- metadata +23 -24
data/lib/dicom/link.rb
CHANGED
@@ -26,8 +26,8 @@ module DICOM
|
|
26
26
|
# * <tt>:ae</tt> -- String. The name of the client (application entity).
|
27
27
|
# * <tt>:file_handler</tt> -- A customized FileHandler class to use instead of the default FileHandler.
|
28
28
|
# * <tt>:host_ae</tt> -- String. The name of the server (application entity).
|
29
|
-
# * <tt>:max_package_size</tt> --
|
30
|
-
# * <tt>:timeout</tt> --
|
29
|
+
# * <tt>:max_package_size</tt> -- Integer. The maximum allowed size of network packages (in bytes).
|
30
|
+
# * <tt>:timeout</tt> -- Integer. The maximum period to wait for an answer before aborting the communication.
|
31
31
|
#
|
32
32
|
def initialize(options={})
|
33
33
|
require 'socket'
|
@@ -1075,7 +1075,7 @@ module DICOM
|
|
1075
1075
|
# === Parameters
|
1076
1076
|
#
|
1077
1077
|
# * <tt>adress</tt> -- String. The adress (IP) of the remote node.
|
1078
|
-
# * <tt>port</tt> --
|
1078
|
+
# * <tt>port</tt> -- Integer. The network port to be used in the network communication.
|
1079
1079
|
#
|
1080
1080
|
def start_session(adress, port)
|
1081
1081
|
@session = TCPSocket.new(adress, port)
|
@@ -1303,7 +1303,7 @@ module DICOM
|
|
1303
1303
|
#
|
1304
1304
|
# === Parameters
|
1305
1305
|
#
|
1306
|
-
# * <tt>result</tt> --
|
1306
|
+
# * <tt>result</tt> -- Integer. The result code from an association response.
|
1307
1307
|
#
|
1308
1308
|
def process_result(result)
|
1309
1309
|
unless result == 0
|
@@ -1350,7 +1350,7 @@ module DICOM
|
|
1350
1350
|
#
|
1351
1351
|
# === Parameters
|
1352
1352
|
#
|
1353
|
-
# * <tt>status</tt> --
|
1353
|
+
# * <tt>status</tt> -- Integer. A status code from a command fragment.
|
1354
1354
|
#
|
1355
1355
|
def process_status(status)
|
1356
1356
|
case status
|
@@ -1397,7 +1397,7 @@ module DICOM
|
|
1397
1397
|
#
|
1398
1398
|
# === Parameters
|
1399
1399
|
#
|
1400
|
-
# * <tt>min_length</tt> --
|
1400
|
+
# * <tt>min_length</tt> -- Integer. The minimum possible length of a valid incoming transmission.
|
1401
1401
|
#
|
1402
1402
|
def receive_transmission(min_length=0)
|
1403
1403
|
data = receive_transmission_data
|
data/lib/dicom/sequence.rb
CHANGED
@@ -1,98 +1,98 @@
|
|
1
|
-
module DICOM
|
2
|
-
|
3
|
-
# The Sequence class handles information related to Sequence elements.
|
4
|
-
#
|
5
|
-
class Sequence < Parent
|
6
|
-
|
7
|
-
include Elemental
|
8
|
-
include ElementalParent
|
9
|
-
|
10
|
-
# Creates a Sequence instance.
|
11
|
-
#
|
12
|
-
# @note Private sequences are named as 'Private'.
|
13
|
-
# @note Non-private sequences that are not found in the dictionary are named as 'Unknown'.
|
14
|
-
#
|
15
|
-
# @param [String] tag a ruby-dicom type element tag string
|
16
|
-
# @param [Hash] options the options to use for creating the sequence
|
17
|
-
# @option options [Integer] :length the sequence length, which refers to the length of the encoded string of children of this sequence
|
18
|
-
# @option options [Integer] :name the name of the sequence may be specified upon creation (if it is not, the name is retrieved from the dictionary)
|
19
|
-
# @option options [Integer] :parent an Item or DObject instance which the sequence instance shall belong to
|
20
|
-
# @option options [Integer] :vr the value representation of the Sequence may be specified upon creation (if it is not, a default vr is chosen)
|
21
|
-
#
|
22
|
-
# @example Create a new Sequence and connect it to a DObject instance
|
23
|
-
# structure_set_roi = Sequence.new('3006,0020', :parent => dcm)
|
24
|
-
# @example Create an "Encapsulated Pixel Data" Sequence
|
25
|
-
# encapsulated_pixel_data = Sequence.new('7FE0,0010', :name => 'Encapsulated Pixel Data', :parent => dcm, :vr => 'OW')
|
26
|
-
#
|
27
|
-
def initialize(tag, options={})
|
28
|
-
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?
|
29
|
-
# Set common parent variables:
|
30
|
-
initialize_parent
|
31
|
-
# Set instance variables:
|
32
|
-
@tag = tag.upcase
|
33
|
-
@value = nil
|
34
|
-
@bin = nil
|
35
|
-
# We may beed to retrieve name and vr from the library:
|
36
|
-
if options[:name] and options[:vr]
|
37
|
-
@name = options[:name]
|
38
|
-
@vr = options[:vr]
|
39
|
-
else
|
40
|
-
name, vr = LIBRARY.name_and_vr(tag)
|
41
|
-
@name = options[:name] || name
|
42
|
-
@vr = options[:vr] || 'SQ'
|
43
|
-
end
|
44
|
-
@length = options[:length] || -1
|
45
|
-
if options[:parent]
|
46
|
-
@parent = options[:parent]
|
47
|
-
@parent.add(self, :no_follow => true)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Checks for equality.
|
52
|
-
#
|
53
|
-
# Other and self are considered equivalent if they are
|
54
|
-
# of compatible types and their attributes are equivalent.
|
55
|
-
#
|
56
|
-
# @param other an object to be compared with self.
|
57
|
-
# @return [Boolean] true if self and other are considered equivalent
|
58
|
-
#
|
59
|
-
def ==(other)
|
60
|
-
if other.respond_to?(:to_sequence)
|
61
|
-
other.send(:state) == state
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
alias_method :eql?, :==
|
66
|
-
|
67
|
-
# Computes a hash code for this object.
|
68
|
-
#
|
69
|
-
# @note Two objects with the same attributes will have the same hash code.
|
70
|
-
#
|
71
|
-
# @return [
|
72
|
-
#
|
73
|
-
def hash
|
74
|
-
state.hash
|
75
|
-
end
|
76
|
-
|
77
|
-
# Returns self.
|
78
|
-
#
|
79
|
-
# @return [Sequence] self
|
80
|
-
#
|
81
|
-
def to_sequence
|
82
|
-
self
|
83
|
-
end
|
84
|
-
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
|
89
|
-
# Collects the attributes of this instance.
|
90
|
-
#
|
91
|
-
# @return [Array<String, Item>] an array of attributes
|
92
|
-
#
|
93
|
-
def state
|
94
|
-
[@tag, @vr, @tags]
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
end
|
1
|
+
module DICOM
|
2
|
+
|
3
|
+
# The Sequence class handles information related to Sequence elements.
|
4
|
+
#
|
5
|
+
class Sequence < Parent
|
6
|
+
|
7
|
+
include Elemental
|
8
|
+
include ElementalParent
|
9
|
+
|
10
|
+
# Creates a Sequence instance.
|
11
|
+
#
|
12
|
+
# @note Private sequences are named as 'Private'.
|
13
|
+
# @note Non-private sequences that are not found in the dictionary are named as 'Unknown'.
|
14
|
+
#
|
15
|
+
# @param [String] tag a ruby-dicom type element tag string
|
16
|
+
# @param [Hash] options the options to use for creating the sequence
|
17
|
+
# @option options [Integer] :length the sequence length, which refers to the length of the encoded string of children of this sequence
|
18
|
+
# @option options [Integer] :name the name of the sequence may be specified upon creation (if it is not, the name is retrieved from the dictionary)
|
19
|
+
# @option options [Integer] :parent an Item or DObject instance which the sequence instance shall belong to
|
20
|
+
# @option options [Integer] :vr the value representation of the Sequence may be specified upon creation (if it is not, a default vr is chosen)
|
21
|
+
#
|
22
|
+
# @example Create a new Sequence and connect it to a DObject instance
|
23
|
+
# structure_set_roi = Sequence.new('3006,0020', :parent => dcm)
|
24
|
+
# @example Create an "Encapsulated Pixel Data" Sequence
|
25
|
+
# encapsulated_pixel_data = Sequence.new('7FE0,0010', :name => 'Encapsulated Pixel Data', :parent => dcm, :vr => 'OW')
|
26
|
+
#
|
27
|
+
def initialize(tag, options={})
|
28
|
+
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?
|
29
|
+
# Set common parent variables:
|
30
|
+
initialize_parent
|
31
|
+
# Set instance variables:
|
32
|
+
@tag = tag.upcase
|
33
|
+
@value = nil
|
34
|
+
@bin = nil
|
35
|
+
# We may beed to retrieve name and vr from the library:
|
36
|
+
if options[:name] and options[:vr]
|
37
|
+
@name = options[:name]
|
38
|
+
@vr = options[:vr]
|
39
|
+
else
|
40
|
+
name, vr = LIBRARY.name_and_vr(tag)
|
41
|
+
@name = options[:name] || name
|
42
|
+
@vr = options[:vr] || 'SQ'
|
43
|
+
end
|
44
|
+
@length = options[:length] || -1
|
45
|
+
if options[:parent]
|
46
|
+
@parent = options[:parent]
|
47
|
+
@parent.add(self, :no_follow => true)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Checks for equality.
|
52
|
+
#
|
53
|
+
# Other and self are considered equivalent if they are
|
54
|
+
# of compatible types and their attributes are equivalent.
|
55
|
+
#
|
56
|
+
# @param other an object to be compared with self.
|
57
|
+
# @return [Boolean] true if self and other are considered equivalent
|
58
|
+
#
|
59
|
+
def ==(other)
|
60
|
+
if other.respond_to?(:to_sequence)
|
61
|
+
other.send(:state) == state
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
alias_method :eql?, :==
|
66
|
+
|
67
|
+
# Computes a hash code for this object.
|
68
|
+
#
|
69
|
+
# @note Two objects with the same attributes will have the same hash code.
|
70
|
+
#
|
71
|
+
# @return [Integer] the object's hash code
|
72
|
+
#
|
73
|
+
def hash
|
74
|
+
state.hash
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns self.
|
78
|
+
#
|
79
|
+
# @return [Sequence] self
|
80
|
+
#
|
81
|
+
def to_sequence
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
|
89
|
+
# Collects the attributes of this instance.
|
90
|
+
#
|
91
|
+
# @return [Array<String, Item>] an array of attributes
|
92
|
+
#
|
93
|
+
def state
|
94
|
+
[@tag, @vr, @tags]
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
data/lib/dicom/stream.rb
CHANGED
@@ -1,461 +1,461 @@
|
|
1
|
-
module DICOM
|
2
|
-
|
3
|
-
# The Stream class handles string operations (encoding to and decoding from binary strings).
|
4
|
-
# It is used by the various classes of ruby-dicom for tasks such as reading and writing
|
5
|
-
# from/to files or network packets.
|
6
|
-
#
|
7
|
-
# @note In practice, this class is for internal library use. It is typically not accessed
|
8
|
-
# by the user, and can thus be considered a 'private' class.
|
9
|
-
#
|
10
|
-
class Stream
|
11
|
-
|
12
|
-
# A boolean which reports the relationship between the endianness of the system and the instance string.
|
13
|
-
attr_reader :equal_endian
|
14
|
-
# Our current position in the instance string (used only for decoding).
|
15
|
-
attr_accessor :index
|
16
|
-
# The instance string.
|
17
|
-
attr_accessor :string
|
18
|
-
# The endianness of the instance string.
|
19
|
-
attr_reader :str_endian
|
20
|
-
# An array of warning/error messages that (may) have been accumulated.
|
21
|
-
attr_reader :errors
|
22
|
-
# A hash with vr as key and its corresponding pad byte as value.
|
23
|
-
attr_reader :pad_byte
|
24
|
-
|
25
|
-
# Creates a Stream instance.
|
26
|
-
#
|
27
|
-
# @param [String, NilClass] binary a binary string (or nil, if creating an empty instance)
|
28
|
-
# @param [Boolean] string_endian the endianness of the instance string (true for big endian, false for small endian)
|
29
|
-
# @param [Hash] options the options to use for creating the instance
|
30
|
-
# @option options [Integer] :index a position (offset) in the instance string where reading will start
|
31
|
-
#
|
32
|
-
def initialize(binary, string_endian, options={})
|
33
|
-
@string = binary || ''
|
34
|
-
@index = options[:index] || 0
|
35
|
-
@errors = Array.new
|
36
|
-
self.endian = string_endian
|
37
|
-
end
|
38
|
-
|
39
|
-
# Prepends a pre-encoded string to the instance string (inserts at the beginning).
|
40
|
-
#
|
41
|
-
# @param [String] binary a binary string
|
42
|
-
#
|
43
|
-
def add_first(binary)
|
44
|
-
@string = "#{binary}#{@string}" if binary
|
45
|
-
end
|
46
|
-
|
47
|
-
# Appends a pre-encoded string to the instance string (inserts at the end).
|
48
|
-
#
|
49
|
-
# @param [String] binary a binary string
|
50
|
-
#
|
51
|
-
def add_last(binary)
|
52
|
-
@string = "#{@string}#{binary}" if binary
|
53
|
-
end
|
54
|
-
|
55
|
-
# Decodes a section of the instance string.
|
56
|
-
# The instance index is offset in accordance with the length read.
|
57
|
-
#
|
58
|
-
# @note If multiple numbers are decoded, these are returned in an array.
|
59
|
-
# @param [Integer] length the string length to be decoded
|
60
|
-
# @param [String] type the type (vr) of data to decode
|
61
|
-
# @return [String, Integer, Float, Array] the formatted (decoded) data
|
62
|
-
#
|
63
|
-
def decode(length, type)
|
64
|
-
raise ArgumentError, "Invalid argument length. Expected
|
65
|
-
raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
|
66
|
-
value = nil
|
67
|
-
if (@index + length) <= @string.length
|
68
|
-
# There are sufficient bytes remaining to extract the value:
|
69
|
-
if type == 'AT'
|
70
|
-
# We need to guard ourselves against the case where a string contains an invalid 'AT' value:
|
71
|
-
if length == 4
|
72
|
-
value = decode_tag
|
73
|
-
else
|
74
|
-
# Invalid. Just return nil.
|
75
|
-
skip(length)
|
76
|
-
end
|
77
|
-
else
|
78
|
-
# Decode the binary string and return value:
|
79
|
-
value = @string.slice(@index, length).unpack(vr_to_str(type))
|
80
|
-
# If the result is an array of one element, return the element instead of the array.
|
81
|
-
# If result is contained in a multi-element array, the original array is returned.
|
82
|
-
if value.length == 1
|
83
|
-
value = value[0]
|
84
|
-
# If value is a string, strip away possible trailing whitespace:
|
85
|
-
value = value.rstrip if value.is_a?(String)
|
86
|
-
end
|
87
|
-
# Update our position in the string:
|
88
|
-
skip(length)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
value
|
92
|
-
end
|
93
|
-
|
94
|
-
# Decodes the entire instance string (typically used for decoding image data).
|
95
|
-
#
|
96
|
-
# @note If multiple numbers are decoded, these are returned in an array.
|
97
|
-
# @param [String] type the type (vr) of data to decode
|
98
|
-
# @return [String, Integer, Float, Array] the formatted (decoded) data
|
99
|
-
#
|
100
|
-
def decode_all(type)
|
101
|
-
length = @string.length
|
102
|
-
value = @string.slice(@index, length).unpack(vr_to_str(type))
|
103
|
-
skip(length)
|
104
|
-
return value
|
105
|
-
end
|
106
|
-
|
107
|
-
# Decodes 4 bytes of the instance string and formats it as a ruby-dicom tag string.
|
108
|
-
#
|
109
|
-
# @return [String, NilClass] a formatted tag string ('GGGG,EEEE'), or nil (e.g. if at end of string)
|
110
|
-
#
|
111
|
-
def decode_tag
|
112
|
-
length = 4
|
113
|
-
tag = nil
|
114
|
-
if (@index + length) <= @string.length
|
115
|
-
# There are sufficient bytes remaining to extract a full tag:
|
116
|
-
str = @string.slice(@index, length).unpack(@hex)[0].upcase
|
117
|
-
if @equal_endian
|
118
|
-
tag = "#{str[2..3]}#{str[0..1]},#{str[6..7]}#{str[4..5]}"
|
119
|
-
else
|
120
|
-
tag = "#{str[0..3]},#{str[4..7]}"
|
121
|
-
end
|
122
|
-
# Update our position in the string:
|
123
|
-
skip(length)
|
124
|
-
end
|
125
|
-
tag
|
126
|
-
end
|
127
|
-
|
128
|
-
# Encodes a given value to a binary string.
|
129
|
-
#
|
130
|
-
# @param [String, Integer, Float, Array] value a formatted value (String,
|
131
|
-
# @param [String] type the type (vr) of data to encode
|
132
|
-
# @return [String] an encoded binary string
|
133
|
-
#
|
134
|
-
def encode(value, type)
|
135
|
-
raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
|
136
|
-
value = [value] unless value.is_a?(Array)
|
137
|
-
return value.pack(vr_to_str(type))
|
138
|
-
end
|
139
|
-
|
140
|
-
# Encodes a value to a binary string and prepends it to the instance string.
|
141
|
-
#
|
142
|
-
# @param [String, Integer, Float, Array] value a formatted value (String,
|
143
|
-
# @param [String] type the type (vr) of data to encode
|
144
|
-
#
|
145
|
-
def encode_first(value, type)
|
146
|
-
value = [value] unless value.is_a?(Array)
|
147
|
-
@string = "#{value.pack(vr_to_str(type))}#{@string}"
|
148
|
-
end
|
149
|
-
|
150
|
-
# Encodes a value to a binary string and appends it to the instance string.
|
151
|
-
#
|
152
|
-
# @param [String, Integer, Float, Array] value a formatted value (String,
|
153
|
-
# @param [String] type the type (vr) of data to encode
|
154
|
-
#
|
155
|
-
def encode_last(value, type)
|
156
|
-
value = [value] unless value.is_a?(Array)
|
157
|
-
@string = "#{@string}#{value.pack(vr_to_str(type))}"
|
158
|
-
end
|
159
|
-
|
160
|
-
# Appends a string with trailling spaces to achieve a target length, and encodes it to a binary string.
|
161
|
-
#
|
162
|
-
# @param [String] string a string to be padded
|
163
|
-
# @param [Integer] target_length the target length of the string
|
164
|
-
# @return [String] an encoded binary string
|
165
|
-
#
|
166
|
-
def encode_string_with_trailing_spaces(string, target_length)
|
167
|
-
length = string.length
|
168
|
-
if length < target_length
|
169
|
-
return "#{[string].pack(@str)}#{['20'*(target_length-length)].pack(@hex)}"
|
170
|
-
elsif length == target_length
|
171
|
-
return [string].pack(@str)
|
172
|
-
else
|
173
|
-
raise "The specified string is longer than the allowed maximum length (String: #{string}, Target length: #{target_length})."
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# Encodes a tag from the ruby-dicom format ('GGGG,EEEE') to a proper binary string.
|
178
|
-
#
|
179
|
-
# @param [String] tag a ruby-dicom type tag string
|
180
|
-
# @return [String] an encoded binary string
|
181
|
-
#
|
182
|
-
def encode_tag(tag)
|
183
|
-
[
|
184
|
-
@equal_endian ? "#{tag[2..3]}#{tag[0..1]}#{tag[7..8]}#{tag[5..6]}" : "#{tag[0..3]}#{tag[5..8]}"
|
185
|
-
].pack(@hex)
|
186
|
-
end
|
187
|
-
|
188
|
-
# Encodes a value, and if the the resulting binary string has an
|
189
|
-
# odd length, appends a proper padding byte to make it even length.
|
190
|
-
#
|
191
|
-
# @param [String, Integer, Float, Array] value a formatted value (String,
|
192
|
-
# @param [String] vr the value representation of data to encode
|
193
|
-
# @return [String] the encoded binary string
|
194
|
-
#
|
195
|
-
def encode_value(value, vr)
|
196
|
-
if vr == 'AT'
|
197
|
-
bin = encode_tag(value)
|
198
|
-
else
|
199
|
-
# Make sure the value is in an array:
|
200
|
-
value = [value] unless value.is_a?(Array)
|
201
|
-
# Get the proper pack string:
|
202
|
-
type = vr_to_str(vr)
|
203
|
-
# Encode:
|
204
|
-
bin = value.pack(type)
|
205
|
-
# Add an empty byte if the resulting binary has an odd length:
|
206
|
-
bin = "#{bin}#{@pad_byte[vr]}" if bin.length.odd?
|
207
|
-
end
|
208
|
-
return bin
|
209
|
-
end
|
210
|
-
|
211
|
-
# Sets the endianness of the instance string. The relationship between the string
|
212
|
-
# endianness and the system endianness determines which encoding/decoding flags to use.
|
213
|
-
#
|
214
|
-
# @param [Boolean] string_endian the endianness of the instance string (true for big endian, false for small endian)
|
215
|
-
#
|
216
|
-
def endian=(string_endian)
|
217
|
-
@str_endian = string_endian
|
218
|
-
configure_endian
|
219
|
-
set_pad_byte
|
220
|
-
set_string_formats
|
221
|
-
set_format_hash
|
222
|
-
end
|
223
|
-
|
224
|
-
# Extracts the entire instance string, or optionally,
|
225
|
-
# just the first part of it if a length is specified.
|
226
|
-
#
|
227
|
-
# @note The exported string is removed from the instance string.
|
228
|
-
# @param [Integer] length the length of the string to cut out (if nil, the entire string is exported)
|
229
|
-
# @return [String] the instance string (or part of it)
|
230
|
-
#
|
231
|
-
def export(length=nil)
|
232
|
-
if length
|
233
|
-
string = @string.slice!(0, length)
|
234
|
-
else
|
235
|
-
string = @string
|
236
|
-
reset
|
237
|
-
end
|
238
|
-
return string
|
239
|
-
end
|
240
|
-
|
241
|
-
# Extracts and returns a binary string of the given length, starting at the index position.
|
242
|
-
# The instance index is then offset in accordance with the length read.
|
243
|
-
#
|
244
|
-
# @param [Integer] length the length of the string to be extracted
|
245
|
-
# @return [String] a part of the instance string
|
246
|
-
#
|
247
|
-
def extract(length)
|
248
|
-
str = @string.slice(@index, length)
|
249
|
-
skip(length)
|
250
|
-
return str
|
251
|
-
end
|
252
|
-
|
253
|
-
# Gives the length of the instance string.
|
254
|
-
#
|
255
|
-
# @return [Integer] the instance string's length
|
256
|
-
#
|
257
|
-
def length
|
258
|
-
return @string.length
|
259
|
-
end
|
260
|
-
|
261
|
-
# Calculates the remaining length of the instance string (from the index position).
|
262
|
-
#
|
263
|
-
# @return [Integer] the remaining length of the instance string
|
264
|
-
#
|
265
|
-
def rest_length
|
266
|
-
length = @string.length - @index
|
267
|
-
return length
|
268
|
-
end
|
269
|
-
|
270
|
-
# Extracts the remaining part of the instance string (from the index position to the end of the string).
|
271
|
-
#
|
272
|
-
# @return [String] the remaining part of the instance string
|
273
|
-
#
|
274
|
-
def rest_string
|
275
|
-
str = @string[@index..(@string.length-1)]
|
276
|
-
return str
|
277
|
-
end
|
278
|
-
|
279
|
-
# Resets the instance string and index.
|
280
|
-
#
|
281
|
-
def reset
|
282
|
-
@string = ''
|
283
|
-
@index = 0
|
284
|
-
end
|
285
|
-
|
286
|
-
# Resets the instance index.
|
287
|
-
#
|
288
|
-
def reset_index
|
289
|
-
@index = 0
|
290
|
-
end
|
291
|
-
|
292
|
-
# Sets the instance file variable.
|
293
|
-
#
|
294
|
-
# @note For performance reasons, we enable the Stream instance to write directly to file,
|
295
|
-
# to avoid expensive string operations which will otherwise slow down the write performance.
|
296
|
-
#
|
297
|
-
# @param [File] file a File object
|
298
|
-
#
|
299
|
-
def set_file(file)
|
300
|
-
@file = file
|
301
|
-
end
|
302
|
-
|
303
|
-
# Sets a new instance string, and resets the index variable.
|
304
|
-
#
|
305
|
-
# @param [String] binary an encoded string
|
306
|
-
#
|
307
|
-
def set_string(binary)
|
308
|
-
binary = binary[0] if binary.is_a?(Array)
|
309
|
-
@string = binary
|
310
|
-
@index = 0
|
311
|
-
end
|
312
|
-
|
313
|
-
# Applies an offset (positive or negative) to the instance index.
|
314
|
-
#
|
315
|
-
# @param [Integer] offset the length to skip (positive) or rewind (negative)
|
316
|
-
#
|
317
|
-
def skip(offset)
|
318
|
-
@index += offset
|
319
|
-
end
|
320
|
-
|
321
|
-
# Writes a binary string to the File object of this instance.
|
322
|
-
#
|
323
|
-
# @param [String] binary a binary string
|
324
|
-
#
|
325
|
-
def write(binary)
|
326
|
-
@file.write(binary)
|
327
|
-
end
|
328
|
-
|
329
|
-
|
330
|
-
private
|
331
|
-
|
332
|
-
|
333
|
-
# Determines the relationship between system and string endianness, and sets the instance endian variable.
|
334
|
-
#
|
335
|
-
def configure_endian
|
336
|
-
if CPU_ENDIAN == @str_endian
|
337
|
-
@equal_endian = true
|
338
|
-
else
|
339
|
-
@equal_endian = false
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
# Converts a data type/vr to an encode/decode string used by Ruby's pack/unpack methods.
|
344
|
-
#
|
345
|
-
# @param [String] vr a value representation (data type)
|
346
|
-
# @return [String] an encode/decode format string
|
347
|
-
#
|
348
|
-
def vr_to_str(vr)
|
349
|
-
unless @format[vr]
|
350
|
-
errors << "Warning: Element type #{vr} does not have a reading method assigned to it. Something is not implemented correctly or the DICOM data analyzed is invalid."
|
351
|
-
return @hex
|
352
|
-
else
|
353
|
-
return @format[vr]
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
# Sets the hash which is used to convert data element types (VR) to
|
358
|
-
# encode/decode format strings accepted by Ruby's pack/unpack methods.
|
359
|
-
#
|
360
|
-
def set_format_hash
|
361
|
-
@format = {
|
362
|
-
'BY' => @by, # Byte/Character (1-byte integers)
|
363
|
-
'US' => @us, # Unsigned short (2 bytes)
|
364
|
-
'SS' => @ss, # Signed short (2 bytes)
|
365
|
-
'UL' => @ul, # Unsigned long (4 bytes)
|
366
|
-
'SL' => @sl, # Signed long (4 bytes)
|
367
|
-
'FL' => @fs, # Floating point single (4 bytes)
|
368
|
-
'FD' => @fd, # Floating point double (8 bytes)
|
369
|
-
'OB' => @by, # Other byte string (1-byte integers)
|
370
|
-
'OF' => @fs, # Other float string (4-byte floating point numbers)
|
371
|
-
'OW' => @us, # Other word string (2-byte integers)
|
372
|
-
'AT' => @hex, # Tag reference (4 bytes) NB: For tags the spesialized encode_tag/decode_tag methods are used instead of this lookup table.
|
373
|
-
'UN' => @hex, # Unknown information (header element is not recognized from local database)
|
374
|
-
'HEX' => @hex, # HEX
|
375
|
-
# We have a number of VRs that are decoded as string:
|
376
|
-
'AE' => @str,
|
377
|
-
'AS' => @str,
|
378
|
-
'CS' => @str,
|
379
|
-
'DA' => @str,
|
380
|
-
'DS' => @str,
|
381
|
-
'DT' => @str,
|
382
|
-
'IS' => @str,
|
383
|
-
'LO' => @str,
|
384
|
-
'LT' => @str,
|
385
|
-
'PN' => @str,
|
386
|
-
'SH' => @str,
|
387
|
-
'ST' => @str,
|
388
|
-
'TM' => @str,
|
389
|
-
'UI' => @str,
|
390
|
-
'UT' => @str,
|
391
|
-
'STR' => @str
|
392
|
-
}
|
393
|
-
end
|
394
|
-
|
395
|
-
# Sets the hash which is used to keep track of which bytes to use for padding
|
396
|
-
# data elements of various vr which have an odd value length.
|
397
|
-
#
|
398
|
-
def set_pad_byte
|
399
|
-
@pad_byte = {
|
400
|
-
# Space character:
|
401
|
-
'AE' => "\x20",
|
402
|
-
'AS' => "\x20",
|
403
|
-
'CS' => "\x20",
|
404
|
-
'DA' => "\x20",
|
405
|
-
'DS' => "\x20",
|
406
|
-
'DT' => "\x20",
|
407
|
-
'IS' => "\x20",
|
408
|
-
'LO' => "\x20",
|
409
|
-
'LT' => "\x20",
|
410
|
-
'PN' => "\x20",
|
411
|
-
'SH' => "\x20",
|
412
|
-
'ST' => "\x20",
|
413
|
-
'TM' => "\x20",
|
414
|
-
'UT' => "\x20",
|
415
|
-
# Zero byte:
|
416
|
-
'AT' => "\x00",
|
417
|
-
'BY' => "\x00",
|
418
|
-
'FL' => "\x00",
|
419
|
-
'FD' => "\x00",
|
420
|
-
'OB' => "\x00",
|
421
|
-
'OF' => "\x00",
|
422
|
-
'OW' => "\x00",
|
423
|
-
'SL' => "\x00",
|
424
|
-
'SQ' => "\x00",
|
425
|
-
'SS' => "\x00",
|
426
|
-
'UI' => "\x00",
|
427
|
-
'UL' => "\x00",
|
428
|
-
'UN' => "\x00",
|
429
|
-
'US' => "\x00"
|
430
|
-
}
|
431
|
-
end
|
432
|
-
|
433
|
-
# Sets the pack/unpack format strings that are used for encoding/decoding.
|
434
|
-
# Some of these depends on the endianness of the system and the encoded string.
|
435
|
-
#
|
436
|
-
def set_string_formats
|
437
|
-
if @equal_endian
|
438
|
-
# Little endian byte order:
|
439
|
-
@us = 'S<*' # Unsigned short (2 bytes)
|
440
|
-
@ss = 's<*' # Signed short (2 bytes)
|
441
|
-
@ul = 'L<*' # Unsigned long (4 bytes)
|
442
|
-
@sl = 'l<*' # Signed long (4 bytes)
|
443
|
-
@fs = 'e*' # Floating point single (4 bytes)
|
444
|
-
@fd = 'E*' # Floating point double ( 8 bytes)
|
445
|
-
else
|
446
|
-
# Network (big endian) byte order:
|
447
|
-
@us = 'S>*'
|
448
|
-
@ss = 's>*'
|
449
|
-
@ul = 'L>*'
|
450
|
-
@sl = 'l>'
|
451
|
-
@fs = 'g*'
|
452
|
-
@fd = 'G*'
|
453
|
-
end
|
454
|
-
# Format strings that are not dependent on endianness:
|
455
|
-
@by = 'C*' # Unsigned char (1 byte)
|
456
|
-
@str = 'a*'
|
457
|
-
@hex = 'H*' # (this may be dependent on endianness(?))
|
458
|
-
end
|
459
|
-
|
460
|
-
end
|
461
|
-
end
|
1
|
+
module DICOM
|
2
|
+
|
3
|
+
# The Stream class handles string operations (encoding to and decoding from binary strings).
|
4
|
+
# It is used by the various classes of ruby-dicom for tasks such as reading and writing
|
5
|
+
# from/to files or network packets.
|
6
|
+
#
|
7
|
+
# @note In practice, this class is for internal library use. It is typically not accessed
|
8
|
+
# by the user, and can thus be considered a 'private' class.
|
9
|
+
#
|
10
|
+
class Stream
|
11
|
+
|
12
|
+
# A boolean which reports the relationship between the endianness of the system and the instance string.
|
13
|
+
attr_reader :equal_endian
|
14
|
+
# Our current position in the instance string (used only for decoding).
|
15
|
+
attr_accessor :index
|
16
|
+
# The instance string.
|
17
|
+
attr_accessor :string
|
18
|
+
# The endianness of the instance string.
|
19
|
+
attr_reader :str_endian
|
20
|
+
# An array of warning/error messages that (may) have been accumulated.
|
21
|
+
attr_reader :errors
|
22
|
+
# A hash with vr as key and its corresponding pad byte as value.
|
23
|
+
attr_reader :pad_byte
|
24
|
+
|
25
|
+
# Creates a Stream instance.
|
26
|
+
#
|
27
|
+
# @param [String, NilClass] binary a binary string (or nil, if creating an empty instance)
|
28
|
+
# @param [Boolean] string_endian the endianness of the instance string (true for big endian, false for small endian)
|
29
|
+
# @param [Hash] options the options to use for creating the instance
|
30
|
+
# @option options [Integer] :index a position (offset) in the instance string where reading will start
|
31
|
+
#
|
32
|
+
def initialize(binary, string_endian, options={})
|
33
|
+
@string = binary || ''
|
34
|
+
@index = options[:index] || 0
|
35
|
+
@errors = Array.new
|
36
|
+
self.endian = string_endian
|
37
|
+
end
|
38
|
+
|
39
|
+
# Prepends a pre-encoded string to the instance string (inserts at the beginning).
|
40
|
+
#
|
41
|
+
# @param [String] binary a binary string
|
42
|
+
#
|
43
|
+
def add_first(binary)
|
44
|
+
@string = "#{binary}#{@string}" if binary
|
45
|
+
end
|
46
|
+
|
47
|
+
# Appends a pre-encoded string to the instance string (inserts at the end).
|
48
|
+
#
|
49
|
+
# @param [String] binary a binary string
|
50
|
+
#
|
51
|
+
def add_last(binary)
|
52
|
+
@string = "#{@string}#{binary}" if binary
|
53
|
+
end
|
54
|
+
|
55
|
+
# Decodes a section of the instance string.
|
56
|
+
# The instance index is offset in accordance with the length read.
|
57
|
+
#
|
58
|
+
# @note If multiple numbers are decoded, these are returned in an array.
|
59
|
+
# @param [Integer] length the string length to be decoded
|
60
|
+
# @param [String] type the type (vr) of data to decode
|
61
|
+
# @return [String, Integer, Float, Array] the formatted (decoded) data
|
62
|
+
#
|
63
|
+
def decode(length, type)
|
64
|
+
raise ArgumentError, "Invalid argument length. Expected Integer, got #{length.class}" unless length.is_a?(Integer)
|
65
|
+
raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
|
66
|
+
value = nil
|
67
|
+
if (@index + length) <= @string.length
|
68
|
+
# There are sufficient bytes remaining to extract the value:
|
69
|
+
if type == 'AT'
|
70
|
+
# We need to guard ourselves against the case where a string contains an invalid 'AT' value:
|
71
|
+
if length == 4
|
72
|
+
value = decode_tag
|
73
|
+
else
|
74
|
+
# Invalid. Just return nil.
|
75
|
+
skip(length)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# Decode the binary string and return value:
|
79
|
+
value = @string.slice(@index, length).unpack(vr_to_str(type))
|
80
|
+
# If the result is an array of one element, return the element instead of the array.
|
81
|
+
# If result is contained in a multi-element array, the original array is returned.
|
82
|
+
if value.length == 1
|
83
|
+
value = value[0]
|
84
|
+
# If value is a string, strip away possible trailing whitespace:
|
85
|
+
value = value.rstrip if value.is_a?(String)
|
86
|
+
end
|
87
|
+
# Update our position in the string:
|
88
|
+
skip(length)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
value
|
92
|
+
end
|
93
|
+
|
94
|
+
# Decodes the entire instance string (typically used for decoding image data).
|
95
|
+
#
|
96
|
+
# @note If multiple numbers are decoded, these are returned in an array.
|
97
|
+
# @param [String] type the type (vr) of data to decode
|
98
|
+
# @return [String, Integer, Float, Array] the formatted (decoded) data
|
99
|
+
#
|
100
|
+
def decode_all(type)
|
101
|
+
length = @string.length
|
102
|
+
value = @string.slice(@index, length).unpack(vr_to_str(type))
|
103
|
+
skip(length)
|
104
|
+
return value
|
105
|
+
end
|
106
|
+
|
107
|
+
# Decodes 4 bytes of the instance string and formats it as a ruby-dicom tag string.
|
108
|
+
#
|
109
|
+
# @return [String, NilClass] a formatted tag string ('GGGG,EEEE'), or nil (e.g. if at end of string)
|
110
|
+
#
|
111
|
+
def decode_tag
|
112
|
+
length = 4
|
113
|
+
tag = nil
|
114
|
+
if (@index + length) <= @string.length
|
115
|
+
# There are sufficient bytes remaining to extract a full tag:
|
116
|
+
str = @string.slice(@index, length).unpack(@hex)[0].upcase
|
117
|
+
if @equal_endian
|
118
|
+
tag = "#{str[2..3]}#{str[0..1]},#{str[6..7]}#{str[4..5]}"
|
119
|
+
else
|
120
|
+
tag = "#{str[0..3]},#{str[4..7]}"
|
121
|
+
end
|
122
|
+
# Update our position in the string:
|
123
|
+
skip(length)
|
124
|
+
end
|
125
|
+
tag
|
126
|
+
end
|
127
|
+
|
128
|
+
# Encodes a given value to a binary string.
|
129
|
+
#
|
130
|
+
# @param [String, Integer, Float, Array] value a formatted value (String, Integer, etc..) or an array of numbers
|
131
|
+
# @param [String] type the type (vr) of data to encode
|
132
|
+
# @return [String] an encoded binary string
|
133
|
+
#
|
134
|
+
def encode(value, type)
|
135
|
+
raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
|
136
|
+
value = [value] unless value.is_a?(Array)
|
137
|
+
return value.pack(vr_to_str(type))
|
138
|
+
end
|
139
|
+
|
140
|
+
# Encodes a value to a binary string and prepends it to the instance string.
|
141
|
+
#
|
142
|
+
# @param [String, Integer, Float, Array] value a formatted value (String, Integer, etc..) or an array of numbers
|
143
|
+
# @param [String] type the type (vr) of data to encode
|
144
|
+
#
|
145
|
+
def encode_first(value, type)
|
146
|
+
value = [value] unless value.is_a?(Array)
|
147
|
+
@string = "#{value.pack(vr_to_str(type))}#{@string}"
|
148
|
+
end
|
149
|
+
|
150
|
+
# Encodes a value to a binary string and appends it to the instance string.
|
151
|
+
#
|
152
|
+
# @param [String, Integer, Float, Array] value a formatted value (String, Integer, etc..) or an array of numbers
|
153
|
+
# @param [String] type the type (vr) of data to encode
|
154
|
+
#
|
155
|
+
def encode_last(value, type)
|
156
|
+
value = [value] unless value.is_a?(Array)
|
157
|
+
@string = "#{@string}#{value.pack(vr_to_str(type))}"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Appends a string with trailling spaces to achieve a target length, and encodes it to a binary string.
|
161
|
+
#
|
162
|
+
# @param [String] string a string to be padded
|
163
|
+
# @param [Integer] target_length the target length of the string
|
164
|
+
# @return [String] an encoded binary string
|
165
|
+
#
|
166
|
+
def encode_string_with_trailing_spaces(string, target_length)
|
167
|
+
length = string.length
|
168
|
+
if length < target_length
|
169
|
+
return "#{[string].pack(@str)}#{['20'*(target_length-length)].pack(@hex)}"
|
170
|
+
elsif length == target_length
|
171
|
+
return [string].pack(@str)
|
172
|
+
else
|
173
|
+
raise "The specified string is longer than the allowed maximum length (String: #{string}, Target length: #{target_length})."
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Encodes a tag from the ruby-dicom format ('GGGG,EEEE') to a proper binary string.
|
178
|
+
#
|
179
|
+
# @param [String] tag a ruby-dicom type tag string
|
180
|
+
# @return [String] an encoded binary string
|
181
|
+
#
|
182
|
+
def encode_tag(tag)
|
183
|
+
[
|
184
|
+
@equal_endian ? "#{tag[2..3]}#{tag[0..1]}#{tag[7..8]}#{tag[5..6]}" : "#{tag[0..3]}#{tag[5..8]}"
|
185
|
+
].pack(@hex)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Encodes a value, and if the the resulting binary string has an
|
189
|
+
# odd length, appends a proper padding byte to make it even length.
|
190
|
+
#
|
191
|
+
# @param [String, Integer, Float, Array] value a formatted value (String, Integer, etc..) or an array of numbers
|
192
|
+
# @param [String] vr the value representation of data to encode
|
193
|
+
# @return [String] the encoded binary string
|
194
|
+
#
|
195
|
+
def encode_value(value, vr)
|
196
|
+
if vr == 'AT'
|
197
|
+
bin = encode_tag(value)
|
198
|
+
else
|
199
|
+
# Make sure the value is in an array:
|
200
|
+
value = [value] unless value.is_a?(Array)
|
201
|
+
# Get the proper pack string:
|
202
|
+
type = vr_to_str(vr)
|
203
|
+
# Encode:
|
204
|
+
bin = value.pack(type)
|
205
|
+
# Add an empty byte if the resulting binary has an odd length:
|
206
|
+
bin = "#{bin}#{@pad_byte[vr]}" if bin.length.odd?
|
207
|
+
end
|
208
|
+
return bin
|
209
|
+
end
|
210
|
+
|
211
|
+
# Sets the endianness of the instance string. The relationship between the string
|
212
|
+
# endianness and the system endianness determines which encoding/decoding flags to use.
|
213
|
+
#
|
214
|
+
# @param [Boolean] string_endian the endianness of the instance string (true for big endian, false for small endian)
|
215
|
+
#
|
216
|
+
def endian=(string_endian)
|
217
|
+
@str_endian = string_endian
|
218
|
+
configure_endian
|
219
|
+
set_pad_byte
|
220
|
+
set_string_formats
|
221
|
+
set_format_hash
|
222
|
+
end
|
223
|
+
|
224
|
+
# Extracts the entire instance string, or optionally,
|
225
|
+
# just the first part of it if a length is specified.
|
226
|
+
#
|
227
|
+
# @note The exported string is removed from the instance string.
|
228
|
+
# @param [Integer] length the length of the string to cut out (if nil, the entire string is exported)
|
229
|
+
# @return [String] the instance string (or part of it)
|
230
|
+
#
|
231
|
+
def export(length=nil)
|
232
|
+
if length
|
233
|
+
string = @string.slice!(0, length)
|
234
|
+
else
|
235
|
+
string = @string
|
236
|
+
reset
|
237
|
+
end
|
238
|
+
return string
|
239
|
+
end
|
240
|
+
|
241
|
+
# Extracts and returns a binary string of the given length, starting at the index position.
|
242
|
+
# The instance index is then offset in accordance with the length read.
|
243
|
+
#
|
244
|
+
# @param [Integer] length the length of the string to be extracted
|
245
|
+
# @return [String] a part of the instance string
|
246
|
+
#
|
247
|
+
def extract(length)
|
248
|
+
str = @string.slice(@index, length)
|
249
|
+
skip(length)
|
250
|
+
return str
|
251
|
+
end
|
252
|
+
|
253
|
+
# Gives the length of the instance string.
|
254
|
+
#
|
255
|
+
# @return [Integer] the instance string's length
|
256
|
+
#
|
257
|
+
def length
|
258
|
+
return @string.length
|
259
|
+
end
|
260
|
+
|
261
|
+
# Calculates the remaining length of the instance string (from the index position).
|
262
|
+
#
|
263
|
+
# @return [Integer] the remaining length of the instance string
|
264
|
+
#
|
265
|
+
def rest_length
|
266
|
+
length = @string.length - @index
|
267
|
+
return length
|
268
|
+
end
|
269
|
+
|
270
|
+
# Extracts the remaining part of the instance string (from the index position to the end of the string).
|
271
|
+
#
|
272
|
+
# @return [String] the remaining part of the instance string
|
273
|
+
#
|
274
|
+
def rest_string
|
275
|
+
str = @string[@index..(@string.length-1)]
|
276
|
+
return str
|
277
|
+
end
|
278
|
+
|
279
|
+
# Resets the instance string and index.
|
280
|
+
#
|
281
|
+
def reset
|
282
|
+
@string = ''
|
283
|
+
@index = 0
|
284
|
+
end
|
285
|
+
|
286
|
+
# Resets the instance index.
|
287
|
+
#
|
288
|
+
def reset_index
|
289
|
+
@index = 0
|
290
|
+
end
|
291
|
+
|
292
|
+
# Sets the instance file variable.
|
293
|
+
#
|
294
|
+
# @note For performance reasons, we enable the Stream instance to write directly to file,
|
295
|
+
# to avoid expensive string operations which will otherwise slow down the write performance.
|
296
|
+
#
|
297
|
+
# @param [File] file a File object
|
298
|
+
#
|
299
|
+
def set_file(file)
|
300
|
+
@file = file
|
301
|
+
end
|
302
|
+
|
303
|
+
# Sets a new instance string, and resets the index variable.
|
304
|
+
#
|
305
|
+
# @param [String] binary an encoded string
|
306
|
+
#
|
307
|
+
def set_string(binary)
|
308
|
+
binary = binary[0] if binary.is_a?(Array)
|
309
|
+
@string = binary
|
310
|
+
@index = 0
|
311
|
+
end
|
312
|
+
|
313
|
+
# Applies an offset (positive or negative) to the instance index.
|
314
|
+
#
|
315
|
+
# @param [Integer] offset the length to skip (positive) or rewind (negative)
|
316
|
+
#
|
317
|
+
def skip(offset)
|
318
|
+
@index += offset
|
319
|
+
end
|
320
|
+
|
321
|
+
# Writes a binary string to the File object of this instance.
|
322
|
+
#
|
323
|
+
# @param [String] binary a binary string
|
324
|
+
#
|
325
|
+
def write(binary)
|
326
|
+
@file.write(binary)
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
private
|
331
|
+
|
332
|
+
|
333
|
+
# Determines the relationship between system and string endianness, and sets the instance endian variable.
|
334
|
+
#
|
335
|
+
def configure_endian
|
336
|
+
if CPU_ENDIAN == @str_endian
|
337
|
+
@equal_endian = true
|
338
|
+
else
|
339
|
+
@equal_endian = false
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Converts a data type/vr to an encode/decode string used by Ruby's pack/unpack methods.
|
344
|
+
#
|
345
|
+
# @param [String] vr a value representation (data type)
|
346
|
+
# @return [String] an encode/decode format string
|
347
|
+
#
|
348
|
+
def vr_to_str(vr)
|
349
|
+
unless @format[vr]
|
350
|
+
errors << "Warning: Element type #{vr} does not have a reading method assigned to it. Something is not implemented correctly or the DICOM data analyzed is invalid."
|
351
|
+
return @hex
|
352
|
+
else
|
353
|
+
return @format[vr]
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Sets the hash which is used to convert data element types (VR) to
|
358
|
+
# encode/decode format strings accepted by Ruby's pack/unpack methods.
|
359
|
+
#
|
360
|
+
def set_format_hash
|
361
|
+
@format = {
|
362
|
+
'BY' => @by, # Byte/Character (1-byte integers)
|
363
|
+
'US' => @us, # Unsigned short (2 bytes)
|
364
|
+
'SS' => @ss, # Signed short (2 bytes)
|
365
|
+
'UL' => @ul, # Unsigned long (4 bytes)
|
366
|
+
'SL' => @sl, # Signed long (4 bytes)
|
367
|
+
'FL' => @fs, # Floating point single (4 bytes)
|
368
|
+
'FD' => @fd, # Floating point double (8 bytes)
|
369
|
+
'OB' => @by, # Other byte string (1-byte integers)
|
370
|
+
'OF' => @fs, # Other float string (4-byte floating point numbers)
|
371
|
+
'OW' => @us, # Other word string (2-byte integers)
|
372
|
+
'AT' => @hex, # Tag reference (4 bytes) NB: For tags the spesialized encode_tag/decode_tag methods are used instead of this lookup table.
|
373
|
+
'UN' => @hex, # Unknown information (header element is not recognized from local database)
|
374
|
+
'HEX' => @hex, # HEX
|
375
|
+
# We have a number of VRs that are decoded as string:
|
376
|
+
'AE' => @str,
|
377
|
+
'AS' => @str,
|
378
|
+
'CS' => @str,
|
379
|
+
'DA' => @str,
|
380
|
+
'DS' => @str,
|
381
|
+
'DT' => @str,
|
382
|
+
'IS' => @str,
|
383
|
+
'LO' => @str,
|
384
|
+
'LT' => @str,
|
385
|
+
'PN' => @str,
|
386
|
+
'SH' => @str,
|
387
|
+
'ST' => @str,
|
388
|
+
'TM' => @str,
|
389
|
+
'UI' => @str,
|
390
|
+
'UT' => @str,
|
391
|
+
'STR' => @str
|
392
|
+
}
|
393
|
+
end
|
394
|
+
|
395
|
+
# Sets the hash which is used to keep track of which bytes to use for padding
|
396
|
+
# data elements of various vr which have an odd value length.
|
397
|
+
#
|
398
|
+
def set_pad_byte
|
399
|
+
@pad_byte = {
|
400
|
+
# Space character:
|
401
|
+
'AE' => "\x20",
|
402
|
+
'AS' => "\x20",
|
403
|
+
'CS' => "\x20",
|
404
|
+
'DA' => "\x20",
|
405
|
+
'DS' => "\x20",
|
406
|
+
'DT' => "\x20",
|
407
|
+
'IS' => "\x20",
|
408
|
+
'LO' => "\x20",
|
409
|
+
'LT' => "\x20",
|
410
|
+
'PN' => "\x20",
|
411
|
+
'SH' => "\x20",
|
412
|
+
'ST' => "\x20",
|
413
|
+
'TM' => "\x20",
|
414
|
+
'UT' => "\x20",
|
415
|
+
# Zero byte:
|
416
|
+
'AT' => "\x00",
|
417
|
+
'BY' => "\x00",
|
418
|
+
'FL' => "\x00",
|
419
|
+
'FD' => "\x00",
|
420
|
+
'OB' => "\x00",
|
421
|
+
'OF' => "\x00",
|
422
|
+
'OW' => "\x00",
|
423
|
+
'SL' => "\x00",
|
424
|
+
'SQ' => "\x00",
|
425
|
+
'SS' => "\x00",
|
426
|
+
'UI' => "\x00",
|
427
|
+
'UL' => "\x00",
|
428
|
+
'UN' => "\x00",
|
429
|
+
'US' => "\x00"
|
430
|
+
}
|
431
|
+
end
|
432
|
+
|
433
|
+
# Sets the pack/unpack format strings that are used for encoding/decoding.
|
434
|
+
# Some of these depends on the endianness of the system and the encoded string.
|
435
|
+
#
|
436
|
+
def set_string_formats
|
437
|
+
if @equal_endian
|
438
|
+
# Little endian byte order:
|
439
|
+
@us = 'S<*' # Unsigned short (2 bytes)
|
440
|
+
@ss = 's<*' # Signed short (2 bytes)
|
441
|
+
@ul = 'L<*' # Unsigned long (4 bytes)
|
442
|
+
@sl = 'l<*' # Signed long (4 bytes)
|
443
|
+
@fs = 'e*' # Floating point single (4 bytes)
|
444
|
+
@fd = 'E*' # Floating point double ( 8 bytes)
|
445
|
+
else
|
446
|
+
# Network (big endian) byte order:
|
447
|
+
@us = 'S>*'
|
448
|
+
@ss = 's>*'
|
449
|
+
@ul = 'L>*'
|
450
|
+
@sl = 'l>'
|
451
|
+
@fs = 'g*'
|
452
|
+
@fd = 'G*'
|
453
|
+
end
|
454
|
+
# Format strings that are not dependent on endianness:
|
455
|
+
@by = 'C*' # Unsigned char (1 byte)
|
456
|
+
@str = 'a*'
|
457
|
+
@hex = 'H*' # (this may be dependent on endianness(?))
|
458
|
+
end
|
459
|
+
|
460
|
+
end
|
461
|
+
end
|