FANDOM


Get Focus BracketsEdit

Written for/on: G7X and G1X
Should also work on: any camera that runs CHDK
Requires CHDK build: Anything recent as of 2018

Latest version of this script may be downloaded from: https://gist.github.com/pigeonhill/3fe9e52621db7149eb3505bd60b395cb

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 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.

Picture2

There are five focus bracketing strategies: current position to infinity (X2Inf), min camera focus to current position (Min2X), min camera focus to infinity (Min2INF), current position to delta X in front (X2DelX) and current position to delta X behind (-DelX2X).

At each focus point you can also ask the script to create additional exposure brackets at either -xEv & +xEv, -xEv & --xEv or at +xEv and ++xEv. X can be 1 or 2Ev or 3Ev from the base exposure. These options cover all the usual exposure bracketing options, eg if 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 (at ISO 800 or ISO 1600) that assumes (sic) the camera is ISO-less above ISO 800 or 1600 (check your camera to ensure you select the right ISO so the sensor read noise swamps the banding). 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.

G1X G7X

As an illustration, the chart on the right, from the DXOMark site, shows the G1X vs G7X. As can be seen the G7X can be assumed to be fully ISO invariant, which means doing ISO bracketing is pointless, ie simply push a single base exposure in post (as long as you are not too underexposed ;-))

On the other hand the G1X is only ISO-less above about 1600. Hence take a single additional bracket at, say, ISO1600.

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 or +3Ev or +2EV from the first one. To use the ZN option, simply ETTR to capture the highlights and the script will create an additional image for the shadow areas. But beware potential flares from bright highlights if you use the +4Ev option

To use the script, simply focus on the nearest point of interest, unless you are using the Min2 mode.
Test image
The script is started in the normal way, eg by doing a shutter press.

The script doesn't require the camera to be any specific mode. Thus it should run OK from M or Av mode, as well as the camera being AF mode. Note, if in AF mode, obviously, focus at the required position before switching to Alt mode and running the script.

But watch out for the Canon Av restriction, ie Tv < 1s.

Vyne-2

The script will exit Alt mode when complete. Here are a couple of test images I took. 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

Save to your /SCRIPTS/ folder as usual.

The latest version will always be here (the script below may not be the latest):

https://gist.github.com/pigeonhill/3fe9e52621db7149eb3505bd60b395cb

.

--[[
@title Get Brackets

Capture 'Perfect' focus brackets, with focus overlap defined in terms of defocus (CHDK Circle of Confusion (CoC)) blur

Plus option of additional exposure brackets at each focus step, using various logic: 1Ev, 2Ev or 3Ev using -/+ or -/-- or +/++; one iso-invariant at
ISO at 800 or 1600; or one (Zero Noise) exposure at 4Ev, 3Ev or 2Ev

Focus bracketing strategies are: current position to blur-defined infinity (X2Inf); min camera focus to current position (Min2X), min camera focus to blur-defined infinity (Min2Inf); 
current position to a delta x ahead of the current position (X2DelX); from a delta x in front of the current position to the current position (-DelX2X).
None means that requested exposures will only be captured at the current focus point

Overlap, ie bracket to bracket focus insurance, is definded in terms of the defocus (CoC) blur, eg CoC/2 means focus brackets 'touch' at a blur of CoC/2. 
Where as diff means that the defocus blur and the diffraction blur, taken in quadrature, equals the CHDK CoC value

Unless, under focus strategy, none or the delX option is selected the script will take three focus brackets, even if initial focus is beyond the defocus hyperfocal (H), 
ie at H/3, at H and at the blur-based infinity, which means DoF will go from H/4 to infinity

Use Bookends to help differentiate bracket set in post

Option to switch LCD screen off during shooting

Rev 1.35
April 2019
Tested on a G7X ad G1X
(c) Garry George

@chdk_version 1.5
# overlap = 0 "Overlap at" {CoC 2CoC/3 CoC/2 diff}
# focus_strat = 0 "Focus bracket logic?" {X2Inf Min2X Min2Inf X2DelX -DelX2X None}
# delx = 10 "Focus depth (mm)" [5 100]
# delta = 0 "Exposure bracket delta?" {None 1Ev 2Ev 3Ev 800 1600 ZN4 ZN3 ZN2}
# logic = 0 "Exposure bracket logic?" {0/-/+ 0/-/-- 0/+/++}
# delay = 3 "Script Delay (s)" [0 5]
# bookends = 1 "Bookends?" {No Yes}
# quality = 2 "Infinity focus quality?" {CoC/2 CoC/3 CoC/4}
# screen_off = 1 "Screen off?" {No Yes}
--]]

capmode = require("capmode")

function restore()
    if x_start == -1 then refocus(1000000) else refocus(x_start) end
    set_tv96_direct(s)
    set_av96_direct(av)
    press("shoot_half") -- get current camera values
    repeat sleep(10) until get_shooting()
    sleep(1000)
    release("shoot_half")
    repeat sleep(10) until (not get_shooting())

    if current_focus_mode ~= 1 then set_mf(0) end
    if (current_mode ~= "M") then 
        capmode.set(current_mode)
    end
    set_lcd_display(1)
    exit_alt()
end

function set_up()
    current_focus_mode = get_focus_mode() 
    current_mode = capmode.get_name()

    s = get_tv96()
    av = get_av96()

    if (current_mode ~= "M") then
        capmode.set("M")
    end

    set_tv96_direct(s)
    set_av96_direct(av)
    
    if current_focus_mode ~= 1 then
        set_mf(1) 
    end

    dof = get_dofinfo()
    x = dof.focus
    x_start = x
    last_x = x
    base_h = dof.hyp_dist -- that is without diffraction accounted for
    fl = dof.focal_length/100

    return true
end

function myshoot() -- works inside X_bracket function
    local prevcnt=hook_shoot.count()
    local rawprevcnt=hook_raw.count()
    press'shoot_full_only'
    repeat sleep(10) until prevcnt ~= hook_shoot.count()
    release'shoot_full_only'
    repeat sleep(10) until rawprevcnt ~= hook_raw.count()
end

function bookend()
    set_tv96_direct(960)
    set_av96_direct(640)
    local ecnt = get_exp_count()
    shoot()
    set_tv96_direct(s)
    set_av96_direct(av)
    repeat sleep(10) until (get_exp_count() ~= ecnt)
end

function refocus(xx)
    set_focus(xx)
    dof = get_dofinfo() -- update info
end

function X_bracket()
    press("shoot_half")
    repeat sleep(10) until (get_shooting())
    set_tv96_direct(s)
    if delta == 0 then 
        myshoot()
    elseif delta < 4 and logic == 0 then
        myshoot()
        set_tv96_direct(s-96*delta)
        myshoot()
        set_tv96_direct(s+96*delta)
        myshoot()
    elseif delta < 4 and logic == 1 then
        myshoot()
        set_tv96_direct(s+96*delta)
        myshoot()
        set_tv96_direct(s+2*96*delta)
        myshoot()
    elseif delta < 4 and logic == 2 then
        myshoot()
        set_tv96_direct(s-96*delta)
        myshoot()
        set_tv96_direct(s-2*96*delta)
        myshoot()
    elseif delta == 4 then
        local iso = get_sv96()
        myshoot()
        set_sv96(sv96_market_to_real(iso_to_sv96(800)))
        myshoot()
        set_sv96(iso)
    elseif delta == 5 then
        local iso = get_sv96()
        myshoot()
        set_sv96(sv96_market_to_real(iso_to_sv96(1600)))
        myshoot()
        set_sv96(iso)
    elseif delta == 6 then
        myshoot()
        set_tv96_direct(s-96*4)
        myshoot()
    elseif delta == 7 then
        myshoot()
        set_tv96_direct(s-96*3)
        myshoot()
    elseif delta == 8 then
        myshoot()
        set_tv96_direct(s-96*2)
        myshoot()
    end
    set_tv96_direct(s)
    release("shoot_half")
    repeat sleep(10) until (not get_shooting())
end

-- Main section

if not set_up() then -- at the moment set_up() always returns true ;-)
    restore()
    return -- exit script
end

if screen_off == 1 then set_lcd_display(0) end

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

if overlap == 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 overlap == 2 then
    h = 2*h
elseif overlap == 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
        else
            h = (h*temp2)/1000
        end
    else -- diff blur > total blur
        h = 3*h
    end
end

sleep(delay*1000) 

if bookends == 1 then bookend() end

if x > base_h then focus_strat = 0 end

x_stop = h/3

if x == -1 then
    -- skip the other checks
elseif focus_strat == 1 then
    x = 0
    x_stop = x_start
elseif focus_strat == 2 then
    x = 0
elseif focus_strat == 3 then
    x_stop = x_start + delx
elseif focus_strat == 4 then
    x = x_start - delx
    x_stop = x_start
elseif focus_strat == 5 then
    x_stop = x_start
end

while (x < x_stop) and focus_strat ~= 5 and x_start ~= -1 do -- capture the focus brackets up to the required x_stop
    refocus(x)
    X_bracket()
    x = dof.focus -- get actual x moved by cam
    last_x = x
    x = (x*(h*10 - 2*fl))/(h*10 - 2*x*10) -- position of next focus bracket
    if x < last_x then -- something's not right
        print("Unknown Error")   
        refocus(x_start)
        restore()
        return
    elseif x == last_x then -- new estimate of next x to get things going again
        x = last_x + (x*x*dof.aperture*dof.coc)/(fl*fl*10000)
        if x == last_x then x = last_x + 1 end
    end   
end

refocus(x_stop)
X_bracket()

if focus_strat == 0 or focus_strat == 2 or x_start == -1 then 
    refocus(h) -- take shot at h
    X_bracket()

    temp2 = h*(quality+2)
    refocus(temp2) -- take shoot at infinity blur quality
    X_bracket()   
end

if bookends == 1 then bookend() end

restore()