4、定义RNA-seq质控进程:编写FastQC的Nextflow进程,输出质量报告,记录运行参数与版本
质控这步,说白了就是给你的测序数据做"体检"。原始数据质量不好,下游分析全是白搭。我刚开始做RNA-seq时,跳过质控直接跑比对,结果比对率不到60%,折腾了三天才发现是接头污染。嗯,从那以后,我再也不敢省这一步了。
4.1 质控进程的核心目标
FastQC进程要干三件事:
- 质量评估:对每个FASTQ文件生成HTML报告和ZIP压缩包
- 参数记录:把运行时的所有参数原样保存下来
- 版本锁定:记录FastQC软件版本,方便审计时回溯
你想想看,如果没有版本和参数记录,半年后拿到一份旧报告,连用什么参数跑的都不知道,那审计还怎么追?
4.2 进程设计思路
我个人习惯把Nextflow进程拆成三层:声明层(输入输出)、执行层(脚本命令)、审计层(参数与版本)。这样每个进程职责清晰,维护起来不头疼。
下面这张图能帮你快速理解整个流程:
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}
"""
}
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 | 样本标识,关联元数据 |
4.6 审计追踪的完整性
审计记录不是跑完就完事了。你得确保它进了日志系统,或者写到了一个可查询的结构化文件里。我曾经有个项目,审计记录只打印到了终端,结果日志轮转一跑,全没了。那感觉,就像考试写完了发现没保存。
我个人常用的做法有几种:
- 写JSON文件:每个样本跑完后,单独写一个
audit.json到输出目录 - 走Nextflow的publishDir:把审计记录当作一个普通输出文件发布出来
- 汇总到全局记录:用
collect操作符把每个样本的审计记录汇总成一个JSON数组,最后统一写入
你想想看,如果审计记录分散在各种log文件里,找人要花多少时间?结构化管理才是正经事。
4.7 运行与验证
进程写好后,拿一个样本试试水:
nextflow run main.nf \
--input samplesheet.csv \
--outdir ./results \
-process.cpus 4
跑完后检查三样东西:
- 报告能不能打开:HTML页面是否完整,图表是否正常渲染
- 审计记录是否生成:查看输出目录下有没有
audit.json或等效文件 - 参数是否正确:对比实际运行参数和审计记录里的参数是否一致
我在第一次写这个进程时,忘了把 sample_id 传到审计记录里,结果所有报告都对不上样本。排查了一下午才发现是emit通道里丢了一个字段。嗯,这种低级错误,犯一次就记住了。
- FastQC 进程必须同时产出质量报告和审计记录
- 审计记录包含程序名、版本号、运行参数和时间戳
- 使用
emit命名通道,方便下游按名称引用 - 版本号要写具体值,不要用动态标签
- 审计记录要结构化存储,不能只写在日志里
质控进程是整个RNA-seq工作流的"守门员"。门守好了,下游的比对、定量、差异分析才能站得住脚。审计追踪不是额外负担,而是你半年后回头看时,唯一能信任的证据。