[MRG+2] ENH: padding kwarg for psd_welch - #4003
Conversation
Codecov Report
@@ Coverage Diff @@
## master #4003 +/- ##
===========================================
- Coverage 86.06% 73.95% -12.12%
===========================================
Files 349 350 +1
Lines 62680 62999 +319
Branches 9591 9631 +40
===========================================
- Hits 53945 46588 -7357
- Misses 6040 13617 +7577
- Partials 2695 2794 +99Continue to review full report at Codecov.
|
| """Aux function.""" | ||
| return func(epoch, fs=fs, nperseg=nfft, noverlap=noverlap, | ||
| nfft=nfft, window='hann')[2][..., freq_mask, :] | ||
| nfft=padding, window='hann')[2][..., freq_mask, :] |
There was a problem hiding this comment.
I don't like this. nfft and padding have different meanings.
There was a problem hiding this comment.
This is the scipy.spectral.spectrogram API. It differs from mne's psd_welch: nperseg is the length of each welch window and nfft is the final length of each window. So if nfft > nperseg it results in zero padding nperseg segments to nfft. psd_welch already uses nfft to mean nperseg, so I added padding (or pad_to or a different kwarg name if you prefer). We could also change psd_welch a little to use the same logic as scipy spectrogram (nperseg and nfft) but that would require deprecation (although the behavior could stay backward compatible - the meaning of the parameters would change).
There was a problem hiding this comment.
@mmagnuski can you summarize the new logic? You add a padding parameter which can override the n_fft if it is not None?
There was a problem hiding this comment.
Previously both nperseg and nfft were populated with psd_welch nfft - which means that padding was never used. Now there is a separate kwarg for padding that is used only when not None and > nfft. I agree that naming is unfortunate - ideally nfft should control padding if > nperseg (just like in scipy). We can have such behavior in mne but this would probably require a deprecation...
|
Or not - depending on the wording of the docs and your opinion. :) |
|
@mmagnuski I'm trying to better understand a) the motivation for this b) how it changes the exisiting behavior with regard to |
|
Sure, the motivation for this is adding control over padding - see #3621. It wasn't a long discussion but the general agreement was to add control over padding, likely through additional kwarg. Currently you can't have padding - only segment length is controlled. Introducing control over padding requires either:
I also cover the logic proposed here in my previous comments - in case you didn't see all of them. |
|
I'm okay with deprecation here if it gets equivalent API with SciPy.
|
|
what disturb me is that nfft = len(signal) + padding
it was a semantic mismatch.
Why would you want to do some padding in the first place? Have non power of
2 segments but fast ffts?
|
|
@agramfort It is not so much my need to add padding - but in #3621 you agreed it should be added (at least I read your "I agree" this way :) ). In general non power of 2 is one case but interpolation is another. I remember I was a bit surprised when I started using mne and found out that you can't set window length and padding - just as the person who submitted |
|
sorry, I'm on my phone, pushed wrong button by accident. |
|
please show me how high level API would look like
|
|
with solution 2 that would be: psds, freqs = psd_welch(raw, nperseg=128, n_fft=256)with solution 1: psds, freqs = psd_welch(raw, n_fft=128, pad_to=256) |
|
ok fine with solution 2 with nperseg defaulting to None meaning use n_fft
so we have no deprecation.
|
|
n_fft > signal length would do zero-padding, while previously it would be set back to signal length. So there should be some deprecation info if nperseg=None and n_fft > signal length, right? Or do you suggest that n_fft > |
|
(eh, same problem, sorry) |
|
well calling welch on a signal that is less than nperseg is not really
doing welch
and should probably be not done. You should use mulit-tapers for such short
signals
|
Agreed. I think it's okay to raise an error if someone tries to do this. If it breaks some existing code and forces someone who really wants to do it to now be explicit about it, I think that's okay |
|
Ok, so if nperseg=None and n_fft > signal_length -> raise an error stating that if one really wants this nperseg has to specified. |
|
Sounds reasonable to me. This allows us to proceed with solution 2 (SciPy API), right? |
|
yes
|
|
I've added |
larsoner
left a comment
There was a problem hiding this comment.
I guess since we already have different underscores we might as well have n_per_seg. At least the meaning immediately translates to scipy
|
Just in case another option could be |
|
I think it's better to stay close to SciPy. |
|
Ok, I'll change it to |
|
Ready for review/merge from your end? |
|
Yes, it should be good for review/merge now. |
| """Helper to make sure n_fft, n_per_seg and n_overlap make sense.""" | ||
| n_per_seg = n_fft if n_per_seg is None or n_per_seg > n_fft else n_per_seg | ||
| n_per_seg = n if n_per_seg > n else n_per_seg | ||
| n_overlap = n_per_seg - 1 if n_overlap >= n_per_seg else n_overlap |
There was a problem hiding this comment.
Maybe we covered this already, but this should probably just be an error, no?
There was a problem hiding this comment.
that's the previous behavior, I can change it to an error if you prefer.
There was a problem hiding this comment.
I'd rather have people be explicit if they want this behavior. This looks like it's silent-errant-behavior prone to me
| it is the smoother are the PSDs. The default value is 256. | ||
| If ``n_fft > len(inst.times)``, it will be adjusted down to | ||
| ``len(inst.times)``. | ||
| The length of FFT used. If n_per_seg is None n_fft sets the length of the |
There was a problem hiding this comment.
This is good enough:
The length of FFT used, must be ``>= n_per_seg`` (default: 256).
The segments will be zero-padded if ``n_fft > n_per_seg``.
There was a problem hiding this comment.
Yes, that's better, thanks!
| n_per_seg : int | None | ||
| Length of each Welch segment. The smaller it is with respect to the | ||
| signal length the smoother are the PSDs. Defaults to None, which sets | ||
| n_per_seg equal to n_fft. If n_per_seg is smaller than n_fft, each window |
There was a problem hiding this comment.
If you add the bits about padding above, they don't need to be here.
| if n_per_seg is None and n_fft > n_times: | ||
| raise ValueError('If n_per_seg is None n_fft is not allowed to be >' | ||
| ' n_times. If you want zero-padding, you have to set' | ||
| ' n_per_seg to relevant length.') |
There was a problem hiding this comment.
this check should go in _check_nfft
There was a problem hiding this comment.
ok, I'll move it there
|
Codecov is confused, it says "Absolute coverage decreased by -12.11% ..." and lists many tests as not tested... |
|
and Travis did not run |
|
@agramfort +1 for merge from you? looks like comments have been addressed @mmagnuski sometimes there is a delay, everything looks good now. +1 for merge from me |
| n_per_seg = n if n_per_seg > n else n_per_seg | ||
| if n_overlap >= n_per_seg: | ||
| msg = ('n_overlap cannot be greater than n_per_seg (or n_fft). Got ' | ||
| 'n_overlap of {} while n_per_seg is {}.') |
There was a problem hiding this comment.
FYI for future reference by convention we 1. don't split these across multiple vars/commands and 2. use/prefer % (even though there is talk of deprecation)
There was a problem hiding this comment.
Thanks for stating what the preference is - I think I saw this using format and a separate var with text) a few times in Jona's code so I thought it is ok. I'll remember now. :) I can change it here too.
There was a problem hiding this comment.
Sometimes .format is a lot more convenient (e.g., mne/datasets/utils.py I think?), and we do use it in those cases. But it's not a big deal, so don't bother unless someone else has changes they want you to make
|
please update what's new and then +1 for MRG |
|
yes ok for me
|
fb5db5f to
48ac066
Compare
|
@Eric89GXL the errors we added make a test fail: Previous behavior was to set n_fft equal to signal length if n_fft > signal length so I'll change this test by setting n_fft to signal length. |
|
All builds are green except one: |
|
Yep, failure is unrelated. Thanks @mmagnuski ! |
|
Thanks @Eric89GXL @agramfort 🚀 |
|
Onto #4000 :) |
Fixes #3621
by adding
paddingkwarg topsd_welch.I'll add an entry to whats_new once comments are resolved and CIs green.