Snakefile 21 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

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

Sander Bollen's avatar
Sander Bollen committed
37
38
39
40
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
41
42
43
44
45
46
47
48
49
if BED != "":
    BEDS = BED.split(",")
else:
    BEDS = []

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

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

Sander Bollen's avatar
Sander Bollen committed
54
55

def split_genome(ref, approx_n_chunks=100):
Sander Bollen's avatar
Sander Bollen committed
56
57
58
59
60
61
62
63
    """
    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
64
65
66
67
68
    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
69
        pos = 1
Sander Bollen's avatar
Sander Bollen committed
70
71
72
73
74
75
76
77
78
79
80
81
82
        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
83
84
85
def out_path(path):
    return join(OUT_DIR, path)

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

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


Sander Bollen's avatar
Sander Bollen committed
98
def get_bedpath(wildcards):
Sander Bollen's avatar
Sander Bollen committed
99
100
    """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
101
102
103


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


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


Sander Bollen's avatar
Sander Bollen committed
114
115
116
117
118
119
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
120
    fqcm = expand(out_path("{sample}/pre_process/merged_fastqc/{sample}.merged_R1_fastqc.zip"),
Sander Bollen's avatar
Sander Bollen committed
121
                  sample=SAMPLES)
Sander Bollen's avatar
Sander Bollen committed
122
    fqcp = expand(out_path("{sample}/pre_process/postqc_fastqc/{sample}.cutadapt_R1_fastqc.zip"),
Sander Bollen's avatar
Sander Bollen committed
123
                  sample=SAMPLES)
124
125
126
127
128
    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
129
    stats = out_path("stats.json")
Sander Bollen's avatar
Sander Bollen committed
130
    return  fqcr + fqcm + fqcp + coverage_stats + [stats]
Sander Bollen's avatar
Sander Bollen committed
131
132


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

Sander Bollen's avatar
Sander Bollen committed
141
142
143
144
145
146

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
147
rule genome:
Sander Bollen's avatar
Sander Bollen committed
148
    """Create genome file as used by bedtools"""
Sander Bollen's avatar
Sander Bollen committed
149
150
151
152
153
    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
154
    """Merge all forward fastq files into one"""
Sander Bollen's avatar
Sander Bollen committed
155
156
157
158
159
    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
160
    """Merge all reverse fastq files into one"""
Sander Bollen's avatar
Sander Bollen committed
161
162
163
164
    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
165
166

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


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


194
# contains original merged fastq files as input to prevent them from being prematurely deleted
Sander Bollen's avatar
Sander Bollen committed
195
rule sickle:
Sander Bollen's avatar
Sander Bollen committed
196
    """Trim fastq files"""
Sander Bollen's avatar
Sander Bollen committed
197
    input:
Sander Bollen's avatar
Sander Bollen committed
198
        r1 = out_path("{sample}/pre_process/{sample}.sampled_R1.fastq.gz"),
199
200
201
        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
202
203
204
205
206
    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
207
    shell: "sickle pe -f {input.r1} -r {input.r2} -t sanger -o {output.r1} "
Sander Bollen's avatar
Sander Bollen committed
208
209
210
           "-p {output.r2} -s {output.s}"

rule cutadapt:
Sander Bollen's avatar
Sander Bollen committed
211
    """Clip fastq files"""
Sander Bollen's avatar
Sander Bollen committed
212
213
214
215
216
217
218
    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
219
    shell: "cutadapt -a AGATCGGAAGAG -A AGATCGGAAGAG -m 1 -o {output.r1} "
Sander Bollen's avatar
Sander Bollen committed
220
221
222
           "{input.r1} -p {output.r2} {input.r2}"

rule align:
Sander Bollen's avatar
Sander Bollen committed
223
    """Align fastq files"""
Sander Bollen's avatar
Sander Bollen committed
224
225
226
227
228
229
230
231
    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
232
233
    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
234
235
236
           "INPUT=/dev/stdin OUTPUT={output} SORT_ORDER=coordinate"

rule markdup:
Sander Bollen's avatar
Sander Bollen committed
237
    """Mark duplicates in BAM file"""
Sander Bollen's avatar
Sander Bollen committed
238
239
    input:
        bam = out_path("{sample}/bams/{sample}.sorted.bam"),
240
        tmp = ancient(out_path("tmp"))
Sander Bollen's avatar
Sander Bollen committed
241
    output:
Sander Bollen's avatar
Sander Bollen committed
242
        bam = out_path("{sample}/bams/{sample}.markdup.bam"),
Sander Bollen's avatar
Sander Bollen committed
243
        bai = out_path("{sample}/bams/{sample}.markdup.bai"),
Sander Bollen's avatar
Sander Bollen committed
244
245
        metrics = out_path("{sample}/bams/{sample}.markdup.metrics")
    conda: "envs/picard.yml"
Sander Bollen's avatar
Sander Bollen committed
246
247
248
    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
249
250
           "MAX_FILE_HANDLES_FOR_READ_ENDS_MAP=500"

Sander Bollen's avatar
Sander Bollen committed
251
252
253
254
255
256
257
258
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
259
rule baserecal:
Sander Bollen's avatar
Sander Bollen committed
260
    """Base recalibrated BAM files"""
Sander Bollen's avatar
Sander Bollen committed
261
262
263
264
265
266
267
268
269
270
271
    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
272
273
274
275
276
    shell: "{input.java} -jar {input.gatk} -T BaseRecalibrator "
           "-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
277
278
           "-knownSites {input.hapmap}"

Sander Bollen's avatar
Sander Bollen committed
279
rule gvcf_scatter:
Sander Bollen's avatar
Sander Bollen committed
280
    """Run HaplotypeCaller in GVCF mode by chunk"""
Sander Bollen's avatar
Sander Bollen committed
281
    input:
Sander Bollen's avatar
Sander Bollen committed
282
283
        bam=out_path("{sample}/bams/{sample}.markdup.bam"),
        bqsr=out_path("{sample}/bams/{sample}.baserecal.grp"),
Sander Bollen's avatar
Sander Bollen committed
284
        dbsnp=DBSNP,
Sander Bollen's avatar
Sander Bollen committed
285
286
        ref=REFERENCE,
        gatk=GATK
Sander Bollen's avatar
Sander Bollen committed
287
    params:
Sander Bollen's avatar
Sander Bollen committed
288
        chunk="{chunk}"
Sander Bollen's avatar
Sander Bollen committed
289
    output:
Sander Bollen's avatar
Sander Bollen committed
290
        gvcf=out_path("{sample}/vcf/{sample}.{chunk}.part.vcf.gz")
Sander Bollen's avatar
Sander Bollen committed
291
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
292
293
294
295
    shell: "java -jar -Xmx4G {input.gatk} -T HaplotypeCaller -ERC GVCF -I "
           "{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
296
           "-BQSR {input.bqsr}"
Sander Bollen's avatar
Sander Bollen committed
297
298


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


rule genotype_scatter:
Sander Bollen's avatar
Sander Bollen committed
318
    """Run GATK's GenotypeGVCFs by chunk"""
Sander Bollen's avatar
Sander Bollen committed
319
    input:
Sander Bollen's avatar
Sander Bollen committed
320
321
        gvcfs = expand(out_path("{sample}/vcf/{sample}.g.vcf.gz"),
                       sample=SAMPLES),
Sander Bollen's avatar
Sander Bollen committed
322
        ref=REFERENCE,
Sander Bollen's avatar
Sander Bollen committed
323
324
325
        gatk=GATK
    params:
        li=" -V ".join(expand(out_path("{sample}/vcf/{sample}.g.vcf.gz"),
Sander Bollen's avatar
Sander Bollen committed
326
                              sample=SAMPLES)),
Sander Bollen's avatar
Sander Bollen committed
327
328
329
        chunk="{chunk}"
    output:
        vcf=out_path("multisample/genotype.{chunk}.part.vcf.gz")
Sander Bollen's avatar
Sander Bollen committed
330
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
331
    shell: "java -jar -Xmx4G {input.gatk} -T GenotypeGVCFs -R {input.ref} "
Sander Bollen's avatar
Sander Bollen committed
332
           "-V {params.li} -L '{params.chunk}' -o '{output.vcf}'"
Sander Bollen's avatar
Sander Bollen committed
333
334
335


rule genotype_gather:
Sander Bollen's avatar
Sander Bollen committed
336
    """Gather all genotyping scatters"""
Sander Bollen's avatar
Sander Bollen committed
337
338
339
340
341
    input:
        vcfs=expand(out_path("multisample/genotype.{chunk}.part.vcf.gz"),
                    chunk=CHUNKS),
        ref=REFERENCE,
        gatk=GATK
Sander Bollen's avatar
Sander Bollen committed
342
    params:
Sander Bollen's avatar
Sander Bollen committed
343
        vcfs="' -V '".join(expand(out_path("multisample/genotype.{chunk}.part.vcf.gz"),
Sander Bollen's avatar
Sander Bollen committed
344
                                chunk=CHUNKS))
Sander Bollen's avatar
Sander Bollen committed
345
346
347
    output:
        combined=out_path("multisample/genotyped.vcf.gz")
    conda: "envs/gatk.yml"
Sander Bollen's avatar
Sander Bollen committed
348
349
    shell: "java -Xmx4G -cp {input.gatk} org.broadinstitute.gatk.tools.CatVariants "
           "-R {input.ref} -V '{params.vcfs}' -out {output.combined} "
Sander Bollen's avatar
Sander Bollen committed
350
           "-assumeSorted"
Sander Bollen's avatar
Sander Bollen committed
351
352


Sander Bollen's avatar
Sander Bollen committed
353
354
355
## bam metrics

rule mapped_num:
Sander Bollen's avatar
Sander Bollen committed
356
    """Calculate number of mapped reads"""
Sander Bollen's avatar
Sander Bollen committed
357
358
359
360
361
362
363
364
365
    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
366
    """Calculate number of mapped bases"""
Sander Bollen's avatar
Sander Bollen committed
367
368
369
370
371
    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
372
    shell: "samtools view -F 4 {input.bam} | cut -f10 | wc -c > {output.num}"
Sander Bollen's avatar
Sander Bollen committed
373
374
375


rule unique_num:
Sander Bollen's avatar
Sander Bollen committed
376
    """Calculate number of unique reads"""
Sander Bollen's avatar
Sander Bollen committed
377
378
379
380
381
    input:
        bam=out_path("{sample}/bams/{sample}.markdup.bam")
    output:
        num=out_path("{sample}/bams/{sample}.unique.num")
    conda: "envs/samtools.yml"
382
    shell: "samtools view -F 4 -F 1024 {input.bam} | wc -l > {output.num}"
Sander Bollen's avatar
Sander Bollen committed
383
384
385


rule usable_basenum:
Sander Bollen's avatar
Sander Bollen committed
386
    """Calculate number of bases on unique reads"""
Sander Bollen's avatar
Sander Bollen committed
387
388
389
390
391
392
    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
393
394
395
396


## fastqc

Sander Bollen's avatar
Sander Bollen committed
397
rule fastqc_raw:
Sander Bollen's avatar
Sander Bollen committed
398
    """Run fastqc on raw fastq files"""
Sander Bollen's avatar
Sander Bollen committed
399
    input:
Sander Bollen's avatar
Sander Bollen committed
400
        r1=get_r1,
Sander Bollen's avatar
Sander Bollen committed
401
402
403
404
405
        r2=get_r2
    params:
        odir=out_path("{sample}/pre_process/raw_fastqc")
    output:
        aux=out_path("{sample}/pre_process/raw_fastqc/.done.txt")
406
    conda: "envs/fastqc.yml"
Sander Bollen's avatar
Sander Bollen committed
407
    shell: "fastqc --nogroup -o {params.odir} {input.r1} {input.r2} && echo 'done' > {output.aux}"
Sander Bollen's avatar
Sander Bollen committed
408
409


Sander Bollen's avatar
Sander Bollen committed
410
rule fastqc_merged:
Sander Bollen's avatar
Sander Bollen committed
411
    """Run fastqc on merged fastq files"""
Sander Bollen's avatar
Sander Bollen committed
412
    input:
Sander Bollen's avatar
Sander Bollen committed
413
        r1=out_path("{sample}/pre_process/{sample}.merged_R1.fastq.gz"),
Sander Bollen's avatar
Sander Bollen committed
414
415
416
417
        r2=out_path("{sample}/pre_process/{sample}.merged_R2.fastq.gz")
    params:
        odir=out_path("{sample}/pre_process/merged_fastqc")
    output:
Sander Bollen's avatar
Sander Bollen committed
418
419
        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")
420
    conda: "envs/fastqc.yml"
Sander Bollen's avatar
Sander Bollen committed
421
    shell: "fastqc --nogroup -o {params.odir} {input.r1} {input.r2}"
Sander Bollen's avatar
Sander Bollen committed
422
423


Sander Bollen's avatar
Sander Bollen committed
424
rule fastqc_postqc:
Sander Bollen's avatar
Sander Bollen committed
425
    """Run fastqc on fastq files post pre-processing"""
Sander Bollen's avatar
Sander Bollen committed
426
    input:
Sander Bollen's avatar
Sander Bollen committed
427
        r1=out_path("{sample}/pre_process/{sample}.cutadapt_R1.fastq"),
Sander Bollen's avatar
Sander Bollen committed
428
429
430
431
        r2=out_path("{sample}/pre_process/{sample}.cutadapt_R2.fastq")
    params:
        odir=out_path("{sample}/pre_process/postqc_fastqc")
    output:
Sander Bollen's avatar
Sander Bollen committed
432
        r1=out_path("{sample}/pre_process/postqc_fastqc/{sample}.cutadapt_R1_fastqc.zip"),
Sander Bollen's avatar
Sander Bollen committed
433
        r2=out_path("{sample}/pre_process/postqc_fastqc/{sample}.cutadapt_R2_fastqc.zip")
434
    conda: "envs/fastqc.yml"
Sander Bollen's avatar
Sander Bollen committed
435
    shell: "fastqc --nogroup -o {params.odir} {input.r1} {input.r2}"
Sander Bollen's avatar
Sander Bollen committed
436
437
438
439
440


## fastq-count

rule fqcount_preqc:
Sander Bollen's avatar
Sander Bollen committed
441
    """Calculate number of reads and bases before pre-processing"""
Sander Bollen's avatar
Sander Bollen committed
442
443
444
    input:
        r1=out_path("{sample}/pre_process/{sample}.merged_R1.fastq.gz"),
        r2=out_path("{sample}/pre_process/{sample}.merged_R2.fastq.gz")
445
    params:
446
        fastqcount=fqc
Sander Bollen's avatar
Sander Bollen committed
447
448
    output:
        out_path("{sample}/pre_process/{sample}.preqc_count.json")
449
    shell: "{params.fastqcount} {input.r1} {input.r2} > {output}"
Sander Bollen's avatar
Sander Bollen committed
450
451
452


rule fqcount_postqc:
Sander Bollen's avatar
Sander Bollen committed
453
    """Calculate number of reads and bases after pre-processing"""
Sander Bollen's avatar
Sander Bollen committed
454
455
456
    input:
        r1=out_path("{sample}/pre_process/{sample}.cutadapt_R1.fastq"),
        r2=out_path("{sample}/pre_process/{sample}.cutadapt_R2.fastq")
457
    params:
458
        fastqcount=fqc
Sander Bollen's avatar
Sander Bollen committed
459
460
    output:
        out_path("{sample}/pre_process/{sample}.postqc_count.json")
Sander Bollen's avatar
Sander Bollen committed
461
462
463
    shell: "{params.fastqcount} {input.r1} {input.r2} > {output}"


Sander Bollen's avatar
Sander Bollen committed
464
465
466
467
468
469
470
471
472
# 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
473
    conda: "envs/collectstats.yml"
Sander Bollen's avatar
Sander Bollen committed
474
475
    output:
        out_path("{sample}/pre_process/fastq_stats.json")
476
477
478
    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
479
           "--postqc-r2 {input.postqc_r2} > {output}"
Sander Bollen's avatar
Sander Bollen committed
480

Sander Bollen's avatar
Sander Bollen committed
481
482
483
## coverages

rule covstats:
Sander Bollen's avatar
Sander Bollen committed
484
    """Calculate coverage statistics on bam file"""
Sander Bollen's avatar
Sander Bollen committed
485
486
487
488
489
490
491
492
493
    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
494
        covp=out_path("{sample}/coverage/{bed}.covstats.png")
Sander Bollen's avatar
Sander Bollen committed
495
    conda: "envs/covstat.yml"
Sander Bollen's avatar
Sander Bollen committed
496
497
    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
498
           "--title 'Targets coverage' --subtitle '{params.subt}' > {output.covj}"
Sander Bollen's avatar
Sander Bollen committed
499
500


Sander Bollen's avatar
Sander Bollen committed
501
502
503
rule vtools_coverage:
    """Calculate coverage statistics per transcript"""
    input:
Sander Bollen's avatar
Sander Bollen committed
504
        gvcf=out_path("{sample}/vcf/{sample}.g.vcf.gz"),
Sander Bollen's avatar
Sander Bollen committed
505
506
507
508
509
510
511
        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
512
513
514
## vcfstats

rule vcfstats:
Sander Bollen's avatar
Sander Bollen committed
515
    """Calculate vcf statistics"""
Sander Bollen's avatar
Sander Bollen committed
516
    input:
Sander Bollen's avatar
Sander Bollen committed
517
        vcf=out_path("multisample/genotyped.vcf.gz")
Sander Bollen's avatar
Sander Bollen committed
518
519
520
    output:
        stats=out_path("multisample/vcfstats.json")
    conda: "envs/vcfstats.yml"
Sander Bollen's avatar
Sander Bollen committed
521
    shell: "vtools-stats -i {input.vcf} > {output.stats}"
Sander Bollen's avatar
Sander Bollen committed
522
523


Sander Bollen's avatar
Sander Bollen committed
524
## collection
525
526
527
528
529
530
531
532
533
534
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"),
535
            fastqc=out_path("{sample}/pre_process/fastq_stats.json"),
536
537
538
539
540
541
542
543
544
545
546
547
548
            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} "
549
550
               "--female-threshold {params.fthresh} "
               "--fastqc-stats {input.fastqc} {input.cov} > {output}"
551
552
else:
    rule collectstats:
Sander Bollen's avatar
Sander Bollen committed
553
        """Collect all stats for a particular sample without beds"""
Sander Bollen's avatar
Sander Bollen committed
554
555
556
557
558
559
560
        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"),
561
            fastqc=out_path("{sample}/pre_process/fastq_stats.json"),
Sander Bollen's avatar
Sander Bollen committed
562
563
564
565
566
567
568
569
570
571
572
            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} "
573
574
               "--female-threshold {params.fthresh} "
               "--fastqc-stats {input.fastqc}  > {output}"
Sander Bollen's avatar
Sander Bollen committed
575
576

rule merge_stats:
Sander Bollen's avatar
Sander Bollen committed
577
    """Merge all stats of all samples"""
Sander Bollen's avatar
Sander Bollen committed
578
579
580
581
582
583
584
585
    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
586
587
588
589
590
591
592
593
594
595


rule multiqc:
    """
    Create multiQC report
    Depends on stats.json to forcefully run at end of pipeline
    """
    input:
        stats=out_path("stats.json")
    params:
596
597
        odir=out_path("."),
        rdir=out_path("multiqc_report")
Sander Bollen's avatar
Sander Bollen committed
598
599
    output:
        report=out_path("multiqc_report/multiqc_report.html")
Sander Bollen's avatar
Sander Bollen committed
600
    conda: "envs/multiqc.yml"
601
    shell: "multiqc -o {params.rdir} {params.odir}"