Skip to content

Pulseq Event Mapping

KomaMRI uses two representations:

  • Sequence: the runtime representation used by simulation and plotting. It stores repeated block events as RF, Grad, and ADC objects. See Sequence for the sequence model.

  • PulseqSequenceData: the Pulseq-native intermediate representation used by the reader and writer. It stores [BLOCKS] rows, event libraries, definitions, the Pulseq version, and the optional signature.

API references: read_seq, read_seq_data, write_seq, write_seq_data, and PulseqSequenceData.

Reader

read_seq first reads the file into PulseqSequenceData, then materializes a Koma Sequence. Use read_seq_data when you want the Pulseq block/event-library representation without expanding every block into Koma events.

.seq filePulseq sectionsread_seq_dataparse sections + scaleslegacy fixups + optional signaturePulseqSequenceDataids + event librariesSequencedecode onceexpand blocksread_seq_data stops at the Pulseq-native representation; read_seq continues through Sequence(data).

The reader has three stages:

  1. parse_pulseq_file reads sections into PulseqParsedFile. It applies version-specific scales, for example γ conversion for RF/gradient amplitudes and μs/ns conversion for timing fields.

  2. pulseq_sequence_data normalizes legacy details and returns PulseqSequenceData. This is still Pulseq-native: blocks contain integer event ids, and event payloads live in libraries.

  3. Sequence(data) decodes each library entry once, then expands block ids into a Koma Sequence.

RF

Pulseq RF timingKoma fieldsKoma representation
no RF event idRF(0.0, 0.0)BlockPulseRF
time_shape_id == 0A::Vector, T::Number; delay = pulseq_delay + Δt_rf/2UniformlySampledRF
time_shape_id == -1A::Vector, T::Number; half-raster compact timing; delay = pulseq_delay + Δt_rf/2UniformlySampledRF
time_shape_id > 0A::Vector, T::Vector = diff(time_shape) * Δt_rf; delay = pulseq_delay + first(time_shape) * Δt_rfTimeShapedRF

Pulseq RF events store magnitude and phase as shapes. Therefore, even a constant RF pulse in a Pulseq file is read as a sampled RF waveform, not as a scalar BlockPulseRF. BlockPulseRF appears for empty RF placeholders and Koma-native scalar RF construction.

Compact RF timing uses Pulseq's center-sampled convention: Pulseq stores pulseq_delay before the implicit first-sample offset, while Koma stores delay to the first RF sample. For compact RF timing, these differ by Δt_rf/2.

Gradients

Pulseq gradient formKoma fieldsKoma representation
no gradient event idGrad(0.0, 0.0)TrapezoidalGrad
[TRAP] eventA::Number, T::Number, scalar rise/fallTrapezoidalGrad
[GRADIENTS], time_shape_id == 0A::Vector, T::Number, rise = fall = Δt_gr/2UniformlySampledGrad
[GRADIENTS], time_shape_id == -1A::Vector, T::Number, half-raster compact timing, rise = fall = Δt_gr/2UniformlySampledGrad
[GRADIENTS], time_shape_id > 0A::Vector, T::Vector = diff(time_shape) * Δt_gr; delay += first(time_shape) * Δt_gr; rise = fall = 0TimeShapedGrad

For Pulseq versions before 1.5, the reader also reconstructs missing arbitrary gradient first/last values from neighboring blocks before materializing the Koma sequence.

ADC

ADC events do not have shape-based representations. The timing convention is the only translation: Koma stores delay to the first acquired sample, while Pulseq stores pulseq_delay to the dwell interval edge.

Pulseq ADC timingKoma fields
no ADC event idADC(0, 0.0)
[ADC] eventN = num, T = dwell * (num - 1), delay = pulseq_delay + dwell/2

On read, T is the time from the first sample to the last sample. A single sample therefore reads as T = 0.

Writer

write_seq converts a Koma Sequence into PulseqSequenceData and then emits Pulseq sections. Use write_seq_data to get that Pulseq-native representation without writing a file. write_seq(data::PulseqSequenceData, filename) writes an already prepared representation directly.

Sequenceruntime eventswrite_seq_datahardware checksraster + quantizetiming check + deduplicatePulseqSequenceDataids + event libraries.seq filesections + signaturewrite_seq(seq; sys) uses sys for checks and rasters; write_seq(data, filename) skips Koma rasterization.

The writer has four stages:

  1. By default, check_hw_limits=true checks hardware limits before writing. Without sys, this uses metadata in seq.DEF; with sys, it uses sys.

  2. prepare_pulseq_write copies and quantizes RF, gradient, ADC, and block duration timings to the Pulseq rasters. Without sys, raster times come from seq.DEF; with sys, the scanner rasters are used.

  3. By default, check_timing=true checks the quantized copy against the Pulseq rasters.

  4. collect_pulseq_assets and emit_pulseq write [DEFINITIONS], [BLOCKS], event libraries, shape libraries, extensions, and the signature.

Hardware-limit definitions such as MaxGrad, MaxSlew, MaxB1, and dead times are Koma check metadata. They are not emitted to the Pulseq [DEFINITIONS] section; the writer emits Pulseq raster definitions and non-internal user definitions.

RF

Koma RF conditionPulseq output
!is_on(rf)no RF event id
BlockPulseRF[RF] event with constant magnitude and phase shapes
UniformlySampledRF with sample times 0, Δt_rf, 2Δt_rf, ... and compatible delay[RF] with time_shape_id = 0; pulseq_delay = rf.delay - Δt_rf/2
UniformlySampledRF with half-raster sample times and odd sample count[RF] with time_shape_id = -1; pulseq_delay = rf.delay - Δt_rf/2
TimeShapedRF[RF] with an explicit time shape
otherwise[RF] with an explicit time shape

TimeShapedRF is always written with an explicit time shape. This preserves vector timing and avoids replacing it with Pulseq's compact RF convention, where the first RF sample is implicitly offset by Δt_rf/2.

Frequency-modulated RF waveforms are not written to Pulseq: FrequencyModulatedRF throws an error in the writer.

Gradients

Koma gradient conditionPulseq output
!is_on(gr) and zero durationno gradient event id
TrapezoidalGrad with first == last == 0[TRAP]
TrapezoidalGrad with nonzero edge continuityconverted to an edge-timed arbitrary gradient, then [GRADIENTS]
UniformlySampledGrad with two equal samples and first == last == 0[TRAP]
TimeShapedGrad with two equal samples, one interval, and first == last == 0[TRAP]
arbitrary gradient sample times match (i - 1/2)Δt_gr[GRADIENTS] with time_shape_id = 0
arbitrary gradient sample times match i * Δt_gr/2 with odd sample count[GRADIENTS] with time_shape_id = -1
otherwise[GRADIENTS] with an explicit time shape

Unlike RF, a TimeShapedGrad may still be compacted if its sample times exactly match a Pulseq compact gradient timing convention. Gradient compact timing does not carry the same RF first-sample convention, so this is a representation optimization.

ADC

Koma ADC conditionPulseq output
!is_on(adc)no ADC event id
N == 1[ADC] with dwell = T, pulseq_delay = delay - dwell/2
N > 1[ADC] with dwell = T / (N - 1), pulseq_delay = delay - dwell/2

Pulseq ADC timing follows an edge-versus-sample convention: the Pulseq delay points to the dwell interval edge, and the first sample is acquired at dwell/2. Koma stores delay at the first acquired sample, so the writer subtracts dwell/2 and the reader adds it back.

For N == 1, the writer uses T as the dwell time because there is no inter-sample interval from which to infer it.