diff options
Diffstat (limited to 'leptonica/src/numafunc1.c')
-rw-r--r-- | leptonica/src/numafunc1.c | 3697 |
1 files changed, 3697 insertions, 0 deletions
diff --git a/leptonica/src/numafunc1.c b/leptonica/src/numafunc1.c new file mode 100644 index 00000000..acff5559 --- /dev/null +++ b/leptonica/src/numafunc1.c @@ -0,0 +1,3697 @@ +/*====================================================================* + - 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 numafunc1.c + * <pre> + * + * -------------------------------------- + * This file has these Numa utilities: + * - arithmetic operations + * - simple data analysis + * - generation of special sequences + * - permutations + * - interpolation + * - sorting + * - data analysis requiring sorting + * - joins and rearrangements + * -------------------------------------- + * + * Arithmetic and logic + * NUMA *numaArithOp() + * NUMA *numaLogicalOp() + * NUMA *numaInvert() + * l_int32 numaSimilar() + * l_int32 numaAddToNumber() + * + * Simple extractions + * l_int32 numaGetMin() + * l_int32 numaGetMax() + * l_int32 numaGetSum() + * NUMA *numaGetPartialSums() + * l_int32 numaGetSumOnInterval() + * l_int32 numaHasOnlyIntegers() + * NUMA *numaSubsample() + * NUMA *numaMakeDelta() + * NUMA *numaMakeSequence() + * NUMA *numaMakeConstant() + * NUMA *numaMakeAbsValue() + * NUMA *numaAddBorder() + * NUMA *numaAddSpecifiedBorder() + * NUMA *numaRemoveBorder() + * l_int32 numaCountNonzeroRuns() + * l_int32 numaGetNonzeroRange() + * l_int32 numaGetCountRelativeToZero() + * NUMA *numaClipToInterval() + * NUMA *numaMakeThresholdIndicator() + * NUMA *numaUniformSampling() + * NUMA *numaReverse() + * + * Signal feature extraction + * NUMA *numaLowPassIntervals() + * NUMA *numaThresholdEdges() + * NUMA *numaGetSpanValues() + * NUMA *numaGetEdgeValues() + * + * Interpolation + * l_int32 numaInterpolateEqxVal() + * l_int32 numaInterpolateEqxInterval() + * l_int32 numaInterpolateArbxVal() + * l_int32 numaInterpolateArbxInterval() + * + * Functions requiring interpolation + * l_int32 numaFitMax() + * l_int32 numaDifferentiateInterval() + * l_int32 numaIntegrateInterval() + * + * Sorting + * NUMA *numaSortGeneral() + * NUMA *numaSortAutoSelect() + * NUMA *numaSortIndexAutoSelect() + * l_int32 numaChooseSortType() + * NUMA *numaSort() + * NUMA *numaBinSort() + * NUMA *numaGetSortIndex() + * NUMA *numaGetBinSortIndex() + * NUMA *numaSortByIndex() + * l_int32 numaIsSorted() + * l_int32 numaSortPair() + * NUMA *numaInvertMap() + * l_int32 numaAddSorted() + * l_int32 numaFindSortedLoc() + * + * Random permutation + * NUMA *numaPseudorandomSequence() + * NUMA *numaRandomPermutation() + * + * Functions requiring sorting + * l_int32 numaGetRankValue() + * l_int32 numaGetMedian() + * l_int32 numaGetBinnedMedian() + * l_int32 numaGetMeanDevFromMedian() + * l_int32 numaGetMedianDevFromMedian() + * l_int32 numaGetMode() + * + * Rearrangements + * l_int32 numaJoin() + * l_int32 numaaJoin() + * NUMA *numaaFlattenToNuma() + * + * Things to remember when using the Numa: + * + * (1) The numa is a struct, not an array. Always use accessors + * (see numabasic.c), never the fields directly. + * + * (2) The number array holds l_float32 values. It can also + * be used to store l_int32 values. See numabasic.c for + * details on using the accessors. + * + * (3) If you use numaCreate(), no numbers are stored and the size is 0. + * You have to add numbers to increase the size. + * If you want to start with a numa of a fixed size, with each + * entry initialized to the same value, use numaMakeConstant(). + * + * (4) Occasionally, in the comments we denote the i-th element of a + * numa by na[i]. This is conceptual only -- the numa is not an array! + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <math.h> +#include "allheaders.h" + +/*----------------------------------------------------------------------* + * Arithmetic and logical ops on Numas * + *----------------------------------------------------------------------*/ +/*! + * \brief numaArithOp() + * + * \param[in] nad [optional] can be null or equal to na1 (in-place + * \param[in] na1 + * \param[in] na2 + * \param[in] op L_ARITH_ADD, L_ARITH_SUBTRACT, + * L_ARITH_MULTIPLY, L_ARITH_DIVIDE + * \return nad always: operation applied to na1 and na2 + * + * <pre> + * Notes: + * (1) The sizes of na1 and na2 must be equal. + * (2) nad can only null or equal to na1. + * (3) To add a constant to a numa, or to multipy a numa by + * a constant, use numaTransform(). + * </pre> + */ +NUMA * +numaArithOp(NUMA *nad, + NUMA *na1, + NUMA *na2, + l_int32 op) +{ +l_int32 i, n; +l_float32 val1, val2; + + PROCNAME("numaArithOp"); + + if (!na1 || !na2) + return (NUMA *)ERROR_PTR("na1, na2 not both defined", procName, nad); + n = numaGetCount(na1); + if (n != numaGetCount(na2)) + return (NUMA *)ERROR_PTR("na1, na2 sizes differ", procName, nad); + if (nad && nad != na1) + return (NUMA *)ERROR_PTR("nad defined but not in-place", procName, nad); + if (op != L_ARITH_ADD && op != L_ARITH_SUBTRACT && + op != L_ARITH_MULTIPLY && op != L_ARITH_DIVIDE) + return (NUMA *)ERROR_PTR("invalid op", procName, nad); + if (op == L_ARITH_DIVIDE) { + for (i = 0; i < n; i++) { + numaGetFValue(na2, i, &val2); + if (val2 == 0.0) + return (NUMA *)ERROR_PTR("na2 has 0 element", procName, nad); + } + } + + /* If nad is not identical to na1, make it an identical copy */ + if (!nad) + nad = numaCopy(na1); + + for (i = 0; i < n; i++) { + numaGetFValue(nad, i, &val1); + numaGetFValue(na2, i, &val2); + switch (op) { + case L_ARITH_ADD: + numaSetValue(nad, i, val1 + val2); + break; + case L_ARITH_SUBTRACT: + numaSetValue(nad, i, val1 - val2); + break; + case L_ARITH_MULTIPLY: + numaSetValue(nad, i, val1 * val2); + break; + case L_ARITH_DIVIDE: + numaSetValue(nad, i, val1 / val2); + break; + default: + lept_stderr(" Unknown arith op: %d\n", op); + return nad; + } + } + + return nad; +} + + +/*! + * \brief numaLogicalOp() + * + * \param[in] nad [optional] can be null or equal to na1 (in-place + * \param[in] na1 + * \param[in] na2 + * \param[in] op L_UNION, L_INTERSECTION, L_SUBTRACTION, L_EXCLUSIVE_OR + * \return nad always: operation applied to na1 and na2 + * + * <pre> + * Notes: + * (1) The sizes of na1 and na2 must be equal. + * (2) nad can only be null or equal to na1. + * (3) This is intended for use with indicator arrays (0s and 1s). + * Input data is extracted as integers (0 == false, anything + * else == true); output results are 0 and 1. + * (4) L_SUBTRACTION is subtraction of val2 from val1. For bit logical + * arithmetic this is (val1 & ~val2), but because these values + * are integers, we use (val1 && !val2). + * </pre> + */ +NUMA * +numaLogicalOp(NUMA *nad, + NUMA *na1, + NUMA *na2, + l_int32 op) +{ +l_int32 i, n, val1, val2, val; + + PROCNAME("numaLogicalOp"); + + if (!na1 || !na2) + return (NUMA *)ERROR_PTR("na1, na2 not both defined", procName, nad); + n = numaGetCount(na1); + if (n != numaGetCount(na2)) + return (NUMA *)ERROR_PTR("na1, na2 sizes differ", procName, nad); + if (nad && nad != na1) + return (NUMA *)ERROR_PTR("nad defined; not in-place", procName, nad); + if (op != L_UNION && op != L_INTERSECTION && + op != L_SUBTRACTION && op != L_EXCLUSIVE_OR) + return (NUMA *)ERROR_PTR("invalid op", procName, nad); + + /* If nad is not identical to na1, make it an identical copy */ + if (!nad) + nad = numaCopy(na1); + + for (i = 0; i < n; i++) { + numaGetIValue(nad, i, &val1); + numaGetIValue(na2, i, &val2); + val1 = (val1 == 0) ? 0 : 1; + val2 = (val2 == 0) ? 0 : 1; + switch (op) { + case L_UNION: + val = (val1 || val2) ? 1 : 0; + numaSetValue(nad, i, val); + break; + case L_INTERSECTION: + val = (val1 && val2) ? 1 : 0; + numaSetValue(nad, i, val); + break; + case L_SUBTRACTION: + val = (val1 && !val2) ? 1 : 0; + numaSetValue(nad, i, val); + break; + case L_EXCLUSIVE_OR: + val = (val1 != val2) ? 1 : 0; + numaSetValue(nad, i, val); + break; + default: + lept_stderr(" Unknown logical op: %d\n", op); + return nad; + } + } + + return nad; +} + + +/*! + * \brief numaInvert() + * + * \param[in] nad [optional] can be null or equal to nas (in-place + * \param[in] nas + * \return nad always: 'inverts' nas + * + * <pre> + * Notes: + * (1) This is intended for use with indicator arrays (0s and 1s). + * It gives a boolean-type output, taking the input as + * an integer and inverting it: + * 0 --> 1 + * anything else --> 0 + * </pre> + */ +NUMA * +numaInvert(NUMA *nad, + NUMA *nas) +{ +l_int32 i, n, val; + + PROCNAME("numaInvert"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, nad); + if (nad && nad != nas) + return (NUMA *)ERROR_PTR("nad defined; not in-place", procName, nad); + + if (!nad) + nad = numaCopy(nas); + n = numaGetCount(nad); + for (i = 0; i < n; i++) { + numaGetIValue(nad, i, &val); + if (!val) + val = 1; + else + val = 0; + numaSetValue(nad, i, val); + } + return nad; +} + + +/*! + * \brief numaSimilar() + * + * \param[in] na1 + * \param[in] na2 + * \param[in] maxdiff use 0.0 for exact equality + * \param[out] psimilar 1 if similar; 0 if different + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Float values can differ slightly due to roundoff and + * accumulated errors. Using %maxdiff > 0.0 allows similar + * arrays to be identified. + * </pre> +*/ +l_int32 +numaSimilar(NUMA *na1, + NUMA *na2, + l_float32 maxdiff, + l_int32 *psimilar) +{ +l_int32 i, n; +l_float32 val1, val2; + + PROCNAME("numaSimilar"); + + if (!psimilar) + return ERROR_INT("&similar not defined", procName, 1); + *psimilar = 0; + if (!na1 || !na2) + return ERROR_INT("na1 and na2 not both defined", procName, 1); + maxdiff = L_ABS(maxdiff); + + n = numaGetCount(na1); + if (n != numaGetCount(na2)) return 0; + + for (i = 0; i < n; i++) { + numaGetFValue(na1, i, &val1); + numaGetFValue(na2, i, &val2); + if (L_ABS(val1 - val2) > maxdiff) return 0; + } + + *psimilar = 1; + return 0; +} + + +/*! + * \brief numaAddToNumber() + * + * \param[in] na source numa + * \param[in] index element to be changed + * \param[in] val new value to be added + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This is useful for accumulating sums, regardless of the index + * order in which the values are made available. + * (2) Before use, the numa has to be filled up to %index. This would + * typically be used by creating the numa with the full sized + * array, initialized to 0.0, using numaMakeConstant(). + * </pre> + */ +l_ok +numaAddToNumber(NUMA *na, + l_int32 index, + l_float32 val) +{ +l_int32 n; + + PROCNAME("numaAddToNumber"); + + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + if (index < 0 || index >= n) { + L_ERROR("index %d not in [0,...,%d]\n", procName, index, n - 1); + return 1; + } + + na->array[index] += val; + return 0; +} + + +/*----------------------------------------------------------------------* + * Simple extractions * + *----------------------------------------------------------------------*/ +/*! + * \brief numaGetMin() + * + * \param[in] na source numa + * \param[out] pminval [optional] min value + * \param[out] piminloc [optional] index of min location + * \return 0 if OK; 1 on error + */ +l_ok +numaGetMin(NUMA *na, + l_float32 *pminval, + l_int32 *piminloc) +{ +l_int32 i, n, iminloc; +l_float32 val, minval; + + PROCNAME("numaGetMin"); + + if (!pminval && !piminloc) + return ERROR_INT("nothing to do", procName, 1); + if (pminval) *pminval = 0.0; + if (piminloc) *piminloc = 0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + + minval = +1000000000.; + iminloc = 0; + for (i = 0; i < n; i++) { + numaGetFValue(na, i, &val); + if (val < minval) { + minval = val; + iminloc = i; + } + } + + if (pminval) *pminval = minval; + if (piminloc) *piminloc = iminloc; + return 0; +} + + +/*! + * \brief numaGetMax() + * + * \param[in] na source numa + * \param[out] pmaxval [optional] max value + * \param[out] pimaxloc [optional] index of max location + * \return 0 if OK; 1 on error + */ +l_ok +numaGetMax(NUMA *na, + l_float32 *pmaxval, + l_int32 *pimaxloc) +{ +l_int32 i, n, imaxloc; +l_float32 val, maxval; + + PROCNAME("numaGetMax"); + + if (!pmaxval && !pimaxloc) + return ERROR_INT("nothing to do", procName, 1); + if (pmaxval) *pmaxval = 0.0; + if (pimaxloc) *pimaxloc = 0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + + maxval = -1000000000.; + imaxloc = 0; + for (i = 0; i < n; i++) { + numaGetFValue(na, i, &val); + if (val > maxval) { + maxval = val; + imaxloc = i; + } + } + + if (pmaxval) *pmaxval = maxval; + if (pimaxloc) *pimaxloc = imaxloc; + return 0; +} + + +/*! + * \brief numaGetSum() + * + * \param[in] na source numa + * \param[out] psum sum of values + * \return 0 if OK, 1 on error + */ +l_ok +numaGetSum(NUMA *na, + l_float32 *psum) +{ +l_int32 i, n; +l_float32 val, sum; + + PROCNAME("numaGetSum"); + + if (!psum) + return ERROR_INT("&sum not defined", procName, 1); + *psum = 0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + sum = 0.0; + for (i = 0; i < n; i++) { + numaGetFValue(na, i, &val); + sum += val; + } + *psum = sum; + return 0; +} + + +/*! + * \brief numaGetPartialSums() + * + * \param[in] na source numa + * \return nasum, or NULL on error + * + * <pre> + * Notes: + * (1) nasum[i] is the sum for all j <= i of na[j]. + * So nasum[0] = na[0]. + * (2) If you want to generate a rank function, where rank[0] - 0.0, + * insert a 0.0 at the beginning of the nasum array. + * </pre> + */ +NUMA * +numaGetPartialSums(NUMA *na) +{ +l_int32 i, n; +l_float32 val, sum; +NUMA *nasum; + + PROCNAME("numaGetPartialSums"); + + if (!na) + return (NUMA *)ERROR_PTR("na not defined", procName, NULL); + + if ((n = numaGetCount(na)) == 0) + L_WARNING("na is empty\n", procName); + nasum = numaCreate(n); + sum = 0.0; + for (i = 0; i < n; i++) { + numaGetFValue(na, i, &val); + sum += val; + numaAddNumber(nasum, sum); + } + return nasum; +} + + +/*! + * \brief numaGetSumOnInterval() + * + * \param[in] na source numa + * \param[in] first beginning index + * \param[in] last final index; use -1 to go to the end + * \param[out] psum sum of values in the index interval range + * \return 0 if OK, 1 on error + */ +l_ok +numaGetSumOnInterval(NUMA *na, + l_int32 first, + l_int32 last, + l_float32 *psum) +{ +l_int32 i, n; +l_float32 val, sum; + + PROCNAME("numaGetSumOnInterval"); + + if (!psum) + return ERROR_INT("&sum not defined", procName, 1); + *psum = 0.0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + + sum = 0.0; + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + if (first < 0) first = 0; + if (first >= n || last < -1) /* not an error */ + return 0; + if (last == -1) + last = n - 1; + last = L_MIN(last, n - 1); + + for (i = first; i <= last; i++) { + numaGetFValue(na, i, &val); + sum += val; + } + *psum = sum; + return 0; +} + + +/*! + * \brief numaHasOnlyIntegers() + * + * \param[in] na source numa + * \param[out] pallints 1 if all sampled values are ints; else 0 + * \return 0 if OK, 1 on error + */ +l_ok +numaHasOnlyIntegers(NUMA *na, + l_int32 *pallints) +{ +l_int32 i, n; +l_float32 val; + + PROCNAME("numaHasOnlyIntegers"); + + if (!pallints) + return ERROR_INT("&allints not defined", procName, 1); + *pallints = TRUE; + if (!na) + return ERROR_INT("na not defined", procName, 1); + + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + for (i = 0; i < n; i ++) { + numaGetFValue(na, i, &val); + if (val != (l_int32)val) { + *pallints = FALSE; + return 0; + } + } + return 0; +} + + +/*! + * \brief numaSubsample() + * + * \param[in] nas + * \param[in] subfactor subsample factor, >= 1 + * \return nad evenly sampled values from nas, or NULL on error + */ +NUMA * +numaSubsample(NUMA *nas, + l_int32 subfactor) +{ +l_int32 i, n; +l_float32 val; +NUMA *nad; + + PROCNAME("numaSubsample"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (subfactor < 1) + return (NUMA *)ERROR_PTR("subfactor < 1", procName, NULL); + + nad = numaCreate(0); + if ((n = numaGetCount(nas)) == 0) + L_WARNING("nas is empty\n", procName); + for (i = 0; i < n; i++) { + if (i % subfactor != 0) continue; + numaGetFValue(nas, i, &val); + numaAddNumber(nad, val); + } + + return nad; +} + + +/*! + * \brief numaMakeDelta() + * + * \param[in] nas input numa + * \return numa of difference values val[i+1] - val[i], + * or NULL on error + */ +NUMA * +numaMakeDelta(NUMA *nas) +{ +l_int32 i, n; +l_float32 prev, cur; +NUMA *nad; + + PROCNAME("numaMakeDelta"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if ((n = numaGetCount(nas)) < 2) { + L_WARNING("n < 2; returning empty numa\n", procName); + return numaCreate(1); + } + + nad = numaCreate(n - 1); + numaGetFValue(nas, 0, &prev); + for (i = 1; i < n; i++) { + numaGetFValue(nas, i, &cur); + numaAddNumber(nad, cur - prev); + prev = cur; + } + return nad; +} + + +/*! + * \brief numaMakeSequence() + * + * \param[in] startval + * \param[in] increment + * \param[in] size of sequence + * \return numa of sequence of evenly spaced values, or NULL on error + */ +NUMA * +numaMakeSequence(l_float32 startval, + l_float32 increment, + l_int32 size) +{ +l_int32 i; +l_float32 val; +NUMA *na; + + PROCNAME("numaMakeSequence"); + + if ((na = numaCreate(size)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + + for (i = 0; i < size; i++) { + val = startval + i * increment; + numaAddNumber(na, val); + } + return na; +} + + +/*! + * \brief numaMakeConstant() + * + * \param[in] val + * \param[in] size of numa + * \return numa of given size with all entries equal to 'val', + * or NULL on error + */ +NUMA * +numaMakeConstant(l_float32 val, + l_int32 size) +{ + return numaMakeSequence(val, 0.0, size); +} + + +/*! + * \brief numaMakeAbsValue() + * + * \param[in] nad can be null for new array, or the same as nas for inplace + * \param[in] nas input numa + * \return nad with all numbers being the absval of the input, + * or NULL on error + */ +NUMA * +numaMakeAbsValue(NUMA *nad, + NUMA *nas) +{ +l_int32 i, n; +l_float32 val; + + PROCNAME("numaMakeAbsValue"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (nad && nad != nas) + return (NUMA *)ERROR_PTR("nad and not in-place", procName, NULL); + + if (!nad) + nad = numaCopy(nas); + n = numaGetCount(nad); + for (i = 0; i < n; i++) { + val = nad->array[i]; + nad->array[i] = L_ABS(val); + } + + return nad; +} + + +/*! + * \brief numaAddBorder() + * + * \param[in] nas + * \param[in] left number of elements to add before the start + * \param[in] right number of elements to add after the end + * \param[in] val initialize border elements + * \return nad with added elements at left and right, or NULL on error + */ +NUMA * +numaAddBorder(NUMA *nas, + l_int32 left, + l_int32 right, + l_float32 val) +{ +l_int32 i, n, len; +l_float32 startx, delx; +l_float32 *fas, *fad; +NUMA *nad; + + PROCNAME("numaAddBorder"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (left < 0) left = 0; + if (right < 0) right = 0; + if (left == 0 && right == 0) + return numaCopy(nas); + + n = numaGetCount(nas); + len = n + left + right; + nad = numaMakeConstant(val, len); + numaGetParameters(nas, &startx, &delx); + numaSetParameters(nad, startx - delx * left, delx); + fas = numaGetFArray(nas, L_NOCOPY); + fad = numaGetFArray(nad, L_NOCOPY); + for (i = 0; i < n; i++) + fad[left + i] = fas[i]; + + return nad; +} + + +/*! + * \brief numaAddSpecifiedBorder() + * + * \param[in] nas + * \param[in] left number of elements to add before the start + * \param[in] right number of elements to add after the end + * \param[in] type L_CONTINUED_BORDER, L_MIRRORED_BORDER + * \return nad with added elements at left and right, or NULL on error + */ +NUMA * +numaAddSpecifiedBorder(NUMA *nas, + l_int32 left, + l_int32 right, + l_int32 type) +{ +l_int32 i, n; +l_float32 *fa; +NUMA *nad; + + PROCNAME("numaAddSpecifiedBorder"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (left < 0) left = 0; + if (right < 0) right = 0; + if (left == 0 && right == 0) + return numaCopy(nas); + if (type != L_CONTINUED_BORDER && type != L_MIRRORED_BORDER) + return (NUMA *)ERROR_PTR("invalid type", procName, NULL); + n = numaGetCount(nas); + if (type == L_MIRRORED_BORDER && (left > n || right > n)) + return (NUMA *)ERROR_PTR("border too large", procName, NULL); + + nad = numaAddBorder(nas, left, right, 0); + n = numaGetCount(nad); + fa = numaGetFArray(nad, L_NOCOPY); + if (type == L_CONTINUED_BORDER) { + for (i = 0; i < left; i++) + fa[i] = fa[left]; + for (i = n - right; i < n; i++) + fa[i] = fa[n - right - 1]; + } else { /* type == L_MIRRORED_BORDER */ + for (i = 0; i < left; i++) + fa[i] = fa[2 * left - 1 - i]; + for (i = 0; i < right; i++) + fa[n - right + i] = fa[n - right - i - 1]; + } + + return nad; +} + + +/*! + * \brief numaRemoveBorder() + * + * \param[in] nas + * \param[in] left number of elements to remove from the start + * \param[in] right number of elements to remove up to the end + * \return nad with removed elements at left and right, or NULL on error + */ +NUMA * +numaRemoveBorder(NUMA *nas, + l_int32 left, + l_int32 right) +{ +l_int32 i, n, len; +l_float32 startx, delx; +l_float32 *fas, *fad; +NUMA *nad; + + PROCNAME("numaRemoveBorder"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (left < 0) left = 0; + if (right < 0) right = 0; + if (left == 0 && right == 0) + return numaCopy(nas); + + n = numaGetCount(nas); + if ((len = n - left - right) < 0) + return (NUMA *)ERROR_PTR("len < 0 after removal", procName, NULL); + nad = numaMakeConstant(0, len); + numaGetParameters(nas, &startx, &delx); + numaSetParameters(nad, startx + delx * left, delx); + fas = numaGetFArray(nas, L_NOCOPY); + fad = numaGetFArray(nad, L_NOCOPY); + for (i = 0; i < len; i++) + fad[i] = fas[left + i]; + + return nad; +} + + +/*! + * \brief numaCountNonzeroRuns() + * + * \param[in] na e.g., of pixel counts in rows or columns + * \param[out] pcount number of nonzero runs + * \return 0 if OK, 1 on error + */ +l_ok +numaCountNonzeroRuns(NUMA *na, + l_int32 *pcount) +{ +l_int32 n, i, val, count, inrun; + + PROCNAME("numaCountNonzeroRuns"); + + if (!pcount) + return ERROR_INT("&count not defined", procName, 1); + *pcount = 0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + + count = 0; + inrun = FALSE; + for (i = 0; i < n; i++) { + numaGetIValue(na, i, &val); + if (!inrun && val > 0) { + count++; + inrun = TRUE; + } else if (inrun && val == 0) { + inrun = FALSE; + } + } + *pcount = count; + return 0; +} + + +/*! + * \brief numaGetNonzeroRange() + * + * \param[in] na source numa + * \param[in] eps largest value considered to be zero + * \param[out] pfirst, plast interval of array indices + * where values are nonzero + * \return 0 if OK, 1 on error or if no nonzero range is found. + */ +l_ok +numaGetNonzeroRange(NUMA *na, + l_float32 eps, + l_int32 *pfirst, + l_int32 *plast) +{ +l_int32 n, i, found; +l_float32 val; + + PROCNAME("numaGetNonzeroRange"); + + if (pfirst) *pfirst = 0; + if (plast) *plast = 0; + if (!pfirst || !plast) + return ERROR_INT("pfirst and plast not both defined", procName, 1); + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + + found = FALSE; + for (i = 0; i < n; i++) { + numaGetFValue(na, i, &val); + if (val > eps) { + found = TRUE; + break; + } + } + if (!found) { + *pfirst = n - 1; + *plast = 0; + return 1; + } + + *pfirst = i; + for (i = n - 1; i >= 0; i--) { + numaGetFValue(na, i, &val); + if (val > eps) + break; + } + *plast = i; + return 0; +} + + +/*! + * \brief numaGetCountRelativeToZero() + * + * \param[in] na source numa + * \param[in] type L_LESS_THAN_ZERO, L_EQUAL_TO_ZERO, L_GREATER_THAN_ZERO + * \param[out] pcount count of values of given type + * \return 0 if OK, 1 on error + */ +l_ok +numaGetCountRelativeToZero(NUMA *na, + l_int32 type, + l_int32 *pcount) +{ +l_int32 n, i, count; +l_float32 val; + + PROCNAME("numaGetCountRelativeToZero"); + + if (!pcount) + return ERROR_INT("&count not defined", procName, 1); + *pcount = 0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + + for (i = 0, count = 0; i < n; i++) { + numaGetFValue(na, i, &val); + if (type == L_LESS_THAN_ZERO && val < 0.0) + count++; + else if (type == L_EQUAL_TO_ZERO && val == 0.0) + count++; + else if (type == L_GREATER_THAN_ZERO && val > 0.0) + count++; + } + + *pcount = count; + return 0; +} + + +/*! + * \brief numaClipToInterval() + * + * \param[in] nas + * \param[in] first >= 0; <= last + * \param[in] last + * \return numa with the same values as the input, but clipped + * to the specified interval + * + * <pre> + * Notes: + * If you want the indices of the array values to be unchanged, + * use first = 0. + * Usage: + * This is useful to clip a histogram that has a few nonzero + * values to its nonzero range. + * </pre> + */ +NUMA * +numaClipToInterval(NUMA *nas, + l_int32 first, + l_int32 last) +{ +l_int32 n, i; +l_float32 val, startx, delx; +NUMA *nad; + + PROCNAME("numaClipToInterval"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if ((n = numaGetCount(nas)) == 0) + return (NUMA *)ERROR_PTR("nas is empty", procName, NULL); + if (first < 0 || first > last) + return (NUMA *)ERROR_PTR("range not valid", procName, NULL); + if (first >= n) + return (NUMA *)ERROR_PTR("no elements in range", procName, NULL); + + last = L_MIN(last, n - 1); + if ((nad = numaCreate(last - first + 1)) == NULL) + return (NUMA *)ERROR_PTR("nad not made", procName, NULL); + for (i = first; i <= last; i++) { + numaGetFValue(nas, i, &val); + numaAddNumber(nad, val); + } + numaGetParameters(nas, &startx, &delx); + numaSetParameters(nad, startx + first * delx, delx); + return nad; +} + + +/*! + * \brief numaMakeThresholdIndicator() + * + * \param[in] nas input numa + * \param[in] thresh threshold value + * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, + * L_SELECT_IF_LTE, L_SELECT_IF_GTE + * \return nad : indicator array: values are 0 and 1 + * + * <pre> + * Notes: + * (1) For each element in nas, if the constraint given by 'type' + * correctly specifies its relation to thresh, a value of 1 + * is recorded in nad. + * </pre> + */ +NUMA * +numaMakeThresholdIndicator(NUMA *nas, + l_float32 thresh, + l_int32 type) +{ +l_int32 n, i, ival; +l_float32 fval; +NUMA *nai; + + PROCNAME("numaMakeThresholdIndicator"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if ((n = numaGetCount(nas)) == 0) + return (NUMA *)ERROR_PTR("nas is empty", procName, NULL); + + nai = numaCreate(n); + for (i = 0; i < n; i++) { + numaGetFValue(nas, i, &fval); + ival = 0; + switch (type) + { + case L_SELECT_IF_LT: + if (fval < thresh) ival = 1; + break; + case L_SELECT_IF_GT: + if (fval > thresh) ival = 1; + break; + case L_SELECT_IF_LTE: + if (fval <= thresh) ival = 1; + break; + case L_SELECT_IF_GTE: + if (fval >= thresh) ival = 1; + break; + default: + numaDestroy(&nai); + return (NUMA *)ERROR_PTR("invalid type", procName, NULL); + } + numaAddNumber(nai, ival); + } + + return nai; +} + + +/*! + * \brief numaUniformSampling() + * + * \param[in] nas input numa + * \param[in] nsamp number of samples + * \return nad : resampled array, or NULL on error + * + * <pre> + * Notes: + * (1) This resamples the values in the array, using %nsamp + * equal divisions. + * </pre> + */ +NUMA * +numaUniformSampling(NUMA *nas, + l_int32 nsamp) +{ +l_int32 n, i, j, ileft, iright; +l_float32 left, right, binsize, lfract, rfract, sum, startx, delx; +l_float32 *array; +NUMA *nad; + + PROCNAME("numaUniformSampling"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if ((n = numaGetCount(nas)) == 0) + return (NUMA *)ERROR_PTR("nas is empty", procName, NULL); + if (nsamp <= 0) + return (NUMA *)ERROR_PTR("nsamp must be > 0", procName, NULL); + + nad = numaCreate(nsamp); + array = numaGetFArray(nas, L_NOCOPY); + binsize = (l_float32)n / (l_float32)nsamp; + numaGetParameters(nas, &startx, &delx); + numaSetParameters(nad, startx, binsize * delx); + left = 0.0; + for (i = 0; i < nsamp; i++) { + sum = 0.0; + right = left + binsize; + ileft = (l_int32)left; + lfract = 1.0 - left + ileft; + if (lfract >= 1.0) /* on left bin boundary */ + lfract = 0.0; + iright = (l_int32)right; + rfract = right - iright; + iright = L_MIN(iright, n - 1); + if (ileft == iright) { /* both are within the same original sample */ + sum += (lfract + rfract - 1.0) * array[ileft]; + } else { + if (lfract > 0.0001) /* left fraction */ + sum += lfract * array[ileft]; + if (rfract > 0.0001) /* right fraction */ + sum += rfract * array[iright]; + for (j = ileft + 1; j < iright; j++) /* entire pixels */ + sum += array[j]; + } + + numaAddNumber(nad, sum); + left = right; + } + return nad; +} + + +/*! + * \brief numaReverse() + * + * \param[in] nad [optional] can be null or equal to nas + * \param[in] nas input numa + * \return nad : reversed, or NULL on error + * + * <pre> + * Notes: + * (1) Usage: + * numaReverse(nas, nas); // in-place + * nad = numaReverse(NULL, nas); // makes a new one + * </pre> + */ +NUMA * +numaReverse(NUMA *nad, + NUMA *nas) +{ +l_int32 n, i; +l_float32 val1, val2; + + PROCNAME("numaReverse"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (nad && nas != nad) + return (NUMA *)ERROR_PTR("nad defined but != nas", procName, NULL); + + n = numaGetCount(nas); + if (nad) { /* in-place */ + for (i = 0; i < n / 2; i++) { + numaGetFValue(nad, i, &val1); + numaGetFValue(nad, n - i - 1, &val2); + numaSetValue(nad, i, val2); + numaSetValue(nad, n - i - 1, val1); + } + } else { + nad = numaCreate(n); + for (i = n - 1; i >= 0; i--) { + numaGetFValue(nas, i, &val1); + numaAddNumber(nad, val1); + } + } + + /* Reverse the startx and delx fields */ + nad->startx = nas->startx + (n - 1) * nas->delx; + nad->delx = -nas->delx; + return nad; +} + + +/*----------------------------------------------------------------------* + * Signal feature extraction * + *----------------------------------------------------------------------*/ +/*! + * \brief numaLowPassIntervals() + * + * \param[in] nas input numa + * \param[in] thresh threshold fraction of max; in [0.0 ... 1.0] + * \param[in] maxn for normalizing; set maxn = 0.0 to use the max in nas + * \return nad : interval abscissa pairs, or NULL on error + * + * <pre> + * Notes: + * (1) For each interval where the value is less than a specified + * fraction of the maximum, this records the left and right "x" + * value. + * </pre> + */ +NUMA * +numaLowPassIntervals(NUMA *nas, + l_float32 thresh, + l_float32 maxn) +{ +l_int32 n, i, inrun; +l_float32 maxval, threshval, fval, startx, delx, x0, x1; +NUMA *nad; + + PROCNAME("numaLowPassIntervals"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if ((n = numaGetCount(nas)) == 0) + return (NUMA *)ERROR_PTR("nas is empty", procName, NULL); + if (thresh < 0.0 || thresh > 1.0) + return (NUMA *)ERROR_PTR("invalid thresh", procName, NULL); + + /* The input threshold is a fraction of the max. + * The first entry in nad is the value of the max. */ + if (maxn == 0.0) + numaGetMax(nas, &maxval, NULL); + else + maxval = maxn; + numaGetParameters(nas, &startx, &delx); + threshval = thresh * maxval; + nad = numaCreate(0); + numaAddNumber(nad, maxval); + + /* Write pairs of pts (x0, x1) for the intervals */ + inrun = FALSE; + for (i = 0; i < n; i++) { + numaGetFValue(nas, i, &fval); + if (fval < threshval && inrun == FALSE) { /* start a new run */ + inrun = TRUE; + x0 = startx + i * delx; + } else if (fval > threshval && inrun == TRUE) { /* end the run */ + inrun = FALSE; + x1 = startx + i * delx; + numaAddNumber(nad, x0); + numaAddNumber(nad, x1); + } + } + if (inrun == TRUE) { /* must end the last run */ + x1 = startx + (n - 1) * delx; + numaAddNumber(nad, x0); + numaAddNumber(nad, x1); + } + + return nad; +} + + +/*! + * \brief numaThresholdEdges() + * + * \param[in] nas input numa + * \param[in] thresh1 low threshold as fraction of max; in [0.0 ... 1.0] + * \param[in] thresh2 high threshold as fraction of max; in [0.0 ... 1.0] + * \param[in] maxn for normalizing; set maxn = 0.0 to use the max in nas + * \return nad edge interval triplets, or NULL on error + * + * <pre> + * Notes: + * (1) For each edge interval, where where the value is less + * than %thresh1 on one side, greater than %thresh2 on + * the other, and between these thresholds throughout the + * interval, this records a triplet of values: the + * 'left' and 'right' edges, and either +1 or -1, depending + * on whether the edge is rising or falling. + * (2) No assumption is made about the value outside the array, + * so if the value at the array edge is between the threshold + * values, it is not considered part of an edge. We start + * looking for edge intervals only after leaving the thresholded + * band. + * </pre> + */ +NUMA * +numaThresholdEdges(NUMA *nas, + l_float32 thresh1, + l_float32 thresh2, + l_float32 maxn) +{ +l_int32 n, i, istart, inband, output, sign; +l_int32 startbelow, below, above, belowlast, abovelast; +l_float32 maxval, threshval1, threshval2, fval, startx, delx, x0, x1; +NUMA *nad; + + PROCNAME("numaThresholdEdges"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if ((n = numaGetCount(nas)) == 0) + return (NUMA *)ERROR_PTR("nas is empty", procName, NULL); + if (thresh1 < 0.0 || thresh1 > 1.0 || thresh2 < 0.0 || thresh2 > 1.0) + return (NUMA *)ERROR_PTR("invalid thresholds", procName, NULL); + if (thresh2 < thresh1) + return (NUMA *)ERROR_PTR("thresh2 < thresh1", procName, NULL); + + /* The input thresholds are fractions of the max. + * The first entry in nad is the value of the max used + * here for normalization. */ + if (maxn == 0.0) + numaGetMax(nas, &maxval, NULL); + else + maxval = maxn; + numaGetMax(nas, &maxval, NULL); + numaGetParameters(nas, &startx, &delx); + threshval1 = thresh1 * maxval; + threshval2 = thresh2 * maxval; + nad = numaCreate(0); + numaAddNumber(nad, maxval); + + /* Write triplets of pts (x0, x1, sign) for the edges. + * First make sure we start search from outside the band. + * Only one of {belowlast, abovelast} is true. */ + for (i = 0; i < n; i++) { + istart = i; + numaGetFValue(nas, i, &fval); + belowlast = (fval < threshval1) ? TRUE : FALSE; + abovelast = (fval > threshval2) ? TRUE : FALSE; + if (belowlast == TRUE || abovelast == TRUE) + break; + } + if (istart == n) /* no intervals found */ + return nad; + + /* x0 and x1 can only be set from outside the edge. + * They are the values just before entering the band, + * and just after entering the band. We can jump through + * the band, in which case they differ by one index in nas. */ + inband = FALSE; + startbelow = belowlast; /* one of these is true */ + output = FALSE; + x0 = startx + istart * delx; + for (i = istart + 1; i < n; i++) { + numaGetFValue(nas, i, &fval); + below = (fval < threshval1) ? TRUE : FALSE; + above = (fval > threshval2) ? TRUE : FALSE; + if (!inband && belowlast && above) { /* full jump up */ + x1 = startx + i * delx; + sign = 1; + startbelow = FALSE; /* for the next transition */ + output = TRUE; + } else if (!inband && abovelast && below) { /* full jump down */ + x1 = startx + i * delx; + sign = -1; + startbelow = TRUE; /* for the next transition */ + output = TRUE; + } else if (inband && startbelow && above) { /* exit rising; success */ + x1 = startx + i * delx; + sign = 1; + inband = FALSE; + startbelow = FALSE; /* for the next transition */ + output = TRUE; + } else if (inband && !startbelow && below) { + /* exit falling; success */ + x1 = startx + i * delx; + sign = -1; + inband = FALSE; + startbelow = TRUE; /* for the next transition */ + output = TRUE; + } else if (inband && !startbelow && above) { /* exit rising; failure */ + x0 = startx + i * delx; + inband = FALSE; + } else if (inband && startbelow && below) { /* exit falling; failure */ + x0 = startx + i * delx; + inband = FALSE; + } else if (!inband && !above && !below) { /* enter */ + inband = TRUE; + startbelow = belowlast; + } else if (!inband && (above || below)) { /* outside and remaining */ + x0 = startx + i * delx; /* update position */ + } + belowlast = below; + abovelast = above; + if (output) { /* we have exited; save new x0 */ + numaAddNumber(nad, x0); + numaAddNumber(nad, x1); + numaAddNumber(nad, sign); + output = FALSE; + x0 = startx + i * delx; + } + } + + return nad; +} + + +/*! + * \brief numaGetSpanValues() + * + * \param[in] na numa that is output of numaLowPassIntervals() + * \param[in] span span number, zero-based + * \param[out] pstart [optional] location of start of transition + * \param[out] pend [optional] location of end of transition + * \return 0 if OK, 1 on error + */ +l_int32 +numaGetSpanValues(NUMA *na, + l_int32 span, + l_int32 *pstart, + l_int32 *pend) +{ +l_int32 n, nspans; + + PROCNAME("numaGetSpanValues"); + + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + if (n % 2 != 1) + return ERROR_INT("n is not odd", procName, 1); + nspans = n / 2; + if (nspans < 0 || span >= nspans) + return ERROR_INT("invalid span", procName, 1); + + if (pstart) numaGetIValue(na, 2 * span + 1, pstart); + if (pend) numaGetIValue(na, 2 * span + 2, pend); + return 0; +} + + +/*! + * \brief numaGetEdgeValues() + * + * \param[in] na numa that is output of numaThresholdEdges() + * \param[in] edge edge number, zero-based + * \param[out] pstart [optional] location of start of transition + * \param[out] pend [optional] location of end of transition + * \param[out] psign [optional] transition sign: +1 is rising, + * -1 is falling + * \return 0 if OK, 1 on error + */ +l_int32 +numaGetEdgeValues(NUMA *na, + l_int32 edge, + l_int32 *pstart, + l_int32 *pend, + l_int32 *psign) +{ +l_int32 n, nedges; + + PROCNAME("numaGetEdgeValues"); + + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + if (n % 3 != 1) + return ERROR_INT("n % 3 is not 1", procName, 1); + nedges = (n - 1) / 3; + if (edge < 0 || edge >= nedges) + return ERROR_INT("invalid edge", procName, 1); + + if (pstart) numaGetIValue(na, 3 * edge + 1, pstart); + if (pend) numaGetIValue(na, 3 * edge + 2, pend); + if (psign) numaGetIValue(na, 3 * edge + 3, psign); + return 0; +} + + +/*----------------------------------------------------------------------* + * Interpolation * + *----------------------------------------------------------------------*/ +/*! + * \brief numaInterpolateEqxVal() + * + * \param[in] startx xval corresponding to first element in array + * \param[in] deltax x increment between array elements + * \param[in] nay numa of ordinate values, assumed equally spaced + * \param[in] type L_LINEAR_INTERP, L_QUADRATIC_INTERP + * \param[in] xval + * \param[out] pyval interpolated value + * \return 0 if OK, 1 on error e.g., if xval is outside range + * + * <pre> + * Notes: + * (1) Considering nay as a function of x, the x values + * are equally spaced + * (2) Caller should check for valid return. + * + * For linear Lagrangian interpolation (through 2 data pts): + * y(x) = y1(x-x2)/(x1-x2) + y2(x-x1)/(x2-x1) + * + * For quadratic Lagrangian interpolation (through 3 data pts): + * y(x) = y1(x-x2)(x-x3)/((x1-x2)(x1-x3)) + + * y2(x-x1)(x-x3)/((x2-x1)(x2-x3)) + + * y3(x-x1)(x-x2)/((x3-x1)(x3-x2)) + * + * </pre> + */ +l_ok +numaInterpolateEqxVal(l_float32 startx, + l_float32 deltax, + NUMA *nay, + l_int32 type, + l_float32 xval, + l_float32 *pyval) +{ +l_int32 i, n, i1, i2, i3; +l_float32 x1, x2, x3, fy1, fy2, fy3, d1, d2, d3, del, fi, maxx; +l_float32 *fa; + + PROCNAME("numaInterpolateEqxVal"); + + if (!pyval) + return ERROR_INT("&yval not defined", procName, 1); + *pyval = 0.0; + if (!nay) + return ERROR_INT("nay not defined", procName, 1); + if (deltax <= 0.0) + return ERROR_INT("deltax not > 0", procName, 1); + if (type != L_LINEAR_INTERP && type != L_QUADRATIC_INTERP) + return ERROR_INT("invalid interp type", procName, 1); + if ((n = numaGetCount(nay)) < 2) + return ERROR_INT("not enough points", procName, 1); + if (type == L_QUADRATIC_INTERP && n == 2) { + type = L_LINEAR_INTERP; + L_WARNING("only 2 points; using linear interp\n", procName); + } + maxx = startx + deltax * (n - 1); + if (xval < startx || xval > maxx) + return ERROR_INT("xval is out of bounds", procName, 1); + + fa = numaGetFArray(nay, L_NOCOPY); + fi = (xval - startx) / deltax; + i = (l_int32)fi; + del = fi - i; + if (del == 0.0) { /* no interpolation required */ + *pyval = fa[i]; + return 0; + } + + if (type == L_LINEAR_INTERP) { + *pyval = fa[i] + del * (fa[i + 1] - fa[i]); + return 0; + } + + /* Quadratic interpolation */ + d1 = d3 = 0.5 / (deltax * deltax); + d2 = -2. * d1; + if (i == 0) { + i1 = i; + i2 = i + 1; + i3 = i + 2; + } else { + i1 = i - 1; + i2 = i; + i3 = i + 1; + } + x1 = startx + i1 * deltax; + x2 = startx + i2 * deltax; + x3 = startx + i3 * deltax; + fy1 = d1 * fa[i1]; + fy2 = d2 * fa[i2]; + fy3 = d3 * fa[i3]; + *pyval = fy1 * (xval - x2) * (xval - x3) + + fy2 * (xval - x1) * (xval - x3) + + fy3 * (xval - x1) * (xval - x2); + return 0; +} + + +/*! + * \brief numaInterpolateArbxVal() + * + * \param[in] nax numa of abscissa values + * \param[in] nay numa of ordinate values, corresponding to nax + * \param[in] type L_LINEAR_INTERP, L_QUADRATIC_INTERP + * \param[in] xval + * \param[out] pyval interpolated value + * \return 0 if OK, 1 on error e.g., if xval is outside range + * + * <pre> + * Notes: + * (1) The values in nax must be sorted in increasing order. + * If, additionally, they are equally spaced, you can use + * numaInterpolateEqxVal(). + * (2) Caller should check for valid return. + * (3) Uses lagrangian interpolation. See numaInterpolateEqxVal() + * for formulas. + * </pre> + */ +l_ok +numaInterpolateArbxVal(NUMA *nax, + NUMA *nay, + l_int32 type, + l_float32 xval, + l_float32 *pyval) +{ +l_int32 i, im, nx, ny, i1, i2, i3; +l_float32 delu, dell, fract, d1, d2, d3; +l_float32 minx, maxx; +l_float32 *fax, *fay; + + PROCNAME("numaInterpolateArbxVal"); + + if (!pyval) + return ERROR_INT("&yval not defined", procName, 1); + *pyval = 0.0; + if (!nax) + return ERROR_INT("nax not defined", procName, 1); + if (!nay) + return ERROR_INT("nay not defined", procName, 1); + if (type != L_LINEAR_INTERP && type != L_QUADRATIC_INTERP) + return ERROR_INT("invalid interp type", procName, 1); + ny = numaGetCount(nay); + nx = numaGetCount(nax); + if (nx != ny) + return ERROR_INT("nax and nay not same size arrays", procName, 1); + if (ny < 2) + return ERROR_INT("not enough points", procName, 1); + if (type == L_QUADRATIC_INTERP && ny == 2) { + type = L_LINEAR_INTERP; + L_WARNING("only 2 points; using linear interp\n", procName); + } + numaGetFValue(nax, 0, &minx); + numaGetFValue(nax, nx - 1, &maxx); + if (xval < minx || xval > maxx) + return ERROR_INT("xval is out of bounds", procName, 1); + + fax = numaGetFArray(nax, L_NOCOPY); + fay = numaGetFArray(nay, L_NOCOPY); + + /* Linear search for interval. We are guaranteed + * to either return or break out of the loop. + * In addition, we are assured that fax[i] - fax[im] > 0.0 */ + if (xval == fax[0]) { + *pyval = fay[0]; + return 0; + } + im = 0; + dell = 0.0; + for (i = 1; i < nx; i++) { + delu = fax[i] - xval; + if (delu >= 0.0) { /* we've passed it */ + if (delu == 0.0) { + *pyval = fay[i]; + return 0; + } + im = i - 1; + dell = xval - fax[im]; /* >= 0 */ + break; + } + } + fract = dell / (fax[i] - fax[im]); + + if (type == L_LINEAR_INTERP) { + *pyval = fay[i] + fract * (fay[i + 1] - fay[i]); + return 0; + } + + /* Quadratic interpolation */ + if (im == 0) { + i1 = im; + i2 = im + 1; + i3 = im + 2; + } else { + i1 = im - 1; + i2 = im; + i3 = im + 1; + } + d1 = (fax[i1] - fax[i2]) * (fax[i1] - fax[i3]); + d2 = (fax[i2] - fax[i1]) * (fax[i2] - fax[i3]); + d3 = (fax[i3] - fax[i1]) * (fax[i3] - fax[i2]); + *pyval = fay[i1] * (xval - fax[i2]) * (xval - fax[i3]) / d1 + + fay[i2] * (xval - fax[i1]) * (xval - fax[i3]) / d2 + + fay[i3] * (xval - fax[i1]) * (xval - fax[i2]) / d3; + return 0; +} + + +/*! + * \brief numaInterpolateEqxInterval() + * + * \param[in] startx xval corresponding to first element in nas + * \param[in] deltax x increment between array elements in nas + * \param[in] nasy numa of ordinate values, assumed equally spaced + * \param[in] type L_LINEAR_INTERP, L_QUADRATIC_INTERP + * \param[in] x0 start value of interval + * \param[in] x1 end value of interval + * \param[in] npts number of points to evaluate function in interval + * \param[out] pnax [optional] array of x values in interval + * \param[out] pnay array of y values in interval + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Considering nasy as a function of x, the x values + * are equally spaced. + * (2) This creates nay (and optionally nax) of interpolated + * values over the specified interval (x0, x1). + * (3) If the interval (x0, x1) lies partially outside the array + * nasy (as interpreted by startx and deltax), it is an + * error and returns 1. + * (4) Note that deltax is the intrinsic x-increment for the input + * array nasy, whereas delx is the intrinsic x-increment for the + * output interpolated array nay. + * </pre> + */ +l_ok +numaInterpolateEqxInterval(l_float32 startx, + l_float32 deltax, + NUMA *nasy, + l_int32 type, + l_float32 x0, + l_float32 x1, + l_int32 npts, + NUMA **pnax, + NUMA **pnay) +{ +l_int32 i, n; +l_float32 x, yval, maxx, delx; +NUMA *nax, *nay; + + PROCNAME("numaInterpolateEqxInterval"); + + if (pnax) *pnax = NULL; + if (!pnay) + return ERROR_INT("&nay not defined", procName, 1); + *pnay = NULL; + if (!nasy) + return ERROR_INT("nasy not defined", procName, 1); + if ((n = numaGetCount(nasy)) < 2) + return ERROR_INT("n < 2", procName, 1); + if (deltax <= 0.0) + return ERROR_INT("deltax not > 0", procName, 1); + if (type != L_LINEAR_INTERP && type != L_QUADRATIC_INTERP) + return ERROR_INT("invalid interp type", procName, 1); + if (type == L_QUADRATIC_INTERP && n == 2) { + type = L_LINEAR_INTERP; + L_WARNING("only 2 points; using linear interp\n", procName); + } + maxx = startx + deltax * (n - 1); + if (x0 < startx || x1 > maxx || x1 <= x0) + return ERROR_INT("[x0 ... x1] is not valid", procName, 1); + if (npts < 3) + return ERROR_INT("npts < 3", procName, 1); + delx = (x1 - x0) / (l_float32)(npts - 1); /* delx is for output nay */ + + if ((nay = numaCreate(npts)) == NULL) + return ERROR_INT("nay not made", procName, 1); + numaSetParameters(nay, x0, delx); + *pnay = nay; + if (pnax) { + nax = numaCreate(npts); + *pnax = nax; + } + + for (i = 0; i < npts; i++) { + x = x0 + i * delx; + if (pnax) + numaAddNumber(nax, x); + numaInterpolateEqxVal(startx, deltax, nasy, type, x, &yval); + numaAddNumber(nay, yval); + } + + return 0; +} + + +/*! + * \brief numaInterpolateArbxInterval() + * + * \param[in] nax numa of abscissa values + * \param[in] nay numa of ordinate values, corresponding to nax + * \param[in] type L_LINEAR_INTERP, L_QUADRATIC_INTERP + * \param[in] x0 start value of interval + * \param[in] x1 end value of interval + * \param[in] npts number of points to evaluate function in interval + * \param[out] pnadx [optional] array of x values in interval + * \param[out] pnady array of y values in interval + * \return 0 if OK, 1 on error e.g., if x0 or x1 is outside range + * + * <pre> + * Notes: + * (1) The values in nax must be sorted in increasing order. + * If they are not sorted, we do it here, and complain. + * (2) If the values in nax are equally spaced, you can use + * numaInterpolateEqxInterval(). + * (3) Caller should check for valid return. + * (4) We don't call numaInterpolateArbxVal() for each output + * point, because that requires an O(n) search for + * each point. Instead, we do a single O(n) pass through + * nax, saving the indices to be used for each output yval. + * (5) Uses lagrangian interpolation. See numaInterpolateEqxVal() + * for formulas. + * </pre> + */ +l_ok +numaInterpolateArbxInterval(NUMA *nax, + NUMA *nay, + l_int32 type, + l_float32 x0, + l_float32 x1, + l_int32 npts, + NUMA **pnadx, + NUMA **pnady) +{ +l_int32 i, im, j, nx, ny, i1, i2, i3, sorted; +l_int32 *index; +l_float32 del, xval, yval, excess, fract, minx, maxx, d1, d2, d3; +l_float32 *fax, *fay; +NUMA *nasx, *nasy, *nadx, *nady; + + PROCNAME("numaInterpolateArbxInterval"); + + if (pnadx) *pnadx = NULL; + if (!pnady) + return ERROR_INT("&nady not defined", procName, 1); + *pnady = NULL; + if (!nay) + return ERROR_INT("nay not defined", procName, 1); + if (!nax) + return ERROR_INT("nax not defined", procName, 1); + if (type != L_LINEAR_INTERP && type != L_QUADRATIC_INTERP) + return ERROR_INT("invalid interp type", procName, 1); + if (x0 > x1) + return ERROR_INT("x0 > x1", procName, 1); + ny = numaGetCount(nay); + nx = numaGetCount(nax); + if (nx != ny) + return ERROR_INT("nax and nay not same size arrays", procName, 1); + if (ny < 2) + return ERROR_INT("not enough points", procName, 1); + if (type == L_QUADRATIC_INTERP && ny == 2) { + type = L_LINEAR_INTERP; + L_WARNING("only 2 points; using linear interp\n", procName); + } + numaGetMin(nax, &minx, NULL); + numaGetMax(nax, &maxx, NULL); + if (x0 < minx || x1 > maxx) + return ERROR_INT("xval is out of bounds", procName, 1); + + /* Make sure that nax is sorted in increasing order */ + numaIsSorted(nax, L_SORT_INCREASING, &sorted); + if (!sorted) { + L_WARNING("we are sorting nax in increasing order\n", procName); + numaSortPair(nax, nay, L_SORT_INCREASING, &nasx, &nasy); + } else { + nasx = numaClone(nax); + nasy = numaClone(nay); + } + + fax = numaGetFArray(nasx, L_NOCOPY); + fay = numaGetFArray(nasy, L_NOCOPY); + + /* Get array of indices into fax for interpolated locations */ + if ((index = (l_int32 *)LEPT_CALLOC(npts, sizeof(l_int32))) == NULL) { + numaDestroy(&nasx); + numaDestroy(&nasy); + return ERROR_INT("ind not made", procName, 1); + } + del = (x1 - x0) / (npts - 1.0); + for (i = 0, j = 0; j < nx && i < npts; i++) { + xval = x0 + i * del; + while (j < nx - 1 && xval > fax[j]) + j++; + if (xval == fax[j]) + index[i] = L_MIN(j, nx - 1); + else /* the index of fax[] is just below xval */ + index[i] = L_MAX(j - 1, 0); + } + + /* For each point to be interpolated, get the y value */ + nady = numaCreate(npts); + *pnady = nady; + if (pnadx) { + nadx = numaCreate(npts); + *pnadx = nadx; + } + for (i = 0; i < npts; i++) { + xval = x0 + i * del; + if (pnadx) + numaAddNumber(nadx, xval); + im = index[i]; + excess = xval - fax[im]; + if (excess == 0.0) { + numaAddNumber(nady, fay[im]); + continue; + } + fract = excess / (fax[im + 1] - fax[im]); + + if (type == L_LINEAR_INTERP) { + yval = fay[im] + fract * (fay[im + 1] - fay[im]); + numaAddNumber(nady, yval); + continue; + } + + /* Quadratic interpolation */ + if (im == 0) { + i1 = im; + i2 = im + 1; + i3 = im + 2; + } else { + i1 = im - 1; + i2 = im; + i3 = im + 1; + } + d1 = (fax[i1] - fax[i2]) * (fax[i1] - fax[i3]); + d2 = (fax[i2] - fax[i1]) * (fax[i2] - fax[i3]); + d3 = (fax[i3] - fax[i1]) * (fax[i3] - fax[i2]); + yval = fay[i1] * (xval - fax[i2]) * (xval - fax[i3]) / d1 + + fay[i2] * (xval - fax[i1]) * (xval - fax[i3]) / d2 + + fay[i3] * (xval - fax[i1]) * (xval - fax[i2]) / d3; + numaAddNumber(nady, yval); + } + + LEPT_FREE(index); + numaDestroy(&nasx); + numaDestroy(&nasy); + return 0; +} + + +/*----------------------------------------------------------------------* + * Functions requiring interpolation * + *----------------------------------------------------------------------*/ +/*! + * \brief numaFitMax() + * + * \param[in] na numa of ordinate values, to fit a max to + * \param[out] pmaxval max value + * \param[in] naloc [optional] associated numa of abscissa values + * \param[out] pmaxloc abscissa value that gives max value in na; + * if naloc == null, this is given as an interpolated + * index value + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * If %naloc is given, there is no requirement that the + * data points are evenly spaced. Lagrangian interpolation + * handles that. The only requirement is that the + * data points are ordered so that the values in naloc + * are either increasing or decreasing. We test to make + * sure that the sizes of na and naloc are equal, and it + * is assumed that the correspondences %na[i] as a function + * of %naloc[i] are properly arranged for all i. + * + * The formula for Lagrangian interpolation through 3 data pts is: + * y(x) = y1(x-x2)(x-x3)/((x1-x2)(x1-x3)) + + * y2(x-x1)(x-x3)/((x2-x1)(x2-x3)) + + * y3(x-x1)(x-x2)/((x3-x1)(x3-x2)) + * + * Then the derivative, using the constants (c1,c2,c3) defined below, + * is set to 0: + * y'(x) = 2x(c1+c2+c3) - c1(x2+x3) - c2(x1+x3) - c3(x1+x2) = 0 + * </pre> + */ +l_ok +numaFitMax(NUMA *na, + l_float32 *pmaxval, + NUMA *naloc, + l_float32 *pmaxloc) +{ +l_float32 val; +l_float32 smaxval; /* start value of maximum sample, before interpolating */ +l_int32 n, imaxloc; +l_float32 x1, x2, x3, y1, y2, y3, c1, c2, c3, a, b, xmax, ymax; + + PROCNAME("numaFitMax"); + + if (pmaxval) *pmaxval = 0.0; + if (pmaxloc) *pmaxloc = 0.0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + if (!pmaxval) + return ERROR_INT("&maxval not defined", procName, 1); + if (!pmaxloc) + return ERROR_INT("&maxloc not defined", procName, 1); + if (naloc) { + if (n != numaGetCount(naloc)) + return ERROR_INT("na and naloc of unequal size", procName, 1); + } + + numaGetMax(na, &smaxval, &imaxloc); + + /* Simple case: max is at end point */ + if (imaxloc == 0 || imaxloc == n - 1) { + *pmaxval = smaxval; + if (naloc) { + numaGetFValue(naloc, imaxloc, &val); + *pmaxloc = val; + } else { + *pmaxloc = imaxloc; + } + return 0; + } + + /* Interior point; use quadratic interpolation */ + y2 = smaxval; + numaGetFValue(na, imaxloc - 1, &val); + y1 = val; + numaGetFValue(na, imaxloc + 1, &val); + y3 = val; + if (naloc) { + numaGetFValue(naloc, imaxloc - 1, &val); + x1 = val; + numaGetFValue(naloc, imaxloc, &val); + x2 = val; + numaGetFValue(naloc, imaxloc + 1, &val); + x3 = val; + } else { + x1 = imaxloc - 1; + x2 = imaxloc; + x3 = imaxloc + 1; + } + + /* Can't interpolate; just use the max val in na + * and the corresponding one in naloc */ + if (x1 == x2 || x1 == x3 || x2 == x3) { + *pmaxval = y2; + *pmaxloc = x2; + return 0; + } + + /* Use lagrangian interpolation; set dy/dx = 0 */ + c1 = y1 / ((x1 - x2) * (x1 - x3)); + c2 = y2 / ((x2 - x1) * (x2 - x3)); + c3 = y3 / ((x3 - x1) * (x3 - x2)); + a = c1 + c2 + c3; + b = c1 * (x2 + x3) + c2 * (x1 + x3) + c3 * (x1 + x2); + xmax = b / (2 * a); + ymax = c1 * (xmax - x2) * (xmax - x3) + + c2 * (xmax - x1) * (xmax - x3) + + c3 * (xmax - x1) * (xmax - x2); + *pmaxval = ymax; + *pmaxloc = xmax; + + return 0; +} + + +/*! + * \brief numaDifferentiateInterval() + * + * \param[in] nax numa of abscissa values + * \param[in] nay numa of ordinate values, corresponding to nax + * \param[in] x0 start value of interval + * \param[in] x1 end value of interval + * \param[in] npts number of points to evaluate function in interval + * \param[out] pnadx [optional] array of x values in interval + * \param[out] pnady array of derivatives in interval + * \return 0 if OK, 1 on error e.g., if x0 or x1 is outside range + * + * <pre> + * Notes: + * (1) The values in nax must be sorted in increasing order. + * If they are not sorted, it is done in the interpolation + * step, and a warning is issued. + * (2) Caller should check for valid return. + * </pre> + */ +l_ok +numaDifferentiateInterval(NUMA *nax, + NUMA *nay, + l_float32 x0, + l_float32 x1, + l_int32 npts, + NUMA **pnadx, + NUMA **pnady) +{ +l_int32 i, nx, ny; +l_float32 minx, maxx, der, invdel; +l_float32 *fay; +NUMA *nady, *naiy; + + PROCNAME("numaDifferentiateInterval"); + + if (pnadx) *pnadx = NULL; + if (!pnady) + return ERROR_INT("&nady not defined", procName, 1); + *pnady = NULL; + if (!nay) + return ERROR_INT("nay not defined", procName, 1); + if (!nax) + return ERROR_INT("nax not defined", procName, 1); + if (x0 > x1) + return ERROR_INT("x0 > x1", procName, 1); + ny = numaGetCount(nay); + nx = numaGetCount(nax); + if (nx != ny) + return ERROR_INT("nax and nay not same size arrays", procName, 1); + if (ny < 2) + return ERROR_INT("not enough points", procName, 1); + numaGetMin(nax, &minx, NULL); + numaGetMax(nax, &maxx, NULL); + if (x0 < minx || x1 > maxx) + return ERROR_INT("xval is out of bounds", procName, 1); + if (npts < 2) + return ERROR_INT("npts < 2", procName, 1); + + /* Generate interpolated array over specified interval */ + if (numaInterpolateArbxInterval(nax, nay, L_LINEAR_INTERP, x0, x1, + npts, pnadx, &naiy)) + return ERROR_INT("interpolation failed", procName, 1); + + nady = numaCreate(npts); + *pnady = nady; + invdel = 0.5 * ((l_float32)npts - 1.0) / (x1 - x0); + fay = numaGetFArray(naiy, L_NOCOPY); + + /* Compute and save derivatives */ + der = 0.5 * invdel * (fay[1] - fay[0]); + numaAddNumber(nady, der); + for (i = 1; i < npts - 1; i++) { + der = invdel * (fay[i + 1] - fay[i - 1]); + numaAddNumber(nady, der); + } + der = 0.5 * invdel * (fay[npts - 1] - fay[npts - 2]); + numaAddNumber(nady, der); + + numaDestroy(&naiy); + return 0; +} + + +/*! + * \brief numaIntegrateInterval() + * + * \param[in] nax numa of abscissa values + * \param[in] nay numa of ordinate values, corresponding to nax + * \param[in] x0 start value of interval + * \param[in] x1 end value of interval + * \param[in] npts number of points to evaluate function in interval + * \param[out] psum integral of function over interval + * \return 0 if OK, 1 on error e.g., if x0 or x1 is outside range + * + * <pre> + * Notes: + * (1) The values in nax must be sorted in increasing order. + * If they are not sorted, it is done in the interpolation + * step, and a warning is issued. + * (2) Caller should check for valid return. + * </pre> + */ +l_ok +numaIntegrateInterval(NUMA *nax, + NUMA *nay, + l_float32 x0, + l_float32 x1, + l_int32 npts, + l_float32 *psum) +{ +l_int32 i, nx, ny; +l_float32 minx, maxx, sum, del; +l_float32 *fay; +NUMA *naiy; + + PROCNAME("numaIntegrateInterval"); + + if (!psum) + return ERROR_INT("&sum not defined", procName, 1); + *psum = 0.0; + if (!nay) + return ERROR_INT("nay not defined", procName, 1); + if (!nax) + return ERROR_INT("nax not defined", procName, 1); + if (x0 > x1) + return ERROR_INT("x0 > x1", procName, 1); + if (npts < 2) + return ERROR_INT("npts < 2", procName, 1); + ny = numaGetCount(nay); + nx = numaGetCount(nax); + if (nx != ny) + return ERROR_INT("nax and nay not same size arrays", procName, 1); + if (ny < 2) + return ERROR_INT("not enough points", procName, 1); + numaGetMin(nax, &minx, NULL); + numaGetMax(nax, &maxx, NULL); + if (x0 < minx || x1 > maxx) + return ERROR_INT("xval is out of bounds", procName, 1); + + /* Generate interpolated array over specified interval */ + if (numaInterpolateArbxInterval(nax, nay, L_LINEAR_INTERP, x0, x1, + npts, NULL, &naiy)) + return ERROR_INT("interpolation failed", procName, 1); + + del = (x1 - x0) / ((l_float32)npts - 1.0); + fay = numaGetFArray(naiy, L_NOCOPY); + + /* Compute integral (simple trapezoid) */ + sum = 0.5 * (fay[0] + fay[npts - 1]); + for (i = 1; i < npts - 1; i++) + sum += fay[i]; + *psum = del * sum; + + numaDestroy(&naiy); + return 0; +} + + +/*----------------------------------------------------------------------* + * Sorting * + *----------------------------------------------------------------------*/ +/*! + * \brief numaSortGeneral() + * + * \param[in] na source numa + * \param[out] pnasort [optional] sorted numa + * \param[out] pnaindex [optional] index of elements in na associated + * with each element of nasort + * \param[out] pnainvert [optional] index of elements in nasort associated + * with each element of na + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \param[in] sorttype L_SHELL_SORT or L_BIN_SORT + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Sorting can be confusing. Here's an array of five values with + * the results shown for the 3 output arrays. + * + * na nasort naindex nainvert + * ----------------------------------- + * 3 9 2 3 + * 4 6 3 2 + * 9 4 1 0 + * 6 3 0 1 + * 1 1 4 4 + * + * Note that naindex is a LUT into na for the sorted array values, + * and nainvert directly gives the sorted index values for the + * input array. It is useful to view naindex is as a map: + * 0 --> 2 + * 1 --> 3 + * 2 --> 1 + * 3 --> 0 + * 4 --> 4 + * and nainvert, the inverse of this map: + * 0 --> 3 + * 1 --> 2 + * 2 --> 0 + * 3 --> 1 + * 4 --> 4 + * + * We can write these relations symbolically as: + * nasort[i] = na[naindex[i]] + * na[i] = nasort[nainvert[i]] + * </pre> + */ +l_ok +numaSortGeneral(NUMA *na, + NUMA **pnasort, + NUMA **pnaindex, + NUMA **pnainvert, + l_int32 sortorder, + l_int32 sorttype) +{ +l_int32 isize; +l_float32 size; +NUMA *naindex = NULL; + + PROCNAME("numaSortGeneral"); + + if (pnasort) *pnasort = NULL; + if (pnaindex) *pnaindex = NULL; + if (pnainvert) *pnainvert = NULL; + if (!na) + return ERROR_INT("na not defined", procName, 1); + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return ERROR_INT("invalid sort order", procName, 1); + if (sorttype != L_SHELL_SORT && sorttype != L_BIN_SORT) + return ERROR_INT("invalid sort type", procName, 1); + if (!pnasort && !pnaindex && !pnainvert) + return ERROR_INT("nothing to do", procName, 1); + + if (sorttype == L_BIN_SORT) { + numaGetMax(na, &size, NULL); + isize = (l_int32)size; + if (isize > MaxInitPtraSize - 1) { + L_WARNING("array too large; using shell sort\n", procName); + sorttype = L_SHELL_SORT; + } else { + naindex = numaGetBinSortIndex(na, sortorder); + } + } + + if (sorttype == L_SHELL_SORT) + naindex = numaGetSortIndex(na, sortorder); + + if (pnasort) + *pnasort = numaSortByIndex(na, naindex); + if (pnainvert) + *pnainvert = numaInvertMap(naindex); + if (pnaindex) + *pnaindex = naindex; + else + numaDestroy(&naindex); + return 0; +} + + +/*! + * \brief numaSortAutoSelect() + * + * \param[in] nas + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \return naout output sorted numa, or NULL on error + * + * <pre> + * Notes: + * (1) This does either a shell sort or a bin sort, depending on + * the number of elements in nas and the dynamic range. + * </pre> + */ +NUMA * +numaSortAutoSelect(NUMA *nas, + l_int32 sortorder) +{ +l_int32 type; + + PROCNAME("numaSortAutoSelect"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (numaGetCount(nas) == 0) { + L_WARNING("nas is empty; returning copy\n", procName); + return numaCopy(nas); + } + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL); + + type = numaChooseSortType(nas); + if (type != L_SHELL_SORT && type != L_BIN_SORT) + return (NUMA *)ERROR_PTR("invalid sort type", procName, NULL); + + if (type == L_BIN_SORT) + return numaBinSort(nas, sortorder); + else /* shell sort */ + return numaSort(NULL, nas, sortorder); +} + + +/*! + * \brief numaSortIndexAutoSelect() + * + * \param[in] nas + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \return nad indices of nas, sorted by value in nas, or NULL on error + * + * <pre> + * Notes: + * (1) This does either a shell sort or a bin sort, depending on + * the number of elements in nas and the dynamic range. + * </pre> + */ +NUMA * +numaSortIndexAutoSelect(NUMA *nas, + l_int32 sortorder) +{ +l_int32 type; + + PROCNAME("numaSortIndexAutoSelect"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (numaGetCount(nas) == 0) { + L_WARNING("nas is empty; returning copy\n", procName); + return numaCopy(nas); + } + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL); + type = numaChooseSortType(nas); + if (type != L_SHELL_SORT && type != L_BIN_SORT) + return (NUMA *)ERROR_PTR("invalid sort type", procName, NULL); + + if (type == L_BIN_SORT) + return numaGetBinSortIndex(nas, sortorder); + else /* shell sort */ + return numaGetSortIndex(nas, sortorder); +} + + +/*! + * \brief numaChooseSortType() + * + * \param[in] nas to be sorted + * \return sorttype L_SHELL_SORT or L_BIN_SORT, or UNDEF on error. + * + * <pre> + * Notes: + * (1) This selects either a shell sort or a bin sort, depending on + * the number of elements in nas and the dynamic range. + * (2) If there are negative values in nas, it selects shell sort. + * </pre> + */ +l_int32 +numaChooseSortType(NUMA *nas) +{ +l_int32 n; +l_float32 minval, maxval; + + PROCNAME("numaChooseSortType"); + + if (!nas) + return ERROR_INT("nas not defined", procName, UNDEF); + + /* If small histogram or negative values; use shell sort */ + numaGetMin(nas, &minval, NULL); + n = numaGetCount(nas); + if (minval < 0.0 || n < 200) + return L_SHELL_SORT; + + /* If large maxval, use shell sort */ + numaGetMax(nas, &maxval, NULL); + if (maxval > MaxInitPtraSize - 1) + return L_SHELL_SORT; + + /* Otherwise, need to compare nlog(n) with maxval. + * The factor of 0.003 was determined by comparing times for + * different histogram sizes and maxval. It is very small + * because binsort is fast and shell sort gets slow for large n. */ + if (n * log((l_float32)n) < 0.003 * maxval) + return L_SHELL_SORT; + else + return L_BIN_SORT; +} + + +/*! + * \brief numaSort() + * + * \param[in] naout output numa; can be NULL or equal to nain + * \param[in] nain input numa + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \return naout output sorted numa, or NULL on error + * + * <pre> + * Notes: + * (1) Set naout = nain for in-place; otherwise, set naout = NULL. + * (2) Source: Shell sort, modified from K&R, 2nd edition, p.62. + * Slow but simple O(n logn) sort. + * </pre> + */ +NUMA * +numaSort(NUMA *naout, + NUMA *nain, + l_int32 sortorder) +{ +l_int32 i, n, gap, j; +l_float32 tmp; +l_float32 *array; + + PROCNAME("numaSort"); + + if (!nain) + return (NUMA *)ERROR_PTR("nain not defined", procName, NULL); + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL); + + /* Make naout if necessary; otherwise do in-place */ + if (!naout) + naout = numaCopy(nain); + else if (nain != naout) + return (NUMA *)ERROR_PTR("invalid: not in-place", procName, NULL); + if ((n = numaGetCount(naout)) == 0) { + L_WARNING("naout is empty\n", procName); + return naout; + } + array = naout->array; /* operate directly on the array */ + n = numaGetCount(naout); + + /* Shell sort */ + for (gap = n/2; gap > 0; gap = gap / 2) { + for (i = gap; i < n; i++) { + for (j = i - gap; j >= 0; j -= gap) { + if ((sortorder == L_SORT_INCREASING && + array[j] > array[j + gap]) || + (sortorder == L_SORT_DECREASING && + array[j] < array[j + gap])) + { + tmp = array[j]; + array[j] = array[j + gap]; + array[j + gap] = tmp; + } + } + } + } + + return naout; +} + + +/*! + * \brief numaBinSort() + * + * \param[in] nas of non-negative integers with a max that can + * not exceed (MaxInitPtraSize - 1) + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \return na sorted, or NULL on error + * + * <pre> + * Notes: + * (1) Because this uses a bin sort with buckets of size 1, it + * is not appropriate for sorting either small arrays or + * arrays containing very large integer values. For such + * arrays, use a standard general sort function like + * numaSort(). + * (2) You can use numaSortAutoSelect() to decide which sorting + * method to use. + * </pre> + */ +NUMA * +numaBinSort(NUMA *nas, + l_int32 sortorder) +{ +NUMA *nat, *nad; + + PROCNAME("numaBinSort"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (numaGetCount(nas) == 0) { + L_WARNING("nas is empty; returning copy\n", procName); + return numaCopy(nas); + } + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL); + + if ((nat = numaGetBinSortIndex(nas, sortorder)) == NULL) + return (NUMA *)ERROR_PTR("bin sort failed", procName, NULL); + nad = numaSortByIndex(nas, nat); + numaDestroy(&nat); + return nad; +} + + +/*! + * \brief numaGetSortIndex() + * + * \param[in] na source numa + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \return na giving an array of indices that would sort + * the input array, or NULL on error + */ +NUMA * +numaGetSortIndex(NUMA *na, + l_int32 sortorder) +{ +l_int32 i, n, gap, j; +l_float32 tmp; +l_float32 *array; /* copy of input array */ +l_float32 *iarray; /* array of indices */ +NUMA *naisort; + + PROCNAME("numaGetSortIndex"); + + if (!na) + return (NUMA *)ERROR_PTR("na not defined", procName, NULL); + if (numaGetCount(na) == 0) { + L_WARNING("na is empty\n", procName); + return numaCreate(1); + } + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return (NUMA *)ERROR_PTR("invalid sortorder", procName, NULL); + + n = numaGetCount(na); + if ((array = numaGetFArray(na, L_COPY)) == NULL) + return (NUMA *)ERROR_PTR("array not made", procName, NULL); + if ((iarray = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL) { + LEPT_FREE(array); + return (NUMA *)ERROR_PTR("iarray not made", procName, NULL); + } + for (i = 0; i < n; i++) + iarray[i] = i; + + /* Shell sort */ + for (gap = n/2; gap > 0; gap = gap / 2) { + for (i = gap; i < n; i++) { + for (j = i - gap; j >= 0; j -= gap) { + if ((sortorder == L_SORT_INCREASING && + array[j] > array[j + gap]) || + (sortorder == L_SORT_DECREASING && + array[j] < array[j + gap])) + { + tmp = array[j]; + array[j] = array[j + gap]; + array[j + gap] = tmp; + tmp = iarray[j]; + iarray[j] = iarray[j + gap]; + iarray[j + gap] = tmp; + } + } + } + } + + naisort = numaCreate(n); + for (i = 0; i < n; i++) + numaAddNumber(naisort, iarray[i]); + + LEPT_FREE(array); + LEPT_FREE(iarray); + return naisort; +} + + +/*! + * \brief numaGetBinSortIndex() + * + * \param[in] nas of non-negative integers with a max that can + * not exceed (MaxInitPtraSize - 1) + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \return na sorted, or NULL on error + * + * <pre> + * Notes: + * (1) This creates an array (or lookup table) that contains + * the sorted position of the elements in the input Numa. + * (2) Because it uses a bin sort with buckets of size 1, it + * is not appropriate for sorting either small arrays or + * arrays containing very large integer values. For such + * arrays, use a standard general sort function like + * numaGetSortIndex(). + * (3) You can use numaSortIndexAutoSelect() to decide which + * sorting method to use. + * </pre> + */ +NUMA * +numaGetBinSortIndex(NUMA *nas, + l_int32 sortorder) +{ +l_int32 i, n, isize, ival, imax; +l_float32 minsize, size; +NUMA *na, *nai, *nad; +L_PTRA *paindex; + + PROCNAME("numaGetBinSortIndex"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (numaGetCount(nas) == 0) { + L_WARNING("nas is empty\n", procName); + return numaCreate(1); + } + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL); + numaGetMin(nas, &minsize, NULL); + if (minsize < 0) + return (NUMA *)ERROR_PTR("nas has negative numbers", procName, NULL); + numaGetMax(nas, &size, NULL); + isize = (l_int32)size; + if (isize > MaxInitPtraSize - 1) { + L_ERROR("array too large: %d elements > max size = %d\n", + procName, isize, MaxInitPtraSize - 1); + return NULL; + } + + /* Set up a ptra holding numa at indices for which there + * are values in nas. Suppose nas has the value 230 at index + * 7355. A numa holding the index 7355 is created and stored + * at the ptra index 230. If there is another value of 230 + * in nas, its index is added to the same numa (at index 230 + * in the ptra). When finished, the ptra can be scanned for numa, + * and the original indices in the nas can be read out. In this + * way, the ptra effectively sorts the input numbers in the nas. */ + paindex = ptraCreate(isize + 1); + n = numaGetCount(nas); + for (i = 0; i < n; i++) { + numaGetIValue(nas, i, &ival); + nai = (NUMA *)ptraGetPtrToItem(paindex, ival); + if (!nai) { /* make it; no shifting will occur */ + nai = numaCreate(1); + ptraInsert(paindex, ival, nai, L_MIN_DOWNSHIFT); + } + numaAddNumber(nai, i); + } + + /* Sort by scanning the ptra, extracting numas and pulling + * the (index into nas) numbers out of each numa, taken + * successively in requested order. */ + ptraGetMaxIndex(paindex, &imax); + nad = numaCreate(0); + if (sortorder == L_SORT_INCREASING) { + for (i = 0; i <= imax; i++) { + na = (NUMA *)ptraRemove(paindex, i, L_NO_COMPACTION); + if (!na) continue; + numaJoin(nad, na, 0, -1); + numaDestroy(&na); + } + } else { /* L_SORT_DECREASING */ + for (i = imax; i >= 0; i--) { + na = (NUMA *)ptraRemoveLast(paindex); + if (!na) break; /* they've all been removed */ + numaJoin(nad, na, 0, -1); + numaDestroy(&na); + } + } + + ptraDestroy(&paindex, FALSE, FALSE); + return nad; +} + + +/*! + * \brief numaSortByIndex() + * + * \param[in] nas + * \param[in] naindex na that maps from the new numa to the input numa + * \return nad sorted, or NULL on error + */ +NUMA * +numaSortByIndex(NUMA *nas, + NUMA *naindex) +{ +l_int32 i, n, ni, index; +l_float32 val; +NUMA *nad; + + PROCNAME("numaSortByIndex"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if (!naindex) + return (NUMA *)ERROR_PTR("naindex not defined", procName, NULL); + n = numaGetCount(nas); + ni = numaGetCount(naindex); + if (n != ni) + return (NUMA *)ERROR_PTR("numa sizes differ", procName, NULL); + if (n == 0) { + L_WARNING("nas is empty\n", procName); + return numaCopy(nas); + } + + nad = numaCreate(n); + for (i = 0; i < n; i++) { + numaGetIValue(naindex, i, &index); + numaGetFValue(nas, index, &val); + numaAddNumber(nad, val); + } + + return nad; +} + + +/*! + * \brief numaIsSorted() + * + * \param[in] nas + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \param[out] psorted 1 if sorted; 0 if not + * \return 1 if OK; 0 on error + * + * <pre> + * Notes: + * (1) This is a quick O(n) test if nas is sorted. It is useful + * in situations where the array is likely to be already + * sorted, and a sort operation can be avoided. + * </pre> + */ +l_int32 +numaIsSorted(NUMA *nas, + l_int32 sortorder, + l_int32 *psorted) +{ +l_int32 i, n; +l_float32 prevval, val; + + PROCNAME("numaIsSorted"); + + if (!psorted) + return ERROR_INT("&sorted not defined", procName, 1); + *psorted = FALSE; + if (!nas) + return ERROR_INT("nas not defined", procName, 1); + if ((n = numaGetCount(nas))== 0) { + L_WARNING("nas is empty\n", procName); + *psorted = TRUE; + return 0; + } + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return ERROR_INT("invalid sortorder", procName, 1); + + n = numaGetCount(nas); + numaGetFValue(nas, 0, &prevval); + for (i = 1; i < n; i++) { + numaGetFValue(nas, i, &val); + if ((sortorder == L_SORT_INCREASING && val < prevval) || + (sortorder == L_SORT_DECREASING && val > prevval)) + return 0; + } + + *psorted = TRUE; + return 0; +} + + +/*! + * \brief numaSortPair() + * + * \param[in] nax, nay input arrays + * \param[in] sortorder L_SORT_INCREASING or L_SORT_DECREASING + * \param[out] pnasx sorted + * \param[out] pnasy sorted exactly in order of nasx + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This function sorts the two input arrays, nax and nay, + * together, using nax as the key for sorting. + * </pre> + */ +l_ok +numaSortPair(NUMA *nax, + NUMA *nay, + l_int32 sortorder, + NUMA **pnasx, + NUMA **pnasy) +{ +l_int32 sorted; +NUMA *naindex; + + PROCNAME("numaSortPair"); + + if (pnasx) *pnasx = NULL; + if (pnasy) *pnasy = NULL; + if (!pnasx || !pnasy) + return ERROR_INT("&nasx and/or &nasy not defined", procName, 1); + if (!nax) + return ERROR_INT("nax not defined", procName, 1); + if (!nay) + return ERROR_INT("nay not defined", procName, 1); + if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) + return ERROR_INT("invalid sortorder", procName, 1); + + numaIsSorted(nax, sortorder, &sorted); + if (sorted == TRUE) { + *pnasx = numaCopy(nax); + *pnasy = numaCopy(nay); + } else { + naindex = numaGetSortIndex(nax, sortorder); + *pnasx = numaSortByIndex(nax, naindex); + *pnasy = numaSortByIndex(nay, naindex); + numaDestroy(&naindex); + } + + return 0; +} + + +/*! + * \brief numaInvertMap() + * + * \param[in] nas + * \return nad the inverted map, or NULL on error or if not invertible + * + * <pre> + * Notes: + * (1) This requires that nas contain each integer from 0 to n-1. + * The array is typically an index array into a sort or permutation + * of another array. + * </pre> + */ +NUMA * +numaInvertMap(NUMA *nas) +{ +l_int32 i, n, val, error; +l_int32 *test; +NUMA *nad; + + PROCNAME("numaInvertMap"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if ((n = numaGetCount(nas)) == 0) { + L_WARNING("nas is empty\n", procName); + return numaCopy(nas); + } + + nad = numaMakeConstant(0.0, n); + test = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32)); + error = 0; + for (i = 0; i < n; i++) { + numaGetIValue(nas, i, &val); + if (val >= n) { + error = 1; + break; + } + numaReplaceNumber(nad, val, i); + if (test[val] == 0) { + test[val] = 1; + } else { + error = 1; + break; + } + } + + LEPT_FREE(test); + if (error) { + numaDestroy(&nad); + return (NUMA *)ERROR_PTR("nas not invertible", procName, NULL); + } + + return nad; +} + +/*! + * \brief numaAddSorted() + * + * \param[in] na sorted input + * \param[in] val value to be inserted in sorted order + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The input %na is sorted. This function determines the + * sort order of %na and inserts %val into the array. + * </pre> + */ +l_ok +numaAddSorted(NUMA *na, + l_float32 val) +{ +l_int32 index; + + PROCNAME("numaAddSorted"); + + if (!na) + return ERROR_INT("na not defined", procName, 1); + + if (numaFindSortedLoc(na, val, &index) == 1) + return ERROR_INT("insert failure", procName, 1); + numaInsertNumber(na, index, val); + return 0; +} + + +/*! + * \brief numaFindSortedLoc() + * + * \param[in] na sorted input + * \param[in] val value to be inserted in sorted order + * \param[out] *ploc index location to insert @val + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The input %na is sorted. This determines the sort order of @na, + * either increasing or decreasing, and does a binary search for the + * location to insert %val into the array. The search is O(log n). + * (2) The index returned is the location to insert into the array. + * The value at the index, and all values to the right, are + * moved to the right (increasing their index location by 1). + * (3) If n is the size of %na, *ploc can be anything in [0 ... n]. + * if *ploc == 0, the value is inserted at the beginning of the + * array; if *ploc == n, it is inserted at the end. + * (4) If the size of %na is 1, insert with an increasing sort. + * </pre> + */ +l_ok +numaFindSortedLoc(NUMA *na, + l_float32 val, + l_int32 *pindex) +{ +l_int32 n, increasing, lindex, rindex, midindex; +l_float32 val0, valn, valmid; + + PROCNAME("numaFindSortedLoc"); + + if (!pindex) + return ERROR_INT("&index not defined", procName, 1); + *pindex = 0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + + n = numaGetCount(na); + if (n == 0) return 0; + numaGetFValue(na, 0, &val0); + if (n == 1) { /* use increasing sort order */ + if (val >= val0) + *pindex = 1; + return 0; + } + + /* ----------------- n >= 2 ----------------- */ + numaGetFValue(na, n - 1, &valn); + increasing = (valn >= val0) ? 1 : 0; /* sort order */ + + /* Check if outside bounds of existing array */ + if (increasing) { + if (val < val0) { + *pindex = 0; + return 0; + } else if (val > valn) { + *pindex = n; + return 0; + } + } else { /* decreasing */ + if (val > val0) { + *pindex = 0; + return 0; + } else if (val < valn) { + *pindex = n; + return 0; + } + } + + /* Within bounds of existing array; search */ + lindex = 0; + rindex = n - 1; + while (1) { + midindex = (lindex + rindex) / 2; + if (midindex == lindex || midindex == rindex) break; + numaGetFValue(na, midindex, &valmid); + if (increasing) { + if (val > valmid) + lindex = midindex; + else + rindex = midindex; + } else { /* decreasing */ + if (val > valmid) + rindex = midindex; + else + lindex = midindex; + } + } + *pindex = rindex; + return 0; +} + + +/*----------------------------------------------------------------------* + * Random permutation * + *----------------------------------------------------------------------*/ +/*! + * \brief numaPseudorandomSequence() + * + * \param[in] size of sequence + * \param[in] seed for random number generation + * \return na pseudorandom on [0,...,size - 1], or NULL on error + * + * <pre> + * Notes: + * (1) This uses the Durstenfeld shuffle. + * See: http://en.wikipedia.org/wiki/Fisher–Yates_shuffle. + * Result is a pseudorandom permutation of the sequence of integers + * from 0 to size - 1. + * </pre> + */ +NUMA * +numaPseudorandomSequence(l_int32 size, + l_int32 seed) +{ +l_int32 i, index, temp; +l_int32 *array; +NUMA *na; + + PROCNAME("numaPseudorandomSequence"); + + if (size <= 0) + return (NUMA *)ERROR_PTR("size <= 0", procName, NULL); + + if ((array = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL) + return (NUMA *)ERROR_PTR("array not made", procName, NULL); + for (i = 0; i < size; i++) + array[i] = i; + srand(seed); + for (i = size - 1; i > 0; i--) { + index = (l_int32)((i + 1) * ((l_float64)rand() / (l_float64)RAND_MAX)); + index = L_MIN(index, i); + temp = array[i]; + array[i] = array[index]; + array[index] = temp; + } + + na = numaCreateFromIArray(array, size); + LEPT_FREE(array); + return na; +} + + +/*! + * \brief numaRandomPermutation() + * + * \param[in] nas input array + * \param[in] seed for random number generation + * \return nas randomly shuffled array, or NULL on error + */ +NUMA * +numaRandomPermutation(NUMA *nas, + l_int32 seed) +{ +l_int32 i, index, size; +l_float32 val; +NUMA *naindex, *nad; + + PROCNAME("numaRandomPermutation"); + + if (!nas) + return (NUMA *)ERROR_PTR("nas not defined", procName, NULL); + if ((size = numaGetCount(nas)) == 0) { + L_WARNING("nas is empty\n", procName); + return numaCopy(nas); + } + + naindex = numaPseudorandomSequence(size, seed); + nad = numaCreate(size); + for (i = 0; i < size; i++) { + numaGetIValue(naindex, i, &index); + numaGetFValue(nas, index, &val); + numaAddNumber(nad, val); + } + numaDestroy(&naindex); + return nad; +} + + +/*----------------------------------------------------------------------* + * Functions requiring sorting * + *----------------------------------------------------------------------*/ +/*! + * \brief numaGetRankValue() + * + * \param[in] na source numa + * \param[in] fract use 0.0 for smallest, 1.0 for largest + * \param[in] nasort [optional] increasing sorted version of na + * \param[in] usebins 0 for general sort; 1 for bin sort + * \param[out] pval rank val + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) Computes the rank value of a number in the %na, which is + * the number that is a fraction %fract from the small + * end of the sorted version of %na. + * (2) If you do this multiple times for different rank values, + * sort the array in advance and use that for %nasort; + * if you're only calling this once, input %nasort == NULL. + * (3) If %usebins == 1, this uses a bin sorting method. + * Use this only where: + * * the numbers are non-negative integers + * * there are over 100 numbers + * * the maximum value is less than about 50,000 + * (4) The advantage of using a bin sort is that it is O(n), + * instead of O(nlogn) for general sort routines. + * </pre> + */ +l_ok +numaGetRankValue(NUMA *na, + l_float32 fract, + NUMA *nasort, + l_int32 usebins, + l_float32 *pval) +{ +l_int32 n, index; +NUMA *nas; + + PROCNAME("numaGetRankValue"); + + if (!pval) + return ERROR_INT("&val not defined", procName, 1); + *pval = 0.0; /* init */ + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na empty", procName, 1); + if (fract < 0.0 || fract > 1.0) + return ERROR_INT("fract not in [0.0 ... 1.0]", procName, 1); + + if (nasort) { + nas = nasort; + } else { + if (usebins == 0) + nas = numaSort(NULL, na, L_SORT_INCREASING); + else + nas = numaBinSort(na, L_SORT_INCREASING); + if (!nas) + return ERROR_INT("nas not made", procName, 1); + } + index = (l_int32)(fract * (l_float32)(n - 1) + 0.5); + numaGetFValue(nas, index, pval); + + if (!nasort) numaDestroy(&nas); + return 0; +} + + +/*! + * \brief numaGetMedian() + * + * \param[in] na source numa + * \param[out] pval median value + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) Computes the median value of the numbers in the numa, by + * sorting and finding the middle value in the sorted array. + * </pre> + */ +l_ok +numaGetMedian(NUMA *na, + l_float32 *pval) +{ + PROCNAME("numaGetMedian"); + + if (!pval) + return ERROR_INT("&val not defined", procName, 1); + *pval = 0.0; /* init */ + if (!na || numaGetCount(na) == 0) + return ERROR_INT("na not defined or empty", procName, 1); + + return numaGetRankValue(na, 0.5, NULL, 0, pval); +} + + +/*! + * \brief numaGetBinnedMedian() + * + * \param[in] na source numa + * \param[out] pval integer median value + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) Computes the median value of the numbers in the numa, + * using bin sort and finding the middle value in the sorted array. + * (2) See numaGetRankValue() for conditions on na for which + * this should be used. Otherwise, use numaGetMedian(). + * </pre> + */ +l_ok +numaGetBinnedMedian(NUMA *na, + l_int32 *pval) +{ +l_int32 ret; +l_float32 fval; + + PROCNAME("numaGetBinnedMedian"); + + if (!pval) + return ERROR_INT("&val not defined", procName, 1); + *pval = 0; /* init */ + if (!na || numaGetCount(na) == 0) + return ERROR_INT("na not defined or empty", procName, 1); + + ret = numaGetRankValue(na, 0.5, NULL, 1, &fval); + *pval = lept_roundftoi(fval); + return ret; +} + + +/*! + * \brief numaGetMeanDevFromMedian() + * + * \param[in] na source numa + * \param[in] med median value + * \param[out] pdev average absolute value deviation from median value + * \return 0 if OK; 1 on error + */ +l_ok +numaGetMeanDevFromMedian(NUMA *na, + l_float32 med, + l_float32 *pdev) +{ +l_int32 i, n; +l_float32 val, dev; + + PROCNAME("numaGetMeanDevFromMedian"); + + if (!pdev) + return ERROR_INT("&dev not defined", procName, 1); + *pdev = 0.0; /* init */ + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + + dev = 0.0; + for (i = 0; i < n; i++) { + numaGetFValue(na, i, &val); + dev += L_ABS(val - med); + } + *pdev = dev / (l_float32)n; + return 0; +} + + +/*! + * \brief numaGetMedianDevFromMedian() + * + * \param[in] na source numa + * \param[out] pmed [optional] median value + * \param[out] pdev median deviation from median val + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) Finds the median of the absolute value of the deviation from + * the median value in the array. Why take the absolute value? + * Consider the case where you have values equally distributed + * about both sides of a median value. Without taking the absolute + * value of the differences, you will get 0 for the deviation, + * and this is not useful. + * </pre> + */ +l_ok +numaGetMedianDevFromMedian(NUMA *na, + l_float32 *pmed, + l_float32 *pdev) +{ +l_int32 n, i; +l_float32 val, med; +NUMA *nadev; + + PROCNAME("numaGetMedianDevFromMedian"); + + if (pmed) *pmed = 0.0; + if (!pdev) + return ERROR_INT("&dev not defined", procName, 1); + *pdev = 0.0; + if (!na || numaGetCount(na) == 0) + return ERROR_INT("na not defined or empty", procName, 1); + + numaGetMedian(na, &med); + if (pmed) *pmed = med; + n = numaGetCount(na); + nadev = numaCreate(n); + for (i = 0; i < n; i++) { + numaGetFValue(na, i, &val); + numaAddNumber(nadev, L_ABS(val - med)); + } + numaGetMedian(nadev, pdev); + + numaDestroy(&nadev); + return 0; +} + + +/*! + * \brief numaGetMode() + * + * \param[in] na source numa + * \param[out] pval mode val + * \param[out] pcount [optional] mode count + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) Computes the mode value of the numbers in the numa, by + * sorting and finding the value of the number with the + * largest count. + * (2) Optionally, also returns that count. + * </pre> + */ +l_ok +numaGetMode(NUMA *na, + l_float32 *pval, + l_int32 *pcount) +{ +l_int32 i, n, maxcount, prevcount; +l_float32 val, maxval, prevval; +l_float32 *array; +NUMA *nasort; + + PROCNAME("numaGetMode"); + + if (pcount) *pcount = 0; + if (!pval) + return ERROR_INT("&val not defined", procName, 1); + *pval = 0.0; + if (!na) + return ERROR_INT("na not defined", procName, 1); + if ((n = numaGetCount(na)) == 0) + return ERROR_INT("na is empty", procName, 1); + + if ((nasort = numaSort(NULL, na, L_SORT_DECREASING)) == NULL) + return ERROR_INT("nas not made", procName, 1); + array = numaGetFArray(nasort, L_NOCOPY); + + /* Initialize with array[0] */ + prevval = array[0]; + prevcount = 1; + maxval = prevval; + maxcount = prevcount; + + /* Scan the sorted array, aggregating duplicates */ + for (i = 1; i < n; i++) { + val = array[i]; + if (val == prevval) { + prevcount++; + } else { /* new value */ + if (prevcount > maxcount) { /* new max */ + maxcount = prevcount; + maxval = prevval; + } + prevval = val; + prevcount = 1; + } + } + + /* Was the mode the last run of elements? */ + if (prevcount > maxcount) { + maxcount = prevcount; + maxval = prevval; + } + + *pval = maxval; + if (pcount) + *pcount = maxcount; + + numaDestroy(&nasort); + return 0; +} + + +/*----------------------------------------------------------------------* + * Rearrangements * + *----------------------------------------------------------------------*/ +/*! + * \brief numaJoin() + * + * \param[in] nad dest numa; add to this one + * \param[in] nas [optional] source numa; add from this one + * \param[in] istart starting index in nas + * \param[in] iend ending index in nas; 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 nas == NULL, this is a no-op + * </pre> + */ +l_ok +numaJoin(NUMA *nad, + NUMA *nas, + l_int32 istart, + l_int32 iend) +{ +l_int32 n, i; +l_float32 val; + + PROCNAME("numaJoin"); + + if (!nad) + return ERROR_INT("nad not defined", procName, 1); + if (!nas) + return 0; + + if (istart < 0) + istart = 0; + n = numaGetCount(nas); + if (iend < 0 || iend >= n) + iend = n - 1; + if (istart > iend) + return ERROR_INT("istart > iend; nothing to add", procName, 1); + + for (i = istart; i <= iend; i++) { + numaGetFValue(nas, i, &val); + numaAddNumber(nad, val); + } + + return 0; +} + + +/*! + * \brief numaaJoin() + * + * \param[in] naad dest naa; add to this one + * \param[in] naas [optional] source naa; add from this one + * \param[in] istart starting index in nas + * \param[in] iend ending index in naas; 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 naas == NULL, this is a no-op + * </pre> + */ +l_ok +numaaJoin(NUMAA *naad, + NUMAA *naas, + l_int32 istart, + l_int32 iend) +{ +l_int32 n, i; +NUMA *na; + + PROCNAME("numaaJoin"); + + if (!naad) + return ERROR_INT("naad not defined", procName, 1); + if (!naas) + return 0; + + if (istart < 0) + istart = 0; + n = numaaGetCount(naas); + if (iend < 0 || iend >= n) + iend = n - 1; + if (istart > iend) + return ERROR_INT("istart > iend; nothing to add", procName, 1); + + for (i = istart; i <= iend; i++) { + na = numaaGetNuma(naas, i, L_CLONE); + numaaAddNuma(naad, na, L_INSERT); + } + + return 0; +} + + +/*! + * \brief numaaFlattenToNuma() + * + * \param[in] naa + * \return numa, or NULL on error + * + * <pre> + * Notes: + * (1) This 'flattens' the Numaa to a Numa, by joining successively + * each Numa in the Numaa. + * (2) It doesn't make any assumptions about the location of the + * Numas in the Numaa array, unlike most Numaa functions. + * (3) It leaves the input Numaa unchanged. + * </pre> + */ +NUMA * +numaaFlattenToNuma(NUMAA *naa) +{ +l_int32 i, nalloc; +NUMA *na, *nad; +NUMA **array; + + PROCNAME("numaaFlattenToNuma"); + + if (!naa) + return (NUMA *)ERROR_PTR("naa not defined", procName, NULL); + + nalloc = naa->nalloc; + array = numaaGetPtrArray(naa); + nad = numaCreate(0); + for (i = 0; i < nalloc; i++) { + na = array[i]; + if (!na) continue; + numaJoin(nad, na, 0, -1); + } + + return nad; +} + |