dicom 0.5 → 0.6
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 +20 -4
- data/DOCUMENTATION +171 -1
- data/README +11 -3
- data/lib/DClient.rb +579 -0
- data/lib/DLibrary.rb +99 -75
- data/lib/DObject.rb +213 -262
- data/lib/DRead.rb +229 -300
- data/lib/DServer.rb +290 -0
- data/lib/DWrite.rb +218 -234
- data/lib/Dictionary.rb +2859 -2860
- data/lib/Link.rb +1079 -0
- data/lib/Stream.rb +351 -0
- data/lib/dicom.rb +7 -2
- data/lib/ruby_extensions.rb +11 -0
- metadata +10 -6
data/lib/DLibrary.rb
CHANGED
@@ -4,58 +4,56 @@ module DICOM
|
|
4
4
|
# Class which holds the methods that interact with the DICOM dictionary.
|
5
5
|
class DLibrary
|
6
6
|
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :tags, :uid
|
8
8
|
|
9
9
|
# Initialize the DRead instance.
|
10
|
-
def initialize
|
11
|
-
# Dictionary content will be stored in
|
10
|
+
def initialize
|
11
|
+
# Dictionary content will be stored in a number of hash objects.
|
12
12
|
# Load the dictionary:
|
13
|
-
|
13
|
+
dic = Dictionary.new
|
14
14
|
# Data elements:
|
15
|
-
|
16
|
-
@
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@uid_name = uid[1]
|
27
|
-
@uid_type = uid[2]
|
15
|
+
# Value of this hash is a two-element array [vr, name] (where vr itself is an array of 1-3 elements)
|
16
|
+
@tags = dic.load_data_elements
|
17
|
+
# UID (DICOM unique identifiers):
|
18
|
+
# Value of this hash is a two-element array [description, type]
|
19
|
+
@uid = dic.load_uid
|
20
|
+
# Photometric Interpretation: (not in use yet)
|
21
|
+
#@image_types = dic.load_image_types
|
22
|
+
# Value representation library: (not in use yet)
|
23
|
+
#@vr = dic.load_vr
|
24
|
+
# Frame of reference library: (not in use yet)
|
25
|
+
#@frame_of_ref = dic.load_frame_of_ref
|
28
26
|
end
|
29
27
|
|
30
28
|
|
31
|
-
# Returns data element name and value representation from
|
29
|
+
# Returns data element name and value representation from the dictionary if the data element
|
30
|
+
# is recognized, else it returns "Unknown Name" and "UN".
|
32
31
|
def get_name_vr(tag)
|
33
|
-
|
34
|
-
if
|
35
|
-
name =
|
36
|
-
vr =
|
32
|
+
values = @tags[tag]
|
33
|
+
if values != nil
|
34
|
+
name = values[1]
|
35
|
+
vr = values[0][0]
|
37
36
|
else
|
38
37
|
# For the tags that are not recognised, we need to do some additional testing to see if it is one of the special cases:
|
39
38
|
# Split tag in group and element:
|
40
39
|
group = tag[0..3]
|
41
40
|
element = tag[5..8]
|
42
|
-
# Check for group length:
|
43
41
|
if element == "0000"
|
42
|
+
# Group length:
|
44
43
|
name = "Group Length"
|
45
44
|
vr = "UL"
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
vr = @de_vr[pos][0]
|
45
|
+
elsif tag[0..6] == "0020,31"
|
46
|
+
# Source Image ID's: (Retired)
|
47
|
+
values = @tags["0020,31xx"]
|
48
|
+
name = values[1]
|
49
|
+
vr = values[0][0]
|
50
|
+
elsif tag[0..1] == "50" or tag[0..1] == "60"
|
51
|
+
# Group 50xx (retired) and 60xx:
|
52
|
+
new_tag = tag[0..1]+"xx"+tag[4..8]
|
53
|
+
values = @tags[new_tag]
|
54
|
+
if values != nil
|
55
|
+
name = values[1]
|
56
|
+
vr = values[0][0]
|
59
57
|
end
|
60
58
|
end
|
61
59
|
# If none of the above checks yielded a result, the tag is unknown:
|
@@ -70,21 +68,23 @@ module DICOM
|
|
70
68
|
|
71
69
|
# Returns the tag that matches the supplied data element name,
|
72
70
|
# or if a tag is supplied, return that tag.
|
73
|
-
|
71
|
+
# (This method may be considered for removal: Does the usefulnes of being able to create a tag by Name,
|
72
|
+
# outweigh the performance impact of having this method?)
|
73
|
+
def get_tag(name)
|
74
74
|
tag = false
|
75
75
|
# The supplied value should be a string:
|
76
|
-
if
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
# This is a tag.
|
81
|
-
# (Here it is possible to have some further logic to check the validity of the string as a tag.)
|
82
|
-
tag = value
|
76
|
+
if name.is_a?(String)
|
77
|
+
if name.is_a_tag?
|
78
|
+
# This is a tag:
|
79
|
+
tag = name
|
83
80
|
else
|
84
81
|
# We have presumably been dealt a name. Search the dictionary to see if we can identify
|
85
|
-
#
|
86
|
-
|
87
|
-
|
82
|
+
# this name and return its corresponding tag:
|
83
|
+
@tags.each_pair do |key, value|
|
84
|
+
if value[1] == name
|
85
|
+
tag = key
|
86
|
+
end
|
87
|
+
end
|
88
88
|
end
|
89
89
|
end
|
90
90
|
return tag
|
@@ -92,11 +92,11 @@ module DICOM
|
|
92
92
|
|
93
93
|
|
94
94
|
# Checks whether a given string is a valid transfer syntax or not.
|
95
|
-
def check_ts_validity(
|
95
|
+
def check_ts_validity(uid)
|
96
96
|
result = false
|
97
|
-
|
98
|
-
if
|
99
|
-
if
|
97
|
+
value = @uid[uid.rstrip]
|
98
|
+
if value != nil
|
99
|
+
if value[1] == "Transfer Syntax"
|
100
100
|
# Proved valid:
|
101
101
|
result = true
|
102
102
|
end
|
@@ -105,13 +105,12 @@ module DICOM
|
|
105
105
|
end
|
106
106
|
|
107
107
|
|
108
|
-
# Returns the name corresponding to a given UID.
|
109
|
-
def get_uid(
|
110
|
-
|
111
|
-
pos = @uid_value.index(value)
|
108
|
+
# Returns the name/description corresponding to a given UID.
|
109
|
+
def get_uid(uid)
|
110
|
+
value = @uid[uid.rstrip]
|
112
111
|
# Fetch the name of this UID:
|
113
|
-
if
|
114
|
-
name =
|
112
|
+
if value != nil
|
113
|
+
name = value[0]
|
115
114
|
else
|
116
115
|
name = "Unknown UID!"
|
117
116
|
end
|
@@ -120,29 +119,54 @@ module DICOM
|
|
120
119
|
|
121
120
|
|
122
121
|
# Checks if the supplied transfer syntax indicates the presence of pixel compression or not.
|
123
|
-
def get_compression(
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
122
|
+
def get_compression(uid)
|
123
|
+
result = false
|
124
|
+
if uid
|
125
|
+
value = @uid[uid.rstrip]
|
126
|
+
if value != nil
|
127
|
+
if value[1] == "Transfer Syntax" and not value[0].include?("Endian")
|
128
|
+
# It seems we have compression:
|
129
|
+
res = true
|
130
|
+
end
|
131
131
|
end
|
132
132
|
end
|
133
|
-
return
|
133
|
+
return result
|
134
134
|
end
|
135
135
|
|
136
136
|
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
137
|
+
# Checks the Transfer Syntax UID and return the encoding settings associated with this value.
|
138
|
+
def process_transfer_syntax(value)
|
139
|
+
valid = check_ts_validity(value)
|
140
|
+
case value
|
141
|
+
# Some variations with uncompressed pixel data:
|
142
|
+
when "1.2.840.10008.1.2"
|
143
|
+
# Implicit VR, Little Endian
|
144
|
+
explicit = false
|
145
|
+
endian = false
|
146
|
+
when "1.2.840.10008.1.2.1"
|
147
|
+
# Explicit VR, Little Endian
|
148
|
+
explicit = true
|
149
|
+
endian = false
|
150
|
+
when "1.2.840.10008.1.2.1.99"
|
151
|
+
# Deflated Explicit VR, Little Endian
|
152
|
+
#@msg += ["Warning: Transfer syntax 'Deflated Explicit VR, Little Endian' is untested. Unknown if this is handled correctly!"]
|
153
|
+
explicit = true
|
154
|
+
endian = false
|
155
|
+
when "1.2.840.10008.1.2.2"
|
156
|
+
# Explicit VR, Big Endian
|
157
|
+
explicit = true
|
158
|
+
endian = true
|
159
|
+
else
|
160
|
+
# For everything else, assume compressed pixel data, with Explicit VR, Little Endian:
|
161
|
+
explicit = true
|
162
|
+
endian = false
|
163
|
+
end
|
164
|
+
return [valid, explicit, endian]
|
144
165
|
end
|
145
166
|
|
146
167
|
|
147
|
-
|
148
|
-
|
168
|
+
# Following methods are private.
|
169
|
+
#private
|
170
|
+
|
171
|
+
end # of class
|
172
|
+
end # of module
|
data/lib/DObject.rb
CHANGED
@@ -16,10 +16,12 @@
|
|
16
16
|
#--------------------------------------------------------------------------------------------------
|
17
17
|
|
18
18
|
# TODO:
|
19
|
+
# -Improve the retrieve file network functionality
|
20
|
+
# -Make the networking more intelligent in its handling of (unexpected) messages
|
19
21
|
# -Support for writing complex (hierarchical) DICOM files (basic write support is featured).
|
20
22
|
# -Full support for compressed image data.
|
21
23
|
# -Read 12 bit image data correctly.
|
22
|
-
# -Support for color image data to get_image_narray
|
24
|
+
# -Support for color image data to get_image_narray and get_image_magick.
|
23
25
|
# -Complete support for Big endian (basic support is already featured).
|
24
26
|
# -Complete support for multiple frame image data to NArray and RMagick objects (partial support already featured).
|
25
27
|
# -Reading of image data in files that contain two different and unrelated images (this problem has been observed with some MR images).
|
@@ -29,29 +31,32 @@ module DICOM
|
|
29
31
|
# Class for handling the DICOM contents:
|
30
32
|
class DObject
|
31
33
|
|
32
|
-
attr_reader :read_success, :write_success, :modality, :errors,
|
34
|
+
attr_reader :read_success, :write_success, :modality, :errors, :segments,
|
33
35
|
:names, :tags, :types, :lengths, :values, :raw, :levels
|
34
36
|
|
35
37
|
# Initialize the DObject instance.
|
36
|
-
def initialize(
|
38
|
+
def initialize(string=nil, options={})
|
37
39
|
# Process option values, setting defaults for the ones that are not specified:
|
38
|
-
@verbose =
|
39
|
-
@lib =
|
40
|
+
@verbose = options[:verbose]
|
41
|
+
@lib = options[:lib] || DLibrary.new
|
42
|
+
segment_size = options[:segment_size]
|
43
|
+
bin = options[:bin]
|
44
|
+
syntax = options[:syntax]
|
40
45
|
# Default verbosity is true:
|
41
46
|
@verbose = true if @verbose == nil
|
42
47
|
|
43
48
|
# Initialize variables that will be used for the DICOM object:
|
44
|
-
@names = Array.new
|
45
|
-
@tags = Array.new
|
46
|
-
@types = Array.new
|
47
|
-
@lengths = Array.new
|
48
|
-
@values = Array.new
|
49
|
-
@raw = Array.new
|
50
|
-
@levels = Array.new
|
49
|
+
@names = Array.new
|
50
|
+
@tags = Array.new
|
51
|
+
@types = Array.new
|
52
|
+
@lengths = Array.new
|
53
|
+
@values = Array.new
|
54
|
+
@raw = Array.new
|
55
|
+
@levels = Array.new
|
51
56
|
# Array that will holde any messages generated while reading the DICOM file:
|
52
|
-
@errors = Array.new
|
57
|
+
@errors = Array.new
|
53
58
|
# Array to keep track of sequences/structure of the dicom elements:
|
54
|
-
@sequence = Array.new
|
59
|
+
@sequence = Array.new
|
55
60
|
# Index of last element in data element arrays:
|
56
61
|
@last_index=0
|
57
62
|
# Structural information (default values):
|
@@ -63,83 +68,87 @@ module DICOM
|
|
63
68
|
@modality = nil
|
64
69
|
# Control variables:
|
65
70
|
@read_success = false
|
66
|
-
#
|
67
|
-
@
|
68
|
-
# Set format strings for packing/unpacking:
|
69
|
-
set_format_strings()
|
71
|
+
# Initialize a Stream instance which is used for encoding/decoding:
|
72
|
+
@stream = Stream.new(nil, @file_endian, @explicit)
|
70
73
|
|
71
74
|
# If a (valid) file name string is supplied, call the method to read the DICOM file:
|
72
|
-
if
|
73
|
-
@file =
|
74
|
-
read(
|
75
|
+
if string.is_a?(String) and string != ""
|
76
|
+
@file = string
|
77
|
+
read(string, :bin => bin, :segment_size => segment_size, :syntax => syntax)
|
75
78
|
end
|
76
|
-
end # of
|
79
|
+
end # of initialize
|
77
80
|
|
78
81
|
|
79
82
|
# Returns a DICOM object by reading the file specified.
|
80
83
|
# This is accomplished by initliazing the DRead class, which loads DICOM information to arrays.
|
81
84
|
# For the time being, this method is called automatically when initializing the DObject class,
|
82
85
|
# but in the future, when write support is added, this method may have to be called manually.
|
83
|
-
def read(
|
84
|
-
|
86
|
+
def read(string, options = {})
|
87
|
+
r = DRead.new(string, :lib => @lib, :sys_endian => @sys_endian, :bin => options[:bin], :syntax => options[:syntax])
|
85
88
|
# Store the data to the instance variables if the readout was a success:
|
86
|
-
if
|
89
|
+
if r.success
|
87
90
|
@read_success = true
|
88
|
-
@names =
|
89
|
-
@tags =
|
90
|
-
@types =
|
91
|
-
@lengths =
|
92
|
-
@values =
|
93
|
-
@raw =
|
94
|
-
@levels =
|
95
|
-
@explicit =
|
96
|
-
@file_endian =
|
97
|
-
#
|
98
|
-
|
91
|
+
@names = r.names
|
92
|
+
@tags = r.tags
|
93
|
+
@types = r.types
|
94
|
+
@lengths = r.lengths
|
95
|
+
@values = r.values
|
96
|
+
@raw = r.raw
|
97
|
+
@levels = r.levels
|
98
|
+
@explicit = r.explicit
|
99
|
+
@file_endian = r.file_endian
|
100
|
+
# Update Stream instance with settings from this DICOM file:
|
101
|
+
@stream.set_endian(@file_endian)
|
102
|
+
@stream.explicit = @explicit
|
99
103
|
# Index of last data element in element arrays:
|
100
104
|
@last_index=@names.length-1
|
101
105
|
# Update status variables for this object:
|
102
|
-
check_properties
|
106
|
+
check_properties
|
103
107
|
# Set the modality of the DICOM object:
|
104
|
-
set_modality
|
108
|
+
set_modality
|
105
109
|
else
|
106
110
|
@read_success = false
|
107
111
|
end
|
108
|
-
#
|
109
|
-
if
|
110
|
-
|
112
|
+
# Check if a partial extraction has been requested (used for network communication purposes)
|
113
|
+
if options[:segment_size]
|
114
|
+
@segments = r.extract_segments(options[:segment_size])
|
111
115
|
end
|
116
|
+
# If any messages has been recorded, send these to the message handling method:
|
117
|
+
add_msg(r.msg) if r.msg.size != 0
|
112
118
|
end
|
113
119
|
|
114
120
|
|
115
121
|
# Transfers necessary information from the DObject to the DWrite class, which
|
116
122
|
# will attempt to write this information to a valid DICOM file.
|
117
|
-
def write(file_name)
|
118
|
-
w =
|
119
|
-
w.tags = @tags
|
120
|
-
w.types = @types
|
121
|
-
w.lengths = @lengths
|
122
|
-
w.raw = @raw
|
123
|
-
w.rest_endian = @file_endian
|
124
|
-
w.rest_explicit = @explicit
|
123
|
+
def write(file_name, transfer_syntax = nil)
|
124
|
+
w = set_write_object(file_name, transfer_syntax)
|
125
125
|
w.write
|
126
126
|
# Write process succesful?
|
127
127
|
@write_success = w.success
|
128
128
|
# If any messages has been recorded, send these to the message handling method:
|
129
|
-
if w.msg.size != 0
|
130
|
-
|
131
|
-
|
129
|
+
add_msg(w.msg) if w.msg.size != 0
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
# Encodes the DICOM object into a series of binary string segments with a specified maximum length.
|
134
|
+
def encode_segments(size)
|
135
|
+
w = set_write_object
|
136
|
+
@segments = w.encode_segments(size)
|
137
|
+
# Write process succesful?
|
138
|
+
@write_success = w.success
|
139
|
+
# If any messages has been recorded, send these to the message handling method:
|
140
|
+
add_msg(w.msg) if w.msg.size != 0
|
132
141
|
end
|
133
142
|
|
134
143
|
|
135
144
|
#################################################
|
136
|
-
# START OF METHODS FOR READING INFORMATION FROM DICOM OBJECT
|
145
|
+
# START OF METHODS FOR READING INFORMATION FROM DICOM OBJECT:
|
137
146
|
#################################################
|
138
147
|
|
139
148
|
|
140
149
|
# Checks the status of the pixel data that has been read from the DICOM file: whether it exists at all and if its greyscale or color.
|
141
150
|
# Modifies instance variable @color if color image is detected and instance variable @compression if no pixel data is detected.
|
142
|
-
def check_properties
|
151
|
+
def check_properties
|
143
152
|
# Check if pixel data is present:
|
144
153
|
if @tags.index("7FE0,0010") == nil
|
145
154
|
# No pixel data in DICOM file:
|
@@ -160,7 +169,7 @@ module DICOM
|
|
160
169
|
# Returns image data from the provided element index, performing decompression of data if necessary.
|
161
170
|
def read_image_magick(pos, columns, rows)
|
162
171
|
if pos == false or columns == false or rows == false
|
163
|
-
add_msg("Error: Method read_image_magick
|
172
|
+
add_msg("Error: Method read_image_magick does not have enough data available to build an image object.")
|
164
173
|
return false
|
165
174
|
end
|
166
175
|
if @compression != true
|
@@ -170,7 +179,7 @@ module DICOM
|
|
170
179
|
image.import_pixels(0, 0, columns, rows, "I", image_data)
|
171
180
|
return image
|
172
181
|
else
|
173
|
-
# Image data is compressed, we will attempt to
|
182
|
+
# Image data is compressed, we will attempt to deflate it using RMagick (ImageMagick):
|
174
183
|
begin
|
175
184
|
image = Magick::Image.from_blob(@raw[pos])
|
176
185
|
return image
|
@@ -184,7 +193,7 @@ module DICOM
|
|
184
193
|
|
185
194
|
# Returns a 3d NArray object where the array dimensions are related to [frames, columns, rows].
|
186
195
|
# To call this method the user needs to have performed " require 'narray' " in advance.
|
187
|
-
def get_image_narray
|
196
|
+
def get_image_narray
|
188
197
|
# Does pixel data exist at all in the DICOM object?
|
189
198
|
if @compression == nil
|
190
199
|
add_msg("It seems pixel data is not present in this DICOM object: returning false.")
|
@@ -203,8 +212,8 @@ module DICOM
|
|
203
212
|
# Gather information about the dimensions of the image data:
|
204
213
|
rows = get_value("0028,0010")
|
205
214
|
columns = get_value("0028,0011")
|
206
|
-
frames = get_frames
|
207
|
-
image_pos = get_image_pos
|
215
|
+
frames = get_frames
|
216
|
+
image_pos = get_image_pos
|
208
217
|
# Creating a NArray object using int to make sure we have a big enough range for our numbers:
|
209
218
|
image = NArray.int(frames,columns,rows)
|
210
219
|
image_temp = NArray.int(columns,rows)
|
@@ -244,12 +253,12 @@ module DICOM
|
|
244
253
|
image[i,true,true]=temp_image
|
245
254
|
end
|
246
255
|
return image
|
247
|
-
end # of
|
256
|
+
end # of get_image_narray
|
248
257
|
|
249
258
|
|
250
259
|
# Returns an array of RMagick image objects, where the size of the array corresponds with the number of frames in the image data.
|
251
260
|
# To call this method the user needs to have performed " require 'RMagick' " in advance.
|
252
|
-
def get_image_magick
|
261
|
+
def get_image_magick
|
253
262
|
# Does pixel data exist at all in the DICOM object?
|
254
263
|
if @compression == nil
|
255
264
|
add_msg("It seems pixel data is not present in this DICOM object: returning false.")
|
@@ -263,8 +272,8 @@ module DICOM
|
|
263
272
|
# Gather information about the dimensions of the image data:
|
264
273
|
rows = get_value("0028,0010")
|
265
274
|
columns = get_value("0028,0011")
|
266
|
-
frames = get_frames
|
267
|
-
image_pos = get_image_pos
|
275
|
+
frames = get_frames
|
276
|
+
image_pos = get_image_pos
|
268
277
|
# Array that will hold the RMagick image objects, one image object for each frame:
|
269
278
|
image_arr = Array.new(frames)
|
270
279
|
# Handling of image data will depend on whether we have one or more frames,
|
@@ -293,11 +302,11 @@ module DICOM
|
|
293
302
|
end
|
294
303
|
end
|
295
304
|
return image_arr
|
296
|
-
end # of
|
305
|
+
end # of get_image_magick
|
297
306
|
|
298
307
|
|
299
308
|
# Returns the number of frames present in the image data in the DICOM file.
|
300
|
-
def get_frames
|
309
|
+
def get_frames
|
301
310
|
frames = get_value("0028,0008")
|
302
311
|
if frames == false
|
303
312
|
# If the DICOM object does not specify the number of frames explicitly, assume 1 image frame.
|
@@ -313,26 +322,19 @@ module DICOM
|
|
313
322
|
# We need to know what kind of bith depth the pixel data is saved with:
|
314
323
|
bit_depth = get_value("0028,0100")
|
315
324
|
if bit_depth != false
|
316
|
-
# Load the binary pixel data:
|
317
|
-
|
325
|
+
# Load the binary pixel data to the Stream instance:
|
326
|
+
@stream.set_string(get_raw(pos))
|
318
327
|
# Number of bytes used per pixel will determine how to unpack this:
|
319
328
|
case bit_depth
|
320
329
|
when 8
|
321
|
-
pixels =
|
330
|
+
pixels = @stream.decode_all("BY") # Byte/Character/Fixnum (1 byte)
|
322
331
|
when 16
|
323
|
-
pixels =
|
332
|
+
pixels = @stream.decode_all("US") # Unsigned short (2 bytes)
|
324
333
|
when 12
|
325
334
|
# 12 BIT SIMPLY NOT WORKING YET!
|
326
335
|
# This one is a bit more tricky to extract.
|
327
336
|
# I havent really given this priority so far as 12 bit image data is rather rare.
|
328
337
|
add_msg("Warning: Bit depth 12 is not working correctly at this time! Please contact the author.")
|
329
|
-
#pixels = Array.new(length)
|
330
|
-
#(length).times do |i|
|
331
|
-
#hex = bin.unpack('H3')
|
332
|
-
#hex4 = "0"+hex[0]
|
333
|
-
#num = hex[0].unpack('v')
|
334
|
-
#data[i] = num
|
335
|
-
#end
|
336
338
|
else
|
337
339
|
raise "Bit depth ["+bit_depth.to_s+"] has not received implementation in this procedure yet. Please contact the author."
|
338
340
|
end # of case bit_depth
|
@@ -340,11 +342,11 @@ module DICOM
|
|
340
342
|
add_msg("Error: DICOM object does not contain the 'Bit Depth' data element (0028,0010).")
|
341
343
|
end # of if bit_depth ..
|
342
344
|
return pixels
|
343
|
-
end
|
345
|
+
end
|
344
346
|
|
345
347
|
|
346
348
|
# Returns the index(es) of the element(s) that contain image data.
|
347
|
-
def get_image_pos
|
349
|
+
def get_image_pos
|
348
350
|
image_element_pos = get_pos("7FE0,0010")
|
349
351
|
item_pos = get_pos("FFFE,E000")
|
350
352
|
# Proceed only if an image element actually exists:
|
@@ -365,22 +367,22 @@ module DICOM
|
|
365
367
|
# Determine which of these late item elements contain image data.
|
366
368
|
# Usually, there are frames+1 late items, and all except
|
367
369
|
# the first item contain an image frame:
|
368
|
-
frames = get_frames
|
370
|
+
frames = get_frames
|
369
371
|
if frames != false # note: function get_frames will never return false
|
370
372
|
if late_item_pos.size == frames.to_i+1
|
371
373
|
return late_item_pos[1..late_item_pos.size-1]
|
372
374
|
else
|
373
|
-
add_msg("Warning: Unexpected behaviour in DICOM file for method get_image_pos
|
375
|
+
add_msg("Warning: Unexpected behaviour in DICOM file for method get_image_pos. Expected number of image data items not equal to number of frames+1, returning false.")
|
374
376
|
return false
|
375
377
|
end
|
376
378
|
else
|
377
|
-
add_msg("Warning: 'Number of Frames' data element not found. Method get_image_pos
|
379
|
+
add_msg("Warning: 'Number of Frames' data element not found. Method get_image_pos will return false.")
|
378
380
|
return false
|
379
381
|
end
|
380
382
|
end
|
381
383
|
end
|
382
384
|
end
|
383
|
-
end
|
385
|
+
end
|
384
386
|
|
385
387
|
|
386
388
|
# Returns an array of the index(es) of the element(s) in the DICOM file that match the supplied element position, tag or name.
|
@@ -390,11 +392,8 @@ module DICOM
|
|
390
392
|
# through the entire DICOM object. If myArray equals false, the method will return false.
|
391
393
|
# :partial => true - get_pos will not only search for exact matches, but will search the names and tags arrays for
|
392
394
|
# strings that contain the given search string.
|
393
|
-
def get_pos(query,
|
394
|
-
|
395
|
-
keyword_array = opts[:array]
|
396
|
-
keyword_partial = opts[:partial]
|
397
|
-
indexes = Array.new()
|
395
|
+
def get_pos(query, options={})
|
396
|
+
indexes = Array.new
|
398
397
|
# For convenience, allow query to be a one-element array (its value will be extracted):
|
399
398
|
if query.is_a?(Array)
|
400
399
|
if query.length > 1 or query.length == 0
|
@@ -404,25 +403,25 @@ module DICOM
|
|
404
403
|
query = query[0]
|
405
404
|
end
|
406
405
|
end
|
407
|
-
if
|
406
|
+
if options[:array] == false
|
408
407
|
# If the supplied array option equals false, it signals that the user tries to search for an element
|
409
408
|
# in an invalid position, and as such, this method will also return false:
|
410
|
-
add_msg("Warning: Attempted to call get_pos
|
409
|
+
add_msg("Warning: Attempted to call get_pos with query #{query}, but since keyword :array is false I will return false.")
|
411
410
|
indexes = false
|
412
411
|
else
|
413
|
-
# Check if query is a number (some methods want to have the ability to call get_pos
|
412
|
+
# Check if query is a number (some methods want to have the ability to call get_pos with a number):
|
414
413
|
if query.is_a?(Integer)
|
415
414
|
# Return the position if it is valid:
|
416
415
|
indexes = [query] if query >= 0 and query < @names.length
|
417
416
|
elsif query.is_a?(String)
|
418
417
|
# Either use the supplied array, or search the entire DICOM object:
|
419
|
-
if
|
420
|
-
search_array =
|
418
|
+
if options[:array].is_a?(Array)
|
419
|
+
search_array = options[:array]
|
421
420
|
else
|
422
421
|
search_array = Array.new(@names.length) {|i| i}
|
423
422
|
end
|
424
423
|
# Perform search:
|
425
|
-
if
|
424
|
+
if options[:partial] == true
|
426
425
|
# Search for partial string matches:
|
427
426
|
partial_indexes = search_array.all_indices_partial_match(@tags, query.upcase)
|
428
427
|
if partial_indexes.length > 0
|
@@ -443,51 +442,51 @@ module DICOM
|
|
443
442
|
indexes = false if indexes.length == 0
|
444
443
|
end
|
445
444
|
return indexes
|
446
|
-
end # of
|
445
|
+
end # of get_pos
|
447
446
|
|
448
447
|
|
449
448
|
# Dumps the binary content of the Pixel Data element to file.
|
450
449
|
def image_to_file(file)
|
451
|
-
pos = get_image_pos
|
450
|
+
pos = get_image_pos
|
452
451
|
if pos
|
453
452
|
if pos.length == 1
|
454
453
|
# Pixel data located in one element:
|
455
454
|
pixel_data = get_raw(pos[0])
|
456
455
|
f = File.new(file, "wb")
|
457
456
|
f.write(pixel_data)
|
458
|
-
f.close
|
457
|
+
f.close
|
459
458
|
else
|
460
459
|
# Pixel data located in several elements:
|
461
460
|
pos.each_index do |i|
|
462
461
|
pixel_data = get_raw(pos[i])
|
463
462
|
f = File.new(file + i.to_s, "wb")
|
464
463
|
f.write(pixel_data)
|
465
|
-
f.close
|
464
|
+
f.close
|
466
465
|
end
|
467
466
|
end
|
468
|
-
end
|
469
|
-
end
|
467
|
+
end
|
468
|
+
end
|
470
469
|
|
471
470
|
|
472
471
|
# Returns the positions of all data elements inside the hierarchy of a sequence or an item.
|
473
472
|
# Options:
|
474
473
|
# :next_only => true - The method will only search immediately below the specified
|
475
474
|
# item or sequence (that is, in the level of parent + 1).
|
476
|
-
def children(element,
|
475
|
+
def children(element, options={})
|
477
476
|
# Process option values, setting defaults for the ones that are not specified:
|
478
|
-
opt_next_only =
|
477
|
+
opt_next_only = options[:next_only] || false
|
479
478
|
value = false
|
480
479
|
# Retrieve array position:
|
481
480
|
pos = get_pos(element)
|
482
481
|
if pos == false
|
483
|
-
add_msg("Warning: Invalid data element provided to method children
|
482
|
+
add_msg("Warning: Invalid data element provided to method children. Returning false.")
|
484
483
|
else
|
485
484
|
if pos.size > 1
|
486
|
-
add_msg("Warning: Method children
|
485
|
+
add_msg("Warning: Method children does not allow a query which yields multiple array hits. Please use array position instead of tag/name. Returning false.")
|
487
486
|
else
|
488
487
|
# Proceed to find the value:
|
489
488
|
# First we need to establish in which positions to perform the search:
|
490
|
-
below_pos = Array.new
|
489
|
+
below_pos = Array.new
|
491
490
|
pos.each do |p|
|
492
491
|
parent_level = @levels[p]
|
493
492
|
remain_array = @levels[p+1..@levels.size-1]
|
@@ -496,11 +495,11 @@ module DICOM
|
|
496
495
|
if (remain_array[i] > parent_level) and (extract == true)
|
497
496
|
# If search is targetted at any specific level, we can just add this position:
|
498
497
|
if not opt_next_only == true
|
499
|
-
below_pos
|
498
|
+
below_pos << (p+1+i)
|
500
499
|
else
|
501
500
|
# As search is restricted to parent level + 1, do a test for this:
|
502
501
|
if remain_array[i] == parent_level + 1
|
503
|
-
below_pos
|
502
|
+
below_pos << (p+1+i)
|
504
503
|
end
|
505
504
|
end
|
506
505
|
else
|
@@ -513,7 +512,7 @@ module DICOM
|
|
513
512
|
end # of if pos.size..else..
|
514
513
|
end
|
515
514
|
return value
|
516
|
-
end
|
515
|
+
end
|
517
516
|
|
518
517
|
|
519
518
|
# Returns the value (processed raw data) of the requested DICOM data element.
|
@@ -522,32 +521,31 @@ module DICOM
|
|
522
521
|
# :array => true - Allows the query of the value of a tag that occurs more than one time in the
|
523
522
|
# DICOM object. Values will be returned in an array with length equal to the number
|
524
523
|
# of occurances of the tag. If keyword is not specified, the method returns false in this case.
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
silent = opts[:silent]
|
524
|
+
# :silent => true - As this method is also used internally, we want the possibility of warnings not being
|
525
|
+
# raised even if verbose is set to true by the user, in order to avoid confusion.
|
526
|
+
def get_value(element, options={})
|
529
527
|
value = false
|
530
528
|
# Retrieve array position:
|
531
529
|
pos = get_pos(element)
|
532
530
|
if pos == false
|
533
|
-
add_msg("Warning: Invalid data element provided to method get_value
|
531
|
+
add_msg("Warning: Invalid data element provided to method get_value. Returning false.") unless options[:silent]
|
534
532
|
else
|
535
533
|
if pos.size > 1
|
536
|
-
if
|
534
|
+
if options[:array] == true
|
537
535
|
# Retrieve all values into an array:
|
538
|
-
value =
|
536
|
+
value = Array.new
|
539
537
|
pos.each do |i|
|
540
538
|
value << @values[i]
|
541
539
|
end
|
542
540
|
else
|
543
|
-
add_msg("Warning: Method get_value
|
541
|
+
add_msg("Warning: Method get_value does not allow a query which yields multiple array hits. Please use array position instead of tag/name, or use keyword (:array => true). Returning false.") unless options[:silent]
|
544
542
|
end
|
545
543
|
else
|
546
544
|
value = @values[pos[0]]
|
547
545
|
end
|
548
546
|
end
|
549
547
|
return value
|
550
|
-
end
|
548
|
+
end
|
551
549
|
|
552
550
|
|
553
551
|
# Returns the raw data of the requested DICOM data element.
|
@@ -556,30 +554,29 @@ module DICOM
|
|
556
554
|
# :array => true - Allows the query of the value of a tag that occurs more than one time in the
|
557
555
|
# DICOM object. Values will be returned in an array with length equal to the number
|
558
556
|
# of occurances of the tag. If keyword is not specified, the method returns false in this case.
|
559
|
-
def get_raw(element,
|
560
|
-
opts_array = opts[:array]
|
557
|
+
def get_raw(element, options={})
|
561
558
|
value = false
|
562
559
|
# Retrieve array position:
|
563
560
|
pos = get_pos(element)
|
564
561
|
if pos == false
|
565
|
-
add_msg("Warning: Invalid data element provided to method get_raw
|
562
|
+
add_msg("Warning: Invalid data element provided to method get_raw. Returning false.")
|
566
563
|
else
|
567
564
|
if pos.size > 1
|
568
|
-
if
|
565
|
+
if options[:array] == true
|
569
566
|
# Retrieve all values into an array:
|
570
|
-
value =
|
567
|
+
value = Array.new
|
571
568
|
pos.each do |i|
|
572
569
|
value << @raw[i]
|
573
570
|
end
|
574
571
|
else
|
575
|
-
add_msg("Warning: Method get_raw
|
572
|
+
add_msg("Warning: Method get_raw does not allow a query which yields multiple array hits. Please use array position instead of tag/name, or use keyword (:array => true). Returning false.")
|
576
573
|
end
|
577
574
|
else
|
578
575
|
value = @raw[pos[0]]
|
579
576
|
end
|
580
577
|
end
|
581
578
|
return value
|
582
|
-
end
|
579
|
+
end
|
583
580
|
|
584
581
|
|
585
582
|
# Returns the position of (possible) parents of the specified data element in the hierarchy structure of the DICOM object.
|
@@ -588,10 +585,10 @@ module DICOM
|
|
588
585
|
# Retrieve array position:
|
589
586
|
pos = get_pos(element)
|
590
587
|
if pos == false
|
591
|
-
add_msg("Warning: Invalid data element provided to method parents
|
588
|
+
add_msg("Warning: Invalid data element provided to method parents. Returning false.")
|
592
589
|
else
|
593
590
|
if pos.length > 1
|
594
|
-
add_msg("Warning: Method parents
|
591
|
+
add_msg("Warning: Method parents does not allow a query which yields multiple array hits. Please use array position instead of tag/name. Returning false.")
|
595
592
|
else
|
596
593
|
# Proceed to find the value:
|
597
594
|
# Get the level of our element:
|
@@ -599,23 +596,23 @@ module DICOM
|
|
599
596
|
# Element can obviously only have parents if it is not a top level element:
|
600
597
|
unless level == 0
|
601
598
|
# Search backwards, and record the position every time we encounter an upwards change in the level number.
|
602
|
-
parents = Array.new
|
599
|
+
parents = Array.new
|
603
600
|
prev_level = level
|
604
601
|
search_arr = @levels[0..pos[0]-1].reverse
|
605
602
|
search_arr.each_index do |i|
|
606
603
|
if search_arr[i] < prev_level
|
607
|
-
parents
|
604
|
+
parents << search_arr.length-i-1
|
608
605
|
prev_level = search_arr[i]
|
609
606
|
end
|
610
607
|
end
|
611
608
|
# When the element has several generations of parents, we want its top parent to be first in the returned array:
|
612
609
|
parents = parents.reverse
|
613
610
|
value = parents if parents.length > 0
|
614
|
-
end
|
615
|
-
end
|
611
|
+
end
|
612
|
+
end
|
616
613
|
end
|
617
614
|
return value
|
618
|
-
end
|
615
|
+
end
|
619
616
|
|
620
617
|
|
621
618
|
##############################################
|
@@ -625,8 +622,8 @@ module DICOM
|
|
625
622
|
|
626
623
|
# Prints the information of all elements stored in the DICOM object.
|
627
624
|
# This method is kept for backwards compatibility.
|
628
|
-
# Instead of calling print_all
|
629
|
-
def print_all
|
625
|
+
# Instead of calling print_all you may use print(true) for the same functionality.
|
626
|
+
def print_all
|
630
627
|
print(true)
|
631
628
|
end
|
632
629
|
|
@@ -637,14 +634,14 @@ module DICOM
|
|
637
634
|
# :levels => true - method will print the level numbers for each element.
|
638
635
|
# :tree => true - method will print a tree structure for the elements.
|
639
636
|
# :file => true - method will print to file instead of printing to screen.
|
640
|
-
def print(pos,
|
637
|
+
def print(pos, options={})
|
641
638
|
# Process option values, setting defaults for the ones that are not specified:
|
642
|
-
opt_levels =
|
643
|
-
opt_tree =
|
644
|
-
opt_file =
|
639
|
+
opt_levels = options[:levels] || false
|
640
|
+
opt_tree = options[:tree] || false
|
641
|
+
opt_file = options[:file] || false
|
645
642
|
# If pos is false, abort, and inform the user:
|
646
643
|
if pos == false
|
647
|
-
add_msg("Warning: Method print
|
644
|
+
add_msg("Warning: Method print was supplied false instead of a valid position. Aborting print.")
|
648
645
|
return
|
649
646
|
end
|
650
647
|
if not pos.is_a?(Array) and pos != true
|
@@ -658,21 +655,21 @@ module DICOM
|
|
658
655
|
pos_valid = pos
|
659
656
|
end
|
660
657
|
# Extract the information to be printed from the object arrays:
|
661
|
-
indices = Array.new
|
662
|
-
levels = Array.new
|
663
|
-
tags = Array.new
|
664
|
-
names = Array.new
|
665
|
-
types = Array.new
|
666
|
-
lengths = Array.new
|
667
|
-
values = Array.new
|
658
|
+
indices = Array.new
|
659
|
+
levels = Array.new
|
660
|
+
tags = Array.new
|
661
|
+
names = Array.new
|
662
|
+
types = Array.new
|
663
|
+
lengths = Array.new
|
664
|
+
values = Array.new
|
668
665
|
# There may be a more elegant way to do this.
|
669
666
|
pos_valid.each do |pos|
|
670
|
-
tags
|
671
|
-
levels
|
672
|
-
names
|
673
|
-
types
|
674
|
-
lengths
|
675
|
-
values
|
667
|
+
tags << @tags[pos]
|
668
|
+
levels << @levels[pos]
|
669
|
+
names << @names[pos]
|
670
|
+
types << @types[pos]
|
671
|
+
lengths << @lengths[pos].to_s
|
672
|
+
values << @values[pos].to_s
|
676
673
|
end
|
677
674
|
# We have collected the data that is to be printed, now we need to do some string manipulation if hierarchy is to be displayed:
|
678
675
|
if opt_tree
|
@@ -686,10 +683,10 @@ module DICOM
|
|
686
683
|
end
|
687
684
|
end
|
688
685
|
# Extract the string lengths which are needed to make the formatting nice:
|
689
|
-
tag_lengths = Array.new
|
690
|
-
name_lengths = Array.new
|
691
|
-
type_lengths = Array.new
|
692
|
-
length_lengths = Array.new
|
686
|
+
tag_lengths = Array.new
|
687
|
+
name_lengths = Array.new
|
688
|
+
type_lengths = Array.new
|
689
|
+
length_lengths = Array.new
|
693
690
|
names.each_index do |i|
|
694
691
|
tag_lengths[i] = tags[i].length
|
695
692
|
name_lengths[i] = names[i].length
|
@@ -703,7 +700,7 @@ module DICOM
|
|
703
700
|
type_maxL = type_lengths.max
|
704
701
|
length_maxL = length_lengths.max
|
705
702
|
# Construct the strings, one for each line of output, where each line contain the information of one data element:
|
706
|
-
elements = Array.new
|
703
|
+
elements = Array.new
|
707
704
|
# Start of loop which formats the element data:
|
708
705
|
# (This loop is what consumes most of the computing time of this method)
|
709
706
|
tags.each_index do |i|
|
@@ -733,19 +730,19 @@ module DICOM
|
|
733
730
|
when "SQ","()"
|
734
731
|
value = "(Encapsulated Elements)"
|
735
732
|
end
|
736
|
-
elements
|
733
|
+
elements << (f0 + pos_valid[i].to_s + s + lev + s + tags[i] + f2 + names[i] + f3 + types[i] + f4 + f5 + lengths[i].to_s + s + s + value.rstrip)
|
737
734
|
end
|
738
735
|
# Print to either screen or file, depending on what the user requested:
|
739
736
|
if opt_file
|
740
737
|
print_file(elements)
|
741
738
|
else
|
742
739
|
print_screen(elements)
|
743
|
-
end
|
744
|
-
end # of
|
740
|
+
end
|
741
|
+
end # of print
|
745
742
|
|
746
743
|
|
747
744
|
# Prints the key structural properties of the DICOM file.
|
748
|
-
def print_properties
|
745
|
+
def print_properties
|
749
746
|
# Explicitness:
|
750
747
|
if @explicit
|
751
748
|
explicit = "Explicit"
|
@@ -792,11 +789,11 @@ module DICOM
|
|
792
789
|
puts "Bits per pixel: " + bits
|
793
790
|
end
|
794
791
|
puts "-------------------------------"
|
795
|
-
end # of
|
792
|
+
end # of print_properties
|
796
793
|
|
797
794
|
|
798
795
|
####################################################
|
799
|
-
### START OF METHODS FOR WRITING INFORMATION TO THE DICOM OBJECT
|
796
|
+
### START OF METHODS FOR WRITING INFORMATION TO THE DICOM OBJECT:
|
800
797
|
####################################################
|
801
798
|
|
802
799
|
|
@@ -817,8 +814,8 @@ module DICOM
|
|
817
814
|
set_value(bin, "7FE0,0010", :create => true, :bin => true)
|
818
815
|
else
|
819
816
|
add_msg("Content of file is of zero length. Nothing to store.")
|
820
|
-
end
|
821
|
-
end
|
817
|
+
end
|
818
|
+
end
|
822
819
|
|
823
820
|
|
824
821
|
# Transfers pixel data from a RMagick object to the pixel data element:
|
@@ -835,7 +832,7 @@ module DICOM
|
|
835
832
|
pos = get_pos(element)
|
836
833
|
if pos != false
|
837
834
|
if pos.length > 1
|
838
|
-
add_msg("Warning: Method remove
|
835
|
+
add_msg("Warning: Method remove does not allow an element query which yields multiple array hits. Please use array position instead of tag/name. Value NOT removed.")
|
839
836
|
else
|
840
837
|
# Extract first array number:
|
841
838
|
pos = pos[0]
|
@@ -855,29 +852,29 @@ module DICOM
|
|
855
852
|
@raw.delete_at(pos)
|
856
853
|
end
|
857
854
|
else
|
858
|
-
add_msg("Warning: The data element #{element} could not be found in the DICOM object. Method remove
|
855
|
+
add_msg("Warning: The data element #{element} could not be found in the DICOM object. Method remove has no data element to remove.")
|
859
856
|
end
|
860
857
|
end
|
861
858
|
|
862
859
|
|
863
860
|
# Sets the value of a data element by modifying an existing element or creating a new one.
|
864
861
|
# If the supplied value is not binary, it will attempt to encode the value to binary itself.
|
865
|
-
def set_value(value, element,
|
862
|
+
def set_value(value, element, options={})
|
866
863
|
# Options:
|
867
|
-
create =
|
868
|
-
bin =
|
864
|
+
create = options[:create] # =false means no element creation
|
865
|
+
bin = options[:bin] # =true means value already encoded
|
869
866
|
# Retrieve array position:
|
870
867
|
pos = get_pos(element)
|
871
868
|
# We do not support changing multiple data elements:
|
872
869
|
if pos.is_a?(Array)
|
873
870
|
if pos.length > 1
|
874
|
-
add_msg("Warning: Method set_value
|
871
|
+
add_msg("Warning: Method set_value does not allow an element query which yields multiple array hits. Please use array position instead of tag/name. Value NOT saved.")
|
875
872
|
return
|
876
873
|
end
|
877
874
|
end
|
878
875
|
if pos == false and create == false
|
879
876
|
# Since user has requested an element shall only be updated, we can not do so as the element position is not valid:
|
880
|
-
add_msg("Warning: Invalid data element provided to method set_value
|
877
|
+
add_msg("Warning: Invalid data element provided to method set_value. Value NOT updated.")
|
881
878
|
elsif create == false
|
882
879
|
# Modify element:
|
883
880
|
modify_element(value, pos[0], :bin => bin)
|
@@ -890,7 +887,7 @@ module DICOM
|
|
890
887
|
# We need to create element:
|
891
888
|
tag = @lib.get_tag(element)
|
892
889
|
if tag == false
|
893
|
-
add_msg("Warning: Method set_value
|
890
|
+
add_msg("Warning: Method set_value could not create data element, either because data element name was not recognized in the library, or data element tag is invalid (Expected format of tags is 'GGGG,EEEE').")
|
894
891
|
else
|
895
892
|
# As we wish to create a new data element, we need to find out where to insert it in the element arrays:
|
896
893
|
# We will do this by finding the last array position of the last element that will (alphabetically/numerically) stay in front of this element.
|
@@ -913,44 +910,29 @@ module DICOM
|
|
913
910
|
end
|
914
911
|
# The necessary information is gathered; create new data element:
|
915
912
|
create_element(value, tag, index, :bin => bin)
|
916
|
-
end
|
917
|
-
end
|
918
|
-
end
|
919
|
-
end # of
|
913
|
+
end
|
914
|
+
end
|
915
|
+
end
|
916
|
+
end # of set_value
|
920
917
|
|
921
918
|
|
922
919
|
##################################################
|
923
|
-
############## START OF PRIVATE METHODS
|
920
|
+
############## START OF PRIVATE METHODS: ########
|
924
921
|
##################################################
|
925
922
|
private
|
926
923
|
|
927
924
|
|
928
925
|
# Adds a warning or error message to the instance array holding messages, and if verbose variable is true, prints the message as well.
|
929
926
|
def add_msg(msg)
|
930
|
-
if @verbose
|
931
|
-
|
932
|
-
|
933
|
-
if (msg.is_a? String)
|
934
|
-
msg=[msg]
|
935
|
-
end
|
936
|
-
@errors += msg
|
937
|
-
end
|
938
|
-
|
939
|
-
|
940
|
-
# Checks the endianness of the system. Returns false if little endian, true if big endian.
|
941
|
-
def check_sys_endian()
|
942
|
-
x = 0xdeadbeef
|
943
|
-
endian_type = {
|
944
|
-
Array(x).pack("V*") => false, #:little
|
945
|
-
Array(x).pack("N*") => true #:big
|
946
|
-
}
|
947
|
-
return endian_type[Array(x).pack("L*")]
|
927
|
+
puts msg if @verbose
|
928
|
+
@errors << msg
|
929
|
+
@errors.flatten
|
948
930
|
end
|
949
931
|
|
950
932
|
|
951
933
|
# Creates a new data element:
|
952
|
-
def create_element(value, tag, last_pos,
|
953
|
-
bin_only =
|
934
|
+
def create_element(value, tag, last_pos, options={})
|
935
|
+
bin_only = options[:bin]
|
954
936
|
# Fetch the VR:
|
955
937
|
info = @lib.get_name_vr(tag)
|
956
938
|
vr = info[1]
|
@@ -1020,7 +1002,7 @@ module DICOM
|
|
1020
1002
|
else
|
1021
1003
|
add_msg("Binary is nil. Nothing to save.")
|
1022
1004
|
end
|
1023
|
-
end # of
|
1005
|
+
end # of create_element
|
1024
1006
|
|
1025
1007
|
|
1026
1008
|
# Encodes a value to binary (used for inserting values to a DICOM object).
|
@@ -1029,39 +1011,15 @@ module DICOM
|
|
1029
1011
|
value = [value] if not value.is_a?(Array)
|
1030
1012
|
# VR will decide how to encode this value:
|
1031
1013
|
case vr
|
1032
|
-
when "
|
1033
|
-
bin =
|
1034
|
-
when "SL"
|
1035
|
-
bin = value.pack(@sl)
|
1036
|
-
when "US"
|
1037
|
-
bin = value.pack(@us)
|
1038
|
-
when "SS"
|
1039
|
-
bin = value.pack(@ss)
|
1040
|
-
when "FL"
|
1041
|
-
bin = value.pack(@fs)
|
1042
|
-
when "FD"
|
1043
|
-
bin = value.pack(@fd)
|
1044
|
-
when "AT" # (Data element tag: Assume it has the format GGGGEEEE (no comma separation))
|
1045
|
-
# Encode letter pairs indexes in following order 10 3 2:
|
1046
|
-
# NB! This may not be encoded correctly on Big Endian files or computers.
|
1047
|
-
old_format=value[0]
|
1048
|
-
new_format = old_format[2..3]+old_format[0..1]+old_format[6..7]+old_format[4..5]
|
1049
|
-
bin = [new_format].pack("H*")
|
1050
|
-
|
1014
|
+
when "AT" # (Data element tag: Assume it has the format "GGGG,EEEE"
|
1015
|
+
bin = @stream.encode_tag(value)
|
1051
1016
|
# We have a number of VRs that are encoded as string:
|
1052
1017
|
when 'AE','AS','CS','DA','DS','DT','IS','LO','LT','PN','SH','ST','TM','UI','UT'
|
1053
1018
|
# In case we are dealing with a number string element, the supplied value might be a number
|
1054
1019
|
# instead of a string, and as such, we convert to string just to make sure this will work nicely:
|
1055
1020
|
value[0] = value[0].to_s
|
1056
|
-
|
1057
|
-
|
1058
|
-
# Odd (add a zero byte):
|
1059
|
-
bin = value.pack('a*') + ["00"].pack("H*")
|
1060
|
-
else
|
1061
|
-
# Even:
|
1062
|
-
bin = value.pack('a*')
|
1063
|
-
end
|
1064
|
-
# Image related VR's:
|
1021
|
+
bin = @stream.encode_value(value, "STR")
|
1022
|
+
# Image related value representations:
|
1065
1023
|
when "OW"
|
1066
1024
|
# What bit depth to use when encoding the pixel data?
|
1067
1025
|
bit_depth = get_value("0028,0100")
|
@@ -1072,26 +1030,28 @@ module DICOM
|
|
1072
1030
|
# 8,12 or 16 bits?
|
1073
1031
|
case bit_depth
|
1074
1032
|
when 8
|
1075
|
-
bin =
|
1033
|
+
bin = @stream.encode(value, "BY")
|
1076
1034
|
when 12
|
1077
1035
|
# 12 bit not supported yet!
|
1078
1036
|
add_msg("Encoding 12 bit pixel values not supported yet. Please change the bit depth to 8 or 16 bits.")
|
1079
1037
|
when 16
|
1080
|
-
bin =
|
1038
|
+
bin = @stream.encode(value, "US")
|
1081
1039
|
else
|
1082
1040
|
# Unknown bit depth:
|
1083
1041
|
add_msg("Unknown bit depth #{bit_depth}. No data encoded.")
|
1084
|
-
end
|
1085
|
-
end
|
1086
|
-
|
1087
|
-
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
# All other VR's:
|
1045
|
+
else
|
1046
|
+
# Just encode:
|
1047
|
+
bin = @stream.encode(value, vr)
|
1088
1048
|
end # of case vr
|
1089
1049
|
return bin
|
1090
|
-
end # of
|
1050
|
+
end # of encode
|
1091
1051
|
|
1092
1052
|
# Modifies existing data element:
|
1093
|
-
def modify_element(value, pos,
|
1094
|
-
bin_only =
|
1053
|
+
def modify_element(value, pos, options={})
|
1054
|
+
bin_only = options[:bin]
|
1095
1055
|
# Fetch the VR and old length:
|
1096
1056
|
vr = @types[pos]
|
1097
1057
|
old_length = @lengths[pos]
|
@@ -1123,7 +1083,7 @@ module DICOM
|
|
1123
1083
|
else
|
1124
1084
|
add_msg("Binary is nil. Nothing to save.")
|
1125
1085
|
end
|
1126
|
-
end
|
1086
|
+
end
|
1127
1087
|
|
1128
1088
|
|
1129
1089
|
# Prints the selected elements to an ascii text file.
|
@@ -1147,7 +1107,7 @@ module DICOM
|
|
1147
1107
|
|
1148
1108
|
|
1149
1109
|
# Sets the modality variable of the current DICOM object, by querying the library with the object's SOP Class UID.
|
1150
|
-
def set_modality
|
1110
|
+
def set_modality
|
1151
1111
|
value = get_value("0008,0016", :silent => true)
|
1152
1112
|
if value == false
|
1153
1113
|
@modality = "Not specified"
|
@@ -1158,29 +1118,20 @@ module DICOM
|
|
1158
1118
|
end
|
1159
1119
|
|
1160
1120
|
|
1161
|
-
#
|
1162
|
-
def
|
1163
|
-
|
1164
|
-
|
1165
|
-
#
|
1166
|
-
@by = "C*" # Byte (1 byte)
|
1167
|
-
@us = "S*" # Unsigned short (2 bytes)
|
1168
|
-
@ss = "s*" # Signed short (2 bytes)
|
1169
|
-
@ul = "I*" # Unsigned long (4 bytes)
|
1170
|
-
@sl = "l*" # Signed long (4 bytes)
|
1171
|
-
@fs = "e*" # Floating point single (4 bytes)
|
1172
|
-
@fd = "E*" # Floating point double ( 8 bytes)
|
1173
|
-
else
|
1174
|
-
# System endian not equal to file endian:
|
1175
|
-
# Network byte order.
|
1176
|
-
@by = "C*"
|
1177
|
-
@us = "n*"
|
1178
|
-
@ss = "n*" # Not correct (gives US)
|
1179
|
-
@ul = "N*"
|
1180
|
-
@sl = "N*" # Not correct (gives UL)
|
1181
|
-
@fs = "g*"
|
1182
|
-
@fd = "G*"
|
1121
|
+
# Handles the creation of a DWrite object, and returns this object to the calling method.
|
1122
|
+
def set_write_object(file_name = nil, transfer_syntax = nil)
|
1123
|
+
unless transfer_syntax
|
1124
|
+
transfer_syntax = get_value("0002,0010", :silent => true)
|
1125
|
+
transfer_syntax = "1.2.840.10008.1.2" if not transfer_syntax # Default is implicit, little endian
|
1183
1126
|
end
|
1127
|
+
w = DWrite.new(file_name, :lib => @lib, :sys_endian => @sys_endian, :transfer_syntax => transfer_syntax)
|
1128
|
+
w.tags = @tags
|
1129
|
+
w.types = @types
|
1130
|
+
w.lengths = @lengths
|
1131
|
+
w.raw = @raw
|
1132
|
+
w.rest_endian = @file_endian
|
1133
|
+
w.rest_explicit = @explicit
|
1134
|
+
return w
|
1184
1135
|
end
|
1185
1136
|
|
1186
1137
|
|
@@ -1234,9 +1185,9 @@ module DICOM
|
|
1234
1185
|
# Update arrays:
|
1235
1186
|
@values[gl_pos] = value
|
1236
1187
|
@raw[gl_pos] = bin
|
1237
|
-
end
|
1238
|
-
end # of
|
1188
|
+
end
|
1189
|
+
end # of update_group_length
|
1239
1190
|
|
1240
1191
|
|
1241
|
-
end #
|
1242
|
-
end #
|
1192
|
+
end # of class
|
1193
|
+
end # of module
|