Skip to content

Triggered Cardiac bSSFP with Arrhythmia

julia script jupyter notebook launch binder

In this tutorial, you will acquire one 32×32 mid-diastolic bSSFP image using 8 phase-encoding lines per heartbeat. You will then simulate an arrhythmia while the sequence continues to trigger every second and observe the resulting artifact.

Correct triggering with a regular cardiac rhythm

We begin with a myocardial phantom whose contraction and rotation repeat every second. It contracts during systole, relaxes during diastole, and pauses around the mid-diastolic acquisition window.

julia
obj = cardiac_phantom(; RR_intervals=1.0)
trigger_delay = 0.72;

The resulting contraction and rotation have a one-second period:

Each heartbeat starts with a flip-angle ramp. The first of 8 phase-encoding lines is acquired 720 ms after the R peak; the remaining lines follow every TR within the same mid-diastolic window.

julia
N_matrix           = 32          # image size = N x N
lines_per_heartbeat = 8
N_ramp_cycles       = 16          # flip-angle ramp length
FOV                 = 0.11        # [m]
TR                  = 2.96e-3     # [s]
flip_angle          = 40          # [º]
adc_duration        = 0.24e-3;    # [s]

seq = triggered_bSSFP(
    FOV, N_matrix, TR, flip_angle, sys;
    N_ramp_cycles, lines_per_heartbeat, trigger_delay, adc_duration,
);

A heart rate of 1 means 1 Hz, giving one R peak per second:

julia
regular_cardiac_rythm = CardiacSignal(; heart_rate=1);

Passing the signal to plot_seq resolves the trigger waits and adds the ECG trace. The complete sequence is plotted together with the cardiac signal:

julia
pseq = plot_seq(
    seq;
    physio=regular_cardiac_rythm,
    height=450,
);

Simulate the four triggered heartbeat segments:

julia
raw_regular = simulate(
    obj, seq, sys;
    sim_params,
    physio=regular_cardiac_rythm,
    verbose=false,
);

The segments combine into one clean mid-diastolic image:

Sequence triggered incorrectly during arrhythmia

Now make the phantom arrhythmic while keeping the trigger signal at 1 Hz:

julia
arrhythmic_intervals = [1.2, 1.2, 0.8, 0.8]
obj_arrhythmic = cardiac_phantom(; RR_intervals=arrhythmic_intervals);

The varying cycle lengths are visible in the motion plot:

The regular trigger now samples the segments at different cardiac phases:

julia
raw_mismatched = simulate(
    obj_arrhythmic, seq, sys;
    sim_params,
    physio=regular_cardiac_rythm, # triggered incorrectly every second
    verbose=false,
);

The phase-encoding segments no longer describe the same cardiac state:

The phantom's cardiac rhythm and CardiacSignal are independent inputs: changing the motion does not alter the trigger schedule.


This page was generated using Literate.jl.