Variants Calling


让AI整理了一下用于Variants Calling的代码,以后会是Vibe Coding的天下了 可以确定自己在编程是确实没有天赋,但似乎也能发现技术和能力的分布不均


#!/bin/bash
# ============================================================
# Variants Calling Pipeline for Population Genomics
# ============================================================
# 功能: fastq到VCF的变异检测
# 作者: the_sun
# 日期: 2026-04-15
# 
# 流程步骤:
#   Step 1 - 质量过滤 (fastp)
#   Step 2 - 比对与标记重复 (bwa + GATK MarkDuplicates)
#   Step 3 - GVCF生成 (GATK HaplotypeCaller)
#   Step 4 - GenomicsDB构建 (GATK GenomicsDBImport)
#   Step 5 - 联合基因分型与合并 (GATK GenotypeGVCFs + MergeVcfs)
#
# 用法:
#   ./Variants_calling.sh              # 执行全部步骤
#   ./Variants_calling.sh 2 4          # 执行步骤2到4
#   ./Variants_calling.sh -s 3         # 仅执行步骤3
#   ./Variants_calling.sh -h           # 显示帮助信息
# ============================================================

set -e  # 遇错即停
set -u  # 使用未定义变量时报错
set -o pipefail  # 管道命令任一失败即报错

# ==================== 用户配置区 ====================
# 目录配置 (请根据实际情况修改)
INPUT_DIR="/ds3200_1/users_root/sunxiangqian/Analysis_test/01_SNP_Calling/sequence"
OUTPUT_DIR="/ds3200_1/users_root/sunxiangqian/Analysis_test/01_SNP_Calling"
REF="/ds3200_1/users_root/sunxiangqian/Analysis_test/01_SNP_Calling/reference/mira.fasta"

# 并行参数
FASTP_THREADS=4                        # fastp线程数
BWA_THREADS=4                          # bwa线程数
SAMTOOLS_THREADS=4                     # samtools线程数
GATK_THREADS=4                         # GATK线程数
PARALLEL_JOBS=2                        # GNU parallel并行任务数

# 质量过滤参数
MIN_LENGTH=50                          # 最小序列长度
QUALITY_CUTOFF=30                      # 质量 trimming 阈值 (Q值)

# 染色体列表 (请根据参考基因组修改)
CHROMOSOMES=("Pmi01" "Pmi02" "Pmi03" "Pmi04" "Pmi05" "Pmi06" "Pmi07" "Pmi08")

# Java内存设置 (根据服务器内存调整)
GATK_XMS="4g"                          # GATK初始内存
GATK_XMX="60g"                         # GATK最大内存
# ==================== 配置结束 ====================

# ==================== 全局变量 ====================
FILTER_DIR="${OUTPUT_DIR}/01_Filter"
BAM_DIR="${OUTPUT_DIR}/02_BAM"
GVCF_DIR="${OUTPUT_DIR}/03_GVCF"
DB_DIR="${OUTPUT_DIR}/04_DB"

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# ==================== 工具函数 ====================

log_info() {
    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"
}

log_step() {
    echo -e "${BLUE}[STEP ${1}]${NC} $(date '+%Y-%m-%d %H:%M:%S') ${2}"
}

check_tools() {
    log_info "检查必要工具..."
    local tools=("fastp" "bwa" "samtools" "gatk" "parallel" "tabix")
    local missing_tools=()
    
    for tool in "${tools[@]}"; do
        if ! command -v "$tool" &> /dev/null; then
            missing_tools+=("$tool")
        fi
    done
    
    if [[ ${#missing_tools[@]} -gt 0 ]]; then
        log_error "以下工具未找到: ${missing_tools[*]}"
        log_error "请确保已安装并添加到PATH环境变量"
        exit 1
    fi
    
    log_info "所有工具检查通过"
}

check_reference() {
    log_info "检查参考基因组及索引..."
    
    if [[ ! -f "$REF" ]]; then
        log_error "参考基因组文件不存在: $REF"
        exit 1
    fi
    
    # 生成 samtools faidx 索引
    if [[ ! -f "${REF}.fai" ]]; then
        log_info "生成 samtools faidx 索引..."
        samtools faidx "$REF"
    fi
    
    # 生成 bwa 索引
    if [[ ! -f "${REF}.bwt" ]]; then
        log_info "生成 bwa 索引..."
        bwa index "$REF"
    fi
    
    # 生成 GATK 序列字典
    local dict="${REF%.*}.dict"
    if [[ ! -f "$dict" ]]; then
        log_info "生成 GATK 序列字典..."
        gatk CreateSequenceDictionary -R "$REF"
    fi
    
    log_info "参考基因组索引检查完成"
}

create_directories() {
    log_info "创建输出目录..."
    mkdir -p "$FILTER_DIR"
    mkdir -p "$BAM_DIR"
    mkdir -p "$GVCF_DIR"
    mkdir -p "$DB_DIR"
    log_info "输出目录创建完成"
}

# ==================== Step 1: 质量过滤 ====================

run_fastp() {
    local R1_FILE=$1
    local BASENAME=$(basename "$R1_FILE" _R1.fastq.gz)
    local R2_FILE="${INPUT_DIR}/${BASENAME}_R2.fastq.gz"
    
    if [[ ! -f "$R2_FILE" ]]; then
        log_error "R2文件不存在: $R2_FILE,跳过样本 $BASENAME"
        return 1
    fi
    
    log_info "处理样本: $BASENAME"
    
    fastp \
        -i "$R1_FILE" \
        -I "$R2_FILE" \
        -o "${FILTER_DIR}/${BASENAME}_filter_R1.fastq.gz" \
        -O "${FILTER_DIR}/${BASENAME}_filter_R2.fastq.gz" \
        -l ${MIN_LENGTH} \
        --thread ${FASTP_THREADS} \
        --detect_adapter_for_pe \
        --cut_front \
        --cut_front_window_size 1 \
        --cut_front_mean_quality ${QUALITY_CUTOFF} \
        --cut_tail \
        --cut_tail_window_size 1 \
        --cut_tail_mean_quality ${QUALITY_CUTOFF} \
        --json "${FILTER_DIR}/${BASENAME}_fastp.json" \
        --html "${FILTER_DIR}/${BASENAME}_fastp.html"
    
    if [[ $? -eq 0 ]]; then
        log_info "样本 $BASENAME 质量过滤完成"
    else
        log_error "样本 $BASENAME 质量过滤失败"
        return 1
    fi
}

step1_filter() {
    log_step 1 "质量过滤 (fastp) 开始..."
    
    local input_files=("${INPUT_DIR}"/*_R1.fastq.gz)
    local total_samples=${#input_files[@]}
    
    if [[ $total_samples -eq 0 ]]; then
        log_error "未找到输入文件: ${INPUT_DIR}/*_R1.fastq.gz"
        exit 1
    fi
    
    log_info "共发现 $total_samples 个样本"
    
    export -f run_fastp
    export INPUT_DIR FILTER_DIR MIN_LENGTH FASTP_THREADS QUALITY_CUTOFF
    export log_info log_error
    
    parallel -j ${PARALLEL_JOBS} run_fastp {} ::: "${INPUT_DIR}"/*_R1.fastq.gz
    
    log_step 1 "质量过滤完成"
}

# ==================== Step 2: 比对与标记重复 ====================

run_mapping() {
    local R1_FILE=$1
    local BASENAME=$(basename "$R1_FILE" _filter_R1.fastq.gz)
    local R2_FILE="${FILTER_DIR}/${BASENAME}_filter_R2.fastq.gz"
    
    if [[ ! -f "$R2_FILE" ]]; then
        log_error "R2文件不存在: $R2_FILE,跳过样本 $BASENAME"
        return 1
    fi
    
    mkdir -p "${BAM_DIR}/${BASENAME}"
    log_info "处理样本: $BASENAME"
    
    # BWA 比对并排序
    log_info "[$BASENAME] 执行 BWA 比对..."
    bwa mem -t ${BWA_THREADS} \
        -R "@RG\tID:${BASENAME}\tPL:ILLUMINA\tSM:${BASENAME}" \
        "$REF" "$R1_FILE" "$R2_FILE" | \
        samtools sort --threads ${SAMTOOLS_THREADS} \
        -o "${BAM_DIR}/${BASENAME}/${BASENAME}.sorted.bam"
    
    # 标记重复序列
    log_info "[$BASENAME] 标记重复序列..."
    gatk MarkDuplicates \
        -I "${BAM_DIR}/${BASENAME}/${BASENAME}.sorted.bam" \
        -O "${BAM_DIR}/${BASENAME}/${BASENAME}.marked_duplicates.bam" \
        -M "${BAM_DIR}/${BASENAME}/${BASENAME}.marked_dup_metrics.txt" \
        --OPTICAL_DUPLICATE_PIXEL_DISTANCE 2500
    
    # 清理中间文件
    rm -f "${BAM_DIR}/${BASENAME}/${BASENAME}.sorted.bam"
    
    log_info "样本 $BASENAME 比对完成"
}

step2_mapping() {
    log_step 2 "比对与标记重复 (bwa + GATK MarkDuplicates) 开始..."
    
    check_reference
    
    local input_files=("${FILTER_DIR}"/*_filter_R1.fastq.gz)
    local total_samples=${#input_files[@]}
    
    if [[ $total_samples -eq 0 ]]; then
        log_error "未找到过滤后的文件: ${FILTER_DIR}/*_filter_R1.fastq.gz"
        exit 1
    fi
    
    log_info "共发现 $total_samples 个样本"
    
    export -f run_mapping
    export FILTER_DIR BAM_DIR REF BWA_THREADS SAMTOOLS_THREADS
    export log_info log_error
    
    parallel -j ${PARALLEL_JOBS} run_mapping {} ::: "${FILTER_DIR}"/*_filter_R1.fastq.gz
    
    log_step 2 "比对完成"
}

# ==================== Step 3: GVCF生成 ====================

run_gvcf() {
    local R1_FILE=$1
    local BASENAME=$(basename "$R1_FILE" _filter_R1.fastq.gz)
    local BAM_FILE="${BAM_DIR}/${BASENAME}/${BASENAME}.marked_duplicates.bam"
    
    if [[ ! -f "$BAM_FILE" ]]; then
        log_error "BAM文件不存在: $BAM_FILE,跳过样本 $BASENAME"
        return 1
    fi
    
    mkdir -p "${GVCF_DIR}/${BASENAME}"
    log_info "处理样本: $BASENAME"
    
    # 建立 BAM 索引
    log_info "[$BASENAME] 建立 BAM 索引..."
    samtools index "$BAM_FILE"
    
    # 质控统计
    samtools flagstat "$BAM_FILE" > "${BAM_DIR}/${BASENAME}/${BASENAME}.flagstat"
    
    # 生成 GVCF
    log_info "[$BASENAME] 生成 GVCF..."
    gatk HaplotypeCaller \
        -R "$REF" \
        --emit-ref-confidence GVCF \
        -I "$BAM_FILE" \
        -O "${GVCF_DIR}/${BASENAME}/${BASENAME}.g.vcf.gz" \
        --sample-name "$BASENAME"
    
    # 建立 GVCF 索引
    tabix -p vcf "${GVCF_DIR}/${BASENAME}/${BASENAME}.g.vcf.gz"
    
    log_info "样本 $BASENAME GVCF 生成完成"
}

step3_gvcf() {
    log_step 3 "GVCF生成 (GATK HaplotypeCaller) 开始..."
    
    local input_files=("${FILTER_DIR}"/*_filter_R1.fastq.gz)
    local total_samples=${#input_files[@]}
    
    if [[ $total_samples -eq 0 ]]; then
        log_error "未找到过滤后的文件: ${FILTER_DIR}/*_filter_R1.fastq.gz"
        exit 1
    fi
    
    log_info "共发现 $total_samples 个样本"
    
    export -f run_gvcf
    export BAM_DIR GVCF_DIR REF
    export log_info log_error
    
    parallel -j ${PARALLEL_JOBS} run_gvcf {} ::: "${FILTER_DIR}"/*_filter_R1.fastq.gz
    
    log_step 3 "GVCF生成完成"
}

# ==================== Step 4: GenomicsDB构建 ====================

run_db_import() {
    local chr=$1
    local tmp_dir="${DB_DIR}/tmp_${chr}"
    mkdir -p "$tmp_dir"
    
    log_info "处理染色体: $chr"
    
    gatk --java-options "-Xms${GATK_XMS} -Xmx${GATK_XMX} -XX:ParallelGCThreads=${GATK_THREADS}" \
        GenomicsDBImport \
        --batch-size 10 \
        --reader-threads ${GATK_THREADS} \
        -L "$chr" \
        --sample-name-map "${DB_DIR}/sample_map.txt" \
        --genomicsdb-workspace-path "${DB_DIR}/DB_${chr}" \
        --tmp-dir "$tmp_dir" \
        --genomicsdb-shared-posixfs-optimizations true \
        --reference "$REF"
    
    # 清理临时目录
    rm -rf "$tmp_dir"
    
    log_info "染色体 $chr GenomicsDB 构建完成"
}

step4_db() {
    log_step 4 "GenomicsDB构建 (GATK GenomicsDBImport) 开始..."
    
    # 生成样本映射文件
    local map_file="${DB_DIR}/sample_map.txt"
    log_info "生成样本映射文件..."
    
    find "$GVCF_DIR" -name "*.g.vcf.gz" | while read -r file; do
        local sample=$(basename "$file" .g.vcf.gz)
        echo -e "${sample}\t${file}"
    done > "$map_file"
    
    local sample_count=$(wc -l < "$map_file")
    log_info "共发现 $sample_count 个样本"
    
    if [[ $sample_count -eq 0 ]]; then
        log_error "未找到 GVCF 文件"
        exit 1
    fi
    
    export -f run_db_import
    export DB_DIR GVCF_DIR REF GATK_THREADS GATK_XMS GATK_XMX
    export log_info log_error
    
    parallel -j ${PARALLEL_JOBS} run_db_import {} ::: "${CHROMOSOMES[@]}"
    
    log_step 4 "GenomicsDB构建完成"
}

# ==================== Step 5: 联合基因分型与合并 ====================

run_genotype() {
    local chr=$1
    local out_dir="${DB_DIR}/${chr}"
    local tmp_dir="${DB_DIR}/tmp_genotype_${chr}"
    
    mkdir -p "$out_dir" "$tmp_dir"
    
    log_info "处理染色体: $chr"
    
    gatk --java-options "-Xms${GATK_XMS} -Xmx${GATK_XMX} -XX:ParallelGCThreads=${GATK_THREADS}" \
        GenotypeGVCFs \
        -R "$REF" \
        -V "gendb://${DB_DIR}/DB_${chr}" \
        -O "${out_dir}/${chr}.vcf.gz" \
        --allow-old-rms-mapping-quality-annotation-data \
        --max-alternate-alleles 4 \
        --genomicsdb-max-alternate-alleles 10 \
        --genomicsdb-shared-posixfs-optimizations \
        --tmp-dir "$tmp_dir"
    
    # 清理临时目录
    rm -rf "$tmp_dir"
    
    log_info "染色体 $chr 基因分型完成"
}

step5_genotype() {
    log_step 5 "联合基因分型 (GATK GenotypeGVCFs) 开始..."
    
    export -f run_genotype
    export DB_DIR REF GATK_THREADS GATK_XMS GATK_XMX
    export log_info log_error
    
    parallel -j ${PARALLEL_JOBS} run_genotype {} ::: "${CHROMOSOMES[@]}"
    
    # 合并所有染色体的 VCF 文件
    log_info "合并所有染色体 VCF 文件..."
    
    local merge_inputs=""
    for chr in "${CHROMOSOMES[@]}"; do
        merge_inputs+=" -I ${DB_DIR}/${chr}/${chr}.vcf.gz"
    done
    
    gatk --java-options "-Xms${GATK_XMS} -Xmx${GATK_XMX}" \
        MergeVcfs \
        $merge_inputs \
        -O "${OUTPUT_DIR}/final_variants.vcf.gz"
    
    # 建立最终 VCF 索引
    tabix -p vcf "${OUTPUT_DIR}/final_variants.vcf.gz"
    
    log_step 5 "联合基因分型完成"
    log_info "最终 VCF 文件: ${OUTPUT_DIR}/final_variants.vcf.gz"
}

# ==================== 主函数 ====================

main() {
    local start_step=${1:-1}
    local end_step=${2:-5}
    
    echo "=========================================="
    echo "  Variants Calling Pipeline"
    echo "=========================================="
    echo "输入目录: $INPUT_DIR"
    echo "输出目录: $OUTPUT_DIR"
    echo "参考基因组: $REF"
    echo "并行任务数: ${PARALLEL_JOBS}"
    echo "执行步骤: ${start_step} - ${end_step}"
    echo "=========================================="
    echo ""
    
    check_tools
    create_directories
    
    if [[ $start_step -le 1 ]] && [[ $end_step -ge 1 ]]; then step1_filter; fi
    if [[ $start_step -le 2 ]] && [[ $end_step -ge 2 ]]; then step2_mapping; fi
    if [[ $start_step -le 3 ]] && [[ $end_step -ge 3 ]]; then step3_gvcf; fi
    if [[ $start_step -le 4 ]] && [[ $end_step -ge 4 ]]; then step4_db; fi
    if [[ $start_step -le 5 ]] && [[ $end_step -ge 5 ]]; then step5_genotype; fi
    
    echo ""
    echo "=========================================="
    log_info "流程全部完成!"
    echo "=========================================="
}

# ==================== 帮助信息 ====================

usage() {
    cat << EOF
用法: $0 [选项] [起始步骤] [结束步骤]

Variants Calling Pipeline - 基于GATK最佳实践的变异检测流程

选项:
    -h, --help      显示此帮助信息
    -a, --all       执行全部步骤 (1-5)
    -s, --step N    仅执行单个步骤 N

步骤说明:
    1   质量过滤 (fastp)
        - 过滤低质量序列
        - 自动检测并切除接头
        - 前后端质量修剪

    2   比对与标记重复 (bwa + GATK MarkDuplicates)
        - BWA比对到参考基因组
        - 标记PCR重复序列

    3   GVCF生成 (GATK HaplotypeCaller)
        - 为每个样本生成GVCF文件
        - 包含所有位点信息

    4   GenomicsDB构建 (GATK GenomicsDBImport)
        - 按染色体导入GVCF到GenomicsDB
        - 便于大规模样本处理

    5   联合基因分型与合并 (GATK GenotypeGVCFs + MergeVcfs)
        - 跨样本联合基因分型
        - 合并所有染色体VCF

示例:
    $0                      # 执行全部步骤 (1-5)
    $0 2 4                  # 执行步骤2到4
    $0 -s 3                 # 仅执行步骤3
    $0 -a                   # 执行全部步骤

配置:
    请修改脚本顶部的"用户配置区"设置以下参数:
    - INPUT_DIR     : 原始fastq文件目录
    - OUTPUT_DIR    : 输出根目录
    - REF           : 参考基因组路径
    - CHROMOSOMES   : 染色体名称列表
    - PARALLEL_JOBS : 并行任务数
    - GATK_XMX      : GATK最大内存

输出文件:
    最终结果: \${OUTPUT_DIR}/final_variants.vcf.gz
    各步骤中间文件位于:
      - 01_Filter/  : 过滤后的fastq文件
      - 02_BAM/      : 比对结果BAM文件
      - 03_GVCF/     : GVCF文件
      - 04_DB/       : GenomicsDB数据库

EOF
}

# ==================== 参数解析与执行 ====================

case "${1:-}" in
    -h|--help)
        usage
        exit 0
        ;;
    -a|--all)
        main 1 5
        ;;
    -s|--step)
        if [[ -z "${2:-}" ]]; then
            log_error "请指定步骤编号"
            usage
            exit 1
        fi
        main "$2" "$2"
        ;;
    "")
        main 1 5
        ;;
    *)
        if [[ "$1" =~ ^[0-9]+$ ]]; then
            main "$1" "${2:-$1}"
        else
            log_error "无效参数: $1"
            usage
            exit 1
        fi
        ;;
esac