-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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: Add fs=
parameter to filter design functions
#8397
Conversation
Thanks for this, it is a much needed enhancement. I'll try to do a review sometime this weekend (nag me if I don't!). One thing I'll note now is that unit tests are needed for the new option. |
Agreed, even if it's redundant the new code is much more readable. +1 from me |
I've optimistically marked this for 1.1 (mid-March) |
Yeah I wanted to see if people liked it before doing the rest of the work. Sounds like people do, though. |
Instead of default of Also can be added to
Already had fs:
|
I prefer explicit Maybe we can both be happy by using As for what needs to be changed, I'd recommend taking a brief scan of the |
Should freqz, ellipord, etc. return w in fs units if fs specified? Like if you do
should
Should
? I think it makes sense to be in Hz for all of them. Likewise for |
Added examples that use fs and sos to filter a real signal: Also added test for some functions but not all yet |
So doctest failed because of
But there are other examples with
that don't cause failures? |
Looks like https://github.com/scipy/scipy/blob/master/tools/refguide_check.py#L518 This could be improved/extended with e.g. |
Maybe the thing to do is to change it to This should probably be a separate PR, though, so that it gets proper discussion as it modifies the test suite. |
Please no doctest:+SKIP, just fix the refguide-checker. |
I can live with |
@endolith asked
My expectation was that |
As long as the new defaults give the same old results, we should be good to go on this. I have the same expectation as you, that all outputs would be modified appropriately. |
Another observation: the functions |
I'm not sure what to do about the failures.
but it's supposed to produce an error, and does in Python 3.6.3, but not in Python 3.4.6 the test is running in? |
Different Travis runs use different NumPy versions, this is probably to blame. The 3.4.6 appears to use NumPy 1.8.2. The code for |
So this goes back to #7351 and how to verify int parameters. I made a chart of how different types are handled here: https://stackoverflow.com/a/48940855/125507 I did this using |
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.
operator.index
is indeed what we settled on for int
checks.
I made some small comments on the code, let's see what @WarrenWeckesser says about the fs
default, and once that's settled I'll take a look at the examples and tests.
scipy/signal/filter_design.py
Outdated
except TypeError: | ||
pass | ||
else: | ||
w = findfreqs(b, a, N) |
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.
You could save on branching/lines a little bit with else
instead of elif
here:
else:
try:
N = operator.index(worN)
except TypeError:
w = worN
else:
w = findfreqs(b, a, N)
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.
It needs to do the ndim check to interpret array([8])
as "measure at point 8", rather than "measure at 8 points" since apparently older numpy versions returned .index() on 1-element N-D arrays.
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.
Using, for example, worN=3.0
, results in UnboundLocalError: local variable 'w' referenced before assignment
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 modified it to fix the unbound error. Now it calls index twice, but that's not a big deal? Otherwise I'd have to duplicate other things and it would be harder to read. Still need to write tests for this condition
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.
Calling index
twice seems fine if it leads to the simplest code.
scipy/signal/filter_design.py
Outdated
@@ -299,12 +304,15 @@ def freqz(b, a=1, worN=None, whole=False, plot=None): | |||
A callable that takes two arguments. If given, the return parameters | |||
`w` and `h` are passed to plot. Useful for plotting the frequency | |||
response inside `freqz`. | |||
fs : float | |||
The sampling frequency of the digital system. | |||
.. versionadded:: 1.1.0 |
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.
Need an extra newline before the ..
directive otherwise it doesn't render properly (not seen as separate paragraph)
scipy/signal/filter_design.py
Outdated
The normalized frequencies at which `h` was computed, in | ||
radians/sample. | ||
The frequencies at which `h` was computed. If `fs` is specified, | ||
these are in the same units. Otherwise they are in radians/sample. |
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.
This is a case where setting fs=2*pi
makes things simpler. Here you can just say they are in units of the sample rate, and you can get rid of the conditionals below. We can make fs=None
behave like 2 * pi
if this would make achieving "use-the-default" behavior easier for people. @WarrenWeckesser WDYT?
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 worN=None
arguments follow the same pattern, right? They actually default to numbers, too.
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.
Yes, I am arguing against that design choice as it hurts argument introspection. I think we should ideally change this everywhere, so at least avoid propagating the problem 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 just remembered this was discussed in #7788. I'll try to move that forward so we can proceed 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.
Maybe that should be done in a separate PR then? (And changing worN=512
as well?) Changing these is 100% backwards compatible, right?
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.
Sorry for disappearing from this conversation. If no one else objects to fs=2*np.pi
, then go ahead. (Of course, introspection of the signature will then show fs=6.283185307179586
.)
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.
@WarrenWeckesser We could make an object that has repr of 2*pi
but otherwise behaves as a float.
class Foo(float):
def __new__(self, value):
return float.__new__(self, value)
def __init__(self, value):
float.__init__(value)
def __repr__(self):
return '2*pi'
foo = Foo(1)
def freqz(fs=foo):
print(fs)
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.
That's a neat idea, but I don't think the extra code is worth it just to fix a cosmetic issue.
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.
Back to this line of code -- we should just say that the frequencies are in units of fs
(and if you want, add that the default fs=2*pi
implies the units are radians/sample)
This was tested in the functions a few different ways. Some don't recognize np.int8(8) as an integer that means "measure at 8 frequencies". Some accept np.array([8]) as an integer (with a warning) and measure at 8 frequencies, even though it should just mean "measure at frequency 8".
"512 frequencies equally spaced around the unit circle." is not correct if whole=False.
iirfilter() Examples now include both analog and digital freqz_zpk() Examples: Get rid of MatplotlibDeprecationWarning: "Adding an axes using the same arguments as a previous axes currently reuses the earlier instance." subplot(1, 1, 1) is more intuitive than magic behavior of subplot(111). Move grid() command so vertical lines are shown, too.
scipy#8397 (comment) worN=3.0, results in UnboundLocalError: local variable 'w' referenced before assignment Harmonize all functions that detect w vs N using type, and use N or w variable names in branches for clarity
Test that it handles type-checking correctly: worN=8 means "check 8 equally-spaced points around the unit circle" worN=8.0 means "check at frequency 8 Hz" Test freqz and sosfreqz in both fft and non-fft conditions.
Reminder to not remove these; they're for backwards compatibility after the changes in scipy#8584
It was checking for exact equality of input w to output w, but we are normalizing and un-normalizing now, so there will be slight differences. (Also can't just use a temporary normalized w internally, because we still need to un-normalize it when N is int and fs is specified.)
Ok updated the version number |
I guess everybody's happy now. In it goes then. Thanks a lot @endolith !! |
Nice:) @endolith would you mind adding a few sentences to the release notes at https://github.com/scipy/scipy/wiki/Release-note-entries-for-SciPy-1.2.0? |
fs=
parameter to filter design functionsfs=
parameter to filter design functions
This is just a more convenient way of specifying normalized frequencies for digital filters. Instead of having to remember that
butter
usesfc/(fs/2)
, whilefreqz
usesfc/(fs/(2*pi))
, for instance, you can just specify the frequencies in Hz and the sampling rate in Hz.Old way:
New way:
I proposed this a while ago and no one seemed interested, but it's easy enough to implement so we'll see what people think.
Examples of confusion: