Pulseq Event Mapping
KomaMRI uses two representations:
Sequence: the runtime representation used by simulation and plotting. It stores repeated block events asRF,Grad, andADCobjects. 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.
The reader has three stages:
parse_pulseq_filereads sections intoPulseqParsedFile. It applies version-specific scales, for exampleγconversion for RF/gradient amplitudes andμs/nsconversion for timing fields.pulseq_sequence_datanormalizes legacy details and returnsPulseqSequenceData. This is still Pulseq-native: blocks contain integer event ids, and event payloads live in libraries.Sequence(data)decodes each library entry once, then expands block ids into a KomaSequence.
RF
| Pulseq RF timing | Koma fields | Koma representation |
|---|---|---|
| no RF event id | RF(0.0, 0.0) | BlockPulseRF |
time_shape_id == 0 | A::Vector, T::Number; delay = pulseq_delay + Δt_rf/2 | UniformlySampledRF |
time_shape_id == -1 | A::Vector, T::Number; half-raster compact timing; delay = pulseq_delay + Δt_rf/2 | UniformlySampledRF |
time_shape_id > 0 | A::Vector, T::Vector = diff(time_shape) * Δt_rf; delay = pulseq_delay + first(time_shape) * Δt_rf | TimeShapedRF |
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 form | Koma fields | Koma representation |
|---|---|---|
| no gradient event id | Grad(0.0, 0.0) | TrapezoidalGrad |
[TRAP] event | A::Number, T::Number, scalar rise/fall | TrapezoidalGrad |
[GRADIENTS], time_shape_id == 0 | A::Vector, T::Number, rise = fall = Δt_gr/2 | UniformlySampledGrad |
[GRADIENTS], time_shape_id == -1 | A::Vector, T::Number, half-raster compact timing, rise = fall = Δt_gr/2 | UniformlySampledGrad |
[GRADIENTS], time_shape_id > 0 | A::Vector, T::Vector = diff(time_shape) * Δt_gr; delay += first(time_shape) * Δt_gr; rise = fall = 0 | TimeShapedGrad |
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 timing | Koma fields |
|---|---|
| no ADC event id | ADC(0, 0.0) |
[ADC] event | N = 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.
The writer has four stages:
By default,
check_hw_limits=truechecks hardware limits before writing. Withoutsys, this uses metadata inseq.DEF; withsys, it usessys.prepare_pulseq_writecopies and quantizes RF, gradient, ADC, and block duration timings to the Pulseq rasters. Withoutsys, raster times come fromseq.DEF; withsys, the scanner rasters are used.By default,
check_timing=truechecks the quantized copy against the Pulseq rasters.collect_pulseq_assetsandemit_pulseqwrite[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 condition | Pulseq 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 condition | Pulseq output |
|---|---|
!is_on(gr) and zero duration | no gradient event id |
TrapezoidalGrad with first == last == 0 | [TRAP] |
TrapezoidalGrad with nonzero edge continuity | converted 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 condition | Pulseq 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.