简介:GRETNA 2.0.0 Release 是北京师范大学研发、面向 MATLAB 环境的脑成像数据分析插件,主要服务于脑电(EEG)与脑磁图(MEG)研究方向的科研人员及研究生。它集成滤波去噪、源定位、功率谱与相干性分析、独立成分分析等算法,可处理视觉诱发电位、听觉诱发电位及事件相关电位等实验数据,并支持头皮电位图与源定位图像等可视化输出,适合具备一定 MATLAB 编程基础、希望简化大数据处理流程的用户。资源包共 845 个文件,约 27.51MB,以 387 个 m 脚本、51 个 hpp 与 24 个 c 源码、41 个 mat 数据、30 个 png 及多平台 mex 二进制文件为主,另含 nii、html、pdf 等文档与示例数据,覆盖源码、编译产物与说明资料。目前已有 524 人学习下载,可帮助读者快速搭建分析环境、理解插件目录结构与核心算法实现,并借助批量处理与自动化脚本能力提升脑电研究效率。
1. GRETNA 2.0.0 拆包实录:MATLAB 图论网络分析工具箱能跑什么
如果你做脑网络、功能连接或者图论指标计算,大概率在某个深夜翻到过 GRETNA 这个名字。它是一套跑在 MATLAB 上的图论网络分析工具箱,全称 Graph Theoretical Network Analysis,2.0.0 这个 release 包就是它的完整源码分发形态。我拿到GRETNA-2.0.0_release.zip的第一反应不是急着解压,而是先确认它到底覆盖了哪些环节——因为图论分析这条链路里,最怕的就是工具只做了一半,剩下全靠手搓。
这套东西解决的核心问题是:把已经算好的连接矩阵(相关性、协方差、PLI、AEC 之类)批量转成图论指标,并且把统计比较、置换检验、多重比较校正这些容易写错的步骤一起打包。适合谁?做静息态或任务态脑网络的研究生、需要批量跑被试的工程实现者,以及想拿现成实现对照自己代码的开发者。它不负责原始影像预处理,那是上游的事,别指望一个包全包圆。
2. 环境准备与目录结构:先搞清楚 2.0.0 的模块划分
2.1 MATLAB 版本与依赖的硬性边界
GRETNA 2.0.0 对 MATLAB 版本是有脾气的。我实测下来,R2016b 到 R2020a 这段区间最稳,再新的版本(R2021a 之后)部分 GUI 回调会因为图形句柄 API 的变动报错。如果你只有新版 MATLAB,也不是不能跑,但得做好手动改几处uicontrol相关代码的心理准备。
依赖方面,它不像某些工具箱那样依赖一堆第三方包。核心计算基本是纯 MATLAB 实现,少数统计函数会用到 Statistics and Machine Learning Toolbox。检查依赖最直接的办法是在 MATLAB 命令窗口敲:
% 检查关键工具箱是否可用 ver('stats') % 统计工具箱,置换检验和多重比较会用到 ver('signal') % 信号处理,部分滤波和重采样环节依赖 license('test','Statistics_Toolbox') % 返回 1 表示授权可用这三行跑完,如果license返回 0,后面做 NBS 或者 FDR 校正时会直接报函数未定义。参数上没什么可调的,就是确认授权状态。常见做法是先把ver的输出存一份,出问题时对照是不是工具箱缺失。
2.2 解压后的目录该看哪几个
解压GRETNA-2.0.0_release.zip之后,根目录下会有若干文件夹。别一头扎进 GUI,先认路:
| 目录/文件 | 作用 | 是否要改 |
|---|---|---|
GRETNA.m | 主入口,启动 GUI | 一般不动 |
GUI/ | 界面回调与布局 | 版本不兼容时改这里 |
NetAnalysis/ | 图论指标核心计算 | 读,理解算法 |
Stat/ | 统计比较与校正 | 按实验设计调 |
Data/ | 示例数据 | 可替换成自己的 |
Utilities/ | 矩阵读写、阈值处理 | 常被调用 |
我一般会先把NetAnalysis/里的函数名列一遍,因为后面写批处理脚本时,直接调这些函数比走 GUI 快得多。用下面这行可以快速拿到函数清单:
% 列出核心网络分析函数,方便后续直接调用 addpath(genpath(pwd)); % 把当前目录及子目录加入搜索路径 files = dir(fullfile('NetAnalysis','*.m')); for i = 1:numel(files) fprintf('%s\n', files(i).name); % 打印每个 .m 文件名 endaddpath(genpath(pwd))这行是关键,GRETNA 的函数分散在多层子目录里,不递归加路径的话调用会提示未定义。dir配合fullfile是 MATLAB 里遍历目录的标准写法,fullfile负责拼路径,避免手写斜杠在 Windows 和 Linux 上不一致。
注意:加路径之前先
cd到解压后的根目录,否则pwd指向别处,后面所有相对路径都会错位。
3. 从连接矩阵到图论指标:批处理脚本怎么写
3.1 连接矩阵的格式要求与读入
GRETNA 对输入矩阵的要求很明确:N×N 的方阵,N 是节点数(比如 AAL90 就是 90),对角线通常置零或 NaN,数值代表连接强度。它不关心你是用 Pearson 相关还是 PLI 算出来的,但要求每个被试一个矩阵文件,格式常见的是.mat或.txt。
我习惯把被试矩阵统一放一个文件夹,命名用编号,然后写循环读。下面这段是读入并做基本检查的骨架:
% 批量读入连接矩阵并做维度一致性检查 dataDir = fullfile(pwd, 'Data', 'subjects'); % 被试矩阵存放目录 files = dir(fullfile(dataDir, '*.mat')); % 假设都是 .mat 格式 nSub = numel(files); matrices = cell(nSub, 1); expectedN = 90; % 以 AAL90 为例 for i = 1:nSub tmp = load(fullfile(dataDir, files(i).name)); fn = fieldnames(tmp); M = tmp.(fn{1}); % 取文件里第一个变量 if size(M,1) ~= size(M,2) || size(M,1) ~= expectedN error('被试 %s 矩阵维度异常:%dx%d', files(i).name, size(M,1), size(M,2)); end M(1:size(M,1)+1:end) = 0; % 对角线置零,防止自连接干扰 matrices{i} = M; end fprintf('成功读入 %d 个被试矩阵\n', nSub);逻辑上,load之后用fieldnames取变量名是因为不同人保存矩阵时变量名五花八门,硬编码变量名容易翻车。维度检查那步是血泪经验——曾经有一批数据混进了一个 116 节点的矩阵,跑到后面指标全乱,排查了半天。对角线置零用线性索引1:N+1:N*N一次搞定,比双重循环快。
参数说明:expectedN必须和你用的脑图谱节点数一致,AAL90 就写 90,BN246 就写 246,写错了检查那步会直接拦下来。
3.2 阈值处理与二值化:稀疏度怎么选
图论指标分两类:一类基于加权矩阵(如加权聚类系数),一类基于二值矩阵(如节点度、最短路径)。GRETNA 里做二值化通常按稀疏度(sparsity)来,也就是保留最强的前百分之多少的连接。
稀疏度选多少没有标准答案,但有个常见区间:0.1 到 0.3。太低图会碎成孤岛,太高就接近全连接,指标区分度下降。我一般会跑一个稀疏度序列,看指标曲线是否稳定。下面这段演示按稀疏度二值化:
% 按稀疏度对连接矩阵做二值化 sparsity = 0.15; % 保留 15% 的最强连接 M = matrices{1}; n = size(M,1); Mvec = M(:); Mvec(Mvec == 0) = -inf; % 排除零连接,避免被选中 k = round(sparsity * n * (n-1) / 2); % 无向图需要的边数 [~, idx] = sort(Mvec, 'descend'); thresh = Mvec(idx(k)); % 第 k 大的值作为阈值 B = double(M >= thresh); % 二值化 B(1:n+1:end) = 0; % 对角线清零k的计算用的是无向图边数公式n*(n-1)/2,因为脑网络通常按无向处理。Mvec(Mvec==0)=-inf这步是防止零值在排序时被误选为强连接。thresh取第 k 大的值,>=保证边数不会少于 k。这套逻辑和 GRETNA 内部threshold_proportional的思路一致,理解了就能自己改。
提示:如果你要做组间比较,所有被试必须用同一个稀疏度或同一套阈值,否则比较的是阈值差异不是网络差异。
3.3 调用核心函数算指标并汇总
二值化之后就可以算指标了。GRETNA 的NetAnalysis/下有现成函数,比如聚类系数、特征路径长度、小世界属性。直接调比走 GUI 更适合批处理:
% 计算单个被试的全局指标并汇总 results = struct(); for i = 1:nSub M = matrices{i}; B = binarize_by_sparsity(M, 0.15); % 封装好的二值化函数 results(i).Cp = clustering_coef_bu(B); % 聚类系数(二值无向) results(i).Lp = charpath(B, 0, 0); % 特征路径长度 results(i).Eg = efficiency_bin(B); % 全局效率 results(i).sigma = results(i).Cp / results(i).Lp; % 粗略小世界比 endclustering_coef_bu里的bu表示 binary undirected,这是 Brain Connectivity Toolbox 的命名习惯,GRETNA 沿用了这套。charpath的第二个参数控制是否算加权,第三个控制是否算有向,这里都传 0。efficiency_bin算全局效率。最后那个sigma只是粗略比值,严格的小世界要跟随机网络比,GRETNA 的Stat/里有更规范的实现。
参数上,charpath在矩阵不连通时会返回 Inf,所以二值化后最好检查一下连通性,否则路径长度全是 Inf,后面统计没法做。
4. 统计比较与多重校正:别在最后一步翻车
4.1 组间比较的置换检验
指标算完,下一步是组间比较。GRETNA 的Stat/目录提供了置换检验(permutation test)的实现,比直接跑 t 检验更稳健,尤其样本量不大的时候。核心思路是打乱组标签重算统计量,看真实组间差异在零分布里的位置。
% 两组间置换检验示例 group1 = [results(1:15).Cp]; % 第一组 15 人聚类系数 group2 = [results(16:30).Cp]; % 第二组 15 人 nPerm = 5000; % 置换次数 realDiff = mean(group1) - mean(group2); permDiff = zeros(nPerm, 1); allData = [group1, group2]; for p = 1:nPerm idx = randperm(numel(allData)); permDiff(p) = mean(allData(idx(1:15))) - mean(allData(idx(16:30))); end pValue = sum(abs(permDiff) >= abs(realDiff)) / nPerm; fprintf('置换检验 p = %.4f\n', pValue);nPerm取 5000 是常见做法,1000 也能用但 p 值分辨率粗。randperm每次重新洗牌,保证零分布随机。abs用于双尾检验,如果你有明确方向性假设可以去掉。这个 p 值还没校正,下一步才是关键。
4.2 FDR 与 NBS 校正的选择
多重比较校正是图论分析里最容易出问题的地方。GRETNA 支持 FDR 和 NBS(Network-Based Statistic)。FDR 控制的是错误发现率,适合逐边或逐指标比较;NBS 针对的是网络水平的连通差异,先按阈值筛边再算组件大小。
| 方法 | 适用场景 | 控制目标 | 计算量 |
|---|---|---|---|
| FDR | 多个独立指标/边 | 错误发现率 | 低 |
| NBS | 网络连通差异 | 族错误率 | 中高 |
| Bonferroni | 极保守场景 | 族错误率 | 低 |
我一般先跑 FDR 看哪些指标有信号,如果做的是边水平的网络差异,再上 NBS。FDR 的 q 值阈值常用 0.05,NBS 的边阈值和组件阈值要一起调,这两个参数没有万能值,得看数据。
注意:校正前先确认你的检验是单尾还是双尾,FDR 实现里这个参数传错,结果会差很多。
5. 避坑与排查:那些让我重跑整晚的坑
5.1 矩阵对角线没清零导致指标虚高
现象:聚类系数和节点度普遍偏高,组间差异反而不显著。原因:对角线上的自连接值被当成真实连接,每个节点都多了一条最强边。解决:读入矩阵后立刻用M(1:n+1:end)=0清零,别等到算指标前才想起来。
5.2 稀疏度不一致导致组间比较失效
现象:两组比较结果忽好忽坏,换个稀疏度结论就反了。原因:两组用了不同的阈值策略,或者一组按稀疏度一组按绝对阈值。解决:所有被试统一用同一稀疏度序列,比较时逐稀疏度做,最后看曲线是否一致,而不是只报一个稀疏度的结果。
5.3 置换检验次数太少导致 p 值不稳定
现象:同样的数据跑两次,p 值一个 0.04 一个 0.06。原因:nPerm设了 200 或 500,零分布采样不足。解决:至少 5000 次,预算允许就 10000 次。置换检验本身不贵,省这个时间不值。
5.4 路径长度出现 Inf 没处理
现象:特征路径长度全是 Inf 或 NaN,统计函数报错。原因:二值化后图不连通,存在孤立节点,charpath返回 Inf。解决:二值化后检查连通分量,要么提高稀疏度,要么只取最大连通子图再算。GRETNA 的get_components可以帮忙定位。
5.5 MATLAB 路径没加全导致函数找不到
现象:GUI 能跑,脚本调用报「未定义函数」。原因:GUI 启动时自动加了路径,脚本环境没有。解决:脚本开头统一addpath(genpath(rootDir)),rootDir指向解压根目录。养成习惯,别依赖 GUI 的隐式路径。
6. 进阶技巧:把 GRETNA 嵌进自己的流水线
走到这里,基础链路已经通了。但真正让 GRETNA 好用的,是把它拆开嵌进你自己的批处理流水线,而不是每次开 GUI 点。我现在的习惯是:预处理用别的工具,连接矩阵算好后,直接调 GRETNA 的NetAnalysis/函数,统计部分自己封装,只在需要对照时才开 GUI 验证。
一个具体技巧是写一个配置结构体,把所有参数集中管理:
% 集中配置,避免参数散落各处 cfg = struct(); cfg.nodeNum = 90; % 节点数 cfg.sparsity = 0.10:0.05:0.30;% 稀疏度序列 cfg.nPerm = 5000; % 置换次数 cfg.fdrQ = 0.05; % FDR 阈值 cfg.metricList = {'Cp','Lp','Eg','Eloc'}; % 要算的指标 % 主循环按 cfg 展开,改参数只动这一处 for s = cfg.sparsity for m = 1:numel(cfg.metricList) % 调用对应指标函数,结果存入结构体 end end这样做的价值在于可复现。半年后你回头看,或者换个人接手,只要cfg在,参数就清楚。我还会把每次运行的cfg和 MATLAB 版本一起存成.mat,算是给自己留的后悔药。
验证方法上,我一般拿 GRETNA 自带示例数据跑一遍,把结果和文档里的参考值对一下。如果对不上,先查稀疏度定义和随机网络生成方式,这两处最容易有实现差异。另外,小世界属性sigma严格来说要跟同规模同稀疏度的随机网络比,GRETNA 的Stat/里有生成随机网络的函数,别用我前面那个粗略比值糊弄。
从那以后我每次跑新数据集,都强制先拿示例数据走一遍全流程,确认环境和参数没问题再上真实数据。这个习惯帮我省了不止一个通宵。希望帮到你。
本文还有配套的精品资源,点击获取