msl 1.3.0
Loading...
Searching...
No Matches
RF Pulses, Read-out and Gradient Pulses

RF Pulses

In our simple FLASH sequence, the only RF pulse is a selective sinc pulse, played at the beginning of the repetition, performing excitation of the magnetization. In msl, all RF pulses are defined by classes which inherit from msl::RFPulse : these can be concrete RF pulses (e.g. msl::rf_pulses::Sinc or msl::rf_pulses::External), or modifiers to other pulses (e.g. msl::rf_pulses::Selective). All pulses will share the API of msl::RFPulse, and some will add extra members, e.g. msl::rf_pulses::External which handles family of pulses.

Simple Sinc Pulse

The creation of a concrete sinc pulse requires creating the RF pulse in a default state and then modifying it. Note that it is possible to chain calls to the setters function.

Note
Most code snippets in this tutorial are self-contained to show the required include directives, both from IDEA and from msl
#include <MrProtSrv/Domain/MrProtData/MrProt/MrProt.h>
void createSincPulse(MrProt & protocol)
{
rfPulse.setFlipAngle(protocol.flipAngle()).setAdditionalPhase(rfPhase);
rfPulse.setDuration(2560).setTimeBandwidthProduct(2.70).setSamples(128);
rfPulse
.setThickness(protocol.sliceSeries().aFront().thickness())
.setSlice(slice);
}
@ Excitation
Definition RFPulse.h:20
virtual RealTimeEvents & setSlice(sSLICE_POS const &slice)
Set the slice used in prepare and run.
SimpleRFPulse< TPulse > & setAdditionalPhase(double additionalPhase) override
Set the additional phase of the RF pulse (deg)
SimpleRFPulse< TPulse > & setDuration(long duration) override
Set the duration of the RF pulse (µs)
SimpleRFPulse< TPulse > & setIdent(std::string const &ident) override
Set the user-defined name of the RF pulse.
SimpleRFPulse< TPulse > & setSamples(long samples) override
Set the number of discrete samples of the RF pulse.
SimpleRFPulse< TPulse > & setFlipAngle(double flipAngle) override
Set the flip angle (deg)
SimpleRFPulse< TPulse > & setType(Type type) override
Set the type of the RF pulse.
SimpleRFPulse< TPulse > & setStartTime(long startTime) override
Set the start time of the RF pulse (µs)
Sinc RF pulse.
Definition Sinc.h:23

Selective Sinc Pulse

The creation of a selective sinc pulse is very similar, and uses both the msl::rf_pulses::Selective modifier and the msl::rf_pulses::Sinc concrete pulse.

#include <MrProtSrv/Domain/MrProtData/MrProt/MrProt.h>
void createSelectiveSincPulse(MrProt & protocol)
{
rfPulse.setIdent("exc").setType(msl::RFPulse::Excitation);
rfPulse.setFlipAngle(protocol.flipAngle()).setAdditionalPhase(rfPhase);
rfPulse.setRFDuration(2560).setSamples(128);
rfPulse.rf().setTimeBandwidthProduct(2.70);
rfPulse
.setThickness(protocol.sliceSeries().aFront().thickness())
.setSlice(slice);
}
Selective RF pulse, i.e. played with a gradient pulse.
Definition Selective.h:23
TPulse const & rf() const
Return a reference to the child RF pulse.
Selective< TPulse > & setFlipAngle(double flipAngle) override
Set the flip angle (deg)
Selective< TPulse > & setIdent(std::string const &ident) override
Set the user-defined name of the RF pulse.
Selective< TPulse > & setRFDuration(long duration)
Set the duration of the RF pulse (µs)
Selective< TPulse > & setStartTime(long startTime) override
Set the start time of the gradient+RF pulse block (µs)

The two differences are:

Preparation and Execution of the Pulse

Once the parameters of a pulse have been set, the RF pulse object can be prepared using, as in IDEA, the protocol, sequence limits and exports. The full set-up of the excitation pulse would then be:

#include <MrProtSrv/Domain/CoreNative/SeqLim.h>
#include <MrProtSrv/Domain/MrProtData/MrProt/MrProt.h>
#include <MrProtSrv/Domain/MrProtData/MrProt/SeqIF/SeqExpo.h>
NLSStatus prepareExcitation(MrProt & protocol, SeqLim & limits, SeqExpo & exports)
{
rfPulse.setIdent("exc").setType(msl::RFPulse::Excitation);
rfPulse.setFlipAngle(protocol.flipAngle()).setAdditionalPhase(rfPhase);
rfPulse.setRFDuration(2560).setSamples(128);
rfPulse.rf().setTimeBandwidthProduct(2.70);
rfPulse
.setThickness(protocol.sliceSeries().aFront().thickness())
.setSlice(slice);
return rfPulse.prepare(protocol, limits, exports);
}
NLSStatus prepare(MrProt &protocol, SeqLim &limits, SeqExpo &exports) override
Prepare the real-time events.
Note
In IDEA, some prepare-like return a boolean, some return a status. In msl, all such functions will return a status.

Once the pulse is prepared, it can be run in a similar way. This must happen in a real-time events block, here opened and closed explicitely. In addition to setting the current slice object (which varies across runs), we also set the phase of the RF pulse, e.g. for RF spoiling.

#include <MrMeasSrv/SeqIF/libRT/sSLICE_POS.h>
#include <MrProtSrv/Domain/CoreNative/SeqLim.h>
#include <MrProtSrv/Domain/MrProtData/MrProt/MrProt.h>
#include <MrProtSrv/Domain/MrProtData/MrProt/SeqIF/SeqExpo.h>
#include <msl/helpers.h>
#include <msl/RFPulse.h>
NLSStatus runExcitation(
MrProt & protocol, SeqLim & limits, SeqExpo & exports,
msl::RFPulse & pulse, double rfPhase, sSLICE_POS const & slice)
{
rfPulse.setAdditionalPhase(rfPhase).setSlice(slice);
ON_ERROR_RETURN_STATUS(fRTEBInit(slice->getROT_MATRIX()));
ON_ERROR_RETURN_STATUS(this->_excitation.run(protocol, limits, exports));
ON_ERROR_RETURN_STATUS(fRTEBFinish());
return MRI_SEQ_SEQU_NORMAL;
}
Base class for RF pulses.
Definition RFPulse.h:14
Selective< TPulse > & setAdditionalPhase(double additionalPhase) override
Set the additional phase of the RF pulse (deg)
#define ON_ERROR_RETURN_STATUS(S)
Execute statement S, and, if not MRRESULT_SUCCESS, return the status.
Definition helpers.h:21
Note
The ON_ERROR_RETURN_STATUS macro checks the return code of its parameter and returns it if it is an error status (if it is a non-error status, the function continue).

Dynamic Pulses

In a more complex sequence, the user may choose between selective and non-selective (e.g. msl::rf_pulses::Rect) pulses: since all RF pulses are derived from the same base class, it is easy to switch between selective and non-selective pulses at run-time by using a pointer to msl::RFPulse. In the following example, a std::shared_ptr is used to avoir manual memory management.

#include <memory>
#include <MrProtSrv/Domain/MrProtData/MrProt/MrProt.h>
#include <msl/RFPulse.h>
void createPulse(MrProt & protocol, long rfDuration)
{
std::shared_ptr<msl::RFPulse> rfPulse;
auto const excitationMode = protocol.getsTXSPEC().getucExcitMode();
if(excitationMode == SEQ::EXCITATION_SLICE_SELECTIVE)
{
auto selective = std::make_shared<msl::rf_pulses::Selective<msl::rf_pulses::Sinc>>();
(*selective)
.setRFDuration(rfDuration)
.setThickness(protocol.sliceSeries().aFront().thickness())
.setSamples(500)
.rf()
.setTimeBandwidthProduct(6.);
rfPulse = selective;
}
else if(excitationMode == SEQ::EXCITATION_VOLUME_SELECTIVE)
{
auto nonSelective = std::make_shared<msl::rf_pulses::Rect>();
(*nonSelective)
.setDuration(rfDuration)
.setSamples(rfDuration / 10);
rfPulse = nonSelective;
}
else
{
throw MrException("Invalid RF Pulse mode");
}
}

Read-out

Our FLASH implementation uses Cartesian trajectories along the \(k_x\) direction of the k-space: this very common situation is handled by the msl::CartesianReadout class. This class handles the timing with respect to the echo times defined in the protocol, and only requires the user to define

If the phase of the RF pulse has been modified (e.g. for RF spoiling), then the readout object must also include this information.

The preparation and error handling are the same as for RF pulses.

#include <MrProtSrv/Domain/CoreNative/SeqLim.h>
#include <MrProtSrv/Domain/MrProtData/MrProt/MrProt.h>
#include <MrProtSrv/Domain/MrProtData/MrProt/SeqIF/SeqExpo.h>
#include <msl/RFPulse.h>
NLSStatus prepareReadout(
MrProt & protocol, SeqLim & limits, SeqExpo & exports,
msl::RFPulse & excitation, double rfPhase)
{
readout
.setADCPosition(-1, +1, protocol)
.setTimeOffset(excitation.peakTime());
// Our FLASH sequence includes RF spoiling: update the phase of the readout
readout.setPhase(rfPhase);
return readout.prepare(protocol, limits, exports);
}
Cartesian read-out along the X gradient, comprising the ADC, NCO manipulation, and read-out gradient.
Definition CartesianReadout.h:23
CartesianReadout & setContrast(long contrast)
Set the contrast number.
CartesianReadout & setPhase(double phase)
Set the phase of the NCO during the readout.
CartesianReadout & setTimeOffset(long timeOffset)
Set the time offset to get the effective echo time, accounting e.g. for the time of the excitation pu...
CartesianReadout & setADCPosition(double kStart, double kEnd)
Set the position on the x-axis of the k-space at the start and the end of the ADC.
NLSStatus prepare(MrProt &protocol, SeqLim &limits, SeqExpo &exports) override
Prepare the real-time events.
virtual long peakTime() const =0
Return the time of the RF peak amplitude (µs)
Note
The names of getter and setters functions are always the same for all objects: for a property named something, the getter will be called something() and the setter setSomething(value). To follow the conventions of IDEA, class names and function names are camelCased.

Running the read-out is also similar to running an RF pulse. In addition to the RF phase and slice specification, the readout object also requires the coordinates of the current line, in the 2D \((k_y, k_z)\) space. More details about this index will be provided in the next part of the tutorial.

#include <MrProtSrv/Domain/CoreNative/SeqLim.h>
#include <MrProtSrv/Domain/MrProtData/MrProt/MrProt.h>
#include <MrProtSrv/Domain/MrProtData/MrProt/SeqIF/SeqExpo.h>
#include <msl/RFPulse.h>
NLSStatus runReadout(
MrProt & protocol, SeqLim & limits, SeqExpo & exports,
msl::CartesianReadout & readout, double rfPhase, msl::Vector2d const & index,
sSLICE_POS const & slice)
{
readout.setPhase(rfPhase).setIndex(index).setSlice(this->slice());
// TODO: don't forget to include all real time objects (RF pulses,
//readouts, gradients, etc.) in a real-time block
ON_ERROR_RETURN_STATUS(readout.run(protocol, limits, exports));
return MRI_SEQ_SEQU_NORMAL;
}
CartesianReadout & setIndex(msl::Vector2l const &index)
Set the index of the current point in the discrete ky-kz space.
NLSStatus run(MrProt &protocol, SeqLim &limits, SeqExpo &exports) override
Run the real-time events.
A vector of arithmetic types.
Definition Vector.h:17
Note
msl::Vector includes simple vector arithmetic for any type and any dimension.

Gradient Pulses

Both the RF pulse and the readout object include gradient pulses, automatically defined to match the k-space extent and per-pixel bandwidth. The FLASH sequence will require two additional gradient lobes:

  • Moving from the k-space position at the end of the excitation pulse (i.e. including part of the slice-selection gradient) to the beginning of the trajectory
  • Moving from the end of the trajectory back to the center of the k-space and spoiling

These two lobes will be tuned so that their duration is minimal, according to the k-space extents and the performance of the gradient system.

The msl::GradientPulse class handles a trapezoidal gradient pulse defined by a maximal amplitude on the \(x\), \(y\), and \(z\) axes, a symmetric ramp duration (i.e. ramp-up time is equal to ramp-down time) and a plateau duration. Although gradient pulses can be defined from these three values, it can be easier to use of the high-level functions.

Once created, the gradient area can be queried in a number of ways: total area, area between two times, area between start and specified time, area between specified time and end, etc.

The k-space vector of the to-begin lobe has two components

  • a fixed component, depending on the readout object and on the slice-selection gradient
  • a varying component, depending on the current k-space line being recorded,

The fixed component is designed so that the echo happens at the center of the k-space (i.e. -readout.areaBeforeEcho()), and that the area of the slice-selection which happens after the peak of the RF pulse is compensated (i.e. -excitation.gradient().areaFrom(peak)).

In the following example, the varying component is computed from the index on the trajectory in the discretized 2D \((k_y, k_z)\) k-space, with the continuous k-space vector obtained using the msl::KSpace object.

Regarding the timing, the gradient lobe is the shortest possible (msl::gradient_pulses::quickest) and ends when the readout begins (msl::GradientPulse::setEndTime and msl::GradientPulse::startTime).

void createToBegin(
MrProt & protocol,
msl::RFPulse const & excitation, msl::CartesianReadout const & readout,
msl::Vector2l const & index)
{
auto const postPeakSliceSelectionArea =
excitation.gradient().areaFrom(
excitation.peakTime() - excitation.startTime());
auto const fixed =
// Move to begin on the readout axis
- readout.areaBeforeEcho()
// Compensate the gradient offset
- postPeakSliceSelectionArea;
msl::KSpace const kSpace(protocol);
auto const point =
msl::Vector3l{kSpace.nCenter()[0], index[0], index[1]
- kSpace.nCenter();
auto const gradientSpecs = msl::GradientSpecs::current(protocol);
fixed+kSpace.k(point), gradientSpecs);
toBegin.setEndTime(readout.startTime());
}
msl::Vector3d areaBeforeEcho() const
Return the area of the read-out gradient before the effective echo time.
long startTime() const override
Return the start time of the events.
Description of the discretized k-space geometry.
Definition KSpace.h:22
virtual long startTime() const =0
Return the start time of the events.
GradientPulse quickest(Vector3d const &k, double maxAmplitude, double minRiseTime)
Compute the quickest gradient pulse to achieve the given k-space vector.
static GradientSpecs current(MrProt const &protocol)
Return the gradient specs from SysProperties and the current gradient mode.