spine-segment 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.
- spine_segment-0.1.0/PKG-INFO +298 -0
- spine_segment-0.1.0/README.md +285 -0
- spine_segment-0.1.0/pyproject.toml +34 -0
- spine_segment-0.1.0/setup.cfg +4 -0
- spine_segment-0.1.0/src/spine_segment/__init__.py +16 -0
- spine_segment-0.1.0/src/spine_segment/api.py +214 -0
- spine_segment-0.1.0/src/spine_segment/backend.py +92 -0
- spine_segment-0.1.0/src/spine_segment/backend_template.py +34 -0
- spine_segment-0.1.0/src/spine_segment/checkpoint_mapping.py +132 -0
- spine_segment-0.1.0/src/spine_segment/cli.py +165 -0
- spine_segment-0.1.0/src/spine_segment/compartments.py +195 -0
- spine_segment-0.1.0/src/spine_segment/device.py +38 -0
- spine_segment-0.1.0/src/spine_segment/graph_resources.py +15 -0
- spine_segment-0.1.0/src/spine_segment/io.py +46 -0
- spine_segment-0.1.0/src/spine_segment/labels.py +5 -0
- spine_segment-0.1.0/src/spine_segment/landmarks.py +31 -0
- spine_segment-0.1.0/src/spine_segment/model_bundle.py +198 -0
- spine_segment-0.1.0/src/spine_segment/native_torch_backend.py +859 -0
- spine_segment-0.1.0/src/spine_segment/outputs.py +33 -0
- spine_segment-0.1.0/src/spine_segment/postprocess.py +132 -0
- spine_segment-0.1.0/src/spine_segment/preprocess.py +75 -0
- spine_segment-0.1.0/src/spine_segment/process_body_relabel.py +342 -0
- spine_segment-0.1.0/src/spine_segment/pytorch_models.py +380 -0
- spine_segment-0.1.0/src/spine_segment/resources/__init__.py +1 -0
- spine_segment-0.1.0/src/spine_segment/resources/possible_successors.pickle +0 -0
- spine_segment-0.1.0/src/spine_segment/resources/units_distances.pickle +0 -0
- spine_segment-0.1.0/src/spine_segment/sequence_solver.py +148 -0
- spine_segment-0.1.0/src/spine_segment/tiling.py +45 -0
- spine_segment-0.1.0/src/spine_segment.egg-info/PKG-INFO +298 -0
- spine_segment-0.1.0/src/spine_segment.egg-info/SOURCES.txt +46 -0
- spine_segment-0.1.0/src/spine_segment.egg-info/dependency_links.txt +1 -0
- spine_segment-0.1.0/src/spine_segment.egg-info/entry_points.txt +2 -0
- spine_segment-0.1.0/src/spine_segment.egg-info/requires.txt +6 -0
- spine_segment-0.1.0/src/spine_segment.egg-info/top_level.txt +1 -0
- spine_segment-0.1.0/tests/test_api.py +141 -0
- spine_segment-0.1.0/tests/test_checkpoint_mapping.py +75 -0
- spine_segment-0.1.0/tests/test_cli.py +29 -0
- spine_segment-0.1.0/tests/test_compartments.py +105 -0
- spine_segment-0.1.0/tests/test_device.py +19 -0
- spine_segment-0.1.0/tests/test_model_bundle.py +115 -0
- spine_segment-0.1.0/tests/test_native_torch_backend.py +36 -0
- spine_segment-0.1.0/tests/test_outputs.py +13 -0
- spine_segment-0.1.0/tests/test_postprocess.py +42 -0
- spine_segment-0.1.0/tests/test_preprocess.py +30 -0
- spine_segment-0.1.0/tests/test_process_body_relabel.py +39 -0
- spine_segment-0.1.0/tests/test_resources.py +11 -0
- spine_segment-0.1.0/tests/test_sequence_solver.py +23 -0
- spine_segment-0.1.0/tests/test_tiling.py +18 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spine-segment
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Standalone CLI-first vertebral CT segmentation package
|
|
5
|
+
Author: Matthias Walle
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy<3,>=1.26
|
|
9
|
+
Requires-Dist: SimpleITK>=2.5
|
|
10
|
+
Requires-Dist: torch
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
13
|
+
|
|
14
|
+
# spine-segment
|
|
15
|
+
|
|
16
|
+
`spine-segment` is a lightweight command-line tool for vertebral CT
|
|
17
|
+
segmentation. It provides a native PyTorch implementation of the coarse-to-fine
|
|
18
|
+
vertebra localization, identification, and segmentation approach described by
|
|
19
|
+
Payer et al. (VISAPP/VISIGRAPP 2020), with additional vertebral body/process
|
|
20
|
+
and cortical/trabecular compartment outputs for quantitative spine analysis.
|
|
21
|
+
|
|
22
|
+
The package is designed for simple local use:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
spine-segment image.nii.gz --output ./segmentation
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It reads and writes NIfTI images with SimpleITK, uses PyTorch for inference, and
|
|
29
|
+
automatically selects `cuda`, `mps`, or `cpu` when `--device auto` is used.
|
|
30
|
+
|
|
31
|
+
## Outputs
|
|
32
|
+
|
|
33
|
+
For each input CT volume, the default command writes:
|
|
34
|
+
|
|
35
|
+
- `*_vertebral-level.nii.gz`: vertebra instance labels using VerSe vertebral
|
|
36
|
+
level IDs, e.g. `20` is L1
|
|
37
|
+
- `*_process-body.nii.gz`: posterior process versus vertebral body labels,
|
|
38
|
+
where `1` is posterior processes and `2` is vertebral body
|
|
39
|
+
- `*_cort-trab.nii.gz`: cortical versus trabecular compartment labels, where
|
|
40
|
+
`1` is cortical bone and `2` is trabecular bone
|
|
41
|
+
- `*_centroids.json`: vertebral centroids in the original input scan grid
|
|
42
|
+
|
|
43
|
+
Two reduced-output modes are available:
|
|
44
|
+
|
|
45
|
+
- `--level-only`: writes `*_vertebral-level.nii.gz` and `*_centroids.json`
|
|
46
|
+
- `--localization-only`: writes only `*_centroids.json`
|
|
47
|
+
|
|
48
|
+
The centroid JSON is keyed by vertebral label. For example, label `20`
|
|
49
|
+
corresponds to L1 in the VerSe convention used by this model:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"20": {
|
|
54
|
+
"label": 20,
|
|
55
|
+
"index": 19,
|
|
56
|
+
"voxel_xyz": [264.77, 206.40, 95.50],
|
|
57
|
+
"physical_xyz": [17.75, 45.77, -235.56],
|
|
58
|
+
"voxel_count": 18422,
|
|
59
|
+
"source": "segmentation"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`voxel_xyz` is reported in the original input scan coordinate grid.
|
|
65
|
+
In `--localization-only` mode, centroids are generated directly from the
|
|
66
|
+
localization model and include a model response score.
|
|
67
|
+
|
|
68
|
+
## Label Reference
|
|
69
|
+
|
|
70
|
+
### `*_vertebral-level.nii.gz`
|
|
71
|
+
|
|
72
|
+
The vertebral-level output uses the VerSe vertebral label convention:
|
|
73
|
+
|
|
74
|
+
| Label | Anatomy |
|
|
75
|
+
| ---: | --- |
|
|
76
|
+
| `0` | Background |
|
|
77
|
+
| `1`-`7` | C1-C7 |
|
|
78
|
+
| `8`-`19` | T1-T12 |
|
|
79
|
+
| `20`-`25` | L1-L6 |
|
|
80
|
+
| `28` | Sacrum, when detected |
|
|
81
|
+
|
|
82
|
+
For example, `20` corresponds to L1.
|
|
83
|
+
|
|
84
|
+
### `*_process-body.nii.gz`
|
|
85
|
+
|
|
86
|
+
The process/body output is a binary compartment relabeling inside the vertebral
|
|
87
|
+
segmentation:
|
|
88
|
+
|
|
89
|
+
| Label | Anatomy |
|
|
90
|
+
| ---: | --- |
|
|
91
|
+
| `0` | Background |
|
|
92
|
+
| `1` | Posterior processes |
|
|
93
|
+
| `2` | Vertebral body |
|
|
94
|
+
|
|
95
|
+
### `*_cort-trab.nii.gz`
|
|
96
|
+
|
|
97
|
+
The cortical/trabecular output is derived from the vertebral-level segmentation
|
|
98
|
+
and CT intensities:
|
|
99
|
+
|
|
100
|
+
| Label | Anatomy |
|
|
101
|
+
| ---: | --- |
|
|
102
|
+
| `0` | Background |
|
|
103
|
+
| `1` | Cortical compartment |
|
|
104
|
+
| `2` | Trabecular compartment |
|
|
105
|
+
|
|
106
|
+
### `*_centroids.json`
|
|
107
|
+
|
|
108
|
+
The centroid JSON is keyed by the same vertebral labels as
|
|
109
|
+
`*_vertebral-level.nii.gz`. Each entry contains:
|
|
110
|
+
|
|
111
|
+
| Field | Meaning |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `label` | Vertebral label value, e.g. `20` for L1 |
|
|
114
|
+
| `index` | Internal model index for that label |
|
|
115
|
+
| `voxel_xyz` | Continuous centroid index in original input voxel coordinates |
|
|
116
|
+
| `physical_xyz` | Centroid physical coordinate from the input image geometry |
|
|
117
|
+
| `voxel_count` | Number of voxels for segmentation-derived centroids |
|
|
118
|
+
| `score` | Localization model response, only for `--localization-only` |
|
|
119
|
+
| `source` | Centroid source, usually `segmentation` in normal mode |
|
|
120
|
+
|
|
121
|
+
## Installation
|
|
122
|
+
|
|
123
|
+
Install from PyPI:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python3 -m pip install spine-segment
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The first segmentation run downloads the model bundle from the
|
|
130
|
+
`spine-segment` GitHub Releases page into the local user cache, verifies the
|
|
131
|
+
checkpoint SHA256 hashes, and reuses that cached bundle on later runs.
|
|
132
|
+
|
|
133
|
+
To disable the automatic download on offline or managed systems:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
spine-segment image.nii.gz --output ./segmentation --no-model-download
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
or set:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
export SPINE_SEGMENT_NO_DOWNLOAD=1
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
To use a manually staged bundle:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
export SPINE_SEGMENT_MODEL_BUNDLE=/path/to/model-bundle
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
or pass it directly:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
spine-segment image.nii.gz --output ./segmentation --model-bundle /path/to/model-bundle
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For editable development installs, the model weights are stored with Git LFS.
|
|
158
|
+
Install Git LFS before cloning, or run `git lfs pull` after cloning if the
|
|
159
|
+
weights were not downloaded.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
git lfs install
|
|
163
|
+
git clone https://github.com/wallematthias/spine-segment.git
|
|
164
|
+
cd spine-segment
|
|
165
|
+
git lfs pull
|
|
166
|
+
python3 -m pip install -e .
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The runtime Python dependencies are intentionally small:
|
|
170
|
+
|
|
171
|
+
- `torch`
|
|
172
|
+
- `numpy`
|
|
173
|
+
- `SimpleITK`
|
|
174
|
+
|
|
175
|
+
On macOS, the default PyPI PyTorch package supports CPU and Apple MPS on
|
|
176
|
+
compatible machines. On CUDA systems, install the PyTorch build that matches
|
|
177
|
+
your CUDA runtime before installing `spine-segment`; see the PyTorch install
|
|
178
|
+
selector at <https://pytorch.org/get-started/locally/>.
|
|
179
|
+
|
|
180
|
+
If Git LFS did not fetch correctly, files in
|
|
181
|
+
`build/model-bundle-pytorch/weights/` will be small text pointer files instead
|
|
182
|
+
of PyTorch checkpoint files. Run:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git lfs pull
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The development checkout model bundle is expected at:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
build/model-bundle-pytorch/
|
|
192
|
+
manifest.json
|
|
193
|
+
weights/
|
|
194
|
+
spine-locator.pt
|
|
195
|
+
vertebra-locator.pt
|
|
196
|
+
vertebra-segmenter.pt
|
|
197
|
+
process-body-segmenter.pt
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Command Line Usage
|
|
201
|
+
|
|
202
|
+
Segment one scan:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
spine-segment image.nii.gz --output ./segmentation
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Segment multiple scans:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
spine-segment *.nii.gz --output ./segmentation
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Write only vertebral-level labels:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
spine-segment image.nii.gz --output ./segmentation --level-only
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Write only vertebral centroids from the localization model:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
spine-segment image.nii.gz --output ./segmentation --localization-only
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Select a device explicitly:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
spine-segment image.nii.gz --output ./segmentation --device cuda
|
|
230
|
+
spine-segment image.nii.gz --output ./segmentation --device mps
|
|
231
|
+
spine-segment image.nii.gz --output ./segmentation --device cpu
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Implementation Notes
|
|
235
|
+
|
|
236
|
+
The vertebral-level model follows a three-stage coarse-to-fine pipeline:
|
|
237
|
+
|
|
238
|
+
1. Spine localization at coarse resolution.
|
|
239
|
+
2. Vertebra centroid localization and identification.
|
|
240
|
+
3. Per-vertebra segmentation from 1 mm isotropic patches.
|
|
241
|
+
|
|
242
|
+
For long whole-body CT scans whose superior-inferior extent exceeds the coarse
|
|
243
|
+
spine localizer field of view, the implementation evaluates overlapping
|
|
244
|
+
localizer windows along z and selects the candidate whose downstream vertebra
|
|
245
|
+
localization yields the strongest/most landmarks. Output metadata records the
|
|
246
|
+
number of spine-localizer tiles and the selected tile.
|
|
247
|
+
|
|
248
|
+
The implementation in this repository is native PyTorch and does not require
|
|
249
|
+
TensorFlow or nnU-Net at runtime. The process/body segmentation network is
|
|
250
|
+
loaded directly from a PyTorch checkpoint using a minimal in-repository model
|
|
251
|
+
definition.
|
|
252
|
+
|
|
253
|
+
The cortical/trabecular compartment output is derived from the vertebral-level
|
|
254
|
+
segmentation and CT intensities. The current post-processing keeps the outer
|
|
255
|
+
surface constrained to the vertebral labelmap, assigns a minimum one-voxel
|
|
256
|
+
cortical outline, and allows connected high-density cortical extension within a
|
|
257
|
+
6 mm shell.
|
|
258
|
+
|
|
259
|
+
## Citations
|
|
260
|
+
|
|
261
|
+
If you use this package, please cite the underlying methods:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
Payer C, Štern D, Bischof H, Urschler M.
|
|
265
|
+
Coarse to Fine Vertebrae Localization and Segmentation with
|
|
266
|
+
SpatialConfiguration-Net and U-Net.
|
|
267
|
+
In: Proceedings of the 15th International Joint Conference on Computer Vision,
|
|
268
|
+
Imaging and Computer Graphics Theory and Applications (VISIGRAPP 2020),
|
|
269
|
+
Volume 5: VISAPP. 2020;124-133.
|
|
270
|
+
doi:10.5220/0008975201240133
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
```text
|
|
274
|
+
Walle M, Matheson BE, Boyd SK.
|
|
275
|
+
Comparing linear and nonlinear finite element models of vertebral strength
|
|
276
|
+
across the thoracolumbar spine: a benchmark from density-calibrated computed
|
|
277
|
+
tomography.
|
|
278
|
+
GigaScience. 2025;14:giaf094.
|
|
279
|
+
doi:10.1093/gigascience/giaf094
|
|
280
|
+
PMID:40880132
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Development
|
|
284
|
+
|
|
285
|
+
Run the test suite with:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
python3 -m pip install -e ".[test]"
|
|
289
|
+
python3 -m pytest tests -q
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
The repository also includes utilities for staging model bundles and converting
|
|
293
|
+
the original TensorFlow checkpoints into PyTorch state dictionaries:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
python3 scripts/stage_model_bundle.py --help
|
|
297
|
+
python3 scripts/convert_mdat_tf_checkpoints.py --help
|
|
298
|
+
```
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# spine-segment
|
|
2
|
+
|
|
3
|
+
`spine-segment` is a lightweight command-line tool for vertebral CT
|
|
4
|
+
segmentation. It provides a native PyTorch implementation of the coarse-to-fine
|
|
5
|
+
vertebra localization, identification, and segmentation approach described by
|
|
6
|
+
Payer et al. (VISAPP/VISIGRAPP 2020), with additional vertebral body/process
|
|
7
|
+
and cortical/trabecular compartment outputs for quantitative spine analysis.
|
|
8
|
+
|
|
9
|
+
The package is designed for simple local use:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
spine-segment image.nii.gz --output ./segmentation
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It reads and writes NIfTI images with SimpleITK, uses PyTorch for inference, and
|
|
16
|
+
automatically selects `cuda`, `mps`, or `cpu` when `--device auto` is used.
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
For each input CT volume, the default command writes:
|
|
21
|
+
|
|
22
|
+
- `*_vertebral-level.nii.gz`: vertebra instance labels using VerSe vertebral
|
|
23
|
+
level IDs, e.g. `20` is L1
|
|
24
|
+
- `*_process-body.nii.gz`: posterior process versus vertebral body labels,
|
|
25
|
+
where `1` is posterior processes and `2` is vertebral body
|
|
26
|
+
- `*_cort-trab.nii.gz`: cortical versus trabecular compartment labels, where
|
|
27
|
+
`1` is cortical bone and `2` is trabecular bone
|
|
28
|
+
- `*_centroids.json`: vertebral centroids in the original input scan grid
|
|
29
|
+
|
|
30
|
+
Two reduced-output modes are available:
|
|
31
|
+
|
|
32
|
+
- `--level-only`: writes `*_vertebral-level.nii.gz` and `*_centroids.json`
|
|
33
|
+
- `--localization-only`: writes only `*_centroids.json`
|
|
34
|
+
|
|
35
|
+
The centroid JSON is keyed by vertebral label. For example, label `20`
|
|
36
|
+
corresponds to L1 in the VerSe convention used by this model:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"20": {
|
|
41
|
+
"label": 20,
|
|
42
|
+
"index": 19,
|
|
43
|
+
"voxel_xyz": [264.77, 206.40, 95.50],
|
|
44
|
+
"physical_xyz": [17.75, 45.77, -235.56],
|
|
45
|
+
"voxel_count": 18422,
|
|
46
|
+
"source": "segmentation"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`voxel_xyz` is reported in the original input scan coordinate grid.
|
|
52
|
+
In `--localization-only` mode, centroids are generated directly from the
|
|
53
|
+
localization model and include a model response score.
|
|
54
|
+
|
|
55
|
+
## Label Reference
|
|
56
|
+
|
|
57
|
+
### `*_vertebral-level.nii.gz`
|
|
58
|
+
|
|
59
|
+
The vertebral-level output uses the VerSe vertebral label convention:
|
|
60
|
+
|
|
61
|
+
| Label | Anatomy |
|
|
62
|
+
| ---: | --- |
|
|
63
|
+
| `0` | Background |
|
|
64
|
+
| `1`-`7` | C1-C7 |
|
|
65
|
+
| `8`-`19` | T1-T12 |
|
|
66
|
+
| `20`-`25` | L1-L6 |
|
|
67
|
+
| `28` | Sacrum, when detected |
|
|
68
|
+
|
|
69
|
+
For example, `20` corresponds to L1.
|
|
70
|
+
|
|
71
|
+
### `*_process-body.nii.gz`
|
|
72
|
+
|
|
73
|
+
The process/body output is a binary compartment relabeling inside the vertebral
|
|
74
|
+
segmentation:
|
|
75
|
+
|
|
76
|
+
| Label | Anatomy |
|
|
77
|
+
| ---: | --- |
|
|
78
|
+
| `0` | Background |
|
|
79
|
+
| `1` | Posterior processes |
|
|
80
|
+
| `2` | Vertebral body |
|
|
81
|
+
|
|
82
|
+
### `*_cort-trab.nii.gz`
|
|
83
|
+
|
|
84
|
+
The cortical/trabecular output is derived from the vertebral-level segmentation
|
|
85
|
+
and CT intensities:
|
|
86
|
+
|
|
87
|
+
| Label | Anatomy |
|
|
88
|
+
| ---: | --- |
|
|
89
|
+
| `0` | Background |
|
|
90
|
+
| `1` | Cortical compartment |
|
|
91
|
+
| `2` | Trabecular compartment |
|
|
92
|
+
|
|
93
|
+
### `*_centroids.json`
|
|
94
|
+
|
|
95
|
+
The centroid JSON is keyed by the same vertebral labels as
|
|
96
|
+
`*_vertebral-level.nii.gz`. Each entry contains:
|
|
97
|
+
|
|
98
|
+
| Field | Meaning |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| `label` | Vertebral label value, e.g. `20` for L1 |
|
|
101
|
+
| `index` | Internal model index for that label |
|
|
102
|
+
| `voxel_xyz` | Continuous centroid index in original input voxel coordinates |
|
|
103
|
+
| `physical_xyz` | Centroid physical coordinate from the input image geometry |
|
|
104
|
+
| `voxel_count` | Number of voxels for segmentation-derived centroids |
|
|
105
|
+
| `score` | Localization model response, only for `--localization-only` |
|
|
106
|
+
| `source` | Centroid source, usually `segmentation` in normal mode |
|
|
107
|
+
|
|
108
|
+
## Installation
|
|
109
|
+
|
|
110
|
+
Install from PyPI:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python3 -m pip install spine-segment
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The first segmentation run downloads the model bundle from the
|
|
117
|
+
`spine-segment` GitHub Releases page into the local user cache, verifies the
|
|
118
|
+
checkpoint SHA256 hashes, and reuses that cached bundle on later runs.
|
|
119
|
+
|
|
120
|
+
To disable the automatic download on offline or managed systems:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
spine-segment image.nii.gz --output ./segmentation --no-model-download
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
or set:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export SPINE_SEGMENT_NO_DOWNLOAD=1
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
To use a manually staged bundle:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
export SPINE_SEGMENT_MODEL_BUNDLE=/path/to/model-bundle
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
or pass it directly:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
spine-segment image.nii.gz --output ./segmentation --model-bundle /path/to/model-bundle
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
For editable development installs, the model weights are stored with Git LFS.
|
|
145
|
+
Install Git LFS before cloning, or run `git lfs pull` after cloning if the
|
|
146
|
+
weights were not downloaded.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
git lfs install
|
|
150
|
+
git clone https://github.com/wallematthias/spine-segment.git
|
|
151
|
+
cd spine-segment
|
|
152
|
+
git lfs pull
|
|
153
|
+
python3 -m pip install -e .
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The runtime Python dependencies are intentionally small:
|
|
157
|
+
|
|
158
|
+
- `torch`
|
|
159
|
+
- `numpy`
|
|
160
|
+
- `SimpleITK`
|
|
161
|
+
|
|
162
|
+
On macOS, the default PyPI PyTorch package supports CPU and Apple MPS on
|
|
163
|
+
compatible machines. On CUDA systems, install the PyTorch build that matches
|
|
164
|
+
your CUDA runtime before installing `spine-segment`; see the PyTorch install
|
|
165
|
+
selector at <https://pytorch.org/get-started/locally/>.
|
|
166
|
+
|
|
167
|
+
If Git LFS did not fetch correctly, files in
|
|
168
|
+
`build/model-bundle-pytorch/weights/` will be small text pointer files instead
|
|
169
|
+
of PyTorch checkpoint files. Run:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
git lfs pull
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The development checkout model bundle is expected at:
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
build/model-bundle-pytorch/
|
|
179
|
+
manifest.json
|
|
180
|
+
weights/
|
|
181
|
+
spine-locator.pt
|
|
182
|
+
vertebra-locator.pt
|
|
183
|
+
vertebra-segmenter.pt
|
|
184
|
+
process-body-segmenter.pt
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Command Line Usage
|
|
188
|
+
|
|
189
|
+
Segment one scan:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
spine-segment image.nii.gz --output ./segmentation
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Segment multiple scans:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
spine-segment *.nii.gz --output ./segmentation
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Write only vertebral-level labels:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
spine-segment image.nii.gz --output ./segmentation --level-only
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Write only vertebral centroids from the localization model:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
spine-segment image.nii.gz --output ./segmentation --localization-only
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Select a device explicitly:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
spine-segment image.nii.gz --output ./segmentation --device cuda
|
|
217
|
+
spine-segment image.nii.gz --output ./segmentation --device mps
|
|
218
|
+
spine-segment image.nii.gz --output ./segmentation --device cpu
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Implementation Notes
|
|
222
|
+
|
|
223
|
+
The vertebral-level model follows a three-stage coarse-to-fine pipeline:
|
|
224
|
+
|
|
225
|
+
1. Spine localization at coarse resolution.
|
|
226
|
+
2. Vertebra centroid localization and identification.
|
|
227
|
+
3. Per-vertebra segmentation from 1 mm isotropic patches.
|
|
228
|
+
|
|
229
|
+
For long whole-body CT scans whose superior-inferior extent exceeds the coarse
|
|
230
|
+
spine localizer field of view, the implementation evaluates overlapping
|
|
231
|
+
localizer windows along z and selects the candidate whose downstream vertebra
|
|
232
|
+
localization yields the strongest/most landmarks. Output metadata records the
|
|
233
|
+
number of spine-localizer tiles and the selected tile.
|
|
234
|
+
|
|
235
|
+
The implementation in this repository is native PyTorch and does not require
|
|
236
|
+
TensorFlow or nnU-Net at runtime. The process/body segmentation network is
|
|
237
|
+
loaded directly from a PyTorch checkpoint using a minimal in-repository model
|
|
238
|
+
definition.
|
|
239
|
+
|
|
240
|
+
The cortical/trabecular compartment output is derived from the vertebral-level
|
|
241
|
+
segmentation and CT intensities. The current post-processing keeps the outer
|
|
242
|
+
surface constrained to the vertebral labelmap, assigns a minimum one-voxel
|
|
243
|
+
cortical outline, and allows connected high-density cortical extension within a
|
|
244
|
+
6 mm shell.
|
|
245
|
+
|
|
246
|
+
## Citations
|
|
247
|
+
|
|
248
|
+
If you use this package, please cite the underlying methods:
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
Payer C, Štern D, Bischof H, Urschler M.
|
|
252
|
+
Coarse to Fine Vertebrae Localization and Segmentation with
|
|
253
|
+
SpatialConfiguration-Net and U-Net.
|
|
254
|
+
In: Proceedings of the 15th International Joint Conference on Computer Vision,
|
|
255
|
+
Imaging and Computer Graphics Theory and Applications (VISIGRAPP 2020),
|
|
256
|
+
Volume 5: VISAPP. 2020;124-133.
|
|
257
|
+
doi:10.5220/0008975201240133
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
Walle M, Matheson BE, Boyd SK.
|
|
262
|
+
Comparing linear and nonlinear finite element models of vertebral strength
|
|
263
|
+
across the thoracolumbar spine: a benchmark from density-calibrated computed
|
|
264
|
+
tomography.
|
|
265
|
+
GigaScience. 2025;14:giaf094.
|
|
266
|
+
doi:10.1093/gigascience/giaf094
|
|
267
|
+
PMID:40880132
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Development
|
|
271
|
+
|
|
272
|
+
Run the test suite with:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
python3 -m pip install -e ".[test]"
|
|
276
|
+
python3 -m pytest tests -q
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The repository also includes utilities for staging model bundles and converting
|
|
280
|
+
the original TensorFlow checkpoints into PyTorch state dictionaries:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
python3 scripts/stage_model_bundle.py --help
|
|
284
|
+
python3 scripts/convert_mdat_tf_checkpoints.py --help
|
|
285
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spine-segment"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Standalone CLI-first vertebral CT segmentation package"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Matthias Walle" }
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"numpy>=1.26,<3",
|
|
16
|
+
"SimpleITK>=2.5",
|
|
17
|
+
"torch",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
test = ["pytest>=8"]
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
spine-segment = "spine_segment.cli:main"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
package-dir = {"" = "src"}
|
|
28
|
+
include-package-data = true
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.package-data]
|
|
34
|
+
spine_segment = ["resources/*.pickle"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from spine_segment.api import segment_file, segment_files
|
|
2
|
+
from spine_segment.backend import (
|
|
3
|
+
SegmentationResult,
|
|
4
|
+
SpineSegmentBackend,
|
|
5
|
+
SpineSegmentBackendError,
|
|
6
|
+
load_backend,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"SegmentationResult",
|
|
11
|
+
"SpineSegmentBackend",
|
|
12
|
+
"SpineSegmentBackendError",
|
|
13
|
+
"load_backend",
|
|
14
|
+
"segment_file",
|
|
15
|
+
"segment_files",
|
|
16
|
+
]
|