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