111111}
112112region2num = {v : k for k , v in num2region .items ()}
113113
114+ temporal_regions_nums = [33 , 34 , 35 , 36 , 74 , 41 , 43 , 72 , 73 , 38 , 37 , 75 , 76 , 77 , 78 , 79 , 80 , 81 ]
115+ temporal_regions_superlist = [num2region [num ] for num in temporal_regions_nums ]
116+ temporal_regions_superlist += ['alHG' ,'pmHG' ,'HG' ,'TTS' ,'PT' ,'PP' ,'MTG' ,'ITG' ,'mSTG' ,'pSTG' ,'STG' ,'STS' ,'T.Pole' ]
117+
118+
114119num2region_mni = {
115120 0 : 'unknown' ,
116121 1 : 'bankssts' ,
@@ -766,6 +771,85 @@ def paint_overlay(self, labels, value=1):
766771 self .has_overlay [verts == 1 ] = True
767772 self .has_overlay_cells [add_overlay == 1 ] = True
768773 return self
774+
775+ def interpolate_electrodes_onto_brain (self , coords , values , k , max_dist , roi = 'all' ):
776+ """
777+ Use electrode coordinates to interpolate 1-dimensional values corresponding
778+ to each electrode onto the brain's surface.
779+
780+ Parameters
781+ ----------
782+ coords : np.ndarray (elecs, 3)
783+ 3D coordinates of electrodes
784+ values : np.ndarray (elecs,)
785+ Value for each electrode
786+ k : int
787+ Number of nearest neighbors to consider
788+ max_dist : float
789+ Maximum distance outside of which nearest neighbors will be ignored
790+ roi : list of strings, or string in {'all', 'temporal'}, default='all'
791+ Regions to allow interpolation over. By default, the entire brain surface
792+ is allowed. Can also be specified as a list of string labels (drawing from self.label_names)
793+
794+ Notes
795+ -----
796+ After running this function, you can use the visualization function ``plot_brain_overlay``
797+ for a quick matplotlib plot, or you can extract the surface values from the ``self.overlay``
798+ attribute for plotting with another tool like pysurfer.
799+ """
800+
801+ if isinstance (roi , str ) and roi == 'all' :
802+ roi_list = self .label_names
803+ elif isinstance (roi , str ) and roi == 'temporal' :
804+ if self .atlas == 'MNI152' :
805+ raise ValueError ("roi='temporal' is not supported for MNI brain. Must specify list of specific region names" )
806+ roi_list = temporal_regions_superlist
807+ else :
808+ roi_list = roi
809+ assert isinstance (roi , list )
810+
811+ roi_list_subset = [x for x in roi_list if x in self .label_names ]
812+ zones_to_include , _ , _ = self .zones (roi_list_subset )
813+
814+ # Euclidean distances from each surface vertex to each coordinate
815+ dists = cdist (self .surf [0 ], coords )
816+ sorted_dists = np .sort (dists , axis = - 1 )[:, :k ]
817+ indices = np .argsort (dists , axis = - 1 )[:, :k ] # get closest k electrodes to each vertex
818+
819+ # Mask out distances greater than max_dist
820+ valid_mask = sorted_dists <= max_dist
821+
822+ # Retrieve the corresponding values using indices
823+ neighbor_values = values [indices ]
824+
825+ # Mask invalid values
826+ masked_values = np .where (valid_mask , neighbor_values , np .nan )
827+ masked_distances = np .where (valid_mask , sorted_dists , np .nan )
828+
829+ # Compute weights: inverse distance weighting (avoiding division by zero)
830+ weights = np .where (valid_mask , 1 / (masked_distances + 1e-10 ), 0 )
831+
832+ # # Compute weighted sum and normalize by total weight per vertex
833+ weighted_sum = np .nansum (masked_values * weights , axis = 1 )
834+ total_weight = np .nansum (weights , axis = 1 )
835+
836+ # # Normalize to get final smoothed values
837+ updated_vertices = np .logical_and (total_weight > 0 , zones_to_include )
838+ total_weight [~ updated_vertices ] += 1e-10 # this just gets ride of the division by zero warning, but doesn't affect result since these values are turned to nan anyway
839+ smoothed_values = np .where (updated_vertices , weighted_sum / total_weight , np .nan )
840+
841+ # update the surface vertices and triangle attributes with the values
842+ verts = updated_vertices .astype ('float' )
843+ trigs = np .zeros (self .n_trigs , dtype = float )
844+ for i in range (self .n_trigs ):
845+ trigs [i ] = np .mean ([verts [self .trigs [i , j ]] != 0 for j in range (3 )])
846+
847+ self .overlay [updated_vertices ] = smoothed_values [updated_vertices ]
848+ self .has_overlay [updated_vertices ] = True
849+ self .has_overlay_cells [trigs == 1 ] = True
850+
851+ return self
852+
769853
770854 def mark_overlay (self , verts , value = 1 , inner_radius = 0.8 , taper = True ):
771855 """
@@ -801,6 +885,11 @@ def set_visible(self, labels, min_alpha=0):
801885 self .keep_visible_cells = self .alpha > min_alpha
802886 self .alpha = np .maximum (self .alpha , min_alpha )
803887 return self
888+
889+ def reset_overlay_except (self , labels ):
890+ keep_visible , self .alpha , _ = self .zones (labels , min_alpha = 0 )
891+ self .overlay [~ keep_visible ] = 0
892+ return self
804893
805894
806895class Brain :
@@ -1134,7 +1223,7 @@ def mark_overlay(self, verts, isleft, value=1, inner_radius=0.8, taper=True):
11341223
11351224 def set_visible (self , labels , min_alpha = 0 ):
11361225 """
1137- Set certain regions as visible with a float label.
1226+ Set certain regions as visible with a float label, and the rest will be invisible .
11381227
11391228 Parameters
11401229 ----------
@@ -1150,6 +1239,61 @@ def set_visible(self, labels, min_alpha=0):
11501239 self .lh .set_visible (labels , min_alpha )
11511240 self .rh .set_visible (labels , min_alpha )
11521241 return self
1242+
1243+ def reset_overlay_except (self , labels ):
1244+ """
1245+ Keep certain regions and the rest as colorless.
1246+
1247+ Parameters
1248+ ----------
1249+ labels : str | list[str]
1250+ Label(s) to set as visible.
1251+
1252+ Returns
1253+ -------
1254+ self : instance of self
1255+ """
1256+ self .lh .reset_overlay_except (labels )
1257+ self .rh .reset_overlay_except (labels )
1258+ return self
1259+
1260+ def interpolate_electrodes_onto_brain (self , coords , values , isleft = None , k = 10 , max_dist = 10 , roi = 'all' , reset_overlay_first = True ):
1261+ """
1262+ Use electrode coordinates to interpolate 1-dimensional values corresponding
1263+ to each electrode onto the brain's surface.
1264+
1265+ Parameters
1266+ ----------
1267+ coords : np.ndarray (elecs, 3)
1268+ 3D coordinates of electrodes
1269+ values : np.ndarray (elecs,)
1270+ Value for each electrode
1271+ isleft : np.ndarray (elecs,), optional
1272+ If provided, specifies a boolean which is True for each electrode that is in the left hemisphere.
1273+ If not given, this will be inferred from the first dimension of the coords (negative is left).
1274+ k : int, default=10
1275+ Number of nearest neighbors to consider
1276+ max_dist : float, default=10
1277+ Maximum distance (in mm) outside of which nearest neighbors will be ignored
1278+ roi : list of strings, or string in {'all', 'temporal'}, default='all'
1279+ Regions to allow interpolation over. By default, the entire brain surface
1280+ is allowed. Can also be specified as a list of string labels (drawing from self.lh.label_names)
1281+ reset_overlay_first : bool, default=True
1282+ If True (default), reset the overlay before creating a new overlay
1283+
1284+ Notes
1285+ -----
1286+ After running this function, you can use the visualization function ``plot_brain_overlay``
1287+ for a quick matplotlib plot, or you can extract the surface values from the ``self.lh.overlay``
1288+ and ``self.rh.overlay`` attributes, etc, for plotting with another tool like pysurfer or plotly.
1289+ """
1290+ if reset_overlay_first :
1291+ self .reset_overlay ()
1292+ if isleft is None :
1293+ isleft = coords [:,0 ] < 0
1294+ self .lh .interpolate_electrodes_onto_brain (coords [isleft ], values [isleft ], k = k , max_dist = max_dist , roi = roi )
1295+ self .rh .interpolate_electrodes_onto_brain (coords [~ isleft ], values [~ isleft ], k = k , max_dist = max_dist , roi = roi )
1296+ return self
11531297
11541298
11551299def get_nearest_vert_index (coords , isleft , surf_lh , surf_rh , verbose = False ):
@@ -1190,4 +1334,3 @@ def find_closest_vertices(surface_coords, point_coords):
11901334 point_coords = np .atleast_2d (point_coords )
11911335 dists = cdist (surface_coords , point_coords )
11921336 return np .argmin (dists , axis = 0 ), np .min (dists , axis = 0 )
1193-
0 commit comments