KSFoundation  [April-2021]
A platform for simpler EPIC programming on GE MR systems
EPIC crash course


Compiling pulse sequences (PSDs) for the MR-scanner

Compiling pulse sequences (PSDs) for the MR-scanner usually involves the following steps:

  • Making sure there is a match between the PSD main file mypsd.e and the following line in the Imakefile
    PSD = mypsd

  • Setting up sub directories once before compiling by (do this after downloading/updating from Git):
    you@linux> prep_psd_dir

  • Compiling mypsd.e for Simulation and/or Hardware (and dealing with compilation errors)
    you@linux> psdqmake clean sim // compile for simulation, i.e. WTools (part of ESE distribution)
    you@linux> psdqmake clean hw // compile for hardware, i.e. the MR-scanner
    you@linux> psdqmake clean all // compile for simulation and hardware

  • Running the sequence in simulation mode on a linux machine to figure out various HOST and TGT bugs and problems using tools such as:
    - The debugger (e.g. ddd or some other graphical debugger) on HOST and TGT
    • fprintf(stderr, "") to see info in the WTools main window (but learn to use e.g. the ddd debugger, it's way better than printing). KSFoundation EPIC has also functions ks_error() and ks_dbg(), which also stores the output in text files (ks_errors.txt and ks_debug.txt)
    • KSFoundation EPIC automatically saves PNG, SVG or PDF files for each sequence module and also a slice-time chart. See Plotting (HTML).
    • Plotting in WTools using [Analysis]->[MGD Sim]->[LoadCVs->Pulsegen->PlotPulse]
    • Running the PSD using [Analysis]->[MGD Sim]->[LoadCVs->Pulsegen->RunEntry], while also hammering on the [PlotPulse] button to force a screen refresh in the PlotPulse window. Sometimes the [LoadCVs] or [Pulsegen] button hangs, and when it does, press the [Refresh] button at the bottom of the [MGD Sim] window. But see also Plotting (HTML).

  • Moving the sequence binaries (2 files) to the MR-scanner (mypsd and mypsd.psd.o). GE has its product PSDs in /usr/g/bin, so you must avoid overwriting these. A good practice is to put your own PSDs in /usr/g/research/<yourname>/ and make symolic links to them from /usr/g/bin, where the MR-scanner expects PSDs to reside:
    you@linux> scp mypsd mypsd.psd.o sdc@<mrscanner>:/usr/g/research/<yourname>/
    sdc@mrscanner> cd /usr/g/bin/
    sdc@mrscanner> rm mypsd mypsd.o // remove old links that might be here. Don't remove any product PSDs!
    sdc@mrscanner> ln -s /usr/g/research/<yourname>/mypsd .
    sdc@mrscanner> ln -s /usr/g/research/<yourname>/mypsd.psd.o .

  • Testing the user interface (UI) behavior on the MR-scanner, trying to predownload the PSD by pressing [Save Rx].

  • Download the PSD, start prescan and scan, by pressing [Scan].

Simulation vs. Hardware

Most code in a pulse sequence should run both in simulation (WTools) and on the MR-scanner. Exception to this include:

  • Plotting and debugging calls that can not be run on the MR-scanner since it won't be able to keep up with its real-time scanning process at the same time
  • Fake slices (using e.g. simscan()) in simulation, as there is no one end user prescribing the slices in simulation

The ESE environment have C-preprocessing directives that are automatically set when compiling using psdqmake:

  • SIM (-DSIM): Simulation
  • PSD_HW (-DPSD_HW): Hardware

    Simulation vs. Hardware


The MR-scanner is really two computers, one for the user interface (HOST) and one for the real-time scanning (TGT, a.k.a. IPG). This is why the pulse sequence is split up into two binaries to be placed on the MR-scanner. In simulation (WTools), HOST is the EvalTool window and TGT is the MGDSim window.

When running psdqmake, first the EPIC preprocessor takes the mypsd.e file (including all other *.e files mypsd.e refers to) and generates mypsd.host.c (HOST code) and mypsd.tgt.c (TGT code). If there is a compilation error, it will refer to either the .host.c or the .tgt.c file and point to some line number. One can open these files to see where the compilation error is, but one must sure NOT to modify these autogenerated c-files but instead the corresponding lines in mypsd.e.


Mandatory code sections (@****) in *.e and their use

  • @global: Global C-code (variables declaration, #include). Accessible on both HOST and TGT. Limited use usually.

  • @cv: EPIC specific Control Variables (CVs). Modifyable on HOST, readable on TGT. Variables may be of type int or float and there is an associate (hidden) _myvar struct to each myvar.

  • @ipgexport: Section to put global C-arrays and C-structs in. Accessible on both HOST and TGT.

  • @host: HOST code containing mandatory functions cvinit(), cveval(), cvcheck() and predownload() (see below), etc.

  • @pg: TGT code (but also used on HOST by the UsePgenOnHost() entry in the Imakefile) related to the generation of pulses (waveforms and trapezoids) on hardware. No real-time execution here. KSFoundation EPIC PSDs heavily relies on running @pg function in cveval() to figure out e.g. sequence durations and SAR limits.

  • @rspvar: Declaration of rsp-variables for TGT use, which are variables that can be modified manually during scanning on the MR-scanner (by [Scan]->[ModifyRSP]). This may be useful for debugging sometimes.

  • @rsp: TGT code containing the mandatory functions mps2(), aps2() and scan(), where scan() corresponds to the actual pressing of the [Scan] button.

Mandatory top-level functions in a PSD and what they do

  • cvinit() - HOST: Should set up UI menu button content and initialize various things. Menu buttons are controlled by variables beginning with pi**, see epic.h for a complete list or e.g. ksepi_init_UI() for an example. In WTools, this function is normally called every time the user modifies a value in EvalTool. On the MR-scanner, it will execute when the PSD is selected (but actually also a few times whenever a UI value is changed).

  • cveval() - HOST: Should evaluate UI selections, perform gradient/RF amplitude/time calculations, set up various timings etc. In WTools, this function is normally called multiple times every time the user modifies a value in EvalTool. On the MR-scanner it will execute 37+ times whenever a UI value is changed.

  • cvcheck() - HOST: Should contain epic_error() (or ks_error() for KSFoundation EPIC PSDs) with messages to the end user about e.g. errors in parameter combinations. In WTools, this function is normally called once only every time the user modifies a value in EvalTool. On the MR-scanner it will execute once only whenever a UI value is changed.

  • predownload() - HOST: Should set e.g. recon steering variables (rh**) and other variables that are consequences of other parameter choices. See e.g. GEReq_predownload_setrecon_readphase() for an example. In WTools, this function is normally called once only every time the user modifies a value in EvalTool. On the MR-scanner it will execute once when the user presses [Save Rx].

  • pulsegen() - TGT (but also HOST): Using the variables set up on HOST using cvinit(), cveval(), and predownload(), pulsegen() is creating actual waveform memory and instruction amplitudes on the hardware (TGT). pulsegen() typically includes more than one pulse sequence (module), and the way to mark a new sequence is via the EPIC macro SEQLENGTH(). KSFoundation EPIC uses its own KS_SEQLENGTH() instead. A "sequence" may be the main sequence or e.g. a fatsat sequence, the latter to be conceptually viewed as a plugin/option to the main sequence. In scan(), it is possible to switch between these sequences in real-time (not unlike a drum machine).

  • mps2() - TGT: Manual Prescan 2. Prescan involves setting transmit gain (TG), center frequency (CFH, CFL), and receiver gain (R1, R2). TG, CFH/CFL are set by Prescan.e, but the receiver gain must be set by calling the current PSD without phase encoding gradients on. mps2() is called by selecting the [ScanTR R1/R2] button in [ManualPrescan] on the MR-scanner and should play the sequence in a way that the receiver can get a proper signal to tune gain settings from. R1 is the analog receiver gain and R2 the digital receiver gain (step 2).

  • aps2() - TGT: Same as mps2(), but executed during Auto Prescan, rather than Manual Prescan, before every scan.

  • scan() - TGT: Should play out the sequence in real-time and change phase encoding steps, RF frequencies etc. Switching between sequeneces (e.g. between the main sequence and a fatsat sequence) is done via a call to boffset(). In KSFoundation EPIC, the sequence switching and playout is done via ks_scan_playsequence().

CVs (Control Variables)

A control variable may be an int or a float declared as (example with given numbers):

int myvar = 12 with {2, 20, 12, VIS, "My description of this variable",};

In this example, myvar is declared as an int and initialized to 12, but behind the scenes a struct _myvar will be declared with fields _myvar.minval = 2, _myvar.maxval = 20, _myvar.defval = 12, and _myvar.descr = "My description of this variable". "VIS" means that one can search and find the variable in [Display CVs] on the MR-scanner. In addition, this _myvar struct has the fields .existflag and .fixedflag.

The _struct should normally not be used directly. Instead, there are EPIC C-macros like cvmin(), cvmax(), cvdef(), cvmod() and cvoverride(), which takes myvar as an input argument, not _myvar. The idea behind min/max is to force a variable to be within a certain range. Often a UI error is passed to the end user if this is not the case, but sometimes the error is only shown as a "Download failure" when pressing [Scan].

The field .existflag is automatically set to PSD_ON (= TRUE = 1) when the CV exist. For all CVs that belong to a UI menu button or field (called op***), the UI sets the _op***.existflag = TRUE when the user has selected it. To ask if the user has selected a CV myvar:

if (existcv(myvar)) // returns TRUE (= PSD_ON = 1) or FALSE (= PSD_OFF = 0)

Tricker still, one may see the following look-a-like call, but with completely different result:

x = exist(myvar) // if the CV "exist", x will be set to myvar, otherwise _myvar.defval

When the user sets an op** (i.e. UI) CV, the .fixedflag field is also set. If this field is set for a CV, any assignment in the PSD code will be ignored (!). For example, before the user has set the echo time (TE) corresponding to the CV opte, the following code

opte = 15000; // time in [us]

will set TE to 15 ms. But as soon as the user has selected/modified it, _opte.fixedflag = TRUE, and the very same assignment is ignored. They way this works it that the EPIC preprocessor (producing .host.c and tgt.c from the .e file), changes the line opte = 15000; to:

opte = _opte.fixedflag ? ((void)(15000), opte) : 15000;

which effectively is the same thing as only assigning it to 15000 if .fixedflag = FALSE.

Sometimes this fixedflag mechanism can be good even though it makes the coding different. But sometimes, one may want to override the user's selection/change of a CV, which is done by (here opte as an example):

cvoverride(opte, <newval>, PSD_FIX_ON, PSD_EXIST_ON);

where the second argument is the forced new value. The third argument sets the _opte.fixedflag field and the fourth argument set the _opte.existflag field afterwards.

See /ESE_xxx/psd/psdsource/epic.h for most pre-existing CVs, incl. op***.

@inline and %ifdef

A C preprocessor uses defines to include/exclude code using

do something;
do somthing else;
// or
#if SOMETHING > 10
do something;

which is simply performed in-place in the code before the C-compiler sees it. A typical example in EPIC is to include/exclude code hunks for Simulation/Hardware or HOST/TGT.

However, before the C preprocessor acts on the .host.c and .tgt.c files generated from the .e file (by the EPIC preprocessor), there are also directives for the EPIC preprocessor in the .e file.

Analogously to #include for .c files, there is an

@inline somefiletoinclude.e

for EPIC's .e files. This includes the file somefiletoinclude.e, but any section (@pg, @host, etc.) in that file will be moved to the corresponding sections rather than placed at the point of @inline.

There is also a way to conditionally include/exclude code in the .e file parsed by the EPIC preprocessor using %ifdef instead of #ifdef. There is however no %if like #if. The need for %ifdef is rare, but must be used when one need to exclude EPIC specific code, e.g. a CV declaration dependent on release versions, before the EPIC processing to *.c files.

For example, for KSFoundation EPIC PSDs, their Imakefile adds two defines based on the EPIC release. To make a pulse sequence compatible with multiple EPIC releases, we let the Imakefile create one define for the EPIC preprocessor and one define for the C preprocessor (example here for release 25.x):

  • EPIC_RELEASE_IS_25x (EPIC preprocessor). Allows %ifdef EPIC_RELEASE_IS_25x
  • EPIC_RELEASE = 25 (C preprocessor). Allows #if EPIC_RELEASE >= 25

These can be used in the sequence to handle quirks (e.g. change of # of arguments) in some functions, or new/obsolete functions, between releases. For KSFoundation EPIC PSDs, all release-specific stuff has already been placed in GERequired.e to make the PSDs compatible with multiple releases without clutter.