File size: 4,599 Bytes
fc8df74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""
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