/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&) constUnexecuted 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&) constUnexecuted 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>&) constUnexecuted 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&) constUnexecuted 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>&) constUnexecuted 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 |