# 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/2)+1])
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