def find_center_by_gaussian_fit(IM, verbose=False, round_output=False, **kwargs): """Deprecated function. Use :func:`find_origin_by_gaussian_fit` instead.""" _deprecate('abel.tools.center.find_center_by_gaussian_fit() ' 'is deprecated, use ' 'abel.tools.center.find_origin_by_gaussian_fit() instead.') return find_origin_by_gaussian_fit(IM, verbose, round_output, **kwargs)
def find_image_center_by_slice(IM, slice_width=10, radial_range=(0, -1), axis=(0, 1), **kwargs): """Deprecated function. Use :func:`find_origin_by_slice` instead.""" _deprecate('abel.tools.center.find_image_center_by_slice() ' 'is deprecated, use ' 'abel.tools.center.find_origin_by_slice() instead.') return find_origin_by_slice(IM, slice_width, radial_range, axis, **kwargs)
def find_center_by_center_of_image(data, verbose=False, **kwargs): """Deprecated function. Use :func:`find_origin_by_center_of_image` instead.""" _deprecate('abel.tools.center.find_center_by_center_of_image() ' 'is deprecated, use ' 'abel.tools.center.find_origin_by_center_of_image() instead.') return find_origin_by_center_of_image(data, verbose, **kwargs)
def find_center(IM, center='image_center', square=False, verbose=False, **kwargs): """Deprecated function. Use :func:`find_origin` instead.""" _deprecate('abel.tools.center.find_center() ' 'is deprecated, use abel.tools.center.find_origin() instead.') if square: _deprecate('Argument "square" has no effect and is deprecated.') return find_origin(IM, center, verbose, **kwargs)
def __init__(self, IM, direction='inverse', method='three_point', origin='none', symmetry_axis=None, use_quadrants=(True, True, True, True), symmetrize_method='average', angular_integration=False, transform_options=dict(), center_options=dict(), angular_integration_options=dict(), recast_as_float64=True, verbose=False, center=_deprecated): """ The one-stop transform function. """ if center is not _deprecated: _deprecate( 'abel.transform.Transform() ' 'argument "center" is deprecated, use "origin" instead.') origin = center # public class variables self.IM = IM # (optionally) centered, odd-width image self.method = method self.direction = direction # private internal variables self._origin = origin self._symmetry_axis = symmetry_axis self._symmetrize_method = symmetrize_method self._use_quadrants = use_quadrants self._transform_options = transform_options self._recast_as_float64 = recast_as_float64 _verbose = verbose # image processing self._verify_some_inputs() self._center_image(origin, **center_options) self._abel_transform_image(**transform_options) self._integration(angular_integration, transform_options, **angular_integration_options)
def image(self): """Deprecated. Use :attr:`func` instead.""" _deprecate('SampleImage attribute ".image" is deprecated, ' 'use ".func" instead.') return self.func
def radial_integration(IM, origin=None, radial_ranges=None): r""" Intensity variation in the angular coordinate. This function is the :math:`\theta`-coordinate complement to :func:`abel.tools.vmi.angular_integration`. Evaluates intensity vs angle for defined radial ranges. Determines the anisotropy parameter for each radial range. See :doc:`examples/example_O2_PES_PAD.py <example_O2_PES_PAD>`. Parameters ---------- IM : 2D numpy.array the image data origin : tuple or None image origin in the (row, column) format. If ``None``, the geometric center of the image (``rows // 2, cols // 2``) is used. radial_ranges : list of tuple ranges or int step tuple integration ranges ``[(r0, r1), (r2, r3), ...]`` evaluates the intensity vs angle for the radial ranges ``r0_r1``, ``r2_r3``, etc. int the whole radial range ``(0, step), (step, 2*step), ..`` Returns ------- Beta : array of tuples (beta0, error_beta_fit0), (beta1, error_beta_fit1), ... corresponding to the radial ranges Amplitude : array of tuples (amp0, error_amp_fit0), (amp1, error_amp_fit1), ... corresponding to the radial ranges Rmidpt : numpy float 1D array radial mid-point of each radial range Intensity_vs_theta: 2D numpy.array intensity vs angle distribution for each selected radial range theta: 1D numpy.array angle coordinates, referenced to vertical direction """ if origin is not None and not isinstance(origin, tuple): _deprecate('radial_integration() has 2nd argument "origin", ' 'use keyword argument "radial_ranges" or insert "None".') radial_ranges = origin origin = None polarIM, r_grid, theta_grid = reproject_image_into_polar(IM, origin) theta = theta_grid[0, :] # theta coordinates r = r_grid[:, 0] # radial coordinates if radial_ranges is None: radial_ranges = 1 if isinstance(radial_ranges, int): rr = np.arange(0, r[-1], radial_ranges) # @DanHickstein clever code to map ranges radial_ranges = list(zip(rr[:-1], rr[1:])) Intensity_vs_theta = [] radial_midpt = [] Beta = [] Amp = [] for rr in radial_ranges: subr = np.logical_and(r >= rr[0], r <= rr[1]) # sum intensity across radius of spectral feature intensity_vs_theta_at_R = np.sum(polarIM[subr], axis=0) Intensity_vs_theta.append(intensity_vs_theta_at_R) radial_midpt.append(np.mean(rr)) beta, amp = anisotropy_parameter(theta, intensity_vs_theta_at_R) Beta.append(beta) Amp.append(amp) return Beta, Amp, radial_midpt, Intensity_vs_theta, theta
def circularize_image(IM, method="lsq", origin=None, radial_range=None, dr=0.5, dt=0.5, smooth=_deprecated, ref_angle=None, inverse=False, return_correction=False, tol=0, center=_deprecated): r""" Corrects image distortion on the basis that the structure should be circular. This is a simplified radial scaling version of the algorithm described in J. R. Gascooke, S. T. Gibson, W. D. Lawrance, "A 'circularisation' method to repair deformations and determine the centre of velocity map images", `J. Chem. Phys. 147, 013924 (2017) <https://dx.doi.org/10.1063/1.4981024>`_. This function is especially useful for correcting the image obtained with a velocity-map-imaging spectrometer, in the case where there is distortion of the Newton sphere (ring) structure due to an imperfect electrostatic lens or stray electromagnetic fields. The correction allows the highest-resolution 1D photoelectron distribution to be extracted. The algorithm splits the image into "slices" at many different angles (set by **dt**) and compares the radial intensity profile of adjacent slices. A scaling factor is found which aligns each slice profile with the previous slice. The image is then corrected using a spline function that smoothly connects the discrete scaling factors as a continuous function of angle. This circularization algorithm should only be applied to a well-centered image, otherwise use the **origin** keyword (described below) to center it. Parameters ---------- IM : numpy 2D array Image to be circularized. method : str Method used to determine the radial correction factor to align slice profiles: ``argmax`` compare intensity-profile.argmax() of each radial slice. This method is quick and reliable, but it assumes that the radial intensity profile has an obvious maximum. The positioning is limited to the nearest pixel. ``lsq`` minimize the difference between a slice intensity-profile with its adjacent slice. This method is slower and may fail to converge, but it may be applied to images with any (circular) structure. It aligns the slices with sub-pixel precision. origin : float tuple, str or None Pre-center image using :func:`abel.tools.center.center_image`. May be an explicit (row, column) tuple or a method name: ``'com'``, ``'convolution'``, ``'gaussian;``, ``'image_center'``, ``'slice'``. ``None`` (default) assumes that the image is already centered. radial_range : tuple or None Limit slice comparison to the radial range tuple (rmin, rmax), in pixels, from the image origin. Use to determine the distortion correction associated with particular peaks. It is recommended to select a region of your image where the signal-to-noise ratio is highest, with sharp persistent (in angle) features. dr : float Radial grid size for the polar coordinate image, default = 0.5 pixel. This is passed to :func:`abel.tools.polar.reproject_image_into_polar`. Small values may improve the distortion correction, which is often of sub-pixel dimensions, at the cost of reduced signal to noise for the slice intensity profile. As a general rule, `dr` should be significantly smaller than the radial "feature size" in the image. dt : float Angular grid size. This sets the number of radial slices, given by :math:`2\pi/dt`. Default = 0.1, ~ 63 slices. More slices, using smaller `dt`, may provide a more detailed angular variation of the correction, at the cost of greater signal to noise in the correction function. Also passed to :func:`abel.tools.polar.reproject_image_into_polar`. smooth : float Deprecated, use **tol** instead. The relationship is **smooth** = `N`\ :sub:`angles` × **tol**\ :sup:`2`, where `N`\ :sub:`angles` is the number of slices (see **dt**). ref_angle : None or float Reference angle for which radial coordinate is unchanged. Angle varies between :math:`-\pi` and :math:`\pi`, with zero angle vertical. ``None`` uses :func:`numpy.mean` of the radial correction function, which attempts to maintain the same average radial scaling. This approximation is likely valid, unless you know for certain that a specific angle of your image corresponds to an undistorted image. inverse : bool Apply an inverse Abel transform to the `polar`-coordinate image, to remove the background intensity. This may improve the signal-to-noise ratio, allowing the weaker intensity featured to be followed in angle. Note that this step is only for the purposes of allowing the algorithm to better follow peaks in the image. It does not affect the final image that is returned, except for (hopefully) slightly improving the precision of the distortion correction. return_correction : bool Additional outputs, as describe below. tol : float Root-mean-square (RMS) fitting tolerance for the spline function. At the default zero value, the spline interpolates between the discrete scaling factors. At larger values, a smoother spline is found such that its RMS deviation from the discrete scaling factors does not exceed this number. For example, ``tol=0.01`` means 1% RMS tolerance for the radial scaling correction. At very large tolerances, the spline degenerates to a constant, the average of the discrete scaling factors. Typically, **tol** may remain zero (use interpolation), but noisy data may require some smoothing, since the found discrete scaling factors can have noticeable errors. To examine the relative scaling factors and how well they are represented by the spline function, use the option ``return_correction=True``. Returns ------- IMcirc : numpy 2D array Circularized version of the input image, same size as input. The following values are returned if ``return_correction=True``: angles : numpy 1D array Mid-point angle (radians) of each image slice. radial_correction : numpy 1D array Radial correction scale factor at each angular slice. radial_correction_function : function(numpy 1D array) Function that may be used to evaluate the radial correction at any angle. """ if center is not _deprecated: _deprecate('abel.tools.circularize.circularize_image() ' 'argument "center" is deprecated, use "origin" instead.') origin = center if origin is not None: # convenience function for the case image is not centered IM = abel.tools.center.center_image(IM, method=origin) # map image into polar coordinates - much easier to slice # cartesian (Y, X) -> polar (Radius, Theta) polarIM, radial_coord, angle_coord = \ abel.tools.polar.reproject_image_into_polar(IM, dr=dr, dt=dt) if inverse: # pseudo inverse Abel transform of the polar image, removes background # to enhance transition peaks polarIM = abel.dasch.two_point_transform(polarIM.T).T # more convenient 1-D coordinate arrays angles = angle_coord[0] # angle coordinate radial = radial_coord[:, 0] # radial coordinate # limit radial range of polar image, if selected if radial_range is not None: subr = np.logical_and(radial > radial_range[0], radial < radial_range[1]) polarIM = polarIM[subr] radial = radial[subr] # evaluate radial correction factor that aligns each angular slice radcorr = correction(polarIM.T, angles, radial, method=method) if smooth is not _deprecated: _deprecate('abel.tools.circularize.circularize_image() ' 'argument "smooth" is deprecated, use "tol" instead.') else: smooth = len(angles) * tol**2 # periodic spline radial correction vs angle spl = splrep(np.append(angles, angles[0] + 2 * np.pi), np.append(radcorr, radcorr[0]), s=smooth, per=True) def radial_correction_function(angle): return splev(angle, spl) # apply the correction IMcirc = circularize(IM, radial_correction_function, ref_angle=ref_angle) if return_correction: return IMcirc, angles, radcorr, radial_correction_function else: return IMcirc
def find_center_by_convolution(IM, **kwargs): """Deprecated function. Use :func:`find_origin_by_convolution` instead.""" _deprecate('abel.tools.center.find_center_by_convolution() ' 'is deprecated, use ' 'abel.tools.center.find_origin_by_convolution() instead.') return find_origin_by_convolution(IM, **kwargs)
def center_image(IM, method='com', odd_size=True, square=False, axes=(0, 1), crop='maintain_size', order=3, verbose=False, center=_deprecated, **kwargs): """ Center image with the custom value or by several methods provided in :func:`find_origin()` function. Parameters ---------- IM : 2D np.array The image data. method : str or tuple of float either a tuple (float, float), the coordinate of the origin of the image in the (row, column) format, or a string to specify an automatic centering method: ``image_center`` the center of the image is used as the origin. The trivial result. ``com`` the origin is found as the center of mass. ``convolution`` the origin is found as the maximum of autoconvolution of the image projections along each axis. ``gaussian`` the origin is extracted from a fit to a Gaussian function. This is probably only appropriate if the data resembles a gaussian. ``slice`` the image is broken into slices, and these slices compared for symmetry. odd_size : boolean if ``True``, the returned image will contain an odd number of columns. Most of the transform methods require this, so it's best to set this to ``True`` if the image will subsequently be Abel-transformed. square : bool if ``True``, the returned image will have a square shape. crop : str determines how the image should be cropped. The options are: ``maintain_size`` return image of the same size. Some regions of the original image may be lost, and some regions may be filled with zeros. ``valid_region`` return the largest image that can be created without padding. All of the returned image will correspond to the original image. However, portions of the original image will be lost. If you can tolerate clipping the edges of the image, this is probably the method to choose. ``maintain_data`` the image will be padded with zeros such that none of the original image will be cropped. See :func:`set_center` for examples. axes : int or tuple of int center image with respect to axis ``0`` (vertical), ``1`` (horizontal), or both axes ``(0, 1)`` (default). When specifying an explicit origin in **method**, unused coordinates can also be passed as ``None``, for example, ``method=(row, None)`` or ``method=(None, col)``. order : int interpolation order, see :func:`set_center` for details. Returns ------- out : 2D np.array centered image """ if center is not _deprecated: _deprecate('abel.tools.center.center_image() ' 'argument "center" is deprecated, use "method" instead.') method = center rows, cols = IM.shape if odd_size and cols % 2 == 0: # drop rightside column IM = IM[:, :-1] rows, cols = IM.shape if square and rows != cols: # make rows == cols, but maintain approx. center if rows > cols: diff = rows - cols trim = diff // 2 if trim > 0: IM = IM[trim: -trim] # remove even number of rows off each end if diff % 2: IM = IM[: -1] # remove one additional row else: # make rows == cols, check row oddness if odd_size and rows % 2 == 0: IM = IM[:-1, :] rows -= 1 xs = (cols - rows) // 2 IM = IM[:, xs:-xs] rows, cols = IM.shape # origin is in (row, column) format! if isinstance(method, string_types): origin = find_origin(IM, method=method, axes=axes, verbose=verbose, **kwargs) else: origin = method centered_data = set_center(IM, origin=origin, crop=crop, axes=axes, order=order, verbose=verbose) return centered_data
def find_origin_by_slice(IM, axes=(0, 1), slice_width=10, radial_range=(0, -1), axis=_deprecated, **kwargs): """ Find the image origin by comparing opposite sides. Parameters ---------- IM : 2D np.array the image data slice_width : integer Sum together this number of rows (cols) to improve signal, default 10. radial_range: tuple (rmin, rmax): radial range ``[rmin:rmax]`` for slice profile comparison. axes : int or tuple find origin coordinates: ``0`` (vertical), or ``1`` (horizontal), or ``(0, 1)`` (both vertical and horizontal). Returns ------- origin : (float, float) (row, column) """ if axis is not _deprecated: _deprecate('abel.tools.center.find_origin_by_slice() ' 'argument "axis" is deprecated, use "axes" instead.') axes = axis def _align(offset, sliceA, sliceB): """ Intensity difference between an axial slice and its shifted opposite. """ # always shift to the left (towards center) if offset < 0: diff = shift(sliceA, offset) - sliceB else: diff = sliceA - shift(sliceB, -offset) fvec = (diff**2).sum() return fvec if isinstance(axes, int): axes = [axes] rows, cols = IM.shape r2 = (rows - 1) / 2 c2 = (cols - 1) / 2 top, bottom, left, right = axis_slices(IM, radial_range, slice_width) xyoffset = [0.0, 0.0] # determine shift to align both slices # limit shift to +- 20 pixels initial_shift = [0.1, ] # vertical axis if 0 in axes: fit = minimize(_align, initial_shift, args=(top, bottom), bounds=((-50, 50), ), tol=0.1) if fit["success"]: xyoffset[0] = -float(fit['x']) / 2 # x1/2 for image shift else: raise RuntimeError("fit failure: axis 0, zero shift set", fit) # horizontal axis if 1 in axes: fit = minimize(_align, initial_shift, args=(left, right), bounds=((-50, 50), ), tol=0.1) if fit["success"]: xyoffset[1] = -float(fit['x']) / 2 # x1/2 for image shift else: raise RuntimeError("fit failure: axis 1, zero shift set", fit) # this is the (row, col) shift to align the slice profiles return r2 - xyoffset[0], c2 - xyoffset[1]
def set_center(data, origin, crop='maintain_size', axes=(0, 1), order=3, verbose=False, center=_deprecated): """ Move image origin to mid-point of image (``rows // 2, cols // 2``). Parameters ---------- data : 2D np.array the image data origin : tuple of float (row, column) coordinates of the image origin. Coordinates set to ``None`` are ignored. crop : str determines how the image should be cropped. The options are: ``maintain_size`` (default) return image of the same size. Some regions of the original image may be lost and some regions may be filled with zeros. ``valid_region`` return the largest image that can be created without padding. All of the returned image will correspond to the original image. However, portions of the original image will be lost. If you can tolerate clipping the edges of the image, this is probably the method to choose. ``maintain_data`` the image will be padded with zeros such that none of the original image will be cropped. Examples: .. plot:: tools/crop_options.py axes : int or tuple of int center image with respect to axis ``0`` (vertical), ``1`` (horizontal), or both axes ``(0, 1)`` (default). order : int interpolation order (0–5, default is 3) for centering with fractional **origin**. Lower orders work faster; **order** = 0 (also implied for integer **origin**) means a whole-pixel shift without interpolation and is much faster. verbose : bool print some information for debugging Returns ------- out : 2D np.array centered image """ if center is not _deprecated: _deprecate('abel.tools.center.set_center() ' 'argument "center" is deprecated, use "origin" instead.') origin = center def center_of(a): """ Indices of array center """ return np.array(a.shape) // 2 shape = data.shape if verbose: print('Original shape', shape, 'and origin', tuple(origin)) center = center_of(data) # remove axes with "None" coordinates, preprocess origin if isinstance(axes, int): axes = [axes] axes = set(axes) origin = np.array(origin, dtype=object) # (for None, int or float) subpixel = np.zeros(2) origin_ = [None, None] for a in [0, 1]: if origin[a] is None: axes.discard(a) else: # to absolute coordinates if origin[a] < 0: origin[a] += shape[a] if order: # split integer and fractional parts i = int(origin[a]) origin[a], subpixel[a] = i, origin[a] - i else: # round to whole pixels origin[a] = int(round(origin[a])) # complement (from the other edge) origin_[a] = shape[a] - 1 - origin[a] # don't interpolate for whole-pixels shifts if np.all(subpixel == 0): order = 0 if verbose: print('Centering axes', tuple(axes), 'using order', order) # Note: current scipy.ndimage.shift() implementation erases fractional edge # pixels, so we wrap it with padding and cropping. Once this behavior is # corrected, our code can be cleaned up. if crop == 'maintain_size': delta = [0, 0] if order: # fractional shift for a in axes: if origin[a] is not None: delta[a] = center[a] - (origin[a] + subpixel[a]) out = shift(np.pad(data, 1, 'constant'), delta, order=order)[1:-1, 1:-1] # (see note above) else: # whole-pixel shift src = [slice(None), slice(None)] # for the source region dst = [slice(None), slice(None)] # for the destination region for a in axes: delta[a] = center[a] - origin[a] # gaps for positive and negative shifts wrt edges dpos = max(0, delta[a]) dneg = max(0, -delta[a]) # corresponding regions src[a] = slice(dneg, shape[a] - dpos) dst[a] = slice(dpos, shape[a] - dneg) out = np.zeros_like(data) out[tuple(dst)] = data[tuple(src)] if verbose: print('Shifted by', tuple(delta)) print('Output shape', out.shape, 'centered at', tuple(center_of(out))) return out # for other crop options, first do subpixel shift # size will change to add/remove the shifted fractional pixel parts if order: if verbose: print('Subpixel shift by', tuple(-subpixel)) # (see the note above about padding on both sides and cropping) data = shift(np.pad(data, 1, 'constant'), -subpixel, order=order)[:-1, :-1] # shift origin or cut unused pixels cut = [slice(None), slice(None)] for a in [0, 1]: if subpixel[a]: if crop == 'valid_region': cut[a] = slice(1, -1) # cut both fractional ends origin_[a] -= 1 else: origin[a] += 1 # (shape is not used below, thus not updated here) else: cut[a] = slice(1, None) # cut empty (not shifted) pixel data = data[tuple(cut)] if crop == 'valid_region': src = [slice(None), slice(None)] for a in axes: # distance to the closest edge d = min(origin[a], origin_[a]) # crop symmetrically around the origin src[a] = slice(origin[a] - d, origin[a] + d + 1) out = data[tuple(src)].copy() # (independent data, as in other cases) if verbose: print('Output cropped to', out.shape, 'centered at', tuple(center_of(out))) return out elif crop == 'maintain_data': pad = [(0, 0), (0, 0)] for a in axes: # distance to the farthest edge d = max(origin[a], origin_[a]) # pad to symmetrize around the origin pad[a] = (d - origin[a], d - origin_[a]) out = np.pad(data, pad, 'constant') if verbose: print('Output padded to', out.shape, 'centered at', tuple(center_of(out))) return out else: raise ValueError('Invalid crop option "{}".'.format(crop))
def set_center(data, origin, crop='maintain_size', axes=(0, 1), verbose=False, center=_deprecated): """ Move image origin to mid-point of image. Parameters ---------- data : 2D np.array the image data origin : tuple (row, column) coordinates of the image origin crop : str determines how the image should be cropped. The options are: ``maintain_size`` return image of the same size. Some regions of the original image may be lost and some regions may be filled with zeros. ``valid_region`` return the largest image that can be created without padding. All of the returned image will correspond to the original image. However, portions of the original image will be lost. If you can tolerate clipping the edges of the image, this is probably the method to choose. ``maintain_data`` the image will be padded with zeros such that none of the original image will be cropped. axes : int or tuple center image with respect to axis ``0`` (vertical), ``1`` (horizontal), or both axes ``(0, 1)`` (default). verbose : bool ``True``: print diagnostics """ if center is not _deprecated: _deprecate('abel.tools.center.set_center() ' 'argument "center" is deprecated, use "origin" instead.') origin = center old_shape = data.shape old_center = data.shape[0] // 2, data.shape[1] // 2 origin = list(origin) if origin[0] < 0: origin[0] += data.shape[0] if origin[1] < 0: origin[1] += data.shape[1] delta0 = old_center[0] - origin[0] delta1 = old_center[1] - origin[1] if crop == 'maintain_data': # pad the image so that the origin can be moved without losing any of # the original data # we need to pad the image with zeros before using the shift() function shift0, shift1 = (None, None) if delta0 != 0: shift0 = 1 + int(np.abs(delta0)) if delta1 != 0: shift1 = 1 + int(np.abs(delta1)) container = np.zeros((data.shape[0] + shift0, data.shape[1] + shift1), dtype=data.dtype) area = container[:, :] if shift0: if delta0 > 0: area = area[:-shift0, :] else: area = area[shift0:, :] if shift1: if delta1 > 0: area = area[:, :-shift1] else: area = area[:, shift1:] area[:, :] = data[:, :] data = container delta0 += np.sign(delta0) * shift0 / 2.0 delta1 += np.sign(delta1) * shift1 / 2.0 if verbose: print("delta = ({0}, {1})".format(delta0, delta1)) if isinstance(axes, int): if axes == 0: centered_data = shift(data, (delta0, 0)) elif axes == 1: centered_data = shift(data, (0, delta1)) else: raise ValueError("axes value not 0, or 1") else: centered_data = shift(data, (delta0, delta1)) if crop == 'maintain_data': # pad the image so that the origin can be moved # without losing any of the original data return centered_data elif crop == 'maintain_size': return centered_data elif crop == 'valid_region': # crop to region containing data shift0, shift1 = (None, None) if isinstance(axes, int): if axes == 0 and delta0 != 0: shift0 = 1 + int(np.abs(delta0)) return centered_data[shift0:-shift0, :] elif axes == 1 and delta1 != 0: shift1 = 1 + int(np.abs(delta1)) return centered_data[:, shift1:-shift1] else: raise ValueError("axes value not 0, or 1") else: if delta0 != 0: shift0 = 1 + int(np.abs(delta0)) if delta1 != 0: shift1 = 1 + int(np.abs(delta1)) return centered_data[shift0:-shift0, shift1:-shift1] else: raise ValueError("Invalid crop method!!")