Exemplo n.º 1
0
def subtract_img(source_img: SpatialImage, target_img: SpatialImage,
                 edge_dist: int = 0) -> SpatialImage:
    """Subtracts 3D array y from x. Optionally, y is expanded by the
    edge_dist value (in milimeters), using the x and y affine values
    to calculate Euclidean distance."""

    source_data = source_img.get_data()  # min
    target_data = target_img.get_data()  # sub

    if source_data.shape != target_data.shape:
        raise ShapeError(source_data.shape, target_data.shape)

    difference = np.zeros(source_data.shape)
    source_voxels = np.asarray(np.where(source_data == 1)).transpose()
    target_voxels = np.asarray(np.where(target_data == 1)).transpose()

    # Calculate Euclidean distance from outermost voxels in y
    source_ref = nib.affines.apply_affine(source_img.affine, source_voxels)
    target_ref = nib.affines.apply_affine(target_img.affine, target_voxels)
    dist = distance.cdist(target_ref, source_ref, 'euclidean')

    # for each target voxel get the minimum distance value
    dist = np.amin(dist, axis=0)
    x, y, z = source_voxels[np.where(dist > edge_dist), :].squeeze()\
        .transpose()
    difference[x, y, z] = 1

    return nib.Nifti1Image(np.float32(difference), source_img.affine,
                           source_img.header)
Exemplo n.º 2
0
def mask_image(image: SpatialImage,
               mask: np.ndarray,
               data_type: type = None) -> np.ndarray:
    """Mask image after optionally casting its type.

    Parameters
    ----------
    image
        Image to mask. Can include time as the last dimension.
    mask
        Mask to apply. Must have the same shape as the image data.
    data_type
        Type to cast image to.

    Returns
    -------
    np.ndarray
        Masked image.

    Raises
    ------
    ValueError
        Image data and masks have different shapes.
    """
    image_data = image.get_data()
    if image_data.shape[:3] != mask.shape:
        raise ValueError("Image data and mask have different shapes.")
    if data_type is not None:
        cast_data = image_data.astype(data_type)
    else:
        cast_data = image_data
    return cast_data[mask]
Exemplo n.º 3
0
def mask_image(image: SpatialImage, mask: np.ndarray, data_type: type = None
               ) -> np.ndarray:
    """Mask image after optionally casting its type.

    Parameters
    ----------
    image
        Image to mask. Can include time as the last dimension.
    mask
        Mask to apply. Must have the same shape as the image data.
    data_type
        Type to cast image to.

    Returns
    -------
    np.ndarray
        Masked image.

    Raises
    ------
    ValueError
        Image data and masks have different shapes.
    """
    image_data = image.get_data()
    if image_data.shape[:3] != mask.shape:
        raise ValueError("Image data and mask have different shapes.")
    if data_type is not None:
        cast_data = image_data.astype(data_type)
    else:
        cast_data = image_data
    return cast_data[mask]
Exemplo n.º 4
0
def median_filter_img(img: SpatialImage, dist: int = 1) -> SpatialImage:
    """Median filtering of non-zero elements in an image.

    Median filter all selected voxels of a binary 3D input image
    and a 2-iteration dilation border around it. Voxel distance
    dist is used to determine the size of the area for computing
    the median, where a distance of 1 results in a 3*3*3 shape.
    The number of repetitions nrep determines how often the median
    filter will be repeated.
    """

    if not img_is_3d(img):
        raise ShapeError(3, len(img.shape))

    data = img.get_data()
    dilated = binary_dilation(data, iterations=2).astype(int)
    voxels = np.asarray(np.where(dilated == 1)).transpose()

    filtered = np.zeros(img.shape)
    for x, y, z in voxels:
        area = data[x-dist:x+(dist+1), y-dist:y+(dist+1), z-dist:z+(dist+1)]

        if np.median(area) > float_info.min:
            filtered[x, y, z] = 1

    return nib.Nifti1Image(np.float32(filtered), img.affine, img.header)
Exemplo n.º 5
0
def get_mask_indices(img: SpatialImage, order: str = 'C') -> np.ndarray:
    """Get voxel space coordinates (indices) of seed voxels

    Parameters
    ----------
    img : SpatialImage
        Mask NIfTI image
    order : str, optional
        Order that the seed-mask voxels will be extracted in.
        The resulting indices will be listed in this way

    Returns
    -------
    np.ndarray
        2D array of shape (n_voxels, 3) containing the 3D coordinates of
        all mask image voxels
    """

    if order not in ('C', 'F', 'c', 'f'):
        raise ValueError('Order has unexpected value: expected %s, got \'%s\''
                         % ("'C' or 'F'", order))

    data = img.get_data()
    indices = np.asarray(tuple(zip(*np.where(data == 1))))

    if order.upper() == 'F':
        # indices are C order and must become F order
        reorder = get_c2f_order(img)
        indices = indices[reorder]

    return indices
Exemplo n.º 6
0
def subsample_img(img: SpatialImage, f: int = 2) -> SpatialImage:
    """Reduce image features of a 3D array by a given factor f."""
    if not img_is_3d(img):
        raise ShapeError(3, len(img.shape))

    data = img.get_data().astype(int)
    mask = np.zeros(img.shape).astype(int)
    mask[::f, ::f, ::f] = 1
    data *= mask
    return nib.Nifti1Image(data, img.affine, img.header)
Exemplo n.º 7
0
def binarize_3d(img: SpatialImage, threshold: float = 0.0) -> SpatialImage:
    """binarize 3D spatial image. NaNs and infs in the image are set to 0."""
    if not img_is_3d(img):
        raise ShapeError(3, len(img.shape))

    data = img.get_data()
    data[np.where(np.isnan(data))] = 0
    data[np.where(np.isinf(data))] = 0
    data = np.where(data > threshold, 1, 0)

    return nib.Nifti1Image(data, img.affine, img.header)
Exemplo n.º 8
0
def get_masked_series(time_series: SpatialImage, mask: SpatialImage):
    """Apply a 3D mask to a 4D image

    Parameters
    ----------
    time_series : SpatialImage
        4D nifti image, with time-series on the 4th dimension
    mask : SpatialImage
        3D boolean mask image
    """
    if not imgs_equal_3d([time_series, mask]):
        raise ValueError('Time-series and mask do not have equal shape '
                         'and/or affine')

    if not img_is_4d(time_series):
        raise ShapeError(4, len(time_series.shape))

    time_series_data = time_series.get_data()
    mask_data = mask.get_data().astype(bool)

    return time_series_data[mask_data].T
Exemplo n.º 9
0
def extract_regions(atlas: SpatialImage,
                    region_ids: Union[list, int]) -> SpatialImage:
    """Extract regions from an atlas"""
    if isinstance(region_ids, int):
        region_ids = [region_ids]

    data = atlas.get_data()

    if not np.all(np.isin(region_ids, data)):
        raise ValueError('could not find some (or all) of the given '
                         'region-ids in the atlas')

    data[~np.isin(data, region_ids)] = 0
    data[np.where(data > 0)] = 1

    if len(np.unique(data)) != 2:
        raise ValueError('mask is empty after extracting region from atlas')

    return nib.Nifti1Image(data.astype(int), atlas.affine, atlas.header)
Exemplo n.º 10
0
def get_c2f_order(img: SpatialImage) -> np.ndarray:
    """The order in which voxels are extracted from a mask is either
    F-contiguous or C-contiguous (C by default in NumPy), which is reflected
    by the order in which they are placed in an array. 
    
    This function provides reordering indices such that a C extraction order 
    is turned into an F extraction order.

    Parameters
    ----------
    img : SpatialImage
        Mask NIfTI image
    """
    mask = img.get_data()
    reorder = np.arange(int(np.prod(mask.shape)))
    reorder = reorder.reshape(mask.shape, order='F')
    reorder = reorder[mask.astype(bool)]
    reorder = np.argsort(reorder)
    return reorder
Exemplo n.º 11
0
    def _preproc_binarize(field: str,
                          img: SpatialImage,
                          bin_threshold: float = 0.0) -> SpatialImage:
        data = img.get_data()

        if not np.array_equal(data, data.astype(bool)):
            n_nans = np.count_nonzero(np.isnan(data))
            n_infs = np.count_nonzero(np.isinf(data))

            if n_nans > 0:
                logging.warning(
                    '%s NaN values in %s. Please manually verify %s.nii' %
                    (n_nans, field, field))

            if n_infs > 0:
                logging.warning(
                    '%s inf values in %s. Please manually verify %s.nii' %
                    (n_infs, field, field))

        img = binarize_3d(img, threshold=bin_threshold)
        return img
Exemplo n.º 12
0
def stretch_img(source_img: SpatialImage,
                target: Union[tuple, SpatialImage]) -> SpatialImage:
    """Stretch a binary image to meet the dimensions of a
    template image.

    The stretching process will not generate new indices of ones,
    instead keeping the original amount of mask values but spacing
    them out over the template dimensions. This function assumes
    that the template dimensions are larger than the input image
    dimensions and the input image is binary.
    """

    try:
        target_shape, target_affine = target.shape, target.affine

    except AttributeError:
        target_shape, target_affine = target

    s_affine = np.abs(np.diag(source_img.affine[:3]))
    t_affine = np.abs(np.diag(target_affine[:3]))
    if np.all(s_affine <= t_affine):
        raise ValueError('This function is meant for upsampling, not '
                         'downsampling')

    x, y, z = np.nonzero(source_img.get_data())
    xyz = np.asarray([x, y, z, np.ones(len(z))]).T

    # 'Stretch' coordinates so they space out in a larger template
    xyz = np.diag(np.linalg.solve(target_affine, source_img.affine)) * xyz
    xyz = np.unique(np.round(xyz), axis=0)
    xyz = xyz.astype(int)
    x, y, z, _ = np.hsplit(xyz, 4)
    m = np.zeros(target_shape)
    m[x, y, z] = 1

    return nib.Nifti1Image(np.float32(m), target_affine, source_img.header)
Exemplo n.º 13
0
def images(spatial_image: SpatialImage) -> Iterable[SpatialImage]:
    images = [spatial_image]
    image_data = spatial_image.get_data().copy()
    image_data[1, 1, 1, 0] = 2
    images.append(Nifti1Pair(image_data, np.eye(4)))
    return images
Exemplo n.º 14
0
def images(spatial_image: SpatialImage) -> Iterable[SpatialImage]:
    images = [spatial_image]
    image_data = spatial_image.get_data().copy()
    image_data[1, 1, 1] = 2
    images.append(Nifti1Pair(image_data, np.eye(4)))
    return images