_waveforms.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. # Author: Travis Oliphant
  2. # 2003
  3. #
  4. # Feb. 2010: Updated by Warren Weckesser:
  5. # Rewrote much of chirp()
  6. # Added sweep_poly()
  7. import numpy as np
  8. from numpy import asarray, zeros, place, nan, mod, pi, extract, log, sqrt, \
  9. exp, cos, sin, polyval, polyint
  10. __all__ = ['sawtooth', 'square', 'gausspulse', 'chirp', 'sweep_poly',
  11. 'unit_impulse']
  12. def sawtooth(t, width=1):
  13. """
  14. Return a periodic sawtooth or triangle waveform.
  15. The sawtooth waveform has a period ``2*pi``, rises from -1 to 1 on the
  16. interval 0 to ``width*2*pi``, then drops from 1 to -1 on the interval
  17. ``width*2*pi`` to ``2*pi``. `width` must be in the interval [0, 1].
  18. Note that this is not band-limited. It produces an infinite number
  19. of harmonics, which are aliased back and forth across the frequency
  20. spectrum.
  21. Parameters
  22. ----------
  23. t : array_like
  24. Time.
  25. width : array_like, optional
  26. Width of the rising ramp as a proportion of the total cycle.
  27. Default is 1, producing a rising ramp, while 0 produces a falling
  28. ramp. `width` = 0.5 produces a triangle wave.
  29. If an array, causes wave shape to change over time, and must be the
  30. same length as t.
  31. Returns
  32. -------
  33. y : ndarray
  34. Output array containing the sawtooth waveform.
  35. Examples
  36. --------
  37. A 5 Hz waveform sampled at 500 Hz for 1 second:
  38. >>> import numpy as np
  39. >>> from scipy import signal
  40. >>> import matplotlib.pyplot as plt
  41. >>> t = np.linspace(0, 1, 500)
  42. >>> plt.plot(t, signal.sawtooth(2 * np.pi * 5 * t))
  43. """
  44. t, w = asarray(t), asarray(width)
  45. w = asarray(w + (t - t))
  46. t = asarray(t + (w - w))
  47. if t.dtype.char in ['fFdD']:
  48. ytype = t.dtype.char
  49. else:
  50. ytype = 'd'
  51. y = zeros(t.shape, ytype)
  52. # width must be between 0 and 1 inclusive
  53. mask1 = (w > 1) | (w < 0)
  54. place(y, mask1, nan)
  55. # take t modulo 2*pi
  56. tmod = mod(t, 2 * pi)
  57. # on the interval 0 to width*2*pi function is
  58. # tmod / (pi*w) - 1
  59. mask2 = (1 - mask1) & (tmod < w * 2 * pi)
  60. tsub = extract(mask2, tmod)
  61. wsub = extract(mask2, w)
  62. place(y, mask2, tsub / (pi * wsub) - 1)
  63. # on the interval width*2*pi to 2*pi function is
  64. # (pi*(w+1)-tmod) / (pi*(1-w))
  65. mask3 = (1 - mask1) & (1 - mask2)
  66. tsub = extract(mask3, tmod)
  67. wsub = extract(mask3, w)
  68. place(y, mask3, (pi * (wsub + 1) - tsub) / (pi * (1 - wsub)))
  69. return y
  70. def square(t, duty=0.5):
  71. """
  72. Return a periodic square-wave waveform.
  73. The square wave has a period ``2*pi``, has value +1 from 0 to
  74. ``2*pi*duty`` and -1 from ``2*pi*duty`` to ``2*pi``. `duty` must be in
  75. the interval [0,1].
  76. Note that this is not band-limited. It produces an infinite number
  77. of harmonics, which are aliased back and forth across the frequency
  78. spectrum.
  79. Parameters
  80. ----------
  81. t : array_like
  82. The input time array.
  83. duty : array_like, optional
  84. Duty cycle. Default is 0.5 (50% duty cycle).
  85. If an array, causes wave shape to change over time, and must be the
  86. same length as t.
  87. Returns
  88. -------
  89. y : ndarray
  90. Output array containing the square waveform.
  91. Examples
  92. --------
  93. A 5 Hz waveform sampled at 500 Hz for 1 second:
  94. >>> import numpy as np
  95. >>> from scipy import signal
  96. >>> import matplotlib.pyplot as plt
  97. >>> t = np.linspace(0, 1, 500, endpoint=False)
  98. >>> plt.plot(t, signal.square(2 * np.pi * 5 * t))
  99. >>> plt.ylim(-2, 2)
  100. A pulse-width modulated sine wave:
  101. >>> plt.figure()
  102. >>> sig = np.sin(2 * np.pi * t)
  103. >>> pwm = signal.square(2 * np.pi * 30 * t, duty=(sig + 1)/2)
  104. >>> plt.subplot(2, 1, 1)
  105. >>> plt.plot(t, sig)
  106. >>> plt.subplot(2, 1, 2)
  107. >>> plt.plot(t, pwm)
  108. >>> plt.ylim(-1.5, 1.5)
  109. """
  110. t, w = asarray(t), asarray(duty)
  111. w = asarray(w + (t - t))
  112. t = asarray(t + (w - w))
  113. if t.dtype.char in ['fFdD']:
  114. ytype = t.dtype.char
  115. else:
  116. ytype = 'd'
  117. y = zeros(t.shape, ytype)
  118. # width must be between 0 and 1 inclusive
  119. mask1 = (w > 1) | (w < 0)
  120. place(y, mask1, nan)
  121. # on the interval 0 to duty*2*pi function is 1
  122. tmod = mod(t, 2 * pi)
  123. mask2 = (1 - mask1) & (tmod < w * 2 * pi)
  124. place(y, mask2, 1)
  125. # on the interval duty*2*pi to 2*pi function is
  126. # (pi*(w+1)-tmod) / (pi*(1-w))
  127. mask3 = (1 - mask1) & (1 - mask2)
  128. place(y, mask3, -1)
  129. return y
  130. def gausspulse(t, fc=1000, bw=0.5, bwr=-6, tpr=-60, retquad=False,
  131. retenv=False):
  132. """
  133. Return a Gaussian modulated sinusoid:
  134. ``exp(-a t^2) exp(1j*2*pi*fc*t).``
  135. If `retquad` is True, then return the real and imaginary parts
  136. (in-phase and quadrature).
  137. If `retenv` is True, then return the envelope (unmodulated signal).
  138. Otherwise, return the real part of the modulated sinusoid.
  139. Parameters
  140. ----------
  141. t : ndarray or the string 'cutoff'
  142. Input array.
  143. fc : float, optional
  144. Center frequency (e.g. Hz). Default is 1000.
  145. bw : float, optional
  146. Fractional bandwidth in frequency domain of pulse (e.g. Hz).
  147. Default is 0.5.
  148. bwr : float, optional
  149. Reference level at which fractional bandwidth is calculated (dB).
  150. Default is -6.
  151. tpr : float, optional
  152. If `t` is 'cutoff', then the function returns the cutoff
  153. time for when the pulse amplitude falls below `tpr` (in dB).
  154. Default is -60.
  155. retquad : bool, optional
  156. If True, return the quadrature (imaginary) as well as the real part
  157. of the signal. Default is False.
  158. retenv : bool, optional
  159. If True, return the envelope of the signal. Default is False.
  160. Returns
  161. -------
  162. yI : ndarray
  163. Real part of signal. Always returned.
  164. yQ : ndarray
  165. Imaginary part of signal. Only returned if `retquad` is True.
  166. yenv : ndarray
  167. Envelope of signal. Only returned if `retenv` is True.
  168. See Also
  169. --------
  170. scipy.signal.morlet
  171. Examples
  172. --------
  173. Plot real component, imaginary component, and envelope for a 5 Hz pulse,
  174. sampled at 100 Hz for 2 seconds:
  175. >>> import numpy as np
  176. >>> from scipy import signal
  177. >>> import matplotlib.pyplot as plt
  178. >>> t = np.linspace(-1, 1, 2 * 100, endpoint=False)
  179. >>> i, q, e = signal.gausspulse(t, fc=5, retquad=True, retenv=True)
  180. >>> plt.plot(t, i, t, q, t, e, '--')
  181. """
  182. if fc < 0:
  183. raise ValueError("Center frequency (fc=%.2f) must be >=0." % fc)
  184. if bw <= 0:
  185. raise ValueError("Fractional bandwidth (bw=%.2f) must be > 0." % bw)
  186. if bwr >= 0:
  187. raise ValueError("Reference level for bandwidth (bwr=%.2f) must "
  188. "be < 0 dB" % bwr)
  189. # exp(-a t^2) <-> sqrt(pi/a) exp(-pi^2/a * f^2) = g(f)
  190. ref = pow(10.0, bwr / 20.0)
  191. # fdel = fc*bw/2: g(fdel) = ref --- solve this for a
  192. #
  193. # pi^2/a * fc^2 * bw^2 /4=-log(ref)
  194. a = -(pi * fc * bw) ** 2 / (4.0 * log(ref))
  195. if isinstance(t, str):
  196. if t == 'cutoff': # compute cut_off point
  197. # Solve exp(-a tc**2) = tref for tc
  198. # tc = sqrt(-log(tref) / a) where tref = 10^(tpr/20)
  199. if tpr >= 0:
  200. raise ValueError("Reference level for time cutoff must "
  201. "be < 0 dB")
  202. tref = pow(10.0, tpr / 20.0)
  203. return sqrt(-log(tref) / a)
  204. else:
  205. raise ValueError("If `t` is a string, it must be 'cutoff'")
  206. yenv = exp(-a * t * t)
  207. yI = yenv * cos(2 * pi * fc * t)
  208. yQ = yenv * sin(2 * pi * fc * t)
  209. if not retquad and not retenv:
  210. return yI
  211. if not retquad and retenv:
  212. return yI, yenv
  213. if retquad and not retenv:
  214. return yI, yQ
  215. if retquad and retenv:
  216. return yI, yQ, yenv
  217. def chirp(t, f0, t1, f1, method='linear', phi=0, vertex_zero=True):
  218. """Frequency-swept cosine generator.
  219. In the following, 'Hz' should be interpreted as 'cycles per unit';
  220. there is no requirement here that the unit is one second. The
  221. important distinction is that the units of rotation are cycles, not
  222. radians. Likewise, `t` could be a measurement of space instead of time.
  223. Parameters
  224. ----------
  225. t : array_like
  226. Times at which to evaluate the waveform.
  227. f0 : float
  228. Frequency (e.g. Hz) at time t=0.
  229. t1 : float
  230. Time at which `f1` is specified.
  231. f1 : float
  232. Frequency (e.g. Hz) of the waveform at time `t1`.
  233. method : {'linear', 'quadratic', 'logarithmic', 'hyperbolic'}, optional
  234. Kind of frequency sweep. If not given, `linear` is assumed. See
  235. Notes below for more details.
  236. phi : float, optional
  237. Phase offset, in degrees. Default is 0.
  238. vertex_zero : bool, optional
  239. This parameter is only used when `method` is 'quadratic'.
  240. It determines whether the vertex of the parabola that is the graph
  241. of the frequency is at t=0 or t=t1.
  242. Returns
  243. -------
  244. y : ndarray
  245. A numpy array containing the signal evaluated at `t` with the
  246. requested time-varying frequency. More precisely, the function
  247. returns ``cos(phase + (pi/180)*phi)`` where `phase` is the integral
  248. (from 0 to `t`) of ``2*pi*f(t)``. ``f(t)`` is defined below.
  249. See Also
  250. --------
  251. sweep_poly
  252. Notes
  253. -----
  254. There are four options for the `method`. The following formulas give
  255. the instantaneous frequency (in Hz) of the signal generated by
  256. `chirp()`. For convenience, the shorter names shown below may also be
  257. used.
  258. linear, lin, li:
  259. ``f(t) = f0 + (f1 - f0) * t / t1``
  260. quadratic, quad, q:
  261. The graph of the frequency f(t) is a parabola through (0, f0) and
  262. (t1, f1). By default, the vertex of the parabola is at (0, f0).
  263. If `vertex_zero` is False, then the vertex is at (t1, f1). The
  264. formula is:
  265. if vertex_zero is True:
  266. ``f(t) = f0 + (f1 - f0) * t**2 / t1**2``
  267. else:
  268. ``f(t) = f1 - (f1 - f0) * (t1 - t)**2 / t1**2``
  269. To use a more general quadratic function, or an arbitrary
  270. polynomial, use the function `scipy.signal.sweep_poly`.
  271. logarithmic, log, lo:
  272. ``f(t) = f0 * (f1/f0)**(t/t1)``
  273. f0 and f1 must be nonzero and have the same sign.
  274. This signal is also known as a geometric or exponential chirp.
  275. hyperbolic, hyp:
  276. ``f(t) = f0*f1*t1 / ((f0 - f1)*t + f1*t1)``
  277. f0 and f1 must be nonzero.
  278. Examples
  279. --------
  280. The following will be used in the examples:
  281. >>> import numpy as np
  282. >>> from scipy.signal import chirp, spectrogram
  283. >>> import matplotlib.pyplot as plt
  284. For the first example, we'll plot the waveform for a linear chirp
  285. from 6 Hz to 1 Hz over 10 seconds:
  286. >>> t = np.linspace(0, 10, 1500)
  287. >>> w = chirp(t, f0=6, f1=1, t1=10, method='linear')
  288. >>> plt.plot(t, w)
  289. >>> plt.title("Linear Chirp, f(0)=6, f(10)=1")
  290. >>> plt.xlabel('t (sec)')
  291. >>> plt.show()
  292. For the remaining examples, we'll use higher frequency ranges,
  293. and demonstrate the result using `scipy.signal.spectrogram`.
  294. We'll use a 4 second interval sampled at 7200 Hz.
  295. >>> fs = 7200
  296. >>> T = 4
  297. >>> t = np.arange(0, int(T*fs)) / fs
  298. We'll use this function to plot the spectrogram in each example.
  299. >>> def plot_spectrogram(title, w, fs):
  300. ... ff, tt, Sxx = spectrogram(w, fs=fs, nperseg=256, nfft=576)
  301. ... fig, ax = plt.subplots()
  302. ... ax.pcolormesh(tt, ff[:145], Sxx[:145], cmap='gray_r',
  303. ... shading='gouraud')
  304. ... ax.set_title(title)
  305. ... ax.set_xlabel('t (sec)')
  306. ... ax.set_ylabel('Frequency (Hz)')
  307. ... ax.grid(True)
  308. ...
  309. Quadratic chirp from 1500 Hz to 250 Hz
  310. (vertex of the parabolic curve of the frequency is at t=0):
  311. >>> w = chirp(t, f0=1500, f1=250, t1=T, method='quadratic')
  312. >>> plot_spectrogram(f'Quadratic Chirp, f(0)=1500, f({T})=250', w, fs)
  313. >>> plt.show()
  314. Quadratic chirp from 1500 Hz to 250 Hz
  315. (vertex of the parabolic curve of the frequency is at t=T):
  316. >>> w = chirp(t, f0=1500, f1=250, t1=T, method='quadratic',
  317. ... vertex_zero=False)
  318. >>> plot_spectrogram(f'Quadratic Chirp, f(0)=1500, f({T})=250\\n' +
  319. ... '(vertex_zero=False)', w, fs)
  320. >>> plt.show()
  321. Logarithmic chirp from 1500 Hz to 250 Hz:
  322. >>> w = chirp(t, f0=1500, f1=250, t1=T, method='logarithmic')
  323. >>> plot_spectrogram(f'Logarithmic Chirp, f(0)=1500, f({T})=250', w, fs)
  324. >>> plt.show()
  325. Hyperbolic chirp from 1500 Hz to 250 Hz:
  326. >>> w = chirp(t, f0=1500, f1=250, t1=T, method='hyperbolic')
  327. >>> plot_spectrogram(f'Hyperbolic Chirp, f(0)=1500, f({T})=250', w, fs)
  328. >>> plt.show()
  329. """
  330. # 'phase' is computed in _chirp_phase, to make testing easier.
  331. phase = _chirp_phase(t, f0, t1, f1, method, vertex_zero)
  332. # Convert phi to radians.
  333. phi *= pi / 180
  334. return cos(phase + phi)
  335. def _chirp_phase(t, f0, t1, f1, method='linear', vertex_zero=True):
  336. """
  337. Calculate the phase used by `chirp` to generate its output.
  338. See `chirp` for a description of the arguments.
  339. """
  340. t = asarray(t)
  341. f0 = float(f0)
  342. t1 = float(t1)
  343. f1 = float(f1)
  344. if method in ['linear', 'lin', 'li']:
  345. beta = (f1 - f0) / t1
  346. phase = 2 * pi * (f0 * t + 0.5 * beta * t * t)
  347. elif method in ['quadratic', 'quad', 'q']:
  348. beta = (f1 - f0) / (t1 ** 2)
  349. if vertex_zero:
  350. phase = 2 * pi * (f0 * t + beta * t ** 3 / 3)
  351. else:
  352. phase = 2 * pi * (f1 * t + beta * ((t1 - t) ** 3 - t1 ** 3) / 3)
  353. elif method in ['logarithmic', 'log', 'lo']:
  354. if f0 * f1 <= 0.0:
  355. raise ValueError("For a logarithmic chirp, f0 and f1 must be "
  356. "nonzero and have the same sign.")
  357. if f0 == f1:
  358. phase = 2 * pi * f0 * t
  359. else:
  360. beta = t1 / log(f1 / f0)
  361. phase = 2 * pi * beta * f0 * (pow(f1 / f0, t / t1) - 1.0)
  362. elif method in ['hyperbolic', 'hyp']:
  363. if f0 == 0 or f1 == 0:
  364. raise ValueError("For a hyperbolic chirp, f0 and f1 must be "
  365. "nonzero.")
  366. if f0 == f1:
  367. # Degenerate case: constant frequency.
  368. phase = 2 * pi * f0 * t
  369. else:
  370. # Singular point: the instantaneous frequency blows up
  371. # when t == sing.
  372. sing = -f1 * t1 / (f0 - f1)
  373. phase = 2 * pi * (-sing * f0) * log(np.abs(1 - t/sing))
  374. else:
  375. raise ValueError("method must be 'linear', 'quadratic', 'logarithmic',"
  376. " or 'hyperbolic', but a value of %r was given."
  377. % method)
  378. return phase
  379. def sweep_poly(t, poly, phi=0):
  380. """
  381. Frequency-swept cosine generator, with a time-dependent frequency.
  382. This function generates a sinusoidal function whose instantaneous
  383. frequency varies with time. The frequency at time `t` is given by
  384. the polynomial `poly`.
  385. Parameters
  386. ----------
  387. t : ndarray
  388. Times at which to evaluate the waveform.
  389. poly : 1-D array_like or instance of numpy.poly1d
  390. The desired frequency expressed as a polynomial. If `poly` is
  391. a list or ndarray of length n, then the elements of `poly` are
  392. the coefficients of the polynomial, and the instantaneous
  393. frequency is
  394. ``f(t) = poly[0]*t**(n-1) + poly[1]*t**(n-2) + ... + poly[n-1]``
  395. If `poly` is an instance of numpy.poly1d, then the
  396. instantaneous frequency is
  397. ``f(t) = poly(t)``
  398. phi : float, optional
  399. Phase offset, in degrees, Default: 0.
  400. Returns
  401. -------
  402. sweep_poly : ndarray
  403. A numpy array containing the signal evaluated at `t` with the
  404. requested time-varying frequency. More precisely, the function
  405. returns ``cos(phase + (pi/180)*phi)``, where `phase` is the integral
  406. (from 0 to t) of ``2 * pi * f(t)``; ``f(t)`` is defined above.
  407. See Also
  408. --------
  409. chirp
  410. Notes
  411. -----
  412. .. versionadded:: 0.8.0
  413. If `poly` is a list or ndarray of length `n`, then the elements of
  414. `poly` are the coefficients of the polynomial, and the instantaneous
  415. frequency is:
  416. ``f(t) = poly[0]*t**(n-1) + poly[1]*t**(n-2) + ... + poly[n-1]``
  417. If `poly` is an instance of `numpy.poly1d`, then the instantaneous
  418. frequency is:
  419. ``f(t) = poly(t)``
  420. Finally, the output `s` is:
  421. ``cos(phase + (pi/180)*phi)``
  422. where `phase` is the integral from 0 to `t` of ``2 * pi * f(t)``,
  423. ``f(t)`` as defined above.
  424. Examples
  425. --------
  426. Compute the waveform with instantaneous frequency::
  427. f(t) = 0.025*t**3 - 0.36*t**2 + 1.25*t + 2
  428. over the interval 0 <= t <= 10.
  429. >>> import numpy as np
  430. >>> from scipy.signal import sweep_poly
  431. >>> p = np.poly1d([0.025, -0.36, 1.25, 2.0])
  432. >>> t = np.linspace(0, 10, 5001)
  433. >>> w = sweep_poly(t, p)
  434. Plot it:
  435. >>> import matplotlib.pyplot as plt
  436. >>> plt.subplot(2, 1, 1)
  437. >>> plt.plot(t, w)
  438. >>> plt.title("Sweep Poly\\nwith frequency " +
  439. ... "$f(t) = 0.025t^3 - 0.36t^2 + 1.25t + 2$")
  440. >>> plt.subplot(2, 1, 2)
  441. >>> plt.plot(t, p(t), 'r', label='f(t)')
  442. >>> plt.legend()
  443. >>> plt.xlabel('t')
  444. >>> plt.tight_layout()
  445. >>> plt.show()
  446. """
  447. # 'phase' is computed in _sweep_poly_phase, to make testing easier.
  448. phase = _sweep_poly_phase(t, poly)
  449. # Convert to radians.
  450. phi *= pi / 180
  451. return cos(phase + phi)
  452. def _sweep_poly_phase(t, poly):
  453. """
  454. Calculate the phase used by sweep_poly to generate its output.
  455. See `sweep_poly` for a description of the arguments.
  456. """
  457. # polyint handles lists, ndarrays and instances of poly1d automatically.
  458. intpoly = polyint(poly)
  459. phase = 2 * pi * polyval(intpoly, t)
  460. return phase
  461. def unit_impulse(shape, idx=None, dtype=float):
  462. """
  463. Unit impulse signal (discrete delta function) or unit basis vector.
  464. Parameters
  465. ----------
  466. shape : int or tuple of int
  467. Number of samples in the output (1-D), or a tuple that represents the
  468. shape of the output (N-D).
  469. idx : None or int or tuple of int or 'mid', optional
  470. Index at which the value is 1. If None, defaults to the 0th element.
  471. If ``idx='mid'``, the impulse will be centered at ``shape // 2`` in
  472. all dimensions. If an int, the impulse will be at `idx` in all
  473. dimensions.
  474. dtype : data-type, optional
  475. The desired data-type for the array, e.g., ``numpy.int8``. Default is
  476. ``numpy.float64``.
  477. Returns
  478. -------
  479. y : ndarray
  480. Output array containing an impulse signal.
  481. Notes
  482. -----
  483. The 1D case is also known as the Kronecker delta.
  484. .. versionadded:: 0.19.0
  485. Examples
  486. --------
  487. An impulse at the 0th element (:math:`\\delta[n]`):
  488. >>> from scipy import signal
  489. >>> signal.unit_impulse(8)
  490. array([ 1., 0., 0., 0., 0., 0., 0., 0.])
  491. Impulse offset by 2 samples (:math:`\\delta[n-2]`):
  492. >>> signal.unit_impulse(7, 2)
  493. array([ 0., 0., 1., 0., 0., 0., 0.])
  494. 2-dimensional impulse, centered:
  495. >>> signal.unit_impulse((3, 3), 'mid')
  496. array([[ 0., 0., 0.],
  497. [ 0., 1., 0.],
  498. [ 0., 0., 0.]])
  499. Impulse at (2, 2), using broadcasting:
  500. >>> signal.unit_impulse((4, 4), 2)
  501. array([[ 0., 0., 0., 0.],
  502. [ 0., 0., 0., 0.],
  503. [ 0., 0., 1., 0.],
  504. [ 0., 0., 0., 0.]])
  505. Plot the impulse response of a 4th-order Butterworth lowpass filter:
  506. >>> imp = signal.unit_impulse(100, 'mid')
  507. >>> b, a = signal.butter(4, 0.2)
  508. >>> response = signal.lfilter(b, a, imp)
  509. >>> import numpy as np
  510. >>> import matplotlib.pyplot as plt
  511. >>> plt.plot(np.arange(-50, 50), imp)
  512. >>> plt.plot(np.arange(-50, 50), response)
  513. >>> plt.margins(0.1, 0.1)
  514. >>> plt.xlabel('Time [samples]')
  515. >>> plt.ylabel('Amplitude')
  516. >>> plt.grid(True)
  517. >>> plt.show()
  518. """
  519. out = zeros(shape, dtype)
  520. shape = np.atleast_1d(shape)
  521. if idx is None:
  522. idx = (0,) * len(shape)
  523. elif idx == 'mid':
  524. idx = tuple(shape // 2)
  525. elif not hasattr(idx, "__iter__"):
  526. idx = (idx,) * len(shape)
  527. out[idx] = 1
  528. return out