mrid-python 0.1.3__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 (32) hide show
  1. mrid_python-0.1.3/PKG-INFO +140 -0
  2. mrid_python-0.1.3/README.md +126 -0
  3. mrid_python-0.1.3/mrid/__init__.py +5 -0
  4. mrid_python-0.1.3/mrid/atlas/MNI152/__init__.py +80 -0
  5. mrid_python-0.1.3/mrid/atlas/SRI24/__init__.py +77 -0
  6. mrid_python-0.1.3/mrid/atlas/__init__.py +7 -0
  7. mrid_python-0.1.3/mrid/loading/__init__.py +1 -0
  8. mrid_python-0.1.3/mrid/loading/convert.py +68 -0
  9. mrid_python-0.1.3/mrid/preprocessing/__init__.py +12 -0
  10. mrid_python-0.1.3/mrid/preprocessing/bias_field_correction.py +28 -0
  11. mrid_python-0.1.3/mrid/preprocessing/cropping.py +36 -0
  12. mrid_python-0.1.3/mrid/preprocessing/registration.py +251 -0
  13. mrid_python-0.1.3/mrid/preprocessing/skullstripping.py +185 -0
  14. mrid_python-0.1.3/mrid/study.py +442 -0
  15. mrid_python-0.1.3/mrid/utils/__init__.py +3 -0
  16. mrid_python-0.1.3/mrid/utils/dcm2niix.py +90 -0
  17. mrid_python-0.1.3/mrid/utils/dicom_uid_fixer.py +86 -0
  18. mrid_python-0.1.3/mrid/utils/plotting.py +102 -0
  19. mrid_python-0.1.3/mrid/utils/python_utils.py +48 -0
  20. mrid_python-0.1.3/mrid/utils/stl_utils.py +70 -0
  21. mrid_python-0.1.3/mrid/utils/torch_utils.py +16 -0
  22. mrid_python-0.1.3/mrid_python.egg-info/PKG-INFO +140 -0
  23. mrid_python-0.1.3/mrid_python.egg-info/SOURCES.txt +30 -0
  24. mrid_python-0.1.3/mrid_python.egg-info/dependency_links.txt +1 -0
  25. mrid_python-0.1.3/mrid_python.egg-info/requires.txt +2 -0
  26. mrid_python-0.1.3/mrid_python.egg-info/top_level.txt +3 -0
  27. mrid_python-0.1.3/pyproject.toml +45 -0
  28. mrid_python-0.1.3/setup.cfg +4 -0
  29. mrid_python-0.1.3/tests/test_loading.py +82 -0
  30. mrid_python-0.1.3/tests/test_preprocessing.py +43 -0
  31. mrid_python-0.1.3/tests/test_study.py +136 -0
  32. mrid_python-0.1.3/tests/test_utils.py +16 -0
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: mrid-python
3
+ Version: 0.1.3
4
+ Summary: Tools for working with 3D medical images and segmentations - registration, brain skull-stripping, etc.
5
+ Author-email: Ivan Nikishev <nkshv2@gmail.com>
6
+ Project-URL: Homepage, https://github.com/inikishev/mrid
7
+ Project-URL: Repository, https://github.com/inikishev/mrid
8
+ Project-URL: Issues, https://github.com/inikishev/mrid/isses
9
+ Keywords: MRI,medical imaging
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: numpy
13
+ Requires-Dist: SimpleITK
14
+
15
+ <h1 align='center'>mrid</h1>
16
+
17
+ mrid is a library for preprocessing of 3D images, particularly medical images.
18
+
19
+ `mrid.Study` provides a convenient way to work with scans and provides all the tools (skull-stripping, registration, etc). See [this notebook](https://nbviewer.org/github/inikishev/mrid/blob/main/mrid_tutorial.ipynb) for an example of how to use it ([Google Colab](https://colab.research.google.com/drive/1d1MWPaGAqfK6q_5879LpwwxPCB-tbExe?usp=sharing)).
20
+
21
+ All methods are also available as separate functions, see below.
22
+
23
+ ## Registering a scan
24
+
25
+ All methods accept scans that can be a path to NIfTI, DICOM dir, sitk Image, numpy array or torch tensor, and return sitk.Image format.
26
+
27
+ For registering you need to have SimpleITK-SimpleElastix installed (<https://pypi.org/project/SimpleITK-SimpleElastix/>)
28
+
29
+ ```python
30
+ registered = mrid.register(moving, fixed) # returns sitk.Image
31
+ ```
32
+
33
+ mrid includes downloaders for commonly used templates - MNI152 and SRI24: `mrid.get_mni152`, and `mrid.get_sri24`.
34
+
35
+ ```python
36
+ t1_sri = mrid.register(t1, mrid.get_sri24("T1")) # returns sitk.Image
37
+ ```
38
+
39
+ note you can always convert anything to numpy array using `mrid.tonumpy(src)`:
40
+
41
+ ```python
42
+ t1_sri_np = mrid.tonumpy(t1_sri) # np.array
43
+ ```
44
+
45
+ ## Registering multiple scans
46
+
47
+ If you have multiple scans that are already aligned, you can register one of them and use the same transformation to transform the rest. This can also be used to register segmentations.
48
+
49
+ In this example we register T1 to SRI24 T1 template, and transform the rest
50
+
51
+ ```python
52
+ data = {"t1": "t1c.nii.gz", "t2": "t2w.nii.gz", "flair": "t2f.nii.gz"}
53
+ registered_data = mrid.register_D(data, key="t1", to=mrid.get_sri24("T1"))
54
+ # dict of sitk.Image registered to SRI24
55
+ ```
56
+
57
+ If you have multiple scans that are not aligned (e.g. have different shapes, orientations), you can use `register_each`. This registers one scan to the target, and then registers all other scans to the first scan. In the example it registers T1 to SRI24, and then registers T2 and FLAIR to T1.
58
+
59
+ ```python
60
+ data = {"t1": "t1c.nii.gz", "t2": "t2w.nii.gz", "flair": "t2f.nii.gz"}
61
+ registered_data = mrid.register_each(data, key="t1", to=mrid.get_sri24("T1"))
62
+ # dict of sitk.Image registered to SRI24
63
+ ```
64
+
65
+ ## Inverse transform
66
+
67
+ mrid provides a simple way to compute an inverse of registration, this is also another way to perform registration in general.
68
+
69
+ ```python
70
+ reg = mrid.Registration()
71
+
72
+ # first we need to fit a transform
73
+ reg.find_transform(t1, mrid.get_sri24("T1"))
74
+
75
+ # now we can use `reg.apply_transform` to transform any scan (assuming it is aligned to `t1`)
76
+ t2_sri24 = reg.apply_transform(t2) # returns sitk.Image
77
+
78
+ # use `apply_inverse_transform` to transform back to original space
79
+ t2_recovered = reg.apply_inverse_transform(t2_sri24) # returns sitk.Image
80
+
81
+ # when transforming segmentations, use nearest
82
+ # neigbour interpolation to preserve edges
83
+ seg = (mrid.tonumpy(t2_sri24) > 100).astype(np.uint8)
84
+ seg_orig = reg.apply_inverse_transform(seg, use_nearest_interpolation=True) # returns sitk.Image
85
+ ```
86
+
87
+ ## Skullstripping
88
+
89
+ For skullstripping mrid uses HD-BET model (<https://github.com/MIC-DKFZ/HD-BET>), and you need to have it installed (it uses torch). It works with pre/post contrast T1, T2 and FLAIR MRIs, but it expects them to be in MNI152 space (while many new datasets like BraTS are in SRI24). If your scans aren't in MNI152, the `skullstrip` function can register specified scan to MNI152, pass it to HD-BET, and then register back to original space, if `register_to_mni152` argument is specified.
90
+
91
+ ```python
92
+ t1_skullstripped = mrid.skullstrip(t1) # if t1 is in MNI152
93
+ t1_skullstripped = mrid.skullstrip(t1, register_to_mni152="T1") # if t1 is not in MNI152
94
+ ```
95
+
96
+ HD-BET is generally very robust but it can remove parts of tumors adjascent to the skull, but this is true for all skullstripping tools.
97
+
98
+ ## Skullstripping multiple scans
99
+
100
+ If you have multiple scans that are already aligned, you can compute brain mask on one of them (ideally post-contrast T1) and apply it to all scans.
101
+
102
+ ```python
103
+ data = {"t1": "t1c.nii.gz", "t2": "t2w.nii.gz", "flair": "t2f.nii.gz"}
104
+ skullstripped_data = mrid.skullstrip_D(data, key="t1", to=mrid.get_sri24("T1"))
105
+ # dict of skullstripped sitk.Image
106
+ ```
107
+
108
+ ## dcm2niix
109
+
110
+ mrid provides a simple wrapper around [dcm2niix](https://github.com/rordenlab/dcm2niix), which is a very robust tool for converting DICOMs to NIfTI.
111
+
112
+ ```python
113
+ # this saves output.nii.gz to output_path. It can save multiple files if there are multiple cases in "path/to/dicoms".
114
+ mrid.utils.run_dcm2niix("path/to/dicoms", outfolder="output_path", outfname="output")
115
+ ```
116
+
117
+ If you want to use it convert DICOMs directly to SimpleITK, use `dcm2sitk`:
118
+
119
+ ```python
120
+ t1 = mrid.utils.dcm2sitk("path/to/dicoms") # returns sitk.Image with correct spatial information
121
+ ```
122
+ dcm2niix is much slower compared to SimpleITK's series reader which mrid uses by default, but it's more careful about splitting different studies to different files, although it might fail with malformed DICOMs which are surprisingly common (where it would think each slice is a separate study).
123
+
124
+ ## Loading STL segmentations
125
+
126
+ `stl2sitk` converts segmentations in STL format to `sitk.Image` given a reference `sitk.Image` that the STL should be aligned to. Basically something I encountered so I had to make this.
127
+
128
+ ```python
129
+ seg = mrid.utils.stl2sitk("path/to/file.stl", reference) # returns sitk.Image (hopefully) aligned with `reference`
130
+ ```
131
+
132
+ ## How to install
133
+ Either run
134
+ ```
135
+ pip install mrid-python
136
+ ```
137
+ or
138
+ ```
139
+ pip install git+https://github.com/inikishev/mrid
140
+ ```
@@ -0,0 +1,126 @@
1
+ <h1 align='center'>mrid</h1>
2
+
3
+ mrid is a library for preprocessing of 3D images, particularly medical images.
4
+
5
+ `mrid.Study` provides a convenient way to work with scans and provides all the tools (skull-stripping, registration, etc). See [this notebook](https://nbviewer.org/github/inikishev/mrid/blob/main/mrid_tutorial.ipynb) for an example of how to use it ([Google Colab](https://colab.research.google.com/drive/1d1MWPaGAqfK6q_5879LpwwxPCB-tbExe?usp=sharing)).
6
+
7
+ All methods are also available as separate functions, see below.
8
+
9
+ ## Registering a scan
10
+
11
+ All methods accept scans that can be a path to NIfTI, DICOM dir, sitk Image, numpy array or torch tensor, and return sitk.Image format.
12
+
13
+ For registering you need to have SimpleITK-SimpleElastix installed (<https://pypi.org/project/SimpleITK-SimpleElastix/>)
14
+
15
+ ```python
16
+ registered = mrid.register(moving, fixed) # returns sitk.Image
17
+ ```
18
+
19
+ mrid includes downloaders for commonly used templates - MNI152 and SRI24: `mrid.get_mni152`, and `mrid.get_sri24`.
20
+
21
+ ```python
22
+ t1_sri = mrid.register(t1, mrid.get_sri24("T1")) # returns sitk.Image
23
+ ```
24
+
25
+ note you can always convert anything to numpy array using `mrid.tonumpy(src)`:
26
+
27
+ ```python
28
+ t1_sri_np = mrid.tonumpy(t1_sri) # np.array
29
+ ```
30
+
31
+ ## Registering multiple scans
32
+
33
+ If you have multiple scans that are already aligned, you can register one of them and use the same transformation to transform the rest. This can also be used to register segmentations.
34
+
35
+ In this example we register T1 to SRI24 T1 template, and transform the rest
36
+
37
+ ```python
38
+ data = {"t1": "t1c.nii.gz", "t2": "t2w.nii.gz", "flair": "t2f.nii.gz"}
39
+ registered_data = mrid.register_D(data, key="t1", to=mrid.get_sri24("T1"))
40
+ # dict of sitk.Image registered to SRI24
41
+ ```
42
+
43
+ If you have multiple scans that are not aligned (e.g. have different shapes, orientations), you can use `register_each`. This registers one scan to the target, and then registers all other scans to the first scan. In the example it registers T1 to SRI24, and then registers T2 and FLAIR to T1.
44
+
45
+ ```python
46
+ data = {"t1": "t1c.nii.gz", "t2": "t2w.nii.gz", "flair": "t2f.nii.gz"}
47
+ registered_data = mrid.register_each(data, key="t1", to=mrid.get_sri24("T1"))
48
+ # dict of sitk.Image registered to SRI24
49
+ ```
50
+
51
+ ## Inverse transform
52
+
53
+ mrid provides a simple way to compute an inverse of registration, this is also another way to perform registration in general.
54
+
55
+ ```python
56
+ reg = mrid.Registration()
57
+
58
+ # first we need to fit a transform
59
+ reg.find_transform(t1, mrid.get_sri24("T1"))
60
+
61
+ # now we can use `reg.apply_transform` to transform any scan (assuming it is aligned to `t1`)
62
+ t2_sri24 = reg.apply_transform(t2) # returns sitk.Image
63
+
64
+ # use `apply_inverse_transform` to transform back to original space
65
+ t2_recovered = reg.apply_inverse_transform(t2_sri24) # returns sitk.Image
66
+
67
+ # when transforming segmentations, use nearest
68
+ # neigbour interpolation to preserve edges
69
+ seg = (mrid.tonumpy(t2_sri24) > 100).astype(np.uint8)
70
+ seg_orig = reg.apply_inverse_transform(seg, use_nearest_interpolation=True) # returns sitk.Image
71
+ ```
72
+
73
+ ## Skullstripping
74
+
75
+ For skullstripping mrid uses HD-BET model (<https://github.com/MIC-DKFZ/HD-BET>), and you need to have it installed (it uses torch). It works with pre/post contrast T1, T2 and FLAIR MRIs, but it expects them to be in MNI152 space (while many new datasets like BraTS are in SRI24). If your scans aren't in MNI152, the `skullstrip` function can register specified scan to MNI152, pass it to HD-BET, and then register back to original space, if `register_to_mni152` argument is specified.
76
+
77
+ ```python
78
+ t1_skullstripped = mrid.skullstrip(t1) # if t1 is in MNI152
79
+ t1_skullstripped = mrid.skullstrip(t1, register_to_mni152="T1") # if t1 is not in MNI152
80
+ ```
81
+
82
+ HD-BET is generally very robust but it can remove parts of tumors adjascent to the skull, but this is true for all skullstripping tools.
83
+
84
+ ## Skullstripping multiple scans
85
+
86
+ If you have multiple scans that are already aligned, you can compute brain mask on one of them (ideally post-contrast T1) and apply it to all scans.
87
+
88
+ ```python
89
+ data = {"t1": "t1c.nii.gz", "t2": "t2w.nii.gz", "flair": "t2f.nii.gz"}
90
+ skullstripped_data = mrid.skullstrip_D(data, key="t1", to=mrid.get_sri24("T1"))
91
+ # dict of skullstripped sitk.Image
92
+ ```
93
+
94
+ ## dcm2niix
95
+
96
+ mrid provides a simple wrapper around [dcm2niix](https://github.com/rordenlab/dcm2niix), which is a very robust tool for converting DICOMs to NIfTI.
97
+
98
+ ```python
99
+ # this saves output.nii.gz to output_path. It can save multiple files if there are multiple cases in "path/to/dicoms".
100
+ mrid.utils.run_dcm2niix("path/to/dicoms", outfolder="output_path", outfname="output")
101
+ ```
102
+
103
+ If you want to use it convert DICOMs directly to SimpleITK, use `dcm2sitk`:
104
+
105
+ ```python
106
+ t1 = mrid.utils.dcm2sitk("path/to/dicoms") # returns sitk.Image with correct spatial information
107
+ ```
108
+ dcm2niix is much slower compared to SimpleITK's series reader which mrid uses by default, but it's more careful about splitting different studies to different files, although it might fail with malformed DICOMs which are surprisingly common (where it would think each slice is a separate study).
109
+
110
+ ## Loading STL segmentations
111
+
112
+ `stl2sitk` converts segmentations in STL format to `sitk.Image` given a reference `sitk.Image` that the STL should be aligned to. Basically something I encountered so I had to make this.
113
+
114
+ ```python
115
+ seg = mrid.utils.stl2sitk("path/to/file.stl", reference) # returns sitk.Image (hopefully) aligned with `reference`
116
+ ```
117
+
118
+ ## How to install
119
+ Either run
120
+ ```
121
+ pip install mrid-python
122
+ ```
123
+ or
124
+ ```
125
+ pip install git+https://github.com/inikishev/mrid
126
+ ```
@@ -0,0 +1,5 @@
1
+ from . import utils
2
+ from .atlas import get_mni152, get_sri24
3
+ from .preprocessing import *
4
+ from .loading import *
5
+ from .study import Study
@@ -0,0 +1,80 @@
1
+ # https://zenodo.org/api/records/15470657/files-archive
2
+
3
+ import os
4
+ import shutil
5
+ import tempfile
6
+ from pathlib import Path
7
+ from typing import Literal
8
+
9
+ __all__ = [
10
+ "get_mni152",
11
+ ]
12
+
13
+ _ROOT = Path(os.path.dirname(__file__))
14
+
15
+ _URLS = {
16
+ "2006 T1w symmetric": (
17
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t1_06_sym.nii.gz?download=1",
18
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t1_06_sym_bet.nii.gz?download=1",
19
+ ),
20
+ "2009a T1w symmetric": (
21
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t1_09a_sym.nii.gz?download=1",
22
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t1_09a_sym_bet.nii.gz?download=1",
23
+ ),
24
+ "2009a T2w symmetric": (
25
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t2_09a_sym.nii.gz?download=1",
26
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t2_09a_sym_bet.nii.gz?download=1",
27
+ ),
28
+ "2009a T1w asymmetric": (
29
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t1_09a_asym.nii.gz?download=1",
30
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t1_09a_asym_bet.nii.gz?download=1",
31
+ ),
32
+ "2009a T2w asymmetric": (
33
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t2_09a_asym.nii.gz?download=1",
34
+ "https://zenodo.org/records/15470657/files/icbm_mni152_t2_09a_asym_bet.nii.gz?download=1",
35
+ ),
36
+ }
37
+
38
+ _mni152_url = "https://zenodo.org/records/15470657/files/icbm_mni152_t1_06_sym_bet.nii.gz?download=1"
39
+
40
+ def _download_template(type: str, bet:bool):
41
+ filename = f"{type} {bool(bet)}.nii.gz"
42
+ if filename in os.listdir(_ROOT):
43
+ raise RuntimeError(f"Template {type} is already downloaded")
44
+
45
+ import requests
46
+
47
+ response = requests.get(_URLS[type][bet], stream=True, timeout=30)
48
+ response.raise_for_status()
49
+
50
+ with open(_ROOT / f"{filename}", 'wb') as file:
51
+ shutil.copyfileobj(response.raw, file) # type:ignore
52
+
53
+ def get_mni152(
54
+ type: Literal[
55
+ "2006 T1w symmetric",
56
+ "2009a T1w symmetric",
57
+ "2009a T2w symmetric",
58
+ "2009a T1w asymmetric",
59
+ "2009a T2w asymmetric",
60
+ ],
61
+ skullstripped: bool = False,
62
+ ):
63
+ """Returns path to .nii.gz file of specified MNI-152 template.
64
+
65
+ Descriptions of templates are available here https://zenodo.org/records/15470657
66
+ """
67
+ filename = f"{type} {bool(skullstripped)}.nii.gz"
68
+
69
+ if filename in os.listdir(_ROOT):
70
+ return str(_ROOT / filename)
71
+
72
+ print(f"{filename} will be downloaded from https://zenodo.org/records/15470657, this may take a few minutes.")
73
+ _download_template(type, skullstripped)
74
+
75
+ if filename not in os.listdir(_ROOT):
76
+ raise RuntimeError(
77
+ f"Failed to download {filename}; try downloading it manually from https://zenodo.org/records/15470657"
78
+ )
79
+
80
+ return str(_ROOT / filename)
@@ -0,0 +1,77 @@
1
+ """This subpackage allows one to download SRI-24 brain atlas files from https://www.nitrc.org/projects/sri24/.
2
+
3
+ The SRI24 atlas is licensed under the terms of the
4
+
5
+ Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
6
+
7
+ license (https://creativecommons.org/licenses/by-...).
8
+
9
+ In publications using the SRI24 atlas, please cite the following paper:
10
+
11
+ T. Rohlfing, N.M. Zahr, E.V. Sullivan, A. Pfefferbaum, "The SRI24
12
+ Multichannel Atlas of Normal Adult Human Brain Structure," Human
13
+ Brain Mapping, vol. 31, no. 5, pp. 798-819, 2010.
14
+
15
+ http://dx.doi.org/10.1002/hbm.20906
16
+
17
+ """
18
+ import os
19
+ import shutil
20
+ import tempfile
21
+ from pathlib import Path
22
+ from typing import Literal
23
+
24
+ __all__ = [
25
+ "get_sri24",
26
+ ]
27
+
28
+ _ROOT = Path(os.path.dirname(__file__))
29
+
30
+ _sri24_url = "https://www.nitrc.org/frs/download.php/4841/sri24_spm8.zip//?i_agree=1&download_now=1"
31
+
32
+ def _download_sri24() -> None:
33
+ import requests
34
+
35
+ response = requests.get(_sri24_url, stream=True, timeout=30)
36
+ response.raise_for_status()
37
+
38
+ with tempfile.TemporaryDirectory() as tmpdir:
39
+ tmpdir = Path(tmpdir)
40
+ with open(tmpdir / "sri24_spm8.zip", 'wb') as file:
41
+ shutil.copyfileobj(response.raw, file) # type:ignore
42
+
43
+ shutil.unpack_archive(tmpdir / "sri24_spm8.zip", tmpdir / "sri24_spm8")
44
+
45
+ for file in os.listdir(tmpdir / "sri24_spm8" / "templates"):
46
+ shutil.copyfile(tmpdir / "sri24_spm8" / "templates" / file, _ROOT / file)
47
+
48
+
49
+ def get_sri24(type: Literal["EPI", "EPI_brain", "PD", "PD_brain", "T1", "T1_brain", "T2", "T2_brain"]) -> str:
50
+ """Returns path to .nii file of specified SRI-24 template. Templates are downloaded if they haven't been downloaded already.
51
+
52
+ The following templates are available:
53
+ - `"T1"`: post-contrast T1-weighted MRI with skull;
54
+ - `"T1_brain"`: post-contrast T1-weighted MRI without skull;
55
+ - `"T2"`: T2-weighted MRI with skull;
56
+ - `"T2_brain"`: T2-weighted MRI without skull;
57
+ - `"EPI"`: echo-planar imaging MRI with skull;
58
+ - `"EPI_brain"`: echo-planar imaging MRU without skull;
59
+ - `"PD"`: proton density weighted spin-echo imaging MRI with skull;
60
+ - `"PD_brain"`: proton density weighted spin-echo imaging MRI without skull;
61
+
62
+ """
63
+ filename = f"{type}.nii"
64
+ if filename in os.listdir(_ROOT):
65
+ return str(_ROOT / filename)
66
+
67
+ print("SRI24 will be downloaded from https://www.nitrc.org/projects/sri24/, this may take a few minutes.")
68
+ _download_sri24()
69
+
70
+ if filename not in os.listdir(_ROOT):
71
+ raise RuntimeError(
72
+ f"Failed to download {filename}; try downloading it manually from https://www.nitrc.org/projects/sri24/, "
73
+ "then unpack the zip file, open it, open `templates` folder, you will see files such as `EPI.nii`. "
74
+ f"Copy all of those files to {_ROOT}."
75
+ )
76
+
77
+ return str(_ROOT / filename)
@@ -0,0 +1,7 @@
1
+ from . MNI152 import get_mni152
2
+ from .SRI24 import get_sri24
3
+
4
+ __all__ = [
5
+ "get_mni152",
6
+ "get_sri24",
7
+ ]
@@ -0,0 +1 @@
1
+ from .convert import tonumpy, tositk, totensor, ImageLike
@@ -0,0 +1,68 @@
1
+ import importlib.util
2
+ import os
3
+ from typing import TYPE_CHECKING, TypeAlias
4
+
5
+ import numpy as np
6
+ import SimpleITK as sitk
7
+
8
+ from ..utils.torch_utils import TORCH_INSTALLED
9
+
10
+ if TYPE_CHECKING:
11
+ import torch
12
+
13
+ PREFER_DCM2NIIX = False
14
+ ImageLike: TypeAlias = "np.ndarray | sitk.Image | torch.Tensor | str | os.PathLike"
15
+
16
+ def read_dicoms(dir: str | os.PathLike) -> sitk.Image:
17
+ """reads a directory of DICOM files and returns a ``sitk.Image``"""
18
+ # load with dcm2niix
19
+ if PREFER_DCM2NIIX and importlib.util.find_spec("dcm2niix") is not None:
20
+ from ..utils.dcm2niix import dcm2sitk
21
+ return dcm2sitk(dir)
22
+
23
+ # load with SimpleITK
24
+ reader = sitk.ImageSeriesReader()
25
+ dicom_names = reader.GetGDCMSeriesFileNames(str(dir))
26
+
27
+ if not dicom_names:
28
+ raise FileNotFoundError(f"No DICOM series found in directory: {dir}")
29
+
30
+ reader.SetFileNames(dicom_names)
31
+ return reader.Execute()
32
+
33
+ def _read_sitk(path: str | os.PathLike) -> sitk.Image:
34
+ if os.path.isfile(path): return sitk.ReadImage(str(path))
35
+ if os.path.isdir(path): return read_dicoms(str(path))
36
+ raise FileNotFoundError(f"{path} doesn't exist")
37
+
38
+ def tositk(x: ImageLike) -> sitk.Image:
39
+ """Load an image into an ``sitk.Image`` object.
40
+ ``x`` can be a numpy array, a ``sitk.Image``, a ``torch.Tensor`` or a string (path to an image file)."""
41
+ if isinstance(x, np.ndarray): return sitk.GetImageFromArray(x)
42
+ if isinstance(x, sitk.Image): return x
43
+ if isinstance(x, (str, os.PathLike)): return _read_sitk(x)
44
+ if TORCH_INSTALLED:
45
+ import torch
46
+ if isinstance(x, torch.Tensor): return sitk.GetImageFromArray(x.numpy())
47
+ raise TypeError(f"Unsupported type {type(x)}")
48
+
49
+ def tonumpy(x: ImageLike) -> np.ndarray:
50
+ """Load an image into a numpy.ndarray.
51
+ ``x`` can be a numpy array, a ``sitk.Image``, a ``torch.Tensor`` or a string (path to an image file)."""
52
+ if isinstance(x, np.ndarray): return x
53
+ if isinstance(x, sitk.Image): return sitk.GetArrayFromImage(x)
54
+ if isinstance(x, (str, os.PathLike)): return sitk.GetArrayFromImage(_read_sitk(x))
55
+ if TORCH_INSTALLED:
56
+ import torch
57
+ if isinstance(x, torch.Tensor): return x.numpy()
58
+ raise TypeError(f"Unsupported type {type(x)}")
59
+
60
+ def totensor(x: ImageLike) -> "torch.Tensor":
61
+ """Load an image into a torch.Tensor.
62
+ ``x`` can be a numpy array, a ``sitk.Image``, a ``torch.Tensor`` or a string (path to an image file)."""
63
+ import torch
64
+ if isinstance(x, np.ndarray): return torch.from_numpy(x)
65
+ if isinstance(x, sitk.Image): return torch.from_numpy(sitk.GetArrayFromImage(x))
66
+ if isinstance(x, (str, os.PathLike)): return torch.from_numpy(sitk.GetArrayFromImage(_read_sitk(x)))
67
+ if isinstance(x, torch.Tensor): return x
68
+ raise TypeError(f"Unsupported type {type(x)}")
@@ -0,0 +1,12 @@
1
+ from .bias_field_correction import n4_bias_field_correction
2
+ from .cropping import crop_bg, crop_bg_D
3
+ from .registration import resample_to, register, register_D, register_each, resize, downsample, Registration
4
+ from .skullstripping import skullstrip, skullstrip_D, run_hd_bet, predict_brain_mask
5
+
6
+ __all__ = [
7
+ "n4_bias_field_correction",
8
+ "crop_bg", "crop_bg_D",
9
+ "resample_to", "register", "register_D", "register_each", "resize", "downsample",
10
+ "skullstrip", "skullstrip_D", "run_hd_bet", "predict_brain_mask"
11
+
12
+ ]
@@ -0,0 +1,28 @@
1
+ import SimpleITK as sitk
2
+ from ..loading.convert import tositk, ImageLike
3
+
4
+ def n4_bias_field_correction(image: ImageLike, shrink: int = 4) -> sitk.Image:
5
+ """Perform N4 Bias Field Correction to correct low frequency intensity non-uniformity present in MRI image.
6
+
7
+ Args:
8
+ image (ImageLike): Input MRI image to be corrected. Can be any format supported by tositk conversion.
9
+ shrink (int, optional): Shrink factor for reducing image size before correction to speed up computation.
10
+ Default is 4. If set to 1 or less, no shrinking is performed.
11
+
12
+ """
13
+ image = tositk(image)
14
+
15
+ norm_image = sitk.RescaleIntensity(image, 0, 255)
16
+ mask = sitk.OtsuThreshold(norm_image, 0, 1)
17
+
18
+ if shrink > 1:
19
+ reduced = sitk.Shrink(image, [shrink] * image.GetDimension())
20
+ mask = sitk.Shrink(mask, [shrink] * mask.GetDimension())
21
+
22
+ else: reduced = image
23
+
24
+ corrector = sitk.N4BiasFieldCorrectionImageFilter()
25
+ corrector.Execute(reduced, mask)
26
+ log_bias_field = corrector.GetLogBiasFieldAsImage(image)
27
+
28
+ return image / sitk.Cast(sitk.Exp(log_bias_field), image.GetPixelID())
@@ -0,0 +1,36 @@
1
+ from collections.abc import Mapping
2
+ from typing import Any
3
+ import SimpleITK as sitk
4
+
5
+ from ..loading.convert import tositk, ImageLike
6
+
7
+ def _get_bbox(image: sitk.Image):
8
+ rescaled = sitk.RescaleIntensity(image, 0, 255)
9
+ filt = sitk.LabelShapeStatisticsImageFilter()
10
+ filt.Execute(sitk.OtsuThreshold(rescaled, 0, 255))
11
+ return filt.GetBoundingBox(255)
12
+
13
+
14
+ def crop_bg(image: ImageLike) -> sitk.Image:
15
+ """Crops black background of a single 3D image via Otsu's thresholding.
16
+
17
+ Args:
18
+ image (ImageLike): Input 3D image to be cropped. Can be any format supported by tositk conversion.
19
+
20
+ Returns:
21
+ sitk.Image: Cropped image with black background removed, maintaining the same pixel type as input.
22
+ """
23
+ image = tositk(image)
24
+ bbox = _get_bbox(image)
25
+ return sitk.RegionOfInterest( image, bbox[int(len(bbox) / 2) :], bbox[0 : int(len(bbox) / 2)],)
26
+
27
+ def crop_bg_D(images: Mapping[str, ImageLike], key: str) -> dict[str, sitk.Image]:
28
+ """Finds the bounding box of ``images[key]`` and crops all images in ``images`` to that bounding box."""
29
+ images = {k: tositk(v) for k,v in images.items()}
30
+ reference = images[key]
31
+
32
+ bbox = _get_bbox(reference)
33
+
34
+ ret = {k: sitk.RegionOfInterest(v, bbox[int(len(bbox) / 2) :], bbox[0 : int(len(bbox) / 2)]) for k,v in images.items()}
35
+ return ret
36
+