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/LevelSetFilter.h | ||
7 | /// | ||
8 | /// @brief Performs various types of level set deformations with | ||
9 | /// interface tracking. These unrestricted deformations include | ||
10 | /// surface smoothing (e.g., Laplacian flow), filtering (e.g., mean | ||
11 | /// value) and morphological operations (e.g., morphological opening). | ||
12 | /// All these operations can optionally be masked with another grid that | ||
13 | /// acts as an alpha-mask. | ||
14 | |||
15 | #ifndef OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED | ||
16 | #define OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED | ||
17 | |||
18 | #include "LevelSetTracker.h" | ||
19 | #include "Interpolation.h" | ||
20 | #include <algorithm> // for std::max() | ||
21 | #include <functional> | ||
22 | #include <type_traits> | ||
23 | |||
24 | |||
25 | namespace openvdb { | ||
26 | OPENVDB_USE_VERSION_NAMESPACE | ||
27 | namespace OPENVDB_VERSION_NAME { | ||
28 | namespace tools { | ||
29 | |||
30 | /// @brief Filtering (e.g. diffusion) of narrow-band level sets. An | ||
31 | /// optional scalar field can be used to produce a (smooth) alpha mask | ||
32 | /// for the filtering. | ||
33 | /// | ||
34 | /// @note This class performs proper interface tracking which allows | ||
35 | /// for unrestricted surface deformations | ||
36 | template<typename GridT, | ||
37 | typename MaskT = typename GridT::template ValueConverter<float>::Type, | ||
38 | typename InterruptT = util::NullInterrupter> | ||
39 | class LevelSetFilter : public LevelSetTracker<GridT, InterruptT> | ||
40 | { | ||
41 | public: | ||
42 | using BaseType = LevelSetTracker<GridT, InterruptT>; | ||
43 | using GridType = GridT; | ||
44 | using MaskType = MaskT; | ||
45 | using TreeType = typename GridType::TreeType; | ||
46 | using ValueType = typename TreeType::ValueType; | ||
47 | using AlphaType = typename MaskType::ValueType; | ||
48 | static_assert(std::is_floating_point<AlphaType>::value, | ||
49 | "LevelSetFilter requires a mask grid with floating-point values"); | ||
50 | |||
51 | /// @brief Main constructor from a grid | ||
52 | /// @param grid The level set to be filtered. | ||
53 | /// @param interrupt Optional interrupter. | ||
54 | 2 | LevelSetFilter(GridType& grid, InterruptT* interrupt = nullptr) | |
55 | : BaseType(grid, interrupt) | ||
56 | , mMinMask(0) | ||
57 | , mMaxMask(1) | ||
58 |
1/4✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
|
2 | , mInvertMask(false) |
59 | { | ||
60 | } | ||
61 | /// @brief Default destructor | ||
62 | 2 | ~LevelSetFilter() override {} | |
63 | |||
64 | /// @brief Return the minimum value of the mask to be used for the | ||
65 | /// derivation of a smooth alpha value. | ||
66 | ✗ | AlphaType minMask() const { return mMinMask; } | |
67 | /// @brief Return the maximum value of the mask to be used for the | ||
68 | /// derivation of a smooth alpha value. | ||
69 | ✗ | AlphaType maxMask() const { return mMaxMask; } | |
70 | /// @brief Define the range for the (optional) scalar mask. | ||
71 | /// @param min Minimum value of the range. | ||
72 | /// @param max Maximum value of the range. | ||
73 | /// @details Mask values outside the range maps to alpha values of | ||
74 | /// respectfully zero and one, and values inside the range maps | ||
75 | /// smoothly to 0->1 (unless of course the mask is inverted). | ||
76 | /// @throw ValueError if @a min is not smaller than @a max. | ||
77 | ✗ | void setMaskRange(AlphaType min, AlphaType max) | |
78 | { | ||
79 | ✗ | if (!(min < max)) OPENVDB_THROW(ValueError, "Invalid mask range (expects min < max)"); | |
80 | ✗ | mMinMask = min; | |
81 | ✗ | mMaxMask = max; | |
82 | } | ||
83 | |||
84 | /// @brief Return true if the mask is inverted, i.e. min->max in the | ||
85 | /// original mask maps to 1->0 in the inverted alpha mask. | ||
86 | ✗ | bool isMaskInverted() const { return mInvertMask; } | |
87 | /// @brief Invert the optional mask, i.e. min->max in the original | ||
88 | /// mask maps to 1->0 in the inverted alpha mask. | ||
89 | ✗ | void invertMask(bool invert=true) { mInvertMask = invert; } | |
90 | |||
91 | /// @brief One iteration of mean-curvature flow of the level set. | ||
92 | /// @param mask Optional alpha mask. | ||
93 | ✗ | void meanCurvature(const MaskType* mask = nullptr) | |
94 | { | ||
95 | ✗ | Filter f(this, mask); f.meanCurvature(); | |
96 | } | ||
97 | |||
98 | /// @brief One iteration of Laplacian flow of the level set. | ||
99 | /// @param mask Optional alpha mask. | ||
100 | ✗ | void laplacian(const MaskType* mask = nullptr) | |
101 | { | ||
102 | ✗ | Filter f(this, mask); f.laplacian(); | |
103 | } | ||
104 | |||
105 | /// @brief One iteration of a fast separable Gaussian filter. | ||
106 | /// @param width Width of the Gaussian kernel in voxel units. | ||
107 | /// @param mask Optional alpha mask. | ||
108 | /// | ||
109 | /// @note This is approximated as 4 iterations of a separable mean filter | ||
110 | /// which typically leads an approximation that's better than 95%! | ||
111 | ✗ | void gaussian(int width = 1, const MaskType* mask = nullptr) | |
112 | { | ||
113 | ✗ | Filter f(this, mask); f.gaussian(width); | |
114 | } | ||
115 | |||
116 | /// @brief Offset the level set by the specified (world) distance. | ||
117 | /// @param offset Value of the offset. | ||
118 | /// @param mask Optional alpha mask. | ||
119 | ✗ | void offset(ValueType offset, const MaskType* mask = nullptr) | |
120 | { | ||
121 | ✗ | Filter f(this, mask); f.offset(offset); | |
122 | } | ||
123 | |||
124 | /// @brief One iteration of median-value flow of the level set. | ||
125 | /// @param width Width of the median-value kernel in voxel units. | ||
126 | /// @param mask Optional alpha mask. | ||
127 | /// | ||
128 | /// @warning This filter is not separable and is hence relatively | ||
129 | /// slow! | ||
130 | ✗ | void median(int width = 1, const MaskType* mask = nullptr) | |
131 | { | ||
132 | ✗ | Filter f(this, mask); f.median(width); | |
133 | } | ||
134 | |||
135 | /// @brief One iteration of mean-value flow of the level set. | ||
136 | /// @param width Width of the mean-value kernel in voxel units. | ||
137 | /// @param mask Optional alpha mask. | ||
138 | /// | ||
139 | /// @note This filter is separable so it's fast! | ||
140 | ✗ | void mean(int width = 1, const MaskType* mask = nullptr) | |
141 | { | ||
142 | ✗ | Filter f(this, mask); f.mean(width); | |
143 | } | ||
144 | |||
145 | private: | ||
146 | // disallow copy construction and copy by assignment! | ||
147 | LevelSetFilter(const LevelSetFilter&);// not implemented | ||
148 | LevelSetFilter& operator=(const LevelSetFilter&);// not implemented | ||
149 | |||
150 | // Private struct that implements all the filtering. | ||
151 | struct Filter | ||
152 | { | ||
153 | using LeafT = typename TreeType::LeafNodeType; | ||
154 | using VoxelIterT = typename LeafT::ValueOnIter; | ||
155 | using VoxelCIterT = typename LeafT::ValueOnCIter; | ||
156 | using BufferT = typename tree::LeafManager<TreeType>::BufferType; | ||
157 | using LeafRange = typename tree::LeafManager<TreeType>::LeafRange; | ||
158 | using LeafIterT = typename LeafRange::Iterator; | ||
159 | using AlphaMaskT = tools::AlphaMask<GridT, MaskT>; | ||
160 | |||
161 | ✗ | Filter(LevelSetFilter* parent, const MaskType* mask) : mParent(parent), mMask(mask) {} | |
162 | ✗ | Filter(const Filter&) = default; | |
163 | ✗ | virtual ~Filter() {} | |
164 | |||
165 | void box(int width); | ||
166 | void median(int width); | ||
167 | void mean(int width); | ||
168 | void gaussian(int width); | ||
169 | void laplacian(); | ||
170 | void meanCurvature(); | ||
171 | void offset(ValueType value); | ||
172 | ✗ | void operator()(const LeafRange& r) const | |
173 | { | ||
174 | ✗ | if (mTask) mTask(const_cast<Filter*>(this), r); | |
175 | ✗ | else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); | |
176 | } | ||
177 | ✗ | void cook(bool swap) | |
178 | { | ||
179 | ✗ | const int n = mParent->getGrainSize(); | |
180 | ✗ | if (n>0) { | |
181 | ✗ | tbb::parallel_for(mParent->leafs().leafRange(n), *this); | |
182 | } else { | ||
183 | ✗ | (*this)(mParent->leafs().leafRange()); | |
184 | } | ||
185 | ✗ | if (swap) mParent->leafs().swapLeafBuffer(1, n==0); | |
186 | } | ||
187 | |||
188 | template <size_t Axis> | ||
189 | struct Avg { | ||
190 | ✗ | Avg(const GridT& grid, Int32 w) : | |
191 | ✗ | acc(grid.tree()), width(w), frac(1/ValueType(2*w+1)) {} | |
192 | ✗ | inline ValueType operator()(Coord xyz) | |
193 | { | ||
194 | ValueType sum = zeroVal<ValueType>(); | ||
195 | ✗ | Int32& i = xyz[Axis], j = i + width; | |
196 | ✗ | for (i -= width; i <= j; ++i) sum += acc.getValue(xyz); | |
197 | ✗ | return sum*frac; | |
198 | } | ||
199 | typename GridT::ConstAccessor acc; | ||
200 | const Int32 width; | ||
201 | const ValueType frac; | ||
202 | }; | ||
203 | |||
204 | template<typename AvgT> | ||
205 | void boxImpl(const LeafRange& r, Int32 w); | ||
206 | |||
207 | ✗ | void boxXImpl(const LeafRange& r, Int32 w) { this->boxImpl<Avg<0> >(r,w); } | |
208 | ✗ | void boxZImpl(const LeafRange& r, Int32 w) { this->boxImpl<Avg<1> >(r,w); } | |
209 | ✗ | void boxYImpl(const LeafRange& r, Int32 w) { this->boxImpl<Avg<2> >(r,w); } | |
210 | |||
211 | void medianImpl(const LeafRange&, int); | ||
212 | void meanCurvatureImpl(const LeafRange&); | ||
213 | void laplacianImpl(const LeafRange&); | ||
214 | void offsetImpl(const LeafRange&, ValueType); | ||
215 | |||
216 | LevelSetFilter* mParent; | ||
217 | const MaskType* mMask; | ||
218 | typename std::function<void (Filter*, const LeafRange&)> mTask; | ||
219 | }; // end of private Filter struct | ||
220 | |||
221 | AlphaType mMinMask, mMaxMask; | ||
222 | bool mInvertMask; | ||
223 | |||
224 | }; // end of LevelSetFilter class | ||
225 | |||
226 | |||
227 | //////////////////////////////////////// | ||
228 | |||
229 | template<typename GridT, typename MaskT, typename InterruptT> | ||
230 | inline void | ||
231 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::median(int width) | |
232 | { | ||
233 | ✗ | mParent->startInterrupter("Median-value flow of level set"); | |
234 | |||
235 | ✗ | mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0); | |
236 | |||
237 | ✗ | mTask = std::bind(&Filter::medianImpl, | |
238 | ✗ | std::placeholders::_1, std::placeholders::_2, std::max(1, width)); | |
239 | ✗ | this->cook(true); | |
240 | |||
241 | ✗ | mParent->track(); | |
242 | |||
243 | ✗ | mParent->endInterrupter(); | |
244 | } | ||
245 | |||
246 | template<typename GridT, typename MaskT, typename InterruptT> | ||
247 | inline void | ||
248 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::mean(int width) | |
249 | { | ||
250 | ✗ | mParent->startInterrupter("Mean-value flow of level set"); | |
251 | |||
252 | ✗ | this->box(width); | |
253 | |||
254 | ✗ | mParent->endInterrupter(); | |
255 | } | ||
256 | |||
257 | template<typename GridT, typename MaskT, typename InterruptT> | ||
258 | inline void | ||
259 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::gaussian(int width) | |
260 | { | ||
261 | ✗ | mParent->startInterrupter("Gaussian flow of level set"); | |
262 | |||
263 | ✗ | for (int n=0; n<4; ++n) this->box(width); | |
264 | |||
265 | ✗ | mParent->endInterrupter(); | |
266 | } | ||
267 | |||
268 | template<typename GridT, typename MaskT, typename InterruptT> | ||
269 | inline void | ||
270 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::box(int width) | |
271 | { | ||
272 | ✗ | mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0); | |
273 | |||
274 | ✗ | width = std::max(1, width); | |
275 | |||
276 | ✗ | mTask = std::bind(&Filter::boxXImpl, std::placeholders::_1, std::placeholders::_2, width); | |
277 | ✗ | this->cook(true); | |
278 | |||
279 | ✗ | mTask = std::bind(&Filter::boxYImpl, std::placeholders::_1, std::placeholders::_2, width); | |
280 | ✗ | this->cook(true); | |
281 | |||
282 | ✗ | mTask = std::bind(&Filter::boxZImpl, std::placeholders::_1, std::placeholders::_2, width); | |
283 | ✗ | this->cook(true); | |
284 | |||
285 | ✗ | mParent->track(); | |
286 | } | ||
287 | |||
288 | template<typename GridT, typename MaskT, typename InterruptT> | ||
289 | inline void | ||
290 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::meanCurvature() | |
291 | { | ||
292 | ✗ | mParent->startInterrupter("Mean-curvature flow of level set"); | |
293 | |||
294 | ✗ | mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0); | |
295 | |||
296 | ✗ | mTask = std::bind(&Filter::meanCurvatureImpl, std::placeholders::_1, std::placeholders::_2); | |
297 | ✗ | this->cook(true); | |
298 | |||
299 | ✗ | mParent->track(); | |
300 | |||
301 | ✗ | mParent->endInterrupter(); | |
302 | } | ||
303 | |||
304 | template<typename GridT, typename MaskT, typename InterruptT> | ||
305 | inline void | ||
306 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::laplacian() | |
307 | { | ||
308 | ✗ | mParent->startInterrupter("Laplacian flow of level set"); | |
309 | |||
310 | ✗ | mParent->leafs().rebuildAuxBuffers(1, mParent->getGrainSize()==0); | |
311 | |||
312 | ✗ | mTask = std::bind(&Filter::laplacianImpl, std::placeholders::_1, std::placeholders::_2); | |
313 | ✗ | this->cook(true); | |
314 | |||
315 | ✗ | mParent->track(); | |
316 | |||
317 | ✗ | mParent->endInterrupter(); | |
318 | } | ||
319 | |||
320 | template<typename GridT, typename MaskT, typename InterruptT> | ||
321 | inline void | ||
322 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::offset(ValueType value) | |
323 | { | ||
324 | ✗ | mParent->startInterrupter("Offsetting level set"); | |
325 | |||
326 | ✗ | mParent->leafs().removeAuxBuffers();// no auxiliary buffers required | |
327 | |||
328 | ✗ | const ValueType CFL = ValueType(0.5) * mParent->voxelSize(), offset = openvdb::math::Abs(value); | |
329 | ValueType dist = 0.0; | ||
330 | ✗ | while (offset-dist > ValueType(0.001)*CFL && mParent->checkInterrupter()) { | |
331 | ✗ | const ValueType delta = openvdb::math::Min(offset-dist, CFL); | |
332 | ✗ | dist += delta; | |
333 | |||
334 | ✗ | mTask = std::bind(&Filter::offsetImpl, | |
335 | ✗ | std::placeholders::_1, std::placeholders::_2, copysign(delta, value)); | |
336 | ✗ | this->cook(false); | |
337 | |||
338 | ✗ | mParent->track(); | |
339 | } | ||
340 | |||
341 | ✗ | mParent->endInterrupter(); | |
342 | } | ||
343 | |||
344 | |||
345 | ///////////////////////// PRIVATE METHODS ////////////////////// | ||
346 | |||
347 | /// Performs parabolic mean-curvature diffusion | ||
348 | template<typename GridT, typename MaskT, typename InterruptT> | ||
349 | inline void | ||
350 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::meanCurvatureImpl(const LeafRange& range) | |
351 | { | ||
352 | ✗ | mParent->checkInterrupter(); | |
353 | //const float CFL = 0.9f, dt = CFL * mDx * mDx / 6.0f; | ||
354 | ✗ | const ValueType dx = mParent->voxelSize(), dt = math::Pow2(dx) / ValueType(3.0); | |
355 | ✗ | math::CurvatureStencil<GridType> stencil(mParent->grid(), dx); | |
356 | ✗ | if (mMask) { | |
357 | typename AlphaMaskT::FloatType a, b; | ||
358 | ✗ | AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), | |
359 | ✗ | mParent->maxMask(), mParent->isMaskInverted()); | |
360 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
361 | ✗ | ValueType* buffer = leafIter.buffer(1).data(); | |
362 | ✗ | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { | |
363 | ✗ | if (alpha(iter.getCoord(), a, b)) { | |
364 | ✗ | stencil.moveTo(iter); | |
365 | ✗ | const ValueType phi0 = *iter, phi1 = phi0 + dt*stencil.meanCurvatureNormGrad(); | |
366 | ✗ | buffer[iter.pos()] = b * phi0 + a * phi1; | |
367 | } | ||
368 | } | ||
369 | } | ||
370 | } else { | ||
371 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
372 | ✗ | ValueType* buffer = leafIter.buffer(1).data(); | |
373 | ✗ | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { | |
374 | ✗ | stencil.moveTo(iter); | |
375 | ✗ | buffer[iter.pos()] = *iter + dt*stencil.meanCurvatureNormGrad(); | |
376 | } | ||
377 | } | ||
378 | } | ||
379 | } | ||
380 | |||
381 | /// Performs Laplacian diffusion. Note if the grids contains a true | ||
382 | /// signed distance field (e.g. a solution to the Eikonal equation) | ||
383 | /// Laplacian diffusions (e.g. geometric heat equation) is actually | ||
384 | /// identical to mean curvature diffusion, yet less computationally | ||
385 | /// expensive! In other words if you're performing renormalization | ||
386 | /// anyway (e.g. rebuilding the narrow-band) you should consider | ||
387 | /// performing Laplacian diffusion over mean curvature flow! | ||
388 | template<typename GridT, typename MaskT, typename InterruptT> | ||
389 | inline void | ||
390 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::laplacianImpl(const LeafRange& range) | |
391 | { | ||
392 | ✗ | mParent->checkInterrupter(); | |
393 | //const float CFL = 0.9f, half_dt = CFL * mDx * mDx / 12.0f; | ||
394 | ✗ | const ValueType dx = mParent->voxelSize(), dt = math::Pow2(dx) / ValueType(6.0); | |
395 | ✗ | math::GradStencil<GridType> stencil(mParent->grid(), dx); | |
396 | ✗ | if (mMask) { | |
397 | typename AlphaMaskT::FloatType a, b; | ||
398 | ✗ | AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), | |
399 | ✗ | mParent->maxMask(), mParent->isMaskInverted()); | |
400 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
401 | ✗ | ValueType* buffer = leafIter.buffer(1).data(); | |
402 | ✗ | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { | |
403 | ✗ | if (alpha(iter.getCoord(), a, b)) { | |
404 | ✗ | stencil.moveTo(iter); | |
405 | ✗ | const ValueType phi0 = *iter, phi1 = phi0 + dt*stencil.laplacian(); | |
406 | ✗ | buffer[iter.pos()] = b * phi0 + a * phi1; | |
407 | } | ||
408 | } | ||
409 | } | ||
410 | } else { | ||
411 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
412 | ✗ | ValueType* buffer = leafIter.buffer(1).data(); | |
413 | ✗ | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { | |
414 | ✗ | stencil.moveTo(iter); | |
415 | ✗ | buffer[iter.pos()] = *iter + dt*stencil.laplacian(); | |
416 | } | ||
417 | } | ||
418 | } | ||
419 | } | ||
420 | |||
421 | /// Offsets the values by a constant | ||
422 | template<typename GridT, typename MaskT, typename InterruptT> | ||
423 | inline void | ||
424 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::offsetImpl( | |
425 | const LeafRange& range, ValueType offset) | ||
426 | { | ||
427 | ✗ | mParent->checkInterrupter(); | |
428 | ✗ | if (mMask) { | |
429 | typename AlphaMaskT::FloatType a, b; | ||
430 | ✗ | AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), | |
431 | ✗ | mParent->maxMask(), mParent->isMaskInverted()); | |
432 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
433 | ✗ | for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { | |
434 | ✗ | if (alpha(iter.getCoord(), a, b)) iter.setValue(*iter + a*offset); | |
435 | } | ||
436 | } | ||
437 | } else { | ||
438 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
439 | ✗ | for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { | |
440 | ✗ | iter.setValue(*iter + offset); | |
441 | } | ||
442 | } | ||
443 | } | ||
444 | } | ||
445 | |||
446 | /// Performs simple but slow median-value diffusion | ||
447 | template<typename GridT, typename MaskT, typename InterruptT> | ||
448 | inline void | ||
449 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::medianImpl(const LeafRange& range, int width) | |
450 | { | ||
451 | ✗ | mParent->checkInterrupter(); | |
452 | ✗ | typename math::DenseStencil<GridType> stencil(mParent->grid(), width);//creates local cache! | |
453 | ✗ | if (mMask) { | |
454 | typename AlphaMaskT::FloatType a, b; | ||
455 | ✗ | AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), | |
456 | ✗ | mParent->maxMask(), mParent->isMaskInverted()); | |
457 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
458 | ✗ | ValueType* buffer = leafIter.buffer(1).data(); | |
459 | ✗ | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { | |
460 | ✗ | if (alpha(iter.getCoord(), a, b)) { | |
461 | stencil.moveTo(iter); | ||
462 | ✗ | buffer[iter.pos()] = b * (*iter) + a * stencil.median(); | |
463 | } | ||
464 | } | ||
465 | } | ||
466 | } else { | ||
467 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
468 | ✗ | ValueType* buffer = leafIter.buffer(1).data(); | |
469 | ✗ | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { | |
470 | stencil.moveTo(iter); | ||
471 | ✗ | buffer[iter.pos()] = stencil.median(); | |
472 | } | ||
473 | } | ||
474 | } | ||
475 | } | ||
476 | |||
477 | /// One dimensional convolution of a separable box filter | ||
478 | template<typename GridT, typename MaskT, typename InterruptT> | ||
479 | template <typename AvgT> | ||
480 | inline void | ||
481 | ✗ | LevelSetFilter<GridT, MaskT, InterruptT>::Filter::boxImpl(const LeafRange& range, Int32 w) | |
482 | { | ||
483 | ✗ | mParent->checkInterrupter(); | |
484 | ✗ | AvgT avg(mParent->grid(), w); | |
485 | ✗ | if (mMask) { | |
486 | typename AlphaMaskT::FloatType a, b; | ||
487 | ✗ | AlphaMaskT alpha(mParent->grid(), *mMask, mParent->minMask(), | |
488 | ✗ | mParent->maxMask(), mParent->isMaskInverted()); | |
489 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
490 | ✗ | ValueType* buffer = leafIter.buffer(1).data(); | |
491 | ✗ | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { | |
492 | ✗ | const Coord xyz = iter.getCoord(); | |
493 | ✗ | if (alpha(xyz, a, b)) buffer[iter.pos()] = b * (*iter)+ a * avg(xyz); | |
494 | } | ||
495 | } | ||
496 | } else { | ||
497 | ✗ | for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { | |
498 | ✗ | ValueType* buffer = leafIter.buffer(1).data(); | |
499 | ✗ | for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { | |
500 | ✗ | buffer[iter.pos()] = avg(iter.getCoord()); | |
501 | } | ||
502 | } | ||
503 | } | ||
504 | } | ||
505 | |||
506 | |||
507 | //////////////////////////////////////// | ||
508 | |||
509 | |||
510 | // Explicit Template Instantiation | ||
511 | |||
512 | #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
513 | |||
514 | #ifdef OPENVDB_INSTANTIATE_LEVELSETFILTER | ||
515 | #include <openvdb/util/ExplicitInstantiation.h> | ||
516 | #endif | ||
517 | |||
518 | OPENVDB_INSTANTIATE_CLASS LevelSetFilter<FloatGrid, FloatGrid, util::NullInterrupter>; | ||
519 | OPENVDB_INSTANTIATE_CLASS LevelSetFilter<DoubleGrid, FloatGrid, util::NullInterrupter>; | ||
520 | |||
521 | #endif // OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
522 | |||
523 | |||
524 | } // namespace tools | ||
525 | } // namespace OPENVDB_VERSION_NAME | ||
526 | } // namespace openvdb | ||
527 | |||
528 | #endif // OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED | ||
529 |