OpenVDB  12.0.0
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>
27 #include <openvdb/openvdb.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 {
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>
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>
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>
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;
164 
165  Morphology(TreeType& tree)
166  : mManagerPtr(new tree::LeafManager<TreeType>(tree))
167  , mManager(*mManagerPtr)
168  , mThreaded(true) {}
169 
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 
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
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
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
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
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
890 Morphology<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
921 Morphology<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>
974 Morphology<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>
1002 Morphology<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
1195 #endif
1196 
1197 #define _FUNCTION(TreeT) \
1198  void dilateActiveValues(TreeT&, \
1199  const int, const NearestNeighbors, const TilePolicy, const bool)
1201 #undef _FUNCTION
1202 
1203 #define _FUNCTION(TreeT) \
1204  void dilateActiveValues(tree::LeafManager<TreeT>&, \
1205  const int, const NearestNeighbors, const TilePolicy, const bool)
1207 #undef _FUNCTION
1208 
1209 #define _FUNCTION(TreeT) \
1210  void erodeActiveValues(TreeT&, \
1211  const int, const NearestNeighbors, const TilePolicy, const bool)
1213 #undef _FUNCTION
1214 
1215 #define _FUNCTION(TreeT) \
1216  void erodeActiveValues(tree::LeafManager<TreeT>&, \
1217  const int, const NearestNeighbors, const TilePolicy, const bool)
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
typename LeafType::NodeMaskType MaskType
Definition: Morphology.h:159
Definition: Morphology.h:60
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
Dilation/Erosion operations over a Trees leaf level voxel topology.
Definition: Morphology.h:155
TilePolicy
Different policies when dilating trees with active tiles.
Definition: Morphology.h:82
The Value Accessor Implementation and API methods. The majoirty of the API matches the API of a compa...
Definition: ValueAccessor.h:68
#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 TreeType::LeafNodeType LeafType
Definition: Morphology.h:158
NearestNeighbors
Voxel topology of nearest neighbors.
Definition: Morphology.h:60
const tree::LeafManager< TreeType > & leafManager() const
Return a const reference to the leaf manager.
Definition: Morphology.h:182
Definition: Morphology.h:82
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
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
const Type & Max(const Type &a, const Type &b)
Return the maximum of two values.
Definition: Math.h:595
Morphology(tree::LeafManager< TreeType > &tree)
Definition: Morphology.h:170
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:252
int32_t Int32
Definition: Types.h:56
OutGridT XformOp & op
Definition: ValueTransformer.h:139
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
Implementation of topological activation/deactivation.
Defined various multi-threaded utility functions for trees.
typename TreeType::ValueType ValueType
Definition: Morphology.h:160
void setThreaded(const bool threaded)
Set whether to use multi-threading.
Definition: Morphology.h:179
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
size_t leafCount() const
Return the number of leaf nodes.
Definition: LeafManager.h:288
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
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
#define OPENVDB_ALL_TREE_INSTANTIATE(Function)
Definition: version.h.in:166
#define OPENVDB_ASSERT(X)
Definition: Assert.h:41
Morphology(TreeType &tree)
Definition: Morphology.h:165
Definition: Morphology.h:82
bool getThreaded() const
Return whether this class is using multi-threading.
Definition: Morphology.h:176
LeafType & leaf(size_t leafIdx) const
Return a pointer to the leaf node at index leafIdx in the array.
Definition: LeafManager.h:319
Definition: Morphology.h:82
OutGridT XformOp bool threaded
Definition: ValueTransformer.h:140
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
Definition: Exceptions.h:13
NodeMaskOp(AccessorType &accessor, const NearestNeighbors op)
Definition: Morphology.h:257
void addLeaf(LeafNodeT *leaf)
Add the specified leaf to this tree, possibly creating a child branch in the process. If the leaf node already exists, replace it.
Definition: ValueAccessor.h:729
ValueAccessors are designed to help accelerate accesses into the OpenVDB Tree structures by storing c...
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
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
Definition: Morphology.h:60
Definition: Exceptions.h:61
Attribute-owned data structure for points. Point attributes are stored in leaf nodes and ordered by v...
typename TreeType::template ValueConverter< ValueMask >::Type MaskTreeT
Definition: Morphology.h:161
Tag dispatch class that distinguishes topology copy constructors from deep copy constructors.
Definition: Types.h:683
std::enable_if<!std::is_same< TreeT, typename TreeT::template ValueConverter< ValueMask >::Type >::value, typename TreeT::template ValueConverter< ValueMask >::Type * >::type getMaskTree(TreeT &)
Definition: Morphology.h:454
A LeafManager manages a linear array of pointers to a given tree&#39;s leaf nodes, as well as optional au...
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h.in:121
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
typename MaskTreeT::LeafNodeType MaskLeafT
Definition: Morphology.h:162
RangeType getRange(size_t grainsize=1) const
Return a tbb::blocked_range of leaf array indices.
Definition: LeafManager.h:343
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h.in:218