| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright Contributors to the OpenVDB Project | ||
| 2 | // SPDX-License-Identifier: MPL-2.0 | ||
| 3 | // | ||
| 4 | /// @file TopologyToLevelSet.h | ||
| 5 | /// | ||
| 6 | /// @brief This tool generates a narrow-band signed distance field / level set | ||
| 7 | /// from the interface between active and inactive voxels in a vdb grid. | ||
| 8 | /// | ||
| 9 | /// @par Example: | ||
| 10 | /// Combine with @c tools::PointsToVolume for fast point cloud to level set conversion. | ||
| 11 | |||
| 12 | #ifndef OPENVDB_TOOLS_TOPOLOGY_TO_LEVELSET_HAS_BEEN_INCLUDED | ||
| 13 | #define OPENVDB_TOOLS_TOPOLOGY_TO_LEVELSET_HAS_BEEN_INCLUDED | ||
| 14 | |||
| 15 | #include "LevelSetFilter.h" | ||
| 16 | #include "Morphology.h" // for erodeActiveValues and dilateActiveValues | ||
| 17 | |||
| 18 | #include <openvdb/Grid.h> | ||
| 19 | #include <openvdb/Types.h> | ||
| 20 | #include <openvdb/math/FiniteDifference.h> // for math::BiasedGradientScheme | ||
| 21 | #include <openvdb/util/NullInterrupter.h> | ||
| 22 | #include <openvdb/openvdb.h> | ||
| 23 | #include <openvdb/points/PointDataGrid.h> | ||
| 24 | |||
| 25 | #include <tbb/task_group.h> | ||
| 26 | |||
| 27 | #include <algorithm> // for std::min(), std::max() | ||
| 28 | #include <vector> | ||
| 29 | |||
| 30 | |||
| 31 | namespace openvdb { | ||
| 32 | OPENVDB_USE_VERSION_NAMESPACE | ||
| 33 | namespace OPENVDB_VERSION_NAME { | ||
| 34 | namespace tools { | ||
| 35 | |||
| 36 | |||
| 37 | /// @brief Compute the narrow-band signed distance to the interface between | ||
| 38 | /// active and inactive voxels in the input grid. | ||
| 39 | /// | ||
| 40 | /// @return A shared pointer to a new sdf / level set grid of type @c float | ||
| 41 | /// | ||
| 42 | /// @param grid Input grid of arbitrary type whose active voxels are used | ||
| 43 | /// in constructing the level set. | ||
| 44 | /// @param halfWidth Half the width of the narrow band in voxel units. | ||
| 45 | /// @param closingSteps Number of morphological closing steps used to fill gaps | ||
| 46 | /// in the active voxel region. | ||
| 47 | /// @param dilation Number of voxels to expand the active voxel region. | ||
| 48 | /// @param smoothingSteps Number of smoothing interations. | ||
| 49 | template<typename GridT> | ||
| 50 | typename GridT::template ValueConverter<float>::Type::Ptr | ||
| 51 | topologyToLevelSet(const GridT& grid, | ||
| 52 | int halfWidth = 3, | ||
| 53 | int closingSteps = 1, | ||
| 54 | int dilation = 0, | ||
| 55 | int smoothingSteps = 0); | ||
| 56 | |||
| 57 | |||
| 58 | /// @brief Compute the narrow-band signed distance to the interface between | ||
| 59 | /// active and inactive voxels in the input grid. | ||
| 60 | /// | ||
| 61 | /// @return A shared pointer to a new sdf / level set grid of type @c float | ||
| 62 | /// | ||
| 63 | /// @param grid Input grid of arbitrary type whose active voxels are used | ||
| 64 | /// in constructing the level set. | ||
| 65 | /// @param halfWidth Half the width of the narrow band in voxel units. | ||
| 66 | /// @param closingSteps Number of morphological closing steps used to fill gaps | ||
| 67 | /// in the active voxel region. | ||
| 68 | /// @param dilation Number of voxels to expand the active voxel region. | ||
| 69 | /// @param smoothingSteps Number of smoothing interations. | ||
| 70 | /// @param interrupt Optional object adhering to the util::NullInterrupter interface. | ||
| 71 | template<typename GridT, typename InterrupterT> | ||
| 72 | typename GridT::template ValueConverter<float>::Type::Ptr | ||
| 73 | topologyToLevelSet(const GridT& grid, | ||
| 74 | int halfWidth = 3, | ||
| 75 | int closingSteps = 1, | ||
| 76 | int dilation = 0, | ||
| 77 | int smoothingSteps = 0, | ||
| 78 | InterrupterT* interrupt = nullptr); | ||
| 79 | |||
| 80 | |||
| 81 | //////////////////////////////////////// | ||
| 82 | |||
| 83 | /// @cond OPENVDB_DOCS_INTERNAL | ||
| 84 | |||
| 85 | namespace ttls_internal { | ||
| 86 | |||
| 87 | |||
| 88 | template<typename TreeType> | ||
| 89 | struct OffsetAndMinComp | ||
| 90 | { | ||
| 91 | using LeafNodeType = typename TreeType::LeafNodeType; | ||
| 92 | using ValueType = typename TreeType::ValueType; | ||
| 93 | |||
| 94 | ✗ | OffsetAndMinComp(std::vector<LeafNodeType*>& lhsNodes, | |
| 95 | const TreeType& rhsTree, | ||
| 96 | ValueType offset) | ||
| 97 | : mLhsNodes(lhsNodes) | ||
| 98 | , mRhsTree(rhsTree) | ||
| 99 | ✗ | , mOffset(offset) {} | |
| 100 | |||
| 101 | ✗ | void operator()(const tbb::blocked_range<size_t>& range) const | |
| 102 | { | ||
| 103 | using Iterator = typename LeafNodeType::ValueOnIter; | ||
| 104 | |||
| 105 | ✗ | tree::ValueAccessor<const TreeType> rhsAcc(mRhsTree); | |
| 106 | ✗ | const ValueType offset = mOffset; | |
| 107 | |||
| 108 | ✗ | for (size_t n = range.begin(), N = range.end(); n < N; ++n) { | |
| 109 | |||
| 110 | ✗ | LeafNodeType& lhsNode = *mLhsNodes[n]; | |
| 111 | const LeafNodeType* rhsNodePt = rhsAcc.probeConstLeaf(lhsNode.origin()); | ||
| 112 | ✗ | if (!rhsNodePt) continue; | |
| 113 | |||
| 114 | ✗ | auto* const data = lhsNode.buffer().data(); | |
| 115 | ✗ | for (Iterator it = lhsNode.beginValueOn(); it; ++it) { | |
| 116 | ✗ | ValueType& val = data[it.pos()]; | |
| 117 | ✗ | val = std::min(val, offset + rhsNodePt->getValue(it.pos())); | |
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | private: | ||
| 123 | std::vector<LeafNodeType*>& mLhsNodes; | ||
| 124 | const TreeType& mRhsTree; | ||
| 125 | const ValueType mOffset; | ||
| 126 | }; // struct OffsetAndMinComp | ||
| 127 | |||
| 128 | |||
| 129 | template<typename GridType, typename InterrupterType> | ||
| 130 | inline void | ||
| 131 | 2 | normalizeLevelSet(GridType& grid, const int halfWidthInVoxels, InterrupterType* interrupt = nullptr) | |
| 132 | { | ||
| 133 | LevelSetFilter<GridType, GridType, InterrupterType> filter(grid, interrupt); | ||
| 134 | filter.setSpatialScheme(math::FIRST_BIAS); | ||
| 135 | filter.setNormCount(halfWidthInVoxels); | ||
| 136 | filter.normalize(); | ||
| 137 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | filter.prune(); |
| 138 | 2 | } | |
| 139 | |||
| 140 | |||
| 141 | template<typename GridType, typename InterrupterType> | ||
| 142 | inline void | ||
| 143 | ✗ | smoothLevelSet(GridType& grid, int iterations, int halfBandWidthInVoxels, | |
| 144 | InterrupterType* interrupt = nullptr) | ||
| 145 | { | ||
| 146 | using ValueType = typename GridType::ValueType; | ||
| 147 | using TreeType = typename GridType::TreeType; | ||
| 148 | using LeafNodeType = typename TreeType::LeafNodeType; | ||
| 149 | |||
| 150 | ✗ | GridType filterGrid(grid); | |
| 151 | |||
| 152 | LevelSetFilter<GridType, GridType, InterrupterType> filter(filterGrid, interrupt); | ||
| 153 | filter.setSpatialScheme(math::FIRST_BIAS); | ||
| 154 | |||
| 155 | ✗ | for (int n = 0; n < iterations; ++n) { | |
| 156 | ✗ | if (interrupt && interrupt->wasInterrupted()) break; | |
| 157 | ✗ | filter.mean(1); | |
| 158 | } | ||
| 159 | |||
| 160 | std::vector<LeafNodeType*> nodes; | ||
| 161 | grid.tree().getNodes(nodes); | ||
| 162 | |||
| 163 | ✗ | const ValueType offset = ValueType(double(0.5) * grid.transform().voxelSize()[0]); | |
| 164 | |||
| 165 | ✗ | tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()), | |
| 166 | OffsetAndMinComp<TreeType>(nodes, filterGrid.tree(), -offset)); | ||
| 167 | |||
| 168 | // Clean up any damanage that was done by the min operation | ||
| 169 | ✗ | normalizeLevelSet(grid, halfBandWidthInVoxels, interrupt); | |
| 170 | } | ||
| 171 | |||
| 172 | } // namespace ttls_internal | ||
| 173 | |||
| 174 | /// @endcond | ||
| 175 | |||
| 176 | template<typename GridT, typename InterrupterT> | ||
| 177 | typename GridT::template ValueConverter<float>::Type::Ptr | ||
| 178 | 2 | topologyToLevelSet(const GridT& grid, int halfWidth, int closingSteps, int dilation, | |
| 179 | int smoothingSteps, InterrupterT* interrupt) | ||
| 180 | { | ||
| 181 | using MaskTreeT = typename GridT::TreeType::template ValueConverter<ValueMask>::Type; | ||
| 182 | using FloatTreeT = typename GridT::TreeType::template ValueConverter<float>::Type; | ||
| 183 | using FloatGridT = Grid<FloatTreeT>; | ||
| 184 | |||
| 185 | // Check inputs | ||
| 186 | |||
| 187 | 2 | halfWidth = std::max(halfWidth, 1); | |
| 188 | 2 | closingSteps = std::max(closingSteps, 0); | |
| 189 | 2 | dilation = std::max(dilation, 0); | |
| 190 | |||
| 191 | 2 | if (!grid.hasUniformVoxels()) { | |
| 192 | ✗ | OPENVDB_THROW(ValueError, "Non-uniform voxels are not supported!"); | |
| 193 | } | ||
| 194 | |||
| 195 | // Copy the topology into a MaskGrid. | ||
| 196 | 4 | MaskTreeT maskTree(grid.tree(), false/*background*/, openvdb::TopologyCopy()); | |
| 197 | |||
| 198 | // Morphological closing operation. | ||
| 199 | 2 | tools::dilateActiveValues(maskTree, closingSteps + dilation, tools::NN_FACE, tools::PRESERVE_TILES); | |
| 200 | 2 | tools::erodeActiveValues(maskTree, /*iterations=*/closingSteps, tools::NN_FACE, tools::PRESERVE_TILES); | |
| 201 | 2 | tools::pruneInactive(maskTree); | |
| 202 | |||
| 203 | // Generate a volume with an implicit zero crossing at the boundary | ||
| 204 | // between active and inactive values in the input grid. | ||
| 205 | 2 | const float background = float(grid.voxelSize()[0]) * float(halfWidth); | |
| 206 | 2 | typename FloatTreeT::Ptr lsTree( | |
| 207 | 2 | new FloatTreeT(maskTree, /*out=*/background, /*in=*/-background, openvdb::TopologyCopy())); | |
| 208 | |||
| 209 | tbb::task_group pool; | ||
| 210 | 8 | pool.run([&]() { | |
| 211 | 2 | tools::erodeActiveValues(maskTree, /*iterations=*/halfWidth, tools::NN_FACE, tools::PRESERVE_TILES); | |
| 212 | 2 | tools::pruneInactive(maskTree); | |
| 213 | }); | ||
| 214 | 4 | pool.run([&]() { | |
| 215 | 2 | tools::dilateActiveValues(*lsTree, /*iterations=*/halfWidth, tools::NN_FACE, tools::PRESERVE_TILES); | |
| 216 | }); | ||
| 217 | 2 | pool.wait();// wait for both tasks to complete | |
| 218 | |||
| 219 | lsTree->topologyDifference(maskTree); | ||
| 220 | 2 | maskTree.clear(); | |
| 221 | |||
| 222 | lsTree->voxelizeActiveTiles(); // has to come before ::pruneLevelSet | ||
| 223 | 2 | tools::pruneLevelSet(*lsTree, /*threading=*/true); | |
| 224 | |||
| 225 | // Create a level set grid from the tree | ||
| 226 | 4 | typename FloatGridT::Ptr lsGrid = FloatGridT::create(lsTree); | |
| 227 | 4 | lsGrid->setTransform(grid.transform().copy()); | |
| 228 | 2 | lsGrid->setGridClass(openvdb::GRID_LEVEL_SET); | |
| 229 | |||
| 230 | // Use a PDE based scheme to propagate distance values from the | ||
| 231 | // implicit zero crossing. | ||
| 232 | 2 | ttls_internal::normalizeLevelSet(*lsGrid, 3*halfWidth, interrupt); | |
| 233 | |||
| 234 | // Additional filtering | ||
| 235 | 2 | if (smoothingSteps > 0) { | |
| 236 | ✗ | ttls_internal::smoothLevelSet(*lsGrid, smoothingSteps, halfWidth, interrupt); | |
| 237 | } | ||
| 238 | |||
| 239 | 2 | return lsGrid; | |
| 240 | } | ||
| 241 | |||
| 242 | |||
| 243 | template<typename GridT> | ||
| 244 | typename GridT::template ValueConverter<float>::Type::Ptr | ||
| 245 | 2 | topologyToLevelSet(const GridT& grid, int halfWidth, int closingSteps, int dilation, int smoothingSteps) | |
| 246 | { | ||
| 247 | 2 | util::NullInterrupter interrupt; | |
| 248 | 2 | return topologyToLevelSet(grid, halfWidth, closingSteps, dilation, smoothingSteps, &interrupt); | |
| 249 | } | ||
| 250 | |||
| 251 | |||
| 252 | //////////////////////////////////////// | ||
| 253 | |||
| 254 | |||
| 255 | // Explicit Template Instantiation | ||
| 256 | |||
| 257 | #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
| 258 | |||
| 259 | #ifdef OPENVDB_INSTANTIATE_TOPOLOGYTOLEVELSET | ||
| 260 | #include <openvdb/util/ExplicitInstantiation.h> | ||
| 261 | #endif | ||
| 262 | |||
| 263 | #define _FUNCTION(TreeT) \ | ||
| 264 | Grid<TreeT>::ValueConverter<float>::Type::Ptr topologyToLevelSet(const Grid<TreeT>&, int, int, int, int, \ | ||
| 265 | util::NullInterrupter*) | ||
| 266 | OPENVDB_ALL_TREE_INSTANTIATE(_FUNCTION) | ||
| 267 | #undef _FUNCTION | ||
| 268 | |||
| 269 | #endif // OPENVDB_USE_EXPLICIT_INSTANTIATION | ||
| 270 | |||
| 271 | |||
| 272 | } // namespace tools | ||
| 273 | } // namespace OPENVDB_VERSION_NAME | ||
| 274 | } // namespace openvdb | ||
| 275 | |||
| 276 | #endif // OPENVDB_TOOLS_TOPOLOGY_TO_LEVELSET_HAS_BEEN_INCLUDED | ||
| 277 | |||
| 278 |