From 62e42660409ad7e0ad76f8552192cbff7afcc887 Mon Sep 17 00:00:00 2001 From: Sarah Griffiths Date: Tue, 29 Jul 2025 08:54:20 +0000 Subject: [PATCH] Output dexseq file [CW-6285] --- CHANGELOG.md | 5 ++- .../wfg_helpers/configure_igv.py | 45 +++++++++++++------ lib/common.nf | 4 ++ main.nf | 1 + nextflow.config | 6 +-- nextflow_schema.json | 3 +- subworkflows/differential_expression.nf | 2 +- 7 files changed, 46 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c3a52..903dba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,20 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed -- Updated to wf-template v5.6.1, changing: +- Updated to wf-template v5.6.2, changing: - Reduce verbosity of debug logging from fastcat which can occasionally occlude errors found in FASTQ files during ingress. - Log banner art to say "EPI2ME" instead of "EPI2ME Labs" to match current branding. This has no effect on the workflow outputs. - pre-commit configuration to resolve an internal dependency problem with flake8. This has no effect on the workflow. - Stringtie updated to v2.2.3, which fixes stalling at transcriptome assembly step. - Gffcompare updated to v0.12.6, which fixes issue where ref_gene_id was assigned an nan value. ### Fixed -- Updated to wf-template v5.6.1, fixing: +- Updated to wf-template v5.6.2, fixing: - dacite.exceptions.WrongTypeError during report generation when barcode is null. - Sequence summary read length N50 incorrectly displayed minimum read length, it now correctly shows the N50. - Sequence summary component alignment and coverage plots failed to plot under some conditions. - Error in `deAnalysis` process - `mode(counts) %in% "numeric" is not TRUE` - caused by hyphens in sample sheet aliases. - Error in `deAnalysis` process - `values in 'transcripts$tx_strand' must be "+" or "-"`. - The workflow will now filter out any unstranded annotations from downstream analysis and log a warning. +- Output the `results_dexseq.tsv` file when `--de_analysis` enabled. ## [v1.7.0] ### Changed diff --git a/bin/workflow_glue/wfg_helpers/configure_igv.py b/bin/workflow_glue/wfg_helpers/configure_igv.py index a817e53..e9a9864 100644 --- a/bin/workflow_glue/wfg_helpers/configure_igv.py +++ b/bin/workflow_glue/wfg_helpers/configure_igv.py @@ -107,10 +107,11 @@ class TrackBuilder: if idx_basename == f"{basename}.gzi" and basename.endswith(".gz"): self.gzi = ref_index - def parse_fnames(self, fofn): - """Parse list with filenames and return them grouped. + def parse_fnames(self, fofn, keep_track_order=False): + """Parse list with filenames and set self with samples info per track. :param fofn: File with list of file names (one per line) + :param keep_track_order: Keep track order as in the list of file names. """ tmp_samples = {} with open(fofn, "r") as f: @@ -134,14 +135,18 @@ class TrackBuilder: tmp_samples["NO_SAMPLE"] = SampleBundle(sample="NO_SAMPLE") tmp_samples["NO_SAMPLE"].append(fname) # Re-order samples in dict and add them to the list, leaving - # NO_SAMPLE as last - sorted_samples = ( - sorted([sample for sample in tmp_samples.keys() if sample != 'NO_SAMPLE']) - ) - if 'NO_SAMPLE' in tmp_samples.keys(): - sorted_samples += ['NO_SAMPLE'] - for sample in sorted_samples: - self.samples[sample] = tmp_samples[sample] + # NO_SAMPLE + samples = { + sample_name: sample_content + for sample_name, sample_content in tmp_samples.items() + if sample_name != "NO_SAMPLE" + } + if not keep_track_order: + samples = dict(sorted(samples.items())) + # Add NO_SAMPLE as last + if "NO_SAMPLE" in tmp_samples.keys(): + samples.update({"NO_SAMPLE": tmp_samples["NO_SAMPLE"]}) + self.samples = samples def build_igv_json(self): """Ensure there is a reference genome.""" @@ -171,12 +176,22 @@ class TrackBuilder: bundle.process_data() # Add the bundled data to the tracks for fname, index, file_fmt in bundle.data_bundles: + # Check if there are custom opts per track + if sample != "NO_SAMPLE" and isinstance( + self.extra_opts_lookups[file_fmt], list + ): + sample_info = {} + for e in self.extra_opts_lookups[file_fmt]: + sample_info.update(e) + extra_opts_lookups_track = sample_info[sample] + else: + extra_opts_lookups_track = self.extra_opts_lookups[file_fmt] self.add_track( fname, file_fmt, sample_name=sample if sample != "NO_SAMPLE" else None, index=index, - extra_opts=self.extra_opts_lookups[file_fmt], + extra_opts=extra_opts_lookups_track, ) def add_track(self, infile, file_fmt, sample_name=None, index=None, extra_opts={}): @@ -325,7 +340,7 @@ def main(args): ) # Import files - igv_builder.parse_fnames(args.fofn) + igv_builder.parse_fnames(args.fofn, args.keep_track_order) # initialise the IGV options dict with the reference options igv_builder.build_igv_json() @@ -335,7 +350,6 @@ def main(args): igv_builder.add_locus(args.locus) json.dump(igv_builder.igv_json, sys.stdout, indent=4) - logger.info("Printed IGV config JSON to STDOUT.") @@ -350,6 +364,11 @@ def argparser(): "(one filename per line)" ), ) + parser.add_argument( + "--keep-track-order", + action="store_true", + help="Keep track order as provided in fofn", + ) parser.add_argument( "--locus", help="Locus string to set initial genomic coordinates to display in IGV", diff --git a/lib/common.nf b/lib/common.nf index 82a9d12..84e89cc 100644 --- a/lib/common.nf +++ b/lib/common.nf @@ -28,6 +28,7 @@ process configure_igv { val locus_str val aln_extra_opts val var_extra_opts + val keep_track_order output: path "igv.json" script: // the locus argument just makes sure that the initial view in IGV shows something @@ -43,6 +44,8 @@ process configure_igv { var_extra_opts ? new JsonBuilder(var_extra_opts).toPrettyString() : "" String var_extra_opts_arg = \ var_extra_opts ? "--extra-vcf-opts extra-var-opts.json" : "" + String keep_track_order_arg = \ + keep_track_order ? "--keep-track-order" : "" """ # write out JSON files with extra options for the alignment and variant tracks echo '$aln_opts_json_str' > extra-aln-opts.json @@ -53,6 +56,7 @@ process configure_igv { $locus_arg \ $aln_extra_opts_arg \ $var_extra_opts_arg \ + $keep_track_order_arg \ > igv.json """ } diff --git a/main.nf b/main.nf index ff1944c..ac733c7 100644 --- a/main.nf +++ b/main.nf @@ -919,6 +919,7 @@ workflow pipeline { Channel.of(null), // igv locus [displayMode: "SQUISHED", colorBy: "strand"], // bam extra opts Channel.of(null), // vcf extra opts + Channel.of(false), // keep_track_order opts ) results = results.concat(igv_conf.map{ [it, null]}) diff --git a/nextflow.config b/nextflow.config index 357d8bd..70a891a 100644 --- a/nextflow.config +++ b/nextflow.config @@ -94,9 +94,9 @@ params { "--ref_genome 'wf-transcriptomes-demo/hg38_chr20.fa'", "--sample_sheet 'wf-transcriptomes-demo/sample_sheet.csv'", ] - agent = null - container_sha = "shaaaf20a5a0e76f9e18bad21af639a6b69e4a31a2f" - common_sha = "sha1c69fd30053aad5d516e9567b3944384325a0fee" + agent = null + container_sha = "shaaaf20a5a0e76f9e18bad21af639a6b69e4a31a2f" + common_sha = "sha72f3517dd994984e0e2da0b97cb3f23f8540be4b" } } diff --git a/nextflow_schema.json b/nextflow_schema.json index 9e4a2c7..aa23839 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -318,7 +318,8 @@ }, "store_dir": { "type": "string", - "hidden": true + "hidden": true, + "format" : "directory-path" }, "disable_ping": { "type": "boolean", diff --git a/subworkflows/differential_expression.nf b/subworkflows/differential_expression.nf index 981b1e7..68c19ff 100644 --- a/subworkflows/differential_expression.nf +++ b/subworkflows/differential_expression.nf @@ -192,7 +192,7 @@ workflow differential_expression { analysis.gene_counts, analysis.dge, analysis.dexseq, analysis.stageR, sample_sheet, merged, ref_annotation, merged_TPM, analysis.unflt_counts).collect() // Concat files required to be output to user without any changes - de_outputs_concat = analysis.cpm.concat(plotResults.out.dtu_plots, analysis.dge_pdf, analysis.dge_tsv, + de_outputs_concat = analysis.cpm.concat(analysis.dexseq, plotResults.out.dtu_plots, analysis.dge_pdf, analysis.dge_tsv, analysis.dtu_gene, analysis.dtu_transcript, analysis.dtu_stageR, analysis.dtu_pdf, merged_TPM).collect() collected_de_alignment_stats = mapped.align_stats.collect() emit: