Skip to content

Instantly share code, notes, and snippets.

@pigeonhill
Created August 6, 2017 11:31
  • Star 0 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/97a511da5a1e4d54ceffe40c8d02b24f to your computer and use it in GitHub Desktop.
Auto Landscape Bracketing Script
--[[
Auto Bracketing Script
Auto focus bracketing with exposure bracketing
Plus the script does both time and frame bracketing, eg for ND/LE simulation
Release August 2017
This version has some error detection, but the user must be aware of certain things:
* Long FL lenses may fail and this script is best used with MEDIUM & WIDE angle lenses.
* Select FP2HFD option to estimate number of focus brackets to HFD. Add 1 bracket to this for FP2INF use case.
* The QHFD option takes a two bracket set, one either side of the HFD. One at HFD/2 and one at either 2xHFD or 3xHFD.
* Under settings you can select the 2x or 3x HFD: the default is 2xHFD.
* This script runs independant of the ML DoFs and always calculated DoFs based on diffraction aware
* Must use a lens that reports focus distance and DoF can be calculated.
* Must be in manual mode.
* Switch stabalisation off as you will not be hand holding ;-)
* Must be in LV mode: script won't run unless in LV.
* ETTR ON: settings recommended are - no link to dual, SNRs to OFF, highlights very low or zero (your choice, and not Always On or Auto Snap as this may 'confuse' the script.
* You can switch focus bracketing on/off in the menu, ie use LE and/or exposure mode alone. The script handles the logic, ie if LE mode is being used,
* then you can't use focus or exposure bracketing.
* You can select Dual-ISO when focus bracketing. But you need to ensure Dual-ISO settings are 'correct'.
* Image review must not be set to HOLD in Canon menu.
* Assumes lens is focused at correct FP to start a focus stack, ie script moves to infinity or HFD according to mode.
* When exposure bracketing the script will calculate the ETTR (and ETTL if required) of the scene and # brackets.
* For non-auto exposure backeting (1 or 2 Ev) set base exposure to capture shadows. For Auto exposure backeting (1 or 2 Ev) the base exposure (shutter and aperture) can be anywhere.
* If ETTR fails, then try adjusting ETTR set time in menu to give your camera more time (or experiment with less)
* Final thought: photography should be relaxing, hence I personally don't mind that this script takes time to run. As it's
* running, look around you and enjoy nature and the moment :-)
*******************************************************************************************************
* *
* If you reference this script, please consider acknowledging me at http://photography.grayheron.net/ *
* *
*******************************************************************************************************
--]]
-- Declare a few variables for the script which load when the camera is switced on
delay = 0
factor = 0
num_brackets = 0
ETTR_tv = 0
base_tv = 0
user_tv = 0
LE_brackets = 0
running = false
no_error = true
brackets_requested = false
infinity = 655000 -- based on ML infinity (same for all lenses)
function my_lens_dof_near()
--************************************************************
-- This replaces the ML calculated DoF near
local focus_distance = lens.focus_distance
local focal_length = lens.focal_length
local aperture_value = camera.aperture.value
local unity_blur = menu.get("DOF Settings","Circle of Confusion") -- ML set total blur criterion
local ml_blur = unity_blur
local b_diff = 2.44*0.550*aperture_value -- visible diffraction blur at infinity, ie magnification = 0
local b_diff_fp = b_diff*focus_distance/(focus_distance-focal_length) -- diffraction at fp, ie with magnification (really only kicks in for close work)
ml_blur = math.sqrt(unity_blur*unity_blur - b_diff_fp*b_diff_fp) -- calculate defocus blur
local d = focal_length/aperture_value -- diameter of aperture
local dof_near = d*focal_length*focus_distance
dof_near = dof_near/(ml_blur*(focus_distance-focal_length)/1000+d*focal_length)
return dof_near
--************************************************************
end
function my_lens_dof_far()
--************************************************************
-- This replaces the ML calculated DoF far
local focus_distance = lens.focus_distance
local focal_length = lens.focal_length
local aperture_value = camera.aperture.value
local unity_blur = menu.get("DOF Settings","Circle of Confusion") -- ML set total blur criterion
local ml_blur = unity_blur
local b_diff = 2.44*0.550*aperture_value -- visible diffraction blur at infinity, ie magnification = 0
local b_diff_fp = b_diff*focus_distance/(focus_distance-focal_length) -- diffraction at fp, ie with magnification (really only kicks in for close work)
ml_blur = math.sqrt(unity_blur*unity_blur - b_diff_fp*b_diff_fp) -- calculate defocus blur
local d = focal_length/aperture_value -- diameter of aperture
local dof_far = -1*d*focal_length*focus_distance
dof_far = dof_far/(ml_blur*(focus_distance-focal_length)/1000-d*focal_length)
return dof_far
--************************************************************
end
function my_lens_hyperfocal()
--************************************************************
-- This replaces the ML calculated HFD
local focus_distance = lens.focus_distance
local focal_length = lens.focal_length
local aperture_value = camera.aperture.value
local unity_blur = menu.get("DOF Settings","Circle of Confusion") -- ML set total blur criterion
local ml_blur = unity_blur
local b_diff = 2.44*0.550*aperture_value -- visible diffraction blur at infinity, ie magnification = 0
local b_diff_fp = b_diff*focus_distance/(focus_distance-focal_length) -- diffraction at fp, ie with magnification (really only kicks in for close work)
ml_blur = 0.001*math.sqrt(unity_blur*unity_blur - b_diff_fp*b_diff_fp) -- calculate defocus blur
local hyperfocal = focal_length*(1+focal_length/(aperture_value*ml_blur))
return hyperfocal
--************************************************************
end
function show_message(message,onoroff)
if onoroff then
display.print(message, 10, 45, FONT.LARGE ,COLOR.RED,COLOR.WHITE)
else
display.print(message, 10, 45, FONT.LARGE ,COLOR.TRANSPARENT,COLOR.TRANSPARENT)
end
end
function reset() -- tidy up at the end
if no_error == true then
display.notify_box("***FINISHED***",4000)
beep (3, 400 , 1000) -- notify end of script
-- Before completly finishing the script, check if any LE extras asked for
if scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 and scriptmenu.submenu["LE mode?"].submenu["Plus"].value == "Dual-ISO" then
menu.set("Expo","Dual ISO",1)
msleep(500)
my_shoot()
menu.set("Expo","Dual ISO",0)
elseif scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 and scriptmenu.submenu["LE mode?"].submenu["Plus"].value == "Auto Bracketing" then
menu.set("Shoot","Advanced Bracket",1)
msleep(500)
my_shoot()
menu.set("Shoot","Advanced Bracket",0)
end
else
beep (3, 200 , 1000) -- notify end of script
display.notify_box("***COULDN'T FINISH***",4000)
end
brackets_requested = false
LE_brackets = 0
running = true
no_error = true
end
function my_shoot()
camera.shoot(false)
repeat
msleep(200)
until lv.running
end
function check_bookend()
if scriptmenu.submenu["Bookends?"].value == "yes"
then
local tv = camera.shutter.ms
local av = camera.aperture.value
camera.shutter.ms = 1
camera.aperture.apex = 9
my_shoot()
camera.shutter.ms = tv
camera.aperture.value = av
end
end
function find_ETTR() -- Find the 'ETTR' shutter value: make sure you have set ML ETTR values correctly
show_message("Getting exposure for the highlights",true)
local m = menu.get("Auto ETTR","Trigger mode")
menu.set("Auto ETTR","Trigger mode",0) -- set ETTR menu to get an ETTR exposure
local check_tv = 0
repeat -- hang around until ETTR value has stabilised
check_tv = camera.shutter.apex
msleep (scriptmenu.submenu["Settings"].submenu["ETTR Set Time?"].value*1000) -- adjust this for your camera. The time delay allows the ML ETTR process to settle down
until check_tv == camera.shutter.apex
ETTR_tv = camera.shutter.apex
menu.set("Auto ETTR","Trigger mode",m) -- reset to user ML ETTR menu setting
show_message("Getting exposure for the highlights",false)
if ETTR_tv < base_tv
then -- base TV needs resetting
display.notify_box("Warning Shadow Tv < ETTR Tv",4000)
beep (3, 200 , 500) -- warning message
no_error = false
end
end
function find_ETTL() -- Find the 'ETTL' shutter value
show_message("Getting exposure for the shadows",true)
local m = menu.get("Auto ETTR","Shadow SNR limit")
local n = menu.get("Auto ETTR","Trigger mode")
menu.set("Auto ETTR","Trigger mode",0) -- set ETTR menu to get an ETTR exposure
menu.set("Auto ETTR","Shadow SNR limit", scriptmenu.submenu["Settings"].submenu["Shadow SNR"].value)
local check_tv = 0
repeat -- hang around until ETTL value has stabilised
check_tv = camera.shutter.apex
msleep (scriptmenu.submenu["Settings"].submenu["ETTR Set Time?"].value*1000) -- adjust this for your camera. The time delay allows the ML ETTR process to settle down
until check_tv == camera.shutter.apex
base_tv = camera.shutter.apex
menu.set("Auto ETTR","Trigger mode",n) -- reset to user ML ETTR menu setting
menu.set("Auto ETTR","Shadow SNR limit",m)
show_message("Getting exposure for the shadows",false)
end
function calc_brackets()
if no_error then
num_brackets = (ETTR_tv - base_tv)/factor
num_brackets = math.ceil(math.abs(num_brackets))
if num_brackets <= 1 then num_brackets = 1 end
end
end
function check_LE()
-- Check to see if user has requested LE bracketing of some kind
if scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0
then
if scriptmenu.submenu["LE mode?"].submenu["Mode?"].value == "time"
then
LE_brackets = math.ceil(scriptmenu.submenu["LE mode?"].submenu["select"].value/camera.shutter.value)
else
LE_brackets = scriptmenu.submenu["LE mode?"].submenu["select"].value
end
brackets_requested = true
end
end
function take_brackets()
local i = 0
if LE_brackets == 0 then
if no_error and brackets_requested then -- take exposure brackets
camera.shutter.apex = ETTR_tv + factor
for i = 1, num_brackets , 1
do
camera.shutter.apex = camera.shutter.apex - factor
my_shoot()
end
if camera.shutter.apex > base_tv
then
camera.shutter.apex = base_tv
my_shoot()
end
-- check_lens_ready()
else -- take a single image
camera.shutter.apex = base_tv
my_shoot()
end
else -- take some LE bracketing images
for i = 1, LE_brackets , 1
do
my_shoot()
end
end
end
function move(direction)
-- Note direction not used in this version
local lens_ok = true
local a1 = 0
local b1 = 0
local a2 = my_lens_dof_near()
local b2 = my_lens_dof_far()
local HFD_reached = false
local infinity_reached = false
local fp = lens.focus_distance
local shift_right = my_lens_hyperfocal()*scriptmenu.submenu["Settings"].submenu["Far QHFD shift"].value
local shift_left = my_lens_hyperfocal()/2
repeat -- wait just in case LV not running
msleep(200)
until lv.running
if scriptmenu.submenu["Focus bracketing?"].value == "QHFD" then -- take a two focus brackets either side of the HFD. First go to HFD/2:
if lens.focus_distance < shift_left then
repeat
lens_ok = lens.focus(-1,1,true)
until lens.focus_distance > shift_left
elseif lens.focus_distance > shift_left then
repeat
lens_ok = lens.focus(1,1,true)
until lens.focus_distance < shift_left or not lens_ok
end -- as lens positioned at start of QHFD focus bracket set
take_brackets() -- then move to the other side of the HFD position
repeat
lens_ok = lens.focus(-1,1,true)
until lens.focus_distance > shift_right
take_brackets() -- to complete the QHFD two image bracket set
else -- do one of the other focus bracket requests
repeat
a1=a2
b1=b2
repeat
if scriptmenu.submenu["Focus bracketing?"].value == "FP2HFD" and fp > shift_right then
HFD_reached = true
break
end
if fp > infinity then
infinity_reached = true
break
end
lens_ok = lens.focus(-1,2,true) -- move large steps forward
b2 = my_lens_dof_far()
a2 = my_lens_dof_near()
fp = lens.focus_distance
until (a2 >= b1)
repeat
if (a2 < b1) then break end
lens.focus(1,1,true) -- move small steps back
b2 = my_lens_dof_far()
a2 = my_lens_dof_near()
fp = lens.focus_distance
until (a2 < b1)
if scriptmenu.submenu["Focus bracketing?"].value == "FP2HFD" and fp > shift_right then -- uses 2x or 3x HFD for best results
HFD_reached = true
repeat lens.focus(1,1,true) until (lens.focus_distance < shift_right )
repeat lens.focus(-1,1,true) until lens.focus_distance >= shift_right
else
HFD_reached = false
end
if fp > infinity then
infinity_reached = true
repeat lens.focus(1,1,true) until (lens.focus_distance < infinity )
repeat lens.focus(-1,1,true) until lens.focus_distance >= infinity -- move one step to infinity
else
infinity_reached = false
end
take_brackets()
until (HFD_reached or infinity_reached)
end
end
function albs()
if brackets_requested and scriptmenu.submenu["LE mode?"].submenu["select"].value == 0 then
if factor == 3 or factor == 4 then -- calculate the shadow end of the bracket set
find_ETTL()
factor = factor - 2
end
find_ETTR() -- calculate the highlight end of the bracket set
calc_brackets() -- number of exposure brackets
else
msleep(scriptmenu.submenu["Script delay"].value*1000) -- delay in ms
end
if no_error then
check_bookend()
if scriptmenu.submenu["Settings"].submenu["Dual?"].value == "On" then menu.set("Expo","Dual ISO",1) end
-- Get DoF info & take first (only) exposure(s)
if scriptmenu.submenu["Focus bracketing?"].value ~= "QHFD" then
take_brackets()
end
-- Take additional focus brackets if requested
if scriptmenu.submenu["Focus bracketing?"].value ~= "no" then
move (0)
end
if scriptmenu.submenu["Settings"].submenu["Dual?"].value == "On" then menu.set("Expo","Dual ISO",0) end
check_bookend()
end
reset() -- do some tiding up beforing exiting
end
function setup()
-- Start the main script running and do some error checking
menu.close()
factor = scriptmenu.submenu["Exp Ev option"].value -- exposure bracketing options
if not lv.enabled then lv.start() end -- just in case
-- Do some checking
if camera.model_short == "EOSM" then
no_error = false
beep (3, 200 , 1000)
display.notify_box("Sorry doesn't (yet) work with the EOSM", 3000)
msleep(3000)
reset()
elseif scriptmenu.submenu["Focus bracketing?"].value == "FP2HFD" and lens.focus_distance > my_lens_hyperfocal() then
no_error = false
beep (3, 200 , 1000)
display.notify_box("Warning: FP beyond HFD", 3000)
msleep(3000)
reset()
elseif scriptmenu.submenu["Focus bracketing?"].value == "FP2INF" and lens.focus_distance > infinity then
no_error = false
beep (3, 200 , 1000)
display.notify_box("Warning: FP at or beyond infinity", 3000)
msleep(3000)
reset()
elseif my_lens_dof_far() == my_lens_dof_near() then
no_error = false
beep (3, 200 , 1000)
display.notify_box("Check ML setting for CoC", 3000)
msleep(3000)
reset()
elseif math.abs(my_lens_dof_near()-my_lens_dof_far()) < 10 then -- adjust this test to your taste (default is 10mm)
-- the above test is there to handle long FL cases, as the DoF.near and Dof.far may be too close together
-- which can throw out the script.
-- So don't use a long lens ;-)
display.notify_box("Warning: Change lens FL or focus point", 3000)
reset()
else -- Good to go :-)
check_LE()
if factor ~= 0 then brackets_requested = true else brackets_requested = false end
if running -- then script has already been used since camera switched on
then -- reset some stuff...just to be sure :-)
no_error = true
end
if scriptmenu.submenu["Focus bracketing?"].value ~= "no" then -- focus stacking requested
if lens.focus_distance ~= 0 and lens.af -- lens is OK
then -- fit to run the main function
base_tv = camera.shutter.apex
albs()
else -- lens not OK!
beep (3, 200 , 500) -- warning message
display.notify_box("Check your lens", 5000)
end
else -- 'just' use in non-focus stacking mode
base_tv = camera.shutter.apex
albs()
end
end
end
function hfd_count() -- estimates number of focus brackets to HFD
local s = lens.focus_distance
local H = my_lens_hyperfocal()
local dof_f = my_lens_dof_far()
local count = 1
local df = 0
local s2 = 0
if s > H then s = 0.999*H end
repeat
count = count + 1
df = (H*s)/(H-s)
s2 = (df*H)/(H-df)
s = s2
until s > H or s < 0
return count
end
scriptmenu = menu.new
{
parent = "Shoot", -- change this to position the script into other ML menu locations
name = "Auto Bracketing",
help = "Remember: set LV, DoF & AF on",
depends_on = DEPENDS_ON.LIVEVIEW,
warning = function(this)
if menu.get("Expo","Auto ETTR") == 0 then return "ETTR switched off" end
end,
submenu =
{
{
name = "Script Control",
help = "Does what it says",
warning = function(this)
if menu.get("Expo","Auto ETTR") == 0 and not lv.enabled then return "ETTR and LV switched off" end
if not lv.enabled then return "LV not enabled" end
if menu.get("Expo","Auto ETTR") == 0 then return "ETTR switched off" end
end,
submenu =
{
{
name = "Run Script",
select = function(this)
if menu.get("Expo","Auto ETTR") ~= 0 and lv.enabled and key.last == KEY.SET then task.create(setup) end
end,
help = "Runs the main script",
},
{
name = "Turn Script Off",
select = function(this)
event.keypress = nil
menu.close()
end,
help = "Simply ensures key presses are returned to normal",
},
},
},
{
name = "Focus bracketing?",
help = "Select FP2HFD option to estimate number of focus brackets to HFD",
choices = {"no","FP2INF","FP2HFD","QHFD"},
icon_type = ICON_TYPE.BOOL,
warning = function(this)
if scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 then
this.value = "no"
return "LE mode on"
end
end,
rinfo = function(this)
if scriptmenu.submenu["Focus bracketing?"].value == "FP2HFD" and lens.focus_distance < my_lens_hyperfocal()
then
return hfd_count() end
end,
},
{
name = "Exp Ev option",
help = "Auto exposure bracketing. 1Ev & 2Ev are from user set shadow exposure",
help2 = "If Auto 1Ev & Auto 2Ev selected bracketing is fully auto",
min = 0,
max = 4,
icon_type = ICON_TYPE.BOOL,
warning = function(this)
if scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 then
this.value = 0
return "LE mode on"
end
end,
rinfo = function(this)
if this.value == 0 then return "Off"
elseif this.value == 1 then return "1Ev"
elseif this.value == 2 then return "2Ev"
elseif this.value == 3 then return "Auto 1Ev"
elseif this.value == 4 then return "Auto 2Ev"
end
end,
},
{
name = "LE mode?",
rinfo = function(this)
if scriptmenu.submenu["LE mode?"].submenu["select"].value == 0
then return "Off"
else return "On"
end
end,
help = "In seconds or frames. Zero = no LE bracketing",
help2 = "LE or frame bracketing trumps all other bracketing modes",
submenu =
{
{
name = "select",
min = 0,
max = 300,
icon_type = ICON_TYPE.BOOL,
rinfo = function(this)
if scriptmenu.submenu["LE mode?"].submenu["Mode?"].value == "time" then return "secs" else return "frames" end
end,
select = function(this,delta)
this.value = this.value + delta
if this.value > this.max then
this.value = this.min
elseif this.value < this.min then
this.value = this.max
end
end,
warning = function(this)
if scriptmenu.submenu["LE mode?"].submenu["Mode?"].value == "time" then
LE_brackets = math.ceil(scriptmenu.submenu["LE mode?"].submenu["select"].value/camera.shutter.value)
if LE_brackets == 0 then
return "LE bracket taking switched off"
else
return "Note: "..tostring(LE_brackets).." brackets will be taken"
end
end
end
},
{
name = "Mode?",
choices = {"time","# frames"},
help = "In seconds or #frames. Zero = no LE bracketing"
},
{
name = "Plus",
icon_type = ICON_TYPE.BOOL,
choices = {"Nothing","Dual-ISO","Auto Bracketing"},
help = "Takes an addition image or images, on top of the LE sequence"
}
}
},
{
name = "Bookends?",
help = "Places a dark frame at start and end of focus bracket set",
choices = {"no","yes"},
icon_type = ICON_TYPE.BOOL,
},
{
name = "Script delay",
help = "Delays script start by stated number of secs",
min = 0,
max = 5,
icon_type = ICON_TYPE.BOOL,
select = function(this,delta)
if this.value == 0 and delta == 1 then this.value = 2
elseif this.value == 0 and delta == -1 then this.value = 5
elseif this.value == 2 and delta == 1 then this.value = 5
elseif this.value == 2 and delta == -1 then this.value = 0
elseif this.value == 5 and delta == 1 then this.value = 0
elseif this.value == 5 and delta == -1 then this.value = 2
end
end
},
{
name = "Settings",
help = "Various settings for the script",
submenu =
{
{
name = "ETTR Set Time?",
help = "A camera specific variable in seconds",
help2 = "Change if ETTR fails to find a solution",
min = 2,
max = 8,
value = 4 -- this works on a 5D3
},
{
name = "Shadow SNR",
help = "Signal-2-Noise ratio in Ev: 4-6 is the sweet spot",
min = 2,
max = 6,
value = 6
},
{
name = "Dual?",
help = "Dual-ISO On or Off",
choices = {"Off","On"},
icon_type = ICON_TYPE.BOOL,
},
{
name = "Far QHFD shift",
help = "2 or 3 x HFD",
min = 2,
max = 3,
value = 2
},
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment