From 2b77c4654729467a0a9440b7c4ab032f45d7f4bb Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Thu, 13 Feb 2025 23:46:01 -0500 Subject: [PATCH 01/11] Update brain_plots.py --- naplib/visualization/brain_plots.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/naplib/visualization/brain_plots.py b/naplib/visualization/brain_plots.py index 1b5c3cd..1f5a828 100644 --- a/naplib/visualization/brain_plots.py +++ b/naplib/visualization/brain_plots.py @@ -74,13 +74,13 @@ def _view(hemi, mode: str = "lateral", backend: str = "mpl"): raise ValueError(f"Unknown `mode`: {mode}.") -def _plot_hemi(hemi, cmap="coolwarm", ax=None, denorm=False, view="best"): +def _plot_hemi(hemi, cmap="coolwarm", ax=None, denorm=False, view="best", thresh=None): surfdist_viz( *hemi.surf, hemi.overlay, *_view(hemi.hemi, mode=view), cmap=cmap(hemi.overlay.max()) if denorm else cmap, - threshold=0.25, + threshold=thresh, alpha=hemi.alpha, bg_map=hemi.sulc, bg_on_stat=True, @@ -111,7 +111,7 @@ def plot_brain_overlay( view : {'lateral','medial','frontal','top','best'}, default='best' Which view to plot for each hemisphere. **kwargs : kwargs - Any other kwargs to pass to matplotlib.pyplot.figure + Any other kwargs to pass to matplotlib.pyplot.figure (such as figsize) Returns ------- From 29b403869afa26b3b8e438e5901fac1e4d2b146b Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Thu, 13 Feb 2025 23:48:30 -0500 Subject: [PATCH 02/11] add interpolate_electrodes_onto_brain function --- naplib/localization/freesurfer.py | 118 +++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/naplib/localization/freesurfer.py b/naplib/localization/freesurfer.py index 81ea134..2df0151 100644 --- a/naplib/localization/freesurfer.py +++ b/naplib/localization/freesurfer.py @@ -766,6 +766,68 @@ def paint_overlay(self, labels, value=1): self.has_overlay[verts == 1] = True self.has_overlay_cells[add_overlay == 1] = True return self + + def interpolate_electrodes_onto_brain(self, coords, values, k, max_dist): + """ + Use electrode coordinates to interpolate 1-dimensional values corresponding + to each electrode onto the brain's surface. + + Parameters + ---------- + coords : np.ndarray (elecs, 3) + 3D coordinates of electrodes + values : np.ndarray (elecs,) + Value for each electrode + k : int + Number of nearest neighbors to consider + max_dist : float + Maximum distance outside of which nearest neighbors will be ignored + + Notes + ----- + After running this function, you can use the visualization function ``plot_brain_overlay`` + for a quick matplotlib plot, or you can extract the surface values from the ``self.overlay`` + attribute for plotting with another tool like pysurfer. + """ + # Euclidean distances from each surface vertex to each coordinate + dists = cdist(self.surf[0], coords) + sorted_dists = np.sort(dists, axis=-1)[:, :k] + indices = np.argsort(dists, axis=-1)[:, :k] # get closest k electrodes to each vertex + + # Mask out distances greater than max_dist + valid_mask = sorted_dists <= max_dist + + # Retrieve the corresponding values using indices + neighbor_values = values[indices] + + # Mask invalid values + masked_values = np.where(valid_mask, neighbor_values, np.nan) + masked_distances = np.where(valid_mask, sorted_dists, np.nan) + + # Compute weights: inverse distance weighting (avoiding division by zero) + weights = np.where(valid_mask, 1 / (masked_distances + 1e-10), 0) + + # # Compute weighted sum and normalize by total weight per vertex + weighted_sum = np.nansum(masked_values * weights, axis=1) + total_weight = np.nansum(weights, axis=1) + + # # Normalize to get final smoothed values + updated_vertices = total_weight > 0 + 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 + smoothed_values = np.where(updated_vertices, weighted_sum / total_weight, np.nan) + + # update the surface vertices and triangle attributes with the values + verts = updated_vertices.astype('float') + trigs = np.zeros(self.n_trigs, dtype=float) + for i in range(self.n_trigs): + trigs[i] = np.mean([verts[self.trigs[i, j]] != 0 for j in range(3)]) + + self.overlay[updated_vertices] = smoothed_values[updated_vertices] + self.has_overlay[updated_vertices] = True + self.has_overlay_cells[trigs == 1] = True + + return self + def mark_overlay(self, verts, value=1, inner_radius=0.8, taper=True): """ @@ -801,6 +863,11 @@ def set_visible(self, labels, min_alpha=0): self.keep_visible_cells = self.alpha > min_alpha self.alpha = np.maximum(self.alpha, min_alpha) return self + + def reset_overlay_except(self, labels): + keep_visible, self.alpha, _ = self.zones(labels, min_alpha=0) + self.overlay[~keep_visible] = 0 + return self class Brain: @@ -1134,7 +1201,7 @@ def mark_overlay(self, verts, isleft, value=1, inner_radius=0.8, taper=True): def set_visible(self, labels, min_alpha=0): """ - Set certain regions as visible with a float label. + Set certain regions as visible with a float label, and the rest will be invisible. Parameters ---------- @@ -1150,6 +1217,55 @@ def set_visible(self, labels, min_alpha=0): self.lh.set_visible(labels, min_alpha) self.rh.set_visible(labels, min_alpha) return self + + def reset_overlay_except(self, labels): + """ + Keep certain regions and the rest as colorless. + + Parameters + ---------- + labels : str | list[str] + Label(s) to set as visible. + + Returns + ------- + self : instance of self + """ + self.lh.reset_overlay_except(labels) + self.rh.reset_overlay_except(labels) + return self + + def interpolate_electrodes_onto_brain(self, coords, values, isleft=None, k=10, max_dist=20, reset_overlay_first=True): + """ + Use electrode coordinates to interpolate 1-dimensional values corresponding + to each electrode onto the brain's surface. + + Parameters + ---------- + coords : np.ndarray (elecs, 3) + 3D coordinates of electrodes + values : np.ndarray (elecs,) + Value for each electrode + k : int + Number of nearest neighbors to consider + max_dist : float + Maximum distance outside of which nearest neighbors will be ignored + reset_overlay_first : bool + If True (default), reset the overlay before creating a new overlay + + Notes + ----- + After running this function, you can use the visualization function ``plot_brain_overlay`` + for a quick matplotlib plot, or you can extract the surface values from the ``self.lh.overlay`` + and ``self.lh.overlay_cell`` attributes, etc, for plotting with another tool like pysurfer. + """ + if reset_overlay_first: + self.reset_overlay() + if isleft is None: + isleft = coords[:,0] < 0 + self.lh.interpolate_electrodes_onto_brain(coords[isleft], values[isleft], k=k, max_dist=max_dist) + self.rh.interpolate_electrodes_onto_brain(coords[~isleft], values[~isleft], k=k, max_dist=max_dist) + return self def get_nearest_vert_index(coords, isleft, surf_lh, surf_rh, verbose=False): From 91823c0b0f5a79be0c49f338115099ea78dab1fd Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 10:30:37 -0500 Subject: [PATCH 03/11] Update plot_intracranial_electrodes.py --- .../plot_intracranial_electrodes.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/examples/brain_plotting/plot_intracranial_electrodes.py b/examples/brain_plotting/plot_intracranial_electrodes.py index 7c517e5..98c1bf4 100644 --- a/examples/brain_plotting/plot_intracranial_electrodes.py +++ b/examples/brain_plotting/plot_intracranial_electrodes.py @@ -77,6 +77,22 @@ dist_from_HG = brain.distance_from_region(coords, isleft, region='pmHG', metric='surf') print(dist_from_HG) +############################################################################### +# Smoothly interpolate values over the brain's surface from electrodes + +brain_pial = Brain('pial', subject_dir='./fsaverage/') + +# As an example, we will use the y coordinate of the electrode +values_per_electrode = coords[:,1] - coords[:,1].min() + +# Interpolate onto only the temporal lobe, using the 5 nearest neighbor interpolation with +# a maximum distance of 10mm +brain_pial.interpolate_electrodes_onto_brain(coords, values, isleft, roi='temporal', max_dist=10) + +# Plot the overlay for just the left hemisphere +fig, axes = plot_brain_overlay(brain_pial, cmap='Reds', view='lateral', figsize=(12,6), hemi='lh') +plt.show() + ############################################################################### # Create a brain with the inflated surface for plotting brain = Brain('inflated', subject_dir='./fsaverage/').split_hg('midpoint').split_stg().simplify_labels() From 191ff023cb3a586bc224e275081d79f32f18f8d3 Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 10:31:39 -0500 Subject: [PATCH 04/11] Update plot_intracranial_electrodes.py --- examples/brain_plotting/plot_intracranial_electrodes.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/brain_plotting/plot_intracranial_electrodes.py b/examples/brain_plotting/plot_intracranial_electrodes.py index 98c1bf4..c57de4d 100644 --- a/examples/brain_plotting/plot_intracranial_electrodes.py +++ b/examples/brain_plotting/plot_intracranial_electrodes.py @@ -80,17 +80,15 @@ ############################################################################### # Smoothly interpolate values over the brain's surface from electrodes -brain_pial = Brain('pial', subject_dir='./fsaverage/') - # As an example, we will use the y coordinate of the electrode values_per_electrode = coords[:,1] - coords[:,1].min() # Interpolate onto only the temporal lobe, using the 5 nearest neighbor interpolation with # a maximum distance of 10mm -brain_pial.interpolate_electrodes_onto_brain(coords, values, isleft, roi='temporal', max_dist=10) +brain.interpolate_electrodes_onto_brain(coords, values, isleft, roi='temporal', k=5, max_dist=10) # Plot the overlay for just the left hemisphere -fig, axes = plot_brain_overlay(brain_pial, cmap='Reds', view='lateral', figsize=(12,6), hemi='lh') +fig, axes = plot_brain_overlay(brain, cmap='Reds', view='lateral', figsize=(12,6), hemi='lh') plt.show() ############################################################################### From 269984f271fb334b12f74845d495353422bd3989 Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 10:32:50 -0500 Subject: [PATCH 05/11] Update __init__.py --- naplib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naplib/__init__.py b/naplib/__init__.py index 73e426c..923162b 100644 --- a/naplib/__init__.py +++ b/naplib/__init__.py @@ -56,5 +56,5 @@ def set_logging(level: Union[int, str]): from .data import Data, join_fields, concat import naplib.naplab -__version__ = "2.2.0" +__version__ = "2.3.0" From c5e4219475e818c436dfae0f36447be0872691a7 Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 10:34:44 -0500 Subject: [PATCH 06/11] Update freesurfer.py --- naplib/localization/freesurfer.py | 47 ++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/naplib/localization/freesurfer.py b/naplib/localization/freesurfer.py index 2df0151..9730c42 100644 --- a/naplib/localization/freesurfer.py +++ b/naplib/localization/freesurfer.py @@ -111,6 +111,11 @@ } region2num = {v: k for k, v in num2region.items()} +temporal_regions_nums = [33, 34, 35, 36, 74, 41, 43, 72, 73, 38, 37, 75, 76, 77, 78, 79, 80, 81] +temporal_regions_superlist = [num2region[num] for num in temporal_regions_nums] +temporal_regions_superlist += ['alHG','pmHG','HG','TTS','PT','PP','MTG','ITG','mSTG','pSTG','STG','STS','T.Pole'] + + num2region_mni = { 0: 'unknown', 1: 'bankssts', @@ -767,7 +772,7 @@ def paint_overlay(self, labels, value=1): self.has_overlay_cells[add_overlay == 1] = True return self - def interpolate_electrodes_onto_brain(self, coords, values, k, max_dist): + def interpolate_electrodes_onto_brain(self, coords, values, k, max_dist, roi='all'): """ Use electrode coordinates to interpolate 1-dimensional values corresponding to each electrode onto the brain's surface. @@ -782,6 +787,9 @@ def interpolate_electrodes_onto_brain(self, coords, values, k, max_dist): Number of nearest neighbors to consider max_dist : float Maximum distance outside of which nearest neighbors will be ignored + roi : list of strings, or string in {'all', 'temporal'}, default='all' + Regions to allow interpolation over. By default, the entire brain surface + is allowed. Can also be specified as a list of string labels (drawing from self.label_names) Notes ----- @@ -789,6 +797,20 @@ def interpolate_electrodes_onto_brain(self, coords, values, k, max_dist): for a quick matplotlib plot, or you can extract the surface values from the ``self.overlay`` attribute for plotting with another tool like pysurfer. """ + + if isinstance(roi, str) and roi == 'all': + roi_list = self.label_names + elif isinstance(roi, str) and roi == 'temporal': + if self.atlas == 'MNI152': + raise ValueError("roi='temporal' is not supported for MNI brain. Must specify list of specific region names") + roi_list = temporal_regions_superlist + else: + roi_list = roi + assert isinstance(roi, list) + + roi_list_subset = [x for x in roi_list if x in self.label_names] + zones_to_include, _, _ = self.zones(roi_list_subset) + # Euclidean distances from each surface vertex to each coordinate dists = cdist(self.surf[0], coords) sorted_dists = np.sort(dists, axis=-1)[:, :k] @@ -812,7 +834,7 @@ def interpolate_electrodes_onto_brain(self, coords, values, k, max_dist): total_weight = np.nansum(weights, axis=1) # # Normalize to get final smoothed values - updated_vertices = total_weight > 0 + updated_vertices = np.logical_and(total_weight > 0, zones_to_include) 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 smoothed_values = np.where(updated_vertices, weighted_sum / total_weight, np.nan) @@ -1235,7 +1257,7 @@ def reset_overlay_except(self, labels): self.rh.reset_overlay_except(labels) return self - def interpolate_electrodes_onto_brain(self, coords, values, isleft=None, k=10, max_dist=20, reset_overlay_first=True): + def interpolate_electrodes_onto_brain(self, coords, values, isleft=None, k=10, max_dist=10, roi='all', reset_overlay_first=True): """ Use electrode coordinates to interpolate 1-dimensional values corresponding to each electrode onto the brain's surface. @@ -1246,11 +1268,17 @@ def interpolate_electrodes_onto_brain(self, coords, values, isleft=None, k=10, m 3D coordinates of electrodes values : np.ndarray (elecs,) Value for each electrode - k : int + isleft : np.ndarray (elecs,), optional + If provided, specifies a boolean which is True for each electrode that is in the left hemisphere. + If not given, this will be inferred from the first dimension of the coords (negative is left). + k : int, default=10 Number of nearest neighbors to consider - max_dist : float - Maximum distance outside of which nearest neighbors will be ignored - reset_overlay_first : bool + max_dist : float, default=10 + Maximum distance (in mm) outside of which nearest neighbors will be ignored + roi : list of strings, or string in {'all', 'temporal'}, default='all' + Regions to allow interpolation over. By default, the entire brain surface + is allowed. Can also be specified as a list of string labels (drawing from self.lh.label_names) + reset_overlay_first : bool, default=True If True (default), reset the overlay before creating a new overlay Notes @@ -1263,8 +1291,8 @@ def interpolate_electrodes_onto_brain(self, coords, values, isleft=None, k=10, m self.reset_overlay() if isleft is None: isleft = coords[:,0] < 0 - self.lh.interpolate_electrodes_onto_brain(coords[isleft], values[isleft], k=k, max_dist=max_dist) - self.rh.interpolate_electrodes_onto_brain(coords[~isleft], values[~isleft], k=k, max_dist=max_dist) + self.lh.interpolate_electrodes_onto_brain(coords[isleft], values[isleft], k=k, max_dist=max_dist, roi=roi) + self.rh.interpolate_electrodes_onto_brain(coords[~isleft], values[~isleft], k=k, max_dist=max_dist, roi=roi) return self @@ -1306,4 +1334,3 @@ def find_closest_vertices(surface_coords, point_coords): point_coords = np.atleast_2d(point_coords) dists = cdist(surface_coords, point_coords) return np.argmin(dists, axis=0), np.min(dists, axis=0) - From 3b0ed3ec927c1c81e4fd820a47fa2913205cf58d Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 10:35:35 -0500 Subject: [PATCH 07/11] Update surfdist.py --- naplib/utils/surfdist.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/naplib/utils/surfdist.py b/naplib/utils/surfdist.py index 8fef3c2..0db54a9 100644 --- a/naplib/utils/surfdist.py +++ b/naplib/utils/surfdist.py @@ -126,6 +126,8 @@ def surfdist_viz( bg_on_stat=False, figsize=None, ax=None, + vmin=None, + vmax=None, ): """Visualize results on cortical surface using matplotlib. @@ -232,8 +234,10 @@ def surfdist_viz( # Ensure symmetric colour range, based on Nilearn helper function: # https://github.com/nilearn/nilearn/blob/master/nilearn/plotting/img_plotting.py#L52 - vmax = max(-np.nanmin(stat_map_faces), np.nanmax(stat_map_faces)) - vmin = -vmax + if vmax is None: + vmax = max(-np.nanmin(stat_map_faces), np.nanmax(stat_map_faces)) + if vmin is None: + vmin = -vmax if threshold is not None: kept_indices = np.where(abs(stat_map_faces) >= threshold)[0] From b962389b89748adee60ccee27b8c55cb5f8f8a58 Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 10:36:09 -0500 Subject: [PATCH 08/11] Update brain_plots.py --- naplib/visualization/brain_plots.py | 64 ++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/naplib/visualization/brain_plots.py b/naplib/visualization/brain_plots.py index 1f5a828..ec5bcf5 100644 --- a/naplib/visualization/brain_plots.py +++ b/naplib/visualization/brain_plots.py @@ -74,24 +74,26 @@ def _view(hemi, mode: str = "lateral", backend: str = "mpl"): raise ValueError(f"Unknown `mode`: {mode}.") -def _plot_hemi(hemi, cmap="coolwarm", ax=None, denorm=False, view="best", thresh=None): +def _plot_hemi(hemi, cmap="coolwarm", ax=None, view="best", thresh=None, vmin=None, vmax=None): surfdist_viz( *hemi.surf, hemi.overlay, *_view(hemi.hemi, mode=view), - cmap=cmap(hemi.overlay.max()) if denorm else cmap, + cmap=cmap, threshold=thresh, alpha=hemi.alpha, bg_map=hemi.sulc, bg_on_stat=True, ax=ax, + vmin=vmin, + vmax=vmax ) ax.axes.set_axis_off() ax.grid(False) def plot_brain_overlay( - brain, cmap="coolwarm", ax=None, denorm=False, view="best", **kwargs + brain, cmap="coolwarm", ax=None, hemi='both', denorm=False, view="best", cmap_quantile=1.0, **kwargs ): """ Plot brain overlay on the 3D cortical surface using matplotlib. @@ -106,10 +108,17 @@ def plot_brain_overlay( Colormap to use. ax : list | tuple of matplotlib Axes 2 Axes to plot the left and right hemispheres with. + hemi : {'both', 'lh', 'rh'}, default='both' + Hemisphere(s) to plot. If 'both', then 2 subplots are created, one for each hemisphere. + Otherwise only one hemisphere is displayed with its overlay. denorm : bool, default=False Whether to center the overlay labels around 0 or not before sending to the colormap. view : {'lateral','medial','frontal','top','best'}, default='best' Which view to plot for each hemisphere. + cmap_quantile : float (optional), default=1.0 + If less than 1, will only use the central ``cmap_quantile`` portion of the range + of values to create the vmin and vmax for the colormap. For example, if set to 0.95, + then only the middle 95% of the values will be used to set the range of the colormap. **kwargs : kwargs Any other kwargs to pass to matplotlib.pyplot.figure (such as figsize) @@ -121,14 +130,49 @@ def plot_brain_overlay( """ fig = plt.figure(**kwargs) if ax is None: - ax1 = fig.add_subplot(1, 2, 1, projection="3d") - ax2 = fig.add_subplot(1, 2, 2, projection="3d") - ax = (ax1, ax2) + if hemi in ['both', 'b']: + ax1 = fig.add_subplot(1, 2, 1, projection="3d") + ax2 = fig.add_subplot(1, 2, 2, projection="3d") + ax = (ax1, ax2) + else: + ax = fig.add_subplot(1, 1, 1, projection="3d") + if hemi in ['left','lh']: + ax = [ax, None] + elif hemi in ['right','rh']: + ax = [None, ax] else: - ax1, ax2 = ax - - _plot_hemi(brain.lh, cmap, ax1, denorm, view=view) - _plot_hemi(brain.rh, cmap, ax2, denorm, view=view) + if hemi in ['both', 'b']: + assert len(ax) == 2 + + if cmap_quantile is not None: + assert cmap_quantile <= 1 and cmap_quantile > 0 + cmap_diff = (1.0 - cmap_quantile) / 2. + vmin_l = np.quantile(brain.lh.overlay[brain.lh.overlay!=0], cmap_diff) + vmax_l = np.quantile(brain.lh.overlay[brain.lh.overlay!=0], 1.0 - cmap_diff) + vmin_r = np.quantile(brain.lh.overlay[brain.rh.overlay!=0], cmap_diff) + vmax_r = np.quantile(brain.lh.overlay[brain.rh.overlay!=0], 1.0 - cmap_diff) + else: + vmin_l = brain.lh.overlay[brain.lh.overlay!=0].min() + vmax_l = brain.lh.overlay[brain.lh.overlay!=0].max() + vmin_r = brain.lh.overlay[brain.rh.overlay!=0].min() + vmax_r = brain.lh.overlay[brain.rh.overlay!=0].max() + + # determine vmin and vmax + if hemi in ['both', 'b']: + vmin = min([vmin_l, vmin_r]) + vmax = min([vmax_l, vmax_r]) + elif hemi in ['left','lh']: + vmin = vmin_l + vmax = vmax_l + elif hemi in ['right','rh']: + vmin = vmin_r + vmax = vmax_r + + + if ax[0] is not None: + _plot_hemi(brain.lh, cmap, ax[0], view=view, vmin=vmin, vmax=vmax) + if ax[1] is not None: + _plot_hemi(brain.rh, cmap, ax[1], view=view, vmin=vmin, vmax=vmax) return fig, ax From 898e668352dbac6b488893457214a4416417408d Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 10:36:48 -0500 Subject: [PATCH 09/11] Update plot_intracranial_electrodes.py --- examples/brain_plotting/plot_intracranial_electrodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/brain_plotting/plot_intracranial_electrodes.py b/examples/brain_plotting/plot_intracranial_electrodes.py index c57de4d..f502b26 100644 --- a/examples/brain_plotting/plot_intracranial_electrodes.py +++ b/examples/brain_plotting/plot_intracranial_electrodes.py @@ -85,7 +85,7 @@ # Interpolate onto only the temporal lobe, using the 5 nearest neighbor interpolation with # a maximum distance of 10mm -brain.interpolate_electrodes_onto_brain(coords, values, isleft, roi='temporal', k=5, max_dist=10) +brain.interpolate_electrodes_onto_brain(coords, values_per_electrode, isleft, roi='temporal', k=5, max_dist=10) # Plot the overlay for just the left hemisphere fig, axes = plot_brain_overlay(brain, cmap='Reds', view='lateral', figsize=(12,6), hemi='lh') From 6a63bf6a27cb2b433baf1f66367cd493c5bfc99c Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 10:54:00 -0500 Subject: [PATCH 10/11] Update freesurfer.py --- naplib/localization/freesurfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naplib/localization/freesurfer.py b/naplib/localization/freesurfer.py index 9730c42..72088aa 100644 --- a/naplib/localization/freesurfer.py +++ b/naplib/localization/freesurfer.py @@ -1285,7 +1285,7 @@ def interpolate_electrodes_onto_brain(self, coords, values, isleft=None, k=10, m ----- After running this function, you can use the visualization function ``plot_brain_overlay`` for a quick matplotlib plot, or you can extract the surface values from the ``self.lh.overlay`` - and ``self.lh.overlay_cell`` attributes, etc, for plotting with another tool like pysurfer. + and ``self.rh.overlay`` attributes, etc, for plotting with another tool like pysurfer or plotly. """ if reset_overlay_first: self.reset_overlay() From 05aabc496d2600f19914d5fca498aa97bf08c470 Mon Sep 17 00:00:00 2001 From: Gavin Mischler Date: Fri, 14 Feb 2025 11:14:28 -0500 Subject: [PATCH 11/11] Update brain_plots.py --- naplib/visualization/brain_plots.py | 38 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/naplib/visualization/brain_plots.py b/naplib/visualization/brain_plots.py index ec5bcf5..17dd656 100644 --- a/naplib/visualization/brain_plots.py +++ b/naplib/visualization/brain_plots.py @@ -115,10 +115,12 @@ def plot_brain_overlay( Whether to center the overlay labels around 0 or not before sending to the colormap. view : {'lateral','medial','frontal','top','best'}, default='best' Which view to plot for each hemisphere. - cmap_quantile : float (optional), default=1.0 - If less than 1, will only use the central ``cmap_quantile`` portion of the range + cmap_quantile : float | tuple of floats (optional), default=1.0 + If a single float less than 1, will only use the central ``cmap_quantile`` portion of the range of values to create the vmin and vmax for the colormap. For example, if set to 0.95, - then only the middle 95% of the values will be used to set the range of the colormap. + then only the middle 95% of the values will be used to set the range of the colormap. If a tuple, + then it should specify 2 quantiles, one for the vmin and one for the vmax, such as (0.025, 0.975), + which would be equivalent to passing a single value of 0.95. **kwargs : kwargs Any other kwargs to pass to matplotlib.pyplot.figure (such as figsize) @@ -144,30 +146,40 @@ def plot_brain_overlay( if hemi in ['both', 'b']: assert len(ax) == 2 + if cmap_quantile is not None: - assert cmap_quantile <= 1 and cmap_quantile > 0 - cmap_diff = (1.0 - cmap_quantile) / 2. - vmin_l = np.quantile(brain.lh.overlay[brain.lh.overlay!=0], cmap_diff) - vmax_l = np.quantile(brain.lh.overlay[brain.lh.overlay!=0], 1.0 - cmap_diff) - vmin_r = np.quantile(brain.lh.overlay[brain.rh.overlay!=0], cmap_diff) - vmax_r = np.quantile(brain.lh.overlay[brain.rh.overlay!=0], 1.0 - cmap_diff) + if isinstance(cmap_quantile, float): + assert cmap_quantile <= 1 and cmap_quantile > 0 + cmap_diff = (1.0 - cmap_quantile) / 2. + vmin_l = np.quantile(brain.lh.overlay[brain.lh.overlay!=0], cmap_diff) + vmax_l = np.quantile(brain.lh.overlay[brain.lh.overlay!=0], 1.0 - cmap_diff) + vmin_r = np.quantile(brain.rh.overlay[brain.rh.overlay!=0], cmap_diff) + vmax_r = np.quantile(brain.rh.overlay[brain.rh.overlay!=0], 1.0 - cmap_diff) + elif isinstance(cmap_quantile, tuple): + vmin_l = np.quantile(brain.lh.overlay[brain.lh.overlay!=0], cmap_quantile[0]) + vmax_l = np.quantile(brain.lh.overlay[brain.lh.overlay!=0], cmap_quantile[1]) + vmin_r = np.quantile(brain.rh.overlay[brain.rh.overlay!=0], cmap_quantile[0]) + vmax_r = np.quantile(brain.rh.overlay[brain.rh.overlay!=0], cmap_quantile[1]) + else: + raise ValueError('cmap_quantile must be either a float or a tuple') else: vmin_l = brain.lh.overlay[brain.lh.overlay!=0].min() vmax_l = brain.lh.overlay[brain.lh.overlay!=0].max() - vmin_r = brain.lh.overlay[brain.rh.overlay!=0].min() - vmax_r = brain.lh.overlay[brain.rh.overlay!=0].max() + vmin_r = brain.rh.overlay[brain.rh.overlay!=0].min() + vmax_r = brain.rh.overlay[brain.rh.overlay!=0].max() + # determine vmin and vmax if hemi in ['both', 'b']: vmin = min([vmin_l, vmin_r]) - vmax = min([vmax_l, vmax_r]) + vmax = max([vmax_l, vmax_r]) elif hemi in ['left','lh']: vmin = vmin_l vmax = vmax_l elif hemi in ['right','rh']: vmin = vmin_r vmax = vmax_r - + if ax[0] is not None: _plot_hemi(brain.lh, cmap, ax[0], view=view, vmin=vmin, vmax=vmax)