dicom 0.5 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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/Stream.rb
ADDED
@@ -0,0 +1,351 @@
|
|
1
|
+
# Copyright 2009 Christoffer Lervag
|
2
|
+
|
3
|
+
# This file contains the Stream class, which handles all encoding to and
|
4
|
+
# decoding from binary strings. It is used by the other components of
|
5
|
+
# Ruby DICOM for tasks such as reading from file, writing to file,
|
6
|
+
# reading and writing network data packets. These operations have been
|
7
|
+
# gathered in this one class in an attemt to minimize code duplication.
|
8
|
+
|
9
|
+
module DICOM
|
10
|
+
# Class for handling binary string operations:
|
11
|
+
class Stream
|
12
|
+
|
13
|
+
attr_accessor :endian, :explicit, :index, :string
|
14
|
+
attr_reader :errors
|
15
|
+
|
16
|
+
# Initialize the Stream instance.
|
17
|
+
def initialize(string, str_endian, explicit, options={})
|
18
|
+
# Set instance variables:
|
19
|
+
@explicit = explicit # true or false
|
20
|
+
@string = string # input binary string
|
21
|
+
@string = "" unless @string # if nil, change it to an empty string
|
22
|
+
@index = options[:index] || 0
|
23
|
+
@errors = Array.new
|
24
|
+
set_endian(str_endian) # true or false
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# Adds a pre-encoded string to the end of this instance's string.
|
29
|
+
def add_first(binary)
|
30
|
+
@string = binary + @string if binary
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Adds a pre-encoded string to the beginning of this instance's string.
|
35
|
+
def add_last(binary)
|
36
|
+
@string = @string + binary if binary
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# Decodes a section of the binary string and returns the formatted data.
|
41
|
+
def decode(length, type)
|
42
|
+
# Check if values are valid:
|
43
|
+
if (@index + length) > @string.length
|
44
|
+
# The index number is bigger then the length of the binary string.
|
45
|
+
# We have reached the end and will return nil.
|
46
|
+
value = nil
|
47
|
+
else
|
48
|
+
# Decode the binary string and return value:
|
49
|
+
value = @string.slice(@index, length).unpack(vr_to_str(type))
|
50
|
+
# If the result is an array of one element, return the element instead of the array.
|
51
|
+
# If result is contained in a multi-element array, the original array is returned.
|
52
|
+
if value.length == 1
|
53
|
+
value = value[0]
|
54
|
+
# If value is a string, strip away possible trailing whitespace:
|
55
|
+
value = value.rstrip if value.is_a?(String)
|
56
|
+
end
|
57
|
+
# Update our position in the string:
|
58
|
+
skip(length)
|
59
|
+
end
|
60
|
+
return value
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Decodes the entire binary string and returns the formatted data.
|
65
|
+
# Typically used for decoding image data.
|
66
|
+
def decode_all(type)
|
67
|
+
length = @string.length
|
68
|
+
value = @string.slice(@index, length).unpack(vr_to_str(type))
|
69
|
+
skip(length)
|
70
|
+
return value
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Decodes a tag from a binary string to our standard ascii format ("GGGG,EEEE").
|
75
|
+
def decode_tag
|
76
|
+
length = 4
|
77
|
+
# Check if values are valid:
|
78
|
+
if (@index + length) > @string.length
|
79
|
+
# The index number is bigger then the length of the binary string.
|
80
|
+
# We have reached the end and will return nil.
|
81
|
+
tag = nil
|
82
|
+
else
|
83
|
+
# Decode and process:
|
84
|
+
string = @string.slice(@index, length).unpack(@hex)[0].upcase
|
85
|
+
if @endian
|
86
|
+
tag = string[2..3] + string[0..1] + "," + string[6..7] + string[4..5]
|
87
|
+
else
|
88
|
+
tag = string[0..3] + "," + string[4..7]
|
89
|
+
end
|
90
|
+
# Update our position in the string:
|
91
|
+
skip(length)
|
92
|
+
end
|
93
|
+
return tag
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Encodes content (string, number, array of numbers) and returns the binary string.
|
98
|
+
def encode(value, type)
|
99
|
+
bin = [value].pack(vr_to_str(type))
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
# Encodes content (string, number, array of numbers) to a binary string and pastes it to
|
104
|
+
# the beginning of the @string variable of this instance.
|
105
|
+
def encode_first(value, type)
|
106
|
+
bin = [value].pack(vr_to_str(type))
|
107
|
+
@string = bin + @string
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Encodes content (string, number, array of numbers) to a binary string and pastes it to
|
112
|
+
# the end of the @string variable of this instance.
|
113
|
+
def encode_last(value, type)
|
114
|
+
bin = [value].pack(vr_to_str(type))
|
115
|
+
@string = @string + bin
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
# Some of the strings that are passed along in the network communication are allocated a fixed length.
|
120
|
+
# This method is used to encode such strings.
|
121
|
+
def encode_string_with_trailing_spaces(string, target_length)
|
122
|
+
length = string.length
|
123
|
+
if length < target_length
|
124
|
+
return [string].pack(@str)+["20"*(target_length-length)].pack(@hex)
|
125
|
+
elsif length == target_length
|
126
|
+
return [string].pack(@str)
|
127
|
+
else
|
128
|
+
raise "The string provided is longer than the allowed maximum length. (string: #{string}, target_length: #{target_length.to_s})"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
# Encodes a tag from its standard text format ("GGGG,EEEE"), to a proper binary string.
|
134
|
+
def encode_tag(string)
|
135
|
+
if @endian
|
136
|
+
tag = string[2..3] + string[0..1] + string[7..8] + string[5..6]
|
137
|
+
else
|
138
|
+
tag = string[0..3] + string[5..8]
|
139
|
+
end
|
140
|
+
return [tag].pack(@hex)
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# Encodes and returns a data element value. The reason for not using the encode()
|
145
|
+
# method for this is that we may need to know the length of the encoded value before
|
146
|
+
# we send it to the encode() method.
|
147
|
+
# String values will be checked for possible odd length, and if so padded with an extra byte,
|
148
|
+
# to comply with the DICOM standard.
|
149
|
+
def encode_value(value, type)
|
150
|
+
type = vr_to_str(type)
|
151
|
+
if type == @str
|
152
|
+
# String: Check length.
|
153
|
+
if value.length[0] == 1
|
154
|
+
# Odd length (add a zero byte, encode and return):
|
155
|
+
return [value].pack(type)+["00"].pack(@hex)
|
156
|
+
else
|
157
|
+
# Even length (encode and return):
|
158
|
+
return [value].pack(type)
|
159
|
+
end
|
160
|
+
elsif type == @hex
|
161
|
+
return [value].pack(type)
|
162
|
+
else
|
163
|
+
# Number.
|
164
|
+
return [value].pack(type)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# Extracts and returns a binary string of the given length from the current @index position and out.
|
170
|
+
def extract(length)
|
171
|
+
str = @string.slice(@index, length)
|
172
|
+
skip(length)
|
173
|
+
return str
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
# Returns the total length of the binary string of this instance.
|
178
|
+
def length
|
179
|
+
return @string.length
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
# Calculates and returns the remaining length of the binary string of this instance.
|
184
|
+
def rest_length
|
185
|
+
length = @string.length - @index
|
186
|
+
return length
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
# Extracts and returns the remaining binary string of this instance.
|
191
|
+
# (the part of the string which occurs after the position of the @index variable)
|
192
|
+
def rest_string
|
193
|
+
str = @string[@index..(@string.length-1)]
|
194
|
+
return str
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
# Resets the string variable (along with the index variable).
|
199
|
+
def reset
|
200
|
+
@string = ""
|
201
|
+
@index = 0
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
# Resets the string index variable.
|
206
|
+
def reset_index
|
207
|
+
@index = 0
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
# This method updates the endianness to be used for the binary string, and checks
|
212
|
+
# the system endianness to determine which encoding/decoding flags to use.
|
213
|
+
def set_endian(str_endian)
|
214
|
+
# Update endianness variables:
|
215
|
+
@str_endian = str_endian
|
216
|
+
configure_endian
|
217
|
+
set_string_formats
|
218
|
+
set_format_hash
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
# Set a file variable for the Stream class.
|
223
|
+
# For performance reasons, we will enable the Stream class to write directly
|
224
|
+
# to file, to avoid expensive string operations which will otherwise slow down write performance.
|
225
|
+
def set_file(file)
|
226
|
+
@file = file
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
# Set a new binary string for this instance.
|
231
|
+
def set_string(binary)
|
232
|
+
binary = binary[0] if binary.is_a?(Array)
|
233
|
+
@string = binary
|
234
|
+
@index = 0
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
# Applies an offset (positive or negative) to the index variable.
|
239
|
+
def skip(offset)
|
240
|
+
@index += offset
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
# Write a binary string to file.
|
245
|
+
def write(string)
|
246
|
+
@file.write(string)
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
# Following methods are private:
|
251
|
+
private
|
252
|
+
|
253
|
+
|
254
|
+
# Determine the endianness of the system.
|
255
|
+
# Together with the specified endianness of the binary string,
|
256
|
+
# this will decide what encoding/decoding flags to use.
|
257
|
+
def configure_endian
|
258
|
+
x = 0xdeadbeef
|
259
|
+
endian_type = {
|
260
|
+
Array(x).pack("V*") => false, #:little
|
261
|
+
Array(x).pack("N*") => true #:big
|
262
|
+
}
|
263
|
+
@sys_endian = endian_type[Array(x).pack("L*")]
|
264
|
+
# Use a "relationship endian" variable to guide encoding/decoding options:
|
265
|
+
if @sys_endian == @str_endian
|
266
|
+
@endian = true
|
267
|
+
else
|
268
|
+
@endian = false
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
# Convert a data element type (VR) to a encode/decode string.
|
274
|
+
def vr_to_str(vr)
|
275
|
+
str = @format[vr]
|
276
|
+
if str == nil
|
277
|
+
errors << "Warning: Element type #{vr} does not have a reading method assigned to it. Something is not implemented correctly or the DICOM data analyzed is invalid."
|
278
|
+
str = @hex
|
279
|
+
end
|
280
|
+
return str
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
# Set the hash which is used to convert a data element type (VR) to a encode/decode string.
|
285
|
+
def set_format_hash
|
286
|
+
@format = {
|
287
|
+
"BY" => @by, # Byte/Character (1-byte integers)
|
288
|
+
"US" => @us, # Unsigned short (2 bytes)
|
289
|
+
"SS" => @ss, # Signed short (2 bytes)
|
290
|
+
"UL" => @ul, # Unsigned long (4 bytes)
|
291
|
+
"SL" => @sl, # Signed long (4 bytes)
|
292
|
+
"FL" => @fs, # Floating point single (4 bytes)
|
293
|
+
"FD" => @fd, # Floating point double (8 bytes)
|
294
|
+
"OB" => @by, # Other byte string (1-byte integers)
|
295
|
+
"OF" => @fs, # Other float string (4-byte floating point numbers)
|
296
|
+
"OW" => @us, # Other word string (2-byte integers)
|
297
|
+
"AT" => @hex, # Tag reference (4 bytes) NB: This may need to be revisited at some point...
|
298
|
+
"UN" => @hex, # Unknown information (header element is not recognized from local database)
|
299
|
+
"HEX" => @hex, # HEX
|
300
|
+
# We have a number of VRs that are decoded as string:
|
301
|
+
"AE" => @str,
|
302
|
+
"AS" => @str,
|
303
|
+
"CS" => @str,
|
304
|
+
"DA" => @str,
|
305
|
+
"DS" => @str,
|
306
|
+
"DT" => @str,
|
307
|
+
"IS" => @str,
|
308
|
+
"LO" => @str,
|
309
|
+
"LT" => @str,
|
310
|
+
"PN" => @str,
|
311
|
+
"SH" => @str,
|
312
|
+
"ST" => @str,
|
313
|
+
"TM" => @str,
|
314
|
+
"UI" => @str,
|
315
|
+
"UT" => @str,
|
316
|
+
"STR" => @str
|
317
|
+
}
|
318
|
+
end
|
319
|
+
|
320
|
+
|
321
|
+
# Sets the pack/unpack format strings that will be used for encoding/decoding.
|
322
|
+
# Some of these will depend on the endianness of the system and file.
|
323
|
+
def set_string_formats
|
324
|
+
if @endian
|
325
|
+
# System endian equals string endian:
|
326
|
+
# Native byte order.
|
327
|
+
@by = "C*" # Byte (1 byte)
|
328
|
+
@us = "S*" # Unsigned short (2 bytes)
|
329
|
+
@ss = "s*" # Signed short (2 bytes)
|
330
|
+
@ul = "I*" # Unsigned long (4 bytes)
|
331
|
+
@sl = "l*" # Signed long (4 bytes)
|
332
|
+
@fs = "e*" # Floating point single (4 bytes)
|
333
|
+
@fd = "E*" # Floating point double ( 8 bytes)
|
334
|
+
else
|
335
|
+
# System endian is opposite string endian:
|
336
|
+
# Network byte order.
|
337
|
+
@by = "C*"
|
338
|
+
@us = "n*"
|
339
|
+
@ss = "n*" # Not correct (gives US)
|
340
|
+
@ul = "N*"
|
341
|
+
@sl = "N*" # Not correct (gives UL)
|
342
|
+
@fs = "g*"
|
343
|
+
@fd = "G*"
|
344
|
+
end
|
345
|
+
# Format strings that are not dependent on endianness:
|
346
|
+
@str = "a*"
|
347
|
+
@hex = "H*" # (this may be dependent on endianness(?))
|
348
|
+
end
|
349
|
+
|
350
|
+
end # of class
|
351
|
+
end # of module
|
data/lib/dicom.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
# Core library:
|
2
|
+
require 'DClient'
|
2
3
|
require 'Dictionary'
|
3
4
|
require 'DLibrary'
|
4
5
|
require 'DObject'
|
5
6
|
require 'DRead'
|
7
|
+
require 'DServer'
|
6
8
|
require 'DWrite'
|
7
|
-
require '
|
9
|
+
require 'Link'
|
10
|
+
require 'Stream'
|
8
11
|
# Extended library:
|
9
|
-
require 'Anonymizer'
|
12
|
+
require 'Anonymizer'
|
13
|
+
# Ruby library extensions:
|
14
|
+
require 'ruby_extensions'
|
data/lib/ruby_extensions.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dicom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.6"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christoffer Lervag
|
@@ -9,11 +9,11 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-08-13 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description: DICOM is a standard widely used throughout the world to store and transfer medical image data. This project aims to make a library that is able to handle DICOM in the Ruby language, to the benefit of any student or professional who would like to use Ruby to process their DICOM files.
|
16
|
+
description: DICOM is a standard widely used throughout the world to store and transfer medical image data. This project aims to make a library that is able to handle DICOM in the Ruby language, to the benefit of any student or professional who would like to use Ruby to process their DICOM files or communicate across the network.
|
17
17
|
email: chris.lervag@gmail.com
|
18
18
|
executables: []
|
19
19
|
|
@@ -26,7 +26,11 @@ files:
|
|
26
26
|
- lib/DRead.rb
|
27
27
|
- lib/DWrite.rb
|
28
28
|
- lib/dicom.rb
|
29
|
+
- lib/DServer.rb
|
30
|
+
- lib/DClient.rb
|
31
|
+
- lib/Stream.rb
|
29
32
|
- lib/Anonymizer.rb
|
33
|
+
- lib/Link.rb
|
30
34
|
- lib/DLibrary.rb
|
31
35
|
- lib/ruby_extensions.rb
|
32
36
|
- lib/Dictionary.rb
|
@@ -34,7 +38,7 @@ files:
|
|
34
38
|
- README
|
35
39
|
- COPYING
|
36
40
|
- CHANGELOG
|
37
|
-
has_rdoc:
|
41
|
+
has_rdoc: true
|
38
42
|
homepage: http://dicom.rubyforge.org/
|
39
43
|
licenses: []
|
40
44
|
|
@@ -58,9 +62,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
62
|
requirements: []
|
59
63
|
|
60
64
|
rubyforge_project: dicom
|
61
|
-
rubygems_version: 1.3.
|
65
|
+
rubygems_version: 1.3.3
|
62
66
|
signing_key:
|
63
67
|
specification_version: 3
|
64
|
-
summary: Library for
|
68
|
+
summary: Library for handling DICOM files and network communication.
|
65
69
|
test_files: []
|
66
70
|
|