dicom 0.9.2 → 0.9.3
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/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
|