@@ -581,6 +581,7 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
581581 self ._picked_patches = {key : list () for key in all_keys }
582582 self ._picked_points = dict ()
583583 self ._peak_vertices = {}
584+ self ._auto_peak_points = set ()
584585 self ._trace_meta = {}
585586 self ._mouse_no_mvt = - 1
586587 self ._show_hover_info = False
@@ -920,6 +921,13 @@ def _configure_dock_colormap_widget(self, name):
920921 def select_data_key (value ):
921922 self ._active_data_key = value
922923 self ._refresh_colormap_widgets ()
924+ self ._update_act_data_smooth ()
925+ if self .show_traces :
926+ self ._update_peak_vertices ()
927+ if self .mpl_canvas is not None :
928+ self .mpl_canvas .axes .relim ()
929+ self .mpl_canvas .axes .autoscale_view ()
930+ self .mpl_canvas .update_plot ()
923931
924932 self .widgets ["data_key" ] = self ._renderer ._dock_add_combo_box (
925933 name = "Overlay" ,
@@ -1205,6 +1213,17 @@ def _configure_vertex_time_course(self):
12051213 self .plot_time_line (update = False )
12061214
12071215 # then the picked points
1216+ self ._update_peak_vertices ()
1217+
1218+ def _update_peak_vertices (self ):
1219+ """(Re)compute the peak vertex per hemi for the active overlay."""
1220+ if self .traces_mode != "vertex" :
1221+ # in label mode a VertexSelect would toggle the label containing
1222+ # the peak (and label extraction may not even be possible, e.g.,
1223+ # data added without an src)
1224+ return
1225+ old_peak_vertices = self ._peak_vertices
1226+ self ._peak_vertices = {}
12081227 for idx , hemi in enumerate (["lh" , "rh" , "vol" ]):
12091228 act_data = self .act_data_smooth .get (hemi , [None ])[0 ]
12101229 if act_data is None :
@@ -1228,16 +1247,46 @@ def _configure_vertex_time_course(self):
12281247 )
12291248 vertex_id = vertices [ind [0 ]]
12301249 self ._peak_vertices [hemi ] = vertex_id
1250+
1251+ old_vertex_id = old_peak_vertices .get (hemi )
1252+ if old_vertex_id == vertex_id :
1253+ # same peak vertex but possibly different data: refresh the
1254+ # auto-picked trace in place (manually picked traces keep
1255+ # showing the overlay they were picked from)
1256+ spheres = self ._picked_points .get ((hemi , vertex_id ))
1257+ if (hemi , vertex_id ) in self ._auto_peak_points and spheres is not None :
1258+ spheres [0 ]["line" ].set_ydata (
1259+ self ._vertex_trace_data (hemi , vertex_id )
1260+ )
1261+ continue
1262+ was_auto_picked = (hemi , old_vertex_id ) in self ._auto_peak_points
1263+ if old_vertex_id is not None and was_auto_picked :
1264+ self ._remove_vertex_glyph (hemi = hemi , vertex_id = old_vertex_id )
1265+ # a vertex the user already picked stays a manual pick (and must
1266+ # not be auto-removed on the next overlay switch)
1267+ if (hemi , vertex_id ) not in self ._picked_points :
1268+ self ._auto_peak_points .add ((hemi , vertex_id ))
12311269 publish (
12321270 self ,
12331271 VertexSelect (hemi = hemi , vertex_id = vertex_id , source_id = ind [0 ]),
12341272 )
1273+ if self .mpl_canvas is not None :
1274+ self .mpl_canvas .sync_traces ()
12351275
1236- def _configure_picking (self ):
1276+ def _vertex_trace_data (self , hemi , vertex_id ):
1277+ """Get the active overlay's time course at a mesh vertex."""
1278+ act_data , smooth = self .act_data_smooth [hemi ]
1279+ if smooth is not None :
1280+ act_data = (smooth [[vertex_id ]] @ act_data )[0 ]
1281+ else : # full-resolution data
1282+ act_data = act_data [vertex_id ].copy ()
1283+ return act_data
1284+
1285+ def _update_act_data_smooth (self ):
12371286 # get data for each hemi
12381287 from scipy .sparse import csr_array
12391288
1240- for idx , hemi in enumerate ( ["vol" , "lh" , "rh" ]) :
1289+ for hemi in ["vol" , "lh" , "rh" ]:
12411290 hemi_data = self ._data .get (hemi )
12421291 if hemi_data is not None :
12431292 act_data = hemi_data ["array" ]
@@ -1252,6 +1301,9 @@ def _configure_picking(self):
12521301 )
12531302 self .act_data_smooth [hemi ] = (act_data , smooth_mat )
12541303
1304+ def _configure_picking (self ):
1305+ self ._update_act_data_smooth ()
1306+
12551307 self ._renderer ._update_picking_callback (
12561308 self ._on_mouse_move ,
12571309 self ._on_button_press ,
@@ -1656,6 +1708,7 @@ def _add_vertex_glyph(self, hemi, mesh, vertex_id, update=True):
16561708 rindex = lst .index (self ._picked_renderer )
16571709 row , col = self ._renderer ._index_to_loc (rindex )
16581710
1711+ is_peak = self ._peak_vertices .get (hemi ) == vertex_id
16591712 spheres = list ()
16601713 for _ in self ._iter_views (hemi ):
16611714 # Using _sphere() instead of renderer.sphere() for 2 reasons:
@@ -1667,8 +1720,14 @@ def _add_vertex_glyph(self, hemi, mesh, vertex_id, update=True):
16671720 actor , mesh = self ._renderer ._sphere (
16681721 center = np .array (center ),
16691722 color = color ,
1670- radius = 4.0 ,
1723+ radius = 4.5 if is_peak else 3.0 ,
1724+ resolution = 24 if is_peak else 8 ,
16711725 )
1726+ if is_peak :
1727+ prop = actor .GetProperty ()
1728+ prop .SetSpecular (0.6 )
1729+ prop .SetSpecularPower (40 )
1730+ prop .SetSpecularColor (1 , 1 , 1 )
16721731 spheres .append (dict (mesh = mesh , actor = actor ))
16731732
16741733 # add metadata for picking
@@ -1686,6 +1745,7 @@ def _remove_vertex_glyph(self, *, hemi, vertex_id, render=True):
16861745 # to all linked brains, so by the time a given brain's own loop (e.g.
16871746 # in clear_glyphs) reaches this (hemi, vertex_id) it may already be
16881747 # gone; just no-op in that case.
1748+ self ._auto_peak_points .discard ((hemi , vertex_id ))
16891749 spheres = self ._picked_points .pop ((hemi , vertex_id ), None )
16901750 if spheres is None :
16911751 return
@@ -1808,11 +1868,7 @@ def plot_time_course(self, hemi, vertex_id, color, update=True):
18081868 mni_str = None
18091869 mni_suffix = ""
18101870 label = f"{ hemi_str } :{ str (vertex_id ).ljust (6 )} { mni_suffix } "
1811- act_data , smooth = self .act_data_smooth [hemi ]
1812- if smooth is not None :
1813- act_data = (smooth [[vertex_id ]] @ act_data )[0 ]
1814- else :
1815- act_data = act_data [vertex_id ].copy ()
1871+ act_data = self ._vertex_trace_data (hemi , vertex_id )
18161872 line = self .mpl_canvas .plot (
18171873 time ,
18181874 act_data ,
@@ -2253,14 +2309,6 @@ def add_data(
22532309 self .set_time_interpolation (self .time_interpolation )
22542310 self ._update_colormap_range ()
22552311
2256- if "data_key" in self .widgets :
2257- keys = list (self ._all_data .keys ())
2258- self .widgets ["data_key" ].set_items (keys )
2259- self .widgets ["data_key" ].set_value (key )
2260- if len (keys ) > 1 :
2261- self .widgets ["data_key" ].show ()
2262- self ._refresh_colormap_widgets ()
2263-
22642312 # 1) add the surfaces first
22652313 actor = None
22662314 for _ in self ._iter_views (hemi ):
@@ -2277,6 +2325,16 @@ def add_data(
22772325 # _current_time
22782326 self .set_data_smoothing (self ._all_data [key ]["smoothing_steps" ])
22792327
2328+ # setting the data_key widget fires select_data_key, which needs this
2329+ # overlay's smooth_mat (and, for volumes, its grid) to already exist
2330+ if "data_key" in self .widgets :
2331+ keys = list (self ._all_data .keys ())
2332+ self .widgets ["data_key" ].set_items (keys )
2333+ self .widgets ["data_key" ].set_value (key )
2334+ if len (keys ) > 1 :
2335+ self .widgets ["data_key" ].show ()
2336+ self ._refresh_colormap_widgets ()
2337+
22802338 # 3) add the other actors
22812339 if colorbar is True :
22822340 # bottom left by default
0 commit comments