Depth of focus

This is an implementation of depth of focus.  The Sobel gradient magnitude of the focus stack is used as a sharpness measure.  An image with extended depth of field is created (deep view).  Furthermore a height field is generated.  With POVRay the height field and the deep view can be used to generate a 3D rendering.

Note that the ‘trollop’ Ruby-extension is required for parsing the command line.

See also

#!/usr/bin/env ruby
require 'hornetseye'
require 'trollop'
include Hornetseye
opts = Trollop::options do
  banner <<EOS
Generate height field and deep view from focus stack.

Usage:
       ./depthoffocus.rb [options] <file names>+

where [options] are:
EOS
  opt :sigma, 'Sigma for Gaussian blur (1/pixelsize)', :default => 2.5
  opt :field, 'Output PGM file name for height field',
      :type => String
  opt :view, 'Output PPM file name for deep view', :type => String
  opt :alternative, 'Threat later half of filenames as alternative focus ' +
      'stack to generate deep view'
end
sigma = opts[ :sigma ]
Trollop::die :sigma, 'must be greater than zero' unless sigma > 0
field_file = opts[ :field ]
Trollop::die :field, 'is required' unless field_file
view_file = opts[ :view ]
Trollop::die :view, 'is required' unless view_file
alternative = opts[ :alternative ]
if alternative
  if ARGV.size % 2 != 0
    Trollop::die 'Even number of file names required when using alternative ' +
      'focus stack'
  end
  n = ARGV.size / 2
  stack_file = ARGV.slice! 0, n
  alternative_file = ARGV.slice! 0, n
else
  stack_file = ARGV
  alternative_file = nil
end
Trollop::die 'Cannot handle more than 255 files' if stack_file.size > 255
display = X11Display.new
field_output = XImageOutput.new
view_output = XImageOutput.new
field_window = X11Window.new display, field_output, 320, 240
view_window = X11Window.new display, view_output, 320, 240
field, view, max_sharpness = nil, nil, nil
stack_file.each_with_index do |f_name,i|
  img = MultiArray.load_ubytergb f_name
  if field
    if img.shape != field.shape
      raise "Image '#{f_name}' must be of size #{field.shape[0]}x" +
        "#{field.shape[1]} (but was #{img.shape[0]}x#{img.shape[1]})"
    end
  else
    field = MultiArray.ubyte( *img.shape ).fill!
    view = MultiArray.ubytergb( *img.shape ).fill!
    max_sharpness = MultiArray.dfloat( *img.shape ).fill!
    field_window.resize *img.shape
    view_window.resize *img.shape
    field_window.show
    view_window.show
  end
  sharpness = ( img.sobel( 0 ) ** 2 +
                img.sobel( 1 ) ** 2 ).to_dfloat.gauss_blur sigma
  mask = sharpness > max_sharpness
  field = mask.conditional i, field
  if alternative
    alternative_img = MultiArray.load_ubytergb alternative_file[ i ]
    view = mask.conditional alternative_img, view
  else
    view = mask.conditional img, view
  end
  max_sharpness = mask.conditional sharpness, max_sharpness
  progress = "#{ "%3d" % i }/#{ "%3d" % stack_file.size }"
  field_window.title = "Height field (#{progress})"
  view_window.title = "Deep view (#{progress})"
  field_output.write field * ( 255.0 / stack_file.size )
  view_output.write view
  display.processEvents
end
field.save_ubyte field_file
view.save_ubytergb view_file
field_window.title = 'Height field'
view_window.title = 'Deep view'
display.eventLoop
Close