def leave_feeding_trace(x, y, shape, trace_strength=1.0, sigma=7, mode="wrap", wrap_around=True, truncate=4): """Turns x,y-coordinates returned by a list of calls to init-functions into a smooth feeding array.""" base = np.zeros(shape) if wrap_around == True: x = x % shape[0] y = y % shape[1] else: print("CutOff overly large x and y: Not yet implemented.") base[np.floor(x % (base.shape[0])).astype(int), np.floor(y % (base.shape[1])).astype(int), ] = trace_strength base = gaussian_filter(base, sigma=sigma, mode=mode, truncate=truncate) return base
def diffuse_gaussian(self, sigma=7, mode="wrap", truncate=4): """Pheromones get distributed using gaussian smoothing.""" self.trail_map = gaussian_filter(self.trail_map, sigma=sigma, mode=mode, truncate=truncate)
def __init__(self, image, factors, sigmas, image_grid2world=None, input_spacing=None, mask0=False): """ IsotropicScaleSpace Computes the Scale Space representation of an image using isotropic smoothing kernels for all scales. The scale space is simply a list of images produced by smoothing the input image with a Gaussian kernel with different smoothing parameters. This specialization of ScaleSpace allows the user to provide custom scale and smoothing factors for all scales. Parameters ---------- image : array, shape (r,c) or (s, r, c) where s is the number of slices, r is the number of rows and c is the number of columns of the input image. factors : list of floats custom scale factors to build the scale space (one factor for each scale). sigmas : list of floats custom smoothing parameter to build the scale space (one parameter for each scale). image_grid2world : array, shape (dim + 1, dim + 1), optional the grid-to-space transform of the image grid. The default is the identity matrix. input_spacing : array, shape (dim,), optional the spacing (voxel size) between voxels in physical space. The default if 1.0 along all axes. mask0 : Boolean, optional if True, all smoothed images will be zero at all voxels that are zero in the input image. The default is False. """ self.dim = len(image.shape) self.num_levels = len(factors) if len(sigmas) != self.num_levels: raise ValueError("sigmas and factors must have the same length") input_size = np.array(image.shape) if mask0: mask = cp.asarray(image > 0, dtype=np.int32) # Normalize input image to [0,1] img = (image.astype(cp.float64) - image.min()) / cp.ptp(image) if mask0: img *= mask # The properties are saved in separate lists. Insert input image # properties at the first level of the scale space self.images = [img.astype(floating)] self.domain_shapes = [input_size.astype(np.int32)] if input_spacing is None: input_spacing = np.ones((self.dim, ), dtype=np.int32) elif isinstance(input_spacing, cp.ndarray): input_spacing = cp.asnumpy(input_spacing) self.spacings = [input_spacing] self.scalings = [np.ones(self.dim)] self.sigmas = [np.ones(self.dim) * sigmas[self.num_levels - 1]] if image_grid2world is not None: if isinstance(image_grid2world, cp.ndarray): image_grid2world = cp.asnumpy(image_grid2world) self.affines = [image_grid2world] self.affine_invs = [np.linalg.inv(image_grid2world)] else: self.affines = [None] self.affine_invs = [None] # Compute the rest of the levels min_index = np.argmin(input_spacing) for i in range(1, self.num_levels): factor = factors[self.num_levels - 1 - i] shrink_factors = np.zeros(self.dim) new_spacing = np.zeros(self.dim) shrink_factors[min_index] = factor new_spacing[min_index] = input_spacing[min_index] * factor for j in range(self.dim): if j != min_index: # Select the factor that maximizes isotropy shrink_factors[j] = factor new_spacing[j] = input_spacing[j] * factor min_diff = np.abs(new_spacing[j] - new_spacing[min_index]) for f in range(1, factor): diff = input_spacing[j] * f - new_spacing[min_index] diff = np.abs(diff) if diff < min_diff: shrink_factors[j] = f new_spacing[j] = input_spacing[j] * f min_diff = diff extended = np.append(shrink_factors, [1]) if image_grid2world is not None: affine = image_grid2world.dot(np.diag(extended)) else: affine = np.diag(extended) output_size = (input_size / shrink_factors).astype(np.int32) new_sigmas = np.ones(self.dim) * sigmas[self.num_levels - i - 1] # Filter along each direction with the appropriate sigma filtered = filters.gaussian_filter(image.astype(np.float64), new_sigmas) filtered = (filtered.astype(np.float64) - filtered.min()) / cp.ptp(filtered) if mask0: filtered *= mask # Add current level to the scale space self.images.append(filtered.astype(floating)) self.domain_shapes.append(output_size) self.spacings.append(new_spacing) self.scalings.append(shrink_factors) self.affines.append(affine) self.affine_invs.append(np.linalg.inv(affine)) self.sigmas.append(new_sigmas)
def structure_tensor_3d(volume, sigma, rho, out=None): """Structure tensor for 3D image data. Arguments: volume: array_like A 3D array. Pass cupy.ndarray to avoid copying volume. sigma: scalar A noise scale, structures smaller than sigma will be removed by smoothing. rho: scalar An integration scale giving the size over the neighborhood in which the orientation is to be analysed. out: cupy.ndarray, optional An array with the shape ``(6, volume.shape)`` in which to place the output. Returns: S: cupy.ndarray An array with shape ``(6, volume.shape)`` containing elements of structure tensor (s_xx, s_yy, s_zz, s_xy, s_xz, s_yz). Authors: [email protected], 2019; [email protected], 2019-2020 """ # Make sure it's a Numpy array. volume = np.asarray(volume) # Computing derivatives (scipy implementation truncates filter at 4 sigma). Vx = filters.gaussian_filter(volume, sigma, order=[0, 0, 1], mode='nearest') Vy = filters.gaussian_filter(volume, sigma, order=[0, 1, 0], mode='nearest') Vz = filters.gaussian_filter(volume, sigma, order=[1, 0, 0], mode='nearest') if out is None: # Allocate S. S = np.empty((6, ) + volume.shape, dtype=volume.dtype) else: # S is already allocated. We assume the size is correct. S = out # Integrating elements of structure tensor (scipy uses sequence of 1D). tmp = np.empty(volume.shape, dtype=volume.dtype) np.multiply(Vx, Vx, out=tmp) filters.gaussian_filter(tmp, rho, mode='nearest', output=S[0]) np.multiply(Vy, Vy, out=tmp) filters.gaussian_filter(tmp, rho, mode='nearest', output=S[1]) np.multiply(Vz, Vz, out=tmp) filters.gaussian_filter(tmp, rho, mode='nearest', output=S[2]) np.multiply(Vx, Vy, out=tmp) filters.gaussian_filter(tmp, rho, mode='nearest', output=S[3]) np.multiply(Vx, Vz, out=tmp) filters.gaussian_filter(tmp, rho, mode='nearest', output=S[4]) np.multiply(Vy, Vz, out=tmp) filters.gaussian_filter(tmp, rho, mode='nearest', output=S[5]) return S
def __init__(self, image, num_levels, image_grid2world=None, input_spacing=None, sigma_factor=0.2, mask0=False): """ ScaleSpace Computes the Scale Space representation of an image. The scale space is simply a list of images produced by smoothing the input image with a Gaussian kernel with increasing smoothing parameter. If the image's voxels are isotropic, the smoothing will be the same along all directions: at level L = 0, 1, ..., the sigma is given by $s * ( 2^L - 1 )$. If the voxel dimensions are not isotropic, then the smoothing is weaker along low resolution directions. Parameters ---------- image : array, shape (r,c) or (s, r, c) where s is the number of slices, r is the number of rows and c is the number of columns of the input image. num_levels : int the desired number of levels (resolutions) of the scale space image_grid2world : array, shape (dim + 1, dim + 1), optional the grid-to-space transform of the image grid. The default is the identity matrix input_spacing : array, shape (dim,), optional the spacing (voxel size) between voxels in physical space. The default is 1.0 along all axes sigma_factor : float, optional the smoothing factor to be used in the construction of the scale space. The default is 0.2 mask0 : Boolean, optional if True, all smoothed images will be zero at all voxels that are zero in the input image. The default is False. """ self.dim = len(image.shape) self.num_levels = num_levels input_size = np.array(image.shape) if mask0: mask = cp.asarray(image > 0, dtype=np.int32) # Normalize input image to [0,1] img = (image - image.min()) / cp.ptp(image) if mask0: img *= mask # The properties are saved in separate lists. Insert input image # properties at the first level of the scale space self.images = [img.astype(floating)] self.domain_shapes = [input_size.astype(np.int32)] # Note: Keep small arrays like spacings, scalings, sigmas on # the host. if input_spacing is None: input_spacing = np.ones((self.dim, ), dtype=np.int32) elif isinstance(input_spacing, cp.ndarray): input_spacing = cp.asnumpy(input_spacing) self.spacings = [input_spacing] self.scalings = [np.ones(self.dim)] self.sigmas = [np.zeros(self.dim)] # also keep affines on the host for now if image_grid2world is not None: if isinstance(image_grid2world, cp.ndarray): image_grid2world = cp.asnumpy(image_grid2world) self.affines = [image_grid2world] self.affine_invs = [np.linalg.inv(image_grid2world)] else: self.affines = [None] self.affine_invs = [None] # Compute the rest of the levels min_spacing = cp.min(input_spacing) for i in range(1, num_levels): scaling_factor = 2**i # Note: the minimum below is present in ANTS to prevent the scaling # from being too large (making the sub-sampled image to be too # small) this makes the sub-sampled image at least 32 voxels at # each direction it is risky to make this decision based on image # size, though (we need to investigate more the effect of this) # scaling = np.minimum(scaling_factor * min_spacing /input_spacing, # input_size / 32) scaling = scaling_factor * min_spacing / input_spacing output_spacing = input_spacing * scaling extended = np.append(scaling, [1]) if image_grid2world is not None: affine = image_grid2world.dot(np.diag(extended)) else: affine = np.diag(extended) output_size = input_size * (input_spacing / output_spacing) + 0.5 output_size = output_size.astype(np.int32) sigmas = sigma_factor * (output_spacing / input_spacing - 1.0) # Filter along each direction with the appropriate sigma filtered = filters.gaussian_filter(image, sigmas) filtered = (filtered - filtered.min()) / cp.ptp(filtered) if mask0: filtered *= mask # Add current level to the scale space self.images.append(filtered.astype(floating)) self.domain_shapes.append(output_size) self.spacings.append(output_spacing) self.scalings.append(scaling) self.affines.append(affine) self.affine_invs.append(np.linalg.inv(affine)) self.sigmas.append(sigmas)