dicom 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/DWrite.rb CHANGED
@@ -1,31 +1,29 @@
1
- # Copyright 2008-2009 Christoffer Lerv�g
1
+ # Copyright 2008-2009 Christoffer Lervag
2
2
 
3
3
  # Some notes about this DICOM file writing class:
4
- # In its current state, this class will always try to write the file such that it is
5
- # compliant to the official standard (DICOM 3 Part 10), containing header
6
- # and meta information (group 0002). If this is unwanted behaviour, it is easy
7
- # to modify the source code here to avoid this.
4
+ # In its current state, this class will always try to write the file such that it is compliant to the
5
+ # official standard (DICOM 3 Part 10), containing header and meta information (group 0002).
6
+ # If this is unwanted behaviour, it is easy to modify the source code here to avoid this.
8
7
  #
9
- # It is important to note, that while the goal is to be fully DICOM compliant,
10
- # no guarantees are given that this is actually achieved. You are encouraged
11
- # to thouroughly test your files for compatibility after creation.
12
- #Please contact the author if you discover any issues with file creation.
8
+ # It is important to note, that while the goal is to be fully DICOM compliant, no guarantees are given
9
+ # that this is actually achieved. You are encouraged to thouroughly test your files for compatibility after creation.
10
+ # Please contact the author if you discover any issues with file creation.
13
11
 
14
12
  module DICOM
15
13
  # Class for writing the data from DObject to a valid DICOM file:
16
14
  class DWrite
17
- attr_writer :labels, :types, :lengths, :raw, :rest_endian, :rest_explicit
15
+ attr_writer :tags, :types, :lengths, :raw, :rest_endian, :rest_explicit
18
16
  attr_reader :success, :msg
19
-
17
+
20
18
  # Initialize the DWrite instance.
21
19
  def initialize(file_name=nil, opts={})
22
20
  # Process option values, setting defaults for the ones that are not specified:
23
21
  @lib = opts[:lib] || DLibrary.new
24
22
  @sys_endian = opts[:sys_endian] || false
25
23
  @file_name = file_name
26
-
27
- # Create arrays used for storing tag information:
28
- @labels = Array.new
24
+
25
+ # Create arrays used for storing data element information:
26
+ @tags = Array.new
29
27
  @types = Array.new
30
28
  @lengths = Array.new
31
29
  @raw = Array.new
@@ -37,11 +35,11 @@ module DICOM
37
35
  # Endianness of the remaining groups after the first group:
38
36
  @rest_endian = false
39
37
  end # of method initialize
40
-
41
-
38
+
39
+
42
40
  # Writes the DICOM information to file.
43
41
  def write()
44
- if @labels.size > 0
42
+ if @tags.size > 0
45
43
  # Check if we are able to create given file:
46
44
  open_file(@file_name)
47
45
  # Read the initial header of the file:
@@ -52,11 +50,11 @@ module DICOM
52
50
  write_header()
53
51
  # Write meta information (if it is not present in the DICOM object):
54
52
  write_meta()
55
- # Write tags:
56
- @labels.each_index do |i|
57
- write_tag(i)
53
+ # Write data elements:
54
+ @tags.each_index do |i|
55
+ write_data_element(i)
58
56
  end
59
- # We are finished writing the tags, and as such, can close the file:
57
+ # We are finished writing the data elements, and as such, can close the file:
60
58
  @file.close()
61
59
  # Mark this write session as successful:
62
60
  @success = true
@@ -66,15 +64,15 @@ module DICOM
66
64
  return
67
65
  end # of if @file != nil
68
66
  else
69
- @msg += ["Error. No tags to write."]
70
- end # of if @labels.size > 0
67
+ @msg += ["Error. No data elements to write."]
68
+ end # of if @tags.size > 0
71
69
  end # of method write
72
-
73
-
70
+
71
+
74
72
  # Following methods are private:
75
73
  private
76
-
77
-
74
+
75
+
78
76
  # Writes the official DICOM header:
79
77
  def write_header()
80
78
  # Fill in 128 empty bytes:
@@ -82,24 +80,24 @@ module DICOM
82
80
  # Write the string "DICM" which is central to DICOM standards compliance:
83
81
  @file.write("DICM")
84
82
  end # of write_header
85
-
86
-
83
+
84
+
87
85
  # Inserts group 0002 if it is missing, to ensure DICOM compliance.
88
86
  def write_meta()
89
87
  # We will check for the existance of 5 group 0002 elements, and if they are not present, we will insert them:
90
88
  pos = Array.new()
91
89
  meta = Array.new()
92
90
  # File Meta Information Version:
93
- pos += [@labels.index("0002,0001")]
91
+ pos += [@tags.index("0002,0001")]
94
92
  meta += [["0002,0001", "OB", 2, ["0100"].pack("H*")]]
95
93
  # Transfer Syntax UID:
96
- pos += [@labels.index("0002,0010")]
94
+ pos += [@tags.index("0002,0010")]
97
95
  meta += [["0002,0010", "UI", 18, ["1.2.840.10008.1.2"].pack("a*")+["00"].pack("H*")]] # Implicit, little endian
98
96
  # Implementation Class UID:
99
- pos += [@labels.index("0002,0012")]
97
+ pos += [@tags.index("0002,0012")]
100
98
  meta += [["0002,0012", "UI", 26, ["1.2.826.0.1.3680043.8.641"].pack("a*")+["00"].pack("H*")]] # Ruby DICOM UID
101
99
  # Implementation Version Name:
102
- pos += [@labels.index("0002,0013")]
100
+ pos += [@tags.index("0002,0013")]
103
101
  meta += [["0002,0013", "SH", 10, ["RUBY_DICOM"].pack("a*")]]
104
102
  # Insert meta information:
105
103
  meta_added = false
@@ -107,14 +105,14 @@ module DICOM
107
105
  # Only insert element if it does not already exist (corresponding pos element shows no match):
108
106
  if pos[i] == nil
109
107
  meta_added = true
110
- # Find where to insert this data element
108
+ # Find where to insert this data element.
111
109
  index = -1
112
- label = meta[i][0]
110
+ tag = meta[i][0]
113
111
  quit = false
114
112
  while quit != true do
115
- if label < @labels[index+1]
113
+ if tag < @tags[index+1]
116
114
  quit = true
117
- elsif @labels[index+1][0..3] != "0002"
115
+ elsif @tags[index+1][0..3] != "0002"
118
116
  # Abort to avoid needlessly going through the whole array.
119
117
  quit = true
120
118
  else
@@ -125,13 +123,13 @@ module DICOM
125
123
  # Insert data element in the correct array position:
126
124
  if index == -1
127
125
  # Insert at the beginning of array:
128
- @labels = [meta[i][0]] + @labels
126
+ @tags = [meta[i][0]] + @tags
129
127
  @types = [meta[i][1]] + @types
130
128
  @lengths = [meta[i][2]] + @lengths
131
129
  @raw = [meta[i][3]] + @raw
132
130
  else
133
131
  # One or more elements comes before this element:
134
- @labels = @labels[0..index] + [meta[i][0]] + @labels[(index+1)..(@labels.length-1)]
132
+ @tags = @tags[0..index] + [meta[i][0]] + @tags[(index+1)..(@tags.length-1)]
135
133
  @types = @types[0..index] + [meta[i][1]] + @types[(index+1)..(@types.length-1)]
136
134
  @lengths = @lengths[0..index] + [meta[i][2]] + @lengths[(index+1)..(@lengths.length-1)]
137
135
  @raw = @raw[0..index] + [meta[i][3]] + @raw[(index+1)..(@raw.length-1)]
@@ -143,86 +141,86 @@ module DICOM
143
141
  quit = false
144
142
  j = 0
145
143
  while quit == false do
146
- if @labels[j][0..3] != "0002"
144
+ if @tags[j][0..3] != "0002"
147
145
  quit = true
148
146
  else
149
147
  # Add to length if group 0002:
150
- if @labels[j] != "0002,0000"
148
+ if @tags[j] != "0002,0000"
151
149
  if @types[j] == "OB"
152
150
  length += 12 + @lengths[j]
153
151
  else
154
152
  length += 8 + @lengths[j]
155
153
  end
156
154
  end
157
- j += 1
158
- end # of if @labels[j][0..3]..
155
+ j += 1
156
+ end # of if @tags[j][0..3]..
159
157
  end # of while
160
158
  # Set group length:
161
- gl_pos = @labels.index("0002,0000")
159
+ gl_pos = @tags.index("0002,0000")
162
160
  gl_info = ["0002,0000", "UL", 4, [length].pack("I*")]
163
161
  # Update group length, but only if there have been some modifications or GL is nonexistant:
164
162
  if meta_added == true or gl_pos != nil
165
163
  if gl_pos == nil
166
164
  # Add group length (to beginning of arrays):
167
- @labels = [gl_info[0]] + @labels
165
+ @tags = [gl_info[0]] + @tags
168
166
  @types = [gl_info[1]] + @types
169
167
  @lengths = [gl_info[2]] + @lengths
170
168
  @raw = [gl_info[3]] + @raw
171
169
  else
172
170
  # Edit existing group length:
173
- @labels[gl_pos] = gl_info[0]
171
+ @tags[gl_pos] = gl_info[0]
174
172
  @types[gl_pos] = gl_info[1]
175
173
  @lengths[gl_pos] = gl_info[2]
176
174
  @raw[gl_pos] = gl_info[3]
177
175
  end
178
176
  end
179
177
  end # of method write_meta
180
-
181
-
182
- # Writes the tags to file:
183
- def write_tag(i)
184
- # Step 1: Write label:
185
- write_label(i)
178
+
179
+
180
+ # Writes each data element to file:
181
+ def write_data_element(i)
182
+ # Step 1: Write tag:
183
+ write_tag(i)
186
184
  # Step 2: Write [type] and value length:
187
185
  write_type_length(i)
188
186
  # Step 3: Write value:
189
187
  write_value(i)
190
188
  # If DICOM object contains encapsulated pixel data, we need some special handling for its items:
191
- if @labels[i] == "7FE0,0010"
189
+ if @tags[i] == "7FE0,0010"
192
190
  @enc_image = true if @lengths[i].to_i == 0
193
191
  end
194
192
  # Should have some kind of test that the last data was written succesfully?
195
- end # of method write_tag
196
-
197
-
198
- # Writes the label (first part of the data element):
199
- def write_label(i)
200
- # Label is originally of the form 0002,0010.
193
+ end # of method write_data_element
194
+
195
+
196
+ # Writes the tag (first part of the data element):
197
+ def write_tag(i)
198
+ # Tag is originally of the form "0002,0010".
201
199
  # We need to reformat to get rid of the comma:
202
- label = @labels[i][0..3] + @labels[i][5..8]
200
+ tag = @tags[i][0..3] + @tags[i][5..8]
203
201
  # Whether DICOM file is big or little endian, the first 0002 group is always little endian encoded.
204
202
  # On a big endian system, I believe the order of the numbers need not be changed,
205
203
  # but this has not been tested yet.
206
204
  if @sys_endian == false
207
205
  # System is little endian:
208
206
  # Change the order of the numbers so that it becomes correct when packed as hex:
209
- label_corr = label[2..3] + label[0..1] + label[6..7] + label[4..5]
207
+ tag_corr = tag[2..3] + tag[0..1] + tag[6..7] + tag[4..5]
210
208
  end
211
209
  # When we shift from group 0002 to another group we need to update our endian/explicitness variables:
212
- if label[0..3] != "0002" and @switched == false
210
+ if tag[0..3] != "0002" and @switched == false
213
211
  switch_syntax()
214
212
  end
215
- # Perhaps we need to rearrange the labels if the file encoding is now big endian:
213
+ # Perhaps we need to rearrange the tag if the file encoding is now big endian:
216
214
  if not @endian
217
215
  # Need to rearrange the first and second part of each string:
218
- label_corr = label
216
+ tag_corr = tag
219
217
  end
220
218
  # Write to file:
221
- @file.write([label_corr].pack('H*'))
222
- end # of method write_label
223
-
224
-
225
- # Writes the type (if to be written) and length value (these two are the middle part of the data element):
219
+ @file.write([tag_corr].pack('H*'))
220
+ end # of method write_tag
221
+
222
+
223
+ # Writes the type (VR) (if it is to be written) and length value (these two are the middle part of the data element):
226
224
  def write_type_length(i)
227
225
  # First some preprocessing:
228
226
  # Set length value:
@@ -243,12 +241,12 @@ module DICOM
243
241
  # *****EXPLICIT*****:
244
242
  if @explicit == true
245
243
  # Step 1: Write VR (if it is to be written)
246
- unless @labels[i] == "FFFE,E000" or @labels[i] == "FFFE,E00D" or @labels[i] == "FFFE,E0DD"
247
- # Write tag type field (2 bytes - since we are not dealing with an item related tag):
244
+ unless @tags[i] == "FFFE,E000" or @tags[i] == "FFFE,E00D" or @tags[i] == "FFFE,E0DD"
245
+ # Write data element type (VR) (2 bytes - since we are not dealing with an item related element):
248
246
  @file.write([@types[i]].pack('a*'))
249
247
  end
250
248
  # Step 2: Write length
251
- # Three possible structures for value length here, dependent on tag type:
249
+ # Three possible structures for value length here, dependent on data element type:
252
250
  case @types[i]
253
251
  when "OB","OW","SQ","UN"
254
252
  if @enc_image
@@ -264,11 +262,11 @@ module DICOM
264
262
  end
265
263
  when "()"
266
264
  # 4 bytes:
267
- # For labels "FFFE,E000", "FFFE,E00D" and "FFFE,E0DD"
265
+ # For tags "FFFE,E000", "FFFE,E00D" and "FFFE,E0DD"
268
266
  @file.write(length4)
269
267
  else
270
268
  # 2 bytes:
271
- # For all the other tag types, value length is 2 bytes:
269
+ # For all the other data element types, value length is 2 bytes:
272
270
  @file.write(length2)
273
271
  end # of case type
274
272
  else
@@ -278,15 +276,15 @@ module DICOM
278
276
  @file.write(length4)
279
277
  end # of if @explicit == true
280
278
  end # of method write_type_length
281
-
282
-
279
+
280
+
283
281
  # Writes the value (last part of the data element):
284
282
  def write_value(i)
285
283
  # This is pretty straightforward, just dump the binary data to the file:
286
284
  @file.write(@raw[i])
287
285
  end # of method write_value
288
-
289
-
286
+
287
+
290
288
  # Tests if the file is writable and opens it.
291
289
  def open_file(file)
292
290
  # Two cases: File already exists or it does not.
@@ -324,7 +322,7 @@ module DICOM
324
322
 
325
323
  # Changes encoding variables as the file writing proceeds past the initial 0002 group of the DICOM file.
326
324
  def switch_syntax()
327
- # The information from the Transfer syntax tag (if present), needs to be processed:
325
+ # The information from the Transfer syntax element (if present), needs to be processed:
328
326
  process_transfer_syntax()
329
327
  # We only plan to run this method once:
330
328
  @switched = true
@@ -340,11 +338,11 @@ module DICOM
340
338
  end
341
339
 
342
340
 
343
- # Checks the Transfer Syntax UID tag and updates class variables to prepare for correct writing of DICOM file.
341
+ # Checks the Transfer Syntax UID element and updates class variables to prepare for correct writing of DICOM file.
344
342
  def process_transfer_syntax()
345
- ts_pos = @labels.index("0002,0010")
343
+ ts_pos = @tags.index("0002,0010")
346
344
  if ts_pos != nil
347
- ts_value = @raw[ts_pos].unpack('a*').to_s.rstrip
345
+ ts_value = @raw[ts_pos].unpack('a*').join.rstrip
348
346
  valid = @lib.check_ts_validity(ts_value)
349
347
  if not valid
350
348
  @msg+=["Warning: Invalid/unknown transfer syntax! Will still write the file, but you should give this a closer look."]
@@ -375,8 +373,8 @@ module DICOM
375
373
  end # of case ts_value
376
374
  end # of if ts_pos != nil
377
375
  end # of method process_syntax
378
-
379
-
376
+
377
+
380
378
  # Sets the pack format strings that will be used for numbers depending on endianness of file/system.
381
379
  def set_pack_strings
382
380
  if @endian
@@ -401,14 +399,14 @@ module DICOM
401
399
  @fd = "G*"
402
400
  end
403
401
  end
404
-
405
-
402
+
403
+
406
404
  # Initializes the variables used when executing this program.
407
405
  def init_variables()
408
406
  # Variables that are accesible from outside:
409
407
  # Until a DICOM write has completed successfully the status is 'unsuccessful':
410
408
  @success = false
411
-
409
+
412
410
  # Variables used internally:
413
411
  # Default explicitness of start of DICOM file:
414
412
  @explicit = true
@@ -424,9 +422,9 @@ module DICOM
424
422
  end
425
423
  # Set which format strings to use when unpacking numbers:
426
424
  set_pack_strings
427
- # Items contained under the pixel data tag needs some special attention to write correctly:
425
+ # Items contained under the Pixel Data element needs some special attention to write correctly:
428
426
  @enc_image = false
429
427
  end # of method init_variables
430
-
428
+
431
429
  end # of class
432
430
  end # of module
data/lib/Dictionary.rb CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright 2008-2009 Christoffer Lerv�g
1
+ # coding: ISO-8859-1
2
+
3
+ # Copyright 2008-2009 Christoffer Lervag
2
4
 
3
5
  module DICOM
4
6
  # This class holds all the dictionary data.
@@ -6,7 +8,7 @@ module DICOM
6
8
  class Dictionary
7
9
 
8
10
  # Loads the image type library (Photometric Interpretation).
9
- # (Tag label: 0028,0004)
11
+ # (Data element tag: 0028,0004)
10
12
  def load_image_types()
11
13
  return [
12
14
  ["MONOCHROME1", "grey level image description (high values=dark, low values=bright)"],
@@ -58,7 +60,7 @@ module DICOM
58
60
  def load_uid()
59
61
  return [
60
62
  ["1.2.840.10008.1.1", "Verification SOP Class", "SOP Class"],
61
- # Start: Transfer syntax (Tag label: 0002,0010)
63
+ # Start: Transfer syntax (Data element tag: 0002,0010)
62
64
  ["1.2.840.10008.1.2", "Implicit VR Little Endian: Default Transfer Syntax for DICOM", "Transfer Syntax"],
63
65
  ["1.2.840.10008.1.2.1", "Explicit VR Little Endian", "Transfer Syntax"],
64
66
  ["1.2.840.10008.1.2.1.99", "Deflated Explicit VR Little Endian", "Transfer Syntax"],
@@ -360,8 +362,8 @@ module DICOM
360
362
  end
361
363
 
362
364
 
363
- # Tag data
364
- def load_tags()
365
+ # Data element dictionary:
366
+ def load_data_elements()
365
367
  return [
366
368
  # E.1 Registry of DICOM command elements
367
369
  # Group 0000
@@ -2872,16 +2874,16 @@ module DICOM
2872
2874
  ["60xx,1500", ["LO"], "Overlay Label"],
2873
2875
  ["60xx,3000", ["OB","OW"], "Overlay Data"],
2874
2876
  ["60xx,4000", ["LT"], "Overlay Comments"], # RET
2875
- # Pixel data and structural tags:
2877
+ # Pixel data and structural data elements:
2876
2878
  ["7FE0,0010", ["OW","OB"], "Pixel Data"],
2877
2879
  ["FFFA,FFFA", ["SQ"], "Digital Signatures Sequence"],
2878
2880
  ["FFFC,FFFC", ["OB"], "Data Set Trailing Padding"],
2879
- ["FFFE,E000", ["()"], "Item"], # VR does not exist for this tag
2880
- ["FFFE,E00D", ["()"], "Item Delimitation Item"], # VR does not exist for this tag
2881
- ["FFFE,E0DD", ["()"], "Sequence Delimitation Item"] # VR does not exist for this tag
2881
+ ["FFFE,E000", ["()"], "Item"], # VR does not exist for this element
2882
+ ["FFFE,E00D", ["()"], "Item Delimitation Item"], # VR does not exist for this element
2883
+ ["FFFE,E0DD", ["()"], "Sequence Delimitation Item"] # VR does not exist for this element
2882
2884
  ].transpose
2883
- end # End of method load_tags()
2884
-
2885
-
2885
+ end # End of method load_data_elements()
2886
+
2887
+
2886
2888
  end # End of class
2887
2889
  end # End of module
data/lib/dicom.rb CHANGED
@@ -4,5 +4,6 @@ require 'DLibrary'
4
4
  require 'DObject'
5
5
  require 'DRead'
6
6
  require 'DWrite'
7
+ require 'ruby_extensions'
7
8
  # Extended library:
8
9
  require 'Anonymizer'