dicom 0.6.1 → 0.7

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.
@@ -1,4 +1,5 @@
1
- # Copyright 2008-2009 Christoffer Lervag
1
+ # Copyright 2008-2010 Christoffer Lervag
2
+
2
3
  module DICOM
3
4
 
4
5
  # Class for anonymizing DICOM files:
@@ -6,21 +7,25 @@ module DICOM
6
7
  # ftp://medical.nema.org/medical/dicom/Supps/sup142_03.pdf
7
8
  class Anonymizer
8
9
 
9
- attr_accessor :blank, :enumeration, :identity_file, :verbose, :write_path
10
+ attr_accessor :blank, :enumeration, :identity_file, :remove_private, :verbose, :write_path
10
11
 
11
12
  # Initialize the Anonymizer instance:
12
13
  def initialize(opts={})
13
14
  # Default verbosity is true: # NB: verbosity is not used currently
14
15
  @verbose = opts[:verbose]
15
16
  @verbose = true if @verbose == nil
16
- # Load library:
17
- @lib = DLibrary.new
18
17
  # Default value of accessors:
18
+ # Replace all values with a blank string?
19
19
  @blank = false
20
+ # Enumerate selected replacement values?
20
21
  @enumeration = false
22
+ # All private tags may be removed if desired:
23
+ @remove_private = false
24
+ # A separate path may be selected for writing the anonymized files:
21
25
  @write_path = nil
22
26
  # Array of folders to be processed for anonymization:
23
27
  @folders = Array.new
28
+ # Folders that will be skipped:
24
29
  @exceptions = Array.new
25
30
  # Data elements which will be anonymized (the array will hold a list of tag strings):
26
31
  @tags = Array.new
@@ -36,19 +41,23 @@ module DICOM
36
41
  # Write paths will be determined later and put in this array:
37
42
  @write_paths = Array.new
38
43
  # Set the default data elements to be anonymized:
39
- set_defaults()
40
- end # of method initialize
44
+ set_defaults
45
+ end
41
46
 
42
47
 
43
48
  # Adds a folder who's files will be anonymized:
44
49
  def add_folder(path)
45
- @folders += [path] if path
50
+ @folders << path if path
46
51
  end
47
52
 
48
53
 
49
54
  # Adds an exception folder that is to be avoided when anonymizing:
50
55
  def add_exception(path)
51
- @exceptions += [path] if path
56
+ if path
57
+ # Remove last character if the path ends with a file separator:
58
+ path.chop! if path[-1..-1] == File::SEPARATOR
59
+ @exceptions << path if path
60
+ end
52
61
  end
53
62
 
54
63
 
@@ -61,9 +70,9 @@ module DICOM
61
70
  if tag.is_a?(String)
62
71
  if tag.length == 9
63
72
  # Add anonymization information for this tag:
64
- @tags += [tag]
65
- @values += [value]
66
- @enum += [enum]
73
+ @tags << tag
74
+ @values << value
75
+ @enum << enum
67
76
  else
68
77
  puts "Warning: Invalid tag length. Please use the form 'GGGG,EEEE'."
69
78
  end
@@ -73,7 +82,7 @@ module DICOM
73
82
  else
74
83
  puts "Warning: No tag supplied. Nothing to add."
75
84
  end
76
- end # of method add_tag
85
+ end
77
86
 
78
87
 
79
88
  # Set enumeration status for a specific tag (toggle true/false)
@@ -88,7 +97,7 @@ module DICOM
88
97
  else
89
98
  puts "Specified tag not found in anonymization array. No changes made."
90
99
  end
91
- end # of method change_enum
100
+ end
92
101
 
93
102
 
94
103
  # Changes the value used in anonymization for a specific tag:
@@ -103,7 +112,7 @@ module DICOM
103
112
  else
104
113
  puts "Specified tag not found in anonymization array. No changes made."
105
114
  end
106
- end # of method change_value
115
+ end
107
116
 
108
117
 
109
118
  # Executes the anonymization process:
@@ -113,7 +122,7 @@ module DICOM
113
122
  puts "Initiating anonymization process."
114
123
  start_time = Time.now.to_f
115
124
  puts "Searching for files..."
116
- load_files()
125
+ load_files
117
126
  puts "Done."
118
127
  if @files.length > 0
119
128
  if @tags.length > 0
@@ -121,7 +130,7 @@ module DICOM
121
130
  if @write_path
122
131
  # Determine the write paths, as anonymized files will be written to a separate location:
123
132
  puts "Processing write paths..."
124
- process_write_paths()
133
+ process_write_paths
125
134
  puts "Done"
126
135
  else
127
136
  # Overwriting old files:
@@ -130,59 +139,71 @@ module DICOM
130
139
  end
131
140
  # If the user wants enumeration, we need to prepare variables for storing
132
141
  # existing information associated with each tag:
133
- create_enum_hash() if @enumeration
142
+ create_enum_hash if @enumeration
134
143
  # Start the read/update/write process:
135
144
  puts "Initiating read/update/write process (This may take some time)..."
136
145
  # Monitor whether every file read/write was successful:
137
146
  all_read = true
138
147
  all_write = true
148
+ files_written = 0
149
+ files_failed_read = 0
139
150
  @files.each_index do |i|
140
151
  # Read existing file to DICOM object:
141
- obj = DICOM::DObject.new(@files[i], :verbose => verbose, :lib => @lib)
152
+ obj = DICOM::DObject.new(@files[i], :verbose => verbose)
142
153
  if obj.read_success
143
154
  # Anonymize the desired tags:
144
155
  @tags.each_index do |j|
145
- if @blank
146
- value = ""
147
- elsif @enumeration
148
- # Get old value:
149
- current = obj.get_value(@tags[j])
150
- # Only launch enumeration logic if tag exists:
151
- if current != false
152
- value = get_enumeration_value(current, j)
153
- else
156
+ positions = obj.get_pos(@tags[j])
157
+ positions.each do |pos|
158
+ if @blank
154
159
  value = ""
160
+ elsif @enumeration
161
+ old_value = obj.get_value(pos, :silent => true)
162
+ # Only launch enumeration logic if tag exists:
163
+ if old_value
164
+ value = get_enumeration_value(old_value, j)
165
+ else
166
+ value = ""
167
+ end
168
+ else
169
+ # Value is simply value in array:
170
+ value = @values[j]
155
171
  end
156
- else
157
- # Value is simply value in array:
158
- value = @values[j]
159
- end # of if @blank..else..
160
- # Update DICOM object with new value:
161
- obj.set_value(value, @tags[j], :create => false)
172
+ # Update DICOM object with new value:
173
+ obj.set_value(value, pos, :create => false, :silent => true)
174
+ end
162
175
  end
176
+ # Remove private tags?
177
+ obj.remove_private if @remove_private
163
178
  # Write DICOM file:
164
179
  obj.write(@write_paths[i])
165
- all_write = false unless obj.write_success
180
+ if obj.write_success
181
+ files_written += 1
182
+ else
183
+ all_write = false
184
+ end
166
185
  else
167
186
  all_read = false
187
+ files_failed_read += 1
168
188
  end
169
- end # of @files.each...
189
+ end
190
+ # Finished anonymizing files. Print elapsed time and status of anonymization:
170
191
  end_time = Time.now.to_f
171
192
  puts "Anonymization process completed!"
172
193
  if all_read
173
194
  puts "All files in specified folder(s) were SUCCESSFULLY read to DICOM objects."
174
195
  else
175
- puts "Some files were NOT successfully read. If folder(s) contain non-DICOM files, then this is probably the reason."
196
+ puts "Some files were NOT successfully read (#{files_failed_read} files). If folder(s) contain non-DICOM files, this is probably the reason."
176
197
  end
177
198
  if all_write
178
- puts "All DICOM objects were SUCCESSFULLY written as DICOM files."
199
+ puts "All DICOM objects were SUCCESSFULLY written as DICOM files (#{files_written} files)."
179
200
  else
180
- puts "Some DICOM objects were NOT succesfully written to file. You are advised to have a closer look."
201
+ puts "Some DICOM objects were NOT succesfully written to file. You are advised to have a closer look (#{files_written} files succesfully written)."
181
202
  end
182
203
  # Has user requested enumeration and specified an identity file in which to store the anonymized values?
183
204
  if @enumeration and @identity_file
184
205
  puts "Writing identity file."
185
- write_identity_file()
206
+ write_identity_file
186
207
  puts "Done"
187
208
  end
188
209
  elapsed = (end_time-start_time).to_s
@@ -199,7 +220,7 @@ module DICOM
199
220
 
200
221
  # Prints a list of which tags are currently selected for anonymization along with
201
222
  # replacement values that will be used and enumeration status.
202
- def print()
223
+ def print
203
224
  # Extract the string lengths which are needed to make the formatting nice:
204
225
  names = Array.new
205
226
  types = Array.new
@@ -208,9 +229,9 @@ module DICOM
208
229
  type_lengths = Array.new
209
230
  value_lengths = Array.new
210
231
  @tags.each_index do |i|
211
- arr = @lib.get_name_vr(@tags[i])
212
- names += [arr[0]]
213
- types += [arr[1]]
232
+ arr = LIBRARY.get_name_vr(@tags[i])
233
+ names << arr[0]
234
+ types << arr[1]
214
235
  tag_lengths[i] = @tags[i].length
215
236
  name_lengths[i] = names[i].length
216
237
  type_lengths[i] = types[i].length
@@ -243,13 +264,13 @@ module DICOM
243
264
  value = @values[i]
244
265
  end
245
266
  tag = @tags[i]
246
- lines += [tag + f1 + names[i] + f2 + types[i] + f3 + value.to_s + f4 + enum.to_s ]
267
+ lines << tag + f1 + names[i] + f2 + types[i] + f3 + value.to_s + f4 + enum.to_s
247
268
  end
248
269
  # Print to screen:
249
270
  lines.each do |line|
250
271
  puts line
251
272
  end
252
- end # of method print_tags
273
+ end
253
274
 
254
275
 
255
276
  # Removes a tag from the list of tags that will be anonymized:
@@ -262,7 +283,7 @@ module DICOM
262
283
  else
263
284
  puts "Specified tag not found in anonymization array. No changes made."
264
285
  end
265
- end # of method remove_tag
286
+ end
266
287
 
267
288
 
268
289
  # The following methods are private:
@@ -287,11 +308,11 @@ module DICOM
287
308
  result = index - 1
288
309
  end
289
310
  return result
290
- end # of method common_path
311
+ end
291
312
 
292
313
 
293
314
  # Creates a hash that is used for storing information used when enumeration is desired.
294
- def create_enum_hash()
315
+ def create_enum_hash
295
316
  @enum.each_index do |i|
296
317
  @enum_old_hash[@tags[i]] = Array.new
297
318
  @enum_new_hash[@tags[i]] = Array.new
@@ -311,23 +332,23 @@ module DICOM
311
332
  # Current value has not been encountered before:
312
333
  value = @values[j]+(p_index + 1).to_s
313
334
  # Store value in array (and hash):
314
- previous_old += [current]
315
- previous_new += [value]
335
+ previous_old << current
336
+ previous_new << value
316
337
  @enum_old_hash[@tags[j]] = previous_old
317
338
  @enum_new_hash[@tags[j]] = previous_new
318
339
  else
319
340
  # Current value has been observed before:
320
341
  value = previous_new[previous_old.index(current)]
321
- end # of if previous.index...else..
342
+ end
322
343
  else
323
344
  value = @values[j]
324
- end # of if @enum[j]..else..
345
+ end
325
346
  return value
326
- end # of method handle_enumeration
347
+ end
327
348
 
328
349
 
329
350
  # Discover all the files contained in the specified directory and all its sub-directories:
330
- def load_files()
351
+ def load_files
331
352
  # Load find library:
332
353
  require 'find'
333
354
  # Iterate through the folders (and its subfolders) to extract all files:
@@ -344,50 +365,50 @@ module DICOM
344
365
  Find.prune # Don't look any further into this directory.
345
366
  end
346
367
  else
347
- @files += [path] # Store the file in our array
368
+ @files << path # Store the file in our array
348
369
  end
349
370
  end
350
- end # of for dir...
351
- end # of method load_files
371
+ end
372
+ end
352
373
 
353
374
 
354
375
  # Analyses the write_path and the 'read' file path to determine if the have some common root.
355
376
  # If there are parts of file that exist also in write path, it will not add those parts to write_path.
356
- def process_write_paths()
357
- # First make sure @write_path ends with a "/":
358
- last_character = @write_path[(@write_path.length-1)..(@write_path.length-1)]
359
- @write_path = @write_path + "/" unless last_character == "/"
360
- # Separate behaviour if we have one, or several files in our array:
377
+ def process_write_paths
378
+ # First make sure @write_path ends with a file separator character:
379
+ last_character = @write_path[-1..-1]
380
+ @write_path = @write_path + File::SEPARATOR unless last_character == File::SEPARATOR
381
+ # Differing behaviour if we have one, or several files in our array:
361
382
  if @files.length == 1
362
383
  # One file.
363
384
  # Write path is requested write path + old file name:
364
- str_arr = @files[0].split("/")
365
- @write_paths += [@write_path + str_arr.last]
385
+ str_arr = @files[0].split(File::SEPARATOR)
386
+ @write_paths << @write_path + str_arr.last
366
387
  else
367
388
  # Several files.
368
389
  # Find out how much of the path they have in common, remove that and
369
390
  # add the remaining to the @write_path:
370
- str_arr = @files[0].split("/")
391
+ str_arr = @files[0].split(File::SEPARATOR)
371
392
  last_match_index = common_path(str_arr, 0)
372
393
  if last_match_index >= 0
373
394
  # Remove the matching folders from the path that will be added to @write_path:
374
395
  @files.each do |file|
375
- arr = file.split("/")
376
- part_to_write = arr[(last_match_index+1)..(arr.length-1)].join("/")
377
- @write_paths += [@write_path + part_to_write]
396
+ arr = file.split(File::SEPARATOR)
397
+ part_to_write = arr[(last_match_index+1)..(arr.length-1)].join(File::SEPARATOR)
398
+ @write_paths << @write_path + part_to_write
378
399
  end
379
400
  else
380
401
  # No common folders. Add all of original path to write path:
381
402
  @files.each do |file|
382
- @write_paths += [@write_path + file]
403
+ @write_paths << @write_path + file
383
404
  end
384
405
  end
385
- end # of if @files.length..
386
- end # of method process_write_paths
406
+ end
407
+ end
387
408
 
388
409
 
389
- # Default tags that will be anonymized, along with some settings for each:
390
- def set_defaults()
410
+ # Default tags that will be anonymized, along with default replacement value and enumeration setting.
411
+ def set_defaults
391
412
  data = [
392
413
  ["0008,0012", "20000101", false], # Instance Creation Date
393
414
  ["0008,0013", "000000.00", false], # Instance Creation Time
@@ -398,6 +419,7 @@ module DICOM
398
419
  ["0008,0080", "Institution", true], # Institution name
399
420
  ["0008,0090", "Physician", true], # Referring Physician's name
400
421
  ["0008,1010", "Station", true], # Station name
422
+ ["0008,1070", "Operator", true], # Operator's Name
401
423
  ["0010,0010", "Patient", true], # Patient's name
402
424
  ["0010,0020", "ID", true], # Patient's ID
403
425
  ["0010,0030", "20000101", false], # Patient's Birth Date
@@ -407,12 +429,12 @@ module DICOM
407
429
  @tags = data[0]
408
430
  @values = data[1]
409
431
  @enum = data[2]
410
- end # of method set_defaults
432
+ end
411
433
 
412
434
 
413
435
  # Writes an identity file, which allows reidentification of DICOM files that have been anonymized
414
436
  # using the enumeration feature. Values will be saved in a text file, using semi colon delineation.
415
- def write_identity_file()
437
+ def write_identity_file
416
438
  # Open file and prepare to write text:
417
439
  File.open( @identity_file, 'w' ) do |output|
418
440
  # Cycle through each
@@ -428,10 +450,10 @@ module DICOM
428
450
  end
429
451
  # Print empty line for separation between different tags:
430
452
  output.print "\n"
431
- end # of if @enum[i]
432
- end # of @tags.each...
433
- end # of File.open...
434
- end # of method write...
453
+ end
454
+ end
455
+ end
456
+ end
435
457
 
436
458
 
437
459
  end # of class
@@ -1,4 +1,4 @@
1
- # Copyright 2009 Christoffer Lervag
1
+ # Copyright 2009-2010 Christoffer Lervag
2
2
 
3
3
  module DICOM
4
4
 
@@ -16,7 +16,6 @@ module DICOM
16
16
  @port = port
17
17
  # Optional parameters (and default values):
18
18
  @ae = options[:ae] || "RUBY_DICOM"
19
- @lib = options[:lib] || DLibrary.new
20
19
  @host_ae = options[:host_ae] || "DEFAULT"
21
20
  @max_package_size = options[:max_package_size] || 32768 # 16384
22
21
  @timeout = options[:timeout] || 10 # seconds
@@ -139,7 +138,7 @@ module DICOM
139
138
  # Send a DICOM file to a service class provider (SCP/PACS).
140
139
  def send(file_path)
141
140
  # Load the DICOM file from the specified path:
142
- obj = DObject.new(file_path, :verbose => false, :lib => @lib)
141
+ obj = DObject.new(file_path, :verbose => false)
143
142
  if obj.read_success
144
143
  # Get the SOP Class UID (abstract syntax) from the DICOM obj:
145
144
  @abstract_syntax = obj.get_value("0008,0016")
@@ -219,7 +218,7 @@ module DICOM
219
218
  @link.build_association_request(@application_context_uid, @abstract_syntax, @transfer_syntax, @user_information)
220
219
  @connection = TCPSocket.new(@host_ip, @port)
221
220
  @link.transmit(@connection)
222
- info = @link.receive_single_transmission(@connection).first
221
+ info = @link.receive_multiple_transmissions(@connection).first
223
222
  # Interpret the results:
224
223
  if info[:valid]
225
224
  if info[:pdu] == "02"
@@ -309,9 +308,11 @@ module DICOM
309
308
  @link.build_data_fragment(@data_elements) # (uses flag = 02)
310
309
  @link.transmit(@connection)
311
310
  # Listen for incoming file data:
312
- @link.handle_incoming_data(@connection, path)
313
- # Send confirmation response:
314
- @link.handle_response(@connection)
311
+ success = @link.handle_incoming_data(@connection, path)
312
+ if success
313
+ # Send confirmation response:
314
+ @link.handle_response(@connection)
315
+ end
315
316
  end
316
317
  # Close the DICOM link:
317
318
  establish_release
@@ -409,7 +410,7 @@ module DICOM
409
410
  @command_elements = [
410
411
  ["0000,0002", "UI", @abstract_syntax], # Affected SOP Class UID
411
412
  ["0000,0100", "US", 16], # Command Field: 16 (C-GET-RQ)
412
- ["0000,0600", "AE", destination], # Move destination
413
+ ["0000,0600", "AE", @ae], # Destination is ourselves
413
414
  ["0000,0700", "US", 0], # Priority: 0: medium
414
415
  ["0000,0800", "US", 1] # Data Set Type: 1
415
416
  ]
@@ -570,12 +571,12 @@ module DICOM
570
571
  end
571
572
 
572
573
 
573
- # Set user information [item type code, vr/type, value]
574
+ # Set user information [item type code, VR, value]
574
575
  def set_user_information_array
575
576
  @user_information = [
576
577
  ["51", "UL", @max_package_size], # Max PDU Length
577
- ["52", "STR", "1.2.826.0.1.3680043.8.641"], # Implementation UID
578
- ["55", "STR", "RUBY_DICOM"] # Implementation Version
578
+ ["52", "STR", UID], # Implementation UID
579
+ ["55", "STR", NAME] # Implementation Version (Name & version)
579
580
  ]
580
581
  end
581
582