Create a gist now

Instantly share code, notes, and snippets.

@pigeonhill /FOCUS
Last active May 29, 2017

What would you like to do?
FOCUS
--[[
Focus Bar demo Script (Version 1.4)
This script demos a focus bar function to help visualise linear focus 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.
Garry George May 2017
http://photography.grayheron.net/
The lens must report distance and LV needs to be on.
Also make sure ML DoF is set to diffraction on for best results.
Check the ML set the CoC (blur) is set for your needs. The script uses this value.
Script has been tested on a 5D3 and EOSM.
--]]
-- Declare some globals
hidden = false
CON = "ON"
COFF = "OFF"
last_ndof = 0
last_fdof = 0
current_ndof = 0 -- at the time a full press is made
current_fdof = 0
last_dof_flag = false
--[[
The near field DoF distance shown meets the total (diffraction + optical) blur at infinity.
If the optical blur turns red, you really have over focused, ie beyond the 2xsensor pixel limit.
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.
Aim for total blurs at infinity of lower than 30, but not lower than, say, 15 (/crop).
Also note that diffraction blur applies over the entire FoV and, to some extent, can be reduced reasonably well in post, albeit with the risk of some artifacts.
Optical blur varies over the scene approaches 0 only in the plane of focus.
The focus bar is there to provide visual (linear) feedback on the near focus field, ie the infinity blurs provide the other (far) end.
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.
A setting of -2 forces the use of the start and finish distances.
A useful thing to note is that the Focus Bar works in Canon zoom mode :-) Thus DoF mode (-1) and Canon zoomed in may be useful for portrait work.
If the far DoF goes beyond the HFD, ie you are in infinity focusing mode, the far scale shows the total blur (optical + diffraction) and the near scale shows the near DoF based on the infinity blur.
Thus you have all the data you need, eg:
- Infinity blur data
- Focus bar showing an impression of the (optical) focus field from the camera to the focus point
- ML DoF values with diffraction correcion
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 above or below unity are grey.
Blurs 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 (red dot) as your references.
The blur data at infinity can be switched off so you just have the focus bar showing.
NOTE: The script's menu settings can be changed to create your 'defaults at start up'.
To repeat: for the landscape photographers (or anyone) I recommend the following ML seetings:
- Set diffraction aware DoF on and visible, thus the green dots are providing you info on the total DoF with diffraction.
Bottom line: try and minimise the total blur in camera if you can.
--]]
--** The following two functions, if used in another script, must be used together, with the 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 (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
Garry George May 2017
http://photography.grayheron.net/
The lens must report distance and LV needs to be on.
Has been tested on a 5D3 and EOSM.
--]]
local message = ""
local b_inf = 0
local b_total = 0
local b_near = 0
local b_near2 = 0
--local sensor = 6.3 -- change this to match your camera, ie 2 x sensor pixel pitch or pixel size. EOSM = 4.29. 5D3 = 6.3
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
local inf = 999990 -- 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 = lens.dof_near
local dof_far = lens.dof_far
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)
--************************************************************
-- ML corrections: make sure you switch of 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+10 end
current_fdof = dof_far
current_ndof = dof_near
-- End ML corrections
--************************************************************
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'
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 -- Optical 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_total*aperture_value/1000.0)/(focal_length*focal_length) -- near DoF using total blur at infinity
b_near = b_near*(focus_distance-focal_length)
b_near = focus_distance/(b_near+1)
b_near2 = (2*b_total*aperture_value/1000.0)/(focal_length*focal_length) -- near DoF using twice the total blur at infinity
b_near2 = b_near2*(focus_distance-focal_length)
b_near2 = focus_distance/(b_near2+1)
x_step = (x_end - x_start)/num_steps
local x = 0 -- point of interest in the scene
local b_x = 0 -- blur at POI
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 ((b_total < b_limit) and (dof_far >= 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.RED,COLOR.RED)
-- 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 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["x2 Near DoF Criterion"].value == "ON" then x_start = b_near2 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
temp = x_start
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
qq = FONT.MED:width("XXXXXXXXXXXXXXXXXX")
display.print("XXXXXXXXXXXXXXXXXX", 490-qq/2, 420, FONT.MEDIUM ,COLOR.TRANSPARENT,COLOR.TRANSPARENT)
message = " | "
qq = FONT.MED:width(message)
display.print(message, 490, 420, FONT.MEDIUM ,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
temp = dof_near
if temp < 1000 then
message = " "..myround(temp/10, 0).."cm"
else
message = " "..myround(temp/1000,2).."m"
end
qq = FONT.MED:width(message)
display.print(message, 490-qq, 420, FONT.MEDIUM ,COLOR.WHITE,COLOR.TRANSPARENT_BLACK)
temp = dof_far
message = " | "
qq = FONT.MED:width(message)
if dof_far > inf then
message = " Inf "
elseif temp < 1000 then
message = " "..myround(temp/10, 0).."cm "
else
message = " "..myround(temp/1000,2).."m "
end
qq = 490+qq/2
display.print(message, qq, 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 on & off",
choices = {CON,COFF},
},
{
name = "Show Info",
help = "Switches the Info panel on/off",
choices = {CON,COFF},
},
{
name = "Unity Blur Multiplier",
help = "Used for focus bar display, ie blur red zone",
min = 1,
max = 10,
value = 5
},
{
name = "Diffraction",
help = "Switches the diffraction on & off in the focus bar",
help2 = "Focus Bar blur is dynamically linked to ML blur (CoC) setting",
choices = {COFF,CON},
},
{
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
},
{
name = "x2 Near DoF Criterion",
help = "Provides near DoF based on twice total infinity blur",
help2 = "Based on the logic that near blur can be larger than far blur",
choices = {CON,COFF},
},
{
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