1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 //
4 /// @author Ken Museth
5 ///
6 /// @file LevelSetMeasure.h
7
8 #ifndef OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED
9 #define OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED
10
11 #include "openvdb/Types.h"
12 #include "openvdb/Grid.h"
13 #include "openvdb/tree/LeafManager.h"
14 #include "openvdb/tree/ValueAccessor.h"
15 #include "openvdb/math/Math.h"
16 #include "openvdb/math/FiniteDifference.h"
17 #include "openvdb/math/Operators.h"
18 #include "openvdb/math/Stencils.h"
19 #include "openvdb/util/NullInterrupter.h"
20 #include "openvdb/thread/Threading.h"
21 #include <openvdb/openvdb.h>
22
23 #include <tbb/parallel_for.h>
24 #include <tbb/parallel_sort.h>
25 #include <tbb/parallel_invoke.h>
26
27 #include <type_traits>
28
29 namespace openvdb {
30 OPENVDB_USE_VERSION_NAMESPACE
31 namespace OPENVDB_VERSION_NAME {
32 namespace tools {
33
34 /// @brief Return the surface area of a narrow-band level set.
35 ///
36 /// @param grid a scalar, floating-point grid with one or more disjoint,
37 /// closed level set surfaces
38 /// @param useWorldSpace if true the area is computed in
39 /// world space units, else in voxel units.
40 ///
41 /// @throw TypeError if @a grid is not scalar or not floating-point or not a level set or empty.
42 template<class GridType>
43 Real
44 levelSetArea(const GridType& grid, bool useWorldSpace = true);
45
46 /// @brief Return the volume of a narrow-band level set surface.
47 ///
48 /// @param grid a scalar, floating-point grid with one or more disjoint,
49 /// closed level set surfaces
50 /// @param useWorldSpace if true the volume is computed in
51 /// world space units, else in voxel units.
52 ///
53 /// @throw TypeError if @a grid is not scalar or not floating-point or not a level set or empty.
54 template<class GridType>
55 Real
56 levelSetVolume(const GridType& grid, bool useWorldSpace = true);
57
58 /// @brief Return the Euler Characteristics of a narrow-band level set surface (possibly disconnected).
59 ///
60 /// @param grid a scalar, floating-point grid with one or more disjoint,
61 /// closed level set surfaces
62 ///
63 /// @throw TypeError if @a grid is not scalar or not floating-point or not a level set or empty.
64 template<class GridType>
65 int
66 levelSetEulerCharacteristic(const GridType& grid);
67
68 /// @brief Return the genus of a narrow-band level set surface.
69 ///
70 /// @param grid a scalar, floating-point grid with one or more disjoint,
71 /// closed level set surfaces
72 /// @warning The genus is only well defined for a single connected surface
73 ///
74 /// @throw TypeError if @a grid is not scalar or not floating-point or not a level set or empty.
75 template<class GridType>
76 int
77 levelSetGenus(const GridType& grid);
78
79 ////////////////////////////////////////////////////////////////////////////////////////
80
81 /// @brief Smeared-out and continuous Dirac Delta function.
82 template<typename RealT>
83 class DiracDelta
84 {
85 public:
86 // eps is the half-width of the dirac delta function in units of phi
DiracDelta(RealT eps)87 DiracDelta(RealT eps) : mC(0.5/eps), mD(2*math::pi<RealT>()*mC), mE(eps) {}
88 // values of the dirac delta function are in units of one over the units of phi
operator()89 inline RealT operator()(RealT phi) const { return math::Abs(phi) > mE ? 0 : mC*(1+cos(mD*phi)); }
90 private:
91 const RealT mC, mD, mE;
92 };// DiracDelta functor
93
94
95 /// @brief Multi-threaded computation of surface area, volume and
96 /// average mean-curvature for narrow band level sets.
97 ///
98 /// @details To reduce the risk of round-off errors (primarily due to
99 /// catastrophic cancellation) and guarantee determinism during
100 /// multi-threading this class is implemented using parallel_for, and
101 /// delayed reduction of a sorted list.
102 template<typename GridT, typename InterruptT = util::NullInterrupter>
103 class LevelSetMeasure
104 {
105 public:
106 using GridType = GridT;
107 using TreeType = typename GridType::TreeType;
108 using ValueType = typename TreeType::ValueType;
109 using ManagerType = typename tree::LeafManager<const TreeType>;
110
111 static_assert(std::is_floating_point<ValueType>::value,
112 "level set measure is supported only for scalar, floating-point grids");
113
114 /// @brief Main constructor from a grid
115 /// @param grid The level set to be measured.
116 /// @param interrupt Optional interrupter.
117 /// @throw RuntimeError if the grid is not a level set or if it's empty.
118 LevelSetMeasure(const GridType& grid, InterruptT* interrupt = nullptr);
119
120 /// @brief Re-initialize using the specified grid.
121 /// @param grid The level set to be measured.
122 /// @throw RuntimeError if the grid is not a level set or if it's empty.
123 void init(const GridType& grid);
124
125 /// @brief Destructor
~LevelSetMeasure()126 virtual ~LevelSetMeasure() {}
127
128 /// @return the grain-size used for multi-threading
getGrainSize()129 int getGrainSize() const { return mGrainSize; }
130
131 /// @brief Set the grain-size used for multi-threading.
132 /// @note A grain size of 0 or less disables multi-threading!
setGrainSize(int grainsize)133 void setGrainSize(int grainsize) { mGrainSize = grainsize; }
134
135 /// @brief Compute the surface area of the level set.
136 /// @param useWorldUnits Specifies if the result is in world or voxel units.
137 /// @note Performs internal caching so only the initial call incurs actual computation.
138 Real area(bool useWorldUnits = true);
139
140 /// @brief Compute the volume of the level set surface.
141 /// @param useWorldUnits Specifies if the result is in world or voxel units.
142 /// @note Performs internal caching so only the initial call incurs actual computation.
143 Real volume(bool useWorldUnits = true);
144
145 /// @brief Compute the total mean curvature of the level set surface.
146 /// @param useWorldUnits Specifies if the result is in world or voxel units.
147 /// @note Performs internal caching so only the initial call incurs actual computation.
148 Real totMeanCurvature(bool useWorldUnits = true);
149
150 /// @brief Compute the total gaussian curvature of the level set surface.
151 /// @param useWorldUnits Specifies if the result is in world or voxel units.
152 /// @note Performs internal caching so only the initial call incurs actual computation.
153 Real totGaussianCurvature(bool useWorldUnits = true);
154
155 /// @brief Compute the average mean curvature of the level set surface.
156 /// @param useWorldUnits Specifies if the result is in world or voxel units.
157 /// @note Performs internal caching so only the initial call incurs actual computation.
158 Real avgMeanCurvature(bool useWorldUnits = true) {return this->totMeanCurvature(useWorldUnits) / this->area(useWorldUnits);}
159
160 /// @brief Compute the average gaussian curvature of the level set surface.
161 /// @param useWorldUnits Specifies if the result is in world or voxel units.
162 /// @note Performs internal caching so only the initial call incurs actual computation.
163 Real avgGaussianCurvature(bool useWorldUnits = true) {return this->totGaussianCurvature(useWorldUnits) / this->area(useWorldUnits); }
164
165 /// @brief Compute the Euler characteristic of the level set surface.
166 /// @note Performs internal caching so only the initial call incurs actual computation.
167 int eulerCharacteristic();
168
169 /// @brief Compute the genus of the level set surface.
170 /// @warning The genus is only well defined for a single connected surface.
171 /// @note Performs internal caching so only the initial call incurs actual computation.
genus()172 int genus() { return 1 - this->eulerCharacteristic()/2;}
173
174 private:
175
176 using LeafT = typename TreeType::LeafNodeType;
177 using VoxelCIterT = typename LeafT::ValueOnCIter;
178 using LeafRange = typename ManagerType::LeafRange;
179 using LeafIterT = typename LeafRange::Iterator;
180 using ManagerPtr = std::unique_ptr<ManagerType>;
181 using BufferPtr = std::unique_ptr<double[]>;
182
183 // disallow copy construction and copy by assignment!
184 LevelSetMeasure(const LevelSetMeasure&);// not implemented
185 LevelSetMeasure& operator=(const LevelSetMeasure&);// not implemented
186
187 const GridType *mGrid;
188 ManagerPtr mLeafs;
189 BufferPtr mBuffer;
190 InterruptT *mInterrupter;
191 double mDx, mArea, mVolume, mTotMeanCurvature, mTotGausCurvature;
192 int mGrainSize;
193 bool mUpdateArea, mUpdateCurvature;
194
195 // @brief Return false if the process was interrupted
196 bool checkInterrupter();
197
198 struct MeasureArea
199 {
MeasureAreaMeasureArea200 MeasureArea(LevelSetMeasure* parent) : mParent(parent), mStencil(*mParent->mGrid)
201 {
202 if (parent->mInterrupter) parent->mInterrupter->start("Measuring area and volume of level set");
203 if (parent->mGrainSize>0) {
204 tbb::parallel_for(parent->mLeafs->leafRange(parent->mGrainSize), *this);
205 } else {
206 (*this)(parent->mLeafs->leafRange());
207 }
208 tbb::parallel_invoke([&](){parent->mArea = parent->reduce(0);},
209 [&](){parent->mVolume = parent->reduce(1)/3.0;});
210 parent->mUpdateArea = false;
211 if (parent->mInterrupter) parent->mInterrupter->end();
212 }
MeasureAreaMeasureArea213 MeasureArea(const MeasureArea& other) : mParent(other.mParent), mStencil(*mParent->mGrid) {}
214 void operator()(const LeafRange& range) const;
215 LevelSetMeasure* mParent;
216 mutable math::GradStencil<GridT, false> mStencil;
217 };// MeasureArea
218
219 struct MeasureCurvatures
220 {
MeasureCurvaturesMeasureCurvatures221 MeasureCurvatures(LevelSetMeasure* parent) : mParent(parent), mStencil(*mParent->mGrid)
222 {
223 if (parent->mInterrupter) parent->mInterrupter->start("Measuring curvatures of level set");
224 if (parent->mGrainSize>0) {
225 tbb::parallel_for(parent->mLeafs->leafRange(parent->mGrainSize), *this);
226 } else {
227 (*this)(parent->mLeafs->leafRange());
228 }
229 tbb::parallel_invoke([&](){parent->mTotMeanCurvature = parent->reduce(0);},
230 [&](){parent->mTotGausCurvature = parent->reduce(1);});
231 parent->mUpdateCurvature = false;
232 if (parent->mInterrupter) parent->mInterrupter->end();
233 }
MeasureCurvaturesMeasureCurvatures234 MeasureCurvatures(const MeasureCurvatures& other) : mParent(other.mParent), mStencil(*mParent->mGrid) {}
235 void operator()(const LeafRange& range) const;
236 LevelSetMeasure* mParent;
237 mutable math::CurvatureStencil<GridT, false> mStencil;
238 };// MeasureCurvatures
239
reduce(int offset)240 double reduce(int offset)
241 {
242 double *first = mBuffer.get() + offset*mLeafs->leafCount(), *last = first + mLeafs->leafCount();
243 tbb::parallel_sort(first, last);// mitigates catastrophic cancellation
244 Real sum = 0.0;
245 while(first != last) sum += *first++;
246 return sum;
247 }
248
249 }; // end of LevelSetMeasure class
250
251
252 template<typename GridT, typename InterruptT>
253 inline
LevelSetMeasure(const GridType & grid,InterruptT * interrupt)254 LevelSetMeasure<GridT, InterruptT>::LevelSetMeasure(const GridType& grid, InterruptT* interrupt)
255 : mInterrupter(interrupt)
256 , mGrainSize(1)
257 {
258 this->init(grid);
259 }
260
261 template<typename GridT, typename InterruptT>
262 inline void
init(const GridType & grid)263 LevelSetMeasure<GridT, InterruptT>::init(const GridType& grid)
264 {
265 if (!grid.hasUniformVoxels()) {
266 OPENVDB_THROW(RuntimeError,
267 "The transform must have uniform scale for the LevelSetMeasure to function");
268 }
269 if (grid.getGridClass() != GRID_LEVEL_SET) {
270 OPENVDB_THROW(RuntimeError,
271 "LevelSetMeasure only supports level sets;"
272 " try setting the grid class to \"level set\"");
273 }
274 if (grid.empty()) {
275 OPENVDB_THROW(RuntimeError,
276 "LevelSetMeasure does not support empty grids;");
277 }
278 mGrid = &grid;
279 mDx = grid.voxelSize()[0];
280 mLeafs = std::make_unique<ManagerType>(mGrid->tree());
281 mBuffer = std::make_unique<double[]>(2*mLeafs->leafCount());
282 mUpdateArea = mUpdateCurvature = true;
283 }
284
285 template<typename GridT, typename InterruptT>
286 inline Real
area(bool useWorldUnits)287 LevelSetMeasure<GridT, InterruptT>::area(bool useWorldUnits)
288 {
289 if (mUpdateArea) {MeasureArea m(this);};
290 double area = mArea;
291 if (useWorldUnits) area *= math::Pow2(mDx);
292 return area;
293 }
294
295 template<typename GridT, typename InterruptT>
296 inline Real
volume(bool useWorldUnits)297 LevelSetMeasure<GridT, InterruptT>::volume(bool useWorldUnits)
298 {
299 if (mUpdateArea) {MeasureArea m(this);};
300 double volume = mVolume;
301 if (useWorldUnits) volume *= math::Pow3(mDx) ;
302 return volume;
303 }
304
305 template<typename GridT, typename InterruptT>
306 inline Real
totMeanCurvature(bool useWorldUnits)307 LevelSetMeasure<GridT, InterruptT>::totMeanCurvature(bool useWorldUnits)
308 {
309 if (mUpdateCurvature) {MeasureCurvatures m(this);};
310 return mTotMeanCurvature * (useWorldUnits ? mDx : 1);
311 }
312
313 template<typename GridT, typename InterruptT>
314 inline Real
totGaussianCurvature(bool)315 LevelSetMeasure<GridT, InterruptT>::totGaussianCurvature(bool)
316 {
317 if (mUpdateCurvature) {MeasureCurvatures m(this);};
318 return mTotGausCurvature;
319 }
320
321 template<typename GridT, typename InterruptT>
322 inline int
eulerCharacteristic()323 LevelSetMeasure<GridT, InterruptT>::eulerCharacteristic()
324 {
325 const Real x = this->totGaussianCurvature(true) / (2.0*math::pi<Real>());
326 return int(math::Round( x ));
327 }
328
329 ///////////////////////// PRIVATE METHODS //////////////////////
330
331 template<typename GridT, typename InterruptT>
332 inline bool
checkInterrupter()333 LevelSetMeasure<GridT, InterruptT>::checkInterrupter()
334 {
335 if (util::wasInterrupted(mInterrupter)) {
336 thread::cancelGroupExecution();
337 return false;
338 }
339 return true;
340 }
341
342 template<typename GridT, typename InterruptT>
343 inline void
344 LevelSetMeasure<GridT, InterruptT>::
operator()345 MeasureArea::operator()(const LeafRange& range) const
346 {
347 using Vec3T = math::Vec3<ValueType>;
348 // computations are performed in index space where dV = 1
349 mParent->checkInterrupter();
350 const Real invDx = 1.0/mParent->mDx;
351 const DiracDelta<Real> DD(1.5);// dirac delta function is 3 voxel units wide
352 const size_t leafCount = mParent->mLeafs->leafCount();
353 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
354 Real sumA = 0, sumV = 0;//reduce risk of catastrophic cancellation
355 for (VoxelCIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) {
356 const Real dd = DD(invDx * (*voxelIter));
357 if (dd > 0.0) {
358 mStencil.moveTo(voxelIter);
359 const Coord& p = mStencil.getCenterCoord();// in voxel units
360 const Vec3T g = mStencil.gradient();// in world units
361 sumA += dd*g.length();// \delta(\phi)*|\nabla\phi|
362 sumV += dd*(g[0]*Real(p[0]) + g[1]*Real(p[1]) + g[2]*Real(p[2]));// \delta(\phi)\vec{x}\cdot\nabla\phi
363 }
364 }
365 double* ptr = mParent->mBuffer.get() + leafIter.pos();
366 *ptr = sumA;
367 ptr += leafCount;
368 *ptr = sumV;
369 }
370 }
371
372 template<typename GridT, typename InterruptT>
373 inline void
374 LevelSetMeasure<GridT, InterruptT>::
operator()375 MeasureCurvatures::operator()(const LeafRange& range) const
376 {
377 using Vec3T = math::Vec3<ValueType>;
378 // computations are performed in index space where dV = 1
379 mParent->checkInterrupter();
380 const Real dx = mParent->mDx, dx2=dx*dx, invDx = 1.0/dx;
381 const DiracDelta<Real> DD(1.5);// dirac delta function is 3 voxel units wide
382 ValueType mean, gauss;
383 const size_t leafCount = mParent->mLeafs->leafCount();
384 for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) {
385 Real sumM = 0, sumG = 0;//reduce risk of catastrophic cancellation
386 for (VoxelCIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) {
387 const Real dd = DD(invDx * (*voxelIter));
388 if (dd > 0.0) {
389 mStencil.moveTo(voxelIter);
390 const Vec3T g = mStencil.gradient();
391 const Real dA = dd*g.length();// \delta(\phi)*\delta(\phi)
392 mStencil.curvatures(mean, gauss);
393 sumM += dA*mean*dx;// \delta(\phi)*\delta(\phi)*MeanCurvature
394 sumG += dA*gauss*dx2;// \delta(\phi)*\delta(\phi)*GaussCurvature
395 }
396 }
397 double* ptr = mParent->mBuffer.get() + leafIter.pos();
398 *ptr = sumM;
399 ptr += leafCount;
400 *ptr = sumG;
401 }
402 }
403
404 ////////////////////////////////////////
405
406 //{
407 /// @cond OPENVDB_DOCS_INTERNAL
408
409 template<class GridT>
410 inline
411 typename std::enable_if<std::is_floating_point<typename GridT::ValueType>::value, Real>::type
doLevelSetArea(const GridT & grid,bool useWorldUnits)412 doLevelSetArea(const GridT& grid, bool useWorldUnits)
413 {
414 LevelSetMeasure<GridT> m(grid);
415 return m.area(useWorldUnits);
416 }
417
418 template<class GridT>
419 inline
420 typename std::enable_if<!std::is_floating_point<typename GridT::ValueType>::value, Real>::type
doLevelSetArea(const GridT &,bool)421 doLevelSetArea(const GridT&, bool)
422 {
423 OPENVDB_THROW(TypeError,
424 "level set area is supported only for scalar, floating-point grids");
425 }
426
427 /// @endcond
428 //}
429
430 template<class GridT>
431 Real
levelSetArea(const GridT & grid,bool useWorldUnits)432 levelSetArea(const GridT& grid, bool useWorldUnits)
433 {
434 return doLevelSetArea<GridT>(grid, useWorldUnits);
435 }
436
437 ////////////////////////////////////////
438
439 //{
440 /// @cond OPENVDB_DOCS_INTERNAL
441
442 template<class GridT>
443 inline
444 typename std::enable_if<std::is_floating_point<typename GridT::ValueType>::value, Real>::type
doLevelSetVolume(const GridT & grid,bool useWorldUnits)445 doLevelSetVolume(const GridT& grid, bool useWorldUnits)
446 {
447 LevelSetMeasure<GridT> m(grid);
448 return m.volume(useWorldUnits);
449 }
450
451 template<class GridT>
452 inline
453 typename std::enable_if<!std::is_floating_point<typename GridT::ValueType>::value, Real>::type
doLevelSetVolume(const GridT &,bool)454 doLevelSetVolume(const GridT&, bool)
455 {
456 OPENVDB_THROW(TypeError,
457 "level set volume is supported only for scalar, floating-point grids");
458 }
459
460 /// @endcond
461 //}
462
463 template<class GridT>
464 Real
levelSetVolume(const GridT & grid,bool useWorldUnits)465 levelSetVolume(const GridT& grid, bool useWorldUnits)
466 {
467 return doLevelSetVolume<GridT>(grid, useWorldUnits);
468 }
469
470 ////////////////////////////////////////
471
472 //{
473 /// @cond OPENVDB_DOCS_INTERNAL
474
475 template<class GridT>
476 inline
477 typename std::enable_if<std::is_floating_point<typename GridT::ValueType>::value, int>::type
doLevelSetEulerCharacteristic(const GridT & grid)478 doLevelSetEulerCharacteristic(const GridT& grid)
479 {
480 LevelSetMeasure<GridT> m(grid);
481 return m.eulerCharacteristic();
482 }
483
484 template<class GridT>
485 inline
486 typename std::enable_if<!std::is_floating_point<typename GridT::ValueType>::value, int>::type
doLevelSetEulerCharacteristic(const GridT &)487 doLevelSetEulerCharacteristic(const GridT&)
488 {
489 OPENVDB_THROW(TypeError,
490 "level set euler characteristic is supported only for scalar, floating-point grids");
491 }
492
493 /// @endcond
494 //}
495
496
497 template<class GridT>
498 int
levelSetEulerCharacteristic(const GridT & grid)499 levelSetEulerCharacteristic(const GridT& grid)
500 {
501 return doLevelSetEulerCharacteristic(grid);
502 }
503
504 ////////////////////////////////////////
505
506 //{
507 /// @cond OPENVDB_DOCS_INTERNAL
508
509 template<class GridT>
510 inline
511 typename std::enable_if<std::is_floating_point<typename GridT::ValueType>::value, int>::type
doLevelSetEuler(const GridT & grid)512 doLevelSetEuler(const GridT& grid)
513 {
514 LevelSetMeasure<GridT> m(grid);
515 return m.eulerCharacteristics();
516
517 }
518
519 template<class GridT>
520 inline
521 typename std::enable_if<std::is_floating_point<typename GridT::ValueType>::value, int>::type
doLevelSetGenus(const GridT & grid)522 doLevelSetGenus(const GridT& grid)
523 {
524 LevelSetMeasure<GridT> m(grid);
525 return m.genus();
526 }
527
528 template<class GridT>
529 inline
530 typename std::enable_if<!std::is_floating_point<typename GridT::ValueType>::value, int>::type
doLevelSetGenus(const GridT &)531 doLevelSetGenus(const GridT&)
532 {
533 OPENVDB_THROW(TypeError,
534 "level set genus is supported only for scalar, floating-point grids");
535 }
536
537 /// @endcond
538 //}
539
540 template<class GridT>
541 int
levelSetGenus(const GridT & grid)542 levelSetGenus(const GridT& grid)
543 {
544 return doLevelSetGenus(grid);
545 }
546
547
548 ////////////////////////////////////////
549
550
551 // Explicit Template Instantiation
552
553 #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION
554
555 #ifdef OPENVDB_INSTANTIATE_LEVELSETMEASURE
556 #include <openvdb/util/ExplicitInstantiation.h>
557 #endif
558
559 #define _FUNCTION(TreeT) \
560 Real levelSetArea(const Grid<TreeT>&, bool)
561 OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
562 #undef _FUNCTION
563
564 #define _FUNCTION(TreeT) \
565 Real levelSetVolume(const Grid<TreeT>&, bool)
566 OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
567 #undef _FUNCTION
568
569 #define _FUNCTION(TreeT) \
570 int levelSetEulerCharacteristic(const Grid<TreeT>&)
571 OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
572 #undef _FUNCTION
573
574 #define _FUNCTION(TreeT) \
575 int levelSetGenus(const Grid<TreeT>&)
576 OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
577 #undef _FUNCTION
578
579 OPENVDB_INSTANTIATE_CLASS LevelSetMeasure<FloatGrid, util::NullInterrupter>;
580 OPENVDB_INSTANTIATE_CLASS LevelSetMeasure<DoubleGrid, util::NullInterrupter>;
581
582 #endif // OPENVDB_USE_EXPLICIT_INSTANTIATION
583
584
585 } // namespace tools
586 } // namespace OPENVDB_VERSION_NAME
587 } // namespace openvdb
588
589 #endif // OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED
590