Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
FSL
fsl_mrs
Commits
45eb362d
Commit
45eb362d
authored
Nov 27, 2021
by
William Clarke
Browse files
Merge branch 'rf/dyn_results_format' into 'master'
Rf/dynamic fitting results format See merge request
!34
parents
8329d2ee
59e3f454
Pipeline
#11902
canceled with stages
in 8 seconds
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CHANGELOG.rst
View file @
45eb362d
...
...
@@ -8,9 +8,10 @@ This document contains the FSL-MRS release history in reverse chronological orde
- Dynamic fitting now handles multiple different basis sets.
- Fix mapped parameter uncertainties in dynamic MRS results.
- Dynamic fitting results can now be saved to and loaded from a directory.
- Changes to the dynamic fitting results API.
1.1.8 (Tuesday 5th October 2021)
-------------------------------
-------------------------------
-
- Fix bug in fsl_mrsi when default MM are added to a incorrectly conjugated basis set.
- Fix MRM reference in HTML report.
...
...
fsl_mrs/tests/test_utils_dynamic_dyn_results.py
View file @
45eb362d
...
...
@@ -71,11 +71,11 @@ def test_dynRes(fixed_ratio_mrs):
assert
isinstance
(
res_obj
.
_init_x
,
pd
.
DataFrame
)
assert
np
.
allclose
(
dyn_obj
.
vm
.
mapped_to_free
(
resinit
[
'x'
]),
res_obj
.
free_parameters_init
)
assert
isinstance
(
res_obj
.
mapped_parameters
,
np
.
ndarray
)
assert
res_obj
.
mapped_parameters
.
shape
==
(
1
,
len
(
mrs_list
),
len
(
dyn_obj
.
mapped_names
))
assert
isinstance
(
res_obj
.
mapped_parameters
_array
,
np
.
ndarray
)
assert
res_obj
.
mapped_parameters
_array
.
shape
==
(
1
,
len
(
mrs_list
),
len
(
dyn_obj
.
mapped_names
))
assert
isinstance
(
res_obj
.
mapped_parameters_init
,
np
.
ndarray
)
assert
res_obj
.
mapped_parameters_init
.
shape
==
(
len
(
mrs_list
),
len
(
dyn_obj
.
mapped_names
))
assert
isinstance
(
res_obj
.
mapped_parameters_init
_array
,
np
.
ndarray
)
assert
res_obj
.
mapped_parameters_init
_array
.
shape
==
(
len
(
mrs_list
),
len
(
dyn_obj
.
mapped_names
))
assert
isinstance
(
res_obj
.
free_parameters_init
,
np
.
ndarray
)
assert
res_obj
.
free_parameters_init
.
shape
==
(
len
(
dyn_obj
.
free_names
),)
...
...
@@ -83,8 +83,8 @@ def test_dynRes(fixed_ratio_mrs):
assert
isinstance
(
res_obj
.
init_dataframe
,
pd
.
DataFrame
)
assert
res_obj
.
init_dataframe
.
shape
==
(
1
,
len
(
dyn_obj
.
free_names
),)
assert
isinstance
(
res_obj
.
mapped_parameters_fitted_init
,
np
.
ndarray
)
assert
res_obj
.
mapped_parameters_fitted_init
.
shape
==
(
len
(
mrs_list
),
len
(
dyn_obj
.
mapped_names
))
assert
isinstance
(
res_obj
.
mapped_parameters_fitted_init
_array
,
np
.
ndarray
)
assert
res_obj
.
mapped_parameters_fitted_init
_array
.
shape
==
(
len
(
mrs_list
),
len
(
dyn_obj
.
mapped_names
))
assert
res_obj
.
mapped_names
==
dyn_obj
.
mapped_names
assert
res_obj
.
free_names
==
dyn_obj
.
free_names
...
...
fsl_mrs/utils/dynamic/dyn_results.py
View file @
45eb362d
...
...
@@ -181,11 +181,11 @@ class dynRes:
return
np
.
asarray
(
nested
,
dtype
=
object
)
@
property
def
mapped_parameters
(
self
):
def
mapped_parameters
_array
(
self
):
"""Flattened mapped parameters. Shape is samples x timepoints x parameters.
Number of samples will be 1 for newton, and >1 for MCMC.
:return: array of mappe
s
samples
:return: array of mappe
d
samples
:rtype: np.array
"""
mapped_samples
=
[]
...
...
@@ -194,7 +194,16 @@ class dynRes:
return
np
.
asarray
(
mapped_samples
)
@
property
def
mapped_parameters_init
(
self
):
def
mapped_params
(
self
):
"""Mapped parameters arising from dynamic fit.
:return: Pandas dataframe containing the mean mapped parameters for each time point
:rtype: pandas.DataFrame
"""
return
pd
.
DataFrame
(
self
.
mapped_parameters_array
.
mean
(
axis
=
0
),
columns
=
self
.
mapped_names
)
@
property
def
mapped_parameters_init_array
(
self
):
"""Flattened mapped parameters from initilisation
Shape is timepoints x parameters.
...
...
@@ -203,6 +212,15 @@ class dynRes:
"""
return
self
.
_init_x
.
to_numpy
()
@
property
def
mapped_params_init
(
self
):
"""Mapped parameters arising from dynamic fit.
:return: Pandas dataframe containing the mean mapped parameters for each time point
:rtype: pandas.DataFrame
"""
return
self
.
_init_x
@
property
def
free_parameters_init
(
self
):
"""Free parameters calculated from the inversion of the dynamic model using the initilisation as input.
...
...
@@ -210,7 +228,7 @@ class dynRes:
:return: Free parameters estimated from initilisation
:rtype: np.array
"""
nested_init
=
self
.
nest_mapped
(
self
.
mapped_parameters_init
,
self
.
_dyn
.
vm
)
nested_init
=
self
.
nest_mapped
(
self
.
mapped_parameters_init
_array
,
self
.
_dyn
.
vm
)
return
self
.
_dyn
.
vm
.
mapped_to_free
(
nested_init
)
@
property
...
...
@@ -223,7 +241,7 @@ class dynRes:
return
pd
.
DataFrame
(
data
=
self
.
free_parameters_init
,
index
=
self
.
_dyn
.
free_names
).
T
@
property
def
mapped_parameters_fitted_init
(
self
):
def
mapped_parameters_fitted_init
_array
(
self
):
"""Mapped parameters resulting from inversion of model using initilised parameters.
Shape is timepoints x parameters.
...
...
@@ -232,6 +250,15 @@ class dynRes:
"""
return
self
.
flatten_mapped
(
self
.
_dyn
.
vm
.
free_to_mapped
(
self
.
free_parameters_init
))
@
property
def
mapped_params_fitted_init
(
self
):
"""Mapped parameters arising from fitting the initilisation parameters to the model.
:return: Pandas dataframe containing the mean mapped parameters for each time point
:rtype: pandas.DataFrame
"""
return
pd
.
DataFrame
(
self
.
mapped_parameters_fitted_init_array
,
columns
=
self
.
mapped_names
)
@
property
def
mapped_names
(
self
):
"""Mapped names from stored dynamic object"""
...
...
@@ -275,15 +302,6 @@ class dynRes:
"""
pass
# TODO: Do we want to keep this and the similar method in the parent class?
@
property
def
mapped_params
(
self
):
"""Implemented in child class
Returns mapped parameters as pandas data series
"""
pass
def
collected_results
(
self
,
to_file
=
None
):
"""Collect the results of dynamic MRS fitting
...
...
@@ -358,10 +376,10 @@ class dynRes:
:type fit_to_init: bool, optional
"""
init_params
=
self
.
mapped_parameters_init
fitted_init_params
=
self
.
mapped_parameters_fitted_init
dyn_params
=
self
.
mapped_parameters
.
mean
(
axis
=
0
)
dyn_params_sd
=
self
.
mapped_parameters
.
std
(
axis
=
0
)
init_params
=
self
.
mapped_parameters_init
_array
fitted_init_params
=
self
.
mapped_parameters_fitted_init
_array
dyn_params
=
self
.
mapped_parameters
_array
.
mean
(
axis
=
0
)
dyn_params_sd
=
self
.
mapped_parameters
_array
.
std
(
axis
=
0
)
names
=
self
.
mapped_names
if
tvals
is
None
:
tvals
=
self
.
_dyn
.
time_var
...
...
@@ -400,11 +418,11 @@ class dynRes:
fwd
.
append
(
self
.
_dyn
.
forward
[
idx
](
mp
))
return
np
.
asarray
(
fwd
)
init_fit
=
calc_fit_from_flatmapped
(
self
.
mapped_parameters_init
)
init_fitted_fit
=
calc_fit_from_flatmapped
(
self
.
mapped_parameters_fitted_init
)
init_fit
=
calc_fit_from_flatmapped
(
self
.
mapped_parameters_init
_array
)
init_fitted_fit
=
calc_fit_from_flatmapped
(
self
.
mapped_parameters_fitted_init
_array
)
dyn_fit
=
[]
for
mp
in
self
.
mapped_parameters
:
for
mp
in
self
.
mapped_parameters
_array
:
dyn_fit
.
append
(
calc_fit_from_flatmapped
(
mp
))
dyn_fit
=
np
.
asarray
(
dyn_fit
)
dyn_fit_mean
=
np
.
mean
(
dyn_fit
,
axis
=
0
)
...
...
@@ -421,7 +439,10 @@ class dynRes:
init_fit
=
0.5
,
dyn
=
1
)
sp_titles
=
[
f
'#
{
idx
}
:
{
t
}
'
for
idx
,
t
in
enumerate
(
self
.
_dyn
.
time_var
)]
if
isinstance
(
self
.
_dyn
.
time_var
,
dict
):
sp_titles
=
[
f
'#
{
idx
}
'
for
idx
in
range
(
self
.
_dyn
.
_t_steps
)]
else
:
sp_titles
=
[
f
'#
{
idx
}
:
{
t
}
'
for
idx
,
t
in
enumerate
(
self
.
_dyn
.
time_var
)]
row
,
col
=
subplot_shape
(
len
(
sp_titles
))
...
...
@@ -541,12 +562,7 @@ class dynRes_mcmc(dynRes):
:return: Std as data Series
:rtype: pandas.Series
"""
return
pd
.
DataFrame
(
self
.
mapped_parameters
.
std
(
axis
=
0
),
columns
=
self
.
mapped_names
)
# TODO: Do we want to keep this and the similar method in the parent class?
@
property
def
mapped_params
(
self
):
return
pd
.
DataFrame
(
self
.
mapped_parameters
.
mean
(
axis
=
0
),
columns
=
self
.
mapped_names
)
return
pd
.
DataFrame
(
self
.
mapped_parameters_array
.
std
(
axis
=
0
),
columns
=
self
.
mapped_names
)
class
dynRes_newton
(
dynRes
):
...
...
@@ -630,8 +646,3 @@ class dynRes_newton(dynRes):
:rtype: pandas.Series
"""
return
pd
.
DataFrame
(
np
.
hstack
(
list
(
self
.
_std
.
values
())),
columns
=
self
.
mapped_names
)
# TODO: Do we want to keep this and the similar method in the parent class?
@
property
def
mapped_params
(
self
):
return
pd
.
DataFrame
(
self
.
mapped_parameters
.
mean
(
axis
=
0
),
columns
=
self
.
mapped_names
)
fsl_mrs/utils/dynamic/dynmrs.py
View file @
45eb362d
...
...
@@ -47,8 +47,8 @@ class dynMRS(object):
:param mrs_list: List of MRS objects, one per time_var
:type mrs_list: List
:param time_var: List containing the dynamic variable
:type time_var: List
:param time_var: List containing the dynamic variable
, or dict for multiple lists
:type time_var: List
or dict
:param config_file: Path to the python model configuration file
:type config_file: str
:param model: 'voigt' or 'lorentzian', defaults to 'voigt'
...
...
@@ -63,7 +63,22 @@ class dynMRS(object):
:type rescale: bool, optional
"""
self
.
time_var
=
np
.
asarray
(
time_var
)
if
isinstance
(
time_var
,
dict
):
self
.
time_var
=
{}
t_size
=
[]
for
key
in
time_var
:
t_element
=
np
.
asarray
(
time_var
[
key
])
t_size
.
append
(
t_element
.
shape
[
0
])
self
.
time_var
.
update
({
key
:
t_element
})
t_size
=
np
.
asarray
(
t_size
)
if
np
.
all
(
np
.
isclose
(
t_size
,
t_size
[
0
])):
self
.
_t_steps
=
t_size
[
0
]
else
:
raise
ValueError
(
'All values in time_var dict must hav ethe same first diemension shape.'
)
else
:
self
.
time_var
=
np
.
asarray
(
time_var
)
self
.
_t_steps
=
self
.
time_var
.
shape
[
0
]
self
.
mrs_list
=
mrs_list
if
rescale
:
self
.
_process_mrs_list
()
...
...
@@ -298,7 +313,7 @@ class dynMRS(object):
else
:
x2p
=
models
.
FSLModel_x2param_Voigt
# Get init from fitting to individual time points
init
=
np
.
empty
((
len
(
self
.
time_var
)
,
len
(
varNames
)),
dtype
=
object
)
init
=
np
.
empty
((
self
.
_t_steps
,
len
(
varNames
)),
dtype
=
object
)
resList
=
[]
for
t
,
mrs
in
enumerate
(
self
.
mrs_list
):
if
verbose
:
...
...
@@ -387,7 +402,7 @@ class dynMRS(object):
"""Add loss functions across data list"""
ret
=
0
mapped
=
self
.
vm
.
free_to_mapped
(
x
)
for
time_index
in
range
(
len
(
self
.
vm
.
time
_variable
)
):
for
time_index
in
range
(
self
.
vm
.
n
time
s
):
p
=
np
.
hstack
(
mapped
[
time_index
,
:])
ret
+=
self
.
loss
(
p
,
time_index
)
return
ret
...
...
@@ -397,7 +412,7 @@ class dynMRS(object):
mapped
=
self
.
vm
.
free_to_mapped
(
x
)
LUT
=
self
.
vm
.
free_to_mapped
(
np
.
arange
(
self
.
vm
.
nfree
),
copy_only
=
True
)
dfdx
=
0
for
time_index
,
_
in
enumerat
e
(
self
.
vm
.
time
_variable
):
for
time_index
in
rang
e
(
self
.
vm
.
n
time
s
):
# dfdmapped
p
=
np
.
hstack
(
mapped
[
time_index
,
:])
dfdp
=
self
.
loss_grad
(
p
,
time_index
)
...
...
@@ -425,7 +440,7 @@ class dynMRS(object):
ll
=
0.0
mapped
=
self
.
vm
.
free_to_mapped
(
x
)
n_over_2
=
len
(
self
.
data
[
0
])
/
2
for
time_index
in
range
(
len
(
self
.
vm
.
time
_variable
)
):
for
time_index
in
range
(
self
.
vm
.
n
time
s
):
p
=
np
.
hstack
(
mapped
[
time_index
,
:])
pred
=
self
.
forward
[
time_index
](
p
)
ll
+=
np
.
log
(
np
.
linalg
.
norm
(
pred
-
self
.
data
[
time_index
]))
*
n_over_2
...
...
fsl_mrs/utils/dynamic/variable_mapping.py
View file @
45eb362d
...
...
@@ -30,12 +30,25 @@ class VariableMapping(object):
----------
param_names : list
param_sizes : list
time_variale : array-like
time_varia
b
le : array-like
or dict
config_file : string
"""
self
.
time_variable
=
np
.
asarray
(
time_variable
)
self
.
ntimes
=
self
.
time_variable
.
shape
[
0
]
if
isinstance
(
time_variable
,
dict
):
self
.
time_variable
=
{}
t_size
=
[]
for
key
in
time_variable
:
t_element
=
np
.
asarray
(
time_variable
[
key
])
t_size
.
append
(
t_element
.
shape
[
0
])
self
.
time_variable
.
update
({
key
:
t_element
})
t_size
=
np
.
asarray
(
t_size
)
if
np
.
all
(
np
.
isclose
(
t_size
,
t_size
[
0
])):
self
.
ntimes
=
t_size
[
0
]
else
:
raise
ValueError
(
'All values in time_variable dict must hav ethe same first diemension shape.'
)
else
:
self
.
time_variable
=
np
.
asarray
(
time_variable
)
self
.
ntimes
=
self
.
time_variable
.
shape
[
0
]
self
.
mapped_names
=
param_names
self
.
mapped_nparams
=
len
(
self
.
mapped_names
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment