OpenVDB 11.0.0
Loading...
Searching...
No Matches
Morphology.h
Go to the documentation of this file.
1// Copyright Contributors to the OpenVDB Project
2// SPDX-License-Identifier: MPL-2.0
3//
4/// @file Morphology.h
5///
6/// @authors Ken Museth, Nick Avramoussis
7///
8/// @brief Implementation of morphological dilation and erosion.
9///
10/// @note By design the morphological operations only change the
11/// state of voxels, not their values. If one desires to
12/// change the values of voxels that change state an efficient
13/// technique is to construct a boolean mask by performing a
14/// topology difference between the original and final grids.
15
16#ifndef OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED
17#define OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED
18
19#include "Activate.h" // backwards compatibility
20#include "Prune.h"
21#include "ValueTransformer.h"
22
23#include <openvdb/Types.h>
24#include <openvdb/Grid.h>
27#include <openvdb/openvdb.h>
29
30#include <tbb/task_arena.h>
31#include <tbb/enumerable_thread_specific.h>
32#include <tbb/parallel_for.h>
33
34#include <type_traits>
35#include <vector>
36
37
38namespace openvdb {
40namespace OPENVDB_VERSION_NAME {
41namespace tools {
42
43/// @brief Voxel topology of nearest neighbors
44/// @details
45/// <dl>
46/// <dt><b>NN_FACE</b>
47/// <dd>face adjacency (6 nearest neighbors, defined as all neighbor
48/// voxels connected along one of the primary axes)
49///
50/// <dt><b>NN_FACE_EDGE</b>
51/// <dd>face and edge adjacency (18 nearest neighbors, defined as all
52/// neighbor voxels connected along either one or two of the primary axes)
53///
54/// <dt><b>NN_FACE_EDGE_VERTEX</b>
55/// <dd>face, edge and vertex adjacency (26 nearest neighbors, defined
56/// as all neighbor voxels connected along either one, two or all
57/// three of the primary axes)
58/// </dl>
60
61/// @brief Different policies when dilating trees with active tiles
62/// @details
63/// <dl>
64/// <dt><b>IGNORE_TILES</b>
65/// <dd>Active tiles are ignores. For dilation, only active voxels are
66/// dilated. For erosion, active tiles still appear as neighboring
67/// activity however will themselves not be eroded.
68///
69/// <dt><b>EXPAND_TILES</b>
70/// <dd>For dilation and erosion, active tiles are voxelized (expanded),
71/// dilated or eroded and left in their voxelized state irrespective of
72/// their final state.
73///
74/// <dt><b>PRESERVE_TILES</b>
75/// <dd>For dilation, active tiles remain unchanged but they still
76/// contribute to the dilation as if they were active voxels. For
77/// erosion, active tiles are only eroded should the erosion wavefront
78/// reach them, otherwise they are left unchanged. Additionally, dense
79/// or empty nodes with constant values are pruned.
80/// </dl>
82
83/// @brief Topologically dilate all active values (i.e. both voxels
84/// and tiles) in a tree using one of three nearest neighbor
85/// connectivity patterns.
86/// @details If the input is *not* a MaskTree OR if tiles are being
87/// preserved, this algorithm will copy the input tree topology onto a
88/// MaskTree, performs the dilation on the mask and copies the resulting
89/// topology back. This algorithm guarantees topology preservation
90/// (non-pruned leaf nodes will persists) EXCEPT for direct MaskTree
91/// dilation. MaskTree dilation is optimised for performance and may
92/// replace existing leaf nodes i.e. any held leaf node pointers may
93/// become invalid. See the Morphology class for more granular control.
94/// @note This method is fully multi-threaded and support active tiles,
95/// however only the PRESERVE_TILES policy ensures a pruned topology.
96/// The values of any voxels are unchanged.
97///
98/// @param tree tree or leaf manager to be dilated. The leaf
99/// manager will be synchronized with the result.
100/// @param iterations number of iterations to apply the dilation
101/// @param nn connectivity pattern of the dilation: either
102/// face-adjacent (6 nearest neighbors), face- and edge-adjacent
103/// (18 nearest neighbors) or face-, edge- and vertex-adjacent (26
104/// nearest neighbors).
105/// @param mode Defined the policy for handling active tiles
106/// (see above for details)
107/// @param threaded Whether to multi-thread execution
108template<typename TreeOrLeafManagerT>
109void dilateActiveValues(TreeOrLeafManagerT& tree,
110 const int iterations = 1,
111 const NearestNeighbors nn = NN_FACE,
112 const TilePolicy mode = PRESERVE_TILES,
113 const bool threaded = true);
114
115/// @brief Topologically erode all active values (i.e. both voxels
116/// and tiles) in a tree using one of three nearest neighbor
117/// connectivity patterns.
118/// @details If tiles are being preserve, this algorithm will copy the input
119/// tree topology onto a MaskTree, performs the erosion on the mask and
120/// intersects the resulting topology back. This algorithm guarantees
121/// topology preservation (non-pruned leaf nodes will persists). See the
122/// Morphology class for more granular control.
123/// @note This method is fully multi-threaded and support active tiles,
124/// however only the PRESERVE_TILES policy ensures a pruned topology.
125/// The values of any voxels are unchanged. Erosion by NN_FACE neighbors
126/// is usually faster than other neighbor schemes. NN_FACE_EDGE and
127/// NN_FACE_EDGE_VERTEX operate at comparable dilation speeds.
128///
129/// @param tree tree or leaf manager to be eroded. The leaf
130/// manager will be synchronized with the result.
131/// @param iterations number of iterations to apply the erosion
132/// @param nn connectivity pattern of the erosion: either
133/// face-adjacent (6 nearest neighbors), face- and edge-adjacent
134/// (18 nearest neighbors) or face-, edge- and vertex-adjacent (26
135/// nearest neighbors).
136/// @param mode Defined the policy for handling active tiles
137/// (see above for details)
138/// @param threaded Whether to multi-thread execution
139template<typename TreeOrLeafManagerT>
140void erodeActiveValues(TreeOrLeafManagerT& tree,
141 const int iterations = 1,
142 const NearestNeighbors nn = NN_FACE,
143 const TilePolicy mode = PRESERVE_TILES,
144 const bool threaded = true);
145
146
147////////////////////////////////////////
148
149
150namespace morphology {
151
152/// @brief Dilation/Erosion operations over a Trees leaf level voxel topology.
153template<typename TreeType>
155{
156public:
157 using LeafType = typename TreeType::LeafNodeType;
158 using MaskType = typename LeafType::NodeMaskType;
159 using ValueType = typename TreeType::ValueType;
160 using MaskTreeT = typename TreeType::template ValueConverter<ValueMask>::Type;
161 using MaskLeafT = typename MaskTreeT::LeafNodeType;
163
164 Morphology(TreeType& tree)
165 : mManagerPtr(new tree::LeafManager<TreeType>(tree))
166 , mManager(*mManagerPtr)
167 , mThreaded(true) {}
168
170 : mManagerPtr(nullptr)
171 , mManager(tree)
172 , mThreaded(true) {}
173
174 /// @brief Return whether this class is using multi-threading.
175 bool getThreaded() const { return mThreaded; }
176 /// @brief Set whether to use multi-threading.
177 /// @note The grain size is not exposed
178 inline void setThreaded(const bool threaded) { mThreaded = threaded; }
179
180 /// @brief Return a const reference to the leaf manager
181 inline const tree::LeafManager<TreeType>& leafManager() const { return mManager; }
182
183 /// @brief Topologically erode all voxels by the provided nearest neighbor
184 /// scheme and optionally collapse constant leaf nodes
185 /// @details Inactive Tiles contribute to the erosion but active tiles are
186 /// not modified.
187 /// @param iter Number of erosion iterations
188 /// @param nn Connectivity pattern of the erosion
189 /// @param prune Whether to collapse constant leaf nodes after the erosion
190 void erodeVoxels(const size_t iter,
191 const NearestNeighbors nn,
192 const bool prune = false);
193
194 /// @brief Topologically dilate all voxels by the provided nearest neighbor
195 /// scheme and optionally collapse constant leaf nodes
196 /// @details Voxel values are unchanged and only leaf nodes are used to
197 /// propagate the dilation.
198 /// @param iter Number of dilation iterations
199 /// @param nn Connectivity pattern of the dilation
200 /// @param prune Whether to collapse constant leaf nodes after the dilation
201 /// @param preserveMaskLeafNodes When dilating mask trees, the default behaviour
202 /// chooses to steal the mask nodes rather than copy them. Although faster,
203 /// this means that leaf nodes may be re-allocated. Set this to true if you
204 /// need the original topology pointers to be preserved.
205 void dilateVoxels(const size_t iter,
206 const NearestNeighbors nn,
207 const bool prune = false,
208 const bool preserveMaskLeafNodes = false);
209
210
211 /// @brief Copy the current node masks onto the provided vector. The vector
212 /// is resized if necessary.
213 /// @param masks The vector of NodeMasks to copy onto
214 void copyMasks(std::vector<MaskType>& masks) const
215 {
216 if (masks.size() < mManager.leafCount()) {
217 masks.resize(mManager.leafCount());
218 }
219
220 if (this->getThreaded()) {
221 // @note this is marginally faster than using leafRange or foreach
222 tbb::parallel_for(mManager.getRange(),
223 [&](const tbb::blocked_range<size_t>& r){
224 for (size_t idx = r.begin(); idx < r.end(); ++idx)
225 masks[idx] = mManager.leaf(idx).getValueMask();
226 });
227 }
228 else {
229 for (size_t idx = 0; idx < mManager.leafCount(); ++idx) {
230 masks[idx] = mManager.leaf(idx).getValueMask();
231 }
232 }
233 }
234
235public:
236 /// @brief Node Mask dilation/erosion operations for individual leaf nodes on
237 /// a given tree. The leaf node may optionally belong to a different tree
238 /// than the provided accessor, which will have the effect of dilating the
239 /// leaf node mask into a different tree, or eroding the node mask based
240 /// on corresponding neighbors in a different tree.
242 {
243 static const Int32 DIM = static_cast<Int32>(LeafType::DIM);
244 static const Int32 LOG2DIM = static_cast<Int32>(LeafType::LOG2DIM);
245
246 // Select the storage size based off the dimensions of the leaf node
247 using Word = typename std::conditional<LOG2DIM == 3, uint8_t,
248 typename std::conditional<LOG2DIM == 4, uint16_t,
249 typename std::conditional<LOG2DIM == 5, uint32_t,
250 typename std::conditional<LOG2DIM == 6, uint64_t,
251 void>::type>::type>::type>::type;
252
253 static_assert(!std::is_same<Word, void>::value,
254 "Unsupported Node Dimension for node mask dilation/erosion");
255
257 const NearestNeighbors op)
258 : mOrigin(nullptr)
259 , mNeighbors(NodeMaskOp::ksize(op), nullptr)
260 , mAccessor(&accessor)
261 , mOnTile(true)
262 , mOffTile(false)
263 , mOp(op) {}
264
265 /// @brief Dilate a single leaf node by the current spatial scheme
266 /// stored on the instance of this NodeMaskOp. Neighbor leaf
267 /// nodes are also updated.
268 /// @details Unlike erode, dilate is expected to be called in a
269 /// single threaded context as it will update the node masks
270 /// of neighboring leaf nodes as well as the provided leaf.
271 /// @param leaf The leaf to dilate. The leaf's origin and value mask
272 /// are used to calculate the result of the dilation.
273 inline void dilate(LeafType& leaf)
274 {
275 // copy the mask
276 const MaskType mask = leaf.getValueMask();
277 this->dilate(leaf, mask);
278 }
279
280 /// @brief Dilate a single leaf node by the current spatial scheme
281 /// stored on the instance of this NodeMaskOp. The provided
282 /// mask is used in place of the actual leaf's node mask and
283 /// applied to the leaf afterwards. Neighbor leaf nodes are
284 /// also updated.
285 /// @details Unlike erode, dilate is expected to be called in a
286 /// single threaded context as it will update the node masks
287 /// of neighboring leaf nodes as well as the provided leaf.
288 /// @param leaf The leaf to dilate. The leaf's origin is used to
289 /// calculate the result of the dilation.
290 /// @param mask The node mask to use in place of the current leaf
291 /// node mask.
292 inline void dilate(LeafType& leaf, const MaskType& mask)
293 {
294 this->clear();
295 mNeighbors[0] = &(leaf.getValueMask());
296 this->setOrigin(leaf.origin());
297 switch (mOp) {
298 case NN_FACE_EDGE : { this->dilate18(mask); return; }
299 case NN_FACE_EDGE_VERTEX : { this->dilate26(mask); return; }
300 case NN_FACE : { this->dilate6(mask); return; }
301 default : {
302 assert(false && "Unknown op during dilation."); return;
303 }
304 }
305 }
306
307 /// @brief Erode a single leaf node by the current spatial scheme
308 /// stored on the instance of this NodeMaskOp.
309 /// @details Unlike dilate, this method updates the provided mask
310 /// and does not apply the result to the leaf node. The
311 /// leaf node is simply used to infer the position in the
312 /// tree to find it's neighbors. This allows erode to be
313 /// called from multiple threads
314 /// @param leaf The leaf to erode. The leaf's origin is used to
315 /// calculate the result of the erosion.
316 /// @return The eroded mask
317 inline MaskType erode(const LeafType& leaf)
318 {
319 // copy the mask
320 MaskType mask = leaf.getValueMask();
321 this->erode(leaf, mask);
322 return mask;
323 }
324
325 /// @brief Erode a single leaf node by the current spatial scheme
326 /// stored on the instance of this NodeMaskOp. The provided
327 /// mask is used in place of the actual leaf's node mask and
328 /// stores the erosion result.
329 /// @details Unlike dilate, this method updates the provided mask
330 /// and does not apply the result to the leaf node. The
331 /// leaf node is simply used to infer the position in the
332 /// tree to find it's neighbors.
333 /// @param leaf The leaf to erode. The leaf's origin is used to
334 /// calculate the result of the erosion.
335 /// @param mask The node mask to use in place of the current leaf
336 /// node mask.
337 inline void erode(const LeafType& leaf, MaskType& mask)
338 {
339 this->clear();
340 // @note leaf mask will not be modified through gather methods
341 mNeighbors[0] = const_cast<MaskType*>(&leaf.getValueMask());
342 this->setOrigin(leaf.origin());
343 switch (mOp) {
344 case NN_FACE_EDGE : { this->erode18(mask); return; }
345 case NN_FACE_EDGE_VERTEX : { this->erode26(mask); return; }
346 case NN_FACE : { this->erode6(mask); return; }
347 default : {
348 assert(false && "Unknown op during erosion."); return;
349 }
350 }
351 }
352
353 private:
354 static size_t ksize(const NearestNeighbors op) {
355 switch (op) {
356 case NN_FACE_EDGE : return 19;
357 case NN_FACE_EDGE_VERTEX : return 27;
358 case NN_FACE : return 7;
359 default : return 7;
360 }
361 }
362
363 void dilate6(const MaskType& mask);
364 void dilate18(const MaskType& mask);
365 void dilate26(const MaskType& mask);
366 void erode6(MaskType& mask);
367
368 /// @note Forward API for erosion of 18/26 trees is to use erodeActiveValues
369 /// which falls back to an inverse dilation
370 /// @todo It may still be worth investigating more optimal gathering
371 /// techniques
372 inline void erode18(MaskType&) { OPENVDB_THROW(NotImplementedError, "erode18 is not implemented yet!"); }
373 inline void erode26(MaskType&) { OPENVDB_THROW(NotImplementedError, "erode26 is not implemented yet!"); }
374
375 inline void setOrigin(const Coord& origin) { mOrigin = &origin; }
376 inline const Coord& getOrigin() const { return *mOrigin; }
377 inline void clear() { std::fill(mNeighbors.begin(), mNeighbors.end(), nullptr); }
378
379 inline void scatter(size_t n, int indx)
380 {
381 assert(n < mNeighbors.size());
382 assert(mNeighbors[n]);
383 mNeighbors[n]->template getWord<Word>(indx) |= mWord;
384
385 }
386 template<int DX, int DY, int DZ>
387 inline void scatter(size_t n, int indx)
388 {
389 assert(n < mNeighbors.size());
390 if (!mNeighbors[n]) {
391 mNeighbors[n] = this->getNeighbor<DX,DY,DZ,true>();
392 }
393 assert(mNeighbors[n]);
394 this->scatter(n, indx - (DIM - 1)*(DY + DX*DIM));
395 }
396 inline Word gather(size_t n, int indx)
397 {
398 assert(n < mNeighbors.size());
399 return mNeighbors[n]->template getWord<Word>(indx);
400 }
401 template<int DX, int DY, int DZ>
402 inline Word gather(size_t n, int indx)
403 {
404 assert(n < mNeighbors.size());
405 if (!mNeighbors[n]) {
406 mNeighbors[n] = this->getNeighbor<DX,DY,DZ,false>();
407 }
408 return this->gather(n, indx - (DIM -1)*(DY + DX*DIM));
409 }
410
411 void scatterFacesXY(int x, int y, int i1, int n, int i2);
412 void scatterEdgesXY(int x, int y, int i1, int n, int i2);
413 Word gatherFacesXY(int x, int y, int i1, int n, int i2);
414 /// @note Currently unused
415 Word gatherEdgesXY(int x, int y, int i1, int n, int i2);
416
417 template<int DX, int DY, int DZ, bool Create>
418 inline MaskType* getNeighbor()
419 {
420 const Coord xyz = mOrigin->offsetBy(DX*DIM, DY*DIM, DZ*DIM);
421 auto* leaf = mAccessor->probeLeaf(xyz);
422 if (leaf) return &(leaf->getValueMask());
423 if (mAccessor->isValueOn(xyz)) return &mOnTile;
424 if (!Create) return &mOffTile;
425 leaf = mAccessor->touchLeaf(xyz);
426 return &(leaf->getValueMask());
427 }
428
429 private:
430 const Coord* mOrigin;
431 std::vector<MaskType*> mNeighbors;
432 AccessorType* const mAccessor;
433 Word mWord;
434 MaskType mOnTile, mOffTile;
435 const NearestNeighbors mOp;
436 };// NodeMaskOp
437
438private:
439 std::unique_ptr<tree::LeafManager<TreeType>> mManagerPtr;
440 tree::LeafManager<TreeType>& mManager;
441 bool mThreaded;
442};// Morphology
443
444
445template <typename TreeT>
446typename std::enable_if<std::is_same<TreeT, typename TreeT::template ValueConverter<ValueMask>::Type>::value,
447 typename TreeT::template ValueConverter<ValueMask>::Type*>::type
448getMaskTree(TreeT& tree) { return &tree; }
449
450template <typename TreeT>
451typename std::enable_if<!std::is_same<TreeT, typename TreeT::template ValueConverter<ValueMask>::Type>::value,
452 typename TreeT::template ValueConverter<ValueMask>::Type*>::type
453getMaskTree(TreeT&) { return nullptr; }
454
455
456template <typename TreeType>
458 const NearestNeighbors nn,
459 const bool prune)
460{
461 if (iter == 0) return;
462 const size_t leafCount = mManager.leafCount();
463 if (leafCount == 0) return;
464 auto& tree = mManager.tree();
465
466 // If the nearest neighbor mode is not FACE, fall back to an
467 // inverse dilation scheme which executes over a mask topology
468 if (nn != NN_FACE) {
469 // This method 1) dilates the input topology, 2) reverse the node masks,
470 // 3) performs a final dilation and 4) subtracts the result from the original
471 // topology. A cache of the original leaf pointers is required which tracks
472 // the original leaf nodes in a mask topology. These will need their
473 // masks updated in the original tree. The first dilation may create new leaf
474 // nodes in two instances. The first is where no topology existed before. The
475 // second is where an active tile overlaps with dilated topology. These
476 // tiles will be expanded to a dense leaf nodes by topologyUnion. We need
477 // to make sure these tiles are properly turned off.
478
479 MaskTreeT mask(tree, false, TopologyCopy());
480
481 // Create a new morphology class to perform dilation over the mask
482 tree::LeafManager<MaskTreeT> manager(mask);
483 Morphology<MaskTreeT> m(manager);
484 m.setThreaded(this->getThreaded());
485
486 // perform a single dilation using the current scheme. Necessary to
487 // create edge leaf nodes and compute the active wavefront. Note that
488 // the cached array pointers will continue to be valid
489 m.dilateVoxels(1, nn, /*prune=*/false);
490
491 // compute the wavefront. If the leaf previously existed, compute the
492 // xor activity result which is guaranteed to be equal to but slightly
493 // faster than a subtraction
494 auto computeWavefront = [&](const size_t idx) {
495 auto& leaf = manager.leaf(idx);
496 auto& nodemask = leaf.getValueMask();
497 if (const auto* original = tree.probeConstLeaf(leaf.origin())) {
498 nodemask ^= original->getValueMask();
499 }
500 else {
501 // should never have a dense leaf if it didn't exist in the
502 // original tree (it was previous possible when dilateVoxels()
503 // called topologyUnion without the preservation of active
504 // tiles)
505 assert(!nodemask.isOn());
506 }
507 };
508
509 if (this->getThreaded()) {
510 tbb::parallel_for(manager.getRange(),
511 [&](const tbb::blocked_range<size_t>& r){
512 for (size_t idx = r.begin(); idx < r.end(); ++idx) {
513 computeWavefront(idx);
514 }
515 });
516 }
517 else {
518 for (size_t idx = 0; idx < manager.leafCount(); ++idx) {
519 computeWavefront(idx);
520 }
521 }
522
523 // perform the inverse dilation
524 m.dilateVoxels(iter, nn, /*prune=*/false);
525
526 // subtract the inverse dilation from the original node masks
527 auto subtractTopology = [&](const size_t idx) {
528 auto& leaf = mManager.leaf(idx);
529 const auto* maskleaf = mask.probeConstLeaf(leaf.origin());
530 assert(maskleaf);
531 leaf.getValueMask() -= maskleaf->getValueMask();
532 };
533
534 if (this->getThreaded()) {
535 tbb::parallel_for(mManager.getRange(),
536 [&](const tbb::blocked_range<size_t>& r){
537 for (size_t idx = r.begin(); idx < r.end(); ++idx) {
538 subtractTopology(idx);
539 }
540 });
541 }
542 else {
543 for (size_t idx = 0; idx < leafCount; ++idx) {
544 subtractTopology(idx);
545 }
546 }
547 }
548 else {
549 // NN_FACE erosion scheme
550
551 // Save the value masks of all leaf nodes.
552 std::vector<MaskType> nodeMasks;
553 this->copyMasks(nodeMasks);
554
555 if (this->getThreaded()) {
556 const auto range = mManager.getRange();
557 for (size_t i = 0; i < iter; ++i) {
558 // For each leaf, in parallel, gather neighboring off values
559 // and update the cached value mask
560 tbb::parallel_for(range,
561 [&](const tbb::blocked_range<size_t>& r) {
562 AccessorType accessor(tree);
563 NodeMaskOp cache(accessor, nn);
564 for (size_t idx = r.begin(); idx < r.end(); ++idx) {
565 const auto& leaf = mManager.leaf(idx);
566 if (leaf.isEmpty()) continue;
567 // original bit-mask of current leaf node
568 MaskType& newMask = nodeMasks[idx];
569 cache.erode(leaf, newMask);
570 }
571 });
572
573 // update the masks after all nodes have been eroded
574 tbb::parallel_for(range,
575 [&](const tbb::blocked_range<size_t>& r){
576 for (size_t idx = r.begin(); idx < r.end(); ++idx)
577 mManager.leaf(idx).setValueMask(nodeMasks[idx]);
578 });
579 }
580 }
581 else {
582 AccessorType accessor(tree);
583 NodeMaskOp cache(accessor, nn);
584 for (size_t i = 0; i < iter; ++i) {
585 // For each leaf, in parallel, gather neighboring off values
586 // and update the cached value mask
587 for (size_t idx = 0; idx < leafCount; ++idx) {
588 const auto& leaf = mManager.leaf(idx);
589 if (leaf.isEmpty()) continue;
590 // original bit-mask of current leaf node
591 MaskType& newMask = nodeMasks[idx];
592 cache.erode(leaf, newMask);
593 }
594
595 for (size_t idx = 0; idx < leafCount; ++idx) {
596 mManager.leaf(idx).setValueMask(nodeMasks[idx]);
597 }
598 }
599 }
600 }
601
602 // if prune, replace any inactive nodes
603 if (prune) {
604 tools::prune(mManager.tree(),
605 zeroVal<typename TreeType::ValueType>(),
606 this->getThreaded());
607 mManager.rebuild(!this->getThreaded());
608 }
609}
610
611template <typename TreeType>
613 const NearestNeighbors nn,
614 const bool prune,
615 const bool preserveMaskLeafNodes)
616{
617 if (iter == 0) return;
618
619 const bool threaded = this->getThreaded();
620
621 // Actual dilation op. main implementation is single threaded. Note that this
622 // is templated (auto-ed) as the threaded implemenation may call this with a
623 // different value type to the source morphology class
624 // @note GCC 6.4.0 crashes trying to compile this lambda with [&] capture
625 auto dilate = [iter, nn, threaded](auto& manager, const bool collapse) {
626
627 using LeafManagerT = typename std::decay<decltype(manager)>::type;
628 using TreeT = typename LeafManagerT::TreeType;
629 using ValueT = typename TreeT::ValueType;
630 using LeafT = typename TreeT::LeafNodeType;
631
632 // this is only used for the impl of copyMasks
633 Morphology<TreeT> m(manager);
634 m.setThreaded(threaded);
635
636 TreeT& tree = manager.tree();
637 tree::ValueAccessor<TreeT> accessor(tree);
638
639 // build cache objects
640 typename Morphology<TreeT>::NodeMaskOp cache(accessor, nn);
641 std::vector<MaskType> nodeMasks;
642 std::vector<std::unique_ptr<LeafT>> nodes;
643 const ValueT& bg = tree.background();
644 const bool steal = iter > 1;
645
646 for (size_t i = 0; i < iter; ++i) {
647 if (i > 0) manager.rebuild(!threaded);
648 // If the leaf count is zero, we can stop dilation
649 const size_t leafCount = manager.leafCount();
650 if (leafCount == 0) return;
651
652 // Copy the masks. This only resizes if necessary. As we're stealing/replacing
653 // dense nodes, it's possible we don't need to re-allocate the cache.
654 m.copyMasks(nodeMasks);
655
656 // For each node, dilate the mask into itself and neighboring leaf nodes.
657 // If the node was originally dense (all active), steal/replace it so
658 // subsequent iterations are faster
659 manager.foreach([&](auto& leaf, const size_t idx) {
660 // original bit-mask of current leaf node
661 const MaskType& oldMask = nodeMasks[idx];
662 const bool dense = oldMask.isOn();
663 cache.dilate(leaf, oldMask);
664 if (!dense) return;
665 // This node does not need to be visited again - replace or steal
666 if (collapse) {
667 // if collapse, replace this dense leaf with an active background tile
668 accessor.addTile(1, leaf.origin(), bg, true);
669 }
670 else if (steal) {
671 // otherwise, temporarily steal this node
672 nodes.emplace_back(
673 tree.template stealNode<LeafT>(leaf.origin(),
674 zeroVal<ValueT>(), true));
675 }
676 }, false);
677 }
678
679 if (nodes.empty()) return;
680 // Add back all dense nodes
681 for (auto& node : nodes) {
682 accessor.addLeaf(node.release());
683 }
684 };
685
686 //
687
688 if (!threaded) {
689 // single threaded dilation. If it's a mask tree we can collapse
690 // nodes during the dilation, otherwise we must call prune afterwards
691 constexpr bool isMask = std::is_same<TreeType, MaskTreeT>::value;
692 dilate(mManager, isMask && prune);
693 if (!isMask && prune) {
694 tools::prune(mManager.tree(),
695 zeroVal<typename TreeType::ValueType>(),
696 threaded);
697 }
698 }
699 else {
700 // multi-threaded dilation
701
702 // Steal or create mask nodes that represent the current leaf nodes.
703 // If the input is a mask tree, optionally re-allocate the nodes if
704 // preserveMaskLeafNodes is true. This ensures that leaf node
705 // pointers are not changed in the source tree. Stealing the mask
706 // nodes is significantly faster as it also avoids a post union.
707 std::vector<MaskLeafT*> array;
708 MaskTreeT* mask = getMaskTree(mManager.tree());
709
710 if (!mask) {
711 MaskTreeT topology;
712 topology.topologyUnion(mManager.tree());
713 array.reserve(mManager.leafCount());
714 topology.stealNodes(array);
715 }
716 else if (preserveMaskLeafNodes) {
717 mask = nullptr; // act as if theres no mask tree
718 array.resize(mManager.leafCount());
719 tbb::parallel_for(mManager.getRange(),
720 [&](const tbb::blocked_range<size_t>& r){
721 for (size_t idx = r.begin(); idx < r.end(); ++idx) {
722 array[idx] = new MaskLeafT(mManager.leaf(idx));
723 }
724 });
725 }
726 else {
727 array.reserve(mManager.leafCount());
728 mask->stealNodes(array);
729 }
730
731 // @note this grain size is used for optimal threading
732 const size_t numThreads = size_t(tbb::this_task_arena::max_concurrency());
733 const size_t subTreeSize = math::Max(size_t(1), array.size()/(2*numThreads));
734
735 // perform recursive dilation to sub trees
736 tbb::enumerable_thread_specific<std::unique_ptr<MaskTreeT>> pool;
737 MaskLeafT** start = array.data();
738 tbb::parallel_for(tbb::blocked_range<MaskLeafT**>(start, start + array.size(), subTreeSize),
739 [&](const tbb::blocked_range<MaskLeafT**>& range) {
740 std::unique_ptr<MaskTreeT> mask(new MaskTreeT);
741 for (MaskLeafT** it = range.begin(); it != range.end(); ++it) mask->addLeaf(*it);
742 tree::LeafManager<MaskTreeT> manager(*mask, range.begin(), range.end());
743 dilate(manager, prune);
744 auto& subtree = pool.local();
745 if (!subtree) subtree = std::move(mask);
746 else subtree->merge(*mask, MERGE_ACTIVE_STATES);
747 });
748
749 if (!pool.empty()) {
750 auto piter = pool.begin();
751 MaskTreeT& subtree = mask ? *mask : **piter++;
752 for (; piter != pool.end(); ++piter) subtree.merge(**piter);
753 // prune, ensures partially merged nodes that may have become
754 // dense are converted to tiles
755 if (prune) tools::prune(subtree, zeroVal<typename MaskTreeT::ValueType>(), threaded);
756 // copy final topology onto dest. If mask exists, then this
757 // has already been handled by the above subtree merges
758 if (!mask) mManager.tree().topologyUnion(subtree, /*preserve-active-tiles*/true);
759 }
760 }
761
762 // sync
763 mManager.rebuild(!threaded);
764}
765
766
767template <typename TreeType>
768inline void
769Morphology<TreeType>::NodeMaskOp::erode6(MaskType& mask)
770{
771 for (int x = 0; x < DIM; ++x) {
772 for (int y = 0, n = (x << LOG2DIM); y < DIM; ++y, ++n) {
773 // Extract the portion of the original mask that corresponds to a row in z.
774 if (Word& w = mask.template getWord<Word>(n)) {
775 // erode in two z directions (this is first since it uses the original w)
776 w = Word(w &
777 (Word(w<<1 | (this->template gather<0,0,-1>(1, n)>>(DIM-1))) &
778 Word(w>>1 | (this->template gather<0,0, 1>(2, n)<<(DIM-1)))));
779 w = Word(w & this->gatherFacesXY(x, y, 0, n, 3));
780 }
781 }// loop over y
782 }//loop over x
783}
784
785template <typename TreeType>
786inline void
787Morphology<TreeType>::NodeMaskOp::dilate6(const MaskType& mask)
788{
789 for (int x = 0; x < DIM; ++x ) {
790 for (int y = 0, n = (x << LOG2DIM);
791 y < DIM; ++y, ++n) {
792 // Extract the portion of the original mask that corresponds to a row in z.
793 if (const Word w = mask.template getWord<Word>(n)) {
794 // Dilate the current leaf in the +z and -z direction
795 this->mWord = Word(w | (w>>1) | (w<<1));
796 this->scatter(0, n);
797 // Dilate into neighbor leaf in the -z direction
798 if ( (this->mWord = Word(w<<(DIM-1))) ) {
799 this->template scatter< 0, 0,-1>(1, n);
800 }
801 // Dilate into neighbor leaf in the +z direction
802 if ( (this->mWord = Word(w>>(DIM-1))) ) {
803 this->template scatter< 0, 0, 1>(2, n);
804 }
805 // Dilate in the xy-face directions relative to the center leaf
806 this->mWord = w;
807 this->scatterFacesXY(x, y, 0, n, 3);
808 }
809 }// loop over y
810 }//loop over x
811}
812
813template <typename TreeType>
814inline void
815Morphology<TreeType>::NodeMaskOp::dilate18(const MaskType& mask)
816{
817 //origins of neighbor leaf nodes in the -z and +z directions
818 const Coord origin = this->getOrigin();
819 const Coord orig_mz = origin.offsetBy(0, 0, -DIM);
820 const Coord orig_pz = origin.offsetBy(0, 0, DIM);
821 for (int x = 0; x < DIM; ++x ) {
822 for (int y = 0, n = (x << LOG2DIM); y < DIM; ++y, ++n) {
823 if (const Word w = mask.template getWord<Word>(n)) {
824 {
825 this->mWord = Word(w | (w>>1) | (w<<1));
826 this->setOrigin(origin);
827 this->scatter(0, n);
828 this->scatterFacesXY(x, y, 0, n, 3);
829 this->mWord = w;
830 this->scatterEdgesXY(x, y, 0, n, 3);
831 }
832 if ( (this->mWord = Word(w<<(DIM-1))) ) {
833 this->setOrigin(origin);
834 this->template scatter< 0, 0,-1>(1, n);
835 this->setOrigin(orig_mz);
836 this->scatterFacesXY(x, y, 1, n, 11);
837 }
838 if ( (this->mWord = Word(w>>(DIM-1))) ) {
839 this->setOrigin(origin);
840 this->template scatter< 0, 0, 1>(2, n);
841 this->setOrigin(orig_pz);
842 this->scatterFacesXY(x, y, 2, n, 15);
843 }
844 }
845 }// loop over y
846 }//loop over x
847}
848
849
850template <typename TreeType>
851inline void
852Morphology<TreeType>::NodeMaskOp::dilate26(const MaskType& mask)
853{
854 //origins of neighbor leaf nodes in the -z and +z directions
855 const Coord origin = this->getOrigin();
856 const Coord orig_mz = origin.offsetBy(0, 0, -DIM);
857 const Coord orig_pz = origin.offsetBy(0, 0, DIM);
858 for (int x = 0; x < DIM; ++x) {
859 for (int y = 0, n = (x << LOG2DIM); y < DIM; ++y, ++n) {
860 if (const Word w = mask.template getWord<Word>(n)) {
861 {
862 this->mWord = Word(w | (w>>1) | (w<<1));
863 this->setOrigin(origin);
864 this->scatter(0, n);
865 this->scatterFacesXY(x, y, 0, n, 3);
866 this->scatterEdgesXY(x, y, 0, n, 3);
867 }
868 if ( (this->mWord = Word(w<<(DIM-1))) ) {
869 this->setOrigin(origin);
870 this->template scatter< 0, 0,-1>(1, n);
871 this->setOrigin(orig_mz);
872 this->scatterFacesXY(x, y, 1, n, 11);
873 this->scatterEdgesXY(x, y, 1, n, 11);
874 }
875 if ( (this->mWord = Word(w>>(DIM-1))) ) {
876 this->setOrigin(origin);
877 this->template scatter< 0, 0, 1>(2, n);
878 this->setOrigin(orig_pz);
879 this->scatterFacesXY(x, y, 2, n, 19);
880 this->scatterEdgesXY(x, y, 2, n, 19);
881 }
882 }
883 }// loop over y
884 }//loop over x
885}
886
887template<typename TreeType>
888inline void
889Morphology<TreeType>::NodeMaskOp::scatterFacesXY(int x, int y, int i1, int n, int i2)
890{
891 // dilate current leaf or neighbor in the -x direction
892 if (x > 0) {
893 this->scatter(i1, n-DIM);
894 } else {
895 this->template scatter<-1, 0, 0>(i2, n);
896 }
897 // dilate current leaf or neighbor in the +x direction
898 if (x < DIM-1) {
899 this->scatter(i1, n+DIM);
900 } else {
901 this->template scatter< 1, 0, 0>(i2+1, n);
902 }
903 // dilate current leaf or neighbor in the -y direction
904 if (y > 0) {
905 this->scatter(i1, n-1);
906 } else {
907 this->template scatter< 0,-1, 0>(i2+2, n);
908 }
909 // dilate current leaf or neighbor in the +y direction
910 if (y < DIM-1) {
911 this->scatter(i1, n+1);
912 } else {
913 this->template scatter< 0, 1, 0>(i2+3, n);
914 }
915}
916
917
918template<typename TreeType>
919inline void
920Morphology<TreeType>::NodeMaskOp::scatterEdgesXY(int x, int y, int i1, int n, int i2)
921{
922 if (x > 0) {
923 if (y > 0) {
924 this->scatter(i1, n-DIM-1);
925 } else {
926 this->template scatter< 0,-1, 0>(i2+2, n-DIM);
927 }
928 if (y < DIM-1) {
929 this->scatter(i1, n-DIM+1);
930 } else {
931 this->template scatter< 0, 1, 0>(i2+3, n-DIM);
932 }
933 } else {
934 if (y < DIM-1) {
935 this->template scatter<-1, 0, 0>(i2 , n+1);
936 } else {
937 this->template scatter<-1, 1, 0>(i2+7, n );
938 }
939 if (y > 0) {
940 this->template scatter<-1, 0, 0>(i2 , n-1);
941 } else {
942 this->template scatter<-1,-1, 0>(i2+4, n );
943 }
944 }
945 if (x < DIM-1) {
946 if (y > 0) {
947 this->scatter(i1, n+DIM-1);
948 } else {
949 this->template scatter< 0,-1, 0>(i2+2, n+DIM);
950 }
951 if (y < DIM-1) {
952 this->scatter(i1, n+DIM+1);
953 } else {
954 this->template scatter< 0, 1, 0>(i2+3, n+DIM);
955 }
956 } else {
957 if (y > 0) {
958 this->template scatter< 1, 0, 0>(i2+1, n-1);
959 } else {
960 this->template scatter< 1,-1, 0>(i2+6, n );
961 }
962 if (y < DIM-1) {
963 this->template scatter< 1, 0, 0>(i2+1, n+1);
964 } else {
965 this->template scatter< 1, 1, 0>(i2+5, n );
966 }
967 }
968}
969
970
971template<typename TreeType>
972inline typename Morphology<TreeType>::NodeMaskOp::Word
973Morphology<TreeType>::NodeMaskOp::gatherFacesXY(int x, int y, int i1, int n, int i2)
974{
975 // erode current leaf or neighbor in negative x-direction
976 Word w = x > 0 ?
977 this->gather(i1, n - DIM) :
978 this->template gather<-1,0,0>(i2, n);
979
980 // erode current leaf or neighbor in positive x-direction
981 w = Word(w & (x < DIM - 1 ?
982 this->gather(i1, n + DIM) :
983 this->template gather<1,0,0>(i2 + 1, n)));
984
985 // erode current leaf or neighbor in negative y-direction
986 w = Word(w & (y > 0 ?
987 this->gather(i1, n - 1) :
988 this->template gather<0,-1,0>(i2 + 2, n)));
989
990 // erode current leaf or neighbor in positive y-direction
991 w = Word(w & (y < DIM - 1 ?
992 this->gather(i1, n + 1) :
993 this->template gather<0,1,0>(i2+3, n)));
994
995 return w;
996}
997
998
999template<typename TreeType>
1000inline typename Morphology<TreeType>::NodeMaskOp::Word
1001Morphology<TreeType>::NodeMaskOp::gatherEdgesXY(int x, int y, int i1, int n, int i2)
1002{
1003 Word w = ~Word(0);
1004
1005 if (x > 0) {
1006 w &= y > 0 ? this->gather(i1, n-DIM-1) :
1007 this->template gather< 0,-1, 0>(i2+2, n-DIM);
1008 w &= y < DIM-1 ? this->gather(i1, n-DIM+1) :
1009 this->template gather< 0, 1, 0>(i2+3, n-DIM);
1010 } else {
1011 w &= y < DIM-1 ? this->template gather<-1, 0, 0>(i2 , n+1):
1012 this->template gather<-1, 1, 0>(i2+7, n );
1013 w &= y > 0 ? this->template gather<-1, 0, 0>(i2 , n-1):
1014 this->template gather<-1,-1, 0>(i2+4, n );
1015 }
1016 if (x < DIM-1) {
1017 w &= y > 0 ? this->gather(i1, n+DIM-1) :
1018 this->template gather< 0,-1, 0>(i2+2, n+DIM);
1019 w &= y < DIM-1 ? this->gather(i1, n+DIM+1) :
1020 this->template gather< 0, 1, 0>(i2+3, n+DIM);
1021 } else {
1022 w &= y > 0 ? this->template gather< 1, 0, 0>(i2+1, n-1):
1023 this->template gather< 1,-1, 0>(i2+6, n );
1024 w &= y < DIM-1 ? this->template gather< 1, 0, 0>(i2+1, n+1):
1025 this->template gather< 1, 1, 0>(i2+5, n );
1026 }
1027
1028 return w;
1029}
1030
1031} // namespace morphology
1032
1033
1034/////////////////////////////////////////////////////////////////////
1035/////////////////////////////////////////////////////////////////////
1036
1037/// @cond OPENVDB_DOCS_INTERNAL
1038
1039namespace morph_internal {
1040template <typename T> struct Adapter {
1041 using TreeType = T;
1042 static TreeType& get(T& tree) { return tree; }
1043 static void sync(T&) {} // no-op
1044};
1045template <typename T>
1046struct Adapter<openvdb::tree::LeafManager<T>> {
1047 using TreeType = T;
1048 static TreeType& get(openvdb::tree::LeafManager<T>& M) { return M.tree(); }
1049 static void sync(openvdb::tree::LeafManager<T>& M) { M.rebuild(); }
1050};
1051}
1052
1053/// @endcond
1054
1055template<typename TreeOrLeafManagerT>
1056void dilateActiveValues(TreeOrLeafManagerT& treeOrLeafM,
1057 const int iterations,
1058 const NearestNeighbors nn,
1059 const TilePolicy mode,
1060 const bool threaded)
1061{
1062 using AdapterT = morph_internal::Adapter<TreeOrLeafManagerT>;
1063 using TreeT = typename AdapterT::TreeType;
1064 using MaskT = typename TreeT::template ValueConverter<ValueMask>::Type;
1065
1066 if (iterations <= 0) return;
1067
1068 if (mode == IGNORE_TILES) {
1069 morphology::Morphology<TreeT> morph(treeOrLeafM);
1070 morph.setThreaded(threaded);
1071 // This will also sync the leaf manager
1072 morph.dilateVoxels(static_cast<size_t>(iterations), nn, /*prune=*/false);
1073 return;
1074 }
1075
1076 // The following branching optimises from the different tree types
1077 // and TilePolicy combinations
1078
1079 auto& tree = AdapterT::get(treeOrLeafM);
1080
1081 // If the input is a mask tree, don't copy the topology - voxelize
1082 // it directly and let the morphology class directly steal/prune
1083 // its nodes
1084 constexpr bool isMask = std::is_same<TreeT, MaskT>::value;
1085
1086 if (isMask || mode == EXPAND_TILES) {
1087 tree.voxelizeActiveTiles();
1088 AdapterT::sync(treeOrLeafM);
1089 morphology::Morphology<TreeT> morph(treeOrLeafM);
1090 morph.setThreaded(threaded);
1091
1092 if (mode == PRESERVE_TILES) {
1093 morph.dilateVoxels(static_cast<size_t>(iterations), nn, /*prune=*/true);
1094 }
1095 else {
1096 assert(mode == EXPAND_TILES);
1097 morph.dilateVoxels(static_cast<size_t>(iterations), nn, /*prune=*/false);
1098 }
1099 return;
1100 }
1101
1102 // If the tree TreeType being dilated is not a MaskTree, always copy
1103 // the topology over onto a MaskTree, perform the required dilation
1104 // and copy the final topology back. This technique avoids unnecessary
1105 // allocation with tile expansion and correctly preserves the tree
1106 // topology.
1107 //
1108 // Note that we also always use a mask if the tile policy is PRESERVE_TILES
1109 // due to the way the underlying dilation only works on voxels.
1110 // @todo Investigate tile based dilation
1111 assert(mode == PRESERVE_TILES);
1112
1113 MaskT topology;
1114 topology.topologyUnion(tree);
1115 topology.voxelizeActiveTiles();
1116
1117 morphology::Morphology<MaskT> morph(topology);
1118 morph.setThreaded(threaded);
1119 morph.dilateVoxels(static_cast<size_t>(iterations), nn, /*prune=*/true);
1120
1121 tree.topologyUnion(topology, /*preserve-tiles*/true);
1122 topology.clear();
1123
1124 // @note this is necessary to match the behaviour of mask tree dilation
1125 // where source partial leaf nodes that become dense are also
1126 // converted into tiles, not simply newly created dense nodes
1127 tools::prune(tree, zeroVal<typename TreeT::ValueType>(), threaded);
1128 AdapterT::sync(treeOrLeafM);
1129}
1130
1131
1132template<typename TreeOrLeafManagerT>
1133void erodeActiveValues(TreeOrLeafManagerT& treeOrLeafM,
1134 const int iterations,
1135 const NearestNeighbors nn,
1136 const TilePolicy mode,
1137 const bool threaded)
1138{
1139 using AdapterT = morph_internal::Adapter<TreeOrLeafManagerT>;
1140 using TreeT = typename AdapterT::TreeType;
1141 using MaskT = typename TreeT::template ValueConverter<ValueMask>::Type;
1142
1143 if (iterations <= 0) return;
1144
1145 // If the tile policiy is PRESERVE_TILES, peform the erosion on a
1146 // voxelized mask grid followed by a topology intersection such that
1147 // the original uneroded topology is preserved.
1148 if (mode == PRESERVE_TILES) {
1149 auto& tree = AdapterT::get(treeOrLeafM);
1150 MaskT topology;
1151 topology.topologyUnion(tree);
1152 topology.voxelizeActiveTiles();
1153
1154 {
1155 morphology::Morphology<MaskT> morph(topology);
1156 morph.setThreaded(threaded);
1157 morph.erodeVoxels(static_cast<size_t>(iterations), nn, /*prune=*/false);
1158 }
1159
1160 // prune to ensure topologyIntersection does not expand tiles
1161 // which have not been changed
1162 tools::prune(topology, zeroVal<typename MaskT::ValueType>(), threaded);
1163 tree.topologyIntersection(topology);
1164 AdapterT::sync(treeOrLeafM);
1165 return;
1166 }
1167
1168 if (mode == EXPAND_TILES) {
1169 // if expanding, voxelize everything first if there are active tiles
1170 // @note check first to avoid any unnecessary rebuilds
1171 auto& tree = AdapterT::get(treeOrLeafM);
1172 if (tree.hasActiveTiles()) {
1173 tree.voxelizeActiveTiles();
1174 AdapterT::sync(treeOrLeafM);
1175 }
1176 }
1177
1178 // ignoring tiles. They won't be eroded
1179 morphology::Morphology<TreeT> morph(treeOrLeafM);
1180 morph.setThreaded(threaded);
1181 morph.erodeVoxels(static_cast<size_t>(iterations), nn, /*prune=*/false);
1182}
1183
1184
1185////////////////////////////////////////
1186
1187
1188// Explicit Template Instantiation
1189
1190#ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION
1191
1192#ifdef OPENVDB_INSTANTIATE_MORPHOLOGY
1194#endif
1195
1196#define _FUNCTION(TreeT) \
1197 void dilateActiveValues(TreeT&, \
1198 const int, const NearestNeighbors, const TilePolicy, const bool)
1200#undef _FUNCTION
1201
1202#define _FUNCTION(TreeT) \
1203 void dilateActiveValues(tree::LeafManager<TreeT>&, \
1204 const int, const NearestNeighbors, const TilePolicy, const bool)
1206#undef _FUNCTION
1207
1208#define _FUNCTION(TreeT) \
1209 void erodeActiveValues(TreeT&, \
1210 const int, const NearestNeighbors, const TilePolicy, const bool)
1212#undef _FUNCTION
1213
1214#define _FUNCTION(TreeT) \
1215 void erodeActiveValues(tree::LeafManager<TreeT>&, \
1216 const int, const NearestNeighbors, const TilePolicy, const bool)
1218#undef _FUNCTION
1219
1220#endif // OPENVDB_USE_EXPLICIT_INSTANTIATION
1221
1222
1223} // namespace tools
1224} // namespace OPENVDB_VERSION_NAME
1225} // namespace openvdb
1226
1227#endif // OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED
Implementation of topological activation/deactivation.
A LeafManager manages a linear array of pointers to a given tree's leaf nodes, as well as optional au...
Attribute-owned data structure for points. Point attributes are stored in leaf nodes and ordered by v...
Defined various multi-threaded utility functions for trees.
ValueAccessors are designed to help accelerate accesses into the OpenVDB Tree structures by storing c...
Definition Exceptions.h:61
Tag dispatch class that distinguishes topology copy constructors from deep copy constructors.
Definition Types.h:683
Signed (x, y, z) 32-bit integer coordinates.
Definition Coord.h:25
Dilation/Erosion operations over a Trees leaf level voxel topology.
Definition Morphology.h:155
Morphology(tree::LeafManager< TreeType > &tree)
Definition Morphology.h:169
typename TreeType::ValueType ValueType
Definition Morphology.h:159
void dilateVoxels(const size_t iter, const NearestNeighbors nn, const bool prune=false, const bool preserveMaskLeafNodes=false)
Topologically dilate all voxels by the provided nearest neighbor scheme and optionally collapse const...
Definition Morphology.h:612
typename LeafType::NodeMaskType MaskType
Definition Morphology.h:158
typename MaskTreeT::LeafNodeType MaskLeafT
Definition Morphology.h:161
bool getThreaded() const
Return whether this class is using multi-threading.
Definition Morphology.h:175
void setThreaded(const bool threaded)
Set whether to use multi-threading.
Definition Morphology.h:178
void erodeVoxels(const size_t iter, const NearestNeighbors nn, const bool prune=false)
Topologically erode all voxels by the provided nearest neighbor scheme and optionally collapse consta...
Definition Morphology.h:457
Morphology(TreeType &tree)
Definition Morphology.h:164
const tree::LeafManager< TreeType > & leafManager() const
Return a const reference to the leaf manager.
Definition Morphology.h:181
typename TreeType::template ValueConverter< ValueMask >::Type MaskTreeT
Definition Morphology.h:160
typename TreeType::LeafNodeType LeafType
Definition Morphology.h:157
void copyMasks(std::vector< MaskType > &masks) const
Copy the current node masks onto the provided vector. The vector is resized if necessary.
Definition Morphology.h:214
This class manages a linear array of pointers to a given tree's leaf nodes, as well as optional auxil...
Definition LeafManager.h:85
LeafType & leaf(size_t leafIdx) const
Return a pointer to the leaf node at index leafIdx in the array.
Definition LeafManager.h:318
RangeType getRange(size_t grainsize=1) const
Return a tbb::blocked_range of leaf array indices.
Definition LeafManager.h:342
The Value Accessor Implementation and API methods. The majoirty of the API matches the API of a compa...
Definition ValueAccessor.h:367
void addLeaf(LeafNodeT *leaf)
Add the specified leaf to this tree, possibly creating a child branch in the process....
Definition ValueAccessor.h:729
void addTile(Index level, const Coord &xyz, const ValueType &value, bool state)
Add a tile at the specified tree level that contains the coordinate xyz, possibly deleting existing n...
Definition ValueAccessor.h:754
std::enable_if< std::is_same< TreeT, typenameTreeT::templateValueConverter< ValueMask >::Type >::value, typenameTreeT::templateValueConverter< ValueMask >::Type * >::type getMaskTree(TreeT &tree)
Definition Morphology.h:448
NearestNeighbors
Voxel topology of nearest neighbors.
Definition Morphology.h:59
@ NN_FACE_EDGE_VERTEX
Definition Morphology.h:59
@ NN_FACE_EDGE
Definition Morphology.h:59
@ NN_FACE
Definition Morphology.h:59
void erodeActiveValues(TreeOrLeafManagerT &tree, const int iterations=1, const NearestNeighbors nn=NN_FACE, const TilePolicy mode=PRESERVE_TILES, const bool threaded=true)
Topologically erode all active values (i.e. both voxels and tiles) in a tree using one of three neare...
Definition Morphology.h:1133
void dilateActiveValues(TreeOrLeafManagerT &tree, const int iterations=1, const NearestNeighbors nn=NN_FACE, const TilePolicy mode=PRESERVE_TILES, const bool threaded=true)
Topologically dilate all active values (i.e. both voxels and tiles) in a tree using one of three near...
Definition Morphology.h:1056
void prune(TreeT &tree, typename TreeT::ValueType tolerance=zeroVal< typename TreeT::ValueType >(), bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing with tiles any nodes whose values are all the same...
Definition Prune.h:335
TilePolicy
Different policies when dilating trees with active tiles.
Definition Morphology.h:81
@ EXPAND_TILES
Definition Morphology.h:81
@ IGNORE_TILES
Definition Morphology.h:81
@ PRESERVE_TILES
Definition Morphology.h:81
int32_t Int32
Definition Types.h:56
Definition Exceptions.h:13
#define OPENVDB_THROW(exception, message)
Definition Exceptions.h:74
Node Mask dilation/erosion operations for individual leaf nodes on a given tree. The leaf node may op...
Definition Morphology.h:242
typename std::conditional< LOG2DIM==3, uint8_t, typename std::conditional< LOG2DIM==4, uint16_t, typename std::conditional< LOG2DIM==5, uint32_t, typename std::conditional< LOG2DIM==6, uint64_t, void >::type >::type >::type >::type Word
Definition Morphology.h:247
void dilate(LeafType &leaf, const MaskType &mask)
Dilate a single leaf node by the current spatial scheme stored on the instance of this NodeMaskOp....
Definition Morphology.h:292
MaskType erode(const LeafType &leaf)
Erode a single leaf node by the current spatial scheme stored on the instance of this NodeMaskOp.
Definition Morphology.h:317
void erode(const LeafType &leaf, MaskType &mask)
Erode a single leaf node by the current spatial scheme stored on the instance of this NodeMaskOp....
Definition Morphology.h:337
void dilate(LeafType &leaf)
Dilate a single leaf node by the current spatial scheme stored on the instance of this NodeMaskOp....
Definition Morphology.h:273
NodeMaskOp(AccessorType &accessor, const NearestNeighbors op)
Definition Morphology.h:256
#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:212
#define OPENVDB_ALL_TREE_INSTANTIATE(Function)
Definition version.h.in:161