Source code for machinevisiontoolbox.base.findpeaks

import numpy as np
import spatialmath.base as base
import scipy as sp
from numpy.polynomial import Polynomial


[docs]def findpeaks(y, x=None, npeaks=None, scale=1, interp=0, return_poly=False): r""" Find peaks in a 1D signal :param y: 1D-signal :math:`y(x)` :type y: ndarray(N) :param x: corresponding dependent variable, defaults to the integer sequence :math:`0 \ldots N-1`. :type x: ndarray(N), optional :param npeaks: number of peaks ``P`` to return, defaults to all :type npeaks: int, optional :param scale: peak scale, defaults to 1 :type scale: int, optional :param interp: interpolate the peak value, defaults to False :type interp: bool or int, optional :param return_poly: return interpolation polynomial, defaults to False :type return_poly: bool, optional :raises ValueError: ``interp`` must be > 2 if given :return: peak positions and values :rtype: ndarray(P), ndarray(P) Find the peak in a 1D signal. Example: .. runblock:: pycon >>> from machinevisiontoolbox.base import * >>> import numpy as np >>> y = np.array([0, 0, 1, 2, 0, 0, 0, 3, 1, 0, 0, 0, 0]) >>> findpeaks(y, scale=3) >>> findpeaks(y, scale=3, interp=True) .. note:: - A maxima is defined as an element that is larger than its ``scale`` neighbours on either side. This is the largest value in a 2*scale+1 sliding window. - The first and last ``scale`` elements will never be returned as maxima. - To find minima, use ``findpeak(-y)``. - The ``interp`` options fits points in the neighbourhood about the peak with an M'th order polynomial and its peak position is returned. Typically choose M to be even. Setting ``interp`` to True uses ``M=2``. Alternatively set ``interp`` to M. - If ``return_poly`` is True then an additional value is returned which is a list of polynomial coefficients for each peak, see :obj:`numpy.polynomial`. :seealso: :func:`findpeaks2d` :func:`findpeaks3d` """ # if second argument is a matrix we take this as the corresponding x # coordinates y = base.getvector(y) if x is None: x = np.arange(len(y)) else: x = base.getvector(x, len(y)) # find the maxima if scale > 0: dx = x[1] - x[0] # compare to a moving window max filtered version n = round(scale / dx) * 2 + 1 kernel = np.ones((n,)) (k,) = np.nonzero(y == sp.signal.order_filter(y, kernel, n - 1)) k = np.array([kk for kk in k if kk >= scale and kk <= len(y) - scale]) else: # take the zero crossings dy = np.diff(y) # TODO: I think itertools can do this? (k,) = np.nonzero([v and w for v, w in zip(np.r_[dy, 0] < 0, np.r_[0, dy] > 0)]) # sort the maxima into descending magnitude i = np.argsort(-y[k]) k = k[i] # indices of the maxima if npeaks is not None: k = k[:npeaks] # interpolate the peaks if required if interp is True: interp = 2 if interp > 0: if interp < 2: raise ValueError("interpolation polynomial must be at least second order") n2 = round(interp / 2) # for each previously identified peak x(i), y(i) refined_x = [] refined_y = [] polys = [] for i in k: # fit a polynomial to the local neighbourhood try: poly, *_ = Polynomial.fit( x[i - n2 : i + n2 + 1], y[i - n2 : i + n2 + 1], interp, full=True ) except: # handle situation where neighbourhood falls off the data # vector print(f"Peak at {x[i]} couldn't be fitted, skipping") continue # find the roots of the polynomial closest to the coarse peak r = poly.deriv(1).roots() if len(r) == 0: # no roots found continue j = np.argmin(abs(r - x[i])) xp = r[j] # store x, y for the refined peak refined_x.append(xp) refined_y.append(poly(xp)) polys.append(poly) if return_poly: return np.array(refined_x), np.array(refined_y), polys else: return np.array(refined_x), np.array(refined_y) else: return x[k], y[k]
[docs]def findpeaks2d(z, npeaks=2, scale=1, interp=False, positive=True): r""" Find peaks in a 2D signal :param z: 2D-signal :math:`z(x,y)` :type z: ndarray(H,W) :param npeaks: number of peaks to return (default all) :type npeaks: int :param scale: scale of peaks to consider :type scale: float :param interp: interpolate the peak value, defaults to False :type interp: bool, optional :param positive: peak must be > 0, defaults to False :type positive: bool, optional :return: peak positions and magnitudes, one per row :rtype: ndarray(P,3) Find the maximum of a 2D signal, typically a greyscale image. Example: .. runblock:: pycon >>> from machinevisiontoolbox.base import * >>> import numpy as np >>> z = np.zeros((10,10)) >>> z[3,4] = 2 >>> z[4,4] = 1 >>> findpeaks2d(z) >>> findpeaks2d(z, interp=True) .. note:: - A maxima is defined as an element that larger than its neighbours in a 2*scale+1 square window. - Elements where the window falls off the edge of the input array will never be returned as maxima. - To find minima, use ``findpeaks2d(-image)``. - The interp options fits points in the neighbourhood about the peak with a paraboloid and its peak position is returned. :seealso: :func:`findpeaks` :func:`findpeaks3d` """ # TODO check valid input # create a neighbourhood mask for non-local maxima suppression # scale is taken as half-width of the window w = 2 * scale + 1 M = np.ones((w, w), dtype="uint8") M[scale, scale] = 0 # set middle pixel to zero # compute the neighbourhood maximum # znh = self.window(self.float(z), M, 'max', 'wrap') # image = self.asint() # nh_max = cv.morphologyEx(image, cv.MORPH_DILATE, M) nhood_max = sp.ndimage.maximum_filter(z, footprint=M) # find all pixels greater than their neighbourhood if positive: k = np.flatnonzero((z > nhood_max) & (z > 0)) else: k = np.flatnonzero(z > nhood_max) # sort these local maxima into descending order image_flat = z.ravel() maxima = image_flat[k] ks = np.argsort(-maxima) k = k[ks] if npeaks is not None: npks = min(len(k), npeaks) k = k[0:npks] y, x = np.unravel_index(k, z.shape) # interpolate peaks if required if interp: refined = [] for xk, yk in zip(x, y): # now try to interpolate the peak over a 3x3 window try: zc = z[yk, xk] zn = z[yk - 1, xk] zs = z[yk + 1, xk] ze = z[yk, xk + 1] zw = z[yk, xk - 1] except IndexError: continue dx = (ze - zw) / (2 * (2 * zc - ze - zw)) dy = (zs - zn) / (2 * (2 * zc - zn - zs)) zest = ( zc - (ze - zw) ** 2 / (8 * (ze - 2 * zc + zw)) - (zn - zs) ** 2 / (8 * (zn - 2 * zc + zs)) ) aest = np.min(np.abs(np.r_[ze / 2 - zc + zw / 2, zn / 2 - zc + zs / 2])) refined.append([xk + dx, yk + dy, zest, aest]) return np.array(refined) else: # xy = np.stack((y, x), axis=0) return np.column_stack((x, y, image_flat[k]))
[docs]def findpeaks3d(v, npeaks=None): r""" Find peaks in a 3D signal :param z: 3D-data :math:`v(x,y,z)` :type z: ndarray(H,W,D) :param npeaks: number of peaks to return (default all) :type npeaks: int :return: peak position and magnitude, one per row :rtype: ndarray(N,4) Find the maximum of a 3D signal, typically volumetric data such as a scale-space image stack. Example: .. runblock:: pycon >>> from machinevisiontoolbox.base import * >>> import numpy as np >>> z = np.zeros((10,10,10)) >>> z[3,4,5] = 1 >>> findpeaks3d(z) .. note:: A maxima is defined as an element that larger than its 26 neighbours. Edges elements will never be returned as maxima. :seealso: :func:`findpeaks` :func:`findpeaks2d` """ # absolute value of Laplacian as a 3D matrix, with scale along axis 2 # find maxima within all 26 neighbouring pixels # create 3x3x3 structuring element and maximum filter se_nhood = np.ones((3, 3, 3)) se_nhood[1, 1, 1] = 0 eps = np.finfo(np.float64).eps maxima = v > sp.ndimage.maximum_filter(v, footprint=se_nhood, mode="nearest") # find the locations of the minima i, j, k = np.nonzero(maxima) # create result matrix, one row per feature: i, j, k, L # where k is index into scale result = np.column_stack((i, j, k, v[i, j, k])) # sort the rows on strength column, descending order k = np.argsort(-result[:, 3]) result = result[k, :] if npeaks is not None: result = result[:npeaks, :] return result
if __name__ == "__main__": from machinevisiontoolbox.base import * import numpy as np y = np.array([0, 0, 1, 2, 0, 0, 0, 3, 1, 0, 0, 0, 0]) print(findpeaks(y, scale=3)) print(findpeaks(y, scale=3, interp=True)) z = np.zeros((10, 10)) z[3, 4] = 2 z[4, 4] = 1 print(findpeaks2d(z)) print(findpeaks2d(z, interp=True)) # y = mvtb_load_matfile('data/peakfit.mat')["y"] # plt.plot(y, '-o') # xmax, ymax = findpeaks(y, interp=2) # print(xmax, ymax) # a = [1, 1, 1, 1, 1] # print(findpeaks(a)) # a = [5, 1, 1, 1, 1] # print(findpeaks(a)) # a = [1, 1, 5, 1, 1] # print(findpeaks(a)) # a = [1, 1, 5, 1, 1] # print(findpeaks(a, [10, 11, 12, 13, 14])) # a = [1, 2, 3, 4, 3, 2, 1] # print(findpeaks(a)) # a.extend(a) # print(findpeaks(a)) # a = [1, 1, 1, 2, 1, 4, 1, 1, 2] # print(findpeaks(a))