From 5e47b2e478c0a42fbfdd608f922ebc4430d37978 Mon Sep 17 00:00:00 2001 From: Daniel Mapleson Date: Sun, 25 Feb 2024 20:59:28 +0000 Subject: [PATCH] Normalisation and bug fixes (#39) * fix(essentiality): essentiality analysis report now contains data Previously we were using the .all.csv as input to the essentiality report file, however this expect the .tsv input from the insertion site analysis output. This fix changes that. Also we remove the .all.csv file from output generated by the R script as it doesn't serve any purpose. Also updated the docs. fixing stuff * feat(normalisation): adding plot file normalisation to the compare pipeline We add normalisation to the beginning of the plot compare pipeline. fix no normalisation issue --------- Co-authored-by: Sarah Bastkowski --- README.md | 3 + docs/pipeline_combine.png | Bin 38292 -> 40366 bytes docs/pipeline_combine.pu | 8 +- quatradis/comparison/cli.py | 16 +- quatradis/comparison/comparison.py | 30 ++- quatradis/comparison/essentiality.py | 3 +- quatradis/comparison/essentiality_analysis.py | 66 +++-- quatradis/comparison/scatterplot.py | 2 +- quatradis/comparison/split.py | 2 +- quatradis/comparison/tradis_comparison.R | 2 +- quatradis/pipelines/compare.smk | 81 +++--- quatradis/pipelines/pipeline.py | 255 +++++++++++++----- quatradis/tisp/cli.py | 7 +- quatradis/tisp/generator/from_values.py | 10 +- quatradis/tisp/normalise.py | 17 +- quatradis/tisp/parser.py | 4 +- tests/py/comparison/scatterplot_test.py | 2 + tests/py/tisp/normalise_test.py | 58 +++- tests/py/util/mapper_test.py | 97 ++++++- tests/scripts/comparison_test.sh | 4 +- tests/scripts/plot_test.sh | 4 +- tradis | 4 +- 22 files changed, 472 insertions(+), 203 deletions(-) diff --git a/README.md b/README.md index 31a8178..619dbb1 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,9 @@ conda activate quatradis # Install mamba if you haven't already conda install -c conda-forge mamba +# Install edgeR separately because this doesn't get imported correctly with quatradis. +mamba install -c conda-forge -c bioconda bioconductor-edger + # Install quatradis mamba install -c conda-forge -c bioconda quatradis ``` diff --git a/docs/pipeline_combine.png b/docs/pipeline_combine.png index 03cc4de6d4ec72f773aedad2a4f64cbdef60e2a2..5c813077c02e7ed16182c884fdcc5ccc6243afa9 100644 GIT binary patch literal 40366 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
1^U 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 1 and r[1] != 'gene_name'] + with open(filename, "r") as fileh: + reader = csv.reader(fileh, delimiter="\t", quotechar='"') + gene_names1 = [r[1] for r in reader if len(r) > 1 and r[1] != "gene_name"] all_gene_names = all_gene_names.union(set(gene_names1)) - for f in control_files: - with open(filename, 'r') as fileh: - reader = csv.reader(fileh, delimiter='\t', quotechar='"') - gene_names2 = [r[1] for r in reader if len(r) > 1 and r[1] != 'gene_name'] + for filename in control_files: + with open(filename, "r") as fileh: + reader = csv.reader(fileh, delimiter="\t", quotechar='"') + gene_names2 = [r[1] for r in reader if len(r) > 1 and r[1] != "gene_name"] all_gene_names = all_gene_names.union(set(gene_names2)) return list(all_gene_names) + def all_gene_essentiality(input: EssentialityInput, analysis_type, verbose=False): all_gene_names = get_all_gene_names(input.control_files, input.condition_files) @@ -48,7 +50,7 @@ def all_gene_essentiality(input: EssentialityInput, analysis_type, verbose=False genes_ess = {g: GeneEssentiality() for g in all_gene_names} if analysis_type == "original": for f in input.only_ess_condition_files: - ess_gene_names = gene_names_from_essentiality_file(f, verbose) + ess_gene_names = gene_names_from_essentiality_file(f) if verbose: print("ess_gene_names condition: " + str(len(ess_gene_names))) print("genes_ess: " + str(len(genes_ess))) @@ -57,7 +59,7 @@ def all_gene_essentiality(input: EssentialityInput, analysis_type, verbose=False genes_ess[e].condition += 1 genes_ess[e].number_of_reps = len(input.only_ess_condition_files) for f in input.only_ess_control_files: - ess_gene_names = gene_names_from_essentiality_file(f, verbose) + ess_gene_names = gene_names_from_essentiality_file(f) if verbose: print("ess_gene_names control: " + str(len(ess_gene_names))) print("genes_ess: " + str(len(genes_ess))) @@ -73,16 +75,19 @@ def all_gene_essentiality(input: EssentialityInput, analysis_type, verbose=False return genes_ess -def add_gene_essentiality_to_file(input_filename, output_filename, genes_ess, analysis_type): + +def add_gene_essentiality_to_file( + input_filename, output_filename, genes_ess, analysis_type +): """ We can add information on gene essentiality to the comparison output, but this will not reflect full set of essential genes as the output does not contain all genes In order to prevent confusion this is "switched" off, but could be used if uncommented """ - with open(input_filename, 'r') as inputfh: + with open(input_filename, "r") as inputfh: output_content = [] - reader = csv.reader(inputfh, delimiter=',', quotechar='"') + reader = csv.reader(inputfh, delimiter=",", quotechar='"') input_content = [r for r in reader] # if analysis_type == "original": # print("Number of cells: " + len(input_content)) @@ -99,21 +104,34 @@ def add_gene_essentiality_to_file(input_filename, output_filename, genes_ess, an output_content = input_content - with open(output_filename, 'w') as outputfh: + with open(output_filename, "w") as outputfh: for line in output_content: outputfh.write(",".join(line) + "\n") -def essentiality_analysis(input: EssentialityInput, output_dir, analysis_type, output_filename=""): +def essentiality_analysis( + input: EssentialityInput, output_dir, analysis_type, output_filename="" +): - #out_csv = mkstemp() + # out_csv = mkstemp() genes_ess = all_gene_essentiality(input, analysis_type) - #add_gene_essentiality_to_file(out_csv, output_filename, genes_ess, analysis_type) - #os.remove(out_csv) + # add_gene_essentiality_to_file(out_csv, output_filename, genes_ess, analysis_type) + # os.remove(out_csv) + ensure_output_dir_exists(output_dir) ess = open(os.path.join(output_dir, "essentiality.csv"), "w+") ess.write("Gene,Essentiality,Control,Condition,Replicates\n") for e in genes_ess: - ess.write(",".join([e, str(genes_ess[e].status()), str(genes_ess[e].control), - str(genes_ess[e].condition), str(genes_ess[e].number_of_reps)]) + "\n") + ess.write( + ",".join( + [ + e, + str(genes_ess[e].status()), + str(genes_ess[e].control), + str(genes_ess[e].condition), + str(genes_ess[e].number_of_reps), + ] + ) + + "\n" + ) ess.close() diff --git a/quatradis/comparison/scatterplot.py b/quatradis/comparison/scatterplot.py index f4b959b..9a0a9eb 100644 --- a/quatradis/comparison/scatterplot.py +++ b/quatradis/comparison/scatterplot.py @@ -48,7 +48,7 @@ def __init__(self, conditions, controls, window_size, prefix, normalise=False, v self.normalise = normalise if normalise: - n = NormalisePlots(self.conditions + self.controls, 0.0000001, verbose=self.verbose) + n = NormalisePlots(self.conditions + self.controls, 0.0000001, output_dir="normalised", verbose=self.verbose) plotfiles, max_reads = n.create_normalised_files() self.conditions = plotfiles[0:len(self.conditions)] self.controls = plotfiles[len(self.conditions):] diff --git a/quatradis/comparison/split.py b/quatradis/comparison/split.py index d44a903..cafe6d7 100644 --- a/quatradis/comparison/split.py +++ b/quatradis/comparison/split.py @@ -27,7 +27,7 @@ def get_combined_file_path(self): return self._construct_file_path("combined") def _create_split_plot_file(self, forward, reverse, filename): - p = PlotFromValuesGenerator(forward, reverse, filename, self.gzipped) + p = PlotFromValuesGenerator(forward, reverse, filename) p.construct_file() def create_all_files(self): diff --git a/quatradis/comparison/tradis_comparison.R b/quatradis/comparison/tradis_comparison.R index d677a02..7b3ab2d 100755 --- a/quatradis/comparison/tradis_comparison.R +++ b/quatradis/comparison/tradis_comparison.R @@ -119,4 +119,4 @@ abline(v=2, col="red") #write results -write.table(diff,file=opt$output,append=FALSE, quote=TRUE, sep=",", row.names=FALSE, col.names=c("locus_tag","gene_name","function","logFC","logCPM","PValue","q.value")) +write.table(diff,file=opt$output,append=FALSE, quote=FALSE, sep=",", row.names=FALSE, col.names=c("locus_tag","gene_name","function","logFC","logCPM","PValue","q.value")) diff --git a/quatradis/pipelines/compare.smk b/quatradis/pipelines/compare.smk index c977b44..d940219 100644 --- a/quatradis/pipelines/compare.smk +++ b/quatradis/pipelines/compare.smk @@ -1,21 +1,30 @@ import os import shutil +input_files=[] +norm_files=[] plotnames=[] controlnames=[] conditionnames=[] -plot_lut={} +norm_lut={} for p in config["condition_files"]: + input_files.append(p) plotname = os.path.basename(p).split('.')[0] plotnames.append(plotname) conditionnames.append(plotname) - plot_lut[plotname]=p + for p in config["control_files"]: + input_files.append(p) plotname = os.path.basename(p).split('.')[0] plotnames.append(plotname) controlnames.append(plotname) - plot_lut[plotname]=p + +for p in input_files: + plotname = os.path.basename(p).split('.')[0] + n=os.path.join(config["output_dir"], "analysis", plotname, "original.plot.gz") + norm_files.append(n) + norm_lut[plotname]=n ZCAT_CMD='gzcat' if not shutil.which('gzcat'): @@ -24,14 +33,27 @@ if not shutil.which('gzcat'): else: raise Error("Couldn't find gzcat or zcat on your system. Please install and try again.") +def make_no_normalise_cmd(): + cmds = [] + for i in range(len(input_files)): + out_dir = os.path.join(config["output_dir"], "analysis", plotname) + cmds.append("mkdir -p " + out_dir) + cmds.append("if [[ $(file " + input_files[i] + " | grep ASCII | wc -l | cut -f 1) > 0 ]]; then gzip -c " + input_files[i] + " > " + norm_files[i] + "; else cp " + input_files[i] + " " + norm_files[i] + "; fi") + + return "; ".join(cmds) + +def make_normalise_cmd(): + return "tradis plot normalise -o " + os.path.join(config["output_dir"], "analysis") + " -n original.plot.gz " + \ + ("--minimum_proportion_insertions=" + config["minimum_proportion_insertions"] if config["minimum_proportion_insertions"] else "") + \ + " " + " ".join(input_files) rule finish: input: expand(os.path.join(config["output_dir"], "gene_report.tsv")), expand(os.path.join(config["output_dir"], "comparison", "{type}", "plot_absscatter.png"), type=["original", "forward", "reverse", "combined"]), - #expand(os.path.join(config["output_dir"],"comparison","{type}","{type}.compare.csv"), type=["original", "forward", "reverse", "combined"]), - expand(os.path.join(config["output_dir"],"comparison","{type}","essentiality.csv"), type=["original", "forward", "reverse", "combined"]), - expand(os.path.join(config["output_dir"],"analysis","{plot}","{type}.count.tsv.all.csv"), type=["original", "combined", "forward", "reverse"], plot=plotnames) + #expand(os.path.join(config["output_dir"], "comparison", "{type}", "{type}.compare.csv"), type=["original", "forward", "reverse", "combined"]), + expand(os.path.join(config["output_dir"], "comparison", "{type}", "essentiality.csv"), type=["original", "forward", "reverse", "combined"]), + expand(os.path.join(config["output_dir"], "analysis", "{plot}", "{type}.count.tsv.essen.csv"), type=["original", "combined", "forward", "reverse"], plot=plotnames) run: print("All done!") @@ -49,10 +71,21 @@ rule prepare_embl: prime_feature_size="--prime_feature_size=" + config["prime_feature_size"] if config["prime_feature_size"] else "" shell: "tradis compare prepare_embl --output={output} {params.minimum_threshold} {params.window_size} {params.window_interval} {params.prime_feature_size} --emblfile {input.embl} {input.plot}" +rule normalise: + input: + input_files + output: + norm_files + message: "Normalising plot files" + log: os.path.join(config["output_dir"], "analysis", "normalise.log") + params: + cmd=make_normalise_cmd() if config["normalise"] else make_no_normalise_cmd() + shell: "{params.cmd}" + rule split_plots: input: - p=lambda wildcards: plot_lut[wildcards.plot] + p=lambda wildcards: norm_lut[wildcards.plot] output: c=os.path.join(config["output_dir"], "analysis", "{plot}", "combined.plot.gz"), f=os.path.join(config["output_dir"], "analysis", "{plot}", "forward.plot.gz"), @@ -74,39 +107,13 @@ rule count_plots: params: output_dir=os.path.join(config["output_dir"], "analysis", "{plot}"), suffix="count.tsv" - wildcard_constraints: - type="(?!original).*" shell: "tradis plot count -o {params.output_dir} -s {params.suffix} {input.embl} {input.p}" -rule copy_original: - input: - lambda wildcards: plot_lut[wildcards.plot] - output: - os.path.join(config["output_dir"],"analysis","{plot}","original.plot.gz") - message: "Copying original plot file to target folder: {input}" - params: - output_dir=os.path.join(config["output_dir"],"analysis","{plot}") - shell: "if [[ $(file {input} | grep ASCII | wc -l | cut -f 1) > 0 ]]; then gzip -c {input} > {output}; else cp {input} {output}; fi" - - -rule count_original_plots: - input: - p=rules.copy_original.output, - embl=config["annotations"] - output: - os.path.join(config["output_dir"], "analysis", "{plot}", "original.count.tsv") - message: "Analysing original plot and embl file {input.p}, {input.embl}" - params: - output_dir=os.path.join(config["output_dir"], "analysis", "{plot}") - shell: "tradis plot count -o {params.output_dir} -s count.tsv {input.embl} {input.p}" - - rule essentiality: input: c=os.path.join(config["output_dir"], "analysis", "{plot}", "{type}.count.tsv"), output: - all=os.path.join(config["output_dir"], "analysis", "{plot}", "{type}.count.tsv.all.csv"), ess=os.path.join(config["output_dir"], "analysis", "{plot}", "{type}.count.tsv.essen.csv") log: os.path.join(config["output_dir"], "analysis", "{plot}", "{type}.count.tsv.essen.log") message: "Determining gene essentiality for {input}" @@ -128,7 +135,7 @@ rule plot: shell: "tradis compare figures {params.window_size} {params.prefix} {params.controls} {params.conditions} > /dev/null 2>&1" -rule logfc: +rule insertion_site_comparison: input: controls=lambda wildcards: expand(os.path.join(config["output_dir"], "analysis", "{control}", wildcards.tt + ".count.tsv"), control=controlnames), conditions=lambda wildcards: expand(os.path.join(config["output_dir"], "analysis", "{condition}", wildcards.tt + ".count.tsv"), condition=conditionnames), @@ -143,7 +150,7 @@ rule logfc: output_dir="--prefix=" + os.path.join(config["output_dir"], "comparison", "{tt}"), zcat=ZCAT_CMD message: "Calculating logfc for {output}" - shell: "SEQLENGTH=$({params.zcat} {params.combined} | wc -l); tradis compare logfc_plot {input.embl} {params.tt} {params.output_dir} -g ${{SEQLENGTH}} --verbose --controls {input.controls} --conditions {input.conditions} > {log} 2>&1" + shell: "SEQLENGTH=$({params.zcat} {params.combined} | wc -l); tradis compare insertion_sites {input.embl} {params.tt} {params.output_dir} -g ${{SEQLENGTH}} --verbose --controls {input.controls} --conditions {input.conditions} > {log} 2>&1" rule gene_stats: @@ -165,8 +172,8 @@ rule gene_stats: rule essentiality_analysis: input: - controls = lambda wildcards: expand(os.path.join(config["output_dir"],"analysis","{control}",wildcards.type + ".count.tsv.all.csv"),control=controlnames), - conditions = lambda wildcards: expand(os.path.join(config["output_dir"],"analysis","{condition}",wildcards.type + ".count.tsv.all.csv"),condition=conditionnames), + controls = lambda wildcards: expand(os.path.join(config["output_dir"],"analysis","{control}",wildcards.type + ".count.tsv"),control=controlnames), + conditions = lambda wildcards: expand(os.path.join(config["output_dir"],"analysis","{condition}",wildcards.type + ".count.tsv"),condition=conditionnames), ess_controls= lambda wildcards: expand(os.path.join(config["output_dir"],"analysis","{control}",wildcards.type + ".count.tsv.essen.csv"),control=controlnames), ess_conditions=lambda wildcards: expand(os.path.join(config["output_dir"],"analysis","{condition}",wildcards.type + ".count.tsv.essen.csv"),condition=conditionnames) output: diff --git a/quatradis/pipelines/pipeline.py b/quatradis/pipelines/pipeline.py index 2ef30dd..856dba9 100644 --- a/quatradis/pipelines/pipeline.py +++ b/quatradis/pipelines/pipeline.py @@ -8,42 +8,90 @@ def add_subparser(subparsers): - pipeline_parser_desc = "TraDIS pipelines that stitch together other tools in this package." + pipeline_parser_desc = ( + "TraDIS pipelines that stitch together other tools in this package." + ) pipeline_parser = subparsers.add_parser("pipeline", help=pipeline_parser_desc) pipeline_subparsers = pipeline_parser.add_subparsers(title=pipeline_parser_desc) - create_parser("create_plots", pipeline_subparsers, create_plots_pipeline, create_plots_options, - "Creates transponson insertion site plot files for multiple fastqs in parallel where possible using snakemake.", - description='''This pipeline uses snakemake, therefore it is possible to customise how this operates to distribute the workload - across a cluster using a snakemake config file.''', - usage="tradis pipeline create_plots [options] ") - - create_parser("compare", pipeline_subparsers, compare_pipeline, compare_options, - "Calculates gene essentiality for a set of transposon insertion site plots files ", - description='''This pipeline uses snakemake, therefore it is possible to customise how this operates to distribute the workload - across a cluster using a snakemake config file.''', - usage="tradis pipeline compare [options]") + create_parser( + "create_plots", + pipeline_subparsers, + create_plots_pipeline, + create_plots_options, + "Creates transponson insertion site plot files for multiple fastqs in parallel where possible using snakemake.", + description="""This pipeline uses snakemake, therefore it is possible to customise how this operates to distribute the workload + across a cluster using a snakemake config file.""", + usage="tradis pipeline create_plots [options] ", + ) + + create_parser( + "compare", + pipeline_subparsers, + compare_pipeline, + compare_options, + "Calculates gene essentiality for a set of transposon insertion site plots files ", + description="""This pipeline uses snakemake, therefore it is possible to customise how this operates to distribute the workload + across a cluster using a snakemake config file.""", + usage="tradis pipeline compare [options]", + ) def create_plots_options(parser): - parser.add_argument('fastqs', type=str, - help='Either a line separated text file containing a list of fastq formatted reads for processing or a single fastq file (can be gzipped).') - parser.add_argument('reference', type=str, - help='The fasta formatted reference for processing.') - parser.add_argument('-o', '--output_dir', default="results", - help='The output directory to use for all output files (default: results)') - parser.add_argument('-n', '--threads', type=int, default=1, - help='number of threads to use when mapping and sorting (default: 1)') - parser.add_argument('-a', '--aligner', default="bwa", - help='mapping tool to use (bwa, smalt, minimap2) (default: bwa)') - parser.add_argument('-m', '--mapping_score', type=int, default=30, - help='mapping quality must be greater than X (Default: 30)') - parser.add_argument('-t', '--tag', type=str, default="", - help='the tag to remove from fastq input (default: "")') - parser.add_argument('-mm', '--mismatch', type=int, default=0, - help='number of mismatches allowed when matching tag (default: 0)') - parser.add_argument('-sp', '--snakemake_profile', type=str, - help='If provided, pass this directory onto snakemake. Assumes there is a file called "config.yaml" in that directory.') + parser.add_argument( + "fastqs", + type=str, + help="Either a line separated text file containing a list of fastq formatted reads for processing or a single fastq file (can be gzipped).", + ) + parser.add_argument( + "reference", type=str, help="The fasta formatted reference for processing." + ) + parser.add_argument( + "-o", + "--output_dir", + default="results", + help="The output directory to use for all output files (default: results)", + ) + parser.add_argument( + "-n", + "--threads", + type=int, + default=1, + help="number of threads to use when mapping and sorting (default: 1)", + ) + parser.add_argument( + "-a", + "--aligner", + default="bwa", + help="mapping tool to use (bwa, smalt, minimap2) (default: bwa)", + ) + parser.add_argument( + "-m", + "--mapping_score", + type=int, + default=30, + help="mapping quality must be greater than X (Default: 30)", + ) + parser.add_argument( + "-t", + "--tag", + type=str, + default="", + help='the tag to remove from fastq input (default: "")', + ) + parser.add_argument( + "-mm", + "--mismatch", + type=int, + default=0, + help="number of mismatches allowed when matching tag (default: 0)", + ) + parser.add_argument( + "-sp", + "--snakemake_profile", + type=str, + help='If provided, pass this directory onto snakemake. Assumes there is a file called "config.yaml" in that directory.', + ) def create_plots_pipeline(args): @@ -55,15 +103,15 @@ def create_plots_pipeline(args): fastqs = [args.fastqs] if not fhh.is_fastq(args.fastqs): - with open(args.fastqs, 'r') as fql: + with open(args.fastqs, "r") as fql: fastqs = [x.strip() for x in fql.readlines() if x] snakemake_config = os.path.join(args.output_dir, "create_plots_config.yaml") fastq_dir, fq_fn = os.path.split(args.fastqs) - with open(snakemake_config, 'w') as ofql: + with open(snakemake_config, "w") as ofql: ofql.write(create_yaml_option("output_dir", args.output_dir)) ofql.write(create_yaml_option("reference", args.reference)) - ofql.write("fastq_dir: \"" + fastq_dir + "\"\n") + ofql.write('fastq_dir: "' + fastq_dir + '"\n') ofql.write("fastqs:\n") for x in fastqs: ofql.write("- " + x + "\n") @@ -75,30 +123,86 @@ def create_plots_pipeline(args): pipeline = find_pipeline_file("create_plots.smk") - start_snakemake(pipeline, snakemake_config, threads=args.threads, - snakemake_profile=args.snakemake_profile, verbose=args.verbose) + start_snakemake( + pipeline, + snakemake_config, + threads=args.threads, + snakemake_profile=args.snakemake_profile, + verbose=args.verbose, + ) def compare_options(parser): - parser.add_argument('--condition_files', type=str, nargs='+', - help='A set of condition plot files to process (can be gzipped)') - parser.add_argument('--control_files', type=str, nargs='+', - help='A set of control plot files to process (can be gzipped)') - parser.add_argument('-o', '--output_dir', default="results", - help='The output directory to use for all output files (default: results)') - parser.add_argument('-n', '--threads', type=int, default=1, - help='number of threads to use when processing (default: 1)') - parser.add_argument('--annotations', '-a', - help='If provided genes in this EMBL annotations file will expanded based on data in the plotfile.', - type=str, default=None) - parser.add_argument('--minimum_threshold', '-m', - help='Only include insert sites with this number or greater insertions', type=int, default=5) - parser.add_argument('--prime_feature_size', '-z', - help='Feature size when adding 5/3 prime block when --use_annotation', type=int, default=198) - parser.add_argument('--window_interval', '-l', help='Window interval', type=int, default=25) - parser.add_argument('--window_size', '-w', help='Window size', type=int, default=100) - parser.add_argument('-sp', '--snakemake_profile', type=str, - help='If provided, pass this directory onto snakemake. Assumes there is a file called "config.yaml" in that directory.') + parser.add_argument( + "--condition_files", + type=str, + nargs="+", + help="A set of condition plot files to process (can be gzipped)", + ) + parser.add_argument( + "--control_files", + type=str, + nargs="+", + help="A set of control plot files to process (can be gzipped)", + ) + parser.add_argument( + "-o", + "--output_dir", + default="results", + help="The output directory to use for all output files (default: results)", + ) + parser.add_argument( + "--disable_normalisation", + action='store_true', + default=False, + help="Don't normalise the plots prior to comparison (default: false)", + ) + parser.add_argument( + "-n", + "--threads", + type=int, + default=1, + help="number of threads to use when processing (default: 1)", + ) + parser.add_argument( + "--annotations", + "-a", + help="If provided genes in this EMBL annotations file will expanded based on data in the plotfile.", + type=str, + default=None, + ) + parser.add_argument( + "--minimum_threshold", + "-m", + help="Only include insert sites with this number or greater insertions", + type=int, + default=5, + ) + parser.add_argument( + "--minimum_proportion_insertions", + help="If the proportion of insertions is too low compared to control, dont call decreased insertions below this level", + type=float, + default=0.1, + ) + parser.add_argument( + "--prime_feature_size", + "-z", + help="Feature size when adding 5/3 prime block when --use_annotation", + type=int, + default=198, + ) + parser.add_argument( + "--window_interval", "-l", help="Window interval", type=int, default=25 + ) + parser.add_argument( + "--window_size", "-w", help="Window size", type=int, default=100 + ) + parser.add_argument( + "-sp", + "--snakemake_profile", + type=str, + help='If provided, pass this directory onto snakemake. Assumes there is a file called "config.yaml" in that directory.', + ) def compare_pipeline(args): @@ -110,12 +214,14 @@ def compare_pipeline(args): raise ValueError("Must have equal number of control and condition files") if len(args.control_files) <= 1: - raise ValueError("Must have 2 or more replicates of control and condition files") + raise ValueError( + "Must have 2 or more replicates of control and condition files" + ) fhh.ensure_output_dir_exists(args.output_dir) snakemake_config = os.path.join(args.output_dir, "analysis_config.yaml") - with open(snakemake_config, 'w') as ofql: + with open(snakemake_config, "w") as ofql: ofql.write(create_yaml_option("output_dir", args.output_dir)) ofql.write("condition_files:\n") for x in args.condition_files: @@ -123,29 +229,44 @@ def compare_pipeline(args): ofql.write("control_files:\n") for x in args.control_files: ofql.write("- " + x + "\n") + ofql.write(create_yaml_option("normalise", not args.disable_normalisation, bool=True)) ofql.write(create_yaml_option("threads", args.threads, num=True)) ofql.write(create_yaml_option("annotations", args.annotations)) ofql.write(create_yaml_option("minimum_threshold", args.minimum_threshold)) ofql.write(create_yaml_option("prime_feature_size", args.prime_feature_size)) ofql.write(create_yaml_option("window_interval", args.window_interval)) ofql.write(create_yaml_option("window_size", args.window_size)) + ofql.write( + create_yaml_option( + "minimum_proportion_insertions", args.minimum_proportion_insertions + ) + ) pipeline = find_pipeline_file("compare.smk") - start_snakemake(pipeline, snakemake_config, threads=args.threads, snakemake_profile=args.snakemake_profile, - verbose=args.verbose) + start_snakemake( + pipeline, + snakemake_config, + threads=args.threads, + snakemake_profile=args.snakemake_profile, + verbose=args.verbose, + ) -def start_snakemake(snakefile, snakemake_config, threads=1, snakemake_profile=None, verbose=False): +def start_snakemake( + snakefile, snakemake_config, threads=1, snakemake_profile=None, verbose=False +): if verbose: print("Using snakemake config files at: " + ", ".join([snakemake_config])) print("Starting snakemake pipeline") - cmd_list = ["snakemake", - "--snakefile=" + snakefile, - "--configfile=" + snakemake_config, - "--cores=" + str(threads), - "--printshellcmds"] + cmd_list = [ + "snakemake", + "--snakefile=" + snakefile, + "--configfile=" + snakemake_config, + "--cores=" + str(threads), + "--printshellcmds", + ] if snakemake_profile: cmd_list.append("--profile=" + snakemake_profile) @@ -187,11 +308,13 @@ def find_pipeline_file(pipeline_file): raise RuntimeError("Could not find nextflow pipeline file.") -def create_yaml_option(option, value, num=False): +def create_yaml_option(option, value, num=False, bool=False): opt = option + ": " if num: opt += str(value) + elif bool: + opt += "true" if value else "false" else: - opt += "\"" + str(value) + "\"" + opt += '"' + str(value) + '"' opt += "\n" return opt diff --git a/quatradis/tisp/cli.py b/quatradis/tisp/cli.py index 2ded00b..a547fbb 100644 --- a/quatradis/tisp/cli.py +++ b/quatradis/tisp/cli.py @@ -8,6 +8,7 @@ from quatradis.tisp.analyse import count_insert_sites from quatradis.tisp.normalise import NormalisePlots + def add_subparser(subparsers): plot_parser_desc = "TraDIS plot file tools" plot_parser = subparsers.add_parser("plot", help=plot_parser_desc) @@ -103,9 +104,13 @@ def normalise_plot_options(parser): parser.add_argument('--minimum_proportion_insertions', '-d', help='If the proportion of insertions is too low compared to control, dont call decreased insertions below this level', type=float, default=0.1) + parser.add_argument('-o', '--output_dir', type=str, default="", + help='The directory in which to put all output files (default: current working directory)') + parser.add_argument('-n', '--output_filename', type=str, default="normalised.plot.gz", + help='The name for the normalised output files. Normalised files will be distinguished by directory') def normalise_plots(args): - n = NormalisePlots(args.plot_files, args.minimum_proportion_insertions, output_temp_files=False) + n = NormalisePlots(args.plot_files, args.minimum_proportion_insertions, output_dir=args.output_dir, output_filename=args.output_filename, verbose=args.verbose) plotfiles, max_plot_reads = n.create_normalised_files() n.decreased_insertion_reporting(max_plot_reads) \ No newline at end of file diff --git a/quatradis/tisp/generator/from_values.py b/quatradis/tisp/generator/from_values.py index 897341a..7f497b4 100644 --- a/quatradis/tisp/generator/from_values.py +++ b/quatradis/tisp/generator/from_values.py @@ -1,6 +1,10 @@ +import os + import numpy import pyximport +from quatradis.util.file_handle_helpers import ensure_output_dir_exists + pyximport.install() from quatradis.tisp.write import write_plot_file, write_gzipped_plot_file @@ -10,12 +14,10 @@ class PlotFromValuesGenerator: """ Takes in arrays for forward and reverse integers and creates a new file """ - - def __init__(self, forward, reverse, filename, gzipped=False): + def __init__(self, forward, reverse, filename): self.forward = forward self.reverse = reverse self.filename = filename - self.gzipped = gzipped self.forward_length = len(self.forward) self.reverse_length = len(self.reverse) @@ -29,7 +31,7 @@ def construct_file(self): if len(self.reverse) == 0: self.reverse = numpy.zeros(p_len, dtype=float) - if self.gzipped: + if self.filename.endswith('.gz'): write_gzipped_plot_file(self.filename, self.forward, self.reverse, p_len) else: write_plot_file(self.filename, self.forward, self.reverse, p_len) diff --git a/quatradis/tisp/normalise.py b/quatradis/tisp/normalise.py index 4d2b780..01d097a 100644 --- a/quatradis/tisp/normalise.py +++ b/quatradis/tisp/normalise.py @@ -1,7 +1,6 @@ - -from tempfile import mkstemp +import os import numpy - +from quatradis.util.file_handle_helpers import ensure_output_dir_exists from quatradis.tisp.parser import PlotParser from quatradis.tisp.generator.from_values import PlotFromValuesGenerator @@ -9,9 +8,10 @@ class NormalisePlots: '''Given a set of plot files, find the one with the highest number of reads and normalise all the rest into new files''' - def __init__(self, plotfiles, minimum_proportion_insertions, output_temp_files=True, verbose=False): + def __init__(self, plotfiles, minimum_proportion_insertions, output_dir=".", output_filename="normalised.plot_gz", verbose=False): self.plotfiles = plotfiles - self.output_temp_files=output_temp_files + self.output_dir = output_dir + self.output_filename = output_filename self.verbose = verbose self.minimum_proportion_insertions = minimum_proportion_insertions self.plot_objs = self.read_plots() @@ -20,9 +20,10 @@ def create_normalised_files(self): plot_objs, max_plot_reads = self.normalise() output_files = [] for p in self.plotfiles: - output_filename = p + ".norm" - if self.output_temp_files: - fd, output_filename = mkstemp() + plotname = os.path.basename(p).split('.')[0] + out_dir = os.path.join(self.output_dir, plotname) + output_filename = os.path.join(out_dir, self.output_filename) + ensure_output_dir_exists(out_dir) pg = PlotFromValuesGenerator(plot_objs[p].forward, plot_objs[p].reverse, output_filename) pg.construct_file() output_files.append(output_filename) diff --git a/quatradis/tisp/parser.py b/quatradis/tisp/parser.py index 13b1684..a7a6f3c 100644 --- a/quatradis/tisp/parser.py +++ b/quatradis/tisp/parser.py @@ -140,7 +140,7 @@ def get_plot_file_length(plot_file, working_dir): def read(self): handle = PlotParser.create_file_handle(self.filename) - insert_site_array = pandas.read_csv(handle, delim_whitespace=True, dtype=float, engine='c', + insert_site_array = pandas.read_csv(handle, sep='\s+', dtype=float, engine='c', header=None).values self.genome_length = len(insert_site_array) @@ -176,7 +176,7 @@ def __init__(self, filename, minimum_threshold=0): self.split_lines() def split_lines(self): - insert_site_array = pandas.read_csv(self.filename, delim_whitespace=True, dtype=float, engine='c', + insert_site_array = pandas.read_csv(self.filename, sep='\s+', dtype=float, engine='c', header=None).values self.genome_length = len(insert_site_array) diff --git a/tests/py/comparison/scatterplot_test.py b/tests/py/comparison/scatterplot_test.py index 9a6ba1a..174a390 100644 --- a/tests/py/comparison/scatterplot_test.py +++ b/tests/py/comparison/scatterplot_test.py @@ -1,3 +1,4 @@ +import shutil import unittest import os from quatradis.comparison.scatterplot import ScatterPlot @@ -24,3 +25,4 @@ def test_valid(self): os.remove("scattertest_absscatter.png") os.remove("scattertest_linear.png") os.remove("scattertest_scatter.png") + shutil.rmtree("normalised") diff --git a/tests/py/tisp/normalise_test.py b/tests/py/tisp/normalise_test.py index 8284549..8243fde 100644 --- a/tests/py/tisp/normalise_test.py +++ b/tests/py/tisp/normalise_test.py @@ -1,40 +1,76 @@ import filecmp import os +import shutil import unittest from quatradis.tisp.normalise import NormalisePlots -class ErrorReadingFile(Exception): pass +class ErrorReadingFile(Exception): + pass -class InvalidFileFormat(Exception): pass +class InvalidFileFormat(Exception): + pass -data_dir = os.path.join('data', 'tisp', 'normalise') -plotparser_dir = os.path.join('data', 'tisp', 'parser') +data_dir = os.path.join("data", "tisp", "normalise") +plotparser_dir = os.path.join("data", "tisp", "parser") class TestNormalisePlots(unittest.TestCase): def test_big_differences(self): - p = NormalisePlots([os.path.join(data_dir, 'sample1'), os.path.join(data_dir, 'sample2')], 0.1) + p = NormalisePlots( + [os.path.join(data_dir, "sample1"), os.path.join(data_dir, "sample2")], + 0.1, + output_dir="normalised", + ) output_files, max_reads = p.create_normalised_files() self.assertEqual(2, len(output_files)) - self.assertTrue(filecmp.cmp(os.path.join(data_dir, 'sample2'), output_files[1])) - self.assertTrue(filecmp.cmp(os.path.join(data_dir, 'expected_sample1'), output_files[0])) + self.assertTrue(filecmp.cmp(os.path.join(data_dir, "sample2"), output_files[1])) + self.assertTrue( + filecmp.cmp(os.path.join(data_dir, "expected_sample1"), output_files[0]) + ) self.assertTrue(p.decreased_insertion_reporting()) + shutil.rmtree("normalised") def test_ignore_decreased_insertions(self): - p = NormalisePlots([os.path.join(data_dir, 'lowinsertions'), os.path.join(data_dir, 'highinsertions')], 0.1) + p = NormalisePlots( + [ + os.path.join(data_dir, "lowinsertions"), + os.path.join(data_dir, "highinsertions"), + ], + 0.1, + output_dir="normalised" + ) self.assertFalse(p.decreased_insertion_reporting()) def test_ignore_decreased_insertions_insert_sites(self): - p = NormalisePlots([os.path.join(data_dir, 'fewinsertions'), os.path.join(data_dir, 'manyinsertions')], 0.1) + p = NormalisePlots( + [ + os.path.join(data_dir, "fewinsertions"), + os.path.join(data_dir, "manyinsertions"), + ], + 0.1, + output_dir="normalised", + ) self.assertFalse(p.decreased_insertion_reporting()) def test_large_file_zipped(self): - p = NormalisePlots([os.path.join(plotparser_dir, 'Control2.out.CP009273.insert_site_plot.gz'), - os.path.join(plotparser_dir, 'Chloramrep2-MICpool.out.CP009273.insert_site_plot.gz')], 0.1) + p = NormalisePlots( + [ + os.path.join( + plotparser_dir, "Control2.out.CP009273.insert_site_plot.gz" + ), + os.path.join( + plotparser_dir, + "Chloramrep2-MICpool.out.CP009273.insert_site_plot.gz", + ), + ], + 0.1, + output_dir="normalised", + ) output_files, max_reads = p.create_normalised_files() self.assertTrue(p.decreased_insertion_reporting()) + shutil.rmtree("normalised") diff --git a/tests/py/util/mapper_test.py b/tests/py/util/mapper_test.py index f29cacc..ca932df 100644 --- a/tests/py/util/mapper_test.py +++ b/tests/py/util/mapper_test.py @@ -12,6 +12,7 @@ data_dir = os.path.join("data", "util", "mapper") + class MapperTest(unittest.TestCase): """ These unit tests are for the mapper functions @@ -22,29 +23,87 @@ def test_calc_read_len(self): self.assertEqual(44, read_len) def test_smalt_index_cmd(self): - index_cmd, exitcode = mapper.index_reference(os.path.join(data_dir, "smallref.fa"), "test.ref", 44, mapper="smalt", dry_run=True) - self.assertEqual("smalt index -k 13 -s 4 test.ref " + os.path.join(data_dir, "smallref.fa") + " > /dev/null 2>&1", index_cmd) + index_cmd, exitcode = mapper.index_reference( + os.path.join(data_dir, "smallref.fa"), + "test.ref", + 44, + mapper="smalt", + dry_run=True, + ) + self.assertEqual( + "smalt index -k 13 -s 4 test.ref " + + os.path.join(data_dir, "smallref.fa") + + " > /dev/null 2>&1", + index_cmd, + ) self.assertEqual(0, exitcode) def test_smalt_align_cmd(self): - align_cmd, exitcode = mapper.map_reads(os.path.join(data_dir, "test.fastq"), os.path.join(data_dir, "smallref.fa"), "test.ref", "mapped.out", 44, mapper="smalt", dry_run=True) - self.assertEqual("smalt map -x -n 1 test.ref " + os.path.join(data_dir, "test.fastq") + " 1> mapped.out 2> mapped.out.stderr", align_cmd) + align_cmd, exitcode = mapper.map_reads( + os.path.join(data_dir, "test.fastq"), + os.path.join(data_dir, "smallref.fa"), + "test.ref", + "mapped.out", + 44, + mapper="smalt", + dry_run=True, + ) + self.assertEqual( + "smalt map -x -n 1 test.ref " + + os.path.join(data_dir, "test.fastq") + + " 1> mapped.out 2> mapped.out.stderr", + align_cmd, + ) self.assertEqual(0, exitcode) def test_bwa_index_cmd(self): - index_cmd, exitcode = mapper.index_reference(os.path.join(data_dir, "smallref.fa"), "test.ref", 44, mapper="bwa", dry_run=True) - self.assertEqual("bwa index " + os.path.join(data_dir, "smallref.fa") + " > /dev/null 2>&1", index_cmd) + index_cmd, exitcode = mapper.index_reference( + os.path.join(data_dir, "smallref.fa"), + "test.ref", + 44, + mapper="bwa", + dry_run=True, + ) + self.assertEqual( + "bwa index " + os.path.join(data_dir, "smallref.fa") + " > /dev/null 2>&1", + index_cmd, + ) self.assertEqual(0, exitcode) def test_bwa_align_cmd(self): - align_cmd, exitcode = mapper.map_reads(os.path.join(data_dir, "test.fastq"), os.path.join(data_dir, "smallref.fa"), "test.ref", "mapped.out", 44, mapper="bwa", dry_run=True) - self.assertEqual("bwa mem -k 13 -t 1 " + os.path.join(data_dir, "smallref.fa") + " " + os.path.join(data_dir, "test.fastq") + " 1> mapped.out 2> mapped.out.stderr", align_cmd) + align_cmd, exitcode = mapper.map_reads( + os.path.join(data_dir, "test.fastq"), + os.path.join(data_dir, "smallref.fa"), + "test.ref", + "mapped.out", + 44, + mapper="bwa", + dry_run=True, + ) + self.assertEqual( + "bwa mem -k 13 -t 1 " + + os.path.join(data_dir, "smallref.fa") + + " " + + os.path.join(data_dir, "test.fastq") + + " 1> mapped.out 2> mapped.out.stderr", + align_cmd, + ) self.assertEqual(0, exitcode) # This test needs smalt installed def test_smalt(self): - mapper.index_and_align(os.path.join(data_dir, "test.fastq"), os.path.join(data_dir, "smallref.fa"), "test.ref", "mapped.out", mapper="smalt") - os.system("grep -v ^@ mapped.out > mapped.nohead.out && grep -v ^@ " + os.path.join(data_dir, "expected.smalt.sam") + " > expected.smalt.nohead.sam") + mapper.index_and_align( + os.path.join(data_dir, "test.fastq"), + os.path.join(data_dir, "smallref.fa"), + "test.ref", + "mapped.out", + mapper="smalt", + ) + os.system( + "grep -v ^@ mapped.out > mapped.nohead.out && grep -v ^@ " + + os.path.join(data_dir, "expected.smalt.sam") + + " > expected.smalt.nohead.sam" + ) self.assertTrue(filecmp.cmp("mapped.nohead.out", "expected.smalt.nohead.sam")) os.remove("test.ref.sma") os.remove("test.ref.smi") @@ -53,8 +112,18 @@ def test_smalt(self): # This test need bwa installed def test_bwa(self): - mapper.index_and_align(os.path.join(data_dir, "test.fastq"), os.path.join(data_dir, "smallref.fa"), "test.ref", "mapped.out", mapper="bwa") - os.system("grep -v ^@ mapped.out > mapped.nohead.out && grep -v ^@ " + os.path.join(data_dir, "expected.bwa.sam") + " > expected.bwa.nohead.sam") + mapper.index_and_align( + os.path.join(data_dir, "test.fastq"), + os.path.join(data_dir, "smallref.fa"), + "test.ref", + "mapped.out", + mapper="bwa", + ) + os.system( + "grep -v ^@ mapped.out > mapped.nohead.out && grep -v ^@ " + + os.path.join(data_dir, "expected.bwa.sam") + + " > expected.bwa.nohead.sam" + ) self.assertTrue(filecmp.cmp("mapped.nohead.out", "expected.bwa.nohead.sam")) os.system("rm mapped.*") os.remove("expected.bwa.nohead.sam") @@ -68,7 +137,9 @@ def test_sam2bam(self): os.system("rm *nice.bam*") def test_sam2bam_multithread(self): - mapper.sam2bam(os.path.join(data_dir, "expected.bwa.sam"), "nice.bam", threads=2) + mapper.sam2bam( + os.path.join(data_dir, "expected.bwa.sam"), "nice.bam", threads=2 + ) self.assertTrue(os.path.exists("nice.bam")) self.assertTrue(os.path.exists("nice.bam.bai")) self.assertTrue(os.path.exists("nice.bam.bamcheck")) diff --git a/tests/scripts/comparison_test.sh b/tests/scripts/comparison_test.sh index bc2168e..254cf44 100755 --- a/tests/scripts/comparison_test.sh +++ b/tests/scripts/comparison_test.sh @@ -11,7 +11,7 @@ echo -n "Checking 'tradis compare' help messages ... " ./tradis compare --help > /dev/null ./tradis compare figures --help > /dev/null ./tradis compare presence_absence --help > /dev/null -./tradis compare logfc_plot --help > /dev/null +./tradis compare insertion_sites --help > /dev/null ./tradis compare gene_report --help > /dev/null ./tradis compare split --help > /dev/null ./tradis compare essentiality --help > /dev/null @@ -46,7 +46,7 @@ echo "ok" echo -n "Checking 'tradis compare essentiality' ... " ESS_DATA_DIR=$DATA_DIR/comparison/essentiality ./tradis compare essentiality $ESS_DATA_DIR/combined.count.tsv -ESS_OUT=$ESS_DATA_DIR/combined.count.tsv.all.csv +ESS_OUT=$ESS_DATA_DIR/combined.count.tsv.essen.csv if [ ! -f "$ESS_OUT" ]; then echo "tradis compare essentiality test failed. $ESS_OUT does not exist." exit 1 diff --git a/tests/scripts/plot_test.sh b/tests/scripts/plot_test.sh index 1c20b9f..f5f4c36 100755 --- a/tests/scripts/plot_test.sh +++ b/tests/scripts/plot_test.sh @@ -37,6 +37,6 @@ rm -r temp_test echo "ok" echo -n "Checking 'tradis plot normalise' ... " -./tradis plot normalise $DATA_DIR/normalise/fewinsertions $DATA_DIR/normalise/manyinsertions > /dev/null 2>&1 -rm $DATA_DIR/normalise/*.norm +./tradis plot normalise --output_dir temp_test $DATA_DIR/normalise/fewinsertions $DATA_DIR/normalise/manyinsertions > /dev/null 2>&1 +rm -r temp_test echo "ok" \ No newline at end of file diff --git a/tradis b/tradis index a8133b3..076e2be 100755 --- a/tradis +++ b/tradis @@ -3,7 +3,7 @@ import argparse import sys -import pkg_resources # part of setuptools +import importlib.metadata import quatradis.util.cli as qutils import quatradis.pipelines.pipeline as pipelines @@ -11,7 +11,7 @@ import quatradis.tisp.cli as tisp import quatradis.comparison.cli as ca # Establish the software version from the package resources -version = pkg_resources.require("quatradis")[0].version +version = importlib.metadata.version("quatradis") def call_func(parser, args): """