caideface 0.3.0__tar.gz → 0.3.2__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.
- {caideface-0.3.0/src/caideface.egg-info → caideface-0.3.2}/PKG-INFO +5 -5
- {caideface-0.3.0 → caideface-0.3.2}/README.md +1 -1
- {caideface-0.3.0 → caideface-0.3.2}/pyproject.toml +5 -2
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/__init__.py +3 -1
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/cli.py +11 -4
- caideface-0.3.2/src/caideface/data/ct_face_mask.nii.gz +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/pipeline.py +15 -7
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/register.py +54 -13
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/reorient.py +22 -10
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/skull_strip.py +135 -34
- {caideface-0.3.0 → caideface-0.3.2/src/caideface.egg-info}/PKG-INFO +5 -5
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface.egg-info/SOURCES.txt +1 -1
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface.egg-info/requires.txt +3 -0
- caideface-0.3.0/LICENSE.md +0 -21
- {caideface-0.3.0 → caideface-0.3.2}/setup.cfg +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/anonymize.py +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/background.py +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/mni_icbm152_t1_tal_nlin_sym_55_ext_brain_only.nii.gz +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/config.cfg +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/meta.json +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/ner/cfg +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/ner/model +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/ner/moves +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/tok2vec/cfg +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/tok2vec/model +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/tokenizer +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/vocab/key2row +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/vocab/lookups.bin +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/vocab/strings.json +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/vocab/vectors +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/ner_model/vocab/vectors.cfg +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface/data/t1_mask.nii.gz +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface.egg-info/dependency_links.txt +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface.egg-info/entry_points.txt +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/src/caideface.egg-info/top_level.txt +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/tests/test_anonymize.py +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/tests/test_background.py +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/tests/test_register.py +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/tests/test_reorient.py +0 -0
- {caideface-0.3.0 → caideface-0.3.2}/tests/test_skull_strip.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: caideface
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: MRI defacing pipeline with skull-stripping and affine registration from cai4cai
|
|
5
5
|
Author-email: Lorena Garcia-Foncillas <lorenagarfon00@gmail.com>
|
|
6
|
-
License:
|
|
6
|
+
License: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/cai4cai/defacing_pipeline
|
|
8
8
|
Project-URL: Repository, https://github.com/cai4cai/defacing_pipeline
|
|
9
9
|
Keywords: MRI,defacing,anonymisation,skull-stripping,neuroimaging,NER,text-anonymization
|
|
@@ -13,7 +13,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
14
14
|
Requires-Python: >=3.9
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE.md
|
|
17
16
|
Requires-Dist: nibabel>=4.0
|
|
18
17
|
Requires-Dist: numpy<2,>=1.22
|
|
19
18
|
Requires-Dist: scipy>=1.9
|
|
@@ -27,7 +26,8 @@ Requires-Dist: faker>=18.0
|
|
|
27
26
|
Provides-Extra: dev
|
|
28
27
|
Requires-Dist: pytest; extra == "dev"
|
|
29
28
|
Requires-Dist: ruff; extra == "dev"
|
|
30
|
-
|
|
29
|
+
Provides-Extra: ct
|
|
30
|
+
Requires-Dist: TotalSegmentator; extra == "ct"
|
|
31
31
|
|
|
32
32
|
# caideface
|
|
33
33
|
|
|
@@ -330,4 +330,4 @@ If you use the text anonymisation (NER + HIPS), please also cite:
|
|
|
330
330
|
|
|
331
331
|
## License
|
|
332
332
|
|
|
333
|
-
This project is licensed under the
|
|
333
|
+
This project is licensed under the Apache License 2.0 -- see the [LICENSE](https://github.com/cai4cai/defacing_pipeline/blob/main/LICENSE) file for details.
|
|
@@ -299,4 +299,4 @@ If you use the text anonymisation (NER + HIPS), please also cite:
|
|
|
299
299
|
|
|
300
300
|
## License
|
|
301
301
|
|
|
302
|
-
This project is licensed under the
|
|
302
|
+
This project is licensed under the Apache License 2.0 -- see the [LICENSE](https://github.com/cai4cai/defacing_pipeline/blob/main/LICENSE) file for details.
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "caideface"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "MRI defacing pipeline with skull-stripping and affine registration from cai4cai"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
license = {text = "
|
|
10
|
+
license = {text = "Apache-2.0"}
|
|
11
11
|
requires-python = ">=3.9"
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "Lorena Garcia-Foncillas", email = "lorenagarfon00@gmail.com"},
|
|
@@ -38,6 +38,9 @@ dev = [
|
|
|
38
38
|
"pytest",
|
|
39
39
|
"ruff",
|
|
40
40
|
]
|
|
41
|
+
ct = [
|
|
42
|
+
"TotalSegmentator",
|
|
43
|
+
]
|
|
41
44
|
|
|
42
45
|
[project.scripts]
|
|
43
46
|
caideface = "caideface.cli:main"
|
|
@@ -8,13 +8,14 @@ A three-step pipeline for anonymising head MRI scans:
|
|
|
8
8
|
Plus standalone text anonymisation via NER + HIPS (Hiding in Plain Sight).
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
__version__ = "0.3.
|
|
11
|
+
__version__ = "0.3.2"
|
|
12
12
|
|
|
13
13
|
from .pipeline import DefacePipeline
|
|
14
14
|
from .reorient import reorient_batch, reorient_single
|
|
15
15
|
from .skull_strip import skull_strip_batch, skull_strip_single
|
|
16
16
|
from .register import deface_batch, deface_single
|
|
17
17
|
from .anonymize import anonymize_batch, anonymize_single, default_ner_model_path
|
|
18
|
+
from .background import detect_background_value
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
20
21
|
"DefacePipeline",
|
|
@@ -27,4 +28,5 @@ __all__ = [
|
|
|
27
28
|
"anonymize_batch",
|
|
28
29
|
"anonymize_single",
|
|
29
30
|
"default_ner_model_path",
|
|
31
|
+
"detect_background_value",
|
|
30
32
|
]
|
|
@@ -37,25 +37,28 @@ def main():
|
|
|
37
37
|
run_parser = subparsers.add_parser("run", help="Run the full defacing pipeline", parents=[parent])
|
|
38
38
|
run_parser.add_argument("input_dir", help="Directory containing raw NIfTI files")
|
|
39
39
|
run_parser.add_argument("output_dir", help="Root output directory")
|
|
40
|
+
run_parser.add_argument("--modality", required=True, choices=["mri", "ct"], help="Image modality (required)")
|
|
40
41
|
run_parser.add_argument("--brainsfit", required=True, help="Path to BRAINSFit executable")
|
|
41
42
|
run_parser.add_argument("--brainsresample", required=True, help="Path to BRAINSResample executable")
|
|
42
43
|
run_parser.add_argument("--device", default=None, choices=["cpu", "cuda"], help="Device for HD-BET (auto-detected if omitted)")
|
|
43
44
|
run_parser.add_argument("--no-tta", action="store_true", default=True, help="Disable HD-BET test-time augmentation (default: disabled)")
|
|
44
45
|
run_parser.add_argument("--dilation-mm", type=float, default=14.0, help="Brain mask dilation in mm (default: 14)")
|
|
45
|
-
run_parser.add_argument("--background", type=float, default=
|
|
46
|
+
run_parser.add_argument("--background", type=float, default=None, help="Background value for defaced voxels (auto-detected per volume if omitted)")
|
|
46
47
|
run_parser.add_argument("--template", default=None, help="Custom MNI152 skull-stripped template (uses bundled if omitted)")
|
|
47
48
|
run_parser.add_argument("--face-mask", default=None, help="Custom face mask in MNI152 space (uses bundled if omitted)")
|
|
48
49
|
run_parser.add_argument("--steps", default="all", help="Steps to run: all, or comma-separated: reorient,skull_strip,deface")
|
|
49
50
|
|
|
50
51
|
# --- reorient ---
|
|
51
|
-
reorient_parser = subparsers.add_parser("reorient", help="Step 1: Reorient NIfTI scans
|
|
52
|
+
reorient_parser = subparsers.add_parser("reorient", help="Step 1: Reorient NIfTI scans", parents=[parent])
|
|
52
53
|
reorient_parser.add_argument("input_dir", help="Directory with NIfTI files")
|
|
53
54
|
reorient_parser.add_argument("output_dir", help="Output directory for reoriented files")
|
|
55
|
+
reorient_parser.add_argument("--modality", required=True, choices=["mri", "ct"], help="Image modality (mri→LAS, ct→RAS)")
|
|
54
56
|
|
|
55
57
|
# --- skull-strip ---
|
|
56
58
|
ss_parser = subparsers.add_parser("skull-strip", help="Step 2: Skull-strip with HD-BET", parents=[parent])
|
|
57
59
|
ss_parser.add_argument("input_dir", help="Directory with reoriented NIfTI files")
|
|
58
60
|
ss_parser.add_argument("output_dir", help="Output directory for HD-BET results")
|
|
61
|
+
ss_parser.add_argument("--modality", required=True, choices=["mri", "ct"], help="Image modality (required)")
|
|
59
62
|
ss_parser.add_argument("--device", default=None, choices=["cpu", "cuda"], help="Device for HD-BET")
|
|
60
63
|
ss_parser.add_argument("--no-tta", action="store_true", default=True, help="Disable test-time augmentation")
|
|
61
64
|
ss_parser.add_argument("--dilation-mm", type=float, default=14.0, help="Dilation in mm")
|
|
@@ -65,11 +68,12 @@ def main():
|
|
|
65
68
|
deface_parser.add_argument("reoriented_dir", help="Directory with reoriented scans (Step 1 output)")
|
|
66
69
|
deface_parser.add_argument("hdbet_dir", help="Directory with HD-BET results (Step 2 output)")
|
|
67
70
|
deface_parser.add_argument("output_dir", help="Output directory for defaced scans")
|
|
71
|
+
deface_parser.add_argument("--modality", required=True, choices=["mri", "ct"], help="Image modality (required)")
|
|
68
72
|
deface_parser.add_argument("--brainsfit", required=True, help="Path to BRAINSFit executable")
|
|
69
73
|
deface_parser.add_argument("--brainsresample", required=True, help="Path to BRAINSResample executable")
|
|
70
74
|
deface_parser.add_argument("--template", default=None, help="Custom MNI152 skull-stripped template")
|
|
71
75
|
deface_parser.add_argument("--face-mask", default=None, help="Custom face mask in MNI152 space")
|
|
72
|
-
deface_parser.add_argument("--background", type=float, default=
|
|
76
|
+
deface_parser.add_argument("--background", type=float, default=None, help="Background value (auto-detected per volume if omitted)")
|
|
73
77
|
|
|
74
78
|
# --- anonymize (batch) ---
|
|
75
79
|
anon_parser = subparsers.add_parser("anonymize", help="Anonymize personal names in all .txt files in a directory", parents=[parent])
|
|
@@ -99,6 +103,7 @@ def main():
|
|
|
99
103
|
pipeline = DefacePipeline(
|
|
100
104
|
brainsfit_path=args.brainsfit,
|
|
101
105
|
brainsresample_path=args.brainsresample,
|
|
106
|
+
modality=args.modality,
|
|
102
107
|
device=args.device,
|
|
103
108
|
disable_tta=args.no_tta,
|
|
104
109
|
desired_dilation_mm=args.dilation_mm,
|
|
@@ -113,12 +118,13 @@ def main():
|
|
|
113
118
|
sys.exit(1)
|
|
114
119
|
|
|
115
120
|
elif args.command == "reorient":
|
|
116
|
-
reorient_batch(args.input_dir, args.output_dir)
|
|
121
|
+
reorient_batch(args.input_dir, args.output_dir, modality=args.modality)
|
|
117
122
|
|
|
118
123
|
elif args.command == "skull-strip":
|
|
119
124
|
skull_strip_batch(
|
|
120
125
|
args.input_dir,
|
|
121
126
|
args.output_dir,
|
|
127
|
+
modality=args.modality,
|
|
122
128
|
device=args.device,
|
|
123
129
|
disable_tta=args.no_tta,
|
|
124
130
|
desired_dilation_mm=args.dilation_mm,
|
|
@@ -131,6 +137,7 @@ def main():
|
|
|
131
137
|
output_dir=args.output_dir,
|
|
132
138
|
brainsfit_path=args.brainsfit,
|
|
133
139
|
brainsresample_path=args.brainsresample,
|
|
140
|
+
modality=args.modality,
|
|
134
141
|
target_path=args.template,
|
|
135
142
|
face_mask_path=args.face_mask,
|
|
136
143
|
background_value=args.background,
|
|
Binary file
|
|
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class DefacePipeline:
|
|
16
|
-
"""Orchestrates the three-step
|
|
16
|
+
"""Orchestrates the three-step defacing pipeline.
|
|
17
17
|
|
|
18
18
|
Parameters
|
|
19
19
|
----------
|
|
@@ -21,14 +21,16 @@ class DefacePipeline:
|
|
|
21
21
|
Path to the BRAINSFit executable (from 3D Slicer).
|
|
22
22
|
brainsresample_path : str
|
|
23
23
|
Path to the BRAINSResample executable (from 3D Slicer).
|
|
24
|
+
modality : str
|
|
25
|
+
Image modality: ``'mri'`` or ``'ct'``.
|
|
24
26
|
device : str or None
|
|
25
27
|
'cpu' or 'cuda' for HD-BET. Auto-detected if None.
|
|
26
28
|
disable_tta : bool
|
|
27
29
|
Disable HD-BET test-time augmentation for faster processing.
|
|
28
30
|
desired_dilation_mm : float
|
|
29
31
|
Physical dilation in mm for brain mask expansion.
|
|
30
|
-
background_value : float
|
|
31
|
-
Value for defaced voxels
|
|
32
|
+
background_value : float or None
|
|
33
|
+
Value for defaced voxels. Auto-detected per volume when None.
|
|
32
34
|
target_path : str or None
|
|
33
35
|
Custom MNI152 skull-stripped template. Uses bundled if None.
|
|
34
36
|
face_mask_path : str or None
|
|
@@ -39,15 +41,17 @@ class DefacePipeline:
|
|
|
39
41
|
self,
|
|
40
42
|
brainsfit_path: str,
|
|
41
43
|
brainsresample_path: str,
|
|
44
|
+
modality: str = "mri",
|
|
42
45
|
device: str | None = None,
|
|
43
46
|
disable_tta: bool = True,
|
|
44
47
|
desired_dilation_mm: float = 14.0,
|
|
45
|
-
background_value: float =
|
|
48
|
+
background_value: float | None = None,
|
|
46
49
|
target_path: str | None = None,
|
|
47
50
|
face_mask_path: str | None = None,
|
|
48
51
|
):
|
|
49
52
|
self.brainsfit_path = brainsfit_path
|
|
50
53
|
self.brainsresample_path = brainsresample_path
|
|
54
|
+
self.modality = modality
|
|
51
55
|
self.device = device
|
|
52
56
|
self.disable_tta = disable_tta
|
|
53
57
|
self.desired_dilation_mm = desired_dilation_mm
|
|
@@ -93,9 +97,10 @@ class DefacePipeline:
|
|
|
93
97
|
# Step 1: Reorientation
|
|
94
98
|
if "reorient" in run_steps:
|
|
95
99
|
logger.info("=" * 60)
|
|
96
|
-
|
|
100
|
+
target = "RAS" if self.modality == "ct" else "LAS"
|
|
101
|
+
logger.info("STEP 1: Reorientation to %s", target)
|
|
97
102
|
logger.info("=" * 60)
|
|
98
|
-
reorient_log = reorient_batch(input_dir, reoriented_dir)
|
|
103
|
+
reorient_log = reorient_batch(input_dir, reoriented_dir, modality=self.modality)
|
|
99
104
|
results["reorient_log"] = reorient_log
|
|
100
105
|
else:
|
|
101
106
|
logger.info("Skipping Step 1 (reorientation)")
|
|
@@ -103,11 +108,13 @@ class DefacePipeline:
|
|
|
103
108
|
# Step 2: Skull-stripping
|
|
104
109
|
if "skull_strip" in run_steps:
|
|
105
110
|
logger.info("=" * 60)
|
|
106
|
-
|
|
111
|
+
backend = "TotalSegmentator" if self.modality == "ct" else "HD-BET"
|
|
112
|
+
logger.info("STEP 2: Skull-stripping with %s", backend)
|
|
107
113
|
logger.info("=" * 60)
|
|
108
114
|
skull_strip_log = skull_strip_batch(
|
|
109
115
|
input_dir=reoriented_dir,
|
|
110
116
|
output_dir=hdbet_dir,
|
|
117
|
+
modality=self.modality,
|
|
111
118
|
device=self.device,
|
|
112
119
|
disable_tta=self.disable_tta,
|
|
113
120
|
desired_dilation_mm=self.desired_dilation_mm,
|
|
@@ -127,6 +134,7 @@ class DefacePipeline:
|
|
|
127
134
|
output_dir=defaced_dir,
|
|
128
135
|
brainsfit_path=self.brainsfit_path,
|
|
129
136
|
brainsresample_path=self.brainsresample_path,
|
|
137
|
+
modality=self.modality,
|
|
130
138
|
target_path=self.target_path,
|
|
131
139
|
face_mask_path=self.face_mask_path,
|
|
132
140
|
background_value=self.background_value,
|
|
@@ -11,6 +11,8 @@ import numpy as np
|
|
|
11
11
|
from numpy.linalg import inv
|
|
12
12
|
from natsort import natsorted
|
|
13
13
|
|
|
14
|
+
from .background import detect_background_value
|
|
15
|
+
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
16
18
|
|
|
@@ -23,14 +25,35 @@ def _data_dir() -> str:
|
|
|
23
25
|
return os.path.join(os.path.dirname(__file__), "data")
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
def default_template_path() -> str:
|
|
28
|
+
def default_template_path(modality: str = "mri") -> str:
|
|
29
|
+
"""Return the default template path for the given modality."""
|
|
30
|
+
if modality == "ct":
|
|
31
|
+
return default_ct_template_path()
|
|
27
32
|
return os.path.join(_data_dir(), "mni_icbm152_t1_tal_nlin_sym_55_ext_brain_only.nii.gz")
|
|
28
33
|
|
|
29
34
|
|
|
30
|
-
def default_face_mask_path() -> str:
|
|
35
|
+
def default_face_mask_path(modality: str = "mri") -> str:
|
|
36
|
+
"""Return the default face mask path for the given modality."""
|
|
37
|
+
if modality == "ct":
|
|
38
|
+
return os.path.join(_data_dir(), "ct_face_mask.nii.gz")
|
|
31
39
|
return os.path.join(_data_dir(), "t1_mask.nii.gz")
|
|
32
40
|
|
|
33
41
|
|
|
42
|
+
def default_ct_template_path() -> str:
|
|
43
|
+
"""Return the CT brain atlas from the TotalSegmentator installation."""
|
|
44
|
+
try:
|
|
45
|
+
import totalsegmentator
|
|
46
|
+
ts_dir = os.path.dirname(totalsegmentator.__file__)
|
|
47
|
+
ct_atlas = os.path.join(ts_dir, "resources", "ct_brain_atlas_1mm.nii.gz")
|
|
48
|
+
if os.path.isfile(ct_atlas):
|
|
49
|
+
return ct_atlas
|
|
50
|
+
except ImportError:
|
|
51
|
+
pass
|
|
52
|
+
raise FileNotFoundError(
|
|
53
|
+
"CT brain atlas not found. Install TotalSegmentator with: pip install caideface[ct]"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
34
57
|
# ---------------------------------------------------------------------------
|
|
35
58
|
# Transform I/O (supports both plain 4x4 text and ITK/Slicer formats)
|
|
36
59
|
# ---------------------------------------------------------------------------
|
|
@@ -113,6 +136,7 @@ def run_brainsfit(
|
|
|
113
136
|
output_volume_path: str,
|
|
114
137
|
output_transform_path: str,
|
|
115
138
|
brainsfit_path: str,
|
|
139
|
+
background_value: float = 0,
|
|
116
140
|
) -> int:
|
|
117
141
|
"""Run BRAINSFit affine registration and return the exit code."""
|
|
118
142
|
cmd = (
|
|
@@ -129,7 +153,7 @@ def run_brainsfit(
|
|
|
129
153
|
f'--medianFilterSize 0,0,0 '
|
|
130
154
|
f'--removeIntensityOutliers 0 '
|
|
131
155
|
f'--outputVolumePixelType float '
|
|
132
|
-
f'--backgroundFillValue
|
|
156
|
+
f'--backgroundFillValue {background_value} '
|
|
133
157
|
f'--interpolationMode Linear '
|
|
134
158
|
f'--numberOfIterations 1500 '
|
|
135
159
|
f'--maximumStepLength 0.05 '
|
|
@@ -219,8 +243,9 @@ def deface_single(
|
|
|
219
243
|
face_mask_path: str,
|
|
220
244
|
brainsfit_path: str,
|
|
221
245
|
brainsresample_path: str,
|
|
246
|
+
modality: str = "mri",
|
|
222
247
|
existing_transform: str | None = None,
|
|
223
|
-
background_value: float =
|
|
248
|
+
background_value: float | None = None,
|
|
224
249
|
) -> bool:
|
|
225
250
|
"""Register, warp face mask, and deface a single scan.
|
|
226
251
|
|
|
@@ -236,6 +261,20 @@ def deface_single(
|
|
|
236
261
|
logger.info("Already defaced, skipping: %s", masked_path)
|
|
237
262
|
return True
|
|
238
263
|
|
|
264
|
+
# --- Background value detection (always runs per volume) ---
|
|
265
|
+
floating_nii = nib.load(reoriented_path)
|
|
266
|
+
floating_data = floating_nii.get_fdata()
|
|
267
|
+
|
|
268
|
+
detected_bg = detect_background_value(floating_data, modality)
|
|
269
|
+
if background_value is not None:
|
|
270
|
+
logger.info(
|
|
271
|
+
"Detected background: %.1f, using override: %.1f",
|
|
272
|
+
detected_bg, background_value,
|
|
273
|
+
)
|
|
274
|
+
else:
|
|
275
|
+
background_value = detected_bg
|
|
276
|
+
logger.info("Using detected background: %.1f", background_value)
|
|
277
|
+
|
|
239
278
|
out_affine = os.path.join(results_dir, base.replace(".nii.gz", ".txt"))
|
|
240
279
|
out_resampled = os.path.join(results_dir, base.replace(".nii.gz", "_resampled.nii.gz"))
|
|
241
280
|
|
|
@@ -246,7 +285,8 @@ def deface_single(
|
|
|
246
285
|
else:
|
|
247
286
|
logger.info("Running BRAINSFit registration...")
|
|
248
287
|
exit_code = run_brainsfit(
|
|
249
|
-
target_path, floating_path, out_resampled, out_affine, brainsfit_path
|
|
288
|
+
target_path, floating_path, out_resampled, out_affine, brainsfit_path,
|
|
289
|
+
background_value=background_value,
|
|
250
290
|
)
|
|
251
291
|
if exit_code != 0:
|
|
252
292
|
logger.error("BRAINSFit failed (exit %d) for %s", exit_code, floating_path)
|
|
@@ -267,9 +307,6 @@ def deface_single(
|
|
|
267
307
|
)
|
|
268
308
|
|
|
269
309
|
# --- Combine masks and deface ---
|
|
270
|
-
floating_nii = nib.load(reoriented_path)
|
|
271
|
-
floating_data = floating_nii.get_fdata()
|
|
272
|
-
|
|
273
310
|
face_data = nib.load(face_mask_resampled).get_fdata()
|
|
274
311
|
brain_data = nib.load(brain_mask_path).get_fdata()
|
|
275
312
|
|
|
@@ -291,9 +328,10 @@ def deface_batch(
|
|
|
291
328
|
output_dir: str,
|
|
292
329
|
brainsfit_path: str,
|
|
293
330
|
brainsresample_path: str,
|
|
331
|
+
modality: str = "mri",
|
|
294
332
|
target_path: str | None = None,
|
|
295
333
|
face_mask_path: str | None = None,
|
|
296
|
-
background_value: float =
|
|
334
|
+
background_value: float | None = None,
|
|
297
335
|
) -> list[str]:
|
|
298
336
|
"""Run the full defacing pipeline (Step 3) on all dilated scans.
|
|
299
337
|
|
|
@@ -309,12 +347,14 @@ def deface_batch(
|
|
|
309
347
|
Path to the BRAINSFit executable.
|
|
310
348
|
brainsresample_path : str
|
|
311
349
|
Path to the BRAINSResample executable.
|
|
350
|
+
modality : str
|
|
351
|
+
Image modality: ``'mri'`` or ``'ct'``.
|
|
312
352
|
target_path : str or None
|
|
313
353
|
Path to skull-stripped MNI152 template. Uses bundled if None.
|
|
314
354
|
face_mask_path : str or None
|
|
315
355
|
Path to face mask in MNI152 space. Uses bundled if None.
|
|
316
|
-
background_value : float
|
|
317
|
-
Value to fill defaced regions
|
|
356
|
+
background_value : float or None
|
|
357
|
+
Value to fill defaced regions. Auto-detected per volume when None.
|
|
318
358
|
|
|
319
359
|
Returns
|
|
320
360
|
-------
|
|
@@ -322,9 +362,9 @@ def deface_batch(
|
|
|
322
362
|
Paths of scans that failed to deface.
|
|
323
363
|
"""
|
|
324
364
|
if target_path is None:
|
|
325
|
-
target_path = default_template_path()
|
|
365
|
+
target_path = default_template_path(modality)
|
|
326
366
|
if face_mask_path is None:
|
|
327
|
-
face_mask_path = default_face_mask_path()
|
|
367
|
+
face_mask_path = default_face_mask_path(modality)
|
|
328
368
|
|
|
329
369
|
hdbet_dir = os.path.abspath(hdbet_dir)
|
|
330
370
|
reoriented_dir = os.path.abspath(reoriented_dir)
|
|
@@ -371,6 +411,7 @@ def deface_batch(
|
|
|
371
411
|
face_mask_path=face_mask_path,
|
|
372
412
|
brainsfit_path=brainsfit_path,
|
|
373
413
|
brainsresample_path=brainsresample_path,
|
|
414
|
+
modality=modality,
|
|
374
415
|
existing_transform=existing_transforms.get(fimg),
|
|
375
416
|
background_value=background_value,
|
|
376
417
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Step 1: Reorientation of NIfTI scans
|
|
1
|
+
"""Step 1: Reorientation of NIfTI scans using nibabel."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import logging
|
|
@@ -9,15 +9,18 @@ import pandas as pd
|
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
|
-
# Target
|
|
13
|
-
|
|
12
|
+
# Target orientations per modality
|
|
13
|
+
TARGET_ORIENTATIONS = {
|
|
14
|
+
"mri": ("L", "A", "S"), # LAS — matches fslreorient2std
|
|
15
|
+
"ct": ("R", "A", "S"), # RAS — matches CT brain atlas
|
|
16
|
+
}
|
|
14
17
|
|
|
15
18
|
|
|
16
|
-
def reorient_single(input_file: str, output_file: str) -> bool:
|
|
17
|
-
"""Reorient a single NIfTI file to
|
|
19
|
+
def reorient_single(input_file: str, output_file: str, modality: str = "mri") -> bool:
|
|
20
|
+
"""Reorient a single NIfTI file to the target orientation for *modality*.
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
MRI is reoriented to LAS (matching ``fslreorient2std`` / MNI152).
|
|
23
|
+
CT is reoriented to RAS (matching the CT brain atlas).
|
|
21
24
|
|
|
22
25
|
Parameters
|
|
23
26
|
----------
|
|
@@ -25,6 +28,8 @@ def reorient_single(input_file: str, output_file: str) -> bool:
|
|
|
25
28
|
Path to the input NIfTI (.nii.gz) file.
|
|
26
29
|
output_file : str
|
|
27
30
|
Path where the reoriented file will be saved.
|
|
31
|
+
modality : str
|
|
32
|
+
``'mri'`` (target LAS) or ``'ct'`` (target RAS).
|
|
28
33
|
|
|
29
34
|
Returns
|
|
30
35
|
-------
|
|
@@ -33,10 +38,12 @@ def reorient_single(input_file: str, output_file: str) -> bool:
|
|
|
33
38
|
"""
|
|
34
39
|
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
|
35
40
|
|
|
41
|
+
target = TARGET_ORIENTATIONS.get(modality, TARGET_ORIENTATIONS["mri"])
|
|
42
|
+
|
|
36
43
|
try:
|
|
37
44
|
img = nib.load(input_file)
|
|
38
45
|
orig_ornt = io_orientation(img.affine)
|
|
39
|
-
target_ornt = axcodes2ornt(
|
|
46
|
+
target_ornt = axcodes2ornt(target)
|
|
40
47
|
transform = ornt_transform(orig_ornt, target_ornt)
|
|
41
48
|
reoriented = img.as_reoriented(transform)
|
|
42
49
|
nib.save(reoriented, output_file)
|
|
@@ -47,7 +54,7 @@ def reorient_single(input_file: str, output_file: str) -> bool:
|
|
|
47
54
|
return os.path.exists(output_file)
|
|
48
55
|
|
|
49
56
|
|
|
50
|
-
def reorient_batch(input_dir: str, output_dir: str) -> pd.DataFrame:
|
|
57
|
+
def reorient_batch(input_dir: str, output_dir: str, modality: str = "mri") -> pd.DataFrame:
|
|
51
58
|
"""Reorient all NIfTI files found recursively under *input_dir*.
|
|
52
59
|
|
|
53
60
|
The directory structure is mirrored under *output_dir*.
|
|
@@ -59,6 +66,8 @@ def reorient_batch(input_dir: str, output_dir: str) -> pd.DataFrame:
|
|
|
59
66
|
output_dir : str
|
|
60
67
|
Root directory where reoriented files will be saved,
|
|
61
68
|
preserving the subdirectory structure.
|
|
69
|
+
modality : str
|
|
70
|
+
``'mri'`` (target LAS) or ``'ct'`` (target RAS).
|
|
62
71
|
|
|
63
72
|
Returns
|
|
64
73
|
-------
|
|
@@ -68,6 +77,9 @@ def reorient_batch(input_dir: str, output_dir: str) -> pd.DataFrame:
|
|
|
68
77
|
input_dir = os.path.abspath(input_dir)
|
|
69
78
|
output_dir = os.path.abspath(output_dir)
|
|
70
79
|
|
|
80
|
+
target = TARGET_ORIENTATIONS.get(modality, TARGET_ORIENTATIONS["mri"])
|
|
81
|
+
logger.info("Target orientation: %s (%s)", "".join(target), modality)
|
|
82
|
+
|
|
71
83
|
log_rows = []
|
|
72
84
|
for root, _dirs, files in os.walk(input_dir):
|
|
73
85
|
for fname in files:
|
|
@@ -79,7 +91,7 @@ def reorient_batch(input_dir: str, output_dir: str) -> pd.DataFrame:
|
|
|
79
91
|
out_path = os.path.join(output_dir, rel, fname)
|
|
80
92
|
|
|
81
93
|
logger.info("Reorienting %s", input_path)
|
|
82
|
-
success = reorient_single(input_path, out_path)
|
|
94
|
+
success = reorient_single(input_path, out_path, modality=modality)
|
|
83
95
|
log_rows.append({"input": input_path, "output": out_path, "success": success})
|
|
84
96
|
|
|
85
97
|
if success:
|
|
@@ -82,15 +82,81 @@ def is_valid_3d_volume(filepath: str) -> bool:
|
|
|
82
82
|
return False
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# Brain extraction backends
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
def _extract_brain_hdbet(
|
|
90
|
+
input_file: str, output_file: str, device: str = "cpu", disable_tta: bool = True
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Run HD-BET brain extraction (MRI)."""
|
|
93
|
+
hdbet_bin = _hd_bet_executable()
|
|
94
|
+
cmd = [hdbet_bin, "-i", input_file, "-o", output_file, "-device", device]
|
|
95
|
+
if disable_tta:
|
|
96
|
+
cmd.append("--disable_tta")
|
|
97
|
+
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _extract_brain_totalseg(
|
|
101
|
+
input_file: str, output_file: str, binary_mask_file: str, device: str = "cpu"
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Run TotalSegmentator brain extraction (CT).
|
|
104
|
+
|
|
105
|
+
Saves both a brain-extracted volume (*output_file*) and the raw binary
|
|
106
|
+
brain mask (*binary_mask_file*). The mask is needed because CT brain-
|
|
107
|
+
extracted volumes have negative values, so ``data > 0`` cannot be used
|
|
108
|
+
to recover the mask (unlike MRI).
|
|
109
|
+
|
|
110
|
+
Requires ``pip install caideface[ct]``.
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
from totalsegmentator.python_api import totalsegmentator
|
|
114
|
+
except ImportError:
|
|
115
|
+
raise ImportError(
|
|
116
|
+
"TotalSegmentator is required for CT skull-stripping.\n"
|
|
117
|
+
"Install it with: pip install caideface[ct]"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
ts_device = "cpu" if device == "cpu" else "gpu"
|
|
121
|
+
|
|
122
|
+
input_img = nib.load(input_file)
|
|
123
|
+
brain_mask = totalsegmentator(
|
|
124
|
+
input=input_img,
|
|
125
|
+
task="total",
|
|
126
|
+
roi_subset=["brain"],
|
|
127
|
+
device=ts_device,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Save the raw binary brain mask
|
|
131
|
+
mask_data = brain_mask.get_fdata().astype(np.uint8)
|
|
132
|
+
nib.save(
|
|
133
|
+
nib.Nifti1Image(mask_data, input_img.affine, input_img.header),
|
|
134
|
+
binary_mask_file,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Apply brain mask to get brain-extracted volume
|
|
138
|
+
data = input_img.get_fdata()
|
|
139
|
+
brain_data = data * mask_data
|
|
140
|
+
nib.save(
|
|
141
|
+
nib.Nifti1Image(brain_data, input_img.affine, input_img.header),
|
|
142
|
+
output_file,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
# Single / batch skull-stripping
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
|
|
85
150
|
def skull_strip_single(
|
|
86
151
|
input_file: str,
|
|
87
152
|
input_root: str,
|
|
88
153
|
output_dir: str,
|
|
154
|
+
modality: str = "mri",
|
|
89
155
|
device: str = "cpu",
|
|
90
156
|
disable_tta: bool = True,
|
|
91
157
|
desired_dilation_mm: float = 14.0,
|
|
92
158
|
) -> dict:
|
|
93
|
-
"""
|
|
159
|
+
"""Extract brain, create dilated mask, and apply it to a single NIfTI file.
|
|
94
160
|
|
|
95
161
|
Parameters
|
|
96
162
|
----------
|
|
@@ -100,58 +166,71 @@ def skull_strip_single(
|
|
|
100
166
|
Root input directory (used to compute relative paths).
|
|
101
167
|
output_dir : str
|
|
102
168
|
Root output directory.
|
|
169
|
+
modality : str
|
|
170
|
+
``'mri'`` (uses HD-BET) or ``'ct'`` (uses TotalSegmentator).
|
|
103
171
|
device : str
|
|
104
172
|
'cpu' or 'cuda'.
|
|
105
173
|
disable_tta : bool
|
|
106
|
-
Disable test-time augmentation
|
|
174
|
+
Disable HD-BET test-time augmentation (MRI only).
|
|
107
175
|
desired_dilation_mm : float
|
|
108
176
|
Physical dilation size in mm for mask expansion.
|
|
109
177
|
|
|
110
178
|
Returns
|
|
111
179
|
-------
|
|
112
180
|
dict
|
|
113
|
-
Status dict with keys: input, output,
|
|
181
|
+
Status dict with keys: input, output, brain_extract, mask, dilated.
|
|
114
182
|
"""
|
|
115
183
|
filename = os.path.basename(input_file)
|
|
116
184
|
stem = filename.replace(".nii.gz", "")
|
|
117
185
|
subfolder = os.path.relpath(os.path.dirname(input_file), start=input_root)
|
|
118
186
|
|
|
119
|
-
|
|
187
|
+
brain_file = os.path.join(output_dir, subfolder, f"{stem}_brain.nii.gz")
|
|
120
188
|
mask_file = os.path.join(output_dir, subfolder, f"{stem}_mask.nii.gz")
|
|
121
189
|
dilated_file = os.path.join(output_dir, subfolder, f"{stem}_dilated.nii.gz")
|
|
122
190
|
|
|
123
|
-
os.makedirs(os.path.dirname(
|
|
191
|
+
os.makedirs(os.path.dirname(brain_file), exist_ok=True)
|
|
124
192
|
|
|
125
193
|
status = {
|
|
126
|
-
"
|
|
194
|
+
"brain_extract": "skipped" if os.path.exists(brain_file) else "pending",
|
|
127
195
|
"mask": "skipped" if os.path.exists(mask_file) else "pending",
|
|
128
196
|
"dilated": "skipped" if os.path.exists(dilated_file) else "pending",
|
|
129
197
|
}
|
|
130
198
|
|
|
131
199
|
try:
|
|
132
|
-
# ---
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
200
|
+
# --- Brain extraction (modality-dependent) ---
|
|
201
|
+
# For CT, a separate binary mask file is saved alongside the
|
|
202
|
+
# brain-extracted volume because CT values include negatives,
|
|
203
|
+
# making ``data > 0`` unreliable for mask recovery.
|
|
204
|
+
ct_binary_mask_file = os.path.join(
|
|
205
|
+
output_dir, subfolder, f"{stem}_brain_binary_mask.nii.gz"
|
|
206
|
+
)
|
|
207
|
+
if not os.path.exists(brain_file):
|
|
208
|
+
if modality == "ct":
|
|
209
|
+
logger.info("Running TotalSegmentator brain extraction...")
|
|
210
|
+
_extract_brain_totalseg(input_file, brain_file, ct_binary_mask_file, device)
|
|
211
|
+
else:
|
|
212
|
+
logger.info("Running HD-BET brain extraction...")
|
|
213
|
+
try:
|
|
214
|
+
_extract_brain_hdbet(input_file, brain_file, device, disable_tta)
|
|
215
|
+
except subprocess.CalledProcessError as e:
|
|
216
|
+
logger.error("HD-BET failed for %s: %s", input_file, e.stderr)
|
|
217
|
+
status["brain_extract"] = "failed"
|
|
218
|
+
return {"input": input_file, "output": brain_file, **status, "error": e.stderr}
|
|
219
|
+
status["brain_extract"] = "success"
|
|
145
220
|
|
|
146
221
|
# --- Binary mask + dilation ---
|
|
147
222
|
if not os.path.exists(mask_file):
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
223
|
+
if modality == "ct" and os.path.exists(ct_binary_mask_file):
|
|
224
|
+
# Use the raw TotalSegmentator binary mask directly
|
|
225
|
+
img = nib.load(ct_binary_mask_file)
|
|
226
|
+
binary_volume = (img.get_fdata() > 0).astype(np.uint8)
|
|
227
|
+
else:
|
|
228
|
+
# MRI: threshold the brain-extracted volume
|
|
229
|
+
img = nib.load(brain_file)
|
|
230
|
+
binary_volume = (img.get_fdata() > 0).astype(np.uint8)
|
|
151
231
|
|
|
232
|
+
voxel_sizes = img.header.get_zooms()[:3]
|
|
152
233
|
struct_elem = get_safe_structuring_element(voxel_sizes, desired_dilation_mm)
|
|
153
|
-
|
|
154
|
-
binary_volume = (data > 0).astype(np.uint8)
|
|
155
234
|
dilated_data = binary_dilation(binary_volume, structure=struct_elem)
|
|
156
235
|
|
|
157
236
|
nib.save(nib.Nifti1Image(dilated_data, img.affine, img.header), mask_file)
|
|
@@ -165,7 +244,12 @@ def skull_strip_single(
|
|
|
165
244
|
original_img = nib.load(input_file)
|
|
166
245
|
original_data = original_img.get_fdata()
|
|
167
246
|
|
|
168
|
-
|
|
247
|
+
# Detect background so outside-mask voxels keep the correct
|
|
248
|
+
# intensity (e.g. ~-1000 HU for CT instead of 0).
|
|
249
|
+
from .background import detect_background_value
|
|
250
|
+
bg = detect_background_value(original_data, modality)
|
|
251
|
+
|
|
252
|
+
dilated_volume = np.where(mask_data, original_data, bg)
|
|
169
253
|
nib.save(
|
|
170
254
|
nib.Nifti1Image(dilated_volume, original_img.affine, original_img.header),
|
|
171
255
|
dilated_file,
|
|
@@ -178,18 +262,20 @@ def skull_strip_single(
|
|
|
178
262
|
if status[k] == "pending":
|
|
179
263
|
status[k] = "failed"
|
|
180
264
|
|
|
181
|
-
return {"input": input_file, "output":
|
|
265
|
+
return {"input": input_file, "output": brain_file, **status}
|
|
182
266
|
|
|
183
267
|
|
|
184
268
|
def skull_strip_batch(
|
|
185
269
|
input_dir: str,
|
|
186
270
|
output_dir: str,
|
|
271
|
+
modality: str = "mri",
|
|
187
272
|
device: str | None = None,
|
|
188
273
|
disable_tta: bool = True,
|
|
189
274
|
desired_dilation_mm: float = 14.0,
|
|
190
275
|
) -> pd.DataFrame:
|
|
191
|
-
"""Run
|
|
276
|
+
"""Run skull-stripping on all 3D NIfTI volumes under *input_dir*.
|
|
192
277
|
|
|
278
|
+
Uses HD-BET for MRI or TotalSegmentator for CT, based on *modality*.
|
|
193
279
|
Scans that are not 3D volumes (2D slices or 4D time series) are skipped.
|
|
194
280
|
|
|
195
281
|
Parameters
|
|
@@ -197,11 +283,13 @@ def skull_strip_batch(
|
|
|
197
283
|
input_dir : str
|
|
198
284
|
Root directory with reoriented NIfTI files.
|
|
199
285
|
output_dir : str
|
|
200
|
-
Root output directory for
|
|
286
|
+
Root output directory for skull-stripping results.
|
|
287
|
+
modality : str
|
|
288
|
+
``'mri'`` (HD-BET) or ``'ct'`` (TotalSegmentator).
|
|
201
289
|
device : str or None
|
|
202
290
|
'cpu' or 'cuda'. Auto-detected if None.
|
|
203
291
|
disable_tta : bool
|
|
204
|
-
Disable test-time augmentation.
|
|
292
|
+
Disable test-time augmentation (MRI/HD-BET only).
|
|
205
293
|
desired_dilation_mm : float
|
|
206
294
|
Physical dilation in mm.
|
|
207
295
|
|
|
@@ -210,8 +298,17 @@ def skull_strip_batch(
|
|
|
210
298
|
pd.DataFrame
|
|
211
299
|
Processing log.
|
|
212
300
|
"""
|
|
213
|
-
# Validate
|
|
214
|
-
|
|
301
|
+
# Validate backend is available
|
|
302
|
+
if modality == "mri":
|
|
303
|
+
_hd_bet_executable()
|
|
304
|
+
elif modality == "ct":
|
|
305
|
+
try:
|
|
306
|
+
import totalsegmentator # noqa: F401
|
|
307
|
+
except ImportError:
|
|
308
|
+
raise ImportError(
|
|
309
|
+
"TotalSegmentator is required for CT skull-stripping.\n"
|
|
310
|
+
"Install it with: pip install caideface[ct]"
|
|
311
|
+
)
|
|
215
312
|
|
|
216
313
|
if device is None:
|
|
217
314
|
device = get_default_device()
|
|
@@ -231,18 +328,22 @@ def skull_strip_batch(
|
|
|
231
328
|
|
|
232
329
|
logger.info("Processing: %s", input_file)
|
|
233
330
|
result = skull_strip_single(
|
|
234
|
-
input_file, input_dir, output_dir,
|
|
331
|
+
input_file, input_dir, output_dir,
|
|
332
|
+
modality=modality,
|
|
333
|
+
device=device,
|
|
334
|
+
disable_tta=disable_tta,
|
|
335
|
+
desired_dilation_mm=desired_dilation_mm,
|
|
235
336
|
)
|
|
236
337
|
log_data.append(result)
|
|
237
338
|
|
|
238
339
|
df = pd.DataFrame(log_data)
|
|
239
|
-
log_path = os.path.join(output_dir, "
|
|
340
|
+
log_path = os.path.join(output_dir, "skull_strip_log.csv")
|
|
240
341
|
|
|
241
342
|
if os.path.exists(log_path):
|
|
242
343
|
existing = pd.read_csv(log_path)
|
|
243
344
|
df = pd.concat([existing, df], ignore_index=True)
|
|
244
345
|
|
|
245
346
|
df.to_csv(log_path, index=False)
|
|
246
|
-
logger.info("
|
|
347
|
+
logger.info("Skull-stripping log saved at %s", log_path)
|
|
247
348
|
|
|
248
349
|
return df
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: caideface
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: MRI defacing pipeline with skull-stripping and affine registration from cai4cai
|
|
5
5
|
Author-email: Lorena Garcia-Foncillas <lorenagarfon00@gmail.com>
|
|
6
|
-
License:
|
|
6
|
+
License: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/cai4cai/defacing_pipeline
|
|
8
8
|
Project-URL: Repository, https://github.com/cai4cai/defacing_pipeline
|
|
9
9
|
Keywords: MRI,defacing,anonymisation,skull-stripping,neuroimaging,NER,text-anonymization
|
|
@@ -13,7 +13,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
14
14
|
Requires-Python: >=3.9
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE.md
|
|
17
16
|
Requires-Dist: nibabel>=4.0
|
|
18
17
|
Requires-Dist: numpy<2,>=1.22
|
|
19
18
|
Requires-Dist: scipy>=1.9
|
|
@@ -27,7 +26,8 @@ Requires-Dist: faker>=18.0
|
|
|
27
26
|
Provides-Extra: dev
|
|
28
27
|
Requires-Dist: pytest; extra == "dev"
|
|
29
28
|
Requires-Dist: ruff; extra == "dev"
|
|
30
|
-
|
|
29
|
+
Provides-Extra: ct
|
|
30
|
+
Requires-Dist: TotalSegmentator; extra == "ct"
|
|
31
31
|
|
|
32
32
|
# caideface
|
|
33
33
|
|
|
@@ -330,4 +330,4 @@ If you use the text anonymisation (NER + HIPS), please also cite:
|
|
|
330
330
|
|
|
331
331
|
## License
|
|
332
332
|
|
|
333
|
-
This project is licensed under the
|
|
333
|
+
This project is licensed under the Apache License 2.0 -- see the [LICENSE](https://github.com/cai4cai/defacing_pipeline/blob/main/LICENSE) file for details.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
LICENSE.md
|
|
2
1
|
README.md
|
|
3
2
|
pyproject.toml
|
|
4
3
|
src/caideface/__init__.py
|
|
@@ -15,6 +14,7 @@ src/caideface.egg-info/dependency_links.txt
|
|
|
15
14
|
src/caideface.egg-info/entry_points.txt
|
|
16
15
|
src/caideface.egg-info/requires.txt
|
|
17
16
|
src/caideface.egg-info/top_level.txt
|
|
17
|
+
src/caideface/data/ct_face_mask.nii.gz
|
|
18
18
|
src/caideface/data/mni_icbm152_t1_tal_nlin_sym_55_ext_brain_only.nii.gz
|
|
19
19
|
src/caideface/data/t1_mask.nii.gz
|
|
20
20
|
src/caideface/data/ner_model/config.cfg
|
caideface-0.3.0/LICENSE.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025-present cai4cai
|
|
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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|