Last active
June 12, 2023 16:15
-
-
Save pigeonhill/3fe9e52621db7149eb3505bd60b395cb to your computer and use it in GitHub Desktop.
LBS (CHDK)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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