MNutils 1.2.4__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.4 → mnutils-1.3.0}/PKG-INFO +3 -3
- {mnutils-1.2.4 → mnutils-1.3.0}/README.md +2 -2
- {mnutils-1.2.4 → mnutils-1.3.0}/pyproject.toml +1 -1
- {mnutils-1.2.4 → mnutils-1.3.0}/pyproject.toml.orig +1 -1
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/GEExam.py +12 -6
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/GESeries.py +96 -167
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/__init__.py +1 -2
- mnutils-1.2.4/src/mnutils/fitting/AMARES.py +0 -831
- mnutils-1.2.4/src/mnutils/fitting/__init__.py +0 -16
- {mnutils-1.2.4 → mnutils-1.3.0}/LICENSE +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/_scripts.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/matlab/__init__.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/matlab/setup_matlab.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/__init__.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/__init__.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/_html.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/_shared/__init__.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/_shared/dom.js +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/_shared/theme.css +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/image_grid.css +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/image_grid.js +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/image_grid.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/mrsi_inspector.css +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/mrsi_inspector.js +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/mrsi_inspector.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/slice_viewer.css +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/slice_viewer.js +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/_widgets/slice_viewer.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/images.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/saving.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/plotting/spectra.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/py.typed +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/rcparams.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/testing/__init__.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/testing/_spectra.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/testing/synthetic.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/utils/__init__.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/utils/data_loaders.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/utils/file_helpers.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/utils/images.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/utils/nifti.py +0 -0
- {mnutils-1.2.4 → mnutils-1.3.0}/src/mnutils/utils/partial_volume.py +0 -0
- {mnutils-1.2.4 → 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
|
|
@@ -117,8 +117,8 @@ already know a series's type, construct it directly (e.g. `MRSSeries(DATA_FOLDER
|
|
|
117
117
|
the exam-level overview entirely.
|
|
118
118
|
|
|
119
119
|
From there, spectral fitting is handled by [**xmris**](https://github.com/andrewendlinger/xmris)
|
|
120
|
-
— MNUtils deliberately doesn't reimplement fitting,
|
|
121
|
-
(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:
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
124
|
uv add "xmris[fitting]"
|
|
@@ -80,8 +80,8 @@ already know a series's type, construct it directly (e.g. `MRSSeries(DATA_FOLDER
|
|
|
80
80
|
the exam-level overview entirely.
|
|
81
81
|
|
|
82
82
|
From there, spectral fitting is handled by [**xmris**](https://github.com/andrewendlinger/xmris)
|
|
83
|
-
— MNUtils deliberately doesn't reimplement fitting,
|
|
84
|
-
(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:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
87
|
uv add "xmris[fitting]"
|
|
@@ -29,6 +29,7 @@ class ExamBase:
|
|
|
29
29
|
BASE_FOLDER: str | Path,
|
|
30
30
|
DATA_FOLDER: str | Path | None = None,
|
|
31
31
|
OUTPUT_FOLDER: str | Path | None = None,
|
|
32
|
+
show_overview: bool = False,
|
|
32
33
|
) -> None:
|
|
33
34
|
self.BASE_FOLDER = Path(BASE_FOLDER)
|
|
34
35
|
if DATA_FOLDER is None:
|
|
@@ -42,7 +43,9 @@ class ExamBase:
|
|
|
42
43
|
else:
|
|
43
44
|
self.OUTPUT_FOLDER = Path(OUTPUT_FOLDER)
|
|
44
45
|
|
|
45
|
-
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
|
+
)
|
|
46
49
|
self.series = self.exam_overview.index.tolist()
|
|
47
50
|
self.series_dict = self._create_series_dict()
|
|
48
51
|
|
|
@@ -170,8 +173,9 @@ class DMIExam(ExamBase):
|
|
|
170
173
|
DATA_FOLDER: str | Path | None = None,
|
|
171
174
|
OUTPUT_FOLDER: str | Path | None = None,
|
|
172
175
|
ANATOMICAL_series: str | int | None = None,
|
|
176
|
+
show_overview: bool = False,
|
|
173
177
|
) -> None:
|
|
174
|
-
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER)
|
|
178
|
+
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, show_overview)
|
|
175
179
|
|
|
176
180
|
if isinstance(ANATOMICAL_series, int):
|
|
177
181
|
self.ANATOMICAL_series = ANATOMICAL_series
|
|
@@ -248,9 +252,10 @@ class MS_DMIExam(DMIExam):
|
|
|
248
252
|
DATA_FOLDER: str | Path | None = None,
|
|
249
253
|
OUTPUT_FOLDER: str | Path | None = None,
|
|
250
254
|
ANATOMICAL_series: str | int | None = None,
|
|
255
|
+
show_overview: bool = False,
|
|
251
256
|
) -> None:
|
|
252
|
-
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, ANATOMICAL_series)
|
|
253
|
-
logger.
|
|
257
|
+
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, ANATOMICAL_series, show_overview)
|
|
258
|
+
logger.debug("MS DMI Exam initialized.")
|
|
254
259
|
|
|
255
260
|
self.MRSI: GESeries.MRSISeries
|
|
256
261
|
self.pre_MRS: GESeries.MRSSeries
|
|
@@ -309,8 +314,9 @@ class DMIinjExam(DMIExam):
|
|
|
309
314
|
DATA_FOLDER: str | Path | None = None,
|
|
310
315
|
OUTPUT_FOLDER: str | Path | None = None,
|
|
311
316
|
ANATOMICAL_series: str | int | None = None,
|
|
317
|
+
show_overview: bool = False,
|
|
312
318
|
) -> None:
|
|
313
|
-
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, ANATOMICAL_series)
|
|
319
|
+
super().__init__(BASE_FOLDER, DATA_FOLDER, OUTPUT_FOLDER, ANATOMICAL_series, show_overview)
|
|
314
320
|
|
|
315
321
|
self.WASHIN_series = self._ids_with_description("MRS_washin")[0]
|
|
316
322
|
|
|
@@ -322,7 +328,7 @@ class DMIinjExam(DMIExam):
|
|
|
322
328
|
self.MRSI_series, self.WASHIN_series, "MRSI"
|
|
323
329
|
)
|
|
324
330
|
|
|
325
|
-
logger.
|
|
331
|
+
logger.debug("DMI Intravenous Injection Exam initialized.")
|
|
326
332
|
|
|
327
333
|
self.washin: GESeries.MRSWashinSeries
|
|
328
334
|
self.pre_MRS: GESeries.MRSSeries
|
|
@@ -7,13 +7,12 @@ from pathlib import Path
|
|
|
7
7
|
import h5py
|
|
8
8
|
import numpy as np
|
|
9
9
|
import numpy.typing as npt
|
|
10
|
-
import pandas as pd
|
|
11
10
|
import xarray as xr
|
|
12
11
|
import xmris # noqa: F401, necessary for xarray accessor to work
|
|
13
12
|
from loguru import logger
|
|
14
13
|
from nibabel import nifti1, spatialimages
|
|
15
14
|
|
|
16
|
-
from . import
|
|
15
|
+
from . import plotting
|
|
17
16
|
from .utils import data_loaders, file_helpers, nifti, spectra
|
|
18
17
|
|
|
19
18
|
|
|
@@ -730,6 +729,7 @@ class MRSISeries(RawMRISeries):
|
|
|
730
729
|
|
|
731
730
|
self.fitted_metabolite_maps: dict = {}
|
|
732
731
|
self.goodness_of_fit_maps: dict = {}
|
|
732
|
+
self.fit_results: xr.Dataset | None = None
|
|
733
733
|
if load_processed_data:
|
|
734
734
|
self.load_processed_data()
|
|
735
735
|
|
|
@@ -849,59 +849,59 @@ class MRSISeries(RawMRISeries):
|
|
|
849
849
|
x: int,
|
|
850
850
|
y: int,
|
|
851
851
|
slice: int,
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
):
|
|
855
|
-
"""Fit the FID at voxel (x, y, slice) with AMARES.
|
|
856
|
-
if fit_params is None:
|
|
857
|
-
fit_params = {}
|
|
858
|
-
fid = self.fids.sel(i=x, j=y, k=slice)
|
|
852
|
+
prior_knowledge,
|
|
853
|
+
**fit_kwargs,
|
|
854
|
+
) -> xr.Dataset:
|
|
855
|
+
"""Fit the FID at voxel (x, y, slice) with AMARES (via ``xmris``).
|
|
859
856
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
857
|
+
Parameters
|
|
858
|
+
----------
|
|
859
|
+
x, y, slice : int
|
|
860
|
+
Voxel indices.
|
|
861
|
+
prior_knowledge : Mapping | pandas.DataFrame | str | Path
|
|
862
|
+
Prior knowledge, forwarded to ``xmris`` ``fit_amares``.
|
|
863
|
+
**fit_kwargs
|
|
864
|
+
Further keyword arguments for ``fit_amares`` (``method``, ``g_global``, ...).
|
|
865
|
+
|
|
866
|
+
Returns
|
|
867
|
+
-------
|
|
868
|
+
xarray.Dataset
|
|
869
|
+
The ``fit_amares`` result for this voxel.
|
|
870
|
+
"""
|
|
871
|
+
fid = self.fids.sel(i=x, j=y, k=slice)
|
|
872
|
+
return fid.xmr.fit_amares(prior_knowledge, **fit_kwargs)
|
|
874
873
|
|
|
875
874
|
def fit_all_voxels(
|
|
876
875
|
self,
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
bad_fit_chisqr_threshold: float = -1.0,
|
|
876
|
+
prior_knowledge,
|
|
877
|
+
snr_threshold: float = 1.5,
|
|
880
878
|
testing_mode: bool = False,
|
|
881
|
-
|
|
879
|
+
**fit_kwargs,
|
|
880
|
+
) -> xr.Dataset:
|
|
882
881
|
"""Fit all voxels above the SNR threshold with AMARES and store the maps.
|
|
883
882
|
|
|
884
883
|
Parameters
|
|
885
884
|
----------
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
bad_fit_chisqr_threshold : float, optional
|
|
891
|
-
Chi-squared threshold above which a fit is flagged as bad. Defaults to -1.0
|
|
892
|
-
(disabled).
|
|
885
|
+
prior_knowledge : Mapping | pandas.DataFrame | str | Path
|
|
886
|
+
Prior knowledge, forwarded to ``xmris`` ``fit_amares``.
|
|
887
|
+
snr_threshold : float, optional
|
|
888
|
+
Voxels with ``SNR_map`` below this are not fitted. Defaults to 1.5.
|
|
893
889
|
testing_mode : bool, optional
|
|
894
890
|
If True, limit fitting to the first 100 voxels above the SNR threshold.
|
|
891
|
+
**fit_kwargs
|
|
892
|
+
Further keyword arguments for ``fit_amares`` (``method``, ``g_global``, ...).
|
|
893
|
+
``num_workers`` defaults to ``-1`` (every core) here, unlike ``xmris``'s
|
|
894
|
+
in-process default of 1, because a voxel grid is exactly where a pool pays off.
|
|
895
895
|
|
|
896
896
|
Returns
|
|
897
897
|
-------
|
|
898
|
-
|
|
899
|
-
|
|
898
|
+
xarray.Dataset
|
|
899
|
+
The ``fit_amares`` result on the full ``(i, j, k)`` grid; voxels that were not
|
|
900
|
+
fitted are NaN. Also stored as ``fit_results`` and summarised in
|
|
901
|
+
``fitted_metabolite_maps`` and ``goodness_of_fit_maps``.
|
|
900
902
|
"""
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
SNR_mask = self.get_SNR_mask()
|
|
904
|
-
# If in testing mode, limit to first 100 voxels with SNR above threshold
|
|
903
|
+
grid = ("i", "j", "k")
|
|
904
|
+
SNR_mask = self.get_SNR_mask(snr_threshold)
|
|
905
905
|
if testing_mode:
|
|
906
906
|
flat_SNR_mask = SNR_mask.stack(voxel=SNR_mask.dims)
|
|
907
907
|
indices = np.where(flat_SNR_mask)[0][:100]
|
|
@@ -909,69 +909,28 @@ class MRSISeries(RawMRISeries):
|
|
|
909
909
|
SNR_mask[indices] = True
|
|
910
910
|
SNR_mask = SNR_mask.unstack("voxel")
|
|
911
911
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
results = fitting.AMARES.fit_multiple_fids(
|
|
921
|
-
self.fids.where(SNR_mask).stack(voxel=SNR_mask.dims).dropna("voxel"),
|
|
922
|
-
batch_fitting_params=batch_fitting_params,
|
|
923
|
-
init_params=passed_init_params,
|
|
924
|
-
)
|
|
925
|
-
|
|
926
|
-
# Extract results back into original shape
|
|
927
|
-
extracted_results = fitting.AMARES.extract_from_fit_results(
|
|
928
|
-
results, bad_fit_chisqr_threshold
|
|
912
|
+
fit_kwargs.setdefault("num_workers", -1)
|
|
913
|
+
stacked = self.fids.where(SNR_mask).stack(voxel=SNR_mask.dims).dropna("voxel")
|
|
914
|
+
# fit_amares stacks the non-time dims itself, so hand it a plain voxel dim
|
|
915
|
+
fitted = stacked.reset_index("voxel").xmr.fit_amares(prior_knowledge, **fit_kwargs)
|
|
916
|
+
results = (
|
|
917
|
+
fitted.set_index(voxel=list(grid))
|
|
918
|
+
.unstack("voxel")
|
|
919
|
+
.reindex(i=self.fids["i"], j=self.fids["j"], k=self.fids["k"])
|
|
929
920
|
)
|
|
921
|
+
self.fit_results = results
|
|
930
922
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
"
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
fitting.AMARES.save_extracted_results(
|
|
943
|
-
self.OUTPUT_FOLDER
|
|
944
|
-
/ "processing_data"
|
|
945
|
-
/ "dev"
|
|
946
|
-
/ f"Series{self.SERIES_ID:02d}_extracted_results.h5",
|
|
947
|
-
extracted_results,
|
|
948
|
-
additional_attributes,
|
|
949
|
-
additional_datasets=additional_datasets,
|
|
950
|
-
prepend_datetime=True,
|
|
923
|
+
amplitude = results["amplitude"]
|
|
924
|
+
for metabolite in amplitude["metabolite"].values:
|
|
925
|
+
self.fitted_metabolite_maps[str(metabolite)] = (
|
|
926
|
+
amplitude.sel(metabolite=metabolite).transpose(*grid).to_numpy()
|
|
927
|
+
)
|
|
928
|
+
residual_rms = np.sqrt((np.abs(results["residuals"]) ** 2).mean("time"))
|
|
929
|
+
self.goodness_of_fit_maps["residual_rms"] = residual_rms.transpose(*grid).to_numpy()
|
|
930
|
+
self.goodness_of_fit_maps["fit_status"] = (
|
|
931
|
+
results["fit_status"].transpose(*grid).astype(float).to_numpy()
|
|
951
932
|
)
|
|
952
|
-
|
|
953
|
-
combined_fit_results = extracted_results.combined_fit_results
|
|
954
|
-
fitted_metabolites_temp = np.full((*self.dims, combined_fit_results.columns.size), np.nan)
|
|
955
|
-
fitted_metabolites_temp[SNR_mask] = combined_fit_results.loc[
|
|
956
|
-
(slice(None), "amplitude", "value"), :
|
|
957
|
-
].to_numpy()
|
|
958
|
-
for i, metabolite in enumerate(combined_fit_results.columns):
|
|
959
|
-
self.fitted_metabolite_maps[metabolite] = fitted_metabolites_temp[:, :, :, i]
|
|
960
|
-
|
|
961
|
-
goodness_of_fit = extracted_results.goodness_of_fit
|
|
962
|
-
gof_metrics_temp = np.full((*self.dims, goodness_of_fit.columns.size), np.nan)
|
|
963
|
-
gof_metrics_temp[SNR_mask] = goodness_of_fit.to_numpy()
|
|
964
|
-
for i, metric in enumerate(goodness_of_fit.columns):
|
|
965
|
-
self.goodness_of_fit_maps[metric] = gof_metrics_temp[:, :, :, i]
|
|
966
|
-
|
|
967
|
-
# The badfit_ids correspond to the flattened indices of the fitted voxels
|
|
968
|
-
# Create mapping from badfit_ids to original voxel flattened indices
|
|
969
|
-
if extracted_results.badfit_ids.size == 0:
|
|
970
|
-
original_badfit_ids = np.array([], dtype=int)
|
|
971
|
-
else:
|
|
972
|
-
original_badfit_ids = np.where(SNR_mask.flatten())[0][extracted_results.badfit_ids]
|
|
973
|
-
|
|
974
|
-
return original_badfit_ids
|
|
933
|
+
return results
|
|
975
934
|
|
|
976
935
|
def save_processed_data(self, output_folder: Path | None = None) -> Path:
|
|
977
936
|
"""Save fitted maps, FIDs, and identifying attributes to an HDF5 file.
|
|
@@ -1515,15 +1474,27 @@ class MRSSeries(RawMRISeries):
|
|
|
1515
1474
|
|
|
1516
1475
|
def fit_average_fid(
|
|
1517
1476
|
self,
|
|
1518
|
-
|
|
1519
|
-
fit_params: dict | None = None,
|
|
1477
|
+
prior_knowledge,
|
|
1520
1478
|
normalisation_factor: np.complexfloating | None = None,
|
|
1521
|
-
|
|
1522
|
-
):
|
|
1523
|
-
"""Fit the averaged FID (avg_fid) with AMARES, normalizing it first.
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1479
|
+
**fit_kwargs,
|
|
1480
|
+
) -> xr.Dataset:
|
|
1481
|
+
"""Fit the averaged FID (avg_fid) with AMARES (via ``xmris``), normalizing it first.
|
|
1482
|
+
|
|
1483
|
+
Parameters
|
|
1484
|
+
----------
|
|
1485
|
+
prior_knowledge : Mapping | pandas.DataFrame | str | Path
|
|
1486
|
+
Prior knowledge, forwarded to ``xmris`` ``fit_amares``.
|
|
1487
|
+
normalisation_factor : complex, optional
|
|
1488
|
+
Scaling shared across series so amplitudes are comparable. Defaults to the
|
|
1489
|
+
maximum of the averaged FID, with a warning.
|
|
1490
|
+
**fit_kwargs
|
|
1491
|
+
Further keyword arguments for ``fit_amares``.
|
|
1492
|
+
|
|
1493
|
+
Returns
|
|
1494
|
+
-------
|
|
1495
|
+
xarray.Dataset
|
|
1496
|
+
The ``fit_amares`` result.
|
|
1497
|
+
"""
|
|
1527
1498
|
if normalisation_factor is None:
|
|
1528
1499
|
scaling_factor = np.max(self.avg_fid) * self.number_of_excitations
|
|
1529
1500
|
logger.warning(
|
|
@@ -1538,16 +1509,8 @@ class MRSSeries(RawMRISeries):
|
|
|
1538
1509
|
f"Number of excitations: {self.number_of_excitations}"
|
|
1539
1510
|
)
|
|
1540
1511
|
|
|
1541
|
-
avg_fid = self.avg_fid / scaling_factor
|
|
1542
|
-
|
|
1543
|
-
fit_result = fitting.AMARES.fit_single_fid(
|
|
1544
|
-
avg_fid,
|
|
1545
|
-
init_params,
|
|
1546
|
-
fit_params,
|
|
1547
|
-
raw_header=self.header,
|
|
1548
|
-
filter_by_ppm=filter_by_ppm,
|
|
1549
|
-
)
|
|
1550
|
-
return fit_result
|
|
1512
|
+
avg_fid = (self.avg_fid / scaling_factor).assign_attrs(self.avg_fid.attrs)
|
|
1513
|
+
return avg_fid.xmr.fit_amares(prior_knowledge, **fit_kwargs)
|
|
1551
1514
|
|
|
1552
1515
|
|
|
1553
1516
|
class MRSWashinSeries(MRSSeries):
|
|
@@ -1555,19 +1518,7 @@ class MRSWashinSeries(MRSSeries):
|
|
|
1555
1518
|
|
|
1556
1519
|
def __init__(self, DATA_FOLDER: Path, SERIES_ID: int) -> None:
|
|
1557
1520
|
super().__init__(DATA_FOLDER, SERIES_ID)
|
|
1558
|
-
self.fit_results:
|
|
1559
|
-
|
|
1560
|
-
@property
|
|
1561
|
-
def extracted_results(self):
|
|
1562
|
-
"""The fit results extracted into a FitResults, or an empty one if unfit."""
|
|
1563
|
-
if not self.fit_results:
|
|
1564
|
-
logger.info(
|
|
1565
|
-
"No fit results present. Please run fit_grouped_by_duration() "
|
|
1566
|
-
"first. Returning empty results."
|
|
1567
|
-
)
|
|
1568
|
-
return fitting.AMARES.FitResults(np.asarray([]), pd.DataFrame(), pd.DataFrame())
|
|
1569
|
-
else:
|
|
1570
|
-
return self.extract_from_fit_results()
|
|
1521
|
+
self.fit_results: xr.Dataset | None = None
|
|
1571
1522
|
|
|
1572
1523
|
def plot_washin(self, **kwargs):
|
|
1573
1524
|
"""Plot the duration-averaged spectra, one line per minute."""
|
|
@@ -1581,14 +1532,19 @@ class MRSWashinSeries(MRSSeries):
|
|
|
1581
1532
|
|
|
1582
1533
|
def fit_grouped_by_duration(
|
|
1583
1534
|
self,
|
|
1584
|
-
|
|
1585
|
-
fit_params: dict | None = None,
|
|
1535
|
+
prior_knowledge,
|
|
1586
1536
|
normalisation_factor: np.complexfloating | None = None,
|
|
1587
1537
|
group_duration: int = 60,
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1538
|
+
**fit_kwargs,
|
|
1539
|
+
) -> xr.Dataset:
|
|
1540
|
+
"""Average FIDs by duration and fit all groups with AMARES (via ``xmris``).
|
|
1541
|
+
|
|
1542
|
+
Returns
|
|
1543
|
+
-------
|
|
1544
|
+
xarray.Dataset
|
|
1545
|
+
The ``fit_amares`` result, with one entry per duration group along the group
|
|
1546
|
+
dimension. Also stored as ``fit_results``.
|
|
1547
|
+
"""
|
|
1592
1548
|
if normalisation_factor is None:
|
|
1593
1549
|
logger.warning(
|
|
1594
1550
|
"No normalisation_factor provided. This can lead to different "
|
|
@@ -1610,33 +1566,6 @@ class MRSWashinSeries(MRSSeries):
|
|
|
1610
1566
|
f"Number of excitations: {self.number_of_excitations}"
|
|
1611
1567
|
)
|
|
1612
1568
|
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
# Normalize fid
|
|
1617
|
-
fid = fid / scaling_factor
|
|
1618
|
-
|
|
1619
|
-
fit_result = fitting.AMARES.fit_single_fid(
|
|
1620
|
-
fid,
|
|
1621
|
-
init_params,
|
|
1622
|
-
fit_params,
|
|
1623
|
-
raw_header=self.header,
|
|
1624
|
-
)
|
|
1625
|
-
fit_results.append(fit_result)
|
|
1626
|
-
|
|
1627
|
-
self.fit_results = fit_results
|
|
1628
|
-
return fit_results
|
|
1629
|
-
|
|
1630
|
-
def extract_from_fit_results(
|
|
1631
|
-
self, index_key: str = "group_id", index_values: list | None = None
|
|
1632
|
-
) -> fitting.AMARES.FitResults:
|
|
1633
|
-
"""Restructure self.fit_results into a combined FitResults."""
|
|
1634
|
-
restructured_results = []
|
|
1635
|
-
for res in self.fit_results:
|
|
1636
|
-
data = (res.result_multiplets, res.out_obj)
|
|
1637
|
-
restructured_results.append(data)
|
|
1638
|
-
|
|
1639
|
-
extracted_results = fitting.AMARES.extract_from_fit_results(
|
|
1640
|
-
restructured_results, index_key=index_key, index_values=index_values
|
|
1641
|
-
)
|
|
1642
|
-
return extracted_results
|
|
1569
|
+
scaled_fids = (averaged_fids / scaling_factor).assign_attrs(averaged_fids.attrs)
|
|
1570
|
+
self.fit_results = scaled_fids.xmr.fit_amares(prior_knowledge, **fit_kwargs)
|
|
1571
|
+
return self.fit_results
|
|
@@ -19,7 +19,7 @@ matplotlib's::
|
|
|
19
19
|
|
|
20
20
|
from importlib.metadata import PackageNotFoundError, version
|
|
21
21
|
|
|
22
|
-
from . import GEExam, GESeries,
|
|
22
|
+
from . import GEExam, GESeries, plotting, testing, utils
|
|
23
23
|
from .GEExam import DMIExam, DMIinjExam, ExamBase, MS_DMIExam
|
|
24
24
|
from .GESeries import (
|
|
25
25
|
MRISeries,
|
|
@@ -40,7 +40,6 @@ __all__ = [
|
|
|
40
40
|
# subpackages / modules
|
|
41
41
|
"GEExam",
|
|
42
42
|
"GESeries",
|
|
43
|
-
"fitting",
|
|
44
43
|
"plotting",
|
|
45
44
|
"testing",
|
|
46
45
|
"utils",
|