brainstorm文件层级的解析


Brainstorm 核心概念辨析

在深入代码之前,我们先厘清几个关键概念的属性和作用:

  1. 协议

    • 属性:一个 Brainstorm 协议是所有实验数据、被试、分析的顶级容器。它对应文件系统中的一个特定文件夹,里面包含了所有子文件夹和数据库条目。
    • 作用:用于管理和隔离不同的研究项目。例如,你可以有一个“视觉诱发电位_研究”协议和一个“睡眠脑电_研究”协议,它们彼此完全独立。
  2. 被试

    • 属性:属于某个协议。一个协议下可以有多个被试。每个被试有其自己的解剖结构(MRI)、记录文件等。Subject(iSubj).FileName 就是指向该被试在 Brainstorm 数据库中定义的文件的路径。
    • 作用:代表一个独立的个体或实验对象。允许对同一个被试进行多次记录或不同条件的实验。
  3. 研究

    • 属性:属于某个被试(对于单个被试研究)或一组被试(对于组分析研究)。一个研究是条件会话的容器。例如,对于一个被试“John”,你可能有两个研究:“John/声音刺激”和“John/光刺激”。
    • 作用:用于组织在特定实验条件下收集的数据。一个研究内部包含了一组数据文件结果文件
  4. 数据文件 & 结果文件

    • 属性:属于某个研究
    • 作用
      • 数据文件:存储原始的或预处理后的脑电/脑磁记录。
      • 结果文件:存储源成像、统计分析等计算后的结果。

这些概念的关系可以用一个层级结构来表示:
协议 -> 一个或多个被试 -> 一个或多个研究 -> 一个或多个数据/结果文件


代码逐行解析

现在,我们来看您的代码,它完美地演示了如何在这个层级结构中导航。

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, ...))。

总结与类比

您可以把这段代码想象成在文件系统中进行操作:

  1. sSubjects = bst_get('ProtocolSubjects’);
    • 就像进入一个项目文件夹(cd /My_Project)然后列出所有子文件夹(ls),这些子文件夹就是以被试名字命名的。
      sSubjects有Subject和sSubjects.DefaultSubject两个结构。
  2. [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. 批量处理优化

  • 自动遍历所有被试
  • 统一的参数设置
  • 并行处理支持(通过循环)

四、应用场景

适合用于:

  1. 静息态MEG研究 - 默认处理流程
  2. 临床研究 - 帕金森患者与健康对照
  3. 大样本分析 - 自动批量处理
  4. 标准化预处理 - 确保处理一致性

可能需要的调整:

  1. 通道名称 - 根据实际设备调整ECG/EOG通道名
  2. 滤波参数 - 根据数据质量调整
  3. 源定位方法 - 可根据研究需求选择不同算法
  4. 质量控制 - 可添加更多质量检查步骤

这个脚本提供了一个完整的、稳健的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
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐