GPI and the BART

Posted by Nicholas Zwart on Tue 16 February 2016

Recently, the BORG has been used to wrap the Berkeley Advanced Reconstruction Toolbox (BART) into a GPI node library for the purpose of allowing GPI users to easily explore and utilize this new compressed sensing library. This GPI-BART library is accompanied by some example networks that demonstrate basic wavelet based compressed sensing as well as some of the advanced joint compressed sensing and parallel imaging techniques that the BART package provides for MR reconstruction. This post gives an introductory overview of how the BART was wrapped in GPI and how the GPI-BART node library can be installed using the latest GPI v1.

The BORG

One of the new features in GPI v1 is the BORG interface which allows node developers to easily assimilate command-line tools for native use in GPI. The BORG, which stands for Building Outside Relationships with GPI, provides a simple wrapper interface that takes care of the file I/O required to communicate with external binaries. In the case of the BART, most of the tools require an input data file to process and subsequently produce a data file as an output. The BORG simply manages the temporary files required for this I/O. Reader and writer functions are required to translate the Python object (in GPI) to the appropriate file format for the command-line tool. In this case the BART comes with a Python library that translates their CFL/HDR file format to Numpy complex float arrays. This can be observed in the following code snippet of a GPI node that wraps the BART's traj command.

# load commandline tools
from bart.gpi.borg import IFilePath, OFilePath, Command
from bart.python.cfl import cfl # BART file format
...
    # grab user input from UI widgets
    x = self.getVal('readout samples')
    y = self.getVal('phase encoding lines')
    a = self.getVal('acceleration')
    r = self.getVal('radial')
    g = self.getVal('golden-ratio sampling')

    # assemble the argument string
    args = [base_path+'/traj']
    args += ['-x '+str(x)]
    args += ['-y '+str(y)]
    args += ['-a '+str(a)]
    args += r*['-r']
    args += g*['-G']
    args += d*['-D']

    # setup temp file for getting data back
    # from the external command
    out = OFilePath(cfl.readcfl, asuffix=['.cfl','.hdr'])
    args += [out]

    # run commandline and echo full command string
    print(Command(*args))

    # set GPI node output
    self.setData('trajectory', out.data())

    return 0

The example above starts with a local import of the BORG tools, which were developed in tandem with this node library. The GPI widgets values are then translated to command-line arguments, which are held as strings in a Python list object. Since the Traj node generates k-space trajectory coordinates, it requires an output file path to write out the trajectory data. The OFilePath object takes a reference to the file format reader function and generates a random temporary filename which is included in the arguments list. When the Command object executes with the argument list, it spawns a traj process and waits for it to finish and then it uses the reader function to convert the file to a Numpy array. The OFilePath object provides a reference to the Numpy via its data() method. Finally, when the OFilePath object falls out of scope it cleans up the temporary file on disk. The random filenames are important since the GPI canvas may contain multiple instances of the Traj node. The BORG interface simplifies this process which can save time when wrapping a large library like the BART.

For more examples, check out the wrapper nodes to the BART and FSL libraries.

Why a Fork?

The GPI-BART library is built off of a fork of the actual BART project. While the GPI wrappers could be considered a separate project, the BART project is currently undergoing a lot of development and the fork effectively ensures that the interface between the GPI wrappers and the BART is compatible. Another advantage is that this library can coexist with other BART installations on the same machine without interfering.

BART Data Conventions

The BART and GPI projects have a few key organizational differences in how they convey numeric arrays and k-space coordinates. In order to pass the data between nodes from each library, the data need to be manipulated to fit the each node's requirements. This is easily accomplished with the core GPI nodes. Lets start with an example of the worst case scenario: the traj node.

fitw75

The image above shows an example gridded 2D radial trajectory (via the BART node library) and a gridded 2D spiral trajectory (via the GPI core library). Starting from the top nodes, you can see that the Traj node produces radial coordinates with dimensions [3, 256, 32] which is 3 cartesian coordinates, 256 sample points and 32 radial arms. The spiral coordinates from SpiralCoords are ordered [32, 256, 2] for 32 spiral arms, 256 sample points and 2 cartesian coordinates.

Step 1: Reduce Coordinates

Since this is a 2D trajectory, the Traj node passes zeros for the 'Z' coordinate and the SpiralCoords node passes 2 coordinates per point. In order to use the radial trajectory with the core Grid node, the 3rd coordinate of the array must be cropped out. This is accomplished with the Reduce node set to B/E where the '(B)eginning' index is 1 and the '(E)nding' index is 2. You can see that after Reduce, the Traj dimensions are [2, 256, 32].

Step 2: Transpose Dimensions

The Transpose node is used to flip the order that the trajectory data is organized in. After the Transpose node, the dimensions are [32, 256, 2], which matches the order of the spiral coordinates.

Step 3: Recast the Data Type

If you hover the mouse cursor over the coordinate port on the Grid node a tooltip will pop up with the data type required by that port. In this case, Grid can take floating point arrays with single or double precision. Hovering over the Traj output port shows that the array is of type complex float. In this case the trajectory definition only makes use of the real component so the imaginary component is set to zero. This can be directly recast into a float array without loss of data.

Step 4: Coordinate Scaling

You have noticed that the first node after Traj is Math. This is because the trajectory coordinate convention used in the BART is from N/2-1 to N/2 where N is the number of points along a dimension of the grid matrix. The GPI core library uses the convention of -0.5 to 0.5. So the coordinates are divided by the target matrix size via element-wise scalar division.

This example has shown how the BART and core GPI nodes can be easily adapted to communicate data between the two libraries using the stock data manipulation nodes in the core library. The conventions for data between these two libraries are different as would be expected between any two libraries that are developed independently. The goal for this example is to show that a cross library data transfer may require some extra attention.

fitw75

Another approach to this problem would be to integrate these differences into the nodes themselves. An example of this can be seen in the image above. The SpiralCoords node is generating a trajectory that is reformatted using the BNI2BART_Traj node (included in the wrapper library). This allows the coordinates to be directly used in the BART's NuFFT node.

GPI-BART Installation

There are two components to the GPI-BART node library installation: the BART compilation and the GPI node library installation. Before starting download or clone the GPI-BART fork:

First (if you haven't already), make a 'gpi' directory in your home directory. If you're not sure why you'd do this, checkout the post Installing Node Libraries.

    $ cd ~/
    $ mkdir ~/gpi
    $ cd ~/gpi

Next, use git to clone the node library project into a directory called 'bart'.

    $ git clone https://github.com/nckz/bart.git bart

BART Compilation

The BART compilation instructions can be found in the README.md in the base directory of the project source code. To summarize, the BART has some third party library dependencies that are specific to each OS (e.g. Mac OSX). For OSX, the Xcode and the gcc compiler must be installed before the make command can be issued. Once you've installed the dependencies, make the BART.

    $ cd ~/gpi/bart
    $ make

This will make the BART command-line executables, which will be accessible from the ~/gpi/bart directory.

Installing Node Libraries

The BART compilation instructions above basically install the BART under a GPI node library directory structure. GPI will look at the ~/gpi/bart directory as if it where a Python library. In our fork, we've included the GPI wrapper nodes under the bart/gpi directory. This fixed directory structure allows the wrapper code to maintain the relative paths for the BART executables. Since the GPI wrappers are pure Python, they don't require compilation. If your GPI installation is setup with the default paths then GPI-BART library is ready to go.

If you have path modifications in your ~/.gpirc file or the nodes are not visible in your library menu, consult the Configuration docs or check out the post on Installing Node Libraries.

Events

This work has been presented at the ISMRM Workshop on Data Sampling & Image Reconstruction in Sedona Arizona this last January and will be presented at the ISMRM Annual Meeting & Exhibition in Singapore this March.

Thanks

We'd like to thanks the BART developers at UC Berkeley for their help in building these nodes.