@@ -243,14 +243,15 @@ def detrend_linear(y):
243243#This is a helper function that implements the commonality between the
244244#psd, csd, and spectrogram. It is *NOT* meant to be used outside of mlab
245245def _spectral_helper (x , y , NFFT = 256 , Fs = 2 , detrend = detrend_none ,
246- window = window_hanning , noverlap = 0 , pad_to = None , sides = 'default' ):
246+ window = window_hanning , noverlap = 0 , pad_to = None , sides = 'default' ,
247+ scale_by_freq = None ):
247248 #The checks for if y is x are so that we can use the same function to
248249 #implement the core of psd(), csd(), and spectrogram() without doing
249250 #extra calculations. We return the unaveraged Pxy, freqs, and t.
251+ same_data = y is x
250252
251253 #Make sure we're dealing with a numpy array. If y and x were the same
252254 #object to start with, keep them that way
253- same_data = y is x
254255
255256 x = np .asarray (x )
256257 if not same_data :
@@ -270,15 +271,31 @@ def _spectral_helper(x, y, NFFT=256, Fs=2, detrend=detrend_none,
270271 if pad_to is None :
271272 pad_to = NFFT
272273
274+ if scale_by_freq is None :
275+ warnings .warn ("psd, csd, and specgram have changed to scale their "
276+ "densities by the sampling frequency for better MatLab "
277+ "compatibility. You can pass scale_by_freq=False to disable "
278+ "this behavior. Also, one-sided densities are scaled by a "
279+ "factor of 2." )
280+ scale_by_freq = True
281+
273282 # For real x, ignore the negative frequencies unless told otherwise
274283 if (sides == 'default' and np .iscomplexobj (x )) or sides == 'twosided' :
275284 numFreqs = pad_to
285+ scaling_factor = 1.
276286 elif sides in ('default' , 'onesided' ):
277287 numFreqs = pad_to // 2 + 1
288+ scaling_factor = 2.
278289 else :
279290 raise ValueError ("sides must be one of: 'default', 'onesided', or "
280291 "'twosided'" )
281292
293+ # Matlab divides by the sampling frequency so that density function
294+ # has units of dB/Hz and can be integrated by the plotted frequency
295+ # values. Perform the same scaling here.
296+ if scale_by_freq :
297+ scaling_factor /= Fs
298+
282299 if cbook .iterable (window ):
283300 assert (len (window ) == NFFT )
284301 windowVals = window
@@ -305,8 +322,10 @@ def _spectral_helper(x, y, NFFT=256, Fs=2, detrend=detrend_none,
305322 Pxy [:,i ] = np .conjugate (fx [:numFreqs ]) * fy [:numFreqs ]
306323
307324 # Scale the spectrum by the norm of the window to compensate for
308- # windowing loss; see Bendat & Piersol Sec 11.5.2
309- Pxy /= (np .abs (windowVals )** 2 ).sum ()
325+ # windowing loss; see Bendat & Piersol Sec 11.5.2. Also include
326+ # scaling factors for one-sided densities and dividing by the sampling
327+ # frequency, if desired.
328+ Pxy *= scaling_factor / (np .abs (windowVals )** 2 ).sum ()
310329 t = 1. / Fs * (ind + NFFT / 2. )
311330 freqs = float (Fs ) / pad_to * np .arange (numFreqs )
312331
@@ -363,12 +382,18 @@ def _spectral_helper(x, y, NFFT=256, Fs=2, detrend=detrend_none,
363382 *sides*: [ 'default' | 'onesided' | 'twosided' ]
364383 Specifies which sides of the PSD to return. Default gives the
365384 default behavior, which returns one-sided for real data and both
366- for complex data. 'one' forces the return of a one-sided PSD, while
367- 'both' forces two-sided.
385+ for complex data. 'onesided' forces the return of a one-sided PSD,
386+ while 'twosided' forces two-sided.
387+
388+ *scale_by_freq*: boolean
389+ Specifies whether the resulting density values should be scaled
390+ by the scaling frequency, which gives density in units of Hz^-1.
391+ This allows for integration over the returned frequency values.
392+ The default is True for MatLab compatibility.
368393"""
369394
370- def psd (x , NFFT = 256 , Fs = 2 , detrend = detrend_none ,
371- window = window_hanning , noverlap = 0 , pad_to = None , sides = 'default' ):
395+ def psd (x , NFFT = 256 , Fs = 2 , detrend = detrend_none , window = window_hanning ,
396+ noverlap = 0 , pad_to = None , sides = 'default' , scale_by_freq = None ):
372397 """
373398 The power spectral density by Welch's average periodogram method.
374399 The vector *x* is divided into *NFFT* length blocks. Each block
@@ -388,13 +413,14 @@ def psd(x, NFFT=256, Fs=2, detrend=detrend_none,
388413 Bendat & Piersol -- Random Data: Analysis and Measurement
389414 Procedures, John Wiley & Sons (1986)
390415 """
391- Pxx ,freqs = csd (x , x , NFFT , Fs , detrend , window , noverlap , pad_to , sides )
416+ Pxx ,freqs = csd (x , x , NFFT , Fs , detrend , window , noverlap , pad_to , sides ,
417+ scale_by_freq )
392418 return Pxx .real ,freqs
393419
394420psd .__doc__ = psd .__doc__ % kwdocd
395421
396- def csd (x , y , NFFT = 256 , Fs = 2 , detrend = detrend_none ,
397- window = window_hanning , noverlap = 0 , pad_to = None , sides = 'default' ):
422+ def csd (x , y , NFFT = 256 , Fs = 2 , detrend = detrend_none , window = window_hanning ,
423+ noverlap = 0 , pad_to = None , sides = 'default' , scale_by_freq = None ):
398424 """
399425 The cross power spectral density by Welch's average periodogram
400426 method. The vectors *x* and *y* are divided into *NFFT* length
@@ -417,17 +443,16 @@ def csd(x, y, NFFT=256, Fs=2, detrend=detrend_none,
417443 Procedures, John Wiley & Sons (1986)
418444 """
419445 Pxy , freqs , t = _spectral_helper (x , y , NFFT , Fs , detrend , window ,
420- noverlap , pad_to , sides )
446+ noverlap , pad_to , sides , scale_by_freq )
421447
422448 if len (Pxy .shape ) == 2 and Pxy .shape [1 ]> 1 :
423449 Pxy = Pxy .mean (axis = 1 )
424450 return Pxy , freqs
425451
426452csd .__doc__ = csd .__doc__ % kwdocd
427453
428- def specgram (x , NFFT = 256 , Fs = 2 , detrend = detrend_none ,
429- window = window_hanning , noverlap = 128 , pad_to = None ,
430- sides = 'default' ):
454+ def specgram (x , NFFT = 256 , Fs = 2 , detrend = detrend_none , window = window_hanning ,
455+ noverlap = 128 , pad_to = None , sides = 'default' , scale_by_freq = None ):
431456 """
432457 Compute a spectrogram of data in *x*. Data are split into *NFFT*
433458 length segements and the PSD of each section is computed. The
@@ -458,7 +483,7 @@ def specgram(x, NFFT=256, Fs=2, detrend=detrend_none,
458483 assert (NFFT > noverlap )
459484
460485 Pxx , freqs , t = _spectral_helper (x , x , NFFT , Fs , detrend , window ,
461- noverlap , pad_to , sides )
486+ noverlap , pad_to , sides , scale_by_freq )
462487 Pxx = Pxx .real #Needed since helper implements generically
463488
464489 if (np .iscomplexobj (x ) and sides == 'default' ) or sides == 'twosided' :
@@ -473,8 +498,8 @@ def specgram(x, NFFT=256, Fs=2, detrend=detrend_none,
473498_coh_error = """Coherence is calculated by averaging over *NFFT*
474499length segments. Your signal is too short for your choice of *NFFT*.
475500"""
476- def cohere (x , y , NFFT = 256 , Fs = 2 , detrend = detrend_none ,
477- window = window_hanning , noverlap = 0 , pad_to = None , sides = 'default' ):
501+ def cohere (x , y , NFFT = 256 , Fs = 2 , detrend = detrend_none , window = window_hanning ,
502+ noverlap = 0 , pad_to = None , sides = 'default' , scale_by_freq = None ):
478503 """
479504 The coherence between *x* and *y*. Coherence is the normalized
480505 cross spectral density:
@@ -487,7 +512,9 @@ def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none,
487512 Array or sequence containing the data
488513 %(PSD)s
489514 The return value is the tuple (*Cxy*, *f*), where *f* are the
490- frequencies of the coherence vector.
515+ frequencies of the coherence vector. For cohere, scaling the
516+ individual densities by the sampling frequency has no effect, since
517+ the factors cancel out.
491518
492519 .. seealso::
493520 :func:`psd` and :func:`csd`:
@@ -497,9 +524,12 @@ def cohere(x, y, NFFT=256, Fs=2, detrend=detrend_none,
497524
498525 if len (x )< 2 * NFFT :
499526 raise ValueError (_coh_error )
500- Pxx , f = psd (x , NFFT , Fs , detrend , window , noverlap , pad_to , sides )
501- Pyy , f = psd (y , NFFT , Fs , detrend , window , noverlap , pad_to , sides )
502- Pxy , f = csd (x , y , NFFT , Fs , detrend , window , noverlap , pad_to , sides )
527+ Pxx , f = psd (x , NFFT , Fs , detrend , window , noverlap , pad_to , sides ,
528+ scale_by_freq )
529+ Pyy , f = psd (y , NFFT , Fs , detrend , window , noverlap , pad_to , sides ,
530+ scale_by_freq )
531+ Pxy , f = csd (x , y , NFFT , Fs , detrend , window , noverlap , pad_to , sides ,
532+ scale_by_freq )
503533
504534 Cxy = np .divide (np .absolute (Pxy )** 2 , Pxx * Pyy )
505535 Cxy .shape = (len (f ),)
@@ -3246,4 +3276,3 @@ def quad2cubic(q0x, q0y, q1x, q1y, q2x, q2y):
32463276 c2x , c2y = c1x + 1. / 3. * (q2x - q0x ), c1y + 1. / 3. * (q2y - q0y )
32473277 # c3x, c3y = q2x, q2y
32483278 return q0x , q0y , c1x , c1y , c2x , c2y , q2x , q2y
3249-
0 commit comments