dicom 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +20 -1
- data/README.rdoc +26 -17
- data/lib/dicom.rb +32 -28
- data/lib/dicom/anonymizer.rb +187 -49
- data/lib/dicom/audit_trail.rb +117 -0
- data/lib/dicom/constants.rb +1 -2
- data/lib/dicom/d_client.rb +13 -13
- data/lib/dicom/d_object.rb +50 -22
- data/lib/dicom/d_read.rb +9 -9
- data/lib/dicom/d_server.rb +14 -10
- data/lib/dicom/d_write.rb +6 -6
- data/lib/dicom/deprecated.rb +73 -0
- data/lib/dicom/element.rb +37 -5
- data/lib/dicom/elemental.rb +4 -4
- data/lib/dicom/file_handler.rb +10 -10
- data/lib/dicom/image_item.rb +28 -12
- data/lib/dicom/item.rb +36 -4
- data/lib/dicom/link.rb +1 -1
- data/lib/dicom/logging.rb +0 -0
- data/lib/dicom/parent.rb +41 -41
- data/lib/dicom/ruby_extensions.rb +6 -4
- data/lib/dicom/sequence.rb +34 -2
- data/lib/dicom/stream.rb +9 -2
- data/lib/dicom/variables.rb +19 -0
- data/lib/dicom/version.rb +1 -1
- metadata +30 -17
data/CHANGELOG.rdoc
CHANGED
@@ -1,6 +1,25 @@
|
|
1
|
+
= 0.9.3
|
2
|
+
|
3
|
+
=== 6th May, 2012
|
4
|
+
|
5
|
+
* Deprecated all #remove* methods (replaced with #delete*) to increase consistency with Ruby.
|
6
|
+
* Changed preferred DObject variable name from obj to dcm for all examples.
|
7
|
+
* Added comparison methods (#==, #eql? and #hash) as well as dynamic #to_* methods to DObject, Element, Item & Sequence classes.
|
8
|
+
* Fixed incorrect handling of 3D pixel data with NArray (regression introduced in v0.8).
|
9
|
+
* Anonymizer can take a list of tags to be entirely deleted from the DICOM files during anonymization.
|
10
|
+
* Added Accession Number to default list of tags that are anonymized.
|
11
|
+
* Added a generate_uid method for creating custom (but valid) UIDs.
|
12
|
+
* Added an Anonymizer AuditTrail feature based on JSON, which will replace the (deprecated) identity_file feature.
|
13
|
+
* Implemented a proper UID anonymization with AuditTrail support and preservation of relations.
|
14
|
+
* Fixed and improved logging when failing to decompress pixel data.
|
15
|
+
* DServer defaults to bind to 127.0.0.1, with an option available for other bindings.
|
16
|
+
* More robust handling of invalid AT Element values when parsing DICOM files.
|
17
|
+
* Optimizations and code cleanups allowed by the introduced Ruby 1.9 requirement.
|
18
|
+
|
19
|
+
|
1
20
|
= 0.9.2
|
2
21
|
|
3
|
-
===
|
22
|
+
=== 10th Oct, 2011
|
4
23
|
|
5
24
|
* Enabled the use of lower case tag letters in methods which previously required the use of upper case letters.
|
6
25
|
* Added new DObject class methods to offload DObject#new:
|
data/README.rdoc
CHANGED
@@ -13,6 +13,11 @@ communication modalities like querying, moving, sending and receiving files.
|
|
13
13
|
gem install dicom
|
14
14
|
|
15
15
|
|
16
|
+
== REQUIREMENTS
|
17
|
+
|
18
|
+
* Ruby 1.9.2 (if you are still on Ruby 1.8, gems up to version 0.9.1 can be used)
|
19
|
+
|
20
|
+
|
16
21
|
== BASIC USAGE
|
17
22
|
|
18
23
|
=== Load & Include
|
@@ -23,42 +28,42 @@ communication modalities like querying, moving, sending and receiving files.
|
|
23
28
|
=== Read, modify and write
|
24
29
|
|
25
30
|
# Read file:
|
26
|
-
|
31
|
+
dcm = DObject.read("some_file.dcm")
|
27
32
|
# Extract the Patient's Name value:
|
28
|
-
|
33
|
+
dcm.patients_name.value
|
29
34
|
# Add or modify the Patient's Name element:
|
30
|
-
|
35
|
+
dcm.patients_name = "Anonymous"
|
31
36
|
# Remove a data element from the DICOM object:
|
32
|
-
|
37
|
+
dcm.pixel_data = nil
|
33
38
|
# Write to file:
|
34
|
-
|
39
|
+
dcm.write("new_file.dcm")
|
35
40
|
|
36
41
|
=== Modify using tag strings instead of dictionary method names
|
37
42
|
|
38
43
|
# Extract the Patient's Name value:
|
39
|
-
|
44
|
+
dcm.value("0010,0010")
|
40
45
|
# Modify the Patient's Name element:
|
41
|
-
|
42
|
-
#
|
43
|
-
|
46
|
+
dcm["0010,0010"].value = "Anonymous"
|
47
|
+
# Delete a data element from the DICOM object:
|
48
|
+
dcm.delete("7FE0,0010")
|
44
49
|
|
45
50
|
=== Extracting information about the DICOM object
|
46
51
|
|
47
52
|
# Display a short summary of the file's properties:
|
48
|
-
|
53
|
+
dcm.summary
|
49
54
|
# Print all data elements to screen:
|
50
|
-
|
55
|
+
dcm.print
|
51
56
|
# Convert the data element hierarchy to a nested hash:
|
52
|
-
|
57
|
+
dcm.to_hash
|
53
58
|
|
54
59
|
=== Handle pixel data
|
55
60
|
|
56
61
|
# Retrieve the pixel data in a Ruby Array:
|
57
|
-
|
62
|
+
dcm.pixels
|
58
63
|
# Load the pixel data to an numerical array (NArray):
|
59
|
-
|
64
|
+
dcm.narray
|
60
65
|
# Load the pixel data to an RMagick image object and display it on the screen:
|
61
|
-
|
66
|
+
dcm.image.display
|
62
67
|
|
63
68
|
=== Transmit a DICOM file
|
64
69
|
|
@@ -91,19 +96,21 @@ that is printed to screen, regardless if you have set verbose as false. This is
|
|
91
96
|
in irb every variable loaded in the program is automatically printed to the screen.
|
92
97
|
A useful hack to avoid this effect is to append ";0" after a command.
|
93
98
|
Example:
|
94
|
-
|
99
|
+
dcm = DObject.read("some_file.dcm") ;0
|
95
100
|
|
96
101
|
|
97
102
|
== RESOURCES
|
98
103
|
|
99
104
|
* {Official home page}[http://dicom.rubyforge.org/]
|
100
105
|
* {Discussion forum}[http://groups.google.com/group/ruby-dicom]
|
106
|
+
* {Documentation}[http://rubydoc.info/gems/dicom/frames]
|
107
|
+
* {Tutorials}[http://dicom.rubyforge.org/tutorials.html]
|
101
108
|
* {Source code repository}[https://github.com/dicom/ruby-dicom]
|
102
109
|
|
103
110
|
|
104
111
|
== COPYRIGHT
|
105
112
|
|
106
|
-
Copyright 2008-
|
113
|
+
Copyright 2008-2012 Christoffer Lervåg
|
107
114
|
|
108
115
|
This program is free software: you can redistribute it and/or modify
|
109
116
|
it under the terms of the GNU General Public License as published by
|
@@ -133,7 +140,9 @@ Please don't hesitate to email me if you have any feedback related to this proje
|
|
133
140
|
* {Christoffer Lervåg}[https://github.com/dicom]
|
134
141
|
* {John Axel Eriksson}[https://github.com/johnae]
|
135
142
|
* {Kamil Bujniewicz}[https://github.com/icdark]
|
143
|
+
* {Jeff Miller}[https://github.com/jeffmax]
|
136
144
|
* {Donnie Millar}[https://github.com/dmillar]
|
137
145
|
* {Björn Albers}[https://github.com/bjoernalbers]
|
138
146
|
* {Lars Benner}[https://github.com/Maturin]
|
147
|
+
* {Felix Petriconi}[https://github.com/FelixPetriconi]
|
139
148
|
* {Steven Bedrick}[https://github.com/stevenbedrick]
|
data/lib/dicom.rb
CHANGED
@@ -2,46 +2,50 @@
|
|
2
2
|
#
|
3
3
|
# The following classes are meant to be used by users of Ruby DICOM:
|
4
4
|
# * DObject - for reading, manipulating and writing DICOM files.
|
5
|
-
# * Element, Sequence, Item, Parent, Elemental - users who wish to interact with
|
5
|
+
# * Element, Sequence, Item, Parent, Elemental - users who wish to interact with
|
6
|
+
# their DICOM objects will use these classes/modules.
|
6
7
|
# * ImageItem - Image related methods are found in this class.
|
7
8
|
# * DClient - for client side network communication, like querying, moving & sending DICOM files.
|
8
9
|
# * DServer - for server side network communication: Setting up your own DICOM storage node (SCP).
|
9
10
|
# * Anonymizer - a convenience class for anonymizing your DICOM files.
|
10
11
|
#
|
11
|
-
# The rest of the classes visible in the documentation generated by RDoc
|
12
|
-
# 'private' classes, which are mainly of interest to developers.
|
12
|
+
# The rest of the classes visible in the documentation generated by RDoc are
|
13
|
+
# in principle 'private' classes, which are mainly of interest to developers.
|
13
14
|
|
14
15
|
# Logging:
|
15
|
-
|
16
|
+
require_relative 'dicom/logging'
|
16
17
|
# Core library:
|
17
18
|
# Super classes/modules:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
require_relative 'dicom/image_processor'
|
20
|
+
require_relative 'dicom/parent'
|
21
|
+
require_relative 'dicom/image_item'
|
22
|
+
require_relative 'dicom/elemental'
|
22
23
|
# Subclasses and independent classes:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
24
|
+
require_relative 'dicom/d_client'
|
25
|
+
require_relative 'dicom/dictionary'
|
26
|
+
require_relative 'dicom/d_library'
|
27
|
+
require_relative 'dicom/d_object'
|
28
|
+
require_relative 'dicom/d_read'
|
29
|
+
require_relative 'dicom/d_server'
|
30
|
+
require_relative 'dicom/d_write'
|
31
|
+
require_relative 'dicom/element'
|
32
|
+
require_relative 'dicom/file_handler'
|
33
|
+
require_relative 'dicom/item'
|
34
|
+
require_relative 'dicom/link'
|
35
|
+
require_relative 'dicom/sequence'
|
36
|
+
require_relative 'dicom/stream'
|
36
37
|
# Extensions to the Ruby library:
|
37
|
-
|
38
|
+
require_relative 'dicom/ruby_extensions'
|
38
39
|
# Module settings:
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
require_relative 'dicom/version'
|
41
|
+
require_relative 'dicom/constants'
|
42
|
+
require_relative 'dicom/variables'
|
42
43
|
# Image processors:
|
43
|
-
|
44
|
-
|
44
|
+
require_relative 'dicom/image_processor_mini_magick'
|
45
|
+
require_relative 'dicom/image_processor_r_magick'
|
46
|
+
# Deprecated methods:
|
47
|
+
require_relative 'dicom/deprecated'
|
45
48
|
|
46
49
|
# Extensions (non-core functionality):
|
47
|
-
|
50
|
+
require_relative 'dicom/anonymizer'
|
51
|
+
require_relative 'dicom/audit_trail'
|
data/lib/dicom/anonymizer.rb
CHANGED
@@ -11,18 +11,26 @@ module DICOM
|
|
11
11
|
class Anonymizer
|
12
12
|
include Logging
|
13
13
|
|
14
|
+
# An AuditTrail instance used for this anonymization (if specified).
|
15
|
+
attr_reader :audit_trail
|
16
|
+
# The file name used for the AuditTrail serialization (if specified).
|
17
|
+
attr_reader :audit_trail_file
|
14
18
|
# A boolean that if set as true will cause all anonymized tags to be blank instead of get some generic value.
|
15
19
|
attr_accessor :blank
|
16
20
|
# A boolean that if set as true will cause all anonymized tags to be get enumerated values, to enable post-anonymization identification by the user.
|
17
21
|
attr_accessor :enumeration
|
18
|
-
#
|
19
|
-
|
20
|
-
#
|
21
|
-
attr_accessor :
|
22
|
-
# A boolean that if set as true, will make the anonymization remove all private tags.
|
23
|
-
attr_accessor :remove_private
|
22
|
+
# The identity file attribute.
|
23
|
+
attr_reader :identity_file
|
24
|
+
# A boolean that if set as true, will make the anonymization delete all private tags.
|
25
|
+
attr_accessor :delete_private
|
24
26
|
# The path where the anonymized files will be saved. If this value is not set, the original DICOM files will be overwritten.
|
25
27
|
attr_accessor :write_path
|
28
|
+
# A boolean indicating whether or not UIDs shall be replaced when executing the anonymization.
|
29
|
+
attr_accessor :uid
|
30
|
+
# The DICOM UID root to use when generating new UIDs.
|
31
|
+
attr_accessor :uid_root
|
32
|
+
# An array of UID tags that will be anonymized if the uid option is used.
|
33
|
+
attr_accessor :uids
|
26
34
|
|
27
35
|
# Creates an Anonymizer instance.
|
28
36
|
#
|
@@ -30,19 +38,33 @@ module DICOM
|
|
30
38
|
#
|
31
39
|
# * To customize logging behaviour, refer to the Logging module documentation.
|
32
40
|
#
|
41
|
+
# === Parameters
|
42
|
+
#
|
43
|
+
# * <tt>options</tt> -- A hash of parameters.
|
44
|
+
#
|
45
|
+
# === Options
|
46
|
+
#
|
47
|
+
# * <tt>:audit_trail</tt> -- String. A file name path. If the file contains old audit data, these are loaded and used in the current anonymization.
|
48
|
+
# * <tt>:uid</tt> -- Boolean. If true, all (top level) UIDs will be replaced with custom generated UIDs. To preserve UID relations in studies/series, the AuditTrail feature must be used.
|
49
|
+
# * <tt>:uid_root</tt> -- String. An organization (or custom) UID root to use when replacing UIDs.
|
50
|
+
#
|
33
51
|
# === Examples
|
34
52
|
#
|
35
53
|
# # Create an Anonymizer instance and restrict the log output:
|
36
54
|
# a = Anonymizer.new
|
37
55
|
# a.logger.level = Logger::ERROR
|
38
|
-
#
|
39
|
-
|
56
|
+
# # Carry out anonymization using the audit trail feature:
|
57
|
+
# a = Anonymizer.new(:audit_trail => "trail.json")
|
58
|
+
# a.enumeration = true
|
59
|
+
# a.folder = "//dicom/today/"
|
60
|
+
# a.write_path = "//anonymized/"
|
61
|
+
# a.execute
|
62
|
+
#
|
63
|
+
def initialize(options={})
|
40
64
|
# Default value of accessors:
|
41
65
|
@blank = false
|
42
66
|
@enumeration = false
|
43
|
-
@
|
44
|
-
@remove_private = false
|
45
|
-
@write_path = nil
|
67
|
+
@delete_private = false
|
46
68
|
# Array of folders to be processed for anonymization:
|
47
69
|
@folders = Array.new
|
48
70
|
# Folders that will be skipped:
|
@@ -60,8 +82,21 @@ module DICOM
|
|
60
82
|
@files = Array.new
|
61
83
|
# Write paths will be determined later and put in this array:
|
62
84
|
@write_paths = Array.new
|
63
|
-
#
|
64
|
-
@
|
85
|
+
# Register the uid anonymization option:
|
86
|
+
@uid = options[:uid]
|
87
|
+
# Set the uid_root to be used when anonymizing study_uid series_uid and sop_instance_uid
|
88
|
+
@uid_root = options[:uid_root] ? options[:uid_root] : UID
|
89
|
+
# Setup audit trail if requested:
|
90
|
+
if options[:audit_trail]
|
91
|
+
@audit_trail_file = options[:audit_trail]
|
92
|
+
if File.exists?(@audit_trail_file) && File.size(@audit_trail_file) > 2
|
93
|
+
# Load the pre-existing audit trail from file:
|
94
|
+
@audit_trail = AuditTrail.read(@audit_trail_file)
|
95
|
+
else
|
96
|
+
# Start from scratch with an empty audit trail:
|
97
|
+
@audit_trail = AuditTrail.new
|
98
|
+
end
|
99
|
+
end
|
65
100
|
# Set the default data elements to be anonymized:
|
66
101
|
set_defaults
|
67
102
|
end
|
@@ -160,17 +195,24 @@ module DICOM
|
|
160
195
|
all_write = true
|
161
196
|
files_written = 0
|
162
197
|
files_failed_read = 0
|
198
|
+
begin
|
199
|
+
require 'progressbar'
|
200
|
+
pbar = ProgressBar.new("Anonymizing", @files.length)
|
201
|
+
rescue LoadError
|
202
|
+
pbar = nil
|
203
|
+
end
|
163
204
|
# Temporarily increase the log threshold to suppress messages from the DObject class:
|
164
205
|
anonymizer_level = logger.level
|
165
206
|
logger.level = Logger::FATAL
|
166
207
|
@files.each_index do |i|
|
208
|
+
pbar.inc if pbar
|
167
209
|
# Read existing file to DICOM object:
|
168
|
-
|
169
|
-
if
|
210
|
+
dcm = DObject.read(@files[i])
|
211
|
+
if dcm.read?
|
170
212
|
# Anonymize the desired tags:
|
171
213
|
@tags.each_index do |j|
|
172
|
-
if
|
173
|
-
element =
|
214
|
+
if dcm.exists?(@tags[j])
|
215
|
+
element = dcm[@tags[j]]
|
174
216
|
if element.is_a?(Element)
|
175
217
|
if @blank
|
176
218
|
value = ""
|
@@ -178,7 +220,7 @@ module DICOM
|
|
178
220
|
old_value = element.value
|
179
221
|
# Only launch enumeration logic if there is an actual value to the data element:
|
180
222
|
if old_value
|
181
|
-
value =
|
223
|
+
value = enumerated_value(old_value, j)
|
182
224
|
else
|
183
225
|
value = ""
|
184
226
|
end
|
@@ -190,11 +232,17 @@ module DICOM
|
|
190
232
|
end
|
191
233
|
end
|
192
234
|
end
|
193
|
-
#
|
194
|
-
|
235
|
+
# Handle UIDs if requested:
|
236
|
+
replace_uids(dcm) if @uid
|
237
|
+
# Delete private tags?
|
238
|
+
dcm.delete_private if @delete_private
|
239
|
+
# Delete Tags marked for removal:
|
240
|
+
@delete_tags.each_index do |j|
|
241
|
+
dcm.delete(@delete_tags[j]) if dcm.exists?(@delete_tags[j])
|
242
|
+
end
|
195
243
|
# Write DICOM file:
|
196
|
-
|
197
|
-
if
|
244
|
+
dcm.write(@write_paths[i])
|
245
|
+
if dcm.written?
|
198
246
|
files_written += 1
|
199
247
|
else
|
200
248
|
all_write = false
|
@@ -204,7 +252,8 @@ module DICOM
|
|
204
252
|
files_failed_read += 1
|
205
253
|
end
|
206
254
|
end
|
207
|
-
|
255
|
+
pbar.finish if pbar
|
256
|
+
# Finished anonymizing files. Reset the log threshold:
|
208
257
|
logger.level = anonymizer_level
|
209
258
|
# Print elapsed time and status of anonymization:
|
210
259
|
end_time = Time.now.to_f
|
@@ -219,8 +268,9 @@ module DICOM
|
|
219
268
|
else
|
220
269
|
logger.warn("Some DICOM objects were NOT succesfully written to file. You are advised to investigate the result (#{files_written} files succesfully written).")
|
221
270
|
end
|
271
|
+
@audit_trail.write(@audit_trail_file) if @audit_trail
|
222
272
|
# Has user requested enumeration and specified an identity file in which to store the anonymized values?
|
223
|
-
if @enumeration and @identity_file
|
273
|
+
if @enumeration and @identity_file and !@audit_trail
|
224
274
|
logger.info("Writing identity file.")
|
225
275
|
write_identity_file
|
226
276
|
logger.info("Done")
|
@@ -235,6 +285,16 @@ module DICOM
|
|
235
285
|
end
|
236
286
|
end
|
237
287
|
|
288
|
+
# Setter method for the identity file.
|
289
|
+
# NB! The identity file feature is deprecated!
|
290
|
+
# Please use the AuditTrail feature instead.
|
291
|
+
#
|
292
|
+
def identity_file=(file_name)
|
293
|
+
# Deprecation warning:
|
294
|
+
logger.warn("The identity_file feature of the Anonymization class has been deprecated! Please use the AuditTrail feature instead.")
|
295
|
+
@identity_file = file_name
|
296
|
+
end
|
297
|
+
|
238
298
|
# Prints to screen a list of which tags are currently selected for anonymization along with
|
239
299
|
# the replacement values that will be used and enumeration status.
|
240
300
|
#
|
@@ -310,6 +370,22 @@ module DICOM
|
|
310
370
|
@enumerations.delete_at(pos)
|
311
371
|
end
|
312
372
|
end
|
373
|
+
|
374
|
+
# Compeletely deletes a tag from the file
|
375
|
+
#
|
376
|
+
# === Parameters
|
377
|
+
#
|
378
|
+
# * <tt>tag</tt> -- String. A data element tag.
|
379
|
+
#
|
380
|
+
# === Examples
|
381
|
+
#
|
382
|
+
# a.delete_tag("0010,0010")
|
383
|
+
#
|
384
|
+
def delete_tag(tag)
|
385
|
+
raise ArgumentError, "Expected String, got #{tag.class}." unless tag.is_a?(String)
|
386
|
+
raise ArgumentError, "Expected a valid tag of format 'GGGG,EEEE', got #{tag}." unless tag.tag?
|
387
|
+
@delete_tags.push(tag) if not @delete_tags.include?(tag)
|
388
|
+
end
|
313
389
|
|
314
390
|
# Sets the anonymization settings for the specified tag. If the tag is already present in the list
|
315
391
|
# of tags to be anonymized, its settings are updated, and if not, a new tag entry is created.
|
@@ -406,39 +482,53 @@ module DICOM
|
|
406
482
|
end
|
407
483
|
end
|
408
484
|
|
409
|
-
# Handles the enumeration for the
|
410
|
-
# If its value has been encountered before, its corresponding enumerated
|
411
|
-
#
|
412
|
-
#
|
485
|
+
# Handles the enumeration for the given data element tag.
|
486
|
+
# If its value has been encountered before, its corresponding enumerated
|
487
|
+
# replacement value is retrieved, and if a new original value is encountered,
|
488
|
+
# a new enumerated replacement value is found by increasing an index by 1.
|
489
|
+
# Returns the replacement value which is used for the anonymization of the tag.
|
413
490
|
#
|
414
491
|
# === Parameters
|
415
492
|
#
|
416
|
-
# * <tt>
|
493
|
+
# * <tt>original</tt> -- The original value of the tag that to be anonymized.
|
417
494
|
# * <tt>j</tt> -- Fixnum. The index of this tag in the tag-related instance arrays.
|
418
495
|
#
|
419
|
-
def
|
496
|
+
def enumerated_value(original, j)
|
420
497
|
# Is enumeration requested for this tag?
|
421
498
|
if @enumerations[j]
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
@enum_new_hash[@tags[j]] = previous_new
|
499
|
+
if @audit_trail
|
500
|
+
# Check if the UID has been encountered already:
|
501
|
+
replacement = @audit_trail.replacement(@tags[j], original)
|
502
|
+
unless replacement
|
503
|
+
# This original value has not been encountered yet. Determine the index to use.
|
504
|
+
index = @audit_trail.records(@tags[j]).length + 1
|
505
|
+
# Create the replacement value:
|
506
|
+
replacement = @values[j] + index.to_s
|
507
|
+
# Add this tag record to the audit trail:
|
508
|
+
@audit_trail.add_record(@tags[j], original, replacement)
|
509
|
+
end
|
434
510
|
else
|
435
|
-
#
|
436
|
-
|
511
|
+
# Retrieve earlier used anonymization values:
|
512
|
+
previous_old = @enum_old_hash[@tags[j]]
|
513
|
+
previous_new = @enum_new_hash[@tags[j]]
|
514
|
+
p_index = previous_old.length
|
515
|
+
if previous_old.index(original) == nil
|
516
|
+
# Current value has not been encountered before:
|
517
|
+
replacement = @values[j]+(p_index + 1).to_s
|
518
|
+
# Store value in array (and hash):
|
519
|
+
previous_old << original
|
520
|
+
previous_new << replacement
|
521
|
+
@enum_old_hash[@tags[j]] = previous_old
|
522
|
+
@enum_new_hash[@tags[j]] = previous_new
|
523
|
+
else
|
524
|
+
# Current value has been observed before:
|
525
|
+
replacement = previous_new[previous_old.index(original)]
|
526
|
+
end
|
437
527
|
end
|
438
528
|
else
|
439
|
-
|
529
|
+
replacement = @values[j]
|
440
530
|
end
|
441
|
-
return
|
531
|
+
return replacement
|
442
532
|
end
|
443
533
|
|
444
534
|
# Discovers all the files contained in the specified directory (all its sub-directories),
|
@@ -502,10 +592,54 @@ module DICOM
|
|
502
592
|
end
|
503
593
|
end
|
504
594
|
|
505
|
-
#
|
506
|
-
#
|
595
|
+
# Replaces the UIDs of the given DICOM object.
|
596
|
+
#
|
597
|
+
# === Notes
|
598
|
+
#
|
599
|
+
# Empty UIDs are ignored (we don't generate new UIDs for these).
|
600
|
+
# If AuditTrail is set, the relationship between old and new UIDs
|
601
|
+
# are preserved, and the relations between files in a study/series
|
602
|
+
# should remain valid.
|
603
|
+
#
|
604
|
+
#
|
605
|
+
def replace_uids(dcm)
|
606
|
+
@uids.each_pair do |tag, prefix|
|
607
|
+
original = dcm.value(tag)
|
608
|
+
if original && original.length > 0
|
609
|
+
# We have a UID value, go ahead and replace it:
|
610
|
+
if @audit_trail
|
611
|
+
# Check if the UID has been encountered already:
|
612
|
+
replacement = @audit_trail.replacement(tag, original)
|
613
|
+
unless replacement
|
614
|
+
# The UID has not been stored previously. Generate a new one:
|
615
|
+
replacement = DICOM.generate_uid(@uid_root, prefix)
|
616
|
+
# Add this tag record to the audit trail:
|
617
|
+
@audit_trail.add_record(tag, original, replacement)
|
618
|
+
end
|
619
|
+
# Replace the UID in the DICOM object:
|
620
|
+
dcm[tag].value = replacement
|
621
|
+
# NB! The SOP Instance UID must also be written to the Media Storage SOP Instance UID tag:
|
622
|
+
dcm["0002,0003"].value = replacement if tag == "0008,0018" && dcm.exists?("0002,0003")
|
623
|
+
else
|
624
|
+
# We don't care about preserving UID relations. Just insert a custom UID:
|
625
|
+
dcm[tag].value = DICOM.generate_uid(@uid_root, prefix)
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
# Sets up some default information variables that are used by the Anonymizer.
|
507
632
|
#
|
508
633
|
def set_defaults
|
634
|
+
# A hash of UID tags to be replaced (if requested) and prefixes to use for each tag:
|
635
|
+
@uids = {
|
636
|
+
"0008,0018" => 3, # SOP Instance UID
|
637
|
+
"0020,000D" => 1, # Study Instance UID
|
638
|
+
"0020,000E" => 2, # Series Instance UID
|
639
|
+
"0020,0052" => 9 # Frame of Reference UID
|
640
|
+
}
|
641
|
+
# Sets up default tags that will be anonymized, along with default replacement values and enumeration settings.
|
642
|
+
# This data is stored in 3 separate instance arrays for tags, values and enumeration.
|
509
643
|
data = [
|
510
644
|
["0008,0012", "20000101", false], # Instance Creation Date
|
511
645
|
["0008,0013", "000000.00", false], # Instance Creation Time
|
@@ -513,6 +647,7 @@ module DICOM
|
|
513
647
|
["0008,0023", "20000101", false], # Image Date
|
514
648
|
["0008,0030", "000000.00", false], # Study Time
|
515
649
|
["0008,0033", "000000.00", false], # Image Time
|
650
|
+
["0008,0050", "", true], # Accession Number
|
516
651
|
["0008,0080", "Institution", true], # Institution name
|
517
652
|
["0008,0090", "Physician", true], # Referring Physician's name
|
518
653
|
["0008,1010", "Station", true], # Station name
|
@@ -526,6 +661,10 @@ module DICOM
|
|
526
661
|
@tags = data[0]
|
527
662
|
@values = data[1]
|
528
663
|
@enumerations = data[2]
|
664
|
+
|
665
|
+
# Tags to be deleted completely during anonymization
|
666
|
+
@delete_tags = [
|
667
|
+
]
|
529
668
|
end
|
530
669
|
|
531
670
|
# Writes an identity file, which allows reidentification of DICOM files that have been anonymized
|
@@ -552,6 +691,5 @@ module DICOM
|
|
552
691
|
end
|
553
692
|
end
|
554
693
|
end
|
555
|
-
|
556
694
|
end
|
557
695
|
end
|