OpenVDB 12.1.0
Loading...
Searching...
No Matches
PrincipalComponentAnalysisImpl.h
Go to the documentation of this file.
1// Copyright Contributors to the OpenVDB Project
2// SPDX-License-Identifier: MPL-2.0
3
4#ifndef OPENVDB_POINTS_POINT_PRINCIPAL_COMPONENT_ANALYSIS_IMPL_HAS_BEEN_INCLUDED
5#define OPENVDB_POINTS_POINT_PRINCIPAL_COMPONENT_ANALYSIS_IMPL_HAS_BEEN_INCLUDED
6
7/// when enabled, prints timings for each substep of the PCA algorithm
8#ifdef OPENVDB_PROFILE_PCA
10#endif
11
12/// @brief Experimental option to skip storing the self weight for the
13/// weighted PCA when set to 0
14/// @todo Decide what's more correct and remove this. Self contributions aren't
15/// guaranteed with maxSourcePointsPerVoxel/maxTargetPointsPerVoxel
16#define OPENVDB_PCA_SELF_CONTRIBUTION 1
17
18namespace openvdb {
20namespace OPENVDB_VERSION_NAME {
21namespace points {
22
23namespace pca_internal {
24
25#ifdef OPENVDB_PROFILE_PCA
27#else
28struct NoTimer {
29inline void start(const char*) {}
30inline void stop() {}
31};
33#endif
34
35struct PcaTimer final : public TimerT
36{
38 : TimerT()
39 , mInterrupt(interrupt) {}
40 inline void start(const char* msg)
41 {
42 TimerT::start(msg);
43 if (mInterrupt) mInterrupt->start(msg);
44 }
45 inline void stop()
46 {
48 if (mInterrupt) mInterrupt->end();
49 }
51};
52
53using WeightSumT = double;
56
65
66template <typename T, typename LeafNodeT>
67inline T* initPcaArrayAttribute(LeafNodeT& leaf, const size_t idx, const bool fill = true)
68{
69 auto& array = leaf.attributeArray(idx);
70 OPENVDB_ASSERT(array.valueType() == typeNameAsString<T>());
71 OPENVDB_ASSERT(array.codecType() == std::string(NullCodec::name()));
72 array.expand(fill); // @note does nothing if array is already not uniform
73 const char* data = array.constDataAsByteArray();
74 return reinterpret_cast<T*>(const_cast<char*>(data));
75}
76
77/// @note The PCA transfer modules are broken into two separate steps, the
78/// first which computes the weighted neighbourhoods of points and the second
79/// which computes the covariance matrices. Both these steps perform an
80/// identical loop over each points neighbourhood, with the second being
81/// necessary to re-compute the exact positions and per position weights.
82/// In theory, only a single loop is required; each point could instead
83/// create and append a large list of the exact neighbourhood positions and
84/// weights that impact it and use these to compute the covariance after the
85/// first neighbourhood loop has completed (a true gather style approach).
86///
87/// The double loop technique was chosen to better handle the computation
88/// of anisotropy for _all_ points and _all_ neighbourhoods (i.e. no limiting
89/// via the max point per voxel (MPPV) options). It makes the PCA method
90/// extremely memory efficient for _all_ MPPV and radius values. However
91/// it might be worth falling back to a gather stlye approach when the MPPV
92/// and radius values are relatively small. We should investigate this in the
93/// future.
94template <typename PointDataTreeT>
96 : public VolumeTransfer<PointDataTreeT>
98{
100 using LeafNodeType = typename PointDataTreeT::LeafNodeType;
101 // We know the codec is null as this works on the world space positions
102 // that are temporarily computed for this algorithm
104
105 PcaTransfer(const AttrIndices& indices,
106 const PcaSettings& settings,
107 const Real vs,
109 util::NullInterrupter* interrupt)
110 : BaseT(manager.tree())
111 , InterruptableTransfer(interrupt)
112 , mIndices(indices)
113 , mSettings(settings)
114 , mDxInv(1.0/vs)
115 , mManager(manager)
117 , mSourcePosition() {
118 OPENVDB_ASSERT(std::isfinite(mSettings.searchRadius));
119 OPENVDB_ASSERT(std::isfinite(mDxInv));
120 }
121
123 : BaseT(other)
124 , InterruptableTransfer(other)
125 , mIndices(other.mIndices)
126 , mSettings(other.mSettings)
127 , mDxInv(other.mDxInv)
128 , mManager(other.mManager)
130 , mSourcePosition() {}
131
132 /*
133 // This is no longer used but kept for reference; each axis iterations of
134 // derived PCA methods uses this technique to skip voxels entirely if a
135 // point's position and search radius does not intersect it
136
137 static bool VoxelIntersectsSphere(const Coord& ijk, const Vec3d& PosIS, const Real r2)
138 {
139 const Vec3d min = ijk.asVec3d() - 0.5;
140 const Vec3d max = ijk.asVec3d() + 0.5;
141 Real dmin = 0;
142 for (int i = 0; i < 3; ++i) {
143 if (PosIS[i] < min[i]) dmin += math::Pow2(PosIS[i] - min[i]);
144 else if (PosIS[i] > max[i]) dmin += math::Pow2(PosIS[i] - max[i]);
145 }
146 return dmin <= r2;
147 }
148 */
149
150 float searchRadius() const { return mSettings.searchRadius; }
151 size_t neighbourThreshold() const { return mSettings.neighbourThreshold; }
152 size_t maxSourcePointsPerVoxel() const { return mSettings.maxSourcePointsPerVoxel; }
153 size_t maxTargetPointsPerVoxel() const { return mSettings.maxTargetPointsPerVoxel; }
154
155 Vec3i range(const Coord&, size_t) const { return this->range(); }
156 Vec3i range() const { return Vec3i(math::Round(mSettings.searchRadius * mDxInv)); }
157
158 inline LeafNodeType* initialize(const Coord& origin, const size_t idx, const CoordBBox& bounds)
159 {
160 BaseT::initialize(origin, idx, bounds);
161 auto& leaf = mManager.leaf(idx);
162 mTargetPosition = std::make_unique<PositionHandleT>(leaf.constAttributeArray(mIndices.mPWsIndex));
163 return &leaf;
164 }
165
166 inline bool startPointLeaf(const typename PointDataTreeT::LeafNodeType& leaf)
167 {
168 mSourcePosition = std::make_unique<PositionHandleT>(leaf.constAttributeArray(mIndices.mPWsIndex));
169#if OPENVDB_PCA_SELF_CONTRIBUTION == 0
170 mIsSameLeaf = this->mTargetPosition->array() == this->mSourcePosition->array();
171#endif
172 return true;
173 }
174
175 bool endPointLeaf(const typename PointDataTreeT::LeafNodeType&) { return true; }
176
177protected:
182 std::unique_ptr<PositionHandleT> mTargetPosition;
183 std::unique_ptr<PositionHandleT> mSourcePosition;
184#if OPENVDB_PCA_SELF_CONTRIBUTION == 0
185 bool mIsSameLeaf {false};
186#endif
187};
188
189template <typename PointDataTreeT>
191 : public PcaTransfer<PointDataTreeT>
192{
194
195 static const Index DIM = PointDataTreeT::LeafNodeType::DIM;
196 static const Index LOG2DIM = PointDataTreeT::LeafNodeType::LOG2DIM;
197
199 const PcaSettings& settings,
200 const Real vs,
202 util::NullInterrupter* interrupt)
203 : BaseT(indices, settings, vs, manager, interrupt)
204 , mWeights()
205 , mWeightedPositions()
206 , mCounts() {}
207
209 : BaseT(other)
210 , mWeights()
211 , mWeightedPositions()
212 , mCounts() {}
213
214 inline void initialize(const Coord& origin, const size_t idx, const CoordBBox& bounds)
215 {
216 auto& leaf = (*BaseT::initialize(origin, idx, bounds));
217 mWeights = initPcaArrayAttribute<WeightSumT>(leaf, this->mIndices.mWeightSumIndex);
218 mWeightedPositions = initPcaArrayAttribute<WeightedPositionSumT>(leaf, this->mIndices.mPosSumIndex);
219 // track neighbours
220 mCounts.assign(this->mTargetPosition->size(), 0);
221 }
222
223 inline void rasterizePoints(const Coord&, const Index start, const Index end, const CoordBBox& bounds)
224 {
225 const Index step = std::max(Index(1), Index((end - start) / this->maxSourcePointsPerVoxel()));
226
227 const auto* const data = this->template buffer<0>();
228 const auto& mask = *(this->template mask<0>());
229
230 const float searchRadiusInv = 1.0f / this->searchRadius();
231 const Real searchRadius2 = math::Pow2(this->searchRadius());
232 const Real searchRadiusIS2 = math::Pow2(this->searchRadius() * this->mDxInv);
233
234 OPENVDB_ASSERT(std::isfinite(searchRadiusInv));
235 OPENVDB_ASSERT(std::isfinite(searchRadius2));
236 OPENVDB_ASSERT(std::isfinite(searchRadiusIS2));
237
238 const Coord& a(bounds.min());
239 const Coord& b(bounds.max());
240
241 for (Index srcid = start; srcid < end; srcid += step) {
242 const Vec3d Psrc(this->mSourcePosition->get(srcid));
243 const Vec3d PsrcIS = Psrc * this->mDxInv;
244
245 for (Coord c = a; c.x() <= b.x(); ++c.x()) {
246 const Real minx = c.x() - 0.5;
247 const Real maxx = c.x() + 0.5;
248 const Real dminx =
249 (PsrcIS[0] < minx ? math::Pow2(PsrcIS[0] - minx) :
250 (PsrcIS[0] > maxx ? math::Pow2(PsrcIS[0] - maxx) : 0));
251 if (dminx > searchRadiusIS2) continue; // next target voxel
252 const Index i = ((c.x() & (DIM-1u)) << 2*LOG2DIM); // unsigned bit shift mult
253 for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) {
254 const Real miny = c.y() - 0.5;
255 const Real maxy = c.y() + 0.5;
256 const Real dminxy = dminx +
257 (PsrcIS[1] < miny ? math::Pow2(PsrcIS[1] - miny) :
258 (PsrcIS[1] > maxy ? math::Pow2(PsrcIS[1] - maxy) : 0));
259 if (dminxy > searchRadiusIS2) continue; // next target voxel
260 const Index ij = i + ((c.y() & (DIM-1u)) << LOG2DIM);
261 for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) {
262 const Index offset = ij + /*k*/(c.z() & (DIM-1u));
263 if (!mask.isOn(offset)) continue; // next target voxel
264
265 const Real minz = c.z() - 0.5;
266 const Real maxz = c.z() + 0.5;
267 const Real dminxyz = dminxy +
268 (PsrcIS[2] < minz ? math::Pow2(PsrcIS[2] - minz) :
269 (PsrcIS[2] > maxz ? math::Pow2(PsrcIS[2] - maxz) : 0));
270 // Does this point's radius overlap the voxel c
271 if (dminxyz > searchRadiusIS2) continue; // next target voxel
272
273 // src point overlaps voxel c
274 const Index targetEnd = data[offset];
275 const Index targetStart = (offset == 0) ? 0 : Index(data[offset - 1]);
276 const Index targetStep =
277 std::max(Index(1), Index((targetEnd - targetStart) / this->maxTargetPointsPerVoxel()));
278
279 /// @warning stepping in this way does not guarantee
280 /// we get a self contribution, could guarantee this
281 /// by enabling the OPENVDB_PCA_SELF_CONTRIBUTION == 0
282 /// check and adding it afterwards.
283 for (Index tgtid = targetStart; tgtid < targetEnd; tgtid += targetStep) {
284#if OPENVDB_PCA_SELF_CONTRIBUTION == 0
285 if (OPENVDB_UNLIKELY(this->mIsSameLeaf && tgtid == srcid)) continue;
286#endif
287 const Vec3d Ptgt(this->mTargetPosition->get(tgtid));
288 const Real d2 = (Psrc - Ptgt).lengthSqr();
289 if (d2 > searchRadius2) continue;
290
291 // src point (srcid) reaches target point (tgtid)
292 const float weight = 1.0f - math::Pow3(float(math::Sqrt(d2)) * searchRadiusInv);
293 OPENVDB_ASSERT(weight >= 0.0f && weight <= 1.0f);
294
295 mWeights[tgtid] += weight;
296 mWeightedPositions[tgtid] += Psrc * weight; // @note: world space position is weighted
297 ++mCounts[tgtid];
298 }
299 }
300 }
301 } // outer sdf voxel
302 } // point idx
303 }
304
305 bool finalize(const Coord&, size_t idx)
306 {
307 // Add points to group with counts which are more than the neighbouring threshold
308 auto& leaf = this->mManager.leaf(idx);
309
310 {
311 // @todo add API to get the array from the group handle. The handle
312 // calls loadData but not expand.
313 auto& array = leaf.attributeArray(this->mIndices.mEllipsesGroupIndex.first);
314 array.loadData(); // so we can call setUnsafe/getUnsafe
315 array.expand();
316 }
317
318 points::GroupWriteHandle group(leaf.groupWriteHandle(this->mIndices.mEllipsesGroupIndex));
319
320 const int32_t threshold = int32_t(this->neighbourThreshold());
321 for (Index i = 0; i < this->mTargetPosition->size(); ++i) {
322 // turn points OFF if they are ON and don't meet max neighbour requirements
323 if ((threshold == 0 || (mCounts[i] < threshold)) && group.getUnsafe(i)) {
324 group.setUnsafe(i, false);
325 }
326 if (mCounts[i] <= 0) continue;
327 // Normalize weights
328 OPENVDB_ASSERT(mWeights[i] >= 0.0f);
329 mWeights[i] = 1.0 / mWeights[i];
330 mWeightedPositions[i] *= mWeights[i];
331 }
332 return true;
333 }
334
335private:
336 WeightSumT* mWeights;
337 WeightedPositionSumT* mWeightedPositions;
338 std::vector<int32_t> mCounts;
339};
340
341template <typename PointDataTreeT>
344{
346
347 static const Index DIM = PointDataTreeT::LeafNodeType::DIM;
348 static const Index LOG2DIM = PointDataTreeT::LeafNodeType::LOG2DIM;
349
351 const PcaSettings& settings,
352 const Real vs,
354 util::NullInterrupter* interrupt)
355 : BaseT(indices, settings, vs, manager, interrupt)
356 , mInclusionGroupHandle()
357 , mWeights()
358 , mWeightedPositions()
359 , mCovMats() {}
360
362 : BaseT(other)
363 , mInclusionGroupHandle()
364 , mWeights()
365 , mWeightedPositions()
366 , mCovMats() {}
367
368 inline void initialize(const Coord& origin, const size_t idx, const CoordBBox& bounds)
369 {
370 auto& leaf = (*BaseT::initialize(origin, idx, bounds));
371 mInclusionGroupHandle = std::make_unique<points::GroupHandle>(leaf.groupHandle(this->mIndices.mEllipsesGroupIndex));
372 mWeights = initPcaArrayAttribute<WeightSumT>(leaf, this->mIndices.mWeightSumIndex);
373 mWeightedPositions = initPcaArrayAttribute<WeightedPositionSumT>(leaf, this->mIndices.mPosSumIndex);
374 mCovMats = initPcaArrayAttribute<math::Mat3s>(leaf, this->mIndices.mCovMatrixIndex);
375 }
376
377 inline void rasterizePoints(const Coord&, const Index start, const Index end, const CoordBBox& bounds)
378 {
379 const Index step = std::max(Index(1), Index((end - start) / this->maxSourcePointsPerVoxel()));
380
381 const auto* const data = this->template buffer<0>();
382 const auto& mask = *(this->template mask<0>());
383
384 const float searchRadiusInv = 1.0f/this->searchRadius();
385 const Real searchRadius2 = math::Pow2(this->searchRadius());
386 const Real searchRadiusIS2 = math::Pow2(this->searchRadius() * this->mDxInv);
387
388 OPENVDB_ASSERT(std::isfinite(searchRadiusInv));
389 OPENVDB_ASSERT(std::isfinite(searchRadius2));
390 OPENVDB_ASSERT(std::isfinite(searchRadiusIS2));
391
392 const Coord& a(bounds.min());
393 const Coord& b(bounds.max());
394
395 for (Index srcid = start; srcid < end; srcid += step) {
396 const Vec3d Psrc(this->mSourcePosition->get(srcid));
397 const Vec3d PsrcIS = Psrc * this->mDxInv;
398
399 for (Coord c = a; c.x() <= b.x(); ++c.x()) {
400 const Real minx = c.x() - 0.5;
401 const Real maxx = c.x() + 0.5;
402 const Real dminx =
403 (PsrcIS[0] < minx ? math::Pow2(PsrcIS[0] - minx) :
404 (PsrcIS[0] > maxx ? math::Pow2(PsrcIS[0] - maxx) : 0));
405 if (dminx > searchRadiusIS2) continue;
406 const Index i = ((c.x() & (DIM-1u)) << 2*LOG2DIM); // unsigned bit shift mult
407 for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) {
408 const Real miny = c.y() - 0.5;
409 const Real maxy = c.y() + 0.5;
410 const Real dminxy = dminx +
411 (PsrcIS[1] < miny ? math::Pow2(PsrcIS[1] - miny) :
412 (PsrcIS[1] > maxy ? math::Pow2(PsrcIS[1] - maxy) : 0));
413 if (dminxy > searchRadiusIS2) continue;
414 const Index ij = i + ((c.y() & (DIM-1u)) << LOG2DIM);
415 for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) {
416 const Index offset = ij + /*k*/(c.z() & (DIM-1u));
417 if (!mask.isOn(offset)) continue;
418
419 const Real minz = c.z() - 0.5;
420 const Real maxz = c.z() + 0.5;
421 const Real dminxyz = dminxy +
422 (PsrcIS[2] < minz ? math::Pow2(PsrcIS[2] - minz) :
423 (PsrcIS[2] > maxz ? math::Pow2(PsrcIS[2] - maxz) : 0));
424 // Does this point's radius overlap the voxel c
425 if (dminxyz > searchRadiusIS2) continue;
426
427 const Index targetEnd = data[offset];
428 const Index targetStart = (offset == 0) ? 0 : Index(data[offset - 1]);
429 const Index targetStep =
430 std::max(Index(1), Index((targetEnd - targetStart) / this->maxTargetPointsPerVoxel()));
431
432 for (Index tgtid = targetStart; tgtid < targetEnd; tgtid += targetStep) {
433 if (!mInclusionGroupHandle->get(tgtid)) continue;
434#if OPENVDB_PCA_SELF_CONTRIBUTION == 0
435 if (OPENVDB_UNLIKELY(this->mIsSameLeaf && tgtid == srcid)) continue;
436#endif
437 const Vec3d Ptgt(this->mTargetPosition->get(tgtid));
438 const Real d2 = (Psrc - Ptgt).lengthSqr();
439 if (d2 > searchRadius2) continue;
440
441 // @note I've observed some performance degradation if
442 // we don't take copies of the buffers here (aliasing?)
443 const WeightSumT totalWeightInv = mWeights[tgtid];
444 const WeightedPositionSumT currWeightSum = mWeightedPositions[tgtid];
445
446 // re-compute weight
447 // @note A gather style approach might be better,
448 // where each point appends weights/positions into
449 // a container. We lose some time having to re-
450 // iterate, but this is far more memory efficient.
451 const WeightSumT weight = 1.0f - math::Pow3(float(math::Sqrt(d2)) * searchRadiusInv);
452 const WeightedPositionSumT posMeanDiff = Psrc - currWeightSum;
453 // @note Could extract the mult by totalWeightInv
454 // and put it into a loop in finalize() - except
455 // it would be mat3*float rather than vec*float,
456 // which would probably better as maxppv increases
457 const WeightedPositionSumT x = (totalWeightInv * weight) * posMeanDiff;
458
459 float* const m = mCovMats[tgtid].asPointer();
460 /// @note: equal to:
461 // mat.setCol(0, mat.col(0) + (x * posMeanDiff[0]));
462 // mat.setCol(1, mat.col(1) + (x * posMeanDiff[1]));
463 // mat.setCol(2, mat.col(2) + (x * posMeanDiff[2]));
464 // @todo formalize precision of these methods
465 m[0] += float(x[0] * posMeanDiff[0]);
466 m[1] += float(x[0] * posMeanDiff[1]);
467 m[2] += float(x[0] * posMeanDiff[2]);
468 //
469 m[3] += float(x[1] * posMeanDiff[0]);
470 m[4] += float(x[1] * posMeanDiff[1]);
471 m[5] += float(x[1] * posMeanDiff[2]);
472 //
473 m[6] += float(x[2] * posMeanDiff[0]);
474 m[7] += float(x[2] * posMeanDiff[1]);
475 m[8] += float(x[2] * posMeanDiff[2]);
476 } //point idx
477 }
478 }
479 } // outer sdf voxel
480 } // point idx
481 }
482
483 bool finalize(const Coord&, size_t) { return true; }
484
485private:
486 points::GroupHandle::UniquePtr mInclusionGroupHandle;
487 const WeightSumT* mWeights;
488 const WeightedPositionSumT* mWeightedPositions;
489 math::Mat3s* mCovMats;
490 std::vector<int32_t> mCounts;
491};
492
493/// @brief Sort a vector into descending order and output a vector of the resulting order
494/// @param vector Vector to sort
495template <typename Scalar>
496inline Vec3i
498{
499 Vec3i order(0,1,2);
500 if (vector[0] < vector[1]) {
501 std::swap(vector[0], vector[1]);
502 std::swap(order[0], order[1]);
503 }
504 if (vector[1] < vector[2]) {
505 std::swap(vector[1], vector[2]);
506 std::swap(order[1], order[2]);
507 }
508 if (vector[0] < vector[1]) {
509 std::swap(vector[0], vector[1]);
510 std::swap(order[0], order[1]);
511 }
512 return order;
513}
514
515/// @brief Decomposes a symmetric matrix into its eigenvalues and a rotation matrix of eigenvectors.
516/// Note that if mat is positive-definite, this will be equivalent to a singular value
517/// decomposition where V = U.
518/// @param mat Matrix to decompose
519/// @param U rotation matrix. The order of its columns (which will be eigenvectors) will match
520/// the eigenvalues in sigma
521/// @param sigma vector of eigenvalues
522template <typename Scalar>
523inline bool
526 math::Vec3<Scalar>& sigma)
527{
529 const bool diagonalized = math::diagonalizeSymmetricMatrix(mat, Q, sigma);
530
531 if (!diagonalized) return false;
532
533 // need to sort eigenvalues and eigenvectors
534 Vec3i order = descendingOrder(sigma);
535
536 // we need to re-order the matrix ("Q") columns to match the new eigenvalue order
537 // to obtain the correct "U" matrix
538 U.setColumns(Q.col(order[0]), Q.col(order[1]), Q.col(order[2]));
539
540 return true;
541}
542
543} // namespace pca_internal
544
545
546template <typename PointDataGridT>
547inline void
548pca(PointDataGridT& points,
549 const PcaSettings& settings,
550 const PcaAttributes& attrs)
551{
553
554 using namespace pca_internal;
555
556 using PointDataTreeT = typename PointDataGridT::TreeType;
557 using LeafManagerT = tree::LeafManager<PointDataTreeT>;
558 using LeafNodeT = typename PointDataTreeT::LeafNodeType;
559
560 auto& tree = points.tree();
561 const auto leaf = tree.cbeginLeaf();
562 if (!leaf) return;
563
564 // Small lambda to init any one of the necessary ellipsoid attributes
565 // @note Various algorithms here assume that we are responsible for creating
566 // these attributes and so can optimize accordingly. If this changes we'll
567 // need to also change those optimizations (e.g. NullCodec, loadData, etc)
568 const auto initAttribute = [&](const std::string& name, const auto val)
569 {
570 using ValueT = std::decay_t<decltype(val)>;
571 if (leaf->hasAttribute(name)) {
572 OPENVDB_THROW(KeyError, "PCA attribute '" << name << "' already exists!");
573 }
574
576 return leaf->attributeSet().find(name);
577 };
578
579 //
580
581 const size_t pvsIdx = leaf->attributeSet().find("P");
582 const auto& xform = points.constTransform();
583 const double vs = xform.voxelSize()[0];
584 LeafManagerT manager(tree);
585
586 // 1) Create persisting attributes
587 const size_t pwsIdx = initAttribute(attrs.positionWS, zeroVal<PcaAttributes::PosWsT>());
588 const size_t rotIdx = initAttribute(attrs.rotation, zeroVal<PcaAttributes::RotationT>());
589 const size_t strIdx = initAttribute(attrs.stretch, PcaAttributes::StretchT(settings.nonAnisotropicStretch));
590
591 // 2) Create temporary attributes
592 const auto& descriptor = leaf->attributeSet().descriptor();
593 const std::vector<std::string> temps {
594 descriptor.uniqueName("_weightedpositionsums"),
595 descriptor.uniqueName("_inv_weightssum")
596 };
597
598 const size_t posSumIndex = initAttribute(temps[0], zeroVal<WeightedPositionSumT>());
599 const size_t weightSumIndex = initAttribute(temps[1], zeroVal<WeightSumT>());
600
601 // 3) Create ellipses group
602 if (!leaf->attributeSet().descriptor().hasGroup(attrs.ellipses)) {
604 // Include everything by default to start with
605 points::setGroup(tree, attrs.ellipses, true);
606 }
607
608 // Re-acquire the updated descriptor and get the group idx
609 const GroupIndexT ellipsesIdx =
610 leaf->attributeSet().descriptor().groupIndex(attrs.ellipses);
611
612 PcaTimer timer(settings.interrupter);
613
614 // 3) Store the world space position on the PDG to speed up subsequent
615 // calculations.
616 timer.start("Compute position world spaces");
617 manager.foreach([&](LeafNodeT& leafnode, size_t)
618 {
619 using PvsT = Vec3f;
620 using PwsT = PcaAttributes::PosWsT;
621
622 points::AttributeHandle<PvsT> Pvs(leafnode.constAttributeArray(pvsIdx));
623 PwsT* Pws = initPcaArrayAttribute<PwsT>(leafnode, pwsIdx, /*fill=*/false);
624
625 for (auto voxel = leafnode.cbeginValueOn(); voxel; ++voxel) {
626 const Coord voxelCoord = voxel.getCoord();
627 const Vec3d coordVec = voxelCoord.asVec3d();
628 for (auto iter = leafnode.beginIndexVoxel(voxelCoord); iter; ++iter) {
629 Pws[*iter] = xform.indexToWorld(Pvs.get(*iter) + coordVec);
630 }
631 }
632 });
633
634 timer.stop();
635 if (util::wasInterrupted(settings.interrupter)) return;
636
637 AttrIndices indices;
638 indices.mPosSumIndex = posSumIndex;
639 indices.mWeightSumIndex = weightSumIndex;
640 indices.mCovMatrixIndex = rotIdx;
641 indices.mPWsIndex = pwsIdx;
642 indices.mEllipsesGroupIndex = ellipsesIdx;
643
644 // 4) Init temporary attributes and calculate:
645 // sum_j w_{i,j} * x_j / (sum_j w_j)
646 // And neighbour counts for each point.
647 // simultaneously calculates the sum of weighted vector positions (sum w_{i,j} * x_i)
648 // weighted against the inverse sum of weights (1.0 / sum w_{i,j}). Also counts number
649 // of neighours each point has and updates the ellipses group based on minimum
650 // neighbour threshold. Those points which are "included" but which lack sufficient
651 // neighbours will be marked as "not included".
652 timer.start("Compute position weights");
653 {
654 WeightPosSumsTransfer<PointDataTreeT>
655 transfer(indices, settings, float(vs), manager, settings.interrupter);
657 }
658
659 timer.stop();
660 if (util::wasInterrupted(settings.interrupter)) return;
661
662 // 5) Principal axes define the rotation matrix of the ellipsoid.
663 // Calculates covariance matrices given weighted sums of positions and
664 // sums of weights per-particle
665 timer.start("Compute covariance matrices");
666 {
667 CovarianceTransfer<PointDataTreeT>
668 transfer(indices, settings, float(vs), manager, settings.interrupter);
670 }
671
672 timer.stop();
673 if (util::wasInterrupted(settings.interrupter)) return;
674
675 // 6) radii stretches are given by the scaled singular values. Decompose
676 // the covariance matrix into its principal axes and their lengths
677 timer.start("Decompose covariance matrices");
678 manager.foreach([&](LeafNodeT& leafnode, size_t)
679 {
680 AttributeWriteHandle<Vec3f, NullCodec> stretchHandle(leafnode.attributeArray(strIdx));
681 AttributeWriteHandle<math::Mat3s, NullCodec> rotHandle(leafnode.attributeArray(rotIdx));
682 GroupHandle ellipsesGroupHandle(leafnode.groupHandle(ellipsesIdx));
683
684 for (Index idx = 0; idx < stretchHandle.size(); ++idx) {
685 if (!ellipsesGroupHandle.get(idx)) {
686 rotHandle.set(idx, math::Mat3s::identity());
687 continue;
688 }
689
690 // get singular values of the covariance matrix
691 math::Mat3s u;
692 Vec3s sigma;
693 decomposeSymmetricMatrix(rotHandle.get(idx), u, sigma);
694
695 // fix sigma values, the principal lengths
696 auto maxs = sigma[0] * settings.allowedAnisotropyRatio;
697 sigma[1] = std::max(sigma[1], maxs);
698 sigma[2] = std::max(sigma[2], maxs);
699
700 // should only happen if all neighbours are coincident
701 // @note The specific tolerance here relates to the normalization
702 // of the stetch values in step (7) e.g. s*(1.0/cbrt(s.product())).
703 // math::Tolerance<float>::value is 1e-7f, but we have a bit more
704 // flexibility here, we can deal with smaller values, common for
705 // the case where a point only has one neighbour
706 // @todo have to manually construct the tolerance because
707 // math::Tolerance<Vec3f> resolves to 0.0. fix this in the math lib
708 if (math::isApproxZero(sigma, Vec3f(1e-11f))) {
709 sigma = Vec3f::ones();
710 }
711
712 stretchHandle.set(idx, sigma);
713 rotHandle.set(idx, u);
714 }
715 });
716 timer.stop();
717
718 // 7) normalise the principal lengths such that the transformation they
719 // describe 'preserves volume' thus becoming the stretch of the ellipsoids
720
721 // Calculates the average volume change that would occur if applying the
722 // transformations defined by the calculate principal axes and their
723 // lengths using the determinant of the stretch component of the
724 // transformation (given by the sum of the diagonal values) and uses this
725 // value to normalise these lengths such that they are relative to the
726 // identity. This ensures that the transformation preserves volume.
727 timer.start("Normalise the principal lengths");
728 manager.foreach([&](LeafNodeT& leafnode, size_t)
729 {
730 points::GroupFilter filter(ellipsesIdx);
731 filter.reset(leafnode);
732 AttributeWriteHandle<Vec3f, NullCodec> stretchHandle(leafnode.attributeArray(strIdx));
733
734 for (Index i = 0; i < stretchHandle.size(); ++i)
735 {
736 if (!filter.valid(&i)) continue;
737 const Vec3f stretch = stretchHandle.get(i);
738 OPENVDB_ASSERT(stretch != Vec3f::zero());
739 const float stretchScale = 1.0f / std::cbrt(stretch.product());
740 stretchHandle.set(i, stretchScale * stretch);
741 }
742 });
743
744 timer.stop();
745 if (util::wasInterrupted(settings.interrupter)) return;
746
747 // 8) do laplacian position smoothing here as we have the weights already
748 /// calculated (calculates the smoothed kernel origins as described in
749 /// the paper). averagePositions value biases the smoothed positions
750 /// towards the weighted mean positions. 1.0 will use the weighted means
751 /// while 0.0 will use the original world-space positions
752 timer.start("Laplacian smooth positions");
753 if (settings.averagePositions > 0.0f)
754 {
755 manager.foreach([&](LeafNodeT& leafnode, size_t) {
756 AttributeWriteHandle<Vec3d, NullCodec> Pws(leafnode.attributeArray(pwsIdx));
757 AttributeHandle<WeightedPositionSumT, NullCodec> weightedPosSumHandle(leafnode.constAttributeArray(posSumIndex));
758
759 for (Index i = 0; i < Pws.size(); ++i)
760 {
761 // @note Technically possible for the weights to be valid
762 // _and_ zero.
763 if (math::isApproxZero(weightedPosSumHandle.get(i),
765 continue;
766 }
767 const Vec3d smoothedPosition = (1.0f - settings.averagePositions) *
768 Pws.get(i) + settings.averagePositions * weightedPosSumHandle.get(i);
769 Pws.set(i, smoothedPosition);
770 }
771 });
772 }
773
774 timer.stop();
775
776 // Remove temporary attributes
778}
779
780}
781}
782}
783
784#endif // OPENVDB_POINTS_POINT_PRINCIPAL_COMPONENT_ANALYSIS_IMPL_HAS_BEEN_INCLUDED
#define OPENVDB_ASSERT(X)
Definition Assert.h:41
#define OPENVDB_UNLIKELY(x)
Definition Platform.h:92
Definition Exceptions.h:59
Axis-aligned bounding box of signed integer coordinates.
Definition Coord.h:252
const Coord & min() const
Definition Coord.h:324
const Coord & max() const
Definition Coord.h:325
Signed (x, y, z) 32-bit integer coordinates.
Definition Coord.h:26
Vec3d asVec3d() const
Definition Coord.h:144
Int32 y() const
Definition Coord.h:132
Int32 x() const
Definition Coord.h:131
Int32 z() const
Definition Coord.h:133
3x3 matrix class.
Definition Mat3.h:29
Vec3< T > col(int j) const
Get jth column, e.g. Vec3d v = m.col(0);.
Definition Mat3.h:168
static const Mat3< float > & identity()
Definition Mat3.h:121
void setColumns(const Vec3< T > &v1, const Vec3< T > &v2, const Vec3< T > &v3)
Set the columns of this matrix to the vectors v1, v2, v3.
Definition Mat3.h:209
Definition Vec3.h:25
T * asPointer()
Definition Vec3.h:95
T product() const
Return the product of all the vector components.
Definition Vec3.h:357
static Vec3< float > zero()
Definition Vec3.h:467
static Vec3< float > ones()
Definition Vec3.h:468
Definition AttributeArray.h:764
Index size() const
Definition AttributeArray.h:786
ValueType get(Index n, Index m=0) const
Definition AttributeArray.h:2058
Util::GroupIndex GroupIndex
Definition AttributeSet.h:317
Write-able version of AttributeHandle.
Definition AttributeArray.h:832
void set(Index n, const ValueType &value)
Definition AttributeArray.h:2115
Index filtering on group membership.
Definition AttributeGroup.h:136
void reset(const LeafT &leaf)
Definition AttributeGroup.h:151
bool valid(const IterT &iter) const
Definition AttributeGroup.h:156
Definition AttributeGroup.h:74
std::unique_ptr< GroupHandle > UniquePtr
Definition AttributeGroup.h:77
Definition AttributeGroup.h:103
void setUnsafe(Index n, bool on)
Set on at the given index n (assumes in-core and non-uniform)
This class manages a linear array of pointers to a given tree's leaf nodes, as well as optional auxil...
Definition LeafManager.h:86
Simple timer for basic profiling.
Definition CpuTimer.h:67
bool diagonalizeSymmetricMatrix(const Mat3< T > &input, Mat3< T > &Q, Vec3< T > &D, unsigned int MAX_ITERATIONS=250)
Use Jacobi iterations to decompose a symmetric 3x3 matrix (diagonalize and compute eigenvectors)
Definition Mat3.h:736
bool isApproxZero(const Type &x)
Return true if x is equal to zero to within the default floating-point comparison tolerance.
Definition Math.h:349
float Sqrt(float x)
Return the square root of a floating-point value.
Definition Math.h:761
Mat3< float > Mat3s
Definition Mat3.h:832
Vec3< double > Vec3d
Definition Vec3.h:665
float Round(float x)
Return x rounded to the nearest integer.
Definition Math.h:819
Type Pow3(Type x)
Return x3.
Definition Math.h:552
Vec3< int32_t > Vec3i
Definition Vec3.h:662
Vec3< float > Vec3s
Definition Vec3.h:664
Type Pow2(Type x)
Return x2.
Definition Math.h:548
Definition PrincipalComponentAnalysisImpl.h:23
double WeightSumT
Definition PrincipalComponentAnalysisImpl.h:53
NoTimer TimerT
Definition PrincipalComponentAnalysisImpl.h:32
Vec3i descendingOrder(math::Vec3< Scalar > &vector)
Sort a vector into descending order and output a vector of the resulting order.
Definition PrincipalComponentAnalysisImpl.h:497
T * initPcaArrayAttribute(LeafNodeT &leaf, const size_t idx, const bool fill=true)
Definition PrincipalComponentAnalysisImpl.h:67
points::AttributeSet::Descriptor::GroupIndex GroupIndexT
Definition PrincipalComponentAnalysisImpl.h:55
bool decomposeSymmetricMatrix(const math::Mat3< Scalar > &mat, math::Mat3< Scalar > &U, math::Vec3< Scalar > &sigma)
Decomposes a symmetric matrix into its eigenvalues and a rotation matrix of eigenvectors....
Definition PrincipalComponentAnalysisImpl.h:524
Vec3d WeightedPositionSumT
Definition PrincipalComponentAnalysisImpl.h:54
Definition AttributeArray.h:42
void setGroup(PointDataTreeT &tree, const PointIndexTreeT &indexTree, const std::vector< short > &membership, const Name &group, const bool remove=false)
Sets group membership from a PointIndexTree-ordered vector.
Definition PointGroupImpl.h:438
void appendGroup(PointDataTreeT &tree, const Name &group)
Appends a new empty group to the VDB tree.
Definition PointGroupImpl.h:209
void pca(PointDataGridT &points, const PcaSettings &settings, const PcaAttributes &attrs)
Calculate ellipsoid transformations from the local point distributions as described in Yu and Turk's ...
Definition PrincipalComponentAnalysisImpl.h:548
void appendAttribute(PointDataTreeT &tree, const Name &name, const NamePair &type, const Index strideOrTotalSize=1, const bool constantStride=true, const Metadata *defaultValue=nullptr, const bool hidden=false, const bool transient=false)
Appends a new attribute to the VDB tree (this method does not require a templated AttributeType)
Definition PointAttributeImpl.h:103
void dropAttributes(PointDataTreeT &tree, const std::vector< size_t > &indices)
Drops attributes from the VDB tree.
Definition PointAttributeImpl.h:238
void rasterize(const PointDataTreeOrGridT &points, TransferT &transfer)
Perform potentially complex rasterization from a user defined transfer scheme. See below comments for...
Definition PointTransfer.h:700
Definition PointDataGrid.h:170
bool wasInterrupted(T *i, int percent=-1)
Definition NullInterrupter.h:49
Index32 Index
Definition Types.h:54
math::Vec3< float > Vec3f
Definition Types.h:74
double Real
Definition Types.h:60
constexpr T zeroVal()
Return the value of type T that corresponds to zero.
Definition Math.h:70
const char * typeNameAsString()
Definition Types.h:587
Definition Exceptions.h:13
#define OPENVDB_THROW(exception, message)
Definition Exceptions.h:74
Helper metafunction used to determine if the first template parameter is a specialization of the clas...
Definition Types.h:263
static T value()
Definition Math.h:148
InterruptableTransfer(util::NullInterrupter *const interrupt)
Definition PointTransfer.h:232
static const char * name()
Definition AttributeArray.h:443
The persistent attributes created by the PCA methods.
Definition PrincipalComponentAnalysis.h:156
std::string rotation
Definition PrincipalComponentAnalysis.h:167
math::Vec3< float > StretchT
Settings for the "stretch" attribute, a floating point vector attribute which represents the scaling ...
Definition PrincipalComponentAnalysis.h:160
std::string positionWS
Definition PrincipalComponentAnalysis.h:176
std::string ellipses
A point group to create that represents points which have valid ellipsoidal neighborhood....
Definition PrincipalComponentAnalysis.h:185
std::string stretch
Definition PrincipalComponentAnalysis.h:161
math::Vec3< double > PosWsT
Settings for the world space position of every point. This may end up being different to their actual...
Definition PrincipalComponentAnalysis.h:175
Various settings for the neighborhood analysis of point distributions.
Definition PrincipalComponentAnalysis.h:67
float allowedAnisotropyRatio
Definition PrincipalComponentAnalysis.h:94
float averagePositions
Definition PrincipalComponentAnalysis.h:147
util::NullInterrupter * interrupter
Definition PrincipalComponentAnalysis.h:149
float nonAnisotropicStretch
Definition PrincipalComponentAnalysis.h:99
void initialize(const Coord &origin, const size_t, const CoordBBox &)
ValueType< Idx > * buffer()
Definition PointTransfer.h:325
NodeMaskT * mask()
Definition PointTransfer.h:337
Definition PrincipalComponentAnalysisImpl.h:58
size_t mCovMatrixIndex
Definition PrincipalComponentAnalysisImpl.h:61
size_t mPWsIndex
Definition PrincipalComponentAnalysisImpl.h:62
size_t mPosSumIndex
Definition PrincipalComponentAnalysisImpl.h:59
size_t mWeightSumIndex
Definition PrincipalComponentAnalysisImpl.h:60
GroupIndexT mEllipsesGroupIndex
Definition PrincipalComponentAnalysisImpl.h:63
void initialize(const Coord &origin, const size_t idx, const CoordBBox &bounds)
Definition PrincipalComponentAnalysisImpl.h:368
PcaTransfer< PointDataTreeT > BaseT
Definition PrincipalComponentAnalysisImpl.h:345
bool finalize(const Coord &, size_t)
Definition PrincipalComponentAnalysisImpl.h:483
static const Index DIM
Definition PrincipalComponentAnalysisImpl.h:347
CovarianceTransfer(const AttrIndices &indices, const PcaSettings &settings, const Real vs, tree::LeafManager< PointDataTreeT > &manager, util::NullInterrupter *interrupt)
Definition PrincipalComponentAnalysisImpl.h:350
CovarianceTransfer(const CovarianceTransfer &other)
Definition PrincipalComponentAnalysisImpl.h:361
static const Index LOG2DIM
Definition PrincipalComponentAnalysisImpl.h:348
void rasterizePoints(const Coord &, const Index start, const Index end, const CoordBBox &bounds)
Definition PrincipalComponentAnalysisImpl.h:377
Definition PrincipalComponentAnalysisImpl.h:28
void stop()
Definition PrincipalComponentAnalysisImpl.h:30
void start(const char *)
Definition PrincipalComponentAnalysisImpl.h:29
PcaTimer(util::NullInterrupter *const interrupt)
Definition PrincipalComponentAnalysisImpl.h:37
void stop()
Definition PrincipalComponentAnalysisImpl.h:45
void start(const char *msg)
Definition PrincipalComponentAnalysisImpl.h:40
util::NullInterrupter *const mInterrupt
Definition PrincipalComponentAnalysisImpl.h:50
PcaTransfer(const AttrIndices &indices, const PcaSettings &settings, const Real vs, tree::LeafManager< PointDataTreeT > &manager, util::NullInterrupter *interrupt)
Definition PrincipalComponentAnalysisImpl.h:105
bool startPointLeaf(const typename PointDataTreeT::LeafNodeType &leaf)
Definition PrincipalComponentAnalysisImpl.h:166
size_t maxSourcePointsPerVoxel() const
Definition PrincipalComponentAnalysisImpl.h:152
size_t maxTargetPointsPerVoxel() const
Definition PrincipalComponentAnalysisImpl.h:153
Vec3i range() const
Definition PrincipalComponentAnalysisImpl.h:156
const AttrIndices & mIndices
Definition PrincipalComponentAnalysisImpl.h:178
size_t neighbourThreshold() const
Definition PrincipalComponentAnalysisImpl.h:151
const tree::LeafManager< PointDataTreeT > & mManager
Definition PrincipalComponentAnalysisImpl.h:181
LeafNodeType * initialize(const Coord &origin, const size_t idx, const CoordBBox &bounds)
Definition PrincipalComponentAnalysisImpl.h:158
float searchRadius() const
Definition PrincipalComponentAnalysisImpl.h:150
std::unique_ptr< PositionHandleT > mTargetPosition
Definition PrincipalComponentAnalysisImpl.h:182
Vec3i range(const Coord &, size_t) const
Definition PrincipalComponentAnalysisImpl.h:155
VolumeTransfer< PointDataTreeT > BaseT
Definition PrincipalComponentAnalysisImpl.h:99
const PcaSettings & mSettings
Definition PrincipalComponentAnalysisImpl.h:179
PcaTransfer(const PcaTransfer &other)
Definition PrincipalComponentAnalysisImpl.h:122
points::AttributeHandle< Vec3d, NullCodec > PositionHandleT
Definition PrincipalComponentAnalysisImpl.h:103
typename PointDataTreeT::LeafNodeType LeafNodeType
Definition PrincipalComponentAnalysisImpl.h:100
const Real mDxInv
Definition PrincipalComponentAnalysisImpl.h:180
bool endPointLeaf(const typename PointDataTreeT::LeafNodeType &)
Definition PrincipalComponentAnalysisImpl.h:175
std::unique_ptr< PositionHandleT > mSourcePosition
Definition PrincipalComponentAnalysisImpl.h:183
bool finalize(const Coord &, size_t idx)
Definition PrincipalComponentAnalysisImpl.h:305
WeightPosSumsTransfer(const WeightPosSumsTransfer &other)
Definition PrincipalComponentAnalysisImpl.h:208
void initialize(const Coord &origin, const size_t idx, const CoordBBox &bounds)
Definition PrincipalComponentAnalysisImpl.h:214
PcaTransfer< PointDataTreeT > BaseT
Definition PrincipalComponentAnalysisImpl.h:193
static const Index DIM
Definition PrincipalComponentAnalysisImpl.h:195
WeightPosSumsTransfer(const AttrIndices &indices, const PcaSettings &settings, const Real vs, tree::LeafManager< PointDataTreeT > &manager, util::NullInterrupter *interrupt)
Definition PrincipalComponentAnalysisImpl.h:198
static const Index LOG2DIM
Definition PrincipalComponentAnalysisImpl.h:196
void rasterizePoints(const Coord &, const Index start, const Index end, const CoordBBox &bounds)
Definition PrincipalComponentAnalysisImpl.h:223
Base class for interrupters.
Definition NullInterrupter.h:26
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition version.h.in:121
#define OPENVDB_USE_VERSION_NAMESPACE
Definition version.h.in:218