Skip to content

Commit

Permalink
MRG: Allow removal of active projectors if channels they applied to h…
Browse files Browse the repository at this point in the history
…ave meanwhile been dropped (#8003)

* Allow removal of active proj if chs were dropped

In cases where the channels that an applied projector
applies to have been removed from the data (pick
after apply_proj() call), it should besafe to allow
users to remove the projector. Currently we disallow
deletion of any "active" (applied) projectors, so this
commit adds more flexibility there.

* Typo

* Fix reference

* Test that projector got removed
  • Loading branch information
hoechenberger committed Jul 13, 2020
1 parent d839a6c commit b1dfd53
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 4 deletions.
2 changes: 2 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ Changelog

- BrainVision data format files are now parsed for EEG impedance values in :func:`mne.io.read_raw_brainvision` and provided as a ``.impedances`` attribute of ``raw`` by `Stefan Appelhoff`_ and `Jan Sedivy`_

- Deletion of applied (active) projectors via `~mne.io.Raw.del_proj`, `~mne.Epochs.del_proj`, and `~mne.Evoked.del_proj` is now possible if the channels the to-be-removed projector applies to are not present in the data anymore by `Richard Höchenberger`_

Bug
~~~

Expand Down
12 changes: 12 additions & 0 deletions mne/io/fiff/tests/test_raw_fiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,18 @@ def test_proj(tmpdir):
raw.apply_proj()
assert_allclose(raw[:, :][0][:1], raw[0, :][0])

# Read file again, apply proj, pick all channels one proj did NOT apply to;
# then try to delete this proj, which now exclusively refers to channels
# which are not present in the data anymore.
raw = read_raw_fif(fif_fname).apply_proj()
del_proj_idx = 0
picks = list(set(raw.ch_names) -
set(raw.info['projs'][del_proj_idx]['data']['col_names']))
raw.pick(picks)
n_projs = len(raw.info['projs'])
raw.del_proj(del_proj_idx)
assert len(raw.info['projs']) == n_projs - 1


@testing.requires_testing_data
@pytest.mark.parametrize('preload', [False, True, 'memmap.dat'])
Expand Down
17 changes: 13 additions & 4 deletions mne/io/proj.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ def del_proj(self, idx='all'):
"""Remove SSP projection vector.
.. note:: The projection vector can only be removed if it is inactive
(has not been applied to the data).
(has not been applied to the data), unless the channels it
was applied to no longer exist in the data.
Parameters
----------
Expand All @@ -238,9 +239,17 @@ def del_proj(self, idx='all'):
if isinstance(idx, str) and idx == 'all':
idx = list(range(len(self.info['projs'])))
idx = np.atleast_1d(np.array(idx, int)).ravel()
if any(self.info['projs'][ii]['active'] for ii in idx):
raise ValueError('Cannot remove projectors that have already '
'been applied')

for ii in idx:
proj = self.info['projs'][ii]
if (proj['active'] and
set(self.info['ch_names']) &
set(proj['data']['col_names'])):
msg = (f'Cannot remove projector that has already been '
f'applied, unless you first remove all channels it '
f'applies to. The problematic projector is: {proj}')
raise ValueError(msg)

keep = np.ones(len(self.info['projs']))
keep[idx] = False # works with negative indexing and does checks
self.info['projs'] = [p for p, k in zip(self.info['projs'], keep) if k]
Expand Down

0 comments on commit b1dfd53

Please sign in to comment.