def recon_ute(twix_file): # recon the ute file, input the path of the Siemens twix file ## read in data scans, evps = readTwix(twix_file) data = np.asarray([x.data for x in scans]) # parse hdr "MEAS" for more information meas_dict, _ = read_twix_hdr(evps[2][1]) npts = np.shape(data)[1] nFrames = np.shape(data)[0] gen_traj_dict = { 'npts': npts, 'nFrames': nFrames, 'traj_type': 3, #halton Spiral 'dwell_time': float(meas_dict['alDwellTime'].split()[0]) / 1000.0, 'oversampling': 3, 'ramp_time': float(meas_dict['RORampTime']), 'plat_time': 2500, 'decay_time': 60, 'del_x': 0, 'del_y': 0, 'del_z': 0, } x, y, z = generate_traj(**gen_traj_dict) def vectorize(x): return np.reshape(x, (np.prod(np.shape(x)), 1)) traj = np.squeeze(0.5 * np.stack( (vectorize(x), vectorize(y), vectorize(z)), axis=-1)) kernel_sharpness = 0.15 kernel_extent = 7 * kernel_sharpness recon_dict = { 'traj': traj, 'data': np.reshape(data, (npts * nFrames, 1)), 'kernel_sharpness': kernel_sharpness, 'kernel_extent': kernel_extent, 'overgrid_factor': 3, 'n_pipe_iter': 20, 'image_size': (npts, npts, npts), 'verbosity': 0, } print('Starting recon UTE') uteVol = recon(**recon_dict) uteVol = np.transpose(uteVol, (2, 1, 0)) # uteVol = complex_align(uteVol) return (uteVol)
from GX_Twix_parser import readTwix from scipy.stats import norm import scipy.sparse as sps import numpy as np import pdb import os from GX_Recon_utils import read_twix_hdr from GX_Spec_classmap import NMR_TimeFit twix_cali_file = 'meas_005026_cali.dat' twix_dixon_file = 'meas_005026_dixon.dat' ## ************************************************part 1, fit on calibration scans, evps = readTwix(twix_cali_file) data = np.asarray([x.data for x in scans]) meas_dict = read_twix_hdr(evps[2][1]) nFids = np.shape(data)[0] nPts = np.shape(data)[1] # 200 + 1 + 20 nSkip = 100 # skip to reach steady state nGas = 1 #number of dedicated gas spectra for frequency reference nCal = 20 #number of flipangle calibration frames following the dissolved hit nDis = nFids - nCal - nGas data_dis = data[nSkip:nDis, :] data_dis_ave = np.average(data_dis, axis=0) data_gas = data[nDis, :] dwell_time = float(
def recon_dixon(twix_file): ## recon_dixon images ################################################################### ## read in data scans, evps = readTwix(twix_file) data_dixon = np.asarray([x.data for x in scans[:-2]]) # the last 2 are spectrums data_spect = np.asarray(scans[-1].data) data_dis = data_dixon[3::2, :] data_gas = data_dixon[ 2::2, :] # the first gas is contaminated, so we throw it away # parse hdr "MEAS" for more information meas_dict, _ = read_twix_hdr(evps[2][1]) _, measYaps_dict = read_twix_hdr(evps[3][1]) # to read out user-set values # reading out gradient delay try: gradient_delay = int(measYaps_dict['sWiPMemBlock.adFree[3]']) except: # if the field was not used, set it to 0 gradient_delay = 0 npts = np.shape(data_dixon)[1] nFrames = np.shape(data_dixon)[0] + 2 # the last 2 are spectrums TE90 = float(meas_dict['alTE'].split()[0]) gen_traj_dict = { 'npts': npts, 'nFrames': np.floor(nFrames / 2).astype(int), 'traj_type': 3, #halton Spiral 'dwell_time': float(meas_dict['alDwellTime'].split()[0]) / 1000.0, 'oversampling': 3, 'ramp_time': float(meas_dict['RORampTime']), 'plat_time': 2500, 'decay_time': 60, 'del_x': gradient_delay - 13, 'del_y': gradient_delay - 14, 'del_z': gradient_delay - 9, } x, y, z = generate_traj(**gen_traj_dict) # the last 2 are used for spectroscopy x = x[1:-1:, :] y = y[1:-1:, :] z = z[1:-1:, :] def vectorize(x): return np.reshape(x, (np.prod(np.shape(x)), 1)) data_dis, x_dis, y_dis, z_dis, nFrames_dis = remove_noise_rays( data=data_dis, x=x, y=y, z=z, thre_snr=0.5) print("Threw away bad dissolved FID: " + str(nFrames / 2 - 2 - nFrames_dis)) data_gas, x_gas, y_gas, z_gas, nFrames_gas = remove_noise_rays( data=data_gas, x=x, y=y, z=z, thre_snr=0.5) print("Threw away bad gas FID: " + str(nFrames / 2 - 2 - nFrames_gas)) ## recon gas for high SNR and high resolution traj = np.squeeze(0.5 * np.stack( (vectorize(x_gas), vectorize(y_gas), vectorize(z_gas)), axis=-1)) recon_dict_highSNR = { 'traj': traj, 'data': np.reshape(data_gas, (npts * nFrames_gas, 1)), 'kernel_sharpness': 0.14, 'kernel_extent': 9 * 0.14, 'overgrid_factor': 3, 'n_pipe_iter': 20, 'image_size': (npts, npts, npts), 'verbosity': 0, } print('Starting recon gas high SNR') gasVol_highSNR = recon(**recon_dict_highSNR) recon_dict_highreso = { 'traj': traj, 'data': np.reshape(data_gas, (npts * nFrames_gas, 1)), 'kernel_sharpness': 0.32, 'kernel_extent': 9 * 0.32, 'overgrid_factor': 3, 'n_pipe_iter': 20, 'image_size': (npts, npts, npts), 'verbosity': 0, } print('Starting recon gas high resolution') gasVol_highreso = recon(**recon_dict_highreso) ## recon dissolved for high SNR traj = np.squeeze(0.5 * np.stack( (vectorize(x_dis), vectorize(y_dis), vectorize(z_dis)), axis=-1)) recon_dict_highSNR['traj'] = traj recon_dict_highSNR['data'] = np.reshape(data_dis, (npts * nFrames_dis, 1)) print('Starting recon dissolved') dissolvedVol = recon(**recon_dict_highSNR) gasVol_highreso = np.transpose(gasVol_highreso, (2, 1, 0)) gasVol_highSNR = np.transpose(gasVol_highSNR, (2, 1, 0)) dissolvedVol = np.transpose(dissolvedVol, (2, 1, 0)) return gasVol_highSNR, gasVol_highreso, dissolvedVol, TE90
def spect_fit(twix_cali_file, twix_dixon_file, Subject_ID): ## spectroscopic fitting on calibration file and dixon bonus spectrum ## ************************************************part 1, fit on calibration scans, evps = readTwix(twix_cali_file) data = np.asarray([x.data for x in scans]) meas_dict,_ = read_twix_hdr(evps[2][1]) nFids = np.shape(data)[0] nPts = np.shape(data)[1] # 200 + 1 + 20 nSkip = 100 # skip to reach steady state nGas = 1 #number of dedicated gas spectra for frequency reference nCal = 20 #number of flipangle calibration frames following the dissolved hit nDis = nFids - nCal - nGas data_dis = data[nSkip:nDis,:] data_dis_ave = np.average(data_dis,axis=0) data_gas = data[nDis,:] dwell_time = float(meas_dict['alDwellTime'].split()[0])*1e-9 # unit in second t = np.array(range(0,nPts))*dwell_time ## initial fit from the calibration to determine frequency and fwhm of gas and dissolved gasfit = NMR_TimeFit(time_signal=data_gas, t=t, area= 1e-4, freq=-84, fwhm=30, phase=0, line_boardening=0,zeropad_size=10000,method='lorenzian') gasfit.fit_time_signal() disfit = NMR_TimeFit(time_signal=data_dis_ave, t=t, area=[1,1,1],freq=[0,-700,-7400], fwhmL=[250,200,30],fwhmG=[0,200,0],phase=[0,0,0],line_boardening=0,zeropad_size=np.size(t),method='voigt') lb = np.stack(([-np.inf,-np.inf,-np.inf],[-100,-900,-np.inf],[-np.inf,-np.inf,-np.inf],[-np.inf,-np.inf,-np.inf],[-np.inf,-np.inf,-np.inf])).flatten() ub = np.stack(([+np.inf,+np.inf,+np.inf],[+100,-500,+np.inf],[+np.inf,+np.inf,+np.inf],[+np.inf,+np.inf,+np.inf],[+np.inf,+np.inf,+np.inf])).flatten() bounds = (lb,ub) disfit.fit_time_signal(bounds) # pdb.set_trace() ## ************************************************part 2, fit on dixon bonus scans, evps = readTwix(twix_dixon_file) data_dixon = np.asarray(scans[-1].data) meas_dict,_ = read_twix_hdr(evps[2][1]) nPts = np.size(data_dixon) dwell_time = float(meas_dict['alDwellTime'].split()[0])*1e-9 # unit in second t = np.array(range(0,nPts))*dwell_time dixonfit = NMR_TimeFit(time_signal=data_dixon, t=t, area=[1,1,1],freq=[0,-700,-7400], fwhmL=[250,200,30],fwhmG=[0,200,0],phase=[0,0,0],line_boardening=0,zeropad_size=np.size(t),method='voigt') dixonfit.fit_time_signal() # fit again with the freq and fwhm from the calibration fitting area = dixonfit.area freq = disfit.freq - gasfit.freq fwhmL = disfit.fwhmL fwhmG = disfit.fwhmG phase = dixonfit.phase fwhmL[fwhmL<0] = 1e-4 fwhmG[fwhmG<0] = 1e-4 assert np.prod(fwhmL>0), "There is a negative value in Dissolved fitting fwhmL" assert np.prod(fwhmG>0), "There is a negative value in Dissolved fitting fwhmG" # set up bounds to constrain frequency and fwhm change lb = np.stack((0.33*area,freq-1.0,0.9*fwhmL,0.9*fwhmG-1.0,[-np.inf,-np.inf,-np.inf])).flatten() ub = np.stack((3.00*area,freq+1.0,1.1*fwhmL,1.1*fwhmG+1.0,[+np.inf,+np.inf,+np.inf])).flatten() # lb = np.stack((0.33*area,[-np.inf,-np.inf,-np.inf],0.9*fwhmL,0.9*fwhmG-1.0,[-np.inf,-np.inf,-np.inf])).flatten() # ub = np.stack((3.00*area,[+np.inf,+np.inf,+np.inf],1.1*fwhmL,1.1*fwhmG+1.0,[+np.inf,+np.inf,+np.inf])).flatten() bounds = (lb,ub) dixonfit = NMR_TimeFit(time_signal=data_dixon, t=t, area=area,freq=freq, fwhmL=fwhmL,fwhmG=fwhmG,phase=phase,line_boardening=0,zeropad_size=np.size(t),method='voigt') dixonfit.fit_time_signal(bounds) fit_box_cali = { 'Area': disfit.area, 'Freq': disfit.freq, 'FwhmL': disfit.fwhmL, 'FwhmG': disfit.fwhmG, 'Phase': disfit.phase, } print_fit(fit_box = fit_box_cali, Subject_ID = Subject_ID, key = 1) fit_box = { 'Area': dixonfit.area, 'Freq': dixonfit.freq, 'FwhmL': dixonfit.fwhmL, 'FwhmG': dixonfit.fwhmG, 'Phase': dixonfit.phase, } print_fit(fit_box = fit_box, Subject_ID = Subject_ID, key =2) # pdb.set_trace() RBC2barrier = dixonfit.area[0]/dixonfit.area[1] return RBC2barrier, fit_box
def spect_calibration(twix_cali_file, result_file_path): ## ************************************************part 1, fit on calibration scans, evps = readTwix(twix_cali_file) data = np.asarray([x.data for x in scans]) meas_dict, _ = read_twix_hdr(evps[2][1]) dicom_dict, _ = read_twix_hdr(evps[1][1]) # fetch useful values nFids = np.shape(data)[0] nPts = np.shape(data)[1] dwell_time = float( meas_dict['alDwellTime'].split()[0]) * 1e-9 # unit in second t = np.array(range(0, nPts)) * dwell_time te = float(meas_dict['alTE'].split()[0]) freq = int(dicom_dict['lFrequency']) # 200 + 1 + 20 nSkip = 100 # skip to reach steady state nGas = 1 #number of dedicated gas spectra for frequency reference nCal = 20 #number of flipangle calibration frames following the dissolved hit nDis = nFids - nGas - nCal # some parameters that may be useful freq_std = 34091550 freq_tol = 200 deltaPhase1_tol = 90 TrueRefScale_tol = 1.17 SNR_tol = 25 flip_angle_target = 20 rbc_bar_adjust = 80.0 # split gas and dissolved data nDis = nFids - nCal - nGas data_dis = data[nSkip:nDis, :] data_dis_ave = np.average(data_dis, axis=0) data_gas = data[nDis, :] ## initial fit from the calibration to determine frequency and fwhm of gas and dissolved gasfit = NMR_TimeFit(time_signal=data_gas, t=t, area=1e-4, freq=-84, fwhm=30, phase=0, line_boardening=0, zeropad_size=10000, method='lorenzian') gasfit.fit_time_signal() disfit = NMR_TimeFit(time_signal=data_dis_ave, t=t, area=[1, 1, 1], freq=[0, -700, -7400], fwhmL=[250, 200, 30], fwhmG=[0, 200, 0], phase=[0, 0, 0], line_boardening=0, zeropad_size=np.size(t), method='voigt') disfit.fit_time_signal() # 1 . report frequency freq_target = freq + gasfit.freq freq_target = int(np.asscalar(np.round(freq_target))) stream = '129Xe cloud is delivering calibration results:\r\n' print('\r\nFrequency_target = {:8.0f} Hz'.format(freq_target)) stream = stream + 'Frequency_target = {:8.0f} Hz *****\r\n'.format( freq_target) if ((freq_target - freq_std) > freq_tol): print('***Warning! Frequency adjust exceeds tolerances; Check system') stream = stream + '***Warning! Frequency adjust exceeds tolerances; Check system\r\n' # 2. report TE90 deltaPhase = disfit.phase[1] - disfit.phase[0] deltaPhase = np.mod(abs(deltaPhase), 180) deltaFreq = abs(disfit.freq[1] - disfit.freq[0]) deltaTE90 = (90 - deltaPhase) / (360 * deltaFreq) TE90 = te + deltaTE90 * 1e6 # in usec print("TE90 = {:3.2f} ms".format(TE90 / 1000)) stream = stream + "TE90 = {:3.2f} ms *****\r\n".format(TE90 / 1000) if abs(deltaPhase) > 90: print('***WARNING! Phi_cal = {:3.0f}{}; Use min TE!'.format( deltaPhase, u'\u00b0'.encode('utf8'))) stream = stream + '***WARNING! Phi_cal = {:3.0f}{}; Use min TE!\r\n'.format( deltaPhase, u'\u00b0'.encode('utf8')) # 3. report reference voltage (Flip angle) calData = data[(nDis + 1):(nDis + nCal + 1), :] flipCalAmps = np.amax(abs(calData), axis=1) guess = [np.max(flipCalAmps), 20.0 * np.pi / 180] x_data = np.array(range(1, len(flipCalAmps) + 1)) y_data = flipCalAmps # curve fitting using trust region reflection algorithm def calc_fitting_residual(coef): y_fit = coef[0] * np.cos(coef[1])**(x_data - 1) residual = (y_fit - y_data).flatten() return residual max_nfev = 13000 bounds = ([-np.inf, -np.inf], [np.inf, np.inf]) fit_result = least_squares(fun=calc_fitting_residual, x0=guess, jac='2-point', bounds=bounds, method='dogbox', max_nfev=max_nfev) flip_angle = abs(fit_result['x'][1] * 180 / np.pi) v_ref_scale_factor = flip_angle_target / flip_angle print('True Ref scale factor = {:3.3f}'.format(v_ref_scale_factor)) stream = stream + 'True Ref scale factor = {:3.3f} *****\r\n'.format( v_ref_scale_factor) print('For 600V calibration, True_Ref = {:3.0f} V'.format( 600 * v_ref_scale_factor)) stream = stream + 'For 600V calibration, True_Ref = {:3.0f} V *****\r\n'.format( 600 * v_ref_scale_factor) if (v_ref_scale_factor > TrueRefScale_tol): print('***Warning! Excessive calibration scale factor; check system') stream = stream + '***Warning! Excessive calibration scale factor; check system\r\n' # 4. calculate and report SNR for dissolved peaks time_fit = disfit.calc_time_sig(disfit.t) time_res = time_fit - disfit.time_signal n25pct = int(round(len(time_res) / 4.0)) std25 = np.std(time_res[-n25pct:]) SNR_dis = disfit.area / std25 print('\r\nSNR for dissolved peaks = {:3.1f}, {:3.1f}, {:3.1f}'.format( SNR_dis[0], SNR_dis[1], SNR_dis[2])) stream = stream + '\r\nSNR for dissolved peaks = {:3.1f}, {:3.1f}, {:3.1f}\r\n'.format( SNR_dis[0], SNR_dis[1], SNR_dis[2]) # 5. calculate and report SNR for gas time_fit = gasfit.calc_time_sig(gasfit.t) time_res = time_fit - gasfit.time_signal n25pct = int(round(len(time_res) / 4.0)) std25 = np.std(time_res[-n25pct:]) SNR_gas = gasfit.area / std25 print('SNR for gas peak = {:3.1f}'.format(SNR_gas[0])) stream = stream + 'SNR for gas peak = {:3.1f}\r\n'.format(SNR_gas[0]) if SNR_gas < SNR_tol: print('***WARNING! Gas FID SNR below minimums; Check Coil Plug') stream = stream + '***WARNING! Gas FID SNR below minimums; Check Coil Plug\r\n' # 6. Quantify ammount of off resonance excitation gas_dis_ratio = disfit.area[2] / sum(disfit.area[:2]) print('\r\ngas_dis_ratio = {:3.3f}'.format(gas_dis_ratio)) stream = stream + '\r\ngas_dis_ratio = {:3.3f}\r\n'.format(gas_dis_ratio) # 7. Quantify RBC:Barrier ratio rbc_bar_ratio = disfit.area[0] / disfit.area[1] print('rbc_bar_ratio = {:3.3f}'.format(rbc_bar_ratio)) print('RbcBar_{:2.0f} = {:3.3f}'.format( rbc_bar_adjust, rbc_bar_ratio * rbc_bar_adjust / 100.0)) stream = stream + 'rbc_bar_ratio = {:3.3f}\r\n'.format(rbc_bar_ratio) stream = stream + 'RbcBar_{:2.0f} = {:3.3f}\r\n'.format( rbc_bar_adjust, rbc_bar_ratio * rbc_bar_adjust / 100.0) with open(result_file_path, 'w') as cali_doc: cali_doc.write(stream) return stream