Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 110be94

Browse files
committed
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.
svn path=/trunk/matplotlib/; revision=6518
1 parent cddb7f5 commit 110be94

2 files changed

Lines changed: 73 additions & 36 deletions

File tree

lib/matplotlib/axes.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6719,13 +6719,13 @@ def hist(self, x, bins=10, range=None, normed=False, cumulative=False,
67196719

67206720
def psd(self, x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
67216721
window=mlab.window_hanning, noverlap=0, pad_to=None,
6722-
sides='default', **kwargs):
6722+
sides='default', scale_by_freq=None, **kwargs):
67236723
"""
67246724
call signature::
67256725
67266726
psd(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
67276727
window=mlab.window_hanning, noverlap=0, pad_to=None,
6728-
sides='default', **kwargs)
6728+
sides='default', scale_by_freq=None, **kwargs)
67296729
67306730
The power spectral density by Welch's average periodogram
67316731
method. The vector *x* is divided into *NFFT* length
@@ -6764,13 +6764,18 @@ def psd(self, x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
67646764
"""
67656765
if not self._hold: self.cla()
67666766
pxx, freqs = mlab.psd(x, NFFT, Fs, detrend, window, noverlap, pad_to,
6767-
sides)
6767+
sides, scale_by_freq)
67686768
pxx.shape = len(freqs),
67696769
freqs += Fc
67706770

6771+
if scale_by_freq in (None, True):
6772+
psd_units = 'dB/Hz'
6773+
else:
6774+
psd_units = 'dB'
6775+
67716776
self.plot(freqs, 10*np.log10(pxx), **kwargs)
67726777
self.set_xlabel('Frequency')
6773-
self.set_ylabel('Power Spectrum (dB)')
6778+
self.set_ylabel('Power Spectral Density (%s)' % psd_units)
67746779
self.grid(True)
67756780
vmin, vmax = self.viewLim.intervaly
67766781
intv = vmax-vmin
@@ -6791,13 +6796,13 @@ def psd(self, x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
67916796

67926797
def csd(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
67936798
window=mlab.window_hanning, noverlap=0, pad_to=None,
6794-
sides='default', **kwargs):
6799+
sides='default', scale_by_freq=None, **kwargs):
67956800
"""
67966801
call signature::
67976802
67986803
csd(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
67996804
window=mlab.window_hanning, noverlap=0, pad_to=None,
6800-
sides='default', **kwargs)
6805+
sides='default', scale_by_freq=None, **kwargs)
68016806
68026807
The cross spectral density :math:`P_{xy}` by Welch's average
68036808
periodogram method. The vectors *x* and *y* are divided into
@@ -6837,7 +6842,7 @@ def csd(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
68376842
"""
68386843
if not self._hold: self.cla()
68396844
pxy, freqs = mlab.csd(x, y, NFFT, Fs, detrend, window, noverlap,
6840-
pad_to, sides)
6845+
pad_to, sides, scale_by_freq)
68416846
pxy.shape = len(freqs),
68426847
# pxy is complex
68436848
freqs += Fc
@@ -6859,13 +6864,13 @@ def csd(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
68596864

68606865
def cohere(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
68616866
window=mlab.window_hanning, noverlap=0, pad_to=None,
6862-
sides='default', **kwargs):
6867+
sides='default', scale_by_freq=None, **kwargs):
68636868
"""
68646869
call signature::
68656870
68666871
cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend = mlab.detrend_none,
68676872
window = mlab.window_hanning, noverlap=0, pad_to=None,
6868-
sides='default', **kwargs)
6873+
sides='default', scale_by_freq=None, **kwargs)
68696874
68706875
cohere the coherence between *x* and *y*. Coherence is the normalized
68716876
cross spectral density:
@@ -6902,7 +6907,8 @@ def cohere(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
69026907
.. plot:: mpl_examples/pylab_examples/cohere_demo.py
69036908
"""
69046909
if not self._hold: self.cla()
6905-
cxy, freqs = mlab.cohere(x, y, NFFT, Fs, detrend, window, noverlap)
6910+
cxy, freqs = mlab.cohere(x, y, NFFT, Fs, detrend, window, noverlap,
6911+
scale_by_freq)
69066912
freqs += Fc
69076913

69086914
self.plot(freqs, cxy, **kwargs)
@@ -6915,13 +6921,15 @@ def cohere(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
69156921

69166922
def specgram(self, x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
69176923
window=mlab.window_hanning, noverlap=128,
6918-
cmap=None, xextent=None, pad_to=None, sides='default'):
6924+
cmap=None, xextent=None, pad_to=None, sides='default',
6925+
scale_by_freq=None):
69196926
"""
69206927
call signature::
69216928
69226929
specgram(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
69236930
window=mlab.window_hanning, noverlap=128,
6924-
cmap=None, xextent=None, pad_to=None, sides='default')
6931+
cmap=None, xextent=None, pad_to=None, sides='default',
6932+
scale_by_freq=None)
69256933
69266934
Compute a spectrogram of data in *x*. Data are split into
69276935
*NFFT* length segments and the PSD of each section is
@@ -6965,7 +6973,7 @@ def specgram(self, x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
69656973
if not self._hold: self.cla()
69666974

69676975
Pxx, freqs, bins = mlab.specgram(x, NFFT, Fs, detrend,
6968-
window, noverlap, pad_to, sides)
6976+
window, noverlap, pad_to, sides, scale_by_freq)
69696977

69706978
Z = 10. * np.log10(Pxx)
69716979
Z = np.flipud(Z)

lib/matplotlib/mlab.py

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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
245245
def _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

394420
psd.__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

426452
csd.__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*
474499
length 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

Comments
 (0)