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.
@@ -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