""" Please read the copyright notice located on the readme file (README.md). """ import utils.src.Functions as Fu import numpy as np from scipy import special def PCE(C, shift_range=[0,0], squaresize=11): """ Computes Peak-to-Correlation Energy (PCE) obtained from correlation surface restricted to possible shifts due to cropping. In this implementation of PCE, it carries the sign of the peak (i.e. PCE can be negative) Parameters ---------- C : numpy.ndarray('float32') cross-correlation surface calculated by function 'crosscorr' shift_range : list maximum shift is from [0,0] to [shift_range] squaresize : int removes the peak neighborhood of size (squaresize x squaresize) Returns ------- dict A dictionary with the following items: PCE : peak-to-correlation energy PeakLocation : location of the primary peak, [0 0] when correlated signals are not shifted to each other pvalue : probability of obtaining peakheight or higher (under Gaussian assumption) P_FA : probability of false alarm (increases with increasing range of admissible shifts (shift_range) dict A dictionary similar to the first output but for test under assumption of no cropping (i.e. equal to 'Out0,_ = PCE(C)') Example ------- Out, Out0 = PCE(crosscorr(Noise1, Noise2), size(Noise)-1); C = crosscorr(Noise1,Noise2); Out0,_ = PCE(C) Note: 'Out0.PCE == Out.PCE' and 'Out.P_FA == Out.pvalue' when no shifts are considered """ if any(np.greater_equal(shift_range,C.shape)): shift_range = min(shift_range,C.shape-1) # all possible shift in at least one dimension shift_range = np.array(shift_range) Out = dict(PCE=[], pvalue=[], PeakLocation=[], peakheight=[], P_FA=[], log10P_FA=[]) if not C.any(): # the case when cross-correlation C has zero energy (see crosscor2) Out['PCE'] = 0 Out['pvalue'] = 1 Out['PeakLocation'] = [0,0] return Cinrange = C[-1-shift_range[0]:,-1-shift_range[1]:] # C[-1,-1] location corresponds to no shift of the first matrix argument of 'crosscor2' [max_cc, imax] = np.max(Cinrange.flatten()), np.argmax(Cinrange.flatten()) [ypeak, xpeak] = np.unravel_index(imax,Cinrange.shape)[0], np.unravel_index(imax,Cinrange.shape)[1] Out['peakheight'] = Cinrange[ypeak,xpeak] del Cinrange Out['PeakLocation'] = [shift_range[0]-ypeak, shift_range[1]-xpeak] C_without_peak = _RemoveNeighborhood(C, np.array(C.shape)-Out['PeakLocation'], squaresize) correl = C[-1,-1]; del C # signed PCE, peak-to-correlation energy PCE_energy = np.mean(C_without_peak*C_without_peak) Out['PCE'] = (Out['peakheight']**2)/PCE_energy * np.sign(Out['peakheight']) # p-value Out['pvalue'] = 1/2*special.erfc(Out['peakheight']/np.sqrt(PCE_energy)/np.sqrt(2)) # under simplifying assumption that C are samples from Gaussian pdf [Out['P_FA'], Out['log10P_FA']] = _FAfromPCE(Out['PCE'], np.prod(shift_range+1)) Out0 = dict(PCE=[], P_FA=[], log10P_FA=[]) Out0['PCE'] = (correl**2)/PCE_energy Out0['P_FA'], Out0['log10P_FA'] = _FAfromPCE(Out0['PCE'],1) return Out, Out0 # ---------------------------------------- def _RemoveNeighborhood(X,x,ssize): # Remove a 2-D neighborhood around x=[x1,x2] from matrix X and output a 1-D vector Y # ssize square neighborhood has size (ssize x ssize) square [M,N] = X.shape radius = (ssize-1)/2 X = np.roll(X,[int(radius-x[0]),int(radius-x[1])], axis=[0,1]) Y = X[ssize:,:ssize]; Y = Y.flatten() Y = np.concatenate([Y, X.flatten()[int(M*ssize):]], axis=0) return Y def _FAfromPCE(pce,search_space): # Calculates false alarm probability from having peak-to-cross-correlation (PCE) measure of the peak # pce PCE measure obtained from PCE.m # seach_space number of correlation samples from which the maximum is taken # USAGE: FA = FAfromPCE(31.5,32*32); [p,logp] = Fu.Qfunction(np.sign(pce)*np.sqrt(np.abs(pce))) if pce<50: FA = np.power(1-(1-p),search_space) else: FA = search_space*p # an approximation if FA==0: FA = search_space*p log10FA = np.log10(search_space)+logp*np.log10(np.exp(1)) else: log10FA = np.log10(FA) return FA, log10FA