MNutils 1.2.3__tar.gz → 1.3.0__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.
- {mnutils-1.2.3 → mnutils-1.3.0}/PKG-INFO +6 -12
- {mnutils-1.2.3 → mnutils-1.3.0}/README.md +5 -8
- {mnutils-1.2.3 → mnutils-1.3.0}/pyproject.toml +4 -5
- {mnutils-1.2.3 → mnutils-1.3.0}/pyproject.toml.orig +5 -5
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/GEExam.py +63 -84
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/GESeries.py +177 -189
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/__init__.py +14 -2
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/_scripts.py +34 -16
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/__init__.py +1 -13
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/image_grid.css +21 -5
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/image_grid.js +7 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/image_grid.py +8 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/images.py +228 -96
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/saving.py +23 -45
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/spectra.py +18 -17
- mnutils-1.3.0/src/mnutils/rcparams.py +449 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/testing/synthetic.py +176 -34
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/utils/data_loaders.py +163 -5
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/utils/file_helpers.py +305 -175
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/utils/images.py +9 -9
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/utils/nifti.py +277 -287
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/utils/partial_volume.py +105 -20
- mnutils-1.2.3/src/mnutils/fitting/AMARES.py +0 -831
- mnutils-1.2.3/src/mnutils/fitting/__init__.py +0 -5
- {mnutils-1.2.3 → mnutils-1.3.0}/LICENSE +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/matlab/__init__.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/matlab/setup_matlab.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/__init__.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/_html.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/_shared/__init__.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/_shared/dom.js +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/_shared/theme.css +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/mrsi_inspector.css +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/mrsi_inspector.js +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/mrsi_inspector.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/slice_viewer.css +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/slice_viewer.js +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/slice_viewer.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/py.typed +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/testing/__init__.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/testing/_spectra.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/utils/__init__.py +0 -0
- {mnutils-1.2.3 → mnutils-1.3.0}/src/mnutils/utils/spectra.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MNutils
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Utility functions for multinuclear MR data processing
|
|
5
5
|
Keywords: mri,mrs,xarray,spectroscopy,nmr
|
|
6
6
|
Author: Sebastian Bauer
|
|
@@ -14,7 +14,6 @@ Classifier: Intended Audience :: Science/Research
|
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Framework :: Jupyter
|
|
17
|
-
Requires-Dist: dcm2niiw>=0.1.5
|
|
18
17
|
Requires-Dist: ipython>=8.0
|
|
19
18
|
Requires-Dist: loguru>=0.7.3
|
|
20
19
|
Requires-Dist: mat73
|
|
@@ -26,7 +25,6 @@ Requires-Dist: scipy>=1.15.3
|
|
|
26
25
|
Requires-Dist: seaborn>=0.13.2
|
|
27
26
|
Requires-Dist: xarray>=2025.6.1
|
|
28
27
|
Requires-Dist: xmris[fitting]
|
|
29
|
-
Requires-Dist: matlabengine==25.1.* ; extra == '2025a'
|
|
30
28
|
Requires-Dist: hd-bet>=2.0.1 ; extra == 'bet'
|
|
31
29
|
Requires-Dist: torch>=2.10.0 ; extra == 'bet'
|
|
32
30
|
Requires-Python: >=3.12
|
|
@@ -34,7 +32,6 @@ Project-URL: Homepage, https://github.com/bastigw/mnutils
|
|
|
34
32
|
Project-URL: Documentation, https://bastigw.github.io/mnutils/
|
|
35
33
|
Project-URL: Repository, https://github.com/bastigw/mnutils
|
|
36
34
|
Project-URL: Issues, https://github.com/bastigw/mnutils/issues
|
|
37
|
-
Provides-Extra: 2025a
|
|
38
35
|
Provides-Extra: bet
|
|
39
36
|
Description-Content-Type: text/markdown
|
|
40
37
|
|
|
@@ -58,7 +55,7 @@ scanners.
|
|
|
58
55
|
## Features
|
|
59
56
|
|
|
60
57
|
- **Data loading** — read GE raw/reconstructed data via `GEExam`/`GESeries`.
|
|
61
|
-
- **NIfTI
|
|
58
|
+
- **NIfTI handling** — read dcm2niix-converted scanner output (NIfTI + JSON sidecar); DICOMs are not read.
|
|
62
59
|
- **Plotting** — spectral and slice plotting utilities.
|
|
63
60
|
- **Spectral fitting** — AMARES fitting delegated to [`xmris`](https://github.com/andrewendlinger/xmris)
|
|
64
61
|
(`xmris[fitting]`, which wraps pyAMARES).
|
|
@@ -78,7 +75,6 @@ Optional extras:
|
|
|
78
75
|
|
|
79
76
|
```bash
|
|
80
77
|
uv add "mnutils[bet]" # brain extraction (hd-bet, torch)
|
|
81
|
-
uv add "mnutils[2025a]" # MATLAB Engine pinned to MATLAB 2025a
|
|
82
78
|
```
|
|
83
79
|
|
|
84
80
|
To track the development version instead:
|
|
@@ -87,10 +83,8 @@ To track the development version instead:
|
|
|
87
83
|
uv add git+https://github.com/bastigw/mnutils.git
|
|
88
84
|
```
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Dependency](#matlab-engine-dependency) below) — there's no way to pin one version project-wide
|
|
93
|
-
across contributors' machines.
|
|
86
|
+
The MATLAB Engine is not an extra: its version has to match your local MATLAB install, so you
|
|
87
|
+
install it yourself (see [MATLAB Engine Dependency](#matlab-engine-dependency) below).
|
|
94
88
|
|
|
95
89
|
## Usage
|
|
96
90
|
|
|
@@ -123,8 +117,8 @@ already know a series's type, construct it directly (e.g. `MRSSeries(DATA_FOLDER
|
|
|
123
117
|
the exam-level overview entirely.
|
|
124
118
|
|
|
125
119
|
From there, spectral fitting is handled by [**xmris**](https://github.com/andrewendlinger/xmris)
|
|
126
|
-
— MNUtils deliberately doesn't reimplement fitting,
|
|
127
|
-
(itself built on pyAMARES)
|
|
120
|
+
— MNUtils deliberately doesn't reimplement fitting, a series hands its FIDs straight to xmris's
|
|
121
|
+
AMARES implementation (itself built on pyAMARES) and returns the xmris result:
|
|
128
122
|
|
|
129
123
|
```bash
|
|
130
124
|
uv add "xmris[fitting]"
|
|
@@ -18,7 +18,7 @@ scanners.
|
|
|
18
18
|
## Features
|
|
19
19
|
|
|
20
20
|
- **Data loading** — read GE raw/reconstructed data via `GEExam`/`GESeries`.
|
|
21
|
-
- **NIfTI
|
|
21
|
+
- **NIfTI handling** — read dcm2niix-converted scanner output (NIfTI + JSON sidecar); DICOMs are not read.
|
|
22
22
|
- **Plotting** — spectral and slice plotting utilities.
|
|
23
23
|
- **Spectral fitting** — AMARES fitting delegated to [`xmris`](https://github.com/andrewendlinger/xmris)
|
|
24
24
|
(`xmris[fitting]`, which wraps pyAMARES).
|
|
@@ -38,7 +38,6 @@ Optional extras:
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
uv add "mnutils[bet]" # brain extraction (hd-bet, torch)
|
|
41
|
-
uv add "mnutils[2025a]" # MATLAB Engine pinned to MATLAB 2025a
|
|
42
41
|
```
|
|
43
42
|
|
|
44
43
|
To track the development version instead:
|
|
@@ -47,10 +46,8 @@ To track the development version instead:
|
|
|
47
46
|
uv add git+https://github.com/bastigw/mnutils.git
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Dependency](#matlab-engine-dependency) below) — there's no way to pin one version project-wide
|
|
53
|
-
across contributors' machines.
|
|
49
|
+
The MATLAB Engine is not an extra: its version has to match your local MATLAB install, so you
|
|
50
|
+
install it yourself (see [MATLAB Engine Dependency](#matlab-engine-dependency) below).
|
|
54
51
|
|
|
55
52
|
## Usage
|
|
56
53
|
|
|
@@ -83,8 +80,8 @@ already know a series's type, construct it directly (e.g. `MRSSeries(DATA_FOLDER
|
|
|
83
80
|
the exam-level overview entirely.
|
|
84
81
|
|
|
85
82
|
From there, spectral fitting is handled by [**xmris**](https://github.com/andrewendlinger/xmris)
|
|
86
|
-
— MNUtils deliberately doesn't reimplement fitting,
|
|
87
|
-
(itself built on pyAMARES)
|
|
83
|
+
— MNUtils deliberately doesn't reimplement fitting, a series hands its FIDs straight to xmris's
|
|
84
|
+
AMARES implementation (itself built on pyAMARES) and returns the xmris result:
|
|
88
85
|
|
|
89
86
|
```bash
|
|
90
87
|
uv add "xmris[fitting]"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "MNutils"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0"
|
|
4
4
|
description = "Utility functions for multinuclear MR data processing"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "BSD-3-Clause"
|
|
@@ -23,7 +23,6 @@ classifiers = [
|
|
|
23
23
|
]
|
|
24
24
|
requires-python = ">=3.12"
|
|
25
25
|
dependencies = [
|
|
26
|
-
"dcm2niiw>=0.1.5",
|
|
27
26
|
"ipython>=8.0",
|
|
28
27
|
"loguru>=0.7.3",
|
|
29
28
|
"mat73",
|
|
@@ -48,7 +47,6 @@ Repository = "https://github.com/bastigw/mnutils"
|
|
|
48
47
|
Issues = "https://github.com/bastigw/mnutils/issues"
|
|
49
48
|
|
|
50
49
|
[project.optional-dependencies]
|
|
51
|
-
2025a = ["matlabengine==25.1.*"]
|
|
52
50
|
bet = [
|
|
53
51
|
"hd-bet>=2.0.1",
|
|
54
52
|
"torch>=2.10.0",
|
|
@@ -62,7 +60,7 @@ test-mnutils-gen = "mnutils._scripts:generate_test_notebooks"
|
|
|
62
60
|
test-mnutils = "mnutils._scripts:run_tests"
|
|
63
61
|
|
|
64
62
|
[build-system]
|
|
65
|
-
requires = ["uv_build>=0.8.13,<0.
|
|
63
|
+
requires = ["uv_build>=0.8.13,<0.14.0"]
|
|
66
64
|
build-backend = "uv_build"
|
|
67
65
|
|
|
68
66
|
[dependency-groups]
|
|
@@ -78,10 +76,11 @@ dev = [
|
|
|
78
76
|
"griffe<0.40.0",
|
|
79
77
|
"jupyter-server>=2.17.0",
|
|
80
78
|
"jupytext>=1.19.1",
|
|
79
|
+
"pytest-xdist>=3.8.0",
|
|
81
80
|
]
|
|
82
81
|
|
|
83
82
|
[tool.pytest.ini_options]
|
|
84
|
-
addopts = "--
|
|
83
|
+
addopts = "--nbmake -n auto -v --durations=0"
|
|
85
84
|
testpaths = ["tests"]
|
|
86
85
|
|
|
87
86
|
[tool.uv.sources.mat73]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "MNutils"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0"
|
|
4
4
|
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Sebastian Bauer", email = "28663646+bastigw@users.noreply.github.com"}
|
|
@@ -25,7 +25,6 @@ classifiers = [
|
|
|
25
25
|
requires-python = ">=3.12"
|
|
26
26
|
|
|
27
27
|
dependencies = [
|
|
28
|
-
"dcm2niiw>=0.1.5",
|
|
29
28
|
"ipython>=8.0",
|
|
30
29
|
"loguru>=0.7.3",
|
|
31
30
|
"mat73",
|
|
@@ -46,7 +45,6 @@ Repository = "https://github.com/bastigw/mnutils"
|
|
|
46
45
|
Issues = "https://github.com/bastigw/mnutils/issues"
|
|
47
46
|
|
|
48
47
|
[project.optional-dependencies]
|
|
49
|
-
2025a = ["matlabengine==25.1.*"]
|
|
50
48
|
bet = ["hd-bet>=2.0.1", "torch>=2.10.0"]
|
|
51
49
|
|
|
52
50
|
[project.scripts]
|
|
@@ -57,7 +55,7 @@ test-mnutils-gen = "mnutils._scripts:generate_test_notebooks" # docs/*.md -> te
|
|
|
57
55
|
test-mnutils = "mnutils._scripts:run_tests" # test-gen, then run the full pytest suite
|
|
58
56
|
|
|
59
57
|
[build-system]
|
|
60
|
-
requires = ["uv_build>=0.8.13,<0.
|
|
58
|
+
requires = ["uv_build>=0.8.13,<0.14.0"]
|
|
61
59
|
build-backend = "uv_build"
|
|
62
60
|
|
|
63
61
|
[dependency-groups]
|
|
@@ -77,10 +75,12 @@ dev = [
|
|
|
77
75
|
"griffe<0.40.0", # Explicit pin to fix quartodoc parsing bug
|
|
78
76
|
"jupyter-server>=2.17.0", # Required for rendering docs
|
|
79
77
|
"jupytext>=1.19.1",
|
|
78
|
+
"pytest-xdist>=3.8.0",
|
|
80
79
|
]
|
|
81
80
|
|
|
82
81
|
[tool.pytest.ini_options]
|
|
83
|
-
|
|
82
|
+
# See conftest.py for further details
|
|
83
|
+
addopts = "--nbmake -n auto -v --durations=0"
|
|
84
84
|
testpaths = ["tests"]
|
|
85
85
|
|
|
86
86
|
# [tool.uv]
|
|
@@ -16,6 +16,11 @@ from .utils import file_helpers
|
|
|
16
16
|
ANATOMICAL_SERIES_TO_SKIP = ["FGRE", "localizer", "GE_HOS", "localiser"]
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def _normalise_description(description: object) -> str:
|
|
20
|
+
"""Lower-case, spaces as underscores: ``MRS washin`` -> ``mrs_washin``."""
|
|
21
|
+
return description.strip().lower().replace(" ", "_") if isinstance(description, str) else ""
|
|
22
|
+
|
|
23
|
+
|
|
19
24
|
class ExamBase:
|
|
20
25
|
"""Base class for a GE exam, loading series metadata and providing series access."""
|
|
21
26
|
|
|
@@ -24,6 +29,7 @@ class ExamBase:
|
|
|
24
29
|
BASE_FOLDER: str | Path,
|
|
25
30
|
DATA_FOLDER: str | Path | None = None,
|
|
26
31
|
OUTPUT_FOLDER: str | Path | None = None,
|
|
32
|
+
show_overview: bool = False,
|
|
27
33
|
) -> None:
|
|
28
34
|
self.BASE_FOLDER = Path(BASE_FOLDER)
|
|
29
35
|
if DATA_FOLDER is None:
|
|
@@ -37,7 +43,9 @@ class ExamBase:
|
|
|
37
43
|
else:
|
|
38
44
|
self.OUTPUT_FOLDER = Path(OUTPUT_FOLDER)
|
|
39
45
|
|
|
40
|
-
self.exam_overview = file_helpers.get_exam_overview(
|
|
46
|
+
self.exam_overview = file_helpers.get_exam_overview(
|
|
47
|
+
self.DATA_FOLDER, print_overview=show_overview
|
|
48
|
+
)
|
|
41
49
|
self.series = self.exam_overview.index.tolist()
|
|
42
50
|
self.series_dict = self._create_series_dict()
|
|
43
51
|
|
|
@@ -69,60 +77,45 @@ class ExamBase:
|
|
|
69
77
|
"""Iterate over loaded series as (series ID, series object) pairs."""
|
|
70
78
|
yield from self.all.items()
|
|
71
79
|
|
|
80
|
+
def _ids_with_description(self, fragment: str) -> npt.NDArray[np.int_]:
|
|
81
|
+
"""Series IDs whose normalised description contains `fragment` (also normalised)."""
|
|
82
|
+
normalised = self.exam_overview["Description"].map(_normalise_description)
|
|
83
|
+
mask = normalised.str.contains(_normalise_description(fragment), regex=False)
|
|
84
|
+
return self.exam_overview[mask].index.to_numpy().astype(int)
|
|
85
|
+
|
|
72
86
|
def _create_series_dict(self) -> dict[int, type[GESeries.MRISeries]]:
|
|
87
|
+
"""Pick a series class per series from its description, never from a folder name."""
|
|
73
88
|
series_class_dict = {}
|
|
74
89
|
for series_num in self.series:
|
|
75
|
-
|
|
90
|
+
raw_description = self.exam_overview.at[series_num, "Description"]
|
|
91
|
+
description = _normalise_description(raw_description)
|
|
76
92
|
has_exam = self.exam_overview.at[series_num, "Has Exam"]
|
|
77
|
-
has_dicom = self.exam_overview.at[series_num, "Has DICOM"]
|
|
78
|
-
|
|
79
|
-
if has_exam and not has_dicom:
|
|
80
|
-
logger.debug(
|
|
81
|
-
f"Series {series_num} does not have DICOM. Assuming it is an MRS series."
|
|
82
|
-
)
|
|
83
|
-
self.exam_overview.at[series_num, "Folder Name"] = "ASSUMING MRS"
|
|
84
|
-
series_class_dict[series_num] = GESeries.MRSSeries
|
|
85
|
-
continue
|
|
86
|
-
|
|
87
|
-
# Make sure folder name is a string
|
|
88
|
-
if isinstance(folder_name, str) and has_dicom:
|
|
89
|
-
folder_name = folder_name.strip().lower()
|
|
90
|
-
else:
|
|
91
|
-
logger.warning(
|
|
92
|
-
f"Folder name for series {series_num} is not a string: {folder_name}. Skipping."
|
|
93
|
-
)
|
|
94
|
-
continue
|
|
95
|
-
|
|
96
|
-
if not has_exam and not has_dicom:
|
|
97
|
-
logger.debug(f"Series {series_num} does not have exam or DICOM data. Skipping")
|
|
98
|
-
continue
|
|
99
93
|
|
|
100
|
-
if
|
|
101
|
-
|
|
102
|
-
# Based on the folder name determine if it is anatomical data we want to load
|
|
103
|
-
if any(skip_name.lower() in folder_name for skip_name in ANATOMICAL_SERIES_TO_SKIP):
|
|
94
|
+
if not has_exam:
|
|
95
|
+
if any(skip_name.lower() in description for skip_name in ANATOMICAL_SERIES_TO_SKIP):
|
|
104
96
|
logger.debug(f"Series {series_num} is anatomical data to skip.")
|
|
105
97
|
continue
|
|
106
98
|
series_class_dict[series_num] = GESeries.MRISeries
|
|
107
99
|
continue
|
|
108
100
|
|
|
109
|
-
if
|
|
110
|
-
logger.
|
|
111
|
-
f"Series {series_num} has
|
|
101
|
+
if not description:
|
|
102
|
+
logger.debug(
|
|
103
|
+
f"Series {series_num} has no series description. Assuming it is an MRS series."
|
|
104
|
+
)
|
|
105
|
+
series_class_dict[series_num] = GESeries.MRSSeries
|
|
106
|
+
elif "mrs_washin" in description:
|
|
107
|
+
series_class_dict[series_num] = GESeries.MRSWashinSeries
|
|
108
|
+
elif "mrsi" in description:
|
|
109
|
+
series_class_dict[series_num] = GESeries.MRSISeries
|
|
110
|
+
elif "mrs_" in description:
|
|
111
|
+
series_class_dict[series_num] = GESeries.MRSSeries
|
|
112
|
+
elif "bs_prescan" in description:
|
|
113
|
+
continue # Skip the bs_prescan series
|
|
114
|
+
else:
|
|
115
|
+
logger.warning(
|
|
116
|
+
f"Series {series_num} with description '{raw_description}' does not "
|
|
117
|
+
f"match any known series types. Skipping."
|
|
112
118
|
)
|
|
113
|
-
if "mrs_washin" in folder_name:
|
|
114
|
-
series_class_dict[series_num] = GESeries.MRSWashinSeries
|
|
115
|
-
elif "mrsi_pseudo" in folder_name:
|
|
116
|
-
series_class_dict[series_num] = GESeries.MRSISeries
|
|
117
|
-
elif "mrs_" in folder_name:
|
|
118
|
-
series_class_dict[series_num] = GESeries.MRSSeries
|
|
119
|
-
elif "bs_prescan" in folder_name:
|
|
120
|
-
continue # Skip the bs_prescan series
|
|
121
|
-
else:
|
|
122
|
-
logger.warning(
|
|
123
|
-
f"Series {series_num} with folder name '{folder_name}' does not "
|
|
124
|
-
f"match any known series types. Skipping."
|
|
125
|
-
)
|
|
126
119
|
|
|
127
120
|
return series_class_dict
|
|
128
121
|
|
|
@@ -180,8 +173,9 @@ class DMIExam(ExamBase):
|
|
|
180
173
|
DATA_FOLDER: str | Path | None = None,
|
|
181
174
|
OUTPUT_FOLDER: str | Path | None = None,
|
|
182
175
|
ANATOMICAL_series: str | int | None = None,
|
|
176
|
+
show_overview: bool = False,
|
|
183
177
|
) -> None:
|
|
184
|
-
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER)
|
|
178
|
+
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, show_overview)
|
|
185
179
|
|
|
186
180
|
if isinstance(ANATOMICAL_series, int):
|
|
187
181
|
self.ANATOMICAL_series = ANATOMICAL_series
|
|
@@ -190,33 +184,22 @@ class DMIExam(ExamBase):
|
|
|
190
184
|
identifier = ANATOMICAL_series.strip().lower()
|
|
191
185
|
else:
|
|
192
186
|
identifier = "t1_bravo"
|
|
193
|
-
self.ANATOMICAL_series = (
|
|
194
|
-
self.exam_overview[
|
|
195
|
-
self.exam_overview["Folder Name"].str.contains(identifier, na=False, case=False)
|
|
196
|
-
]
|
|
197
|
-
.index.to_numpy()
|
|
198
|
-
.astype(int)[0]
|
|
199
|
-
)
|
|
187
|
+
self.ANATOMICAL_series = self._ids_with_description(identifier)[0]
|
|
200
188
|
|
|
201
|
-
self.MRS_series = (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
]
|
|
205
|
-
.index.to_numpy()
|
|
206
|
-
.astype(int)
|
|
189
|
+
self.MRS_series = self._ids_with_description("MRS_unloc")
|
|
190
|
+
# By default append all series without a NIfTI (raw data only)
|
|
191
|
+
no_nifti_series = (
|
|
192
|
+
self.exam_overview[~self.exam_overview["Has NIfTI"]].index.to_numpy().astype(int)
|
|
207
193
|
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
]
|
|
218
|
-
.index.to_numpy()
|
|
219
|
-
.astype(int)
|
|
194
|
+
self.MRS_series = np.union1d(self.MRS_series, no_nifti_series)
|
|
195
|
+
|
|
196
|
+
self.MRSI_series = np.array(
|
|
197
|
+
[
|
|
198
|
+
series_id
|
|
199
|
+
for series_id, series_class in self.series_dict.items()
|
|
200
|
+
if issubclass(series_class, GESeries.MRSISeries)
|
|
201
|
+
],
|
|
202
|
+
dtype=int,
|
|
220
203
|
)
|
|
221
204
|
|
|
222
205
|
self.anatomical: GESeries.MRISeries
|
|
@@ -253,9 +236,9 @@ class DMIExam(ExamBase):
|
|
|
253
236
|
logger.warning(
|
|
254
237
|
f"Multiple anatomical datasets detected. "
|
|
255
238
|
f"Previous: Series ID {self.anatomical.SERIES_ID}, "
|
|
256
|
-
f"Name '{self.exam_overview.at[self.anatomical.SERIES_ID, '
|
|
239
|
+
f"Name '{self.exam_overview.at[self.anatomical.SERIES_ID, 'Description']}'."
|
|
257
240
|
f" New: Series ID {series_obj.SERIES_ID}, "
|
|
258
|
-
f"Name '{self.exam_overview.at[series_obj.SERIES_ID, '
|
|
241
|
+
f"Name '{self.exam_overview.at[series_obj.SERIES_ID, 'Description']}'."
|
|
259
242
|
)
|
|
260
243
|
self.anatomical = series_obj
|
|
261
244
|
|
|
@@ -269,9 +252,10 @@ class MS_DMIExam(DMIExam):
|
|
|
269
252
|
DATA_FOLDER: str | Path | None = None,
|
|
270
253
|
OUTPUT_FOLDER: str | Path | None = None,
|
|
271
254
|
ANATOMICAL_series: str | int | None = None,
|
|
255
|
+
show_overview: bool = False,
|
|
272
256
|
) -> None:
|
|
273
|
-
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, ANATOMICAL_series)
|
|
274
|
-
logger.
|
|
257
|
+
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, ANATOMICAL_series, show_overview)
|
|
258
|
+
logger.debug("MS DMI Exam initialized.")
|
|
275
259
|
|
|
276
260
|
self.MRSI: GESeries.MRSISeries
|
|
277
261
|
self.pre_MRS: GESeries.MRSSeries
|
|
@@ -330,16 +314,11 @@ class DMIinjExam(DMIExam):
|
|
|
330
314
|
DATA_FOLDER: str | Path | None = None,
|
|
331
315
|
OUTPUT_FOLDER: str | Path | None = None,
|
|
332
316
|
ANATOMICAL_series: str | int | None = None,
|
|
317
|
+
show_overview: bool = False,
|
|
333
318
|
) -> None:
|
|
334
|
-
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, ANATOMICAL_series)
|
|
335
|
-
|
|
336
|
-
self.WASHIN_series = (
|
|
337
|
-
self.exam_overview[
|
|
338
|
-
self.exam_overview["Folder Name"].str.contains("MRS_washin", na=False)
|
|
339
|
-
]
|
|
340
|
-
.index.to_numpy()
|
|
341
|
-
.astype(int)[0]
|
|
342
|
-
)
|
|
319
|
+
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, ANATOMICAL_series, show_overview)
|
|
320
|
+
|
|
321
|
+
self.WASHIN_series = self._ids_with_description("MRS_washin")[0]
|
|
343
322
|
|
|
344
323
|
# Define pre and post series based on wash-in series
|
|
345
324
|
self.pre_MRS_series, self.post_MRS_series = self._split_series(
|
|
@@ -349,7 +328,7 @@ class DMIinjExam(DMIExam):
|
|
|
349
328
|
self.MRSI_series, self.WASHIN_series, "MRSI"
|
|
350
329
|
)
|
|
351
330
|
|
|
352
|
-
logger.
|
|
331
|
+
logger.debug("DMI Intravenous Injection Exam initialized.")
|
|
353
332
|
|
|
354
333
|
self.washin: GESeries.MRSWashinSeries
|
|
355
334
|
self.pre_MRS: GESeries.MRSSeries
|