Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Get Focus Bracket (ML)
--[[
Get Focus Brackets
This is a Lua 'simple script' to capture 'perfect' focus brackets from the current focus position to a defocus blur defined infinity.
I wrote it to cover auto capture, Multi-Image, Deep Field photography needs.
This version includes a modified split thin lens model, which better matches the real world.
There is no menu: just run the script from the ML Scripts menu.
The UI is simple, but functional: just follow the on screen instructions. The script pauses to allow you to decide if you wish to complete, ie in case
there are too many brackets. During the pause you can refocus, change aperture and focal length, ie to change the number of brackets.
During the pause the SET key can be used, ie for ETTRing. A long half shutter will take the brackets, any other key, including a half shutter press, but other than SET, will terminate the script.
The long half shutter (>2s) is indicated by a color change in the UI’s text, to green.
The script will display the estimated the number of brackets. The actual number may be different by +/- 1. That is the estimate assumes perfectly touching brackets, something that the Canon
lens control can't achieve.
Script needs to run in LV, AF mode and M Mode. Script will switch to LV if called outside of LV.
The script displays the defocus, diffraction and total blurs at infinity. Remember ML shows you diffraction aware DoFs ;-)
If the current position is greater than the hyperfocal, then additional brackets will be taken, eg at H/3, H and 4*H, giving a 3 image depth of focus of H/4 to (4*H) infinity.
Set the ML CoC to the required bracket to bracket defocus blur overlap, ie focus insurance, but, sensibly, don't go below, say, 15/Crop.
Reducing the ML CoC below the 'normally recognised' CoC, eg 30/Crop , will result in more brackets, but higher focus quality through the scene.
Consider CoCs less than 2*sensor_pixel_size the minimum
The UI provides colour feedback: red means you are focused short of H (with H based on the ML CoC, ie the overlap blur); yellow means you are focused between H and 2*H;
In addition the infinity defocus blur and diffraction blur are shown as a % of the total
Green means you are between 2*H and 4*H; and orange beyond 4*H
To enable exposure brackets, simply turn on ML's Advanced Bracketing.
The algorithm used is OK for non-macro use, ie 'normal' lenses, and works best with WA lenses.
Two (dark exposure) bookend images are taken, one at the beginning and one at end of bracketing, to help differentiate the bracket set in post. Can be switched off by changing line 37.
The script is run from the ML Scripts menu or be triggered by the (separate) helper script.
***WARNING*** Some lenses may fail with this script: sorry.
Tested on 5D3 with an EF 24-105 F4/L & Sigma 12-24 F4
Add your lens below
Please let me know if you have any issues or feedback.
Release 1.6
May 2019
Garry George
photography.grayheron.net
--]]
direction = 1
bookends = true
x = 0
start_x = 0
H = 0
inf = 650000
m = 0
f = 0
a = 0
last_key = key.last
ML_blur = menu.get("DOF Settings","Circle of Confusion",0)
infinity_blur = 0
diff_blur = 0
total_blur = 0
blurs = ""
timer = 0
trigger_time = 0
timer_on = false
num = 3
last_f = 0
last_x = 0
last_a = 0
last_v = 0
dirty = true
finish = false
mfd = 0
max_fl = 0
max_mag = 0
lens_thickness = 0
lens_exists = false
v = 0 -- lens to object distance = x - lens_thickness - (1+m)*f
-- Lens info: just add your lens to the list. Mag and FL are those at the maximum FL of a zoom. Switch on the commented-out code in the capture_brackets function, to get your lens name etc
-- That is, switch on the code, run the script and do a half shutter to exit the script.
lens_info =
{
{name = "EF24-105mm f/4L IS USM", mag = 0.23, fl = 105, mfd = 450, dir = -1},
{name = "12-24mm", mag = 0.17, fl = 24, mfd = 280, dir = -1},
}
function myround(nu,dp)
-- nu = a number, dp = decimal places required to show
-- returns number as a string
if dp == 0 then
nu = tostring(math.floor(nu))
else
nu = tostring(math.floor(nu)).."."..string.sub(tostring(math.floor(num*10^dp)),-dp,-1)
end
return nu
end
function my_shoot()
camera.shoot()
camera.wait()
end
function box()
display.rect(40, 150, 550, 135, COLOR.BLACK, COLOR.BLACK)
display.rect(43, 153, 546, 129, COLOR.BLACK, COLOR.TRANSPARENT_BLACK)
end
function lens_zero(xx)
lens_thickness = mfd - ((f*max_mag + max_fl)^2)/(max_fl*max_mag) -- the virtual thickness of the lens, ie between principal planes - could be negative!
xx = lens_thickness + (1 + max_mag*(f/max_fl)*(mfd/xx))*f
return xx
end
function my_display(text,time,x_pos,y_pos)
text = string.rep("\n",y_pos)..string.rep(" ",x_pos)..text
display.notify_box(text,time*1000)
end
function set_focus(xx)
local lens_ok = true
repeat sleep(0.1) until lv.running
local current_x = lens.focus_distance
if xx == current_x then return xx end
if xx > current_x then
while lens.focus_distance < xx and lens_ok do
lens_ok = lens.focus(direction,2,true)
end
sleep(0.1)
lens_ok = true
while lens.focus_distance > xx and lens_ok do
lens_ok = lens.focus(-direction,1,true)
end
else
while lens.focus_distance > xx and lens_ok do
lens_ok = lens.focus(-direction,2,true)
end
sleep(0.1)
lens_ok = true
while lens.focus_distance < xx and lens_ok do
lens_ok = lens.focus(direction,1,true)
end
end
return lens.focus_distance -- final focus distance, ie may not be extactly x ;-)
end
function calibrate()
f = lens.focal_length
x = lens.focus_distance
a = camera.aperture.value
start_x = x
for i = 1, #lens_info, 1
do
if lens.name == lens_info[i].name then
max_mag = lens_info[i].mag
max_fl = lens_info[i].fl
mfd = lens_info[i].mfd
direction = lens_info[i].dir
lens_exists = true
end
end
if not lens_exists then
direction = 1
start_x = lens.focus_distance
local lens_ok = lens.focus(1,3,true) -- test lens chirality
sleep(0.2)
if lens.focus_distance < start_x then
direction = -direction
elseif lens.focus_distance == start_x and start_x < inf then
direction = -direction
end
lens_ok = set_focus(start_x)
display.print("Need to register lens",50,160,FONT.SANS_32)
display.print("Lens name:"..lens.name,50,200,FONT.SANS_32)
display.print("Direction: "..tostring(direction),50,240,FONT.SANS_32)
display.print("Press halfshutter to clear",50,280,FONT.SANS_32)
key.wait(KEY.HALFSHUTTER)
display.clear()
end
if lens_exists then
lens_thickness = mfd - ((f*max_mag + max_fl)^2)/(max_fl*max_mag) -- the virtual thickness of the lens, ie between principal planes - could be negative!
end
dirty = true
end
function bookend()
local test = menu.get("Shoot","Advanced Bracket",0)
if test == 1 then
menu.set("Shoot","Advanced Bracket",0)
repeat sleep(0.1) until menu.get("Shoot","Advanced Bracket",0) == 0
end
local test2 = menu.get("Expo","Dual ISO",0)
if test2 == 1 then
menu.set("Expo","Dual ISO",0)
repeat sleep(1) until menu.get("Expo","Dual ISO",0) == 0
end
local t = camera.shutter.apex
camera.shutter.apex = 10
my_shoot()
camera.shutter.apex = t
if test == 1 then
menu.set("Shoot","Advanced Bracket",1)
repeat sleep(0.1) until menu.get("Shoot","Advanced Bracket",0) == 1
end
if test2 == 1 then
menu.set("Expo","Dual ISO",1)
repeat sleep(0.1) until menu.get("Expo","Dual ISO",0) == 1
end
end
function update()
last_f = f
last_x = x
last_a = a
lens_thickness = mfd - ((f*max_mag + max_fl)^2)/(max_fl*max_mag) -- the virtual thickness of the lens, ie between principal planes - could be negative!
dirty = false
H = f + (1000*f*f)/(a*ML_blur)
box()
display.print("Estimate "..tostring(num).." brackets",50, 200,FONT.SANS_32,COLOR.TRANSPARENT_BLACK,COLOR.TRANSPARENT_BLACK)
num = 1 + math.ceil((1 + (H/(x-lens_zero(x))))/2)
if num < 3 then num = 3 end
display.print("Estimate "..tostring(num).." brackets",50, 200,FONT.SANS_32,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
infinity_blur = ML_blur*H/(x-lens_zero(x))
diff_blur = 2.44*a*0.550 -- assumes you are shooting in the visible bands and not IR
total_blur = math.sqrt(infinity_blur*infinity_blur + diff_blur*diff_blur)
display.print("Def/Dif/Tot Blurs: "..blurs.." um" ,50, 160,FONT.SANS_32,COLOR.TRANSPARENT_BLACK,COLOR.TRANSPARENT_BLACK,540)
blurs = myround(infinity_blur,0).."/"..myround(diff_blur,0).."/"..myround(total_blur,0)
display.print("Def/Dif/Tot Blurs: "..blurs.." um",50, 160,FONT.SANS_32,COLOR.WHITE,COLOR.TRANSPARENT_BLACK,540)
if infinity_blur > ML_blur then
display.rect(400, 240, 150, 30, COLOR.BLACK, COLOR.RED)
elseif infinity_blur > ML_blur/2 and infinity_blur < ML_blur then
display.rect(400, 240, 150, 30, COLOR.BLACK, COLOR.YELLOW)
elseif infinity_blur < ML_blur/2 and infinity_blur > ML_blur/4 then
display.rect(400, 240, 150, 30, COLOR.BLACK, COLOR.GREEN1)
elseif infinity_blur < ML_blur/4 and x < inf then
display.rect(400, 240, 150, 30, COLOR.BLACK, COLOR.ORANGE)
elseif x > inf then
display.rect(400, 240, 150, 30, COLOR.BLACK, COLOR.WHITE)
end
if timer <= 2.0 then
display.print("Long Half Shutter if OK",50,240,FONT.SANS_32,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
else
display.print("Long Half Shutter if OK",50,240,FONT.SANS_32,COLOR.GREEN1,COLOR.TRANSPARENT_BLACK)
end
-- display infinity defocus and diffraction blurs as a percentage of total
local percent1 = (infinity_blur/total_blur)*150
display.rect(400, 245, percent1, 10, COLOR.DARK_GRAY, COLOR.DARK_GRAY)
local percent2 = (diff_blur/total_blur)*150
display.rect(400, 255, percent2, 10, COLOR.GRAY, COLOR.GRAY)
end
function capture_brackets()
if menu.get("Get Focus Brackets","Autorun",0) == 1 then
menu.set("Get Focus Brackets","Autorun",0)
return
end
if not lv.running then lv.start() end
repeat sleep(0.1) until lv.running
if not (camera.mode == MODE.M) then my_display("Must be in M Mode",2,0,5) return end
if menu.visible then menu.close() end
if console.visible then console.off() end
if not lens.af then my_display("Switch AF on",2,0,5) return end
calibrate()
if not lens_exists then return end
--[[
Note: all calculations are relative to the lens principal plane, which we don't know for real: so we assume a thin (or quasi thick) lens for simplicity
All moves are realtive to the sensor plane and Canon/ML tells us this!
Also: any change to focus will result in the bracketing starting at the new focus position, ie not at the position when the script started.
--]]
display.pixel(30,200,COLOR.RED)
repeat -- note SET and HALFSHUTTER keys are reserved/protected
f = lens.focal_length
x = lens.focus_distance
a = camera.aperture.value
last_key = key.last
if display.pixel(30,200) ~= COLOR.RED then
dirty = true
display.pixel(30,200,COLOR.RED)
end
if last_a ~= a or last_x ~= x or last_f ~= f or dirty or timer_on then -- need to update UI
display.draw(update)
end
if key.last == KEY.HALFSHUTTER and not timer_on then
trigger_time = dryos.clock
timer_on = true
end
if key.last == KEY.UNPRESS_HALFSHUTTER then
timer_on = false
finish = true
end
if timer_on then timer = dryos.clock - trigger_time end
if (key.last == KEY.SET or key.last == KEY.UNPRESS_SET) then
dirty = true
end -- ETTR so update UI
sleep(0.2) -- refresh delay
until (last_key ~= key.last and (not (key.last == KEY.SET or key.last == KEY.UNPRESS_SET)) and (not (key.last == KEY.HALFSHUTTER or key.last == KEY.UNPRESS_HALFSHUTTER))) or finish
display.clear()
if timer <= 2 then
my_display("Finished",2,0,0)
return end
if bookends then bookend() end
f = lens.focal_length
x = lens.focus_distance
a = camera.aperture.value
H = f + (1000*f*f)/(a*ML_blur) -- relative to the (modified) thin lens' principal plane (zero), ie not the sensor
start_x = x
v = x - lens_zero(x) -- Thin/Thick lens estimate of distance between lens zero and the object
local take_3 = false
if v > H/2 then take_3 = true end
local new_x = x
while v < H/3 do
x = set_focus(new_x)
my_shoot()
v = x - lens_zero(x)
new_x = lens_zero(x)+v+(2*(f-v)*(f-v))/(f+H-2*v) -- position of next focus, based on Merklinger's formulation of DoF
end
if (x - lens_zero(x)) <= H/3 or take_3 then -- reasonable to ignore magnification from now on
new_x = f + lens_thickness + H/3
x = set_focus(new_x)
lens.focus(direction,1,true)
x = lens.focus_distance
my_shoot()
end
if (x - lens_zero(x)) <= H or take_3 then
new_x = f + lens_thickness + H
x = set_focus(new_x)
lens.focus(direction,1,true)
x = lens.focus_distance
my_shoot()
end
new_x = f + lens_thickness + 4*H
x = set_focus(new_x)
lens.focus(direction,1,true)
my_shoot()
if bookends then bookend() end
end
if menu.get("Focus","Bracketeer","") == "ON" then return end
capture_brackets()
set_focus(start_x)
my_display("Finished",2,0,0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.