Repository: mne-tools/mne-python

Issue:
Dig points disappear under transparent skin surface using mne coreg GUI
### Description of the problem

Hi! Looks like there's something quirky going on with mne coreg. 

Additional points in a head digitization are disappearing from view when they sink beneath the skin surface, regardless of the opacity of the surface. While cHPI coils and EEG sensor locations persist (red & pink dots), additional dig points (white dots) disappear. 

### Steps to reproduce

```Python
1. Set your subjects dir path to point to the "subjects" directory of the MNE sample data set. 
2. In the command line, open `mne coreg`
3. In the "path to info" field, set "sample_audvis_raw.fif" from the mne-python MEG sample dataset as your digitization source. 
4. Using the translation buttons, decrease the y-translation value (hit the down arrow by "tY") until the digitization points in the back of the head cross the head surface.
```


### Link to data

_No response_

### Expected results

White dots should remain visible underneath the skin surface. 

### Actual results

![Screenshot from 2023-10-23 10-29-49](https://github.com/mne-tools/mne-python/assets/38704848/118b1c26-b8e4-4698-9d9f-3f3895b99b58)
![Screenshot from 2023-10-23 10-30-02](https://github.com/mne-tools/mne-python/assets/38704848/65ceed97-5846-44c5-96a2-a10ea815a854)


### Additional information

`>>> mne.sys_info()
Platform             Linux-6.2.0-34-generic-x86_64-with-glibc2.35
Python               3.11.5 | packaged by conda-forge | (main, Aug 27 2023, 03:34:09) [GCC 12.3.0]
Executable           /home/erica/miniconda3/envs/sprint_2023/bin/python
CPU                  x86_64 (24 cores)
Memory               62.7 GB

Core
├☑ mne               1.6.0.dev88+gb9e907667
├☑ numpy             1.26.0 (unknown linalg bindings (threadpoolctl module not found: No module named 'threadpoolctl'))
├☑ scipy             1.11.2
├☑ matplotlib        3.8.0 (backend=QtAgg)
├☑ pooch             1.7.0
└☑ jinja2            3.1.2

Numerical (optional)
├☑ nibabel           5.1.0
├☑ dipy              1.7.0
├☑ pandas            2.1.1
└☐ unavailable       sklearn, numba, nilearn, openmeeg, cupy

Visualization (optional)
├☑ pyvista           0.42.2 (OpenGL 4.6 (Core Profile) Mesa 23.0.4-0ubuntu1~22.04.1 via AMD Radeon RX 570 Series (polaris10, LLVM 15.0.7, DRM 3.49, 6.2.0-34-generic))
├☑ pyvistaqt         0.11.0
├☑ vtk               9.2.6
Warning: Ignoring XDG_SESSION_TYPE=wayland on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
├☑ qtpy              2.4.0 (PyQt5=5.15.2)
└☐ unavailable       ipympl, pyqtgraph, mne-qt-browser, ipywidgets, trame_client, trame_server, trame_vtk, trame_vuetify

Ecosystem (optional)
├☑ mne-bids          0.14.dev0
├☑ mne-connectivity  0.6.0dev0
└☐ unavailable       mne-nirs, mne-features, mne-icalabel, mne-bids-pipeline
`


Discussion:
Running this to try to rule some stuff out
```
MNE_COREG_HEAD_OPACITY=0.25 MNE_COREG_ADVANCED_RENDERING=false MNE_COREG_MARK_INSIDE=false mne coreg --subjects-dir=~/mne_data/MNE-sample-data/subjects -s sample -f ~/mne_data/MNE-sample-data/MEG/sample/sample_audvis_raw.fif --trans ~/mne_data/MNE-sample-data/MEG/sample/sample_audvis_raw-trans.fif
```
and then clicking "lock fiducials" and setting the EEG weight to zero (so the EEG electrodes disappear) it's still clear that many extra points are missing:

![Screenshot from 2023-10-23 14-05-34](https://github.com/mne-tools/mne-python/assets/2365790/daef91bf-dc3f-4529-9951-659fd18af4f4)


Will have to look into why this is the case... this is where the plotting happens if you want to look into it before I get a chance to in the coming days @nordme 

https://github.com/mne-tools/mne-python/blob/c54d229913c5fe1bdb7a76b70930f66a89601c95/mne/viz/_3d.py#L1280-L1332
Hi!! I have pulled the fix, but I'm still getting the disappearing points. 
![Screenshot from 2023-11-01 10-37-27](https://github.com/mne-tools/mne-python/assets/38704848/7e6b078a-db59-493d-b035-dbcf22e321d9)

Here's my mne sys_info output again: 
$ mne sys_info
Platform             Linux-6.2.0-35-generic-x86_64-with-glibc2.35
Python               3.11.5 | packaged by conda-forge | (main, Aug 27 2023, 03:34:09) [GCC 12.3.0]
Executable           /home/erica/miniconda3/envs/sprint_2023/bin/python3.1
CPU                  x86_64 (24 cores)
Memory               62.7 GB

Core
├☑ mne               1.6.0.dev88+gb9e907667
├☑ numpy             1.26.0 (unknown linalg bindings (threadpoolctl module not found: No module named 'threadpoolctl'))
├☑ scipy             1.11.2
├☑ matplotlib        3.8.0 (backend=QtAgg)
├☑ pooch             1.7.0
└☑ jinja2            3.1.2

Numerical (optional)
├☑ nibabel           5.1.0
├☑ dipy              1.7.0
├☑ pandas            2.1.1
└☐ unavailable       sklearn, numba, nilearn, openmeeg, cupy

Visualization (optional)
├☑ pyvista           0.42.2 (OpenGL 4.6 (Core Profile) Mesa 23.0.4-0ubuntu1~22.04.1 via AMD Radeon RX 570 Series (polaris10, LLVM 15.0.7, DRM 3.49, 6.2.0-35-generic))
├☑ pyvistaqt         0.11.0
├☑ vtk               9.2.6
Warning: Ignoring XDG_SESSION_TYPE=wayland on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
├☑ qtpy              2.4.0 (PyQt5=5.15.2)
└☐ unavailable       ipympl, pyqtgraph, mne-qt-browser, ipywidgets, trame_client, trame_server, trame_vtk, trame_vuetify

Ecosystem (optional)
├☑ mne-bids          0.14.dev0
├☑ mne-connectivity  0.6.0dev0
└☐ unavailable       mne-nirs, mne-features, mne-icalabel, mne-bids-pipeline

Can you try with `MNE_COREG_MARK_INSIDE=false` in your env? That should at least show them
unrelated note:

> Python 3.11.5 | packaged by conda-forge | (main, Aug 27 2023, 03:34:09) [GCC 12.3.0]
> Executable /home/erica/miniconda3/envs/sprint_2023/bin/python3.1

executable is reported as python 3.1. I think this is not actually wrong (on my system too, there's a `python3.1` in the env bin folder that is a symlink to `python3.10`; guessing that it's a shim put in by conda/mamba to deal with short-sighted minor version parsing code).  But ideally the symlink should get followed before writing the path, i.e. `Path(sys.executable).resolve()`.
@nordme never mind, I can replicate on my macOS machine. It's an issue with the `scalars` affecting not just the color but also the glyph scaling even though we set `scale=False` somewhere. So when we use 0 and 1 to set things to use color0 and color1, we end up scaling the color0 points (inside the head) to zero. I'll investigate further... unclear why this doesn't happen on my Linux machine but does on macOS.
@larsoner  Yeah, sorry I forgot to mention that it happens regardless of what `MNE_COREG_MARK_INSIDE` is set to. 

Resolving patch:
diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py
--- a/mne/viz/backends/_pyvista.py
+++ b/mne/viz/backends/_pyvista.py
@@ -655,6 +655,9 @@ def quiver3d(
         clim=None,
     ):
         _check_option("mode", mode, ALLOWED_QUIVER_MODES)
+        _validate_type(scale_mode, str, "scale_mode")
+        scale_map = dict(none=False, scalar="scalars", vector="vec")
+        _check_option("scale_mode", scale_mode, list(scale_map))
         with warnings.catch_warnings():
             warnings.filterwarnings("ignore", category=FutureWarning)
             factor = scale
@@ -667,7 +670,10 @@ def quiver3d(
             grid = UnstructuredGrid(*args)
             if scalars is None:
                 scalars = np.ones((n_points,))
-            grid.point_data["scalars"] = np.array(scalars)
+                mesh_scalars = None
+            else:
+                mesh_scalars = "scalars"
+            grid.point_data["scalars"] = np.array(scalars, float)
             grid.point_data["vec"] = vectors
             if mode == "2darrow":
                 return _arrow_glyph(grid, factor), grid
@@ -715,14 +721,17 @@ def quiver3d(
                 glyph.Update()
                 geom = glyph.GetOutput()
                 mesh = grid.glyph(
-                    orient="vec", scale=scale_mode == "vector", factor=factor, geom=geom
+                    orient="vec",
+                    scale=scale_map[scale_mode],
+                    factor=factor,
+                    geom=geom,
                 )
             actor = _add_mesh(
                 self.plotter,
                 mesh=mesh,
                 color=color,
                 opacity=opacity,
-                scalars=None,
+                scalars=mesh_scalars,
                 colormap=colormap,
                 show_scalar_bar=False,
                 backface_culling=backface_culling,


Test patch: