Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
FOCUS
--[[
Focus Bar Script (Version 1.965)
This script provides a focus bar functionality that allows visualisation of the focus/defocus field.
This version should work on all ML enabled cameras with the latest Lua version: as of the release date below.
Switch off ML DoF display, ie this script generates all DoFs, and displays them.
The lens must report distance and LV needs to be on: but doesn't need to be an AF lens.
Check the ML CoC (total blur) is set for your needs. The script uses this value.
Script has been tested on a 5D3 and EOSM.
The near field DoF distance shown meets the total (diffraction (if set) + defocus) blur at infinity.
In DoF mode, if the far DoF blur feedback at infinity turns red, you are beyond the 2xsensor pixel limit. If the whole bar turns red, then you are focusing beyond infinity and need to back off.
Note that there is a linear relationship between total blur and the achievable print quality. A typical blur of, say, 30 microns on a Full Frame (reduce by the crop on a cropped sensor) is OK for
screen presentation. If printing this equates to about 5 lp/mm at arms distance away. For high quality printing you should be seeking to multiple the lp/mm by 1.5 or 2, ie halfing the blur to, say, 15 microns.
For very large prints, viewd at great distances, the lp/mm can be reduced.
For 'normal' viewing, aim for total (sic) far DoF blurs at infinity of lower than 30 (/crop), but not lower than, say, 15 (/crop).
Also note that diffraction blur can be approximated as 'flat' over the entire DoF and, to some extent, you might be able to reduce it in post, albeit with the risk of some artifacts.
The defocus blur varies over the scene and is only zero at the plane of focus. This can't be reduced in post, ie you need to get it right in camera.
The focus bar's response is changed via the following parameters:
- Unity Blur Multiplier: controls the max of the focus bar gray zone, ie multiplier of 5 = 5 x the ML set blur value
- Unity Blur: Optical or with diffraction
- Focus Bar Multiplier: this allows you to stretch out the zone of interest of the end of the focus bar, ie a multiplier of 1 = zero to far-DoF. A multiplier of > 1
means zoom out. The distance of the zoom bar into the field is shown in meters. The start of the focus bar is as defined, eg zero of near depth of field.
Note a setting of 0 represents a special and useful mode. In the zero mode the end of the focus bar is set to the focus point and the start to zero, ie the sensor plane.
A setting of -1 switches the bar to DoF mode. This is the default mode.
A setting of -2 forces the use of the start and finish distances in the function call: but this is not accessible via the script's menu.
A useful thing to note is that the Focus Bar works in Canon zoom mode :-) Thus DoF mode (-1) and zoomed in may be useful for some capture workflows.
If the far DoF goes beyond the HFD, ie you move into infinity focusing mode, and the far DoF scale will switch from showin a distance,
to show the total blur (optical + diffraction, if switched on) and the near scale will switch to show the near DoF distance based on the infinity blur option selected in the menu,
eg defocus, 2xdefocus, total or 2xtotal.
Thus you have all the data you need for tack sharp capture, eg:
- Toal blur info (in microns) at infinity, ie when focused beyond the HFD
- Focus bar showing an impression of the (optical) focus field from the camera to the focus point
- In DoF mode an impression of the blur diameters as they vary between the DoFs
Blurs in the focus bar greater or less than unity (referenced to the ML setting of blur or CoC) are shown in gray, blurs at unity are black.
Blurs in the focus bar above or below unity are also shown in grey.
Blurs in the focus bar above the blur max you set are red.
The generic layout of the focus bar looks like this:
[X][--R--][W--G--B][NDoF][B--W][FP][W--B][FDoF][B--G--W][--R--][Y]
W=White, B=Black, G=Grey, R=Red, FP=Focus Plane, X=focus bar start distance, Y=focus bar end distance or info
Although at first the 'continuuum of grey' may look confusing, all is clear once you use the near and far DoFs (green dot) and the focus point (also Green, but a different shade) as your references.
The blur data at infinity can be switched off so you just have the focus bar showing.
NOTE: Change the script's menu settings to create your 'defaults at start up'.
In focus stacking mode the DoFs at the bottom show the DoF of the last image taken. These will be 0 at camera start up, ie until an image is taken.
The number inbetween the near and far DoFs, at the bottom of the screen, is the defocus blur, ie the ML set CoC/blur criterion less the effect of diffraction (as set in the ML menu (sic))
To repeat: for the landscape photographers (or anyone) I recommend the following ML seetings:
- Set the script's diffraction aware on and DoF visible, thus the green dots are providing you info on the DoF with diffraction.
- Switch ML DoF display to off, but the ML diffraction aware on.
Bottom line: try and minimise the total blur in camera if you can: post processing wil corrupt your captured data ;-)
Garry George July 2017
http://photography.grayheron.net/
--]]
-- Declare some globals
hidden = false
CON = "ON"
COFF = "OFF"
options = {"Defocus","2xDefocus","Total","2xTotal"}
last_ndof = 0
last_fdof = 0
current_ndof = 0 -- at the time a full press is made
current_fdof = 0
last_dof_flag = false
inf = 999999
--** The following two functions, if used in another script, must be used together, with the added global hidden = false variable **
function myround(num,dp)
-- Used by function focus_bar()
if dp == 0 then
local int = math.floor(num+0.5)
return tostring(int)
else
local int = math.floor(num)
local frac = math.floor((num-int)*(10^dp))
return tostring(int).."."..tostring(frac)
end
end
function focus_bar(showing,start,finish,info,b_max,zoom,diff)
--[[
Focus Bar Function (Version 1.0) : used to show a linear-distance-scale (sic) representation of the focus field
***********************************************************************************
* Note: Focus Bar Function requires global variable to be defined: hidden = false *
* Note: Focus Bar Function also uses function myround *
***********************************************************************************
showing = focus bar visible : true or false
start = left hand real world end of focus bar, eg 0 or lens.dof_near or any scene depth you are interested in
finish = right hand real world end of focus bar, eg lens.dof_far or lens.focus_distance or any scene depth you are interested in
info = distance info visible : true or false
b_max = max blur, relative to unity blur (optical or with diffraction), ie start of red zone. 0 = FP mode
zoom = zoom far focus bar out. Zoom is used like this : end of focus bar = (zoom-1)*(dof_far-focus_distance) + dof_far
diff = diffraction on or off : true or false
The lens must report distance and LV needs to be on.
Has been tested on a 5D3 and EOSM.
Garry George June 2017
http://photography.grayheron.net/
--]]
local message = ""
local b_inf = 0
local b_total = 0
local b_near = 0
local b_near2 = 0
local b_near_tot = 0
local b_near_tot2 = 0
local b_show = 0
local sensor = 4.29 -- change this to match your camera, ie 2 x sensor pixel pitch or pixel size. EOSM = 4.29. 5D3 = 6.3
local b_limit = sensor*2 -- smallest total blur that is sensible to aim for
local inf = 999999 -- Trap for ML reported infinity in mm - used for DoFs
local fp_inf = 655000 -- Trap for Canon lens reported infinity in mm - used for FP
local focus_distance = lens.focus_distance -- lock the next five varables in together in time
local dof_near = 0
local dof_far = 0
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: used a focus bar
local fb_blur = unity_blur
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)
if diff then -- calculate defocus blur criterion for focus bar without diffraction
fb_blur = math.sqrt(unity_blur*unity_blur - b_diff_fp*b_diff_fp)
end
local d = focal_length/aperture_value -- diameter of aperture
local focus_bar_ndof = d*focal_length*focus_distance
focus_bar_ndof = focus_bar_ndof/(fb_blur*(focus_distance-focal_length)/1000+d*focal_length)
local focus_bar_fdof = -1*d*focal_length*focus_distance
focus_bar_fdof = focus_bar_fdof/(fb_blur*(focus_distance-focal_length)/1000-d*focal_length)
--************************************************************
-- This replaces the ML calculated DoF: Make sure you switch off the DoF display in ML
if menu.get("DOF Settings","DOF formula") == 1 then
ml_blur = math.sqrt(unity_blur*unity_blur - b_diff_fp*b_diff_fp)
end
local dof_near = d*focal_length*focus_distance
dof_near = dof_near/(ml_blur*(focus_distance-focal_length)/1000+d*focal_length)
local dof_far = -1*d*focal_length*focus_distance
dof_far = dof_far/(ml_blur*(focus_distance-focal_length)/1000-d*focal_length)
if dof_far >= inf or dof_far < 0 then dof_far = inf end
current_fdof = dof_far
current_ndof = dof_near
--************************************************************
if focus_bar_fdof > inf or focus_bar_fdof < 0 then focus_bar_fdof = inf end
local pos_x = 10
local pos_y = 40
local num_steps = 233 -- tunable but note you only have 700 LV pixels to play with. 100 is good. 233 is a max ;-)
local h = 11 -- focus bar height
local w = 3 -- 700.0/(num_steps) -- focus bar width = 700
local x_end = finish
local x_start = start
local x_step = 0
local temp = 0
if zoom == 0 then -- configure focus bar to show FP on the right
x_start = 0
x_end = focus_distance
elseif zoom == -1 then -- configure for DoF mode
x_start = focus_bar_ndof
x_end = focus_bar_fdof
elseif zoom == -2 then
x_start = start
x_end = finish
else -- configure for 0 to dof_far*multiplier mode
x_start = 0
if focus_bar_fdof < inf then
x_end = (zoom-1)*(focus_bar_fdof-focus_distance) + focus_bar_fdof
if x_end > inf then x_end = inf end
else
x_end = focus_bar_fdof
end
end
if (menu.visible or camera.state ~= 0) or (not showing) or (not display.idle) or (lv.paused) then -- don't show bar
-- you may see some 'strangeness' in the LV behaviour when switching between modes. Sorry ;-)
if not hidden then
display.clear()
end
hidden = true -- now hidden so don't need to keep hiding'
else
hidden = false -- show the focus bar
b_inf = 1000*focal_length*focal_length -- Defocus blur at infinity
b_inf = b_inf/aperture_value
b_inf = b_inf/(focus_distance - focal_length)
b_total = math.sqrt(b_inf*b_inf + b_diff*b_diff) -- Total blur in quadrature at infinity
b_near = (b_inf*aperture_value/1000.0)/(focal_length*focal_length) -- near DoF using defocus blur at infinity
b_near = b_near*(focus_distance-focal_length)
b_near = focus_distance/(b_near+1)
b_near2 = (2*b_inf*aperture_value/1000.0)/(focal_length*focal_length) -- near DoF using twice the defocus blur at infinity
b_near2 = b_near2*(focus_distance-focal_length)
b_near2 = focus_distance/(b_near2+1)
b_near_tot = (b_total*aperture_value/1000.0)/(focal_length*focal_length) -- near DoF using total blur at infinity
b_near_tot = b_near_tot*(focus_distance-focal_length)
b_near_tot = focus_distance/(b_near_tot+1)
b_near_tot2 = (2*b_total*aperture_value/1000.0)/(focal_length*focal_length) -- near DoF using twice the total blur at infinity
b_near_tot2 = b_near_tot2*(focus_distance-focal_length)
b_near_tot2 = focus_distance/(b_near_tot2+1)
x_step = (x_end - x_start)/num_steps
local x = 0 -- point of interest in the scene
local b_x = 0 -- blur at x
local percent = 0 -- % blur relative to blur max and unity blur
local old_pos = 5 -- used to manage points
local old_pos2 = 5
local old_pos3 = 5
local wx = 0
b_max = b_max * unity_blur -- entering the red zone
display.circle(5, pos_y+6, 5, COLOR.TRANSPARENT,COLOR.TRANSPARENT) -- clean up just in case
display.circle(715, pos_y+6, 5, COLOR.TRANSPARENT,COLOR.TRANSPARENT)
for i = 1, num_steps, 1
do
wx = 10 + math.floor(w*(i-1))
x = x_start + x_step*i - x_step/2
b_x = (focus_distance - x) / ((focus_distance - focal_length)*x)
b_x = (1000*focal_length*focal_length/aperture_value)*math.abs(b_x)
if b_x >= b_max or (dof_near == dof_far) or focus_distance >= fp_inf then
display.rect(wx, pos_y, w, h, COLOR.RED, COLOR.RED)
elseif b_x < b_max and b_x > unity_blur then
percent = math.floor(100*(b_x - unity_blur)/(b_max-unity_blur))
if percent < 0 then percent = 0 end -- just in case
if percent > 100 then percent = 100 end -- just in case
display.rect(wx, pos_y, w, h, COLOR.gray(percent), COLOR.gray(percent))
elseif b_x <= unity_blur then
percent = math.floor(100*(unity_blur - b_x)/(unity_blur))
if percent < 0 then percent = 0 end -- just in case
if percent > 100 then percent = 100 end -- just in case
display.rect(wx, pos_y, w, h, COLOR.gray(percent), COLOR.gray(percent))
end
if zoom == -1 and Focus_Bar_Menu.submenu["Focus Stacking"].value == "ON" then
if (dof_near < last_ndof ) and (x > last_ndof) then
display.rect(wx, pos_y, w, h, COLOR.MAGENTA, COLOR.MAGENTA)
elseif (dof_far > last_fdof) and (x < last_fdof) then
display.rect(wx, pos_y, w, h, COLOR.MAGENTA, COLOR.MAGENTA)
end
end
end
-- display ML far DoF point
display.circle(old_pos3, pos_y+6, 4, COLOR.TRANSPARENT,COLOR.TRANSPARENT) -- clean up previous points
display.circle(715, pos_y+6, 4, COLOR.TRANSPARENT,COLOR.TRANSPARENT)
display.circle(5, pos_y+6, 4, COLOR.TRANSPARENT,COLOR.TRANSPARENT)
if dof_far < x_end then
pos_x = 10 + math.floor(700*(dof_far-x_start)/(x_end - x_start))
else
pos_x = 715
end
if pos_x >= 710 then pos_x = 715 end
if pos_x <= 10 then pos_x = 5 end
if dof_far > inf then pos_x = 715 end
old_pos3 = pos_x
display.circle(pos_x, pos_y+6, 4, COLOR.GREEN1,COLOR.GREEN1)
-- display focus point
display.circle(old_pos, pos_y+6, 4, COLOR.TRANSPARENT,COLOR.TRANSPARENT)
if focus_distance > x_start then
pos_x = 10 + math.floor(700*(focus_distance-x_start)/(x_end - x_start))
else
pos_x = 5
end
if pos_x > 710 then pos_x = 715 end
if pos_x <= 10 then pos_x = 5 end
if focus_distance > fp_inf then pos_x = 715 end
old_pos = pos_x
display.circle(pos_x, pos_y+6, 4, COLOR.GREEN2,COLOR.GREEN2)
-- display ML near DoF point
display.circle(old_pos2, pos_y+6, 4, COLOR.TRANSPARENT,COLOR.TRANSPARENT)
if dof_near > x_start then
pos_x = 10 + math.floor(700*(dof_near-x_start)/(x_end- x_start))
else
pos_x = 5
end
if pos_x > 710 then pos_x = 715 end
if pos_x <= 10 then pos_x = 5 end
old_pos2 = pos_x
display.circle(pos_x, pos_y+6, 4, COLOR.GREEN1,COLOR.GREEN1)
-- display % marks if in DoF mode
local x = 0
local r = 0
if zoom == -1 then
for i = 1, 9, 1
do
pos_x = 10 + math.floor(700*i/10)
x = x_start + i*(x_end-x_start)/10
b_x = (focus_distance - x) / ((focus_distance - focal_length)*x)
b_x = (1000*focal_length*focal_length/aperture_value)*math.abs(b_x)
r = (h+1)*(b_x/fb_blur)/2
if r == 0 then r=1 end
if b_x > b_limit then
display.circle(pos_x, pos_y+h/2, r, COLOR.BLACK,COLOR.BLACK)
else
display.circle(pos_x, pos_y+h/2, r, COLOR.RED,COLOR.RED)
end
end
end
display.rect(680, 100, 30, 100, COLOR.TRANSPARENT, COLOR.TRANSPARENT) -- clear infinity focus bar
-- display focus bar distance info: first the far field info
if info then
temp = x_end
message = "XXXXXXXXXXXXX"
local qq = FONT.MED:width(message)
display.print(message, 720-qq, 53, FONT.MEDIUM ,COLOR.TRANSPARENT,COLOR.TRANSPARENT)
if dof_far >= inf then
temp = b_total
if dof_far == dof_near then temp = 0 end
if b_total > b_limit then
message = " Inf:"..myround(temp,0).." "
qq = FONT.MED:width(message)
display.print(message, 720-qq, 53, FONT.MEDIUM ,COLOR.GREEN1,COLOR.TRANSPARENT_BLACK)
else
message = " Inf:"..myround(temp,0).." "
qq = FONT.MED:width(message)
display.print(message, 720-qq, 53, FONT.MEDIUM ,COLOR.RED,COLOR.TRANSPARENT_BLACK)
end
x_start = b_near
if Focus_Bar_Menu.submenu["Near DoF Criterion"].value == "Defocus" then
x_start = b_near
elseif Focus_Bar_Menu.submenu["Near DoF Criterion"].value == "2xDefocus" then
x_start = b_near2
elseif Focus_Bar_Menu.submenu["Near DoF Criterion"].value == "Total" then
x_start = b_near_tot
elseif Focus_Bar_Menu.submenu["Near DoF Criterion"].value == "2xTotal" then
x_start = b_near_tot2
end
b_show = x_start
-- ** Focus breakdown at Infinity (Yellow = diffraction, Green or Red = defocus, Blue = total blur, White = total CoC or blur set in ML menu)
display.rect(680, 100, 30, 100, COLOR.BLACK, COLOR.WHITE)
local h = 100*b_total/unity_blur
display.rect(680, 200-h, 30, h, COLOR.BLACK, COLOR.BLUE)
h = 100*b_diff/unity_blur
display.rect(680, 200-h, 30, h, COLOR.BLACK, COLOR.YELLOW)
local hh = 100*b_inf/unity_blur
if b_inf >= b_limit then
display.rect(690, 200-hh, 10, hh, COLOR.BLACK, COLOR.GREEN1)
else
display.rect(690, 200-hh, 10, hh, COLOR.BLACK, COLOR.RED)
end
-- **
else
if dof_far == dof_near then temp = 0 end
if temp < 1000 then
message = " "..myround(temp/10, 0).."cm "
else
message = " "..myround(temp/1000,2).."m "
end
if focus_bar_fdof >= inf then message = "-> infinity" end
qq = FONT.MED:width(message)
display.print(message, 720-qq, 53, FONT.MEDIUM ,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
end
-- Now the near field info
if dof_far > inf then temp = b_show else temp = x_start end
message = "XXXXXXXXXXX"
display.print(message, 20, 53, FONT.MEDIUM ,COLOR.TRANSPARENT,COLOR.TRANSPARENT)
if temp < 1000 then
message = myround(temp/10, 0).."cm "
else
message = myround(temp/1000,2).."m "
end
if dof_far >= inf then
message = " DoF:"..message
if b_total > b_limit then
display.print(message, 0, 53, FONT.MEDIUM ,COLOR.GREEN1,COLOR.TRANSPARENT_BLACK)
else
display.print(message, 0, 53, FONT.MEDIUM ,COLOR.RED,COLOR.TRANSPARENT_BLACK)
end
else
message = " "..message
display.print(message, 0, 53, FONT.MEDIUM ,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
end
-- Now the DoFs (which show the non-infinity DoFs, calculated from the CoC (diffraction corrected or not))
local xpos = 380
message = "XXXXXXXXXXXXXXXXXXXX"
qq = FONT.MED:width(message)
display.print(message, xpos-qq/2, 420, FONT.MEDIUM ,COLOR.TRANSPARENT,COLOR.TRANSPARENT)
if dof_far >= inf then
b_show = b_inf
else
b_show = fb_blur
end
if b_show >= 9.5 then
message = " ("..myround(b_show,0)..") "
else
message = " ( "..myround(b_show,0)..") "
end
qq = FONT.MED:width(message)
display.print(message, xpos-qq/2, 420, FONT.MEDIUM ,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
temp = dof_near
if zoom == -1 and Focus_Bar_Menu.submenu["Focus Stacking"].value == "ON" then temp = last_ndof end
if temp < 1000 then
message = myround(temp/10, 0).."cm"
else
message = myround(temp/1000,2).."m"
end
display.print(message, xpos-qq/2-FONT.MED:width(message), 420, FONT.MEDIUM ,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
temp = dof_far
if zoom == -1 and Focus_Bar_Menu.submenu["Focus Stacking"].value == "ON" then temp = last_fdof end
if temp >= inf
then
message = "Inf"
elseif temp < 1000 then
message = myround(temp/10, 0).."cm"
else
message = myround(temp/1000,2).."m"
end
display.print(message, xpos+qq/2, 420, FONT.MEDIUM ,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
else
display.print("XXXXXXXXXXX", 640, 53, FONT.MEDIUM ,COLOR.TRANSPARENT,COLOR.TRANSPARENT)
display.print("XXXXXXXXXXX", 0, 53, FONT.MEDIUM ,COLOR.TRANSPARENT,COLOR.TRANSPARENT)
end
end
end
function show_focus_bar()
--focus_bar(showing,start,finish,info,b_max,zoom,diff) maps to focus_bar(a,b,c,d,e,f,g)
local a = true
local b = 500 -- Note, although not used in this demo script, function will use explicit distances, b & c, if zoom option is -2
local c = 3000
local d = true
local g = true
if Focus_Bar_Menu.submenu["Focus Bar"].value == "ON" then
a = true
else
a = false
end
if Focus_Bar_Menu.submenu["Show Info"].value == "ON" then
d = true
else
d = false
end
if Focus_Bar_Menu.submenu["Diffraction"].value == "ON" then
g = true
else
g = false
end
local f = Focus_Bar_Menu.submenu["Focus Bar Multiplier"].value
local e = Focus_Bar_Menu.submenu["Unity Blur Multiplier"].value
focus_bar(a,b,c,d,e,f,g)
end
function check_requests(arg)
display.draw(show_focus_bar)
end
function test4dof(k)
if k == KEY.UNPRESS_FULLSHUTTER then -- set the last DoFs to the current DoFs (if requested)
if Focus_Bar_Menu.submenu["Focus Bar Multiplier"].value == -1 and Focus_Bar_Menu.submenu["Focus Stacking"].value == "ON" then
last_fdof = current_fdof
last_ndof = current_ndof
end
end
return true
end
event.shoot_task = check_requests
event.keypress = test4dof
Focus_Bar_Menu = menu.new
{
parent = "Focus",
name = "Focus Bar",
help = "Moves lens to HFD focus distance",
depends_on = DEPENDS_ON.LIVEVIEW,
submenu =
{
{
name = "Focus Bar",
help = "Switches the focus bar display on & off",
choices = {CON,COFF},
},
{
name = "Show Info",
help = "Switches the focus bar info on/off",
choices = {CON,COFF},
},
{
name = "Unity Blur Multiplier",
help = "Used for focus bar display, ie the red zone",
min = 1,
max = 10,
value = 5 -- start up default
},
{
name = "Diffraction",
help = "Switches the diffraction on & off in the focus bar",
help2 = "Focus Bar total blur is dynamically linked to ML total blur (CoC) setting",
choices = {CON,COFF},
},
{
name = "Focus Bar Multiplier",
help = "Zoom out the focus bar by the specified amount",
help2 = "1 means no zoom. 0 = place FP at focus bar max. -1 = DoF mode",
min = -1,
max = 9,
value = -1 -- start up default
},
{
name = "Near DoF Criterion",
help = "Provides near DoF based on different infinity blurs",
help2 = "Based on the logic that near blur can be larger than the infinity blur",
choices = options,
value = "2xDefocus" -- start up default
},
{
name = "Focus Stacking",
help = "Only works in DoF mode",
help2 = "Magenta bar shows the amount of overlap re the last image",
choices = {CON,COFF},
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment