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.
Files changed (39) hide show
  1. mrid_python-0.1.5/PKG-INFO +73 -0
  2. mrid_python-0.1.5/README.md +59 -0
  3. mrid_python-0.1.5/mrid/__init__.py +5 -0
  4. mrid_python-0.1.5/mrid/atlas/MNI152/__init__.py +87 -0
  5. mrid_python-0.1.5/mrid/atlas/SRI24/__init__.py +77 -0
  6. mrid_python-0.1.5/mrid/atlas/__init__.py +7 -0
  7. mrid_python-0.1.5/mrid/loading/__init__.py +1 -0
  8. mrid_python-0.1.5/mrid/loading/convert.py +68 -0
  9. mrid_python-0.1.5/mrid/preprocessing/CTseg.py +81 -0
  10. mrid_python-0.1.5/mrid/preprocessing/__init__.py +12 -0
  11. mrid_python-0.1.5/mrid/preprocessing/bias_field_correction.py +28 -0
  12. mrid_python-0.1.5/mrid/preprocessing/cropping.py +36 -0
  13. mrid_python-0.1.5/mrid/preprocessing/hd_bet.py +242 -0
  14. mrid_python-0.1.5/mrid/preprocessing/mask.py +41 -0
  15. mrid_python-0.1.5/mrid/preprocessing/simple_elastix.py +189 -0
  16. mrid_python-0.1.5/mrid/preprocessing/spatial.py +82 -0
  17. mrid_python-0.1.5/mrid/preprocessing/synthstrip.py +270 -0
  18. mrid_python-0.1.5/mrid/study.py +644 -0
  19. mrid_python-0.1.5/mrid/training/__init__.py +0 -0
  20. mrid_python-0.1.5/mrid/training/slicer.py +244 -0
  21. mrid_python-0.1.5/mrid/training/transforms.py +109 -0
  22. mrid_python-0.1.5/mrid/utils/__init__.py +4 -0
  23. mrid_python-0.1.5/mrid/utils/dcm2niix.py +90 -0
  24. mrid_python-0.1.5/mrid/utils/dicom_uid_fixer.py +86 -0
  25. mrid_python-0.1.5/mrid/utils/plotting.py +106 -0
  26. mrid_python-0.1.5/mrid/utils/python_utils.py +48 -0
  27. mrid_python-0.1.5/mrid/utils/stl_utils.py +142 -0
  28. mrid_python-0.1.5/mrid/utils/torch_utils.py +16 -0
  29. mrid_python-0.1.5/mrid_python.egg-info/PKG-INFO +73 -0
  30. mrid_python-0.1.5/mrid_python.egg-info/SOURCES.txt +37 -0
  31. mrid_python-0.1.5/mrid_python.egg-info/dependency_links.txt +1 -0
  32. mrid_python-0.1.5/mrid_python.egg-info/requires.txt +2 -0
  33. mrid_python-0.1.5/mrid_python.egg-info/top_level.txt +4 -0
  34. mrid_python-0.1.5/pyproject.toml +45 -0
  35. mrid_python-0.1.5/setup.cfg +4 -0
  36. mrid_python-0.1.5/tests/test_loading.py +82 -0
  37. mrid_python-0.1.5/tests/test_preprocessing.py +43 -0
  38. mrid_python-0.1.5/tests/test_study.py +136 -0
  39. 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,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,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,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,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
+