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 calc_spectra(self, lbr, cutoff, sampling_rate=5000, min_ppm=-0.7, max_ppm=4.3): # Once we've done that, we only care about the water-suppressed data self.f, self.spec = ana.get_spectra(nt.TimeSeries( self.timeseries, sampling_rate=sampling_rate), line_broadening=lbr, filt_method=dict(lb=cutoff, filt_order=256)) # The first echo (off-resonance) is in the first output self.echo1 = self.spec[:, 0] # The on-resonance is in the second: self.echo2 = self.spec[:, 1] f_ppm = ut.freq_to_ppm(self.f) idx0 = np.argmin(np.abs(f_ppm - min_ppm)) idx1 = np.argmin(np.abs(f_ppm - max_ppm)) idx = slice(idx1, idx0) # Convert from Hz to ppm and extract the part you are interested in. self.f_ppm = f_ppm[idx] # Pack it into a dict: m_e1 = np.mean(self.echo1[:, idx], 0) m_e2 = np.mean(self.echo2[:, idx], 0) self.diff = m_e2 - m_e1 return dict(echo1=m_e1, echo2=m_e2, diff=self.diff), f_ppm
def test_get_spectra(): """ Test the function that does the spectral analysis """ data = np.transpose(nib.load(file_name).get_data(), [1,2,3,4,5,0]).squeeze() w_data, w_supp_data = ana.coil_combine(data) # XXX Just basic smoke-testing for now: f_nonw, nonw_sig1 = ana.get_spectra(nt.TimeSeries(w_supp_data, sampling_rate=5000)) f_nonw, nonw_sig2 = ana.get_spectra(nt.TimeSeries(w_supp_data, sampling_rate=5000), line_broadening=5) f_nonw, nonw_sig3 = ana.get_spectra(nt.TimeSeries(w_supp_data, sampling_rate=5000), line_broadening=5, zerofill=1000)
def __init__(self, in_file, line_broadening=5, zerofill=100, filt_method=None, min_ppm=-0.7, max_ppm=4.3): """ Parameters ---------- in_file : str Path to a nifti file with SV-PROBE MRS data. line_broadening : float How much to broaden the spectral line-widths (Hz) zerofill : int How many zeros to add to the spectrum for additional spectral resolution min_ppm, max_ppm : float The limits of the spectra that are represented fit_lb, fit_ub : float The limits for the part of the spectrum for which we fit the creatine and GABA peaks. """ self.raw_data = np.transpose( nib.load(in_file).get_data(), [1, 2, 3, 4, 5, 0]).squeeze() w_data, w_supp_data = ana.coil_combine(self.raw_data, w_idx=range(8), coil_dim=1) # We keep these around for reference, as private attrs self._water_data = w_data self._w_supp_data = w_supp_data # This is the time-domain signal of interest, combined over coils: self.data = ana.subtract_water(w_data, w_supp_data) f_hz, spectra = ana.get_spectra(self.data, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method) self.f_hz = f_hz # Convert from Hz to ppm and extract the part you are interested in. f_ppm = ut.freq_to_ppm(self.f_hz) idx0 = np.argmin(np.abs(f_ppm - min_ppm)) idx1 = np.argmin(np.abs(f_ppm - max_ppm)) self.idx = slice(idx1, idx0) self.f_ppm = f_ppm self.spectra = spectra[:, self.idx]
def __init__(self, in_file, line_broadening=5, zerofill=100, filt_method=None, min_ppm=-0.7, max_ppm=4.3): """ Parameters ---------- in_file : str Path to a nifti file with SV-PROBE MRS data. line_broadening : float How much to broaden the spectral line-widths (Hz) zerofill : int How many zeros to add to the spectrum for additional spectral resolution min_ppm, max_ppm : float The limits of the spectra that are represented fit_lb, fit_ub : float The limits for the part of the spectrum for which we fit the creatine and GABA peaks. """ self.raw_data = np.transpose(nib.load(in_file).get_data(), [1,2,3,4,5,0]).squeeze() w_data, w_supp_data = ana.coil_combine(self.raw_data, w_idx = range(8), coil_dim=1) # We keep these around for reference, as private attrs self._water_data = w_data self._w_supp_data = w_supp_data # This is the time-domain signal of interest, combined over coils: self.data = ana.subtract_water(w_data, w_supp_data) f_hz, spectra = ana.get_spectra(self.data, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method) self.f_hz = f_hz # Convert from Hz to ppm and extract the part you are interested in. f_ppm = ut.freq_to_ppm(self.f_hz) idx0 = np.argmin(np.abs(f_ppm - min_ppm)) idx1 = np.argmin(np.abs(f_ppm - max_ppm)) self.idx = slice(idx1, idx0) self.f_ppm = f_ppm self.spectra = spectra[:,self.idx]
def __init__(self, in_data, line_broadening=5, zerofill=100, filt_method=None, min_ppm=-0.7, max_ppm=4.3): """ Parameters ---------- in_data : str Path to a nifti file containing MRS data. line_broadening : float How much to broaden the spectral line-widths (Hz) zerofill : int How many zeros to add to the spectrum for additional spectral resolution min_ppm, max_ppm : float The limits of the spectra that are represented fit_lb, fit_ub : float The limits for the part of the spectrum for which we fit the creatine and GABA peaks. """ if isinstance(in_data, str): # The nifti files follow the strange nifti convention, but we want # to use our own logic, which is transients on dim 0 and time on # dim -1: self.raw_data = np.transpose( nib.load(in_data).get_data(), [1, 2, 3, 4, 5, 0]).squeeze() elif isinstance(in_data, np.ndarray): self.raw_data = in_data w_data, w_supp_data = ana.coil_combine(self.raw_data) f_hz, w_supp_spectra = ana.get_spectra(w_supp_data, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method) self.w_supp_spectra = w_supp_spectra # Often, there will be some small offset from the on-resonance # frequency, which we can correct for. We fit a Lorentzian to each of # the spectra from the water-suppressed data, so that we can get a # phase-corrected estimate of the frequency shift, instead of just # relying on the frequency of the maximum: self.w_supp_lorentz = np.zeros(w_supp_spectra.shape[:-1] + (6, )) for ii in range(self.w_supp_lorentz.shape[0]): for jj in range(self.w_supp_lorentz.shape[1]): self.w_supp_lorentz[ii,jj]=\ ana._do_lorentzian_fit(f_hz, w_supp_spectra[ii,jj]) # We store the frequency offset for each transient/echo: self.freq_offset = self.w_supp_lorentz[..., 0] # But for now, we average over all the transients/echos for the # correction: mean_freq_offset = np.mean(self.w_supp_lorentz[..., 0]) f_hz = f_hz - mean_freq_offset self.water_fid = w_data self.w_supp_fid = w_supp_data # This is the time-domain signal of interest, combined over coils: self.data = ana.subtract_water(w_data, w_supp_data) _, spectra = ana.get_spectra(self.data, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method) self.f_hz = f_hz # Convert from Hz to ppm and extract the part you are interested in. f_ppm = ut.freq_to_ppm(self.f_hz) idx0 = np.argmin(np.abs(f_ppm - min_ppm)) idx1 = np.argmin(np.abs(f_ppm - max_ppm)) self.idx = slice(idx1, idx0) self.f_ppm = f_ppm self.echo_off = spectra[:, 1] self.echo_on = spectra[:, 0] # Calculate sum and difference: self.diff_spectra = self.echo_on - self.echo_off self.sum_spectra = self.echo_off + self.echo_on
def __init__(self, in_data, w_idx=[1,2,3], line_broadening=5, zerofill=100, filt_method=None, spect_method=dict(NFFT=1024, n_overlap=1023, BW=2), min_ppm=-0.7, max_ppm=4.3, sampling_rate=5000.): """ Parameters ---------- in_data : str Path to a nifti file containing MRS data. w_idx : list (optional) The indices to the non-water-suppressed transients. Per default we take the 2nd-4th transients. We dump the first one, because it seems to be quite different than the rest of them. line_broadening : float (optional) How much to broaden the spectral line-widths (Hz). Default: 5 zerofill : int (optional) How many zeros to add to the spectrum for additional spectral resolution. Default: 100 filt_method : dict (optional) How/whether to filter the data. Default: None (#nofilter) spect_method: dict (optional) How to derive spectra. Per default, a simple Fourier transform will be derived from apodized time-series, but other methods can also be used (see `nitime` documentation for details) min_ppm, max_ppm : float The limits of the spectra that are represented sampling_rate : float The sampling rate in Hz. """ if isinstance(in_data, str): # The nifti files follow the strange nifti convention, but we want # to use our own logic, which is transients on dim 0 and time on # dim -1: self.raw_data = np.transpose(nib.load(in_data).get_data(), [1,2,3,4,5,0]).squeeze() elif isinstance(in_data, np.ndarray): self.raw_data = in_data w_data, w_supp_data = ana.coil_combine(self.raw_data, w_idx=w_idx, sampling_rate=sampling_rate) f_hz, w_supp_spectra = ana.get_spectra(w_supp_data, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method, spect_method=spect_method) self.w_supp_spectra = w_supp_spectra # Often, there will be some small offset from the on-resonance # frequency, which we can correct for. We fit a Lorentzian to each of # the spectra from the water-suppressed data, so that we can get a # phase-corrected estimate of the frequency shift, instead of just # relying on the frequency of the maximum: self.w_supp_lorentz = np.zeros(w_supp_spectra.shape[:-1] + (6,)) for ii in range(self.w_supp_lorentz.shape[0]): for jj in range(self.w_supp_lorentz.shape[1]): self.w_supp_lorentz[ii,jj]=\ ana._do_lorentzian_fit(f_hz, w_supp_spectra[ii,jj]) # We store the frequency offset for each transient/echo: self.freq_offset = self.w_supp_lorentz[..., 0] # But for now, we average over all the transients/echos for the # correction: mean_freq_offset = np.mean(self.w_supp_lorentz[..., 0]) f_hz = f_hz - mean_freq_offset self.water_fid = w_data self.w_supp_fid = w_supp_data # This is the time-domain signal of interest, combined over coils: self.data = ana.subtract_water(w_data, w_supp_data) _, spectra = ana.get_spectra(self.data, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method) self.f_hz = f_hz # Convert from Hz to ppm and extract the part you are interested in. f_ppm = ut.freq_to_ppm(self.f_hz) idx0 = np.argmin(np.abs(f_ppm - min_ppm)) idx1 = np.argmin(np.abs(f_ppm - max_ppm)) self.idx = slice(idx1, idx0) self.f_ppm = f_ppm self.echo_off = spectra[:, 1] self.echo_on = spectra[:, 0] # Calculate sum and difference: self.diff_spectra = self.echo_on - self.echo_off self.sum_spectra = self.echo_off + self.echo_on
def __init__(self, in_data, line_broadening=5, zerofill=100, filt_method=None, min_ppm=-0.7, max_ppm=4.3): """ Parameters ---------- in_data : str Path to a nifti file containing MRS data. line_broadening : float How much to broaden the spectral line-widths (Hz) zerofill : int How many zeros to add to the spectrum for additional spectral resolution min_ppm, max_ppm : float The limits of the spectra that are represented fit_lb, fit_ub : float The limits for the part of the spectrum for which we fit the creatine and GABA peaks. """ if isinstance(in_data, str): # The nifti files follow the strange nifti convention, but we want # to use our own logic, which is transients on dim 0 and time on # dim -1: self.raw_data = np.transpose(nib.load(in_data).get_data(), [1,2,3,4,5,0]).squeeze() elif isinstance(in_data, np.ndarray): self.raw_data = in_data w_data, w_supp_data = ana.coil_combine(self.raw_data) f_hz, w_supp_spectra = ana.get_spectra(w_supp_data, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method) self.w_supp_spectra = w_supp_spectra # Often, there will be some small offset from the on-resonance # frequency, which we can correct for. We fit a Lorentzian to each of # the spectra from the water-suppressed data, so that we can get a # phase-corrected estimate of the frequency shift, instead of just # relying on the frequency of the maximum: self.w_supp_lorentz = np.zeros(w_supp_spectra.shape[:-1] + (6,)) for ii in range(self.w_supp_lorentz.shape[0]): for jj in range(self.w_supp_lorentz.shape[1]): self.w_supp_lorentz[ii,jj]=\ ana._do_lorentzian_fit(f_hz, w_supp_spectra[ii,jj]) # We store the frequency offset for each transient/echo: self.freq_offset = self.w_supp_lorentz[..., 0] # But for now, we average over all the transients/echos for the # correction: mean_freq_offset = np.mean(self.w_supp_lorentz[..., 0]) f_hz = f_hz - mean_freq_offset self.water_fid = w_data self.w_supp_fid = w_supp_data # This is the time-domain signal of interest, combined over coils: self.data = ana.subtract_water(w_data, w_supp_data) _, spectra = ana.get_spectra(self.data, line_broadening=line_broadening, zerofill=zerofill, filt_method=filt_method) self.f_hz = f_hz # Convert from Hz to ppm and extract the part you are interested in. f_ppm = ut.freq_to_ppm(self.f_hz) idx0 = np.argmin(np.abs(f_ppm - min_ppm)) idx1 = np.argmin(np.abs(f_ppm - max_ppm)) self.idx = slice(idx1, idx0) self.f_ppm = f_ppm self.echo_off = spectra[:, 1] self.echo_on = spectra[:, 0] # Calculate sum and difference: self.diff_spectra = self.echo_on - self.echo_off self.sum_spectra = self.echo_off + self.echo_on