Repository: mne-tools/mne-python

Issue:
Bug in vector source estimate plotting
#### Describe the bug
This was reported to me by @rythorpe. The vector source estimate does not plot arrows for the somato data.

#### Steps and/or code to reproduce
Here is a [gist](https://gist.github.com/rythorpe/c51a5b66b492b67437843283c67050af) to reproduce the problem. I don't see any issue with the script. Indeed, if you do:

```py
print(stc)
```

you get:

```sh
<VectorSourceEstimate  |  8155 vertices, subject : 01, tmin : -199.79521315838787 (ms), tmax : 199.79521315838792 (ms), tstep : 3.3299202193064645 (ms), data shape : (8155, 3, 121)>
```

and if you do:

```py
print(stc.data.shape)
```

you get:

```py
(8155, 3, 121)
```

indicating that the stc is vector.

#### Expected results
There should be an arrow plot

#### Actual results

but the plot shows:

![noarrows_stc](https://user-images.githubusercontent.com/15852194/68157579-f0c2aa00-ff1b-11e9-8231-b3f113a44c11.png)

#### Additional information
I'm on the latest master, commit 42948689e.

and `mne.sys_info()` gives:

```sh
Platform:      Linux-3.10.0-957.1.3.el7.x86_64-x86_64-with-centos-7.6.1810-Core
Python:        3.6.7 | packaged by conda-forge | (default, Feb 28 2019, 09:07:38)  [GCC 7.3.0]
Executable:    /autofs/space/meghnn_001/users/mjas/anaconda3/envs/mne/bin/python
CPU:           x86_64: 64 cores
Memory:        125.3 GB

mne:           0.20.dev0
numpy:         1.15.4 {blas=mkl_rt, lapack=mkl_rt}
scipy:         1.2.1
matplotlib:    3.0.2 {backend=Qt5Agg}

sklearn:       0.20.2
numba:         Not found
nibabel:       2.3.3
cupy:          Not found
pandas:        0.24.1
dipy:          0.15.0
mayavi:        4.7.0.dev0 {qt_api=pyqt5, PyQt5=5.9.2}
pyvista:       Not found
vtk:           8.1.2
```



Discussion:
@GuillaumeFavelier maybe can you have a look? thanks
Okay, first good news: I'm able to reproduce. Thanks for reporting @jasmainak
Then there is the fact that the automatically computed `scale_factor` value is really small in my experiment. This variable influences the scaling of the glyphs:
```py
magnitude = np.linalg.norm(array, axis=1)
magnitude_max = magnitude.max()
scale_factor_norm = scale_factor / magnitude_max
```
My results using the gist:
```
scale_factor=3.2947158840968823e-21
magnitude_max=3.0642358332091084e-10
```

Which gives `scale_factor_norm=1.07521615940585e-11`, this is too small I think.

For reference: https://github.com/nipy/PySurfer/blob/master/surfer/viz.py#L1098-L1105
do you suggest a fix?

>

Apparently using `scale_factor=20` instead of `scale_factor=None` already helps in my case but there is maybe more to it.
could you try it out @jasmainak ?

```diff
...
# Plot brain
- brain = stc.plot(subjects_dir=subjects_dir,subject='01',hemi='rh',views='par',initial_time=0.04)
+ brain = stc.plot(subjects_dir=subjects_dir,subject='01',hemi='rh',views='par',initial_time=0.04, scale_factor=20)
```
If the scale factor logic can be improved we could consider it a bug fix
Yes, the `scale_factor` does seem to improve things. Although it is still a bit hard to see the arrows depending on how you rotate the brain. Not sure if that's expected. If the automatic logic is not so reliable, maybe one option is to add it as an option to tweak in the `time_viewer` ?
At the moment, the automatic scaling for `plot_vector_source_estimates` still relies on PySurfer. If we make a fix in MNE, the patch would be PySurfer-specific right?

On the MNE side, I could suggest something which is in the order of magnitude of:
```py
scale_factor = (np.linalg.norm(array, axis=1)).max() / magnitude[:, 0].max()
```
I haven't looked at how this is currently handled in the code, so maybe this is a dumb idea, but it seems to me that you will never want your longest arrow to be longer than, say, 1/5 the width of a cerebral hemisphere at most. Would it be enough to always scale such that the longest arrow ends up that size?

On November 5, 2019 8:24:08 AM PST, Mainak Jas <notifications@github.com> wrote:
>Yes, the `scale_factor` does seem to improve things. Although it is
>still a bit hard to see the arrows depending on how you rotate the
>brain. Not sure if that's expected. If the automatic logic is not so
>reliable, maybe one option is to add it as an option to tweak in the
>`time_viewer` ?

@GuillaumeFavelier we can implement the fix on the MNE side, then we don't break people's independent PySurfer code. Or you can try to backport the fix.

We'll probably want it for the PyVista implementation anyway.

Regarding which heuristic to use (e.g., @drammock's comment) it's probably worth trying a few options to see what tends to work. But instead of using `max` another option is to use a percentile (other than effectively 100%) to help deal with outliers.

Resolving patch:
diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py
--- a/mne/viz/_3d.py
+++ b/mne/viz/_3d.py
@@ -2308,6 +2308,23 @@ def plot_vector_source_estimates(stc, subject=None, hemi='lh', colormap='hot',
                                scale_factor=scale_factor,
                                min=scale_pts[0], max=scale_pts[2],
                                **ad_kwargs)
+            if scale_factor is None:
+                # Compute the width of the brain
+                width = np.ptp(brain.geo[hemi].coords[:, 1])
+                # Retrieve the current hemi
+                for b in brain._brain_list:
+                    if b['hemi'] == hemi:
+                        found_hemi = b['brain']
+                layer_id = brain.data['layer_id']
+                # Configure the glyphs scale directly
+                glyphs = found_hemi.data[layer_id]['glyphs']
+                glyphs.glyph.glyph.scale_factor = width * 0.1
+            # depth peeling patch
+            if brain_alpha < 1.0:
+                for ff in brain._figures:
+                    for f in ff:
+                        if f.scene is not None:
+                            f.scene.renderer.use_depth_peeling = True
         brain.scale_data_colormap(fmin=scale_pts[0], fmid=scale_pts[1],
                                   fmax=scale_pts[2], **sd_kwargs)
 
diff --git a/tutorials/source-modeling/plot_mne_dspm_source_localization.py b/tutorials/source-modeling/plot_mne_dspm_source_localization.py
--- a/tutorials/source-modeling/plot_mne_dspm_source_localization.py
+++ b/tutorials/source-modeling/plot_mne_dspm_source_localization.py
@@ -50,7 +50,7 @@
 ###############################################################################
 # Compute the evoked response
 # ---------------------------
-# Let's just use MEG channels for simplicity.
+# Let's just use the MEG channels for simplicity.
 
 evoked = epochs.average().pick('meg')
 evoked.plot(time_unit='s')


Test patch: