Raw-data enrichment and routing#
TL;DR
The proxy replaces the encoding counters, flags, encoding spaces and trajectory a client records with those the sequence states, before any reconstruction code reads the stream.
The field-of-view offset is played as frequency and phase offsets; where the readout gradient varies across the sampling window, the proxy applies the remaining phase.
A reconstruction unit is the readouts of one branch and encoding space that share their image-selecting counters; it closes on the flag its branch declares, and its images carry the values the plugin returned.
The scanner’s reconstruction client streams each series as MRD: a configuration text, the XML header and the acquisitions. The encoding counters, flags, encoding spaces and trajectory a vendor client records describe the interpreter’s playout loop, not the Pulseq sequence: the counters of a non-Cartesian or segmented acquisition, the navigator readouts and the k-space location of each sample are defined by the sequence. The reconstruction proxy therefore replaces them with the values the sequence states, before any reconstruction code reads the stream.
The prescribed field-of-view offset is applied to the sequence when its IR is built (Scanner representation (PulSeg)), as the frequency and phase offsets the scanner plays at the middle of each sampling window. Under a readout gradient that holds one value across the window, these are the whole phase of the offset. Under one that does not, as on a ramp-sampled or non-Cartesian readout, the phase is not linear in time, and the proxy applies the remainder,
for the offset \(\mathbf{c}\) along the logical axes and the window centre \(t_c\), together with any phase modulation the sequence’s ADC events store, which the IR cache does not carry. The samples are otherwise left as received, and the trajectory the proxy attaches is that of the sequence as designed, in the logical frame.
Enrichment#
The header names the design the series was acquired with
(Designs and the design store). The proxy reads that design’s sequence chain and tabulates
its readouts in play order (SequenceTable). The table
is applied to the stream as follows.
The header receives one encoding space for the imaging readouts of each subsequence, and one for its navigator readouts when it has any. The reconstruction space of each carries the matrix size and field of view the sequence defines. The encoded space of a Cartesian space carries them with the readout widened to the full echo: its matrix size along the readout is the number of samples of the echo that is symmetric about the readouts’
center_sample, readout oversampling included, and its field of view along the readout is the reconstructed one times the ratio of the two matrix sizes. The ratio of the encoded to the reconstructed field of view along the readout is the readout oversampling. A space with a trajectory keeps the matrix size and field of view the sequence defines.The encoding limits of each space are the minimum and maximum of the counters its readouts reach. The
centerofkspace_encoding_step_1andkspace_encoding_step_2is the counter of the k-space centre the sequence defines (kSpaceCenterLine,kSpaceCenterPartition); where it defines none, and for every other counter, the centre is the minimum plus half the number of positions between the minimum and the maximum, rounded down.The header’s sequence parameters are the TR, TE, TI and flip angles the sequence defines. The TR and TE it does not define are measured by pypulseqpp’s
Sequence.test_report_dict: TE from the excitation before the closest approach to the k-space centre, and TR between the excitations around it. The flip angles it does not define are the distinct values ofSequence.rf_flip_angles.Each acquisition is matched to a table row by its position in the stream and receives the encoding counters, the MRD flags, the dwell time and the encoding space reference, and the k-space trajectory when the k-space location changes across the readout, except on a Cartesian readout sampled on the flat top of its readout gradient alone. When the client numbers its acquisitions, each
scan_countermust follow the previous one by one; a gap or a repeat stops the series before it is reconstructed, since every later row would be shifted.
The flags are those the sequence’s labels set, such as IS_NAVIGATION_DATA,
and the first-and-last flags (FIRST_IN_SLICE, LAST_IN_SLICE and the
others), derived from the counters. A reconstruction plugin selects the data a
reconstruction runs on by these flags.
Trajectory#
The k-space location of each sample is the integral of the sequence’s
gradients, with block rotations applied, in 1/m. The table does not hold it for
the whole scan: it is integrated a run of consecutive readouts at a time, by
pypulseqpp’s Sequence.adc_kspace, from the last excitation before the run.
An excitation resets k to zero at the pulse centre, so the samples that follow
it do not depend on the gradients played before it, and a run integrated from
there gives the k-space locations of the scan integrated from its first block.
A refocusing pulse reverses k rather than resetting it and does not begin an
integration, nor does an excitation in a block that also holds a readout.
An acquisition carries as its trajectory every axis its encoding space varies
along, up to the last, whether or not its own k moves along it: the line of a
PROPELLER blade that is played unrotated keeps its phase encoding in ky. The
partitions of a stack of spokes or spirals are Cartesian along z and placed by
their kspace_encode_step_2 counter, so their kz is not part of the
trajectory. A reconstruction takes the trajectory in grid units, k times the
reconstructed field of view, through
grid_trajectory().
A Cartesian readout carries a trajectory only when it samples the ramps of its
readout gradient. Sampled on the flat top alone, its samples are equally
spaced in k and placed by the encoding counters and center_sample, and an
encoding space whose readouts are all of that kind carries no trajectory.
Tabulating a design integrates every range once, for the echo sample of each readout and the k-space axes its trajectory spans, which decide the header’s trajectory type. An acquisition’s trajectory is integrated again when it is enriched, from the range holding it, and the table keeps the ranges it integrated last. The k-space locations the proxy holds therefore do not grow with the length of the scan.
Reconstruction units#
A reconstruction takes the readouts of one image, or of several images
reconstructed together, such as the echoes of a slice. The stream states where
those readouts end only through the encoding counters and flags that
enrichment supplies. A reconstruction unit is the set of readouts of one
branch and one encoding space that share their slice, contrast, cardiac phase,
repetition, set and average counters; a branch is a kind of readout a plugin
reconstructs separately, such as imaging or navigator. The segment and
user counters name positions within a readout train and do not separate units.
A plugin names in its axes the counters it takes as axes of the unit’s
k-space instead, such as the echoes of a multi-echo acquisition, and these do
not separate units either.
A unit allocates its imaging k-space when its first imaging readout arrives,
from the encoding space the header gives it, and places each readout as
described under Placement. The flags of a readout select
its buffer as Gadgetron’s acquisition bucket divides readouts: parallel-imaging
calibration readouts are placed in ref, readouts flagged as calibration and
imaging in ref and data, phase-correction readouts in neither, and any
other readout in data. The calibration k-space is allocated when the unit
closes, over the lines its readouts cover.
A unit closes when the flag its branch declares has arrived at every position
along its axes. Enrichment sets the last-in flag of a counter within each
combination of the encoding space and the other image-selecting counters, so
LAST_IN_SLICE arrives once per echo or average of a slice, and a plugin with
those counters as axes waits for all of them. Readouts of different units may
be interleaved: slices acquired line by line each close at their own flagged
readout.
A closed unit leaves the plugin before its reconstruction runs, and nothing in the runtime holds it afterwards, so the memory a series holds is that of the units open at once, not that of the series. A unit still open at the last readout of the measurement or at the end of the stream is reconstructed then, under its own branch, in the order the units opened.
Images#
A ReconResult becomes an MRD image whose header the
runtime builds from the unit that produced it. The values of the image are those
of the array the plugin returned.
Header#
The field of view is that of the reconstruction space of the unit’s encoding
space, (x, y, z) in mm, so the images of a unit in the second encoding space
carry the second space’s field of view. The matrix is the shape of the returned
array, which is image_shape, the
reconstruction matrix of the unit’s space, for a plugin that uses it.
The position, the orientation (read_dir, phase_dir, slice_dir), the
patient table position, the physiology time stamps and the user fields are those
of the unit’s reference acquisition, the imaging acquisition placed nearest the
k-space centre, as Gadgetron selects it for an image header. The centre lies at
extent // 2 along the phase-encoding axis and, where the encoded space has more
than one partition, the partition axis, as described under
Placement; nearness is the Euclidean distance from it in
grid positions. Of acquisitions at the same distance, the first placed is the
reference. The acquisition time stamp of the image is the earliest of the unit’s
imaging acquisitions.
A plugin reads the reference acquisition as
reference, and takes the geometry from
another acquisition by passing it as ReconResult.reference.
Values#
The MRD image holds the values the plugin returned: float32 for real
floating-point data, complex64 for complex data, wider types being converted
to these, and integer data in its own type. No scale or normalisation is
applied, so the ratios between the images of a series, such as the decay across
the echoes of a multi-echo acquisition or the ratios between frames, are those
of the reconstruction. The units are the reconstruction’s own.
A (z, y, x) result becomes one image per partition. Each image states the
smallest and largest finite value of the result, of its magnitude where it is
complex, in the meta attributes ArrayMinimum and ArrayMaximum.
Reconstruction server#
A proxy given a server to forward to reconstructs no series itself. It
enriches each series as it arrives and sends it on over TCP to that MRD server:
a reconstruction server (ReconServer) on another
computer, or any server that reads the MRD streaming protocol. The server
receives a config file message naming the reconstruction plugin of the series,
or a name the proxy is configured with, then the enriched header and
acquisitions. What it returns is relayed to the client as a worker’s output
is, and a reconstruction timeout closes the connection to it. With DICOM
conversion enabled, the proxy converts each image the server returns to DICOM
from the enriched header before relaying it.
A reconstruction server runs each series it receives with the plugin its config names, in workers, slots and a queue of its own, and enriches nothing: the series a proxy forwards arrive enriched.
An MRD message carries no length by which a reader can skip a message type it does not know. A message the proxy has no reader for therefore ends what it relays, and the client receives a text naming the message’s type.
See also#
Reconstruction plugins — writing a reconstruction.
Reconstruction clients — the MRD stream a reconstruction client sends.
Calibration, noise and coil compression — coil sensitivity maps, prewhitening and coil compression.
Reconstruction proxy — the proxy, the reconstruction server and the enrichment interface.
Reconstruction plugins — the reconstruction plugin interface.
Field-of-view offset and the proxy phase — enrichment and reconstruction of a simulated series.