dicom 0.8 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/{CHANGELOG → CHANGELOG.rdoc} +100 -52
- data/README.rdoc +126 -0
- data/lib/dicom.rb +13 -7
- data/lib/dicom/anonymizer.rb +129 -111
- data/lib/dicom/constants.rb +60 -10
- data/lib/dicom/d_client.rb +230 -157
- data/lib/dicom/d_library.rb +88 -8
- data/lib/dicom/d_object.rb +141 -149
- data/lib/dicom/d_read.rb +42 -36
- data/lib/dicom/d_server.rb +8 -10
- data/lib/dicom/d_write.rb +25 -46
- data/lib/dicom/dictionary.rb +1 -3
- data/lib/dicom/{data_element.rb → element.rb} +61 -49
- data/lib/dicom/elemental.rb +126 -0
- data/lib/dicom/file_handler.rb +18 -17
- data/lib/dicom/image_item.rb +844 -0
- data/lib/dicom/image_processor.rb +69 -0
- data/lib/dicom/image_processor_mini_magick.rb +74 -0
- data/lib/dicom/image_processor_r_magick.rb +102 -0
- data/lib/dicom/item.rb +21 -19
- data/lib/dicom/link.rb +64 -82
- data/lib/dicom/{super_parent.rb → parent.rb} +270 -39
- data/lib/dicom/ruby_extensions.rb +175 -3
- data/lib/dicom/sequence.rb +5 -6
- data/lib/dicom/stream.rb +37 -25
- data/lib/dicom/variables.rb +51 -0
- data/lib/dicom/version.rb +6 -0
- metadata +97 -29
- data/README +0 -100
- data/init.rb +0 -1
- data/lib/dicom/elements.rb +0 -82
- data/lib/dicom/super_item.rb +0 -696
data/lib/dicom/d_library.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
# Copyright 2008-2010 Christoffer Lervag
|
2
|
-
|
3
1
|
module DICOM
|
4
2
|
|
5
3
|
# This class contains methods that interact with Ruby DICOM's dictionary.
|
6
4
|
#
|
7
5
|
class DLibrary
|
8
6
|
|
7
|
+
# A hash with element name strings as key and method name symbols as value.
|
8
|
+
attr_reader :methods_from_names
|
9
|
+
# A hash with element method name symbols as key and name strings as value.
|
10
|
+
attr_reader :names_from_methods
|
9
11
|
# A hash containing tags as key and an array as value, where the array contains data element vr and name.
|
10
12
|
attr_reader :tags
|
11
13
|
# A hash containing UIDs as key and an array as value, where the array contains name and type.
|
@@ -20,6 +22,58 @@ module DICOM
|
|
20
22
|
# Load UID hash (DICOM unique identifiers), where the keys are UID strings,
|
21
23
|
# and values are two-element arrays [description, type]:
|
22
24
|
@uid = Dictionary.load_uid
|
25
|
+
create_method_conversion_tables
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the method (symbol) corresponding to the specified string value (which may represent a element tag, name or method).
|
29
|
+
# Returns nil if no match is found.
|
30
|
+
#
|
31
|
+
def as_method(value)
|
32
|
+
case true
|
33
|
+
when value.tag?
|
34
|
+
name, vr = get_name_vr(value)
|
35
|
+
@methods_from_names[name]
|
36
|
+
when value.dicom_name?
|
37
|
+
@methods_from_names[value]
|
38
|
+
when value.dicom_method?
|
39
|
+
@names_from_methods.has_key?(value.to_sym) ? value.to_sym : nil
|
40
|
+
else
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the name (string) corresponding to the specified string value (which may represent a element tag, name or method).
|
46
|
+
# Returns nil if no match is found.
|
47
|
+
#
|
48
|
+
def as_name(value)
|
49
|
+
case true
|
50
|
+
when value.tag?
|
51
|
+
name, vr = get_name_vr(value)
|
52
|
+
name
|
53
|
+
when value.dicom_name?
|
54
|
+
@methods_from_names.has_key?(value) ? value.to_s : nil
|
55
|
+
when value.dicom_method?
|
56
|
+
@names_from_methods[value.to_sym]
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the tag (string) corresponding to the specified string value (which may represent a element tag, name or method).
|
63
|
+
# Returns nil if no match is found.
|
64
|
+
#
|
65
|
+
def as_tag(value)
|
66
|
+
case true
|
67
|
+
when value.tag?
|
68
|
+
name, vr = get_name_vr(value)
|
69
|
+
name.nil? ? nil : value
|
70
|
+
when value.dicom_name?
|
71
|
+
get_tag(value)
|
72
|
+
when value.dicom_method?
|
73
|
+
get_tag(@names_from_methods[value.to_sym])
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
23
77
|
end
|
24
78
|
|
25
79
|
# Checks whether a given string is a valid transfer syntax or not.
|
@@ -64,12 +118,12 @@ module DICOM
|
|
64
118
|
# * <tt>uid</tt> -- String. A DICOM UID value.
|
65
119
|
#
|
66
120
|
def get_compression(uid)
|
121
|
+
raise ArgumentError, "Expected String, got #{uid.class}" unless uid.is_a?(String)
|
67
122
|
result = false
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
123
|
+
value = @uid[uid]
|
124
|
+
if value
|
125
|
+
first_word = value[0].split(" ").first
|
126
|
+
result = true if value[1] == "Transfer Syntax" and not ["Implicit", "Explicit"].include?(first_word)
|
73
127
|
end
|
74
128
|
return result
|
75
129
|
end
|
@@ -151,9 +205,15 @@ module DICOM
|
|
151
205
|
#
|
152
206
|
def get_tag(name)
|
153
207
|
tag = nil
|
208
|
+
name = name.to_s.downcase
|
209
|
+
@tag_name_pairs_cache ||= Hash.new
|
210
|
+
return @tag_name_pairs_cache[name] unless @tag_name_pairs_cache[name].nil?
|
154
211
|
@tags.each_pair do |key, value|
|
155
|
-
|
212
|
+
next unless value[1].downcase == name
|
213
|
+
tag = key
|
214
|
+
break
|
156
215
|
end
|
216
|
+
@tag_name_pairs_cache[name]=tag
|
157
217
|
return tag
|
158
218
|
end
|
159
219
|
|
@@ -204,5 +264,25 @@ module DICOM
|
|
204
264
|
return valid, explicit, endian
|
205
265
|
end
|
206
266
|
|
267
|
+
|
268
|
+
private
|
269
|
+
|
270
|
+
|
271
|
+
# Creates the instance hashes that are used for name to/from method conversion.
|
272
|
+
#
|
273
|
+
def create_method_conversion_tables
|
274
|
+
if @methods_from_names.nil?
|
275
|
+
@methods_from_names = Hash.new
|
276
|
+
@names_from_methods = Hash.new
|
277
|
+
# Fill the hashes:
|
278
|
+
@tags.each_pair do |key, value|
|
279
|
+
name = value[1]
|
280
|
+
method_name = name.dicom_methodize
|
281
|
+
@methods_from_names[name] = method_name.to_sym
|
282
|
+
@names_from_methods[method_name.to_sym] = name
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
207
287
|
end
|
208
288
|
end
|
data/lib/dicom/d_object.rb
CHANGED
@@ -1,4 +1,18 @@
|
|
1
|
-
#
|
1
|
+
# === TODO:
|
2
|
+
#
|
3
|
+
# * The retrieve file network functionality (get_image() in DClient class) has not been tested.
|
4
|
+
# * Make the networking code more intelligent in its handling of unexpected network communication.
|
5
|
+
# * Full support for compressed image data.
|
6
|
+
# * Read/Write 12 bit image data.
|
7
|
+
# * Full color support (RGB and PALETTE COLOR with get_object_magick() already implemented).
|
8
|
+
# * Support for extraction of multiple encapsulated pixel data frames in get_image() and get_image_narray().
|
9
|
+
# * Image handling currently ignores DICOM tags like Pixel Aspect Ratio, Image Orientation and (to some degree) Photometric Interpretation.
|
10
|
+
# * More robust and flexible options for reorienting extracted pixel arrays?
|
11
|
+
# * A curious observation: Creating a DLibrary instance is exceptionally slow on Ruby 1.9.1: 0.4 seconds versus ~0.01 seconds on Ruby 1.8.7!
|
12
|
+
# * Add these as github issues and remove this list!
|
13
|
+
|
14
|
+
|
15
|
+
# Copyright 2008-2011 Christoffer Lervag
|
2
16
|
#
|
3
17
|
# This program is free software: you can redistribute it and/or modify
|
4
18
|
# it under the terms of the GNU General Public License as published by
|
@@ -13,20 +27,6 @@
|
|
13
27
|
# You should have received a copy of the GNU General Public License
|
14
28
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
29
|
#
|
16
|
-
|
17
|
-
# === TODO:
|
18
|
-
#
|
19
|
-
# * The retrieve file network functionality (get_image() in DClient class) has not been tested.
|
20
|
-
# * Make the networking code more intelligent in its handling of unexpected network communication.
|
21
|
-
# * Full support for compressed image data.
|
22
|
-
# * Read/Write 12 bit image data.
|
23
|
-
# * Support for color image data.
|
24
|
-
# * Complete support for Big endian (Everything but signed short and signed long has been implemented).
|
25
|
-
# * Complete support for multiple frame image data to NArray and RMagick objects (partial support already featured).
|
26
|
-
# * Image handling does not take into consideration DICOM tags which specify orientation, samples per pixel and photometric interpretation.
|
27
|
-
# * More robust and flexible options for reorienting extracted pixel arrays?
|
28
|
-
# * A curious observation: Creating a DLibrary instance is exceptionally slow on my Ruby 1.9.1 install: 0.4 seconds versus ~0.01 seconds on my Ruby 1.8.7 install!
|
29
|
-
|
30
30
|
module DICOM
|
31
31
|
|
32
32
|
# The DObject class is the main class for interacting with the DICOM object.
|
@@ -34,10 +34,10 @@ module DICOM
|
|
34
34
|
#
|
35
35
|
# === Inheritance
|
36
36
|
#
|
37
|
-
# As the DObject class inherits from the
|
38
|
-
# all
|
37
|
+
# As the DObject class inherits from the ImageItem class, which itself inherits from the Parent class,
|
38
|
+
# all ImageItem and Parent methods are also available to instances of DObject.
|
39
39
|
#
|
40
|
-
class DObject <
|
40
|
+
class DObject < ImageItem
|
41
41
|
|
42
42
|
# An array which contain any notices/warnings/errors that have been recorded for the DObject instance.
|
43
43
|
attr_reader :errors
|
@@ -50,9 +50,12 @@ module DICOM
|
|
50
50
|
# A boolean which is set as true if a DObject instance has been successfully written to file (or successfully encoded).
|
51
51
|
attr_reader :write_success
|
52
52
|
|
53
|
+
alias_method :read?, :read_success
|
54
|
+
alias_method :written?, :write_success
|
55
|
+
|
53
56
|
# Creates a DObject instance (DObject is an abbreviation for "DICOM object").
|
54
57
|
#
|
55
|
-
# The DObject instance holds references to the different types of objects (
|
58
|
+
# The DObject instance holds references to the different types of objects (Element, Item, Sequence)
|
56
59
|
# that makes up a DICOM object. A DObject is typically buildt by reading and parsing a file or a
|
57
60
|
# binary string, but can also be buildt from an empty state by the user.
|
58
61
|
#
|
@@ -63,7 +66,7 @@ module DICOM
|
|
63
66
|
#
|
64
67
|
# === Options
|
65
68
|
#
|
66
|
-
# * <tt>:bin</tt> -- Boolean. If
|
69
|
+
# * <tt>:bin</tt> -- Boolean. If true, the string parameter will be interpreted as a binary DICOM string instead of a path string.
|
67
70
|
# * <tt>:syntax</tt> -- String. If a syntax string is specified, the DRead class will be forced to use this transfer syntax when decoding the file/binary string.
|
68
71
|
# * <tt>:verbose</tt> -- Boolean. If set to false, the DObject instance will run silently and not output warnings and error messages to the screen. Defaults to true.
|
69
72
|
#
|
@@ -89,15 +92,17 @@ module DICOM
|
|
89
92
|
@explicit = true
|
90
93
|
@file_endian = false
|
91
94
|
# Control variables:
|
92
|
-
@read_success =
|
95
|
+
@read_success = nil
|
93
96
|
# Initialize a Stream instance which is used for encoding/decoding:
|
94
97
|
@stream = Stream.new(nil, @file_endian)
|
95
98
|
# The DObject instance is the top of the hierarchy and unlike other elements it has no parent:
|
96
99
|
@parent = nil
|
97
100
|
# For convenience, call the read method if a string has been supplied:
|
98
|
-
if string.is_a?(String)
|
101
|
+
if string.is_a?(String)
|
99
102
|
@file = string unless options[:bin]
|
100
103
|
read(string, options)
|
104
|
+
elsif string
|
105
|
+
raise ArgumentError, "Invalid argument. Expected String (or nil), got #{string.class}."
|
101
106
|
end
|
102
107
|
end
|
103
108
|
|
@@ -108,12 +113,16 @@ module DICOM
|
|
108
113
|
# === Parameters
|
109
114
|
#
|
110
115
|
# * <tt>max_size</tt> -- An integer (Fixnum) which specifies the maximum allowed size of the binary data strings which will be encoded.
|
116
|
+
# * <tt>transfer_syntax</tt> -- The transfer syntax string to be used when encoding the DICOM object to string segments. When this method is used for making network packets, the transfer_syntax is not part of the object, and thus needs to be specified. Defaults to the DObject's transfer syntax/Implicit little endian.
|
111
117
|
#
|
112
118
|
# === Examples
|
113
119
|
#
|
114
120
|
# encoded_strings = obj.encode_segments(16384)
|
115
121
|
#
|
116
|
-
def encode_segments(max_size)
|
122
|
+
def encode_segments(max_size, transfer_syntax=transfer_syntax)
|
123
|
+
raise ArgumentError, "Invalid argument. Expected an Integer, got #{max_size.class}." unless max_size.is_a?(Integer)
|
124
|
+
raise ArgumentError, "Argument too low (#{max_size}), please specify a bigger Integer." unless max_size > 16
|
125
|
+
raise "Can not encode binary segments for an empty DICOM object." if children.length == 0
|
117
126
|
w = DWrite.new(self, transfer_syntax, file_name=nil)
|
118
127
|
w.encode_segments(max_size)
|
119
128
|
# Write process succesful?
|
@@ -123,59 +132,97 @@ module DICOM
|
|
123
132
|
return w.segments
|
124
133
|
end
|
125
134
|
|
135
|
+
# Prints information of interest related to the DICOM object.
|
136
|
+
# Calls the print() method of Parent as well as the information() method of DObject.
|
137
|
+
#
|
138
|
+
def print_all
|
139
|
+
puts ""
|
140
|
+
print(:value_max => 30)
|
141
|
+
summary
|
142
|
+
end
|
143
|
+
|
144
|
+
# Fills a DICOM object by reading and parsing the specified DICOM file,
|
145
|
+
# and transfers the DICOM data to the DICOM object (self).
|
146
|
+
#
|
147
|
+
# === Notes
|
148
|
+
#
|
149
|
+
# This method is called automatically when initializing the DObject class with a file parameter.
|
150
|
+
# In practice this method is rarely called by the user.
|
151
|
+
#
|
152
|
+
# === Parameters
|
153
|
+
#
|
154
|
+
# * <tt>string</tt> -- A string which specifies either the path of a DICOM file to be loaded, or a binary DICOM string to be parsed.
|
155
|
+
# * <tt>options</tt> -- A hash of parameters.
|
156
|
+
#
|
157
|
+
# === Options
|
158
|
+
#
|
159
|
+
# * <tt>:bin</tt> -- Boolean. If true, the string parameter will be interpreted as a binary DICOM string instead of a path string.
|
160
|
+
# * <tt>:syntax</tt> -- String. If a syntax string is specified, the DRead class will be forced to use this transfer syntax when decoding the file/binary string.
|
161
|
+
#
|
162
|
+
def read(string, options={})
|
163
|
+
raise ArgumentError, "Invalid argument. Expected String, got #{string.class}." unless string.is_a?(String)
|
164
|
+
# Clear any existing DObject tags, then read:
|
165
|
+
@tags = Hash.new
|
166
|
+
r = DRead.new(self, string, options)
|
167
|
+
# If reading failed, and no transfer syntax was detected, we will make another attempt at reading the file while forcing explicit (little endian) decoding.
|
168
|
+
# This will help for some rare cases where the DICOM file is saved (erroneously, Im sure) with explicit encoding without specifying the transfer syntax tag.
|
169
|
+
if !r.success and !exists?("0002,0010")
|
170
|
+
# Clear the existing DObject tags:
|
171
|
+
@tags = Hash.new
|
172
|
+
r_explicit = DRead.new(self, string, :bin => options[:bin], :syntax => EXPLICIT_LITTLE_ENDIAN)
|
173
|
+
# Only extract information from this new attempt if it was successful:
|
174
|
+
r = r_explicit if r_explicit.success
|
175
|
+
end
|
176
|
+
# Store the data to the instance variables if the readout was a success:
|
177
|
+
if r.success
|
178
|
+
@read_success = true
|
179
|
+
# Update instance variables based on the properties of the DICOM object:
|
180
|
+
@explicit = r.explicit
|
181
|
+
@file_endian = r.file_endian
|
182
|
+
@signature = r.signature
|
183
|
+
@stream.endian = @file_endian
|
184
|
+
else
|
185
|
+
@read_success = false
|
186
|
+
end
|
187
|
+
# If any messages has been recorded, send these to the message handling method:
|
188
|
+
add_msg(r.msg) if r.msg.length > 0
|
189
|
+
end
|
190
|
+
|
126
191
|
# Gathers key information about the DObject as well as some system data, and prints this information to the screen.
|
127
192
|
#
|
128
193
|
# This information includes properties like encoding, byte order, modality and various image properties.
|
129
194
|
#
|
130
195
|
#--
|
131
|
-
# FIXME: Perhaps this method should be split up in one or two separate methods
|
132
|
-
# and a third method for printing this to the screen.
|
196
|
+
# FIXME: Perhaps this method should be split up in one or two separate methods
|
197
|
+
# which just builds the information arrays, and a third method for printing this to the screen.
|
133
198
|
#
|
134
|
-
def
|
199
|
+
def summary
|
135
200
|
sys_info = Array.new
|
136
201
|
info = Array.new
|
137
202
|
# Version of Ruby DICOM used:
|
138
203
|
sys_info << "Ruby DICOM version: #{VERSION}"
|
139
204
|
# System endian:
|
140
|
-
|
141
|
-
cpu = "Big Endian"
|
142
|
-
else
|
143
|
-
cpu = "Little Endian"
|
144
|
-
end
|
205
|
+
cpu = (CPU_ENDIAN ? "Big Endian" : "Little Endian")
|
145
206
|
sys_info << "Byte Order (CPU): #{cpu}"
|
146
207
|
# File path/name:
|
147
208
|
info << "File: #{@file}"
|
148
209
|
# Modality:
|
149
|
-
|
150
|
-
if sop_class_uid
|
151
|
-
modality = LIBRARY.get_syntax_description(sop_class_uid.value) || "Unknown UID!"
|
152
|
-
else
|
153
|
-
modality = "SOP Class not specified!"
|
154
|
-
end
|
210
|
+
modality = (exists?("0008,0016") ? LIBRARY.get_syntax_description(self["0008,0016"].value) : "SOP Class unknown or not specified!")
|
155
211
|
info << "Modality: #{modality}"
|
156
212
|
# Meta header presence (Simply check for the presence of the transfer syntax data element), VR and byte order:
|
157
213
|
transfer_syntax = self["0002,0010"]
|
158
214
|
if transfer_syntax
|
159
215
|
syntax_validity, explicit, endian = LIBRARY.process_transfer_syntax(transfer_syntax.value)
|
160
216
|
if syntax_validity
|
161
|
-
meta_comment = ""
|
162
|
-
explicit_comment = ""
|
163
|
-
encoding_comment = ""
|
217
|
+
meta_comment, explicit_comment, encoding_comment = "", "", ""
|
164
218
|
else
|
165
219
|
meta_comment = " (But unknown/invalid transfer syntax: #{transfer_syntax})"
|
166
220
|
explicit_comment = " (Assumed)"
|
167
221
|
encoding_comment = " (Assumed)"
|
168
222
|
end
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
explicitness = "Implicit"
|
173
|
-
end
|
174
|
-
if endian
|
175
|
-
encoding = "Big Endian"
|
176
|
-
else
|
177
|
-
encoding = "Little Endian"
|
178
|
-
end
|
223
|
+
explicitness = (explicit ? "Explicit" : "Implicit")
|
224
|
+
encoding = (endian ? "Big Endian" : "Little Endian")
|
225
|
+
meta = "Yes#{meta_comment}"
|
179
226
|
else
|
180
227
|
meta = "No"
|
181
228
|
explicitness = (@explicit == true ? "Explicit" : "Implicit")
|
@@ -183,11 +230,9 @@ module DICOM
|
|
183
230
|
explicit_comment = " (Assumed)"
|
184
231
|
encoding_comment = " (Assumed)"
|
185
232
|
end
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
info << "Value Representation: #{explicit}"
|
190
|
-
info << "Byte Order (File): #{encoding}"
|
233
|
+
info << "Meta Header: #{meta}"
|
234
|
+
info << "Value Representation: #{explicitness}#{explicit_comment}"
|
235
|
+
info << "Byte Order (File): #{encoding}#{encoding_comment}"
|
191
236
|
# Pixel data:
|
192
237
|
pixels = self[PIXEL_TAG]
|
193
238
|
unless pixels
|
@@ -195,23 +240,23 @@ module DICOM
|
|
195
240
|
else
|
196
241
|
info << "Pixel Data: Yes"
|
197
242
|
# Image size:
|
198
|
-
cols = self["0028,0011"]
|
199
|
-
rows = self["0028,0010"]
|
200
|
-
info << "Image Size: #{cols
|
243
|
+
cols = (exists?("0028,0011") ? self["0028,0011"].value : "Columns missing")
|
244
|
+
rows = (exists?("0028,0010") ? self["0028,0010"].value : "Rows missing")
|
245
|
+
info << "Image Size: #{cols}*#{rows}"
|
201
246
|
# Frames:
|
202
|
-
frames =
|
203
|
-
|
247
|
+
frames = value("0028,0008") || "1"
|
248
|
+
unless frames == "1" or frames == 1
|
204
249
|
# Encapsulated or 3D pixel data:
|
205
|
-
if pixels.is_a?(
|
206
|
-
frames = frames.
|
250
|
+
if pixels.is_a?(Element)
|
251
|
+
frames = frames.to_s + " (3D Pixel Data)"
|
207
252
|
else
|
208
|
-
frames = frames.
|
253
|
+
frames = frames.to_s + " (Encapsulated Multiframe Image)"
|
209
254
|
end
|
210
255
|
end
|
211
256
|
info << "Number of frames: #{frames}"
|
212
257
|
# Color:
|
213
|
-
colors = self["0028,0004"]
|
214
|
-
info << "Photometry: #{colors
|
258
|
+
colors = (exists?("0028,0004") ? self["0028,0004"].value : "Not specified")
|
259
|
+
info << "Photometry: #{colors}"
|
215
260
|
# Compression:
|
216
261
|
if transfer_syntax
|
217
262
|
compression = LIBRARY.get_compression(transfer_syntax.value)
|
@@ -225,14 +270,13 @@ module DICOM
|
|
225
270
|
end
|
226
271
|
info << "Compression: #{compression}"
|
227
272
|
# Pixel bits (allocated):
|
228
|
-
bits = self["0028,0100"]
|
229
|
-
info << "Bits per Pixel: #{bits
|
273
|
+
bits = (exists?("0028,0100") ? self["0028,0100"].value : "Not specified")
|
274
|
+
info << "Bits per Pixel: #{bits}"
|
230
275
|
end
|
231
276
|
# Print the DICOM object's key properties:
|
232
277
|
separator = "-------------------------------------------"
|
233
|
-
puts "\n"
|
234
278
|
puts "System Properties:"
|
235
|
-
puts separator
|
279
|
+
puts separator + "\n"
|
236
280
|
puts sys_info
|
237
281
|
puts "\n"
|
238
282
|
puts "DICOM Object Properties:"
|
@@ -242,52 +286,6 @@ module DICOM
|
|
242
286
|
return info
|
243
287
|
end
|
244
288
|
|
245
|
-
# Prints information of interest related to the DICOM object.
|
246
|
-
# Calls the print() method of SuperParent as well as the information() method of DObject.
|
247
|
-
#
|
248
|
-
def print_all
|
249
|
-
puts ""
|
250
|
-
print(:value_max => 30)
|
251
|
-
information
|
252
|
-
end
|
253
|
-
|
254
|
-
# Returns a DICOM object by reading and parsing the specified file.
|
255
|
-
# This is accomplished by initializing the DRead class, which loads DICOM information to arrays.
|
256
|
-
#
|
257
|
-
# === Notes
|
258
|
-
#
|
259
|
-
# This method is called automatically when initializing the DObject class with a file parameter,
|
260
|
-
# and in practice should not be called by users.
|
261
|
-
#
|
262
|
-
#--
|
263
|
-
# FIXME: It should be considered whether this should be a private method.
|
264
|
-
#
|
265
|
-
def read(string, options={})
|
266
|
-
r = DRead.new(self, string, options)
|
267
|
-
# If reading failed, and no transfer syntax was detected, we will make another attempt at reading the file while forcing explicit (little endian) decoding.
|
268
|
-
# This will help for some rare cases where the DICOM file is saved (erroneously, Im sure) with explicit encoding without specifying the transfer syntax tag.
|
269
|
-
unless r.success or exists?("0002,0010")
|
270
|
-
# Clear the existing DObject tags:
|
271
|
-
@tags = Hash.new
|
272
|
-
r_explicit = DRead.new(self, string, :bin => options[:bin], :syntax => EXPLICIT_LITTLE_ENDIAN)
|
273
|
-
# Only extract information from this new attempt if it was successful:
|
274
|
-
r = r_explicit if r_explicit.success
|
275
|
-
end
|
276
|
-
# Store the data to the instance variables if the readout was a success:
|
277
|
-
if r.success
|
278
|
-
@read_success = true
|
279
|
-
# Update instance variables based on the properties of the DICOM object:
|
280
|
-
@explicit = r.explicit
|
281
|
-
@file_endian = r.file_endian
|
282
|
-
@signature = r.signature
|
283
|
-
@stream.endian = @file_endian
|
284
|
-
else
|
285
|
-
@read_success = false
|
286
|
-
end
|
287
|
-
# If any messages has been recorded, send these to the message handling method:
|
288
|
-
add_msg(r.msg) if r.msg.length > 0
|
289
|
-
end
|
290
|
-
|
291
289
|
# Returns the transfer syntax string of the DObject.
|
292
290
|
#
|
293
291
|
# If a transfer syntax has not been defined in the DObject, a default tansfer syntax is assumed and returned.
|
@@ -296,7 +294,7 @@ module DICOM
|
|
296
294
|
return value("0002,0010") || IMPLICIT_LITTLE_ENDIAN
|
297
295
|
end
|
298
296
|
|
299
|
-
# Changes the transfer syntax
|
297
|
+
# Changes the transfer syntax Element of the DObject instance, and performs re-encoding of all
|
300
298
|
# numerical values if a switch of endianness is implied.
|
301
299
|
#
|
302
300
|
# === Restrictions
|
@@ -309,28 +307,20 @@ module DICOM
|
|
309
307
|
# * <tt>new_syntax</tt> -- The new transfer syntax string which will be applied to the DObject.
|
310
308
|
#
|
311
309
|
def transfer_syntax=(new_syntax)
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
else
|
320
|
-
add(DataElement.new("0002,0010", new_syntax))
|
321
|
-
end
|
322
|
-
# Update our Stream instance with the new encoding:
|
323
|
-
@stream.endian = new_endian
|
324
|
-
# Determine if re-encoding is needed:
|
325
|
-
if old_endian != new_endian
|
326
|
-
# Re-encode all Data Elements with number values:
|
327
|
-
encode_children(old_endian)
|
328
|
-
else
|
329
|
-
add_msg("New transfer syntax #{new_syntax} does not change encoding: No re-encoding needed.")
|
330
|
-
end
|
310
|
+
valid_ts, new_explicit, new_endian = LIBRARY.process_transfer_syntax(new_syntax)
|
311
|
+
raise ArgumentError, "Invalid transfer syntax specified: #{new_syntax}" unless valid_ts
|
312
|
+
# Get the old transfer syntax and write the new one to the DICOM object:
|
313
|
+
old_syntax = transfer_syntax
|
314
|
+
valid_ts, old_explicit, old_endian = LIBRARY.process_transfer_syntax(old_syntax)
|
315
|
+
if exists?("0002,0010")
|
316
|
+
self["0002,0010"].value = new_syntax
|
331
317
|
else
|
332
|
-
|
318
|
+
add(Element.new("0002,0010", new_syntax))
|
333
319
|
end
|
320
|
+
# Update our Stream instance with the new encoding:
|
321
|
+
@stream.endian = new_endian
|
322
|
+
# If endianness is changed, re-encode elements (only elements depending on endianness will actually be re-encoded):
|
323
|
+
encode_children(old_endian) if old_endian != new_endian
|
334
324
|
end
|
335
325
|
|
336
326
|
# Passes the DObject to the DWrite class, which traverses the data element
|
@@ -350,6 +340,7 @@ module DICOM
|
|
350
340
|
# obj.write(path + "test.dcm")
|
351
341
|
#
|
352
342
|
def write(file_name, options={})
|
343
|
+
raise ArgumentError, "Invalid file_name. Expected String, got #{file_name.class}." unless file_name.is_a?(String)
|
353
344
|
insert_missing_meta unless options[:add_meta] == false
|
354
345
|
w = DWrite.new(self, transfer_syntax, file_name, options)
|
355
346
|
w.write
|
@@ -374,7 +365,7 @@ module DICOM
|
|
374
365
|
def add_msg(msg)
|
375
366
|
puts msg if @verbose
|
376
367
|
@errors << msg
|
377
|
-
@errors.flatten
|
368
|
+
@errors.flatten!
|
378
369
|
end
|
379
370
|
|
380
371
|
# Adds any missing meta group (0002,xxxx) data elements to the DICOM object,
|
@@ -382,23 +373,24 @@ module DICOM
|
|
382
373
|
#
|
383
374
|
def insert_missing_meta
|
384
375
|
# File Meta Information Version:
|
385
|
-
|
376
|
+
Element.new("0002,0001", [0,1], :parent => self) unless exists?("0002,0001")
|
386
377
|
# Media Storage SOP Class UID:
|
387
|
-
|
378
|
+
Element.new("0002,0002", value("0008,0016"), :parent => self) unless exists?("0002,0002")
|
388
379
|
# Media Storage SOP Instance UID:
|
389
|
-
|
380
|
+
Element.new("0002,0003", value("0008,0018"), :parent => self) unless exists?("0002,0003")
|
390
381
|
# Transfer Syntax UID:
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
382
|
+
Element.new("0002,0010", transfer_syntax, :parent => self) unless exists?("0002,0010")
|
383
|
+
if !exists?("0002,0012") and !exists?("0002,0013")
|
384
|
+
# Implementation Class UID:
|
385
|
+
Element.new("0002,0012", UID, :parent => self)
|
386
|
+
# Implementation Version Name:
|
387
|
+
Element.new("0002,0013", NAME, :parent => self)
|
388
|
+
end
|
396
389
|
# Source Application Entity Title:
|
397
|
-
|
398
|
-
# Group
|
399
|
-
# Remove old group length (if it exists) before creating a new one:
|
390
|
+
Element.new("0002,0016", DICOM.source_app_title, :parent => self) unless exists?("0002,0016")
|
391
|
+
# Group Length: Remove the old one (if it exists) before creating a new one.
|
400
392
|
remove("0002,0000")
|
401
|
-
|
393
|
+
Element.new("0002,0000", meta_group_length, :parent => self)
|
402
394
|
end
|
403
395
|
|
404
396
|
# Determines and returns the length of the meta group in the DObject instance.
|