From 9559db52f40231242c06a32f2a1cde4f0407bb17 Mon Sep 17 00:00:00 2001 From: Sarah Bastkowski Date: Sat, 24 Feb 2024 23:24:38 +0000 Subject: [PATCH] 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 --- docs/pipeline_combine.png | Bin 37436 -> 40366 bytes docs/pipeline_combine.pu | 2 +- quatradis/comparison/essentiality_analysis.py | 7 +- quatradis/comparison/scatterplot.py | 2 +- quatradis/comparison/split.py | 2 +- quatradis/comparison/tradis_comparison.R | 2 +- quatradis/pipelines/compare.smk | 72 ++--- 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 | 2 +- tests/py/comparison/scatterplot_test.py | 2 + tests/py/tisp/normalise_test.py | 58 +++- tests/scripts/plot_test.sh | 4 +- 15 files changed, 309 insertions(+), 133 deletions(-) diff --git a/docs/pipeline_combine.png b/docs/pipeline_combine.png index 6edad495282d86848ce5984cd4fe9a389761a7aa..5c813077c02e7ed16182c884fdcc5ccc6243afa9 100644 GIT binary patch literal 40366 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 JM_B9G{{tMi9gqM3 diff --git a/docs/pipeline_combine.pu b/docs/pipeline_combine.pu index 6549857..b0ccd9e 100644 --- a/docs/pipeline_combine.pu +++ b/docs/pipeline_combine.pu @@ -12,7 +12,7 @@ split again end split #lightblue:start tradis pipeline compare; - +:normalise plot files; split : create count file for original plot and original embl; diff --git a/quatradis/comparison/essentiality_analysis.py b/quatradis/comparison/essentiality_analysis.py index 17174a0..9f97200 100644 --- a/quatradis/comparison/essentiality_analysis.py +++ b/quatradis/comparison/essentiality_analysis.py @@ -1,7 +1,6 @@ import csv import os from dataclasses import dataclass -from tempfile import mkstemp from quatradis.comparison.essentiality import GeneEssentiality from quatradis.util.file_handle_helpers import ensure_output_dir_exists @@ -33,7 +32,7 @@ def get_all_gene_names(control_files, condition_files): 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: + 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"] @@ -51,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))) @@ -60,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))) 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 64cd049..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.essen.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,34 +107,9 @@ 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"), 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 ec5bf4f..a7a6f3c 100644 --- a/quatradis/tisp/parser.py +++ b/quatradis/tisp/parser.py @@ -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/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