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