topologysegmentation 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topologysegmentation/VERSION +1 -0
- topologysegmentation/__init__.py +27 -0
- topologysegmentation/_native/__init__.py +112 -0
- topologysegmentation/_native/cubical_betti.c +329 -0
- topologysegmentation/_native/cubical_matching.c +427 -0
- topologysegmentation/_native/fallback.py +159 -0
- topologysegmentation/_native/matching.py +270 -0
- topologysegmentation/losses/__init__.py +74 -0
- topologysegmentation/losses/common.py +36 -0
- topologysegmentation/losses/critical.py +44 -0
- topologysegmentation/losses/hu.py +83 -0
- topologysegmentation/losses/pooling.py +100 -0
- topologysegmentation/losses/registry.py +61 -0
- topologysegmentation/losses/segmentation.py +81 -0
- topologysegmentation/losses/topology.py +231 -0
- topologysegmentation/losses/vendor/__init__.py +1 -0
- topologysegmentation/losses/vendor/bm/BettiMatching.py +3040 -0
- topologysegmentation/losses/vendor/bm/__init__.py +0 -0
- topologysegmentation/losses/vendor/bm/loss_functions.py +158 -0
- topologysegmentation/losses/vendor/bm3d/__init__.py +1 -0
- topologysegmentation/losses/vendor/bm3d/loss.py +154 -0
- topologysegmentation/losses/vendor/cldice/__init__.py +0 -0
- topologysegmentation/losses/vendor/cldice/cldice.py +73 -0
- topologysegmentation/losses/vendor/cldice/soft_skeleton.py +56 -0
- topologysegmentation/losses/vendor/sat/PDMatching.py +116 -0
- topologysegmentation/losses/vendor/sat/SATLoss.py +104 -0
- topologysegmentation/losses/vendor/sat/__init__.py +0 -0
- topologysegmentation/losses/vendor/sat/cubical_complex.py +106 -0
- topologysegmentation/losses/vendor/tloss/LICENSE +182 -0
- topologysegmentation/losses/vendor/tloss/__init__.py +0 -0
- topologysegmentation/losses/vendor/tloss/tloss.py +126 -0
- topologysegmentation/metrics.py +133 -0
- topologysegmentation/persistence.py +140 -0
- topologysegmentation-0.1.0.dist-info/METADATA +139 -0
- topologysegmentation-0.1.0.dist-info/RECORD +40 -0
- topologysegmentation-0.1.0.dist-info/WHEEL +5 -0
- topologysegmentation-0.1.0.dist-info/licenses/LICENSE +21 -0
- topologysegmentation-0.1.0.dist-info/licenses/NOTICE +39 -0
- topologysegmentation-0.1.0.dist-info/licenses/src/topologysegmentation/losses/vendor/tloss/LICENSE +182 -0
- topologysegmentation-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Losses and metrics for topology-aware segmentation.
|
|
2
|
+
|
|
3
|
+
The VessMAP training pipeline lives in ``experiments/`` and is not installed
|
|
4
|
+
with this package. The release number is the single line in ``VERSION``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
__version__ = Path(__file__).with_name("VERSION").read_text(encoding="utf-8").strip()
|
|
10
|
+
|
|
11
|
+
from topologysegmentation.losses.registry import available_losses, create_loss, loss_shapes
|
|
12
|
+
from topologysegmentation.losses.segmentation import BCELoss, CombinedLoss, DiceLoss, FocalLoss
|
|
13
|
+
from topologysegmentation.metrics import betti_numbers, centerline_dice, dice_iou, topology_errors
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BCELoss",
|
|
17
|
+
"CombinedLoss",
|
|
18
|
+
"DiceLoss",
|
|
19
|
+
"FocalLoss",
|
|
20
|
+
"available_losses",
|
|
21
|
+
"betti_numbers",
|
|
22
|
+
"centerline_dice",
|
|
23
|
+
"create_loss",
|
|
24
|
+
"dice_iou",
|
|
25
|
+
"loss_shapes",
|
|
26
|
+
"topology_errors",
|
|
27
|
+
]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Optional C implementation of binary cubical Betti numbers.
|
|
2
|
+
|
|
3
|
+
The source ships inside the wheel. The first call compiles it with the system
|
|
4
|
+
C compiler and caches the library under ``~/.cache/topologysegmentation``.
|
|
5
|
+
``TOPOLOGYSEGMENTATION_NATIVE=0`` keeps the NumPy fallback. Install itself
|
|
6
|
+
does not need a compiler.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ctypes
|
|
12
|
+
import hashlib
|
|
13
|
+
import os
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import threading
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
_LOCK = threading.Lock()
|
|
23
|
+
_LIBRARY = None
|
|
24
|
+
_UNAVAILABLE = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def binary_betti(mask: np.ndarray) -> tuple[int, ...] | None:
|
|
28
|
+
"""Return Betti numbers, or ``None`` when the compiled routine is unavailable."""
|
|
29
|
+
library = _library()
|
|
30
|
+
if library is None:
|
|
31
|
+
return None
|
|
32
|
+
out = np.zeros(mask.ndim, dtype=np.int32)
|
|
33
|
+
shape = np.ascontiguousarray(mask.shape, dtype=np.int64)
|
|
34
|
+
code = library.cubical_betti(
|
|
35
|
+
mask.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)),
|
|
36
|
+
int(mask.ndim),
|
|
37
|
+
shape.ctypes.data_as(ctypes.POINTER(ctypes.c_int64)),
|
|
38
|
+
out.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)),
|
|
39
|
+
)
|
|
40
|
+
if code != 0:
|
|
41
|
+
return None
|
|
42
|
+
return tuple(int(value) for value in out)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _library():
|
|
46
|
+
global _LIBRARY, _UNAVAILABLE
|
|
47
|
+
if _UNAVAILABLE:
|
|
48
|
+
return None
|
|
49
|
+
if _LIBRARY is not None:
|
|
50
|
+
return _LIBRARY
|
|
51
|
+
with _LOCK:
|
|
52
|
+
if _LIBRARY is not None or _UNAVAILABLE:
|
|
53
|
+
return _LIBRARY
|
|
54
|
+
if os.environ.get("TOPOLOGYSEGMENTATION_NATIVE") == "0":
|
|
55
|
+
_UNAVAILABLE = True
|
|
56
|
+
return None
|
|
57
|
+
try:
|
|
58
|
+
_LIBRARY = _compile()
|
|
59
|
+
except (OSError, subprocess.SubprocessError):
|
|
60
|
+
_LIBRARY = None
|
|
61
|
+
_UNAVAILABLE = True
|
|
62
|
+
return _LIBRARY
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _compile():
|
|
66
|
+
source = Path(__file__).with_name("cubical_betti.c")
|
|
67
|
+
digest = hashlib.sha256(source.read_bytes()).hexdigest()[:16]
|
|
68
|
+
suffix = ".dylib" if sys.platform == "darwin" else ".so"
|
|
69
|
+
cache = _cache_dir()
|
|
70
|
+
cache.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
library_path = cache / f"cubical_betti_{digest}{suffix}"
|
|
72
|
+
if not library_path.exists():
|
|
73
|
+
compiler = _compiler()
|
|
74
|
+
link = "-dynamiclib" if sys.platform == "darwin" else "-shared"
|
|
75
|
+
command = [compiler, "-O3", "-std=c11", "-fPIC", link, "-o", str(library_path), str(source)]
|
|
76
|
+
subprocess.run(command, check=True, capture_output=True)
|
|
77
|
+
library = ctypes.CDLL(str(library_path))
|
|
78
|
+
library.cubical_betti.argtypes = [
|
|
79
|
+
ctypes.POINTER(ctypes.c_uint8),
|
|
80
|
+
ctypes.c_int,
|
|
81
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
82
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
83
|
+
]
|
|
84
|
+
library.cubical_betti.restype = ctypes.c_int
|
|
85
|
+
return library
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _compiler() -> str:
|
|
89
|
+
for name in ("clang", "cc", "gcc"):
|
|
90
|
+
if _which(name):
|
|
91
|
+
return name
|
|
92
|
+
raise OSError("No C compiler for the Betti routine")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _which(name: str) -> bool:
|
|
96
|
+
path = os.environ.get("PATH", "")
|
|
97
|
+
for directory in path.split(os.pathsep):
|
|
98
|
+
if (Path(directory) / name).is_file():
|
|
99
|
+
return True
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _cache_dir() -> Path:
|
|
104
|
+
override = os.environ.get("TOPOLOGYSEGMENTATION_CACHE")
|
|
105
|
+
if override:
|
|
106
|
+
return Path(override)
|
|
107
|
+
home_cache = Path.home() / ".cache" / "topologysegmentation"
|
|
108
|
+
try:
|
|
109
|
+
home_cache.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
return home_cache
|
|
111
|
+
except OSError:
|
|
112
|
+
return Path(tempfile.gettempdir()) / "topologysegmentation"
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/* Binary cubical Betti numbers for a 2D or 3D mask.
|
|
2
|
+
|
|
3
|
+
Foreground cells that share a corner are one component (8-connected in 2D,
|
|
4
|
+
26-connected in 3D). Voids are finite 6-connected pieces of the complement.
|
|
5
|
+
The Euler characteristic uses the same closed cubes as Gudhi, so the counts
|
|
6
|
+
match a superlevel diagram of a 0/1 image read at a threshold in (0, 1].
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#include <stdint.h>
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
#include <string.h>
|
|
12
|
+
|
|
13
|
+
static int64_t find_root(int32_t *parent, int64_t x) {
|
|
14
|
+
int64_t root = x;
|
|
15
|
+
while (parent[root] != root) {
|
|
16
|
+
root = parent[root];
|
|
17
|
+
}
|
|
18
|
+
while (parent[x] != (int32_t)root) {
|
|
19
|
+
int32_t next = parent[x];
|
|
20
|
+
parent[x] = (int32_t)root;
|
|
21
|
+
x = next;
|
|
22
|
+
}
|
|
23
|
+
return root;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static void unite(int32_t *parent, int64_t a, int64_t b) {
|
|
27
|
+
a = find_root(parent, a);
|
|
28
|
+
b = find_root(parent, b);
|
|
29
|
+
if (a != b) {
|
|
30
|
+
parent[b] = (int32_t)a;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static int link_if_set(int32_t *parent, int64_t i, int64_t j) {
|
|
35
|
+
if (parent[j] >= 0) {
|
|
36
|
+
unite(parent, i, j);
|
|
37
|
+
}
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static int betti2(const uint8_t *mask, int64_t height, int64_t width, int32_t *out) {
|
|
42
|
+
int64_t count = height * width;
|
|
43
|
+
int32_t *parent = (int32_t *)malloc((size_t)count * sizeof(int32_t));
|
|
44
|
+
if (parent == NULL) {
|
|
45
|
+
return -1;
|
|
46
|
+
}
|
|
47
|
+
for (int64_t i = 0; i < count; i++) {
|
|
48
|
+
parent[i] = -1;
|
|
49
|
+
}
|
|
50
|
+
for (int64_t row = 0; row < height; row++) {
|
|
51
|
+
for (int64_t col = 0; col < width; col++) {
|
|
52
|
+
int64_t index = row * width + col;
|
|
53
|
+
if (!mask[index]) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
parent[index] = (int32_t)index;
|
|
57
|
+
if (row > 0) {
|
|
58
|
+
if (col > 0) {
|
|
59
|
+
link_if_set(parent, index, index - width - 1);
|
|
60
|
+
}
|
|
61
|
+
link_if_set(parent, index, index - width);
|
|
62
|
+
if (col + 1 < width) {
|
|
63
|
+
link_if_set(parent, index, index - width + 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (col > 0) {
|
|
67
|
+
link_if_set(parent, index, index - 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
int64_t components = 0;
|
|
72
|
+
int64_t faces = 0;
|
|
73
|
+
for (int64_t i = 0; i < count; i++) {
|
|
74
|
+
if (parent[i] >= 0) {
|
|
75
|
+
faces += 1;
|
|
76
|
+
if (find_root(parent, i) == i) {
|
|
77
|
+
components += 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
free(parent);
|
|
82
|
+
|
|
83
|
+
int64_t edges = 0;
|
|
84
|
+
for (int64_t row = 0; row <= height; row++) {
|
|
85
|
+
for (int64_t col = 0; col < width; col++) {
|
|
86
|
+
int above = row > 0 && mask[(row - 1) * width + col];
|
|
87
|
+
int below = row < height && mask[row * width + col];
|
|
88
|
+
edges += above || below;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (int64_t row = 0; row < height; row++) {
|
|
92
|
+
for (int64_t col = 0; col <= width; col++) {
|
|
93
|
+
int left = col > 0 && mask[row * width + (col - 1)];
|
|
94
|
+
int right = col < width && mask[row * width + col];
|
|
95
|
+
edges += left || right;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
int64_t vertices = 0;
|
|
99
|
+
for (int64_t row = 0; row <= height; row++) {
|
|
100
|
+
for (int64_t col = 0; col <= width; col++) {
|
|
101
|
+
int on = 0;
|
|
102
|
+
if (row > 0 && col > 0 && mask[(row - 1) * width + (col - 1)]) on = 1;
|
|
103
|
+
if (row > 0 && col < width && mask[(row - 1) * width + col]) on = 1;
|
|
104
|
+
if (row < height && col > 0 && mask[row * width + (col - 1)]) on = 1;
|
|
105
|
+
if (row < height && col < width && mask[row * width + col]) on = 1;
|
|
106
|
+
vertices += on;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
out[0] = (int32_t)components;
|
|
110
|
+
out[1] = (int32_t)(components - (vertices - edges + faces));
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static int64_t offset3(int64_t z, int64_t y, int64_t x, int64_t height, int64_t width) {
|
|
115
|
+
return (z * height + y) * width + x;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static int betti3(const uint8_t *mask, int64_t depth, int64_t height, int64_t width, int32_t *out) {
|
|
119
|
+
int64_t count = depth * height * width;
|
|
120
|
+
int32_t *parent = (int32_t *)malloc((size_t)count * sizeof(int32_t));
|
|
121
|
+
int32_t *background = (int32_t *)malloc((size_t)count * sizeof(int32_t));
|
|
122
|
+
uint8_t *touches = (uint8_t *)malloc((size_t)count);
|
|
123
|
+
if (parent == NULL || background == NULL || touches == NULL) {
|
|
124
|
+
free(parent);
|
|
125
|
+
free(background);
|
|
126
|
+
free(touches);
|
|
127
|
+
return -1;
|
|
128
|
+
}
|
|
129
|
+
for (int64_t i = 0; i < count; i++) {
|
|
130
|
+
parent[i] = -1;
|
|
131
|
+
background[i] = -1;
|
|
132
|
+
touches[i] = 0;
|
|
133
|
+
}
|
|
134
|
+
static const int neighbor[13][3] = {
|
|
135
|
+
{-1, -1, -1}, {-1, -1, 0}, {-1, -1, 1}, {-1, 0, -1}, {-1, 0, 0},
|
|
136
|
+
{-1, 0, 1}, {-1, 1, -1}, {-1, 1, 0}, {-1, 1, 1}, {0, -1, -1},
|
|
137
|
+
{0, -1, 0}, {0, -1, 1}, {0, 0, -1},
|
|
138
|
+
};
|
|
139
|
+
for (int64_t z = 0; z < depth; z++) {
|
|
140
|
+
for (int64_t y = 0; y < height; y++) {
|
|
141
|
+
for (int64_t x = 0; x < width; x++) {
|
|
142
|
+
int64_t index = offset3(z, y, x, height, width);
|
|
143
|
+
if (!mask[index]) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
parent[index] = (int32_t)index;
|
|
147
|
+
for (int k = 0; k < 13; k++) {
|
|
148
|
+
int64_t zz = z + neighbor[k][0];
|
|
149
|
+
int64_t yy = y + neighbor[k][1];
|
|
150
|
+
int64_t xx = x + neighbor[k][2];
|
|
151
|
+
if (zz < 0 || yy < 0 || xx < 0 || yy >= height || xx >= width) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
int64_t other = offset3(zz, yy, xx, height, width);
|
|
155
|
+
link_if_set(parent, index, other);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
int64_t components = 0;
|
|
161
|
+
int64_t cubes = 0;
|
|
162
|
+
for (int64_t i = 0; i < count; i++) {
|
|
163
|
+
if (parent[i] >= 0) {
|
|
164
|
+
cubes += 1;
|
|
165
|
+
if (find_root(parent, i) == i) {
|
|
166
|
+
components += 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
free(parent);
|
|
171
|
+
|
|
172
|
+
for (int64_t z = 0; z < depth; z++) {
|
|
173
|
+
for (int64_t y = 0; y < height; y++) {
|
|
174
|
+
for (int64_t x = 0; x < width; x++) {
|
|
175
|
+
int64_t index = offset3(z, y, x, height, width);
|
|
176
|
+
if (mask[index]) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
background[index] = (int32_t)index;
|
|
180
|
+
if (z > 0) link_if_set(background, index, offset3(z - 1, y, x, height, width));
|
|
181
|
+
if (y > 0) link_if_set(background, index, offset3(z, y - 1, x, height, width));
|
|
182
|
+
if (x > 0) link_if_set(background, index, offset3(z, y, x - 1, height, width));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (int64_t z = 0; z < depth; z++) {
|
|
187
|
+
for (int64_t y = 0; y < height; y++) {
|
|
188
|
+
for (int64_t x = 0; x < width; x++) {
|
|
189
|
+
if (z != 0 && y != 0 && x != 0 && z + 1 != depth && y + 1 != height && x + 1 != width) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
int64_t index = offset3(z, y, x, height, width);
|
|
193
|
+
if (background[index] >= 0) {
|
|
194
|
+
touches[find_root(background, index)] = 1;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
int64_t voids = 0;
|
|
200
|
+
for (int64_t i = 0; i < count; i++) {
|
|
201
|
+
if (background[i] >= 0 && find_root(background, i) == i && !touches[i]) {
|
|
202
|
+
voids += 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
free(background);
|
|
206
|
+
free(touches);
|
|
207
|
+
|
|
208
|
+
int64_t faces = 0;
|
|
209
|
+
int64_t edges = 0;
|
|
210
|
+
int64_t vertices = 0;
|
|
211
|
+
for (int64_t z = 0; z <= depth; z++) {
|
|
212
|
+
for (int64_t y = 0; y < height; y++) {
|
|
213
|
+
for (int64_t x = 0; x < width; x++) {
|
|
214
|
+
int behind = z > 0 && mask[offset3(z - 1, y, x, height, width)];
|
|
215
|
+
int ahead = z < depth && mask[offset3(z, y, x, height, width)];
|
|
216
|
+
faces += behind || ahead;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (int64_t z = 0; z < depth; z++) {
|
|
221
|
+
for (int64_t y = 0; y <= height; y++) {
|
|
222
|
+
for (int64_t x = 0; x < width; x++) {
|
|
223
|
+
int behind = y > 0 && mask[offset3(z, y - 1, x, height, width)];
|
|
224
|
+
int ahead = y < height && mask[offset3(z, y, x, height, width)];
|
|
225
|
+
faces += behind || ahead;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (int64_t z = 0; z < depth; z++) {
|
|
230
|
+
for (int64_t y = 0; y < height; y++) {
|
|
231
|
+
for (int64_t x = 0; x <= width; x++) {
|
|
232
|
+
int behind = x > 0 && mask[offset3(z, y, x - 1, height, width)];
|
|
233
|
+
int ahead = x < width && mask[offset3(z, y, x, height, width)];
|
|
234
|
+
faces += behind || ahead;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (int64_t z = 0; z <= depth; z++) {
|
|
239
|
+
for (int64_t y = 0; y <= height; y++) {
|
|
240
|
+
for (int64_t x = 0; x < width; x++) {
|
|
241
|
+
int on = 0;
|
|
242
|
+
for (int dz = 0; dz < 2; dz++) {
|
|
243
|
+
for (int dy = 0; dy < 2; dy++) {
|
|
244
|
+
int64_t zz = z - dz;
|
|
245
|
+
int64_t yy = y - dy;
|
|
246
|
+
if (zz < 0 || yy < 0 || zz >= depth || yy >= height) continue;
|
|
247
|
+
if (mask[offset3(zz, yy, x, height, width)]) on = 1;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
edges += on;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (int64_t z = 0; z <= depth; z++) {
|
|
255
|
+
for (int64_t y = 0; y < height; y++) {
|
|
256
|
+
for (int64_t x = 0; x <= width; x++) {
|
|
257
|
+
int on = 0;
|
|
258
|
+
for (int dz = 0; dz < 2; dz++) {
|
|
259
|
+
for (int dx = 0; dx < 2; dx++) {
|
|
260
|
+
int64_t zz = z - dz;
|
|
261
|
+
int64_t xx = x - dx;
|
|
262
|
+
if (zz < 0 || xx < 0 || zz >= depth || xx >= width) continue;
|
|
263
|
+
if (mask[offset3(zz, y, xx, height, width)]) on = 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
edges += on;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (int64_t z = 0; z < depth; z++) {
|
|
271
|
+
for (int64_t y = 0; y <= height; y++) {
|
|
272
|
+
for (int64_t x = 0; x <= width; x++) {
|
|
273
|
+
int on = 0;
|
|
274
|
+
for (int dy = 0; dy < 2; dy++) {
|
|
275
|
+
for (int dx = 0; dx < 2; dx++) {
|
|
276
|
+
int64_t yy = y - dy;
|
|
277
|
+
int64_t xx = x - dx;
|
|
278
|
+
if (yy < 0 || xx < 0 || yy >= height || xx >= width) continue;
|
|
279
|
+
if (mask[offset3(z, yy, xx, height, width)]) on = 1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
edges += on;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (int64_t z = 0; z <= depth; z++) {
|
|
287
|
+
for (int64_t y = 0; y <= height; y++) {
|
|
288
|
+
for (int64_t x = 0; x <= width; x++) {
|
|
289
|
+
int on = 0;
|
|
290
|
+
for (int dz = 0; dz < 2; dz++) {
|
|
291
|
+
for (int dy = 0; dy < 2; dy++) {
|
|
292
|
+
for (int dx = 0; dx < 2; dx++) {
|
|
293
|
+
int64_t zz = z - dz;
|
|
294
|
+
int64_t yy = y - dy;
|
|
295
|
+
int64_t xx = x - dx;
|
|
296
|
+
if (zz < 0 || yy < 0 || xx < 0 || zz >= depth || yy >= height || xx >= width) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (mask[offset3(zz, yy, xx, height, width)]) on = 1;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
vertices += on;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
int64_t chi = vertices - edges + faces - cubes;
|
|
308
|
+
out[0] = (int32_t)components;
|
|
309
|
+
out[1] = (int32_t)(components + voids - chi);
|
|
310
|
+
out[2] = (int32_t)voids;
|
|
311
|
+
return 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
int cubical_betti(const uint8_t *mask, int ndim, const int64_t *shape, int32_t *out) {
|
|
315
|
+
if (mask == NULL || shape == NULL || out == NULL || (ndim != 2 && ndim != 3)) {
|
|
316
|
+
return -2;
|
|
317
|
+
}
|
|
318
|
+
int64_t count = 1;
|
|
319
|
+
for (int axis = 0; axis < ndim; axis++) {
|
|
320
|
+
if (shape[axis] <= 0 || count > (INT64_C(0x7fffffff) / shape[axis])) {
|
|
321
|
+
return -1;
|
|
322
|
+
}
|
|
323
|
+
count *= shape[axis];
|
|
324
|
+
}
|
|
325
|
+
if (ndim == 2) {
|
|
326
|
+
return betti2(mask, shape[0], shape[1], out);
|
|
327
|
+
}
|
|
328
|
+
return betti3(mask, shape[0], shape[1], shape[2], out);
|
|
329
|
+
}
|