def _validate_xyz_resolution(ndim, xyz_resolution): """Validate xyz_resolution to assure its length matches the dimensionality of image.""" xyz_resolution = _validate_scalar_to_multi(xyz_resolution, size=ndim) if np.any(xyz_resolution <= 0): raise ValueError(f"All elements of xyz_resolution must be positive.\n" f"np.min(xyz_resolution): {np.min(xyz_resolution)}.") return xyz_resolution
def downsample_image(image, scale_factors, truncate=False): """ Downsample an image by averaging. Arguments: image {np.ndarray} -- The image to be downsampled. scale_factors {int, sequence} -- The per-axis factor by which to reduce the image size. Keyword Arguments: truncate {bool} -- If True, evenly truncates the image down to the nearest multiple of the scale_factor for each axis. (default: {False}) Raises: ValueError: Raised if any of scale_factors is less than 1. Returns: np.ndarray -- A downsampled copy of <image>. """ # Validate arguments. # Validate image. image = _validate_ndarray(image) # Validate scale_factors. scale_factors = _validate_scalar_to_multi(scale_factors, image.ndim, int) # Verify that all scale_factors are at least 1. if np.any(scale_factors < 1): raise ValueError( f"Every element of scale_factors must be at least 1.\n" f"np.min(scale_factors): {np.min(scale_factors)}.") # Downsample a copy of image by averaging. scaled_image = np.copy( image) # Not necessary since _downsample_along_axis does not mutate. # Downsample image along each dimension. for dim, scale_factor in enumerate(scale_factors): scaled_image = _downsample_along_axis( scaled_image, dim, scale_factor, truncate) # Side effect: breaks alias. return scaled_image """
def test__validate_scalar_to_multi(): # Test proper use. kwargs = dict(value=1, size=1, dtype=float) correct_output = np.array([1], float) assert np.array_equal(_validate_scalar_to_multi(**kwargs), correct_output) kwargs = dict(value=1, size=0, dtype=int) correct_output = np.array([], int) assert np.array_equal(_validate_scalar_to_multi(**kwargs), correct_output) kwargs = dict(value=9.5, size=4, dtype=int) correct_output = np.full(4, 9, int) assert np.array_equal(_validate_scalar_to_multi(**kwargs), correct_output) kwargs = dict(value=[1, 2, 3.5], size=3, dtype=float) correct_output = np.array([1, 2, 3.5], float) assert np.array_equal(_validate_scalar_to_multi(**kwargs), correct_output) kwargs = dict(value=[1, 2, 3.5], size=3, dtype=int) correct_output = np.array([1, 2, 3], int) assert np.array_equal(_validate_scalar_to_multi(**kwargs), correct_output) kwargs = dict(value=(1, 2, 3), size=3, dtype=int) correct_output = np.array([1, 2, 3], int) assert np.array_equal(_validate_scalar_to_multi(**kwargs), correct_output) kwargs = dict(value=np.array([1, 2, 3], float), size=3, dtype=int) correct_output = np.array([1, 2, 3], int) assert np.array_equal(_validate_scalar_to_multi(**kwargs), correct_output) # Test improper use. kwargs = dict(value=[1, 2, 3, 4], size='size: not an int', dtype=float) expected_exception = TypeError match = "size must be interpretable as an integer." with pytest.raises(expected_exception, match=match): _validate_scalar_to_multi(**kwargs) kwargs = dict(value=[], size=-1, dtype=float) expected_exception = ValueError match = "size must be non-negative." with pytest.raises(expected_exception, match=match): _validate_scalar_to_multi(**kwargs) kwargs = dict(value=[1, 2, 3, 4], size=3, dtype=int) expected_exception = ValueError match = "The length of value must either be 1 or it must match size." with pytest.raises(expected_exception, match=match): _validate_scalar_to_multi(**kwargs) kwargs = dict(value=np.arange(3 * 4, dtype=int).reshape(3, 4), size=3, dtype=float) expected_exception = ValueError match = "value must not have more than 1 dimension." with pytest.raises(expected_exception, match=match): _validate_scalar_to_multi(**kwargs) kwargs = dict(value=[1, 2, 'c'], size=3, dtype=int) expected_exception = ValueError match = "value and dtype are incompatible with one another." with pytest.raises(expected_exception, match=match): _validate_scalar_to_multi(**kwargs) kwargs = dict(value='c', size=3, dtype=int) expected_exception = ValueError match = "value and dtype are incompatible with one another." with pytest.raises(expected_exception, match=match): _validate_scalar_to_multi(**kwargs)
def change_resolution_by(image, xyz_scales, xyz_resolution=1, pad_to_match_res=True, err_to_higher_res=True, average_on_downsample=True, truncate=False, return_true_resolution=False, **resample_kwargs): """ Resample image such that its resolution is scaled by 1 / <xyz_scales>[dim] or abs(xyz_scales[dim]) if xyz_scales[dim] is negative, in each dimension dim. Arguments: image {np.ndarray} -- The image to be resampled, allowing arbitrary dimensions. xyz_scales {float, sequence} -- The per-axis factors by which to adjust the resolution of <image>. Negative values are treated as the reciprocal of their positive counterparts. xyz_scales[dim] > 1 implies upsampling - increasing resolution and image size. xyz_scales[dim] = 1 implies unity - no change in resolution for this dimension. xyz_scales[dim] < 1 implies downsampling - decreasing resolution and image size. xyz_scales[dim] < 0 implies downsampling by this factor - cast to -1 / xyz_scales[dim]. Examples: xyz_scales[dim] = 2 --> upsample by 2 xyz_scales[dim] = 1 --> do nothing xyz_scales[dim] = 1/2 --> downsample by 2 xyz_scales[dim] = -3 --> downsample by 3 xyz_scales[dim] = -1/5 --> upsample by 5 Keyword Arguments: xyz_resolution {float, sequence} -- The per-axis resolution of <image>. (default: {1}) pad_to_match_res {bool} -- If True, pads a copy of <image> to guarantee that <desired_xyz_resolution> is achieved. (default: {True}) err_to_higher_res {bool} -- If True and <pad_to_match_res> is False, rounds the shape of the new image up rather than down. (default: {True}) average_on_downsample {bool} -- If True, performs downsample_image on a copy of <image> before resampling to prevent aliasing. It scales the image by the largest integer possible along each axis without reducing the resolution past the final resolution. (default: {True}) truncate {bool} -- A kwarg passed to downsample_image. If true, evenly truncates the image down to the nearest multiple of the scale_factor for each axis. (default: {False}) return_true_resolution {bool} -- If True, rather than just returning the resampled image, returns a tuple containing the resampled image and its actual resolution. (default: {False}) Returns: np.ndarray, tuple -- A resampled copy of <image>. If <return_true_resolution> was provided as True, then the return value is a tuple containing the resampled copy of <image> and its actual resolution. """ # Validate arguments. # Validate image. image = _validate_ndarray(image) # Validate xyz_scales. xyz_scales = _validate_scalar_to_multi(xyz_scales, size=image.ndim) for dim, scale in enumerate(xyz_scales): if scale < 0: xyz_scales[dim] = -1 / xyz_scales[dim] # Validate xyz_resolution. xyz_resolution = _validate_xyz_resolution(image.ndim, xyz_resolution) # Compute desired_xyz_resolution. desired_xyz_resolution = xyz_resolution / xyz_scales change_resolution_to_kwargs = dict( image=image, xyz_resolution=xyz_resolution, desired_xyz_resolution=desired_xyz_resolution, pad_to_match_res=pad_to_match_res, err_to_higher_res=err_to_higher_res, average_on_downsample=average_on_downsample, truncate=truncate, return_true_resolution=return_true_resolution, **resample_kwargs) return change_resolution_to(**change_resolution_to_kwargs) # TODO: reconcile use of scipy.interpolate.interpn vs scipy.misc.resize & skimage.transform.downscale_local_mean. # TODO: isolate negative scale conversion into its own function. # TODO: merge necessary new_shape transformations etc. into _resample.