Repository: mne-tools/mne-python

Issue:
ENH: Improve usability with scaled MRIs
I have some data for which a scaled infant MRI was used (but the same arguments go for scaling fsaverage for example). If I do a dipole fit, I get coordinates in the subject's head frame. Then `mri_pos = mne.head_to_mri(head_pos, trans)` gives me the MRI position in the *scaled* subject's MRI. **It would be nice to get it back in the original/surrogate subject's MRI frame** for example to use with `plot_dipole_locations`.

I see at least two potential ways:

1. New param with default `unscale=False` where if you pass `True`:
    ```
    mri_pos_orig = mne.head_to_mri(head_pos, subject, trans, unscale=True)
    ```
    It will look for the `mri scaling parameters.cfg` (or whatever it's called) for `subject` and apply the opposite transform to get it back to the original surrogate MRI's position (and raise an error if the `.cfg` is not found).
2. New function `mne.mri_to_unscaled_mri(mri_pos, subject)` so the use would be:
    ```
    mri_pos_orig = mne.mri_to_unscaled_mri(mne.head_to_mri(head_pos, subject, trans), subject)
    ```

I think I lean toward (1) since it is shorter, and a little bit less redundant / only need to specify `subject` once.


Discussion:
option 1 works for me. 👍 

Resolving patch:
diff --git a/mne/_freesurfer.py b/mne/_freesurfer.py
--- a/mne/_freesurfer.py
+++ b/mne/_freesurfer.py
@@ -16,7 +16,7 @@
                          _ensure_trans, read_ras_mni_t, Transform)
 from .surface import read_surface, _read_mri_surface
 from .utils import (verbose, _validate_type, _check_fname, _check_option,
-                    get_subjects_dir, _require_version, logger)
+                    get_subjects_dir, _require_version, logger, warn)
 
 
 def _check_subject_dir(subject, subjects_dir):
@@ -253,13 +253,10 @@ def get_volume_labels_from_aseg(mgz_fname, return_colors=False,
 
 
 @verbose
-def head_to_mri(pos, subject, mri_head_t, subjects_dir=None,
-                verbose=None):
+def head_to_mri(pos, subject, mri_head_t, subjects_dir=None, *,
+                kind=None, unscale=False, verbose=None):
     """Convert pos from head coordinate system to MRI ones.
 
-    This function converts to MRI RAS coordinates and not to surface
-    RAS.
-
     Parameters
     ----------
     pos : array, shape (n_pos, 3)
@@ -268,6 +265,18 @@ def head_to_mri(pos, subject, mri_head_t, subjects_dir=None,
     mri_head_t : instance of Transform
         MRI<->Head coordinate transformation.
     %(subjects_dir)s
+    kind : str
+        The  MRI coordinate frame kind, can be ``'ras'`` (default in 1.2) to
+        use MRI RAS (scanner RAS) or ``'mri'`` (default in 1.3) for
+        FreeSurfer surface RAS.
+
+        .. versionadded:: 1.2
+    unscale : bool
+        For surrogate MRIs (e.g., scaled using ``mne coreg``), if True
+        (default False), use the MRI scaling parameters to obtain points in
+        the original/surrogate subject's MRI space.
+
+        .. versionadded:: 1.2
     %(verbose)s
 
     Returns
@@ -279,12 +288,30 @@ def head_to_mri(pos, subject, mri_head_t, subjects_dir=None,
     -----
     This function requires nibabel.
     """
+    from .coreg import read_mri_cfg
+    if kind is None:
+        warn('kind defaults to "ras" in 1.2 and will change to "mri" in 1.3, '
+             'set it explicitly to avoid this warning', FutureWarning)
+        kind = 'ras'
+    _validate_type(kind, str, 'kind')
+    _check_option('kind', kind, ('ras', 'mri'))
     subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
     t1_fname = op.join(subjects_dir, subject, 'mri', 'T1.mgz')
     head_mri_t = _ensure_trans(mri_head_t, 'head', 'mri')
-    _, _, mri_ras_t, _, _ = _read_mri_info(t1_fname)
-    head_ras_t = combine_transforms(head_mri_t, mri_ras_t, 'head', 'ras')
-    return 1e3 * apply_trans(head_ras_t, pos)  # mm
+    if kind == 'ras':
+        _, _, mri_ras_t, _, _ = _read_mri_info(t1_fname)
+        head_ras_t = combine_transforms(head_mri_t, mri_ras_t, 'head', 'ras')
+        head_dest_t = head_ras_t
+    else:
+        assert kind == 'mri'
+        head_dest_t = head_mri_t
+    pos_dest = apply_trans(head_dest_t, pos)
+    # unscale if requested
+    if unscale:
+        params = read_mri_cfg(subject, subjects_dir)
+        pos_dest *= params['scale']
+    pos_dest *= 1e3  # mm
+    return pos_dest
 
 
 ##############################################################################
diff --git a/mne/coreg.py b/mne/coreg.py
--- a/mne/coreg.py
+++ b/mne/coreg.py
@@ -26,6 +26,7 @@
 # namespace, too)
 from ._freesurfer import (_read_mri_info, get_mni_fiducials,  # noqa: F401
                           estimate_head_mri_t)  # noqa: F401
+from ._freesurfer import _import_nibabel
 from .label import read_label, Label
 from .source_space import (add_source_space_distances, read_source_spaces,  # noqa: E501,F401
                            write_source_spaces)
@@ -1170,12 +1171,7 @@ def _scale_mri(subject_to, mri_fname, subject_from, scale, subjects_dir):
     """Scale an MRI by setting its affine."""
     subjects_dir, subject_from, scale, _ = _scale_params(
         subject_to, subject_from, scale, subjects_dir)
-
-    if not has_nibabel():
-        warn('Skipping MRI scaling for %s, please install nibabel')
-        return
-
-    import nibabel
+    nibabel = _import_nibabel('scale an MRI')
     fname_from = op.join(mri_dirname.format(
         subjects_dir=subjects_dir, subject=subject_from), mri_fname)
     fname_to = op.join(mri_dirname.format(
diff --git a/mne/dipole.py b/mne/dipole.py
--- a/mne/dipole.py
+++ b/mne/dipole.py
@@ -241,7 +241,8 @@ def to_mri(self, subject, trans, subjects_dir=None,
         """
         mri_head_t, trans = _get_trans(trans)
         return head_to_mri(self.pos, subject, mri_head_t,
-                           subjects_dir=subjects_dir, verbose=verbose)
+                           subjects_dir=subjects_dir, verbose=verbose,
+                           kind='mri')
 
     @verbose
     def to_volume_labels(self, trans, subject='fsaverage', aseg='aparc+aseg',


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
@@ -236,6 +236,23 @@ def test_scale_mri_xfm(tmp_path, few_surfaces, subjects_dir_tmp_few):
         pos_head = apply_trans(invert_transform(trans), pos_mri)
         pos_mni = mne.head_to_mni(pos_head, subject_to, trans, tempdir)
         assert_allclose(pos_mni, pos_mni_from, atol=1e-3)
+        # another way
+        pos_mri_from_2 = mne.head_to_mri(
+            pos_head_from, subject_from, trans, tempdir, kind='mri')
+        with pytest.warns(FutureWarning, match='kind defaults to'):
+            pos_mri_from_ras = mne.head_to_mri(
+                pos_head_from, subject_from, trans, tempdir)
+        mri_eq_ras = np.allclose(pos_mri_from_2, pos_mri_from_ras, atol=1e-1)
+        if subject_from == 'fsaverage':
+            assert mri_eq_ras  # fsaverage is special this way
+        else:
+            assert not mri_eq_ras  # sample is not
+        assert_allclose(pos_mri_from_2, 1e3 * pos_mri_from,
+                        atol=1e-3)
+        with pytest.raises(OSError, match=r'parameters\.cfg'):
+            mne.head_to_mri(
+                pos_head_from, subject_from, trans, tempdir, unscale=True,
+                kind='mri')
 
 
 def test_fit_matched_points():