blob: 688a2f2260a5a5c9f53da6a6de98be08b20341ce [file] [log] [blame]
/*====================================================================*
- 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 *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 *
*------------------------------------------------------------------------*/
/*!
* 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;
}