Getting Started¶
Import precon:
import precon as pr
from pathlib import Path
Reading Parameters¶
The Parameter class reads all scan parameters and scanner labels from a Philips raw file.
Parameters are automatically sourced from the embedded ReconFrame patch data, or from a companion .sin file if no patch is present.
pars = pr.Parameter(Path('my_rawfile.raw'))
Access a commonly used parameter directly:
voxel_sizes = pars.get_voxel_sizes(mix=0)
recon_resolution = pars.get_recon_resolution(mix=0, xovs=False, yovs=True, zovs=True)
Retrieve any scanner GOAL parameter by name:
flip_angle = pars.get_value('EX_ACQ_flip_angle', default=[90.0])
get_value always returns a list. If the parameter is absent the default is returned; if no default is given, None is returned.
Access a GOAL object attribute directly:
sq_base = pars.goal.get_object('SQ', 'base')
sq_base_dur = sq_base.get_attribute('dur', cmp=0)
Reading and Sorting Data¶
Use Parameter2Read to specify which data to read. After reading, the data is in temporal order — use sort() to assemble it into a proper k-space.
parameter2read = pr.Parameter2Read(pars.labels)
for mix in parameter2read.mix:
for stack in parameter2read.stack:
parameter2read.stack = stack
parameter2read.mix = mix
with open('my_rawfile.raw', 'rb') as raw:
data, labels = pr.read(raw, parameter2read, pars.labels, pars.coil_info)
cur_recon_resolution = pars.get_recon_resolution(mix=mix, xovs=False, yovs=True, zovs=True)
data, labels = pr.sort(data, labels, output_size=cur_recon_resolution)
The read function always returns data in the shape samples × coils × profiles. sort reorganises it into a 12-dimensional k-space:
(kx, ky, kz, channels, dynamics, cardiac phases, echoes, locations, mixes, extra1, extra2, averages).
Note
read only handles data of a single size and geometry. Always loop over mixes and stacks.
Basic Reconstruction¶
A minimal Cartesian reconstruction after reading and sorting:
# Fourier transform kx, ky, kz → image space
data = pr.k2i(data, axis=(0, 1, 2))
# Sum-of-squares coil combination
data = pr.sos(data, axis=3)
Common Patterns¶
Partial Fourier (Homodyne)¶
kx_range = pars.get_range(enc=0, mix=mix, stack=stack, ovs=False)
ky_range = pars.get_range(enc=1, mix=mix, stack=stack)
kz_range = pars.get_range(enc=2, mix=mix, stack=stack)
if pr.is_partial_fourier(kx_range) or pr.is_partial_fourier(ky_range) or pr.is_partial_fourier(kz_range):
data = pr.homodyne(data, kx_range, ky_range, kz_range)
data = pr.k2i(data, axis=(0, 1, 2))
SENSE Reconstruction¶
sens = pr.calculate_sensitivities(Path(refscan), Path(rawfile), match_target_size=False)
regularization_factor = pars.get_value(pars.SENSE_REGULARIZATION_FACTOR, at=0, default=2)
output_size = pars.get_recon_resolution(mix=mix, xovs=False, yovs=True, zovs=True, folded=False)
data = pr.sense_unfold(data, sens, output_size, regularization_factor=regularization_factor)
Array Compression¶
Array compression reduces the number of coil channels before reconstruction, cutting memory and compute cost:
ref_pars = pr.Parameter(Path(refscan))
qbc, coil = pr.reconstruct_refscan(ref_pars)
sens = pr.reformat_refscan(qbc, coil, ref_pars, pars, match_target_size=True)
A = pr.get_array_compression_matrix(sens.surfacecoil)
virtual_coils = 8
A = A[0:virtual_coils, :]
# Compress the sensitivity maps for SENSE
sens = pr.compress_sensitivity(sens, A)
# Pass A to read() so compression is applied on-the-fly during reading
with open(rawfile, 'rb') as raw:
data, labels = pr.read(raw, parameter2read, pars.labels, pars.coil_info, array_compression=A)
Flow Reconstruction¶
data = pr.divide_flow_segments(data, pars.is_hadamard_encoding())
data = pr.format_flow(data, pars.get_coordinate_system(), pars.get_venc(), pars.is_hadamard_encoding())
data = pr.fit_flow_phase(data, order=3)
Radiological Formatting¶
Transform images into radiological convention as a final step:
transforms = pars.get_in_plane_transformation(mix=0, stack=0)
data = pr.format(data, transforms)
Non-Cartesian Reconstruction¶
See the Reference page for the full API. A radial example:
no_spokes = labels_sorted[0, 0, ...].shape[1] # number of acquired spokes
traj = pr.noncart.get_radial_trajectory(no_samples, no_spokes)
weights = pr.noncart.get_radial_weights(traj)
images = pr.noncart.nufft(traj, data, weights, out_res=[matrix_size, matrix_size, 1])
Further Reading¶
Complete reconstruction examples are available at github.com/GyroTools/precon-examples.
Example datasets can be found at github.com/GyroTools/precon-examples/tree/master/data.