Source code for seq.inversionRecovery

"""
@author: T. Guallart Naval, november 2021
@modifield: J.M. Algarín, february 25th 2022
@modified: J.M. Algarín, june 8th 2022, adapted to new gui structure
MRILAB @ I3M
@summary: spin echo with inversion recovery where we sweep the time between the IR pulse and the excitation pulse.
"""


import experiment as ex
import numpy as np
import seq.mriBlankSeq as blankSeq  # Import the mriBlankSequence for any new sequence.
import scipy.signal as sig
import configs.hw_config as hw

[docs] class InversionRecovery(blankSeq.MRIBLANKSEQ): def __init__(self): super(InversionRecovery, self).__init__() # Input the parameters self.addParameter(key='seqName', string='InverionRecoveryInfo', val='InversionRecovery') self.addParameter(key='nScans', string='Number of scans', val=1, field='SEQ') self.addParameter(key='larmorFreq', string='Larmor frequency (MHz)', val=3.08, field='RF') self.addParameter(key='rfExAmp', string='RF excitation amplitude (a.u.)', val=0.3, field='RF') self.addParameter(key='rfReAmp', string='RF refocusing amplitude (a.u.)', val=0.3, field='RF') self.addParameter(key='rfExTime', string='RF excitation time (us)', val=30.0, field='RF') self.addParameter(key='rfReTime', string='RF refocusing time (us)', val=60.0, field='RF') self.addParameter(key='echoTime', string='Echo time (ms)', val=10.0, field='SEQ') self.addParameter(key='repetitionTime', string='Repetition time (ms)', val=2000., field='SEQ') self.addParameter(key='nPoints', string='nPoints', val=60, field='IM') self.addParameter(key='acqTime', string='Acquisition time (ms)', val=4.0, field='SEQ') self.addParameter(key='shimming', string='Shimming (*1e4)', val=[-70, -90, 10], field='OTH') self.addParameter(key='crusherAmp', string='Crusher grad amp (mT/m)', val=5.0, field='OTH') self.addParameter(key='crusherTime', string='Crusher grad time (us)', val=100.0, field='OTH') self.addParameter(key='crusherDelay', string='Crusher grad delay (us)', val=0.0, field='OTH') self.addParameter(key='tInv0', string='Inversion time, Start (ms)', val=50.0, field='SEQ') self.addParameter(key='tInv1', string='Inversion time, End (ms)', val=1000.0, field='SEQ') self.addParameter(key='nSteps', string='Number of steps', val=10, field='SEQ')
[docs] def sequenceInfo(self): print("Inversion Recovery") print("Author: Dr. J.M. Algarín") print("Contact: josalggui@i3m.upv.es") print("mriLab @ i3M, CSIC, Spain") print("This sequence runs Inversion Recovery and sweep the inversion time\n")
[docs] def sequenceTime(self): nScans = self.mapVals['nScans'] repetitionTime = self.mapVals['repetitionTime']*1e-3 return(repetitionTime*nScans/60) # minutes, scanTime
[docs] def sequenceRun(self, plotSeq, demo=False): init_gpa = False # Starts the gpa # Create the inputs automatically. For some reason it only works if there is a few code later... # for key in self.mapKeys: # locals()[key] = self.mapVals[key] # if not key in locals(): # print('Error') # locals()[key] = self.mapVals[key] # Create the inputs manually, pufff seqName = self.mapVals['seqName'] nScans = self.mapVals['nScans'] larmorFreq = self.mapVals['larmorFreq'] # MHz rfExAmp = self.mapVals['rfExAmp'] rfReAmp = self.mapVals['rfReAmp'] rfExTime = self.mapVals['rfExTime'] # us rfReTime = self.mapVals['rfReTime'] # us acqTime = self.mapVals['acqTime'] # ms echoTime = self.mapVals['echoTime'] # ms repetitionTime = self.mapVals['repetitionTime'] # ms tInv0 = self.mapVals['tInv0'] # ms tInv1 = self.mapVals['tInv1'] # ms nSteps = self.mapVals['nSteps'] # number of samples nPoints = self.mapVals['nPoints'] # number of readout points shimming = self.mapVals['shimming'] crusherAmp = self.mapVals['crusherAmp'] * 1e-3 crusherTime = self.mapVals['crusherTime'] crusherDelay = self.mapVals['crusherDelay'] # Parameters in fundamental units rfExTime = rfExTime * 1e-6 rfReTime = rfReTime * 1e-6 acqTime = acqTime * 1e-3 echoTime = echoTime * 1e-3 repetitionTime = repetitionTime*1e-3 shimming = np.array(shimming) * 1e-4 crusherTime = crusherTime*1e-6 crusherDelay = crusherDelay*1e-6 tInv0 = tInv0*1e-3 tInv1 = tInv1*1e-3 # Miscellaneous gradRiseTime = 200 # us gSteps = int(gradRiseTime / 5) axes = np.array([0, 1, 2]) self.mapVals['gradRiseTime'] = gradRiseTime self.mapVals['gSteps'] = gSteps # Inversion time vector irTimeVector = np.geomspace(tInv0, tInv1, nSteps) self.mapVals['irTimeVector'] = irTimeVector def createSequence(): # Set shimming self.iniSequence(20, shimming) for repeIndex in range(nSteps): # Inversion time for current iteration inversionTime = irTimeVector[repeIndex] # Initialize time tEx = 20e3 + np.max(irTimeVector) + repetitionTime * repeIndex # Inversion pulse t0 = tEx-inversionTime-hw.blkTime-rfReTime/2 self.rfRecPulse(t0, rfReTime, rfReAmp, 0) # Spoiler gradients to destroy residual transversal signal detected for ultrashort inversion times t0 = tEx-inversionTime+rfReTime/2 self.gradTrap(t0, gradRiseTime, inversionTime*0.5, crusherAmp, gSteps, axes[0], shimming) self.gradTrap(t0, gradRiseTime, inversionTime*0.5, crusherAmp, gSteps, axes[1], shimming) self.gradTrap(t0, gradRiseTime, inversionTime*0.5, crusherAmp, gSteps, axes[2], shimming) # Excitation pulse t0 = tEx-hw.blkTime-rfExTime/2 self.rfRecPulse(t0, rfExTime, rfExAmp, 0) # Crusher gradient t0 = tEx + echoTime / 2 - crusherTime / 2 - gradRiseTime - hw.gradDelay - crusherDelay self.gradTrap(t0, gradRiseTime, crusherTime, crusherAmp, gSteps, axes[0], shimming) self.gradTrap(t0, gradRiseTime, crusherTime, crusherAmp, gSteps, axes[1], shimming) self.gradTrap(t0, gradRiseTime, crusherTime, crusherAmp, gSteps, axes[2], shimming) # Refocusing pulse t0 = tEx + echoTime / 2 - rfReTime / 2 - hw.blkTime self.rfRecPulse(t0, rfReTime, rfReAmp, np.pi / 2) # Rx gating t0 = tEx + echoTime - acqTime / 2 self.rxGate(t0, acqTime) # End sequence self.endSequence(scanTime) # Time variables in us rfExTime *= 1e6 rfReTime *= 1e6 repetitionTime *= 1e6 echoTime *= 1e6 irTimeVector *= 1e6 scanTime = nSteps*repetitionTime # us crusherDelay *= 1e6 crusherTime *= 1e6 acqTime *= 1e6 # Bandwidth and sampling rate bw = nPoints / acqTime * hw.oversamplingFactor # MHz samplingPeriod = 1 / bw # us self.expt = ex.Experiment(lo_freq=larmorFreq, rx_t=samplingPeriod, init_gpa=init_gpa, gpa_fhdo_offset_time=(1 / 0.2 / 3.1)) samplingPeriod = self.expt.get_rx_ts()[0] # us bw = 1 / samplingPeriod / hw.oversamplingFactor # MHz acqTime = nPoints / bw # us self.mapVals['samplingPeriod'] = samplingPeriod * 1e-6 self.mapVals['bw'] = bw * 1e6 createSequence() if self.floDict2Exp(): print("Sequence waveforms loaded successfully") pass else: print("ERROR: sequence waveforms out of hardware bounds") return False if plotSeq: self.expt.__del__() else: rxd, msgs = self.expt.run() print(msgs) data = sig.decimate(rxd['rx0']*hw.adcFactor, hw.oversamplingFactor, ftype='fir', zero_phase=True) self.mapVals['data'] = data self.expt.__del__() # Process data to be plotted data = np.reshape(data, (nSteps, -1)) data = data[:, int(nPoints / 2)] self.data = [irTimeVector*1e-3, data] self.mapVals['sampledPoint'] = data return True
[docs] def sequenceAnalysis(self, obj=''): # Signal vs inverion time result1 = {'widget': 'curve', 'xData': self.data[0], 'yData': [np.abs(self.data[1])], 'xLabel': 'Time (ms)', 'yLabel': 'Signal amplitude (mV)', 'title': '', 'legend': [''], 'row': 0, 'col': 0} # create self.out to run in iterative mode self.output = [result1] self.saveRawData() return self.output