Snakefile 22.5 KB
Newer Older
Sander Bollen's avatar
Sander Bollen committed
1
import json
Sander Bollen's avatar
Sander Bollen committed
2
from functools import partial
Sander Bollen's avatar
Sander Bollen committed
3
from os.path import join, basename
Sander Bollen's avatar
Sander Bollen committed
4
5

from pyfaidx import Fasta
Sander Bollen's avatar
Sander Bollen committed
6
7
8
9
10
11
12
13

OUT_DIR = config.get("OUTPUT_DIR")
REFERENCE = config.get("REFERENCE")
JAVA = config.get("JAVA")
GATK = config.get("GATK")
DBSNP = config.get("DBSNP")
ONETHOUSAND = config.get("ONETHOUSAND")
HAPMAP = config.get("HAPMAP")
14
15
BED = config.get("BED", "")  # comma-separated list of BED files
REFFLAT = config.get("REFFLAT", "")  # comma-separated list of refFlat files
Sander Bollen's avatar
Sander Bollen committed
16
FEMALE_THRESHOLD = config.get("FEMALE_THRESHOLD", 0.6)
17
FASTQ_COUNT = config.get("FASTQ_COUNT")
Sander Bollen's avatar
Sander Bollen committed
18
MAX_BASES = config.get("MAX_BASES", "")
Sander Bollen's avatar
Sander Bollen committed
19

20
21
22
23
24
def fsrc_dir(*args):
    """Wrapper around snakemake's srcdir to work like os.path.join"""
    if len(args) == 1:
        return srcdir(args[0])
    return srcdir(join(*args))
Sander Bollen's avatar
Sander Bollen committed
25

26
27
28
covpy = fsrc_dir("src", "covstats.py")
colpy = fsrc_dir("src", "collect_stats.py")
mpy = fsrc_dir("src", "merge_stats.py")
Sander Bollen's avatar
Sander Bollen committed
29
seq = fsrc_dir("src", "seqtk.sh")
Sander Bollen's avatar
Sander Bollen committed
30
fqpy = fsrc_dir("src", "fastqc_stats.py")
Sander Bollen's avatar
Sander Bollen committed
31
tsvpy = fsrc_dir("src", "stats_to_tsv.py")
Sander Bollen's avatar
Sander Bollen committed
32
fqsc = fsrc_dir("src", "safe_fastqc.sh")
Sander Bollen's avatar
Sander Bollen committed
33

34
if FASTQ_COUNT is None:
35
    fqc = "python {0}".format(fsrc_dir("src", "fastq-count.py"))
36
37
38
else:
    fqc = FASTQ_COUNT

Sander Bollen's avatar
Sander Bollen committed
39
40
41
42
with open(config.get("SAMPLE_CONFIG")) as handle:
    SAMPLE_CONFIG = json.load(handle)
SAMPLES = SAMPLE_CONFIG['samples'].keys()

Sander Bollen's avatar
Sander Bollen committed
43
44
45
46
47
48
49
50
51
if BED != "":
    BEDS = BED.split(",")
else:
    BEDS = []

if REFFLAT != "":
    REFFLATS = REFFLAT.split(",")
else:
    REFFLATS = []
Sander Bollen's avatar
Sander Bollen committed
52
53

BASE_BEDS = [basename(x) for x in BEDS]
Sander Bollen's avatar
Sander Bollen committed
54
BASE_REFFLATS = [basename(x) for x in REFFLATS]
Sander Bollen's avatar
Sander Bollen committed
55

Sander Bollen's avatar
Sander Bollen committed
56
57

def split_genome(ref, approx_n_chunks=100):
Sander Bollen's avatar
Sander Bollen committed
58
59
60
61
62
63
64
65
    """
    Split genome in chunks.

    Chunks are strings in the format: `<ctg>:<start>-<end>`
    These follow the region string format as used by htslib,
    which uses _1_-based indexing.
    See: http://www.htslib.org/doc/tabix.html
    """
Sander Bollen's avatar
Sander Bollen committed
66
67
68
69
70
    fa = Fasta(ref)
    tot_size = sum([len(x) for x in fa.records.values()])
    chunk_size = tot_size//approx_n_chunks
    chunks = []
    for chrom_name, chrom_value in fa.records.items():
Sander Bollen's avatar
Sander Bollen committed
71
        pos = 1
Sander Bollen's avatar
Sander Bollen committed
72
73
74
75
76
77
78
79
80
81
82
83
84
        while pos <= len(chrom_value):
            end = pos+chunk_size
            if end <= len(chrom_value):
                chunk = "{0}:{1}-{2}".format(chrom_name, pos, end)
            else:
                chunk = "{0}:{1}-{2}".format(chrom_name, pos, len(chrom_value))
            chunks.append(chunk)
            pos = end
    return chunks

CHUNKS = split_genome(REFERENCE)


Sander Bollen's avatar
Sander Bollen committed
85
86
87
def out_path(path):
    return join(OUT_DIR, path)

Sander Bollen's avatar
Sander Bollen committed
88
89
def get_r(strand, wildcards):
    """Get fastq files on a single strand for a sample"""
Sander Bollen's avatar
Sander Bollen committed
90
    s = SAMPLE_CONFIG['samples'].get(wildcards.sample)
Sander Bollen's avatar
Sander Bollen committed
91
    rs = []
92
    for l in sorted(s['libraries'].keys()):
Sander Bollen's avatar
Sander Bollen committed
93
94
        rs.append(s['libraries'][l][strand])
    return rs
Sander Bollen's avatar
Sander Bollen committed
95

Sander Bollen's avatar
Sander Bollen committed
96
97
get_r1 = partial(get_r, "R1")
get_r2 = partial(get_r, "R2")
Sander Bollen's avatar
Sander Bollen committed
98
99


Sander Bollen's avatar
Sander Bollen committed
100
def get_bedpath(wildcards):
Sander Bollen's avatar
Sander Bollen committed
101
102
    """Get absolute path of a bed file"""
    return next(x for x in BEDS if basename(x) == wildcards.bed)
Sander Bollen's avatar
Sander Bollen committed
103
104
105


def get_refflatpath(wildcards):
Sander Bollen's avatar
Sander Bollen committed
106
    """Get absolute path of a refflat file"""
Sander Bollen's avatar
Sander Bollen committed
107
    return next(x for x in REFFLATS if basename(x) == wildcards.ref)
Sander Bollen's avatar
Sander Bollen committed
108
109


Sander Bollen's avatar
Sander Bollen committed
110
def sample_gender(wildcards):
Sander Bollen's avatar
Sander Bollen committed
111
    """Get sample gender"""
Sander Bollen's avatar
Sander Bollen committed
112
113
114
115
    sam = SAMPLE_CONFIG['samples'].get(wildcards.sample)
    return sam.get("gender", "null")


Sander Bollen's avatar
Sander Bollen committed
116
117
118
119
120
121
def metrics(do_metrics=True):
    if not do_metrics:
        return ""

    fqcr = expand(out_path("{sample}/pre_process/raw_fastqc/.done.txt"),
                  sample=SAMPLES)
Sander Bollen's avatar
Sander Bollen committed
122
    fqcm = expand(out_path("{sample}/pre_process/merged_fastqc/{sample}.merged_R1_fastqc.zip"),
Sander Bollen's avatar
Sander Bollen committed
123
                  sample=SAMPLES)
Sander Bollen's avatar
Sander Bollen committed
124
    fqcp = expand(out_path("{sample}/pre_process/postqc_fastqc/{sample}.cutadapt_R1_fastqc.zip"),
Sander Bollen's avatar
Sander Bollen committed
125
                  sample=SAMPLES)
126
127
128
129
130
    if len(REFFLATS) >= 1:
        coverage_stats = expand(out_path("{sample}/coverage/{ref}.coverages.tsv"),
                                sample=SAMPLES, ref=BASE_REFFLATS)
    else:
        coverage_stats = []
Sander Bollen's avatar
Sander Bollen committed
131
    stats = out_path("stats.json")
Sander Bollen's avatar
Sander Bollen committed
132
    return  fqcr + fqcm + fqcp + coverage_stats + [stats]
Sander Bollen's avatar
Sander Bollen committed
133
134


Sander Bollen's avatar
Sander Bollen committed
135
136
rule all:
    input:
Sander Bollen's avatar
Sander Bollen committed
137
        combined=out_path("multisample/genotyped.vcf.gz"),
Sander Bollen's avatar
Sander Bollen committed
138
        report=out_path("multiqc_report/multiqc_report.html"),
Sander Bollen's avatar
Sander Bollen committed
139
140
        bais=expand(out_path("{sample}/bams/{sample}.markdup.bam.bai"),
                    sample=SAMPLES),
141
142
        vcfs=expand(out_path("{sample}/vcf/{sample}_single.vcf.gz"),
                    sample=SAMPLES),
Sander Bollen's avatar
Sander Bollen committed
143
        stats=metrics()
Sander Bollen's avatar
Sander Bollen committed
144

Sander Bollen's avatar
Sander Bollen committed
145
146
147
148
149
150

rule create_markdup_tmp:
    """Create tmp directory for mark duplicates"""
    output: ancient(out_path("tmp"))
    shell: "mkdir -p {output}"

Sander Bollen's avatar
Sander Bollen committed
151
rule genome:
Sander Bollen's avatar
Sander Bollen committed
152
    """Create genome file as used by bedtools"""
Sander Bollen's avatar
Sander Bollen committed
153
154
155
156
157
    input: REFERENCE
    output: out_path("current.genome")
    shell: "awk -v OFS='\t' {{'print $1,$2'}} {input}.fai > {output}"

rule merge_r1:
Sander Bollen's avatar
Sander Bollen committed
158
    """Merge all forward fastq files into one"""
Sander Bollen's avatar
Sander Bollen committed
159
160
161
162
163
    input: get_r1
    output: temp(out_path("{sample}/pre_process/{sample}.merged_R1.fastq.gz"))
    shell: "cat {input} > {output}"

rule merge_r2:
Sander Bollen's avatar
Sander Bollen committed
164
    """Merge all reverse fastq files into one"""
Sander Bollen's avatar
Sander Bollen committed
165
166
167
168
    input: get_r2
    output: temp(out_path("{sample}/pre_process/{sample}.merged_R2.fastq.gz"))
    shell: "cat {input} > {output}"

Sander Bollen's avatar
Sander Bollen committed
169
170

rule seqtk_r1:
Sander Bollen's avatar
Sander Bollen committed
171
    """Either subsample or link forward fastq file"""
Sander Bollen's avatar
Sander Bollen committed
172
173
    input:
        stats=out_path("{sample}/pre_process/{sample}.preqc_count.json"),
Sander Bollen's avatar
Sander Bollen committed
174
175
        fastq=out_path("{sample}/pre_process/{sample}.merged_R1.fastq.gz"),
        seqtk=seq
Sander Bollen's avatar
Sander Bollen committed
176
    params:
Sander Bollen's avatar
Sander Bollen committed
177
        max_bases=str(MAX_BASES)
Sander Bollen's avatar
Sander Bollen committed
178
179
    output:
        fastq=temp(out_path("{sample}/pre_process/{sample}.sampled_R1.fastq.gz"))
Sander Bollen's avatar
Sander Bollen committed
180
    conda: "envs/seqtk.yml"
Sander Bollen's avatar
Sander Bollen committed
181
    shell: "bash {input.seqtk} {input.stats} {input.fastq} {output.fastq} {params.max_bases}"
Sander Bollen's avatar
Sander Bollen committed
182
183
184


rule seqtk_r2:
Sander Bollen's avatar
Sander Bollen committed
185
    """Either subsample or link reverse fastq file"""
Sander Bollen's avatar
Sander Bollen committed
186
187
    input:
        stats = out_path("{sample}/pre_process/{sample}.preqc_count.json"),
Sander Bollen's avatar
Sander Bollen committed
188
189
        fastq = out_path("{sample}/pre_process/{sample}.merged_R2.fastq.gz"),
        seqtk=seq
Sander Bollen's avatar
Sander Bollen committed
190
    params:
Sander Bollen's avatar
Sander Bollen committed
191
        max_bases =str(MAX_BASES)
Sander Bollen's avatar
Sander Bollen committed
192
193
    output:
        fastq = temp(out_path("{sample}/pre_process/{sample}.sampled_R2.fastq.gz"))
Sander Bollen's avatar
Sander Bollen committed
194
    conda: "envs/seqtk.yml"
Sander Bollen's avatar
Sander Bollen committed
195
    shell: "bash {input.seqtk} {input.stats} {input.fastq} {output.fastq} {params.max_bases}"
Sander Bollen's avatar
Sander Bollen committed
196
197


198
# contains original merged fastq files as input to prevent them from being prematurely deleted
Sander Bollen's avatar
Sander Bollen committed
199
rule sickle:
Sander Bollen's avatar
Sander Bollen committed
200
    """Trim fastq files"""
Sander Bollen's avatar
Sander Bollen committed
201
    input:
Sander Bollen's avatar
Sander Bollen committed
202
        r1 = out_path("{sample}/pre_process/{sample}.sampled_R1.fastq.gz"),
203
204
205
        r2 = out_path("{sample}/pre_process/{sample}.sampled_R2.fastq.gz"),
        rr1 = out_path("{sample}/pre_process/{sample}.merged_R1.fastq.gz"),
        rr2 = out_path("{sample}/pre_process/{sample}.merged_R2.fastq.gz")
Sander Bollen's avatar
Sander Bollen committed
206
207
208
209
210
    output:
        r1 = temp(out_path("{sample}/pre_process/{sample}.trimmed_R1.fastq")),
        r2 = temp(out_path("{sample}/pre_process/{sample}.trimmed_R2.fastq")),
        s = out_path("{sample}/pre_process/{sample}.trimmed_singles.fastq"),
    conda: "envs/sickle.yml"
Sander Bollen's avatar
Sander Bollen committed
211
    shell: "sickle pe -f {input.r1} -r {input.r2} -t sanger -o {output.r1} "
Sander Bollen's avatar
Sander Bollen committed
212
213
214
           "-p {output.r2} -s {output.s}"

rule cutadapt:
Sander Bollen's avatar
Sander Bollen committed
215
    """Clip fastq files"""
Sander Bollen's avatar
Sander Bollen committed
216
217
218
219
220
221
222
    input:
        r1 = out_path("{sample}/pre_process/{sample}.trimmed_R1.fastq"),
        r2 = out_path("{sample}/pre_process/{sample}.trimmed_R2.fastq")
    output:
        r1 = temp(out_path("{sample}/pre_process/{sample}.cutadapt_R1.fastq")),
        r2 = temp(out_path("{sample}/pre_process/{sample}.cutadapt_R2.fastq"))
    conda: "envs/cutadapt.yml"
Sander Bollen's avatar
Sander Bollen committed
223
    shell: "cutadapt -a AGATCGGAAGAG -A AGATCGGAAGAG -m 1 -o {output.r1} "
Sander Bollen's avatar
Sander Bollen committed
224
225
226
           "{input.r1} -p {output.r2} {input.r2}"

rule align:
Sander Bollen's avatar
Sander Bollen committed
227
    """Align fastq files"""
Sander Bollen's avatar
Sander Bollen committed
228
229
230
231
232
233
234
235
    input:
        r1 = out_path("{sample}/pre_process/{sample}.cutadapt_R1.fastq"),
        r2 = out_path("{sample}/pre_process/{sample}.cutadapt_R2.fastq"),
        ref = REFERENCE
    params:
        rg = "@RG\\tID:{sample}_lib1\\tSM:{sample}\\tPL:ILLUMINA"
    output: temp(out_path("{sample}/bams/{sample}.sorted.bam"))
    conda: "envs/bwa.yml"
Sander Bollen's avatar
Sander Bollen committed
236
237
    shell: "bwa mem -t 8 -R '{params.rg}' {input.ref} {input.r1} {input.r2} "
           "| picard SortSam CREATE_INDEX=TRUE TMP_DIR=null "
Sander Bollen's avatar
Sander Bollen committed
238
239
240
           "INPUT=/dev/stdin OUTPUT={output} SORT_ORDER=coordinate"

rule markdup:
Sander Bollen's avatar
Sander Bollen committed
241
    """Mark duplicates in BAM file"""
Sander Bollen's avatar
Sander Bollen committed
242
243
    input:
        bam = out_path("{sample}/bams/{sample}.sorted.bam"),
244
        tmp = ancient(out_path("tmp"))
Sander Bollen's avatar
Sander Bollen committed
245
    output:
Sander Bollen's avatar
Sander Bollen committed
246
        bam = out_path("{sample}/bams/{sample}.markdup.bam"),
Sander Bollen's avatar
Sander Bollen committed
247
        bai = out_path("{sample}/bams/{sample}.markdup.bai"),
Sander Bollen's avatar
Sander Bollen committed
248
249
        metrics = out_path("{sample}/bams/{sample}.markdup.metrics")
    conda: "envs/picard.yml"
Sander Bollen's avatar
Sander Bollen committed
250
251
252
    shell: "picard MarkDuplicates CREATE_INDEX=TRUE TMP_DIR={input.tmp} "
           "INPUT={input.bam} OUTPUT={output.bam} "
           "METRICS_FILE={output.metrics} "
Sander Bollen's avatar
Sander Bollen committed
253
254
           "MAX_FILE_HANDLES_FOR_READ_ENDS_MAP=500"

Sander Bollen's avatar
Sander Bollen committed
255
256
257
258
259
260
261
262
rule bai:
    """Copy bai files as some genome browsers can only .bam.bai files"""
    input:
        bai = out_path("{sample}/bams/{sample}.markdup.bai")
    output:
        bai = out_path("{sample}/bams/{sample}.markdup.bam.bai")
    shell: "cp {input.bai} {output.bai}"

Sander Bollen's avatar
Sander Bollen committed
263
rule baserecal:
Sander Bollen's avatar
Sander Bollen committed
264
    """Base recalibrated BAM files"""
Sander Bollen's avatar
Sander Bollen committed
265
266
267
268
269
270
271
272
273
274
275
    input:
        bam = out_path("{sample}/bams/{sample}.markdup.bam"),
        java = JAVA,
        gatk = GATK,
        ref = REFERENCE,
        dbsnp = DBSNP,
        one1kg = ONETHOUSAND,
        hapmap = HAPMAP
    output:
        grp = out_path("{sample}/bams/{sample}.baserecal.grp")
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
276
    shell: "{input.java} -XX:ParallelGCThreads=1 -jar {input.gatk} -T BaseRecalibrator "
Sander Bollen's avatar
Sander Bollen committed
277
278
279
280
           "-I {input.bam} -o {output.grp} -nct 8 -R {input.ref} "
           "-cov ReadGroupCovariate -cov QualityScoreCovariate "
           "-cov CycleCovariate -cov ContextCovariate -knownSites "
           "{input.dbsnp} -knownSites {input.one1kg} "
Sander Bollen's avatar
Sander Bollen committed
281
282
           "-knownSites {input.hapmap}"

Sander Bollen's avatar
Sander Bollen committed
283
rule gvcf_scatter:
Sander Bollen's avatar
Sander Bollen committed
284
    """Run HaplotypeCaller in GVCF mode by chunk"""
Sander Bollen's avatar
Sander Bollen committed
285
    input:
Sander Bollen's avatar
Sander Bollen committed
286
287
        bam=out_path("{sample}/bams/{sample}.markdup.bam"),
        bqsr=out_path("{sample}/bams/{sample}.baserecal.grp"),
Sander Bollen's avatar
Sander Bollen committed
288
        dbsnp=DBSNP,
Sander Bollen's avatar
Sander Bollen committed
289
290
        ref=REFERENCE,
        gatk=GATK
Sander Bollen's avatar
Sander Bollen committed
291
    params:
Sander Bollen's avatar
Sander Bollen committed
292
        chunk="{chunk}"
Sander Bollen's avatar
Sander Bollen committed
293
    output:
294
295
        gvcf=temp(out_path("{sample}/vcf/{sample}.{chunk}.part.vcf.gz")),
        gvcf_tbi=temp(out_path("{sample}/vcf/{sample}.{chunk}.part.vcf.gz.tbi"))
Sander Bollen's avatar
Sander Bollen committed
296
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
297
    shell: "java -jar -Xmx4G -XX:ParallelGCThreads=1 {input.gatk} -T HaplotypeCaller -ERC GVCF -I "
Sander Bollen's avatar
Sander Bollen committed
298
299
300
           "{input.bam} -R {input.ref} -D {input.dbsnp} "
           "-L '{params.chunk}' -o '{output.gvcf}' "
           "-variant_index_type LINEAR -variant_index_parameter 128000 "
Sander Bollen's avatar
Sander Bollen committed
301
           "-BQSR {input.bqsr}"
Sander Bollen's avatar
Sander Bollen committed
302
303


Sander Bollen's avatar
Sander Bollen committed
304
rule gvcf_gather:
Sander Bollen's avatar
Sander Bollen committed
305
    """Gather all gvcf scatters"""
Sander Bollen's avatar
Sander Bollen committed
306
    input:
Sander Bollen's avatar
Sander Bollen committed
307
308
        gvcfs=expand(out_path("{{sample}}/vcf/{{sample}}.{chunk}.part.vcf.gz"),
                     chunk=CHUNKS),
309
310
        tbis=expand(out_path("{{sample}}/vcf/{{sample}}.{chunk}.part.vcf.gz.tbi"),
                     chunk=CHUNKS),
Sander Bollen's avatar
Sander Bollen committed
311
        ref=REFERENCE,
Sander Bollen's avatar
Sander Bollen committed
312
        gatk=GATK
Sander Bollen's avatar
Sander Bollen committed
313
    params:
Sander Bollen's avatar
Sander Bollen committed
314
        gvcfs="' -V '".join(expand(out_path("{{sample}}/vcf/{{sample}}.{chunk}.part.vcf.gz"),
Sander Bollen's avatar
Sander Bollen committed
315
                                 chunk=CHUNKS))
Sander Bollen's avatar
Sander Bollen committed
316
317
    output:
        gvcf=out_path("{sample}/vcf/{sample}.g.vcf.gz")
Sander Bollen's avatar
Sander Bollen committed
318
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
319
    shell: "java -Xmx4G -XX:ParallelGCThreads=1 -cp {input.gatk} org.broadinstitute.gatk.tools.CatVariants "
Sander Bollen's avatar
Sander Bollen committed
320
           "-R {input.ref} -V '{params.gvcfs}' -out {output.gvcf} "
Sander Bollen's avatar
Sander Bollen committed
321
322
323
324
           "-assumeSorted"


rule genotype_scatter:
Sander Bollen's avatar
Sander Bollen committed
325
    """Run GATK's GenotypeGVCFs by chunk"""
Sander Bollen's avatar
Sander Bollen committed
326
    input:
Sander Bollen's avatar
Sander Bollen committed
327
328
        gvcfs = expand(out_path("{sample}/vcf/{sample}.g.vcf.gz"),
                       sample=SAMPLES),
Sander Bollen's avatar
Sander Bollen committed
329
        ref=REFERENCE,
Sander Bollen's avatar
Sander Bollen committed
330
331
332
        gatk=GATK
    params:
        li=" -V ".join(expand(out_path("{sample}/vcf/{sample}.g.vcf.gz"),
Sander Bollen's avatar
Sander Bollen committed
333
                              sample=SAMPLES)),
Sander Bollen's avatar
Sander Bollen committed
334
335
        chunk="{chunk}"
    output:
336
337
        vcf=temp(out_path("multisample/genotype.{chunk}.part.vcf.gz")),
        vcf_tbi=temp(out_path("multisample/genotype.{chunk}.part.vcf.gz.tbi"))
Sander Bollen's avatar
Sander Bollen committed
338
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
339
    shell: "java -jar -Xmx15G -XX:ParallelGCThreads=1 {input.gatk} -T GenotypeGVCFs -R {input.ref} "
Sander Bollen's avatar
Sander Bollen committed
340
           "-V {params.li} -L '{params.chunk}' -o '{output.vcf}'"
Sander Bollen's avatar
Sander Bollen committed
341
342
343


rule genotype_gather:
Sander Bollen's avatar
Sander Bollen committed
344
    """Gather all genotyping scatters"""
Sander Bollen's avatar
Sander Bollen committed
345
346
347
    input:
        vcfs=expand(out_path("multisample/genotype.{chunk}.part.vcf.gz"),
                    chunk=CHUNKS),
348
349
        tbis=expand(out_path("multisample/genotype.{chunk}.part.vcf.gz.tbi"),
                    chunk=CHUNKS),
Sander Bollen's avatar
Sander Bollen committed
350
351
        ref=REFERENCE,
        gatk=GATK
Sander Bollen's avatar
Sander Bollen committed
352
    params:
Sander Bollen's avatar
Sander Bollen committed
353
        vcfs="' -V '".join(expand(out_path("multisample/genotype.{chunk}.part.vcf.gz"),
Sander Bollen's avatar
Sander Bollen committed
354
                                chunk=CHUNKS))
Sander Bollen's avatar
Sander Bollen committed
355
356
357
    output:
        combined=out_path("multisample/genotyped.vcf.gz")
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
358
    shell: "java -Xmx4G -XX:ParallelGCThreads=1 -cp {input.gatk} org.broadinstitute.gatk.tools.CatVariants "
Sander Bollen's avatar
Sander Bollen committed
359
           "-R {input.ref} -V '{params.vcfs}' -out {output.combined} "
Sander Bollen's avatar
Sander Bollen committed
360
           "-assumeSorted"
Sander Bollen's avatar
Sander Bollen committed
361
362


363
364
365
366
367
368
369
370
371
372
373
rule split_vcf:
    """Split multisample VCF in single samples"""
    input:
        vcf=out_path("multisample/genotyped.vcf.gz"),
        gatk=GATK,
        ref=REFERENCE
    params:
        s="{sample}"
    output:
        splitted=out_path("{sample}/vcf/{sample}_single.vcf.gz")
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
374
    shell: "java -Xmx15G -XX:ParallelGCThreads=1 -jar {input.gatk} -T SelectVariants -sn "
375
           "{params.s} -env -R {input.ref} -V {input.vcf} -o {output.splitted}"
376
377


Sander Bollen's avatar
Sander Bollen committed
378
379
380
## bam metrics

rule mapped_num:
Sander Bollen's avatar
Sander Bollen committed
381
    """Calculate number of mapped reads"""
Sander Bollen's avatar
Sander Bollen committed
382
383
384
385
386
387
388
389
390
    input:
        bam=out_path("{sample}/bams/{sample}.sorted.bam")
    output:
        num=out_path("{sample}/bams/{sample}.mapped.num")
    conda: "envs/samtools.yml"
    shell: "samtools view -F 4 {input.bam} | wc -l > {output.num}"


rule mapped_basenum:
Sander Bollen's avatar
Sander Bollen committed
391
    """Calculate number of mapped bases"""
Sander Bollen's avatar
Sander Bollen committed
392
393
394
395
396
    input:
        bam=out_path("{sample}/bams/{sample}.sorted.bam")
    output:
        num=out_path("{sample}/bams/{sample}.mapped.basenum")
    conda: "envs/samtools.yml"
Sander Bollen's avatar
Sander Bollen committed
397
    shell: "samtools view -F 4 {input.bam} | cut -f10 | wc -c > {output.num}"
Sander Bollen's avatar
Sander Bollen committed
398
399
400


rule unique_num:
Sander Bollen's avatar
Sander Bollen committed
401
    """Calculate number of unique reads"""
Sander Bollen's avatar
Sander Bollen committed
402
403
404
405
406
    input:
        bam=out_path("{sample}/bams/{sample}.markdup.bam")
    output:
        num=out_path("{sample}/bams/{sample}.unique.num")
    conda: "envs/samtools.yml"
407
    shell: "samtools view -F 4 -F 1024 {input.bam} | wc -l > {output.num}"
Sander Bollen's avatar
Sander Bollen committed
408
409
410


rule usable_basenum:
Sander Bollen's avatar
Sander Bollen committed
411
    """Calculate number of bases on unique reads"""
Sander Bollen's avatar
Sander Bollen committed
412
413
414
415
416
417
    input:
        bam=out_path("{sample}/bams/{sample}.markdup.bam")
    output:
        num=out_path("{sample}/bams/{sample}.usable.basenum")
    conda: "envs/samtools.yml"
    shell: "samtools view -F 4 -F 1024 {input.bam} | cut -f10 | wc -c > {output.num}"
Sander Bollen's avatar
Sander Bollen committed
418
419
420
421


## fastqc

Sander Bollen's avatar
Sander Bollen committed
422
rule fastqc_raw:
Sander Bollen's avatar
Sander Bollen committed
423
    """Run fastqc on raw fastq files"""
Sander Bollen's avatar
Sander Bollen committed
424
    input:
Sander Bollen's avatar
Sander Bollen committed
425
        r1=get_r1,
Sander Bollen's avatar
Sander Bollen committed
426
427
428
429
430
        r2=get_r2
    params:
        odir=out_path("{sample}/pre_process/raw_fastqc")
    output:
        aux=out_path("{sample}/pre_process/raw_fastqc/.done.txt")
431
    conda: "envs/fastqc.yml"
Sander Bollen's avatar
Sander Bollen committed
432
    shell: "fastqc --nogroup -o {params.odir} {input.r1} {input.r2} && echo 'done' > {output.aux}"
Sander Bollen's avatar
Sander Bollen committed
433
434


Sander Bollen's avatar
Sander Bollen committed
435
rule fastqc_merged:
Sander Bollen's avatar
Sander Bollen committed
436
    """Run fastqc on merged fastq files"""
Sander Bollen's avatar
Sander Bollen committed
437
    input:
Sander Bollen's avatar
Sander Bollen committed
438
        r1=out_path("{sample}/pre_process/{sample}.merged_R1.fastq.gz"),
Sander Bollen's avatar
Sander Bollen committed
439
440
        r2=out_path("{sample}/pre_process/{sample}.merged_R2.fastq.gz"),
        fq=fqsc
Sander Bollen's avatar
Sander Bollen committed
441
442
443
    params:
        odir=out_path("{sample}/pre_process/merged_fastqc")
    output:
Sander Bollen's avatar
Sander Bollen committed
444
445
        r1=out_path("{sample}/pre_process/merged_fastqc/{sample}.merged_R1_fastqc.zip"),
        r2=out_path("{sample}/pre_process/merged_fastqc/{sample}.merged_R2_fastqc.zip")
446
    conda: "envs/fastqc.yml"
Sander Bollen's avatar
Sander Bollen committed
447
448
    shell: "bash {input.fq} {input.r1} {input.r2} "
           "{output.r1} {output.r2} {params.odir}"
Sander Bollen's avatar
Sander Bollen committed
449
450


Sander Bollen's avatar
Sander Bollen committed
451
rule fastqc_postqc:
Sander Bollen's avatar
Sander Bollen committed
452
    """Run fastqc on fastq files post pre-processing"""
Sander Bollen's avatar
Sander Bollen committed
453
    input:
Sander Bollen's avatar
Sander Bollen committed
454
        r1=out_path("{sample}/pre_process/{sample}.cutadapt_R1.fastq"),
Sander Bollen's avatar
Sander Bollen committed
455
456
        r2=out_path("{sample}/pre_process/{sample}.cutadapt_R2.fastq"),
        fq=fqsc
Sander Bollen's avatar
Sander Bollen committed
457
458
459
    params:
        odir=out_path("{sample}/pre_process/postqc_fastqc")
    output:
Sander Bollen's avatar
Sander Bollen committed
460
        r1=out_path("{sample}/pre_process/postqc_fastqc/{sample}.cutadapt_R1_fastqc.zip"),
Sander Bollen's avatar
Sander Bollen committed
461
        r2=out_path("{sample}/pre_process/postqc_fastqc/{sample}.cutadapt_R2_fastqc.zip")
462
    conda: "envs/fastqc.yml"
Sander Bollen's avatar
Sander Bollen committed
463
464
    shell: "bash {input.fq} {input.r1} {input.r2} "
           "{output.r1} {output.r2} {params.odir}"
Sander Bollen's avatar
Sander Bollen committed
465
466
467
468
469


## fastq-count

rule fqcount_preqc:
Sander Bollen's avatar
Sander Bollen committed
470
    """Calculate number of reads and bases before pre-processing"""
Sander Bollen's avatar
Sander Bollen committed
471
472
473
    input:
        r1=out_path("{sample}/pre_process/{sample}.merged_R1.fastq.gz"),
        r2=out_path("{sample}/pre_process/{sample}.merged_R2.fastq.gz")
474
    params:
475
        fastqcount=fqc
Sander Bollen's avatar
Sander Bollen committed
476
477
    output:
        out_path("{sample}/pre_process/{sample}.preqc_count.json")
478
    shell: "{params.fastqcount} {input.r1} {input.r2} > {output}"
Sander Bollen's avatar
Sander Bollen committed
479
480
481


rule fqcount_postqc:
Sander Bollen's avatar
Sander Bollen committed
482
    """Calculate number of reads and bases after pre-processing"""
Sander Bollen's avatar
Sander Bollen committed
483
484
485
    input:
        r1=out_path("{sample}/pre_process/{sample}.cutadapt_R1.fastq"),
        r2=out_path("{sample}/pre_process/{sample}.cutadapt_R2.fastq")
486
    params:
487
        fastqcount=fqc
Sander Bollen's avatar
Sander Bollen committed
488
489
    output:
        out_path("{sample}/pre_process/{sample}.postqc_count.json")
Sander Bollen's avatar
Sander Bollen committed
490
491
492
    shell: "{params.fastqcount} {input.r1} {input.r2} > {output}"


Sander Bollen's avatar
Sander Bollen committed
493
494
495
496
497
498
499
500
501
# fastqc stats
rule fastqc_stats:
    """Collect fastq stats for a sample in json format"""
    input:
        preqc_r1=out_path("{sample}/pre_process/merged_fastqc/{sample}.merged_R1_fastqc.zip"),
        preqc_r2=out_path("{sample}/pre_process/merged_fastqc/{sample}.merged_R2_fastqc.zip"),
        postqc_r1=out_path("{sample}/pre_process/postqc_fastqc/{sample}.cutadapt_R1_fastqc.zip"),
        postqc_r2=out_path("{sample}/pre_process/postqc_fastqc/{sample}.cutadapt_R2_fastqc.zip"),
        sc=fqpy
502
    conda: "envs/collectstats.yml"
Sander Bollen's avatar
Sander Bollen committed
503
504
    output:
        out_path("{sample}/pre_process/fastq_stats.json")
505
506
507
    shell: "python {input.sc} --preqc-r1 {input.preqc_r1} "
           "--preqc-r2 {input.preqc_r2} "
           "--postqc-r1 {input.postqc_r1} "
Sander Bollen's avatar
Sander Bollen committed
508
           "--postqc-r2 {input.postqc_r2} > {output}"
Sander Bollen's avatar
Sander Bollen committed
509

Sander Bollen's avatar
Sander Bollen committed
510
511
512
## coverages

rule covstats:
Sander Bollen's avatar
Sander Bollen committed
513
    """Calculate coverage statistics on bam file"""
Sander Bollen's avatar
Sander Bollen committed
514
515
516
517
518
519
520
521
522
    input:
        bam=out_path("{sample}/bams/{sample}.markdup.bam"),
        genome=out_path("current.genome"),
        covpy=covpy,
        bed=get_bedpath
    params:
        subt="Sample {sample}"
    output:
        covj=out_path("{sample}/coverage/{bed}.covstats.json"),
Sander Bollen's avatar
Sander Bollen committed
523
        covp=out_path("{sample}/coverage/{bed}.covstats.png")
Sander Bollen's avatar
Sander Bollen committed
524
    conda: "envs/covstat.yml"
Sander Bollen's avatar
Sander Bollen committed
525
526
    shell: "bedtools coverage -sorted -g {input.genome} -a {input.bed} -b {input.bam} "
           "-d  | python {input.covpy} - --plot {output.covp} "
Sander Bollen's avatar
Sander Bollen committed
527
           "--title 'Targets coverage' --subtitle '{params.subt}' > {output.covj}"
Sander Bollen's avatar
Sander Bollen committed
528
529


Sander Bollen's avatar
Sander Bollen committed
530
531
532
rule vtools_coverage:
    """Calculate coverage statistics per transcript"""
    input:
Sander Bollen's avatar
Sander Bollen committed
533
        gvcf=out_path("{sample}/vcf/{sample}.g.vcf.gz"),
Sander Bollen's avatar
Sander Bollen committed
534
535
536
537
538
539
540
        ref=get_refflatpath
    output:
        tsv=out_path("{sample}/coverage/{ref}.coverages.tsv")
    conda: "envs/vcfstats.yml"
    shell: "vtools-gcoverage -I {input.gvcf} -R {input.ref} > {output.tsv}"


Sander Bollen's avatar
Sander Bollen committed
541
542
543
## vcfstats

rule vcfstats:
Sander Bollen's avatar
Sander Bollen committed
544
    """Calculate vcf statistics"""
Sander Bollen's avatar
Sander Bollen committed
545
    input:
Sander Bollen's avatar
Sander Bollen committed
546
        vcf=out_path("multisample/genotyped.vcf.gz")
Sander Bollen's avatar
Sander Bollen committed
547
548
549
    output:
        stats=out_path("multisample/vcfstats.json")
    conda: "envs/vcfstats.yml"
Sander Bollen's avatar
Sander Bollen committed
550
    shell: "vtools-stats -i {input.vcf} > {output.stats}"
Sander Bollen's avatar
Sander Bollen committed
551
552


Sander Bollen's avatar
Sander Bollen committed
553
## collection
554
555
556
557
558
559
560
561
562
563
if len(BASE_BEDS) >= 1:
    rule collectstats:
        """Collect all stats for a particular sample with beds"""
        input:
            preqc=out_path("{sample}/pre_process/{sample}.preqc_count.json"),
            postq=out_path("{sample}/pre_process/{sample}.postqc_count.json"),
            mnum=out_path("{sample}/bams/{sample}.mapped.num"),
            mbnum=out_path("{sample}/bams/{sample}.mapped.basenum"),
            unum=out_path("{sample}/bams/{sample}.unique.num"),
            ubnum=out_path("{sample}/bams/{sample}.usable.basenum"),
564
            fastqc=out_path("{sample}/pre_process/fastq_stats.json"),
565
566
567
568
569
570
571
572
573
574
575
576
577
            cov=expand(out_path("{{sample}}/coverage/{bed}.covstats.json"),
                       bed=BASE_BEDS),
            colpy=colpy
        params:
            sample_name="{sample}",
            fthresh=FEMALE_THRESHOLD
        output:
            out_path("{sample}/{sample}.stats.json")
        conda: "envs/collectstats.yml"
        shell: "python {input.colpy} --sample-name {params.sample_name} "
               "--pre-qc-fastq {input.preqc} --post-qc-fastq {input.postq} "
               "--mapped-num {input.mnum} --mapped-basenum {input.mbnum} "
               "--unique-num {input.unum} --usable-basenum {input.ubnum} "
578
579
               "--female-threshold {params.fthresh} "
               "--fastqc-stats {input.fastqc} {input.cov} > {output}"
580
581
else:
    rule collectstats:
Sander Bollen's avatar
Sander Bollen committed
582
        """Collect all stats for a particular sample without beds"""
Sander Bollen's avatar
Sander Bollen committed
583
584
585
586
587
588
589
        input:
            preqc = out_path("{sample}/pre_process/{sample}.preqc_count.json"),
            postq = out_path("{sample}/pre_process/{sample}.postqc_count.json"),
            mnum = out_path("{sample}/bams/{sample}.mapped.num"),
            mbnum = out_path("{sample}/bams/{sample}.mapped.basenum"),
            unum = out_path("{sample}/bams/{sample}.unique.num"),
            ubnum = out_path("{sample}/bams/{sample}.usable.basenum"),
590
            fastqc=out_path("{sample}/pre_process/fastq_stats.json"),
Sander Bollen's avatar
Sander Bollen committed
591
592
593
594
595
596
597
598
599
600
601
            colpy = colpy
        params:
            sample_name = "{sample}",
            fthresh = FEMALE_THRESHOLD
        output:
            out_path("{sample}/{sample}.stats.json")
        conda: "envs/collectstats.yml"
        shell: "python {input.colpy} --sample-name {params.sample_name} "
               "--pre-qc-fastq {input.preqc} --post-qc-fastq {input.postq} "
               "--mapped-num {input.mnum} --mapped-basenum {input.mbnum} "
               "--unique-num {input.unum} --usable-basenum {input.ubnum} "
602
603
               "--female-threshold {params.fthresh} "
               "--fastqc-stats {input.fastqc}  > {output}"
Sander Bollen's avatar
Sander Bollen committed
604
605

rule merge_stats:
Sander Bollen's avatar
Sander Bollen committed
606
    """Merge all stats of all samples"""
Sander Bollen's avatar
Sander Bollen committed
607
608
609
610
611
612
613
614
    input:
        cols=expand(out_path("{sample}/{sample}.stats.json"), sample=SAMPLES),
        vstat=out_path("multisample/vcfstats.json"),
        mpy=mpy
    output:
        stats=out_path("stats.json")
    conda: "envs/collectstats.yml"
    shell: "python {input.mpy} --vcfstats {input.vstat} {input.cols} > {output.stats}"
Sander Bollen's avatar
Sander Bollen committed
615
616


Sander Bollen's avatar
Sander Bollen committed
617
618
619
620
621
622
623
624
625
626
627
rule stats_tsv:
    """Convert stats.json to tsv"""
    input:
        stats=out_path("stats.json"),
        sc=tsvpy
    output:
        stats=out_path("stats.tsv")
    conda: "envs/collectstats.yml"
    shell: "python {input.sc} -i {input.stats} > {output.stats}"


Sander Bollen's avatar
Sander Bollen committed
628
629
630
rule multiqc:
    """
    Create multiQC report
Sander Bollen's avatar
Sander Bollen committed
631
    Depends on stats.tsv to forcefully run at end of pipeline
Sander Bollen's avatar
Sander Bollen committed
632
633
    """
    input:
Sander Bollen's avatar
Sander Bollen committed
634
        stats=out_path("stats.tsv")
Sander Bollen's avatar
Sander Bollen committed
635
    params:
636
637
        odir=out_path("."),
        rdir=out_path("multiqc_report")
Sander Bollen's avatar
Sander Bollen committed
638
639
    output:
        report=out_path("multiqc_report/multiqc_report.html")
Sander Bollen's avatar
Sander Bollen committed
640
    conda: "envs/multiqc.yml"
641
    shell: "multiqc -f -o {params.rdir} {params.odir} || touch {output.report}"