4、定义RNA-seq质控进程:编写FastQC的Nextflow进程,输出质量报告,记录运行参数与版本

质控这步,说白了就是给你的测序数据做"体检"。原始数据质量不好,下游分析全是白搭。我刚开始做RNA-seq时,跳过质控直接跑比对,结果比对率不到60%,折腾了三天才发现是接头污染。嗯,从那以后,我再也不敢省这一步了。

4.1 质控进程的核心目标

FastQC进程要干三件事:

  • 质量评估:对每个FASTQ文件生成HTML报告和ZIP压缩包
  • 参数记录:把运行时的所有参数原样保存下来
  • 版本锁定:记录FastQC软件版本,方便审计时回溯

你想想看,如果没有版本和参数记录,半年后拿到一份旧报告,连用什么参数跑的都不知道,那审计还怎么追?

4.2 进程设计思路

我个人习惯把Nextflow进程拆成三层:声明层(输入输出)、执行层(脚本命令)、审计层(参数与版本)。这样每个进程职责清晰,维护起来不头疼。

下面这张图能帮你快速理解整个流程:

FastQC 质控进程工作流 输入 FASTQ 文件 FastQC 进程 fastqc -o out_dir 输出 HTML + ZIP 报告 审计记录 参数 + 版本号 输出 Channel report_ch + version_ch ⚠ 每次运行必须同时产出质量报告和审计记录 缺少任意一项,审计追踪都不完整

4.3 进程代码实现

下面是我在实际项目中打磨出来的FastQC进程。注意看审计部分的写法:

process FASTQC_QC {
    tag "样本: ${sample_id}"
    label 'qc_process'
    publishDir "${params.outdir}/fastqc", mode: 'copy'

    input:
    tuple val(sample_id), path(reads)

    output:
    tuple val(sample_id), path("${sample_id}_fastqc.html"), emit: report_html
    tuple val(sample_id), path("${sample_id}_fastqc.zip"),  emit: report_zip
    tuple val(sample_id), val(meta_info),                   emit: audit_record

    script:
    meta_info = [
        'program'   : 'FastQC',
        'version'   : '0.11.9',
        'parameters': [
            'threads' : task.cpus,
            'outdir'  : "${params.outdir}/fastqc"
        ],
        'timestamp' : new Date().format("yyyy-MM-dd HH:mm:ss"),
        'sample_id' : sample_id
    ]
    """
    fastqc \\
        --outdir "${params.outdir}/fastqc" \\
        --threads ${task.cpus} \\
        ${reads}
    """
}
💡 个人小技巧:tag 字段一定要带上样本ID。进程一多,日志里全是进程名,你根本不知道哪个任务对应哪个样本。加了 tag 后,一眼就能定位。

4.4 输入输出声明

输入用的是 tuple val(sample_id), path(reads)。为什么用tuple?因为RNA-seq通常是双端测序,reads可能包含两个文件。用tuple把样本ID和文件路径打包在一起,下游处理时才不会乱。

输出声明了三样东西:

  • report_html:可视化质量报告,直接浏览器打开
  • report_zip:数据包,给其他工具做批量分析用
  • audit_record:审计记录,里面装着参数和版本信息

我建议你把审计记录单独作为一个emit通道。不要把它塞进文件名或者日志里,那样查询的时候会疯掉。

4.5 参数与版本记录

审计记录不能光靠脑子记。你得把它结构化了再存下来。下面这个表格是我常用的审计字段清单:

字段 类型 示例值 说明
program string FastQC 工具名称,防混淆
version string 0.11.9 精确版本号,必须与容器/模块一致
parameters.threads int 4 实际使用的线程数
parameters.outdir string ./results/fastqc 输出目录路径
timestamp string 2025-06-18 14:23:01 ISO格式时间戳
sample_id string SAMPLE_001 样本标识,关联元数据
⚠ 注意:version 字段不要写死成 "latest" 或者 "current"。我曾经在一个项目里用了 "latest",三个月后容器镜像更新,版本变了,所有历史记录的版本号都失效了。审计时根本说不清当时用的到底是哪个版本。务必写具体版本号。

4.6 审计追踪的完整性

审计记录不是跑完就完事了。你得确保它进了日志系统,或者写到了一个可查询的结构化文件里。我曾经有个项目,审计记录只打印到了终端,结果日志轮转一跑,全没了。那感觉,就像考试写完了发现没保存。

我个人常用的做法有几种:

  • 写JSON文件:每个样本跑完后,单独写一个 audit.json 到输出目录
  • 走Nextflow的publishDir:把审计记录当作一个普通输出文件发布出来
  • 汇总到全局记录:用 collect 操作符把每个样本的审计记录汇总成一个JSON数组,最后统一写入

你想想看,如果审计记录分散在各种log文件里,找人要花多少时间?结构化管理才是正经事。

4.7 运行与验证

进程写好后,拿一个样本试试水:

nextflow run main.nf \
    --input samplesheet.csv \
    --outdir ./results \
    -process.cpus 4

跑完后检查三样东西:

  1. 报告能不能打开:HTML页面是否完整,图表是否正常渲染
  2. 审计记录是否生成:查看输出目录下有没有 audit.json 或等效文件
  3. 参数是否正确:对比实际运行参数和审计记录里的参数是否一致

我在第一次写这个进程时,忘了把 sample_id 传到审计记录里,结果所有报告都对不上样本。排查了一下午才发现是emit通道里丢了一个字段。嗯,这种低级错误,犯一次就记住了。

🔑 核心要点:
  • FastQC 进程必须同时产出质量报告和审计记录
  • 审计记录包含程序名、版本号、运行参数和时间戳
  • 使用 emit 命名通道,方便下游按名称引用
  • 版本号要写具体值,不要用动态标签
  • 审计记录要结构化存储,不能只写在日志里

质控进程是整个RNA-seq工作流的"守门员"。门守好了,下游的比对、定量、差异分析才能站得住脚。审计追踪不是额外负担,而是你半年后回头看时,唯一能信任的证据。