Equivalent of fslswapdim for left-right flipping fsLR 32k surface data?

Hi everyone,

For my volumetric analysis, I compared LH and RH movement by left-right flipping the RH runs after fMRIPrep preprocessing (registered to MNI152NLin2009cSym) using fslswapdim. I did this to analyze my data in an ipsilateral/contralateral framework, where the left hemisphere was always ipsilateral to the moving hand and the right hemisphere was always contralateral.

I’m now trying to do the same thing with fMRIPrep-generated fsLR 32k surface data files, but I’m not sure what the equivalent approach is.

Is there a recommended way to left-right flip RH fsLR data? Does Connectome Workbench have a command for this, or is the better approach to flip the raw RH BOLD images first, rerun fMRIPrep to generate fsLR files, and then analyze the resulting fsLR outputs?

My goal is simply to reproduce the same ipsilateral/contralateral analysis that I used in the volumetric data, but on the cortical surface.

Thanks!

fsLR is supposed to be symmetric, i.e., the same vertex in the left hemisphere is the same as in the right. Assuming you’re using CIFTI data, the main thing you need to care about is flipping the volumetric grayordinates.

You can decompose a CIFTI file in Python (from this notebook):

def volume_from_cifti(data, axis):
    assert isinstance(axis, nb.cifti2.BrainModelAxis)
    data = data.T[axis.volume_mask]                          # Assume brainmodels axis is last, move it to front
    volmask = axis.volume_mask                               # Which indices on this axis are for voxels?
    vox_indices = tuple(axis.voxel[axis.volume_mask].T)      # ([x0, x1, ...], [y0, ...], [z0, ...])
    vol_data = np.zeros(axis.volume_shape + data.shape[1:],  # Volume + any extra dimensions
                        dtype=data.dtype)
    vol_data[vox_indices] = data                             # "Fancy indexing"
    return nb.Nifti1Image(vol_data, axis.affine)             # Add affine for spatial interpretation

def surf_data_from_cifti(data, axis, surf_name):
    assert isinstance(axis, nb.cifti2.BrainModelAxis)
    for name, data_indices, model in axis.iter_structures():  # Iterates over volumetric and surface structures
        if name == surf_name:                                 # Just looking for a surface
            data = data.T[data_indices]                       # Assume brainmodels axis is last, move it to front
            vtx_indices = model.vertex                        # Generally 1-N, except medial wall vertices
            surf_data = np.zeros((vtx_indices.max() + 1,) + data.shape[1:], dtype=data.dtype)
            surf_data[vtx_indices] = data
            return surf_data
    raise ValueError(f"No structure named {surf_name}")

def decompose_cifti(img):
    data = img.get_fdata(dtype=np.float32)
    brain_models = img.header.get_axis(1)  # Assume we know this
    return (volume_from_cifti(data, brain_models),
            surf_data_from_cifti(data, brain_models, "CIFTI_STRUCTURE_CORTEX_LEFT"),
            surf_data_from_cifti(data, brain_models, "CIFTI_STRUCTURE_CORTEX_RIGHT"))

cifti = nb.load(...)
vol, left, right = decompose_cifti(cifti)

To flip the volume:

# It gets more complicated if the R/L axis can't be assumed to be first
assert nb.aff2axcodes(vol.affine) in ('R', 'L')
# Reverse first axis
flipped_vol = vol.slicer[::-1, ...]

Now you can get a difference like:

surf_diff = left - right
vol_diff = vol - flipped_vol

Or do whatever statistical test pleases you.