This page describes the design guidelines of the module Classes for sequence design (odinseq library)
This is a framework for NMR sequence design. It follows a hardware independent, object oriented design approach to reach a high degree of flexibilty and portability while keeping the amount of source code to a minimum.
An NMR sequence typically consists of different components like pulses, gradient shapes and so on. From the physicists point of view these objects and their relations can be exclusively described by physical parameters (pulse duration, gradient stength, echo time, ...). This description does not depend on the current hardware the sequence is used on. Furthermore, at this level of abstraction these objects can be grouped together in different ways to form a variety of NMR sequences.
Unfortunately most programming enviroments of contemporary spectrometers/scanners do not reflect this modularity. A sequence written in the native enviroment of the machine often contains a vast amount of redundant source code repeated in every method and a difficult low-level interface. Furthermore each manufacturer follows his own approach to write unportable sequences, which may be a disadvantage for scientists working on different spectrometers.
This is the point where this framework could come into play. It offers a hardware independent programming interface to a C++ class library. This library then performs all the low-level operations that are required for playing out the sequence.
The class hierachy (Classes for sequence design (odinseq library)) is designed according to the following guidelines:
Each type of component in an NMR sequence (RF-pulse, gradient pulse, ...) is identified with a class in the C++ programming language. The components are ordered in a 'family tree' where each child is a specialisation of its parent. For instance a constant gradient (SeqGradConst) on one of the gradient channels has a parent (SeqGradChan) that represents arbitrarily shaped gradient waveforms. This inheritance graph is modelled by the inheritence mechanisms for classes in C++. This approach minimises the amount of code that perform certain operations on the objects, e.g. a function for rotating the SeqGradChan class in the spatial domain (set_gradrotmatrix()) is automatically available for all derived classes.
In this manual the term 'sequence objects' refers to all components of an NMR experiment that control the timing of the sequence. For example RF-pulses, delays and acquisition windows are all specialised sequence objects. An (abstract) base class SeqObjBase exists for all sequence objects. These objects then have a common interface, e.g. they know how to write themselve to the pulse/gradient program.
The term 'gradient objects' refers to all components of an NMR sequence that control the timecourse of the gradient fields, e.g. constant gradients, phase encoding gradients, gradient waveforms. An (abstract) base class SeqGradInterface exists for all gradient objects in the sequence. These objects then have a common interface, i.e. they know how to write themselve to the gradient program or how to be combined among themselves.
Sequence objects can be grouped together by ordering them along the time axis. The result is then a new, more complex sequence object. In analogy to notations commonly used in scientific papers, this is done by using the
+ operator. For instance two RF-pulses represented by two objects called
beta can build a new sequence object
which results in a sequence object which will play out the first pulse and then the second pulse.
Gradient objects can also be serialised by using the
+ operator. Furthermore the
/ operator tries to build a new object that plays out the two operands in parallel if the timing is apropriate. For example two gradient pulses
gp2, one for the read and another for the phase channel, can be played out in parallel by using the combination
Sequence and gradient objects can also be serialised by the
+ operator. To play out a gradient and a sequence object in parallel, the
/ operator can be used. For example let
alpha be an excitation pulse and
constgrad a constant gradient, then the combination
would give a slice selective pulse.
The sequence object SeqObjList can be used as a container for other sequence objects. That is, each SeqObjList can have its own sub-sequence that consists of other sequence components. If a certain operation is applied to a SeqObjList object, the operation will also be applied to all elements of the sub-sequence. For example:
Calling the member function
would then return the sum of the durations of
The gradient object SeqGradChanList can be used as a container for other gradient objects which share the same gradient channel. That is, each SeqGradChanList can have its own sub-sequence that consists of other gradient components. These sub-objects are then played out subsequently. This analogous to the above described sequence container.
New objects can be added to the container class SeqObjList by using the
Loops are special sequence containers. They are used to repeat a certain part of the sequence. Within ODIN, the class SeqObjLoop accomplishes this task. Two kind of loops are possible when using this class: Pure repitition loops and vector loops. Repitition loops simply repeat the specified part of the sequence without altering it. Let
loop be of type SeqObjLoop, then heir syntax is:
This statement will return a sequence object that repeats the sequence object
n times, where
n is an integer number.
Vector loops also repeat a specified part of the sequence. Furthermore they increment the value of a sequence vector each time it is played out. Sequence vectors are for example phase encoding gradients or pulses with a frequency list. All sequence vectors share the same base class SeqVector. Let
loop be of type SeqObjLoop, then the syntax for vector loops is:
This statement will return a sequence object that repeats the sequence object
kernel while incrementing the values of the attached sequence vectors
vector2, ... The vectors must contain the same number of values. The loop is then repeated this number of times.
ODIN is consistent corncerning the physical units. That is, everywhere in the library the following units and their combinations are used:
This system has shown to be of practical value for NMR because the numbers are then in a reasonable range. For example the gradient strength is then given in mT/mm and the frequency is given in 1/ms=kHz. The only exception is the angular unit which is treated internally as rad but can be specified at the interface functions in degree for phaselists and flipangles.
If you want to write your own class that can be plugged into the ODIN framework, please use the following form for your class 'SeqMyClass' that is derived from 'SeqBaseClass':
Following this standard prevents the base classes from getting confused by not correctly initialising or copying them.
Unfortunately, on some systems the stanard C++ library seems to be either broken or absent. For these platforms, ODIN contains its own implementation that is a subset of the original library. To use the same source code on different platforms, classes of the standard library are used together with the prefix STD_, e.g. STD_list instead of std::list. These macros will be replaced by the appropriate classes on each platform.
To generate debugging and tracing output, please use the Log template class instead of using streams. Instances of that class offer a stream to log trace messages via the ODINLOG macro:
Which will result in the following output:
It can generate debugging output at different levels of verbosity and the output is redirected to the native logging channel (console, log file) of the current platform. Furthermore, it can be completely removed from the executable for release compilations in a safe way without changing the source code.