@@ -257,8 +257,9 @@ def gausspulse(t, fc=1000, bw=0.5, bwr=-6, tpr=-60, retquad=False,
257
257
return yI , yQ , yenv
258
258
259
259
260
- def chirp (t , f0 , t1 , f1 , method = 'linear' , phi = 0 , vertex_zero = True ):
261
- """Frequency-swept cosine generator.
260
+ def chirp (t , f0 , t1 , f1 , method = 'linear' , phi = 0 , vertex_zero = True , * ,
261
+ complex = False ):
262
+ r"""Frequency-swept cosine generator.
262
263
263
264
In the following, 'Hz' should be interpreted as 'cycles per unit';
264
265
there is no requirement here that the unit is one second. The
@@ -284,135 +285,146 @@ def chirp(t, f0, t1, f1, method='linear', phi=0, vertex_zero=True):
284
285
This parameter is only used when `method` is 'quadratic'.
285
286
It determines whether the vertex of the parabola that is the graph
286
287
of the frequency is at t=0 or t=t1.
288
+ complex : bool, optional
289
+ This parameter creates a complex-valued analytic signal instead of a
290
+ real-valued signal. It allows the use of complex baseband (in communications
291
+ domain). Default is False.
292
+
293
+ .. versionadded:: 1.15.0
287
294
288
295
Returns
289
296
-------
290
297
y : ndarray
291
- A numpy array containing the signal evaluated at `t` with the
292
- requested time-varying frequency. More precisely, the function
293
- returns ``cos(phase + (pi/180)*phi)`` where `phase` is the integral
294
- (from 0 to `t`) of ``2*pi*f(t)``. ``f(t)`` is defined below.
298
+ A numpy array containing the signal evaluated at `t` with the requested
299
+ time-varying frequency. More precisely, the function returns
300
+ ``exp(1j*phase + 1j*(pi/180)*phi) if complex else cos(phase + (pi/180)*phi)``
301
+ where `phase` is the integral (from 0 to `t`) of ``2*pi*f(t)``.
302
+ The instantaneous frequency ``f(t)`` is defined below.
295
303
296
304
See Also
297
305
--------
298
306
sweep_poly
299
307
300
308
Notes
301
309
-----
302
- There are four options for the `method`. The following formulas give
303
- the instantaneous frequency (in Hz) of the signal generated by
304
- `chirp()`. For convenience, the shorter names shown below may also be
305
- used.
306
-
307
- linear, lin, li:
308
-
309
- ``f(t) = f0 + (f1 - f0) * t / t1``
310
-
311
- quadratic, quad, q:
310
+ There are four possible options for the parameter `method`, which have a (long)
311
+ standard form and some allowed abbreviations. The formulas for the instantaneous
312
+ frequency :math:`f(t)` of the generated signal are as follows:
312
313
313
- The graph of the frequency f(t) is a parabola through (0, f0) and
314
- (t1, f1). By default, the vertex of the parabola is at (0, f0).
315
- If `vertex_zero` is False, then the vertex is at (t1, f1). The
316
- formula is:
314
+ 1. Parameter `method` in ``('linear', 'lin', 'li')``:
317
315
318
- if vertex_zero is True:
316
+ .. math::
317
+ f(t) = f_0 + \beta\, t \quad\text{with}\quad
318
+ \beta = \frac{f_1 - f_0}{t_1}
319
319
320
- ``f(t) = f0 + (f1 - f0) * t**2 / t1**2``
320
+ Frequency :math:`f(t)` varies linearly over time with a constant rate
321
+ :math:`\beta`.
321
322
322
- else :
323
+ 2. Parameter `method` in ``('quadratic', 'quad', 'q')`` :
323
324
324
- ``f(t) = f1 - (f1 - f0) * (t1 - t)**2 / t1**2``
325
+ .. math::
326
+ f(t) =
327
+ \begin{cases}
328
+ f_0 + \beta\, t^2 & \text{if vertex_zero is True,}\\
329
+ f_1 + \beta\, (t_1 - t)^2 & \text{otherwise,}
330
+ \end{cases}
331
+ \quad\text{with}\quad
332
+ \beta = \frac{f_1 - f_0}{t_1^2}
325
333
326
- To use a more general quadratic function, or an arbitrary
327
- polynomial, use the function `scipy.signal.sweep_poly`.
334
+ The graph of the frequency f(t) is a parabola through :math:`(0, f_0)` and
335
+ :math:`(t_1, f_1)`. By default, the vertex of the parabola is at
336
+ :math:`(0, f_0)`. If `vertex_zero` is ``False``, then the vertex is at
337
+ :math:`(t_1, f_1)`.
338
+ To use a more general quadratic function, or an arbitrary
339
+ polynomial, use the function `scipy.signal.sweep_poly`.
328
340
329
- logarithmic, log, lo :
341
+ 3. Parameter `method` in ``(' logarithmic', ' log', 'lo')`` :
330
342
331
- ``f(t) = f0 * (f1/f0)**(t/t1)``
343
+ .. math::
344
+ f(t) = f_0 \left(\frac{f_1}{f_0}\right)^{t/t_1}
332
345
333
- f0 and f1 must be nonzero and have the same sign.
346
+ :math:`f_0` and :math:`f_1` must be nonzero and have the same sign.
347
+ This signal is also known as a geometric or exponential chirp.
334
348
335
- This signal is also known as a geometric or exponential chirp.
349
+ 4. Parameter `method` in ``('hyperbolic', 'hyp')``:
336
350
337
- hyperbolic, hyp:
351
+ .. math::
352
+ f(t) = \frac{\alpha}{\beta\, t + \gamma} \quad\text{with}\quad
353
+ \alpha = f_0 f_1 t_1, \ \beta = f_0 - f_1, \ \gamma = f_1 t_1
338
354
339
- ``f(t) = f0*f1*t1 / ((f0 - f1)*t + f1*t1)``
355
+ :math:`f_0` and :math:`f_1` must be nonzero.
340
356
341
- f0 and f1 must be nonzero.
342
357
343
358
Examples
344
359
--------
345
- The following will be used in the examples:
360
+ For the first example, a linear chirp ranging from 6 Hz to 1 Hz over 10 seconds is
361
+ plotted:
346
362
347
363
>>> import numpy as np
348
- >>> from scipy.signal import chirp, spectrogram
364
+ >>> from matplotlib.pyplot import tight_layout
365
+ >>> from scipy.signal import chirp, square, ShortTimeFFT
366
+ >>> from scipy.signal.windows import gaussian
349
367
>>> import matplotlib.pyplot as plt
350
-
351
- For the first example, we'll plot the waveform for a linear chirp
352
- from 6 Hz to 1 Hz over 10 seconds:
353
-
354
- >>> t = np.linspace(0, 10, 1500)
355
- >>> w = chirp(t, f0=6, f1=1, t1=10, method='linear')
356
- >>> plt.plot(t, w)
357
- >>> plt.title("Linear Chirp, f(0)=6, f(10)=1")
358
- >>> plt.xlabel('t (sec)')
359
- >>> plt.show()
360
-
361
- For the remaining examples, we'll use higher frequency ranges,
362
- and demonstrate the result using `scipy.signal.spectrogram`.
363
- We'll use a 4 second interval sampled at 7200 Hz.
364
-
365
- >>> fs = 7200
366
- >>> T = 4
367
- >>> t = np.arange(0, int(T*fs)) / fs
368
-
369
- We'll use this function to plot the spectrogram in each example.
370
-
371
- >>> def plot_spectrogram(title, w, fs):
372
- ... ff, tt, Sxx = spectrogram(w, fs=fs, nperseg=256, nfft=576)
373
- ... fig, ax = plt.subplots()
374
- ... ax.pcolormesh(tt, ff[:145], Sxx[:145], cmap='gray_r',
375
- ... shading='gouraud')
376
- ... ax.set_title(title)
377
- ... ax.set_xlabel('t (sec)')
378
- ... ax.set_ylabel('Frequency (Hz)')
379
- ... ax.grid(True)
380
368
...
381
-
382
- Quadratic chirp from 1500 Hz to 250 Hz
383
- (vertex of the parabolic curve of the frequency is at t=0):
384
-
385
- >>> w = chirp(t, f0=1500, f1=250, t1=T, method='quadratic')
386
- >>> plot_spectrogram(f'Quadratic Chirp, f(0)=1500, f({T})=250', w, fs)
387
- >>> plt.show()
388
-
389
- Quadratic chirp from 1500 Hz to 250 Hz
390
- (vertex of the parabolic curve of the frequency is at t=T):
391
-
392
- >>> w = chirp(t, f0=1500, f1=250, t1=T, method='quadratic',
393
- ... vertex_zero=False)
394
- >>> plot_spectrogram(f'Quadratic Chirp, f(0)=1500, f({T})=250\\ n' +
395
- ... '(vertex_zero=False)', w, fs)
369
+ >>> N, T = 1000, 0.01 # number of samples and sampling interval for 10 s signal
370
+ >>> t = np.arange(N) * T # timestamps
371
+ ...
372
+ >>> x_lin = chirp(t, f0=6, f1=1, t1=10, method='linear')
373
+ ...
374
+ >>> fg0, ax0 = plt.subplots()
375
+ >>> ax0.set_title(r"Linear Chirp from $f(0)=6\,$Hz to $f(10)=1\,$Hz")
376
+ >>> ax0.set(xlabel="Time $t$ in Seconds", ylabel=r"Amplitude $x_\text{lin}(t)$")
377
+ >>> ax0.plot(t, x_lin)
396
378
>>> plt.show()
397
379
398
- Logarithmic chirp from 1500 Hz to 250 Hz:
380
+ The following four plots each show the short-time Fourier transform of a chirp
381
+ ranging from 45 Hz to 5 Hz with different values for the parameter `method`
382
+ (and `vertex_zero`):
399
383
400
- >>> w = chirp(t, f0=1500, f1=250, t1=T, method='logarithmic')
401
- >>> plot_spectrogram(f'Logarithmic Chirp, f(0)=1500, f({T})=250', w, fs)
384
+ >>> x_qu0 = chirp(t, f0=45, f1=5, t1=N*T, method='quadratic', vertex_zero=True)
385
+ >>> x_qu1 = chirp(t, f0=45, f1=5, t1=N*T, method='quadratic', vertex_zero=False)
386
+ >>> x_log = chirp(t, f0=45, f1=5, t1=N*T, method='logarithmic')
387
+ >>> x_hyp = chirp(t, f0=45, f1=5, t1=N*T, method='hyperbolic')
388
+ ...
389
+ >>> win = gaussian(50, std=12, sym=True)
390
+ >>> SFT = ShortTimeFFT(win, hop=2, fs=1/T, mfft=800, scale_to='magnitude')
391
+ >>> ts = ("'quadratic', vertex_zero=True", "'quadratic', vertex_zero=False",
392
+ ... "'logarithmic'", "'hyperbolic'")
393
+ >>> fg1, ax1s = plt.subplots(2, 2, sharex='all', sharey='all',
394
+ ... figsize=(6, 5), layout="constrained")
395
+ >>> for x_, ax_, t_ in zip([x_qu0, x_qu1, x_log, x_hyp], ax1s.ravel(), ts):
396
+ ... aSx = abs(SFT.stft(x_))
397
+ ... im_ = ax_.imshow(aSx, origin='lower', aspect='auto', extent=SFT.extent(N),
398
+ ... cmap='plasma')
399
+ ... ax_.set_title(t_)
400
+ ... if t_ == "'hyperbolic'":
401
+ ... fg1.colorbar(im_, ax=ax1s, label='Magnitude $|S_z(t,f)|$')
402
+ >>> _ = fg1.supxlabel("Time $t$ in Seconds") # `_ =` is needed to pass doctests
403
+ >>> _ = fg1.supylabel("Frequency $f$ in Hertz")
402
404
>>> plt.show()
403
405
404
- Hyperbolic chirp from 1500 Hz to 250 Hz:
406
+ Finally, the short-time Fourier transform of a complex-valued linear chirp
407
+ ranging from -30 Hz to 30 Hz is depicted:
405
408
406
- >>> w = chirp(t, f0=1500, f1=250, t1=T, method='hyperbolic')
407
- >>> plot_spectrogram(f'Hyperbolic Chirp, f(0)=1500, f({T})=250', w, fs)
409
+ >>> z_lin = chirp(t, f0=-30, f1=30, t1=N*T, method="linear", complex=True)
410
+ >>> SFT.fft_mode = 'centered' # needed to work with complex signals
411
+ >>> aSz = abs(SFT.stft(z_lin))
412
+ ...
413
+ >>> fg2, ax2 = plt.subplots()
414
+ >>> ax2.set_title(r"Linear Chirp from $-30\,$Hz to $30\,$Hz")
415
+ >>> ax2.set(xlabel="Time $t$ in Seconds", ylabel="Frequency $f$ in Hertz")
416
+ >>> im2 = ax2.imshow(aSz, origin='lower', aspect='auto',
417
+ ... extent=SFT.extent(N), cmap='viridis')
418
+ >>> fg2.colorbar(im2, label='Magnitude $|S_z(t,f)|$')
408
419
>>> plt.show()
409
420
421
+ Note that using negative frequencies makes only sense with complex-valued signals.
422
+ Furthermore, the magnitude of the complex exponential function is one whereas the
423
+ magnitude of the real-valued cosine function is only 1/2.
410
424
"""
411
425
# 'phase' is computed in _chirp_phase, to make testing easier.
412
- phase = _chirp_phase (t , f0 , t1 , f1 , method , vertex_zero )
413
- # Convert phi to radians.
414
- phi *= pi / 180
415
- return cos (phase + phi )
426
+ phase = _chirp_phase (t , f0 , t1 , f1 , method , vertex_zero ) + np .deg2rad (phi )
427
+ return np .exp (1j * phase ) if complex else np .cos (phase )
416
428
417
429
418
430
def _chirp_phase (t , f0 , t1 , f1 , method = 'linear' , vertex_zero = True ):
0 commit comments