Repository: mne-tools/mne-python

Issue:
Right singular vector in "label_sign_flip"
In line 1082-1085 of mne/label.py

1082:  _, _, Vh = linalg.svd(ori, full_matrices=False)
1083: 
1084: # Comparing to the direction of the first right singular vector
1085: flip = np.sign(np.dot(ori, Vh[:, 0] if len(vertno_sel) > 3 else Vh[0]))

If I am following correctly,  "ori" is a [n_dipoles, 3] vector.  According to "numpy.linalg.svd", the right singular vectors are the rows rather than columns of "Vh".  So should line 1085 be

flip = np.sign(np.dot(ori, Vh[0])) ?

Thanks. 



Discussion:
I agree with Ying, and especially do not understand why the code uses the first row Vh[0] when n_dipoles < 3, but uses the first column Vh[:,0] otherwise.

I stepped through the code myself with an example src and label to check... yes, ori has shape [n_dipoles, 3], where n_dipoles is len(vertno_sel) (number of dipoles in the label). According to documentation, numpy.linalg.svd returns Vh = V.H of size [min(n_dipoles, 3), 3]. If we had V, the columns would be the right singular vectors, but we have V.H, so the right singular vectors should be the rows, so we would want Vh[0] no matter what n_dipoles. Unless we are both missing something here... any input or other thoughts would be helpful!

Thanks

hum... would what you suggest work in terms of dimensions?

can you try to do the change and see the impact of this example

http://martinos.org/mne/dev/auto_examples/inverse/plot_compute_mne_inverse_epochs_in_label.html#sphx-glr-auto-examples-inverse-plot-compute-mne-inverse-epochs-in-label-py

?

So as Ying said originally, I think it should always be `Vh[0]`, never `Vh[:,0]`.

For the example: by chance, the flip vector comes out the same, but the arguments to `np.sign()` in line 1084 of label.py are **not** equal (ie `np.dot(ori, Vh[0,:]) != np.dot(ori, Vh[0])`)

I changed the label in the example to 'Aud-rh' to see if I could get different behavior, and now the signs indeed come out different. See the attached plot - "myflip" is where I made this change.

[test_signflip.pdf](https://github.com/mne-tools/mne-python/files/187998/test_signflip.pdf)

@natalieklein I agree it should always be the row `Vh[0]`. Here is a simple example to illustrate the problem:

``` Python
>>> x
array([[ 1.        ,  0.        ,  0.        ],
       [ 0.        ,  1.        ,  0.        ],
       [ 0.        ,  0.        ,  1.        ],
       [ 1.41421356,  1.41421356,  0.        ],
       [ 1.41421356,  1.41421356,  0.        ]])
>>> linalg.svd(x, full_matrices=False)[2]
array([[-0.70710678, -0.70710678, -0.        ],
       [ 0.70710678, -0.70710678, -0.        ],
       [ 0.        ,  0.        ,  1.        ]])

```

The first row is pretty clearly better aligned with the vectors than the first column. @agramfort agree?

Do you want to make a PR? If not, I can do it.

@Eric89GXL I can do it if you want, since I already have the change in my fork. Good catch @YingYang !

We'll want to add some test. Maybe:
1. Make a label with 5 arbitrary vertices (can be left-hemi `np.arange(5)`)
2. Load a source space for `sample`
3. Modify the source space for the chosen vertices to have the normals I defined above
4. Verify that `assert_allclose(np.abs(np.dot(flip_vector, [np.sqrt(2), np.sqrt(2), 0])), 1.)`, i.e. it's aligned a reasonable way

That test would fail on `master` and pass on your PR, satisfying TDD.

But yes please take a shot at it. If my test suggestion doesn't make any sense I can make a PR into your branch


Resolving patch:
diff --git a/mne/label.py b/mne/label.py
--- a/mne/label.py
+++ b/mne/label.py
@@ -1082,7 +1082,7 @@ def label_sign_flip(label, src):
     _, _, Vh = linalg.svd(ori, full_matrices=False)
 
     # Comparing to the direction of the first right singular vector
-    flip = np.sign(np.dot(ori, Vh[:, 0] if len(vertno_sel) > 3 else Vh[0]))
+    flip = np.sign(np.dot(ori, Vh[0]))
     return flip
 
 


Test patch:
diff --git a/mne/tests/test_label.py b/mne/tests/test_label.py
--- a/mne/tests/test_label.py
+++ b/mne/tests/test_label.py
@@ -16,7 +16,7 @@
                  read_source_spaces, grow_labels, read_labels_from_annot,
                  write_labels_to_annot, split_label, spatial_tris_connectivity,
                  read_surface)
-from mne.label import Label, _blend_colors
+from mne.label import Label, _blend_colors, label_sign_flip
 from mne.utils import (_TempDir, requires_sklearn, get_subjects_dir,
                        run_tests_if_main, slow_test)
 from mne.fixes import digitize, in1d, assert_is, assert_is_not
@@ -754,4 +754,22 @@ def test_grow_labels():
     assert_array_equal(l1.vertices, l0.vertices)
 
 
+@testing.requires_testing_data
+def test_label_sign_flip():
+    src = read_source_spaces(src_fname)
+    label = Label(vertices=src[0]['vertno'][:5], hemi='lh')
+    src[0]['nn'][label.vertices] = np.array(
+        [[1., 0., 0.],
+         [0.,  1., 0.],
+         [0,  0, 1.],
+         [1. / np.sqrt(2), 1. / np.sqrt(2), 0.],
+         [1. / np.sqrt(2), 1. / np.sqrt(2), 0.]])
+    known_flips = np.array([1, 1, np.nan, 1, 1])
+    idx = [0, 1, 3, 4]  # indices that are usable (third row is orthognoal)
+    flip = label_sign_flip(label, src)
+    # Need the abs here because the direction is arbitrary
+    assert_array_almost_equal(np.abs(np.dot(flip[idx], known_flips[idx])),
+                              len(idx))
+
+
 run_tests_if_main()