Skip to content

[MRG+2] ENH: padding kwarg for psd_welch - #4003

Merged
larsoner merged 11 commits into
mne-tools:masterfrom
mmagnuski:welch_padding
Feb 24, 2017
Merged

[MRG+2] ENH: padding kwarg for psd_welch#4003
larsoner merged 11 commits into
mne-tools:masterfrom
mmagnuski:welch_padding

Conversation

@mmagnuski

@mmagnuski mmagnuski commented Feb 18, 2017

Copy link
Copy Markdown
Member

Fixes #3621
by adding padding kwarg to psd_welch.
I'll add an entry to whats_new once comments are resolved and CIs green.

@codecov-io

codecov-io commented Feb 18, 2017

Copy link
Copy Markdown

Codecov Report

Merging #4003 into master will decrease coverage by -12.12%.
The diff coverage is 100%.

@@             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       +99

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1182668...fb5db5f. Read the comment docs.

Comment thread mne/time_frequency/psd.py Outdated
"""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, :]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't like this. nfft and padding have different meanings.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mmagnuski can you summarize the new logic? You add a padding parameter which can override the n_fft if it is not None?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

@mmagnuski

Copy link
Copy Markdown
Member Author

Or not - depending on the wording of the docs and your opinion. :)
We can pass nfft to scipys nfft and add nperseg kwarg.

@dengemann

Copy link
Copy Markdown
Member

@mmagnuski I'm trying to better understand a) the motivation for this b) how it changes the exisiting behavior with regard to n_fft. Maybe you want to share your insights :)

@mmagnuski

Copy link
Copy Markdown
Member Author

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.
Segment length is termed n_fft in mne and nperseg in scipy. Mne naming seems to stem from the fact that there was no ability to have zero-padding, so segment length was the same as n_fft.

Introducing control over padding requires either:

  • adding nperseg kwarg that defines segment length, and make n_fft control padding when n_fft > nperseg. But because previously n_fft was closer in meaning to nperseg it was adjusted down to len(inst.times) when n_fft > len(inst.times). So this kind of change would rather require deprecation.
  • another option is to add padding or pad_to, as done in this PR. A separate kwarg that controls padding, while n_fft controls segment length. Padding takes place only when padding > n_fft. The downside here is that n_fft works as nperseg which may be conterintuitive.

I also cover the logic proposed here in my previous comments - in case you didn't see all of them.
And I actually would prefer adding nperseg instead of padding - but this requires some kind of deprecation (n_fft >signal_length would do padding).

@larsoner

larsoner commented Feb 19, 2017 via email

Copy link
Copy Markdown
Member

@agramfort

agramfort commented Feb 19, 2017 via email

Copy link
Copy Markdown
Member

@mmagnuski

Copy link
Copy Markdown
Member Author

@agramfort
Yeah, I understand the problem with semantic mismatch. What about solution 2?

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

@mmagnuski mmagnuski closed this Feb 19, 2017
@mmagnuski

Copy link
Copy Markdown
Member Author

sorry, I'm on my phone, pushed wrong button by accident.
...the person who submitted #3621.

@mmagnuski mmagnuski reopened this Feb 19, 2017
@agramfort

agramfort commented Feb 19, 2017 via email

Copy link
Copy Markdown
Member

@mmagnuski

Copy link
Copy Markdown
Member Author

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)

@agramfort

agramfort commented Feb 20, 2017 via email

Copy link
Copy Markdown
Member

@mmagnuski

Copy link
Copy Markdown
Member Author

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 >

@mmagnuski mmagnuski closed this Feb 20, 2017
@mmagnuski

Copy link
Copy Markdown
Member Author

(eh, same problem, sorry)
n_fft > signal length should not do zero padding when nperseg=None?

@mmagnuski mmagnuski reopened this Feb 20, 2017
@agramfort

agramfort commented Feb 20, 2017 via email

Copy link
Copy Markdown
Member

@larsoner

Copy link
Copy Markdown
Member

well calling welch on a signal that is less than nperseg is not really doing welch and should probably be not done.

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

@mmagnuski

Copy link
Copy Markdown
Member Author

Ok, so if nperseg=None and n_fft > signal_length -> raise an error stating that if one really wants this nperseg has to specified.

@larsoner

Copy link
Copy Markdown
Member

Sounds reasonable to me. This allows us to proceed with solution 2 (SciPy API), right?

@agramfort

agramfort commented Feb 20, 2017 via email

Copy link
Copy Markdown
Member

@mmagnuski

Copy link
Copy Markdown
Member Author

I've added nperseg kwarg, but everywhere in mne there is rather n_something than nsomething. For example - psd_welch has n_fft, not nfft. So n_per_seg might be more consistent with the rest of naming (although I don't like two underscores there). I've also changed the docstring a little, so take a look if you like the changes.

@larsoner larsoner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess since we already have different underscores we might as well have n_per_seg. At least the meaning immediately translates to scipy

@mmagnuski

Copy link
Copy Markdown
Member Author

Just in case another option could be segment_len (or maybe n_segment) or window_len (etc.)

@larsoner

Copy link
Copy Markdown
Member

I think it's better to stay close to SciPy. n_per_seg seems like the right compromise between equivalence with SciPy (which we can't get fully anyway anymore) and our internal style practices

@mmagnuski

Copy link
Copy Markdown
Member Author

Ok, I'll change it to n_per_seg then.

@larsoner

Copy link
Copy Markdown
Member

Ready for review/merge from your end?

@mmagnuski

Copy link
Copy Markdown
Member Author

Yes, it should be good for review/merge now.

@larsoner larsoner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Otherwise LGTM

Comment thread mne/time_frequency/psd.py Outdated
"""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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe we covered this already, but this should probably just be an error, no?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

that's the previous behavior, I can change it to an error if you prefer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd rather have people be explicit if they want this behavior. This looks like it's silent-errant-behavior prone to me

Comment thread mne/time_frequency/psd.py Outdated
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, that's better, thanks!

Comment thread mne/time_frequency/psd.py Outdated
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you add the bits about padding above, they don't need to be here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok

Comment thread mne/time_frequency/psd.py Outdated
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.')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this check should go in _check_nfft

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok, I'll move it there

@mmagnuski

Copy link
Copy Markdown
Member Author

Codecov is confused, it says "Absolute coverage decreased by -12.11% ..." and lists many tests as not tested...

@mmagnuski

Copy link
Copy Markdown
Member Author

and Travis did not run

@larsoner

Copy link
Copy Markdown
Member

@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

@larsoner larsoner changed the title [MRG] ENH: padding kwarg for psd_welch [MRG+1] ENH: padding kwarg for psd_welch Feb 22, 2017
Comment thread mne/time_frequency/psd.py Outdated
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 {}.')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

@agramfort

Copy link
Copy Markdown
Member

please update what's new and then +1 for MRG

@agramfort

agramfort commented Feb 23, 2017 via email

Copy link
Copy Markdown
Member

@larsoner larsoner changed the title [MRG+1] ENH: padding kwarg for psd_welch [MRG+2] ENH: padding kwarg for psd_welch Feb 23, 2017
@mmagnuski

Copy link
Copy Markdown
Member Author

@Eric89GXL the errors we added make a test fail:

======================================================================
ERROR: Test creating raw from array.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/miniconda/envs/test/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/home/travis/miniconda/envs/test/lib/python2.7/site-packages/numpy/testing/decorators.py", line 147, in skipper_func
    return f(*args, **kwargs)
  File "/home/travis/build/mne-tools/mne-python/mne/io/array/tests/test_array.py", line 95, in test_array_raw
    raw2.plot_psd(tmax=np.inf, average=True, spatial_colors=False)
  File "<string>", line 2, in plot_psd
  File "/home/travis/build/mne-tools/mne-python/mne/utils.py", line 709, in verbose
    return function(*args, **kwargs)
  File "/home/travis/build/mne-tools/mne-python/mne/io/base.py", line 1638, in plot_psd
    spatial_colors=spatial_colors)
  File "<string>", line 2, in plot_raw_psd
  File "/home/travis/build/mne-tools/mne-python/mne/utils.py", line 709, in verbose
    return function(*args, **kwargs)
  File "/home/travis/build/mne-tools/mne-python/mne/viz/raw.py", line 598, in plot_raw_psd
    n_overlap=n_overlap, n_jobs=n_jobs)
  File "<string>", line 2, in psd_welch
  File "/home/travis/build/mne-tools/mne-python/mne/utils.py", line 709, in verbose
    return function(*args, **kwargs)
  File "/home/travis/build/mne-tools/mne-python/mne/time_frequency/psd.py", line 200, in psd_welch
    n_jobs=n_jobs, verbose=verbose)
  File "<string>", line 2, in psd_array_welch
  File "/home/travis/build/mne-tools/mne-python/mne/utils.py", line 709, in verbose
    return function(*args, **kwargs)
  File "/home/travis/build/mne-tools/mne-python/mne/time_frequency/psd.py", line 114, in psd_array_welch
    n_overlap)
  File "/home/travis/build/mne-tools/mne-python/mne/time_frequency/psd.py", line 26, in _check_nfft
    ' signal length is %d.') % (n_fft, n))
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. Got n_fft of 2048 while signal length is 1803.

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.

@mmagnuski

Copy link
Copy Markdown
Member Author

All builds are green except one:

======================================================================
FAIL: Test object size estimation.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/miniconda/envs/test/lib/python3.5/site-packages/nose/case.py", line 198, in runTest
    self.test(*self.arg)
  File "/home/travis/miniconda/envs/test/lib/python3.5/site-packages/mne-0.14.dev0-py3.5.egg/mne/tests/test_utils.py", line 111, in test_object_size
    msg='%s < %s < %s:\n%s' % (lower, size, upper, obj))
AssertionError: False is not true : 0 < 68 < 60:
foo
    'False is not true : 0 < 68 < 60:\nfoo' = self._formatMessage('False is not true : 0 < 68 < 60:\nfoo', "%s is not true" % safe_repr(False))
>>  raise self.failureException('False is not true : 0 < 68 < 60:\nfoo')

@larsoner
larsoner merged commit 9e4ac8a into mne-tools:master Feb 24, 2017
@larsoner

Copy link
Copy Markdown
Member

Yep, failure is unrelated. Thanks @mmagnuski !

@mmagnuski

Copy link
Copy Markdown
Member Author

Thanks @Eric89GXL @agramfort 🚀

@larsoner

Copy link
Copy Markdown
Member

Onto #4000 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

n_fft issue

5 participants