// eyedist.cpp: calculate eye-mouth and inter-eye dist
//
// The functions in this file know how to deal with missing points.  This
// matters during testing when we are comparing results to manually
// landmarked reference shapes.  For example, a reference eye pupil may be
// concealed by the side of the nose.  When calculating the inter-eye
// distance, if the pupil is missing we can instead use a point near the
// pupil.  We must then adjust the point-to-point distance calculated using
// this surrogate point.  We use the mean face shape to figure out the
// adjustment.  The accuracy of the resulting estimated inter-eye distance will
// depend upon how similar the proportions of the face are to the mean face.
//
// Copyright (C) 2005-2013, Stephen Milborrow

#include "stasm.h"

namespace stasm
{
static int TabPoint(    // return first used point in tab, -1 if none
    const int*   tab,   // in
    int          ntab,  // in
    const Shape& shape) // in
{
    for (int i = 0; i < ntab; i++)
        if (PointUsed(shape, tab[i]))
            return tab[i]; // note return

    return -1;
}

static double CanonicalEyeMouthDist( // return 0 if pupils and mouth not avail
    const Shape& shape17)            // in
{
    if (!PointUsed(shape17, L17_LPupil) ||
        !PointUsed(shape17, L17_RPupil) ||
        !PointUsed(shape17, L17_CBotOfBotLip))
    {
        return 0; // note return
    }
    return PointDist(
             MeanPoint(shape17, L17_LPupil, L17_RPupil, IX), // eye mid point
             MeanPoint(shape17, L17_LPupil, L17_RPupil, IY),
             shape17(L17_CBotOfBotLip, IX),                  // bot of bot lip
             shape17(L17_CBotOfBotLip, IY));
}

double EyeMouthDist(    // eye-mouth distance of a face shape, return 1 if not a face
    const Shape& shape) // in
{
    static const int eyes[] = // surrogates for pupil midpoint
    {
        L17_LPupil,
        L17_RPupil,
        L17_LEyeOuter,
        L17_REyeOuter,
        L17_LEyeInner,
        L17_REyeInner,
        L17_LEyebrowInner,
        L17_REyebrowInner,
        L17_LEyebrowOuter,
        L17_REyebrowOuter
    };
    static const int mouths[] = // surrogates for bot of bot lip
    {
        L17_CBotOfBotLip,
        L17_CTopOfTopLip,
        L17_LMouthCorner,
        L17_RMouthCorner
    };
    const Shape shape17(Shape17OrEmpty(shape));
    if (shape17.rows == 0)            // could not convert the shape to a Shape17?
        return ShapeWidth(shape) / 2; // fallback, note return
    double eyemouth = CanonicalEyeMouthDist(shape17);
    if (eyemouth == 0) // pupils and mouth not available?
    {
        const int eye   = TabPoint(eyes,   NELEMS(eyes),   shape17);
        const int mouth = TabPoint(mouths, NELEMS(mouths), shape17);
        if (eye >= 0 && mouth >= 0)  // actual or surrogate points available?
        {
            eyemouth = PointDist(shape17, eye, mouth) *
                       CanonicalEyeMouthDist(MEANSHAPE17) /
                       PointDist(MEANSHAPE17, eye, mouth);
        }
    }
    if (eyemouth == 0)
    {
        // last resort, estimate eyemouth dist from shape extent
        eyemouth = MAX(ShapeWidth(shape17), ShapeHeight(shape17)) *
                   PointDist(MEANSHAPE17, L17_LPupil, L17_CBotOfBotLip) /
                   MAX(ShapeWidth(MEANSHAPE17), ShapeHeight(MEANSHAPE17));
    }
    CV_Assert(eyemouth > 1 && eyemouth < 1e5); // sanity check
    return eyemouth;
}

double InterEyeDist(    // inter-pupil distance of a face shape
    const Shape& shape) // in
{
    static const int leyes[] = // surrogates for left pupil
    {
        L17_LPupil,
        L17_LEyeOuter,
        L17_LEyeInner,
        L17_LEyebrowInner,
        L17_LEyebrowOuter
    };
    static const int reyes[] = // surrogates for right pupil
    {
        L17_RPupil,
        L17_REyeOuter,
        L17_REyeInner,
        L17_REyebrowInner,
        L17_REyebrowOuter
    };
    const Shape shape17(Shape17OrEmpty(shape));
    if (shape17.rows == 0)            // could not convert the shape to a Shape17?
        return ShapeWidth(shape) / 2; // fallback, note return
    double eyedist = 0;
    const int leye = TabPoint(leyes, NELEMS(leyes), shape17);
    const int reye = TabPoint(reyes, NELEMS(reyes), shape17);
    if (leye >= 0 && reye >= 0 &&           // actual or surrogate points available?
        PointDist(shape17, leye, reye) > 1) // surrogates aren't co-located?
    {
        eyedist = PointDist(shape17, leye, reye) *
                  PointDist(MEANSHAPE17, L17_LPupil, L17_RPupil) /
                  PointDist(MEANSHAPE17, leye, reye);
    }
    else // last resort, estimate inter-pupil distance from shape extent
    {
        eyedist = MAX(ShapeWidth(shape17), ShapeHeight(shape17)) *
                  PointDist(MEANSHAPE17, L17_LPupil, L17_RPupil) /
                  MAX(ShapeWidth(MEANSHAPE17), ShapeHeight(MEANSHAPE17));
    }
    CV_Assert(eyedist > 1 && eyedist < 1e5); // sanity check
    return eyedist;
}

} // namespace stasm
