Apply a transform list to map a pointset from one domain to
     another. In registration, one computes mappings between pairs of
     domains. These transforms are often a sequence of increasingly
     complex maps, e.g. from translation, to rigid, to affine to
     deformation.  The list of such transforms is passed to this
     function to interpolate one image domain into the next image
     domain, as below.  The order matters strongly and the user is
     advised to familiarize with the standards established in examples.
     Importantly, point mapping goes the opposite direction of image
     mapping, for both reasons of convention and engineering.

    ANTsR function: `antsApplyTransformsToPoints`

    Arguments
    ---------
    dim: integer
         dimensionality of the transformation.

    points: data frame
          moving point set with n-points in rows of at least dim
          columns - we maintain extra information in additional
          columns. this should be a data frame with columns names x, y, z, t.

    transformlist : list of strings
        list of transforms generated by ants.registration where each transform is a filename.

    whichtoinvert : list of booleans (optional)
        Must be same length as transformlist.
        whichtoinvert[i] is True if transformlist[i] is a matrix,
        and the matrix should be inverted. If transformlist[i] is a
        warp field, whichtoinvert[i] must be False.
        If the transform list is a matrix followed by a warp field,
        whichtoinvert defaults to (True,False). Otherwise it defaults
        to [False]*len(transformlist)).

    verbose : boolean

    Returns
    -------
    data frame of transformed points

    Example
    -------
    >>> import ants
    >>> fixed = ants.image_read( ants.get_ants_data('r16') )
    >>> moving = ants.image_read( ants.get_ants_data('r27') )
    >>> reg = ants.registration( fixed, moving, 'Affine' )
    >>> d = {'x': [128, 127], 'y': [101, 111]}
    >>> pts = pd.DataFrame(data=d)
    >>> ptsw = ants.apply_transforms_to_points( 2, pts, reg['fwdtransforms'])

def apply_transforms_to_points( dim, points, transformlist,
                     whichtoinvert=None, verbose=False ):
    """
     Apply a transform list to map a pointset from one domain to
     another. In registration, one computes mappings between pairs of
     domains. These transforms are often a sequence of increasingly
     complex maps, e.g. from translation, to rigid, to affine to
     deformation.  The list of such transforms is passed to this
     function to interpolate one image domain into the next image
     domain, as below.  The order matters strongly and the user is
     advised to familiarize with the standards established in examples.
     Importantly, point mapping goes the opposite direction of image
     mapping, for both reasons of convention and engineering.

    ANTsR function: `antsApplyTransformsToPoints`

    Arguments
    ---------
    dim: integer
         dimensionality of the transformation.

    points: data frame
          moving point set with n-points in rows of at least dim
          columns - we maintain extra information in additional
          columns. this should be a data frame with columns names x, y, z, t.

    transformlist : list of strings
        list of transforms generated by ants.registration where each transform is a filename.

    whichtoinvert : list of booleans (optional)
        Must be same length as transformlist.
        whichtoinvert[i] is True if transformlist[i] is a matrix,
        and the matrix should be inverted. If transformlist[i] is a
        warp field, whichtoinvert[i] must be False.
        If the transform list is a matrix followed by a warp field,
        whichtoinvert defaults to (True,False). Otherwise it defaults
        to [False]*len(transformlist)).

    verbose : boolean

    Returns
    -------
    data frame of transformed points

    Example
    -------
    >>> import ants
    >>> fixed = ants.image_read( ants.get_ants_data('r16') )
    >>> moving = ants.image_read( ants.get_ants_data('r27') )
    >>> reg = ants.registration( fixed, moving, 'Affine' )
    >>> d = {'x': [128, 127], 'y': [101, 111]}
    >>> pts = pd.DataFrame(data=d)
    >>> ptsw = ants.apply_transforms_to_points( 2, pts, reg['fwdtransforms'])
    """

    if not isinstance(transformlist, (tuple, list)) and (transformlist is not None):
        transformlist = [transformlist]

    args = [dim, points, transformlist, whichtoinvert]

    for tl_path in transformlist:
        if not os.path.exists(tl_path):
            raise Exception('Transform %s does not exist' % tl_path)

    mytx = []

    if whichtoinvert is None or (isinstance(whichtoinvert, (tuple,list)) and (sum([w is not None for w in whichtoinvert])==0)):
        if (len(transformlist) == 2) and ('.mat' in transformlist[0]) and ('.mat' not in transformlist[1]):
            whichtoinvert = (True, False)
        else:
            whichtoinvert = tuple([False]*len(transformlist))

    if len(whichtoinvert) != len(transformlist):
        raise ValueError('Transform list and inversion list must be the same length')

    for i in range(len(transformlist)):
        ismat = False
        if '.mat' in transformlist[i]:
            ismat = True
        if whichtoinvert[i] and (not ismat):
            raise ValueError('Cannot invert transform %i (%s) because it is not a matrix' % (i, transformlist[i]))
        if whichtoinvert[i]:
            mytx = mytx + ['-t', '[%s,1]' % (transformlist[i])]
        else:
            mytx = mytx + ['-t', transformlist[i]]
    if dim == 2:
        pointsSub = points[['x','y']]
    if dim == 3:
        pointsSub = points[['x','y','z']]
    if dim == 4:
        pointsSub = points[['x','y','z','t']]
    pointImage = core.make_image( pointsSub.shape, pointsSub.values.flatten())
    pointsOut = pointImage.clone()
    args = ['-d', dim,
            '-i', pointImage,
            '-o', pointsOut ]
    args = args + mytx
    myargs = utils._int_antsProcessArguments(args)

    myverb = int(verbose)
    if verbose:
        print(myargs)

    processed_args = myargs + [ '-f', str(1), '--precision', str(0)]
    libfn = utils.get_lib_fn('antsApplyTransformsToPoints')
    libfn(processed_args)
    mynp = pointsOut.numpy()
    pointsOutDF = points.copy()
    pointsOutDF['x'] = mynp[:,0]
    if dim >= 2:
        pointsOutDF['y'] = mynp[:,1]
    if dim >= 3:
        pointsOutDF['z'] = mynp[:,2]
    if dim >= 4:
        pointsOutDF['t'] = mynp[:,3]
    return pointsOutDF