Skip to content
Snippets Groups Projects
Forked from FSL / fslpy
1186 commits behind the upstream repository.
fsl_convert_x5.py 6.25 KiB
#!/usr/bin/env python
#
# fsl_convert_x5.py - Convert between FSL and X5 transformation files.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This script can be used to convert between FSL and X5 linear and non-linear
transformation file formats.
"""


import os.path   as op
import functools as ft
import              sys
import              shutil
import              logging
import              argparse

import fsl.data.image       as fslimage
import fsl.utils.parse_data as parse_data
import fsl.transform.flirt  as flirt
import fsl.transform.fnirt  as fnirt
import fsl.transform.x5     as x5


log = logging.getLogger(__name__)


def parseArgs(args):
    """Create an argument parser and parse the given ``args``.

    :arg args: Sequence of commane line arguments.
    :return:   An ``argparse.Namespace`` object containing parsed arguments.
    """

    helps = {
        'input'         : 'Input file',
        'output'        : 'Output file',
        'source'        : 'Source image',
        'reference'     : 'Reference image',
        'input_format'  : 'Input format - if not provided, the input format '
                          'is automatically inferred from the input file '
                          'name.',
        'output_format' : 'Output format - if not provided, the output format '
                          'is automatically inferred from the output file '
                          'name.',
    }

    parser     = argparse.ArgumentParser('fsl_convert_x5')
    subparsers = parser.add_subparsers(dest='ctype')
    flirt      = subparsers.add_parser('flirt')
    fnirt      = subparsers.add_parser('fnirt')
    imgtype    = ft.partial(parse_data.Image, loadData=False)

    flirt.add_argument('input',                  help=helps['input'])
    flirt.add_argument('output',                 help=helps['output'])
    flirt.add_argument('-s',  '--source',        help=helps['source'],
                       type=imgtype)
    flirt.add_argument('-r',  '--reference',     help=helps['reference'],
                       type=imgtype)
    flirt.add_argument('-if', '--input_format',  help=helps['input_format'],
                       choices=('x5', 'mat'))
    flirt.add_argument('-of', '--output_format', help=helps['output_format'],
                       choices=('x5', 'mat'))

    fnirt  .add_argument('input',                  help=helps['input'])
    fnirt  .add_argument('output',                 help=helps['output'])
    fnirt  .add_argument('-s',  '--source',        help=helps['source'],
                         type=imgtype)
    fnirt  .add_argument('-r',  '--reference',     help=helps['reference'],
                         type=imgtype)
    fnirt  .add_argument('-if', '--input_format',  help=helps['input_format'],
                         choices=('x5', 'nii'))
    fnirt  .add_argument('-of', '--output_format', help=helps['output_format'],
                         choices=('x5', 'nii'))

    args = parser.parse_args(args)

    if args.ctype is None:
        parser.print_help()
        sys.exit(0)

    # If input/output formats were not
    # specified, infer them from the
    # file names
    def getfmt(arg, fname, exist):
        ext = op.splitext(fname)[1]
        if ext in ('.mat', '.x5'):
            return ext[1:]

        fname = fslimage.addExt(fname, mustExist=exist)

        if fslimage.looksLikeImage(fname):
            return 'nii'
        parser.error('Could not infer format from '
                     'filename: {}'.format(args.input))

    if args.input_format  is None:
        args.input_format  = getfmt('input',  args.input, True)
    if args.output_format is None:
        args.output_format = getfmt('output', args.output, False)

    # The source and reference arguments are
    # required if the input is a FLIRT matrix
    # or a FNIRT displacement/coefficient field.
    if args.input_format in ('mat', 'nii') and \
       (args.source is None or args.reference is None):
        parser.error('You must specify a source and reference '
                     'when the input is not an X5 file!')

    return args


def flirtToX5(args):
    """Convert a linear FLIRT transformation matrix to an X5 transformation
    file.
    """
    src   = args.source
    ref   = args.reference
    xform = flirt.readFlirt(args.input)
    xform = flirt.fromFlirt(xform, src, ref, 'world', 'world')
    x5.writeLinearX5(args.output, xform, src, ref)


def X5ToFlirt(args):
    """Convert a linear X5 transformation file to a FLIRT matrix. """
    xform, src, ref = x5.readLinearX5(args.input)
    xform           = flirt.toFlirt(xform, src, ref, 'world', 'world')
    flirt.writeFlirt(xform, args.output)


def fnirtToX5(args):
    """Convert a non-linear FNIRT transformation into an X5 transformation
    file.
    """
    src   = args.source
    ref   = args.reference
    field = fnirt.readFnirt(args.input, src=src, ref=ref)
    field = fnirt.fromFnirt(field, 'world', 'world')
    x5.writeNonLinearX5(args.output, field)


def X5ToFnirt(args):
    """Convert a non-linear X5 transformation file to a FNIRT-style
    transformation file.
    """
    field = x5.readNonLinearX5(args.input)
    field = fnirt.toFnirt(field)
    field.save(args.output)


def doFlirt(args):
    """Converts a linear FIRT transformation file to or from the X5 format. """
    infmt  = args.input_format
    outfmt = args.output_format

    if   (infmt, outfmt) == ('x5', 'mat'): X5ToFlirt(args)
    elif (infmt, outfmt) == ('mat', 'x5'): flirtToX5(args)
    else: shutil.copy(args.input, args.output)


def doFnirt(args):
    """Converts a non-linear FNIRT transformation file to or from the X5
    format.
    """
    infmt  = args.input_format
    outfmt = args.output_format

    if   (infmt, outfmt) == ('x5', 'nii'): X5ToFnirt(args)
    elif (infmt, outfmt) == ('nii', 'x5'): fnirtToX5(args)
    else: shutil.copy(args.input, args.output)


def main(args=None):
    """Entry point. Calls :func:`doFlirt` or :func:`doFnirt` depending on
    the sub-command specified in the arguments.

    :arg args: Sequence of command-line arguments. If not provided,
               ``sys.argv`` is used.
    """

    if args is None:
        args = sys.argv[1:]

    args = parseArgs(args)

    if   args.ctype == 'flirt': doFlirt(args)
    elif args.ctype == 'fnirt': doFnirt(args)

    return 0


if __name__ == '__main__':
    sys.exit(main())