/*

    PsychToolbox-3/PsychSourceGL/Source/Common/PsychCV/PsychCV.c

    PLATFORMS:  All

    AUTHORS:

    Mario Kleiner   mk  mario.kleiner.de@gmail.com

    HISTORY:

    5.01.2008       mk  wrote it.

    DESCRIPTION:

    A container for all kind of miscellaneous C or high-perf OpenGL routines somehow
    loosely related to computer vision and image processing tasks.

    Usually user code won't call this module directly, but it will call high level
    M-File code which in turn "outsources" parts of computationally very expensive
    tasks to this module.

    The initial implementation contains parts of the OpenEyes C-based computer vision
    eyetracker: The original pure C based, pure Linux implementation was split up in
    parts that can be handled by Psychtoolbox Screen() (image acquisition and visualization),
    high-level M file code for general flow control, and compute-intense C routines and
    routines that utilize OpenCV - these parts are inside PsychCV.

    The initial implementation will also contain other interesting bits of computer vision
    code taken from OpenCV.

    IMPORTANT:

    PsychCV will be built with CVEyeTracker / OpenEyes support and OpenCV library support
    enabled if the #define PSYCHCV_USE_OPENCV 1 is defined somewhere in the build scripts!

    By default (if this is omitted), it will build without OpenCV / OpenEyes support.
    The reason is that support of OpenCV incurs a large number of library dependencies which
    make installation quite a hazzle.

    As long as OpenEyes support is not in a enduser useable state anyway, we can save
    ourselves and the users from this dependency hell.

*/

#include "PsychCV.h"

// Declare variables local to this file.

#ifndef PI
#define PI 3.141592654
#endif

// Help/Usage strings:
#define MAX_SYNOPSIS_STRINGS 50
static const char *synopsisSYNOPSIS[MAX_SYNOPSIS_STRINGS];

// Level of verbosity:
unsigned int verbosity = 4;

// Status: Initialized or not?
static psych_bool psychCVInitialized = FALSE;

// Extern shutdown function for AR toolkit, defined in PsychCVARToolkit.hc
extern void PsychCVARExit(void);

/* Logger callback function to output OpenCV et al. debug messages at 'verbosity' > 5. */
void PsychCVLogger(const char* msg)
{
    if (verbosity > 5)
        printf("PTB-DEBUG: OpenCV says: %s", msg);
}

// Setup help/usage strings:
void InitializeSynopsis()
{
    int i = 0;
    const char **synopsis = synopsisSYNOPSIS;  //abbreviate the long name

    synopsis[i++] = "PsychCV - Helper module for miscellaneous functionality related to image processing and/or computer vision:\n";
    synopsis[i++] = "Copyright 2008 - 2024 Mario Kleiner. Licensed under MIT license. For potential statically included libraries,";
    synopsis[i++] = "see licenses below. This module employs various different 3rd party software, so here are the credits for those parts:\n";
    #ifdef PSYCHCV_USE_OPENCV
    synopsis[i++] = "OpenEyes eye tracking is based mostly on a modified version of the OpenEyes toolkit,";
    synopsis[i++] = "http://hcvl.hci.iastate.edu/openEyes which was written by Dongheng Li, Derrick Parkhurst,";
    synopsis[i++] = "Jason Babcock and David Winfield.";
    synopsis[i++] = "OpenEyes toolkit is licensed to you under GPL v2.\n";
    synopsis[i++] = "PsychCV is also dependent on the OpenCV Open computer vision library: http://sourceforge.net/projects/opencvlibrary/ \n";
    synopsis[i++] = "which is licensed under the BSD license.\n";
    #endif
    #ifdef PSYCHCV_USE_ARTOOLKIT
    synopsis[i++] = "The 'ARxxx' subfunctions are based on the ARToolkit: http://www.hitl.washington.edu/artoolkit/";
    synopsis[i++] = "ARToolkit is licensed to you under GPL v2.\n";
    #endif
    #ifdef PSYCHCV_USE_APRILTAGS
    synopsis[i++] = "The 'Aprilxxx' subfunctions for April tag tracking are implemented by use of the apriltag library from:";
    synopsis[i++] = "https://april.eecs.umich.edu/software/apriltag and https://github.com/AprilRobotics/apriltag .";
    synopsis[i++] = "The apriltag library is licensed under BSD-2 clause license. See Psychtoolbox main License.txt file for details.\n";
    #endif
    synopsis[i++] = "\nGeneral information and settings:\n";
    synopsis[i++] = "version = PsychCV('Version');";
    synopsis[i++] = "oldlevel = PsychCV('Verbosity' [,level]);";
    synopsis[i++] = "\nHelper functions for memory buffer copies:\n";
    synopsis[i++] = "PsychCV('CopyMatrixToMemBuffer', matrix, memBufferPtr);";
    #ifdef PSYCHCV_USE_OPENCV
    synopsis[i++] = "\nSupport for the OpenEyes computer vision based eye tracker:\n";
    synopsis[i++] = "[EyeImageMemBuffer, EyeColorImageMemBuffer, SceneImageMemBuffer, ThresholdImageMemBuffer, EllipseImageMemBuffer] = PsychCV('OpenEyesInitialize', handle [, eyeChannels] [, eyeWidth][, eyeHeight][, sceneWidth][, sceneHeight][, logfilename]);";
    synopsis[i++] = "PsychCV('OpenEyesShutdown', handle);";
    synopsis[i++] = "[oldSettings, ...] = PsychCV('OpenEyesParameters', handle [, pupilEdgeThreshold][, starburstRays][, minFeatureCandidates][, corneaWindowSize][, edgeThreshold][, gaussWidth][, maxPupilEccentricity] [, initialAngleSpread] [, fanoutAngle1] [, fanoutAngle2] [, featuresPerRay] [, specialFlags]);";
    synopsis[i++] = "EyeResult = PsychCV('OpenEyesTrackEyePosition', handle [, mode] [, px], [, py]);";
    #endif
    #ifdef PSYCHCV_USE_ARTOOLKIT
    synopsis[i++] = "\nSupport for the ARToolkit computer vision based 3D marker tracking library:\n";
    synopsis[i++] = "[SceneImageMemBuffer, glProjectionMatrix, DebugImageMemBuffer] = PsychCV('ARInitialize', cameraCalibFilename, imgWidth, imgHeight, imgChannels [, imgFormat]);";
    synopsis[i++] = "PsychCV('ARShutdown');";
    synopsis[i++] = "[markerId] = PsychCV('ARLoadMarker', markerFilename [, isMultiMarker][, patt_width][, patt_center_x][, patt_center_y]);";
    synopsis[i++] = "[templateMatchingInColor, imageProcessingFullSized, imageProcessingIdeal, trackingWithPCA] = PsychCV('ARTrackerSettings' [, templateMatchingInColor][, imageProcessingFullSized][, imageProcessingIdeal][, trackingWithPCA]);";
    synopsis[i++] = "[detectedMarkers] = PsychCV('ARDetectMarkers'[, markerSubset][, threshold] [, infoType]);";
    synopsis[i++] = "[scale, minDist, maxDist] = PsychCV('ARRenderSettings' [, scale][, minDist][, maxDist]);";
    synopsis[i++] = "PsychCV('ARRenderImage');";
    #endif
    #ifdef PSYCHCV_USE_APRILTAGS
    synopsis[i++] = "\nSupport for the apriltag 2D/3D april tag marker tracking library:\n";
    synopsis[i++] = "[inputImageMemBuffer] = PsychCV('AprilInitialize', tagFamilyName, imgWidth, imgHeight, imgChannels [, imgFormat][, maxNrTags]);";
    synopsis[i++] = "[detectedMarkers] = PsychCV('AprilDetectMarkers'[, markerSubset=all][, infoType=all]);";
    synopsis[i++] = "PsychCV('AprilShutdown');";
    synopsis[i++] = "[nrThreads, imageDecimation, quadSigma, refineEdges, decodeSharpening, criticalRadAngle, deglitch, maxLineFitMse, minWhiteBlackDiff, minClusterPixels, maxNMaxima] = PsychCV('AprilSettings' [, nrThreads][, imageDecimation][, quadSigma][, refineEdges][, decodeSharpening][, criticalRadAngle][, deglitch][, maxLineFitMse][, minWhiteBlackDiff][, minClusterPixels][, maxNMaxima]);";
    synopsis[i++] = "[glProjectionMatrix, camCalib, tagSize, minD, maxD] = PsychCV('April3DSettings' [, camCalib][, tagSize][, minD][, maxD]);";
    #endif

    synopsis[i++] = NULL;  //this tells PsychDisplayScreenSynopsis where to stop
    if (i > MAX_SYNOPSIS_STRINGS) {
        PrintfExit("%s: increase dimension of synopsis[] from %ld to at least %ld and recompile.",__FILE__,(long)MAX_SYNOPSIS_STRINGS,(long)i);
    }
}

// Show help/usage strings:
PsychError PSYCHCVDisplaySynopsis(void)
{
    int i;

    for (i = 0; synopsisSYNOPSIS[i] != NULL; i++)
        printf("%s\n",synopsisSYNOPSIS[i]);

    return(PsychError_none);
}

// Module exit function: Shutdown what's running, clean up after yourself...
PsychError PsychCVExit(void)
{
    // Shutdown only if we are online:
    if (psychCVInitialized) {
        #ifdef PSYCHCV_USE_ARTOOLKIT
        // Perform AR toolkit shutdown, if needed:
        PsychCVARExit();
        #endif

        #ifdef PSYCHCV_USE_APRILTAGS
        PsychCVAprilExit();
        #endif

        // Mark us dead:
        psychCVInitialized = FALSE;
    }

    return(PsychError_none);
}

// Startup function: Called at module load time / First time invocation:
void PsychCVInitialize(void)
{
    // Already initialized?
    if (!psychCVInitialized) {
        // We're alive :-)
        psychCVInitialized = TRUE;
    }
}

/* PsychCV('Verbosity') - Set level of verbosity.
 */
PsychError PSYCHCVVerbosity(void)
{
    static char useString[] = "oldlevel = PsychCV('Verbosity' [,level]);";
    static char synopsisString[] =
        "Set level of verbosity for error/warning/status messages. 'level' optional, new level "
        "of verbosity. 'oldlevel' is the old level of verbosity. The following levels are "
        "supported: 0 = Shut up. 1 = Print errors, 2 = Print also warnings, 3 = Print also some info, "
        "4 = Print more useful info (default), >5 = Be very verbose (mostly for debugging the driver itself). ";
    static char seeAlsoString[] = "";

    int level= -1;

    // Setup online help:
    PsychPushHelp(useString, synopsisString, seeAlsoString);
    if (PsychIsGiveHelp()) { PsychGiveHelp(); return(PsychError_none); };

    PsychErrorExit(PsychCapNumInputArgs(1));        // The maximum number of inputs
    PsychErrorExit(PsychRequireNumInputArgs(0));    // The required number of inputs
    PsychErrorExit(PsychCapNumOutputArgs(1));       // The maximum number of outputs

    PsychCopyInIntegerArg(1, kPsychArgOptional, &level);
    if (level < -1)
        PsychErrorExitMsg(PsychError_user, "Invalid level of verbosity provided. Valid are levels of zero and greater.");

    // Return current/old level:
    PsychCopyOutDoubleArg(1, kPsychArgOptional, (double) verbosity);

    // Set new level, if one was provided:
    if (level > -1)
        verbosity = level;

    return(PsychError_none);
}

/* Runtime parameters for the OpenEyes computer vision based eyetracker: See source of cvEyeTracker.cc for
 * meaning of the parameters. They are just cached here in PsychCV.c for simplicity of internal implementation.
 *
 * They get initialized to their defaults (according to original OpenEyes implementation) in PsychCVOpenEyesInitialize().
 * After that they're cached, returned during queries from the cached local copy, and send to cvEyeTracker.cc when they're
 * changed in PSYCHCVOpenEyesParameters().
 */
static int pupilEdgeThreshold, starburstRays, minFeatureCandidates, corneaWindowSize, edgeThreshold, gaussWidth;
static double maxPupilEccentricity, initialAngleSpread;
static double fanoutAngle1, fanoutAngle2;
static int featuresPerRay, specialFlags;

#ifdef PSYCHCV_USE_OPENCV

/* PsychCV('OpenEyesInitialize') - Initialize a new tracking session with OpenEyes:
 */
PsychError PSYCHCVOpenEyesInitialize(void)
{

    static char useString[] = "[EyeImageMemBuffer, EyeColorImageMemBuffer, SceneImageMemBuffer, ThresholdImageMemBuffer, EllipseImageMemBuffer] = PsychCV('OpenEyesInitialize', handle [, eyeChannels] [, eyeWidth][, eyeHeight][, sceneWidth][, sceneHeight][, logfilename]);";
    static char synopsisString[] =
        "Set level of verbosity for error/warning/status messages. 'level' optional, new level "
        "of verbosity. 'oldlevel' is the old level of verbosity. The following levels are "
        "supported: 0 = Shut up. 1 = Print errors, 2 = Print also warnings, 3 = Print also some info, "
        "4 = Print more useful info (default), >5 = Be very verbose (mostly for debugging the driver itself). ";
    static char seeAlsoString[] = "";

    int handle = -1;
    int eyewidth, eyeheight, eyechannels, scenewidth, sceneheight;
    char* logfilename = NULL;
    void* eyeImage = NULL;
    void* eyeColorImage = NULL;
    void* sceneImage = NULL;
    void* thresholdImage = NULL;
    void* ellipseImage = NULL;

    // Setup online help:
    PsychPushHelp(useString, synopsisString, seeAlsoString);
    if (PsychIsGiveHelp()) { PsychGiveHelp(); return(PsychError_none); };

    PsychErrorExit(PsychCapNumInputArgs(7));        // The maximum number of inputs
    PsychErrorExit(PsychRequireNumInputArgs(1));    // The required number of inputs
    PsychErrorExit(PsychCapNumOutputArgs(5));       // The maximum number of outputs

    // Get tracker handle: This is not used yet, just here for future extensions:
    PsychCopyInIntegerArg(1, kPsychArgRequired, &handle);
    if (handle < 0)
        PsychErrorExitMsg(PsychError_user, "Invalid trackerhandle 'handle' provided. Valid handles are of value zero or greater.");

    eyechannels = 1;
    PsychCopyInIntegerArg(2, kPsychArgOptional, &eyechannels);
    if (eyechannels < 1 || eyechannels > 4)
        PsychErrorExitMsg(PsychError_invalidRectArg, "Invalid eye image channels provided. Must be between 1 and 4!");

    // Get optional eye ROI: We default to 640x480 pixels ROI:
    eyewidth = 640;
    PsychCopyInIntegerArg(3, kPsychArgOptional, &eyewidth);
    if (eyewidth < 1)
        PsychErrorExitMsg(PsychError_invalidRectArg, "Invalid eye image width provided. Must be at least 1 pixel!");

    eyeheight = 480;
    PsychCopyInIntegerArg(4, kPsychArgOptional, &eyeheight);
    if (eyeheight < 1)
        PsychErrorExitMsg(PsychError_invalidRectArg, "Invalid eye image height provided. Must be at least 1 pixel!");

    scenewidth = 640;
    PsychCopyInIntegerArg(5, kPsychArgOptional, &scenewidth);
    if (scenewidth < 1)
        PsychErrorExitMsg(PsychError_invalidRectArg, "Invalid scene image width provided. Must be at least 1 pixel!");

    sceneheight = 480;
    PsychCopyInIntegerArg(6, kPsychArgOptional, &sceneheight);
    if (sceneheight < 1)
        PsychErrorExitMsg(PsychError_invalidRectArg, "Invalid scene image height provided. Must be at least 1 pixel!");

    // Get optional name of tracker logfile. If it is empty, we disable logging:
    PsychAllocInCharArg(7, kPsychArgOptional, &logfilename);

    // Call tracker init call:
    if (!cvEyeTrackerInitialize(logfilename, eyewidth, eyeheight, eyechannels, &eyeImage, &eyeColorImage,
        scenewidth, sceneheight, &sceneImage, &ellipseImage, &thresholdImage)) {
        // Failed!
        PsychErrorExitMsg(PsychError_system, "OpenEyes tracker setup failed.");
    }

    // Return double-encoded void* memory pointer to eye image input/output buffer:
    PsychCopyOutDoubleArg(1, kPsychArgRequired, PsychPtrToDouble(eyeImage));

    // Return double-encoded void* memory pointer to eye color image input/output buffer:
    PsychCopyOutDoubleArg(2, kPsychArgRequired, PsychPtrToDouble(eyeColorImage));

    // Return double-encoded void* memory pointer to eye image input/output buffer:
    PsychCopyOutDoubleArg(3, kPsychArgOptional, PsychPtrToDouble(sceneImage));

    // Return double-encoded void* memory pointer to eye image input/output buffer:
    PsychCopyOutDoubleArg(4, kPsychArgOptional, PsychPtrToDouble(thresholdImage));

    // Return double-encoded void* memory pointer to eye image input/output buffer:
    PsychCopyOutDoubleArg(5, kPsychArgOptional, PsychPtrToDouble(ellipseImage));

    // Setup default parameters for OpenEyes:
    pupilEdgeThreshold = 20;
    starburstRays = 18;
    minFeatureCandidates = 10;
    corneaWindowSize = 301;
    edgeThreshold = 20;
    gaussWidth = 5;
    maxPupilEccentricity = 1.2;    // Original is 2.0 (!)
    initialAngleSpread = 360.0;
    fanoutAngle1 = 0.0;
    fanoutAngle2 = 180.0;
    featuresPerRay = 1;
    specialFlags = 0x0;

    // Commit default parameters to tracker:
    cvEyeTrackerSetParameters(pupilEdgeThreshold, starburstRays, minFeatureCandidates, corneaWindowSize, edgeThreshold,
                              gaussWidth, maxPupilEccentricity, initialAngleSpread * PI/180, fanoutAngle1 * PI/180,
                              fanoutAngle2 * PI/180, featuresPerRay, specialFlags);

    return(PsychError_none);
}

/* PsychCV('OpenEyesShutdown') - Shutdown a tracking session with OpenEyes:
 */
PsychError PSYCHCVOpenEyesShutdown(void)
{
    static char useString[] = "PsychCV('OpenEyesShutdown', handle);";
    static char synopsisString[] =
        "Set level of verbosity for error/warning/status messages. 'level' optional, new level "
        "of verbosity. 'oldlevel' is the old level of verbosity. The following levels are "
        "supported: 0 = Shut up. 1 = Print errors, 2 = Print also warnings, 3 = Print also some info, "
        "4 = Print more useful info (default), >5 = Be very verbose (mostly for debugging the driver itself). ";
    static char seeAlsoString[] = "";

    int handle = -1;

    // Setup online help:
    PsychPushHelp(useString, synopsisString, seeAlsoString);
    if (PsychIsGiveHelp()) { PsychGiveHelp(); return(PsychError_none); };

    PsychErrorExit(PsychCapNumInputArgs(1));        // The maximum number of inputs
    PsychErrorExit(PsychRequireNumInputArgs(1));    // The required number of inputs
    PsychErrorExit(PsychCapNumOutputArgs(0));       // The maximum number of outputs

    // Get tracker handle: This is not used yet, just here for future extensions:
    PsychCopyInIntegerArg(1, kPsychArgRequired, &handle);
    if (handle < 0)
        PsychErrorExitMsg(PsychError_user, "Invalid trackerhandle 'handle' provided. Valid handles are of value zero or greater.");

    // Call tracker shutdown call:
    if (!cvEyeTrackerShutdown())
        PsychErrorExitMsg(PsychError_system, "OpenEyes tracker shutdown failed.");

    return(PsychError_none);
}

/* PsychCV('OpenEyesTrackEyePosition') - Perform a single eye tracking cycle with OpenEyes and return result:
 */
PsychError PSYCHCVOpenEyesTrackEyePosition(void)
{
    static char useString[] = "EyeResult = PsychCV('OpenEyesTrackEyePosition', handle [, mode][, px][, py][, c1][, c2]);";
    static char synopsisString[] =
        "Set level of verbosity for error/warning/status messages. 'level' optional, new level "
        "of verbosity. 'oldlevel' is the old level of verbosity. The following levels are "
        "supported: 0 = Shut up. 1 = Print errors, 2 = Print also warnings, 3 = Print also some info, "
        "4 = Print more useful info (default), >5 = Be very verbose (mostly for debugging the driver itself). ";
    static char seeAlsoString[] = "";

    PsychGenericScriptType *eyeStruct;
    const char *FieldNames[] = { "GazeX", "GazeY", "PupilX", "PupilY", "CorneaX", "CorneaY", "Valid", "Count", "Timestamp" };
    const int FieldCount = 9;

    int handle = -1;
    int mode;
    double px, py, minArea, maxArea;
    psych_bool useGUI = FALSE;
    PsychCVEyeResult eyeResult;

    // Setup online help:
    PsychPushHelp(useString, synopsisString, seeAlsoString);
    if (PsychIsGiveHelp()) { PsychGiveHelp(); return(PsychError_none); };

    PsychErrorExit(PsychCapNumInputArgs(6));        // The maximum number of inputs
    PsychErrorExit(PsychRequireNumInputArgs(1));    // The required number of inputs
    PsychErrorExit(PsychCapNumOutputArgs(1));       // The maximum number of outputs

    // Get tracker handle: This is not used yet, just here for future extensions:
    PsychCopyInIntegerArg(1, kPsychArgRequired, &handle);
    if (handle < 0)
        PsychErrorExitMsg(PsychError_user, "Invalid trackerhandle 'handle' provided. Valid handles are of value zero or greater.");

    mode = 0;
    PsychCopyInIntegerArg(2, kPsychArgOptional, &mode);
    if (mode < 0 || mode > 6)
        PsychErrorExitMsg(PsychError_user, "Invalid mode provided. Valid modes between 0 and 4.");

    if (mode == 0 || mode == 3) {
        // Call standard tracker processing cycle:
        if (!cvEyeTrackerExecuteTrackingCycle(&eyeResult, useGUI)) {
            // Failed!
            PsychErrorExitMsg(PsychError_system, "OpenEyes tracking cycle failed.");
        }
    }

    // Any calibration mode requested?
    if (mode > 0) {
        if (mode == 1) cvEyeTrackerRecalibrate(FALSE);  // Activate scene calibration.
        if (mode == 2) cvEyeTrackerRecalibrate(TRUE);   // Reset scene calibration to "None".

        if (mode > 2) {
            // Need (px, py) location either for eyepos init or for setting of calibration points:
            PsychCopyInDoubleArg(3, kPsychArgRequired, &px);
            PsychCopyInDoubleArg(4, kPsychArgRequired, &py);

            if (mode == 3) cvEyeTrackerSetPupilLocation((int) px, (int) py);    // Set Starburst start location (px, py) for approximate pupil center.
            if (mode == 4) cvEyeTrackerAddCalibrationPoint((int) px, (int) py); // Add (px, py) as a new eye -> scene calibration point.

            if (mode == 5) {
                minArea = 0.0;
                PsychCopyInDoubleArg(5, kPsychArgOptional, &minArea);
                maxArea = 1000.0 * 1000.0;
                PsychCopyInDoubleArg(6, kPsychArgOptional, &maxArea);
                cvEyeTrackerSetRansacConstraints(px, py, minArea, maxArea); // Add minDist=px, maxDist=py as distance constraint for features.
            }

            if (mode == 6) {
                // Set manual override reference point (cornea x/y):
                cvEyeTrackerSetOverrideReferencePoint((int) px, (int) py);
            }
        }
    }

    // Create result return struct:
    PsychAllocOutStructArray(1, kPsychArgOptional, -1, FieldCount, FieldNames, &eyeStruct);
    PsychSetStructArrayDoubleElement("GazeX", 0, eyeResult.gaze_x, eyeStruct);
    PsychSetStructArrayDoubleElement("GazeY", 0, eyeResult.gaze_y, eyeStruct);
    PsychSetStructArrayDoubleElement("PupilX", 0, eyeResult.pupil_x, eyeStruct);
    PsychSetStructArrayDoubleElement("PupilY", 0, eyeResult.pupil_y, eyeStruct);
    PsychSetStructArrayDoubleElement("CorneaX", 0, eyeResult.cornea_x, eyeStruct);
    PsychSetStructArrayDoubleElement("CorneaY", 0, eyeResult.cornea_y, eyeStruct);
    PsychSetStructArrayBooleanElement("Valid", 0, eyeResult.validresult, eyeStruct);
    PsychSetStructArrayDoubleElement("Count", 0, eyeResult.trackcount, eyeStruct);
    PsychSetStructArrayDoubleElement("Timestamp", 0, eyeResult.timestamp, eyeStruct);

    return(PsychError_none);
}

/* PsychCV('OpenEyesParameters') - Change parameters of a tracking session with OpenEyes:
 */
PsychError PSYCHCVOpenEyesParameters(void)
{

    static char useString[] = "[oldSettings, ...] = PsychCV('OpenEyesParameters', handle [, pupilEdgeThreshold][, starburstRays][, minFeatureCandidates][, corneaWindowSize][, edgeThreshold][, gaussWidth][, maxPupilEccentricity] [, initialAngleSpread] [, fanoutAngle1] [, fanoutAngle2] [, featuresPerRay] [, specialFlags]);";
    static char synopsisString[] =
        "Set level of verbosity for error/warning/status messages. 'level' optional, new level "
        "of verbosity. 'oldlevel' is the old level of verbosity. The following levels are "
        "supported: 0 = Shut up. 1 = Print errors, 2 = Print also warnings, 3 = Print also some info, "
        "4 = Print more useful info (default), >5 = Be very verbose (mostly for debugging the driver itself). ";
    static char seeAlsoString[] = "";

    int handle = -1;

    // Setup online help:
    PsychPushHelp(useString, synopsisString, seeAlsoString);
    if (PsychIsGiveHelp()) { PsychGiveHelp(); return(PsychError_none); };

    PsychErrorExit(PsychCapNumInputArgs(13));       // The maximum number of inputs
    PsychErrorExit(PsychRequireNumInputArgs(1));    // The required number of inputs
    PsychErrorExit(PsychCapNumOutputArgs(12));      // The maximum number of outputs

    // Get tracker handle: This is not used yet, just here for future extensions:
    PsychCopyInIntegerArg(1, kPsychArgRequired, &handle);
    if (handle < 0)
        PsychErrorExitMsg(PsychError_user, "Invalid trackerhandle 'handle' provided. Valid handles are of value zero or greater.");

    // Firt copy out optional return values: The old / current settings of the tracker parameters:
    PsychCopyOutDoubleArg(1, kPsychArgOptional, pupilEdgeThreshold);
    PsychCopyOutDoubleArg(2, kPsychArgOptional, starburstRays);
    PsychCopyOutDoubleArg(3, kPsychArgOptional, minFeatureCandidates);
    PsychCopyOutDoubleArg(4, kPsychArgOptional, corneaWindowSize);
    PsychCopyOutDoubleArg(5, kPsychArgOptional, edgeThreshold);
    PsychCopyOutDoubleArg(6, kPsychArgOptional, gaussWidth);
    PsychCopyOutDoubleArg(7, kPsychArgOptional, maxPupilEccentricity);
    PsychCopyOutDoubleArg(8, kPsychArgOptional, initialAngleSpread);
    PsychCopyOutDoubleArg(9, kPsychArgOptional, fanoutAngle1);
    PsychCopyOutDoubleArg(10, kPsychArgOptional, fanoutAngle2);
    PsychCopyOutDoubleArg(11, kPsychArgOptional, featuresPerRay);
    PsychCopyOutDoubleArg(12, kPsychArgOptional, specialFlags);

    // Get optional parameters. The defaults are the original settings of OpenEyes, set in the initialization call...
    PsychCopyInIntegerArg(2, kPsychArgOptional, &pupilEdgeThreshold);
    if (pupilEdgeThreshold < 1) PsychErrorExitMsg(PsychError_user, "Invalid pupilEdgeThreshold provided. Must be at least 1 unit!");

    PsychCopyInIntegerArg(3, kPsychArgOptional, &starburstRays);
    if (starburstRays < 2) PsychErrorExitMsg(PsychError_user, "Invalid number of starburstRays provided. Must be at least 2 rays!");

    PsychCopyInIntegerArg(4, kPsychArgOptional, &minFeatureCandidates);
    if (minFeatureCandidates < 1) PsychErrorExitMsg(PsychError_user, "Invalid number of minFeatureCandidates provided. Must be at least 1 candidate!");

    PsychCopyInIntegerArg(5, kPsychArgOptional, &corneaWindowSize);
    if (corneaWindowSize < 3) PsychErrorExitMsg(PsychError_user, "Invalid corneaWindowSize provided. Must be at least 3 pixels!");

    PsychCopyInIntegerArg(6, kPsychArgOptional, &edgeThreshold);
    if (edgeThreshold < 5) PsychErrorExitMsg(PsychError_user, "Invalid edgeThreshold provided. Must be at least 5 units!");

    PsychCopyInIntegerArg(7, kPsychArgOptional, &gaussWidth);
    if (gaussWidth < 0) PsychErrorExitMsg(PsychError_user, "Invalid gaussWidth provided. Must be at least 0 units!");

    if (gaussWidth % 2 == 0)
        gaussWidth += 1; // Make it odd if it's even!

    PsychCopyInIntegerArg(7, kPsychArgOptional, &gaussWidth);
    if (gaussWidth < 0) PsychErrorExitMsg(PsychError_user, "Invalid gaussWidth provided. Must be at least 0 units!");

    PsychCopyInDoubleArg(8, kPsychArgOptional, &maxPupilEccentricity);
    if (maxPupilEccentricity < 1.0) PsychErrorExitMsg(PsychError_user, "Invalid maxPupilEccentricity provided. Must be at least 1.0 units!");

    PsychCopyInDoubleArg(9, kPsychArgOptional, &initialAngleSpread);
    if (initialAngleSpread > 360 || initialAngleSpread < 0)
        PsychErrorExitMsg(PsychError_user, "Invalid initialAngleSpread provided. Must be between 0 and 360 deg.!");

    // Fanout midline angle in degrees for beam fan 1: This parameter is unlimited.
    PsychCopyInDoubleArg(10, kPsychArgOptional, &fanoutAngle1);

    // Fanout midline angle in degrees for beam fan 2: This parameter is unlimited.
    PsychCopyInDoubleArg(11, kPsychArgOptional, &fanoutAngle2);

    PsychCopyInIntegerArg(12, kPsychArgOptional, &featuresPerRay);
    if (featuresPerRay < 1) PsychErrorExitMsg(PsychError_user, "Invalid featuresPerRay provided. Must be at least 1!");

    PsychCopyInIntegerArg(13, kPsychArgOptional, &specialFlags);
    if (specialFlags < 0) PsychErrorExitMsg(PsychError_user, "Invalid specialFlags provided. Must be at least 0!");

    // Commit new parameters to tracker:
    cvEyeTrackerSetParameters(pupilEdgeThreshold, starburstRays, minFeatureCandidates, corneaWindowSize, edgeThreshold,
                              gaussWidth, maxPupilEccentricity, initialAngleSpread * PI/180, fanoutAngle1 * PI/180,
                              fanoutAngle2 * PI/180, featuresPerRay, specialFlags);

    return(PsychError_none);
}

#endif

PsychError PSYCHCVCopyMatrixToMemBuffer(void)
{
    static char useString[] = "PsychCV('CopyMatrixToMemBuffer', matrix, memBufferPtr);";
    static char synopsisString[] =
        "Copies a Matlab/Octave uint8 or double matrix 'matrix' into a C memory buffer, "
        "whose memory (void*) pointer is encoded as a double scalar value 'memBufferPtr'. "
        "The target buffer must be of sufficient size, no checking is performed! This is "
        "essentially a C memcpy() operation, so use with caution, or Matlab/Octave will crash!";
    static char seeAlsoString[] = "";

    int m, n, p;
    unsigned char* inMatrixUint8;
    void* srcptr;
    double doubleptr;
    void* dstptr;
    int elsize;

    // Setup online help:
    PsychPushHelp(useString, synopsisString, seeAlsoString);
    if (PsychIsGiveHelp()) { PsychGiveHelp(); return(PsychError_none); };

    PsychErrorExit(PsychCapNumInputArgs(2));        // The maximum number of inputs
    PsychErrorExit(PsychRequireNumInputArgs(2));    // The required number of inputs
    PsychErrorExit(PsychCapNumOutputArgs(0));       // The maximum number of outputs

    // Get the uint8 or double matrix from Matlab/Octave:
    m = n = p = 0;
    if (PsychAllocInUnsignedByteMatArg(1, kPsychArgAnything, &m, &n, &p, (unsigned char**) &srcptr)) {
        elsize = sizeof(unsigned char);
    }
    else {
        if (PsychAllocInDoubleMatArg(1, kPsychArgAnything, &m, &n, &p, (double**) &srcptr)) {
            elsize = sizeof(double);
        }
        else {
            PsychErrorExitMsg(PsychError_user, "Invalid input matrix specified. Must be a uint8 or double matrix!");
        }
    }

    // Check dimensions:
    if (p < 1)
        p = 1;

    if (m < 1 || n < 1)
        PsychErrorExitMsg(PsychError_user, "Invalid input matrix specified. Must have non-zero row- and column count!");

    // Get the double-encoded void* to the destination buffer:
    PsychCopyInDoubleArg(2, kPsychArgRequired, &doubleptr);
    dstptr = PsychDoubleToPtr(doubleptr);

    // Perform memcpy to target buffer:
    memcpy(dstptr, (const void*) srcptr, elsize * m * n* p);

    return(PsychError_none);
}
