static l_int32 GenerateSplitPlot(l_int32 i) { char title[256]; l_int32 split; l_float32 ave1, ave2, num1, num2, maxnum, maxscore; GPLOT *gplot; NUMA *na1, *na2, *nascore, *nax, *nay; PIX *pixs, *pixd; /* Generate */ na1 = MakeGaussian(gaussmean1[i], gaussstdev1[i], gaussfract1[i]); na2 = MakeGaussian(gaussmean2[i], gaussstdev1[i], 1.0 - gaussfract1[i]); numaArithOp(na1, na1, na2, L_ARITH_ADD); /* Otsu splitting */ numaSplitDistribution(na1, 0.08, &split, &ave1, &ave2, &num1, &num2, &nascore); fprintf(stderr, "split = %d, ave1 = %6.1f, ave2 = %6.1f\n", split, ave1, ave2); fprintf(stderr, "num1 = %8.0f, num2 = %8.0f\n", num1, num2); /* Prepare for plotting a vertical line at the split point */ nax = numaMakeConstant(split, 2); numaGetMax(na1, &maxnum, NULL); nay = numaMakeConstant(0, 2); numaReplaceNumber(nay, 1, (l_int32)(0.5 * maxnum)); /* Plot the input histogram with the split location */ sprintf(buf, "/tmp/junkplot.%d", i); sprintf(title, "Plot %d", i); gplot = gplotCreate(buf, GPLOT_PNG, "Histogram: mixture of 2 gaussians", "Grayscale value", "Number of pixels"); gplotAddPlot(gplot, NULL, na1, GPLOT_LINES, title); gplotAddPlot(gplot, nax, nay, GPLOT_LINES, NULL); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&na1); numaDestroy(&na2); /* Plot the score function */ sprintf(buf, "/tmp/junkplots.%d", i); sprintf(title, "Plot %d", i); gplot = gplotCreate(buf, GPLOT_PNG, "Otsu score function for splitting", "Grayscale value", "Score"); gplotAddPlot(gplot, NULL, nascore, GPLOT_LINES, title); numaGetMax(nascore, &maxscore, NULL); numaReplaceNumber(nay, 1, maxscore); gplotAddPlot(gplot, nax, nay, GPLOT_LINES, NULL); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&nax); numaDestroy(&nay); numaDestroy(&nascore); return 0; }
/*! * fpixBuildHorizontalDisparity() * * Input: fpixv (vertical disparity model) * factor (conversion factor for vertical disparity slope; * use 0 for default) * &extraw (<return> extra width to be added to dewarped pix) * Return: fpixh, or null on error * * Notes: * (1) This takes the difference in vertical disparity at top * and bottom of the image, and converts it to an assumed * horizontal disparity. */ FPIX * fpixBuildHorizontalDisparity(FPIX *fpixv, l_float32 factor, l_int32 *pextraw) { l_int32 w, h, i, j, fw, wpl, maxloc; l_float32 val1, val2, vdisp, vdisp0, maxval; l_float32 *data, *line, *fadiff; NUMA *nadiff; FPIX *fpixh; PROCNAME("fpixBuildHorizontalDisparity"); if (!fpixv) return (FPIX *)ERROR_PTR("fpixv not defined", procName, NULL); if (!pextraw) return (FPIX *)ERROR_PTR("&extraw not defined", procName, NULL); if (factor == 0.0) factor = DEFAULT_SLOPE_FACTOR; /* Estimate horizontal disparity from the vertical disparity * difference between the top and bottom, normalized to the * image height. Add the maximum value to the width of the * output image, so that all src pixels can be mapped * into the dest. */ fpixGetDimensions(fpixv, &w, &h); nadiff = numaCreate(w); for (j = 0; j < w; j++) { fpixGetPixel(fpixv, j, 0, &val1); fpixGetPixel(fpixv, j, h - 1, &val2); vdisp = factor * (val2 - val1) / (l_float32)h; if (j == 0) vdisp0 = vdisp; vdisp = vdisp0 - vdisp; numaAddNumber(nadiff, vdisp); } numaGetMax(nadiff, &maxval, &maxloc); *pextraw = (l_int32)(maxval + 0.5); fw = w + *pextraw; fpixh = fpixCreate(fw, h); data = fpixGetData(fpixh); wpl = fpixGetWpl(fpixh); fadiff = numaGetFArray(nadiff, L_NOCOPY); for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < fw; j++) { if (j < maxloc) /* this may not work for even pages */ line[j] = fadiff[j]; else /* keep it at the max value the rest of the way across */ line[j] = maxval; } } numaDestroy(&nadiff); return fpixh; }
/*! * \brief pixFindBaselines() * * \param[in] pixs 1 bpp, 300 ppi * \param[out] ppta [optional] pairs of pts corresponding to * approx. ends of each text line * \param[in] pixadb for debug output; use NULL to skip * \return na of baseline y values, or NULL on error * * <pre> * Notes: * (1) Input binary image must have text lines already aligned * horizontally. This can be done by either rotating the * image with pixDeskew(), or, if a projective transform * is required, by doing pixDeskewLocal() first. * (2) Input null for &pta if you don't want this returned. * The pta will come in pairs of points (left and right end * of each baseline). * (3) Caution: this will not work properly on text with multiple * columns, where the lines are not aligned between columns. * If there are multiple columns, they should be extracted * separately before finding the baselines. * (4) This function constructs different types of output * for baselines; namely, a set of raster line values and * a set of end points of each baseline. * (5) This function was designed to handle short and long text lines * without using dangerous thresholds on the peak heights. It does * this by combining the differential signal with a morphological * analysis of the locations of the text lines. One can also * combine this data to normalize the peak heights, by weighting * the differential signal in the region of each baseline * by the inverse of the width of the text line found there. * </pre> */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, PIXA *pixadb) { l_int32 h, i, j, nbox, val1, val2, ndiff, bx, by, bw, bh; l_int32 imaxloc, peakthresh, zerothresh, inpeak; l_int32 mintosearch, max, maxloc, nloc, locval; l_int32 *array; l_float32 maxval; BOXA *boxa1, *boxa2, *boxa3; GPLOT *gplot; NUMA *nasum, *nadiff, *naloc, *naval; PIX *pix1, *pix2; PTA *pta; PROCNAME("pixFindBaselines"); if (ppta) *ppta = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Close up the text characters, removing noise */ pix1 = pixMorphSequence(pixs, "c25.1 + e15.1", 0); /* Estimate the resolution */ if (pixadb) pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pix1, NULL)) == NULL) { pixDestroy(&pix1); return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); } h = pixGetHeight(pixs); nadiff = numaCreate(h); numaGetIValue(nasum, 0, &val2); for (i = 0; i < h - 1; i++) { val1 = val2; numaGetIValue(nasum, i + 1, &val2); numaAddNumber(nadiff, val1 - val2); } numaDestroy(&nasum); if (pixadb) { /* show the difference signal */ lept_mkdir("lept/baseline"); gplotSimple1(nadiff, GPLOT_PNG, "/tmp/lept/baseline/diff", "Diff Sig"); pix2 = pixRead("/tmp/lept/baseline/diff.png"); pixaAddPix(pixadb, pix2, L_INSERT); } /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); numaDestroy(&nadiff); /* Use this to begin locating a new peak: */ peakthresh = (l_int32)maxval / PEAK_THRESHOLD_RATIO; /* Use this to begin a region between peaks: */ zerothresh = (l_int32)maxval / ZERO_THRESHOLD_RATIO; naloc = numaCreate(0); naval = numaCreate(0); inpeak = FALSE; for (i = 0; i < ndiff; i++) { if (inpeak == FALSE) { if (array[i] > peakthresh) { /* transition to in-peak */ inpeak = TRUE; mintosearch = i + MIN_DIST_IN_PEAK; /* accept no zeros * between i and mintosearch */ max = array[i]; maxloc = i; } } else { /* inpeak == TRUE; look for max */ if (array[i] > max) { max = array[i]; maxloc = i; mintosearch = i + MIN_DIST_IN_PEAK; } else if (i > mintosearch && array[i] <= zerothresh) { /* leave */ inpeak = FALSE; numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } } } LEPT_FREE(array); /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } if (pixadb) { /* show the raster locations for the peaks */ gplot = gplotCreate("/tmp/lept/baseline/loc", GPLOT_PNG, "Peak locs", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); pix2 = pixRead("/tmp/lept/baseline/loc.png"); pixaAddPix(pixadb, pix2, L_INSERT); } numaDestroy(&naval); /* Generate an approximate profile of text line width. * First, filter the boxes of text, where there may be * more than one box for a given textline. */ pix2 = pixMorphSequence(pix1, "r11 + c20.1 + o30.1 +c1.3", 0); if (pixadb) pixaAddPix(pixadb, pix2, L_COPY); boxa1 = pixConnComp(pix2, NULL, 4); pixDestroy(&pix1); pixDestroy(&pix2); if (boxaGetCount(boxa1) == 0) { numaDestroy(&naloc); boxaDestroy(&boxa1); L_INFO("no compnents after filtering\n", procName); return NULL; } boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* Optionally, find the baseline segments */ pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } if (pta) { nloc = numaGetCount(naloc); nbox = boxaGetCount(boxa3); for (i = 0; i < nbox; i++) { boxaGetBoxGeometry(boxa3, i, &bx, &by, &bw, &bh); for (j = 0; j < nloc; j++) { numaGetIValue(naloc, j, &locval); if (L_ABS(locval - (by + bh)) > 25) continue; ptaAddPt(pta, bx, locval); ptaAddPt(pta, bx + bw, locval); break; } } } boxaDestroy(&boxa3); if (pixadb && pta) { /* display baselines */ l_int32 npts, x1, y1, x2, y2; pix1 = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pix1, x1, y1, x2, y2, 2, 255, 0, 0); } pixWrite("/tmp/lept/baseline/baselines.png", pix1, IFF_PNG); pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); pixDestroy(&pix1); } return naloc; }
/*! * pixFindSkewSweepAndSearchScorePivot() * * Input: pixs (1 bpp) * &angle (<return> angle required to deskew; in degrees) * &conf (<return> confidence given by ratio of max/min score) * &endscore (<optional return> max score; use NULL to ignore) * redsweep (sweep reduction factor = 1, 2, 4 or 8) * redsearch (binary search reduction factor = 1, 2, 4 or 8; * and must not exceed redsweep) * sweepcenter (angle about which sweep is performed; in degrees) * sweeprange (half the full range, taken about sweepcenter; * in degrees) * sweepdelta (angle increment of sweep; in degrees) * minbsdelta (min binary search increment angle; in degrees) * pivot (L_SHEAR_ABOUT_CORNER, L_SHEAR_ABOUT_CENTER) * Return: 0 if OK, 1 on error or if angle measurment not valid * * Notes: * (1) See notes in pixFindSkewSweepAndSearchScore(). * (2) This allows choice of shear pivoting from either the UL corner * or the center. For small angles, the ability to discriminate * angles is better with shearing from the UL corner. However, * for large angles (say, greater than 20 degrees), it is better * to shear about the center because a shear from the UL corner * loses too much of the image. */ l_int32 pixFindSkewSweepAndSearchScorePivot(PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_float32 *pendscore, l_int32 redsweep, l_int32 redsearch, l_float32 sweepcenter, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_int32 pivot) { l_int32 ret, bzero, i, nangles, n, ratio, maxindex, minloc; l_int32 width, height; l_float32 deg2rad, theta, delta; l_float32 sum, maxscore, maxangle; l_float32 centerangle, leftcenterangle, rightcenterangle; l_float32 lefttemp, righttemp; l_float32 bsearchscore[5]; l_float32 minscore, minthresh; l_float32 rangeleft; NUMA *natheta, *nascore; PIX *pixsw, *pixsch, *pixt1, *pixt2; PROCNAME("pixFindSkewSweepAndSearchScorePivot"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetDepth(pixs) != 1) return ERROR_INT("pixs not 1 bpp", procName, 1); if (!pangle) return ERROR_INT("&angle not defined", procName, 1); if (!pconf) return ERROR_INT("&conf not defined", procName, 1); if (redsweep != 1 && redsweep != 2 && redsweep != 4 && redsweep != 8) return ERROR_INT("redsweep must be in {1,2,4,8}", procName, 1); if (redsearch != 1 && redsearch != 2 && redsearch != 4 && redsearch != 8) return ERROR_INT("redsearch must be in {1,2,4,8}", procName, 1); if (redsearch > redsweep) return ERROR_INT("redsearch must not exceed redsweep", procName, 1); if (pivot != L_SHEAR_ABOUT_CORNER && pivot != L_SHEAR_ABOUT_CENTER) return ERROR_INT("invalid pivot", procName, 1); *pangle = 0.0; *pconf = 0.0; deg2rad = 3.1415926535 / 180.; ret = 0; /* Generate reduced image for binary search, if requested */ if (redsearch == 1) pixsch = pixClone(pixs); else if (redsearch == 2) pixsch = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); else if (redsearch == 4) pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0); else /* redsearch == 8 */ pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0); pixZero(pixsch, &bzero); if (bzero) { pixDestroy(&pixsch); return 1; } /* Generate reduced image for sweep, if requested */ ratio = redsweep / redsearch; if (ratio == 1) { pixsw = pixClone(pixsch); } else { /* ratio > 1 */ if (ratio == 2) pixsw = pixReduceRankBinaryCascade(pixsch, 1, 0, 0, 0); else if (ratio == 4) pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 0, 0); else /* ratio == 8 */ pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 2, 0); } pixt1 = pixCreateTemplate(pixsw); if (ratio == 1) pixt2 = pixClone(pixt1); else pixt2 = pixCreateTemplate(pixsch); nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1); natheta = numaCreate(nangles); nascore = numaCreate(nangles); if (!pixsch || !pixsw) { ret = ERROR_INT("pixsch and pixsw not both made", procName, 1); goto cleanup; } if (!pixt1 || !pixt2) { ret = ERROR_INT("pixt1 and pixt2 not both made", procName, 1); goto cleanup; } if (!natheta || !nascore) { ret = ERROR_INT("natheta and nascore not both made", procName, 1); goto cleanup; } /* Do sweep */ rangeleft = sweepcenter - sweeprange; for (i = 0; i < nangles; i++) { theta = rangeleft + i * sweepdelta; /* degrees */ /* Shear pix and put the result in pixt1 */ if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE); else pixVShearCenter(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE); /* Get score */ pixFindDifferentialSquareSum(pixt1, &sum); #if DEBUG_PRINT_SCORES L_INFO("sum(%7.2f) = %7.0f\n", procName, theta, sum); #endif /* DEBUG_PRINT_SCORES */ /* Save the result in the output arrays */ numaAddNumber(nascore, sum); numaAddNumber(natheta, theta); } /* Find the largest of the set (maxscore at maxangle) */ numaGetMax(nascore, &maxscore, &maxindex); numaGetFValue(natheta, maxindex, &maxangle); #if DEBUG_PRINT_SWEEP L_INFO(" From sweep: angle = %7.3f, score = %7.3f\n", procName, maxangle, maxscore); #endif /* DEBUG_PRINT_SWEEP */ #if DEBUG_PLOT_SCORES /* Plot the sweep result -- the scores versus rotation angle -- * using gnuplot with GPLOT_LINES (lines connecting data points). */ {GPLOT *gplot; gplot = gplotCreate("sweep_output", GPLOT_PNG, "Sweep. Variance of difference of ON pixels vs. angle", "angle (deg)", "score"); gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1"); gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } #endif /* DEBUG_PLOT_SCORES */ /* Check if the max is at the end of the sweep. */ n = numaGetCount(natheta); if (maxindex == 0 || maxindex == n - 1) { L_WARNING("max found at sweep edge\n", procName); goto cleanup; } /* Empty the numas for re-use */ numaEmpty(nascore); numaEmpty(natheta); /* Do binary search to find skew angle. * First, set up initial three points. */ centerangle = maxangle; if (pivot == L_SHEAR_ABOUT_CORNER) { pixVShearCorner(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]); pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle - sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]); pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle + sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]); } else { pixVShearCenter(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]); pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle - sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]); pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle + sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]); } numaAddNumber(nascore, bsearchscore[2]); numaAddNumber(natheta, centerangle); numaAddNumber(nascore, bsearchscore[0]); numaAddNumber(natheta, centerangle - sweepdelta); numaAddNumber(nascore, bsearchscore[4]); numaAddNumber(natheta, centerangle + sweepdelta); /* Start the search */ delta = 0.5 * sweepdelta; while (delta >= minbsdelta) { /* Get the left intermediate score */ leftcenterangle = centerangle - delta; if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt2, pixsch, deg2rad * leftcenterangle, L_BRING_IN_WHITE); else pixVShearCenter(pixt2, pixsch, deg2rad * leftcenterangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[1]); numaAddNumber(nascore, bsearchscore[1]); numaAddNumber(natheta, leftcenterangle); /* Get the right intermediate score */ rightcenterangle = centerangle + delta; if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt2, pixsch, deg2rad * rightcenterangle, L_BRING_IN_WHITE); else pixVShearCenter(pixt2, pixsch, deg2rad * rightcenterangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[3]); numaAddNumber(nascore, bsearchscore[3]); numaAddNumber(natheta, rightcenterangle); /* Find the maximum of the five scores and its location. * Note that the maximum must be in the center * three values, not in the end two. */ maxscore = bsearchscore[1]; maxindex = 1; for (i = 2; i < 4; i++) { if (bsearchscore[i] > maxscore) { maxscore = bsearchscore[i]; maxindex = i; } } /* Set up score array to interpolate for the next iteration */ lefttemp = bsearchscore[maxindex - 1]; righttemp = bsearchscore[maxindex + 1]; bsearchscore[2] = maxscore; bsearchscore[0] = lefttemp; bsearchscore[4] = righttemp; /* Get new center angle and delta for next iteration */ centerangle = centerangle + delta * (maxindex - 2); delta = 0.5 * delta; } *pangle = centerangle; #if DEBUG_PRINT_SCORES L_INFO(" Binary search score = %7.3f\n", procName, bsearchscore[2]); #endif /* DEBUG_PRINT_SCORES */ if (pendscore) /* save if requested */ *pendscore = bsearchscore[2]; /* Return the ratio of Max score over Min score * as a confidence value. Don't trust if the Min score * is too small, which can happen if the image is all black * with only a few white pixels interspersed. In that case, * we get a contribution from the top and bottom edges when * vertically sheared, but this contribution becomes zero when * the shear angle is zero. For zero shear angle, the only * contribution will be from the white pixels. We expect that * the signal goes as the product of the (height * width^2), * so we compute a (hopefully) normalized minimum threshold as * a function of these dimensions. */ numaGetMin(nascore, &minscore, &minloc); width = pixGetWidth(pixsch); height = pixGetHeight(pixsch); minthresh = MINSCORE_THRESHOLD_CONSTANT * width * width * height; #if DEBUG_THRESHOLD L_INFO(" minthresh = %10.2f, minscore = %10.2f\n", procName, minthresh, minscore); L_INFO(" maxscore = %10.2f\n", procName, maxscore); #endif /* DEBUG_THRESHOLD */ if (minscore > minthresh) *pconf = maxscore / minscore; else *pconf = 0.0; /* Don't trust it if too close to the edge of the sweep * range or if maxscore is small */ if ((centerangle > rangeleft + 2 * sweeprange - sweepdelta) || (centerangle < rangeleft + sweepdelta) || (maxscore < MIN_VALID_MAXSCORE)) *pconf = 0.0; #if DEBUG_PRINT_BINARY fprintf(stderr, "Binary search: angle = %7.3f, score ratio = %6.2f\n", *pangle, *pconf); fprintf(stderr, " max score = %8.0f\n", maxscore); #endif /* DEBUG_PRINT_BINARY */ #if DEBUG_PLOT_SCORES /* Plot the result -- the scores versus rotation angle -- * using gnuplot with GPLOT_POINTS. Because the data * points are not ordered by theta (increasing or decreasing), * using GPLOT_LINES would be confusing! */ {GPLOT *gplot; gplot = gplotCreate("search_output", GPLOT_PNG, "Binary search. Variance of difference of ON pixels vs. angle", "angle (deg)", "score"); gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot1"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } #endif /* DEBUG_PLOT_SCORES */ cleanup: pixDestroy(&pixsw); pixDestroy(&pixsch); pixDestroy(&pixt1); pixDestroy(&pixt2); numaDestroy(&nascore); numaDestroy(&natheta); return ret; }