/*! * numaaWriteStream() * * Input: stream, naa * Return: 0 if OK, 1 on error */ l_int32 numaaWriteStream(FILE *fp, NUMAA *naa) { l_int32 i, n; NUMA *na; PROCNAME("numaaWriteStream"); if (!fp) return ERROR_INT("stream not defined", procName, 1); if (!naa) return ERROR_INT("naa not defined", procName, 1); n = numaaGetCount(naa); fprintf(fp, "\nNumaa Version %d\n", NUMA_VERSION_NUMBER); fprintf(fp, "Number of numa = %d\n\n", n); for (i = 0; i < n; i++) { if ((na = numaaGetNuma(naa, i, L_CLONE)) == NULL) return ERROR_INT("na not found", procName, 1); fprintf(fp, "Numa[%d]:", i); numaWriteStream(fp, na); numaDestroy(&na); } return 0; }
/*! * gplotSimpleXYN() * * Input: nax (<optional>; can be NULL) * naay (numaa of arrays to plot against @nax) * outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11, * GPLOT_LATEX) * outroot (root of output files) * title (<optional>) * Return: 0 if OK, 1 on error * * Notes: * (1) This gives line plots of each Numa in @naa against nax, * generated in the specified output format. The title is optional. * (2) @nax is optional. If NULL, each Numa array is plotted against * the array index. * (3) When calling these simple plot functions more than once, use * different @outroot to avoid overwriting the output files. */ l_int32 gplotSimpleXYN(NUMA *nax, NUMAA *naay, l_int32 outformat, const char *outroot, const char *title) { l_int32 i, n; GPLOT *gplot; NUMA *nay; PROCNAME("gplotSimpleXYN"); if (!naay) return ERROR_INT("naay not defined", procName, 1); if ((n = numaaGetCount(naay)) == 0) return ERROR_INT("no numa in array", procName, 1); if (outformat != GPLOT_PNG && outformat != GPLOT_PS && outformat != GPLOT_EPS && outformat != GPLOT_X11 && outformat != GPLOT_LATEX) return ERROR_INT("invalid outformat", procName, 1); if (!outroot) return ERROR_INT("outroot not specified", procName, 1); if ((gplot = gplotCreate(outroot, outformat, title, NULL, NULL)) == 0) return ERROR_INT("gplot not made", procName, 1); for (i = 0; i < n; i++) { nay = numaaGetNuma(naay, i, L_CLONE); gplotAddPlot(gplot, nax, nay, GPLOT_LINES, NULL); numaDestroy(&nay); } gplotMakeOutput(gplot); gplotDestroy(&gplot); return 0; }
/*! * numaaGetValue() * * Input: naa * i (index of numa within numaa) * j (index into numa) * fval (<optional return> float value) * ival (<optional return> int value) * Return: 0 if OK, 1 on error */ l_int32 numaaGetValue(NUMAA *naa, l_int32 i, l_int32 j, l_float32 *pfval, l_int32 *pival) { l_int32 n; NUMA *na; PROCNAME("numaaGetValue"); if (!pfval && !pival) return ERROR_INT("no return val requested", procName, 1); if (pfval) *pfval = 0.0; if (pival) *pival = 0; if (!naa) return ERROR_INT("naa not defined", procName, 1); n = numaaGetCount(naa); if (i < 0 || i >= n) return ERROR_INT("invalid index into naa", procName, 1); na = naa->numa[i]; if (j < 0 || j >= na->n) return ERROR_INT("invalid index into na", procName, 1); if (pfval) *pfval = na->array[j]; if (pival) *pival = (l_int32)(na->array[j]); return 0; }
/*! * numaaAddNuma() * * Input: naa * na (to be added) * copyflag (L_INSERT, L_COPY, L_CLONE) * Return: 0 if OK, 1 on error */ l_int32 numaaAddNuma(NUMAA *naa, NUMA *na, l_int32 copyflag) { l_int32 n; NUMA *nac; PROCNAME("numaaAddNuma"); if (!naa) return ERROR_INT("naa not defined", procName, 1); if (!na) return ERROR_INT("na not defined", procName, 1); if (copyflag == L_INSERT) { nac = na; } else if (copyflag == L_COPY) { if ((nac = numaCopy(na)) == NULL) return ERROR_INT("nac not made", procName, 1); } else if (copyflag == L_CLONE) { nac = numaClone(na); } else { return ERROR_INT("invalid copyflag", procName, 1); } n = numaaGetCount(naa); if (n >= naa->nalloc) numaaExtendArray(naa); naa->numa[n] = nac; naa->n++; return 0; }
/*! * numaaTruncate() * * Input: naa * Return: 0 if OK, 1 on error * * Notes: * (1) This identifies the largest index containing a numa that * has any numbers within it, destroys all numa beyond that * index, and resets the count. */ l_int32 numaaTruncate(NUMAA *naa) { l_int32 i, n, nn; NUMA *na; PROCNAME("numaaTruncate"); if (!naa) return ERROR_INT("naa not defined", procName, 1); n = numaaGetCount(naa); for (i = n - 1; i >= 0; i--) { na = numaaGetNuma(naa, i, L_CLONE); if (!na) continue; nn = numaGetCount(na); numaDestroy(&na); if (nn == 0) numaDestroy(&naa->numa[i]); else break; } naa->n = i + 1; return 0; }
/*! * pixaSort2dByIndex() * * Input: pixas * naa (numaa that maps from the new pixaa to the input pixas) * copyflag (L_CLONE or L_COPY) * Return: pixaa (sorted), or null on error */ PIXAA * pixaSort2dByIndex(PIXA *pixas, NUMAA *naa, l_int32 copyflag) { l_int32 pixtot, ntot, i, j, n, nn, index; BOX *box; NUMA *na; PIX *pix; PIXA *pixa; PIXAA *pixaa; PROCNAME("pixaSort2dByIndex"); if (!pixas) return (PIXAA *)ERROR_PTR("pixas not defined", procName, NULL); if (!naa) return (PIXAA *)ERROR_PTR("naindex not defined", procName, NULL); /* Check counts */ ntot = numaaGetNumberCount(naa); pixtot = pixaGetCount(pixas); if (ntot != pixtot) return (PIXAA *)ERROR_PTR("element count mismatch", procName, NULL); n = numaaGetCount(naa); pixaa = pixaaCreate(n); for (i = 0; i < n; i++) { na = numaaGetNuma(naa, i, L_CLONE); nn = numaGetCount(na); pixa = pixaCreate(nn); for (j = 0; j < nn; j++) { numaGetIValue(na, j, &index); pix = pixaGetPix(pixas, index, copyflag); box = pixaGetBox(pixas, index, copyflag); pixaAddPix(pixa, pix, L_INSERT); pixaAddBox(pixa, box, L_INSERT); } pixaaAddPixa(pixaa, pixa, L_INSERT); numaDestroy(&na); } return pixaa; }
/*! * numaaGetNumberCount() * * Input: naa * Return: count (total number of numbers in the numaa), * or 0 if no numbers or on error */ l_int32 numaaGetNumberCount(NUMAA *naa) { NUMA *na; l_int32 n, sum, i; PROCNAME("numaaGetNumberCount"); if (!naa) return ERROR_INT("naa not defined", procName, 0); n = numaaGetCount(naa); for (sum = 0, i = 0; i < n; i++) { na = numaaGetNuma(naa, i, L_CLONE); sum += numaGetCount(na); numaDestroy(&na); } return sum; }
/*! * numaaAddNumber() * * Input: naa * index (of numa within numaa) * val (float or int to be added; stored as a float) * Return: 0 if OK, 1 on error * * Notes: * (1) Adds to an existing numa only. */ l_int32 numaaAddNumber(NUMAA *naa, l_int32 index, l_float32 val) { l_int32 n; NUMA *na; PROCNAME("numaaAddNumber"); if (!naa) return ERROR_INT("naa not defined", procName, 1); n = numaaGetCount(naa); if (index < 0 || index >= n) return ERROR_INT("invalid index in naa", procName, 1); na = numaaGetNuma(naa, index, L_CLONE); numaAddNumber(na, val); numaDestroy(&na); return 0; }
/*! * numaaReplaceNuma() * * Input: naa * index (to the index-th numa) * numa (insert and replace any existing one) * Return: 0 if OK, 1 on error * * Notes: * (1) Any existing numa is destroyed, and the input one * is inserted in its place. * (2) If the index is invalid, return 1 (error) */ l_int32 numaaReplaceNuma(NUMAA *naa, l_int32 index, NUMA *na) { l_int32 n; PROCNAME("numaaReplaceNuma"); if (!naa) return ERROR_INT("naa not defined", procName, 1); if (!na) return ERROR_INT("na not defined", procName, 1); n = numaaGetCount(naa); if (index < 0 || index >= n) return ERROR_INT("index not valid", procName, 1); numaDestroy(&naa->numa[index]); naa->numa[index] = na; return 0; }
/*! * boxaSort2dByIndex() * * Input: boxas * naa (numaa that maps from the new baa to the input boxa) * Return: baa (sorted boxaa), or null on error */ BOXAA * boxaSort2dByIndex(BOXA *boxas, NUMAA *naa) { l_int32 ntot, boxtot, i, j, n, nn, index; BOX *box; BOXA *boxa; BOXAA *baa; NUMA *na; PROCNAME("boxaSort2dByIndex"); if (!boxas) return (BOXAA *)ERROR_PTR("boxas not defined", procName, NULL); if (!naa) return (BOXAA *)ERROR_PTR("naindex not defined", procName, NULL); /* Check counts */ ntot = numaaGetNumberCount(naa); boxtot = boxaGetCount(boxas); if (ntot != boxtot) return (BOXAA *)ERROR_PTR("element count mismatch", procName, NULL); n = numaaGetCount(naa); baa = boxaaCreate(n); for (i = 0; i < n; i++) { na = numaaGetNuma(naa, i, L_CLONE); nn = numaGetCount(na); boxa = boxaCreate(nn); for (j = 0; j < nn; j++) { numaGetIValue(na, i, &index); box = boxaGetBox(boxas, index, L_COPY); boxaAddBox(boxa, box, L_INSERT); } boxaaAddBoxa(baa, boxa, L_INSERT); numaDestroy(&na); } return baa; }
/*! * \brief numaaCompareImagesByBoxes() * * \param[in] naa1 for image 1, formatted by boxaExtractSortedPattern() * \param[in] naa2 ditto; for image 2 * \param[in] nperline number of box regions to be used in each textline * \param[in] nreq number of complete row matches required * \param[in] maxshiftx max allowed x shift between two patterns, in pixels * \param[in] maxshifty max allowed y shift between two patterns, in pixels * \param[in] delx max allowed difference in x data, after alignment * \param[in] dely max allowed difference in y data, after alignment * \param[out] psame 1 if %nreq row matches are found; 0 otherwise * \param[in] debugflag 1 for debug output * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) Each input numaa describes a set of sorted bounding boxes * (sorted by textline and, within each textline, from * left to right) in the images from which they are derived. * See boxaExtractSortedPattern() for a description of the data * format in each of the input numaa. * (2) This function does an alignment between the input * descriptions of bounding boxes for two images. The * input parameter %nperline specifies the number of boxes * to consider in each line when testing for a match, and * %nreq is the required number of lines that must be well-aligned * to get a match. * (3) Testing by alignment has 3 steps: * (a) Generating the location of word bounding boxes from the * images (prior to calling this function). * (b) Listing all possible pairs of aligned rows, based on * tolerances in horizontal and vertical positions of * the boxes. Specifically, all pairs of rows are enumerated * whose first %nperline boxes can be brought into close * alignment, based on the delx parameter for boxes in the * line and within the overall the %maxshiftx and %maxshifty * constraints. * (c) Each pair, starting with the first, is used to search * for a set of %nreq - 1 other pairs that can all be aligned * with a difference in global translation of not more * than (%delx, %dely). * </pre> */ l_int32 numaaCompareImagesByBoxes(NUMAA *naa1, NUMAA *naa2, l_int32 nperline, l_int32 nreq, l_int32 maxshiftx, l_int32 maxshifty, l_int32 delx, l_int32 dely, l_int32 *psame, l_int32 debugflag) { l_int32 n1, n2, i, j, nbox, y1, y2, xl1, xl2; l_int32 shiftx, shifty, match; l_int32 *line1, *line2; /* indicator for sufficient boxes in a line */ l_int32 *yloc1, *yloc2; /* arrays of y value for first box in a line */ l_int32 *xleft1, *xleft2; /* arrays of x value for left side of first box */ NUMA *na1, *na2, *nai1, *nai2, *nasx, *nasy; PROCNAME("numaaCompareImagesByBoxes"); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; if (!naa1) return ERROR_INT("naa1 not defined", procName, 1); if (!naa2) return ERROR_INT("naa2 not defined", procName, 1); if (nperline < 1) return ERROR_INT("nperline < 1", procName, 1); if (nreq < 1) return ERROR_INT("nreq < 1", procName, 1); n1 = numaaGetCount(naa1); n2 = numaaGetCount(naa2); if (n1 < nreq || n2 < nreq) return 0; /* Find the lines in naa1 and naa2 with sufficient boxes. * Also, find the y-values for each of the lines, and the * LH x-values of the first box in each line. */ line1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); line2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32)); yloc1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); yloc2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32)); xleft1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); xleft2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32)); for (i = 0; i < n1; i++) { na1 = numaaGetNuma(naa1, i, L_CLONE); numaGetIValue(na1, 0, yloc1 + i); numaGetIValue(na1, 1, xleft1 + i); nbox = (numaGetCount(na1) - 1) / 2; if (nbox >= nperline) line1[i] = 1; numaDestroy(&na1); } for (i = 0; i < n2; i++) { na2 = numaaGetNuma(naa2, i, L_CLONE); numaGetIValue(na2, 0, yloc2 + i); numaGetIValue(na2, 1, xleft2 + i); nbox = (numaGetCount(na2) - 1) / 2; if (nbox >= nperline) line2[i] = 1; numaDestroy(&na2); } /* Enumerate all possible line matches. A 'possible' line * match is one where the x and y shifts for the first box * in each line are within the maxshiftx and maxshifty * constraints, and the left and right sides of the remaining * (nperline - 1) successive boxes are within delx of each other. * The result is a set of four numas giving parameters of * each set of matching lines. */ nai1 = numaCreate(0); /* line index 1 of match */ nai2 = numaCreate(0); /* line index 2 of match */ nasx = numaCreate(0); /* shiftx for match */ nasy = numaCreate(0); /* shifty for match */ for (i = 0; i < n1; i++) { if (line1[i] == 0) continue; y1 = yloc1[i]; xl1 = xleft1[i]; na1 = numaaGetNuma(naa1, i, L_CLONE); for (j = 0; j < n2; j++) { if (line2[j] == 0) continue; y2 = yloc2[j]; if (L_ABS(y1 - y2) > maxshifty) continue; xl2 = xleft2[j]; if (L_ABS(xl1 - xl2) > maxshiftx) continue; shiftx = xl1 - xl2; /* shift to add to x2 values */ shifty = y1 - y2; /* shift to add to y2 values */ na2 = numaaGetNuma(naa2, j, L_CLONE); /* Now check if 'nperline' boxes in the two lines match */ match = testLineAlignmentX(na1, na2, shiftx, delx, nperline); if (match) { numaAddNumber(nai1, i); numaAddNumber(nai2, j); numaAddNumber(nasx, shiftx); numaAddNumber(nasy, shifty); } numaDestroy(&na2); } numaDestroy(&na1); } /* Determine if there are a sufficient number of mutually * aligned matches. Mutually aligned matches place an additional * constraint on the 'possible' matches, where the relative * shifts must not exceed the (delx, dely) distances. */ countAlignedMatches(nai1, nai2, nasx, nasy, n1, n2, delx, dely, nreq, psame, debugflag); LEPT_FREE(line1); LEPT_FREE(line2); LEPT_FREE(yloc1); LEPT_FREE(yloc2); LEPT_FREE(xleft1); LEPT_FREE(xleft2); numaDestroy(&nai1); numaDestroy(&nai2); numaDestroy(&nasx); numaDestroy(&nasy); return 0; }