Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/nitf/kdtree.h
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  NITF Read/Write Library
4
 * Purpose:  Pairwise Nearest Neighbor (PNN) clustering for Vector
5
 *           Quantization (VQ) using a KDTree.
6
 * Author:   Even Rouault, even dot rouault at spatialys dot com
7
 *
8
 **********************************************************************
9
 * Copyright (c) 2026, T-Kartor
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#ifndef KDTREE_INCLUDED
15
#define KDTREE_INCLUDED
16
17
/**
18
 * This file implements Pairwise Nearest Neighbor (PNN) clustering for Vector
19
 * Quantization (VQ) using a KDTree.
20
 *
21
 * It implements paper "A New Vector Quantization Clustering Algorithm", by
22
 * William H. Equitz, from IEEE Transactions on Acoustics, Speech, and Signal
23
 * Processing, Vol. 37, Issue 10, October 1989. DOI: 10.1109/29.35395
24
 * https://ieeexplore.ieee.org/document/35395 (behind paywall)
25
 *
26
 * A higher level (freely accessible) and more generic paper on PNN clustering
27
 * is also available at
28
 * https://www.researchgate.net/publication/27661047_Pairwise_Nearest_Neighbor_Method_Revisited
29
 *
30
 * Papers "Analysis of Compression Techniques for Common Mapping Stdandard (CMS)
31
 * Raster Data" by N.J. Markuson, July 1994 (https://apps.dtic.mil/sti/tr/pdf/ADA283396.pdf)
32
 * and "Compression of Digitized Map Image" by D.A. Southard, March 1992
33
 * (https://apps.dtic.mil/sti/tr/pdf/ADA250707.pdf) analyses VQ compression and
34
 * contain a high-level description of the Equitz paper.
35
 */
36
37
#include <cassert>
38
#include <cstdio>
39
40
#include <algorithm>
41
#include <array>
42
#include <deque>
43
#include <iterator>
44
#include <functional>
45
#include <limits>
46
#include <map>
47
#include <memory>
48
#include <set>
49
#include <utility>
50
#include <vector>
51
52
#include "cpl_error.h"
53
54
// #define DEBUG_INVARIANTS
55
56
#ifdef KDTREE_DEBUG_TIMING
57
#include <sys/time.h>
58
59
static double totalTimeRebalancing = 0;
60
static double totalTimeCentroid = 0;
61
static double totalTimeStats = 0;
62
63
#endif
64
65
#if defined(__GNUC__) && !defined(__clang__)
66
#pragma GCC optimize("unroll-loops")
67
#endif
68
69
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(KDTREE_DISABLE_SIMD)
70
#define KDTREE_USE_SSE2
71
#endif
72
73
#ifdef KDTREE_USE_SSE2
74
#include <emmintrin.h>
75
#endif
76
77
/************************************************************************/
78
/*                              Vector<T>                               */
79
/************************************************************************/
80
81
/** "Interface" of a "vector" of dimension DIM_COUNT to insert and cluster in
82
 * a PNNKDTree.
83
 *
84
 * Below functions must be implemented in classes that specialize Vector: there
85
 * is no default generic implementation.
86
 *
87
 * There are no constraints on the T type.
88
 */
89
template <class T> class Vector
90
{
91
  public:
92
    /** Returns the dimension of the vector. */
93
    static constexpr int DIM_COUNT = -1;
94
95
    /** Whether the get() method returns uint8_t values.
96
     * Used for speed optimizations.
97
     */
98
    static constexpr bool getReturnUInt8 = false;
99
100
    /** Returns the k(th) value of the vector, with k in [0, DIM_COUNT-1] range.
101
     *
102
     * The actual returned type might not be double, but must be convertible to
103
     * double.
104
     */
105
    double get(int k, const T &ctxt) const /* = 0 */;
106
107
#ifdef KDTREE_USE_SSE2
108
    /** Whether the computeHeightSumAndSumSquareSSE2() method is implemented.
109
     * Used for speed optimizations.
110
     */
111
    static constexpr bool hasComputeHeightSumAndSumSquareSSE2 = false;
112
113
    /** The function must do the equivalent of:
114
     *
115
     *   for (int i = 0; i < 8; ++i )
116
     *   {
117
     *      int val = item.m_vec.get(k + i, ctxt);
118
     *      int valMulCount = val * item.m_count;
119
     *      {sum0, sum1}[i] += valMulCount;
120
     *      {sumSquare0_lo, sumSquare0_hi,sumSquare1_lo, sumSquare1_hi}[i] += val * valMulCount;
121
     *   }
122
     *
123
     * k is in the [0, DIM_COUNT-8-1] range (and generally a multiple of 8).
124
     */
125
    void computeHeightSumAndSumSquareSSE2(int k, const T &ctxt, int count,
126
                                          __m128i &sum0, __m128i &sumSquare0_lo,
127
                                          __m128i &sumSquare0_hi, __m128i &sum1,
128
                                          __m128i &sumSquare1_lo,
129
                                          __m128i &sumSquare1_hi) const
130
        /* = 0 */;
131
#endif
132
133
    /** Returns the squared distance between this vector and other.
134
     * It must be symmetric, that is this->squared_distance(other, ctx) must
135
     * be equal to other.squared_distance(*this, ctx).
136
     */
137
    double squared_distance(const Vector &other, const T &ctxt) const /* = 0 */;
138
139
    /** Whether the compute_four_squared_distances() method is implemented
140
     * Used for speed optimizations.
141
     */
142
    static constexpr bool hasComputeFourSquaredDistances = false;
143
144
    /** Equivalent of
145
     *
146
     * for(int i = 0; i < 4; ++i)
147
     * {
148
     *      tabSquaredDist[i] = squared_distance(*(other[i]), ctxt);
149
     * }
150
     */
151
    void compute_four_squared_distances(
152
        const std::array<const Vector *const, 4> &others,
153
        std::array<int, 4> & /* out */ tabSquaredDist, const T &ctxt) const
154
        /* = 0 */;
155
156
    /** Computes a new vector that is the centroid of vector a of weight nA,
157
     * and vector b of weight nB.
158
     */
159
    static Vector centroid(const Vector &a, int nA, const Vector &b, int nB,
160
                           const T &ctxt) /* = 0 */;
161
};
162
163
/************************************************************************/
164
/*                            BucketItem<T>                             */
165
/************************************************************************/
166
167
/** Definition of an item placed in a bucket of a PNNKDTree.
168
 *
169
 * This class does not need to be specialized.
170
 */
171
template <class T> struct BucketItem
172
{
173
  public:
174
    /** Value vector */
175
    Vector<T> m_vec;
176
177
    /** Type of elements in m_origVectorIndices */
178
    using IdxType = int;
179
180
    /** Vector that points to indices in the original value space that evaluate
181
     * to m_vec.
182
     * Typically m_origVectorIndices.size() == m_count, but
183
     * the clustering algorithm will not enforce it. It will just concatenate
184
     * m_origVectorIndices from different BucketItem when merging them.
185
     */
186
    std::vector<IdxType> m_origVectorIndices;
187
188
    /** Number of samples that have the value of m_vec */
189
    int m_count;
190
191
    /** Constructor */
192
    BucketItem(const Vector<T> &vec, int count,
193
               std::vector<IdxType> &&origVectorIndices)
194
0
        : m_vec(vec), m_origVectorIndices(std::move(origVectorIndices)),
195
0
          m_count(count)
196
0
    {
197
0
    }
Unexecuted instantiation: BucketItem<ColorTableBased4x4Pixels>::BucketItem(Vector<ColorTableBased4x4Pixels> const&, int, std::__1::vector<int, std::__1::allocator<int> >&&)
Unexecuted instantiation: BucketItem<CADRG_RGB_Type>::BucketItem(Vector<CADRG_RGB_Type> const&, int, std::__1::vector<int, std::__1::allocator<int> >&&)
198
199
0
    BucketItem(BucketItem &&) = default;
Unexecuted instantiation: BucketItem<ColorTableBased4x4Pixels>::BucketItem(BucketItem<ColorTableBased4x4Pixels>&&)
Unexecuted instantiation: BucketItem<CADRG_RGB_Type>::BucketItem(BucketItem<CADRG_RGB_Type>&&)
200
0
    BucketItem &operator=(BucketItem &&) = default;
Unexecuted instantiation: BucketItem<ColorTableBased4x4Pixels>::operator=(BucketItem<ColorTableBased4x4Pixels>&&)
Unexecuted instantiation: BucketItem<CADRG_RGB_Type>::operator=(BucketItem<CADRG_RGB_Type>&&)
201
202
  private:
203
    BucketItem(const BucketItem &) = delete;
204
    BucketItem &operator=(const BucketItem &) = delete;
205
};
206
207
/************************************************************************/
208
/*                             PNNKDTree<T>                             */
209
/************************************************************************/
210
211
/**
212
 * KDTree designed for Pairwise Nearest Neighbor (PNN) clustering for Vector
213
 * Quantization (VQ).
214
 *
215
 * This class does not need to be specialized.
216
 */
217
template <class T> class PNNKDTree
218
{
219
  public:
220
0
    PNNKDTree() = default;
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::PNNKDTree()
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::PNNKDTree()
221
222
    /* Inserts value vectors with their cardinality in the KD-Tree.
223
     *
224
     * This method must be called only once.
225
     *
226
     * Returns the initial count of buckets, that must be passed as an input
227
     * to cluster().
228
     */
229
    int insert(std::vector<BucketItem<T>> &&vectors, const T &ctxt);
230
231
    /** Iterate over leaf nodes (that contain buckets) */
232
    void iterateOverLeaves(const std::function<void(PNNKDTree &)> &f);
233
234
    /** Perform clustering to reduce the number of buckets from initialBucketCount
235
     * to targetCount.
236
     *
237
     * It modifies the tree structure, and returns the achieved number of
238
     * buckets (<= targetCount).
239
     */
240
    int cluster(int initialBucketCount, int targetCount, const T &ctxt);
241
242
    /** Returns the bucket items for this node. */
243
    inline const std::vector<BucketItem<T>> &bucketItems() const
244
    {
245
        return m_bucketItems;
246
    }
247
248
    /** Returns the bucket items for this node. */
249
    inline std::vector<BucketItem<T>> &bucketItems()
250
0
    {
251
0
        return m_bucketItems;
252
0
    }
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::bucketItems()
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::bucketItems()
253
254
  private:
255
    static constexpr int BUCKET_MAX_SIZE = 8;
256
257
    /** Left node. When non null, m_right is also non null, and m_bucketItems is empty. */
258
    std::unique_ptr<PNNKDTree> m_left{};
259
260
    /** Right node. When non null, m_left is also non null, and m_bucketItems is empty. */
261
    std::unique_ptr<PNNKDTree> m_right{};
262
263
    /** Contains items that form a bucket. The bucket is nominally at most BUCKET_MAX_SIZE
264
     * large (maybe transiently slightly larger during clustering operations).
265
     *
266
     * m_bucketItems is non empty only on leaf nodes.
267
     */
268
    std::vector<BucketItem<T>> m_bucketItems{};
269
270
    /** Data type returned by Vector<T>::get() */
271
    using ValType = decltype(std::declval<Vector<T>>().get(
272
        0, *static_cast<const T *>(nullptr)));
273
274
    /** Clean the current node and move it to queueNodes.
275
     *
276
     * This saves dynamic allocation and de-allocation of nodes when rebalancing.
277
     */
278
    void freeAndMoveToQueue(std::deque<std::unique_ptr<PNNKDTree>> &queueNodes);
279
280
    int insert(std::vector<BucketItem<T>> &&vectors, int totalCount,
281
               std::vector<std::pair<ValType, int>> &weightedVals,
282
               std::deque<std::unique_ptr<PNNKDTree>> &queueNodes,
283
               std::vector<BucketItem<T>> &vectLeft,
284
               std::vector<BucketItem<T>> &vectRight, const T &ctxt);
285
286
    /** Rebalance the KD-Tree. Current implementation fully rebuilds a new
287
     * KD-Tree using the insert() algorithm
288
     */
289
    int rebalance(const T &ctxt, std::vector<BucketItem<T>> &newLeaves,
290
                  std::deque<std::unique_ptr<PNNKDTree>> &queueNodes);
291
};
292
293
/************************************************************************/
294
/*                        PNNKDTree<T>::insert()                        */
295
/************************************************************************/
296
297
template <class T>
298
int PNNKDTree<T>::insert(std::vector<BucketItem<T>> &&vectors, const T &ctxt)
299
0
{
300
0
    assert(m_left == nullptr);
301
0
    assert(m_right == nullptr);
302
0
    assert(m_bucketItems.empty());
303
304
0
    int totalCount = 0;
305
0
    for (const auto &it : vectors)
306
0
    {
307
0
        totalCount += it.m_count;
308
0
    }
309
0
    std::vector<std::pair<ValType, int>> weightedVals;
310
0
    std::deque<std::unique_ptr<PNNKDTree>> queueNodes;
311
0
    std::vector<BucketItem<T>> vectLeft;
312
0
    std::vector<BucketItem<T>> vectRight;
313
0
    if (totalCount == 0)
314
0
        return 0;
315
0
    return insert(std::move(vectors), totalCount, weightedVals, queueNodes,
316
0
                  vectLeft, vectRight, ctxt);
317
0
}
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::insert(std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&&, ColorTableBased4x4Pixels const&)
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::insert(std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&&, CADRG_RGB_Type const&)
318
319
/************************************************************************/
320
/*                        PNNKDTree<T>::insert()                        */
321
/************************************************************************/
322
323
template <class T>
324
int PNNKDTree<T>::insert(std::vector<BucketItem<T>> &&vectors, int totalCount,
325
                         std::vector<std::pair<ValType, int>> &weightedVals,
326
                         std::deque<std::unique_ptr<PNNKDTree>> &queueNodes,
327
                         std::vector<BucketItem<T>> &vectLeft,
328
                         std::vector<BucketItem<T>> &vectRight, const T &ctxt)
329
0
{
330
#ifdef DEBUG_INVARIANTS
331
    std::map<Vector<T>, int> mapValuesToBucketIdx;
332
    for (int i = 0; i < static_cast<int>(vectors.size()); ++i)
333
    {
334
        CPLAssert(mapValuesToBucketIdx.find(vectors[i].m_vec) ==
335
                  mapValuesToBucketIdx.end());
336
        mapValuesToBucketIdx[vectors[i].m_vec] = i;
337
    }
338
#endif
339
340
0
    if (vectors.size() <= BUCKET_MAX_SIZE)
341
0
    {
342
0
        m_bucketItems = std::move(vectors);
343
0
        return static_cast<int>(m_bucketItems.size());
344
0
    }
345
346
#ifdef KDTREE_DEBUG_TIMING
347
    struct timeval tv1, tv2;
348
    gettimeofday(&tv1, nullptr);
349
#endif
350
351
    // Find dimension with maximum variance
352
0
    double maxM2 = 0;
353
0
    int maxM2_k = 0;
354
355
0
    for (int k = 0; k < Vector<T>::DIM_COUNT; ++k)
356
0
    {
357
        if constexpr (Vector<T>::getReturnUInt8)
358
0
        {
359
0
            constexpr int MAX_BYTE_VALUE = std::numeric_limits<uint8_t>::max();
360
0
            bool canUseOptimization =
361
0
                (totalCount <= std::numeric_limits<int64_t>::max() /
362
0
                                   (MAX_BYTE_VALUE * MAX_BYTE_VALUE));
363
0
            if (canUseOptimization)
364
0
            {
365
0
                int maxCountPerVector = 0;
366
0
                for (const auto &item : vectors)
367
0
                {
368
0
                    maxCountPerVector =
369
0
                        std::max(maxCountPerVector, item.m_count);
370
0
                }
371
0
                canUseOptimization = (maxCountPerVector <=
372
0
                                      std::numeric_limits<int32_t>::max() /
373
0
                                          (MAX_BYTE_VALUE * MAX_BYTE_VALUE));
374
0
            }
375
0
            if (canUseOptimization)
376
0
            {
377
                // Do statistics computation in integer domain if possible.
378
379
0
#if !(defined(__i386__) || defined(_M_IX86))
380
                // Below code requires more than 8 general purpose registers,
381
                // so exclude i386.
382
383
0
                constexpr int VALS_AT_ONCE = 4;
384
                if constexpr ((Vector<T>::DIM_COUNT % VALS_AT_ONCE) == 0)
385
0
                {
386
0
#ifdef KDTREE_USE_SSE2
387
0
                    constexpr int TWICE_VALS_AT_ONCE = 2 * VALS_AT_ONCE;
388
                    if constexpr ((Vector<T>::DIM_COUNT % TWICE_VALS_AT_ONCE) ==
389
                                      0 &&
390
                                  Vector<
391
                                      T>::hasComputeHeightSumAndSumSquareSSE2)
392
                    {
393
                        __m128i sum0 = _mm_setzero_si128();
394
                        __m128i sumSquare0_lo = _mm_setzero_si128();
395
                        __m128i sumSquare0_hi = _mm_setzero_si128();
396
                        __m128i sum1 = _mm_setzero_si128();
397
                        __m128i sumSquare1_lo = _mm_setzero_si128();
398
                        __m128i sumSquare1_hi = _mm_setzero_si128();
399
400
                        for (const auto &item : vectors)
401
                        {
402
                            item.m_vec.computeHeightSumAndSumSquareSSE2(
403
                                k, ctxt, item.m_count, sum0, sumSquare0_lo,
404
                                sumSquare0_hi, sum1, sumSquare1_lo,
405
                                sumSquare1_hi);
406
                        }
407
                        int64_t sumSquares[TWICE_VALS_AT_ONCE];
408
                        _mm_storeu_si128(
409
                            reinterpret_cast<__m128i *>(sumSquares + 0),
410
                            sumSquare0_lo);
411
                        _mm_storeu_si128(
412
                            reinterpret_cast<__m128i *>(sumSquares + 2),
413
                            sumSquare0_hi);
414
                        _mm_storeu_si128(
415
                            reinterpret_cast<__m128i *>(sumSquares + 4),
416
                            sumSquare1_lo);
417
                        _mm_storeu_si128(
418
                            reinterpret_cast<__m128i *>(sumSquares + 6),
419
                            sumSquare1_hi);
420
                        int sums[TWICE_VALS_AT_ONCE];
421
                        _mm_storeu_si128(reinterpret_cast<__m128i *>(sums),
422
                                         sum0);
423
                        _mm_storeu_si128(
424
                            reinterpret_cast<__m128i *>(sums + VALS_AT_ONCE),
425
                            sum1);
426
                        for (int i = 0; i < TWICE_VALS_AT_ONCE; ++i)
427
                        {
428
                            const double M2 = static_cast<double>(
429
                                sumSquares[i] * totalCount -
430
                                static_cast<int64_t>(sums[i]) * sums[i]);
431
                            if (M2 > maxM2)
432
                            {
433
                                maxM2 = M2;
434
                                maxM2_k = k + i;
435
                            }
436
                        }
437
                        k += TWICE_VALS_AT_ONCE - 1;
438
                    }
439
                    else
440
#endif
441
0
                    {
442
0
                        int sum0 = 0;
443
0
                        int sum1 = 0;
444
0
                        int sum2 = 0;
445
0
                        int sum3 = 0;
446
0
                        int64_t sumSquare0 = 0;
447
0
                        int64_t sumSquare1 = 0;
448
0
                        int64_t sumSquare2 = 0;
449
0
                        int64_t sumSquare3 = 0;
450
0
                        for (const auto &item : vectors)
451
0
                        {
452
0
                            const int val0 = item.m_vec.get(k + 0, ctxt);
453
0
                            const int val1 = item.m_vec.get(k + 1, ctxt);
454
0
                            const int val2 = item.m_vec.get(k + 2, ctxt);
455
0
                            const int val3 = item.m_vec.get(k + 3, ctxt);
456
0
                            const int val0MulCount = val0 * item.m_count;
457
0
                            const int val1MulCount = val1 * item.m_count;
458
0
                            const int val2MulCount = val2 * item.m_count;
459
0
                            const int val3MulCount = val3 * item.m_count;
460
0
                            sum0 += val0MulCount;
461
0
                            sum1 += val1MulCount;
462
0
                            sum2 += val2MulCount;
463
0
                            sum3 += val3MulCount;
464
                            // It's fine to cast to int64 after multiplication
465
0
                            sumSquare0 +=
466
0
                                cpl::fits_on<int64_t>(val0 * val0MulCount);
467
0
                            sumSquare1 +=
468
0
                                cpl::fits_on<int64_t>(val1 * val1MulCount);
469
0
                            sumSquare2 +=
470
0
                                cpl::fits_on<int64_t>(val2 * val2MulCount);
471
0
                            sumSquare3 +=
472
0
                                cpl::fits_on<int64_t>(val3 * val3MulCount);
473
0
                        }
474
475
0
                        const double M2[] = {
476
0
                            static_cast<double>(sumSquare0 * totalCount -
477
0
                                                static_cast<int64_t>(sum0) *
478
0
                                                    sum0),
479
0
                            static_cast<double>(sumSquare1 * totalCount -
480
0
                                                static_cast<int64_t>(sum1) *
481
0
                                                    sum1),
482
0
                            static_cast<double>(sumSquare2 * totalCount -
483
0
                                                static_cast<int64_t>(sum2) *
484
0
                                                    sum2),
485
0
                            static_cast<double>(sumSquare3 * totalCount -
486
0
                                                static_cast<int64_t>(sum3) *
487
0
                                                    sum3)};
488
0
                        for (int i = 0; i < VALS_AT_ONCE; ++i)
489
0
                        {
490
0
                            if (M2[i] > maxM2)
491
0
                            {
492
0
                                maxM2 = M2[i];
493
0
                                maxM2_k = k + i;
494
0
                            }
495
0
                        }
496
0
                        k += VALS_AT_ONCE - 1;
497
0
                    }
498
                }
499
                else
500
#endif
501
0
                {
502
0
                    int sum = 0;
503
0
                    int64_t sumSquare = 0;
504
0
                    for (const auto &item : vectors)
505
0
                    {
506
0
                        const int val = item.m_vec.get(k, ctxt);
507
0
                        const int valMulCount = val * item.m_count;
508
0
                        sum += valMulCount;
509
                        // It's fine to cast to int64 after multiplication
510
0
                        sumSquare += cpl::fits_on<int64_t>(val * valMulCount);
511
0
                    }
512
0
                    const double M2 =
513
0
                        static_cast<double>(sumSquare * totalCount -
514
0
                                            static_cast<int64_t>(sum) * sum);
515
0
                    if (M2 > maxM2)
516
0
                    {
517
0
                        maxM2 = M2;
518
0
                        maxM2_k = k;
519
0
                    }
520
0
                }
521
0
                continue;
522
0
            }
523
0
        }
524
525
        // Generic code path:
526
527
        // First pass to compute mean value along k(th) dimension
528
0
        double sum = 0;
529
0
        for (const auto &item : vectors)
530
0
        {
531
0
            sum += static_cast<double>(item.m_vec.get(k, ctxt)) * item.m_count;
532
0
        }
533
0
        const double mean = sum / totalCount;
534
        // Second pass to compute M2 value (n * variance) along k(th) dimension
535
0
        double M2 = 0;
536
0
        for (const auto &item : vectors)
537
0
        {
538
0
            const double delta =
539
0
                static_cast<double>(item.m_vec.get(k, ctxt)) - mean;
540
0
            M2 += delta * delta * item.m_count;
541
0
        }
542
0
        if (M2 > maxM2)
543
0
        {
544
0
            maxM2 = M2;
545
0
            maxM2_k = k;
546
0
        }
547
0
    }
548
549
#ifdef KDTREE_DEBUG_TIMING
550
    gettimeofday(&tv2, nullptr);
551
    totalTimeStats +=
552
        (tv2.tv_sec + tv2.tv_usec * 1e-6) - (tv1.tv_sec + tv1.tv_usec * 1e-6);
553
#endif
554
555
    // Find median value along that dimension
556
0
    weightedVals.reserve(vectors.size());
557
0
    weightedVals.clear();
558
0
    for (const auto &item : vectors)
559
0
    {
560
0
        const auto d = item.m_vec.get(maxM2_k, ctxt);
561
0
        weightedVals.emplace_back(d, item.m_count);
562
0
    }
563
564
0
    std::sort(weightedVals.begin(), weightedVals.end(),
565
0
              [](const auto &a, const auto &b) { return a.first < b.first; });
Unexecuted instantiation: auto PNNKDTree<ColorTableBased4x4Pixels>::insert(std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&&, int, std::__1::vector<std::__1::pair<int, int>, std::__1::allocator<std::__1::pair<int, int> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > > > >&, std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&, std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&, ColorTableBased4x4Pixels const&)::{lambda(auto:1 const&, auto:2 const&)#1}::operator()<std::__1::pair<int, int>, std::__1::pair<int, int> >(std::__1::pair<int, int> const&, std::__1::pair<int, int> const&) const
Unexecuted instantiation: auto PNNKDTree<CADRG_RGB_Type>::insert(std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&&, int, std::__1::vector<std::__1::pair<int, int>, std::__1::allocator<std::__1::pair<int, int> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > > > >&, std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&, std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&, CADRG_RGB_Type const&)::{lambda(auto:1 const&, auto:2 const&)#1}::operator()<std::__1::pair<int, int>, std::__1::pair<int, int> >(std::__1::pair<int, int> const&, std::__1::pair<int, int> const&) const
566
567
0
    auto median = weightedVals[0].first;
568
0
    int cumulativeCount = 0;
569
0
    const int targetCount = totalCount / 2;
570
0
    for (const auto &[value, count] : weightedVals)
571
0
    {
572
0
        cumulativeCount += count;
573
0
        if (cumulativeCount > targetCount)
574
0
        {
575
0
            median = value;
576
0
            break;
577
0
        }
578
0
    }
579
580
    // Split the original vectors in a "left" half that is below or equal to
581
    // the median and a "right" half that is above.
582
0
    vectLeft.clear();
583
0
    vectLeft.reserve(weightedVals.size() / 2);
584
0
    vectRight.clear();
585
0
    vectRight.reserve(weightedVals.size() / 2);
586
0
    int countLeft = 0;
587
0
    int countRight = 0;
588
0
    for (auto &item : vectors)
589
0
    {
590
0
        if (item.m_vec.get(maxM2_k, ctxt) <= median)
591
0
        {
592
0
            countLeft += item.m_count;
593
0
            vectLeft.push_back(std::move(item));
594
0
        }
595
0
        else
596
0
        {
597
0
            countRight += item.m_count;
598
0
            vectRight.push_back(std::move(item));
599
0
        }
600
0
    }
601
602
    // In some cases, the median can actually be the maximum value
603
    // Then, retry but excluding the median itself.
604
0
    if (vectLeft.empty() || vectRight.empty())
605
0
    {
606
0
        if (!vectLeft.empty())
607
0
            vectors = std::move(vectLeft);
608
0
        else
609
0
            vectors = std::move(vectRight);
610
0
        vectLeft.clear();
611
0
        vectRight.clear();
612
0
        countLeft = 0;
613
0
        countRight = 0;
614
0
        for (auto &item : vectors)
615
0
        {
616
0
            if (item.m_vec.get(maxM2_k, ctxt) < median)
617
0
            {
618
0
                countLeft += item.m_count;
619
0
                vectLeft.push_back(std::move(item));
620
0
            }
621
0
            else
622
0
            {
623
0
                countRight += item.m_count;
624
0
                vectRight.push_back(std::move(item));
625
0
            }
626
0
        }
627
628
        // Normally we shouldn't reach that point, unless the initial samples
629
        // where all identical, and thus clustering wasn't needed.
630
0
        if (vectLeft.empty() || vectRight.empty())
631
0
        {
632
0
            CPLError(CE_Failure, CPLE_AppDefined,
633
0
                     "Unexpected situation in %s:%d\n", __FILE__, __LINE__);
634
0
            return 0;
635
0
        }
636
0
    }
637
0
    vectors.clear();
638
639
    // Allocate (or recycle) left and right nodes
640
0
    if (!queueNodes.empty())
641
0
    {
642
0
        m_left = std::move(queueNodes.back());
643
0
        queueNodes.pop_back();
644
0
    }
645
0
    else
646
0
        m_left = std::make_unique<PNNKDTree<T>>();
647
648
0
    if (!queueNodes.empty())
649
0
    {
650
0
        m_right = std::move(queueNodes.back());
651
0
        queueNodes.pop_back();
652
0
    }
653
0
    else
654
0
        m_right = std::make_unique<PNNKDTree<T>>();
655
656
    // Recursively insert vectLeft in m_left and vectRight in m_right
657
0
    std::vector<BucketItem<T>> vectTmp;
658
    // Sort for replicability of results across platforms
659
0
    const auto sortFunc = [](const BucketItem<T> &a, const BucketItem<T> &b)
660
0
    { return a.m_vec < b.m_vec; };
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::insert(std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&&, int, std::__1::vector<std::__1::pair<int, int>, std::__1::allocator<std::__1::pair<int, int> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > > > >&, std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&, std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&, ColorTableBased4x4Pixels const&)::{lambda(BucketItem<ColorTableBased4x4Pixels> const&, BucketItem<ColorTableBased4x4Pixels> const&)#1}::operator()(BucketItem<ColorTableBased4x4Pixels> const&, BucketItem<ColorTableBased4x4Pixels> const&) const
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::insert(std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&&, int, std::__1::vector<std::__1::pair<int, int>, std::__1::allocator<std::__1::pair<int, int> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > > > >&, std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&, std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&, CADRG_RGB_Type const&)::{lambda(BucketItem<CADRG_RGB_Type> const&, BucketItem<CADRG_RGB_Type> const&)#1}::operator()(BucketItem<CADRG_RGB_Type> const&, BucketItem<CADRG_RGB_Type> const&) const
661
0
    std::sort(vectLeft.begin(), vectLeft.end(), sortFunc);
662
0
    std::sort(vectRight.begin(), vectRight.end(), sortFunc);
663
0
    int retLeft = m_left->insert(std::move(vectLeft), countLeft, weightedVals,
664
0
                                 queueNodes, vectors, vectTmp, ctxt);
665
0
    int retRight =
666
0
        m_right->insert(std::move(vectRight), countRight, weightedVals,
667
0
                        queueNodes, vectors, vectTmp, ctxt);
668
0
    vectLeft = std::vector<BucketItem<T>>();
669
0
    vectRight = std::vector<BucketItem<T>>();
670
0
    return (retLeft == 0 || retRight == 0) ? 0 : retLeft + retRight;
671
0
}
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::insert(std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&&, int, std::__1::vector<std::__1::pair<int, int>, std::__1::allocator<std::__1::pair<int, int> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > > > >&, std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&, std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&, ColorTableBased4x4Pixels const&)
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::insert(std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&&, int, std::__1::vector<std::__1::pair<int, int>, std::__1::allocator<std::__1::pair<int, int> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > > > >&, std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&, std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&, CADRG_RGB_Type const&)
672
673
/************************************************************************/
674
/*                  PNNKDTree<T>::iterateOverLeaves()                   */
675
/************************************************************************/
676
677
template <class T>
678
void PNNKDTree<T>::iterateOverLeaves(const std::function<void(PNNKDTree &)> &f)
679
0
{
680
0
    if (m_left && m_right)
681
0
    {
682
0
        m_left->iterateOverLeaves(f);
683
0
        m_right->iterateOverLeaves(f);
684
0
    }
685
0
    else
686
0
    {
687
0
        f(*this);
688
0
    }
689
0
}
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::iterateOverLeaves(std::__1::function<void (PNNKDTree<ColorTableBased4x4Pixels>&)> const&)
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::iterateOverLeaves(std::__1::function<void (PNNKDTree<CADRG_RGB_Type>&)> const&)
690
691
/************************************************************************/
692
/*                       PNNKDTree<T>::cluster()                        */
693
/************************************************************************/
694
695
template <class T>
696
int PNNKDTree<T>::cluster(int initialBucketCount, int targetCount,
697
                          const T &ctxt)
698
0
{
699
0
    int curBucketCount = initialBucketCount;
700
701
0
    std::vector<BucketItem<T>> newLeaves;
702
0
    newLeaves.reserve(initialBucketCount);
703
0
    std::deque<std::unique_ptr<PNNKDTree>> queueNodes;
704
705
0
    struct TupleInfo
706
0
    {
707
0
        PNNKDTree *bucket;
708
0
        int i;
709
0
        int j;
710
0
        double increasedDistortion;
711
0
    };
712
713
0
    std::vector<TupleInfo> distCollector;
714
0
    distCollector.reserve(curBucketCount);
715
716
0
    int iter = 0;
717
#ifdef DEBUG_INVARIANTS
718
    std::map<Vector<T>, int> mapValuesToBucketIdx;
719
#endif
720
0
    while (curBucketCount > targetCount)
721
0
    {
722
        /* For each bucket (leaf node), compute the increase in distortion
723
         * that would result in merging each (i,j) vector it contains.
724
         */
725
0
        distCollector.clear();
726
0
        iterateOverLeaves(
727
0
            [&distCollector, &ctxt](PNNKDTree &bucket)
728
0
            {
729
0
                const int itemsCount =
730
0
                    static_cast<int>(bucket.m_bucketItems.size());
731
0
                for (int i = 0; i < itemsCount - 1; ++i)
732
0
                {
733
0
                    const auto &itemI = bucket.m_bucketItems[i];
734
0
                    int j = i + 1;
735
                    if constexpr (Vector<T>::hasComputeFourSquaredDistances)
736
0
                    {
737
0
                        constexpr int CHUNK_SIZE = 4;
738
0
                        if (j + CHUNK_SIZE <= itemsCount)
739
0
                        {
740
0
                            std::array<const Vector<T> *const, CHUNK_SIZE>
741
0
                                otherVectors = {
742
0
                                    &(bucket.m_bucketItems[j + 0].m_vec),
743
0
                                    &(bucket.m_bucketItems[j + 1].m_vec),
744
0
                                    &(bucket.m_bucketItems[j + 2].m_vec),
745
0
                                    &(bucket.m_bucketItems[j + 3].m_vec)};
746
0
                            std::array<int, CHUNK_SIZE> tabSquaredDist;
747
0
                            itemI.m_vec.compute_four_squared_distances(
748
0
                                otherVectors, tabSquaredDist, ctxt);
749
0
                            for (int subj = 0; subj < CHUNK_SIZE; ++subj)
750
0
                            {
751
0
                                const auto &itemJ =
752
0
                                    bucket.m_bucketItems[j + subj];
753
0
                                const double increasedDistortion =
754
0
                                    static_cast<double>(itemI.m_count) *
755
0
                                    itemJ.m_count * tabSquaredDist[subj] /
756
0
                                    (itemI.m_count + itemJ.m_count);
757
0
                                TupleInfo ti;
758
0
                                ti.bucket = &bucket;
759
0
                                ti.i = i;
760
0
                                ti.j = j + subj;
761
0
                                ti.increasedDistortion = increasedDistortion;
762
0
                                distCollector.push_back(std::move(ti));
763
0
                            }
764
765
0
                            j += CHUNK_SIZE;
766
0
                        }
767
0
                    }
768
0
                    for (; j < itemsCount; ++j)
769
0
                    {
770
0
                        const auto &itemJ = bucket.m_bucketItems[j];
771
0
                        const double increasedDistortion =
772
0
                            static_cast<double>(itemI.m_count) * itemJ.m_count *
773
0
                            itemI.m_vec.squared_distance(itemJ.m_vec, ctxt) /
774
0
                            (itemI.m_count + itemJ.m_count);
775
0
                        TupleInfo ti;
776
0
                        ti.bucket = &bucket;
777
0
                        ti.i = i;
778
0
                        ti.j = j;
779
0
                        ti.increasedDistortion = increasedDistortion;
780
0
                        distCollector.push_back(std::move(ti));
781
0
                    }
782
0
                }
783
0
            });
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::cluster(int, int, ColorTableBased4x4Pixels const&)::{lambda(PNNKDTree<ColorTableBased4x4Pixels>&)#1}::operator()(PNNKDTree<ColorTableBased4x4Pixels>&) const
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::cluster(int, int, CADRG_RGB_Type const&)::{lambda(PNNKDTree<CADRG_RGB_Type>&)#1}::operator()(PNNKDTree<CADRG_RGB_Type>&) const
784
785
        /** Identify the median of the increased distortion */
786
0
        const int bucketCountToMerge =
787
0
            std::min(static_cast<int>(distCollector.size() / 2),
788
0
                     curBucketCount - targetCount);
789
0
        const auto sortFunc = [](const TupleInfo &a, const TupleInfo &b)
790
0
        {
791
0
            return a.increasedDistortion < b.increasedDistortion ||
792
0
                   (a.increasedDistortion == b.increasedDistortion &&
793
0
                    (a.bucket->m_bucketItems[0].m_vec <
794
0
                         b.bucket->m_bucketItems[0].m_vec ||
795
0
                     (a.bucket->m_bucketItems[0].m_vec ==
796
0
                          b.bucket->m_bucketItems[0].m_vec &&
797
0
                      (a.i < b.i || (a.i == b.i && a.j < b.j)))));
798
0
        };
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::cluster(int, int, ColorTableBased4x4Pixels const&)::{lambda(PNNKDTree<ColorTableBased4x4Pixels>::cluster(int, int, ColorTableBased4x4Pixels const&)::TupleInfo const&, PNNKDTree<ColorTableBased4x4Pixels>::cluster(int, int, ColorTableBased4x4Pixels const&)::TupleInfo const&)#1}::operator()(PNNKDTree<ColorTableBased4x4Pixels>::cluster(int, int, ColorTableBased4x4Pixels const&)::TupleInfo const&, PNNKDTree<ColorTableBased4x4Pixels>::cluster(int, int, ColorTableBased4x4Pixels const&)::TupleInfo const&) const
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::cluster(int, int, CADRG_RGB_Type const&)::{lambda(PNNKDTree<CADRG_RGB_Type>::cluster(int, int, CADRG_RGB_Type const&)::TupleInfo const&, PNNKDTree<CADRG_RGB_Type>::cluster(int, int, CADRG_RGB_Type const&)::TupleInfo const&)#1}::operator()(PNNKDTree<CADRG_RGB_Type>::cluster(int, int, CADRG_RGB_Type const&)::TupleInfo const&, PNNKDTree<CADRG_RGB_Type>::cluster(int, int, CADRG_RGB_Type const&)::TupleInfo const&) const
799
0
        const auto median_iter = distCollector.begin() + bucketCountToMerge;
800
0
        std::nth_element(distCollector.begin(), median_iter,
801
0
                         distCollector.end(), sortFunc);
802
        /** Sort elements by increasing increasedDistortion, but only for
803
         * the first half of the array
804
         */
805
0
        std::sort(distCollector.begin(), median_iter, sortFunc);
806
807
0
        static_assert(BUCKET_MAX_SIZE <= sizeof(uint32_t) * 8);
808
0
        std::map<PNNKDTree *, uint32_t> invalidatedClusters;
809
810
0
        int expectedBucketCount = curBucketCount;
811
812
        // Merge all the tuple of vectors whose increased distortion is lower
813
        // than the median.
814
0
        for (auto oIterCollector = distCollector.begin();
815
0
             oIterCollector != median_iter; ++oIterCollector)
816
0
        {
817
0
            const auto &tupleInfo = *oIterCollector;
818
            // assert( tupleInfo.increasedDistortion <= median_iter->increasedDistortion );
819
0
            auto oIter = invalidatedClusters.find(tupleInfo.bucket);
820
0
            if (oIter != invalidatedClusters.end())
821
0
            {
822
                // Be careful not to merge a (i,j) tuple whose at least one of
823
                // the element has already been merged.
824
                // (this aspect is not covered by the Equitz's paper)
825
0
                if ((oIter->second &
826
0
                     ((1U << tupleInfo.i) | (1U << tupleInfo.j))) != 0)
827
0
                {
828
0
                    continue;
829
0
                }
830
0
            }
831
0
            else
832
0
            {
833
0
                oIter =
834
0
                    invalidatedClusters.insert(std::pair(tupleInfo.bucket, 0))
835
0
                        .first;
836
0
            }
837
838
0
            auto &bucketItemI = tupleInfo.bucket->m_bucketItems[tupleInfo.i];
839
0
            const auto &bucketItemJ =
840
0
                tupleInfo.bucket->m_bucketItems[tupleInfo.j];
841
0
            auto origVectorIndices = std::move(bucketItemI.m_origVectorIndices);
842
0
            origVectorIndices.insert(origVectorIndices.end(),
843
0
                                     bucketItemJ.m_origVectorIndices.begin(),
844
0
                                     bucketItemJ.m_origVectorIndices.end());
845
846
#ifdef KDTREE_DEBUG_TIMING
847
            struct timeval tv1, tv2;
848
            gettimeofday(&tv1, nullptr);
849
#endif
850
0
            auto newVal = Vector<T>::centroid(
851
0
                bucketItemI.m_vec, bucketItemI.m_count, bucketItemJ.m_vec,
852
0
                bucketItemJ.m_count, ctxt);
853
854
#ifdef KDTREE_DEBUG_TIMING
855
            gettimeofday(&tv2, nullptr);
856
            totalTimeCentroid += (tv2.tv_sec + tv2.tv_usec * 1e-6) -
857
                                 (tv1.tv_sec + tv1.tv_usec * 1e-6);
858
#endif
859
860
            // Look if there is an existing item in the bucket with the new
861
            // vector value
862
0
            int bucketItemIdx = -1;
863
0
            for (int i = 0;
864
0
                 i < static_cast<int>(tupleInfo.bucket->m_bucketItems.size());
865
0
                 ++i)
866
0
            {
867
0
                if ((oIter->second & (1U << i)) == 0 &&
868
0
                    tupleInfo.bucket->m_bucketItems[i].m_vec == newVal)
869
0
                {
870
0
                    bucketItemIdx = i;
871
0
                    break;
872
0
                }
873
0
            }
874
0
            oIter->second |= ((1U << tupleInfo.i) | (1U << tupleInfo.j));
875
0
            int newCount = bucketItemI.m_count + bucketItemJ.m_count;
876
0
            if (bucketItemIdx >= 0 && bucketItemIdx != tupleInfo.i &&
877
0
                bucketItemIdx != tupleInfo.j)
878
0
            {
879
0
                oIter->second |= ((1U << bucketItemIdx));
880
0
                auto &existingItem =
881
0
                    tupleInfo.bucket->m_bucketItems[bucketItemIdx];
882
0
                newCount += existingItem.m_count;
883
0
                origVectorIndices.insert(
884
0
                    origVectorIndices.end(),
885
0
                    std::make_move_iterator(
886
0
                        existingItem.m_origVectorIndices.begin()),
887
0
                    std::make_move_iterator(
888
0
                        existingItem.m_origVectorIndices.end()));
889
0
            }
890
            // Insert the new bucket item
891
0
            tupleInfo.bucket->m_bucketItems.emplace_back(
892
0
                std::move(newVal), newCount, std::move(origVectorIndices));
893
894
0
            --expectedBucketCount;
895
0
        }
896
897
        // Remove items that have been merged
898
0
        for (auto [node, indices] : invalidatedClusters)
899
0
        {
900
            // Inside a same bucket, be careful to remove from the end so that
901
            // noted indices are still valid...
902
0
            for (int i = static_cast<int>(node->m_bucketItems.size()) - 1;
903
0
                 i >= 0; --i)
904
0
            {
905
0
                if ((indices & (1U << i)) != 0)
906
0
                {
907
0
                    node->m_bucketItems.erase(node->m_bucketItems.begin() + i);
908
0
                }
909
0
            }
910
911
#ifdef DEBUG_INVARIANTS
912
            mapValuesToBucketIdx.clear();
913
            for (int i = 0; i < static_cast<int>(node->m_bucketItems.size());
914
                 ++i)
915
            {
916
                CPLAssert(
917
                    mapValuesToBucketIdx.find(node->m_bucketItems[i].m_vec) ==
918
                    mapValuesToBucketIdx.end());
919
                mapValuesToBucketIdx[node->m_bucketItems[i].m_vec] = i;
920
            }
921
#endif
922
0
        }
923
924
        // Rebalance the tree only half of the time, to speed up things a bit
925
        // This is quite arbitrary. Systematic rebalancing could result in
926
        // slightly better results.
927
0
        if ((iter % 2) == 0)
928
0
            curBucketCount = expectedBucketCount;
929
0
        else
930
0
            curBucketCount = rebalance(ctxt, newLeaves, queueNodes);
931
0
        ++iter;
932
0
    }
933
934
0
    return curBucketCount;
935
0
}
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::cluster(int, int, ColorTableBased4x4Pixels const&)
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::cluster(int, int, CADRG_RGB_Type const&)
936
937
/************************************************************************/
938
/*                  PNNKDTree<T>::freeAndMoveToQueue()                  */
939
/************************************************************************/
940
941
template <class T>
942
void PNNKDTree<T>::freeAndMoveToQueue(
943
    std::deque<std::unique_ptr<PNNKDTree>> &queueNodes)
944
0
{
945
0
    m_bucketItems.clear();
946
0
    if (m_left)
947
0
    {
948
0
        m_left->freeAndMoveToQueue(queueNodes);
949
0
        queueNodes.push_back(std::move(m_left));
950
0
    }
951
0
    if (m_right)
952
0
    {
953
0
        m_right->freeAndMoveToQueue(queueNodes);
954
0
        queueNodes.push_back(std::move(m_right));
955
0
    }
956
0
}
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::freeAndMoveToQueue(std::__1::deque<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > > > >&)
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::freeAndMoveToQueue(std::__1::deque<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > > > >&)
957
958
/************************************************************************/
959
/*                      PNNKDTree<T>::rebalance()                       */
960
/************************************************************************/
961
962
template <class T>
963
int PNNKDTree<T>::rebalance(const T &ctxt,
964
                            std::vector<BucketItem<T>> &newLeaves,
965
                            std::deque<std::unique_ptr<PNNKDTree>> &queueNodes)
966
0
{
967
0
    if (m_left && m_right)
968
0
    {
969
#ifdef KDTREE_DEBUG_TIMING
970
        struct timeval tv1, tv2;
971
        gettimeofday(&tv1, nullptr);
972
#endif
973
0
        std::map<Vector<T>,
974
0
                 std::pair<int, std::vector<typename BucketItem<T>::IdxType>>>
975
0
            mapVectors;
976
0
        int totalCount = 0;
977
        // Rebuild a new map of vector values -> (count, indices)
978
        // This needs to be a map as we cannot guarantee the uniqueness
979
        // of vector values after the clustering pass
980
0
        iterateOverLeaves(
981
0
            [&mapVectors, &totalCount](PNNKDTree &bucket)
982
0
            {
983
0
                for (auto &item : bucket.m_bucketItems)
984
0
                {
985
0
                    totalCount += item.m_count;
986
0
                    auto oIter = mapVectors.find(item.m_vec);
987
0
                    if (oIter == mapVectors.end())
988
0
                    {
989
0
                        mapVectors[item.m_vec] = std::make_pair(
990
0
                            item.m_count, std::move(item.m_origVectorIndices));
991
0
                    }
992
0
                    else
993
0
                    {
994
0
                        oIter->second.first += item.m_count;
995
0
                        oIter->second.second.insert(
996
0
                            oIter->second.second.end(),
997
0
                            std::make_move_iterator(
998
0
                                item.m_origVectorIndices.begin()),
999
0
                            std::make_move_iterator(
1000
0
                                item.m_origVectorIndices.end()));
1001
0
                    }
1002
0
                }
1003
0
            });
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::rebalance(ColorTableBased4x4Pixels const&, std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > > > >&)::{lambda(PNNKDTree<ColorTableBased4x4Pixels>&)#1}::operator()(PNNKDTree<ColorTableBased4x4Pixels>&) const
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::rebalance(CADRG_RGB_Type const&, std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > > > >&)::{lambda(PNNKDTree<CADRG_RGB_Type>&)#1}::operator()(PNNKDTree<CADRG_RGB_Type>&) const
1004
1005
0
        freeAndMoveToQueue(queueNodes);
1006
1007
        // Convert the map to an array
1008
0
        newLeaves.clear();
1009
0
        for (auto &[key, value] : mapVectors)
1010
0
        {
1011
0
            newLeaves.emplace_back(std::move(key), value.first,
1012
0
                                   std::move(value.second));
1013
0
        }
1014
1015
0
        std::vector<std::pair<ValType, int>> weightedVals;
1016
0
        std::vector<BucketItem<T>> vectLeft;
1017
0
        std::vector<BucketItem<T>> vectRight;
1018
0
        const int ret = insert(std::move(newLeaves), totalCount, weightedVals,
1019
0
                               queueNodes, vectLeft, vectRight, ctxt);
1020
#ifdef KDTREE_DEBUG_TIMING
1021
        gettimeofday(&tv2, nullptr);
1022
        totalTimeRebalancing += (tv2.tv_sec + tv2.tv_usec * 1e-6) -
1023
                                (tv1.tv_sec + tv1.tv_usec * 1e-6);
1024
#endif
1025
0
        newLeaves = std::vector<BucketItem<T>>();
1026
0
        return ret;
1027
0
    }
1028
0
    else
1029
0
    {
1030
0
        return static_cast<int>(m_bucketItems.size());
1031
0
    }
1032
0
}
Unexecuted instantiation: PNNKDTree<ColorTableBased4x4Pixels>::rebalance(ColorTableBased4x4Pixels const&, std::__1::vector<BucketItem<ColorTableBased4x4Pixels>, std::__1::allocator<BucketItem<ColorTableBased4x4Pixels> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<ColorTableBased4x4Pixels>, std::__1::default_delete<PNNKDTree<ColorTableBased4x4Pixels> > > > >&)
Unexecuted instantiation: PNNKDTree<CADRG_RGB_Type>::rebalance(CADRG_RGB_Type const&, std::__1::vector<BucketItem<CADRG_RGB_Type>, std::__1::allocator<BucketItem<CADRG_RGB_Type> > >&, std::__1::deque<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > >, std::__1::allocator<std::__1::unique_ptr<PNNKDTree<CADRG_RGB_Type>, std::__1::default_delete<PNNKDTree<CADRG_RGB_Type> > > > >&)
1033
1034
#endif  // KDTREE_INCLUDED