/*====================================================================* - 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 #include #include #include #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 () * 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 (; 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 ( 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 ( minimum value) * &max ( 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 ( x part of kernel) * &kely ( 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; }