Skip to content

Commit 1b71a43

Browse files
feat: respect connected components for invalidation + full voxel graph support (#86)
* wip: use dijkstra type method for invalidation ball This method will stay within the given component and may even be faster for certain situations (slower for others). * feat: use straight line distance * fix: expand coverage by voxel graph, use new ccl-26 * refactor: clean up old invalidation code * refactor: remove obsolete test code * fix: bump connected components version * ci: add py312 to testing * feat: expose connectivity parameter in roll_invalidation_ball_inside_component
1 parent bd3b131 commit 1b71a43

File tree

10 files changed

+3707
-87
lines changed

10 files changed

+3707
-87
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: ["3.8", "3.9", "3.10", "3.11"]
18+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
1919

2020
steps:
2121
- uses: actions/checkout@v2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
/*
2+
* This file is part of Kimimaro.
3+
*
4+
* Kimimaro is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Kimimaro is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with Kimimaro. If not, see <https://www.gnu.org/licenses/>.
16+
*
17+
*
18+
* This algorithm is derived from dijkstra3d:
19+
* https://github.com/seung-lab/dijkstra3d
20+
*
21+
* Author: William Silversmith
22+
* Affiliation: Seung Lab, Princeton University
23+
* Date: May 2024
24+
*/
25+
26+
#ifndef DIJKSTRA_INVALIDATION_HPP
27+
#define DIJKSTRA_INVALIDATION_HPP
28+
29+
#include <algorithm>
30+
#include <cmath>
31+
#include <cstdio>
32+
#include <cstdint>
33+
#include <functional>
34+
#include <memory>
35+
#include <queue>
36+
#include <vector>
37+
38+
#include "./libdivide.h"
39+
40+
#define NHOOD_SIZE 26
41+
42+
namespace dijkstra_invalidation {
43+
44+
// helper function to compute 2D anisotropy ("_s" = "square")
45+
inline float _s(const float wa, const float wb) {
46+
return std::sqrt(wa * wa + wb * wb);
47+
}
48+
49+
// helper function to compute 3D anisotropy ("_c" = "cube")
50+
inline float _c(const float wa, const float wb, const float wc) {
51+
return std::sqrt(wa * wa + wb * wb + wc * wc);
52+
}
53+
54+
void connectivity_check(int connectivity) {
55+
if (connectivity != 6 && connectivity != 18 && connectivity != 26) {
56+
throw std::runtime_error("Only 6, 18, and 26 connectivities are supported.");
57+
}
58+
}
59+
60+
void compute_neighborhood_helper_6(
61+
int *neighborhood,
62+
const int x, const int y, const int z,
63+
const uint64_t sx, const uint64_t sy, const uint64_t sz
64+
) {
65+
66+
const int sxy = sx * sy;
67+
68+
// 6-hood
69+
neighborhood[0] = -1 * (x > 0); // -x
70+
neighborhood[1] = (x < (static_cast<int>(sx) - 1)); // +x
71+
neighborhood[2] = -static_cast<int>(sx) * (y > 0); // -y
72+
neighborhood[3] = static_cast<int>(sx) * (y < static_cast<int>(sy) - 1); // +y
73+
neighborhood[4] = -sxy * static_cast<int>(z > 0); // -z
74+
neighborhood[5] = sxy * (z < static_cast<int>(sz) - 1); // +z
75+
}
76+
77+
void compute_neighborhood_helper_18(
78+
int *neighborhood,
79+
const int x, const int y, const int z,
80+
const uint64_t sx, const uint64_t sy, const uint64_t sz
81+
) {
82+
// 6-hood
83+
compute_neighborhood_helper_6(neighborhood, x,y,z, sx,sy,sz);
84+
85+
// 18-hood
86+
87+
// xy diagonals
88+
neighborhood[6] = (neighborhood[0] + neighborhood[2]) * (neighborhood[0] && neighborhood[2]); // up-left
89+
neighborhood[7] = (neighborhood[0] + neighborhood[3]) * (neighborhood[0] && neighborhood[3]); // up-right
90+
neighborhood[8] = (neighborhood[1] + neighborhood[2]) * (neighborhood[1] && neighborhood[2]); // down-left
91+
neighborhood[9] = (neighborhood[1] + neighborhood[3]) * (neighborhood[1] && neighborhood[3]); // down-right
92+
93+
// yz diagonals
94+
neighborhood[10] = (neighborhood[2] + neighborhood[4]) * (neighborhood[2] && neighborhood[4]); // up-left
95+
neighborhood[11] = (neighborhood[2] + neighborhood[5]) * (neighborhood[2] && neighborhood[5]); // up-right
96+
neighborhood[12] = (neighborhood[3] + neighborhood[4]) * (neighborhood[3] && neighborhood[4]); // down-left
97+
neighborhood[13] = (neighborhood[3] + neighborhood[5]) * (neighborhood[3] && neighborhood[5]); // down-right
98+
99+
// xz diagonals
100+
neighborhood[14] = (neighborhood[0] + neighborhood[4]) * (neighborhood[0] && neighborhood[4]); // up-left
101+
neighborhood[15] = (neighborhood[0] + neighborhood[5]) * (neighborhood[0] && neighborhood[5]); // up-right
102+
neighborhood[16] = (neighborhood[1] + neighborhood[4]) * (neighborhood[1] && neighborhood[4]); // down-left
103+
neighborhood[17] = (neighborhood[1] + neighborhood[5]) * (neighborhood[1] && neighborhood[5]); // down-right
104+
}
105+
106+
void compute_neighborhood_helper_26(
107+
int *neighborhood,
108+
const int x, const int y, const int z,
109+
const uint64_t sx, const uint64_t sy, const uint64_t sz
110+
) {
111+
compute_neighborhood_helper_18(neighborhood, x,y,z, sx,sy,sz);
112+
113+
// 26-hood
114+
115+
// Now the eight corners of the cube
116+
neighborhood[18] = (neighborhood[0] + neighborhood[2] + neighborhood[4]) * (neighborhood[2] && neighborhood[4]);
117+
neighborhood[19] = (neighborhood[1] + neighborhood[2] + neighborhood[4]) * (neighborhood[2] && neighborhood[4]);
118+
neighborhood[20] = (neighborhood[0] + neighborhood[3] + neighborhood[4]) * (neighborhood[3] && neighborhood[4]);
119+
neighborhood[21] = (neighborhood[0] + neighborhood[2] + neighborhood[5]) * (neighborhood[2] && neighborhood[5]);
120+
neighborhood[22] = (neighborhood[1] + neighborhood[3] + neighborhood[4]) * (neighborhood[3] && neighborhood[4]);
121+
neighborhood[23] = (neighborhood[1] + neighborhood[2] + neighborhood[5]) * (neighborhood[2] && neighborhood[5]);
122+
neighborhood[24] = (neighborhood[0] + neighborhood[3] + neighborhood[5]) * (neighborhood[3] && neighborhood[5]);
123+
neighborhood[25] = (neighborhood[1] + neighborhood[3] + neighborhood[5]) * (neighborhood[3] && neighborhood[5]);
124+
}
125+
126+
inline void compute_neighborhood(
127+
int *neighborhood,
128+
const int x, const int y, const int z,
129+
const uint64_t sx, const uint64_t sy, const uint64_t sz,
130+
const int connectivity = 26, const uint32_t* voxel_connectivity_graph = NULL) {
131+
132+
if (connectivity == 26) {
133+
compute_neighborhood_helper_26(neighborhood, x, y, z, sx, sy, sz);
134+
}
135+
else if (connectivity == 18) {
136+
compute_neighborhood_helper_18(neighborhood, x, y, z, sx, sy, sz);
137+
}
138+
else {
139+
compute_neighborhood_helper_6(neighborhood, x, y, z, sx, sy, sz);
140+
}
141+
142+
if (voxel_connectivity_graph == NULL) {
143+
return;
144+
}
145+
146+
uint64_t loc = x + sx * (y + sy * z);
147+
uint32_t graph = voxel_connectivity_graph[loc];
148+
149+
// graph conventions are defined here:
150+
// https://github.com/seung-lab/connected-components-3d/blob/3.2.0/cc3d_graphs.hpp#L73-L92
151+
152+
// 6-hood
153+
neighborhood[0] *= ((graph & 0b000010) > 0); // -x
154+
neighborhood[1] *= ((graph & 0b000001) > 0); // +x
155+
neighborhood[2] *= ((graph & 0b001000) > 0); // -y
156+
neighborhood[3] *= ((graph & 0b000100) > 0); // +y
157+
neighborhood[4] *= ((graph & 0b100000) > 0); // -z
158+
neighborhood[5] *= ((graph & 0b010000) > 0); // +z
159+
160+
// 18-hood
161+
162+
// xy diagonals
163+
neighborhood[6] *= ((graph & 0b1000000000) > 0); // up-left -x,-y
164+
neighborhood[7] *= ((graph & 0b0010000000) > 0); // up-right -x,+y
165+
neighborhood[8] *= ((graph & 0b0100000000) > 0); // down-left +x,-y
166+
neighborhood[9] *= ((graph & 0b0001000000) > 0); // down-right +x,+y
167+
168+
// yz diagonals
169+
neighborhood[10] *= ((graph & 0b100000000000000000) > 0); // up-left -y,-z
170+
neighborhood[11] *= ((graph & 0b000010000000000000) > 0); // up-right -y,+z
171+
neighborhood[12] *= ((graph & 0b010000000000000000) > 0); // down-left +y,-z
172+
neighborhood[13] *= ((graph & 0b000001000000000000) > 0); // down-right +y,+z
173+
174+
// xz diagonals
175+
neighborhood[14] *= ((graph & 0b001000000000000000) > 0); // up-left, -x,-z
176+
neighborhood[15] *= ((graph & 0b000000100000000000) > 0); // up-right, -x,+z
177+
neighborhood[16] *= ((graph & 0b000100000000000000) > 0); // down-left +x,-z
178+
neighborhood[17] *= ((graph & 0b000000010000000000) > 0); // down-right +x,+z
179+
180+
// 26-hood
181+
182+
// Now the eight corners of the cube
183+
neighborhood[18] *= ((graph & 0b10000000000000000000000000) > 0); // -x,-y,-z
184+
neighborhood[19] *= ((graph & 0b01000000000000000000000000) > 0); // +x,-y,-z
185+
neighborhood[20] *= ((graph & 0b00100000000000000000000000) > 0); // -x,+y,-z
186+
neighborhood[21] *= ((graph & 0b00001000000000000000000000) > 0); // -x,-y,+z
187+
neighborhood[22] *= ((graph & 0b00010000000000000000000000) > 0); // +x,+y,-z
188+
neighborhood[23] *= ((graph & 0b00000100000000000000000000) > 0); // +x,-y,+z
189+
neighborhood[24] *= ((graph & 0b00000010000000000000000000) > 0); // -x,+y,+z
190+
neighborhood[25] *= ((graph & 0b00000001000000000000000000) > 0); // +x,+y,+z
191+
}
192+
193+
#define DIJKSTRA_3D_PREFETCH_26WAY(field, loc) \
194+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) - 1]), 0, 1); \
195+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) + sxy - 1]), 0, 1); \
196+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) - sxy - 1]), 0, 1); \
197+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) + sxy + sx - 1]), 0, 1); \
198+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) + sxy - sx - 1]), 0, 1); \
199+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) - sxy + sx - 1]), 0, 1); \
200+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) - sxy - sx - 1]), 0, 1); \
201+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) + sx - 1]), 0, 1); \
202+
HEDLEYX_PREFETCH(reinterpret_cast<char*>(&field[(loc) - sx - 1]), 0, 1);
203+
204+
class HeapDistanceNode {
205+
public:
206+
float dist;
207+
uint64_t original_loc;
208+
uint64_t value;
209+
float max_dist;
210+
211+
HeapDistanceNode() {
212+
dist = 0;
213+
value = 0;
214+
original_loc = 0;
215+
max_dist = 0;
216+
}
217+
218+
HeapDistanceNode (float d, uint64_t o_loc, uint64_t val, float mx_dist) {
219+
dist = d;
220+
value = val;
221+
original_loc = o_loc;
222+
max_dist = mx_dist;
223+
}
224+
225+
HeapDistanceNode (const HeapDistanceNode &h) {
226+
dist = h.dist;
227+
value = h.value;
228+
max_dist = h.max_dist;
229+
original_loc = h.original_loc;
230+
}
231+
};
232+
233+
struct HeapDistanceNodeCompare {
234+
bool operator()(const HeapDistanceNode &t1, const HeapDistanceNode &t2) const {
235+
return t1.dist >= t2.dist;
236+
}
237+
};
238+
239+
int64_t _roll_invalidation_ball(
240+
uint8_t* field, // really a boolean field
241+
const uint64_t sx, const uint64_t sy, const uint64_t sz,
242+
const float wx, const float wy, const float wz,
243+
const std::vector<uint64_t> &sources,
244+
const std::vector<float> &max_distances,
245+
const int connectivity = 26,
246+
const uint32_t* voxel_connectivity_graph = NULL
247+
) {
248+
249+
const uint64_t sxy = sx * sy;
250+
251+
const libdivide::divider<uint64_t> fast_sx(sx);
252+
const libdivide::divider<uint64_t> fast_sxy(sxy);
253+
254+
const bool power_of_two = !((sx & (sx - 1)) || (sy & (sy - 1)));
255+
const int xshift = std::log2(sx); // must use log2 here, not lg/lg2 to avoid fp errors
256+
const int yshift = std::log2(sy);
257+
258+
connectivity_check(connectivity);
259+
260+
int neighborhood[NHOOD_SIZE] = {};
261+
262+
std::priority_queue<
263+
HeapDistanceNode, std::vector<HeapDistanceNode>, HeapDistanceNodeCompare
264+
> queue;
265+
266+
for (uint64_t i = 0; i < sources.size(); i++) {
267+
queue.emplace(0.0, sources[i], sources[i], max_distances[i]);
268+
}
269+
270+
uint64_t loc;
271+
uint64_t neighboridx;
272+
273+
int64_t x, y, z;
274+
int64_t orig_x, orig_y, orig_z;
275+
276+
int64_t invalidated = 0;
277+
278+
auto xyzfn = [=](uint64_t l, int64_t& x, int64_t& y, int64_t& z) {
279+
if (power_of_two) {
280+
z = l >> (xshift + yshift);
281+
y = (l - (z << (xshift + yshift))) >> xshift;
282+
x = l - ((y + (z << yshift)) << xshift);
283+
}
284+
else {
285+
z = l / fast_sxy;
286+
y = (l - (z * sxy)) / fast_sx;
287+
x = l - sx * (y + z * sy);
288+
}
289+
};
290+
291+
while (!queue.empty()) {
292+
const float max_dist = queue.top().max_dist;
293+
const uint64_t original_loc = queue.top().original_loc;
294+
loc = queue.top().value;
295+
queue.pop();
296+
297+
if (!field[loc]) {
298+
continue;
299+
}
300+
301+
field[loc] = 0;
302+
invalidated++;
303+
304+
xyzfn(loc, x, y, z);
305+
xyzfn(original_loc, orig_x, orig_y, orig_z);
306+
compute_neighborhood(neighborhood, x, y, z, sx, sy, sz, connectivity, voxel_connectivity_graph);
307+
308+
for (int i = 0; i < connectivity; i++) {
309+
if (neighborhood[i] == 0) {
310+
continue;
311+
}
312+
313+
neighboridx = loc + neighborhood[i];
314+
if (field[neighboridx] == 0) {
315+
continue;
316+
}
317+
318+
xyzfn(neighboridx, x, y, z);
319+
float new_dist = _c(
320+
wx * static_cast<float>(x - orig_x),
321+
wy * static_cast<float>(y - orig_y),
322+
wz * static_cast<float>(z - orig_z)
323+
);
324+
325+
if (new_dist < max_dist) {
326+
queue.emplace(new_dist, original_loc, neighboridx, max_dist);
327+
}
328+
}
329+
}
330+
331+
return invalidated;
332+
}
333+
334+
};
335+
336+
#undef NHOOD_SIZE
337+
#undef DIJKSTRA_3D_PREFETCH_26WAY
338+
339+
#endif

0 commit comments

Comments
 (0)