Sampling support and ordering

Sampling support and ordering#

TL;DR

  • A Cartesian acquisition is specified by its support, the set of phase- and partition-encoding views that are acquired, and its temporal ordering, the repetition, shot and echo at which each acquired view is played. The sampling routines keep the two separate and create neither events nor labels.

  • Encoded view indices, centred coordinates, boolean support masks and ordering indices are distinct kinds of value. Ordering indices trains[s][e] are row numbers into the array the caller passed, not coordinates.

  • The geometric echo-train orderings take centred coordinates, because the k-space centre (n_y // 2, n_z // 2) is a property of the encoding grid. With an even matrix, partial Fourier or an asymmetric undersampled support, the centroid of the acquired views is not the centre.

  • Poisson-disc sampling determines which views are acquired; T2 Shuffling determines when already selected views are acquired along the echo train. Either can be used without the other.

  • Label events are created only by the sequence application, in kernel. evaluate_labels() recovers the labels actually written, per acquisition.

A Cartesian acquisition is specified by two independent choices: its support, the set of phase- and partition-encoding views that are acquired, and its temporal ordering, the repetition, shot and echo at which each acquired view is played. The sampling routines keep the two separate, and neither creates events or labels. A SequenceApp combines them in its scan loop.

acquisition prescription     matrix, acceleration, ACS, partial Fourier, CAIPI
        │
        ▼
acquired support             encoded view indices (y, z), split into
        │                    calibration and imaging views
        ▼
temporal ordering            loop order, or [shot][echo] indices into the views
        │
        ▼
SequenceApp.loop / kernel    one call per repetition
        │
        ├─▶ gradient scaling      phase and partition encodes from (y, z)
        └─▶ Pulseq labels         LIN, PAR, ECO, SEG, IMA, ...

Kinds of value#

The routines exchange values of distinct kinds, and the distinction is part of their interface.

Kind

Example

Produced by

Meaning

A. Encoded view indices

(y, z) with 0 ≤ y < n_y

make_cartesian_axis_sampling(), make_cartesian_plane_sampling()

Zero-based line and partition numbers on the encoding grid, as the LIN and PAR labels count them. The k-space centre is (n_y // 2, n_z // 2).

B. Centred coordinates

(y - n_y // 2, z - n_z // 2)

the caller

Offsets from the k-space centre in encoding steps. The echo-train orderings measure distance and angle in this frame.

C. Boolean support mask

mask[y, z]

make_*_mask

True where the view (y, z) is acquired. No order and no role.

D. Ordering indices

trains[s][e]

make_*_order

Row numbers into the array the caller passed, grouped by shot s and echo e. Not coordinates.

E. EPI relative offsets

(Δky, Δkz) per echo

make_epi_shot_offsets()

Offsets from echo 0 of one shot. The scan loop chooses each shot’s origin.

F. Orientation schedules

angle per shot, or 3D directions

calc_*_angles, calc_projection_shell()

Rotations applied to a non-Cartesian readout, in radians.

G. RF schedules

phase or flip angle per repetition

make_*_schedule

Values applied to the RF (and ADC) events of each repetition.

Centred coordinates are the input of the geometric echo-train orderings because the k-space centre is a property of the encoding grid, not of the acquired set. With an even matrix, partial Fourier or an asymmetric undersampled support, the centroid of the acquired views is not the centre, and an ordering that measured distance from it would acquire the wrong view at the target echo. The conversion is explicit:

calibration, imaging = pp.make_cartesian_plane_sampling(
    (n_y, n_z), (2, 2), (24, 24), partial_fourier=(0.75, 1.0)
)
views = np.array(calibration + imaging)       # A: encoded indices
centred = views - (n_y // 2, n_z // 2)        # B: centred coordinates
trains = pp.make_radial_adaptive_order(       # D: [shot][echo] indices
    centred, etl, center_echo=te_echo
)
view_of = [[tuple(views[i]) for i in train] for train in trains]

The same indices i select the encoded view views[i], from which the kernel scales the phase-encoding gradient and writes the LIN and PAR labels. A boolean mask enters the same way, through np.argwhere(mask).

Poisson-disc support and T2 Shuffling#

Both are associated with variable-density, echo-resolved acquisitions, and they answer different questions.

Poisson-disc sampling

T2 Shuffling

Determines

which views are acquired

when already selected views are acquired along the echo train

Routine

make_cartesian_plane_sampling(..., sampling='poisson'), make_poisson_disc_mask()

make_shuffling_order()

Output

encoded views, or a boolean mask

trains[shot][echo] indices

The shipped fast-spin-echo application combines them under ordering='shuffling'. The MPRAGE application’s ordering='shuffling' pairs the same Poisson-disc support with a random line order within each partition. Either choice can be used without the other.

Labels#

Label events are created only by the sequence application. In each call of kernel it writes LIN and PAR from the encoded view, ECO from the echo index, IMA from membership of the calibration list, and SEG, SLC, SET or REP from its position in the loop. The sampling routines return plain Python and NumPy values; which calibration views are played first, and whether the support is reordered before acquisition, is decided by the application. evaluate_labels() recovers the labels actually written, per acquisition.

See also#