From 44111b4462de5b46aceea2aa8cc6f69c2c706879 Mon Sep 17 00:00:00 2001 From: Gnefil Date: Wed, 29 Jul 2026 00:05:24 +0200 Subject: [PATCH 1/8] ENH: Add extra source id parameter to vertex select --- mne/viz/_brain/_brain.py | 22 +++++++++++++++++----- mne/viz/ui_events.py | 5 +++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 1711333bf76..c3ad5716e9f 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -1183,7 +1183,9 @@ def _configure_vertex_time_course(self): np.argmax(np.abs(use_data), axis=None), use_data.shape ) vertex_id = vertices[ind[0]] - publish(self, VertexSelect(hemi=hemi, vertex_id=vertex_id)) + publish( + self, VertexSelect(hemi=hemi, vertex_id=vertex_id, source_id=ind[0]) + ) def _configure_picking(self): # get data for each hemi @@ -1457,6 +1459,7 @@ def _on_pick(self, vtk_picker, event): # shift = np.array(grid.GetOrigin()) + spacing / 2. # ijk = np.round((pos - shift) / spacing).astype(int) # vertex_id = np.ravel_multi_index(ijk, shape, order='F') + source_id = idx else: vtk_cell = mesh.GetCell(cell_id) cell = [ @@ -1466,7 +1469,14 @@ def _on_pick(self, vtk_picker, event): vert_pos = mesh.points[cell] vertex_id = cell[np.argmin(np.linalg.norm(vert_pos - pos, axis=1))] - publish(self, VertexSelect(hemi=hemi, vertex_id=vertex_id)) + # retrieve the nearest source_id from the smooth_mat + smooth_mat = self.act_data_smooth[hemi][1] + # to deal CSR format and avoid materializing per click + # equivalent to: source_id = np.argmax(smooth_mat[vertex_id].toarray()) + lo, hi = smooth_mat.indptr[vertex_id], smooth_mat.indptr[vertex_id + 1] + source_id = smooth_mat.indices[lo + np.argmax(smooth_mat.data[lo:hi])] + + publish(self, VertexSelect(hemi=hemi, vertex_id=vertex_id, source_id=source_id)) def _on_time_change(self, event): """Respond to a time change UI event.""" @@ -4050,9 +4060,11 @@ def _update_current_time_idx(self, time_idx): mesh = self.layered_meshes[hemi] mesh.smooth_mat = hemi_data.get("smooth_mat") key_rng = [ - -key_data["fmax"] - if key_data["center"] is not None - else key_data["fmin"], + ( + -key_data["fmax"] + if key_data["center"] is not None + else key_data["fmin"] + ), key_data["fmax"], ] if data_key in mesh._overlays: diff --git a/mne/viz/ui_events.py b/mne/viz/ui_events.py index 7667248a140..6fc7d6ba0aa 100644 --- a/mne/viz/ui_events.py +++ b/mne/viz/ui_events.py @@ -167,6 +167,8 @@ class VertexSelect(UIEvent): Can be ``"lh"``, ``"rh"``, or ``"vol"``. vertex_id : int The vertex number (in the high resolution mesh) that was selected. + source_id : int + The index number of the source estimate point that was selected. Attributes ---------- @@ -176,10 +178,13 @@ class VertexSelect(UIEvent): Can be ``"lh"``, ``"rh"``, or ``"vol"``. vertex_id : int The vertex number (in the high resolution mesh) that was selected. + source_id : int + The index number of the source estimate point that was selected. """ hemi: str vertex_id: int + source_id: int = None @dataclass From 1ee22165b1d2a7f06451b932efd4563021a38926 Mon Sep 17 00:00:00 2001 From: Gnefil Date: Tue, 4 Aug 2026 09:15:34 +0200 Subject: [PATCH 2/8] ENH: Simplify argmax selection with non-zero guard --- mne/viz/_brain/_brain.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index c3ad5716e9f..e4f698accab 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -1471,10 +1471,8 @@ def _on_pick(self, vtk_picker, event): # retrieve the nearest source_id from the smooth_mat smooth_mat = self.act_data_smooth[hemi][1] - # to deal CSR format and avoid materializing per click - # equivalent to: source_id = np.argmax(smooth_mat[vertex_id].toarray()) - lo, hi = smooth_mat.indptr[vertex_id], smooth_mat.indptr[vertex_id + 1] - source_id = smooth_mat.indices[lo + np.argmax(smooth_mat.data[lo:hi])] + row = smooth_mat[vertex_id] + source_id = smooth_mat[vertex_id].argmax() if row.nnz else None publish(self, VertexSelect(hemi=hemi, vertex_id=vertex_id, source_id=source_id)) From 8b84360c3370672ac164e50ea329f135fda69616 Mon Sep 17 00:00:00 2001 From: Lifeng <76589235+Gnefil@users.noreply.github.com> Date: Sat, 8 Aug 2026 01:50:06 +0800 Subject: [PATCH 3/8] Apply suggestions from code review Co-authored-by: Marijn van Vliet --- mne/viz/_brain/_brain.py | 2 +- mne/viz/ui_events.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index e4f698accab..c1586ca8566 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -1184,7 +1184,7 @@ def _configure_vertex_time_course(self): ) vertex_id = vertices[ind[0]] publish( - self, VertexSelect(hemi=hemi, vertex_id=vertex_id, source_id=ind[0]) + self, VertexSelect(hemi=hemi, vertex_id=vertices[ind[0]], source_id=ind[0]) ) def _configure_picking(self): diff --git a/mne/viz/ui_events.py b/mne/viz/ui_events.py index 6fc7d6ba0aa..c65fa86bbec 100644 --- a/mne/viz/ui_events.py +++ b/mne/viz/ui_events.py @@ -168,7 +168,8 @@ class VertexSelect(UIEvent): vertex_id : int The vertex number (in the high resolution mesh) that was selected. source_id : int - The index number of the source estimate point that was selected. + The index number of the closest source point to the vertex. + Only set if the publishing figure contains a source estimate. Attributes ---------- @@ -179,7 +180,8 @@ class VertexSelect(UIEvent): vertex_id : int The vertex number (in the high resolution mesh) that was selected. source_id : int - The index number of the source estimate point that was selected. + The index number of the closest source point to the vertex. + Only set if the publishing figure contains a source estimate. """ hemi: str From 9624128fc73ac1072c2cd773bb2e909de4ca676d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:52:38 +0000 Subject: [PATCH 4/8] [autofix.ci] apply automated fixes --- mne/viz/_brain/_brain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index c1586ca8566..9049c1f595e 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -1184,7 +1184,8 @@ def _configure_vertex_time_course(self): ) vertex_id = vertices[ind[0]] publish( - self, VertexSelect(hemi=hemi, vertex_id=vertices[ind[0]], source_id=ind[0]) + self, + VertexSelect(hemi=hemi, vertex_id=vertices[ind[0]], source_id=ind[0]), ) def _configure_picking(self): From 6212a6470a566ea67a2978addcce69b4f0fda163 Mon Sep 17 00:00:00 2001 From: Gnefil Date: Fri, 7 Aug 2026 19:53:43 +0200 Subject: [PATCH 5/8] ENH: Clean extra line --- mne/viz/_brain/_brain.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 9049c1f595e..d904d301493 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -1182,7 +1182,6 @@ def _configure_vertex_time_course(self): ind = np.unravel_index( np.argmax(np.abs(use_data), axis=None), use_data.shape ) - vertex_id = vertices[ind[0]] publish( self, VertexSelect(hemi=hemi, vertex_id=vertices[ind[0]], source_id=ind[0]), From 1b10afb50e8e9b0022f962548a51662bab0dfc7b Mon Sep 17 00:00:00 2001 From: Gnefil Date: Fri, 7 Aug 2026 20:07:51 +0200 Subject: [PATCH 6/8] DOC: Add changelog --- doc/changes/dev/14126.newfeature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/dev/14126.newfeature.rst diff --git a/doc/changes/dev/14126.newfeature.rst b/doc/changes/dev/14126.newfeature.rst new file mode 100644 index 00000000000..e4850922bd1 --- /dev/null +++ b/doc/changes/dev/14126.newfeature.rst @@ -0,0 +1 @@ +Add ``source_id`` as an optional parameter in :class:`mne.viz.ui_events.VertexSelect`, by `Lifeng Qiu Lin`_. \ No newline at end of file From 1563bc1a0a768b37e19d5fc9077dbdc2bc48578a Mon Sep 17 00:00:00 2001 From: Gnefil Date: Sat, 8 Aug 2026 10:50:26 +0200 Subject: [PATCH 7/8] ENH: Add None option for source_id parameter --- mne/viz/ui_events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/viz/ui_events.py b/mne/viz/ui_events.py index c65fa86bbec..bb54447a9e4 100644 --- a/mne/viz/ui_events.py +++ b/mne/viz/ui_events.py @@ -167,7 +167,7 @@ class VertexSelect(UIEvent): Can be ``"lh"``, ``"rh"``, or ``"vol"``. vertex_id : int The vertex number (in the high resolution mesh) that was selected. - source_id : int + source_id : int | None The index number of the closest source point to the vertex. Only set if the publishing figure contains a source estimate. @@ -179,7 +179,7 @@ class VertexSelect(UIEvent): Can be ``"lh"``, ``"rh"``, or ``"vol"``. vertex_id : int The vertex number (in the high resolution mesh) that was selected. - source_id : int + source_id : int | None The index number of the closest source point to the vertex. Only set if the publishing figure contains a source estimate. """ From 15f459a2127801c620b6f66c289fe45e5dbc6e8a Mon Sep 17 00:00:00 2001 From: Gnefil Date: Sat, 8 Aug 2026 19:07:45 +0200 Subject: [PATCH 8/8] ENH: Add test for the new functionality --- mne/viz/_brain/tests/test_brain.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/mne/viz/_brain/tests/test_brain.py b/mne/viz/_brain/tests/test_brain.py index 3e94837886a..6f85b02cacb 100644 --- a/mne/viz/_brain/tests/test_brain.py +++ b/mne/viz/_brain/tests/test_brain.py @@ -1674,6 +1674,35 @@ def test_brain_ui_events(renderer_interactive_pyvistaqt, brain_gc): # Should remain unchanged. assert_array_equal(brain._data["ctable"][:3, 3], [0, 2, 4]) + # Test effect of vertex selection publishing. + events = list() + ui_events.subscribe(brain, "vertex_select", lambda event: events.append(event)) + + mesh = brain.layered_meshes["lh"]._polydata + faces = brain.geo["lh"].faces + vertices = brain._data["lh"]["vertices"] + smooth_mat = brain.act_data_smooth["lh"][1] + + # each for existing and missing source vertex cases + for is_source in (True, False): + mask = np.isin(faces[:, 0], vertices) + # select first vertex satisfying the condition + cell_id = np.where(mask if is_source else ~mask)[0][0] + vertex_id = faces[cell_id, 0] + # make the selection + n_events = len(events) + brain._on_pick(TstVTKPicker(mesh, cell_id, "lh", brain), None) + assert len(events) == n_events + 1 + event = events[-1] + assert event.vertex_id == vertex_id + row = smooth_mat[vertex_id, :] + if is_source: + assert event.source_id == row.argmax() + assert vertices[event.source_id] == event.vertex_id + else: + assert row.sum() == 0 + assert event.source_id is None + brain.close()