|
msl 1.3.0
|
This part introduces two main concepts of msl: the registry, a dictionary which can shared across objects of the sequence, and the sampling, describing how the k-space will be scanned.
The msl::Sequence class holds a msl::Dictionary object: this dictionary maps keys, in this case strings, to values, which may have any type. As will be shown in later parts, this registry is shared across different objects, so that common data is not duplicated nor needs to be maintained up-to-date at several locations. By default, this registry is empty, but the msl::Sequence class expects a couple of information: these are currently missing, which explains why running the sequence fails.
The registry can be accessed from within the sequence using this->_root->registry(); you can check that it is empty using the following code snippet in the prepare function:
Re-compiling and running mp will then show display 0 items in registry in the terminal.
Storing and retrieving a value is handled by the msl::Dictionary::get and msl::Dictionary::set functions. In a msl::Sequence, it can also be accessed by msl::Sequence::get and msl::Sequence::set. You can for example store the double value 3.14 in the key "pi" in the initialize function, and retrieve it in the prepare function:
Note that both the get and the set are typed explicitely: you must use the correct when retrieving a value from the registry, otherwise an exception will be thrown. To simplify this, instances of msl::DictionaryItem can be stored in objects which make frequent calls to dictionary objects. Continuing with the previous example, we can add a msl::DictionaryItem as a private member of the FLASH class:
The code in the initialize functions is untouched, but the prepare function becomes:
When "calling" the dictionary item, a reference is returned, which allows retrieving and modifying the stored value, the type of the value being stored once and for all in the private member declaration.
The k-space may be sampled different ways: while the Cartesian sampling is the most well-known one, sampling can be radial (as a 2D stack-of-stars or a 3D kooshball), or even non-linear with spiral or rosette trajectories. This behavior is encapsulated in the msl::Sampling class and its children, stored in the msl::samplings namespace (which of course contains msl::samplings::Cartesian).
The sampling object will generate a set of trajectories defined by gradients, coupled with an ADC object to sample the signal. These are stored in children of the msl::Readout class, defined in the msl::readouts. For example, msl::samplings::Cartesian generates msl::readouts::Linear readouts. The current readout is defined by the index property of the sampling class, through the msl::Sampling::index and msl::Sampling::setIndex functions.
Internally, the index is stored as a msl::Counter, an object which enumerates values between 0 (included) and a maximum (excluded) when incremented. Since the value of the counter needs to be shared between the sampling and other objects, it must be stored in the registry, and the msl::Sequence class holds a msl::DictionaryItem referencing the counter, called _trajectories. It is the responsability of the sequence developer to store this object, while incrementation and bound control is handled by the msl::Sequence object. In this tutorial, we store this counter under the "trajectories" key. Add the following line at the end of the initialize function (the comment implies that we will set other similar keys in the following sections).
The msl::Sequence class actually contains not one but two msl::Sampling objects: one used at run-time (_runSampling), and one used at check time (_checkSampling). The msl::Sequence class also contains two other members to indicate whether the sequence is at check time or at run time:
The values of those two members are set automatically by msl. However, the keys associated with them have to be set by the user of the class. As above for the _trajectory counter, set up the keys for the active sampling and kernel mode.
By default, a Cartesian sampling will sample the whole k-space. This behavior can be changed using a msl::Mask, for example to implement GRAPPA or to sample a subset of trajectories while in the check function. The mask is stored as a 2D binary grid in the \(k_y, k_z\) space: any point with value true is sampled, any point with value false is skipped. Pre-defined masks are available (for example msl::partialMask for partial Fourier or msl::ellipticalMask for elliptical scanning), and masks can be combined with the usual boolean operator of '|' for union and & for intersection.
For the run-mode, a high-level helper function merging elliptical scanning, partial Fourier and iPAT is also available in msl::acceleration::mask. For the check mode, it is usual to only sample the extremal k-space lines: in msl, this is provided by msl::cornersMask.
In addition to the previous code snippets, we only need to include a few headers, initialize the two sampling objects and to configure their masks. The initialization is performed in the initialize function:
The configuration of the masks is performed in the prepare function. It involves the notion of msl::GradientSpecs, which describes the maximum gradient amplitude and minimum rise-time. Both values can be set directly, or obtained from the current protocol using msl::GradientSpecs::current.
As before, the sequence should compile and load in POET. In addition, it can now even be simulated, albeit with a disappointing result: the sequence still does nothing.