Module: DicomS::Support
Instance Method Summary collapse
- #assign_dicom_pixels(dicom, pixels) ⇒ Object
- #cast_metadata(metadata) ⇒ Object
- #decode_vector(v) ⇒ Object
- #define_transfer(options, *defaults) ⇒ Object
- #dicom?(file) ⇒ Boolean
- #dicom_bit_depth(dicom) ⇒ Object
- #dicom_compression(dicom) ⇒ Object
- #dicom_element_value(dicom, tag, options = {}) ⇒ Object
- #dicom_name_pattern(name, output_dir) ⇒ Object
- #dicom_narray(dicom, options = {}) ⇒ Object
- #dicom_rescale_intercept(dicom) ⇒ Object
- #dicom_rescale_slope(dicom) ⇒ Object
- #dicom_signed?(dicom) ⇒ Boolean
- #dicom_stored_bits(dicom) ⇒ Object
-
#dicom_window_center(dicom) ⇒ Object
WL (window level).
-
#dicom_window_width(dicom) ⇒ Object
WW (window width).
- #encode_vector(v) ⇒ Object
-
#find_dicom_files(dicom_directory) ⇒ Object
Find DICOM files in a directory; Return the file names in an array.
-
#keeping_path ⇒ Object
Code that use images should be wrapped with this.
-
#normalized_path(path) ⇒ Object
Replace ALT_SEPARATOR in pathname (Windows).
- #output_file_name(dir, prefix, name, ext = '.jpg') ⇒ Object
- #pixel_value_range(num_bits, signed) ⇒ Object
- #single_dicom_metadata(dicom) ⇒ Object
Instance Method Details
#assign_dicom_pixels(dicom, pixels) ⇒ Object
337 338 339 340 341 342 |
# File 'lib/dicoms/support.rb', line 337 def assign_dicom_pixels(dicom, pixels) if dicom.compression? dicom.delete DICOM::PIXEL_TAG end dicom.pixels = pixels end |
#cast_metadata(metadata) ⇒ Object
32 33 34 35 36 37 38 39 40 |
# File 'lib/dicoms/support.rb', line 32 def () = Hash[.to_h.to_a.map { |key, value| key = key.to_s.downcase.to_sym trans = METADATA_TYPES[key] value = value.send(trans) if trans [key, value] }] Settings[] end |
#decode_vector(v) ⇒ Object
159 160 161 |
# File 'lib/dicoms/support.rb', line 159 def decode_vector(v) Vector[*v.split(',').map(&:to_f)] end |
#define_transfer(options, *defaults) ⇒ Object
185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/dicoms/support.rb', line 185 def define_transfer(, *defaults) strategy, params = Array([:transfer]) unless defaults.first.is_a?(Hash) default_strategy = defaults.shift.to_sym end defautl_strategy ||= :sample default_params = defaults.shift || {} raise "Invalid number of parametrs" unless defaults.empty? Transfer.strategy strategy || default_strategy, default_params.merge((params || {}).to_h) end |
#dicom?(file) ⇒ Boolean
70 71 72 73 74 75 76 77 78 79 |
# File 'lib/dicoms/support.rb', line 70 def dicom?(file) ok = false if File.file?(file) File.open(file, 'rb') do |data| data.seek 128, IO::SEEK_SET # skip preamble ok = (data.read(4) == 'DICM') end end ok end |
#dicom_bit_depth(dicom) ⇒ Object
243 244 245 246 |
# File 'lib/dicoms/support.rb', line 243 def dicom_bit_depth(dicom) # dicom.send(:bit_depth) dicom_element_value dicom, '0028,0100', convert: :to_i end |
#dicom_compression(dicom) ⇒ Object
344 345 346 347 |
# File 'lib/dicoms/support.rb', line 344 def dicom_compression(dicom) ts = DICOM::LIBRARY.uid(dicom.transfer_syntax) ts.name if ts.compressed_pixels? end |
#dicom_element_value(dicom, tag, options = {}) ⇒ Object
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/dicoms/support.rb', line 206 def dicom_element_value(dicom, tag, = {}) if dicom.exists?(tag) value = dicom[tag].value if [:first] if value.is_a?(String) value = value.split('\\').first elsif value.is_a?(Array) value = value.first end end value = value.send([:convert]) if [:convert] value else [:default] end end |
#dicom_name_pattern(name, output_dir) ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/dicoms/support.rb', line 167 def dicom_name_pattern(name, output_dir) dir = File.dirname(name) file = File.basename(name) number_pattern = /\d+/ match = number_pattern.match(file) raise "Invalid DICOM file name" unless match number = match[0] file = file.sub(number_pattern, "%d") if match.begin(0) == 0 # ffmpeg has troubles with filename patterns starting with digits, so we'll add a prefix prefix = "d-" else prefix = nil end pattern = output_file_name(output_dir, prefix, file) [prefix, pattern, number] end |
#dicom_narray(dicom, options = {}) ⇒ Object
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/dicoms/support.rb', line 263 def dicom_narray(dicom, = {}) if dicom.compression? img = dicom.image pixels = dicom.export_pixels(img, dicom.send(:photometry)) na = NArray.to_na(pixels).reshape!(dicom.num_cols, dicom.num_rows) bits = dicom_bit_depth(dicom) signed = dicom_signed?(dicom) stored_bits = dicom_stored_bits(dicom) if stored_bits != Magick::MAGICKCORE_QUANTUM_DEPTH use_float = stored_bits < Magick::MAGICKCORE_QUANTUM_DEPTH if use_float na = na.to_type(NArray::SFLOAT) na.mul! 2.0**(stored_bits - Magick::MAGICKCORE_QUANTUM_DEPTH) na = na.to_type(NArray::INT) else na.mul! (1 << (stored_bits - Magick::MAGICKCORE_QUANTUM_DEPTH)) end end if remap = [:remap] || level = [:level] intercept = dicom_rescale_intercept(dicom) slope = dicom_rescale_slope(dicom) if intercept != 0 || slope != 1 na.mul! slope na.add! intercept end if level if level.is_a?(Array) center, width = level else center = dicom_window_center(dicom) width = dicom_window_width(dicom) end if center && width low = center - width/2 high = center + width/2 na[na < low] = low na[na > high] = high end end # Now we limit the output values range. # Note that we don't use: # min, max = pixel_value_range(bits, signed) # because thats the limits for the stored values, but not for # the representation values we're computing here (which are # typically signed even if the storage is unsigned) # We coud use this, but that would have to be # min, max = pixel_value_range(stored_bits, false) # min = -max # but that requires some reviewing. # Maybe this shold be parameterized. min, max = -65535, 65535 min_pixel_value = na.min if min if min_pixel_value < min offset = min_pixel_value.abs na.add! offset end end max_pixel_value = na.max if max if max_pixel_value > max factor = (max_pixel_value.to_f/max.to_f).ceil na.div! factor end end end na else dicom.narray end end |
#dicom_rescale_intercept(dicom) ⇒ Object
235 236 237 |
# File 'lib/dicoms/support.rb', line 235 def dicom_rescale_intercept(dicom) dicom_element_value(dicom, '0028,1052', convert: :to_f, default: 0) end |
#dicom_rescale_slope(dicom) ⇒ Object
239 240 241 |
# File 'lib/dicoms/support.rb', line 239 def dicom_rescale_slope(dicom) dicom_element_value(dicom, '0028,1053', convert: :to_f, default: 1) end |
#dicom_signed?(dicom) ⇒ Boolean
248 249 250 251 252 253 254 255 256 |
# File 'lib/dicoms/support.rb', line 248 def dicom_signed?(dicom) # dicom.send(:signed_pixels?) case dicom_element_value(dicom, '0028,0103', convert: :to_i) when 1 true when 0 false end end |
#dicom_stored_bits(dicom) ⇒ Object
258 259 260 261 |
# File 'lib/dicoms/support.rb', line 258 def dicom_stored_bits(dicom) # dicom.bits_stored.value.to_i dicom_element_value dicom, '0028,0101', convert: :to_i end |
#dicom_window_center(dicom) ⇒ Object
WL (window level)
224 225 226 227 |
# File 'lib/dicoms/support.rb', line 224 def dicom_window_center(dicom) # dicom.window_center.value.to_i dicom_element_value(dicom, '0028,1050', convert: :to_f, first: true) end |
#dicom_window_width(dicom) ⇒ Object
WW (window width)
230 231 232 233 |
# File 'lib/dicoms/support.rb', line 230 def dicom_window_width(dicom) # dicom.window_center.value.to_i dicom_element_value(dicom, '0028,1051', convert: :to_f, first: true) end |
#encode_vector(v) ⇒ Object
155 156 157 |
# File 'lib/dicoms/support.rb', line 155 def encode_vector(v) v.to_a*',' end |
#find_dicom_files(dicom_directory) ⇒ Object
Find DICOM files in a directory; Return the file names in an array. DICOM files with a numeric part in the name are returned first, ordered by the numeric value. DICOM files with non-numeric names are returned last ordered by name.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/dicoms/support.rb', line 86 def find_dicom_files(dicom_directory) # TODO: look recursively inside nested directories if File.directory?(dicom_directory) dicom_directory = normalized_path(dicom_directory) files = Dir.glob(File.join(dicom_directory, '*')).select{|f| dicom?(f)} elsif File.file?(dicom_directory) && dicom?(dicom_directory) files = [dicom_directory] else files = [] end non_numeric = [] numeric_files = [] files.each do |name| base = File.basename(name) match = /\d+/.match(base) if match number = match[0] if base =~ /\AI\d\d\d\d\d\d\d\Z/ # funny scheme found in some DICOMS: # the I is followed by the instance number (unpadded), then right # padded with zeros, then increased (which affects the last digit) # while it coincides with some prior value. match = /I(\d\d\d\d)/.match(base) number = match[1] number = number[0...-1] while number.size > 1 && number[-1] == '0' number_zeros = name[-1].to_i number << '0'*number_zeros end numeric_files << [number, name] else non_numeric << name end end numeric_files.sort_by{ |text, name| text.to_i }.map(&:last) + non_numeric.sort end |
#keeping_path ⇒ Object
Code that use images should be wrapped with this.
Reason: if RMagick is used by DICOM to handle images, then the first time it is needed, ‘rmagick’ will be required. This has the effect of placing the path of ImageMagick in front of the PATH. On Windows, ImageMagick includes FFMPeg in its path and we may require a later version than the bundled with IM, so we keep the original path rbefore RMagick alters it. We may be less dependant on the FFMpeg version is we avoid using the start_number option by renumbering the extracted images…
54 55 56 57 58 59 |
# File 'lib/dicoms/support.rb', line 54 def keeping_path path = ENV['PATH'] yield ensure ENV['PATH'] = path end |
#normalized_path(path) ⇒ Object
Replace ALT_SEPARATOR in pathname (Windows)
62 63 64 65 66 67 68 |
# File 'lib/dicoms/support.rb', line 62 def normalized_path(path) if File::ALT_SEPARATOR path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) else path end end |
#output_file_name(dir, prefix, name, ext = '.jpg') ⇒ Object
163 164 165 |
# File 'lib/dicoms/support.rb', line 163 def output_file_name(dir, prefix, name, ext = '.jpg') File.join dir, "#{prefix}#{File.basename(name,'.dcm')}#{ext}" end |
#pixel_value_range(num_bits, signed) ⇒ Object
197 198 199 200 201 202 203 204 |
# File 'lib/dicoms/support.rb', line 197 def pixel_value_range(num_bits, signed) num_values = (1 << num_bits) # 2**num_bits if signed [-num_values/2, num_values/2-1] else [0, num_values-1] end end |
#single_dicom_metadata(dicom) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/dicoms/support.rb', line 122 def (dicom) # 0028,0030 Pixel Spacing: dx, dy = dicom.pixel_spacing.value.split('\\').map(&:to_f) # 0020,0032 Image Position (Patient): x, y, z = dicom.image_position_patient.value.split('\\').map(&:to_f) # 0020,0037 Image Orientation (Patient): xx, xy, xz, yx, yy, yz = dicom.image_orientation_patient.value.split('\\').map(&:to_f) if USE_SLICE_Z # according to http://www.vtk.org/Wiki/VTK/FAQ#The_spacing_in_my_DICOM_files_are_wrong # this is not reliable # 0020,1041 Slice Location: slice_z = dicom.slice_location.value.to_f else slice_z = z end # 0028,0011 Columns : nx = dicom.num_cols # dicom.columns.value.to_i # 0028,0010 Rows: ny = dicom.num_rows # dicom.rows.value.to_i unless dicom.samples_per_pixel.value.to_i == 1 raise "Invalid DICOM format" end Settings[ dx: dx, dy: dy, x: x, y: y, z: z, slice_z: slice_z, nx: nx, ny: ny, xaxis: encode_vector([xx,xy,xz]), yaxis: encode_vector([yx,yy,yz]) # TODO: + min, max (original values corresponding to 0, 255) ] end |