Line | Branch | Exec | Source |
---|---|---|---|
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 |
10/20✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
✓ Branch 20 taken 1 times.
✗ Branch 21 not taken.
|
17 | 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 | 17 | 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 |
12/24✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 1 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 1 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 1 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 1 times.
✗ Branch 29 not taken.
✓ Branch 31 taken 1 times.
✗ Branch 32 not taken.
✓ Branch 34 taken 1 times.
✗ Branch 35 not taken.
|
17 | , 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. | ||
77 | 1400 | Filter(const Filter& other) | |
78 | 1400 | : mGrid(other.mGrid) | |
79 | 1400 | , mTask(other.mTask) | |
80 | 1400 | , mInterrupter(other.mInterrupter) | |
81 | 1400 | , mMask(other.mMask) | |
82 | 1400 | , mGrainSize(other.mGrainSize) | |
83 | 1400 | , mMinMask(other.mMinMask) | |
84 | 1400 | , mMaxMask(other.mMaxMask) | |
85 | 1400 | , mInvertMask(other.mInvertMask) | |
86 | 1400 | , mTiles(other.mTiles) {} | |
87 | |||
88 | /// @return the grain-size used for multi-threading | ||
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! | ||
92 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
2 | void setGrainSize(int grainsize) { mGrainSize = grainsize; } |
93 | |||
94 | /// @return whether active tiles are being processed | ||
95 | 25 | 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 | ||
102 |
9/18✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 1 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 1 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 1 times.
✗ Branch 26 not taken.
|
14 | 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. | ||
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. | ||
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. | ||
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. | ||
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! | ||
162 |
1/2✓ Branch 0 taken 1677 times.
✗ Branch 1 not taken.
|
3354 | void operator()(const RangeType& range) const |
163 | { | ||
164 |
1/2✓ Branch 0 taken 1677 times.
✗ Branch 1 not taken.
|
3354 | 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 { | ||
180 | 2582 | 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); | ||
190 | 850 | void doBoxX(const RangeType& r, Int32 w) { this->doBox<Avg<0> >(r,w); } | |
191 | 876 | void doBoxY(const RangeType& r, Int32 w) { this->doBox<Avg<1> >(r,w); } | |
192 | 856 | 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 | 13 | 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 | |||
223 | 13 | Voxelizer(TreeT& tree, const bool allNeighbors, const size_t grainSize) | |
224 | : mVoxelTopology() | ||
225 | , mManager(nullptr) | ||
226 | , mGrainSize(grainSize) | ||
227 |
2/8✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 2 times.
|
9 | , 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 | ||
235 | 28 | int run(const int width) | |
236 | { | ||
237 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
28 | if (!mOp.tree().hasActiveTiles()) return 0; |
238 | 28 | this->init(); | |
239 | int count = 0; | ||
240 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 14 times.
|
58 | for (int i = 0; i < width; i += int(TreeT::LeafNodeType::DIM), ++count) { |
241 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 14 times.
|
30 | if (i > 0) mManager->rebuild(); |
242 | 30 | mManager->foreachBottomUp(mOp, mGrainSize > 0, mGrainSize); | |
243 | mOp.tree().topologyUnion(mVoxelTopology); | ||
244 | } | ||
245 | return count; | ||
246 | } | ||
247 | |||
248 | private: | ||
249 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
|
28 | void init() |
250 | { | ||
251 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
|
28 | if (mManager) { |
252 | 2 | 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 | 26 | mVoxelTopology.topologyUnion(mOp.tree()); | |
258 |
1/2✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
|
26 | 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 | |||
267 | 13 | CreateVoxelMask(TreeT& tree, MaskT& mask, const size_t NN) | |
268 |
2/12✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 9 times.
|
13 | : mTree(tree), mVoxelTopology(mask), mNeighbors(NN) {} |
269 | |||
270 | 55 | 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. | ||
274 | void operator()(const LeafT&) const { assert(false); } | ||
275 | |||
276 | 30 | 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 | 30 | const Tester op(mTree, mNeighbors); | |
282 | |||
283 | 30 | auto step = | |
284 | 6 | [&](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 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 3072 times.
✓ Branch 3 taken 6 times.
|
3078 | for (a = 0; a < CHILDDIM; a+=LEAFDIM) { |
293 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1572864 times.
✓ Branch 3 taken 3072 times.
|
1575936 | for (b = 0; b < CHILDDIM; b+=LEAFDIM) { |
294 | 1572864 | const Coord childijk = ijk + offset; | |
295 |
2/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 1572842 times.
|
1572864 | if (op.test(childijk, val)) { |
296 | 22 | mVoxelTopology.touchLeaf(childijk); | |
297 | } | ||
298 | } | ||
299 | } | ||
300 | |||
301 | offset.reset(CHILDDIM-1); | ||
302 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 3072 times.
✓ Branch 3 taken 6 times.
|
3078 | for (a = 0; a < CHILDDIM; a+=LEAFDIM) { |
303 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1572864 times.
✓ Branch 3 taken 3072 times.
|
1575936 | for (b = 0; b < CHILDDIM; b+=LEAFDIM) { |
304 | 1572864 | const Coord childijk = ijk + offset; | |
305 |
1/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1572864 times.
|
1572864 | if (op.test(childijk, val)) { |
306 | ✗ | mVoxelTopology.touchLeaf(childijk); | |
307 | } | ||
308 | } | ||
309 | } | ||
310 | }; | ||
311 | |||
312 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 15 times.
|
34 | for (auto iter = node.cbeginValueOn(); iter; ++iter) { |
313 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | const Coord& ijk = iter.getCoord(); |
314 | // @todo step only needs to search if a given direction | ||
315 | // depending on the face | ||
316 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | step(ijk, 0, 1, *iter); |
317 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | step(ijk, 0, 2, *iter); |
318 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | step(ijk, 1, 2, *iter); |
319 | } | ||
320 | } | ||
321 | |||
322 | template<typename NodeT> | ||
323 | 66 | 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 | 98316 | [](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 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1573056 times.
✓ Branch 3 taken 98316 times.
|
1671372 | for (a = 0; a < CHILDDIM; a+=LEAFDIM) { |
341 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 25168896 times.
✓ Branch 3 taken 1573056 times.
|
26741952 | for (b = 0; b < CHILDDIM; b+=LEAFDIM) { |
342 | 25168896 | const Coord childijk = ijk + offset; | |
343 |
2/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 1553 times.
✓ Branch 5 taken 25167343 times.
|
25168896 | if (op.test(childijk, val)) { |
344 | 1553 | coords.emplace_back(childijk); | |
345 | } | ||
346 | } | ||
347 | } | ||
348 | |||
349 | offset.reset(CHILDDIM-1); | ||
350 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1573056 times.
✓ Branch 3 taken 98316 times.
|
1671372 | for (a = 0; a < CHILDDIM; a+=LEAFDIM) { |
351 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 25168896 times.
✓ Branch 3 taken 1573056 times.
|
26741952 | for (b = 0; b < CHILDDIM; b+=LEAFDIM) { |
352 | 25168896 | const Coord childijk = ijk + offset; | |
353 |
2/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 1536 times.
✓ Branch 5 taken 25167360 times.
|
25168896 | if (op.test(childijk, val)) { |
354 | 1536 | 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 | 28 | std::vector<char> flags(NodeT::NUM_VALUES, char(0)); | |
389 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
28 | tbb::parallel_for(tbb::blocked_range<size_t>(0, NodeT::NUM_VALUES), |
390 | 10040 | [&](const tbb::blocked_range<size_t>& range) { | |
391 | 1851 | const Tester op(mTree, mNeighbors); | |
392 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 57344 times.
✓ Branch 3 taken 1851 times.
|
59195 | for (size_t n = range.begin(), N = range.end(); n < N; ++n) { |
393 |
2/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 8189 times.
✓ Branch 5 taken 49155 times.
|
57344 | if (node.isValueMaskOn(Index(n))) { |
394 | // if index is a tile, search its neighbors | ||
395 | 8189 | const Coord ijk = node.offsetToGlobalCoord(Index(n)); | |
396 |
2/8✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 7 taken 8189 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8189 times.
✗ Branch 11 not taken.
|
8189 | 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 |
2/2✓ Branch 0 taken 57344 times.
✓ Branch 1 taken 14 times.
|
114716 | for (auto iter = flags.begin(); iter != flags.end(); ++iter, ++idx) { |
404 |
2/4✓ Branch 0 taken 39 times.
✓ Branch 1 taken 57305 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
114766 | 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 | 38 | tbb::concurrent_vector<Coord> nodes; | |
415 |
2/4✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
|
38 | tbb::parallel_for(tbb::blocked_range<size_t>(0, NodeT::NUM_VALUES), |
416 | 2609 | [&](const tbb::blocked_range<size_t>& range) | |
417 | { | ||
418 | 2606 | const Tester op(mTree, mNeighbors); | |
419 | std::vector<Coord> coords; | ||
420 | |||
421 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 622592 times.
✓ Branch 3 taken 2606 times.
|
625198 | for (size_t n = range.begin(), N = range.end(); n < N; ++n) { |
422 |
2/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 589820 times.
✓ Branch 5 taken 32772 times.
|
622592 | if (!node.isValueMaskOn(Index(n))) continue; |
423 | |||
424 | 32772 | const Coord ijk = node.offsetToGlobalCoord(Index(n)); | |
425 |
1/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 32772 times.
✗ Branch 5 not taken.
|
32772 | const auto& val = node.getValue(ijk); |
426 | // @todo step only needs to search if a given direction | ||
427 | // depending on the face | ||
428 |
1/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 32772 times.
✗ Branch 5 not taken.
|
32772 | step(op, ijk, 0, 1, val, coords); |
429 |
1/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 32772 times.
✗ Branch 5 not taken.
|
32772 | step(op, ijk, 0, 2, val, coords); |
430 |
1/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 32772 times.
✗ Branch 5 not taken.
|
32772 | step(op, ijk, 1, 2, val, coords); |
431 | } | ||
432 | |||
433 |
2/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2603 times.
|
2606 | if (!coords.empty()) { |
434 |
1/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | 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 |
2/2✓ Branch 0 taken 3089 times.
✓ Branch 1 taken 19 times.
|
6216 | for (const auto& coord : nodes) { |
442 |
1/2✓ Branch 1 taken 3089 times.
✗ Branch 2 not taken.
|
6178 | mVoxelTopology.touchLeaf(coord); |
443 | } | ||
444 | } | ||
445 | } | ||
446 | |||
447 | private: | ||
448 | struct Tester | ||
449 | { | ||
450 | 4472 | Tester(const TreeT& tree, const size_t NN) | |
451 | 4472 | : mAcc(tree), mNeighbors(NN) {} | |
452 | |||
453 | 106983418 | 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 |
2/2✓ Branch 0 taken 352540917 times.
✓ Branch 1 taken 53488559 times.
|
812058952 | for (size_t i = 0; i < mNeighbors; ++i, ++NN) { |
459 | 705081834 | Coord neighbor(*NN); | |
460 | 705081834 | neighbor.x() *= LEAFDIM; | |
461 | 705081834 | neighbor.y() *= LEAFDIM; | |
462 | 705081834 | neighbor.z() *= LEAFDIM; | |
463 | neighbor += ijk; | ||
464 | // if a leaf exists, assume its buffer is not constant | ||
465 |
4/4✓ Branch 1 taken 352537802 times.
✓ Branch 2 taken 3115 times.
✓ Branch 3 taken 35 times.
✓ Branch 4 taken 352537767 times.
|
1410157438 | if (mAcc.getValue(neighbor) != val || |
466 | mAcc.probeConstLeaf(neighbor)) { | ||
467 | 6300 | 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() | ||
491 | 8236032 | template<typename T> static inline void accum(T& sum, T addend) { sum += addend; } | |
492 | // Overload for bool ValueType | ||
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 | ||
505 | 5025792 | Filter<GridT, MaskT, InterruptT>::Avg<Axis>::operator()(Coord xyz) | |
506 | { | ||
507 | ValueType sum = zeroVal<ValueType>(); | ||
508 | 5025792 | Int32 &i = xyz[Axis], j = i + width; | |
509 |
2/2✓ Branch 0 taken 8236032 times.
✓ Branch 1 taken 2512896 times.
|
21497856 | for (i -= width; i <= j; ++i) filter_internal::accum(sum, acc.getValue(xyz)); |
510 | OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN | ||
511 | 5025792 | ValueType value = static_cast<ValueType>(sum * frac); | |
512 | OPENVDB_NO_TYPE_CONVERSION_WARNING_END | ||
513 | 5025792 | return value; | |
514 | } | ||
515 | |||
516 | |||
517 | //////////////////////////////////////// | ||
518 | |||
519 | |||
520 | template<typename GridT, typename MaskT, typename InterruptT> | ||
521 | void | ||
522 | 24 | Filter<GridT, MaskT, InterruptT>::mean(int width, int iterations, const MaskType* mask) | |
523 | { | ||
524 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
24 | if (iterations <= 0) return; |
525 | 24 | mMask = mask; | |
526 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
|
24 | const int w = std::max(1, width); |
527 | 24 | const bool serial = mGrainSize == 0; | |
528 | |||
529 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
24 | if (mInterrupter) mInterrupter->start("Applying mean filter"); |
530 | |||
531 | 24 | std::unique_ptr<filter_internal::Voxelizer<TreeType>> voxelizer; | |
532 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
|
24 | 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 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
18 | voxelizer.reset(new filter_internal::Voxelizer<TreeType> |
539 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
18 | (mGrid->tree(), allNeighbors, mGrainSize)); |
540 |
2/4✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
|
18 | if (!voxelizer->run(w)) voxelizer.reset(); |
541 | } | ||
542 | |||
543 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
48 | 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 |
4/6✓ Branch 0 taken 21 times.
✓ Branch 1 taken 12 times.
✓ Branch 3 taken 21 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
|
66 | for (int i=0; i<iterations && !this->wasInterrupted(); ++i, dist+=w) { |
548 |
3/4✓ Branch 0 taken 9 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
42 | if (i > 0 && voxelizer) { |
549 | // the total influence distance in voxels of this iteration | ||
550 | // minus how far we've already accounted for | ||
551 | 18 | const int remain = dist - iter * int(TreeType::LeafNodeType::DIM); | |
552 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
|
18 | if (remain > 0) { |
553 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
2 | const int searches = voxelizer->run(remain); |
554 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
2 | if (searches == 0) voxelizer.reset(); |
555 | else leafs.rebuild(serial); | ||
556 | 2 | iter += searches; | |
557 | } | ||
558 | } | ||
559 | |||
560 |
1/2✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
|
42 | mTask = std::bind(&Filter::doBoxX, std::placeholders::_1, std::placeholders::_2, w); |
561 |
1/2✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
|
42 | 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 |
1/2✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
|
42 | mTask = std::bind(&Filter::doBoxZ, std::placeholders::_1, std::placeholders::_2, w); |
565 |
1/2✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
|
42 | this->cook(leafs); |
566 |
1/2✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
|
42 | mTask = std::bind(&Filter::doBoxY, std::placeholders::_1, std::placeholders::_2, w); |
567 |
1/2✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
|
42 | this->cook(leafs); |
568 | } | ||
569 | |||
570 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
24 | if (mInterrupter) mInterrupter->end(); |
571 | } | ||
572 | |||
573 | |||
574 | template<typename GridT, typename MaskT, typename InterruptT> | ||
575 | void | ||
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 | ||
633 | 14 | Filter<GridT, MaskT, InterruptT>::median(int width, int iterations, const MaskType* mask) | |
634 | { | ||
635 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
14 | if (iterations <= 0) return; |
636 | 14 | mMask = mask; | |
637 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
|
14 | const int w = std::max(1, width); |
638 | 14 | const bool serial = mGrainSize == 0; | |
639 | |||
640 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
14 | if (mInterrupter) mInterrupter->start("Applying median filter"); |
641 | |||
642 | 14 | std::unique_ptr<filter_internal::Voxelizer<TreeType>> voxelizer; | |
643 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
|
14 | 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 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
8 | voxelizer.reset(new filter_internal::Voxelizer<TreeType> |
647 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
8 | (mGrid->tree(), /*allNeighbors*/true, mGrainSize)); |
648 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
|
8 | if (!voxelizer->run(w)) voxelizer.reset(); |
649 | } | ||
650 | |||
651 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
28 | LeafManagerType leafs(mGrid->tree(), 1, serial); |
652 | |||
653 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
14 | 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 |
4/6✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
|
28 | for (int i=0; i<iterations && !this->wasInterrupted(); ++i, dist+=w) { |
658 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
14 | 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 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
14 | this->cook(leafs); |
671 | } | ||
672 | |||
673 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
14 | if (mInterrupter) mInterrupter->end(); |
674 | } | ||
675 | |||
676 | |||
677 | template<typename GridT, typename MaskT, typename InterruptT> | ||
678 | void | ||
679 | 12 | Filter<GridT, MaskT, InterruptT>::offset(ValueType value, const MaskType* mask) | |
680 | { | ||
681 | 12 | mMask = mask; | |
682 | |||
683 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
12 | if (mInterrupter) mInterrupter->start("Applying offset"); |
684 | |||
685 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
|
12 | 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 | 8 | NodeManagerT manager(mGrid->tree()); | |
690 | |||
691 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
4 | 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 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
10 | manager.foreachBottomUp([&](auto& node) { |
706 | 5 | this->wasInterrupted(); | |
707 |
5/12✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 2 times.
|
9 | for (auto iter = node.beginValueOn(); iter; ++iter) { |
708 | 2 | iter.modifyValue([&](ValueType& v) { v += value; }); | |
709 | } | ||
710 | }); | ||
711 | } | ||
712 | } | ||
713 | |||
714 | 24 | LeafManagerType leafs(mGrid->tree(), 0, mGrainSize==0); | |
715 |
1/2✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
|
12 | mTask = std::bind(&Filter::doOffset, std::placeholders::_1, std::placeholders::_2, value); |
716 |
1/2✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
|
12 | this->cook(leafs); |
717 | |||
718 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
12 | 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 | ||
729 | 152 | Filter<GridT, MaskT, InterruptT>::cook(LeafManagerType& leafs) | |
730 | { | ||
731 |
2/2✓ Branch 0 taken 75 times.
✓ Branch 1 taken 1 times.
|
152 | if (mGrainSize>0) { |
732 | 150 | tbb::parallel_for(leafs.leafRange(mGrainSize), *this); | |
733 | } else { | ||
734 | 2 | (*this)(leafs.leafRange()); | |
735 | } | ||
736 | 152 | 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 | ||
744 | 2582 | Filter<GridT, MaskT, InterruptT>::doBox(const RangeType& range, Int32 w) | |
745 | { | ||
746 | 2582 | this->wasInterrupted(); | |
747 | 2582 | AvgT avg(mGrid, w); | |
748 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1291 times.
|
2582 | 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 |
2/2✓ Branch 1 taken 4947 times.
✓ Branch 2 taken 1291 times.
|
12476 | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { |
765 | BufferT& buffer = leafIter.buffer(1); | ||
766 |
2/2✓ Branch 0 taken 2512896 times.
✓ Branch 1 taken 4947 times.
|
5035686 | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { |
767 |
3/8✓ Branch 1 taken 2512896 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2512896 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2512896 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
5025792 | 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 | ||
777 | 520 | Filter<GridT, MaskT, InterruptT>::doMedian(const RangeType& range, int width) | |
778 | { | ||
779 | 520 | this->wasInterrupted(); | |
780 | 520 | typename math::DenseStencil<GridType> stencil(*mGrid, width);//creates local cache! | |
781 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 260 times.
|
520 | 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 |
2/2✓ Branch 1 taken 1478 times.
✓ Branch 2 taken 260 times.
|
3476 | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { |
798 | BufferT& buffer = leafIter.buffer(1); | ||
799 |
2/2✓ Branch 0 taken 756736 times.
✓ Branch 1 taken 1478 times.
|
1516428 | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { |
800 | stencil.moveTo(iter); | ||
801 |
2/4✓ Branch 1 taken 756736 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 756736 times.
✗ Branch 5 not taken.
|
1513472 | 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 | ||
811 | 252 | Filter<GridT, MaskT, InterruptT>::doOffset(const RangeType& range, ValueType offset) | |
812 | { | ||
813 | 252 | this->wasInterrupted(); | |
814 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 126 times.
|
252 | 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 |
2/2✓ Branch 1 taken 250 times.
✓ Branch 2 taken 126 times.
|
752 | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { |
829 |
2/2✓ Branch 0 taken 128000 times.
✓ Branch 1 taken 250 times.
|
256500 | for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { |
830 | 256000 | iter.setValue(*iter + offset); | |
831 | } | ||
832 | } | ||
833 | } | ||
834 | } | ||
835 | |||
836 | |||
837 | template<typename GridT, typename MaskT, typename InterruptT> | ||
838 | inline bool | ||
839 | 1710 | Filter<GridT, MaskT, InterruptT>::wasInterrupted() | |
840 | { | ||
841 | 1710 | 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 |