seqseg 2.1.7__tar.gz → 2.1.9__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.
Files changed (85) hide show
  1. {seqseg-2.1.7 → seqseg-2.1.9}/PKG-INFO +28 -51
  2. seqseg-2.1.9/README.md +114 -0
  3. {seqseg-2.1.7 → seqseg-2.1.9}/pyproject.toml +1 -1
  4. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/cli.py +84 -39
  5. seqseg-2.1.9/seqseg/pipeline/nnunet_cli.py +108 -0
  6. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/pipeline/train.py +629 -64
  7. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_cli_helpers.py +87 -14
  8. seqseg-2.1.9/seqseg/tests/test_nnunet_cli.py +139 -0
  9. seqseg-2.1.9/seqseg/tests/test_train_data.py +97 -0
  10. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_train_pipeline.py +439 -73
  11. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg.egg-info/PKG-INFO +28 -51
  12. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg.egg-info/SOURCES.txt +3 -0
  13. seqseg-2.1.7/README.md +0 -137
  14. {seqseg-2.1.7 → seqseg-2.1.9}/LICENSE +0 -0
  15. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/__init__.py +0 -0
  16. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/api.py +0 -0
  17. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/citations.py +0 -0
  18. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/__init__.py +0 -0
  19. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/aorta_tutorial.yaml +0 -0
  20. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global.yaml +0 -0
  21. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global_aorta.yaml +0 -0
  22. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global_cereb.yaml +0 -0
  23. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global_coro.yaml +0 -0
  24. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global_debug.yaml +0 -0
  25. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global_default.yaml +0 -0
  26. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global_pulm.yaml +0 -0
  27. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global_seg.yaml +0 -0
  28. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config/global_test.yaml +0 -0
  29. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/config_models.py +0 -0
  30. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/__init__.py +0 -0
  31. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/assembly.py +0 -0
  32. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/capping.py +0 -0
  33. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/centerline.py +0 -0
  34. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/datasets.py +0 -0
  35. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/evaluation.py +0 -0
  36. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/initialization.py +0 -0
  37. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/local_assembly.py +0 -0
  38. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/nnunet.py +0 -0
  39. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/params.py +0 -0
  40. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/prediction.py +0 -0
  41. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/simvascular.py +0 -0
  42. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/simvascular_contour.py +0 -0
  43. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/sitk_functions.py +0 -0
  44. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/sweep.py +0 -0
  45. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/tracing.py +0 -0
  46. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/tracing_functions.py +0 -0
  47. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/vmtk_functions.py +0 -0
  48. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/modules/vtk_functions.py +0 -0
  49. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/parallel/old_auto_centerline.py +0 -0
  50. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/parallel/parallel_auto_centerline.py +0 -0
  51. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/parallel/parallel_auto_centerline2.py +0 -0
  52. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/parallel/parallel_auto_centerline3.py +0 -0
  53. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/parallel/parallel_auto_centerline4.py +0 -0
  54. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/pipeline/__init__.py +0 -0
  55. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/pipeline/classic.py +0 -0
  56. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/pipeline/config.py +0 -0
  57. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/pipeline/directories.py +0 -0
  58. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/pipeline/plus.py +0 -0
  59. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/pipeline/post.py +0 -0
  60. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/pipeline/single_trace.py +0 -0
  61. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/scripts/__init__.py +0 -0
  62. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/scripts/prepare_casx_dataset.py +0 -0
  63. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/scripts/taubin_smooth.py +0 -0
  64. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/seqseg.py +0 -0
  65. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/seqseg_plus.py +0 -0
  66. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/conftest.py +0 -0
  67. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_api_entry.py +0 -0
  68. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_centerline_get_end_points.py +0 -0
  69. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_centerline_gradient.py +0 -0
  70. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_datasets_paths.py +0 -0
  71. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_nnunet2.py +0 -0
  72. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_nnunet_device.py +0 -0
  73. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_prepare_casx_dataset.py +0 -0
  74. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_resample_spacing.py +0 -0
  75. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_segmentation.py +0 -0
  76. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_single_trace_staging.py +0 -0
  77. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_start_seg.py +0 -0
  78. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_user_paths.py +0 -0
  79. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/tests/test_vtk.py +0 -0
  80. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg/user_paths.py +0 -0
  81. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg.egg-info/dependency_links.txt +0 -0
  82. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg.egg-info/entry_points.txt +0 -0
  83. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg.egg-info/requires.txt +0 -0
  84. {seqseg-2.1.7 → seqseg-2.1.9}/seqseg.egg-info/top_level.txt +0 -0
  85. {seqseg-2.1.7 → seqseg-2.1.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seqseg
3
- Version: 2.1.7
3
+ Version: 2.1.9
4
4
  Summary: A deep learning-based medical image blood vessel tracking and segmentation tool.
5
5
  Author-email: Numi Sveinsson Cepero <numi@berkeley.com>
6
6
  License: Apache-2.0
@@ -30,8 +30,8 @@ Provides-Extra: viz
30
30
  Requires-Dist: matplotlib>=3.5; extra == "viz"
31
31
  Dynamic: license-file
32
32
 
33
- ![example workflow](https://github.com/numisveinsson/SeqSeg/actions/workflows/python-app.yml/badge.svg)
34
- ![example workflow](https://github.com/numisveinsson/SeqSeg/actions/workflows/test.yml/badge.svg)
33
+ ![test seqseg without torch build](https://github.com/numisveinsson/SeqSeg/actions/workflows/python-app.yml/badge.svg)
34
+ ![test seqseg build](https://github.com/numisveinsson/SeqSeg/actions/workflows/test.yml/badge.svg)
35
35
 
36
36
  <p align="center">
37
37
  <img src="https://raw.githubusercontent.com/numisveinsson/SeqSeg/main/seqseg/assets/seqseg_logo.png" alt="SeqSeg — Sequential Vessel Segmentation" width="480"/><br/>
@@ -50,10 +50,6 @@ Dynamic: license-file
50
50
  <a href="https://www.python.org"><img src="https://img.shields.io/badge/Python-3.9%2B-blue.svg" alt="Python 3.9+"/></a>
51
51
  </p>
52
52
 
53
- > **News:** SeqSeg writes a full SimVascular project in the `simvascular/` subdirectory — open it directly in SimVascular with pathlines and contours for every segmented branch.
54
-
55
- ---
56
-
57
53
  ## Why SeqSeg?
58
54
 
59
55
  SeqSeg segments vessels **sequentially**, taking steps along vessel centerlines and detecting bifurcations to grow complete vascular trees from just **1–2 seed points**. By combining local deep-learning predictions (nnU-Net) with geometric tracking, it stays robust across vessel scales — from small coronaries to large aortas.
@@ -65,57 +61,38 @@ SeqSeg segments vessels **sequentially**, taking steps along vessel centerlines
65
61
  - ✅ **Clinically validated** — coronary, aortic, cerebral, and pulmonary anatomies (pre-trained weights for aorta CT/MR and coronary CT)
66
62
  - ⚡ **Fast** — on CPU, about 2–5 minutes for an aorta, 5–15 for a coronary tree, and 10–30 for a cerebral tree. GPU is faster. Details in [Benchmarks](docs/benchmarks.md).
67
63
 
64
+ A finished case includes a `simvascular/` project you can open directly in SimVascular, with pathlines and contours for every segmented branch.
65
+
68
66
  <p align="center">
69
67
  <img src="https://raw.githubusercontent.com/numisveinsson/SeqSeg/main/seqseg/assets/mr_model_tracing_fast_shorter.gif" alt="SeqSeg Demo"/><br/>
70
68
  <i>Real-time demonstration: automatic segmentation of an abdominal aorta in a 3D MR scan.</i>
71
69
  </p>
72
70
 
73
- ## What's new in 2.x
74
-
75
- SeqSeg **2.0** uses subcommands. A command from 1.x that starts with a flag, such as `seqseg -data_dir ...`, still runs: it is rewritten to `seqseg run batch` automatically.
76
-
77
- ### Commands
78
-
79
- | Command | Description |
80
- | -------- | ----------- |
81
- | **`seqseg run batch`** | Dataset batch tracing |
82
- | **`seqseg run single`** | One volume and seeds. Stages files under `<outdir>/_seqseg_single_staging/`, then traces |
83
- | **`seqseg run plus batch`** | Global nnU-Net sweep, then SeqSeg |
84
- | **`seqseg init dataset`** | Create `images/`, `centerlines/`, `truths/`, and a template `seeds.json` |
85
- | **`seqseg paths init` / `set` / `show`** | Save default nnU-Net, data, and output directories in `~/.seqseg/paths.yaml` |
86
- | **`seqseg train prepare`** | Extract patches or whole volumes and build an nnU-Net dataset |
87
- | **`seqseg train nnunet`** | Run nnU-Net planning, preprocessing, and training |
88
- | **`seqseg doctor`** | Check imports (SimpleITK, vtk, nnunetv2, scipy, vascular-segment-sampler) and paths |
89
- | **`seqseg config dump` / `fingerprint`** | Print a packaged YAML config, or list keys that differ from another |
90
- | **`seqseg post global-centerline`** | Build global centerlines from existing segmentations |
91
- | **`seqseg simvascular init`** | Create or refresh a SimVascular project under a case directory |
92
- | **`seqseg --version`** | Print the installed version |
93
-
94
- `seqseg run batch` flags use underscores (`-outdir` / `--outdir`). The data folder is `-data_dir` or `--data_directory`. `seqseg run single`, `seqseg train`, and `seqseg paths` use hyphens (`--image`, `--data-dir`).
71
+ ## Start here
95
72
 
96
- ### Migrating from 1.x
97
-
98
- 1. Prefer `seqseg run batch`. The old flag-only form still works.
99
- 2. Use `seqseg run plus batch` in place of `python -m seqseg.seqseg_plus`. The nnU-Net path flags are unchanged.
100
- 3. Check the install with `seqseg --version` after `pip install -U seqseg`.
73
+ - **First segmentation** — the [tutorial](seqseg/tutorial/tutorial.md) runs a sample abdominal-aorta MR scan.
74
+ - **Your own images** — [Usage](docs/usage.md) describes `images/` and `seeds.json`. The command is in [Quick Start](#quick-start).
75
+ - **Train a model** — [Training](docs/training.md).
101
76
 
102
77
  ## Quick Start
103
78
 
104
79
  Python 3.9 or newer. 3.11 is the version used in the tutorial and conda example.
105
80
 
81
+ The [tutorial](seqseg/tutorial/tutorial.md) is the sample run (abdominal-aorta MR, seed points, and Windows notes). Plan on 15–20 minutes including setup. Tracing is capped, so that part finishes in a few minutes; a real case should use the defaults.
82
+
83
+ Install SeqSeg and the aorta weights below, then run `seqseg run batch` on your own data. The folder needs `images/` and `seeds.json` ([Usage](docs/usage.md)). This example is an aorta MR case. Change `-train_dataset`, `-config_name`, and `-img_ext` to match your images and weights.
84
+
106
85
  ```bash
107
86
  pip install seqseg
108
87
 
109
- # Aorta and femoral weights (MR and CT). Coronary weights are a separate zip; see Installation.
88
+ # Aorta and femoral weights (MR and CT)
110
89
  curl -L -o nnUNet_results.zip https://zenodo.org/records/15020477/files/nnUNet_results.zip
111
90
  unzip nnUNet_results.zip
112
91
  ```
113
92
 
114
- `-nnunet_results_path` is the folder that contains `Dataset005_SEQAORTANDFEMOMR` (and the other dataset folders in that zip).
115
-
116
- **First run:** follow the [step-by-step tutorial](seqseg/tutorial/tutorial.md). It includes an abdominal-aorta MR scan, seed points, and Windows notes. The tutorial caps the number of steps so the example finishes in a few minutes; a real case should use the defaults.
93
+ [Aorta and femoral weights](https://zenodo.org/records/15020477) (`nnUNet_results.zip`). [Coronary CT weights](https://zenodo.org/records/19547894) are a separate archive, `nnUNet_results_coronary.zip`.
117
94
 
118
- **Your own data** needs `images/` and `seeds.json` (see [Usage](docs/usage.md)). This command is an aorta MR case. Change `-train_dataset`, `-config_name`, and `-img_ext` to match your images and weights:
95
+ `-nnunet_results_path` is the folder that contains `Dataset005_SEQAORTANDFEMOMR` (and the other dataset folders in that zip).
119
96
 
120
97
  ```bash
121
98
  seqseg run batch \
@@ -133,21 +110,21 @@ seqseg run batch \
133
110
  | Aorta / femoral CT | `Dataset006_SEQAORTANDFEMOCT` | `global_aorta` |
134
111
  | Coronary CT | `Dataset010_SEQCOROASOCACT` | `global_coro` |
135
112
 
136
- The tutorial uses `-config_name aorta_tutorial` for its sample scan. Download links and the trainer-folder layout are in [Installation](docs/installation.md).
113
+ The tutorial uses `-config_name aorta_tutorial` for its sample scan. The trainer-folder layout is in [Installation](docs/installation.md). The full command list and flag spelling are in [Usage](docs/usage.md).
137
114
 
138
115
  ## Documentation
139
116
 
140
- | Guide | Description |
141
- |-------|-------------|
142
- | [Installation](docs/installation.md) | Setup, dependencies, and pre-trained model weights |
143
- | [Tutorial](seqseg/tutorial/tutorial.md) | End-to-end aorta example with sample data |
144
- | [Usage](docs/usage.md) | Data layout, seeds, CLI arguments, and output files |
145
- | [Configuration](docs/configuration.md) | YAML configs and tracking parameters |
146
- | [Algorithm Overview](docs/algorithm.md) | Methodology, workflow, and training strategy |
147
- | [Training](docs/training.md) | Train nnU-Net models on a new dataset |
148
- | [Performance & Benchmarks](docs/benchmarks.md) | Accuracy, timing, and qualitative comparisons |
149
- | [API](docs/api.md) | Call tracing from Python |
150
- | [Research & Development](docs/development.md) | SimVascular, 3D Slicer, and repository layout |
117
+ | Job | Guide | Description |
118
+ |-----|-------|-------------|
119
+ | Run | [Installation](docs/installation.md) | Setup, dependencies, and pre-trained model weights |
120
+ | Run | [Tutorial](seqseg/tutorial/tutorial.md) | End-to-end aorta example with sample data |
121
+ | Run | [Usage](docs/usage.md) | Data layout, seeds, CLI arguments, and output files |
122
+ | Run | [Configuration](docs/configuration.md) | YAML configs and tracking parameters |
123
+ | Train | [Training](docs/training.md) | Train nnU-Net models on a new dataset |
124
+ | Understand | [Algorithm Overview](docs/algorithm.md) | Methodology, workflow, and training strategy |
125
+ | Understand | [Performance & Benchmarks](docs/benchmarks.md) | Accuracy, timing, and qualitative comparisons |
126
+ | Integrate | [API](docs/api.md) | Call tracing from Python |
127
+ | Integrate | [Research & Development](docs/development.md) | SimVascular, 3D Slicer, and repository layout |
151
128
 
152
129
  ## Citation
153
130
 
seqseg-2.1.9/README.md ADDED
@@ -0,0 +1,114 @@
1
+ ![test seqseg without torch build](https://github.com/numisveinsson/SeqSeg/actions/workflows/python-app.yml/badge.svg)
2
+ ![test seqseg build](https://github.com/numisveinsson/SeqSeg/actions/workflows/test.yml/badge.svg)
3
+
4
+ <p align="center">
5
+ <img src="https://raw.githubusercontent.com/numisveinsson/SeqSeg/main/seqseg/assets/seqseg_logo.png" alt="SeqSeg — Sequential Vessel Segmentation" width="480"/><br/>
6
+ <img src="https://raw.githubusercontent.com/numisveinsson/SeqSeg/main/seqseg/assets/coronary.png" alt="Example coronary segmentation (SeqSeg)" width="260"/>
7
+ </p>
8
+
9
+ <h1 align="center">SeqSeg: Sequential Vessel Segmentation and Tracking</h1>
10
+
11
+ <p align="center">
12
+ <b>Automatic tracking and segmentation of blood vessels in CT and MR images using deep learning and geometric tracking.</b>
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="https://rdcu.be/dU0wy"><img src="https://img.shields.io/badge/Paper-Annals%20of%20BME-blue" alt="Paper"/></a>
17
+ <a href="https://github.com/numisveinsson/SeqSeg/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-green.svg" alt="License"/></a>
18
+ <a href="https://www.python.org"><img src="https://img.shields.io/badge/Python-3.9%2B-blue.svg" alt="Python 3.9+"/></a>
19
+ </p>
20
+
21
+ ## Why SeqSeg?
22
+
23
+ SeqSeg segments vessels **sequentially**, taking steps along vessel centerlines and detecting bifurcations to grow complete vascular trees from just **1–2 seed points**. By combining local deep-learning predictions (nnU-Net) with geometric tracking, it stays robust across vessel scales — from small coronaries to large aortas.
24
+
25
+ - 🌱 **Minimal supervision** — only 1–2 seed points to initialize
26
+ - 🌿 **Robust bifurcation detection** — automatically follows every branch
27
+ - 🩻 **Multi-modal** — works with CT and MR 3D medical images
28
+ - 📏 **Scalable** — vessels from ~1mm coronaries to ~30mm aortas
29
+ - ✅ **Clinically validated** — coronary, aortic, cerebral, and pulmonary anatomies (pre-trained weights for aorta CT/MR and coronary CT)
30
+ - ⚡ **Fast** — on CPU, about 2–5 minutes for an aorta, 5–15 for a coronary tree, and 10–30 for a cerebral tree. GPU is faster. Details in [Benchmarks](docs/benchmarks.md).
31
+
32
+ A finished case includes a `simvascular/` project you can open directly in SimVascular, with pathlines and contours for every segmented branch.
33
+
34
+ <p align="center">
35
+ <img src="https://raw.githubusercontent.com/numisveinsson/SeqSeg/main/seqseg/assets/mr_model_tracing_fast_shorter.gif" alt="SeqSeg Demo"/><br/>
36
+ <i>Real-time demonstration: automatic segmentation of an abdominal aorta in a 3D MR scan.</i>
37
+ </p>
38
+
39
+ ## Start here
40
+
41
+ - **First segmentation** — the [tutorial](seqseg/tutorial/tutorial.md) runs a sample abdominal-aorta MR scan.
42
+ - **Your own images** — [Usage](docs/usage.md) describes `images/` and `seeds.json`. The command is in [Quick Start](#quick-start).
43
+ - **Train a model** — [Training](docs/training.md).
44
+
45
+ ## Quick Start
46
+
47
+ Python 3.9 or newer. 3.11 is the version used in the tutorial and conda example.
48
+
49
+ The [tutorial](seqseg/tutorial/tutorial.md) is the sample run (abdominal-aorta MR, seed points, and Windows notes). Plan on 15–20 minutes including setup. Tracing is capped, so that part finishes in a few minutes; a real case should use the defaults.
50
+
51
+ Install SeqSeg and the aorta weights below, then run `seqseg run batch` on your own data. The folder needs `images/` and `seeds.json` ([Usage](docs/usage.md)). This example is an aorta MR case. Change `-train_dataset`, `-config_name`, and `-img_ext` to match your images and weights.
52
+
53
+ ```bash
54
+ pip install seqseg
55
+
56
+ # Aorta and femoral weights (MR and CT)
57
+ curl -L -o nnUNet_results.zip https://zenodo.org/records/15020477/files/nnUNet_results.zip
58
+ unzip nnUNet_results.zip
59
+ ```
60
+
61
+ [Aorta and femoral weights](https://zenodo.org/records/15020477) (`nnUNet_results.zip`). [Coronary CT weights](https://zenodo.org/records/19547894) are a separate archive, `nnUNet_results_coronary.zip`.
62
+
63
+ `-nnunet_results_path` is the folder that contains `Dataset005_SEQAORTANDFEMOMR` (and the other dataset folders in that zip).
64
+
65
+ ```bash
66
+ seqseg run batch \
67
+ -data_dir your_data/ \
68
+ -nnunet_results_path nnUNet_results/ \
69
+ -train_dataset Dataset005_SEQAORTANDFEMOMR \
70
+ -config_name global_aorta \
71
+ -img_ext .mha \
72
+ -outdir results/
73
+ ```
74
+
75
+ | Anatomy | `-train_dataset` | `-config_name` |
76
+ |---------|------------------|----------------|
77
+ | Aorta / femoral MR | `Dataset005_SEQAORTANDFEMOMR` | `global_aorta` |
78
+ | Aorta / femoral CT | `Dataset006_SEQAORTANDFEMOCT` | `global_aorta` |
79
+ | Coronary CT | `Dataset010_SEQCOROASOCACT` | `global_coro` |
80
+
81
+ The tutorial uses `-config_name aorta_tutorial` for its sample scan. The trainer-folder layout is in [Installation](docs/installation.md). The full command list and flag spelling are in [Usage](docs/usage.md).
82
+
83
+ ## Documentation
84
+
85
+ | Job | Guide | Description |
86
+ |-----|-------|-------------|
87
+ | Run | [Installation](docs/installation.md) | Setup, dependencies, and pre-trained model weights |
88
+ | Run | [Tutorial](seqseg/tutorial/tutorial.md) | End-to-end aorta example with sample data |
89
+ | Run | [Usage](docs/usage.md) | Data layout, seeds, CLI arguments, and output files |
90
+ | Run | [Configuration](docs/configuration.md) | YAML configs and tracking parameters |
91
+ | Train | [Training](docs/training.md) | Train nnU-Net models on a new dataset |
92
+ | Understand | [Algorithm Overview](docs/algorithm.md) | Methodology, workflow, and training strategy |
93
+ | Understand | [Performance & Benchmarks](docs/benchmarks.md) | Accuracy, timing, and qualitative comparisons |
94
+ | Integrate | [API](docs/api.md) | Call tracing from Python |
95
+ | Integrate | [Research & Development](docs/development.md) | SimVascular, 3D Slicer, and repository layout |
96
+
97
+ ## Citation
98
+
99
+ When using SeqSeg, please cite the following [paper](https://rdcu.be/dU0wy):
100
+
101
+ ```
102
+ @Article{SveinssonCepero2024,
103
+ author={Sveinsson Cepero, Numi
104
+ and Shadden, Shawn C.},
105
+ title={SeqSeg: Learning Local Segments for Automatic Vascular Model Construction},
106
+ journal={Annals of Biomedical Engineering},
107
+ year={2024},
108
+ month={Sep},
109
+ day={18},
110
+ issn={1573-9686},
111
+ doi={10.1007/s10439-024-03611-z},
112
+ url={https://doi.org/10.1007/s10439-024-03611-z},
113
+ }
114
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "seqseg"
7
- version = "2.1.7"
7
+ version = "2.1.9"
8
8
  description = "A deep learning-based medical image blood vessel tracking and segmentation tool."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -437,7 +437,7 @@ def _cmd_init_dataset(ns: argparse.Namespace) -> None:
437
437
  "-nnunet_results_path ...\n"
438
438
  "\nNext (train a new model): also fill centerlines/ and truths/, then:\n"
439
439
  f" seqseg paths init --data-dir {root}/ --outdir ~/seqseg_train\n"
440
- " seqseg train prepare --name MYDATA --dataset-number 999 --modality CT\n"
440
+ " seqseg train prepare --name MYDATA --dataset-id 999 --modality CT\n"
441
441
  )
442
442
 
443
443
 
@@ -634,6 +634,12 @@ def _cmd_paths_export(_ns: argparse.Namespace) -> None:
634
634
 
635
635
 
636
636
  def _cmd_train_prepare(ns: argparse.Namespace) -> None:
637
+ if not ns.preprocess_only and not ns.name:
638
+ print(
639
+ "seqseg train prepare: --name is required",
640
+ file=sys.stderr,
641
+ )
642
+ sys.exit(2)
637
643
  try:
638
644
  result = prepare_training_dataset(
639
645
  data_dir=ns.data_dir,
@@ -656,11 +662,14 @@ def _cmd_train_prepare(ns: argparse.Namespace) -> None:
656
662
  skip_sample=ns.skip_sample,
657
663
  skip_convert=ns.skip_convert,
658
664
  also_test=ns.also_test,
659
- yes=ns.yes,
660
665
  verbose=ns.verbose,
661
666
  img_ext=ns.img_ext,
662
667
  global_volumes=ns.global_volumes,
663
668
  keep_extracted=ns.keep_extracted,
669
+ configuration=ns.configuration,
670
+ plans=ns.plans,
671
+ np=ns.np,
672
+ preprocess_only=ns.preprocess_only,
664
673
  )
665
674
  except TrainDependencyError as e:
666
675
  print(str(e), file=sys.stderr)
@@ -670,6 +679,9 @@ def _cmd_train_prepare(ns: argparse.Namespace) -> None:
670
679
  if message.startswith("Sampler config"):
671
680
  print(message, file=sys.stderr)
672
681
  sys.exit(2)
682
+ if "seqseg train prepare" in message:
683
+ print(message, file=sys.stderr)
684
+ sys.exit(1)
673
685
  import traceback
674
686
 
675
687
  traceback.print_exc()
@@ -691,17 +703,30 @@ def _cmd_train_prepare(ns: argparse.Namespace) -> None:
691
703
  sys.exit(1)
692
704
 
693
705
  print("\nPrepare complete.")
694
- if result.removed_staging:
706
+ if ns.preprocess_only:
695
707
  print(
696
- " Extracted volumes were linked into nnU-Net raw and the "
697
- "temporary folder was removed."
708
+ f" Preprocessed {ns.configuration} from the existing raw dataset."
709
+ )
710
+ elif result.removed_staging:
711
+ print(
712
+ " Extracted subvolumes were removed after conversion. "
713
+ "done.txt and the .txt/.csv metadata were kept in\n"
714
+ f" {result.extracted_dir}"
698
715
  )
699
716
  else:
700
717
  kind = "Whole volumes" if ns.global_volumes else "Patches"
701
718
  print(f" {kind}: {result.extracted_dir}")
719
+ if not result.preprocessed:
720
+ print(
721
+ " nnU-Net preprocessing was skipped (--skip-convert). "
722
+ "Re-run prepare without --skip-convert before training."
723
+ )
702
724
  results = resolve_path("nnunet_results")
703
725
  if results:
704
726
  print(f" Trained models will be written under: {results}")
727
+ config_flag = ""
728
+ if ns.configuration != "3d_fullres":
729
+ config_flag = f" --configuration {ns.configuration}"
705
730
  for name, path in zip(result.dataset_names, result.dataset_dirs):
706
731
  try:
707
732
  ds_id = dataset_id_from_name(name)
@@ -709,11 +734,12 @@ def _cmd_train_prepare(ns: argparse.Namespace) -> None:
709
734
  ds_id = "?"
710
735
  print(f" nnU-Net dataset: {name}")
711
736
  print(f" path: {path}")
712
- print(
713
- " next:\n"
714
- f" seqseg train nnunet --dataset-id {ds_id} "
715
- "--configuration 3d_fullres --fold 0"
716
- )
737
+ if result.preprocessed:
738
+ print(
739
+ " next:\n"
740
+ f" seqseg train nnunet --dataset-id {ds_id}"
741
+ f"{config_flag} --fold 0"
742
+ )
717
743
 
718
744
 
719
745
  def _cmd_train_nnunet(ns: argparse.Namespace) -> None:
@@ -736,12 +762,10 @@ def _cmd_train_nnunet(ns: argparse.Namespace) -> None:
736
762
  dataset_id,
737
763
  configuration=ns.configuration,
738
764
  fold=ns.fold,
739
- skip_plan=ns.skip_plan,
740
- plan_only=ns.plan_only,
741
- np=ns.np,
742
765
  trainer=ns.trainer,
743
766
  plans=ns.plans,
744
767
  cleanup=ns.cleanup,
768
+ continue_training=ns.continue_training,
745
769
  )
746
770
  except Exception as e: # noqa: BLE001
747
771
  print(f"seqseg train nnunet failed: {e}", file=sys.stderr)
@@ -1021,7 +1045,7 @@ def _build_parser() -> argparse.ArgumentParser:
1021
1045
 
1022
1046
  p_prep = train_sub.add_parser(
1023
1047
  "prepare",
1024
- help="Prepare patches or whole volumes and convert to nnU-Net DatasetXXX format",
1048
+ help="Extract volumes, write an nnU-Net dataset, and plan/preprocess",
1025
1049
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
1026
1050
  )
1027
1051
  p_prep.add_argument(
@@ -1038,22 +1062,27 @@ def _build_parser() -> argparse.ArgumentParser:
1038
1062
  default=None,
1039
1063
  type=str,
1040
1064
  help="Optional scratch directory for extracted patches or whole volumes. "
1041
- "Default: <parent of nnUNet_raw>/_seqseg_extracted, removed after "
1042
- "conversion. Pass this to keep extracts or resume with --skip-sample.",
1065
+ "Default: nnUNet_raw/DatasetXXX_*/_seqseg_extracted. Subvolumes are "
1066
+ "removed after conversion; done.txt and .txt/.csv metadata are kept. "
1067
+ "Pass this to keep a copy elsewhere.",
1043
1068
  )
1044
1069
  p_prep.add_argument(
1045
1070
  "--name",
1046
1071
  "-name",
1047
- required=True,
1072
+ default=None,
1048
1073
  type=str,
1049
- help="Dataset name stem (e.g. MYDATA → Dataset0XX_MYDATACT)",
1074
+ help="Dataset name stem (e.g. MYDATA → Dataset0XX_MYDATACT). "
1075
+ "Required unless --preprocess-only",
1050
1076
  )
1051
1077
  p_prep.add_argument(
1078
+ "--dataset-id",
1052
1079
  "--dataset-number",
1053
1080
  "-dataset_number",
1081
+ dest="dataset_number",
1054
1082
  required=True,
1055
1083
  type=int,
1056
- help="nnU-Net dataset id (e.g. 999). Incremented per modality if several",
1084
+ help="nnU-Net dataset id to create (e.g. 999). "
1085
+ "Incremented per modality if several",
1057
1086
  )
1058
1087
  p_prep.add_argument(
1059
1088
  "--modality",
@@ -1065,7 +1094,7 @@ def _build_parser() -> argparse.ArgumentParser:
1065
1094
  p_prep.add_argument(
1066
1095
  "--config-name",
1067
1096
  "-config_name",
1068
- default="global",
1097
+ default="default_mm",
1069
1098
  type=str,
1070
1099
  help="Sampler YAML name/path (from vascular-segment-sampler)",
1071
1100
  )
@@ -1125,18 +1154,43 @@ def _build_parser() -> argparse.ArgumentParser:
1125
1154
  action="store_true",
1126
1155
  help="Also convert modality_test folders when present",
1127
1156
  )
1128
- p_prep.add_argument("--yes", action="store_true", help="Non-interactive confirms")
1129
1157
  p_prep.add_argument("--verbose", action="store_true")
1130
1158
  p_prep.add_argument(
1131
1159
  "--keep-extracted",
1132
1160
  action="store_true",
1133
- help="Keep <nnunet-root>/_seqseg_extracted after conversion into nnUNet_raw",
1161
+ help="Keep extracted subvolumes in nnUNet_raw/DatasetXXX_*/_seqseg_extracted "
1162
+ "after conversion into nnUNet_raw",
1163
+ )
1164
+ p_prep.add_argument(
1165
+ "--configuration",
1166
+ default="3d_fullres",
1167
+ choices=["3d_fullres", "2d", "3d_lowres", "3d_cascade_fullres"],
1168
+ help="nnU-Net configuration to preprocess. Default 3d_fullres",
1169
+ )
1170
+ p_prep.add_argument(
1171
+ "--plans",
1172
+ default="nnUNetPlans",
1173
+ type=str,
1174
+ help="nnU-Net plans name (training -p). Default nnUNetPlans",
1175
+ )
1176
+ p_prep.add_argument(
1177
+ "--np",
1178
+ type=int,
1179
+ default=None,
1180
+ help="Processes for nnU-Net preprocessing. Default: --num-cores",
1181
+ )
1182
+ p_prep.add_argument(
1183
+ "--preprocess-only",
1184
+ action="store_true",
1185
+ help="Use an existing nnUNet_raw dataset. Plans and preprocesses it "
1186
+ "when plans are missing; otherwise resamples --configuration. "
1187
+ "Does not extract or convert subvolumes",
1134
1188
  )
1135
1189
  p_prep.set_defaults(_handler=_cmd_train_prepare, img_ext=None)
1136
1190
 
1137
1191
  p_nn = train_sub.add_parser(
1138
1192
  "nnunet",
1139
- help="Run nnUNetv2_plan_and_preprocess and/or nnUNetv2_train",
1193
+ help="Train with nnUNetv2_train (--c continues from the latest checkpoint)",
1140
1194
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
1141
1195
  )
1142
1196
  p_nn.add_argument(
@@ -1149,13 +1203,13 @@ def _build_parser() -> argparse.ArgumentParser:
1149
1203
  "--dataset-name",
1150
1204
  type=str,
1151
1205
  default=None,
1152
- help="Alternative to --dataset-id (e.g. Dataset0999_MYDATACT)",
1206
+ help="Alternative to --dataset-id (e.g. Dataset999_MYDATACT)",
1153
1207
  )
1154
1208
  p_nn.add_argument(
1155
1209
  "--configuration",
1156
1210
  default="3d_fullres",
1157
1211
  choices=["3d_fullres", "2d", "3d_lowres", "3d_cascade_fullres"],
1158
- help="nnU-Net configuration for preprocessing (-c) and training",
1212
+ help="nnU-Net configuration to train. Default 3d_fullres",
1159
1213
  )
1160
1214
  p_nn.add_argument(
1161
1215
  "--fold",
@@ -1164,20 +1218,11 @@ def _build_parser() -> argparse.ArgumentParser:
1164
1218
  help="Fold to train (0-4 or all)",
1165
1219
  )
1166
1220
  p_nn.add_argument(
1167
- "--skip-plan",
1168
- action="store_true",
1169
- help="Skip plan_and_preprocess; only train",
1170
- )
1171
- p_nn.add_argument(
1172
- "--plan-only",
1221
+ "--c",
1222
+ "--continue",
1223
+ dest="continue_training",
1173
1224
  action="store_true",
1174
- help="Only run plan_and_preprocess",
1175
- )
1176
- p_nn.add_argument(
1177
- "--np",
1178
- type=int,
1179
- default=None,
1180
- help="Processes for plan_and_preprocess (-np)",
1225
+ help="Continue training from checkpoint_latest.pth (nnU-Net --c)",
1181
1226
  )
1182
1227
  p_nn.add_argument("--trainer", default="nnUNetTrainer", type=str)
1183
1228
  p_nn.add_argument("--plans", default="nnUNetPlans", type=str)
@@ -1186,7 +1231,7 @@ def _build_parser() -> argparse.ArgumentParser:
1186
1231
  action="store_true",
1187
1232
  help="After training succeeds, delete this dataset's nnUNet_raw and "
1188
1233
  "nnUNet_preprocessed folders. Leaves nnUNet_results. "
1189
- "Ignored with --plan-only.",
1234
+ "A later --c run needs that preprocessed data.",
1190
1235
  )
1191
1236
  p_nn.set_defaults(_handler=_cmd_train_nnunet)
1192
1237
 
@@ -0,0 +1,108 @@
1
+ """Run an nnU-Net planning or preprocessing entry point with a faster file lookup.
2
+
3
+ ``python -m seqseg.pipeline.nnunet_cli plan_and_preprocess -d 999 ...`` takes the
4
+ same arguments as ``nnUNetv2_plan_and_preprocess``.
5
+
6
+ nnU-Net finds a dataset's training files by matching every case id against
7
+ every name in ``imagesTr``. That is quadratic in the number of cases, and it
8
+ runs again in fingerprinting, planning, preprocessing, and the label copy:
9
+ about an hour each for several hundred thousand patches. Listing the files
10
+ under ``dataset`` in dataset.json avoids it, but nnU-Net sends dataset.json to
11
+ every preprocessing job, so a long list slows preprocessing far more.
12
+
13
+ This module swaps in a single-pass lookup before nnU-Net starts. It returns
14
+ the same mapping as nnU-Net's folder scan. nnU-Net's files are not modified.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ import re
21
+ import sys
22
+ from typing import Callable, Dict, List, Optional
23
+
24
+ _ENTRY_POINTS = {
25
+ "plan_and_preprocess": "plan_and_preprocess_entry",
26
+ "preprocess": "preprocess_entry",
27
+ }
28
+
29
+ _CHANNEL_SUFFIX = re.compile(r"_\d{4}$")
30
+
31
+
32
+ def training_files_from_folder(raw_dataset_folder: str, dataset_json: dict) -> Dict[str, dict]:
33
+ """Same result as nnU-Net's ``imagesTr`` scan, in one directory listing."""
34
+ ending = dataset_json["file_ending"]
35
+ crop = len(ending) + 5
36
+ images_dir = os.path.join(raw_dataset_folder, "imagesTr")
37
+ labels_dir = os.path.join(raw_dataset_folder, "labelsTr")
38
+
39
+ images: Dict[str, List[str]] = {}
40
+ with os.scandir(images_dir) as entries:
41
+ for entry in entries:
42
+ name = entry.name
43
+ if not name.endswith(ending) or not entry.is_file():
44
+ continue
45
+ files = images.setdefault(name[:-crop], [])
46
+ if _CHANNEL_SUFFIX.search(name[: -len(ending)]):
47
+ files.append(name)
48
+
49
+ return {
50
+ case: {
51
+ "images": [os.path.join(images_dir, name) for name in sorted(images[case])],
52
+ "label": os.path.join(labels_dir, case + ending),
53
+ }
54
+ for case in sorted(images)
55
+ }
56
+
57
+
58
+ def _fast_lookup(original: Callable) -> Callable:
59
+ def get_filenames_of_train_images_and_targets(
60
+ raw_dataset_folder: str, dataset_json: Optional[dict] = None
61
+ ):
62
+ if dataset_json is None:
63
+ from batchgenerators.utilities.file_and_folder_operations import load_json
64
+
65
+ dataset_json = load_json(os.path.join(raw_dataset_folder, "dataset.json"))
66
+ if "dataset" in dataset_json:
67
+ return original(raw_dataset_folder, dataset_json)
68
+ return training_files_from_folder(raw_dataset_folder, dataset_json)
69
+
70
+ return get_filenames_of_train_images_and_targets
71
+
72
+
73
+ def install_fast_lookup() -> None:
74
+ """Point every loaded nnU-Net module at the single-pass lookup.
75
+
76
+ Modules bind the function with ``from ... import``, so each one is patched.
77
+ Only the parent process looks up files; spawned workers do not need it.
78
+ """
79
+ import nnunetv2.experiment_planning.plan_and_preprocess_entrypoints # noqa: F401
80
+ import nnunetv2.preprocessing.preprocessors.default_preprocessor # noqa: F401
81
+ import nnunetv2.utilities.utils as nnunet_utils
82
+
83
+ original = nnunet_utils.get_filenames_of_train_images_and_targets
84
+ fast = _fast_lookup(original)
85
+ for name, module in list(sys.modules.items()):
86
+ if not name.startswith("nnunetv2") or module is None:
87
+ continue
88
+ if getattr(module, "get_filenames_of_train_images_and_targets", None) is original:
89
+ module.get_filenames_of_train_images_and_targets = fast
90
+
91
+
92
+ def main(argv: Optional[List[str]] = None) -> None:
93
+ args = list(sys.argv[1:] if argv is None else argv)
94
+ if not args or args[0] not in _ENTRY_POINTS:
95
+ raise SystemExit(
96
+ "usage: python -m seqseg.pipeline.nnunet_cli "
97
+ f"{{{','.join(_ENTRY_POINTS)}}} [nnU-Net arguments]"
98
+ )
99
+ install_fast_lookup()
100
+ from nnunetv2.experiment_planning import plan_and_preprocess_entrypoints
101
+
102
+ command = args.pop(0)
103
+ sys.argv = [f"nnUNetv2_{command}", *args]
104
+ getattr(plan_and_preprocess_entrypoints, _ENTRY_POINTS[command])()
105
+
106
+
107
+ if __name__ == "__main__":
108
+ main()