Brainstorm脚本[数据批处理设置]
脑磁静息态数据批处理的一个brainstorm脚本
brainstorm文件层级的解析
Brainstorm 核心概念辨析
在深入代码之前,我们先厘清几个关键概念的属性和作用:
-
协议
- 属性:一个 Brainstorm 协议是所有实验数据、被试、分析的顶级容器。它对应文件系统中的一个特定文件夹,里面包含了所有子文件夹和数据库条目。
- 作用:用于管理和隔离不同的研究项目。例如,你可以有一个“视觉诱发电位_研究”协议和一个“睡眠脑电_研究”协议,它们彼此完全独立。
-
被试
- 属性:属于某个协议。一个协议下可以有多个被试。每个被试有其自己的解剖结构(MRI)、记录文件等。
Subject(iSubj).FileName就是指向该被试在 Brainstorm 数据库中定义的文件的路径。 - 作用:代表一个独立的个体或实验对象。允许对同一个被试进行多次记录或不同条件的实验。
- 属性:属于某个协议。一个协议下可以有多个被试。每个被试有其自己的解剖结构(MRI)、记录文件等。
-
研究
- 属性:属于某个被试(对于单个被试研究)或一组被试(对于组分析研究)。一个研究是条件或会话的容器。例如,对于一个被试“John”,你可能有两个研究:“John/声音刺激”和“John/光刺激”。
- 作用:用于组织在特定实验条件下收集的数据。一个研究内部包含了一组数据文件和结果文件。
-
数据文件 & 结果文件
- 属性:属于某个研究。
- 作用:
- 数据文件:存储原始的或预处理后的脑电/脑磁记录。
- 结果文件:存储源成像、统计分析等计算后的结果。
这些概念的关系可以用一个层级结构来表示:协议 -> 一个或多个被试 -> 一个或多个研究 -> 一个或多个数据/结果文件
代码逐行解析
现在,我们来看您的代码,它完美地演示了如何在这个层级结构中导航。
sSubjects = bst_get('ProtocolSubjects');
- 作用:
bst_get是 Brainstorm API 中最核心的函数之一,用于从 Brainstorm 数据库中获取各种对象。 - 解析:
‘ProtocolSubjects’作为参数,表示“获取当前已打开协议下的所有被试信息”。 - 返回值:
sSubjects是一个结构体数组,其中包含了当前协议中所有被试的详细信息(如文件名、名称、路径等)。
[sStudies, iStudies] = bst_get('StudyWithSubject', sSubjects.Subject(iSubj).FileName);
- 作用:根据一个被试的文件名,找到所有包含该被试的研究。
- 参数解析:
‘StudyWithSubject’:告诉bst_get函数你想要进行“通过被试找研究”的查询。sSubjects.Subject(iSubj).FileName:sSubjects.Subject是包含所有被试的数组。(iSubj)是你指定的某个被试的索引(例如,1 代表第一个被试)。.FileName是该被试在 Brainstorm 数据库中的唯一标识符。
- 返回值解析:
sStudies:一个结构体数组,包含了找到的所有相关研究的完整信息(如研究名称、条件、以及其包含的所有数据文件、结果文件、通道文件等)。这是你后续操作(如读取数据、进行计算)的主要对象。iStudies:一个整数数组,包含了这些研究在 Brainstorm 数据库中的索引。这个索引通常用于其他需要指定研究位置的 Brainstorm 函数调用(例如,bst_process(‘CallProcess’, ..., iStudies, ...))。
总结与类比
您可以把这段代码想象成在文件系统中进行操作:
sSubjects = bst_get('ProtocolSubjects’);- 就像进入一个项目文件夹(
cd /My_Project)然后列出所有子文件夹(ls),这些子文件夹就是以被试名字命名的。
sSubjects有Subject和sSubjects.DefaultSubject两个结构。
- 就像进入一个项目文件夹(
[sStudies, iStudies] = bst_get(...)- 就像你选中一个被试文件夹(例如
Subject_01),然后找出这个文件夹下所有以实验条件命名的子文件夹(例如Auditory/,Visual/)。 sStudies就像是这些条件文件夹的完整内容列表(里面有哪些文件)。iStudies就像是这些条件文件夹的快捷方式或索引号,方便你快速指向它们。
- 就像你选中一个被试文件夹(例如
典型工作流程
在 Brainstorm 脚本中,您通常会这样使用它们:
% 获取当前协议的所有被试
sSubjects = bst_get('ProtocolSubjects');
numSubjects = length(sSubjects.Subject);
% 遍历每个被试
for iSubj = 1:numSubjects
% 获取该被试的所有研究
[sStudies, iStudies] = bst_get('StudyWithSubject', sSubjects.Subject(iSubj).FileName);
% 遍历该被试的每个研究
for iStd = 1:length(sStudies)
currentStudy = sStudies(iStd);
% 现在你可以操作这个研究下的数据了
% 例如,获取该研究下的所有数据文件
dataFiles = currentStudy.Data;
for iFile = 1:length(dataFiles)
% 对每个数据文件进行处理...
end
end
end
总而言之,您提供的这两行代码是 Brainstorm 脚本编程中用于定位和获取研究数据的基础和关键步骤。它们体现了 Brainstorm 以被试和研究为核心的数据组织哲学。
关于 sStudies 结构体内部具体字段或后续处理步骤的问题,
fieldnames(sStudies)
{‘Name’ }
{‘FileName’ }
{‘DateOfStudy’ }
{‘BrainStormSubject’}
{‘Condition’ }
{‘Channel’ }
{‘iChannel’ }
{‘Data’ }
{‘HeadModel’ }
{‘iHeadModel’ }
{‘Result’ }
{‘Stat’ }
{‘Image’ }
{‘NoiseCov’ }
{‘Dipoles’ }
{‘Timefreq’ }
{‘Matrix’ }
% 启动 Brainstorm(无 GUI)
if ~brainstorm('status')
brainstorm nogui
end
% 设置协议名称
protocol_name = 'RestingState_BatchProcessing2';
% 检查协议是否存在
iProtocol = bst_get('Protocol', protocol_name);
if isempty(iProtocol)
error('协议 "%s" 不存在,请先运行数据导入脚本', protocol_name);
end
% 设置当前协议
gui_brainstorm('SetCurrentProtocol', iProtocol);
fprintf('当前协议: %s\n', protocol_name);
%% 获取所有被试
sSubjects = bst_get('ProtocolSubjects');
if isempty(sSubjects.Subject)
error('协议中没有被试数据');
end
fprintf('找到 %d 个被试\n', length(sSubjects.Subject));
下面给我的脚本的ai描述和代码。
这是一个用于脑磁图(MEG)数据批量预处理的Brainstorm脚本。让我详细解析其处理流程和思路:
一、整体目标
批量处理静息态MEG数据,进行系统性的预处理和源空间分析。
二、核心处理流程
1. 初始化阶段
% 启动Brainstorm无界面模式
% 设置并验证协议
% 获取所有被试信息
- 以无GUI模式运行Brainstorm,适合批量处理
- 验证数据协议存在性
- 获取被试列表,跳过默认模板
2. 预处理参数配置
params.ecg_channel = 'ECG003'; % 心电通道
params.eog_channel = 'EOG002'; % 眼电通道
params.highpass_freq = 0.5; % 高通滤波
params.bandstop_freq = 100; % 带阻滤波
params.notch_freqs = [50, 100]; % 陷波滤波(工频干扰)
params.resample_freq = 250; % 重采样频率
- 针对MEG数据特点设置滤波参数
- 主要去除低频漂移、高频噪声和工频干扰
3. 核心处理步骤(按顺序)
步骤1-2:心电伪迹去除
1. 检测心跳事件 → 2. SSP心电去除
- 使用ECG通道检测心跳
- SSP(信号空间投影)去除心电伪迹
步骤3-4:眼电伪迹去除
3. 检测眼电事件 → 4. SSP眼电去除
- 使用EOG通道检测眨眼
- SSP去除眼电伪迹
步骤5-7:滤波处理
5. 高通滤波(0.5Hz) → 6. 带阻滤波(100Hz) → 7. 陷波滤波(50/100Hz)
- 去除低频漂移(0.5Hz高通)
- 去除高频噪声(100Hz带阻)
- 去除电源线干扰(50Hz及谐波)
步骤8:重采样
- 统一采样率为250Hz,减少计算量
步骤9:噪声协方差复制
% 从task-noise study复制噪声协方差矩阵到当前study
% 这是源定位的关键步骤
- 确保使用正确的噪声估计进行源定位
步骤10-11:源定位计算
10. 计算头模型 → 11. 源分析
- 建立正向模型(头模型)
- 使用最小范数估计进行逆问题求解
- 计算源空间活动
4. 智能处理机制
错误处理机制
function safe_process(...)
% 如果当前步骤失败,使用上一步结果继续
% 记录失败步骤但不中断流程
- 单步失败不影响整体流程
- 详细的错误日志记录
跳过已处理被试
% 检查是否已有重采样数据
% 避免重复处理
- 提高处理效率
- 支持断点续处理
5. 数据组织逻辑
Study结构管理
% 查找特定task-rest数据
% 区分不同实验条件
% 管理文件依赖关系
- 按实验条件(task-rest/task-noise)组织数据
- 保持Brainstorm数据库的整洁性
文件关联
- 确保中间处理文件正确关联
- 保持处理链条的完整性
6. 质量控制与报告
处理状态跟踪
processing_summary = {}; % 记录每个被试的处理状态
failed_subjects = {}; % 记录完全失败的被试
failed_steps = {}; % 记录部分失败的步骤
最终报告生成
- 统计成功/失败数量
- 列出具体失败原因
- 便于问题排查
三、关键技术要点
1. SSP去伪迹技术
- 使用信号空间投影分离伪迹成分
- 不直接删除数据,保留更多有用信息
2. 分层滤波策略
- 先去除生理伪迹,再进行频率滤波
- 避免滤波影响伪迹检测
3. 源定位流程
原始数据 → 预处理 → 头模型 → 逆问题求解 → 源空间数据
- 标准的MEG源分析流程
- 使用最小范数估计方法
4. 批量处理优化
- 自动遍历所有被试
- 统一的参数设置
- 并行处理支持(通过循环)
四、应用场景
适合用于:
- 静息态MEG研究 - 默认处理流程
- 临床研究 - 帕金森患者与健康对照
- 大样本分析 - 自动批量处理
- 标准化预处理 - 确保处理一致性
可能需要的调整:
- 通道名称 - 根据实际设备调整ECG/EOG通道名
- 滤波参数 - 根据数据质量调整
- 源定位方法 - 可根据研究需求选择不同算法
- 质量控制 - 可添加更多质量检查步骤
这个脚本提供了一个完整的、稳健的MEG数据处理流水线,特别适合需要处理多个被试的科学研究项目。
直接给出我的批处理脚本
clc; clear; close all;
% 启动 Brainstorm(无 GUI)
if ~brainstorm('status')
brainstorm nogui
end
% 设置协议名称
%修改协议名称
% protocol_name = 'RestingState_pd_patientContorl';
protocol_name = 'RestingState_HealthContorl';
% 检查协议是否存在
iProtocol = bst_get('Protocol', protocol_name);
if isempty(iProtocol)
error('协议 "%s" 不存在,请先运行数据导入脚本', protocol_name);
end
% 设置当前协议
gui_brainstorm('SetCurrentProtocol', iProtocol);
fprintf('当前协议: %s\n', protocol_name);
%% 预处理参数配置
params = struct();
params.ecg_channel = 'ECG003'; % 心电通道名
params.eog_channel = 'EOG002'; % 眼电通道名
% 分析时间窗应该是动态获取的,如何获取,在后面处理的时候,读取文件获取
params.highpass_freq = 0.5; % 高通滤波频率 (Hz)
params.bandstop_freq = 100; % 带阻滤波频率 (Hz)
params.notch_freqs = [50, 100]; % 陷波滤波频率 (Hz)
params.resample_freq = 250; % 重采样频率 (Hz)
% 是否跳过已处理的被试
skip_processed = true; % true=跳过, false=重新处理
%% 获取所有被试
sSubjects = bst_get('ProtocolSubjects');
if isempty(sSubjects.Subject)
error('协议中没有被试数据');
end
fprintf('找到 %d 个被试\n', length(sSubjects.Subject));
%% 批量处理每个被试
failed_subjects = {};
processing_summary = {};
for iSubj = 1:length(sSubjects.Subject)
subject_name = sSubjects.Subject(iSubj).Name;
% 跳过默认解剖模板
if strcmpi(subject_name, 'Group_analysis')
continue;
end
fprintf('\n========================================\n');
fprintf('=== 处理被试 %d/%d: %s ===\n', iSubj, length(sSubjects.Subject), subject_name);
fprintf('========================================\n');
try
% 获取该被试的所有 study
[sStudies, iStudies] = bst_get('StudyWithSubject', sSubjects.Subject(iSubj).FileName);
if isempty(sStudies)
warning('被试 %s 没有找到任何 study', subject_name);
continue;
end
% 导入时,计算过协方差矩阵了,因此,此处设置一个noise_from_room的路径记录
noise_study_idx = [];
noise_cov_file = '';
for k = 1:length(sStudies)
if contains(sStudies(k).Name, 'task-noise') ...
&& ~isempty(sStudies(k).NoiseCov)
noise_study_idx = k;
noise_cov_file = sStudies(k).NoiseCov(1).FileName;
break;
end
end
% 查找 task-rest 的原始数据
rest_file_found = false;
for iStudy = 1:length(sStudies)
study_name = sStudies(iStudy).Name;
% 只处理包含 'task-rest' 的 study
if ~contains(study_name, 'task-rest')
continue;
end
fprintf('\n处理 Study: %s\n', study_name);
% 检查是否有数据
if isempty(sStudies(iStudy).Data)
fprintf(' 该 Study 中没有数据文件\n');
continue;
end
% 查找原始数据文件 (包含Comment 'Link' 的文件)
raw_file_idx = [];
for iData = 1:length(sStudies(iStudy).Data)
if contains(sStudies(iStudy).Data(iData).Comment, 'Link')
raw_file_idx = iData;
break;
end
end
if isempty(raw_file_idx)
warning(' 未找到原始数据文件 (0raw)');
continue;
end
data_file = sStudies(iStudy).Data(raw_file_idx).FileName;
fprintf(' 找到原始数据: %s\n', data_file);
% 把comment的Link作为原始数据的
% 检查是否已经处理过 (通过查找重采样后的文件)
if skip_processed
processed = false;
for k = 1:length(sStudies)
for iData = 1:length(sStudies(k).Data)
comment = lower(sStudies(k).Data(iData).Comment);
if contains(comment, 'resample') || contains(comment, 'resampled')
processed = true;
break;
end
end
if processed
break;
end
end
if processed
fprintf(' ⚠ 检测到已有重采样数据,跳过该被试所有后续处理\n');
continue;
end
end
rest_file_found = true;
% 开始处理流程
sFiles = {data_file};
sFiles_cache = {};
failed_steps = {};
% 启动报告
bst_report('Start', sFiles);
sData = in_bst_data(data_file);
params.time_window = sData.F.prop.times;
% === 步骤 1: 检测心跳 ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_evt_detect_ecg', '检测心跳', [], ...
'channelname', params.ecg_channel, ...
'timewindow', params.time_window, ...
'eventname', 'cardiac');
% === 步骤 2: SSP 心电去除 ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_ssp_ecg', 'SSP心电去除', [], ...
'eventname', 'cardiac', ...
'sensortypes', 'MEG', ...
'usessp', 1, ...
'select', 1);
% === 步骤 3: 检测眼电 ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_evt_detect_eog', '检测眼电', [], ...
'channelname', params.eog_channel, ...
'timewindow', params.time_window, ...
'eventname', 'blink');
% === 步骤 4: SSP 眼电去除 ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_ssp_eog', 'SSP眼电去除', [], ...
'eventname', 'blink', ...
'sensortypes', 'MEG', ...
'usessp', 1, ...
'select', 1);
% === 步骤 5: 高通滤波 0.5Hz ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_bandpass', sprintf('高通滤波 %.1fHz', params.highpass_freq), [], ...
'sensortypes', 'MEG', ...
'highpass', params.highpass_freq, ...
'lowpass', 0, ...
'tranband', 0, ...
'attenuation', 'relax', ...
'ver', '2019', ...
'mirror', 0, ...
'read_all', 1);
% === 步骤 6: 带阻滤波 100Hz ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_bandstop', sprintf('带阻滤波 %dHz', params.bandstop_freq), [], ...
'sensortypes', 'MEG', ...
'freqlist', params.bandstop_freq, ...
'freqwidth', 1.5, ...
'read_all', 1);
% === 步骤 7: 陷波滤波 50Hz 100Hz ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_notch', sprintf('陷波滤波 %s', mat2str(params.notch_freqs)), [], ...
'sensortypes', 'MEG', ...
'freqlist', params.notch_freqs, ...
'cutoffW', 0.5, ...
'useold', 0, ...
'read_all', 1);
% === 步骤 8: 重采样 250Hz ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_resample', sprintf('重采样 %dHz', params.resample_freq), [], ...
'freq', params.resample_freq, ...
'read_all', 1);
% 获取噪声协方差文件绝对路径
noise_cov_full = file_fullpath(noise_cov_file);
if exist(noise_cov_full,'file') ~= 2
fprintf('⚠ 找不到 NoiseCov 文件: %s\n', noise_cov_file);
else
fprintf('→ NoiseCov 绝对路径:\n %s\n', noise_cov_full);
% 获取最新重采样文件所在 Study 文件夹
resampled_file = sFiles.FileName; % sFiles 是 struct
resampled_study_path = bst_fileparts(file_fullpath(resampled_file));
fprintf('→ 重采样文件所在 Study:\n %s\n', resampled_study_path);
% 生成目标文件路径
[~, name, ext] = fileparts(noise_cov_full);
target_file = fullfile(resampled_study_path, [name, ext]);
fprintf('→ 将复制到:\n %s\n', target_file);
% 执行复制(覆盖)
try
copyfile(noise_cov_full, target_file);
fprintf('✔ NoiseCov 已复制到重采样后 Study\n');
catch ME
fprintf('✗ 复制失败: %s\n', ME.message);
end
% 刷新数据库
db_reload_studies(iStudies(iStudy));
fprintf('✔ 数据库刷新完成\n');
end
% === 步骤 9: 计算头模型 ===
[sFiles, sFiles_cache, failed_steps] = safe_process(...
sFiles, sFiles_cache, failed_steps, ...
'process_headmodel', '计算头模型', [], ...
'Comment', '', ...
'sourcespace', 1, ...
'meg', 3, ...
'eeg', 3, ...
'ecog', 2, ...
'seeg', 2, ...
'openmeeg', struct(...
'BemFiles', {{}}, ...
'BemNames', {{'Scalp', 'Skull', 'Brain'}}, ...
'BemCond', [1, 0.0125, 1], ...
'BemSelect', [1, 1, 1], ...
'isAdjoint', 0, ...
'isAdaptative', 1, ...
'isSplit', 0, ...
'SplitLength', 4000), ...
'channelfile', '');
[sFiles, sFiles_cache, failed_steps] = bst_process('CallProcess', 'process_inverse_2018', sFiles, [], ...
'output', 3, ... % Full results: one per file
'inverse', struct(...
'Comment', 'MN: MEG ALL', ...
'InverseMethod', 'minnorm', ...
'InverseMeasure', 'amplitude', ...
'SourceOrient', {{'fixed'}}, ...
'Loose', 0.2, ...
'UseDepth', 1, ...
'WeightExp', 0.5, ...
'WeightLimit', 10, ...
'NoiseMethod', 'shrink', ...
'NoiseReg', 0.1, ...
'SnrMethod', 'fixed', ...
'SnrRms', 1e-06, ...
'SnrFixed', 3, ...
'ComputeKernel', 0, ...
'DataTypes', {{'MEG GRAD', 'MEG MAG'}}));
% Process: Compute sources [2018]
[sFiles, sFiles_cache, failed_steps] = bst_process('CallProcess', 'process_inverse_2018', sFiles, [], ...
'output', 2, ... % Kernel only: shared
'inverse', struct(...
'Comment', 'MN: MEG ALL', ...
'InverseMethod', 'minnorm', ...
'InverseMeasure', 'amplitude', ...
'SourceOrient', {{'fixed'}}, ...
'Loose', 0.2, ...
'UseDepth', 1, ...
'WeightExp', 0.5, ...
'WeightLimit', 10, ...
'NoiseMethod', 'shrink', ...
'NoiseReg', 0.1, ...
'SnrMethod', 'fixed', ...
'SnrRms', 1e-06, ...
'SnrFixed', 3, ...
'ComputeKernel', 1, ...
'DataTypes', {{'MEG GRAD', 'MEG MAG'}}));
% 保存报告
ReportFile = bst_report('Save', sFiles);
% 记录处理结果
processing_summary{end+1} = struct(...
'subject', subject_name, ...
'status', 'success', ...
'failed_steps', {failed_steps});
fprintf('\n✓ 被试 %s 处理完成\n', subject_name);
if ~isempty(failed_steps)
fprintf(' 警告: 以下步骤失败: %s\n', strjoin(failed_steps, ', '));
end
end
if ~rest_file_found
warning('被试 %s 未找到 task-rest 原始数据', subject_name);
end
catch ME
fprintf('\n✗ 处理被试 %s 时出错: %s\n', subject_name, ME.message);
fprintf('错误详情:\n%s\n', ME.getReport());
failed_subjects{end+1} = subject_name;
% 记录失败
processing_summary{end+1} = struct(...
'subject', subject_name, ...
'status', 'error', ...
'error_msg', ME.message);
continue;
end
end
%% 最终总结报告
fprintf('\n\n========================================\n');
fprintf('========== 批量处理完成总结 ==========\n');
fprintf('========================================\n');
n_total = length(processing_summary);
n_success = sum(cellfun(@(x) strcmp(x.status, 'success'), processing_summary));
n_failed = length(failed_subjects);
fprintf('总计处理: %d 个被试\n', n_total);
fprintf('成功: %d 个\n', n_success);
fprintf('失败: %d 个\n', n_failed);
if ~isempty(failed_subjects)
fprintf('\n失败的被试:\n');
for i = 1:length(failed_subjects)
fprintf(' %d. %s\n', i, failed_subjects{i});
end
end
% 显示有部分步骤失败的被试
fprintf('\n部分步骤失败的被试:\n');
for i = 1:length(processing_summary)
if strcmp(processing_summary{i}.status, 'success') && ...
~isempty(processing_summary{i}.failed_steps)
fprintf(' - %s: %s\n', processing_summary{i}.subject, ...
strjoin(processing_summary{i}.failed_steps, ', '));
end
end
fprintf('\n处理完成! 请在 Brainstorm GUI 中检查结果\n');
%% ========== 核心函数 ==========
function [sFiles_out, sFiles_cache, failed_steps] = safe_process(...
sFiles_in, sFiles_cache, failed_steps, process_name, step_name, sFiles2, varargin)
% 缓存当前sFiles
sFiles_cache{end+1} = sFiles_in;
% 执行处理步骤
fprintf(' [%s] ', step_name);
try
sFiles_out = bst_process('CallProcess', process_name, sFiles_in, sFiles2, varargin{:});
% 检查返回值
if isempty(sFiles_out) || (iscell(sFiles_out) && all(cellfun(@isempty, sFiles_out)))
% 处理失败
fprintf('✗ 失败\n');
failed_steps{end+1} = step_name;
sFiles_out = sFiles_in; % 使用上一步结果
fprintf(' -> 跳过此步骤,使用上一步结果继续\n');
else
% 成功
fprintf('✓\n');
end
catch ME
% 捕获异常
fprintf('✗ 异常: %s\n', ME.message);
failed_steps{end+1} = step_name;
sFiles_out = sFiles_in; % 使用上一步结果
fprintf(' -> 跳过此步骤,使用上一步结果继续\n');
end
end
更多推荐


所有评论(0)