Sequence Events

As we already know, a Sequence struct contains field names that store arrays of RF, Grad, and ADC structs. In the context of MRI, we refer to RF, Grad, and ADC as "events." To create a Sequence, it's essential to understand how to create these fundamental events.

In the following subsections, we will provide detailed explanations of event parameters and guide you through the process of creating a Sequence using RF, Grad, and ADC events.

RF

The RF struct is defined in the source code of KomaMRI as follows:

mutable struct RF
    A
    T
    Δf
    delay::Real
end

As you can see, it has 4 field names: ''A'' defines amplitude, ''T'' defines duration time, ''delay'' is the distance between the 0 time and the first waveform sample and ''Δf'' is the displacement respect to the main field carrier frequency (this is for advanced users).

''A'' and ''T'' can be numbers or vectors of numbers. Depending on the length of the ''A'' and ''T'', KomaMRI interprets different waveforms:

  • Pulse Waveform: A and T are numbers
  • Uniformly-Sampled Waveform: A is a vector and T is a number
  • Time-Shaped Waveform: A and T are both vectors with the same length (zero-order-hold)

In the image below, we provide a summary of how you can define RF events:

Let's look at some basic examples of creating these RF structs and including them in a Sequence struct. The examples should be self-explanatory.

RF Pulse Waveform

julia> A, T, delay =  10e-3, 0.5e-3, 0.1e-3;

julia> rf = RF(A, T, 0, delay)
←0.1 ms→ RF(10000.0 uT, 0.5 ms, 0.0 Hz)

julia> seq = Sequence(); seq += rf
Sequence[ τ = 0.6 ms | blocks: 1 | ADC: 0 | GR: 0 | RF: 1 | DEF: 0 ]

julia> plot_seq(seq; slider=false)

RF Uniformly-Sampled Waveform

julia> tl = -3:0.2:-0.2; tr = 0.2:0.2:3;

julia> A = (10e-3)*[sin.(π*tl)./(π*tl); 1; sin.(π*tr)./(π*tr)];

julia> T, delay = 0.5e-3, 0.1e-3;

julia> rf = RF(A, T, 0, delay)
←0.1 ms→ RF(∿ uT, 0.5 ms, 0.0 Hz)

julia> seq = Sequence(); seq += rf
Sequence[ τ = 0.6 ms | blocks: 1 | ADC: 0 | GR: 0 | RF: 1 | DEF: 0 ]

julia> plot_seq(seq; slider=false)

RF Time-Shaped Waveform

julia> tl = -4:0.2:-0.2; tr = 0.2:0.2:4

julia> A = (10e-3)*[sin.(π*tl)./(π*tl); 1; 1; sin.(π*tr)./(π*tr)]

julia> T = [0.05e-3*ones(length(tl)); 2e-3; 0.05e-3*ones(length(tl))]

julia> delay = 0.1e-3;

julia> rf = RF(A, T, 0, delay)
←0.1 ms→ RF(∿ uT, 4.0 ms, 0.0 Hz)

julia> seq = Sequence(); seq += rf
Sequence[ τ = 4.1 ms | blocks: 1 | ADC: 0 | GR: 0 | RF: 1 | DEF: 0 ]

julia> plot_seq(seq; slider=false)

Gradient

The Grad struct is defined as follows in the source code of KomaMRI:

mutable struct Grad
    A
    T
    rise::Real
    fall::Real
    delay::Real
end

As you can see, it has 5 field names: ''A'' defines amplitude, ''T'' defines duration time, ''delay'' is the distance between the 0 time and the first waveform sample, ''rise'' and ''fall'' are the time durations of the first and last gradient ramps.

Just like the RF, ''A'' and ''T'' in the Grad struct can be numbers or vectors of numbers. Depending on the length of the ''A'' and ''T'', KomaMRI interprets different waveforms:

  • Trapezoidal Waveform: A and T are numbers
  • Uniformly-Sampled Waveform: A is a vector and T is a number
  • Time-Shaped Waveform: A and T are both vectors, A has one sample more the T (linear interpolation)

In the image below, we provide a summary of how you can define Grad events:

Let's look at some basic examples of creating these Grad structs and including them in a Sequence struct, focusing on the ''x'' component of the gradients. The examples should be self-explanatory.

Gradient Trapezoidal Waveform

julia> A, T, delay, rise, fall = 50*10e-6, 5e-3, 2e-3, 1e-3, 1e-3;

julia> gr = Grad(A, T, rise, fall, delay)
←2.0 ms→ Grad(0.5 mT, 0.5 ms, ↑1.0 ms, ↓1.0 ms)

julia> seq = Sequence([gr])
Sequence[ τ = 9.0 ms | blocks: 1 | ADC: 0 | GR: 1 | RF: 0 | DEF: 0 ]

julia> plot_seq(seq; slider=false)

Gradient Uniformly-Sampled Waveform

julia> t = 0:0.25:7.5

julia> A = 10*10e-6 * sqrt.(π*t) .* sin.(π*t)

julia> T = 10e-3;

julia> delay, rise, fall = 1e-3, 0, 1e-3;

julia> gr = Grad(A, T, rise, fall, delay)
←1.0 ms→ Grad(∿ mT, 10.0 ms, ↑0.0 ms, ↓1.0 ms)

julia> seq = Sequence([gr])
Sequence[ τ = 12.0 ms | blocks: 1 | ADC: 0 | GR: 1 | RF: 0 | DEF: 0 ]

julia> plot_seq(seq; slider=false)

Gradient Time-Shaped Waveform

julia> A = 50*10e-6*[1; 1; 0.8; 0.8; 1; 1];

julia> T = 1e-3*[5; 0.2; 5; 0.2; 5];

julia> delay, rise, fall = 1e-3, 1e-3, 1e-3;

julia> gr = Grad(A, T, rise, fall, delay)
←1.0 ms→ Grad(∿ mT, 15.4 ms, ↑1.0 ms, ↓1.0 ms)

julia> seq = Sequence([gr])
Sequence[ τ = 10.75 ms | blocks: 1 | ADC: 0 | GR: 1 | RF: 0 | DEF: 0 ]

julia> plot_seq(seq; slider=false)

ADC

The ADC struct is defined in the KomaMRI source code as follows:

mutable struct ADC
    N::Integer
    T::Real
    delay::Real
    Δf::Real
    ϕ::Real
end

As you can see, it has 5 field names: ''N'' defines number of samples, ''T'' defines total acquisition duration, ''delay'' is the distance between the 0 time and the first sampled signal, ''Δf'' and ''ϕ' are factor to correct signal acquisition (for advanced users).

In the image below you can see how to define an ADC event:

Let's look at a basic example of defining an ADC struct and including it in a Sequence struct:

julia> N, T, delay =  16, 5e-3, 1e-3;

julia> adc = ADC(N, T, delay)
ADC(16, 0.005, 0.001, 0.0, 0.0)

julia> seq = Sequence(); seq += adc
Sequence[ τ = 6.0 ms | blocks: 1 | ADC: 1 | GR: 0 | RF: 0 | DEF: 0 ]

julia> plot_seq(seq; slider=false)

Combination of Events

We can include multiple events within a single block of a sequence. The example below demonstrates how to combine an RF struct, three Grad structs for the x-y-z components, and an ADC struct in a single block of a sequence:

# Define an RF struct
A, T =  1e-6*[0; -0.1; 0.2; -0.5; 1; -0.5; 0.2; -0.1; 0], 0.5e-3;
rf = RF(A, T)

# Define a Grad struct for Gx
A, T, rise =  50*10e-6, 5e-3, 1e-3
gx = Grad(A, T, rise)

# Define a Grad struct for Gy
A = 50*10e-6*[0; 0.5; 0.9; 1; 0.9; 0.5; 0; -0.5; -0.9; -1]
T, rise = 5e-3, 2e-3;
gy = Grad(A, T, rise)

# Define a Grad struct for Gz
A = 50*10e-6*[0; 0.5; 0.9; 1; 0.9; 0.5; 0; -0.5; -0.9; -1]
T = 5e-3*[0.0; 0.1; 0.3; 0.2; 0.1; 0.2; 0.3; 0.2; 0.1]
gz = Grad(A, T)

# Define an ADC struct
N, T, delay =  16, 5e-3, 1e-3
adc = ADC(N, T, delay)
julia> seq = Sequence([gx; gy; gz;;], [rf;;], [adc])
Sequence[ τ = 9.0 ms | blocks: 1 | ADC: 1 | GR: 3 | RF: 1 | DEF: 0 ]

julia> plot_seq(seq; slider=false)

Once the struct events are defined, it's important to note that to create a single block sequence, you need to provide 2D matrices of Grad and RF structs, as well as a vector of ADC structs as arguments in the Sequence constructor.

Algebraic manipulation

Certain mathematical operations can be directly applied to events and sequence structs. This proves helpful when constructing sequences using reference structs and manipulating them algebraically to create new structs. Below, we provide a list of operations you can perform, along with examples where we check the equivalence of two different struct definitions:

  • RF scaling
# Define params
A, T = 10e-6, 0.5e-3    # Define base RF params  
α = (1 + im*1)/sqrt(2)  # Define a complex scaling factor

# Create two equivalent RFs in different ways
ra = RF(α * A, T)
rb = α * RF(A, T)
julia> ra ≈ rb
true
  • Gradient scaling
# Define params
A, T = 10e-3, 0.5e-3   # Define base gradient params  
α = 2                  # Define a scaling factor

# Create two equivalent gradients in different ways
ga = Grad(α * A, T)
gb = α * Grad(A, T)
julia> ga ≈ gb
true
  • Gradient addition
# Define params
T = 0.5e-3      # Define common duration of the gradients
A1 = 5e-3       # Define base amplitude for gradient  
A2 = 10e-3      # Define another base amplitude for gradient  

# Create two equivalent gradients in different ways
ga = Grad(A1 + A2, T)
gb = Grad(A1, T) + Grad(A2, T)
julia> ga ≈ gb
true
  • Gradient array multiplication by a matrix
# Define params
T = 0.5e-3                          # Define common duration of the gradients
Ax, Ay, Az = 10e-3, 20e-3, 5e-3     # Define base amplitude for gradients  
gx, gy, gz = Grad(Ax, T), Grad(Ay, T), Grad(Az, T)  # Define gradients
R = [0 1. 0; 0 0 1.; 1. 0 0]        # Define matrix (a rotation matrix in this example)

# Create two equivalent gradient vectors in different ways
ga = [gy; gz; gx]
gb = R * [gx; gy; gz]

# Create two equivalent gradient matrices in different ways
gc = [gy 2*gy; gz 2*gz; gx 2*gx]
gd = R * [gx 2*gx; gy 2*gy; gz 2*gz]
julia> all(ga .≈ gb)
true

julia> all(gc .≈ gd)
true
  • Sequence rotation
# Define params
T = 0.5e-3                          # Define common duration of the gradients
Ax, Ay, Az = 10e-3, 20e-3, 5e-3     # Define base amplitude for gradients  
gx, gy, gz = Grad(Ax, T), Grad(Ay, T), Grad(Az, T)  # Define gradients
R = [0 1. 0; 0 0 1.; 1. 0 0]        # Define matrix (a rotation matrix in this example)

# Create two equivalent sequences in different ways
sa = Sequence(R * [gx; gy; gz;;])
sb = R * Sequence([gx; gy; gz;;])
julia> all(sa.GR .≈ sb.GR)
true