dicom 0.7 → 0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +55 -0
- data/README +51 -29
- data/init.rb +1 -0
- data/lib/dicom.rb +35 -21
- data/lib/dicom/{Anonymizer.rb → anonymizer.rb} +178 -80
- data/lib/dicom/constants.rb +121 -0
- data/lib/dicom/d_client.rb +888 -0
- data/lib/dicom/d_library.rb +208 -0
- data/lib/dicom/d_object.rb +424 -0
- data/lib/dicom/d_read.rb +433 -0
- data/lib/dicom/d_server.rb +397 -0
- data/lib/dicom/d_write.rb +420 -0
- data/lib/dicom/data_element.rb +175 -0
- data/lib/dicom/{Dictionary.rb → dictionary.rb} +390 -398
- data/lib/dicom/elements.rb +82 -0
- data/lib/dicom/file_handler.rb +116 -0
- data/lib/dicom/item.rb +87 -0
- data/lib/dicom/{Link.rb → link.rb} +749 -388
- data/lib/dicom/ruby_extensions.rb +44 -35
- data/lib/dicom/sequence.rb +62 -0
- data/lib/dicom/stream.rb +493 -0
- data/lib/dicom/super_item.rb +696 -0
- data/lib/dicom/super_parent.rb +615 -0
- metadata +25 -18
- data/DOCUMENTATION +0 -469
- data/lib/dicom/DClient.rb +0 -584
- data/lib/dicom/DLibrary.rb +0 -194
- data/lib/dicom/DObject.rb +0 -1579
- data/lib/dicom/DRead.rb +0 -532
- data/lib/dicom/DServer.rb +0 -304
- data/lib/dicom/DWrite.rb +0 -410
- data/lib/dicom/FileHandler.rb +0 -50
- data/lib/dicom/Stream.rb +0 -354
@@ -0,0 +1,208 @@
|
|
1
|
+
# Copyright 2008-2010 Christoffer Lervag
|
2
|
+
|
3
|
+
module DICOM
|
4
|
+
|
5
|
+
# This class contains methods that interact with Ruby DICOM's dictionary.
|
6
|
+
#
|
7
|
+
class DLibrary
|
8
|
+
|
9
|
+
# A hash containing tags as key and an array as value, where the array contains data element vr and name.
|
10
|
+
attr_reader :tags
|
11
|
+
# A hash containing UIDs as key and an array as value, where the array contains name and type.
|
12
|
+
attr_reader :uid
|
13
|
+
|
14
|
+
# Creates a DLibrary instance.
|
15
|
+
#
|
16
|
+
def initialize
|
17
|
+
# Load the data elements hash, where the keys are tag strings, and values
|
18
|
+
# are two-element arrays [vr, name] (where vr itself is an array of 1-3 elements):
|
19
|
+
@tags = Dictionary.load_data_elements
|
20
|
+
# Load UID hash (DICOM unique identifiers), where the keys are UID strings,
|
21
|
+
# and values are two-element arrays [description, type]:
|
22
|
+
@uid = Dictionary.load_uid
|
23
|
+
end
|
24
|
+
|
25
|
+
# Checks whether a given string is a valid transfer syntax or not.
|
26
|
+
# Returns true if valid, false if not.
|
27
|
+
#
|
28
|
+
# === Parameters
|
29
|
+
#
|
30
|
+
# * <tt>uid</tt> -- String. A DICOM UID value which will be matched against known transfer syntaxes.
|
31
|
+
#
|
32
|
+
def check_ts_validity(uid)
|
33
|
+
result = false
|
34
|
+
value = @uid[uid]
|
35
|
+
if value
|
36
|
+
result = true if value[1] == "Transfer Syntax"
|
37
|
+
end
|
38
|
+
return result
|
39
|
+
end
|
40
|
+
|
41
|
+
# Extracts, and returns, all transfer syntaxes and SOP Classes from the dictionary,
|
42
|
+
# in the form of a transfer syntax hash and a sop class hash.
|
43
|
+
#
|
44
|
+
# Both hashes have UIDs as keys and their descriptions as values.
|
45
|
+
#
|
46
|
+
def extract_transfer_syntaxes_and_sop_classes
|
47
|
+
transfer_syntaxes = Hash.new
|
48
|
+
sop_classes = Hash.new
|
49
|
+
@uid.each_pair do |key, value|
|
50
|
+
if value[1] == "Transfer Syntax"
|
51
|
+
transfer_syntaxes[key] = value[0]
|
52
|
+
elsif value[1] == "SOP Class"
|
53
|
+
sop_classes[key] = value[0]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
return transfer_syntaxes, sop_classes
|
57
|
+
end
|
58
|
+
|
59
|
+
# Checks if the specified transfer syntax implies the presence of pixel compression.
|
60
|
+
# Returns true if pixel compression is implied, false if not.
|
61
|
+
#
|
62
|
+
# === Parameters
|
63
|
+
#
|
64
|
+
# * <tt>uid</tt> -- String. A DICOM UID value.
|
65
|
+
#
|
66
|
+
def get_compression(uid)
|
67
|
+
result = false
|
68
|
+
if uid
|
69
|
+
value = @uid[uid]
|
70
|
+
if value
|
71
|
+
result = true if value[1] == "Transfer Syntax" and not value[0].include?("Endian")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
return result
|
75
|
+
end
|
76
|
+
|
77
|
+
# Determines, and returns, the name and vr of the data element which the specified tag belongs to.
|
78
|
+
# Values are retrieved from the Ruby DICOM dictionary if a match is found.
|
79
|
+
#
|
80
|
+
# === Notes
|
81
|
+
#
|
82
|
+
# * Private tags will have their names listed as "Private".
|
83
|
+
# * Non-private tags that are not found in the dictionary will be listed as "Unknown".
|
84
|
+
#
|
85
|
+
# === Parameters
|
86
|
+
#
|
87
|
+
# * <tt>tag</tt> -- String. A data element tag.
|
88
|
+
#
|
89
|
+
def get_name_vr(tag)
|
90
|
+
if tag.private? and tag.element != GROUP_LENGTH
|
91
|
+
name = "Private"
|
92
|
+
vr = "UN"
|
93
|
+
else
|
94
|
+
# Check the dictionary:
|
95
|
+
values = @tags[tag]
|
96
|
+
if values
|
97
|
+
name = values[1]
|
98
|
+
vr = values[0][0]
|
99
|
+
else
|
100
|
+
# For the tags that are not recognised, we need to do some additional testing to see if it is one of the special cases:
|
101
|
+
if tag.element == GROUP_LENGTH
|
102
|
+
# Group length:
|
103
|
+
name = "Group Length"
|
104
|
+
vr = "UL"
|
105
|
+
elsif tag[0..6] == "0020,31"
|
106
|
+
# Source Image ID's (Retired):
|
107
|
+
values = @tags["0020,31xx"]
|
108
|
+
name = values[1]
|
109
|
+
vr = values[0][0]
|
110
|
+
elsif tag.group == "1000" and tag.element =~ /\A\h{3}[0-5]\z/
|
111
|
+
# Group 1000,xxx[0-5] (Retired):
|
112
|
+
new_tag = tag.group + "xx" + tag.element[3..3]
|
113
|
+
values = @tags[new_tag]
|
114
|
+
elsif tag.group == "1010"
|
115
|
+
# Group 1010,xxxx (Retired):
|
116
|
+
new_tag = tag.group + "xxxx"
|
117
|
+
values = @tags[new_tag]
|
118
|
+
elsif tag[0..1] == "50" or tag[0..1] == "60"
|
119
|
+
# Group 50xx (Retired) and 60xx:
|
120
|
+
new_tag = tag[0..1]+"xx"+tag[4..8]
|
121
|
+
values = @tags[new_tag]
|
122
|
+
if values
|
123
|
+
name = values[1]
|
124
|
+
vr = values[0][0]
|
125
|
+
end
|
126
|
+
elsif tag[0..1] == "7F" and tag[5..6] == "00"
|
127
|
+
# Group 7Fxx,00[10,11,20,30,40] (Retired):
|
128
|
+
new_tag = tag[0..1]+"xx"+tag[4..8]
|
129
|
+
values = @tags[new_tag]
|
130
|
+
if values
|
131
|
+
name = values[1]
|
132
|
+
vr = values[0][0]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
# If none of the above checks yielded a result, the tag is unknown:
|
136
|
+
unless name
|
137
|
+
name = "Unknown"
|
138
|
+
vr = "UN"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
return name, vr
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the tag that matches the supplied data element name, by searching the Ruby DICOM dictionary.
|
146
|
+
# Returns nil if no match is found.
|
147
|
+
#
|
148
|
+
# === Parameters
|
149
|
+
#
|
150
|
+
# * <tt>name</tt> -- String. A data element name.
|
151
|
+
#
|
152
|
+
def get_tag(name)
|
153
|
+
tag = nil
|
154
|
+
@tags.each_pair do |key, value|
|
155
|
+
tag = key if value[1] == name
|
156
|
+
end
|
157
|
+
return tag
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns the description/name of a specified UID (i.e. a transfer syntax or SOP class).
|
161
|
+
# Returns nil if no match is found
|
162
|
+
#
|
163
|
+
# === Parameters
|
164
|
+
#
|
165
|
+
# * <tt>uid</tt> -- String. A DICOM UID value.
|
166
|
+
#
|
167
|
+
def get_syntax_description(uid)
|
168
|
+
name = nil
|
169
|
+
value = @uid[uid]
|
170
|
+
name = value[0] if value
|
171
|
+
return name
|
172
|
+
end
|
173
|
+
|
174
|
+
# Checks the validity of the specified transfer syntax UID and determines the
|
175
|
+
# encoding settings (explicitness & endianness) associated with this value.
|
176
|
+
# The results are returned as 3 booleans: validity, explicitness & endianness.
|
177
|
+
#
|
178
|
+
# === Parameters
|
179
|
+
#
|
180
|
+
# * <tt>uid</tt> -- String. A DICOM UID value.
|
181
|
+
#
|
182
|
+
def process_transfer_syntax(uid)
|
183
|
+
valid = check_ts_validity(uid)
|
184
|
+
case uid
|
185
|
+
# Some variations with uncompressed pixel data:
|
186
|
+
when IMPLICIT_LITTLE_ENDIAN
|
187
|
+
explicit = false
|
188
|
+
endian = false
|
189
|
+
when EXPLICIT_LITTLE_ENDIAN
|
190
|
+
explicit = true
|
191
|
+
endian = false
|
192
|
+
when "1.2.840.10008.1.2.1.99" # Deflated Explicit VR, Little Endian
|
193
|
+
# Note: Has this transfer syntax been tested yet?
|
194
|
+
explicit = true
|
195
|
+
endian = false
|
196
|
+
when EXPLICIT_BIG_ENDIAN
|
197
|
+
explicit = true
|
198
|
+
endian = true
|
199
|
+
else
|
200
|
+
# For everything else, assume compressed pixel data, with Explicit VR, Little Endian:
|
201
|
+
explicit = true
|
202
|
+
endian = false
|
203
|
+
end
|
204
|
+
return valid, explicit, endian
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,424 @@
|
|
1
|
+
# Copyright 2008-2010 Christoffer Lervag
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
#
|
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
|
+
module DICOM
|
31
|
+
|
32
|
+
# The DObject class is the main class for interacting with the DICOM object.
|
33
|
+
# Reading from and writing to files is executed from instances of this class.
|
34
|
+
#
|
35
|
+
# === Inheritance
|
36
|
+
#
|
37
|
+
# As the DObject class inherits from the SuperItem class, which itself inherits from the SuperParent class,
|
38
|
+
# all SuperItem and SuperParent methods are also available to instances of DObject.
|
39
|
+
#
|
40
|
+
class DObject < SuperItem
|
41
|
+
|
42
|
+
# An array which contain any notices/warnings/errors that have been recorded for the DObject instance.
|
43
|
+
attr_reader :errors
|
44
|
+
# A boolean set as false. This attribute is included to provide consistency with other object types for the internal methods which use it.
|
45
|
+
attr_reader :parent
|
46
|
+
# A boolean which is set as true if a DICOM file has been successfully read & parsed from a file (or binary string).
|
47
|
+
attr_reader :read_success
|
48
|
+
# The Stream instance associated with this DObject instance (this attribute is mostly used internally).
|
49
|
+
attr_reader :stream
|
50
|
+
# A boolean which is set as true if a DObject instance has been successfully written to file (or successfully encoded).
|
51
|
+
attr_reader :write_success
|
52
|
+
|
53
|
+
# Creates a DObject instance (DObject is an abbreviation for "DICOM object").
|
54
|
+
#
|
55
|
+
# The DObject instance holds references to the different types of objects (DataElement, Item, Sequence)
|
56
|
+
# that makes up a DICOM object. A DObject is typically buildt by reading and parsing a file or a
|
57
|
+
# binary string, but can also be buildt from an empty state by the user.
|
58
|
+
#
|
59
|
+
# === Parameters
|
60
|
+
#
|
61
|
+
# * <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. The parameter defaults to nil, in which case an empty DObject instance is created.
|
62
|
+
# * <tt>options</tt> -- A hash of parameters.
|
63
|
+
#
|
64
|
+
# === Options
|
65
|
+
#
|
66
|
+
# * <tt>:bin</tt> -- Boolean. If set to true, string parameter will be interpreted as a binary DICOM string, and not a path string, which is the default behaviour.
|
67
|
+
# * <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
|
+
# * <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
|
+
#
|
70
|
+
# === Examples
|
71
|
+
#
|
72
|
+
# # Load a DICOM file:
|
73
|
+
# require 'dicom'
|
74
|
+
# obj = DICOM::DObject.new("test.dcm")
|
75
|
+
# # Read a DICOM file that has already been loaded into memory in a binary string (with a known transfer syntax):
|
76
|
+
# obj = DICOM::DObject.new(binary_string, :bin => true, :syntax => string_transfer_syntax)
|
77
|
+
# # Create an empty DICOM object & choose non-verbose behaviour:
|
78
|
+
# obj = DICOM::DObject.new(nil, :verbose => false)
|
79
|
+
#
|
80
|
+
def initialize(string=nil, options={})
|
81
|
+
# Process option values, setting defaults for the ones that are not specified:
|
82
|
+
# Default verbosity is true if verbosity hasn't been specified (nil):
|
83
|
+
@verbose = (options[:verbose] == false ? false : true)
|
84
|
+
# Initialization of variables that DObject share with other parent elements:
|
85
|
+
initialize_parent
|
86
|
+
# Messages (errors, warnings or notices) will be accumulated in an array:
|
87
|
+
@errors = Array.new
|
88
|
+
# Structural information (default values):
|
89
|
+
@explicit = true
|
90
|
+
@file_endian = false
|
91
|
+
# Control variables:
|
92
|
+
@read_success = false
|
93
|
+
# Initialize a Stream instance which is used for encoding/decoding:
|
94
|
+
@stream = Stream.new(nil, @file_endian)
|
95
|
+
# The DObject instance is the top of the hierarchy and unlike other elements it has no parent:
|
96
|
+
@parent = nil
|
97
|
+
# For convenience, call the read method if a string has been supplied:
|
98
|
+
if string.is_a?(String) and string != ""
|
99
|
+
@file = string unless options[:bin]
|
100
|
+
read(string, options)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Encodes the DICOM object into a series of binary string segments with a specified maximum length.
|
105
|
+
#
|
106
|
+
# Returns the encoded binary strings in an array.
|
107
|
+
#
|
108
|
+
# === Parameters
|
109
|
+
#
|
110
|
+
# * <tt>max_size</tt> -- An integer (Fixnum) which specifies the maximum allowed size of the binary data strings which will be encoded.
|
111
|
+
#
|
112
|
+
# === Examples
|
113
|
+
#
|
114
|
+
# encoded_strings = obj.encode_segments(16384)
|
115
|
+
#
|
116
|
+
def encode_segments(max_size)
|
117
|
+
w = DWrite.new(self, transfer_syntax, file_name=nil)
|
118
|
+
w.encode_segments(max_size)
|
119
|
+
# Write process succesful?
|
120
|
+
@write_success = w.success
|
121
|
+
# If any messages has been recorded, send these to the message handling method:
|
122
|
+
add_msg(w.msg) if w.msg.length > 0
|
123
|
+
return w.segments
|
124
|
+
end
|
125
|
+
|
126
|
+
# Gathers key information about the DObject as well as some system data, and prints this information to the screen.
|
127
|
+
#
|
128
|
+
# This information includes properties like encoding, byte order, modality and various image properties.
|
129
|
+
#
|
130
|
+
#--
|
131
|
+
# FIXME: Perhaps this method should be split up in one or two separate methods which just builds the information arrays,
|
132
|
+
# and a third method for printing this to the screen.
|
133
|
+
#
|
134
|
+
def information
|
135
|
+
sys_info = Array.new
|
136
|
+
info = Array.new
|
137
|
+
# Version of Ruby DICOM used:
|
138
|
+
sys_info << "Ruby DICOM version: #{VERSION}"
|
139
|
+
# System endian:
|
140
|
+
if CPU_ENDIAN
|
141
|
+
cpu = "Big Endian"
|
142
|
+
else
|
143
|
+
cpu = "Little Endian"
|
144
|
+
end
|
145
|
+
sys_info << "Byte Order (CPU): #{cpu}"
|
146
|
+
# File path/name:
|
147
|
+
info << "File: #{@file}"
|
148
|
+
# Modality:
|
149
|
+
sop_class_uid = self["0008,0016"]
|
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
|
155
|
+
info << "Modality: #{modality}"
|
156
|
+
# Meta header presence (Simply check for the presence of the transfer syntax data element), VR and byte order:
|
157
|
+
transfer_syntax = self["0002,0010"]
|
158
|
+
if transfer_syntax
|
159
|
+
syntax_validity, explicit, endian = LIBRARY.process_transfer_syntax(transfer_syntax.value)
|
160
|
+
if syntax_validity
|
161
|
+
meta_comment = ""
|
162
|
+
explicit_comment = ""
|
163
|
+
encoding_comment = ""
|
164
|
+
else
|
165
|
+
meta_comment = " (But unknown/invalid transfer syntax: #{transfer_syntax})"
|
166
|
+
explicit_comment = " (Assumed)"
|
167
|
+
encoding_comment = " (Assumed)"
|
168
|
+
end
|
169
|
+
if explicit
|
170
|
+
explicitness = "Explicit"
|
171
|
+
else
|
172
|
+
explicitness = "Implicit"
|
173
|
+
end
|
174
|
+
if endian
|
175
|
+
encoding = "Big Endian"
|
176
|
+
else
|
177
|
+
encoding = "Little Endian"
|
178
|
+
end
|
179
|
+
else
|
180
|
+
meta = "No"
|
181
|
+
explicitness = (@explicit == true ? "Explicit" : "Implicit")
|
182
|
+
encoding = (@file_endian == true ? "Big Endian" : "Little Endian")
|
183
|
+
explicit_comment = " (Assumed)"
|
184
|
+
encoding_comment = " (Assumed)"
|
185
|
+
end
|
186
|
+
meta = "Yes#{meta_comment}"
|
187
|
+
explicit = "#{explicitness}#{explicit_comment}"
|
188
|
+
encoding = "#{encoding}#{encoding_comment}"
|
189
|
+
info << "Value Representation: #{explicit}"
|
190
|
+
info << "Byte Order (File): #{encoding}"
|
191
|
+
# Pixel data:
|
192
|
+
pixels = self[PIXEL_TAG]
|
193
|
+
unless pixels
|
194
|
+
info << "Pixel Data: No"
|
195
|
+
else
|
196
|
+
info << "Pixel Data: Yes"
|
197
|
+
# Image size:
|
198
|
+
cols = self["0028,0011"] || "Columns missing"
|
199
|
+
rows = self["0028,0010"] || "Rows missing"
|
200
|
+
info << "Image Size: #{cols.value}*#{rows.value}"
|
201
|
+
# Frames:
|
202
|
+
frames = self["0028,0008"] || "1"
|
203
|
+
if frames != "1"
|
204
|
+
# Encapsulated or 3D pixel data:
|
205
|
+
if pixels.is_a?(DataElement)
|
206
|
+
frames = frames.value + " (3D Pixel Data)"
|
207
|
+
else
|
208
|
+
frames = frames.value + " (Encapsulated Multiframe Image)"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
info << "Number of frames: #{frames}"
|
212
|
+
# Color:
|
213
|
+
colors = self["0028,0004"] || "Not specified"
|
214
|
+
info << "Photometry: #{colors.value}"
|
215
|
+
# Compression:
|
216
|
+
if transfer_syntax
|
217
|
+
compression = LIBRARY.get_compression(transfer_syntax.value)
|
218
|
+
if compression
|
219
|
+
compression = LIBRARY.get_syntax_description(transfer_syntax.value) || "Unknown UID!"
|
220
|
+
else
|
221
|
+
compression = "No"
|
222
|
+
end
|
223
|
+
else
|
224
|
+
compression = "No (Assumed)"
|
225
|
+
end
|
226
|
+
info << "Compression: #{compression}"
|
227
|
+
# Pixel bits (allocated):
|
228
|
+
bits = self["0028,0100"] || "Not specified"
|
229
|
+
info << "Bits per Pixel: #{bits.value}"
|
230
|
+
end
|
231
|
+
# Print the DICOM object's key properties:
|
232
|
+
separator = "-------------------------------------------"
|
233
|
+
puts "\n"
|
234
|
+
puts "System Properties:"
|
235
|
+
puts separator
|
236
|
+
puts sys_info
|
237
|
+
puts "\n"
|
238
|
+
puts "DICOM Object Properties:"
|
239
|
+
puts separator
|
240
|
+
puts info
|
241
|
+
puts separator
|
242
|
+
return info
|
243
|
+
end
|
244
|
+
|
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
|
+
# Returns the transfer syntax string of the DObject.
|
292
|
+
#
|
293
|
+
# If a transfer syntax has not been defined in the DObject, a default tansfer syntax is assumed and returned.
|
294
|
+
#
|
295
|
+
def transfer_syntax
|
296
|
+
return value("0002,0010") || IMPLICIT_LITTLE_ENDIAN
|
297
|
+
end
|
298
|
+
|
299
|
+
# Changes the transfer syntax DataElement of the DObject instance, and performs re-encoding of all
|
300
|
+
# numerical values if a switch of endianness is implied.
|
301
|
+
#
|
302
|
+
# === Restrictions
|
303
|
+
#
|
304
|
+
# This method does not change the compressed state of the pixel data element. Changing the transfer syntax between
|
305
|
+
# an uncompressed and compressed state will NOT change the pixel data accordingly (this must be taken care of manually).
|
306
|
+
#
|
307
|
+
# === Parameters
|
308
|
+
#
|
309
|
+
# * <tt>new_syntax</tt> -- The new transfer syntax string which will be applied to the DObject.
|
310
|
+
#
|
311
|
+
def transfer_syntax=(new_syntax)
|
312
|
+
valid, new_explicit, new_endian = LIBRARY.process_transfer_syntax(new_syntax)
|
313
|
+
if valid
|
314
|
+
# Get the old transfer syntax and write the new one to the DICOM object:
|
315
|
+
old_syntax = transfer_syntax
|
316
|
+
valid, old_explicit, old_endian = LIBRARY.process_transfer_syntax(old_syntax)
|
317
|
+
if exists?("0002,0010")
|
318
|
+
self["0002,0010"].value = new_syntax
|
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
|
331
|
+
else
|
332
|
+
raise "Invalid transfer syntax specified: #{new_syntax}"
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Passes the DObject to the DWrite class, which traverses the data element
|
337
|
+
# structure and encodes a proper DICOM binary string, which is finally written to the specified file.
|
338
|
+
#
|
339
|
+
# === Parameters
|
340
|
+
#
|
341
|
+
# * <tt>file_name</tt> -- A string which identifies the path & name of the DICOM file which is to be written to disk.
|
342
|
+
# * <tt>options</tt> -- A hash of parameters.
|
343
|
+
#
|
344
|
+
# === Options
|
345
|
+
#
|
346
|
+
# * <tt>:add_meta</tt> -- Boolean. If set to false, no manipulation of the DICOM object's meta group will be performed before the DObject is written to file.
|
347
|
+
#
|
348
|
+
# === Examples
|
349
|
+
#
|
350
|
+
# obj.write(path + "test.dcm")
|
351
|
+
#
|
352
|
+
def write(file_name, options={})
|
353
|
+
insert_missing_meta unless options[:add_meta] == false
|
354
|
+
w = DWrite.new(self, transfer_syntax, file_name, options)
|
355
|
+
w.write
|
356
|
+
# Write process succesful?
|
357
|
+
@write_success = w.success
|
358
|
+
# If any messages has been recorded, send these to the message handling method:
|
359
|
+
add_msg(w.msg) if w.msg.length > 0
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
# Following methods are private:
|
364
|
+
private
|
365
|
+
|
366
|
+
|
367
|
+
# Adds one or more status messages to the instance array holding messages, and if the verbose instance variable
|
368
|
+
# is true, the status message(s) are printed to the screen as well.
|
369
|
+
#
|
370
|
+
# === Parameters
|
371
|
+
#
|
372
|
+
# * <tt>msg</tt> -- Status message string, or an array containing one or more status message strings.
|
373
|
+
#
|
374
|
+
def add_msg(msg)
|
375
|
+
puts msg if @verbose
|
376
|
+
@errors << msg
|
377
|
+
@errors.flatten
|
378
|
+
end
|
379
|
+
|
380
|
+
# Adds any missing meta group (0002,xxxx) data elements to the DICOM object,
|
381
|
+
# to ensure that a valid DICOM object will be written to file.
|
382
|
+
#
|
383
|
+
def insert_missing_meta
|
384
|
+
# File Meta Information Version:
|
385
|
+
DataElement.new("0002,0001", [0,1], :parent => self) unless exists?("0002,0001")
|
386
|
+
# Media Storage SOP Class UID:
|
387
|
+
DataElement.new("0002,0002", value("0008,0016"), :parent => self) unless exists?("0002,0002")
|
388
|
+
# Media Storage SOP Instance UID:
|
389
|
+
DataElement.new("0002,0003", value("0008,0018"), :parent => self) unless exists?("0002,0003")
|
390
|
+
# Transfer Syntax UID:
|
391
|
+
DataElement.new("0002,0010", transfer_syntax, :parent => self) unless exists?("0002,0010")
|
392
|
+
# Implementation Class UID:
|
393
|
+
DataElement.new("0002,0012", UID, :parent => self) unless exists?("0002,0012")
|
394
|
+
# Implementation Version Name:
|
395
|
+
DataElement.new("0002,0013", NAME, :parent => self) unless exists?("0002,0013")
|
396
|
+
# Source Application Entity Title:
|
397
|
+
DataElement.new("0002,0016", SOURCE_APP_TITLE, :parent => self) unless exists?("0002,0016")
|
398
|
+
# Group length:
|
399
|
+
# Remove old group length (if it exists) before creating a new one:
|
400
|
+
remove("0002,0000")
|
401
|
+
DataElement.new("0002,0000", meta_group_length, :parent => self)
|
402
|
+
end
|
403
|
+
|
404
|
+
# Determines and returns the length of the meta group in the DObject instance.
|
405
|
+
#
|
406
|
+
def meta_group_length
|
407
|
+
group_length = 0
|
408
|
+
meta_elements = group(META_GROUP)
|
409
|
+
tag = 4
|
410
|
+
vr = 2
|
411
|
+
meta_elements.each do |element|
|
412
|
+
case element.vr
|
413
|
+
when "OB","OW","OF","SQ","UN","UT"
|
414
|
+
length = 6
|
415
|
+
else
|
416
|
+
length = 2
|
417
|
+
end
|
418
|
+
group_length += tag + vr + length + element.bin.length
|
419
|
+
end
|
420
|
+
return group_length
|
421
|
+
end
|
422
|
+
|
423
|
+
end
|
424
|
+
end
|