mrid-python 0.1.5__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.
- mrid_python-0.1.5/PKG-INFO +73 -0
- mrid_python-0.1.5/README.md +59 -0
- mrid_python-0.1.5/mrid/__init__.py +5 -0
- mrid_python-0.1.5/mrid/atlas/MNI152/__init__.py +87 -0
- mrid_python-0.1.5/mrid/atlas/SRI24/__init__.py +77 -0
- mrid_python-0.1.5/mrid/atlas/__init__.py +7 -0
- mrid_python-0.1.5/mrid/loading/__init__.py +1 -0
- mrid_python-0.1.5/mrid/loading/convert.py +68 -0
- mrid_python-0.1.5/mrid/preprocessing/CTseg.py +81 -0
- mrid_python-0.1.5/mrid/preprocessing/__init__.py +12 -0
- mrid_python-0.1.5/mrid/preprocessing/bias_field_correction.py +28 -0
- mrid_python-0.1.5/mrid/preprocessing/cropping.py +36 -0
- mrid_python-0.1.5/mrid/preprocessing/hd_bet.py +242 -0
- mrid_python-0.1.5/mrid/preprocessing/mask.py +41 -0
- mrid_python-0.1.5/mrid/preprocessing/simple_elastix.py +189 -0
- mrid_python-0.1.5/mrid/preprocessing/spatial.py +82 -0
- mrid_python-0.1.5/mrid/preprocessing/synthstrip.py +270 -0
- mrid_python-0.1.5/mrid/study.py +644 -0
- mrid_python-0.1.5/mrid/training/__init__.py +0 -0
- mrid_python-0.1.5/mrid/training/slicer.py +244 -0
- mrid_python-0.1.5/mrid/training/transforms.py +109 -0
- mrid_python-0.1.5/mrid/utils/__init__.py +4 -0
- mrid_python-0.1.5/mrid/utils/dcm2niix.py +90 -0
- mrid_python-0.1.5/mrid/utils/dicom_uid_fixer.py +86 -0
- mrid_python-0.1.5/mrid/utils/plotting.py +106 -0
- mrid_python-0.1.5/mrid/utils/python_utils.py +48 -0
- mrid_python-0.1.5/mrid/utils/stl_utils.py +142 -0
- mrid_python-0.1.5/mrid/utils/torch_utils.py +16 -0
- mrid_python-0.1.5/mrid_python.egg-info/PKG-INFO +73 -0
- mrid_python-0.1.5/mrid_python.egg-info/SOURCES.txt +37 -0
- mrid_python-0.1.5/mrid_python.egg-info/dependency_links.txt +1 -0
- mrid_python-0.1.5/mrid_python.egg-info/requires.txt +2 -0
- mrid_python-0.1.5/mrid_python.egg-info/top_level.txt +4 -0
- mrid_python-0.1.5/pyproject.toml +45 -0
- mrid_python-0.1.5/setup.cfg +4 -0
- mrid_python-0.1.5/tests/test_loading.py +82 -0
- mrid_python-0.1.5/tests/test_preprocessing.py +43 -0
- mrid_python-0.1.5/tests/test_study.py +136 -0
- mrid_python-0.1.5/tests/test_utils.py +16 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mrid-python
|
|
3
|
+
Version: 0.1.5
|
|
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
|
+
It provide interfaces for many medical image processing tools such as [SimpleElastix](https://simpleelastix.github.io/), [HD-BET](https://github.com/MIC-DKFZ/HD-BET#Installation), [SynthStrip](https://surfer.nmr.mgh.harvard.edu/docs/synthstrip/), [CTSeg](https://github.com/WCHN/CTseg). Note that those libraries are not bundled, installation instructions are included in all examples below.
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
Either run
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install mrid-python
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
or
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
pip install git+https://github.com/inikishev/mrid
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Registering images with SimpleITK-SimpleElastix
|
|
36
|
+
|
|
37
|
+
[SimpleElastix](https://simpleelastix.github.io/) is a robust tool for image registration which works really well out-of-the-box. It works on both Windows and Linux.
|
|
38
|
+
|
|
39
|
+
See [this notebook](https://github.com/inikishev/mrid/blob/main/notebooks/SimpleElastix%20tutorial.ipynb) for how to install and use it.
|
|
40
|
+
<img width="828" height="839" alt="image" src="https://github.com/user-attachments/assets/f083178a-82f0-411d-9d46-ffff774248e0" />
|
|
41
|
+
|
|
42
|
+
### Skullstripping MRI scans with HD-BET
|
|
43
|
+
|
|
44
|
+
[HD-BET](https://github.com/MIC-DKFZ/HD-BET) is a model that performs skullstripping of pre- and post-constrast T1, T2 and FALIR MRIs. It works on both Windows and Linux.
|
|
45
|
+
|
|
46
|
+
See [this notebook](https://github.com/inikishev/mrid/blob/main/notebooks/HD-BET%20tutorial.ipynb) for how to install and use it
|
|
47
|
+
<img width="828" height="840" alt="image" src="https://github.com/user-attachments/assets/ec3a8a39-0554-419f-9df9-b8e5bebc9232" />
|
|
48
|
+
|
|
49
|
+
### Skullstripping with SynthStrip
|
|
50
|
+
|
|
51
|
+
[SynthStrip](https://surfer.nmr.mgh.harvard.edu/docs/synthstrip/) is a skull-stripping tool that works with many different image types and modalities, including MRI, DWI, CT, PET, etc.
|
|
52
|
+
|
|
53
|
+
See [this notebook](https://github.com/inikishev/mrid/blob/main/notebooks/SynthStrip%20tutorial.ipynb) for how to install and use it
|
|
54
|
+
<img width="828" height="840" alt="image" src="https://github.com/user-attachments/assets/60fdca28-054d-4e62-93f1-6b84daa3fb9a" />
|
|
55
|
+
|
|
56
|
+
### Skullstripping and segmentation of CT images with CTseg
|
|
57
|
+
|
|
58
|
+
[CTseg](https://github.com/WCHN/CTseg) can skull-strip CT images and perform their segmentation, it also registers them to a common space (see its README). Note that it can be very slow for 512x512 series (can take few hours), but you can downsample to 256x256. If you only need to quickly skullstrip CT scans without warping them you can use SynthStrip.
|
|
59
|
+
|
|
60
|
+
TODO!!!
|
|
61
|
+
|
|
62
|
+
### Example workflow - preprocessing MRIs to BraTS format
|
|
63
|
+
|
|
64
|
+
Many [BraTS](https://www.synapse.org/brats) datasets are provided as skullstripped images in SRI24 space. See [this notebook](https://github.com/inikishev/mrid/blob/main/notebooks/BraTS%20preprocessing%20workflow.ipynb) for how to process raw scans to this format.
|
|
65
|
+
|
|
66
|
+
<img width="828" height="849" alt="image" src="https://github.com/user-attachments/assets/f1b38db3-6648-4660-a381-d68a2eb8508d" />
|
|
67
|
+
|
|
68
|
+
(T1n image looks weird because that's just how it is in the zenodo dataset)
|
|
69
|
+
|
|
70
|
+
### References
|
|
71
|
+
The MRIs for all images above are from https://zenodo.org/records/7213153.
|
|
72
|
+
|
|
73
|
+
> Colin Vanden Bulcke. (2022). Open-Access DICOM MRI session (1.0) [Data set]. Zenodo. https://doi.org/10.5281/zenodo.7213153
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<h1 align='center'>mrid</h1>
|
|
2
|
+
|
|
3
|
+
mrid is a library for preprocessing of 3D images, particularly medical images.
|
|
4
|
+
|
|
5
|
+
It provide interfaces for many medical image processing tools such as [SimpleElastix](https://simpleelastix.github.io/), [HD-BET](https://github.com/MIC-DKFZ/HD-BET#Installation), [SynthStrip](https://surfer.nmr.mgh.harvard.edu/docs/synthstrip/), [CTSeg](https://github.com/WCHN/CTseg). Note that those libraries are not bundled, installation instructions are included in all examples below.
|
|
6
|
+
|
|
7
|
+
### Installation
|
|
8
|
+
|
|
9
|
+
Either run
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install mrid-python
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
or
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
pip install git+https://github.com/inikishev/mrid
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Registering images with SimpleITK-SimpleElastix
|
|
22
|
+
|
|
23
|
+
[SimpleElastix](https://simpleelastix.github.io/) is a robust tool for image registration which works really well out-of-the-box. It works on both Windows and Linux.
|
|
24
|
+
|
|
25
|
+
See [this notebook](https://github.com/inikishev/mrid/blob/main/notebooks/SimpleElastix%20tutorial.ipynb) for how to install and use it.
|
|
26
|
+
<img width="828" height="839" alt="image" src="https://github.com/user-attachments/assets/f083178a-82f0-411d-9d46-ffff774248e0" />
|
|
27
|
+
|
|
28
|
+
### Skullstripping MRI scans with HD-BET
|
|
29
|
+
|
|
30
|
+
[HD-BET](https://github.com/MIC-DKFZ/HD-BET) is a model that performs skullstripping of pre- and post-constrast T1, T2 and FALIR MRIs. It works on both Windows and Linux.
|
|
31
|
+
|
|
32
|
+
See [this notebook](https://github.com/inikishev/mrid/blob/main/notebooks/HD-BET%20tutorial.ipynb) for how to install and use it
|
|
33
|
+
<img width="828" height="840" alt="image" src="https://github.com/user-attachments/assets/ec3a8a39-0554-419f-9df9-b8e5bebc9232" />
|
|
34
|
+
|
|
35
|
+
### Skullstripping with SynthStrip
|
|
36
|
+
|
|
37
|
+
[SynthStrip](https://surfer.nmr.mgh.harvard.edu/docs/synthstrip/) is a skull-stripping tool that works with many different image types and modalities, including MRI, DWI, CT, PET, etc.
|
|
38
|
+
|
|
39
|
+
See [this notebook](https://github.com/inikishev/mrid/blob/main/notebooks/SynthStrip%20tutorial.ipynb) for how to install and use it
|
|
40
|
+
<img width="828" height="840" alt="image" src="https://github.com/user-attachments/assets/60fdca28-054d-4e62-93f1-6b84daa3fb9a" />
|
|
41
|
+
|
|
42
|
+
### Skullstripping and segmentation of CT images with CTseg
|
|
43
|
+
|
|
44
|
+
[CTseg](https://github.com/WCHN/CTseg) can skull-strip CT images and perform their segmentation, it also registers them to a common space (see its README). Note that it can be very slow for 512x512 series (can take few hours), but you can downsample to 256x256. If you only need to quickly skullstrip CT scans without warping them you can use SynthStrip.
|
|
45
|
+
|
|
46
|
+
TODO!!!
|
|
47
|
+
|
|
48
|
+
### Example workflow - preprocessing MRIs to BraTS format
|
|
49
|
+
|
|
50
|
+
Many [BraTS](https://www.synapse.org/brats) datasets are provided as skullstripped images in SRI24 space. See [this notebook](https://github.com/inikishev/mrid/blob/main/notebooks/BraTS%20preprocessing%20workflow.ipynb) for how to process raw scans to this format.
|
|
51
|
+
|
|
52
|
+
<img width="828" height="849" alt="image" src="https://github.com/user-attachments/assets/f1b38db3-6648-4660-a381-d68a2eb8508d" />
|
|
53
|
+
|
|
54
|
+
(T1n image looks weird because that's just how it is in the zenodo dataset)
|
|
55
|
+
|
|
56
|
+
### References
|
|
57
|
+
The MRIs for all images above are from https://zenodo.org/records/7213153.
|
|
58
|
+
|
|
59
|
+
> Colin Vanden Bulcke. (2022). Open-Access DICOM MRI session (1.0) [Data set]. Zenodo. https://doi.org/10.5281/zenodo.7213153
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
The following templates are available:
|
|
66
|
+
- ``"2006 T1w symmetric"``
|
|
67
|
+
- ``"2009a T1w symmetric"``
|
|
68
|
+
- ``"2009a T2w symmetric"``
|
|
69
|
+
- ``"2009a T1w asymmetric"``
|
|
70
|
+
- ``"2009a T2w asymmetric"``
|
|
71
|
+
|
|
72
|
+
Descriptions of templates are available here https://zenodo.org/records/15470657
|
|
73
|
+
"""
|
|
74
|
+
filename = f"{type} {bool(skullstripped)}.nii.gz"
|
|
75
|
+
|
|
76
|
+
if filename in os.listdir(_ROOT):
|
|
77
|
+
return str(_ROOT / filename)
|
|
78
|
+
|
|
79
|
+
print(f"{filename} will be downloaded from https://zenodo.org/records/15470657, this may take a few minutes.")
|
|
80
|
+
_download_template(type, skullstripped)
|
|
81
|
+
|
|
82
|
+
if filename not in os.listdir(_ROOT):
|
|
83
|
+
raise RuntimeError(
|
|
84
|
+
f"Failed to download {filename}; try downloading it manually from https://zenodo.org/records/15470657"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
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 @@
|
|
|
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,81 @@
|
|
|
1
|
+
""""
|
|
2
|
+
This requires CTseg docker image to be present in the system.
|
|
3
|
+
|
|
4
|
+
If you haven't already, install docker to your OS following this https://docs.docker.com/engine/install/
|
|
5
|
+
|
|
6
|
+
CTseg is available in the following repository: https://github.com/WCHN/CTseg
|
|
7
|
+
|
|
8
|
+
Navigate to any folder, open the terminal in that folder and type in ``git clone https://github.com/WCHN/CTseg``. You might have to [install git](https://git-scm.com/install/) if you don't have it installed. This will create a new directory called ``CTseg`` in the folder and download the repository to it. Alternatively you can open the repository in your web browser, click on the green "Code" button near the top and click "Download ZIP", and unpack the archive to a folder named ``CTseg``.
|
|
9
|
+
|
|
10
|
+
Then type in the command specified in "Docker" section of the read-me in https://github.com/WCHN/CTseg to build an image from the Dockerfile in this repository. I decided to not copy the command here in case it gets updated in the repository. This will download and build a docker image, usually to ``/var/lib/docker/`` on Linux, or inside the Docker Desktop virtual machine disk image on Windows. Note that this will download 3 GB of data, so it might take some time.
|
|
11
|
+
|
|
12
|
+
After it is done, the CTseg functions from mrid can be used.
|
|
13
|
+
"""
|
|
14
|
+
import os
|
|
15
|
+
import subprocess
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from ..loading import tositk
|
|
19
|
+
|
|
20
|
+
def run_CTseg(
|
|
21
|
+
pth_ct: str | os.PathLike,
|
|
22
|
+
dir_out: str = "",
|
|
23
|
+
docker_image = "ubuntu:ctseg",
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Runs ``CTseg`` command-line routine via ``subprocess.run``.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
pth_ct (str | os.PathLike): path to a file which must be in a ``*.nii`` format.
|
|
29
|
+
dir_out (str, optional):
|
|
30
|
+
optional name of a directory that will be created next to ``path_ct`` nii file to save CTseg outputs to.
|
|
31
|
+
If empty, outputs are saved next to ``path_ct`` file. Defaults to ''.
|
|
32
|
+
docker_image (str, optional): name of the docker image that ``CTseg`` is installed in. Defaults to "ubuntu:ctseg".
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# docker run --rm -it -v "/home/jj/data":/data ubuntu:ctseg function spm_CTseg '/data/CT.nii'
|
|
36
|
+
# better
|
|
37
|
+
# docker run --rm -it -v "/home/jj/data":/data ubuntu:ctseg eval "spm_CTseg('/data/CT.nii', '', true, true, true, true, 1.0)"
|
|
38
|
+
pth_ct = Path(pth_ct)
|
|
39
|
+
|
|
40
|
+
if dir_out != "":
|
|
41
|
+
if "/" in dir_out or "\\" in dir_out:
|
|
42
|
+
raise RuntimeError(
|
|
43
|
+
"dir_out should be name of directory that will be created next to `path_ct`. "
|
|
44
|
+
f"It can't be a path. Got '{dir_out}'"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
dir_out = f"/data/{dir_out}"
|
|
48
|
+
|
|
49
|
+
command = [
|
|
50
|
+
"docker",
|
|
51
|
+
"run",
|
|
52
|
+
|
|
53
|
+
# --rm automatically removes the container's file system after the container exits. This is useful for running temporary containers.
|
|
54
|
+
"--rm",
|
|
55
|
+
|
|
56
|
+
# This is a combination of two flags, -i and -t:
|
|
57
|
+
# -i (interactive): Keeps the standard input (STDIN) open, allowing you to interact with the container.
|
|
58
|
+
# -t (tty): Allocates a pseudo-TTY, which makes the container behave like a normal terminal session. (doesn't work with subprocess)
|
|
59
|
+
#"-it",
|
|
60
|
+
"-i",
|
|
61
|
+
|
|
62
|
+
# -v used for mounting volumes, which allows you to connect a file path on your host machine to a path inside the container. This option requires a specific format: -v <host_path>:<container_path>.
|
|
63
|
+
"-v",
|
|
64
|
+
f"{os.path.normpath(pth_ct.parent)}:/data",
|
|
65
|
+
|
|
66
|
+
# docker image name
|
|
67
|
+
docker_image,
|
|
68
|
+
|
|
69
|
+
# evaluate matlab code
|
|
70
|
+
"eval",
|
|
71
|
+
|
|
72
|
+
# code to evaluate
|
|
73
|
+
f"spm_CTseg('/data/{pth_ct.name}', '{dir_out}', true, true, true, true, 1.0)",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# run dcm2niix
|
|
77
|
+
subprocess.run(command, check=True)
|
|
78
|
+
|
|
79
|
+
# this creates
|
|
80
|
+
# wc01_1_00001_temp_CT_CTseg.nii
|
|
81
|
+
# wc02_1_00001_temp_CT_CTseg
|
|
@@ -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 .spatial import downsample, resample_to, resize
|
|
4
|
+
|
|
5
|
+
# lib wrappers
|
|
6
|
+
from . import hd_bet, CTseg, simple_elastix, synthstrip, mask
|
|
7
|
+
__all__ = [
|
|
8
|
+
"n4_bias_field_correction",
|
|
9
|
+
"crop_bg", "crop_bg_D",
|
|
10
|
+
"resample_to", "resize", "downsample",
|
|
11
|
+
"hd_bet", "CTseg", "simple_elastix", "synthstrip", "mask",
|
|
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
|
+
|