dicom 0.9.1 → 0.9.2
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 +17 -0
 - data/README.rdoc +16 -3
 - data/lib/dicom.rb +3 -1
 - data/lib/dicom/anonymizer.rb +35 -54
 - data/lib/dicom/d_client.rb +49 -64
 - data/lib/dicom/d_object.rb +511 -416
 - data/lib/dicom/d_read.rb +21 -34
 - data/lib/dicom/d_server.rb +21 -61
 - data/lib/dicom/d_write.rb +3 -6
 - data/lib/dicom/element.rb +1 -1
 - data/lib/dicom/file_handler.rb +14 -9
 - data/lib/dicom/image_item.rb +7 -6
 - data/lib/dicom/link.rb +42 -77
 - data/lib/dicom/logging.rb +158 -0
 - data/lib/dicom/parent.rb +9 -8
 - data/lib/dicom/sequence.rb +1 -1
 - data/lib/dicom/version.rb +1 -1
 - metadata +5 -4
 
    
        data/CHANGELOG.rdoc
    CHANGED
    
    | 
         @@ -1,9 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            = 0.9.2
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            === (Not released yet)
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            * Enabled the use of lower case tag letters in methods which previously required the use of upper case letters.
         
     | 
| 
      
 6 
     | 
    
         
            +
            * Added new DObject class methods to offload DObject#new:
         
     | 
| 
      
 7 
     | 
    
         
            +
              * DObject#read is the new preferred method for reading a DICOM file.
         
     | 
| 
      
 8 
     | 
    
         
            +
              * DObject#parse is the new preferred method for parsing an encoded DICOM string.
         
     | 
| 
      
 9 
     | 
    
         
            +
              * The experimental feature of retrieving a DICOM file through http was moved to DObject#get.
         
     | 
| 
      
 10 
     | 
    
         
            +
              * Calling DObject with a string argument was deprecated. 
         
     | 
| 
      
 11 
     | 
    
         
            +
            * Introduced proper logging capabilities which replaced the simple message printouts to STDOUT:
         
     | 
| 
      
 12 
     | 
    
         
            +
              * Based on the Logger class of the Ruby Standard Library.
         
     | 
| 
      
 13 
     | 
    
         
            +
              * Automatically integrates with the Rails logger in a Rails application.
         
     | 
| 
      
 14 
     | 
    
         
            +
              * Supports information levels, logging to file, and more as available in the Ruby Logger.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
       1 
17 
     | 
    
         
             
            = 0.9.1
         
     | 
| 
       2 
18 
     | 
    
         | 
| 
       3 
19 
     | 
    
         
             
            === 27th May, 2011
         
     | 
| 
       4 
20 
     | 
    
         | 
| 
       5 
21 
     | 
    
         
             
            * Fixed a regression in 0.9 where ruby-dicom would cause a Rails application to crash.
         
     | 
| 
       6 
22 
     | 
    
         | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
       7 
24 
     | 
    
         
             
            = 0.9
         
     | 
| 
       8 
25 
     | 
    
         | 
| 
       9 
26 
     | 
    
         
             
            === 17th May, 2011
         
     | 
    
        data/README.rdoc
    CHANGED
    
    | 
         @@ -23,7 +23,7 @@ communication modalities like querying, moving, sending and receiving files. 
     | 
|
| 
       23 
23 
     | 
    
         
             
            === Read, modify and write
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
       25 
25 
     | 
    
         
             
              # Read file:
         
     | 
| 
       26 
     | 
    
         
            -
              obj = DObject. 
     | 
| 
      
 26 
     | 
    
         
            +
              obj = DObject.read("some_file.dcm")
         
     | 
| 
       27 
27 
     | 
    
         
             
              # Extract the Patient's Name value:
         
     | 
| 
       28 
28 
     | 
    
         
             
              obj.patients_name.value
         
     | 
| 
       29 
29 
     | 
    
         
             
              # Add or modify the Patient's Name element:
         
     | 
| 
         @@ -72,6 +72,18 @@ communication modalities like querying, moving, sending and receiving files. 
     | 
|
| 
       72 
72 
     | 
    
         
             
              s = DServer.new(104, :host_ae => "MY_DICOM_SERVER")
         
     | 
| 
       73 
73 
     | 
    
         
             
              s.start_scp("C:/temp/")
         
     | 
| 
       74 
74 
     | 
    
         | 
| 
      
 75 
     | 
    
         
            +
            === Log settings
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
              # Change the log level so that only error messages are displayed:
         
     | 
| 
      
 78 
     | 
    
         
            +
              DICOM.logger.level = Logger::ERROR
         
     | 
| 
      
 79 
     | 
    
         
            +
              # Setting up a simple file log:
         
     | 
| 
      
 80 
     | 
    
         
            +
              l = Logger.new('my_logfile.log')
         
     | 
| 
      
 81 
     | 
    
         
            +
              DICOM.logger = l
         
     | 
| 
      
 82 
     | 
    
         
            +
              # Create a logger which ages logfile daily/monthly:
         
     | 
| 
      
 83 
     | 
    
         
            +
              DICOM.logger = Logger.new('foo.log', 'daily')
         
     | 
| 
      
 84 
     | 
    
         
            +
              DICOM.logger = Logger.new('foo.log', 'monthly')
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
       75 
87 
     | 
    
         
             
            === IRB Tip
         
     | 
| 
       76 
88 
     | 
    
         | 
| 
       77 
89 
     | 
    
         
             
            When working with Ruby DICOM in irb, you may be annoyed with all the information
         
     | 
| 
         @@ -120,7 +132,8 @@ Please don't hesitate to email me if you have any feedback related to this proje 
     | 
|
| 
       120 
132 
     | 
    
         | 
| 
       121 
133 
     | 
    
         
             
            * {Christoffer Lervåg}[https://github.com/dicom]
         
     | 
| 
       122 
134 
     | 
    
         
             
            * {John Axel Eriksson}[https://github.com/johnae]
         
     | 
| 
      
 135 
     | 
    
         
            +
            * {Kamil Bujniewicz}[https://github.com/icdark]
         
     | 
| 
       123 
136 
     | 
    
         
             
            * {Donnie Millar}[https://github.com/dmillar]
         
     | 
| 
       124 
     | 
    
         
            -
            * Björn Albers
         
     | 
| 
      
 137 
     | 
    
         
            +
            * {Björn Albers}[https://github.com/bjoernalbers]
         
     | 
| 
       125 
138 
     | 
    
         
             
            * {Lars Benner}[https://github.com/Maturin]
         
     | 
| 
       126 
     | 
    
         
            -
            * {Steven Bedrick}[https://github.com/stevenbedrick]
         
     | 
| 
      
 139 
     | 
    
         
            +
            * {Steven Bedrick}[https://github.com/stevenbedrick]
         
     | 
    
        data/lib/dicom.rb
    CHANGED
    
    | 
         @@ -11,6 +11,8 @@ 
     | 
|
| 
       11 
11 
     | 
    
         
             
            # The rest of the classes visible in the documentation generated by RDoc is in principle
         
     | 
| 
       12 
12 
     | 
    
         
             
            # 'private' classes, which are mainly of interest to developers.
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
      
 14 
     | 
    
         
            +
            # Logging:
         
     | 
| 
      
 15 
     | 
    
         
            +
            require 'dicom/logging'
         
     | 
| 
       14 
16 
     | 
    
         
             
            # Core library:
         
     | 
| 
       15 
17 
     | 
    
         
             
            # Super classes/modules:
         
     | 
| 
       16 
18 
     | 
    
         
             
            require 'dicom/image_processor'
         
     | 
| 
         @@ -42,4 +44,4 @@ require 'dicom/image_processor_mini_magick' 
     | 
|
| 
       42 
44 
     | 
    
         
             
            require 'dicom/image_processor_r_magick'
         
     | 
| 
       43 
45 
     | 
    
         | 
| 
       44 
46 
     | 
    
         
             
            # Extensions (non-core functionality):
         
     | 
| 
       45 
     | 
    
         
            -
            require 'dicom/anonymizer'
         
     | 
| 
      
 47 
     | 
    
         
            +
            require 'dicom/anonymizer'
         
     | 
    
        data/lib/dicom/anonymizer.rb
    CHANGED
    
    | 
         @@ -1,4 +1,3 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
1 
     | 
    
         
             
            module DICOM
         
     | 
| 
       3 
2 
     | 
    
         | 
| 
       4 
3 
     | 
    
         
             
              # This is a convenience class for handling anonymization of DICOM files.
         
     | 
| 
         @@ -10,6 +9,7 @@ module DICOM 
     | 
|
| 
       10 
9 
     | 
    
         
             
              # (Clinical Trials De-identification Profiles, DICOM Standards Committee, Working Group 18)
         
     | 
| 
       11 
10 
     | 
    
         
             
              #
         
     | 
| 
       12 
11 
     | 
    
         
             
              class Anonymizer
         
     | 
| 
      
 12 
     | 
    
         
            +
                include Logging
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                # A boolean that if set as true will cause all anonymized tags to be blank instead of get some generic value.
         
     | 
| 
       15 
15 
     | 
    
         
             
                attr_accessor :blank
         
     | 
| 
         @@ -26,23 +26,17 @@ module DICOM 
     | 
|
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
                # Creates an Anonymizer instance.
         
     | 
| 
       28 
28 
     | 
    
         
             
                #
         
     | 
| 
       29 
     | 
    
         
            -
                # ===  
     | 
| 
       30 
     | 
    
         
            -
                #
         
     | 
| 
       31 
     | 
    
         
            -
                # * <tt>options</tt> -- A hash of parameters.
         
     | 
| 
       32 
     | 
    
         
            -
                #
         
     | 
| 
       33 
     | 
    
         
            -
                # === Options
         
     | 
| 
      
 29 
     | 
    
         
            +
                # === Notes
         
     | 
| 
       34 
30 
     | 
    
         
             
                #
         
     | 
| 
       35 
     | 
    
         
            -
                # *  
     | 
| 
      
 31 
     | 
    
         
            +
                # * To customize logging behaviour, refer to the Logging module documentation.
         
     | 
| 
       36 
32 
     | 
    
         
             
                #
         
     | 
| 
       37 
33 
     | 
    
         
             
                # === Examples
         
     | 
| 
       38 
34 
     | 
    
         
             
                #
         
     | 
| 
      
 35 
     | 
    
         
            +
                #   # Create an Anonymizer instance and restrict the log output:
         
     | 
| 
       39 
36 
     | 
    
         
             
                #   a = Anonymizer.new
         
     | 
| 
       40 
     | 
    
         
            -
                #    
     | 
| 
       41 
     | 
    
         
            -
                #   a = Anonymizer.new(:verbose => false)
         
     | 
| 
      
 37 
     | 
    
         
            +
                #   a.logger.level = Logger::ERROR
         
     | 
| 
       42 
38 
     | 
    
         
             
                #
         
     | 
| 
       43 
     | 
    
         
            -
                def initialize 
     | 
| 
       44 
     | 
    
         
            -
                  # Default verbosity is true if verbosity hasn't been specified (nil):
         
     | 
| 
       45 
     | 
    
         
            -
                  @verbose = (options[:verbose] == false ? false : true)
         
     | 
| 
      
 39 
     | 
    
         
            +
                def initialize
         
     | 
| 
       46 
40 
     | 
    
         
             
                  # Default value of accessors:
         
     | 
| 
       47 
41 
     | 
    
         
             
                  @blank = false
         
     | 
| 
       48 
42 
     | 
    
         
             
                  @enumeration = false
         
     | 
| 
         @@ -120,7 +114,7 @@ module DICOM 
     | 
|
| 
       120 
114 
     | 
    
         
             
                  if pos
         
     | 
| 
       121 
115 
     | 
    
         
             
                    return @enumerations[pos]
         
     | 
| 
       122 
116 
     | 
    
         
             
                  else
         
     | 
| 
       123 
     | 
    
         
            -
                     
     | 
| 
      
 117 
     | 
    
         
            +
                    logger.warn("The specified tag (#{tag}) was not found in the list of tags to be anonymized.")
         
     | 
| 
       124 
118 
     | 
    
         
             
                    return nil
         
     | 
| 
       125 
119 
     | 
    
         
             
                  end
         
     | 
| 
       126 
120 
     | 
    
         
             
                end
         
     | 
| 
         @@ -133,47 +127,45 @@ module DICOM 
     | 
|
| 
       133 
127 
     | 
    
         
             
                #
         
     | 
| 
       134 
128 
     | 
    
         
             
                # * Only top level data elements are anonymized!
         
     | 
| 
       135 
129 
     | 
    
         
             
                #
         
     | 
| 
       136 
     | 
    
         
            -
                # === Parameters
         
     | 
| 
       137 
     | 
    
         
            -
                #
         
     | 
| 
       138 
     | 
    
         
            -
                # * <tt>verbose</tt> -- Boolean. If set as true, verbose behaviour will be set for the DObject instances that are anonymized. Defaults to false.
         
     | 
| 
       139 
     | 
    
         
            -
                #
         
     | 
| 
       140 
130 
     | 
    
         
             
                #--
         
     | 
| 
       141 
131 
     | 
    
         
             
                # FIXME: This method has grown a bit lengthy. Perhaps it should be looked at one day.
         
     | 
| 
       142 
132 
     | 
    
         
             
                #
         
     | 
| 
       143 
     | 
    
         
            -
                def execute 
     | 
| 
      
 133 
     | 
    
         
            +
                def execute
         
     | 
| 
       144 
134 
     | 
    
         
             
                  # Search through the folders to gather all the files to be anonymized:
         
     | 
| 
       145 
     | 
    
         
            -
                   
     | 
| 
       146 
     | 
    
         
            -
                  add_msg("Initiating anonymization process.")
         
     | 
| 
      
 135 
     | 
    
         
            +
                  logger.info("Initiating anonymization process.")
         
     | 
| 
       147 
136 
     | 
    
         
             
                  start_time = Time.now.to_f
         
     | 
| 
       148 
     | 
    
         
            -
                   
     | 
| 
      
 137 
     | 
    
         
            +
                  logger.info("Searching for files...")
         
     | 
| 
       149 
138 
     | 
    
         
             
                  load_files
         
     | 
| 
       150 
     | 
    
         
            -
                   
     | 
| 
      
 139 
     | 
    
         
            +
                  logger.info("Done.")
         
     | 
| 
       151 
140 
     | 
    
         
             
                  if @files.length > 0
         
     | 
| 
       152 
141 
     | 
    
         
             
                    if @tags.length > 0
         
     | 
| 
       153 
     | 
    
         
            -
                       
     | 
| 
      
 142 
     | 
    
         
            +
                      logger.info(@files.length.to_s + " files have been identified in the specified folder(s).")
         
     | 
| 
       154 
143 
     | 
    
         
             
                      if @write_path
         
     | 
| 
       155 
144 
     | 
    
         
             
                        # Determine the write paths, as anonymized files will be written to a separate location:
         
     | 
| 
       156 
     | 
    
         
            -
                         
     | 
| 
      
 145 
     | 
    
         
            +
                        logger.info("Processing write paths...")
         
     | 
| 
       157 
146 
     | 
    
         
             
                        process_write_paths
         
     | 
| 
       158 
     | 
    
         
            -
                         
     | 
| 
      
 147 
     | 
    
         
            +
                        logger.info("Done")
         
     | 
| 
       159 
148 
     | 
    
         
             
                      else
         
     | 
| 
       160 
149 
     | 
    
         
             
                        # Overwriting old files:
         
     | 
| 
       161 
     | 
    
         
            -
                         
     | 
| 
      
 150 
     | 
    
         
            +
                        logger.warn("Separate write folder not specified. Existing DICOM files will be overwritten.")
         
     | 
| 
       162 
151 
     | 
    
         
             
                        @write_paths = @files
         
     | 
| 
       163 
152 
     | 
    
         
             
                      end
         
     | 
| 
       164 
153 
     | 
    
         
             
                      # If the user wants enumeration, we need to prepare variables for storing
         
     | 
| 
       165 
154 
     | 
    
         
             
                      # existing information associated with each tag:
         
     | 
| 
       166 
155 
     | 
    
         
             
                      create_enum_hash if @enumeration
         
     | 
| 
       167 
156 
     | 
    
         
             
                      # Start the read/update/write process:
         
     | 
| 
       168 
     | 
    
         
            -
                       
     | 
| 
      
 157 
     | 
    
         
            +
                      logger.info("Initiating read/update/write process. This may take some time...")
         
     | 
| 
       169 
158 
     | 
    
         
             
                      # Monitor whether every file read/write was successful:
         
     | 
| 
       170 
159 
     | 
    
         
             
                      all_read = true
         
     | 
| 
       171 
160 
     | 
    
         
             
                      all_write = true
         
     | 
| 
       172 
161 
     | 
    
         
             
                      files_written = 0
         
     | 
| 
       173 
162 
     | 
    
         
             
                      files_failed_read = 0
         
     | 
| 
      
 163 
     | 
    
         
            +
                      # Temporarily increase the log threshold to suppress messages from the DObject class:
         
     | 
| 
      
 164 
     | 
    
         
            +
                      anonymizer_level = logger.level
         
     | 
| 
      
 165 
     | 
    
         
            +
                      logger.level = Logger::FATAL
         
     | 
| 
       174 
166 
     | 
    
         
             
                      @files.each_index do |i|
         
     | 
| 
       175 
167 
     | 
    
         
             
                        # Read existing file to DICOM object:
         
     | 
| 
       176 
     | 
    
         
            -
                        obj = DICOM::DObject. 
     | 
| 
      
 168 
     | 
    
         
            +
                        obj = DICOM::DObject.read(@files[i])
         
     | 
| 
       177 
169 
     | 
    
         
             
                        if obj.read_success
         
     | 
| 
       178 
170 
     | 
    
         
             
                          # Anonymize the desired tags:
         
     | 
| 
       179 
171 
     | 
    
         
             
                          @tags.each_index do |j|
         
     | 
| 
         @@ -212,34 +204,35 @@ module DICOM 
     | 
|
| 
       212 
204 
     | 
    
         
             
                          files_failed_read += 1
         
     | 
| 
       213 
205 
     | 
    
         
             
                        end
         
     | 
| 
       214 
206 
     | 
    
         
             
                      end
         
     | 
| 
       215 
     | 
    
         
            -
                      # Finished anonymizing files.  
     | 
| 
      
 207 
     | 
    
         
            +
                      # Finished anonymizing files. Reset the logg threshold:
         
     | 
| 
      
 208 
     | 
    
         
            +
                      logger.level = anonymizer_level
         
     | 
| 
      
 209 
     | 
    
         
            +
                      # Print elapsed time and status of anonymization:
         
     | 
| 
       216 
210 
     | 
    
         
             
                      end_time = Time.now.to_f
         
     | 
| 
       217 
     | 
    
         
            -
                       
     | 
| 
      
 211 
     | 
    
         
            +
                      logger.info("Anonymization process completed!")
         
     | 
| 
       218 
212 
     | 
    
         
             
                      if all_read
         
     | 
| 
       219 
     | 
    
         
            -
                         
     | 
| 
      
 213 
     | 
    
         
            +
                        logger.info("All files in the specified folder(s) were SUCCESSFULLY read to DICOM objects.")
         
     | 
| 
       220 
214 
     | 
    
         
             
                      else
         
     | 
| 
       221 
     | 
    
         
            -
                         
     | 
| 
      
 215 
     | 
    
         
            +
                        logger.warn("Some files were NOT successfully read (#{files_failed_read} files). If some folder(s) contain non-DICOM files, this is expected.")
         
     | 
| 
       222 
216 
     | 
    
         
             
                      end
         
     | 
| 
       223 
217 
     | 
    
         
             
                      if all_write
         
     | 
| 
       224 
     | 
    
         
            -
                         
     | 
| 
      
 218 
     | 
    
         
            +
                        logger.info("All DICOM objects were SUCCESSFULLY written as DICOM files (#{files_written} files).")
         
     | 
| 
       225 
219 
     | 
    
         
             
                      else
         
     | 
| 
       226 
     | 
    
         
            -
                         
     | 
| 
      
 220 
     | 
    
         
            +
                        logger.warn("Some DICOM objects were NOT succesfully written to file. You are advised to investigate the result (#{files_written} files succesfully written).")
         
     | 
| 
       227 
221 
     | 
    
         
             
                      end
         
     | 
| 
       228 
222 
     | 
    
         
             
                      # Has user requested enumeration and specified an identity file in which to store the anonymized values?
         
     | 
| 
       229 
223 
     | 
    
         
             
                      if @enumeration and @identity_file
         
     | 
| 
       230 
     | 
    
         
            -
                         
     | 
| 
      
 224 
     | 
    
         
            +
                        logger.info("Writing identity file.")
         
     | 
| 
       231 
225 
     | 
    
         
             
                        write_identity_file
         
     | 
| 
       232 
     | 
    
         
            -
                         
     | 
| 
      
 226 
     | 
    
         
            +
                        logger.info("Done")
         
     | 
| 
       233 
227 
     | 
    
         
             
                      end
         
     | 
| 
       234 
228 
     | 
    
         
             
                      elapsed = (end_time-start_time).to_s
         
     | 
| 
       235 
     | 
    
         
            -
                       
     | 
| 
      
 229 
     | 
    
         
            +
                      logger.info("Elapsed time: #{elapsed[0..elapsed.index(".")+1]} seconds")
         
     | 
| 
       236 
230 
     | 
    
         
             
                    else
         
     | 
| 
       237 
     | 
    
         
            -
                       
     | 
| 
      
 231 
     | 
    
         
            +
                      logger.warn("No tags were selected for anonymization. Aborting.")
         
     | 
| 
       238 
232 
     | 
    
         
             
                    end
         
     | 
| 
       239 
233 
     | 
    
         
             
                  else
         
     | 
| 
       240 
     | 
    
         
            -
                     
     | 
| 
      
 234 
     | 
    
         
            +
                    logger.warn("No files were found in specified folders. Aborting.")
         
     | 
| 
       241 
235 
     | 
    
         
             
                  end
         
     | 
| 
       242 
     | 
    
         
            -
                  add_msg("*******************************************************")
         
     | 
| 
       243 
236 
     | 
    
         
             
                end
         
     | 
| 
       244 
237 
     | 
    
         | 
| 
       245 
238 
     | 
    
         
             
                # Prints to screen a list of which tags are currently selected for anonymization along with
         
     | 
| 
         @@ -367,7 +360,7 @@ module DICOM 
     | 
|
| 
       367 
360 
     | 
    
         
             
                  if pos
         
     | 
| 
       368 
361 
     | 
    
         
             
                    return @values[pos]
         
     | 
| 
       369 
362 
     | 
    
         
             
                  else
         
     | 
| 
       370 
     | 
    
         
            -
                     
     | 
| 
      
 363 
     | 
    
         
            +
                    logger.warn("The specified tag (#{tag}) was not found in the list of tags to be anonymized.")
         
     | 
| 
       371 
364 
     | 
    
         
             
                    return nil
         
     | 
| 
       372 
365 
     | 
    
         
             
                  end
         
     | 
| 
       373 
366 
     | 
    
         
             
                end
         
     | 
| 
         @@ -377,18 +370,6 @@ module DICOM 
     | 
|
| 
       377 
370 
     | 
    
         
             
                private
         
     | 
| 
       378 
371 
     | 
    
         | 
| 
       379 
372 
     | 
    
         | 
| 
       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 
     | 
    
         
            -
             
     | 
| 
       392 
373 
     | 
    
         
             
                # Finds the common path (if any) in the instance file path array, by performing a recursive search
         
     | 
| 
       393 
374 
     | 
    
         
             
                # on the folders that make up the path of one such file.
         
     | 
| 
       394 
375 
     | 
    
         
             
                # Returns the index of the last folder in the path of the selected file that is common for all file paths.
         
     | 
| 
         @@ -573,4 +554,4 @@ module DICOM 
     | 
|
| 
       573 
554 
     | 
    
         
             
                end
         
     | 
| 
       574 
555 
     | 
    
         | 
| 
       575 
556 
     | 
    
         
             
              end
         
     | 
| 
       576 
     | 
    
         
            -
            end
         
     | 
| 
      
 557 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/dicom/d_client.rb
    CHANGED
    
    | 
         @@ -10,6 +10,7 @@ module DICOM 
     | 
|
| 
       10 
10 
     | 
    
         
             
              # FIXME: The code which waits for incoming network packets seems to be very CPU intensive. Perhaps there is a more elegant way to wait for incoming messages?
         
     | 
| 
       11 
11 
     | 
    
         
             
              #
         
     | 
| 
       12 
12 
     | 
    
         
             
              class DClient
         
     | 
| 
      
 13 
     | 
    
         
            +
                include Logging
         
     | 
| 
       13 
14 
     | 
    
         | 
| 
       14 
15 
     | 
    
         
             
                # The name of this client (application entity).
         
     | 
| 
       15 
16 
     | 
    
         
             
                attr_accessor :ae
         
     | 
| 
         @@ -23,19 +24,17 @@ module DICOM 
     | 
|
| 
       23 
24 
     | 
    
         
             
                attr_accessor :port
         
     | 
| 
       24 
25 
     | 
    
         
             
                # The maximum period the client will wait on an answer from a server before aborting the communication.
         
     | 
| 
       25 
26 
     | 
    
         
             
                attr_accessor :timeout
         
     | 
| 
       26 
     | 
    
         
            -
                # A boolean which defines if notices/warnings/errors will be printed to the screen (true) or not (false).
         
     | 
| 
       27 
     | 
    
         
            -
                attr_accessor :verbose
         
     | 
| 
       28 
27 
     | 
    
         
             
                # An array, where each index contains a hash with the data elements received in a command response (with tags as keys).
         
     | 
| 
       29 
28 
     | 
    
         
             
                attr_reader :command_results
         
     | 
| 
       30 
29 
     | 
    
         
             
                # An array, where each index contains a hash with the data elements received in a data response (with tags as keys).
         
     | 
| 
       31 
30 
     | 
    
         
             
                attr_reader :data_results
         
     | 
| 
       32 
     | 
    
         
            -
                # An array containing any error messages recorded.
         
     | 
| 
       33 
     | 
    
         
            -
                attr_reader :errors
         
     | 
| 
       34 
     | 
    
         
            -
                # An array containing any status messages recorded.
         
     | 
| 
       35 
     | 
    
         
            -
                attr_reader :notices
         
     | 
| 
       36 
31 
     | 
    
         | 
| 
       37 
32 
     | 
    
         
             
                # Creates a DClient instance.
         
     | 
| 
       38 
33 
     | 
    
         
             
                #
         
     | 
| 
      
 34 
     | 
    
         
            +
                # === Notes
         
     | 
| 
      
 35 
     | 
    
         
            +
                #
         
     | 
| 
      
 36 
     | 
    
         
            +
                # * To customize logging behaviour, refer to the Logging module documentation.
         
     | 
| 
      
 37 
     | 
    
         
            +
                #
         
     | 
| 
       39 
38 
     | 
    
         
             
                # === Parameters
         
     | 
| 
       40 
39 
     | 
    
         
             
                #
         
     | 
| 
       41 
40 
     | 
    
         
             
                # * <tt>host_ip</tt> -- String. The IP adress of the server which you are going to communicate with.
         
     | 
| 
         @@ -48,7 +47,6 @@ module DICOM 
     | 
|
| 
       48 
47 
     | 
    
         
             
                # * <tt>:host_ae</tt> -- String. The name of the server (application entity).
         
     | 
| 
       49 
48 
     | 
    
         
             
                # * <tt>:max_package_size</tt> -- Fixnum. The maximum allowed size of network packages (in bytes).
         
     | 
| 
       50 
49 
     | 
    
         
             
                # * <tt>:timeout</tt> -- Fixnum. The maximum period the server will wait on an answer from a client before aborting the communication.
         
     | 
| 
       51 
     | 
    
         
            -
                # * <tt>:verbose</tt> -- Boolean. If set to false, the DClient instance will run silently and not output warnings and error messages to the screen. Defaults to true.
         
     | 
| 
       52 
50 
     | 
    
         
             
                #
         
     | 
| 
       53 
51 
     | 
    
         
             
                # === Examples
         
     | 
| 
       54 
52 
     | 
    
         
             
                #
         
     | 
| 
         @@ -66,11 +64,6 @@ module DICOM 
     | 
|
| 
       66 
64 
     | 
    
         
             
                  @max_package_size = options[:max_package_size] || 32768 # 16384
         
     | 
| 
       67 
65 
     | 
    
         
             
                  @timeout = options[:timeout] || 10 # seconds
         
     | 
| 
       68 
66 
     | 
    
         
             
                  @min_length = 12 # minimum number of bytes to expect in an incoming transmission
         
     | 
| 
       69 
     | 
    
         
            -
                  @verbose = options[:verbose]
         
     | 
| 
       70 
     | 
    
         
            -
                  @verbose = true if @verbose == nil # Default verbosity is 'on'.
         
     | 
| 
       71 
     | 
    
         
            -
                  # Other instance variables:
         
     | 
| 
       72 
     | 
    
         
            -
                  @errors = Array.new # errors and warnings are put in this array
         
     | 
| 
       73 
     | 
    
         
            -
                  @notices = Array.new # information on successful transmissions are put in this array
         
     | 
| 
       74 
67 
     | 
    
         
             
                  # Variables used for monitoring state of transmission:
         
     | 
| 
       75 
68 
     | 
    
         
             
                  @association = nil # DICOM Association status
         
     | 
| 
       76 
69 
     | 
    
         
             
                  @request_approved = nil # Status of our DICOM request
         
     | 
| 
         @@ -396,14 +389,14 @@ module DICOM 
     | 
|
| 
       396 
389 
     | 
    
         
             
                    establish_release
         
     | 
| 
       397 
390 
     | 
    
         
             
                  else
         
     | 
| 
       398 
391 
     | 
    
         
             
                    # Failed when loading the specified parameter as DICOM file(s). Will not transmit.
         
     | 
| 
       399 
     | 
    
         
            -
                     
     | 
| 
      
 392 
     | 
    
         
            +
                    logger.error(message)
         
     | 
| 
       400 
393 
     | 
    
         
             
                  end
         
     | 
| 
       401 
394 
     | 
    
         
             
                end
         
     | 
| 
       402 
395 
     | 
    
         | 
| 
       403 
396 
     | 
    
         
             
                # Tests the connection to the server in a very simple way  by negotiating an association and then releasing it.
         
     | 
| 
       404 
397 
     | 
    
         
             
                #
         
     | 
| 
       405 
398 
     | 
    
         
             
                def test
         
     | 
| 
       406 
     | 
    
         
            -
                   
     | 
| 
      
 399 
     | 
    
         
            +
                  logger.info("TESTING CONNECTION...")
         
     | 
| 
       407 
400 
     | 
    
         
             
                  success = false
         
     | 
| 
       408 
401 
     | 
    
         
             
                  # Verification SOP Class:
         
     | 
| 
       409 
402 
     | 
    
         
             
                  set_default_presentation_context(VERIFICATION_SOP)
         
     | 
| 
         @@ -417,9 +410,9 @@ module DICOM 
     | 
|
| 
       417 
410 
     | 
    
         
             
                    establish_release
         
     | 
| 
       418 
411 
     | 
    
         
             
                  end
         
     | 
| 
       419 
412 
     | 
    
         
             
                  if success
         
     | 
| 
       420 
     | 
    
         
            -
                     
     | 
| 
      
 413 
     | 
    
         
            +
                    logger.info("TEST SUCCSESFUL!")
         
     | 
| 
       421 
414 
     | 
    
         
             
                  else
         
     | 
| 
       422 
     | 
    
         
            -
                     
     | 
| 
      
 415 
     | 
    
         
            +
                    logger.warn("TEST FAILED!")
         
     | 
| 
       423 
416 
     | 
    
         
             
                  end
         
     | 
| 
       424 
417 
     | 
    
         
             
                  return success
         
     | 
| 
       425 
418 
     | 
    
         
             
                end
         
     | 
| 
         @@ -429,30 +422,6 @@ module DICOM 
     | 
|
| 
       429 
422 
     | 
    
         
             
                private
         
     | 
| 
       430 
423 
     | 
    
         | 
| 
       431 
424 
     | 
    
         | 
| 
       432 
     | 
    
         
            -
                # Adds a warning or error message to the instance array holding messages,
         
     | 
| 
       433 
     | 
    
         
            -
                # and prints the information to the screen if verbose is set.
         
     | 
| 
       434 
     | 
    
         
            -
                #
         
     | 
| 
       435 
     | 
    
         
            -
                # === Parameters
         
     | 
| 
       436 
     | 
    
         
            -
                #
         
     | 
| 
       437 
     | 
    
         
            -
                # * <tt>error</tt> -- A single error message or an array of error messages.
         
     | 
| 
       438 
     | 
    
         
            -
                #
         
     | 
| 
       439 
     | 
    
         
            -
                def add_error(error)
         
     | 
| 
       440 
     | 
    
         
            -
                  puts error if @verbose
         
     | 
| 
       441 
     | 
    
         
            -
                  @errors << error
         
     | 
| 
       442 
     | 
    
         
            -
                end
         
     | 
| 
       443 
     | 
    
         
            -
             
     | 
| 
       444 
     | 
    
         
            -
                # Adds a notice (information regarding progress or successful communications) to the instance array,
         
     | 
| 
       445 
     | 
    
         
            -
                # and prints the information to the screen if verbose is set.
         
     | 
| 
       446 
     | 
    
         
            -
                #
         
     | 
| 
       447 
     | 
    
         
            -
                # === Parameters
         
     | 
| 
       448 
     | 
    
         
            -
                #
         
     | 
| 
       449 
     | 
    
         
            -
                # * <tt>notice</tt> -- A single status message or an array of status messages.
         
     | 
| 
       450 
     | 
    
         
            -
                #
         
     | 
| 
       451 
     | 
    
         
            -
                def add_notice(notice)
         
     | 
| 
       452 
     | 
    
         
            -
                  puts notice if @verbose
         
     | 
| 
       453 
     | 
    
         
            -
                  @notices << notice
         
     | 
| 
       454 
     | 
    
         
            -
                end
         
     | 
| 
       455 
     | 
    
         
            -
             
     | 
| 
       456 
425 
     | 
    
         
             
                # Returns an array of supported transfer syntaxes for the specified transfer syntax.
         
     | 
| 
       457 
426 
     | 
    
         
             
                # For compressed transfer syntaxes, we currently do not support reencoding these to other syntaxes.
         
     | 
| 
       458 
427 
     | 
    
         
             
                #
         
     | 
| 
         @@ -486,11 +455,11 @@ module DICOM 
     | 
|
| 
       486 
455 
     | 
    
         
             
                      # Values of importance are extracted and put into instance variables:
         
     | 
| 
       487 
456 
     | 
    
         
             
                      @association = true
         
     | 
| 
       488 
457 
     | 
    
         
             
                      @max_pdu_length = info[:max_pdu_length]
         
     | 
| 
       489 
     | 
    
         
            -
                       
     | 
| 
      
 458 
     | 
    
         
            +
                      logger.info("Association successfully negotiated with host #{@host_ae} (#{@host_ip}).")
         
     | 
| 
       490 
459 
     | 
    
         
             
                      # Check if all our presentation contexts was accepted by the host:
         
     | 
| 
       491 
460 
     | 
    
         
             
                      process_presentation_context_response(info[:pc])
         
     | 
| 
       492 
461 
     | 
    
         
             
                    else
         
     | 
| 
       493 
     | 
    
         
            -
                       
     | 
| 
      
 462 
     | 
    
         
            +
                      logger.error("Association was denied from host #{@host_ae} (#{@host_ip})!")
         
     | 
| 
       494 
463 
     | 
    
         
             
                    end
         
     | 
| 
       495 
464 
     | 
    
         
             
                  end
         
     | 
| 
       496 
465 
     | 
    
         
             
                end
         
     | 
| 
         @@ -501,7 +470,7 @@ module DICOM 
     | 
|
| 
       501 
470 
     | 
    
         
             
                  @release = false
         
     | 
| 
       502 
471 
     | 
    
         
             
                  if @abort
         
     | 
| 
       503 
472 
     | 
    
         
             
                    @link.stop_session
         
     | 
| 
       504 
     | 
    
         
            -
                     
     | 
| 
      
 473 
     | 
    
         
            +
                    logger.info("Association has been closed. (#{@host_ae}, #{@host_ip})")
         
     | 
| 
       505 
474 
     | 
    
         
             
                  else
         
     | 
| 
       506 
475 
     | 
    
         
             
                    unless @link.session.closed?
         
     | 
| 
       507 
476 
     | 
    
         
             
                      @link.build_release_request
         
     | 
| 
         @@ -509,12 +478,12 @@ module DICOM 
     | 
|
| 
       509 
478 
     | 
    
         
             
                      info = @link.receive_single_transmission.first
         
     | 
| 
       510 
479 
     | 
    
         
             
                      @link.stop_session
         
     | 
| 
       511 
480 
     | 
    
         
             
                      if info[:pdu] == PDU_RELEASE_RESPONSE
         
     | 
| 
       512 
     | 
    
         
            -
                         
     | 
| 
      
 481 
     | 
    
         
            +
                        logger.info("Association released properly from host #{@host_ae}.")
         
     | 
| 
       513 
482 
     | 
    
         
             
                      else
         
     | 
| 
       514 
     | 
    
         
            -
                         
     | 
| 
      
 483 
     | 
    
         
            +
                        logger.error("Association released from host #{@host_ae}, but a release response was not registered.")
         
     | 
| 
       515 
484 
     | 
    
         
             
                      end
         
     | 
| 
       516 
485 
     | 
    
         
             
                    else
         
     | 
| 
       517 
     | 
    
         
            -
                       
     | 
| 
      
 486 
     | 
    
         
            +
                      logger.error("Connection was closed by the host (for some unknown reason) before the association could be released properly.")
         
     | 
| 
       518 
487 
     | 
    
         
             
                    end
         
     | 
| 
       519 
488 
     | 
    
         
             
                  end
         
     | 
| 
       520 
489 
     | 
    
         
             
                  @abort = false
         
     | 
| 
         @@ -533,33 +502,38 @@ module DICOM 
     | 
|
| 
       533 
502 
     | 
    
         
             
                #
         
     | 
| 
       534 
503 
     | 
    
         
             
                # === Parameters
         
     | 
| 
       535 
504 
     | 
    
         
             
                #
         
     | 
| 
       536 
     | 
    
         
            -
                # * <tt> 
     | 
| 
      
 505 
     | 
    
         
            +
                # * <tt>files_or_objects</tt> -- A single file path or an array of paths, or a DObject or an array of DObject instances.
         
     | 
| 
       537 
506 
     | 
    
         
             
                #
         
     | 
| 
       538 
     | 
    
         
            -
                def load_files( 
     | 
| 
      
 507 
     | 
    
         
            +
                def load_files(files_or_objects)
         
     | 
| 
      
 508 
     | 
    
         
            +
                  files_or_objects = [files_or_objects] unless files_or_objects.is_a?(Array)
         
     | 
| 
       539 
509 
     | 
    
         
             
                  status = true
         
     | 
| 
       540 
510 
     | 
    
         
             
                  message = ""
         
     | 
| 
       541 
511 
     | 
    
         
             
                  objects = Array.new
         
     | 
| 
       542 
512 
     | 
    
         
             
                  abstracts = Array.new
         
     | 
| 
       543 
513 
     | 
    
         
             
                  id = 1
         
     | 
| 
       544 
514 
     | 
    
         
             
                  @presentation_contexts = Hash.new
         
     | 
| 
       545 
     | 
    
         
            -
                   
     | 
| 
       546 
     | 
    
         
            -
             
     | 
| 
       547 
     | 
    
         
            -
             
     | 
| 
       548 
     | 
    
         
            -
                       
     | 
| 
      
 515 
     | 
    
         
            +
                  files_or_objects.each do |file_or_object|
         
     | 
| 
      
 516 
     | 
    
         
            +
                    if file_or_object.is_a?(String)
         
     | 
| 
      
 517 
     | 
    
         
            +
                      # Temporarily increase the log threshold to suppress messages from the DObject class:
         
     | 
| 
      
 518 
     | 
    
         
            +
                      client_level = logger.level
         
     | 
| 
      
 519 
     | 
    
         
            +
                      logger.level = Logger::FATAL
         
     | 
| 
      
 520 
     | 
    
         
            +
                      obj = DObject.read(file_or_object)
         
     | 
| 
      
 521 
     | 
    
         
            +
                      # Reset the logg threshold:
         
     | 
| 
      
 522 
     | 
    
         
            +
                      logger.level = client_level
         
     | 
| 
       549 
523 
     | 
    
         
             
                      if obj.read_success
         
     | 
| 
       550 
524 
     | 
    
         
             
                        # Load the DICOM object:
         
     | 
| 
       551 
525 
     | 
    
         
             
                        objects << obj
         
     | 
| 
       552 
526 
     | 
    
         
             
                      else
         
     | 
| 
       553 
527 
     | 
    
         
             
                        status = false
         
     | 
| 
       554 
     | 
    
         
            -
                        message = "Failed to  
     | 
| 
      
 528 
     | 
    
         
            +
                        message = "Failed to read a DObject from this file: #{file_or_object}"
         
     | 
| 
       555 
529 
     | 
    
         
             
                      end
         
     | 
| 
       556 
     | 
    
         
            -
                    elsif  
     | 
| 
      
 530 
     | 
    
         
            +
                    elsif file_or_object.is_a?(DObject)
         
     | 
| 
       557 
531 
     | 
    
         
             
                      # Load the DICOM object and its abstract syntax:
         
     | 
| 
       558 
     | 
    
         
            -
                      abstracts <<  
     | 
| 
       559 
     | 
    
         
            -
                      objects <<  
     | 
| 
      
 532 
     | 
    
         
            +
                      abstracts << file_or_object.value("0008,0016")
         
     | 
| 
      
 533 
     | 
    
         
            +
                      objects << file_or_object
         
     | 
| 
       560 
534 
     | 
    
         
             
                    else
         
     | 
| 
       561 
535 
     | 
    
         
             
                      status = false
         
     | 
| 
       562 
     | 
    
         
            -
                      message = "Array contains invalid object #{ 
     | 
| 
      
 536 
     | 
    
         
            +
                      message = "Array contains invalid object: #{file_or_object.class}."
         
     | 
| 
       563 
537 
     | 
    
         
             
                    end
         
     | 
| 
       564 
538 
     | 
    
         
             
                  end
         
     | 
| 
       565 
539 
     | 
    
         
             
                  # Extract available transfer syntaxes for the various sop classes found amongst these objects
         
     | 
| 
         @@ -732,7 +706,7 @@ module DICOM 
     | 
|
| 
       732 
706 
     | 
    
         
             
                        process_returned_data(segments)
         
     | 
| 
       733 
707 
     | 
    
         
             
                      end
         
     | 
| 
       734 
708 
     | 
    
         
             
                    else
         
     | 
| 
       735 
     | 
    
         
            -
                       
     | 
| 
      
 709 
     | 
    
         
            +
                      logger.error("Unable to extract SOP Class UID and/or SOP Instance UID for this DICOM object. File will not be sent to its destination.")
         
     | 
| 
       736 
710 
     | 
    
         
             
                    end
         
     | 
| 
       737 
711 
     | 
    
         
             
                  end
         
     | 
| 
       738 
712 
     | 
    
         
             
                end
         
     | 
| 
         @@ -767,16 +741,27 @@ module DICOM 
     | 
|
| 
       767 
741 
     | 
    
         
             
                  if rejected.length == 0
         
     | 
| 
       768 
742 
     | 
    
         
             
                    @request_approved = true
         
     | 
| 
       769 
743 
     | 
    
         
             
                    if @approved_syntaxes.length == 1 and presentation_contexts.length == 1
         
     | 
| 
       770 
     | 
    
         
            -
                       
     | 
| 
      
 744 
     | 
    
         
            +
                      logger.info("The presentation context was accepted by host #{@host_ae}.")
         
     | 
| 
       771 
745 
     | 
    
         
             
                    else
         
     | 
| 
       772 
     | 
    
         
            -
                       
     | 
| 
      
 746 
     | 
    
         
            +
                      logger.info("All #{presentation_contexts.length} presentation contexts were accepted by host #{@host_ae} (#{@host_ip}).")
         
     | 
| 
       773 
747 
     | 
    
         
             
                    end
         
     | 
| 
       774 
748 
     | 
    
         
             
                  else
         
     | 
| 
       775 
749 
     | 
    
         
             
                    # We still consider the request 'approved' if at least one context were accepted:
         
     | 
| 
       776 
750 
     | 
    
         
             
                    @request_approved = true if @approved_syntaxes.length > 0
         
     | 
| 
       777 
     | 
    
         
            -
             
     | 
| 
       778 
     | 
    
         
            -
                     
     | 
| 
       779 
     | 
    
         
            -
             
     | 
| 
      
 751 
     | 
    
         
            +
             
     | 
| 
      
 752 
     | 
    
         
            +
                    logger.error("One or more of your presentation contexts were denied by host #{@host_ae}!")
         
     | 
| 
      
 753 
     | 
    
         
            +
             
     | 
| 
      
 754 
     | 
    
         
            +
                    @approved_syntaxes.each_pair do |key, value|
         
     | 
| 
      
 755 
     | 
    
         
            +
                      sntx_k = LIBRARY.get_syntax_description(key)
         
     | 
| 
      
 756 
     | 
    
         
            +
                      sntx_v = LIBRARY.get_syntax_description(value[1])
         
     | 
| 
      
 757 
     | 
    
         
            +
                      logger.info("APPROVED: #{sntx_k} (#{sntx_v})")
         
     | 
| 
      
 758 
     | 
    
         
            +
                    end
         
     | 
| 
      
 759 
     | 
    
         
            +
             
     | 
| 
      
 760 
     | 
    
         
            +
                    rejected.each_pair do |key, value|
         
     | 
| 
      
 761 
     | 
    
         
            +
                      sntx_k = LIBRARY.get_syntax_description(key)
         
     | 
| 
      
 762 
     | 
    
         
            +
                      sntx_v = LIBRARY.get_syntax_description(value[1])
         
     | 
| 
      
 763 
     | 
    
         
            +
                      logger.error("REJECTED: #{sntx_k} (#{sntx_v})")
         
     | 
| 
      
 764 
     | 
    
         
            +
                    end
         
     | 
| 
       780 
765 
     | 
    
         
             
                  end
         
     | 
| 
       781 
766 
     | 
    
         
             
                end
         
     | 
| 
       782 
767 
     | 
    
         | 
| 
         @@ -958,4 +943,4 @@ module DICOM 
     | 
|
| 
       958 
943 
     | 
    
         
             
                end
         
     | 
| 
       959 
944 
     | 
    
         | 
| 
       960 
945 
     | 
    
         
             
              end
         
     | 
| 
       961 
     | 
    
         
            -
            end
         
     | 
| 
      
 946 
     | 
    
         
            +
            end
         
     |