Source code for dipy.nn.utils

import numpy as np
from scipy.ndimage import affine_transform, map_coordinates

from dipy.testing.decorators import warning_for_keywords


[docs] @warning_for_keywords() def normalize(image, *, min_v=None, max_v=None, new_min=-1, new_max=1): """ normalization function Parameters ---------- image : np.ndarray Image to be normalized. min_v : int or float, optional minimum value range for normalization intensities below min_v will be clipped if None it is set to min value of image max_v : int or float, optional maximum value range for normalization intensities above max_v will be clipped if None it is set to max value of image new_min : int or float, optional new minimum value after normalization new_max : int or float, optional new maximum value after normalization Returns ------- np.ndarray Normalized image from range new_min to new_max """ if min_v is None: min_v = np.min(image) if max_v is None: max_v = np.max(image) return np.interp(image, (min_v, max_v), (new_min, new_max))
[docs] def unnormalize(image, norm_min, norm_max, min_v, max_v): """ unnormalization function Parameters ---------- image : np.ndarray norm_min : int or float minimum value of normalized image norm_max : int or float maximum value of normalized image min_v : int or float minimum value of unnormalized image max_v : int or float maximum value of unnormalized image Returns ------- np.ndarray unnormalized image from range min_v to max_v """ return (image - norm_min) / (norm_max - norm_min) * (max_v - min_v) + min_v
def set_logger_level(log_level, logger): """Change the logger to one of the following: DEBUG, INFO, WARNING, CRITICAL, ERROR Parameters ---------- log_level : str Log level for the logger """ logger.setLevel(level=log_level)
[docs] @warning_for_keywords() def get_bounds(shape, affine, *, considered_points="corners"): """ Function to get the bounds of an image after applying affine Parameters ---------- shape : tuple (3,) Shape of the image affine : np.ndarray Affine matrix provided by the image file. considered_points : str, optional Considered points when calculating the transformed shape. \"corners\" will consider only corners of the image shape. \"all\" will consider all voxels. Might be needed when shearing is applied. Returns ------- min_bounds : np.ndarray (3,) Minimum bounds of the transformed image max_bounds : np.ndarray (3,) Maximum bounds of the transformed image """ if considered_points == "corners": points = np.array( [ [0, 0, 0, 1], [shape[0] - 1, 0, 0, 1], [0, shape[1] - 1, 0, 1], [0, 0, shape[2] - 1, 1], [shape[0] - 1, shape[1] - 1, shape[2] - 1, 1], [shape[0] - 1, 0, shape[2] - 1, 1], [0, shape[1] - 1, shape[2] - 1, 1], [shape[0] - 1, shape[1] - 1, 0, 1], ], dtype=np.float64, ) elif considered_points == "all": temp1 = np.arange(shape[0]) temp2 = np.arange(shape[1]) temp3 = np.arange(shape[2]) grid1, grid2, grid3 = np.meshgrid(temp1, temp2, temp3) points = np.vstack([grid1.ravel(), grid2.ravel(), grid3.ravel()]).T points = np.hstack([points, np.full((points.shape[0], 1), 1)]) points = points.astype(np.float64) else: ValueError('considered points should be "corners" or "all"') transformed_corners = (affine @ points.T).T min_bounds = transformed_corners.min(axis=0)[:3] max_bounds = transformed_corners.max(axis=0)[:3] return min_bounds, max_bounds
[docs] @warning_for_keywords() def transform_img( image, affine, *, target_voxsize=None, final_size=None, order=3, considered_points="corners", ): """ Function to transform images for Deep Learning models Parameters ---------- image : np.ndarray Image to transform. affine : np.ndarray Affine matrix provided by the image file. target_voxsize : tuple (3,), optional The voxel size we want to start from. If none is provided, we calculate the closest isotropic voxel size. final_size : tuple (3,), optional The final size of the image array. order : int, optional The order of the spline interpolation. The order has to be in the range 0-5. If transforming an int image, order 0 is recommended. considered_points : str, optional Considered points when calculating the transformed shape. \"corners\" will consider only corners of the image shape. \"all\" will consider all voxels. Might be needed when shearing is applied. Returns ------- new_image : np.ndarray Transformed image to be used in the Deep Learning model. params : dict Parameters that are used when recovering the original image space. """ init_shape = image.shape R = affine[:3, :3] voxsize = np.sqrt(np.sum(R * R, axis=0)) if target_voxsize is None: target_voxsize = np.array([np.min(voxsize)] * 3) else: target_voxsize = np.array(target_voxsize, dtype=float) min_bounds, max_bounds = get_bounds( init_shape, affine, considered_points=considered_points ) new_shape = (max_bounds - min_bounds) / target_voxsize new_shape = np.round(new_shape).astype(int) invA = np.linalg.inv(affine) D = np.diag(target_voxsize) new_origin_world = min_bounds matrix = (invA[:3, :3] @ D).astype(float) offset = (invA[:3, 3] + invA[:3, :3] @ new_origin_world).astype(float) new_image = np.zeros(tuple(new_shape), dtype=image.dtype) affine_transform( image, matrix=matrix, offset=offset, output_shape=tuple(new_shape), output=new_image, order=order, ) if final_size is None: final_size = new_image.shape final_size = tuple(int(x) for x in final_size) new_image, pad_vs, crop_vs = pad_crop(new_image, final_size) params = { "init_shape": init_shape, "ori_affine": affine, "target_voxsize": target_voxsize, "new_origin_world": new_origin_world, "pad_value": pad_vs, "crop_value": crop_vs, } return new_image, params
[docs] def recover_img(image, params, *, order=3): """ Function to recover image from transform_img Parameters ---------- image : np.ndarray Image to recover. params : dict Parameters for recover_img function. Returned from transform_img. order : int, optional The order of the spline interpolation. The order has to be in the range 0-5. If recovering an int image, order 0 is recommended. Returns ------- recovered : np.ndarray Recovered image """ expected = { "init_shape", "ori_affine", "target_voxsize", "new_origin_world", "pad_value", "crop_value", } missing = expected - set(params.keys()) if missing: raise ValueError(f"params missing: {missing}") init_shape = tuple(int(x) for x in params["init_shape"]) affine = np.asarray(params["ori_affine"], dtype=float) target_voxsize = np.asarray(params["target_voxsize"], dtype=float) new_origin_world = np.asarray(params["new_origin_world"], dtype=float) pad_vs = params["pad_value"] crop_vs = params["crop_value"] if crop_vs is not None and pad_vs is not None: new_image = inv_pad_crop(image, crop_vs, pad_vs) else: new_image = image A = affine D = np.diag(target_voxsize) coords_idx = np.indices(init_shape, dtype=float) coords_idx = coords_idx.reshape((len(init_shape), -1)) world_coords = (A[:3, :3] @ coords_idx) + A[:3, 3][:, None] coords_new = np.linalg.inv(D) @ (world_coords - new_origin_world[:, None]) coords_for_map = coords_new.reshape((len(init_shape),) + tuple(init_shape)) recovered = map_coordinates(new_image, coords_for_map, order=order) return recovered
[docs] def pad_crop(image, target_shape): """ Function to figure out pad and crop range to fit the target shape with the image Parameters ---------- image : np.ndarray Target image target_shape : (3,) Target shape Returns ------- image : np.ndarray Padded/cropped image pad_vs : np.ndarray (3,2) Pad range used crop_vs : np.ndarray (3,2) Crop range used """ crop_vs = np.zeros((3, 2)).astype(int) pad_vs = np.zeros((3, 2)).astype(int) if np.all(np.array(image.shape) == target_shape): return image, pad_vs, crop_vs pad_crop_v = np.array(target_shape) - np.array(image.shape) for d in range(3): if pad_crop_v[d] < 0: crop_vs[d, 0] = -pad_crop_v[d] // 2 crop_vs[d, 1] = -pad_crop_v[d] - crop_vs[d, 0] elif pad_crop_v[d] > 0: pad_vs[d, 0] = pad_crop_v[d] // 2 pad_vs[d, 1] = pad_crop_v[d] - pad_vs[d, 0] image = np.pad( image, ( (pad_vs[0, 0], pad_vs[0, 1]), (pad_vs[1, 0], pad_vs[1, 1]), (pad_vs[2, 0], pad_vs[2, 1]), ), ) image = image[ crop_vs[0, 0] : image.shape[0] - crop_vs[0, 1], crop_vs[1, 0] : image.shape[1] - crop_vs[1, 1], crop_vs[2, 0] : image.shape[2] - crop_vs[2, 1], ] return image, pad_vs, crop_vs
[docs] def inv_pad_crop(image, crop_vs, pad_vs): """ Function to figure out pad and crop range to fit the target shape with the image Parameters ---------- image : np.ndarray Target image crop_vs : np.ndarray (3,2) Crop range used when padding/cropping pad_vs : np.ndarray (3,2) Pad range used when padding/cropping Returns ------- image : np.ndarray Recovered image """ crop_vs = crop_vs.astype(int) pad_vs = pad_vs.astype(int) image = np.pad( image, ( (crop_vs[0, 0], crop_vs[0, 1]), (crop_vs[1, 0], crop_vs[1, 1]), (crop_vs[2, 0], crop_vs[2, 1]), ), ) image = image[ pad_vs[0, 0] : image.shape[0] - pad_vs[0, 1], pad_vs[1, 0] : image.shape[1] - pad_vs[1, 1], pad_vs[2, 0] : image.shape[2] - pad_vs[2, 1], ] return image