Commit 018be4d8 authored by William Clarke's avatar William Clarke
Browse files

Many changes from ISMRM and dev after.

parent 8329d2ee
......@@ -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 mappes samples
:return: array of mapped 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)
......@@ -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.ntimes):
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 enumerate(self.vm.time_variable):
for time_index in range(self.vm.ntimes):
# 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.ntimes):
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
......
......@@ -30,12 +30,25 @@ class VariableMapping(object):
----------
param_names : list
param_sizes : list
time_variale : array-like
time_variable : 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)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment