
__all__ = ['get_neighborhood_in_mask',
           'get_neighborhood_at_voxel']

import numpy as np

import ants

from ants.internal import get_lib_fn
from ants.decorators import image_method

@image_method
def get_neighborhood_in_mask(image, mask, radius, physical_coordinates=False,
                            boundary_condition=None, spatial_info=False, get_gradient=False):
    """
    Get neighborhoods for voxels within mask.
    
    This converts a scalar image to a matrix with rows that contain neighbors 
    around a center voxel
    
    ANTsR function: `getNeighborhoodInMask`

    Arguments
    ---------
    image : ANTsImage
        image to get values from
    
    mask : ANTsImage
        image indicating which voxels to examine. Each voxel > 0 will be used as the 
        center of a neighborhood
    
    radius : tuple/list
        array of values for neighborhood radius (in voxels)
    
    physical_coordinates : boolean
        whether voxel indices and offsets should be in voxel or physical coordinates
    
    boundary_condition : string (optional)
        how to handle voxels in a neighborhood, but not in the mask.
            None : fill values with `NaN`
            `image` : use image value, even if not in mask
            `mean` : use mean of all non-NaN values for that neighborhood
    
    spatial_info : boolean
        whether voxel locations and neighborhood offsets should be returned along with pixel values.
    
    get_gradient : boolean
        whether a matrix of gradients (at the center voxel) should be returned in 
        addition to the value matrix (WIP)

    Returns
    -------
    if spatial_info is False:
        if get_gradient is False:
            ndarray
                an array of pixel values where the number of rows is the size of the 
                neighborhood and there is a column for each voxel

        else if get_gradient is True:
            dictionary w/ following key-value pairs:
                values : ndarray
                    array of pixel values where the number of rows is the size of the 
                    neighborhood and there is a column for each voxel.

                gradients : ndarray
                    array providing the gradients at the center voxel of each 
                    neighborhood
        
    else if spatial_info is True:
        dictionary w/ following key-value pairs:
            values : ndarray
                array of pixel values where the number of rows is the size of the 
                neighborhood and there is a column for each voxel.

            indices : ndarray
                array provinding the center coordinates for each neighborhood

            offsets : ndarray
                array providing the offsets from center for each voxel in a neighborhood

    Example
    -------
    >>> import ants
    >>> r16 = ants.image_read(ants.get_ants_data('r16'))
    >>> mask = ants.get_mask(r16)
    >>> mat = ants.get_neighborhood_in_mask(r16, mask, radius=(2,2))
    """
    if not ants.is_image(image):
        raise ValueError('image must be ANTsImage type')
    if not ants.is_image(mask):
        raise ValueError('mask must be ANTsImage type')
    if isinstance(radius, (int, float)):
        radius = [radius]*image.dimension
    if (not isinstance(radius, (tuple,list))) or (len(radius) != image.dimension):
        raise ValueError('radius must be tuple or list with length == image.dimension')

    boundary = 0
    if boundary_condition == 'image':
        boundary = 1
    elif boundary_condition == 'mean':
        boundary = 2
    
    libfn = get_lib_fn('getNeighborhoodMatrix%s' % image._libsuffix)
    retvals = libfn(image.pointer, 
                    mask.pointer, 
                    list(radius),
                    int(physical_coordinates), 
                    int(boundary),
                    int(spatial_info),
                    int(get_gradient))

    if not spatial_info:
        if get_gradient:
            retvals['values'] = np.asarray(retvals['values'])
            retvals['gradients'] = np.asarray(retvals['gradients'])
        else:
            retvals = np.asarray(retvals['matrix'])
    else:
        retvals['values'] = np.asarray(retvals['values'])
        retvals['indices'] = np.asarray(retvals['indices'])
        retvals['offsets'] = np.asarray(retvals['offsets'])

    return retvals

@image_method
def get_neighborhood_at_voxel(image, center, kernel, physical_coordinates=False):
    """
    Get a hypercube neighborhood at a voxel. Get the values in a local 
    neighborhood of an image.
    
    ANTsR function: `getNeighborhoodAtVoxel`

    Arguments
    ---------
    image : ANTsImage
        image to get values from.
    
    center : tuple/list
        indices for neighborhood center
    
    kernel : tuple/list
        either a collection of values for neighborhood radius (in voxels) or 
        a binary collection of the same dimension as the image, specifying the shape of the neighborhood to extract
    
    physical_coordinates : boolean
        whether voxel indices and offsets should be in voxel 
        or physical coordinates

    Returns
    -------
    dictionary w/ following key-value pairs:
        values : ndarray
            array of neighborhood values at the voxel

        indices : ndarray
            matrix providing the coordinates for each value

    Example
    -------
    >>> import ants
    >>> img = ants.image_read(ants.get_ants_data('r16'))
    >>> center = (2,2)
    >>> radius = (3,3)
    >>> retval = ants.get_neighborhood_at_voxel(img, center, radius)
    """
    if not ants.is_image(image):
        raise ValueError('image must be ANTsImage type')

    if (not isinstance(center, (tuple,list))) or (len(center) != image.dimension):
        raise ValueError('center must be tuple or list with length == image.dimension')

    if (not isinstance(kernel, (tuple,list))) or (len(kernel) != image.dimension):
        raise ValueError('kernel must be tuple or list with length == image.dimension')

    radius = [int((k-1)/2) for k in kernel]

    libfn = get_lib_fn('getNeighborhood%s' % image._libsuffix)
    retvals = libfn(image.pointer, 
                    list(center), 
                    list(kernel), 
                    list(radius), 
                    int(physical_coordinates))
    for k in retvals.keys():
        retvals[k] = np.asarray(retvals[k])
    return retvals


