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