_spectral_py.py 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059
  1. """Tools for spectral analysis.
  2. """
  3. import numpy as np
  4. from scipy import fft as sp_fft
  5. from . import _signaltools
  6. from .windows import get_window
  7. from ._spectral import _lombscargle
  8. from ._arraytools import const_ext, even_ext, odd_ext, zero_ext
  9. import warnings
  10. __all__ = ['periodogram', 'welch', 'lombscargle', 'csd', 'coherence',
  11. 'spectrogram', 'stft', 'istft', 'check_COLA', 'check_NOLA']
  12. def lombscargle(x,
  13. y,
  14. freqs,
  15. precenter=False,
  16. normalize=False):
  17. """
  18. lombscargle(x, y, freqs)
  19. Computes the Lomb-Scargle periodogram.
  20. The Lomb-Scargle periodogram was developed by Lomb [1]_ and further
  21. extended by Scargle [2]_ to find, and test the significance of weak
  22. periodic signals with uneven temporal sampling.
  23. When *normalize* is False (default) the computed periodogram
  24. is unnormalized, it takes the value ``(A**2) * N/4`` for a harmonic
  25. signal with amplitude A for sufficiently large N.
  26. When *normalize* is True the computed periodogram is normalized by
  27. the residuals of the data around a constant reference model (at zero).
  28. Input arrays should be 1-D and will be cast to float64.
  29. Parameters
  30. ----------
  31. x : array_like
  32. Sample times.
  33. y : array_like
  34. Measurement values.
  35. freqs : array_like
  36. Angular frequencies for output periodogram.
  37. precenter : bool, optional
  38. Pre-center measurement values by subtracting the mean.
  39. normalize : bool, optional
  40. Compute normalized periodogram.
  41. Returns
  42. -------
  43. pgram : array_like
  44. Lomb-Scargle periodogram.
  45. Raises
  46. ------
  47. ValueError
  48. If the input arrays `x` and `y` do not have the same shape.
  49. See Also
  50. --------
  51. istft: Inverse Short Time Fourier Transform
  52. check_COLA: Check whether the Constant OverLap Add (COLA) constraint is met
  53. welch: Power spectral density by Welch's method
  54. spectrogram: Spectrogram by Welch's method
  55. csd: Cross spectral density by Welch's method
  56. Notes
  57. -----
  58. This subroutine calculates the periodogram using a slightly
  59. modified algorithm due to Townsend [3]_ which allows the
  60. periodogram to be calculated using only a single pass through
  61. the input arrays for each frequency.
  62. The algorithm running time scales roughly as O(x * freqs) or O(N^2)
  63. for a large number of samples and frequencies.
  64. References
  65. ----------
  66. .. [1] N.R. Lomb "Least-squares frequency analysis of unequally spaced
  67. data", Astrophysics and Space Science, vol 39, pp. 447-462, 1976
  68. .. [2] J.D. Scargle "Studies in astronomical time series analysis. II -
  69. Statistical aspects of spectral analysis of unevenly spaced data",
  70. The Astrophysical Journal, vol 263, pp. 835-853, 1982
  71. .. [3] R.H.D. Townsend, "Fast calculation of the Lomb-Scargle
  72. periodogram using graphics processing units.", The Astrophysical
  73. Journal Supplement Series, vol 191, pp. 247-253, 2010
  74. Examples
  75. --------
  76. >>> import numpy as np
  77. >>> import matplotlib.pyplot as plt
  78. >>> rng = np.random.default_rng()
  79. First define some input parameters for the signal:
  80. >>> A = 2.
  81. >>> w0 = 1. # rad/sec
  82. >>> nin = 150
  83. >>> nout = 100000
  84. Randomly generate sample times:
  85. >>> x = rng.uniform(0, 10*np.pi, nin)
  86. Plot a sine wave for the selected times:
  87. >>> y = A * np.cos(w0*x)
  88. Define the array of frequencies for which to compute the periodogram:
  89. >>> w = np.linspace(0.01, 10, nout)
  90. Calculate Lomb-Scargle periodogram:
  91. >>> import scipy.signal as signal
  92. >>> pgram = signal.lombscargle(x, y, w, normalize=True)
  93. Now make a plot of the input data:
  94. >>> fig, (ax_t, ax_w) = plt.subplots(2, 1, constrained_layout=True)
  95. >>> ax_t.plot(x, y, 'b+')
  96. >>> ax_t.set_xlabel('Time [s]')
  97. Then plot the normalized periodogram:
  98. >>> ax_w.plot(w, pgram)
  99. >>> ax_w.set_xlabel('Angular frequency [rad/s]')
  100. >>> ax_w.set_ylabel('Normalized amplitude')
  101. >>> plt.show()
  102. """
  103. x = np.ascontiguousarray(x, dtype=np.float64)
  104. y = np.ascontiguousarray(y, dtype=np.float64)
  105. freqs = np.ascontiguousarray(freqs, dtype=np.float64)
  106. assert x.ndim == 1
  107. assert y.ndim == 1
  108. assert freqs.ndim == 1
  109. if precenter:
  110. pgram = _lombscargle(x, y - y.mean(), freqs)
  111. else:
  112. pgram = _lombscargle(x, y, freqs)
  113. if normalize:
  114. pgram *= 2 / np.dot(y, y)
  115. return pgram
  116. def periodogram(x, fs=1.0, window='boxcar', nfft=None, detrend='constant',
  117. return_onesided=True, scaling='density', axis=-1):
  118. """
  119. Estimate power spectral density using a periodogram.
  120. Parameters
  121. ----------
  122. x : array_like
  123. Time series of measurement values
  124. fs : float, optional
  125. Sampling frequency of the `x` time series. Defaults to 1.0.
  126. window : str or tuple or array_like, optional
  127. Desired window to use. If `window` is a string or tuple, it is
  128. passed to `get_window` to generate the window values, which are
  129. DFT-even by default. See `get_window` for a list of windows and
  130. required parameters. If `window` is array_like it will be used
  131. directly as the window and its length must be equal to the length
  132. of the axis over which the periodogram is computed. Defaults
  133. to 'boxcar'.
  134. nfft : int, optional
  135. Length of the FFT used. If `None` the length of `x` will be
  136. used.
  137. detrend : str or function or `False`, optional
  138. Specifies how to detrend each segment. If `detrend` is a
  139. string, it is passed as the `type` argument to the `detrend`
  140. function. If it is a function, it takes a segment and returns a
  141. detrended segment. If `detrend` is `False`, no detrending is
  142. done. Defaults to 'constant'.
  143. return_onesided : bool, optional
  144. If `True`, return a one-sided spectrum for real data. If
  145. `False` return a two-sided spectrum. Defaults to `True`, but for
  146. complex data, a two-sided spectrum is always returned.
  147. scaling : { 'density', 'spectrum' }, optional
  148. Selects between computing the power spectral density ('density')
  149. where `Pxx` has units of V**2/Hz and computing the power
  150. spectrum ('spectrum') where `Pxx` has units of V**2, if `x`
  151. is measured in V and `fs` is measured in Hz. Defaults to
  152. 'density'
  153. axis : int, optional
  154. Axis along which the periodogram is computed; the default is
  155. over the last axis (i.e. ``axis=-1``).
  156. Returns
  157. -------
  158. f : ndarray
  159. Array of sample frequencies.
  160. Pxx : ndarray
  161. Power spectral density or power spectrum of `x`.
  162. See Also
  163. --------
  164. welch: Estimate power spectral density using Welch's method
  165. lombscargle: Lomb-Scargle periodogram for unevenly sampled data
  166. Notes
  167. -----
  168. .. versionadded:: 0.12.0
  169. Examples
  170. --------
  171. >>> import numpy as np
  172. >>> from scipy import signal
  173. >>> import matplotlib.pyplot as plt
  174. >>> rng = np.random.default_rng()
  175. Generate a test signal, a 2 Vrms sine wave at 1234 Hz, corrupted by
  176. 0.001 V**2/Hz of white noise sampled at 10 kHz.
  177. >>> fs = 10e3
  178. >>> N = 1e5
  179. >>> amp = 2*np.sqrt(2)
  180. >>> freq = 1234.0
  181. >>> noise_power = 0.001 * fs / 2
  182. >>> time = np.arange(N) / fs
  183. >>> x = amp*np.sin(2*np.pi*freq*time)
  184. >>> x += rng.normal(scale=np.sqrt(noise_power), size=time.shape)
  185. Compute and plot the power spectral density.
  186. >>> f, Pxx_den = signal.periodogram(x, fs)
  187. >>> plt.semilogy(f, Pxx_den)
  188. >>> plt.ylim([1e-7, 1e2])
  189. >>> plt.xlabel('frequency [Hz]')
  190. >>> plt.ylabel('PSD [V**2/Hz]')
  191. >>> plt.show()
  192. If we average the last half of the spectral density, to exclude the
  193. peak, we can recover the noise power on the signal.
  194. >>> np.mean(Pxx_den[25000:])
  195. 0.000985320699252543
  196. Now compute and plot the power spectrum.
  197. >>> f, Pxx_spec = signal.periodogram(x, fs, 'flattop', scaling='spectrum')
  198. >>> plt.figure()
  199. >>> plt.semilogy(f, np.sqrt(Pxx_spec))
  200. >>> plt.ylim([1e-4, 1e1])
  201. >>> plt.xlabel('frequency [Hz]')
  202. >>> plt.ylabel('Linear spectrum [V RMS]')
  203. >>> plt.show()
  204. The peak height in the power spectrum is an estimate of the RMS
  205. amplitude.
  206. >>> np.sqrt(Pxx_spec.max())
  207. 2.0077340678640727
  208. """
  209. x = np.asarray(x)
  210. if x.size == 0:
  211. return np.empty(x.shape), np.empty(x.shape)
  212. if window is None:
  213. window = 'boxcar'
  214. if nfft is None:
  215. nperseg = x.shape[axis]
  216. elif nfft == x.shape[axis]:
  217. nperseg = nfft
  218. elif nfft > x.shape[axis]:
  219. nperseg = x.shape[axis]
  220. elif nfft < x.shape[axis]:
  221. s = [np.s_[:]]*len(x.shape)
  222. s[axis] = np.s_[:nfft]
  223. x = x[tuple(s)]
  224. nperseg = nfft
  225. nfft = None
  226. if hasattr(window, 'size'):
  227. if window.size != nperseg:
  228. raise ValueError('the size of the window must be the same size '
  229. 'of the input on the specified axis')
  230. return welch(x, fs=fs, window=window, nperseg=nperseg, noverlap=0,
  231. nfft=nfft, detrend=detrend, return_onesided=return_onesided,
  232. scaling=scaling, axis=axis)
  233. def welch(x, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None,
  234. detrend='constant', return_onesided=True, scaling='density',
  235. axis=-1, average='mean'):
  236. r"""
  237. Estimate power spectral density using Welch's method.
  238. Welch's method [1]_ computes an estimate of the power spectral
  239. density by dividing the data into overlapping segments, computing a
  240. modified periodogram for each segment and averaging the
  241. periodograms.
  242. Parameters
  243. ----------
  244. x : array_like
  245. Time series of measurement values
  246. fs : float, optional
  247. Sampling frequency of the `x` time series. Defaults to 1.0.
  248. window : str or tuple or array_like, optional
  249. Desired window to use. If `window` is a string or tuple, it is
  250. passed to `get_window` to generate the window values, which are
  251. DFT-even by default. See `get_window` for a list of windows and
  252. required parameters. If `window` is array_like it will be used
  253. directly as the window and its length must be nperseg. Defaults
  254. to a Hann window.
  255. nperseg : int, optional
  256. Length of each segment. Defaults to None, but if window is str or
  257. tuple, is set to 256, and if window is array_like, is set to the
  258. length of the window.
  259. noverlap : int, optional
  260. Number of points to overlap between segments. If `None`,
  261. ``noverlap = nperseg // 2``. Defaults to `None`.
  262. nfft : int, optional
  263. Length of the FFT used, if a zero padded FFT is desired. If
  264. `None`, the FFT length is `nperseg`. Defaults to `None`.
  265. detrend : str or function or `False`, optional
  266. Specifies how to detrend each segment. If `detrend` is a
  267. string, it is passed as the `type` argument to the `detrend`
  268. function. If it is a function, it takes a segment and returns a
  269. detrended segment. If `detrend` is `False`, no detrending is
  270. done. Defaults to 'constant'.
  271. return_onesided : bool, optional
  272. If `True`, return a one-sided spectrum for real data. If
  273. `False` return a two-sided spectrum. Defaults to `True`, but for
  274. complex data, a two-sided spectrum is always returned.
  275. scaling : { 'density', 'spectrum' }, optional
  276. Selects between computing the power spectral density ('density')
  277. where `Pxx` has units of V**2/Hz and computing the power
  278. spectrum ('spectrum') where `Pxx` has units of V**2, if `x`
  279. is measured in V and `fs` is measured in Hz. Defaults to
  280. 'density'
  281. axis : int, optional
  282. Axis along which the periodogram is computed; the default is
  283. over the last axis (i.e. ``axis=-1``).
  284. average : { 'mean', 'median' }, optional
  285. Method to use when averaging periodograms. Defaults to 'mean'.
  286. .. versionadded:: 1.2.0
  287. Returns
  288. -------
  289. f : ndarray
  290. Array of sample frequencies.
  291. Pxx : ndarray
  292. Power spectral density or power spectrum of x.
  293. See Also
  294. --------
  295. periodogram: Simple, optionally modified periodogram
  296. lombscargle: Lomb-Scargle periodogram for unevenly sampled data
  297. Notes
  298. -----
  299. An appropriate amount of overlap will depend on the choice of window
  300. and on your requirements. For the default Hann window an overlap of
  301. 50% is a reasonable trade off between accurately estimating the
  302. signal power, while not over counting any of the data. Narrower
  303. windows may require a larger overlap.
  304. If `noverlap` is 0, this method is equivalent to Bartlett's method
  305. [2]_.
  306. .. versionadded:: 0.12.0
  307. References
  308. ----------
  309. .. [1] P. Welch, "The use of the fast Fourier transform for the
  310. estimation of power spectra: A method based on time averaging
  311. over short, modified periodograms", IEEE Trans. Audio
  312. Electroacoust. vol. 15, pp. 70-73, 1967.
  313. .. [2] M.S. Bartlett, "Periodogram Analysis and Continuous Spectra",
  314. Biometrika, vol. 37, pp. 1-16, 1950.
  315. Examples
  316. --------
  317. >>> import numpy as np
  318. >>> from scipy import signal
  319. >>> import matplotlib.pyplot as plt
  320. >>> rng = np.random.default_rng()
  321. Generate a test signal, a 2 Vrms sine wave at 1234 Hz, corrupted by
  322. 0.001 V**2/Hz of white noise sampled at 10 kHz.
  323. >>> fs = 10e3
  324. >>> N = 1e5
  325. >>> amp = 2*np.sqrt(2)
  326. >>> freq = 1234.0
  327. >>> noise_power = 0.001 * fs / 2
  328. >>> time = np.arange(N) / fs
  329. >>> x = amp*np.sin(2*np.pi*freq*time)
  330. >>> x += rng.normal(scale=np.sqrt(noise_power), size=time.shape)
  331. Compute and plot the power spectral density.
  332. >>> f, Pxx_den = signal.welch(x, fs, nperseg=1024)
  333. >>> plt.semilogy(f, Pxx_den)
  334. >>> plt.ylim([0.5e-3, 1])
  335. >>> plt.xlabel('frequency [Hz]')
  336. >>> plt.ylabel('PSD [V**2/Hz]')
  337. >>> plt.show()
  338. If we average the last half of the spectral density, to exclude the
  339. peak, we can recover the noise power on the signal.
  340. >>> np.mean(Pxx_den[256:])
  341. 0.0009924865443739191
  342. Now compute and plot the power spectrum.
  343. >>> f, Pxx_spec = signal.welch(x, fs, 'flattop', 1024, scaling='spectrum')
  344. >>> plt.figure()
  345. >>> plt.semilogy(f, np.sqrt(Pxx_spec))
  346. >>> plt.xlabel('frequency [Hz]')
  347. >>> plt.ylabel('Linear spectrum [V RMS]')
  348. >>> plt.show()
  349. The peak height in the power spectrum is an estimate of the RMS
  350. amplitude.
  351. >>> np.sqrt(Pxx_spec.max())
  352. 2.0077340678640727
  353. If we now introduce a discontinuity in the signal, by increasing the
  354. amplitude of a small portion of the signal by 50, we can see the
  355. corruption of the mean average power spectral density, but using a
  356. median average better estimates the normal behaviour.
  357. >>> x[int(N//2):int(N//2)+10] *= 50.
  358. >>> f, Pxx_den = signal.welch(x, fs, nperseg=1024)
  359. >>> f_med, Pxx_den_med = signal.welch(x, fs, nperseg=1024, average='median')
  360. >>> plt.semilogy(f, Pxx_den, label='mean')
  361. >>> plt.semilogy(f_med, Pxx_den_med, label='median')
  362. >>> plt.ylim([0.5e-3, 1])
  363. >>> plt.xlabel('frequency [Hz]')
  364. >>> plt.ylabel('PSD [V**2/Hz]')
  365. >>> plt.legend()
  366. >>> plt.show()
  367. """
  368. freqs, Pxx = csd(x, x, fs=fs, window=window, nperseg=nperseg,
  369. noverlap=noverlap, nfft=nfft, detrend=detrend,
  370. return_onesided=return_onesided, scaling=scaling,
  371. axis=axis, average=average)
  372. return freqs, Pxx.real
  373. def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None,
  374. detrend='constant', return_onesided=True, scaling='density',
  375. axis=-1, average='mean'):
  376. r"""
  377. Estimate the cross power spectral density, Pxy, using Welch's method.
  378. Parameters
  379. ----------
  380. x : array_like
  381. Time series of measurement values
  382. y : array_like
  383. Time series of measurement values
  384. fs : float, optional
  385. Sampling frequency of the `x` and `y` time series. Defaults
  386. to 1.0.
  387. window : str or tuple or array_like, optional
  388. Desired window to use. If `window` is a string or tuple, it is
  389. passed to `get_window` to generate the window values, which are
  390. DFT-even by default. See `get_window` for a list of windows and
  391. required parameters. If `window` is array_like it will be used
  392. directly as the window and its length must be nperseg. Defaults
  393. to a Hann window.
  394. nperseg : int, optional
  395. Length of each segment. Defaults to None, but if window is str or
  396. tuple, is set to 256, and if window is array_like, is set to the
  397. length of the window.
  398. noverlap: int, optional
  399. Number of points to overlap between segments. If `None`,
  400. ``noverlap = nperseg // 2``. Defaults to `None`.
  401. nfft : int, optional
  402. Length of the FFT used, if a zero padded FFT is desired. If
  403. `None`, the FFT length is `nperseg`. Defaults to `None`.
  404. detrend : str or function or `False`, optional
  405. Specifies how to detrend each segment. If `detrend` is a
  406. string, it is passed as the `type` argument to the `detrend`
  407. function. If it is a function, it takes a segment and returns a
  408. detrended segment. If `detrend` is `False`, no detrending is
  409. done. Defaults to 'constant'.
  410. return_onesided : bool, optional
  411. If `True`, return a one-sided spectrum for real data. If
  412. `False` return a two-sided spectrum. Defaults to `True`, but for
  413. complex data, a two-sided spectrum is always returned.
  414. scaling : { 'density', 'spectrum' }, optional
  415. Selects between computing the cross spectral density ('density')
  416. where `Pxy` has units of V**2/Hz and computing the cross spectrum
  417. ('spectrum') where `Pxy` has units of V**2, if `x` and `y` are
  418. measured in V and `fs` is measured in Hz. Defaults to 'density'
  419. axis : int, optional
  420. Axis along which the CSD is computed for both inputs; the
  421. default is over the last axis (i.e. ``axis=-1``).
  422. average : { 'mean', 'median' }, optional
  423. Method to use when averaging periodograms. If the spectrum is
  424. complex, the average is computed separately for the real and
  425. imaginary parts. Defaults to 'mean'.
  426. .. versionadded:: 1.2.0
  427. Returns
  428. -------
  429. f : ndarray
  430. Array of sample frequencies.
  431. Pxy : ndarray
  432. Cross spectral density or cross power spectrum of x,y.
  433. See Also
  434. --------
  435. periodogram: Simple, optionally modified periodogram
  436. lombscargle: Lomb-Scargle periodogram for unevenly sampled data
  437. welch: Power spectral density by Welch's method. [Equivalent to
  438. csd(x,x)]
  439. coherence: Magnitude squared coherence by Welch's method.
  440. Notes
  441. -----
  442. By convention, Pxy is computed with the conjugate FFT of X
  443. multiplied by the FFT of Y.
  444. If the input series differ in length, the shorter series will be
  445. zero-padded to match.
  446. An appropriate amount of overlap will depend on the choice of window
  447. and on your requirements. For the default Hann window an overlap of
  448. 50% is a reasonable trade off between accurately estimating the
  449. signal power, while not over counting any of the data. Narrower
  450. windows may require a larger overlap.
  451. .. versionadded:: 0.16.0
  452. References
  453. ----------
  454. .. [1] P. Welch, "The use of the fast Fourier transform for the
  455. estimation of power spectra: A method based on time averaging
  456. over short, modified periodograms", IEEE Trans. Audio
  457. Electroacoust. vol. 15, pp. 70-73, 1967.
  458. .. [2] Rabiner, Lawrence R., and B. Gold. "Theory and Application of
  459. Digital Signal Processing" Prentice-Hall, pp. 414-419, 1975
  460. Examples
  461. --------
  462. >>> import numpy as np
  463. >>> from scipy import signal
  464. >>> import matplotlib.pyplot as plt
  465. >>> rng = np.random.default_rng()
  466. Generate two test signals with some common features.
  467. >>> fs = 10e3
  468. >>> N = 1e5
  469. >>> amp = 20
  470. >>> freq = 1234.0
  471. >>> noise_power = 0.001 * fs / 2
  472. >>> time = np.arange(N) / fs
  473. >>> b, a = signal.butter(2, 0.25, 'low')
  474. >>> x = rng.normal(scale=np.sqrt(noise_power), size=time.shape)
  475. >>> y = signal.lfilter(b, a, x)
  476. >>> x += amp*np.sin(2*np.pi*freq*time)
  477. >>> y += rng.normal(scale=0.1*np.sqrt(noise_power), size=time.shape)
  478. Compute and plot the magnitude of the cross spectral density.
  479. >>> f, Pxy = signal.csd(x, y, fs, nperseg=1024)
  480. >>> plt.semilogy(f, np.abs(Pxy))
  481. >>> plt.xlabel('frequency [Hz]')
  482. >>> plt.ylabel('CSD [V**2/Hz]')
  483. >>> plt.show()
  484. """
  485. freqs, _, Pxy = _spectral_helper(x, y, fs, window, nperseg, noverlap, nfft,
  486. detrend, return_onesided, scaling, axis,
  487. mode='psd')
  488. # Average over windows.
  489. if len(Pxy.shape) >= 2 and Pxy.size > 0:
  490. if Pxy.shape[-1] > 1:
  491. if average == 'median':
  492. # np.median must be passed real arrays for the desired result
  493. bias = _median_bias(Pxy.shape[-1])
  494. if np.iscomplexobj(Pxy):
  495. Pxy = (np.median(np.real(Pxy), axis=-1)
  496. + 1j * np.median(np.imag(Pxy), axis=-1))
  497. else:
  498. Pxy = np.median(Pxy, axis=-1)
  499. Pxy /= bias
  500. elif average == 'mean':
  501. Pxy = Pxy.mean(axis=-1)
  502. else:
  503. raise ValueError('average must be "median" or "mean", got %s'
  504. % (average,))
  505. else:
  506. Pxy = np.reshape(Pxy, Pxy.shape[:-1])
  507. return freqs, Pxy
  508. def spectrogram(x, fs=1.0, window=('tukey', .25), nperseg=None, noverlap=None,
  509. nfft=None, detrend='constant', return_onesided=True,
  510. scaling='density', axis=-1, mode='psd'):
  511. """Compute a spectrogram with consecutive Fourier transforms.
  512. Spectrograms can be used as a way of visualizing the change of a
  513. nonstationary signal's frequency content over time.
  514. Parameters
  515. ----------
  516. x : array_like
  517. Time series of measurement values
  518. fs : float, optional
  519. Sampling frequency of the `x` time series. Defaults to 1.0.
  520. window : str or tuple or array_like, optional
  521. Desired window to use. If `window` is a string or tuple, it is
  522. passed to `get_window` to generate the window values, which are
  523. DFT-even by default. See `get_window` for a list of windows and
  524. required parameters. If `window` is array_like it will be used
  525. directly as the window and its length must be nperseg.
  526. Defaults to a Tukey window with shape parameter of 0.25.
  527. nperseg : int, optional
  528. Length of each segment. Defaults to None, but if window is str or
  529. tuple, is set to 256, and if window is array_like, is set to the
  530. length of the window.
  531. noverlap : int, optional
  532. Number of points to overlap between segments. If `None`,
  533. ``noverlap = nperseg // 8``. Defaults to `None`.
  534. nfft : int, optional
  535. Length of the FFT used, if a zero padded FFT is desired. If
  536. `None`, the FFT length is `nperseg`. Defaults to `None`.
  537. detrend : str or function or `False`, optional
  538. Specifies how to detrend each segment. If `detrend` is a
  539. string, it is passed as the `type` argument to the `detrend`
  540. function. If it is a function, it takes a segment and returns a
  541. detrended segment. If `detrend` is `False`, no detrending is
  542. done. Defaults to 'constant'.
  543. return_onesided : bool, optional
  544. If `True`, return a one-sided spectrum for real data. If
  545. `False` return a two-sided spectrum. Defaults to `True`, but for
  546. complex data, a two-sided spectrum is always returned.
  547. scaling : { 'density', 'spectrum' }, optional
  548. Selects between computing the power spectral density ('density')
  549. where `Sxx` has units of V**2/Hz and computing the power
  550. spectrum ('spectrum') where `Sxx` has units of V**2, if `x`
  551. is measured in V and `fs` is measured in Hz. Defaults to
  552. 'density'.
  553. axis : int, optional
  554. Axis along which the spectrogram is computed; the default is over
  555. the last axis (i.e. ``axis=-1``).
  556. mode : str, optional
  557. Defines what kind of return values are expected. Options are
  558. ['psd', 'complex', 'magnitude', 'angle', 'phase']. 'complex' is
  559. equivalent to the output of `stft` with no padding or boundary
  560. extension. 'magnitude' returns the absolute magnitude of the
  561. STFT. 'angle' and 'phase' return the complex angle of the STFT,
  562. with and without unwrapping, respectively.
  563. Returns
  564. -------
  565. f : ndarray
  566. Array of sample frequencies.
  567. t : ndarray
  568. Array of segment times.
  569. Sxx : ndarray
  570. Spectrogram of x. By default, the last axis of Sxx corresponds
  571. to the segment times.
  572. See Also
  573. --------
  574. periodogram: Simple, optionally modified periodogram
  575. lombscargle: Lomb-Scargle periodogram for unevenly sampled data
  576. welch: Power spectral density by Welch's method.
  577. csd: Cross spectral density by Welch's method.
  578. Notes
  579. -----
  580. An appropriate amount of overlap will depend on the choice of window
  581. and on your requirements. In contrast to welch's method, where the
  582. entire data stream is averaged over, one may wish to use a smaller
  583. overlap (or perhaps none at all) when computing a spectrogram, to
  584. maintain some statistical independence between individual segments.
  585. It is for this reason that the default window is a Tukey window with
  586. 1/8th of a window's length overlap at each end.
  587. .. versionadded:: 0.16.0
  588. References
  589. ----------
  590. .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck
  591. "Discrete-Time Signal Processing", Prentice Hall, 1999.
  592. Examples
  593. --------
  594. >>> import numpy as np
  595. >>> from scipy import signal
  596. >>> from scipy.fft import fftshift
  597. >>> import matplotlib.pyplot as plt
  598. >>> rng = np.random.default_rng()
  599. Generate a test signal, a 2 Vrms sine wave whose frequency is slowly
  600. modulated around 3kHz, corrupted by white noise of exponentially
  601. decreasing magnitude sampled at 10 kHz.
  602. >>> fs = 10e3
  603. >>> N = 1e5
  604. >>> amp = 2 * np.sqrt(2)
  605. >>> noise_power = 0.01 * fs / 2
  606. >>> time = np.arange(N) / float(fs)
  607. >>> mod = 500*np.cos(2*np.pi*0.25*time)
  608. >>> carrier = amp * np.sin(2*np.pi*3e3*time + mod)
  609. >>> noise = rng.normal(scale=np.sqrt(noise_power), size=time.shape)
  610. >>> noise *= np.exp(-time/5)
  611. >>> x = carrier + noise
  612. Compute and plot the spectrogram.
  613. >>> f, t, Sxx = signal.spectrogram(x, fs)
  614. >>> plt.pcolormesh(t, f, Sxx, shading='gouraud')
  615. >>> plt.ylabel('Frequency [Hz]')
  616. >>> plt.xlabel('Time [sec]')
  617. >>> plt.show()
  618. Note, if using output that is not one sided, then use the following:
  619. >>> f, t, Sxx = signal.spectrogram(x, fs, return_onesided=False)
  620. >>> plt.pcolormesh(t, fftshift(f), fftshift(Sxx, axes=0), shading='gouraud')
  621. >>> plt.ylabel('Frequency [Hz]')
  622. >>> plt.xlabel('Time [sec]')
  623. >>> plt.show()
  624. """
  625. modelist = ['psd', 'complex', 'magnitude', 'angle', 'phase']
  626. if mode not in modelist:
  627. raise ValueError('unknown value for mode {}, must be one of {}'
  628. .format(mode, modelist))
  629. # need to set default for nperseg before setting default for noverlap below
  630. window, nperseg = _triage_segments(window, nperseg,
  631. input_length=x.shape[axis])
  632. # Less overlap than welch, so samples are more statisically independent
  633. if noverlap is None:
  634. noverlap = nperseg // 8
  635. if mode == 'psd':
  636. freqs, time, Sxx = _spectral_helper(x, x, fs, window, nperseg,
  637. noverlap, nfft, detrend,
  638. return_onesided, scaling, axis,
  639. mode='psd')
  640. else:
  641. freqs, time, Sxx = _spectral_helper(x, x, fs, window, nperseg,
  642. noverlap, nfft, detrend,
  643. return_onesided, scaling, axis,
  644. mode='stft')
  645. if mode == 'magnitude':
  646. Sxx = np.abs(Sxx)
  647. elif mode in ['angle', 'phase']:
  648. Sxx = np.angle(Sxx)
  649. if mode == 'phase':
  650. # Sxx has one additional dimension for time strides
  651. if axis < 0:
  652. axis -= 1
  653. Sxx = np.unwrap(Sxx, axis=axis)
  654. # mode =='complex' is same as `stft`, doesn't need modification
  655. return freqs, time, Sxx
  656. def check_COLA(window, nperseg, noverlap, tol=1e-10):
  657. r"""Check whether the Constant OverLap Add (COLA) constraint is met.
  658. Parameters
  659. ----------
  660. window : str or tuple or array_like
  661. Desired window to use. If `window` is a string or tuple, it is
  662. passed to `get_window` to generate the window values, which are
  663. DFT-even by default. See `get_window` for a list of windows and
  664. required parameters. If `window` is array_like it will be used
  665. directly as the window and its length must be nperseg.
  666. nperseg : int
  667. Length of each segment.
  668. noverlap : int
  669. Number of points to overlap between segments.
  670. tol : float, optional
  671. The allowed variance of a bin's weighted sum from the median bin
  672. sum.
  673. Returns
  674. -------
  675. verdict : bool
  676. `True` if chosen combination satisfies COLA within `tol`,
  677. `False` otherwise
  678. See Also
  679. --------
  680. check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met
  681. stft: Short Time Fourier Transform
  682. istft: Inverse Short Time Fourier Transform
  683. Notes
  684. -----
  685. In order to enable inversion of an STFT via the inverse STFT in
  686. `istft`, it is sufficient that the signal windowing obeys the constraint of
  687. "Constant OverLap Add" (COLA). This ensures that every point in the input
  688. data is equally weighted, thereby avoiding aliasing and allowing full
  689. reconstruction.
  690. Some examples of windows that satisfy COLA:
  691. - Rectangular window at overlap of 0, 1/2, 2/3, 3/4, ...
  692. - Bartlett window at overlap of 1/2, 3/4, 5/6, ...
  693. - Hann window at 1/2, 2/3, 3/4, ...
  694. - Any Blackman family window at 2/3 overlap
  695. - Any window with ``noverlap = nperseg-1``
  696. A very comprehensive list of other windows may be found in [2]_,
  697. wherein the COLA condition is satisfied when the "Amplitude
  698. Flatness" is unity.
  699. .. versionadded:: 0.19.0
  700. References
  701. ----------
  702. .. [1] Julius O. Smith III, "Spectral Audio Signal Processing", W3K
  703. Publishing, 2011,ISBN 978-0-9745607-3-1.
  704. .. [2] G. Heinzel, A. Ruediger and R. Schilling, "Spectrum and
  705. spectral density estimation by the Discrete Fourier transform
  706. (DFT), including a comprehensive list of window functions and
  707. some new at-top windows", 2002,
  708. http://hdl.handle.net/11858/00-001M-0000-0013-557A-5
  709. Examples
  710. --------
  711. >>> from scipy import signal
  712. Confirm COLA condition for rectangular window of 75% (3/4) overlap:
  713. >>> signal.check_COLA(signal.windows.boxcar(100), 100, 75)
  714. True
  715. COLA is not true for 25% (1/4) overlap, though:
  716. >>> signal.check_COLA(signal.windows.boxcar(100), 100, 25)
  717. False
  718. "Symmetrical" Hann window (for filter design) is not COLA:
  719. >>> signal.check_COLA(signal.windows.hann(120, sym=True), 120, 60)
  720. False
  721. "Periodic" or "DFT-even" Hann window (for FFT analysis) is COLA for
  722. overlap of 1/2, 2/3, 3/4, etc.:
  723. >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 60)
  724. True
  725. >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 80)
  726. True
  727. >>> signal.check_COLA(signal.windows.hann(120, sym=False), 120, 90)
  728. True
  729. """
  730. nperseg = int(nperseg)
  731. if nperseg < 1:
  732. raise ValueError('nperseg must be a positive integer')
  733. if noverlap >= nperseg:
  734. raise ValueError('noverlap must be less than nperseg.')
  735. noverlap = int(noverlap)
  736. if isinstance(window, str) or type(window) is tuple:
  737. win = get_window(window, nperseg)
  738. else:
  739. win = np.asarray(window)
  740. if len(win.shape) != 1:
  741. raise ValueError('window must be 1-D')
  742. if win.shape[0] != nperseg:
  743. raise ValueError('window must have length of nperseg')
  744. step = nperseg - noverlap
  745. binsums = sum(win[ii*step:(ii+1)*step] for ii in range(nperseg//step))
  746. if nperseg % step != 0:
  747. binsums[:nperseg % step] += win[-(nperseg % step):]
  748. deviation = binsums - np.median(binsums)
  749. return np.max(np.abs(deviation)) < tol
  750. def check_NOLA(window, nperseg, noverlap, tol=1e-10):
  751. r"""Check whether the Nonzero Overlap Add (NOLA) constraint is met.
  752. Parameters
  753. ----------
  754. window : str or tuple or array_like
  755. Desired window to use. If `window` is a string or tuple, it is
  756. passed to `get_window` to generate the window values, which are
  757. DFT-even by default. See `get_window` for a list of windows and
  758. required parameters. If `window` is array_like it will be used
  759. directly as the window and its length must be nperseg.
  760. nperseg : int
  761. Length of each segment.
  762. noverlap : int
  763. Number of points to overlap between segments.
  764. tol : float, optional
  765. The allowed variance of a bin's weighted sum from the median bin
  766. sum.
  767. Returns
  768. -------
  769. verdict : bool
  770. `True` if chosen combination satisfies the NOLA constraint within
  771. `tol`, `False` otherwise
  772. See Also
  773. --------
  774. check_COLA: Check whether the Constant OverLap Add (COLA) constraint is met
  775. stft: Short Time Fourier Transform
  776. istft: Inverse Short Time Fourier Transform
  777. Notes
  778. -----
  779. In order to enable inversion of an STFT via the inverse STFT in
  780. `istft`, the signal windowing must obey the constraint of "nonzero
  781. overlap add" (NOLA):
  782. .. math:: \sum_{t}w^{2}[n-tH] \ne 0
  783. for all :math:`n`, where :math:`w` is the window function, :math:`t` is the
  784. frame index, and :math:`H` is the hop size (:math:`H` = `nperseg` -
  785. `noverlap`).
  786. This ensures that the normalization factors in the denominator of the
  787. overlap-add inversion equation are not zero. Only very pathological windows
  788. will fail the NOLA constraint.
  789. .. versionadded:: 1.2.0
  790. References
  791. ----------
  792. .. [1] Julius O. Smith III, "Spectral Audio Signal Processing", W3K
  793. Publishing, 2011,ISBN 978-0-9745607-3-1.
  794. .. [2] G. Heinzel, A. Ruediger and R. Schilling, "Spectrum and
  795. spectral density estimation by the Discrete Fourier transform
  796. (DFT), including a comprehensive list of window functions and
  797. some new at-top windows", 2002,
  798. http://hdl.handle.net/11858/00-001M-0000-0013-557A-5
  799. Examples
  800. --------
  801. >>> import numpy as np
  802. >>> from scipy import signal
  803. Confirm NOLA condition for rectangular window of 75% (3/4) overlap:
  804. >>> signal.check_NOLA(signal.windows.boxcar(100), 100, 75)
  805. True
  806. NOLA is also true for 25% (1/4) overlap:
  807. >>> signal.check_NOLA(signal.windows.boxcar(100), 100, 25)
  808. True
  809. "Symmetrical" Hann window (for filter design) is also NOLA:
  810. >>> signal.check_NOLA(signal.windows.hann(120, sym=True), 120, 60)
  811. True
  812. As long as there is overlap, it takes quite a pathological window to fail
  813. NOLA:
  814. >>> w = np.ones(64, dtype="float")
  815. >>> w[::2] = 0
  816. >>> signal.check_NOLA(w, 64, 32)
  817. False
  818. If there is not enough overlap, a window with zeros at the ends will not
  819. work:
  820. >>> signal.check_NOLA(signal.windows.hann(64), 64, 0)
  821. False
  822. >>> signal.check_NOLA(signal.windows.hann(64), 64, 1)
  823. False
  824. >>> signal.check_NOLA(signal.windows.hann(64), 64, 2)
  825. True
  826. """
  827. nperseg = int(nperseg)
  828. if nperseg < 1:
  829. raise ValueError('nperseg must be a positive integer')
  830. if noverlap >= nperseg:
  831. raise ValueError('noverlap must be less than nperseg')
  832. if noverlap < 0:
  833. raise ValueError('noverlap must be a nonnegative integer')
  834. noverlap = int(noverlap)
  835. if isinstance(window, str) or type(window) is tuple:
  836. win = get_window(window, nperseg)
  837. else:
  838. win = np.asarray(window)
  839. if len(win.shape) != 1:
  840. raise ValueError('window must be 1-D')
  841. if win.shape[0] != nperseg:
  842. raise ValueError('window must have length of nperseg')
  843. step = nperseg - noverlap
  844. binsums = sum(win[ii*step:(ii+1)*step]**2 for ii in range(nperseg//step))
  845. if nperseg % step != 0:
  846. binsums[:nperseg % step] += win[-(nperseg % step):]**2
  847. return np.min(binsums) > tol
  848. def stft(x, fs=1.0, window='hann', nperseg=256, noverlap=None, nfft=None,
  849. detrend=False, return_onesided=True, boundary='zeros', padded=True,
  850. axis=-1, scaling='spectrum'):
  851. r"""Compute the Short Time Fourier Transform (STFT).
  852. STFTs can be used as a way of quantifying the change of a
  853. nonstationary signal's frequency and phase content over time.
  854. Parameters
  855. ----------
  856. x : array_like
  857. Time series of measurement values
  858. fs : float, optional
  859. Sampling frequency of the `x` time series. Defaults to 1.0.
  860. window : str or tuple or array_like, optional
  861. Desired window to use. If `window` is a string or tuple, it is
  862. passed to `get_window` to generate the window values, which are
  863. DFT-even by default. See `get_window` for a list of windows and
  864. required parameters. If `window` is array_like it will be used
  865. directly as the window and its length must be nperseg. Defaults
  866. to a Hann window.
  867. nperseg : int, optional
  868. Length of each segment. Defaults to 256.
  869. noverlap : int, optional
  870. Number of points to overlap between segments. If `None`,
  871. ``noverlap = nperseg // 2``. Defaults to `None`. When
  872. specified, the COLA constraint must be met (see Notes below).
  873. nfft : int, optional
  874. Length of the FFT used, if a zero padded FFT is desired. If
  875. `None`, the FFT length is `nperseg`. Defaults to `None`.
  876. detrend : str or function or `False`, optional
  877. Specifies how to detrend each segment. If `detrend` is a
  878. string, it is passed as the `type` argument to the `detrend`
  879. function. If it is a function, it takes a segment and returns a
  880. detrended segment. If `detrend` is `False`, no detrending is
  881. done. Defaults to `False`.
  882. return_onesided : bool, optional
  883. If `True`, return a one-sided spectrum for real data. If
  884. `False` return a two-sided spectrum. Defaults to `True`, but for
  885. complex data, a two-sided spectrum is always returned.
  886. boundary : str or None, optional
  887. Specifies whether the input signal is extended at both ends, and
  888. how to generate the new values, in order to center the first
  889. windowed segment on the first input point. This has the benefit
  890. of enabling reconstruction of the first input point when the
  891. employed window function starts at zero. Valid options are
  892. ``['even', 'odd', 'constant', 'zeros', None]``. Defaults to
  893. 'zeros', for zero padding extension. I.e. ``[1, 2, 3, 4]`` is
  894. extended to ``[0, 1, 2, 3, 4, 0]`` for ``nperseg=3``.
  895. padded : bool, optional
  896. Specifies whether the input signal is zero-padded at the end to
  897. make the signal fit exactly into an integer number of window
  898. segments, so that all of the signal is included in the output.
  899. Defaults to `True`. Padding occurs after boundary extension, if
  900. `boundary` is not `None`, and `padded` is `True`, as is the
  901. default.
  902. axis : int, optional
  903. Axis along which the STFT is computed; the default is over the
  904. last axis (i.e. ``axis=-1``).
  905. scaling: {'spectrum', 'psd'}
  906. The default 'spectrum' scaling allows each frequency line of `Zxx` to
  907. be interpreted as a magnitude spectrum. The 'psd' option scales each
  908. line to a power spectral density - it allows to calculate the signal's
  909. energy by numerically integrating over ``abs(Zxx)**2``.
  910. .. versionadded:: 1.9.0
  911. Returns
  912. -------
  913. f : ndarray
  914. Array of sample frequencies.
  915. t : ndarray
  916. Array of segment times.
  917. Zxx : ndarray
  918. STFT of `x`. By default, the last axis of `Zxx` corresponds
  919. to the segment times.
  920. See Also
  921. --------
  922. istft: Inverse Short Time Fourier Transform
  923. check_COLA: Check whether the Constant OverLap Add (COLA) constraint
  924. is met
  925. check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met
  926. welch: Power spectral density by Welch's method.
  927. spectrogram: Spectrogram by Welch's method.
  928. csd: Cross spectral density by Welch's method.
  929. lombscargle: Lomb-Scargle periodogram for unevenly sampled data
  930. Notes
  931. -----
  932. In order to enable inversion of an STFT via the inverse STFT in
  933. `istft`, the signal windowing must obey the constraint of "Nonzero
  934. OverLap Add" (NOLA), and the input signal must have complete
  935. windowing coverage (i.e. ``(x.shape[axis] - nperseg) %
  936. (nperseg-noverlap) == 0``). The `padded` argument may be used to
  937. accomplish this.
  938. Given a time-domain signal :math:`x[n]`, a window :math:`w[n]`, and a hop
  939. size :math:`H` = `nperseg - noverlap`, the windowed frame at time index
  940. :math:`t` is given by
  941. .. math:: x_{t}[n]=x[n]w[n-tH]
  942. The overlap-add (OLA) reconstruction equation is given by
  943. .. math:: x[n]=\frac{\sum_{t}x_{t}[n]w[n-tH]}{\sum_{t}w^{2}[n-tH]}
  944. The NOLA constraint ensures that every normalization term that appears
  945. in the denomimator of the OLA reconstruction equation is nonzero. Whether a
  946. choice of `window`, `nperseg`, and `noverlap` satisfy this constraint can
  947. be tested with `check_NOLA`.
  948. .. versionadded:: 0.19.0
  949. References
  950. ----------
  951. .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck
  952. "Discrete-Time Signal Processing", Prentice Hall, 1999.
  953. .. [2] Daniel W. Griffin, Jae S. Lim "Signal Estimation from
  954. Modified Short-Time Fourier Transform", IEEE 1984,
  955. 10.1109/TASSP.1984.1164317
  956. Examples
  957. --------
  958. >>> import numpy as np
  959. >>> from scipy import signal
  960. >>> import matplotlib.pyplot as plt
  961. >>> rng = np.random.default_rng()
  962. Generate a test signal, a 2 Vrms sine wave whose frequency is slowly
  963. modulated around 3kHz, corrupted by white noise of exponentially
  964. decreasing magnitude sampled at 10 kHz.
  965. >>> fs = 10e3
  966. >>> N = 1e5
  967. >>> amp = 2 * np.sqrt(2)
  968. >>> noise_power = 0.01 * fs / 2
  969. >>> time = np.arange(N) / float(fs)
  970. >>> mod = 500*np.cos(2*np.pi*0.25*time)
  971. >>> carrier = amp * np.sin(2*np.pi*3e3*time + mod)
  972. >>> noise = rng.normal(scale=np.sqrt(noise_power),
  973. ... size=time.shape)
  974. >>> noise *= np.exp(-time/5)
  975. >>> x = carrier + noise
  976. Compute and plot the STFT's magnitude.
  977. >>> f, t, Zxx = signal.stft(x, fs, nperseg=1000)
  978. >>> plt.pcolormesh(t, f, np.abs(Zxx), vmin=0, vmax=amp, shading='gouraud')
  979. >>> plt.title('STFT Magnitude')
  980. >>> plt.ylabel('Frequency [Hz]')
  981. >>> plt.xlabel('Time [sec]')
  982. >>> plt.show()
  983. Compare the energy of the signal `x` with the energy of its STFT:
  984. >>> E_x = sum(x**2) / fs # Energy of x
  985. >>> # Calculate a two-sided STFT with PSD scaling:
  986. >>> f, t, Zxx = signal.stft(x, fs, nperseg=1000, return_onesided=False,
  987. ... scaling='psd')
  988. >>> # Integrate numerically over abs(Zxx)**2:
  989. >>> df, dt = f[1] - f[0], t[1] - t[0]
  990. >>> E_Zxx = sum(np.sum(Zxx.real**2 + Zxx.imag**2, axis=0) * df) * dt
  991. >>> # The energy is the same, but the numerical errors are quite large:
  992. >>> np.isclose(E_x, E_Zxx, rtol=1e-2)
  993. True
  994. """
  995. if scaling == 'psd':
  996. scaling = 'density'
  997. elif scaling != 'spectrum':
  998. raise ValueError(f"Parameter {scaling=} not in ['spectrum', 'psd']!")
  999. freqs, time, Zxx = _spectral_helper(x, x, fs, window, nperseg, noverlap,
  1000. nfft, detrend, return_onesided,
  1001. scaling=scaling, axis=axis,
  1002. mode='stft', boundary=boundary,
  1003. padded=padded)
  1004. return freqs, time, Zxx
  1005. def istft(Zxx, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None,
  1006. input_onesided=True, boundary=True, time_axis=-1, freq_axis=-2,
  1007. scaling='spectrum'):
  1008. r"""Perform the inverse Short Time Fourier transform (iSTFT).
  1009. Parameters
  1010. ----------
  1011. Zxx : array_like
  1012. STFT of the signal to be reconstructed. If a purely real array
  1013. is passed, it will be cast to a complex data type.
  1014. fs : float, optional
  1015. Sampling frequency of the time series. Defaults to 1.0.
  1016. window : str or tuple or array_like, optional
  1017. Desired window to use. If `window` is a string or tuple, it is
  1018. passed to `get_window` to generate the window values, which are
  1019. DFT-even by default. See `get_window` for a list of windows and
  1020. required parameters. If `window` is array_like it will be used
  1021. directly as the window and its length must be nperseg. Defaults
  1022. to a Hann window. Must match the window used to generate the
  1023. STFT for faithful inversion.
  1024. nperseg : int, optional
  1025. Number of data points corresponding to each STFT segment. This
  1026. parameter must be specified if the number of data points per
  1027. segment is odd, or if the STFT was padded via ``nfft >
  1028. nperseg``. If `None`, the value depends on the shape of
  1029. `Zxx` and `input_onesided`. If `input_onesided` is `True`,
  1030. ``nperseg=2*(Zxx.shape[freq_axis] - 1)``. Otherwise,
  1031. ``nperseg=Zxx.shape[freq_axis]``. Defaults to `None`.
  1032. noverlap : int, optional
  1033. Number of points to overlap between segments. If `None`, half
  1034. of the segment length. Defaults to `None`. When specified, the
  1035. COLA constraint must be met (see Notes below), and should match
  1036. the parameter used to generate the STFT. Defaults to `None`.
  1037. nfft : int, optional
  1038. Number of FFT points corresponding to each STFT segment. This
  1039. parameter must be specified if the STFT was padded via ``nfft >
  1040. nperseg``. If `None`, the default values are the same as for
  1041. `nperseg`, detailed above, with one exception: if
  1042. `input_onesided` is True and
  1043. ``nperseg==2*Zxx.shape[freq_axis] - 1``, `nfft` also takes on
  1044. that value. This case allows the proper inversion of an
  1045. odd-length unpadded STFT using ``nfft=None``. Defaults to
  1046. `None`.
  1047. input_onesided : bool, optional
  1048. If `True`, interpret the input array as one-sided FFTs, such
  1049. as is returned by `stft` with ``return_onesided=True`` and
  1050. `numpy.fft.rfft`. If `False`, interpret the input as a a
  1051. two-sided FFT. Defaults to `True`.
  1052. boundary : bool, optional
  1053. Specifies whether the input signal was extended at its
  1054. boundaries by supplying a non-`None` ``boundary`` argument to
  1055. `stft`. Defaults to `True`.
  1056. time_axis : int, optional
  1057. Where the time segments of the STFT is located; the default is
  1058. the last axis (i.e. ``axis=-1``).
  1059. freq_axis : int, optional
  1060. Where the frequency axis of the STFT is located; the default is
  1061. the penultimate axis (i.e. ``axis=-2``).
  1062. scaling: {'spectrum', 'psd'}
  1063. The default 'spectrum' scaling allows each frequency line of `Zxx` to
  1064. be interpreted as a magnitude spectrum. The 'psd' option scales each
  1065. line to a power spectral density - it allows to calculate the signal's
  1066. energy by numerically integrating over ``abs(Zxx)**2``.
  1067. Returns
  1068. -------
  1069. t : ndarray
  1070. Array of output data times.
  1071. x : ndarray
  1072. iSTFT of `Zxx`.
  1073. See Also
  1074. --------
  1075. stft: Short Time Fourier Transform
  1076. check_COLA: Check whether the Constant OverLap Add (COLA) constraint
  1077. is met
  1078. check_NOLA: Check whether the Nonzero Overlap Add (NOLA) constraint is met
  1079. Notes
  1080. -----
  1081. In order to enable inversion of an STFT via the inverse STFT with
  1082. `istft`, the signal windowing must obey the constraint of "nonzero
  1083. overlap add" (NOLA):
  1084. .. math:: \sum_{t}w^{2}[n-tH] \ne 0
  1085. This ensures that the normalization factors that appear in the denominator
  1086. of the overlap-add reconstruction equation
  1087. .. math:: x[n]=\frac{\sum_{t}x_{t}[n]w[n-tH]}{\sum_{t}w^{2}[n-tH]}
  1088. are not zero. The NOLA constraint can be checked with the `check_NOLA`
  1089. function.
  1090. An STFT which has been modified (via masking or otherwise) is not
  1091. guaranteed to correspond to a exactly realizible signal. This
  1092. function implements the iSTFT via the least-squares estimation
  1093. algorithm detailed in [2]_, which produces a signal that minimizes
  1094. the mean squared error between the STFT of the returned signal and
  1095. the modified STFT.
  1096. .. versionadded:: 0.19.0
  1097. References
  1098. ----------
  1099. .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck
  1100. "Discrete-Time Signal Processing", Prentice Hall, 1999.
  1101. .. [2] Daniel W. Griffin, Jae S. Lim "Signal Estimation from
  1102. Modified Short-Time Fourier Transform", IEEE 1984,
  1103. 10.1109/TASSP.1984.1164317
  1104. Examples
  1105. --------
  1106. >>> import numpy as np
  1107. >>> from scipy import signal
  1108. >>> import matplotlib.pyplot as plt
  1109. >>> rng = np.random.default_rng()
  1110. Generate a test signal, a 2 Vrms sine wave at 50Hz corrupted by
  1111. 0.001 V**2/Hz of white noise sampled at 1024 Hz.
  1112. >>> fs = 1024
  1113. >>> N = 10*fs
  1114. >>> nperseg = 512
  1115. >>> amp = 2 * np.sqrt(2)
  1116. >>> noise_power = 0.001 * fs / 2
  1117. >>> time = np.arange(N) / float(fs)
  1118. >>> carrier = amp * np.sin(2*np.pi*50*time)
  1119. >>> noise = rng.normal(scale=np.sqrt(noise_power),
  1120. ... size=time.shape)
  1121. >>> x = carrier + noise
  1122. Compute the STFT, and plot its magnitude
  1123. >>> f, t, Zxx = signal.stft(x, fs=fs, nperseg=nperseg)
  1124. >>> plt.figure()
  1125. >>> plt.pcolormesh(t, f, np.abs(Zxx), vmin=0, vmax=amp, shading='gouraud')
  1126. >>> plt.ylim([f[1], f[-1]])
  1127. >>> plt.title('STFT Magnitude')
  1128. >>> plt.ylabel('Frequency [Hz]')
  1129. >>> plt.xlabel('Time [sec]')
  1130. >>> plt.yscale('log')
  1131. >>> plt.show()
  1132. Zero the components that are 10% or less of the carrier magnitude,
  1133. then convert back to a time series via inverse STFT
  1134. >>> Zxx = np.where(np.abs(Zxx) >= amp/10, Zxx, 0)
  1135. >>> _, xrec = signal.istft(Zxx, fs)
  1136. Compare the cleaned signal with the original and true carrier signals.
  1137. >>> plt.figure()
  1138. >>> plt.plot(time, x, time, xrec, time, carrier)
  1139. >>> plt.xlim([2, 2.1])
  1140. >>> plt.xlabel('Time [sec]')
  1141. >>> plt.ylabel('Signal')
  1142. >>> plt.legend(['Carrier + Noise', 'Filtered via STFT', 'True Carrier'])
  1143. >>> plt.show()
  1144. Note that the cleaned signal does not start as abruptly as the original,
  1145. since some of the coefficients of the transient were also removed:
  1146. >>> plt.figure()
  1147. >>> plt.plot(time, x, time, xrec, time, carrier)
  1148. >>> plt.xlim([0, 0.1])
  1149. >>> plt.xlabel('Time [sec]')
  1150. >>> plt.ylabel('Signal')
  1151. >>> plt.legend(['Carrier + Noise', 'Filtered via STFT', 'True Carrier'])
  1152. >>> plt.show()
  1153. """
  1154. # Make sure input is an ndarray of appropriate complex dtype
  1155. Zxx = np.asarray(Zxx) + 0j
  1156. freq_axis = int(freq_axis)
  1157. time_axis = int(time_axis)
  1158. if Zxx.ndim < 2:
  1159. raise ValueError('Input stft must be at least 2d!')
  1160. if freq_axis == time_axis:
  1161. raise ValueError('Must specify differing time and frequency axes!')
  1162. nseg = Zxx.shape[time_axis]
  1163. if input_onesided:
  1164. # Assume even segment length
  1165. n_default = 2*(Zxx.shape[freq_axis] - 1)
  1166. else:
  1167. n_default = Zxx.shape[freq_axis]
  1168. # Check windowing parameters
  1169. if nperseg is None:
  1170. nperseg = n_default
  1171. else:
  1172. nperseg = int(nperseg)
  1173. if nperseg < 1:
  1174. raise ValueError('nperseg must be a positive integer')
  1175. if nfft is None:
  1176. if (input_onesided) and (nperseg == n_default + 1):
  1177. # Odd nperseg, no FFT padding
  1178. nfft = nperseg
  1179. else:
  1180. nfft = n_default
  1181. elif nfft < nperseg:
  1182. raise ValueError('nfft must be greater than or equal to nperseg.')
  1183. else:
  1184. nfft = int(nfft)
  1185. if noverlap is None:
  1186. noverlap = nperseg//2
  1187. else:
  1188. noverlap = int(noverlap)
  1189. if noverlap >= nperseg:
  1190. raise ValueError('noverlap must be less than nperseg.')
  1191. nstep = nperseg - noverlap
  1192. # Rearrange axes if necessary
  1193. if time_axis != Zxx.ndim-1 or freq_axis != Zxx.ndim-2:
  1194. # Turn negative indices to positive for the call to transpose
  1195. if freq_axis < 0:
  1196. freq_axis = Zxx.ndim + freq_axis
  1197. if time_axis < 0:
  1198. time_axis = Zxx.ndim + time_axis
  1199. zouter = list(range(Zxx.ndim))
  1200. for ax in sorted([time_axis, freq_axis], reverse=True):
  1201. zouter.pop(ax)
  1202. Zxx = np.transpose(Zxx, zouter+[freq_axis, time_axis])
  1203. # Get window as array
  1204. if isinstance(window, str) or type(window) is tuple:
  1205. win = get_window(window, nperseg)
  1206. else:
  1207. win = np.asarray(window)
  1208. if len(win.shape) != 1:
  1209. raise ValueError('window must be 1-D')
  1210. if win.shape[0] != nperseg:
  1211. raise ValueError('window must have length of {0}'.format(nperseg))
  1212. ifunc = sp_fft.irfft if input_onesided else sp_fft.ifft
  1213. xsubs = ifunc(Zxx, axis=-2, n=nfft)[..., :nperseg, :]
  1214. # Initialize output and normalization arrays
  1215. outputlength = nperseg + (nseg-1)*nstep
  1216. x = np.zeros(list(Zxx.shape[:-2])+[outputlength], dtype=xsubs.dtype)
  1217. norm = np.zeros(outputlength, dtype=xsubs.dtype)
  1218. if np.result_type(win, xsubs) != xsubs.dtype:
  1219. win = win.astype(xsubs.dtype)
  1220. if scaling == 'spectrum':
  1221. xsubs *= win.sum()
  1222. elif scaling == 'psd':
  1223. xsubs *= np.sqrt(fs * sum(win**2))
  1224. else:
  1225. raise ValueError(f"Parameter {scaling=} not in ['spectrum', 'psd']!")
  1226. # Construct the output from the ifft segments
  1227. # This loop could perhaps be vectorized/strided somehow...
  1228. for ii in range(nseg):
  1229. # Window the ifft
  1230. x[..., ii*nstep:ii*nstep+nperseg] += xsubs[..., ii] * win
  1231. norm[..., ii*nstep:ii*nstep+nperseg] += win**2
  1232. # Remove extension points
  1233. if boundary:
  1234. x = x[..., nperseg//2:-(nperseg//2)]
  1235. norm = norm[..., nperseg//2:-(nperseg//2)]
  1236. # Divide out normalization where non-tiny
  1237. if np.sum(norm > 1e-10) != len(norm):
  1238. warnings.warn("NOLA condition failed, STFT may not be invertible")
  1239. x /= np.where(norm > 1e-10, norm, 1.0)
  1240. if input_onesided:
  1241. x = x.real
  1242. # Put axes back
  1243. if x.ndim > 1:
  1244. if time_axis != Zxx.ndim-1:
  1245. if freq_axis < time_axis:
  1246. time_axis -= 1
  1247. x = np.moveaxis(x, -1, time_axis)
  1248. time = np.arange(x.shape[0])/float(fs)
  1249. return time, x
  1250. def coherence(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None,
  1251. nfft=None, detrend='constant', axis=-1):
  1252. r"""
  1253. Estimate the magnitude squared coherence estimate, Cxy, of
  1254. discrete-time signals X and Y using Welch's method.
  1255. ``Cxy = abs(Pxy)**2/(Pxx*Pyy)``, where `Pxx` and `Pyy` are power
  1256. spectral density estimates of X and Y, and `Pxy` is the cross
  1257. spectral density estimate of X and Y.
  1258. Parameters
  1259. ----------
  1260. x : array_like
  1261. Time series of measurement values
  1262. y : array_like
  1263. Time series of measurement values
  1264. fs : float, optional
  1265. Sampling frequency of the `x` and `y` time series. Defaults
  1266. to 1.0.
  1267. window : str or tuple or array_like, optional
  1268. Desired window to use. If `window` is a string or tuple, it is
  1269. passed to `get_window` to generate the window values, which are
  1270. DFT-even by default. See `get_window` for a list of windows and
  1271. required parameters. If `window` is array_like it will be used
  1272. directly as the window and its length must be nperseg. Defaults
  1273. to a Hann window.
  1274. nperseg : int, optional
  1275. Length of each segment. Defaults to None, but if window is str or
  1276. tuple, is set to 256, and if window is array_like, is set to the
  1277. length of the window.
  1278. noverlap: int, optional
  1279. Number of points to overlap between segments. If `None`,
  1280. ``noverlap = nperseg // 2``. Defaults to `None`.
  1281. nfft : int, optional
  1282. Length of the FFT used, if a zero padded FFT is desired. If
  1283. `None`, the FFT length is `nperseg`. Defaults to `None`.
  1284. detrend : str or function or `False`, optional
  1285. Specifies how to detrend each segment. If `detrend` is a
  1286. string, it is passed as the `type` argument to the `detrend`
  1287. function. If it is a function, it takes a segment and returns a
  1288. detrended segment. If `detrend` is `False`, no detrending is
  1289. done. Defaults to 'constant'.
  1290. axis : int, optional
  1291. Axis along which the coherence is computed for both inputs; the
  1292. default is over the last axis (i.e. ``axis=-1``).
  1293. Returns
  1294. -------
  1295. f : ndarray
  1296. Array of sample frequencies.
  1297. Cxy : ndarray
  1298. Magnitude squared coherence of x and y.
  1299. See Also
  1300. --------
  1301. periodogram: Simple, optionally modified periodogram
  1302. lombscargle: Lomb-Scargle periodogram for unevenly sampled data
  1303. welch: Power spectral density by Welch's method.
  1304. csd: Cross spectral density by Welch's method.
  1305. Notes
  1306. -----
  1307. An appropriate amount of overlap will depend on the choice of window
  1308. and on your requirements. For the default Hann window an overlap of
  1309. 50% is a reasonable trade off between accurately estimating the
  1310. signal power, while not over counting any of the data. Narrower
  1311. windows may require a larger overlap.
  1312. .. versionadded:: 0.16.0
  1313. References
  1314. ----------
  1315. .. [1] P. Welch, "The use of the fast Fourier transform for the
  1316. estimation of power spectra: A method based on time averaging
  1317. over short, modified periodograms", IEEE Trans. Audio
  1318. Electroacoust. vol. 15, pp. 70-73, 1967.
  1319. .. [2] Stoica, Petre, and Randolph Moses, "Spectral Analysis of
  1320. Signals" Prentice Hall, 2005
  1321. Examples
  1322. --------
  1323. >>> import numpy as np
  1324. >>> from scipy import signal
  1325. >>> import matplotlib.pyplot as plt
  1326. >>> rng = np.random.default_rng()
  1327. Generate two test signals with some common features.
  1328. >>> fs = 10e3
  1329. >>> N = 1e5
  1330. >>> amp = 20
  1331. >>> freq = 1234.0
  1332. >>> noise_power = 0.001 * fs / 2
  1333. >>> time = np.arange(N) / fs
  1334. >>> b, a = signal.butter(2, 0.25, 'low')
  1335. >>> x = rng.normal(scale=np.sqrt(noise_power), size=time.shape)
  1336. >>> y = signal.lfilter(b, a, x)
  1337. >>> x += amp*np.sin(2*np.pi*freq*time)
  1338. >>> y += rng.normal(scale=0.1*np.sqrt(noise_power), size=time.shape)
  1339. Compute and plot the coherence.
  1340. >>> f, Cxy = signal.coherence(x, y, fs, nperseg=1024)
  1341. >>> plt.semilogy(f, Cxy)
  1342. >>> plt.xlabel('frequency [Hz]')
  1343. >>> plt.ylabel('Coherence')
  1344. >>> plt.show()
  1345. """
  1346. freqs, Pxx = welch(x, fs=fs, window=window, nperseg=nperseg,
  1347. noverlap=noverlap, nfft=nfft, detrend=detrend,
  1348. axis=axis)
  1349. _, Pyy = welch(y, fs=fs, window=window, nperseg=nperseg, noverlap=noverlap,
  1350. nfft=nfft, detrend=detrend, axis=axis)
  1351. _, Pxy = csd(x, y, fs=fs, window=window, nperseg=nperseg,
  1352. noverlap=noverlap, nfft=nfft, detrend=detrend, axis=axis)
  1353. Cxy = np.abs(Pxy)**2 / Pxx / Pyy
  1354. return freqs, Cxy
  1355. def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None,
  1356. nfft=None, detrend='constant', return_onesided=True,
  1357. scaling='density', axis=-1, mode='psd', boundary=None,
  1358. padded=False):
  1359. """Calculate various forms of windowed FFTs for PSD, CSD, etc.
  1360. This is a helper function that implements the commonality between
  1361. the stft, psd, csd, and spectrogram functions. It is not designed to
  1362. be called externally. The windows are not averaged over; the result
  1363. from each window is returned.
  1364. Parameters
  1365. ----------
  1366. x : array_like
  1367. Array or sequence containing the data to be analyzed.
  1368. y : array_like
  1369. Array or sequence containing the data to be analyzed. If this is
  1370. the same object in memory as `x` (i.e. ``_spectral_helper(x,
  1371. x, ...)``), the extra computations are spared.
  1372. fs : float, optional
  1373. Sampling frequency of the time series. Defaults to 1.0.
  1374. window : str or tuple or array_like, optional
  1375. Desired window to use. If `window` is a string or tuple, it is
  1376. passed to `get_window` to generate the window values, which are
  1377. DFT-even by default. See `get_window` for a list of windows and
  1378. required parameters. If `window` is array_like it will be used
  1379. directly as the window and its length must be nperseg. Defaults
  1380. to a Hann window.
  1381. nperseg : int, optional
  1382. Length of each segment. Defaults to None, but if window is str or
  1383. tuple, is set to 256, and if window is array_like, is set to the
  1384. length of the window.
  1385. noverlap : int, optional
  1386. Number of points to overlap between segments. If `None`,
  1387. ``noverlap = nperseg // 2``. Defaults to `None`.
  1388. nfft : int, optional
  1389. Length of the FFT used, if a zero padded FFT is desired. If
  1390. `None`, the FFT length is `nperseg`. Defaults to `None`.
  1391. detrend : str or function or `False`, optional
  1392. Specifies how to detrend each segment. If `detrend` is a
  1393. string, it is passed as the `type` argument to the `detrend`
  1394. function. If it is a function, it takes a segment and returns a
  1395. detrended segment. If `detrend` is `False`, no detrending is
  1396. done. Defaults to 'constant'.
  1397. return_onesided : bool, optional
  1398. If `True`, return a one-sided spectrum for real data. If
  1399. `False` return a two-sided spectrum. Defaults to `True`, but for
  1400. complex data, a two-sided spectrum is always returned.
  1401. scaling : { 'density', 'spectrum' }, optional
  1402. Selects between computing the cross spectral density ('density')
  1403. where `Pxy` has units of V**2/Hz and computing the cross
  1404. spectrum ('spectrum') where `Pxy` has units of V**2, if `x`
  1405. and `y` are measured in V and `fs` is measured in Hz.
  1406. Defaults to 'density'
  1407. axis : int, optional
  1408. Axis along which the FFTs are computed; the default is over the
  1409. last axis (i.e. ``axis=-1``).
  1410. mode: str {'psd', 'stft'}, optional
  1411. Defines what kind of return values are expected. Defaults to
  1412. 'psd'.
  1413. boundary : str or None, optional
  1414. Specifies whether the input signal is extended at both ends, and
  1415. how to generate the new values, in order to center the first
  1416. windowed segment on the first input point. This has the benefit
  1417. of enabling reconstruction of the first input point when the
  1418. employed window function starts at zero. Valid options are
  1419. ``['even', 'odd', 'constant', 'zeros', None]``. Defaults to
  1420. `None`.
  1421. padded : bool, optional
  1422. Specifies whether the input signal is zero-padded at the end to
  1423. make the signal fit exactly into an integer number of window
  1424. segments, so that all of the signal is included in the output.
  1425. Defaults to `False`. Padding occurs after boundary extension, if
  1426. `boundary` is not `None`, and `padded` is `True`.
  1427. Returns
  1428. -------
  1429. freqs : ndarray
  1430. Array of sample frequencies.
  1431. t : ndarray
  1432. Array of times corresponding to each data segment
  1433. result : ndarray
  1434. Array of output data, contents dependent on *mode* kwarg.
  1435. Notes
  1436. -----
  1437. Adapted from matplotlib.mlab
  1438. .. versionadded:: 0.16.0
  1439. """
  1440. if mode not in ['psd', 'stft']:
  1441. raise ValueError("Unknown value for mode %s, must be one of: "
  1442. "{'psd', 'stft'}" % mode)
  1443. boundary_funcs = {'even': even_ext,
  1444. 'odd': odd_ext,
  1445. 'constant': const_ext,
  1446. 'zeros': zero_ext,
  1447. None: None}
  1448. if boundary not in boundary_funcs:
  1449. raise ValueError("Unknown boundary option '{0}', must be one of: {1}"
  1450. .format(boundary, list(boundary_funcs.keys())))
  1451. # If x and y are the same object we can save ourselves some computation.
  1452. same_data = y is x
  1453. if not same_data and mode != 'psd':
  1454. raise ValueError("x and y must be equal if mode is 'stft'")
  1455. axis = int(axis)
  1456. # Ensure we have np.arrays, get outdtype
  1457. x = np.asarray(x)
  1458. if not same_data:
  1459. y = np.asarray(y)
  1460. outdtype = np.result_type(x, y, np.complex64)
  1461. else:
  1462. outdtype = np.result_type(x, np.complex64)
  1463. if not same_data:
  1464. # Check if we can broadcast the outer axes together
  1465. xouter = list(x.shape)
  1466. youter = list(y.shape)
  1467. xouter.pop(axis)
  1468. youter.pop(axis)
  1469. try:
  1470. outershape = np.broadcast(np.empty(xouter), np.empty(youter)).shape
  1471. except ValueError as e:
  1472. raise ValueError('x and y cannot be broadcast together.') from e
  1473. if same_data:
  1474. if x.size == 0:
  1475. return np.empty(x.shape), np.empty(x.shape), np.empty(x.shape)
  1476. else:
  1477. if x.size == 0 or y.size == 0:
  1478. outshape = outershape + (min([x.shape[axis], y.shape[axis]]),)
  1479. emptyout = np.moveaxis(np.empty(outshape), -1, axis)
  1480. return emptyout, emptyout, emptyout
  1481. if x.ndim > 1:
  1482. if axis != -1:
  1483. x = np.moveaxis(x, axis, -1)
  1484. if not same_data and y.ndim > 1:
  1485. y = np.moveaxis(y, axis, -1)
  1486. # Check if x and y are the same length, zero-pad if necessary
  1487. if not same_data:
  1488. if x.shape[-1] != y.shape[-1]:
  1489. if x.shape[-1] < y.shape[-1]:
  1490. pad_shape = list(x.shape)
  1491. pad_shape[-1] = y.shape[-1] - x.shape[-1]
  1492. x = np.concatenate((x, np.zeros(pad_shape)), -1)
  1493. else:
  1494. pad_shape = list(y.shape)
  1495. pad_shape[-1] = x.shape[-1] - y.shape[-1]
  1496. y = np.concatenate((y, np.zeros(pad_shape)), -1)
  1497. if nperseg is not None: # if specified by user
  1498. nperseg = int(nperseg)
  1499. if nperseg < 1:
  1500. raise ValueError('nperseg must be a positive integer')
  1501. # parse window; if array like, then set nperseg = win.shape
  1502. win, nperseg = _triage_segments(window, nperseg, input_length=x.shape[-1])
  1503. if nfft is None:
  1504. nfft = nperseg
  1505. elif nfft < nperseg:
  1506. raise ValueError('nfft must be greater than or equal to nperseg.')
  1507. else:
  1508. nfft = int(nfft)
  1509. if noverlap is None:
  1510. noverlap = nperseg//2
  1511. else:
  1512. noverlap = int(noverlap)
  1513. if noverlap >= nperseg:
  1514. raise ValueError('noverlap must be less than nperseg.')
  1515. nstep = nperseg - noverlap
  1516. # Padding occurs after boundary extension, so that the extended signal ends
  1517. # in zeros, instead of introducing an impulse at the end.
  1518. # I.e. if x = [..., 3, 2]
  1519. # extend then pad -> [..., 3, 2, 2, 3, 0, 0, 0]
  1520. # pad then extend -> [..., 3, 2, 0, 0, 0, 2, 3]
  1521. if boundary is not None:
  1522. ext_func = boundary_funcs[boundary]
  1523. x = ext_func(x, nperseg//2, axis=-1)
  1524. if not same_data:
  1525. y = ext_func(y, nperseg//2, axis=-1)
  1526. if padded:
  1527. # Pad to integer number of windowed segments
  1528. # I.e make x.shape[-1] = nperseg + (nseg-1)*nstep, with integer nseg
  1529. nadd = (-(x.shape[-1]-nperseg) % nstep) % nperseg
  1530. zeros_shape = list(x.shape[:-1]) + [nadd]
  1531. x = np.concatenate((x, np.zeros(zeros_shape)), axis=-1)
  1532. if not same_data:
  1533. zeros_shape = list(y.shape[:-1]) + [nadd]
  1534. y = np.concatenate((y, np.zeros(zeros_shape)), axis=-1)
  1535. # Handle detrending and window functions
  1536. if not detrend:
  1537. def detrend_func(d):
  1538. return d
  1539. elif not hasattr(detrend, '__call__'):
  1540. def detrend_func(d):
  1541. return _signaltools.detrend(d, type=detrend, axis=-1)
  1542. elif axis != -1:
  1543. # Wrap this function so that it receives a shape that it could
  1544. # reasonably expect to receive.
  1545. def detrend_func(d):
  1546. d = np.moveaxis(d, -1, axis)
  1547. d = detrend(d)
  1548. return np.moveaxis(d, axis, -1)
  1549. else:
  1550. detrend_func = detrend
  1551. if np.result_type(win, np.complex64) != outdtype:
  1552. win = win.astype(outdtype)
  1553. if scaling == 'density':
  1554. scale = 1.0 / (fs * (win*win).sum())
  1555. elif scaling == 'spectrum':
  1556. scale = 1.0 / win.sum()**2
  1557. else:
  1558. raise ValueError('Unknown scaling: %r' % scaling)
  1559. if mode == 'stft':
  1560. scale = np.sqrt(scale)
  1561. if return_onesided:
  1562. if np.iscomplexobj(x):
  1563. sides = 'twosided'
  1564. warnings.warn('Input data is complex, switching to '
  1565. 'return_onesided=False')
  1566. else:
  1567. sides = 'onesided'
  1568. if not same_data:
  1569. if np.iscomplexobj(y):
  1570. sides = 'twosided'
  1571. warnings.warn('Input data is complex, switching to '
  1572. 'return_onesided=False')
  1573. else:
  1574. sides = 'twosided'
  1575. if sides == 'twosided':
  1576. freqs = sp_fft.fftfreq(nfft, 1/fs)
  1577. elif sides == 'onesided':
  1578. freqs = sp_fft.rfftfreq(nfft, 1/fs)
  1579. # Perform the windowed FFTs
  1580. result = _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides)
  1581. if not same_data:
  1582. # All the same operations on the y data
  1583. result_y = _fft_helper(y, win, detrend_func, nperseg, noverlap, nfft,
  1584. sides)
  1585. result = np.conjugate(result) * result_y
  1586. elif mode == 'psd':
  1587. result = np.conjugate(result) * result
  1588. result *= scale
  1589. if sides == 'onesided' and mode == 'psd':
  1590. if nfft % 2:
  1591. result[..., 1:] *= 2
  1592. else:
  1593. # Last point is unpaired Nyquist freq point, don't double
  1594. result[..., 1:-1] *= 2
  1595. time = np.arange(nperseg/2, x.shape[-1] - nperseg/2 + 1,
  1596. nperseg - noverlap)/float(fs)
  1597. if boundary is not None:
  1598. time -= (nperseg/2) / fs
  1599. result = result.astype(outdtype)
  1600. # All imaginary parts are zero anyways
  1601. if same_data and mode != 'stft':
  1602. result = result.real
  1603. # Output is going to have new last axis for time/window index, so a
  1604. # negative axis index shifts down one
  1605. if axis < 0:
  1606. axis -= 1
  1607. # Roll frequency axis back to axis where the data came from
  1608. result = np.moveaxis(result, -1, axis)
  1609. return freqs, time, result
  1610. def _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides):
  1611. """
  1612. Calculate windowed FFT, for internal use by
  1613. `scipy.signal._spectral_helper`.
  1614. This is a helper function that does the main FFT calculation for
  1615. `_spectral helper`. All input validation is performed there, and the
  1616. data axis is assumed to be the last axis of x. It is not designed to
  1617. be called externally. The windows are not averaged over; the result
  1618. from each window is returned.
  1619. Returns
  1620. -------
  1621. result : ndarray
  1622. Array of FFT data
  1623. Notes
  1624. -----
  1625. Adapted from matplotlib.mlab
  1626. .. versionadded:: 0.16.0
  1627. """
  1628. # Created strided array of data segments
  1629. if nperseg == 1 and noverlap == 0:
  1630. result = x[..., np.newaxis]
  1631. else:
  1632. # https://stackoverflow.com/a/5568169
  1633. step = nperseg - noverlap
  1634. shape = x.shape[:-1]+((x.shape[-1]-noverlap)//step, nperseg)
  1635. strides = x.strides[:-1]+(step*x.strides[-1], x.strides[-1])
  1636. result = np.lib.stride_tricks.as_strided(x, shape=shape,
  1637. strides=strides)
  1638. # Detrend each data segment individually
  1639. result = detrend_func(result)
  1640. # Apply window by multiplication
  1641. result = win * result
  1642. # Perform the fft. Acts on last axis by default. Zero-pads automatically
  1643. if sides == 'twosided':
  1644. func = sp_fft.fft
  1645. else:
  1646. result = result.real
  1647. func = sp_fft.rfft
  1648. result = func(result, n=nfft)
  1649. return result
  1650. def _triage_segments(window, nperseg, input_length):
  1651. """
  1652. Parses window and nperseg arguments for spectrogram and _spectral_helper.
  1653. This is a helper function, not meant to be called externally.
  1654. Parameters
  1655. ----------
  1656. window : string, tuple, or ndarray
  1657. If window is specified by a string or tuple and nperseg is not
  1658. specified, nperseg is set to the default of 256 and returns a window of
  1659. that length.
  1660. If instead the window is array_like and nperseg is not specified, then
  1661. nperseg is set to the length of the window. A ValueError is raised if
  1662. the user supplies both an array_like window and a value for nperseg but
  1663. nperseg does not equal the length of the window.
  1664. nperseg : int
  1665. Length of each segment
  1666. input_length: int
  1667. Length of input signal, i.e. x.shape[-1]. Used to test for errors.
  1668. Returns
  1669. -------
  1670. win : ndarray
  1671. window. If function was called with string or tuple than this will hold
  1672. the actual array used as a window.
  1673. nperseg : int
  1674. Length of each segment. If window is str or tuple, nperseg is set to
  1675. 256. If window is array_like, nperseg is set to the length of the
  1676. window.
  1677. """
  1678. # parse window; if array like, then set nperseg = win.shape
  1679. if isinstance(window, str) or isinstance(window, tuple):
  1680. # if nperseg not specified
  1681. if nperseg is None:
  1682. nperseg = 256 # then change to default
  1683. if nperseg > input_length:
  1684. warnings.warn('nperseg = {0:d} is greater than input length '
  1685. ' = {1:d}, using nperseg = {1:d}'
  1686. .format(nperseg, input_length))
  1687. nperseg = input_length
  1688. win = get_window(window, nperseg)
  1689. else:
  1690. win = np.asarray(window)
  1691. if len(win.shape) != 1:
  1692. raise ValueError('window must be 1-D')
  1693. if input_length < win.shape[-1]:
  1694. raise ValueError('window is longer than input signal')
  1695. if nperseg is None:
  1696. nperseg = win.shape[0]
  1697. elif nperseg is not None:
  1698. if nperseg != win.shape[0]:
  1699. raise ValueError("value specified for nperseg is different"
  1700. " from length of window")
  1701. return win, nperseg
  1702. def _median_bias(n):
  1703. """
  1704. Returns the bias of the median of a set of periodograms relative to
  1705. the mean.
  1706. See Appendix B from [1]_ for details.
  1707. Parameters
  1708. ----------
  1709. n : int
  1710. Numbers of periodograms being averaged.
  1711. Returns
  1712. -------
  1713. bias : float
  1714. Calculated bias.
  1715. References
  1716. ----------
  1717. .. [1] B. Allen, W.G. Anderson, P.R. Brady, D.A. Brown, J.D.E. Creighton.
  1718. "FINDCHIRP: an algorithm for detection of gravitational waves from
  1719. inspiraling compact binaries", Physical Review D 85, 2012,
  1720. :arxiv:`gr-qc/0509116`
  1721. """
  1722. ii_2 = 2 * np.arange(1., (n-1) // 2 + 1)
  1723. return 1 + np.sum(1. / (ii_2 + 1) - 1. / ii_2)