mrax 0.1.0__py3-none-any.whl
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.
- mrax/__init__.py +3 -0
- mrax/analysis/__init__.py +107 -0
- mrax/analysis/dce.py +273 -0
- mrax/analysis/dti.py +401 -0
- mrax/analysis/group.py +288 -0
- mrax/analysis/roi.py +279 -0
- mrax/analysis/saturation_transfer.py +218 -0
- mrax/analysis/stats.py +155 -0
- mrax/analysis/timecourse.py +134 -0
- mrax/analysis/timecourse_processing.py +391 -0
- mrax/core/__init__.py +22 -0
- mrax/core/batch.py +109 -0
- mrax/core/fit_sanity.py +480 -0
- mrax/core/loss_builder.py +921 -0
- mrax/core/runtime.py +24 -0
- mrax/core/study_runner.py +165 -0
- mrax/core/synthetic.py +139 -0
- mrax/data_struct/__init__.py +46 -0
- mrax/data_struct/dataset.py +36 -0
- mrax/data_struct/dimensions.py +485 -0
- mrax/data_struct/experiment.py +237 -0
- mrax/data_struct/joint.py +426 -0
- mrax/data_struct/model.py +333 -0
- mrax/data_struct/quantity.py +59 -0
- mrax/data_struct/storage.py +1032 -0
- mrax/data_struct/study.py +209 -0
- mrax/gui/__init__.py +39 -0
- mrax/gui/_deps.py +73 -0
- mrax/gui/app.py +13591 -0
- mrax/gui/masks.py +602 -0
- mrax/gui/models.py +288 -0
- mrax/gui/registration.py +631 -0
- mrax/gui/session.py +2680 -0
- mrax/io/__init__.py +42 -0
- mrax/io/bruker_import.py +6713 -0
- mrax/io/dipy.py +61 -0
- mrax/io/import_definitions.py +1005 -0
- mrax/io/nifti.py +281 -0
- mrax/io/pulseq_import.py +918 -0
- mrax/io/study_selection.py +164 -0
- mrax/library/__init__.py +45 -0
- mrax/library/default_experiments.py +158 -0
- mrax/library/default_models.py +1678 -0
- mrax/library/parameter_library.py +191 -0
- mrax/library/parameter_library.toml +159 -0
- mrax/opt/__init__.py +38 -0
- mrax/opt/ops.py +88 -0
- mrax/opt/optimizers.py +208 -0
- mrax/opt/regularizers.py +329 -0
- mrax/opt/wavelets.py +53 -0
- mrax/preprocessing/__init__.py +62 -0
- mrax/preprocessing/denoise.py +436 -0
- mrax/preprocessing/drift.py +898 -0
- mrax/preprocessing/motion.py +637 -0
- mrax/reco/__init__.py +87 -0
- mrax/reco/jax_fft.py +40 -0
- mrax/reco/observables.py +251 -0
- mrax/reco/pipeline.py +380 -0
- mrax/reco/processing.py +280 -0
- mrax/reco/utils.py +75 -0
- mrax/reco/variational.py +1736 -0
- mrax-0.1.0.dist-info/METADATA +177 -0
- mrax-0.1.0.dist-info/RECORD +67 -0
- mrax-0.1.0.dist-info/WHEEL +5 -0
- mrax-0.1.0.dist-info/entry_points.txt +2 -0
- mrax-0.1.0.dist-info/licenses/LICENSE +21 -0
- mrax-0.1.0.dist-info/top_level.txt +1 -0
mrax/__init__.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Post-fitting and reconstruction analysis helpers."""
|
|
2
|
+
|
|
3
|
+
from .dce import (
|
|
4
|
+
DCEAnalysisResult,
|
|
5
|
+
analyze_dce_series,
|
|
6
|
+
infer_dce_times_seconds,
|
|
7
|
+
replace_isolated_spikes,
|
|
8
|
+
spgr_signal_ratio_to_concentration,
|
|
9
|
+
)
|
|
10
|
+
from .dti import (
|
|
11
|
+
DTIMaps,
|
|
12
|
+
compute_dti_maps,
|
|
13
|
+
dti_adc_fa,
|
|
14
|
+
dti_eigendecomposition,
|
|
15
|
+
dti_metrics_from_eigenvalues,
|
|
16
|
+
tensor_from_fields,
|
|
17
|
+
)
|
|
18
|
+
from .group import (
|
|
19
|
+
GroupSpec,
|
|
20
|
+
analyze_groups,
|
|
21
|
+
collect_subject_roi_stats,
|
|
22
|
+
compare_group_stats,
|
|
23
|
+
discover_study_manifests,
|
|
24
|
+
resolve_groups,
|
|
25
|
+
summarize_group_stats,
|
|
26
|
+
)
|
|
27
|
+
from .roi import (
|
|
28
|
+
ROI,
|
|
29
|
+
broadcast_mask,
|
|
30
|
+
collect_roi_stats,
|
|
31
|
+
extract_roi_values,
|
|
32
|
+
match_spatial_mask,
|
|
33
|
+
normalize_stats,
|
|
34
|
+
roi_stats,
|
|
35
|
+
summarize_values,
|
|
36
|
+
)
|
|
37
|
+
from .saturation_transfer import (
|
|
38
|
+
ZSpectraResult,
|
|
39
|
+
choose_z_reference_scan,
|
|
40
|
+
compute_z_spectra,
|
|
41
|
+
dataset_with_z_spectra,
|
|
42
|
+
experiment_with_ab_mt_z_prev,
|
|
43
|
+
get_z_spectra,
|
|
44
|
+
prepare_ab_mt_z_spectra,
|
|
45
|
+
)
|
|
46
|
+
from .stats import cohens_d, describe_values, fdr_bh, hedges_g, nonparametric_test, ttest
|
|
47
|
+
from .timecourse import extract_timecourse, extract_timecourse_from_experiment
|
|
48
|
+
from .timecourse_processing import (
|
|
49
|
+
apply_baseline,
|
|
50
|
+
apply_dilution_correction,
|
|
51
|
+
estimate_timecourse_baseline,
|
|
52
|
+
interpolate_timecourses,
|
|
53
|
+
mean_timecourses_by_label,
|
|
54
|
+
nan_gaussian_filter,
|
|
55
|
+
stack_timecourses_by_group,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"GroupSpec",
|
|
60
|
+
"ROI",
|
|
61
|
+
"ZSpectraResult",
|
|
62
|
+
"analyze_groups",
|
|
63
|
+
"broadcast_mask",
|
|
64
|
+
"choose_z_reference_scan",
|
|
65
|
+
"cohens_d",
|
|
66
|
+
"collect_roi_stats",
|
|
67
|
+
"collect_subject_roi_stats",
|
|
68
|
+
"compare_group_stats",
|
|
69
|
+
"compute_z_spectra",
|
|
70
|
+
"describe_values",
|
|
71
|
+
"dataset_with_z_spectra",
|
|
72
|
+
"discover_study_manifests",
|
|
73
|
+
"DTIMaps",
|
|
74
|
+
"DCEAnalysisResult",
|
|
75
|
+
"apply_baseline",
|
|
76
|
+
"apply_dilution_correction",
|
|
77
|
+
"analyze_dce_series",
|
|
78
|
+
"estimate_timecourse_baseline",
|
|
79
|
+
"extract_roi_values",
|
|
80
|
+
"extract_timecourse",
|
|
81
|
+
"extract_timecourse_from_experiment",
|
|
82
|
+
"fdr_bh",
|
|
83
|
+
"experiment_with_ab_mt_z_prev",
|
|
84
|
+
"get_z_spectra",
|
|
85
|
+
"hedges_g",
|
|
86
|
+
"interpolate_timecourses",
|
|
87
|
+
"infer_dce_times_seconds",
|
|
88
|
+
"mean_timecourses_by_label",
|
|
89
|
+
"match_spatial_mask",
|
|
90
|
+
"nan_gaussian_filter",
|
|
91
|
+
"nonparametric_test",
|
|
92
|
+
"normalize_stats",
|
|
93
|
+
"prepare_ab_mt_z_spectra",
|
|
94
|
+
"resolve_groups",
|
|
95
|
+
"roi_stats",
|
|
96
|
+
"compute_dti_maps",
|
|
97
|
+
"dti_adc_fa",
|
|
98
|
+
"dti_eigendecomposition",
|
|
99
|
+
"dti_metrics_from_eigenvalues",
|
|
100
|
+
"tensor_from_fields",
|
|
101
|
+
"stack_timecourses_by_group",
|
|
102
|
+
"replace_isolated_spikes",
|
|
103
|
+
"spgr_signal_ratio_to_concentration",
|
|
104
|
+
"summarize_group_stats",
|
|
105
|
+
"summarize_values",
|
|
106
|
+
"ttest",
|
|
107
|
+
]
|
mrax/analysis/dce.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Sequence
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from mrax.analysis.roi import match_spatial_mask
|
|
9
|
+
from mrax.analysis.timecourse_processing import mean_timecourses_by_label, nan_gaussian_filter
|
|
10
|
+
from mrax.data_struct.quantity import Quantity
|
|
11
|
+
|
|
12
|
+
DEFAULT_GADOLINIUM_RELAXIVITY_MMM_MS = 4.1 / 1000.0
|
|
13
|
+
DEFAULT_DCE_TR_MS = 12.5
|
|
14
|
+
DEFAULT_DCE_FLIP_ANGLE_DEG = 15.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class DCEAnalysisResult:
|
|
19
|
+
"""Processed DCE concentration series and optional ROI summaries."""
|
|
20
|
+
|
|
21
|
+
concentration_mM: np.ndarray
|
|
22
|
+
signal_ratio: np.ndarray
|
|
23
|
+
reference_image: np.ndarray
|
|
24
|
+
times_seconds: np.ndarray
|
|
25
|
+
frame_time_seconds: float
|
|
26
|
+
baseline_frames: int
|
|
27
|
+
timecourses_mM: np.ndarray | None = None
|
|
28
|
+
labels: list[int] | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _as_array(value: object) -> np.ndarray:
|
|
32
|
+
if isinstance(value, Quantity):
|
|
33
|
+
return np.asarray(value.values)
|
|
34
|
+
return np.asarray(value)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _first_finite_scalar(value: object | None) -> float | None:
|
|
38
|
+
if value is None:
|
|
39
|
+
return None
|
|
40
|
+
arr = _as_array(value).astype(float, copy=False).ravel()
|
|
41
|
+
arr = arr[np.isfinite(arr)]
|
|
42
|
+
if arr.size == 0:
|
|
43
|
+
return None
|
|
44
|
+
return float(arr[0])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def infer_dce_times_seconds(
|
|
48
|
+
frame_count: int,
|
|
49
|
+
*,
|
|
50
|
+
total_scan_time_ms: float | None = None,
|
|
51
|
+
frame_time_ms: float | None = None,
|
|
52
|
+
centered: bool = True,
|
|
53
|
+
) -> tuple[np.ndarray, float]:
|
|
54
|
+
"""Infer DCE frame times in seconds from total scan time or frame spacing."""
|
|
55
|
+
|
|
56
|
+
if frame_count <= 0:
|
|
57
|
+
raise ValueError("frame_count must be positive")
|
|
58
|
+
|
|
59
|
+
dt_seconds: float | None = None
|
|
60
|
+
if total_scan_time_ms is not None and np.isfinite(total_scan_time_ms) and total_scan_time_ms > 0:
|
|
61
|
+
dt_seconds = float(total_scan_time_ms) * 1e-3 / float(frame_count)
|
|
62
|
+
elif frame_time_ms is not None and np.isfinite(frame_time_ms) and frame_time_ms > 0:
|
|
63
|
+
dt_seconds = float(frame_time_ms) * 1e-3
|
|
64
|
+
else:
|
|
65
|
+
dt_seconds = 1.0
|
|
66
|
+
|
|
67
|
+
if centered:
|
|
68
|
+
times = (np.arange(frame_count, dtype=float) + 0.5) * dt_seconds
|
|
69
|
+
else:
|
|
70
|
+
times = np.arange(frame_count, dtype=float) * dt_seconds
|
|
71
|
+
return times, float(dt_seconds)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def replace_isolated_spikes(
|
|
75
|
+
image_series: np.ndarray,
|
|
76
|
+
*,
|
|
77
|
+
mask: np.ndarray | None = None,
|
|
78
|
+
threshold: float = 1.05,
|
|
79
|
+
) -> np.ndarray:
|
|
80
|
+
"""Replace isolated high-intensity frames using neighboring frames."""
|
|
81
|
+
|
|
82
|
+
series = np.asarray(image_series, dtype=float)
|
|
83
|
+
if series.ndim < 2 or series.shape[0] < 3 or threshold <= 1.0:
|
|
84
|
+
return series.copy()
|
|
85
|
+
|
|
86
|
+
mask_arr = None
|
|
87
|
+
if mask is not None:
|
|
88
|
+
mask_arr = np.asarray(mask, dtype=bool)
|
|
89
|
+
if mask_arr.shape != series.shape[1:]:
|
|
90
|
+
mask_arr = match_spatial_mask(mask_arr, series.shape[1:])
|
|
91
|
+
|
|
92
|
+
reduced = series.reshape((series.shape[0], -1))
|
|
93
|
+
if mask_arr is not None:
|
|
94
|
+
reduced = series[:, mask_arr]
|
|
95
|
+
frame_means = np.nanmean(reduced, axis=1)
|
|
96
|
+
|
|
97
|
+
out = series.copy()
|
|
98
|
+
for idx in range(1, series.shape[0] - 1):
|
|
99
|
+
cur = float(frame_means[idx])
|
|
100
|
+
prev = float(frame_means[idx - 1])
|
|
101
|
+
nxt = float(frame_means[idx + 1])
|
|
102
|
+
if not np.isfinite(cur):
|
|
103
|
+
continue
|
|
104
|
+
high_prev = np.isfinite(prev) and cur > prev * threshold
|
|
105
|
+
high_next = np.isfinite(nxt) and cur > nxt * threshold
|
|
106
|
+
if high_prev and high_next:
|
|
107
|
+
out[idx] = 0.5 * (series[idx - 1] + series[idx + 1])
|
|
108
|
+
elif high_prev:
|
|
109
|
+
out[idx] = series[idx + 1]
|
|
110
|
+
elif high_next:
|
|
111
|
+
out[idx] = series[idx - 1]
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def spgr_signal_ratio_to_concentration(
|
|
116
|
+
signal_ratio: np.ndarray,
|
|
117
|
+
t1_map_ms: np.ndarray,
|
|
118
|
+
*,
|
|
119
|
+
repetition_time_ms: float = DEFAULT_DCE_TR_MS,
|
|
120
|
+
flip_angle_deg: float = DEFAULT_DCE_FLIP_ANGLE_DEG,
|
|
121
|
+
relaxivity_mM_ms: float = DEFAULT_GADOLINIUM_RELAXIVITY_MMM_MS,
|
|
122
|
+
eps: float = 1e-8,
|
|
123
|
+
) -> np.ndarray:
|
|
124
|
+
"""Convert baseline-normalized SPGR signal ratios to CA concentration in mM."""
|
|
125
|
+
|
|
126
|
+
ratio = np.asarray(signal_ratio, dtype=float)
|
|
127
|
+
t1_ms = np.asarray(t1_map_ms, dtype=float)
|
|
128
|
+
if ratio.ndim != t1_ms.ndim + 1:
|
|
129
|
+
raise ValueError("signal_ratio must be time-first and t1_map_ms must match its spatial shape")
|
|
130
|
+
|
|
131
|
+
theta = np.deg2rad(float(flip_angle_deg))
|
|
132
|
+
tr_ms = float(repetition_time_ms)
|
|
133
|
+
r1 = float(relaxivity_mM_ms)
|
|
134
|
+
|
|
135
|
+
valid_t1 = np.isfinite(t1_ms) & (t1_ms > eps)
|
|
136
|
+
t1_safe = np.where(valid_t1, t1_ms, np.nan)
|
|
137
|
+
|
|
138
|
+
e10 = np.exp(-tr_ms / t1_safe)
|
|
139
|
+
s_star = (1.0 - e10) / (1.0 - np.cos(theta) * e10)
|
|
140
|
+
|
|
141
|
+
numer = 1.0 - np.cos(theta) * s_star[None, ...] * ratio
|
|
142
|
+
denom = 1.0 - s_star[None, ...] * ratio
|
|
143
|
+
numer = np.where(np.isfinite(numer), np.clip(numer, eps, None), np.nan)
|
|
144
|
+
denom = np.where(np.isfinite(denom), np.clip(denom, eps, None), np.nan)
|
|
145
|
+
|
|
146
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
147
|
+
r1_t = np.log(numer / denom) / tr_ms
|
|
148
|
+
concentration = (r1_t - (1.0 / t1_safe)[None, ...]) / r1
|
|
149
|
+
concentration[:, ~valid_t1] = np.nan
|
|
150
|
+
return concentration
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def analyze_dce_series(
|
|
154
|
+
image_series: np.ndarray,
|
|
155
|
+
t1_map_ms: np.ndarray | Quantity,
|
|
156
|
+
*,
|
|
157
|
+
label_mask: np.ndarray | None = None,
|
|
158
|
+
mask: np.ndarray | None = None,
|
|
159
|
+
times_seconds: Sequence[float] | None = None,
|
|
160
|
+
total_scan_time_ms: float | None = None,
|
|
161
|
+
frame_time_ms: float | None = None,
|
|
162
|
+
repetition_time_ms: float = DEFAULT_DCE_TR_MS,
|
|
163
|
+
flip_angle_deg: float = DEFAULT_DCE_FLIP_ANGLE_DEG,
|
|
164
|
+
relaxivity_mM_ms: float = DEFAULT_GADOLINIUM_RELAXIVITY_MMM_MS,
|
|
165
|
+
infusion_time_seconds: float | None = None,
|
|
166
|
+
baseline_seconds: float | None = None,
|
|
167
|
+
baseline_frames: int | None = None,
|
|
168
|
+
min_baseline_frames: int = 5,
|
|
169
|
+
smooth_sigma_frames: float = 2.0,
|
|
170
|
+
spike_threshold: float = 1.05,
|
|
171
|
+
) -> DCEAnalysisResult:
|
|
172
|
+
"""Preprocess a DCE FLASH series and compute CA concentration maps/timecourses."""
|
|
173
|
+
|
|
174
|
+
series = np.asarray(image_series, dtype=float)
|
|
175
|
+
if series.ndim < 2:
|
|
176
|
+
raise ValueError("image_series must be time-first with at least one spatial dimension")
|
|
177
|
+
|
|
178
|
+
spatial_shape = series.shape[1:]
|
|
179
|
+
t1_arr = _as_array(t1_map_ms).astype(float, copy=False)
|
|
180
|
+
if t1_arr.shape != spatial_shape:
|
|
181
|
+
t1_arr = match_spatial_mask(t1_arr, spatial_shape)
|
|
182
|
+
|
|
183
|
+
mask_arr = None
|
|
184
|
+
if label_mask is not None:
|
|
185
|
+
label_mask = match_spatial_mask(np.asarray(label_mask), spatial_shape)
|
|
186
|
+
if mask is None:
|
|
187
|
+
mask = np.asarray(label_mask) != 0
|
|
188
|
+
if mask is not None:
|
|
189
|
+
mask_arr = match_spatial_mask(np.asarray(mask, dtype=bool), spatial_shape)
|
|
190
|
+
else:
|
|
191
|
+
mask_arr = np.isfinite(t1_arr) & (t1_arr > 0)
|
|
192
|
+
|
|
193
|
+
if times_seconds is None:
|
|
194
|
+
times_arr, frame_time_seconds = infer_dce_times_seconds(
|
|
195
|
+
series.shape[0],
|
|
196
|
+
total_scan_time_ms=total_scan_time_ms,
|
|
197
|
+
frame_time_ms=frame_time_ms,
|
|
198
|
+
)
|
|
199
|
+
else:
|
|
200
|
+
times_arr = np.asarray(times_seconds, dtype=float)
|
|
201
|
+
if times_arr.ndim != 1 or times_arr.shape[0] != series.shape[0]:
|
|
202
|
+
raise ValueError("times_seconds must be a 1D array matching the number of frames")
|
|
203
|
+
if times_arr.shape[0] >= 2:
|
|
204
|
+
frame_time_seconds = float(np.nanmedian(np.diff(times_arr)))
|
|
205
|
+
else:
|
|
206
|
+
frame_time_seconds = float((frame_time_ms or 1000.0) * 1e-3)
|
|
207
|
+
|
|
208
|
+
if baseline_seconds is None and infusion_time_seconds is not None:
|
|
209
|
+
baseline_seconds = float(infusion_time_seconds)
|
|
210
|
+
if baseline_frames is None:
|
|
211
|
+
if baseline_seconds is not None and np.isfinite(baseline_seconds) and baseline_seconds > 0:
|
|
212
|
+
baseline_sel = times_arr <= float(baseline_seconds)
|
|
213
|
+
baseline_frames = int(np.sum(baseline_sel))
|
|
214
|
+
else:
|
|
215
|
+
baseline_frames = 0
|
|
216
|
+
baseline_frames = max(int(baseline_frames), int(min_baseline_frames))
|
|
217
|
+
baseline_frames = min(baseline_frames, int(series.shape[0]))
|
|
218
|
+
baseline_slice = slice(0, baseline_frames)
|
|
219
|
+
|
|
220
|
+
cleaned = replace_isolated_spikes(series, mask=mask_arr, threshold=spike_threshold)
|
|
221
|
+
reference_image = np.nanmean(cleaned[baseline_slice], axis=0)
|
|
222
|
+
|
|
223
|
+
baseline = np.nanmean(cleaned[baseline_slice], axis=0)
|
|
224
|
+
baseline = np.where(np.isfinite(baseline) & (baseline > 0), baseline, np.nan)
|
|
225
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
226
|
+
signal_ratio = cleaned / baseline[None, ...]
|
|
227
|
+
signal_ratio = np.where(np.isfinite(signal_ratio), signal_ratio, np.nan)
|
|
228
|
+
|
|
229
|
+
if smooth_sigma_frames and smooth_sigma_frames > 0:
|
|
230
|
+
sigma = (float(smooth_sigma_frames),) + (0.0,) * (signal_ratio.ndim - 1)
|
|
231
|
+
signal_ratio = nan_gaussian_filter(signal_ratio, sigma=sigma)
|
|
232
|
+
|
|
233
|
+
concentration = spgr_signal_ratio_to_concentration(
|
|
234
|
+
signal_ratio,
|
|
235
|
+
t1_arr,
|
|
236
|
+
repetition_time_ms=repetition_time_ms,
|
|
237
|
+
flip_angle_deg=flip_angle_deg,
|
|
238
|
+
relaxivity_mM_ms=relaxivity_mM_ms,
|
|
239
|
+
)
|
|
240
|
+
concentration = np.where(mask_arr[None, ...], concentration, np.nan)
|
|
241
|
+
|
|
242
|
+
roi_timecourses = None
|
|
243
|
+
labels: list[int] | None = None
|
|
244
|
+
if label_mask is not None:
|
|
245
|
+
roi_timecourses, labels = mean_timecourses_by_label(
|
|
246
|
+
concentration,
|
|
247
|
+
label_mask,
|
|
248
|
+
baseline_frames=0,
|
|
249
|
+
smooth_sigma=0.0,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return DCEAnalysisResult(
|
|
253
|
+
concentration_mM=np.asarray(concentration),
|
|
254
|
+
signal_ratio=np.asarray(signal_ratio),
|
|
255
|
+
reference_image=np.asarray(reference_image),
|
|
256
|
+
times_seconds=np.asarray(times_arr),
|
|
257
|
+
frame_time_seconds=float(frame_time_seconds),
|
|
258
|
+
baseline_frames=int(baseline_frames),
|
|
259
|
+
timecourses_mM=None if roi_timecourses is None else np.asarray(roi_timecourses),
|
|
260
|
+
labels=labels,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
__all__ = [
|
|
265
|
+
"DCEAnalysisResult",
|
|
266
|
+
"DEFAULT_DCE_FLIP_ANGLE_DEG",
|
|
267
|
+
"DEFAULT_DCE_TR_MS",
|
|
268
|
+
"DEFAULT_GADOLINIUM_RELAXIVITY_MMM_MS",
|
|
269
|
+
"analyze_dce_series",
|
|
270
|
+
"infer_dce_times_seconds",
|
|
271
|
+
"replace_isolated_spikes",
|
|
272
|
+
"spgr_signal_ratio_to_concentration",
|
|
273
|
+
]
|