def fit_gaussian(spectra, f_ppm, lb=2.6, ub=3.6): """ Fit a gaussian function to the difference spectra to be used for estimation of the GABA peak. Parameters ---------- spectra : array of shape (n_transients, n_points) Typically the difference of the on/off spectra in each transient. f_ppm : array lb, ub : floats In ppm, the range over which optimization is bounded """ idx = ut.make_idx(f_ppm, lb, ub) # We are only going to look at the interval between lb and ub n_points = idx.stop - idx.start n_params = 5 fit_func = ut.gaussian # Set the bounds for the optimization bounds = [ (lb, ub), # peak location (0, None), # sigma (0, None), # amp (None, None), # offset (None, None) # drift ] model = np.empty((spectra.shape[0], n_points)) signal = np.empty((spectra.shape[0], n_points)) params = np.empty((spectra.shape[0], n_params)) for ii, xx in enumerate(spectra): # We fit to the real spectrum: signal[ii] = np.real(xx[idx]) # Use the signal for a rough estimate of the parameters for # initialization : max_idx = np.argmax(signal[ii]) max_sig = np.max(signal[ii]) initial_f0 = f_ppm[idx][max_idx] half_max_idx = np.argmin(np.abs(signal[ii] - max_sig / 2)) # We estimate sigma as the hwhm: initial_sigma = np.abs(initial_f0 - f_ppm[idx][half_max_idx]) initial_off = np.min(signal[ii]) initial_drift = 0 initial_amp = max_sig initial = (initial_f0, initial_sigma, initial_amp, initial_off, initial_drift) params[ii], _ = lsq.leastsqbound(mopt.err_func, initial, args=(f_ppm[idx], np.real(signal[ii]), fit_func), bounds=bounds) model[ii] = fit_func(f_ppm[idx], *params[ii]) return model, signal, params
def fit_water(self, line_broadening=5, zerofill=100, filt_method=None, min_ppm=-5.0, max_ppm=5.0): """ """ # Get the water spectrum as well: f_hz, w_spectra = ana.get_spectra(self.water_fid, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method) f_ppm = ut.freq_to_ppm(f_hz) # Averaging across echos: self.water_spectra = np.mean(w_spectra, 1) model, signal, params = ana.fit_lorentzian(self.water_spectra, self.f_ppm, lb=min_ppm, ub=max_ppm) # Store the params: self.water_model = model self.water_signal = signal self.water_params = params self.water_idx = ut.make_idx(self.f_ppm, min_ppm, max_ppm) mean_params = stats.nanmean(params, 0) self.water_auc = self._calc_auc(ut.lorentzian, params, self.water_idx)
def fit_two_gaussian(spectra, f_ppm, lb=3.6, ub=3.9): """ Fit a gaussian function to the difference spectra This is useful for estimation of the Glx peak, which tends to have two peaks. Parameters ---------- spectra : array of shape (n_transients, n_points) Typically the difference of the on/off spectra in each transient. f_ppm : array lb, ub : floats In ppm, the range over which optimization is bounded """ idx = ut.make_idx(f_ppm, lb, ub) # We are only going to look at the interval between lb and ub n_points = idx.stop - idx.start n_params = 8 fit_func = ut.two_gaussian # Set the bounds for the optimization bounds = [ (lb, ub), # peak 1 location (lb, ub), # peak 2 location (0, None), # sigma 1 (0, None), # sigma 2 (0, None), # amp 1 (0, None), # amp 2 (None, None), # offset (None, None), # drift ] model = np.empty((spectra.shape[0], n_points)) signal = np.empty((spectra.shape[0], n_points)) params = np.empty((spectra.shape[0], n_params)) for ii, xx in enumerate(spectra): # We fit to the real spectrum: signal[ii] = np.real(xx[idx]) params[ii] = _do_two_gaussian_fit(f_ppm[idx], np.real(signal[ii]), bounds=bounds) model[ii] = fit_func(f_ppm[idx], *params[ii]) return model, signal, params
def fit_two_lorentzian(spectra, f_ppm, lb=2.6, ub=3.6): """ Fit a lorentzian function to the sum spectra to be used for estimation of the creatine and choline peaks. Parameters ---------- spectra : array of shape (n_transients, n_points) Typically the sum of the on/off spectra in each transient. f_ppm : array lb, ub : floats In ppm, the range over which optimization is bounded """ # We are only going to look at the interval between lb and ub idx = ut.make_idx(f_ppm, lb, ub) n_points = np.abs(idx.stop - idx.start) n_params = 10 # Lotsa params! # Set the bounds for the optimization bounds = [ (lb, ub), #peak1 (lb, ub), #peak2 (0, None), #area1 (0, None), #area2 (0, ub - lb), #hwhm1 (0, ub - lb), #hwhm2 (-np.pi / 2, np.pi / 2), #phase (-np.pi / 2, np.pi / 2), #phase (None, None), #offset (None, None) ] #drift model = np.empty((spectra.shape[0], n_points)) signal = np.empty((spectra.shape[0], n_points)) params = np.empty((spectra.shape[0], n_params)) for ii, xx in enumerate(spectra): # We fit to the real spectrum: signal[ii] = np.real(xx[idx]) params[ii] = _do_two_lorentzian_fit(f_ppm[idx], np.real(signal[ii]), bounds=bounds) model[ii] = ut.two_lorentzian(f_ppm[idx], *params[ii]) return model, signal, params
def fit_two_gaussian(spectra, f_ppm, lb=3.6, ub=3.9): """ Fit a gaussian function to the difference spectra This is useful for estimation of the Glx peak, which tends to have two peaks. Parameters ---------- spectra : array of shape (n_transients, n_points) Typically the difference of the on/off spectra in each transient. f_ppm : array lb, ub: floats In ppm, the range over which optimization is bounded """ idx = ut.make_idx(f_ppm, lb, ub) # We are only going to look at the interval between lb and ub n_points = idx.stop - idx.start n_params = 8 fit_func = ut.two_gaussian # Set the bounds for the optimization bounds = [(lb,ub), # peak 1 location (lb,ub), # peak 2 location (0,None), # sigma 1 (0,None), # sigma 2 (0,None), # amp 1 (0,None), # amp 2 (None, None), # offset (None, None), # drift ] model = np.empty((spectra.shape[0], n_points)) signal = np.empty((spectra.shape[0], n_points)) params = np.empty((spectra.shape[0], n_params)) for ii, xx in enumerate(spectra): # We fit to the real spectrum: signal[ii] = np.real(xx[idx]) params[ii] = _do_two_gaussian_fit(f_ppm[idx], np.real(signal[ii]), bounds=bounds) model[ii] = fit_func(f_ppm[idx], *params[ii]) return model, signal, params
def fit_two_lorentzian(spectra, f_ppm, lb=2.6, ub=3.6): """ Fit a lorentzian function to the sum spectra to be used for estimation of the creatine and choline peaks. Parameters ---------- spectra : array of shape (n_transients, n_points) Typically the sum of the on/off spectra in each transient. f_ppm : array lb, ub: floats In ppm, the range over which optimization is bounded """ # We are only going to look at the interval between lb and ub idx = ut.make_idx(f_ppm, lb, ub) n_points = np.abs(idx.stop - idx.start) n_params = 10 # Lotsa params! # Set the bounds for the optimization bounds = [(lb,ub), #peak1 (lb,ub), #peak2 (0,None), #area1 (0,None), #area2 (0,ub-lb), #hwhm1 (0,ub-lb), #hwhm2 (-np.pi/2, np.pi/2), #phase (-np.pi/2, np.pi/2), #phase (None,None), #offset (None, None)] #drift model = np.empty((spectra.shape[0], n_points)) signal = np.empty((spectra.shape[0], n_points)) params = np.empty((spectra.shape[0], n_params)) for ii, xx in enumerate(spectra): # We fit to the real spectrum: signal[ii] = np.real(xx[idx]) params[ii] = _do_two_lorentzian_fit(f_ppm[idx], np.real(signal[ii]), bounds=bounds) model[ii] = ut.two_lorentzian(f_ppm[idx], *params[ii]) return model, signal, params
def fit_naa(self, reject_outliers=3.0, fit_lb=1.8, fit_ub=2.4, phase_correct=True): """ Fit a Lorentzian function to the NAA peak at ~ 2 ppm. Example of fitting inverted peak: Foerster et al. 2013, An imbalance between excitatory and inhibitory neurotransmitters in amyothrophic lateral sclerosis revealed by use of 3T proton MRS """ model, signal, params = ana.fit_lorentzian(self.diff_spectra, self.f_ppm, lb=fit_lb, ub=fit_ub) # Store the params: self.naa_model = model self.naa_signal = signal self.naa_params = params self.naa_idx = ut.make_idx(self.f_ppm, fit_lb, fit_ub) mean_params = stats.nanmean(params, 0) self.naa_auc = self._calc_auc(ut.lorentzian, params, self.naa_idx)
def fit_glx2(self, reject_outliers=3.0, fit_lb=3.6, fit_ub=3.9, phase_correct=True, scalefit=False): """ Parameters ---------- reject_outliers : float or bool If set to a float, this is the z score threshold for rejection (on any of the parameters). If set to False, no outlier rejection fit_lb, fit_ub : float What part of the spectrum (in ppm) contains the creatine peak. Default (3.5, 4.2) scalefit : boolean If this is set to true, attempt is made to tighten the fit to the peak with a second round of fitting where the fitted curve is fit with a scale factor. (default false) """ if not hasattr(self, 'creatine_params'): self.fit_creatine() fit_spectra = self.diff_spectra # We fit a two-gaussian function to this entire chunk of the spectrum, # to catch both glx peaks model, signal, params = ana.fit_two_gaussian(fit_spectra, self.f_ppm, lb=fit_lb, ub=fit_ub) # Use an array of ones to index everything but the outliers and nans: ii = np.ones(signal.shape[0], dtype=bool) # Reject outliers: if reject_outliers: model, signal, params, ii = self._outlier_rejection( params, model, signal, ii) # We'll keep around a private attribute to tell us which transients # were good: self._glx2_transients = np.where(ii) # Now we separate params of the two glx peaks from each other # (remember that they both share offset and drift!): self.glxp1_params = params[:, (0, 2, 4, 6, 7)] self.glxp2_params = params[:, (1, 3, 5, 6, 7)] self.glx2_idx = ut.make_idx(self.f_ppm, fit_lb, fit_ub) # We'll need to generate the model predictions from these parameters, # because what we're holding in 'model' is for both together: self.glxp1_model = np.zeros( (self.glxp1_params.shape[0], np.abs(self.glx2_idx.stop - self.glx2_idx.start))) self.glxp2_model = np.zeros( (self.glxp2_params.shape[0], np.abs(self.glx2_idx.stop - self.glx2_idx.start))) for idx in range(self.glxp2_params.shape[0]): self.glxp2_model[idx] = ut.gaussian(self.f_ppm[self.glx2_idx], *self.glxp2_params[idx]) self.glxp1_model[idx] = ut.gaussian(self.f_ppm[self.glx2_idx], *self.glxp1_params[idx]) if scalefit: combinedmodel = self.glxp2_model + self.glxp1_model scalefac, scalemodel = ana._do_scale_fit(self.f_ppm[self.glx2_idx], signal, combinedmodel) # Reject outliers: scalemodel, signal, params, ii = self._rm_outlier_by_amp( params, scalemodel, signal, ii) self.glx2_model = scalemodel else: self.glx2_model = self.glxp1_model + self.glxp2_model self.glx2_signal = signal self.glx2_auc = ( self._calc_auc(ut.gaussian, self.glxp2_params, self.glx2_idx) + self._calc_auc(ut.gaussian, self.glxp1_params, self.glx2_idx))
def _fit_helper(self, fit_spectra, reject_outliers, fit_lb, fit_ub, fit_func): """ This is a helper function for fitting different segments of the spectrum with Gaussian functions (GLX and GABA). Parameters ---------- fit_spectra : ndarray The data to fit reject_outliers : float or bool Z score for outlier rejection. If set to `False`, not outlier rejection. fit_lb : float The lower bound of the part of the ppm scale for which the Gaussian is fit. fit_ub : float The upper bound of the part of the scale fit. fit_func: callable e.g. `fit_gaussian` Returns ------- choose_transients : tuple Indices into the original data's transients dimension to select non-outlier transients. If reject_outliers is set to `False`, this is all the transients model : ndarray The model predicition in each transient, based on the fit. signal : ndarray The original signal in this part of the difference spectrum. params : ndarray The Gaussian parameters in each transient as fit. this_idx : slice object A slice into the part of the spectrum that is fit """ # fit_idx should already be set from fitting the creatine params: model, signal, params = fit_func(fit_spectra, self.f_ppm, lb=fit_lb, ub=fit_ub) # We'll use these indices to reject outliers (or not): ii = np.ones(signal.shape[0], dtype=bool) # Reject outliers: if reject_outliers: model, signal, params, ii = self._outlier_rejection( params, model, signal, ii) choose_transients = np.where(ii) this_idx = ut.make_idx(self.f_ppm, fit_lb, fit_ub) return choose_transients, model, signal, params, this_idx
def fit_creatine(self, reject_outliers=3.0, fit_lb=2.7, fit_ub=3.5): """ Fit a model to the portion of the summed spectra containing the creatine and choline signals. Parameters ---------- reject_outliers : float or bool If set to a float, this is the z score threshold for rejection (on any of the parameters). If set to False, no outlier rejection fit_lb, fit_ub : float What part of the spectrum (in ppm) contains the creatine peak. Default (2.7, 3.5) Note ---- We use upper and lower bounds that are a variation on the bounds mentioned on the GANNET ISMRM2013 poster [1]_. [1] RAE Edden et al (2013). Gannet GABA analysis toolkit. ISMRM conference poster. """ # We fit a two-lorentz function to this entire chunk of the spectrum, # to catch both choline and creatine model, signal, params = ana.fit_two_lorentzian(self.sum_spectra, self.f_ppm, lb=fit_lb, ub=fit_ub) # Use an array of ones to index everything but the outliers and nans: ii = np.ones(signal.shape[0], dtype=bool) # Reject outliers: if reject_outliers: model, signal, params, ii = self._outlier_rejection( params, model, signal, ii) # We'll keep around a private attribute to tell us which transients # were good (this is for both creatine and choline): self._cr_transients = np.where(ii) # Now we separate choline and creatine params from each other (remember # that they both share offset and drift!): self.choline_params = params[:, (0, 2, 4, 6, 8, 9)] self.creatine_params = params[:, (1, 3, 5, 7, 8, 9)] self.cr_idx = ut.make_idx(self.f_ppm, fit_lb, fit_ub) # We'll need to generate the model predictions from these parameters, # because what we're holding in 'model' is for both together: self.choline_model = np.zeros( (self.creatine_params.shape[0], np.abs(self.cr_idx.stop - self.cr_idx.start))) self.creatine_model = np.zeros( (self.choline_params.shape[0], np.abs(self.cr_idx.stop - self.cr_idx.start))) for idx in range(self.creatine_params.shape[0]): self.creatine_model[idx] = ut.lorentzian( self.f_ppm[self.cr_idx], *self.creatine_params[idx]) self.choline_model[idx] = ut.lorentzian(self.f_ppm[self.cr_idx], *self.choline_params[idx]) self.creatine_signal = signal self.creatine_auc = self._calc_auc(ut.lorentzian, self.creatine_params, self.cr_idx) self.choline_auc = self._calc_auc(ut.lorentzian, self.choline_params, self.cr_idx)
def fit_gaussian(spectra, f_ppm, lb=2.6, ub=3.6): """ Fit a gaussian function to the difference spectra to be used for estimation of the GABA peak. Parameters ---------- spectra : array of shape (n_transients, n_points) Typically the difference of the on/off spectra in each transient. f_ppm : array lb, ub: floats In ppm, the range over which optimization is bounded """ idx = ut.make_idx(f_ppm, lb, ub) # We are only going to look at the interval between lb and ub n_points = idx.stop - idx.start n_params = 5 fit_func = ut.gaussian # Set the bounds for the optimization bounds = [(lb,ub), # peak location (0,None), # sigma (0,None), # amp (None, None), # offset (None, None) # drift ] model = np.empty((spectra.shape[0], n_points)) signal = np.empty((spectra.shape[0], n_points)) params = np.empty((spectra.shape[0], n_params)) for ii, xx in enumerate(spectra): # We fit to the real spectrum: signal[ii] = np.real(xx[idx]) # Use the signal for a rough estimate of the parameters for # initialization : max_idx = np.argmax(signal[ii]) max_sig = np.max(signal[ii]) initial_f0 = f_ppm[idx][max_idx] half_max_idx = np.argmin(np.abs(signal[ii] - max_sig/2)) # We estimate sigma as the hwhm: initial_sigma = np.abs(initial_f0 - f_ppm[idx][half_max_idx]) initial_off = np.min(signal[ii]) initial_drift = 0 initial_amp = max_sig initial = (initial_f0, initial_sigma, initial_amp, initial_off, initial_drift) params[ii], _ = lsq.leastsqbound(mopt.err_func, initial, args=(f_ppm[idx], np.real(signal[ii]), fit_func), bounds=bounds) model[ii] = fit_func(f_ppm[idx], *params[ii]) return model, signal, params
def fit_glx2(self, reject_outliers=3.0, fit_lb=3.6, fit_ub=3.9, phase_correct=True, scalefit=False): """ Parameters ---------- reject_outliers : float or bool If set to a float, this is the z score threshold for rejection (on any of the parameters). If set to False, no outlier rejection fit_lb, fit_ub : float What part of the spectrum (in ppm) contains the creatine peak. Default (3.5, 4.2) scalefit : boolean If this is set to true, attempt is made to tighten the fit to the peak with a second round of fitting where the fitted curve is fit with a scale factor. (default false) """ if not hasattr(self, 'creatine_params'): self.fit_creatine() fit_spectra = self.diff_spectra # We fit a two-gaussian function to this entire chunk of the spectrum, # to catch both glx peaks model, signal, params = ana.fit_two_gaussian(fit_spectra, self.f_ppm, lb=fit_lb, ub=fit_ub) # Use an array of ones to index everything but the outliers and nans: ii = np.ones(signal.shape[0], dtype=bool) # Reject outliers: if reject_outliers: model, signal, params, ii = self._outlier_rejection(params, model, signal, ii) # We'll keep around a private attribute to tell us which transients # were good: self._glx2_transients = np.where(ii) # Now we separate params of the two glx peaks from each other # (remember that they both share offset and drift!): self.glxp1_params = params[:, (0, 2, 4, 6, 7)] self.glxp2_params = params[:, (1, 3, 5, 6, 7)] self.glx2_idx = ut.make_idx(self.f_ppm, fit_lb, fit_ub) # We'll need to generate the model predictions from these parameters, # because what we're holding in 'model' is for both together: self.glxp1_model = np.zeros((self.glxp1_params.shape[0], np.abs(self.glx2_idx.stop-self.glx2_idx.start))) self.glxp2_model = np.zeros((self.glxp2_params.shape[0], np.abs(self.glx2_idx.stop-self.glx2_idx.start))) for idx in range(self.glxp2_params.shape[0]): self.glxp2_model[idx] = ut.gaussian(self.f_ppm[self.glx2_idx], *self.glxp2_params[idx]) self.glxp1_model[idx] = ut.gaussian(self.f_ppm[self.glx2_idx], *self.glxp1_params[idx]) if scalefit: combinedmodel = self.glxp2_model + self.glxp1_model scalefac, scalemodel = ana._do_scale_fit( self.f_ppm[self.glx2_idx], signal,combinedmodel) # Reject outliers: scalemodel, signal, params, ii = self._rm_outlier_by_amp(params, scalemodel, signal, ii) self.glx2_model = scalemodel else: self.glx2_model = self.glxp1_model + self.glxp2_model self.glx2_signal = signal self.glx2_auc = ( self._calc_auc(ut.gaussian, self.glxp2_params, self.glx2_idx) + self._calc_auc(ut.gaussian, self.glxp1_params, self.glx2_idx))
def _fit_helper(self, fit_spectra, reject_outliers, fit_lb, fit_ub, fit_func): """ This is a helper function for fitting different segments of the spectrum with Gaussian functions (GLX and GABA). Parameters ---------- fit_spectra : ndarray The data to fit reject_outliers : float or bool Z score for outlier rejection. If set to `False`, not outlier rejection. fit_lb : float The lower bound of the part of the ppm scale for which the Gaussian is fit. fit_ub : float The upper bound of the part of the scale fit. fit_func: callable e.g. `fit_gaussian` Returns ------- choose_transients : tuple Indices into the original data's transients dimension to select non-outlier transients. If reject_outliers is set to `False`, this is all the transients model : ndarray The model predicition in each transient, based on the fit. signal : ndarray The original signal in this part of the difference spectrum. params : ndarray The Gaussian parameters in each transient as fit. this_idx : slice object A slice into the part of the spectrum that is fit """ # fit_idx should already be set from fitting the creatine params: model, signal, params = fit_func(fit_spectra, self.f_ppm, lb=fit_lb, ub=fit_ub) # We'll use these indices to reject outliers (or not): ii = np.ones(signal.shape[0], dtype=bool) # Reject outliers: if reject_outliers: model, signal, params, ii = self._outlier_rejection(params, model, signal, ii) choose_transients = np.where(ii) this_idx = ut.make_idx(self.f_ppm, fit_lb, fit_ub) return choose_transients, model, signal, params, this_idx
def fit_creatine(self, reject_outliers=3.0, fit_lb=2.7, fit_ub=3.5): """ Fit a model to the portion of the summed spectra containing the creatine and choline signals. Parameters ---------- reject_outliers : float or bool If set to a float, this is the z score threshold for rejection (on any of the parameters). If set to False, no outlier rejection fit_lb, fit_ub : float What part of the spectrum (in ppm) contains the creatine peak. Default (2.7, 3.5) Note ---- We use upper and lower bounds that are a variation on the bounds mentioned on the GANNET ISMRM2013 poster [1]_. [1] RAE Edden et al (2013). Gannet GABA analysis toolkit. ISMRM conference poster. """ # We fit a two-lorentz function to this entire chunk of the spectrum, # to catch both choline and creatine model, signal, params = ana.fit_two_lorentzian(self.sum_spectra, self.f_ppm, lb=fit_lb, ub=fit_ub) # Use an array of ones to index everything but the outliers and nans: ii = np.ones(signal.shape[0], dtype=bool) # Reject outliers: if reject_outliers: model, signal, params, ii = self._outlier_rejection(params, model, signal, ii) # We'll keep around a private attribute to tell us which transients # were good (this is for both creatine and choline): self._cr_transients = np.where(ii) # Now we separate choline and creatine params from each other (remember # that they both share offset and drift!): self.choline_params = params[:, (0,2,4,6,8,9)] self.creatine_params = params[:, (1,3,5,7,8,9)] self.cr_idx = ut.make_idx(self.f_ppm, fit_lb, fit_ub) # We'll need to generate the model predictions from these parameters, # because what we're holding in 'model' is for both together: self.choline_model = np.zeros((self.creatine_params.shape[0], np.abs(self.cr_idx.stop-self.cr_idx.start))) self.creatine_model = np.zeros((self.choline_params.shape[0], np.abs(self.cr_idx.stop-self.cr_idx.start))) for idx in range(self.creatine_params.shape[0]): self.creatine_model[idx] = ut.lorentzian(self.f_ppm[self.cr_idx], *self.creatine_params[idx]) self.choline_model[idx] = ut.lorentzian(self.f_ppm[self.cr_idx], *self.choline_params[idx]) self.creatine_signal = signal self.creatine_auc = self._calc_auc(ut.lorentzian, self.creatine_params, self.cr_idx) self.choline_auc = self._calc_auc(ut.lorentzian, self.choline_params, self.cr_idx)