Skip to content

Commit 225f50e

Browse files
authored
Merge pull request #114 from dyollb/feature/topology_simplification
add topological simplification of voxel set = "spherize"
2 parents 2026d6a + cc0d80a commit 225f50e

11 files changed

+1249
-102
lines changed

Core/Graph.h

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/// \file FastMarching.h
2+
///
3+
/// \author Bryn Lloyd
4+
/// \copyright 2016/2019, IT'IS Foundation
5+
6+
#pragma once
7+
8+
#include <array>
9+
#include <cmath>
10+
11+
namespace XCore
12+
{
13+
14+
/** Uniform n-dim grid
15+
*/
16+
template<typename TIndex, int D>
17+
class CUniformGridGraph
18+
{
19+
template <typename T> static constexpr T Pow(T num, unsigned pow) { return pow == 0 ? 1 : num * Pow(num, pow - 1); }
20+
21+
template<int N> struct type{};
22+
23+
template<typename T, int SIZE>
24+
class StackVector
25+
{
26+
public:
27+
using value_type = T;
28+
29+
const T* begin() const { return &_data[0]; }
30+
const T* end() const { return begin() + _size; }
31+
32+
T* begin() { return &_data[0]; }
33+
T* end() { return begin() + _size; }
34+
35+
inline T& operator[](size_t idx) { return _data[idx]; }
36+
inline const T& operator[](size_t idx) const { return _data[idx]; }
37+
38+
size_t size() const { return _size; }
39+
40+
void push_back(T v)
41+
{
42+
_data[_size] = v;
43+
_size++;
44+
}
45+
46+
private:
47+
unsigned _size = 0;
48+
std::array<T, SIZE> _data;
49+
};
50+
51+
public:
52+
using idx_type = TIndex;
53+
using idx_value = typename idx_type::IndexValueType;
54+
using idx_list_type = StackVector<idx_type, Pow(3, D) - 1>;
55+
56+
CUniformGridGraph(const std::array<idx_value, D>& dims, const std::array<double, D>& spacing)
57+
: m_Dims(dims)
58+
{
59+
static_assert(std::is_signed<idx_value>::value, "idx_value should be signed");
60+
static_assert(D > 0, "Dimension should be positive");
61+
62+
// total number of nodes in grid
63+
m_Size = 1;
64+
for (int k=0; k<D; ++k)
65+
{
66+
m_Strides[k] = m_Size;
67+
m_Size *= dims[k];
68+
}
69+
// squared spacing
70+
std::transform(spacing.begin(), spacing.end(), m_Spacing2.begin(),
71+
[](double s){ return s*s; });
72+
}
73+
74+
size_t Size() const
75+
{
76+
return m_Size;
77+
}
78+
79+
const std::array<idx_value, D>& Shape() const
80+
{
81+
return m_Dims;
82+
}
83+
84+
const std::array<size_t, D>& Strides() const
85+
{
86+
return m_Strides;
87+
}
88+
89+
double Length(const idx_type& a, const idx_type& b) const
90+
{
91+
return std::sqrt(LengthD<D-1>(type<D-1>(), a, b));
92+
}
93+
94+
idx_list_type Neighbors(const idx_type& idx) const
95+
{
96+
idx_list_type neighbors;
97+
NeighborsD<D-1>(idx, false, neighbors);
98+
return neighbors;
99+
}
100+
101+
private:
102+
template<int N>
103+
inline double LengthD(type<N>, const idx_type& a, const idx_type& b) const
104+
{
105+
return (a[N] - b[N])*(a[N] - b[N])*m_Spacing2[N] + LengthD<N - 1>(a, b);
106+
}
107+
108+
inline double LengthD(type<-1>, const idx_type& a, const idx_type& b) const
109+
{
110+
return 0;
111+
}
112+
113+
template<int N>
114+
inline void NeighborsD(const idx_type& idx, bool collect, idx_list_type& collected) const
115+
{
116+
NeighborsD(type<N>(), idx, collect, collected);
117+
}
118+
119+
template<int N>
120+
inline void NeighborsD(type<N>, const idx_type& idx, bool collect, idx_list_type& collected) const
121+
{
122+
// make modifiable copy
123+
auto ref{ idx };
124+
125+
// center
126+
NeighborsD<N - 1>(ref, collect, collected);
127+
// bottom
128+
if (idx[N] > 0)
129+
{
130+
ref[N] = idx[N] - 1;
131+
NeighborsD<N - 1>(ref, true, collected);
132+
}
133+
// top
134+
if (idx[N] + 1 < m_Dims[N])
135+
{
136+
ref[N] = idx[N] + 1;
137+
NeighborsD<N - 1>(ref, true, collected);
138+
}
139+
}
140+
141+
void NeighborsD(type<-1>, const idx_type& idx, bool collect, idx_list_type& collected) const
142+
{
143+
if (collect)
144+
collected.push_back(idx);
145+
}
146+
147+
std::array<idx_value, D> m_Dims;
148+
std::array<double, D> m_Spacing2;
149+
size_t m_Size;
150+
std::array<size_t, D> m_Strides;
151+
};
152+
153+
}

Core/TopologyInvariants.h

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/// \file TopologyInvariants.h
2+
///
3+
/// \author Bryn Lloyd
4+
/// \copyright 2020, IT'IS Foundation
5+
6+
#pragma once
7+
8+
#include <array>
9+
#include <deque>
10+
11+
namespace topology {
12+
13+
template<typename T>
14+
inline int CountIf(const T& v)
15+
{
16+
static_assert(std::is_same<T,bool>::value, "CountIf argument must be a boolean.");
17+
return (v != 0) ? 1 : 0;
18+
}
19+
20+
/** \brief True if Euler characteristic of voxel set does not change
21+
*/
22+
template<typename TNeighborhood, typename TLabel>
23+
bool EulerInvariant(const TNeighborhood& neighbors, const TLabel label)
24+
{
25+
// For a face-connected set, a vertex, edge, or facet is included
26+
// only if all voxels it is incident to are in the set, and for a
27+
// vertex-connected set it is included if any incident voxels are in the set
28+
29+
// Here we count only the changes at the center voxel.
30+
// ==> If we mark the center as BG, then P, F, E and V are '0'.
31+
static const int c = 27 / 2;
32+
33+
//fprintf(stderr, "neighbors[c]: %d vs %d", static_cast<int>(neighbors[c]), static_cast<int>(label));
34+
assert(neighbors[c] == label);
35+
36+
// Voxels (parallelepipeds) - count center
37+
const int P = 1;
38+
39+
// Faces - count faces around center voxel
40+
const int F = CountIf(neighbors[c - 1] == label) // -x
41+
+ CountIf(neighbors[c + 1] == label) // +x
42+
+ CountIf(neighbors[c - 3] == label) // -y
43+
+ CountIf(neighbors[c + 3] == label) // +y
44+
+ CountIf(neighbors[c - 9] == label) // -z
45+
+ CountIf(neighbors[c + 9] == label); // +z
46+
47+
// Edges - count edges around center voxel
48+
int E = 0;
49+
{
50+
static const short offsets[12][2] = {
51+
{-1, -3},
52+
{+1, -3},
53+
{-1, +3},
54+
{+1, +3},
55+
{-1, -9},
56+
{+1, -9},
57+
{-1, +9},
58+
{+1, +9},
59+
{-3, -9},
60+
{+3, -9},
61+
{-3, +9},
62+
{+3, +9}};
63+
for (int i = 0; i < 12; i++)
64+
{
65+
short o = offsets[i][0];
66+
short u = offsets[i][1];
67+
68+
E += CountIf(
69+
neighbors[c + o] == label &&
70+
neighbors[c + u] == label &&
71+
neighbors[c + o + u] == label);
72+
}
73+
}
74+
75+
// Vertices - count verts around center voxel
76+
int V = 0;
77+
{
78+
static const short offsets[8][3] = {
79+
{-1, -3, -9},
80+
{+1, -3, -9},
81+
{-1, +3, -9},
82+
{+1, +3, -9},
83+
{-1, -3, +9},
84+
{+1, -3, +9},
85+
{-1, +3, +9},
86+
{+1, +3, +9}};
87+
for (int i = 0; i < 8; i++)
88+
{
89+
short x = offsets[i][0];
90+
short y = offsets[i][1];
91+
short z = offsets[i][2];
92+
93+
V += CountIf(
94+
neighbors[c + x] == label &&
95+
neighbors[c + y] == label &&
96+
neighbors[c + z] == label &&
97+
neighbors[c + x + y] == label &&
98+
neighbors[c + x + z] == label &&
99+
neighbors[c + y + z] == label &&
100+
neighbors[c + x + y + z] == label);
101+
}
102+
}
103+
104+
return (V - E + F - P) == 0;
105+
}
106+
107+
/** \brief Returns the number of connected components for selected label
108+
*/
109+
template<typename TNeighborhood, typename TLabel>
110+
unsigned ConnectedComponents(const TNeighborhood& neighbors, const TLabel label)
111+
{
112+
std::deque<unsigned> queue;
113+
std::array<bool, 27> visited;
114+
visited.fill(false);
115+
116+
static const short ijk_lut[27][3] = {
117+
{0, 0, 0},
118+
{1, 0, 0},
119+
{2, 0, 0},
120+
{0, 1, 0},
121+
{1, 1, 0},
122+
{2, 1, 0},
123+
{0, 2, 0},
124+
{1, 2, 0},
125+
{2, 2, 0},
126+
//
127+
{0, 0, 1},
128+
{1, 0, 1},
129+
{2, 0, 1},
130+
{0, 1, 1},
131+
{1, 1, 1},
132+
{2, 1, 1},
133+
{0, 2, 1},
134+
{1, 2, 1},
135+
{2, 2, 1},
136+
//
137+
{0, 0, 2},
138+
{1, 0, 2},
139+
{2, 0, 2},
140+
{0, 1, 2},
141+
{1, 1, 2},
142+
{2, 1, 2},
143+
{0, 2, 2},
144+
{1, 2, 2},
145+
{2, 2, 2},
146+
};
147+
148+
unsigned new_label = 0;
149+
for (unsigned n = 0; n < 27; ++n)
150+
{
151+
// skip other labels and visited voxels
152+
if (neighbors[n] != label || visited[n])
153+
continue;
154+
155+
queue.push_back(n);
156+
visited[n] = true;
157+
new_label++;
158+
159+
while (!queue.empty())
160+
{
161+
auto id = queue.front();
162+
queue.pop_front();
163+
auto ijk = ijk_lut[id];
164+
165+
if (ijk[0] < 2 && neighbors[id] == neighbors[id + 1] && !visited[id + 1]) // +x
166+
{
167+
queue.push_back(id + 1);
168+
visited[id + 1] = true;
169+
}
170+
if (ijk[0] > 0 && neighbors[id] == neighbors[id - 1] && !visited[id - 1]) // -x
171+
{
172+
queue.push_back(id - 1);
173+
visited[id - 1] = true;
174+
}
175+
if (ijk[1] < 2 && neighbors[id] == neighbors[id + 3] && !visited[id + 3]) // +y
176+
{
177+
queue.push_back(id + 3);
178+
visited[id + 3] = true;
179+
}
180+
if (ijk[1] > 0 && neighbors[id] == neighbors[id - 3] && !visited[id - 3]) // -y
181+
{
182+
queue.push_back(id - 3);
183+
visited[id - 3] = true;
184+
}
185+
if (ijk[2] < 2 && neighbors[id] == neighbors[id + 9] && !visited[id + 9]) // +z
186+
{
187+
queue.push_back(id + 9);
188+
visited[id + 9] = true;
189+
}
190+
if (ijk[2] > 0 && neighbors[id] == neighbors[id - 9] && !visited[id - 9]) // -z
191+
{
192+
queue.push_back(id - 9);
193+
visited[id - 9] = true;
194+
}
195+
}
196+
}
197+
198+
return new_label;
199+
}
200+
201+
/** \brief True if number of connected components of voxel set does not change
202+
*/
203+
template<typename TNeighborhood, typename TLabel>
204+
bool CCInvariant(TNeighborhood neighbors, const TLabel label)
205+
{
206+
neighbors[27 / 2] = 1;
207+
unsigned cc_before = ConnectedComponents(neighbors, label);
208+
209+
neighbors[27 / 2] = 0;
210+
unsigned cc_after = ConnectedComponents(neighbors, label);
211+
return (cc_before == cc_after);
212+
}
213+
214+
} // namespace topology

0 commit comments

Comments
 (0)