Sequence
This section delves into some details about how a sequence is constructed. The sequence definition in KomaMRI is strongly related to the Pulseq definition. After reading this section, you should be able to create your own Sequence structs for conducting custom simulations using the KomaMRI package.
KomaMRI Sequence Overview
Let's introduce the following simple sequence figure to expand from a visual example to a more general sequence definition:
A sequence can be thought of as an ordered concatenation of blocks over time. Each block is essentially a sequence with a length of 1. Every block consists of an RF pulse, the
A Sequence stores one column per block. Its main fields are:
| Field | Meaning |
|---|---|
GR | Gradient events. Rows are the x, y, and z axes; columns are blocks. |
RF | RF events. Rows are RF channels; columns are blocks. |
ADC | ADC event for each block. |
DUR | Duration of each block in seconds. |
EXT | Pulseq extensions for each block, such as labels, triggers, or rotations. |
DEF | Dictionary of sequence definitions, mostly used for file I/O and reconstruction metadata. |
The RF, Grad, and ADC events are explained in Sequence Events.
Warning
So far, KomaMRI can only manage one coil for RF excitations. However, in future versions, parallel transmit pTX will be managed by adding more ``rows'' to the RF matrix of the Sequence field name.
In order to understand how a Sequence struct can be manipulated in Julia, let's use the EPI sequence example. You can display basic information of the Sequence variable in the Julia REPL:
julia> seq = PulseDesigner.EPI_example()
Sequence[ τ = 66.847 ms | blocks: 204 | ADC: 101 | GR: 205 | RF: 1 | EXT: 0 | DEF: 17 ]The summary reports total duration, block and event counts, and the number of definitions.
To display the sequence in an graph, we can use the plot_seq function:
julia> plot_seq(seq; slider=false)This way, you can see exactly where the RF, Grad and ADC structs are located in time.
You can access and filter information for the RF, Grad, ADC, and DUR field names of a Sequence using the dot notation. This allows you to display helpful information about the organization of the Sequence struct:
julia> seq.RF
1×204 Matrix{Union{RF{Float64, Float64, Float64}, RF{Float64, Float64, Int64}}}:
⊓(0.5872 ms) ⇿(0.0 ms) ⇿(0.0 ms) … ⇿(0.0 ms) ⇿(0.0 ms) ⇿(0.0 ms)
julia> seq.GR
3×204 Matrix{Union{Grad{Float64, Float64}, Grad{Int64, Float64}}}:
⇿(0.5872 ms) ⊓(0.42 ms) ⊓(0.42 ms) … ⇿(0.23 ms) ⊓(0.42 ms) ⊓(0.42 ms)
⇿(0.5872 ms) ⊓(0.42 ms) ⇿(0.42 ms) ⊓(0.23 ms) ⇿(0.42 ms) ⊓(0.42 ms)
⇿(0.5872 ms) ⇿(0.0 ms) ⇿(0.0 ms) ⇿(0.0 ms) ⇿(0.0 ms) ⇿(0.0 ms)
julia> seq.ADC
204-element Vector{ADC}:
ADC(0, 0.0, 0.0, 0.0, 0.0)
ADC(0, 0.0, 0.0, 0.0, 0.0)
ADC(101, 0.00019999999999999998, 0.00011, 0.0, 0.0)
ADC(0, 1.0e-5, 0.00011, 0.0, 0.0)
ADC(101, 0.00019999999999999998, 0.00011, 0.0, 0.0)
ADC(0, 1.0e-5, 0.00011, 0.0, 0.0)
ADC(101, 0.00019999999999999998, 0.00011, 0.0, 0.0)
ADC(0, 1.0e-5, 0.00011, 0.0, 0.0)
ADC(101, 0.00019999999999999998, 0.00011, 0.0, 0.0)
ADC(0, 1.0e-5, 0.00011, 0.0, 0.0)
⋮
ADC(0, 1.0e-5, 0.00011, 0.0, 0.0)
ADC(101, 0.00019999999999999998, 0.00011, 0.0, 0.0)
ADC(0, 1.0e-5, 0.00011, 0.0, 0.0)
ADC(101, 0.00019999999999999998, 0.00011, 0.0, 0.0)
ADC(0, 1.0e-5, 0.00011, 0.0, 0.0)
ADC(101, 0.00019999999999999998, 0.00011, 0.0, 0.0)
ADC(0, 1.0e-5, 0.00011, 0.0, 0.0)
ADC(101, 0.00019999999999999998, 0.00011, 0.0, 0.0)
ADC(0, 0.0, 0.0, 0.0, 0.0)
julia> seq.DUR
204-element Vector{Float64}:
0.0005871650124959989
0.00042
0.00042
0.00023
0.00042
0.00023
0.00042
0.00023
0.00042
0.00023
⋮
0.00023
0.00042
0.00023
0.00042
0.00023
0.00042
0.00023
0.00042
0.00042Additionally, you can access a subset of blocks in a Sequence by slicing or indexing. The result will also be a Sequence struct, allowing you to perform the same operations as you would with a full Sequence (just a heads-up: this is analogous for the Phantom structure). For example, if you want to analyze the first 11 blocks, you can do the following:
julia> seq[1:11]
Sequence[ τ = 4.027 ms | blocks: 11 | ADC: 5 | GR: 11 | RF: 1 | EXT: 0 | DEF: 17 ]
julia> seq[1:11].GR
3×11 view(::Matrix{Union{Grad{Float64, Float64}, Grad{Int64, Float64}}}, :, 1:11) with eltype Union{Grad{Float64, Float64}, Grad{Int64, Float64}}:
⇿(0.5872 ms) ⊓(0.42 ms) ⊓(0.42 ms) … ⊓(0.42 ms) ⇿(0.23 ms) ⊓(0.42 ms)
⇿(0.5872 ms) ⊓(0.42 ms) ⇿(0.42 ms) ⇿(0.42 ms) ⊓(0.23 ms) ⇿(0.42 ms)
⇿(0.5872 ms) ⇿(0.0 ms) ⇿(0.0 ms) ⇿(0.0 ms) ⇿(0.0 ms) ⇿(0.0 ms)plot_seq(seq[1:11]; slider=false)Concatenation of Sequences
Sequences can be concatenated side by side. The example below demonstrates how to concatenate sequences:
julia> s = PulseDesigner.EPI_example()[1:11]
Sequence[ τ = 4.027 ms | blocks: 11 | ADC: 5 | GR: 11 | RF: 1 | EXT: 0 | DEF: 17 ]
julia> seq = s + s + s
Sequence[ τ = 12.081 ms | blocks: 33 | ADC: 15 | GR: 33 | RF: 3 | EXT: 0 | DEF: 17 ]plot_seq(seq; slider=false)The + operator returns a copied sequence, so reused sequence parts do not share mutable events. For long construction loops, use @addblock inside the loop to append efficiently.