Linear operators#
bartorch.linop. An operator maps between two C-order shapes. They compose
and combine into a single BART operator through the algebra below – every one
of those is a BART constructor applied to BART operators, so what a solver
drives is one operator in BART’s own loop, with no arithmetic in Python
between the steps. Applying an operator to a tensor that requires a gradient
records it for autograd.
MRI encoding#
These are what most reconstructions here are: an encoding, and a solver driving it. They come first because they are what the rest of the page is for.
Coils, a Fourier transform, and the samples that were taken. |
|
Sensitivities followed by a NUFFT, applied |
|
Wave-CAIPI encoding, as BART's |
|
Coil sensitivities, without the transform that usually follows them. |
|
An encoding with off-resonance during the readout, by time segmentation. |
|
Multiplication by a sampling pattern, broadcast over the axes where it is one. |
NoncartesianSense is BART’s own operator – the coil batching and the
Toeplitz normal are what make it what it is. CartesianSense is that same
operator over BART’s FFT rather than a NUFFT, with Sampling chained on;
WaveSense is a composition of operators BART already has, chained by
linop_chain, and is the same six in the same order that src/wave.c chains.
Each is a single BART operator once built.
Coils is the sensitivity multiply on its own, with the same slab loop and
the same kernels, for an encoding whose transform is not a Fourier transform
and so cannot be a SENSE operator. It is what carries the coils through
WaveSense, which is why sensitivities held as the k-space kernels nlinv
produces work there too.
All of them take several sets of maps – ESPIRiT’s second, ENLIVE’s relaxed
model – as (sets, coils, *spatial), which is what tools.ecalib and
tools.nlinv return for maps > 1 and needs no reshaping in between. The
image then carries the sets and the samples do not: the encoding is
\(y_c = \sum_m S_{m,c} x_m\), contracted in BART’s own md_ztenmul rather than
by anything in Python.
CartesianSense and WaveSense read a temporal subspace when given a
basis – T2 shuffling and Wave-Shuffling, the forward pics -B builds.
With toeplitz=True the normal collapses the frames into one
coefficient-by-coefficient kernel, so an iteration never makes them: on
sixty-four echoes over four coefficients that is sixteen times less k-space in
the middle of every step. Nothing is convolved and no grid is doubled, since
the pattern already lies on the grid the transform is circular over.
FieldCorrected wraps any of them and is a sum of chains rather than one:
off-resonance during the readout is a different transform per sample, and time
segmentation stands in for it with a short sum of ordinary encodings. The
coefficients come from mri-nufft; everything applied is BART’s.
Linear operator class#
An operator is combined with others through the algebra its class defines,
rather than by naming a combining class: A @ B composes, A + B and
A - B add, c * A scales, A ** n repeats, A.H and A.T transpose,
A.gram() and A.cogram() are the normal operators, and A[key] restricts
its output the way indexing a tensor does. What each returns is private,
because the algebra promises the operator, not the shape of the tree it is
made of.
A linear map between two C-order shapes, with an adjoint. |
Elementary operators#
The identity on |
|
The operator that sends everything to zero, BART's |
|
Pointwise multiplication by |
|
A diagonal on the real part and another on the imaginary part. |
|
Complex conjugation, BART's |
|
The real part, BART's |
|
BART's unitary Fourier transform along |
|
Non-uniform FFT from coil images to samples along a trajectory. |
|
Multiply by a tensor and sum over the axes absent from the codomain. |
Filtering and differencing#
Combining operators#
The only functions here. Everything else BART offers maps a tensor to a tensor and is an operator in its own right, which makes it a class; these take operators and give back an operator, which is the one thing that cannot be written as arithmetic or as indexing. Each is one BART operator, so what a solver drives is a single operator rather than a list walked per iteration.
Apply every operator to the same input and lay the results end to end. |
|
Apply every operator to the same input and put the results on a new axis. |
|
Split the input between the operators and add up what they return. |
|
Give each operator its own part of the input and its own part of the output. |
|
A grid of operators, as a block matrix is. |
Shape#
Rearranging, reducing and restricting are operators like any other: each maps
a tensor to a tensor, so each is a class, composes with @ and +, and is
solved by bartorch.optim. Only something that takes operators and
gives back an operator would be a function here.
Read the same elements under another shape, BART's |
|
Swap two axes, BART's |
|
Rearrange the axes, BART's |
|
Reverse |
|
Shift along one axis, BART's |
|
Make each axis longer, BART's |
|
Crop or zero-fill about the centre of each axis, BART's |
|
Take the block of |
|
A sliding window along |
|
Sum over |
|
Sum over |
|
Average over |
|
Repeat along |
Python-defined operators#
A linear operator from Python functions, applied through BART. |