dicom 0.7 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,50 +0,0 @@
1
- # Copyright 2010 Christoffer Lervag
2
-
3
- # The purpose of this file is to make it very easy for users to customise the way
4
- # DICOM files are handled when they are received through the network.
5
- # The default behaviour is to save the file to disk using a folder structure determined by the file's DICOM tags.
6
- # Some suggested alternatives:
7
- # - Analyzing tags and/or image data to determine further actions.
8
- # - Modify the DICOM object before it is saved to disk.
9
- # - Modify the folder structure in which DICOM files are saved to disk.
10
- # - Store DICOM contents in a database (highly relevant if you are building a Ruby on Rails application).
11
- # - Retransmit the DICOM object to another network destination using the DClient class.
12
- # - Write information to a log file.
13
-
14
- module DICOM
15
-
16
- # This class handles DICOM files that have been received through network communication.
17
- class FileHandler
18
-
19
- # Handles the reception of a DICOM file.
20
- # Default action: Save to disk.
21
- # Modify this method if you want a different behaviour!
22
- def self.receive_file(obj, path_prefix, transfer_syntax)
23
- # Did we receive a valid DICOM file?
24
- if obj.read_success
25
- # File name is set using the SOP Instance UID
26
- file_name = obj.get_value("0008,0018") || "no_SOP_UID.dcm"
27
- # File will be saved with the following path:
28
- # path_prefix/<PatientID>/<StudyDate>/<Modality>/
29
- folders = Array.new(3)
30
- folders[0] = obj.get_value("0010,0020") || "PatientID"
31
- folders[1] = obj.get_value("0008,0020") || "StudyDate"
32
- folders[2] = obj.get_value("0008,0060") || "Modality"
33
- local_path = folders.join(File::SEPARATOR) + File::SEPARATOR + file_name
34
- full_path = path_prefix + local_path
35
- # Save the DICOM object to disk:
36
- obj.write(full_path, transfer_syntax)
37
- # As the file has been received successfully, set the success boolean and a corresponding 'success string':
38
- success = true
39
- message = "DICOM file saved to: #{full_path}"
40
- else
41
- # Received data was not successfully read as a DICOM file.
42
- success = false
43
- message = "Error: The received file was not successfully parsed as a DICOM object."
44
- end
45
- # A boolean indicating success/failure, and a message string must be returned:
46
- return success, message
47
- end
48
-
49
- end # of class
50
- end # of module
data/lib/dicom/Stream.rb DELETED
@@ -1,354 +0,0 @@
1
- # Copyright 2009-2010 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
- value = [value] unless value.is_a?(Array)
100
- return value.pack(vr_to_str(type))
101
- end
102
-
103
-
104
- # Encodes content (string, number, array of numbers) to a binary string and pastes it to
105
- # the beginning of the @string variable of this instance.
106
- def encode_first(value, type)
107
- value = [value] unless value.is_a?(Array)
108
- bin = value.pack(vr_to_str(type))
109
- @string = bin + @string
110
- end
111
-
112
-
113
- # Encodes content (string, number, array of numbers) to a binary string and pastes it to
114
- # the end of the @string variable of this instance.
115
- def encode_last(value, type)
116
- value = [value] unless value.is_a?(Array)
117
- bin = value.pack(vr_to_str(type))
118
- @string = @string + bin
119
- end
120
-
121
-
122
- # Some of the strings that are passed along in the network communication are allocated a fixed length.
123
- # This method is used to encode such strings.
124
- def encode_string_with_trailing_spaces(string, target_length)
125
- length = string.length
126
- if length < target_length
127
- return [string].pack(@str)+["20"*(target_length-length)].pack(@hex)
128
- elsif length == target_length
129
- return [string].pack(@str)
130
- else
131
- raise "The string provided is longer than the allowed maximum length. (string: #{string}, target_length: #{target_length.to_s})"
132
- end
133
- end
134
-
135
-
136
- # Encodes a tag from its standard text format ("GGGG,EEEE"), to a proper binary string.
137
- def encode_tag(string)
138
- if @endian
139
- tag = string[2..3] + string[0..1] + string[7..8] + string[5..6]
140
- else
141
- tag = string[0..3] + string[5..8]
142
- end
143
- return [tag].pack(@hex)
144
- end
145
-
146
-
147
- # Encodes and returns a data element value. The reason for not using the encode()
148
- # method for this is that we may need to know the length of the encoded value before
149
- # we send it to the encode() method.
150
- # String values will be checked for possible odd length, and if so padded with an extra byte,
151
- # to comply with the DICOM standard.
152
- def encode_value(value, type)
153
- type = vr_to_str(type)
154
- if type == @str
155
- # String: Check length.
156
- if value.length[0] == 1
157
- # Odd length (add a zero byte, encode and return):
158
- return [value].pack(type)+["00"].pack(@hex)
159
- else
160
- # Even length (encode and return):
161
- return [value].pack(type)
162
- end
163
- elsif type == @hex
164
- return [value].pack(type)
165
- else
166
- # Number.
167
- return [value].pack(type)
168
- end
169
- end
170
-
171
-
172
- # Extracts and returns a binary string of the given length from the current @index position and out.
173
- def extract(length)
174
- str = @string.slice(@index, length)
175
- skip(length)
176
- return str
177
- end
178
-
179
-
180
- # Returns the total length of the binary string of this instance.
181
- def length
182
- return @string.length
183
- end
184
-
185
-
186
- # Calculates and returns the remaining length of the binary string of this instance.
187
- def rest_length
188
- length = @string.length - @index
189
- return length
190
- end
191
-
192
-
193
- # Extracts and returns the remaining binary string of this instance.
194
- # (the part of the string which occurs after the position of the @index variable)
195
- def rest_string
196
- str = @string[@index..(@string.length-1)]
197
- return str
198
- end
199
-
200
-
201
- # Resets the string variable (along with the index variable).
202
- def reset
203
- @string = ""
204
- @index = 0
205
- end
206
-
207
-
208
- # Resets the string index variable.
209
- def reset_index
210
- @index = 0
211
- end
212
-
213
-
214
- # This method updates the endianness to be used for the binary string, and checks
215
- # the system endianness to determine which encoding/decoding flags to use.
216
- def set_endian(str_endian)
217
- # Update endianness variables:
218
- @str_endian = str_endian
219
- configure_endian
220
- set_string_formats
221
- set_format_hash
222
- end
223
-
224
-
225
- # Set a file variable for the Stream class.
226
- # For performance reasons, we will enable the Stream class to write directly
227
- # to file, to avoid expensive string operations which will otherwise slow down write performance.
228
- def set_file(file)
229
- @file = file
230
- end
231
-
232
-
233
- # Set a new binary string for this instance.
234
- def set_string(binary)
235
- binary = binary[0] if binary.is_a?(Array)
236
- @string = binary
237
- @index = 0
238
- end
239
-
240
-
241
- # Applies an offset (positive or negative) to the index variable.
242
- def skip(offset)
243
- @index += offset
244
- end
245
-
246
-
247
- # Write a binary string to file.
248
- def write(string)
249
- @file.write(string)
250
- end
251
-
252
-
253
- # Following methods are private:
254
- private
255
-
256
-
257
- # Determine the endianness of the system.
258
- # Together with the specified endianness of the binary string,
259
- # this will decide what encoding/decoding flags to use.
260
- def configure_endian
261
- x = 0xdeadbeef
262
- endian_type = {
263
- Array(x).pack("V*") => false, #:little
264
- Array(x).pack("N*") => true #:big
265
- }
266
- @sys_endian = endian_type[Array(x).pack("L*")]
267
- # Use a "relationship endian" variable to guide encoding/decoding options:
268
- if @sys_endian == @str_endian
269
- @endian = true
270
- else
271
- @endian = false
272
- end
273
- end
274
-
275
-
276
- # Convert a data element type (VR) to a encode/decode string.
277
- def vr_to_str(vr)
278
- str = @format[vr]
279
- if str == nil
280
- 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."
281
- str = @hex
282
- end
283
- return str
284
- end
285
-
286
-
287
- # Set the hash which is used to convert a data element type (VR) to a encode/decode string.
288
- def set_format_hash
289
- @format = {
290
- "BY" => @by, # Byte/Character (1-byte integers)
291
- "US" => @us, # Unsigned short (2 bytes)
292
- "SS" => @ss, # Signed short (2 bytes)
293
- "UL" => @ul, # Unsigned long (4 bytes)
294
- "SL" => @sl, # Signed long (4 bytes)
295
- "FL" => @fs, # Floating point single (4 bytes)
296
- "FD" => @fd, # Floating point double (8 bytes)
297
- "OB" => @by, # Other byte string (1-byte integers)
298
- "OF" => @fs, # Other float string (4-byte floating point numbers)
299
- "OW" => @us, # Other word string (2-byte integers)
300
- "AT" => @hex, # Tag reference (4 bytes) NB: This may need to be revisited at some point...
301
- "UN" => @hex, # Unknown information (header element is not recognized from local database)
302
- "HEX" => @hex, # HEX
303
- # We have a number of VRs that are decoded as string:
304
- "AE" => @str,
305
- "AS" => @str,
306
- "CS" => @str,
307
- "DA" => @str,
308
- "DS" => @str,
309
- "DT" => @str,
310
- "IS" => @str,
311
- "LO" => @str,
312
- "LT" => @str,
313
- "PN" => @str,
314
- "SH" => @str,
315
- "ST" => @str,
316
- "TM" => @str,
317
- "UI" => @str,
318
- "UT" => @str,
319
- "STR" => @str
320
- }
321
- end
322
-
323
-
324
- # Sets the pack/unpack format strings that will be used for encoding/decoding.
325
- # Some of these will depend on the endianness of the system and file.
326
- def set_string_formats
327
- if @endian
328
- # System endian equals string endian:
329
- # Native byte order.
330
- @by = "C*" # Byte (1 byte)
331
- @us = "S*" # Unsigned short (2 bytes)
332
- @ss = "s*" # Signed short (2 bytes)
333
- @ul = "I*" # Unsigned long (4 bytes)
334
- @sl = "l*" # Signed long (4 bytes)
335
- @fs = "e*" # Floating point single (4 bytes)
336
- @fd = "E*" # Floating point double ( 8 bytes)
337
- else
338
- # System endian is opposite string endian:
339
- # Network byte order.
340
- @by = "C*"
341
- @us = "n*"
342
- @ss = "n*" # Not correct (gives US)
343
- @ul = "N*"
344
- @sl = "N*" # Not correct (gives UL)
345
- @fs = "g*"
346
- @fd = "G*"
347
- end
348
- # Format strings that are not dependent on endianness:
349
- @str = "a*"
350
- @hex = "H*" # (this may be dependent on endianness(?))
351
- end
352
-
353
- end # of class
354
- end # of module