OpenVDB  12.0.0
Filter.h
Go to the documentation of this file.
1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: Apache-2.0
3 //
4 /// @author Ken Museth
5 ///
6 /// @file tools/Filter.h
7 ///
8 /// @brief Filtering of VDB volumes. All operations can optionally be masked
9 /// with another grid that acts as an alpha-mask. By default, filtering
10 /// operations do not modify the topology of the input tree and thus do
11 /// not process active tiles. However Filter::setProcessTiles can be
12 /// used to process active tiles, densifying them on demand when necessary.
13 
14 #ifndef OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED
15 #define OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED
16 
17 #include <openvdb/Types.h>
18 #include <openvdb/Grid.h>
19 #include <openvdb/math/Math.h>
20 #include <openvdb/math/Stencils.h>
21 #include <openvdb/math/Transform.h>
25 #include <openvdb/util/Util.h>
26 #include <openvdb/util/Assert.h>
27 #include <openvdb/thread/Threading.h>
28 #include "Interpolation.h"
29 
30 #include <tbb/parallel_for.h>
31 #include <tbb/concurrent_vector.h>
32 
33 #include <algorithm> // for std::max()
34 #include <functional>
35 #include <type_traits>
36 
37 namespace openvdb {
39 namespace OPENVDB_VERSION_NAME {
40 namespace tools {
41 
42 /// @brief Volume filtering (e.g., diffusion) with optional alpha masking
43 template<typename GridT,
44  typename MaskT = typename GridT::template ValueConverter<float>::Type,
45  typename InterruptT = util::NullInterrupter>
46 class Filter
47 {
48 public:
49  using GridType = GridT;
50  using MaskType = MaskT;
51  using TreeType = typename GridType::TreeType;
52  using LeafType = typename TreeType::LeafNodeType;
53  using ValueType = typename GridType::ValueType;
54  using AlphaType = typename MaskType::ValueType;
56  using RangeType = typename LeafManagerType::LeafRange;
57  using BufferType = typename LeafManagerType::BufferType;
58  static_assert(std::is_floating_point<AlphaType>::value,
59  "openvdb::tools::Filter requires a mask grid with floating-point values");
60 
61  /// Constructor
62  /// @param grid Grid to be filtered.
63  /// @param interrupt Optional interrupter.
64  Filter(GridT& grid, InterruptT* interrupt = nullptr)
65  : mGrid(&grid)
66  , mTask(nullptr)
67  , mInterrupter(interrupt)
68  , mMask(nullptr)
69  , mGrainSize(1)
70  , mMinMask(0)
71  , mMaxMask(1)
72  , mInvertMask(false)
73  , mTiles(false) {}
74 
75  /// @brief Shallow copy constructor called by tbb::parallel_for()
76  /// threads during filtering.
77  /// @param other The other Filter from which to copy.
78  Filter(const Filter& other)
79  : mGrid(other.mGrid)
80  , mTask(other.mTask)
81  , mInterrupter(other.mInterrupter)
82  , mMask(other.mMask)
83  , mGrainSize(other.mGrainSize)
84  , mMinMask(other.mMinMask)
85  , mMaxMask(other.mMaxMask)
86  , mInvertMask(other.mInvertMask)
87  , mTiles(other.mTiles) {}
88 
89  /// @return the grain-size used for multi-threading
90  int getGrainSize() const { return mGrainSize; }
91  /// @brief Set the grain-size used for multi-threading.
92  /// @note A grain size of 0 or less disables multi-threading!
93  void setGrainSize(int grainsize) { mGrainSize = grainsize; }
94 
95  /// @return whether active tiles are being processed
96  bool getProcessTiles() const { return mTiles; }
97  /// @brief Set whether active tiles should also be processed.
98  /// @note If true, some tiles may become voxelized
99  /// @warning If using with a mask, ensure that the mask topology matches the
100  /// tile topology of the filter grid as tiles will not respect overlapping
101  /// mask values at tree levels finer than themselves e.g. a leaf level tile
102  /// will only use the corresponding tile ijk value in the mask grid
103  void setProcessTiles(bool flag) { mTiles = flag; }
104 
105  /// @brief Return the minimum value of the mask to be used for the
106  /// derivation of a smooth alpha value.
107  AlphaType minMask() const { return mMinMask; }
108  /// @brief Return the maximum value of the mask to be used for the
109  /// derivation of a smooth alpha value.
110  AlphaType maxMask() const { return mMaxMask; }
111  /// @brief Define the range for the (optional) scalar mask.
112  /// @param min Minimum value of the range.
113  /// @param max Maximum value of the range.
114  /// @details Mask values outside the range are clamped to zero or one, and
115  /// values inside the range map smoothly to 0->1 (unless the mask is inverted).
116  /// @throw ValueError if @a min is not smaller than @a max.
118  {
119  if (!(min < max)) OPENVDB_THROW(ValueError, "Invalid mask range (expects min < max)");
120  mMinMask = min;
121  mMaxMask = max;
122  }
123 
124  /// @brief Return true if the mask is inverted, i.e. min->max in the
125  /// original mask maps to 1->0 in the inverted alpha mask.
126  bool isMaskInverted() const { return mInvertMask; }
127  /// @brief Invert the optional mask, i.e. min->max in the original
128  /// mask maps to 1->0 in the inverted alpha mask.
129  void invertMask(bool invert=true) { mInvertMask = invert; }
130 
131  /// @brief One iteration of a fast separable mean-value (i.e. box) filter.
132  /// @param width The width of the mean-value filter is 2*width+1 voxels.
133  /// @param iterations Number of times the mean-value filter is applied.
134  /// @param mask Optional alpha mask.
135  void mean(int width = 1, int iterations = 1, const MaskType* mask = nullptr);
136 
137  /// @brief One iteration of a fast separable Gaussian filter.
138  ///
139  /// @note This is approximated as 4 iterations of a separable mean filter
140  /// which typically leads an approximation that's better than 95%!
141  /// @param width The width of the mean-value filter is 2*width+1 voxels.
142  /// @param iterations Number of times the mean-value filter is applied.
143  /// @param mask Optional alpha mask.
144  void gaussian(int width = 1, int iterations = 1, const MaskType* mask = nullptr);
145 
146  /// @brief One iteration of a median-value filter
147  ///
148  /// @note This filter is not separable and is hence relatively slow!
149  /// @param width The width of the mean-value filter is 2*width+1 voxels.
150  /// @param iterations Number of times the mean-value filter is applied.
151  /// @param mask Optional alpha mask.
152  void median(int width = 1, int iterations = 1, const MaskType* mask = nullptr);
153 
154  /// Offsets (i.e. adds) a constant value to all active voxels.
155  /// @param offset Offset in the same units as the grid.
156  /// @param mask Optional alpha mask.
157  void offset(ValueType offset, const MaskType* mask = nullptr);
158 
159  /// @brief Used internally by tbb::parallel_for()
160  /// @param range Range of LeafNodes over which to multi-thread.
161  ///
162  /// @warning Never call this method directly!
163  void operator()(const RangeType& range) const
164  {
165  if (mTask) mTask(const_cast<Filter*>(this), range);
166  else OPENVDB_THROW(ValueError, "task is undefined - call median(), mean(), etc.");
167  }
168 
169 private:
170  using LeafT = typename TreeType::LeafNodeType;
171  using VoxelIterT = typename LeafT::ValueOnIter;
172  using VoxelCIterT = typename LeafT::ValueOnCIter;
173  using BufferT = typename tree::LeafManager<TreeType>::BufferType;
174  using LeafIterT = typename RangeType::Iterator;
176 
177  void cook(LeafManagerType& leafs);
178 
179  template<size_t Axis>
180  struct Avg {
181  Avg(const GridT* grid, Int32 w): acc(grid->tree()), width(w), frac(1.f/float(2*w+1)) {}
182  inline ValueType operator()(Coord xyz);
183  typename GridT::ConstAccessor acc;
184  const Int32 width;
185  const float frac;
186  };
187 
188  // Private filter methods called by tbb::parallel_for threads
189  template <typename AvgT>
190  void doBox(const RangeType& r, Int32 w);
191  void doBoxX(const RangeType& r, Int32 w) { this->doBox<Avg<0> >(r,w); }
192  void doBoxY(const RangeType& r, Int32 w) { this->doBox<Avg<1> >(r,w); }
193  void doBoxZ(const RangeType& r, Int32 w) { this->doBox<Avg<2> >(r,w); }
194  void doMedian(const RangeType&, int);
195  void doOffset(const RangeType&, ValueType);
196  /// @return true if the process was interrupted
197  bool wasInterrupted();
198 
199  GridType* mGrid;
200  typename std::function<void (Filter*, const RangeType&)> mTask;
201  InterruptT* mInterrupter;
202  const MaskType* mMask;
203  int mGrainSize;
204  AlphaType mMinMask, mMaxMask;
205  bool mInvertMask;
206  bool mTiles;
207 }; // end of Filter class
208 
209 
210 ////////////////////////////////////////
211 
212 /// @cond OPENVDB_DOCS_INTERNAL
213 
214 namespace filter_internal {
215 
216 template<typename TreeT>
217 struct Voxelizer
218 {
219  // NodeManager for processing internal/root node values
220  // @note Should not cache leaf nodes
221  using NodeManagerT = tree::NodeManager<TreeT, TreeT::RootNodeType::LEVEL-1>;
222  using MaskT = typename TreeT::template ValueConverter<ValueMask>::Type;
223 
224  Voxelizer(TreeT& tree, const bool allNeighbors, const size_t grainSize)
225  : mVoxelTopology()
226  , mManager(nullptr)
227  , mGrainSize(grainSize)
228  , mOp(tree, mVoxelTopology, allNeighbors ? 26 : 6) {}
229 
230  /// @brief Convert tiles to leaf nodes that exist at a particular
231  /// voxel distance away
232  /// @param width distance in voxels to seach for tiles from each leaf
233  /// @return Returns how many search iterations were performed, which
234  /// also represents how many leaf node neighbors may have been
235  /// created. Returns 0 if the tree is already entirely voxelized
236  int run(const int width)
237  {
238  if (!mOp.tree().hasActiveTiles()) return 0;
239  this->init();
240  int count = 0;
241  for (int i = 0; i < width; i += int(TreeT::LeafNodeType::DIM), ++count) {
242  if (i > 0) mManager->rebuild();
243  mManager->foreachBottomUp(mOp, mGrainSize > 0, mGrainSize);
244  mOp.tree().topologyUnion(mVoxelTopology);
245  }
246  return count;
247  }
248 
249 private:
250  void init()
251  {
252  if (mManager) {
253  mManager->rebuild();
254  }
255  else {
256  // @note We don't actually need the leaf topology here, just the
257  // internal node structure so that we can generate leaf nodes in parallel
258  mVoxelTopology.topologyUnion(mOp.tree());
259  mManager.reset(new NodeManagerT(mOp.tree()));
260  }
261  }
262 
263  struct CreateVoxelMask
264  {
265  using LeafT = typename TreeT::LeafNodeType;
266  using RootT = typename TreeT::RootNodeType;
267 
268  CreateVoxelMask(TreeT& tree, MaskT& mask, const size_t NN)
269  : mTree(tree), mVoxelTopology(mask), mNeighbors(NN) {}
270 
271  TreeT& tree() { return mTree; }
272 
273  // do nothing for leaf nodes. They shouldn't even be cached as
274  // part of the NodeManager used with this method.
275  void operator()(const LeafT&) const { OPENVDB_ASSERT(false); }
276 
277  void operator()(const RootT& node) const
278  {
279  using ChildT = typename RootT::ChildNodeType;
280  static constexpr Int32 CHILDDIM = Int32(ChildT::DIM);
281  static constexpr Int32 LEAFDIM = Int32(LeafT::DIM);
282  const Tester op(mTree, mNeighbors);
283 
284  auto step =
285  [&](const Coord& ijk,
286  const size_t axis1,
287  const size_t axis2,
288  const auto& val)
289  {
290  Coord offset(0);
291  Int32& a = offset[axis1];
292  Int32& b = offset[axis2];
293  for (a = 0; a < CHILDDIM; a+=LEAFDIM) {
294  for (b = 0; b < CHILDDIM; b+=LEAFDIM) {
295  const Coord childijk = ijk + offset;
296  if (op.test(childijk, val)) {
297  mVoxelTopology.touchLeaf(childijk);
298  }
299  }
300  }
301 
302  offset.reset(CHILDDIM-1);
303  for (a = 0; a < CHILDDIM; a+=LEAFDIM) {
304  for (b = 0; b < CHILDDIM; b+=LEAFDIM) {
305  const Coord childijk = ijk + offset;
306  if (op.test(childijk, val)) {
307  mVoxelTopology.touchLeaf(childijk);
308  }
309  }
310  }
311  };
312 
313  for (auto iter = node.cbeginValueOn(); iter; ++iter) {
314  const Coord& ijk = iter.getCoord();
315  // @todo step only needs to search if a given direction
316  // depending on the face
317  step(ijk, 0, 1, *iter);
318  step(ijk, 0, 2, *iter);
319  step(ijk, 1, 2, *iter);
320  }
321  }
322 
323  template<typename NodeT>
324  void operator()(const NodeT& node) const
325  {
326  using ChildT = typename NodeT::ChildNodeType;
327  static constexpr Int32 CHILDDIM = Int32(ChildT::DIM);
328  static constexpr Int32 LEAFDIM = Int32(LeafT::DIM);
329 
330  static auto step =
331  [](const Tester& op,
332  const Coord& ijk,
333  const size_t axis1,
334  const size_t axis2,
335  const auto& val,
336  std::vector<Coord>& coords)
337  {
338  Coord offset(0);
339  Int32& a = offset[axis1];
340  Int32& b = offset[axis2];
341  for (a = 0; a < CHILDDIM; a+=LEAFDIM) {
342  for (b = 0; b < CHILDDIM; b+=LEAFDIM) {
343  const Coord childijk = ijk + offset;
344  if (op.test(childijk, val)) {
345  coords.emplace_back(childijk);
346  }
347  }
348  }
349 
350  offset.reset(CHILDDIM-1);
351  for (a = 0; a < CHILDDIM; a+=LEAFDIM) {
352  for (b = 0; b < CHILDDIM; b+=LEAFDIM) {
353  const Coord childijk = ijk + offset;
354  if (op.test(childijk, val)) {
355  coords.emplace_back(childijk);
356  }
357  }
358  }
359  };
360 
361  /// Two types of algorithms here
362  /// 1) For the case where this node is the direct parent of leaf nodes
363  /// 2) For all other node types
364  ///
365  /// In general, given a tile's ijk, search its faces/edges/corners for
366  /// values which differ from its own or leaf level topology. When a
367  /// difference is detected, mask topology is generated which can be used
368  /// with topologyUnion to ensure valid voxel values exist in the source
369  /// grid.
370  ///
371  /// This operator handles all internal node types. For example, for the
372  /// lowest level internal node (which contains leaf nodes as children)
373  /// each tile is at the leaf level (a single tile represents an 8x8x8
374  /// node). CHILDDIM is this case will match the valid of LEAFDIM, as we
375  /// only need to check each tiles immediate neighbors. For higher level
376  /// internal nodes (and the root node) each child tile will have a
377  /// significantly larger CHILDDIM than the grid's LEAFDIM. We
378  /// consistently probe values along the LEAFDIM stride to ensure no
379  /// changes are missed.
380 
381  if (CHILDDIM == LEAFDIM) {
382  // If the current node is the parent of leaf nodes, search each
383  // neighbor directly and use a flag buffer to test offsets in
384  // this node which need converting to leaf level topology.
385  // This is faster than the more general method which steps across
386  // faces (unecessary due to CHILDDIM == LEAFDIM) and provides
387  // a simpler way of tracking new topology
388 
389  std::vector<char> flags(NodeT::NUM_VALUES, char(0));
390  tbb::parallel_for(tbb::blocked_range<size_t>(0, NodeT::NUM_VALUES),
391  [&](const tbb::blocked_range<size_t>& range) {
392  const Tester op(mTree, mNeighbors);
393  for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
394  if (node.isValueMaskOn(Index(n))) {
395  // if index is a tile, search its neighbors
396  const Coord ijk = node.offsetToGlobalCoord(Index(n));
397  flags[n] = op.test(ijk, node.getValue(ijk));
398  }
399  }
400  });
401 
402  // create leaf level topology in this internal node
403  Index idx = 0;
404  for (auto iter = flags.begin(); iter != flags.end(); ++iter, ++idx) {
405  if (*iter) mVoxelTopology.touchLeaf(node.offsetToGlobalCoord(idx));
406  }
407  }
408  else {
409  // If this is a higher level internal node, we only need to search its
410  // face/edge/vertex neighbors for values which differ or leaf level
411  // topology. When a difference is detected, store the coordinate which
412  // needs to be voxelized.
413  // @todo investigate better threaded impl
414 
415  tbb::concurrent_vector<Coord> nodes;
416  tbb::parallel_for(tbb::blocked_range<size_t>(0, NodeT::NUM_VALUES),
417  [&](const tbb::blocked_range<size_t>& range)
418  {
419  const Tester op(mTree, mNeighbors);
420  std::vector<Coord> coords;
421 
422  for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
423  if (!node.isValueMaskOn(Index(n))) continue;
424 
425  const Coord ijk = node.offsetToGlobalCoord(Index(n));
426  const auto& val = node.getValue(ijk);
427  // @todo step only needs to search if a given direction
428  // depending on the face
429  step(op, ijk, 0, 1, val, coords);
430  step(op, ijk, 0, 2, val, coords);
431  step(op, ijk, 1, 2, val, coords);
432  }
433 
434  if (!coords.empty()) {
435  std::copy(coords.begin(), coords.end(),
436  nodes.grow_by(coords.size()));
437  }
438  });
439 
440  // create leaf level topology in this internal node
441  // @note nodes may contain duplicate coords
442  for (const auto& coord : nodes) {
443  mVoxelTopology.touchLeaf(coord);
444  }
445  }
446  }
447 
448  private:
449  struct Tester
450  {
451  Tester(const TreeT& tree, const size_t NN)
452  : mAcc(tree), mNeighbors(NN) {}
453 
454  inline bool test(const Coord& ijk,
455  const typename TreeT::ValueType& val) const
456  {
457  static constexpr Int32 LEAFDIM = Int32(LeafT::DIM);
458  const Coord* NN = util::COORD_OFFSETS;
459  for (size_t i = 0; i < mNeighbors; ++i, ++NN) {
460  Coord neighbor(*NN);
461  neighbor.x() *= LEAFDIM;
462  neighbor.y() *= LEAFDIM;
463  neighbor.z() *= LEAFDIM;
464  neighbor += ijk;
465  // if a leaf exists, assume its buffer is not constant
466  if (mAcc.getValue(neighbor) != val ||
467  mAcc.probeConstLeaf(neighbor)) {
468  return true;
469  }
470  }
471  return false;
472  }
473  private:
475  const size_t mNeighbors;
476  };
477 
478  private:
479  TreeT& mTree;
480  MaskT& mVoxelTopology;
481  const size_t mNeighbors;
482  };// CreateVoxelMask
483 
484 private:
485  MaskT mVoxelTopology;
486  std::unique_ptr<NodeManagerT> mManager;
487  const size_t mGrainSize;
488  CreateVoxelMask mOp;
489 };
490 
491 // Helper function for Filter::Avg::operator()
492 template<typename T> static inline void accum(T& sum, T addend) { sum += addend; }
493 // Overload for bool ValueType
494 inline void accum(bool& sum, bool addend) { sum = sum || addend; }
495 
496 } // namespace filter_internal
497 
498 /// @endcond
499 
500 ////////////////////////////////////////
501 
502 
503 template<typename GridT, typename MaskT, typename InterruptT>
504 template<size_t Axis>
505 inline typename GridT::ValueType
507 {
508  ValueType sum = zeroVal<ValueType>();
509  Int32 &i = xyz[Axis], j = i + width;
510  for (i -= width; i <= j; ++i) filter_internal::accum(sum, acc.getValue(xyz));
511  if constexpr(std::is_same<ValueType, bool>::value) {
512  return sum && frac > 0.0f;
513  } else {
515  ValueType value = static_cast<ValueType>(sum * frac);
517  return value;
518  }
519 }
520 
521 
522 ////////////////////////////////////////
523 
524 
525 template<typename GridT, typename MaskT, typename InterruptT>
526 void
527 Filter<GridT, MaskT, InterruptT>::mean(int width, int iterations, const MaskType* mask)
528 {
529  if (iterations <= 0) return;
530  mMask = mask;
531  const int w = std::max(1, width);
532  const bool serial = mGrainSize == 0;
533 
534  if (mInterrupter) mInterrupter->start("Applying mean filter");
535 
536  std::unique_ptr<filter_internal::Voxelizer<TreeType>> voxelizer;
537  if (this->getProcessTiles()) {
538  // if performing multiple iterations, also search edge/vertex
539  // neighbors for difference topology.
540  const bool allNeighbors = iterations > 1;
541  // If processing tiles, create a voxelizer and run a single
542  // width based search for tiles that need to be voxelized
543  voxelizer.reset(new filter_internal::Voxelizer<TreeType>
544  (mGrid->tree(), allNeighbors, mGrainSize));
545  if (!voxelizer->run(w)) voxelizer.reset();
546  }
547 
548  LeafManagerType leafs(mGrid->tree(), 1, serial);
549 
550  int iter = 1; // num of leaf level neighbor based searches performed
551  int dist = w; // kernel distance of the current iteration
552  for (int i=0; i<iterations && !this->wasInterrupted(); ++i, dist+=w) {
553  if (i > 0 && voxelizer) {
554  // the total influence distance in voxels of this iteration
555  // minus how far we've already accounted for
556  const int remain = dist - iter * int(TreeType::LeafNodeType::DIM);
557  if (remain > 0) {
558  const int searches = voxelizer->run(remain);
559  if (searches == 0) voxelizer.reset();
560  else leafs.rebuild(serial);
561  iter += searches;
562  }
563  }
564 
565  mTask = std::bind(&Filter::doBoxX, std::placeholders::_1, std::placeholders::_2, w);
566  this->cook(leafs);
567  // note that the order of the YZ passes are flipped to maintain backwards-compatibility
568  // with an indexing typo in the original logic
569  mTask = std::bind(&Filter::doBoxZ, std::placeholders::_1, std::placeholders::_2, w);
570  this->cook(leafs);
571  mTask = std::bind(&Filter::doBoxY, std::placeholders::_1, std::placeholders::_2, w);
572  this->cook(leafs);
573  }
574 
575  if (mInterrupter) mInterrupter->end();
576 }
577 
578 
579 template<typename GridT, typename MaskT, typename InterruptT>
580 void
581 Filter<GridT, MaskT, InterruptT>::gaussian(int width, int iterations, const MaskType* mask)
582 {
583  if (iterations <= 0) return;
584  mMask = mask;
585  const int w = std::max(1, width);
586  const bool serial = mGrainSize == 0;
587 
588  if (mInterrupter) mInterrupter->start("Applying Gaussian filter");
589 
590  std::unique_ptr<filter_internal::Voxelizer<TreeType>> voxelizer;
591  if (this->getProcessTiles()) {
592  // if performing multiple iterations, also search edge/vertex
593  // neighbors for difference topology.
594  const bool allNeighbors = iterations > 1;
595  // If processing tiles, create a voxelizer and run a single
596  // width based search for tiles that need to be voxelized
597  // @note account for sub iteration due to gaussian filter
598  voxelizer.reset(new filter_internal::Voxelizer<TreeType>
599  (mGrid->tree(), allNeighbors, mGrainSize));
600  if (!voxelizer->run(w*4)) voxelizer.reset();
601  }
602 
603  LeafManagerType leafs(mGrid->tree(), 1, serial);
604 
605  int iter = 1; // num of leaf level neighbor based searches performed
606  int dist = w*4; // kernel distance of the current iteration
607  for (int i=0; i<iterations; ++i, dist+=(w*4)) {
608  if (i > 0 && voxelizer) {
609  // the total influence distance in voxels of this iteration
610  // minus how far we've already accounted for
611  const int remain = dist - iter * int(TreeType::LeafNodeType::DIM);
612  if (remain > 0) {
613  const int searches = voxelizer->run(remain);
614  if (searches == 0) voxelizer.reset();
615  else leafs.rebuild(serial);
616  iter += searches;
617  }
618  }
619 
620  for (int n=0; n<4 && !this->wasInterrupted(); ++n) {
621  mTask = std::bind(&Filter::doBoxX, std::placeholders::_1, std::placeholders::_2, w);
622  this->cook(leafs);
623  // note that the order of the YZ passes are flipped to maintain backwards-compatibility
624  // with an indexing typo in the original logic
625  mTask = std::bind(&Filter::doBoxZ, std::placeholders::_1, std::placeholders::_2, w);
626  this->cook(leafs);
627  mTask = std::bind(&Filter::doBoxY, std::placeholders::_1, std::placeholders::_2, w);
628  this->cook(leafs);
629  }
630  }
631 
632  if (mInterrupter) mInterrupter->end();
633 }
634 
635 
636 template<typename GridT, typename MaskT, typename InterruptT>
637 void
638 Filter<GridT, MaskT, InterruptT>::median(int width, int iterations, const MaskType* mask)
639 {
640  if (iterations <= 0) return;
641  mMask = mask;
642  const int w = std::max(1, width);
643  const bool serial = mGrainSize == 0;
644 
645  if (mInterrupter) mInterrupter->start("Applying median filter");
646 
647  std::unique_ptr<filter_internal::Voxelizer<TreeType>> voxelizer;
648  if (this->getProcessTiles()) {
649  // If processing tiles, create a voxelizer and run a single
650  // width based search for tiles that need to be voxelized
651  voxelizer.reset(new filter_internal::Voxelizer<TreeType>
652  (mGrid->tree(), /*allNeighbors*/true, mGrainSize));
653  if (!voxelizer->run(w)) voxelizer.reset();
654  }
655 
656  LeafManagerType leafs(mGrid->tree(), 1, serial);
657 
658  mTask = std::bind(&Filter::doMedian, std::placeholders::_1, std::placeholders::_2, w);
659 
660  int iter = 1; // num of leaf level neighbor based searches performed
661  int dist = w; // kernel distance of the current iteration
662  for (int i=0; i<iterations && !this->wasInterrupted(); ++i, dist+=w) {
663  if (i > 0 && voxelizer) {
664  // the total influence distance in voxels of this iteration
665  // minus how far we've already accounted for
666  const int remain = dist - iter * int(TreeType::LeafNodeType::DIM);
667  if (remain > 0) {
668  const int searches = voxelizer->run(remain);
669  if (searches == 0) voxelizer.reset();
670  else leafs.rebuild(serial);
671  iter += searches;
672  }
673  }
674 
675  this->cook(leafs);
676  }
677 
678  if (mInterrupter) mInterrupter->end();
679 }
680 
681 
682 template<typename GridT, typename MaskT, typename InterruptT>
683 void
685 {
686  mMask = mask;
687 
688  if (mInterrupter) mInterrupter->start("Applying offset");
689 
690  if (this->getProcessTiles()) {
691  // Don't process leaf nodes with the node manager - we'll do them
692  // separately to allow for cleaner branching
693  using NodeManagerT = tree::NodeManager<TreeType, TreeType::RootNodeType::LEVEL-1>;
694  NodeManagerT manager(mGrid->tree());
695 
696  if (mask) {
697  manager.foreachBottomUp([&](auto& node) {
698  this->wasInterrupted();
699  AlphaMaskT alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask);
700  typename AlphaMaskT::FloatType a, b;
701  for (auto iter = node.beginValueOn(); iter; ++iter) {
702  if (!alpha(iter.getCoord(), a, b)) continue;
704  iter.modifyValue([&](ValueType& v) { v += a*value; });
706  }
707  });
708  }
709  else {
710  manager.foreachBottomUp([&](auto& node) {
711  this->wasInterrupted();
712  for (auto iter = node.beginValueOn(); iter; ++iter) {
713  iter.modifyValue([&](ValueType& v) { v += value; });
714  }
715  });
716  }
717  }
718 
719  LeafManagerType leafs(mGrid->tree(), 0, mGrainSize==0);
720  mTask = std::bind(&Filter::doOffset, std::placeholders::_1, std::placeholders::_2, value);
721  this->cook(leafs);
722 
723  if (mInterrupter) mInterrupter->end();
724 }
725 
726 
727 ////////////////////////////////////////
728 
729 
730 /// Private method to perform the task (serial or threaded) and
731 /// subsequently swap the leaf buffers.
732 template<typename GridT, typename MaskT, typename InterruptT>
733 void
735 {
736  if (mGrainSize>0) {
737  tbb::parallel_for(leafs.leafRange(mGrainSize), *this);
738  } else {
739  (*this)(leafs.leafRange());
740  }
741  leafs.swapLeafBuffer(1, mGrainSize==0);
742 }
743 
744 
745 /// One dimensional convolution of a separable box filter
746 template<typename GridT, typename MaskT, typename InterruptT>
747 template <typename AvgT>
748 void
750 {
751  this->wasInterrupted();
752  AvgT avg(mGrid, w);
753  if (mMask) {
754  typename AlphaMaskT::FloatType a, b;
755  AlphaMaskT alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask);
756  for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
757  BufferT& buffer = leafIter.buffer(1);
758  for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
759  const Coord xyz = iter.getCoord();
760  if (alpha(xyz, a, b)) {
762  const ValueType value(b*(*iter) + a*avg(xyz));
764  buffer.setValue(iter.pos(), value);
765  }
766  }
767  }
768  } else {
769  for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
770  BufferT& buffer = leafIter.buffer(1);
771  for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
772  buffer.setValue(iter.pos(), avg(iter.getCoord()));
773  }
774  }
775  }
776 }
777 
778 
779 /// Performs simple but slow median-value diffusion
780 template<typename GridT, typename MaskT, typename InterruptT>
781 void
783 {
784  this->wasInterrupted();
785  typename math::DenseStencil<GridType> stencil(*mGrid, width);//creates local cache!
786  if (mMask) {
787  typename AlphaMaskT::FloatType a, b;
788  AlphaMaskT alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask);
789  for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
790  BufferT& buffer = leafIter.buffer(1);
791  for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
792  if (alpha(iter.getCoord(), a, b)) {
793  stencil.moveTo(iter);
795  ValueType value(b*(*iter) + a*stencil.median());
797  buffer.setValue(iter.pos(), value);
798  }
799  }
800  }
801  } else {
802  for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
803  BufferT& buffer = leafIter.buffer(1);
804  for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) {
805  stencil.moveTo(iter);
806  buffer.setValue(iter.pos(), stencil.median());
807  }
808  }
809  }
810 }
811 
812 
813 /// Offsets the values by a constant
814 template<typename GridT, typename MaskT, typename InterruptT>
815 void
817 {
818  this->wasInterrupted();
819  if (mMask) {
820  typename AlphaMaskT::FloatType a, b;
821  AlphaMaskT alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask);
822  for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
823  for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) {
824  if (alpha(iter.getCoord(), a, b)) {
826  ValueType value(*iter + a*offset);
828  iter.setValue(value);
829  }
830  }
831  }
832  } else {
833  for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
834  for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) {
835  iter.setValue(*iter + offset);
836  }
837  }
838  }
839 }
840 
841 
842 template<typename GridT, typename MaskT, typename InterruptT>
843 inline bool
845 {
846  if (util::wasInterrupted(mInterrupter)) {
847  thread::cancelGroupExecution();
848  return true;
849  }
850  return false;
851 }
852 
853 
854 ////////////////////////////////////////
855 
856 
857 // Explicit Template Instantiation
858 
859 #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION
860 
861 #ifdef OPENVDB_INSTANTIATE_FILTER
863 #endif
864 
867 
868 #endif // OPENVDB_USE_EXPLICIT_INSTANTIATION
869 
870 
871 } // namespace tools
872 } // namespace OPENVDB_VERSION_NAME
873 } // namespace openvdb
874 
875 #endif // OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN
Bracket code with OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN/_END, to inhibit warnings about type conve...
Definition: Platform.h:221
void invertMask(bool invert=true)
Invert the optional mask, i.e. min->max in the original mask maps to 1->0 in the inverted alpha mask...
Definition: Filter.h:129
To facilitate threading over the nodes of a tree, cache node pointers in linear arrays, one for each level of the tree.
Definition: NodeManager.h:31
typename LeafManagerType::BufferType BufferType
Definition: Filter.h:57
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
General-purpose arithmetic and comparison routines, most of which accept arbitrary value types (or at...
AlphaType minMask() const
Return the minimum value of the mask to be used for the derivation of a smooth alpha value...
Definition: Filter.h:107
void setProcessTiles(bool flag)
Set whether active tiles should also be processed.
Definition: Filter.h:103
Filter(GridT &grid, InterruptT *interrupt=nullptr)
Definition: Filter.h:64
typename MaskType::ValueType AlphaType
Definition: Filter.h:54
int32_t Int32
Definition: Types.h:56
Index32 Index
Definition: Types.h:54
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition: Composite.h:110
typename TreeType::LeafNodeType LeafType
Definition: Filter.h:52
OutGridT XformOp & op
Definition: ValueTransformer.h:139
bool getProcessTiles() const
Definition: Filter.h:96
bool wasInterrupted(T *i, int percent=-1)
Definition: NullInterrupter.h:49
GridT GridType
Definition: Filter.h:49
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_END
Definition: Platform.h:222
ValueType median() const
Return the median value of the current stencil.
Definition: Stencils.h:121
Definition: Exceptions.h:65
constexpr Coord COORD_OFFSETS[26]
coordinate offset table for neighboring voxels
Definition: Util.h:22
bool isMaskInverted() const
Return true if the mask is inverted, i.e. min->max in the original mask maps to 1->0 in the inverted ...
Definition: Filter.h:126
typename GridType::TreeType TreeType
Definition: Filter.h:51
MaskT MaskType
Definition: Filter.h:50
void setMaskRange(AlphaType min, AlphaType max)
Define the range for the (optional) scalar mask.
Definition: Filter.h:117
typename tree::LeafManager< TreeType > LeafManagerType
Definition: Filter.h:55
#define OPENVDB_ASSERT(X)
Definition: Assert.h:41
void foreachBottomUp(const NodeOp &op, bool threaded=true, size_t grainSize=1)
Threaded method that applies a user-supplied functor to all the nodes in the tree.
Definition: NodeManager.h:625
Axis
Definition: Math.h:901
int getGrainSize() const
Definition: Filter.h:90
Definition: Exceptions.h:13
typename LeafManagerType::LeafRange RangeType
Definition: Filter.h:56
void moveTo(const Coord &ijk)
Initialize the stencil buffer with the values of voxel (x, y, z) and its neighbors.
Definition: Stencils.h:1784
Filter(const Filter &other)
Shallow copy constructor called by tbb::parallel_for() threads during filtering.
Definition: Filter.h:78
Dense stencil of a given width.
Definition: Stencils.h:1764
OPENVDB_AX_API void run(const char *ax, openvdb::GridBase &grid, const AttributeBindings &bindings={})
Run a full AX pipeline (parse, compile and execute) on a single OpenVDB Grid.
typename CopyConstness< TreeType, NonConstBufferType >::Type BufferType
Definition: LeafManager.h:96
#define OPENVDB_INSTANTIATE_CLASS
Definition: version.h.in:158
AlphaType maxMask() const
Return the maximum value of the mask to be used for the derivation of a smooth alpha value...
Definition: Filter.h:110
typename GridType::ValueType ValueType
Definition: Filter.h:53
Definition: Interpolation.h:544
void operator()(const RangeType &range) const
Used internally by tbb::parallel_for()
Definition: Filter.h:163
FloatT FloatType
Definition: Interpolation.h:552
const std::enable_if<!VecTraits< T >::IsVec, T >::type & min(const T &a, const T &b)
Definition: Composite.h:106
A LeafManager manages a linear array of pointers to a given tree&#39;s leaf nodes, as well as optional au...
NodeManager produces linear arrays of all tree nodes allowing for efficient threading and bottom-up p...
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h.in:121
void setGrainSize(int grainsize)
Set the grain-size used for multi-threading.
Definition: Filter.h:93
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h.in:218
Volume filtering (e.g., diffusion) with optional alpha masking.
Definition: Filter.h:46