|
msl 1.3.0
|
The loop sequence defined in the previous parts being complete, the only thing remaining is to play RF pulses and gradient pulses. We will also need to provide RF power information, and, in order to interface with the reconstruction pipeline, we will also need to fill what is know as the measurement data header, or MDH.
All this will be performed by a user-defined graph node, which we will call Kernel (and which will of course replace our previous kernel node which only printed progress).
Each node class in the sequence graph derives, directly or indirectly, from the msl::graph::AbstractNode class, and node classes must respect the interface of msl::graph::AbstractNode. At minimum, this means:
The duration function returns the duration of real-time objets (for example RF pulses or gradient pulses), if any, contained in the node: the msl::graph::If node has a duration depending on whether the condition is true, the msl::graph::Loop node has a duration equal to the number of repetitions times the duration of all children, etc. The duration of the kernel node for the FLASH sequence is fixed: it is equal to the repetition time since it encompasses everything happening during one repetition of the sequence.
The rfInfo function similarly returns the RF power of real-time objects contained in the node. The FLASH sequence has only one RF pulse per repetition and, since gradient pulses carry no RF power, its rfInfo function will return the RF power of the excitation pulse.
The declaration of the Kernel will be stored in the Kernel.h file, as follows:
In this declaration, the DECLARE_POINTERS macro simply defines to type aliases (Pointer and ConstPointer) to follow the common API of all nodes. The creation of a node instance is handled by the New static function, as for previous node examples. This function will call the private constructor, which will handle all the necessary initializations. Using these two functions will guarantee that only pointers to nodes can be created while respecting the usual C++ coding guidelines regarding constructors.
In this specific class, the destructor does not do anything, and is thus defaulted. As usual, if objects are allocated in the constructor, they must be de-allocated in the destructor.
The definitions of the class functions will be stored in the Kernel.cpp file, as follows
Note that the duration functions cannot directly return the TR since it would need access to the protocol object: this is the reason that the class contains a _duration variable, in which the TR is stored in the prepare function.
The sequence can now be modified to use the Kernel class in place of the previous progress-printing function. In the prepare function, we will also update the duration and SAR information of the sequence based on the graph information.
Things will work as before, with one difference: the expected duration of the sequence, as shown in POET, is now correct, and will update according to changes in TR and resolution.
RF pulses in msl are defined in the msl::rf_pulses namespace. Two main kinds of obects exist there: pulses per se, for example msl::rf_pulses::Sinc or msl::rf_pulses::Rect, and pulse modifiers, for example msl::rf_pulses::Selective. This version of the FLASH sequence will work with a selective excitation pulse, combining those two kinds of objects.
We will start by adding the _excitation object to the kernel, as well as a way to access the current slice: the slice is required since it will affect the slice selection gradient played during the RF pulse. The access to the slice is provided by a msl::DictionaryItem referencing the "slices" counter defined in the registry.
Modify the Kernel.h file to add the two members, along with their headers, and to add a parameter to the constructor:
In the Kernel.cpp file, modify the definition of the constructors accordingly:
Finally, modify the sequence class to use the new prototype:
The RF pulse can now be configured in the prepare function of the Kernel class. This consists in setting fields common to all pulse objects:
Other fields are specific to the sinc pulse (msl::rf_pulses::Sinc::setTimeBandwidthProduct) or to selective pulses (msl::rf_pulses::Selective::setThickness, msl::rf_pulses::Selective::setRFDuration).
After being set up, the RF pulse must be prepared: its prepare function returns a status: if the preparation succeeded, we can continue; however, if the preparation failed, the status must be propagated to the caller of the function. We will use the ON_ERROR_RETURN_STATUS macro to perform this.
Put together, we get the following:
We can then play the RF pulse in the Kernel::run function. For this, we need to:
The real-time event block is opened by calling fRTEBInit and close by calling fRTEBFinish, both of the returning a status object, so they will be wrapped in the ON_ERROR_RETURN_STATUS macro. In the event block, we will also add a dummy event at the repetition time: without it, the sequence timing would not be correct.
The run function is then:
You can simulate the sequence, with something finally happening: on the RF channel, you can see sinc pulses, and their accompanying slice selection gradient on the Z gradient channel.
Since the FLASH is a sequence with a short TR, some spoiling will be required. The next part will add gradient spoiling, and we will add RF spoiling in this part. RF spoiling consists in incrementing the phase of the RF pulse in a quadratic pattern. This can of course be handled manually, but the msl::PhaseCycling object will take care of this for us.
Start by adding a object of type msl::PhaseCycling to the registry:
The two parameters of the constructor are respectively the linear phase increment and the quadratic phase increment. The phase can be increment as for any integer (++phaseCycling), and the current phase is retrieved through phaseCycling.phase(). We will then need to increment the phase after each kernel call using an msl::graph::Action node:
We then add a new parameter to the kernel constructor, and a matching msl::DictionaryItem, and update the construction of the kernel in the sequence:
The last modification to implement RF spoiling requires setting the phase of the RF pulse, both in the prepare and in the run functions of the kernel:
The phase of the RF pulse can be seen on the NC1 channel in the simulation.