Module ukat.mapping.t2_stimfit
Classes
class StimFitModel (mode='non_selective', n_comp=1, ukrin_vendor=None)
-
A class to set up the T2 StimFit model.
This model generates an optimisation dictionary (
opt
) containing the model parameters and fitting options.Parameters
mode
:{'non_selective', 'selective'}
, optional- Default 'non_selective' Choose whether the refocusing pulses are selective on non-selective.
n_comp
:{1, 2, 3}
, optional- Default 1 The number of components to fit e.g. if n_comp=2, the model will estimate two T2 values, two M0 values and one B1 value per voxel.
ukrin_vendor
:{None, 'ge', 'philips', 'siemens'}
, optional- Default None The vendor of the MRI scanner used to acquire the data if the UKRIN protocol was used. Specifying a vendor at this stage overrides the relevant parameters in the model with those from the UKRIN protocol. If no vendor is specified, the default parameters are used but can be manually updated after instantiation.
Key Parameters In Options Dictionary
mode : {'non_selective', 'selective'} Choose whether the refocusing pulses are slice selective or non-selective. esp : float The echo spacing in seconds. etl : int The echo train length. T1 : float The approximate T1 value in seconds. Dz : list The start and end position of each slice in cm. Nz : int The number of positions along the slice profile to simulate signal decay for. Nrf : int The number of resampled points in the RF waveform. RFe : dict The excitation pulse parameters, outlined below. RFr : dict The refocusing pulse parameters, outlined below. lsq : dict The least squares fitting parameters, outlined below.
Key Parameters In Rfe Dictionary
RF : np.ndarray The excitation pulse shape. G : float The amplitude of the excitation pulse in Gauss/cm. tau : float The excitation pulse duration in seconds. phase : float The relative phase of the excitation pulse in degrees (0 in CPMG). angle : float The flip angle of the excitation pulse in degrees (typically 90). ref : float The rephasing gradient fraction, times two. Near unity for excitation. alpha : list, optional The actual tip angle distribution across the slice (degrees). If not specified, the tip angle distribution is calculated.
Key Parameters In Rfr Dictionary
RF : np.ndarray The refocusing pulse shape. G : float The amplitude of the refocusing pulse in Gauss/cm. tau : float The refocusing pulse duration in seconds. phase : float The relative phase of the refocusing pulse in degrees (90 in CPMG). angle : float The flip angle of the refocusing pulse in degrees (typically 180). ref : float The rephasing gradient fraction, times two. Typically, 0 for refocusing. alpha : list, optional The actual refocusing angle distribution across the slice (degrees). If not specified, the tip angle distribution is calculated.
Key Parameters In Lsq Dictionary
Ncomp : int The number of components to fit. X0 : list The initial guess for the fitting parameters in the order [[T2_comp, M0_comp] * Ncomp, B1]. XL : list The lower bounds for the fitting parameters in the order [[T2_comp, M0_comp] * Ncomp, B1]. XU : list The upper bounds for the fitting parameters in the order [[T2_comp, M0_comp] * Ncomp, B1]. xtol : float Tolerance for termination by the change of the independent variables. ftol : float Tolerance for termination by the change of the cost function.
Expand source code
class StimFitModel: def __init__(self, mode='non_selective', n_comp=1, ukrin_vendor=None): """ A class to set up the T2 StimFit model. This model generates an optimisation dictionary (`opt`) containing the model parameters and fitting options. Parameters ---------- mode : {'non_selective', 'selective'}, optional Default 'non_selective' Choose whether the refocusing pulses are selective on non-selective. n_comp : {1, 2, 3}, optional Default 1 The number of components to fit e.g. if n_comp=2, the model will estimate two T2 values, two M0 values and one B1 value per voxel. ukrin_vendor : {None, 'ge', 'philips', 'siemens'}, optional Default None The vendor of the MRI scanner used to acquire the data if the UKRIN protocol was used. Specifying a vendor at this stage overrides the relevant parameters in the model with those from the UKRIN protocol. If no vendor is specified, the default parameters are used but can be manually updated after instantiation. Key Parameters in Options Dictionary ---------- mode : {'non_selective', 'selective'} Choose whether the refocusing pulses are slice selective or non-selective. esp : float The echo spacing in seconds. etl : int The echo train length. T1 : float The approximate T1 value in seconds. Dz : list The start and end position of each slice in cm. Nz : int The number of positions along the slice profile to simulate signal decay for. Nrf : int The number of resampled points in the RF waveform. RFe : dict The excitation pulse parameters, outlined below. RFr : dict The refocusing pulse parameters, outlined below. lsq : dict The least squares fitting parameters, outlined below. Key Parameters in RFe Dictionary ---------- RF : np.ndarray The excitation pulse shape. G : float The amplitude of the excitation pulse in Gauss/cm. tau : float The excitation pulse duration in seconds. phase : float The relative phase of the excitation pulse in degrees (0 in CPMG). angle : float The flip angle of the excitation pulse in degrees (typically 90). ref : float The rephasing gradient fraction, times two. Near unity for excitation. alpha : list, optional The actual tip angle distribution across the slice (degrees). If not specified, the tip angle distribution is calculated. Key Parameters in RFr Dictionary ---------- RF : np.ndarray The refocusing pulse shape. G : float The amplitude of the refocusing pulse in Gauss/cm. tau : float The refocusing pulse duration in seconds. phase : float The relative phase of the refocusing pulse in degrees (90 in CPMG). angle : float The flip angle of the refocusing pulse in degrees (typically 180). ref : float The rephasing gradient fraction, times two. Typically, 0 for refocusing. alpha : list, optional The actual refocusing angle distribution across the slice (degrees). If not specified, the tip angle distribution is calculated. Key Parameters in lsq Dictionary ---------- Ncomp : int The number of components to fit. X0 : list The initial guess for the fitting parameters in the order [[T2_comp, M0_comp] * Ncomp, B1]. XL : list The lower bounds for the fitting parameters in the order [[T2_comp, M0_comp] * Ncomp, B1]. XU : list The upper bounds for the fitting parameters in the order [[T2_comp, M0_comp] * Ncomp, B1]. xtol : float Tolerance for termination by the change of the independent variables. ftol : float Tolerance for termination by the change of the cost function. """ if mode != 'non_selective' and mode != 'selective': raise ValueError(f'mode must be either "non_selective" or ' f'"selective". You specified {mode}.') self.mode = mode if n_comp not in [1, 2, 3]: raise ValueError(f'n_comp must be either 1, 2 or 3. You specified ' f'{n_comp}.') self.n_comp = n_comp if ukrin_vendor not in ['ge', 'philips', 'siemens']: warnings.warn('ukrin_vendor was not specified. Using default ' 'pulse sequence parameters.') self.opt = dict() self.opt['mode'] = self.mode self.opt['esp'] = 10e-3 self.opt['etl'] = 20 self.opt['T1'] = 3 self.opt['RFe'] = dict() self.opt['RFr'] = dict() if self.mode == 'selective': self.opt['Dz'] = [-0.5, 0.5] self.opt['Nz'] = 51 self.opt['Nrf'] = 64 self.opt['RFe'] = {'RF': [], 'tau': 2e-3, 'G': 0.5, 'phase': 0, 'ref': 1, 'alpha': [], 'angle': 90} self.opt['RFr'] = {'RF': [], 'tau': 2e-3, 'G': 0.5, 'phase': 90, 'ref': 0, 'alpha': [], 'angle': 180, 'FA_array': np.ones(self.opt['etl'])} else: self.opt['RFe'] = {'angle': 90} self.opt['RFr'] = {'angle': 180, 'FA_array': np.ones(self.opt['etl'])} # Curve fitting parameters self.opt['lsq'] = {'Ncomp': n_comp, 'xtol': 5e-4, 'ftol': 1e-9} if self.opt['lsq']['Ncomp'] == 1: # [T2(sec), amp, B1] self.opt['lsq']['X0'] = [0.06, 0.1, 1] self.opt['lsq']['XU'] = [3, 1e+3, 1.8] self.opt['lsq']['XL'] = [0.015, 0, 0.2] elif self.opt['lsq']['Ncomp'] == 2: # [T2, amp, T2, amp, B1] self.opt['lsq']['X0'] = [0.02, 0.1, 0.331, 0.1, 1] self.opt['lsq']['XU'] = [0.25, 1e+3, 3, 1e+3, 1.8] self.opt['lsq']['XL'] = [0.015, 0, 0.25, 0, 0.2] elif self.opt['lsq']['Ncomp'] == 3: # [T2, amp, T2, amp, T2, amp, B1] self.opt['lsq']['X0'] = [0.02, 0.1, 0.036, 0.1, 0.131, 0.1, 1] self.opt['lsq']['XU'] = [0.035, 1e+3, 0.13, 1e3, 3, 1e+3, 1.8] self.opt['lsq']['XL'] = [0.015, 0, 0.035, 0, 0.13, 0, 0.2] if ukrin_vendor is not None: self._set_ukrin_vendor(ukrin_vendor) if self.mode == 'selective': self.opt['RFe'] = self._set_rf(self.opt['RFe']) self.opt['RFr'] = self._set_rf(self.opt['RFr']) def get_opt(self): return self.opt def get_lsq(self): return self.opt['lsq'] def get_rfe(self): return self.opt['RFe'] def get_rfr(self): return self.opt['RFr'] def _set_ukrin_vendor(self, vendor): self.vendor = vendor self.opt['T1'] = 1.5 self.opt['esp'] = 0.0129 self.opt['etl'] = 10 self.opt['te'] = (np.arange(self.opt['etl']) + 1) * self.opt['esp'] self.opt['RFr']['FA_array'] = np.ones(self.opt['etl']) if self.vendor == 'ge': self.opt['RFe']['tau'] = 2000 / 1e6 # Duration self.opt['RFe']['G'] = 0.751599 # Amplitude self.opt['RFr']['tau'] = 3136 / 1e6 self.opt['RFr']['G'] = 0.276839 self.opt['RFe']['RF'] = rf_pulses.ge_90 self.opt['RFr']['RF'] = rf_pulses.ge_180 self.opt['Dz'] = [0, 0.45] # Slice thickness elif self.vendor == 'philips': self.opt['RFe']['tau'] = 3820 / 1e6 self.opt['RFe']['G'] = 0.392 self.opt['RFr']['tau'] = 6010 / 1e6 self.opt['RFr']['G'] = 0.327 self.opt['RFe']['RF'] = rf_pulses.philips_90 self.opt['RFr']['RF'] = rf_pulses.philips_180 self.opt['Dz'] = [0, 0.45] elif self.vendor == 'siemens': self.opt['RFe']['tau'] = 3072 / 1e6 self.opt['RFe']['G'] = 0.417 self.opt['RFr']['tau'] = 3000 / 1e6 self.opt['RFr']['G'] = 0.326 self.opt['RFe']['RF'] = rf_pulses.ge_90 self.opt['RFr']['RF'] = rf_pulses.ge_180 self.opt['Dz'] = [0, 0.5] else: warnings.warn(f'{self.vendor} is not implemented. Please ' f'manually specify the models parameters.') def _set_rf(self, rf): dz = self.opt['Dz'] nz = self.opt['Nz'] nrf = self.opt['Nrf'] gamma = 2 * np.pi * 42.575e6 / 10000 # Gauss z = np.linspace(dz[0], dz[1], nz) scale = rf['angle'] / (gamma * rf['tau'] * abs(np.sum(rf['RF'])) / len( rf['RF']) * 180 / np.pi) rf['RF'] *= scale m = np.zeros([3, nz]) m[2, :] = 1 rf['RF'] = 1e-4 * rf['RF'] # approximation for # small tip angle phi = gamma * rf['G'] * z * rf['tau'] / nrf cphi = np.cos(phi) sphi = np.sin(phi) cp_rf = np.cos(rf['phase'] * np.pi / 180) sp_rf = np.sin(rf['phase'] * np.pi / 180) theta_rf = gamma * rf['RF'] * rf['tau'] / nrf ct_rf = np.cos(theta_rf) st_rf = np.sin(theta_rf) for i in range(nrf): for j in range(nz): rz = np.array([[cphi[j], sphi[j], 0], [-sphi[j], cphi[j], 0], [0, 0, 1]]) m[:, j] = np.dot(rz, m[:, j]) r = np.array([[1, 0, 0], [0, ct_rf[i], st_rf[i]], [0, -st_rf[i], ct_rf[i]]]) if rf['phase'] != 0: rz = np.array([[cp_rf, sp_rf, 0], [-sp_rf, cp_rf, 0], [0, 0, 1]]) rzm = np.array([[cp_rf, -sp_rf, 0], [sp_rf, cp_rf, 0], [0, 0, 1]]) r = np.dot(rzm, np.dot(r, rz)) m = np.dot(r, m) if rf['ref'] > 0: psi = -rf['ref'] / 2 * gamma * rf['G'] * z * rf['tau'] for j in range(nz): rz = np.array([[np.cos(psi[j]), np.sin(psi[j]), 0], [-np.sin(psi[j]), np.cos(psi[j]), 0], [0, 0, 1]]) m[:, j] = np.dot(rz, m[:, j]) rf['RF'] = 1e4 * rf['RF'] rf['alpha'] = 1e4 * np.arccos(m[2, :]) return rf
Methods
def get_lsq(self)
def get_opt(self)
def get_rfe(self)
def get_rfr(self)
class T2StimFit (pixel_array, affine, model, mask=None, multithread='auto', norm=True)
-
Attributes
t2_map
:np.ndarray
- The estimated T2 values in ms
m0_map
:np.ndarray
- The estimated M0 values
b1_map
:np.ndarray
- The estimated B1 values where 1 represents the nominal flip angle
b1_map_scaled
:np.ndarray
- The estimated B1 values scaled to the range [0, 1] where 1 represents the nominal flip angle. All values over 1 are reflected about 1.
r2_map
:np.ndarray
- The R-Squared value of the fit, values close to 1 indicate a good fit, lower values indicate a poorer fit
shape
:tuple
- The shape of the T2 map
n_vox
:int
- The number of voxels in the map i.e. the product of all dimensions apart from TE
- Class for performing stimulated echo T2 fitting as in Marc Lebel R.
StimFit
:A Toolbox for Robust T2 Mapping with Stimulated Echo
Compensation. In: Proc. Intl. Soc. Mag. Reson. Med. 20. Melbourne; 2012:2558. https://archive.ismrm.org/2012/2558.html.
Parameters
pixel_array
:np.ndarray
- An array containing the signal from each voxel at each echo time with the last dimension being time i.e. the array needed to generate a 3D T2 map would have dimensions [x, y, z, TE].
affine
:np.ndarray
- A matrix giving the relationship between voxel coordinates and world coordinates.
model
:StimFitModel
- A StimFitModel object containing the model parameters.
mask
:np.ndarray
, optional- A boolean mask of the voxels to fit. Should be the shape of the desired T2 map rather than the raw data i.e. omit the time dimension.
multithread
:bool
or'auto'
, optional- Default 'auto'. If True, fitting will be distributed over all cores available on the node. If False, fitting will be carried out on a single thread. 'auto' attempts to apply multithreading where appropriate based on the number of voxels being fit.
norm
:bool
, optional- Default True. StimFit is performed on normalised data. If norm is False, it is assumed that the data has already been normalised. If norm is True, the data will be normalised before fitting.
Expand source code
class T2StimFit: """ Attributes ---------- t2_map : np.ndarray The estimated T2 values in ms m0_map : np.ndarray The estimated M0 values b1_map : np.ndarray The estimated B1 values where 1 represents the nominal flip angle b1_map_scaled : np.ndarray The estimated B1 values scaled to the range [0, 1] where 1 represents the nominal flip angle. All values over 1 are reflected about 1. r2_map : np.ndarray The R-Squared value of the fit, values close to 1 indicate a good fit, lower values indicate a poorer fit shape : tuple The shape of the T2 map n_vox : int The number of voxels in the map i.e. the product of all dimensions apart from TE """ def __init__(self, pixel_array, affine, model, mask=None, multithread='auto', norm=True): """ Class for performing stimulated echo T2 fitting as in Marc Lebel R. StimFit: A Toolbox for Robust T2 Mapping with Stimulated Echo Compensation. In: Proc. Intl. Soc. Mag. Reson. Med. 20. Melbourne; 2012:2558. https://archive.ismrm.org/2012/2558.html. Parameters ---------- pixel_array : np.ndarray An array containing the signal from each voxel at each echo time with the last dimension being time i.e. the array needed to generate a 3D T2 map would have dimensions [x, y, z, TE]. affine : np.ndarray A matrix giving the relationship between voxel coordinates and world coordinates. model : StimFitModel A StimFitModel object containing the model parameters. mask : np.ndarray, optional A boolean mask of the voxels to fit. Should be the shape of the desired T2 map rather than the raw data i.e. omit the time dimension. multithread : bool or 'auto', optional Default 'auto'. If True, fitting will be distributed over all cores available on the node. If False, fitting will be carried out on a single thread. 'auto' attempts to apply multithreading where appropriate based on the number of voxels being fit. norm : bool, optional Default True. StimFit is performed on normalised data. If norm is False, it is assumed that the data has already been normalised. If norm is True, the data will be normalised before fitting. """ self.pixel_array = np.copy(pixel_array) self.shape = pixel_array.shape[:-1] self.n_vox = np.prod(self.shape) self.affine = affine self.model = model assert multithread is True \ or multithread is False \ or multithread == 'auto', f'multithreaded must be True,' \ f'False or auto. You entered ' \ f'{multithread}' if multithread == 'auto': if self.n_vox > 20: multithread = True else: multithread = False self.multithread = multithread # Generate a mask if there isn't one specified if mask is None: self.mask = np.ones(self.shape, dtype=bool) else: self.mask = mask # Don't process any nan values self.mask[np.isnan(np.sum(pixel_array, axis=-1))] = False # Normalise the data if norm: self.pixel_array /= np.nanmax(self.pixel_array) if np.nanmax(self.pixel_array) > 1: warnings.warn('Pixel array contains values greater than 1. ' 'Data should be normalised, please set norm=True ' 'or manually normalise your data.') # Perform the fit self._fit() def to_nifti(self, output_directory=os.getcwd(), base_file_name='Output', maps='all'): """Exports some of the T2StimFit class attributes to NIFTI. Parameters ---------- output_directory : string, optional Path to the folder where the NIFTI files will be saved. base_file_name : string, optional Filename of the resulting NIFTI. This code appends the extension. Eg., base_file_name = 'Output' will result in 'Output.nii.gz'. maps : list or 'all', optional List of maps to save to NIFTI. This should either the string "all" or a list of maps from ["t2", "m0", "b1", "b1_scaled", "r2", "mask"]. """ os.makedirs(output_directory, exist_ok=True) base_path = os.path.join(output_directory, base_file_name) if maps == 'all' or maps == ['all']: maps = ['t2', 'm0', 'b1', 'b1_scaled', 'r2', 'mask'] if isinstance(maps, list): for result in maps: if result == 't2' or result == 't2_map': t2_nifti = nib.Nifti1Image(self.t2_map, affine=self.affine) nib.save(t2_nifti, f'{base_path}_t2_map.nii.gz') elif result == 'm0' or result == 'm0_map': m0_nifti = nib.Nifti1Image(self.m0_map, affine=self.affine) nib.save(m0_nifti, f'{base_path}_m0_map.nii.gz') elif result == 'b1': b1_nifti = nib.Nifti1Image(self.b1_map, affine=self.affine) nib.save(b1_nifti, f'{base_path}_b1_map.nii.gz') elif result == 'b1_scaled': b1_scaled_nifti = nib.Nifti1Image(self.b1_map_scaled, affine=self.affine) nib.save(b1_scaled_nifti, f'{base_path}_b1_map_scaled.nii.gz') elif result == 'r2' or result == 'r2_map': r2_nifti = nib.Nifti1Image(self.r2_map, affine=self.affine) nib.save(r2_nifti, f'{base_path}_r2_map.nii.gz') elif result == 'mask': mask_nifti = nib.Nifti1Image(self.mask.astype(np.uint16), affine=self.affine) nib.save(mask_nifti, f'{base_path}_mask.nii.gz') else: raise ValueError('No NIFTI file saved. The variable "maps" ' 'should be "all" or a list of maps from ' '"["t2", "m0", "b1", "r2", ' '"mask"]".') def get_fit_signal(self): """ Get the fit signal from the model used to fit the data i.e. the simulated signal at each echo time given the estimated T2, M0. Note if norm=True, the fit signal will also be normalised. Returns ------- fit_signal : np.ndarray An array containing the fit signal generated by the model """ fit_signal = np.zeros((self.n_vox, self.model.opt['etl'])) params = np.array([self.t2_map.reshape(-1), self.m0_map.reshape(-1)]) for n in range(self.n_vox): fit_signal[n, :] = two_param_eq(self.model.opt['te'] * 1000, params[0, n], params[1, n]) fit_signal = fit_signal.reshape((*self.shape, self.model.opt['etl'])) return fit_signal def _fit(self): mask = self.mask.flatten() signal = self.pixel_array.reshape(self.n_vox, self.model.opt['etl']) idx = np.argwhere(mask).squeeze() signal = signal[idx, :] if self.multithread: with ProcessPool() as executor: results = executor.map(self._fit_signal, signal) else: results = list(tqdm(map(self._fit_signal, signal), total=np.sum(self.mask))) t2 = np.array([result[0] for result in results]) m0 = np.array([result[1] for result in results]) b1 = np.array([result[2] for result in results]) r2 = np.array([result[3] for result in results]) if self.model.n_comp > 1: t2_map = np.zeros((self.n_vox, self.model.n_comp)) m0_map = np.zeros((self.n_vox, self.model.n_comp)) r2_map = np.zeros((self.n_vox, self.model.n_comp)) else: t2_map = np.zeros(self.n_vox) m0_map = np.zeros(self.n_vox) r2_map = np.zeros(self.n_vox) b1_map = np.zeros(self.n_vox) t2_map[idx] = t2 * 1000 # Convert to ms m0_map[idx] = m0 b1_map[idx] = b1 r2_map[idx] = r2 self.t2_map = np.squeeze(t2_map.reshape((*self.shape, self.model.n_comp))) self.m0_map = np.squeeze(m0_map.reshape((*self.shape, self.model.n_comp))) self.b1_map = b1_map.reshape(self.shape) self.b1_map_scaled = rescale_b1_map(self.b1_map * 100) self.r2_map = np.squeeze(r2_map.reshape((*self.shape, self.model.n_comp))) def _fit_signal(self, signal): if len(signal) != self.model.opt['etl']: raise Exception('Inconsistent echo train length') # Two component fitting if self.model.opt['lsq']['Ncomp'] == 2: x = optimize.least_squares(self._residual2, self.model.opt['lsq']['X0'], args=(signal, self.model.opt, self.model.mode), bounds=(self.model.opt['lsq']['XL'], self.model.opt['lsq']['XU']), xtol=self.model.opt['lsq']['xtol'], ftol=self.model.opt['lsq']['ftol']).x t2, amp, b1 = [x[0], x[2]], [x[1], x[3]], x[4] r2 = [r2_score(signal, two_param_eq(self.model.opt['te'], t2[0], amp[0])), r2_score(signal, two_param_eq(self.model.opt['te'], t2[1], amp[1]))] # Three component fitting elif self.model.opt['lsq']['Ncomp'] == 3: x = optimize.least_squares(self._residual3, self.model.opt['lsq']['X0'], args=(signal, self.model.opt, self.model.mode), bounds=(self.model.opt['lsq']['XL'], self.model.opt['lsq']['XU']), xtol=self.model.opt['lsq']['xtol'], ftol=self.model.opt['lsq']['ftol']).x t2, amp, b1 = [x[0], x[2], x[4]], [x[1], x[3], x[5]], x[6] r2 = [r2_score(signal, two_param_eq(self.model.opt['te'], t2[0], amp[0])), r2_score(signal, two_param_eq(self.model.opt['te'], t2[1], amp[1])), r2_score(signal, two_param_eq(self.model.opt['te'], t2[2], amp[2]))] # One component fitting else: x = optimize.least_squares(self._residual1, self.model.opt['lsq']['X0'], args=(signal, self.model.opt, self.model.mode), bounds=(self.model.opt['lsq']['XL'], self.model.opt['lsq']['XU']), xtol=self.model.opt['lsq']['xtol'], ftol=self.model.opt['lsq']['ftol']).x t2, amp, b1 = x fit_sig = two_param_eq(self.model.opt['te'], t2, amp) r2 = r2_score(signal, fit_sig) return t2, amp, b1, r2 @staticmethod def _residual1(p, y, opt, mode): return y - _epgsig(p[0], p[2], opt, mode) * p[1] @staticmethod def _residual2(p, y, opt, mode): return y - (_epgsig(p[0], p[4], opt, mode) * p[1] - _epgsig(p[2], p[4], opt, mode) * p[3]) @staticmethod def _residual3(p, y, opt, mode): return y - (_epgsig(p[0], p[6], opt, mode) * p[1] - _epgsig(p[4], p[6], opt, mode) * p[5] - _epgsig(p[2], p[6], opt, mode) * p[3])
Methods
def get_fit_signal(self)
-
Get the fit signal from the model used to fit the data i.e. the simulated signal at each echo time given the estimated T2, M0. Note if norm=True, the fit signal will also be normalised.
Returns
fit_signal
:np.ndarray
- An array containing the fit signal generated by the model
def to_nifti(self, output_directory='/home/runner/work/ukat/ukat', base_file_name='Output', maps='all')
-
Exports some of the T2StimFit class attributes to NIFTI.
Parameters
output_directory
:string
, optional- Path to the folder where the NIFTI files will be saved.
base_file_name
:string
, optional- Filename of the resulting NIFTI. This code appends the extension. Eg., base_file_name = 'Output' will result in 'Output.nii.gz'.
maps
:list
or'all'
, optional- List of maps to save to NIFTI. This should either the string "all" or a list of maps from ["t2", "m0", "b1", "b1_scaled", "r2", "mask"].