Revision: 6518
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6518&view=rev
Author:   ryanmay
Date:     2008-12-08 21:15:13 +0000 (Mon, 08 Dec 2008)

Log Message:
-----------
Update spectral methods (psd, csd, etc.) to scale one-sided densities by a 
factor of 2 and, optionally, scale all densities by the sampling frequency.  
This gives better MatLab compatibility.  Update corresponding Axes methods, and 
make Axes.psd() label the y-axis of the plot as appropriate.

Modified Paths:
--------------
    trunk/matplotlib/lib/matplotlib/axes.py
    trunk/matplotlib/lib/matplotlib/mlab.py

Modified: trunk/matplotlib/lib/matplotlib/axes.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/axes.py     2008-12-08 21:06:49 UTC (rev 
6517)
+++ trunk/matplotlib/lib/matplotlib/axes.py     2008-12-08 21:15:13 UTC (rev 
6518)
@@ -6719,13 +6719,13 @@
 
     def psd(self, x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
             window=mlab.window_hanning, noverlap=0, pad_to=None,
-            sides='default', **kwargs):
+            sides='default', scale_by_freq=None, **kwargs):
         """
         call signature::
 
           psd(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
               window=mlab.window_hanning, noverlap=0, pad_to=None,
-              sides='default', **kwargs)
+              sides='default', scale_by_freq=None, **kwargs)
 
         The power spectral density by Welch's average periodogram
         method.  The vector *x* is divided into *NFFT* length
@@ -6764,13 +6764,18 @@
         """
         if not self._hold: self.cla()
         pxx, freqs = mlab.psd(x, NFFT, Fs, detrend, window, noverlap, pad_to,
-            sides)
+            sides, scale_by_freq)
         pxx.shape = len(freqs),
         freqs += Fc
 
+        if scale_by_freq in (None, True):
+            psd_units = 'dB/Hz'
+        else:
+            psd_units = 'dB'
+
         self.plot(freqs, 10*np.log10(pxx), **kwargs)
         self.set_xlabel('Frequency')
-        self.set_ylabel('Power Spectrum (dB)')
+        self.set_ylabel('Power Spectral Density (%s)' % psd_units)
         self.grid(True)
         vmin, vmax = self.viewLim.intervaly
         intv = vmax-vmin
@@ -6791,13 +6796,13 @@
 
     def csd(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
             window=mlab.window_hanning, noverlap=0, pad_to=None,
-            sides='default', **kwargs):
+            sides='default', scale_by_freq=None, **kwargs):
         """
         call signature::
 
           csd(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
               window=mlab.window_hanning, noverlap=0, pad_to=None,
-              sides='default', **kwargs)
+              sides='default', scale_by_freq=None, **kwargs)
 
         The cross spectral density :math:`P_{xy}` by Welch's average
         periodogram method.  The vectors *x* and *y* are divided into
@@ -6837,7 +6842,7 @@
         """
         if not self._hold: self.cla()
         pxy, freqs = mlab.csd(x, y, NFFT, Fs, detrend, window, noverlap,
-            pad_to, sides)
+            pad_to, sides, scale_by_freq)
         pxy.shape = len(freqs),
         # pxy is complex
         freqs += Fc
@@ -6859,13 +6864,13 @@
 
     def cohere(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
                window=mlab.window_hanning, noverlap=0, pad_to=None,
-               sides='default', **kwargs):
+               sides='default', scale_by_freq=None, **kwargs):
         """
         call signature::
 
           cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend = mlab.detrend_none,
                  window = mlab.window_hanning, noverlap=0, pad_to=None,
-                 sides='default', **kwargs)
+                 sides='default', scale_by_freq=None, **kwargs)
 
         cohere the coherence between *x* and *y*.  Coherence is the normalized
         cross spectral density:
@@ -6902,7 +6907,8 @@
         .. plot:: mpl_examples/pylab_examples/cohere_demo.py
         """
         if not self._hold: self.cla()
-        cxy, freqs = mlab.cohere(x, y, NFFT, Fs, detrend, window, noverlap)
+        cxy, freqs = mlab.cohere(x, y, NFFT, Fs, detrend, window, noverlap,
+            scale_by_freq)
         freqs += Fc
 
         self.plot(freqs, cxy, **kwargs)
@@ -6915,13 +6921,15 @@
 
     def specgram(self, x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
                  window=mlab.window_hanning, noverlap=128,
-                 cmap=None, xextent=None, pad_to=None, sides='default'):
+                 cmap=None, xextent=None, pad_to=None, sides='default',
+                 scale_by_freq=None):
         """
         call signature::
 
           specgram(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
                    window=mlab.window_hanning, noverlap=128,
-                   cmap=None, xextent=None, pad_to=None, sides='default')
+                   cmap=None, xextent=None, pad_to=None, sides='default',
+                   scale_by_freq=None)
 
         Compute a spectrogram of data in *x*.  Data are split into
         *NFFT* length segments and the PSD of each section is
@@ -6965,7 +6973,7 @@
         if not self._hold: self.cla()
 
         Pxx, freqs, bins = mlab.specgram(x, NFFT, Fs, detrend,
-             window, noverlap, pad_to, sides)
+             window, noverlap, pad_to, sides, scale_by_freq)
 
         Z = 10. * np.log10(Pxx)
         Z = np.flipud(Z)

Modified: trunk/matplotlib/lib/matplotlib/mlab.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/mlab.py     2008-12-08 21:06:49 UTC (rev 
6517)
+++ trunk/matplotlib/lib/matplotlib/mlab.py     2008-12-08 21:15:13 UTC (rev 
6518)
@@ -243,14 +243,15 @@
 #This is a helper function that implements the commonality between the
 #psd, csd, and spectrogram.  It is *NOT* meant to be used outside of mlab
 def _spectral_helper(x, y, NFFT=256, Fs=2, detrend=detrend_none,
-        window=window_hanning, noverlap=0, pad_to=None, sides='default'):
+        window=window_hanning, noverlap=0, pad_to=None, sides='default',
+        scale_by_freq=None):
     #The checks for if y is x are so that we can use the same function to
     #implement the core of psd(), csd(), and spectrogram() without doing
     #extra calculations.  We return the unaveraged Pxy, freqs, and t.
+    same_data = y is x
 
     #Make sure we're dealing with a numpy array. If y and x were the same
     #object to start with, keep them that way
-    same_data = y is x
 
     x = np.asarray(x)
     if not same_data:
@@ -270,15 +271,31 @@
     if pad_to is None:
         pad_to = NFFT
 
+    if scale_by_freq is None:
+        warnings.warn("psd, csd, and specgram have changed to scale their "
+            "densities by the sampling frequency for better MatLab "
+            "compatibility. You can pass scale_by_freq=False to disable "
+            "this behavior.  Also, one-sided densities are scaled by a "
+            "factor of 2.")
+        scale_by_freq = True
+
     # For real x, ignore the negative frequencies unless told otherwise
     if (sides == 'default' and np.iscomplexobj(x)) or sides == 'twosided':
         numFreqs = pad_to
+        scaling_factor = 1.
     elif sides in ('default', 'onesided'):
         numFreqs = pad_to//2 + 1
+        scaling_factor = 2.
     else:
         raise ValueError("sides must be one of: 'default', 'onesided', or "
             "'twosided'")
 
+    # Matlab divides by the sampling frequency so that density function
+    # has units of dB/Hz and can be integrated by the plotted frequency
+    # values. Perform the same scaling here.
+    if scale_by_freq:
+        scaling_factor /= Fs
+
     if cbook.iterable(window):
         assert(len(window) == NFFT)
         windowVals = window
@@ -305,8 +322,10 @@
         Pxy[:,i] = np.conjugate(fx[:numFreqs]) * fy[:numFreqs]
 
     # Scale the spectrum by the norm of the window to compensate for
-    # windowing loss; see Bendat & Piersol Sec 11.5.2
-    Pxy /= (np.abs(windowVals)**2).sum()
+    # windowing loss; see Bendat & Piersol Sec 11.5.2.  Also include
+    # scaling factors for one-sided densities and dividing by the sampling
+    # frequency, if desired.
+    Pxy *= scaling_factor / (np.abs(windowVals)**2).sum()
     t = 1./Fs * (ind + NFFT / 2.)
     freqs = float(Fs) / pad_to * np.arange(numFreqs)
 
@@ -363,12 +382,18 @@
       *sides*: [ 'default' | 'onesided' | 'twosided' ]
           Specifies which sides of the PSD to return.  Default gives the
           default behavior, which returns one-sided for real data and both
-          for complex data.  'one' forces the return of a one-sided PSD, while
-          'both' forces two-sided.
+          for complex data.  'onesided' forces the return of a one-sided PSD,
+          while 'twosided' forces two-sided.
+
+      *scale_by_freq*: boolean
+          Specifies whether the resulting density values should be scaled
+          by the scaling frequency, which gives density in units of Hz^-1.
+          This allows for integration over the returned frequency values.
+          The default is True for MatLab compatibility.
 """
 
-def psd(x, NFFT=256, Fs=2, detrend=detrend_none,
-        window=window_hanning, noverlap=0, pad_to=None, sides='default'):
+def psd(x, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning,
+        noverlap=0, pad_to=None, sides='default', scale_by_freq=None):
     """
     The power spectral density by Welch's average periodogram method.
     The vector *x* is divided into *NFFT* length blocks.  Each block
@@ -388,13 +413,14 @@
         Bendat & Piersol -- Random Data: Analysis and Measurement
         Procedures, John Wiley & Sons (1986)
     """
-    Pxx,freqs = csd(x, x, NFFT, Fs, detrend, window, noverlap, pad_to, sides)
+    Pxx,freqs = csd(x, x, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
+        scale_by_freq)
     return Pxx.real,freqs
 
 psd.__doc__ = psd.__doc__ % kwdocd
 
-def csd(x, y, NFFT=256, Fs=2, detrend=detrend_none,
-        window=window_hanning, noverlap=0, pad_to=None, sides='default'):
+def csd(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning,
+        noverlap=0, pad_to=None, sides='default', scale_by_freq=None):
     """
     The cross power spectral density by Welch's average periodogram
     method.  The vectors *x* and *y* are divided into *NFFT* length
@@ -417,7 +443,7 @@
         Procedures, John Wiley & Sons (1986)
     """
     Pxy, freqs, t = _spectral_helper(x, y, NFFT, Fs, detrend, window,
-        noverlap, pad_to, sides)
+        noverlap, pad_to, sides, scale_by_freq)
 
     if len(Pxy.shape) == 2 and Pxy.shape[1]>1:
         Pxy = Pxy.mean(axis=1)
@@ -425,9 +451,8 @@
 
 csd.__doc__ = csd.__doc__ % kwdocd
 
-def specgram(x, NFFT=256, Fs=2, detrend=detrend_none,
-             window=window_hanning, noverlap=128, pad_to=None,
-             sides='default'):
+def specgram(x, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning,
+        noverlap=128, pad_to=None, sides='default', scale_by_freq=None):
     """
     Compute a spectrogram of data in *x*.  Data are split into *NFFT*
     length segements and the PSD of each section is computed.  The
@@ -458,7 +483,7 @@
     assert(NFFT > noverlap)
 
     Pxx, freqs, t = _spectral_helper(x, x, NFFT, Fs, detrend, window,
-        noverlap, pad_to, sides)
+        noverlap, pad_to, sides, scale_by_freq)
     Pxx = Pxx.real #Needed since helper implements generically
 
     if (np.iscomplexobj(x) and sides == 'default') or sides == 'twosided':
@@ -473,8 +498,8 @@
 _coh_error = """Coherence is calculated by averaging over *NFFT*
 length segments.  Your signal is too short for your choice of *NFFT*.
 """
-def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none,
-           window=window_hanning, noverlap=0, pad_to=None, sides='default'):
+def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none, window=window_hanning,
+        noverlap=0, pad_to=None, sides='default', scale_by_freq=None):
     """
     The coherence between *x* and *y*.  Coherence is the normalized
     cross spectral density:
@@ -487,7 +512,9 @@
         Array or sequence containing the data
     %(PSD)s
     The return value is the tuple (*Cxy*, *f*), where *f* are the
-    frequencies of the coherence vector.
+    frequencies of the coherence vector. For cohere, scaling the
+    individual densities by the sampling frequency has no effect, since
+    the factors cancel out.
 
     .. seealso::
         :func:`psd` and :func:`csd`:
@@ -497,9 +524,12 @@
 
     if len(x)<2*NFFT:
         raise ValueError(_coh_error)
-    Pxx, f = psd(x, NFFT, Fs, detrend, window, noverlap, pad_to, sides)
-    Pyy, f = psd(y, NFFT, Fs, detrend, window, noverlap, pad_to, sides)
-    Pxy, f = csd(x, y, NFFT, Fs, detrend, window, noverlap, pad_to, sides)
+    Pxx, f = psd(x, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
+        scale_by_freq)
+    Pyy, f = psd(y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
+        scale_by_freq)
+    Pxy, f = csd(x, y, NFFT, Fs, detrend, window, noverlap, pad_to, sides,
+        scale_by_freq)
 
     Cxy = np.divide(np.absolute(Pxy)**2, Pxx*Pyy)
     Cxy.shape = (len(f),)
@@ -3246,4 +3276,3 @@
     c2x, c2y = c1x + 1./3. * (q2x - q0x), c1y + 1./3. * (q2y - q0y)
     # c3x, c3y = q2x, q2y
     return q0x, q0y, c1x, c1y, c2x, c2y, q2x, q2y
-


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
SF.Net email is Sponsored by MIX09, March 18-20, 2009 in Las Vegas, Nevada.
The future of the web can't happen without you.  Join us at MIX09 to help
pave the way to the Next Web now. Learn more and register at
http://ad.doubleclick.net/clk;208669438;13503038;i?http://2009.visitmix.com/
_______________________________________________
Matplotlib-checkins mailing list
Matplotlib-checkins@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to