Skip to content

Build Sequences with @addblock

A Koma Sequence is a list of sequence blocks. Each block stores RF events, one gradient per axis, ADC events, a duration, and extensions such as labels, triggers, or rotations.

This page shows the block-oriented style for building pulse programs. It maps closely to MATLAB Pulseq and PyPulseq addBlock / add_block code, but Koma can also append named Sequence parts, such as a readout or prephaser, in one statement.

Use @addblock to append block expressions:

julia
seq = Sequence()  # or Sequence(sys) for scanner export checks
@addblock seq += (rf, z=gz)

After +=, each tuple becomes a Sequence of length one and its events are copied into seq. Each Sequence on the right-hand side contributes all of its blocks. + appends them left-to-right:

julia
@addblock seq += (rf, z=gz) + (x=gx, adc) + (Delay(TR), LabelInc(1, "ECO"))

Gradient Axes

Koma Grad events are axis-neutral. Choose the axis when adding the block with x=, y=, or z=. RF, ADC, and extensions are positional.

julia
@addblock seq += (rf, LabelSet(ky, "LIN"), z=gz)

Named tuples are useful when a block has gradients on several axes:

julia
slice_select = (x=gx_slice, y=gy_slice, z=gz_slice)
rewinder = (x=gx_rewind, y=gy_rewind, z=gz_rewind)

excitation = Sequence()
@addblock excitation += (rf; slice_select...) + (; rewinder...)

Block Duration

Use Delay(T) for a minimum block duration. Use Duration(T) for an exact block duration; it errors if any RF, gradient, or ADC event is longer than T.

julia
@addblock seq += (rf, Delay(TR), z=gz)     # at least TR
@addblock seq += (rf, Duration(TR), z=gz)  # exactly TR, or errors

Delay and Duration are construction helpers: they update block DUR; they are not stored as RF, gradient, ADC, or extension events.

Scanner and Raster Times

Use Sequence(sys) when export checks should use a scanner. write_seq checks raster timing and event-local hardware limits from seq.DEF, or from sys when passed. Plain Sequence() uses Pulseq file rasters and non-limiting hardware limits.

julia
sys = Scanner(Gmax=40e-3, Smax=150)
seq = Sequence(sys)
@addblock seq += (rf, z=gz)
write_seq(seq, "sequence.seq")  # checks raster and hw limits in seq.DEF

Add Blocks in Loops

Use @addblocks around loops that append to a sequence. Inside the macro, seq += ... appends in place.

julia
@addblocks for ky in 1:Ny
    seq += (rf, z=gz)
    seq += (x=gx, y=phase_blip(ky), adc)
end

Do not use plain seq += readout in long loops. In Julia it means seq = seq + readout; Koma's + returns a copied sequence so reused events do not share mutable events. That is safe, but can be more than 800x slower in long loops.

Reuse Named Sequences

Name a small part of the pulse program as a normal Sequence, then append it. For example, readout below is a one-block Sequence, not a new event type.

julia
readout = Sequence()
@addblock readout += (ADC(num_readout_samples, adc_duration, ζ), x=Grad(G_readout, T_readout, ζ))

prephaser = Sequence()
@addblock prephaser += (x=Grad(Gx_prephaser, T_prephaser, ζ_prephaser), y=Grad(Gy_prephaser, T_prephaser, ζ_prephaser))

@addblock seq += prephaser + readout

Append named sequences in loops with @addblocks:

julia
@addblocks for ky in 1:Ny
    seq += rf_preparation + readout(ky)
end

Transform Sequences

Koma defines arithmetic on Sequence values. Each operation returns a copy, so the original sequence can be reused. Inside @addblock, left-multiplying a block tuple applies the same operation to that one-block sequence.

Scale Gradients

Use real scalars to scale gradient amplitudes.

julia
@addblock seq += 0.5 * (x=gx, adc)

Rotate Gradients

real_matrix * sequence mixes gradient axes and leaves RF and ADC unchanged.

julia
@addblock seq += rotz(π / 6) * (x=gx, adc)

Phase RF and ADC

complex_scalar * sequence phase-shifts RF and ADC. Gradients are unchanged. Use cis(ϕ) for exp(im * ϕ).

julia
phase = cis(π / 2)  # exp(im * π / 2)
@addblock seq += phase * (rf, z=gz) + phase * (x=gx, adc)

Build Radial Spokes

Rotate the readout block for each spoke:

julia
@addblocks for spoke in 0:Nspokes-1
    θ = π * spoke / Nspokes
    seq += (rf, z=gz) + rotz(θ) * (x=gx, adc)
end

Append Semantics

Use this section when you need to reason about copies or performance. Tuple terms become one new block through addblock!. Existing Sequence terms append all of their blocks through append!.

julia
@addblock seq += (rf, z=gz) + (x=gx, adc)

@addblocks applies the same rewrite to += expressions in its scope when the left-hand side is a Sequence.