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