111
111
}
112
112
region2num = {v : k for k , v in num2region .items ()}
113
113
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
+
114
119
num2region_mni = {
115
120
0 : 'unknown' ,
116
121
1 : 'bankssts' ,
@@ -766,6 +771,85 @@ def paint_overlay(self, labels, value=1):
766
771
self .has_overlay [verts == 1 ] = True
767
772
self .has_overlay_cells [add_overlay == 1 ] = True
768
773
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
+
769
853
770
854
def mark_overlay (self , verts , value = 1 , inner_radius = 0.8 , taper = True ):
771
855
"""
@@ -801,6 +885,11 @@ def set_visible(self, labels, min_alpha=0):
801
885
self .keep_visible_cells = self .alpha > min_alpha
802
886
self .alpha = np .maximum (self .alpha , min_alpha )
803
887
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
804
893
805
894
806
895
class Brain :
@@ -1134,7 +1223,7 @@ def mark_overlay(self, verts, isleft, value=1, inner_radius=0.8, taper=True):
1134
1223
1135
1224
def set_visible (self , labels , min_alpha = 0 ):
1136
1225
"""
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 .
1138
1227
1139
1228
Parameters
1140
1229
----------
@@ -1150,6 +1239,61 @@ def set_visible(self, labels, min_alpha=0):
1150
1239
self .lh .set_visible (labels , min_alpha )
1151
1240
self .rh .set_visible (labels , min_alpha )
1152
1241
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
1153
1297
1154
1298
1155
1299
def get_nearest_vert_index (coords , isleft , surf_lh , surf_rh , verbose = False ):
@@ -1190,4 +1334,3 @@ def find_closest_vertices(surface_coords, point_coords):
1190
1334
point_coords = np .atleast_2d (point_coords )
1191
1335
dists = cdist (surface_coords , point_coords )
1192
1336
return np .argmin (dists , axis = 0 ), np .min (dists , axis = 0 )
1193
-
0 commit comments