Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/ptafunc1.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/leptonica/src/ptafunc1.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,2640 @@ +/*====================================================================* + - Copyright (C) 2001 Leptonica. All rights reserved. + - + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - 2. Redistributions in binary form must reproduce the above + - copyright notice, this list of conditions and the following + - disclaimer in the documentation and/or other materials + - provided with the distribution. + - + - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY + - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *====================================================================*/ + +/*! + * \file ptafunc1.c + * <pre> + * + * -------------------------------------- + * This file has these Pta utilities: + * - simple rearrangements + * - geometric analysis + * - min/max and filtering + * - least squares fitting + * - interconversions with Pix and Numa + * - display into a pix + * -------------------------------------- + * + * Simple rearrangements + * PTA *ptaSubsample() + * l_int32 ptaJoin() + * l_int32 ptaaJoin() + * PTA *ptaReverse() + * PTA *ptaTranspose() + * PTA *ptaCyclicPerm() + * PTA *ptaSelectRange() + * + * Geometric + * BOX *ptaGetBoundingRegion() + * l_int32 *ptaGetRange() + * PTA *ptaGetInsideBox() + * PTA *pixFindCornerPixels() + * l_int32 ptaContainsPt() + * l_int32 ptaTestIntersection() + * PTA *ptaTransform() + * l_int32 ptaPtInsidePolygon() + * l_float32 l_angleBetweenVectors() + * l_int32 ptaPolygonIsConvex() + * + * Min/max and filtering + * l_int32 ptaGetMinMax() + * PTA *ptaSelectByValue() + * PTA *ptaCropToMask() + * + * Least Squares Fit + * l_int32 ptaGetLinearLSF() + * l_int32 ptaGetQuadraticLSF() + * l_int32 ptaGetCubicLSF() + * l_int32 ptaGetQuarticLSF() + * l_int32 ptaNoisyLinearLSF() + * l_int32 ptaNoisyQuadraticLSF() + * l_int32 applyLinearFit() + * l_int32 applyQuadraticFit() + * l_int32 applyCubicFit() + * l_int32 applyQuarticFit() + * + * Interconversions with Pix + * l_int32 pixPlotAlongPta() + * PTA *ptaGetPixelsFromPix() + * PIX *pixGenerateFromPta() + * PTA *ptaGetBoundaryPixels() + * PTAA *ptaaGetBoundaryPixels() + * PTAA *ptaaIndexLabeledPixels() + * PTA *ptaGetNeighborPixLocs() + * + * Interconversion with Numa + * PTA *numaConvertToPta1() + * PTA *numaConvertToPta2() + * l_int32 ptaConvertToNuma() + * + * Display Pta and Ptaa + * PIX *pixDisplayPta() + * PIX *pixDisplayPtaaPattern() + * PIX *pixDisplayPtaPattern() + * PTA *ptaReplicatePattern() + * PIX *pixDisplayPtaa() + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <math.h> +#include "allheaders.h" +#include "pix_internal.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif /* M_PI */ + +/*---------------------------------------------------------------------* + * Simple rearrangements * + *---------------------------------------------------------------------*/ +/*! + * \brief ptaSubsample() + * + * \param[in] ptas + * \param[in] subfactor subsample factor, >= 1 + * \return ptad evenly sampled pt values from ptas, or NULL on error + */ +PTA * +ptaSubsample(PTA *ptas, + l_int32 subfactor) +{ +l_int32 n, i; +l_float32 x, y; +PTA *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + if (subfactor < 1) + return (PTA *)ERROR_PTR("subfactor < 1", __func__, NULL); + + ptad = ptaCreate(0); + n = ptaGetCount(ptas); + for (i = 0; i < n; i++) { + if (i % subfactor != 0) continue; + ptaGetPt(ptas, i, &x, &y); + ptaAddPt(ptad, x, y); + } + + return ptad; +} + + +/*! + * \brief ptaJoin() + * + * \param[in] ptad dest pta; add to this one + * \param[in] ptas source pta; add from this one + * \param[in] istart starting index in ptas + * \param[in] iend ending index in ptas; use -1 to cat all + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) istart < 0 is taken to mean 'read from the start' (istart = 0) + * (2) iend < 0 means 'read to the end' + * (3) if ptas == NULL, this is a no-op + * </pre> + */ +l_ok +ptaJoin(PTA *ptad, + PTA *ptas, + l_int32 istart, + l_int32 iend) +{ +l_int32 n, i, x, y; + + if (!ptad) + return ERROR_INT("ptad not defined", __func__, 1); + if (!ptas) + return 0; + + if (istart < 0) + istart = 0; + n = ptaGetCount(ptas); + if (iend < 0 || iend >= n) + iend = n - 1; + if (istart > iend) + return ERROR_INT("istart > iend; no pts", __func__, 1); + + for (i = istart; i <= iend; i++) { + ptaGetIPt(ptas, i, &x, &y); + if (ptaAddPt(ptad, x, y) == 1) { + L_ERROR("failed to add pt at i = %d\n", __func__, i); + return 1; + } + } + return 0; +} + + +/*! + * \brief ptaaJoin() + * + * \param[in] ptaad dest ptaa; add to this one + * \param[in] ptaas source ptaa; add from this one + * \param[in] istart starting index in ptaas + * \param[in] iend ending index in ptaas; use -1 to cat all + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) istart < 0 is taken to mean 'read from the start' (istart = 0) + * (2) iend < 0 means 'read to the end' + * (3) if ptas == NULL, this is a no-op + * </pre> + */ +l_ok +ptaaJoin(PTAA *ptaad, + PTAA *ptaas, + l_int32 istart, + l_int32 iend) +{ +l_int32 n, i; +PTA *pta; + + if (!ptaad) + return ERROR_INT("ptaad not defined", __func__, 1); + if (!ptaas) + return 0; + + if (istart < 0) + istart = 0; + n = ptaaGetCount(ptaas); + if (iend < 0 || iend >= n) + iend = n - 1; + if (istart > iend) + return ERROR_INT("istart > iend; no pts", __func__, 1); + + for (i = istart; i <= iend; i++) { + pta = ptaaGetPta(ptaas, i, L_CLONE); + ptaaAddPta(ptaad, pta, L_INSERT); + } + + return 0; +} + + +/*! + * \brief ptaReverse() + * + * \param[in] ptas + * \param[in] type 0 for float values; 1 for integer values + * \return ptad reversed pta, or NULL on error + */ +PTA * +ptaReverse(PTA *ptas, + l_int32 type) +{ +l_int32 n, i, ix, iy; +l_float32 x, y; +PTA *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + + n = ptaGetCount(ptas); + if ((ptad = ptaCreate(n)) == NULL) + return (PTA *)ERROR_PTR("ptad not made", __func__, NULL); + for (i = n - 1; i >= 0; i--) { + if (type == 0) { + ptaGetPt(ptas, i, &x, &y); + ptaAddPt(ptad, x, y); + } else { /* type == 1 */ + ptaGetIPt(ptas, i, &ix, &iy); + ptaAddPt(ptad, ix, iy); + } + } + + return ptad; +} + + +/*! + * \brief ptaTranspose() + * + * \param[in] ptas + * \return ptad with x and y values swapped, or NULL on error + */ +PTA * +ptaTranspose(PTA *ptas) +{ +l_int32 n, i; +l_float32 x, y; +PTA *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + + n = ptaGetCount(ptas); + if ((ptad = ptaCreate(n)) == NULL) + return (PTA *)ERROR_PTR("ptad not made", __func__, NULL); + for (i = 0; i < n; i++) { + ptaGetPt(ptas, i, &x, &y); + ptaAddPt(ptad, y, x); + } + + return ptad; +} + + +/*! + * \brief ptaCyclicPerm() + * + * \param[in] ptas + * \param[in] xs, ys start point; must be in ptas + * \return ptad cyclic permutation, starting and ending at (xs, ys, + * or NULL on error + * + * <pre> + * Notes: + * (1) Check to insure that (a) ptas is a closed path where + * the first and last points are identical, and (b) the + * resulting pta also starts and ends on the same point + * (which in this case is (xs, ys). + * </pre> + */ +PTA * +ptaCyclicPerm(PTA *ptas, + l_int32 xs, + l_int32 ys) +{ +l_int32 n, i, x, y, j, index, state; +l_int32 x1, y1, x2, y2; +PTA *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + + n = ptaGetCount(ptas); + + /* Verify input data */ + ptaGetIPt(ptas, 0, &x1, &y1); + ptaGetIPt(ptas, n - 1, &x2, &y2); + if (x1 != x2 || y1 != y2) + return (PTA *)ERROR_PTR("start and end pts not same", __func__, NULL); + state = L_NOT_FOUND; + for (i = 0; i < n; i++) { + ptaGetIPt(ptas, i, &x, &y); + if (x == xs && y == ys) { + state = L_FOUND; + break; + } + } + if (state == L_NOT_FOUND) + return (PTA *)ERROR_PTR("start pt not in ptas", __func__, NULL); + + if ((ptad = ptaCreate(n)) == NULL) + return (PTA *)ERROR_PTR("ptad not made", __func__, NULL); + for (j = 0; j < n - 1; j++) { + if (i + j < n - 1) + index = i + j; + else + index = (i + j + 1) % n; + ptaGetIPt(ptas, index, &x, &y); + ptaAddPt(ptad, x, y); + } + ptaAddPt(ptad, xs, ys); + + return ptad; +} + + +/*! + * \brief ptaSelectRange() + * + * \param[in] ptas + * \param[in] first use 0 to select from the beginning + * \param[in] last use -1 to select to the end + * \return ptad, or NULL on error + */ +PTA * +ptaSelectRange(PTA *ptas, + l_int32 first, + l_int32 last) +{ +l_int32 n, npt, i; +l_float32 x, y; +PTA *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + if ((n = ptaGetCount(ptas)) == 0) { + L_WARNING("ptas is empty\n", __func__); + return ptaCopy(ptas); + } + first = L_MAX(0, first); + if (last < 0) last = n - 1; + if (first >= n) + return (PTA *)ERROR_PTR("invalid first", __func__, NULL); + if (last >= n) { + L_WARNING("last = %d is beyond max index = %d; adjusting\n", + __func__, last, n - 1); + last = n - 1; + } + if (first > last) + return (PTA *)ERROR_PTR("first > last", __func__, NULL); + + npt = last - first + 1; + ptad = ptaCreate(npt); + for (i = first; i <= last; i++) { + ptaGetPt(ptas, i, &x, &y); + ptaAddPt(ptad, x, y); + } + return ptad; +} + + +/*---------------------------------------------------------------------* + * Geometric * + *---------------------------------------------------------------------*/ +/*! + * \brief ptaGetBoundingRegion() + * + * \param[in] pta + * \return box, or NULL on error + * + * <pre> + * Notes: + * (1) This is used when the pta represents a set of points in + * a two-dimensional image. It returns the box of minimum + * size containing the pts in the pta. + * </pre> + */ +BOX * +ptaGetBoundingRegion(PTA *pta) +{ +l_int32 n, i, x, y, minx, maxx, miny, maxy; + + if (!pta) + return (BOX *)ERROR_PTR("pta not defined", __func__, NULL); + + minx = 10000000; + miny = 10000000; + maxx = -10000000; + maxy = -10000000; + n = ptaGetCount(pta); + for (i = 0; i < n; i++) { + ptaGetIPt(pta, i, &x, &y); + if (x < minx) minx = x; + if (x > maxx) maxx = x; + if (y < miny) miny = y; + if (y > maxy) maxy = y; + } + + return boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1); +} + + +/*! + * \brief ptaGetRange() + * + * \param[in] pta + * \param[out] pminx [optional] min value of x + * \param[out] pmaxx [optional] max value of x + * \param[out] pminy [optional] min value of y + * \param[out] pmaxy [optional] max value of y + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) We can use pts to represent pairs of floating values, that + * are not necessarily tied to a two-dimension region. For + * example, the pts can represent a general function y(x). + * </pre> + */ +l_ok +ptaGetRange(PTA *pta, + l_float32 *pminx, + l_float32 *pmaxx, + l_float32 *pminy, + l_float32 *pmaxy) +{ +l_int32 n, i; +l_float32 x, y, minx, maxx, miny, maxy; + + if (!pminx && !pmaxx && !pminy && !pmaxy) + return ERROR_INT("no output requested", __func__, 1); + if (pminx) *pminx = 0; + if (pmaxx) *pmaxx = 0; + if (pminy) *pminy = 0; + if (pmaxy) *pmaxy = 0; + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if ((n = ptaGetCount(pta)) == 0) + return ERROR_INT("no points in pta", __func__, 1); + + ptaGetPt(pta, 0, &x, &y); + minx = x; + maxx = x; + miny = y; + maxy = y; + for (i = 1; i < n; i++) { + ptaGetPt(pta, i, &x, &y); + if (x < minx) minx = x; + if (x > maxx) maxx = x; + if (y < miny) miny = y; + if (y > maxy) maxy = y; + } + if (pminx) *pminx = minx; + if (pmaxx) *pmaxx = maxx; + if (pminy) *pminy = miny; + if (pmaxy) *pmaxy = maxy; + return 0; +} + + +/*! + * \brief ptaGetInsideBox() + * + * \param[in] ptas input pts + * \param[in] box + * \return ptad of pts in ptas that are inside the box, or NULL on error + */ +PTA * +ptaGetInsideBox(PTA *ptas, + BOX *box) +{ +PTA *ptad; +l_int32 n, i, contains; +l_float32 x, y; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + if (!box) + return (PTA *)ERROR_PTR("box not defined", __func__, NULL); + + n = ptaGetCount(ptas); + ptad = ptaCreate(0); + for (i = 0; i < n; i++) { + ptaGetPt(ptas, i, &x, &y); + boxContainsPt(box, x, y, &contains); + if (contains) + ptaAddPt(ptad, x, y); + } + + return ptad; +} + + +/*! + * \brief pixFindCornerPixels() + * + * \param[in] pixs 1 bpp + * \return pta, or NULL on error + * + * <pre> + * Notes: + * (1) Finds the 4 corner-most pixels, as defined by a search + * inward from each corner, using a 45 degree line. + * </pre> + */ +PTA * +pixFindCornerPixels(PIX *pixs) +{ +l_int32 i, j, x, y, w, h, wpl, mindim, found; +l_uint32 *data, *line; +PTA *pta; + + if (!pixs) + return (PTA *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 1) + return (PTA *)ERROR_PTR("pixs not 1 bpp", __func__, NULL); + + w = pixGetWidth(pixs); + h = pixGetHeight(pixs); + mindim = L_MIN(w, h); + data = pixGetData(pixs); + wpl = pixGetWpl(pixs); + + if ((pta = ptaCreate(4)) == NULL) + return (PTA *)ERROR_PTR("pta not made", __func__, NULL); + + for (found = FALSE, i = 0; i < mindim; i++) { + for (j = 0; j <= i; j++) { + y = i - j; + line = data + y * wpl; + if (GET_DATA_BIT(line, j)) { + ptaAddPt(pta, j, y); + found = TRUE; + break; + } + } + if (found == TRUE) + break; + } + + for (found = FALSE, i = 0; i < mindim; i++) { + for (j = 0; j <= i; j++) { + y = i - j; + line = data + y * wpl; + x = w - 1 - j; + if (GET_DATA_BIT(line, x)) { + ptaAddPt(pta, x, y); + found = TRUE; + break; + } + } + if (found == TRUE) + break; + } + + for (found = FALSE, i = 0; i < mindim; i++) { + for (j = 0; j <= i; j++) { + y = h - 1 - i + j; + line = data + y * wpl; + if (GET_DATA_BIT(line, j)) { + ptaAddPt(pta, j, y); + found = TRUE; + break; + } + } + if (found == TRUE) + break; + } + + for (found = FALSE, i = 0; i < mindim; i++) { + for (j = 0; j <= i; j++) { + y = h - 1 - i + j; + line = data + y * wpl; + x = w - 1 - j; + if (GET_DATA_BIT(line, x)) { + ptaAddPt(pta, x, y); + found = TRUE; + break; + } + } + if (found == TRUE) + break; + } + + return pta; +} + + +/*! + * \brief ptaContainsPt() + * + * \param[in] pta + * \param[in] x, y point + * \return 1 if contained, 0 otherwise or on error + */ +l_int32 +ptaContainsPt(PTA *pta, + l_int32 x, + l_int32 y) +{ +l_int32 i, n, ix, iy; + + if (!pta) + return ERROR_INT("pta not defined", __func__, 0); + + n = ptaGetCount(pta); + for (i = 0; i < n; i++) { + ptaGetIPt(pta, i, &ix, &iy); + if (x == ix && y == iy) + return 1; + } + return 0; +} + + +/*! + * \brief ptaTestIntersection() + * + * \param[in] pta1, pta2 + * \return bval which is 1 if they have any elements in common; + * 0 otherwise or on error. + */ +l_int32 +ptaTestIntersection(PTA *pta1, + PTA *pta2) +{ +l_int32 i, j, n1, n2, x1, y1, x2, y2; + + if (!pta1) + return ERROR_INT("pta1 not defined", __func__, 0); + if (!pta2) + return ERROR_INT("pta2 not defined", __func__, 0); + + n1 = ptaGetCount(pta1); + n2 = ptaGetCount(pta2); + for (i = 0; i < n1; i++) { + ptaGetIPt(pta1, i, &x1, &y1); + for (j = 0; j < n2; j++) { + ptaGetIPt(pta2, i, &x2, &y2); + if (x1 == x2 && y1 == y2) + return 1; + } + } + + return 0; +} + + +/*! + * \brief ptaTransform() + * + * \param[in] ptas + * \param[in] shiftx, shifty + * \param[in] scalex, scaley + * \return pta, or NULL on error + * + * <pre> + * Notes: + * (1) Shift first, then scale. + * </pre> + */ +PTA * +ptaTransform(PTA *ptas, + l_int32 shiftx, + l_int32 shifty, + l_float32 scalex, + l_float32 scaley) +{ +l_int32 n, i, x, y; +PTA *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + n = ptaGetCount(ptas); + ptad = ptaCreate(n); + for (i = 0; i < n; i++) { + ptaGetIPt(ptas, i, &x, &y); + x = (l_int32)(scalex * (x + shiftx) + 0.5); + y = (l_int32)(scaley * (y + shifty) + 0.5); + ptaAddPt(ptad, x, y); + } + + return ptad; +} + + +/*! + * \brief ptaPtInsidePolygon() + * + * \param[in] pta vertices of a polygon + * \param[in] x, y point to be tested + * \param[out] pinside 1 if inside; 0 if outside or on boundary + * \return 1 if OK, 0 on error + * + * The abs value of the sum of the angles subtended from a point by + * the sides of a polygon, when taken in order traversing the polygon, + * is 0 if the point is outside the polygon and 2*pi if inside. + * The sign will be positive if traversed cw and negative if ccw. + */ +l_int32 +ptaPtInsidePolygon(PTA *pta, + l_float32 x, + l_float32 y, + l_int32 *pinside) +{ +l_int32 i, n; +l_float32 sum, x1, y1, x2, y2, xp1, yp1, xp2, yp2; + + if (!pinside) + return ERROR_INT("&inside not defined", __func__, 1); + *pinside = 0; + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + + /* Think of (x1,y1) as the end point of a vector that starts + * from the origin (0,0), and ditto for (x2,y2). */ + n = ptaGetCount(pta); + sum = 0.0; + for (i = 0; i < n; i++) { + ptaGetPt(pta, i, &xp1, &yp1); + ptaGetPt(pta, (i + 1) % n, &xp2, &yp2); + x1 = xp1 - x; + y1 = yp1 - y; + x2 = xp2 - x; + y2 = yp2 - y; + sum += l_angleBetweenVectors(x1, y1, x2, y2); + } + + if (L_ABS(sum) > M_PI) + *pinside = 1; + return 0; +} + + +/*! + * \brief l_angleBetweenVectors() + * + * \param[in] x1, y1 end point of first vector + * \param[in] x2, y2 end point of second vector + * \return angle radians, or 0.0 on error + * + * <pre> + * Notes: + * (1) This gives the angle between two vectors, going between + * vector1 (x1,y1) and vector2 (x2,y2). The angle is swept + * out from 1 --> 2. If this is clockwise, the angle is + * positive, but the result is folded into the interval [-pi, pi]. + * </pre> + */ +l_float32 +l_angleBetweenVectors(l_float32 x1, + l_float32 y1, + l_float32 x2, + l_float32 y2) +{ +l_float64 ang; + + ang = atan2(y2, x2) - atan2(y1, x1); + if (ang > M_PI) ang -= 2.0 * M_PI; + if (ang < -M_PI) ang += 2.0 * M_PI; + return ang; +} + + +/*! + * \brief ptaPolygonIsConvex() + * + * \param[in] pta corners of polygon + * \param[out] pisconvex 1 if convex; 0 otherwise + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) A Pta of size n describes a polygon with n sides, where + * the n-th side goes from point[n - 1] to point[0]. + * (2) The pta must describe a CLOCKWISE traversal of the boundary + * of the polygon. + * (3) Algorithm: traversing the boundary in a cw direction, the + * polygon interior is always on the right. If the polygon is + * convex, for each set of 3 points, the third point is either + * on the ray extending from the first point and going through + * the second point, or to the right of it. + * </pre> + */ +l_int32 +ptaPolygonIsConvex(PTA *pta, + l_int32 *pisconvex) +{ +l_int32 i, n; +l_float32 x0, y0, x1, y1, x2, y2; +l_float64 cprod; + + if (!pisconvex) + return ERROR_INT("&isconvex not defined", __func__, 1); + *pisconvex = 0; + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if ((n = ptaGetCount(pta)) < 3) + return ERROR_INT("pta has < 3 pts", __func__, 1); + + for (i = 0; i < n; i++) { + ptaGetPt(pta, i, &x0, &y0); + ptaGetPt(pta, (i + 1) % n, &x1, &y1); + ptaGetPt(pta, (i + 2) % n, &x2, &y2); + /* The vector v02 from p0 to p2 must be to the right of the + vector v01 from p0 to p1. This is true if the cross + product v02 x v01 > 0. In coordinates: + v02x * v01y - v01x * v02y, where + v01x = x1 - x0, v01y = y1 - y0, + v02x = x2 - x0, v02y = y2 - y0 */ + cprod = (x2 - x0) * (y1 - y0) - (x1 - x0) * (y2 - y0); + if (cprod < -0.0001) /* small delta for float accuracy; test fails */ + return 0; + } + *pisconvex = 1; + return 0; +} + + +/*---------------------------------------------------------------------* + * Min/max and filtering * + *---------------------------------------------------------------------*/ +/*! + * \brief ptaGetMinMax() + * + * \param[in] pta + * \param[out] pxmin [optional] min of x + * \param[out] pymin [optional] min of y + * \param[out] pxmax [optional] max of x + * \param[out] pymax [optional] max of y + * \return 0 if OK, 1 on error. If pta is empty, requested + * values are returned as -1.0. + */ +l_ok +ptaGetMinMax(PTA *pta, + l_float32 *pxmin, + l_float32 *pymin, + l_float32 *pxmax, + l_float32 *pymax) +{ +l_int32 i, n; +l_float32 x, y, xmin, ymin, xmax, ymax; + + if (pxmin) *pxmin = -1.0; + if (pymin) *pymin = -1.0; + if (pxmax) *pxmax = -1.0; + if (pymax) *pymax = -1.0; + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if (!pxmin && !pxmax && !pymin && !pymax) + return ERROR_INT("no output requested", __func__, 1); + if ((n = ptaGetCount(pta)) == 0) { + L_WARNING("pta is empty\n", __func__); + return 0; + } + + xmin = ymin = 1.0e20f; + xmax = ymax = -1.0e20f; + for (i = 0; i < n; i++) { + ptaGetPt(pta, i, &x, &y); + if (x < xmin) xmin = x; + if (y < ymin) ymin = y; + if (x > xmax) xmax = x; + if (y > ymax) ymax = y; + } + if (pxmin) *pxmin = xmin; + if (pymin) *pymin = ymin; + if (pxmax) *pxmax = xmax; + if (pymax) *pymax = ymax; + return 0; +} + + +/*! + * \brief ptaSelectByValue() + * + * \param[in] ptas + * \param[in] xth, yth threshold values + * \param[in] type L_SELECT_XVAL, L_SELECT_YVAL, + * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH + * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT, + * L_SELECT_IF_LTE, L_SELECT_IF_GTE + * \return ptad filtered set, or NULL on error + */ +PTA * +ptaSelectByValue(PTA *ptas, + l_float32 xth, + l_float32 yth, + l_int32 type, + l_int32 relation) +{ +l_int32 i, n; +l_float32 x, y; +PTA *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + if (ptaGetCount(ptas) == 0) { + L_WARNING("ptas is empty\n", __func__); + return ptaCopy(ptas); + } + if (type != L_SELECT_XVAL && type != L_SELECT_YVAL && + type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH) + return (PTA *)ERROR_PTR("invalid type", __func__, NULL); + if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT && + relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE) + return (PTA *)ERROR_PTR("invalid relation", __func__, NULL); + + n = ptaGetCount(ptas); + ptad = ptaCreate(n); + for (i = 0; i < n; i++) { + ptaGetPt(ptas, i, &x, &y); + if (type == L_SELECT_XVAL) { + if ((relation == L_SELECT_IF_LT && x < xth) || + (relation == L_SELECT_IF_GT && x > xth) || + (relation == L_SELECT_IF_LTE && x <= xth) || + (relation == L_SELECT_IF_GTE && x >= xth)) + ptaAddPt(ptad, x, y); + } else if (type == L_SELECT_YVAL) { + if ((relation == L_SELECT_IF_LT && y < yth) || + (relation == L_SELECT_IF_GT && y > yth) || + (relation == L_SELECT_IF_LTE && y <= yth) || + (relation == L_SELECT_IF_GTE && y >= yth)) + ptaAddPt(ptad, x, y); + } else if (type == L_SELECT_IF_EITHER) { + if (((relation == L_SELECT_IF_LT) && (x < xth || y < yth)) || + ((relation == L_SELECT_IF_GT) && (x > xth || y > yth)) || + ((relation == L_SELECT_IF_LTE) && (x <= xth || y <= yth)) || + ((relation == L_SELECT_IF_GTE) && (x >= xth || y >= yth))) + ptaAddPt(ptad, x, y); + } else { /* L_SELECT_IF_BOTH */ + if (((relation == L_SELECT_IF_LT) && (x < xth && y < yth)) || + ((relation == L_SELECT_IF_GT) && (x > xth && y > yth)) || + ((relation == L_SELECT_IF_LTE) && (x <= xth && y <= yth)) || + ((relation == L_SELECT_IF_GTE) && (x >= xth && y >= yth))) + ptaAddPt(ptad, x, y); + } + } + + return ptad; +} + + +/*! + * \brief ptaCropToMask() + * + * \param[in] ptas input pta + * \param[in] pixm 1 bpp mask + * \return ptad with only pts under the mask fg, or NULL on error + */ +PTA * +ptaCropToMask(PTA *ptas, + PIX *pixm) +{ +l_int32 i, n, x, y; +l_uint32 val; +PTA *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + if (!pixm || pixGetDepth(pixm) != 1) + return (PTA *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL); + if (ptaGetCount(ptas) == 0) { + L_INFO("ptas is empty\n", __func__); + return ptaCopy(ptas); + } + + n = ptaGetCount(ptas); + ptad = ptaCreate(n); + for (i = 0; i < n; i++) { + ptaGetIPt(ptas, i, &x, &y); + pixGetPixel(pixm, x, y, &val); + if (val == 1) + ptaAddPt(ptad, x, y); + } + return ptad; +} + + +/*---------------------------------------------------------------------* + * Least Squares Fit * + *---------------------------------------------------------------------*/ +/*! + * \brief ptaGetLinearLSF() + * + * \param[in] pta + * \param[out] pa [optional] slope a of least square fit: y = ax + b + * \param[out] pb [optional] intercept b of least square fit + * \param[out] pnafit [optional] numa of least square fit + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Either or both &a and &b must be input. They determine the + * type of line that is fit. + * (2) If both &a and &b are defined, this returns a and b that minimize: + * + * sum (yi - axi -b)^2 + * i + * + * The method is simple: differentiate this expression w/rt a and b, + * and solve the resulting two equations for a and b in terms of + * various sums over the input data (xi, yi). + * (3) We also allow two special cases, where either a = 0 or b = 0: + * (a) If &a is given and &b = null, find the linear LSF that + * goes through the origin (b = 0). + * (b) If &b is given and &a = null, find the linear LSF with + * zero slope (a = 0). + * (4) If &nafit is defined, this returns an array of fitted values, + * corresponding to the two implicit Numa arrays (nax and nay) in pta. + * Thus, just as you can plot the data in pta as nay vs. nax, + * you can plot the linear least square fit as nafit vs. nax. + * Get the nax array using ptaGetArrays(pta, &nax, NULL); + * </pre> + */ +l_ok +ptaGetLinearLSF(PTA *pta, + l_float32 *pa, + l_float32 *pb, + NUMA **pnafit) +{ +l_int32 n, i; +l_float32 a, b, factor, sx, sy, sxx, sxy, val; +l_float32 *xa, *ya; + + if (pa) *pa = 0.0; + if (pb) *pb = 0.0; + if (pnafit) *pnafit = NULL; + if (!pa && !pb && !pnafit) + return ERROR_INT("no output requested", __func__, 1); + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if ((n = ptaGetCount(pta)) < 2) + return ERROR_INT("less than 2 pts found", __func__, 1); + + xa = pta->x; /* not a copy */ + ya = pta->y; /* not a copy */ + sx = sy = sxx = sxy = 0.; + if (pa && pb) { /* general line */ + for (i = 0; i < n; i++) { + sx += xa[i]; + sy += ya[i]; + sxx += xa[i] * xa[i]; + sxy += xa[i] * ya[i]; + } + factor = n * sxx - sx * sx; + if (factor == 0.0) + return ERROR_INT("no solution found", __func__, 1); + factor = 1.f / factor; + + a = factor * ((l_float32)n * sxy - sx * sy); + b = factor * (sxx * sy - sx * sxy); + } else if (pa) { /* b = 0; line through origin */ + for (i = 0; i < n; i++) { + sxx += xa[i] * xa[i]; + sxy += xa[i] * ya[i]; + } + if (sxx == 0.0) + return ERROR_INT("no solution found", __func__, 1); + a = sxy / sxx; + b = 0.0; + } else { /* a = 0; horizontal line */ + for (i = 0; i < n; i++) + sy += ya[i]; + a = 0.0; + b = sy / (l_float32)n; + } + + if (pnafit) { + *pnafit = numaCreate(n); + for (i = 0; i < n; i++) { + val = a * xa[i] + b; + numaAddNumber(*pnafit, val); + } + } + + if (pa) *pa = a; + if (pb) *pb = b; + return 0; +} + + +/*! + * \brief ptaGetQuadraticLSF() + * + * \param[in] pta + * \param[out] pa [optional] coeff a of LSF: y = ax^2 + bx + c + * \param[out] pb [optional] coeff b of LSF: y = ax^2 + bx + c + * \param[out] pc [optional] coeff c of LSF: y = ax^2 + bx + c + * \param[out] pnafit [optional] numa of least square fit + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This does a quadratic least square fit to the set of points + * in %pta. That is, it finds coefficients a, b and c that minimize: + * + * sum (yi - a*xi*xi -b*xi -c)^2 + * i + * + * The method is simple: differentiate this expression w/rt + * a, b and c, and solve the resulting three equations for these + * coefficients in terms of various sums over the input data (xi, yi). + * The three equations are in the form: + * f[0][0]a + f[0][1]b + f[0][2]c = g[0] + * f[1][0]a + f[1][1]b + f[1][2]c = g[1] + * f[2][0]a + f[2][1]b + f[2][2]c = g[2] + * (2) If &nafit is defined, this returns an array of fitted values, + * corresponding to the two implicit Numa arrays (nax and nay) in pta. + * Thus, just as you can plot the data in pta as nay vs. nax, + * you can plot the linear least square fit as nafit vs. nax. + * Get the nax array using ptaGetArrays(pta, &nax, NULL); + * </pre> + */ +l_ok +ptaGetQuadraticLSF(PTA *pta, + l_float32 *pa, + l_float32 *pb, + l_float32 *pc, + NUMA **pnafit) +{ +l_int32 n, i, ret; +l_float32 x, y, sx, sy, sx2, sx3, sx4, sxy, sx2y; +l_float32 *xa, *ya; +l_float32 *f[3]; +l_float32 g[3]; + + if (pa) *pa = 0.0; + if (pb) *pb = 0.0; + if (pc) *pc = 0.0; + if (pnafit) *pnafit = NULL; + if (!pa && !pb && !pc && !pnafit) + return ERROR_INT("no output requested", __func__, 1); + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if ((n = ptaGetCount(pta)) < 3) + return ERROR_INT("less than 3 pts found", __func__, 1); + + xa = pta->x; /* not a copy */ + ya = pta->y; /* not a copy */ + sx = sy = sx2 = sx3 = sx4 = sxy = sx2y = 0.; + for (i = 0; i < n; i++) { + x = xa[i]; + y = ya[i]; + sx += x; + sy += y; + sx2 += x * x; + sx3 += x * x * x; + sx4 += x * x * x * x; + sxy += x * y; + sx2y += x * x * y; + } + + for (i = 0; i < 3; i++) + f[i] = (l_float32 *)LEPT_CALLOC(3, sizeof(l_float32)); + f[0][0] = sx4; + f[0][1] = sx3; + f[0][2] = sx2; + f[1][0] = sx3; + f[1][1] = sx2; + f[1][2] = sx; + f[2][0] = sx2; + f[2][1] = sx; + f[2][2] = n; + g[0] = sx2y; + g[1] = sxy; + g[2] = sy; + + /* Solve for the unknowns, also putting f-inverse into f */ + ret = gaussjordan(f, g, 3); + for (i = 0; i < 3; i++) + LEPT_FREE(f[i]); + if (ret) + return ERROR_INT("quadratic solution failed", __func__, 1); + + if (pa) *pa = g[0]; + if (pb) *pb = g[1]; + if (pc) *pc = g[2]; + if (pnafit) { + *pnafit = numaCreate(n); + for (i = 0; i < n; i++) { + x = xa[i]; + y = g[0] * x * x + g[1] * x + g[2]; + numaAddNumber(*pnafit, y); + } + } + return 0; +} + + +/*! + * \brief ptaGetCubicLSF() + * + * \param[in] pta + * \param[out] pa [optional] coeff a of LSF: y = ax^3 + bx^2 + cx + d + * \param[out] pb [optional] coeff b of LSF + * \param[out] pc [optional] coeff c of LSF + * \param[out] pd [optional] coeff d of LSF + * \param[out] pnafit [optional] numa of least square fit + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This does a cubic least square fit to the set of points + * in %pta. That is, it finds coefficients a, b, c and d + * that minimize: + * + * sum (yi - a*xi*xi*xi -b*xi*xi -c*xi - d)^2 + * i + * + * Differentiate this expression w/rt a, b, c and d, and solve + * the resulting four equations for these coefficients in + * terms of various sums over the input data (xi, yi). + * The four equations are in the form: + * f[0][0]a + f[0][1]b + f[0][2]c + f[0][3] = g[0] + * f[1][0]a + f[1][1]b + f[1][2]c + f[1][3] = g[1] + * f[2][0]a + f[2][1]b + f[2][2]c + f[2][3] = g[2] + * f[3][0]a + f[3][1]b + f[3][2]c + f[3][3] = g[3] + * (2) If &nafit is defined, this returns an array of fitted values, + * corresponding to the two implicit Numa arrays (nax and nay) in pta. + * Thus, just as you can plot the data in pta as nay vs. nax, + * you can plot the linear least square fit as nafit vs. nax. + * Get the nax array using ptaGetArrays(pta, &nax, NULL); + * </pre> + */ +l_ok +ptaGetCubicLSF(PTA *pta, + l_float32 *pa, + l_float32 *pb, + l_float32 *pc, + l_float32 *pd, + NUMA **pnafit) +{ +l_int32 n, i, ret; +l_float32 x, y, sx, sy, sx2, sx3, sx4, sx5, sx6, sxy, sx2y, sx3y; +l_float32 *xa, *ya; +l_float32 *f[4]; +l_float32 g[4]; + + if (pa) *pa = 0.0; + if (pb) *pb = 0.0; + if (pc) *pc = 0.0; + if (pd) *pd = 0.0; + if (pnafit) *pnafit = NULL; + if (!pa && !pb && !pc && !pd && !pnafit) + return ERROR_INT("no output requested", __func__, 1); + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if ((n = ptaGetCount(pta)) < 4) + return ERROR_INT("less than 4 pts found", __func__, 1); + + xa = pta->x; /* not a copy */ + ya = pta->y; /* not a copy */ + sx = sy = sx2 = sx3 = sx4 = sx5 = sx6 = sxy = sx2y = sx3y = 0.; + for (i = 0; i < n; i++) { + x = xa[i]; + y = ya[i]; + sx += x; + sy += y; + sx2 += x * x; + sx3 += x * x * x; + sx4 += x * x * x * x; + sx5 += x * x * x * x * x; + sx6 += x * x * x * x * x * x; + sxy += x * y; + sx2y += x * x * y; + sx3y += x * x * x * y; + } + + for (i = 0; i < 4; i++) + f[i] = (l_float32 *)LEPT_CALLOC(4, sizeof(l_float32)); + f[0][0] = sx6; + f[0][1] = sx5; + f[0][2] = sx4; + f[0][3] = sx3; + f[1][0] = sx5; + f[1][1] = sx4; + f[1][2] = sx3; + f[1][3] = sx2; + f[2][0] = sx4; + f[2][1] = sx3; + f[2][2] = sx2; + f[2][3] = sx; + f[3][0] = sx3; + f[3][1] = sx2; + f[3][2] = sx; + f[3][3] = n; + g[0] = sx3y; + g[1] = sx2y; + g[2] = sxy; + g[3] = sy; + + /* Solve for the unknowns, also putting f-inverse into f */ + ret = gaussjordan(f, g, 4); + for (i = 0; i < 4; i++) + LEPT_FREE(f[i]); + if (ret) + return ERROR_INT("cubic solution failed", __func__, 1); + + if (pa) *pa = g[0]; + if (pb) *pb = g[1]; + if (pc) *pc = g[2]; + if (pd) *pd = g[3]; + if (pnafit) { + *pnafit = numaCreate(n); + for (i = 0; i < n; i++) { + x = xa[i]; + y = g[0] * x * x * x + g[1] * x * x + g[2] * x + g[3]; + numaAddNumber(*pnafit, y); + } + } + return 0; +} + + +/*! + * \brief ptaGetQuarticLSF() + * + * \param[in] pta + * \param[out] pa [optional] coeff a of LSF: + * y = ax^4 + bx^3 + cx^2 + dx + e + * \param[out] pb [optional] coeff b of LSF + * \param[out] pc [optional] coeff c of LSF + * \param[out] pd [optional] coeff d of LSF + * \param[out] pe [optional] coeff e of LSF + * \param[out] pnafit [optional] numa of least square fit + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This does a quartic least square fit to the set of points + * in %pta. That is, it finds coefficients a, b, c, d and 3 + * that minimize: + * + * sum (yi - a*xi*xi*xi*xi -b*xi*xi*xi -c*xi*xi - d*xi - e)^2 + * i + * + * Differentiate this expression w/rt a, b, c, d and e, and solve + * the resulting five equations for these coefficients in + * terms of various sums over the input data (xi, yi). + * The five equations are in the form: + * f[0][0]a + f[0][1]b + f[0][2]c + f[0][3] + f[0][4] = g[0] + * f[1][0]a + f[1][1]b + f[1][2]c + f[1][3] + f[1][4] = g[1] + * f[2][0]a + f[2][1]b + f[2][2]c + f[2][3] + f[2][4] = g[2] + * f[3][0]a + f[3][1]b + f[3][2]c + f[3][3] + f[3][4] = g[3] + * f[4][0]a + f[4][1]b + f[4][2]c + f[4][3] + f[4][4] = g[4] + * (2) If &nafit is defined, this returns an array of fitted values, + * corresponding to the two implicit Numa arrays (nax and nay) in pta. + * Thus, just as you can plot the data in pta as nay vs. nax, + * you can plot the linear least square fit as nafit vs. nax. + * Get the nax array using ptaGetArrays(pta, &nax, NULL); + * </pre> + */ +l_ok +ptaGetQuarticLSF(PTA *pta, + l_float32 *pa, + l_float32 *pb, + l_float32 *pc, + l_float32 *pd, + l_float32 *pe, + NUMA **pnafit) +{ +l_int32 n, i, ret; +l_float32 x, y, sx, sy, sx2, sx3, sx4, sx5, sx6, sx7, sx8; +l_float32 sxy, sx2y, sx3y, sx4y; +l_float32 *xa, *ya; +l_float32 *f[5]; +l_float32 g[5]; + + if (pa) *pa = 0.0; + if (pb) *pb = 0.0; + if (pc) *pc = 0.0; + if (pd) *pd = 0.0; + if (pe) *pe = 0.0; + if (pnafit) *pnafit = NULL; + if (!pa && !pb && !pc && !pd && !pe && !pnafit) + return ERROR_INT("no output requested", __func__, 1); + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if ((n = ptaGetCount(pta)) < 5) + return ERROR_INT("less than 5 pts found", __func__, 1); + + xa = pta->x; /* not a copy */ + ya = pta->y; /* not a copy */ + sx = sy = sx2 = sx3 = sx4 = sx5 = sx6 = sx7 = sx8 = 0; + sxy = sx2y = sx3y = sx4y = 0.; + for (i = 0; i < n; i++) { + x = xa[i]; + y = ya[i]; + sx += x; + sy += y; + sx2 += x * x; + sx3 += x * x * x; + sx4 += x * x * x * x; + sx5 += x * x * x * x * x; + sx6 += x * x * x * x * x * x; + sx7 += x * x * x * x * x * x * x; + sx8 += x * x * x * x * x * x * x * x; + sxy += x * y; + sx2y += x * x * y; + sx3y += x * x * x * y; + sx4y += x * x * x * x * y; + } + + for (i = 0; i < 5; i++) + f[i] = (l_float32 *)LEPT_CALLOC(5, sizeof(l_float32)); + f[0][0] = sx8; + f[0][1] = sx7; + f[0][2] = sx6; + f[0][3] = sx5; + f[0][4] = sx4; + f[1][0] = sx7; + f[1][1] = sx6; + f[1][2] = sx5; + f[1][3] = sx4; + f[1][4] = sx3; + f[2][0] = sx6; + f[2][1] = sx5; + f[2][2] = sx4; + f[2][3] = sx3; + f[2][4] = sx2; + f[3][0] = sx5; + f[3][1] = sx4; + f[3][2] = sx3; + f[3][3] = sx2; + f[3][4] = sx; + f[4][0] = sx4; + f[4][1] = sx3; + f[4][2] = sx2; + f[4][3] = sx; + f[4][4] = n; + g[0] = sx4y; + g[1] = sx3y; + g[2] = sx2y; + g[3] = sxy; + g[4] = sy; + + /* Solve for the unknowns, also putting f-inverse into f */ + ret = gaussjordan(f, g, 5); + for (i = 0; i < 5; i++) + LEPT_FREE(f[i]); + if (ret) + return ERROR_INT("quartic solution failed", __func__, 1); + + if (pa) *pa = g[0]; + if (pb) *pb = g[1]; + if (pc) *pc = g[2]; + if (pd) *pd = g[3]; + if (pe) *pe = g[4]; + if (pnafit) { + *pnafit = numaCreate(n); + for (i = 0; i < n; i++) { + x = xa[i]; + y = g[0] * x * x * x * x + g[1] * x * x * x + g[2] * x * x + + g[3] * x + g[4]; + numaAddNumber(*pnafit, y); + } + } + return 0; +} + + +/*! + * \brief ptaNoisyLinearLSF() + * + * \param[in] pta + * \param[in] factor reject outliers with error greater than this + * number of medians; typically ~ 3 + * \param[out] pptad [optional] with outliers removed + * \param[out] pa [optional] slope a of least square fit: y = ax + b + * \param[out] pb [optional] intercept b of least square fit + * \param[out] pmederr [optional] median error + * \param[out] pnafit [optional] numa of least square fit to ptad + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This does a linear least square fit to the set of points + * in %pta. It then evaluates the errors and removes points + * whose error is >= factor * median_error. It then re-runs + * the linear LSF on the resulting points. + * (2) Either or both &a and &b must be input. They determine the + * type of line that is fit. + * (3) The median error can give an indication of how good the fit + * is likely to be. + * </pre> + */ +l_ok +ptaNoisyLinearLSF(PTA *pta, + l_float32 factor, + PTA **pptad, + l_float32 *pa, + l_float32 *pb, + l_float32 *pmederr, + NUMA **pnafit) +{ +l_int32 n, i, ret; +l_float32 x, y, yf, val, mederr; +NUMA *nafit, *naerror; +PTA *ptad; + + if (pptad) *pptad = NULL; + if (pa) *pa = 0.0; + if (pb) *pb = 0.0; + if (pmederr) *pmederr = 0.0; + if (pnafit) *pnafit = NULL; + if (!pptad && !pa && !pb && !pnafit) + return ERROR_INT("no output requested", __func__, 1); + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if (factor <= 0.0) + return ERROR_INT("factor must be > 0.0", __func__, 1); + if ((n = ptaGetCount(pta)) < 3) + return ERROR_INT("less than 2 pts found", __func__, 1); + + if (ptaGetLinearLSF(pta, pa, pb, &nafit) != 0) + return ERROR_INT("error in linear LSF", __func__, 1); + + /* Get the median error */ + naerror = numaCreate(n); + for (i = 0; i < n; i++) { + ptaGetPt(pta, i, &x, &y); + numaGetFValue(nafit, i, &yf); + numaAddNumber(naerror, L_ABS(y - yf)); + } + numaGetMedian(naerror, &mederr); + if (pmederr) *pmederr = mederr; + numaDestroy(&nafit); + + /* Remove outliers */ + ptad = ptaCreate(n); + for (i = 0; i < n; i++) { + ptaGetPt(pta, i, &x, &y); + numaGetFValue(naerror, i, &val); + if (val <= factor * mederr) /* <= in case mederr = 0 */ + ptaAddPt(ptad, x, y); + } + numaDestroy(&naerror); + + /* Do LSF again */ + ret = ptaGetLinearLSF(ptad, pa, pb, pnafit); + if (pptad) + *pptad = ptad; + else + ptaDestroy(&ptad); + + return ret; +} + + +/*! + * \brief ptaNoisyQuadraticLSF() + * + * \param[in] pta + * \param[in] factor reject outliers with error greater than this + * number of medians; typically ~ 3 + * \param[out] pptad [optional] with outliers removed + * \param[out] pa [optional] coeff a of LSF: y = ax^2 + bx + c + * \param[out] pb [optional] coeff b of LSF: y = ax^2 + bx + c + * \param[out] pc [optional] coeff c of LSF: y = ax^2 + bx + c + * \param[out] pmederr [optional] median error + * \param[out] pnafit [optional] numa of least square fit to ptad + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This does a quadratic least square fit to the set of points + * in %pta. It then evaluates the errors and removes points + * whose error is >= factor * median_error. It then re-runs + * a quadratic LSF on the resulting points. + * </pre> + */ +l_ok +ptaNoisyQuadraticLSF(PTA *pta, + l_float32 factor, + PTA **pptad, + l_float32 *pa, + l_float32 *pb, + l_float32 *pc, + l_float32 *pmederr, + NUMA **pnafit) +{ +l_int32 n, i, ret; +l_float32 x, y, yf, val, mederr; +NUMA *nafit, *naerror; +PTA *ptad; + + if (pptad) *pptad = NULL; + if (pa) *pa = 0.0; + if (pb) *pb = 0.0; + if (pc) *pc = 0.0; + if (pmederr) *pmederr = 0.0; + if (pnafit) *pnafit = NULL; + if (!pptad && !pa && !pb && !pc && !pnafit) + return ERROR_INT("no output requested", __func__, 1); + if (factor <= 0.0) + return ERROR_INT("factor must be > 0.0", __func__, 1); + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if ((n = ptaGetCount(pta)) < 3) + return ERROR_INT("less than 3 pts found", __func__, 1); + + if (ptaGetQuadraticLSF(pta, NULL, NULL, NULL, &nafit) != 0) + return ERROR_INT("error in quadratic LSF", __func__, 1); + + /* Get the median error */ + naerror = numaCreate(n); + for (i = 0; i < n; i++) { + ptaGetPt(pta, i, &x, &y); + numaGetFValue(nafit, i, &yf); + numaAddNumber(naerror, L_ABS(y - yf)); + } + numaGetMedian(naerror, &mederr); + if (pmederr) *pmederr = mederr; + numaDestroy(&nafit); + + /* Remove outliers */ + ptad = ptaCreate(n); + for (i = 0; i < n; i++) { + ptaGetPt(pta, i, &x, &y); + numaGetFValue(naerror, i, &val); + if (val <= factor * mederr) /* <= in case mederr = 0 */ + ptaAddPt(ptad, x, y); + } + numaDestroy(&naerror); + n = ptaGetCount(ptad); + if ((n = ptaGetCount(ptad)) < 3) { + ptaDestroy(&ptad); + return ERROR_INT("less than 3 pts found", __func__, 1); + } + + /* Do LSF again */ + ret = ptaGetQuadraticLSF(ptad, pa, pb, pc, pnafit); + if (pptad) + *pptad = ptad; + else + ptaDestroy(&ptad); + + return ret; +} + + +/*! + * \brief applyLinearFit() + * + * \param[in] a, b linear fit coefficients + * \param[in] x + * \param[out] py y = a * x + b + * \return 0 if OK, 1 on error + */ +l_ok +applyLinearFit(l_float32 a, + l_float32 b, + l_float32 x, + l_float32 *py) +{ + if (!py) + return ERROR_INT("&y not defined", __func__, 1); + + *py = a * x + b; + return 0; +} + + +/*! + * \brief applyQuadraticFit() + * + * \param[in] a, b, c quadratic fit coefficients + * \param[in] x + * \param[out] py y = a * x^2 + b * x + c + * \return 0 if OK, 1 on error + */ +l_ok +applyQuadraticFit(l_float32 a, + l_float32 b, + l_float32 c, + l_float32 x, + l_float32 *py) +{ + if (!py) + return ERROR_INT("&y not defined", __func__, 1); + + *py = a * x * x + b * x + c; + return 0; +} + + +/*! + * \brief applyCubicFit() + * + * \param[in] a, b, c, d cubic fit coefficients + * \param[in] x + * \param[out] py y = a * x^3 + b * x^2 + c * x + d + * \return 0 if OK, 1 on error + */ +l_ok +applyCubicFit(l_float32 a, + l_float32 b, + l_float32 c, + l_float32 d, + l_float32 x, + l_float32 *py) +{ + if (!py) + return ERROR_INT("&y not defined", __func__, 1); + + *py = a * x * x * x + b * x * x + c * x + d; + return 0; +} + + +/*! + * \brief applyQuarticFit() + * + * \param[in] a, b, c, d, e quartic fit coefficients + * \param[in] x + * \param[out] py y = a * x^4 + b * x^3 + c * x^2 + d * x + e + * \return 0 if OK, 1 on error + */ +l_ok +applyQuarticFit(l_float32 a, + l_float32 b, + l_float32 c, + l_float32 d, + l_float32 e, + l_float32 x, + l_float32 *py) +{ +l_float32 x2; + + if (!py) + return ERROR_INT("&y not defined", __func__, 1); + + x2 = x * x; + *py = a * x2 * x2 + b * x2 * x + c * x2 + d * x + e; + return 0; +} + + +/*---------------------------------------------------------------------* + * Interconversions with Pix * + *---------------------------------------------------------------------*/ +/*! + * \brief pixPlotAlongPta() + * + * \param[in] pixs any depth + * \param[in] pta set of points on which to plot + * \param[in] outformat GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_LATEX + * \param[in] title [optional] for plot; can be null + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This is a debugging function. + * (2) Removes existing colormaps and clips the pta to the input %pixs. + * (3) If the image is RGB, three separate plots are generated. + * </pre> + */ +l_ok +pixPlotAlongPta(PIX *pixs, + PTA *pta, + l_int32 outformat, + const char *title) +{ +char buffer[128]; +char *rtitle, *gtitle, *btitle; +static l_int32 count = 0; /* require separate temp files for each call */ +l_int32 i, x, y, d, w, h, npts, rval, gval, bval; +l_uint32 val; +NUMA *na, *nar, *nag, *nab; +PIX *pixt; + + lept_mkdir("lept/plot"); + + if (!pixs) + return ERROR_INT("pixs not defined", __func__, 1); + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + if (outformat != GPLOT_PNG && outformat != GPLOT_PS && + outformat != GPLOT_EPS && outformat != GPLOT_LATEX) { + L_WARNING("outformat invalid; using GPLOT_PNG\n", __func__); + outformat = GPLOT_PNG; + } + + pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); + d = pixGetDepth(pixt); + w = pixGetWidth(pixt); + h = pixGetHeight(pixt); + npts = ptaGetCount(pta); + if (d == 32) { + nar = numaCreate(npts); + nag = numaCreate(npts); + nab = numaCreate(npts); + for (i = 0; i < npts; i++) { + ptaGetIPt(pta, i, &x, &y); + if (x < 0 || x >= w) + continue; + if (y < 0 || y >= h) + continue; + pixGetPixel(pixt, x, y, &val); + rval = GET_DATA_BYTE(&val, COLOR_RED); + gval = GET_DATA_BYTE(&val, COLOR_GREEN); + bval = GET_DATA_BYTE(&val, COLOR_BLUE); + numaAddNumber(nar, rval); + numaAddNumber(nag, gval); + numaAddNumber(nab, bval); + } + + snprintf(buffer, sizeof(buffer), "/tmp/lept/plot/%03d", count++); + rtitle = stringJoin("Red: ", title); + gplotSimple1(nar, outformat, buffer, rtitle); + snprintf(buffer, sizeof(buffer), "/tmp/lept/plot/%03d", count++); + gtitle = stringJoin("Green: ", title); + gplotSimple1(nag, outformat, buffer, gtitle); + snprintf(buffer, sizeof(buffer), "/tmp/lept/plot/%03d", count++); + btitle = stringJoin("Blue: ", title); + gplotSimple1(nab, outformat, buffer, btitle); + numaDestroy(&nar); + numaDestroy(&nag); + numaDestroy(&nab); + LEPT_FREE(rtitle); + LEPT_FREE(gtitle); + LEPT_FREE(btitle); + } else { + na = numaCreate(npts); + for (i = 0; i < npts; i++) { + ptaGetIPt(pta, i, &x, &y); + if (x < 0 || x >= w) + continue; + if (y < 0 || y >= h) + continue; + pixGetPixel(pixt, x, y, &val); + numaAddNumber(na, (l_float32)val); + } + + snprintf(buffer, sizeof(buffer), "/tmp/lept/plot/%03d", count++); + gplotSimple1(na, outformat, buffer, title); + numaDestroy(&na); + } + pixDestroy(&pixt); + return 0; +} + + +/*! + * \brief ptaGetPixelsFromPix() + * + * \param[in] pixs 1 bpp + * \param[in] box [optional] can be null + * \return pta, or NULL on error + * + * <pre> + * Notes: + * (1) Generates a pta of fg pixels in the pix, within the box. + * If box == NULL, it uses the entire pix. + * </pre> + */ +PTA * +ptaGetPixelsFromPix(PIX *pixs, + BOX *box) +{ +l_int32 i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh; +l_uint32 *data, *line; +PTA *pta; + + if (!pixs || (pixGetDepth(pixs) != 1)) + return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); + + pixGetDimensions(pixs, &w, &h, NULL); + data = pixGetData(pixs); + wpl = pixGetWpl(pixs); + xstart = ystart = 0; + xend = w - 1; + yend = h - 1; + if (box) { + boxGetGeometry(box, &xstart, &ystart, &bw, &bh); + xend = xstart + bw - 1; + yend = ystart + bh - 1; + } + + if ((pta = ptaCreate(0)) == NULL) + return (PTA *)ERROR_PTR("pta not made", __func__, NULL); + for (i = ystart; i <= yend; i++) { + line = data + i * wpl; + for (j = xstart; j <= xend; j++) { + if (GET_DATA_BIT(line, j)) + ptaAddPt(pta, j, i); + } + } + + return pta; +} + + +/*! + * \brief pixGenerateFromPta() + * + * \param[in] pta + * \param[in] w, h of pix + * \return pix 1 bpp, or NULL on error + * + * <pre> + * Notes: + * (1) Points are rounded to nearest ints. + * (2) Any points outside (w,h) are silently discarded. + * (3) Output 1 bpp pix has values 1 for each point in the pta. + * </pre> + */ +PIX * +pixGenerateFromPta(PTA *pta, + l_int32 w, + l_int32 h) +{ +l_int32 n, i, x, y; +PIX *pix; + + if (!pta) + return (PIX *)ERROR_PTR("pta not defined", __func__, NULL); + + if ((pix = pixCreate(w, h, 1)) == NULL) + return (PIX *)ERROR_PTR("pix not made", __func__, NULL); + n = ptaGetCount(pta); + for (i = 0; i < n; i++) { + ptaGetIPt(pta, i, &x, &y); + if (x < 0 || x >= w || y < 0 || y >= h) + continue; + pixSetPixel(pix, x, y, 1); + } + + return pix; +} + + +/*! + * \brief ptaGetBoundaryPixels() + * + * \param[in] pixs 1 bpp + * \param[in] type L_BOUNDARY_FG, L_BOUNDARY_BG + * \return pta, or NULL on error + * + * <pre> + * Notes: + * (1) This generates a pta of either fg or bg boundary pixels. + * (2) See also pixGeneratePtaBoundary() for rendering of + * fg boundary pixels. + * </pre> + */ +PTA * +ptaGetBoundaryPixels(PIX *pixs, + l_int32 type) +{ +PIX *pixt; +PTA *pta; + + if (!pixs || (pixGetDepth(pixs) != 1)) + return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); + if (type != L_BOUNDARY_FG && type != L_BOUNDARY_BG) + return (PTA *)ERROR_PTR("invalid type", __func__, NULL); + + if (type == L_BOUNDARY_FG) + pixt = pixMorphSequence(pixs, "e3.3", 0); + else + pixt = pixMorphSequence(pixs, "d3.3", 0); + pixXor(pixt, pixt, pixs); + pta = ptaGetPixelsFromPix(pixt, NULL); + + pixDestroy(&pixt); + return pta; +} + + +/*! + * \brief ptaaGetBoundaryPixels() + * + * \param[in] pixs 1 bpp + * \param[in] type L_BOUNDARY_FG, L_BOUNDARY_BG + * \param[in] connectivity 4 or 8 + * \param[out] pboxa [optional] bounding boxes of the c.c. + * \param[out] ppixa [optional] pixa of the c.c. + * \return ptaa, or NULL on error + * + * <pre> + * Notes: + * (1) This generates a ptaa of either fg or bg boundary pixels, + * where each pta has the boundary pixels for a connected + * component. + * (2) We can't simply find all the boundary pixels and then select + * those within the bounding box of each component, because + * bounding boxes can overlap. It is necessary to extract and + * dilate or erode each component separately. Note also that + * special handling is required for bg pixels when the + * component touches the pix boundary. + * </pre> + */ +PTAA * +ptaaGetBoundaryPixels(PIX *pixs, + l_int32 type, + l_int32 connectivity, + BOXA **pboxa, + PIXA **ppixa) +{ +l_int32 i, n, w, h, x, y, bw, bh, left, right, top, bot; +BOXA *boxa; +PIX *pixt1, *pixt2; +PIXA *pixa; +PTA *pta1, *pta2; +PTAA *ptaa; + + if (pboxa) *pboxa = NULL; + if (ppixa) *ppixa = NULL; + if (!pixs || (pixGetDepth(pixs) != 1)) + return (PTAA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); + if (type != L_BOUNDARY_FG && type != L_BOUNDARY_BG) + return (PTAA *)ERROR_PTR("invalid type", __func__, NULL); + if (connectivity != 4 && connectivity != 8) + return (PTAA *)ERROR_PTR("connectivity not 4 or 8", __func__, NULL); + + pixGetDimensions(pixs, &w, &h, NULL); + boxa = pixConnComp(pixs, &pixa, connectivity); + n = boxaGetCount(boxa); + ptaa = ptaaCreate(0); + for (i = 0; i < n; i++) { + pixt1 = pixaGetPix(pixa, i, L_CLONE); + boxaGetBoxGeometry(boxa, i, &x, &y, &bw, &bh); + left = right = top = bot = 0; + if (type == L_BOUNDARY_BG) { + if (x > 0) left = 1; + if (y > 0) top = 1; + if (x + bw < w) right = 1; + if (y + bh < h) bot = 1; + pixt2 = pixAddBorderGeneral(pixt1, left, right, top, bot, 0); + } else { + pixt2 = pixClone(pixt1); + } + pta1 = ptaGetBoundaryPixels(pixt2, type); + pta2 = ptaTransform(pta1, x - left, y - top, 1.0, 1.0); + ptaaAddPta(ptaa, pta2, L_INSERT); + ptaDestroy(&pta1); + pixDestroy(&pixt1); + pixDestroy(&pixt2); + } + + if (pboxa) + *pboxa = boxa; + else + boxaDestroy(&boxa); + if (ppixa) + *ppixa = pixa; + else + pixaDestroy(&pixa); + return ptaa; +} + + +/*! + * \brief ptaaIndexLabeledPixels() + * + * \param[in] pixs 32 bpp, of indices of c.c. + * \param[out] pncc [optional] number of connected components + * \return ptaa, or NULL on error + * + * <pre> + * Notes: + * (1) The pixel values in %pixs are the index of the connected component + * to which the pixel belongs; %pixs is typically generated from + * a 1 bpp pix by pixConnCompTransform(). Background pixels in + * the generating 1 bpp pix are represented in %pixs by 0. + * We do not check that the pixel values are correctly labelled. + * (2) Each pta in the returned ptaa gives the pixel locations + * corresponding to a connected component, with the label of each + * given by the index of the pta into the ptaa. + * (3) Initialize with the first pta in ptaa being empty and + * representing the background value (index 0) in the pix. + * </pre> + */ +PTAA * +ptaaIndexLabeledPixels(PIX *pixs, + l_int32 *pncc) +{ +l_int32 wpl, index, i, j, w, h; +l_uint32 maxval; +l_uint32 *data, *line; +PTA *pta; +PTAA *ptaa; + + if (pncc) *pncc = 0; + if (!pixs || (pixGetDepth(pixs) != 32)) + return (PTAA *)ERROR_PTR("pixs undef or not 32 bpp", __func__, NULL); + + /* The number of c.c. is the maximum pixel value. Use this to + * initialize ptaa with sufficient pta arrays */ + pixGetMaxValueInRect(pixs, NULL, &maxval, NULL, NULL); + if (pncc) *pncc = maxval; + pta = ptaCreate(1); + ptaa = ptaaCreate(maxval + 1); + ptaaInitFull(ptaa, pta); + ptaDestroy(&pta); + + /* Sweep over %pixs, saving the pixel coordinates of each pixel + * with nonzero value in the appropriate pta, indexed by that value. */ + pixGetDimensions(pixs, &w, &h, NULL); + data = pixGetData(pixs); + wpl = pixGetWpl(pixs); + for (i = 0; i < h; i++) { + line = data + wpl * i; + for (j = 0; j < w; j++) { + index = line[j]; + if (index > 0) + ptaaAddPt(ptaa, index, j, i); + } + } + + return ptaa; +} + + +/*! + * \brief ptaGetNeighborPixLocs() + * + * \param[in] pixs any depth + * \param[in] x, y pixel from which we search for nearest neighbors + * \param[in] conn 4 or 8 connectivity + * \return pta, or NULL on error + * + * <pre> + * Notes: + * (1) Generates a pta of all valid neighbor pixel locations, + * or NULL on error. + * </pre> + */ +PTA * +ptaGetNeighborPixLocs(PIX *pixs, + l_int32 x, + l_int32 y, + l_int32 conn) +{ +l_int32 w, h; +PTA *pta; + + if (!pixs) + return (PTA *)ERROR_PTR("pixs not defined", __func__, NULL); + pixGetDimensions(pixs, &w, &h, NULL); + if (x < 0 || x >= w || y < 0 || y >= h) + return (PTA *)ERROR_PTR("(x,y) not in pixs", __func__, NULL); + if (conn != 4 && conn != 8) + return (PTA *)ERROR_PTR("conn not 4 or 8", __func__, NULL); + + pta = ptaCreate(conn); + if (x > 0) + ptaAddPt(pta, x - 1, y); + if (x < w - 1) + ptaAddPt(pta, x + 1, y); + if (y > 0) + ptaAddPt(pta, x, y - 1); + if (y < h - 1) + ptaAddPt(pta, x, y + 1); + if (conn == 8) { + if (x > 0) { + if (y > 0) + ptaAddPt(pta, x - 1, y - 1); + if (y < h - 1) + ptaAddPt(pta, x - 1, y + 1); + } + if (x < w - 1) { + if (y > 0) + ptaAddPt(pta, x + 1, y - 1); + if (y < h - 1) + ptaAddPt(pta, x + 1, y + 1); + } + } + + return pta; +} + + +/*---------------------------------------------------------------------* + * Interconversion with Numa * + *---------------------------------------------------------------------*/ +/*! + * \brief numaConvertToPta1() + * + * \param[in] na numa with implicit y(x) + * \return pta if OK; null on error + */ +PTA * +numaConvertToPta1(NUMA *na) +{ +l_int32 i, n; +l_float32 startx, delx, val; +PTA *pta; + + if (!na) + return (PTA *)ERROR_PTR("na not defined", __func__, NULL); + + n = numaGetCount(na); + pta = ptaCreate(n); + numaGetParameters(na, &startx, &delx); + for (i = 0; i < n; i++) { + numaGetFValue(na, i, &val); + ptaAddPt(pta, startx + i * delx, val); + } + return pta; +} + + +/*! + * \brief numaConvertToPta2() + * + * \param[in] nax + * \param[in] nay + * \return pta if OK; null on error + */ +PTA * +numaConvertToPta2(NUMA *nax, + NUMA *nay) +{ +l_int32 i, n, nx, ny; +l_float32 valx, valy; +PTA *pta; + + if (!nax || !nay) + return (PTA *)ERROR_PTR("nax and nay not both defined", __func__, NULL); + + nx = numaGetCount(nax); + ny = numaGetCount(nay); + n = L_MIN(nx, ny); + if (nx != ny) + L_WARNING("nx = %d does not equal ny = %d\n", __func__, nx, ny); + pta = ptaCreate(n); + for (i = 0; i < n; i++) { + numaGetFValue(nax, i, &valx); + numaGetFValue(nay, i, &valy); + ptaAddPt(pta, valx, valy); + } + return pta; +} + + +/*! + * \brief ptaConvertToNuma() + * + * \param[in] pta + * \param[out] pnax addr of nax + * \param[out] pnay addr of nay + * \return 0 if OK, 1 on error + */ +l_ok +ptaConvertToNuma(PTA *pta, + NUMA **pnax, + NUMA **pnay) +{ +l_int32 i, n; +l_float32 valx, valy; + + if (pnax) *pnax = NULL; + if (pnay) *pnay = NULL; + if (!pnax || !pnay) + return ERROR_INT("&nax and &nay not both defined", __func__, 1); + if (!pta) + return ERROR_INT("pta not defined", __func__, 1); + + n = ptaGetCount(pta); + *pnax = numaCreate(n); + *pnay = numaCreate(n); + for (i = 0; i < n; i++) { + ptaGetPt(pta, i, &valx, &valy); + numaAddNumber(*pnax, valx); + numaAddNumber(*pnay, valy); + } + return 0; +} + + +/*---------------------------------------------------------------------* + * Display Pta and Ptaa * + *---------------------------------------------------------------------*/ +/*! + * \brief pixDisplayPta() + * + * \param[in] pixd can be same as pixs or NULL; 32 bpp if in-place + * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp + * \param[in] pta of path to be plotted + * \return pixd 32 bpp RGB version of pixs, with path in green. + * + * <pre> + * Notes: + * (1) To write on an existing pixs, pixs must be 32 bpp and + * call with pixd == pixs: + * pixDisplayPta(pixs, pixs, pta); + * To write to a new pix, use pixd == NULL and call: + * pixd = pixDisplayPta(NULL, pixs, pta); + * (2) On error, returns pixd to avoid losing pixs if called as + * pixs = pixDisplayPta(pixs, pixs, pta); + * </pre> + */ +PIX * +pixDisplayPta(PIX *pixd, + PIX *pixs, + PTA *pta) +{ +l_int32 i, n, w, h, x, y; +l_uint32 rpixel, gpixel, bpixel; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd); + if (!pta) + return (PIX *)ERROR_PTR("pta not defined", __func__, pixd); + if (pixd && (pixd != pixs || pixGetDepth(pixd) != 32)) + return (PIX *)ERROR_PTR("invalid pixd", __func__, pixd); + + if (!pixd) + pixd = pixConvertTo32(pixs); + pixGetDimensions(pixd, &w, &h, NULL); + composeRGBPixel(255, 0, 0, &rpixel); /* start point */ + composeRGBPixel(0, 255, 0, &gpixel); + composeRGBPixel(0, 0, 255, &bpixel); /* end point */ + + n = ptaGetCount(pta); + for (i = 0; i < n; i++) { + ptaGetIPt(pta, i, &x, &y); + if (x < 0 || x >= w || y < 0 || y >= h) + continue; + if (i == 0) + pixSetPixel(pixd, x, y, rpixel); + else if (i < n - 1) + pixSetPixel(pixd, x, y, gpixel); + else + pixSetPixel(pixd, x, y, bpixel); + } + + return pixd; +} + + +/*! + * \brief pixDisplayPtaaPattern() + * + * \param[in] pixd 32 bpp + * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp; 32 bpp if in place + * \param[in] ptaa giving locations at which the pattern is displayed + * \param[in] pixp 1 bpp pattern to be placed such that its reference + * point co-locates with each point in pta + * \param[in] cx, cy reference point in pattern + * \return pixd 32 bpp RGB version of pixs. + * + * <pre> + * Notes: + * (1) To write on an existing pixs, pixs must be 32 bpp and + * call with pixd == pixs: + * pixDisplayPtaPattern(pixs, pixs, pta, ...); + * To write to a new pix, use pixd == NULL and call: + * pixd = pixDisplayPtaPattern(NULL, pixs, pta, ...); + * (2) Puts a random color on each pattern associated with a pta. + * (3) On error, returns pixd to avoid losing pixs if called as + * pixs = pixDisplayPtaPattern(pixs, pixs, pta, ...); + * (4) A typical pattern to be used is a circle, generated with + * generatePtaFilledCircle() + * </pre> + */ +PIX * +pixDisplayPtaaPattern(PIX *pixd, + PIX *pixs, + PTAA *ptaa, + PIX *pixp, + l_int32 cx, + l_int32 cy) +{ +l_int32 i, n; +l_uint32 color; +PIXCMAP *cmap; +PTA *pta; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd); + if (!ptaa) + return (PIX *)ERROR_PTR("ptaa not defined", __func__, pixd); + if (pixd && (pixd != pixs || pixGetDepth(pixd) != 32)) + return (PIX *)ERROR_PTR("invalid pixd", __func__, pixd); + if (!pixp) + return (PIX *)ERROR_PTR("pixp not defined", __func__, pixd); + + if (!pixd) + pixd = pixConvertTo32(pixs); + + /* Use 256 random colors */ + cmap = pixcmapCreateRandom(8, 0, 0); + n = ptaaGetCount(ptaa); + for (i = 0; i < n; i++) { + pixcmapGetColor32(cmap, i % 256, &color); + pta = ptaaGetPta(ptaa, i, L_CLONE); + pixDisplayPtaPattern(pixd, pixd, pta, pixp, cx, cy, color); + ptaDestroy(&pta); + } + + pixcmapDestroy(&cmap); + return pixd; +} + + +/*! + * \brief pixDisplayPtaPattern() + * + * \param[in] pixd can be same as pixs or NULL; 32 bpp if in-place + * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp + * \param[in] pta giving locations at which the pattern is displayed + * \param[in] pixp 1 bpp pattern to be placed such that its reference + * point co-locates with each point in pta + * \param[in] cx, cy reference point in pattern + * \param[in] color in 0xrrggbb00 format + * \return pixd 32 bpp RGB version of pixs. + * + * <pre> + * Notes: + * (1) To write on an existing pixs, pixs must be 32 bpp and + * call with pixd == pixs: + * pixDisplayPtaPattern(pixs, pixs, pta, ...); + * To write to a new pix, use pixd == NULL and call: + * pixd = pixDisplayPtaPattern(NULL, pixs, pta, ...); + * (2) On error, returns pixd to avoid losing pixs if called as + * pixs = pixDisplayPtaPattern(pixs, pixs, pta, ...); + * (3) A typical pattern to be used is a circle, generated with + * generatePtaFilledCircle() + * </pre> + */ +PIX * +pixDisplayPtaPattern(PIX *pixd, + PIX *pixs, + PTA *pta, + PIX *pixp, + l_int32 cx, + l_int32 cy, + l_uint32 color) +{ +l_int32 i, n, w, h, x, y; +PTA *ptat; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd); + if (!pta) + return (PIX *)ERROR_PTR("pta not defined", __func__, pixd); + if (pixd && (pixd != pixs || pixGetDepth(pixd) != 32)) + return (PIX *)ERROR_PTR("invalid pixd", __func__, pixd); + if (!pixp) + return (PIX *)ERROR_PTR("pixp not defined", __func__, pixd); + + if (!pixd) + pixd = pixConvertTo32(pixs); + pixGetDimensions(pixs, &w, &h, NULL); + ptat = ptaReplicatePattern(pta, pixp, NULL, cx, cy, w, h); + + n = ptaGetCount(ptat); + for (i = 0; i < n; i++) { + ptaGetIPt(ptat, i, &x, &y); + if (x < 0 || x >= w || y < 0 || y >= h) + continue; + pixSetPixel(pixd, x, y, color); + } + + ptaDestroy(&ptat); + return pixd; +} + + +/*! + * \brief ptaReplicatePattern() + * + * \param[in] ptas "sparse" input pta + * \param[in] pixp [optional] 1 bpp pattern, to be replicated + * in output pta + * \param[in] ptap [optional] set of pts, to be replicated in output pta + * \param[in] cx, cy reference point in pattern + * \param[in] w, h clipping sizes for output pta + * \return ptad with all points of replicated pattern, or NULL on error + * + * <pre> + * Notes: + * (1) You can use either the image %pixp or the set of pts %ptap. + * (2) The pattern is placed with its reference point at each point + * in ptas, and all the fg pixels are colleced into ptad. + * For %pixp, this is equivalent to blitting pixp at each point + * in ptas, and then converting the resulting pix to a pta. + * </pre> + */ +PTA * +ptaReplicatePattern(PTA *ptas, + PIX *pixp, + PTA *ptap, + l_int32 cx, + l_int32 cy, + l_int32 w, + l_int32 h) +{ +l_int32 i, j, n, np, x, y, xp, yp, xf, yf; +PTA *ptat, *ptad; + + if (!ptas) + return (PTA *)ERROR_PTR("ptas not defined", __func__, NULL); + if (!pixp && !ptap) + return (PTA *)ERROR_PTR("no pattern is defined", __func__, NULL); + if (pixp && ptap) + L_WARNING("pixp and ptap defined; using ptap\n", __func__); + + n = ptaGetCount(ptas); + ptad = ptaCreate(n); + if (ptap) + ptat = ptaClone(ptap); + else + ptat = ptaGetPixelsFromPix(pixp, NULL); + np = ptaGetCount(ptat); + for (i = 0; i < n; i++) { + ptaGetIPt(ptas, i, &x, &y); + for (j = 0; j < np; j++) { + ptaGetIPt(ptat, j, &xp, &yp); + xf = x - cx + xp; + yf = y - cy + yp; + if (xf >= 0 && xf < w && yf >= 0 && yf < h) + ptaAddPt(ptad, xf, yf); + } + } + + ptaDestroy(&ptat); + return ptad; +} + + +/*! + * \brief pixDisplayPtaa() + * + * \param[in] pixs 1, 2, 4, 8, 16 or 32 bpp + * \param[in] ptaa array of paths to be plotted + * \return pixd 32 bpp RGB version of pixs, with paths plotted + * in different colors, or NULL on error + */ +PIX * +pixDisplayPtaa(PIX *pixs, + PTAA *ptaa) +{ +l_int32 i, j, w, h, npta, npt, x, y, rv, gv, bv; +l_uint32 *pixela; +NUMA *na1, *na2, *na3; +PIX *pixd; +PTA *pta; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!ptaa) + return (PIX *)ERROR_PTR("ptaa not defined", __func__, NULL); + npta = ptaaGetCount(ptaa); + if (npta == 0) + return (PIX *)ERROR_PTR("no pta", __func__, NULL); + + if ((pixd = pixConvertTo32(pixs)) == NULL) + return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); + pixGetDimensions(pixd, &w, &h, NULL); + + /* Make a colormap for the paths */ + if ((pixela = (l_uint32 *)LEPT_CALLOC(npta, sizeof(l_uint32))) == NULL) { + pixDestroy(&pixd); + return (PIX *)ERROR_PTR("calloc fail for pixela", __func__, NULL); + } + na1 = numaPseudorandomSequence(256, 14657); + na2 = numaPseudorandomSequence(256, 34631); + na3 = numaPseudorandomSequence(256, 54617); + for (i = 0; i < npta; i++) { + numaGetIValue(na1, i % 256, &rv); + numaGetIValue(na2, i % 256, &gv); + numaGetIValue(na3, i % 256, &bv); + composeRGBPixel(rv, gv, bv, &pixela[i]); + } + numaDestroy(&na1); + numaDestroy(&na2); + numaDestroy(&na3); + + for (i = 0; i < npta; i++) { + pta = ptaaGetPta(ptaa, i, L_CLONE); + npt = ptaGetCount(pta); + for (j = 0; j < npt; j++) { + ptaGetIPt(pta, j, &x, &y); + if (x < 0 || x >= w || y < 0 || y >= h) + continue; + pixSetPixel(pixd, x, y, pixela[i]); + } + ptaDestroy(&pta); + } + + LEPT_FREE(pixela); + return pixd; +}
