| 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 |