KSFoundation  [April-2021]
A platform for simpler EPIC programming on GE MR systems
KS_RF Struct Reference

#include <KSFoundation.h>

Data Fields

int role
 
float flip
 
float bw
 
float cf_offset
 
float amp
 
int start2iso
 
int iso2end
 
int iso2end_subpulse
 
KS_DESCRIPTION designinfo
 
RF_PULSE rfpulse
 
KS_WAVE rfwave
 
KS_WAVE omegawave
 
KS_WAVE thetawave
 

Detailed Description

Composite sequence object for RF (with optional OMEGA & THETA pulses)

KS_RF.png
KS_RF


The composite KS_RF sequence object holds all information necessary to create RF pulses, both real and freq/phase modulated ones. For RF pulses with slice selection, use a KS_SELRF object instead, which contains a KS_RF object and KS_TRAP objects. The KS_RF object (be it standalone or a part of KS_SELRF) needs to be set up along the lines described here

RF Design

Independent of the design method chosen (see below), KS_RF has some field members worth mentioning:

  • .role: This is a label for the role of the RF pulse. This information is primarily used by ks_eval_selrf() to determine what gradients to apply for slice selection. Valid values for .role are
    • KS_RF_ROLE_EXC (for excitation)
    • KS_RF_ROLE_REF (for refocusing)
    • KS_RF_ROLE_CHEMSAT (for fat-sat, non-slice selective)
    • KS_RF_ROLE_INV (for inversion)
    • KS_RF_ROLE_SPSAT (for spatial saturation)
  • .flip: The desired flip angle of the RF pulse [degrees]
  • .bw: The bandwidth of the RF pulse [Hz]
  • .iso2end and .start2iso: These fields are for convenience and easier timing calculations and are set in ks_eval_rf(). The value of .iso2end is equal to .rfpulse.isodelay and refers to the time from the effective center of the RF pulse to the end. Field .iso2end must have a correct value before calling ks_eval_rf(). The .start2iso field is the time from the beginning of the RF pulse to its effective center, calculated as .rfwave.duration - .iso2end in ks_eval_rf()
  • .cf_offset: is used to change the relative center frequency for non-slice selective RFs. A typical application is non-slice selective fat saturation.
  • .designinfo: is an optional string to keep track of useful RF design details (mostly for debugging purposes), and it is up to the user to fill this in, except for some pre-generated RF pulses available in KSFoundation_GERF.h
  • .amp: This field is set to 1 by ks_eval_rf(), but will get a proper value after calling GEReq_eval_rfscaling(). The value of .amp is used in the @pg section by ks_pg_rf() to properly amplitude scale the actual RF waveform before internally calling ks_pg_wave() using the .rfwave field.

Method 1: Using pre-defined KS_RF objects

The simplest way to create a KS_RF object is to use one of the RF pulses in GE's product sequences, some of which are available in KSFoundation_GERF.h. To use these ready-made RF pulses, perform the following steps:

  1. Declare a KS_RF object in the @ipgexport section
    • KS_RF myrf = KS_INIT_RF;
  2. Assign the KS_RF object after choosing one in KSFoundation_GERF.h
    • e.g. myrf = exc_fse90;
  3. Call ks_eval_rf() or ks_eval_selrf() to name and initialize the RF pulse

The field .flip can be changed anytime between ks_eval_rf() and GEReq_eval_rfscaling(), e.g. by setting .flip = opflip

Method 2: Creating Sinc RF pulses

For small flip angles, the Fourier transform of the RF envelope is a good approximation of the final slice profile and SLR designed pulses may not be necessary. For these cases, the function ks_eval_rf_sinc() can be used to create a Sinc RF pulse (as a KS_RF object) with a desired bandwidth (BW), time-bandwidth-product (TBW) and window function (e.g. KS_RF_SINCWIN_HAMMING), as follows:

  1. Declare a KS_RF object in the @ipgexport section
    • KS_RF mysincrf = KS_INIT_RF;
  2. Call ks_eval_rf_sinc() to populate the KS_RF object
    • The duration of the RF pulse (mysincrf.rfwave.duration) depends on the chosen BW and TBW
  3. Assign the role of the RF pulse
    • mysincrf.role = KS_RF_ROLE_EXC; // or KS_RF_ROLE_REF, KS_RF_ROLE_INV, KS_RF_ROLE_CHEMSAT

Note that in most scenarios a KS_SELRF is used instead of a KS_RF, as Sinc RFs are typically used with slice selection. Hence, in practice, a KS_SELRF should be declared, and the above procedure should be applied to the .rf field (of type KS_RF) in the KS_SELRF object.

Method 3: Creating custom RF pulses

Another method is to copy a waveform designed elsewhere into the .rfwave (KS_WAVE) field of the KS_RF object. This can be done from either memory (ks_eval_wave()) or disk (ks_eval_wave_file()).

Assuming we have an RF envelope in some float array myexternalrfwave[], the following steps are performed:

  1. Declare a KS_RF object in the @ipgexport section
    • KS_RF mycustomrf = KS_INIT_RF;
  2. Call ks_eval_wave() as ks_eval_wave(&mycustomrf.rfwave, "", res, duration, myexternalrfwave);
    • If the RF pulse should use a THETA or OMEGA waveform, do the same procedure for the fields .thetawave and/or .omegawave
    • res should be number of elements in myexternalrfwave
    • duration (in [us]) must be an integer multiple (>= 2x) of res
  3. Assign the following fields manually:
    • .role
    • .flip [degrees] (will also be the nominal flip angle)
    • .bw [Hz]
    • .iso2end [us] (the time from effective center of the RF pulse to the end)
  4. Call ks_eval_rfstat() to populate the RF_PULSE struct (mycustomrf.rfpulse) that is needed for RF scaling and SAR calculations
    • Note that ks_eval_rfstat() only works for real RF pulses (without THETA or OMEGA waveforms). For complex RF pulses, the .rfpulse struct needs to be set up manually
  5. Call ks_eval_rf() or ks_eval_selrf() to name and initialize the RF pulse, and to let the KS_SEQ_CONTROL struct link to it for later RF scaling in ks_eval_gradrflimits()

Automatic RF scaling and SAR calculations (for sequences written entirely using KSFoundation)

First make sure there is a separate sequence making function in the @pg section that holds all function calls to the various ks_pg_***() functions building up the sequence. E.g. there is a ksfse_pg() function in ksfse.e.

  • It is crucial that this function, only when called on HOST at the end of the function, sets seqctrl.min_duration to a value corresponding to the number of us to the end of the sequence module (see example below).
  • As this sequence making function is to be played out in both cveval() (on HOST) and in pulsegen() in the @pg section (on TGT), UsePgenOnHost() must be set in the Imakefile
  • Multiple sequence modules (such as main sequence, FatSat, Inversion etc.), each with its own KS_SEQ_CONTROL handle and sequence making function, are added to one common KS_SEQ_COLLECTION for all sequence modules involved. This KS_SEQ_COLLECTION struct is passed both to RF scaling (GEReq_eval_rfscaling()) and SAR handling (GEReq_eval_checkTR_SAR(), which calls ks_eval_gradrflimits()) routines.
  • Please see KS_SEQ_COLLECTION for more information on the order of events necessary in cveval() to perform RF scaling and SAR handling. **

Example for a pulse sequence entirely written in KSFoundation

@ipgexport (HOST)
KS_RF myfatsat = KS_INIT_RF;
@host
cveval() {
if (cffield == 30000)
myfatsat = chemsat_cs3t; // chemsat_cs3t is a const KS_RF declared in KSFoundation_GERF.h
else
myfatsat = chemsat_csm; // chemsat_csm is a const KS_RF declared in KSFoundation_GERF.h
ks_eval_rf(&myfatsat, "myfatsat");
mypsd_pg(); // at the end of mypsd_pg(), seqctrl.min_duration needs to be set to the absolute end time in [us] of this sequence (module)
// add this sequence module to the sequence collection
// perform RF scaling using all sequence modules to be used (e.g. main sequence, FatSat, Inversion etc.)
// TR timing, slice ordering and SAR calcs, in four steps.
// Here we assume there is a wrapper function (mysliceloop_nargs(int slperpass, int nargs, void **args)) to the sequence's sliceloop function (e.g. mysliceloop()),
// where e.g. mysliceloop_nargs(7, 0, NULL) plays out seven 2D slices and returns the slice loop duration in [us]
// Also, it is assumed the the sliceloop function calls ks_scan_playsequence() to accumulate the time required for all its sequence modules
// Step 1: Calculate # slices per TR and how much spare time we have within the current TR by running the slice loop
// Output 1: slperpass - Number of slices that fit within the selected TR (optr)
// Output 2: timetoadd_perTR - The necessary filling time to reach the set TR, given slperpass slices
GEReq_eval_TR(&slperpass, &timetoadd_perTR, 0, seqcollection, mysliceloop_nargs, 0, NULL);
// Step 2: Calculate the slice plan (slice sorting over one or more passes) and passes (acqs) for normal interleaved 2D imaging
// ks_slice_plan is passed to GEReq_predownload_store_sliceplan() in predownload()
ks_calc_sliceplan(&ks_slice_plan, exist(opslquant), slperpass);
// Step 3: Spread the available `timetoadd_perTR` evenly on the main sequence module, by increasing the .duration of each slice by timetoadd_perTR/slperpass
seqctrl.duration = RUP_GRD(seqctrl.duration + CEIL_DIV(timetoadd_perTR, ks_slice_plan.nslices_per_pass));
// Step 4: Check that TR is fullfilled by dryrunning the slice loop with updated seqctrl.duration fields for the sequence modules involved.
// Update SAR values in the UI (error will occur if the sum of sequence durations differs from optr)
} // end of cveval()
@pg
void mypsd_pg() {
tmploc.pos = 1ms;
ks_pg_rf(&myfatsat, tmploc, &seqctrl); // play the fat-sat RF pulse, beginning at 1ms into the sequence
tmploc.pos = 20ms;
tmploc.ampscale = 0.5;
ks_pg_rf(&myfatsat, tmploc, &seqctrl); // contrived example where a 2nd instance of the RF pulse is played out at 20ms with half the flip angle
tmploc.pos += myfatsat.rfwave.duration; // time for the end of last sequence entry
#ifndef IPG
// HOST only (make also sure, seqctrl.ssi_time > 0 before calling this function):
#endif
}
mypsd_pg();
KS_SEQLENGTH(seqcore, seqctrl); // The only KSFoundation EPIC Macro - KS_SEQLENGTH() - which takes a KS_SEQ_CONTROL object as 2nd arg
}
@rsp (TGT)
scan() {
...for run-time amplitude modulation of the 1st instance of the RF pulse (i.e. the one at 20ms), call...
ks_scan_rf_ampscale(&myfatsat, 0, 0.5); // reduces the 1st instance of the RF pulse by another factor of 2
ks_scan_rf_off(&myfatsat, 0); // turn OFF the 0th instance of the RF pulse
ks_scan_rf_on(&myfatsat, 0); // turn ON the 0th instance of the RF pulse
ks_scan_rf_on_chop(&myfatsat, 0); // turn ON the 0th instance and toggle the sign of the RF pulse every time it is called
}

RF scaling when KS_RF object(s) are added to an existing standard EPIC sequence

When KS_RF objects are added to an existing GE product sequence (or any other standard EPIC sequence), RF scaling and SAR calculations are already taken care of for the non-KSFoundation RF-pulses. To include also the KS_RF pulses (including the KS_RF objects in KS_SELRF) as a part of the main sequence's RF scaling, the following steps are needed:

  1. Add one new SLOT in the rfpulse[] struct array for each KS_RF (or KS_SELRF) object by editing grad_rf_<psdname>.h. The file grad_rf_empty.h in the KSFoundation folder can be used as a template. This only serves a place holder to fill up space in rfpulse[], and no need to edit the grad_rf_<psdname>.h further
  2. Increase RF_FREE1 (sometimes RF_FREE2) in grad_rf_<psdname>.globals.h and add defines for the new slots
  3. After setting up the KS_RF objects using one of the methods above, make sure the .rfpulse field of each KS_RF object has the fields:
    • .activity = PSD_SCAN_ON
    • .num = <number of instances of this KS_RF object in the sequence>
  4. Copy the .rfpulse field of each KS_RF object in to each corresponding SLOT in the rfpulse[] array. E.g.:
    • rfpulse[MYOWNFATSAT_SLOT] = myfatsat.rfpulse; where MYOWNFATSAT_SLOT has been defined as an available number less than RF_FREE1 in grad_rf_<psdname>.globals.h

Field Documentation

◆ role

int role

The purpose of the RF pulse. Valid values: KS_RF_ROLE_EXC, KS_RF_ROLE_REF, KS_RF_ROLE_CHEMSAT, KS_RF_ROLE_INV, KS_RF_ROLE_SPSAT

◆ flip

float flip

The flip angle in [degrees]

◆ bw

float bw

The bandwidth of the RF pulse [Hz]

◆ cf_offset

float cf_offset

Center frequency offset in [Hz] for non-slice selective RF. Use e.g. 0 for water, 220 (1.5T) or 440 (3T) for fat

◆ amp

float amp

Relative amplitude, not in [G], set by GE's RF scaling routines using ks_eval_gradrflimits()

◆ start2iso

int start2iso

Time from start of RF pulse to its magnetic center in [us]

◆ iso2end

int iso2end

Time from the magnetic center of the RF pulse to the end in [us]

◆ iso2end_subpulse

int iso2end_subpulse

Time for last subpulse to end of RF waveform. Used by SPSP

◆ designinfo

KS_DESCRIPTION designinfo

Descriptive string regarding the RF pulse design with maximum KS_DESCRIPTION_LENGTH characters

◆ rfpulse

RF_PULSE rfpulse

Internal use (typedef struct RF_PULSE)

◆ rfwave

KS_WAVE rfwave

KS_WAVE object for RF. Unit: [a.u.]

◆ omegawave

KS_WAVE omegawave

Optional KS_WAVE object for OMEGA (frequency modulation). Unit: [a.u.]

◆ thetawave

KS_WAVE thetawave

Optional KS_WAVE object for THETA (phase modulation). Unit: [degrees]


The documentation for this struct was generated from the following file: