/*====================================================================*
 -  Copyright (C) 2001 Leptonica.  All rights reserved.
 -  This software is distributed in the hope that it will be
 -  useful, but with NO WARRANTY OF ANY KIND.
 -  No author or distributor accepts responsibility to anyone for the
 -  consequences of using this software, or for whether it serves any
 -  particular purpose or works at all, unless he or she says so in
 -  writing.  Everyone is granted permission to copy, modify and
 -  redistribute this source code, for commercial or non-commercial
 -  purposes, with the following restrictions: (1) the origin of this
 -  source code must not be misrepresented; (2) modified versions must
 -  be plainly marked as such; and (3) this notice may not be removed
 -  or altered from any source or modified source distribution.
 *====================================================================*/



/*
 *  kernel.c
 *
 *      Basic operations on kernels for image convolution
 *
 *         Create/destroy/copy
 *            L_KERNEL   *kernelCreate()
 *            void        kernelDestroy()
 *            L_KERNEL   *kernelCopy()
 *
 *         Accessors:
 *            l_int32     kernelGetElement()
 *            l_int32     kernelSetElement()
 *            l_int32     kernelGetParameters()
 *            l_int32     kernelSetOrigin()
 *            l_int32     kernelGetSum()
 *            l_int32     kernelGetMinMax()
 *
 *         Normalize/invert
 *            L_KERNEL   *kernelNormalize()
 *            L_KERNEL   *kernelInvert()
 *
 *         Helper function
 *            l_float32 **create2dFloatArray()
 *
 *         Serialized I/O
 *            L_KERNEL   *kernelRead()
 *            L_KERNEL   *kernelReadStream()
 *            l_int32     kernelWrite()
 *            l_int32     kernelWriteStream()
 *       
 *         Making a kernel from a compiled string
 *            L_KERNEL   *kernelCreateFromString()
 *
 *         Making a kernel from a simple file format
 *            L_KERNEL   *kernelCreateFromFile()
 *
 *         Making a kernel from a Pix
 *            L_KERNEL   *kernelCreateFromPix()
 *
 *         Display a kernel in a pix
 *            PIX        *kernelDisplayInPix()
 *
 *         Parse string to extract numbers
 *            NUMA       *parseStringForNumbers()
 *
 *      Simple parametric kernels
 *            L_KERNEL   *makeFlatKernel()
 *            L_KERNEL   *makeGaussianKernel()
 *            L_KERNEL   *makeGaussianKernelSep()
 *            L_KERNEL   *makeDoGKernel()
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "allheaders.h"


/*------------------------------------------------------------------------*
 *                           Create / Destroy                             *
 *------------------------------------------------------------------------*/
/*!
 *  kernelCreate()
 *
 *      Input:  height, width
 *      Return: kernel, or null on error
 *
 *  Notes:
 *      (1) kernelCreate() initializes all values to 0.
 *      (2) After this call, (cy,cx) and nonzero data values must be
 *          assigned.
 */
L_KERNEL *
kernelCreate(l_int32  height,
             l_int32  width)
{
L_KERNEL  *kel;

    PROCNAME("kernelCreate");

    if ((kel = (L_KERNEL *)CALLOC(1, sizeof(L_KERNEL))) == NULL)
        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
    kel->sy = height;
    kel->sx = width;
    if ((kel->data = create2dFloatArray(height, width)) == NULL)
        return (L_KERNEL *)ERROR_PTR("data not allocated", procName, NULL);

    return kel;
}


/*!
 *  kernelDestroy()
 *
 *      Input:  &kel (<to be nulled>)
 *      Return: void
 */
void
kernelDestroy(L_KERNEL  **pkel)
{
l_int32    i;
L_KERNEL  *kel;

    PROCNAME("kernelDestroy");

    if (pkel == NULL)  {
        L_WARNING("ptr address is NULL!", procName);
        return;
    }
    if ((kel = *pkel) == NULL)
        return;

    for (i = 0; i < kel->sy; i++)
        FREE(kel->data[i]);
    FREE(kel->data);
    FREE(kel);

    *pkel = NULL;
    return;
}


/*!
 *  kernelCopy()
 *
 *      Input:  kels (source kernel)
 *      Return: keld (copy of kels), or null on error
 */
L_KERNEL *
kernelCopy(L_KERNEL  *kels)
{
l_int32    i, j, sx, sy, cx, cy;
L_KERNEL  *keld;

    PROCNAME("kernelCopy");

    if (!kels)
        return (L_KERNEL *)ERROR_PTR("kels not defined", procName, NULL);

    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
    if ((keld = kernelCreate(sy, sx)) == NULL)
        return (L_KERNEL *)ERROR_PTR("keld not made", procName, NULL);
    keld->cy = cy;
    keld->cx = cx;
    for (i = 0; i < sy; i++)
        for (j = 0; j < sx; j++)
            keld->data[i][j] = kels->data[i][j];

    return keld;
}


/*----------------------------------------------------------------------*
 *                               Accessors                              *
 *----------------------------------------------------------------------*/
/*!
 *  kernelGetElement()
 *
 *      Input:  kel
 *              row
 *              col
 *              &val
 *      Return: 0 if OK; 1 on error
 */
l_int32
kernelGetElement(L_KERNEL   *kel,
                 l_int32     row,
                 l_int32     col,
                 l_float32  *pval)
{
    PROCNAME("kernelGetElement");

    if (!pval)
        return ERROR_INT("&val not defined", procName, 1);
    *pval = 0;
    if (!kel)
        return ERROR_INT("kernel not defined", procName, 1);
    if (row < 0 || row >= kel->sy)
        return ERROR_INT("kernel row out of bounds", procName, 1);
    if (col < 0 || col >= kel->sx)
        return ERROR_INT("kernel col out of bounds", procName, 1);

    *pval = kel->data[row][col];
    return 0;
}


/*!
 *  kernelSetElement()
 *
 *      Input:  kernel
 *              row
 *              col
 *              val
 *      Return: 0 if OK; 1 on error
 */
l_int32
kernelSetElement(L_KERNEL  *kel,
                 l_int32    row,
                 l_int32    col,
                 l_float32  val)
{
    PROCNAME("kernelSetElement");

    if (!kel)
        return ERROR_INT("kel not defined", procName, 1);
    if (row < 0 || row >= kel->sy)
        return ERROR_INT("kernel row out of bounds", procName, 1);
    if (col < 0 || col >= kel->sx)
        return ERROR_INT("kernel col out of bounds", procName, 1);

    kel->data[row][col] = val;
    return 0;
}


/*!
 *  kernelGetParameters()
 *
 *      Input:  kernel
 *              &sy, &sx, &cy, &cx (<optional return>; each can be null)
 *      Return: 0 if OK, 1 on error
 */
l_int32
kernelGetParameters(L_KERNEL  *kel,
                    l_int32   *psy,
                    l_int32   *psx,
                    l_int32   *pcy,
                    l_int32   *pcx)
{
    PROCNAME("kernelGetParameters");

    if (psy) *psy = 0;
    if (psx) *psx = 0;
    if (pcy) *pcy = 0;
    if (pcx) *pcx = 0;
    if (!kel)
        return ERROR_INT("kernel not defined", procName, 1);
    if (psy) *psy = kel->sy; 
    if (psx) *psx = kel->sx; 
    if (pcy) *pcy = kel->cy; 
    if (pcx) *pcx = kel->cx; 
    return 0;
}


/*!
 *  kernelSetOrigin()
 *
 *      Input:  kernel
 *              cy, cx
 *      Return: 0 if OK; 1 on error
 */
l_int32
kernelSetOrigin(L_KERNEL  *kel,
                l_int32    cy,
                l_int32    cx)
{
    PROCNAME("kernelSetOrigin");

    if (!kel)
        return ERROR_INT("kel not defined", procName, 1);
    kel->cy = cy;
    kel->cx = cx;
    return 0;
}


/*!
 *  kernelGetSum()
 *
 *      Input:  kernel
 *              &sum (<return> sum of all kernel values)
 *      Return: 0 if OK, 1 on error
 */
l_int32
kernelGetSum(L_KERNEL   *kel,
             l_float32  *psum)
{
l_int32    sx, sy, i, j;

    PROCNAME("kernelGetSum");

    if (!psum)
        return ERROR_INT("&sum not defined", procName, 1);
    *psum = 0.0;
    if (!kel)
        return ERROR_INT("kernel not defined", procName, 1);

    kernelGetParameters(kel, &sy, &sx, NULL, NULL);
    for (i = 0; i < sy; i++) {
        for (j = 0; j < sx; j++) {
            *psum += kel->data[i][j];
        }
    }
    return 0;
}


/*!
 *  kernelGetMinMax()
 *
 *      Input:  kernel
 *              &min (<optional return> minimum value)
 *              &max (<optional return> maximum value)
 *      Return: 0 if OK, 1 on error
 */
l_int32
kernelGetMinMax(L_KERNEL   *kel,
                l_float32  *pmin,
                l_float32  *pmax)
{
l_int32    sx, sy, i, j;
l_float32  val, minval, maxval;

    PROCNAME("kernelGetMinmax");

    if (!pmin && !pmax)
        return ERROR_INT("neither &min nor &max defined", procName, 1);
    if (pmin) *pmin = 0.0;
    if (pmax) *pmax = 0.0;
    if (!kel)
        return ERROR_INT("kernel not defined", procName, 1);

    kernelGetParameters(kel, &sy, &sx, NULL, NULL);
    minval = 10000000.0;
    maxval = -10000000.0;
    for (i = 0; i < sy; i++) {
        for (j = 0; j < sx; j++) {
            val = kel->data[i][j];
            if (val < minval)
                minval = val;
            if (val > maxval)
                maxval = val;
        }
    }
    if (pmin)
        *pmin = minval;
    if (pmax)
        *pmax = maxval;

    return 0;
}


/*----------------------------------------------------------------------*
 *                          Normalize/Invert                            *
 *----------------------------------------------------------------------*/
/*!
 *  kernelNormalize()
 *
 *      Input:  kels (source kel, to be normalized)
 *              normsum (desired sum of elements in keld)
 *      Return: keld (normalized version of kels), or null on error
 *                   or if sum of elements is very close to 0)
 *
 *  Notes:
 *      (1) If the sum of kernel elements is close to 0, do not
 *          try to calculate the normalized kernel.  Instead,
 *          return a copy of the input kernel, with an error message.
 */
L_KERNEL *
kernelNormalize(L_KERNEL  *kels,
                l_float32  normsum)
{
l_int32    i, j, sx, sy, cx, cy;
l_float32  sum, factor;
L_KERNEL  *keld;

    PROCNAME("kernelNormalize");

    if (!kels)
        return (L_KERNEL *)ERROR_PTR("kels not defined", procName, NULL);

    kernelGetSum(kels, &sum);
    if (L_ABS(sum) < 0.01) {
        L_ERROR("null sum; not normalizing; returning a copy", procName);
        return kernelCopy(kels);
    }

    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
    if ((keld = kernelCreate(sy, sx)) == NULL)
        return (L_KERNEL *)ERROR_PTR("keld not made", procName, NULL);
    keld->cy = cy;
    keld->cx = cx;

    factor = normsum / sum;
    for (i = 0; i < sy; i++)
        for (j = 0; j < sx; j++)
            keld->data[i][j] = factor * kels->data[i][j];

    return keld;
}


/*!
 *  kernelInvert()
 *
 *      Input:  kels (source kel, to be inverted)
 *      Return: keld (spatially inverted, about the origin), or null on error
 *
 *  Notes:
 *      (1) For convolution, the kernel is spatially inverted before
 *          a "correlation" operation is done between the kernel and the image.
 */
L_KERNEL *
kernelInvert(L_KERNEL  *kels)
{
l_int32    i, j, sx, sy, cx, cy;
L_KERNEL  *keld;

    PROCNAME("kernelInvert");

    if (!kels)
        return (L_KERNEL *)ERROR_PTR("kels not defined", procName, NULL);

    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
    if ((keld = kernelCreate(sy, sx)) == NULL)
        return (L_KERNEL *)ERROR_PTR("keld not made", procName, NULL);
    keld->cy = sy - 1 - cy;
    keld->cx = sx - 1 - cx;

    for (i = 0; i < sy; i++)
        for (j = 0; j < sx; j++)
            keld->data[i][j] = kels->data[sy - 1 - i][sx - 1 - j];

    return keld;
}


/*----------------------------------------------------------------------*
 *                            Helper function                           *
 *----------------------------------------------------------------------*/
/*!
 *  create2dFloatArray()
 *
 *      Input:  sy (rows == height)
 *              sx (columns == width)
 *      Return: doubly indexed array (i.e., an array of sy row pointers,
 *              each of which points to an array of sx floats)
 *
 *  Notes:
 *      (1) The array[sy][sx] is indexed in standard "matrix notation",
 *          with the row index first.
 */
l_float32 **
create2dFloatArray(l_int32  sy,
                   l_int32  sx)
{
l_int32      i;
l_float32  **array;

    PROCNAME("create2dFloatArray");

    if ((array = (l_float32 **)CALLOC(sy, sizeof(l_float32 *))) == NULL)
        return (l_float32 **)ERROR_PTR("ptr array not made", procName, NULL);

    for (i = 0; i < sy; i++) {
        if ((array[i] = (l_float32 *)CALLOC(sx, sizeof(l_float32))) == NULL)
            return (l_float32 **)ERROR_PTR("array not made", procName, NULL);
    }

    return array;
}


/*----------------------------------------------------------------------*
 *                            Kernel serialized I/O                     *
 *----------------------------------------------------------------------*/
/*!
 *  kernelRead()
 *
 *      Input:  filename
 *      Return: kernel, or null on error
 */
L_KERNEL *
kernelRead(const char  *fname)
{
FILE      *fp;
L_KERNEL  *kel;

    PROCNAME("kernelRead");

    if (!fname)
        return (L_KERNEL *)ERROR_PTR("fname not defined", procName, NULL);

    if ((fp = fopen(fname, "rb")) == NULL)
        return (L_KERNEL *)ERROR_PTR("stream not opened", procName, NULL);
    if ((kel = kernelReadStream(fp)) == NULL)
        return (L_KERNEL *)ERROR_PTR("kel not returned", procName, NULL);
    fclose(fp);

    return kel;
}


/*!
 *  kernelReadStream()
 *
 *      Input:  stream
 *      Return: kernel, or null on error
 */
L_KERNEL *
kernelReadStream(FILE  *fp)
{
l_int32    sy, sx, cy, cx, i, j, ret, version;
L_KERNEL  *kel;

    PROCNAME("kernelReadStream");

    if (!fp)
        return (L_KERNEL *)ERROR_PTR("stream not defined", procName, NULL);

    ret = fscanf(fp, "  Kernel Version %d\n", &version);
    if (ret != 1)
        return (L_KERNEL *)ERROR_PTR("not a kernel file", procName, NULL);
    if (version != KERNEL_VERSION_NUMBER)
        return (L_KERNEL *)ERROR_PTR("invalid kernel version", procName, NULL);

    if (fscanf(fp, "  sy = %d, sx = %d, cy = %d, cx = %d\n",
            &sy, &sx, &cy, &cx) != 4)
        return (L_KERNEL *)ERROR_PTR("dimensions not read", procName, NULL);

    if ((kel = kernelCreate(sy, sx)) == NULL)
        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
    kernelSetOrigin(kel, cy, cx);

    for (i = 0; i < sy; i++) {
        for (j = 0; j < sx; j++)
            fscanf(fp, "%15f", &kel->data[i][j]);
        fscanf(fp, "\n");
    }
    fscanf(fp, "\n");

    return kel;
}


/*!
 *  kernelWrite()
 *
 *      Input:  fname (output file)
 *              kernel
 *      Return: 0 if OK, 1 on error
 */
l_int32
kernelWrite(const char  *fname,
            L_KERNEL    *kel)
{
FILE  *fp;

    PROCNAME("kernelWrite");

    if (!fname)
        return ERROR_INT("fname not defined", procName, 1);
    if (!kel)
        return ERROR_INT("kel not defined", procName, 1);

    if ((fp = fopen(fname, "wb")) == NULL)
        return ERROR_INT("stream not opened", procName, 1);
    kernelWriteStream(fp, kel);
    fclose(fp);

    return 0;
}


/*!
 *  kernelWriteStream()
 *
 *      Input:  stream
 *              kel
 *      Return: 0 if OK, 1 on error
 */
l_int32
kernelWriteStream(FILE      *fp,
                  L_KERNEL  *kel)
{
l_int32  sx, sy, cx, cy, i, j;

    PROCNAME("kernelWriteStream");

    if (!fp)
        return ERROR_INT("stream not defined", procName, 1);
    if (!kel)
        return ERROR_INT("kel not defined", procName, 1);
    kernelGetParameters(kel, &sy, &sx, &cy, &cx);

    fprintf(fp, "  Kernel Version %d\n", KERNEL_VERSION_NUMBER);
    fprintf(fp, "  sy = %d, sx = %d, cy = %d, cx = %d\n", sy, sx, cy, cx);
    for (i = 0; i < sy; i++) {
        for (j = 0; j < sx; j++)
            fprintf(fp, "%15.4f", kel->data[i][j]);
        fprintf(fp, "\n");
    }
    fprintf(fp, "\n");

    return 0;
}


/*----------------------------------------------------------------------*
 *                 Making a kernel from a compiled string               *
 *----------------------------------------------------------------------*/
/*!
 *  kernelCreateFromString()
 *
 *      Input:  height, width
 *              cy, cx   (origin)
 *              kdata
 *      Return: kernel of the given size, or null on error
 *
 *  Notes:
 *      (1) The data is an array of chars, in row-major order, giving
 *          space separated integers in the range [-255 ... 255].
 *      (2) The only other formatting limitation is that you must
 *          leave space between the last number in each row and
 *          the double-quote.  If possible, it's also nice to have each
 *          line in the string represent a line in the kernel; e.g.,
 *              static const char *kdata =
 *                  " 20   50   20 "
 *                  " 70  140   70 "
 *                  " 20   50   20 ";
 */
L_KERNEL *
kernelCreateFromString(l_int32      h,
                       l_int32      w,
                       l_int32      cy,
                       l_int32      cx,
                       const char  *kdata)
{
l_int32    n, i, j, index;
l_float32  val;
L_KERNEL  *kel;
NUMA      *na;

    PROCNAME("kernelCreateFromString");

    if (h < 1)
        return (L_KERNEL *)ERROR_PTR("height must be > 0", procName, NULL);
    if (w < 1)
        return (L_KERNEL *)ERROR_PTR("width must be > 0", procName, NULL);
    if (cy < 0 || cy >= h)
        return (L_KERNEL *)ERROR_PTR("cy invalid", procName, NULL);
    if (cx < 0 || cx >= w)
        return (L_KERNEL *)ERROR_PTR("cx invalid", procName, NULL);
    
    kel = kernelCreate(h, w);
    kernelSetOrigin(kel, cy, cx);
    na = parseStringForNumbers(kdata, " \t\n");
    n = numaGetCount(na);
    if (n != w * h) {
        numaDestroy(&na);
	fprintf(stderr, "w = %d, h = %d, num ints = %d\n", w, h, n);
        return (L_KERNEL *)ERROR_PTR("invalid integer data", procName, NULL);
    }

    index = 0;
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            numaGetFValue(na, index, &val);
            kernelSetElement(kel, i, j, val);
	    index++;
        }
    }

    numaDestroy(&na);
    return kel;
}


/*----------------------------------------------------------------------*
 *                Making a kernel from a simple file format             *
 *----------------------------------------------------------------------*/
/*!
 *  kernelCreateFromFile()
 *
 *      Input:  filename
 *      Return: kernel, or null on error
 *
 *  Notes:
 *      (1) The file contains, in the following order:
 *           - Any number of comment lines starting with '#' are ignored
 *           - The height and width of the kernel
 *           - The y and x values of the kernel origin
 *           - The kernel data, formatted as lines of numbers (integers
 *             or floats) for the kernel values in row-major order,
 *             and with no other punctuation.
 *             (Note: this differs from kernelCreateFromString(),
 *             where each line must begin and end with a double-quote
 *             to tell the compiler it's part of a string.)
 *           - The kernel specification ends when a blank line,
 *             a comment line, or the end of file is reached.
 *      (2) All lines must be left-justified.
 *      (3) See kernelCreateFromString() for a description of the string
 *          format for the kernel data.  As an example, here are the lines
 *          of a valid kernel description file  In the file, all lines 
 *          are left-justified:
 *                    # small 3x3 kernel
 *                    3 3
 *                    1 1
 *                    25.5   51    24.3
 *                    70.2  146.3  73.4
 *                    20     50.9  18.4 
 */
L_KERNEL *
kernelCreateFromFile(const char  *filename)
{
char      *filestr, *line;
l_int32    nbytes, nlines, i, j, first, index, w, h, cx, cy, n;
l_float32  val;
NUMA      *na, *nat;
SARRAY    *sa;
L_KERNEL  *kel;

    PROCNAME("kernelCreateFromFile");

    if (!filename)
        return (L_KERNEL *)ERROR_PTR("filename not defined", procName, NULL);
    
    filestr = (char *)arrayRead(filename, &nbytes);
    sa = sarrayCreateLinesFromString(filestr, 1);
    FREE(filestr);
    nlines = sarrayGetCount(sa);

        /* Find the first data line. */
    for (i = 0; i < nlines; i++) {
        line = sarrayGetString(sa, i, L_NOCOPY);
	if (line[0] != '#') {
            first = i;
            break;
        }
    }

        /* Find the kernel dimensions and origin location. */
    line = sarrayGetString(sa, first, L_NOCOPY);
    if (sscanf(line, "%d %d", &h, &w) != 2)
        return (L_KERNEL *)ERROR_PTR("error reading h,w", procName, NULL);
    line = sarrayGetString(sa, first + 1, L_NOCOPY);
    if (sscanf(line, "%d %d", &cy, &cx) != 2)
        return (L_KERNEL *)ERROR_PTR("error reading cy,cx", procName, NULL);

        /* Extract the data.  This ends when we reach eof, or when we
	 * encounter a line of data that is either a null string or
	 * contains just a newline. */
    na = numaCreate(0);
    for (i = first + 2; i < nlines; i++) {
        line = sarrayGetString(sa, i, L_NOCOPY);
        if (line[0] == '\0' || line[0] == '\n' || line[0] == '#')
            break;
        nat = parseStringForNumbers(line, " \t\n");
	numaJoin(na, nat, 0, 0);
	numaDestroy(&nat);
    }
    sarrayDestroy(&sa);

    n = numaGetCount(na);
    if (n != w * h) {
        numaDestroy(&na);
	fprintf(stderr, "w = %d, h = %d, num ints = %d\n", w, h, n);
        return (L_KERNEL *)ERROR_PTR("invalid integer data", procName, NULL);
    }

    kel = kernelCreate(h, w);
    kernelSetOrigin(kel, cy, cx);
    index = 0;
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            numaGetFValue(na, index, &val);
            kernelSetElement(kel, i, j, val);
	    index++;
        }
    }

    numaDestroy(&na);
    return kel;
}


/*----------------------------------------------------------------------*
 *                       Making a kernel from a Pix                     *
 *----------------------------------------------------------------------*/
/*!
 *  kernelCreateFromPix()
 *
 *      Input:  pix
 *              cy, cx (origin of kernel)
 *      Return: kernel, or null on error
 *
 *  Notes:
 *      (1) The origin must be positive and within the dimensions of the pix.
 */
L_KERNEL *
kernelCreateFromPix(PIX         *pix,
                    l_int32      cy,
                    l_int32      cx)
{
l_int32    i, j, w, h, d;
l_uint32   val;
L_KERNEL  *kel;

    PROCNAME("kernelCreateFromPix");

    if (!pix)
        return (L_KERNEL *)ERROR_PTR("pix not defined", procName, NULL);
    pixGetDimensions(pix, &w, &h, &d);
    if (d != 8)
        return (L_KERNEL *)ERROR_PTR("pix not 8 bpp", procName, NULL);
    if (cy < 0 || cx < 0 || cy >= h || cx >= w)
        return (L_KERNEL *)ERROR_PTR("(cy, cx) invalid", procName, NULL);

    kel = kernelCreate(h, w);
    kernelSetOrigin(kel, cy, cx);
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            pixGetPixel(pix, j, i, &val);
            kernelSetElement(kel, i, j, (l_float32)val);
        }
    }

    return kel;
}


/*----------------------------------------------------------------------*
 *                     Display a kernel in a pix                        *
 *----------------------------------------------------------------------*/
/*!
 *  kernelDisplayInPix()
 *
 *      Input:  kernel
 *              size (of grid interiors; odd; minimum size of 17 is enforced)
 *              gthick (grid thickness; minimum size of 2 is enforced)
 *      Return: pix (display of kernel), or null on error
 *
 *  Notes:
 *      (1) This gives a visual representation of a kernel.
 *      (2) The origin is outlined in red.
 */
PIX *
kernelDisplayInPix(L_KERNEL     *kel,
                   l_int32       size,
                   l_int32       gthick)
{
l_int32    i, j, w, h, sx, sy, cx, cy, width, x0, y0;
l_int32    normval;
l_float32  minval, maxval, max, val, norm;
PIX       *pixd, *pixt0, *pixt1;

    PROCNAME("kernelDisplayInPix");

    if (!kel)
        return (PIX *)ERROR_PTR("kernel not defined", procName, NULL);
    if (size < 17) {
        L_WARNING("size < 17; setting to 17", procName);
        size = 17;
    }
    if (size % 2 == 0)
        size++;
    if (gthick < 2) {
        L_WARNING("grid thickness < 2; setting to 2", procName);
        gthick = 2;
    }

        /* Normalize the max value to be 255 for display */
    kernelGetParameters(kel, &sy, &sx, &cy, &cx);
    kernelGetMinMax(kel, &minval, &maxval);
    max = L_MAX(maxval, -minval);
    norm = 255. / (l_float32)max;
    w = size * sx + gthick * (sx + 1);
    h = size * sy + gthick * (sy + 1);
    pixd = pixCreate(w, h, 8);

        /* Generate grid lines */
    for (i = 0; i <= sy; i++)
        pixRenderLine(pixd, 0, gthick / 2 + i * (size + gthick),
                      w - 1, gthick / 2 + i * (size + gthick),
                      gthick, L_SET_PIXELS);
    for (j = 0; j <= sx; j++)
        pixRenderLine(pixd, gthick / 2 + j * (size + gthick), 0,
                      gthick / 2 + j * (size + gthick), h - 1,
                      gthick, L_SET_PIXELS);

        /* Generate mask for each element */
    pixt0 = pixCreate(size, size, 1);
    pixSetAll(pixt0);

        /* Generate crossed lines for origin pattern */
    pixt1 = pixCreate(size, size, 1);
    width = size / 8;
    pixRenderLine(pixt1, size / 2, (l_int32)(0.12 * size),
                           size / 2, (l_int32)(0.88 * size),
                           width, L_SET_PIXELS);
    pixRenderLine(pixt1, (l_int32)(0.15 * size), size / 2,
                           (l_int32)(0.85 * size), size / 2,
                           width, L_FLIP_PIXELS);
    pixRasterop(pixt1, size / 2 - width, size / 2 - width,
                2 * width, 2 * width, PIX_NOT(PIX_DST), NULL, 0, 0);

        /* Paste the patterns in */
    y0 = gthick;
    for (i = 0; i < sy; i++) {
        x0 = gthick;
        for (j = 0; j < sx; j++) {
            kernelGetElement(kel, i, j, &val);
            normval = (l_int32)(norm * L_ABS(val));
            pixSetMaskedGeneral(pixd, pixt0, normval, x0, y0);
	    if (i == cy && j == cx)
                pixPaintThroughMask(pixd, pixt1, x0, y0, 255 - normval);
            x0 += size + gthick;
        }
        y0 += size + gthick;
    }

    pixDestroy(&pixt0);
    pixDestroy(&pixt1);
    return pixd;
}


/*------------------------------------------------------------------------*
 *                     Parse string to extract numbers                    *
 *------------------------------------------------------------------------*/
/*!
 *  parseStringForNumbers()
 *
 *      Input:  string (containing numbers; not changed)
 *              seps (string of characters that can be used between ints)
 *      Return: numa (of numbers found), or null on error
 *
 *  Note:
 *     (1) The numbers can be ints or floats.
 */
NUMA *
parseStringForNumbers(const char  *str,
                      const char  *seps)
{
char      *newstr, *head, *tail;
l_float32  val;
NUMA      *na;

    PROCNAME("parseStringForNumbers");

    if (!str)
        return (NUMA *)ERROR_PTR("str not defined", procName, NULL);

    newstr = stringNew(str);  /* to enforce const-ness of str */
    na = numaCreate(0);
    head = strtokSafe(newstr, seps, &tail);
    val = atof(head);
    numaAddNumber(na, val);
    FREE(head);
    while ((head = strtokSafe(NULL, seps, &tail)) != NULL) {
        val = atof(head);
        numaAddNumber(na, val);
        FREE(head);
    }

    FREE(newstr);
    return na;
}


/*------------------------------------------------------------------------*
 *                        Simple parametric kernels                       *
 *------------------------------------------------------------------------*/
/*!
 *  makeFlatKernel()
 *
 *      Input:  height, width
 *              cy, cx (origin of kernel)
 *      Return: kernel, or null on error
 *
 *  Notes:
 *      (1) This is the same low-pass filtering kernel that is used
 *          in the block convolution functions.
 *      (2) The kernel origin (@cy, @cx) is typically placed as near
 *          the center of the kernel as possible.  If height and
 *          width are odd, then using cy = (height - 1) / 2 and
 *          cx = (width - 1) / 2 places the origin at the exact center.
 *      (3) This returns a normalized kernel.
 */
L_KERNEL *
makeFlatKernel(l_int32  height,
               l_int32  width,
               l_int32  cy,
               l_int32  cx)
{
l_int32    i, j;
l_float32  normval;
L_KERNEL  *kel;

    PROCNAME("makeFlatKernel");

    if ((kel = kernelCreate(height, width)) == NULL)
        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
    kernelSetOrigin(kel, cy, cx);
    normval = 1.0 / (l_float32)(height * width);
    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            kernelSetElement(kel, i, j, normval);
        }
    }

    return kel;
}


/*!
 *  makeGaussianKernel()
 *
 *      Input:  halfheight, halfwidth (sx = 2 * halfwidth + 1, etc)
 *              stdev (standard deviation)
 *              max (value at (cx,cy))
 *      Return: kernel, or null on error
 *
 *  Notes:
 *      (1) The kernel size (sx, sy) = (2 * halfwidth + 1, 2 * halfheight + 1).
 *      (2) The kernel center (cx, cy) = (halfwidth, halfheight).
 *      (3) The halfwidth and halfheight are typically equal, and
 *          are typically several times larger than the standard deviation.
 *      (4) If pixConvolve() is invoked with normalization (the sum of
 *          kernel elements = 1.0), use 1.0 for max (or any number that's
 *          not too small or too large).
 */
L_KERNEL *
makeGaussianKernel(l_int32    halfheight,
                   l_int32    halfwidth,
                   l_float32  stdev,
                   l_float32  max)
{
l_int32    sx, sy, i, j;
l_float32  val;
L_KERNEL  *kel;

    PROCNAME("makeGaussianKernel");

    sx = 2 * halfwidth + 1;
    sy = 2 * halfheight + 1;
    if ((kel = kernelCreate(sy, sx)) == NULL)
        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
    kernelSetOrigin(kel, halfheight, halfwidth);
    for (i = 0; i < sy; i++) {
        for (j = 0; j < sx; j++) {
            val = expf(-(l_float32)((i - halfheight) * (i - halfheight) +
                                    (j - halfwidth) * (j - halfwidth)) /
                        (2. * stdev * stdev));
            kernelSetElement(kel, i, j, max * val);
        }
    }

    return kel;
}


/*!
 *  makeGaussianKernelSep()
 *
 *      Input:  halfheight, halfwidth (sx = 2 * halfwidth + 1, etc)
 *              stdev (standard deviation)
 *              max (value at (cx,cy))
 *              &kelx (<return> x part of kernel)
 *              &kely (<return> y part of kernel)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) See makeGaussianKernel() for description of input parameters.
 *      (2) These kernels are constructed so that the result of both
 *          normalized and un-normalized convolution will be the same
 *          as when convolving with pixConvolve() using the full kernel.
 *      (3) The trick for the un-normalized convolution is to have the
 *          product of the two kernel elemets at (cx,cy) be equal to max,
 *          not max**2.  That's why the max for kely is 1.0.  If instead
 *          we use sqrt(max) for both, the results are slightly less
 *          accurate, when compared to using the full kernel in
 *          makeGaussianKernel().
 */
l_int32
makeGaussianKernelSep(l_int32    halfheight,
                      l_int32    halfwidth,
                      l_float32  stdev,
                      l_float32  max,
                      L_KERNEL **pkelx,
                      L_KERNEL **pkely)
{
    PROCNAME("makeGaussianKernelSep");

    if (!pkelx || !pkely)
        return ERROR_INT("&kelx and &kely not defined", procName, 1);

    *pkelx = makeGaussianKernel(0, halfwidth, stdev, max);
    *pkely = makeGaussianKernel(halfheight, 0, stdev, 1.0);
    return 0;
}


/*!
 *  makeDoGKernel()
 *
 *      Input:  halfheight, halfwidth (sx = 2 * halfwidth + 1, etc)
 *              stdev (standard deviation)
 *              ratio (of stdev for wide filter to stdev for narrow one)
 *      Return: kernel, or null on error
 *
 *  Notes:
 *      (1) The DoG (difference of gaussians) is a wavelet mother
 *          function with null total sum.  By subtracting two blurred
 *          versions of the image, it acts as a bandpass filter for
 *          frequencies passed by the narrow gaussian but stopped
 *          by the wide one.See:
 *               http://en.wikipedia.org/wiki/Difference_of_Gaussians
 *      (2) The kernel size (sx, sy) = (2 * halfwidth + 1, 2 * halfheight + 1).
 *      (3) The kernel center (cx, cy) = (halfwidth, halfheight).
 *      (4) The halfwidth and halfheight are typically equal, and
 *          are typically several times larger than the standard deviation.
 *      (5) The ratio is the ratio of standard deviations of the wide
 *          to narrow gaussian.  It must be >= 1.0; 1.0 is a no-op.
 *      (6) Because the kernel is a null sum, it must be invoked without
 *          normalization in pixConvolve().
 */
L_KERNEL *
makeDoGKernel(l_int32    halfheight,
              l_int32    halfwidth,
              l_float32  stdev,
              l_float32  ratio)
{
l_int32    sx, sy, i, j;
l_float32  pi, squaredist, highnorm, lownorm, val;
L_KERNEL  *kel;

    PROCNAME("makeDoGKernel");

    sx = 2 * halfwidth + 1;
    sy = 2 * halfheight + 1;
    if ((kel = kernelCreate(sy, sx)) == NULL)
        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
    kernelSetOrigin(kel, halfheight, halfwidth);

    pi = 3.1415926535;
    for (i = 0; i < sy; i++) {
        for (j = 0; j < sx; j++) {
            squaredist = (l_float32)((i - halfheight) * (i - halfheight) +
                                     (j - halfwidth) * (j - halfwidth));
            highnorm = 1. / (2 * stdev * stdev);
            lownorm = highnorm / (ratio * ratio);
            val = (highnorm / pi) * expf(-(highnorm * squaredist)) -
                  (lownorm / pi) * expf(-(lownorm * squaredist));
            kernelSetElement(kel, i, j, val);
        }
    }

    return kel;
}


