summaryrefslogtreecommitdiff
path: root/nitime/analysis/snr.py
blob: a05183fa4cb9b7e2486efe839b51599ac4fc4023 (plain)
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import numpy as np
from nitime.lazy import scipy_stats as stats

from nitime import descriptors as desc
from nitime import algorithms as tsa
from nitime import timeseries as ts

from nitime.index_utils import tril_indices_from


from .base import BaseAnalyzer


def signal_noise(response):
    """
    Signal and noise as defined in Borst and Theunissen 1999, Figure 2

    Parameters
    ----------

    response: nitime TimeSeries object
       The data here are individual responses of a single unit to the same
       stimulus, with repetitions being the first dimension and time as the
       last dimension
    """

    signal = np.mean(response.data, 0)  # The estimate of the signal is the
                                       # average response

    noise = response.data - signal  # Noise is the individual
                               # repetition's deviation from the
                               # estimate of the signal

    # Return TimeSeries objects with the sampling rate of the input:
    return  (ts.TimeSeries(signal, sampling_rate=response.sampling_rate),
             ts.TimeSeries(noise, sampling_rate=response.sampling_rate))


class SNRAnalyzer(BaseAnalyzer):
    """
    Calculate SNR for a response to repetitions of the same stimulus, according
    to (Borst, 1999) (Figure 2) and (Hsu, 2004).

    Hsu A, Borst A and Theunissen, FE (2004) Quantifying variability in neural
    responses ans its application for the validation of model
    predictions. Network: Comput Neural Syst 15:91-109

    Borst A and Theunissen FE (1999) Information theory and neural coding. Nat
    Neurosci 2:947-957
    """
    def __init__(self, input=None, bandwidth=None, adaptive=False,
                 low_bias=False):
        """
        Initializer for the multi_taper_SNR object

        Parameters
        ----------
        input: TimeSeries object

        bandwidth: float,
           The bandwidth of the windowing function will determine the number
           tapers to use. This parameters represents trade-off between
           frequency resolution (lower main lobe bandwidth for the taper) and
           variance reduction (higher bandwidth and number of averaged
           estimates). Per default will be set to 4 times the fundamental
           frequency, such that NW=4

        adaptive: bool, default to False
            Whether to set the weights for the tapered spectra according to the
            adaptive algorithm (Thompson, 2007).

        low_bias : bool, default to False
            Rather than use 2NW tapers, only use the tapers that have better
            than 90% spectral concentration within the bandwidth (still using a
            maximum of 2NW tapers)

        Notes
        -----

        Thompson, DJ (2007) Jackknifing multitaper spectrum estimates. IEEE
        Signal Processing Magazing. 24: 20-30

        """
        self.input = input
        self.signal, self.noise = signal_noise(input)
        self.bandwidth = bandwidth
        self.adaptive = adaptive
        self.low_bias = low_bias

    @desc.setattr_on_read
    def mt_frequencies(self):
        return np.linspace(0, self.input.sampling_rate / 2,
                           self.input.data.shape[-1] / 2 + 1)

    @desc.setattr_on_read
    def mt_signal_psd(self):
        _, p, _ = tsa.multi_taper_psd(self.signal.data,
                                    Fs=self.input.sampling_rate,
                                    BW=self.bandwidth,
                                    adaptive=self.adaptive,
                                    low_bias=self.low_bias)
        return p

    @desc.setattr_on_read
    def mt_noise_psd(self):
        p = np.empty((self.noise.data.shape[0],
                     self.noise.data.shape[-1] / 2 + 1))

        for i in range(p.shape[0]):
            _, p[i], _ = tsa.multi_taper_psd(self.noise.data[i],
                                    Fs=self.input.sampling_rate,
                                    BW=self.bandwidth,
                                    adaptive=self.adaptive,
                                    low_bias=self.low_bias)
        return np.mean(p, 0)

    @desc.setattr_on_read
    def mt_coherence(self):
        """ """
        return self.mt_signal_psd / (self.mt_signal_psd + self.mt_noise_psd)

    @desc.setattr_on_read
    def mt_information(self):
        df = self.mt_frequencies[1] - self.mt_frequencies[0]
        return -1 * np.log2(1 - self.mt_coherence) * df
        #These two formulations should be equivalent
        #return np.log2(1+self.mt_snr)

    @desc.setattr_on_read
    def mt_snr(self):
        return self.mt_signal_psd / self.mt_noise_psd

    @desc.setattr_on_read
    def correlation(self):
        """
        The correlation between all combinations of trials

        Returns
        -------
        (r,e) : tuple
           r is the mean correlation and e is the mean error of the correlation
           (with df = n_trials - 1)
        """

        c = np.corrcoef(self.input.data)
        c = c[tril_indices_from(c, -1)]

        return np.mean(c), stats.sem(c)