From 8fb40415fc7e355970d532e97ae2c6c43764b592 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 29 May 2019 11:58:37 -0700 Subject: [PATCH 001/139] Addhtseq to environment requirements --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 3facf7c9c..c4e01fea4 100644 --- a/environment.yml +++ b/environment.yml @@ -25,3 +25,4 @@ dependencies: - gffread=0.9.12 - deeptools=3.2.0 - multiqc=1.7 + - htseq=0.11.2 From 71bcd9f87d098ac24a1cd82586840ada13b7eeec Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 29 May 2019 12:04:05 -0700 Subject: [PATCH 002/139] Add HTSeq to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4703fab..9f65cf057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ * subread 1.6.2 -> 1.6.4 * gffread 0.9.9 -> 0.9.12 * multiqc 1.6 -> 1.7 +* Add htseq=0.11.2 ## [Version 1.2](https://github.com/nf-core/rnaseq/releases/tag/1.2) - 2018-12-12 From 27273929d9b0f8ad19c2d8cea0d2c17d8ce1ff8c Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 29 May 2019 12:10:02 -0700 Subject: [PATCH 003/139] small change to trigger build --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f65cf057..1a333ae4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ * subread 1.6.2 -> 1.6.4 * gffread 0.9.9 -> 0.9.12 * multiqc 1.6 -> 1.7 -* Add htseq=0.11.2 +* Added htseq=0.11.2 ## [Version 1.2](https://github.com/nf-core/rnaseq/releases/tag/1.2) - 2018-12-12 From 74c004c322b6160c6b45c74225d3bcaa552d9c5b Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 29 May 2019 12:11:32 -0700 Subject: [PATCH 004/139] Add v1.4dev section and move change there --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a333ae4a..95c86de04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # nf-core/rnaseq: Changelog +## Version 1.4dev + +#### Dependency Updates +* Added htseq=0.11.2 + ## Version 1.3 #### Pipeline Updates @@ -26,7 +31,6 @@ * subread 1.6.2 -> 1.6.4 * gffread 0.9.9 -> 0.9.12 * multiqc 1.6 -> 1.7 -* Added htseq=0.11.2 ## [Version 1.2](https://github.com/nf-core/rnaseq/releases/tag/1.2) - 2018-12-12 From eccbec3481d39d1ba4e3c6cb37fb06b1f8dd8900 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 14:30:26 -0700 Subject: [PATCH 005/139] Add salmon to requirements --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 9a079fa68..1dcd8d323 100644 --- a/environment.yml +++ b/environment.yml @@ -26,4 +26,4 @@ dependencies: - gffread=0.9.12 - deeptools=3.2.1 - multiqc=1.7 - - htseq=0.11.2 + - salmon=0.14.0 From eeb85c624b1dfa1c0a96711afc76842ea908b6e4 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 14:37:37 -0700 Subject: [PATCH 006/139] Update changelog with salmon=0.14.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd3214094..1be7da344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Version 1.4dev #### Dependency Updates -* Added htseq=0.11.2 +* Added salmon=0.14.0 ## [Version 1.3](https://github.com/nf-core/rnaseq/releases/tag/1.3) - 2019-03-26 #### Pipeline updates From 2a3e6b46761b29d4c8b248e34a5d9dbe493c750b Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 14:47:47 -0700 Subject: [PATCH 007/139] Add salmon index --- main.nf | 36 ++++++++++++++++++++++++++++++++++++ nextflow.config | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index c3dc2ec49..924098d24 100644 --- a/main.nf +++ b/main.nf @@ -36,6 +36,7 @@ def helpMessage() { --star_index Path to STAR index --hisat2_index Path to HiSAT2 index --fasta Path to Fasta reference + --transcriptome Path to Fasta transcriptome --gtf Path to GTF file --gff Path to GFF3 file --bed12 Path to bed12 file @@ -168,6 +169,15 @@ if( params.gtf ){ exit 1, "No GTF or GFF3 annotation specified!" } +if (params.transcriptome){ + Channel + .fromPath(params.transcriptome) + .ifEmpty { exit 1, "Transcript fasta file is unreachable: ${params.transcriptome}" } + .set { tx_fasta_ch } +} + + + if( params.bed12 ){ bed12 = Channel .fromPath(params.bed12) @@ -251,6 +261,7 @@ if(params.aligner == 'star'){ else if(params.fasta) summary['Fasta Ref'] = params.fasta if(params.splicesites) summary['Splice Sites'] = params.splicesites } +if(params.transcriptome) summary['Transcriptome sequences'] = params.transcriptome if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff if(params.bed12) summary['BED Annotation'] = params.bed12 @@ -431,6 +442,31 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ """ } } + + +/* + * PREPROCESSING - Create Salmon transcriptome index + */ +if(params.transcriptome){ + process index { + tag "$transcriptome.simpleName" + publishDir path: { "${params.outdir}" }, + mode: 'copy' + + input: + file transcriptome from tx_fasta_ch + + output: + file 'salmon_index' into index_ch + + script: + """ + salmon index --threads $task.cpus -t $transcriptome -i salmon_index + """ + } +} + + /* * PREPROCESSING - Convert GFF3 to GTF */ diff --git a/nextflow.config b/nextflow.config index 900f25292..5924aba23 100644 --- a/nextflow.config +++ b/nextflow.config @@ -18,7 +18,7 @@ params { fcExtraAttributes = 'gene_name' fcGroupFeatures = 'gene_id' fcGroupFeaturesType = 'gene_biotype' - markdup_java_options = '"-Xms4000m -Xmx7g"' //Established values for markDuplicate memory consumption, see issue PR #689 (in Sarek) for details + markdup_java_options = '"-Xms4000m -Xmx7g"' //Established values for markDuplicate memory consumption, see issue PR #689 (in Sarek) for details splicesites = false saveReference = false saveTrimmed = false @@ -26,6 +26,7 @@ params { singleEnd = false reads = "data/*{1,2}.fastq.gz" outdir = './results' + transcriptome = false seqCenter = false skip_qc = false skip_fastqc = false From 5dd9a145534473ea329662359711b7b31cf6b7a8 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 14:52:11 -0700 Subject: [PATCH 008/139] Add transcriptome to test config --- conf/test.config | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/test.config b/conf/test.config index 14d3aedb9..b6e539b24 100644 --- a/conf/test.config +++ b/conf/test.config @@ -26,4 +26,5 @@ params { // Genome references fasta = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genome.fa' gtf = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genes.gtf' + transcriptome = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/transcriptome.fa' } From 10d0d6d49fc3368e1a3c099c03153b8450f0c7d3 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 14:53:05 -0700 Subject: [PATCH 009/139] fa --> fasta --- conf/test.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/test.config b/conf/test.config index b6e539b24..a3deac2f3 100644 --- a/conf/test.config +++ b/conf/test.config @@ -26,5 +26,5 @@ params { // Genome references fasta = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genome.fa' gtf = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genes.gtf' - transcriptome = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/transcriptome.fa' + transcriptome = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/transcriptome.fasta' } From 62526067476d4640f9a86ae7aa9b262355f3f0ec Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 14:54:28 -0700 Subject: [PATCH 010/139] Less character width to specify transcriptome in summary --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 924098d24..23ceb3fbd 100644 --- a/main.nf +++ b/main.nf @@ -261,7 +261,7 @@ if(params.aligner == 'star'){ else if(params.fasta) summary['Fasta Ref'] = params.fasta if(params.splicesites) summary['Splice Sites'] = params.splicesites } -if(params.transcriptome) summary['Transcriptome sequences'] = params.transcriptome +if(params.transcriptome) summary['Transcriptome'] = params.transcriptome if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff if(params.bed12) summary['BED Annotation'] = params.bed12 From c1a4dfb3b2d35d66a46f6240e21d80bb5a85a885 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 15:01:13 -0700 Subject: [PATCH 011/139] Add salmon quant --- main.nf | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 23ceb3fbd..53cbeac08 100644 --- a/main.nf +++ b/main.nf @@ -448,7 +448,7 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ * PREPROCESSING - Create Salmon transcriptome index */ if(params.transcriptome){ - process index { + process salmon_index { tag "$transcriptome.simpleName" publishDir path: { "${params.outdir}" }, mode: 'copy' @@ -1004,6 +1004,31 @@ process dupradar { """ } +if (params.transcriptome){ + process salmon_quant { + tag "$sample" + publishDir "${params.outdir}/Salmon", mode: 'copy' + + input: + file index from index_ch.collect() + set sample, file(reads) from raw_salmon + + output: + file(sample) into quant_ch + + script: + if (params.singleEnd){ + """ + salmon quant --validateMappings --threads $task.cpus --libType=A -i $index -r ${reads[0]} -o $sample + """ + }else{ + """ + salmon quant --validateMappings --threads $task.cpus --libType=A -i $index -1 ${reads[0]} -2 ${reads[1]} -o $sample + """ + } + } +} + /* * STEP 9 - Feature counts From 6aa3689a15b1f9888bc0198abadeee406bbe6c33 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 15:02:30 -0700 Subject: [PATCH 012/139] add raw_salmon channel --- main.nf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.nf b/main.nf index 53cbeac08..bf8dd5b82 100644 --- a/main.nf +++ b/main.nf @@ -224,19 +224,19 @@ if(params.readPaths){ .from(params.readPaths) .map { row -> [ row[0], [file(row[1][0])]] } .ifEmpty { exit 1, "params.readPaths was empty - no input files supplied" } - .into { raw_reads_fastqc; raw_reads_trimgalore } + .into { raw_reads_fastqc; raw_reads_trimgalore; raw_salmon } } else { Channel .from(params.readPaths) .map { row -> [ row[0], [file(row[1][0]), file(row[1][1])]] } .ifEmpty { exit 1, "params.readPaths was empty - no input files supplied" } - .into { raw_reads_fastqc; raw_reads_trimgalore } + .into { raw_reads_fastqc; raw_reads_trimgalore; raw_salmon } } } else { Channel .fromFilePairs( params.reads, size: params.singleEnd ? 1 : 2 ) .ifEmpty { exit 1, "Cannot find any reads matching: ${params.reads}\nNB: Path needs to be enclosed in quotes!\nNB: Path requires at least one * wildcard!\nIf this is single-end data, please specify --singleEnd on the command line." } - .into { raw_reads_fastqc; raw_reads_trimgalore } + .into { raw_reads_fastqc; raw_reads_trimgalore; raw_salmon } } From 2d6c85be892b4a0234d392490ea7ad36a0e717c5 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 15:06:16 -0700 Subject: [PATCH 013/139] Add salmon index to changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd3214094..ed7ed0dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Version 1.4dev +#### Pipeline updates + +* Add Salmon index for transcriptome + #### Dependency Updates * Added htseq=0.11.2 From 1eeea233026182dd3c6803b1ea180630af03b3d6 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 15:16:26 -0700 Subject: [PATCH 014/139] Add salmon to usage --- docs/usage.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index 517108025..664f61dad 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -17,6 +17,8 @@ * [FeatureCounts Extra Gene Names](#featurecounts-extra-gene-names) * [Default Attribute Type](#default-attribute-type) * [Extra Gene Names](#extra-gene-names) +* [Transcriptome mapping with Salmon](#transcriptome-mapping-with-salmon) + * [Indexing the transcriptome](#indexing-the-transcriptome) * [Alignment tool](#alignment-tool) * [Reference genomes](#reference-genomes) * [`--genome` (using iGenomes)](#--genome-using-igenomes) @@ -196,6 +198,15 @@ Note that you can also specify more than one desired value, separated by a comma ``--fcExtraAttributes gene_id,...`` +## Transcriptome mapping with Salmon + +If the option `--transcriptome` is provided to a fasta file of cDNA sequences, the pipeline will also run transcriptome quantification using [Salmon](https://salmon.readthedocs.io/en/latest/salmon.html). + +### Indexing the transcriptome + +The transcriptome is indexed using the default parameters of Salmon, using the default k-mer size of 31. As [discussed](https://salmon.readthedocs.io/en/latest/salmon.html#preparing-transcriptome-indices-mapping-based-mode), the a k-mer size off 31 works well with reads that are length 75bp or longer. + + ## Alignment tool By default, the pipeline uses [STAR](https://github.com/alexdobin/STAR) to align the raw FastQ reads to the reference genome. STAR is fast and common, but requires a lot of memory to run, typically around 38GB for the Human GRCh37 reference genome. From 362304a914d357e0d97de150e6a169fdcad92483 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 15:20:33 -0700 Subject: [PATCH 015/139] Output salmon index to reference_transcriptome folder --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index bf8dd5b82..4304b359a 100644 --- a/main.nf +++ b/main.nf @@ -450,7 +450,7 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ if(params.transcriptome){ process salmon_index { tag "$transcriptome.simpleName" - publishDir path: { "${params.outdir}" }, + publishDir path: { "${params.outdir}/reference_transcriptome" }, mode: 'copy' input: From 110fc455afd2ead333c5816e84d2ba8a67fbe5f5 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 15:25:49 -0700 Subject: [PATCH 016/139] write out more docs for salmon index --- docs/output.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/output.md b/docs/output.md index 0f3a4c8ff..3bb9d2aea 100644 --- a/docs/output.md +++ b/docs/output.md @@ -23,6 +23,7 @@ and processes data using the following steps: * [dupRadar](#dupradar) - technical / biological read duplication * [Preseq](#preseq) - library complexity * [featureCounts](#featurecounts) - gene counts, biotype counts, rRNA estimation. +* [Salmon](#salmon) - gene counts, biotype counts, rRNA estimation. * [StringTie](#stringtie) - FPKMs for genes and transcripts * [Sample_correlation](#Sample_correlation) - create MDS plot and sample pairwise distance heatmap / dendrogram * [MultiQC](#multiqc) - aggregate report, describing results of the whole pipeline @@ -301,6 +302,32 @@ We also use featureCounts to count overlaps with different classes of features. **Output directory: `results/featureCounts`** +* `Sample.bam_biotype_counts.txt` + * Read counts for the different gene biotypes that featureCounts distinguishes. +* `Sample.featureCounts.txt` + * Read the counts for each gene provided in the reference `gtf` file +* `Sample.featureCounts.txt.summary` + * Summary file, containing statistics about the counts + +## Salmon +[Salmon](https://salmon.readthedocs.io/en/latest/salmon.html) from [Ocean Genomics](https://oceangenomics.com/) quasi-maps reads to a transcriptome and counts gene expression per transcript. + +### Salmon Index + +**Output directory: `results/reference_transcriptome`** + +* `Sample.bam_biotype_counts.txt` + * Read counts for the different gene biotypes that featureCounts distinguishes. +* `Sample.featureCounts.txt` + * Read the counts for each gene provided in the reference `gtf` file +* `Sample.featureCounts.txt.summary` + * Summary file, containing statistics about the counts + + +### Salmon quant + +**Output directory: `results/salmon`** + * `Sample.bam_biotype_counts.txt` * Read counts for the different gene biotypes that featureCounts distinguishes. * `Sample.featureCounts.txt` From 23e582171f2f00c2018800ecc4354d41e36f1b79 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 16:16:21 -0700 Subject: [PATCH 017/139] Add cpus for salmon quant --- conf/base.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conf/base.config b/conf/base.config index 3b087a99d..0fcf75a78 100644 --- a/conf/base.config +++ b/conf/base.config @@ -34,6 +34,10 @@ process { memory = { check_max( 200.GB * task.attempt, 'memory' ) } time = { check_max( 5.h * task.attempt, 'time' ) } } + withName: salmon_quant { + cpus = { check_max( 8, 'cpus' ) } + memory = { check_max( 16.GB * task.attempt, 'memory' ) } + } withLabel: low_memory { memory = { check_max( 16.GB * task.attempt, 'memory' ) } } From da96b5a7bd0f7b371d38ad0aae5e0ea951c71c99 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 16:16:46 -0700 Subject: [PATCH 018/139] Add gtf for gene summarization --- main.nf | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/main.nf b/main.nf index 4304b359a..ddd164601 100644 --- a/main.nf +++ b/main.nf @@ -161,7 +161,7 @@ if( params.gtf ){ .fromPath(params.gtf) .ifEmpty { exit 1, "GTF annotation file not found: ${params.gtf}" } .into { gtf_makeSTARindex; gtf_makeHisatSplicesites; gtf_makeHISATindex; gtf_makeBED12; - gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM } + gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM, gtf_salmon } } else if( params.gff ){ gffFile = Channel.fromPath(params.gff) .ifEmpty { exit 1, "GFF annotation file not found: ${params.gff}" } @@ -479,7 +479,7 @@ if(params.gff){ output: file "${gff.baseName}.gtf" into gtf_makeSTARindex, gtf_makeHisatSplicesites, gtf_makeHISATindex, gtf_makeBED12, - gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM + gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon script: """ @@ -1007,10 +1007,11 @@ process dupradar { if (params.transcriptome){ process salmon_quant { tag "$sample" - publishDir "${params.outdir}/Salmon", mode: 'copy' + publishDir "${params.outdir}/salmon", mode: 'copy' input: file index from index_ch.collect() + file gtff from set sample, file(reads) from raw_salmon output: @@ -1019,11 +1020,11 @@ if (params.transcriptome){ script: if (params.singleEnd){ """ - salmon quant --validateMappings --threads $task.cpus --libType=A -i $index -r ${reads[0]} -o $sample + salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -r ${reads[0]} -o $sample """ }else{ """ - salmon quant --validateMappings --threads $task.cpus --libType=A -i $index -1 ${reads[0]} -2 ${reads[1]} -o $sample + salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -1 ${reads[0]} -2 ${reads[1]} -o $sample """ } } From ad0791a9ab82f27a27ebf529b96ccb955d8af662 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 17:04:14 -0700 Subject: [PATCH 019/139] use BOTH salmon_quant and salmon_index configs --- conf/base.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conf/base.config b/conf/base.config index 0fcf75a78..4a5578ff1 100644 --- a/conf/base.config +++ b/conf/base.config @@ -34,6 +34,10 @@ process { memory = { check_max( 200.GB * task.attempt, 'memory' ) } time = { check_max( 5.h * task.attempt, 'time' ) } } + withName: salmon_index { + cpus = { check_max( 8, 'cpus' ) } + memory = { check_max( 16.GB * task.attempt, 'memory' ) } + } withName: salmon_quant { cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 16.GB * task.attempt, 'memory' ) } From f3448199b4c1819532df0ef1b0403078132e27af Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 16:19:28 -0700 Subject: [PATCH 020/139] use same cpu, memory for both salmon quant and index --- conf/base.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conf/base.config b/conf/base.config index 4a5578ff1..30da93777 100644 --- a/conf/base.config +++ b/conf/base.config @@ -42,6 +42,10 @@ process { cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 16.GB * task.attempt, 'memory' ) } } + withName: salmon_quant { + cpus = { check_max( 8, 'cpus' ) } + memory = { check_max( 16.GB * task.attempt, 'memory' ) } + } withLabel: low_memory { memory = { check_max( 16.GB * task.attempt, 'memory' ) } } From 5b5424df6888690d4d3f5c46aa8b0176f509bda2 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 16:24:57 -0700 Subject: [PATCH 021/139] semicolon, not comma -_-;;; --- main.nf | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index ddd164601..1fca8ebc5 100644 --- a/main.nf +++ b/main.nf @@ -161,7 +161,7 @@ if( params.gtf ){ .fromPath(params.gtf) .ifEmpty { exit 1, "GTF annotation file not found: ${params.gtf}" } .into { gtf_makeSTARindex; gtf_makeHisatSplicesites; gtf_makeHISATindex; gtf_makeBED12; - gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM, gtf_salmon } + gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM; gtf_salmon } } else if( params.gff ){ gffFile = Channel.fromPath(params.gff) .ifEmpty { exit 1, "GFF annotation file not found: ${params.gff}" } @@ -1028,6 +1028,13 @@ if (params.transcriptome){ """ } } + + process merge_salmon_quant { + label 'low_memory' + publishDir "${params.outdir}/salmon", mode: 'copy' + + + } } From 5ab4b4d65ecd580e70a041c7e9ab604da28a71ec Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 16:57:36 -0700 Subject: [PATCH 022/139] Add merging of salmon gene and transcript counts --- main.nf | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 1fca8ebc5..4a76a884c 100644 --- a/main.nf +++ b/main.nf @@ -1011,20 +1011,26 @@ if (params.transcriptome){ input: file index from index_ch.collect() - file gtff from + file gtf from gtf_salmon set sample, file(reads) from raw_salmon output: file(sample) into quant_ch + file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant + file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant script: if (params.singleEnd){ """ salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -r ${reads[0]} -o $sample + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt """ }else{ """ salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -1 ${reads[0]} -2 ${reads[1]} -o $sample + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt """ } } @@ -1033,6 +1039,26 @@ if (params.transcriptome){ label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' + input: + file transcript_quants from salmon_transcript_quant.collect() + file gene_quants from salmon_gene_quant.collect() + + output: + file 'salmon_merged_gene_counts.txt' + file 'salmon_merged_transcript_counts.txt' + + script: + //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. + def single = input_files instanceof Path ? 1 : input_files.size() + def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' + """ + ## Merge gene counts + echo gene_id $input_files | sed 's/.quant.genes.ids-only.txt//g' | tr ' ' '\t' > gene_header.txt + $merge $input_files | csvtk cut -t -f "Name" | tail -n +2 | cat gene_header.txt - > merged_gene_counts.txt + ## Merge transcript counts + echo gene_id $input_files | sed 's/.quant.ids-only.txt//g' | tr ' ' '\t' > transcript_header.txt + $merge $input_files | csvtk cut -t -f "Name" | tail -n +2 | cat transcript_header.txt - > merged_transcript_counts.txt + """ } } From be6eafcf197371f181e4b5d3becef1287c91984f Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 17:04:45 -0700 Subject: [PATCH 023/139] Don't need salmon quant twice --- conf/base.config | 4 ---- 1 file changed, 4 deletions(-) diff --git a/conf/base.config b/conf/base.config index 30da93777..4a5578ff1 100644 --- a/conf/base.config +++ b/conf/base.config @@ -42,10 +42,6 @@ process { cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 16.GB * task.attempt, 'memory' ) } } - withName: salmon_quant { - cpus = { check_max( 8, 'cpus' ) } - memory = { check_max( 16.GB * task.attempt, 'memory' ) } - } withLabel: low_memory { memory = { check_max( 16.GB * task.attempt, 'memory' ) } } From eff09b2941c0ebce4d324871b2d6e39cc59e9afb Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 17:37:15 -0700 Subject: [PATCH 024/139] Add merging of transcript id to gene name mapping --- main.nf | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/main.nf b/main.nf index 4a76a884c..bf7c4d9c5 100644 --- a/main.nf +++ b/main.nf @@ -1052,12 +1052,25 @@ if (params.transcriptome){ def single = input_files instanceof Path ? 1 : input_files.size() def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' """ + ## Merge transcript counts + echo transcript_id gene_name $input_files | sed 's/.quant.ids-only.txt//g' | tr ' ' '\t' > transcript_header.txt + ## Gene transcript_id <--> gene_name mapping + awk -F "\t" '$3 == "transcript" { print $9 }' $gtf | grep -oP '(?<=transcript_id ")(\w+)' > transcript_ids.txt + awk -F "\t" '$3 == "transcript" { print $9 }' $gtf | grep -oP '(?<=gene_name ")(\w+)' > transcript_gene_names.txt + paste transcript_ids.txt transcript_gene_names.txt > transcript_ids__to__gene_names.txt + $merge $input_files \\ + | csvtk cut -t -f "Name" \\ + | tail -n +2 \\ + | csvtk join -t -f 1 - transcript_ids__to__gene_names.txt \\ + | cat transcript_header.txt - \\ + awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' |\\ + cut -f '1,3-' |\\ + > merged_transcript_counts.txt + ## Merge gene counts echo gene_id $input_files | sed 's/.quant.genes.ids-only.txt//g' | tr ' ' '\t' > gene_header.txt $merge $input_files | csvtk cut -t -f "Name" | tail -n +2 | cat gene_header.txt - > merged_gene_counts.txt - ## Merge transcript counts - echo gene_id $input_files | sed 's/.quant.ids-only.txt//g' | tr ' ' '\t' > transcript_header.txt - $merge $input_files | csvtk cut -t -f "Name" | tail -n +2 | cat transcript_header.txt - > merged_transcript_counts.txt + """ } From faf2b3201dc90763b9ce93f71e73af532b56ed7e Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 10:16:11 -0700 Subject: [PATCH 025/139] Move salmon to after featurecounts to use featurecounts output --- main.nf | 171 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 93 insertions(+), 78 deletions(-) diff --git a/main.nf b/main.nf index bf7c4d9c5..21400229d 100644 --- a/main.nf +++ b/main.nf @@ -161,7 +161,8 @@ if( params.gtf ){ .fromPath(params.gtf) .ifEmpty { exit 1, "GTF annotation file not found: ${params.gtf}" } .into { gtf_makeSTARindex; gtf_makeHisatSplicesites; gtf_makeHISATindex; gtf_makeBED12; - gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM; gtf_salmon } + gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM; + gtf_salmon_quant; gtf_merge_salmon_quant } } else if( params.gff ){ gffFile = Channel.fromPath(params.gff) .ifEmpty { exit 1, "GFF annotation file not found: ${params.gff}" } @@ -479,7 +480,7 @@ if(params.gff){ output: file "${gff.baseName}.gtf" into gtf_makeSTARindex, gtf_makeHisatSplicesites, gtf_makeHISATindex, gtf_makeBED12, - gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon + gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon_quant, gtf_merge_salmon_quant script: """ @@ -1004,77 +1005,6 @@ process dupradar { """ } -if (params.transcriptome){ - process salmon_quant { - tag "$sample" - publishDir "${params.outdir}/salmon", mode: 'copy' - - input: - file index from index_ch.collect() - file gtf from gtf_salmon - set sample, file(reads) from raw_salmon - - output: - file(sample) into quant_ch - file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant - file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant - - script: - if (params.singleEnd){ - """ - salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -r ${reads[0]} -o $sample - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt - """ - }else{ - """ - salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -1 ${reads[0]} -2 ${reads[1]} -o $sample - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt - """ - } - } - - process merge_salmon_quant { - label 'low_memory' - publishDir "${params.outdir}/salmon", mode: 'copy' - - input: - file transcript_quants from salmon_transcript_quant.collect() - file gene_quants from salmon_gene_quant.collect() - - output: - file 'salmon_merged_gene_counts.txt' - file 'salmon_merged_transcript_counts.txt' - - script: - //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def single = input_files instanceof Path ? 1 : input_files.size() - def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' - """ - ## Merge transcript counts - echo transcript_id gene_name $input_files | sed 's/.quant.ids-only.txt//g' | tr ' ' '\t' > transcript_header.txt - ## Gene transcript_id <--> gene_name mapping - awk -F "\t" '$3 == "transcript" { print $9 }' $gtf | grep -oP '(?<=transcript_id ")(\w+)' > transcript_ids.txt - awk -F "\t" '$3 == "transcript" { print $9 }' $gtf | grep -oP '(?<=gene_name ")(\w+)' > transcript_gene_names.txt - paste transcript_ids.txt transcript_gene_names.txt > transcript_ids__to__gene_names.txt - $merge $input_files \\ - | csvtk cut -t -f "Name" \\ - | tail -n +2 \\ - | csvtk join -t -f 1 - transcript_ids__to__gene_names.txt \\ - | cat transcript_header.txt - \\ - awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' |\\ - cut -f '1,3-' |\\ - > merged_transcript_counts.txt - - ## Merge gene counts - echo gene_id $input_files | sed 's/.quant.genes.ids-only.txt//g' | tr ' ' '\t' > gene_header.txt - $merge $input_files | csvtk cut -t -f "Name" | tail -n +2 | cat gene_header.txt - > merged_gene_counts.txt - - """ - - } -} /* @@ -1130,7 +1060,7 @@ process merge_featureCounts { file input_files from featureCounts_to_merge.collect() output: - file 'merged_gene_counts.txt' + file 'merged_gene_counts.txt' into featurecounts_merged script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1143,7 +1073,92 @@ process merge_featureCounts { /* - * STEP 11 - stringtie FPKM + * STEP 11 - Salmon on transcriptome + */ +if (params.transcriptome){ + process salmon_quant { + tag "$sample" + publishDir "${params.outdir}/salmon", mode: 'copy' + + input: + file index from index_ch.collect() + file gtf from gtf_salmon_quant + set sample, file(reads) from raw_salmon + + output: + file(sample) into quant_ch + file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant + file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant + + script: + if (params.singleEnd){ + """ + salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -r ${reads[0]} -o $sample + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt + """ + }else{ + """ + salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -1 ${reads[0]} -2 ${reads[1]} -o $sample + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt + """ + } + } + + process merge_salmon_quant { + label 'low_memory' + publishDir "${params.outdir}/salmon", mode: 'copy' + + input: + file transcript_quants from salmon_transcript_quant.collect() + file gene_quants from salmon_gene_quant.collect() + file gtf from gtf_merge_salmon_quant + // Use gene_id, gene_name mapping from featurecounts to make sure it matches + file featurecounts_merged from featurecounts_merged + + output: + file 'salmon_merged_gene_counts.txt' + file 'salmon_merged_transcript_counts.txt' + + script: + //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. + def single = input_files instanceof Path ? 1 : input_files.size() + def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' + """ + ## Merge transcript counts + echo transcript_id gene_name $transcript_quants | sed 's/.quant.ids-only.txt//g' | tr ' ' '\\t' > transcript_header.txt + ## Gene transcript_id <--> gene_name mapping + awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=transcript_id ")(\\w+)' > transcript_ids.txt + awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=gene_name ")(\\w+)' > transcript_gene_names.txt + paste transcript_ids.txt transcript_gene_names.txt > transcript_ids__to__gene_names.txt + $merge $transcript_quants \\ + | csvtk cut -t -f "Name" \\ + | tail -n +2 \\ + | csvtk join -t -f 1 - transcript_ids__to__gene_names.txt \\ + | cat transcript_header.txt - \\ + awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' |\\ + cut -f '1,3-' |\\ + > merged_transcript_counts.txt + + ## Merge gene counts + ## Make header + echo gene_id gene_name $gene_quants | sed 's/.quant.genes.ids-only.txt//g' | tr ' ' '\t' > gene_header.txt + ## Merge gene counts using gene_id to gene_name mapping from featurecounts, as + $merge $gene_quants \\ + | csvtk cut -t -f "Name" \\ + | tail -n +2 \\ + | csvtk join -t -f 1 - \$(csvtk cut -t -f 1,2 $featurecounts_merged) \\ + | cat gene_header.txt - > merged_gene_counts.txt + + """ + + } +} + + +/* + * STEP 12 - stringtie FPKM */ process stringtieFPKM { tag "${bam_stringtieFPKM.baseName - '.sorted'}" @@ -1187,7 +1202,7 @@ process stringtieFPKM { } /* - * STEP 12 - edgeR MDS and heatmap + * STEP 13 - edgeR MDS and heatmap */ process sample_correlation { label 'low_memory' @@ -1220,7 +1235,7 @@ process sample_correlation { } /* - * STEP 13 - MultiQC + * STEP 14 - MultiQC */ process multiqc { publishDir "${params.outdir}/MultiQC", mode: 'copy' @@ -1260,7 +1275,7 @@ process multiqc { } /* - * STEP 14 - Output Description HTML + * STEP 15 - Output Description HTML */ process output_documentation { publishDir "${params.outdir}/pipeline_info", mode: 'copy' From 49b694a6a1296378af7401b82b98a00749733c17 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 10:26:34 -0700 Subject: [PATCH 026/139] Add scraping software versions --- bin/scrape_software_versions.py | 2 ++ main.nf | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/bin/scrape_software_versions.py b/bin/scrape_software_versions.py index 522a7dcd4..97527ace0 100755 --- a/bin/scrape_software_versions.py +++ b/bin/scrape_software_versions.py @@ -14,6 +14,7 @@ 'Picard MarkDuplicates': ['v_markduplicates.txt', r"([\d\.]+)-SNAPSHOT"], 'Samtools': ['v_samtools.txt', r"samtools (\S+)"], 'featureCounts': ['v_featurecounts.txt', r"featureCounts v(\S+)"], + 'Salmon': ['v_salmon.txt', r"salmon (\S+)"], 'deepTools': ['v_deeptools.txt', r"bamCoverage (\S+)"], 'StringTie': ['v_stringtie.txt', r"(\S+)"], 'Preseq': ['v_preseq.txt', r"Version: (\S+)"], @@ -34,6 +35,7 @@ results['Picard MarkDuplicates'] = 'N/A' results['Samtools'] = 'N/A' results['featureCounts'] = 'N/A' +results['Salmon'] = 'N/A' results['StringTie'] = 'N/A' results['Preseq'] = 'N/A' results['deepTools'] = 'N/A' diff --git a/main.nf b/main.nf index 21400229d..48dd642ef 100644 --- a/main.nf +++ b/main.nf @@ -338,6 +338,7 @@ process get_software_versions { read_duplication.py --version &> v_rseqc.txt echo \$(bamCoverage --version 2>&1) > v_deeptools.txt featureCounts -v &> v_featurecounts.txt + salmon --version &> v_salmon.txt picard MarkDuplicates --version &> v_markduplicates.txt || true samtools --version &> v_samtools.txt multiqc --version &> v_multiqc.txt @@ -1078,7 +1079,7 @@ process merge_featureCounts { if (params.transcriptome){ process salmon_quant { tag "$sample" - publishDir "${params.outdir}/salmon", mode: 'copy' + publishDir "${params.outdir}/salmon/", mode: 'copy' input: file index from index_ch.collect() @@ -1091,15 +1092,31 @@ if (params.transcriptome){ file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant script: + def strandedness = params.unstranded ? 'U' : 'SR' if (params.singleEnd){ """ - salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -r ${reads[0]} -o $sample + salmon quant --validateMappings \\ + --seqBias --useVBOpt --gcBias \\ + --geneMap ${gtf} \\ + --threads $task.cpus \\ + --libType=$strandedness \\ + --index $index \\ + -r ${reads[0]} \\ + -o $sample csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt """ }else{ """ - salmon quant --validateMappings --geneMap ${gtf} --threads $task.cpus --libType=A -i $index -1 ${reads[0]} -2 ${reads[1]} -o $sample + salmon quant --validateMappings \\ + --seqBias --useVBOpt --gcBias \\ + --geneMap ${gtf} \\ + --threads $task.cpus \\ + --libType=$strandedness \\ + --index $index \\ + -1 ${reads[0]} \\ + -2 ${reads[2]} \\ + -o $sample csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt """ @@ -1123,7 +1140,7 @@ if (params.transcriptome){ script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def single = input_files instanceof Path ? 1 : input_files.size() + def single = transcript_quants instanceof Path ? 1 : transcript_quants.size() def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' """ ## Merge transcript counts From 4267ba9b43e1f559f5858de52629c057afb0e6ab Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 10:30:36 -0700 Subject: [PATCH 027/139] Separate transcript and gene count merging --- main.nf | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/main.nf b/main.nf index 48dd642ef..c46b8dffe 100644 --- a/main.nf +++ b/main.nf @@ -1122,20 +1122,15 @@ if (params.transcriptome){ """ } } - - process merge_salmon_quant { + process merge_salmon_transcript_quant { label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' input: file transcript_quants from salmon_transcript_quant.collect() - file gene_quants from salmon_gene_quant.collect() file gtf from gtf_merge_salmon_quant - // Use gene_id, gene_name mapping from featurecounts to make sure it matches - file featurecounts_merged from featurecounts_merged output: - file 'salmon_merged_gene_counts.txt' file 'salmon_merged_transcript_counts.txt' script: @@ -1157,7 +1152,26 @@ if (params.transcriptome){ awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' |\\ cut -f '1,3-' |\\ > merged_transcript_counts.txt + """ + } + + process merge_salmon_gene_quant { + label 'low_memory' + publishDir "${params.outdir}/salmon", mode: 'copy' + input: + file gene_quants from salmon_gene_quant.collect() + // Use gene_id, gene_name mapping from featurecounts to make sure it matches + file featurecounts_merged from featurecounts_merged + + output: + file 'salmon_merged_gene_counts.txt' + + script: + //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. + def single = gene_quants instanceof Path ? 1 : gene_quants.size() + def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' + """ ## Merge gene counts ## Make header echo gene_id gene_name $gene_quants | sed 's/.quant.genes.ids-only.txt//g' | tr ' ' '\t' > gene_header.txt @@ -1169,7 +1183,6 @@ if (params.transcriptome){ | cat gene_header.txt - > merged_gene_counts.txt """ - } } From 68dac0c0043b505d666b5c908f40bde921ba27fd Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 10:35:51 -0700 Subject: [PATCH 028/139] Output sample name with trimmed reads --- main.nf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main.nf b/main.nf index c46b8dffe..16a487103 100644 --- a/main.nf +++ b/main.nf @@ -556,7 +556,7 @@ process trim_galore { file wherearemyfiles from ch_where_trim_galore.collect() output: - file "*fq.gz" into trimmed_reads + set val(name), file("*fq.gz") into trimmed_reads_alignment, trimmed_reads_salmon file "*trimming_report.txt" into trimgalore_results file "*_fastqc.{zip,html}" into trimgalore_fastqc_reports file "where_are_my_files.txt" @@ -606,7 +606,7 @@ if(params.aligner == 'star'){ hisat_stdout = Channel.from(false) process star { label 'high_memory' - tag "$prefix" + tag "$name" publishDir "${params.outdir}/STAR", mode: 'copy', saveAs: {filename -> if (filename.indexOf(".bam") == -1) "logs/$filename" @@ -616,7 +616,7 @@ if(params.aligner == 'star'){ } input: - file reads from trimmed_reads + set val(name), file(reads) from trimmed_reads_alignment file index from star_index.collect() file gtf from gtf_star.collect() file wherearemyfiles from ch_where_star.collect() @@ -664,7 +664,7 @@ if(params.aligner == 'hisat2'){ star_log = Channel.from(false) process hisat2Align { label 'high_memory' - tag "$prefix" + tag "$name" publishDir "${params.outdir}/HISAT2", mode: 'copy', saveAs: {filename -> if (filename.indexOf(".hisat2_summary.txt") > 0) "logs/$filename" @@ -674,7 +674,7 @@ if(params.aligner == 'hisat2'){ } input: - file reads from trimmed_reads + set val(name), file(reads) from trimmed_reads_alignment file hs2_indices from hs2_indices.collect() file alignment_splicesites from alignment_splicesites.collect() file wherearemyfiles from ch_where_hisat2.collect() @@ -1084,7 +1084,7 @@ if (params.transcriptome){ input: file index from index_ch.collect() file gtf from gtf_salmon_quant - set sample, file(reads) from raw_salmon + set sample, file(reads) from trimmed_reads_salmon output: file(sample) into quant_ch From 29c44cf577a04260a6571fc58a5d5eb70050271c Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 10:47:46 -0700 Subject: [PATCH 029/139] try some different logic to get salmon quant to run enough times --- main.nf | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/main.nf b/main.nf index 16a487103..3fadc2a99 100644 --- a/main.nf +++ b/main.nf @@ -1103,8 +1103,12 @@ if (params.transcriptome){ --index $index \\ -r ${reads[0]} \\ -o $sample - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ + | sed "s:TPM:$sample:" \\ + > ${sample}/${sample}.quant.ids-only.txt + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ + | sed "s:TPM:$sample:" \\ + > ${sample}/${sample}.quant.genes.ids-only.txt """ }else{ """ @@ -1117,11 +1121,20 @@ if (params.transcriptome){ -1 ${reads[0]} \\ -2 ${reads[2]} \\ -o $sample - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf > ${sample}/${sample}.quant.ids-only.txt - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf > ${sample}/${sample}.quant.genes.ids-only.txt + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ + | sed "s:TPM:$sample:" \\ + > ${sample}/${sample}.quant.ids-only.txt + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ + | sed "s:TPM:$sample:" \\ + > ${sample}/${sample}.quant.genes.ids-only.txt """ } } +} + + + +if (params.transcriptome){ process merge_salmon_transcript_quant { label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' @@ -1139,18 +1152,15 @@ if (params.transcriptome){ def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' """ ## Merge transcript counts - echo transcript_id gene_name $transcript_quants | sed 's/.quant.ids-only.txt//g' | tr ' ' '\\t' > transcript_header.txt ## Gene transcript_id <--> gene_name mapping awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=transcript_id ")(\\w+)' > transcript_ids.txt awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=gene_name ")(\\w+)' > transcript_gene_names.txt paste transcript_ids.txt transcript_gene_names.txt > transcript_ids__to__gene_names.txt $merge $transcript_quants \\ - | csvtk cut -t -f "Name" \\ | tail -n +2 \\ | csvtk join -t -f 1 - transcript_ids__to__gene_names.txt \\ - | cat transcript_header.txt - \\ - awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' |\\ - cut -f '1,3-' |\\ + | awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ + | cut -f '1,3-' \\ > merged_transcript_counts.txt """ } @@ -1173,14 +1183,14 @@ if (params.transcriptome){ def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' """ ## Merge gene counts - ## Make header - echo gene_id gene_name $gene_quants | sed 's/.quant.genes.ids-only.txt//g' | tr ' ' '\t' > gene_header.txt + csvtk cut -t -f 1,2 $featurecounts_merged > gene_id__to__gene_name.txt ## Merge gene counts using gene_id to gene_name mapping from featurecounts, as $merge $gene_quants \\ - | csvtk cut -t -f "Name" \\ | tail -n +2 \\ - | csvtk join -t -f 1 - \$(csvtk cut -t -f 1,2 $featurecounts_merged) \\ - | cat gene_header.txt - > merged_gene_counts.txt + | csvtk join -t -f 1 gene_id__to__gene_name.txt - \\ + | awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ + | cut -f '1,3-' \\ + > merged_gene_counts.txt """ } From d08fb65e0c2e3b042a385d7ae5667cf882675617 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 10:49:08 -0700 Subject: [PATCH 030/139] fix more salmon merging commands --- main.nf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.nf b/main.nf index 3fadc2a99..5b6b61fd0 100644 --- a/main.nf +++ b/main.nf @@ -1119,7 +1119,7 @@ if (params.transcriptome){ --libType=$strandedness \\ --index $index \\ -1 ${reads[0]} \\ - -2 ${reads[2]} \\ + -2 ${reads[1]} \\ -o $sample csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ | sed "s:TPM:$sample:" \\ @@ -1159,7 +1159,7 @@ if (params.transcriptome){ $merge $transcript_quants \\ | tail -n +2 \\ | csvtk join -t -f 1 - transcript_ids__to__gene_names.txt \\ - | awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ + | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ > merged_transcript_counts.txt """ @@ -1188,7 +1188,7 @@ if (params.transcriptome){ $merge $gene_quants \\ | tail -n +2 \\ | csvtk join -t -f 1 gene_id__to__gene_name.txt - \\ - | awk '{FS="\t"; OFS="\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ + | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ > merged_gene_counts.txt From 639d95c33ed50f38c3ec3005508f44536b30b829 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 11:24:33 -0700 Subject: [PATCH 031/139] try to get salmon quant to work on each file individually --- main.nf | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/main.nf b/main.nf index 5b6b61fd0..ecf272fd4 100644 --- a/main.nf +++ b/main.nf @@ -1079,15 +1079,14 @@ process merge_featureCounts { if (params.transcriptome){ process salmon_quant { tag "$sample" - publishDir "${params.outdir}/salmon/", mode: 'copy' + publishDir "${params.outdir}/salmon", mode: 'copy' input: + set sample, file(reads) from trimmed_reads_salmon file index from index_ch.collect() file gtf from gtf_salmon_quant - set sample, file(reads) from trimmed_reads_salmon output: - file(sample) into quant_ch file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant @@ -1098,34 +1097,38 @@ if (params.transcriptome){ salmon quant --validateMappings \\ --seqBias --useVBOpt --gcBias \\ --geneMap ${gtf} \\ - --threads $task.cpus \\ - --libType=$strandedness \\ - --index $index \\ + --threads ${task.cpus} \\ + --libType=${strandedness} \\ + --index ${index} \\ -r ${reads[0]} \\ - -o $sample + -o ${sample} + # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ - | sed "s:TPM:$sample:" \\ + | sed "s:TPM:${sample}:" \\ > ${sample}/${sample}.quant.ids-only.txt + # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ - | sed "s:TPM:$sample:" \\ + | sed "s:TPM:${sample}:" \\ > ${sample}/${sample}.quant.genes.ids-only.txt """ - }else{ + } else { """ salmon quant --validateMappings \\ --seqBias --useVBOpt --gcBias \\ --geneMap ${gtf} \\ - --threads $task.cpus \\ - --libType=$strandedness \\ - --index $index \\ + --threads ${task.cpus} \\ + --libType=${strandedness} \\ + --index ${index} \\ -1 ${reads[0]} \\ -2 ${reads[1]} \\ - -o $sample + -o ${sample} + # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ - | sed "s:TPM:$sample:" \\ + | sed "s:TPM:${sample}:" \\ > ${sample}/${sample}.quant.ids-only.txt + # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ - | sed "s:TPM:$sample:" \\ + | sed "s:TPM:${sample}:" \\ > ${sample}/${sample}.quant.genes.ids-only.txt """ } @@ -1144,7 +1147,7 @@ if (params.transcriptome){ file gtf from gtf_merge_salmon_quant output: - file 'salmon_merged_transcript_counts.txt' + file 'salmon_merged_transcript_tpm.txt' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1159,10 +1162,10 @@ if (params.transcriptome){ $merge $transcript_quants \\ | tail -n +2 \\ | csvtk join -t -f 1 - transcript_ids__to__gene_names.txt \\ - | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ - | cut -f '1,3-' \\ - > merged_transcript_counts.txt + > salmon_merged_transcript_tpm.txt """ + // | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ + // | cut -f '1,3-' \\ } process merge_salmon_gene_quant { @@ -1175,7 +1178,7 @@ if (params.transcriptome){ file featurecounts_merged from featurecounts_merged output: - file 'salmon_merged_gene_counts.txt' + file 'salmon_merged_gene_tpm.txt' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1188,11 +1191,10 @@ if (params.transcriptome){ $merge $gene_quants \\ | tail -n +2 \\ | csvtk join -t -f 1 gene_id__to__gene_name.txt - \\ - | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ - | cut -f '1,3-' \\ - > merged_gene_counts.txt - + > salmon_merged_gene_tpm.txt """ + // | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ + // | cut -f '1,3-' \\ } } From c209e29942c6d48fe1421f058c629dc51e6e05bd Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 12:11:40 -0700 Subject: [PATCH 032/139] Add changes from @kerimoff --- main.nf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main.nf b/main.nf index ecf272fd4..68cecaa10 100644 --- a/main.nf +++ b/main.nf @@ -450,16 +450,16 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ * PREPROCESSING - Create Salmon transcriptome index */ if(params.transcriptome){ - process salmon_index { + process makeSalmonIndex { tag "$transcriptome.simpleName" - publishDir path: { "${params.outdir}/reference_transcriptome" }, - mode: 'copy' + publishDir path: { params.saveReference ? "${params.outdir}/reference_transcriptome" : params.outdir }, + saveAs: { params.saveReference ? it : null }, mode: 'copy' input: file transcriptome from tx_fasta_ch output: - file 'salmon_index' into index_ch + file 'salmon_index' into salmon_index_ch script: """ @@ -1083,8 +1083,8 @@ if (params.transcriptome){ input: set sample, file(reads) from trimmed_reads_salmon - file index from index_ch.collect() - file gtf from gtf_salmon_quant + file index from salmon_index_ch.collect() + file gtf from gtf_salmon_quant.collect() output: file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant From 493c1ac15e415e06062c063bfa4ad529aff49285 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Wed, 5 Jun 2019 16:32:06 -0400 Subject: [PATCH 033/139] force matplotlib to be 3.0.3 There is an error right now that makes multiqc fails in fastqc, qualimap and similar in travis. --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 1dcd8d323..3fa850b57 100644 --- a/environment.yml +++ b/environment.yml @@ -27,3 +27,4 @@ dependencies: - deeptools=3.2.1 - multiqc=1.7 - salmon=0.14.0 + - matplotlib=3.0.3 From e765bae5a711e6845073cc6c701d844f76733553 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Wed, 5 Jun 2019 16:33:57 -0400 Subject: [PATCH 034/139] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1be7da344..a755953a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Version 1.4dev #### Dependency Updates +* Force matplotlib=3.0.3 * Added salmon=0.14.0 ## [Version 1.3](https://github.com/nf-core/rnaseq/releases/tag/1.3) - 2019-03-26 @@ -46,6 +47,7 @@ * deeptools 3.2.0 -> 3.2.1 * trim-galore 0.5.0 -> 0.6.1 * qualimap 2.2.2b +* matplotlib 3.0.3 ## [Version 1.2](https://github.com/nf-core/rnaseq/releases/tag/1.2) - 2018-12-12 From f49819e109cafd0875971ba7ceee2ec086a1c940 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 13:40:04 -0700 Subject: [PATCH 035/139] Get salmon merging to work --- main.nf | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/main.nf b/main.nf index 68cecaa10..85669c511 100644 --- a/main.nf +++ b/main.nf @@ -1147,7 +1147,7 @@ if (params.transcriptome){ file gtf from gtf_merge_salmon_quant output: - file 'salmon_merged_transcript_tpm.txt' + file 'salmon_merged_transcript_tpm.csv' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1160,12 +1160,12 @@ if (params.transcriptome){ awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=gene_name ")(\\w+)' > transcript_gene_names.txt paste transcript_ids.txt transcript_gene_names.txt > transcript_ids__to__gene_names.txt $merge $transcript_quants \\ - | tail -n +2 \\ - | csvtk join -t -f 1 - transcript_ids__to__gene_names.txt \\ - > salmon_merged_transcript_tpm.txt + | csvtk join -t -f 1 transcript_ids__to__gene_names.txt - \\ + | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ + | cut -f '1,3-' \\ + | csvtk tab2csv | + > salmon_merged_transcript_tpm.csv """ - // | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ - // | cut -f '1,3-' \\ } process merge_salmon_gene_quant { @@ -1178,7 +1178,7 @@ if (params.transcriptome){ file featurecounts_merged from featurecounts_merged output: - file 'salmon_merged_gene_tpm.txt' + file 'salmon_merged_gene_tpm.csv' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1189,12 +1189,12 @@ if (params.transcriptome){ csvtk cut -t -f 1,2 $featurecounts_merged > gene_id__to__gene_name.txt ## Merge gene counts using gene_id to gene_name mapping from featurecounts, as $merge $gene_quants \\ - | tail -n +2 \\ | csvtk join -t -f 1 gene_id__to__gene_name.txt - \\ - > salmon_merged_gene_tpm.txt + | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ + | cut -f '1,3-' \\ + | csvtk tab2csv | + > salmon_merged_gene_tpm.csv """ - // | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ - // | cut -f '1,3-' \\ } } From 392135aafe67dcb41ea2f6b6ecbcfe2f0a4d39ad Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 13:41:30 -0700 Subject: [PATCH 036/139] salmon_index --> makeSalmonIndex --- conf/base.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/base.config b/conf/base.config index 4a5578ff1..b043000cc 100644 --- a/conf/base.config +++ b/conf/base.config @@ -34,7 +34,7 @@ process { memory = { check_max( 200.GB * task.attempt, 'memory' ) } time = { check_max( 5.h * task.attempt, 'time' ) } } - withName: salmon_index { + withName: makeSalmonIndex { cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 16.GB * task.attempt, 'memory' ) } } From 81fbef5348ff74c8b4b4eeeaba1ed15401573a7c Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 5 Jun 2019 13:41:49 -0700 Subject: [PATCH 037/139] Add escaped backslashes --- main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.nf b/main.nf index 85669c511..833f87925 100644 --- a/main.nf +++ b/main.nf @@ -1163,7 +1163,7 @@ if (params.transcriptome){ | csvtk join -t -f 1 transcript_ids__to__gene_names.txt - \\ | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ - | csvtk tab2csv | + | csvtk tab2csv \\ > salmon_merged_transcript_tpm.csv """ } @@ -1192,7 +1192,7 @@ if (params.transcriptome){ | csvtk join -t -f 1 gene_id__to__gene_name.txt - \\ | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ - | csvtk tab2csv | + | csvtk tab2csv \\ > salmon_merged_gene_tpm.csv """ } From 7c05cad1589285921a8c3ffe29699b46b08c8716 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Wed, 5 Jun 2019 16:59:45 -0400 Subject: [PATCH 038/139] add r-base 3.5.1 to speed up docker build --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 3fa850b57..5eb696e23 100644 --- a/environment.yml +++ b/environment.yml @@ -28,3 +28,4 @@ dependencies: - multiqc=1.7 - salmon=0.14.0 - matplotlib=3.0.3 + - r-base=3.5.1 From 01a0b2a31158c512724c4934193a8c4193966f84 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Wed, 5 Jun 2019 17:00:14 -0400 Subject: [PATCH 039/139] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a755953a2..4ef8d507a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ * trim-galore 0.5.0 -> 0.6.1 * qualimap 2.2.2b * matplotlib 3.0.3 +* r-base 3.5.1 ## [Version 1.2](https://github.com/nf-core/rnaseq/releases/tag/1.2) - 2018-12-12 From 8930e29e4d092b3ec25dd7be853ef0d630b29416 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 14:30:26 -0700 Subject: [PATCH 040/139] Add salmon to requirements --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 9a079fa68..1dcd8d323 100644 --- a/environment.yml +++ b/environment.yml @@ -26,4 +26,4 @@ dependencies: - gffread=0.9.12 - deeptools=3.2.1 - multiqc=1.7 - - htseq=0.11.2 + - salmon=0.14.0 From 130a99307e4bf581f77250d7fe9520f8b23e6ec0 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 3 Jun 2019 14:37:37 -0700 Subject: [PATCH 041/139] Update changelog with salmon=0.14.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed7ed0dec..a87b1f85d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Add Salmon index for transcriptome #### Dependency Updates -* Added htseq=0.11.2 +* Added salmon=0.14.0 ## [Version 1.3](https://github.com/nf-core/rnaseq/releases/tag/1.3) - 2019-03-26 #### Pipeline updates From 5545cc095699f2cbef33ab6786a398ffc336863e Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Wed, 5 Jun 2019 16:32:06 -0400 Subject: [PATCH 042/139] force matplotlib to be 3.0.3 There is an error right now that makes multiqc fails in fastqc, qualimap and similar in travis. --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 1dcd8d323..3fa850b57 100644 --- a/environment.yml +++ b/environment.yml @@ -27,3 +27,4 @@ dependencies: - deeptools=3.2.1 - multiqc=1.7 - salmon=0.14.0 + - matplotlib=3.0.3 From 0f0e75d29228069708389bcd6bbfb9c59f621d49 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Wed, 5 Jun 2019 16:33:57 -0400 Subject: [PATCH 043/139] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a87b1f85d..c6909243d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Add Salmon index for transcriptome #### Dependency Updates +* Force matplotlib=3.0.3 * Added salmon=0.14.0 ## [Version 1.3](https://github.com/nf-core/rnaseq/releases/tag/1.3) - 2019-03-26 @@ -50,6 +51,7 @@ * deeptools 3.2.0 -> 3.2.1 * trim-galore 0.5.0 -> 0.6.1 * qualimap 2.2.2b +* matplotlib 3.0.3 ## [Version 1.2](https://github.com/nf-core/rnaseq/releases/tag/1.2) - 2018-12-12 From bff7e2fe6ee749764e93ec40defb3d2149f60898 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Wed, 5 Jun 2019 16:59:45 -0400 Subject: [PATCH 044/139] add r-base 3.5.1 to speed up docker build --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 3fa850b57..5eb696e23 100644 --- a/environment.yml +++ b/environment.yml @@ -28,3 +28,4 @@ dependencies: - multiqc=1.7 - salmon=0.14.0 - matplotlib=3.0.3 + - r-base=3.5.1 From f4dfe032957e9d77e08eb8f6388be08347745bbe Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Wed, 5 Jun 2019 17:00:14 -0400 Subject: [PATCH 045/139] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6909243d..d7b625f42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ * trim-galore 0.5.0 -> 0.6.1 * qualimap 2.2.2b * matplotlib 3.0.3 +* r-base 3.5.1 ## [Version 1.2](https://github.com/nf-core/rnaseq/releases/tag/1.2) - 2018-12-12 From 8b2187706125deb0cbf91904ba6f5be3df2b0524 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 6 Jun 2019 09:54:02 -0700 Subject: [PATCH 046/139] Make label for salmon to simplify cpu/memory config --- conf/base.config | 6 +----- main.nf | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/conf/base.config b/conf/base.config index b043000cc..3e97709e8 100644 --- a/conf/base.config +++ b/conf/base.config @@ -34,11 +34,7 @@ process { memory = { check_max( 200.GB * task.attempt, 'memory' ) } time = { check_max( 5.h * task.attempt, 'time' ) } } - withName: makeSalmonIndex { - cpus = { check_max( 8, 'cpus' ) } - memory = { check_max( 16.GB * task.attempt, 'memory' ) } - } - withName: salmon_quant { + withLabel: salmon { cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 16.GB * task.attempt, 'memory' ) } } diff --git a/main.nf b/main.nf index 833f87925..0f5f1e6fa 100644 --- a/main.nf +++ b/main.nf @@ -451,6 +451,7 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ */ if(params.transcriptome){ process makeSalmonIndex { + label 'salmon' tag "$transcriptome.simpleName" publishDir path: { params.saveReference ? "${params.outdir}/reference_transcriptome" : params.outdir }, saveAs: { params.saveReference ? it : null }, mode: 'copy' @@ -1078,6 +1079,7 @@ process merge_featureCounts { */ if (params.transcriptome){ process salmon_quant { + label 'salmon' tag "$sample" publishDir "${params.outdir}/salmon", mode: 'copy' From 2c280930fd3ec09bd0974e049de3b859ab8db00d Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 6 Jun 2019 12:09:52 -0700 Subject: [PATCH 047/139] Add salmon to multiqc --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 0f5f1e6fa..8609823a9 100644 --- a/main.nf +++ b/main.nf @@ -1314,7 +1314,7 @@ process multiqc { rfilename = custom_runName ? "--filename " + custom_runName.replaceAll('\\W','_').replaceAll('_+','_') + "_multiqc_report" : '' """ multiqc . -f $rtitle $rfilename --config $multiqc_config \\ - -m custom_content -m picard -m preseq -m rseqc -m featureCounts -m hisat2 -m star -m cutadapt -m fastqc -m qualimap + -m custom_content -m picard -m preseq -m rseqc -m featureCounts -m hisat2 -m star -m cutadapt -m fastqc -m qualimap -m salmon """ } From c11df52668327f50a76c2c1ffe9bf367ae9b3de1 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Fri, 7 Jun 2019 19:13:09 +0200 Subject: [PATCH 048/139] Adding in Lorena and Olga --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c273d7c2f..a1b089539 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,6 @@ Many thanks to other who have helped out along the way too, including (but not l [@pditommaso](https://github.com/pditommaso), [@orzechoj](https://github.com/orzechoj), [@apeltzer](https://github.com/apeltzer), -[@colindaven](https://github.com/colindaven). +[@colindaven](https://github.com/colindaven), +[@lpantano](https://github.com/lpantano), +[@olgabot](https://github.com/olgabot). From 9dc8beabb17c6dded70d9ec2a40f472e8a61101e Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Fri, 7 Jun 2019 19:18:22 +0200 Subject: [PATCH 049/139] Rephrased Changelog --- CHANGELOG.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7b625f42..ca4582a39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,18 @@ ## Version 1.4dev -#### Pipeline updates +### Pipeline updates + +* Added Salmon as an alternative method to STAR and HiSAT2 -* Add Salmon index for transcriptome +### Dependency Updates -#### Dependency Updates * Force matplotlib=3.0.3 * Added salmon=0.14.0 ## [Version 1.3](https://github.com/nf-core/rnaseq/releases/tag/1.3) - 2019-03-26 -#### Pipeline updates + +### Pipeline updates * Appointed changes because of missing output of the multiqc_plots folder [#200](https://github.com/nf-core/rnaseq/issues/200) * Add Qualimap dependency [#202](https://github.com/nf-core/rnaseq/issues/202) @@ -19,9 +21,6 @@ * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183): added the folder "multiqc_plots" to the output. * Get MultiQC to write out the software versions in a .csv file [#185](https://github.com/nf-core/rnaseq/issues/185) - -#### Pipeline Updates - * Added configurable options to specify group attributes for featureCounts [#144](https://github.com/nf-core/rnaseq/issues/144) * Added support for RSeqC 3.0 [#148](https://github.com/nf-core/rnaseq/issues/148) * Added a `parameters.settings.json` file for use with the new `nf-core launch` helper tool. @@ -29,14 +28,13 @@ * Fixed all centralized configs [for offline usage](https://github.com/nf-core/rnaseq/issues/163) * Hide %dup in [multiqc report](https://github.com/nf-core/rnaseq/issues/150) -#### Bug fixes +### Bug fixes * Fixing HISAT2 Index Building for large reference genomes [#153](https://github.com/nf-core/rnaseq/issues/153) * Fixing HISAT2 BAM sorting using more memory than available on the system * Fixing MarkDuplicates memory consumption issues following [#179](https://github.com/nf-core/rnaseq/pull/179) - -#### Dependency Updates +### Dependency Updates * RSeQC 2.6.4 -> 3.0.0 * Picard 2.18.15 -> 2.20.0 From cc4a0ff7b5206c0cbfb59e7096fe39ef4cc23210 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Fri, 7 Jun 2019 19:18:28 +0200 Subject: [PATCH 050/139] Adjust the summary --- main.nf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.nf b/main.nf index 8609823a9..46c88fb6c 100644 --- a/main.nf +++ b/main.nf @@ -261,6 +261,9 @@ if(params.aligner == 'star'){ if(params.hisat2_index) summary['HISAT2 Index'] = params.hisat2_index else if(params.fasta) summary['Fasta Ref'] = params.fasta if(params.splicesites) summary['Splice Sites'] = params.splicesites +} else if(params.aligner == 'salmon') { + summary['Aligner'] = 'Salmon' + if(params.transcriptome) summary['Transcriptome'] = params.transcriptome } if(params.transcriptome) summary['Transcriptome'] = params.transcriptome if(params.gtf) summary['GTF Annotation'] = params.gtf From 546bc7f113b865057ff68f3b322bd7171547069c Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Fri, 7 Jun 2019 19:29:04 +0200 Subject: [PATCH 051/139] Adding Salmon to MultiQC logs --- main.nf | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/main.nf b/main.nf index 46c88fb6c..1ff599ad6 100644 --- a/main.nf +++ b/main.nf @@ -134,8 +134,8 @@ if (params.pico){ } // Validate inputs -if (params.aligner != 'star' && params.aligner != 'hisat2'){ - exit 1, "Invalid aligner option: ${params.aligner}. Valid options: 'star', 'hisat2'" +if (params.aligner != 'star' && params.aligner != 'hisat2' && params.aligner != 'salmon'){ + exit 1, "Invalid aligner option: ${params.aligner}. Valid options: 'star', 'hisat2', 'salmon'" } if( params.star_index && params.aligner == 'star' ){ star_index = Channel @@ -261,9 +261,9 @@ if(params.aligner == 'star'){ if(params.hisat2_index) summary['HISAT2 Index'] = params.hisat2_index else if(params.fasta) summary['Fasta Ref'] = params.fasta if(params.splicesites) summary['Splice Sites'] = params.splicesites -} else if(params.aligner == 'salmon') { - summary['Aligner'] = 'Salmon' - if(params.transcriptome) summary['Transcriptome'] = params.transcriptome +} else if(params.transcriptome) { + summary['Quantification Method'] = 'Salmon' + summary['Transcriptome'] = params.transcriptome } if(params.transcriptome) summary['Transcriptome'] = params.transcriptome if(params.gtf) summary['GTF Annotation'] = params.gtf @@ -1094,6 +1094,7 @@ if (params.transcriptome){ output: file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant + set file("${sample}/*.json"), file("${sample}/aux_info/*.json") into salmon_multiqc_logs script: def strandedness = params.unstranded ? 'U' : 'SR' @@ -1303,6 +1304,7 @@ process multiqc { file ('featureCounts/*') from featureCounts_logs.collect() file ('featureCounts_biotype/*') from featureCounts_biotype.collect() file ('stringtie/stringtie_log*') from stringtie_log.collect() + file ('salmon/*') from salmon_multiqc_logs.collect().ifEmpty([]) file ('sample_correlation_results/*') from sample_correlation_results.collect().ifEmpty([]) // If the Edge-R is not run create an Empty array file ('software_versions/*') from software_versions_yaml.collect() file workflow_summary from create_workflow_summary(summary) From 87185bac003497b4e4fa6a0bedc0ce170d24db42 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Fri, 7 Jun 2019 19:34:06 +0200 Subject: [PATCH 052/139] Should have the salmon module now --- main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.nf b/main.nf index 1ff599ad6..99d2a2adc 100644 --- a/main.nf +++ b/main.nf @@ -1094,7 +1094,7 @@ if (params.transcriptome){ output: file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant - set file("${sample}/*.json"), file("${sample}/aux_info/*.json") into salmon_multiqc_logs + file("${sample}/aux_info/*.json") into salmon_multiqc_logs script: def strandedness = params.unstranded ? 'U' : 'SR' @@ -1304,7 +1304,7 @@ process multiqc { file ('featureCounts/*') from featureCounts_logs.collect() file ('featureCounts_biotype/*') from featureCounts_biotype.collect() file ('stringtie/stringtie_log*') from stringtie_log.collect() - file ('salmon/*') from salmon_multiqc_logs.collect().ifEmpty([]) + file ('salmon/**') from salmon_multiqc_logs.collect().ifEmpty([]) file ('sample_correlation_results/*') from sample_correlation_results.collect().ifEmpty([]) // If the Edge-R is not run create an Empty array file ('software_versions/*') from software_versions_yaml.collect() file workflow_summary from create_workflow_summary(summary) From 0414c5ed97a5bac0da761664e9f00d9f57d24207 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sat, 8 Jun 2019 11:43:24 +0200 Subject: [PATCH 053/139] Fix summary transcriptome statement --- main.nf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/main.nf b/main.nf index 99d2a2adc..928880991 100644 --- a/main.nf +++ b/main.nf @@ -261,10 +261,6 @@ if(params.aligner == 'star'){ if(params.hisat2_index) summary['HISAT2 Index'] = params.hisat2_index else if(params.fasta) summary['Fasta Ref'] = params.fasta if(params.splicesites) summary['Splice Sites'] = params.splicesites -} else if(params.transcriptome) { - summary['Quantification Method'] = 'Salmon' - summary['Transcriptome'] = params.transcriptome -} if(params.transcriptome) summary['Transcriptome'] = params.transcriptome if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff From 819e21f2b06b5954eb7cfea89bf3230e3b26a752 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sat, 8 Jun 2019 11:47:24 +0200 Subject: [PATCH 054/139] Use condition instead of duplicating code :) --- main.nf | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/main.nf b/main.nf index 928880991..0848b4405 100644 --- a/main.nf +++ b/main.nf @@ -1094,7 +1094,7 @@ if (params.transcriptome){ script: def strandedness = params.unstranded ? 'U' : 'SR' - if (params.singleEnd){ + def endedness = params.singleEnd ? "-r ${reads[0]}" : "-1 ${reads[0]} -2 ${reads[1]}" """ salmon quant --validateMappings \\ --seqBias --useVBOpt --gcBias \\ @@ -1102,27 +1102,7 @@ if (params.transcriptome){ --threads ${task.cpus} \\ --libType=${strandedness} \\ --index ${index} \\ - -r ${reads[0]} \\ - -o ${sample} - # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ - | sed "s:TPM:${sample}:" \\ - > ${sample}/${sample}.quant.ids-only.txt - # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ - | sed "s:TPM:${sample}:" \\ - > ${sample}/${sample}.quant.genes.ids-only.txt - """ - } else { - """ - salmon quant --validateMappings \\ - --seqBias --useVBOpt --gcBias \\ - --geneMap ${gtf} \\ - --threads ${task.cpus} \\ - --libType=${strandedness} \\ - --index ${index} \\ - -1 ${reads[0]} \\ - -2 ${reads[1]} \\ + $endedness \\ -o ${sample} # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ @@ -1134,12 +1114,7 @@ if (params.transcriptome){ > ${sample}/${sample}.quant.genes.ids-only.txt """ } - } -} - - - -if (params.transcriptome){ + process merge_salmon_transcript_quant { label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' From bcaa19da61b53a76634bec6b21e3875425c87562 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sat, 8 Jun 2019 12:00:04 +0200 Subject: [PATCH 055/139] Fix parentheses --- main.nf | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/main.nf b/main.nf index 0848b4405..25beca65d 100644 --- a/main.nf +++ b/main.nf @@ -261,6 +261,7 @@ if(params.aligner == 'star'){ if(params.hisat2_index) summary['HISAT2 Index'] = params.hisat2_index else if(params.fasta) summary['Fasta Ref'] = params.fasta if(params.splicesites) summary['Splice Sites'] = params.splicesites +} if(params.transcriptome) summary['Transcriptome'] = params.transcriptome if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff @@ -1095,24 +1096,24 @@ if (params.transcriptome){ script: def strandedness = params.unstranded ? 'U' : 'SR' def endedness = params.singleEnd ? "-r ${reads[0]}" : "-1 ${reads[0]} -2 ${reads[1]}" - """ - salmon quant --validateMappings \\ - --seqBias --useVBOpt --gcBias \\ - --geneMap ${gtf} \\ - --threads ${task.cpus} \\ - --libType=${strandedness} \\ - --index ${index} \\ - $endedness \\ - -o ${sample} - # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ - | sed "s:TPM:${sample}:" \\ - > ${sample}/${sample}.quant.ids-only.txt - # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ - | sed "s:TPM:${sample}:" \\ - > ${sample}/${sample}.quant.genes.ids-only.txt - """ + """ + salmon quant --validateMappings \\ + --seqBias --useVBOpt --gcBias \\ + --geneMap ${gtf} \\ + --threads ${task.cpus} \\ + --libType=${strandedness} \\ + --index ${index} \\ + $endedness \\ + -o ${sample} + # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ + | sed "s:TPM:${sample}:" \\ + > ${sample}/${sample}.quant.ids-only.txt + # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging + csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ + | sed "s:TPM:${sample}:" \\ + > ${sample}/${sample}.quant.genes.ids-only.txt + """ } process merge_salmon_transcript_quant { @@ -1144,7 +1145,6 @@ if (params.transcriptome){ > salmon_merged_transcript_tpm.csv """ } - process merge_salmon_gene_quant { label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' From be0fba8058287f8f370bc860b029b24e1f74c07e Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sat, 8 Jun 2019 12:16:25 +0200 Subject: [PATCH 056/139] MultiQC logging working now --- main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.nf b/main.nf index 25beca65d..e61eae5a3 100644 --- a/main.nf +++ b/main.nf @@ -1091,7 +1091,7 @@ if (params.transcriptome){ output: file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant - file("${sample}/aux_info/*.json") into salmon_multiqc_logs + file("${sample}") into salmon_multiqc_logs //MultiQC needs the sample folder to have proper names for samples script: def strandedness = params.unstranded ? 'U' : 'SR' @@ -1275,7 +1275,7 @@ process multiqc { file ('featureCounts/*') from featureCounts_logs.collect() file ('featureCounts_biotype/*') from featureCounts_biotype.collect() file ('stringtie/stringtie_log*') from stringtie_log.collect() - file ('salmon/**') from salmon_multiqc_logs.collect().ifEmpty([]) + file ('salmon/*') from salmon_multiqc_logs.collect().ifEmpty([]) file ('sample_correlation_results/*') from sample_correlation_results.collect().ifEmpty([]) // If the Edge-R is not run create an Empty array file ('software_versions/*') from software_versions_yaml.collect() file workflow_summary from create_workflow_summary(summary) From dc9876025593ff0dd6ea4cb03a237e94f4c9bb96 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:10:26 +0200 Subject: [PATCH 057/139] Update docs/output.md Co-Authored-By: Harshil Patel --- docs/output.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/output.md b/docs/output.md index 3bb9d2aea..35c50d9b0 100644 --- a/docs/output.md +++ b/docs/output.md @@ -310,7 +310,7 @@ We also use featureCounts to count overlaps with different classes of features. * Summary file, containing statistics about the counts ## Salmon -[Salmon](https://salmon.readthedocs.io/en/latest/salmon.html) from [Ocean Genomics](https://oceangenomics.com/) quasi-maps reads to a transcriptome and counts gene expression per transcript. +[Salmon](https://salmon.readthedocs.io/en/latest/salmon.html) from [Ocean Genomics](https://oceangenomics.com/) quasi-maps and quantifies expression relative to the transcriptome. ### Salmon Index From df3aaa2471f326e39d60c3f8bbafc4a7969dc793 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:10:55 +0200 Subject: [PATCH 058/139] Update main.nf Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index e61eae5a3..40f9709a2 100644 --- a/main.nf +++ b/main.nf @@ -173,7 +173,7 @@ if( params.gtf ){ if (params.transcriptome){ Channel .fromPath(params.transcriptome) - .ifEmpty { exit 1, "Transcript fasta file is unreachable: ${params.transcriptome}" } + .ifEmpty { exit 1, "Transcript fasta file not found: ${params.transcriptome}" } .set { tx_fasta_ch } } From 0aef01ee884e35f4eaf641f9207166befc7f8554 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:11:08 +0200 Subject: [PATCH 059/139] Update main.nf Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 40f9709a2..46aba72cb 100644 --- a/main.nf +++ b/main.nf @@ -231,7 +231,7 @@ if(params.readPaths){ .from(params.readPaths) .map { row -> [ row[0], [file(row[1][0]), file(row[1][1])]] } .ifEmpty { exit 1, "params.readPaths was empty - no input files supplied" } - .into { raw_reads_fastqc; raw_reads_trimgalore; raw_salmon } + .into { raw_reads_fastqc; raw_reads_trimgalore; raw_reads_salmon } } } else { Channel From bc9ce625e189d4c2dc65a32fee905ad0f42da53a Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:11:17 +0200 Subject: [PATCH 060/139] Update main.nf Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 46aba72cb..88a9d4e12 100644 --- a/main.nf +++ b/main.nf @@ -237,7 +237,7 @@ if(params.readPaths){ Channel .fromFilePairs( params.reads, size: params.singleEnd ? 1 : 2 ) .ifEmpty { exit 1, "Cannot find any reads matching: ${params.reads}\nNB: Path needs to be enclosed in quotes!\nNB: Path requires at least one * wildcard!\nIf this is single-end data, please specify --singleEnd on the command line." } - .into { raw_reads_fastqc; raw_reads_trimgalore; raw_salmon } + .into { raw_reads_fastqc; raw_reads_trimgalore; raw_reads_salmon } } From d84f72e257336efb9008742bab0ab201c7ab2a4c Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:11:39 +0200 Subject: [PATCH 061/139] Update main.nf Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 88a9d4e12..418cfdd8b 100644 --- a/main.nf +++ b/main.nf @@ -36,7 +36,7 @@ def helpMessage() { --star_index Path to STAR index --hisat2_index Path to HiSAT2 index --fasta Path to Fasta reference - --transcriptome Path to Fasta transcriptome + --transcriptome Path to Transcriptome fasta file --gtf Path to GTF file --gff Path to GFF3 file --bed12 Path to bed12 file From 607fa764836b0102f55458598252ed0aac51107f Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:11:45 +0200 Subject: [PATCH 062/139] Update main.nf Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 418cfdd8b..94f7bd20b 100644 --- a/main.nf +++ b/main.nf @@ -35,7 +35,7 @@ def helpMessage() { References If not specified in the configuration file or you wish to overwrite any of the references. --star_index Path to STAR index --hisat2_index Path to HiSAT2 index - --fasta Path to Fasta reference + --fasta Path to Genome fasta file --transcriptome Path to Transcriptome fasta file --gtf Path to GTF file --gff Path to GFF3 file From 54dbe03cf4a81fe05676f5d58dd697d97aab34a2 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:11:55 +0200 Subject: [PATCH 063/139] Update main.nf Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 94f7bd20b..75e333826 100644 --- a/main.nf +++ b/main.nf @@ -1091,7 +1091,7 @@ if (params.transcriptome){ output: file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant - file("${sample}") into salmon_multiqc_logs //MultiQC needs the sample folder to have proper names for samples + file "${sample}" into salmon_multiqc_logs //MultiQC needs the sample folder to have proper names for samples script: def strandedness = params.unstranded ? 'U' : 'SR' From 6a2356d07a35aa774326d231615a96f1c3b86a5a Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:17:19 +0200 Subject: [PATCH 064/139] Update main.nf Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 75e333826..ebe137618 100644 --- a/main.nf +++ b/main.nf @@ -225,7 +225,7 @@ if(params.readPaths){ .from(params.readPaths) .map { row -> [ row[0], [file(row[1][0])]] } .ifEmpty { exit 1, "params.readPaths was empty - no input files supplied" } - .into { raw_reads_fastqc; raw_reads_trimgalore; raw_salmon } + .into { raw_reads_fastqc; raw_reads_trimgalore; raw_reads_salmon } } else { Channel .from(params.readPaths) From cdca78ff8a5d0ee977ebd7f368829cc545947f5e Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:20:55 +0200 Subject: [PATCH 065/139] Mini change to add aligner to help --- main.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/main.nf b/main.nf index ebe137618..48a33cc9a 100644 --- a/main.nf +++ b/main.nf @@ -27,6 +27,7 @@ def helpMessage() { Options: --genome Name of iGenomes reference --singleEnd Specifies that the input is single end reads + --aligner Specifies the aligner to use (available are: 'hisat2', 'star') Strandedness: --forward_stranded The library is forward stranded --reverse_stranded The library is reverse stranded From 6642c158f033533ee749c6c9177d8a651be394e1 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 9 Jun 2019 10:33:12 +0200 Subject: [PATCH 066/139] Adding transcriptome option to parameters.settings.json --- parameters.settings.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/parameters.settings.json b/parameters.settings.json index 774ee269f..d78782a8f 100644 --- a/parameters.settings.json +++ b/parameters.settings.json @@ -183,6 +183,16 @@ "pattern": ".*", "default_value": "" }, + { + "name": "transcriptome", + "label": "Transcriptome Fasta", + "usage": "Path to Transcriptome Fasta file", + "group": "Alignment", + "render": "file", + "type": "string", + "pattern": ".*", + "default_value": "" + }, { "name": "gff", "label": "GFF", From 162ec29d942b143439560c8c837333563bdab8f9 Mon Sep 17 00:00:00 2001 From: drpatelh Date: Mon, 10 Jun 2019 12:42:21 +0100 Subject: [PATCH 067/139] First pass update of relevant files --- CHANGELOG.md | 2 + README.md | 5 +- docs/output.md | 59 ++++--- docs/usage.md | 65 ++++---- environment.yml | 33 ++-- main.nf | 340 +++++++++++++++++++-------------------- nextflow.config | 37 ++--- parameters.settings.json | 69 +++++--- 8 files changed, 319 insertions(+), 291 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b62533337..7eaa97b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183): added the folder "multiqc_plots" to the output. * Get MultiQC to write out the software versions in a .csv file [#185](https://github.com/nf-core/rnaseq/issues/185) +* Change all boolean parameters from snake_case to camelCase and vice versa for value parameters +* Add `--skipSalmon` parameter to skip Salmon transcriptome quantification ### Dependency Updates diff --git a/README.md b/README.md index 94fcd24b1..2f0942573 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ **nf-core/rnaseq** is a bioinformatics analysis pipeline used for RNA sequencing data. -The workflow processes raw data from FastQ inputs ([FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/), [Trim Galore!](https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/)), aligns the reads ([STAR](https://github.com/alexdobin/STAR) or [HiSAT2](https://ccb.jhu.edu/software/hisat2/index.shtml)), generates gene counts ([featureCounts](http://bioinf.wehi.edu.au/featureCounts/), [StringTie](https://ccb.jhu.edu/software/stringtie/)) and performs extensive quality-control on the results ([RSeQC](http://rseqc.sourceforge.net/), [Qualimap](http://qualimap.bioinfo.cipf.es/), [dupRadar](https://bioconductor.org/packages/release/bioc/html/dupRadar.html), [Preseq](http://smithlabresearch.org/software/preseq/), [edgeR](https://bioconductor.org/packages/release/bioc/html/edgeR.html), [MultiQC](http://multiqc.info/)). See the [output documentation](docs/output.md) for more details of the results. +The workflow processes raw data from FastQ inputs ([FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/), [Trim Galore!](https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/)), aligns the reads ([STAR](https://github.com/alexdobin/STAR) or [HiSAT2](https://ccb.jhu.edu/software/hisat2/index.shtml)), generates counts relative to genes ([featureCounts](http://bioinf.wehi.edu.au/featureCounts/), [StringTie](https://ccb.jhu.edu/software/stringtie/)) or transcripts ([Salmon](https://combine-lab.github.io/salmon/)) and performs extensive quality-control on the results ([RSeQC](http://rseqc.sourceforge.net/), [Qualimap](http://qualimap.bioinfo.cipf.es/), [dupRadar](https://bioconductor.org/packages/release/bioc/html/dupRadar.html), [Preseq](http://smithlabresearch.org/software/preseq/), [edgeR](https://bioconductor.org/packages/release/bioc/html/edgeR.html), [MultiQC](http://multiqc.info/)). See the [output documentation](docs/output.md) for more details of the results. The pipeline is built using [Nextflow](https://www.nextflow.io), a bioinformatics workflow tool to run tasks across multiple compute infrastructures in a very portable manner. It comes with docker / singularity containers making installation trivial and results highly reproducible. @@ -39,4 +39,5 @@ Many thanks to other who have helped out along the way too, including (but not l [@colindaven](https://github.com/colindaven), [@lpantano](https://github.com/lpantano), [@olgabot](https://github.com/olgabot), -[@jburos](https://github.com/jburos). \ No newline at end of file +[@jburos](https://github.com/jburos), +[@drpatelh](https://github.com/drpatelh). diff --git a/docs/output.md b/docs/output.md index 35c50d9b0..55856ec11 100644 --- a/docs/output.md +++ b/docs/output.md @@ -312,28 +312,49 @@ We also use featureCounts to count overlaps with different classes of features. ## Salmon [Salmon](https://salmon.readthedocs.io/en/latest/salmon.html) from [Ocean Genomics](https://oceangenomics.com/) quasi-maps and quantifies expression relative to the transcriptome. -### Salmon Index - -**Output directory: `results/reference_transcriptome`** - -* `Sample.bam_biotype_counts.txt` - * Read counts for the different gene biotypes that featureCounts distinguishes. -* `Sample.featureCounts.txt` - * Read the counts for each gene provided in the reference `gtf` file -* `Sample.featureCounts.txt.summary` - * Summary file, containing statistics about the counts - - -### Salmon quant +### Index files + +**Output directory: `results/reference_genome/salmon_index`** + +* `duplicate_clusters.tsv` + * Stores which transcripts are duplicates of one another +* `hash.bin` +* `header.json` + * Information about k-mer size, uniquely identifying hashes for the reference +* `indexing.log` + * Time log for creating transcriptome index +* `quasi_index.log` + * Step-by-step log for making transcriptome index +* `refInfo.json` + * Information about file used for the reference +* `rsd.bin` +* `sa.bin` +* `txpInfo.bin` +* `versionInfo.json` + * Salmon and indexing version sed to make the index + +### Quantification output **Output directory: `results/salmon`** -* `Sample.bam_biotype_counts.txt` - * Read counts for the different gene biotypes that featureCounts distinguishes. -* `Sample.featureCounts.txt` - * Read the counts for each gene provided in the reference `gtf` file -* `Sample.featureCounts.txt.summary` - * Summary file, containing statistics about the counts +* `aux_info/` + * Auxiliary info e.g. versions and number of mapped reads +* `cmd_info.json` + * Information about the Salmon quantification command, version, and options +* `lib_format_counts.json` + * Number of fragments assigned, unassigned and incompatible +* `libParams/` + * Contains the file `flenDist.txt` for the fragment length distribution +* `logs/` + * Contains the file `salmon_quant.log` giving a record of Salmon's quantification +* `quant.genes.sf` + * *Gene*-level quantification of the sample, including gene length, effective length, TPM, and number of reads +* `quant.sf` + * *Transcript*-level quantification of the sample, including gene length, effective length, TPM, and number of reads +* `Sample.quant.genes.ids-only.txt` + * Subset of `quant.genes.sf`, only containing the gene id and TPM +* `Sample.quant.ids-only.txt` + * Subset of `quant.sf`, only containing the transcript id and TPM ## StringTie [StringTie](https://ccb.jhu.edu/software/stringtie/) assembles RNA-Seq alignments into potential transcripts. It assembles and quantitates full-length transcripts representing multiple splice variants for each gene locus. diff --git a/docs/usage.md b/docs/usage.md index 76e97d758..0d252f34d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -18,7 +18,6 @@ * [Default Attribute Type](#default-attribute-type) * [Extra Gene Names](#extra-gene-names) * [Transcriptome mapping with Salmon](#transcriptome-mapping-with-salmon) - * [Indexing the transcriptome](#indexing-the-transcriptome) * [Alignment tool](#alignment-tool) * [Reference genomes](#reference-genomes) * [`--genome` (using iGenomes)](#--genome-using-igenomes) @@ -52,8 +51,8 @@ * [`--max_memory`](#--max_memory) * [`--max_time`](#--max_time) * [`--max_cpus`](#--max_cpus) - * [`--hisatBuildMemory`](#--hisatbuildmemory) - * [`--subsampFilesizeThreshold`](#--subsampfilesizethreshold) + * [`--hisat_build_memory`](#--hisat_build_memory) + * [`--subsamp_filesize_thresh`](#--subsamp_filesize_thresh) * [`--sampleLevel`](#--samplelevel) * [`--plaintext_email`](#--plaintext_email) * [`--monochrome_logs`](#--monochrome_logs) @@ -154,31 +153,31 @@ It is not possible to run a mixture of single-end and paired-end files in one ru ### Library strandedness Three command line flags / config parameters set the library strandedness for a run: -* `--forward_stranded` -* `--reverse_stranded` -* `--unstranded` +* `--forwardStranded` +* `--reverseStranded` +* `--unStranded` -If not set, the pipeline will be run as unstranded. Specifying `--pico` makes the pipeline run in `forward_stranded` mode. +If not set, the pipeline will be run as unstranded. Specifying `--pico` makes the pipeline run in `forwardStranded` mode. You can set a default in a cutom Nextflow configuration file such as one saved in `~/.nextflow/config` (see the [nextflow docs](https://www.nextflow.io/docs/latest/config.html) for more). For example: ```nextflow params { - reverse_stranded = true + reverseStranded = true } ``` -If you have a default strandedness set in your personal config file you can use `--unstranded` to overwrite it for a given run. +If you have a default strandedness set in your personal config file you can use `--unStranded` to overwrite it for a given run. These flags affect the commands used for several steps in the pipeline - namely HISAT2, featureCounts, RSeQC (`RPKM_saturation.py`), Qualimap and StringTie: -* `--forward_stranded` +* `--forwardStranded` * HISAT2: `--rna-strandness F` / `--rna-strandness FR` * featureCounts: `-s 1` * RSeQC: `-d ++,--` / `-d 1++,1--,2+-,2-+` * Qualimap: `-pe strand-specific-forward` * StringTie: `--fr` -* `--reverse_stranded` +* `--reverseStranded` * HISAT2: `--rna-strandness R` / `--rna-strandness RF` * featureCounts: `-s 2` * RSeQC: `-d +-,-+` / `-d 1+-,1-+,2++,2--` @@ -189,31 +188,27 @@ These flags affect the commands used for several steps in the pipeline - namely ### Default Attribute Type -By default, the pipeline uses `gene_name` as the default gene identifier group. In case you need to adjust this, specify using the option `--fcGroupFeatures` to use a different category present in your provided GTF file. Please also take care to use a suitable attribute to categorize the `biotype` of the selected features in your GTF then, using the option `--fcGroupFeaturesType` (default: `gene_biotype`). +By default, the pipeline uses `gene_name` as the default gene identifier group. In case you need to adjust this, specify using the option `--fc_group_features` to use a different category present in your provided GTF file. Please also take care to use a suitable attribute to categorize the `biotype` of the selected features in your GTF then, using the option `--fc_group_features_type` (default: `gene_biotype`). ### Extra Gene Names By default, the pipeline uses `gene_names` as additional gene identifiers apart from ENSEMBL identifiers in the pipeline. -This behaviour can be modified by specifying `--fcExtraAttributes` when running the pipeline, which is passed on to featureCounts as an `--extraAttributes` parameter. +This behaviour can be modified by specifying `--fc_extra_attributes` when running the pipeline, which is passed on to featureCounts as an `--extraAttributes` parameter. See the user guide of the [Subread package here](http://bioinf.wehi.edu.au/subread-package/SubreadUsersGuide.pdf). Note that you can also specify more than one desired value, separated by a comma: -``--fcExtraAttributes gene_id,...`` - +``--fc_extra_attributes gene_id,...`` ## Transcriptome mapping with Salmon -If the option `--transcriptome` is provided to a fasta file of cDNA sequences, the pipeline will also run transcriptome quantification using [Salmon](https://salmon.readthedocs.io/en/latest/salmon.html). - -### Indexing the transcriptome - -The transcriptome is indexed using the default parameters of Salmon, using the default k-mer size of 31. As [discussed](https://salmon.readthedocs.io/en/latest/salmon.html#preparing-transcriptome-indices-mapping-based-mode), the a k-mer size off 31 works well with reads that are length 75bp or longer. +If a fasta file of cDNA sequences is provided via the `--transcriptome` parameter then the pipeline will run an additional transcriptome quantification using [Salmon](https://salmon.readthedocs.io/en/latest/salmon.html). You can use the `--skipSalmon` parameter if you wish to skip this step. +The default Salmon parameters and a k-mer size of 31 are used to index the transcriptome. As [discussed here](https://salmon.readthedocs.io/en/latest/salmon.html#preparing-transcriptome-indices-mapping-based-mode)), a k-mer size off 31 works well with reads that are 75bp or longer. ## Alignment tool By default, the pipeline uses [STAR](https://github.com/alexdobin/STAR) to align the raw FastQ reads to the reference genome. STAR is fast and common, but requires a lot of memory to run, typically around 38GB for the Human GRCh37 reference genome. If you prefer, you can use [HISAT2](https://ccb.jhu.edu/software/hisat2/index.shtml) as the alignment tool instead. Developed by the same group behind the popular Tophat aligner, HISAT2 has a much smaller memory footprint. -To use HISAT2, use the parameter `--aligner hisat2` or set `params.aligner = 'hisat2'` in your config file. +To use HISAT2, use the parameter `--aligner hisat2` or set `params.aligner = 'hisat2'` in your config file. Alternatively, you can also use `--aligner salmon` if you want to just perform a fast mapping to the transcriptome with Salmon (you will also have to supply the `--transcriptome` parameter). ## Reference genomes @@ -309,22 +304,22 @@ If you have a kit that you'd like a preset added for, please let us know! ### `--pico` Sets trimming and standedness settings for the _SMARTer Stranded Total RNA-Seq Kit - Pico Input_ kit. -Equivalent to: `--forward_stranded` `--clip_r1 3` `--three_prime_clip_r2 3` +Equivalent to: `--forwardStranded` `--clip_r1 3` `--three_prime_clip_r2 3` ## Skipping QC steps The pipeline contains a large number of quality control steps. Sometimes, it may not be desirable to run all of them if time and compute resources are limited. The following options make this easy: -* `--skip_qc` - Skip **all QC steps**, apart from MultiQC -* `--skip_fastqc` - Skip FastQC -* `--skip_rseqc` - Skip RSeQC -* `--skip_qualimap` - Skip Qualimap -* `--skip_genebody_coverage` - Skip calculating the genebody coverage -* `--skip_preseq` - Skip Preseq -* `--skip_dupradar` - Skip dupRadar (and Picard MarkDups) -* `--skip_edger` - Skip edgeR MDS plot and heatmap -* `--skip_multiqc` - Skip MultiQC +* `--skipQC` - Skip **all QC steps**, apart from MultiQC +* `--skipFastQC` - Skip FastQC +* `--skipRseQC` - Skip RSeQC +* `--skipQualimap` - Skip Qualimap +* `--skipGenebodyCoverage` - Skip calculating the genebody coverage +* `--skipPreseq` - Skip Preseq +* `--skipDupRadar` - Skip dupRadar (and Picard MarkDuplicates) +* `--skipEdgeR` - Skip edgeR MDS plot and heatmap +* `--skipMultiQC` - Skip MultiQC ## Job resources ### Automatic resubmission @@ -416,16 +411,16 @@ Should be a string in the format integer-unit. eg. `--max_time '2.h'` Use to set a top-limit for the default CPU requirement for each process. Should be a string in the format integer-unit. eg. `--max_cpus 1` -### `--hisatBuildMemory` +### `--hisat_build_memory` Required amount of memory in GB to build HISAT2 index with splice sites. The HiSAT2 index build can proceed with or without exon / splice junction information. To work with this, a very large amount of memory is required. If this memory is not available, the index build will proceed without splicing information. -The `--hisatBuildMemory` option changes this threshold. By default it is `200GB` - if your system +The `--hisat_build_memory` option changes this threshold. By default it is `200GB` - if your system `--max_memory` is set to `128GB` but your genome is small enough to build using this, then you can -allow the exon build to proceed by supplying `--hisatBuildMemory 100GB` +allow the exon build to proceed by supplying `--hisat_build_memory 100GB` -### `--subsampFilesizeThreshold` +### `--subsamp_filesize_thresh` This parameter defines the threshold in BAM file size (in bytes) at which data subsampling is used prior to the RSeQC `gene_body_coverage` step. This step is done to speed up and reduce compute resources for the gene body coverage analysis . For very large files this means, that the BAM file will be subsampled to compute the `gene_body_coverage`, for small files there will not be a subsampling step. By default this parameter is set to `10000000000` - ten gigabytes. diff --git a/environment.yml b/environment.yml index 289a54dbf..2c588959d 100644 --- a/environment.yml +++ b/environment.yml @@ -6,27 +6,30 @@ channels: - bioconda - defaults dependencies: + ## conda-forge packages + - r-base=3.5.1 + - conda-forge::r-data.table=1.12.2 + - conda-forge::r-gplots=3.0.1.1 + - conda-forge::r-markdown=0.9 + - matplotlib=3.0.3 # Current 3.1.0 build incompatible with multiqc=1.7 + + ## bioconda packages - fastqc=0.11.8 - trim-galore=0.6.2 - - star=2.6.1d # don't upgrade me - 2.7X indices incompatible with iGenomes. + - star=2.6.1d # Don't upgrade me - 2.7X indices incompatible with iGenomes. + - rsem=1.3.2 + - salmon=0.14.0 - hisat2=2.1.0 + - stringtie=1.3.6 + - samtools=1.9 - picard=2.20.0 - - bioconductor-dupradar=1.14.0 - - conda-forge::r-data.table=1.12.2 - - conda-forge::r-gplots=3.0.1.1 - - bioconductor-edger=3.24.3 - - conda-forge::r-markdown=0.9 + - preseq=2.0.3 + - deeptools=3.2.1 + - gffread=0.9.12 - csvtk=0.17.0 - qualimap=2.2.2b - - preseq=2.0.3 - rseqc=3.0.0 - - rsem=1.3.2 - - samtools=1.9 - - salmon=0.14.0 - - stringtie=1.3.6 - subread=1.6.4 - - gffread=0.9.12 - - deeptools=3.2.1 - multiqc=1.7 - - matplotlib=3.0.3 - - r-base=3.5.1 + - bioconductor-dupradar=1.14.0 + - bioconductor-edger=3.24.3 diff --git a/main.nf b/main.nf index 97c2ad80f..44d37c131 100644 --- a/main.nf +++ b/main.nf @@ -24,60 +24,61 @@ def helpMessage() { -profile Configuration profile to use. Can use multiple (comma separated) Available: conda, docker, singularity, awsbatch, test and more. - Options: + References: If not specified in the configuration file or you wish to overwrite any of the references. --genome Name of iGenomes reference - --singleEnd Specifies that the input is single end reads - --aligner Specifies the aligner to use (available are: 'hisat2', 'star') - - Strandedness: - --forward_stranded The library is forward stranded - --reverse_stranded The library is reverse stranded - --unstranded The default behaviour - - References If not specified in the configuration file or you wish to overwrite any of the references. --star_index Path to STAR index --hisat2_index Path to HiSAT2 index - --fasta Path to Genome fasta file - --transcriptome Path to Transcriptome fasta file + --fasta Path to genome fasta file + --transcriptome Path to transcript fasta file required for `salmon` --gtf Path to GTF file --gff Path to GFF3 file --bed12 Path to bed12 file - --saveReference Save the generated reference files the the Results directory. - --saveTrimmed Save trimmed FastQ file intermediates - --saveAlignedIntermediates Save the BAM files from the Aligment step - not done by default + --saveReference Save the generated reference files to the results directory + --saveAlignedIntermediates Save the BAM files from the aligment step - not done by default - Trimming options + Trimming options: --clip_r1 [int] Instructs Trim Galore to remove bp from the 5' end of read 1 (or single-end reads) --clip_r2 [int] Instructs Trim Galore to remove bp from the 5' end of read 2 (paired-end reads only) --three_prime_clip_r1 [int] Instructs Trim Galore to remove bp from the 3' end of read 1 AFTER adapter/quality trimming has been performed --three_prime_clip_r2 [int] Instructs Trim Galore to remove bp from the 3' end of read 2 AFTER adapter/quality trimming has been performed - --trim_nextseq [int] Instructs Trim Galore to apply the --nextseq=X option, to trim based on quality after removing poly-G tails - + --trim_nextseq [int] Instructs Trim Galore to apply the --nextseq=X option, to trim based on quality after removing poly-G tails + --saveTrimmed Save trimmed FastQ file intermediates + + Alignment: + --singleEnd Specifies that the input is single-end reads + --aligner Specifies the aligner to use (available are: 'hisat2', 'star', 'salmon') + --seq_center Add sequencing center in @RG line of output BAM header + --skipSalmon Skip Salmon quantification step + + Strandedness: + --forwardStranded The library is forward stranded + --reverseStranded The library is reverse stranded + --unStranded The default behaviour + Presets: - --pico Sets trimming and standedness settings for the SMARTer Stranded Total RNA-Seq Kit - Pico Input kit. Equivalent to: --forward_stranded --clip_r1 3 --three_prime_clip_r2 3 - --fcExtraAttributes Define which extra parameters should also be included in featureCounts (default: gene_names) - --fcGroupFeatures Define the attribute type used to group features. (default: 'gene_name') - --fcGroupFeaturesType Define the type attribute used to group features based on the group attribute (default: 'gene_biotype') + --pico Sets trimming and standedness settings for the SMARTer Stranded Total RNA-Seq Kit - Pico Input kit. Equivalent to: --forwardStranded --clip_r1 3 --three_prime_clip_r2 3 + --fc_extra_attributes Define which extra parameters should also be included in featureCounts (default: 'gene_name') + --fc_group_features Define the attribute type used to group features. (default: 'gene_id') + --fc_group_features_type Define the type attribute used to group features based on the group attribute (default: 'gene_biotype') + + QC options: + --skipQC Skip all QC steps apart from MultiQC + --skipFastQC Skip FastQC + --skipRseQC Skip RSeQC + --skipQualimap Skip Qualimap + --skipGenebodyCoverage Skip calculating genebody coverage + --skipPreseq Skip Preseq + --skipDupRadar Skip dupRadar (and Picard MarkDuplicates) + --skipEdgeR Skip edgeR MDS plot and heatmap + --skipMultiQC Skip MultiQC Other options: + --sampleLevel Used to turn off the edgeR MDS and heatmap. Set automatically when running on fewer than 3 samples --outdir The output directory where the results will be saved -w/--work-dir The temporary directory where intermediate data will be saved --email Set this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits - --sampleLevel Used to turn of the edgeR MDS and heatmap. Set automatically when running on fewer than 3 samples --maxMultiqcEmailFileSize Theshold size for MultiQC report to be attached in notification email. If file generated by pipeline exceeds the threshold, it will not be attached (Default: 25MB) - -name Name for the pipeline run. If not specified, Nextflow will automatically generate a random mnemonic. - --seqCenter Add sequencing center in @RG line of output BAM header - - QC options: - --skip_qc Skip all QC steps apart from MultiQC - --skip_fastqc Skip FastQC - --skip_rseqc Skip RSeQC - --skip_qualimap Skip Qualimap - --skip_genebody_coverage Skip calculating genebody coverage - --skip_preseq Skip Preseq - --skip_dupradar Skip dupRadar (and Picard MarkDups) - --skip_edger Skip edgeR MDS plot and heatmap - --skip_multiqc Skip MultiQC + -name Name for the pipeline run. If not specified, Nextflow will automatically generate a random mnemonic AWSBatch options: --awsqueue The AWSBatch JobQueue that needs to be set when running on AWSBatch @@ -109,11 +110,10 @@ params.gff = params.genome ? params.genomes[ params.genome ].gff ?: false : fals params.bed12 = params.genome ? params.genomes[ params.genome ].bed12 ?: false : false params.hisat2_index = params.genome ? params.genomes[ params.genome ].hisat2 ?: false : false - -ch_mdsplot_header = Channel.fromPath("$baseDir/assets/mdsplot_header.txt") -ch_heatmap_header = Channel.fromPath("$baseDir/assets/heatmap_header.txt") -ch_biotypes_header = Channel.fromPath("$baseDir/assets/biotypes_header.txt") -Channel.fromPath("$baseDir/assets/where_are_my_files.txt") +ch_mdsplot_header = Channel.fromPath("$baseDir/assets/mdsplot_header.txt", checkIfExists: true) +ch_heatmap_header = Channel.fromPath("$baseDir/assets/heatmap_header.txt", checkIfExists: true) +ch_biotypes_header = Channel.fromPath("$baseDir/assets/biotypes_header.txt", checkIfExists: true) +Channel.fromPath("$baseDir/assets/where_are_my_files.txt", checkIfExists: true) .into{ch_where_trim_galore; ch_where_star; ch_where_hisat2; ch_where_hisat2_sort} // Define regular variables so that they can be overwritten @@ -121,9 +121,9 @@ clip_r1 = params.clip_r1 clip_r2 = params.clip_r2 three_prime_clip_r1 = params.three_prime_clip_r1 three_prime_clip_r2 = params.three_prime_clip_r2 -forward_stranded = params.forward_stranded -reverse_stranded = params.reverse_stranded -unstranded = params.unstranded +forwardStranded = params.forwardStranded +reverseStranded = params.reverseStranded +unStranded = params.unStranded // Preset trimming options if (params.pico){ @@ -131,9 +131,9 @@ if (params.pico){ clip_r2 = 0 three_prime_clip_r1 = 0 three_prime_clip_r2 = 3 - forward_stranded = true - reverse_stranded = false - unstranded = false + forwardStranded = true + reverseStranded = false + unStranded = false } // Validate inputs @@ -142,18 +142,23 @@ if (params.aligner != 'star' && params.aligner != 'hisat2' && params.aligner != } if( params.star_index && params.aligner == 'star' ){ star_index = Channel - .fromPath(params.star_index) + .fromPath(params.star_index, checkIfExists: true) .ifEmpty { exit 1, "STAR index not found: ${params.star_index}" } } else if ( params.hisat2_index && params.aligner == 'hisat2' ){ hs2_indices = Channel - .fromPath("${params.hisat2_index}*") + .fromPath("${params.hisat2_index}*", checkIfExists: true) .ifEmpty { exit 1, "HISAT2 index not found: ${params.hisat2_index}" } } +else if ( params.transcriptome && params.aligner == 'salmon' ){ + tx_fasta = Channel + .fromPath(params.transcriptome, checkIfExists: true) + .ifEmpty { exit 1, "Transcriptome fasta file not found: ${params.transcriptome}" } +} else if ( params.fasta ){ - Channel.fromPath(params.fasta) + Channel.fromPath(params.fasta, checkIfExists: true) .ifEmpty { exit 1, "Fasta file not found: ${params.fasta}" } - .into { ch_fasta_for_star_index; ch_fasta_for_hisat_index} + .into { ch_fasta_for_star_index; ch_fasta_for_hisat_index } } else { exit 1, "No reference genome specified!" @@ -161,36 +166,27 @@ else { if( params.gtf ){ Channel - .fromPath(params.gtf) + .fromPath(params.gtf, checkIfExists: true) .ifEmpty { exit 1, "GTF annotation file not found: ${params.gtf}" } .into { gtf_makeSTARindex; gtf_makeHisatSplicesites; gtf_makeHISATindex; gtf_makeBED12; - gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM; - gtf_salmon_quant; gtf_merge_salmon_quant } + gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM; + gtf_salmon_quant; gtf_merge_salmon_quant } } else if( params.gff ){ - gffFile = Channel.fromPath(params.gff) + gffFile = Channel.fromPath(params.gff, checkIfExists: true) .ifEmpty { exit 1, "GFF annotation file not found: ${params.gff}" } } else { exit 1, "No GTF or GFF3 annotation specified!" } -if (params.transcriptome){ - Channel - .fromPath(params.transcriptome) - .ifEmpty { exit 1, "Transcript fasta file not found: ${params.transcriptome}" } - .set { tx_fasta_ch } -} - - - if( params.bed12 ){ bed12 = Channel - .fromPath(params.bed12) + .fromPath(params.bed12, checkIfExists: true) .ifEmpty { exit 1, "BED12 annotation file not found: ${params.bed12}" } - .into {bed_rseqc; bed_genebody_coverage} + .into { bed_rseqc; bed_genebody_coverage } } if( params.aligner == 'hisat2' && params.splicesites ){ Channel - .fromPath(params.bed12) + .fromPath(params.bed12, checkIfExists: true) .ifEmpty { exit 1, "HISAT2 splice sites file not found: $alignment_splicesites" } .into { indexing_splicesites; alignment_splicesites } } @@ -205,7 +201,6 @@ if( !(workflow.runName ==~ /[a-z]+_[a-z]+/) ){ custom_runName = workflow.runName } - if( workflow.profile == 'awsbatch') { // AWSBatch sanity checking if (!params.awsqueue || !params.awsregion) exit 1, "Specify correct --awsqueue and --awsregion parameters on AWSBatch!" @@ -216,8 +211,8 @@ if( workflow.profile == 'awsbatch') { } // Stage config files -ch_multiqc_config = Channel.fromPath(params.multiqc_config) -ch_output_docs = Channel.fromPath("$baseDir/docs/output.md") +ch_multiqc_config = Channel.fromPath(params.multiqc_config, checkIfExists: true) +ch_output_docs = Channel.fromPath("$baseDir/docs/output.md", checkIfExists: true) /* * Create a channel for input read files @@ -228,19 +223,19 @@ if(params.readPaths){ .from(params.readPaths) .map { row -> [ row[0], [file(row[1][0])]] } .ifEmpty { exit 1, "params.readPaths was empty - no input files supplied" } - .into { raw_reads_fastqc; raw_reads_trimgalore; raw_reads_salmon } + .into { raw_reads_fastqc; raw_reads_trimgalore } } else { Channel .from(params.readPaths) .map { row -> [ row[0], [file(row[1][0]), file(row[1][1])]] } .ifEmpty { exit 1, "params.readPaths was empty - no input files supplied" } - .into { raw_reads_fastqc; raw_reads_trimgalore; raw_reads_salmon } + .into { raw_reads_fastqc; raw_reads_trimgalore } } } else { Channel .fromFilePairs( params.reads, size: params.singleEnd ? 1 : 2 ) .ifEmpty { exit 1, "Cannot find any reads matching: ${params.reads}\nNB: Path needs to be enclosed in quotes!\nNB: Path requires at least one * wildcard!\nIf this is single-end data, please specify --singleEnd on the command line." } - .into { raw_reads_fastqc; raw_reads_trimgalore; raw_reads_salmon } + .into { raw_reads_fastqc; raw_reads_trimgalore } } @@ -253,7 +248,7 @@ summary['Reads'] = params.reads summary['Data Type'] = params.singleEnd ? 'Single-End' : 'Paired-End' if(params.genome) summary['Genome'] = params.genome if(params.pico) summary['Library Prep'] = "SMARTer Stranded Total RNA-Seq Kit - Pico Input" -summary['Strandedness'] = ( unstranded ? 'None' : forward_stranded ? 'Forward' : reverse_stranded ? 'Reverse' : 'None' ) +summary['Strandedness'] = ( unStranded ? 'None' : forwardStranded ? 'Forward' : reverseStranded ? 'Reverse' : 'None' ) summary['Trimming'] = "5'R1: $clip_r1 / 5'R2: $clip_r2 / 3'R1: $three_prime_clip_r1 / 3'R2: $three_prime_clip_r2 / NextSeq Trim: $params.trim_nextseq" if(params.aligner == 'star'){ summary['Aligner'] = "STAR" @@ -264,8 +259,10 @@ if(params.aligner == 'star'){ if(params.hisat2_index) summary['HISAT2 Index'] = params.hisat2_index else if(params.fasta) summary['Fasta Ref'] = params.fasta if(params.splicesites) summary['Splice Sites'] = params.splicesites +} else if(params.aligner == 'salmon') { + summary['Aligner'] = "Salmon" + summary['Transcriptome'] = params.transcriptome } -if(params.transcriptome) summary['Transcriptome'] = params.transcriptome if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff if(params.bed12) summary['BED Annotation'] = params.bed12 @@ -352,6 +349,50 @@ process get_software_versions { """ } +/* + * PREPROCESSING - Convert GFF3 to GTF + */ +if(params.gff){ + process convertGFFtoGTF { + tag "$gff" + + input: + file gff from gffFile + + output: + file "${gff.baseName}.gtf" into gtf_makeSTARindex, gtf_makeHisatSplicesites, gtf_makeHISATindex, gtf_makeBED12, + gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon_quant, + gtf_merge_salmon_quant + + script: + """ + gffread $gff -T -o ${gff.baseName}.gtf + """ + } +} + +/* + * PREPROCESSING - Build BED12 file + */ +if(!params.bed12){ + process makeBED12 { + tag "$gtf" + publishDir path: { params.saveReference ? "${params.outdir}/reference_genome" : params.outdir }, + saveAs: { params.saveReference ? it : null }, mode: 'copy' + + input: + file gtf from gtf_makeBED12 + + output: + file "${gtf.baseName}.bed" into bed_rseqc, bed_genebody_coverage + + script: // This script is bundled with the pipeline, in nfcore/rnaseq/bin/ + """ + gtf2bed $gtf > ${gtf.baseName}.bed + """ + } +} + /* * PREPROCESSING - Build STAR index */ @@ -383,6 +424,7 @@ if(params.aligner == 'star' && !params.star_index && params.fasta){ """ } } + /* * PREPROCESSING - Build HISAT2 splice sites file */ @@ -404,6 +446,7 @@ if(params.aligner == 'hisat2' && !params.splicesites){ """ } } + /* * PREPROCESSING - Build HISAT2 index */ @@ -448,74 +491,29 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ } } - /* * PREPROCESSING - Create Salmon transcriptome index */ if(params.transcriptome){ - process makeSalmonIndex { - label 'salmon' - tag "$transcriptome.simpleName" - publishDir path: { params.saveReference ? "${params.outdir}/reference_transcriptome" : params.outdir }, - saveAs: { params.saveReference ? it : null }, mode: 'copy' - - input: - file transcriptome from tx_fasta_ch - - output: - file 'salmon_index' into salmon_index_ch - - script: - """ - salmon index --threads $task.cpus -t $transcriptome -i salmon_index - """ - } -} - - -/* - * PREPROCESSING - Convert GFF3 to GTF - */ -if(params.gff){ - process convertGFFtoGTF { - tag "$gff" - - input: - file gff from gffFile - - output: - file "${gff.baseName}.gtf" into gtf_makeSTARindex, gtf_makeHisatSplicesites, gtf_makeHISATindex, gtf_makeBED12, - gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon_quant, gtf_merge_salmon_quant - - script: - """ - gffread $gff -T -o ${gff.baseName}.gtf - """ - } -} -/* - * PREPROCESSING - Build BED12 file - */ -if(!params.bed12){ - process makeBED12 { - tag "$gtf" + process makeSalmonIndex { + label 'salmon' + tag "$transcriptome.simpleName" publishDir path: { params.saveReference ? "${params.outdir}/reference_genome" : params.outdir }, - saveAs: { params.saveReference ? it : null }, mode: 'copy' + saveAs: { params.saveReference ? it : null }, mode: 'copy' input: - file gtf from gtf_makeBED12 + file transcriptome from tx_fasta output: - file "${gtf.baseName}.bed" into bed_rseqc, bed_genebody_coverage + file 'salmon_index' into salmon_index_ch - script: // This script is bundled with the pipeline, in nfcore/rnaseq/bin/ + script: """ - gtf2bed $gtf > ${gtf.baseName}.bed + salmon index --threads $task.cpus -t $transcriptome -i salmon_index """ } } - /* * STEP 1 - FastQC */ @@ -525,7 +523,7 @@ process fastqc { saveAs: {filename -> filename.indexOf(".zip") > 0 ? "zips/$filename" : "$filename"} when: - !params.skip_qc && !params.skip_fastqc + !params.skipQC && !params.skipFastQC input: set val(name), file(reads) from raw_reads_fastqc @@ -571,7 +569,7 @@ process trim_galore { c_r2 = clip_r2 > 0 ? "--clip_r2 ${clip_r2}" : '' tpc_r1 = three_prime_clip_r1 > 0 ? "--three_prime_clip_r1 ${three_prime_clip_r1}" : '' tpc_r2 = three_prime_clip_r2 > 0 ? "--three_prime_clip_r2 ${three_prime_clip_r2}" : '' - nextseq = params.trim_nextseq > 0 ? "--nextseq ${params.trim_nextseq}" : '' + nextseq = params.trim_nextseq > 0 ? "--nextseq ${params.trim_nextseq}" : '' if (params.singleEnd) { """ trim_galore --fastqc --gzip $c_r1 $tpc_r1 $nextseq $reads @@ -583,7 +581,6 @@ process trim_galore { } } - /* * STEP 3 - align with STAR */ @@ -638,7 +635,7 @@ if(params.aligner == 'star'){ prefix = reads[0].toString() - ~/(_R1)?(_trimmed)?(_val_1)?(\.fq)?(\.fastq)?(\.gz)?$/ def star_mem = task.memory ?: params.star_memory ?: false def avail_mem = star_mem ? "--limitBAMsortRAM ${star_mem.toBytes() - 100000000}" : '' - seqCenter = params.seqCenter ? "--outSAMattrRGline ID:$prefix 'CN:$params.seqCenter'" : '' + seq_center = params.seq_center ? "--outSAMattrRGline ID:$prefix 'CN:$params.seq_center'" : '' """ STAR --genomeDir $index \\ --sjdbGTFfile $gtf \\ @@ -649,7 +646,7 @@ if(params.aligner == 'star'){ --outSAMtype BAM SortedByCoordinate $avail_mem \\ --readFilesCommand zcat \\ --runDirPerm All_RWX \\ - --outFileNamePrefix $prefix $seqCenter + --outFileNamePrefix $prefix $seq_center samtools index ${prefix}Aligned.sortedByCoord.out.bam """ @@ -692,11 +689,11 @@ if(params.aligner == 'hisat2'){ script: index_base = hs2_indices[0].toString() - ~/.\d.ht2l?/ prefix = reads[0].toString() - ~/(_R1)?(_trimmed)?(_val_1)?(\.fq)?(\.fastq)?(\.gz)?$/ - seqCenter = params.seqCenter ? "--rg-id ${prefix} --rg CN:${params.seqCenter.replaceAll('\\s','_')}" : '' + seq_center = params.seq_center ? "--rg-id ${prefix} --rg CN:${params.seq_center.replaceAll('\\s','_')}" : '' def rnastrandness = '' - if (forward_stranded && !unstranded){ + if (forwardStranded && !unStranded){ rnastrandness = params.singleEnd ? '--rna-strandness F' : '--rna-strandness FR' - } else if (reverse_stranded && !unstranded){ + } else if (reverseStranded && !unStranded){ rnastrandness = params.singleEnd ? '--rna-strandness R' : '--rna-strandness RF' } if (params.singleEnd) { @@ -708,7 +705,7 @@ if(params.aligner == 'hisat2'){ -p ${task.cpus} \\ --met-stderr \\ --new-summary \\ - --summary-file ${prefix}.hisat2_summary.txt $seqCenter \\ + --summary-file ${prefix}.hisat2_summary.txt $seq_center \\ | samtools view -bS -F 4 -F 256 - > ${prefix}.bam """ } else { @@ -723,7 +720,7 @@ if(params.aligner == 'hisat2'){ -p ${task.cpus} \\ --met-stderr \\ --new-summary \\ - --summary-file ${prefix}.hisat2_summary.txt $seqCenter \\ + --summary-file ${prefix}.hisat2_summary.txt $seq_center \\ | samtools view -bS -F 4 -F 8 -F 256 - > ${prefix}.bam """ } @@ -794,7 +791,7 @@ process rseqc { } when: - !params.skip_qc && !params.skip_rseqc + !params.skipQC && !params.skipRseQC input: file bam_rseqc @@ -858,7 +855,7 @@ process genebody_coverage { } when: - !params.skip_qc && !params.skip_genebody_coverage + !params.skipQC && !params.skipGenebodyCoverage input: file bam from bam_subsampled.concat(bam_skipSubsampFiltered) @@ -886,7 +883,7 @@ process preseq { publishDir "${params.outdir}/preseq", mode: 'copy' when: - !params.skip_qc && !params.skip_preseq + !params.skipQC && !params.skipPreseq input: file bam_preseq @@ -900,7 +897,6 @@ process preseq { """ } - /* * STEP 6 - Mark duplicates */ @@ -910,7 +906,7 @@ process markDuplicates { saveAs: {filename -> filename.indexOf("_metrics.txt") > 0 ? "metrics/$filename" : "$filename"} when: - !params.skip_qc && !params.skip_dupradar + !params.skipQC && !params.skipDupRadar input: file bam from bam_markduplicates @@ -922,7 +918,6 @@ process markDuplicates { script: markdup_java_options = (task.memory.toGiga() > 8) ? ${params.markdup_java_options} : "\"-Xms" + (task.memory.toGiga() / 2 )+"g "+ "-Xmx" + (task.memory.toGiga() - 1)+ "g\"" - """ picard ${markdup_java_options} MarkDuplicates \\ INPUT=$bam \\ @@ -945,7 +940,7 @@ process qualimap { publishDir "${params.outdir}/qualimap", mode: 'copy' when: - !params.skip_qc && !params.skip_qualimap + !params.skipQC && !params.skipQualimap input: file bam from bam_qualimap @@ -956,9 +951,9 @@ process qualimap { script: def qualimap_direction = 'non-strand-specific' - if (forward_stranded){ + if (forwardStranded){ qualimap_direction = 'strand-specific-forward' - }else if (reverse_stranded){ + }else if (reverseStranded){ qualimap_direction = 'strand-specific-reverse' } def paired = params.singleEnd ? '' : '-pe' @@ -969,8 +964,6 @@ process qualimap { """ } - - /* * STEP 8 - dupRadar */ @@ -989,7 +982,7 @@ process dupradar { } when: - !params.skip_qc && !params.skip_dupradar + !params.skipQC && !params.skipDupRadar input: file bam_md @@ -1000,9 +993,9 @@ process dupradar { script: // This script is bundled with the pipeline, in nfcore/rnaseq/bin/ def dupradar_direction = 0 - if (forward_stranded && !unstranded) { + if (forwardStranded && !unStranded) { dupradar_direction = 1 - } else if (reverse_stranded && !unstranded){ + } else if (reverseStranded && !unStranded){ dupradar_direction = 2 } def paired = params.singleEnd ? 'single' : 'paired' @@ -1011,8 +1004,6 @@ process dupradar { """ } - - /* * STEP 9 - Feature counts */ @@ -1039,17 +1030,17 @@ process featureCounts { script: def featureCounts_direction = 0 - def extraAttributes = params.fcExtraAttributes ? "--extraAttributes ${params.fcExtraAttributes}" : '' - if (forward_stranded && !unstranded) { + def extraAttributes = params.fc_extra_attributes ? "--extraAttributes ${params.fc_extra_attributes}" : '' + if (forwardStranded && !unStranded) { featureCounts_direction = 1 - } else if (reverse_stranded && !unstranded){ + } else if (reverseStranded && !unStranded){ featureCounts_direction = 2 } // Try to get real sample name sample_name = bam_featurecounts.baseName - 'Aligned.sortedByCoord.out' """ - featureCounts -a $gtf -g ${params.fcGroupFeatures} -o ${bam_featurecounts.baseName}_gene.featureCounts.txt $extraAttributes -p -s $featureCounts_direction $bam_featurecounts - featureCounts -a $gtf -g ${params.fcGroupFeaturesType} -o ${bam_featurecounts.baseName}_biotype.featureCounts.txt -p -s $featureCounts_direction $bam_featurecounts + featureCounts -a $gtf -g ${params.fc_group_features} -o ${bam_featurecounts.baseName}_gene.featureCounts.txt $extraAttributes -p -s $featureCounts_direction $bam_featurecounts + featureCounts -a $gtf -g ${params.fc_group_features_type} -o ${bam_featurecounts.baseName}_biotype.featureCounts.txt -p -s $featureCounts_direction $bam_featurecounts cut -f 1,7 ${bam_featurecounts.baseName}_biotype.featureCounts.txt | tail -n +3 | cat $biotypes_header - >> ${bam_featurecounts.baseName}_biotype_counts_mqc.txt mqc_features_stat.py ${bam_featurecounts.baseName}_biotype_counts_mqc.txt -s $sample_name -f rRNA -o ${bam_featurecounts.baseName}_biotype_counts_gs_mqc.tsv """ @@ -1077,9 +1068,8 @@ process merge_featureCounts { """ } - /* - * STEP 11 - Salmon on transcriptome + * STEP 11 - Transcriptome quantification with Salmon */ if (params.transcriptome){ process salmon_quant { @@ -1098,7 +1088,7 @@ if (params.transcriptome){ file "${sample}" into salmon_multiqc_logs //MultiQC needs the sample folder to have proper names for samples script: - def strandedness = params.unstranded ? 'U' : 'SR' + def strandedness = params.unStranded ? 'U' : 'SR' def endedness = params.singleEnd ? "-r ${reads[0]}" : "-1 ${reads[0]} -2 ${reads[1]}" """ salmon quant --validateMappings \\ @@ -1109,24 +1099,26 @@ if (params.transcriptome){ --index ${index} \\ $endedness \\ -o ${sample} + # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ | sed "s:TPM:${sample}:" \\ > ${sample}/${sample}.quant.ids-only.txt + # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ | sed "s:TPM:${sample}:" \\ > ${sample}/${sample}.quant.genes.ids-only.txt """ } - + process merge_salmon_transcript_quant { label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' input: - file transcript_quants from salmon_transcript_quant.collect() - file gtf from gtf_merge_salmon_quant + file transcript_quants from salmon_transcript_quant + file gtf from gtf_merge_salmon_quant.collect() output: file 'salmon_merged_transcript_tpm.csv' @@ -1154,9 +1146,9 @@ if (params.transcriptome){ publishDir "${params.outdir}/salmon", mode: 'copy' input: - file gene_quants from salmon_gene_quant.collect() + file gene_quants from salmon_gene_quant // Use gene_id, gene_name mapping from featurecounts to make sure it matches - file featurecounts_merged from featurecounts_merged + file featurecounts_merged from featurecounts_merged.collect() output: file 'salmon_merged_gene_tpm.csv' @@ -1179,7 +1171,6 @@ if (params.transcriptome){ } } - /* * STEP 12 - stringtie FPKM */ @@ -1206,9 +1197,9 @@ process stringtieFPKM { script: def st_direction = '' - if (forward_stranded && !unstranded){ + if (forwardStranded && !unStranded){ st_direction = "--fr" - } else if (reverse_stranded && !unstranded){ + } else if (reverseStranded && !unStranded){ st_direction = "--rf" } """ @@ -1233,7 +1224,7 @@ process sample_correlation { publishDir "${params.outdir}/sample_correlation", mode: 'copy' when: - !params.skip_qc && !params.skip_edger + !params.skipQC && !params.skipEdgeR input: file input_files from geneCounts.collect() @@ -1264,7 +1255,7 @@ process multiqc { publishDir "${params.outdir}/MultiQC", mode: 'copy' when: - !params.skip_multiqc + !params.skipMultiQC input: file multiqc_config from ch_multiqc_config.collect() @@ -1316,8 +1307,6 @@ process output_documentation { """ } - - /* * Completion e-mail notification */ @@ -1359,7 +1348,7 @@ workflow.onComplete { // On success try attach the multiqc report def mqc_report = null try { - if (workflow.success && !params.skip_multiqc) { + if (workflow.success && !params.skipMultiQC) { mqc_report = multiqc_report.getVal() if (mqc_report.getClass() == ArrayList){ log.warn "[nf-core/rnaseq] Found multiple reports from process 'multiqc', will use only one" @@ -1428,7 +1417,6 @@ workflow.onComplete { } - def nfcoreHeader(){ // Log colors ANSI codes c_reset = params.monochrome_logs ? '' : "\033[0m"; diff --git a/nextflow.config b/nextflow.config index 5924aba23..84697c7a6 100644 --- a/nextflow.config +++ b/nextflow.config @@ -12,31 +12,32 @@ params { project = false aligner = 'star' genome = false - forward_stranded = false - reverse_stranded = false - unstranded = false - fcExtraAttributes = 'gene_name' - fcGroupFeatures = 'gene_id' - fcGroupFeaturesType = 'gene_biotype' + forwardStranded = false + reverseStranded = false + unStranded = false + fc_extra_attributes = 'gene_name' + fc_group_features = 'gene_id' + fc_group_features_type = 'gene_biotype' markdup_java_options = '"-Xms4000m -Xmx7g"' //Established values for markDuplicate memory consumption, see issue PR #689 (in Sarek) for details splicesites = false saveReference = false saveTrimmed = false saveAlignedIntermediates = false + skipSalmon = false singleEnd = false reads = "data/*{1,2}.fastq.gz" outdir = './results' transcriptome = false - seqCenter = false - skip_qc = false - skip_fastqc = false - skip_rseqc = false - skip_qualimap = false - skip_genebody_coverage = false - skip_preseq = false - skip_dupradar = false - skip_edger = false - skip_multiqc = false + seq_center = false + skipQC = false + skipFastQC = false + skipRseQC = false + skipQualimap = false + skipGenebodyCoverage = false + skipPreseq = false + skipDupRadar = false + skipEdgeR = false + skipMultiQC = false // Custom trimming options pico = false @@ -47,8 +48,8 @@ params { // Defaults sampleLevel = false - hisatBuildMemory = 200 // Required amount of memory in GB to build HISAT2 index with splice sites - subsampFilesizeThreshold = 10000000000 // Don't subsample BAMs for RSeQC gene_body_coverage if less than this + hisat_build_memory = 200 // Required amount of memory in GB to build HISAT2 index with splice sites + subsamp_filesize_thresh = 10000000000 // Don't subsample BAMs for RSeQC gene_body_coverage if less than this maxMultiqcEmailFileSize = 25.MB readPaths = null star_memory = false // Cluster specific param required for hebbe diff --git a/parameters.settings.json b/parameters.settings.json index e52870779..cd9150fe3 100644 --- a/parameters.settings.json +++ b/parameters.settings.json @@ -39,7 +39,7 @@ "type": "boolean" }, { - "name": "forward_stranded", + "name": "forwardStranded", "label": "Forward stranded", "usage": "Samples are made using a forward-stranded library type.", "group": "Main options", @@ -48,7 +48,7 @@ "type": "boolean" }, { - "name": "reverse_stranded", + "name": "reverseStranded", "label": "Reverse stranded", "usage": "Samples are made using a reverse-stranded library type.", "group": "Main options", @@ -244,9 +244,9 @@ "default_value": "" }, { - "name": "fcGroupFeatures", + "name": "fc_group_features", "label": "FeatureCounts Group Features", - "usage": "By default, the pipeline uses `gene_name` as the default gene identifier group. Specifying `--fcGroupFeatures` uses a different category present in your provided GTF file.", + "usage": "By default, the pipeline uses `gene_name` as the default gene identifier group. Specifying `--fc_group_features` uses a different category present in your provided GTF file.", "default_value": "gene_id", "render": "textfield", "pattern": ".*", @@ -254,7 +254,7 @@ "group": "FeatureCount settings" }, { - "name": "fcGroupFeaturesType", + "name": "fc_group_features_type", "label": "FeatureCounts Group Features Biotype", "usage": "GTF attribute name that gives the biotype of a feature.", "group": "FeatureCount settings", @@ -264,9 +264,9 @@ "type": "string" }, { - "name": "fcExtraAttributes", + "name": "fc_extra_attributes", "label": "FeatureCounts Extra Gene Names", - "usage": "By default the pipeline uses `gene_names` as additional gene identifiers apart from ENSEMBL identifiers. --fcExtraAttributes is passed to featureCounts as an --extraAttributes parameter", + "usage": "By default the pipeline uses `gene_names` as additional gene identifiers apart from ENSEMBL identifiers. --fc_extra_attributes is passed to featureCounts as an --extraAttributes parameter", "render": "textfield", "pattern": ".*", "type": "string", @@ -352,7 +352,16 @@ { "name": "saveTrimmed", "label": "Save Trimmed FastQ files", - "usage": "Save the trimmed FastQ files the the results directory.", + "usage": "Save the trimmed FastQ files to the results directory.", + "group": "Pipeline defaults", + "render": "check-box", + "default_value": false, + "type": "boolean" + }, + { + "name": "skipSalmon", + "label": "Skip Salmon quantification step", + "usage": "Skip Salmon quantification step.", "group": "Pipeline defaults", "render": "check-box", "default_value": false, @@ -361,7 +370,7 @@ { "name": "saveAlignedIntermediates", "label": "Save Aligned Intermediate BAM files", - "usage": "Save intermediate BAM files the the results directory.", + "usage": "Save intermediate BAM files to the results directory.", "group": "Pipeline defaults", "render": "check-box", "default_value": false, @@ -370,7 +379,7 @@ { "name": "saveReference", "label": "Save reference genome index", - "usage": "Save the generated reference files the the results directory.", + "usage": "Save the generated reference files to the results directory.", "group": "Pipeline defaults", "render": "check-box", "default_value": false, @@ -387,7 +396,7 @@ "type": "string" }, { - "name": "skip_qc", + "name": "skipQC", "label": "Skip all QC steps, apart from MultiQC", "render": "check-box", "default_value": false, @@ -395,7 +404,7 @@ "group": "Skip pipeline steps" }, { - "name": "skip_multiqc", + "name": "skipMultiQC", "label": "Skip MultiQC", "render": "check-box", "default_value": false, @@ -403,39 +412,47 @@ "group": "Skip pipeline steps" }, { - "name": "skip_genebody_coverage", - "label": "Skip RSeQC gene body coverage", + "name": "skipEdgeR", + "label": "Skip edgeR QC analysis", "render": "check-box", "default_value": false, "type": "boolean", "group": "Skip pipeline steps" }, { - "name": "skip_edger", - "label": "Skip edgeR QC analysis", + "name": "skipDupRadar", + "label": "Skip DupRadar QC", "render": "check-box", "default_value": false, "type": "boolean", "group": "Skip pipeline steps" }, { - "name": "skip_dupradar", - "label": "Skip DupRadar QC", + "name": "skipRseQC", + "label": "Skip RSeQC steps, apart from genebody coverage", + "render": "check-box", + "default_value": false, + "type": "boolean", + "group": "Skip pipeline steps" + }, + { + "name": "skipQualimap", + "label": "Skip Qualimap step", "render": "check-box", "default_value": false, "type": "boolean", "group": "Skip pipeline steps" }, { - "name": "skip_rseqc", - "label": "Skip RSeQC steps, apart from Gene body coverage", + "name": "skipGenebodyCoverage", + "label": "Skip RSeQC genebody coverage", "render": "check-box", "default_value": false, "type": "boolean", "group": "Skip pipeline steps" }, { - "name": "skip_preseq", + "name": "skipPreseq", "label": "Skip Preseq analysis", "render": "check-box", "default_value": false, @@ -443,7 +460,7 @@ "group": "Skip pipeline steps" }, { - "name": "skip_fastqc", + "name": "skipFastQC", "label": "Skip FastQC", "render": "check-box", "default_value": false, @@ -491,7 +508,7 @@ "type": "string" }, { - "name": "subsampFilesizeThreshold", + "name": "subsamp_filesize_thresh", "label": "Subsample file-size threshold", "usage": "Defines the threshold in BAM file size (in bytes) at which data subsampling is used prior to the RSeQC `gene_body_coverage` step.", "group": "Advanced", @@ -511,7 +528,7 @@ "default_value": "" }, { - "name": "hisatBuildMemory", + "name": "hisat_build_memory", "label": "HISAT2 indexing: required memory for splice sites in GB", "usage": "HISAT2 needs a very large amount of memory to build an index with splice sites. If the available memory is below this threshold, the index build will proceed without splicing information.", "group": "Advanced", @@ -520,7 +537,7 @@ "type": "integer" }, { - "name": "seqCenter", + "name": "seq_center", "label": "Sequencing center", "usage": "Add sequencing center in @RG line of output BAM header", "group": "Advanced", @@ -550,7 +567,7 @@ "type": "string" }, { - "name": "unstranded", + "name": "unStranded", "label": "Unstranded", "usage": "Force the library strandedness to be unstranded", "render": "none", From d79565836949386c9575b014042fd5b68865f865 Mon Sep 17 00:00:00 2001 From: drpatelh Date: Mon, 10 Jun 2019 12:58:29 +0100 Subject: [PATCH 068/139] Fix lint errors and warnings --- CHANGELOG.md | 2 +- nextflow.config | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eaa97b19..a96c7a6de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183): added the folder "multiqc_plots" to the output. * Get MultiQC to write out the software versions in a .csv file [#185](https://github.com/nf-core/rnaseq/issues/185) * Change all boolean parameters from snake_case to camelCase and vice versa for value parameters -* Add `--skipSalmon` parameter to skip Salmon transcriptome quantification +* Add `--skipSalmon` parameter to skip Salmon transcriptome quantification ### Dependency Updates diff --git a/nextflow.config b/nextflow.config index 84697c7a6..5358eb860 100644 --- a/nextflow.config +++ b/nextflow.config @@ -45,6 +45,7 @@ params { clip_r2 = 0 three_prime_clip_r1 = 0 three_prime_clip_r2 = 0 + trim_nextseq = 0 // Defaults sampleLevel = false From 4523b874e3a9bba716cede7c0496e86b18fc44d1 Mon Sep 17 00:00:00 2001 From: drpatelh Date: Mon, 10 Jun 2019 13:31:02 +0100 Subject: [PATCH 069/139] Reorder parameters --- conf/base.config | 6 +++--- main.nf | 33 ++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/conf/base.config b/conf/base.config index 3e97709e8..933dbec42 100644 --- a/conf/base.config +++ b/conf/base.config @@ -23,8 +23,8 @@ process { withName: trim_galore { time = { check_max( 8.h * task.attempt, 'time' ) } } - withName:markDuplicates { - // Actually the -Xmx value should be kept lower, + withName: markDuplicates { + // Actually the -Xmx value should be kept lower, // and is set through the markdup_java_options cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 8.GB * task.attempt, 'memory' ) } @@ -50,7 +50,7 @@ process { memory = { check_max( 80.GB * task.attempt, 'memory' ) } time = { check_max( 8.h * task.attempt, 'time' ) } } - withName: "multiqc|get_software_versions" { + withName: 'multiqc|get_software_versions' { memory = { check_max( 2.GB * task.attempt, 'memory' ) } cache = false } diff --git a/main.nf b/main.nf index 44d37c131..5dc76530e 100644 --- a/main.nf +++ b/main.nf @@ -24,6 +24,9 @@ def helpMessage() { -profile Configuration profile to use. Can use multiple (comma separated) Available: conda, docker, singularity, awsbatch, test and more. + Generic: + --singleEnd Specifies that the input is single-end reads + References: If not specified in the configuration file or you wish to overwrite any of the references. --genome Name of iGenomes reference --star_index Path to STAR index @@ -34,9 +37,8 @@ def helpMessage() { --gff Path to GFF3 file --bed12 Path to bed12 file --saveReference Save the generated reference files to the results directory - --saveAlignedIntermediates Save the BAM files from the aligment step - not done by default - Trimming options: + Trimming: --clip_r1 [int] Instructs Trim Galore to remove bp from the 5' end of read 1 (or single-end reads) --clip_r2 [int] Instructs Trim Galore to remove bp from the 5' end of read 2 (paired-end reads only) --three_prime_clip_r1 [int] Instructs Trim Galore to remove bp from the 3' end of read 1 AFTER adapter/quality trimming has been performed @@ -44,24 +46,25 @@ def helpMessage() { --trim_nextseq [int] Instructs Trim Galore to apply the --nextseq=X option, to trim based on quality after removing poly-G tails --saveTrimmed Save trimmed FastQ file intermediates - Alignment: - --singleEnd Specifies that the input is single-end reads - --aligner Specifies the aligner to use (available are: 'hisat2', 'star', 'salmon') - --seq_center Add sequencing center in @RG line of output BAM header - --skipSalmon Skip Salmon quantification step - Strandedness: --forwardStranded The library is forward stranded --reverseStranded The library is reverse stranded --unStranded The default behaviour - - Presets: --pico Sets trimming and standedness settings for the SMARTer Stranded Total RNA-Seq Kit - Pico Input kit. Equivalent to: --forwardStranded --clip_r1 3 --three_prime_clip_r2 3 + + Alignment: + --aligner Specifies the aligner to use (available are: 'hisat2', 'star', 'salmon') + --seq_center Add sequencing center in @RG line of output BAM header + --skipSalmon Skip Salmon quantification step + --saveAlignedIntermediates Save the BAM files from the aligment step - not done by default + + Read Counting: --fc_extra_attributes Define which extra parameters should also be included in featureCounts (default: 'gene_name') --fc_group_features Define the attribute type used to group features. (default: 'gene_id') --fc_group_features_type Define the type attribute used to group features based on the group attribute (default: 'gene_biotype') + --sampleLevel Used to turn off the edgeR MDS and heatmap. Set automatically when running on fewer than 3 samples - QC options: + QC: --skipQC Skip all QC steps apart from MultiQC --skipFastQC Skip FastQC --skipRseQC Skip RSeQC @@ -72,8 +75,7 @@ def helpMessage() { --skipEdgeR Skip edgeR MDS plot and heatmap --skipMultiQC Skip MultiQC - Other options: - --sampleLevel Used to turn off the edgeR MDS and heatmap. Set automatically when running on fewer than 3 samples + Other options --outdir The output directory where the results will be saved -w/--work-dir The temporary directory where intermediate data will be saved --email Set this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits @@ -157,11 +159,11 @@ else if ( params.transcriptome && params.aligner == 'salmon' ){ } else if ( params.fasta ){ Channel.fromPath(params.fasta, checkIfExists: true) - .ifEmpty { exit 1, "Fasta file not found: ${params.fasta}" } + .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } .into { ch_fasta_for_star_index; ch_fasta_for_hisat_index } } else { - exit 1, "No reference genome specified!" + exit 1, "No reference genome files specified!" } if( params.gtf ){ @@ -494,6 +496,7 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ /* * PREPROCESSING - Create Salmon transcriptome index */ +println(params.transcriptome) if(params.transcriptome){ process makeSalmonIndex { label 'salmon' From 66a586cd6fd27bd78ddb4f7b3d54c55f33b4c54a Mon Sep 17 00:00:00 2001 From: drpatelh Date: Mon, 10 Jun 2019 13:46:44 +0100 Subject: [PATCH 070/139] Rename Salmon processes --- conf/base.config | 35 ++++++++++++++++++----------------- main.nf | 27 +++++++++++++++------------ 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/conf/base.config b/conf/base.config index 933dbec42..2d223a14f 100644 --- a/conf/base.config +++ b/conf/base.config @@ -20,6 +20,24 @@ process { maxErrors = '-1' // Process-specific resource requirements + withLabel: low_memory { + memory = { check_max( 16.GB * task.attempt, 'memory' ) } + } + withLabel: mid_memory { + memory = { check_max( 32.GB * task.attempt, 'memory' ) } + time = { check_max( 8.h * task.attempt, 'time' ) } + } + withLabel: high_memory { + cpus = { check_max (10, 'cpus')} + memory = { check_max( 80.GB * task.attempt, 'memory' ) } + time = { check_max( 8.h * task.attempt, 'time' ) } + } + + withName: makeHISATindex { + cpus = { check_max( 10, 'cpus' ) } + memory = { check_max( 200.GB * task.attempt, 'memory' ) } + time = { check_max( 5.h * task.attempt, 'time' ) } + } withName: trim_galore { time = { check_max( 8.h * task.attempt, 'time' ) } } @@ -29,27 +47,10 @@ process { cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 8.GB * task.attempt, 'memory' ) } } - withName: makeHISATindex { - cpus = { check_max( 10, 'cpus' ) } - memory = { check_max( 200.GB * task.attempt, 'memory' ) } - time = { check_max( 5.h * task.attempt, 'time' ) } - } withLabel: salmon { cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 16.GB * task.attempt, 'memory' ) } } - withLabel: low_memory { - memory = { check_max( 16.GB * task.attempt, 'memory' ) } - } - withLabel: mid_memory { - memory = { check_max( 32.GB * task.attempt, 'memory' ) } - time = { check_max( 8.h * task.attempt, 'time' ) } - } - withLabel: high_memory { - cpus = { check_max (10, 'cpus')} - memory = { check_max( 80.GB * task.attempt, 'memory' ) } - time = { check_max( 8.h * task.attempt, 'time' ) } - } withName: 'multiqc|get_software_versions' { memory = { check_max( 2.GB * task.attempt, 'memory' ) } cache = false diff --git a/main.nf b/main.nf index 5dc76530e..e07513b12 100644 --- a/main.nf +++ b/main.nf @@ -152,11 +152,6 @@ else if ( params.hisat2_index && params.aligner == 'hisat2' ){ .fromPath("${params.hisat2_index}*", checkIfExists: true) .ifEmpty { exit 1, "HISAT2 index not found: ${params.hisat2_index}" } } -else if ( params.transcriptome && params.aligner == 'salmon' ){ - tx_fasta = Channel - .fromPath(params.transcriptome, checkIfExists: true) - .ifEmpty { exit 1, "Transcriptome fasta file not found: ${params.transcriptome}" } -} else if ( params.fasta ){ Channel.fromPath(params.fasta, checkIfExists: true) .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } @@ -166,6 +161,14 @@ else { exit 1, "No reference genome files specified!" } +if ( params.transcriptome ) { + tx_fasta = Channel + .fromPath(params.transcriptome, checkIfExists: true) + .ifEmpty { exit 1, "Transcriptome fasta file not found: ${params.transcriptome}" } +} else if ( !params.transcriptome && params.aligner == 'salmon' ) { + exit 1, "Transcriptome fasta file required to run Salmon not specified!" +} + if( params.gtf ){ Channel .fromPath(params.gtf, checkIfExists: true) @@ -1075,7 +1078,7 @@ process merge_featureCounts { * STEP 11 - Transcriptome quantification with Salmon */ if (params.transcriptome){ - process salmon_quant { + process salmon { label 'salmon' tag "$sample" publishDir "${params.outdir}/salmon", mode: 'copy' @@ -1115,7 +1118,7 @@ if (params.transcriptome){ """ } - process merge_salmon_transcript_quant { + process salmon_merge_transcript { label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' @@ -1124,7 +1127,7 @@ if (params.transcriptome){ file gtf from gtf_merge_salmon_quant.collect() output: - file 'salmon_merged_transcript_tpm.csv' + file 'salmon_merge_transcript_tpm.csv' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1141,10 +1144,10 @@ if (params.transcriptome){ | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ | csvtk tab2csv \\ - > salmon_merged_transcript_tpm.csv + > salmon_merge_transcript_tpm.csv """ } - process merge_salmon_gene_quant { + process salmon_merge_gene { label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' @@ -1154,7 +1157,7 @@ if (params.transcriptome){ file featurecounts_merged from featurecounts_merged.collect() output: - file 'salmon_merged_gene_tpm.csv' + file 'salmon_merge_gene_tpm.csv' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1169,7 +1172,7 @@ if (params.transcriptome){ | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ | csvtk tab2csv \\ - > salmon_merged_gene_tpm.csv + > salmon_merge_gene_tpm.csv """ } } From d563371ca5c7cefbbfcd2b9d7d9919a6b48c35fd Mon Sep 17 00:00:00 2001 From: drpatelh Date: Mon, 10 Jun 2019 14:01:23 +0100 Subject: [PATCH 071/139] Use correct strandedness --- main.nf | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/main.nf b/main.nf index e07513b12..f3ee5e2d7 100644 --- a/main.nf +++ b/main.nf @@ -1094,14 +1094,20 @@ if (params.transcriptome){ file "${sample}" into salmon_multiqc_logs //MultiQC needs the sample folder to have proper names for samples script: - def strandedness = params.unStranded ? 'U' : 'SR' + def strandedness = 'U' + if (forwardStranded) { + strandedness = 'SF' + } else if (reverseStranded) { + strandedness = 'SR' + } + def end_strandedness = params.singleEnd ? strandedness : "I${strandedness}" def endedness = params.singleEnd ? "-r ${reads[0]}" : "-1 ${reads[0]} -2 ${reads[1]}" """ salmon quant --validateMappings \\ --seqBias --useVBOpt --gcBias \\ --geneMap ${gtf} \\ --threads ${task.cpus} \\ - --libType=${strandedness} \\ + --libType=${end_strandedness} \\ --index ${index} \\ $endedness \\ -o ${sample} @@ -1127,7 +1133,7 @@ if (params.transcriptome){ file gtf from gtf_merge_salmon_quant.collect() output: - file 'salmon_merge_transcript_tpm.csv' + file 'salmon_merged_transcript_tpm.csv' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1144,7 +1150,7 @@ if (params.transcriptome){ | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ | csvtk tab2csv \\ - > salmon_merge_transcript_tpm.csv + > salmon_merged_transcript_tpm.csv """ } process salmon_merge_gene { @@ -1153,11 +1159,10 @@ if (params.transcriptome){ input: file gene_quants from salmon_gene_quant - // Use gene_id, gene_name mapping from featurecounts to make sure it matches - file featurecounts_merged from featurecounts_merged.collect() + file featurecounts_merged from featurecounts_merged.collect() // Use gene_id > gene_name mapping from featurecounts to make sure it matches output: - file 'salmon_merge_gene_tpm.csv' + file 'salmon_merged_gene_tpm.csv' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. @@ -1166,13 +1171,14 @@ if (params.transcriptome){ """ ## Merge gene counts csvtk cut -t -f 1,2 $featurecounts_merged > gene_id__to__gene_name.txt + ## Merge gene counts using gene_id to gene_name mapping from featurecounts, as $merge $gene_quants \\ | csvtk join -t -f 1 gene_id__to__gene_name.txt - \\ | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ | csvtk tab2csv \\ - > salmon_merge_gene_tpm.csv + > salmon_merged_gene_tpm.csv """ } } From c8910831f807206f3e969547b211c35db82d714e Mon Sep 17 00:00:00 2001 From: drpatelh Date: Mon, 10 Jun 2019 14:11:54 +0100 Subject: [PATCH 072/139] Reorder validate inputs --- main.nf | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/main.nf b/main.nf index f3ee5e2d7..807384913 100644 --- a/main.nf +++ b/main.nf @@ -161,6 +161,13 @@ else { exit 1, "No reference genome files specified!" } +if( params.aligner == 'hisat2' && params.splicesites ){ + Channel + .fromPath(params.bed12, checkIfExists: true) + .ifEmpty { exit 1, "HISAT2 splice sites file not found: $alignment_splicesites" } + .into { indexing_splicesites; alignment_splicesites } +} + if ( params.transcriptome ) { tx_fasta = Channel .fromPath(params.transcriptome, checkIfExists: true) @@ -189,12 +196,7 @@ if( params.bed12 ){ .ifEmpty { exit 1, "BED12 annotation file not found: ${params.bed12}" } .into { bed_rseqc; bed_genebody_coverage } } -if( params.aligner == 'hisat2' && params.splicesites ){ - Channel - .fromPath(params.bed12, checkIfExists: true) - .ifEmpty { exit 1, "HISAT2 splice sites file not found: $alignment_splicesites" } - .into { indexing_splicesites; alignment_splicesites } -} + if( workflow.profile == 'uppmax' || workflow.profile == 'uppmax-devel' ){ if ( !params.project ) exit 1, "No UPPMAX project ID found! Use --project" } From a7536c162d29f7bc84e084bd76c970223ad930a5 Mon Sep 17 00:00:00 2001 From: drpatelh Date: Mon, 10 Jun 2019 17:28:45 +0100 Subject: [PATCH 073/139] Bug fixes --- main.nf | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/main.nf b/main.nf index 807384913..4e0ecadb5 100644 --- a/main.nf +++ b/main.nf @@ -501,7 +501,6 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ /* * PREPROCESSING - Create Salmon transcriptome index */ -println(params.transcriptome) if(params.transcriptome){ process makeSalmonIndex { label 'salmon' @@ -1096,20 +1095,19 @@ if (params.transcriptome){ file "${sample}" into salmon_multiqc_logs //MultiQC needs the sample folder to have proper names for samples script: - def strandedness = 'U' - if (forwardStranded) { - strandedness = 'SF' - } else if (reverseStranded) { - strandedness = 'SR' + def rnastrandness = params.singleEnd ? 'U' : 'IU' + if (forwardStranded && !unStranded){ + rnastrandness = params.singleEnd ? 'SF' : 'ISF' + } else if (reverseStranded && !unStranded){ + rnastrandness = params.singleEnd ? 'SR' : 'ISR' } - def end_strandedness = params.singleEnd ? strandedness : "I${strandedness}" def endedness = params.singleEnd ? "-r ${reads[0]}" : "-1 ${reads[0]} -2 ${reads[1]}" """ salmon quant --validateMappings \\ --seqBias --useVBOpt --gcBias \\ --geneMap ${gtf} \\ --threads ${task.cpus} \\ - --libType=${end_strandedness} \\ + --libType=${rnastrandness} \\ --index ${index} \\ $endedness \\ -o ${sample} @@ -1131,7 +1129,7 @@ if (params.transcriptome){ publishDir "${params.outdir}/salmon", mode: 'copy' input: - file transcript_quants from salmon_transcript_quant + file transcript_quants from salmon_transcript_quant.collect() file gtf from gtf_merge_salmon_quant.collect() output: @@ -1160,7 +1158,7 @@ if (params.transcriptome){ publishDir "${params.outdir}/salmon", mode: 'copy' input: - file gene_quants from salmon_gene_quant + file gene_quants from salmon_gene_quant.collect() file featurecounts_merged from featurecounts_merged.collect() // Use gene_id > gene_name mapping from featurecounts to make sure it matches output: From fbad8b6b040f2a42c1e7d75d020a866e0abb1b94 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 11 Jun 2019 10:46:21 -0700 Subject: [PATCH 074/139] Add empty salmon_multiqc_logs channel --- main.nf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.nf b/main.nf index 4e0ecadb5..e5043829f 100644 --- a/main.nf +++ b/main.nf @@ -174,6 +174,8 @@ if ( params.transcriptome ) { .ifEmpty { exit 1, "Transcriptome fasta file not found: ${params.transcriptome}" } } else if ( !params.transcriptome && params.aligner == 'salmon' ) { exit 1, "Transcriptome fasta file required to run Salmon not specified!" +} else { + salmon_multiqc_logs = Channel.create() } if( params.gtf ){ From 05e4eafce4794622cb03ec8257d7279bd7a87ece Mon Sep 17 00:00:00 2001 From: drpatelh Date: Thu, 13 Jun 2019 12:06:21 +0100 Subject: [PATCH 075/139] Major overhaul of Salmon requirements --- .travis.yml | 6 +- README.md | 29 +- docs/output.md | 12 +- docs/usage.md | 4 +- main.nf | 193 ++++++++------ nextflow.config | 63 +++-- parameters.settings.json | 559 ++++++++++++++++++++------------------- 7 files changed, 467 insertions(+), 399 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa9cf9470..29e8c8ce3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,9 @@ script: - nf-core lint ${TRAVIS_BUILD_DIR} # Lint the documentation - markdownlint ${TRAVIS_BUILD_DIR} -c ${TRAVIS_BUILD_DIR}/.github/markdownlint.yml - # Run, build reference genome with STAR + # Run with STAR - nextflow run ${TRAVIS_BUILD_DIR} -profile test,docker - # Run, build reference genome with HISAT2 + # Run with HISAT2 - nextflow run ${TRAVIS_BUILD_DIR} -profile test,docker --aligner hisat2 + # Run with STAR and Salmon + - nextflow run ${TRAVIS_BUILD_DIR} -profile test,docker --pseudo_aligner salmon diff --git a/README.md b/README.md index f77e286a9..37ed22fd3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ [![install with bioconda](https://img.shields.io/badge/install%20with-bioconda-brightgreen.svg)](http://bioconda.github.io/) [![Docker](https://img.shields.io/docker/automated/nfcore/rnaseq.svg)](https://hub.docker.com/r/nfcore/rnaseq/) - ### Introduction **nf-core/rnaseq** is a bioinformatics analysis pipeline used for RNA sequencing data. @@ -16,6 +15,26 @@ The workflow processes raw data from FastQ inputs ([FastQC](https://www.bioinfor The pipeline is built using [Nextflow](https://www.nextflow.io), a workflow tool to run tasks across multiple compute infrastructures in a very portable manner. It comes with docker containers making installation trivial and results highly reproducible. +## Quick Start + +i. Install [`nextflow`](https://nf-co.re/usage/installation) + +ii. Install one of [`docker`](https://docs.docker.com/engine/installation/), [`singularity`](https://www.sylabs.io/guides/3.0/user-guide/) or [`conda`](https://conda.io/miniconda.html) + +iii. Download the pipeline and test it on a minimal dataset with a single command + +```bash +nextflow run nf-core/rnaseq -profile test, +``` + +iv. Start running your own analysis! + +```bash +nextflow run nf-core/rnaseq -profile --reads '*_R{1,2}.fastq.gz' --genome GRCh37 +``` + +See [usage docs](docs/usage.md) for all of the available options when running the pipeline. + ### Documentation The nf-core/rnaseq pipeline comes with documentation about the pipeline, found in the `docs/` directory: @@ -41,3 +60,11 @@ Many thanks to other who have helped out along the way too, including (but not l [@olgabot](https://github.com/olgabot), [@jburos](https://github.com/jburos), [@drpatelh](https://github.com/drpatelh). + +## Citation + + +If you use nf-core/rnaseq for your analysis, please cite it using the following doi: [10.5281/zenodo.1400710](https://doi.org/10.5281/zenodo.1400710) + +You can cite the `nf-core` pre-print as follows: +Ewels PA, Peltzer A, Fillinger S, Alneberg JA, Patel H, Wilm A, Garcia MU, Di Tommaso P, Nahnsen S. **nf-core: Community curated bioinformatics pipelines**. *bioRxiv*. 2019. p. 610741. [doi: 10.1101/610741](https://www.biorxiv.org/content/10.1101/610741v1). diff --git a/docs/output.md b/docs/output.md index 55856ec11..55f299f47 100644 --- a/docs/output.md +++ b/docs/output.md @@ -347,14 +347,14 @@ We also use featureCounts to count overlaps with different classes of features. * Contains the file `flenDist.txt` for the fragment length distribution * `logs/` * Contains the file `salmon_quant.log` giving a record of Salmon's quantification -* `quant.genes.sf` - * *Gene*-level quantification of the sample, including gene length, effective length, TPM, and number of reads * `quant.sf` * *Transcript*-level quantification of the sample, including gene length, effective length, TPM, and number of reads -* `Sample.quant.genes.ids-only.txt` - * Subset of `quant.genes.sf`, only containing the gene id and TPM -* `Sample.quant.ids-only.txt` - * Subset of `quant.sf`, only containing the transcript id and TPM +* `quant.genes.sf` + * *Gene*-level quantification of the sample, including gene length, effective length, TPM, and number of reads +* `Sample.transcript.tpm.txt` + * Subset of `quant.sf`, only containing the transcript id and TPM values +* `Sample.gene.tpm.txt` + * Subset of `quant.genes.sf`, only containing the gene id and TPM values ## StringTie [StringTie](https://ccb.jhu.edu/software/stringtie/) assembles RNA-Seq alignments into potential transcripts. It assembles and quantitates full-length transcripts representing multiple splice variants for each gene locus. diff --git a/docs/usage.md b/docs/usage.md index 99ec28605..61c333abd 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -199,9 +199,9 @@ Note that you can also specify more than one desired value, separated by a comma ## Transcriptome mapping with Salmon -If a fasta file of cDNA sequences is provided via the `--transcriptome` parameter then the pipeline will run an additional transcriptome quantification using [Salmon](https://salmon.readthedocs.io/en/latest/salmon.html). You can use the `--skipSalmon` parameter if you wish to skip this step. +Use the `--pseudo aligner salmon` option to perform additional quantification at the transcript- and gene-level using [Salmon](https://salmon.readthedocs.io/en/latest/salmon.html). This will be run in addition to either STAR or HiSat2 and cannot be run in isolation, mainly because it allows you to obtain QC metrics with respect to the genomic alignments. By default, the pipeline will use the genome fasta and gtf file to generate the transcript fasta file, and then to build the Salmon index. You can override these parameters using the `--transcript_fasta` and `--salmon_index`, respectively. -The default Salmon parameters and a k-mer size of 31 are used to index the transcriptome. As [discussed here](https://salmon.readthedocs.io/en/latest/salmon.html#preparing-transcriptome-indices-mapping-based-mode)), a k-mer size off 31 works well with reads that are 75bp or longer. +The default Salmon parameters and a k-mer size of 31 are used to create the index. As [discussed here](https://salmon.readthedocs.io/en/latest/salmon.html#preparing-transcriptome-indices-mapping-based-mode)), a k-mer size off 31 works well with reads that are 75bp or longer. ## Alignment tool By default, the pipeline uses [STAR](https://github.com/alexdobin/STAR) to align the raw FastQ reads to the reference genome. STAR is fast and common, but requires a lot of memory to run, typically around 38GB for the Human GRCh37 reference genome. diff --git a/main.nf b/main.nf index 65dbadaf6..3d6251490 100644 --- a/main.nf +++ b/main.nf @@ -31,51 +31,53 @@ def helpMessage() { --genome Name of iGenomes reference --star_index Path to STAR index --hisat2_index Path to HiSAT2 index + --salmon_index Path to Salmon index --fasta Path to genome fasta file - --transcriptome Path to transcript fasta file required for `salmon` + --transcript_fasta Path to transcript fasta file + --splicesites Path to splice sites file for building HiSat2 index --gtf Path to GTF file --gff Path to GFF3 file --bed12 Path to bed12 file --saveReference Save the generated reference files to the results directory + Strandedness: + --forwardStranded The library is forward stranded + --reverseStranded The library is reverse stranded + --unStranded The default behaviour + Trimming: --clip_r1 [int] Instructs Trim Galore to remove bp from the 5' end of read 1 (or single-end reads) --clip_r2 [int] Instructs Trim Galore to remove bp from the 5' end of read 2 (paired-end reads only) --three_prime_clip_r1 [int] Instructs Trim Galore to remove bp from the 3' end of read 1 AFTER adapter/quality trimming has been performed --three_prime_clip_r2 [int] Instructs Trim Galore to remove bp from the 3' end of read 2 AFTER adapter/quality trimming has been performed --trim_nextseq [int] Instructs Trim Galore to apply the --nextseq=X option, to trim based on quality after removing poly-G tails - --saveTrimmed Save trimmed FastQ file intermediates - - Strandedness: - --forwardStranded The library is forward stranded - --reverseStranded The library is reverse stranded - --unStranded The default behaviour --pico Sets trimming and standedness settings for the SMARTer Stranded Total RNA-Seq Kit - Pico Input kit. Equivalent to: --forwardStranded --clip_r1 3 --three_prime_clip_r2 3 + --saveTrimmed Save trimmed FastQ file intermediates Alignment: - --aligner Specifies the aligner to use (available are: 'hisat2', 'star', 'salmon') + --aligner Specifies the aligner to use (available are: 'hisat2', 'star') + --pseudo_aligner Specifies the pseudo aligner to use (available are: 'salmon'). Runs in addition to `--aligner` --seq_center Add sequencing center in @RG line of output BAM header - --skipSalmon Skip Salmon quantification step --saveAlignedIntermediates Save the BAM files from the aligment step - not done by default Read Counting: --fc_extra_attributes Define which extra parameters should also be included in featureCounts (default: 'gene_name') --fc_group_features Define the attribute type used to group features. (default: 'gene_id') --fc_group_features_type Define the type attribute used to group features based on the group attribute (default: 'gene_biotype') - --sampleLevel Used to turn off the edgeR MDS and heatmap. Set automatically when running on fewer than 3 samples QC: --skipQC Skip all QC steps apart from MultiQC --skipFastQC Skip FastQC - --skipRseQC Skip RSeQC - --skipQualimap Skip Qualimap - --skipGenebodyCoverage Skip calculating genebody coverage --skipPreseq Skip Preseq --skipDupRadar Skip dupRadar (and Picard MarkDuplicates) + --skipQualimap Skip Qualimap + --skipRseQC Skip RSeQC + --skipGenebodyCoverage Skip calculating genebody coverage --skipEdgeR Skip edgeR MDS plot and heatmap --skipMultiQC Skip MultiQC Other options + --sampleLevel Used to turn off the edgeR MDS and heatmap. Set automatically when running on fewer than 3 samples --outdir The output directory where the results will be saved -w/--work-dir The temporary directory where intermediate data will be saved --email Set this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits @@ -92,7 +94,7 @@ def helpMessage() { * SET UP CONFIGURATION VARIABLES */ -// Show help emssage +// Show help message if (params.help){ helpMessage() exit 0 @@ -139,8 +141,11 @@ if (params.pico){ } // Validate inputs -if (params.aligner != 'star' && params.aligner != 'hisat2' && params.aligner != 'salmon'){ - exit 1, "Invalid aligner option: ${params.aligner}. Valid options: 'star', 'hisat2', 'salmon'" +if (params.aligner != 'star' && params.aligner != 'hisat2'){ + exit 1, "Invalid aligner option: ${params.aligner}. Valid options: 'star', 'hisat2'" +} +if (params.pseudo_aligner && params.pseudo_aligner != 'salmon'){ + exit 1, "Invalid pseudo aligner option: ${params.pseaudo_aligner}. Valid options: 'salmon'" } if( params.star_index && params.aligner == 'star' ){ star_index = Channel @@ -154,8 +159,8 @@ else if ( params.hisat2_index && params.aligner == 'hisat2' ){ } else if ( params.fasta ){ Channel.fromPath(params.fasta, checkIfExists: true) - .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } - .into { ch_fasta_for_star_index; ch_fasta_for_hisat_index } + .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } + .into { ch_fasta_for_star_index; ch_fasta_for_hisat_index; ch_fasta_for_salmon_transcripts } } else { exit 1, "No reference genome files specified!" @@ -168,26 +173,27 @@ if( params.aligner == 'hisat2' && params.splicesites ){ .into { indexing_splicesites; alignment_splicesites } } -if ( params.transcriptome ) { - tx_fasta = Channel - .fromPath(params.transcriptome, checkIfExists: true) - .ifEmpty { exit 1, "Transcriptome fasta file not found: ${params.transcriptome}" } -} else if ( !params.transcriptome && params.aligner == 'salmon' ) { - exit 1, "Transcriptome fasta file required to run Salmon not specified!" -} else { - salmon_multiqc_logs = Channel.create() +if ( params.pseudo_aligner == 'salmon' ) { + if ( params.salmon_index ) { + salmon_index = Channel + .fromPath(params.salmon_index, checkIfExists: true) + .ifEmpty { exit 1, "Salmon index not found: ${params.salmon_index}" } + } else if ( params.transcript_fasta ) { + ch_fasta_for_salmon_index = Channel + .fromPath(params.transcript_fasta, checkIfExists: true) + .ifEmpty { exit 1, "Transcript fasta file not found: ${params.transcript_fasta}" } + } } if( params.gtf ){ Channel .fromPath(params.gtf, checkIfExists: true) .ifEmpty { exit 1, "GTF annotation file not found: ${params.gtf}" } - .into { gtf_makeSTARindex; gtf_makeHisatSplicesites; gtf_makeHISATindex; gtf_makeBED12; - gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM; - gtf_salmon_quant; gtf_merge_salmon_quant } -} else if( params.gff ){ - gffFile = Channel.fromPath(params.gff, checkIfExists: true) - .ifEmpty { exit 1, "GFF annotation file not found: ${params.gff}" } + .into { gtf_makeSTARindex; gtf_makeHisatSplicesites; gtf_makeHISATindex; gtf_makeSalmonIndex; gtf_makeBED12; + gtf_star; gtf_dupradar; gtf_qualimap; gtf_featureCounts; gtf_stringtieFPKM; gtf_salmon; gtf_salmon_merge } +} else if ( params.gff ){ + gffFile = Channel.fromPath(params.gff, checkIfExists: true) + .ifEmpty { exit 1, "GFF annotation file not found: ${params.gff}" } } else { exit 1, "No GTF or GFF3 annotation specified!" } @@ -269,9 +275,10 @@ if(params.aligner == 'star'){ if(params.hisat2_index) summary['HISAT2 Index'] = params.hisat2_index else if(params.fasta) summary['Fasta Ref'] = params.fasta if(params.splicesites) summary['Splice Sites'] = params.splicesites -} else if(params.aligner == 'salmon') { - summary['Aligner'] = "Salmon" - summary['Transcriptome'] = params.transcriptome +} +if(params.pseudo_aligner == 'salmon') { + summary['Pseudo Aligner'] = "Salmon" + if(params.transcript_fasta) summary['Transcript Fasta'] = params.transcript_fasta } if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff @@ -370,9 +377,8 @@ if(params.gff){ file gff from gffFile output: - file "${gff.baseName}.gtf" into gtf_makeSTARindex, gtf_makeHisatSplicesites, gtf_makeHISATindex, gtf_makeBED12, - gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon_quant, - gtf_merge_salmon_quant + file "${gff.baseName}.gtf" into gtf_makeSTARindex, gtf_makeHisatSplicesites, gtf_makeHISATindex, gtf_makeSalmonIndex, gtf_makeBED12, + gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon, gtf_salmon_merge script: """ @@ -482,14 +488,14 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ log.info "[HISAT2 index build] Available memory: ${task.memory}" avail_mem = task.memory.toGiga() } - if( avail_mem > params.hisatBuildMemory ){ - log.info "[HISAT2 index build] Over ${params.hisatBuildMemory} GB available, so using splice sites and exons in HISAT2 index" + if( avail_mem > params.hisat_build_memory ){ + log.info "[HISAT2 index build] Over ${params.hisat_build_memory} GB available, so using splice sites and exons in HISAT2 index" extract_exons = "hisat2_extract_exons.py $gtf > ${gtf.baseName}.hisat2_exons.txt" ss = "--ss $indexing_splicesites" exon = "--exon ${gtf.baseName}.hisat2_exons.txt" } else { - log.info "[HISAT2 index build] Less than ${params.hisatBuildMemory} GB available, so NOT using splice sites and exons in HISAT2 index." - log.info "[HISAT2 index build] Use --hisatBuildMemory [small number] to skip this check." + log.info "[HISAT2 index build] Less than ${params.hisat_build_memory} GB available, so NOT using splice sites and exons in HISAT2 index." + log.info "[HISAT2 index build] Use --hisat_build_memory [small number] to skip this check." extract_exons = '' ss = '' exon = '' @@ -504,22 +510,41 @@ if(params.aligner == 'hisat2' && !params.hisat2_index && params.fasta){ /* * PREPROCESSING - Create Salmon transcriptome index */ -if(params.transcriptome){ +if(params.pseudo_aligner == 'salmon' && !params.salmon_index){ + if(!params.transcript_fasta) { + process transcriptsToFasta { + tag "$fasta" + publishDir path: { params.saveReference ? "${params.outdir}/reference_genome" : params.outdir }, + saveAs: { params.saveReference ? it : null }, mode: 'copy' + + input: + file fasta from ch_fasta_for_salmon_transcripts + file gtf from gtf_makeSalmonIndex + + output: + file "*.fa" into ch_fasta_for_salmon_index + + script: + """ + gffread -w transcripts.fa -g $fasta $gtf + """ + } + } process makeSalmonIndex { - label 'salmon' - tag "$transcriptome.simpleName" + label "salmon" + tag "$fasta" publishDir path: { params.saveReference ? "${params.outdir}/reference_genome" : params.outdir }, saveAs: { params.saveReference ? it : null }, mode: 'copy' input: - file transcriptome from tx_fasta + file fasta from ch_fasta_for_salmon_index output: - file 'salmon_index' into salmon_index_ch + file 'salmon_index' into salmon_index script: """ - salmon index --threads $task.cpus -t $transcriptome -i salmon_index + salmon index --threads $task.cpus -t $fasta -i salmon_index """ } } @@ -1081,7 +1106,7 @@ process merge_featureCounts { /* * STEP 11 - Transcriptome quantification with Salmon */ -if (params.transcriptome){ +if (params.pseudo_aligner == 'salmon'){ process salmon { label 'salmon' tag "$sample" @@ -1089,13 +1114,13 @@ if (params.transcriptome){ input: set sample, file(reads) from trimmed_reads_salmon - file index from salmon_index_ch.collect() - file gtf from gtf_salmon_quant.collect() + file index from salmon_index.collect() + file gtf from gtf_salmon.collect() output: - file "${sample}/${sample}.quant.ids-only.txt" into salmon_transcript_quant - file "${sample}/${sample}.quant.genes.ids-only.txt" into salmon_gene_quant - file "${sample}" into salmon_multiqc_logs //MultiQC needs the sample folder to have proper names for samples + file "${sample}/*transcript.tpm.txt" into salmon_transcript_tpm + file "${sample}/*gene.tpm.txt" into salmon_gene_tpm + file "${sample}/" into salmon_logs script: def rnastrandness = params.singleEnd ? 'U' : 'IU' @@ -1115,15 +1140,8 @@ if (params.transcriptome){ $endedness \\ -o ${sample} - # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.sf \\ - | sed "s:TPM:${sample}:" \\ - > ${sample}/${sample}.quant.ids-only.txt - - # Replace first occurence of "TPM" from output .sf file with sample ID for easy merging - csvtk cut -t -f "-Length,-EffectiveLength,-NumReads" ${sample}/quant.genes.sf \\ - | sed "s:TPM:${sample}:" \\ - > ${sample}/${sample}.quant.genes.ids-only.txt + cut -f 1,4 ${sample}/quant.sf | sed '1s/TPM/${sample}/g' > ${sample}/${sample}.transcript.tpm.txt + cut -f 1,4 ${sample}/quant.genes.sf | sed '1s/TPM/${sample}/g' > ${sample}/${sample}.gene.tpm.txt """ } @@ -1132,28 +1150,26 @@ if (params.transcriptome){ publishDir "${params.outdir}/salmon", mode: 'copy' input: - file transcript_quants from salmon_transcript_quant.collect() - file gtf from gtf_merge_salmon_quant.collect() + file tpms from salmon_transcript_tpm.collect() + file gtf from gtf_salmon_merge.collect() output: - file 'salmon_merged_transcript_tpm.csv' + file 'salmon_transcript_tpm_merged.csv' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def single = transcript_quants instanceof Path ? 1 : transcript_quants.size() - def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' + def merge = tpms instanceof Path ? 'cat' : 'csvtk join -t -f "Name"' """ - ## Merge transcript counts - ## Gene transcript_id <--> gene_name mapping awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=transcript_id ")(\\w+)' > transcript_ids.txt - awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=gene_name ")(\\w+)' > transcript_gene_names.txt - paste transcript_ids.txt transcript_gene_names.txt > transcript_ids__to__gene_names.txt - $merge $transcript_quants \\ - | csvtk join -t -f 1 transcript_ids__to__gene_names.txt - \\ + awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=gene_name ")(\\w+)' > gene_ids.txt + paste transcript_ids.txt gene_ids.txt > transcript_to_gene_mapping.txt + + $merge $tpms \\ + | csvtk join -t -f 1 transcript_to_gene_mapping.txt - \\ | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ | csvtk tab2csv \\ - > salmon_merged_transcript_tpm.csv + > salmon_transcript_tpm_merged.csv """ } process salmon_merge_gene { @@ -1161,29 +1177,30 @@ if (params.transcriptome){ publishDir "${params.outdir}/salmon", mode: 'copy' input: - file gene_quants from salmon_gene_quant.collect() - file featurecounts_merged from featurecounts_merged.collect() // Use gene_id > gene_name mapping from featurecounts to make sure it matches + file tpms from salmon_gene_tpm.collect() + file featurecounts from featurecounts_merged.collect() // Use gene_id > gene_name mapping from featurecounts to make sure it matches output: - file 'salmon_merged_gene_tpm.csv' + file 'salmon_gene_tpm_merged.csv' script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def single = gene_quants instanceof Path ? 1 : gene_quants.size() - def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Name"' + def merge = tpms instanceof Path ? 'cat' : 'csvtk join -t -f "Name"' """ ## Merge gene counts - csvtk cut -t -f 1,2 $featurecounts_merged > gene_id__to__gene_name.txt + csvtk cut -t -f 1,2 $featurecounts > gene_id_to_gene_name.txt ## Merge gene counts using gene_id to gene_name mapping from featurecounts, as - $merge $gene_quants \\ - | csvtk join -t -f 1 gene_id__to__gene_name.txt - \\ + $merge $tpms \\ + | csvtk join -t -f 1 gene_id_to_gene_name.txt - \\ | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ | cut -f '1,3-' \\ | csvtk tab2csv \\ - > salmon_merged_gene_tpm.csv + > salmon_gene_tpm_merged.csv """ } +} else { + salmon_logs = Channel.empty() } /* @@ -1276,16 +1293,16 @@ process multiqc { file multiqc_config from ch_multiqc_config.collect() file (fastqc:'fastqc/*') from fastqc_results.collect().ifEmpty([]) file ('trimgalore/*') from trimgalore_results.collect() - file ('alignment/*') from alignment_logs.collect() + file ('alignment/*') from alignment_logs.collect().ifEmpty([]) file ('rseqc/*') from rseqc_results.collect().ifEmpty([]) file ('rseqc/*') from genebody_coverage_results.collect().ifEmpty([]) file ('qualimap/*') from qualimap_results.collect().ifEmpty([]) file ('preseq/*') from preseq_results.collect().ifEmpty([]) file ('dupradar/*') from dupradar_results.collect().ifEmpty([]) - file ('featureCounts/*') from featureCounts_logs.collect() + file ('featureCounts/*') from featureCounts_logs.collect().ifEmpty([]) file ('featureCounts_biotype/*') from featureCounts_biotype.collect() - file ('stringtie/stringtie_log*') from stringtie_log.collect() - file ('salmon/*') from salmon_multiqc_logs.collect().ifEmpty([]) + file ('stringtie/stringtie_log*') from stringtie_log.collect().ifEmpty([]) + file ('salmon/*') from salmon_logs.collect().ifEmpty([]) file ('sample_correlation_results/*') from sample_correlation_results.collect().ifEmpty([]) // If the Edge-R is not run create an Empty array file ('software_versions/*') from software_versions_yaml.collect() file workflow_summary from create_workflow_summary(summary) diff --git a/nextflow.config b/nextflow.config index 5358eb860..38674a6b8 100644 --- a/nextflow.config +++ b/nextflow.config @@ -9,59 +9,70 @@ params { // Pipeline Options - project = false - aligner = 'star' + reads = "data/*{1,2}.fastq.gz" + singleEnd = false + + // References genome = false + salmon_index = false + transcript_fasta = false + splicesites = false + saveReference = false + + // Strandedness forwardStranded = false reverseStranded = false unStranded = false + + // Trimming + clip_r1 = 0 + clip_r2 = 0 + three_prime_clip_r1 = 0 + three_prime_clip_r2 = 0 + trim_nextseq = 0 + pico = false + saveTrimmed = false + + // Alignment + aligner = 'star' + pseudo_aligner = false + seq_center = false + saveAlignedIntermediates = false + + // Read Counting fc_extra_attributes = 'gene_name' fc_group_features = 'gene_id' fc_group_features_type = 'gene_biotype' - markdup_java_options = '"-Xms4000m -Xmx7g"' //Established values for markDuplicate memory consumption, see issue PR #689 (in Sarek) for details - splicesites = false - saveReference = false - saveTrimmed = false - saveAlignedIntermediates = false - skipSalmon = false - singleEnd = false - reads = "data/*{1,2}.fastq.gz" - outdir = './results' - transcriptome = false - seq_center = false + sampleLevel = false + + // QC skipQC = false skipFastQC = false - skipRseQC = false - skipQualimap = false - skipGenebodyCoverage = false skipPreseq = false skipDupRadar = false + skipQualimap = false + skipRseQC = false + skipGenebodyCoverage = false skipEdgeR = false skipMultiQC = false - // Custom trimming options - pico = false - clip_r1 = 0 - clip_r2 = 0 - three_prime_clip_r1 = 0 - three_prime_clip_r2 = 0 - trim_nextseq = 0 - // Defaults - sampleLevel = false + project = false + markdup_java_options = '"-Xms4000m -Xmx7g"' //Established values for markDuplicate memory consumption, see issue PR #689 (in Sarek) for details hisat_build_memory = 200 // Required amount of memory in GB to build HISAT2 index with splice sites subsamp_filesize_thresh = 10000000000 // Don't subsample BAMs for RSeQC gene_body_coverage if less than this - maxMultiqcEmailFileSize = 25.MB readPaths = null star_memory = false // Cluster specific param required for hebbe // Boilerplate options + outdir = './results' name = false multiqc_config = "$baseDir/assets/multiqc_config.yaml" email = false plaintext_email = false monochrome_logs = false help = false + maxMultiqcEmailFileSize = 25.MB igenomes_base = "./iGenomes" tracedir = "${params.outdir}/pipeline_info" awsqueue = false diff --git a/parameters.settings.json b/parameters.settings.json index cd9150fe3..4dc261f90 100644 --- a/parameters.settings.json +++ b/parameters.settings.json @@ -10,25 +10,6 @@ "pattern": ".*\\*.*", "type": "string" }, - { - "name": "outdir", - "label": "Output directory", - "usage": "Set where to save the results from the pipeline", - "group": "Main options", - "default_value": "./results", - "render": "textfield", - "pattern": ".*", - "type": "string" - }, - { - "name": "pico", - "label": "Library type: Pico", - "usage": "Set trimming and standedness settings for the SMARTer Stranded Total RNA-Seq Kit - Pico Input kit.", - "group": "Main options", - "render": "check-box", - "default_value": false, - "type": "boolean" - }, { "name": "singleEnd", "label": "Single-end sequencing input", @@ -38,87 +19,6 @@ "default_value": false, "type": "boolean" }, - { - "name": "forwardStranded", - "label": "Forward stranded", - "usage": "Samples are made using a forward-stranded library type.", - "group": "Main options", - "render": "check-box", - "default_value": false, - "type": "boolean" - }, - { - "name": "reverseStranded", - "label": "Reverse stranded", - "usage": "Samples are made using a reverse-stranded library type.", - "group": "Main options", - "render": "check-box", - "default_value": false, - "type": "boolean" - }, - { - "name": "clip_r1", - "label": "Read Clipping: 5' R1", - "usage": "Instructs Trim Galore to remove bp from the 5' end of read 1 (or single-end reads).", - "group": "Read trimming", - "render": "textfield", - "pattern": "\\d*", - "type": "integer", - "default_value": 0 - }, - { - "name": "clip_r2", - "label": "Read Clipping: 5' R1", - "usage": "Instructs Trim Galore to remove bp from the 5' end of read 2 (paired-end reads only).", - "group": "Read trimming", - "render": "textfield", - "pattern": "\\d*", - "type": "integer", - "default_value": 0 - }, - { - "name": "three_prime_clip_r1", - "label": "Read Clipping: 3' R1", - "usage": "Instructs Trim Galore to remove bp from the 3' end of read 1 AFTER adapter/quality trimming has been performed.", - "group": "Read trimming", - "render": "textfield", - "pattern": "\\d*", - "type": "integer", - "default_value": 0 - }, - { - "name": "three_prime_clip_r2", - "label": "Read Clipping: 3' R2", - "usage": "Instructs Trim Galore to remove bp from the 3' end of read 2 AFTER adapter/quality trimming has been performed.", - "group": "Read trimming", - "render": "textfield", - "pattern": "\\d*", - "type": "integer", - "default_value": 0 - }, - { - "name": "trim_nextseq", - "label": "NextSeq Trimming", - "usage": "This enables the option --nextseq-trim=3'CUTOFF within Cutadapt in Trim Galore, which will set a quality cutoff (that is normally given with -q instead), but qualities of G bases are ignored. This trimming is in common for the NextSeq- and NovaSeq-platforms, where basecalls without any signal are called as high-quality G bases.", - "group": "Read trimming", - "render": "textfield", - "pattern": "\\d*", - "type": "integer", - "default_value": 0 - }, - { - "name": "aligner", - "label": "Alignment tool", - "usage": "Choose whether to align reads with STAR or HISAT2", - "type": "string", - "render": "radio-button", - "choices": [ - "star", - "hisat2" - ], - "default_value": "star", - "group": "Alignment" - }, { "name": "genome", "label": "Alignment reference iGenomes key", @@ -174,9 +74,9 @@ "default_value": "" }, { - "name": "fasta", - "label": "FASTA", - "usage": "Path to Fasta reference", + "name": "salmon_index", + "label": "Salmon index", + "usage": "Path to Salmon index", "group": "Alignment", "render": "file", "type": "string", @@ -184,9 +84,9 @@ "default_value": "" }, { - "name": "gtf", - "label": "GTF", - "usage": "Path to GTF file", + "name": "fasta", + "label": "FASTA", + "usage": "Path to Fasta reference", "group": "Alignment", "render": "file", "type": "string", @@ -194,9 +94,9 @@ "default_value": "" }, { - "name": "transcriptome", - "label": "Transcriptome Fasta", - "usage": "Path to Transcriptome Fasta file", + "name": "transcript_fasta", + "label": "FASTA", + "usage": "Path to transcript fasta file", "group": "Alignment", "render": "file", "type": "string", @@ -204,9 +104,9 @@ "default_value": "" }, { - "name": "gff", - "label": "GFF", - "usage": "Path to GFF3 file", + "name": "splicesites", + "label": "HISAT2 splice sites file", + "usage": "Optional splice-sites file for building a HISAT2 alignment index", "group": "Alignment", "render": "file", "type": "string", @@ -214,9 +114,9 @@ "default_value": "" }, { - "name": "bed12", - "label": "BED12", - "usage": "Path to bed12 file", + "name": "gtf", + "label": "GTF", + "usage": "Path to GTF file", "group": "Alignment", "render": "file", "type": "string", @@ -224,19 +124,9 @@ "default_value": "" }, { - "name": "igenomes_base", - "label": "iGenomes base path", - "usage": "Base path for iGenomes reference files", - "group": "Alignment", - "default_value": "s3://ngi-igenomes/igenomes/", - "render": "textfield", - "pattern": ".*", - "type": "string" - }, - { - "name": "splicesites", - "label": "HISAT2 splice sites file", - "usage": "Optional splice-sites file for building a HISAT2 alignment index", + "name": "gff", + "label": "GFF", + "usage": "Path to GFF3 file", "group": "Alignment", "render": "file", "type": "string", @@ -244,129 +134,152 @@ "default_value": "" }, { - "name": "fc_group_features", - "label": "FeatureCounts Group Features", - "usage": "By default, the pipeline uses `gene_name` as the default gene identifier group. Specifying `--fc_group_features` uses a different category present in your provided GTF file.", - "default_value": "gene_id", - "render": "textfield", - "pattern": ".*", - "type": "string", - "group": "FeatureCount settings" - }, - { - "name": "fc_group_features_type", - "label": "FeatureCounts Group Features Biotype", - "usage": "GTF attribute name that gives the biotype of a feature.", - "group": "FeatureCount settings", - "default_value": "gene_biotype", - "render": "textfield", - "pattern": ".*", - "type": "string" - }, - { - "name": "fc_extra_attributes", - "label": "FeatureCounts Extra Gene Names", - "usage": "By default the pipeline uses `gene_names` as additional gene identifiers apart from ENSEMBL identifiers. --fc_extra_attributes is passed to featureCounts as an --extraAttributes parameter", - "render": "textfield", - "pattern": ".*", + "name": "bed12", + "label": "BED12", + "usage": "Path to bed12 file", + "group": "Alignment", + "render": "file", "type": "string", - "default_value": "", - "group": "FeatureCount settings" - }, - { - "name": "container", - "label": "Software container", - "usage": "Dockerhub address for pipeline container", - "default_value": "nfcore/rnaseq:latest", - "render": "textfield", "pattern": ".*", - "type": "string", - "group": "Pipeline defaults" + "default_value": "" }, { - "name": "plaintext_email", - "label": "Plain text email", - "usage": "Set to receive plain-text e-mails instead of HTML formatted.", + "name": "saveReference", + "label": "Save reference genome index", + "usage": "Save the generated reference files to the results directory.", "group": "Pipeline defaults", "render": "check-box", "default_value": false, "type": "boolean" }, { - "name": "help", - "label": "Help", - "usage": "Specify to show the pipeline help text.", - "group": "Pipeline defaults", - "render": "none", + "name": "forwardStranded", + "label": "Forward stranded", + "usage": "Samples are made using a forward-stranded library type.", + "group": "Main options", + "render": "check-box", "default_value": false, "type": "boolean" }, { - "name": "sampleLevel", - "label": "sampleLevel", - "usage": "Turn off project-level analysis (edgeR MDS plot and heatmap).", - "group": "Pipeline defaults", + "name": "reverseStranded", + "label": "Reverse stranded", + "usage": "Samples are made using a reverse-stranded library type.", + "group": "Main options", "render": "check-box", "default_value": false, "type": "boolean" }, { - "name": "email", - "label": "Your email address", - "usage": "Your email address, required to receive completion notification.", - "group": "Pipeline defaults", + "name": "unStranded", + "label": "Unstranded", + "usage": "Force the library strandedness to be unstranded", + "render": "none", + "default_value": false, + "type": "boolean", + "group": "Advanced" + }, + { + "name": "clip_r1", + "label": "Read Clipping: 5' R1", + "usage": "Instructs Trim Galore to remove bp from the 5' end of read 1 (or single-end reads).", + "group": "Read trimming", "render": "textfield", - "pattern": "^$|(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)", - "type": "string", - "default_value": "" + "pattern": "\\d*", + "type": "integer", + "default_value": 0 }, { - "name": "max_cpus", - "label": "Maximum available CPUs", - "usage": "Use to set a top-limit for the default CPUs requirement for each process.", - "group": "Pipeline defaults", - "default_value": 16, + "name": "clip_r2", + "label": "Read Clipping: 5' R1", + "usage": "Instructs Trim Galore to remove bp from the 5' end of read 2 (paired-end reads only).", + "group": "Read trimming", "render": "textfield", - "type": "integer" + "pattern": "\\d*", + "type": "integer", + "default_value": 0 }, { - "name": "max_time", - "label": "Maximum available time", - "usage": "Use to set a top-limit for the default time requirement for each process.", - "group": "Pipeline defaults", - "default_value": "10d", + "name": "three_prime_clip_r1", + "label": "Read Clipping: 3' R1", + "usage": "Instructs Trim Galore to remove bp from the 3' end of read 1 AFTER adapter/quality trimming has been performed.", + "group": "Read trimming", "render": "textfield", - "pattern": "\\d+[smhd]", - "type": "string" + "pattern": "\\d*", + "type": "integer", + "default_value": 0 }, { - "name": "max_memory", - "label": "Maximum available memory", - "usage": "Use to set a top-limit for the default memory requirement for each process.", - "group": "Pipeline defaults", - "default_value": "128.GB", + "name": "three_prime_clip_r2", + "label": "Read Clipping: 3' R2", + "usage": "Instructs Trim Galore to remove bp from the 3' end of read 2 AFTER adapter/quality trimming has been performed.", + "group": "Read trimming", "render": "textfield", - "pattern": "\\d+\\.[KMGT]?B", - "type": "string" + "pattern": "\\d*", + "type": "integer", + "default_value": 0 }, { - "name": "saveTrimmed", - "label": "Save Trimmed FastQ files", - "usage": "Save the trimmed FastQ files to the results directory.", - "group": "Pipeline defaults", + "name": "trim_nextseq", + "label": "NextSeq Trimming", + "usage": "This enables the option --nextseq-trim=3'CUTOFF within Cutadapt in Trim Galore, which will set a quality cutoff (that is normally given with -q instead), but qualities of G bases are ignored. This trimming is in common for the NextSeq- and NovaSeq-platforms, where basecalls without any signal are called as high-quality G bases.", + "group": "Read trimming", + "render": "textfield", + "pattern": "\\d*", + "type": "integer", + "default_value": 0 + }, + { + "name": "pico", + "label": "Library type: Pico", + "usage": "Set trimming and standedness settings for the SMARTer Stranded Total RNA-Seq Kit - Pico Input kit.", + "group": "Main options", "render": "check-box", "default_value": false, "type": "boolean" }, { - "name": "skipSalmon", - "label": "Skip Salmon quantification step", - "usage": "Skip Salmon quantification step.", + "name": "saveTrimmed", + "label": "Save Trimmed FastQ files", + "usage": "Save the trimmed FastQ files to the results directory.", "group": "Pipeline defaults", "render": "check-box", "default_value": false, "type": "boolean" }, + { + "name": "aligner", + "label": "Alignment tool", + "usage": "Choose whether to align reads with STAR or HISAT2", + "type": "string", + "render": "radio-button", + "choices": [ + "star", + "hisat2" + ], + "default_value": "star", + "group": "Alignment" + }, + { + "name": "pseudo_aligner", + "label": "Psuedo alignment tool", + "usage": "Choose whether to pseudo align reads with Salmon", + "type": "string", + "render": "radio-button", + "choices": [ "salmon" ], + "default_value": "", + "group": "Alignment" + }, + { + "name": "seq_center", + "label": "Sequencing center", + "usage": "Add sequencing center in @RG line of output BAM header", + "group": "Advanced", + "render": "textfield", + "pattern": ".*", + "type": "string", + "default_value": "" + }, { "name": "saveAlignedIntermediates", "label": "Save Aligned Intermediate BAM files", @@ -377,24 +290,35 @@ "type": "boolean" }, { - "name": "saveReference", - "label": "Save reference genome index", - "usage": "Save the generated reference files to the results directory.", - "group": "Pipeline defaults", - "render": "check-box", - "default_value": false, - "type": "boolean" + "name": "fc_group_features", + "label": "FeatureCounts Group Features", + "usage": "By default, the pipeline uses `gene_name` as the default gene identifier group. Specifying `--fc_group_features` uses a different category present in your provided GTF file.", + "default_value": "gene_id", + "render": "textfield", + "pattern": ".*", + "type": "string", + "group": "FeatureCount settings" }, { - "name": "maxMultiqcEmailFileSize", - "label": "Maximum MultiQC email file size", - "usage": "Theshold size for MultiQC report to be attached in notification email. If file generated by pipeline exceeds the threshold, it will not be attached.", - "group": "Pipeline defaults", - "default_value": "25.MB", + "name": "fc_group_features_type", + "label": "FeatureCounts Group Features Biotype", + "usage": "GTF attribute name that gives the biotype of a feature.", + "group": "FeatureCount settings", + "default_value": "gene_biotype", "render": "textfield", - "pattern": "\\d+\\.[KMGT]?B", + "pattern": ".*", "type": "string" }, + { + "name": "fc_extra_attributes", + "label": "FeatureCounts Extra Gene Names", + "usage": "By default the pipeline uses `gene_names` as additional gene identifiers apart from ENSEMBL identifiers. --fc_extra_attributes is passed to featureCounts as an --extraAttributes parameter", + "render": "textfield", + "pattern": ".*", + "type": "string", + "default_value": "", + "group": "FeatureCount settings" + }, { "name": "skipQC", "label": "Skip all QC steps, apart from MultiQC", @@ -404,16 +328,16 @@ "group": "Skip pipeline steps" }, { - "name": "skipMultiQC", - "label": "Skip MultiQC", + "name": "skipFastQC", + "label": "Skip FastQC", "render": "check-box", "default_value": false, "type": "boolean", "group": "Skip pipeline steps" }, { - "name": "skipEdgeR", - "label": "Skip edgeR QC analysis", + "name": "skipPreseq", + "label": "Skip Preseq analysis", "render": "check-box", "default_value": false, "type": "boolean", @@ -428,16 +352,16 @@ "group": "Skip pipeline steps" }, { - "name": "skipRseQC", - "label": "Skip RSeQC steps, apart from genebody coverage", + "name": "skipQualimap", + "label": "Skip Qualimap step", "render": "check-box", "default_value": false, "type": "boolean", "group": "Skip pipeline steps" }, { - "name": "skipQualimap", - "label": "Skip Qualimap step", + "name": "skipRseQC", + "label": "Skip RSeQC steps, apart from genebody coverage", "render": "check-box", "default_value": false, "type": "boolean", @@ -452,27 +376,66 @@ "group": "Skip pipeline steps" }, { - "name": "skipPreseq", - "label": "Skip Preseq analysis", + "name": "skipEdgeR", + "label": "Skip edgeR QC analysis", "render": "check-box", "default_value": false, "type": "boolean", "group": "Skip pipeline steps" }, { - "name": "skipFastQC", - "label": "Skip FastQC", + "name": "skipMultiQC", + "label": "Skip MultiQC", "render": "check-box", "default_value": false, "type": "boolean", "group": "Skip pipeline steps" }, { - "name": "project", - "label": "Cluster project", - "usage": "For use on HPC systems where a project ID is required for job submission", - "group": "Cluster job submission", + "name": "sampleLevel", + "label": "sampleLevel", + "usage": "Turn off project-level analysis (edgeR MDS plot and heatmap).", + "group": "Pipeline defaults", + "render": "check-box", + "default_value": false, + "type": "boolean" + }, + { + "name": "outdir", + "label": "Output directory", + "usage": "Set where to save the results from the pipeline", + "group": "Main options", + "default_value": "./results", + "render": "textfield", + "pattern": ".*", + "type": "string" + }, + { + "name": "email", + "label": "Your email address", + "usage": "Your email address, required to receive completion notification.", + "group": "Pipeline defaults", + "render": "textfield", + "pattern": "^$|(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)", + "type": "string", + "default_value": "" + }, + { + "name": "maxMultiqcEmailFileSize", + "label": "Maximum MultiQC email file size", + "usage": "Theshold size for MultiQC report to be attached in notification email. If file generated by pipeline exceeds the threshold, it will not be attached.", + "group": "Pipeline defaults", + "default_value": "25.MB", "render": "textfield", + "pattern": "\\d+\\.[KMGT]?B", + "type": "string" + }, + { + "name": "name", + "label": "Custom run name", + "usage": "Helper variable. Do not set, use -name instead.", + "group": "Advanced", + "render": "none", "pattern": ".*", "type": "string", "default_value": "" @@ -497,16 +460,6 @@ "type": "string", "default_value": "" }, - { - "name": "tracedir", - "label": "Trace directory", - "usage": "Set to where the pipeline trace should be saved. Set to local path when using AWS on S3.", - "group": "AWS cloud usage", - "default_value": "./results/pipeline_info", - "render": "textfield", - "pattern": ".*", - "type": "string" - }, { "name": "subsamp_filesize_thresh", "label": "Subsample file-size threshold", @@ -517,16 +470,6 @@ "pattern": "\\d*", "type": "integer" }, - { - "name": "name", - "label": "Custom run name", - "usage": "Helper variable. Do not set, use -name instead.", - "group": "Advanced", - "render": "none", - "pattern": ".*", - "type": "string", - "default_value": "" - }, { "name": "hisat_build_memory", "label": "HISAT2 indexing: required memory for splice sites in GB", @@ -536,16 +479,6 @@ "render": "textfield", "type": "integer" }, - { - "name": "seq_center", - "label": "Sequencing center", - "usage": "Add sequencing center in @RG line of output BAM header", - "group": "Advanced", - "render": "textfield", - "pattern": ".*", - "type": "string", - "default_value": "" - }, { "name": "star_memory", "label": "STAR memory", @@ -567,13 +500,91 @@ "type": "string" }, { - "name": "unStranded", - "label": "Unstranded", - "usage": "Force the library strandedness to be unstranded", + "name": "project", + "label": "Cluster project", + "usage": "For use on HPC systems where a project ID is required for job submission", + "group": "Cluster job submission", + "render": "textfield", + "pattern": ".*", + "type": "string", + "default_value": "" + }, + { + "name": "igenomes_base", + "label": "iGenomes base path", + "usage": "Base path for iGenomes reference files", + "group": "Alignment", + "default_value": "s3://ngi-igenomes/igenomes/", + "render": "textfield", + "pattern": ".*", + "type": "string" + }, + { + "name": "container", + "label": "Software container", + "usage": "Dockerhub address for pipeline container", + "default_value": "nfcore/rnaseq:latest", + "render": "textfield", + "pattern": ".*", + "type": "string", + "group": "Pipeline defaults" + }, + { + "name": "plaintext_email", + "label": "Plain text email", + "usage": "Set to receive plain-text e-mails instead of HTML formatted.", + "group": "Pipeline defaults", + "render": "check-box", + "default_value": false, + "type": "boolean" + }, + { + "name": "help", + "label": "Help", + "usage": "Specify to show the pipeline help text.", + "group": "Pipeline defaults", "render": "none", "default_value": false, - "type": "boolean", - "group": "Advanced" + "type": "boolean" + }, + { + "name": "max_cpus", + "label": "Maximum available CPUs", + "usage": "Use to set a top-limit for the default CPUs requirement for each process.", + "group": "Pipeline defaults", + "default_value": 16, + "render": "textfield", + "type": "integer" + }, + { + "name": "max_time", + "label": "Maximum available time", + "usage": "Use to set a top-limit for the default time requirement for each process.", + "group": "Pipeline defaults", + "default_value": "10d", + "render": "textfield", + "pattern": "\\d+[smhd]", + "type": "string" + }, + { + "name": "max_memory", + "label": "Maximum available memory", + "usage": "Use to set a top-limit for the default memory requirement for each process.", + "group": "Pipeline defaults", + "default_value": "128.GB", + "render": "textfield", + "pattern": "\\d+\\.[KMGT]?B", + "type": "string" + }, + { + "name": "tracedir", + "label": "Trace directory", + "usage": "Set to where the pipeline trace should be saved. Set to local path when using AWS on S3.", + "group": "AWS cloud usage", + "default_value": "./results/pipeline_info", + "render": "textfield", + "pattern": ".*", + "type": "string" }, { "name": "readPaths", From a77dd453e8ec202492aaaf35b13a6ccd14c82d9d Mon Sep 17 00:00:00 2001 From: drpatelh Date: Thu, 13 Jun 2019 12:16:49 +0100 Subject: [PATCH 076/139] Update CHANGELOG --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 485cc549d..b222906a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,16 +4,18 @@ ### Pipeline updates -* Added Salmon as an alternative method to STAR and HiSAT2 +* Added Salmon as an supplementary method to STAR and HiSAT2 +* Added `--psuedo_aligner`, `--transcript_fasta` and `--salmon_index` parameters +* Add `Citation` and `Quick Start` section to `README.md` +* Integrate changes in `nf-core/tools v1.6` template * Add tximport and summarizedexperiment dependency [#171](https://github.com/nf-core/rnaseq/issues/171) +* Change all boolean parameters from snake_case to camelCase and vice versa for value parameters * Appointed changes because of missing output of the multiqc_plots folder [#200](https://github.com/nf-core/rnaseq/issues/200) * Add Qualimap dependency [#202](https://github.com/nf-core/rnaseq/issues/202) * Obtain edgeR + dupRadar version information [#198](https://github.com/nf-core/rnaseq/issues/198) and [#112](https://github.com/nf-core/rnaseq/issues/112) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183): added the folder "multiqc_plots" to the output. * Get MultiQC to write out the software versions in a .csv file [#185](https://github.com/nf-core/rnaseq/issues/185) -* Change all boolean parameters from snake_case to camelCase and vice versa for value parameters -* Add `--skipSalmon` parameter to skip Salmon transcriptome quantification ### Dependency Updates From 6ff4577ed07b671a720f11729558e153f1590a91 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Thu, 13 Jun 2019 10:46:52 -0400 Subject: [PATCH 077/139] Add tximport function --- assets/multiqc_config.yaml | 1 + bin/parse_gtf.py | 69 +++++++++++++++++++++++++++++++++++ bin/tximport.r | 73 ++++++++++++++++++++++++++++++++++++++ main.nf | 58 +++++------------------------- 4 files changed, 152 insertions(+), 49 deletions(-) create mode 100755 bin/parse_gtf.py create mode 100755 bin/tximport.r diff --git a/assets/multiqc_config.yaml b/assets/multiqc_config.yaml index b58dc1d5f..71db9c259 100644 --- a/assets/multiqc_config.yaml +++ b/assets/multiqc_config.yaml @@ -3,6 +3,7 @@ extra_fn_clean_exts: - _R2 - .hisat - '.sorted.markDups' + - '.sorted' report_comment: > This report has been generated by the nf-core/rnaseq diff --git a/bin/parse_gtf.py b/bin/parse_gtf.py new file mode 100755 index 000000000..5d4102bcc --- /dev/null +++ b/bin/parse_gtf.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +from __future__ import print_function +from collections import OrderedDict, defaultdict, Counter +import logging +import argparse +import glob +import os + +# Create a logger +logging.basicConfig(format='%(name)s - %(asctime)s %(levelname)s: %(message)s') +logger = logging.getLogger(__file__) +logger.setLevel(logging.INFO) + + +def read_top_transcript(salmon): + txs = set() + fn = glob.glob(os.path.join(salmon, "*", "quant.sf"))[1] + with open(fn) as inh: + for line in inh: + if line.startswith("Name"): + continue + txs.add(line.split()[0]) + if len(txs) > 100: + break + logger.info("Transcripts found in FASTA: %s" % txs) + return txs + + +def tx2gene(gtf, salmon, gene_id, extra, out): + txs = read_top_transcript(salmon) + votes = Counter() + gene_dict = defaultdict(dict) + with open(gtf) as inh: + for line in inh: + if line.startswith("#"): + continue + cols = line.split("\t") + attr_dict = OrderedDict() + for gff_item in cols[8].split(";"): + item_pair = gff_item.strip().split(" ") + if len(item_pair) > 1: + value = item_pair[1].strip().replace("\"", "") + if value in txs: + votes[item_pair[0].strip()] += 1 + + attr_dict[item_pair[0].strip()] = value + gene_dict[attr_dict[gene_id]] = attr_dict + + if not votes: + logger.warning("No attribute in GTF matching transcripts") + return None + + txid = votes.most_common(1)[0][0] + logger.info("Attributed found to be transcript: %s" % txid) + with open(out, 'w') as outh: + for gene in gene_dict: + print("%s,%s,%s" % (gene_dict[gene][txid], gene, gene_dict[gene][extra]), file=outh) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="""Get tx to gene names for tximport""") + parser.add_argument("--gtf", type=str, help="GTF file") + parser.add_argument("--salmon", type=str, help="output of salmon") + parser.add_argument("--id", type=str, help="gene id in the gtf file") + parser.add_argument("--extra", type=str, help="extra id in the gtf file") + parser.add_argument("-o", "--output", dest='output', default='tx2gene.csv', type=str, help="file with output") + + args = parser.parse_args() + tx2gene(args.gtf, args.salmon, args.id, args.extra, args.output) diff --git a/bin/tximport.r b/bin/tximport.r new file mode 100755 index 000000000..2ce8b7da0 --- /dev/null +++ b/bin/tximport.r @@ -0,0 +1,73 @@ +#!/usr/bin/env Rscript + +args = commandArgs(trailingOnly=TRUE) +if (length(args) < 2) { + stop("Usage: tximeta.r ", call.=FALSE) +} + +path = args[2] +coldata = args[1] + +tx2gene = "tx2gene.csv" +info = file.info(tx2gene) +if (info$size == 0){ + tx2gene = NULL +}else{ + rowdata = read.csv(tx2gene, header = FALSE) + colnames(rowdata) = c("tx", "gene_id", "gene_name") + tx2gene = rowdata[,1:2] +} + +fns = list.files(path, pattern = "quant.sf", recursive = T, full.names = T) +names = basename(dirname(fns)) +names(fns) = names +coldata = list.files(coldata, full.names = TRUE) +if (length(coldata)==0){ + coldata = "NULL" +} +if (file.exists(coldata)){ + coldata = read.csv(coldata) + coldata = coldata[match(names, coldata[,1]),] + coldata = cbind(files = fns, coldata) +}else{ + message("ColData not avaliable ", coldata) + coldata = data.frame(files = fns, names = names) +} + +library(SummarizedExperiment) + +# if not genome version is giving +library(tximport) + +txi = tximport(fns, type = "salmon", txOut = TRUE) +rownames(coldata) = coldata[["names"]] +rowdata = rowdata[match(rownames(txi[[1]]), rowdata[["tx"]]),] +se = SummarizedExperiment(assays = list(counts = txi[["counts"]], + abundance = txi[["abundance"]], + length = txi[["length"]]), + colData = DataFrame(coldata), + rowData = rowdata) +if (!is.null(tx2gene)){ + gi = summarizeToGene(txi, tx2gene = tx2gene) + growdata = unique(rowdata[,2:3]) + growdata = growdata[match(rownames(gi[[1]]), growdata[["gene_id"]]),] + gse = SummarizedExperiment(assays = list(counts = gi[["counts"]], + abundance = gi[["abundance"]], + length = gi[["length"]]), + colData = DataFrame(coldata), + rowData = growdata) +} + +if(exists("gse")){ + saveRDS(gse, file = "gse.rds") + write.csv(assays(se)[["abundance"]], "merged_salmon_gene_tpm.csv") + write.csv(assays(se)[["counts"]], "merged_salmon_gene_reads.csv") +} + +saveRDS(se, file = "se.rds") +write.csv(assays(se)[["abundance"]], "merged_salmon_tx_tpm.csv") +write.csv(assays(se)[["counts"]], "merged_salmon_tx_reads.csv") + +# Print sessioninfo to standard out +citation("tximeta") +sessionInfo() \ No newline at end of file diff --git a/main.nf b/main.nf index 3d6251490..a2284bf48 100644 --- a/main.nf +++ b/main.nf @@ -1118,9 +1118,7 @@ if (params.pseudo_aligner == 'salmon'){ file gtf from gtf_salmon.collect() output: - file "${sample}/*transcript.tpm.txt" into salmon_transcript_tpm - file "${sample}/*gene.tpm.txt" into salmon_gene_tpm - file "${sample}/" into salmon_logs + file "${sample}/" into salmon_logs, salmon_quant script: def rnastrandness = params.singleEnd ? 'U' : 'IU' @@ -1139,64 +1137,26 @@ if (params.pseudo_aligner == 'salmon'){ --index ${index} \\ $endedness \\ -o ${sample} - - cut -f 1,4 ${sample}/quant.sf | sed '1s/TPM/${sample}/g' > ${sample}/${sample}.transcript.tpm.txt - cut -f 1,4 ${sample}/quant.genes.sf | sed '1s/TPM/${sample}/g' > ${sample}/${sample}.gene.tpm.txt """ } - process salmon_merge_transcript { - label 'low_memory' - publishDir "${params.outdir}/salmon", mode: 'copy' - - input: - file tpms from salmon_transcript_tpm.collect() - file gtf from gtf_salmon_merge.collect() - - output: - file 'salmon_transcript_tpm_merged.csv' - - script: - //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def merge = tpms instanceof Path ? 'cat' : 'csvtk join -t -f "Name"' - """ - awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=transcript_id ")(\\w+)' > transcript_ids.txt - awk -F "\\t" '\$3 == "transcript" { print \$9 }' $gtf | grep -oP '(?<=gene_name ")(\\w+)' > gene_ids.txt - paste transcript_ids.txt gene_ids.txt > transcript_to_gene_mapping.txt - - $merge $tpms \\ - | csvtk join -t -f 1 transcript_to_gene_mapping.txt - \\ - | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ - | cut -f '1,3-' \\ - | csvtk tab2csv \\ - > salmon_transcript_tpm_merged.csv - """ - } - process salmon_merge_gene { + process salmon_merge { label 'low_memory' + tag "$sample" publishDir "${params.outdir}/salmon", mode: 'copy' input: - file tpms from salmon_gene_tpm.collect() - file featurecounts from featurecounts_merged.collect() // Use gene_id > gene_name mapping from featurecounts to make sure it matches + file gtf from gtf_salmon_merge + file ("salmon/*") from salmon_quant.collect() output: - file 'salmon_gene_tpm_merged.csv' + file "*se.rds" into rse_ch + file "*.csv" into salmon_counts_ch script: - //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def merge = tpms instanceof Path ? 'cat' : 'csvtk join -t -f "Name"' """ - ## Merge gene counts - csvtk cut -t -f 1,2 $featurecounts > gene_id_to_gene_name.txt - - ## Merge gene counts using gene_id to gene_name mapping from featurecounts, as - $merge $tpms \\ - | csvtk join -t -f 1 gene_id_to_gene_name.txt - \\ - | awk '{FS="\\t"; OFS="\\t"} { if (length(\$2) == 0) {\$1=\$1} else {\$1=\$2 " ("\$1")"}; \$2="" ; print \$0 }' \\ - | cut -f '1,3-' \\ - | csvtk tab2csv \\ - > salmon_gene_tpm_merged.csv + parse_gtf.py --gtf $gtf --salmon salmon --id ${params.fc_group_features} --extra ${params.fc_extra_attributes} -o tx2gene.csv + tximport.r NULL salmon """ } } else { From ec4915af3ed5d297ee6df5d15d0f0f54a898bf02 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Thu, 13 Jun 2019 11:31:09 -0400 Subject: [PATCH 078/139] add docs for tximport --- docs/output.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/output.md b/docs/output.md index 55f299f47..2f695dfb3 100644 --- a/docs/output.md +++ b/docs/output.md @@ -23,7 +23,8 @@ and processes data using the following steps: * [dupRadar](#dupradar) - technical / biological read duplication * [Preseq](#preseq) - library complexity * [featureCounts](#featurecounts) - gene counts, biotype counts, rRNA estimation. -* [Salmon](#salmon) - gene counts, biotype counts, rRNA estimation. +* [Salmon](#salmon) - gene counts, transcripts counts. +* [tximport](#tximport) - gene counts, transcripts counts, SummarizedExperimment object. * [StringTie](#stringtie) - FPKMs for genes and transcripts * [Sample_correlation](#Sample_correlation) - create MDS plot and sample pairwise distance heatmap / dendrogram * [MultiQC](#multiqc) - aggregate report, describing results of the whole pipeline @@ -312,6 +313,36 @@ We also use featureCounts to count overlaps with different classes of features. ## Salmon [Salmon](https://salmon.readthedocs.io/en/latest/salmon.html) from [Ocean Genomics](https://oceangenomics.com/) quasi-maps and quantifies expression relative to the transcriptome. +**Output directory: `results/salmon`** + +* `Sample/quant.sf` + * Read counts for the different transcripts. +* `Sample/quant.genes.sf` + * Read the counts for each gene provided in the reference `gtf` file +* `Sample/logs` + * Summary file with information about the process + +## tximport +[tximport](https://bioconductor.org/packages/release/bioc/html/tximport.html) imports transcript-level abundance, estimated counts and transcript lengths, and summarizes into matrices for use with downstream gene-level analysis packages. Average transcript length, weighted by sample-specific transcript abundance estimates, is provided as a matrix which can be used as an offset for different expression of gene-level counts. + +**Output directory: `results/salmon`** + +* `merged_salmon_tx_tpm.csv` + * TPM counts for the different transcripts. +* `merged_salmon_gene_tpm.csv` + * TPM counts for the different genes. +* `merged_salmon_tx_reads.csv` + * estimated counts for the different transcripts. +* `merged_salmon_gene_reads.csv` + * estimated counts for the different genes. +* `tx2gene.csv` + * CSV file with transcript and genes (`params.fc_group_features`) and extra name (`params.fc_extra_attributes`) in each column. +* `se.rds` + * RDS object to be loaded in R that contains a [SummarizedExperiment](https://bioconductor.org/packages/release/bioc/html/SummarizedExperiment.html) with the TPM (`abundance`), estimated counts (`counts`) and transcript length (`length`) in the assays slot for transcripts. +* `gse.rds` + * RDS object to be loaded in R that contains a [SummarizedExperiment](https://bioconductor.org/packages/release/bioc/html/SummarizedExperiment.html) with the TPM (`abundance`), estimated counts (`counts`) and transcript length (`length`) in the assays slot for genes. + + ### Index files **Output directory: `results/reference_genome/salmon_index`** From fd6ab71bb3237f51eb36ad14128159ac85a83317 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Thu, 13 Jun 2019 11:35:41 -0400 Subject: [PATCH 079/139] update readme and changelog --- CHANGELOG.md | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b222906a6..76884895c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Pipeline updates +* Added tximport to merge salmon output * Added Salmon as an supplementary method to STAR and HiSAT2 * Added `--psuedo_aligner`, `--transcript_fasta` and `--salmon_index` parameters * Add `Citation` and `Quick Start` section to `README.md` diff --git a/README.md b/README.md index 37ed22fd3..badbe2b6a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ **nf-core/rnaseq** is a bioinformatics analysis pipeline used for RNA sequencing data. -The workflow processes raw data from FastQ inputs ([FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/), [Trim Galore!](https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/)), aligns the reads ([STAR](https://github.com/alexdobin/STAR) or [HiSAT2](https://ccb.jhu.edu/software/hisat2/index.shtml)), generates counts relative to genes ([featureCounts](http://bioinf.wehi.edu.au/featureCounts/), [StringTie](https://ccb.jhu.edu/software/stringtie/)) or transcripts ([Salmon](https://combine-lab.github.io/salmon/)) and performs extensive quality-control on the results ([RSeQC](http://rseqc.sourceforge.net/), [Qualimap](http://qualimap.bioinfo.cipf.es/), [dupRadar](https://bioconductor.org/packages/release/bioc/html/dupRadar.html), [Preseq](http://smithlabresearch.org/software/preseq/), [edgeR](https://bioconductor.org/packages/release/bioc/html/edgeR.html), [MultiQC](http://multiqc.info/)). See the [output documentation](docs/output.md) for more details of the results. +The workflow processes raw data from FastQ inputs ([FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/), [Trim Galore!](https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/)), aligns the reads ([STAR](https://github.com/alexdobin/STAR) or [HiSAT2](https://ccb.jhu.edu/software/hisat2/index.shtml)), generates counts relative to genes ([featureCounts](http://bioinf.wehi.edu.au/featureCounts/), [StringTie](https://ccb.jhu.edu/software/stringtie/)) or transcripts ([Salmon](https://combine-lab.github.io/salmon/), [tximport](https://bioconductor.org/packages/release/bioc/html/tximport.html)) and performs extensive quality-control on the results ([RSeQC](http://rseqc.sourceforge.net/), [Qualimap](http://qualimap.bioinfo.cipf.es/), [dupRadar](https://bioconductor.org/packages/release/bioc/html/dupRadar.html), [Preseq](http://smithlabresearch.org/software/preseq/), [edgeR](https://bioconductor.org/packages/release/bioc/html/edgeR.html), [MultiQC](http://multiqc.info/)). See the [output documentation](docs/output.md) for more details of the results. The pipeline is built using [Nextflow](https://www.nextflow.io), a workflow tool to run tasks across multiple compute infrastructures in a very portable manner. It comes with docker containers making installation trivial and results highly reproducible. From c65197d264e0efd8ef881cdf85ac45e59b1e04f4 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Sat, 15 Jun 2019 09:49:12 -0400 Subject: [PATCH 080/139] fix variable and correct version to parse gtf --- bin/parse_gtf.py | 12 +++++++++--- bin/tximport.r | 7 +------ conf/test.config | 2 +- main.nf | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/bin/parse_gtf.py b/bin/parse_gtf.py index 5d4102bcc..ca69e1eb1 100755 --- a/bin/parse_gtf.py +++ b/bin/parse_gtf.py @@ -29,7 +29,7 @@ def read_top_transcript(salmon): def tx2gene(gtf, salmon, gene_id, extra, out): txs = read_top_transcript(salmon) votes = Counter() - gene_dict = defaultdict(dict) + gene_dict = defaultdict(list) with open(gtf) as inh: for line in inh: if line.startswith("#"): @@ -44,7 +44,7 @@ def tx2gene(gtf, salmon, gene_id, extra, out): votes[item_pair[0].strip()] += 1 attr_dict[item_pair[0].strip()] = value - gene_dict[attr_dict[gene_id]] = attr_dict + gene_dict[attr_dict[gene_id]].append(attr_dict) if not votes: logger.warning("No attribute in GTF matching transcripts") @@ -52,9 +52,15 @@ def tx2gene(gtf, salmon, gene_id, extra, out): txid = votes.most_common(1)[0][0] logger.info("Attributed found to be transcript: %s" % txid) + seen = set() with open(out, 'w') as outh: for gene in gene_dict: - print("%s,%s,%s" % (gene_dict[gene][txid], gene, gene_dict[gene][extra]), file=outh) + for row in gene_dict[gene]: + if txid not in row: + continue + if (gene, row[txid]) not in seen: + seen.add((gene, row[txid])) + print("%s,%s,%s" % (row[txid], gene, row[extra]), file=outh) if __name__ == "__main__": diff --git a/bin/tximport.r b/bin/tximport.r index 2ce8b7da0..9f2a7c07d 100755 --- a/bin/tximport.r +++ b/bin/tximport.r @@ -21,10 +21,7 @@ if (info$size == 0){ fns = list.files(path, pattern = "quant.sf", recursive = T, full.names = T) names = basename(dirname(fns)) names(fns) = names -coldata = list.files(coldata, full.names = TRUE) -if (length(coldata)==0){ - coldata = "NULL" -} + if (file.exists(coldata)){ coldata = read.csv(coldata) coldata = coldata[match(names, coldata[,1]),] @@ -35,8 +32,6 @@ if (file.exists(coldata)){ } library(SummarizedExperiment) - -# if not genome version is giving library(tximport) txi = tximport(fns, type = "salmon", txOut = TRUE) diff --git a/conf/test.config b/conf/test.config index 34813ee9c..25be3a5fa 100644 --- a/conf/test.config +++ b/conf/test.config @@ -23,7 +23,7 @@ params { ['SRR4238379', ['https://github.com/nf-core/test-datasets/raw/rnaseq/testdata/SRR4238379_subsamp.fastq.gz']], ] // Subsample some (but not all) files - subsampFilesizeThreshold = 4000000 + subsamp_filesize_thresh = 4000000 // Genome references fasta = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genome.fa' gtf = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genes.gtf' diff --git a/main.nf b/main.nf index a2284bf48..4e19a42a1 100644 --- a/main.nf +++ b/main.nf @@ -852,11 +852,11 @@ process rseqc { * Step 4.1 Subsample the BAM files if necessary */ bam_forSubsamp - .filter { it.size() > params.subsampFilesizeThreshold } - .map { [it, params.subsampFilesizeThreshold / it.size() ] } + .filter { it.size() > params.subsamp_filesize_thresh } + .map { [it, params.subsamp_filesize_thresh / it.size() ] } .set{ bam_forSubsampFiltered } bam_skipSubsamp - .filter { it.size() <= params.subsampFilesizeThreshold } + .filter { it.size() <= params.subsamp_filesize_thresh } .set{ bam_skipSubsampFiltered } process bam_subsample { From 4a0a3d77e312d0dd5e8158d2af89cf5493501fd8 Mon Sep 17 00:00:00 2001 From: drpatelh Date: Tue, 18 Jun 2019 12:43:56 +0100 Subject: [PATCH 081/139] Close outstanding issues and amend salmon merge --- .github/CONTRIBUTING.md | 2 +- CHANGELOG.md | 8 +- assets/heatmap_header.txt | 4 +- bin/edgeR_heatmap_MDS.r | 19 ++-- bin/tximport.r | 10 +- conf/base.config | 6 +- docs/images/heatmap.png | Bin 103498 -> 64795 bytes docs/images/rseqc_gene_body_coverage_plot.png | Bin 83366 -> 0 bytes docs/output.md | 45 +++------ docs/usage.md | 3 - main.nf | 88 +++--------------- nextflow.config | 2 +- parameters.settings.json | 17 ++-- 13 files changed, 64 insertions(+), 140 deletions(-) delete mode 100644 docs/images/rseqc_gene_body_coverage_plot.png diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 16dec7c2f..b443ed4e7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -32,7 +32,7 @@ Typically, pull-requests are only fully reviewed when these tests are passing, t There are typically two types of tests that run: ### Lint Tests -The nf-core has a [set of guidelines](http://nf-co.re/guidelines) which all pipelines must adhere to. +The nf-core has a [set of guidelines](https://nf-co.re/developers/guidelines) which all pipelines must adhere to. To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core lint ` command. If any failures or warnings are encountered, please follow the listed URL for more documentation. diff --git a/CHANGELOG.md b/CHANGELOG.md index 76884895c..96a4b81f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,17 @@ ### Pipeline updates +* Removed `genebody_coverage` process [#195](https://github.com/nf-core/rnaseq/issues/195) +* Implemented Pearsons correlation instead of euclidean distance [146](https://github.com/nf-core/rnaseq/issues/146) +* Add `--stringTieIgnoreGTF` parameter [#206](https://github.com/nf-core/rnaseq/issues/206) +* Resolved link to guidelines is broken [#203](https://github.com/nf-core/rnaseq/issues/203) +* Removed unnecessary `stringtie` channels for `MultiQC` * Added tximport to merge salmon output * Added Salmon as an supplementary method to STAR and HiSAT2 * Added `--psuedo_aligner`, `--transcript_fasta` and `--salmon_index` parameters * Add `Citation` and `Quick Start` section to `README.md` -* Integrate changes in `nf-core/tools v1.6` template +* Closed missing multiqc_plots in dev branch output [#200](https://github.com/nf-core/rnaseq/issues/200) +* Integrate changes in `nf-core/tools v1.6` template which resolved [#90](https://github.com/nf-core/rnaseq/issues/90) * Add tximport and summarizedexperiment dependency [#171](https://github.com/nf-core/rnaseq/issues/171) * Change all boolean parameters from snake_case to camelCase and vice versa for value parameters * Appointed changes because of missing output of the multiqc_plots folder [#200](https://github.com/nf-core/rnaseq/issues/200) diff --git a/assets/heatmap_header.txt b/assets/heatmap_header.txt index b3ab70389..7d6e2b76c 100644 --- a/assets/heatmap_header.txt +++ b/assets/heatmap_header.txt @@ -2,10 +2,10 @@ # section_name: 'edgeR: Sample Similarity' # description: "is generated from normalised gene counts through # edgeR. -# Euclidean distances between log2 normalised CPM values are then calculated and clustered." +# Pearson's correlation between log2 normalised CPM values are then calculated and clustered." # plot_type: 'heatmap' # anchor: 'ngi_rnaseq-sample_similarity' # pconfig: -# title: 'edgeR: Euclidean distances' +# title: 'edgeR: Pearsons correlation' # xlab: True # reverseColors: True diff --git a/bin/edgeR_heatmap_MDS.r b/bin/edgeR_heatmap_MDS.r index 301910713..7889772bf 100755 --- a/bin/edgeR_heatmap_MDS.r +++ b/bin/edgeR_heatmap_MDS.r @@ -67,25 +67,24 @@ write.csv(MDSxy, 'edgeR_MDS_Aplot_coordinates_mqc.csv', quote=FALSE, append=TRUE # Get the log counts per million values logcpm <- cpm(dataNorm, prior.count=2, log=TRUE) -# Calculate the euclidean distances between samples -dists = dist(t(logcpm)) - +# Calculate the Pearsons correlation between samples # Plot a heatmap of correlations -pdf('log2CPM_sample_distances_heatmap.pdf') -hmap <- heatmap.2(as.matrix(dists), - main="Sample Correlations", key.title="Distance", trace="none", +pdf('log2CPM_sample_correlation_heatmap.pdf') +hmap <- heatmap.2(as.matrix(cor(logcpm, method="pearson")), + key.title="Pearsons Correlation", trace="none", dendrogram="row", margin=c(9, 9) ) dev.off() +# Write correlation values to file +write.csv(hmap$carpet, 'log2CPM_sample_correlation_mqc.csv', quote=FALSE, append=TRUE) + # Plot the heatmap dendrogram pdf('log2CPM_sample_distances_dendrogram.pdf') -plot(hmap$rowDendrogram, main="Sample Dendrogram") +hmap <- heatmap.2(as.matrix(dist(t(logcpm)))) +plot(hmap$rowDendrogram, main="Sample Euclidean Distance Clustering") dev.off() -# Write clustered distance values to file -write.csv(hmap$carpet, 'log2CPM_sample_distances_mqc.csv', quote=FALSE, append=TRUE) - file.create("corr.done") # Printing sessioninfo to standard out diff --git a/bin/tximport.r b/bin/tximport.r index 9f2a7c07d..6aabd47fe 100755 --- a/bin/tximport.r +++ b/bin/tximport.r @@ -55,14 +55,14 @@ if (!is.null(tx2gene)){ if(exists("gse")){ saveRDS(gse, file = "gse.rds") - write.csv(assays(se)[["abundance"]], "merged_salmon_gene_tpm.csv") - write.csv(assays(se)[["counts"]], "merged_salmon_gene_reads.csv") + write.csv(assays(se)[["abundance"]], "salmon_merged_gene_tpm.csv") + write.csv(assays(se)[["counts"]], "salmon_merged_gene_counts.csv") } saveRDS(se, file = "se.rds") -write.csv(assays(se)[["abundance"]], "merged_salmon_tx_tpm.csv") -write.csv(assays(se)[["counts"]], "merged_salmon_tx_reads.csv") +write.csv(assays(se)[["abundance"]], "salmon_merged_transcript_tpm.csv") +write.csv(assays(se)[["counts"]], "salmon_merged_transcript_counts.csv") # Print sessioninfo to standard out citation("tximeta") -sessionInfo() \ No newline at end of file +sessionInfo() diff --git a/conf/base.config b/conf/base.config index 2d223a14f..dbe2c84f0 100644 --- a/conf/base.config +++ b/conf/base.config @@ -51,7 +51,11 @@ process { cpus = { check_max( 8, 'cpus' ) } memory = { check_max( 16.GB * task.attempt, 'memory' ) } } - withName: 'multiqc|get_software_versions' { + withName: 'get_software_versions' { + memory = { check_max( 2.GB * task.attempt, 'memory' ) } + cache = false + } + withName: 'multiqc' { memory = { check_max( 2.GB * task.attempt, 'memory' ) } cache = false } diff --git a/docs/images/heatmap.png b/docs/images/heatmap.png index f2b0c0ea4a2b2c88769dd6a72447d5d37d8e93fe..b0a80aa82f651ff988fb810ddb9cb039ae5b6b5f 100644 GIT binary patch literal 64795 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 DsiM?C literal 103498 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 HU620*(7|pt diff --git a/docs/output.md b/docs/output.md index 2f695dfb3..a8913c17c 100644 --- a/docs/output.md +++ b/docs/output.md @@ -16,7 +16,6 @@ and processes data using the following steps: * [RPKM saturation](#rpkm-saturation) * [Read duplication](#read-duplication) * [Inner distance](#inner-distance) - * [Gene body coverage](#gene-body-coverage) * [Read distribution](#read-distribution) * [Junction annotation](#junction-annotation) * [Qualimap](#qualimap) - RNA quality control metrics @@ -207,24 +206,6 @@ This plot will not be generated for single-end data. Very short inner distances RSeQC documentation: [inner_distance.py](http://rseqc.sourceforge.net/#inner-distance-py) -### Gene body coverage -**NB:** In nfcore/rnaseq we subsample this to 1 Million reads. This speeds up this task significantly and has no to little effect on the results. - -**Output:** - -* `Sample_rseqc.geneBodyCoverage.curves.pdf` -* `Sample_rseqc.geneBodyCoverage.r` -* `Sample_rseqc.geneBodyCoverage.txt` - -This script calculates the reads coverage across gene bodies. This makes it easy to identify 3' or 5' skew in libraries. A skew towards increased 3' coverage can happen in degraded samples prepared with poly-A selection. - -A typical set of libraries with little or no bias will look as follows: - -![Gene body coverage](images/rseqc_gene_body_coverage_plot.png) - -RSeQC documentation: [gene\_body_coverage.py](http://rseqc.sourceforge.net/#genebody-coverage-py) - - ### Read distribution **Output: `Sample_read_distribution.txt`** @@ -327,13 +308,13 @@ We also use featureCounts to count overlaps with different classes of features. **Output directory: `results/salmon`** -* `merged_salmon_tx_tpm.csv` +* `salmon_merged_transcript_tpm.csv` * TPM counts for the different transcripts. -* `merged_salmon_gene_tpm.csv` +* `salmon_merged_gene_tpm.csv` * TPM counts for the different genes. -* `merged_salmon_tx_reads.csv` +* `salmon_merged_transcript_counts.csv` * estimated counts for the different transcripts. -* `merged_salmon_gene_reads.csv` +* `salmon_merged_gene_counts.csv` * estimated counts for the different genes. * `tx2gene.csv` * CSV file with transcript and genes (`params.fc_group_features`) and extra name (`params.fc_extra_attributes`) in each column. @@ -402,7 +383,7 @@ StringTie outputs FPKM metrics for genes and transcripts as well as the transcri * This `.gtf` file contains the transcripts that are fully covered by reads. ## Sample Correlation -[edgeR](https://bioconductor.org/packages/release/bioc/html/edgeR.html) is a Bioconductor package for R used for RNA-seq data analysis. The script included in the pipeline uses edgeR to normalise read counts and create a heatmap / dendrogram showing pairwise euclidean distance (sample similarity). It also creates a 2D MDS scatter plot showing sample grouping. These help to show sample similarity and can reveal batch effects and sample groupings. +[edgeR](https://bioconductor.org/packages/release/bioc/html/edgeR.html) is a Bioconductor package for R used for RNA-seq data analysis. The script included in the pipeline uses edgeR to normalise read counts and create a heatmap showing Pearsons correlation and a dendrogram showing pairwise Euclidean distances between the samples in the experiment. It also creates a 2D MDS scatter plot showing sample grouping. These help to show sample similarity and can reveal batch effects and sample groupings. **Heatmap:** @@ -415,17 +396,17 @@ StringTie outputs FPKM metrics for genes and transcripts as well as the transcri **Output directory: `results/sample_correlation`** * `edgeR_MDS_plot.pdf` - * MDS scatter plot, showing sample similarity -* `edgeR_MDS_distance_matrix.txt` + * MDS scatter plot showing sample similarity +* `edgeR_MDS_distance_matrix.csv` * Distance matrix containing raw data from MDS analysis -* `edgeR_MDS_plot_coordinates.txt` +* `edgeR_MDS_Aplot_coordinates_mqc.csv` * Scatter plot coordinates from MDS plot, used for MultiQC report * `log2CPM_sample_distances_dendrogram.pdf` - * Dendrogram plot showing the euclidian distance between your samples -* `log2CPM_sample_distances_heatmap.pdf` - * Heatmap plot showing the euclidian distance between your samples -* `log2CPM_sample_distances.txt` - * Raw data used for heatmap and dendrogram plots. + * Dendrogram showing the Euclidean distance between your samples +* `log2CPM_sample_correlation_heatmap.pdf` + * Heatmap showing the Pearsons correlation between your samples +* `log2CPM_sample_correlation_mqc.csv` + * Raw data from Pearsons correlation heatmap, used for MultiQC report ## MultiQC [MultiQC](http://multiqc.info) is a visualisation tool that generates a single HTML report summarising all samples in your project. Most of the pipeline QC results are visualised in the report and further statistics are available in within the report data directory. diff --git a/docs/usage.md b/docs/usage.md index 61c333abd..db71e5222 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -60,8 +60,6 @@ * [Stand-alone scripts](#stand-alone-scripts) - - ## Introduction Nextflow handles job submissions on SLURM or other environments, and supervises running the jobs. Thus the Nextflow process must run until the pipeline is finished. We recommend that you put the process running in the background through `screen` / `tmux` or similar tool. Alternatively you can run nextflow within a cluster job submitted your job scheduler. @@ -315,7 +313,6 @@ The following options make this easy: * `--skipFastQC` - Skip FastQC * `--skipRseQC` - Skip RSeQC * `--skipQualimap` - Skip Qualimap -* `--skipGenebodyCoverage` - Skip calculating the genebody coverage * `--skipPreseq` - Skip Preseq * `--skipDupRadar` - Skip dupRadar (and Picard MarkDuplicates) * `--skipEdgeR` - Skip edgeR MDS plot and heatmap diff --git a/main.nf b/main.nf index 4e19a42a1..57227cbbe 100644 --- a/main.nf +++ b/main.nf @@ -57,6 +57,7 @@ def helpMessage() { Alignment: --aligner Specifies the aligner to use (available are: 'hisat2', 'star') --pseudo_aligner Specifies the pseudo aligner to use (available are: 'salmon'). Runs in addition to `--aligner` + --stringTieIgnoreGTF Perform reference-guided de novo assembly of transcripts using StringTie i.e. dont restrict to those in GTF file --seq_center Add sequencing center in @RG line of output BAM header --saveAlignedIntermediates Save the BAM files from the aligment step - not done by default @@ -72,7 +73,6 @@ def helpMessage() { --skipDupRadar Skip dupRadar (and Picard MarkDuplicates) --skipQualimap Skip Qualimap --skipRseQC Skip RSeQC - --skipGenebodyCoverage Skip calculating genebody coverage --skipEdgeR Skip edgeR MDS plot and heatmap --skipMultiQC Skip MultiQC @@ -202,7 +202,7 @@ if( params.bed12 ){ bed12 = Channel .fromPath(params.bed12, checkIfExists: true) .ifEmpty { exit 1, "BED12 annotation file not found: ${params.bed12}" } - .into { bed_rseqc; bed_genebody_coverage } + .into { bed_rseqc } } if( workflow.profile == 'uppmax' || workflow.profile == 'uppmax-devel' ){ @@ -283,6 +283,7 @@ if(params.pseudo_aligner == 'salmon') { if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff if(params.bed12) summary['BED Annotation'] = params.bed12 +if(params.stringTieIgnoreGTF) summary['StringTie Ignore GTF'] = params.stringTieIgnoreGTF summary['Save prefs'] = "Ref Genome: "+(params.saveReference ? 'Yes' : 'No')+" / Trimmed FastQ: "+(params.saveTrimmed ? 'Yes' : 'No')+" / Alignment intermediates: "+(params.saveAlignedIntermediates ? 'Yes' : 'No') summary['Max Resources'] = "$params.max_memory memory, $params.max_cpus cpus, $params.max_time time per job" if(workflow.containerEngine) summary['Container'] = "$workflow.containerEngine - $workflow.container" @@ -400,7 +401,7 @@ if(!params.bed12){ file gtf from gtf_makeBED12 output: - file "${gtf.baseName}.bed" into bed_rseqc, bed_genebody_coverage + file "${gtf.baseName}.bed" into bed_rseqc script: // This script is bundled with the pipeline, in nfcore/rnaseq/bin/ """ @@ -848,68 +849,6 @@ process rseqc { """ } -/* - * Step 4.1 Subsample the BAM files if necessary - */ -bam_forSubsamp - .filter { it.size() > params.subsamp_filesize_thresh } - .map { [it, params.subsamp_filesize_thresh / it.size() ] } - .set{ bam_forSubsampFiltered } -bam_skipSubsamp - .filter { it.size() <= params.subsamp_filesize_thresh } - .set{ bam_skipSubsampFiltered } - -process bam_subsample { - tag "${bam.baseName - '.sorted'}" - - input: - set file(bam), val(fraction) from bam_forSubsampFiltered - - output: - file "*_subsamp.bam" into bam_subsampled - - script: - """ - samtools view -s $fraction -b $bam | samtools sort -o ${bam.baseName}_subsamp.bam - """ -} - -/* - * Step 4.2 Rseqc genebody_coverage - */ -process genebody_coverage { - label 'mid_memory' - tag "${bam.baseName - '.sorted'}" - publishDir "${params.outdir}/rseqc" , mode: 'copy', - saveAs: {filename -> - if (filename.indexOf("geneBodyCoverage.curves.pdf") > 0) "geneBodyCoverage/$filename" - else if (filename.indexOf("geneBodyCoverage.r") > 0) "geneBodyCoverage/rscripts/$filename" - else if (filename.indexOf("geneBodyCoverage.txt") > 0) "geneBodyCoverage/data/$filename" - else if (filename.indexOf("log.txt") > -1) false - else filename - } - - when: - !params.skipQC && !params.skipGenebodyCoverage - - input: - file bam from bam_subsampled.concat(bam_skipSubsampFiltered) - file bed12 from bed_genebody_coverage.collect() - - output: - file "*.{txt,pdf,r}" into genebody_coverage_results - - script: - """ - samtools index $bam - geneBody_coverage.py \\ - -i $bam \\ - -o ${bam.baseName}.rseqc \\ - -r $bed12 - mv log.txt ${bam.baseName}.rseqc.log.txt - """ -} - /* * STEP 5 - preseq analysis */ @@ -1118,7 +1057,7 @@ if (params.pseudo_aligner == 'salmon'){ file gtf from gtf_salmon.collect() output: - file "${sample}/" into salmon_logs, salmon_quant + file "${sample}/" into salmon_merge, salmon_logs script: def rnastrandness = params.singleEnd ? 'U' : 'IU' @@ -1142,15 +1081,14 @@ if (params.pseudo_aligner == 'salmon'){ process salmon_merge { label 'low_memory' - tag "$sample" publishDir "${params.outdir}/salmon", mode: 'copy' input: + file ("salmon/*") from salmon_merge.collect() file gtf from gtf_salmon_merge - file ("salmon/*") from salmon_quant.collect() output: - file "*se.rds" into rse_ch + file "*se.rds" into salmon_rds_ch file "*.csv" into salmon_counts_ch script: @@ -1184,7 +1122,6 @@ process stringtieFPKM { file "${bam_stringtieFPKM.baseName}_transcripts.gtf" file "${bam_stringtieFPKM.baseName}.gene_abund.txt" file "${bam_stringtieFPKM}.cov_refs.gtf" - file ".command.log" into stringtie_log file "${bam_stringtieFPKM.baseName}_ballgown" script: @@ -1194,6 +1131,7 @@ process stringtieFPKM { } else if (reverseStranded && !unStranded){ st_direction = "--rf" } + def ignore_gtf = params.stringTieIgnoreGTF ? "" : "-e" """ stringtie $bam_stringtieFPKM \\ $st_direction \\ @@ -1202,8 +1140,8 @@ process stringtieFPKM { -G $gtf \\ -A ${bam_stringtieFPKM.baseName}.gene_abund.txt \\ -C ${bam_stringtieFPKM}.cov_refs.gtf \\ - -e \\ - -b ${bam_stringtieFPKM.baseName}_ballgown + -b ${bam_stringtieFPKM.baseName}_ballgown \\ + $ignore_gtf """ } @@ -1235,8 +1173,8 @@ process sample_correlation { edgeR_heatmap_MDS.r $input_files cat $mdsplot_header edgeR_MDS_Aplot_coordinates_mqc.csv >> tmp_file mv tmp_file edgeR_MDS_Aplot_coordinates_mqc.csv - cat $heatmap_header log2CPM_sample_distances_mqc.csv >> tmp_file - mv tmp_file log2CPM_sample_distances_mqc.csv + cat $heatmap_header log2CPM_sample_correlation_mqc.csv >> tmp_file + mv tmp_file log2CPM_sample_correlation_mqc.csv """ } @@ -1255,13 +1193,11 @@ process multiqc { file ('trimgalore/*') from trimgalore_results.collect() file ('alignment/*') from alignment_logs.collect().ifEmpty([]) file ('rseqc/*') from rseqc_results.collect().ifEmpty([]) - file ('rseqc/*') from genebody_coverage_results.collect().ifEmpty([]) file ('qualimap/*') from qualimap_results.collect().ifEmpty([]) file ('preseq/*') from preseq_results.collect().ifEmpty([]) file ('dupradar/*') from dupradar_results.collect().ifEmpty([]) file ('featureCounts/*') from featureCounts_logs.collect().ifEmpty([]) file ('featureCounts_biotype/*') from featureCounts_biotype.collect() - file ('stringtie/stringtie_log*') from stringtie_log.collect().ifEmpty([]) file ('salmon/*') from salmon_logs.collect().ifEmpty([]) file ('sample_correlation_results/*') from sample_correlation_results.collect().ifEmpty([]) // If the Edge-R is not run create an Empty array file ('software_versions/*') from software_versions_yaml.collect() diff --git a/nextflow.config b/nextflow.config index 38674a6b8..6006746f1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -36,6 +36,7 @@ params { // Alignment aligner = 'star' pseudo_aligner = false + stringTieIgnoreGTF = false seq_center = false saveAlignedIntermediates = false @@ -52,7 +53,6 @@ params { skipDupRadar = false skipQualimap = false skipRseQC = false - skipGenebodyCoverage = false skipEdgeR = false skipMultiQC = false diff --git a/parameters.settings.json b/parameters.settings.json index 4dc261f90..b973cfce4 100644 --- a/parameters.settings.json +++ b/parameters.settings.json @@ -270,6 +270,15 @@ "default_value": "", "group": "Alignment" }, + { + "name": "stringTieIgnoreGTF", + "label": "Alignment options", + "usage": "Perform reference-guided de novo assembly of transcripts using StringTie i.e. dont restrict to those in GTF file.", + "group": "Alignment", + "render": "check-box", + "default_value": false, + "type": "boolean" + }, { "name": "seq_center", "label": "Sequencing center", @@ -367,14 +376,6 @@ "type": "boolean", "group": "Skip pipeline steps" }, - { - "name": "skipGenebodyCoverage", - "label": "Skip RSeQC genebody coverage", - "render": "check-box", - "default_value": false, - "type": "boolean", - "group": "Skip pipeline steps" - }, { "name": "skipEdgeR", "label": "Skip edgeR QC analysis", From 38316eb42c5bfc74a08c82b3bb927b18135ea40f Mon Sep 17 00:00:00 2001 From: drpatelh Date: Tue, 18 Jun 2019 14:20:44 +0100 Subject: [PATCH 082/139] Update CHANGELOG --- CHANGELOG.md | 2 +- main.nf | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96a4b81f4..68645363f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Pipeline updates * Removed `genebody_coverage` process [#195](https://github.com/nf-core/rnaseq/issues/195) -* Implemented Pearsons correlation instead of euclidean distance [146](https://github.com/nf-core/rnaseq/issues/146) +* Implemented Pearsons correlation instead of euclidean distance [#146](https://github.com/nf-core/rnaseq/issues/146) * Add `--stringTieIgnoreGTF` parameter [#206](https://github.com/nf-core/rnaseq/issues/206) * Resolved link to guidelines is broken [#203](https://github.com/nf-core/rnaseq/issues/203) * Removed unnecessary `stringtie` channels for `MultiQC` diff --git a/main.nf b/main.nf index 57227cbbe..b5763a528 100644 --- a/main.nf +++ b/main.nf @@ -1035,8 +1035,7 @@ process merge_featureCounts { script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def single = input_files instanceof Path ? 1 : input_files.size() - def merge = (single == 1) ? 'cat' : 'csvtk join -t -f "Geneid,Start,Length,End,Chr,Strand,gene_name"' + def merge = input_files instanceof Path ? 'cat' : 'csvtk join -t -f "Geneid,Start,Length,End,Chr,Strand,gene_name"' """ $merge $input_files | csvtk cut -t -f "-Start,-Chr,-End,-Length,-Strand" | sed 's/Aligned.sortedByCoord.out.markDups.bam//g' > merged_gene_counts.txt """ From 7f74c7a6d3b0492a9709cede72ba6fc96dae2dc6 Mon Sep 17 00:00:00 2001 From: drpatelh Date: Tue, 18 Jun 2019 15:01:36 +0100 Subject: [PATCH 083/139] Remove subsamp_filesize_thresh parameter --- conf/test.config | 2 -- docs/usage.md | 6 ------ nextflow.config | 1 - parameters.settings.json | 10 ---------- 4 files changed, 19 deletions(-) diff --git a/conf/test.config b/conf/test.config index 25be3a5fa..dc3b68bb7 100644 --- a/conf/test.config +++ b/conf/test.config @@ -22,8 +22,6 @@ params { ['SRR4238359', ['https://github.com/nf-core/test-datasets/raw/rnaseq/testdata/SRR4238359_subsamp.fastq.gz']], ['SRR4238379', ['https://github.com/nf-core/test-datasets/raw/rnaseq/testdata/SRR4238379_subsamp.fastq.gz']], ] - // Subsample some (but not all) files - subsamp_filesize_thresh = 4000000 // Genome references fasta = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genome.fa' gtf = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genes.gtf' diff --git a/docs/usage.md b/docs/usage.md index db71e5222..99d24025d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -52,7 +52,6 @@ * [`--max_time`](#--max_time) * [`--max_cpus`](#--max_cpus) * [`--hisat_build_memory`](#--hisat_build_memory) - * [`--subsamp_filesize_thresh`](#--subsamp_filesize_thresh) * [`--sampleLevel`](#--samplelevel) * [`--plaintext_email`](#--plaintext_email) * [`--monochrome_logs`](#--monochrome_logs) @@ -417,11 +416,6 @@ The `--hisat_build_memory` option changes this threshold. By default it is `200G `--max_memory` is set to `128GB` but your genome is small enough to build using this, then you can allow the exon build to proceed by supplying `--hisat_build_memory 100GB` -### `--subsamp_filesize_thresh` -This parameter defines the threshold in BAM file size (in bytes) at which data subsampling is used prior to the RSeQC `gene_body_coverage` step. This step is done to speed up and reduce compute resources for the gene body coverage analysis . -For very large files this means, that the BAM file will be subsampled to compute the `gene_body_coverage`, for small files there will not be a subsampling step. -By default this parameter is set to `10000000000` - ten gigabytes. - ### `--sampleLevel` Used to turn of the edgeR MDS and heatmap. Set automatically when running on fewer than 3 samples. diff --git a/nextflow.config b/nextflow.config index 6006746f1..cb9fa1022 100644 --- a/nextflow.config +++ b/nextflow.config @@ -60,7 +60,6 @@ params { project = false markdup_java_options = '"-Xms4000m -Xmx7g"' //Established values for markDuplicate memory consumption, see issue PR #689 (in Sarek) for details hisat_build_memory = 200 // Required amount of memory in GB to build HISAT2 index with splice sites - subsamp_filesize_thresh = 10000000000 // Don't subsample BAMs for RSeQC gene_body_coverage if less than this readPaths = null star_memory = false // Cluster specific param required for hebbe diff --git a/parameters.settings.json b/parameters.settings.json index b973cfce4..55018d9af 100644 --- a/parameters.settings.json +++ b/parameters.settings.json @@ -461,16 +461,6 @@ "type": "string", "default_value": "" }, - { - "name": "subsamp_filesize_thresh", - "label": "Subsample file-size threshold", - "usage": "Defines the threshold in BAM file size (in bytes) at which data subsampling is used prior to the RSeQC `gene_body_coverage` step.", - "group": "Advanced", - "default_value": 10000000000, - "render": "textfield", - "pattern": "\\d*", - "type": "integer" - }, { "name": "hisat_build_memory", "label": "HISAT2 indexing: required memory for splice sites in GB", From 827a8958a2650a4a5f5d820e161c7b2afb465f67 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Fri, 21 Jun 2019 11:50:16 -0700 Subject: [PATCH 084/139] Don't extract transcripts if transcript_fasta is provided --- main.nf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.nf b/main.nf index b5763a528..f62a366c5 100644 --- a/main.nf +++ b/main.nf @@ -518,6 +518,9 @@ if(params.pseudo_aligner == 'salmon' && !params.salmon_index){ publishDir path: { params.saveReference ? "${params.outdir}/reference_genome" : params.outdir }, saveAs: { params.saveReference ? it : null }, mode: 'copy' + when: + !params.transcript_fasta + input: file fasta from ch_fasta_for_salmon_transcripts file gtf from gtf_makeSalmonIndex From 3aa02d4d924d3edfb2ade68c24100a4f731b7d00 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Fri, 21 Jun 2019 11:55:01 -0700 Subject: [PATCH 085/139] Read transcript_fasta from config genome --- main.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/main.nf b/main.nf index f62a366c5..d8c9afea2 100644 --- a/main.nf +++ b/main.nf @@ -109,6 +109,7 @@ if (params.genomes && params.genome && !params.genomes.containsKey(params.genome // Define these here - after the profiles are loaded with the iGenomes paths params.star_index = params.genome ? params.genomes[ params.genome ].star ?: false : false params.fasta = params.genome ? params.genomes[ params.genome ].fasta ?: false : false +params.transcript_fasta = params.genome ? params.genomes[ params.genome ].transcript_fasta ?: false : false params.gtf = params.genome ? params.genomes[ params.genome ].gtf ?: false : false params.gff = params.genome ? params.genomes[ params.genome ].gff ?: false : false params.bed12 = params.genome ? params.genomes[ params.genome ].bed12 ?: false : false From 7d44b79a11354ccdcb3193b40ce5dbd19df7c270 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Fri, 21 Jun 2019 11:58:20 -0700 Subject: [PATCH 086/139] fix typo in 'pseudo_aligner' --- main.nf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index d8c9afea2..01bd8f2c6 100644 --- a/main.nf +++ b/main.nf @@ -146,8 +146,9 @@ if (params.aligner != 'star' && params.aligner != 'hisat2'){ exit 1, "Invalid aligner option: ${params.aligner}. Valid options: 'star', 'hisat2'" } if (params.pseudo_aligner && params.pseudo_aligner != 'salmon'){ - exit 1, "Invalid pseudo aligner option: ${params.pseaudo_aligner}. Valid options: 'salmon'" + exit 1, "Invalid pseudo aligner option: ${params.pseudo_aligner}. Valid options: 'salmon'" } + if( params.star_index && params.aligner == 'star' ){ star_index = Channel .fromPath(params.star_index, checkIfExists: true) From fbc2f2feb242e8406982fe2954604ae2370f8f2b Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Fri, 21 Jun 2019 15:34:25 -0400 Subject: [PATCH 087/139] fix typo in object name --- bin/tximport.r | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/tximport.r b/bin/tximport.r index 6aabd47fe..a9bbe0fb9 100755 --- a/bin/tximport.r +++ b/bin/tximport.r @@ -55,8 +55,8 @@ if (!is.null(tx2gene)){ if(exists("gse")){ saveRDS(gse, file = "gse.rds") - write.csv(assays(se)[["abundance"]], "salmon_merged_gene_tpm.csv") - write.csv(assays(se)[["counts"]], "salmon_merged_gene_counts.csv") + write.csv(assays(gse)[["abundance"]], "salmon_merged_gene_tpm.csv") + write.csv(assays(gse)[["counts"]], "salmon_merged_gene_counts.csv") } saveRDS(se, file = "se.rds") From 6a349b25ad829e3ac4700e87c103b372dd3d885e Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Fri, 21 Jun 2019 13:46:18 -0700 Subject: [PATCH 088/139] Fix logic for both star index provided + salmon for fasta --- main.nf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.nf b/main.nf index 01bd8f2c6..94ed6f2de 100644 --- a/main.nf +++ b/main.nf @@ -149,6 +149,7 @@ if (params.pseudo_aligner && params.pseudo_aligner != 'salmon'){ exit 1, "Invalid pseudo aligner option: ${params.pseudo_aligner}. Valid options: 'salmon'" } + if( params.star_index && params.aligner == 'star' ){ star_index = Channel .fromPath(params.star_index, checkIfExists: true) @@ -159,6 +160,10 @@ else if ( params.hisat2_index && params.aligner == 'hisat2' ){ .fromPath("${params.hisat2_index}*", checkIfExists: true) .ifEmpty { exit 1, "HISAT2 index not found: ${params.hisat2_index}" } } +else if ( params.pseudo_aligner == 'salmon' && params.fasta) { + ch_fasta_for_salmon_transcripts = Channel.fromPath(params.fasta, checkIfExists: true) + .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } +} else if ( params.fasta ){ Channel.fromPath(params.fasta, checkIfExists: true) .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } From bceaddac2e929df22d3f8e7eed437173961b8b3d Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Fri, 21 Jun 2019 13:51:40 -0700 Subject: [PATCH 089/139] --transcriptome --> --transcript_fasta --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 94ed6f2de..cadd3aad3 100644 --- a/main.nf +++ b/main.nf @@ -167,7 +167,7 @@ else if ( params.pseudo_aligner == 'salmon' && params.fasta) { else if ( params.fasta ){ Channel.fromPath(params.fasta, checkIfExists: true) .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } - .into { ch_fasta_for_star_index; ch_fasta_for_hisat_index; ch_fasta_for_salmon_transcripts } + .into { ch_fasta_for_star_index; ch_fasta_for_hisat_index } } else { exit 1, "No reference genome files specified!" From 7effd36357b840f117db5a6d88f498c9faa02c1e Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 23 Jun 2019 14:03:14 +0200 Subject: [PATCH 090/139] Add in ReadGroups for QualiMap compatibility --- CHANGELOG.md | 1 + main.nf | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68645363f..b477a32d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * Change all boolean parameters from snake_case to camelCase and vice versa for value parameters * Appointed changes because of missing output of the multiqc_plots folder [#200](https://github.com/nf-core/rnaseq/issues/200) * Add Qualimap dependency [#202](https://github.com/nf-core/rnaseq/issues/202) +* Add SM ReadGroup info for QualiMap compatibility[#238](https://github.com/nf-core/rnaseq/issues/238) * Obtain edgeR + dupRadar version information [#198](https://github.com/nf-core/rnaseq/issues/198) and [#112](https://github.com/nf-core/rnaseq/issues/112) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183): added the folder "multiqc_plots" to the output. diff --git a/main.nf b/main.nf index cadd3aad3..7411ad415 100644 --- a/main.nf +++ b/main.nf @@ -681,7 +681,7 @@ if(params.aligner == 'star'){ prefix = reads[0].toString() - ~/(_R1)?(_trimmed)?(_val_1)?(\.fq)?(\.fastq)?(\.gz)?$/ def star_mem = task.memory ?: params.star_memory ?: false def avail_mem = star_mem ? "--limitBAMsortRAM ${star_mem.toBytes() - 100000000}" : '' - seq_center = params.seq_center ? "--outSAMattrRGline ID:$prefix 'CN:$params.seq_center'" : '' + seqCenter = params.seqCenter ? "--outSAMattrRGline ID:$prefix 'CN:$params.seqCenter' 'SM:$prefix'" : "--outSAMattrRGline ID:$prefix 'SM:$prefix'" """ STAR --genomeDir $index \\ --sjdbGTFfile $gtf \\ @@ -735,8 +735,7 @@ if(params.aligner == 'hisat2'){ script: index_base = hs2_indices[0].toString() - ~/.\d.ht2l?/ prefix = reads[0].toString() - ~/(_R1)?(_trimmed)?(_val_1)?(\.fq)?(\.fastq)?(\.gz)?$/ - seq_center = params.seq_center ? "--rg-id ${prefix} --rg CN:${params.seq_center.replaceAll('\\s','_')}" : '' - def rnastrandness = '' + seqCenter = params.seqCenter ? "--rg-id ${prefix} --rg CN:${params.seqCenter.replaceAll('\\s','_')} SM:$prefix" : "--rg-id ${prefix} --rg SM:$prefix" def rnastrandness = '' if (forwardStranded && !unStranded){ rnastrandness = params.singleEnd ? '--rna-strandness F' : '--rna-strandness FR' } else if (reverseStranded && !unStranded){ From d55179ec80199a3a0f56345fb86dd035895df5e5 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 23 Jun 2019 14:19:30 +0200 Subject: [PATCH 091/139] Fix typo --- main.nf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 7411ad415..063f2de62 100644 --- a/main.nf +++ b/main.nf @@ -735,7 +735,8 @@ if(params.aligner == 'hisat2'){ script: index_base = hs2_indices[0].toString() - ~/.\d.ht2l?/ prefix = reads[0].toString() - ~/(_R1)?(_trimmed)?(_val_1)?(\.fq)?(\.fastq)?(\.gz)?$/ - seqCenter = params.seqCenter ? "--rg-id ${prefix} --rg CN:${params.seqCenter.replaceAll('\\s','_')} SM:$prefix" : "--rg-id ${prefix} --rg SM:$prefix" def rnastrandness = '' + seqCenter = params.seqCenter ? "--rg-id ${prefix} --rg CN:${params.seqCenter.replaceAll('\\s','_')} SM:$prefix" : "--rg-id ${prefix} --rg SM:$prefix" + def rnastrandness = '' if (forwardStranded && !unStranded){ rnastrandness = params.singleEnd ? '--rna-strandness F' : '--rna-strandness FR' } else if (reverseStranded && !unStranded){ From a7417550bff26277552c64c75788b2c69a0e93f5 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 23 Jun 2019 15:09:59 +0200 Subject: [PATCH 092/139] Fix seqCenter --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 063f2de62..c749a8caa 100644 --- a/main.nf +++ b/main.nf @@ -692,7 +692,7 @@ if(params.aligner == 'star'){ --outSAMtype BAM SortedByCoordinate $avail_mem \\ --readFilesCommand zcat \\ --runDirPerm All_RWX \\ - --outFileNamePrefix $prefix $seq_center + --outFileNamePrefix $prefix $seqCenter samtools index ${prefix}Aligned.sortedByCoord.out.bam """ From b0b7be4ee170f63f15ac450d9cd031fe5f287914 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 23 Jun 2019 15:27:11 +0200 Subject: [PATCH 093/139] HISAT2 seq_center --- main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.nf b/main.nf index c749a8caa..01c0e999e 100644 --- a/main.nf +++ b/main.nf @@ -751,7 +751,7 @@ if(params.aligner == 'hisat2'){ -p ${task.cpus} \\ --met-stderr \\ --new-summary \\ - --summary-file ${prefix}.hisat2_summary.txt $seq_center \\ + --summary-file ${prefix}.hisat2_summary.txt $seqCenter \\ | samtools view -bS -F 4 -F 256 - > ${prefix}.bam """ } else { @@ -766,7 +766,7 @@ if(params.aligner == 'hisat2'){ -p ${task.cpus} \\ --met-stderr \\ --new-summary \\ - --summary-file ${prefix}.hisat2_summary.txt $seq_center \\ + --summary-file ${prefix}.hisat2_summary.txt $seqCenter \\ | samtools view -bS -F 4 -F 8 -F 256 - > ${prefix}.bam """ } From 674285eeae2ed2a99df1e1d32cf5872a8743eb70 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 04:40:33 -0700 Subject: [PATCH 094/139] Revert "Read transcript_fasta from config genome" This reverts commit 3aa02d4d924d3edfb2ade68c24100a4f731b7d00. --- main.nf | 1 - 1 file changed, 1 deletion(-) diff --git a/main.nf b/main.nf index cadd3aad3..c453a9042 100644 --- a/main.nf +++ b/main.nf @@ -109,7 +109,6 @@ if (params.genomes && params.genome && !params.genomes.containsKey(params.genome // Define these here - after the profiles are loaded with the iGenomes paths params.star_index = params.genome ? params.genomes[ params.genome ].star ?: false : false params.fasta = params.genome ? params.genomes[ params.genome ].fasta ?: false : false -params.transcript_fasta = params.genome ? params.genomes[ params.genome ].transcript_fasta ?: false : false params.gtf = params.genome ? params.genomes[ params.genome ].gtf ?: false : false params.gff = params.genome ? params.genomes[ params.genome ].gff ?: false : false params.bed12 = params.genome ? params.genomes[ params.genome ].bed12 ?: false : false From 4b5a495d15f4379224e80380de64eefe3e67fba6 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 05:25:48 -0700 Subject: [PATCH 095/139] Change logic to deal with both salmon + alignment and fasta references --- main.nf | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/main.nf b/main.nf index c453a9042..90a667465 100644 --- a/main.nf +++ b/main.nf @@ -159,10 +159,6 @@ else if ( params.hisat2_index && params.aligner == 'hisat2' ){ .fromPath("${params.hisat2_index}*", checkIfExists: true) .ifEmpty { exit 1, "HISAT2 index not found: ${params.hisat2_index}" } } -else if ( params.pseudo_aligner == 'salmon' && params.fasta) { - ch_fasta_for_salmon_transcripts = Channel.fromPath(params.fasta, checkIfExists: true) - .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } -} else if ( params.fasta ){ Channel.fromPath(params.fasta, checkIfExists: true) .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } @@ -179,6 +175,8 @@ if( params.aligner == 'hisat2' && params.splicesites ){ .into { indexing_splicesites; alignment_splicesites } } +// Separately check for whether salmon needs a genome fasta to extract +// transcripts from, or can use a transcript fasta directly if ( params.pseudo_aligner == 'salmon' ) { if ( params.salmon_index ) { salmon_index = Channel @@ -188,6 +186,11 @@ if ( params.pseudo_aligner == 'salmon' ) { ch_fasta_for_salmon_index = Channel .fromPath(params.transcript_fasta, checkIfExists: true) .ifEmpty { exit 1, "Transcript fasta file not found: ${params.transcript_fasta}" } + } else if (params.fasta && params.gtf) { + ch_fasta_for_salmon_transcripts = Channel.fromPath(params.fasta, checkIfExists: true) + .ifEmpty { exit 1, "Genome fasta file not found: ${params.fasta}" } + } else { + exit 1, "To use with `--pseudo_aligner 'salmon'`, must provide either --transcript_fasta or both --fasta and --gtf" } } From 8fb4a9d2d7d1196bcf54f3e1b8b5dbba55375abc Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 05:26:12 -0700 Subject: [PATCH 096/139] Add --gencode flag to salmon index' --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 90a667465..ab9880169 100644 --- a/main.nf +++ b/main.nf @@ -557,7 +557,7 @@ if(params.pseudo_aligner == 'salmon' && !params.salmon_index){ script: """ - salmon index --threads $task.cpus -t $fasta -i salmon_index + salmon index --threads $task.cpus -t $fasta --gencode -i salmon_index """ } } From df5d1c8fe2e46293fa1c624961d16c3d46f6edee Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 05:26:47 -0700 Subject: [PATCH 097/139] --transcriptome --> --transcript_fasta --- conf/test.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/test.config b/conf/test.config index dc3b68bb7..5a4a0980b 100644 --- a/conf/test.config +++ b/conf/test.config @@ -25,5 +25,5 @@ params { // Genome references fasta = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genome.fa' gtf = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/genes.gtf' - transcriptome = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/transcriptome.fasta' + transcript_fasta = 'https://github.com/nf-core/test-datasets/raw/rnaseq/reference/transcriptome.fasta' } From 44b7686656815f8221e964fb7518878547119bca Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 08:54:28 -0700 Subject: [PATCH 098/139] Only transfer quant.sf files for salon_merge" --- main.nf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index ab9880169..36f40e1f0 100644 --- a/main.nf +++ b/main.nf @@ -1068,7 +1068,8 @@ if (params.pseudo_aligner == 'salmon'){ file gtf from gtf_salmon.collect() output: - file "${sample}/" into salmon_merge, salmon_logs + file "${sample}/quant*.sf" into salmon_merge + file "${sample}/" salmon_logs script: def rnastrandness = params.singleEnd ? 'U' : 'IU' From a1de00884278325a6e9a300899e7a78a0a78d55e Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 09:27:28 -0700 Subject: [PATCH 099/139] Add separate step to clean featurecounts output to minimize memory needed for merging --- main.nf | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/main.nf b/main.nf index c453a9042..e043aeb7b 100644 --- a/main.nf +++ b/main.nf @@ -1007,7 +1007,7 @@ process featureCounts { file biotypes_header from ch_biotypes_header.collect() output: - file "${bam_featurecounts.baseName}_gene.featureCounts.txt" into geneCounts, featureCounts_to_merge + file "${bam_featurecounts.baseName}_gene.featureCounts.txt" into geneCounts, featureCounts_to_clean file "${bam_featurecounts.baseName}_gene.featureCounts.txt.summary" into featureCounts_logs file "${bam_featurecounts.baseName}_biotype_counts*mqc.{txt,tsv}" into featureCounts_biotype @@ -1029,10 +1029,32 @@ process featureCounts { """ } + +/* + * STEP 10 - Clean featurecounts + */ +process clean_featureCounts { + tag "${input_files[0].baseName - '.sorted'}" + + input: + file input_files from featureCounts_to_clean.collect() + + output: + file featureCounts_to_merge + + script: + """ + csvtk cut -t -f "-Start,-Chr,-End,-Length,-Strand" \\ + | sed 's/Aligned.sortedByCoord.out.markDups.bam//g' \\ + > $featureCounts_to_merge + """ +} + /* * STEP 10 - Merge featurecounts */ process merge_featureCounts { + label "mid_memory" tag "${input_files[0].baseName - '.sorted'}" publishDir "${params.outdir}/featureCounts", mode: 'copy' @@ -1046,7 +1068,7 @@ process merge_featureCounts { //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. def merge = input_files instanceof Path ? 'cat' : 'csvtk join -t -f "Geneid,Start,Length,End,Chr,Strand,gene_name"' """ - $merge $input_files | csvtk cut -t -f "-Start,-Chr,-End,-Length,-Strand" | sed 's/Aligned.sortedByCoord.out.markDups.bam//g' > merged_gene_counts.txt + $merge $input_files > merged_gene_counts.txt """ } From e60c8c27069b2c656db0b158d600b83b5c5e0ee2 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 09:38:11 -0700 Subject: [PATCH 100/139] Make salmon_merge also mid_memory --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index e043aeb7b..9e5621a22 100644 --- a/main.nf +++ b/main.nf @@ -1110,7 +1110,7 @@ if (params.pseudo_aligner == 'salmon'){ } process salmon_merge { - label 'low_memory' + label 'mid_memory' publishDir "${params.outdir}/salmon", mode: 'copy' input: From d4e29338cc4ab33bf4e013c0ab86972c06ed88dd Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 09:39:19 -0700 Subject: [PATCH 101/139] missing 'into' --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 36f40e1f0..bca42c457 100644 --- a/main.nf +++ b/main.nf @@ -1069,7 +1069,7 @@ if (params.pseudo_aligner == 'salmon'){ output: file "${sample}/quant*.sf" into salmon_merge - file "${sample}/" salmon_logs + file "${sample}/" into salmon_logs script: def rnastrandness = params.singleEnd ? 'U' : 'IU' From aaab9ad69a15afd8240ea20ed75966e47f048495 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 09:50:07 -0700 Subject: [PATCH 102/139] Get clean_featureCounts to work with test data --- main.nf | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/main.nf b/main.nf index 9e5621a22..b304a3f47 100644 --- a/main.nf +++ b/main.nf @@ -1034,19 +1034,20 @@ process featureCounts { * STEP 10 - Clean featurecounts */ process clean_featureCounts { - tag "${input_files[0].baseName - '.sorted'}" + tag "${input_file[0].baseName - '.sorted'}" input: - file input_files from featureCounts_to_clean.collect() + file input_file from featureCounts_to_clean output: - file featureCounts_to_merge + file output into featureCounts_to_merge script: + output = "${input_file}_cleaned.txt" """ - csvtk cut -t -f "-Start,-Chr,-End,-Length,-Strand" \\ + csvtk cut -t -f "-Start,-Chr,-End,-Length,-Strand" $input_file \\ | sed 's/Aligned.sortedByCoord.out.markDups.bam//g' \\ - > $featureCounts_to_merge + > $output """ } @@ -1066,7 +1067,7 @@ process merge_featureCounts { script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def merge = input_files instanceof Path ? 'cat' : 'csvtk join -t -f "Geneid,Start,Length,End,Chr,Strand,gene_name"' + def merge = input_files instanceof Path ? 'cat' : 'csvtk join -t -f "Geneid,gene_name"' """ $merge $input_files > merged_gene_counts.txt """ From 66d9274f065fae8d34b6905d0458dd1bf4f43993 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 10:09:15 -0700 Subject: [PATCH 103/139] Use all quant files into salmon_merge -- Reverts 44b7686656815f8221e964fb7518878547119bca --- main.nf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.nf b/main.nf index bca42c457..a1fd20d61 100644 --- a/main.nf +++ b/main.nf @@ -1068,8 +1068,12 @@ if (params.pseudo_aligner == 'salmon'){ file gtf from gtf_salmon.collect() output: +<<<<<<< HEAD file "${sample}/quant*.sf" into salmon_merge file "${sample}/" into salmon_logs +======= + file "${sample}/" into salmon_merge, salmon_logs +>>>>>>> parent of 44b7686... Only transfer quant.sf files for salon_merge" script: def rnastrandness = params.singleEnd ? 'U' : 'IU' From ae68f311e9cf9f84300bb6809dad6055385a4050 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 10:11:26 -0700 Subject: [PATCH 104/139] remove git cruft --- main.nf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/main.nf b/main.nf index a1fd20d61..ab9880169 100644 --- a/main.nf +++ b/main.nf @@ -1068,12 +1068,7 @@ if (params.pseudo_aligner == 'salmon'){ file gtf from gtf_salmon.collect() output: -<<<<<<< HEAD - file "${sample}/quant*.sf" into salmon_merge - file "${sample}/" into salmon_logs -======= file "${sample}/" into salmon_merge, salmon_logs ->>>>>>> parent of 44b7686... Only transfer quant.sf files for salon_merge" script: def rnastrandness = params.singleEnd ? 'U' : 'IU' From 21bf9d8ebcf6e41b61e24bf4e8f8a32ec01d2049 Mon Sep 17 00:00:00 2001 From: Lorena Pantano Date: Tue, 25 Jun 2019 13:56:21 -0400 Subject: [PATCH 105/139] fix mismatch between tx2gene and quant.sf --- bin/tximport.r | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/bin/tximport.r b/bin/tximport.r index a9bbe0fb9..3807fba0d 100755 --- a/bin/tximport.r +++ b/bin/tximport.r @@ -36,21 +36,30 @@ library(tximport) txi = tximport(fns, type = "salmon", txOut = TRUE) rownames(coldata) = coldata[["names"]] -rowdata = rowdata[match(rownames(txi[[1]]), rowdata[["tx"]]),] +extra = setdiff(rownames(txi[[1]]), as.character(rowdata[["tx"]])) +if (length(extra) > 0){ + rowdata = rbind(rowdata, + data.frame(tx=extra, + gene_id=extra, + gene_name=extra)) +} +rowdata = rowdata[match(rownames(txi[[1]]), as.character(rowdata[["tx"]])),] +rownames(rowdata) = rowdata[["tx"]] se = SummarizedExperiment(assays = list(counts = txi[["counts"]], abundance = txi[["abundance"]], length = txi[["length"]]), colData = DataFrame(coldata), rowData = rowdata) if (!is.null(tx2gene)){ - gi = summarizeToGene(txi, tx2gene = tx2gene) - growdata = unique(rowdata[,2:3]) - growdata = growdata[match(rownames(gi[[1]]), growdata[["gene_id"]]),] - gse = SummarizedExperiment(assays = list(counts = gi[["counts"]], - abundance = gi[["abundance"]], - length = gi[["length"]]), - colData = DataFrame(coldata), - rowData = growdata) + gi = summarizeToGene(txi, tx2gene = tx2gene) + growdata = unique(rowdata[,2:3]) + growdata = growdata[match(rownames(gi[[1]]), growdata[["gene_id"]]),] + rownames(growdata) = growdata[["tx"]] + gse = SummarizedExperiment(assays = list(counts = gi[["counts"]], + abundance = gi[["abundance"]], + length = gi[["length"]]), + colData = DataFrame(coldata), + rowData = growdata) } if(exists("gse")){ From 02d3ce9ee1d2880d7eb113ac17565b6cf4165b9d Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 11:42:34 -0700 Subject: [PATCH 106/139] Use paste to merge everything --- main.nf | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/main.nf b/main.nf index b304a3f47..46b610069 100644 --- a/main.nf +++ b/main.nf @@ -1040,14 +1040,19 @@ process clean_featureCounts { file input_file from featureCounts_to_clean output: - file output into featureCounts_to_merge + file counts into featureCounts_to_merge + file gene_ids into featureCounts_to_merge_ids script: - output = "${input_file}_cleaned.txt" + intermediate = 'intermediate.txt' + counts = "${input_file}_counts_only.txt" + gene_ids = "${input_file}_gene_ids.txt" """ csvtk cut -t -f "-Start,-Chr,-End,-Length,-Strand" $input_file \\ | sed 's/Aligned.sortedByCoord.out.markDups.bam//g' \\ - > $output + > $intermediate + cut -f 3 $intermediate > $counts + cut -f 1,2 $intermediate > $gene_ids """ } @@ -1061,15 +1066,16 @@ process merge_featureCounts { input: file input_files from featureCounts_to_merge.collect() + file gene_ids from featureCounts_to_merge_ids.collect() output: file 'merged_gene_counts.txt' into featurecounts_merged script: //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - def merge = input_files instanceof Path ? 'cat' : 'csvtk join -t -f "Geneid,gene_name"' + gene_ids_single = gene_ids[0] """ - $merge $input_files > merged_gene_counts.txt + paste $gene_ids_single $input_files > merged_gene_counts.txt """ } From f66abb02a983561446f4e52139b366e8616117ec Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 19:14:44 -0700 Subject: [PATCH 107/139] Use params.gencode to decide on --gencode flag Co-Authored-By: Harshil Patel --- main.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/main.nf b/main.nf index ab9880169..916074a7e 100644 --- a/main.nf +++ b/main.nf @@ -556,6 +556,7 @@ if(params.pseudo_aligner == 'salmon' && !params.salmon_index){ file 'salmon_index' into salmon_index script: + def gencode = params.gencode ? "--gencode" : "" """ salmon index --threads $task.cpus -t $fasta --gencode -i salmon_index """ From a70c42afa28f637bddbf68c2f4ee8a350cb378f5 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Tue, 25 Jun 2019 19:15:12 -0700 Subject: [PATCH 108/139] use evaluated $gencode parameter Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 916074a7e..0561cbd12 100644 --- a/main.nf +++ b/main.nf @@ -558,7 +558,7 @@ if(params.pseudo_aligner == 'salmon' && !params.salmon_index){ script: def gencode = params.gencode ? "--gencode" : "" """ - salmon index --threads $task.cpus -t $fasta --gencode -i salmon_index + salmon index --threads $task.cpus -t $fasta $gencode -i salmon_index """ } } From 3ab840fc9ad7bec01f2a4c0b98ab304d6047f78e Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 26 Jun 2019 14:38:13 -0700 Subject: [PATCH 109/139] add default value for gencode --- nextflow.config | 1 + 1 file changed, 1 insertion(+) diff --git a/nextflow.config b/nextflow.config index cb9fa1022..53dae32e6 100644 --- a/nextflow.config +++ b/nextflow.config @@ -18,6 +18,7 @@ params { transcript_fasta = false splicesites = false saveReference = false + gencode = false // Strandedness forwardStranded = false From a322ebd89e0bcfecefabc86ab111fd75dc36a24e Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 26 Jun 2019 14:38:24 -0700 Subject: [PATCH 110/139] Set params.fc_group_features_type = 'gene_type' if gencode --- main.nf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.nf b/main.nf index 0561cbd12..2a290b7d5 100644 --- a/main.nf +++ b/main.nf @@ -120,6 +120,10 @@ ch_biotypes_header = Channel.fromPath("$baseDir/assets/biotypes_header.txt", che Channel.fromPath("$baseDir/assets/where_are_my_files.txt", checkIfExists: true) .into{ch_where_trim_galore; ch_where_star; ch_where_hisat2; ch_where_hisat2_sort} +if (params.gencode) { + params.fc_group_features_type = 'gene_type' +} + // Define regular variables so that they can be overwritten clip_r1 = params.clip_r1 clip_r2 = params.clip_r2 From 1357c9c559ce519fb53bc2fdc76d47ed61ece8d8 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 26 Jun 2019 15:42:51 -0700 Subject: [PATCH 111/139] Add note about --gencode for usage" --- docs/usage.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index 99d24025d..7b1ffee02 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -25,6 +25,7 @@ * [`--saveReference`](#--savereference) * [`--saveTrimmed`](#--savetrimmed) * [`--saveAlignedIntermediates`](#--savealignedintermediates) + * [`--gencode`](#--gencode) * [Adapter Trimming](#adapter-trimming) * [`--clip_r1 [int]`](#--clip_r1-int) * [`--clip_r2 [int]`](#--clip_r2-int) @@ -271,6 +272,49 @@ flag (or set to true in your config file) to copy these files when complete. ### `--saveAlignedIntermediates` As above, by default intermediate BAM files from the alignment will not be saved. The final BAM files created after the Picard MarkDuplicates step are always saved. Set to true to also copy out BAM files from STAR / HISAT2 and sorting steps. +### `--gencode` + +tl;dr version: +If specified, then `params.fc_group_features_type` is set to `gene_type` and the `--gencode` flag is added to `salmon index` (if `--pseudo_aligner salmon` is specified). + +[GENCODE](gencodegenes.org/) gene annotations are slightly different from ENSEMBL or iGenome annotations in two ways. + +#### "Type" of gene + +First, the gene "biotype" field, e.g. `protein_coding` or `lincRNA` or `rRNA`, in the GTF file is called `gene_type`, rather than `gene_biotype` as in iGenomes. + +ENSEMBL version: +``` +8 havana transcript 70635318 70669174 . - . gene_id "ENSG00000147592"; gene_version "9"; transcript_id "ENST00000522447"; transcript_version "5"; gene_name "LACTB2"; gene_source "ensembl_havana"; gene_biotype "protein_coding"; transcript_name "LACTB2-203"; transcript_source "havana"; transcript_biotype "protein_coding"; tag "CCDS"; ccds_id "CCDS6208"; tag "basic"; transcript_support_level "2"; +``` + + +GENCODE version: + +``` +chr8 HAVANA transcript 70635318 70669174 . - . gene_id "ENSG00000147592.9"; transcript_id "ENST00000522447.5"; gene_type "protein_coding"; gene_name "LACTB2"; transcript_type "protein_coding"; transcript_name "LACTB2-203"; level 2; protein_id "ENSP00000428801.1"; transcript_support_level "2"; tag "alternative_3_UTR"; tag "basic"; tag "appris_principal_1"; tag "CCDS"; ccdsid "CCDS6208.1"; havana_gene "OTTHUMG00000164430.2"; havana_transcript "OTTHUMT00000378747.1"; +``` + + +Therefore, for `featureCounts` to correctly count the different biotypes, when GENCODE annotations are specified, the `fc_group_features_type` must be set to `gene_type`, and adding the `--gencode` flag accomplishes this. + +#### Transcript IDs in FASTA files + +Second, the transcript FASTA file contains IDs separated by vertical pipes (`|`) rather than spaces, so programs that expect to separate the IDs by spaces cannot. + +ENSEMBL version: +``` +>ENST00000522447.5 cds chromosome:GRCh38:8:70635318:70669174:-1 gene:ENSG00000147592.9 gene_biotype:protein_coding transcript_biotype:protein_coding gene_symbol:LACTB2 description:lactamase beta 2 [Source:HGNC Symbol;Acc:HGNC:18512] +``` + +GENCODE version: +``` +>ENST00000522447.5|ENSG00000147592.9|OTTHUMG00000164430.2|OTTHUMT00000378747.1|LACTB2-203|LACTB2|1034|protein_coding| +``` + +Thankfully, the Salmon pseudo-aligner has [already](https://github.com/COMBINE-lab/salmon/issues/15) taken care of this issue and simply needs a `--gencode` flag, which gets added when one specifies `--gencode` for the `nf-core/rnaseq` workflow. + + ## Adapter Trimming If specific additional trimming is required (for example, from additional tags), you can use any of the following command line parameters. These affect the command From 29369b8f4313c487fa0bb86bf1e6e8d2a98704bb Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 26 Jun 2019 15:44:03 -0700 Subject: [PATCH 112/139] Add note about --gencode for changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68645363f..5e69a6b1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183): added the folder "multiqc_plots" to the output. * Get MultiQC to write out the software versions in a .csv file [#185](https://github.com/nf-core/rnaseq/issues/185) +* Add `--gencode` option for compatibility of Salmon and featureCounts biotypes with GENCODE gene annotations [#242](https://github.com/nf-core/rnaseq/pull/242) ### Dependency Updates From 5b2de6f649759181e27de24b8c92d1af7a96f4cf Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Wed, 26 Jun 2019 18:30:02 -0700 Subject: [PATCH 113/139] no "markdups" in filename --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 46b610069..ab5063a9f 100644 --- a/main.nf +++ b/main.nf @@ -1049,7 +1049,7 @@ process clean_featureCounts { gene_ids = "${input_file}_gene_ids.txt" """ csvtk cut -t -f "-Start,-Chr,-End,-Length,-Strand" $input_file \\ - | sed 's/Aligned.sortedByCoord.out.markDups.bam//g' \\ + | sed 's/Aligned.sortedByCoord.out.bam//g' \\ > $intermediate cut -f 3 $intermediate > $counts cut -f 1,2 $intermediate > $gene_ids From 51e84b047f4dc3de02f9c95cf89e9cad745e37da Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 07:22:03 -0700 Subject: [PATCH 114/139] Update docs/usage.md Co-Authored-By: Harshil Patel --- docs/usage.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 7b1ffee02..924b5d5e6 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -274,7 +274,6 @@ As above, by default intermediate BAM files from the alignment will not be saved ### `--gencode` -tl;dr version: If specified, then `params.fc_group_features_type` is set to `gene_type` and the `--gencode` flag is added to `salmon index` (if `--pseudo_aligner salmon` is specified). [GENCODE](gencodegenes.org/) gene annotations are slightly different from ENSEMBL or iGenome annotations in two ways. From a61e16faff4bba460542228da787ca1c5b787c60 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 07:22:41 -0700 Subject: [PATCH 115/139] Use @drpatelh's description Co-Authored-By: Harshil Patel --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 924b5d5e6..529f751a5 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -274,7 +274,7 @@ As above, by default intermediate BAM files from the alignment will not be saved ### `--gencode` -If specified, then `params.fc_group_features_type` is set to `gene_type` and the `--gencode` flag is added to `salmon index` (if `--pseudo_aligner salmon` is specified). +If your `--gtf` file is in GENCODE format and you would like to run Salmon (`--pseudo_aligner salmon`) you will need to provide this parameter in order to build the Salmon index appropriately. The `params.fc_group_features_type=gene_type` will also be set as explained below.``` [GENCODE](gencodegenes.org/) gene annotations are slightly different from ENSEMBL or iGenome annotations in two ways. From 6f269a51605e4694fa8563c9679724dc2f22ccdb Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 07:25:39 -0700 Subject: [PATCH 116/139] Apply suggestions from code review Use @drpatelh's suggestions for documentation language Co-Authored-By: Harshil Patel --- docs/usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 529f751a5..7a337f5ba 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -295,11 +295,11 @@ chr8 HAVANA transcript 70635318 70669174 . - ``` -Therefore, for `featureCounts` to correctly count the different biotypes, when GENCODE annotations are specified, the `fc_group_features_type` must be set to `gene_type`, and adding the `--gencode` flag accomplishes this. +Therefore, for `featureCounts` to correctly count the different biotypes when using a GENCODE annotation the `fc_group_features_type` is automatically set to `gene_type` when the `--gencode` flag is specified. #### Transcript IDs in FASTA files -Second, the transcript FASTA file contains IDs separated by vertical pipes (`|`) rather than spaces, so programs that expect to separate the IDs by spaces cannot. +The transcript IDs in GENCODE fasta files are separated by vertical pipes (`|`) rather than spaces. ENSEMBL version: ``` @@ -311,7 +311,7 @@ GENCODE version: >ENST00000522447.5|ENSG00000147592.9|OTTHUMG00000164430.2|OTTHUMT00000378747.1|LACTB2-203|LACTB2|1034|protein_coding| ``` -Thankfully, the Salmon pseudo-aligner has [already](https://github.com/COMBINE-lab/salmon/issues/15) taken care of this issue and simply needs a `--gencode` flag, which gets added when one specifies `--gencode` for the `nf-core/rnaseq` workflow. +This [issue](https://github.com/COMBINE-lab/salmon/issues/15) can be overcome by specifying the `--gencode` flag when building the Salmon index. ## Adapter Trimming From b544095a2d718dabc54d99b14948a8b3c667c773 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 07:30:24 -0700 Subject: [PATCH 117/139] Remove reference to PR for changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e69a6b1a..98f5a43db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183): added the folder "multiqc_plots" to the output. * Get MultiQC to write out the software versions in a .csv file [#185](https://github.com/nf-core/rnaseq/issues/185) -* Add `--gencode` option for compatibility of Salmon and featureCounts biotypes with GENCODE gene annotations [#242](https://github.com/nf-core/rnaseq/pull/242) +* Add `--gencode` option for compatibility of Salmon and featureCounts biotypes with GENCODE gene annotations ### Dependency Updates From bbd8ba17654ef4b5f8156688a816ce59609557ee Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 07:32:51 -0700 Subject: [PATCH 118/139] evaluate params.fc_group_features_type within featureCounts process --- main.nf | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/main.nf b/main.nf index 2a290b7d5..e5d30f9fa 100644 --- a/main.nf +++ b/main.nf @@ -120,10 +120,6 @@ ch_biotypes_header = Channel.fromPath("$baseDir/assets/biotypes_header.txt", che Channel.fromPath("$baseDir/assets/where_are_my_files.txt", checkIfExists: true) .into{ch_where_trim_galore; ch_where_star; ch_where_hisat2; ch_where_hisat2_sort} -if (params.gencode) { - params.fc_group_features_type = 'gene_type' -} - // Define regular variables so that they can be overwritten clip_r1 = params.clip_r1 clip_r2 = params.clip_r2 @@ -1021,6 +1017,7 @@ process featureCounts { script: def featureCounts_direction = 0 + def biotype = params.gencode ? 'gene_type' : params.fc_group_features_type def extraAttributes = params.fc_extra_attributes ? "--extraAttributes ${params.fc_extra_attributes}" : '' if (forwardStranded && !unStranded) { featureCounts_direction = 1 @@ -1031,7 +1028,7 @@ process featureCounts { sample_name = bam_featurecounts.baseName - 'Aligned.sortedByCoord.out' """ featureCounts -a $gtf -g ${params.fc_group_features} -o ${bam_featurecounts.baseName}_gene.featureCounts.txt $extraAttributes -p -s $featureCounts_direction $bam_featurecounts - featureCounts -a $gtf -g ${params.fc_group_features_type} -o ${bam_featurecounts.baseName}_biotype.featureCounts.txt -p -s $featureCounts_direction $bam_featurecounts + featureCounts -a $gtf -g ${biotype} -o ${bam_featurecounts.baseName}_biotype.featureCounts.txt -p -s $featureCounts_direction $bam_featurecounts cut -f 1,7 ${bam_featurecounts.baseName}_biotype.featureCounts.txt | tail -n +3 | cat $biotypes_header - >> ${bam_featurecounts.baseName}_biotype_counts_mqc.txt mqc_features_stat.py ${bam_featurecounts.baseName}_biotype_counts_mqc.txt -s $sample_name -f rRNA -o ${bam_featurecounts.baseName}_biotype_counts_gs_mqc.tsv """ From 7bc52ec7998450cb8dd1984b9eff664f9aaf8858 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 07:44:26 -0700 Subject: [PATCH 119/139] Use unix-fu to merge featurecounts --- main.nf | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/main.nf b/main.nf index ab5063a9f..30158d1ca 100644 --- a/main.nf +++ b/main.nf @@ -1007,7 +1007,7 @@ process featureCounts { file biotypes_header from ch_biotypes_header.collect() output: - file "${bam_featurecounts.baseName}_gene.featureCounts.txt" into geneCounts, featureCounts_to_clean + file "${bam_featurecounts.baseName}_gene.featureCounts.txt" into geneCounts, featureCounts_to_merge file "${bam_featurecounts.baseName}_gene.featureCounts.txt.summary" into featureCounts_logs file "${bam_featurecounts.baseName}_biotype_counts*mqc.{txt,tsv}" into featureCounts_biotype @@ -1030,31 +1030,6 @@ process featureCounts { } -/* - * STEP 10 - Clean featurecounts - */ -process clean_featureCounts { - tag "${input_file[0].baseName - '.sorted'}" - - input: - file input_file from featureCounts_to_clean - - output: - file counts into featureCounts_to_merge - file gene_ids into featureCounts_to_merge_ids - - script: - intermediate = 'intermediate.txt' - counts = "${input_file}_counts_only.txt" - gene_ids = "${input_file}_gene_ids.txt" - """ - csvtk cut -t -f "-Start,-Chr,-End,-Length,-Strand" $input_file \\ - | sed 's/Aligned.sortedByCoord.out.bam//g' \\ - > $intermediate - cut -f 3 $intermediate > $counts - cut -f 1,2 $intermediate > $gene_ids - """ -} /* * STEP 10 - Merge featurecounts @@ -1066,16 +1041,15 @@ process merge_featureCounts { input: file input_files from featureCounts_to_merge.collect() - file gene_ids from featureCounts_to_merge_ids.collect() output: file 'merged_gene_counts.txt' into featurecounts_merged script: - //if we only have 1 file, just use cat and pipe output to csvtk. Else join all files first, and then remove unwanted column names. - gene_ids_single = gene_ids[0] + gene_ids = "<(cut -f1,2 ${input_files[0]})" + counts = input_files.collect{filename -> "<(cut -f3 ${filename})"}.join(" ") """ - paste $gene_ids_single $input_files > merged_gene_counts.txt + paste $gene_ids $counts > merged_gene_counts.txt """ } From f2a135e9416f8081e99a0fe35fc41ac5b5c7bf87 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 08:15:12 -0700 Subject: [PATCH 120/139] Wrap biotype variable in braces Co-Authored-By: Harshil Patel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index e5d30f9fa..4ee41ef3a 100644 --- a/main.nf +++ b/main.nf @@ -1028,7 +1028,7 @@ process featureCounts { sample_name = bam_featurecounts.baseName - 'Aligned.sortedByCoord.out' """ featureCounts -a $gtf -g ${params.fc_group_features} -o ${bam_featurecounts.baseName}_gene.featureCounts.txt $extraAttributes -p -s $featureCounts_direction $bam_featurecounts - featureCounts -a $gtf -g ${biotype} -o ${bam_featurecounts.baseName}_biotype.featureCounts.txt -p -s $featureCounts_direction $bam_featurecounts + featureCounts -a $gtf -g $biotype -o ${bam_featurecounts.baseName}_biotype.featureCounts.txt -p -s $featureCounts_direction $bam_featurecounts cut -f 1,7 ${bam_featurecounts.baseName}_biotype.featureCounts.txt | tail -n +3 | cat $biotypes_header - >> ${bam_featurecounts.baseName}_biotype_counts_mqc.txt mqc_features_stat.py ${bam_featurecounts.baseName}_biotype_counts_mqc.txt -s $sample_name -f rRNA -o ${bam_featurecounts.baseName}_biotype_counts_gs_mqc.tsv """ From 3ff8ed78a2b4cd4bc99c0c7ffaca9d5e8b75c8ed Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 08:42:58 -0700 Subject: [PATCH 121/139] use 'bash' for syntax of fasta/gtf to fix markdownlint --- docs/usage.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 7a337f5ba..d2d9bb738 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -283,14 +283,15 @@ If your `--gtf` file is in GENCODE format and you would like to run Salmon (`--p First, the gene "biotype" field, e.g. `protein_coding` or `lincRNA` or `rRNA`, in the GTF file is called `gene_type`, rather than `gene_biotype` as in iGenomes. ENSEMBL version: -``` + +```bash 8 havana transcript 70635318 70669174 . - . gene_id "ENSG00000147592"; gene_version "9"; transcript_id "ENST00000522447"; transcript_version "5"; gene_name "LACTB2"; gene_source "ensembl_havana"; gene_biotype "protein_coding"; transcript_name "LACTB2-203"; transcript_source "havana"; transcript_biotype "protein_coding"; tag "CCDS"; ccds_id "CCDS6208"; tag "basic"; transcript_support_level "2"; ``` GENCODE version: -``` +```bash chr8 HAVANA transcript 70635318 70669174 . - . gene_id "ENSG00000147592.9"; transcript_id "ENST00000522447.5"; gene_type "protein_coding"; gene_name "LACTB2"; transcript_type "protein_coding"; transcript_name "LACTB2-203"; level 2; protein_id "ENSP00000428801.1"; transcript_support_level "2"; tag "alternative_3_UTR"; tag "basic"; tag "appris_principal_1"; tag "CCDS"; ccdsid "CCDS6208.1"; havana_gene "OTTHUMG00000164430.2"; havana_transcript "OTTHUMT00000378747.1"; ``` @@ -302,12 +303,14 @@ Therefore, for `featureCounts` to correctly count the different biotypes when us The transcript IDs in GENCODE fasta files are separated by vertical pipes (`|`) rather than spaces. ENSEMBL version: -``` + +```bash >ENST00000522447.5 cds chromosome:GRCh38:8:70635318:70669174:-1 gene:ENSG00000147592.9 gene_biotype:protein_coding transcript_biotype:protein_coding gene_symbol:LACTB2 description:lactamase beta 2 [Source:HGNC Symbol;Acc:HGNC:18512] ``` GENCODE version: -``` + +```bash >ENST00000522447.5|ENSG00000147592.9|OTTHUMG00000164430.2|OTTHUMT00000378747.1|LACTB2-203|LACTB2|1034|protein_coding| ``` From 8c4ad54a7b836bce70c5a0ac0f6621632d292a80 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 08:48:37 -0700 Subject: [PATCH 122/139] Remove first line of featurecounts files --- main.nf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main.nf b/main.nf index 30158d1ca..b03e47203 100644 --- a/main.nf +++ b/main.nf @@ -1046,8 +1046,10 @@ process merge_featureCounts { file 'merged_gene_counts.txt' into featurecounts_merged script: - gene_ids = "<(cut -f1,2 ${input_files[0]})" - counts = input_files.collect{filename -> "<(cut -f3 ${filename})"}.join(" ") + gene_ids = "<(tail -n +2 ${input_files[0]} | cut -f1,2 )" + counts = input_files.collect{filename -> + // Remove first line and take third column + "<(tail -n +2 ${filename} | cut -f3)"}.join(" ") """ paste $gene_ids $counts > merged_gene_counts.txt """ From 6d345127c5241fad8d78b92ed24177a196e20ead Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 08:52:41 -0700 Subject: [PATCH 123/139] Evaluate gene biotype earlier and print in summary --- main.nf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 4ee41ef3a..6c293b5a4 100644 --- a/main.nf +++ b/main.nf @@ -214,6 +214,12 @@ if( params.bed12 ){ .into { bed_rseqc } } +if (params.gencode){ + biotype = "gene_type" +} else { + biotype = params.fc_group_features_type +} + if( workflow.profile == 'uppmax' || workflow.profile == 'uppmax-devel' ){ if ( !params.project ) exit 1, "No UPPMAX project ID found! Use --project" } @@ -293,6 +299,7 @@ if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff if(params.bed12) summary['BED Annotation'] = params.bed12 if(params.stringTieIgnoreGTF) summary['StringTie Ignore GTF'] = params.stringTieIgnoreGTF +if(params.fc_group_features_type) summary['Biotype field in GTF'] = biotype summary['Save prefs'] = "Ref Genome: "+(params.saveReference ? 'Yes' : 'No')+" / Trimmed FastQ: "+(params.saveTrimmed ? 'Yes' : 'No')+" / Alignment intermediates: "+(params.saveAlignedIntermediates ? 'Yes' : 'No') summary['Max Resources'] = "$params.max_memory memory, $params.max_cpus cpus, $params.max_time time per job" if(workflow.containerEngine) summary['Container'] = "$workflow.containerEngine - $workflow.container" @@ -1017,7 +1024,6 @@ process featureCounts { script: def featureCounts_direction = 0 - def biotype = params.gencode ? 'gene_type' : params.fc_group_features_type def extraAttributes = params.fc_extra_attributes ? "--extraAttributes ${params.fc_extra_attributes}" : '' if (forwardStranded && !unStranded) { featureCounts_direction = 1 From d8242ec40937035904787a78f1e481370b1909e4 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 08:55:24 -0700 Subject: [PATCH 124/139] Remove csvtk from requirements --- environment.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/environment.yml b/environment.yml index ee2d355ed..7571f1fe1 100644 --- a/environment.yml +++ b/environment.yml @@ -26,7 +26,6 @@ dependencies: - preseq=2.0.3 - deeptools=3.2.1 - gffread=0.11.4 - - csvtk=0.18.2 - qualimap=2.2.2c - rseqc=3.0.0 - subread=1.6.4 From a3c30ad1c257f8c07fdcd9e84d8b4d3410e138ce Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 08:55:44 -0700 Subject: [PATCH 125/139] add a note about redirection --- main.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/main.nf b/main.nf index b03e47203..e6af36b69 100644 --- a/main.nf +++ b/main.nf @@ -1046,6 +1046,7 @@ process merge_featureCounts { file 'merged_gene_counts.txt' into featurecounts_merged script: + // Redirection (the `<()`) for the win! gene_ids = "<(tail -n +2 ${input_files[0]} | cut -f1,2 )" counts = input_files.collect{filename -> // Remove first line and take third column From ac2b2a236a049dcdccb39a24220b62f5c74d330d Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 08:58:44 -0700 Subject: [PATCH 126/139] Use @drpatelh's wording Co-Authored-By: Harshil Patel --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index d2d9bb738..da7df16d3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -280,7 +280,7 @@ If your `--gtf` file is in GENCODE format and you would like to run Salmon (`--p #### "Type" of gene -First, the gene "biotype" field, e.g. `protein_coding` or `lincRNA` or `rRNA`, in the GTF file is called `gene_type`, rather than `gene_biotype` as in iGenomes. +The `gene_biotype` field which is typically found in Ensembl GTF files contains a key word description regarding the type of gene e.g. `protein_coding`, `lincRNA`, `rRNA`. In GENCODE GTF files this field has been renamed to `gene_type`. ENSEMBL version: From 11276af43f85865cf7df5c5d7353d0eecc29189d Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 08:59:09 -0700 Subject: [PATCH 127/139] remove random fenced code --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index da7df16d3..482e7d7c3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -274,7 +274,7 @@ As above, by default intermediate BAM files from the alignment will not be saved ### `--gencode` -If your `--gtf` file is in GENCODE format and you would like to run Salmon (`--pseudo_aligner salmon`) you will need to provide this parameter in order to build the Salmon index appropriately. The `params.fc_group_features_type=gene_type` will also be set as explained below.``` +If your `--gtf` file is in GENCODE format and you would like to run Salmon (`--pseudo_aligner salmon`) you will need to provide this parameter in order to build the Salmon index appropriately. The `params.fc_group_features_type=gene_type` will also be set as explained below. [GENCODE](gencodegenes.org/) gene annotations are slightly different from ENSEMBL or iGenome annotations in two ways. From beab23f95b700fb2c0a4e546dceceae6f4e684cd Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 09:31:48 -0700 Subject: [PATCH 128/139] Use 7th column for gene namec --- main.nf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.nf b/main.nf index e6af36b69..ccc54639d 100644 --- a/main.nf +++ b/main.nf @@ -1047,7 +1047,8 @@ process merge_featureCounts { script: // Redirection (the `<()`) for the win! - gene_ids = "<(tail -n +2 ${input_files[0]} | cut -f1,2 )" + // Geneid in 1st column and gene_name in 7th + gene_ids = "<(tail -n +2 ${input_files[0]} | cut -f1,7 )" counts = input_files.collect{filename -> // Remove first line and take third column "<(tail -n +2 ${filename} | cut -f3)"}.join(" ") From 32a37fd92558e23e7d9bf886358687fa7108dda0 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 09:36:16 -0700 Subject: [PATCH 129/139] Shorten name of biotype field in summary for brevity --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 6c293b5a4..6bccf800e 100644 --- a/main.nf +++ b/main.nf @@ -299,7 +299,7 @@ if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff if(params.bed12) summary['BED Annotation'] = params.bed12 if(params.stringTieIgnoreGTF) summary['StringTie Ignore GTF'] = params.stringTieIgnoreGTF -if(params.fc_group_features_type) summary['Biotype field in GTF'] = biotype +if(params.fc_group_features_type) summary['Biotype GTF field'] = biotype summary['Save prefs'] = "Ref Genome: "+(params.saveReference ? 'Yes' : 'No')+" / Trimmed FastQ: "+(params.saveTrimmed ? 'Yes' : 'No')+" / Alignment intermediates: "+(params.saveAlignedIntermediates ? 'Yes' : 'No') summary['Max Resources'] = "$params.max_memory memory, $params.max_cpus cpus, $params.max_time time per job" if(workflow.containerEngine) summary['Container'] = "$workflow.containerEngine - $workflow.container" From 7d84ba2a11060f89edb08cfd68f73f064073f0e5 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 14:30:17 -0700 Subject: [PATCH 130/139] use 0'th item not 1th --- bin/parse_gtf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/parse_gtf.py b/bin/parse_gtf.py index ca69e1eb1..fc5d67362 100755 --- a/bin/parse_gtf.py +++ b/bin/parse_gtf.py @@ -14,7 +14,7 @@ def read_top_transcript(salmon): txs = set() - fn = glob.glob(os.path.join(salmon, "*", "quant.sf"))[1] + fn = glob.glob(os.path.join(salmon, "*", "quant.sf"))[0] with open(fn) as inh: for line in inh: if line.startswith("Name"): From 17dd8fee4ad39df7923b88b35fa736b6d905c7d0 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 14:37:00 -0700 Subject: [PATCH 131/139] Add sample name to output --- bin/tximport.r | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/tximport.r b/bin/tximport.r index a9bbe0fb9..afaf112f8 100755 --- a/bin/tximport.r +++ b/bin/tximport.r @@ -8,6 +8,10 @@ if (length(args) < 2) { path = args[2] coldata = args[1] +sample_name = args[3] + +prefix = paste(c(sample_name, "salmon"), sep="_") + tx2gene = "tx2gene.csv" info = file.info(tx2gene) if (info$size == 0){ @@ -55,13 +59,13 @@ if (!is.null(tx2gene)){ if(exists("gse")){ saveRDS(gse, file = "gse.rds") - write.csv(assays(gse)[["abundance"]], "salmon_merged_gene_tpm.csv") - write.csv(assays(gse)[["counts"]], "salmon_merged_gene_counts.csv") + write.csv(assays(gse)[["abundance"]], paste(c(prefix, "gene_tpm.csv"), collapse="_"), quote=FALSE) + write.csv(assays(gse)[["counts"]], paste(c(prefix, "gene_counts.csv"), collapse="_"), quote=FALSE) } saveRDS(se, file = "se.rds") -write.csv(assays(se)[["abundance"]], "salmon_merged_transcript_tpm.csv") -write.csv(assays(se)[["counts"]], "salmon_merged_transcript_counts.csv") +write.csv(assays(se)[["abundance"]], paste(c(prefix, "transcript_tpm.csv"), collapse="_"), quote=FALSE) +write.csv(assays(se)[["counts"]], paste(c(prefix, "transcript_counts.csv"), collapse="_"), quote=FALSE) # Print sessioninfo to standard out citation("tximeta") From 0028728b0162754b21ee3d7608326b07a134df04 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 14:41:58 -0700 Subject: [PATCH 132/139] use tximport for each sample and then merge individually --- main.nf | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/main.nf b/main.nf index 6bccf800e..0fa2c6136 100644 --- a/main.nf +++ b/main.nf @@ -1076,7 +1076,8 @@ if (params.pseudo_aligner == 'salmon'){ file gtf from gtf_salmon.collect() output: - file "${sample}/" into salmon_merge, salmon_logs + file "${sample}/" into salmon_logs + set val(sample), file("${sample}/") into salmon_tximport script: def rnastrandness = params.singleEnd ? 'U' : 'IU' @@ -1098,22 +1099,53 @@ if (params.pseudo_aligner == 'salmon'){ """ } - process salmon_merge { + process salmon_tximport { label 'low_memory' publishDir "${params.outdir}/salmon", mode: 'copy' input: - file ("salmon/*") from salmon_merge.collect() + set val(name), file ("salmon/*") from salmon_tximport file gtf from gtf_salmon_merge output: file "*se.rds" into salmon_rds_ch - file "*.csv" into salmon_counts_ch + file "${name}_salmon_gene_tpm.csv" into salmon_gene_tpm + file "${name}_salmon_gene_counts.csv" into salmon_gene_counts + file "${name}_salmon_transcript_tpm.csv" into salmon_transcript_tpm + file "${name}_salmon_transcript_counts.csv" into salmon_transcript_counts script: """ parse_gtf.py --gtf $gtf --salmon salmon --id ${params.fc_group_features} --extra ${params.fc_extra_attributes} -o tx2gene.csv - tximport.r NULL salmon + tximport.r NULL salmon ${name} + """ + } + + process salmon_merge { + label 'low_memory' + publishDir "${params.outdir}/salmon", mode: 'copy' + + input: + file gene_tpm_files from salmon_gene_tpm.collect() + file gene_count_files from salmon_gene_counts.collect() + file transcript_tpm_files from salmon_transcript_tpm.collect() + file transcript_count_files from salmon_transcript_counts.collect() + + output: + file "*.csv" into salmon_merged_ch + + script: + gene_ids = "<(cut -f1,2 -d, ${gene_tpm_files[0]})" + transcript_ids = "<(cut -f1,2 -d, ${transcript_tpm_files[0]})" + gene_tpm = gene_tpm_files.collect{f -> "<(cut -f3 ${f})"}.join(" ") + gene_counts = gene_count_files.collect{f -> "<(cut -f3 ${f})"}.join(" ") + transcript_tpm = transcript_tpm_files.collect{f -> "<(cut -f3 ${f})"}.join(" ") + transcript_counts = transcript_count_files.collect{f -> "<(cut -f3 ${f})"}.join(" ") + """ + paste -d, $gene_ids $gene_tpm > salmon_merged_gene_tpm.csv + paste -d, $gene_ids $gene_counts > salmon_merged_gene_counts.csv + paste -d, $transcript_ids $transcript_tpm > salmon_merged_transcript_tpm.csv + paste -d, $transcript_ids $transcript_counts > salmon_merged_transcript_counts.csv """ } } else { From f1f3b8a44c91770f6719360f2caa3c418ed86a8c Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 14:51:19 -0700 Subject: [PATCH 133/139] properly merge gene counts and tpm files --- main.nf | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/main.nf b/main.nf index 0fa2c6136..b0d10e699 100644 --- a/main.nf +++ b/main.nf @@ -1101,11 +1101,10 @@ if (params.pseudo_aligner == 'salmon'){ process salmon_tximport { label 'low_memory' - publishDir "${params.outdir}/salmon", mode: 'copy' input: set val(name), file ("salmon/*") from salmon_tximport - file gtf from gtf_salmon_merge + file gtf from gtf_salmon_merge.collect() output: file "*se.rds" into salmon_rds_ch @@ -1132,15 +1131,18 @@ if (params.pseudo_aligner == 'salmon'){ file transcript_count_files from salmon_transcript_counts.collect() output: - file "*.csv" into salmon_merged_ch + file "salmon_merged*.csv" into salmon_merged_ch script: - gene_ids = "<(cut -f1,2 -d, ${gene_tpm_files[0]})" - transcript_ids = "<(cut -f1,2 -d, ${transcript_tpm_files[0]})" - gene_tpm = gene_tpm_files.collect{f -> "<(cut -f3 ${f})"}.join(" ") - gene_counts = gene_count_files.collect{f -> "<(cut -f3 ${f})"}.join(" ") - transcript_tpm = transcript_tpm_files.collect{f -> "<(cut -f3 ${f})"}.join(" ") - transcript_counts = transcript_count_files.collect{f -> "<(cut -f3 ${f})"}.join(" ") + // First field is the gene/transcript ID + gene_ids = "<(cut -f1 -d, ${gene_tpm_files[0]})" + transcript_ids = "<(cut -f1 -d, ${transcript_tpm_files[0]})" + + // Second field is counts/TPM + gene_tpm = gene_tpm_files.collect{f -> "<(cut -d, -f2 ${f})"}.join(" ") + gene_counts = gene_count_files.collect{f -> "<(cut -d, -f2 ${f})"}.join(" ") + transcript_tpm = transcript_tpm_files.collect{f -> "<(cut -d, -f2 ${f})"}.join(" ") + transcript_counts = transcript_count_files.collect{f -> "<(cut -d, -f2 ${f})"}.join(" ") """ paste -d, $gene_ids $gene_tpm > salmon_merged_gene_tpm.csv paste -d, $gene_ids $gene_counts > salmon_merged_gene_counts.csv From 2be0b274e648290a6be23f956c8fb45f0eac31e8 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 19:40:15 -0700 Subject: [PATCH 134/139] actually use the gene counts to merge .. --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index ccc54639d..7157007a4 100644 --- a/main.nf +++ b/main.nf @@ -1051,7 +1051,7 @@ process merge_featureCounts { gene_ids = "<(tail -n +2 ${input_files[0]} | cut -f1,7 )" counts = input_files.collect{filename -> // Remove first line and take third column - "<(tail -n +2 ${filename} | cut -f3)"}.join(" ") + "<(tail -n +2 ${filename} | sed 's:.sorted.bam::' | cut -f8)"}.join(" ") """ paste $gene_ids $counts > merged_gene_counts.txt """ From 70bb37c9d680e8db722b8af8c319aca181ed388d Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Thu, 27 Jun 2019 19:59:57 -0700 Subject: [PATCH 135/139] Add transcript_id and gene_id to salmon output csv --- main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.nf b/main.nf index b0d10e699..7236d0da8 100644 --- a/main.nf +++ b/main.nf @@ -1135,8 +1135,8 @@ if (params.pseudo_aligner == 'salmon'){ script: // First field is the gene/transcript ID - gene_ids = "<(cut -f1 -d, ${gene_tpm_files[0]})" - transcript_ids = "<(cut -f1 -d, ${transcript_tpm_files[0]})" + gene_ids = "<(cut -f1 -d, ${gene_tpm_files[0]} | tail -n +2 | cat <(echo '${params.fc_group_features}') - )" + transcript_ids = "<(cut -f1 -d, ${transcript_tpm_files[0]} | tail -n +2 | cat <(echo 'transcript_id') - )" // Second field is counts/TPM gene_tpm = gene_tpm_files.collect{f -> "<(cut -d, -f2 ${f})"}.join(" ") From 3bef844274f39cb62f6c3483f4b5fddc05d69014 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Fri, 28 Jun 2019 08:33:11 -0700 Subject: [PATCH 136/139] Add --gencode flag to usage and summary output --- main.nf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.nf b/main.nf index 7236d0da8..d502b8047 100644 --- a/main.nf +++ b/main.nf @@ -39,6 +39,7 @@ def helpMessage() { --gff Path to GFF3 file --bed12 Path to bed12 file --saveReference Save the generated reference files to the results directory + --gencode Use fc_group_features_type = 'gene_type' and pass '--gencode' flag to Salmon Strandedness: --forwardStranded The library is forward stranded @@ -298,6 +299,7 @@ if(params.pseudo_aligner == 'salmon') { if(params.gtf) summary['GTF Annotation'] = params.gtf if(params.gff) summary['GFF3 Annotation'] = params.gff if(params.bed12) summary['BED Annotation'] = params.bed12 +if(params.gencode) summary['GENCODE'] = params.gencode if(params.stringTieIgnoreGTF) summary['StringTie Ignore GTF'] = params.stringTieIgnoreGTF if(params.fc_group_features_type) summary['Biotype GTF field'] = biotype summary['Save prefs'] = "Ref Genome: "+(params.saveReference ? 'Yes' : 'No')+" / Trimmed FastQ: "+(params.saveTrimmed ? 'Yes' : 'No')+" / Alignment intermediates: "+(params.saveAlignedIntermediates ? 'Yes' : 'No') From 41c58f8ea29c2e233ea54d93e036e82057ca0c48 Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Fri, 28 Jun 2019 08:36:26 -0700 Subject: [PATCH 137/139] Don't need salmon RDS files --- main.nf | 1 - 1 file changed, 1 deletion(-) diff --git a/main.nf b/main.nf index d502b8047..a133d1b5e 100644 --- a/main.nf +++ b/main.nf @@ -1109,7 +1109,6 @@ if (params.pseudo_aligner == 'salmon'){ file gtf from gtf_salmon_merge.collect() output: - file "*se.rds" into salmon_rds_ch file "${name}_salmon_gene_tpm.csv" into salmon_gene_tpm file "${name}_salmon_gene_counts.csv" into salmon_gene_counts file "${name}_salmon_transcript_tpm.csv" into salmon_transcript_tpm From 5c13cc0f44d9e8ff7ab2e0bd8fdd354e4388edee Mon Sep 17 00:00:00 2001 From: Olga Botvinnik Date: Mon, 1 Jul 2019 09:08:31 -0700 Subject: [PATCH 138/139] Add gtf_qualimap --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 01c0e999e..50399b5f9 100644 --- a/main.nf +++ b/main.nf @@ -386,7 +386,7 @@ if(params.gff){ output: file "${gff.baseName}.gtf" into gtf_makeSTARindex, gtf_makeHisatSplicesites, gtf_makeHISATindex, gtf_makeSalmonIndex, gtf_makeBED12, - gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon, gtf_salmon_merge + gtf_star, gtf_dupradar, gtf_featureCounts, gtf_stringtieFPKM, gtf_salmon, gtf_salmon_merge, gtf_qualimap script: """ From d1b6d34d672365e53941819e463184bb93a8cd29 Mon Sep 17 00:00:00 2001 From: Alexander Peltzer Date: Sun, 7 Jul 2019 18:37:21 +0200 Subject: [PATCH 139/139] Remove trailing slash --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b477a32d6..673856aaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ * Change all boolean parameters from snake_case to camelCase and vice versa for value parameters * Appointed changes because of missing output of the multiqc_plots folder [#200](https://github.com/nf-core/rnaseq/issues/200) * Add Qualimap dependency [#202](https://github.com/nf-core/rnaseq/issues/202) -* Add SM ReadGroup info for QualiMap compatibility[#238](https://github.com/nf-core/rnaseq/issues/238) +* Add SM ReadGroup info for QualiMap compatibility[#238](https://github.com/nf-core/rnaseq/issues/238) * Obtain edgeR + dupRadar version information [#198](https://github.com/nf-core/rnaseq/issues/198) and [#112](https://github.com/nf-core/rnaseq/issues/112) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183) * Get MultiQC to save plots as [standalone files](https://github.com/nf-core/rnaseq/issues/183): added the folder "multiqc_plots" to the output.