def gaussian_filter(input, sigma, order=0, output=None, mode="reflect", cval=0.0, truncate=4.0): """Multidimensional Gaussian filter. Parameters ---------- %(input)s sigma : scalar or sequence of scalars Standard deviation for Gaussian kernel. The standard deviations of the Gaussian filter are given for each axis as a sequence, or as a single number, in which case it is equal for all axes. order : {0, 1, 2, 3} or sequence from same set, optional The order of the filter along each axis is given as a sequence of integers, or as a single number. An order of 0 corresponds to convolution with a Gaussian kernel. An order of 1, 2, or 3 corresponds to convolution with the first, second or third derivatives of a Gaussian. Higher order derivatives are not implemented %(output)s %(mode)s %(cval)s truncate : float Truncate the filter at this many standard deviations. Default is 4.0. Returns ------- gaussian_filter : ndarray Returned array of same shape as `input`. Notes ----- The multidimensional filter is implemented as a sequence of one-dimensional convolution filters. The intermediate arrays are stored in the same data type as the output. Therefore, for output types with a limited precision, the results may be imprecise because intermediate results may be stored with insufficient precision. """ input = numpy.asarray(input) output, return_value = _ni_support._get_output(output, input) orders = _ni_support._normalize_sequence(order, input.ndim) if not set(orders).issubset(set(range(4))): raise ValueError('Order outside 0..4 not implemented') sigmas = _ni_support._normalize_sequence(sigma, input.ndim) axes = list(range(input.ndim)) axes = [(axes[ii], sigmas[ii], orders[ii]) for ii in range(len(axes)) if sigmas[ii] > 1e-15] if len(axes) > 0: for axis, sigma, order in axes: gaussian_filter1d(input, sigma, axis, order, output, mode, cval, truncate) input = output else: output[...] = input[...] return return_value
def rolling_ball_filter(data, ball_radius, spacing=None, top=False, **kwargs): """Rolling ball filter implemented with morphology operations This implenetation is very similar to that in ImageJ and uses a top hat transform with a ball shaped structuring element https://en.wikipedia.org/wiki/Top-hat_transform Parameters ---------- data : ndarray image data (assumed to be on a regular grid) ball_radius : float the radius of the ball to roll spacing : int or sequence the spacing of the image data top : bool whether to roll the ball on the top or bottom of the data kwargs : key word arguments these are passed to the ndimage morphological operations Returns ------- data_nb : ndarray data with background subtracted bg : ndarray background that was subtracted from the data """ ndim = data.ndim if spacing is None: spacing = np.ones_like(ndim) else: spacing = _normalize_sequence(spacing, ndim) radius = np.asarray(_normalize_sequence(ball_radius, ndim)) mesh = np.array( np.meshgrid( *[np.arange(-r, r + s, s) for r, s in zip(radius, spacing)], indexing="ij")) structure = 2 * np.sqrt(1 - ((mesh / radius.reshape(-1, *( (1, ) * ndim)))**2).sum(0)) structure[~np.isfinite(structure)] = 0 if not top: # ndi.white_tophat(y, structure=structure, output=background) background = ndi.grey_erosion(data, structure=structure, **kwargs) background = ndi.grey_dilation(background, structure=structure, **kwargs) else: # ndi.black_tophat(y, structure=structure, output=background) background = ndi.grey_dilation(data, structure=structure, **kwargs) background = ndi.grey_erosion(background, structure=structure, **kwargs) return data - background, background
def rolling_ball_filter(data, ball_radius, spacing=None, top=False, **kwargs): """Rolling ball filter implemented with morphology operations This implenetation is very similar to that in ImageJ and uses a top hat transform with a ball shaped structuring element https://en.wikipedia.org/wiki/Top-hat_transform Parameters ---------- data : ndarray type uint8 image data (assumed to be on a regular grid) ball_radius : float the radius of the ball to roll spacing : int or sequence the spacing of the image data top : bool whether to roll the ball on the top or bottom of the data kwargs : key word arguments these are passed to the ndimage morphological operations Returns ------- data_nb : ndarray data with background subtracted as uint8 bg : ndarray background that was subtracted from the data """ data = data.astype(np.int16) ndim = data.ndim if spacing is None: spacing = _normalize_sequence(1, ndim) else: spacing = _normalize_sequence(spacing, ndim) radius = np.asarray(_normalize_sequence(ball_radius, ndim)) mesh = np.array(np.meshgrid(*[np.arange(-r, r + s, s) for r, s in zip(radius, spacing)], indexing="ij")) structure = 2 * np.sqrt(2 - ((mesh / radius.reshape(-1, *((1,) * ndim)))**2).sum(0)) structure[~np.isfinite(structure)] = 0 if not top: # ndi.white_tophat(y, structure=structure, output=background) background = ndi.grey_erosion(data, structure=structure, **kwargs) background = ndi.grey_dilation(background, structure=structure, **kwargs) else: # ndi.black_tophat(y, structure=structure, output=background) background = ndi.grey_dilation(data, structure=structure, **kwargs) background = ndi.grey_erosion(background, structure=structure, **kwargs) data_corr = data - background data_corr[data_corr<0] = 0 return data_corr.astype(np.uint8), background.astype(np.uint8)
def __surface_distances(result, reference, voxelspacing=None, connectivity=1): """ The distances between the surface voxel of binary objects in result and their nearest partner surface voxel of a binary object in reference. """ result = numpy.atleast_1d(result.astype(numpy.bool)) reference = numpy.atleast_1d(reference.astype(numpy.bool)) if voxelspacing is not None: voxelspacing = _ni_support._normalize_sequence(voxelspacing, result.ndim) voxelspacing = numpy.asarray(voxelspacing, dtype=numpy.float64) if not voxelspacing.flags.contiguous: voxelspacing = voxelspacing.copy() # binary structure footprint = generate_binary_structure(result.ndim, connectivity) # test for emptiness if 0 == numpy.count_nonzero(result): raise RuntimeError('The first supplied array does not contain any binary object.') if 0 == numpy.count_nonzero(reference): raise RuntimeError('The second supplied array does not contain any binary object.') # extract only 1-pixel border line of objects result_border = result ^ binary_erosion(result, structure=footprint, iterations=1) reference_border = reference ^ binary_erosion(reference, structure=footprint, iterations=1) # compute average surface distance # Note: scipys distance transform is calculated only inside the borders of the # foreground objects, therefore the input has to be reversed dt = distance_transform_edt(~reference_border, sampling=voxelspacing) sds = dt[result_border] return sds
def __surface_distances(input1, input2, voxelspacing=None, connectivity=1): """ The distances between the surface voxel of binary objects in input1 and their nearest partner surface voxel of a binary object in input2. """ input1 = numpy.atleast_1d(input1.astype(numpy.bool)) input2 = numpy.atleast_1d(input2.astype(numpy.bool)) if voxelspacing is not None: voxelspacing = _ni_support._normalize_sequence(voxelspacing, input1.ndim) voxelspacing = numpy.asarray(voxelspacing, dtype=numpy.float64) if not voxelspacing.flags.contiguous: voxelspacing = voxelspacing.copy() # binary structure footprint = generate_binary_structure(input1.ndim, connectivity) # extract only 1-pixel border line of objects input1_border = input1 - binary_erosion(input1, structure=footprint, iterations=1) input2_border = input2 - binary_erosion(input2, structure=footprint, iterations=1) # compute average surface distance # Note: scipys distance transform is calculated only inside the borders of the # foreground objects, therefore the input has to be reversed dt = distance_transform_edt(~input2_border, sampling=voxelspacing) sds = dt[input1_border] return sds
def _surface_distances(input1, input2, voxelspacing=None, connectivity=1): input1 = np.atleast_1d(input1.astype(bool)) input2 = np.atleast_1d(input2.astype(bool)) if voxelspacing is not None: voxelspacing = _ni_support._normalize_sequence(voxelspacing, input1.ndim) voxelspacing = np.asarray(voxelspacing, dtype=np.float64) if not voxelspacing.flags.contiguous: voxelspacing = voxelspacing.copy() if 0 == np.count_nonzero(input1): raise RuntimeError( 'The first supplied array does not contain any binary object.') if 0 == np.count_nonzero(input2): raise RuntimeError( 'The second supplied array does not contain any binary object.') # binary structure, used by binary_erosion() footprint = generate_binary_structure(input1.ndim, connectivity) # extract only 1-pixel border line of objects input1_border = input1 - binary_erosion( input1, structure=footprint, iterations=1) input2_border = input2 - binary_erosion( input2, structure=footprint, iterations=1) # compute average surface distance # Note: scipy's distance transform is calculated only inside the borders of the # foreground objects, therefore the input has to be reversed dt = distance_transform_edt(~input2_border, sampling=voxelspacing) sds = dt[input1_border] return sds
def square_gaussian_filter(input, sigma, output = None, mode = "reflect", cval = 0.0): """Multi-dimensional Squared Gaussian filter. The standard-deviations of the Gaussian filter are given for each axis as a sequence, or as a single number, in which case it is equal for all axes. Note: The multi-dimensional filter is implemented as a sequence of one-dimensional convolution filters. The intermediate arrays are stored in the same data type as the output. Therefore, for output types with a limited precision, the results may be imprecise because intermediate results may be stored with insufficient precision. """ input = np.asarray(input) output, return_value =_ni_support._get_output(output, input) sigmas =_ni_support._normalize_sequence(sigma, input.ndim) axes = range(input.ndim) axes = [(axes[ii], sigmas[ii]) for ii in range(len(axes)) if sigmas[ii] > 1e-15] if len(axes) > 0: for axis, sigma in axes: square_gaussian_filter1d(input, sigma, axis, output, mode, cval) input = output else: output[...] = input[...] return return_value
def square_gaussian_filter(input, sigma, output=None, mode="reflect", cval=0.0): """Multi-dimensional Squared Gaussian filter. The standard-deviations of the Gaussian filter are given for each axis as a sequence, or as a single number, in which case it is equal for all axes. Note: The multi-dimensional filter is implemented as a sequence of one-dimensional convolution filters. The intermediate arrays are stored in the same data type as the output. Therefore, for output types with a limited precision, the results may be imprecise because intermediate results may be stored with insufficient precision. """ input = np.asarray(input) output, return_value = _ni_support._get_output(output, input) sigmas = _ni_support._normalize_sequence(sigma, input.ndim) axes = range(input.ndim) axes = [(axes[ii], sigmas[ii]) for ii in range(len(axes)) if sigmas[ii] > 1e-15] if len(axes) > 0: for axis, sigma in axes: square_gaussian_filter1d(input, sigma, axis, output, mode, cval) input = output else: output[...] = input[...] return return_value
def __surface_distances(result, reference, voxelspacing=None, connectivity=1): """ The distances between the surface voxel of binary objects in result and their nearest partner surface voxel of a binary object in reference. """ result = numpy.atleast_1d(result.astype(numpy.bool)) reference = numpy.atleast_1d(reference.astype(numpy.bool)) if voxelspacing is not None: voxelspacing = _ni_support._normalize_sequence(voxelspacing, result.ndim) voxelspacing = numpy.asarray(voxelspacing, dtype=numpy.float64) if not voxelspacing.flags.contiguous: voxelspacing = voxelspacing.copy() # binary structure footprint = generate_binary_structure(result.ndim, connectivity) # test for emptiness if 0 == numpy.count_nonzero(result): raise RuntimeError('The first supplied array does not contain any binary object.') if 0 == numpy.count_nonzero(reference): raise RuntimeError('The second supplied array does not contain any binary object.') # extract only 1-pixel border line of objects result_border = result - binary_erosion(result, structure=footprint, iterations=1) reference_border = reference - binary_erosion(reference, structure=footprint, iterations=1) # compute average surface distance # Note: scipys distance transform is calculated only inside the borders of the # foreground objects, therefore the input has to be reversed dt = distance_transform_edt(~reference_border, sampling=voxelspacing) sds = dt[result_border] return sds
def _correlate_or_convolve(input, weights, output, mode, cval, origin, convolution): input = np.asarray(input) if np.iscomplexobj(input): raise TypeError('Complex type not supported') origins = _ni_support._normalize_sequence(origin, input.ndim) weights = np.asarray(weights, dtype=np.float64) wshape = [ii for ii in weights.shape if ii > 0] if len(wshape) != input.ndim: raise RuntimeError('filter weights array has incorrect shape.') if convolution: weights = weights[tuple([slice(None, None, -1)] * weights.ndim)] for ii in range(len(origins)): origins[ii] = -origins[ii] if not weights.shape[ii] & 1: origins[ii] -= 1 for origin, lenw in zip(origins, wshape): if _invalid_origin(origin, lenw): raise ValueError('Invalid origin; origin must satisfy ' '-(weights.shape[k] // 2) <= origin[k] <= ' '(weights.shape[k]-1) // 2') if not weights.flags.contiguous: weights = weights.copy() output = _ni_support._get_output(output, input) mode = _ni_support._extend_mode_to_code(mode) _nd_image.correlate(input, weights, output, mode, cval, origins) return output
def sinc_filter(input, sigma, order=0, output=None, mode="reflect", cval=0.0, truncate=6.0): input = np.asarray(input) output = _ni_support._get_output(output, input) orders = _ni_support._normalize_sequence(order, input.ndim) sigmas = _ni_support._normalize_sequence(sigma, input.ndim) modes = _ni_support._normalize_sequence(mode, input.ndim) axes = list(range(input.ndim)) axes = [(axes[ii], sigmas[ii], orders[ii], modes[ii]) for ii in range(len(axes)) if sigmas[ii] > 1e-15] if len(axes) > 0: for axis, sigma, order, mode in axes: sinc_filter1d(input, sigma, axis, order, output, mode, cval, truncate) input = output else: output[...] = input[...] return output
def __make_footprint(input, size, footprint): "Creates a standard footprint element ala scipy.ndimage." if footprint is None: if size is None: raise RuntimeError("no footprint or filter size provided") sizes = _ni_support._normalize_sequence(size, input.ndim) footprint = numpy.ones(sizes, dtype=bool) else: footprint = numpy.asarray(footprint, dtype=bool) return footprint
def slice_maker(xs, ws): """ A utility function to generate slices for later use. Parameters ---------- y0 : int center y position of the slice x0 : int center x position of the slice width : int Width of the slice Returns ------- slices : list A list of slice objects, the first one is for the y dimension and and the second is for the x dimension. Notes ----- The method will automatically coerce slices into acceptable bounds. Examples -------- >>> slice_maker((30,20),10) [slice(25, 35, None), slice(15, 25, None)] >>> slice_maker((30,20),25) [slice(18, 43, None), slice(8, 33, None)] """ # normalize inputs xs = np.asarray(xs) ws = np.asarray(_normalize_sequence(ws, len(xs))) if not np.isrealobj((xs, ws)): raise TypeError("`slice_maker` only accepts real input") if np.any(ws < 0): raise ValueError("width cannot be negative, width = {}".format(ws)) # ensure integers xs = np.rint(xs).astype(int) ws = np.rint(ws).astype(int) # use _calc_pad toreturn = [] for x, w in zip(xs, ws): half2, half1 = _calc_pad(0, w) xstart = x - half1 xend = x + half2 assert xstart <= xend, "xstart > xend" if xend <= 0: xstart, xend = 0, 0 # the max calls are to make slice_maker play nice with edges. toreturn.append(slice(max(0, xstart), xend)) # return a list of slices return tuple(toreturn)
def gaussian_filter(array: np.ndarray, sigma: float, order: int = 0, mode='constant', cval: float = 0.0, truncate: float = 4.0, radius: int = None, points: np.ndarray = None, nonzero: bool = False): orders = _ni_support._normalize_sequence(order, array.ndim) sigmas = _ni_support._normalize_sequence(sigma, array.ndim) modes = _ni_support._normalize_sequence(mode, array.ndim) axes = list(range(array.ndim)) axes = [(axes[ii], sigmas[ii], orders[ii], modes[ii]) for ii in range(len(axes)) if sigmas[ii] > 1e-15] if len(axes) > 0: for axis, sigma, order, mode in axes: array = gaussian_filter1d(array, sigma, axis, order, mode, cval, truncate, radius, points, nonzero) return array
def rolling_ball_filter(self, data, ball_radius, spacing=None, top=False, **kwargs): data = data.astype(np.int16) ndim = data.ndim if spacing is None: spacing = _normalize_sequence(1, ndim) else: spacing = _normalize_sequence(spacing, ndim) radius = np.asarray(_normalize_sequence(ball_radius, ndim)) mesh = np.array( np.meshgrid( *[np.arange(-r, r + s, s) for r, s in zip(radius, spacing)], indexing="ij")) structure = np.uint8(2 * np.sqrt( np.absolute(2 - ((mesh / radius.reshape(-1, *( (1, ) * ndim)))**2).sum(0)))) structure[~np.isfinite(structure)] = 0 if not top: # ndi.white_tophat(y, structure=structure, output=background) background = cv.erode(data, structure, **kwargs) background = cv.dilate(background, structure, **kwargs) else: # ndi.black_tophat(y, structure=structure, output=background) background = cv.dilate(data, structure, **kwargs) background = cv.erode(background, structure, **kwargs) data_corr = data - background data_corr[data_corr < 0] = 0 return data_corr.astype(np.uint8), background.astype(np.uint8)
def __surface_distances(result, reference, voxelspacing=None, connectivity=1): """ The distances between the surface voxel of binary objects in result and their nearest partner surface voxel of a binary object in reference. Adopted from https://github.com/loli/medpy/blob/39131b94f0ab5328ab14a874229320efc2f74d98/medpy/metric/binary.py#L1195 """ result = numpy.atleast_1d(result.astype(numpy.bool)) reference = numpy.atleast_1d(reference.astype(numpy.bool)) if voxelspacing is not None: voxelspacing = _ni_support._normalize_sequence(voxelspacing, result.ndim) voxelspacing = numpy.asarray(voxelspacing, dtype=numpy.float64) if not voxelspacing.flags.contiguous: voxelspacing = voxelspacing.copy() # binary structure footprint = generate_binary_structure(result.ndim, connectivity) # test for emptiness if 0 == numpy.count_nonzero(result): # print( # "The first supplied array does not contain any binary object.", # file=sys.stderr, # ) return 0 if 0 == numpy.count_nonzero(reference): # print( # "The second supplied array does not contain any binary object.", # file=sys.stderr, # ) return 0 # extract only 1-pixel border line of objects result_border = result ^ binary_erosion( result, structure=footprint, iterations=1) reference_border = reference ^ binary_erosion( reference, structure=footprint, iterations=1) # compute average surface distance # Note: scipys distance transform is calculated only inside the borders of the # foreground objects, therefore the input has to be reversed dt = distance_transform_edt(~reference_border, sampling=voxelspacing) sds = dt[result_border] return sds
def surface_distances(input1, input2, voxelspacing=None, connectivity=1.0): """ http://pythonhosted.org/MedPy/_modules/medpy/metric/binary.html The distances between the surface voxel of binary objects in input1 and their nearest partner surface voxel of a binary object in input2. """ input1 = np.atleast_1d(input1.astype(np.bool)) input2 = np.atleast_1d(input2.astype(np.bool)) if voxelspacing is not None: voxelspacing = _ni_support._normalize_sequence(voxelspacing, input1.ndim) voxelspacing = np.asarray(voxelspacing, dtype=np.float64) if not voxelspacing.flags.contiguous: voxelspacing = voxelspacing.copy() # binary structure footprint = generate_binary_structure(input1.ndim, connectivity) # test for emptiness if 0 == np.count_nonzero(input1): return np.array([ float("inf") ]) # Can´t calculate the hausdorff distance if there is no object if 0 == np.count_nonzero(input2): return np.array([ float("inf") ]) # Can´t calculate the hausdorff distance if there is no object # extract only 1-pixel border line of objects input1_border = input1 ^ binary_erosion( input1, structure=footprint, iterations=1) input2_border = input2 ^ binary_erosion( input2, structure=footprint, iterations=1) # compute average surface distance # Note: scipys distance transform is calculated only inside the borders of the # foreground objects, therefore the input has to be reversed dt = distance_transform_edt(~input2_border, sampling=voxelspacing) sds = dt[input1_border] return sds
def __surface_distances(input1, input2, voxelspacing=None, connectivity=1): """ The distances between the surface voxel of binary objects in input1 and their nearest partner surface voxel of a binary object in input2. """ input1 = numpy.atleast_1d(input1.astype(numpy.bool)) input2 = numpy.atleast_1d(input2.astype(numpy.bool)) if voxelspacing is not None: voxelspacing = _ni_support._normalize_sequence(voxelspacing, input1.ndim) voxelspacing = numpy.asarray(voxelspacing, dtype=numpy.float64) if not voxelspacing.flags.contiguous: voxelspacing = voxelspacing.copy() # binary structure footprint = generate_binary_structure(input1.ndim, connectivity) # test for emptiness # if 0 == numpy.count_nonzero(input1): # raise RuntimeError('The first supplied array does not contain any binary object.') # if 0 == numpy.count_nonzero(input2): # raise RuntimeError('The second supplied array does not contain any binary object.') if numpy.count_nonzero(input1) == 0 or numpy.count_nonzero(input2) == 0: return numpy.max(numpy.shape(input1)) # extract only 1-pixel border line of objects input1_border = input1 - binary_erosion( input1, structure=footprint, iterations=1) input2_border = input2 - binary_erosion( input2, structure=footprint, iterations=1) # compute average surface distance # Note: scipys distance transform is calculated only inside the borders of the # foreground objects, therefore the input has to be reversed dt = distance_transform_edt(~input2_border, sampling=voxelspacing) sds = dt[input1_border] return sds
def smo(input, sigma, size): """Applies the Silver Mountain Operator (SMO) to a scalar field. Parameters ---------- input : numpy.array Input field. sigma : scalar or sequence of scalars Standard deviation for Gaussian kernel. size : int or sequence of int Averaging window size. Returns ------- numpy.array """ size = _normalize_sequence(size, input.ndim) input = ndimage.gaussian_filter(input.astype(float, copy=False), sigma=sigma) norm_grad = normalized_gradient(input) sliding_mean = ndimage.uniform_filter(norm_grad, size=(1, *size)) magnitude = np.linalg.norm(sliding_mean, axis=0) return magnitude
def pad(input, size=None, footprint=None, output=None, mode="reflect", cval=0.0): """ Returns a copy of the input, padded by the supplied structuring element. In the case of odd dimensionality, the structure element will be centered as following on the currently processed position: [[T, Tx, T], [T, T , T]] , where Tx denotes the center of the structure element. Simulates the behaviour of scipy.ndimage filters. input : array_like Input array to pad. size : scalar or tuple, optional See footprint, below footprint : array, optional Either `size` or `footprint` must be defined. `size` gives the shape that is taken from the input array, at every element position, to define the input to the filter function. `footprint` is a boolean array that specifies (implicitly) a shape, but also which of the elements within this shape will get passed to the filter function. Thus ``size=(n,m)`` is equivalent to ``footprint=np.ones((n,m))``. We adjust `size` to the number of dimensions of the input array, so that, if the input array is shape (10,10,10), and `size` is 2, then the actual size used is (2,2,2). output : array, optional The `output` parameter passes an array in which to store the filter output. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional The `mode` parameter determines how the array borders are handled, where `cval` is the value when mode is equal to 'constant'. Default is 'reflect'. cval : scalar, optional Value to fill past edges of input if `mode` is 'constant'. Default is 0.0 """ input = numpy.asarray(input) if footprint is None: if size is None: raise RuntimeError("no footprint or filter size provided") sizes = _ni_support._normalize_sequence(size, input.ndim) footprint = numpy.ones(sizes, dtype=bool) else: footprint = numpy.asarray(footprint, dtype=bool) fshape = [ii for ii in footprint.shape if ii > 0] if len(fshape) != input.ndim: raise RuntimeError('filter footprint array has incorrect shape.') padding_offset = [((s - 1) / 2, s / 2) for s in fshape] input_slicer = [slice(l, None if 0 == r else -1 * r) for l, r in padding_offset] output_shape = [s + sum(os) for s, os in zip(input.shape, padding_offset)] output, return_value = _ni_support._get_output(output, input, output_shape) if 'constant' == mode: output += cval output[input_slicer] = input return return_value elif 'nearest' == mode: output[input_slicer] = input dim_mult_slices = [(d, l, slice(None, l), slice(l, l + 1)) for d, (l, _) in zip(range(output.ndim), padding_offset) if not 0 == l] dim_mult_slices.extend([(d, r, slice(-1 * r, None), slice(-2 * r, -2 * r + 1)) for d, (_, r) in zip(range(output.ndim), padding_offset) if not 0 == r]) for dim, mult, to_slice, from_slice in dim_mult_slices: slicer_to = [to_slice if d == dim else slice(None) for d in range(output.ndim)] slicer_from = [from_slice if d == dim else slice(None) for d in range(output.ndim)] if not 0 == mult: output[slicer_to] = numpy.concatenate([output[slicer_from]] * mult, dim) return return_value elif 'mirror' == mode: dim_slices = [(d, slice(None, l), slice(l + 1, 2 * l + 1)) for d, (l, _) in zip(range(output.ndim), padding_offset) if not 0 == l] dim_slices.extend([(d, slice(-1 * r, None), slice(-2 * r - 1, -1 * r - 1)) for d, (_, r) in zip(range(output.ndim), padding_offset) if not 0 == r]) reverse_slice = slice(None, None, -1) elif 'reflect' == mode: dim_slices = [(d, slice(None, l), slice(l, 2 * l)) for d, (l, _) in zip(range(output.ndim), padding_offset) if not 0 == l] dim_slices.extend([(d, slice(-1 * r, None), slice(-2 * r, -1 * r)) for d, (_, r) in zip(range(output.ndim), padding_offset) if not 0 == r]) reverse_slice = slice(None, None, -1) elif 'wrap' == mode: dim_slices = [(d, slice(None, l), slice(-1 * (l + r), -1 * r if not 0 == r else None)) for d, (l, r) in zip(range(output.ndim), padding_offset) if not 0 == l] dim_slices.extend([(d, slice(-1 * r, None), slice(l, r + l)) for d, (l, r) in zip(range(output.ndim), padding_offset) if not 0 == r]) reverse_slice = slice(None) else: raise RuntimeError('boundary mode not supported') output[input_slicer] = input for dim, to_slice, from_slice in dim_slices: slicer_reverse = [reverse_slice if d == dim else slice(None) for d in range(output.ndim)] slicer_to = [to_slice if d == dim else slice(None) for d in range(output.ndim)] slicer_from = [from_slice if d == dim else slice(None) for d in range(output.ndim)] output[slicer_to] = output[slicer_from][slicer_reverse] return return_value
def distance_transform_edt_float32( input, sampling=None, return_distances=True, return_indices=False, distances=None, indices=None, ): """ The same as scipy.ndimage.morphology.distance_transform_edt but using float32 and better memory cleaning internally. In addition to the distance transform, the feature transform can be calculated. In this case the index of the closest background element is returned along the first axis of the result. Parameters ---------- input : array_like Input data to transform. Can be any type but will be converted into binary: 1 wherever input equates to True, 0 elsewhere. sampling : float or int, or sequence of same, optional Spacing of elements along each dimension. If a sequence, must be of length equal to the input rank; if a single number, this is used for all axes. If not specified, a grid spacing of unity is implied. return_distances : bool, optional Whether to return distance matrix. At least one of return_distances/return_indices must be True. Default is True. return_indices : bool, optional Whether to return indices matrix. Default is False. distances : ndarray, optional Used for output of distance array, must be of type float64. indices : ndarray, optional Used for output of indices, must be of type int32. Returns ------- distance_transform_edt : ndarray or list of ndarrays Either distance matrix, index matrix, or a list of the two, depending on `return_x` flags and `distance` and `indices` input parameters. Notes ----- The euclidean distance transform gives values of the euclidean distance:: n y_i = sqrt(sum (x[i]-b[i])**2) i where b[i] is the background point (value 0) with the smallest Euclidean distance to input points x[i], and n is the number of dimensions. --- Copyright (C) 2003-2005 Peter J. Verveer Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ from scipy.ndimage import _nd_image, _ni_support if (not return_distances) and (not return_indices): msg = "at least one of distances/indices must be specified" raise RuntimeError(msg) ft_inplace = isinstance(indices, np.ndarray) dt_inplace = isinstance(distances, np.ndarray) # calculate the feature transform input = np.atleast_1d(np.where(input, 1, 0).astype(np.int8)) garbage_collect = gc.collect if input.nbytes > 100E6 else lambda: None garbage_collect() input = input.astype(np.int32) garbage_collect() if sampling is not None: sampling = _ni_support._normalize_sequence(sampling, input.ndim) sampling = np.asarray(sampling, dtype=np.float64) if not sampling.flags.contiguous: sampling = sampling.copy() if ft_inplace: ft = indices if ft.shape != (input.ndim,) + input.shape: raise RuntimeError("indices has wrong shape") if ft.dtype.type != np.int32: raise RuntimeError("indices must be of int32 type") else: ft = np.zeros((input.ndim,) + input.shape, dtype=np.int32) _nd_image.euclidean_feature_transform(input, sampling, ft) input_shape = input.shape del input garbage_collect() # if requested, calculate the distance transform if return_distances: # dt = ft - np.indices(input.shape, dtype=ft.dtype) # Paul K. Gerke: Save a lot of memory by doing the operation # column-wise and in-pace. if return_indices: dt = ft.copy() else: dt = ft del ft c_indices = np.indices((1,) + input_shape[1:], dtype=dt.dtype) for c in range(input_shape[0]): dt[:, c : (c + 1)] -= c_indices # noqa: E203 c_indices[0] += 1 dt = dt.astype(np.float32, copy=False) if sampling is not None: for ii in range(len(sampling)): dt[ii, ...] *= sampling[ii] np.multiply(dt, dt, dt) if dt_inplace: dt = np.add.reduce(dt, axis=0) if distances.shape != dt.shape: raise RuntimeError("indices has wrong shape") if distances.dtype.type != np.float32: raise RuntimeError("indices must be of float32 type") np.sqrt(dt, distances) else: dt = np.add.reduce(dt, axis=0) dt = np.sqrt(dt) # construct and return the result result = [] if return_distances and not dt_inplace: result.append(dt) if return_indices and not ft_inplace: result.append(ft) if len(result) == 2: return tuple(result) elif len(result) == 1: return result[0] else: return None
def zoom(input, zoom, output=None, order=3, mode='constant', cval=0.0, prefilter=True): """ Zoom an array. The array is zoomed using spline interpolation of the requested order. Parameters ---------- input : ndarray The input array. zoom : float or sequence, optional The zoom factor along the axes. If a float, `zoom` is the same for each axis. If a sequence, `zoom` should contain one value for each axis. output : ndarray or dtype, optional The array in which to place the output, or the dtype of the returned array. order : int, optional The order of the spline interpolation, default is 3. The order has to be in the range 0-5. mode : str, optional Points outside the boundaries of the input are filled according to the given mode ('constant', 'nearest', 'reflect' or 'wrap'). Default is 'constant'. cval : scalar, optional Value used for points outside the boundaries of the input if ``mode='constant'``. Default is 0.0 prefilter : bool, optional The parameter prefilter determines if the input is pre-filtered with `spline_filter` before interpolation (necessary for spline interpolation of order > 1). If False, it is assumed that the input is already filtered. Default is True. Returns ------- return_value : ndarray or None The zoomed input. If `output` is given as a parameter, None is returned. """ if order < 0 or order > 5: raise RuntimeError('spline order not supported') input = numpy.asarray(input) if numpy.iscomplexobj(input): raise TypeError('Complex type not supported') if input.ndim < 1: raise RuntimeError('input and output rank must be > 0') mode = _extend_mode_to_code(mode) if prefilter and order > 1: filtered = spline_filter(input, order, output = numpy.float64) else: filtered = input zoom = _ni_support._normalize_sequence(zoom, input.ndim) output_shape = tuple([int(ii * jj) for ii, jj in zip(input.shape, zoom)]) zoom_div = numpy.array(output_shape, float) zoom = (numpy.array(input.shape)) / zoom_div # Zooming to non-finite values in unpredictable, so just choose # zoom factor 1 instead zoom[~numpy.isfinite(zoom)] = 1 output, return_value = _ni_support._get_output(output, input, shape=output_shape) zoom = numpy.asarray(zoom, dtype = numpy.float64) zoom = numpy.ascontiguousarray(zoom) _nd_image.zoom_shift(filtered, zoom, None, output, order, mode, cval) return return_value
def pad(input, size=None, footprint=None, output=None, mode="reflect", cval=0.0): r""" Returns a copy of the input, padded by the supplied structuring element. In the case of odd dimensionality, the structure element will be centered as following on the currently processed position:: [[T, Tx, T], [T, T , T]] , where Tx denotes the center of the structure element. Simulates the behaviour of scipy.ndimage filters. Parameters ---------- input : array_like Input array to pad. size : scalar or tuple, optional See footprint, below footprint : array, optional Either `size` or `footprint` must be defined. `size` gives the shape that is taken from the input array, at every element position, to define the input to the filter function. `footprint` is a boolean array that specifies (implicitly) a shape, but also which of the elements within this shape will get passed to the filter function. Thus ``size=(n,m)`` is equivalent to ``footprint=np.ones((n,m))``. We adjust `size` to the number of dimensions of the input array, so that, if the input array is shape (10,10,10), and `size` is 2, then the actual size used is (2,2,2). output : array, optional The `output` parameter passes an array in which to store the filter output. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional The `mode` parameter determines how the array borders are handled, where `cval` is the value when mode is equal to 'constant'. Default is 'reflect'. cval : scalar, optional Value to fill past edges of input if `mode` is 'constant'. Default is 0.0 Returns ------- output : ndarray The padded version of the input image. Notes ----- Since version 1.7.0, numpy supplied a pad function `numpy.pad` that provides the same functionality and should be preferred. Raises ------ ValueError If the provided footprint/size is more than double the image size. """ input = numpy.asarray(input) if footprint is None: if size is None: raise RuntimeError("no footprint or filter size provided") sizes = _ni_support._normalize_sequence(size, input.ndim) footprint = numpy.ones(sizes, dtype=bool) else: footprint = numpy.asarray(footprint, dtype=bool) fshape = [ii for ii in footprint.shape if ii > 0] if len(fshape) != input.ndim: raise RuntimeError('filter footprint array has incorrect shape.') if numpy.any([x > 2 * y for x, y in zip(footprint.shape, input.shape)]): raise ValueError( 'The size of the padding element is not allowed to be more than double the size of the input array in any dimension.' ) padding_offset = [((s - 1) / 2, s / 2) for s in fshape] input_slicer = [ slice(l, None if 0 == r else -1 * r) for l, r in padding_offset ] output_shape = [s + sum(os) for s, os in zip(input.shape, padding_offset)] output, return_value = _ni_support._get_output(output, input, output_shape) if 'constant' == mode: output += cval output[input_slicer] = input return return_value elif 'nearest' == mode: output[input_slicer] = input dim_mult_slices = [ (d, l, slice(None, l), slice(l, l + 1)) for d, (l, _) in zip(list(range(output.ndim)), padding_offset) if not 0 == l ] dim_mult_slices.extend([ (d, r, slice(-1 * r, None), slice(-2 * r, -2 * r + 1)) for d, (_, r) in zip(list(range(output.ndim)), padding_offset) if not 0 == r ]) for dim, mult, to_slice, from_slice in dim_mult_slices: slicer_to = [ to_slice if d == dim else slice(None) for d in range(output.ndim) ] slicer_from = [ from_slice if d == dim else slice(None) for d in range(output.ndim) ] if not 0 == mult: output[slicer_to] = numpy.concatenate([output[slicer_from]] * mult, dim) return return_value elif 'mirror' == mode: dim_slices = [ (d, slice(None, l), slice(l + 1, 2 * l + 1)) for d, (l, _) in zip(list(range(output.ndim)), padding_offset) if not 0 == l ] dim_slices.extend([ (d, slice(-1 * r, None), slice(-2 * r - 1, -1 * r - 1)) for d, (_, r) in zip(list(range(output.ndim)), padding_offset) if not 0 == r ]) reverse_slice = slice(None, None, -1) elif 'reflect' == mode: dim_slices = [ (d, slice(None, l), slice(l, 2 * l)) for d, (l, _) in zip(list(range(output.ndim)), padding_offset) if not 0 == l ] dim_slices.extend([ (d, slice(-1 * r, None), slice(-2 * r, -1 * r)) for d, (_, r) in zip(list(range(output.ndim)), padding_offset) if not 0 == r ]) reverse_slice = slice(None, None, -1) elif 'wrap' == mode: dim_slices = [ (d, slice(None, l), slice(-1 * (l + r), -1 * r if not 0 == r else None)) for d, (l, r) in zip(list(range(output.ndim)), padding_offset) if not 0 == l ] dim_slices.extend([ (d, slice(-1 * r, None), slice(l, r + l)) for d, (l, r) in zip(list(range(output.ndim)), padding_offset) if not 0 == r ]) reverse_slice = slice(None) else: raise RuntimeError('boundary mode not supported') output[input_slicer] = input for dim, to_slice, from_slice in dim_slices: slicer_reverse = [ reverse_slice if d == dim else slice(None) for d in range(output.ndim) ] slicer_to = [ to_slice if d == dim else slice(None) for d in range(output.ndim) ] slicer_from = [ from_slice if d == dim else slice(None) for d in range(output.ndim) ] output[slicer_to] = output[slicer_from][slicer_reverse] return return_value
def rolling_ball_filter(data, ball_radius, spacing=None, top=False, **kwargs): """Filter data via a rolling ball algorithm. Implemented with morphological operations This implenetation is very similar to that in ImageJ and uses a top hat transform with a ball shaped structuring element https://en.wikipedia.org/wiki/Top-hat_transform Parameters ---------- data : ndarray image data (assumed to be on a regular grid) ball_radius : float the radius of the ball to roll spacing : int or sequence the spacing of the image data top : bool whether to roll the ball on the top or bottom of the data kwargs : key word arguments these are passed to the ndimage morphological operations Returns ------- data_nb : ndarray data with background subtracted bg : ndarray background that was subtracted from the data """ # get dimension of data ndim = data.ndim # set spacing to 1 if not specified if spacing is None: spacing = np.ones(ndim) else: spacing = _normalize_sequence(spacing, ndim) # arrayify radius radius = np.asarray(_normalize_sequence(ball_radius, ndim)) # generate the mesh for the sphere mesh = np.array( np.meshgrid( *[np.arange(-r, r + s, s) for r, s in zip(radius, spacing)], indexing="ij")) # make the sphere and replace nan with 0 structure = 2 * np.sqrt( np.fmax(0, 1 - ((mesh / radius.reshape(-1, *((1, ) * ndim)))**2).sum(0))) # roll ball on top or bottom dpending on request if not top: # ndi.white_tophat(y, structure=structure, output=background) background = ndi.grey_erosion(data, structure=structure, **kwargs) background = ndi.grey_dilation(background, structure=structure, **kwargs) else: # ndi.black_tophat(y, structure=structure, output=background) background = ndi.grey_dilation(data, structure=structure, **kwargs) background = ndi.grey_erosion(background, structure=structure, **kwargs) return data - background, background