Skip to content

Instantly share code, notes, and snippets.

@pigeonhill
Last active June 12, 2023 16:15
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save pigeonhill/3fe9e52621db7149eb3505bd60b395cb to your computer and use it in GitHub Desktop.
LBS (CHDK)
--[[
Landscape Bracketing Script (LBS)
Capture 'perfect' focus brackets, with focus overlap defined in terms of defocus (CHDK) Circle of Confusion blur
Focus bracketing strategies are: current position to blur-defined infinity (X2INF); min camera focus to blur-defined infinity (Min2INF); H/x to infinity (H/x2INF);...
and Min focus to current focus position (Min2X).
Overlap, ie bracket to bracket, is definded in terms of the defocus (CoC) blur, eg CoC/2 means focus brackets 'touch' at a CHDK defocus blur of CoC/2.
Exposure brackets may be taken at each focus step, using various schemes: 1Ev, 2Ev or 3Ev steps using -/+ or -/-- or +/++; one
ISO at 800 or 1600; or one (Zero Noise) exposure at +4Ev, +3Ev or +2Ev, auto from shadow exposure to an ETTR exposure, a wind bracket where the exposure stays the same...
but ISO and shutter speed are adjusted, ie to achieve a faster shutter; and a full auto.
You can check the camera's black level and set this value via the menu, eg put the lens cover on and run the script with the check black level option.
Black and White level sensitivity can be user set to Normal, Mediun or High. White level influences ETTR exposure insurance, ie high skewing the exposure away from 100% saturation.
You can also switch on Infinity Brackets that allows you to take an ETTR shot at the infinity focus position, eg for the sky, or capture a simulated ND bracket set, up to a 5 ND, or both.
Use Bookends to differentiate the captured bracket set in post (reccomended). Note the ND sub bracket will be bookended as well.
The script will pause and display info about the option you have selected. You can refocus, with the script recalculating number of brackets...
or telling you the infinity defocus blur if you are positioned beyond the hyperfocal.
When the script is paused you can change the shutter; however, don't try and change aperture or focal length, as the script will terminate in a controlled way.
You can switch off the screen during bracketing (recommended).
Press Half Shutter to run the script or MENU to exit script.
The script should run on most CHDK cameras, but bits of the code may need tweaking, eg to account for camera specific buttons.
Rev 1.190
March 2023
Garry George
@chdk_version 1.6
@title LBS
@subtitle Focus Stuff
# focus_strat = 0 "Focus logic?" {None X2INF Min2INF H/x2INF Min2X}
# hfrac = 0 "H start" {H H/3 H/5 H/7 H/9}
# overlap = 0 "Overlap at" {CoC 2CoC/3 CoC/2}
# quality = 2 "Infinity quality?" {CoC/2 CoC/3 CoC/4}
@subtitle Exposure Stuff
# delta = 0 "Exposure logic?" {None 1Ev 2Ev 3Ev 800 1600 ZN4 ZN3 ZN2 A:x>H Wind A:S>H}
# logic = 0 "Bracketing logic?" {0/-/+ 0/-/-- 0/+/++}
# hilit = 0 "Over X Sensitivity" {Normal Medium High}
# bp = 0 "Black Level Sensitivity" {Normal Medium High}
# sky = 0 "Inf Bracketing?" {None Sky NDSim Both Auto}
# ndf = 1 "ND value" [-5 30]
@subtitle Other Stuff
# delay = 3 "Script Delay (s)" [0 5]
# bookends = 1 "Bookends?" {No Yes}
# screen_off = 1 "Screen off?" {No Yes}
# bplevel = 0 "Check Black Level?" {No Yes}
# bpval = 30 "Black Level" [0 200]
# title = 1 "Title Line" {Off Full Exit}
--]]
capmode = require("capmode")
props=require'propcase'
bi = get_buildinfo()
if (bi.platform == "m3") then
print("Doesn't run on M3")
sleep(3000)
return
end
if bi.platform == "s95" then
set_exit_key("display")
--elseif bi.platform == "???" then -- add additional camera specific exit keys here
--set_exit_key("display")
else
set_exit_key("down") -- default exit key
end
set_config_value(2151,0) -- switching these two off means we are measuring distance 'from the lens': ...
set_config_value(2152,0) -- ...this is the 'best' we can do without knowing the position of the front principal plane and pupil magnification
set_draw_title_line(title) -- switch off CHDK info according to the menu request
function restore() -- called at script termination
exit_alt()
end
function my_restore()
if x_start == -1 then refocus(1000000) else refocus(x_start) end
set_user_tv96(s_start)
set_av96_direct(av)
press("shoot_half")
repeat sleep(10) until get_shooting()
sleep(100)
release("shoot_half")
repeat sleep(10) until (not get_shooting())
set_lcd_display(1)
cls()
exit_alt() -- just in case
sleep(1000) -- just in case
end
function set_up()
current_focus_mode = get_focus_mode()
current_mode = capmode.get_name()
if capmode.get_name() ~= "M" then
print("Switch to M mode")
sleep(2000)
exit_alt()
return false
end
if current_focus_mode ~= 1 then
print("Switch to MF mode")
sleep(2000)
exit_alt()
return false
end
press("shoot_half")
repeat sleep(10) until get_shooting()
dof = get_dofinfo()
x = dof.focus
x_start = x
fl = fmath.new(dof.focal_length,1000) -- focal length in mm (real)
s = get_prop(props.TV)
s_start = s
av = get_prop(props.AV)
n = fmath.new(av96_to_aperture(av),1000) -- aperture number as a real
release("shoot_half")
repeat sleep(10) until (not get_shooting())
x_start = x
xr = x
base_h = (1000*fl*fl)/(n*dof.coc)
H = base_h + fl
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 snap() -- single image capture
press("shoot_half")
repeat sleep(10) until (get_shooting())
myshoot()
release("shoot_half")
repeat sleep(10) until (not get_shooting())
end
function bookend()
--set_user_tv96(usec_to_tv96(1000))
set_tv96_direct(usec_to_tv96(1000))
set_av96_direct(get_max_av96())
local ecnt = get_exp_count()
snap()
--set_user_tv96(s_start)
set_tv96_direct(s_start)
set_av96_direct(av)
repeat sleep(10) until (get_exp_count() ~= ecnt)
end
function refocus(xx)
set_focus(xx)
dof = get_dofinfo() -- update info
return dof.focus
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()
iso = sv96_to_iso(iso)
myshoot()
set_iso_mode(800)
myshoot()
set_iso_mode(iso)
elseif delta == 5 then
local iso = get_sv96()
iso = sv96_to_iso(iso)
myshoot()
set_iso_mode(1600)
myshoot()
set_iso_mode(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()
elseif delta == 9 or delta == 11 then
shot_histo_enable(1)
myshoot()
sleep(100)
if bp == 1 then
bp = 3
elseif bp == 2 then
bp = 9
end
hilit2 = hilit
if hilit2 == 1 then
hilit2 = 3
elseif hilit2 == 2 then
hilit2 = 6
end
do
local bpl = bpval
local lower = 1023 - hilit2
local test=get_histo_range(lower,1023)
local test2 = get_histo_range(bpl,bpl+bp)
step = 1
while test > 0 do
set_tv96_direct(s + 96 * 2 * step)
snap()
sleep(100)
test=get_histo_range(lower,1023)
step = step + 1
end
if delta == 11 then
step = 1
while test2 > 0 do
set_tv96_direct(s - 96 * 2 * step)
snap()
sleep(100)
test2=get_histo_range(bpl,bpl+bp)
step = step + 1
end
end
end
shot_histo_enable(0)
elseif delta == 10 then -- 3Ev ISO uplift from a lowish ISO base
snap()
set_tv96_direct(s+96*3)
local svt = get_sv96()
set_sv96(svt+(3*96))
snap()
set_sv96(svt)
end
set_tv96_direct(s)
release("shoot_half")
repeat sleep(10) until (not get_shooting())
end
function set_ETTR(n)
histo={}
count = 0
if bi.platform == "g7x" then -- adjust histogram saturation bin
top = 235
--elseif bi.platform == "???" then -- add additional cameras here
--top = ?
else
top = 255
end
set_user_tv96(s) -- current exposure
press("shoot_half")
repeat sleep(10) until get_shooting()
release("shoot_half")
repeat sleep(10) until (not get_shooting())
histo,total=get_live_histo()
for j = (top-n),top, 1 do
count = count + histo[j]
end
if count > 0 then -- over exposed
i = 0
repeat
i=i+96
count = 0
set_user_tv96(s+i)
press("shoot_half")
repeat sleep(10) until get_shooting()
release("shoot_half")
repeat sleep(10) until (not get_shooting())
histo,total=get_live_histo()
for j = (top-n),top, 1 do
count = count + histo[j]
end
until count <= 0
end
return
end
function num_b (xx)
local xc = xx
local HH = H:int()
if xx < 1 then -- at infinity
return 1
end
if focus_strat == 0 then -- no focus bracketing requested
return 1
elseif (3*xc < HH) then
local i = 0
repeat
xc = ((xc*(h-fl))/(fl+h-2*xc)):int()
i = i + 1
until 3*xc >= HH
return (i + 1)
elseif (xx < HH) then
return 2
else
return 1
end
end
function ND_test()
if ndf > 0 then -- capture an ND sim bracket subset based on time
local current_tv = get_prop(props.USER_TV)
local base_shutter_time = tv96_to_usec(current_tv)
local max_av = get_max_av96()
local current_av = get_av96()
if base_shutter_time < 1000000*ndf then
if bookends == 1 then bookend() end -- additional bookends for ND brackets
del_av = max_av - current_av -- max av shift available in ev
del_tv = current_tv - usec_to_tv96(ndf*1000000) -- required tv shift in ev
if del_tv < del_av then -- adjust things
temp_av = (current_av+del_tv)
temp = current_tv - del_tv -- adjust shutter time to match aperture
else
temp_av = max_av -- set to max
temp = current_tv - del_av -- adjust shutter time to match aperture
end
if temp < -480 then -- max shutter of 30s: just in case
temp = -480
end
i = 1 + (ndf*1000000)/tv96_to_usec(temp)
print(i.." ND brackets")
for j = 1, i do
set_tv96_direct(temp)
set_av96(temp_av)
snap()
end
if bookends == 1 then bookend() end
set_av96(current_av)
set_tv96(current_tv)
end
elseif ndf == 0 then -- can't capture an ND sim bracket subset
print("Can't use ND option")
elseif ndf <0 then -- capture an ND sim bracket subset based on ND filter
if bookends == 1 then bookend() end
if ndf == -1 then
i = 2
elseif ndf == -2 then
i = 4
elseif ndf == -3 then
i = 8
elseif ndf == -4 then
i = 16
elseif ndf == -5 then
i = 32
end
print(i.." ND brackets")
for j = 1, i do
snap()
end
if bookends == 1 then bookend() end
end
end
-- Main section
if not set_up() then
print("Can't run script")
return -- exit script
end
if bplevel == 1 then
shot_histo_enable(1)
jj = 0
snap()
repeat
test2 = get_histo_range(jj,jj)
jj = jj + 1
until test2 > 0
shot_histo_enable(0)
print("Black Level = "..jj-1)
sleep(4000)
return
end
-- Adjust the overlap h if required
h = base_h -- 'short' hyperfocal, ie H = h + fl
if overlap == 1 then -- adjust h to achieve the requested overlap, ie 'touching' at 2*CoC/3 or CoC/2
h = (h*3)/2
elseif overlap == 2 then
h = 2*h
end
H = h + fl -- 'full' hyperfocal as measured 'from the lens'
x_stop = (H/3):int()
if ((focus_strat == 2) or (focus_strat == 4)) then -- move to minimum focus
x = 10
xr = refocus(x)
x = xr
elseif focus_strat == 3 then
x = (H/(2*hfrac+1)):int()
xr = refocus(x)
x = xr
end
-- Keep all DoF calculations in request-space, to limit build up of image to image CHDK-Canon errors
bracks = num_b(xr)
if focus_strat == 0 then bracks = 1 end
if focus_strat == 4 then
x_stop = x_start
if x_stop > (H/3):int() then
print("Too far away")
return
end
bracks = num_b(xr) - num_b(x_stop)
end
if focus_strat == 0 then
print("@:"..x.."mm")
else
print("At least "..bracks.." focus steps")
print("@:"..x.."mm")
if focus_strat == 4 then
print("Stop@:"..x_stop.."mm")
else
print("H@:"..H:int().."mm")
end
end
print("HS 2 run: MENU 2 exit")
dof = get_dofinfo() -- get current focal length and aperture values
fl_check = dof.focal_length
av_check = dof.aperture
if (focus_strat == 1 or focus_strat == 0) then last_x = 0 else last_x = x end
repeat
dof = get_dofinfo() -- Note the following checks may not work on all cameras: so don't change focal length or aperture ;-)
if fl_check ~= dof.focal_length then
print("FL changed!")
sleep(2000)
return
end
if av_check ~= dof.aperture and get_alt_mode() then
print("Aperture changed!")
sleep(2000)
return
end
if focus_strat == 1 then
if last_x ~= dof.focus then
last_x = dof.focus
if last_x == -1 then
print("@ infinity")
elseif last_x >= H:int() then
if last_x > 0 then
blur = ((dof.coc*H)/last_x):int()
else
blur = 0
end
print("@:"..last_x.."mm/InfBlur="..blur.."um")
else
bracks = num_b(last_x) + 1
print("@:"..last_x.."mm/#steps="..bracks)
end
end
elseif focus_strat == 0 then
if last_x ~= dof.focus then
last_x = dof.focus
if last_x == -1 then
print("@ infinity")
elseif last_x >= H:int() then
if last_x > 0 then
blur = ((dof.coc*H)/last_x):int()
else
blur = 0
end
print("@:"..last_x.."mm/InfBlur="..blur.."um")
else
print("<H@:"..last_x.."mm")
end
end
end
dof = get_dofinfo()
fl_check = dof.focal_length
av_check = dof.aperture
wait_click(500)
if is_key("set") then
print("HS 2 run: MENU 2 exit")
last_x = -9
console_redraw()
elseif is_key("left") then -- toggle CHDK histogram
sleep(100)
set_config_value(1060,1-get_config_value(1060,0))
end
if is_key("menu") then
my_restore()
return
end
until is_key("shoot_half") and get_alt_mode()
press("shoot_half")
repeat sleep(10) until get_shooting()
s_start = get_prop(props.TV)
s = s_start
set_user_tv96(s_start)
release("shoot_half")
repeat sleep(10) until (not get_shooting())
xr = refocus(last_x)
x = xr
t1=os.time()
if screen_off == 1 then set_lcd_display(0) end
if bookends == 1 then bookend() end
t1=(os.time()-t1)
if delay >= t1 then
t1=delay-t1
sleep(t1*1000)
end
if focus_strat == 0 then -- take requested exposues only
X_bracket()
else
im = 0
while (xr < x_stop) and (xr > 0) do -- capture the focus brackets up to the required x_stop
im = im + 1
print(im.."/"..bracks..": "..x.."mm")
X_bracket()
if 2*xr >= H:int() then break end
xrn = ((xr*(h-fl))/(fl+h-2*xr)):int()
if xrn == xr then xrn = xr + 1 end
xr = xrn
x = refocus(xr)
end
if focus_strat ~= 4 then
if xr == -1 then
im=im+1
print(im.."/"..bracks..":INF")
X_bracket()
else -- take an image at H and the infinity blur position
temp = H:int()
temp2 = refocus(temp) -- take shot at the base hyperfocal
im=im+1
print(im.."/"..bracks..": "..temp2.."mm")
X_bracket()
temp2 = (H*(quality+2)):int()
temp2 = refocus(temp2) -- take shoot at the selected infinity blur
im=im+1
print(im.."/"..bracks..": "..temp2.."mm")
X_bracket()
end
end
end
set_tv96_direct(s_start)
if (sky == 1 or sky == 3) and focus_strat ~= 4 then -- Take a sky ETTR shot
print("Sky bracket")
hilit2 = hilit
if hilit2 == 1 then
hilit2 = 3
elseif hilit2 == 2 then
hilit2 = 9
end
set_ETTR(hilit2)
press("shoot_half")
repeat sleep(10) until get_shooting()
release("shoot_half")
repeat sleep(10) until (not get_shooting())
snap()
end
if (sky == 2 or sky == 3) then -- capture an ND sim bracket subset
print("ND brackets")
ND_test()
end
if sky == 4 and focus_strat ~= 4 then -- Take a sky auto exposure bracket set
shot_histo_enable(1)
snap()
hilit2 = hilit
if hilit2 == 1 then
hilit2 = 3
elseif hilit2 == 2 then
hilit2 = 6
end
do
local lower = 1023 - hilit2
local test=get_histo_range(lower,1023)
step = 1
while test > 0 do
set_tv96_direct(s + 96 * 2 * step)
snap()
test=get_histo_range(lower,1023)
step = step + 1
end
end
shot_histo_enable(0)
end
if bookends == 1 then bookend() end
my_restore()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment