dicom 0.4 → 0.5

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