dicom 0.8 → 0.9

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,56 +1,114 @@
1
+ = 0.9
2
+
3
+ === 17th May, 2011
4
+
5
+ * Fixed an issue where data elements added to a DObject didn't get their endianness updated.
6
+ * Added the ability to encode/decode 32 bit pixel data.
7
+ * Added the ability to encode/decode big endian signed integers.
8
+ * Added the remove_children() method to parent elements.
9
+ * Added the ability to decode color images.
10
+ * Added the ability to retrieve multiple pixel frames as image objects.
11
+ * Added a :level option to the image retrieval methods which accepts custom window center/width values.
12
+ * Renamed the :rescale option to :remap in the image retrieval methods.
13
+ * Added the ability to decode RLE and JPEG Baseline compressed images.
14
+ * Added a specification test suite (using rspec and mocha).
15
+ * Implemented proper handling of data element values with VR "AT" (tag references).
16
+ * Changed the specification for Item indices (now starts at 0).
17
+ * Introduced the concept of ImageProcessors to replace the previously integrated RMagick image methods:
18
+ * The user can select which ImageProcessor to be used at run-time.
19
+ * Initially supported ImageProcessors: RMagick and mini_magick.
20
+ * Additional ImageProcessors can easily be added using existing template.
21
+ * Major refactoring and simplification of all image related code.
22
+ * Some changes of the Anonymizer class:
23
+ * Added a :verbose option.
24
+ * Added enum(), set_tag() and value() methods.
25
+ * Removed add_tag, change_enum() and change_value() methods.
26
+ * Added a log attribute.
27
+ * Fixed an issue where using a transfer syntax option with a binary string did not result in a correct application of the supplied syntax.
28
+ * Added a .dcm extension to DICOM files received by DServer.
29
+ * Fixed an issue where sending DICOM files with compressed pixel data resulted in an invalid DICOM transfer.
30
+ * Changed the specification to allow the query methods of DClient to accept any tag as a query parameter.
31
+ * Improved the handling of parent-child relations so these are always properly updated when adding, removing or changing parent elements.
32
+ * Renamed the following classes/modules:
33
+ * SuperParent -> Parent
34
+ * SuperItem -> ImageItem
35
+ * DataElement -> Element
36
+ * Elements -> Elemental
37
+ * Replaced ImageItem#image_properties() with num_cols(), num_rows() and num_frames().
38
+ * Renamed the following ImageItem methods:
39
+ * get_image_magick() -> image()
40
+ * get_images_magick() -> images()
41
+ * set_image_magick() -> image=()
42
+ * get_image() -> pixels
43
+ * get_image_narray -> narray
44
+ * set_image -> pixels=()
45
+ * set_image_narray -> pixels=()
46
+ * Added the following conversion methods to DObject, Sequence, Item & Element:
47
+ * inspect()
48
+ * to_hash()
49
+ * to_json()
50
+ * to_yaml()
51
+ * Added the ability to use DICOM element names as methods for creating, editing and deleting elements.
52
+ * Added the ability to read a DICOM file from a http string, using open-uri (experimental).
53
+ * Added alias_methods read? for read_success and written? for write_success in DObject.
54
+ * Renamed DObject#information() to summary().
55
+ * Added various ArgumentError exceptions to facility easier debugging.
56
+ * Various bug fixes.
57
+
58
+
1
59
  = 0.8
2
60
 
3
61
  === 1st August, 2010
4
62
 
5
63
  * Overall changes:
6
- * A complete rewrite of data element handling has resulted in a substantially different syntax and significantly cleaner and simpler code.
7
- * Greatly increased speed in DICOM object interaction. Especially noticeable on larger DICOM files.
8
- * A complete overhaul of the documentation has resulted in a consistent RDoc-style documentation across all classes/modules.
9
- * Increased use of module constants for improved code readability.
10
- * Major cleanup of codes and comments to make it consistent with Ruby 'best practice' guidelines.
11
- * Ready for Rails 3 (compatible with Bundler).
64
+ * A complete rewrite of data element handling has resulted in a substantially different syntax and significantly cleaner and simpler code.
65
+ * Greatly increased speed in DICOM object interaction. Especially noticeable on larger DICOM files.
66
+ * A complete overhaul of the documentation has resulted in a consistent RDoc-style documentation across all classes/modules.
67
+ * Increased use of module constants for improved code readability.
68
+ * Major cleanup of codes and comments to make it consistent with Ruby 'best practice' guidelines.
69
+ * Ready for Rails 3 (compatible with Bundler).
12
70
  * Resulting from the rewrite is a number of new classes and modules for handling the various data objects:
13
- * DataElement: Ordinary data elements are instances of this class.
14
- * Item: Item elements are instances of this class.
15
- * Sequence: Sequence elements are instances of this class.
16
- * SuperItem: Methods which are shared between DObject and Item (mostly image related methods).
17
- * SuperParent: Methods which are shared between all parent objects (DObject, Item & Sequence).
18
- * Elements: A mix-in module used by all element objects (DataElement, Item & Sequence).
71
+ * DataElement: Ordinary data elements are instances of this class.
72
+ * Item: Item elements are instances of this class.
73
+ * Sequence: Sequence elements are instances of this class.
74
+ * SuperItem: Methods which are shared between DObject and Item (mostly image related methods).
75
+ * SuperParent: Methods which are shared between all parent objects (DObject, Item & Sequence).
76
+ * Elements: A mix-in module used by all element objects (DataElement, Item & Sequence).
19
77
  * DObject:
20
- * Completely rewritten data element handling.
21
- * Rewritten print() method with improved tree structure visualization, item indices and the ability to run on any parent element.
22
- * Renamed the print_properties() method to information(), and improved the amount of information outputted.
23
- * Removed the following methods: get_frames(), get_image_pos(), get_pos(), get_value(), get_bin(), set_value()
24
- * Renamed get_pixels() method to decode_pixels().
25
- * Replaced get_value() with value(). The set_value() method is replaced by the new() methods of DataElement, Sequence and Item classes.
26
- * Added the ability to add items at a specific index (shifting any existing items to appear after the inserted item).
27
- * Added the transfer_syntax=() method for changing the transfer syntax of a DICOM object.
28
- * Implemented a more robust padding of data element values with odd length, based on data element VR.
29
- * Added methods for removing all sequences, removing selected groups and returning all data elements of a selected group.
30
- * Optimized the get_image_narray() method to more efficiently return the pixel data.
31
- * Refined the print_all convenience method to call both print() and information().
78
+ * Completely rewritten data element handling.
79
+ * Rewritten print() method with improved tree structure visualization, item indices and the ability to run on any parent element.
80
+ * Renamed the print_properties() method to information(), and improved the amount of information outputted.
81
+ * Removed the following methods: get_frames(), get_image_pos(), get_pos(), get_value(), get_bin(), set_value()
82
+ * Renamed get_pixels() method to decode_pixels().
83
+ * Replaced get_value() with value(). The set_value() method is replaced by the new() methods of DataElement, Sequence and Item classes.
84
+ * Added the ability to add items at a specific index (shifting any existing items to appear after the inserted item).
85
+ * Added the transfer_syntax=() method for changing the transfer syntax of a DICOM object.
86
+ * Implemented a more robust padding of data element values with odd length, based on data element VR.
87
+ * Added methods for removing all sequences, removing selected groups and returning all data elements of a selected group.
88
+ * Optimized the get_image_narray() method to more efficiently return the pixel data.
89
+ * Refined the print_all convenience method to call both print() and information().
32
90
  * DRead:
33
- * More robust against crashing when parsing invalid files by proper use of exception handling.
34
- * Fixed some cases where a failed read where incorrectly marked as successful.
35
- * Increased compatibility (handles DICOM files saved with explicit syntax but no transfer syntax tag).
36
- * Explicit DICOM files with data elements of type "OF" are now handled correctly (both read and write).
91
+ * More robust against crashing when parsing invalid files by proper use of exception handling.
92
+ * Fixed some cases where a failed read where incorrectly marked as successful.
93
+ * Increased compatibility (handles DICOM files saved with explicit syntax but no transfer syntax tag).
94
+ * Explicit DICOM files with data elements of type "OF" are now handled correctly (both read and write).
37
95
  * DWrite:
38
- * Simplified code and execution by removing group lengths (deprecated in the DICOM standard) and using undefined length for sequences/items.
39
- * More flexible insertion of missing meta group elements.
96
+ * Simplified code and execution by removing group lengths (deprecated in the DICOM standard) and using undefined length for sequences/items.
97
+ * More flexible insertion of missing meta group elements.
40
98
  * Dictionary:
41
- * Some minor text corrections.
99
+ * Some minor text corrections.
42
100
  * Anonymizer:
43
- * Anonymizations executes much faster thanks to the work done with DObject data element handling.
44
- * Only top level data elements can be anonymized.
101
+ * Anonymizations executes much faster thanks to the work done with DObject data element handling.
102
+ * Only top level data elements can be anonymized.
45
103
  * DClient:
46
- * Can transmit multiple DICOM files at once with the send() method, which now will accept both DObjects and file strings.
47
- * Added the echo() method for performing a C-ECHO-RQ against a SCP.
104
+ * Can transmit multiple DICOM files at once with the send() method, which now will accept both DObjects and file strings.
105
+ * Added the echo() method for performing a C-ECHO-RQ against a SCP.
48
106
  * DServer:
49
- * Support for role negotiation.
50
- * Properly returns called AE title in association response.
51
- * The lists of acceptable abstract and transfer syntax can now be easily modified by the user.
52
- * Receiving multiple files (like an entire series, or study) in a single association is now supported.
53
- * Properly receives and responds to a C-ECHO-RQ.
107
+ * Support for role negotiation.
108
+ * Properly returns called AE title in association response.
109
+ * The lists of acceptable abstract and transfer syntax can now be easily modified by the user.
110
+ * Receiving multiple files (like an entire series, or study) in a single association is now supported.
111
+ * Properly receives and responds to a C-ECHO-RQ.
54
112
 
55
113
 
56
114
  = 0.7
@@ -102,7 +160,7 @@
102
160
 
103
161
  * Complete rewrite of encoding/decoding to reduce code duplication and simplify the code.
104
162
  * General optimizations to improve speed and reduce code complexity.
105
- * Rewrote Dictionary to use Hash instead of Array. This has significantly improved the read spead of the library.
163
+ * Rewrote Dictionary to use Hash instead of Array. This has significantly improved the read speed of the library.
106
164
  * Ruby DICOM now automatically strips away trailing whitespace when decoding string values.
107
165
  * Introducing basic, experimental network functionality.
108
166
  * The new DClient class enables features such as query, move and send.
@@ -188,14 +246,4 @@
188
246
  First public release.
189
247
  The library does have several known issues and lacks some features I would like it to have, but it does
190
248
  offer basic functionality and should be usable for people interested in working with DICOM files in Ruby.
191
- The reading algorithm has been tested succesfully with some 40 different DICOM files, so it should be fairly robust.
192
-
193
-
194
- Known issues:
195
- * The retrieve file network functionality (get_image() in DClient class) has not been tested.
196
- * Compressed pixel data is poorly handled.
197
- * Read/Write 12 bit image data not available.
198
- * Color image data is poorly handled.
199
- * Incomplete support for Big endian (Everything but signed short and signed long has been implemented).
200
- * Incomplete support for multiple frame image data to NArray and RMagick objects (partial support already featured).
201
- * Image handling does not take into consideration DICOM tags which specify orientation, samples per pixel and photometric interpretation.
249
+ The reading algorithm has been tested successfully with some 40 different DICOM files, so it should be fairly robust.
@@ -0,0 +1,126 @@
1
+ = RUBY DICOM
2
+
3
+ Ruby DICOM is a small and simple library for handling DICOM in Ruby. DICOM (Digital Imaging
4
+ and Communications in Medicine) is a standard for handling, storing, printing,
5
+ and transmitting information in medical imaging. It includes a file format definition
6
+ and a network communications protocol. Ruby DICOM supports reading from, editing
7
+ and writing to this file format. It also features basic support for select network
8
+ communication modalities like querying, moving, sending and receiving files.
9
+
10
+
11
+ == INSTALLATION
12
+
13
+ gem install dicom
14
+
15
+
16
+ == BASIC USAGE
17
+
18
+ === Load & Include
19
+
20
+ require 'dicom'
21
+ include DICOM
22
+
23
+ === Read, modify and write
24
+
25
+ # Read file:
26
+ obj = DObject.new("some_file.dcm")
27
+ # Extract the Patient's Name value:
28
+ obj.patients_name.value
29
+ # Add or modify the Patient's Name element:
30
+ obj.patients_name = "Anonymous"
31
+ # Remove a data element from the DICOM object:
32
+ obj.pixel_data = nil
33
+ # Write to file:
34
+ obj.write("new_file.dcm")
35
+
36
+ === Modify using tag strings instead of dictionary method names
37
+
38
+ # Extract the Patient's Name value:
39
+ obj.value("0010,0010")
40
+ # Modify the Patient's Name element:
41
+ obj["0010,0010"].value = "Anonymous"
42
+ # Remove a data element from the DICOM object:
43
+ obj.remove("7FE0,0010")
44
+
45
+ === Extracting information about the DICOM object
46
+
47
+ # Display a short summary of the file's properties:
48
+ obj.summary
49
+ # Print all data elements to screen:
50
+ obj.print
51
+ # Convert the data element hierarchy to a nested hash:
52
+ obj.to_hash
53
+
54
+ === Handle pixel data
55
+
56
+ # Retrieve the pixel data in a Ruby Array:
57
+ obj.pixels
58
+ # Load the pixel data to an numerical array (NArray):
59
+ obj.narray
60
+ # Load the pixel data to an RMagick image object and display it on the screen:
61
+ obj.image.display
62
+
63
+ === Transmit a DICOM file
64
+
65
+ # Send a local file to a server (PACS) over the network:
66
+ node = DClient.new("10.1.25.200", 104)
67
+ node.send("some_file.dcm")
68
+
69
+ === Start a DICOM server
70
+
71
+ # Initiate a simple storage provider which can receive DICOM files of all modalities:
72
+ s = DServer.new(104, :host_ae => "MY_DICOM_SERVER")
73
+ s.start_scp("C:/temp/")
74
+
75
+ === IRB Tip
76
+
77
+ When working with Ruby DICOM in irb, you may be annoyed with all the information
78
+ that is printed to screen, regardless if you have set verbose as false. This is because
79
+ in irb every variable loaded in the program is automatically printed to the screen.
80
+ A useful hack to avoid this effect is to append ";0" after a command.
81
+ Example:
82
+ obj = DObject.new("some_file.dcm") ;0
83
+
84
+
85
+ == RESOURCES
86
+
87
+ * {Official home page}[http://dicom.rubyforge.org/]
88
+ * {Discussion forum}[http://groups.google.com/group/ruby-dicom]
89
+ * {Source code repository}[https://github.com/dicom/ruby-dicom]
90
+
91
+
92
+ == COPYRIGHT
93
+
94
+ Copyright 2008-2011 Christoffer Lervåg
95
+
96
+ This program is free software: you can redistribute it and/or modify
97
+ it under the terms of the GNU General Public License as published by
98
+ the Free Software Foundation, either version 3 of the License, or
99
+ (at your option) any later version.
100
+
101
+ This program is distributed in the hope that it will be useful,
102
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
103
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
104
+ GNU General Public License for more details.
105
+
106
+ You should have received a copy of the GNU General Public License
107
+ along with this program. If not, see http://www.gnu.org/licenses/ .
108
+
109
+
110
+ == ABOUT THE AUTHOR
111
+
112
+ * Name: Christoffer Lervåg
113
+ * Location: Norway
114
+ * Email: chris.lervag [@nospam.com] @gmail.com
115
+
116
+ Please don't hesitate to email me if you have any feedback related to this project!
117
+
118
+
119
+ == CONTRIBUTORS
120
+
121
+ * {Christoffer Lervåg}[https://github.com/dicom]
122
+ * {John Axel Eriksson}[https://github.com/johnae]
123
+ * {Donnie Millar}[https://github.com/dmillar]
124
+ * Björn Albers
125
+ * {Lars Benner}[https://github.com/Maturin]
126
+ * {Steven Bedrick}[https://github.com/stevenbedrick]
@@ -2,8 +2,8 @@
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
- # * DataElement, Sequence, Item, SuperParent, Elements - users who wish to interact with their DICOM objects will use these classes/modules.
6
- # * SuperItem - Image related methods are found in this class.
5
+ # * Element, Sequence, Item, Parent, Elemental - users who wish to interact with their DICOM objects will use these classes/modules.
6
+ # * ImageItem - Image related methods are found in this class.
7
7
  # * DClient - for client side network communication, like querying, moving & sending DICOM files.
8
8
  # * DServer - for server side network communication: Setting up your own DICOM storage node (SCP).
9
9
  # * Anonymizer - a convenience class for anonymizing your DICOM files.
@@ -13,11 +13,11 @@
13
13
 
14
14
  # Core library:
15
15
  # Super classes/modules:
16
- require 'dicom/super_parent'
17
- require 'dicom/super_item'
18
- require 'dicom/elements'
16
+ require 'dicom/image_processor'
17
+ require 'dicom/parent'
18
+ require 'dicom/image_item'
19
+ require 'dicom/elemental'
19
20
  # Subclasses and independent classes:
20
- require 'dicom/data_element'
21
21
  require 'dicom/d_client'
22
22
  require 'dicom/dictionary'
23
23
  require 'dicom/d_library'
@@ -25,6 +25,7 @@ require 'dicom/d_object'
25
25
  require 'dicom/d_read'
26
26
  require 'dicom/d_server'
27
27
  require 'dicom/d_write'
28
+ require 'dicom/element'
28
29
  require 'dicom/file_handler'
29
30
  require 'dicom/item'
30
31
  require 'dicom/link'
@@ -32,8 +33,13 @@ require 'dicom/sequence'
32
33
  require 'dicom/stream'
33
34
  # Extensions to the Ruby library:
34
35
  require 'dicom/ruby_extensions'
35
- # Module constants:
36
+ # Module settings:
37
+ require 'dicom/version'
36
38
  require 'dicom/constants'
39
+ require 'dicom/variables'
40
+ # Image processors:
41
+ require 'dicom/image_processor_mini_magick'
42
+ require 'dicom/image_processor_r_magick'
37
43
 
38
44
  # Extensions (non-core functionality):
39
45
  require 'dicom/anonymizer'
@@ -1,4 +1,3 @@
1
- # Copyright 2008-2010 Christoffer Lervag
2
1
 
3
2
  module DICOM
4
3
 
@@ -16,8 +15,10 @@ module DICOM
16
15
  attr_accessor :blank
17
16
  # 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.
18
17
  attr_accessor :enumeration
19
- # A boolean, which if enumeration has been selected, can be set as true to make the anonymization produce an identity file that will provide a relationship between original and anonymized values.
18
+ # A string, which if set (and enumeration has been set as well), will make the Anonymizer produce an identity file that provides a relationship between the original and enumerated, anonymized values.
20
19
  attr_accessor :identity_file
20
+ # An array containing status messages accumulated for the Anonymization instance.
21
+ attr_accessor :log
21
22
  # A boolean that if set as true, will make the anonymization remove all private tags.
22
23
  attr_accessor :remove_private
23
24
  # The path where the anonymized files will be saved. If this value is not set, the original DICOM files will be overwritten.
@@ -25,11 +26,23 @@ module DICOM
25
26
 
26
27
  # Creates an Anonymizer instance.
27
28
  #
29
+ # === Parameters
30
+ #
31
+ # * <tt>options</tt> -- A hash of parameters.
32
+ #
33
+ # === Options
34
+ #
35
+ # * <tt>:verbose</tt> -- Boolean. If set to false, the Anonymizer instance will run silently and not output status updates to the screen. Defaults to true.
36
+ #
28
37
  # === Examples
29
38
  #
30
39
  # a = Anonymizer.new
40
+ # # Create an instance in non-verbose mode:
41
+ # a = Anonymizer.new(:verbose => false)
31
42
  #
32
- def initialize
43
+ def initialize(options={})
44
+ # Default verbosity is true if verbosity hasn't been specified (nil):
45
+ @verbose = (options[:verbose] == false ? false : true)
33
46
  # Default value of accessors:
34
47
  @blank = false
35
48
  @enumeration = false
@@ -45,7 +58,7 @@ module DICOM
45
58
  # Default values to use on anonymized data elements:
46
59
  @values = Array.new
47
60
  # Which data elements will have enumeration applied, if requested by the user:
48
- @enum = Array.new
61
+ @enumerations = Array.new
49
62
  # We use a Hash to store information from DICOM files if enumeration is desired:
50
63
  @enum_old_hash = Hash.new
51
64
  @enum_new_hash = Hash.new
@@ -53,6 +66,8 @@ module DICOM
53
66
  @files = Array.new
54
67
  # Write paths will be determined later and put in this array:
55
68
  @write_paths = Array.new
69
+ # Keep track of status messages:
70
+ @log = Array.new
56
71
  # Set the default data elements to be anonymized:
57
72
  set_defaults
58
73
  end
@@ -68,10 +83,11 @@ module DICOM
68
83
  # a.add_exception("/home/dicom/tutorials/")
69
84
  #
70
85
  def add_exception(path)
86
+ raise ArgumentError, "Expected String, got #{path.class}." unless path.is_a?(String)
71
87
  if path
72
88
  # Remove last character if the path ends with a file separator:
73
89
  path.chop! if path[-1..-1] == File::SEPARATOR
74
- @exceptions << path if path
90
+ @exceptions << path
75
91
  end
76
92
  end
77
93
 
@@ -86,88 +102,26 @@ module DICOM
86
102
  # a.add_folder("/home/dicom")
87
103
  #
88
104
  def add_folder(path)
89
- @folders << path if path
90
- end
91
-
92
- # Adds a tag to the list of tags that will be anonymized.
93
- #
94
- # === Parameters
95
- #
96
- # * <tt>tag</tt> -- String. A data element tag.
97
- # * <tt>options</tt> -- A hash of parameters.
98
- #
99
- # === Options
100
- #
101
- # * <tt>:value</tt> -- A replacement value to use for when anonymizing this data element.
102
- # * <tt>:enum</tt> -- Boolean. Specifies if enumeration is to be used for this tag (true) or not (false).
103
- #
104
- # === Examples
105
- #
106
- # a.add_tag("0010,0010, :value => "MrAnonymous", :enum => true)
107
- #
108
- def add_tag(tag, options={})
109
- # Options and defaults:
110
- value = options[:value] || ""
111
- enum = options[:enum] || false
112
- if tag
113
- if tag.is_a?(String)
114
- if tag.length == 9
115
- # Add anonymization information for this tag:
116
- @tags << tag
117
- @values << value
118
- @enum << enum
119
- else
120
- puts "Warning: Invalid tag length. Please use the form 'GGGG,EEEE'."
121
- end
122
- else
123
- puts "Warning: Tag is not a string. Can not add tag."
124
- end
125
- else
126
- puts "Warning: No tag supplied. Nothing to add."
127
- end
128
- end
129
-
130
- # Sets the enumeration status for a specific tag.
131
- #
132
- # === Parameters
133
- #
134
- # * <tt>tag</tt> -- String. A data element tag.
135
- # * <tt>enum</tt> -- Boolean. True to enable enumeration, false to disable it.
136
- #
137
- def change_enum(tag, enum)
138
- pos = @tags.index(tag)
139
- if pos
140
- if enum
141
- @enum[pos] = true
142
- else
143
- @enum[pos] = false
144
- end
145
- else
146
- puts "Specified tag not found in anonymization array. No changes made."
147
- end
105
+ raise ArgumentError, "Expected String, got #{path.class}." unless path.is_a?(String)
106
+ @folders << path
148
107
  end
149
108
 
150
- # Changes the value to be used in the anonymization of a specific tag.
109
+ # Returns the enumeration status for this tag.
110
+ # Returns nil if no match is found for the provided tag.
151
111
  #
152
112
  # === Parameters
153
113
  #
154
114
  # * <tt>tag</tt> -- String. A data element tag.
155
- # * <tt>value</tt> -- The new anonymization replacement value for this data element.
156
115
  #
157
- # === Examples
158
- #
159
- # a.change_value("0008,0090", "Dr.No")
160
- #
161
- def change_value(tag, value)
116
+ def enum(tag)
117
+ raise ArgumentError, "Expected String, got #{tag.class}." unless tag.is_a?(String)
118
+ raise ArgumentError, "Expected a valid tag of format 'GGGG,EEEE', got #{tag}." unless tag.tag?
162
119
  pos = @tags.index(tag)
163
120
  if pos
164
- if value
165
- @values[pos] = value
166
- else
167
- puts "No value were specified. No changes made."
168
- end
121
+ return @enumerations[pos]
169
122
  else
170
- puts "Specified tag not found in anonymization array. No changes made."
123
+ add_msg("The specified tag is not found in the list of tags to be anonymized.")
124
+ return nil
171
125
  end
172
126
  end
173
127
 
@@ -188,30 +142,30 @@ module DICOM
188
142
  #
189
143
  def execute(verbose=false)
190
144
  # Search through the folders to gather all the files to be anonymized:
191
- puts "*******************************************************"
192
- puts "Initiating anonymization process."
145
+ add_msg("*******************************************************")
146
+ add_msg("Initiating anonymization process.")
193
147
  start_time = Time.now.to_f
194
- puts "Searching for files..."
148
+ add_msg("Searching for files...")
195
149
  load_files
196
- puts "Done."
150
+ add_msg("Done.")
197
151
  if @files.length > 0
198
152
  if @tags.length > 0
199
- puts @files.length.to_s + " files have been identified in the specified folder(s)."
153
+ add_msg(@files.length.to_s + " files have been identified in the specified folder(s).")
200
154
  if @write_path
201
155
  # Determine the write paths, as anonymized files will be written to a separate location:
202
- puts "Processing write paths..."
156
+ add_msg("Processing write paths...")
203
157
  process_write_paths
204
- puts "Done"
158
+ add_msg("Done")
205
159
  else
206
160
  # Overwriting old files:
207
- puts "Separate write folder not specified. Will overwrite existing DICOM files."
161
+ add_msg("Separate write folder not specified. Will overwrite existing DICOM files.")
208
162
  @write_paths = @files
209
163
  end
210
164
  # If the user wants enumeration, we need to prepare variables for storing
211
165
  # existing information associated with each tag:
212
166
  create_enum_hash if @enumeration
213
167
  # Start the read/update/write process:
214
- puts "Initiating read/update/write process (This may take some time)..."
168
+ add_msg("Initiating read/update/write process (This may take some time)...")
215
169
  # Monitor whether every file read/write was successful:
216
170
  all_read = true
217
171
  all_write = true
@@ -225,25 +179,22 @@ module DICOM
225
179
  @tags.each_index do |j|
226
180
  if obj.exists?(@tags[j])
227
181
  element = obj[@tags[j]]
228
- if element.is_a?(DataElement)
182
+ if element.is_a?(Element)
229
183
  if @blank
230
184
  value = ""
231
185
  elsif @enumeration
232
186
  old_value = element.value
233
- # Only launch enumeration logic if tag exists:
187
+ # Only launch enumeration logic if there is an actual value to the data element:
234
188
  if old_value
235
189
  value = get_enumeration_value(old_value, j)
236
190
  else
237
191
  value = ""
238
192
  end
239
193
  else
240
- # Value is simply value in array:
194
+ # Use the value that has been set for this tag:
241
195
  value = @values[j]
242
196
  end
243
197
  element.value = value
244
- elsif element.is_a?(Item)
245
- # Possibly a binary data item:
246
- element.bin = ""
247
198
  end
248
199
  end
249
200
  end
@@ -263,32 +214,32 @@ module DICOM
263
214
  end
264
215
  # Finished anonymizing files. Print elapsed time and status of anonymization:
265
216
  end_time = Time.now.to_f
266
- puts "Anonymization process completed!"
217
+ add_msg("Anonymization process completed!")
267
218
  if all_read
268
- puts "All files in specified folder(s) were SUCCESSFULLY read to DICOM objects."
219
+ add_msg("All files in specified folder(s) were SUCCESSFULLY read to DICOM objects.")
269
220
  else
270
- puts "Some files were NOT successfully read (#{files_failed_read} files). If folder(s) contain non-DICOM files, this is probably the reason."
221
+ add_msg("Some files were NOT successfully read (#{files_failed_read} files). If folder(s) contain non-DICOM files, this is probably the reason.")
271
222
  end
272
223
  if all_write
273
- puts "All DICOM objects were SUCCESSFULLY written as DICOM files (#{files_written} files)."
224
+ add_msg("All DICOM objects were SUCCESSFULLY written as DICOM files (#{files_written} files).")
274
225
  else
275
- puts "Some DICOM objects were NOT succesfully written to file. You are advised to have a closer look (#{files_written} files succesfully written)."
226
+ add_msg("Some DICOM objects were NOT succesfully written to file. You are advised to have a closer look (#{files_written} files succesfully written).")
276
227
  end
277
228
  # Has user requested enumeration and specified an identity file in which to store the anonymized values?
278
229
  if @enumeration and @identity_file
279
- puts "Writing identity file."
230
+ add_msg("Writing identity file.")
280
231
  write_identity_file
281
- puts "Done"
232
+ add_msg("Done")
282
233
  end
283
234
  elapsed = (end_time-start_time).to_s
284
- puts "Elapsed time: " + elapsed[0..elapsed.index(".")+1] + " seconds"
235
+ add_msg("Elapsed time: " + elapsed[0..elapsed.index(".")+1] + " seconds")
285
236
  else
286
- puts "No tags have been selected for anonymization. Aborting."
237
+ add_msg("No tags have been selected for anonymization. Aborting.")
287
238
  end
288
239
  else
289
- puts "No files were found in specified folders. Aborting."
240
+ add_msg("No files were found in specified folders. Aborting.")
290
241
  end
291
- puts "*******************************************************"
242
+ add_msg("*******************************************************")
292
243
  end
293
244
 
294
245
  # Prints to screen a list of which tags are currently selected for anonymization along with
@@ -328,7 +279,7 @@ module DICOM
328
279
  f4 = " " if @blank
329
280
  f4 = " "*(value_maxL-@values[i].to_s.length+1) unless @blank
330
281
  if @enumeration
331
- enum = @enum[i]
282
+ enum = @enumerations[i]
332
283
  else
333
284
  enum = ""
334
285
  end
@@ -357,13 +308,67 @@ module DICOM
357
308
  # a.remove_tag("0010,0010")
358
309
  #
359
310
  def remove_tag(tag)
311
+ raise ArgumentError, "Expected String, got #{tag.class}." unless tag.is_a?(String)
312
+ raise ArgumentError, "Expected a valid tag of format 'GGGG,EEEE', got #{tag}." unless tag.tag?
360
313
  pos = @tags.index(tag)
361
314
  if pos
362
315
  @tags.delete_at(pos)
363
316
  @values.delete_at(pos)
364
- @enum.delete_at(pos)
317
+ @enumerations.delete_at(pos)
318
+ end
319
+ end
320
+
321
+ # Sets the anonymization settings for the specified tag. If the tag is already present in the list
322
+ # of tags to be anonymized, its settings are updated, and if not, a new tag entry is created.
323
+ #
324
+ # === Parameters
325
+ #
326
+ # * <tt>tag</tt> -- String. A data element tag.
327
+ # * <tt>options</tt> -- A hash of parameters.
328
+ #
329
+ # === Options
330
+ #
331
+ # * <tt>:value</tt> -- The replacement value to be used when anonymizing this data element. Defaults to the pre-existing value and "" for new tags.
332
+ # * <tt>:enum</tt> -- Boolean. Specifies if enumeration is to be used for this tag. Defaults to the pre-existing value and false for new tags.
333
+ #
334
+ # === Examples
335
+ #
336
+ # a.set_tag("0010,0010, :value => "MrAnonymous", :enum => true)
337
+ #
338
+ def set_tag(tag, options={})
339
+ raise ArgumentError, "Expected String, got #{tag.class}." unless tag.is_a?(String)
340
+ raise ArgumentError, "Expected a valid tag of format 'GGGG,EEEE', got #{tag}." unless tag.tag?
341
+ pos = @tags.index(tag)
342
+ if pos
343
+ # Update existing values:
344
+ @values[pos] = options[:value] if options[:value]
345
+ @enumerations[pos] = options[:enum] if options[:enum] != nil
365
346
  else
366
- puts "Specified tag not found in anonymization array. No changes made."
347
+ # Add new elements:
348
+ @tags << tag
349
+ @values << (options[:value] ? options[:value] : "")
350
+ @enumerations << (options[:enum] ? options[:enum] : false)
351
+ end
352
+ end
353
+
354
+ # Returns the value which will be used when anonymizing this tag.
355
+ # If enumeration is selected for the particular tag, a number will be
356
+ # appended in addition to the string that is returned here.
357
+ # Returns nil if no match is found for the provided tag.
358
+ #
359
+ # === Parameters
360
+ #
361
+ # * <tt>tag</tt> -- String. A data element tag.
362
+ #
363
+ def value(tag)
364
+ raise ArgumentError, "Expected String, got #{tag.class}." unless tag.is_a?(String)
365
+ raise ArgumentError, "Expected a valid tag of format 'GGGG,EEEE', got #{tag}." unless tag.tag?
366
+ pos = @tags.index(tag)
367
+ if pos
368
+ return @values[pos]
369
+ else
370
+ add_msg("The specified tag is not found in the list of tags to be anonymized.")
371
+ return nil
367
372
  end
368
373
  end
369
374
 
@@ -372,6 +377,18 @@ module DICOM
372
377
  private
373
378
 
374
379
 
380
+ # Adds one or more status messages to the log instance array, and if the verbose
381
+ # instance variable is true, the status message is printed to the screen as well.
382
+ #
383
+ # === Parameters
384
+ #
385
+ # * <tt>msg</tt> -- Status message string.
386
+ #
387
+ def add_msg(msg)
388
+ puts msg if @verbose
389
+ @log << msg
390
+ end
391
+
375
392
  # Finds the common path (if any) in the instance file path array, by performing a recursive search
376
393
  # on the folders that make up the path of one such file.
377
394
  # Returns the index of the last folder in the path of the selected file that is common for all file paths.
@@ -402,7 +419,7 @@ module DICOM
402
419
  # Creates a hash that is used for storing information that is used when enumeration is selected.
403
420
  #
404
421
  def create_enum_hash
405
- @enum.each_index do |i|
422
+ @enumerations.each_index do |i|
406
423
  @enum_old_hash[@tags[i]] = Array.new
407
424
  @enum_new_hash[@tags[i]] = Array.new
408
425
  end
@@ -420,7 +437,7 @@ module DICOM
420
437
  #
421
438
  def get_enumeration_value(current, j)
422
439
  # Is enumeration requested for this tag?
423
- if @enum[j]
440
+ if @enumerations[j]
424
441
  # Retrieve earlier used anonymization values:
425
442
  previous_old = @enum_old_hash[@tags[j]]
426
443
  previous_new = @enum_new_hash[@tags[j]]
@@ -527,18 +544,19 @@ module DICOM
527
544
  ].transpose
528
545
  @tags = data[0]
529
546
  @values = data[1]
530
- @enum = data[2]
547
+ @enumerations = data[2]
531
548
  end
532
549
 
533
550
  # Writes an identity file, which allows reidentification of DICOM files that have been anonymized
534
551
  # using the enumeration feature. Values are saved in a text file, using semi colon delineation.
535
552
  #
536
553
  def write_identity_file
554
+ raise ArgumentError, "Expected String, got #{@identity_file.class}. Unable to write identity file." unless @identity_file.is_a?(String)
537
555
  # Open file and prepare to write text:
538
- File.open( @identity_file, 'w' ) do |output|
556
+ File.open(@identity_file, 'w') do |output|
539
557
  # Cycle through each
540
558
  @tags.each_index do |i|
541
- if @enum[i]
559
+ if @enumerations[i]
542
560
  # This tag has had enumeration. Gather original and anonymized values:
543
561
  old_values = @enum_old_hash[@tags[i]]
544
562
  new_values = @enum_new_hash[@tags[i]]