dicom 0.8 → 0.9
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.
- 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.
|