HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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>
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;
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;
175  using AlphaMaskT = tools::AlphaMask<GridT, MaskT>;
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:
474  const tree::ValueAccessor<const TreeT> mAcc;
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));
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
734 Filter<GridT, MaskT, InterruptT>::cook(LeafManagerType& leafs)
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
749 Filter<GridT, MaskT, InterruptT>::doBox(const RangeType& range, Int32 w)
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
782 Filter<GridT, MaskT, InterruptT>::doMedian(const RangeType& range, int width)
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
816 Filter<GridT, MaskT, InterruptT>::doOffset(const RangeType& range, ValueType offset)
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 
865 OPENVDB_INSTANTIATE_CLASS Filter<FloatGrid, FloatGrid, util::NullInterrupter>;
866 OPENVDB_INSTANTIATE_CLASS Filter<DoubleGrid, FloatGrid, util::NullInterrupter>;
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_END
Definition: Platform.h:222
typedef int(APIENTRYP RE_PFNGLXSWAPINTERVALSGIPROC)(int)
GA_API const UT_StringHolder dist
GLbitfield flags
Definition: glcorearb.h:1596
cvex test(vector P=0;int unbound=3;export float s=0;export vector Cf=0;)
Definition: test.vfl:11
void mean(int width=1, int iterations=1, const MaskType *mask=nullptr)
One iteration of a fast separable mean-value (i.e. box) filter.
Definition: Filter.h:527
Volume filtering (e.g., diffusion) with optional alpha masking.
Definition: Filter.h:46
GLenum GLint * range
Definition: glcorearb.h:1925
typename CopyConstness< TreeType, NonConstBufferType >::Type BufferType
Definition: LeafManager.h:96
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
GLboolean invert
Definition: glcorearb.h:549
constexpr Coord COORD_OFFSETS[26]
coordinate offset table for neighboring voxels
Definition: Util.h:22
OIIO_UTIL_API bool copy(string_view from, string_view to, std::string &err)
const GLdouble * v
Definition: glcorearb.h:837
GLsizei const GLfloat * value
Definition: glcorearb.h:824
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
GLboolean GLboolean GLboolean GLboolean a
Definition: glcorearb.h:1222
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h:245
ImageBuf OIIO_API min(Image_or_Const A, Image_or_Const B, ROI roi={}, int nthreads=0)
MaskT< LOG2DIM > mMask
Definition: NanoVDB.h:5924
NodeManager produces linear arrays of all tree nodes allowing for efficient threading and bottom-up p...
GLuint buffer
Definition: glcorearb.h:660
#define OPENVDB_ASSERT(X)
Definition: Assert.h:41
void offset(ValueType offset, const MaskType *mask=nullptr)
Definition: Filter.h:684
SYS_FORCE_INLINE const_iterator end() const
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
GLdouble n
Definition: glcorearb.h:2008
GLfloat f
Definition: glcorearb.h:1926
typename tree::LeafManager< TreeType > LeafManagerType
Definition: Filter.h:55
void setMaskRange(AlphaType min, AlphaType max)
Define the range for the (optional) scalar mask.
Definition: Filter.h:117
GLintptr offset
Definition: glcorearb.h:665
#define OPENVDB_INSTANTIATE_CLASS
typename MaskType::ValueType AlphaType
Definition: Filter.h:54
Filter(GridT &grid, InterruptT *interrupt=nullptr)
Definition: Filter.h:64
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
General-purpose arithmetic and comparison routines, most of which accept arbitrary value types (or at...
Filter(const Filter &other)
Shallow copy constructor called by tbb::parallel_for() threads during filtering.
Definition: Filter.h:78
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
OIIO_UTIL_API void parallel_for(int32_t begin, int32_t end, function_view< void(int32_t)> task, paropt opt=0)
GLint GLuint mask
Definition: glcorearb.h:124
GLuint coords
Definition: glad.h:4091
GLfloat GLfloat GLfloat alpha
Definition: glcorearb.h:112
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
void setGrainSize(int grainsize)
Set the grain-size used for multi-threading.
Definition: Filter.h:93
GLboolean GLboolean GLboolean b
Definition: glcorearb.h:1222
typename GridType::TreeType TreeType
Definition: Filter.h:51
void operator()(const RangeType &range) const
Used internally by tbb::parallel_for()
Definition: Filter.h:163
void gaussian(int width=1, int iterations=1, const MaskType *mask=nullptr)
One iteration of a fast separable Gaussian filter.
Definition: Filter.h:581
GLint j
Definition: glad.h:2733
IMATH_NAMESPACE::V2f IMATH_NAMESPACE::Box2i std::string this attribute is obsolete as of OpenEXR v3 float
typename TreeType::LeafNodeType LeafType
Definition: Filter.h:52
typename LeafManagerType::BufferType BufferType
Definition: Filter.h:57
#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
GLuint GLfloat * val
Definition: glcorearb.h:1608
ImageBuf OIIO_API max(Image_or_Const A, Image_or_Const B, ROI roi={}, int nthreads=0)
GA_API const UT_StringHolder N
void setProcessTiles(bool flag)
Set whether active tiles should also be processed.
Definition: Filter.h:103
typename GridType::ValueType ValueType
Definition: Filter.h:53
GLint GLsizei width
Definition: glcorearb.h:103
void median(int width=1, int iterations=1, const MaskType *mask=nullptr)
One iteration of a median-value filter.
Definition: Filter.h:638
GLubyte GLubyte GLubyte GLubyte w
Definition: glcorearb.h:857
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
GLint GLfloat GLint stencil
Definition: glcorearb.h:1278
bool wasInterrupted(T *i, int percent=-1)
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h:119
bool ValueType
Definition: NanoVDB.h:5729
GLint GLsizei count
Definition: glcorearb.h:405
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
typename LeafManagerType::LeafRange RangeType
Definition: Filter.h:56