Skip to content

Commit 219170e

Browse files
feat: adds feature map for euclidean distance field (#43)
* feat: adds feature map for euclidean distance field * refactor: rename to edf_with_feature_map * fix: color in source location in feature map * test: simple tests for feature map * docs: show how to use the feature map * docs: update docstring for euclidean_distance_field
1 parent 6ce9d65 commit 219170e

File tree

4 files changed

+328
-11
lines changed

4 files changed

+328
-11
lines changed

README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,16 @@ print(path.shape)
4545
# the anisotropic euclidean chamfer distance from the source to all labeled vertices.
4646
# Source can be a single point or a list of points. Accepts bool, (u)int8 dtypes.
4747
dist_field = dijkstra3d.euclidean_distance_field(field, source=(0,0,0), anisotropy=(4,4,40))
48+
49+
sources = [ (0,0,0), (10, 40, 232) ]
4850
dist_field = dijkstra3d.euclidean_distance_field(
49-
field, source=[ (0,0,0), (10, 40, 232) ], anisotropy=(4,4,40)
51+
field, source=sources, anisotropy=(4,4,40)
5052
)
53+
# You can return a map of source vertices to nearest voxels called
54+
# a feature map.
55+
dist_field, feature_map = dijkstra3d.euclidean_distance_field(
56+
field, source=sources, return_feature_map=True,
57+
)
5158

5259
# To make the EDF go faster add the free_space_radius parameter. It's only
5360
# safe to use if you know that some distance around the source point

automated_test.py

+46
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,52 @@ def test_euclidean_distance_field_2d(free_space_radius):
903903
])
904904
assert np.all(np.isclose(field, answer))
905905

906+
def test_euclidean_distance_field_2d_feature_map():
907+
values = np.ones((7, 7, 1), dtype=bool)
908+
field, max_loc, feature_map = dijkstra3d.euclidean_distance_field(values, [0,0,0], return_max_location=True, return_feature_map=True)
909+
910+
assert np.isclose(
911+
np.max(field),
912+
np.sqrt((values.shape[0] - 1) ** 2 + (values.shape[1] - 1) ** 2)
913+
)
914+
assert max_loc == (6,6,0)
915+
916+
assert np.all(np.unique(feature_map) == 1)
917+
918+
sources = [
919+
[0,0,0],
920+
[6,6,0]
921+
]
922+
field, max_loc, feature_map = dijkstra3d.euclidean_distance_field(values, sources, return_max_location=True, return_feature_map=True)
923+
assert tuple(np.unique(feature_map)) == (1,2)
924+
925+
sources = [
926+
[0,0,0],
927+
[6,6,0],
928+
[6,6,0]
929+
]
930+
field, max_loc, feature_map = dijkstra3d.euclidean_distance_field(values, sources, return_max_location=True, return_feature_map=True)
931+
assert tuple(np.unique(feature_map)) == (1,2)
932+
933+
sources = [
934+
[0,0,0],
935+
[6,6,0],
936+
[3,3,0],
937+
]
938+
field, max_loc, feature_map = dijkstra3d.euclidean_distance_field(values, sources, return_max_location=True, return_feature_map=True)
939+
assert tuple(np.unique(feature_map)) == (1,2,3)
940+
941+
result = np.array(
942+
[[[1, 1, 1, 2, 2, 2, 2],
943+
[1, 1, 2, 2, 2, 2, 2],
944+
[1, 2, 2, 2, 2, 2, 2],
945+
[2, 2, 2, 2, 2, 2, 3],
946+
[2, 2, 2, 2, 2, 3, 3],
947+
[2, 2, 2, 2, 3, 3, 3],
948+
[2, 2, 2, 3, 3, 3, 3],]]).T
949+
950+
assert np.all(feature_map == result)
951+
906952
@pytest.mark.parametrize('point', (np.random.randint(0,256, size=(3,)),))
907953
def test_euclidean_distance_field_3d_free_space_eqn(point):
908954
point = tuple(point)

dijkstra3d.hpp

+178
Original file line numberDiff line numberDiff line change
@@ -1835,6 +1835,184 @@ float* euclidean_distance_field3d(
18351835
);
18361836
}
18371837

1838+
1839+
class HeapFeatureNode {
1840+
public:
1841+
float key;
1842+
uint64_t value;
1843+
uint64_t source;
1844+
1845+
HeapFeatureNode() {
1846+
key = 0;
1847+
value = 0;
1848+
}
1849+
1850+
HeapFeatureNode (float k, uint64_t val, uint64_t src) {
1851+
key = k;
1852+
value = val;
1853+
source = src;
1854+
}
1855+
1856+
HeapFeatureNode (const HeapFeatureNode &h) {
1857+
key = h.key;
1858+
value = h.value;
1859+
source = h.source;
1860+
}
1861+
};
1862+
1863+
struct HeapFeatureNodeCompare {
1864+
bool operator()(const HeapFeatureNode &t1, const HeapFeatureNode &t2) const {
1865+
return t1.key >= t2.key;
1866+
}
1867+
};
1868+
1869+
// returns a map of the nearest source vertex
1870+
template <typename OUT = uint64_t>
1871+
OUT* edf_with_feature_map(
1872+
uint8_t* field, // really a boolean field
1873+
const uint64_t sx, const uint64_t sy, const uint64_t sz,
1874+
const float wx, const float wy, const float wz,
1875+
const std::vector<uint64_t> &sources,
1876+
float* dist = NULL, OUT* feature_map = NULL,
1877+
size_t &max_loc = _dummy_max_loc
1878+
) {
1879+
1880+
const uint64_t voxels = sx * sy * sz;
1881+
const uint64_t sxy = sx * sy;
1882+
1883+
const libdivide::divider<uint64_t> fast_sx(sx);
1884+
const libdivide::divider<uint64_t> fast_sxy(sxy);
1885+
1886+
const bool power_of_two = !((sx & (sx - 1)) || (sy & (sy - 1)));
1887+
const int xshift = std::log2(sx); // must use log2 here, not lg/lg2 to avoid fp errors
1888+
const int yshift = std::log2(sy);
1889+
1890+
bool clear_dist = false;
1891+
if (dist == NULL) {
1892+
dist = new float[voxels]();
1893+
clear_dist = true;
1894+
}
1895+
if (feature_map == NULL) {
1896+
feature_map = new OUT[voxels]();
1897+
}
1898+
1899+
fill(dist, +INFINITY, voxels);
1900+
1901+
int neighborhood[NHOOD_SIZE] = {};
1902+
1903+
float neighbor_multiplier[NHOOD_SIZE] = {
1904+
wx, wx, wy, wy, wz, wz, // axial directions (6)
1905+
1906+
// square diagonals (12)
1907+
_s(wx, wy), _s(wx, wy), _s(wx, wy), _s(wx, wy),
1908+
_s(wy, wz), _s(wy, wz), _s(wy, wz), _s(wy, wz),
1909+
_s(wx, wz), _s(wx, wz), _s(wx, wz), _s(wx, wz),
1910+
1911+
// cube diagonals (8)
1912+
_c(wx, wy, wz), _c(wx, wy, wz), _c(wx, wy, wz), _c(wx, wy, wz),
1913+
_c(wx, wy, wz), _c(wx, wy, wz), _c(wx, wy, wz), _c(wx, wy, wz)
1914+
};
1915+
1916+
std::priority_queue<
1917+
HeapFeatureNode, std::vector<HeapFeatureNode>, HeapFeatureNodeCompare
1918+
> queue;
1919+
1920+
uint64_t src = 1;
1921+
for (uint64_t source : sources) {
1922+
dist[source] = -0;
1923+
feature_map[source] = src;
1924+
queue.emplace(0.0, source, src);
1925+
src++;
1926+
}
1927+
1928+
uint64_t loc, next_loc;
1929+
float new_dist;
1930+
uint64_t neighboridx;
1931+
1932+
uint64_t x, y, z;
1933+
1934+
float max_dist = -1;
1935+
max_loc = voxels + 1;
1936+
1937+
while (!queue.empty()) {
1938+
src = queue.top().source;
1939+
loc = queue.top().value;
1940+
queue.pop();
1941+
1942+
if (max_dist < std::abs(dist[loc])) {
1943+
max_dist = std::abs(dist[loc]);
1944+
max_loc = loc;
1945+
}
1946+
1947+
if (std::signbit(dist[loc])) {
1948+
continue;
1949+
}
1950+
1951+
if (!queue.empty()) {
1952+
next_loc = queue.top().value;
1953+
if (!std::signbit(dist[next_loc])) {
1954+
1955+
// As early as possible, start fetching the
1956+
// data from RAM b/c the annotated lines below
1957+
// have 30-50% cache miss.
1958+
DIJKSTRA_3D_PREFETCH_26WAY(field, next_loc)
1959+
DIJKSTRA_3D_PREFETCH_26WAY(dist, next_loc)
1960+
}
1961+
}
1962+
1963+
if (power_of_two) {
1964+
z = loc >> (xshift + yshift);
1965+
y = (loc - (z << (xshift + yshift))) >> xshift;
1966+
x = loc - ((y + (z << yshift)) << xshift);
1967+
}
1968+
else {
1969+
z = loc / fast_sxy;
1970+
y = (loc - (z * sxy)) / fast_sx;
1971+
x = loc - sx * (y + z * sy);
1972+
}
1973+
1974+
compute_neighborhood(neighborhood, x, y, z, sx, sy, sz, NHOOD_SIZE, NULL);
1975+
1976+
for (int i = 0; i < NHOOD_SIZE; i++) {
1977+
if (neighborhood[i] == 0) {
1978+
continue;
1979+
}
1980+
1981+
neighboridx = loc + neighborhood[i];
1982+
if (field[neighboridx] == 0) {
1983+
continue;
1984+
}
1985+
1986+
new_dist = dist[loc] + neighbor_multiplier[i];
1987+
1988+
// Visited nodes are negative and thus the current node
1989+
// will always be less than as field is filled with non-negative
1990+
// integers.
1991+
if (new_dist < dist[neighboridx]) {
1992+
dist[neighboridx] = new_dist;
1993+
feature_map[neighboridx] = src;
1994+
queue.emplace(new_dist, neighboridx, src);
1995+
}
1996+
else if (new_dist == dist[neighboridx] && src > feature_map[neighboridx]) {
1997+
feature_map[neighboridx] = src;
1998+
}
1999+
}
2000+
2001+
dist[loc] = -dist[loc];
2002+
}
2003+
2004+
if (clear_dist) {
2005+
delete[] dist;
2006+
}
2007+
else {
2008+
for (size_t i = 0; i < voxels; i++) {
2009+
dist[i] = std::fabs(dist[i]);
2010+
}
2011+
}
2012+
2013+
return feature_map;
2014+
}
2015+
18382016
#undef sq
18392017
#undef NHOOD_SIZE
18402018
#undef DIJKSTRA_3D_PREFETCH_26WAY

0 commit comments

Comments
 (0)