Skip to content
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

Merged
merged 1 commit into from
Nov 18, 2024

Conversation

anilgurses
Copy link
Contributor

What does this implement/fix?

Added complex option for linear chirp. Now, it's possible to generate complex linear chirps.

@tupui tupui added scipy.signal enhancement A new feature or improvement labels Nov 1, 2022
Copy link
Member

@tupui tupui left a 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.

@anilgurses
Copy link
Contributor Author

anilgurses commented Nov 1, 2022

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.

@anilgurses anilgurses force-pushed the complex_chirp branch 2 times, most recently from 191b5ce to 87d2829 Compare November 2, 2022 03:55
@anilgurses
Copy link
Contributor Author

Another thing about complex chirp is that it's possible to sweep from negative frequency to positive frequency or otherwise.

Copy link
Member

@tupui tupui left a 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?

scipy/signal/_waveforms.py Outdated Show resolved Hide resolved
scipy/signal/_waveforms.py Outdated Show resolved Hide resolved
@@ -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):
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@roryyorke
Copy link
Contributor

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 complex_chirp=True is equal to the output when complex_chirp=False.

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 chirp were changed like this, sweep_poly should probably be changed too, in much the same way. I don't think _chirp_phase or _sweep_poly_phase need to be changed at all.

@lucascolley lucascolley added the needs-decision Items that need further discussion before they are merged or closed label Dec 23, 2023
@anilgurses
Copy link
Contributor Author

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).

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,
A.

@DietBru
Copy link
Contributor

DietBru commented Feb 4, 2024

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)

@anilgurses
Copy link
Contributor Author

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?

@DietBru
Copy link
Contributor

DietBru commented Feb 4, 2024

Any chirp will do. 😃 But why use an FFT if there's a shiny new STFT?
My motivation to write down the derivation in the comment was to hint that the docstring could use some love. I think adding the explicit formulas of the chirp (e.g., $x(t)=\cos(\phi(t)+\phi_0), f_i(t) = \phi'(t)/(2\pi)$, ... ) would add a lot more clarity. Also the Python code could be a bit more expressive, e.g.:

 phase = _chirp_phase(t, f0, t1, f1, method, vertex_zero) + np.deg2rad(phi)
 return np.exp(1j*phase) if complex else np.cos(phase)

@anilgurses
Copy link
Contributor Author

Oh, it makes more sense now 😅. Let me add those details to the docstring. Thanks for the feedback!

Copy link
Member

@j-bowhay j-bowhay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some style nits

scipy/signal/tests/test_waveforms.py Outdated Show resolved Hide resolved
scipy/signal/tests/test_waveforms.py Outdated Show resolved Hide resolved
scipy/signal/tests/test_waveforms.py Outdated Show resolved Hide resolved
scipy/signal/_waveforms.py Outdated Show resolved Hide resolved
@anilgurses
Copy link
Contributor Author

anilgurses commented Feb 6, 2024

Well, I realized something strange with the new spectrogram function. Maybe this is not a place to discuss but I will create a separate pull request to fix it if needed. It doesn't support complex numbers. Am I missing something? @DietBru do you have an idea?

TypeError: x must be a real sequence

@DietBru
Copy link
Contributor

DietBru commented Feb 6, 2024

Well, I realized something strange with the new spectrogram function. Maybe this is not a place to discuss but I will create a separate pull request to fix it if needed. It doesn't support complex numbers. Am I missing something? @DietBru do you have an idea?

TypeError: x must be a real sequence

Nice to see the new functionality get some use. fft_mode='onesided' does not work for complex signals. Use fft_mode='centered' instead. Addressed in #20010

@anilgurses
Copy link
Contributor Author

Thanks for the suggestion! I finished the documentation part and other things. Let me know if something missing.

@lucascolley
Copy link
Member

@anilgurses it's probably time to put this to the mailing list now

Copy link
Contributor

@DietBru DietBru left a 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:

Comment on lines 403 to 408
... 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)")
Copy link
Contributor

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.

Comment on lines 455 to 461
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that you do incorporate this or this suggestions to simplify the code? I my opinion it would make the code much more readable.

@DietBru
Copy link
Contributor

DietBru commented Feb 15, 2024

In the rendered documentation, I noticed four unwanted blocks with gray highlighting, which is caused of blocks being indented too far, e.g.:
image
This highlighting appeared due to the updating of the sphinx theme. If you want, you can remove one space from the indentation from those four blocks.

@anilgurses
Copy link
Contributor Author

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.

@lucascolley lucascolley changed the title ENH: Add functionality of Complex Chirp waveform to Scipy.signal ENH: signal: Add functionality of Complex Chirp waveform Sep 30, 2024
@DietBru
Copy link
Contributor

DietBru commented Sep 30, 2024

The main point is that your addition is hard to understand, as commented here.
Concerning the indentation: It is helpful if the documentation layout is consistent across all functions.

@DietBru
Copy link
Contributor

DietBru commented Oct 23, 2024

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.

@anilgurses
Copy link
Contributor Author

Yes, I am still interested. I was busy with other things. I will try to finish it this week.

@DietBru
Copy link
Contributor

DietBru commented Nov 15, 2024

I just pushed some changes, which:

  • Improved signal.chirp() docstr—rendered version can be found here
  • Simplified signal.chirp() code
  • Used xp_assert_allclose() in _waveforms.py
  • Manually rebased onto main to resolve conflicts

I hope it also addresses your change request, @j-bowhay, @tupui

PS: The test failures seem unrelated.

@j-bowhay j-bowhay dismissed their stale review November 15, 2024 00:42

Changes made

@lucascolley lucascolley removed the needs-decision Items that need further discussion before they are merged or closed label Nov 15, 2024
Copy link
Member

@j-bowhay j-bowhay left a 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

@DietBru DietBru added this to the 1.15.0 milestone Nov 18, 2024
* 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>
@DietBru
Copy link
Contributor

DietBru commented Nov 18, 2024

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 $a(t)$ and a real-valued phase $ϕ(t)$, i.e., $z(t) = a(t)\ \exp\big(j 2 π ϕ (t)\big)$ (with instantaneous frequency $f_i(t)=dϕ/dt$). A great opportunity to deepen the knowledge is to review PR #20097 😁

Also, thank you @anilgurses for your contribution! I squashed the commits again, to be able to add you as author.
Will merge after the tests complete.

@anilgurses
Copy link
Contributor Author

Thanks for your help @DietBru!

@DietBru DietBru merged commit ec30b43 into scipy:main Nov 18, 2024
5 of 6 checks passed
@lucascolley lucascolley added the needs-release-note a maintainer should add a release note written by a reviewer/author to the wiki label Nov 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement A new feature or improvement needs-release-note a maintainer should add a release note written by a reviewer/author to the wiki scipy.signal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants