OpenVDB 13.1.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>
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...
#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:231
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_END
Definition Platform.h:232
Definition Exceptions.h:65
Signed (x, y, z) 32-bit integer coordinates.
Definition Coord.h:26
Definition Interpolation.h:546
FloatT FloatType
Definition Interpolation.h:553
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:969
Definition PointDataGrid.h:170
bool wasInterrupted(T *i, int percent=-1)
Definition NullInterrupter.h:49
Index32 Index
Definition Types.h:34
int32_t Int32
Definition Types.h:36
Definition Exceptions.h:13
#define OPENVDB_THROW(exception, message)
Definition Exceptions.h:74
General-purpose arithmetic and comparison routines, most of which accept arbitrary value types (or at...
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:291
#define OPENVDB_INSTANTIATE_CLASS
Definition version.h.in:231