Hyperfocal distance math needs changing

Issue #2721 resolved
Garry George created an issue

I'm not sure I posted correctly here: https://bitbucket.org/hudson/magic-lantern/issues/2719/proposed-correction-to-focusc

Bluntly, although I suggested the original math, the HFD calculated by ML is wrong.

In an attempt to make it diffraction aware, I used the 'full' diffraction blur, which is focus distance aware, via the inverse of the magnification (to keep things integer in C), ie imag = (fd-fl)/fl.

Being focus distance aware is reasonable and 'correct', ie the fd changes, when calculating DoFs, ie at a specified distance.

For calculating the hyperfocal distance, it is meaningless to have this vary as we focus: which is what happens at the moment.

Here is my suggested fix to focus.c

void focus_calc_dof()
{
// Total (defocus + diffraction) blur dia in microns
uint64_t coc = dof_info_coc;

const uint64_t  fd = lens_info.focus_dist * 10; // into mm
const uint64_t  fl = lens_info.focal_len; // already in mm

// If we have no aperture value then we can't compute any of this
// Also not all lenses report the focus length or distance
if (fl == 0 || lens_info.aperture == 0 || fd == 0)
{
    lens_info.dof_near      = 0;
    lens_info.dof_far       = 0;
    lens_info.hyperfocal    = 0;
    return;
}

// Set up some dof info
const uint64_t  freq = 550;         // mid vis diffraction freq in nm (use 850 if IR)
const uint64_t  imag = (fd-fl)/fl;  // inverse of magnification (to keep as integer)
const uint64_t  diff = (244*freq*lens_info.aperture*(1+imag)/imag)/1000000; // focus distance aware Diffraction blur in microns
const uint64_t  diff2 = (244*freq*lens_info.aperture)/1000000; // Diffraction blur in microns

int dof_flags = 0;

if (dof_info_formula == DOF_FORMULA_DIFFRACTION_AWARE)
{
    // Test if large aperture diffraction limit reached 
    if (diff >= coc)
    {
        // note: in this case, DOF near and far will collapse to focus distance
        dof_flags |= DOF_DIFFRACTION_LIMIT_REACHED;
        coc = 0;
    }
    else
    {
        // calculate defocus only blurs in microns
        const uint64_t sq = (coc*coc - diff*diff);
        const uint64_t sq2 = (coc*coc - diff2*diff2);
        coc = (int) sqrtf(sq); // Focus distance aware defocus only blur
        coc2 = (int) sqrtf(sq2); // Defocus only blur
    }
}

const uint64_t        fl2 = fl * fl;

// Calculate hyperfocal distance H, ie irrespective of lens focus distance, ie this should be a true constant
const uint64_t H = coc2 ? fl + ((10000 * fl2) / (lens_info.aperture  * coc2)) : 1000 * 1000;
lens_info.hyperfocal = H;

// Calculate near and far dofs
lens_info.dof_near = (fd*fl*10000)/(10000*fl + imag*lens_info.aperture*coc); // in mm
if( fd >= H )
{
    lens_info.dof_far = 1000 * 1000; // infinity
}
else
{
    lens_info.dof_far = (fd*fl*10000)/(10000*fl - imag*lens_info.aperture*coc); // in mm
}

// update DOF flags
lens_info.dof_flags = dof_flags;

// make sure we have nonzero DOF values, so they are always displayed
lens_info.dof_near = MAX(lens_info.dof_near, 1);
lens_info.dof_far = MAX(lens_info.dof_far, 1);

lens_info.dof_diffraction_blur = (int) diff;

}

Finally, being ignorant of the details in the ML backend, why is the following correct coc = dof_info_coc; and not coc = dof_info.coc;

Comments (7)

  1. Alex

    Do you mind re-framing this as a pull request? You can do it from the web interface, and makes it easier to see the difference.

  2. Garry George reporter

    Alex

    I've changed the code and I've stumbled over how to do a pull request. I've opened up the Pull Request, but I can't work out how to open a new pull and go to the focus.c to enter my changes.

    Sorry to bother you, but can you educate me.

    Cheers

    Garry

  3. Garry George reporter

    Alex pls see my latest post: Here is the focus.c fix.

    Sorry I can't make a pull request.

    void focus_calc_dof()
    {
    // Total (defocus + diffraction) blur dia in microns
    uint64_t coc = dof_info_coc*10; // from ML setting, converted to tenths here, as the base unit for blurs, to increase 'division accuracy'
    uint64_t coc_hfd = 0; // variable used to calculate HFD

    const uint64_t  fd = lens_info.focus_dist * 10; // into mm
    const uint64_t  fl = lens_info.focal_len; // already in mm
    
    // If we have no aperture value then we can't compute any of this
    // Also not all lenses report the focus length or distance
    if (fl == 0 || lens_info.aperture == 0 || fd == 0)
    {
        lens_info.dof_near      = 0;
        lens_info.dof_far       = 0;
        lens_info.hyperfocal    = 0;
        return;
    }
    
    // Set up some dof info. Note lens_info.aperture = 10*N, eg at F/16 lens_info.aperture = 160
    // Diffraction blur at any fd = 2.44*freq*N*(1+mag). Where mag = fl/(fd-fl)
    // ie at fl = 10 and fd = 200, mag = 10/(200-10) = 0.05. But note for 100mm macro lens at min fd = 300, mag = 100/(300-100) = 0.5
    const uint64_t  freq = 550;         // mid vis diffraction freq in nm (use 850 if IR)
    const uint64_t  diff_hfd = (244*freq*lens_info.aperture)/100000; // Estimation of diffraction blur in tenths of microns, without (1+mag) factor as mag assumed at infinity, ie zero
    const uint64_t  diff = diff_hfd*fd/(fd-fl); // Diffraction blur (in tenths of units) in microns at fd, using  (1+mag), ie being technically correct :-)
    
    int dof_flags = 0;
    
    if (dof_info_formula == DOF_FORMULA_DIFFRACTION_AWARE)
    {
        // Test if large aperture diffraction limit reached
        if (diff >= coc)
        {
            // note: in this case, DOF near and far will collapse to focus distance
            dof_flags |= DOF_DIFFRACTION_LIMIT_REACHED;
            coc = 0;
        }
        else
        {
            // calculate defocus only blurs for fd and hfd, in tenths of micron
            const uint64_t sq = (coc*coc - diff*diff);
            const uint64_t sq_hfd = (coc*coc - diff_hfd*diff_hfd);
            coc_hfd = (int) sqrtf(sq_hfd); // Estimate of defocus blur at HFD in tenths of units. Doesn't vary when focus changes, as uses a mag (at infinity) of zero.
            coc = (int) sqrtf(sq); // Focus distance aware defocus blur in tenths of units
    
        }
    }
    
    const uint64_t        fl2 = fl * fl;
    
    // Calculate defocus hyperfocal distance H. Note this is diffraction aware, but is indepedent of fd
    const uint64_t H = coc ? fl + ((100000 * fl2) / (lens_info.aperture  * coc_hfd)) : 1000 * 1000; // use coc to test for diffraction limit reached
    lens_info.hyperfocal = H;
    
    // Calculate near and far dofs
    const uint64_t  temp = lens_info.aperture*coc*(fd-fl) // note aperture and coc in tenths of their units, hence the 100000 factor below
    lens_info.dof_near = (fd*fl2*100000)/(100000*fl2 + temp); // in mm
    if( fd >= H )
    {
        lens_info.dof_far = 1000 * 1000; // infinity
    }
    else
    {
        lens_info.dof_far = (fd*fl2*100000)/(100000*fl2 - temp); // in mm
    }
    
    // update DOF flags
    lens_info.dof_flags = dof_flags;
    
    // make sure we have nonzero DOF values, so they are always displayed
    lens_info.dof_near = MAX(lens_info.dof_near, 1);
    lens_info.dof_far = MAX(lens_info.dof_far, 1);
    
    lens_info.dof_diffraction_blur = (int) (diff+5)/10; // at point of focus rounded up/down to nearest integer
    

    }

  4. Garry George reporter

    I thought I just made a pull request, but can't see it in the pull request list. So maybe I didn't. The problem is simply the mag or imag part. I've eliminated this, but all the equations remain the same, ie DoF equations are simple.

  5. Log in to comment