Repository: mne-tools/mne-python

Issue:
maxwell filter ctc and cal file
I am working with vectoview data where a sensor triplet was turned off. `maxwell_filter` errors out because the number of channels in the fif file do not match the numbers in the fine calibration and ctc files. Perhaps preserving the behavior of Maxfilter program and treating the system as if only N channels as per the number in the recorded dataset?  



Discussion:
It should probably be able to sub-select channels. But I'm not sure if the cross-talk matrix needs to be modified somehow to compensate. @staulu do you know?

Can you share the file, possibly using `raw.anonymize()` or `mne_anonymize`? You can try `raw.anonymize().crop(0, 1).save('test_missing_raw.fif', buffer_size_sec=1.)`, it might do the trick.

Find file [here](https://www.dropbox.com/s/v8s74kwks06zv3s/test_missing_raw.fif?dl=0). 
I tried rm lines in cal file and that worked but @staulu mentioned that modifying the ctc file "would be more tricky" :)  I don't know if modifying the files themselves was actually a good idea from the python end. He suggested the files be used during maxwell filtering as though the system is 30x instead of 306 channels, and also that this could be a regular occurrence and best to preserve the MF behavior. 

To be exact, the missing channels are physically there, even if they have been excluded from the channel list. This means that they do slightly interfere with some of the neighboring channels through mutual inductance etc. Therefore, their contribution should be taken into account by the CTC matrix. But if we only have data available from, e.g., 303 channels, the next best thing is to exclude the cross-talk columns and rows corresponding to the missing channels. I don't expect this to be a big problem if there are just a few missing channels. 

Samu

Sent from my iPhone

> On Feb 11, 2016, at 13:51, Kambiz notifications@github.com wrote:
> 
> Find file here. 
> I tried rm lines in cal file and that worked but @staulu mentioned that modifying the ctc file "would be more tricky" :) Samu did suggest the files be used during maxwell filtering as though the system is 30x instead of 306 channels, and also that this could be a regular occurrence and best to preserve the MF behavior.
> 
> —
> Reply to this email directly or view it on GitHub.

Ahh good. That's an easy solution to implement :)


Resolving patch:
diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py
--- a/mne/preprocessing/maxwell.py
+++ b/mne/preprocessing/maxwell.py
@@ -279,8 +279,14 @@ def maxwell_filter(raw, origin='auto', int_order=8, ext_order=3,
     if cross_talk is not None:
         sss_ctc = _read_ctc(cross_talk)
         ctc_chs = sss_ctc['proj_items_chs']
-        if set(info['ch_names'][p] for p in meg_picks) != set(ctc_chs):
-            raise RuntimeError('ctc channels and raw channels do not match')
+        meg_ch_names = [info['ch_names'][p] for p in meg_picks]
+        missing = sorted(list(set(meg_ch_names) - set(ctc_chs)))
+        if len(missing) != 0:
+            raise RuntimeError('Missing MEG channels in cross-talk matrix:\n%s'
+                               % missing)
+        missing = sorted(list(set(ctc_chs) - set(meg_ch_names)))
+        if len(missing) > 0:
+            warn('Not all cross-talk channels in raw:\n%s' % missing)
         ctc_picks = pick_channels(ctc_chs,
                                   [info['ch_names'][c] for c in good_picks])
         ctc = sss_ctc['decoupler'][ctc_picks][:, ctc_picks]
@@ -1580,11 +1586,13 @@ def _update_sensor_geometry(info, fine_cal, head_frame, ignore_ref):
     meg_info = pick_info(info, pick_types(info, meg=True, exclude=[]))
     clean_meg_names = _clean_names(meg_info['ch_names'],
                                    remove_whitespace=True)
-    order = pick_channels([c['ch_name'] for c in cal_chs], clean_meg_names)
-    if not (len(cal_chs) == meg_info['nchan'] == len(order)):
-        raise RuntimeError('Number of channels in fine calibration file (%i) '
-                           'does not equal number of channels in info (%i)' %
-                           (len(cal_chs), meg_info['nchan']))
+    cal_names = [c['ch_name'] for c in cal_chs]
+    order = pick_channels(cal_names, clean_meg_names)
+    if meg_info['nchan'] != len(order):
+        raise RuntimeError('Not all MEG channels found in fine calibration '
+                           'file, missing:\n%s'
+                           % sorted(list(set(clean_meg_names) -
+                                         set(cal_names))))
     # ensure they're ordered like our data
     cal_chs = [cal_chs[ii] for ii in order]
 


Test patch:
diff --git a/mne/preprocessing/tests/test_maxwell.py b/mne/preprocessing/tests/test_maxwell.py
--- a/mne/preprocessing/tests/test_maxwell.py
+++ b/mne/preprocessing/tests/test_maxwell.py
@@ -76,6 +76,9 @@
 sample_fname = op.join(data_path, 'MEG', 'sample',
                        'sample_audvis_trunc_raw.fif')
 
+io_dir = op.join(op.dirname(__file__), '..', '..', 'io')
+fname_ctf_raw = op.join(io_dir, 'tests', 'data', 'test_ctf_comp_raw.fif')
+
 int_order, ext_order = 8, 3
 mf_head_origin = (0., 0., 0.04)
 mf_meg_origin = (0., 0.013, -0.006)
@@ -174,8 +177,6 @@ def test_movement_compensation():
 def test_other_systems():
     """Test Maxwell filtering on KIT, BTI, and CTF files
     """
-    io_dir = op.join(op.dirname(__file__), '..', '..', 'io')
-
     # KIT
     kit_dir = op.join(io_dir, 'kit', 'tests', 'data')
     sqd_path = op.join(kit_dir, 'test.sqd')
@@ -230,7 +231,6 @@ def test_other_systems():
     _assert_n_free(raw_sss, 70)
 
     # CTF
-    fname_ctf_raw = op.join(io_dir, 'tests', 'data', 'test_ctf_comp_raw.fif')
     raw_ctf = Raw(fname_ctf_raw, compensation=2)
     assert_raises(RuntimeError, maxwell_filter, raw_ctf)  # compensated
     raw_ctf = Raw(fname_ctf_raw)
@@ -546,6 +546,9 @@ def test_fine_calibration():
                                 origin=mf_head_origin, regularize=None,
                                 bad_condition='ignore')
     assert_meg_snr(raw_sss_3D, sss_fine_cal, 1.0, 6.)
+    raw_ctf = Raw(fname_ctf_raw)
+    assert_raises(RuntimeError, maxwell_filter, raw_ctf, origin=(0., 0., 0.04),
+                  calibration=fine_cal_fname)
 
 
 @slow_test
@@ -605,6 +608,22 @@ def test_cross_talk():
     mf_ctc = sss_ctc.info['proc_history'][0]['max_info']['sss_ctc']
     del mf_ctc['block_id']  # we don't write this
     assert_equal(object_diff(py_ctc, mf_ctc), '')
+    raw_ctf = Raw(fname_ctf_raw)
+    assert_raises(ValueError, maxwell_filter, raw_ctf)  # cannot fit headshape
+    raw_sss = maxwell_filter(raw_ctf, origin=(0., 0., 0.04))
+    _assert_n_free(raw_sss, 68)
+    raw_sss = maxwell_filter(raw_ctf, origin=(0., 0., 0.04), ignore_ref=True)
+    _assert_n_free(raw_sss, 70)
+    raw_missing = raw.crop(0, 0.1, copy=True).load_data().pick_channels(
+        [raw.ch_names[pi] for pi in pick_types(raw.info, meg=True,
+                                               exclude=())[3:]])
+    with warnings.catch_warnings(record=True) as w:
+        maxwell_filter(raw_missing, cross_talk=ctc_fname)
+    assert_equal(len(w), 1)
+    assert_true('Not all cross-talk channels in raw' in str(w[0].message))
+    # MEG channels not in cross-talk
+    assert_raises(RuntimeError, maxwell_filter, raw_ctf, origin=(0., 0., 0.04),
+                  cross_talk=ctc_fname)
 
 
 @testing.requires_testing_data