ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python实现脑电地形图:从原理到Matplotlib可视化实践

Python实现脑电地形图:从原理到Matplotlib可视化实践 1. 项目概述与核心价值最近在整理一些脑电数据分析的旧项目发现一个挺有意思的需求如何用Python把一堆枯燥的脑电通道数据变成一张直观、专业的脑电地形图这玩意儿在科研和临床里叫“脑电地形图”或“脑电拓扑图”说白了就是把每个电极点的电位值通过插值算法“画”在一个模拟的头皮轮廓上用颜色深浅来表示脑电活动的强弱。听起来好像挺复杂得用专业的脑电处理软件其实不然用Python的Matplotlib和NumPy配合一点几何知识完全可以从零开始实现一个轻量级但功能完整的显示工具。我之所以花时间折腾这个是因为很多现成的脑电处理包比如MNE-Python虽然功能强大但有时候我们只是想快速验证一下某个频段比如Alpha波在不同脑区的分布或者给报告配张图启动一个庞大的库反而显得笨重。自己动手实现一遍不仅能彻底搞懂从数据到图像的核心流程还能根据实际需求灵活定制颜色映射、电极位置、甚至头型轮廓这种掌控感是直接用现成工具无法比拟的。这个项目非常适合有一定Python和数据分析基础想深入理解脑电可视化原理或者需要定制化脑电地形图的研究者、工程师和学生。2. 核心思路与方案设计实现脑电地形图核心任务就一个将离散的、在头皮特定点采集到的电位数据转换为一个覆盖整个头皮区域的、连续平滑的彩色图像。这里面的技术链条可以拆解为几个关键环节。2.1 从数据到图像的技术链条拆解首先你得有数据。假设我们有一个实验用了标准的10-20系统放置了19个电极。那么原始数据就是一个19列的矩阵每一列代表一个电极在某个时间点或某个频段比如经过傅里叶变换后的Alpha波段功率的数值。这些电极在三维空间里有坐标X, Y, Z但为了画二维图我们通常使用它们在头皮表面的二维投影坐标比如使用球面投影或极坐标投影。拿到这19个点的坐标和数值后直接画散点图是没意义的因为点与点之间是大片空白。所以第二步也是最核心的一步就是插值。我们需要根据这19个已知点的值去估算头皮表面上其他无数个未知点的值。常用的插值算法有距离反比加权IDW、径向基函数RBF如高斯函数、多重二次函数和最近邻等。对于脑电地形图RBF插值因为能产生比较平滑的结果而更常用。插值之后我们就得到了一个覆盖整个头皮区域的、由密集网格点构成的数值矩阵。第三步就是将这个数值矩阵渲染成图像。这需要定义一个颜色映射Colormap比如用‘viridis’表示从低到高或者用‘RdBu_r’表示正负适用于电位数据。最后将这个彩色图像绘制在一个画好的头皮轮廓一个圆或者更精细的包含鼻子、耳朵轮廓的图形内部就得到了最终的地形图。2.2 工具选型为什么是Matplotlib NumPy SciPy这个组合几乎是Python科学计算和可视化的“标准答案”选择它们理由非常充分NumPy 脑电数据本质是数组运算。电极数据是数组插值生成的网格数据也是数组。NumPy提供了高效的多维数组对象和广播机制所有矩阵操作、数学计算都离不开它。比如计算电极间的距离矩阵用NumPy的向量化操作比用Python循环快成百上千倍。SciPy 它是插值算法的“武器库”。scipy.interpolate.Rbf径向基函数插值和scipy.interpolate.griddata网格数据插值函数可以直接拿来用避免了手写复杂插值算法的麻烦。尤其是Rbf函数几行代码就能实现平滑插值非常方便。Matplotlib 它是绘图的绝对主力。我们不仅要用它画最终的地形图imshow或pcolormesh还要画头皮轮廓、电极位置点、颜色条colorbar。它的Subplot功能可以轻松实现多子图布局方便同时展示多个时间点或频段的地形图。其丰富的颜色映射和精细的图形控制能力能满足出版级的绘图需求。这个工具链成熟、稳定、文档丰富社区支持强大遇到问题几乎都能找到解决方案。相比于使用更专业的脑电工具箱这个组合给了我们从底层理解每一个步骤的机会。2.3 方案设计中的关键决策在动手写代码前有几个设计上的决策点需要想清楚电极坐标系统 是用真实的3D坐标X, Y, Z然后投影到2D还是直接使用预设的2D平面坐标对于标准10-20系统网上可以找到现成的2D平面坐标模板x, y取值范围通常在-1到1之间的单位圆内。为了简化本项目采用后者。这避免了复杂的球面几何计算足以满足大多数展示需求。插值区域与掩膜 插值是在一个方形网格上进行的但我们只需要圆形头皮区域内的部分。这就需要创建一个圆形掩膜mask在绘制时将圆形区域外的像素设为透明或背景色。如何生成一个平滑的圆形边缘避免锯齿也是一个小细节。颜色映射与归一化 数据范围可能每次都不一样。是每次都用数据的最大最小值来自动归一化还是固定一个范围比如±100μV通常自动归一化更通用但固定范围有利于不同图之间的比较。颜色映射的选择也影响解读例如用序列色系sequential表示功率强度用发散色系diverging表示正负电位。性能考量 插值网格的密度分辨率直接影响图像平滑度和计算时间。网格太粗图看起来有马赛克太细计算慢。对于19个电极选择64x64或128x128的网格通常是一个好的平衡点。3. 核心实现步骤详解下面我将一步步拆解如何用代码实现上述思路。我们会从准备模拟数据开始直到画出一张完整的地形图。3.1 环境搭建与数据准备首先确保你的Python环境安装了必要的库。可以通过pip安装pip install numpy scipy matplotlib我们使用模拟数据来演示。假设有19个电极标准10-20系统的部分位置我们为它们分配一个简单的2D坐标这里为了示例坐标是简化的实际应用应使用标准坐标文件。import numpy as np import matplotlib.pyplot as plt from scipy.interpolate import Rbf # 1. 定义19个电极的简化2D坐标 (x, y) 范围大约在-1到1之间 electrode_names [Fp1,Fp2,F3,F4,C3,C4,P3,P4,O1,O2, F7,F8,T3,T4,T5,T6,Fz,Cz,Pz] # 这里是一个示例坐标实际应从标准位置文件读取 np.random.seed(42) # 为了可重复性 x np.random.uniform(-0.9, 0.9, 19) y np.random.uniform(-0.9, 0.9, 19) # 简单调整让点分布更像个头型实际项目请务必使用真实坐标 x[[0,1,9,10,14,15]] * 0.7 # 调整前部和后部电极 y[[6,7,8,9,14,15]] 0.2 # 调整顶部和枕部电极 # 2. 为每个电极生成模拟的脑电数据例如Alpha波段功率 # 假设我们模拟一个“后部Alpha更强”的模式 data np.zeros(19) # 定义后部电极的索引例如O1, O2, P3, P4等 posterior_idx [6, 7, 8, 9, 14, 15] # 对应P3,P4,O1,O2,T5,T6 data[posterior_idx] np.random.uniform(0.7, 1.0, len(posterior_idx)) # 其他区域强度较低 other_idx [i for i in range(19) if i not in posterior_idx] data[other_idx] np.random.uniform(0.1, 0.4, len(other_idx)) print(f电极坐标形状: {x.shape}, {y.shape}) print(f脑电数据形状: {data.shape}) print(f数据范围: {data.min():.2f} ~ {data.max():.2f})注意 这里的电极坐标是随机生成的仅用于演示流程。在实际项目中你必须使用标准化的电极位置坐标。可以查找诸如mne库中的标准电极位置文件或者从easycap等标准帽子的配置文件中读取。使用错误的坐标会导致地形图完全失真失去科学意义。3.2 构建插值网格与执行插值接下来我们要在一个比头皮范围稍大的方形区域上创建密集的网格点然后利用已知的电极数据对这些网格点进行插值。# 3. 创建插值网格 # 定义网格的分辨率越高图像越平滑但计算越慢 grid_resolution 64 # 生成从-1.2到1.2的线性空间范围稍大于电极坐标范围确保覆盖整个头型 xi np.linspace(-1.2, 1.2, grid_resolution) yi np.linspace(-1.2, 1.2, grid_resolution) # 生成网格点坐标矩阵 xi, yi np.meshgrid(xi, yi) # 4. 使用径向基函数RBF进行插值 # 这里选择‘multiquadric’作为径向基函数epsilon参数影响平滑度 rbf_interpolator Rbf(x, y, data, functionmultiquadric, epsilon2) # 对网格上的所有点进行插值 zi rbf_interpolator(xi, yi) print(f插值网格数据形状: {zi.shape})这里有几个关键点grid_resolution 我选择了64这是一个经验值。对于19个电极64x64的网格已经能提供足够平滑的视觉效果计算也很快。你可以尝试128或256观察画质和速度的变化。np.linspace的范围 我用了-1.2到1.2比电极坐标的-1到1范围大一些。这是为了确保插值能覆盖到头皮轮廓的边缘避免边缘出现空白或奇怪的值。Rbf函数function参数可以选择‘multiquadric’、‘gaussian’、‘inverse’等不同的函数会产生略微不同的平滑效果。epsilon参数控制函数的形状对于‘multiquadric’较大的epsilon会使插值曲面更平滑。这个参数可能需要根据你的数据分布进行微调。3.3 创建圆形掩膜与数据处理插值得到的zi是一个方形矩阵但我们只需要其中圆形区域内的部分。我们需要创建一个圆形掩膜。# 5. 创建圆形掩膜用于只显示头皮范围内的区域 # 计算网格上每个点到中心(0,0)的距离 radius 1.0 # 假设头皮半径为1 distance_from_center np.sqrt(xi**2 yi**2) # 创建掩膜距离大于半径的位置为True需要被掩盖 mask distance_from_center radius # 将掩膜外的插值数据设置为NaN非数字这样绘图时这些区域会自动变成透明如果设置的话或背景色 zi_masked zi.copy() zi_masked[mask] np.nan # 可选平滑掩膜边缘避免锯齿 # 可以创建一个软过渡的边缘但为了简单起见我们先使用硬边缘。将圆形区域外的数据设为np.nan是Matplotlib处理这类问题的标准做法。imshow在遇到NaN值时默认会将其渲染为完全透明如果背景是透明的或者显示为坐标轴背景色。3.4 使用Matplotlib进行可视化现在我们有了插值并掩膜后的数据zi_masked可以开始画图了。# 6. 使用Matplotlib绘制脑电地形图 fig, ax plt.subplots(figsize(8, 6), dpi100) # 绘制地形图插值后的彩色图像 # 使用pcolormesh或imshow。imshow需要指定extent来匹配坐标范围。 # 这里用pcolormesh因为它天然接受网格坐标。 topomap ax.pcolormesh(xi, yi, zi_masked, shadingauto, cmapviridis, edgecolorsnone) # 添加头皮轮廓一个圆 circle plt.Circle((0, 0), radius, colorblack, fillFalse, linewidth2) ax.add_patch(circle) # 标记电极位置 ax.scatter(x, y, colorred, s50, edgecolorswhite, linewidth1.5, labelElectrodes, zorder5) # 可选为电极添加标签 # for i, name in enumerate(electrode_names): # ax.annotate(name, (x[i], y[i]), xytext(5,5), textcoordsoffset points, fontsize8) # 添加颜色条colorbar cbar plt.colorbar(topomap, axax, shrink0.8, pad0.05) cbar.set_label(Alpha Power (a.u.), rotation270, labelpad15) # 设置图形属性 ax.set_aspect(equal) # 保证x轴和y轴比例相同圆才不会变成椭圆 ax.set_xlim(-1.3, 1.3) ax.set_ylim(-1.3, 1.3) ax.set_title(EEG Topography - Alpha Band, fontsize14, fontweightbold) ax.set_xlabel(X (normalized)) ax.set_ylabel(Y (normalized)) ax.legend(locupper right) # 隐藏坐标轴根据审美偏好可选 # ax.axis(off) plt.tight_layout() plt.show()这段代码完成了核心的绘图工作pcolormesh(xi, yi, zi_masked, ...) 这是绘制地形图的核心。它根据网格坐标xi, yi和对应的值zi_masked来填充颜色。shading‘auto’让颜色平滑过渡。cmap‘viridis’指定了颜色映射这是一个感知均匀的色系非常适合表示强度数据。plt.Circle 画了一个黑色的圆圈代表头皮轮廓。ax.scatter 用红色的点标出了19个电极的实际位置方便对照。plt.colorbar 添加了颜色条并设置了标签。shrink和pad参数用来调整颜色条的大小和位置。ax.set_aspect(‘equal’)至关重要这确保了X轴和Y轴的单位长度一致否则画出来的圆会变成椭圆整个地形图就失真了。运行这段代码你应该能看到一张显示“后部Alpha功率更强”的脑电地形图。4. 高级功能与定制化实践一张基础的地形图已经完成但在实际应用中我们往往有更复杂的需求。下面介绍几个常见的增强功能。4.1 多子图绘制与对比分析我们经常需要同时对比多个时间点、多个频段、或者不同实验条件的地形图。Matplotlib的subplots功能可以轻松实现。# 模拟4个不同时间点或频段的数据 num_plots 4 fig, axes plt.subplots(2, 2, figsize(12, 10)) axes axes.flatten() # 将2x2的axes数组展平为1维方便循环 # 假设我们有4组不同的数据这里用随机生成模拟差异 all_data_simulated [] for i in range(num_plots): # 在基础数据上添加一些随机扰动模拟不同状态 perturbed_data data np.random.normal(0, 0.1, data.shape) # 确保数据在合理范围内 perturbed_data np.clip(perturbed_data, 0, 1.2) all_data_simulated.append(perturbed_data) # 为每个子图绘制地形图 for idx, ax in enumerate(axes): current_data all_data_simulated[idx] # 为当前数据重新插值注意实际应用中如果电极坐标不变可以复用插值器只改数据 rbf_i Rbf(x, y, current_data, functionmultiquadric, epsilon2) zi_current rbf_i(xi, yi) zi_current_masked zi_current.copy() zi_current_masked[mask] np.nan # 绘制 im ax.pcolormesh(xi, yi, zi_current_masked, shadingauto, cmapviridis, vmin0, vmax1.2) ax.add_patch(plt.Circle((0, 0), radius, colorblack, fillFalse, linewidth1.5)) ax.scatter(x, y, colorred, s20, edgecolorswhite, linewidth0.5) ax.set_aspect(equal) ax.set_xlim(-1.3, 1.3) ax.set_ylim(-1.3, 1.3) ax.set_title(fCondition/Timestamp {idx1}, fontsize10) ax.axis(off) # 多子图时通常关闭坐标轴更美观 # 为整个图添加一个共享的颜色条 # 找到所有子图中数据的最大最小值用于统一colorbar范围 all_zi np.concatenate([d.ravel() for d in all_data_simulated]) vmin, vmax all_zi.min(), all_zi.max() # 更新每个子图的颜色范围可选这里演示统一范围 for im in fig.images: im.set_clim(vmin, vmax) # 调整布局为colorbar腾出空间 fig.subplots_adjust(right0.9) # 在图形右侧添加一个colorbar cbar_ax fig.add_axes([0.92, 0.15, 0.02, 0.7]) # [left, bottom, width, height] cbar fig.colorbar(im, caxcbar_ax) cbar.set_label(Power (a.u.), rotation270, labelpad15) plt.suptitle(EEG Topography Comparison Across Conditions, fontsize16, fontweightbold) plt.tight_layout(rect[0, 0, 0.9, 0.95]) # 调整布局给总标题和colorbar留空间 plt.show()实操心得 在多子图绘制时统一颜色条colorbar的范围至关重要。如果每个子图都按照自己的数据范围归一化那么颜色的深浅在不同子图间就失去了可比性。上面的代码演示了如何计算所有数据的全局最小最大值vmin,vmax并在绘图时通过pcolormesh的vmin和vmax参数或者事后通过set_clim方法来强制所有子图使用相同的颜色映射范围。这样才能进行有效的视觉对比。4.2 颜色映射Colorbar的深度定制颜色映射的选择和设置直接影响图的专业性和可读性。选择色系序列色系Sequential 如‘viridis’,‘plasma’,‘summer’用于表示从低到高的强度数据如功率、幅度。发散色系Diverging 如‘RdBu_r’,‘coolwarm’,‘seismic’用于表示有正有负、有中间值的数据如事件相关电位ERP的电压0点附近是基线。分类色系Qualitative 如‘tab10’用于区分不同类别在地形图中较少用。自定义颜色条刻度与标签# 接前面的绘图代码 cbar plt.colorbar(topomap, axax, shrink0.8, pad0.05) cbar.set_label(Voltage (μV), rotation270, labelpad20, fontsize12) # 设置刻度位置和格式 cbar.set_ticks([-5, 0, 5, 10]) # 指定刻度位置 cbar.set_ticklabels([-5 μV, 0, 5 μV, 10 μV]) # 自定义刻度标签 # 或者使用格式化字符串 # cbar.ax.yaxis.set_major_formatter(plt.FormatStrFormatter(%.1f))离散化颜色条 有时为了突出几个关键阈值需要将连续的颜色条分成几段。import matplotlib.colors as mcolors # 定义边界和对应的颜色 bounds [-10, -2, 2, 10] norm mcolors.BoundaryNorm(boundariesbounds, ncolors256) # 绘图时使用这个norm topomap ax.pcolormesh(xi, yi, zi_masked, shadingauto, cmapRdBu_r, normnorm)4.3 添加鼻尖与耳廓参考点更专业的头皮轮廓图会包含鼻子和耳朵的简单示意使方向感更强。# 在画圆的基础上添加简单的鼻子和耳朵标记 # 头皮轮廓圆 circle plt.Circle((0, 0), radius, colork, fillFalse, linewidth2, zorder1) ax.add_patch(circle) # 鼻尖 (画一个朝上的小三角形) nose_length 0.15 ax.plot([0, 0], [radius, radius nose_length], colork, linewidth2, zorder1) # 也可以用一个多边形表示鼻子 # from matplotlib.patches import Polygon # nose Polygon([[0, radius], [-0.05, radiusnose_length], [0.05, radiusnose_length]], closedTrue, fillTrue, colork, zorder1) # ax.add_patch(nose) # 耳朵 (在圆的两侧画两个小半圆或弧线) import matplotlib.patches as mpatches ear_angle 30 # 耳朵与中心连线和水平线的夹角度 ear_radius 0.08 ear_distance radius * np.cos(np.radians(ear_angle)) # 耳朵中心的x坐标 ear_height radius * np.sin(np.radians(ear_angle)) # 耳朵中心的y坐标 # 左耳 left_ear mpatches.Arc((-ear_distance, ear_height), widthear_radius*2, heightear_radius*1.5, angle0, theta190, theta2270, colork, linewidth2, zorder1) ax.add_patch(left_ear) # 右耳 right_ear mpatches.Arc((ear_distance, ear_height), widthear_radius*2, heightear_radius*1.5, angle0, theta1270, theta290, colork, linewidth2, zorder1) ax.add_patch(right_ear)添加这些解剖学标记后图形立刻显得专业了许多读者也能一眼分辨出前鼻子方向后无鼻子方向左右。5. 性能优化与常见问题排查当电极数量增多如64导、128导或需要批量生成大量地形图时性能可能成为瓶颈。此外实践中总会遇到一些“坑”。5.1 插值算法选择与性能权衡我们之前用的Rbf在小规模数据上很快但当电极点超过100个时计算量会显著增加因为它需要计算每个网格点到所有电极点的距离。scipy.interpolate.griddata提供了另一种选择。from scipy.interpolate import griddata # 假设我们有更多的电极点坐标和数据 (x_highd, y_highd, data_highd) # ... # 使用griddata进行线性插值速度通常比Rbf快 zi_grid griddata((x_highd, y_highd), data_highd, (xi, yi), methodlinear, fill_valuenp.nan) # method 可以是 linear, nearest, cubicmethod‘linear’ 执行线性插值速度快但结果可能不够平滑在电极点稀疏的区域会有明显的三角形纹理。method‘cubic’ 三次样条插值更平滑但计算更慢且要求数据点排列规则对于脑电坐标可能不满足。method‘nearest’ 最近邻插值最快但结果呈块状不连续。选择建议 对于标准密度的脑电数据如64导Rbf的‘multiquadric’或‘gaussian’在质量和速度上是一个不错的平衡。如果电极数非常多256且对实时性要求高可以尝试griddata的‘linear’方法或者考虑对电极坐标进行下采样后再用Rbf。5.2 图像边缘锯齿与平滑处理我们之前用mask distance_from_center radius创建的掩膜是“硬”边缘在低分辨率下地形图与头皮圆的交界处可能会有锯齿。有两种改善方法提高网格分辨率 将grid_resolution从64提高到128或256这是最直接有效的方法但会增加计算量。使用软掩膜Alpha通道 在圆形边缘附近创建一个透明度渐变区域实现平滑过渡。# 创建软掩膜Alpha通道 # distance_from_center 是每个网格点到圆心的距离 soft_width 0.05 # 过渡带宽度 # Alpha值在半径内为1在半径外为0在过渡带内线性衰减 alpha_mask np.ones_like(distance_from_center) # 过渡带外的区域完全在圆外 outer_mask distance_from_center (radius soft_width/2) # 过渡带内的区域 transition_mask (distance_from_center (radius - soft_width/2)) ~outer_mask # 计算过渡带内的alpha值 (从1线性衰减到0) alpha_mask[transition_mask] 1 - (distance_from_center[transition_mask] - (radius - soft_width/2)) / soft_width alpha_mask[outer_mask] 0 # 绘图时使用pcolormesh的alpha参数但pcolormesh不支持逐点alpha需用imshow # 或者更简单的方法将掩膜外的数据设为NaNMatplotlib的imshow会自动处理。 # 软掩膜通常用于自定义混合或更高级的渲染基础需求下提高分辨率即可。5.3 典型错误与调试清单在实际操作中你可能会遇到以下问题问题现象可能原因排查与解决思路图形是一个方块不是圆形1. 忘记应用圆形掩膜。2. 掩膜逻辑错误没有将外部数据设为NaN。3. 使用了imshow但没有正确设置extent且aspect不是‘equal’。1. 检查zi_masked[mask] np.nan这行代码是否执行。2. 打印mask或zi_masked的一小部分看NaN值是否正确设置。3. 确保ax.set_aspect(‘equal’)被调用。如果使用imshow需设置extent[-1.2, 1.2, -1.2, 1.2]。地形图颜色一片均匀没有变化1. 插值失败zi的所有值都相同或接近。2. 数据本身方差很小。3. 颜色映射范围vmin,vmax设置不当覆盖了整个数据范围。1. 打印zi的最小值和最大值看是否有变化。2. 打印原始data检查数据是否真的有差异。3. 检查Rbf插值是否成功尝试不同的function和epsilon参数。4. 检查pcolormesh或imshow是否传入了不合适的vmin/vmax。电极点位置看起来不对电极坐标x, y数据错误或与预期坐标系不符如上下颠倒。1.这是最常见也最严重的问题。务必核实电极坐标来源。标准坐标的Y轴通常是鼻尖方向为正。可以简单画个散点图plt.scatter(x, y)先看看点的大致分布是否像一个头型前部、后部、左耳、右耳。2. 检查坐标是否进行了归一化通常在单位圆内。插值结果在电极点间有奇怪的“峰”或“谷”Rbf的epsilon参数设置不当。epsilon太小会导致过拟合在电极点处出现尖峰太大则过于平滑丢失细节。调整epsilon参数。一个经验法则是将其设置为电极点之间的平均距离。可以尝试几个值如0.5, 1, 2, 5观察地形图形状的变化。绘图速度非常慢1. 网格分辨率grid_resolution设置过高如512。2. 电极数量很多且使用Rbf插值。3. 在循环中重复创建Rbf插值器。1. 降低网格分辨率到128或64。2. 对于高密度电极考虑使用griddata的‘linear’方法。3. 如果电极坐标不变只需在循环外创建一次Rbf插值器在循环内只更新数据rbf_interpolator.values new_data注意Rbf对象创建后不能直接修改值需重新初始化或使用其他方法。5.4 封装与复用构建自己的地形图函数为了提高效率最好将整个流程封装成一个函数。def plot_topography(x, y, data, electrode_namesNone, radius1.0, grid_res64, cmapviridis, interp_methodrbf, titleEEG Topography, with_headTrue, with_ears_noseTrue, axNone): 绘制脑电地形图。 参数 x, y : 一维数组电极的2D坐标。 data : 一维数组与电极对应的数据值。 electrode_names : 列表电极名称标签。 radius : 浮点数头皮轮廓圆的半径。 grid_res : 整数插值网格的分辨率。 cmap : 字符串Matplotlib颜色映射名称。 interp_method : 字符串插值方法 (rbf 或 griddata)。 title : 字符串图形标题。 with_head : 布尔值是否绘制头皮轮廓。 with_ears_nose : 布尔值是否绘制耳朵和鼻子。 ax : Matplotlib轴对象如果为None则创建新图。 返回 fig, ax : 图形和轴对象。 if ax is None: fig, ax plt.subplots(figsize(6,5), dpi100) else: fig ax.figure # --- 插值 --- xi np.linspace(-radius*1.2, radius*1.2, grid_res) yi np.linspace(-radius*1.2, radius*1.2, grid_res) xi, yi np.meshgrid(xi, yi) if interp_method.lower() rbf: rbf Rbf(x, y, data, functionmultiquadric, epsilon2) zi rbf(xi, yi) elif interp_method.lower() griddata: from scipy.interpolate import griddata zi griddata((x, y), data, (xi, yi), methodlinear, fill_valuenp.nan) else: raise ValueError(interp_method must be rbf or griddata) # --- 掩膜 --- distance np.sqrt(xi**2 yi**2) mask distance radius zi_masked zi.copy() zi_masked[mask] np.nan # --- 绘图 --- im ax.pcolormesh(xi, yi, zi_masked, shadingauto, cmapcmap) if with_head: ax.add_patch(plt.Circle((0,0), radius, colork, fillFalse, linewidth2)) if with_ears_nose: # 简化的鼻子和耳朵可根据需要完善 ax.plot([0,0], [radius, radius*1.15], colork, linewidth2) # 简单耳朵标记 ax.plot([-radius*0.85, -radius], [radius*0.1, radius*0.1], colork, linewidth2) ax.plot([radius*0.85, radius], [radius*0.1, radius*0.1], colork, linewidth2) # 绘制电极点 ax.scatter(x, y, colorred, s30, edgecolorswhite, linewidth1, zorder5) if electrode_names is not None: for i, name in enumerate(electrode_names): ax.annotate(name, (x[i], y[i]), xytext(3,3), textcoordsoffset points, fontsize6, hacenter) ax.set_aspect(equal) ax.set_xlim(-radius*1.3, radius*1.3) ax.set_ylim(-radius*1.3, radius*1.3) ax.set_title(title) ax.axis(off) # 添加colorbar plt.colorbar(im, axax, shrink0.8, pad0.03, labelIntensity (a.u.)) plt.tight_layout() return fig, ax # 使用函数 fig, ax plot_topography(x, y, data, electrode_nameselectrode_names, titleCustom Topography Plot, interp_methodrbf) plt.show()封装成函数后只需准备好坐标和数据一行代码就能生成标准地形图极大提升了代码的复用性和可维护性。你可以把这个函数保存为单独的.py文件作为你自己的脑电可视化工具库的一部分。
返回列表