FANDOM


Landscape Focus Bracketing ScriptEdit

Written for/on: G7X
Also works on: Should work on any camera that runs CHDK
Required CHDK build: Anything recent as of 2018

Latest version of script may be downloaded from: https://gist.github.com/pigeonhill/10a43f5ba543bc758f1ce21d28981a89

Notes & Usage Unlike macro focus bracketing, where the focus step is the same each time and very small; in landscape focus bracketing we need to calculate the focus for each step. To get the full benefits from this script, it is useful to remember that, ignoring diffraction, at the near and far depth of fields the defocus blur is, by definition, the so-called Circle of Confusion. This blur is usually accepted as being 30 microns for a 35mm full frame camera, and scaled by the crop factor for other formats, eg 11 microns on the G7X.

Having said this, 30 microns, is not the 'best focus' quality but you will not see the differences on your Facebook images, only when you create high (focus) quality prints that will be scrutinised close up, eg in a competition. As you need a line pair (black and white pixels, to see a line, it is not sensible to consider CoCs less than, say, 2 sensor pixels.

Defocus blur is, of course, zero at the point of focus. Also, at the Hyperfocal distance (H), the blur at infinity will be the CoC. It is useful to note that any blur at infinity can be 'dialed in', by simply focusing at a distance either side of H. Thus, on my G7X, with a CoC of 11 microns, if I focus at twice H, then the infinity blur will be CoC/2. Likewise, at H/4, the infinity blur will be 4*CoC.

The script scales everything relative to H and the following chart illustrates how defocus blur varies with respect to H. Note the chart shows an estimate of the near and far DoFs that can be used in your head: the script uses a more ‘accurate’ estimate.

H

When focus bracketing we must at least ensure the previous image's far DoF is the same as the next image's near DoF: assuming we are focusing near to far as we are in this script. However, many wish to introduce some 'insurance' and have an overlap between the focus brackets. The script provides four options for this overlap: none, 2*CoC/3, CoC/2 and diffraction aware.

With diffraction aware overlap, the script calculates the infinity defocus blur to use, that ensures that the defocus blur and diffraction blur, taken together in quadrature, are equal to the CHDK CoC. This overlap blur criterion will never be less than the CHDK set CoC/3.

The following illustrates the overlap between image n and n+1. Note that using the overlap feature comes at the cost of needing to take more brackets

Pict 2

Having achieved the perfect focus brackets, the script will then create an additional bracket beyond H, ie to cover the infinity focus quality. The options being 2H, 3H or 4H, giving infinity blurs of CoC/2, CoC/3 or CoC/4. If diffraction aware is being used for the overlap, the last image will be around CoC/3.

By default the script creates a dark image at the beginning and end of the focus bracket set, to help differentiate the sequence in post. You can turn this off via the menu.

At each focus point you can also ask the script to create two additional exposure brackets at either -xEv & +xEv, -xEv & --xEv or at +xEv and ++xEv. X can be 1 or 2Ev from the base exposure. These options cover all the usual exposure bracketing options, eg if in Av mode it would be usual to choose the -/+ logic. If using M mode and following an ETTR approach, then a +/++ logic would be the best choice.

In addition to the 'normal' bracketing option, the script offers two additional schemes. First, an ISO-less bracketing option that assumes (sic) the camera is ISO-less above ISO 800, thus there is no point in taking brackets in the camera above this, ie just push in post. The ISO option is good for hand-holding situations and the script assumes your base ISO is 100.

The second additional scheme makes use of the 'Zero-Noise' approach, as suggested by Guillermo Lujik. In this mode a second time bracket is taken at +4Ev from the first one. To use the ZN option, simply ETTR to capture the highlights and the script will create a +4Ev additional image for the shadow areas.

Because focus bracketing can create large sequences, the script will warn you if the requested bracket sequence is more than your maximum number, which is set to 10 focus steps, but this can be changed as you wish in the script. Note that 10 focus steps will result in 30 images if you have exposure bracketing switched on.

In order to cover various needs, the user can select three focusing scenarios:

  • From the current focus to the blur-defined Infinity
  • From the minimum focus to the current focus
  • From the minimum focus to the blur-defined Infinity

Finally, the script will create a log file #2306, where the focus positions (mm) for each image will be recorded. You can delete this file from within the script. The default is no log file.

To use the script, simply focus on the nearest point of interest and place the camera in manual focus mode. On the G7X you seem to need to do at least one focus operation to get CHDK to set the correct H, thus it is good practice to ensure H is stable, ie by looking at the CHDK DoF feedback. That is constant, for the aperture and focal length you are at, irrespective of focus.

The script is started in the normal way, eg by doing a shutter press.

Focus Test

As way of an example, here is a test image where I focused on our cat's mouse, at about a foot away from the camera, and used diffraction aware overlap. The script took 7 perfectly abutted F/4 focus images, accounting for diffraction, and two additional exposure brackets at each focus station, ie 21 images. The post processing was carried out in Lightroom, with a round trip to Helicon Focus.

Finally, you will find information about this script and other photography matters on my blog at photography.grayheron.net


Script Code Save to your /SCRIPTS/ folder as usual. Note the following may not be the latest version, which is always available at:

https://gist.github.com/pigeonhill/10a43f5ba543bc758f1ce21d28981a89
--[[
@title Landscape Bracketing
'Perfect' focus brackets from near to blur defined 'infinity': overlap defined at a fraction of camera's CoC
Plus option of two additional exposure brackets at each focus step, at 1Ev, 2Ev or 3Ev using -/+ or -/-- or +/++ logic
More info at https://chdk.wikia.com/wiki/Landscape_Focus_Bracketing_:_perfect_near_to_far_focus_brackets
Camera should be in manual focus mode and M mode
Release 1.98
Tested on a G7X & G1X
(c) Garry George
@chdk_version 1.5
@param u Focus stack mode?
    @default u 0
    @values  u X2Inf Mac2Inf Mac2X
@param g Overlap at?
    @default g 3
    @values  g CoC 2CoC/3 CoC/2 diff
@param p Exposure bracket delta?
    @default p 0
    @values  p None 1Ev 2Ev 3Ev
@param j Exposure bracket logic?
    @default j 0
    @values  j 0/-/+ 0/-/-- 0/+/++ ISO ZN2
@param n Max number of focus brackets?
    @default n 10
    @range   n 4 30
@param c Script Delay (s)
    @default c 3
    @range   c 0 5
@param b Bookends?
    @default b 1
    @values  b No Yes
@param q Infinity focus quality?
    @default q 1
    @values  q CoC/2 CoC/3 CoC/4
@param v Show bracket pos?
    @default v 1
    @values  v No Yes
@param k Log?
    @default k 0
    @values  k No Yes
@param e Delete Log File?
    @default e 0
    @values  e No Yes
--]]

capmode = require("capmode")
if (capmode.get_name() ~= "M") then
    print("Switch to M mode")
    return -- exit script
end

if get_focus_mode() ~= 1 then
    print("Switch to MF mode")
    return -- exit script
end

if e == 1 then
    local fp = io.open("A/CHDK/DATA/LBS.CFG")
    local str = fp:read("*all")
    fp:close()
    temp = string.sub(str, 1, 1)
    temp = "A/CHDK/DATA/LBS."..temp
    fp = io.open (temp,"r")
    if fp == nil then
        print("No ConFig file found")
        return 
    else
        str = fp:read("*all")
        fp:close()
        if string.find(str,"e=0") == nil then
            str = string.gsub(str,"#e=1","#e=0")
            fp = io.open (temp,"w")
            fp:write(str)
            fp:close()
            os.remove("A/CHDK/LOGS/LOG_2306.TXT")
            print("Log File Deleted")
            return -- exit script
        end
    end
end

press("shoot_half") -- get current exposure
t = get_tick_count()
repeat 
    sleep(50)
    if get_tick_count() - t > 5000 then
        print("Unknown Error")
        release("shoot_half")
        return
    end
until (get_shooting() == true)
release("shoot_half")
s = get_tv96()
av = get_av96()

if u == 1 then -- change focus to min focus
    set_focus(0)
    press("shoot_half") 
    repeat
        x = get_focus()
        sleep(100) -- seems to work on G7X & G1X, may need changing on other cams
    until x == get_focus()
    release("shoot_half")
end

dof = get_dofinfo()
x = dof.focus
x_start = x
last_x = x

base_h = dof.hyp_dist
temp = 0
temp1 = 0
temp2 = 0
ok = true
fl = dof.focal_length/100
log ={}
count = 0

-- A few Functions

function bookend()
    if b == 1 then
        set_tv96(960)
        set_av96(640)
        shoot()
        set_tv96_direct(s)
        set_av96_direct(av)
    end
end

function refocus(xx)
    local dis = 0
    set_focus(xx)
    press("shoot_half") 
    repeat
        dis = get_focus()
        sleep(200) -- seems to work on G7X & G1X, may need changing on other cams
    until dis == get_focus()
    sleep(500)
    release("shoot_half")
end

function X_bracket()
    set_tv96_direct(s)
    if j == 3 then
        local iso = get_sv96()
        set_sv96(sv96_market_to_real(iso_to_sv96(100)))
        shoot()
        set_sv96(sv96_market_to_real(iso_to_sv96(800)))
        shoot()
        set_sv96(iso)
    elseif j == 4 then
        shoot()
        set_tv96_direct(s-96*4)
        shoot()
        set_tv96_direct(s)
    else
        shoot()
        count = count + 1
        if p ~= 0 then 
            if j == 0 then
                set_tv96_direct(s-96*p)
                shoot()
                set_tv96_direct(s+96*p)
            elseif j == 1 then
                set_tv96_direct(s+96*p)
                shoot()
                set_tv96_direct(s+2*96*p)
            elseif j == 2 then
                set_tv96_direct(s-96*p)
                shoot()
                set_tv96_direct(s-2*96*p)
            end
            shoot()
            set_tv96_direct(s)
        end
    end
end

h = base_h -- no overlap case: brackets 'touch' at CHDK CoC

if g == 1 then -- adjust h to achieve the requested overlap, ie 'touching' at 2*CoC/3 or CoC/2 or at the diffraction aware defocus blur
    h = (h*3)/2
elseif g == 2 then
    h = 2*h
elseif g == 3 then -- use diffraction aware overlap: Assume CHDK CoC is total blur, formed in quadrature from defocus and diffraction blurs
    temp1 = 1000*dof.coc -- total blur set up for imath
    temp2 = (1342*dof.aperture)/1000 -- set up for imath. From diff_blur = 2.44*0.55*N (um)
    if temp1 > temp2 then -- can use diffraction aware overlap
        temp = imath.sqrt(imath.mul(temp1,temp1) - imath.mul(temp2,temp2)) -- diffraction aware defocus blur
        temp2 = imath.div(temp1,temp)
        if temp2 > 3000 then 
            h = 3*h
            print("Warning: High Diff: using CoC/3")
        else
            h = (h*temp2)/1000
        end
    else -- diff blur > total blur
        h = 3*h
        print("Warning: Diff>Defocus: using CoC/3")
    end
end

if u == 2 and x < h then
    refocus(0)
    dof = get_dofinfo()
    x = dof.focus -- register actual x moved by cam
    last_x = x
    sleep(c*1000)
    bookend()
    repeat -- to capture focus brackets from min focus up to x
        X_bracket()
        temp = get_exp_count().." @ "..x.."mm"
        if v == 1 then print(temp) end
        x = (x*(h*10 - 2*fl))/(h*10 - 2*x*10) -- position of next focus bracket
        if x <= last_x or x == -1 then 
            print("Unknown Error")   
            refocus(x_start)
            return
        end   
        refocus(x) -- request move to x
        dof = get_dofinfo()
        x = dof.focus -- register actual x moved by cam
        last_x = x
    until ((x <= 0) or (x > x_start))
    refocus(x_start)
    dof = get_dofinfo()
    x = dof.focus -- register actual x moved by cam
    X_bracket()
    temp = get_exp_count().." @ "..x.."mm"
    if v == 1 then print(temp) end
    return -- exit script
end

if x < base_h then
    num = ((10*(h + x))/(2*x) + 5)/10
else
    print("@/beyond H")
    sleep(c*1000)
    X_bracket()
    temp = get_exp_count().." @ "..x_start.."mm"
    if v == 1 then print(temp) end
    return -- exit script
end

if num > n-1 then -- accounting for last bracket at infinity focus
    print("Warning")
    temp2 = num + 1
    print("^ max # brackets to "..temp2)
    return -- exit script
end

sleep(c*1000)
bookend()

refocus(x_start) -- explicitly refocus to start
dof = get_dofinfo()
x = dof.focus
last_x = x
if k == 1 then log[#log + 1] = "\n"..os.date() end
X_bracket()
temp = get_exp_count().." @ "..x.."mm"
if v == 1 then print(temp) end
if k == 1 then log[#log + 1] = temp end

repeat -- to capture focus brackets up to just past h/3
    x = (x*(h*10 - 2*fl))/(h*10 - 2*x*10) -- position of next focus bracket
    if x <= last_x or x == -1 then 
        print("Unknown Error")   
        refocus(x_start)
        return
    end   
    refocus(x) -- request move to x
    dof = get_dofinfo()
    x = dof.focus -- register actual x moved by cam
    last_x = x
    X_bracket()
    temp = get_exp_count().." @ "..x.."mm"
    if v == 1 then print(temp) end
    if k == 1 then log[#log + 1] = temp end
until ((x <= 0) or (x > h/3)) 

temp1 = h
temp2 = base_h*(q+2)

refocus(temp1) -- take shot at h
X_bracket()
temp = get_exp_count().." @ "..temp1.."mm"
if v == 1 then print(temp) end
if k == 1 then log[#log + 1] = temp end

if temp2 > temp1 then -- take additional infinity focus shot
    refocus(temp2)
    X_bracket()     
    temp = get_exp_count().." @ "..temp2.."mm"
    if v == 1 then print(temp) end
    if k == 1 then log[#log + 1] = temp end
end

bookend()

temp = count.." focus brackets"
print(temp)
if k == 1 then log[#log + 1] = temp end
temp1 = ((10*dof.coc*base_h)/temp2 + 5)/10
temp = "Infinity blur "..temp1.."um"
if k == 1 then log[#log + 1] = temp end
if p ~= 0 and k == 1 then log[#log + 1] = "Exposure offset = "..p.."Ev" end

refocus(x_start)

if k == 1 then
    print_screen(-2306)
    for i = 1, #log do
        print(log[i].."\n")
    end
end