topologysegmentation 0.1.0__tar.gz
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-0.1.0/LICENSE +21 -0
- topologysegmentation-0.1.0/MANIFEST.in +14 -0
- topologysegmentation-0.1.0/NOTICE +39 -0
- topologysegmentation-0.1.0/PKG-INFO +139 -0
- topologysegmentation-0.1.0/QUICKSTART.md +68 -0
- topologysegmentation-0.1.0/README.md +103 -0
- topologysegmentation-0.1.0/RELEASING.md +49 -0
- topologysegmentation-0.1.0/pyproject.toml +94 -0
- topologysegmentation-0.1.0/setup.cfg +4 -0
- topologysegmentation-0.1.0/src/topologysegmentation/VERSION +1 -0
- topologysegmentation-0.1.0/src/topologysegmentation/__init__.py +27 -0
- topologysegmentation-0.1.0/src/topologysegmentation/_native/__init__.py +112 -0
- topologysegmentation-0.1.0/src/topologysegmentation/_native/cubical_betti.c +329 -0
- topologysegmentation-0.1.0/src/topologysegmentation/_native/cubical_matching.c +427 -0
- topologysegmentation-0.1.0/src/topologysegmentation/_native/fallback.py +159 -0
- topologysegmentation-0.1.0/src/topologysegmentation/_native/matching.py +270 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/__init__.py +74 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/common.py +36 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/critical.py +44 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/hu.py +83 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/pooling.py +100 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/registry.py +61 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/segmentation.py +81 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/topology.py +231 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/__init__.py +1 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/bm/BettiMatching.py +3040 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/bm/__init__.py +0 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/bm/loss_functions.py +158 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/bm3d/__init__.py +1 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/bm3d/loss.py +154 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/cldice/__init__.py +0 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/cldice/cldice.py +73 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/cldice/soft_skeleton.py +56 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/sat/PDMatching.py +116 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/sat/SATLoss.py +104 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/sat/__init__.py +0 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/sat/cubical_complex.py +106 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/tloss/LICENSE +182 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/tloss/__init__.py +0 -0
- topologysegmentation-0.1.0/src/topologysegmentation/losses/vendor/tloss/tloss.py +126 -0
- topologysegmentation-0.1.0/src/topologysegmentation/metrics.py +133 -0
- topologysegmentation-0.1.0/src/topologysegmentation/persistence.py +140 -0
- topologysegmentation-0.1.0/src/topologysegmentation.egg-info/PKG-INFO +139 -0
- topologysegmentation-0.1.0/src/topologysegmentation.egg-info/SOURCES.txt +45 -0
- topologysegmentation-0.1.0/src/topologysegmentation.egg-info/dependency_links.txt +1 -0
- topologysegmentation-0.1.0/src/topologysegmentation.egg-info/requires.txt +16 -0
- topologysegmentation-0.1.0/src/topologysegmentation.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 proninegor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
include LICENSE NOTICE README.md QUICKSTART.md RELEASING.md pyproject.toml
|
|
2
|
+
recursive-include src/topologysegmentation *.py
|
|
3
|
+
recursive-include src/topologysegmentation/_native *.c
|
|
4
|
+
include src/topologysegmentation/VERSION
|
|
5
|
+
include src/topologysegmentation/losses/vendor/tloss/LICENSE
|
|
6
|
+
prune experiments
|
|
7
|
+
prune tests
|
|
8
|
+
prune configs
|
|
9
|
+
prune splits
|
|
10
|
+
prune runs
|
|
11
|
+
prune logs
|
|
12
|
+
prune checkpoints
|
|
13
|
+
prune src/topologysegmentation/losses/vendor/gd
|
|
14
|
+
global-exclude *.pt *.pth *.so *.dylib *.log *.pyc
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
topologysegmentation
|
|
2
|
+
Copyright (c) 2026 proninegor
|
|
3
|
+
|
|
4
|
+
This product bundles third-party losses. Each file keeps its own copyright
|
|
5
|
+
header. The project license (MIT, see LICENSE) covers the original code.
|
|
6
|
+
The bundled Apache-2.0 file remains under Apache-2.0.
|
|
7
|
+
|
|
8
|
+
Apache-2.0
|
|
9
|
+
src/topologysegmentation/losses/vendor/tloss/tloss.py
|
|
10
|
+
Copyright 2023 University of Basel and Lucerne University of Applied
|
|
11
|
+
Sciences and Arts Authors
|
|
12
|
+
https://github.com/Digital-Dermatology/t-loss
|
|
13
|
+
The license text is in src/topologysegmentation/losses/vendor/tloss/LICENSE
|
|
14
|
+
|
|
15
|
+
MIT
|
|
16
|
+
src/topologysegmentation/losses/vendor/cldice/
|
|
17
|
+
Suprosanna Shit, Johannes C. Paetzold et al.
|
|
18
|
+
https://github.com/jocpae/clDice
|
|
19
|
+
https://arxiv.org/abs/2003.07311
|
|
20
|
+
|
|
21
|
+
src/topologysegmentation/losses/vendor/bm/
|
|
22
|
+
Nico Stucki, Johannes C. Paetzold, Suprosanna Shit, Bjoern Menze, Ulrich Bauer
|
|
23
|
+
https://github.com/nstucki/Betti-matching
|
|
24
|
+
https://proceedings.mlr.press/v202/stucki23a
|
|
25
|
+
|
|
26
|
+
src/topologysegmentation/losses/vendor/sat/
|
|
27
|
+
Bo Wen, Haochen Zhang, Dirk-Uwe G. Bartsch, William R. Freeman,
|
|
28
|
+
Truong Q. Nguyen, Cheolhong An
|
|
29
|
+
https://github.com/JRC-VPLab/SATLoss
|
|
30
|
+
https://arxiv.org/abs/2412.02076
|
|
31
|
+
Cubical persistence in this folder is adapted from torch-topological
|
|
32
|
+
(https://github.com/aidos-lab/pytorch-topological) and calls GUDHI.
|
|
33
|
+
|
|
34
|
+
Not included in the wheel
|
|
35
|
+
src/topologysegmentation/losses/vendor/gd/ has no license grant in the source
|
|
36
|
+
file, so it is not packaged. It is an unregistered 3D Generalized Dice
|
|
37
|
+
(Sudre et al., https://arxiv.org/abs/1707.03237).
|
|
38
|
+
The C++ extension for bm3d is not vendored. Build
|
|
39
|
+
https://github.com/nstucki/Betti-Matching-3D (MIT) separately.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: topologysegmentation
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Losses and metrics for topology-aware image segmentation
|
|
5
|
+
Author-email: proninegor <pr0n1n3@yandex.ru>
|
|
6
|
+
License-Expression: MIT AND Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/ProninEgorDm/TopologyLosses
|
|
8
|
+
Project-URL: Repository, https://github.com/ProninEgorDm/TopologyLosses
|
|
9
|
+
Project-URL: Issues, https://github.com/ProninEgorDm/TopologyLosses/issues
|
|
10
|
+
Keywords: segmentation,topology,persistent-homology,pytorch,loss
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: <3.13,>=3.12
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
License-File: NOTICE
|
|
21
|
+
License-File: src/topologysegmentation/losses/vendor/tloss/LICENSE
|
|
22
|
+
Requires-Dist: numpy>=2.2
|
|
23
|
+
Requires-Dist: torch>=2.2
|
|
24
|
+
Provides-Extra: ph
|
|
25
|
+
Requires-Dist: gudhi>=3.13.0; extra == "ph"
|
|
26
|
+
Requires-Dist: pot>=0.9.7.post1; extra == "ph"
|
|
27
|
+
Requires-Dist: torch-topological>=0.1.9; extra == "ph"
|
|
28
|
+
Provides-Extra: plot
|
|
29
|
+
Requires-Dist: panel>=1.8.10; extra == "plot"
|
|
30
|
+
Provides-Extra: all
|
|
31
|
+
Requires-Dist: gudhi>=3.13.0; extra == "all"
|
|
32
|
+
Requires-Dist: pot>=0.9.7.post1; extra == "all"
|
|
33
|
+
Requires-Dist: torch-topological>=0.1.9; extra == "all"
|
|
34
|
+
Requires-Dist: panel>=1.8.10; extra == "all"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# topologysegmentation
|
|
38
|
+
|
|
39
|
+
Losses and metrics for topology-aware image segmentation.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install topologysegmentation # Dice, clDice, pooling, critical points, T-Loss
|
|
43
|
+
pip install "topologysegmentation[ph]" # also Hu, Betti matching, and SAT
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import torch
|
|
48
|
+
from topologysegmentation import DiceLoss, create_loss, loss_shapes, topology_errors
|
|
49
|
+
|
|
50
|
+
logits = torch.randn(2, 1, 64, 64) # frame (N, 1, H, W) or volume (N, 1, D, H, W)
|
|
51
|
+
target = torch.zeros(2, 1, 64, 64)
|
|
52
|
+
target[:, :, 16:48, 16:48] = 1
|
|
53
|
+
|
|
54
|
+
dice = DiceLoss()
|
|
55
|
+
cldice = create_loss("cldice")
|
|
56
|
+
value = dice(logits, target) + 0.1 * cldice(logits, target)
|
|
57
|
+
report = topology_errors(logits.sigmoid(), target)
|
|
58
|
+
loss_shapes() # {"dice": (2, 3), "sat": (2,), ...}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Every loss applies sigmoid itself. Pass logits. `topology_errors` is eval-only and expects probabilities. Python 3.12 is required. `bm3d` needs a local build of [Betti-Matching-3D](https://github.com/nstucki/Betti-Matching-3D): put its `betti_matching` module on `PYTHONPATH`.
|
|
62
|
+
|
|
63
|
+
Third-party licenses are listed in [NOTICE](https://github.com/ProninEgorDm/TopologyLosses/blob/main/NOTICE). Training this repository is described in [QUICKSTART.md](https://github.com/ProninEgorDm/TopologyLosses/blob/main/QUICKSTART.md).
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
The base package depends only on `torch` and `numpy`. Homology lives in the `ph` extra.
|
|
68
|
+
|
|
69
|
+
| Command | What you get |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `pip install topologysegmentation` | `dice`, `bce`, `focal`, `combined`, `cldice`, `tloss`, `pooling`, `critical`, Dice/IoU, centerline Dice |
|
|
72
|
+
| `pip install "topologysegmentation[ph]"` | also `hu`, `bm`, `sat`, and Betti numbers |
|
|
73
|
+
| `pip install "topologysegmentation[plot]"` | plots for Betti matching |
|
|
74
|
+
| `pip install "topologysegmentation[all]"` | both extras |
|
|
75
|
+
|
|
76
|
+
## Shapes
|
|
77
|
+
|
|
78
|
+
A frame is `(N, 1, H, W)`. A volume is `(N, 1, D, H, W)`. One frame may be `(H, W)` or a channel-free batch `(N, H, W)`. A volume keeps the channel axis: `(D, H, W)` is read as a batch of frames. A side of length 1 is still that axis: a `1×N` strip is a frame, and a slab of depth 1 is a volume.
|
|
79
|
+
|
|
80
|
+
`loss_shapes()` maps each trainable name to the spatial ranks it accepts. `2` is a frame, `3` is a volume.
|
|
81
|
+
|
|
82
|
+
| Ranks | Losses |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| 2 and 3 | `dice`, `bce`, `focal`, `combined`, `cldice`, `tloss`, `pooling`, `critical`, `hu`, `bm3d` |
|
|
85
|
+
| 2 | `sat`, `bm` |
|
|
86
|
+
|
|
87
|
+
`sat` and `bm` raise `ValueError` on a volume. For a volume, Betti matching is `bm3d`.
|
|
88
|
+
|
|
89
|
+
## Losses
|
|
90
|
+
|
|
91
|
+
Every loss is called the same way: `create_loss(name)(logits, target)`.
|
|
92
|
+
|
|
93
|
+
| Name | What it does | Dependency | Weight next to Dice |
|
|
94
|
+
| --- | --- | --- | --- |
|
|
95
|
+
| `dice`, `bce`, `focal`, `combined` | Overlap and per-pixel losses. Any spatial rank | package | 1 |
|
|
96
|
+
| `cldice` | Soft skeleton, Shit et al., [arXiv:2003.07311](https://arxiv.org/abs/2003.07311). Ten pooling iterations, frames and volumes | package | 0.1–0.3 |
|
|
97
|
+
| `tloss` | Robust Student-t likelihood for a noisy mask, per voxel. `nu` is learned. Any shape | package | 0.1 |
|
|
98
|
+
| `pooling` | Multi-scale occupancy after max-pooling. A frame adds two axis projections, a volume adds three | package | 1 |
|
|
99
|
+
| `critical` | Endpoints, junctions, and isolated voxels. Eight neighbors on a frame, twenty-six on a volume | package | 1 |
|
|
100
|
+
| `hu` | Diagram matching toward birth 1 and death 0, extra bars toward the diagonal. Hu et al., NeurIPS 2019. Frames and volumes | `ph` | 0.1 |
|
|
101
|
+
| `bm` | Betti matching, Stucki et al., ICML 2023. Frames | `ph` | 0.1 |
|
|
102
|
+
| `bm3d` | The same objective through the C++ extension. Frames and volumes | compiled `betti_matching` | 0.1 |
|
|
103
|
+
| `sat` | Spatially aware diagram matching, divided by the image side length. Frames | `ph` | 0.1 |
|
|
104
|
+
|
|
105
|
+
Foreground voxels that touch at a corner are one component. `betti_numbers` on a frame is `(β0, β1)`. On a volume it is `(β0, β1, β2)`. An empty mask is all zeros. A single foreground voxel is `(1, 0)` on a frame and `(1, 0, 0)` on a volume.
|
|
106
|
+
|
|
107
|
+
## Speed
|
|
108
|
+
|
|
109
|
+
One CPU (Apple M1), forward and backward together. Frames are VessMAP labels (`annotator1`, native 256×256), resized with nearest-neighbor interpolation. The prediction is that same mask dilated by one pixel, with 12% of the vessel pixels removed, passed as logits ±4. A leading number is the batch: `3×1024²` is three such frames. `sat` and `bm` are frames only, so this table has no volumes.
|
|
110
|
+
|
|
111
|
+
| Loss | 256² | 512² | 1024² | 3×1024² | 4×256² |
|
|
112
|
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
113
|
+
| `dice` | 0.4 ms | 0.6 ms | 3.1 ms | 7.8 ms | 0.7 ms |
|
|
114
|
+
| `bce` | 0.5 ms | 1.5 ms | 4.7 ms | 13 ms | 1.0 ms |
|
|
115
|
+
| `focal` | 1.2 ms | 2.8 ms | 12 ms | 35 ms | 3.1 ms |
|
|
116
|
+
| `combined` | 1.1 ms | 1.9 ms | 7.2 ms | 25 ms | 2.0 ms |
|
|
117
|
+
| `tloss` | 0.6 ms | 1.0 ms | 3.2 ms | 8.7 ms | 0.8 ms |
|
|
118
|
+
| `pooling` | 6.7 ms | 25 ms | 0.11 s | 0.15 s | 9.3 ms |
|
|
119
|
+
| `critical` | 3.3 ms | 9.3 ms | 47 ms | 0.12 s | 7.7 ms |
|
|
120
|
+
| `cldice` | 99 ms | 0.41 s | 1.9 s | 5.3 s | 0.32 s |
|
|
121
|
+
| `hu` | 0.26 s | 1.1 s | 4.5 s | 14 s | 1.0 s |
|
|
122
|
+
| `sat` | 0.24 s | 1.0 s | 4.2 s | 13 s | 0.98 s |
|
|
123
|
+
| `bm` | 0.64 s | 4.4 s | 73 s | 4.8 min | 2.8 s |
|
|
124
|
+
|
|
125
|
+
These masks have a handful of components and loops (for example `(β0, β1)` of `(5, 0)`, `(4, 7)`, `(19, 4)`). Times up to 512² are the median of three passes. At 1024² and for `3×1024²`, `cldice`, `hu`, `sat`, and `bm` are one pass after a warmup.
|
|
126
|
+
|
|
127
|
+
## Metrics
|
|
128
|
+
|
|
129
|
+
`dice_iou(probabilities, target)` returns per-batch Dice and IoU after a 0.5 threshold. Frames and volumes both work. On a mask compared with itself the score is 1, including a single voxel.
|
|
130
|
+
|
|
131
|
+
`centerline_dice(probabilities, target)` is the Dice score between morphological skeletons.
|
|
132
|
+
|
|
133
|
+
`betti_numbers(mask)` is `(β0, β1)` or `(β0, β1, β2)`. The binary count does not import `gudhi`. `hu` does, through the `ph` extra, because it needs the critical voxels of the diagram.
|
|
134
|
+
|
|
135
|
+
`topology_errors(probabilities, target)` reports the mean absolute Betti error (including `betti_b2_mae` on a volume), the centerline Dice, and a per-image breakdown. This is a validation report, not a loss. A frame does not grow a `β2` term when one side is 1; a one-slice volume does.
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
Original code is MIT, see [LICENSE](https://github.com/ProninEgorDm/TopologyLosses/blob/main/LICENSE). T-Loss stays under Apache-2.0, so the package license expression is `MIT AND Apache-2.0`. The file table is in [NOTICE](https://github.com/ProninEgorDm/TopologyLosses/blob/main/NOTICE).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Быстрый старт
|
|
2
|
+
|
|
3
|
+
Две разные установки. Колесо с PyPI — это лоссы. Команды `train.py` есть только в клоне репозитория.
|
|
4
|
+
|
|
5
|
+
## Пакет
|
|
6
|
+
|
|
7
|
+
Нужен Python 3.12.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install topologysegmentation
|
|
11
|
+
pip install "topologysegmentation[ph]"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Первая команда ставит Dice, clDice, T-Loss, `pooling` и `critical`. Вторая добавляет `gudhi`, `pot` и `torch-topological` для `hu`, `bm`, `sat` и чисел Бетти.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from topologysegmentation import DiceLoss, create_loss, loss_shapes
|
|
18
|
+
|
|
19
|
+
# Кадр (N, 1, H, W) или объём (N, 1, D, H, W). Сигмоида считается внутри лосса.
|
|
20
|
+
loss = DiceLoss()(logits, target) + 0.1 * create_loss("cldice")(logits, target)
|
|
21
|
+
loss_shapes() # у каждого имени набор размерностей: 2 — кадр, 3 — объём
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`dice`, `bce`, `focal`, `combined`, `cldice`, `tloss`, `pooling`, `critical`, `hu` и `bm3d` принимают кадр и объём. `sat` и `bm` только кадр: на объёме они бросают `ValueError`, для объёма есть `bm3d`. Тензор `(D, H, W)` без канала читается как батч плоских кадров.
|
|
25
|
+
|
|
26
|
+
`betti_numbers` на кадре возвращает `(β0, β1)`, на объёме `(β0, β1, β2)`. При первом вызове, если найден `clang`, `cc` или `gcc`, собирается `cubical_betti.c` из колеса. Без компилятора считается запасной путь на NumPy. `TOPOLOGYSEGMENTATION_NATIVE=0` оставляет только его. `bm` так же собирает `cubical_matching.c`; `TOPOLOGYSEGMENTATION_MATCHING=0` оставляет для него питоновские циклы.
|
|
27
|
+
|
|
28
|
+
Скорость на текущей машине, вместе с backward:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python tests/bench_losses.py
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`bm3d` этой командой не ставится. Расширение `betti_matching` собирается из [Betti-Matching-3D](https://github.com/nstucki/Betti-Matching-3D), каталог `build` добавляется в `PYTHONPATH`.
|
|
35
|
+
|
|
36
|
+
Полный список лоссов, весов и лицензий — в [README.md](README.md).
|
|
37
|
+
|
|
38
|
+
## Клон репозитория
|
|
39
|
+
|
|
40
|
+
`uv sync` ставит пакет в окружение `.venv` и группу `dev` из `pyproject.toml`: трансформер, чтение кадров, тензорборд. Эти пакеты в колесо не входят.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv sync
|
|
44
|
+
python tests/test_contract.py
|
|
45
|
+
python train.py --config configs/experiments/dice.yaml --max-steps 1 --image-size 64 --batch-size 2 --epochs 1
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Полный прогон:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python train.py --config configs/experiments/dice.yaml
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Сравнение конфигов из списка `DEFAULT_CONFIGS` в `experiments/sweep.py`:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python train.py sweep --max-steps 1 --image-size 64 --batch-size 2
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Согласие двух аннотаторов на общих кадрах:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python train.py agreement
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Кадры VessMAP лежат в `src/VessMAP` и в git не входят. Сплит с сидом 42 — `splits/vessmap_seed42.json`: 80 обучающих, 12 валидационных, 8 тестовых. Каталог прогона `runs/<время>_<имя>/` содержит копию конфига, сплит, `history.json` и `checkpoints/best.pt`. Поле `parts` в истории — уже взвешенный вклад каждого лосса.
|
|
67
|
+
|
|
68
|
+
Конфиги новых лоссов: `configs/experiments/dice_hu.yaml`, `dice_pooling.yaml`, `dice_critical.yaml`. Веса там уже подобраны под масштаб лосса, см. таблицу в README.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# topologysegmentation
|
|
2
|
+
|
|
3
|
+
Losses and metrics for topology-aware image segmentation.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install topologysegmentation # Dice, clDice, pooling, critical points, T-Loss
|
|
7
|
+
pip install "topologysegmentation[ph]" # also Hu, Betti matching, and SAT
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
import torch
|
|
12
|
+
from topologysegmentation import DiceLoss, create_loss, loss_shapes, topology_errors
|
|
13
|
+
|
|
14
|
+
logits = torch.randn(2, 1, 64, 64) # frame (N, 1, H, W) or volume (N, 1, D, H, W)
|
|
15
|
+
target = torch.zeros(2, 1, 64, 64)
|
|
16
|
+
target[:, :, 16:48, 16:48] = 1
|
|
17
|
+
|
|
18
|
+
dice = DiceLoss()
|
|
19
|
+
cldice = create_loss("cldice")
|
|
20
|
+
value = dice(logits, target) + 0.1 * cldice(logits, target)
|
|
21
|
+
report = topology_errors(logits.sigmoid(), target)
|
|
22
|
+
loss_shapes() # {"dice": (2, 3), "sat": (2,), ...}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Every loss applies sigmoid itself. Pass logits. `topology_errors` is eval-only and expects probabilities. Python 3.12 is required. `bm3d` needs a local build of [Betti-Matching-3D](https://github.com/nstucki/Betti-Matching-3D): put its `betti_matching` module on `PYTHONPATH`.
|
|
26
|
+
|
|
27
|
+
Third-party licenses are listed in [NOTICE](https://github.com/ProninEgorDm/TopologyLosses/blob/main/NOTICE). Training this repository is described in [QUICKSTART.md](https://github.com/ProninEgorDm/TopologyLosses/blob/main/QUICKSTART.md).
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
The base package depends only on `torch` and `numpy`. Homology lives in the `ph` extra.
|
|
32
|
+
|
|
33
|
+
| Command | What you get |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `pip install topologysegmentation` | `dice`, `bce`, `focal`, `combined`, `cldice`, `tloss`, `pooling`, `critical`, Dice/IoU, centerline Dice |
|
|
36
|
+
| `pip install "topologysegmentation[ph]"` | also `hu`, `bm`, `sat`, and Betti numbers |
|
|
37
|
+
| `pip install "topologysegmentation[plot]"` | plots for Betti matching |
|
|
38
|
+
| `pip install "topologysegmentation[all]"` | both extras |
|
|
39
|
+
|
|
40
|
+
## Shapes
|
|
41
|
+
|
|
42
|
+
A frame is `(N, 1, H, W)`. A volume is `(N, 1, D, H, W)`. One frame may be `(H, W)` or a channel-free batch `(N, H, W)`. A volume keeps the channel axis: `(D, H, W)` is read as a batch of frames. A side of length 1 is still that axis: a `1×N` strip is a frame, and a slab of depth 1 is a volume.
|
|
43
|
+
|
|
44
|
+
`loss_shapes()` maps each trainable name to the spatial ranks it accepts. `2` is a frame, `3` is a volume.
|
|
45
|
+
|
|
46
|
+
| Ranks | Losses |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| 2 and 3 | `dice`, `bce`, `focal`, `combined`, `cldice`, `tloss`, `pooling`, `critical`, `hu`, `bm3d` |
|
|
49
|
+
| 2 | `sat`, `bm` |
|
|
50
|
+
|
|
51
|
+
`sat` and `bm` raise `ValueError` on a volume. For a volume, Betti matching is `bm3d`.
|
|
52
|
+
|
|
53
|
+
## Losses
|
|
54
|
+
|
|
55
|
+
Every loss is called the same way: `create_loss(name)(logits, target)`.
|
|
56
|
+
|
|
57
|
+
| Name | What it does | Dependency | Weight next to Dice |
|
|
58
|
+
| --- | --- | --- | --- |
|
|
59
|
+
| `dice`, `bce`, `focal`, `combined` | Overlap and per-pixel losses. Any spatial rank | package | 1 |
|
|
60
|
+
| `cldice` | Soft skeleton, Shit et al., [arXiv:2003.07311](https://arxiv.org/abs/2003.07311). Ten pooling iterations, frames and volumes | package | 0.1–0.3 |
|
|
61
|
+
| `tloss` | Robust Student-t likelihood for a noisy mask, per voxel. `nu` is learned. Any shape | package | 0.1 |
|
|
62
|
+
| `pooling` | Multi-scale occupancy after max-pooling. A frame adds two axis projections, a volume adds three | package | 1 |
|
|
63
|
+
| `critical` | Endpoints, junctions, and isolated voxels. Eight neighbors on a frame, twenty-six on a volume | package | 1 |
|
|
64
|
+
| `hu` | Diagram matching toward birth 1 and death 0, extra bars toward the diagonal. Hu et al., NeurIPS 2019. Frames and volumes | `ph` | 0.1 |
|
|
65
|
+
| `bm` | Betti matching, Stucki et al., ICML 2023. Frames | `ph` | 0.1 |
|
|
66
|
+
| `bm3d` | The same objective through the C++ extension. Frames and volumes | compiled `betti_matching` | 0.1 |
|
|
67
|
+
| `sat` | Spatially aware diagram matching, divided by the image side length. Frames | `ph` | 0.1 |
|
|
68
|
+
|
|
69
|
+
Foreground voxels that touch at a corner are one component. `betti_numbers` on a frame is `(β0, β1)`. On a volume it is `(β0, β1, β2)`. An empty mask is all zeros. A single foreground voxel is `(1, 0)` on a frame and `(1, 0, 0)` on a volume.
|
|
70
|
+
|
|
71
|
+
## Speed
|
|
72
|
+
|
|
73
|
+
One CPU (Apple M1), forward and backward together. Frames are VessMAP labels (`annotator1`, native 256×256), resized with nearest-neighbor interpolation. The prediction is that same mask dilated by one pixel, with 12% of the vessel pixels removed, passed as logits ±4. A leading number is the batch: `3×1024²` is three such frames. `sat` and `bm` are frames only, so this table has no volumes.
|
|
74
|
+
|
|
75
|
+
| Loss | 256² | 512² | 1024² | 3×1024² | 4×256² |
|
|
76
|
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
77
|
+
| `dice` | 0.4 ms | 0.6 ms | 3.1 ms | 7.8 ms | 0.7 ms |
|
|
78
|
+
| `bce` | 0.5 ms | 1.5 ms | 4.7 ms | 13 ms | 1.0 ms |
|
|
79
|
+
| `focal` | 1.2 ms | 2.8 ms | 12 ms | 35 ms | 3.1 ms |
|
|
80
|
+
| `combined` | 1.1 ms | 1.9 ms | 7.2 ms | 25 ms | 2.0 ms |
|
|
81
|
+
| `tloss` | 0.6 ms | 1.0 ms | 3.2 ms | 8.7 ms | 0.8 ms |
|
|
82
|
+
| `pooling` | 6.7 ms | 25 ms | 0.11 s | 0.15 s | 9.3 ms |
|
|
83
|
+
| `critical` | 3.3 ms | 9.3 ms | 47 ms | 0.12 s | 7.7 ms |
|
|
84
|
+
| `cldice` | 99 ms | 0.41 s | 1.9 s | 5.3 s | 0.32 s |
|
|
85
|
+
| `hu` | 0.26 s | 1.1 s | 4.5 s | 14 s | 1.0 s |
|
|
86
|
+
| `sat` | 0.24 s | 1.0 s | 4.2 s | 13 s | 0.98 s |
|
|
87
|
+
| `bm` | 0.64 s | 4.4 s | 73 s | 4.8 min | 2.8 s |
|
|
88
|
+
|
|
89
|
+
These masks have a handful of components and loops (for example `(β0, β1)` of `(5, 0)`, `(4, 7)`, `(19, 4)`). Times up to 512² are the median of three passes. At 1024² and for `3×1024²`, `cldice`, `hu`, `sat`, and `bm` are one pass after a warmup.
|
|
90
|
+
|
|
91
|
+
## Metrics
|
|
92
|
+
|
|
93
|
+
`dice_iou(probabilities, target)` returns per-batch Dice and IoU after a 0.5 threshold. Frames and volumes both work. On a mask compared with itself the score is 1, including a single voxel.
|
|
94
|
+
|
|
95
|
+
`centerline_dice(probabilities, target)` is the Dice score between morphological skeletons.
|
|
96
|
+
|
|
97
|
+
`betti_numbers(mask)` is `(β0, β1)` or `(β0, β1, β2)`. The binary count does not import `gudhi`. `hu` does, through the `ph` extra, because it needs the critical voxels of the diagram.
|
|
98
|
+
|
|
99
|
+
`topology_errors(probabilities, target)` reports the mean absolute Betti error (including `betti_b2_mae` on a volume), the centerline Dice, and a per-image breakdown. This is a validation report, not a loss. A frame does not grow a `β2` term when one side is 1; a one-slice volume does.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
Original code is MIT, see [LICENSE](https://github.com/ProninEgorDm/TopologyLosses/blob/main/LICENSE). T-Loss stays under Apache-2.0, so the package license expression is `MIT AND Apache-2.0`. The file table is in [NOTICE](https://github.com/ProninEgorDm/TopologyLosses/blob/main/NOTICE).
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Выкладка на PyPI
|
|
2
|
+
|
|
3
|
+
Колесо содержит только пакет `topologysegmentation`. Обучение VessMAP, тесты, датасет и сборка `betti_matching` остаются в git.
|
|
4
|
+
|
|
5
|
+
Имя на PyPI — `topologysegmentation`, импорт тот же: `import topologysegmentation`. Имя `topologylosses` не использовать: на PyPI уже есть проект `topolosses`.
|
|
6
|
+
|
|
7
|
+
## Версия
|
|
8
|
+
|
|
9
|
+
Номер записан одной строкой в `src/topologysegmentation/VERSION`. Его читают и пакет, и сборка. Первая публикация — `0.1.0`. Повторно залить ту же версию нельзя: перед следующей сборкой строку нужно увеличить.
|
|
10
|
+
|
|
11
|
+
## Сборка
|
|
12
|
+
|
|
13
|
+
Из корня репозитория, Python 3.12:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv build
|
|
17
|
+
uvx twine check dist/*
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Появляются `dist/topologysegmentation-0.1.0.tar.gz` и `dist/topologysegmentation-0.1.0-py3-none-any.whl`. Каталог `dist/` в git не входит.
|
|
21
|
+
|
|
22
|
+
В архиве исходников и в колесе должны быть `LICENSE`, `NOTICE`, `src/topologysegmentation/losses/vendor/tloss/LICENSE`, `src/topologysegmentation/_native/cubical_betti.c` и `src/topologysegmentation/_native/cubical_matching.c`. Не должно быть `experiments/`, `configs/`, `tests/`, `runs/`, `VessMAP`, `Betti-Matching-3D`, собранных библиотек `cubical_betti_*.dylib` и `cubical_matching_*.dylib` и `losses/vendor/gd`.
|
|
23
|
+
|
|
24
|
+
Колесо остаётся `py3-none-any`: компилятор для `pip install` не нужен. `cubical_betti.c` собирается при первом вызове `betti_numbers`, `cubical_matching.c` — при первом вызове `bm`, если в системе есть `clang`, `cc` или `gcc`. Без компилятора работают запасные пути на Python.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
tar -tzf dist/topologysegmentation-0.1.0.tar.gz
|
|
28
|
+
unzip -l dist/topologysegmentation-0.1.0-py3-none-any.whl
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Публикация
|
|
32
|
+
|
|
33
|
+
Нужен токен со страницы https://pypi.org/manage/account/token/ с областью на весь аккаунт или на проект `topologysegmentation`. Токен TestPyPI на основной индекс не действует.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uvx twine upload dist/*
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Проверка во временном окружении:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv venv /tmp/tl-smoke --python 3.12
|
|
43
|
+
uv pip install --python /tmp/tl-smoke/bin/python topologysegmentation
|
|
44
|
+
/tmp/tl-smoke/bin/python -c "import topologysegmentation; print(topologysegmentation.__version__, topologysegmentation.available_losses())"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`uv venv` не кладёт в окружение исполняемый `pip`, поэтому ставить пакеты нужно через `uv pip --python`.
|
|
48
|
+
|
|
49
|
+
Страница проекта берёт текст из `README.md`. `pip install topologysegmentation` не ставит `bm3d`: расширение собирается отдельно, лицензия MIT указана в `NOTICE`.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "topologysegmentation"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Losses and metrics for topology-aware image segmentation"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12,<3.13"
|
|
11
|
+
license = "MIT AND Apache-2.0"
|
|
12
|
+
license-files = [
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"NOTICE",
|
|
15
|
+
"src/topologysegmentation/losses/vendor/tloss/LICENSE",
|
|
16
|
+
]
|
|
17
|
+
authors = [{ name = "proninegor", email = "pr0n1n3@yandex.ru" }]
|
|
18
|
+
keywords = ["segmentation", "topology", "persistent-homology", "pytorch", "loss"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"numpy>=2.2",
|
|
29
|
+
"torch>=2.2",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
# ph: лоссы и метрики на кубической гомологии (hu, bm, sat, числа Бетти).
|
|
33
|
+
# plot: графики Betti matching. Сам лосс bm их не импортирует.
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
ph = [
|
|
36
|
+
"gudhi>=3.13.0",
|
|
37
|
+
"pot>=0.9.7.post1",
|
|
38
|
+
"torch-topological>=0.1.9",
|
|
39
|
+
]
|
|
40
|
+
plot = [
|
|
41
|
+
"panel>=1.8.10",
|
|
42
|
+
]
|
|
43
|
+
all = [
|
|
44
|
+
"gudhi>=3.13.0",
|
|
45
|
+
"pot>=0.9.7.post1",
|
|
46
|
+
"torch-topological>=0.1.9",
|
|
47
|
+
"panel>=1.8.10",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/ProninEgorDm/TopologyLosses"
|
|
52
|
+
Repository = "https://github.com/ProninEgorDm/TopologyLosses"
|
|
53
|
+
Issues = "https://github.com/ProninEgorDm/TopologyLosses/issues"
|
|
54
|
+
|
|
55
|
+
# Версия пакета — одна строка в src/topologysegmentation/VERSION.
|
|
56
|
+
# Файл читается при сборке без импорта torch.
|
|
57
|
+
[tool.setuptools.dynamic]
|
|
58
|
+
version = { file = "src/topologysegmentation/VERSION" }
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.packages.find]
|
|
61
|
+
where = ["src"]
|
|
62
|
+
include = ["topologysegmentation*"]
|
|
63
|
+
# У GDLoss в файле нет лицензии, в колесо он не входит.
|
|
64
|
+
exclude = ["topologysegmentation.losses.vendor.gd*"]
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.package-data]
|
|
67
|
+
topologysegmentation = ["VERSION", "losses/vendor/tloss/LICENSE", "_native/*.c"]
|
|
68
|
+
|
|
69
|
+
# Локальный репозиторий: uv sync ставит эту группу вместе с пакетом.
|
|
70
|
+
# В колесо PyPI она не попадает.
|
|
71
|
+
[dependency-groups]
|
|
72
|
+
dev = [
|
|
73
|
+
"einops>=0.8.2",
|
|
74
|
+
"gudhi>=3.13.0",
|
|
75
|
+
"ipykernel>=7.2.0",
|
|
76
|
+
"monai>=1.5.2",
|
|
77
|
+
"opencv-python>=4.13.0.92",
|
|
78
|
+
"panel>=1.8.10",
|
|
79
|
+
"pixi>=1.0.1",
|
|
80
|
+
"pot>=0.9.7.post1",
|
|
81
|
+
"pyyaml>=6.0.3",
|
|
82
|
+
"scikit-image>=0.25.2",
|
|
83
|
+
"scikit-learn>=1.7.2",
|
|
84
|
+
"seaborn>=0.13.2",
|
|
85
|
+
"tensorboard>=2.20.0",
|
|
86
|
+
"thop>=0.1.1",
|
|
87
|
+
"tifffile>=2025.5.10",
|
|
88
|
+
"torch-topological>=0.1.9",
|
|
89
|
+
"torchvision>=0.26.0",
|
|
90
|
+
"tqdm>=4.67.3",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
[tool.uv]
|
|
94
|
+
default-groups = ["dev"]
|
|
@@ -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
|
+
]
|