dicom 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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 'ruby_extensions'
9
+ require 'Link'
10
+ require 'Stream'
8
11
  # Extended library:
9
- require 'Anonymizer'
12
+ require 'Anonymizer'
13
+ # Ruby library extensions:
14
+ require 'ruby_extensions'
@@ -22,4 +22,15 @@ class Array
22
22
  result
23
23
  end
24
24
 
25
+ end
26
+
27
+ class String
28
+
29
+ # Check if a given string appears to be a valid tag:
30
+ def is_a_tag?
31
+ result = false
32
+ result = true if self.length == 9 and self[4..4] == ','
33
+ result
34
+ end
35
+
25
36
  end
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.5"
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-05-06 00:00:00 +02:00
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: false
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.2
65
+ rubygems_version: 1.3.3
62
66
  signing_key:
63
67
  specification_version: 3
64
- summary: Library for reading, editing and writing DICOM files.
68
+ summary: Library for handling DICOM files and network communication.
65
69
  test_files: []
66
70