DGtal 2.1.0
Loading...
Searching...
No Matches
DigitalSetByOctree.ih
1/**
2 * This program is free software: you can redistribute it and/or modify
3 * it under the terms of the GNU Lesser General Public License as
4 * published by the Free Software Foundation, either version 3 of the
5 * License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *
15 **/
16
17/**
18 * @file DigitalSetByOctree.ih
19 * @author Bastien DOIGNIES
20 * LIRIS
21 *
22 * @date 2025/09/05
23 *
24 * Header file for module DigitalSetByOctree.cpp
25 *
26 * This file is part of the DGtal library.
27 */
28namespace DGtal
29{
30 template<class Space>
31 void DigitalSetByOctree<Space>::OctreeIterator::findNextLeaf()
32 {
33 while (myMemory.size() != 0)
34 {
35 TraversalMemory& m = myMemory.back();
36 // Find next unexplored child. Invalid index means no child was explored yet
37 CellIndex k = (m.currentChildIdx == INVALID_IDX) ? 0 : (m.currentChildIdx + 1);
38 for (; k < CELL_COUNT; ++k)
39 {
40 if (myContainer->myNodes[m.lvl][m.idx].children[k] != INVALID_IDX)
41 {
42 // Update explored child
43 m.currentChildIdx = k;
44 break;
45 }
46 }
47
48 if (k < CELL_COUNT)
49 {
50 // Child found, go down
51 if (myMemory.back().lvl == myContainer->myNodes.size() - 1) break;
52
53 TraversalMemory newMemory;
54 newMemory.lvl = m.lvl + 1;
55 newMemory.domain = splitDomain(m.domain, SIDES_FROM_INDEX[k].data());
56 newMemory.idx = myContainer->myNodes[m.lvl][m.idx].children[k];
57 newMemory.currentChildIdx = INVALID_IDX;
58 myMemory.push_back(std::move(newMemory));
59 }
60 else
61 { // No more child unexplored, go back to parent
62 myMemory.pop_back();
63 }
64 }
65 if (myMemory.size() != 0)
66 {
67 // For leaves, find first non 0 index
68 auto& mem = myMemory.back();
69 if (mem.currentChildIdx == INVALID_IDX)
70 {
71 for (CellIndex k = 0; k < CELL_COUNT; ++k)
72 {
73 if (myContainer->myNodes[mem.lvl][mem.idx].children[k] != 0)
74 {
75 mem.currentChildIdx = k;
76 break;
77 }
78 }
79 }
80 }
81 }
82
83 template<class Space>
84 DigitalSetByOctree<Space>::DigitalSetByOctree(const Domain& dom)
85 {
86 const auto lb = dom.lowerBound();
87 const auto ub = dom.upperBound();
88 auto size = ub - lb;
89
90 // Enforce cubical domain with side that are powers of 2
91 CellIndex newSize = 1;
92 for (DimIndex d = 0; d < D; ++d)
93 {
94 if (!(size[d] & (size[d] - 1)))
95 {
96 newSize = (newSize > size[d]) ? newSize : size[d];
97 }
98 else
99 {
100 CellIndex s = 1 << (static_cast<CellIndex>(std::ceil(std::log2(size[d]))));
101 newSize = (newSize > s) ? newSize : s;
102 }
103 }
104
105 for (DimIndex d = 0; d < D; ++d)
106 size[d] = newSize;
107
108 size_t lvl = static_cast<size_t>(std::ceil(std::log2(newSize)));
109 myNodes.resize(lvl);
110 myNodes[0].push_back(Node());
111 myDomain = CowPtr(new Domain(lb, lb + size));
112 myAdmissibleDomain = CowPtr(new Domain(lb, lb + size - 1));
113 mySize = 0;
114 }
115
116 template<class Space>
117 HyperRectDomain<Space> DigitalSetByOctree<Space>::splitDomain(const Domain& domain, const DimIndex* sides)
118 {
119 auto lb = domain.lowerBound();
120 const auto ub = domain.upperBound();
121 const auto size = (ub - lb) / 2;
122 for (DimIndex d = 0; d < D; ++d)
123 lb[d] += sides[d] * size[d];
124
125 return Domain(lb, lb + size);
126 }
127
128 template<class Space>
129 void DigitalSetByOctree<Space>::insert(const Point& p)
130 {
131 if (!myAdmissibleDomain->isInside(p)) return;
132
133 if (myState == State::OCTREE)
134 {
135 Domain domain = *myDomain;
136 CellIndex nodeIdx = 0;
137
138 for (size_t i = 0; i < myNodes.size() - 1; ++i)
139 {
140 const auto lb = domain.lowerBound();
141 const auto ub = domain.upperBound();
142 const Point middle = domain.lowerBound() + (ub - lb) / 2;
143 CellIndex childIdx = 0;
144 DimIndex sides[D]{};
145 for (DimIndex d = 0; d < D; ++d)
146 {
147 sides[d] = (p[d] >= middle[d]);
148 childIdx += sides[d] * (1 << d);
149 }
150 domain = splitDomain(domain, sides);
151 if (myNodes[i][nodeIdx].children[childIdx] == INVALID_IDX)
152 {
153 myNodes[i + 1].push_back(Node());
154 myNodes[i][nodeIdx].children[childIdx] = myNodes[i + 1].size() - 1;
155 }
156 nodeIdx = myNodes[i][nodeIdx].children[childIdx];
157 }
158 // This octree does not create nodes for leaves.
159 // We treat them separately and use children field as
160 // a binary array to indicate whether a voxel is stored or not
161 const auto lb = domain.lowerBound();
162 const auto ub = domain.upperBound();
163 const Point middle = domain.lowerBound() + (ub - lb) / 2;
164 CellIndex childIdx = 0;
165 for (DimIndex d = 0; d < D; ++d) {
166 childIdx += (p[d] >= middle[d]) * (1 << (D - d - 1));
167 }
168 if (myNodes[myNodes.size() - 1][nodeIdx].children[childIdx] != 1) {
169 myNodes[myNodes.size() - 1][nodeIdx].children[childIdx] = 1;
170 mySize ++;
171 }
172 }
173 }
174
175 template<class Space>
176 typename DigitalSetByOctree<Space>::OctreeIterator
177 DigitalSetByOctree<Space>::find(const Point& p) const
178 {
179 if (!myAdmissibleDomain->isInside(p)) return end();
180
181 Domain domain = *myDomain;
182 std::vector<TraversalMemory> traversal;
183 CellIndex nodeIdx = 0;
184
185 TraversalMemory root;
186 root.lvl = 0;
187 root.idx = 0;
188 root.domain = domain;
189 traversal.push_back(root);
190
191 for (size_t i = 0; i < myNodes.size() - 1; ++i)
192 {
193 const auto lb = domain.lowerBound();
194 const auto ub = domain.upperBound();
195 const Point middle = domain.lowerBound() + (ub - lb) / 2;
196 CellIndex childIdx = 0;
197 DimIndex sides[D]{};
198 for (DimIndex d = 0; d < D; ++d)
199 {
200 sides[d] = (p[d] >= middle[d]);
201 childIdx += sides[d] * (1 << d);
202 }
203
204 if (myNodes[i][nodeIdx].children[childIdx] == INVALID_IDX)
205 return end();
206
207 domain = splitDomain(domain, sides);
208 nodeIdx = myNodes[i][nodeIdx].children[childIdx];
209 TraversalMemory memory;
210 memory.domain = domain;
211 memory.lvl = i + 1;
212 memory.idx = nodeIdx;
213 memory.currentChildIdx = INVALID_IDX;
214
215 traversal.back().currentChildIdx = childIdx;
216 traversal.push_back(memory);
217 }
218
219 const auto lb = domain.lowerBound();
220 const auto ub = domain.upperBound();
221 const Point middle = domain.lowerBound() + (ub - lb) / 2;
222 CellIndex childIdx = 0;
223 for (DimIndex d = 0; d < D; ++d)
224 {
225 childIdx += (p[d] >= middle[d]) * (1 << (D - d - 1));
226 }
227 traversal.back().currentChildIdx = childIdx;
228 if (myNodes[traversal.back().lvl][traversal.back().idx].children[childIdx] != INVALID_IDX)
229 return Iterator(this, traversal);
230 return end();
231 }
232
233 template<class Space>
234 size_t DigitalSetByOctree<Space>::erase(const Iterator& it)
235 {
236 if (it.myMemory.size() == 0) return 0;
237
238 if (myState == State::OCTREE)
239 {
240 std::vector<std::pair<CellIndex, CellIndex>> reindex;
241 bool childRemoved = true;
242 auto mem = it.myMemory;
243 while (!mem.empty())
244 {
245 const auto& tmem = mem.back();
246 if (childRemoved) // Can disconnect parent from the child
247 {
248 CellIndex child = myNodes[tmem.lvl][tmem.idx].children[tmem.currentChildIdx];
249 myNodes[tmem.lvl][tmem.idx].children[tmem.currentChildIdx] = INVALID_IDX;
250
251 // Reindex children because one was removed in previous layer
252 for (CellIndex i = 0; i < myNodes[tmem.lvl].size(); ++i)
253 {
254 auto& node = myNodes[tmem.lvl][i];
255 for (CellIndex j = 0; j < CELL_COUNT; ++j)
256 {
257 if (node.children[j] != INVALID_IDX && node.children[j] >= child)
258 node.children[j] --;
259 }
260 }
261 }
262
263 size_t count = 0;
264 for (CellIndex i = 0; i < CELL_COUNT; ++i)
265 count += (myNodes[tmem.lvl][tmem.idx].children[i] != INVALID_IDX);
266
267 if (count == 0)
268 {
269 myNodes[tmem.lvl].erase(myNodes[tmem.lvl].begin() + tmem.idx);
270 childRemoved = true;
271 }
272 else
273 {
274 childRemoved = false;
275 }
276 mem.pop_back();
277 }
278 mySize --;
279 return 1;
280 }
281 return 0;
282 }
283
284 template<class Space>
285 void DigitalSetByOctree<Space>::convertToDAG()
286 {
287 std::vector<size_t> correspondances;
288 for (CellIndex i = myNodes.size() - 1; i > 0; --i)
289 {
290 std::vector<Node> newNodes;
291 std::map<std::array<CellIndex, CELL_COUNT>, size_t> signatures;
292 std::vector<size_t> newCorrespondances(myNodes[i].size(), 0);
293
294 for (CellIndex j = 0; j < myNodes[i].size(); ++j) {
295 std::array<CellIndex, CELL_COUNT> signature = {};
296
297 if (correspondances.size() != 0) // Mostly leaves
298 {
299 for (CellIndex k = 0; k < CELL_COUNT; ++k) {
300 if (myNodes[i][j].children[k] != INVALID_IDX)
301 myNodes[i][j].children[k] = correspondances[myNodes[i][j].children[k]];
302
303 signature[k] = myNodes[i][j].children[k];
304 }
305 }
306 else
307 {
308 for (CellIndex k = 0; k < CELL_COUNT; ++k)
309 signature[k] = myNodes[i][j].children[k];
310 }
311
312 auto it = signatures.find(signature);
313 if (it != signatures.end())
314 {
315 newCorrespondances[j] = it->second;
316 }
317 else
318 {
319 newNodes.push_back(myNodes[i][j]);
320 signatures[signature] = newNodes.size() - 1;
321 newCorrespondances[j] = newNodes.size() - 1;
322 }
323 }
324 myNodes[i] = newNodes;
325 correspondances = newCorrespondances;
326 }
327 // New pointers for root lvl:
328 if (correspondances.size() != 0)
329 {
330 for (CellIndex k = 0; k < CELL_COUNT; ++k)
331 {
332 if (myNodes[0][0].children[k] != INVALID_IDX)
333 myNodes[0][0].children[k] = correspondances[myNodes[0][0].children[k]];
334 }
335 }
336
337 shrinkToFit();
338 myState = State::DAG;
339 }
340
341 template<class Space>
342 void DigitalSetByOctree<Space>::dumpOctree() const
343 {
344 for (size_t i = 0; i < myNodes.size(); ++i)
345 {
346 trace.info() << "===" << i << "===\n";
347 for (size_t j = 0; j < myNodes[i].size(); j++)
348 {
349 trace.info() << " -> " << j << "\n";
350 std::bitset<CELL_COUNT> bits;
351 for (size_t k = 0; k < CELL_COUNT; k++)
352 {
353 trace.info() << "\t" << k << ": ";
354 if (myNodes[i][j].children[k] == INVALID_IDX)
355 {
356 trace.info() << "N.A." << "\n";
357 }
358 else
359 {
360 trace.info() << myNodes[i][j].children[k] << "\n";
361 bits[k] = 1;
362 }
363 }
364 trace.info() << "\tSig: " << bits.to_ulong() << std::endl;
365 }
366 }
367 trace.info() << std::flush;
368 }
369
370 template<class Space>
371 template<class Func>
372 auto DigitalSetByOctree<Space>::computeFunction(
373 DigitalSetByOctree<Space>::OctreeIterator start,
374 DigitalSetByOctree<Space>::OctreeIterator end,
375 CellIndex range,
376 const Func& f
377 )
378 {
379 using ResultType = decltype(f({}, {}));
380 std::map<ComputationCacheKey, ResultType> cache;
381 std::vector<ResultType> rslt;
382 Point neighborSize;
383 for (DimIndex d = 0; d < D; ++d) neighborSize[d] = range;
384 for (; start != end; ++start)
385 {
386 // Shift so the position is at the minimum
387 const auto pos = *start;
388 const auto shiftedPos = pos - myDomain->lowerBound();
389 // Find parent depth that contains the whole neighborhood
390 // This is the maximum over dimension, of how many prefix
391 // bits of position, position - range, position + range
392 // have in common.
393 CellIndex lvl = 0;
394 for (DimIndex d = 0; d < D; ++d) {
395 const CellIndex coord = static_cast<CellIndex>(shiftedPos[d]);
396 const CellIndex coord_min = coord - range;
397 const CellIndex coord_max = coord + range;
398 // Parent in common, for this dimension, this can be higher than
399 // the depth of the tree for points on the border.
400 const CellIndex l = std::max(boost::core::bit_width(coord ^ coord_min),
401 boost::core::bit_width(coord ^ coord_max));
402 lvl = std::max(lvl, std::min(l, static_cast<CellIndex>(myNodes.size())));
403 }
404 const auto& parent = start.myMemory[start.myMemory.size() - lvl];
405 ComputationCacheKey key;
406 key.parentLvl = parent.lvl;
407 key.parentIdx = parent.idx;
408 key.code.resize(D * lvl);
409 for (CellIndex i = 0; i < lvl; ++i)
410 {
411 const size_t idx = start.myMemory.size() - i - 1;
412 for (DimIndex d = 0; d < D; ++d)
413 {
414 key.code[d + i * D] = SIDES_FROM_INDEX[start.myMemory[idx].currentChildIdx][d];
415 }
416 }
417 const auto it = cache.find(key);
418 if (it != cache.end())
419 {
420 // Cache hit, get value
421 rslt.push_back(it->second);
422 }
423 else
424 {
425 std::vector<Point> neighborhood;
426
427 // Gather neighborhood
428 const Domain neighborhoodDomain(pos - neighborSize, pos + neighborSize);
429 for (auto dIt = neighborhoodDomain.begin(); dIt != neighborhoodDomain.end(); ++dIt)
430 {
431 const auto ptIt = *dIt;
432 if (operator()(ptIt))
433 neighborhood.push_back(ptIt);
434 }
435 const auto feval = f(pos, neighborhood);
436 cache[key] = feval;
437 rslt.push_back(feval);
438 }
439 }
440 return rslt;
441 }
442}