dicom 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,31 +11,45 @@ module DICOM
11
11
  # Class for reading the data from a DICOM file:
12
12
  class DRead
13
13
 
14
- attr_reader :success,:names,:tags,:types,:lengths,:values,:raw,:levels,:explicit,:file_endian,:msg
14
+ attr_reader :success, :names, :tags, :types, :lengths, :values, :raw, :levels, :explicit, :file_endian, :msg
15
15
 
16
16
  # Initialize the DRead instance.
17
- def initialize(file_name=nil, opts={})
17
+ def initialize(string=nil, options={})
18
18
  # Process option values, setting defaults for the ones that are not specified:
19
- @lib = opts[:lib] || DLibrary.new
20
- @sys_endian = opts[:sys_endian] || false
19
+ @lib = options[:lib] || DLibrary.new
20
+ @sys_endian = options[:sys_endian] || false
21
+ @bin = options[:bin]
22
+ @transfer_syntax = options[:syntax]
21
23
  # Initiate the variables that are used during file reading:
22
- init_variables()
24
+ init_variables
23
25
 
24
- # Test if file is readable and open it to the @file variable:
25
- open_file(file_name)
26
-
27
- # Read the initial header of the file:
28
- if @file == nil
29
- # File is not readable, so we return:
30
- return
26
+ # Are we going to read from a file, or read from a binary string:
27
+ if @bin
28
+ # Read from the provided binary string:
29
+ @str = string
31
30
  else
31
+ # Read from file:
32
+ open_file(string)
33
+ # Read the initial header of the file:
34
+ if @file == nil
35
+ # File is not readable, so we return:
36
+ return
37
+ else
38
+ # Extract the content of the file to a binary string:
39
+ @str = @file.read
40
+ @file.close
41
+ end
42
+ end
43
+ # Create a Stream instance to handle the decoding of content from this binary string:
44
+ @stream = Stream.new(@str, @file_endian, @explicit)
45
+ # Do not check for header information when supplied a (network) binary string:
46
+ unless @bin
32
47
  # Read and verify the DICOM header:
33
- header = check_header()
34
- # If the file didnt have the expected header, we will attempt to read data elements from the very start of the file:
48
+ header = check_header
49
+ # If the file didnt have the expected header, we will attempt to read
50
+ # data elements from the very start file:
35
51
  if header == false
36
- @file.close()
37
- @file = File.new(file_name, "rb")
38
- @header_length = 0
52
+ @stream.skip(-132)
39
53
  elsif header == nil
40
54
  # Not a valid DICOM file, return:
41
55
  return
@@ -46,12 +60,10 @@ module DICOM
46
60
  # (Data element information is stored in arrays by the method process_data_element)
47
61
  data_element = true
48
62
  while data_element != false do
49
- data_element = process_data_element()
63
+ data_element = process_data_element
50
64
  end
51
65
 
52
66
  # Post processing:
53
- # Close the file as we are finished reading it:
54
- @file.close()
55
67
  # Assume file has been read successfully:
56
68
  @success = true
57
69
  # Check if the last element was read out correctly (that the length of its data (@raw.last.length)
@@ -59,12 +71,65 @@ module DICOM
59
71
  # We only run this test if the last element has a positive expectation value, obviously.
60
72
  if @lengths.last.to_i > 0
61
73
  if @raw.last.length != @lengths.last
62
- @msg += ["Error! The data content read from file does not match the length specified for the tag #{@tags.last}. It seems this is either an invalid or corrupt DICOM file. Returning."]
74
+ @msg << "Error! The data content read from file does not match the length specified for the tag #{@tags.last}. It seems this is either an invalid or corrupt DICOM file. Returning."
63
75
  @success = false
64
76
  return
65
77
  end
66
78
  end
67
- end # of method initialize
79
+ end # of initialize
80
+
81
+
82
+ # Extract an array of binary strings
83
+ # (this is typically used if one intends to transmit the DICOM file through a network connection)
84
+ def extract_segments(size)
85
+ # For this purpose we are not interested to include header or meta information.
86
+ # We must therefore find the position of the first tag which is not a meta information tag.
87
+ pos = first_non_meta
88
+ # Start position:
89
+ if pos == 0
90
+ start = 0
91
+ else
92
+ # First byte after the integrated length of the previous tag is our start:
93
+ start = @integrated_lengths[pos-1]
94
+ end
95
+ # Iterate through the tags and monitor the integrated_lengths values to determine
96
+ # when we need to start a new segment.
97
+ segments = Array.new
98
+ last_pos = pos
99
+ @tags.each_index do |i|
100
+ # Have we passed the size limit?
101
+ if (@integrated_lengths[i] - start) > size
102
+ # We either need to stop the current segment at the previous tag, or if
103
+ # this is a long tag (typically image data), we need to split its data
104
+ # and put it in several segments.
105
+ if (@integrated_lengths[i] - @integrated_lengths[i-1]) > size
106
+ # This element's value needs to be split up into several segments.
107
+ # How many segments are needed to fit this element?
108
+ number = ((@integrated_lengths[i] - start).to_f / size.to_f).ceil
109
+ number.times do
110
+ # Extract data and add to segments:
111
+ last_pos = (start+size-1)
112
+ segments << @stream.string[start..last_pos]
113
+ # Update start position for next segment:
114
+ start = last_pos + 1
115
+ end
116
+ else
117
+ # End the current segment at the last data element, then start the new segment with this element.
118
+ last_pos = @integrated_lengths[i-1]
119
+ segments << @stream.string[start..last_pos]
120
+ # Update start position for next segment:
121
+ start = last_pos + 1
122
+ end
123
+ end
124
+ end
125
+ # After running the above iteration, it is possible that we have some data elements remaining
126
+ # at the end of the file who's length are beneath the size limit, and thus has not been put into a segment.
127
+ if (last_pos + 1) < @stream.string.length
128
+ # Add the remaining data elements to a segment:
129
+ segments << @stream.string[start..@stream.string.length]
130
+ end
131
+ return segments
132
+ end
68
133
 
69
134
 
70
135
  # Following methods are private:
@@ -72,40 +137,40 @@ module DICOM
72
137
 
73
138
 
74
139
  # Checks the initial header of the DICOM file.
75
- def check_header()
140
+ def check_header
76
141
  # According to the official DICOM standard, a DICOM file shall contain 128
77
142
  # consequtive (zero) bytes followed by 4 bytes that spell the string 'DICM'.
78
143
  # Apparently, some providers seems to skip this in their DICOM files.
79
- bin1 = @file.read(128)
80
- @header_length += 128
81
- # Next 4 bytes should spell 'DICM':
82
- bin2 = @file.read(4)
83
- @header_length += 4
84
- # Check if this binary was successfully read (if not, this short file is not a valid DICOM file and we will return):
85
- if bin2
86
- dicm = bin2.unpack('a' * 4).join
87
- else
144
+ # Check that the file is long enough to contain a valid header:
145
+ if @str.length < 132
146
+ # This does not seem to be a valid DICOM file and so we return.
88
147
  return nil
89
- end
90
- if dicm != 'DICM' then
91
- # Header is not valid (we will still try to read it is a DICOM file though):
92
- @msg += ["Warning: The specified file does not contain the official DICOM header. Will try to read the file anyway, as some sources are known to skip this header."]
93
- # As the file is not conforming to the DICOM standard, it is possible that it does not contain a
94
- # transfer syntax element, and as such, we attempt to choose the most probable encoding values here:
95
- @explicit = false
96
- return false
97
148
  else
98
- # Header is valid:
99
- return true
149
+ @stream.skip(128)
150
+ # Next 4 bytes should spell "DICM":
151
+ identifier = @stream.decode(4, "STR")
152
+ @header_length += 132
153
+ if identifier != "DICM" then
154
+ # Header is not valid (we will still try to read it is a DICOM file though):
155
+ @msg << "Warning: The specified file does not contain the official DICOM header. Will try to read the file anyway, as some sources are known to skip this header."
156
+ # As the file is not conforming to the DICOM standard, it is possible that it does not contain a
157
+ # transfer syntax element, and as such, we attempt to choose the most probable encoding values here:
158
+ @explicit = false
159
+ @stream.explicit = false
160
+ return false
161
+ else
162
+ # Header is valid:
163
+ return true
164
+ end
100
165
  end
101
- end # of method check_header
166
+ end
102
167
 
103
168
 
104
169
  # Governs the process of reading data elements from the DICOM file.
105
- def process_data_element()
170
+ def process_data_element
106
171
  #STEP 1: ------------------------------------------------------
107
172
  # Attempt to read data element tag, but abort if we have reached end of file:
108
- tag = read_tag()
173
+ tag = read_tag
109
174
  if tag == false
110
175
  # End of file, no more elements.
111
176
  return false
@@ -155,102 +220,83 @@ module DICOM
155
220
  # Set the hiearchy level of this data element:
156
221
  set_level(level_type, length, tag, name)
157
222
  # Transfer the gathered data to arrays and return true:
158
- @names += [name]
159
- @tags += [tag]
160
- @types += [type]
161
- @lengths += [length]
162
- @values += [value]
163
- @raw += [raw]
223
+ @names << name
224
+ @tags << tag
225
+ @types << type
226
+ @lengths << length
227
+ @values << value
228
+ @raw << raw
164
229
  return true
165
- end # of method process_data_element
230
+ end # of process_data_element
166
231
 
167
232
 
168
233
  # Reads and returns the data element's TAG (4 first bytes of element).
169
- def read_tag()
170
- bin1 = @file.read(2)
171
- bin2 = @file.read(2)
172
- # Do not proceed if we have reached end of file:
173
- if bin2 == nil
174
- return false
175
- end
176
- # Add the length of the data element tag. If this was the first element read from file, we need to add the header length too:
234
+ def read_tag
235
+ tag = @stream.decode_tag
236
+ # Do not proceed if we have reached end of file (tag is nil):
237
+ return false unless tag
238
+ # Tag was valid, so we add the length of the data element tag.
239
+ # If this was the first element read from file, we need to add the header length too:
177
240
  if @integrated_lengths.length == 0
178
241
  # Increase the array with the length of the header + the 4 bytes:
179
- @integrated_lengths += [@header_length + 4]
242
+ @integrated_lengths << (@header_length + 4)
180
243
  else
181
244
  # For the remaining elements, increase the array with the integrated length of the previous elements + the 4 bytes:
182
- @integrated_lengths += [@integrated_lengths[@integrated_lengths.length-1] + 4]
183
- end
184
- # Unpack the blobs:
185
- tag1 = bin1.unpack('h*')[0].reverse.upcase
186
- tag2 = bin2.unpack('h*')[0].reverse.upcase
187
- # Whether DICOM file is big or little endian, the first 0002 group is always little endian encoded.
188
- # In case of big endian system:
189
- if @sys_endian
190
- # Rearrange the numbers (# This has never been tested btw.):
191
- tag1 = tag1[2..3]+tag1[0..1]
192
- tag2 = tag2[2..3]+tag2[0..1]
245
+ @integrated_lengths << (@integrated_lengths[@integrated_lengths.length-1] + 4)
193
246
  end
194
247
  # When we shift from group 0002 to another group we need to update our endian/explicitness variables:
195
- if tag1 != "0002" and @switched == false
196
- switch_syntax()
197
- end
198
- # Perhaps we need to rearrange the tag strings?
199
- if not @endian
200
- # Need to rearrange the first and second part of each string:
201
- tag1 = tag1[2..3]+tag1[0..1]
202
- tag2 = tag2[2..3]+tag2[0..1]
248
+ if tag[0..3] != "0002" and @switched == false
249
+ switch_syntax
203
250
  end
204
- # Join the tag group & element part together to form the final, complete string:
205
- return tag1+","+tag2
206
- end # of method read_tag
251
+ return tag
252
+ end
207
253
 
208
254
 
209
- # Reads and returns data element TYPE (VR) (2 bytes) and data element LENGTH (Varying length).
255
+ # Reads and returns data element TYPE (VR) (2 bytes) and data element LENGTH (Varying length; 2-6 bytes).
210
256
  def read_type_length(type,tag)
211
257
  # Structure will differ, dependent on whether we have explicit or implicit encoding:
258
+ pre_skip = 0
259
+ bytes = 0
212
260
  # *****EXPLICIT*****:
213
261
  if @explicit == true
214
262
  # Step 1: Read VR (if it exists)
215
263
  unless tag == "FFFE,E000" or tag == "FFFE,E00D" or tag == "FFFE,E0DD"
216
264
  # Read the element's type (2 bytes - since we are not dealing with an item related element):
217
- bin = @file.read(2)
265
+ type = @stream.decode(2, "STR")
218
266
  @integrated_lengths[@integrated_lengths.length-1] += 2
219
- type = bin.unpack('a*').join
220
267
  end
221
268
  # Step 2: Read length
222
269
  # Three possible structures for value length here, dependent on element type:
223
270
  case type
224
271
  when "OB","OW","SQ","UN"
225
272
  # 6 bytes total:
226
- # Two empty first:
227
- bin = @file.read(2)
228
- @integrated_lengths[@integrated_lengths.length-1] += 2
273
+ # Two empty bytes first:
274
+ pre_skip = 2
229
275
  # Value length (4 bytes):
230
- bin = @file.read(4)
231
- @integrated_lengths[@integrated_lengths.length-1] += 4
232
- length = bin.unpack(@ul)[0]
276
+ bytes = 4
233
277
  when "()"
234
278
  # 4 bytes:
235
279
  # For elements "FFFE,E000", "FFFE,E00D" and "FFFE,E0DD"
236
- bin = @file.read(4)
237
- @integrated_lengths[@integrated_lengths.length-1] += 4
238
- length = bin.unpack(@ul)[0]
280
+ bytes = 4
239
281
  else
240
282
  # 2 bytes:
241
283
  # For all the other element types, value length is 2 bytes:
242
- bin = @file.read(2)
243
- @integrated_lengths[@integrated_lengths.length-1] += 2
244
- length = bin.unpack(@us)[0]
284
+ bytes = 2
245
285
  end
246
286
  else
247
287
  # *****IMPLICIT*****:
248
- # No VR (retrieved from library based on the data element's tag)
249
- # Reading value length (4 bytes):
250
- bin = @file.read(4)
251
- @integrated_lengths[@integrated_lengths.length-1] += 4
252
- length = bin.unpack(@ul)[0]
288
+ # Value length (4 bytes):
289
+ bytes = 4
290
+ end
291
+ # Handle skips and read out length value:
292
+ @stream.skip(pre_skip)
293
+ if bytes == 2
294
+ length = @stream.decode(bytes, "US") # (2)
295
+ else
296
+ length = @stream.decode(bytes, "UL") # (4)
253
297
  end
298
+ # Update integrated lengths array:
299
+ @integrated_lengths[@integrated_lengths.length-1] += (pre_skip + bytes)
254
300
  # For encapsulated data, the element length will not be defined. To convey this,
255
301
  # the hex sequence 'ff ff ff ff' is used (-1 converted to signed long, 4294967295 converted to unsigned long).
256
302
  if length == 4294967295
@@ -261,110 +307,36 @@ module DICOM
261
307
  @msg += ["Warning: Odd number of bytes in data element's length occured. This is a violation of the DICOM standard, but program will attempt to read the rest of the file anyway."]
262
308
  end
263
309
  return [type, length]
264
- end # of method read_type_length
310
+ end # of read_type_length
265
311
 
266
312
 
267
313
  # Reads and returns data element VALUE (Of varying length - which is determined at an earlier stage).
268
314
  def read_value(type, length)
269
- # Read the data:
270
- bin = @file.read(length)
315
+ # Extract the binary data:
316
+ bin = @stream.extract(length)
271
317
  @integrated_lengths[@integrated_lengths.size-1] += length
272
- # Decoding of content will naturally depend on what kind of content (VR) we have.
273
- case type
274
-
275
- # Normally the "number elements" will contain just one number, but in some cases, they contain
276
- # multiple numbers. In these cases we will read each number and store them all in a string separated by "/".
277
- # Unsigned long: (4 bytes)
278
- when "UL"
279
- if length <= 4
280
- data = bin.unpack(@ul)[0]
281
- else
282
- data = bin.unpack(@ul).join("/")
283
- end
284
-
285
- # Signed long: (4 bytes)
286
- when "SL"
287
- if length <= 4
288
- data = bin.unpack(@sl)[0]
289
- else
290
- data = bin.unpack(@sl).join("/")
291
- end
292
-
293
- # Unsigned short: (2 bytes)
294
- when "US"
295
- if length <= 2
296
- data = bin.unpack(@us)[0]
297
- else
298
- data = bin.unpack(@us).join("/")
299
- end
300
-
301
- # Signed short: (2 bytes)
302
- when "SS"
303
- if length <= 2
304
- data = bin.unpack(@ss)[0]
305
- else
306
- data = bin.unpack(@ss).join("/")
307
- end
308
-
309
- # Floating point single: (4 bytes)
310
- when "FL"
311
- if length <= 4
312
- data = bin.unpack(@fs)[0]
313
- else
314
- data = bin.unpack(@fs).join("/")
315
- end
316
-
317
- # Floating point double: (8 bytes)
318
- when "FD"
319
- if length <= 8
320
- data = bin.unpack(@fd)[0]
321
- else
322
- data = bin.unpack(@fd).join("/")
323
- end
324
-
325
- # The data element contains a tag as its value (4 bytes):
326
- when "AT"
327
- # Bytes read in following order: 1 0 , 3 2 (And Hex nibbles read in this order: Hh)
328
- # NB! This probably needs to be modified when dealing with something other than little endian.
329
- # Value is unpacked to a string in the format GGGGEEEE.
330
- data = (bin.unpack("xHXhX2HXh").join + bin.unpack("x3HXhX2HXh").join).upcase
331
- #data = (bin.unpack("xHXhX2HXh").join + "," + bin.unpack("x3HXhX2HXh").join).upcase
332
-
333
- # We have a number of VRs that are decoded as string:
334
- when 'AE','AS','CS','DA','DS','DT','IS','LO','LT','PN','SH','ST','TM','UI','UT' #,'VR'
335
- data = bin.unpack('a*').join
336
-
337
- # NB!
338
- # FOLLOWING ELEMENT TYPES WILL NOT BE DECODED.
339
- # DECODING OF PIXEL DATA IS MOVED TO DOBJECT FOR PERFORMANCE REASONS.
340
-
341
- # Unknown information, header element is not recognized from local database:
342
- when "UN"
343
- #data=bin.unpack('H*')[0]
344
-
345
- # Other byte string, 1-byte integers
346
- when "OB"
347
- #data = bin.unpack('H*')[0]
348
-
349
- # Other float string, 4-byte floating point numbers
350
- when "OF"
351
- # NB! This element type has not been tested yet with an actual DICOM file.
352
- #data = bin.unpack(@fs)
353
-
354
- # Image data:
355
- # Other word string, 2-byte integers
356
- when "OW"
357
- # empty
358
-
359
- # Unknown VR:
318
+ # Decode data?
319
+ # Some data elements (like those containing image data, compressed data or unknown data),
320
+ # will not be decoded here.
321
+ unless type == "OW" or type == "OB" or type == "OF" or type == "UN"
322
+ # "Rewind" and extract the value from this binary data:
323
+ @stream.skip(-length)
324
+ # Decode data:
325
+ value = @stream.decode(length, type)
326
+ if not value.is_a?(Array)
327
+ data = value
360
328
  else
361
- @msg += ["Warning: Element type #{type} does not have a reading method assigned to it. Please check the validity of the DICOM file."]
362
- #data = bin.unpack('H*')[0]
363
- end # of case type
364
-
329
+ # If the returned value is not a string, it is an array of multiple elements,
330
+ # which need to be joined to a string with the separator "\":
331
+ data = value.join("\\")
332
+ end
333
+ else
334
+ # No decoded data:
335
+ data = nil
336
+ end
365
337
  # Return the data:
366
338
  return [data, bin]
367
- end # of method read_value
339
+ end # of read_value
368
340
 
369
341
 
370
342
  # Sets the level of the current element in the hiearchy.
@@ -389,9 +361,9 @@ module DICOM
389
361
  @current_level = @current_level + 1
390
362
  # If length of sequence/item is specified, we must note this length + the current element position in the arrays:
391
363
  if length.to_i != 0
392
- @hierarchy += [[length,@integrated_lengths.last]]
364
+ @hierarchy << [length, @integrated_lengths.last]
393
365
  else
394
- @hierarchy += [type]
366
+ @hierarchy << type
395
367
  end
396
368
  end
397
369
  # Need to check whether a previous sequence or item has ended, if so the level must be decreased by one:
@@ -406,14 +378,14 @@ module DICOM
406
378
  if @hierarchy.size > 0
407
379
  # Do not perform this check for Pixel Data Items or Sequence Delimitation Items:
408
380
  # (If performed, it will give false errors for the case when we have Encapsulated Pixel Data)
409
- check_level_end() unless name == "Pixel Data Item" or tag == "FFFE,E0DD"
381
+ check_level_end unless name == "Pixel Data Item" or tag == "FFFE,E0DD"
410
382
  end
411
- end # of method set_level
383
+ end # of set_level
412
384
 
413
385
 
414
386
  # Checks how far we've read in the DICOM file to determine if we have reached a point
415
387
  # where sub-levels are ending. This method is recursive, as multiple sequences/items might end at the same point.
416
- def check_level_end()
388
+ def check_level_end
417
389
  # The test is only meaningful to perform if we are not expecting an 'end of sequence/item' element to signal the level-change.
418
390
  if (@hierarchy.last).is_a?(Array)
419
391
  described_length = (@hierarchy.last)[0]
@@ -427,7 +399,7 @@ module DICOM
427
399
  if (@hierarchy.size > 1)
428
400
  @hierarchy = @hierarchy[0..(@hierarchy.size-2)]
429
401
  # There might be numerous levels that ends at this particular point, so we need to do a recursive repeat to check.
430
- check_level_end()
402
+ check_level_end
431
403
  else
432
404
  @hierarchy = Array.new()
433
405
  end
@@ -439,7 +411,7 @@ module DICOM
439
411
  end
440
412
  end
441
413
  end
442
- end # of method check_level_end
414
+ end
443
415
 
444
416
 
445
417
  # Tests if the file is readable and opens it.
@@ -450,115 +422,78 @@ module DICOM
450
422
  if File.size(file) > 8
451
423
  @file = File.new(file, "rb")
452
424
  else
453
- @msg += ["Error! File is too small to contain DICOM information. Returning. (#{file})"]
425
+ @msg << "Error! File is too small to contain DICOM information. Returning. (#{file})"
454
426
  end
455
427
  else
456
- @msg += ["Error! File is a directory. Returning. (#{file})"]
428
+ @msg << "Error! File is a directory. Returning. (#{file})"
457
429
  end
458
430
  else
459
- @msg += ["Error! File exists but I don't have permission to read it. Returning. (#{file})"]
431
+ @msg << "Error! File exists but I don't have permission to read it. Returning. (#{file})"
460
432
  end
461
433
  else
462
- @msg += ["Error! The file you have supplied does not exist. Returning. (#{file})"]
434
+ @msg << "Error! The file you have supplied does not exist. Returning. (#{file})"
463
435
  end
464
- end # of method open_file
436
+ end
465
437
 
466
438
 
467
439
  # Changes encoding variables as the file reading proceeds past the initial 0002 group of the DICOM file.
468
- def switch_syntax()
469
- # The information read from the Transfer syntax element (if present), needs to be processed:
470
- process_transfer_syntax()
440
+ def switch_syntax
441
+ # Get the transfer syntax string, unless it has already been provided by keyword:
442
+ unless @transfer_syntax
443
+ ts_pos = @tags.index("0002,0010")
444
+ if ts_pos
445
+ @transfer_syntax = @values[ts_pos].rstrip
446
+ else
447
+ @transfer_syntax = "1.2.840.10008.1.2" # Default is implicit, little endian
448
+ end
449
+ end
450
+ # Query the library with our particular transfer syntax string:
451
+ result = @lib.process_transfer_syntax(@transfer_syntax)
452
+ # Result is a 3-element array: [Validity of ts, explicitness, endianness]
453
+ unless result[0]
454
+ @msg+=["Warning: Invalid/unknown transfer syntax! Will try reading the file, but errors may occur."]
455
+ end
456
+ @rest_explicit = result[1]
457
+ @rest_endian = result[2]
471
458
  # We only plan to run this method once:
472
459
  @switched = true
473
460
  # Update endian, explicitness and unpack variables:
474
461
  @file_endian = @rest_endian
462
+ @stream.set_endian(@rest_endian)
475
463
  @explicit = @rest_explicit
476
- if @sys_endian == @file_endian
477
- @endian = true
478
- else
479
- @endian = false
480
- end
481
- set_unpack_strings()
464
+ @stream.explicit = @rest_explicit
482
465
  end
483
466
 
484
467
 
485
- # Checks the Transfer Syntax UID element and updates class variables to prepare for correct reading of DICOM file.
486
- # A lot of code here is duplicated in DWrite class. Should move as much of this code as possible to DLibrary I think.
487
- def process_transfer_syntax()
488
- ts_pos = @tags.index("0002,0010")
489
- if ts_pos != nil
490
- ts_value = @raw[ts_pos].unpack('a*').join.rstrip
491
- valid = @lib.check_ts_validity(ts_value)
492
- if not valid
493
- @msg+=["Warning: Invalid/unknown transfer syntax! Will try reading the file, but errors may occur."]
468
+ # Find the position of the first tag which is not a group "0002" tag:
469
+ def first_non_meta
470
+ i = 0
471
+ go = true
472
+ while go == true and i < @tags.length do
473
+ tag = @tags[i]
474
+ if tag[0..3] == "0002"
475
+ i += 1
476
+ else
477
+ go = false
494
478
  end
495
- case ts_value
496
- # Some variations with uncompressed pixel data:
497
- when "1.2.840.10008.1.2"
498
- # Implicit VR, Little Endian
499
- @rest_explicit = false
500
- @rest_endian = false
501
- when "1.2.840.10008.1.2.1"
502
- # Explicit VR, Little Endian
503
- @rest_explicit = true
504
- @rest_endian = false
505
- when "1.2.840.10008.1.2.1.99"
506
- # Deflated Explicit VR, Little Endian
507
- @msg += ["Warning: Transfer syntax 'Deflated Explicit VR, Little Endian' is untested. Unknown if this is handled correctly!"]
508
- @rest_explicit = true
509
- @rest_endian = false
510
- when "1.2.840.10008.1.2.2"
511
- # Explicit VR, Big Endian
512
- @rest_explicit = true
513
- @rest_endian = true
514
- else
515
- # For everything else, assume compressed pixel data, with Explicit VR, Little Endian:
516
- @rest_explicit = true
517
- @rest_endian = false
518
- end # of case ts_value
519
- end # of if ts_pos != nil
520
- end # of method process_syntax
521
-
522
-
523
- # Sets the unpack format strings that will be used for numbers depending on endianness of file/system.
524
- def set_unpack_strings
525
- if @endian
526
- # System endian equals file endian:
527
- # Native byte order.
528
- @by = "C*" # Byte (1 byte)
529
- @us = "S*" # Unsigned short (2 bytes)
530
- @ss = "s*" # Signed short (2 bytes)
531
- @ul = "I*" # Unsigned long (4 bytes)
532
- @sl = "l*" # Signed long (4 bytes)
533
- @fs = "e*" # Floating point single (4 bytes)
534
- @fd = "E*" # Floating point double ( 8 bytes)
535
- else
536
- # System endian not equal to file endian:
537
- # Network byte order.
538
- @by = "C*"
539
- @us = "n*"
540
- @ss = "n*" # Not correct (gives US)
541
- @ul = "N*"
542
- @sl = "N*" # Not correct (gives UL)
543
- @fs = "g*"
544
- @fd = "G*"
545
479
  end
480
+ return i
546
481
  end
547
482
 
548
483
 
549
484
  # Initiates the variables that are used during file reading.
550
- def init_variables()
485
+ def init_variables
551
486
  # Variables that hold data that will be available to the DObject class.
552
487
  # Arrays that will hold information from the elements of the DICOM file:
553
- @names = Array.new()
554
- @tags = Array.new()
555
- @types = Array.new()
556
- @lengths = Array.new()
557
- @values = Array.new()
558
- @raw = Array.new()
559
- @levels = Array.new()
488
+ @names = Array.new
489
+ @tags = Array.new
490
+ @types = Array.new
491
+ @lengths = Array.new
492
+ @values = Array.new
493
+ @raw = Array.new
494
+ @levels = Array.new
560
495
  # Array that will holde any messages generated while reading the DICOM file:
561
- @msg = Array.new()
496
+ @msg = Array.new
562
497
  # Variables that contain properties of the DICOM file:
563
498
  # Variable to keep track of whether the image pixel data in this file are compressed or not, and if it exists at all:
564
499
  # Default explicitness of start of DICOM file::
@@ -571,10 +506,10 @@ module DICOM
571
506
  # Variables used internally when reading through the DICOM file:
572
507
  # Array for keeping track of how many bytes have been read from the file up to and including each data element:
573
508
  # (This is necessary for tracking the hiearchy in some DICOM files)
574
- @integrated_lengths = Array.new()
509
+ @integrated_lengths = Array.new
575
510
  @header_length = 0
576
511
  # Array to keep track of the hierarchy of elements (this will be used to determine when a sequence or item is finished):
577
- @hierarchy = Array.new()
512
+ @hierarchy = Array.new
578
513
  @hierarchy_error = false
579
514
  # Explicitness of the remaining groups after the initial 0002 group:
580
515
  @rest_explicit = false
@@ -582,14 +517,6 @@ module DICOM
582
517
  @rest_endian = false
583
518
  # When the file switch from group 0002 to a later group we will update encoding values, and this switch will keep track of that:
584
519
  @switched = false
585
- # Use a "relationship endian" variable to guide reading of file:
586
- if @sys_endian == @file_endian
587
- @endian = true
588
- else
589
- @endian = false
590
- end
591
- # Set which format strings to use when unpacking numbers:
592
- set_unpack_strings
593
520
  # A length variable will be used at the end to check whether the last element was read correctly, or whether the file endend unexpectedly:
594
521
  @data_length = 0
595
522
  # Keeping track of the data element's level while reading through the file:
@@ -598,7 +525,9 @@ module DICOM
598
525
  @undef = "UNDEFINED"
599
526
  # Items contained under the pixel data element may contain data directly, so we need a variable to keep track of this:
600
527
  @enc_image = false
528
+ # Assume header size is zero bytes until otherwise is determined:
529
+ @header_length = 0
601
530
  end
602
531
 
603
- end # End of class
604
- end # End of module
532
+ end # of class
533
+ end # of module