/*====================================================================* - 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. *====================================================================*/ /* * pixafunc1.c * * Filters * PIX *pixSelectBySize() * PIXA *pixaSelectBySize() * PIX *pixSelectByAreaPerimRatio() * PIXA *pixaSelectByAreaPerimRatio() * PIX *pixSelectByAreaFraction() * PIXA *pixaSelectByAreaFraction() * PIX *pixSelectByWidthHeightRatio() * PIXA *pixaSelectByWidthHeightRatio() * PIXA *pixaSelectWithIndicator() * l_int32 pixRemoveWithIndicator() * l_int32 pixAddWithIndicator() * * Sort functions * PIXA *pixaSort() * PIXA *pixaBinSort() * PIXA *pixaSortByIndex() * PIXAA *pixaSort2dByIndex() * * Miscellaneous * PIXA *pixaaFlattenToPixa() * l_int32 pixaSizeRange() * PIXA *pixaClipToPix() * l_int32 pixaAnyColormaps() * l_int32 pixaEqual() */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "allheaders.h" /* For more than this number of c.c. in a binarized image of * semi-perimeter (w + h) about 5000 or less, the O(n) binsort * is faster than the O(nlogn) shellsort. */ static const l_int32 MIN_COMPS_FOR_BIN_SORT = 500; /*---------------------------------------------------------------------* * Filters * *---------------------------------------------------------------------*/ /* * These filters work on the connected components of 1 bpp images. * They are typically used on pixa that have been generated from a Pix * using pixConnComp(), so that the corresponding Boxa is available. * * The filters remove or retain c.c. based on these properties: * (a) size [pixaFindDimensions()] * (b) area-to-perimeter ratio [pixaFindAreaPerimRatio()] * (c) foreground area as a fraction of bounding box area (w * h) * [pixaFindForegroundArea()] * (d) number of foreground pixels [pixaCountPixels()] * (e) width/height aspect ratio [pixFindWidthHeightRatio()] * * We provide two different high-level interfaces: * (1) Functions that use one of the filters on either * a pix or the pixa of components. * (2) A general method that generates numas of indicator functions, * logically combines them, and efficiently removes or adds * the selected components. * * For interface (1), the filtering is performed with a single function call. * This is the easiest way to do simple filtering. These functions * are named pixSelectBy*() and pixaSelectBy*(), where the '*' is one of: * Size * AreaPerimRatio * AreaFraction * WidthHeightRatio * * For more complicated filtering, use the general method (2). * The numa indicator functions for a pixa are generated by these functions: * pixaFindDimensions() * pixaFindAreaPerimRatio() * pixaFindAreaFraction() * pixaCountPixels() * pixaFindWidthHeightRatio() * pixaFindWidthHeightProduct() * * Here is an illustration using the general method. Suppose you want * all 8-connected components that have a height greater than 40 pixels, * a width not more than 30 pixels, between 150 and 300 fg pixels, * and an area-to-perimeter ratio between 2.5 and 4.0. * * // Generate the pixa of 8 cc pieces. * boxa = pixConnComp(pixs, &pixa, 8); * * // Extract the data we need about each component. * pixaFindDimensions(pixa, &naw, &nah); * nas = pixaCountPixels(pixa); * nar = pixaFindAreaPerimRatio(pixa); * * // Build the indicator arrays for the set of components, * // based on thresholds and selection criteria. * na1 = numaMakeThresholdIndicator(nah, 40, L_SELECT_IF_GT); * na2 = numaMakeThresholdIndicator(naw, 30, L_SELECT_IF_LTE); * na3 = numaMakeThresholdIndicator(nas, 150, L_SELECT_IF_GTE); * na4 = numaMakeThresholdIndicator(nas, 300, L_SELECT_IF_LTE); * na5 = numaMakeThresholdIndicator(nar, 2.5, L_SELECT_IF_GTE); * na6 = numaMakeThresholdIndicator(nar, 4.0, L_SELECT_IF_LGE); * * // Combine the indicator arrays logically to find * // the components that will be retained. * nad = numaLogicalOp(NULL, na1, na2, L_INTERSECTION); * numaLogicalOp(nad, nad, na3, L_INTERSECTION); * numaLogicalOp(nad, nad, na4, L_INTERSECTION); * numaLogicalOp(nad, nad, na5, L_INTERSECTION); * numaLogicalOp(nad, nad, na6, L_INTERSECTION); * * // Invert to get the components that will be removed. * numaInvert(nad, nad); * * // Remove the components, in-place. * pixRemoveWithIndicator(pixs, pixa, nad); */ /*! * pixSelectBySize() * * Input: pixs (1 bpp) * width, height (threshold dimensions) * connectivity (4 or 8) * type (L_SELECT_WIDTH, L_SELECT_HEIGHT, * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH) * relation (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 otherwise) * Return: filtered pixd, or null on error * * Notes: * (1) The args specify constraints on the size of the * components that are kept. * (2) If unchanged, returns a copy of pixs. Otherwise, * returns a new pix with the filtered components. * (3) If the selection type is L_SELECT_WIDTH, the input * height is ignored, and v.v. * (4) To keep small components, use relation = L_SELECT_IF_LT or * L_SELECT_IF_LTE. * To keep large components, use relation = L_SELECT_IF_GT or * L_SELECT_IF_GTE. */ PIX * pixSelectBySize(PIX *pixs, l_int32 width, l_int32 height, l_int32 connectivity, l_int32 type, l_int32 relation, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectBySize"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT && type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT && relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid relation", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Identify and select the components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectBySize(pixas, width, height, type, relation, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); /* Render the result */ if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } else { if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) /* return empty pix */ pixd = pixCreateTemplate(pixs); else pixd = pixaDisplay(pixad, w, h); pixaDestroy(&pixad); return pixd; } } /*! * pixaSelectBySize() * * Input: pixas * width, height (threshold dimensions) * type (L_SELECT_WIDTH, L_SELECT_HEIGHT, * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH) * relation (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 otherwise) * Return: pixad, or null on error * * Notes: * (1) The args specify constraints on the size of the * components that are kept. * (2) Uses pix and box clones in the new pixa. * (3) If the selection type is L_SELECT_WIDTH, the input * height is ignored, and v.v. * (4) To keep small components, use relation = L_SELECT_IF_LT or * L_SELECT_IF_LTE. * To keep large components, use relation = L_SELECT_IF_GT or * L_SELECT_IF_GTE. */ PIXA * pixaSelectBySize(PIXA *pixas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged) { BOXA *boxa; NUMA *na; PIXA *pixad; PROCNAME("pixaSelectBySize"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT && type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT && relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid relation", procName, NULL); /* Compute the indicator array for saving components */ boxa = pixaGetBoxa(pixas, L_CLONE); na = boxaMakeSizeIndicator(boxa, width, height, type, relation); boxaDestroy(&boxa); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, na, pchanged); numaDestroy(&na); return pixad; } /*! * pixSelectByAreaPerimRatio() * * Input: pixs (1 bpp) * thresh (threshold ratio of interior/boundary pixels) * connectivity (4 or 8) * type (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixd, or null on error * * Notes: * (1) The args specify constraints on the size of the * components that are kept. * (2) If unchanged, returns a copy of pixs. Otherwise, * returns a new pix with the filtered components. * (3) This filters "thin" components, where a thin component * is defined to have a ratio of interior to boundary pixels * that is smaller than a given threshold value. * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save the thin * components, and L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them. */ PIX * pixSelectByAreaPerimRatio(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByAreaPerimRatio"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter thin components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByAreaPerimRatio(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); /* Render the result */ if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } else { if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) /* return empty pix */ pixd = pixCreateTemplate(pixs); else pixd = pixaDisplay(pixad, w, h); pixaDestroy(&pixad); return pixd; } } /*! * pixaSelectByAreaPerimRatio() * * Input: pixas * thresh (threshold ratio of interior/boundary pixels) * type (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixad, or null on error * * Notes: * (1) Returns a pixa clone if no components are removed. * (2) Uses pix and box clones in the new pixa. * (3) We define a thin component here to be one with a ratio * of interior to boundary pixels that is smaller than a given * threshold value. * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save the thin * components, and L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them. */ PIXA * pixaSelectByAreaPerimRatio(PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged) { NUMA *na, *nai; PIXA *pixad; PROCNAME("pixaSelectByAreaPerimRatio"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); /* Compute component ratios. */ na = pixaFindAreaPerimRatio(pixas); /* Generate indicator array for elements to be saved. */ nai = numaMakeThresholdIndicator(na, thresh, type); numaDestroy(&na); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, nai, pchanged); numaDestroy(&nai); return pixad; } /*! * pixSelectByAreaFraction() * * Input: pixs (1 bpp) * thresh (threshold ratio of fg pixels to (w * h)) * connectivity (4 or 8) * type (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixd, or null on error * * Notes: * (1) The args specify constraints on the amount of foreground * coverage of the components that are kept. * (2) If unchanged, returns a copy of pixs. Otherwise, * returns a new pix with the filtered components. * (3) This filters components based on the fraction of fg pixels * of the component in its bounding box. * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components * with less than the threshold fraction of foreground, and * L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them. */ PIX * pixSelectByAreaFraction(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByAreaFraction"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByAreaFraction(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); /* Render the result */ if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } else { if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) /* return empty pix */ pixd = pixCreateTemplate(pixs); else pixd = pixaDisplay(pixad, w, h); pixaDestroy(&pixad); return pixd; } } /*! * pixaSelectByAreaFraction() * * Input: pixas * thresh (threshold ratio of fg pixels to (w * h)) * type (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixad, or null on error * * Notes: * (1) Returns a pixa clone if no components are removed. * (2) Uses pix and box clones in the new pixa. * (3) This filters components based on the fraction of fg pixels * of the component in its bounding box. * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components * with less than the threshold fraction of foreground, and * L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them. */ PIXA * pixaSelectByAreaFraction(PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged) { NUMA *na, *nai; PIXA *pixad; PROCNAME("pixaSelectByAreaFraction"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); /* Compute component ratios. */ na = pixaFindAreaFraction(pixas); /* Generate indicator array for elements to be saved. */ nai = numaMakeThresholdIndicator(na, thresh, type); numaDestroy(&na); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, nai, pchanged); numaDestroy(&nai); return pixad; } /*! * pixSelectByWidthHeightRatio() * * Input: pixs (1 bpp) * thresh (threshold ratio of width/height) * connectivity (4 or 8) * type (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixd, or null on error * * Notes: * (1) The args specify constraints on the width-to-height ratio * for components that are kept. * (2) If unchanged, returns a copy of pixs. Otherwise, * returns a new pix with the filtered components. * (3) This filters components based on the width-to-height ratios. * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components * with less than the threshold ratio, and * L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them. */ PIX * pixSelectByWidthHeightRatio(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByWidthHeightRatio"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByWidthHeightRatio(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); /* Render the result */ if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } else { if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) /* return empty pix */ pixd = pixCreateTemplate(pixs); else pixd = pixaDisplay(pixad, w, h); pixaDestroy(&pixad); return pixd; } } /*! * pixaSelectByWidthHeightRatio() * * Input: pixas * thresh (threshold ratio of width/height) * type (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixad, or null on error * * Notes: * (1) Returns a pixa clone if no components are removed. * (2) Uses pix and box clones in the new pixa. * (3) This filters components based on the width-to-height ratio * of each pix. * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components * with less than the threshold ratio, and * L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them. */ PIXA * pixaSelectByWidthHeightRatio(PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged) { NUMA *na, *nai; PIXA *pixad; PROCNAME("pixaSelectByWidthHeightRatio"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); /* Compute component ratios. */ na = pixaFindWidthHeightRatio(pixas); /* Generate indicator array for elements to be saved. */ nai = numaMakeThresholdIndicator(na, thresh, type); numaDestroy(&na); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, nai, pchanged); numaDestroy(&nai); return pixad; } /*! * pixaSelectWithIndicator() * * Input: pixas * na (indicator numa) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixad, or null on error * * Notes: * (1) Returns a pixa clone if no components are removed. * (2) Uses pix and box clones in the new pixa. * (3) The indicator numa has values 0 (ignore) and 1 (accept). */ PIXA * pixaSelectWithIndicator(PIXA *pixas, NUMA *na, l_int32 *pchanged) { l_int32 i, n, ival, nsave; BOX *box; PIX *pixt; PIXA *pixad; PROCNAME("pixaSelectWithIndicator"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!na) return (PIXA *)ERROR_PTR("na not defined", procName, NULL); nsave = 0; n = numaGetCount(na); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 1) nsave++; } if (nsave == n) { if (pchanged) *pchanged = FALSE; return pixaCopy(pixas, L_CLONE); } if (pchanged) *pchanged = TRUE; pixad = pixaCreate(nsave); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 0) continue; pixt = pixaGetPix(pixas, i, L_CLONE); box = pixaGetBox(pixas, i, L_CLONE); pixaAddPix(pixad, pixt, L_INSERT); pixaAddBox(pixad, box, L_INSERT); } return pixad; } /*! * pixRemoveWithIndicator() * * Input: pixs (1 bpp pix from which components are removed; in-place) * pixa (of connected components in pixs) * na (numa indicator: remove components corresponding to 1s) * Return: 0 if OK, 1 on error * * Notes: * (1) This complements pixAddWithIndicator(). Here, the selected * components are set subtracted from pixs. */ l_int32 pixRemoveWithIndicator(PIX *pixs, PIXA *pixa, NUMA *na) { l_int32 i, n, ival, x, y, w, h; BOX *box; PIX *pix; PROCNAME("pixRemoveWithIndicator"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!na) return ERROR_INT("na not defined", procName, 1); n = pixaGetCount(pixa); if (n != numaGetCount(na)) return ERROR_INT("pixa and na sizes not equal", procName, 1); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 1) { pix = pixaGetPix(pixa, i, L_CLONE); box = pixaGetBox(pixa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); pixRasterop(pixs, x, y, w, h, PIX_DST & PIX_NOT(PIX_SRC), pix, 0, 0); boxDestroy(&box); pixDestroy(&pix); } } return 0; } /*! * pixAddWithIndicator() * * Input: pixs (1 bpp pix from which components are added; in-place) * pixa (of connected components, some of which will be put * into pixs) * na (numa indicator: add components corresponding to 1s) * Return: 0 if OK, 1 on error * * Notes: * (1) This complements pixRemoveWithIndicator(). Here, the selected * components are added to pixs. */ l_int32 pixAddWithIndicator(PIX *pixs, PIXA *pixa, NUMA *na) { l_int32 i, n, ival, x, y, w, h; BOX *box; PIX *pix; PROCNAME("pixAddWithIndicator"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!na) return ERROR_INT("na not defined", procName, 1); n = pixaGetCount(pixa); if (n != numaGetCount(na)) return ERROR_INT("pixa and na sizes not equal", procName, 1); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 1) { pix = pixaGetPix(pixa, i, L_CLONE); box = pixaGetBox(pixa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); pixRasterop(pixs, x, y, w, h, PIX_SRC | PIX_DST, pix, 0, 0); boxDestroy(&box); pixDestroy(&pix); } } return 0; } /*---------------------------------------------------------------------* * Sort functions * *---------------------------------------------------------------------*/ /*! * pixaSort() * * Input: pixas * sorttype (L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH, * L_SORT_BY_HEIGHT, L_SORT_BY_MIN_DIMENSION, * L_SORT_BY_MAX_DIMENSION, L_SORT_BY_PERIMETER, * L_SORT_BY_AREA, L_SORT_BY_ASPECT_RATIO) * sortorder (L_SORT_INCREASING, L_SORT_DECREASING) * &naindex (<optional return> index of sorted order into * original array) * copyflag (L_COPY, L_CLONE) * Return: pixad (sorted version of pixas), or null on error * * Notes: * (1) This sorts based on the data in the boxa. If the boxa * count is not the same as the pixa count, this returns an error. * (2) The copyflag refers to the pix and box copies that are * inserted into the sorted pixa. These are either L_COPY * or L_CLONE. */ PIXA * pixaSort(PIXA *pixas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex, l_int32 copyflag) { l_int32 i, n, x, y, w, h; BOXA *boxa; NUMA *na, *naindex; PIXA *pixad; PROCNAME("pixaSort"); if (pnaindex) *pnaindex = NULL; if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y && sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT && sorttype != L_SORT_BY_MIN_DIMENSION && sorttype != L_SORT_BY_MAX_DIMENSION && sorttype != L_SORT_BY_PERIMETER && sorttype != L_SORT_BY_AREA && sorttype != L_SORT_BY_ASPECT_RATIO) return (PIXA *)ERROR_PTR("invalid sort type", procName, NULL); if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) return (PIXA *)ERROR_PTR("invalid sort order", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (PIXA *)ERROR_PTR("invalid copy flag", procName, NULL); if ((boxa = pixas->boxa) == NULL) /* not owned; do not destroy */ return (PIXA *)ERROR_PTR("boxa not found", procName, NULL); n = pixaGetCount(pixas); if (boxaGetCount(boxa) != n) return (PIXA *)ERROR_PTR("boxa and pixa counts differ", procName, NULL); /* Use O(n) binsort if possible */ if (n > MIN_COMPS_FOR_BIN_SORT && ((sorttype == L_SORT_BY_X) || (sorttype == L_SORT_BY_Y) || (sorttype == L_SORT_BY_WIDTH) || (sorttype == L_SORT_BY_HEIGHT) || (sorttype == L_SORT_BY_PERIMETER))) return pixaBinSort(pixas, sorttype, sortorder, pnaindex, copyflag); /* Build up numa of specific data */ if ((na = numaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("na not made", procName, NULL); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h); switch (sorttype) { case L_SORT_BY_X: numaAddNumber(na, x); break; case L_SORT_BY_Y: numaAddNumber(na, y); break; case L_SORT_BY_WIDTH: numaAddNumber(na, w); break; case L_SORT_BY_HEIGHT: numaAddNumber(na, h); break; case L_SORT_BY_MIN_DIMENSION: numaAddNumber(na, L_MIN(w, h)); break; case L_SORT_BY_MAX_DIMENSION: numaAddNumber(na, L_MAX(w, h)); break; case L_SORT_BY_PERIMETER: numaAddNumber(na, w + h); break; case L_SORT_BY_AREA: numaAddNumber(na, w * h); break; case L_SORT_BY_ASPECT_RATIO: numaAddNumber(na, (l_float32)w / (l_float32)h); break; default: L_WARNING("invalid sort type", procName); } } /* Get the sort index for data array */ if ((naindex = numaGetSortIndex(na, sortorder)) == NULL) return (PIXA *)ERROR_PTR("naindex not made", procName, NULL); /* Build up sorted pixa using sort index */ if ((pixad = pixaSortByIndex(pixas, naindex, copyflag)) == NULL) return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); if (pnaindex) *pnaindex = naindex; else numaDestroy(&naindex); numaDestroy(&na); return pixad; } /*! * pixaBinSort() * * Input: pixas * sorttype (L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH, * L_SORT_BY_HEIGHT, L_SORT_BY_PERIMETER) * sortorder (L_SORT_INCREASING, L_SORT_DECREASING) * &naindex (<optional return> index of sorted order into * original array) * copyflag (L_COPY, L_CLONE) * Return: pixad (sorted version of pixas), or null on error * * Notes: * (1) This sorts based on the data in the boxa. If the boxa * count is not the same as the pixa count, this returns an error. * (2) The copyflag refers to the pix and box copies that are * inserted into the sorted pixa. These are either L_COPY * or L_CLONE. * (3) For a large number of boxes (say, greater than 1000), this * O(n) binsort is much faster than the O(nlogn) shellsort. * For 5000 components, this is over 20x faster than boxaSort(). * (4) Consequently, pixaSort() calls this function if it will * likely go much faster. */ PIXA * pixaBinSort(PIXA *pixas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex, l_int32 copyflag) { l_int32 i, n, x, y, w, h; BOXA *boxa; NUMA *na, *naindex; PIXA *pixad; PROCNAME("pixaBinSort"); if (pnaindex) *pnaindex = NULL; if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y && sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT && sorttype != L_SORT_BY_PERIMETER) return (PIXA *)ERROR_PTR("invalid sort type", procName, NULL); if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) return (PIXA *)ERROR_PTR("invalid sort order", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (PIXA *)ERROR_PTR("invalid copy flag", procName, NULL); /* Verify that the pixa and its boxa have the same count */ if ((boxa = pixas->boxa) == NULL) /* not owned; do not destroy */ return (PIXA *)ERROR_PTR("boxa not found", procName, NULL); n = pixaGetCount(pixas); if (boxaGetCount(boxa) != n) return (PIXA *)ERROR_PTR("boxa and pixa counts differ", procName, NULL); /* Generate Numa of appropriate box dimensions */ if ((na = numaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("na not made", procName, NULL); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h); switch (sorttype) { case L_SORT_BY_X: numaAddNumber(na, x); break; case L_SORT_BY_Y: numaAddNumber(na, y); break; case L_SORT_BY_WIDTH: numaAddNumber(na, w); break; case L_SORT_BY_HEIGHT: numaAddNumber(na, h); break; case L_SORT_BY_PERIMETER: numaAddNumber(na, w + h); break; default: L_WARNING("invalid sort type", procName); } } /* Get the sort index for data array */ if ((naindex = numaGetBinSortIndex(na, sortorder)) == NULL) return (PIXA *)ERROR_PTR("naindex not made", procName, NULL); /* Build up sorted pixa using sort index */ if ((pixad = pixaSortByIndex(pixas, naindex, copyflag)) == NULL) return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); if (pnaindex) *pnaindex = naindex; else numaDestroy(&naindex); numaDestroy(&na); return pixad; } /*! * pixaSortByIndex() * * Input: pixas * naindex (na that maps from the new pixa to the input pixa) * copyflag (L_COPY, L_CLONE) * Return: pixad (sorted), or null on error */ PIXA * pixaSortByIndex(PIXA *pixas, NUMA *naindex, l_int32 copyflag) { l_int32 i, n, index; BOX *box; PIX *pix; PIXA *pixad; PROCNAME("pixaSortByIndex"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!naindex) return (PIXA *)ERROR_PTR("naindex not defined", procName, NULL); if (copyflag != L_CLONE && copyflag != L_COPY) return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL); n = pixaGetCount(pixas); pixad = pixaCreate(n); for (i = 0; i < n; i++) { numaGetIValue(naindex, i, &index); pix = pixaGetPix(pixas, index, copyflag); box = pixaGetBox(pixas, index, copyflag); pixaAddPix(pixad, pix, L_INSERT); pixaAddBox(pixad, box, L_INSERT); } return pixad; } /*! * 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; } /*---------------------------------------------------------------------* * Miscellaneous functions * *---------------------------------------------------------------------*/ /*! * pixaaFlattenToPixa() * * Input: pixaa * &naindex (<optional return> the pixa index in the pixaa) * copyflag (L_COPY or L_CLONE) * Return: pixa, or null on error * * Notes: * (1) This 'flattens' the pixaa to a pixa, taking the pix in * order in the first pixa, then the second, etc. * (2) If &naindex is defined, we generate a Numa that gives, for * each pix in the pixaa, the index of the pixa to which it belongs. */ PIXA * pixaaFlattenToPixa(PIXAA *pixaa, NUMA **pnaindex, l_int32 copyflag) { l_int32 i, j, m, n; BOX *box; NUMA *naindex; PIX *pix; PIXA *pixa, *pixat; PROCNAME("pixaaFlattenToPixa"); if (pnaindex) *pnaindex = NULL; if (!pixaa) return (PIXA *)ERROR_PTR("pixaa not defined", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL); if (pnaindex) { naindex = numaCreate(0); *pnaindex = naindex; } n = pixaaGetCount(pixaa); pixa = pixaCreate(n); for (i = 0; i < n; i++) { pixat = pixaaGetPixa(pixaa, i, L_CLONE); m = pixaGetCount(pixat); for (j = 0; j < m; j++) { pix = pixaGetPix(pixat, j, copyflag); box = pixaGetBox(pixat, j, copyflag); pixaAddPix(pixa, pix, L_INSERT); pixaAddBox(pixa, box, L_INSERT); if (pnaindex) numaAddNumber(naindex, i); /* save 'row' number */ } pixaDestroy(&pixat); } return pixa; } /*! * pixaSizeRange() * * Input: pixa * &minw, &minh, &maxw, &maxh (<optional return> range of * dimensions of pix in the array) * Return: 0 if OK, 1 on error */ l_int32 pixaSizeRange(PIXA *pixa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh) { l_int32 minw, minh, maxw, maxh, i, n, w, h; PIX *pix; PROCNAME("pixaSizeRange"); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!pminw && !pmaxw && !pminh && !pmaxh) return ERROR_INT("no data can be returned", procName, 1); minw = minh = 1000000; maxw = maxh = 0; n = pixaGetCount(pixa); for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); w = pixGetWidth(pix); h = pixGetHeight(pix); if (w < minw) minw = w; if (h < minh) minh = h; if (w > maxw) maxw = w; if (h > maxh) maxh = h; pixDestroy(&pix); } if (pminw) *pminw = minw; if (pminh) *pminh = minh; if (pmaxw) *pmaxw = maxw; if (pmaxh) *pmaxh = maxh; return 0; } /*! * pixaClipToPix() * * Input: pixas * pixs * Return: pixad, or null on error * * Notes: * (1) This is intended for use in situations where pixas * was originally generated from the input pixs. * (2) Returns a pixad where each pix in pixas is ANDed * with its associated region of the input pixs. This * region is specified by the the box that is associated * with the pix. * (3) In a typical application of this function, pixas has * a set of region masks, so this generates a pixa of * the parts of pixs that correspond to each region * mask component, along with the bounding box for * the region. */ PIXA * pixaClipToPix(PIXA *pixas, PIX *pixs) { l_int32 i, n; BOX *box; PIX *pix, *pixc; PIXA *pixad; PROCNAME("pixaClipToPix"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!pixs) return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL); n = pixaGetCount(pixas); if ((pixad = pixaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); for (i = 0; i < n; i++) { pix = pixaGetPix(pixas, i, L_CLONE); box = pixaGetBox(pixas, i, L_COPY); pixc = pixClipRectangle(pixs, box, NULL); pixAnd(pixc, pixc, pix); pixaAddPix(pixad, pixc, L_INSERT); pixaAddBox(pixad, box, L_INSERT); pixDestroy(&pix); } return pixad; } /*! * pixaAnyColormaps() * * Input: pixa * &hascmap (<return> 1 if any pix has a colormap; 0 otherwise) * Return: 0 if OK; 1 on error */ l_int32 pixaAnyColormaps(PIXA *pixa, l_int32 *phascmap) { l_int32 i, n; PIX *pix; PIXCMAP *cmap; PROCNAME("pixaAnyColormaps"); if (!phascmap) return ERROR_INT("&hascmap not defined", procName, 1); *phascmap = 0; if (!pixa) return ERROR_INT("pixa not defined", procName, 1); n = pixaGetCount(pixa); for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); cmap = pixGetColormap(pix); pixDestroy(&pix); if (cmap) { *phascmap = 1; return 0; } } return 0; } /*! * pixaEqual() * * Input: pixa1 * pixa2 * maxdist * &naindex (<optional return> index array of correspondences * &same (<return> 1 if equal; 0 otherwise) * Return 0 if OK, 1 on error * * Notes: * (1) The two pixa are the "same" if they contain the same * boxa and the same ordered set of pix. However, if they * have boxa, the pix in each pixa can differ in ordering * by an amount given by the parameter @maxdist. If they * don't have a boxa, the @maxdist parameter is ignored, * and the ordering must be identical. * (2) This applies only to boxa geometry, pixels and ordering; * other fields in the pix are ignored. * (3) naindex[i] gives the position of the box in pixa2 that * corresponds to box i in pixa1. It is only returned if the * pixa have boxa and the boxa are equal. * (4) In situations where the ordering is very different, so that * a large @maxdist is required for "equality", this should be * implemented with a hash function for efficiency. */ l_int32 pixaEqual(PIXA *pixa1, PIXA *pixa2, l_int32 maxdist, NUMA **pnaindex, l_int32 *psame) { l_int32 i, j, n, same, sameboxa; BOXA *boxa1, *boxa2; NUMA *na; PIX *pix1, *pix2; PROCNAME("pixaEqual"); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; sameboxa = 0; na = NULL; if (!pixa1 || !pixa2) return ERROR_INT("pixa1 and pixa2 not both defined", procName, 1); n = pixaGetCount(pixa1); if (n != pixaGetCount(pixa2)) return 0; boxa1 = pixaGetBoxa(pixa1, L_CLONE); boxa2 = pixaGetBoxa(pixa2, L_CLONE); if (!boxa1 && !boxa2) maxdist = 0; /* exact ordering required */ if (boxa1 && !boxa2) { boxaDestroy(&boxa1); return 0; } if (!boxa1 && boxa2) { boxaDestroy(&boxa2); return 0; } if (boxa1 && boxa2) { boxaEqual(boxa1, boxa2, maxdist, &na, &sameboxa); boxaDestroy(&boxa1); boxaDestroy(&boxa2); if (!sameboxa) { numaDestroy(&na); return 0; } } for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixa1, i, L_CLONE); if (na) numaGetIValue(na, i, &j); else j = i; pix2 = pixaGetPix(pixa2, j, L_CLONE); pixEqual(pix1, pix2, &same); pixDestroy(&pix1); pixDestroy(&pix2); if (!same) { numaDestroy(&na); return 0; } } *psame = 1; if (pnaindex) *pnaindex = na; else numaDestroy(&na); return 0; }