Repository: mne-tools/mne-python

Issue:
mismatch between automated coreg and GUI
I tested out the new automated coreg today, and got different results from the old GUI:

#### GUI
![coreg-gui](https://user-images.githubusercontent.com/1810515/129969186-c7f17083-7860-4dc1-9487-97493922fb24.png)

I got this by setting fiducial point matching to 'mri fiducials' in the fit settings, then clicking "fit icp" under trans/rot (no scaling). 


#### automated + plot_alignment
![plot-alignment](https://user-images.githubusercontent.com/1810515/129969196-80555ef8-0848-48d8-9f58-2a8bbcd63074.png)

code that led to the plot_alignment figure:

```py
raw_fname = os.path.join(data_root, subject, 'raw_fif', f'{subject}_raw.fif')
info = mne.io.read_info(raw_fname)
coreg = mne.coreg.Coregistration(
    info, subject=surrogate, subjects_dir=subjects_dir)
coreg.set_scale_mode('3-axis')
coreg._icp_fid_match = 'matched'
coreg.fit_icp(n_iterations=30)
mne.viz.plot_alignment(
    info, coreg.trans, subject=surrogate, subjects_dir=subjects_dir,
    surfaces=dict(head=0.9), dig=True, mri_fiducials=True, meg=False)
```


Discussion:
It just occurred to me that the weights for points might be different. HPI for example might default to 1. in the class but 0. in the GUI. You can see these in "Fitting options".

I'm hoping that's the problem, because if it's something else, it's going to be harder to figure out. I would suggest first checking if the alignment provided by just the fiducial match works. Once that does, do one ICP iteration, and make sure that matches.

I'm not 100% sure if the "fiducial distances" reported in the GUI are changed based on whether matched vs nearest is selected. IIRC in the Coregistration method `coreg.compute_dig_mri_distances` (where I think the fids are the first three values returned, maybe?) it is probably always be nearest...
... I would also suggest starting out with something easier like `sample` and not doing any scaling. So no scaling, fiducials fit, then one ICP iteration. Once these match to numerical precision, reprocess `sample` using the same steps, but with 3-axis enabled. Something like that that should help isolate the likely cause of the mismatch

Resolving patch:
diff --git a/mne/coreg.py b/mne/coreg.py
--- a/mne/coreg.py
+++ b/mne/coreg.py
@@ -956,8 +956,15 @@ def scale_mri(subject_from, subject_to, scale, overwrite=False,
 
     See Also
     --------
+    scale_bem : Add a scaled BEM to a scaled MRI.
     scale_labels : Add labels to a scaled MRI.
     scale_source_space : Add a source space to a scaled MRI.
+
+    Notes
+    -----
+    This function will automatically call :func:`scale_bem`,
+    :func:`scale_labels`, and :func:`scale_source_space` based on expected
+    filename patterns in the subject directory.
     """
     subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
     paths = _find_mri_paths(subject_from, skip_fiducials, subjects_dir)
@@ -1297,6 +1304,10 @@ class Coregistration(object):
     trans : instance of Transform
         MRI<->Head coordinate transformation.
 
+    See Also
+    --------
+    mne.scale_mri
+
     Notes
     -----
     Internal computation quantities parameters are in the following units:
@@ -1304,6 +1315,9 @@ class Coregistration(object):
     - rotation are in radians
     - translation are in m
     - scale are in scale proportion
+
+    If using a scale mode, the :func:`~mne.scale_mri` should be used
+    to create a surrogate MRI subject with the proper scale factors.
     """
 
     def __init__(self, info, subject, subjects_dir=None, fiducials='auto'):
@@ -1658,6 +1672,17 @@ def _log_dig_mri_distance(self, prefix):
         logger.info(f'{prefix} median distance: '
                     f'{np.median(errs_nearest * 1000):6.2f} mm')
 
+    @property
+    def scale(self):
+        """Get the current scale factor.
+
+        Returns
+        -------
+        scale : ndarray, shape (3,)
+            The scale factors.
+        """
+        return self._scale.copy()
+
     @verbose
     def fit_fiducials(self, lpa_weight=1., nasion_weight=10., rpa_weight=1.,
                       verbose=None):
diff --git a/tutorials/forward/25_automated_coreg.py b/tutorials/forward/25_automated_coreg.py
--- a/tutorials/forward/25_automated_coreg.py
+++ b/tutorials/forward/25_automated_coreg.py
@@ -80,3 +80,13 @@
     f"Distance between HSP and MRI (mean/min/max):\n{np.mean(dists):.2f} mm "
     f"/ {np.min(dists):.2f} mm / {np.max(dists):.2f} mm"
 )
+
+# %%
+# .. note:: The :class:`mne.coreg.Coregistration` class has the ability to
+#           compute MRI scale factors using
+#           :meth:`~mne.coreg.Coregistration.set_scale_mode` that is useful
+#           for creating surrogate MRI subjects, i.e., using a template MRI
+#           (such as one from :func:`mne.datasets.fetch_infant_template`)
+#           matched to a subject's head digitization. When scaling is desired,
+#           a scaled surrogate MRI should be created using
+#           :func:`mne.scale_mri`.


Test patch:
diff --git a/mne/tests/test_coreg.py b/mne/tests/test_coreg.py
--- a/mne/tests/test_coreg.py
+++ b/mne/tests/test_coreg.py
@@ -19,12 +19,16 @@
                        coregister_fiducials, get_mni_fiducials, Coregistration)
 from mne.io import read_fiducials, read_info
 from mne.io.constants import FIFF
-from mne.utils import requires_nibabel, modified_env, check_version
+from mne.utils import (requires_nibabel, modified_env, check_version,
+                       catch_logging)
 from mne.source_space import write_source_spaces
 
 data_path = testing.data_path(download=False)
 subjects_dir = os.path.join(data_path, 'subjects')
 fid_fname = op.join(subjects_dir, 'sample', 'bem', 'sample-fiducials.fif')
+raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif')
+trans_fname = op.join(data_path, 'MEG', 'sample',
+                      'sample_audvis_trunc-trans.fif')
 
 
 @pytest.fixture
@@ -281,6 +285,7 @@ def test_get_mni_fiducials():
     assert (dists < 8).all(), dists
 
 
+@pytest.mark.slowtest
 @testing.requires_testing_data
 @pytest.mark.parametrize(
     'scale_mode,ref_scale,grow_hair,fiducials,fid_match', [
@@ -294,15 +299,11 @@ def test_get_mni_fiducials():
 def test_coregistration(scale_mode, ref_scale, grow_hair, fiducials,
                         fid_match):
     """Test automated coregistration."""
-    trans_fname = op.join(data_path, 'MEG', 'sample',
-                          'sample_audvis_trunc-trans.fif')
-    fname_raw = op.join(op.dirname(__file__), '..', 'io',
-                        'tests', 'data', 'test_raw.fif')
     subject = 'sample'
     if fiducials is None:
         fiducials, coord_frame = read_fiducials(fid_fname)
         assert coord_frame == FIFF.FIFFV_COORD_MRI
-    info = read_info(fname_raw)
+    info = read_info(raw_fname)
     for d in info['dig']:
         d['r'] = d['r'] * ref_scale
     trans = read_trans(trans_fname)
@@ -361,3 +362,84 @@ def test_coregistration(scale_mode, ref_scale, grow_hair, fiducials,
     assert_allclose(coreg._scale, ref_scale, atol=atol)
     coreg.reset()
     assert_allclose(coreg._parameters, default_params)
+
+
+@pytest.mark.slowtest
+@testing.requires_testing_data
+def test_coreg_class_gui_match():
+    """Test that using Coregistration matches mne coreg."""
+    fiducials, _ = read_fiducials(fid_fname)
+    info = read_info(raw_fname)
+    coreg = Coregistration(info, subject='sample', subjects_dir=subjects_dir,
+                           fiducials=fiducials)
+    assert_allclose(coreg.trans['trans'], np.eye(4), atol=1e-6)
+    # mne coreg -s sample -d subjects -f MEG/sample/sample_audvis_trunc_raw.fif
+    # then "Fit Fid.", Save... to get trans, read_trans:
+    want_trans = [
+        [9.99428809e-01, 2.94733196e-02, 1.65350307e-02, -8.76054692e-04],
+        [-1.92420650e-02, 8.98512006e-01, -4.38526988e-01, 9.39774036e-04],
+        [-2.77817696e-02, 4.37958330e-01, 8.98565888e-01, -8.29207990e-03],
+        [0, 0, 0, 1]]
+    coreg.set_fid_match('matched')
+    coreg.fit_fiducials(verbose=True)
+    assert_allclose(coreg.trans['trans'], want_trans, atol=1e-6)
+    # Set ICP iterations to one, click "Fit ICP"
+    want_trans = [
+        [9.99512792e-01, 2.80128177e-02, 1.37659665e-02, 6.08855276e-04],
+        [-1.91694051e-02, 8.98992002e-01, -4.37545270e-01, 9.66848747e-04],
+        [-2.46323701e-02, 4.37068194e-01, 8.99091005e-01, -1.44129358e-02],
+        [0, 0, 0, 1]]
+    coreg.fit_icp(1, verbose=True)
+    assert_allclose(coreg.trans['trans'], want_trans, atol=1e-6)
+    # Set ICP iterations to 20, click "Fit ICP"
+    with catch_logging() as log:
+        coreg.fit_icp(20, verbose=True)
+    log = log.getvalue()
+    want_trans = [
+        [9.97582495e-01, 2.12266613e-02, 6.61706254e-02, -5.07694029e-04],
+        [1.81089472e-02, 8.39900672e-01, -5.42437911e-01, 7.81218382e-03],
+        [-6.70908988e-02, 5.42324841e-01, 8.37485850e-01, -2.50057746e-02],
+        [0, 0, 0, 1]]
+    assert_allclose(coreg.trans['trans'], want_trans, atol=1e-6)
+    assert 'ICP 19' in log
+    assert 'ICP 20' not in log  # converged on 19
+    # Change to uniform scale mode, "Fit Fiducials" in scale UI
+    coreg.set_scale_mode('uniform')
+    coreg.fit_fiducials()
+    want_scale = [0.975] * 3
+    want_trans = [
+        [9.99428809e-01, 2.94733196e-02, 1.65350307e-02, -9.25998494e-04],
+        [-1.92420650e-02, 8.98512006e-01, -4.38526988e-01, -1.03350170e-03],
+        [-2.77817696e-02, 4.37958330e-01, 8.98565888e-01, -9.03170835e-03],
+        [0, 0, 0, 1]]
+    assert_allclose(coreg.scale, want_scale, atol=5e-4)
+    assert_allclose(coreg.trans['trans'], want_trans, atol=1e-6)
+    # Click "Fit ICP" in scale UI
+    with catch_logging() as log:
+        coreg.fit_icp(20, verbose=True)
+    log = log.getvalue()
+    assert 'ICP 18' in log
+    assert 'ICP 19' not in log
+    want_scale = [1.036] * 3
+    want_trans = [
+        [9.98992383e-01, 1.72388796e-02, 4.14364934e-02, 6.19427126e-04],
+        [6.80460501e-03, 8.54430079e-01, -5.19521892e-01, 5.58008114e-03],
+        [-4.43605632e-02, 5.19280374e-01, 8.53451848e-01, -2.03358755e-02],
+        [0, 0, 0, 1]]
+    assert_allclose(coreg.scale, want_scale, atol=5e-4)
+    assert_allclose(coreg.trans['trans'], want_trans, atol=1e-6)
+    # Change scale mode to 3-axis, click "Fit ICP" in scale UI
+    coreg.set_scale_mode('3-axis')
+    with catch_logging() as log:
+        coreg.fit_icp(20, verbose=True)
+    log = log.getvalue()
+    assert 'ICP  7' in log
+    assert 'ICP  8' not in log
+    want_scale = [1.025, 1.010, 1.121]
+    want_trans = [
+        [9.98387098e-01, 2.04762165e-02, 5.29526398e-02, 4.97257097e-05],
+        [1.13287698e-02, 8.42087150e-01, -5.39222538e-01, 7.09863892e-03],
+        [-5.56319728e-02, 5.38952649e-01, 8.40496957e-01, -1.46372067e-02],
+        [0, 0, 0, 1]]
+    assert_allclose(coreg.scale, want_scale, atol=5e-4)
+    assert_allclose(coreg.trans['trans'], want_trans, atol=1e-6)