-
-
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: Compute envelope of a real- or complex-valued signal #20097
Conversation
The rendered version can be found here. I am not sure what to make of the error message:
This is the only error I found in the failing tests which could be related this PR. |
That syntax is not supported in Python < 3.10. |
Thanks @lucascolley, for the prompt answer. It could have been that I asked this question before 😅 |
@yagizolmez would you be interested in reviewing this given your heavy involvement in previous discussions? |
@j-bowhay I will leave a review in the upcoming days. I want to note that this is NOT a competing PR with #19814. Following the discussion we had there, I decided to break up the envelope function I proposed and not implement the analytical envelope. @DietBru I have a quick question before I leave a more detailed review. Comparing this function with
Have you considered extending |
I did, but I came to the conclusion that that would only lead to confusion: The analytic signal is a concept based on real-valued signals. Rightfully, hilbert() raises a The envelope on the other hand is a concept that is inherent to complex-valued signals with real-valued signals being a special case. Note that the treatment of real-valued signals depends on the underlying assumptions of what the signal represents. This is illustrated in the second example of the |
@DietBru I am sorry for the delay. I have been very busy these days, so I am not yet able to provide a detailed review. One thing catched my eye. Under certain circumstances, this function must give the same result with |
Good catch, thank you. I found an edge case where the results differ. Hence, I did some refactoring to ensure that |
@yagizolmez would you like to take another look at this? |
@lucascolley Thanks for reminding me Lucas. I have gotten extremely busy in the school lately. I will take a look when I have time. @DietBru I apologize. |
Resolved merge conflict. Current version of the rendered documentation can be found here. |
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 read through the discussion here.
This looks like a decent chunk of work, but I'll have to bump the release milestone since there's no detailed expert review of the new feature. I could check it for superficial things like test coverage and so on, but I wouldn't feel comfortable merging it on my own. Apologies!
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.
@DietBru Sorry for the delay. In order to keep things moving, I reviewed the documentation part of the code (which may eventually cause changes to the actual code). I haven't reviewed the examples yet, because the docs don't render. I will review the other parts soon.
scipy/signal/_signaltools.py
Outdated
Any complex-valued signal `z(t)` can be split into a real-valued (instantaneous) | ||
amplitude `a(t)` and a real-valued (instantaneous) phase `phi(t)`, i.e., | ||
`z(t) = a(t) * exp(1j*phi(t))`. The envelope is defined as the absolute value of |
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.
Any complex-valued signal `z(t)` can be split into a real-valued (instantaneous) | |
amplitude `a(t)` and a real-valued (instantaneous) phase `phi(t)`, i.e., | |
`z(t) = a(t) * exp(1j*phi(t))`. The envelope is defined as the absolute value of | |
Any complex-valued signal `z(t)` can be described by a real-valued amplitude | |
`a(t)` and a real-valued phase `phi(t)`, i.e., `z(t) = a(t) * exp(1j*phi(t))`. The | |
envelope is defined as the absolute value of |
The word 'instantaneous' is redundant here, since you are already describing the amplitude and phase as a function of time.
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.
The redundancy is intentional here. In signal processing, when talking about phase and amplitude it is often implicitly assumed that those are stationary. I think, the redundancy makes it easier to grasp for many people.
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.
Changed the wording slightly in commit f910d6e
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 think it would be better to write phi in Greek letters. Could you write the math expressions in latex? If this is not possible, I would suggest denoting the phase with p instead of 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.
I also like the LaTeX expression better, but not all IDEs render those correctly when displaying help texts.
There is guidance on that topic in SciPy, though not very straightforward to find:
The SciPy contributor Guide links to the Numpy Documentation Style , which links to Numpydoc guide
which states that LaTeX may be used in the Notes section.
So I followed that convention.
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 guess using p(t)
instead of phi(t)
is a matter of taste—I'm just used to signal processing literature where an phase angle is always denoted by a Greek letter.
Thanks for the review @yagizolmez. The updated rendered version of the documentation can be found here, |
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.
@DietBru I did a second pass on the documentation, now that the examples are rendered. Also, I would like to point out one thing. I don't have much experience ever needing to extract the envelope of a complex-valued signal. I can review for math accuracy, but I cannot review for user-friendliness/usefulness etc.
@ilayn Do you know anyone who could provide a better review for the complex-valued signal case?
Perhaps @roryyorke or @larsoner ? |
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.
The implementation largely looks good to me. Although I didn't understand some parts. I will start going over the unit tests.
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.
First pass at the tests looks good. I will take a second look in the near future. I think you could add some tests for squared = False too? This could also be calculated by hand for simple signals.
bf9978f
to
fff6bf8
Compare
The failure in building the docs seem to be a |
Remove unrelated whitespaces Co-authored-by: Yagiz Olmez <yagiz.olmez@gmail.com>
Co-authored-by: Yagiz Olmez <yagiz.olmez@gmail.com>
…ed out some edges.
fff6bf8
to
1a0d46d
Compare
Rebased this branch on the documentation is building again. The current Thanks again for your input @yagizolmez — I hope I have sufficiently addressed all your remarks by now. |
@DietBru Thanks. I will check in a few days. |
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.
Looks good to me! I would suggest getting at least one more opinion before merging though.
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 @DietBru a few suggestion but otherwise looks in good shape
Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com>
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 @DietBru!! Given @yagizolmez's approval, I'm happy to merge this, modulo the suggestions I have added here, and checking the rendered docs one more time. Let's get this in for 1.15.0 🎉
Please check all of my suggestions and commit them if you agree.
Fixed typo Co-authored-by: Lucas Colley <lucas.colley8@gmail.com>
* In class `TestEnvelope.test_envelope_invalid_parameters()` make linter statement `# noinspection PyTypeChecker` expression-specific instead of method-specific
Thanks @lucascolley for the review and spotting the typos—I think, I addressed all your comments in the last commit. |
thanks Dietrich, let's check the rendered docs once gh-21988 is resolved. |
[skip cirrus]
Glancing at the rendered docs, I saw no surprises. 🎉 |
[skip ci]
This PR proposes a new function for
scipy.signal
, which computes the envelope of a real- or complex-valued signal after applying an optional bandpass filter.The discussion in #19814 revealed that literature is a bit convoluted regarding on how to define envelopes for signals. Hence, this PR also tries to concisely document the approach utilized here.
This PR differs from #19814 in that it provides a general solution, whereas #19814 does not produce correct results (regarding the definitions of a envelope given here) for all possible signals.