dicom 0.9.7 → 0.9.8
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.
- 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
|