diff --git a/fsl/transform/x5.py b/fsl/transform/x5.py
index b02894f0fa245ca313c956d666eecaceaa7595e3..5e3162f4529c42e39eaffd922769abc232e5f934 100644
--- a/fsl/transform/x5.py
+++ b/fsl/transform/x5.py
@@ -344,11 +344,89 @@ X5_FORMAT  = 'X5'
 X5_VERSION = '0.0.1'
 
 
-class InvalidFileError(Exception):
+class X5Error(Exception):
     """Error raised if an invalid/incompatible file is detected. """
     pass
 
 
+def readLinearX5(fname):
+    """Read a linear X5 transformation file from ``fname``.
+
+    :arg fname: File name to read from
+    :returns:    A tuple containing:
+
+                  - A ``(4, 4)`` ``numpy`` array containing the affine
+                    transformation
+                  - A :class:`.Nifti` instance representing the source space
+                  - A :class:`.Nifti` instance representing the reference space
+    """
+    with h5py.File(fname, 'r') as f:
+        _validateMetadata(   f['/'])
+        xform = _readAffine( f['/'])
+        src   = _readSpace(  f['/From'])
+        ref   = _readSpace(  f['/To'])
+
+    return xform, src, ref
+
+
+def writeLinearX5(fname, xform, src, ref):
+    """Write a linear transformation to the specified file.
+
+    :arg fname: File name to write to
+    :arg xform: ``(4, 4)`` ``numpy`` array containing the affine transformation
+    :arg src:   :class:`.Nifti` image representing the source space
+    :arg ref:   :class:`.Nifti` image representing the reference
+    """
+
+    with h5py.File(fname, 'w') as f:
+        _writeMetadata(f)
+        _writeAffine(f, xform)
+
+        from_ = f.create_group('/From')
+        to    = f.create_group('/To')
+
+        _writeSpace(from_, src)
+        _writeSpace(to,    ref)
+
+
+def readNonLinearX5(fname):
+    """Read a nonlinear X5 transformation file from ``fname``.
+
+    :arg fname: File name to read from
+    :returns:   A :class:`.DisplacementField` or :class:`.CoefficientField`
+    """
+
+    with h5py.File(fname, 'r') as f:
+
+        root = f['/']
+        _validateNonLinearTransform(root)
+
+        if root['SubType'] == 'displacement':
+            field = _readDisplacementField(root)
+        elif root['SubType'] == 'coefficient':
+            field = _readCoefficientField(root)
+
+    return field
+
+
+def writeNonLinearX5(fname, field):
+    """Write a nonlinear X5 transformation file to ``fname``.
+
+    :arg fname: File name to write to
+    :arg field: A :class:`.DisplacementField` or :class:`.CoefficientField`
+    """
+
+    with h5py.File(fname, 'w') as f:
+        _writeMetadata(f)
+
+        f.attrs['Type'] = 'nonlinear'
+
+        if isinstance(field, nonlinear.DisplacementField):
+            _writeDisplacementField(f, field)
+        elif isinstance(field, nonlinear.CoefficientField):
+            _writeCoefficientField(f, field)
+
+
 def _writeMetadata(group):
     """Writes a metadata block into the given group.
 
@@ -360,8 +438,10 @@ def _writeMetadata(group):
 
 
 def _validateMetadata(group):
-    """Reads a metadata block from the given group, and raises an
-    :exc:`InvalidFileError` if it does not look valid.
+    """Reads a metadata block from the given group, and raises a :exc:`X5Error`
+    if it does not look valid.
+
+    :arg group: A ``h5py.Group`` object.
     """
 
     try:
@@ -369,30 +449,30 @@ def _validateMetadata(group):
         version = group.attrs['Version']
 
     except Exception:
-        raise InvalidFileError('File does not appear to be an X5 file')
+        raise X5Error('File does not appear to be an X5 file')
 
     if (format != X5_FORMAT) or (version != X5_VERSION):
-        raise InvalidFileError('Incompatible format/version (required: {}/{}, '
-                               'present: {}/{})'.format(X5_FORMAT, X5_VERSION,
-                                                        format, version))
+        raise X5Error('Incompatible format/version (required: {}/{}, '
+                      'present: {}/{})'.format(X5_FORMAT, X5_VERSION,
+                                               format, version))
 
 
 def _readAffine(group):
-    """Reads an affine from the given group,
+    """Reads an affine from the given group.
 
     :arg group: A ``h5py.Group`` object.
     :returns:   ``numpy`` array containing a ``(4, 4)`` affine transformation.
     """
 
     if group.attrs['Type'] != 'linear':
-        raise ValueError('Not a linear transform')
+        raise X5Error('Not a linear transform')
 
-    xform = np.array(group['Transform'])
+    xform = group['Transform']
 
     if xform.shape != (4, 4):
-        raise ValueError('Not a linear transform')
+        raise X5Error('Not a linear transform')
 
-    return xform
+    return np.array(xform)
 
 
 def _writeAffine(group, xform):
@@ -421,7 +501,7 @@ def _readSpace(group):
     import fsl.data.image as fslimage
 
     if group.attrs['Type'] != 'image':
-        raise ValueError('Not an image mapping')
+        raise X5Error('Not an image space')
 
     shape  = group.attrs['Size']
     pixdim = group.attrs['Scales']
@@ -448,130 +528,195 @@ def _writeSpace(group, img):
     _writeAffine(mapping, img.getAffine('voxel', 'world'))
 
 
-def _readDisplacementField(group):
-    """
+def _validateNonLinearTransform(group):
+    """Checks that the attributes of the given group, assumed to contain a
+    nonlinear transform, are valid. Raises a :exc:`X5Error` if not.
+
+    :arg group: A ``h5py.Group`` object.
     """
 
-    field    = np.array(group['Transform'])
-    dispType = group.attrs['Representation']
-    src      = _readSpace( group['From'])
-    ref      = _readSpace( group['To'])
-    pre      = _readAffine(group['Pre'])
-    post     = _readAffine(group['Post'])
-    srcToRef = _readAffine(group['InitialAlignment'])
+    type     = group.attrs['Type']
+    subtype  = group.attrs['SubType']
+    repr     = group.attrs['Representation']
 
-    refSpace = affine.identify(ref, pre,  from_='world')[1]
-    srcSpace = affine.identify(src, post, to='world')[   0]
+    if type != 'nonlinear':
+        raise X5Error('Not a nonlinear transform')
 
-    return nonlinear.DisplacementField(field,
-                                       src,
-                                       ref,
-                                       srcSpace=srcSpace,
-                                       refSpace=refSpace,
-                                       srcToRefMat=srcToRef,
-                                       dispType=dispType)
+    if subtype not in ('displacement', 'coefficient'):
+        raise X5Error('Unrecognised nonlinear subtype: {}'.format(subtype))
 
+    if (subtype == 'displacement') and \
+       (repr not in ('absolute', 'relative')):
+        raise X5Error('Unrecognised nonlinear representation: '
+                      '{}'.format(repr))
 
-def _readCoefficientField(group):
-    """
+    if (subtype == 'coefficient') and \
+       (repr not in ('quadratic bspline', 'cubic bspline')):
+        raise X5Error('Unrecognised nonlinear representation: '
+                      '{}'.format(repr))
+
+
+def _readNonLinearCommon(group):
+    """Reads the spaces and affines from the given group, assumed to contain a
+    nonlinear transform.
+
+    :arg group: A ``h5py.Group`` object.
+
+    :returns:   A tuple containing:
+                 - A :class:`.Nifti` representing the source
+                 - A :class:`.Nifti` representing the reference
+                 - A ``(4, 4)`` ``numpy`` array containing the pre affine, or
+                   ``None``  if there is not one.
+                 - A ``(4, 4)`` ``numpy`` array containing the post affine, or
+                   ``None``  if there is not one.
+                 - A ``(4, 4)`` ``numpy`` array containing the initial
+                   alignment affine, or ``None``  if there is not one.
+                 - A string describing the source space - see
+                   :meth:`.Nifti.getAffine`
+                 - A string describing the reference  space - see
+                   :meth:`.Nifti.getAffine`
     """
-    field     = np.array(group['Transform'])
-    fieldType = group.attrs['Representation']
-    src       = _readSpace( group['From'])
-    ref       = _readSpace( group['To'])
-    pre       = _readAffine(group['Pre'])
-    post      = _readAffine(group['Post'])
-    srcToRef  = _readAffine(group['InitialAlignment'])
 
-    spacing    = group['Parameters'].attrs['Spacing']
-    refToField = _readAffine(group['Parameters']['ReferenceToField'])
-    fieldToRef = affine.inverse(refToField)
+    src  = _readSpace(group['From'])
+    ref  = _readSpace(group['To'])
 
-    refSpace = affine.identify(ref, pre,  from_='world')[1]
-    srcSpace = affine.identify(src, post, to='world')[   0]
+    pre  = group.get('Pre')
+    post = group.get('Post')
+    init = group.get('InitialAlignment')
 
-    return nonlinear.CoefficientField(field,
-                                      src,
-                                      ref,
-                                      srcSpace=srcSpace,
-                                      refSpace=refSpace,
-                                      fieldType=fieldType,
-                                      knotSpacing=spacing,
-                                      srcToRefMat=srcToRef,
-                                      fieldToRefMat=fieldToRef)
+    if pre  is not None: pre  = _readAffine(pre)
+    if post is not None: post = _readAffine(post)
+    if init is not None: init = _readAffine(init)
 
+    refSpace = 'world'
+    srcSpace = 'world'
 
+    try:
+        if pre is not None:
+            refSpace = affine.identify(ref, pre,  from_='world')[1]
+        if post is not None:
+            srcSpace = affine.identify(src, post, to='world')[   0]
+
+    except ValueError:
+        raise X5Error('Invalid pre/post affine')
+
+    return src, ref, pre, post, init, srcSpace, refSpace
 
-def readLinearX5(fname):
-    """Read a linear X5 transformation file from ``fname``.
 
-    :return: A tuple containing:
+def _writeNonLinearCommon(group, field):
+    """Writes the spaces and affines for the given nonlinear transform to the
+    given group.
 
-              - A ``(4, 4)`` ``numpy`` array containing the affine
-                transformation
-              - A :class:`.Nifti` instance representing the source space
-              - A :class:`.Nifti` instance representing the reference space
+    :arg group: A ``h5py.Group`` object.
+    :arg field: A :class:`.NonLinearTransform` object
     """
-    with h5py.File(fname, 'r') as f:
-        _validateMetadata(   f['/'])
-        xform = _readAffine( f['/'])
-        src   = _readSpace(  f['/From'])
-        ref   = _readSpace(  f['/To'])
 
-    return xform, src, ref
+    _writeSpace(group.create_group('From'), field.src)
+    _writeSpace(group.create_group('To'),   field.ref)
 
+    pre  = field.ref.getAffine('world', field.refSpace)
+    post = field.src.getAffine(field.srcSpace, 'world')
 
-def writeLinearX5(fname, xform, src, ref):
-    """Write a linear transformation to the specified file.
+    _writeAffine(group.create_group('Pre'),  pre)
+    _writeAffine(group.create_group('Post'), post)
 
-    :arg fname: File name to write to
-    :arg xform: ``(4, 4)`` ``numpy`` array containing the affine transformation
-    :arg src:   :class:`.Nifti` image representing the source space
-    :arg ref:   :class:`.Nifti` image representing the reference
-    """
+    if field.srcToRefMat is not None:
+        _writeAffine(group.create_group('InitialAlignment', field.srcToRefMat))
 
-    with h5py.File(fname, 'w') as f:
-        _writeMetadata(f)
-        _writeAffine(f, xform)
 
-        from_ = f.create_group('/From')
-        to    = f.create_group('/To')
+def _readDisplacementField(group):
+    """Reads a nonlinear displacement field from the given group.
 
-        _writeSpace(from_, src)
-        _writeSpace(to,    ref)
+    :arg group: A ``h5py.Group`` object.
+    :returns:   A :class:`.DisplacementField` object
+    """
 
+    src, ref, pre, post, init, srcSpace, refSpace = _readNonLinearCommon(group)
+    field = np.array(group['Transform'])
+    dtype = group['Representation']
+    field = nonlinear.DisplacementField(field,
+                                        src=src,
+                                        ref=ref,
+                                        srcSpace=srcSpace,
+                                        refSpace=refSpace,
+                                        dispType=dtype,
+                                        srcToRefMat=init)
+    return field
 
-def readNonLinearX5(fname):
+
+def _writeDisplacementField(group, field):
+    """Writes a nonlinear displacement field to the given group.
+
+    :arg group: A ``h5py.Group`` object.
+    :arg field: A :class:`.DisplacementField` object
     """
+
+    _writeNonLinearCommon(group, field)
+
+    group.attrs['SubType']        = 'displacement'
+    group.attrs['Representation'] = field.displacementType
+
+    xform = np.field.data.astype(np.float64)
+
+    group.create_dataset('Transform', data=xform)
+
+
+def _readCoefficientField(group):
+    """Reads a nonlinear coefficient field from the given group.
+
+    :arg group: A ``h5py.Group`` object.
+    :returns:   A :class:`.CoefficientField` object
     """
 
-    from . import nonlinear
+    src, ref, pre, post, init, srcSpace, refSpace = _readNonLinearCommon(group)
 
-    with h5py.File(fname, 'r') as f:
-        field = _readNonLinearTransform(f['/'])
-        src   = _readSpace(f['/From'])
-        ref   = _readSpace(f['/To'])
+    field      = np.array(group['Transform'])
+    ftype      = group['Representation']
+    spacing    = group['Parameters/Spacing']
+    refToField = _readAffine(group['Parameters/ReferenceToField'])
+    fieldToRef = affine.invert(refToField)
+
+    if   ftype == 'quadratic bspline': ftype = 'quadratic'
+    elif ftype == 'cubic bspline':     ftype = 'cubic'
 
-    # TODO coefficient fields
-    return nonlinear.DisplacementField(field,
+    if spacing.shape != 3:
+        raise X5Error('Invalid spacing: {}'.format(spacing))
+
+    field = nonlinear.CoefficientField(field,
                                        src=src,
                                        ref=ref,
-                                       srcSpace='world',
-                                       refSpace='world')
+                                       srcSpace=srcSpace,
+                                       refSpace=refSpace,
+                                       fieldType=ftype,
+                                       knotSpacing=spacing,
+                                       fieldToRefMat=fieldToRef,
+                                       srcToRefMat=init)
 
+    return field
 
-def writeNonLinearX5(fname, field):
-    """
+
+def _writeCoefficientField(group, field):
+    """Writes a nonlinear coefficient field to the given group.
+
+    :arg group: A ``h5py.Group`` object.
+    :arg field: A :class:`.CoefficientField` object
     """
 
-    # TODO coefficient fields
+    _writeNonLinearCommon(group, field)
 
-    with h5py.File(fname, 'w') as f:
-        _writeMetadata(f)
-        _writeNonLinearTransform(f, field.data)
+    group.attrs['SubType'] = 'coefficient'
 
-        from_ = f.create_group('/From')
-        to    = f.create_group('/To')
+    if field.fieldType == 'cubic':
+        group.attrs['Representation'] = 'cubic bspline'
+    elif field.fieldType == 'quadratic':
+        group.attrs['Representation'] = 'quadratic bspline'
+
+    xform = np.field.data.astype(np.float64)
+
+    group.create_dataset('Transform', data=xform)
+
+    params = group.create_group('Parameters')
+    refToField = params.create_group('ReferenceToField')
 
-        _writeLinearMapping(from_, field.src)
-        _writeLinearMapping(to,    field.ref)
+    params.attrs['Spacing'] = field.knotSpacing
+    _writeAffine(refToField, field.refToFieldMat)