# encoding: utf-8 require 'tmpdir' begin %W{dicom }.each do |lib| #####RMagick require lib unless defined?(lib) end rescue LoadError => e raise LoadError, "Could not load #{e}. Thumbnailing will use slicer instead of ruby-dicom." end # This class is a ruby object encapsulating a .png 2D Thumbnail of a Dataset # Initialize it with an #RawImageDataset class RawImageDatasetThumbnail VALID_PROCESSORS = [:rubydicom, :slicer] # The parent #RawImageDataset attr_reader :dataset # The path to the thumbnail image if it's already been created attr_reader :path # The processor for creating the thumbnail (:rubydicom or :slicer) attr_reader :processor # An array of errors encountered during the reading process. attr_reader :errors # Creates a RawImageDatasetThumbnail instance by passing in a parent dataset to thumbnail. def initialize(dataset) if dataset.class == RawImageDataset @dataset = dataset else raise StandardError, "Dataset #{dataset} class must be RawImageDataset." end end def thumbnail @path ||= create_thumbnail end # Creates a thumbnail image (.png or .jpg) and returns the full file path of the thumbnail. # Raises a ScriptError if the thumbnail could not be created. # Raises a StandardError if the format is incorrect (i.e. P-file instead of DICOM) # # Be sure your filename is a valid unix filename - no spaces. # # Returns the full absolute filename to the new thumbnail image and sets it to @path instance variable. # # === Parameters # # * output: An optional string which specifies a directory or filename for the thumbnail image. # * options: A hash of additional options. # # === Options # # * :processor -- Symbol. Specifies which thumbnail processor to use. Defaults to :rubydicom, alternatively it could be :slicer # # === Examples # # # Load a RawImageDataset # ds = RawImageDataset('s01_assetcal', RawImageFile.new('./s01_assetcal/I0001.dcm')) # # Create a RawImageDatasetThumbnail instance # thumb = RawImageDatasetThumbnail.new(ds) # # Create a thumbnail in a temp directory without options, save it to a destination image, or force it to use FSL Slicer. # thumb.create_thumbnail # thumb.create_thumbnail('/tmp/asset_cal.png') # thumb.create_thumbnail('/tmp/asset_cal.png', :processor => :slicer) # def create_thumbnail(output = nil, options = {:processor => :rubydicom}) raise StandardError, "Thumbnail available only for DICOM format." unless dataset.raw_image_files.first.dicom? raise ArgumentError, "Invalid :processor option #{options[:processor]}" unless VALID_PROCESSORS.include?(options[:processor]) if output if File.directory?(output) # output is a directory. Set the output directory but leave filepath nil. output_directory = output.escape_dirname else # output is a path. Set the output_directory and specify that the full filepath is already complete. output_directory = File.dirname(output).escape_dirname filepath = output end else # If no output was given, default to a new temp directory. output_directory = Dir.mktmpdir end @processor = options[:processor] # Set a default filepath unless one was explicitly passed in. default_name = @dataset.series_description.escape_filename filepath ||= File.join(output_directory, default_name + '.png') begin case @processor when :rubydicom @path = create_thumbnail_with_rubydicom_dcmtk(filepath) #@path = create_thumbnail_with_rubydicom(filepath) when :slicer @path = create_thumbnail_with_fsl_slicer(filepath) end rescue RangeError, ScriptError => e unless @processor == :slicer puts "Could not create thumbnail with rubydicom. Trying FSL slicer." @processor = :slicer retry else raise e end end raise ScriptError, "Could not create thumbnail from #{@dataset.series_description} - #{File.join(@dataset.directory, @dataset.scanned_file)}" unless @path && File.readable?(@path) return @path end private # Creates a thumbnail using RubyDicom and dcmtk # Pass in an absolute or relative filepath, including filename and extension. # Returns an absolute path to the created thumbnail image. def create_thumbnail_with_rubydicom_dcmtk(output_file) output_file = File.expand_path(output_file) dicom_files = Dir.glob(File.join(dataset.directory, dataset.glob)) if dicom_files.empty? # Try the glob again with a zipped extension. dicom_files = Dir.glob(File.join(dataset.directory, dataset.glob) + '*.bz2') end if dicom_files.empty? # If still empty... raise StandardError, "Could not find dicom files using #{dataset.glob} in #{dataset.directory}" end dicom_file = Pathname(dicom_files[(dicom_files.size/3)+1]) # 2 => 3 to keep asl in first half of set --> delay time dicom_file.local_copy do |lc| #dcm = DICOM::DObject.new(lc.to_s) # changing from dicom 0.8.0 to 0.9.5 dcm = DICOM::DObject.read(lc.to_s) raise ScriptError, "Could not read dicom #{dicom_file.to_s}" unless dcm.read_success v_call = "dcmj2pnm -v +Wi 1 --write-png "+lc.to_s+" "+output_file v_results = %x[#{v_call}] puts "results= "+v_results puts "dicom_file= "+dicom_file.to_s puts "output_file= "+output_file #### image = dcm.get_image_magick(:rescale => true) #### raise ScriptError, "RubyDicom did not return an image array (this is probably a color image)." unless image.kind_of? Magick::Image #### image.write(output_file) end raise(ScriptError, "Error creating thumbnail #{output_file}") unless File.exist?(output_file) return output_file end # Creates a thumbnail using RubyDicom # Pass in an absolute or relative filepath, including filename and extension. # Returns an absolute path to the created thumbnail image. def create_thumbnail_with_rubydicom(output_file) output_file = File.expand_path(output_file) dicom_files = Dir.glob(File.join(dataset.directory, dataset.glob)) if dicom_files.empty? # Try the glob again with a zipped extension. dicom_files = Dir.glob(File.join(dataset.directory, dataset.glob) + '*.bz2') end if dicom_files.empty? # If still empty... raise StandardError, "Could not find dicom files using #{dataset.glob} in #{dataset.directory}" end dicom_file = Pathname(dicom_files[dicom_files.size/2]) dicom_file.local_copy do |lc| #dcm = DICOM::DObject.new(lc.to_s) # changing from dicom 0.8.0 to 0.9.5 dcm = DICOM::DObject.read(lc.to_s) raise ScriptError, "Could not read dicom #{dicom_file.to_s}" unless dcm.read_success image = dcm.get_image_magick(:rescale => true) raise ScriptError, "RubyDicom did not return an image array (this is probably a color image)." unless image.kind_of? Magick::Image image.write(output_file) end raise(ScriptError, "Error creating thumbnail #{output_file}") unless File.exist?(output_file) return output_file end # Creates a thumbnail using FSL's Slicer bash utility. # Pass in an output filepath. def create_thumbnail_with_fsl_slicer(output_file) nii_tmpdir = Dir.mktmpdir nifti_output_file = File.basename(output_file, File.extname(output_file)) + '.nii' Pathname.new(dataset.directory).all_dicoms do |dicom_files| # First Create a Nifti File to read @dataset.to_nifti!(nii_tmpdir, nifti_output_file, {:input_directory => File.dirname(dicom_files.first)} ) end # Then create the .png `slicer #{File.join(nii_tmpdir, nifti_output_file)} -a #{output_file}` raise(ScriptError, "Error creating thumbnail #{output_file}") unless File.exist?(output_file) return output_file end end