1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059 |
- """Tools for spectral analysis.
- """
- import numpy as np
- from scipy import fft as sp_fft
- from . import _signaltools
- from .windows import get_window
- from ._spectral import _lombscargle
- from ._arraytools import const_ext, even_ext, odd_ext, zero_ext
- import warnings
- __all__ = ['periodogram', 'welch', 'lombscargle', 'csd', 'coherence',
- 'spectrogram', 'stft', 'istft', 'check_COLA', 'check_NOLA']
- def lombscargle(x,
- y,
- freqs,
- precenter=False,
- normalize=False):
- """
- lombscargle(x, y, freqs)
- Computes the Lomb-Scargle periodogram.
- The Lomb-Scargle periodogram was developed by Lomb [1]_ and further
- extended by Scargle [2]_ to find, and test the significance of weak
- periodic signals with uneven temporal sampling.
- When *normalize* is False (default) the computed periodogram
- is unnormalized, it takes the value ``(A**2) * N/4`` for a harmonic
- signal with amplitude A for sufficiently large N.
- When *normalize* is True the computed periodogram is normalized by
- the residuals of the data around a constant reference model (at zero).
- Input arrays should be 1-D and will be cast to float64.
- Parameters
- ----------
- x : array_like
- Sample times.
- y : array_like
- Measurement values.
- freqs : array_like
- Angular frequencies for output periodogram.
- precenter : bool, optional
- Pre-center measurement values by subtracting the mean.
- normalize : bool, optional
- Compute normalized periodogram.
- Returns
- -------
- pgram : array_like
- Lomb-Scargle periodogram.
- Raises
- ------
- ValueError
- If the input arrays `x` and `y` do not have the same shape.
- See Also
- --------
- istft: Inverse Short Time Fourier Transform
- check_COLA: Check whether the Constant OverLap Add (COLA) constraint is met
- welch: Power spectral density by Welch's method
- spectrogram: Spectrogram by Welch's method
- csd: Cross spectral density by Welch's method
- Notes
- -----
- This subroutine calculates the periodogram using a slightly
- modified algorithm due to Townsend [3]_ which allows the
- periodogram to be calculated using only a single pass through
- the input arrays for each frequency.
- The algorithm running time scales roughly as O(x * freqs) or O(N^2)
- for a large number of samples and frequencies.
- References
- ----------
- .. [1] N.R. Lomb "Least-squares frequency analysis of unequally spaced
- data", Astrophysics and Space Science, vol 39, pp. 447-462, 1976
- .. [2] J.D. Scargle "Studies in astronomical time series analysis. II -
- Statistical aspects of spectral analysis of unevenly spaced data",
- The Astrophysical Journal, vol 263, pp. 835-853, 1982
- .. [3] R.H.D. Townsend, "Fast calculation of the Lomb-Scargle
- periodogram using graphics processing units.", The Astrophysical
- Journal Supplement Series, vol 191, pp. 247-253, 2010
- Examples
- --------
- >>> import numpy as np
- >>> import matplotlib.pyplot as plt
- >>> rng = np.random.default_rng()
- First define some input parameters for the signal:
- >>> A = 2.
- >>> w0 = 1. # rad/sec
- >>> nin = 150
- >>> nout = 100000
- Randomly generate sample times:
- >>> x = rng.uniform(0, 10*np.pi, nin)
- Plot a sine wave for the selected times:
- >>> y = A * np.cos(w0*x)
- Define the array of frequencies for which to compute the periodogram:
- >>> w = np.linspace(0.01, 10, nout)
- Calculate Lomb-Scargle periodogram:
- >>> import scipy.signal as signal
- >>> pgram = signal.lombscargle(x, y, w, normalize=True)
- Now make a plot of the input data:
- >>> fig, (ax_t, ax_w) = plt.subplots(2, 1, constrained_layout=True)
- >>> ax_t.plot(x, y, 'b+')
- >>> ax_t.set_xlabel('Time [s]')
- Then plot the normalized periodogram:
- >>> ax_w.plot(w, pgram)
- >>> ax_w.set_xlabel('Angular frequency [rad/s]')
- >>> ax_w.set_ylabel('Normalized amplitude')
- >>> plt.show()
- """
- x = np.ascontiguousarray(x, dtype=np.float64)
- y = np.ascontiguousarray(y, dtype=np.float64)
- freqs = np.ascontiguousarray(freqs, dtype=np.float64)
- assert x.ndim == 1
- assert y.ndim == 1
- assert freqs.ndim == 1
- if precenter:
- pgram = _lombscargle(x, y - y.mean(), freqs)
- else:
- pgram = _lombscargle(x, y, freqs)
- if normalize:
- pgram *= 2 / np.dot(y, y)
- return pgram
- def periodogram(x, fs=1.0, window='boxcar', nfft=None, detrend='constant',
- return_onesided=True, scaling='density', axis=-1):
- """
- Estimate power spectral density using a periodogram.
- Parameters
- ----------
- x : array_like
- Time series of measurement values
- fs : float, optional
- Sampling frequency of the `x` time series. Defaults to 1.0.
- window : str or tuple or array_like, optional
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be equal to the length
- of the axis over which the periodogram is computed. Defaults
- to 'boxcar'.
- nfft : int, optional
- Length of the FFT used. If `None` the length of `x` will be
- used.
- detrend : str or function or `False`, optional
- Specifies how to detrend each segment. If `detrend` is a
- string, it is passed as the `type` argument to the `detrend`
- function. If it is a function, it takes a segment and returns a
- detrended segment. If `detrend` is `False`, no detrending is
- done. Defaults to 'constant'.
- return_onesided : bool, optional
- If `True`, return a one-sided spectrum for real data. If
- `False` return a two-sided spectrum. Defaults to `True`, but for
- complex data, a two-sided spectrum is always returned.
- scaling : { 'density', 'spectrum' }, optional
- Selects between computing the power spectral density ('density')
- where `Pxx` has units of V**2/Hz and computing the power
- spectrum ('spectrum') where `Pxx` has units of V**2, if `x`
- is measured in V and `fs` is measured in Hz. Defaults to
- 'density'
- axis : int, optional
- Axis along which the periodogram is computed; the default is
- over the last axis (i.e. ``axis=-1``).
- Returns
- -------
- f : ndarray
- Array of sample frequencies.
- Pxx : ndarray
- Power spectral density or power spectrum of `x`.
- See Also
- --------
- welch: Estimate power spectral density using Welch's method
- lombscargle: Lomb-Scargle periodogram for unevenly sampled data
- Notes
- -----
- .. versionadded:: 0.12.0
- Examples
- --------
- >>> import numpy as np
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- >>> rng = np.random.default_rng()
- Generate a test signal, a 2 Vrms sine wave at 1234 Hz, corrupted by
- 0.001 V**2/Hz of white noise sampled at 10 kHz.
- >>> fs = 10e3
- >>> N = 1e5
- >>> amp = 2*np.sqrt(2)
- >>> freq = 1234.0
- >>> noise_power = 0.001 * fs / 2
- >>> time = np.arange(N) / fs
- >>> x = amp*np.sin(2*np.pi*freq*time)
- >>> x += rng.normal(scale=np.sqrt(noise_power), size=time.shape)
- Compute and plot the power spectral density.
- >>> f, Pxx_den = signal.periodogram(x, fs)
- >>> plt.semilogy(f, Pxx_den)
- >>> plt.ylim([1e-7, 1e2])
- >>> plt.xlabel('frequency [Hz]')
- >>> plt.ylabel('PSD [V**2/Hz]')
- >>> plt.show()
- If we average the last half of the spectral density, to exclude the
- peak, we can recover the noise power on the signal.
- >>> np.mean(Pxx_den[25000:])
- 0.000985320699252543
- Now compute and plot the power spectrum.
- >>> f, Pxx_spec = signal.periodogram(x, fs, 'flattop', scaling='spectrum')
- >>> plt.figure()
- >>> plt.semilogy(f, np.sqrt(Pxx_spec))
- >>> plt.ylim([1e-4, 1e1])
- >>> plt.xlabel('frequency [Hz]')
- >>> plt.ylabel('Linear spectrum [V RMS]')
- >>> plt.show()
- The peak height in the power spectrum is an estimate of the RMS
- amplitude.
- >>> np.sqrt(Pxx_spec.max())
- 2.0077340678640727
- """
- x = np.asarray(x)
- if x.size == 0:
- return np.empty(x.shape), np.empty(x.shape)
- if window is None:
- window = 'boxcar'
- if nfft is None:
- nperseg = x.shape[axis]
- elif nfft == x.shape[axis]:
- nperseg = nfft
- elif nfft > x.shape[axis]:
- nperseg = x.shape[axis]
- elif nfft < x.shape[axis]:
- s = [np.s_[:]]*len(x.shape)
- s[axis] = np.s_[:nfft]
- x = x[tuple(s)]
- nperseg = nfft
- nfft = None
- if hasattr(window, 'size'):
- if window.size != nperseg:
- raise ValueError('the size of the window must be the same size '
- 'of the input on the specified axis')
- return welch(x, fs=fs, window=window, nperseg=nperseg, noverlap=0,
- nfft=nfft, detrend=detrend, return_onesided=return_onesided,
- scaling=scaling, axis=axis)
- def welch(x, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None,
- detrend='constant', return_onesided=True, scaling='density',
- axis=-1, average='mean'):
- r"""
- Estimate power spectral density using Welch's method.
- Welch's method [1]_ computes an estimate of the power spectral
- density by dividing the data into overlapping segments, computing a
- modified periodogram for each segment and averaging the
- periodograms.
- Parameters
- ----------
- x : array_like
- Time series of measurement values
- fs : float, optional
- Sampling frequency of the `x` time series. Defaults to 1.0.
- window : str or tuple or array_like, optional
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg. Defaults
- to a Hann window.
- nperseg : int, optional
- Length of each segment. Defaults to None, but if window is str or
- tuple, is set to 256, and if window is array_like, is set to the
- length of the window.
- noverlap : int, optional
- Number of points to overlap between segments. If `None`,
- ``noverlap = nperseg // 2``. Defaults to `None`.
- nfft : int, optional
- Length of the FFT used, if a zero padded FFT is desired. If
- `None`, the FFT length is `nperseg`. Defaults to `None`.
- detrend : str or function or `False`, optional
- Specifies how to detrend each segment. If `detrend` is a
- string, it is passed as the `type` argument to the `detrend`
- function. If it is a function, it takes a segment and returns a
- detrended segment. If `detrend` is `False`, no detrending is
- done. Defaults to 'constant'.
- return_onesided : bool, optional
- If `True`, return a one-sided spectrum for real data. If
- `False` return a two-sided spectrum. Defaults to `True`, but for
- complex data, a two-sided spectrum is always returned.
- scaling : { 'density', 'spectrum' }, optional
- Selects between computing the power spectral density ('density')
- where `Pxx` has units of V**2/Hz and computing the power
- spectrum ('spectrum') where `Pxx` has units of V**2, if `x`
- is measured in V and `fs` is measured in Hz. Defaults to
- 'density'
- axis : int, optional
- Axis along which the periodogram is computed; the default is
- over the last axis (i.e. ``axis=-1``).
- average : { 'mean', 'median' }, optional
- Method to use when averaging periodograms. Defaults to 'mean'.
- .. versionadded:: 1.2.0
- Returns
- -------
- f : ndarray
- Array of sample frequencies.
- Pxx : ndarray
- Power spectral density or power spectrum of x.
- See Also
- --------
- periodogram: Simple, optionally modified periodogram
- lombscargle: Lomb-Scargle periodogram for unevenly sampled data
- Notes
- -----
- An appropriate amount of overlap will depend on the choice of window
- and on your requirements. For the default Hann window an overlap of
- 50% is a reasonable trade off between accurately estimating the
- signal power, while not over counting any of the data. Narrower
- windows may require a larger overlap.
- If `noverlap` is 0, this method is equivalent to Bartlett's method
- [2]_.
- .. versionadded:: 0.12.0
- References
- ----------
- .. [1] P. Welch, "The use of the fast Fourier transform for the
- estimation of power spectra: A method based on time averaging
- over short, modified periodograms", IEEE Trans. Audio
- Electroacoust. vol. 15, pp. 70-73, 1967.
- .. [2] M.S. Bartlett, "Periodogram Analysis and Continuous Spectra",
- Biometrika, vol. 37, pp. 1-16, 1950.
- Examples
- --------
- >>> import numpy as np
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- >>> rng = np.random.default_rng()
- Generate a test signal, a 2 Vrms sine wave at 1234 Hz, corrupted by
- 0.001 V**2/Hz of white noise sampled at 10 kHz.
- >>> fs = 10e3
- >>> N = 1e5
- >>> amp = 2*np.sqrt(2)
- >>> freq = 1234.0
- >>> noise_power = 0.001 * fs / 2
- >>> time = np.arange(N) / fs
- >>> x = amp*np.sin(2*np.pi*freq*time)
- >>> x += rng.normal(scale=np.sqrt(noise_power), size=time.shape)
- Compute and plot the power spectral density.
- >>> f, Pxx_den = signal.welch(x, fs, nperseg=1024)
- >>> plt.semilogy(f, Pxx_den)
- >>> plt.ylim([0.5e-3, 1])
- >>> plt.xlabel('frequency [Hz]')
- >>> plt.ylabel('PSD [V**2/Hz]')
- >>> plt.show()
- If we average the last half of the spectral density, to exclude the
- peak, we can recover the noise power on the signal.
- >>> np.mean(Pxx_den[256:])
- 0.0009924865443739191
- Now compute and plot the power spectrum.
- >>> f, Pxx_spec = signal.welch(x, fs, 'flattop', 1024, scaling='spectrum')
- >>> plt.figure()
- >>> plt.semilogy(f, np.sqrt(Pxx_spec))
- >>> plt.xlabel('frequency [Hz]')
- >>> plt.ylabel('Linear spectrum [V RMS]')
- >>> plt.show()
- The peak height in the power spectrum is an estimate of the RMS
- amplitude.
- >>> np.sqrt(Pxx_spec.max())
- 2.0077340678640727
- If we now introduce a discontinuity in the signal, by increasing the
- amplitude of a small portion of the signal by 50, we can see the
- corruption of the mean average power spectral density, but using a
- median average better estimates the normal behaviour.
- >>> x[int(N//2):int(N//2)+10] *= 50.
- >>> f, Pxx_den = signal.welch(x, fs, nperseg=1024)
- >>> f_med, Pxx_den_med = signal.welch(x, fs, nperseg=1024, average='median')
- >>> plt.semilogy(f, Pxx_den, label='mean')
- >>> plt.semilogy(f_med, Pxx_den_med, label='median')
- >>> plt.ylim([0.5e-3, 1])
- >>> plt.xlabel('frequency [Hz]')
- >>> plt.ylabel('PSD [V**2/Hz]')
- >>> plt.legend()
- >>> plt.show()
- """
- freqs, Pxx = csd(x, x, fs=fs, window=window, nperseg=nperseg,
- noverlap=noverlap, nfft=nfft, detrend=detrend,
- return_onesided=return_onesided, scaling=scaling,
- axis=axis, average=average)
- return freqs, Pxx.real
- def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None,
- detrend='constant', return_onesided=True, scaling='density',
- axis=-1, average='mean'):
- r"""
- Estimate the cross power spectral density, Pxy, using Welch's method.
- Parameters
- ----------
- x : array_like
- Time series of measurement values
- y : array_like
- Time series of measurement values
- fs : float, optional
- Sampling frequency of the `x` and `y` time series. Defaults
- to 1.0.
- window : str or tuple or array_like, optional
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg. Defaults
- to a Hann window.
- nperseg : int, optional
- Length of each segment. Defaults to None, but if window is str or
- tuple, is set to 256, and if window is array_like, is set to the
- length of the window.
- noverlap: int, optional
- Number of points to overlap between segments. If `None`,
- ``noverlap = nperseg // 2``. Defaults to `None`.
- nfft : int, optional
- Length of the FFT used, if a zero padded FFT is desired. If
- `None`, the FFT length is `nperseg`. Defaults to `None`.
- detrend : str or function or `False`, optional
- Specifies how to detrend each segment. If `detrend` is a
- string, it is passed as the `type` argument to the `detrend`
- function. If it is a function, it takes a segment and returns a
- detrended segment. If `detrend` is `False`, no detrending is
- done. Defaults to 'constant'.
- return_onesided : bool, optional
- If `True`, return a one-sided spectrum for real data. If
- `False` return a two-sided spectrum. Defaults to `True`, but for
- complex data, a two-sided spectrum is always returned.
- scaling : { 'density', 'spectrum' }, optional
- Selects between computing the cross spectral density ('density')
- where `Pxy` has units of V**2/Hz and computing the cross spectrum
- ('spectrum') where `Pxy` has units of V**2, if `x` and `y` are
- measured in V and `fs` is measured in Hz. Defaults to 'density'
- axis : int, optional
- Axis along which the CSD is computed for both inputs; the
- default is over the last axis (i.e. ``axis=-1``).
- average : { 'mean', 'median' }, optional
- Method to use when averaging periodograms. If the spectrum is
- complex, the average is computed separately for the real and
- imaginary parts. Defaults to 'mean'.
- .. versionadded:: 1.2.0
- Returns
- -------
- f : ndarray
- Array of sample frequencies.
- Pxy : ndarray
- Cross spectral density or cross power spectrum of x,y.
- See Also
- --------
- periodogram: Simple, optionally modified periodogram
- lombscargle: Lomb-Scargle periodogram for unevenly sampled data
- welch: Power spectral density by Welch's method. [Equivalent to
- csd(x,x)]
- coherence: Magnitude squared coherence by Welch's method.
- Notes
- -----
- By convention, Pxy is computed with the conjugate FFT of X
- multiplied by the FFT of Y.
- If the input series differ in length, the shorter series will be
- zero-padded to match.
- An appropriate amount of overlap will depend on the choice of window
- and on your requirements. For the default Hann window an overlap of
- 50% is a reasonable trade off between accurately estimating the
- signal power, while not over counting any of the data. Narrower
- windows may require a larger overlap.
- .. versionadded:: 0.16.0
- References
- ----------
- .. [1] P. Welch, "The use of the fast Fourier transform for the
- estimation of power spectra: A method based on time averaging
- over short, modified periodograms", IEEE Trans. Audio
- Electroacoust. vol. 15, pp. 70-73, 1967.
- .. [2] Rabiner, Lawrence R., and B. Gold. "Theory and Application of
- Digital Signal Processing" Prentice-Hall, pp. 414-419, 1975
- Examples
- --------
- >>> import numpy as np
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- >>> rng = np.random.default_rng()
- Generate two test signals with some common features.
- >>> fs = 10e3
- >>> N = 1e5
- >>> amp = 20
- >>> freq = 1234.0
- >>> noise_power = 0.001 * fs / 2
- >>> time = np.arange(N) / fs
- >>> b, a = signal.butter(2, 0.25, 'low')
- >>> x = rng.normal(scale=np.sqrt(noise_power), size=time.shape)
- >>> y = signal.lfilter(b, a, x)
- >>> x += amp*np.sin(2*np.pi*freq*time)
- >>> y += rng.normal(scale=0.1*np.sqrt(noise_power), size=time.shape)
- Compute and plot the magnitude of the cross spectral density.
- >>> f, Pxy = signal.csd(x, y, fs, nperseg=1024)
- >>> plt.semilogy(f, np.abs(Pxy))
- >>> plt.xlabel('frequency [Hz]')
- >>> plt.ylabel('CSD [V**2/Hz]')
- >>> plt.show()
- """
- freqs, _, Pxy = _spectral_helper(x, y, fs, window, nperseg, noverlap, nfft,
- detrend, return_onesided, scaling, axis,
- mode='psd')
- # Average over windows.
- if len(Pxy.shape) >= 2 and Pxy.size > 0:
- if Pxy.shape[-1] > 1:
- if average == 'median':
- # np.median must be passed real arrays for the desired result
- bias = _median_bias(Pxy.shape[-1])
- if np.iscomplexobj(Pxy):
- Pxy = (np.median(np.real(Pxy), axis=-1)
- + 1j * np.median(np.imag(Pxy), axis=-1))
- else:
- Pxy = np.median(Pxy, axis=-1)
- Pxy /= bias
- elif average == 'mean':
- Pxy = Pxy.mean(axis=-1)
- else:
- raise ValueError('average must be "median" or "mean", got %s'
- % (average,))
- else:
- Pxy = np.reshape(Pxy, Pxy.shape[:-1])
- return freqs, Pxy
- def spectrogram(x, fs=1.0, window=('tukey', .25), nperseg=None, noverlap=None,
- nfft=None, detrend='constant', return_onesided=True,
- scaling='density', axis=-1, mode='psd'):
- """Compute a spectrogram with consecutive Fourier transforms.
- Spectrograms can be used as a way of visualizing the change of a
- nonstationary signal's frequency content over time.
- Parameters
- ----------
- x : array_like
- Time series of measurement values
- fs : float, optional
- Sampling frequency of the `x` time series. Defaults to 1.0.
- window : str or tuple or array_like, optional
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg.
- Defaults to a Tukey window with shape parameter of 0.25.
- nperseg : int, optional
- Length of each segment. Defaults to None, but if window is str or
- tuple, is set to 256, and if window is array_like, is set to the
- length of the window.
- noverlap : int, optional
- Number of points to overlap between segments. If `None`,
- ``noverlap = nperseg // 8``. Defaults to `None`.
- nfft : int, optional
- Length of the FFT used, if a zero padded FFT is desired. If
- `None`, the FFT length is `nperseg`. Defaults to `None`.
- detrend : str or function or `False`, optional
- Specifies how to detrend each segment. If `detrend` is a
- string, it is passed as the `type` argument to the `detrend`
- function. If it is a function, it takes a segment and returns a
- detrended segment. If `detrend` is `False`, no detrending is
- done. Defaults to 'constant'.
- return_onesided : bool, optional
- If `True`, return a one-sided spectrum for real data. If
- `False` return a two-sided spectrum. Defaults to `True`, but for
- complex data, a two-sided spectrum is always returned.
- scaling : { 'density', 'spectrum' }, optional
- Selects between computing the power spectral density ('density')
- where `Sxx` has units of V**2/Hz and computing the power
- spectrum ('spectrum') where `Sxx` has units of V**2, if `x`
- is measured in V and `fs` is measured in Hz. Defaults to
- 'density'.
- axis : int, optional
- Axis along which the spectrogram is computed; the default is over
- the last axis (i.e. ``axis=-1``).
- mode : str, optional
- Defines what kind of return values are expected. Options are
- ['psd', 'complex', 'magnitude', 'angle', 'phase']. 'complex' is
- equivalent to the output of `stft` with no padding or boundary
- extension. 'magnitude' returns the absolute magnitude of the
- STFT. 'angle' and 'phase' return the complex angle of the STFT,
- with and without unwrapping, respectively.
- Returns
- -------
- f : ndarray
- Array of sample frequencies.
- t : ndarray
- Array of segment times.
- Sxx : ndarray
- Spectrogram of x. By default, the last axis of Sxx corresponds
- to the segment times.
- See Also
- --------
- periodogram: Simple, optionally modified periodogram
- lombscargle: Lomb-Scargle periodogram for unevenly sampled data
- welch: Power spectral density by Welch's method.
- csd: Cross spectral density by Welch's method.
- Notes
- -----
- An appropriate amount of overlap will depend on the choice of window
- and on your requirements. In contrast to welch's method, where the
- entire data stream is averaged over, one may wish to use a smaller
- overlap (or perhaps none at all) when computing a spectrogram, to
- maintain some statistical independence between individual segments.
- It is for this reason that the default window is a Tukey window with
- 1/8th of a window's length overlap at each end.
- .. versionadded:: 0.16.0
- References
- ----------
- .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck
- "Discrete-Time Signal Processing", Prentice Hall, 1999.
- Examples
- --------
- >>> import numpy as np
- >>> from scipy import signal
- >>> from scipy.fft import fftshift
- >>> import matplotlib.pyplot as plt
- >>> rng = np.random.default_rng()
- Generate a test signal, a 2 Vrms sine wave whose frequency is slowly
- modulated around 3kHz, corrupted by white noise of exponentially
- decreasing magnitude sampled at 10 kHz.
- >>> fs = 10e3
- >>> N = 1e5
- >>> amp = 2 * np.sqrt(2)
- >>> noise_power = 0.01 * fs / 2
- >>> time = np.arange(N) / float(fs)
- >>> mod = 500*np.cos(2*np.pi*0.25*time)
- >>> carrier = amp * np.sin(2*np.pi*3e3*time + mod)
- >>> noise = rng.normal(scale=np.sqrt(noise_power), size=time.shape)
- >>> noise *= np.exp(-time/5)
- >>> x = carrier + noise
- Compute and plot the spectrogram.
- >>> f, t, Sxx = signal.spectrogram(x, fs)
- >>> plt.pcolormesh(t, f, Sxx, shading='gouraud')
- >>> plt.ylabel('Frequency [Hz]')
- >>> plt.xlabel('Time [sec]')
- >>> plt.show()
- Note, if using output that is not one sided, then use the following:
- >>> f, t, Sxx = signal.spectrogram(x, fs, return_onesided=False)
- >>> plt.pcolormesh(t, fftshift(f), fftshift(Sxx, axes=0), shading='gouraud')
- >>> plt.ylabel('Frequency [Hz]')
- >>> plt.xlabel('Time [sec]')
- >>> plt.show()
- """
- modelist = ['psd', 'complex', 'magnitude', 'angle', 'phase']
- if mode not in modelist:
- raise ValueError('unknown value for mode {}, must be one of {}'
- .format(mode, modelist))
- # need to set default for nperseg before setting default for noverlap below
- window, nperseg = _triage_segments(window, nperseg,
- input_length=x.shape[axis])
- # Less overlap than welch, so samples are more statisically independent
- if noverlap is None:
- noverlap = nperseg // 8
- if mode == 'psd':
- freqs, time, Sxx = _spectral_helper(x, x, fs, window, nperseg,
- noverlap, nfft, detrend,
- return_onesided, scaling, axis,
- mode='psd')
- else:
- freqs, time, Sxx = _spectral_helper(x, x, fs, window, nperseg,
- noverlap, nfft, detrend,
- return_onesided, scaling, axis,
- mode='stft')
- if mode == 'magnitude':
- Sxx = np.abs(Sxx)
- elif mode in ['angle', 'phase']:
- Sxx = np.angle(Sxx)
- if mode == 'phase':
- # Sxx has one additional dimension for time strides
- if axis < 0:
- axis -= 1
- Sxx = np.unwrap(Sxx, axis=axis)
- # mode =='complex' is same as `stft`, doesn't need modification
- return freqs, time, Sxx
- def check_COLA(window, nperseg, noverlap, tol=1e-10):
- r"""Check whether the Constant OverLap Add (COLA) constraint is met.
- Parameters
- ----------
- window : str or tuple or array_like
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg.
- nperseg : int
- Length of each segment.
- noverlap : int
- Number of points to overlap between segments.
- tol : float, optional
- The allowed variance of a bin's weighted sum from the median bin
- sum.
- Returns
- -------
- verdict : bool
- `True` if chosen combination satisfies COLA within `tol`,
- `False` otherwise
- See Also
- --------
- check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met
- stft: Short Time Fourier Transform
- istft: Inverse Short Time Fourier Transform
- Notes
- -----
- In order to enable inversion of an STFT via the inverse STFT in
- `istft`, it is sufficient that the signal windowing obeys the constraint of
- "Constant OverLap Add" (COLA). This ensures that every point in the input
- data is equally weighted, thereby avoiding aliasing and allowing full
- reconstruction.
- Some examples of windows that satisfy COLA:
- - Rectangular window at overlap of 0, 1/2, 2/3, 3/4, ...
- - Bartlett window at overlap of 1/2, 3/4, 5/6, ...
- - Hann window at 1/2, 2/3, 3/4, ...
- - Any Blackman family window at 2/3 overlap
- - Any window with ``noverlap = nperseg-1``
- A very comprehensive list of other windows may be found in [2]_,
- wherein the COLA condition is satisfied when the "Amplitude
- Flatness" is unity.
- .. versionadded:: 0.19.0
- References
- ----------
- .. [1] Julius O. Smith III, "Spectral Audio Signal Processing", W3K
- Publishing, 2011,ISBN 978-0-9745607-3-1.
- .. [2] G. Heinzel, A. Ruediger and R. Schilling, "Spectrum and
- spectral density estimation by the Discrete Fourier transform
- (DFT), including a comprehensive list of window functions and
- some new at-top windows", 2002,
- http://hdl.handle.net/11858/00-001M-0000-0013-557A-5
- Examples
- --------
- >>> from scipy import signal
- Confirm COLA condition for rectangular window of 75% (3/4) overlap:
- >>> signal.check_COLA(signal.windows.boxcar(100), 100, 75)
- True
- COLA is not true for 25% (1/4) overlap, though:
- >>> signal.check_COLA(signal.windows.boxcar(100), 100, 25)
- False
- "Symmetrical" Hann window (for filter design) is not COLA:
- >>> signal.check_COLA(signal.windows.hann(120, sym=True), 120, 60)
- False
- "Periodic" or "DFT-even" Hann window (for FFT analysis) is COLA for
- overlap of 1/2, 2/3, 3/4, etc.:
- >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 60)
- True
- >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 80)
- True
- >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 90)
- True
- """
- nperseg = int(nperseg)
- if nperseg < 1:
- raise ValueError('nperseg must be a positive integer')
- if noverlap >= nperseg:
- raise ValueError('noverlap must be less than nperseg.')
- noverlap = int(noverlap)
- if isinstance(window, str) or type(window) is tuple:
- win = get_window(window, nperseg)
- else:
- win = np.asarray(window)
- if len(win.shape) != 1:
- raise ValueError('window must be 1-D')
- if win.shape[0] != nperseg:
- raise ValueError('window must have length of nperseg')
- step = nperseg - noverlap
- binsums = sum(win[ii*step:(ii+1)*step] for ii in range(nperseg//step))
- if nperseg % step != 0:
- binsums[:nperseg % step] += win[-(nperseg % step):]
- deviation = binsums - np.median(binsums)
- return np.max(np.abs(deviation)) < tol
- def check_NOLA(window, nperseg, noverlap, tol=1e-10):
- r"""Check whether the Nonzero Overlap Add (NOLA) constraint is met.
- Parameters
- ----------
- window : str or tuple or array_like
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg.
- nperseg : int
- Length of each segment.
- noverlap : int
- Number of points to overlap between segments.
- tol : float, optional
- The allowed variance of a bin's weighted sum from the median bin
- sum.
- Returns
- -------
- verdict : bool
- `True` if chosen combination satisfies the NOLA constraint within
- `tol`, `False` otherwise
- See Also
- --------
- check_COLA: Check whether the Constant OverLap Add (COLA) constraint is met
- stft: Short Time Fourier Transform
- istft: Inverse Short Time Fourier Transform
- Notes
- -----
- In order to enable inversion of an STFT via the inverse STFT in
- `istft`, the signal windowing must obey the constraint of "nonzero
- overlap add" (NOLA):
- .. math:: \sum_{t}w^{2}[n-tH] \ne 0
- for all :math:`n`, where :math:`w` is the window function, :math:`t` is the
- frame index, and :math:`H` is the hop size (:math:`H` = `nperseg` -
- `noverlap`).
- This ensures that the normalization factors in the denominator of the
- overlap-add inversion equation are not zero. Only very pathological windows
- will fail the NOLA constraint.
- .. versionadded:: 1.2.0
- References
- ----------
- .. [1] Julius O. Smith III, "Spectral Audio Signal Processing", W3K
- Publishing, 2011,ISBN 978-0-9745607-3-1.
- .. [2] G. Heinzel, A. Ruediger and R. Schilling, "Spectrum and
- spectral density estimation by the Discrete Fourier transform
- (DFT), including a comprehensive list of window functions and
- some new at-top windows", 2002,
- http://hdl.handle.net/11858/00-001M-0000-0013-557A-5
- Examples
- --------
- >>> import numpy as np
- >>> from scipy import signal
- Confirm NOLA condition for rectangular window of 75% (3/4) overlap:
- >>> signal.check_NOLA(signal.windows.boxcar(100), 100, 75)
- True
- NOLA is also true for 25% (1/4) overlap:
- >>> signal.check_NOLA(signal.windows.boxcar(100), 100, 25)
- True
- "Symmetrical" Hann window (for filter design) is also NOLA:
- >>> signal.check_NOLA(signal.windows.hann(120, sym=True), 120, 60)
- True
- As long as there is overlap, it takes quite a pathological window to fail
- NOLA:
- >>> w = np.ones(64, dtype="float")
- >>> w[::2] = 0
- >>> signal.check_NOLA(w, 64, 32)
- False
- If there is not enough overlap, a window with zeros at the ends will not
- work:
- >>> signal.check_NOLA(signal.windows.hann(64), 64, 0)
- False
- >>> signal.check_NOLA(signal.windows.hann(64), 64, 1)
- False
- >>> signal.check_NOLA(signal.windows.hann(64), 64, 2)
- True
- """
- nperseg = int(nperseg)
- if nperseg < 1:
- raise ValueError('nperseg must be a positive integer')
- if noverlap >= nperseg:
- raise ValueError('noverlap must be less than nperseg')
- if noverlap < 0:
- raise ValueError('noverlap must be a nonnegative integer')
- noverlap = int(noverlap)
- if isinstance(window, str) or type(window) is tuple:
- win = get_window(window, nperseg)
- else:
- win = np.asarray(window)
- if len(win.shape) != 1:
- raise ValueError('window must be 1-D')
- if win.shape[0] != nperseg:
- raise ValueError('window must have length of nperseg')
- step = nperseg - noverlap
- binsums = sum(win[ii*step:(ii+1)*step]**2 for ii in range(nperseg//step))
- if nperseg % step != 0:
- binsums[:nperseg % step] += win[-(nperseg % step):]**2
- return np.min(binsums) > tol
- def stft(x, fs=1.0, window='hann', nperseg=256, noverlap=None, nfft=None,
- detrend=False, return_onesided=True, boundary='zeros', padded=True,
- axis=-1, scaling='spectrum'):
- r"""Compute the Short Time Fourier Transform (STFT).
- STFTs can be used as a way of quantifying the change of a
- nonstationary signal's frequency and phase content over time.
- Parameters
- ----------
- x : array_like
- Time series of measurement values
- fs : float, optional
- Sampling frequency of the `x` time series. Defaults to 1.0.
- window : str or tuple or array_like, optional
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg. Defaults
- to a Hann window.
- nperseg : int, optional
- Length of each segment. Defaults to 256.
- noverlap : int, optional
- Number of points to overlap between segments. If `None`,
- ``noverlap = nperseg // 2``. Defaults to `None`. When
- specified, the COLA constraint must be met (see Notes below).
- nfft : int, optional
- Length of the FFT used, if a zero padded FFT is desired. If
- `None`, the FFT length is `nperseg`. Defaults to `None`.
- detrend : str or function or `False`, optional
- Specifies how to detrend each segment. If `detrend` is a
- string, it is passed as the `type` argument to the `detrend`
- function. If it is a function, it takes a segment and returns a
- detrended segment. If `detrend` is `False`, no detrending is
- done. Defaults to `False`.
- return_onesided : bool, optional
- If `True`, return a one-sided spectrum for real data. If
- `False` return a two-sided spectrum. Defaults to `True`, but for
- complex data, a two-sided spectrum is always returned.
- boundary : str or None, optional
- Specifies whether the input signal is extended at both ends, and
- how to generate the new values, in order to center the first
- windowed segment on the first input point. This has the benefit
- of enabling reconstruction of the first input point when the
- employed window function starts at zero. Valid options are
- ``['even', 'odd', 'constant', 'zeros', None]``. Defaults to
- 'zeros', for zero padding extension. I.e. ``[1, 2, 3, 4]`` is
- extended to ``[0, 1, 2, 3, 4, 0]`` for ``nperseg=3``.
- padded : bool, optional
- Specifies whether the input signal is zero-padded at the end to
- make the signal fit exactly into an integer number of window
- segments, so that all of the signal is included in the output.
- Defaults to `True`. Padding occurs after boundary extension, if
- `boundary` is not `None`, and `padded` is `True`, as is the
- default.
- axis : int, optional
- Axis along which the STFT is computed; the default is over the
- last axis (i.e. ``axis=-1``).
- scaling: {'spectrum', 'psd'}
- The default 'spectrum' scaling allows each frequency line of `Zxx` to
- be interpreted as a magnitude spectrum. The 'psd' option scales each
- line to a power spectral density - it allows to calculate the signal's
- energy by numerically integrating over ``abs(Zxx)**2``.
- .. versionadded:: 1.9.0
- Returns
- -------
- f : ndarray
- Array of sample frequencies.
- t : ndarray
- Array of segment times.
- Zxx : ndarray
- STFT of `x`. By default, the last axis of `Zxx` corresponds
- to the segment times.
- See Also
- --------
- istft: Inverse Short Time Fourier Transform
- check_COLA: Check whether the Constant OverLap Add (COLA) constraint
- is met
- check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met
- welch: Power spectral density by Welch's method.
- spectrogram: Spectrogram by Welch's method.
- csd: Cross spectral density by Welch's method.
- lombscargle: Lomb-Scargle periodogram for unevenly sampled data
- Notes
- -----
- In order to enable inversion of an STFT via the inverse STFT in
- `istft`, the signal windowing must obey the constraint of "Nonzero
- OverLap Add" (NOLA), and the input signal must have complete
- windowing coverage (i.e. ``(x.shape[axis] - nperseg) %
- (nperseg-noverlap) == 0``). The `padded` argument may be used to
- accomplish this.
- Given a time-domain signal :math:`x[n]`, a window :math:`w[n]`, and a hop
- size :math:`H` = `nperseg - noverlap`, the windowed frame at time index
- :math:`t` is given by
- .. math:: x_{t}[n]=x[n]w[n-tH]
- The overlap-add (OLA) reconstruction equation is given by
- .. math:: x[n]=\frac{\sum_{t}x_{t}[n]w[n-tH]}{\sum_{t}w^{2}[n-tH]}
- The NOLA constraint ensures that every normalization term that appears
- in the denomimator of the OLA reconstruction equation is nonzero. Whether a
- choice of `window`, `nperseg`, and `noverlap` satisfy this constraint can
- be tested with `check_NOLA`.
- .. versionadded:: 0.19.0
- References
- ----------
- .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck
- "Discrete-Time Signal Processing", Prentice Hall, 1999.
- .. [2] Daniel W. Griffin, Jae S. Lim "Signal Estimation from
- Modified Short-Time Fourier Transform", IEEE 1984,
- 10.1109/TASSP.1984.1164317
- Examples
- --------
- >>> import numpy as np
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- >>> rng = np.random.default_rng()
- Generate a test signal, a 2 Vrms sine wave whose frequency is slowly
- modulated around 3kHz, corrupted by white noise of exponentially
- decreasing magnitude sampled at 10 kHz.
- >>> fs = 10e3
- >>> N = 1e5
- >>> amp = 2 * np.sqrt(2)
- >>> noise_power = 0.01 * fs / 2
- >>> time = np.arange(N) / float(fs)
- >>> mod = 500*np.cos(2*np.pi*0.25*time)
- >>> carrier = amp * np.sin(2*np.pi*3e3*time + mod)
- >>> noise = rng.normal(scale=np.sqrt(noise_power),
- ... size=time.shape)
- >>> noise *= np.exp(-time/5)
- >>> x = carrier + noise
- Compute and plot the STFT's magnitude.
- >>> f, t, Zxx = signal.stft(x, fs, nperseg=1000)
- >>> plt.pcolormesh(t, f, np.abs(Zxx), vmin=0, vmax=amp, shading='gouraud')
- >>> plt.title('STFT Magnitude')
- >>> plt.ylabel('Frequency [Hz]')
- >>> plt.xlabel('Time [sec]')
- >>> plt.show()
- Compare the energy of the signal `x` with the energy of its STFT:
- >>> E_x = sum(x**2) / fs # Energy of x
- >>> # Calculate a two-sided STFT with PSD scaling:
- >>> f, t, Zxx = signal.stft(x, fs, nperseg=1000, return_onesided=False,
- ... scaling='psd')
- >>> # Integrate numerically over abs(Zxx)**2:
- >>> df, dt = f[1] - f[0], t[1] - t[0]
- >>> E_Zxx = sum(np.sum(Zxx.real**2 + Zxx.imag**2, axis=0) * df) * dt
- >>> # The energy is the same, but the numerical errors are quite large:
- >>> np.isclose(E_x, E_Zxx, rtol=1e-2)
- True
- """
- if scaling == 'psd':
- scaling = 'density'
- elif scaling != 'spectrum':
- raise ValueError(f"Parameter {scaling=} not in ['spectrum', 'psd']!")
- freqs, time, Zxx = _spectral_helper(x, x, fs, window, nperseg, noverlap,
- nfft, detrend, return_onesided,
- scaling=scaling, axis=axis,
- mode='stft', boundary=boundary,
- padded=padded)
- return freqs, time, Zxx
- def istft(Zxx, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None,
- input_onesided=True, boundary=True, time_axis=-1, freq_axis=-2,
- scaling='spectrum'):
- r"""Perform the inverse Short Time Fourier transform (iSTFT).
- Parameters
- ----------
- Zxx : array_like
- STFT of the signal to be reconstructed. If a purely real array
- is passed, it will be cast to a complex data type.
- fs : float, optional
- Sampling frequency of the time series. Defaults to 1.0.
- window : str or tuple or array_like, optional
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg. Defaults
- to a Hann window. Must match the window used to generate the
- STFT for faithful inversion.
- nperseg : int, optional
- Number of data points corresponding to each STFT segment. This
- parameter must be specified if the number of data points per
- segment is odd, or if the STFT was padded via ``nfft >
- nperseg``. If `None`, the value depends on the shape of
- `Zxx` and `input_onesided`. If `input_onesided` is `True`,
- ``nperseg=2*(Zxx.shape[freq_axis] - 1)``. Otherwise,
- ``nperseg=Zxx.shape[freq_axis]``. Defaults to `None`.
- noverlap : int, optional
- Number of points to overlap between segments. If `None`, half
- of the segment length. Defaults to `None`. When specified, the
- COLA constraint must be met (see Notes below), and should match
- the parameter used to generate the STFT. Defaults to `None`.
- nfft : int, optional
- Number of FFT points corresponding to each STFT segment. This
- parameter must be specified if the STFT was padded via ``nfft >
- nperseg``. If `None`, the default values are the same as for
- `nperseg`, detailed above, with one exception: if
- `input_onesided` is True and
- ``nperseg==2*Zxx.shape[freq_axis] - 1``, `nfft` also takes on
- that value. This case allows the proper inversion of an
- odd-length unpadded STFT using ``nfft=None``. Defaults to
- `None`.
- input_onesided : bool, optional
- If `True`, interpret the input array as one-sided FFTs, such
- as is returned by `stft` with ``return_onesided=True`` and
- `numpy.fft.rfft`. If `False`, interpret the input as a a
- two-sided FFT. Defaults to `True`.
- boundary : bool, optional
- Specifies whether the input signal was extended at its
- boundaries by supplying a non-`None` ``boundary`` argument to
- `stft`. Defaults to `True`.
- time_axis : int, optional
- Where the time segments of the STFT is located; the default is
- the last axis (i.e. ``axis=-1``).
- freq_axis : int, optional
- Where the frequency axis of the STFT is located; the default is
- the penultimate axis (i.e. ``axis=-2``).
- scaling: {'spectrum', 'psd'}
- The default 'spectrum' scaling allows each frequency line of `Zxx` to
- be interpreted as a magnitude spectrum. The 'psd' option scales each
- line to a power spectral density - it allows to calculate the signal's
- energy by numerically integrating over ``abs(Zxx)**2``.
- Returns
- -------
- t : ndarray
- Array of output data times.
- x : ndarray
- iSTFT of `Zxx`.
- See Also
- --------
- stft: Short Time Fourier Transform
- check_COLA: Check whether the Constant OverLap Add (COLA) constraint
- is met
- check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met
- Notes
- -----
- In order to enable inversion of an STFT via the inverse STFT with
- `istft`, the signal windowing must obey the constraint of "nonzero
- overlap add" (NOLA):
- .. math:: \sum_{t}w^{2}[n-tH] \ne 0
- This ensures that the normalization factors that appear in the denominator
- of the overlap-add reconstruction equation
- .. math:: x[n]=\frac{\sum_{t}x_{t}[n]w[n-tH]}{\sum_{t}w^{2}[n-tH]}
- are not zero. The NOLA constraint can be checked with the `check_NOLA`
- function.
- An STFT which has been modified (via masking or otherwise) is not
- guaranteed to correspond to a exactly realizible signal. This
- function implements the iSTFT via the least-squares estimation
- algorithm detailed in [2]_, which produces a signal that minimizes
- the mean squared error between the STFT of the returned signal and
- the modified STFT.
- .. versionadded:: 0.19.0
- References
- ----------
- .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck
- "Discrete-Time Signal Processing", Prentice Hall, 1999.
- .. [2] Daniel W. Griffin, Jae S. Lim "Signal Estimation from
- Modified Short-Time Fourier Transform", IEEE 1984,
- 10.1109/TASSP.1984.1164317
- Examples
- --------
- >>> import numpy as np
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- >>> rng = np.random.default_rng()
- Generate a test signal, a 2 Vrms sine wave at 50Hz corrupted by
- 0.001 V**2/Hz of white noise sampled at 1024 Hz.
- >>> fs = 1024
- >>> N = 10*fs
- >>> nperseg = 512
- >>> amp = 2 * np.sqrt(2)
- >>> noise_power = 0.001 * fs / 2
- >>> time = np.arange(N) / float(fs)
- >>> carrier = amp * np.sin(2*np.pi*50*time)
- >>> noise = rng.normal(scale=np.sqrt(noise_power),
- ... size=time.shape)
- >>> x = carrier + noise
- Compute the STFT, and plot its magnitude
- >>> f, t, Zxx = signal.stft(x, fs=fs, nperseg=nperseg)
- >>> plt.figure()
- >>> plt.pcolormesh(t, f, np.abs(Zxx), vmin=0, vmax=amp, shading='gouraud')
- >>> plt.ylim([f[1], f[-1]])
- >>> plt.title('STFT Magnitude')
- >>> plt.ylabel('Frequency [Hz]')
- >>> plt.xlabel('Time [sec]')
- >>> plt.yscale('log')
- >>> plt.show()
- Zero the components that are 10% or less of the carrier magnitude,
- then convert back to a time series via inverse STFT
- >>> Zxx = np.where(np.abs(Zxx) >= amp/10, Zxx, 0)
- >>> _, xrec = signal.istft(Zxx, fs)
- Compare the cleaned signal with the original and true carrier signals.
- >>> plt.figure()
- >>> plt.plot(time, x, time, xrec, time, carrier)
- >>> plt.xlim([2, 2.1])
- >>> plt.xlabel('Time [sec]')
- >>> plt.ylabel('Signal')
- >>> plt.legend(['Carrier + Noise', 'Filtered via STFT', 'True Carrier'])
- >>> plt.show()
- Note that the cleaned signal does not start as abruptly as the original,
- since some of the coefficients of the transient were also removed:
- >>> plt.figure()
- >>> plt.plot(time, x, time, xrec, time, carrier)
- >>> plt.xlim([0, 0.1])
- >>> plt.xlabel('Time [sec]')
- >>> plt.ylabel('Signal')
- >>> plt.legend(['Carrier + Noise', 'Filtered via STFT', 'True Carrier'])
- >>> plt.show()
- """
- # Make sure input is an ndarray of appropriate complex dtype
- Zxx = np.asarray(Zxx) + 0j
- freq_axis = int(freq_axis)
- time_axis = int(time_axis)
- if Zxx.ndim < 2:
- raise ValueError('Input stft must be at least 2d!')
- if freq_axis == time_axis:
- raise ValueError('Must specify differing time and frequency axes!')
- nseg = Zxx.shape[time_axis]
- if input_onesided:
- # Assume even segment length
- n_default = 2*(Zxx.shape[freq_axis] - 1)
- else:
- n_default = Zxx.shape[freq_axis]
- # Check windowing parameters
- if nperseg is None:
- nperseg = n_default
- else:
- nperseg = int(nperseg)
- if nperseg < 1:
- raise ValueError('nperseg must be a positive integer')
- if nfft is None:
- if (input_onesided) and (nperseg == n_default + 1):
- # Odd nperseg, no FFT padding
- nfft = nperseg
- else:
- nfft = n_default
- elif nfft < nperseg:
- raise ValueError('nfft must be greater than or equal to nperseg.')
- else:
- nfft = int(nfft)
- if noverlap is None:
- noverlap = nperseg//2
- else:
- noverlap = int(noverlap)
- if noverlap >= nperseg:
- raise ValueError('noverlap must be less than nperseg.')
- nstep = nperseg - noverlap
- # Rearrange axes if necessary
- if time_axis != Zxx.ndim-1 or freq_axis != Zxx.ndim-2:
- # Turn negative indices to positive for the call to transpose
- if freq_axis < 0:
- freq_axis = Zxx.ndim + freq_axis
- if time_axis < 0:
- time_axis = Zxx.ndim + time_axis
- zouter = list(range(Zxx.ndim))
- for ax in sorted([time_axis, freq_axis], reverse=True):
- zouter.pop(ax)
- Zxx = np.transpose(Zxx, zouter+[freq_axis, time_axis])
- # Get window as array
- if isinstance(window, str) or type(window) is tuple:
- win = get_window(window, nperseg)
- else:
- win = np.asarray(window)
- if len(win.shape) != 1:
- raise ValueError('window must be 1-D')
- if win.shape[0] != nperseg:
- raise ValueError('window must have length of {0}'.format(nperseg))
- ifunc = sp_fft.irfft if input_onesided else sp_fft.ifft
- xsubs = ifunc(Zxx, axis=-2, n=nfft)[..., :nperseg, :]
- # Initialize output and normalization arrays
- outputlength = nperseg + (nseg-1)*nstep
- x = np.zeros(list(Zxx.shape[:-2])+[outputlength], dtype=xsubs.dtype)
- norm = np.zeros(outputlength, dtype=xsubs.dtype)
- if np.result_type(win, xsubs) != xsubs.dtype:
- win = win.astype(xsubs.dtype)
- if scaling == 'spectrum':
- xsubs *= win.sum()
- elif scaling == 'psd':
- xsubs *= np.sqrt(fs * sum(win**2))
- else:
- raise ValueError(f"Parameter {scaling=} not in ['spectrum', 'psd']!")
- # Construct the output from the ifft segments
- # This loop could perhaps be vectorized/strided somehow...
- for ii in range(nseg):
- # Window the ifft
- x[..., ii*nstep:ii*nstep+nperseg] += xsubs[..., ii] * win
- norm[..., ii*nstep:ii*nstep+nperseg] += win**2
- # Remove extension points
- if boundary:
- x = x[..., nperseg//2:-(nperseg//2)]
- norm = norm[..., nperseg//2:-(nperseg//2)]
- # Divide out normalization where non-tiny
- if np.sum(norm > 1e-10) != len(norm):
- warnings.warn("NOLA condition failed, STFT may not be invertible")
- x /= np.where(norm > 1e-10, norm, 1.0)
- if input_onesided:
- x = x.real
- # Put axes back
- if x.ndim > 1:
- if time_axis != Zxx.ndim-1:
- if freq_axis < time_axis:
- time_axis -= 1
- x = np.moveaxis(x, -1, time_axis)
- time = np.arange(x.shape[0])/float(fs)
- return time, x
- def coherence(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None,
- nfft=None, detrend='constant', axis=-1):
- r"""
- Estimate the magnitude squared coherence estimate, Cxy, of
- discrete-time signals X and Y using Welch's method.
- ``Cxy = abs(Pxy)**2/(Pxx*Pyy)``, where `Pxx` and `Pyy` are power
- spectral density estimates of X and Y, and `Pxy` is the cross
- spectral density estimate of X and Y.
- Parameters
- ----------
- x : array_like
- Time series of measurement values
- y : array_like
- Time series of measurement values
- fs : float, optional
- Sampling frequency of the `x` and `y` time series. Defaults
- to 1.0.
- window : str or tuple or array_like, optional
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg. Defaults
- to a Hann window.
- nperseg : int, optional
- Length of each segment. Defaults to None, but if window is str or
- tuple, is set to 256, and if window is array_like, is set to the
- length of the window.
- noverlap: int, optional
- Number of points to overlap between segments. If `None`,
- ``noverlap = nperseg // 2``. Defaults to `None`.
- nfft : int, optional
- Length of the FFT used, if a zero padded FFT is desired. If
- `None`, the FFT length is `nperseg`. Defaults to `None`.
- detrend : str or function or `False`, optional
- Specifies how to detrend each segment. If `detrend` is a
- string, it is passed as the `type` argument to the `detrend`
- function. If it is a function, it takes a segment and returns a
- detrended segment. If `detrend` is `False`, no detrending is
- done. Defaults to 'constant'.
- axis : int, optional
- Axis along which the coherence is computed for both inputs; the
- default is over the last axis (i.e. ``axis=-1``).
- Returns
- -------
- f : ndarray
- Array of sample frequencies.
- Cxy : ndarray
- Magnitude squared coherence of x and y.
- See Also
- --------
- periodogram: Simple, optionally modified periodogram
- lombscargle: Lomb-Scargle periodogram for unevenly sampled data
- welch: Power spectral density by Welch's method.
- csd: Cross spectral density by Welch's method.
- Notes
- -----
- An appropriate amount of overlap will depend on the choice of window
- and on your requirements. For the default Hann window an overlap of
- 50% is a reasonable trade off between accurately estimating the
- signal power, while not over counting any of the data. Narrower
- windows may require a larger overlap.
- .. versionadded:: 0.16.0
- References
- ----------
- .. [1] P. Welch, "The use of the fast Fourier transform for the
- estimation of power spectra: A method based on time averaging
- over short, modified periodograms", IEEE Trans. Audio
- Electroacoust. vol. 15, pp. 70-73, 1967.
- .. [2] Stoica, Petre, and Randolph Moses, "Spectral Analysis of
- Signals" Prentice Hall, 2005
- Examples
- --------
- >>> import numpy as np
- >>> from scipy import signal
- >>> import matplotlib.pyplot as plt
- >>> rng = np.random.default_rng()
- Generate two test signals with some common features.
- >>> fs = 10e3
- >>> N = 1e5
- >>> amp = 20
- >>> freq = 1234.0
- >>> noise_power = 0.001 * fs / 2
- >>> time = np.arange(N) / fs
- >>> b, a = signal.butter(2, 0.25, 'low')
- >>> x = rng.normal(scale=np.sqrt(noise_power), size=time.shape)
- >>> y = signal.lfilter(b, a, x)
- >>> x += amp*np.sin(2*np.pi*freq*time)
- >>> y += rng.normal(scale=0.1*np.sqrt(noise_power), size=time.shape)
- Compute and plot the coherence.
- >>> f, Cxy = signal.coherence(x, y, fs, nperseg=1024)
- >>> plt.semilogy(f, Cxy)
- >>> plt.xlabel('frequency [Hz]')
- >>> plt.ylabel('Coherence')
- >>> plt.show()
- """
- freqs, Pxx = welch(x, fs=fs, window=window, nperseg=nperseg,
- noverlap=noverlap, nfft=nfft, detrend=detrend,
- axis=axis)
- _, Pyy = welch(y, fs=fs, window=window, nperseg=nperseg, noverlap=noverlap,
- nfft=nfft, detrend=detrend, axis=axis)
- _, Pxy = csd(x, y, fs=fs, window=window, nperseg=nperseg,
- noverlap=noverlap, nfft=nfft, detrend=detrend, axis=axis)
- Cxy = np.abs(Pxy)**2 / Pxx / Pyy
- return freqs, Cxy
- def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None,
- nfft=None, detrend='constant', return_onesided=True,
- scaling='density', axis=-1, mode='psd', boundary=None,
- padded=False):
- """Calculate various forms of windowed FFTs for PSD, CSD, etc.
- This is a helper function that implements the commonality between
- the stft, psd, csd, and spectrogram functions. It is not designed to
- be called externally. The windows are not averaged over; the result
- from each window is returned.
- Parameters
- ----------
- x : array_like
- Array or sequence containing the data to be analyzed.
- y : array_like
- Array or sequence containing the data to be analyzed. If this is
- the same object in memory as `x` (i.e. ``_spectral_helper(x,
- x, ...)``), the extra computations are spared.
- fs : float, optional
- Sampling frequency of the time series. Defaults to 1.0.
- window : str or tuple or array_like, optional
- Desired window to use. If `window` is a string or tuple, it is
- passed to `get_window` to generate the window values, which are
- DFT-even by default. See `get_window` for a list of windows and
- required parameters. If `window` is array_like it will be used
- directly as the window and its length must be nperseg. Defaults
- to a Hann window.
- nperseg : int, optional
- Length of each segment. Defaults to None, but if window is str or
- tuple, is set to 256, and if window is array_like, is set to the
- length of the window.
- noverlap : int, optional
- Number of points to overlap between segments. If `None`,
- ``noverlap = nperseg // 2``. Defaults to `None`.
- nfft : int, optional
- Length of the FFT used, if a zero padded FFT is desired. If
- `None`, the FFT length is `nperseg`. Defaults to `None`.
- detrend : str or function or `False`, optional
- Specifies how to detrend each segment. If `detrend` is a
- string, it is passed as the `type` argument to the `detrend`
- function. If it is a function, it takes a segment and returns a
- detrended segment. If `detrend` is `False`, no detrending is
- done. Defaults to 'constant'.
- return_onesided : bool, optional
- If `True`, return a one-sided spectrum for real data. If
- `False` return a two-sided spectrum. Defaults to `True`, but for
- complex data, a two-sided spectrum is always returned.
- scaling : { 'density', 'spectrum' }, optional
- Selects between computing the cross spectral density ('density')
- where `Pxy` has units of V**2/Hz and computing the cross
- spectrum ('spectrum') where `Pxy` has units of V**2, if `x`
- and `y` are measured in V and `fs` is measured in Hz.
- Defaults to 'density'
- axis : int, optional
- Axis along which the FFTs are computed; the default is over the
- last axis (i.e. ``axis=-1``).
- mode: str {'psd', 'stft'}, optional
- Defines what kind of return values are expected. Defaults to
- 'psd'.
- boundary : str or None, optional
- Specifies whether the input signal is extended at both ends, and
- how to generate the new values, in order to center the first
- windowed segment on the first input point. This has the benefit
- of enabling reconstruction of the first input point when the
- employed window function starts at zero. Valid options are
- ``['even', 'odd', 'constant', 'zeros', None]``. Defaults to
- `None`.
- padded : bool, optional
- Specifies whether the input signal is zero-padded at the end to
- make the signal fit exactly into an integer number of window
- segments, so that all of the signal is included in the output.
- Defaults to `False`. Padding occurs after boundary extension, if
- `boundary` is not `None`, and `padded` is `True`.
- Returns
- -------
- freqs : ndarray
- Array of sample frequencies.
- t : ndarray
- Array of times corresponding to each data segment
- result : ndarray
- Array of output data, contents dependent on *mode* kwarg.
- Notes
- -----
- Adapted from matplotlib.mlab
- .. versionadded:: 0.16.0
- """
- if mode not in ['psd', 'stft']:
- raise ValueError("Unknown value for mode %s, must be one of: "
- "{'psd', 'stft'}" % mode)
- boundary_funcs = {'even': even_ext,
- 'odd': odd_ext,
- 'constant': const_ext,
- 'zeros': zero_ext,
- None: None}
- if boundary not in boundary_funcs:
- raise ValueError("Unknown boundary option '{0}', must be one of: {1}"
- .format(boundary, list(boundary_funcs.keys())))
- # If x and y are the same object we can save ourselves some computation.
- same_data = y is x
- if not same_data and mode != 'psd':
- raise ValueError("x and y must be equal if mode is 'stft'")
- axis = int(axis)
- # Ensure we have np.arrays, get outdtype
- x = np.asarray(x)
- if not same_data:
- y = np.asarray(y)
- outdtype = np.result_type(x, y, np.complex64)
- else:
- outdtype = np.result_type(x, np.complex64)
- if not same_data:
- # Check if we can broadcast the outer axes together
- xouter = list(x.shape)
- youter = list(y.shape)
- xouter.pop(axis)
- youter.pop(axis)
- try:
- outershape = np.broadcast(np.empty(xouter), np.empty(youter)).shape
- except ValueError as e:
- raise ValueError('x and y cannot be broadcast together.') from e
- if same_data:
- if x.size == 0:
- return np.empty(x.shape), np.empty(x.shape), np.empty(x.shape)
- else:
- if x.size == 0 or y.size == 0:
- outshape = outershape + (min([x.shape[axis], y.shape[axis]]),)
- emptyout = np.moveaxis(np.empty(outshape), -1, axis)
- return emptyout, emptyout, emptyout
- if x.ndim > 1:
- if axis != -1:
- x = np.moveaxis(x, axis, -1)
- if not same_data and y.ndim > 1:
- y = np.moveaxis(y, axis, -1)
- # Check if x and y are the same length, zero-pad if necessary
- if not same_data:
- if x.shape[-1] != y.shape[-1]:
- if x.shape[-1] < y.shape[-1]:
- pad_shape = list(x.shape)
- pad_shape[-1] = y.shape[-1] - x.shape[-1]
- x = np.concatenate((x, np.zeros(pad_shape)), -1)
- else:
- pad_shape = list(y.shape)
- pad_shape[-1] = x.shape[-1] - y.shape[-1]
- y = np.concatenate((y, np.zeros(pad_shape)), -1)
- if nperseg is not None: # if specified by user
- nperseg = int(nperseg)
- if nperseg < 1:
- raise ValueError('nperseg must be a positive integer')
- # parse window; if array like, then set nperseg = win.shape
- win, nperseg = _triage_segments(window, nperseg, input_length=x.shape[-1])
- if nfft is None:
- nfft = nperseg
- elif nfft < nperseg:
- raise ValueError('nfft must be greater than or equal to nperseg.')
- else:
- nfft = int(nfft)
- if noverlap is None:
- noverlap = nperseg//2
- else:
- noverlap = int(noverlap)
- if noverlap >= nperseg:
- raise ValueError('noverlap must be less than nperseg.')
- nstep = nperseg - noverlap
- # Padding occurs after boundary extension, so that the extended signal ends
- # in zeros, instead of introducing an impulse at the end.
- # I.e. if x = [..., 3, 2]
- # extend then pad -> [..., 3, 2, 2, 3, 0, 0, 0]
- # pad then extend -> [..., 3, 2, 0, 0, 0, 2, 3]
- if boundary is not None:
- ext_func = boundary_funcs[boundary]
- x = ext_func(x, nperseg//2, axis=-1)
- if not same_data:
- y = ext_func(y, nperseg//2, axis=-1)
- if padded:
- # Pad to integer number of windowed segments
- # I.e make x.shape[-1] = nperseg + (nseg-1)*nstep, with integer nseg
- nadd = (-(x.shape[-1]-nperseg) % nstep) % nperseg
- zeros_shape = list(x.shape[:-1]) + [nadd]
- x = np.concatenate((x, np.zeros(zeros_shape)), axis=-1)
- if not same_data:
- zeros_shape = list(y.shape[:-1]) + [nadd]
- y = np.concatenate((y, np.zeros(zeros_shape)), axis=-1)
- # Handle detrending and window functions
- if not detrend:
- def detrend_func(d):
- return d
- elif not hasattr(detrend, '__call__'):
- def detrend_func(d):
- return _signaltools.detrend(d, type=detrend, axis=-1)
- elif axis != -1:
- # Wrap this function so that it receives a shape that it could
- # reasonably expect to receive.
- def detrend_func(d):
- d = np.moveaxis(d, -1, axis)
- d = detrend(d)
- return np.moveaxis(d, axis, -1)
- else:
- detrend_func = detrend
- if np.result_type(win, np.complex64) != outdtype:
- win = win.astype(outdtype)
- if scaling == 'density':
- scale = 1.0 / (fs * (win*win).sum())
- elif scaling == 'spectrum':
- scale = 1.0 / win.sum()**2
- else:
- raise ValueError('Unknown scaling: %r' % scaling)
- if mode == 'stft':
- scale = np.sqrt(scale)
- if return_onesided:
- if np.iscomplexobj(x):
- sides = 'twosided'
- warnings.warn('Input data is complex, switching to '
- 'return_onesided=False')
- else:
- sides = 'onesided'
- if not same_data:
- if np.iscomplexobj(y):
- sides = 'twosided'
- warnings.warn('Input data is complex, switching to '
- 'return_onesided=False')
- else:
- sides = 'twosided'
- if sides == 'twosided':
- freqs = sp_fft.fftfreq(nfft, 1/fs)
- elif sides == 'onesided':
- freqs = sp_fft.rfftfreq(nfft, 1/fs)
- # Perform the windowed FFTs
- result = _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides)
- if not same_data:
- # All the same operations on the y data
- result_y = _fft_helper(y, win, detrend_func, nperseg, noverlap, nfft,
- sides)
- result = np.conjugate(result) * result_y
- elif mode == 'psd':
- result = np.conjugate(result) * result
- result *= scale
- if sides == 'onesided' and mode == 'psd':
- if nfft % 2:
- result[..., 1:] *= 2
- else:
- # Last point is unpaired Nyquist freq point, don't double
- result[..., 1:-1] *= 2
- time = np.arange(nperseg/2, x.shape[-1] - nperseg/2 + 1,
- nperseg - noverlap)/float(fs)
- if boundary is not None:
- time -= (nperseg/2) / fs
- result = result.astype(outdtype)
- # All imaginary parts are zero anyways
- if same_data and mode != 'stft':
- result = result.real
- # Output is going to have new last axis for time/window index, so a
- # negative axis index shifts down one
- if axis < 0:
- axis -= 1
- # Roll frequency axis back to axis where the data came from
- result = np.moveaxis(result, -1, axis)
- return freqs, time, result
- def _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides):
- """
- Calculate windowed FFT, for internal use by
- `scipy.signal._spectral_helper`.
- This is a helper function that does the main FFT calculation for
- `_spectral helper`. All input validation is performed there, and the
- data axis is assumed to be the last axis of x. It is not designed to
- be called externally. The windows are not averaged over; the result
- from each window is returned.
- Returns
- -------
- result : ndarray
- Array of FFT data
- Notes
- -----
- Adapted from matplotlib.mlab
- .. versionadded:: 0.16.0
- """
- # Created strided array of data segments
- if nperseg == 1 and noverlap == 0:
- result = x[..., np.newaxis]
- else:
- # https://stackoverflow.com/a/5568169
- step = nperseg - noverlap
- shape = x.shape[:-1]+((x.shape[-1]-noverlap)//step, nperseg)
- strides = x.strides[:-1]+(step*x.strides[-1], x.strides[-1])
- result = np.lib.stride_tricks.as_strided(x, shape=shape,
- strides=strides)
- # Detrend each data segment individually
- result = detrend_func(result)
- # Apply window by multiplication
- result = win * result
- # Perform the fft. Acts on last axis by default. Zero-pads automatically
- if sides == 'twosided':
- func = sp_fft.fft
- else:
- result = result.real
- func = sp_fft.rfft
- result = func(result, n=nfft)
- return result
- def _triage_segments(window, nperseg, input_length):
- """
- Parses window and nperseg arguments for spectrogram and _spectral_helper.
- This is a helper function, not meant to be called externally.
- Parameters
- ----------
- window : string, tuple, or ndarray
- If window is specified by a string or tuple and nperseg is not
- specified, nperseg is set to the default of 256 and returns a window of
- that length.
- If instead the window is array_like and nperseg is not specified, then
- nperseg is set to the length of the window. A ValueError is raised if
- the user supplies both an array_like window and a value for nperseg but
- nperseg does not equal the length of the window.
- nperseg : int
- Length of each segment
- input_length: int
- Length of input signal, i.e. x.shape[-1]. Used to test for errors.
- Returns
- -------
- win : ndarray
- window. If function was called with string or tuple than this will hold
- the actual array used as a window.
- nperseg : int
- Length of each segment. If window is str or tuple, nperseg is set to
- 256. If window is array_like, nperseg is set to the length of the
- window.
- """
- # parse window; if array like, then set nperseg = win.shape
- if isinstance(window, str) or isinstance(window, tuple):
- # if nperseg not specified
- if nperseg is None:
- nperseg = 256 # then change to default
- if nperseg > input_length:
- warnings.warn('nperseg = {0:d} is greater than input length '
- ' = {1:d}, using nperseg = {1:d}'
- .format(nperseg, input_length))
- nperseg = input_length
- win = get_window(window, nperseg)
- else:
- win = np.asarray(window)
- if len(win.shape) != 1:
- raise ValueError('window must be 1-D')
- if input_length < win.shape[-1]:
- raise ValueError('window is longer than input signal')
- if nperseg is None:
- nperseg = win.shape[0]
- elif nperseg is not None:
- if nperseg != win.shape[0]:
- raise ValueError("value specified for nperseg is different"
- " from length of window")
- return win, nperseg
- def _median_bias(n):
- """
- Returns the bias of the median of a set of periodograms relative to
- the mean.
- See Appendix B from [1]_ for details.
- Parameters
- ----------
- n : int
- Numbers of periodograms being averaged.
- Returns
- -------
- bias : float
- Calculated bias.
- References
- ----------
- .. [1] B. Allen, W.G. Anderson, P.R. Brady, D.A. Brown, J.D.E. Creighton.
- "FINDCHIRP: an algorithm for detection of gravitational waves from
- inspiraling compact binaries", Physical Review D 85, 2012,
- :arxiv:`gr-qc/0509116`
- """
- ii_2 = 2 * np.arange(1., (n-1) // 2 + 1)
- return 1 + np.sum(1. / (ii_2 + 1) - 1. / ii_2)
|