OpenVDB 12.0.0
 
Loading...
Searching...
No Matches
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>
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
37namespace openvdb {
39namespace OPENVDB_VERSION_NAME {
40namespace tools {
41
42/// @brief Volume filtering (e.g., diffusion) with optional alpha masking
43template<typename GridT,
44 typename MaskT = typename GridT::template ValueConverter<float>::Type,
45 typename InterruptT = util::NullInterrupter>
46class Filter
47{
48public:
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
169private:
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
214namespace filter_internal {
215
216template<typename TreeT>
217struct 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
249private:
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
484private:
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()
492template<typename T> static inline void accum(T& sum, T addend) { sum += addend; }
493// Overload for bool ValueType
494inline void accum(bool& sum, bool addend) { sum = sum || addend; }
495
496} // namespace filter_internal
497
498/// @endcond
499
500////////////////////////////////////////
501
502
503template<typename GridT, typename MaskT, typename InterruptT>
504template<size_t Axis>
505inline typename GridT::ValueType
506Filter<GridT, MaskT, InterruptT>::Avg<Axis>::operator()(Coord xyz)
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
525template<typename GridT, typename MaskT, typename InterruptT>
526void
527Filter<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
579template<typename GridT, typename MaskT, typename InterruptT>
580void
581Filter<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
636template<typename GridT, typename MaskT, typename InterruptT>
637void
638Filter<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
682template<typename GridT, typename MaskT, typename InterruptT>
683void
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.
732template<typename GridT, typename MaskT, typename InterruptT>
733void
734Filter<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
746template<typename GridT, typename MaskT, typename InterruptT>
747template <typename AvgT>
748void
749Filter<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
780template<typename GridT, typename MaskT, typename InterruptT>
781void
782Filter<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
814template<typename GridT, typename MaskT, typename InterruptT>
815void
816Filter<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
842template<typename GridT, typename MaskT, typename InterruptT>
843inline bool
844Filter<GridT, MaskT, InterruptT>::wasInterrupted()
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
865OPENVDB_INSTANTIATE_CLASS Filter<FloatGrid, FloatGrid, util::NullInterrupter>;
866OPENVDB_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_ASSERT(X)
Definition Assert.h:41
A LeafManager manages a linear array of pointers to a given tree's leaf nodes, as well as optional au...
General-purpose arithmetic and comparison routines, most of which accept arbitrary value types (or at...
#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
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_END
Definition Platform.h:222
Definition Exceptions.h:65
Signed (x, y, z) 32-bit integer coordinates.
Definition Coord.h:26
Definition Interpolation.h:545
FloatT FloatType
Definition Interpolation.h:552
bool getProcessTiles() const
Definition Filter.h:96
typename MaskType::ValueType AlphaType
Definition Filter.h:54
void gaussian(int width=1, int iterations=1, const MaskType *mask=nullptr)
One iteration of a fast separable Gaussian filter.
Definition Filter.h:581
typename tree::LeafManager< TreeType > LeafManagerType
Definition Filter.h:55
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
void setGrainSize(int grainsize)
Set the grain-size used for multi-threading.
Definition Filter.h:93
void median(int width=1, int iterations=1, const MaskType *mask=nullptr)
One iteration of a median-value filter.
Definition Filter.h:638
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
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
int getGrainSize() const
Definition Filter.h:90
typename GridType::ValueType ValueType
Definition Filter.h:53
typename GridType::TreeType TreeType
Definition Filter.h:51
void setMaskRange(AlphaType min, AlphaType max)
Define the range for the (optional) scalar mask.
Definition Filter.h:117
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
Filter(const Filter &other)
Shallow copy constructor called by tbb::parallel_for() threads during filtering.
Definition Filter.h:78
void operator()(const RangeType &range) const
Used internally by tbb::parallel_for()
Definition Filter.h:163
typename LeafManagerType::LeafRange RangeType
Definition Filter.h:56
typename LeafManagerType::BufferType BufferType
Definition Filter.h:57
void offset(ValueType offset, const MaskType *mask=nullptr)
Definition Filter.h:684
MaskT MaskType
Definition Filter.h:50
typename TreeType::LeafNodeType LeafType
Definition Filter.h:52
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
GridT GridType
Definition Filter.h:49
Filter(GridT &grid, InterruptT *interrupt=nullptr)
Definition Filter.h:64
This class manages a linear array of pointers to a given tree's leaf nodes, as well as optional auxil...
Definition LeafManager.h:86
typename CopyConstness< TreeType, NonConstBufferType >::Type BufferType
Definition LeafManager.h:96
To facilitate threading over the nodes of a tree, cache node pointers in linear arrays,...
Definition NodeManager.h:532
GridType
List of types that are currently supported by NanoVDB.
Definition NanoVDB.h:219
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.
Axis
Definition Math.h:901
OutGridT XformOp & op
Definition ValueTransformer.h:140
Definition PointDataGrid.h:170
bool wasInterrupted(T *i, int percent=-1)
Definition NullInterrupter.h:49
Index32 Index
Definition Types.h:54
int32_t Int32
Definition Types.h:56
Definition Exceptions.h:13
#define OPENVDB_THROW(exception, message)
Definition Exceptions.h:74
Defines various finite difference stencils by means of the "curiously recurring template pattern" on ...
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
#define OPENVDB_USE_VERSION_NAMESPACE
Definition version.h.in:218
#define OPENVDB_INSTANTIATE_CLASS
Definition version.h.in:158