-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: signal: Add functionality of Complex Chirp waveform #17318
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @anilgurses. Can you motivate a bit why this is needed? The PR would also need some tests to ensure the new feature is working as intended.
Thanks for your feedback @tupui . Complex chirp uses both complex and real part of the signal(I and Q). If anyone wants to obtain more bandwidth while maintaining the same sample rate, this will be very helpful. From Nyquist theorem, bandwidth is limited to F_s/2. However, if we use the complex part of the signal as well, bandwidth will be limited to F_s. I'll add the missing tests. |
191b5ce
to
87d2829
Compare
Another thing about complex chirp is that it's possible to sweep from negative frequency to positive frequency or otherwise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did another pass. For the maths and final decision to add this parameter, we still need someone familiar with this function. @ilayn is this something you can help with?
@@ -81,6 +86,11 @@ def test_quadratic_at_zero2(self): | |||
vertex_zero=False) | |||
assert_almost_equal(w, 1.0) | |||
|
|||
def test_quadratic_complex_at_zero(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would need to test more known values to ensure the function behaves as expected and there are no corner cases it cannot handle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have any suggestions?
PS. Sorry, this fell through the cracks. If you can guide me through some example scenarios, I can write them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added one more test. I'll think about what else I can add.
It would be good to have a reference to the definition of a complex chirp, as well as give some idea of applications (in electronic and electrical engineering I think at least radar, power systems, and communication). I'd expect the function to be something like the below, which has the property that the real part of the output when I also don't have a reference to support this, though. def chirp(t, f0, t1, f1, method='linear', phi=0, vertex_zero=True, *, complex_chirp=False):
phase = _chirp_phase(t, f0, t1, f1, method, vertex_zero)
phi *= pi / 180
if complex_chirp:
return exp(1j * (phase + phi)) # exp already imported in _waveforms.py
else:
return cos(phase + phi) There's no need to limit it to just linear and quadratic - you just can't get a sign change in frequency with a logarithmic sweep. I'm not familiar with hyperbolic sweep, but If |
Good point, the only reference I could find for complex chirp waveform is the Matlab user manual. I think we shouldn't limit this specifically to complex chirp. My change is adding complex baseband (sort of) support to the chirp waveform. There is a good post about this at DSP StackExchange link. Proakis's digital communications book might be a good reference for this. Please let me know what I can do for this PR to be merged. PS. Once again, sorry for the delay. I finally had time to look at this. Thanks, |
The STFT plot of a complex chirp using scipy.signal.ShortTimeFFT can be found here. Perhaps it would make sense to put an adapted version into the docstring. I think it is pretty self-explanatory. Note that creating arbitrary chirps is straightforward, if you remember that the instantaneous frequency is derivative of the phase, e.g., n, T = 1000, 1e-3 # number of samples and sampling interval
t = np.arange(n)*T # time stamps
f_i = 5*t +10 # arbitrary function for instantaneous frequency f_i(t)
phi = np.cumsum(f_i)*T # numeric integration
z = np.exp(2j*np.pi*phi) # sinusoid with instantaneous frequency f_i(t) (I did not investigate the given code) |
Thanks for your comment @DietBru. The document you shared generates the chirp waveform from scratch. Therefore, it might not be too relevant to our case here but it's helpful to compare the approach. Regarding the documentation, I can add the FFT output of complex chirp, similar to Matlab's example. Does it sound reasonable? |
Any chirp will do. 😃 But why use an FFT if there's a shiny new STFT? phase = _chirp_phase(t, f0, t1, f1, method, vertex_zero) + np.deg2rad(phi)
return np.exp(1j*phase) if complex else np.cos(phase) |
Oh, it makes more sense now 😅. Let me add those details to the docstring. Thanks for the feedback! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some style nits
Nice to see the new functionality get some use. |
Thanks for the suggestion! I finished the documentation part and other things. Let me know if something missing. |
@anilgurses it's probably time to put this to the mailing list now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have two comments:
scipy/signal/_waveforms.py
Outdated
... Sx2 = SFT.spectrogram(w) | ||
... Sx_dB = 10 * np.log10(np.fmax(Sx2, 1e-4)) | ||
... fig, ax = plt.subplots() | ||
... ax.pcolormesh(tt, ff[:145], Sxx[:145], cmap='gray_r', | ||
... shading='gouraud') | ||
... im1 = ax.imshow(Sx_dB, origin='lower', aspect='auto', | ||
... extent=SFT.extent(len(w)), cmap='plasma') | ||
... fig.colorbar(im1, label = "Power (dB)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps using linear instead of dB scaling is little more suitable here, since it marginally simplifies the code, i.e.:
Sx2 = SFT.spectrogram(w)
fig, ax = plt.subplots()
im1 = ax.imshow(Sx2, origin='lower', aspect='auto',
extent=SFT.extent(len(w)), cmap='plasma')
fig.colorbar(im1, label = "Power Spectral Density")
Nitpick: The option scale_to='psd'
produces a power spectral density not a power as written in the colorbar label.
scipy/signal/_waveforms.py
Outdated
if complex: | ||
a = exp(2j * pi * phi / (2*pi)) | ||
i = cos(phase + phi) | ||
q = cos(phase + phi + (2 * pi * 90 / 360)) * 1j | ||
return a * np.complex64(i - q) | ||
else: | ||
return cos(phase + phi) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the rendered documentation, I noticed four unwanted blocks with gray highlighting, which is caused of blocks being indented too far, e.g.: |
Is there any update on this? It's almost 2 years since my commit :) If the indentation in the document is the problem, I can easily fix it. I believe indentation helps highlight the description of the point. |
The main point is that your addition is hard to understand, as commented here. |
Gentle ping to @anilgurses : Do you still have interest in finishing this PR? If not, that is fine as well. I would be happy to take up the remaining work. |
Yes, I am still interested. I was busy with other things. I will try to finish it this week. |
ca67674
to
74dfbfe
Compare
I just pushed some changes, which:
I hope it also addresses your change request, @j-bowhay, @tupui PS: The test failures seem unrelated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs look really great and changes look fine from a python perspective although the subject area is something I'm not familiar with. Feel free to merge if you're happy @DietBru
* Added boolean parameter `complex` * Simplified `signal.chirp()` code * Added unit tests (including switching over to `xp_assert_allclose`) * Improved docstr Co-authored-by: Anil Gurses <anilgurses98@gmail.com>
b048746
to
96d56d9
Compare
Thanks for the feedback, @j-bowhay. On this subject matter is not a lot to know—just that any signal can be represented by real-valued amplitude Also, thank you @anilgurses for your contribution! I squashed the commits again, to be able to add you as author. |
Thanks for your help @DietBru! |
What does this implement/fix?
Added complex option for linear chirp. Now, it's possible to generate complex linear chirps.