From 134f593786ebb8acee2eaf1eb65b1706e7066afd Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Mon, 22 May 2023 12:00:01 -0700 Subject: [PATCH 01/16] Create compile_protein_cluster_prevalence_table.py --- ...ompile_protein_cluster_prevalence_table.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100755 src/scripts/compile_protein_cluster_prevalence_table.py diff --git a/src/scripts/compile_protein_cluster_prevalence_table.py b/src/scripts/compile_protein_cluster_prevalence_table.py new file mode 100755 index 0000000..84183e4 --- /dev/null +++ b/src/scripts/compile_protein_cluster_prevalence_table.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob +import numpy as np +import pandas as pd +from tqdm import tqdm + +pd.options.display.max_colwidth = 100 +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.5.18" + + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {} + + Create input using the following commands: + ``` + cut -f1,3 metaeuk_output/genomes/identifier_mapping.tsv > metaeuk_output/genomes/protein_to_genome.tsv + concatenate_dataframes.py -n -a 1 metaeuk_output/genomes/protein_to_genome.tsv mmseqs2_output/output/clusters.tsv | cut_table_by_column_index.py -f2,1,3 > input.tsv + ``` + """.format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + + parser.add_argument("-i","--input", type=str, default="stdin", help = "path/to/input.tsv [id_genome][id_protein][id_protein-cluster](No header) [Default: stdin]") + parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.tsv [Default: stdout]") + parser.add_argument("-b","--boolean", action="store_true", help = "Return True/False instead of integer counts") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # I/O + if opts.input == "stdin": + opts.input = sys.stdin + if opts.output == "stdout": + opts.output = sys.stdout + + # Read Input + df_input = pd.read_csv(opts.input, sep="\t", index_col=None, header=None) + genomes = sorted(df_input.iloc[:,0].unique()) + clusters = sorted(df_input.iloc[:,2].unique()) + + # Create array + A = np.zeros((len(genomes), len(clusters)), dtype=int) + + for _, (id_genome, id_protein, id_cluster) in tqdm(df_input.iterrows(), total=df_input.shape[0]): + i = genomes.index(id_genome) + j = clusters.index(id_cluster) + A[i,j] += 1 + + # Create output + df_output = pd.DataFrame(A, index=genomes, columns=clusters) + df_output.index.name = "id_genome" + df_output.columns.name = "id_protein-cluster" + + if opts.boolean: + df_output = df_output > 0 + + df_output.to_csv(opts.output, sep="\t") + + + + + + + + +if __name__ == "__main__": + main() From 3a11cde1686e809bea9169cbc2a65d7d716618ea Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Wed, 7 Jun 2023 14:47:12 -0700 Subject: [PATCH 02/16] 1.1.3b --- DEVELOPMENT.md | 5 ++ VERSION | 2 +- install/docker/Dockerfile | 45 ++---------- install/docker/build_docker_image.sh | 2 +- install/docker/dockerize_environments.sh | 7 ++ .../environments/devel/VEBA-amplicon_env.yml | 12 ++-- src/.DS_Store | Bin 10244 -> 0 bytes src/amplicon.py | 17 +++-- src/annotate.py | 5 +- src/binning-prokaryotic.py | 4 +- ...onsensus_genome_classification_unranked.py | 5 +- src/scripts/fastq_position_statistics.py | 39 ++++++++--- src/scripts/features.tsv.gz | Bin 11249 -> 0 bytes src/scripts/merge_generalized_mapping.py | 16 ++++- walkthroughs/adapting_commands_for_docker.md | 65 +++++++++++------- 15 files changed, 125 insertions(+), 99 deletions(-) create mode 100644 install/docker/dockerize_environments.sh delete mode 100644 src/.DS_Store delete mode 100644 src/scripts/features.tsv.gz diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index ea5509e..ea71def 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -5,6 +5,9 @@ ________________________________________________________________ #### Current Releases: +##### Release v1.1.3 +* Fixed minor error in `binning-prokaryotic.py` where the `--veba_database` argument wasn't utilized and only the environment variable `VEBA_DATABASE` could be used. +* Updated the Docker images to have `/volumes/input`, `/volumes/output`, and `/volumes/database` directories to mount. ##### Release v1.1.2 * Created Docker images for all modules @@ -234,6 +237,8 @@ ________________________________________________________________ #### Change Log: +* [2023.5.18] - Added `compile_protein_cluster_prevalence_table.py` script +* [2023.5.17] - Added `convert_table_to_fasta.py` script * [2023.5.16] - Created Docker images for all modules * [2023.5.16] - Replaced all absolute path symlinks with relative symlinks. * [2023.5.15] - Changed `prokaryotic_taxonomy.tsv` and `prokaryotic_taxonomy.clusters.tsv` in `classify-prokaryotic.py` (along with eukaryotic and viral) files to `taxonomy.tsv` and `taxonomy.clusters.tsv` for uniformity. diff --git a/VERSION b/VERSION index 45a1b3f..893ae19 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.2 +1.1.3b diff --git a/install/docker/Dockerfile b/install/docker/Dockerfile index 6849bd9..9c946c3 100644 --- a/install/docker/Dockerfile +++ b/install/docker/Dockerfile @@ -1,42 +1,4 @@ -# v2023.5.11 -# # ================================= -# # Ubuntu -# # ================================= -# FROM ubuntu:latest - -# ARG ENV_NAME - -# SHELL ["/bin/bash", "-c"] - -# WORKDIR /root/ - -# # Install required packages -# RUN apt-get update && \ -# apt-get install -y coreutils wget bzip2 ca-certificates curl git && \ -# apt-get clean && \ -# rm -rf /var/lib/apt/lists/* - -# # Retrieve VEBA repository -# RUN mkdir -p veba/ -# COPY ./install/ veba/install/ -# COPY ./src/ veba/src/ -# COPY ./VERSION veba/VERSION -# COPY ./LICENSE veba/LICENSE - -# # Install Miniconda -# RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh && \ -# bash miniconda.sh -b -p /opt/conda && \ -# rm ~/miniconda.sh && \ -# ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \ -# /opt/conda/bin/conda init bash && \ -# /opt/conda/bin/conda config --add channels jolespin && \ -# /opt/conda/bin/conda config --add channels bioconda && \ -# /opt/conda/bin/conda config --add channels conda-forge && \ -# /opt/conda/bin/conda update conda -y && \ -# /opt/conda/bin/conda install -c conda-forge mamba -y && \ -# /opt/conda/bin/mamba init bash && \ -# /opt/conda/bin/conda clean -afy - +# v2023.6.6 # ================================= # Miniconda3 # ================================= @@ -50,7 +12,10 @@ SHELL ["/bin/bash","-l", "-c"] WORKDIR /root/ # Data -RUN mkdir -p /data/ +RUN mkdir -p /volumes/input +RUN mkdir -p /volumes/output +RUN mkdir -p /volumes/database + # Retrieve VEBA repository RUN mkdir -p veba/ diff --git a/install/docker/build_docker_image.sh b/install/docker/build_docker_image.sh index 5d0af25..2a85930 100644 --- a/install/docker/build_docker_image.sh +++ b/install/docker/build_docker_image.sh @@ -10,7 +10,7 @@ echo -e "================" FILE=../environments/${ENV_NAME}.yml if [ -f "$FILE" ]; then NAME=$(echo $ENV_NAME | cut -f1 -d "_" | cut -c6-) - TAG="veba/${NAME}:${VERSION}" + TAG="jolespin/veba_${NAME}:${VERSION}" echo -e "Creating Docker image ${TAG} for ${ENV_NAME}" docker build --build-arg ENV_NAME=${ENV_NAME} -t ${TAG} -f Dockerfile ../../ else diff --git a/install/docker/dockerize_environments.sh b/install/docker/dockerize_environments.sh new file mode 100644 index 0000000..b77bc64 --- /dev/null +++ b/install/docker/dockerize_environments.sh @@ -0,0 +1,7 @@ +for ENV_NAME in $(ls ../environments/ | grep ".yml" | grep -v "VEBA-preprocess_env"); +do + ENV_NAME=$(echo $ENV_NAME | cut -f1 -d ".") + echo $ENV_NAME + + time(bash build_docker_image.sh ${ENV_NAME}) +done diff --git a/install/environments/devel/VEBA-amplicon_env.yml b/install/environments/devel/VEBA-amplicon_env.yml index 1e7daf8..7adf5c6 100644 --- a/install/environments/devel/VEBA-amplicon_env.yml +++ b/install/environments/devel/VEBA-amplicon_env.yml @@ -1,4 +1,4 @@ -name: VEBA-amplicon_env__2023.1.20 +name: VEBA-amplicon_env__2023.5.22 channels: - conda-forge - bioconda @@ -71,6 +71,7 @@ dependencies: - bioconductor-xvector=0.38.0=r42hc0cfd56_0 - bioconductor-zlibbioc=1.44.0=r42hc0cfd56_0 - biom-format=2.1.12=py38h26c90d9_2 + - biopython=1.81=py38h1de0b5d_0 - blast=2.13.0=hf3cf87c_0 - bleach=5.0.1=pyhd8ed1ab_0 - bokeh=3.0.3=pyhd8ed1ab_0 @@ -82,11 +83,11 @@ dependencies: - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2022.12.7=ha878542_0 + - ca-certificates=2023.5.7=hbcca054_0 - cachecontrol=0.12.11=pyhd8ed1ab_0 - cached_property=1.5.2=pyha770c72_1 - cairo=1.16.0=ha61ee94_1014 - - certifi=2022.12.7=pyhd8ed1ab_0 + - certifi=2023.5.7=pyhd8ed1ab_0 - cffi=1.15.1=py38h4a40e3a_3 - charset-normalizer=2.1.1=pyhd8ed1ab_0 - click=8.1.3=unix_pyhd8ed1ab_2 @@ -130,7 +131,7 @@ dependencies: - fribidi=1.0.10=h36c2ea0_0 - future=0.18.2=pyhd8ed1ab_6 - gcc_impl_linux-64=12.2.0=hcc96c02_19 - - genopype=2021.8.18=py_0 + - genopype=2023.5.15=py_0 - gettext=0.21.1=h27087fc_0 - gfortran_impl_linux-64=12.2.0=h55be85b_19 - giflib=5.2.1=h36c2ea0_2 @@ -287,7 +288,7 @@ dependencies: - oniguruma=6.9.8=h166bdaf_0 - openjdk=17.0.3=h58dac75_5 - openjpeg=2.5.0=h7d73246_1 - - openssl=1.1.1s=h0b41bf4_1 + - openssl=1.1.1t=h0b41bf4_0 - packaging=22.0=pyhd8ed1ab_0 - pandas=1.5.2=py38hdc8b05c_2 - pandoc=2.19.2=h32600fe_1 @@ -682,4 +683,3 @@ dependencies: - zlib=1.2.13=h166bdaf_4 - zstandard=0.19.0=py38h5945529_1 - zstd=1.5.2=h3eb15da_5 -prefix: /expanse/projects/jcl110/anaconda3/envs/VEBA-amplicon_env__2023.1.20 diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index a84deba80104ee4a201b134bc37a816c03494aed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMTWl0n7(V~Br89J(Qz*0yTUl5E#VWhpN?V1cT@<8JU|V`amf4+=PMDopc4oI| zX>3TmprFAg<1P54(P&HnHQt^SFVT2OC4mQxCL#}N@C9Ft@jr8Bq1|>9AGFapCz<)@ zob&(Z^8Md`&YrV`5NOM(5ki855FQ^YB@tg!BwA0ONs04Hs!;;k6QYq6QHX<5ouus- z_z3t2_z3t2_z3t2{2LI!J)0)Jib?1w0Mu48(XX0sHBN>t%e|}F6*_otaC9(dkWX8~KCmcQpTv>U=j7q+e zuj22H4_ZTUM{#m?o01)%xh~5xl7(+2xm(wU5>jouWjcy(YFXDnS2a}G)1#ZJHPmM1 zOx1R6Hv_jN4$vz^YPuX)6}#BcQH#0cC|C4kWQ;stq&zgp1?#FrxLM5^B3MNZ*bf-h?aZY?=+MQT8JlqsP7dACt z^Wf@rMIJm#XNF@h<-td=vS?BhTr*eubqWnT-_D#;)>4@Eg#velnN{K}xqEj{Z=aZu z=H!v2of${BOnbMMwNWOdY8%PCHD#GQvKp=LNm)iNZQ2Q`CZ!muoS`_{Cc}v9_i0#G zZKtx9VeGJMoi@|g>u84@S~sbxx8Jt1PP^M!ua50AEh%SfyEJT6&ucMk+ODOLj7uQA zI=e2gcBi4%Zd-A%iCk?H8NLQV~KE+OHrmCC!`!SkKN54nc zvO*2~A#|BaS{s`q)l}CnkmcT7Qa_l}oqMGa&%31!*U91@75l!+QmK_Ho#6$yX32H3 zJV0yOvl_lt&tTbdS?scLl9J*n-qa|HT?a6%C0k&o;fN@A8%k0$BvoMjtX(I{J$Z(+ zw9PAQX%*#O47M~5*{*>`uXtlrmiN-0I~3dL%xET#>LaiF7}oXMgGyHG9Kw~wE8T*n zuJ^E<8f{i}$0}5I;6(apbDLDvod(5F-YLg<=_@`VNxGTWaWz8^vPF##MNb@doe+wy zV9p2?eB#0xiiTNUz~3$nWEF{#4zdq}ZPm<@!Y4QO%OTH%Gkc;Feas>c5sDK$z z1+~xsiy#P#VFffnGqgY}#9#;Pgk3NI`ydG^P$3Oja3BYHxE~&Xhu~p23QxdO@C-Z) z&%q1u8k~aDa0cFk&){?T0=|R`@B>_gAK@~`ab;XNH;b#|>bV8n5-!9o<(6^HTnpFE zb#UEWKbK;I=L(TGP?(%oPH%9FBhSuzdjfZA>bMGQ*?Q-;j{iKz{JJ0w zul2XJU1cJpZh@`iW56Z;gm7_*@3NwZv11r-3j+<}qEeCCjS-_A;wkN=N~XO;yq?F1 zkt$erxp*Uwu_Dc2*~Y*vJVvL)xmht1SS?@-N@XnD7-$lB30S5@yj8$xl*-2zE|BXt zY!pR_(A7X-EyV)2^B7SQ|94USJo%nnBA3bUNcGt;7wTa-gke2wfGw~cI+5IQ=!U(J z00k*d32s6L4uTDbVFXD&3P<2kI0lcw<8T6r{xnjZ68%MZ30{U*;B|Nt-iCMJT{sIL z!pHCle2UaR2j9Xa_!+Jg5qkSXLc?#92|a%IKHPewuzMHLK5bpww@YhH*s {}".format(opts.forward_trim, os.path.join(output_directory, "forward_reads_trimming_suggestion", "position_to_trim.txt")), + "&&", ] else: cmd += [ @@ -72,6 +76,7 @@ def get_trim_cmd( input_filepaths, output_filepaths, output_directory, directori ] if opts.maximum_average_loss is not None: cmd += ["--maximum_average_loss {}".format(opts.maximum_average_loss)] + cmd += ["&&"] if opts.reverse_trim is not None: cmd += [ @@ -81,7 +86,6 @@ def get_trim_cmd( input_filepaths, output_filepaths, output_directory, directori ] else: cmd += [ - "&&", os.environ["determine_trim_position.py"], "-i {}".format(input_filepaths[1]), "-o {}".format(os.path.join(output_directory, "reverse_reads_trimming_suggestion")), @@ -111,10 +115,10 @@ def get_dada2_cmd( input_filepaths, output_filepaths, output_directory, director "--p-trunc-len-r ${REVERSE_TRIM_POSITION}", "--o-table {}".format(output_filepaths[0]), "--o-representative-sequences {}".format(output_filepaths[1]), - "--o-denoising-stats {}".format(output_filepaths[2]), - "--p-n-threads {}".format(opts.n_jobs), - "--p-min-overlap {}".format(opts.minimum_overlap), - opts.dada2_options, + "--o-denoising-stats {}".format(output_filepaths[2]), + "--p-n-threads {}".format(opts.n_jobs), + "--p-min-overlap {}".format(opts.minimum_overlap), + opts.dada2_options, ] return cmd @@ -566,6 +570,7 @@ def main(args=None): parser_trimming.add_argument("-m","--minimum_length", default=100,type=int, help = "Minimum length. If minimum quality value makes length shorter than this then an error will yield with which samples are responsible [Default: 100]") parser_trimming.add_argument("-w", "--window_size", default=4,type=int, help = "Window size [Default: 4]") parser_trimming.add_argument("-l", "--maximum_average_loss", type=float, help = "Maximum average loss for window size [Default: --window_size]") + parser_trimming.add_argument("--length_mode", type=str, default="longest", help = "{longest, shortest} [Default: longest]") # DADA2 parser_dada2 = parser.add_argument_group('DADA2 arguments') diff --git a/src/annotate.py b/src/annotate.py index e9badde..9daf129 100755 --- a/src/annotate.py +++ b/src/annotate.py @@ -13,7 +13,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.14" +__version__ = "2023.6.1" def get_diamond_cmd( input_filepaths, output_filepaths, output_directory, directories, opts, program): tmp = os.path.join(directories["tmp"], program) @@ -567,7 +567,8 @@ def configure_parameters(opts, directories): assert_acceptable_arguments(opts.diamond_sensitivity, {"", "fast", "mid-sensitive", "sensitive", "more-sensitive", "very-sensitive", "ultra-sensitive"}) assert_acceptable_arguments(opts.uniref, {"uniref90","uniref50"}) - assert bool(opts.identifier_mapping) != bool(opts.protein_clusters), "--identifier_mapping is for protein-level annotations and cannot be used with --protein_clusters" + if opts.identifier_mapping or opts.protein_clusters: + assert bool(opts.identifier_mapping) != bool(opts.protein_clusters), "--identifier_mapping is for protein-level annotations and cannot be used with --protein_clusters" # Set environment variables add_executables_to_environment(opts=opts) diff --git a/src/binning-prokaryotic.py b/src/binning-prokaryotic.py index 4f924d6..099f28d 100755 --- a/src/binning-prokaryotic.py +++ b/src/binning-prokaryotic.py @@ -12,7 +12,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.6.7" # Assembly def get_coverage_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -335,7 +335,7 @@ def get_checkm2_cmd(input_filepaths, output_filepaths, output_directory, directo "-x faa", # "--tmpdir {}".format(os.environ["TMPDIR"] if "TMPDIR" in os.environ else directories["tmp"]), # Hack around: OSError: AF_UNIX path too long "--tmpdir {}".format(os.path.join(directories["tmp"], "checkm2")), - "--database_path {}".format(os.path.join(os.environ["VEBA_DATABASE"], "Classify", "CheckM2", "uniref100.KO.1.dmnd")), + "--database_path {}".format(os.path.join(opts.veba_database, "Classify", "CheckM2", "uniref100.KO.1.dmnd")), opts.checkm2_options, "&&", diff --git a/src/scripts/consensus_genome_classification_unranked.py b/src/scripts/consensus_genome_classification_unranked.py index 6459fe3..3f030e3 100755 --- a/src/scripts/consensus_genome_classification_unranked.py +++ b/src/scripts/consensus_genome_classification_unranked.py @@ -9,7 +9,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.2.28" +__version__ = "2023.6.7" def main(args=None): # Path info @@ -47,7 +47,8 @@ def main(args=None): if opts.output == "stdout": opts.output = sys.stdout - if not opts.veba_database: + if opts.veba_database is None: + assert "VEBA_DATABASE" in os.environ, "Please set the following environment variable 'export VEBA_DATABASE=/path/to/veba_database' or provide path to --veba_database" opts.veba_database = os.environ["VEBA_DATABASE"] # Blacklist labels diff --git a/src/scripts/fastq_position_statistics.py b/src/scripts/fastq_position_statistics.py index f6d9eec..57605b3 100755 --- a/src/scripts/fastq_position_statistics.py +++ b/src/scripts/fastq_position_statistics.py @@ -9,13 +9,14 @@ pd.options.display.max_colwidth = 100 __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2022.10.24" +__version__ = "2023.5.23" -def statistics(fp, phred): +def statistics(fp, phred, length_mode): # Build table quality_table = list() failed_reads = list() + lengths = list() # for id,seq,quality in tqdm(pyfastx.Fastq(fp, build_index=False), desc="Reading fastq filepath: {}".format(fp), unit=" read"): file_open_function = {True:gzip.open, False:open}[fp.endswith(".gz")] with file_open_function(fp, "rt") as f: @@ -23,18 +24,33 @@ def statistics(fp, phred): try: quality = np.asarray(list(map(lambda q: ord(q) - phred, quality)))#.astype(int) quality_table.append(quality) + lengths.append(len(seq)) except UnicodeDecodeError as e: failed_reads.append(id) + n = len(quality_table) + if length_mode == "longest": + m = max(lengths) + A = np.empty((n, m)) + A[:] = np.nan - quality_table = np.stack(quality_table) + for i, row in enumerate(quality_table): + A[i,:len(row)] = row + + if length_mode == "shortest": + m = min(lengths) + A = np.empty((n,m)) + A[:] = np.nan + + for i, row in enumerate(quality_table): + A[i,:m] = row[:m] df_minmeanmax = pd.DataFrame([ - pd.Series(np.min(quality_table, axis=0), name="min"), - pd.Series(np.mean(quality_table, axis=0), name="mean"), - pd.Series(np.max(quality_table, axis=0), name="max"), + pd.Series(np.nanmin(A, axis=0), name="min"), + pd.Series(np.nanmean(A, axis=0), name="mean"), + pd.Series(np.nanmax(A, axis=0), name="max"), ]).T - df_quantiles = pd.DataFrame(np.quantile(quality_table, q=[0.25,0.5,0.75], axis=0).T, columns = ["q=0.25", "q=0.5", "q=0.75"]) + df_quantiles = pd.DataFrame(np.quantile(A, q=[0.25,0.5,0.75], axis=0).T, columns = ["q=0.25", "q=0.5", "q=0.75"]) df_output = pd.concat([df_minmeanmax, df_quantiles], axis=1) df_output.index = df_output.index.values + 1 df_output.index.name = "position" @@ -62,7 +78,8 @@ def main(args=None): parser.add_argument("-o","--output", default="stdout", type=str, help = "Output filepath [Default: stdout]") parser.add_argument("-f","--field", type=str, help = "Field when batch [min, mean, max, q=0.25, q=0.5, q=0.75]") parser.add_argument("-b","--basename", action="store_true", help = "Output basename for multiple fastq files") - parser.add_argument("--phred", type=int, default=33, help = "Phred offset [Default: 33]") + parser.add_argument("--length_mode", default="longest", type=str, help = "{shortest, longest} [Default: longest]") + parser.add_argument("-p", "--phred", type=int, default=33, help = "Phred offset [Default: 33]") # parser.add_argument("-r","--retain_index", action="store_true", help = "Keep fastq index created by `pyfastx`") @@ -73,7 +90,7 @@ def main(args=None): # Checks assert opts.phred in {33,64}, "--phred must be either 33 or 64. The following is invalid: {}".format(opts.phred) - + assert opts.length_mode in {"longest", "shortest"}, "--length_mode must be either longest or shortest" # Build stats table for single fastq file if len(opts.fastq_filepath) == 1: df_output = statistics(opts.fastq_filepath[0], phred=opts.phred) @@ -86,7 +103,7 @@ def main(args=None): name = fp if opts.basename: name = fp.split("/")[-1] - field_table[name] = statistics(fp, phred=opts.phred)[opts.field] + field_table[name] = statistics(fp, phred=opts.phred, length_mode=opts.length_mode)[opts.field] df_output = pd.DataFrame(field_table) # Build stats table for multiple fastq files but include all columns (results in multiindex) @@ -96,7 +113,7 @@ def main(args=None): name = fp if opts.basename: name = fp.split("/")[-1] - df = statistics(fp, phred=opts.phred) + df = statistics(fp, phred=opts.phred, length_mode=opts.length_mode) df.columns = df.columns.map(lambda x: (name, x)) dataframes.append(df) df_output = pd.concat(dataframes, axis=1) diff --git a/src/scripts/features.tsv.gz b/src/scripts/features.tsv.gz deleted file mode 100644 index efa4d2c3c311160436eec9c26fec19a48193b59f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11249 zcmV-pjB_t$TKp;wpZzr4M8e>^%J`w3TG|Nh~8 z_UhelSJ&s4XE=O3|NQkY=d%wNS8uPUYu=yX+}YLj%ggK8`TN&+b@>wiXioS4dG_k+ z)y3uc47a1pr~4lU`T4i^ujmd}hhP8k78iZMP2RqIH~aAB<<5~iX%1KK&aPkn^bWrq4*zyM>-^Q(ujkiiuP=Um z`S$(HMW4>RNykfG(v<$~B|hlYuP?9Ooc;3CKdx@ve{gZuwq1Aj?)?2P*Kf|&-FAh~ z^Ut&OylJ}4uDLmUgNsEo`xmY~Yi<^&&7#}=?ezOc_nCe9KC_Q=`0~kT%`7kJW9Q4? z&S%YRF{5#3r_F4-tii8~`2z2@%lUFOTg=zZ`u5O|-~IIV;_CPJ*Kf|R-@ZEe+vRWX z&QE{B6MKDnw3e&yPo}%SIN85>`R?rM`t2E=f42Yb?ep8n zzu?j|?EgbGi+1(m6|RQaoS(p=PH>lZKfzQ^rmHdB$>9;6e7L;0KF7a*OZ0$WoGjO? zdHa7)#OXg@zWWWo@8-+%)23;@|L#9$_XnTh^DSn}f5)wU=>B~eX3g@n>3;e!J6+A2 zB|c`eZ0D;5&ADke^93&Vmlv#>iqom6^=K5Ke@iRc=zJusV{Xl8?w(aD;=2a zGcTU&z0HA(%y^JF=#qF65&yBl3|exWTSAC^DaQLW+7v|0nThBEs{>RhnsH^5 zWr}6bcyU73QqmqHCGC~vw#Tt()!_Ty#+dgSuhqV)Bt63$tmjNS7Cje{*cAsot zLhnM(!WEsevJ%5EH56+V6O7@`zUsnLEPdiNXDRw2Nntb3N|vG8lq!8l%Q9|-x44p) zai?sEisR;^(!EzH8{DPpAc9pkavlmVsZYf+;glOvVp&^RtcZFaxk)~FC8`?iz@)+~ zauLkh62@mketQG_b*F8!U48@bxA(!{67;(n4Sk3I_749GV4)-Yx9#nrA3qP_r(MD2 zhch7J0)8Gx{(=i=*3FBH|9blxIC=fst80S%9mbo77;omg^V41T7~=PCen7i}H#z(` zUC-M!fqbz9+9!}N+9yGN+RPX21C%f3?RNI?W8DLJH(#HhE_U}p-md1$k04)u3FMU* zR1-J+evBnsEttvG%RZL95^_jh?2FQt3#-cDS&2LtpT{9PSbfgkDefe~ zob`&a0f>$@Tkb8u5fkf+0$AWDGH#x9QV!@-GTVNLDRB>16EPRYdSzmEmYd%4$fPwa z249P^X6R+Fyw7kRfPJ5(SE}wYcTt2=xj07Pih&gw0#izRCJP$}CW7(h3(Y`y{Zm-n&1oYBxT8u||V z%UkSs2>rV$^lxvETmV*=k73%bcQ4-j{^8;PIl%aFKyEghd3O)2D~f{M>ifg(J_k$; z-)sn%c+oxuOQ7%}EZce8%zz&lO7`AyKlGyQ(khlo*rc9Dz$aSRagZ zOghKjN*`e!2vr`oYStv3f!X7&Vpyb z5LMN9{uZXJ``Lg3yxW> zpj;-IR6QKFOff`Jd9|Q23MwjOwg<>VWpD-wLdqAYon8^NtLU@t8DfI%W6xrZ#As6V z3M?vn5FZsG%|u{51HkQoXyUYpIKaT#5TrnCEE`y~6&TiIgwQHZ6=gi}xhN>IU~UW? z5MhI1T6xq6O&QY6S`UX|B~P_R;j}f}3q%(!qbXV>i4SDhV7aP-aVwmR2z5&20r4*u zlLrlvh7ToE0S)U_r~~6Y)FCt0=9&bIq-UkK@O!F^j%zIt{NM@)#Ys{^XIAf^R-euB ztZ!Dk-RiVkuD?aBTHkj(>lqC_qoMC&)%uQTttgNX(c0Y}nf(FuHsDX&DNwEE>!*MR z^L|K-n)#Y;i3jGu76w;^cb@Z{xhW31U-!b+CY=dF)5kR2F6mN5L_gNI0&}IVKq!V+e6>nbgNBwiZeFf;Gu8s zKlIIvhMv*TcMpAYk6lAmlCWOi9=Z3>KXQ{bJ@Petbw`hUjUe^ZBVWz8t4EJ~xj8&? zU*WpI20{wp2Nf(8PoB|#kqLMW$f4YOK-UU-!XTy4naqj^0VQy%2UQN5Tn|JSmmsUX zh#CJ>hm&9{xVMR*s0`ee*`zXu3vkazNw+uMcGH~Vl=d4u@$LO5zMav~GaCBtiEnS8 z_-;L4O{tV-b9)3&ym=sgrW1esZU+cY+6ON-Pdx7>NgqLp`007K?cu`N%mcRM+d1M) zg2J(!X(*q}KodQnJ!R{xiY{_!?*dC>h)Gc2+8{#l3c;=N8 ztIQff2c%U96Eqy!=wt+YXmjJ!(7+0=`JLzK_5jh}gxU(407Y*ysP%Rejq=b}n zi5~CsEJe@=WHzv2h}Jn6784BGOvmi{SU8J#h4)}^q8~!CPaD|o_EBvY+qRva)doUx z-R?GDvadgN>DlgPyXtnQyWMX4S^D}1clyM-{@~m*a8=Nd4^%XfFW+6A&vr8!d`5#4 zf!)y=x2d`n2p5Hsu2~+B{7Dwsw(X18=MyKpO=cY#<7&B>Zy%)DRz#*2-~TaV{6u1{ z$=882cKBq6ggRbro+gc}ZD*_bif}R{qjiB5D@kcF#`Y0Ejp}(3RtghgQc6KD zmnyVV(nzk9hpoa=SCull2ZTi7LfgU>&rpz*$fsXGA;Hghfy~4_Sj{!M0rLf& zjHYHDjplBMxN)+wR3Mk$tC43%d4|4s8hX&jYA7MXL1LN7_()tPTWvqU+h>k>;$x{j zSYRzS`Qn`o7@Lnml*7eLmmsYW4os8VWO}Qa=&Kgq_ADb*sa&c1An42cO2lw$%i+;Utdt|m9BWAG6obAh<~_;V-QKI z|7{>7+am*g3dP&`u6qFD7EWpQV7z+@Z9Q+#PdD2K`EszM-JL|+ej#6;Y|tT%!ez=c zvw*M>5~K+S3`$KLf~tV>RMziZqrOqOzBNkJA>I1dCsgq6zV9!LW!3J}J;78OSj zjKmm&E<>WUVnE;oJz^u+PT0q)w2!JX5v=AQ#*u?9xmX1P8n-OfRPk;?sI>zddaacd z$pJnd8ZlC4#)ct7k420q_!y|p224>tp9$$8rx@3qsXg35#9;|>7c`H#7+VpQM=A#n z76Dy>woyEWe5(;GAc4`x-tBpjnM!mJaaI{(^qw+7k-2DccrIWyH%+r>cc)GBO#*5A z7+3%ijX#^lKLYEn?6vK1Apr}q=Wt;5$B}K<%okG;iC#Q`h;F`J-{nHu`Gz2|rn?qH zDM1_=;0{4=D(E!jlT{QN42fH3z<2^9r>`>%*~{eoAat$@!HFZbFjI;qNmd}Sy`tnJ ze7lI0Dh4et8txpPpLsG0xM@U!!u61Q1khMP*@Y@TA;wU2&DjDfQN~31LyBoU)xPwq zmZC(jcp{ZBCR?0VxDIha?j0k1Ae=BzJs4*uE5m0=kd?|%wgfOy3E}|o6GhwLRK%pR z-XkV5r!jZ#N-k_bv_yahl8Q(`0punZ0ukEw4kwmkFkkRm#I&3cCcq${%YfUPrkx@~ zyIZwi-+-5o4H(ASPLYAeKN|2|?Pk@@muoWMWp_OAr!$JX7cbwvzWCuE=hqh}9}BNP zzx?&>yWdYfQ;uyHs|P%>rPSiO{W4AbOn~Wb8!x)%$=c6mzF0j7Fi=&CN$v)487(x- zErW5W6d`9ptpc!;;gF-C+GA!RDavQS)CDLRC|43ef_PAw1)?Xx`Lk@KoC+=Q{*W6Q z_8WO3{S**73iwhO`Vltji-Wa}gJBpS3Ppnv3M#B7S6zjxMih~bS)(Xg0Cg@D1N96m z&Kf3+D3t8pa0{(dG+^Vc2zWCqgV)Iw??Ji{KoE|C4Os_|V3Q&1HPBV5$w9-pdnyfS zH}3U*P}%w^H%HK9CO8WesxeXr2o;w>&oDbt`oUO{w0kZPEpNzt@$gq&yZibeT0I8Q za)M7co5nwa=&pv;bgTJ-Dxh809*_LXifY+DR8&hMKfBHMf4Xe})N3_KsrCF&DrmZH zzIp;9;9Ttkj6fc)4k@8M*A`5KsUB8Y_DogF41P0ZpuiFFLQ#S*!}S(Fi7CjE#ClxT z_Zg}RP!d*{sw_A^_=&etQdJTnA?Z9+h4)MbDnLjI_kzPnt1)s94ny4y0ss<8nx0Cv z)Bky@kEUS1YhqxG=c?EC9`AihuY_)1`~l4 zD%z6d09FSrc#l9;_St2OX&|WtSB6pC%wT{#OZ^*c>wH&ceaS6a154qzRoX3!5qOSsC%OfcE zd60Hgp;i!3OY=x0BD@koGad&EIm<+4-a$)ZZVIwU;G3G1qlIsX%9WwFVnBcngVaOB zD0zU#iOd(Ha3*wRB7LQl#e&&+_E`sy5)B9^^xB_ zM*eCxjX#^lKO+CG7P#8Yo2h!-w#Nha6Y@W(i2*j4d^=IV?T%vBZob-0F>Cu|%xV_% zdt0`Vd$L{2G)7X$U)v0ct_=(O*`4<>iGT_v`GswGZhb7i0UV`$!hsfn=Htpp-rf6NOZ?V7bVwY zln}}VQUN-9WblbF1xQI7;QyQ{mUPPyl7_KRJE$dF1((+>=@>!9pj@DbMadzG6rGju znxl4#T5V)SL{cPBN{$NbK9UY{YE)Y5pnwm_Bp+?hV8+ggXw-9A#~RkL-GKS8*SqfP zTgT?Hbxd}#fzzV#kJfQlSK2g`Ya#RKjt8Epi{T4h1*@s=di|j9dYbx=-)%r`CWGB{ zPj(h`@kl&&Y z9tJMJo;X$FECjdoU|dEZu!9Lqa8e^U2f$)M$&?o$1W*qq^@WeZ^_1cQd$Y!5P_%+b zp&X@`p}KXT)_}gJE^lp#sKGt+9t6ZN>x(sg0YIhkGFklOGJvwlU{p2^8pts&`SeZ< z+44bX2OlnZ&=C2MR*D#m576&mHw9odoKuFK-hyDUE`mJRPO1oU0mu-hEVI^FVk;0ykKvw#nu zip9I9i^a=1fb%FlJ1GMP`Kv>;KJ=gsfk&?^WsJ&LYyuIcU>;#Q3JhMCQ59!%wvfXh z4mKA%21xE98%Wav?+=|Y|q$H9M}Q5^g+`Kg{g%_XY&8h zyHXP?w`|C4rYNGAAkC7yy$DD#6OP>l0 z(4_JC2<**v1xxyaKBxaJL;57IKHEZBZI5kEyN@eLcI$aNxiS2l-v6@+$*z0x;rGkm z{`vCyd`dqOZFqhD>gDB67ytG9yZgD7O*>!RORaR%VyiDDA)j9VwOUL)j$jU}DXYJH zGOG_0-#oNo%%Gh-sdrgcCbC4tvn;}kAqMYZget`ZA)hUi%=93~#?;(fPKz<9h03ad z4J>g3BOb~gevq1VC=4^L1#?4!AOt~x!NWiI!p1%+=HT2Aj}2vBdN|@#_~5M<1x5)Q zFVPifDr)($EE5d?eJ-W9PI?)Qx56`%9Ctn_2i*nVpB$Wca-k&4;a))rg5-koJ~2r~ z6sSN;x)8%#Wk~=BD93Tgy zq-sJ=R5^~-s-Tm1U<|f6syd84=K;9NV_ySHK0CR$4xc9(=c0o0bpf-If=fL@LUSc_ zh9Z%i@>q^)8`NkYnWJjFOOEN-;~SF*5mf1i!>BApMKk6K9~xXrL4xApA{JJrt>O^Y zwdWvhmNagJY{@KBQ-X7jk}Ogna_T60;cK6Hl{WhxDp!ggPmsDVLuHjp?Qk5_DkV(S zRK-tJCn+X#jsFFU@Fp5B@yWrk0;R%|crHh>yFoypD#xzvzJ8UXecbOtA&ADGP2(TJ zQhV3(vfj?SsmihHjtBmlR8jAKe|2$nHZA@A`SUL6b2(V*r=F@5^x~-%DlPS0KJX6< zih7TReNYNtf3iMweXm)$?bctYg)3LM#hunllkC>}(77pd@)DtSB=_Ct}B4yu9T zGC-sa?{yYj2U-uO4Y^fg<3R0Z3dC<8hw9uIMTuNhW}F7OtblvT1X@g3_9Gaem3{;} z0NYEHNFCu05a=x>EVR-#$H696ay9|ti&I6`mxO5!iEbFc72t+qRm#WF@TfIZ7&8F! zGoiEvNY~xzvhA9$9`GO6AzO;}-HgUQhWzEn#iPJyH!Z1X4+s8i%x|_Y-duaresIcG*7A~aDwJh(xF*WsE=Tz2#O$& z(F^LCha!UH2!8LWf?c?@F+$IW7MM%A+)-`JUr#c4WOGNk(CQ3{S^4-eO$&7oB z4*XRJM(rWh53F)Fia2OmFPg((00_$syhn<|9s!8@4T?jAM!>2TETyWHYNT|94-oTA zR$4YLApK1VDsqHN;yfzrh{&8gNCTk{pAV7D8-eiFb4pBug@oQRUkgvR$3?;eeQ+kO z;Qc-WbcouH^QsDPPK&gApFK5&CJ&G^un`TNCp^A;HNtCns6fkmf#BRrRTL#CN&?r( zcHC2)7fO(cBs}%sMUlBRGWr2b0_TPlp9#Oq8^C9Cy4`H*1Eb` zZdYqOt!DMrt@Ux)ZZ)H^XEgTFTD#e1Ip1_-6;!xbHJih`f0UqDP*Qw#TdtcoPg>9R zu6o`cdTAC@cB1zC5X(MM2^}6yFj_6a;V`9P1yEAT*=|5IM9fZUvYoU3%jwcMrB3?Ew7=)EWd!U1M(} zEezJmJd-)BZ&vH&@^sy-zkbF2an%Z-#n>|%`(O@t6|1J%(LZDmo5O*Bxyz>6b&uI$ zxBC|oL73rob6|(NC#Bwk`a2)8!}V@5Ln?MtW2IJb2b7<-Y+6=Oav21aSoPFdld_aO zLPC^05iJB3WbjV;p87b9wcPU2l|EPQ*;qRXS{}jiAwrfIS|bQk6%B9cEQi5^P?lVUtU1Phpk`cP7^)83n?=aH zObx1*d^%4W=!P<8sw7$s9EpdPfRTMMCgvQC%|iRE_cQ`QnY-`(cG2b0p+uG%mUcY<1z5pS8S|pTt@C7PMk~1fd z>|m6%{|rc&@dcQZT5=E4=TdY#FM> z=2Ww<%wPoG)3Ropi?>l%5J9Vl08=VJy;0oP3>t}U^N`r0!EzFvOTtG5{okA)3D&qjr1U(d(q)f44 zN;pR4RlJ0kp@j;vAv@TDKq32%Es;UvL2_LoMO9U$(VzAUG zH(F1LpFx+Dq|JuYS_j+^aO>p|Dy@8gTVj+39mB{V{Z!^G(B!m)dQ2f1gYTUwULguc ze6e^oHXdR6;!NUPm4=HCwZ%DjG1ZyTu#LMT(t88yJ>!v;3wyUoXrN;%i?Tp5r z(bxwl-4)}y-C;|Q@l~C zw*vwg_efRbsAB%$+9*Te%AYF`8U=}N)fTZbZx1R0ofltH3cD|)8S=RH_VeQ$Ix(z|3 zxhMNKlh8Qi>EslsiD(ofsImiI1+u_si!^VcxK&@E{AuyBK%k&i0lC_J+*2oFGG6Yv z%02NXDL%8I!MAvZ?agYtIo)iVZ}AM9Po7~r!|*c4=pul&_+XJ81 z;4M9wAHQqY$34Es1gYT$`KY!3C+ED*R#bj}0rQ3@?=;tFA^>_w#oMU?!P zAc6wHM)ZhP%Bo~Eixs{Agp@m84Q-^k_h5v8xrx0O6+tVHsy^G(UEXZA_>Q*S`g6IJ z|INqSe)4#ic(mP&#;5$s^c+8y@E6pt3_R`Dw@3c;X0F>8uP!gHFW&$D?sR{_?kz0( z;e_^heRJ{dcN!opcFXz6-G+C~d^bNCUS7X?bAEO5GTq+d{pE+N5C6D2 zY+QSFcK8y{4mX}pv!m^cUz|)E<6)8mB3Uus`G`;gG1X@n?BD{eUCOa4KDAPTOM#ne z>qAtuIxiMl>RuQmQ=yh`WohFbPs`SeSCyBdG6Wm7(J%$tYiRRFkmO7&0&0r2L<^M} z_r(|AL;2873{xz16@;>e!S)Diic)wH z7gekEi5kZo?Uxs6*)5I>+#dJWz3(*@8{y%3iH7x*yiJMxztQ-5_Rj|O)eT_Qp0>Mn z_bsr#`~>UNs57l>T6cF~UrtH79W5Z7y!m=@cVPB8Xsy8LrcC_mX=-;0>9=@{tz|cD zotK5dV^Nmu&p`V70R0?k6*J5j7Ai>6csnioETX{gvp7u4oS_r!Kz%YMdFDe1J|tK^ zGZ(zurW|A+CkZv zOlPMMqA}F=YmLW$_%w7HLJm0W19WV})I&&#mOvSLJslzm6^lu< zgd(v_>%2YGE2m|?a1oqp9{JgSB4}E42uI(X50iS=!QP8WWGSl086@(*qSxiLKB&%|T5$P!%2+J=pxjo&3sf#Vj?)J)1awLl6*-Qt|u zZygLKk0WG!&Tywvs8Ks6tkpKs=D7$C(EKs+L6NnvEK8PvmDiG4FKEvwSM)w3%5Xv9 zMn#;B8x`6FDDMTFF+PHlwD}jlG9aiRSSjnt@CyuaVAM#G1Q1Ny_|P`21>p_gqNc1Y zg9y-bk~XP=eLE1j0J!p0X=h6iJ%B`3N=Ujr?Slff#b`fY&J>d&ajy8ZGhNA;boR8} zsuzTQ`XC66o>supX2@CZ3y`5i6&hg9KC;|bS@0b8`0fC>Kp77J7&nfzZ;PM?(kT41 zVTbbe&GK}$Z8qNmJKZPPq2xUx08QWyJKb!zImq;GdE6!V`2Oem(|3o>t!N|Fsq%&w zPa>zKuC6=eG*fzSiWbHu1bhk%)N^1dOO8mtr&T{^SvEW;B5rP2g6!-EBa)*%H+~taCmd`4bz-+>P+! zg#SOe-nD;j51FI%0}bD7j(mLkVTFSM!LiLK&2a^XM z7}u5~zI#vsUNU%cirn^m0Bewn(Vs-X4=eYU{3uQfY@gv=;oJM71?~7k#!XuaPg_3A zEXlA1ub2|187WnK8~=x>2^(bx@i{cZvm3cCm`9*XaX~uz%5Ab%9Ok9yge|4 zio9YL<@+JZXou-E3OVF`L0}maM0x<1t5>tIajrp-5Qh zph1@v(}TCuhJ854sW7Oqrv>v6Y_v=b(o#XoG1nL6A}H}lTMAmx2WBN$ePuokmiCO5 zdZ3MPRaR+WK@tbRZAJANp&itPiGk8KI0FnFZ@_;i+JsV3+h(GGE@_bm(IndS+U=P{ zPzRA`hEw+{0|S;mTR9>kAV^410j33hlXZmnl{VAoZOZOz(8yk-)$bmmgRBThEJw&olFFckT5MRqXwOI zHul0)uuce0p>wneUYiIf(rsk-~=*LO4N719Suw6Rgb+mgfizk&1-<+AtQ{ z(jD%<*Yf#_3yYi0X1P1vwwvX*psBl0>kFVVn8A!@aK}?WE@E5L{>hZSY;TWzHl@)u z^X1eIOe^1>&K52oXEdf=!iGqtQJbj+1KNs>1u}g|O3`)=kn@zp5|VZShbI89&{YmG zQ#|&P=`fIPue3dQbXC}CQP5%2?p{gQT#U3`17wcY)L}+#tF&XoUePumb{rrrL5x)y zs0lga?r=+z(qWu-X5q=f%|&QE+8V_N81OTiOLK$g)Sm7ZZTk((rG26t)sUsMGn&Dz zxqMt)vf3RsmZD8Ujz_-edAiN9I0d)0e3H~|yT>_!gX6IvV7xC>yWmOn)JSIV%xD7* zR!0`Siz*xG6)o%N-8d>}eyw91SdU+b7oVq zBKN&bt~cbq6zyq`Pz%CH3oO7St)T5ips=U|rb-wP?&Px9gVOM9)H9>AM?e0SLd>F-)gq2yb;^_HK~$q+ zL<*F@PfEi=3bn@4LI8%RZmH=+1n;YgJ@s~hNYWpN@>Ifymt!OS6*m^9?K5M+u{|q} z3xrr+YYtHjnQ1PDK!5qbQzbqWS*h)|w-7m=wjKmM9ri@&6v$W&qhNOQF^|wyKF}Yt zGP$Sljd3vyGbP@peKaktR^V#- bTM8s0ZA$<4-v9sr|NjF3zgs#yZ*u?u$%%rT diff --git a/src/scripts/merge_generalized_mapping.py b/src/scripts/merge_generalized_mapping.py index 4bf0474..dcaff75 100755 --- a/src/scripts/merge_generalized_mapping.py +++ b/src/scripts/merge_generalized_mapping.py @@ -1,11 +1,12 @@ #!/usr/bin/env python import sys, os, argparse from collections import OrderedDict +import numpy as np import pandas as pd from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2022.4.12" +__version__ = "2022.5.23" def main(args=None): # Path info @@ -29,6 +30,7 @@ def main(args=None): parser.add_argument("-i", "--sample_index", type=int,default=-3, help = "Sample index in filepath (e.g., veba_output/mapping/global/[ID]/output/counts.tsv it would be -3) [Default: -3]") parser.add_argument("-a", "--allow_missing_values", action="store_true", help = "Allow missing values instead of filling with zeros") parser.add_argument("-e", "--remove_empty_features", action="store_true", help = "Remove empty features") + parser.add_argument("--pickle", type=str, help = "path/to/pandas.pkl output") # Options opts = parser.parse_args() @@ -47,15 +49,23 @@ def main(args=None): counts = counts[counts > 0] output[id_sample] = counts X = pd.DataFrame(output).T + if not opts.allow_missing_values: - X = X.fillna(0).astype(int) + X = X.fillna(0) + + if np.allclose(X, X.astype(int), rtol=1e-05, atol=1e-08, equal_nan=True): + X = X.astype(int) X.index.name = opts.sample_column_label X.columns.name = opts.feature_row_label X.to_csv(opts.output, sep=opts.sep) - print("There are n={} samples and m={} features in the concatenated output table.".format(*X.shape), file=sys.stderr) + if opts.pickle: + X.to_pickle(opts.pickle) + sparsity = (X.values == 0).ravel().mean() * 100 + print("There are n={} samples and m={} features in the concatenated output table ({}% sparse).".format(*X.shape, sparsity), file=sys.stderr) + if __name__ == "__main__": main() diff --git a/walkthroughs/adapting_commands_for_docker.md b/walkthroughs/adapting_commands_for_docker.md index 42b5b4d..f9fe942 100644 --- a/walkthroughs/adapting_commands_for_docker.md +++ b/walkthroughs/adapting_commands_for_docker.md @@ -21,64 +21,79 @@ Refer to the [Docker documentation](https://docs.docker.com/engine/install/). #### 2. Pull Docker image for the module -Let's say you wanted to use the `assembly.py` module. Download the Docker image as so: +Let's say you wanted to use the `preprocess` module. Download the Docker image as so: ``` VERSION=1.1.2 -docker image pull jolespin/veba_assembly:${VERSION} +docker image pull jolespin/veba_preprocess:${VERSION} ``` #### 3. Run Docker container One key difference with running a Docker container is that you need to specify the path IN the Docker container but it's pretty simple. Basically, we link a local directory to a container directory using the `--volume` argument. -For example, here's how we would run the `assembly.py` module. First let's just look at the options: +For example, here's how we would run the `preprocess.py` module. First let's just look at the options: ```bash # Version VERSION=1.1.2 # Image -DOCKER_IMAGE="jolespin/veba_assembly:${VERSION}" +DOCKER_IMAGE="jolespin/veba_preprocess:${VERSION}" -docker run --name VEBA-assembly --rm -it ${DOCKER_IMAGE} -c "assembly.py -h" +docker run --name VEBA-preprocess --rm -it ${DOCKER_IMAGE} -c "preprocess.py -h" ``` If we wanted to run it interactively, start the container with `bash` (it automatically loads the appropriate `conda` environment): ``` -docker run --name VEBA-assembly --rm -it ${DOCKER_IMAGE} -c "bash" +docker run --name VEBA-preprocess --rm -it ${DOCKER_IMAGE} -c "bash" ``` -Though, it's the `assembly.py` module so it you're running anything other than a toy dataset, then you probably want to run it on the grid so you can go to do something else. +Though, it's the `preprocess.py` module so it you're running anything other than a toy dataset, then you probably want to run it on the grid so you can go to do something else. -Below, we specify the `LOCAL_WORKING_DIRECTORY` which is just the current local directory. We also need to specify the `CONTAINER_WORKING_DIRECTORY` which will be `/data/` on the volume. We link these with the `--volume` argument so anything created in the `CONTAINER_WORKING_DIRECTORY` will get mirrored into the `LOCAL_WORKING_DIRECTORY`. That is where we want to put the output files. +Below, we specify the `LOCAL_WORKING_DIRECTORY` and in this case it's the local current working directory. We need to link the `CONTAINER_INPUT_DIRECTORY` to the `/volumes/input/` on the container. -Note: If we don't specify the `${CONTAINER_WORKING_DIRECTORY}` prefix for the output then the output will be stranded in the container. +For the output directory, it requires an additional step. We first need to specify the `LOCAL_OUTPUT_PARENT_DIRECTORY` and link this to the `CONTAINER_OUTPUT_DIRECTORY` which will be `/volumes/output/` on the container. After that we need to specify the `RELATIVE_OUTPUT_DIRECTORY` which is `veba_output/preprocess/` in this case. -```bash +We link these with the `--volume` argument so any file in the `LOCAL_WORKING_DIRECTORY` will be mirrored in the `CONTAINER_INPUT_DIRECTORY` and any files created in the `CONTAINER_OUTPUT_DIRECTORY` (i.e., the `RELATIVE_OUTPUT_DIRECTORY`) will be mirrored in the `LOCAL_OUTPUT_PARENT_DIRECTORY`. -# Directories -LOCAL_WORKING_DIRECTORY=. -CONTAINER_WORKING_DIRECTORY=/data/ +Note: If we don't link the local and container output directories then the output files will be stranded in the container. -# Inputs +```bash +# Directories +LOCAL_WORKING_DIRECTORY=$(pwd) +LOCAL_WORKING_DIRECTORY=$(realpath -m ${LOCAL_WORKING_DIRECTORY}) +LOCAL_OUTPUT_PARENT_DIRECTORY=../ +LOCAL_OUTPUT_PARENT_DIRECTORY=$(realpath -m ${LOCAL_OUTPUT_PARENT_DIRECTORY}) +#LOCAL_DATABASE_DIRECTORY=~/VDB-test/ +#LOCAL_DATABASE_DIRECTORY=$(realpath -m ${LOCAL_DATABASE_DIRECTORY}) + +CONTAINER_INPUT_DIRECTORY=/volumes/input/ +CONTAINER_OUTPUT_DIRECTORY=/volumes/output/ +#CONTAINER_DATABASE_DIRECTORY=/volumes/database/ + +# Parameters ID=S1 R1=Fastq/${ID}_1.fastq.gz R2=Fastq/${ID}_2.fastq.gz - -# Output -OUTPUT_DIRECTORY=veba_output/assembly +NAME=VEBA-preprocess__${ID} +RELATIVE_OUTPUT_DIRECTORY=veba_output/preprocess/ # Command -CMD="assembly.py -1 ${CONTAINER_WORKING_DIRECTORY}/${R1} -2 ${CONTAINER_WORKING_DIRECTORY}/${R2} -n ${ID} -o ${CONTAINER_WORKING_DIRECTORY}/${OUTPUT_DIRECTORY}" +CMD="preprocess.py -1 ${CONTAINER_INPUT_DIRECTORY}/${R1} -2 ${CONTAINER_INPUT_DIRECTORY}/${R2} -n ${ID} -o ${CONTAINER_OUTPUT_DIRECTORY}/${RELATIVE_OUTPUT_DIRECTORY} -x ${CONTAINER_DATABASE_DIRECTORY}/Contamination/chm13v2.0/chm13v2.0" +# Docker +DOCKER_IMAGE="jolespin/veba_preprocess:1.1.2" docker run \ - --name VEBA-assembly__${ID} \ - --rm \ - --volume ${LOCAL_WORKING_DIRECTORY}:${CONTAINER_WORKING_DIRECTORY} \ - ${DOCKER_IMAGE} \ - -c "${CMD}" + --name ${NAME} \ + --rm \ + --volume ${LOCAL_WORKING_DIRECTORY}:${CONTAINER_INPUT_DIRECTORY}:ro \ + --volume ${LOCAL_OUTPUT_PARENT_DIRECTORY}:${CONTAINER_OUTPUT_DIRECTORY}:rw \ + #--volume ${LOCAL_DATABASE_DIRECTORY}:${CONTAINER_DATABASE_DIRECTORY}:ro \ + ${DOCKER_IMAGE} \ + -c "${CMD}" + ``` #### 4. Get the results @@ -88,13 +103,13 @@ Now that the container has finished running the commands. Let's view the results To view it all: ``` -tree veba_output/assembly/${ID}/ +tree ${LOCAL_OUTPUT_PARENT_DIRECTORY}/veba_output/preprocess/${ID}/ ``` or just the output: ``` -ls -lhS veba_output/assembly/${ID}/output +ls -lhS ${LOCAL_OUTPUT_PARENT_DIRECTORY}/veba_output/preprocess/${ID}/output ``` From a2da3248102cceb16c150a6296d61687e8674728 Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Fri, 16 Jun 2023 11:42:33 -0700 Subject: [PATCH 03/16] v1.1.3b (cluster.py and classify-prokaryotic.py) --- .DS_Store | Bin 0 -> 14340 bytes DEVELOPMENT.md | 34 ++-- README.md | 2 + src/.DS_Store | Bin 0 -> 8196 bytes src/README.md | 2 +- src/binning-prokaryotic.py | 60 +++++--- src/classify-eukaryotic.py | 3 +- src/classify-prokaryotic.py | 25 ++- src/classify-viral.py | 2 +- src/cluster.py | 2 +- src/phylogeny.py | 4 +- src/scripts/.DS_Store | Bin 0 -> 6148 bytes src/scripts/bgcs.faa.gz | Bin 7646 -> 0 bytes src/scripts/compile_krona.py | 36 ++++- .../consensus_genome_classification.py | 32 ++-- src/scripts/global_clustering.py | 145 +++++++++++------- src/scripts/local_clustering.py | 126 +++++++++------ src/scripts/mmseqs2_wrapper.py | 18 ++- .../reformat_representative_sequences.py | 11 +- src/scripts/synopsis.tsv.gz | Bin 245 -> 0 bytes src/scripts/table_to_fasta.py | 77 ++++++++-- src/scripts/type_counts.tsv.gz | Bin 135 -> 0 bytes walkthroughs/end-to-end_metagenomics.md | 2 +- 23 files changed, 399 insertions(+), 182 deletions(-) create mode 100644 .DS_Store create mode 100644 src/.DS_Store create mode 100644 src/scripts/.DS_Store delete mode 100644 src/scripts/bgcs.faa.gz delete mode 100644 src/scripts/synopsis.tsv.gz delete mode 100644 src/scripts/type_counts.tsv.gz diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..cb119c56d38772cf0a7f1d7b25e9eea6ed365daf GIT binary patch literal 14340 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 literal 0 HcmV?d00001 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index ea71def..edb9d3e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -8,6 +8,10 @@ ________________________________________________________________ ##### Release v1.1.3 * Fixed minor error in `binning-prokaryotic.py` where the `--veba_database` argument wasn't utilized and only the environment variable `VEBA_DATABASE` could be used. * Updated the Docker images to have `/volumes/input`, `/volumes/output`, and `/volumes/database` directories to mount. +* Replaced `prodigal` with `pyrodigal` as it is faster and under active development. +* Added support for missing classifications in `compile_krona.py` and `consensus_genome_classification.py`. +* Updated `GTDB-Tk` from version `2.1.3` → `2.3.0` and `GTDB` from version `r202_v2` → `r214`. Changed `${VEBA_DATABASE}/Classify/GTDBTk` → `${VEBA_DATABASE}/Classify/GTDB`. Added `gtdb_r214.msh` to `GTDB` database for ANI screening. +* Added pangenome and singularity tables to `cluster.py` (and associated global/local clustering scripts) to output automatically. ##### Release v1.1.2 * Created Docker images for all modules @@ -23,7 +27,7 @@ ________________________________________________________________ * Removed "python" prefix for script calls and now uses shebang in script for executable. Also added single paranthesis around script filepath (e.g., `'[script_filepath]'`) to escape characters/spaces in filepath. * Added support for `index.py` to accept individual `--references [file.fasta]` and `--gene_models [file.gff]`. * Added `stdin` support for `scaffolds_to_bins.py` along with the ability to input genome tables [id_genome][filepath]. Also added progress bars. -* As a result of [issues/22](https://github.com/jolespin/veba/issues/22), `assembly.py`, `assembly-sequential.py`, `binning-*.py`, and `mapping.py` will use `-p --countReadPairs` for `featureCounts` and updates `subread 2.0.1 -> subread 2.0.3`. For `binning-*.py`, long reads can be used with the `--long_reads` flag. +* As a result of [issues/22](https://github.com/jolespin/veba/issues/22), `assembly.py`, `assembly-sequential.py`, `binning-*.py`, and `mapping.py` will use `-p --countReadPairs` for `featureCounts` and updates `subread 2.0.1 → subread 2.0.3`. For `binning-*.py`, long reads can be used with the `--long_reads` flag. * Updated `cluster.py` and associated `global_clustering.py`/`local_clustering.py` scripts to use `mmseqs2_wrapper.py` which now automatically outputs representative sequences. * Added `check_fasta_duplicates.py` script that gives `0` and `1` exit codes for fasta without and with duplicates, respectively. Added `reformat_representative_sequences.py` to reformat representative sequences from `MMSEQS2` into either a table or fasta file where the identifers are cluster labels. Removed `--dbtype` from `[global/local]_clustering.py`. Removed appended prefix for `.graph.pkl` and `dict.pkl` in `edgelist_to_clusters.py`. Added `mmseqs2_wrapper.py` and `hmmer_wrapper.py` scripts. * Added an option to `merge_generalized_mapping.py` to include the sample index in a filepath and also an option to remove empty features (useful for Salmon). Added an `executable='/bin/bash'` option to the `subprocess.Popen` calls in `GenoPype` to address [issues/23](https://github.com/jolespin/veba/issues/23). @@ -38,7 +42,7 @@ ________________________________________________________________ * Added `--dastool_minimum_score` to `binning-prokaryotic.py` module * Added a wrapper around `STAR` aligner * Updated `merge_generalized_mapping.py` script to take in BAM files instead of being dependent on a specific directory. -* Added option to have no header in `subst_table.py` +* Added option to have no header in `subset_table.py` ##### Release v1.1.0 @@ -207,36 +211,44 @@ ________________________________________________________________ #### Path to `v2.0.0`: **Definitely:** - +* Consistent usage of the following terms: 1) dataframe vs. table; 2) protein-cluster vs. orthogroup. +* Add support for `FAMSA` in `phylogeny.py` * Create a `assembly-longreads.py` module that uses `MetaFlye` * Create a `noncoding.py` module that uses `tRNASCAN-SE` and other goodies. -* Expand Microeukaryotic Protein Database -* Automated consensus protein cluster annotations. First need to create a hierarchical naming scheme that uses NR > KOFAM > Pfam. -* Create a wrapper around `hmmsearch` that takes in score cutoffs and outputs a useable table. This will be used in place of `KOFAMSCAN` which creates thousands of intermediate files. +* Expand Microeukaryotic Protein Database to include more fungi (Mycocosm) * Add MAG-level counts to prokaryotic and eukaryotic. Add optional bam file for viral binning, if so then add MAG-level counts * Support genome table input for `biosynthetic.py`, `phylogeny.py`, `index.py`, etc. * Install each module via `bioconda` * Add checks for `annotate.py` to ensure there are no proteins > 100K in length. -* Add support for `STAR` in `mapping.py` and `index.py`. This will require adding the `exon` field to `Prodigal` GFF file (`MetaEuk` modified GFF files already have exon ids). +* Add support for `Salmon` in `mapping.py` and `index.py`. This can be used instead of `STAR` which will require adding the `exon` field to `Prodigal` GFF file (`MetaEuk` modified GFF files already have exon ids). * Speed up `binning-eukaryotic.py` by accessing `BUSCO` backends and only running gene calls for genes relevant to genome. If it passes `BUSCO` filters, then run actual gene calls. * Build a clustered version of the Microeukaryotic Protein Database that is more efficient to run. **Probably (Yes)?:** + * Add [iPHoP](https://bitbucket.org/srouxjgi/iphop/src/main/) to `binning-viral.py`. * Add a `metabolic.py` module * Swap [`TransDecoder`](https://github.com/TransDecoder/TransDecoder) for [`TransSuite`](https://github.com/anonconda/TranSuite) -* Add support for `Anvi'o` object export in `cluster.py` * Add spatial coverage to `coverage.py` script like in `mapping.py` module? Maybe just the samtools coverage output. +* Reimplement `KOFAMSCAN` (which creates thousands of intermediate files) using `hmmer_wrapper.py`. **...Maybe (Not)?** * Add `VAMB` as an option for `binning-prokaryotic.py` (requires python >= 3.7,<3.8) +* Add support for `Anvi'o` object export in `cluster.py`. Installation is quite involved as of 2023.6.12 ________________________________________________________________ -#### Change Log: +#### Change Log: +* [2023.6.16] - Compiled and pushed `gtdb_r214.msh` mash file to [Zenodo:8048187](https://zenodo.org/record/8048187) which is now used by default in `classify-prokaryotic.py`. It is now included in `VDB_v5.1`. +* [2023.6.15] - Cleaned up global and local clustering intermediate files. Added pangenome tables and singelton information to outputs. +* [2023.6.12] - Changed `${VEBA_DATABASE}/Classify/GTDBTk` → `${VEBA_DATABASE}/Classify/GTDB`. +* [2023.6.12] - Replace `prodigal` with `pyrodigal` in `binning-prokaryotic.py` (`prodigal` is still in environment b/c `DAS_Tool` dependency). +* [2023.6.12] - `consensus_genome_classification.py` now based missing classifications off of a missing weight value. Defaults for unclassified labels are `Unclassified prokaryote`, `Unclassified eukaryote`, and `Unclassified virus` for the various classification modules. Also changed "id_genome_cluster" to "id" and "genomes" to "components" to generalize for eukaryotic classification. +* [2023.6.12] - `global_clustering.py` and `local_clustering.py` (accessible through `cluster.py`) now outputs NetworkX graph and Python dictionary pickled objects. +* [2023.6.12] - Added support for missing values and unclassified taxa in `compile_krona.py` and `consensus_genome_classification.py`. * [2023.5.18] - Added `compile_protein_cluster_prevalence_table.py` script * [2023.5.17] - Added `convert_table_to_fasta.py` script * [2023.5.16] - Created Docker images for all modules @@ -252,7 +264,7 @@ ________________________________________________________________ * [2023.5.8] - Removed "python" prefix for script calls and now uses shebang in script for executable. Also added single paranthesis around script filepath (e.g., `'[script_filepath]'`) to escape characters/spaces in filepath. * [2023.5.8] - Added support for `index.py` to accept individual `--references [file.fasta]` and `--gene_models [file.gff]`. * [2023.4.25] - Added `stdin` support for `scaffolds_to_bins.py` along with the ability to input genome tables [id_genome][filepath]. Also added progress bars. -* [2023.4.23] - As a result of [issues/22](https://github.com/jolespin/veba/issues/22), `assembly.py`, `assembly-sequential.py`, `binning-*.py`, and `mapping.py` will use `-p --countReadPairs` for `featureCounts` and updates `subread 2.0.1 -> subread 2.0.3`. For `binning-*.py`, long reads can be used with the `--long_reads` flag. +* [2023.4.23] - As a result of [issues/22](https://github.com/jolespin/veba/issues/22), `assembly.py`, `assembly-sequential.py`, `binning-*.py`, and `mapping.py` will use `-p --countReadPairs` for `featureCounts` and updates `subread 2.0.1 → subread 2.0.3`. For `binning-*.py`, long reads can be used with the `--long_reads` flag. * [2023.4.20] - Updated `cluster.py` and associated `global_clustering.py`/`local_clustering.py` scripts to use `mmseqs2_wrapper.py` which now automatically outputs representative sequences. * [2023.4.17] - Added `check_fasta_duplicates.py` script that gives `0` and `1` exit codes for fasta without and with duplicates, respectively. Added `reformat_representative_sequences.py` to reformat representative sequences from `MMSEQS2` into either a table or fasta file where the identifers are cluster labels. Removed `--dbtype` from `[global/local]_clustering.py`. Removed appended prefix for `.graph.pkl` and `dict.pkl` in `edgelist_to_clusters.py`. Added `mmseqs2_wrapper.py` and `hmmer_wrapper.py` scripts. * [2023.4.13] - Added an option to `merge_generalized_mapping.py` to include the sample index in a filepath and also an option to remove empty features (useful for Salmon). Added an `executable='/bin/bash'` option to the `subprocess.Popen` calls in `GenoPype` to address [issues/23](https://github.com/jolespin/veba/issues/23). @@ -298,7 +310,7 @@ ________________________________________________________________ * [2022.03.14] - Created a `binning_wrapper.py` to normalize the binning process and add --minimum_genome_length capabilities. This is useful for eukaryotic binning but more complicated for prokaryotic binning because the current pipeline is hardcoded to handle errors on iterative binning. Also switched to CoverM for all coverage calculations because it's faster. Split out prokaryotic, eukaryotic, and viral binning environments. For eukaryotic binning, I've removed EukCC and use BUSCO v5 instead. * [2022.03.01] - Added a domain classification script that is run during prokaryotic binning. I've created a hack that moves all of the eukaryotic genomes to another directory to allow for proper gene calls in a separate module. This hack will remain until DAS_Tool can handle custom gene sets because it cannot in the current version. The other option is to remove * [2022.02.24] - Added saf file to `assembly.py` and feature counts of scaffolds/transcripts -* [2022.02.22] - Made the original `preprocess.py` -> `preprocess-kneaddata.py` and the new `preprocess.py` a wrapper around `fastq_preprocessor` +* [2022.02.22] - Made the original `preprocess.py` → `preprocess-kneaddata.py` and the new `preprocess.py` a wrapper around `fastq_preprocessor` * [2022.02.22] - Made the `index.py` module * [2022.02.22] - `concatenate_fasta.py` and `concatenate_gff.py` * [2022.02.02] - `consensus_genome_classification.py` diff --git a/README.md b/README.md index 6781878..050ba8f 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Please refer to the [*Installation and Database Configuration Guide*](install/RE **Current Stable Version:** [`v1.1.2`](https://github.com/jolespin/veba/releases/tag/v1.1.2) +**Current Developmental Version:** v1.1.3b + Versions `v1.x.x` are installed using preconfigured `conda` environments. I'm aiming for `bioconda` packages for each module in the `v2.0.0` release. ___________________________________________________________________ diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5b923fea17128cc1bae3211deceb9954a305e185 GIT binary patch literal 8196 zcmeHMU2GIp6h3EK=$$FZwDb=rOIHd8YYR(T`A4=tfJn8_?LR-u?9NC>rZZ({b{D8M zeIi0k)ENAG^_LeOOhjW$#24cOFA_kFi7)z~J{l8W(0K0LSwdU-(g%OS++^;#=bm%! z%=zX!b8~kA0K4+WCV&P2V03Y*R#JD5#_jx?*Q9W!oFq~_z$EzKWs2Tp#-8ULO+*Mp z2t)`(2t)`(2>cfipgo&6w!pqGtWh5!5Fv11BEY{7ak{unhH^&8;L|}}a0DR9j{rfV zF`WZ~n0P3Yp_~yi2&FZ}=>fw~438Kn!pR=z?j)0;oDoul1B!6K@Xi>XP%yYV#l-`6 zz@(5-A0ZGSFdqS4J{90W7II+SeSUW{WcfHVXtyTViXXCBA98B|f#SjYb{qo2RE`R@=03M}PXHJ>j_LMIIP_3t;)2ye`a7%JvG~ z8Jj0nR#i&s+fq`pQmG9NDI+6R)^$?WSu?G!@F=VOq#n$f1)QDV$4jeKS>4X& zZQVCJZJRrLBffds=c6&t&H3GdV_H@7p4o4bpH(n>$eTi|`AU70zU4aJkeTyHHYF3{ z;pNNAYGZ32el+pe_Q$*T9q7BU{DBp!Qmxbs7c9@p*ydo-GW_vT-OHMeVL2zp2sX#J zp0UiFT!k(2fTK^CH`KcNhE)%0+Fb9_DkJ%fbt-T9XVfOf#623dhh+65cEq5Ee9ATW zibi138ntzd9p>}hIg@%Zt*qCYWmRVb9;GanXjL}KswWSeq<1Sq+msHb8uFm6XG~j6 zZBe!{^?*EF5Sfd~YGoIbN3!n31SR8%xun*`)UVhve&b%<^ZO=Er}RikZ7*YE-ngDK z`-;S@QpA&t1uu=}dB`5a^4(m3MsK60jq;^B4Z}1j>4v&`RXe1mn0lRJRLdz?#ASLH zg3_!h*(_d3fT&zEhg^K8S_NyM19rke7zG20@FHA<*Wg{a3|HVY_!7Q`>u?ip!T0bJ z{0hIrZCrvC$WX>=T#2<kBTUfhShIEX_yf}?m0PvAH{izo3Ep2aDg z#^>=IzJxF1EBF?^jql)l_ &+#gLf!FXR-okJ27yJ!x*%Tm8jB$x?y8XQOl@~", + os.path.join(directories["tmp"], "tmp.fasta"), + + "&&", + + os.environ["pyrodigal"], "-p meta", - "-g {}".format(opts.prodigal_genetic_code), + "-i {}".format(os.path.join(directories["tmp"], "tmp.fasta")), + "-g {}".format(opts.pyrodigal_genetic_code), "-f gff", "-d {}".format(os.path.join(output_directory, "gene_models.ffn")), "-a {}".format(os.path.join(output_directory, "gene_models.faa")), + "--min-gene {}".format(opts.pyrodigal_minimum_gene_length), + "--min-edge-gene {}".format(opts.pyrodigal_minimum_edge_gene_length), + "--max-overlap {}".format(opts.pyrodigal_maximum_gene_overlap_length), + # "-j {}".format(opts.n_jobs), + ">", + os.path.join(directories["tmp"], "tmp.gff"), + + "&&", + + "cat", + os.path.join(directories["tmp"], "tmp.gff"), "|", os.environ["append_geneid_to_prodigal_gff.py"], "-a gene_id", ">", os.path.join(output_directory, "gene_models.gff"), + "&&", + + "rm", + os.path.join(directories["tmp"], "tmp.*") + ] return cmd @@ -223,7 +244,7 @@ def get_dastool_cmd(input_filepaths, output_filepaths, output_directory, directo "--write_bins 1", "--create_plots 0", "--threads {}".format(opts.n_jobs), - "--proteins {}".format(os.path.join(directories[("intermediate", "2__prodigal")], "gene_models.faa")), + "--proteins {}".format(os.path.join(directories[("intermediate", "2__pyrodigal")], "gene_models.faa")), "--debug", opts.dastool_options, # Eukaryotic @@ -300,9 +321,9 @@ def get_dastool_cmd(input_filepaths, output_filepaths, output_directory, directo "&&", os.environ["partition_gene_models.py"], "-i {}".format(os.path.join(output_directory, "__DASTool_scaffolds2bin.no_eukaryota.txt")), - "-g {}".format(os.path.join(directories[("intermediate", "2__prodigal")], "gene_models.gff")), - "-d {}".format(os.path.join(directories[("intermediate", "2__prodigal")], "gene_models.ffn")), - "-a {}".format(os.path.join(directories[("intermediate", "2__prodigal")], "gene_models.faa")), + "-g {}".format(os.path.join(directories[("intermediate", "2__pyrodigal")], "gene_models.gff")), + "-d {}".format(os.path.join(directories[("intermediate", "2__pyrodigal")], "gene_models.ffn")), + "-a {}".format(os.path.join(directories[("intermediate", "2__pyrodigal")], "gene_models.faa")), "-o {}".format(os.path.join(output_directory, "__DASTool_bins")), "--use_mag_as_description", @@ -576,18 +597,18 @@ def create_pipeline(opts, directories, f_cmds): ) # ========== - # Prodigal + # Pyrodigal # ========== step = 2 - program = "prodigal" + program = "pyrodigal" program_label = "{}__{}".format(step, program) # Add to directories output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) # Info - description = "Gene calls via Prodigal" + description = "Gene calls via Pyrodigal" # i/o input_filepaths = [ opts.fasta, @@ -608,7 +629,7 @@ def create_pipeline(opts, directories, f_cmds): "directories":directories, } - cmd = get_prodigal_cmd(**params) + cmd = get_pyrodigal_cmd(**params) pipeline.add_step( id=program_label, description = description, @@ -894,7 +915,7 @@ def create_pipeline(opts, directories, f_cmds): os.path.join(directories[("intermediate", "{}__binning_maxbin2-40".format(steps["binning_maxbin2-40"]))], "scaffolds_to_bins.tsv"), os.path.join(directories[("intermediate", "{}__binning_concoct".format(steps["binning_concoct"]))], "scaffolds_to_bins.tsv"), input_fasta, - # os.path.join(directories[("intermediate", "2__prodigal")], "gene_models.faa"), + # os.path.join(directories[("intermediate", "2__pyrodigal")], "gene_models.faa"), ] output_filenames = [ @@ -1007,7 +1028,7 @@ def create_pipeline(opts, directories, f_cmds): # i/o input_filepaths = [ opts.fasta, - os.path.join(directories[("intermediate", "2__prodigal")], "gene_models.gff"), + os.path.join(directories[("intermediate", "2__pyrodigal")], "gene_models.gff"), *opts.bam, ] @@ -1128,7 +1149,7 @@ def add_executables_to_environment(opts): # 6 "DAS_Tool", # 7 - "prodigal", + "pyrodigal", # 8 "checkm2", # 9 @@ -1240,7 +1261,10 @@ def main(args=None): # parser_binning.add_argument("--metacoag_options", type=str, default="", help="metacoag | More options (e.g. --arg 1 ) [Default: '']") parser_genemodels = parser.add_argument_group('Gene model arguments') - parser_genemodels.add_argument("--prodigal_genetic_code", type=str, default=11, help="Prodigal -g translation table [Default: 11]") + parser_genemodels.add_argument("--pyrodigal_minimum_gene_length", type=int, default=90, help="Pyrodigal | Minimum gene length [Default: 90]") + parser_genemodels.add_argument("--pyrodigal_minimum_edge_gene_length", type=int, default=60, help="Pyrodigal | Minimum edge gene length [Default: 60]") + parser_genemodels.add_argument("--pyrodigal_maximum_gene_overlap_length", type=int, default=60, help="Pyrodigal | Maximum gene overlap length [Default: 60]") + parser_genemodels.add_argument("--pyrodigal_genetic_code", type=str, default=11, help="Pyrodigal -g translation table [Default: 11]") parser_evaluation = parser.add_argument_group('Evaluation arguments') parser_evaluation.add_argument("--dastool_searchengine", type=str, default="diamond", help="DAS_Tool searchengine. [Default: diamond] | https://github.com/cmks/DAS_Tool") diff --git a/src/classify-eukaryotic.py b/src/classify-eukaryotic.py index 417c095..82fd60f 100755 --- a/src/classify-eukaryotic.py +++ b/src/classify-eukaryotic.py @@ -13,7 +13,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.6.12" # Assembly def get_concatenate_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -227,6 +227,7 @@ def get_consensus_cluster_classification_cmd( input_filepaths, output_filepaths, "--leniency {}".format(opts.leniency), "-o {}".format(output_filepaths[0]), "-r c__,o__,f__,g__,s__", + "-u 'Unclassified eukaryote'", ] diff --git a/src/classify-prokaryotic.py b/src/classify-prokaryotic.py index cf9c0a3..d083d3b 100755 --- a/src/classify-prokaryotic.py +++ b/src/classify-prokaryotic.py @@ -14,7 +14,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.6.16" # GTDB-Tk def get_gtdbtk_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -51,6 +51,7 @@ def get_gtdbtk_cmd( input_filepaths, output_filepaths, output_directory, directo os.path.join(directories["tmp"], "genomes.list"), ] + # GTDB-Tk cmd += [ "&&", @@ -69,8 +70,22 @@ def get_gtdbtk_cmd( input_filepaths, output_filepaths, output_directory, directo "-x {}".format(opts.extension), "--cpus {}".format(opts.n_jobs), "--tmpdir {}".format(os.path.join(directories["tmp"], "gtdbtk")), - "--skip_ani_screen", opts.gtdbtk_options, + ] + + if opts.skip_ani_screen: + cmd += [ + "--skip_ani_screen", + ] + + else: + cmd += [ + "--mash_db {}".format(os.path.join(opts.gtdbtk_database, "mash","gtdb_r214.msh")), + ] + + + # Concatenation + cmd += [ "&&", @@ -136,6 +151,7 @@ def get_consensus_cluster_classification_cmd( input_filepaths, output_filepaths, os.environ["consensus_genome_classification.py"], "--leniency {}".format(opts.leniency), "-o {}".format(output_filepaths[0]), + "-u 'Unclassified prokaryote'", ] return cmd @@ -377,11 +393,10 @@ def main(args=None): # Databases parser_databases = parser.add_argument_group('Database arguments') parser_databases.add_argument("--veba_database", type=str, help=f"VEBA database location. [Default: $VEBA_DATABASE environment variable]") - # parser_databases.add_argument("--mash_database", type=str, help=f"Default is to create a MASH database") # GTDB-Tk parser_gtdbtk = parser.add_argument_group('GTDB-Tk arguments') - # parser_gtdbtk.add_argument("--skip_ani_screen", action="store_true", help = "Skip ANI screen [Default: Don't skip ANI screen]") + parser_gtdbtk.add_argument("--skip_ani_screen", action="store_true", help = "Skip ANI screen [Default: Don't skip ANI screen]") parser_gtdbtk.add_argument("--gtdbtk_options", type=str, default="", help="GTDB-Tk | classify_wf options (e.g. --arg 1 ) [Default: '']") @@ -421,7 +436,7 @@ def main(args=None): assert "VEBA_DATABASE" in os.environ, "Please set the following environment variable 'export VEBA_DATABASE=/path/to/veba_database' or provide path to --veba_database" opts.veba_database = os.environ["VEBA_DATABASE"] - opts.gtdbtk_database = os.path.join(opts.veba_database, "Classify", "GTDBTk") + opts.gtdbtk_database = os.path.join(opts.veba_database, "Classify", "GTDB") # if opts.mash_database is None: # opts.mash_database = os.path.join(directories["tmp"], "gtdbtk.msh") diff --git a/src/classify-viral.py b/src/classify-viral.py index 6cc93b3..283be43 100755 --- a/src/classify-viral.py +++ b/src/classify-viral.py @@ -89,7 +89,7 @@ def get_consensus_genome_cmd( input_filepaths, output_filepaths, output_director os.environ["consensus_genome_classification_unranked.py"], "-o {}".format(output_filepaths[0]), "-t {}".format(opts.threshold), - "--unclassified_label Unclassified", + "--unclassified_label 'Unclassified virus'", "--unclassified_taxonid -1", "--veba_database {}".format(opts.veba_database), "--verbose", diff --git a/src/cluster.py b/src/cluster.py index d53c473..be73e82 100755 --- a/src/cluster.py +++ b/src/cluster.py @@ -12,7 +12,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.6.14" # Global clustering def get_global_clustering_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): diff --git a/src/phylogeny.py b/src/phylogeny.py index ac069d4..80ad34a 100755 --- a/src/phylogeny.py +++ b/src/phylogeny.py @@ -13,7 +13,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.6.12" # Assembly def preprocess( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -537,7 +537,7 @@ def main(args=None): # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') parser_io.add_argument("-d", "--database_hmm", type=str, help=f"path/to/HMM database of markers") - parser_io.add_argument("-a","--proteins", type=str, help = "Can be the following format: 1) Tab-seperated value table of [id_mag][path/to/protein.fasta]; 2) Files with list of filepaths [path/to/protein.fasta]; or 3) Directory of protein fasta using --extension") + parser_io.add_argument("-a","--proteins", type=str, help = "Can be the following format: 1) Tab-seperated value table of [id_mag][path/to/protein.fasta] (No header); 2) Files with list of filepaths [path/to/protein.fasta] (uses --extension); or 3) Directory of protein fasta (uses --extension)") parser_io.add_argument("-o","--output_directory", type=str, default="veba_output/phylogeny", help = "path/to/project_directory [Default: veba_output/phylogeny]") parser_io.add_argument("-x", "--extension", type=str, default="faa", help = "Fasta file extension for proteins if a list is provided [Default: faa]") diff --git a/src/scripts/.DS_Store b/src/scripts/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 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 diff --git a/src/scripts/compile_krona.py b/src/scripts/compile_krona.py index 3315e4c..59905c1 100755 --- a/src/scripts/compile_krona.py +++ b/src/scripts/compile_krona.py @@ -3,12 +3,11 @@ import sys, os, argparse, glob import numpy as np import pandas as pd -from tqdm import tqdm pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.1" +__version__ = "2023.6.12" def main(args=None): @@ -29,6 +28,8 @@ def main(args=None): parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.tsv [Default: stdout]") parser.add_argument("-m","--mode", type=str, required=True, help = "{prokaryotic, eukaryotic, biosynthetic-global, biosynthetic-local}") parser.add_argument("-R", "--retain_rank_prefix", action="store_true", help = "Retain rank prefixes for modes {prokaryotic, eukaryotic} (e.g., d__, p__, c__)") + parser.add_argument("-u", "--unclassified_label", default="Unclassified", type=str, help = "Unclassified label [Default: Unclassified]") + # parser.add_argument("-C", "--remove_incomplete", action="store_true", help = "Remove BGCs on contig edge for biosynthetic mode") # parser.add_argument("-G", "--remove_genome_column", action="store_true", help = "Remove the genome ID for biosynthetic mode. Useful when using lots of genomes.") @@ -48,7 +49,21 @@ def main(args=None): df_input = pd.read_csv(opts.input, sep="\t", index_col=0) if opts.mode == "prokaryotic": value_counts = df_input["classification"].value_counts() - df_output = pd.DataFrame(np.stack(value_counts.index.map(lambda classification: list(classification.split(";"))))) + try: + df_output = pd.DataFrame(np.stack(value_counts.index.map(lambda classification: list(classification.split(";"))))) + except ValueError: + sizes = value_counts.index.map(lambda classification: len(classification.split(";"))) + index = list() + for i,s in zip(value_counts.index, sizes): + if s < 7: + i = ";".join(map(lambda x: f"{x}__{opts.unclassified_label}", list("dpcofgs"))) + else: + if s > 7: + print(f"Not sure why {i} has more than 7 fields. Please double check this.", file=sys.stderr) + index.append(i) + value_counts.index = index + df_output = pd.DataFrame(np.stack(value_counts.index.map(lambda classification: list(classification.split(";"))))) + if not opts.retain_rank_prefix: df_output = df_output.applymap(lambda x: x.split("__")[-1]) df_output.columns = ["domain", "phylum","class", "order", "family", "genus", "species"] @@ -56,7 +71,20 @@ def main(args=None): if opts.mode == "eukaryotic": value_counts = df_input["consensus_classification"].value_counts() - df_output = pd.DataFrame(np.stack(value_counts.index.map(lambda classification: list(classification.split(";"))))) + try: + df_output = pd.DataFrame(np.stack(value_counts.index.map(lambda classification: list(classification.split(";"))))) + except ValueError: + sizes = value_counts.index.map(lambda classification: len(classification.split(";"))) + index = list() + for i,s in zip(value_counts.index, sizes): + if s < 5: + i = ";".join(map(lambda x: f"{x}__{opts.unclassified_label}", list("cofgs"))) + else: + if s > 5: + print(f"Not sure why {i} has more than 5 fields. Please double check this.", file=sys.stderr) + index.append(i) + value_counts.index = index + df_output = pd.DataFrame(np.stack(value_counts.index.map(lambda classification: list(classification.split(";"))))) if not opts.retain_rank_prefix: df_output = df_output.applymap(lambda x: x.split("__")[-1]) df_output.columns = ["class", "order", "family", "genus", "species"] diff --git a/src/scripts/consensus_genome_classification.py b/src/scripts/consensus_genome_classification.py index a22fa2f..e6608b1 100755 --- a/src/scripts/consensus_genome_classification.py +++ b/src/scripts/consensus_genome_classification.py @@ -9,7 +9,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.2.13" +__version__ = "2023.6.12" # RANK_TO_PREFIX="superkingdom:d__,phylum:p__,class:c__,order:o__,family:f__,genus:g__,species:s__" @@ -87,7 +87,7 @@ def get_consensus_classification( slc_taxa_scores = defaultdict(lambda: defaultdict(float)) # Iterate through MAG, classification, and score - df = pd.concat([genome_to_slc.to_frame("id_genome_cluster"), classification.to_frame("classification"), classification_weights.to_frame("weight")], axis=1) + df = pd.concat([genome_to_slc.to_frame("id"), classification.to_frame("classification"), classification_weights.to_frame("weight")], axis=1) slc_to_mags = defaultdict(list) for id_mag, (id_slc, classification, w) in df.iterrows(): slc_to_mags[id_slc].append(id_mag) @@ -107,11 +107,11 @@ def get_consensus_classification( df_consensus_classification = pd.DataFrame(slc_taxa_scores.map(lambda taxa_scores: sorted(taxa_scores.items(), key=lambda x:(x[1], len(x[0])), reverse=True)[0]).to_dict(), index=["consensus_classification", "score"]).T df_consensus_classification["consensus_classification"] = df_consensus_classification["consensus_classification"].map(";".join) df_consensus_classification["number_of_unique_classifications"] = df["classification"].groupby(genome_to_slc).apply(lambda x: len(set(x))) - df_consensus_classification["number_of_genomes"] = slc_to_mags.map(len) #df["classification"].groupby(genome_to_slc).apply(len) - df_consensus_classification["genomes"] = slc_to_mags + df_consensus_classification["number_of_components"] = slc_to_mags.map(len) #df["classification"].groupby(genome_to_slc).apply(len) + df_consensus_classification["components"] = slc_to_mags df_consensus_classification["classifications"] = df["classification"].groupby(genome_to_slc).apply(lambda x: list(x)) df_consensus_classification["weights"] = df["weight"].groupby(genome_to_slc).apply(lambda x: list(x)) - df_consensus_classification.index.name = "id_genome_cluster" + df_consensus_classification.index.name = "id" # Homogeneity slc_taxa_homogeneity = defaultdict(lambda: defaultdict(float)) @@ -124,8 +124,8 @@ def get_consensus_classification( "consensus_classification", "homogeneity", "number_of_unique_classifications", - "number_of_genomes", - "genomes", + "number_of_components", + "components", "classifications", "weights", "score", @@ -154,6 +154,9 @@ def main(args=None): parser.add_argument("-d", "--delimiter", type=str, default=";", help = "Taxonomic delimiter [Default: ; ]") parser.add_argument("-s", "--simple", action="store_true", help = "Simple classification that does not use lineage information from --rank_prefixes") parser.add_argument("--remove_missing_classifications", action="store_true", help = "Remove all classifications and weights that are null. For viruses this could cause an error if this isn't selected.") + parser.add_argument("-u", "--unclassified_label", default="Unclassified", type=str, help = "Unclassified label [Default: Unclassified]") + parser.add_argument("-w", "--unclassified_weight", default=100.0,type=float, help = "Unclassified label weight [Default: 100.0]") + # Options opts = parser.parse_args() @@ -175,11 +178,16 @@ def main(args=None): # Classifications df_input = pd.read_csv(opts.input, sep="\t", index_col=0, header=None) mag_to_slc = df_input.iloc[:,0] - mag_to_classification = df_input.iloc[:,1] - mag_to_weights = df_input.iloc[:,2] - if opts.remove_missing_classifications: - mag_to_classification = mag_to_classification.dropna() - mag_to_weights = mag_to_weights[mag_to_classification.index] + mag_to_classification = df_input.iloc[:,1].reindex(mag_to_slc.index) + mag_to_weights = df_input.iloc[:,2].reindex(mag_to_slc.index) + if opts.remove_missing_classifications: + mag_to_weights = mag_to_weights.dropna() + mag_to_classification = mag_to_classification[mag_to_weights.index] + else: + mask = mag_to_weights.isnull() + mag_to_classification[mask] = ";".join(map(lambda x: f"{x}__{opts.unclassified_label}", opts.rank_prefixes)) + mag_to_weights[mask] = opts.unclassified_weight + # Consensus classification df_consensus_classification = get_consensus_classification( diff --git a/src/scripts/global_clustering.py b/src/scripts/global_clustering.py index c529532..2e11f78 100755 --- a/src/scripts/global_clustering.py +++ b/src/scripts/global_clustering.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from __future__ import print_function, division -import sys, os, argparse, glob, shutil, time, gzip +import sys, os, argparse, glob, shutil, time, gzip, warnings from multiprocessing import cpu_count from collections import OrderedDict, defaultdict @@ -14,7 +14,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.12" +__version__ = "2023.6.15" def get_basename(x): _, fn = os.path.split(x) @@ -22,6 +22,26 @@ def get_basename(x): fn = fn[:-3] return ".".join(fn.split(".")[:-1]) +def get_protein_cluster_prevalence(df_input:pd.DataFrame): + # Read Input + genomes = sorted(df_input.iloc[:,0].unique()) + clusters = sorted(df_input.iloc[:,2].unique()) + + # Create array + A = np.zeros((len(genomes), len(clusters)), dtype=int) + + for _, (id_genome, id_protein, id_cluster) in df_input.iterrows(): + i = genomes.index(id_genome) + j = clusters.index(id_cluster) + A[i,j] += 1 + + # Create output + df_output = pd.DataFrame(A, index=genomes, columns=clusters) + df_output.index.name = "id_genome" + df_output.columns.name = "id_protein-cluster" + + return df_output + # Set environment variables def add_executables_to_environment(opts): """ @@ -30,6 +50,7 @@ def add_executables_to_environment(opts): accessory_scripts = { "edgelist_to_clusters.py", "mmseqs2_wrapper.py", + # "table_to_fasta.py", } required_executables={ @@ -141,13 +162,17 @@ def main(args=None): directories = dict() directories["project"] = create_directory(opts.output_directory) directories["output"] = create_directory(os.path.join(directories["project"], "output")) + directories["pangenome_tables"] = create_directory(os.path.join(directories["output"], "pangenome_tables")) + directories["serialization"] = create_directory(os.path.join(directories["output"], "serialization")) + + # directories[("misc", "pangenomes", "networkx_graphs")] = create_directory(os.path.join(directories[("misc", "pangenomes")], "networkx_graphs")) + directories["log"] = create_directory(os.path.join(directories["project"], "log")) directories["tmp"] = create_directory(os.path.join(directories["project"], "tmp")) directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) directories["intermediate"] = create_directory(os.path.join(directories["project"], "intermediate")) os.environ["TMPDIR"] = directories["tmp"] - # Info print(format_header(__program__, "="), file=sys.stdout) print(format_header("Configuration:", "-"), file=sys.stdout) @@ -266,6 +291,8 @@ def main(args=None): "--cluster_prefix_zfill {}".format(opts.genome_cluster_prefix_zfill), "-o {}".format(os.path.join(output_directory, "genome_clusters.tsv")), "--identifiers {}".format(os.path.join(directories["intermediate"], organism_type, "genome_identifiers.list")), + "--export_graph {}".format(os.path.join(directories["serialization"], f"{organism_type}.networkx_graph.pkl")), + "--export_dict {}".format(os.path.join(directories["serialization"], f"{organism_type}.dict.pkl")), "&&", @@ -285,6 +312,11 @@ def main(args=None): write_returncode=os.path.join(directories["log"], "{}.returncode".format(name)), checkpoint=os.path.join(directories["checkpoints"], name), ) + if hasattr(cmd, "returncode_"): + if cmd.returncode_ != 0: + print("[Error] | {}".format(description), file=sys.stdout) + print("Check the following files:\ncat {}".format(os.path.join(directories["log"], "{}.*".format(name))), file=sys.stdout) + sys.exit(cmd.returncode_) # MMSEQS2 print(format_header(" * ({}) Running MMSEQS2:".format(format_duration(t0))), file=sys.stdout) @@ -313,43 +345,6 @@ def main(args=None): # Run MMSEQS2 name = "mmseqs2__{}__{}".format(organism_type, id_genomecluster) description = "[Program = MMSEQS2] [Organism_Type = {}] [Genome_Cluster = {}]".format(organism_type, id_genomecluster) - # cmd = Command([ - # os.environ["mmseqs"], - # "easy-cluster", - # os.path.join(genomecluster_directory, "proteins.faa" ), - # os.path.join(genomecluster_directory, "mmseqs2"), - # directories["tmp"], - # "--threads {}".format(opts.n_jobs), - # "--min-seq-id {}".format(opts.minimum_identity_threshold/100), - # "-c {}".format(opts.minimum_coverage_threshold), - # "--cov-mode 1", - # # "--dbtype 1", - # opts.mmseqs2_options, - - # "&&", - - # os.environ["edgelist_to_clusters.py"], - # "-i {}".format(os.path.join(genomecluster_directory, "mmseqs2_cluster.tsv")), - # "--no_singletons" if bool(opts.no_singletons) else "", - # "--cluster_prefix {}_{}".format(id_genomecluster, opts.protein_cluster_prefix), - # "--cluster_suffix {}".format(opts.protein_cluster_suffix) if bool(opts.protein_cluster_suffix) else "", - # "--cluster_prefix_zfill {}".format(opts.protein_cluster_prefix_zfill), - # "-o {}".format(os.path.join(genomecluster_directory, "protein_clusters.tsv")), - # "--identifiers {}".format(os.path.join(genomecluster_directory, "protein_identifiers.list")), - - # "&&", - - # "rm -rf", - # os.path.join(genomecluster_directory, "mmseqs2_all_seqs.fasta"), - # os.path.join(genomecluster_directory, "proteins.faa"), - # os.path.join(directories["tmp"], "*"), - - # "&&", - - # "gzip", - # os.path.join(genomecluster_directory, "mmseqs2_rep_seq.fasta"), - - # ], cmd = Command([ os.environ["mmseqs2_wrapper.py"], @@ -366,6 +361,12 @@ def main(args=None): "--cluster_prefix_zfill {}".format(opts.protein_cluster_prefix_zfill), "--basename protein_clusters", "--identifiers {}".format(os.path.join(genomecluster_directory, "protein_identifiers.list")), + "--no_sequences_and_header", + + "&&", + + "rm -rf", + os.path.join(genomecluster_directory, "proteins.faa" ), ], name=name, f_cmds=f_cmds, @@ -454,8 +455,6 @@ def main(args=None): df_proteinclusters = pd.DataFrame(proteincluster_data).T.sort_index()[["number_of_components", "number_of_samples_of_origin", "components", "samples_of_origin"]] df_proteinclusters.index.name = "id_protein_cluster" - - print(" * ({}) Calculating feature compression ratios for each sample".format(format_duration(t0)), file=sys.stdout) fcr_data = defaultdict(dict) for organism_type, df in df_mags.groupby("organism_type"): @@ -467,17 +466,57 @@ def main(args=None): df_fcr = pd.DataFrame(fcr_data).T.sort_index() df_fcr.index.name = "organism_type" - print(" * ({}) Getting representative sequences".format(format_duration(t0)), file=sys.stdout) + # Get representative sequences if not opts.no_representative_sequences: - with open(os.path.join(directories["output"], "representative_sequences.faa"), "w") as f_out: - for fp in glob.glob(os.path.join(directories["intermediate"], "*", "clusters", "*", "output", "representative_sequences.tsv.gz" )): - with gzip.open(fp, "rt") as f_in: - next(f_in) - for line in f_in: - line = line.strip() - id_cluster, id_representative_sequence, sequence = line.split("\t") - print(">{} {}\n{}".format(id_cluster, id_representative_sequence, sequence), file=f_out) - + # print(format_header(" * ({}) Compiling representative sequences:".format(format_duration(t0))), file=sys.stdout) + + proteincluster_to_representative = list() + for fp in glob.glob(os.path.join(directories["intermediate"], "*", "clusters", "*","output", "representative_sequences.tsv.gz")): + a = pd.read_csv(fp, sep="\t", index_col=0, header=None).iloc[:,0] + proteincluster_to_representative.append(a) + proteincluster_to_representative = pd.concat(proteincluster_to_representative) + + with open(os.path.join(directories["output"], "representative_sequences.faa"), "w") as f_representatives: + for id_proteincluster, id_representative in pv(proteincluster_to_representative.items(), description=" * ({}) Writing protein cluster representative sequences".format(format_duration(t0)), unit="sequence", total=proteincluster_to_representative.size): + seq = protein_to_sequence[id_representative] + header = f"{id_proteincluster} {id_representative}" + print(f">{header}\n{seq}", file=f_representatives) + + # Write prevalence table + # print(format_header(" * ({}) Compiling pangenome protein cluster prevalence tables:".format(format_duration(t0))), file=sys.stdout) + genome_to_number_of_singletons = list() + genome_to_ratio_of_singletons = list() + for id_genomecluster, df in pv(df_proteins.groupby("id_genome_cluster"), description=" * ({}) Writing protein prevalence pangenome tables and counting singletons".format(format_duration(t0)), unit="genome cluster", total=df_proteins["id_genome_cluster"].nunique()): + # Prevalence + df = df.reset_index().loc[:,["id_genome", "id_protein", "id_protein_cluster"]] + df_prevalence = get_protein_cluster_prevalence(df) + df_prevalence.to_csv(os.path.join(directories["pangenome_tables"], f"{id_genomecluster}.tsv.gz"), sep="\t") + + # Singletons + if df_prevalence.shape[0] > 1: + df_prevalence = df_prevalence > 0 + number_of_singletons = df_prevalence.loc[:,df_prevalence.sum(axis=0)[lambda x: x == 1].index].sum(axis=1) + ratio_of_singletons = number_of_singletons/df_prevalence.sum(axis=1) + genome_to_number_of_singletons.append(number_of_singletons) + genome_to_ratio_of_singletons.append(ratio_of_singletons) + + if len(genome_to_number_of_singletons): + df_mags["number_of_singleton_protein_clusters"] = pd.concat(genome_to_number_of_singletons).reindex(df_mags.index).astype("Int64") + df_mags["ratio_of_protein_cluster_are_singletons"] = pd.concat(genome_to_ratio_of_singletons) + else: + warnings.warn("No clusters with more than 1 genome so singleton analysis does not apply") + + # # Symlink pangenome graphs + # print(format_header(" * ({}) Symlinking pangenome protein cluster NetworkX Graphs:".format(format_duration(t0))), file=sys.stdout) + # for src in glob.glob(os.path.join(directories["intermediate"], "*", "clusters", "*","output", "protein_clusters.networkx_graph.pkl")): + # id_genomecluster = src.split("/")[-3] + # dst = os.path.join(directories[("misc", "pangenomes", "networkx_graphs")], f"{id_genomecluster}.pkl") + # if os.path.exists(dst): + # os.remove(dst) + # src_real_path = os.path.realpath(src) + # src_relative_path = os.path.relpath(src_real_path, os.path.split(dst)[0]) + # os.symlink(src_relative_path, dst) + # Writing output files print(format_header(" * ({}) Writing Output Tables:".format(format_duration(t0))), file=sys.stdout) df_mags.to_csv(os.path.join(directories["output"], "identifier_mapping.genomes.tsv"), sep="\t") @@ -494,4 +533,4 @@ def main(args=None): if __name__ == "__main__": - main(sys.argv[1:]) + main(sys.argv[1:]) \ No newline at end of file diff --git a/src/scripts/local_clustering.py b/src/scripts/local_clustering.py index 49a32ac..8f1fca6 100755 --- a/src/scripts/local_clustering.py +++ b/src/scripts/local_clustering.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from __future__ import print_function, division -import sys, os, argparse, glob, shutil, time, gzip +import sys, os, argparse, glob, shutil, time, gzip, warnings from multiprocessing import cpu_count from collections import OrderedDict, defaultdict @@ -14,7 +14,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.12" +__version__ = "2023.6.15" def get_basename(x): _, fn = os.path.split(x) @@ -22,6 +22,26 @@ def get_basename(x): fn = fn[:-3] return ".".join(fn.split(".")[:-1]) +def get_protein_cluster_prevalence(df_input:pd.DataFrame): + # Read Input + genomes = sorted(df_input.iloc[:,0].unique()) + clusters = sorted(df_input.iloc[:,2].unique()) + + # Create array + A = np.zeros((len(genomes), len(clusters)), dtype=int) + + for _, (id_genome, id_protein, id_cluster) in df_input.iterrows(): + i = genomes.index(id_genome) + j = clusters.index(id_cluster) + A[i,j] += 1 + + # Create output + df_output = pd.DataFrame(A, index=genomes, columns=clusters) + df_output.index.name = "id_genome" + df_output.columns.name = "id_protein-cluster" + + return df_output + # Set environment variables def add_executables_to_environment(opts): """ @@ -140,6 +160,8 @@ def main(args=None): directories = dict() directories["project"] = create_directory(opts.output_directory) directories["output"] = create_directory(os.path.join(directories["project"], "output")) + directories["pangenome_tables"] = create_directory(os.path.join(directories["output"], "pangenome_tables")) + directories["serialization"] = create_directory(os.path.join(directories["output"], "serialization")) directories["log"] = create_directory(os.path.join(directories["project"], "log")) directories["tmp"] = create_directory(os.path.join(directories["project"], "tmp")) directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) @@ -269,6 +291,8 @@ def main(args=None): "--cluster_prefix_zfill {}".format(opts.genome_cluster_prefix_zfill), "-o {}".format(os.path.join(os.path.split(fp)[0], "genome_clusters.tsv")), "--identifiers {}".format(os.path.join(directories["intermediate"], organism_type, id_sample, "genome_identifiers.list")), + "--export_graph {}".format(os.path.join(directories["serialization"], f"{organism_type}.networkx_graph.pkl")), + "--export_dict {}".format(os.path.join(directories["serialization"], f"{organism_type}.dict.pkl")), "&&", @@ -288,6 +312,11 @@ def main(args=None): write_returncode=os.path.join(directories["log"], "{}.returncode".format(name)), checkpoint=os.path.join(directories["checkpoints"], name), ) + if hasattr(cmd, "returncode_"): + if cmd.returncode_ != 0: + print("[Error] | {}".format(description), file=sys.stdout) + print("Check the following files:\ncat {}".format(os.path.join(directories["log"], "{}.*".format(name))), file=sys.stdout) + sys.exit(cmd.returncode_) # MMSEQS2 print(format_header(" * ({}) Running MMSEQS2:".format(format_duration(t0))), file=sys.stdout) @@ -318,46 +347,6 @@ def main(args=None): # Run MMSEQS2 name = "mmseqs2__{}__{}".format(organism_type, id_genomecluster) description = "[Program = MMSEQS2] [Organism_Type = {}] [Sample_ID = {}] [Genome_Cluster = {}]".format(organism_type, id_sample, id_genomecluster) - # cmd = Command([ - # os.environ["mmseqs"], - # "easy-cluster", - # os.path.join(genomecluster_directory, "proteins.faa" ), - # os.path.join(genomecluster_directory, "mmseqs2"), - # directories["tmp"], - # "--threads {}".format(opts.n_jobs), - # "--min-seq-id {}".format(opts.minimum_identity_threshold/100), - # "-c {}".format(opts.minimum_coverage_threshold), - # "--cov-mode 1", - # "--dbtype 1", - # opts.mmseqs2_options, - - # "&&", - - # os.environ["edgelist_to_clusters.py"], - # "-i {}".format(os.path.join(genomecluster_directory, "mmseqs2_cluster.tsv")), - # "--no_singletons" if bool(opts.no_singletons) else "", - # "--cluster_prefix {}_{}".format(id_genomecluster, opts.protein_cluster_prefix), - # "--cluster_suffix {}".format(opts.protein_cluster_suffix) if bool(opts.protein_cluster_suffix) else "", - # "--cluster_prefix_zfill {}".format(opts.protein_cluster_prefix_zfill), - # "-o {}".format(os.path.join(genomecluster_directory, "protein_clusters.tsv")), - # "--identifiers {}".format(os.path.join(genomecluster_directory, "protein_identifiers.list")), - - # "&&", - - # "rm -rf", - # os.path.join(genomecluster_directory, "mmseqs2_all_seqs.fasta"), - # os.path.join(genomecluster_directory, "proteins.faa"), - # os.path.join(directories["tmp"], "*"), - - # "&&", - - # "gzip", - # os.path.join(genomecluster_directory, "mmseqs2_rep_seq.fasta"), - - # ], - # name=name, - # f_cmds=f_cmds, - # ) cmd = Command([ os.environ["mmseqs2_wrapper.py"], @@ -374,6 +363,12 @@ def main(args=None): "--cluster_prefix_zfill {}".format(opts.protein_cluster_prefix_zfill), "--basename protein_clusters", "--identifiers {}".format(os.path.join(genomecluster_directory, "protein_identifiers.list")), + "--no_sequences_and_header", + + "&&", + + "rm -rf", + os.path.join(genomecluster_directory, "proteins.faa" ), ], name=name, f_cmds=f_cmds, @@ -478,16 +473,45 @@ def main(args=None): df_fcr = pd.DataFrame(fcr_data).T.sort_index() df_fcr.index.names = ["organism_type", "id_sample"] - print(" * ({}) Getting representative sequences".format(format_duration(t0)), file=sys.stdout) + # Get representative sequences if not opts.no_representative_sequences: - with open(os.path.join(directories["output"], "representative_sequences.faa"), "w") as f_out: - for fp in glob.glob(os.path.join(directories["intermediate"], "*", "clusters", "*", "output", "representative_sequences.tsv.gz" )): - with gzip.open(fp, "rt") as f_in: - next(f_in) - for line in f_in: - line = line.strip() - id_cluster, id_representative_sequence, sequence = line.split("\t") - print(">{} {}\n{}".format(id_cluster, id_representative_sequence, sequence), file=f_out) + # print(format_header(" * ({}) Compiling representative sequences:".format(format_duration(t0))), file=sys.stdout) + # local_clustering_output/intermediate/eukaryotic/S1/clusters/S1__ESLC-1/output/representative_sequences.tsv.gz + proteincluster_to_representative = list() + for fp in glob.glob(os.path.join(directories["intermediate"], "*","*", "clusters", "*","output", "representative_sequences.tsv.gz")): + a = pd.read_csv(fp, sep="\t", index_col=0, header=None).iloc[:,0] + proteincluster_to_representative.append(a) + proteincluster_to_representative = pd.concat(proteincluster_to_representative) + + with open(os.path.join(directories["output"], "representative_sequences.faa"), "w") as f_representatives: + for id_proteincluster, id_representative in pv(proteincluster_to_representative.items(), description=" * ({}) Writing protein cluster representative sequences".format(format_duration(t0)), unit="sequence", total=proteincluster_to_representative.size): + seq = protein_to_sequence[id_representative] + header = f"{id_proteincluster} {id_representative}" + print(f">{header}\n{seq}", file=f_representatives) + + # Write prevalence table + # print(format_header(" * ({}) Compiling pangenome protein cluster prevalence tables:".format(format_duration(t0))), file=sys.stdout) + genome_to_number_of_singletons = list() + genome_to_ratio_of_singletons = list() + for id_genomecluster, df in pv(df_proteins.groupby("id_genome_cluster"), description=" * ({}) Writing protein prevalence pangenome tables and counting singletons".format(format_duration(t0)), unit="genome cluster", total=df_proteins["id_genome_cluster"].nunique()): + # Prevalence + df = df.reset_index().loc[:,["id_genome", "id_protein", "id_protein_cluster"]] + df_prevalence = get_protein_cluster_prevalence(df) + df_prevalence.to_csv(os.path.join(directories["pangenome_tables"], f"{id_genomecluster}.tsv.gz"), sep="\t") + + # Singletons + if df_prevalence.shape[0] > 1: + df_prevalence = df_prevalence > 0 + number_of_singletons = df_prevalence.loc[:,df_prevalence.sum(axis=0)[lambda x: x == 1].index].sum(axis=1) + ratio_of_singletons = number_of_singletons/df_prevalence.sum(axis=1) + genome_to_number_of_singletons.append(number_of_singletons) + genome_to_ratio_of_singletons.append(ratio_of_singletons) + + if len(genome_to_number_of_singletons): + df_mags["number_of_singleton_protein_clusters"] = pd.concat(genome_to_number_of_singletons).reindex(df_mags.index).astype("Int64") + df_mags["ratio_of_protein_cluster_are_singletons"] = pd.concat(genome_to_ratio_of_singletons) + else: + warnings.warn("No clusters with more than 1 genome so singleton analysis does not apply") # Writing output files print(format_header(" * ({}) Writing Output Tables:".format(format_duration(t0))), file=sys.stdout) diff --git a/src/scripts/mmseqs2_wrapper.py b/src/scripts/mmseqs2_wrapper.py index 0afb219..4db4fd7 100755 --- a/src/scripts/mmseqs2_wrapper.py +++ b/src/scripts/mmseqs2_wrapper.py @@ -12,7 +12,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.8" +__version__ = "2023.6.13" # Check def get_check_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -68,8 +68,8 @@ def get_compile_cmd(input_filepaths, output_filepaths, output_directory, directo "--cluster_suffix {}".format(opts.cluster_suffix) if bool(opts.cluster_suffix) else "", "--cluster_prefix_zfill {}".format(opts.cluster_prefix_zfill), "-o {}".format(os.path.join(output_directory, "{}.tsv".format(opts.basename))), - "-g {}".format(os.path.join(output_directory, "{}.graph.pkl".format(opts.basename))), - "-d {}".format(os.path.join(output_directory, "{}.dict.pkl".format(opts.basename))), + # "-g {}".format(os.path.join(output_directory, "{}.networkx_graph.pkl".format(opts.basename))), + # "-d {}".format(os.path.join(output_directory, "{}.dict.pkl".format(opts.basename))), "--identifiers {}".format(opts.identifiers) if bool(opts.identifiers) else "", "&&", @@ -80,6 +80,11 @@ def get_compile_cmd(input_filepaths, output_filepaths, output_directory, directo "-f table", "-o {}".format(os.path.join(output_directory, "representative_sequences.tsv.gz")), ] + if opts.no_sequences_and_header: + cmd += [ + "--no_sequences", + "--no_header", + ] return cmd @@ -236,8 +241,8 @@ def create_pipeline(opts, directories, f_cmds): input_filepaths = output_filepaths output_filenames = [ "{}.tsv".format(opts.basename), - "{}.graph.pkl".format(opts.basename), - "{}.dict.pkl".format(opts.basename), + # "{}.networkx_graph.pkl".format(opts.basename), + # "{}.dict.pkl".format(opts.basename), "representative_sequences.tsv.gz", ] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) @@ -293,7 +298,6 @@ def main(args=None): parser_io.add_argument("-e", "--no_singletons", action="store_true", help="Exclude singletons") parser_io.add_argument("-b", "--basename", type=str, default="clusters", help="Basename for clustering files [Default: clusters]") - # Utility parser_utility = parser.add_argument_group('Utility arguments') parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future @@ -312,6 +316,7 @@ def main(args=None): parser_mmseqs2.add_argument("--cluster_prefix_zfill", type=int, default=0, help="Sequence cluster prefix zfill. Use 7 to match identifiers from OrthoFinder. Use 0 to add no zfill. [Default: 0]") #7 parser_mmseqs2.add_argument("--mmseqs2_options", type=str, default="", help="MMSEQS2 | More options (e.g. --arg 1 ) [Default: '']") parser_mmseqs2.add_argument("--identifiers", type=str, help = "Identifiers to include for `edgelist_to_clusters.py`. If missing identifiers and singletons are allowed, then they will be included as singleton clusters with weight of np.inf") + parser_mmseqs2.add_argument("--no_sequences_and_header", action="store_true", help = "Don't include sequences or header in table. Useful for concatenation and reduced redundancy of sequences") # Options opts = parser.parse_args() @@ -334,7 +339,6 @@ def main(args=None): directories["intermediate"] = create_directory(os.path.join(directories["project"], "intermediate")) os.environ["TMPDIR"] = directories["tmp"] - # Info print(format_header(__program__, "="), file=sys.stdout) print(format_header("Configuration:", "-"), file=sys.stdout) diff --git a/src/scripts/reformat_representative_sequences.py b/src/scripts/reformat_representative_sequences.py index 7292a7a..17a43f2 100755 --- a/src/scripts/reformat_representative_sequences.py +++ b/src/scripts/reformat_representative_sequences.py @@ -10,7 +10,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.3.17" +__version__ = "2023.6.13" def main(args=None): # Path info @@ -31,7 +31,9 @@ def main(args=None): parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output [Default: stdout]") parser.add_argument("-f","--output_format", type=str, default="table", help = "Format of output: {table, fasta} [Default: table]") parser.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) - + parser.add_argument("--no_sequences", action="store_true", help = "Don't include sequences in table") + parser.add_argument("--no_header", action="store_true", help = "Don't include header in table") + # Options opts = parser.parse_args() @@ -53,7 +55,10 @@ def main(args=None): df["representative_sequence"] = representative_sequences df.index.name = "id_representative_sequence" df = df.reset_index(drop=False).set_index("id_cluster")[["id_representative_sequence", "representative_sequence"]] - df.to_csv(opts.output, sep="\t") + if opts.no_sequences: + df = df.drop(["representative_sequence"], axis=1) + + df.to_csv(opts.output, sep="\t", header=not bool(opts.no_header)) if opts.output_format == "fasta": representative_sequences.index = representative_sequences.index.map(lambda id_sequence: "{} {}".format(id_to_cluster[id_sequence], id_sequence)) diff --git a/src/scripts/synopsis.tsv.gz b/src/scripts/synopsis.tsv.gz deleted file mode 100644 index a79b06b23aace5e3346a91c5e62287dfdbaec909..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245 zcmV(|$uIX) zVMyQ`g+UbFs28jKd6=h#Lm={}M#Shdj7}Jlw=g!AAs0Lv@c7Gqh^$SLj0$snsnFja vp>h1oW3iY#;s}YkBttLx$9|}6qhzsxZPOu$;0gc$|NjF3dq!fe>;V7(D|&c& diff --git a/src/scripts/table_to_fasta.py b/src/scripts/table_to_fasta.py index 4df6533..c632f7e 100755 --- a/src/scripts/table_to_fasta.py +++ b/src/scripts/table_to_fasta.py @@ -1,12 +1,13 @@ #!/usr/bin/env python from __future__ import print_function, division -import sys, os, argparse +import sys, os, argparse, gzip from collections import OrderedDict import pandas as pd from tqdm import tqdm +from Bio.SeqIO.FastaIO import SimpleFastaParser __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.4.19" +__version__ = "2023.6.14" # def main(args=None): @@ -26,7 +27,9 @@ def main(args=None): parser.add_argument("-o","--output", default="stdout", type=str, help = "path/to/output.fasta [Default: stdout]") parser.add_argument("-n","--name_index", default=0, type=int, help = "Name index position [Default: 0]") parser.add_argument("-d","--description_index", type=int, help = "Description index position") - parser.add_argument("-s","--sequence_index", default=2, type=int, help = "Sequence index position [Default: 2]") + parser.add_argument("-s","--sequence_index", default=2, type=int, help = "Sequence index position. Not used if --fasta is provided. [Default: 2]") + parser.add_argument("-f","--fasta", type=str, help = "Separate fasta file to use instead of column") + parser.add_argument("-x","--fasta_id_index", type=int, help = "Index to use for fasta ID in table. Most likely will be --name_index or --description_index. Required if --fasta is used.") parser.add_argument("--no_header",action="store_true", help = "Use if there is no header") parser.add_argument("--sep", type=str, default="\t", help = "Separator [Default: ]") parser.add_argument("--skiprows", type=int, help = "Skiprows") @@ -36,7 +39,6 @@ def main(args=None): opts.script_directory = script_directory opts.script_filename = script_filename - # I/O if opts.input == "stdin": opts.input = sys.stdin @@ -44,21 +46,74 @@ def main(args=None): if opts.output == "stdout": opts.output = sys.stdout else: - opts.output = open(opts.output, "w") + if opts.output.endswith(".gz"): + opts.output = gzip.open(opts.output, "wt") + else: + opts.output = open(opts.output, "w") # Read Table if opts.no_header: - df = pd.read_csv(opts.input, sep=opts.sep, index_col=None, skiprows=opts.skiprows, header=None) + df_input = pd.read_csv(opts.input, sep=opts.sep, index_col=None, skiprows=opts.skiprows, header=None) else: - df = pd.read_csv(opts.input, sep=opts.sep, index_col=None, skiprows=opts.skiprows)#, header=bool(opts.no_header)) + df_input = pd.read_csv(opts.input, sep=opts.sep, index_col=None, skiprows=opts.skiprows)#, header=bool(opts.no_header)) if opts.description_index is not None: - assert max(opts.name_index, opts.description_index, opts.sequence_index) < df.shape[1] - for i, (id, description, seq) in tqdm(df.iloc[:,[opts.name_index, opts.description_index, opts.sequence_index]].iterrows(), "Writing sequence records", total=df.shape[0]): + if opts.fasta: + assert opts.fasta_id_index is not None, "If --fasta is provided then --fasta_id_index must also be provided" + assert max(opts.name_index, opts.description_index, opts.fasta_id_index) < df_input.shape[1] + + index = df_input.iloc[:,opts.fasta_id_index].values + + + assert len(set(index)) == len(index), "--fasta_id_index contains duplicates which is not allowed for fasta files" + + df = df_input.iloc[:,[opts.name_index, opts.description_index]] + file_open_function = {True:gzip.open, False:open}[opts.fasta.endswith(".gz")] + + id_to_seq = dict() + with file_open_function(opts.fasta, "rt") as f_fasta: + for header, seq in tqdm(SimpleFastaParser(f_fasta), "Reading fasta sequences"): + id = header.split(" ")[0] + if id in index: + # assert id in index, f"{id} not in --input table under index {opts.fasta_id_index}" + id_to_seq[id] = seq + id_to_seq = pd.Series(id_to_seq).loc[index] + assert id_to_seq.isnull().sum() == 0, f"There are ids in --input table under column index {opts.fasta_id_index} that do not have sequences in --fasta" + df[2] = id_to_seq.values + else: + assert max(opts.name_index, opts.description_index, opts.sequence_index) < df_input.shape[1] + df = df_input.iloc[:,[opts.name_index, opts.description_index, opts.sequence_index]] + + for i, (id, description, seq) in tqdm(df.iterrows(), "Writing sequence records", total=df.shape[0]): print(">{} {}\n{}".format(id, description, seq), file=opts.output) else: - assert max(opts.name_index, opts.sequence_index) < df.shape[1] - for i, (id, seq) in tqdm(df.iloc[:,[opts.name_index, opts.sequence_index]].iterrows(), "Writing sequence records", total=df.shape[0]): + if opts.fasta: + assert opts.fasta_id_index is not None + assert max(opts.name_index, opts.sequence_index, opts.fasta_id_index) < df_input.shape[1] + + index = df_input.iloc[:,opts.fasta_id_index].values + + assert len(set(index)) == len(index), "--fasta_id_index contains duplicates which is not allowed for fasta files" + + df = df_input.iloc[:,[opts.name_index]] + file_open_function = {True:gzip.open, False:open}[opts.fasta.endswith(".gz")] + + id_to_seq = dict() + with file_open_function(opts.fasta, "rt") as f_fasta: + for header, seq in tqdm(SimpleFastaParser(f_fasta), "Reading fasta sequences"): + id = header.split(" ")[0] + if id in index: + # assert id in index, f"{id} not in --input table under index {opts.fasta_id_index}" + id_to_seq[id] = seq + id_to_seq = pd.Series(id_to_seq).loc[index] + assert id_to_seq.isnull().sum() == 0, f"There are ids in --input table under column index {opts.fasta_id_index} that do not have sequences in --fasta" + df[1] = id_to_seq.values + + else: + assert max(opts.name_index, opts.sequence_index) < df_input.shape[1] + df = df_input.iloc[:,[opts.name_index, opts.sequence_index]] + + for i, (id, seq) in tqdm(df.iterrows(), "Writing sequence records", total=df.shape[0]): print(">{}\n{}".format(id, seq), file=opts.output) if opts.output != sys.stdout: diff --git a/src/scripts/type_counts.tsv.gz b/src/scripts/type_counts.tsv.gz deleted file mode 100644 index 5dc03066aad4926f6568d3eae3bf1ba99ee52133..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135 zcmV;20C@i&iwFqxwP9oe|8#k9WnW`&b#8QXE_8Et09?zl4nQyzh2i;n5(WwJ7?uY6 zjdD~&Z$)eH_L|r_p8w0SD;tP+AZ-?o?*)>_Y255LPAO!w6d&0OV>PIXS)vVY4m%rt p1RAVT^=S`KOKpx*r}{BLlS9OPLH^GN009600|4UC&Jm9Q004MPIhg Date: Mon, 19 Jun 2023 15:54:09 -0700 Subject: [PATCH 04/16] Added dependency citation guide --- CITATIONS.md | 43 +++++++++++++++++++++++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 45 insertions(+) create mode 100644 CITATIONS.md diff --git a/CITATIONS.md b/CITATIONS.md new file mode 100644 index 0000000..602bb78 --- /dev/null +++ b/CITATIONS.md @@ -0,0 +1,43 @@ +# Dependency Citations +| Dependency | Citation | | | | +|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|---|---| +| antismash | Blin K, Shaw S, Kloosterman AM, Charlop-Powers Z, van Wezel GP, Medema MH, Weber T. antiSMASH 6.0: improving cluster detection and comparison capabilities. Nucleic Acids Res. 2021 Jul 2;49(W1):W29-W35. doi: 10.1093/nar/gkab335. PMID: 33978755; PMCID: PMC8262755. | | | | +| bbtools | https://sourceforge.net/projects/bbmap/ | | | | +| bowtie2 | Langmead B, Salzberg SL. Fast gapped-read alignment with Bowtie 2. Nat Methods. 2012 Mar 4;9(4):357-9. doi: 10.1038/nmeth.1923. PMID: 22388286; PMCID: PMC3322381. | | | | +| busco | Manni M, Berkeley MR, Seppey M, Simão FA, Zdobnov EM. BUSCO Update: Novel and Streamlined Workflows along with Broader and Deeper Phylogenetic Coverage for Scoring of Eukaryotic, Prokaryotic, and Viral Genomes. Mol Biol Evol. 2021 Sep 27;38(10):4647-4654. doi: 10.1093/molbev/msab199. PMID: 34320186; PMCID: PMC8476166. | | | | +| checkm2 | Alex Chklovski, Donovan H. Parks, Ben J. Woodcroft, Gene W. Tyson bioRxiv 2022.07.11.499243; doi: https://doi.org/10.1101/2022.07.11.499243 | | | | +| checkv | Nayfach S, Camargo AP, Schulz F, Eloe-Fadrosh E, Roux S, Kyrpides NC. CheckV assesses the quality and completeness of metagenome-assembled viral genomes. Nat Biotechnol. 2021 May;39(5):578-585. doi: 10.1038/s41587-020-00774-7. Epub 2020 Dec 21. PMID: 33349699; PMCID: PMC8116208. | | | | +| clipkit | Steenwyk JL, Buida TJ 3rd, Li Y, Shen XX, Rokas A. ClipKIT: A multiple sequence alignment trimming software for accurate phylogenomic inference. PLoS Biol. 2020 Dec 2;18(12):e3001007. doi: 10.1371/journal.pbio.3001007. PMID: 33264284; PMCID: PMC7735675. | | | | +| concoct | Alneberg J, Bjarnason BS, de Bruijn I, Schirmer M, Quick J, Ijaz UZ, Lahti L, Loman NJ, Andersson AF, Quince C. Binning metagenomic contigs by coverage and composition. Nat Methods. 2014 Nov;11(11):1144-6. doi: 10.1038/nmeth.3103. Epub 2014 Sep 14. PMID: 25218180. | | | | +| coverm | https://github.com/wwood/CoverM | | | | +| dada2 | Callahan BJ, McMurdie PJ, Rosen MJ, Han AW, Johnson AJ, Holmes SP. DADA2: High-resolution sample inference from Illumina amplicon data. Nat Methods. 2016 Jul;13(7):581-3. doi: 10.1038/nmeth.3869. Epub 2016 May 23. PMID: 27214047; PMCID: PMC4927377. | | | | +| das_tool | Sieber CMK, Probst AJ, Sharrar A, Thomas BC, Hess M, Tringe SG, Banfield JF. Recovery of genomes from metagenomes via a dereplication, aggregation and scoring strategy. Nat Microbiol. 2018 Jul;3(7):836-843. doi: 10.1038/s41564-018-0171-1. Epub 2018 May 28. PMID: 29807988; PMCID: PMC6786971. | | | | +| diamond | Buchfink B, Reuter K, Drost HG. Sensitive protein alignments at tree-of-life scale using DIAMOND. Nat Methods. 2021 Apr;18(4):366-368. doi: 10.1038/s41592-021-01101-x. Epub 2021 Apr 7. PMID: 33828273; PMCID: PMC8026399. | | | | +| fastani | Jain C, Rodriguez-R LM, Phillippy AM, Konstantinidis KT, Aluru S. High throughput ANI analysis of 90K prokaryotic genomes reveals clear species boundaries. Nat Commun. 2018 Nov 30;9(1):5114. doi: 10.1038/s41467-018-07641-9. PMID: 30504855; PMCID: PMC6269478. | | | | +| fastp | Chen S, Zhou Y, Chen Y, Gu J. fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics. 2018 Sep 1;34(17):i884-i890. doi: 10.1093/bioinformatics/bty560. PMID: 30423086; PMCID: PMC6129281. | | | | +| fastq_preprocessor | Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. doi: 10.1186/s12859-022-04973-8. PMID: 36224545; PMCID: PMC9554839. | | | | +| fasttree | Price MN, Dehal PS, Arkin AP. FastTree 2--approximately maximum-likelihood trees for large alignments. PLoS One. 2010 Mar 10;5(3):e9490. doi: 10.1371/journal.pone.0009490. PMID: 20224823; PMCID: PMC2835736. | | | | +| featurecounts | Liao Y, Smyth GK, Shi W. featureCounts: an efficient general purpose program for assigning sequence reads to genomic features. Bioinformatics. 2014 Apr 1;30(7):923-30. doi: 10.1093/bioinformatics/btt656. Epub 2013 Nov 13. PMID: 24227677. | | | | +| genomad | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | | | | +| gtdbtk | Chaumeil PA, Mussig AJ, Hugenholtz P, Parks DH. GTDB-Tk v2: memory friendly classification with the genome taxonomy database. Bioinformatics. 2022 Nov 30;38(23):5315-5316. doi: 10.1093/bioinformatics/btac672. PMID: 36218463; PMCID: PMC9710552. | | | | +| hmmer | Eddy SR. Accelerated Profile HMM Searches. PLoS Comput Biol. 2011 Oct;7(10):e1002195. doi: 10.1371/journal.pcbi.1002195. Epub 2011 Oct 20. PMID: 22039361; PMCID: PMC3197634. | | | | +| iqtree | Minh BQ, Schmidt HA, Chernomor O, Schrempf D, Woodhams MD, von Haeseler A, Lanfear R. IQ-TREE 2: New Models and Efficient Methods for Phylogenetic Inference in the Genomic Era. Mol Biol Evol. 2020 May 1;37(5):1530-1534. doi: 10.1093/molbev/msaa015. Erratum in: Mol Biol Evol. 2020 Aug 1;37(8):2461. PMID: 32011700; PMCID: PMC7182206. | | | | +| kofamscan | Aramaki T, Blanc-Mathieu R, Endo H, Ohkubo K, Kanehisa M, Goto S, Ogata H. KofamKOALA: KEGG Ortholog assignment based on profile HMM and adaptive score threshold. Bioinformatics. 2020 Apr 1;36(7):2251-2252. doi: 10.1093/bioinformatics/btz859. PMID: 31742321; PMCID: PMC7141845. | | | | +| krona | Ondov BD, Bergman NH, Phillippy AM. Interactive metagenomic visualization in a Web browser. BMC Bioinformatics. 2011 Sep 30;12:385. doi: 10.1186/1471-2105-12-385. PMID: 21961884; PMCID: PMC3190407. | | | | +| maxbin2 | Wu YW, Tang YH, Tringe SG, Simmons BA, Singer SW. MaxBin: an automated binning method to recover individual genomes from metagenomes using an expectation-maximization algorithm. Microbiome. 2014 Aug 1;2:26. doi: 10.1186/2049-2618-2-26. PMID: 25136443; PMCID: PMC4129434. | | | | +| megahit | Li D, Liu CM, Luo R, Sadakane K, Lam TW. MEGAHIT: an ultra-fast single-node solution for large and complex metagenomics assembly via succinct de Bruijn graph. Bioinformatics. 2015 May 15;31(10):1674-6. doi: 10.1093/bioinformatics/btv033. Epub 2015 Jan 20. PMID: 25609793. | | | | +| metabat2 | Kang DD, Li F, Kirton E, Thomas A, Egan R, An H, Wang Z. MetaBAT 2: an adaptive binning algorithm for robust and efficient genome reconstruction from metagenome assemblies. PeerJ. 2019 Jul 26;7:e7359. doi: 10.7717/peerj.7359. PMID: 31388474; PMCID: PMC6662567. | | | | +| metaeuk | Levy Karin E, Mirdita M, Söding J. MetaEuk-sensitive, high-throughput gene discovery, and annotation for large-scale eukaryotic metagenomics. Microbiome. 2020 Apr 3;8(1):48. doi: 10.1186/s40168-020-00808-x. PMID: 32245390; PMCID: PMC7126354. | | | | +| metaspades | Nurk S, Meleshko D, Korobeynikov A, Pevzner PA. metaSPAdes: a new versatile metagenomic assembler. Genome Res. 2017 May;27(5):824-834. doi: 10.1101/gr.213959.116. Epub 2017 Mar 15. PMID: 28298430; PMCID: PMC5411777. | | | | +| mmseqs2 | Steinegger M, Söding J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. Nat Biotechnol. 2017 Nov;35(11):1026-1028. doi: 10.1038/nbt.3988. Epub 2017 Oct 16. PMID: 29035372. | | | | +| muscle | Edgar RC. Muscle5: High-accuracy alignment ensembles enable unbiased assessments of sequence homology and phylogeny. Nat Commun. 2022 Nov 15;13(1):6968. doi: 10.1038/s41467-022-34630-w. PMID: 36379955; PMCID: PMC9664440. | | | | +| prodigal | Hyatt D, Chen GL, Locascio PF, Land ML, Larimer FW, Hauser LJ. Prodigal: prokaryotic gene recognition and translation initiation site identification. BMC Bioinformatics. 2010 Mar 8;11:119. doi: 10.1186/1471-2105-11-119. PMID: 20211023; PMCID: PMC2848648. | | | | +| prodigal-gv | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | | | | +| pyrodigal | Larralde, M., (2022). Pyrodigal: Python bindings and interface to Prodigal, an efficient method for gene prediction in prokaryotes. Journal of Open Source Software, 7(72), 4296, https://doi.org/10.21105/joss.04296 | | | | +| qiime2 | Bolyen E, Rideout JR, Dillon MR, Bokulich NA, Abnet CC, Al-Ghalith GA, Alexander H, Alm EJ, Arumugam M, Asnicar F, Bai Y, Bisanz JE, Bittinger K, Brejnrod A, Brislawn CJ, Brown CT, Callahan BJ, Caraballo-Rodríguez AM, Chase J, Cope EK, Da Silva R, Diener C, Dorrestein PC, Douglas GM, Durall DM, Duvallet C, Edwardson CF, Ernst M, Estaki M, Fouquier J, Gauglitz JM, Gibbons SM, Gibson DL, Gonzalez A, Gorlick K, Guo J, Hillmann B, Holmes S, Holste H, Huttenhower C, Huttley GA, Janssen S, Jarmusch AK, Jiang L, Kaehler BD, Kang KB, Keefe CR, Keim P, Kelley ST, Knights D, Koester I, Kosciolek T, Kreps J, Langille MGI, Lee J, Ley R, Liu YX, Loftfield E, Lozupone C, Maher M, Marotz C, Martin BD, McDonald D, McIver LJ, Melnik AV, Metcalf JL, Morgan SC, Morton JT, Naimey AT, Navas-Molina JA, Nothias LF, Orchanian SB, Pearson T, Peoples SL, Petras D, Preuss ML, Pruesse E, Rasmussen LB, Rivers A, Robeson MS 2nd, Rosenthal P, Segata N, Shaffer M, Shiffer A, Sinha R, Song SJ, Spear JR, Swafford AD, Thompson LR, Torres PJ, Trinh P, Tripathi A, Turnbaugh PJ, Ul-Hasan S, van der Hooft JJJ, Vargas F, Vázquez-Baeza Y, Vogtmann E, von Hippel M, Walters W, Wan Y, Wang M, Warren J, Weber KC, Williamson CHD, Willis AD, Xu ZZ, Zaneveld JR, Zhang Y, Zhu Q, Knight R, Caporaso JG. Reproducible, interactive, scalable and extensible microbiome data science using QIIME 2. Nat Biotechnol. 2019 Aug;37(8):852-857. doi: 10.1038/s41587-019-0209-9. Erratum in: Nat Biotechnol. 2019 Sep;37(9):1091. PMID: 31341288; PMCID: PMC7015180. | | | | +| rnaspades | Bushmanova E, Antipov D, Lapidus A, Prjibelski AD. rnaSPAdes: a de novo transcriptome assembler and its application to RNA-Seq data. Gigascience. 2019 Sep 1;8(9):giz100. doi: 10.1093/gigascience/giz100. PMID: 31494669; PMCID: PMC6736328. | | | | +| samtools | Li H, Handsaker B, Wysoker A, Fennell T, Ruan J, Homer N, Marth G, Abecasis G, Durbin R; 1000 Genome Project Data Processing Subgroup. The Sequence Alignment/Map format and SAMtools. Bioinformatics. 2009 Aug 15;25(16):2078-9. doi: 10.1093/bioinformatics/btp352. Epub 2009 Jun 8. PMID: 19505943; PMCID: PMC2723002. | | | | +| seqkit | Shen W, Le S, Li Y, Hu F. SeqKit: A Cross-Platform and Ultrafast Toolkit for FASTA/Q File Manipulation. PLoS One. 2016 Oct 5;11(10):e0163962. doi: 10.1371/journal.pone.0163962. PMID: 27706213; PMCID: PMC5051824. | | | | +| spades | Bankevich A, Nurk S, Antipov D, Gurevich AA, Dvorkin M, Kulikov AS, Lesin VM, Nikolenko SI, Pham S, Prjibelski AD, Pyshkin AV, Sirotkin AV, Vyahhi N, Tesler G, Alekseyev MA, Pevzner PA. SPAdes: a new genome assembly algorithm and its applications to single-cell sequencing. J Comput Biol. 2012 May;19(5):455-77. doi: 10.1089/cmb.2012.0021. Epub 2012 Apr 16. PMID: 22506599; PMCID: PMC3342519. | | | | +| tiara | Karlicki M, Antonowicz S, Karnkowska A. Tiara: deep learning-based classification system for eukaryotic sequences. Bioinformatics. 2022 Jan 3;38(2):344-350. doi: 10.1093/bioinformatics/btab672. PMID: 34570171; PMCID: PMC8722755. | | | | +| virfinder | Ren J, Ahlgren NA, Lu YY, Fuhrman JA, Sun F. VirFinder: a novel k-mer based tool for identifying viral sequences from assembled metagenomic data. Microbiome. 2017 Jul 6;5(1):69. doi: 10.1186/s40168-017-0283-5. PMID: 28683828; PMCID: PMC5501583. | | | | \ No newline at end of file diff --git a/README.md b/README.md index 050ba8f..3598e5d 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ ___________________________________________________________________ Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. [doi: 10.1186/s12859-022-04973-8](https://doi.org/10.1186/s12859-022-04973-8). PMID: 36224545. +Please cite the software dependencies described under the [*Dependency Citation Table*](CITATIONS.md). + ___________________________________________________________________ ### Announcements From d5f60edab1013c08b54d35a768a650e68df13614 Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Mon, 19 Jun 2023 15:58:25 -0700 Subject: [PATCH 05/16] Update CITATIONS.md --- CITATIONS.md | 85 ++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/CITATIONS.md b/CITATIONS.md index 602bb78..718b836 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -1,43 +1,42 @@ -# Dependency Citations -| Dependency | Citation | | | | -|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|---|---| -| antismash | Blin K, Shaw S, Kloosterman AM, Charlop-Powers Z, van Wezel GP, Medema MH, Weber T. antiSMASH 6.0: improving cluster detection and comparison capabilities. Nucleic Acids Res. 2021 Jul 2;49(W1):W29-W35. doi: 10.1093/nar/gkab335. PMID: 33978755; PMCID: PMC8262755. | | | | -| bbtools | https://sourceforge.net/projects/bbmap/ | | | | -| bowtie2 | Langmead B, Salzberg SL. Fast gapped-read alignment with Bowtie 2. Nat Methods. 2012 Mar 4;9(4):357-9. doi: 10.1038/nmeth.1923. PMID: 22388286; PMCID: PMC3322381. | | | | -| busco | Manni M, Berkeley MR, Seppey M, Simão FA, Zdobnov EM. BUSCO Update: Novel and Streamlined Workflows along with Broader and Deeper Phylogenetic Coverage for Scoring of Eukaryotic, Prokaryotic, and Viral Genomes. Mol Biol Evol. 2021 Sep 27;38(10):4647-4654. doi: 10.1093/molbev/msab199. PMID: 34320186; PMCID: PMC8476166. | | | | -| checkm2 | Alex Chklovski, Donovan H. Parks, Ben J. Woodcroft, Gene W. Tyson bioRxiv 2022.07.11.499243; doi: https://doi.org/10.1101/2022.07.11.499243 | | | | -| checkv | Nayfach S, Camargo AP, Schulz F, Eloe-Fadrosh E, Roux S, Kyrpides NC. CheckV assesses the quality and completeness of metagenome-assembled viral genomes. Nat Biotechnol. 2021 May;39(5):578-585. doi: 10.1038/s41587-020-00774-7. Epub 2020 Dec 21. PMID: 33349699; PMCID: PMC8116208. | | | | -| clipkit | Steenwyk JL, Buida TJ 3rd, Li Y, Shen XX, Rokas A. ClipKIT: A multiple sequence alignment trimming software for accurate phylogenomic inference. PLoS Biol. 2020 Dec 2;18(12):e3001007. doi: 10.1371/journal.pbio.3001007. PMID: 33264284; PMCID: PMC7735675. | | | | -| concoct | Alneberg J, Bjarnason BS, de Bruijn I, Schirmer M, Quick J, Ijaz UZ, Lahti L, Loman NJ, Andersson AF, Quince C. Binning metagenomic contigs by coverage and composition. Nat Methods. 2014 Nov;11(11):1144-6. doi: 10.1038/nmeth.3103. Epub 2014 Sep 14. PMID: 25218180. | | | | -| coverm | https://github.com/wwood/CoverM | | | | -| dada2 | Callahan BJ, McMurdie PJ, Rosen MJ, Han AW, Johnson AJ, Holmes SP. DADA2: High-resolution sample inference from Illumina amplicon data. Nat Methods. 2016 Jul;13(7):581-3. doi: 10.1038/nmeth.3869. Epub 2016 May 23. PMID: 27214047; PMCID: PMC4927377. | | | | -| das_tool | Sieber CMK, Probst AJ, Sharrar A, Thomas BC, Hess M, Tringe SG, Banfield JF. Recovery of genomes from metagenomes via a dereplication, aggregation and scoring strategy. Nat Microbiol. 2018 Jul;3(7):836-843. doi: 10.1038/s41564-018-0171-1. Epub 2018 May 28. PMID: 29807988; PMCID: PMC6786971. | | | | -| diamond | Buchfink B, Reuter K, Drost HG. Sensitive protein alignments at tree-of-life scale using DIAMOND. Nat Methods. 2021 Apr;18(4):366-368. doi: 10.1038/s41592-021-01101-x. Epub 2021 Apr 7. PMID: 33828273; PMCID: PMC8026399. | | | | -| fastani | Jain C, Rodriguez-R LM, Phillippy AM, Konstantinidis KT, Aluru S. High throughput ANI analysis of 90K prokaryotic genomes reveals clear species boundaries. Nat Commun. 2018 Nov 30;9(1):5114. doi: 10.1038/s41467-018-07641-9. PMID: 30504855; PMCID: PMC6269478. | | | | -| fastp | Chen S, Zhou Y, Chen Y, Gu J. fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics. 2018 Sep 1;34(17):i884-i890. doi: 10.1093/bioinformatics/bty560. PMID: 30423086; PMCID: PMC6129281. | | | | -| fastq_preprocessor | Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. doi: 10.1186/s12859-022-04973-8. PMID: 36224545; PMCID: PMC9554839. | | | | -| fasttree | Price MN, Dehal PS, Arkin AP. FastTree 2--approximately maximum-likelihood trees for large alignments. PLoS One. 2010 Mar 10;5(3):e9490. doi: 10.1371/journal.pone.0009490. PMID: 20224823; PMCID: PMC2835736. | | | | -| featurecounts | Liao Y, Smyth GK, Shi W. featureCounts: an efficient general purpose program for assigning sequence reads to genomic features. Bioinformatics. 2014 Apr 1;30(7):923-30. doi: 10.1093/bioinformatics/btt656. Epub 2013 Nov 13. PMID: 24227677. | | | | -| genomad | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | | | | -| gtdbtk | Chaumeil PA, Mussig AJ, Hugenholtz P, Parks DH. GTDB-Tk v2: memory friendly classification with the genome taxonomy database. Bioinformatics. 2022 Nov 30;38(23):5315-5316. doi: 10.1093/bioinformatics/btac672. PMID: 36218463; PMCID: PMC9710552. | | | | -| hmmer | Eddy SR. Accelerated Profile HMM Searches. PLoS Comput Biol. 2011 Oct;7(10):e1002195. doi: 10.1371/journal.pcbi.1002195. Epub 2011 Oct 20. PMID: 22039361; PMCID: PMC3197634. | | | | -| iqtree | Minh BQ, Schmidt HA, Chernomor O, Schrempf D, Woodhams MD, von Haeseler A, Lanfear R. IQ-TREE 2: New Models and Efficient Methods for Phylogenetic Inference in the Genomic Era. Mol Biol Evol. 2020 May 1;37(5):1530-1534. doi: 10.1093/molbev/msaa015. Erratum in: Mol Biol Evol. 2020 Aug 1;37(8):2461. PMID: 32011700; PMCID: PMC7182206. | | | | -| kofamscan | Aramaki T, Blanc-Mathieu R, Endo H, Ohkubo K, Kanehisa M, Goto S, Ogata H. KofamKOALA: KEGG Ortholog assignment based on profile HMM and adaptive score threshold. Bioinformatics. 2020 Apr 1;36(7):2251-2252. doi: 10.1093/bioinformatics/btz859. PMID: 31742321; PMCID: PMC7141845. | | | | -| krona | Ondov BD, Bergman NH, Phillippy AM. Interactive metagenomic visualization in a Web browser. BMC Bioinformatics. 2011 Sep 30;12:385. doi: 10.1186/1471-2105-12-385. PMID: 21961884; PMCID: PMC3190407. | | | | -| maxbin2 | Wu YW, Tang YH, Tringe SG, Simmons BA, Singer SW. MaxBin: an automated binning method to recover individual genomes from metagenomes using an expectation-maximization algorithm. Microbiome. 2014 Aug 1;2:26. doi: 10.1186/2049-2618-2-26. PMID: 25136443; PMCID: PMC4129434. | | | | -| megahit | Li D, Liu CM, Luo R, Sadakane K, Lam TW. MEGAHIT: an ultra-fast single-node solution for large and complex metagenomics assembly via succinct de Bruijn graph. Bioinformatics. 2015 May 15;31(10):1674-6. doi: 10.1093/bioinformatics/btv033. Epub 2015 Jan 20. PMID: 25609793. | | | | -| metabat2 | Kang DD, Li F, Kirton E, Thomas A, Egan R, An H, Wang Z. MetaBAT 2: an adaptive binning algorithm for robust and efficient genome reconstruction from metagenome assemblies. PeerJ. 2019 Jul 26;7:e7359. doi: 10.7717/peerj.7359. PMID: 31388474; PMCID: PMC6662567. | | | | -| metaeuk | Levy Karin E, Mirdita M, Söding J. MetaEuk-sensitive, high-throughput gene discovery, and annotation for large-scale eukaryotic metagenomics. Microbiome. 2020 Apr 3;8(1):48. doi: 10.1186/s40168-020-00808-x. PMID: 32245390; PMCID: PMC7126354. | | | | -| metaspades | Nurk S, Meleshko D, Korobeynikov A, Pevzner PA. metaSPAdes: a new versatile metagenomic assembler. Genome Res. 2017 May;27(5):824-834. doi: 10.1101/gr.213959.116. Epub 2017 Mar 15. PMID: 28298430; PMCID: PMC5411777. | | | | -| mmseqs2 | Steinegger M, Söding J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. Nat Biotechnol. 2017 Nov;35(11):1026-1028. doi: 10.1038/nbt.3988. Epub 2017 Oct 16. PMID: 29035372. | | | | -| muscle | Edgar RC. Muscle5: High-accuracy alignment ensembles enable unbiased assessments of sequence homology and phylogeny. Nat Commun. 2022 Nov 15;13(1):6968. doi: 10.1038/s41467-022-34630-w. PMID: 36379955; PMCID: PMC9664440. | | | | -| prodigal | Hyatt D, Chen GL, Locascio PF, Land ML, Larimer FW, Hauser LJ. Prodigal: prokaryotic gene recognition and translation initiation site identification. BMC Bioinformatics. 2010 Mar 8;11:119. doi: 10.1186/1471-2105-11-119. PMID: 20211023; PMCID: PMC2848648. | | | | -| prodigal-gv | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | | | | -| pyrodigal | Larralde, M., (2022). Pyrodigal: Python bindings and interface to Prodigal, an efficient method for gene prediction in prokaryotes. Journal of Open Source Software, 7(72), 4296, https://doi.org/10.21105/joss.04296 | | | | -| qiime2 | Bolyen E, Rideout JR, Dillon MR, Bokulich NA, Abnet CC, Al-Ghalith GA, Alexander H, Alm EJ, Arumugam M, Asnicar F, Bai Y, Bisanz JE, Bittinger K, Brejnrod A, Brislawn CJ, Brown CT, Callahan BJ, Caraballo-Rodríguez AM, Chase J, Cope EK, Da Silva R, Diener C, Dorrestein PC, Douglas GM, Durall DM, Duvallet C, Edwardson CF, Ernst M, Estaki M, Fouquier J, Gauglitz JM, Gibbons SM, Gibson DL, Gonzalez A, Gorlick K, Guo J, Hillmann B, Holmes S, Holste H, Huttenhower C, Huttley GA, Janssen S, Jarmusch AK, Jiang L, Kaehler BD, Kang KB, Keefe CR, Keim P, Kelley ST, Knights D, Koester I, Kosciolek T, Kreps J, Langille MGI, Lee J, Ley R, Liu YX, Loftfield E, Lozupone C, Maher M, Marotz C, Martin BD, McDonald D, McIver LJ, Melnik AV, Metcalf JL, Morgan SC, Morton JT, Naimey AT, Navas-Molina JA, Nothias LF, Orchanian SB, Pearson T, Peoples SL, Petras D, Preuss ML, Pruesse E, Rasmussen LB, Rivers A, Robeson MS 2nd, Rosenthal P, Segata N, Shaffer M, Shiffer A, Sinha R, Song SJ, Spear JR, Swafford AD, Thompson LR, Torres PJ, Trinh P, Tripathi A, Turnbaugh PJ, Ul-Hasan S, van der Hooft JJJ, Vargas F, Vázquez-Baeza Y, Vogtmann E, von Hippel M, Walters W, Wan Y, Wang M, Warren J, Weber KC, Williamson CHD, Willis AD, Xu ZZ, Zaneveld JR, Zhang Y, Zhu Q, Knight R, Caporaso JG. Reproducible, interactive, scalable and extensible microbiome data science using QIIME 2. Nat Biotechnol. 2019 Aug;37(8):852-857. doi: 10.1038/s41587-019-0209-9. Erratum in: Nat Biotechnol. 2019 Sep;37(9):1091. PMID: 31341288; PMCID: PMC7015180. | | | | -| rnaspades | Bushmanova E, Antipov D, Lapidus A, Prjibelski AD. rnaSPAdes: a de novo transcriptome assembler and its application to RNA-Seq data. Gigascience. 2019 Sep 1;8(9):giz100. doi: 10.1093/gigascience/giz100. PMID: 31494669; PMCID: PMC6736328. | | | | -| samtools | Li H, Handsaker B, Wysoker A, Fennell T, Ruan J, Homer N, Marth G, Abecasis G, Durbin R; 1000 Genome Project Data Processing Subgroup. The Sequence Alignment/Map format and SAMtools. Bioinformatics. 2009 Aug 15;25(16):2078-9. doi: 10.1093/bioinformatics/btp352. Epub 2009 Jun 8. PMID: 19505943; PMCID: PMC2723002. | | | | -| seqkit | Shen W, Le S, Li Y, Hu F. SeqKit: A Cross-Platform and Ultrafast Toolkit for FASTA/Q File Manipulation. PLoS One. 2016 Oct 5;11(10):e0163962. doi: 10.1371/journal.pone.0163962. PMID: 27706213; PMCID: PMC5051824. | | | | -| spades | Bankevich A, Nurk S, Antipov D, Gurevich AA, Dvorkin M, Kulikov AS, Lesin VM, Nikolenko SI, Pham S, Prjibelski AD, Pyshkin AV, Sirotkin AV, Vyahhi N, Tesler G, Alekseyev MA, Pevzner PA. SPAdes: a new genome assembly algorithm and its applications to single-cell sequencing. J Comput Biol. 2012 May;19(5):455-77. doi: 10.1089/cmb.2012.0021. Epub 2012 Apr 16. PMID: 22506599; PMCID: PMC3342519. | | | | -| tiara | Karlicki M, Antonowicz S, Karnkowska A. Tiara: deep learning-based classification system for eukaryotic sequences. Bioinformatics. 2022 Jan 3;38(2):344-350. doi: 10.1093/bioinformatics/btab672. PMID: 34570171; PMCID: PMC8722755. | | | | -| virfinder | Ren J, Ahlgren NA, Lu YY, Fuhrman JA, Sun F. VirFinder: a novel k-mer based tool for identifying viral sequences from assembled metagenomic data. Microbiome. 2017 Jul 6;5(1):69. doi: 10.1186/s40168-017-0283-5. PMID: 28683828; PMCID: PMC5501583. | | | | \ No newline at end of file +| Dependency | Citation | +|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| antismash | Blin K, Shaw S, Kloosterman AM, Charlop-Powers Z, van Wezel GP, Medema MH, Weber T. antiSMASH 6.0: improving cluster detection and comparison capabilities. Nucleic Acids Res. 2021 Jul 2;49(W1):W29-W35. doi: 10.1093/nar/gkab335. PMID: 33978755; PMCID: PMC8262755. | +| bbtools | https://sourceforge.net/projects/bbmap/ | +| bowtie2 | Langmead B, Salzberg SL. Fast gapped-read alignment with Bowtie 2. Nat Methods. 2012 Mar 4;9(4):357-9. doi: 10.1038/nmeth.1923. PMID: 22388286; PMCID: PMC3322381. | +| busco | Manni M, Berkeley MR, Seppey M, Simão FA, Zdobnov EM. BUSCO Update: Novel and Streamlined Workflows along with Broader and Deeper Phylogenetic Coverage for Scoring of Eukaryotic, Prokaryotic, and Viral Genomes. Mol Biol Evol. 2021 Sep 27;38(10):4647-4654. doi: 10.1093/molbev/msab199. PMID: 34320186; PMCID: PMC8476166. | +| checkm2 | Alex Chklovski, Donovan H. Parks, Ben J. Woodcroft, Gene W. Tyson bioRxiv 2022.07.11.499243; doi: https://doi.org/10.1101/2022.07.11.499243 | +| checkv | Nayfach S, Camargo AP, Schulz F, Eloe-Fadrosh E, Roux S, Kyrpides NC. CheckV assesses the quality and completeness of metagenome-assembled viral genomes. Nat Biotechnol. 2021 May;39(5):578-585. doi: 10.1038/s41587-020-00774-7. Epub 2020 Dec 21. PMID: 33349699; PMCID: PMC8116208. | +| clipkit | Steenwyk JL, Buida TJ 3rd, Li Y, Shen XX, Rokas A. ClipKIT: A multiple sequence alignment trimming software for accurate phylogenomic inference. PLoS Biol. 2020 Dec 2;18(12):e3001007. doi: 10.1371/journal.pbio.3001007. PMID: 33264284; PMCID: PMC7735675. | +| concoct | Alneberg J, Bjarnason BS, de Bruijn I, Schirmer M, Quick J, Ijaz UZ, Lahti L, Loman NJ, Andersson AF, Quince C. Binning metagenomic contigs by coverage and composition. Nat Methods. 2014 Nov;11(11):1144-6. doi: 10.1038/nmeth.3103. Epub 2014 Sep 14. PMID: 25218180. | +| coverm | https://github.com/wwood/CoverM | +| dada2 | Callahan BJ, McMurdie PJ, Rosen MJ, Han AW, Johnson AJ, Holmes SP. DADA2: High-resolution sample inference from Illumina amplicon data. Nat Methods. 2016 Jul;13(7):581-3. doi: 10.1038/nmeth.3869. Epub 2016 May 23. PMID: 27214047; PMCID: PMC4927377. | +| das_tool | Sieber CMK, Probst AJ, Sharrar A, Thomas BC, Hess M, Tringe SG, Banfield JF. Recovery of genomes from metagenomes via a dereplication, aggregation and scoring strategy. Nat Microbiol. 2018 Jul;3(7):836-843. doi: 10.1038/s41564-018-0171-1. Epub 2018 May 28. PMID: 29807988; PMCID: PMC6786971. | +| diamond | Buchfink B, Reuter K, Drost HG. Sensitive protein alignments at tree-of-life scale using DIAMOND. Nat Methods. 2021 Apr;18(4):366-368. doi: 10.1038/s41592-021-01101-x. Epub 2021 Apr 7. PMID: 33828273; PMCID: PMC8026399. | +| fastani | Jain C, Rodriguez-R LM, Phillippy AM, Konstantinidis KT, Aluru S. High throughput ANI analysis of 90K prokaryotic genomes reveals clear species boundaries. Nat Commun. 2018 Nov 30;9(1):5114. doi: 10.1038/s41467-018-07641-9. PMID: 30504855; PMCID: PMC6269478. | +| fastp | Chen S, Zhou Y, Chen Y, Gu J. fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics. 2018 Sep 1;34(17):i884-i890. doi: 10.1093/bioinformatics/bty560. PMID: 30423086; PMCID: PMC6129281. | +| fastq_preprocessor | Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. doi: 10.1186/s12859-022-04973-8. PMID: 36224545; PMCID: PMC9554839. | +| fasttree | Price MN, Dehal PS, Arkin AP. FastTree 2--approximately maximum-likelihood trees for large alignments. PLoS One. 2010 Mar 10;5(3):e9490. doi: 10.1371/journal.pone.0009490. PMID: 20224823; PMCID: PMC2835736. | +| featurecounts | Liao Y, Smyth GK, Shi W. featureCounts: an efficient general purpose program for assigning sequence reads to genomic features. Bioinformatics. 2014 Apr 1;30(7):923-30. doi: 10.1093/bioinformatics/btt656. Epub 2013 Nov 13. PMID: 24227677. | +| genomad | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | +| gtdbtk | Chaumeil PA, Mussig AJ, Hugenholtz P, Parks DH. GTDB-Tk v2: memory friendly classification with the genome taxonomy database. Bioinformatics. 2022 Nov 30;38(23):5315-5316. doi: 10.1093/bioinformatics/btac672. PMID: 36218463; PMCID: PMC9710552. | +| hmmer | Eddy SR. Accelerated Profile HMM Searches. PLoS Comput Biol. 2011 Oct;7(10):e1002195. doi: 10.1371/journal.pcbi.1002195. Epub 2011 Oct 20. PMID: 22039361; PMCID: PMC3197634. | +| iqtree | Minh BQ, Schmidt HA, Chernomor O, Schrempf D, Woodhams MD, von Haeseler A, Lanfear R. IQ-TREE 2: New Models and Efficient Methods for Phylogenetic Inference in the Genomic Era. Mol Biol Evol. 2020 May 1;37(5):1530-1534. doi: 10.1093/molbev/msaa015. Erratum in: Mol Biol Evol. 2020 Aug 1;37(8):2461. PMID: 32011700; PMCID: PMC7182206. | +| kofamscan | Aramaki T, Blanc-Mathieu R, Endo H, Ohkubo K, Kanehisa M, Goto S, Ogata H. KofamKOALA: KEGG Ortholog assignment based on profile HMM and adaptive score threshold. Bioinformatics. 2020 Apr 1;36(7):2251-2252. doi: 10.1093/bioinformatics/btz859. PMID: 31742321; PMCID: PMC7141845. | +| krona | Ondov BD, Bergman NH, Phillippy AM. Interactive metagenomic visualization in a Web browser. BMC Bioinformatics. 2011 Sep 30;12:385. doi: 10.1186/1471-2105-12-385. PMID: 21961884; PMCID: PMC3190407. | +| maxbin2 | Wu YW, Tang YH, Tringe SG, Simmons BA, Singer SW. MaxBin: an automated binning method to recover individual genomes from metagenomes using an expectation-maximization algorithm. Microbiome. 2014 Aug 1;2:26. doi: 10.1186/2049-2618-2-26. PMID: 25136443; PMCID: PMC4129434. | +| megahit | Li D, Liu CM, Luo R, Sadakane K, Lam TW. MEGAHIT: an ultra-fast single-node solution for large and complex metagenomics assembly via succinct de Bruijn graph. Bioinformatics. 2015 May 15;31(10):1674-6. doi: 10.1093/bioinformatics/btv033. Epub 2015 Jan 20. PMID: 25609793. | +| metabat2 | Kang DD, Li F, Kirton E, Thomas A, Egan R, An H, Wang Z. MetaBAT 2: an adaptive binning algorithm for robust and efficient genome reconstruction from metagenome assemblies. PeerJ. 2019 Jul 26;7:e7359. doi: 10.7717/peerj.7359. PMID: 31388474; PMCID: PMC6662567. | +| metaeuk | Levy Karin E, Mirdita M, Söding J. MetaEuk-sensitive, high-throughput gene discovery, and annotation for large-scale eukaryotic metagenomics. Microbiome. 2020 Apr 3;8(1):48. doi: 10.1186/s40168-020-00808-x. PMID: 32245390; PMCID: PMC7126354. | +| metaspades | Nurk S, Meleshko D, Korobeynikov A, Pevzner PA. metaSPAdes: a new versatile metagenomic assembler. Genome Res. 2017 May;27(5):824-834. doi: 10.1101/gr.213959.116. Epub 2017 Mar 15. PMID: 28298430; PMCID: PMC5411777. | +| mmseqs2 | Steinegger M, Söding J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. Nat Biotechnol. 2017 Nov;35(11):1026-1028. doi: 10.1038/nbt.3988. Epub 2017 Oct 16. PMID: 29035372. | +| muscle | Edgar RC. Muscle5: High-accuracy alignment ensembles enable unbiased assessments of sequence homology and phylogeny. Nat Commun. 2022 Nov 15;13(1):6968. doi: 10.1038/s41467-022-34630-w. PMID: 36379955; PMCID: PMC9664440. | +| prodigal | Hyatt D, Chen GL, Locascio PF, Land ML, Larimer FW, Hauser LJ. Prodigal: prokaryotic gene recognition and translation initiation site identification. BMC Bioinformatics. 2010 Mar 8;11:119. doi: 10.1186/1471-2105-11-119. PMID: 20211023; PMCID: PMC2848648. | +| prodigal-gv | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | +| pyrodigal | Larralde, M., (2022). Pyrodigal: Python bindings and interface to Prodigal, an efficient method for gene prediction in prokaryotes. Journal of Open Source Software, 7(72), 4296, https://doi.org/10.21105/joss.04296 | +| qiime2 | Bolyen E, Rideout JR, Dillon MR, Bokulich NA, Abnet CC, Al-Ghalith GA, Alexander H, Alm EJ, Arumugam M, Asnicar F, Bai Y, Bisanz JE, Bittinger K, Brejnrod A, Brislawn CJ, Brown CT, Callahan BJ, Caraballo-Rodríguez AM, Chase J, Cope EK, Da Silva R, Diener C, Dorrestein PC, Douglas GM, Durall DM, Duvallet C, Edwardson CF, Ernst M, Estaki M, Fouquier J, Gauglitz JM, Gibbons SM, Gibson DL, Gonzalez A, Gorlick K, Guo J, Hillmann B, Holmes S, Holste H, Huttenhower C, Huttley GA, Janssen S, Jarmusch AK, Jiang L, Kaehler BD, Kang KB, Keefe CR, Keim P, Kelley ST, Knights D, Koester I, Kosciolek T, Kreps J, Langille MGI, Lee J, Ley R, Liu YX, Loftfield E, Lozupone C, Maher M, Marotz C, Martin BD, McDonald D, McIver LJ, Melnik AV, Metcalf JL, Morgan SC, Morton JT, Naimey AT, Navas-Molina JA, Nothias LF, Orchanian SB, Pearson T, Peoples SL, Petras D, Preuss ML, Pruesse E, Rasmussen LB, Rivers A, Robeson MS 2nd, Rosenthal P, Segata N, Shaffer M, Shiffer A, Sinha R, Song SJ, Spear JR, Swafford AD, Thompson LR, Torres PJ, Trinh P, Tripathi A, Turnbaugh PJ, Ul-Hasan S, van der Hooft JJJ, Vargas F, Vázquez-Baeza Y, Vogtmann E, von Hippel M, Walters W, Wan Y, Wang M, Warren J, Weber KC, Williamson CHD, Willis AD, Xu ZZ, Zaneveld JR, Zhang Y, Zhu Q, Knight R, Caporaso JG. Reproducible, interactive, scalable and extensible microbiome data science using QIIME 2. Nat Biotechnol. 2019 Aug;37(8):852-857. doi: 10.1038/s41587-019-0209-9. Erratum in: Nat Biotechnol. 2019 Sep;37(9):1091. PMID: 31341288; PMCID: PMC7015180. | +| rnaspades | Bushmanova E, Antipov D, Lapidus A, Prjibelski AD. rnaSPAdes: a de novo transcriptome assembler and its application to RNA-Seq data. Gigascience. 2019 Sep 1;8(9):giz100. doi: 10.1093/gigascience/giz100. PMID: 31494669; PMCID: PMC6736328. | +| samtools | Li H, Handsaker B, Wysoker A, Fennell T, Ruan J, Homer N, Marth G, Abecasis G, Durbin R; 1000 Genome Project Data Processing Subgroup. The Sequence Alignment/Map format and SAMtools. Bioinformatics. 2009 Aug 15;25(16):2078-9. doi: 10.1093/bioinformatics/btp352. Epub 2009 Jun 8. PMID: 19505943; PMCID: PMC2723002. | +| seqkit | Shen W, Le S, Li Y, Hu F. SeqKit: A Cross-Platform and Ultrafast Toolkit for FASTA/Q File Manipulation. PLoS One. 2016 Oct 5;11(10):e0163962. doi: 10.1371/journal.pone.0163962. PMID: 27706213; PMCID: PMC5051824. | +| spades | Bankevich A, Nurk S, Antipov D, Gurevich AA, Dvorkin M, Kulikov AS, Lesin VM, Nikolenko SI, Pham S, Prjibelski AD, Pyshkin AV, Sirotkin AV, Vyahhi N, Tesler G, Alekseyev MA, Pevzner PA. SPAdes: a new genome assembly algorithm and its applications to single-cell sequencing. J Comput Biol. 2012 May;19(5):455-77. doi: 10.1089/cmb.2012.0021. Epub 2012 Apr 16. PMID: 22506599; PMCID: PMC3342519. | +| tiara | Karlicki M, Antonowicz S, Karnkowska A. Tiara: deep learning-based classification system for eukaryotic sequences. Bioinformatics. 2022 Jan 3;38(2):344-350. doi: 10.1093/bioinformatics/btab672. PMID: 34570171; PMCID: PMC8722755. | +| virfinder | Ren J, Ahlgren NA, Lu YY, Fuhrman JA, Sun F. VirFinder: a novel k-mer based tool for identifying viral sequences from assembled metagenomic data. Microbiome. 2017 Jul 6;5(1):69. doi: 10.1186/s40168-017-0283-5. PMID: 28683828; PMCID: PMC5501583. | \ No newline at end of file From c2e209f422de1998b732b6903401a714a29c3773 Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Wed, 21 Jun 2023 12:50:28 -0700 Subject: [PATCH 06/16] Update LICENSE --- LICENSE | 682 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 661 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index 62f2362..2def0e8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,661 @@ -MIT License - -Copyright (c) [2022] [Josh L. Espinoza (https://github.com/jolespin)] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file From 3f47cdab4a6a0963673e408c2d07e74fdb3b3e90 Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Mon, 3 Jul 2023 13:29:42 -0700 Subject: [PATCH 07/16] eukaryotic_gene_modeling_wrapper.py and adding VFDB to annotate.py --- .DS_Store | Bin 14340 -> 0 bytes DEVELOPMENT.md | 9 +- src/.DS_Store | Bin 8196 -> 0 bytes src/annotate.py | 75 +- src/assembly-sequential.py | 1092 ------------- src/binning-eukaryotic.py | 220 ++- src/binning-prokaryotic.py | 245 ++- src/scripts/.DS_Store | Bin 6148 -> 0 bytes src/scripts/append_geneid_to_barrnap_gff.py | 88 + src/scripts/append_geneid_to_prodigal_gff.py | 4 +- .../append_geneid_to_transdecoder_gff.py | 1 + .../eukaryotic_gene_modeling_wrapper.py | 1443 +++++++++++++++++ src/scripts/merge_annotations.py | 26 +- src/scripts/partition_organelle_sequences.py | 125 ++ 14 files changed, 2191 insertions(+), 1137 deletions(-) delete mode 100644 .DS_Store delete mode 100644 src/.DS_Store delete mode 100755 src/assembly-sequential.py delete mode 100644 src/scripts/.DS_Store create mode 100755 src/scripts/append_geneid_to_barrnap_gff.py create mode 100755 src/scripts/eukaryotic_gene_modeling_wrapper.py create mode 100755 src/scripts/partition_organelle_sequences.py diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index cb119c56d38772cf0a7f1d7b25e9eea6ed365daf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14340 zcmeHN32+nV75?A)+KptqCPtQpWf=@M#)^@AAAscp;|mP#Lc1RBPjRzfU<5D`yW%3AzcPDm~-=^@|=cqZbRKmx>19L|8l zGvxDm$O+cQ`>*5IGKuQP=J*kpKwJWG3B)B3mq1(sSC|ASx8osdcp3kVOCTfPa+;USbl?vS5okXR5!iv(7zePnT&1)rX9k_;dofSiu8qtcd@_B@mZD3<;!Rn-bf5hOJ!=-{=?{ zwfvI@B6#j*@LdYPB|?w;+t%B_`I9^t_inmvd`i3jE{P;C*5K9IL2|XtKQ@3a^J{DJN!P6 zXOl1B4!V8bf&QRtZ;9Y)m{_!FAo^f~Z202d@S*tO}pxNfceQfYK=_IFoT$EVP(j0?f zEA46YyYSbkOvo=VXu1QqE7`6LEHY~}-MighryP*@REt$(=<(QxTprme@%xllXbgQ5 zd{c6}(pXcgG4$h$8^tFMq=Q*$uCo~oJLsJ|?15nCn9GY#^{8_DChQvwjM)9I&PlJ! zue7#cD|=noPA9E%x`Vz@X9sR%fG$_>bh2$w5gIldxXuC8Yu*W~3TO8`(j|$PMHGxr5wI9w5ibgXA=M zf}A7g$sfq8B!CtcKpM!91^KW73SlLbLLJzk9@?N2Hp3R^f^A?22ROk6 zUI@YjOu}Bc8TP{gI0(1F9dI{%6Yhm?!S~^(@G$%g9)UA31HXZD@FKhlufgl^j*u** z2%?ZKEEF#Rqsl0U_GR0fr&A$PQtO+>XhBm+ScB& z;fhtKXBW`7e{IGhU1l!Ts;V^&VFYr>hEN)beFk`ynlQjKG(;hF2K8)lwozl6OF?^3 zQ(SvR3U@h$k6x--CZeXuNxWL1DHKshy$K;jEEWjBkcNV539LiibSmOMj#NB)cm{}*CggiOdq zR2M@PtVL8eKqIV!jS8;2VLR-A>lwB^FbX~x13yf`$Kexj6XN?;hVMggCwv9I249D} z;6C^++z;P_!*CoPgcI-(`~(qy8lHfs;XJ$?h2xEC9K##4I3D#iHE|ph@mTUyfiLHD zePpk5p5C#M2(l?;_fWKBqRa2~_`Ji|Ms-O`CW)PR$ZirOBbb8rk#RDNSAn6)fTBg@ zMD)dG3Q$MnRBPAOYo|!v|`^rNnDh-s&>oH-JVge zZ$H}>fsjc^<@NCIt@CY9#m3t5%z4L#`B``~B#LK3qL3twuB*GJ_mYiCn2A3O?`jtr zOnED=E2&&vzpiz|Oez`H%NaP5ccpX~;KZAnwm{Zp>as*6+%Wm_wRFZD zRNUh#tHOG8I^$ANj5I&Wg!}@WU}v^Sd7L7%g!;~G5uYkqtfC}4xg}GY$}6y`J!ICD z#+q6YpVAz*eY{y$XA{vxM4L3acb%@TNfaZfH#DO~L=r_$NkzRgY~w~P8Wl8k&3BVQuy}$X*P?P$y(A(28n}sQPB>P+sGku7rBod!GCxjCTGZF9(( zw~!vnDHO-z3>|HfmKfzE#6Pbtlk$ynGGd=s3ynoaS)f$P1{WJGlAM4FC9!I$u}G2u zU0S+OrBsb1N~x4pS4(RpnNY3f^?GBY5m}TvrI!rZd&vFdC_?rWLiQYa9)bEd@+PvU zhyc}r9{s{>Fe<)b5d&%^vS|&}LK8G2SXX$q*j|frR`o~9+OTSkec8*WXrK@^X4bULHAW@#jH9@O zdW|T#r=Z-u_}vO38v_VW1iBx(?C8##nnGY>lwhf#lDC#tTd`7B78Np3_~J1qyboe$ z)V9K(dg}puw6dv{f{15e_t1W36@#OLc^OLzckQ+w=|8eToh=*c6+-SsiF7t!*&_ z4c3{X->3>SXufmPX1#vSKm+l_4z>Fx#ZKtulPdf#r(B z`91k7xk%n3?~r#v0F9E+)58*2%A$)Bbk$bF8g$fXDvvs9TbYwK3?rEIpvk-&;6`-P z_G45*(|Oc6;|aalG{>XxB%Fn(;05?2T!2^LZ6Q%e!jv9H7lK(R7FG!s!79`UjY5mi z%6AryHwKlhDuqDnQHmH5Wqc>LE}2?dTiUc8SG+D=uJoSP#G{hv?E7p~!qeF_8MtJr z)+8sfOgOFP7hpA+<-lpR$ZRsn0_vy8l5`fUNh>F?+&b+oH)EAXCA3mqqt(iZEXz)- zYwNH&o8D_|!i88&B8Jn|mjv%8$#WQuT)>d|A|xZQWiYW^UKs|-%@`!NLkDbNaR(2S zT`&x8_!#VFIqJO()}O+-g9gm|;fpNpptH>Do!`un}R}}XLRK2YGVL5I?R02_*4nw9xPW*2Vb#|0>u*{1M|4h)duS zB>;&{T}=(RNa&Bs==Es(^-3eXp2ibPHXMK0r+8ShPw}uqKgFxUGkjp5jaIOn<|SU? kaoIlte7`SzSQs3S|M@pQ?E9s7{Lhez|B$MVt3Uq#4=s4^ga7~l diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index edb9d3e..47e5b9a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -211,15 +211,17 @@ ________________________________________________________________ #### Path to `v2.0.0`: **Definitely:** +* Add contig region and exons to `prodigal` GFF. * Consistent usage of the following terms: 1) dataframe vs. table; 2) protein-cluster vs. orthogroup. * Add support for `FAMSA` in `phylogeny.py` * Create a `assembly-longreads.py` module that uses `MetaFlye` -* Create a `noncoding.py` module that uses `tRNASCAN-SE` and other goodies. +* Add tRNA and rRNA detection in prokaryotic and eukaryotic binning modules. * Expand Microeukaryotic Protein Database to include more fungi (Mycocosm) * Add MAG-level counts to prokaryotic and eukaryotic. Add optional bam file for viral binning, if so then add MAG-level counts * Support genome table input for `biosynthetic.py`, `phylogeny.py`, `index.py`, etc. * Install each module via `bioconda` * Add checks for `annotate.py` to ensure there are no proteins > 100K in length. +* Add VFDB to `annotate.py` * Add support for `Salmon` in `mapping.py` and `index.py`. This can be used instead of `STAR` which will require adding the `exon` field to `Prodigal` GFF file (`MetaEuk` modified GFF files already have exon ids). * Speed up `binning-eukaryotic.py` by accessing `BUSCO` backends and only running gene calls for genes relevant to genome. If it passes `BUSCO` filters, then run actual gene calls. * Build a clustered version of the Microeukaryotic Protein Database that is more efficient to run. @@ -241,7 +243,10 @@ ________________________________________________________________ ________________________________________________________________ -#### Change Log: +#### Change Log: +* [2023.7.3] - Added `eukaryotic_gene_modeling_wrapper.py` which 1) splits nuclear, mitochondrial, and plastid genomes; 2) performs gene modeling via `MetaEuk` and `Pyrodigal`; 3) performs rRNA detection via `BARRNAP`; 4) performs tRNA detection via `tRNAscan-SE`; 5) merges processed GFF files; and 5) calculates sequences statistics. +* [2023.6.29] - Added `gene_biotype=protein_coding` to `prodigal` GFF output. +* [2023.6.20] - Added `VFDB` to `annotate.py` and database. * [2023.6.16] - Compiled and pushed `gtdb_r214.msh` mash file to [Zenodo:8048187](https://zenodo.org/record/8048187) which is now used by default in `classify-prokaryotic.py`. It is now included in `VDB_v5.1`. * [2023.6.15] - Cleaned up global and local clustering intermediate files. Added pangenome tables and singelton information to outputs. * [2023.6.12] - Changed `${VEBA_DATABASE}/Classify/GTDBTk` → `${VEBA_DATABASE}/Classify/GTDB`. diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index 5b923fea17128cc1bae3211deceb9954a305e185..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMU2GIp6h3EK=$$FZwDb=rOIHd8YYR(T`A4=tfJn8_?LR-u?9NC>rZZ({b{D8M zeIi0k)ENAG^_LeOOhjW$#24cOFA_kFi7)z~J{l8W(0K0LSwdU-(g%OS++^;#=bm%! z%=zX!b8~kA0K4+WCV&P2V03Y*R#JD5#_jx?*Q9W!oFq~_z$EzKWs2Tp#-8ULO+*Mp z2t)`(2t)`(2>cfipgo&6w!pqGtWh5!5Fv11BEY{7ak{unhH^&8;L|}}a0DR9j{rfV zF`WZ~n0P3Yp_~yi2&FZ}=>fw~438Kn!pR=z?j)0;oDoul1B!6K@Xi>XP%yYV#l-`6 zz@(5-A0ZGSFdqS4J{90W7II+SeSUW{WcfHVXtyTViXXCBA98B|f#SjYb{qo2RE`R@=03M}PXHJ>j_LMIIP_3t;)2ye`a7%JvG~ z8Jj0nR#i&s+fq`pQmG9NDI+6R)^$?WSu?G!@F=VOq#n$f1)QDV$4jeKS>4X& zZQVCJZJRrLBffds=c6&t&H3GdV_H@7p4o4bpH(n>$eTi|`AU70zU4aJkeTyHHYF3{ z;pNNAYGZ32el+pe_Q$*T9q7BU{DBp!Qmxbs7c9@p*ydo-GW_vT-OHMeVL2zp2sX#J zp0UiFT!k(2fTK^CH`KcNhE)%0+Fb9_DkJ%fbt-T9XVfOf#623dhh+65cEq5Ee9ATW zibi138ntzd9p>}hIg@%Zt*qCYWmRVb9;GanXjL}KswWSeq<1Sq+msHb8uFm6XG~j6 zZBe!{^?*EF5Sfd~YGoIbN3!n31SR8%xun*`)UVhve&b%<^ZO=Er}RikZ7*YE-ngDK z`-;S@QpA&t1uu=}dB`5a^4(m3MsK60jq;^B4Z}1j>4v&`RXe1mn0lRJRLdz?#ASLH zg3_!h*(_d3fT&zEhg^K8S_NyM19rke7zG20@FHA<*Wg{a3|HVY_!7Q`>u?ip!T0bJ z{0hIrZCrvC$WX>=T#2<kBTUfhShIEX_yf}?m0PvAH{izo3Ep2aDg z#^>=IzJxF1EBF?^jql)l_ &+#gLf!FXR-okJ27yJ!x*%Tm8jB$x?y8XQOl@~", - os.path.join(output_directory, "mapped.sorted.bam"), - ")", - - "&&", - - # Get mapped reads - "(", - os.environ["samtools"], - "view", - os.path.join(output_directory, "mapped.sorted.bam"), - "|", - "cut -f1", - ">", - os.path.join(output_directory, "reads.mapped.list"), - ")", - - # Remove temporary files - "&&", - "rm -rf {}".format(os.path.join(directories["tmp"], "*")), - ] - - # Remove intermediate SPAdes files - for fn in [ - "before_rr.fasta", - "K*", - "misc", - "corrected", - "first_pe_contigs.fasta", - ]: - cmd += [ - "&&", - "rm -rf {}".format(os.path.join(output_directory, fn)), - ] - return cmd - - -# metaplasmidspades -def get_metaplasmidspades_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): - - # Command - cmd = [ - - # Get unmapped reads and repair them - "(", - os.environ["filterbyname.sh"], - "in1={}".format(input_filepaths[0]), - "in2={}".format(input_filepaths[1]), - "names={}".format(input_filepaths[2]), - "out=stdout.fastq", - "|", - os.environ["repair.sh"], - "overwrite=t", - "in=stdin.fastq", - "out1={}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), - "out2={}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), - ")", - - "&&", - - # SPAdes - "(", - os.environ["spades.py"], - ] - - if "restart-from" not in str(opts.spades_options): - cmd += [ - "-1 {}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), - "-2 {}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), - ] - cmd += [ - "--metaplasmid", - "-o {}".format(output_directory), - "--tmp-dir {}".format(os.path.join(directories["tmp"])), - "--threads {}".format(opts.n_jobs), - "--memory {}".format(opts.memory), - opts.spades_options, - ")", - - - # Clear temporary directory just in case - "&&", - "rm -rf {}".format(os.path.join(directories["tmp"], "*")), - "&&", - - # Bowtie2 Index - "(", - os.environ["bowtie2-build"], - "--threads {}".format(opts.n_jobs), - "--seed {}".format(opts.random_state), - opts.bowtie2_index_options, - os.path.join(output_directory, "scaffolds.fasta"), # Reference - os.path.join(output_directory, "scaffolds.fasta"), # Index - ")", - - "&&", - - # Bowtie2 - "(", - os.environ["bowtie2"], - "-x {}".format(os.path.join(output_directory, "scaffolds.fasta")), - "-1 {}".format(input_filepaths[0]), - "-2 {}".format(input_filepaths[1]), - "--threads {}".format(opts.n_jobs), - "--seed {}".format(opts.random_state), - "--no-unal", - opts.bowtie2_options, - ")", - - # Convert to sorted BAM - "|", - - "(", - os.environ["samtools"], - "sort", - "--threads {}".format(opts.n_jobs), - "--reference {}".format(os.path.join(output_directory, "scaffolds.fasta")), - "-T {}".format(os.path.join(directories["tmp"], "samtools_sort")), - ">", - os.path.join(output_directory, "mapped.sorted.bam"), - ")", - - "&&", - - # Get mapped reads - "(", - os.environ["samtools"], - "view", - os.path.join(output_directory, "mapped.sorted.bam"), - "|", - "cut -f1", - ">", - os.path.join(output_directory, "reads.mapped.list"), - ")", - - # Aggregated reads - "&&", - "cat", - input_filepaths[2], - os.path.join(output_directory, "reads.mapped.list"), - ">", - os.path.join(output_directory, "reads.mapped.aggregated.list"), - - - - # Remove temporary files - "&&", - "rm -rf {}".format(os.path.join(directories["tmp"], "*")), - ] - - # Remove intermediate SPAdes files - for fn in [ - "before_rr.fasta", - "K*", - "misc", - "corrected", - "first_pe_contigs.fasta", - ]: - cmd += [ - "&&", - "rm -rf {}".format(os.path.join(output_directory, fn)), - ] - return cmd - -# metaspades -def get_metaspades_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): - - # Command - cmd = [ - - # Get unmapped reads and repair them - "(", - os.environ["filterbyname.sh"], - "in1={}".format(input_filepaths[0]), - "in2={}".format(input_filepaths[1]), - "names={}".format(input_filepaths[2]), - "out=stdout.fastq", - "|", - os.environ["repair.sh"], - "overwrite=t", - "in=stdin.fastq", - "out1={}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), - "out2={}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), - ")", - - "&&", - - # SPAdes - "(", - os.environ["spades.py"], - ] - if "restart-from" not in str(opts.spades_options): - cmd += [ - "-1 {}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), - "-2 {}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), - ] - cmd += [ - "--meta", - "-o {}".format(output_directory), - "--tmp-dir {}".format(os.path.join(directories["tmp"])), - "--threads {}".format(opts.n_jobs), - "--memory {}".format(opts.memory), - opts.spades_options, - ")", - - - # Clear temporary directory just in case - "&&", - - "rm -rf {}".format(os.path.join(directories["tmp"], "*")), - - "&&", - - # Bowtie2 Index - "(", - os.environ["bowtie2-build"], - "--threads {}".format(opts.n_jobs), - "--seed {}".format(opts.random_state), - opts.bowtie2_index_options, - os.path.join(output_directory, "scaffolds.fasta"), # Reference - os.path.join(output_directory, "scaffolds.fasta"), # Index - ")", - - "&&", - - # Bowtie2 - "(", - os.environ["bowtie2"], - "-x {}".format(os.path.join(output_directory, "scaffolds.fasta")), - "-1 {}".format(input_filepaths[0]), - "-2 {}".format(input_filepaths[1]), - "--threads {}".format(opts.n_jobs), - "--seed {}".format(opts.random_state), - "--no-unal", - opts.bowtie2_options, - ")", - - # Convert to sorted BAM - "|", - - "(", - os.environ["samtools"], - "sort", - "--threads {}".format(opts.n_jobs), - "--reference {}".format(os.path.join(output_directory, "scaffolds.fasta")), - "-T {}".format(os.path.join(directories["tmp"], "samtools_sort")), - ">", - os.path.join(output_directory, "mapped.sorted.bam"), - ")", - - # Remove temporary files - "&&", - "rm -rf {}".format(os.path.join(directories["tmp"], "*")), - ] - - # Remove intermediate SPAdes files - for fn in [ - "before_rr.fasta", - "K*", - "misc", - "corrected", - "first_pe_contigs.fasta", - ]: - cmd += [ - "&&", - "rm -rf {}".format(os.path.join(output_directory, fn)), - ] - - return cmd - -# Concatenate assemblies and BAM files -def get_concatenate_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): - # Command - cmd = [ - # Add assembler to descriptions - "(", - os.environ["replace_fasta_descriptions.py"], - "-f {}".format(os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta")), - "-d biosyntheticSPAdes", - "-o {}".format(os.path.join(directories["tmp"], "scaffold.biosyntheticspades.fasta")), - - ] - if opts.run_metaplasmidspades: - cmd += [ - "&&", - os.environ["replace_fasta_descriptions.py"], - "-f {}".format(os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta")), - "-d metaplasmidSPAdes", - "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaplasmidspades.fasta")), - - "&&", - - os.environ["replace_fasta_descriptions.py"], - "-f {}".format(os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta")), - "-d metaSPAdes", - "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaspades.fasta")), - ] - else: - cmd += [ - "&&", - os.environ["replace_fasta_descriptions.py"], - "-f {}".format(os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta")), - "-d metaSPAdes", - "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaspades.fasta")), - - ] - - cmd += [")"] - - - # Concatenate scaffolds - cmd += [ - "&&", - "cat {} > {}".format(os.path.join(directories["tmp"], "scaffold.*.fasta"), os.path.join(output_directory, "scaffolds.fasta")), - "&&", - ] - - # Concatenate mapped.sorted.bam - if opts.run_metaplasmidspades: - cmd += [ - "(", - os.environ["samtools"], - "merge", - "-@ {}".format(opts.n_jobs), - "-o {}".format(os.path.join(output_directory, "mapped.sorted.bam")), - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), - ")", - ] - else: - cmd += [ - "(", - os.environ["samtools"], - "merge", - "-@ {}".format(opts.n_jobs), - "-o {}".format(os.path.join(output_directory, "mapped.sorted.bam")), - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), - ")", - ] - - - # Bowtie2 Index - cmd += [ - "&&", - "(", - os.environ["bowtie2-build"], - "--threads {}".format(opts.n_jobs), - "--seed {}".format(opts.random_state), - opts.bowtie2_index_options, - os.path.join(output_directory, "scaffolds.fasta"), # Reference - os.path.join(output_directory, "scaffolds.fasta"), # Index - ")", - - # Samtools Index - "&&", - "(", - os.environ["samtools"], - "index", - "-@ {}".format(opts.n_jobs), - os.path.join(output_directory, "mapped.sorted.bam"), - ")", - - # Create SAF file - "&&", - "(", - os.environ["fasta_to_saf.py"], - "-i", - os.path.join(output_directory, "scaffolds.fasta"), - ">", - os.path.join(output_directory, "scaffolds.fasta.saf"), - ")", - - # Remove temporary files - "&&", - "rm -rf {}".format(os.path.join(directories["tmp"],"*")), - ] - return cmd - - -# featureCounts -def get_featurecounts_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): - - # Command - - # ORF-Level Counts - cmd = [ - "mkdir -p {}".format(os.path.join(directories["tmp"], "featurecounts")), - "&&", - "(", - os.environ["featureCounts"], - # "-G {}".format(input_filepaths[0]), - "-a {}".format(input_filepaths[1]), - "-o {}".format(os.path.join(output_directory, "featurecounts.tsv")), - "-F SAF", - "--tmpDir {}".format(os.path.join(directories["tmp"], "featurecounts")), - "-T {}".format(opts.n_jobs), - "-p --countReadPairs", - opts.featurecounts_options, - input_filepaths[2], - ")", - "&&", - "gzip -f {}".format(os.path.join(output_directory, "featurecounts.tsv")), - ] - return cmd - -# seqkit -def get_seqkit_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): - - # Command - - # ORF-Level Counts - cmd = [ - - os.environ["seqkit"], - "stats", - "-a", - "-j {}".format(opts.n_jobs), - "-T", - # "-b", - " ".join(input_filepaths), - "|", - "gzip", - ">", - output_filepaths[0], - ] - return cmd - -# Output -def get_output_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): - # Command - - # Symlinks - cmd = [ - "DST={}; (for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( - output_directory, - " ".join(input_filepaths), - ) - ] - - # Cleanup intermediate files - if opts.run_metaplasmidspades: - if opts.remove_intermediate_scaffolds: - cmd += [ - "&&", - "rm -rf {} {} {} {} {} {}".format( - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "*.fasta"), - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta.*.bt2"), - os.path.join(directories[("intermediate", "2__metaplasmidspades")], "*.fasta"), - os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta.*.bt2"), - os.path.join(directories[("intermediate", "3__metaspades")], "*.fasta"), - os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta.*.bt2"), - ) - ] - if opts.remove_intermediate_bam: - cmd += [ - "&&", - "rm -rf {} {} {}".format( - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), - ) - ] - else: - if opts.remove_intermediate_scaffolds: - cmd += [ - "&&", - "rm -rf {} {} {} {}".format( - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "*.fasta"), - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta.*.bt2"), - os.path.join(directories[("intermediate", "2__metaspades")], "*.fasta"), - os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta.*.bt2"), - ) - ] - if opts.remove_intermediate_bam: - cmd += [ - "&&", - "rm -rf {} {}".format( - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), - ) - ] - - return cmd - -# def get_output_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): -# # Command - - # cmd += [ - # "&&", - # os.environ["fasta_to_saf.py"], - # "-i", - # os.path.join(output_directory, "scaffolds.fasta"), - # ">", - # os.path.join(output_directory, "scaffolds.fasta.saf"), - # ] - - # "&&", - # "(", - # os.environ["samtools"], - # "index", - # "-@ {}".format(opts.n_jobs), - # output_filepaths[0], - # ")", - - - -# ============ -# Run Pipeline -# ============ -# Set environment variables -def add_executables_to_environment(opts): - """ - Adapted from Soothsayer: https://github.com/jolespin/soothsayer - """ - accessory_scripts = { - "fasta_to_saf.py", - "replace_fasta_descriptions.py", - } - - required_executables={ - "filterbyname.sh", - "bowtie2-build", - "bowtie2", - "samtools", - "repair.sh", - "spades.py", - "featureCounts", - "seqkit", - } | accessory_scripts - - if opts.path_config == "CONDA_PREFIX": - executables = dict() - for name in required_executables: - executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) - else: - if opts.path_config is None: - opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") - opts.path_config = format_path(opts.path_config) - assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) - assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) - df_config = pd.read_csv(opts.path_config, sep="\t") - assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) - df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) - # Get executable paths - executables = OrderedDict(zip(df_config["name"], df_config["executable"])) - assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) - - # Display - for name in sorted(accessory_scripts): - executables[name] = "'{}'".format(os.path.join(opts.script_directory, "scripts", name)) # Can handle spaces in path - - print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) - for name, executable in executables.items(): - if name in required_executables: - print(name, executable, sep = " --> ", file=sys.stdout) - os.environ[name] = executable.strip() - print("", file=sys.stdout) - -# Pipeline -def create_pipeline(opts, directories, f_cmds): - - # ................................................................. - # Primordial - # ................................................................. - # Commands file - pipeline = ExecutablePipeline(name=__program__, description=opts.name, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) - - # ========== - # biosyntheticspades - # ========== - - step = 1 - - # Info - program = "biosyntheticspades" - program_label = "{}__{}".format(step, program) - description = "Assembling paired-end reads using biosyntheticSPAdes" - - # Add to directories - output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) - - - # i/o - input_filepaths = [opts.forward_reads, opts.reverse_reads] - output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "reads.mapped.list"] - output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_biosyntheticspades_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), - validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), - log_prefix=program_label, - - ) - - # ========== - # metaplasmidspades - # ========== - if opts.run_metaplasmidspades: - - step += 1 - - # Info - program = "metaplasmidspades" - program_label = "{}__{}".format(step, program) - description = "Assembling paired-end reads using metaplasmidSPAdes" - - # Add to directories - output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) - - - # i/o - input_filepaths = [opts.forward_reads, opts.reverse_reads, output_filepaths[-1]] # output_filepaths[2] is reads.mapped.list - - output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "reads.mapped.aggregated.list"] - output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_metaplasmidspades_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=True, - validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), - log_prefix=program_label, - - ) - - # ========== - # metaspades - # ========== - - step += 1 - - # Info - program = "metaspades" - program_label = "{}__{}".format(step, program) - description = "Assembling paired-end reads using metaSPAdes" - - # Add to directories - output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) - - - # i/o - input_filepaths = [opts.forward_reads, opts.reverse_reads, output_filepaths[-1]] # output_filepaths[2] is reads.mapped.aggregated.list - output_filenames = ["scaffolds.fasta", "mapped.sorted.bam"] - output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_metaspades_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=True, - validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), - log_prefix=program_label, - - ) - - # ========== - # concatenate - # ========== - - step += 1 - - # Info - program = "concatenate" - program_label = "{}__{}".format(step, program) - description = "Concatenate assemblies and BAM files" - - # Add to directories - output_directory = directories["output"] - - # i/o - if opts.run_metaplasmidspades: - input_filepaths = [ - # scaffolds.fasta - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), - os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta"), - os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta"), - # mapped.sorted.bam - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), - ] - else: - input_filepaths = [ - # scaffolds.fasta - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), - os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta"), - # mapped.sorted.bam - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), - os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), - ] - - output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "mapped.sorted.bam.bai", "scaffolds.fasta.saf", "scaffolds.fasta.*.bt2"] - output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_concatenate_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), # Adjust to allow for assemblies that don't find BGCs or plasmids? - validate_outputs=True, - log_prefix=program_label, - - ) - - - # ========== - # featureCounts - # ========== - step += 1 - - # Info - program = "featurecounts" - program_label = "{}__{}".format(step, program) - description = "Counting reads" - - # Add to directories - output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) - - # i/o - - input_filepaths = [ - os.path.join(directories["output"], "scaffolds.fasta"), - os.path.join(directories["output"], "scaffolds.fasta.saf"), - os.path.join(directories["output"], "mapped.sorted.bam"), - ] - - output_filenames = ["featurecounts.tsv.gz"] - output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_featurecounts_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=True, - validate_outputs=True, - log_prefix=program_label, - ) - - # ========== - # stats - # ========== - step += 1 - - # Info - program = "seqkit" - program_label = "{}__{}".format(step, program) - description = "Assembly statistics" - - # Add to directories - output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) - - # i/o - if opts.run_metaplasmidspades: - input_filepaths = [ - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), - os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta"), - os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta"), - os.path.join(directories["output"], "scaffolds.fasta"), - ] - else: - input_filepaths = [ - os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), - os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta"), - os.path.join(directories["output"], "scaffolds.fasta"), - ] - - output_filenames = ["seqkit_stats.tsv.gz"] - output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_seqkit_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), - validate_outputs=True, - log_prefix=program_label, - - ) - - - # ============= - # Output - # ============= - step += 1 - - # Info - program = "output" - program_label = "{}__{}".format(step, program) - description = "Symlinking relevant output files and removing intermediate" - - # Add to directories - output_directory = directories["output"] - - # i/o - - input_filepaths = [ - os.path.join(directories[("intermediate", "{}__featurecounts".format(step-2))], "featurecounts.tsv.gz"), - os.path.join(directories[("intermediate", "{}__seqkit".format(step-1))], "seqkit_stats.tsv.gz"), - ] - - output_filenames = map(lambda fp: fp.split("/")[-1], input_filepaths) - output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_output_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=True, - validate_outputs=True, - log_prefix=program_label, - - ) - - return pipeline - -# Configure parameters -def configure_parameters(opts, directories): - # os.environ[] - - assert opts.forward_reads != opts.reverse_reads, "You probably mislabeled the input files because `forward_reads` should not be the same as `reverse_reads`: {}".format(opts.forward_reads) - # assert not bool(opts.unpaired_reads), "Cannot have --unpaired_reads if --forward_reads. Note, this behavior may be changed in the future but it's an adaptation of interleaved reads." - - # Set environment variables - add_executables_to_environment(opts=opts) - -def main(args=None): - # Path info - script_directory = os.path.dirname(os.path.abspath( __file__ )) - script_filename = __program__ - # Path info - description = """ - Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -1 -2 -n -o ".format(__program__) - epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" - - # Parser - parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) - # Pipeline - parser_io = parser.add_argument_group('Required I/O arguments') - parser_io.add_argument("-1","--forward_reads", type=str, help = "path/to/forward_reads.fq") - parser_io.add_argument("-2","--reverse_reads", type=str, help = "path/to/reverse_reads.fq") - parser_io.add_argument("-n", "--name", type=str, help="Name of sample", required=True) - parser_io.add_argument("-o","--project_directory", type=str, default="veba_output/assembly_sequential", help = "path/to/project_directory [Default: veba_output/assembly_sequential]") - - # Utility - parser_utility = parser.add_argument_group('Utility arguments') - parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future - parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") - parser_utility.add_argument("--random_state", type=int, default=0, help = "Random state [Default: 0]") - parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") - parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) - parser_utility.add_argument("--tmpdir", type=str, help="Set temporary directory") #site-packges in future - parser_utility.add_argument("-S", "--remove_intermediate_scaffolds", action="store_true", help="Remove intermediate scaffolds.fasta.*. If this option is chosen, output files are not validated [Default is to keep]") - parser_utility.add_argument("-B", "--remove_intermediate_bam", action="store_true", help="Remove intermediate mapped.sorted.bam.*. If this option is chosen, output files are not validated [Default is to keep]") - - # Assembler - parser_assembler = parser.add_argument_group('SPAdes arguments') - parser_assembler.add_argument("--run_metaplasmidspades", action="store_true", help="SPAdes | Run metaplasmidSPAdes. This may sacrifice MAG completeness for plasmid completeness. Will fail if there are no extrachromosomal contigs assembled.") - # parser_assembler.add_argument("--run_metaviralspades", action="store_true", help="SPAdes | Run metaviralSPAdes. This will result in a separete set of scaffolds.") - parser_assembler.add_argument("-m", "--memory", type=int, default=250, help="SPAdes | RAM limit in Gb (terminates if exceeded). [Default: 250]") - parser_assembler.add_argument("--spades_options", type=str, default="", help="SPAdes | More options (e.g. --arg 1 ) [Default: '']\nhttp://cab.spbu.ru/files/release3.11.1/manual.html") - - # Aligner - parser_aligner = parser.add_argument_group('Bowtie2 arguments') - parser_aligner.add_argument("--bowtie2_index_options", type=str, default="", help="bowtie2-build | More options (e.g. --arg 1 ) [Default: '']") - parser_aligner.add_argument("--bowtie2_options", type=str, default="", help="bowtie2 | More options (e.g. --arg 1 ) [Default: '']") - - # featureCounts - parser_featurecounts = parser.add_argument_group('featureCounts arguments') - parser_featurecounts.add_argument("--featurecounts_options", type=str, default="", help="featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/") - - - # Options - opts = parser.parse_args() - opts.script_directory = script_directory - opts.script_filename = script_filename - - # Threads - if opts.n_jobs == -1: - from multiprocessing import cpu_count - opts.n_jobs = cpu_count() - assert opts.n_jobs >= 1, "--n_jobs must be ≥ 1. To select all available threads, use -1." - - # Directories - directories = dict() - directories["project"] = create_directory(opts.project_directory) - directories["sample"] = create_directory(os.path.join(directories["project"], opts.name)) - directories["output"] = create_directory(os.path.join(directories["sample"], "output")) - directories["log"] = create_directory(os.path.join(directories["sample"], "log")) - if not opts.tmpdir: - opts.tmpdir = os.path.join(directories["sample"], "tmp") - directories["tmp"] = create_directory(opts.tmpdir) - directories["checkpoints"] = create_directory(os.path.join(directories["sample"], "checkpoints")) - directories["intermediate"] = create_directory(os.path.join(directories["sample"], "intermediate")) - os.environ["TMPDIR"] = directories["tmp"] - - # Info - print(format_header(__program__, "="), file=sys.stdout) - print(format_header("Configuration:", "-"), file=sys.stdout) - print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) - print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) - print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] - print("Script version:", __version__, file=sys.stdout) - print("Moment:", get_timestamp(), file=sys.stdout) - print("Directory:", os.getcwd(), file=sys.stdout) - print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) - configure_parameters(opts, directories) - sys.stdout.flush() - - # Run pipeline - with open(os.path.join(directories["sample"], "commands.sh"), "w") as f_cmds: - pipeline = create_pipeline( - opts=opts, - directories=directories, - f_cmds=f_cmds, - ) - pipeline.compile() - pipeline.execute(restart_from_checkpoint=opts.restart_from_checkpoint) - -if __name__ == "__main__": - main() diff --git a/src/binning-eukaryotic.py b/src/binning-eukaryotic.py index c35c118..74476b4 100755 --- a/src/binning-eukaryotic.py +++ b/src/binning-eukaryotic.py @@ -13,13 +13,12 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.6.20" # DATABASE_METAEUK="/usr/local/scratch/CORE/jespinoz/db/veba/v1.0/Classify/Eukaryotic/eukaryotic" def get_preprocess_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): - # checkv end_to_end ${FASTA} ${OUT_DIR} -t ${N_JOBS} --restart cmd = [ "(", @@ -388,11 +387,63 @@ def get_busco_cmd(input_filepaths, output_filepaths, output_directory, directori "--contamination {}".format(opts.busco_contamination), "--unbinned", ] + + return cmd +# barrnap +def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ - + +""" + + for GENOME_FASTA in {}; + do + ID = $(basename $GENOME_FASTA .fa) + {} --kingdom euk --threads {} --lencutoff {} --reject {} --evalue {} --outseq {} $GENOME_FASTA > {} + rm $GENOME_FASTA.fai + done + +""".format( + os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes", "*.fa"), + os.environ["barrnap"], + opts.n_jobs, + opts.barrnap_length_cutoff, + opts.barrnap_reject, + opts.barrnap_evalue, + os.path.join(output_directory, "$ID.rRNA.fasta"), + os.path.join(output_directory, "$ID.rRNA.gff"), + ), + ] return cmd +# tRNAscan-SE +def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + +""" + + for GENOME_FASTA in {}; + do + ID = $(basename $GENOME_FASTA .fa) + {} -E --forceow --progress --threads {} --fasta {} --gff {} --struct {} {} $GENOME_FASTA > {} + + done + +""".format( + os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes", "*.fa"), + os.environ["tRNAscan-SE"], + opts.n_jobs, + os.path.join(output_directory, "$ID.tRNA.fasta"), + os.path.join(output_directory, "$ID.tRNA.gff"), + os.path.join(output_directory, "$ID.tRNA.struct"), + opts.trnascan_options, + os.path.join(output_directory, "$ID.tRNA.txt"), + ), + ] + return cmd + + def get_featurecounts_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): # ORF-Level Counts cmd = [ @@ -435,19 +486,17 @@ def get_output_cmd(input_filepaths, output_filepaths, output_directory, director cmd += [ "DST={}; (for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( output_directory, - " ".join(input_filepaths), + " ".join(input_filepaths[:-3]), ) ] - # for fp in input_filepaths: - # fn = fp.split("/")[-1] - # cmd += [ - # "&&", - # "ln -sf", - # os.path.join(fp), - # os.path.join(output_directory,fn), - # ] - + cmd += [ + "DST={}; (for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes"), + " ".join(input_filepaths[-3:]), + ) + ] + cmd += [ "&&", @@ -468,6 +517,7 @@ def get_output_cmd(input_filepaths, output_filepaths, output_directory, director os.path.join(output_directory,"genome_statistics.tsv"), ")", ] + @@ -715,10 +765,114 @@ def create_pipeline(opts, directories, f_cmds): steps[program] = step # ========== - # featureCounts + # barrnap # ========== step = 4 + program = "barrnap" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # Info + description = "Detecting rRNA genes" + # i/o + input_filepaths = [ + os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes", "*.fa"), + ] + + output_filenames = [ + "*.rRNA.fasta", + "*.rRNA.gff", + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_barrnap_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + acceptable_returncodes={0}, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + + steps[program] = step + + # ========== + # tRNASCAN-se + # ========== + step = 5 + + program = "trnascan-se" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # Info + description = "Detecting tRNA genes" + # i/o + input_filepaths = [ + os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes", "*.fa"), + ] + + output_filenames = [ + "*.tRNA.fasta", + "*.tRNA.gff", + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_trnascan_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + acceptable_returncodes={0}, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + + steps[program] = step + + # ========== + # featureCounts + # ========== + step = 6 + # Info program = "featurecounts" program_label = "{}__{}".format(step, program) @@ -764,7 +918,7 @@ def create_pipeline(opts, directories, f_cmds): # ============= # Output # ============= - step = 5 + step = 7 program = "output" program_label = "{}__{}".format(step, program) @@ -787,7 +941,15 @@ def create_pipeline(opts, directories, f_cmds): os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes"), # featureCounts - os.path.join(directories[("intermediate", "4__featurecounts")], "featurecounts.orfs.tsv.gz"), + os.path.join(directories[("intermediate", "6__featurecounts")], "featurecounts.orfs.tsv.gz"), + + # barrnap + os.path.join(directories[("intermediate", "4__barrnap")], "*.rRNA.*"), + + # tRNAscan-SE + os.path.join(directories[("intermediate", "5__trnascan-se")], "*.tRNA.fasta"), + os.path.join(directories[("intermediate", "5__trnascan-se")], "*.tRNA.gff"), + ] output_filenames = [ @@ -858,6 +1020,8 @@ def add_executables_to_environment(opts): "busco", "featureCounts", "tiara", + "barrnap", + "tRNAscan-SE", } | accessory_scripts @@ -933,6 +1097,8 @@ def main(args=None): # Pipeline parser_io = parser.add_argument_group('I/O arguments') + parser_io.add_argument("-t","--tiara_results", type=str, required=True, help = "path/to/scaffolds.fasta") + parser_io.add_argument("-f","--fasta", type=str, required=True, help = "path/to/scaffolds.fasta") parser_io.add_argument("-b","--bam", type=str, nargs="+", required=True, help = "path/to/mapped.sorted.bam files separated by spaces. ") parser_io.add_argument("-n", "--name", type=str, help="Name of sample", required=True) @@ -980,6 +1146,14 @@ def main(args=None): parser_metaeuk.add_argument("--metaeuk_options", type=str, default="", help="MetaEuk | More options (e.g. --arg 1 ) [Default: ''] https://github.com/soedinglab/metaeuk") # --split-memory-limit 70G: https://github.com/soedinglab/metaeuk/issues/59 + # Pyrodigal + parser_pyrodigal = parser.add_argument_group('Pyrodigal arguments (Mitochondria)') + parser_pyrodigal.add_argument("--pyrodigal_minimum_gene_length", type=int, default=90, help="Pyrodigal | Minimum gene length [Default: 90]") + parser_pyrodigal.add_argument("--pyrodigal_minimum_edge_gene_length", type=int, default=60, help="Pyrodigal | Minimum edge gene length [Default: 60]") + parser_pyrodigal.add_argument("--pyrodigal_maximum_gene_overlap_length", type=int, default=60, help="Pyrodigal | Maximum gene overlap length [Default: 60]") + parser_pyrodigal.add_argument("--pyrodigal_mitochondrial_genetic_code", type=int, default=3, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 3] (The Yeast Mitochondrial Code))") + parser_pyrodigal.add_argument("--pyrodigal_plastid_genetic_code", type=int, default=3, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 3] (The Yeast Mitochondrial Code))") + # BUSCO parser_busco = parser.add_argument_group('BUSCO arguments') # parser_busco.add_argument("--busco_offline", type=str, help="BUSCO | Offline database path") @@ -987,6 +1161,20 @@ def main(args=None): parser_busco.add_argument("--busco_contamination", type=float, default=10.0, help = "BUSCO contamination [Default: 10.0]") parser_busco.add_argument("--busco_evalue", type=float, default=0.001, help="BUSCO | E-value cutoff for BLAST searches. Allowed formats, 0.001 or 1e-03 [Default: 1e-03]") + # rRNA + parser_barrnap = parser.add_argument_group('barrnap arguments') + parser_barrnap.add_argument("--barrnap_length_cutoff", type=float, default=0.8, help="barrnap | Proportional length threshold to label as partial [Default: 0.8]") + parser_barrnap.add_argument("--barrnap_reject", type=float, default=0.25, help="barrnap | Proportional length threshold to reject prediction [Default: 0.25]") + parser_barrnap.add_argument("--barrnap_evalue", type=float, default=1e-6, help="barrnap | Similarity e-value cut-off [Default: 1e-6]") + + # tRNA + parser_trnascan = parser.add_argument_group('tRNAscan-SE arguments') + parser_trnascan.add_argument("--trnascan_nuclear_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_mitochondrial_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_mitochondrial_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_plastid_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_plastid_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + # featureCounts parser_featurecounts = parser.add_argument_group('featureCounts arguments') parser_featurecounts.add_argument("--long_reads", action="store_true", help="featureCounts | Use this if long reads are being used") diff --git a/src/binning-prokaryotic.py b/src/binning-prokaryotic.py index 51ca05a..560ed3a 100755 --- a/src/binning-prokaryotic.py +++ b/src/binning-prokaryotic.py @@ -12,7 +12,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.12" +__version__ = "2023.6.20" # Assembly def get_coverage_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -402,6 +402,90 @@ def get_checkm2_cmd(input_filepaths, output_filepaths, output_directory, directo ] return cmd +# barrnap +def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + "cat", + input_filepaths[0], + ">", + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + +""" + +FP={} +for DOMAIN in $(cut -f2 $FP | sort -u); +do + DOMAIN_ABBREVIATION=$(echo $DOMAIN | python -c 'import sys; print(sys.stdin.read().lower()[:3])') + + # Get MAGs for each domain (not all will have passed QC) + for ID in $(cat $FP | grep $DOMAIN | cut -f1) + do + GENOME_FASTA=$(ls {}) || GENOME_FASTA="" + if [ -e "$GENOME_FASTA" ]; then + {} --kingdom $DOMAIN_ABBREVIATION --threads {} --lencutoff {} --reject {} --evalue {} --outseq {} $GENOME_FASTA > {} + rm $GENOME_FASTA.fai + fi + done +done + +rm -f {} +""".format( + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + os.path.join(os.path.split(input_filepaths[1])[0],"$ID.fa"), + os.environ["barrnap"], + opts.n_jobs, + opts.barrnap_length_cutoff, + opts.barrnap_reject, + opts.barrnap_evalue, + os.path.join(output_directory, "$ID.rRNA.fasta"), + os.path.join(output_directory, "$ID.rRNA.gff"), + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + ), + ] + return cmd + +# tRNAscan-SE +def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + "cat", + input_filepaths[0], + ">", + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + + +""" + +FP={} +for DOMAIN in $(cut -f2 $FP | sort -u); +do + DOMAIN_ABBREVIATION=$(echo $DOMAIN | python -c 'import sys; print(sys.stdin.read().upper()[:1])') + + # Get MAGs for each domain (not all will have passed QC) + for ID in $(cat $FP | grep $DOMAIN | cut -f1) + do + GENOME_FASTA=$(ls {}) || GENOME_FASTA="" + if [ -e "$GENOME_FASTA" ]; then + {} -$DOMAIN_ABBREVIATION --forceow --progress --threads {} --fasta {} --gff {} --struct {} {} $GENOME_FASTA > {} + fi + done +done + +rm -f {} +""".format( + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + os.path.join(os.path.split(input_filepaths[1])[0],"$ID.fa"), + os.environ["tRNAscan-SE"], + opts.n_jobs, + os.path.join(output_directory, "$ID.tRNA.fasta"), + os.path.join(output_directory, "$ID.tRNA.gff"), + os.path.join(output_directory, "$ID.tRNA.struct"), + opts.trnascan_options, + os.path.join(output_directory, "$ID.tRNA.txt"), + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + ) + ] + return cmd + def get_featurecounts_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): # ORF-Level Counts @@ -490,7 +574,7 @@ def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, dir "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( input_filepaths[5], output_directory, - ) + ), ] # SeqKit @@ -536,6 +620,34 @@ def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, dir ] + # tRNA + cmd += [ + "&&", + "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( + input_filepaths[6], + os.path.join(output_directory,"genomes"), + ), + ] + + # tRNA + cmd += [ + "&&", + "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( + input_filepaths[7], + os.path.join(output_directory,"genomes"), + ), + ] + + # rRNA + cmd += [ + "&&", + "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( + input_filepaths[8], + os.path.join(output_directory,"genomes"), + ), + ] + + return cmd @@ -1011,6 +1123,112 @@ def create_pipeline(opts, directories, f_cmds): # Reset input_fasta input_fasta = os.path.join(directories["tmp"], "unbinned_{}.fasta".format(iteration)) + # ========== + # barrnap + # ========== + step += 1 + + program = "barrnap" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # Info + description = "Detecting rRNA genes" + # i/o + input_filepaths = [ + os.path.join(directories["intermediate"], "*__dastool", "consensus_domain_classification", "predictions.tsv"), + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "genomes", "*"), + ] + + output_filenames = [ + "*.rRNA.fasta", + "*.rRNA.gff", + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_barrnap_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + acceptable_returncodes={0}, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + + steps[program] = step + + # ========== + # tRNASCAN-se + # ========== + step += 1 + + program = "trnascan-se" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # Info + description = "Detecting tRNA genes" + # i/o + input_filepaths = [ + os.path.join(directories["intermediate"], "*__dastool", "consensus_domain_classification", "predictions.tsv"), + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "genomes", "*"), + ] + + output_filenames = [ + "*.tRNA.fasta", + "*.tRNA.gff", + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_trnascan_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + acceptable_returncodes={0}, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + + steps[program] = step + # ========== # featureCounts @@ -1082,6 +1300,9 @@ def create_pipeline(opts, directories, f_cmds): os.path.join(directories["intermediate"], "*__checkm2", "filtered", "checkm2_results.filtered.tsv"), os.path.join(directories["intermediate"], "*__checkm2", "filtered", "genomes", "*"), os.path.join(directories[("intermediate", "{}__featurecounts".format(step-1))], "featurecounts.orfs.tsv.gz"), + os.path.join(directories[("intermediate", "{}__trnascan-se".format(step-2))], "*.tRNA.fasta"), + os.path.join(directories[("intermediate", "{}__trnascan-se".format(step-2))], "*.tRNA.gff"), + os.path.join(directories[("intermediate", "{}__barrnap".format(step-3))], "*.rRNA.*"), ] output_filenames = [ @@ -1121,6 +1342,8 @@ def create_pipeline(opts, directories, f_cmds): ) + + return pipeline # Set environment variables @@ -1158,6 +1381,10 @@ def add_executables_to_environment(opts): "featureCounts", # 11 "tiara", + # 12 + "barrnap", + # 13 + "tRNAscan-SE", } | accessory_scripts @@ -1264,7 +1491,7 @@ def main(args=None): parser_genemodels.add_argument("--pyrodigal_minimum_gene_length", type=int, default=90, help="Pyrodigal | Minimum gene length [Default: 90]") parser_genemodels.add_argument("--pyrodigal_minimum_edge_gene_length", type=int, default=60, help="Pyrodigal | Minimum edge gene length [Default: 60]") parser_genemodels.add_argument("--pyrodigal_maximum_gene_overlap_length", type=int, default=60, help="Pyrodigal | Maximum gene overlap length [Default: 60]") - parser_genemodels.add_argument("--pyrodigal_genetic_code", type=str, default=11, help="Pyrodigal -g translation table [Default: 11]") + parser_genemodels.add_argument("--pyrodigal_genetic_code", type=int, default=11, help="Pyrodigal -g translation table [Default: 11]") parser_evaluation = parser.add_argument_group('Evaluation arguments') parser_evaluation.add_argument("--dastool_searchengine", type=str, default="diamond", help="DAS_Tool searchengine. [Default: diamond] | https://github.com/cmks/DAS_Tool") @@ -1274,6 +1501,16 @@ def main(args=None): parser_evaluation.add_argument("--checkm2_contamination", type=float, default=10.0, help="CheckM2 contamination threshold [Default: 10.0]") parser_evaluation.add_argument("--checkm2_options", type=str, default="", help="CheckM lineage_wf | More options (e.g. --arg 1 ) [Default: '']") + # rRNA + parser_barrnap = parser.add_argument_group('barrnap arguments') + parser_barrnap.add_argument("--barrnap_length_cutoff", type=float, default=0.8, help="barrnap | Proportional length threshold to label as partial [Default: 0.8]") + parser_barrnap.add_argument("--barrnap_reject", type=float, default=0.25, help="barrnap | Proportional length threshold to reject prediction [Default: 0.25]") + parser_barrnap.add_argument("--barrnap_evalue", type=float, default=1e-6, help="barrnap | Similarity e-value cut-off [Default: 1e-6]") + + # tRNA + parser_trnascan = parser.add_argument_group('tRNAscan-SE arguments') + parser_trnascan.add_argument("--trnascan_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + # featureCounts parser_featurecounts = parser.add_argument_group('featureCounts arguments') parser_featurecounts.add_argument("--long_reads", action="store_true", help="featureCounts | Use this if long reads are being used") @@ -1281,7 +1518,7 @@ def main(args=None): # Tiara parser_domain = parser.add_argument_group('Domain classification arguments') - parser_domain.add_argument("--logit_transform", type=str, default="softmax", help = " Transformation for consensus_domain_classification: {softmax, tss} [Default: softmax") + parser_domain.add_argument("--logit_transform", type=str, default="softmax", help = " Transformation for consensus_domain_classification: {softmax, tss} [Default: softmax]") parser_domain.add_argument("--tiara_minimum_length", type=int, default=3000, help="Tiara | Minimum contig length. Anything lower than 3000 is not recommended. [Default: 3000]") parser_domain.add_argument("--tiara_options", type=str, default="", help="Tiara | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/ibe-uw/tiara") diff --git a/src/scripts/.DS_Store b/src/scripts/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0", + os.path.join(output_directory, "genomes.list"), +""" +OUTPUT_DIRECTORY={} +INTERMEDIATE_DIRECTORY={} +cat $OUTPUT_DIRECTORY/*.fa | grep "^>" | cut -c2- | cut -f1 -d " " > $INTERMEDIATE_DIRECTORY/eukaryotic_contigs.list +cat $OUTPUT_DIRECTORY/mitochondrion/*.fa | grep "^>" | cut -c2- | cut -f1 -d " " > $INTERMEDIATE_DIRECTORY/mitochondria_contigs.list +cat $OUTPUT_DIRECTORY/plastid/*.fa | grep "^>" | cut -c2- | cut -f1 -d " " > $INTERMEDIATE_DIRECTORY/plastid_contigs.list +""".format( + directories["output"], + output_directory, +) + ] + return cmd + +def get_partition_organelle_sequences_multiple_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ +""" +OUTPUT_DIRECTORY={} +INTERMEDIATE_DIRECTORY={} +S2B={} + +# Partition sequences +for ID in $(cut -f2 $S2B | sort -u); +do + SCAFFOLD_LIST={} + grep $ID $S2B | cut -f1 > $SCAFFOLD_LIST + {} grep -f $SCAFFOLD_LIST {} | {} -f stdin -t {} -n $ID -o $OUTPUT_DIRECTORY -u {} --verbose +done + +# Store list of genomes +cat $S2B | cut -f2 | sort -u > $INTERMEDIATE_DIRECTORY/genomes.list + +# Store list of partitioned contigs +cat $OUTPUT_DIRECTORY/*.fa | grep "^>" | cut -c2- | cut -f1 -d " " > $INTERMEDIATE_DIRECTORY/eukaryotic_contigs.list +cat $OUTPUT_DIRECTORY/mitochondrion/*.fa | grep "^>" | cut -c2- | cut -f1 -d " " > $INTERMEDIATE_DIRECTORY/mitochondrion_contigs.list +cat $OUTPUT_DIRECTORY/plastid/*.fa | grep "^>" | cut -c2- | cut -f1 -d " " > $INTERMEDIATE_DIRECTORY/plastid_contigs.list + +rm -rf $SCAFFOLD_LIST +""".format( + directories["output"], + output_directory, + opts.scaffolds_to_bins, + os.path.join(directories["tmp"], "$ID.list"), + os.environ["seqkit"], + input_filepaths[0], + + os.environ["partition_organelle_sequences.py"], + input_filepaths[1], + opts.unknown_organelle_prediction, + # opts.mitochondrion_suffix, + # opts.plastid_suffix, + # opts.unknown_suffix, + +) + ] + return cmd + +# MetaEuk +def get_metaeuk_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + cmd = [ + # Get eukaryotic sequences + "cat", + opts.fasta, + "|", + os.environ["seqkit"], + "grep", + "-f {}".format(input_filepaths[0]), + ">", + os.path.join(directories["tmp"], "tmp.fasta"), + + + "&&", + + # Placeholder + "OUTPUT_DIRECTORY={}; for ID in $(cat {}); do >$OUTPUT_DIRECTORY/$ID.faa; >$OUTPUT_DIRECTORY/$ID.ffn; >$OUTPUT_DIRECTORY/$ID.gff; done".format(output_directory, input_filepaths[1]), + + "&&", + + # Run MetaEuk + os.environ["metaeuk"], + "easy-predict", + "--threads {}".format(opts.n_jobs), + "-s {}".format(opts.metaeuk_sensitivity), + "-e {}".format(opts.metaeuk_evalue), + opts.metaeuk_options, + os.path.join(directories["tmp"], "tmp.fasta"), + opts.metaeuk_database, # db + os.path.join(output_directory, "metaeuk"), # output prefix + os.path.join(directories["tmp"],"metaeuk"), + + # Convert MetaEuk identifiers + "&&", + + os.environ["compile_metaeuk_identifiers.py"], + "--cds {}".format(os.path.join(output_directory, "metaeuk.codon.fas")), + "--protein {}".format(os.path.join(output_directory, "metaeuk.fas")), + "-o {}".format(output_directory), + "-b {}".format(opts.basename), + ] + + # Remove temporary files + cmd += [ + + "&&", + + "rm -rf {} {} {}".format( + os.path.join(output_directory, "*.fas"), # output prefix + os.path.join(output_directory, "metaeuk.gff"), # output prefix + os.path.join(directories["tmp"],"metaeuk", "*"), + os.path.join(directories["tmp"], "tmp.fasta"), + ), + ] + + if opts.scaffolds_to_bins: + cmd += [ + # Partition the gene models and genomes + "&&", + + os.environ["partition_gene_models.py"], + "-i {}".format(opts.scaffolds_to_bins), + # "-f {}".format(opts.fasta), + "-g {}".format(os.path.join(output_directory, "{}.gff".format(opts.basename))), + "-d {}".format(os.path.join(output_directory, "{}.ffn".format(opts.basename))), + "-a {}".format(os.path.join(output_directory, "{}.faa".format(opts.basename))), + "-o {}".format(os.path.join(output_directory)), + "--use_mag_as_description", + + "&&", + + "rm -rf", + os.path.join(output_directory, "{}.*".format(opts.basename)), + + ] + return cmd + +# Pyrodigal +def get_pyrodigal_cmd(input_filepaths, output_filepaths, output_directory, directories, opts, genetic_code): + + cmd = [ + "cat", + opts.fasta, + "|", + os.environ["seqkit"], + "grep", + "-f {}".format(input_filepaths[0]), + ">", + os.path.join(directories["tmp"], "tmp.fasta"), + + "&&", + + # Placeholder + "OUTPUT_DIRECTORY={}; for ID in $(cat {}); do >$OUTPUT_DIRECTORY/$ID.faa; >$OUTPUT_DIRECTORY/$ID.ffn; >$OUTPUT_DIRECTORY/$ID.gff; done".format(output_directory, input_filepaths[1]), + + "&&", + + # Run analysis + os.environ["pyrodigal"], + "-p meta", + "-i {}".format(os.path.join(directories["tmp"], "tmp.fasta")), + "-g {}".format(genetic_code), + "-f gff", + "-d {}".format(os.path.join(output_directory, "{}.ffn".format(opts.basename))), + "-a {}".format(os.path.join(output_directory, "{}.faa".format(opts.basename))), + "--min-gene {}".format(opts.pyrodigal_minimum_gene_length), + "--min-edge-gene {}".format(opts.pyrodigal_minimum_edge_gene_length), + "--max-overlap {}".format(opts.pyrodigal_maximum_gene_overlap_length), + # "-j {}".format(opts.n_jobs), + ">", + os.path.join(directories["tmp"], "tmp.gff"), + + "&&", + + "cat", + os.path.join(directories["tmp"], "tmp.gff"), + "|", + os.environ["append_geneid_to_prodigal_gff.py"], + "-a gene_id", + ">", + os.path.join(output_directory, "{}.gff".format(opts.basename)), + + "&&", + + "rm", + os.path.join(directories["tmp"], "tmp.*"), + ] + + if opts.scaffolds_to_bins: + cmd += [ + # Partition the gene models and genomes + "&&", + + os.environ["partition_gene_models.py"], + "-i {}".format(opts.scaffolds_to_bins), + # "-f {}".format(opts.fasta), + "-g {}".format(os.path.join(output_directory, "{}.gff".format(opts.basename))), + "-d {}".format(os.path.join(output_directory, "{}.ffn".format(opts.basename))), + "-a {}".format(os.path.join(output_directory, "{}.faa".format(opts.basename))), + "-o {}".format(os.path.join(output_directory)), + "--use_mag_as_description", + + "&&", + + "rm -rf", + os.path.join(output_directory, "{}.*".format(opts.basename)), + + ] + + return cmd + +# barrnap +def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directories, opts, kingdom): + cmd = [ + +""" +# Placeholder +OUTPUT_DIRECTORY={} +for ID in $(cat {}) +do + >$OUTPUT_DIRECTORY/$ID.rRNA + >$OUTPUT_DIRECTORY/$ID.rRNA.gff +done + +# Run analysis +for GENOME_FASTA in {} +do + ID=$(basename $GENOME_FASTA .fa) + {} --kingdom {} --threads {} --lencutoff {} --reject {} --evalue {} --outseq $OUTPUT_DIRECTORY/$ID.rRNA $GENOME_FASTA | {} > $OUTPUT_DIRECTORY/$ID.rRNA.gff + rm -rf $GENOME_FASTA.fai +done +""".format( + output_directory, + input_filepaths[0], + + input_filepaths[1], + os.environ["barrnap"], + kingdom, + opts.n_jobs, + opts.barrnap_length_cutoff, + opts.barrnap_reject, + opts.barrnap_evalue, + os.environ["append_geneid_to_barrnap_gff.py"], + ), + ] + return cmd + +# tRNAscan-SE +def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, directories, opts, search_mode, trnascan_options): + cmd = [ + +""" + +# Placeholder +OUTPUT_DIRECTORY={} +for ID in $(cat {}) +do + >$OUTPUT_DIRECTORY/$ID.tRNA + >$OUTPUT_DIRECTORY/$ID.tRNA.gff + >$OUTPUT_DIRECTORY/$ID.tRNA.struct + >$OUTPUT_DIRECTORY/$ID.tRNA.txt + +done + +# Run analysis +for GENOME_FASTA in {} +do + ID=$(basename $GENOME_FASTA .fa) + {} {} --forceow --progress --thread {} --fasta $OUTPUT_DIRECTORY/$ID.tRNA --gff $OUTPUT_DIRECTORY/$ID.tRNA.gff --struct $OUTPUT_DIRECTORY/$ID.tRNA.struct {} $GENOME_FASTA > $OUTPUT_DIRECTORY/$ID.tRNA.txt +done +""".format( + output_directory, + input_filepaths[0], + + input_filepaths[1], + os.environ["tRNAscan-SE"], + search_mode, + opts.n_jobs, + trnascan_options, + ), + ] + return cmd + +def get_symlink_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Nuclear + cmd = [ + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "2__metaeuk")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "5__barrnap-nuclear")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "8__trnascan-nuclear")], + ), + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "2__metaeuk")], + directories[("intermediate", "5__barrnap-nuclear")], + directories[("intermediate", "8__trnascan-nuclear")], + output_directory, + ) + + + ] + + # Mitochondrion + cmd += [ + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "3__pyrodigal-mitochondrion")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "6__barrnap-mitochondrion")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "9__trnascan-mitochondrion")], + ), + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "3__pyrodigal-mitochondrion")], + directories[("intermediate", "6__barrnap-mitochondrion")], + directories[("intermediate", "9__trnascan-mitochondrion")], + os.path.join(output_directory, "mitochondrion"), + ) + ] + + # Plastid + cmd += [ + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "4__pyrodigal-plastid")], + ), + + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "7__barrnap-plastid")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "10__trnascan-plastid")], + ), + + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "4__pyrodigal-plastid")], + directories[("intermediate", "7__barrnap-plastid")], + directories[("intermediate", "10__trnascan-plastid")], + os.path.join(output_directory, "plastid"), + ) + ] + + return cmd + +def get_stats_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Genomes + cmd = [ + + os.environ["seqkit"], + "stats", + "-a", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.fa"), + os.path.join(output_directory, "*", "*.fa"), + + "|", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-3]); df.to_csv(sys.stdout, sep="\t")'""", + ">", + os.path.join(output_directory,"genome_statistics.tsv"), + + + # CDS + + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.ffn"), + os.path.join(output_directory,"*", "*.ffn"), + + "|", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-4]); df.to_csv(sys.stdout, sep="\t")'""", + ">", + os.path.join(output_directory,"gene_statistics.cds.tsv"), + + + # rRNA + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.rRNA"), + os.path.join(output_directory,"*", "*.rRNA"), + + "|", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + ">", + os.path.join(output_directory,"gene_statistics.rRNA.tsv"), + + + # tRNA + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.tRNA"), + os.path.join(output_directory,"*", "*.tRNA"), + + "|", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + ">", + os.path.join(output_directory,"gene_statistics.tRNA.tsv"), + + + ] + + return cmd + + +# ============ +# Run Pipeline +# ============ + + + +def create_pipeline(opts, directories, f_cmds): + + # ................................................................. + # Primordial + # ................................................................. + # Commands file + pipeline = ExecutablePipeline(name=__program__, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + + # ============ + # Partitioning + # ============ + if opts.tiara_results: + output_filepaths = [opts.tiara_results] + else: + + step = 0 + + program = "tiara" + + program_label = "{}__{}".format(step, program) + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # Info + description = "Predict taxonmic domain of contigs" + + # i/o + input_filepaths = [ + opts.fasta, + ] + + output_filepaths = [ + os.path.join(output_directory, "tiara_output.tsv"), + ] + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_tiara_cmd(**params) + + + # Add step to pipeline + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + ) + + + + # ========== + # Partition sequences + # ========== + step = 1 + + program = "partition" + + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "Partitioning nuclear, mitochondrial, and plastid sequences" + + # i/o + + input_filepaths = [ + opts.fasta, + output_filepaths[0], + ] + + + + output_filepaths = [ + os.path.join(directories["output"],"*.fa"), + os.path.join(output_directory,"eukaryotic_contigs.list"), + os.path.join(output_directory,"genomes.list"), + + ] + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + if opts.scaffolds_to_bins: + cmd = get_partition_organelle_sequences_multiple_cmd(**params) + else: + cmd = get_partition_organelle_sequences_single_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + acceptable_returncodes={0}, + log_prefix=program_label, + # whitelist_empty_output_files=[ + # "mitochondrion/*.fa", + # "plastid/*.fa", + # ] + ) + + # ============= + # MetaEuk + # ============= + step = 2 + + program = "metaeuk" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = directories["output"] + + # Info + description = "Ab initio eukaryotic gene prediction" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "eukaryotic_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + "identifier_mapping.metaeuk.tsv", + "metaeuk.headersMap.tsv", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_metaeuk_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Pyrodigal + # ============= + step = 3 + + program = "pyrodigal-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = os.path.join(directories["output"], "mitochondrion") + + + # Info + description = "Ab initio prokaryotic gene prediction [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "mitochondrion_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "genetic_code":opts.pyrodigal_mitochondrial_genetic_code, + } + + cmd = get_pyrodigal_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Pyrodigal + # ============= + step = 4 + + program = "pyrodigal-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = os.path.join(directories["output"], "plastid") + + # Info + description = "Ab initio prokaryotic gene prediction [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "plastid_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "genetic_code":opts.pyrodigal_plastid_genetic_code, + } + + cmd = get_pyrodigal_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 5 + + program = "barrnap-nuclear" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Nuclear]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "*.fa"), + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"euk", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 6 + + program = "barrnap-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "mitochondrion", "*.fa"), + + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"mito", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 7 + + program = "barrnap-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "plastid", "*.fa"), + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"bac", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 8 + + program = "trnascan-nuclear" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Nuclear]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":"-E", + "trnascan_options":opts.trnascan_nuclear_options, + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 9 + + program = "trnascan-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "mitochondrion", "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":opts.trnascan_mitochondrial_searchmode, + "trnascan_options":opts.trnascan_mitochondrial_options, + + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 10 + + program = "trnascan-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "plastid", "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":opts.trnascan_plastid_searchmode, + "trnascan_options":opts.trnascan_plastid_options, + + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Output + # ============= + step = 11 + + program = "symlink" + program_label = "{}__{}".format(step, program) + description = "Merging and symlinking results for output" + + # Add to directories + output_directory = directories["output"] + + # i/o + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + ] + + output_filenames = [ + # "*.faa", + # "*.ffn", + "*.gff", + + + ] + + + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_symlink_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + # ============= + # Output + # ============= + step = 12 + + program = "stats" + program_label = "{}__{}".format(step, program) + description = "Calculating statistics for sequences" + + # Add to directories + output_directory = directories["output"] + + # i/o + input_filepaths = [ + os.path.join(directories["output"], "*.fa"), + # os.path.join(directories["output"], "mitochondrion"), + # os.path.join(directories["output"], "plastid"), + + ] + + output_filenames = [ + "*.tsv", + ] + + + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_stats_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + + + + + + + return pipeline + + + +# Set environment variables +def add_executables_to_environment(opts): + """ + Adapted from Soothsayer: https://github.com/jolespin/soothsayer + """ + accessory_scripts = { + "partition_gene_models.py", + "compile_metaeuk_identifiers.py", + "partition_organelle_sequences.py", + "append_geneid_to_prodigal_gff.py", + "append_geneid_to_barrnap_gff.py", + } + + required_executables={ + "seqkit", + "metaeuk", + "pyrodigal", + "barrnap", + "tRNAscan-SE", + + } + if not opts.tiara_results: + required_executables |= {"tiara"} + + + required_executables |= accessory_scripts + + if opts.path_config == "CONDA_PREFIX": + executables = dict() + for name in sorted(required_executables): + if name not in accessory_scripts: + executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) + else: + if opts.path_config is None: + opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") + opts.path_config = format_path(opts.path_config) + assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) + assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) + df_config = pd.read_csv(opts.path_config, sep="\t") + assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) + df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) + # Get executable paths + executables = OrderedDict(zip(df_config["name"], df_config["executable"])) + assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) + + # Display + + for name in sorted(accessory_scripts): + executables[name] = "'{}'".format(os.path.join(opts.script_directory, name)) # Can handle spaces in path + + + + print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) + for name, executable in executables.items(): + if name in required_executables: + print(name, executable, sep = " --> ", file=sys.stdout) + os.environ[name] = executable.strip() + print("", file=sys.stdout) + + +# Configure parameters +def configure_parameters(opts, directories): + assert bool(opts.name) != bool(opts.scaffolds_to_bins), "--name and --scaffolds_to_bins are mutually exclusive. Use --name if you are modeling genes on a single assembly and --scaffolds_to_bins in batch (faster for multiple assemblies)" + if opts.name: + opts.basename = opts.name + else: + opts.basename = "gene_models" + + # Set environment variables + add_executables_to_environment(opts=opts) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -f -d -i -o ".format(__program__) + + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + + # Pipeline + parser_io = parser.add_argument_group('I/O arguments') + parser_io.add_argument("-f","--fasta", type=str, required=True, help = "path/to/scaffolds.fasta") + parser_io.add_argument("-t","--tiara_results", type=str, required=True, help = "path/to/scaffolds.fasta") + parser_io.add_argument("-n","--name", type=str, required=False, help = "path/to/scaffolds.fasta [Cannot be used with --scaffolds_to_bins]") + parser_io.add_argument("-i","--scaffolds_to_bins", type=str, required=False, help = "path/to/scaffolds_to_bins.tsv, [Optional] Format: [id_scaffold][id_bin], No header. [Cannot be used with --name]") + parser_io.add_argument("-o","--output_directory", type=str, default="eukaryotic_gene_modeling_output", help = "path/to/project_directory [Default: eukaryotic_gene_modeling_output]") + parser_io.add_argument("-d", "--metaeuk_database", type=str, required=True, help=f"MetaEuk/MMSEQS2 database (E.g., $VEBA_DATABASE/Classify/Microeukaryotic/microeukaryotic)") + + # Utility + parser_utility = parser.add_argument_group('Utility arguments') + parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future + parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") + # parser_utility.add_argument("--random_state", type=int, default=0, help = "Random state [Default: 0]") + parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") + parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) + + # Tiara + parser_organelle = parser.add_argument_group('Organelle arguments') + parser_organelle.add_argument("-u","--unknown_organelle_prediction", type=str, default="nuclear", help = "{unknown, nuclear} [Default: nuclear]") + # parser_organelle.add_argument("--mitochondrion_suffix", type=str, default=".mtDNA", help = "Mitochondrion suffix [Default: .mtDNA]") + # parser_organelle.add_argument("--plastid_suffix", type=str, default=".plastid", help = "Plastid suffix [Default: .plastid]") + # parser_organelle.add_argument("--unknown_suffix", type=str, default=".unknown", help = "Unknown suffix [Default: .unknown]") + parser_organelle.add_argument("--tiara_options", type=str, default="", help="Tiara | More options (e.g. --arg 1 ) [Default: '']") + + # MetaEuk + parser_metaeuk = parser.add_argument_group('MetaEuk arguments') + parser_metaeuk.add_argument("--metaeuk_sensitivity", type=float, default=4.0, help="MetaEuk | Sensitivity: 1.0 faster; 4.0 fast; 7.5 sensitive [Default: 4.0]") + parser_metaeuk.add_argument("--metaeuk_evalue", type=float, default=0.01, help="MetaEuk | List matches below this E-value (range 0.0-inf) [Default: 0.01]") + parser_metaeuk.add_argument("--metaeuk_options", type=str, default="", help="MetaEuk | More options (e.g. --arg 1 ) [Default: ''] https://github.com/soedinglab/metaeuk") + + # Pyrodigal + parser_pyrodigal = parser.add_argument_group('Pyrodigal arguments (Mitochondria)') + parser_pyrodigal.add_argument("--pyrodigal_minimum_gene_length", type=int, default=90, help="Pyrodigal | Minimum gene length [Default: 90]") + parser_pyrodigal.add_argument("--pyrodigal_minimum_edge_gene_length", type=int, default=60, help="Pyrodigal | Minimum edge gene length [Default: 60]") + parser_pyrodigal.add_argument("--pyrodigal_maximum_gene_overlap_length", type=int, default=60, help="Pyrodigal | Maximum gene overlap length [Default: 60]") + parser_pyrodigal.add_argument("--pyrodigal_mitochondrial_genetic_code", type=int, default=4, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 4] (The Mold, Protozoan, and Coelenterate Mitochondrial Code and the Mycoplasma/Spiroplasma Code))") + parser_pyrodigal.add_argument("--pyrodigal_plastid_genetic_code", type=int, default=11, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 11] (The Bacterial, Archaeal and Plant Plastid Code))") + + # rRNA + parser_barrnap = parser.add_argument_group('barrnap arguments') + parser_barrnap.add_argument("--barrnap_length_cutoff", type=float, default=0.8, help="barrnap | Proportional length threshold to label as partial [Default: 0.8]") + parser_barrnap.add_argument("--barrnap_reject", type=float, default=0.25, help="barrnap | Proportional length threshold to reject prediction [Default: 0.25]") + parser_barrnap.add_argument("--barrnap_evalue", type=float, default=1e-6, help="barrnap | Similarity e-value cut-off [Default: 1e-6]") + + # tRNA + parser_trnascan = parser.add_argument_group('tRNAscan-SE arguments') + parser_trnascan.add_argument("--trnascan_nuclear_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_mitochondrial_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | Current best option according to developer: https://github.com/UCSC-LoweLab/tRNAscan-SE/issues/24") + parser_trnascan.add_argument("--trnascan_mitochondrial_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_plastid_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_plastid_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + + + # Options + opts = parser.parse_args() + + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Threads + if opts.n_jobs == -1: + from multiprocessing import cpu_count + opts.n_jobs = cpu_count() + assert opts.n_jobs >= 1, "--n_jobs must be ≥ 1. To select all available threads, use -1." + + # Directories + directories = dict() + directories["project"] = create_directory(opts.output_directory) + directories["output"] = create_directory(os.path.join(directories["project"], "output")) + directories["log"] = create_directory(os.path.join(directories["project"], "log")) + directories["tmp"] = create_directory(os.path.join(directories["project"], "tmp")) + directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) + directories["intermediate"] = create_directory(os.path.join(directories["project"], "intermediate")) + os.environ["TMPDIR"] = directories["tmp"] + + # Temporary + opts.mitochondrion_suffix = "" + opts.plastid_suffix = "" + opts.unknown_suffix = "" + + # Info + print(format_header(__program__, "="), file=sys.stdout) + print(format_header("Configuration:", "-"), file=sys.stdout) + print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) + print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("Script version:", __version__, file=sys.stdout) + print("Moment:", get_timestamp(), file=sys.stdout) + print("Directory:", os.getcwd(), file=sys.stdout) + print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) + configure_parameters(opts, directories) + sys.stdout.flush() + + + # Run pipeline + with open(os.path.join(directories["project"], "commands.sh"), "w") as f_cmds: + pipeline = create_pipeline( + opts=opts, + directories=directories, + f_cmds=f_cmds, + ) + pipeline.compile() + pipeline.execute(restart_from_checkpoint=opts.restart_from_checkpoint) + + # shutil.rmtree(directories["intermediate"]) + + + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/src/scripts/merge_annotations.py b/src/scripts/merge_annotations.py index 82a2630..73be360 100755 --- a/src/scripts/merge_annotations.py +++ b/src/scripts/merge_annotations.py @@ -6,7 +6,7 @@ from soothsayer_utils import read_hmmer, pv, get_file_object, assert_acceptable_arguments, format_header __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.14" +__version__ = "2023.6.20" # disclaimer = format_header("DISCLAIMER: Lineage predictions are NOT robust and DO NOT USE CORE MARKERS. Please only use for exploratory suggestions.") @@ -73,7 +73,7 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -i -d --hmmsearch_pfam --hmmsearch_amr --hmmsearch_antifam -a -o ".format(__program__) + usage = "{} -i -d -m -b --hmmsearch_pfam --hmmsearch_amr --hmmsearch_antifam -a -o ".format(__program__) epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" # Parser @@ -83,6 +83,7 @@ def main(args=None): parser_required.add_argument("-o","--output_directory", type=str, default="annotation_output", help = "Output directory for annotations [Default: annotation_output]") parser_required.add_argument("-d","--diamond_uniref", type=str, required=True, help = "path/to/diamond_uniref.blast6 in blast6 format (No header, cannot be empty) with the following fields: \nqseqid sseqid stitle pident length mismatch qlen qstart qend slen sstart send evalue bitscore qcovhsp scovhsp") parser_required.add_argument("-m","--diamond_mibig", type=str, required=True, help = "path/to/diamond_mibig.blast6 in blast6 format (No header) with the following fields: \nqseqid sseqid stitle pident length mismatch qlen qstart qend slen sstart send evalue bitscore qcovhsp scovhsp") + parser_required.add_argument("-b","--diamond_vfdb", type=str, required=True, help = "path/to/diamond_vfdb.blast6 in blast6 format (No header) with the following fields: \nqseqid sseqid stitle pident length mismatch qlen qstart qend slen sstart send evalue bitscore qcovhsp scovhsp") parser_required.add_argument("-k","--kofam", type=str, required=True, help = "path/to/kofamscan.tblout hmmsearch results in tblout format") parser_required.add_argument("-p", "--hmmsearch_pfam", type=str, required=True, help = "path/to/hmmsearch.tblout hmmsearch results in tblout format") parser_required.add_argument("-n", "--hmmsearch_amr", type=str, required=True, help = "path/to/hmmsearch.tblout hmmsearch results in tblout format") @@ -165,6 +166,17 @@ def main(args=None): df_diamond_mibig = df_diamond_mibig.drop(["stitle"], axis=1) proteins = proteins | set(df_diamond_mibig.index) + print(" * Reading Diamond table [VFDB]: {}".format(opts.diamond_mibig), file=sys.stderr) + columns = ["qseqid","sseqid", "stitle", "pident","length","mismatch","qlen","qstart","qend", "slen", "sstart","send", "evalue","bitscore","qcovhsp","scovhsp"] + try: + df_diamond_vfdb = pd.read_csv(opts.diamond_vfdb, sep="\t", index_col=None, header=None) + except pd.errors.EmptyDataError: + df_diamond_vfdb = pd.DataFrame(columns=columns) + df_diamond_vfdb.columns = columns + df_diamond_vfdb = df_diamond_vfdb.set_index("qseqid") + df_diamond_vfdb = df_diamond_vfdb.drop(["stitle"], axis=1) + proteins = proteins | set(df_diamond_vfdb.index) + print(" * Reading HMMSearch table [Pfam]: {}".format(opts.hmmsearch_pfam), file=sys.stderr) df_hmmsearch_pfam = read_hmmer(opts.hmmsearch_pfam, program="hmmsearch", format="tblout", verbose=False) if not df_hmmsearch_pfam.empty: @@ -233,7 +245,7 @@ def main(args=None): df_hmms_pfam = df_hmms_pfam.applymap(lambda x: x if isinstance(x,list) else []) df_hmms_pfam.insert(loc=0, column="number_of_hits", value=df_hmms_pfam["ids"].map(len)) - df_hmms_pfam.index.name = "id_protein" + df_hmms_pfam.index.name = "id_protein-representative" # AMR if not df_hmmsearch_amr.empty: @@ -261,7 +273,7 @@ def main(args=None): else: df_hmms_amr = pd.DataFrame(index=proteins, columns=["hit", "ids", "names", "evalues", "scores"]) df_hmms_amr = df_hmms_amr.applymap(lambda x: x if isinstance(x,list) else []) - df_hmms_amr.index.name = "id_protein" + df_hmms_amr.index.name = "id_protein-representative" df_hmms_amr.insert(loc=0, column="number_of_hits", value=df_hmms_amr["ids"].map(len)) @@ -293,7 +305,7 @@ def main(args=None): df_hmms_antifam = pd.DataFrame(index=proteins, columns=["hit", "ids", "names", "evalues", "scores"]) df_hmms_antifam["hit"] = df_hmms_antifam["hit"].fillna(False) df_hmms_antifam = df_hmms_antifam.applymap(lambda x: x if isinstance(x,list) else []) - df_hmms_antifam.index.name = "id_protein" + df_hmms_antifam.index.name = "id_protein-representative" df_hmms_antifam.insert(loc=0, column="number_of_hits", value=df_hmms_antifam["ids"].map(len)) # KOFAMSCAN @@ -328,12 +340,12 @@ def main(args=None): # Output table dataframes = list() - for (name, df) in zip([ "UniRef", "MIBiG", "Pfam", "NCBIfam-AMR", "AntiFam", "KOFAM"],[df_diamond_uniref, df_diamond_mibig, df_hmms_pfam, df_hmms_amr, df_hmms_antifam, df_hmms_kofam]): + for (name, df) in zip([ "UniRef", "MIBiG", "VFDB", "Pfam", "NCBIfam-AMR", "AntiFam", "KOFAM"],[df_diamond_uniref, df_diamond_mibig, df_diamond_vfdb, df_hmms_pfam, df_hmms_amr, df_hmms_antifam, df_hmms_kofam]): df = df.copy() df.columns = df.columns.map(lambda x: (name,x)) dataframes.append(df) df_annotations = pd.concat(dataframes, axis=1) - df_annotations.index.name = "id_protein" + df_annotations.index.name = "id_protein-representative" # Composite label protein_to_labels = dict() diff --git a/src/scripts/partition_organelle_sequences.py b/src/scripts/partition_organelle_sequences.py new file mode 100755 index 0000000..bb41fc9 --- /dev/null +++ b/src/scripts/partition_organelle_sequences.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob, gzip +from collections import OrderedDict +import pandas as pd +from tqdm import tqdm +from Bio.SeqIO.FastaIO import SimpleFastaParser + + +pd.options.display.max_colwidth = 100 +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.6.28" + + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -f -t -n -o -x fa".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + + parser.add_argument("-f","--fasta", type=str, default="stdin", help = "path/to/genome_assembly.fasta [Default: stdin]") + parser.add_argument("-t","--tiara_results", type=str, required=True, help = "path/to/tiara_results.tsv") + parser.add_argument("-n","--name", type=str, required=True, help = "Name of genome") + parser.add_argument("-o","--output_directory", type=str, required=True, help = "Output directory") + parser.add_argument("-u","--unknown_organelle_prediction", type=str, default="nuclear", help = "{unknown, nuclear} [Default: nuclear]") + parser.add_argument("-x","--extension", type=str, default="fa", help = "Fasta output extension [Default: fa]") + parser.add_argument("--mitochondrion_suffix", type=str, default="", help = "Mitochondrion suffix [Default: '']") + parser.add_argument("--plastid_suffix", type=str, default="", help = "Plastid suffix [Default: '']") + parser.add_argument("--unknown_suffix", type=str, default="", help = "Unknown suffix [Default: '']") + parser.add_argument("--flat_directory", action="store_true", help = "Use if you don't want a nested directory and for all the files to be flatten in the output directory") + parser.add_argument("--no_description", action="store_true", help = "Default is to add [GENOME_NAME]:[nuclear/mitochondrion/plastid] as the description") + parser.add_argument("--no_empty_files", action="store_true", help = "Default is to create file even if it's empty") + parser.add_argument("--verbose", action="store_true", help = "Print ids and lengths for seqeunces not in Tiara") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + assert opts.unknown_organelle_prediction in {"unknown", "nuclear"}, "--unknown_organelle_prediction must be one of the following:{unknown, nuclear}" + + # Organelle predicitions + df_tiara = pd.read_csv(opts.tiara_results, sep="\t", index_col=0)[["class_fst_stage", "class_snd_stage"]] + df_tiara.index = df_tiara.index.map(lambda x: x.split(" ")[0]) + + id_to_classification = dict() + for id, (fst_prediction, snd_prediction) in df_tiara.iterrows(): + if fst_prediction in {"archaea", "bacteria", "eukarya", "unknown"}: + id_to_classification[id] = "nuclear" + else: + if snd_prediction in {"mitochondrion", "plastid"}: + id_to_classification[id] = snd_prediction + else: + id_to_classification[id] = opts.unknown_organelle_prediction + + + # Output directories + os.makedirs(opts.output_directory, exist_ok=True) + output_files = dict() + if opts.no_empty_files: + seq_types = set(id_to_classification.values()) + else: + seq_types = {"nuclear", "mitochondrion", "plastid", opts.unknown_organelle_prediction} + + + for id_type in seq_types: + if id_type == "nuclear": + filepath = os.path.join(opts.output_directory, "{}.{}".format(opts.name, opts.extension)) + else: + suffix = {"mitochondrion":opts.mitochondrion_suffix, "plastid":opts.plastid_suffix, "unknown":opts.unknown_suffix}[id_type] + + if opts.flat_directory: + filepath = os.path.join(opts.output_directory, "{}{}.{}".format(opts.name, suffix, opts.extension)) + else: + os.makedirs(os.path.join(opts.output_directory, id_type), exist_ok=True) + filepath = os.path.join(opts.output_directory, id_type, "{}{}.{}".format(opts.name, suffix, opts.extension)) + output_files[id_type] = open(filepath, "w") + + + # Partition fasta + fasta_filepath = "stdin" + if opts.fasta == "stdin": + opts.fasta = sys.stdin + else: + fasta_filepath = opts.fasta + if opts.fasta.endswith(".gz"): + opts.fasta = gzip.open(opts.fasta, "rt") + else: + opts.fasta = open(opts.fasta, "r") + + for header, seq in tqdm(SimpleFastaParser(opts.fasta), "Partitioning genome assembly: {}".format(fasta_filepath)): + id = header.split(" ")[0] + if id in id_to_classification: + id_type = id_to_classification[id] + else: + id_type = "nuclear" + if opts.verbose: + print("Identifier '{}' of length {} was not in Tiara results and will be tagged as 'nuclear'".format(id, len(seq)), file=sys.stdout) + file = output_files[id_type] + if opts.no_description: + print(">{}\n{}".format(id,seq), file=file) + else: + description = "{}:{}".format(opts.name, id_type) + print(">{} {}\n{}".format(id, description, seq), file=file) + + if opts.fasta != sys.stdin: + opts.fasta.close() + + for id_type, file in output_files.items(): + if opts.verbose: + print("Closing file type: {}".format(id_type), file=sys.stdout) + file.close() + + +if __name__ == "__main__": + main() From b92592b04457d3b14d8020733d7c4faba0b014bf Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Tue, 4 Jul 2023 20:00:04 -0700 Subject: [PATCH 08/16] Update eukaryotic_gene_modeling_wrapper.py --- src/scripts/eukaryotic_gene_modeling_wrapper.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/scripts/eukaryotic_gene_modeling_wrapper.py b/src/scripts/eukaryotic_gene_modeling_wrapper.py index 0b7ca38..7e45868 100755 --- a/src/scripts/eukaryotic_gene_modeling_wrapper.py +++ b/src/scripts/eukaryotic_gene_modeling_wrapper.py @@ -12,7 +12,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.7.3" +__version__ = "2023.7.4" # Tiara def get_tiara_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): @@ -331,7 +331,7 @@ def get_symlink_cmd(input_filepaths, output_filepaths, output_directory, directo # Nuclear cmd = [ - "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.metaeuk.tsv $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( output_directory, directories[("intermediate", "2__metaeuk")], ), @@ -367,7 +367,7 @@ def get_symlink_cmd(input_filepaths, output_filepaths, output_directory, directo cmd += [ "&&", - "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( os.path.join(output_directory,"mitochondrion"), directories[("intermediate", "3__pyrodigal-mitochondrion")], ), @@ -401,7 +401,7 @@ def get_symlink_cmd(input_filepaths, output_filepaths, output_directory, directo cmd += [ "&&", - "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( os.path.join(output_directory,"plastid"), directories[("intermediate", "4__pyrodigal-plastid")], ), @@ -1206,7 +1206,12 @@ def create_pipeline(opts, directories, f_cmds): ] output_filenames = [ - "*.tsv", + "genome_statistics.tsv", + "gene_statistics.cds.tsv", + "gene_statistics.rRNA.tsv", + "gene_statistics.tRNA.tsv", + + ] From 553e5a1638b953c290c3fbb808d8bc876f5a5561 Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Fri, 7 Jul 2023 15:44:11 -0700 Subject: [PATCH 09/16] v1.1.3b --- DEVELOPMENT.md | 8 +- install/README.md | 155 ++++++- install/devel/update_environment_variables.sh | 4 +- install/docker/dockerize_environments.sh | 2 +- install/download_databases.sh | 30 +- install/environments/VEBA-annotate_env.yml | 6 +- .../VEBA-binning-eukaryotic_env.yml | 21 +- .../VEBA-binning-prokaryotic_env.yml | 98 +++-- .../environments/VEBA-binning-viral_env.yml | 10 +- .../environments/VEBA-biosynthetic_env.yml | 6 +- install/environments/VEBA-classify_env.yml | 10 +- install/environments/VEBA-database_env.yml | 195 ++++----- install/update_environment_variables.sh | 10 +- src/binning-eukaryotic.py | 391 ++++++------------ src/binning-prokaryotic.py | 226 ++++++---- src/binning-viral.py | 26 +- src/scripts/append_geneid_to_barrnap_gff.py | 1 - src/scripts/compile_gff.py | 111 +++++ .../eukaryotic_gene_modeling_wrapper.py | 16 +- src/scripts/filter_busco_results.py | 195 ++++++--- src/scripts/merge_busco_json.py | 36 +- 21 files changed, 963 insertions(+), 594 deletions(-) create mode 100755 src/scripts/compile_gff.py diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 47e5b9a..bf99d6f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -211,6 +211,9 @@ ________________________________________________________________ #### Path to `v2.0.0`: **Definitely:** + +* Add `genome_statistics.tsv`, `gene_statistics.cds.tsv`, etc. to `binning-*.py` modules. +* Add `VFDB` homology to `biosynthetic.py`. * Add contig region and exons to `prodigal` GFF. * Consistent usage of the following terms: 1) dataframe vs. table; 2) protein-cluster vs. orthogroup. * Add support for `FAMSA` in `phylogeny.py` @@ -227,9 +230,9 @@ ________________________________________________________________ * Build a clustered version of the Microeukaryotic Protein Database that is more efficient to run. **Probably (Yes)?:** - +* Build Metaphlan (and HUMAnN) database from genomes. * Add [iPHoP](https://bitbucket.org/srouxjgi/iphop/src/main/) to `binning-viral.py`. -* Add a `metabolic.py` module +* Add a `metabolic.py` module * Swap [`TransDecoder`](https://github.com/TransDecoder/TransDecoder) for [`TransSuite`](https://github.com/anonconda/TranSuite) * Add spatial coverage to `coverage.py` script like in `mapping.py` module? Maybe just the samtools coverage output. * Reimplement `KOFAMSCAN` (which creates thousands of intermediate files) using `hmmer_wrapper.py`. @@ -244,6 +247,7 @@ ________________________________________________________________ #### Change Log: +* [2023.7.7] - Added `compile_gff.py` to merge CDS, rRNA, and tRNA GFF files. Used in `binning-prokaryotic.py` and `binning-viral.py`. `binning-eukaryotic.py` uses the source of this in the backend of `filter_busco_results.py`. * [2023.7.3] - Added `eukaryotic_gene_modeling_wrapper.py` which 1) splits nuclear, mitochondrial, and plastid genomes; 2) performs gene modeling via `MetaEuk` and `Pyrodigal`; 3) performs rRNA detection via `BARRNAP`; 4) performs tRNA detection via `tRNAscan-SE`; 5) merges processed GFF files; and 5) calculates sequences statistics. * [2023.6.29] - Added `gene_biotype=protein_coding` to `prodigal` GFF output. * [2023.6.20] - Added `VFDB` to `annotate.py` and database. diff --git a/install/README.md b/install/README.md index 5b48c35..5592b5e 100644 --- a/install/README.md +++ b/install/README.md @@ -83,7 +83,7 @@ The `VEBA` installation is going to configure some `conda` environments for you ``` # For stable version, download and decompress the tarball: -VERSION="1.1.1" +VERSION="1.1.2" wget https://github.com/jolespin/veba/archive/refs/tags/v${VERSION}.tar.gz tar -xvf v${VERSION}.tar.gz && mv veba-${VERSION} veba @@ -288,7 +288,155 @@ A protein database is required not only for eukaryotic gene calls using MetaEuk #### Database Structure: **Current:** -*VEBA Database* version: `VDB_v5` +*VEBA Database* version: `VDB_v5.1` + +* `VDB_v5` → `VDB_v5.1` updates `GTDB` database from `r207_v2` → `r214`. +* Changes `${VEBA_DATABASE}/Classify/GTDBTk` → `${VEBA_DATABASE}/Classify/GTDB`. +* Adds `gtdb_r214.msh` to `${VEBA_DATABASE}/Classify/GTDB/mash/` for ANI screens. +* Adds `VFDB` to `${VEBA_DATABASE}/Annotate/VFDB/VFDB_setA_pro.dmnd` for virulence factor annotation. + +``` +tree -L 3 . +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── MIBiG +│   │   └── mibig_v3.1.dmnd +│   ├── NCBIfam-AMRFinder +│   │   ├── NCBIfam-AMRFinder.changelog.txt +│   │   ├── NCBIfam-AMRFinder.hmm.gz +│   │   └── NCBIfam-AMRFinder.tsv +│   ├── Pfam +│   │   ├── Pfam-A.hmm.gz +│   │   └── relnotes.txt +│   └── UniRef +│   ├── uniref50.dmnd +│   ├── uniref50.release_note +│   ├── uniref90.dmnd +│   └── uniref90.release_note +├── Classify +│   ├── CheckM2 +│   │   └── uniref100.KO.1.dmnd +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── geNomad +│   │   ├── genomad_db +│   │   ├── genomad_db.dbtype +│   │   ├── genomad_db_h +│   │   ├── genomad_db_h.dbtype +│   │   ├── genomad_db_h.index +│   │   ├── genomad_db.index +│   │   ├── genomad_db.lookup +│   │   ├── genomad_db_mapping +│   │   ├── genomad_db.source +│   │   ├── genomad_db_taxonomy +│   │   ├── genomad_integrase_db +│   │   ├── genomad_integrase_db.dbtype +│   │   ├── genomad_integrase_db_h +│   │   ├── genomad_integrase_db_h.dbtype +│   │   ├── genomad_integrase_db_h.index +│   │   ├── genomad_integrase_db.index +│   │   ├── genomad_integrase_db.lookup +│   │   ├── genomad_integrase_db.source +│   │   ├── genomad_marker_metadata.tsv +│   │   ├── genomad_mini_db -> genomad_db +│   │   ├── genomad_mini_db.dbtype +│   │   ├── genomad_mini_db_h -> genomad_db_h +│   │   ├── genomad_mini_db_h.dbtype -> genomad_db_h.dbtype +│   │   ├── genomad_mini_db_h.index -> genomad_db_h.index +│   │   ├── genomad_mini_db.index +│   │   ├── genomad_mini_db.lookup -> genomad_db.lookup +│   │   ├── genomad_mini_db_mapping -> genomad_db_mapping +│   │   ├── genomad_mini_db.source -> genomad_db.source +│   │   ├── genomad_mini_db_taxonomy -> genomad_db_taxonomy +│   │   ├── mini_set_ids +│   │   ├── names.dmp +│   │   ├── nodes.dmp +│   │   ├── plasmid_hallmark_annotation.txt +│   │   ├── version.txt +│   │   └── virus_hallmark_annotation.txt +│   ├── GTDB +│   │   ├── fastani +│   │   ├── markers +│   │   ├── mash +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── humann_uniref50_annotations.tsv.gz +│   │   ├── md5_checksums +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10 +│   │   ├── microeukaryotic.eukaryota_odb10.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h +│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h.index +│   │   ├── microeukaryotic.eukaryota_odb10.index +│   │   ├── microeukaryotic.eukaryota_odb10.lookup +│   │   ├── microeukaryotic.eukaryota_odb10.source +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.eukaryota_odb10.list +│   │   ├── RELEASE_NOTES +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   └── readme.txt +├── Contamination +│   ├── AntiFam +│   │   ├── AntiFam.hmm.gz +│   │   ├── relnotes +│   │   └── version +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +└── MarkerSets + ├── Archaea_76.hmm.gz + ├── Bacteria_71.hmm.gz + ├── CPR_43.hmm.gz + ├── eukaryota_odb10.hmm.gz + ├── eukaryota_odb10.scores_cutoff.tsv.gz + ├── Fungi_593.hmm.gz + ├── Protista_83.hmm.gz + └── README +``` + +**Deprecated:** + +
+ *VEBA Database* version: `VDB_v5` + `VDB_v4` → `VDB_v5` replaces `nr` with `UniRef90` and `UniRef50`. Also includes `MiBIG` database. ``` @@ -426,9 +574,6 @@ tree -L 3 . └── README ``` - -**Deprecated:** -
*VEBA Database* version: `VDB_v4` diff --git a/install/devel/update_environment_variables.sh b/install/devel/update_environment_variables.sh index cc8b87d..861361b 100644 --- a/install/devel/update_environment_variables.sh +++ b/install/devel/update_environment_variables.sh @@ -32,12 +32,12 @@ for ENV_NAME in VEBA-binning-prokaryotic_env; do #GTDB-Tk echo ". .. ... ..... ........ ............." -echo "xiv * Adding the following environment variable to VEBA environments: export GTDBTK_DATA_PATH=${REALPATH_DATABASE_DIRECTORY}/Classify/GTDBTk/" +echo "xiv * Adding the following environment variable to VEBA environments: export GTDBTK_DATA_PATH=${REALPATH_DATABASE_DIRECTORY}/Classify/GTDB/" for ENV_NAME in VEBA-classify_env; do ENV_NAME=test-${ENV_NAME} ENV_PREFIX=${CONDA_BASE}/envs/${ENV_NAME} # GTDB-Tk - echo "export GTDBTK_DATA_PATH=${REALPATH_DATABASE_DIRECTORY}/Classify/GTDBTk/" >> ${ENV_PREFIX}/etc/conda/activate.d/veba.sh + echo "export GTDBTK_DATA_PATH=${REALPATH_DATABASE_DIRECTORY}/Classify/GTDB/" >> ${ENV_PREFIX}/etc/conda/activate.d/veba.sh echo "unset GTDBTK_DATA_PATH" >> ${ENV_PREFIX}/etc/conda/deactivate.d/veba.sh done diff --git a/install/docker/dockerize_environments.sh b/install/docker/dockerize_environments.sh index b77bc64..3db5a86 100644 --- a/install/docker/dockerize_environments.sh +++ b/install/docker/dockerize_environments.sh @@ -1,4 +1,4 @@ -for ENV_NAME in $(ls ../environments/ | grep ".yml" | grep -v "VEBA-preprocess_env"); +for ENV_NAME in $(ls ../environments/ | grep ".yml"); do ENV_NAME=$(echo $ENV_NAME | cut -f1 -d ".") echo $ENV_NAME diff --git a/install/download_databases.sh b/install/download_databases.sh index dbfbd05..8ea1e35 100644 --- a/install/download_databases.sh +++ b/install/download_databases.sh @@ -1,6 +1,6 @@ #!/bin/bash -# __VERSION__ = "2023.5.15" -# VEBA_DATABASE_VERSION = "VDB_v5" +# __VERSION__ = "2023.6.20" +# VEBA_DATABASE_VERSION = "VDB_v5.1" # MICROEUKAYROTIC_DATABASE_VERSION = "VDB-Microeukaryotic_v2.1" # Create database @@ -39,11 +39,21 @@ echo ". .. ... ..... ........ ............." echo "ii * Processing GTDB-Tk" echo ". .. ... ..... ........ ............." -# For GTDBTk v2 -wget -v -P ${DATABASE_DIRECTORY} https://data.gtdb.ecogenomic.org/releases/release207/207.0/auxillary_files/gtdbtk_r207_v2_data.tar.gz -tar xvzf ${DATABASE_DIRECTORY}/gtdbtk_r207_v2_data.tar.gz -C ${DATABASE_DIRECTORY} -mv ${DATABASE_DIRECTORY}/release207_v2 ${DATABASE_DIRECTORY}/Classify/GTDBTk -rm -rf ${DATABASE_DIRECTORY}/gtdbtk_r207_v2_data.tar.gz +# # GTDB r207_v2 +# wget -v -P ${DATABASE_DIRECTORY} https://data.gtdb.ecogenomic.org/releases/release207/207.0/auxillary_files/gtdbtk_r207_v2_data.tar.gz +# tar xvzf ${DATABASE_DIRECTORY}/gtdbtk_r207_v2_data.tar.gz -C ${DATABASE_DIRECTORY} +# mv ${DATABASE_DIRECTORY}/release207_v2 ${DATABASE_DIRECTORY}/Classify/GTDBTk +# rm -rf ${DATABASE_DIRECTORY}/gtdbtk_r207_v2_data.tar.gz + +# GTDB r214.1 +wget -v -P ${DATABASE_DIRECTORY} https://data.gtdb.ecogenomic.org/releases/release214/214.1/auxillary_files/gtdbtk_r214_data.tar.gz +tar xvzf ${DATABASE_DIRECTORY}/gtdbtk_r214_data.tar.gz -C ${DATABASE_DIRECTORY} +mv ${DATABASE_DIRECTORY}/release214 ${DATABASE_DIRECTORY}/Classify/GTDB +rm -rf ${DATABASE_DIRECTORY}/gtdbtk_r214_data.tar.gz + +# GTDB r214.1 mash sketch +wget -v -O ${DATABASE_DIRECTORY}/gtdb_r214.msh https://zenodo.org/record/8048187/files/gtdb_r214.msh?download=1 +mv ${DATABASE_DIRECTORY}/gtdb_r214.msh ${DATABASE_DIRECTORY}/Classify/GTDB/mash/ # CheckV echo ". .. ... ..... ........ ............." @@ -167,6 +177,12 @@ rm -rf ${DATABASE_DIRECTORY}/mibig_prot_seqs_3.1.rmdup.fasta # tar xzfv ${DATABASE_DIRECTORY}/bigslice-models.2020-04-27.tar.gz -C ${DATABASE_DIRECTORY}/Annotate/BiG-SLiCE # rm -rf ${DATABASE_DIRECTORY}/bigslice-models.2020-04-27.tar.gz +# VFDB +mkdir -v -p ${DATABASE_DIRECTORY}/Annotate/VFDB +wget -v -P ${DATABASE_DIRECTORY} http://www.mgc.ac.cn/VFs/Down/VFDB_setA_pro.fas.gz +diamond makedb --in ${DATABASE_DIRECTORY}/VFDB_setA_pro.fas.gz --db ${DATABASE_DIRECTORY}/Annotate/VFDB/VFDB_setA_pro.dmnd +rm -rf ${DATABASE_DIRECTORY}/VFDB_setA_pro.fas.gz + # Contamination echo ". .. ... ..... ........ ............." echo "xi * Processing contamination databases" diff --git a/install/environments/VEBA-annotate_env.yml b/install/environments/VEBA-annotate_env.yml index 159e994..e6faab9 100644 --- a/install/environments/VEBA-annotate_env.yml +++ b/install/environments/VEBA-annotate_env.yml @@ -1,4 +1,4 @@ -name: VEBA-annotate_env__v2023.5.15 +name: VEBA-annotate_env__v2023.6.20 channels: - bioconda - conda-forge @@ -26,7 +26,7 @@ dependencies: - cryptography=38.0.2=py37h38fbfac_1 - curl=7.87.0=h5eee18b_0 - dbus=1.13.6=h48d8840_2 - - diamond=2.1.6=h5b5514e_1 + - diamond=2.1.7=h43eeafb_1 - easel=0.48=h779adbc_0 - eggnog-mapper=2.1.6=pyhdfd78af_0 - entrez-direct=16.2=he881be0_1 @@ -92,7 +92,7 @@ dependencies: - nspr=4.30=h9c3ff4c_0 - nss=3.69=hb5efdd6_0 - numpy=1.21.4=py37h31617e3_0 - - openssl=1.1.1t=h0b41bf4_0 + - openssl=1.1.1u=hd590300_0 - pandas=1.3.5=py37he8f5f7f_0 - parallel=20170422=pl5.22.0_0 - pathlib2=2.3.7.post1=py37h89c1867_1 diff --git a/install/environments/VEBA-binning-eukaryotic_env.yml b/install/environments/VEBA-binning-eukaryotic_env.yml index bfba1e1..034e54b 100644 --- a/install/environments/VEBA-binning-eukaryotic_env.yml +++ b/install/environments/VEBA-binning-eukaryotic_env.yml @@ -1,4 +1,4 @@ -name: VEBA-binning-eukaryotic_env__v2023.5.15 +name: VEBA-binning-eukaryotic_env__v2023.7.6 channels: - conda-forge - bioconda @@ -13,6 +13,9 @@ dependencies: - backports=1.0=py_2 - backports.zoneinfo=0.2.1=py38h497a2fe_4 - bamtools=2.5.1=hd03093a_10 + - barrnap=0.9=hdfd78af_4 + - bbmap=39.01=h92535d8_1 + - bedtools=2.30.0=h468198e_3 - binutils_impl_linux-64=2.36.1=h193b22a_2 - binutils_linux-64=2.36=hf3e587d_7 - biopython=1.79=py38h497a2fe_1 @@ -21,21 +24,21 @@ dependencies: - boost=1.70.0=py38h9de70de_1 - boost-cpp=1.70.0=h7b93d67_3 - brotlipy=0.7.0=py38h497a2fe_1003 - - busco=5.3.2=pyhdfd78af_0 + - busco=5.4.3=pyhdfd78af_0 - bwa=0.7.17=h7132678_9 - bwidget=1.9.14=ha770c72_1 - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2022.12.7=ha878542_0 + - ca-certificates=2023.5.7=hbcca054_0 - cairo=1.16.0=h9f066cc_1006 - cdbtools=0.99=hd03093a_7 - - certifi=2022.12.7=pyhd8ed1ab_0 + - certifi=2023.5.7=pyhd8ed1ab_0 - cffi=1.15.0=py38h3931269_0 - charset-normalizer=2.0.12=pyhd8ed1ab_0 - colorama=0.4.4=pyh9f0ad1d_0 - - coreutils=9.3=h0b41bf4_0 - concoct=1.1.0=py38h7be5676_2 + - coreutils=9.3=h0b41bf4_0 - coverm=0.6.1=ha1d52fc_2 - cryptography=36.0.0=py38h9ce1e76_0 - curl=7.76.1=h979ede3_1 @@ -75,6 +78,7 @@ dependencies: - htslib=1.10.2=hd3b49d5_1 - icu=67.1=he1b5a44_0 - idna=3.3=pyhd8ed1ab_0 + - infernal=1.1.4=h779adbc_0 - intel-openmp=2019.4=243 - jbig=2.1=h7f98852_2003 - joblib=0.17.0=py_0 @@ -138,10 +142,11 @@ dependencies: - openblas=0.3.18=pthreads_h4748800_0 - openjdk=11.0.13=h87a67e3_0 - openmp=8.0.1=0 - - openssl=1.1.1t=h0b41bf4_0 + - openssl=1.1.1u=hd590300_0 - pandas=1.4.1=py38h43a58ef_0 - pango=1.42.4=h69149e4_5 - pathlib2=2.3.7.post1=py38h578d9bd_0 + - pbzip2=1.1.13=0 - pcre=8.45=h9c3ff4c_0 - pcre2=10.37=h032f7d1_0 - perl=5.26.2=h36c2ea0_1008 @@ -229,6 +234,7 @@ dependencies: - prodigal=2.6.3=hec16e2b_4 - pycparser=2.21=pyhd8ed1ab_0 - pyopenssl=22.0.0=pyhd8ed1ab_0 + - pyrodigal=2.1.0=py38he5da3d1_3 - pysocks=1.7.1=py38h578d9bd_4 - python=3.8.12=hb7a2778_2_cpython - python-dateutil=2.8.2=pyhd8ed1ab_0 @@ -294,7 +300,7 @@ dependencies: - scipy=1.8.0=py38h56a6a73_1 - sed=4.8=he412f7d_0 - sepp=4.3.10=py38h3252c3a_2 - - seqkit=2.1.0=h9ee0642_0 + - seqkit=2.4.0=h9ee0642_0 - setuptools=60.9.3=py38h578d9bd_0 - six=1.16.0=pyh6c4a22f_0 - skorch=0.9.0=pyh7b7c402_0 @@ -312,6 +318,7 @@ dependencies: - tk=8.6.12=h27826a3_0 - tktable=2.10=hb7b940f_3 - tqdm=4.54.1=pyhd8ed1ab_1 + - trnascan-se=2.0.9=pl5262h779adbc_1 - typing-extensions=4.4.0=hd8ed1ab_0 - typing_extensions=4.4.0=pyha770c72_0 - tzdata=2021e=he74cb21_0 diff --git a/install/environments/VEBA-binning-prokaryotic_env.yml b/install/environments/VEBA-binning-prokaryotic_env.yml index f3e7cd2..7a40db5 100644 --- a/install/environments/VEBA-binning-prokaryotic_env.yml +++ b/install/environments/VEBA-binning-prokaryotic_env.yml @@ -1,4 +1,4 @@ -name: VEBA-binning-prokaryotic_env__v2023.5.15 +name: VEBA-binning-prokaryotic_env__v2023.7.7 channels: - conda-forge - bioconda @@ -18,11 +18,13 @@ dependencies: - attrs=22.2.0=pyh71513ae_0 - backports=1.1=pyhd3eb1b0_0 - backports.zoneinfo=0.2.1=py38h497a2fe_4 + - barrnap=0.9=hdfd78af_4 + - bedtools=2.30.0=h468198e_3 - binutils_impl_linux-64=2.36.1=h193b22a_2 - binutils_linux-64=2.36=hf3e587d_7 - biopython=1.79=py38h497a2fe_1 - blas=1.0=mkl - - blast=2.11.0=pl526he19e7b1_0 + - blast=2.14.0=h7d5a4b4_1 - blinker=1.5=pyhd8ed1ab_0 - boost=1.70.0=py38h9de70de_1 - boost-cpp=1.70.0=h7b93d67_3 @@ -35,17 +37,17 @@ dependencies: - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2022.12.7=ha878542_0 + - ca-certificates=2023.5.7=hbcca054_0 - cachetools=4.2.4=pyhd8ed1ab_0 - cairo=1.16.0=h9f066cc_1006 - capnproto=0.9.1=h780b84a_5 - - certifi=2022.12.7=pyhd8ed1ab_0 + - certifi=2023.5.7=pyhd8ed1ab_0 - cffi=1.15.0=py38h3931269_0 - charset-normalizer=2.0.12=pyhd8ed1ab_0 - click=8.1.3=unix_pyhd8ed1ab_2 - colorama=0.4.4=pyh9f0ad1d_0 - - coreutils=9.3=h0b41bf4_0 - concoct=1.1.0=py38h7be5676_2 + - coreutils=9.3=h0b41bf4_0 - coverm=0.4.0=hc216eb9_2 - cryptography=36.0.0=py38h9ce1e76_0 - curl=7.76.1=h979ede3_1 @@ -99,6 +101,7 @@ dependencies: - idba=1.1.3=1 - idna=3.3=pyhd8ed1ab_0 - importlib-metadata=6.0.0=pyha770c72_0 + - infernal=1.1.4=pl5321h031d066_4 - intel-openmp=2019.4=243 - joblib=0.17.0=py_0 - jpeg=9e=h7f98852_0 @@ -157,7 +160,7 @@ dependencies: - markupsafe=2.1.2=py38h1de0b5d_0 - mash=2.3=he348c14_1 - matplotlib-base=3.5.1=py38hf4fb855_0 - - maxbin2=2.2.7=h87f3376_4 + - maxbin2=2.2.7=he1b5a44_1 - metabat2=2.15=h986a166_1 - minimap2=2.17=h5bf99c6_4 - mkl=2020.2=256 @@ -170,7 +173,7 @@ dependencies: - oauthlib=3.2.2=pyhd8ed1ab_0 - openblas=0.3.21=pthreads_h320a7e8_3 - openmp=8.0.1=0 - - openssl=1.1.1t=h0b41bf4_0 + - openssl=1.1.1u=hd590300_0 - opt_einsum=3.3.0=pyhd8ed1ab_1 - packaging=21.3=pyhd8ed1ab_0 - pandas=1.4.1=py38h43a58ef_0 @@ -178,29 +181,60 @@ dependencies: - pathlib2=2.3.7.post1=py38h578d9bd_0 - pcre=8.45=h9c3ff4c_0 - pcre2=10.36=h032f7d1_1 - - perl=5.26.2=h36c2ea0_1008 - - perl-archive-tar=2.32=pl526_0 - - perl-carp=1.38=pl526_3 - - perl-common-sense=3.74=pl526_2 - - perl-compress-raw-bzip2=2.087=pl526he1b5a44_0 - - perl-compress-raw-zlib=2.087=pl526hc9558a2_0 - - perl-encode=2.88=pl526_1 - - perl-encode-locale=1.05=pl526_6 - - perl-exporter=5.72=pl526_1 - - perl-exporter-tiny=1.002001=pl526_0 - - perl-extutils-makemaker=7.36=pl526_1 - - perl-io-compress=2.087=pl526he1b5a44_0 - - perl-io-zlib=1.10=pl526_2 - - perl-json=4.02=pl526_0 - - perl-json-xs=2.34=pl526h6bb024c_3 - - perl-list-moreutils=0.428=pl526_1 - - perl-list-moreutils-xs=0.428=pl526_0 - - perl-lwp-simple=6.15=pl526h470a237_4 - - perl-parent=0.236=pl526_1 - - perl-pathtools=3.75=pl526h14c3975_1 - - perl-scalar-list-utils=1.52=pl526h516909a_0 - - perl-types-serialiser=1.0=pl526_2 - - perl-xsloader=0.24=pl526_0 + - perl=5.32.1=2_h7f98852_perl5 + - perl-archive-tar=2.40=pl5321hdfd78af_0 + - perl-base=2.23=pl5321hdfd78af_2 + - perl-business-isbn=3.007=pl5321hdfd78af_0 + - perl-business-isbn-data=20210112.006=pl5321hdfd78af_0 + - perl-carp=1.38=pl5321hdfd78af_4 + - perl-common-sense=3.75=pl5321hdfd78af_0 + - perl-compress-raw-bzip2=2.201=pl5321h87f3376_1 + - perl-compress-raw-zlib=2.105=pl5321h87f3376_0 + - perl-constant=1.33=pl5321hdfd78af_2 + - perl-data-dumper=2.183=pl5321hec16e2b_1 + - perl-digest-hmac=1.04=pl5321hdfd78af_0 + - perl-digest-md5=2.58=pl5321hec16e2b_1 + - perl-encode=3.19=pl5321hec16e2b_1 + - perl-encode-locale=1.05=pl5321hdfd78af_7 + - perl-exporter=5.72=pl5321hdfd78af_2 + - perl-exporter-tiny=1.002002=pl5321hdfd78af_0 + - perl-extutils-makemaker=7.70=pl5321hd8ed1ab_0 + - perl-file-listing=6.15=pl5321hdfd78af_0 + - perl-file-spec=3.48_01=pl5321hdfd78af_2 + - perl-html-parser=3.81=pl5321h4ac6f70_1 + - perl-html-tagset=3.20=pl5321hdfd78af_4 + - perl-http-cookies=6.10=pl5321hdfd78af_0 + - perl-http-daemon=6.16=pl5321hdfd78af_0 + - perl-http-date=6.05=pl5321hdfd78af_0 + - perl-http-message=6.36=pl5321hdfd78af_0 + - perl-http-negotiate=6.01=pl5321hdfd78af_4 + - perl-io-compress=2.201=pl5321hdbdd923_2 + - perl-io-html=1.004=pl5321hdfd78af_0 + - perl-io-socket-ssl=2.074=pl5321hdfd78af_0 + - perl-io-zlib=1.14=pl5321hdfd78af_0 + - perl-json=4.10=pl5321hdfd78af_0 + - perl-json-xs=2.34=pl5321h4ac6f70_6 + - perl-libwww-perl=6.39=pl5321hdfd78af_1 + - perl-list-moreutils=0.430=pl5321hdfd78af_0 + - perl-list-moreutils-xs=0.430=pl5321h031d066_2 + - perl-lwp-mediatypes=6.04=pl5321hdfd78af_1 + - perl-lwp-simple=6.39=pl5321h9ee0642_5 + - perl-mime-base64=3.16=pl5321hec16e2b_2 + - perl-net-http=6.22=pl5321hdfd78af_0 + - perl-net-ssleay=1.92=pl5321h0e0aaa8_1 + - perl-ntlm=1.09=pl5321hdfd78af_5 + - perl-parent=0.236=pl5321hdfd78af_2 + - perl-pathtools=3.75=pl5321hec16e2b_3 + - perl-scalar-list-utils=1.62=pl5321hec16e2b_1 + - perl-socket=2.027=pl5321h031d066_4 + - perl-time-local=1.35=pl5321hdfd78af_0 + - perl-timedate=2.33=pl5321hdfd78af_2 + - perl-try-tiny=0.31=pl5321hdfd78af_1 + - perl-types-serialiser=1.01=pl5321hdfd78af_0 + - perl-uri=5.12=pl5321hdfd78af_0 + - perl-url-encode=0.03=pl5321h9ee0642_0 + - perl-www-robotrules=6.02=pl5321hdfd78af_4 + - perl-xsloader=0.24=pl5321hd8ed1ab_0 - pillow=9.0.1=py38h22f2fdc_0 - pip=22.0.4=pyhd8ed1ab_0 - pixman=0.40.0=h36c2ea0_0 @@ -214,6 +248,7 @@ dependencies: - pyjwt=2.6.0=pyhd8ed1ab_0 - pyopenssl=22.0.0=pyhd8ed1ab_0 - pyparsing=3.0.7=pyhd8ed1ab_0 + - pyrodigal=2.1.0=py38he5da3d1_3 - pysam=0.16.0.1=py38hbdc2ae9_1 - pysocks=1.7.1=py38h578d9bd_4 - python=3.8.12=hb7a2778_2_cpython @@ -296,7 +331,7 @@ dependencies: - scikit-learn=0.23.2=py38h5d63f67_3 - scipy=1.8.0=py38h56a6a73_1 - sed=4.8=he412f7d_0 - - seqkit=2.1.0=h9ee0642_0 + - seqkit=2.4.0=h9ee0642_0 - setuptools=60.9.3=py38h578d9bd_0 - skorch=0.9.0=pyh7b7c402_0 - snappy=1.1.9=hbd366e4_2 @@ -316,6 +351,7 @@ dependencies: - tk=8.6.12=h27826a3_0 - tktable=2.10=hb7b940f_3 - tqdm=4.54.1=pyhd8ed1ab_1 + - trnascan-se=2.0.12=pl5321h031d066_0 - tzdata=2021e=he74cb21_0 - tzlocal=4.1=py38h578d9bd_1 - unicodedata2=14.0.0=py38h497a2fe_0 diff --git a/install/environments/VEBA-binning-viral_env.yml b/install/environments/VEBA-binning-viral_env.yml index a4a182e..b4a46e4 100644 --- a/install/environments/VEBA-binning-viral_env.yml +++ b/install/environments/VEBA-binning-viral_env.yml @@ -1,4 +1,4 @@ -name: VEBA-binning-viral_env__v2023.5.15 +name: VEBA-binning-viral_env__v2023.7.7 channels: - conda-forge - bioconda @@ -27,12 +27,12 @@ dependencies: - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2022.12.7=ha878542_0 + - ca-certificates=2023.5.7=hbcca054_0 - cached-property=1.5.2=hd8ed1ab_1 - cached_property=1.5.2=pyha770c72_1 - cachetools=5.3.0=pyhd8ed1ab_0 - cairo=1.16.0=ha61ee94_1014 - - certifi=2022.12.7=pyhd8ed1ab_0 + - certifi=2023.5.7=pyhd8ed1ab_0 - cffi=1.15.1=py310h255011f_3 - charset-normalizer=2.1.1=pyhd8ed1ab_0 - checkv=1.0.1=pyhdfd78af_0 @@ -136,7 +136,7 @@ dependencies: - numba=0.56.4=py310ha5257ce_0 - numpy=1.23.5=py310h53a5b5f_0 - oauthlib=3.2.2=pyhd8ed1ab_0 - - openssl=3.0.8=h0b41bf4_0 + - openssl=3.1.1=hd590300_1 - opt_einsum=3.3.0=pyhd8ed1ab_1 - packaging=23.0=pyhd8ed1ab_0 - pandas=1.5.3=py310h9b08913_0 @@ -223,7 +223,7 @@ dependencies: - scikit-learn=1.2.2=py310h209a8ca_0 - scipy=1.10.1=py310h8deb116_0 - sed=4.8=he412f7d_0 - - seqkit=2.3.1=h9ee0642_0 + - seqkit=2.4.0=h9ee0642_0 - setuptools=67.6.0=pyhd8ed1ab_0 - six=1.16.0=pyh6c4a22f_0 - snappy=1.1.10=h9fff704_0 diff --git a/install/environments/VEBA-biosynthetic_env.yml b/install/environments/VEBA-biosynthetic_env.yml index 0146ec5..88b20a7 100644 --- a/install/environments/VEBA-biosynthetic_env.yml +++ b/install/environments/VEBA-biosynthetic_env.yml @@ -1,4 +1,4 @@ -name: VEBA-biosynthetic_env__v2023.5.15 +name: VEBA-biosynthetic_env__v2023.6.23 channels: - conda-forge - bioconda @@ -277,6 +277,4 @@ dependencies: - yaml=0.2.5=h7f98852_2 - zipp=3.11.0=pyhd8ed1ab_0 - zlib=1.2.13=h166bdaf_4 - - zstd=1.5.2=h6239696_4 - - pip: - - pysqlite3==0.5.0 + - zstd=1.5.2=h6239696_4 \ No newline at end of file diff --git a/install/environments/VEBA-classify_env.yml b/install/environments/VEBA-classify_env.yml index b8cb84c..694958e 100644 --- a/install/environments/VEBA-classify_env.yml +++ b/install/environments/VEBA-classify_env.yml @@ -1,4 +1,4 @@ -name: VEBA-classify_env__v2023.5.15 +name: VEBA-classify_env__v2023.6.14 channels: - conda-forge - bioconda @@ -27,12 +27,12 @@ dependencies: - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2022.12.7=ha878542_0 + - ca-certificates=2023.5.7=hbcca054_0 - cached-property=1.5.2=hd8ed1ab_1 - cached_property=1.5.2=pyha770c72_1 - cachetools=5.3.0=pyhd8ed1ab_0 - capnproto=0.10.2=h6239696_0 - - certifi=2022.12.7=pyhd8ed1ab_0 + - certifi=2023.5.7=pyhd8ed1ab_0 - cffi=1.15.1=py38h4a40e3a_3 - charset-normalizer=2.1.1=pyhd8ed1ab_0 - click=8.1.3=unix_pyhd8ed1ab_2 @@ -57,7 +57,7 @@ dependencies: - grpc-cpp=1.46.4=hbad87ad_7 - grpcio=1.46.4=py38h5b6373e_7 - gsl=2.7=he838d99_0 - - gtdbtk=2.2.3=pyhdfd78af_1 + - gtdbtk=2.3.0=pyhdfd78af_2 - h5py=3.8.0=nompi_py38h43830be_101 - hdf5=1.14.0=nompi_h5231ba7_102 - hmmer=3.3.2=h87f3376_2 @@ -112,7 +112,7 @@ dependencies: - numba=0.56.4=py38h9a4aae9_0 - numpy=1.23.5=py38h7042d01_0 - oauthlib=3.2.2=pyhd8ed1ab_0 - - openssl=1.1.1t=h0b41bf4_0 + - openssl=1.1.1u=hd590300_0 - opt_einsum=3.3.0=pyhd8ed1ab_1 - packaging=23.0=pyhd8ed1ab_0 - pandas=1.5.3=py38hdc8b05c_0 diff --git a/install/environments/VEBA-database_env.yml b/install/environments/VEBA-database_env.yml index 334bdbd..8e56e1c 100644 --- a/install/environments/VEBA-database_env.yml +++ b/install/environments/VEBA-database_env.yml @@ -1,4 +1,4 @@ -name: VEBA-database_env__v2023.5.15 +name: VEBA-database_env__v2023.6.20 channels: - conda-forge - bioconda @@ -6,125 +6,108 @@ channels: - defaults dependencies: - _libgcc_mutex=0.1=conda_forge - - _openmp_mutex=4.5=1_gnu - - alsa-lib=1.2.3=h516909a_0 - - boost-cpp=1.74.0=h312852a_4 - - brotlipy=0.7.0=py39hb9d737c_1004 + - _openmp_mutex=4.5=2_gnu + - aria2=1.36.0=h43d1f13_4 + - blast=2.14.0=h7d5a4b4_1 + - brotli=1.0.9=h166bdaf_8 + - brotli-bin=1.0.9=h166bdaf_8 - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - - ca-certificates=2022.12.7=ha878542_0 - - certifi=2022.12.7=pyhd8ed1ab_0 - - cffi=1.15.0=py39h4bc2ebd_0 - - charset-normalizer=2.0.12=pyhd8ed1ab_0 - - colorama=0.4.4=pyh9f0ad1d_0 + - c-ares=1.19.1=hd590300_0 + - ca-certificates=2023.5.7=hbcca054_0 + - certifi=2023.5.7=pyhd8ed1ab_0 + - charset-normalizer=3.1.0=pyhd8ed1ab_0 + - colorama=0.4.6=pyhd8ed1ab_0 - coreutils=9.3=h0b41bf4_0 - - cryptography=36.0.2=py39hd97740a_1 - - dbus=1.13.6=h5008d03_3 - - diamond=2.0.14=hb97b32f_1 - - ete3=3.1.2=pyh9f0ad1d_0 - - expat=2.4.8=h27087fc_0 - - fontconfig=2.14.0=h8e229c2_0 - - freetype=2.10.4=h0708190_1 + - curl=8.1.2=h409715c_0 + - diamond=2.1.7=h43eeafb_1 + - entrez-direct=16.2=he881be0_1 - gawk=5.1.0=h7f98852_0 - genopype=2023.5.15=py_0 - - gettext=0.19.8.1=h73d1719_1008 - - gst-plugins-base=1.18.5=hf529b03_3 - - gstreamer=1.18.5=h9f60fe5_3 - - icu=68.2=h9c3ff4c_0 - - idna=3.3=pyhd8ed1ab_0 - - jpeg=9e=h166bdaf_1 + - gettext=0.21.1=h27087fc_0 + - icu=72.1=hcb278e6_0 + - idna=3.4=pyhd8ed1ab_0 - keyutils=1.6.1=h166bdaf_0 - - krb5=1.19.3=h3790be6_0 - - ld_impl_linux-64=2.36.1=hea4e1c9_2 - - libblas=3.9.0=14_linux64_openblas - - libcblas=3.9.0=14_linux64_openblas - - libclang=11.1.0=default_ha53f305_1 + - krb5=1.20.1=h81ceb04_0 + - ld_impl_linux-64=2.40=h41732ed_0 + - libblas=3.9.0=17_linux64_openblas + - libbrotlicommon=1.0.9=h166bdaf_8 + - libbrotlidec=1.0.9=h166bdaf_8 + - libbrotlienc=1.0.9=h166bdaf_8 + - libcblas=3.9.0=17_linux64_openblas + - libcurl=8.1.2=h409715c_0 - libedit=3.1.20191231=he28a2e2_2 - - libevent=2.1.10=h9b69904_4 + - libev=4.33=h516909a_1 + - libexpat=2.5.0=hcb278e6_1 - libffi=3.4.2=h7f98852_5 - - libgcc-ng=11.2.0=h1d223b6_15 - - libgfortran-ng=11.2.0=h69a702a_15 - - libgfortran5=11.2.0=h5c6108e_15 - - libglib=2.70.2=h174f98d_4 - - libgomp=11.2.0=h1d223b6_15 - - libiconv=1.16=h516909a_0 - - libidn2=2.3.2=h7f98852_0 - - liblapack=3.9.0=14_linux64_openblas - - libllvm11=11.1.0=hf817b99_3 + - libgcc-ng=13.1.0=he5830b7_0 + - libgfortran-ng=13.1.0=h69a702a_0 + - libgfortran5=13.1.0=h15d22d2_0 + - libgomp=13.1.0=he5830b7_0 + - libiconv=1.17=h166bdaf_0 + - libidn2=2.3.4=h166bdaf_0 + - liblapack=3.9.0=17_linux64_openblas + - libnghttp2=1.52.0=h61bc06f_0 - libnsl=2.0.0=h7f98852_0 - - libogg=1.3.4=h7f98852_1 - - libopenblas=0.3.20=pthreads_h78a6416_0 - - libopus=1.3.1=h7f98852_1 - - libpng=1.6.37=h21135ba_2 - - libpq=13.5=hd57d9b9_1 - - libstdcxx-ng=11.2.0=he4da1e4_15 + - libopenblas=0.3.23=pthreads_h80387f5_0 + - libsqlite=3.42.0=h2797004_0 + - libssh2=1.11.0=h0841786_0 + - libstdcxx-ng=13.1.0=hfd8a6a1_0 - libunistring=0.9.10=h7f98852_0 - - libuuid=2.32.1=h7f98852_1000 - - libvorbis=1.3.7=h9c3ff4c_0 - - libxcb=1.13=h7f98852_1004 - - libxkbcommon=1.0.3=he3ba5ed_0 - - libxml2=2.9.10=h72842e0_4 - - libxslt=1.1.33=h15afd5d_2 - - libzlib=1.2.11=h166bdaf_1014 - - lxml=4.8.0=py39hb9d737c_3 - - lz4-c=1.9.3=h9c3ff4c_1 - - mmseqs2=13.45111=pl5321hf1761c0_2 - - mysql-common=8.0.28=haf5c9bc_4 - - mysql-libs=8.0.28=h28c427c_4 - - ncurses=6.3=h27087fc_1 - - nspr=4.32=h9c3ff4c_1 - - nss=3.77=h2350873_0 - - numpy=1.22.3=py39h18676bf_2 - - openssl=1.1.1n=h166bdaf_0 - - pandas=1.4.2=py39h1832856_1 - - pathlib2=2.3.7.post1=py39hf3d152e_1 + - libuuid=2.38.1=h0b41bf4_0 + - libxml2=2.11.4=h0d562d8_0 + - libzlib=1.2.13=hd590300_5 + - mmseqs2=14.7e284=pl5321h6a68c12_2 + - ncurses=6.4=hcb278e6_0 + - numpy=1.25.0=py311h64a7726_0 + - openssl=3.1.1=hd590300_1 + - pandas=2.0.2=py311h320fe9a_0 + - pathlib2=2.3.7.post1=py311h38be061_2 - pcre=8.45=h9c3ff4c_0 - perl=5.32.1=2_h7f98852_perl5 - - pip=22.0.4=pyhd8ed1ab_0 - - pthread-stubs=0.4=h36c2ea0_1001 - - pycparser=2.21=pyhd8ed1ab_0 - - pyopenssl=22.0.0=pyhd8ed1ab_0 - - pyqt=5.12.3=py39hf3d152e_8 - - pyqt-impl=5.12.3=py39hde8b62d_8 - - pyqt5-sip=4.19.18=py39he80948d_8 - - pyqtchart=5.12=py39h0fcd23e_8 - - pyqtwebengine=5.12.1=py39h0fcd23e_8 - - pysocks=1.7.1=py39hf3d152e_5 - - python=3.9.12=h9a8a25e_1_cpython + - perl-archive-tar=2.40=pl5321hdfd78af_0 + - perl-carp=1.50=pl5321hd8ed1ab_0 + - perl-common-sense=3.75=pl5321hd8ed1ab_0 + - perl-compress-raw-bzip2=2.201=pl5321h166bdaf_0 + - perl-compress-raw-zlib=2.202=pl5321h166bdaf_0 + - perl-encode=3.19=pl5321h166bdaf_0 + - perl-exporter=5.74=pl5321hd8ed1ab_0 + - perl-exporter-tiny=1.002002=pl5321hd8ed1ab_0 + - perl-extutils-makemaker=7.70=pl5321hd8ed1ab_0 + - perl-io-compress=2.201=pl5321hdbdd923_2 + - perl-io-zlib=1.14=pl5321hdfd78af_0 + - perl-json=4.10=pl5321hdfd78af_0 + - perl-json-xs=2.34=pl5321h4ac6f70_6 + - perl-list-moreutils=0.430=pl5321hdfd78af_0 + - perl-list-moreutils-xs=0.430=pl5321h031d066_2 + - perl-parent=0.241=pl5321hd8ed1ab_0 + - perl-pathtools=3.75=pl5321h166bdaf_0 + - perl-scalar-list-utils=1.63=pl5321h166bdaf_0 + - perl-storable=3.15=pl5321h166bdaf_0 + - perl-types-serialiser=1.01=pl5321hdfd78af_0 + - pip=23.1.2=pyhd8ed1ab_0 + - pysocks=1.7.1=pyha2e5f31_6 + - python=3.11.4=hab00c5b_0_cpython - python-dateutil=2.8.2=pyhd8ed1ab_0 - - python-tzdata=2022.1=pyhd8ed1ab_0 - - python_abi=3.9=2_cp39 - - pytz=2022.1=pyhd8ed1ab_0 - - qt=5.12.9=hda022c4_4 - - readline=8.1=h46c0cb4_0 - - requests=2.27.1=pyhd8ed1ab_0 - - scandir=1.10.0=py39hb9d737c_5 - - scipy=1.8.0=py39hee8e79c_1 - - seqkit=2.3.1=h9ee0642_0 - - setuptools=62.1.0=py39hf3d152e_0 + - python-tzdata=2023.3=pyhd8ed1ab_0 + - python_abi=3.11=3_cp311 + - pytz=2023.3=pyhd8ed1ab_0 + - readline=8.2=h8228510_1 + - requests=2.31.0=pyhd8ed1ab_0 + - scandir=1.10.0=py311hd4cff14_6 + - seqkit=2.4.0=h9ee0642_0 + - setuptools=67.7.2=pyhd8ed1ab_0 - six=1.16.0=pyh6c4a22f_0 - soothsayer_utils=2022.6.24=py_0 - - sqlite=3.38.2=h4ff8645_0 - - tar=1.34=ha1f6473_0 + - tar=1.34=hb2e2bae_1 - tk=8.6.12=h27826a3_0 - - tqdm=4.64.0=pyhd8ed1ab_0 - - tzdata=2022a=h191b570_0 - - tzlocal=3.0=py39hf3d152e_2 + - tqdm=4.65.0=pyhd8ed1ab_1 + - tzdata=2023c=h71feb2d_0 + - tzlocal=5.0.1=py311h38be061_0 - unzip=6.0=h7f98852_3 - - urllib3=1.26.9=pyhd8ed1ab_0 - - wget=1.20.3=ha56f1ee_1 - - wheel=0.37.1=pyhd8ed1ab_0 - - xorg-kbproto=1.0.7=h7f98852_1002 - - xorg-libice=1.0.10=h7f98852_0 - - xorg-libsm=1.2.3=hd9c2040_1000 - - xorg-libx11=1.7.2=h7f98852_0 - - xorg-libxau=1.0.9=h7f98852_0 - - xorg-libxdmcp=1.1.3=h7f98852_0 - - xorg-libxext=1.3.4=h7f98852_1 - - xorg-libxrender=0.9.10=h7f98852_1003 - - xorg-renderproto=0.11.1=h7f98852_1002 - - xorg-xextproto=7.3.0=h7f98852_1002 - - xorg-xproto=7.0.31=h7f98852_1007 - - xz=5.2.5=h516909a_1 - - zlib=1.2.11=h166bdaf_1014 - - zstd=1.5.2=ha95c52a_0 \ No newline at end of file + - urllib3=2.0.3=pyhd8ed1ab_0 + - wget=1.20.3=ha35d2d1_1 + - wheel=0.40.0=pyhd8ed1ab_0 + - xz=5.2.6=h166bdaf_0 + - zlib=1.2.13=hd590300_5 + - zstd=1.5.2=h3eb15da_6 \ No newline at end of file diff --git a/install/update_environment_variables.sh b/install/update_environment_variables.sh index 08373c8..84acb26 100644 --- a/install/update_environment_variables.sh +++ b/install/update_environment_variables.sh @@ -1,5 +1,5 @@ #!/bin/bash -# __VERSION__ = "2023.3.1" +# __VERSION__ = "2023.6.14" # Create database DATABASE_DIRECTORY=${1:-"."} @@ -29,13 +29,13 @@ for ENV_NAME in VEBA-binning-prokaryotic_env; do echo "unset CHECKM2DB" >> ${ENV_PREFIX}/etc/conda/deactivate.d/veba.sh done -#GTDB-Tk +#GTDB echo ". .. ... ..... ........ ............." -echo "xiv * Adding the following environment variable to VEBA environments: export GTDBTK_DATA_PATH=${REALPATH_DATABASE_DIRECTORY}/Classify/GTDBTk/" +echo "xiv * Adding the following environment variable to VEBA environments: export GTDBTK_DATA_PATH=${REALPATH_DATABASE_DIRECTORY}/Classify/GTDB/" for ENV_NAME in VEBA-classify_env; do ENV_PREFIX=${CONDA_BASE}/envs/${ENV_NAME} - # GTDB-Tk - echo "export GTDBTK_DATA_PATH=${REALPATH_DATABASE_DIRECTORY}/Classify/GTDBTk/" >> ${ENV_PREFIX}/etc/conda/activate.d/veba.sh + # GTDB + echo "export GTDBTK_DATA_PATH=${REALPATH_DATABASE_DIRECTORY}/Classify/GTDB/" >> ${ENV_PREFIX}/etc/conda/activate.d/veba.sh echo "unset GTDBTK_DATA_PATH" >> ${ENV_PREFIX}/etc/conda/deactivate.d/veba.sh done diff --git a/src/binning-eukaryotic.py b/src/binning-eukaryotic.py index 74476b4..000c3d5 100755 --- a/src/binning-eukaryotic.py +++ b/src/binning-eukaryotic.py @@ -13,7 +13,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.20" +__version__ = "2023.7.6" # DATABASE_METAEUK="/usr/local/scratch/CORE/jespinoz/db/veba/v1.0/Classify/Eukaryotic/eukaryotic" @@ -128,15 +128,11 @@ def get_binning_cmd( input_filepaths, output_filepaths, output_directory, direct "--logit_transform {}".format(opts.logit_transform), # Move all (this is a hack) - "&&", + "&&", "mv {} {}".format(os.path.join(output_directory, "bins", "*.fa"), os.path.join(output_directory, "bins", "non-eukaryota")), - # "for FP in %s; do BN=$(basename ${FP} .fa); mv $FP %s/%s${BN}.fa; done"%( - # os.path.join(output_directory, "bins", "*.fa"), - # os.path.join(output_directory, "bins", "non-eukaryota"), - # prefix, - # ), + # Move just Eukaryota back "&&", "for ID_GENOME in $(cat %s); do mv %s/${ID_GENOME}.* %s; done"%( @@ -144,16 +140,18 @@ def get_binning_cmd( input_filepaths, output_filepaths, output_directory, direct os.path.join(output_directory, "bins", "non-eukaryota"), os.path.join(output_directory, "bins"), ), - # Non-Eukaryotic scaffolds - # "&&", - # "> {}".format(os.path.join(directories["tmp"], "non-eukaryota.scaffolds.fasta")), # Create empty file - "&&", + # Get scaffolds from non-eukaryotic bins + + "&&", + "(for FP in {}; do cat $FP >> {}; done) 2> /dev/null || > {}".format( # Handle edge cases where there aren't any non-eukaryota bins os.path.join(output_directory, "bins", "non-eukaryota", "*.fa"), os.path.join(directories["tmp"], "non-eukaryota.scaffolds.fasta"), os.path.join(directories["tmp"], "non-eukaryota.scaffolds.fasta"), - ), - "&&", + ), + + "&&", + "cat", os.path.join(directories["tmp"], "non-eukaryota.scaffolds.fasta"), "|", @@ -165,7 +163,8 @@ def get_binning_cmd( input_filepaths, output_filepaths, output_directory, direct os.path.join(output_directory, "non-eukaryota.scaffolds.list"), # Remove non-eukaryotic scaffolds - "&&", + "&&", + "cat", os.path.join(os.path.join(directories["tmp"], "scaffolds_to_bins.tsv")), "|", @@ -180,7 +179,8 @@ def get_binning_cmd( input_filepaths, output_filepaths, output_directory, direct "[ -s {} ] || (echo 'No eukaryotic bins' && exit 1)".format(os.path.join(output_directory, "scaffolds_to_bins.tsv")), # Save non-eukaryotic scaffolds separately - "&&", + "&&", + "cat", os.path.join(os.path.join(directories["tmp"], "scaffolds_to_bins.tsv")), "|", @@ -192,14 +192,17 @@ def get_binning_cmd( input_filepaths, output_filepaths, output_directory, direct # "true", ">", os.path.join(output_directory, "non-eukaryota.scaffolds_to_bins.tsv"), # Make empty file - "&&", + + "&&", + "cut", "-f1", os.path.join(output_directory, "scaffolds_to_bins.tsv"), ">", os.path.join(output_directory, "binned.list"), - "&&", + "&&", + # Unique bins "cut -f2", os.path.join(output_directory, "scaffolds_to_bins.tsv"), @@ -208,7 +211,8 @@ def get_binning_cmd( input_filepaths, output_filepaths, output_directory, direct "-u", ">", os.path.join(output_directory, "bins.list"), - "&&", + + "&&", "(", "rm -rf {} {} {} {} {}".format( @@ -286,12 +290,12 @@ def get_binning_cmd( input_filepaths, output_filepaths, output_directory, direct # ] # return cmd -def get_metaeuk_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): +def get_eukaryotic_gene_modeling_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): cmd = [ # Get the eukaryotic contigs "cat", - opts.fasta, + opts.fasta, #input_filepaths[0] "|", os.environ["seqkit"], "seq", @@ -303,32 +307,66 @@ def get_metaeuk_cmd(input_filepaths, output_filepaths, output_directory, directo ">", os.path.join(directories["tmp"], "scaffolds.binned.eukaryotic.fasta"), # contigs - # Run MetaEuk + # Run Eukaryotic Gene Modeling "&&", - os.environ["metaeuk_wrapper.py"], - "--metaeuk_database {}".format(opts.metaeuk_database), - "--fasta {}".format(os.path.join(directories["tmp"], "scaffolds.binned.eukaryotic.fasta")), - "--scaffolds_to_bins {}".format(os.path.join(directories[("intermediate", "1__binning_{}".format(opts.algorithm))], "scaffolds_to_bins.tsv")), - "-o {}".format(output_directory), - "--n_jobs {}".format(opts.n_jobs), - "--metaeuk_sensitivity {}".format(opts.metaeuk_sensitivity), - "--metaeuk_evalue {}".format(opts.metaeuk_evalue), + os.environ["eukaryotic_gene_modeling_wrapper.py"], + "--fasta {}".format(os.path.join(directories["tmp"], "scaffolds.binned.eukaryotic.fasta")), + "--scaffolds_to_bins {}".format(input_filepaths[1]), + "--tiara_results {}".format(input_filepaths[2]), + "--metaeuk_database {}".format(opts.metaeuk_database), + "-o {}".format(output_directory), + "-p {}".format(opts.n_jobs), + + # MetaEuk + "--metaeuk_sensitivity {}".format(opts.metaeuk_sensitivity), + "--metaeuk_evalue {}".format(opts.metaeuk_evalue), + + # Pyrodigal + "--pyrodigal_minimum_gene_length {}".format(opts.pyrodigal_minimum_gene_length), + "--pyrodigal_minimum_edge_gene_length {}".format(opts.pyrodigal_minimum_edge_gene_length), + "--pyrodigal_maximum_gene_overlap_length {}".format(opts.pyrodigal_maximum_gene_overlap_length), + "--pyrodigal_mitochondrial_genetic_code {}".format(opts.pyrodigal_mitochondrial_genetic_code), + "--pyrodigal_plastid_genetic_code {}".format(opts.pyrodigal_plastid_genetic_code), + + # BARRNAP + "--barrnap_length_cutoff {}".format(opts.barrnap_length_cutoff), + "--barrnap_reject {}".format(opts.barrnap_reject), + "--barrnap_evalue {}".format(opts.barrnap_evalue), + + # tRNAscan-SE + "--trnascan_mitochondrial_searchmode='{}'".format(opts.trnascan_mitochondrial_searchmode), + "--trnascan_plastid_searchmode='{}'".format(opts.trnascan_plastid_searchmode), ] if opts.metaeuk_options: - cmd += ["--metaeuk_options {}".format(opts.metaeuk_options)] + cmd += [ + "--metaeuk_options {}".format(opts.metaeuk_options), + ] + if opts.trnascan_nuclear_options: + cmd += [ + "--trnascan_nuclear_options {}".format(opts.trnascan_nuclear_options), + ] + if opts.trnascan_mitochondrial_options: + cmd += [ + "--trnascan_mitochondrial_options {}".format(opts.trnascan_mitochondrial_options), + ] + if opts.trnascan_plastid_options: + cmd += [ + "--trnascan_plastid_options {}".format(opts.trnascan_plastid_options), + ] return cmd -# def get_busco_offline_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): def get_busco_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): cmd = [ """ # Create busco output directory -rm -rf %s -mkdir -p %s +BUSCO_OUTPUT_DIRECTORY=%s +TMP_BUSCO_DIRECTORY=%s +rm -rf $BUSCO_OUTPUT_DIRECTORY +mkdir -p $BUSCO_OUTPUT_DIRECTORY # Iterate through protein fasta from MetaEuk for FP in %s; @@ -337,49 +375,49 @@ def get_busco_cmd(input_filepaths, output_filepaths, output_directory, directori do ID_GENOME=$(basename $FP .faa); # Create MAG-specific subdirectory within busco output - OUT_DIR=%s/${ID_GENOME} - mkdir -p ${OUT_DIR} + OUT_DIR=$BUSCO_OUTPUT_DIRECTORY/$ID_GENOME + mkdir -p $OUT_DIR echo $FP echo $ID_GENOME echo $OUT_DIR # BUSCO Command - %s --force -i $FP -o $OUT_DIR -m protein --auto-lineage-euk -c %d --evalue %f --download_path %s + %s --force -i $FP -o $OUT_DIR -m protein --auto-lineage-euk -c %d --evalue %f --download_path $TMP_BUSCO_DIRECTORY # Remove big intermediate files echo "Removing run_*. .. ... ..... ........" - rm -rf ${OUT_DIR}/run_* + rm -rf $OUT_DIR/run_* echo "Removing auto_lineage. .. ... ..... ........" - rm -rf ${OUT_DIR}/auto_lineage + rm -rf $OUT_DIR/auto_lineage # End for-loop done # Removing temporary busco files -rm -rf %s +rm -rf $TMP_BUSCO_DIRECTORY/* """%( # Args os.path.join(output_directory, "busco_output"), - os.path.join(output_directory, "busco_output"), - os.path.join(directories[("intermediate", "2__metaeuk")], "genomes", "*.faa"), - os.path.join(output_directory, "busco_output"), + os.path.join(directories["tmp"],"busco"), + os.path.join(directories[("intermediate", "2__eukaryotic_gene_modeling")], "output", "*.faa"), os.environ["busco"], opts.n_jobs, opts.busco_evalue, - os.path.join(directories["tmp"],"busco"), - os.path.join(directories["tmp"],"busco", "*"), ), + os.environ["merge_busco_json.py"], "-i {}".format(os.path.join(output_directory, "busco_output")), "-j {}".format(os.path.join(output_directory, "busco_results.json")), "-o {}".format(os.path.join(output_directory, "busco_results.tsv")), - "&&", + + "&&", + os.environ["filter_busco_results.py"], "-i {}".format(os.path.join(output_directory, "busco_results.tsv")), - "-g {}".format(directories[("intermediate", "2__metaeuk")]), + "-g {}".format(os.path.join(directories[("intermediate", "2__eukaryotic_gene_modeling")], "output")), "-o {}".format(os.path.join(output_directory, "filtered")), "-m {}".format(opts.minimum_contig_length), "-f {}".format(opts.fasta), @@ -390,66 +428,13 @@ def get_busco_cmd(input_filepaths, output_filepaths, output_directory, directori return cmd -# barrnap -def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): - cmd = [ - - -""" - - for GENOME_FASTA in {}; - do - ID = $(basename $GENOME_FASTA .fa) - {} --kingdom euk --threads {} --lencutoff {} --reject {} --evalue {} --outseq {} $GENOME_FASTA > {} - rm $GENOME_FASTA.fai - done - -""".format( - os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes", "*.fa"), - os.environ["barrnap"], - opts.n_jobs, - opts.barrnap_length_cutoff, - opts.barrnap_reject, - opts.barrnap_evalue, - os.path.join(output_directory, "$ID.rRNA.fasta"), - os.path.join(output_directory, "$ID.rRNA.gff"), - ), - ] - return cmd - -# tRNAscan-SE -def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): - cmd = [ - -""" - - for GENOME_FASTA in {}; - do - ID = $(basename $GENOME_FASTA .fa) - {} -E --forceow --progress --threads {} --fasta {} --gff {} --struct {} {} $GENOME_FASTA > {} - - done - -""".format( - os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes", "*.fa"), - os.environ["tRNAscan-SE"], - opts.n_jobs, - os.path.join(output_directory, "$ID.tRNA.fasta"), - os.path.join(output_directory, "$ID.tRNA.gff"), - os.path.join(output_directory, "$ID.tRNA.struct"), - opts.trnascan_options, - os.path.join(output_directory, "$ID.tRNA.txt"), - ), - ] - return cmd - def get_featurecounts_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): # ORF-Level Counts cmd = [ "cat", - os.path.join(directories[("intermediate", "2__metaeuk")], "genomes", "*.gff"), + os.path.join(directories[("intermediate", "3__busco")], "filtered","genomes","*.gff"), ">", os.path.join(directories["tmp"], "gene_models.eukaryotic.gff"), "&&", @@ -486,37 +471,31 @@ def get_output_cmd(input_filepaths, output_filepaths, output_directory, director cmd += [ "DST={}; (for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( output_directory, - " ".join(input_filepaths[:-3]), + " ".join(input_filepaths), ) ] - cmd += [ - "DST={}; (for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( - os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes"), - " ".join(input_filepaths[-3:]), - ) - ] - - cmd += [ - "&&", - # Statistics - "(", - os.environ["seqkit"], - "stats", - "-a", - "-b", - "-T", - "-j {}".format(opts.n_jobs), - os.path.join(output_directory, "genomes", "*.fa"), - - "|", - - """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x[:-3]); df.to_csv(sys.stdout, sep="\t")'""" - ">", - os.path.join(output_directory,"genome_statistics.tsv"), - ")", - ] + # cmd += [ + # "&&", + + # # Statistics + # "(", + # os.environ["seqkit"], + # "stats", + # "-a", + # "-b", + # "-T", + # "-j {}".format(opts.n_jobs), + # os.path.join(output_directory, "genomes", "*.fa"), + + # "|", + + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x[:-3]); df.to_csv(sys.stdout, sep="\t")'""" + # ">", + # os.path.join(output_directory,"genome_statistics.tsv"), + # ")", + # ] @@ -616,6 +595,8 @@ def create_pipeline(opts, directories, f_cmds): # i/o output_filepaths = [ os.path.join(output_directory, "scaffolds_to_bins.tsv"), + os.path.join(output_directory, "scaffolds_to_bins.tsv"), + ] @@ -650,11 +631,11 @@ def create_pipeline(opts, directories, f_cmds): steps[program] = step # ============= - # MetaEuk + # Eukaryotic gene modeling # ============= step = 2 - program = "metaeuk" + program = "eukaryotic_gene_modeling" program_label = "{}__{}".format(step, program) # Add to directories @@ -668,15 +649,16 @@ def create_pipeline(opts, directories, f_cmds): # os.path.join(directories[("intermediate", "1__binning_{}".format(opts.algorithm))], "bins"), opts.fasta, os.path.join(directories[("intermediate", "1__binning_{}".format(opts.algorithm))], "scaffolds_to_bins.tsv"), + os.path.join(directories[("intermediate", "1__binning_{}".format(opts.algorithm))], "consensus_domain_classification", "tiara_output.tsv"), ] output_filenames = [ - "genomes/*.fa", - "genomes/*.faa", - "genomes/*.gff", - "genomes/*.ffn", - "identifier_mapping.metaeuk.tsv", - "metaeuk.headersMap.tsv", + "output/*.fa", + "output/*.faa", + "output/*.gff", + "output/*.ffn", + "output/identifier_mapping.metaeuk.tsv", + # "metaeuk.headersMap.tsv", ] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) @@ -689,7 +671,7 @@ def create_pipeline(opts, directories, f_cmds): "directories":directories, } - cmd = get_metaeuk_cmd(**params) + cmd = get_eukaryotic_gene_modeling_cmd(**params) pipeline.add_step( id=program_label, @@ -723,7 +705,7 @@ def create_pipeline(opts, directories, f_cmds): input_filepaths = [ - os.path.join(directories[("intermediate", "2__metaeuk")], "genomes", "*.faa"), + os.path.join(directories[("intermediate", "2__eukaryotic_gene_modeling")], "output", "*.faa"), ] output_filenames = [ @@ -762,116 +744,12 @@ def create_pipeline(opts, directories, f_cmds): ) - steps[program] = step - - # ========== - # barrnap - # ========== - step = 4 - - program = "barrnap" - program_label = "{}__{}".format(step, program) - # Add to directories - output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) - - - # Info - description = "Detecting rRNA genes" - # i/o - input_filepaths = [ - os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes", "*.fa"), - ] - - output_filenames = [ - "*.rRNA.fasta", - "*.rRNA.gff", - ] - output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_barrnap_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=False, - validate_outputs=False, - errors_ok=False, - acceptable_returncodes={0}, - log_prefix=program_label, - # acceptable_returncodes= {0,1}, - - ) - - - steps[program] = step - - # ========== - # tRNASCAN-se - # ========== - step = 5 - - program = "trnascan-se" - program_label = "{}__{}".format(step, program) - # Add to directories - output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) - - - # Info - description = "Detecting tRNA genes" - # i/o - input_filepaths = [ - os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes", "*.fa"), - ] - - output_filenames = [ - "*.tRNA.fasta", - "*.tRNA.gff", - ] - output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) - - params = { - "input_filepaths":input_filepaths, - "output_filepaths":output_filepaths, - "output_directory":output_directory, - "opts":opts, - "directories":directories, - } - - cmd = get_trnascan_cmd(**params) - pipeline.add_step( - id=program_label, - description = description, - step=step, - cmd=cmd, - input_filepaths = input_filepaths, - output_filepaths = output_filepaths, - validate_inputs=False, - validate_outputs=False, - errors_ok=False, - acceptable_returncodes={0}, - log_prefix=program_label, - # acceptable_returncodes= {0,1}, - - ) - - steps[program] = step # ========== # featureCounts # ========== - step = 6 + step = 4 # Info program = "featurecounts" @@ -884,7 +762,7 @@ def create_pipeline(opts, directories, f_cmds): # i/o input_filepaths = [ opts.fasta, - os.path.join(directories[("intermediate", "2__metaeuk")], "genomes", "*.gff"), + os.path.join(directories[("intermediate", "3__busco")],"filtered", "genomes", "*.gff"), *opts.bam, ] @@ -918,7 +796,7 @@ def create_pipeline(opts, directories, f_cmds): # ============= # Output # ============= - step = 7 + step = 5 program = "output" program_label = "{}__{}".format(step, program) @@ -932,6 +810,10 @@ def create_pipeline(opts, directories, f_cmds): # BUSCO os.path.join(directories[("intermediate", "3__busco")], "filtered", "identifier_mapping.metaeuk.tsv"), + os.path.join(directories[("intermediate", "3__busco")], "filtered", "genome_statistics.tsv"), + os.path.join(directories[("intermediate", "3__busco")], "filtered", "gene_statistics.cds.tsv"), + os.path.join(directories[("intermediate", "3__busco")], "filtered", "gene_statistics.rRNA.tsv"), + os.path.join(directories[("intermediate", "3__busco")], "filtered", "gene_statistics.tRNA.tsv"), os.path.join(directories[("intermediate", "3__busco")], "filtered", "busco_results.filtered.tsv"), os.path.join(directories[("intermediate", "3__busco")], "filtered", "scaffolds_to_bins.tsv"), os.path.join(directories[("intermediate", "3__busco")], "filtered", "bins.list"), @@ -941,14 +823,8 @@ def create_pipeline(opts, directories, f_cmds): os.path.join(directories[("intermediate", "3__busco")], "filtered", "genomes"), # featureCounts - os.path.join(directories[("intermediate", "6__featurecounts")], "featurecounts.orfs.tsv.gz"), - - # barrnap - os.path.join(directories[("intermediate", "4__barrnap")], "*.rRNA.*"), + os.path.join(directories[("intermediate", "4__featurecounts")], "featurecounts.orfs.tsv.gz"), - # tRNAscan-SE - os.path.join(directories[("intermediate", "5__trnascan-se")], "*.tRNA.fasta"), - os.path.join(directories[("intermediate", "5__trnascan-se")], "*.tRNA.gff"), ] @@ -1011,7 +887,8 @@ def add_executables_to_environment(opts): "consensus_domain_classification.py", "merge_busco_json.py", "filter_busco_results.py", - "metaeuk_wrapper.py", + # "metaeuk_wrapper.py", + "eukaryotic_gene_modeling_wrapper.py", } required_executables={ @@ -1097,8 +974,6 @@ def main(args=None): # Pipeline parser_io = parser.add_argument_group('I/O arguments') - parser_io.add_argument("-t","--tiara_results", type=str, required=True, help = "path/to/scaffolds.fasta") - parser_io.add_argument("-f","--fasta", type=str, required=True, help = "path/to/scaffolds.fasta") parser_io.add_argument("-b","--bam", type=str, nargs="+", required=True, help = "path/to/mapped.sorted.bam files separated by spaces. ") parser_io.add_argument("-n", "--name", type=str, help="Name of sample", required=True) @@ -1142,7 +1017,6 @@ def main(args=None): parser_metaeuk = parser.add_argument_group('MetaEuk arguments') parser_metaeuk.add_argument("--metaeuk_sensitivity", type=float, default=4.0, help="MetaEuk | Sensitivity: 1.0 faster; 4.0 fast; 7.5 sensitive [Default: 4.0]") parser_metaeuk.add_argument("--metaeuk_evalue", type=float, default=0.01, help="MetaEuk | List matches below this E-value (range 0.0-inf) [Default: 0.01]") - # parser_metaeuk.add_argument("--metaeuk_database", type=str, default=DATABASE_METAEUK, help="MetaEuk | More options (e.g. --arg 1 ) [Default: {}]".format(DATABASE_METAEUK)) parser_metaeuk.add_argument("--metaeuk_options", type=str, default="", help="MetaEuk | More options (e.g. --arg 1 ) [Default: ''] https://github.com/soedinglab/metaeuk") # --split-memory-limit 70G: https://github.com/soedinglab/metaeuk/issues/59 @@ -1151,8 +1025,8 @@ def main(args=None): parser_pyrodigal.add_argument("--pyrodigal_minimum_gene_length", type=int, default=90, help="Pyrodigal | Minimum gene length [Default: 90]") parser_pyrodigal.add_argument("--pyrodigal_minimum_edge_gene_length", type=int, default=60, help="Pyrodigal | Minimum edge gene length [Default: 60]") parser_pyrodigal.add_argument("--pyrodigal_maximum_gene_overlap_length", type=int, default=60, help="Pyrodigal | Maximum gene overlap length [Default: 60]") - parser_pyrodigal.add_argument("--pyrodigal_mitochondrial_genetic_code", type=int, default=3, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 3] (The Yeast Mitochondrial Code))") - parser_pyrodigal.add_argument("--pyrodigal_plastid_genetic_code", type=int, default=3, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 3] (The Yeast Mitochondrial Code))") + parser_pyrodigal.add_argument("--pyrodigal_mitochondrial_genetic_code", type=int, default=4, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 4] (The Mold, Protozoan, and Coelenterate Mitochondrial Code and the Mycoplasma/Spiroplasma Code))") + parser_pyrodigal.add_argument("--pyrodigal_plastid_genetic_code", type=int, default=11, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 11] (The Bacterial, Archaeal and Plant Plastid Code))") # BUSCO parser_busco = parser.add_argument_group('BUSCO arguments') @@ -1170,7 +1044,7 @@ def main(args=None): # tRNA parser_trnascan = parser.add_argument_group('tRNAscan-SE arguments') parser_trnascan.add_argument("--trnascan_nuclear_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") - parser_trnascan.add_argument("--trnascan_mitochondrial_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_mitochondrial_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | Current best option according to developer: https://github.com/UCSC-LoweLab/tRNAscan-SE/issues/24") parser_trnascan.add_argument("--trnascan_mitochondrial_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") parser_trnascan.add_argument("--trnascan_plastid_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | https://github.com/UCSC-LoweLab/tRNAscan-SE") parser_trnascan.add_argument("--trnascan_plastid_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") @@ -1180,7 +1054,6 @@ def main(args=None): parser_featurecounts.add_argument("--long_reads", action="store_true", help="featureCounts | Use this if long reads are being used") parser_featurecounts.add_argument("--featurecounts_options", type=str, default="", help="featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/") - # Options opts = parser.parse_args() diff --git a/src/binning-prokaryotic.py b/src/binning-prokaryotic.py index 560ed3a..c88805b 100755 --- a/src/binning-prokaryotic.py +++ b/src/binning-prokaryotic.py @@ -12,7 +12,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.20" +__version__ = "2023.7.7" # Assembly def get_coverage_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -229,9 +229,11 @@ def get_dastool_cmd(input_filepaths, output_filepaths, output_directory, directo input_filepaths[2], input_filepaths[3], ), - "&&", + "&&", + 'IFS=" " read -r -a S2B_ARRAY <<< "$S2B"', - "&&", + + "&&", # "echo ${S2B_ARRAY[0]} ${S2B_ARRAY[1]}", os.environ["DAS_Tool"], @@ -411,7 +413,7 @@ def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directo os.path.join(directories["tmp"], "genomes_to_domain.tsv"), """ - +OUTPUT_DIRECTORY={} FP={} for DOMAIN in $(cut -f2 $FP | sort -u); do @@ -422,7 +424,9 @@ def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directo do GENOME_FASTA=$(ls {}) || GENOME_FASTA="" if [ -e "$GENOME_FASTA" ]; then - {} --kingdom $DOMAIN_ABBREVIATION --threads {} --lencutoff {} --reject {} --evalue {} --outseq {} $GENOME_FASTA > {} + >$OUTPUT_DIRECTORY/$ID.rRNA + >$OUTPUT_DIRECTORY/$ID.rRNA.gff + {} --kingdom $DOMAIN_ABBREVIATION --threads {} --lencutoff {} --reject {} --evalue {} --outseq $OUTPUT_DIRECTORY/$ID.rRNA $GENOME_FASTA | {} > $OUTPUT_DIRECTORY/$ID.rRNA.gff rm $GENOME_FASTA.fai fi done @@ -430,6 +434,7 @@ def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directo rm -f {} """.format( + output_directory, os.path.join(directories["tmp"], "genomes_to_domain.tsv"), os.path.join(os.path.split(input_filepaths[1])[0],"$ID.fa"), os.environ["barrnap"], @@ -437,8 +442,7 @@ def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directo opts.barrnap_length_cutoff, opts.barrnap_reject, opts.barrnap_evalue, - os.path.join(output_directory, "$ID.rRNA.fasta"), - os.path.join(output_directory, "$ID.rRNA.gff"), + os.environ["append_geneid_to_barrnap_gff.py"], os.path.join(directories["tmp"], "genomes_to_domain.tsv"), ), ] @@ -454,7 +458,7 @@ def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, direct """ - +OUTPUT_DIRECTORY={} FP={} for DOMAIN in $(cut -f2 $FP | sort -u); do @@ -465,22 +469,23 @@ def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, direct do GENOME_FASTA=$(ls {}) || GENOME_FASTA="" if [ -e "$GENOME_FASTA" ]; then - {} -$DOMAIN_ABBREVIATION --forceow --progress --threads {} --fasta {} --gff {} --struct {} {} $GENOME_FASTA > {} + >$OUTPUT_DIRECTORY/$ID.tRNA + >$OUTPUT_DIRECTORY/$ID.tRNA.gff + >$OUTPUT_DIRECTORY/$ID.tRNA.struct + >$OUTPUT_DIRECTORY/$ID.tRNA.txt + {} -$DOMAIN_ABBREVIATION --forceow --progress --threads {} --fasta $OUTPUT_DIRECTORY/$ID.tRNA --gff $OUTPUT_DIRECTORY/$ID.tRNA.gff --struct $OUTPUT_DIRECTORY/$ID.tRNA.struct {} $GENOME_FASTA > $OUTPUT_DIRECTORY/$ID.tRNA.txt fi done done rm -f {} """.format( + output_directory, os.path.join(directories["tmp"], "genomes_to_domain.tsv"), os.path.join(os.path.split(input_filepaths[1])[0],"$ID.fa"), os.environ["tRNAscan-SE"], opts.n_jobs, - os.path.join(output_directory, "$ID.tRNA.fasta"), - os.path.join(output_directory, "$ID.tRNA.gff"), - os.path.join(output_directory, "$ID.tRNA.struct"), opts.trnascan_options, - os.path.join(output_directory, "$ID.tRNA.txt"), os.path.join(directories["tmp"], "genomes_to_domain.tsv"), ) ] @@ -510,32 +515,31 @@ def get_featurecounts_cmd(input_filepaths, output_filepaths, output_directory, d return cmd -def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): - # os.path.join(directories["intermediate"], "*__checkm2", "filtered", "scaffolds_to_bins.tsv"), - # os.path.join(directories["intermediate"], "*__checkm2", "filtered", "bins.list"), - # os.path.join(directories["intermediate"], "*__checkm2", "filtered", "binned.list"), - # os.path.join(directories["intermediate"], "*__checkm2", "filtered", "checkm2_results.filtered.tsv"), - # os.path.join(directories["intermediate"], "*__checkm2", "filtered", "genomes"), - # os.path.join(directories[("intermediate", "{}__featurecounts".format(step-1))], "featurecounts.orfs.tsv.gz"), +def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, directories, opts, step): + + cmd = [ "rm -rf {}".format(os.path.join(output_directory, "*")), ] - cmd = [ + cmd += [ + "&&", + "mkdir -p {}".format(os.path.join(output_directory, "genomes")), "&&", # scaffolds_to_bins.tsv "cat", - input_filepaths[0], + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "scaffolds_to_bins.tsv"), ">", os.path.join(output_directory, "scaffolds_to_bins.tsv"), "&&", + # bins.list "cat", - input_filepaths[1], + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "bins.list"), ">", os.path.join(output_directory, "bins.list"), @@ -543,36 +547,91 @@ def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, dir # binned.list "cat", - input_filepaths[2], + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "binned.list"), ">", os.path.join(output_directory, "binned.list"), "&&", - # checkm2_results.filtered.tsv + # checkm2_results.filtered.tsv os.environ["concatenate_dataframes.py"], "-a 0", # "-e", - input_filepaths[3], + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "checkm2_results.filtered.tsv"), ">", os.path.join(output_directory, "checkm2_results.filtered.tsv"), ] - # Genomes + # Genomes (.fa, .ffn, .faa, .gff) cmd += [ "&&", - "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( - input_filepaths[4], + + "DST={}; for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done".format( + os.path.join(output_directory,"genomes"), + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "genomes", "*"), + ), + ] + + # GFF + cmd += [ +""" + +DIR_RRNA={} +DIR_TRNA={} +OUTPUT_DIRECTORY={} +mkdir -p $OUTPUT_DIRECTORY + +for GENOME_FASTA in {}; +do + ID=$(basename $GENOME_FASTA .fa) + DIR_GENOME=$(dirname $GENOME_FASTA) + GFF_CDS=$DIR_GENOME/$ID.gff + GFF_RRNA=$DIR_RRNA/$ID.rRNA.gff + GFF_TRNA=$DIR_TRNA/$ID.tRNA.gff + GFF_OUTPUT=$OUTPUT_DIRECTORY/$ID.gff + >$GFF_OUTPUT.tmp + {} -f $GENOME_FASTA -o $GFF_OUTPUT.tmp -n $ID -c $GFF_CDS -r $GFF_RRNA -t $GFF_TRNA -d Prokaryotic + mv $GFF_OUTPUT.tmp $GFF_OUTPUT +done + +""".format( + directories[("intermediate", "{}__barrnap".format(step-3))], + directories[("intermediate", "{}__trnascan-se".format(step-2))], + os.path.join(output_directory,"genomes"), + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "genomes", "*.fa"), + os.environ["compile_gff.py"], + ) + ] + + # rRNA + + cmd += [ + + "DST={}; for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done".format( + os.path.join(output_directory,"genomes"), + os.path.join(directories[("intermediate", "{}__barrnap".format(step-3))], "*.rRNA"), + ), + ] + + # tRNA + + cmd += [ + "&&", + + "DST={}; for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done".format( os.path.join(output_directory,"genomes"), + os.path.join(directories[("intermediate", "{}__trnascan-se".format(step-2))], "*.tRNA"), ), ] + + # Featurecounts cmd += [ "&&", "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( - input_filepaths[5], + os.path.join(directories[("intermediate", "{}__featurecounts".format(step-1))], "featurecounts.orfs.tsv.gz"), output_directory, ), ] @@ -581,6 +640,7 @@ def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, dir cmd += [ "&&", # Statistics + # Assembly os.environ["seqkit"], "stats", "-a", @@ -597,7 +657,58 @@ def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, dir "&&", - # Statistics + # CDS + os.environ["seqkit"], + "stats", + "-a", + "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "genomes", "*.ffn"), + + "|", + + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x[:-4]); df.to_csv(sys.stdout, sep="\t")'""" + ">", + os.path.join(output_directory,"gene_statistics.cds.tsv"), + + "&&", + + # rRNA + os.environ["seqkit"], + "stats", + "-a", + "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "genomes", "*.rRNA"), + + "|", + + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x[:-5]); df.to_csv(sys.stdout, sep="\t")'""" + ">", + os.path.join(output_directory,"gene_statistics.rRNA.tsv"), + + "&&", + + # tRNA + os.environ["seqkit"], + "stats", + "-a", + "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "genomes", "*.tRNA"), + + "|", + + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x[:-5]); df.to_csv(sys.stdout, sep="\t")'""" + ">", + os.path.join(output_directory,"gene_statistics.tRNA.tsv"), + + # Binned/Unbinned + "&&", + "cat", opts.fasta, "|", @@ -614,40 +725,11 @@ def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, dir ">", os.path.join(output_directory,"unbinned.fasta"), - - # "&&", - # "rm -rf {}".format(os.path.join(directories["tmp"],"*")), - - ] - - # tRNA - cmd += [ "&&", - "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( - input_filepaths[6], - os.path.join(output_directory,"genomes"), - ), - ] - # tRNA - cmd += [ - "&&", - "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( - input_filepaths[7], - os.path.join(output_directory,"genomes"), - ), + "rm -rf {}".format(os.path.join(directories["tmp"],"*")), ] - # rRNA - cmd += [ - "&&", - "SRC={}; DST={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST".format( - input_filepaths[8], - os.path.join(output_directory,"genomes"), - ), - ] - - return cmd @@ -1143,7 +1225,7 @@ def create_pipeline(opts, directories, f_cmds): ] output_filenames = [ - "*.rRNA.fasta", + "*.rRNA", "*.rRNA.gff", ] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) @@ -1196,8 +1278,10 @@ def create_pipeline(opts, directories, f_cmds): ] output_filenames = [ - "*.tRNA.fasta", + "*.tRNA", "*.tRNA.gff", + "*.tRNA.struct", + ] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) @@ -1299,10 +1383,10 @@ def create_pipeline(opts, directories, f_cmds): os.path.join(directories["intermediate"], "*__checkm2", "filtered", "binned.list"), os.path.join(directories["intermediate"], "*__checkm2", "filtered", "checkm2_results.filtered.tsv"), os.path.join(directories["intermediate"], "*__checkm2", "filtered", "genomes", "*"), + os.path.join(directories[("intermediate", "{}__trnascan-se".format(step-2))], "*.tRNA"), + os.path.join(directories[("intermediate", "{}__barrnap".format(step-3))], "*.rRNA"), os.path.join(directories[("intermediate", "{}__featurecounts".format(step-1))], "featurecounts.orfs.tsv.gz"), - os.path.join(directories[("intermediate", "{}__trnascan-se".format(step-2))], "*.tRNA.fasta"), - os.path.join(directories[("intermediate", "{}__trnascan-se".format(step-2))], "*.tRNA.gff"), - os.path.join(directories[("intermediate", "{}__barrnap".format(step-3))], "*.rRNA.*"), + ] output_filenames = [ @@ -1314,6 +1398,9 @@ def create_pipeline(opts, directories, f_cmds): "checkm2_results.filtered.tsv", "featurecounts.orfs.tsv.gz", "genome_statistics.tsv", + "gene_statistics.cds.tsv", + "gene_statistics.rRNA.tsv", + "gene_statistics.tRNA.tsv", ] output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) @@ -1325,6 +1412,7 @@ def create_pipeline(opts, directories, f_cmds): "output_directory":output_directory, "opts":opts, "directories":directories, + "step":step, } cmd = get_consolidate_cmd(**params) @@ -1338,12 +1426,8 @@ def create_pipeline(opts, directories, f_cmds): validate_inputs=False, validate_outputs=True, log_prefix=program_label, - ) - - - return pipeline # Set environment variables @@ -1357,10 +1441,12 @@ def add_executables_to_environment(opts): "check_scaffolds_to_bins.py", "partition_gene_models.py", "append_geneid_to_prodigal_gff.py", + "append_geneid_to_barrnap_gff.py", "filter_checkm2_results.py", "consensus_domain_classification.py", "concatenate_dataframes.py", "subset_table.py", + "compile_gff.py", } required_executables={ diff --git a/src/binning-viral.py b/src/binning-viral.py index 2907f61..3f4a6fd 100755 --- a/src/binning-viral.py +++ b/src/binning-viral.py @@ -13,7 +13,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.7.7" # geNomad def get_genomad_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): @@ -39,7 +39,6 @@ def get_genomad_cmd(input_filepaths, output_filepaths, output_directory, directo os.path.join(directories["tmp"], "input.fasta"), os.path.join(directories["output"], "unbinned.fasta"), - "&&", os.environ["genomad"], @@ -293,7 +292,27 @@ def get_prodigal_cmd(input_filepaths, output_filepaths, output_directory, direct "-a {}".format(os.path.join(output_directory, "gene_models.faa")), "-o {}".format(os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes")), - "&&", +""" + +OUTPUT_DIRECTORY={} + +for GENOME_FASTA in {}; +do + ID=$(basename $GENOME_FASTA .fa) + DIR_GENOME=$(dirname $GENOME_FASTA) + GFF_CDS=$DIR_GENOME/$ID.gff + GFF_OUTPUT=$OUTPUT_DIRECTORY/$ID.gff + >$GFF_OUTPUT.tmp + {} -f $GENOME_FASTA -o $GFF_OUTPUT.tmp -n $ID -c $GFF_CDS -d Virus + mv $GFF_OUTPUT.tmp $GFF_OUTPUT +done + +""".format( + os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes"), + os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes", "*.fa"), + os.environ["compile_gff.py"], + ), + "rm -rf", os.path.join(output_directory, "gene_models.gff"), @@ -747,6 +766,7 @@ def add_executables_to_environment(opts): "filter_checkv_results.py", "virfinder_wrapper.r", "genomad_taxonomy_wrapper.py", + "compile_gff.py", # "subset_table.py", # "concatenate_dataframes.py", } diff --git a/src/scripts/append_geneid_to_barrnap_gff.py b/src/scripts/append_geneid_to_barrnap_gff.py index 5f5daff..a82ebff 100755 --- a/src/scripts/append_geneid_to_barrnap_gff.py +++ b/src/scripts/append_geneid_to_barrnap_gff.py @@ -56,7 +56,6 @@ def main(args=None): assert "Name=" in description, "Incorrect BARRNAP formatting. Should have Name= in last field." name = description.split("Name=")[-1] name = name.split(";")[0] - name = name[5:] id_gene = "{}::{}:{}-{}({})".format(name, id_contig, start, end, strand) print( line, diff --git a/src/scripts/compile_gff.py b/src/scripts/compile_gff.py new file mode 100755 index 0000000..0f97311 --- /dev/null +++ b/src/scripts/compile_gff.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +import sys, os, glob, argparse, gzip, warnings +from collections import OrderedDict +import pandas as pd +from tqdm import tqdm +from Bio.SeqIO.FastaIO import SimpleFastaParser + +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.7.7" + +def gc_content(seq): + seq = seq.upper() + number_of_gc = seq.count("G") + seq.count("C") + return number_of_gc/len(seq) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -n -f -o -c -r -t ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + + # Pipeline + parser.add_argument("-f", "--fasta", type=str, default="stdin", help = "path/to/assembly.fasta. Adds contig regions to output GFF. [Default: stdin]") + parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.gff[.gz] [Default: stdout]") + parser.add_argument("-n", "--name", type=str, required=True, help="Genome ID") + parser.add_argument("-c","--gff_cds", type=str, required=True, help = "GFF for CDS genes (Pyrodigal|MetaEuk - modified)") + parser.add_argument("-r","--gff_rRNA", type=str, help = "GFF for rRNA genes (BARRNAP - modified) [Optional]") + parser.add_argument("-t","--gff_tRNA", type=str, help = "GFF for tRNA genes (tRNAscan-SE - modified) [Optional]") + parser.add_argument("-d","--organism_type", type=str, help = "Organism type e.g., {Prokaryote, Eukaryote, Bacteria, Archaea, Virus} [Optional]") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Output + if opts.output == "stdout": + f_out = sys.stdout + else: + if opts.output.endswith(".gz"): + f_out = gzip.open(opts.output, "wt") + else: + f_out = open(opts.output, "w") + + # GFF (Output) + print("##gff-version 3", file=f_out) + print("##Program: VEBA (github.com/jolespin/veba)", file=f_out) + print("##ID: {}".format(opts.name), file=f_out) + print("##Source: Metagenome-Assembled Genome", file=f_out) + if opts.organism_type: + print("##Organism-type: {}".format(opts.organism_type), file=f_out) + + # Contigs + if opts.fasta == "stdin": + f_fa = sys.stdin + else: + f_fa = open(opts.fasta, "r") + + for header, seq in tqdm(SimpleFastaParser(f_fa), "Reading fasta file: {}".format(opts.fasta)): + id = header.split(" ")[0] + attributes = "ID={};genome_id={};gc_cont={:.3f};".format(id, opts.name, gc_content(seq)) + gff_fields = [id, "VEBA", "region", 1, len(seq), ".", "+", ".", attributes] + print(*gff_fields, sep="\t", file=f_out) + + if f_fa != sys.stdin: + f_fa.close() + + # GFF (CDS) + with open(opts.gff_cds, "r") as f_gff: + for line in tqdm(f_gff, "Reading GFF file (CDS): {}".format(opts.gff_cds)): + line = line.strip() + if not line.startswith("#"): + print(line, file=f_out) + + # GFF (rRNA) + if opts.gff_rRNA: + with open(opts.gff_rRNA, "r") as f_gff: + for line in tqdm(f_gff, "Reading GFF file (rRNA): {}".format(opts.gff_rRNA)): + line = line.strip() + if not line.startswith("#"): + print(line, file=f_out) + else: + warnings.warn("No --gff_rRNA was provided") + + # GFF (tRNA) + if opts.gff_tRNA: + with open(opts.gff_tRNA, "r") as f_gff: + for line in tqdm(f_gff, "Reading GFF file (tRNA): {}".format(opts.gff_tRNA)): + line = line.strip() + if not line.startswith("#"): + if not line.endswith(";"): + line += ";" + print(line, file=f_out) + else: + warnings.warn("No --gff_tRNA was provided") + + if f_out != sys.stdout: + f_out.close() + +if __name__ == "__main__": + main() + + \ No newline at end of file diff --git a/src/scripts/eukaryotic_gene_modeling_wrapper.py b/src/scripts/eukaryotic_gene_modeling_wrapper.py index 7e45868..0051cd0 100755 --- a/src/scripts/eukaryotic_gene_modeling_wrapper.py +++ b/src/scripts/eukaryotic_gene_modeling_wrapper.py @@ -449,7 +449,9 @@ def get_stats_cmd(input_filepaths, output_filepaths, output_directory, directori os.path.join(output_directory, "*", "*.fa"), "|", - """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-3]); df.to_csv(sys.stdout, sep="\t")'""", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-3]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-3]); df.to_csv(sys.stdout, sep="\t")'""", + ">", os.path.join(output_directory,"genome_statistics.tsv"), @@ -468,7 +470,9 @@ def get_stats_cmd(input_filepaths, output_filepaths, output_directory, directori os.path.join(output_directory,"*", "*.ffn"), "|", - """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-4]); df.to_csv(sys.stdout, sep="\t")'""", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-4]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-4]); df.to_csv(sys.stdout, sep="\t")'""", + ">", os.path.join(output_directory,"gene_statistics.cds.tsv"), @@ -486,7 +490,9 @@ def get_stats_cmd(input_filepaths, output_filepaths, output_directory, directori os.path.join(output_directory,"*", "*.rRNA"), "|", - """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-5]); df.to_csv(sys.stdout, sep="\t")'""", + ">", os.path.join(output_directory,"gene_statistics.rRNA.tsv"), @@ -504,7 +510,9 @@ def get_stats_cmd(input_filepaths, output_filepaths, output_directory, directori os.path.join(output_directory,"*", "*.tRNA"), "|", - """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-5]); df.to_csv(sys.stdout, sep="\t")'""", + ">", os.path.join(output_directory,"gene_statistics.tRNA.tsv"), diff --git a/src/scripts/filter_busco_results.py b/src/scripts/filter_busco_results.py index fecee46..3dcf21c 100755 --- a/src/scripts/filter_busco_results.py +++ b/src/scripts/filter_busco_results.py @@ -1,12 +1,17 @@ #!/usr/bin/env python import sys, os, glob, argparse -from shutil import copyfile from collections import OrderedDict import pandas as pd from tqdm import tqdm +from Bio.SeqIO.FastaIO import SimpleFastaParser __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2022.12.07" +__version__ = "2023.7.7" + +def gc_content(seq): + seq = seq.upper() + number_of_gc = seq.count("G") + seq.count("C") + return number_of_gc/len(seq) def main(args=None): # Path info @@ -15,7 +20,7 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -i -g -o -f -m 1500 -u".format(__program__) + usage = "{} -i -g -o -f -m 1500 -u".format(__program__) epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" # Parser @@ -23,7 +28,7 @@ def main(args=None): # Pipeline parser.add_argument("-i","--busco_results", type=str, required=True, help = "path/to/busco_output/[id_mag]/busco_results.tsv") - parser.add_argument("-g","--metaeuk_directory", type=str, required=True, help = "path/to/metaeuk/directory") + parser.add_argument("-g","--genome_directory", type=str, required=True, help = "path/to/genome_directory with .fa, .faa, .ffn, .gff, .rRNA, .tRNA, identifier_mapping.tsv, identifier_mapping.metaeuk.tsv, genome_statistics.tsv, gene_statistics.cds.tsv, gene_statistics.rRNA.tsv, gene_statistics.tRNA.tsv, mitochondrion/, and plastid/") parser.add_argument("-o","--output_directory", type=str, default="busco_filtered_output", help = "path/to/output_directory [Default: busco_filtered_output]") parser.add_argument("-f", "--fasta", type=str, help = "path/to/scaffolds.fasta. Include this only if you want to list of unbinned contigs") parser.add_argument("-m", "--minimum_contig_length", type=int, default=1, help="Minimum contig length. [Default: 1]") @@ -31,14 +36,6 @@ def main(args=None): parser.add_argument("--completeness", type=float, default=50.0, help = "BUSCO completeness [Default: 50.0]") parser.add_argument("--contamination", type=float, default=10.0, help = "BUSCO contamination [Default: 10.0]") - # parser.add_argument("--genome_extension", type=str, default="fa", help = "Fasta file extension for bins [Default: fa]") - # parser.add_argument("--protein_extension", type=str, default="faa", help = "Fasta file extension for protein sequences [Default: faa]") - # parser.add_argument("--cds_extension", type=str, default="ffn", help = "Fasta file extension for cds sequences [Default: ffn]") - # parser.add_argument("--genemodel_extension", type=str, default="ffn", help = "Fasta file extension for gene models [Default: gff]") - - parser.add_argument("--symlink", action="store_true", help = "Symlink MAGs instead of copying") - - # Options opts = parser.parse_args() opts.script_directory = script_directory @@ -60,7 +57,6 @@ def main(args=None): # Prefer 'specific' over 'generic' if "specific" in busco_specificities: busco_notation = busco_results["specific"]["one_line_summary"] - else: busco_notation = busco_results["generic"]["one_line_summary"] mag_to_completeness[id_mag] = float(busco_notation.split("%[")[0].split(":")[1]) @@ -95,7 +91,6 @@ def main(args=None): genomes_passed_qc = mag_to_completeness.index.intersection(mag_to_contamination.index) - df_busco.loc[genomes_passed_qc,:].to_csv(os.path.join(opts.output_directory,"busco_results.filtered.tsv"), sep="\t") df_quality.loc[genomes_passed_qc,:].to_csv(os.path.join(opts.output_directory,"busco_results.completeness_contamination.filtered.tsv"), sep="\t") @@ -107,36 +102,133 @@ def main(args=None): for id_mag in sorted(genomes_passed_qc): print(id_mag, file=f_bins) - # Binned f_binned_list = open(os.path.join(opts.output_directory, "binned.list"), "w") - binned_contigs = list() scaffolds_to_bins = OrderedDict() - for id_mag in tqdm(genomes_passed_qc, "Copying fasta files and writing binned contigs", unit=" MAG"): - - for src in glob.glob(os.path.join(opts.metaeuk_directory, "genomes", "{}.*".format(id_mag))): - # Copy or symlink MAG - # src = os.path.join(opts.mag_directory, "{}.{}".format(id_mag, opts.extension)) - fn = os.path.split(src)[-1] - dst = os.path.join(opts.output_directory,"genomes", fn) - - if opts.symlink: - if os.path.exists(dst): - os.remove(dst) - os.symlink(os.path.realpath(src), dst) - else: - copyfile(src,dst) - - # Write contigs to list - with open(os.path.join(opts.metaeuk_directory, "genomes", "{}.fa".format(id_mag)), "r") as f_fasta: - for line in f_fasta.readlines(): - line = line.strip() - if line.startswith(">"): - id_contig = line[1:].split(" ")[0] - scaffolds_to_bins[id_contig] = id_mag - print(id_contig, file=f_binned_list) - binned_contigs.append(id_contig) + + for id_mag in tqdm(genomes_passed_qc, "Merging fasta files and writing binned contigs", unit=" MAG"): + # Output files + f_fa = open(os.path.join(opts.output_directory, "genomes", "{}.fa".format(id_mag)), "w") + f_gff = open(os.path.join(opts.output_directory, "genomes", "{}.gff".format(id_mag)), "w") + f_faa = open(os.path.join(opts.output_directory, "genomes", "{}.faa".format(id_mag)), "w") + f_ffn = open(os.path.join(opts.output_directory, "genomes", "{}.ffn".format(id_mag)), "w") + f_rRNA = open(os.path.join(opts.output_directory, "genomes", "{}.rRNA".format(id_mag)), "w") + f_tRNA = open(os.path.join(opts.output_directory, "genomes", "{}.tRNA".format(id_mag)), "w") + + scaffold_to_seqtype = OrderedDict() + + # GFF [Contigs] + print("##gff-version 3", file=f_gff) + print("##Program: VEBA (github.com/jolespin/veba)", file=f_gff) + print("##ID: {}".format(id_mag), file=f_gff) + print("##Source: Metagenome-Assembled Genome", file=f_gff) + print("##Organism-type: Eukaryotic", file=f_gff) + + # Assembly + path_nuclear = os.path.join(os.path.join(opts.genome_directory, "{}.fa".format(id_mag))) + path_mitochondrion = os.path.join(os.path.join(opts.genome_directory, "mitochondrion", "{}.fa".format(id_mag))) + path_plastid = os.path.join(os.path.join(opts.genome_directory, "plastid", "{}.fa".format(id_mag))) + + for seq_type, fp in zip(["nuclear", "mitochondrion", "plastid"], [path_nuclear, path_mitochondrion, path_plastid]): + with open(fp, "r") as f_in: + for header, seq in SimpleFastaParser(f_in): + id = header.split(" ")[0] + attributes = "ID={};genome_id={};gc_cont={:.3f};seq_type={}".format(id,id_mag,gc_content(seq), seq_type) + gff_fields = [id, "VEBA", "region", 1, len(seq), ".", "+", ".", attributes] + print(">{}\n{}".format(header,seq), file=f_fa) + print(*gff_fields, sep="\t", file=f_gff) + print(id, file=f_binned_list) + binned_contigs.append(id) + scaffolds_to_bins[id] = id_mag + scaffold_to_seqtype[id] = seq_type + f_fa.close() + + scaffold_to_seqtype = pd.Series(scaffold_to_seqtype) + scaffold_to_seqtype.to_frame().to_csv(os.path.join(opts.output_directory, "genomes", "{}.seq_type.tsv".format(id_mag)), sep="\t", header=None) + + # GFF + path_nuclear = os.path.join(os.path.join(opts.genome_directory, "{}.gff".format(id_mag))) + path_mitochondrion = os.path.join(os.path.join(opts.genome_directory, "mitochondrion", "{}.gff".format(id_mag))) + path_plastid = os.path.join(os.path.join(opts.genome_directory, "plastid", "{}.gff".format(id_mag))) + + for seq_type, fp in zip(["nuclear", "mitochondrion", "plastid"], [path_nuclear, path_mitochondrion, path_plastid]): + with open(fp, "r") as f_in: + if seq_type == "nuclear": + for line in f_in: + line = line.strip() + if not line.startswith("#"): + if not line.endswith(";"): + line += ";" + print(line, file=f_gff) + else: + for line in f_in: + line = line.strip() + if not line.startswith("#"): + if not line.endswith(";"): + line += ";" + line = "{};organelle={};".format(line, seq_type) + print(line, file=f_gff) + f_gff.close() + + # Proteins + path_nuclear = os.path.join(os.path.join(opts.genome_directory, "{}.faa".format(id_mag))) + path_mitochondrion = os.path.join(os.path.join(opts.genome_directory, "mitochondrion", "{}.faa".format(id_mag))) + path_plastid = os.path.join(os.path.join(opts.genome_directory, "plastid", "{}.faa".format(id_mag))) + + for seq_type, fp in zip(["nuclear", "mitochondrion", "plastid"], [path_nuclear, path_mitochondrion, path_plastid]): + with open(fp, "r") as f_in: + if seq_type == "nuclear": + for header, seq in SimpleFastaParser(f_in): + print(">{}\n{}".format(header,seq), file=f_faa) + else: + for header, seq in SimpleFastaParser(f_in): + header = "{};seq_type={}".format(header,seq_type) + print(">{}\n{}".format(header,seq), file=f_faa) + f_faa.close() + + # CDS + path_nuclear = os.path.join(os.path.join(opts.genome_directory, "{}.ffn".format(id_mag))) + path_mitochondrion = os.path.join(os.path.join(opts.genome_directory, "mitochondrion", "{}.ffn".format(id_mag))) + path_plastid = os.path.join(os.path.join(opts.genome_directory, "plastid", "{}.ffn".format(id_mag))) + + for seq_type, fp in zip(["nuclear", "mitochondrion", "plastid"], [path_nuclear, path_mitochondrion, path_plastid]): + with open(fp, "r") as f_in: + if seq_type == "nuclear": + for header, seq in SimpleFastaParser(f_in): + print(">{}\n{}".format(header,seq), file=f_ffn) + else: + for header, seq in SimpleFastaParser(f_in): + header = "{};seq_type={}".format(header,seq_type) + print(">{}\n{}".format(header,seq), file=f_ffn) + f_ffn.close() + + # rRNA + path_nuclear = os.path.join(os.path.join(opts.genome_directory, "{}.rRNA".format(id_mag))) + path_mitochondrion = os.path.join(os.path.join(opts.genome_directory, "mitochondrion", "{}.rRNA".format(id_mag))) + path_plastid = os.path.join(os.path.join(opts.genome_directory, "plastid", "{}.rRNA".format(id_mag))) + + for seq_type, fp in zip(["nuclear", "mitochondrion", "plastid"], [path_nuclear, path_mitochondrion, path_plastid]): + with open(fp, "r") as f_in: + for header, seq in SimpleFastaParser(f_in): + id = header.split(" ")[0] + header = "{} {}:{}".format(id,id_mag,seq_type) + print(">{}\n{}".format(header,seq), file=f_rRNA) + f_rRNA.close() + + + # tRNA + path_nuclear = os.path.join(os.path.join(opts.genome_directory, "{}.tRNA".format(id_mag))) + path_mitochondrion = os.path.join(os.path.join(opts.genome_directory, "mitochondrion", "{}.tRNA".format(id_mag))) + path_plastid = os.path.join(os.path.join(opts.genome_directory, "plastid", "{}.tRNA".format(id_mag))) + + for seq_type, fp in zip(["nuclear", "mitochondrion", "plastid"], [path_nuclear, path_mitochondrion, path_plastid]): + with open(fp, "r") as f_in: + for header, seq in SimpleFastaParser(f_in): + # id = header.split(" ")[0] + header = "{} {}:{}".format(header,id_mag,seq_type) + print(">{}\n{}".format(header,seq), file=f_tRNA) + f_tRNA.close() f_binned_list.close() @@ -145,11 +237,10 @@ def main(args=None): scaffolds_to_bins.to_frame().to_csv(os.path.join(opts.output_directory, "scaffolds_to_bins.tsv"), sep="\t", header=None) - # Identifier Mapping f_identifiers = open(os.path.join(opts.output_directory,"genomes", "identifier_mapping.tsv"),"w") - with open(os.path.join(opts.metaeuk_directory,"genomes", "identifier_mapping.tsv"), "r") as f_in: + with open(os.path.join(opts.genome_directory, "identifier_mapping.tsv"), "r") as f_in: for line in f_in: line = line.strip() if line: @@ -159,21 +250,27 @@ def main(args=None): f_identifiers.close() + + # identifier_mapping.metaeuk.tsv - df_metaeuk_identifiers = pd.read_csv(os.path.join(opts.metaeuk_directory, "identifier_mapping.metaeuk.tsv"), sep="\t", index_col=0) + df_metaeuk_identifiers = pd.read_csv(os.path.join(opts.genome_directory, "identifier_mapping.metaeuk.tsv"), sep="\t", index_col=0) mask = df_metaeuk_identifiers["C_acc"].map(lambda x: x in binned_contigs).values.astype(bool) df_metaeuk_identifiers.loc[mask].to_csv(os.path.join(opts.output_directory, "identifier_mapping.metaeuk.tsv"), sep="\t") - # metaeuk_to_simple.tsv - metaeuk_to_simple = pd.read_csv(os.path.join(opts.metaeuk_directory,"metaeuk_to_simple.tsv"), sep="\t", index_col=0, header=None).iloc[:,0] - mask = metaeuk_to_simple.map(lambda x: "_".join(x.split("_")[:-1]) in binned_contigs).values.astype(bool) - metaeuk_to_simple[mask].to_frame().to_csv(os.path.join(opts.output_directory, "metaeuk_to_simple.tsv"), sep="\t", header=None) + # Statistics + for fn in ["genome_statistics.tsv", "gene_statistics.cds.tsv", "gene_statistics.rRNA.tsv", "gene_statistics.tRNA.tsv"]: + df = pd.read_csv(os.path.join(opts.genome_directory, fn), sep="\t", index_col=0) + mask = df.index.map(lambda x: x.split("/")[-1] in genomes_passed_qc) + df.loc[mask].to_csv(os.path.join(opts.output_directory, fn), sep="\t") + + # # metaeuk_to_simple.tsv + # metaeuk_to_simple = pd.read_csv(os.path.join(opts.genome_directory,"metaeuk_to_simple.tsv"), sep="\t", index_col=0, header=None).iloc[:,0] + # mask = metaeuk_to_simple.map(lambda x: "_".join(x.split("_")[:-1]) in binned_contigs).values.astype(bool) + # metaeuk_to_simple[mask].to_frame().to_csv(os.path.join(opts.output_directory, "metaeuk_to_simple.tsv"), sep="\t", header=None) # Get unbinned contigs if opts.fasta: - from Bio.SeqIO.FastaIO import SimpleFastaParser - f_unbinned_list = open(os.path.join(opts.output_directory, "unbinned.list"), "w") with open(opts.fasta, "r") as f_fasta: # Use stdin? diff --git a/src/scripts/merge_busco_json.py b/src/scripts/merge_busco_json.py index 45dcdbb..bc53794 100755 --- a/src/scripts/merge_busco_json.py +++ b/src/scripts/merge_busco_json.py @@ -3,7 +3,14 @@ import pandas as pd __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2022.03.10" +__version__ = "2023.7.5" + +def read_busco_json(path): + json_data = pd.read_json(path, typ="series")[["results", "lineage_dataset"]] + results = pd.Series(json_data["results"]).loc[["one_line_summary", "Complete", "Single copy", "Multi copy", "Fragmented", "Missing", "n_markers"]] + lineage_dataset = pd.Series(json_data["lineage_dataset"]).loc[["name", "creation_date", "number_of_buscos", "number_of_species"]] + lineage_dataset.index = ["dataset_name", "creation_date", "number_of_busco_markers", "number_of_species"] + return pd.concat([results, lineage_dataset]) def main(argv=None): # Path info @@ -31,14 +38,11 @@ def main(argv=None): if opts.output == "stdout": opts.output = sys.stdout - # Get generic json files generic_output = dict() for fp in glob.glob(os.path.join(opts.busco_directory, "*", "short_summary.generic.*.json")): id_genome = fp.split("/")[-2] - data = pd.read_json(fp, typ="series") - data["dataset_name"] = data["dataset"].split("/")[-1] - generic_output[id_genome] = data + generic_output[id_genome] = read_busco_json(fp) df_generic = pd.DataFrame(generic_output).T df_generic.columns = df_generic.columns.map(lambda x: ("generic", x)) @@ -46,9 +50,7 @@ def main(argv=None): specific_output = dict() for fp in glob.glob(os.path.join(opts.busco_directory, "*", "short_summary.specific.*.json")): id_genome = fp.split("/")[-2] - data = pd.read_json(fp, typ="series") - data["dataset_name"] = data["dataset"].split("/")[-1] - specific_output[id_genome] = data + specific_output[id_genome] = read_busco_json(fp) df_specific = pd.DataFrame(specific_output).T df_specific.columns = df_specific.columns.map(lambda x: ("specific", x)) @@ -71,26 +73,10 @@ def main(argv=None): # Output if opts.json_output: - df_output.to_json(opts.json_output) + df_output.T.to_json(opts.json_output) df_output.to_csv(opts.output, sep="\t") - # Run Info - # runinfo_columns = ["input_file", "mode"] - - # df_info = pd.DataFrame() - # if not df_generic.empty: - # df_info = df_generic[runinfo_columns] - - # df_info.columns = df_info.columns.map(lambda x: ("run_info", x)) - - - # Remove run info from generic - # if not df_generic.empty: - # df_generic = df_generic.drop(runinfo_columns, axis=1) - - # Add multiindex to generic - From 9779bd26fe56f3baad11d7b07b318654e34549d4 Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Fri, 7 Jul 2023 15:44:27 -0700 Subject: [PATCH 10/16] Update DEVELOPMENT.md --- DEVELOPMENT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index bf99d6f..bf2a125 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -248,6 +248,7 @@ ________________________________________________________________ #### Change Log: * [2023.7.7] - Added `compile_gff.py` to merge CDS, rRNA, and tRNA GFF files. Used in `binning-prokaryotic.py` and `binning-viral.py`. `binning-eukaryotic.py` uses the source of this in the backend of `filter_busco_results.py`. +* [2023.7.6] - Updated `BUSCO v5.3.2 -> v5.4.3` which changes the json output structure and made the appropriate changes in `filter_busco_results.py`. * [2023.7.3] - Added `eukaryotic_gene_modeling_wrapper.py` which 1) splits nuclear, mitochondrial, and plastid genomes; 2) performs gene modeling via `MetaEuk` and `Pyrodigal`; 3) performs rRNA detection via `BARRNAP`; 4) performs tRNA detection via `tRNAscan-SE`; 5) merges processed GFF files; and 5) calculates sequences statistics. * [2023.6.29] - Added `gene_biotype=protein_coding` to `prodigal` GFF output. * [2023.6.20] - Added `VFDB` to `annotate.py` and database. From ca4052d0445f6ebb55454bf5c4deb43d22ca0372 Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Tue, 11 Jul 2023 17:16:28 -0700 Subject: [PATCH 11/16] v1.2.0 --- DEVELOPMENT.md => CHANGELOG.md | 108 +- DEPENDENCIES.xlsx | Bin 0 -> 81877 bytes README.md | 143 ++- VERSION | 3 +- images/Schematic.pdf | Bin 73741 -> 90350 bytes images/Schematic.png | Bin 136615 -> 754668 bytes install/DATABASE.md | 983 ++++++++++++++++ install/README.md | 1044 +---------------- install/check_installation.sh | 2 +- install/docker/CONTAINER_RESOURCES.tsv | 13 - install/docker/build_docker_image.sh | 2 + install/docker/dockerize_environments.sh | 4 +- install/download_databases.sh | 2 +- install/install_veba.sh | 3 +- install/uninstall_veba.sh | 3 +- install/update_environment_scripts.sh | 4 +- install/update_environment_variables.sh | 2 +- src/MODULE_RESOURCES | 6 +- src/README.md | 742 +++++++----- src/SCRIPT_VERSIONS | 68 +- src/annotate.py | 2 +- src/binning-viral.py | 19 +- src/biosynthetic.py | 207 ++-- src/get_script_versions.sh | 13 +- src/scripts/bgc_novelty_scorer.py | 49 +- walkthroughs/adapting_commands_for_docker.md | 25 +- .../commands/SunGridEngine/cmd_annotate.sh | 23 + .../commands/SunGridEngine/cmd_assembly.sh | 15 + .../SunGridEngine/cmd_binning_eukaryotic.sh | 18 + .../SunGridEngine/cmd_binning_prokaryotic.sh | 13 + .../SunGridEngine/cmd_binning_viral.sh | 10 + .../SunGridEngine/cmd_classify_eukaryotic.sh | 6 + .../SunGridEngine/cmd_classify_prokaryotic.sh | 11 + .../SunGridEngine/cmd_classify_viral.sh | 6 + .../commands/SunGridEngine/cmd_cluster.sh | 11 + .../commands/SunGridEngine/cmd_index.sh | 9 + .../SunGridEngine/cmd_mapping_global.sh | 16 + .../SunGridEngine/cmd_mapping_local.sh | 16 + .../SunGridEngine/cmd_pipline_binning.sh | 12 + .../commands/SunGridEngine/cmd_preprocess.sh | 13 + walkthroughs/end-to-end_metagenomics.md | 73 +- ...vering_viruses_from_metatranscriptomics.md | 47 +- 42 files changed, 2096 insertions(+), 1650 deletions(-) rename DEVELOPMENT.md => CHANGELOG.md (95%) create mode 100644 DEPENDENCIES.xlsx create mode 100644 install/DATABASE.md delete mode 100644 install/docker/CONTAINER_RESOURCES.tsv create mode 100644 walkthroughs/commands/SunGridEngine/cmd_annotate.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_assembly.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_binning_eukaryotic.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_binning_prokaryotic.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_binning_viral.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_classify_eukaryotic.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_classify_prokaryotic.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_classify_viral.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_cluster.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_index.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_mapping_global.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_mapping_local.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_pipline_binning.sh create mode 100644 walkthroughs/commands/SunGridEngine/cmd_preprocess.sh diff --git a/DEVELOPMENT.md b/CHANGELOG.md similarity index 95% rename from DEVELOPMENT.md rename to CHANGELOG.md index bf2a125..de4c700 100644 --- a/DEVELOPMENT.md +++ b/CHANGELOG.md @@ -5,15 +5,27 @@ ________________________________________________________________ #### Current Releases: -##### Release v1.1.3 + +**Release v1.2.0:** + * Fixed minor error in `binning-prokaryotic.py` where the `--veba_database` argument wasn't utilized and only the environment variable `VEBA_DATABASE` could be used. * Updated the Docker images to have `/volumes/input`, `/volumes/output`, and `/volumes/database` directories to mount. * Replaced `prodigal` with `pyrodigal` as it is faster and under active development. * Added support for missing classifications in `compile_krona.py` and `consensus_genome_classification.py`. * Updated `GTDB-Tk` from version `2.1.3` → `2.3.0` and `GTDB` from version `r202_v2` → `r214`. Changed `${VEBA_DATABASE}/Classify/GTDBTk` → `${VEBA_DATABASE}/Classify/GTDB`. Added `gtdb_r214.msh` to `GTDB` database for ANI screening. * Added pangenome and singularity tables to `cluster.py` (and associated global/local clustering scripts) to output automatically. +* Added `compile_gff.py` to merge CDS, rRNA, and tRNA GFF files. Used in `binning-prokaryotic.py` and `binning-viral.py`. `binning-eukaryotic.py` uses the source of this in the backend of `filter_busco_results.py`. Includes GC content for contigs and various tags. +* Updated `BUSCO v5.3.2 -> v5.4.3` which changes the json output structure and made the appropriate changes in `filter_busco_results.py`. +* Added `eukaryotic_gene_modeling_wrapper.py` which 1) splits nuclear, mitochondrial, and plastid genomes; 2) performs gene modeling via `MetaEuk` and `Pyrodigal`; 3) performs rRNA detection via `BARRNAP`; 4) performs tRNA detection via `tRNAscan-SE`; 5) merges processed GFF files; and 5) calculates sequences statistics. +* Added `gene_biotype=protein_coding` to `P(y)rodigal(-GV)` GFF output. +* Added `VFDB` to `annotate.py` and database. +* Compiled and pushed `gtdb_r214.msh` mash file to [Zenodo:8048187](https://zenodo.org/record/8048187) which is now used by default in `classify-prokaryotic.py`. It is now included in `VDB_v5.1`. +* Cleaned up global and local clustering intermediate files. Added pangenome tables and singelton information to outputs. -##### Release v1.1.2 + +
+ **Release v1.1.2:** + * Created Docker images for all modules * Replaced all absolute path symlinks with relative symlinks * Changed `prokaryotic_taxonomy.tsv` and `prokaryotic_taxonomy.clusters.tsv` in `classify-prokaryotic.py` (along with eukaryotic and viral) files to `taxonomy.tsv` and `taxonomy.clusters.tsv` for uniformity. @@ -33,7 +45,10 @@ ________________________________________________________________ * Added an option to `merge_generalized_mapping.py` to include the sample index in a filepath and also an option to remove empty features (useful for Salmon). Added an `executable='/bin/bash'` option to the `subprocess.Popen` calls in `GenoPype` to address [issues/23](https://github.com/jolespin/veba/issues/23). * Added `genbanks/[id_genome]/` to output directory of `biosynthetic.py` which has symlinks to all the BGC genbanks from `antiSMASH`. -##### Release v1.1.1 +
+ +
+ **Release v1.1.1:** * Most important update includes fixing a broken VEBA-`binning-viral.yml` install recipe which had package conflicts for `aria2` 30e8b0a. * Fixes on conda-related environment variables in the install scripts. @@ -44,8 +59,11 @@ ________________________________________________________________ * Updated `merge_generalized_mapping.py` script to take in BAM files instead of being dependent on a specific directory. * Added option to have no header in `subset_table.py` -##### Release v1.1.0 +
+
+ **Release v1.1.0:** + * **Modules**: * `annotate.py` * Added `NCBIfam-AMRFinder` AMR domain annotations @@ -151,14 +169,19 @@ ________________________________________________________________ * Added `transdecoder_wrapper.py` which is a wrapper around `TransDecoder` with direct support for `Diamond` and `HMMSearch` homology searches. Also includes `append_geneid_to_transdecoder_gff.py` which is run in the backend to clean up the GFF file and make them compatible with what is output by `Prodigal` and `MetaEuk` runs of `VEBA`. * Added support for using `n_jobs -1` to use all available threads (similar to `scikit-learn` methodology). +
-##### Release v1.0.4 +
+ **Release v1.0.4:** + * Added `biopython` to `VEBA-assembly_env` which is needed when running `MEGAHIT` as the scaffolds are rewritten and [an error](https://github.com/jolespin/veba/issues/17) was raised. [aea51c3](https://github.com/jolespin/veba/commit/aea51c3e0b775aec90f7343f01cad6911f526f0a) * Updated Microeukaryotic protein database to exclude a few higher eukaryotes that were present in database, changed naming scheme to hash identifiers (from `cat reference.faa | seqkit fx2tab -s -n > id_to_hash.tsv`). Switching database from [FigShare](https://figshare.com/articles/dataset/Microeukaryotic_Protein_Database/19668855) to [Zenodo](https://zenodo.org/record/7485114#.Y6vZO-zMKDU). Uses database version `VDB_v3` which has the updated microeukaryotic protein database (`VDB-Microeukaryotic_v2`) [0845ba6](https://github.com/jolespin/veba/commit/0845ba6be65f3486d61fe7ae21a2937efeb42ee9) -___ +
-##### Release v1.0.3e +
+ **Release v1.0.3e:** + * Patch fix for `install_veba.sh` where `install/environments/VEBA-assembly_env.yml` raised [a compatibilty error](https://github.com/jolespin/veba/issues/15) when creating the `VEBA-assembly_env` environment. [c2ab957](https://github.com/jolespin/veba/commit/c2ab957be132d34e6b99d6dea394be4572b83066) * Patch fix for `VirFinder_wrapper.R` where `__version__ = ` variable was throwing [an R error](https://github.com/jolespin/veba/issues/13) when running `binning-viral.py` module. [19e8f38](https://github.com/jolespin/veba/commit/19e8f38a5050328b7ba88b2271f0221073748cbb) * Patch fix for `filter_busco_results.py` where [an error](https://github.com/jolespin/veba/issues/12) arose that produced empty `identifier_mapping.metaeuk.tsv` subset tables. [359e4569](https://github.com/jolespin/veba/commit/359e45699fc6d6fdf739350263fd34c6e4a62f94) @@ -175,10 +198,11 @@ ___ * Added support for fasta header descriptions in `binning-prokaryotic.py`. [6c0ed82](https://github.com/jolespin/veba/commit/6c0ed82c804ad60a4f1ae51f3e5fecd14dba845f) * Added functionality to `replace_fasta_descriptions.py` script to be able to use a string for replacing fasta headers in addition to the original functionality. [6c0ed82](https://github.com/jolespin/veba/commit/6c0ed82c804ad60a4f1ae51f3e5fecd14dba845f) -___ +
+
+ **Release v1.0.2a:** -##### Release v1.0.2a * Updated *GTDB-Tk* in `VEBA-binning-prokaryotic_env` from `1.x` to `2.x` (this version uses much less memory): [f3507dd](https://github.com/jolespin/veba/commit/f3507dd13a42960e3671c9f8a106c9974fbfce21) * Updated the *GTDB-Tk* database from `R202` to `R207_v2` to be compatible with *GTDB-Tk v2.x*: [f3507dd](https://github.com/jolespin/veba/commit/f3507dd13a42960e3671c9f8a106c9974fbfce21) * Updated the [GRCh38 no-alt analysis set](https://genome-idx.s3.amazonaws.com/bt/GRCh38_noalt_as.zip) to [T2T CHM13v2.0](https://genome-idx.s3.amazonaws.com/bt/chm13v2.0.zip) for the default human reference: [5ccb4e2](https://github.com/jolespin/veba/commit/5ccb4e20564513707fcc3420b18237974455e196) @@ -188,54 +212,51 @@ ___ * Fixed symlinks to scripts for `install_veba.sh`: [d1fad03](https://github.com/jolespin/veba/commit/d1fad03b71537cc6cc0d47fee426b6610000752a) * Added missing `CHECKM_DATA_PATH` environment variable to `VEBA-binning-prokaryotic_env` and `VEBA-classify_env`: [d1fad03](https://github.com/jolespin/veba/commit/d1fad03b71537cc6cc0d47fee426b6610000752a) -___ +
-##### Release v1.0.1 -Small patch fix: +
+ **Release v1.0.1:** * Fixed the fatal binning-eukaryotic.py error: [7c5addf](https://github.com/jolespin/veba/commit/7c5addf9ed6e8e45502274dd353f20b211838a41) * Fixed the minor file naming in cluster.py: [5803845](https://github.com/jolespin/veba/commit/58038451dac0791899aa7fca3f9d79454cb9ed46) * Removes left-over human genome tar.gz during database download/config: [5803845](https://github.com/jolespin/veba/commit/58038451dac0791899aa7fca3f9d79454cb9ed46) -___ +
-##### Release v1.0.0 +
+ **Release v1.0.0:** + * Released with *BMC Bionformatics* publication (doi:10.1186/s12859-022-04973-8). -________________________________________________________________ +
+________________________________________________________________ #### Path to `v2.0.0`: **Definitely:** -* Add `genome_statistics.tsv`, `gene_statistics.cds.tsv`, etc. to `binning-*.py` modules. -* Add `VFDB` homology to `biosynthetic.py`. -* Add contig region and exons to `prodigal` GFF. +* `NextFlow` support * Consistent usage of the following terms: 1) dataframe vs. table; 2) protein-cluster vs. orthogroup. * Add support for `FAMSA` in `phylogeny.py` * Create a `assembly-longreads.py` module that uses `MetaFlye` -* Add tRNA and rRNA detection in prokaryotic and eukaryotic binning modules. -* Expand Microeukaryotic Protein Database to include more fungi (Mycocosm) +* Expand Microeukaryotic Protein Database to include more fungi (`Mycocosm`) * Add MAG-level counts to prokaryotic and eukaryotic. Add optional bam file for viral binning, if so then add MAG-level counts -* Support genome table input for `biosynthetic.py`, `phylogeny.py`, `index.py`, etc. * Install each module via `bioconda` -* Add checks for `annotate.py` to ensure there are no proteins > 100K in length. -* Add VFDB to `annotate.py` * Add support for `Salmon` in `mapping.py` and `index.py`. This can be used instead of `STAR` which will require adding the `exon` field to `Prodigal` GFF file (`MetaEuk` modified GFF files already have exon ids). -* Speed up `binning-eukaryotic.py` by accessing `BUSCO` backends and only running gene calls for genes relevant to genome. If it passes `BUSCO` filters, then run actual gene calls. -* Build a clustered version of the Microeukaryotic Protein Database that is more efficient to run. +* Build Metaphlan (and HUMAnN) database from genomes. + **Probably (Yes)?:** -* Build Metaphlan (and HUMAnN) database from genomes. + * Add [iPHoP](https://bitbucket.org/srouxjgi/iphop/src/main/) to `binning-viral.py`. * Add a `metabolic.py` module * Swap [`TransDecoder`](https://github.com/TransDecoder/TransDecoder) for [`TransSuite`](https://github.com/anonconda/TranSuite) -* Add spatial coverage to `coverage.py` script like in `mapping.py` module? Maybe just the samtools coverage output. * Reimplement `KOFAMSCAN` (which creates thousands of intermediate files) using `hmmer_wrapper.py`. +* Build a clustered version of the Microeukaryotic Protein Database that is more efficient to run. **...Maybe (Not)?** @@ -246,8 +267,11 @@ ________________________________________________________________ ________________________________________________________________ -#### Change Log: -* [2023.7.7] - Added `compile_gff.py` to merge CDS, rRNA, and tRNA GFF files. Used in `binning-prokaryotic.py` and `binning-viral.py`. `binning-eukaryotic.py` uses the source of this in the backend of `filter_busco_results.py`. +
+ **Daily Change Log:** + + +* [2023.7.7] - Added `compile_gff.py` to merge CDS, rRNA, and tRNA GFF files. Used in `binning-prokaryotic.py` and `binning-viral.py`. `binning-eukaryotic.py` uses the source of this in the backend of `filter_busco_results.py`. Includes GC content for contigs and various tags. * [2023.7.6] - Updated `BUSCO v5.3.2 -> v5.4.3` which changes the json output structure and made the appropriate changes in `filter_busco_results.py`. * [2023.7.3] - Added `eukaryotic_gene_modeling_wrapper.py` which 1) splits nuclear, mitochondrial, and plastid genomes; 2) performs gene modeling via `MetaEuk` and `Pyrodigal`; 3) performs rRNA detection via `BARRNAP`; 4) performs tRNA detection via `tRNAscan-SE`; 5) merges processed GFF files; and 5) calculates sequences statistics. * [2023.6.29] - Added `gene_biotype=protein_coding` to `prodigal` GFF output. @@ -325,31 +349,7 @@ ________________________________________________________________ * [2022.02.22] - `concatenate_fasta.py` and `concatenate_gff.py` * [2022.02.02] - `consensus_genome_classification.py` - -________________________________________________________________ - -#### Upcoming Modules: - -* `noncoding.py` module: t-RNAscan-SE, BARRNAP, CORDON -* `metabolism.py` module: gapseq? Metage2Metabo? -* `reassembly.py` module: SPAdes - -________________________________________________________________ - -#### Additional: - -**GenoPype:** - -* Get a mapping between {step:intermediate directory} and clear out intermediate directories for `--restart_from_checkpoint`. - - -**Index/Mapping:** - -* Add STAR support. The limiting factor here is getting an analog to exon in the prodigal generated GTF for this option -sjdbGTFfeatureExon. Will need to make some adjustments to `append_geneid_to_prodigal_gff.py` but this will require new lines instead of modifying existing lines. - -* Relevant GitHub issues: -https://github.com/alexdobin/STAR/issues/994 -https://github.com/alexdobin/STAR/issues/867 +
diff --git a/DEPENDENCIES.xlsx b/DEPENDENCIES.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2cc20f25550e07a7740ad60a2b397cdb505d946c GIT binary patch literal 81877 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 r!zeG_{9ioH=S}t5+4C6B%>KK@I;2?2|u&L8ok + +![Maintainer](https://img.shields.io/badge/Maintainer-@jolespin-blue) ![License](https://img.shields.io/badge/License-GNU AGPLv3-blue) ![DOI:10.1186/s12859-022-04973-8](https://zenodo.org/badge/DOI/10.1186/s12859-022-04973-8.svg) + +[![Forks][forks-shield]][forks-url] +[![Stargazers][stars-shield]][stars-url] +[![Issues][issues-shield]][issues-url] + + + +[forks-shield]: https://img.shields.io/github/forks/jolespin/veba.svg?style=for-the-badge +[forks-url]: https://github.com/jolespin/veba/members +[stars-shield]: https://img.shields.io/github/stars/jolespin/veba.svg?style=for-the-badge +[stars-url]: https://github.com/jolespin/veba/stargazers +[issues-shield]: https://img.shields.io/github/issues/jolespin/veba.svg?style=for-the-badge +[issues-url]: https://github.com/jolespin/veba/issues + ``` _ _ _______ ______ _______ \ / |______ |_____] |_____| \/ |______ |_____] | | ``` -### Description -The *Viral Eukaryotic Bacterial Archaeal* (VEBA) is an open-source software suite developed with all domains of microorganisms as the primary objective (not post hoc adjustments) including prokaryotic, eukaryotic, and viral organisms. VEBA is an end-to-end metagenomics and bioprospecting software suite that can directly recover and analyze eukaryotic and viral genomes in addition to prokaryotic genomes with native support for CPR. VEBA implements a novel iterative binning procedure and an optional hybrid sample-specific/multi-sample framework that recovers more genomes than non-iterative methods. To optimize the microeukaryotic gene calling and taxonomic classifications, VEBA includes a consensus microeukaryotic database containing protists and fungi compiled from several existing databases. VEBA also provides a unique clustering-based dereplication strategy allowing for sample-specific genomes and proteins to be directly compared across non-overlapping biological samples. -**VEBA now automates biosynthetic gene cluster identification and novelty scores for bioprospecting.** +### What is VEBA? +The *Viral Eukaryotic Bacterial Archaeal* (VEBA) is an open-source software suite developed with all domains of microorganisms as the primary objective (not post hoc adjustments) including prokaryotic, eukaryotic, and viral organisms. VEBA is an end-to-end metagenomics and bioprospecting software suite that can directly recover and analyze eukaryotic and viral genomes in addition to prokaryotic genomes with native support for candidate phyla radiation (CPR). VEBA implements a novel iterative binning procedure and an optional hybrid sample-specific/multi-sample framework that recovers more genomes than non-iterative methods. To optimize the microeukaryotic gene calling and taxonomic classifications, VEBA includes a consensus microeukaryotic database containing protists and fungi compiled from several existing databases. VEBA also provides a unique clustering-based dereplication strategy allowing for sample-specific genomes and proteins to be directly compared across non-overlapping biological samples. VEBA also automates biosynthetic gene cluster identification and novelty scores for bioprospecting. + +VEBA's mission is to make robust (meta-)genomics/transcriptomics analysis effortless. The philosophy of VEBA is that workflows should be modular, generalizable, and easy-to-use with minimal intermediate steps. The approach implemented in VEBA is to (try and) think 2 steps ahead of what you may need to do and automate the task for you. + +

^__^

___________________________________________________________________ + ### Citation Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. [doi: 10.1186/s12859-022-04973-8](https://doi.org/10.1186/s12859-022-04973-8). PMID: 36224545. Please cite the software dependencies described under the [*Dependency Citation Table*](CITATIONS.md). +

^__^

+ ___________________________________________________________________ ### Announcements -* `v1.1.2` fixes a fatal error with `featureCounts` -* Docker images are now available for all modules via [DockerHub](https://hub.docker.com/repositories/jolespin) + +* **`VEBA v1.2.0` is now available!** + +* **`VEBA` Modules:** + * Updated `GTDB-Tk` now uses `Mash` for ANI screening to speed up classification (now provided in `VDB_v5.1` database) + * rRNA and tRNA are identified for prokaryotic and eukaryotic genomes via `BARRNAP` and `tRNAscan-SE` + * Eukaryotic genes (CDS, rRNA, tRNA) are analyzed separately for nuclear, mitochondrion, and plastid sequences + * Genome GFF files include contigs, CDS, rRNA, and tRNA with tags for mitochondrion and plastids when applicable + * Clustering automatically generates pangenome protein prevalence tables for each genome cluster + * Ratios of singletons in each genome are now calculated + * [Virulence factor database](http://www.mgc.ac.cn/VFs/main.htm) (`VFDB`) is now included in annotations + * [UniRef50/90](https://www.uniprot.org/help/uniref) is now included in annotations + * `Krona` plots are generated for taxonomy classifications and biosynthetic gene cluster detection + + +* **`VEBA` Database**: + * Added `VFDB` + * Updated `GTDB v207_v2 → v214.1` + * Changed `NR → UniRef50/90` + * Deprecated [`RefSeq non-redundant`](https://www.ncbi.nlm.nih.gov/refseq/about/nonredundantproteins/) in place of `UniRef` + +Check out the [*VEBA* Change Log](CHANGELOG.md) for insight into what is being implemented in the upcoming version. + +

^__^

___________________________________________________________________ ### Installation and databases -Please refer to the [*Installation and Database Configuration Guide*](install/README.md) for software installation and database configuration. +**Current Stable Version:** [`v1.2.0`](https://github.com/jolespin/veba/releases/tag/v1.2.0) -**Current Stable Version:** [`v1.1.2`](https://github.com/jolespin/veba/releases/tag/v1.1.2) +Please refer to the [*Installation and Database Configuration Guide*](install/README.md) for software installation and database configuration. -**Current Developmental Version:** v1.1.3b +Docker containers are now available (starting with `v1.1.2`) for all modules via [DockerHub](https://hub.docker.com/repositories/jolespin) -Versions `v1.x.x` are installed using preconfigured `conda` environments. I'm aiming for `bioconda` packages for each module in the `v2.0.0` release. +

^__^

___________________________________________________________________ ### Getting started with *VEBA* -Please refer to the [*Walkthrough Guides*](walkthroughs/README.md) for tutorials and workflows on how to get started. +[Usage and resource requirements guide](src/README.md) for parameters and module descriptions - [A synopsis of *VEBA* (PDF)](presentations/VEBA-Overview_2022-10-18.pdf) +[*Walkthrough Guides*](walkthroughs/README.md) for tutorials and workflows on how to get started -___________________________________________________________________ - -### Frequently Asked Questions - -If perusing the [*Frequently Asked Questions*](FAQ.md) doesn't address your question, feel free to submit a [GitHub issue](https://github.com/jolespin/veba/issues/new) with the `[Question]` prefix in the title. +

^__^

___________________________________________________________________ -### What's next for *VEBA*? - -Check out the [*VEBA* Development Log](DEVELOPMENT.md) for insight into what is being implemented in the upcoming version. - -___________________________________________________________________ - -### *VEBA* Modules +### What does *VEBA* do? Please refer to the [*Modules*](src/README.md) for a description of all *VEBA* modules and their functionality. -[![Schematic](images/Schematic.png)](images/Schematic.pdf) - -#### Stable: - -* **preprocess** – Fastq quality trimming, adapter removal, decontamination, and read statistics calculations - -* **assembly** – Assemble reads, align reads to assembly, and count mapped reads - -* **biosynthetic** – Identify biosynthetic gene clusters in prokaryotes and fungi +If you wish *VEBA* did something that isn't implemented, please submit a [`[Feature Request Issue]`](https://github.com/jolespin/veba/issues/new/choose). -* **coverage** – Align reads to (concatenated) reference and counts mapped reads - -* **binning-prokaryotic** – Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment - -* **binning-eukaryotic** – Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment - -* **binning-viral** – Detection of viral genomes and quality assessment - -* **classify-prokaryotic** – Taxonomic classification and candidate phyla radiation adjusted quality - -* **classify-eukaryotic** – Taxonomic classification of eukaryotic genomes - -* **classify-viral** – Taxonomic classification and isolation source of viral genomes - -* **cluster** – Species-level clustering of genomes and lineage-specific orthogroup detection - -* **annotate** – Annotates translated gene calls against NR, Pfam, and KOFAM - -* **phylogeny** – Constructs phylogenetic trees given a marker set - -* **index** – Builds local or global index for alignment to genomes - -* **mapping** – Aligns reads to local or global index of genomes - - -#### Developmental and Experimental: - -* **assembly-sequential** – Assemble metagenomes sequentially +[![Schematic](images/Schematic.png)](images/Schematic.pdf) -* **amplicon** - Automated read trim position detection, DADA2 ASV detection, taxonomic classification, and file conversion +

^__^

___________________________________________________________________ ### Output structure -*VEBA*'s is built on the [*GenoPype*](https://github.com/jolespin/genopype) archituecture which creates a reproducible and easy-to-navigate directory structure. *GenoPype*'s philosophy is to use the same names for all files but to have sample names as subdirectories. This makes it easier to glob files for grepping, concatenating, etc. +*VEBA*'s is built on the [*GenoPype*](https://github.com/jolespin/genopype) archituecture which creates a reproducible and easy-to-navigate directory structure. *GenoPype*'s philosophy is to use the same names for all files but to have sample names as subdirectories. This makes it easier to glob files for grepping, concatenating, etc. *NextFlow* support is in the works... -Here is an example of *GenoPype*'s layout: +Example of *GenoPype*'s layout: ``` # Project directory @@ -136,7 +136,11 @@ project_directory/output/ project_directory/commands.sh ``` -For *VEBA*, it has all the directories created by `GenoPype` above but is built for having multiple samples under the same project: + + +For *VEBA*, it has all the directories created by `GenoPype` above but is built for having multiple samples under the same project. + +Example of *VEBA*'s default directory layout: ``` ID="sample_1" @@ -162,5 +166,16 @@ veba_output/binning/viral/${ID}/output/ The above are default output locations but they can be customized. + +

^__^

+ +___________________________________________________________________ + +### Frequently Asked Questions + +If perusing the [*Frequently Asked Questions*](FAQ.md) doesn't address your question, feel free to submit a [[`Question Issue`]](https://github.com/jolespin/veba/issues/new) + +

^__^

+ ___________________________________________________________________ diff --git a/VERSION b/VERSION index 893ae19..5988a6f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1,2 @@ -1.1.3b +1.2.0 +VDB_v5.1 diff --git a/images/Schematic.pdf b/images/Schematic.pdf index 57869a40dd7767356eb5fe53b15f2ddb3c96be65..35e29e5e01518007ab306b2d638b537b04642211 100644 GIT binary patch literal 90350 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%0Ablb@qhzz95Zc`}79>mHs5*HUQN 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

;Ee)t}&om8rdegH*EV)>! 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 z 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 diff --git a/images/Schematic.png b/images/Schematic.png index 1c5db4f5bcf812c7ae2cdf623a0f3f377a737463..fecb1fe168dddc168bbddf3b84d47c69b1102427 100644 GIT binary patch literal 754668 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 I9wukx)x?Iuq%*kzdp;iTu#5w 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

y=w~ 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�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 z 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

ZvEwPJDf-y1^fM;Or}k+J^P~SzQe14n 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

**)eE-PLAu%?_M08 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 literal 136615 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ξcY6tl3<(G_}}Ft+WNnZdQY6BZC*HND%{s zhqy<;@Qb2?PfthzcqB$m9rEDehZ>Es3omQdLm*cI5nH*E+CbwlBfr5Pkk3+ zjdQmI*L6{TG!oGNb&Z6?tqf2R4><@>ngO-?$6*V~n>%-`epzThQd08b#f!=HE-0;V z7y&S6xVE>C57g!P`Y*WI+_}2@2oDCtDTH&_CkMGV3#*xtf;|Q=_#cmyoarv|G=z{j z_Yb!uI3gB#bi*C%euT2l@ z0xdSDqA~>Xgl_5$ap0`in<0uN{(+_-vt2n{z+_j2@IM=?Y}^7h9Dzxx@8GR# zbwjCEjKYYz>yKG88Iz0b7xmq;J=Xew@z0t0x<^^FcUn3cc;r9Mn3-30m=JzHW3lg z*G?zgzg9K^p^4P}xCDnl=+|&PXP6#Hs_~z_@7gfw*tphd#INA69TgFc0Im4Iz`%F^ z;NCxp6-((+Mz?MYb*yjK$X^}C(F{BLCQ?PlW7vyn%mc98F+fZ}^dZlTFdIFzqcvb9 z-$JTRArQ>x|LtR;n`h^~|F{$_dE-3d%B$Chg9v*Hh>RklX^}kw4Mr#+k>2o*da-ar zNfcl{=uw{Jq6uLvi$LHL9`Rr676PLXNNUA{x3WX~Dj$AT<06tS&~eIgC{{ZA5DA_J z=`lFnHJm?P$Y=d$jrs?e4L2?lLazBgR+EOuo-F9G`>ip+RY2<=k|R6Wni=z44xH4b ztu37Y0KS1bfWSOEH(N8uU5F+BT#o*)Ti%lW@6|08WEduNZDwd&?+@G?rc4ILpH&VU zw^*KbWlhyW?|nf$wYg9G5ri#i=I=}RKfCl)n?junfyKUA1%p>^WEAWGIh#*8a;tOD z(WtuD6USJ8beQhue)M8{k74m}GIQ{E+(YJAeg?}miKG2a&;dHbXw3O{5D|zQo)RnHyPp-R7HT~{%xY|x?hR=2 zxpcg-Rc}m?)xBbKkmzRb6&I=GqU5En`-zEZlCM@QL3e_u!_G+9{%e)C|2cl%_Pb}O zbX`+?0CG7oxfLFdx+d7}0B$n`JWt(}|P)(ZDc&p3AhA7|3ES4HU@X_P&?BJ%<}Mo`AN!s+A_)3~=Gu zF7pzD0{kW7GNTaDU#YT0b-LB*AET#Hei!Oe)?C!)tkCm31Q|^z|AV2!En4p1=gb+- zGP~=Rzjz0I!+V}fUYVv!x+XHPBZ@(_OleOiVH99HuG)GS2Tzt)Byg6xC*;cgn{D_W zL>8SS+bKKsz1H_dYRvszJ?Wl9z@*f>^y;Y4^AE_`&HF+p_0yh0<6m>rH0@j2-H#Tq zTBho>+XyG5?S)?-a7DdU;IXz7O7TLaFF66t!Sj1hyO%N3+I8y$bnE40Ia4_3zY4y$ zaRs@;p=QN9?y=n;b3aFGAKG%IyilwO?3h>Vb0^ha96Uk+)dqP2{?hxIoCeoRu}Tr0 ztgAF6sTEX2DcB&W*HdK4QXF2fw(NCpXs*NIN2g_uqYHnz&dSYh0l{XLfN8x2e*sWC zo3G0o8hQxU5;l>lL?pABR|*Oy8&(<1$Y6mz6%a^Va-3>72j2jAa%CbwEa!Qpe(pdi zJT}-fntU*|y1}}FaR&@r>(RvWBhS8yi4-Xh7Ohf|fsEe?icHR4Or} z*)vQkSjnGw&*j21yXv?(52!R^Q7%8)+#%Cw~!~9e*EBqjcMZhZpF{jYU;A7T9*4oL?lMdK^IP{OKBpO04!F3w;?FoloT zJJRIV;?+kDH-l;`FeRcUzdZfa03xFGs_`}C=8HYm3mH?W`}i7!*Q#wVLioLo8LH7j z$Ar>6s7%DX!92w)#r&u|V2KA5@8L2?uT>>N$^eGv0y?@4Kj6J#lYLlbhBs&f&^YKW z5CXH}2jzU&8$kh$WYA{+_X2cRJ402Xa;6+~vw}p&Ry?00$E1`O$Fl;uj{94$k$Wio z1H;rxQ<->;PHu)*D!9#xALP-%MVL|4>W16~xF4-Mm5BgX1n(pGsWt!SAd(J;Q1X|v zPSrIv$WN215-k__>_sNCw6(P0hx>_qMx6w8VcvCgC5fPSH@k>D(P^UEKPG*31hG14 z+l2?GWQMJm#oHhhyJ08Y{Vb98Q8KxI@;AYIwo;&fOIB)`69Dxv=#bjJ!Lmq=*mnm8 z7bn6b`c&EzsR_nL*=t_Fj8}yRbUH#-vBL+<4=Wd39;7m0L{-5*t2{R(&Y1n zWaty4uIttA<)+UaNaHv=`hJtKSeviX+BURfJtv}F`2^Pf@oqnlQ$7EETq2ob-*S{O+&oEAZ4 zBlAxctG5qiq{aoavHmgY7eJqhcq+R`ZI{0<7}H6jMY2UpXP+EB@XJ+Wls8=cU{Le0 z%p@{R^!a#0qDth-_Y+dxf{l}*8k}AFoDU~IC_EL;Ul92TPm&eRA;(2E?G=5NB2_5O zri7aA&B~&JqV#S@XEula#xlu=xf@kGjxxK@Y##mLREC>DWggdnsD!<;ctvopVspX$>S+#n1TmJLR z;+!lgMTd0>z}+Kf-RUwhtI9U$vSc=914YFz&AID$@qG+rXX{11;vtD|#c1^I*z@43 z>1S%{Xp4=?Enz#i$^G(fuC_S33YjrON`YkO19{dJ^*95HYDJRQWEv#~t3!;M38HtO zE+;={+g7?`i&js-FqijQOoUX zZR%W3dJcAWB$}Loy4!De`VpkTyx$xK`X(g zgtE1d>=+odaeS8VYX5mrb7RnXSUA6$M}k93(P$9%IX`^o&ksEk@)Bg57XCke!u|2` zswBRfS~?~G|V5GG4np)`U2=Og%v2TxeAUUAzGwe|x_ZN#}(w&yY_wRh3YPt-5FZ%q4bc)d?5t9L(#RrS3}>djPjzXy$TGxJHdoFB!Rb0 zki?hdWycA+LK~OmmqVbFfLOnuj)}1Uj>VNu5_a)%@oT3nxW_GW$le@=NqOHI7UNfe z4krkPPCss=7Kye6oUL$6M10WSCc)Kzdl?`1DJ-rF9DiRmg!K^F-hbZbY*a3A z)PdH_=?k7+fLVp{kMTHH=HOpFjU;Yrsu}TxM@`(c_QyrVvngYLcAzQ)wSyqnBA=D|OYU>bg(n zYGC5HG{6FX1MSJeWN)=*?Iw{a)K%VWWjXnsDM(0UDgfzh(T+H-XYhf*$>HXGOGnTp zg3cvJyvzcK@@E#M=7UgY2hsuRq7N=yhly*3zlMf3Wd$6nH_taA zaB}k|s2Epr^|W31(qj#NP>Vz?)XpK&v>Yw(8j`dDM_IH3@XiE8rb=~+)B*w%aB-&f zs%QuJrf@NcOwiJ#%5wZ3Gyxp$%PfH6FtMHgE&gHiu63;H^nh2vruv= zAn;Bo#buWH&KP}*^x+DS$)-7f2?iRuB5lo51!2K#$?NLWSj`_X;Z9V{l=+7%mCMYU zH3b6UnGkEKGS^*l0w1dzqRY-~29~AV%eq?SYXSZodDSUkR zF$LZfX$N02C}$A76=7$8|Ni|6Nb?5Bo~x(&0TAj5-(Qmz&vaV^iKTrlAtp%Bs{jkv z!U~Fve|#ohKC=Y{oCh-wHt5VqfQMPblyImmOp*2 z6{EoIcAU!kii4@fmn~f$x?$|yyMBU?B}I`2b+-;vaO!ws6{M%XRuZn8bIcK*tkYqc z(j%?(LK6-Wg0d)zb^k5t(=oIQ2*N#R^0hyM3&uHX$9~hb;G)bTQ4su&=+7wk`nW5s zt;`*0U5)<6MGP(BI-MI+S_2vWaIRgEQ6%?hE#eZuCG|kR7~g8(eLHu?N4{m=Q5|N`9Ti064mu+#dHA&s-M`o-B#9E8(YrpT|PyTY=S zYzpzc)Ua_Z)QsY7{Uj#9%J&ZPE4>1+dZ?!*MN#z}-IKqc6M=R}aEr#mSFlWzV6O?a zN>4do^nWyUx0C*jwBi5yL)a-@@vld;|L;!(hfG0*@qd2{a2SLYvzga$RQQDG;k7Rs zODfAT|G()u$^SndShwbts-!VNmi5Bp;Ul6iIrFWN<3o>ij!mg4Q ziB{W(WvDKFldU?g+uG>Go6Q|0xaFwXE*sABcLZ>YVCaI+UK-Q9KjfOvgYZ0^Lg98< zr>^l0iPhHD`5Jrqs@EHJG7>Kuuoqg-qZS)+UR+{vt=HX6jKXyAG^48PqT89`3V~5iC!Y+aR-F~i+c_l zk%I~M*a9=_>lBX|%l3aV3|Aljb{zib(59p+Qz1X{>e zcRAx^)V@GY;v9sFM=N(o2*XQRv=q&f!zsnPwfHL|ba4a3y!itUG<(pteGCt9I-FdK zvO|qHZW?;$-VY4tV>M_|dp~!r@I#gS;kYRc>K!L@Pa=nWlPb z^!b_1WR;G;1DQgaCh7D2G$~SU>yI;!bKN3P_f!j*W}A=7WF#uxaTd}Q8M~d)-?slu zsI^|HeTuP9%dcHq{J4ZK!SY*T(rmWQNXNB8GpZj7M2vLG8JL8o9N4pMdpPP2$L*zM zL^H3o?^?NgC6pN6?@giJ+jotCG!)t1qCbu8z33NFpJl&nqvk28Zgc8l%Z zE2{B#5vDi?4~j2xPU(BS{}n(`g1^epnG)RS4;ZtmjtfD&Qz?5 z+UL2P#o5!FEL)to+SJwto!4Uv0Tf}h+(c6>rFoN;C>pE?V-G308S{}qFeO$0OkN`A z&A9TVUH2+bvl}G>-mY4vZ021eqIRY@n;sz#Y9$WXTGml@2kUuX9wlu z0|!H;RQ4{J8NSmj`$)BMok?3_pyOhmT1A~E8_pXY{g&K>4vI7tpEpWo%2hf2)N&4_ zd@7OpzF5jn&F+}c*Vh$4_dmv$D#?5NSpJTirnYo}$obda*E9xn(Uv)Ey$?t1cIp4; zpjY|JB+g(siHx;-H9zq7_GX@Noq9*pVQ4swOLPCrBl5VJb#g!Y*XW`Ap^067f#H}f za?#8k*1^I05i+e!)-we4)Rv#dP_4B>MCDwptAoE$_!07$1nSl@KWh|Wsos8AYV&OT z=e$9fj4ySJByO-4g)(a!j`#zbS>gy*X#q8z0*9ZSJk}qrXWF%k9zH)xYRvf-pb&Jg zH`(<>xLB+D35|ws?(=IJYA5RE--^qa<#nn!XURyBn+jtxDLLlEixR6g`k2qml0)O= z&)f-bJ$2d%l$XQjcpeqplV_u@VYwl4usdT*@$1Fh{tHLjR_llnJAbiXcQ8(|p zhseZvy?kkC@r>I>k(5merA$Vn4U4E-n^iY0gZY70lnmw4tUAVGx7{jky7=>|JU+y% zF!i)B=~aFS8vgIpbOe?q>Hg#_nh&@O73)2jJ6Lqx_g%Kv*R>oexe{uaaUx&2>`_-N zu{B>1fj#bow3ku)mHpAae83~sS_>WxQciQr+DKM|Do2^YJzjfNr_@O1j*@C>^tS2{ zRLmB0w)OUF7OS<6hK>B%k}?sq_hgeI=cb&_Ylx-#6f7Z`7g1d_iQuftdTn!~oZfB2|YqX4Hxx_2-)r?*@r^S=b+~ zh_OlG3y)BVNK_ubrPg4>QFXMTel+e@uD?OADRX>-r#(k%VpF?@j^yq~n_0GvgC&CSGpk-({iEw#SSJ6mO0S@K zT=^P7(jk4Y3UOhADrLu$57g3-=|o7039I00{Rr#x37B}Xa+SJ+W6rQ@yRW;o*g(5X zv@AC&svww~WM&&@@Wo1e?zGLE;2?v9M4r=~K#j_Wmo+t34|>Hk+;YU3?(Q05EfqE| z&@50cC}te7Y4nC1lPleBN>{E``%rK~;i*hZHCFFeDviI-O~~#btYA$LY)c_39-7IV zduz;j`9U!=icDl?zQ##!=yeBK`V0sZy`^tk$jHk|e|7cKrxcYw5iDEHMF&Z@x&9|8o7 zJFvHMiC@g9ZwXT_M&GfXIL~vd)mC~SAhup%pf!cQSWo!FOt$Vje}NHd8MEW<1YE=n zIfXi%q8NzanC3FK_Gfc0)>nnH!cG|G)5EMWwqv?0MiRkFTd0-h#sbUgu-c&ek@aB* z31Liy9z_g3n(0ZWO}z?v6bzz=lSke?YmsK{7}D(9?5SmgmsF}1*u2`9??)P%gMA_3 zu(7Y@T)Nczg)?OK(p|DB!y^`n#ZS~R1%$mmoqGo5bU#X>s` z>rmfO<^>Vb=VltAF2UQHZ&b)mO^lLPGMu~VI=;R4OcBZq;EbZUjPejM`c5Z`)|4u5VOiZ3-FS2#+uZa8S2#X@p9LXvkW85$oEHlCea#`sO#4V$Jb=voJn014jEvneRJr z*f?^Q%(V?+amL<~c)CS#Zt=SEPIrK{JpM0Dyl{?)9eo<(2NpT*>K}HrixH(i2To?5yuZ`6lTZNVt*!P^CG`y=pE%%H^r(C z%CC)VsV8M|o(Ats6O7BP*XB+f|)gb`t!!xP-8(Ea9kwK5}hkp@Y#~ zVemO488yS|)Ek`;jx<4=G?NJmP7N!g*BVoe7~7)Y+SZSa9Y$|%o0{)q*RowVLXhzm zZ5boYGHaFHRiVRb>~Bfo0a>W}{mYreY?W#vBi$bdEEgz-m4!Q8G8f&unBB~gA@SaKI9sM=RRzTJAr`IOA zgKMNo_Y;T5i(7ZjoIM*+v~)1mc0~10@0QH2Y0+Jtd4@WAF$jEwmF=NG6{($m_?0Ad zK=1evA=4ZbpB}0k*Op=%mASXIoiDYn(g>OL%kR$`m8)j=^B8muxF!Yaa({X+tf9g~u>-uI8#Rv6Y zOu_g>a=T;`PL(H?b|U`3A+uN7LeF#TWg{Fc*@Afn+sFZkGKrxS#PL9U!w5tZkfzm|HvHig*v zb@&H{2??p@qzg6Z>)w&L@%5x2C%kEk%jY$1WK-&AoMEo&Ush%&x@-Mxo`uiO1m|sg zPvjcnJg0`N`*Zncm0VW8^DjV~F8B05*=UC|98b=*srEpg%4|M1)1eY zVs0lk5-%9N*-DJ@T2xMugKgVnxMkc@KO!z)54G@U>zWw1C9X}4M|o--kgGyDzy9vtoo)YhJ7S#Q zC7$`dv+wAxwB9WG`_}$v(q?>@>l$15|1*AxK;~w<@)7M{Tcr#P5b5{e6kyAKQE$(f zXw1_H{u4Mw+W-1MAO-DzI_+(l@hPX&f{k4+g3J5$_4Vm!8JL3JP)73PDy~M%8+927S|Eg5MR&76 zS+R5LoK4Ms2@tV&K>ukhO?VSBHUt7NH_@ApvkoQ{9L}H*zV@Da5(AU$d6SOOKo;uR zx&tIoHqAaj2fV)Cvrl4($gf`i<3}jm68g{;3PG*&8Dt_M<6u9x0p$f3^^U#fG5c}Z z8}$tT@xeK-=|pyX9*&u`C&?V{FQEe|324T}4!4ycpK+=E2xO>I$9WS8ZnnX{wH`Tk zvP`K4HLEa;@0_H6^2w{ZI*Exq6KO&^d;(z!Mq^4q>8PM$^+6gi+HP6|G_!@RqP%C# zyiudSr{V|(_jl%UL7O@>H84IgK{69K*z>b3F)&hMG-QP%vH#`|_kjVM8Y`M_RP^-i zit`poDaiIMK)&ipq0NZsM8L@>H!pR4dA>NH`1alTTyFwd=(LsN~ z0Cd#SQ!}ky$h<+i2-1htETSxrN2=ZX3XDO*Brh%fq)rH#>=T$7Eb#VbTs_E^@Dl(U zS>i7WZe90rDCYi^4ZTe;`g-Oi>JCO)Fkl9CPfPFDB4P=i3B{4OAHgyVU61P^L0H}T zbID$_ir>DBNTU8?hxw4n2}y!1=+H@h?55ut-HDqHJSnHJuv;+Nv=!@}Qv%zb=PfMA zeDw9WDX%2_>|^Vf0B%F!RBBAWZ4&WED&XK>$A3E?&5(3VjGDJPVCAj#Pm5+qvRDF9 ze$zuh&B!nK?B3&+EuOzVuX6m`09$~a;K$PXtseOhc%0?J8xzfceV}6a_xMXwH-w!Y z6Kqk5#j^59N)vhrn7CboDoqRF3!>g#$apI)0Of*Vp|M8>f z^+NxglAM;^K1nS-T{_o+`*@ZgSod&Xz;B4<<;i2-d6eYI@8Ex~HN8KdkjcpzRJFi} zKM0l`UVmZOe&_4s;GL7ta#w(w-~0HP2kWrY2Y;GU zBCHV6oA}2m65c2Yx(r_y)BtSYLJd3~KKeiLxHU_k4F#m#kA3WM`k%e&GbXyiyR*~; z)n6d-uy%~NIcWfE3V+5UP{sDf!>u{%v$L@k4hpU-lC@2B&5ysQI4e>^;a zjIfDkryzk_ARw;wX!_&Q|Nafa^${ZO4Z$%N*e+@g_m?hV`-=}f7Id37&i=8$cgTYJ zF5^g4mjVTcL7tA$J%Ft1)C|n&%)|G2+V*iUU-IX!4xZ!BQ78s$VAGrTsZ8L;Q|KhR zRJ9rR)C#0U2fGEP6aDJSj(&pBBh&$U{ME{C4~T7z9;qCymy@nyiE}c5Oj$wRwudk)B6_!W){dxYoULXlon0WjomzKKH<_(&D<|&mV52q_%yh zlVuUE6C}l}%d=gpmPU(%k8}#Z@Y%#UZ5L!!x-LHGq2L6l>ro%ZinHh}D{!yK8mWGG zw-Gv^QCzz$6k=RUl2`f9MGz4|tan`$Oie^7QkvKGqg0B_RN7BWDg2lH=0faaLl|wP zF>%2UwYjf6>X`2Skdzo@? zx|6lmDzD_vyYzN9S_LWsA{wWltW6FY<9-l75v|ItSCW-w0*TA^Q5b-0-JADexEg(U z02O|6@`{wy0*|kyUAp;XbS56H54{?=YxY!avfkdd&wzHnJoY)N$xYBwv9k#Zsh~ru zx+cs$Er13eAQY&|dVBrRXQ;-A<+r>#H#JkSR6UTc#-igfo1WUKa~m4X#oxBKfK0rR z@tdm(o#{G@Sn-H1Yv}Y?4Lt%6(TsdBJT^pc3)Bc{$gcsH60KW%^9`qRp*2?dgFLA3 zh7Qouo$Zp)nldSH@H6z)#O7+D=6hGbe-|w8t+hgxhRV)euC5dX>g_f1Vl7)BGoV4Q zl8#QXg@*eJF0>pdW}z9D-vzBK?GKvxR>+b?{3?(X=HRJ%SEgh^tZNgC{W4*n22u8Lq+EHP4U#^%^8+GM{N^8D2i{tANS3 z#|cOJOO-e)%Z+?>M^a?o$Q&b-6=)!(D6yFBJh!nFi7B#*rRhwXLgqZ!-um$Y?ssH( zWU18~RKESeo}p#WbW7a+;`W-wxek4|{C>lrM)9H9E<*dotc}YWmC7GVHM69h*El%- zbOSRUBlREwfyDl`Bll8b?|GkT3}J+3PR89_ZyKQ8GJbIKTj2}dFMlq?yR9}t^JV6% zsl?Gb4()F1ZTHXJ=9M*h8+w|_9%Ldfy}g^h*BtENC$iwjPs1qauqc%@&p=&N?gv|F^yK4P22~N5+Y;N6!@|>+LC;r&TOu5c>FFvSgs2G%vzTND$ z58LT$Z7MbOEmmu_J%sUol0S@%W~pLf{~Acm!a23%KJ(7e`z$RRdKxN*nEGU_GmhuM z|Ec%OnW?PVVjaqJ;5;`qK}*XNdCE{iBR_5g3rUjc-k|Ym67z(%B8!ciVb`SGJi;gC8%dNl8P}lKr9n zdv+->VjyK$e;i)CmzE8^SZqKgo)Ez`+nt@M*(9u=qj+_kryN92HOG$?>mSfWqvd8B z;zV{~K2?`&bY!YLCG=Hw8S|p-l;bNXg<`R7z}q~jk&0?0lTxy$!Vof;G_Bk?kGuOV z*RZWnYBf7IwFvexVh~H_+UZXxezeQIqaGwpU8~jZyOSwR3z2DfRZ8(HxI~MW(wN{A zjaPj8{i#DWCN+TsKdahI2k1!FXZ!AGm`rI$D@BBiSjA6^xu(i^1h&0PAvGg=;g7 zjKth9hpIq@u~?N%I^Cc?LMd(!*Z4-pC$@B0b#7#!aHb_iVdrc|+MoV~<{cLUBV*TU zSEkk!RwgB*fJ5zxP6T#~G07h++Jz42qt2ibn+vT26$Khw)74TlWEP;sOed?n?E=pl zOs{rI5b)HxaqkT6?IOSp7Es9+-735$pFnZZXvV@g!NCyF!~0a?KC1QmfEBu_Sz=QB zLC>a>EesuPot)s;pXP!HY*q5jswbb^o*^c9SWPcgA(n!c_8sgnIHD(eEHPLnv?j z{jE;T=)J1_+P`nx(1u6ct;fAD5B72Wx6#$r=Gd1+Rxy0h!e+`%a@!^V%h{_iE{CgA zmMKa-Xm{5cMQqnZZcUCymFvM?!qW)@=nG0o%c=HgGuJpcJ!6;l3bUG#`2&*BKXmvNN_HAtmm)p{6=ZiaBh~Uq>T_Hmi#;&7U zPu6!nf^#5zVE`@cm}5HEy}F(`+>R)aCbh2_yyaE~;E=WEN$u*tUUZ!T-D(@mP4t1A z_nAxU8yXqvD^XlI$eL;-UJIy`tADW^tUc)QI*C+xJYgDFxPNB3eWqsiI_ieYx|zKs zP+K4%)MEFl3x^)3Sgg27<1F)8$-|M#Meq<75m_EqooOL)_&(O}H8H9FP#$s>$@lc* zvm=0W@CTz8w0-@M9@lpoJ5j8vM7iptm8rR~Ny|KP-S;$1m)P;W7y3yx#UISAtYLW{ z$O$F=&O5KvQ-Vdx89|CK()Kk13crjMU&@gvHLqHa9b|H^!pthf3dlTY(~`{{7DW6+ z4*`CKF01Q+9Auxk!1Q(KfyB0%E_>ev^TSY;AcZ*kxUgF4>lVi$tqs7wRfntJnB}NC zv#|P%uIpt0MVt0}0hwFdh@qYL$^tS!-MC?Kn|(sabYtFPb$xMh=*p*0JkJLzt%2^e&=;K}a6MDtE5GxYqdZeQhD zGS%vLEI#x7y;x|?P9`~v-vy4SJhL{D1@&teN*A?a$h@=2##XEzcliA!lFdE60w9-(}hMxV^pp_cDcOh&p6cM zE!VK?l88>38y=lIEX1_Xl3>N-9Y!`=(zvU)#IL68^ssh9bFV6Uz3F$e?!7$rfC*@X zot`8(V#T@A^xd51vP+>I?p3YSRDWpL-~8j4MvsG*7rk0SOKR<-8XUBSt`zw0Tn;7+-SWng2I9qI=h z8hK4+90(h^&f!k3(rsK1GEx|#i?NurA!!*ZHiWdSGhmC(OAkW1I)`G?PBcK*;0AF3 zt5e(>>`3RS3)^!^BG~ci90Xwh#t?mX?W8c8`KjOvi}7& zHB*guG5l|IR*74M6LYt>$0cRBnNMG6Z(Wl~#2*|Ndt74I(AtbxZ_OdMR!&*&-a*SH zk?HjQ?S9L6dZCmEW)lfZmIvvfU;XE=3>`R+<(Il22LbKWS1f~y5VsfugZPWPa6;!g z(+#C50lyQIkib=MbMKH$*bwlX?<*9v>Mv4FmW9^WktWbvR|UcY2o1VQtn{MuL^lWR zc*YdhxeyJL`X;ru_Ms%519XF8ycT;~E4B!p3lUg*Z~#mL4NVY%QnIQ_Ea1BxLkt;Q zBe^lLQcN686UnMJ4bvL{6SO7zSS+7{=h`jG1dIzn1n=24Sfgb%WoNIYxL0aSHH9Y| z;!q(lBc-hDrVg~`_NpT&?*wzeg?{03>J8Ejf=oQv#jP*umUAsXVz!}h*V%TR*-%g$ldDVY2gD`-5G znOn_PdVi7Mmk*X9L?s_OwYAsxU*cs8W7DXR4e#nmN$1xpv57R>$=-MxT+F5=@b)9G zYuzdHx3*#>fFK42T6wHo3M@1>`D-!uuCu-@Oxa#D4Oc$h!;M)^e4X)s6>P@Bo@}Vm z?6J@vh#jhEe4}L28j%elXb(BrY)@JUBZs_6X*{h+%qph+1CU1H5c95lxOcbzYZ4cY zAeWq3+AgaqkEq-Qc9W>iMMQ~wO!`L}jiR)5yC!$K2j!}Zr}_#}xPTA$a5ypnmlA74 zy8I>gzz33rCrNBgT0NWxO2T&Y^YC7b3-PX-`BSdO#1|jfBtg5B+dgc@vFogV1tlr| z^d4JdFt^1*$0T8?a_zNl#H}=rITp%yx9p}tY}>vCZLXk=O>tI8U(!Ka4g{3Jtkrzo zhig{LV00EnYLHjo?Q}0y?=Hd^3Xs@_yL;>|lvjQ@$MVZJ9`iM!Vm6ZY+XC4e?XH% z*KO*q1$FMBYXxYX=(MVtyN3q0($4djmqE0``5W zj2^Def_Y6YX4j8NUC+}z)h@b*juW2U&jmOV(pR_>pOf^;FIjG6uy_u_0o$1to#F?#bPfiJ zEhUN1_X;~&H_gfV03E1>LE%cYkR;79_D-9N?Q;8vTkU-l7uXePu7CWYZ~JOiiKvk* z{Ql-F^O#QVQtR98-D1n|<_Ha&E5Wo5&Iiu+pOzHkBQ|)Z!b5WP&up(Zkd{;G$KR-3 zHc;0M!&67enH?C>TPW!&hW;IFs#O5RX5~*S`ny1ayN~uRRXm{x=L)bKqx^kYMtx<& zO@c12tkwI2Pn+e8a_=u$=*)`dcso+5lVZjr*F7S^xOiDm zMbcl^q#~6$Q2r8cMlRd#%3yh};@jZ3!-t0lf{`2{CaalYE!Ij|dMqR!m?u{Zr5nb& zGL3aiFnxu_LRC)<|7AI%hO3g%r>B|)t8GSraa*Jj&9Vv28RoHx`>cU5NS4zWoEKF|920+;>nDx?3k8J15F77L3)3_@Rt@#0?-tIsQCs zUio(7{pQc(kw~uJE~O`U$!DLQ{#Id%Z7(|5T(}nwR;9N!l){LOgXpgNuRiHOMLjC9|w&i-jm zhRQUvWsRHM65RRqvYWz(M($s!Rdql5+?8Z@Q0JYifc^65S9VRl$KlFX+QfHs+?Agn zH<39%JH*|tpM0vkY>+at#HyK^bY&r34e<7*&^-h7{$EUDa^|trKVlhrx2-Xsiba+w zc4~YKD$wrKax&cpDEZ%);3g$KJsC|_wczH)^JCHX?uoh0W&e2L-4$T`!r&vuXkqi!-^lyYvVMGmllUPtK9Fol1{{J==%d%S#TW? zs%9W{X1JZy>#)%e5k2zpIm73zup_D5fN1;63?B#-PTGd!7ayM(70;QHK(xO*N6(Nd>rV1&6KTuRC+{BO)f(}~>KxOrAfS8xJDu;OGo)8M+lMJWnERlS zX_TM|_rSHkj}b8W*X|s98ze?M|IrX8c`yiJ+`b^NVOK@Pum3twp;>PxHCG1;wNNCah9D?x1I8`S}94dtQmHd0Yu+%H0aEDpi^a63zilSyKw--8SQ2q zkZV8=hkos>m`onl%};PsSD*gzW0~gG>6vDh5bbxdsHObkti^CaR=NIaWT>Br?*!C3wUS(IS+!9%^_azl^nLI_@Oi8M84OzWFOp#_1 zXwjv6%h)f=xxk_ezDJs1$B2mIBKSLR$0aTyXV;nU7?^r7{hgD)+6HN#{!-E(Aj?px z)c}kchnv80QAhw>U#O8R1=nMzvCcoE;NNX4K7mJ{*8a%jx0|pw>xEu$+PN_O82p_C z*Vj%&4p{)!!oBa`Vl(-hS(I`#%2$KA5C%p3ZwN^;^s6gfzFBp3rib2GWR92KcB}R7 z8FrfJV7$Kru}zKPR~w~ws+3`7D_y?i?Y18_D4fe5uZVkf>LHoZ8g?K&8}6&x{XSL; ziwktngYMRgw~LB4vW3jGeviHWHUIS=8N$UrjD6%x{78+Pv%-SV9VUVEuL6XNA9?Pv zh1~0Qvjei(wABMW-9os#*?Q?#fpWE5!D>ScKiQeip1Z>x)jYP{%VQc%5$@?ovEu2e zUhO{5odA>cv#p|;)lGSKzs2m_-*z01a z-h#pTM;ehGfx)Oo^ALXx-2FNF3F6wu+|VPpAqI%&Mx6c@hF$*>SfgJfiqXogBqeR< zz*rAKqD54zrkc;yxHk{NXmwX*wGF2Dcp9uRT?vVO`VG^$l@+h2+;(o2!8)cmp@;a+fnAgD-Q{TPGo05g$i zxniKhrJ48s;Oe~A*8uaat%=Q|ARji3tlk1&@+EVY2mCg@%QihZjo*a?ZE{4Q`9)Tcb0# zefJJvoquw$q8(lI^tab1%I#QtTsg|$duVof&U)_e*4(m(imK-3|7yHeL$;rz{|lFk zDo*~FU2}T;1Nv`W{HS7JXo%O#yn5vE5K%Ae z|I_z^D(u&Pzl;*EYvu^1}GpDT0B|OBMdzrUP)~^x5$2# z_C76v59nQF3_#34HSH{o4!BJ~2Tco@_ADu0Py9qbul)c&A`pgJbOM50C<1kofO=X2 zefaw_G+7`ZsDygKQBr)Bx)Z)8mk}={T87%gXZU^u1fv(=??L#Y@Q>{9cX(&0575HD zA-*X5p;&cChqzl0)5BlCw(;iHN)@%>H90o#9&LhMe5-;ydhF-V@KS-_U*7KJ1?9Yt z@sIG)BZ-(8YJGe0Pw{MDnv#M7)GEIoWH*G11IqdTcvD7%wl-*0r0|b3e;<>VCr$Y* z5dTEIL8wQM;DM-r6$Sqv{vcB;jFV-(9}vf7x#fAr%`klqhZMVoGHWY)O1`s+;KYPLT?63{AZu3QEJg#uV*&Rw0B zTkd+`_9Zx&r{2FpP_ELg9KhKhIrr`2C)I9BA}bO=g(de3Sm~BX_=ZaQmCZ!o=lW|G z3a=yF9CuJacV|R$*BE_1?w)Mq&EJvQ{26jB%tZ74{vCoiv#tzqF&4J~F=Rxu5m6r# zvR7>ml9c4UD5N=()YFKYW6oR$!CIl-#?2B3D=_EA`;fqG)A~*&T6xDPY4Ezv!Fodo zKo9E1wyC%XkyS~F$8kb}QyRjL|JvwyFYNe7zYBnk$7fnt0r=$h*&F|d53T|$<>)ib`8sFNHj4BR|3MvGrdShjt5a9dL=c;iWH=Tbu3@6 z0J?gNjqbx=XR`G(>ITGbF8mgIK%N~92@yiNDs<25R%)+$%TH}Wi>;=pm5Pp3HGNr& zxxH-&z0PIp+B@Mah*-#u;WZO|ZHWakn%*W&>;4W>Z(OruZ}BSSJSeDzlW5c&Dyc*a z5(`5i6kyY7pcXVNCwqX%k~yjaAaCz>j#4~maoqCIH5qEu0>Ut4&sW@QF;2{;%*;BvU34MH`~ z!7wh($I_8p1?06F!n*&ujS0Y?uh>mDd^UxNrDsXpZCc|@NT{T3gZb4?o>RO;W~3Mo zi5(8+pKNtSmP0;wbglF}H`+QJ}i1OcgNKalhR^jENAZ+Ni~%Xw(pV zSG8`hEwQ4+?22W=T=zRj8P2oG3sKMZ%=Oq!wZLr@?prT~F_jxXRdS_B?5R0e|6Ut5 zgq!V8Wo2cUyn?e#HTkAL+moyP>Ba2+ncUVW(l|z+UEu?tLQ~^!5Xcy}#{*j^7J`iv zSc>GgCDq~5egEusqD-!k&pA2uVC4_!q{n?^%qC~4uf{rXBQl27@*VpMcK9u%)2`_x zy?TJ{{Ny>MWxfoFkVU~&pp(PcH04V$OJe+gn*+^ce*p3l)8DfXU0f!-PS^b|6v?OTxO5EXJ! z7r@dhWM;T#YLZxVqmUhi_Lex=`nn^>IbpK%>OD`q90<8Igq{szpIni?On& z<#yAPAWRjz^UH3-6WyqCm3#OExdvC~1Op=uc`i%)3hyo$jm}IJ#{1gZ%~F9(whcSL z*2=uRrk@s^0oN_mi#RiPQck<5VR>5-0-0|Y$!tB=Og7nL1TEAR7K$=q@6Rj^BPb>r zK+iOLMdI*dALNU`h6v^MF(s?`i`yfTI@?eKcQLUvQ$P|_UCYO z1xmPdH6)FYNm-x_=5(y2m&u}ojGoMEiV8Ppi z_b02hJoF!|0wS?;>{wx2Icuky8M~S8J&YFiAH0o#FRNKOu`fw z_@90xC{DM=%>p7)a<+$?6T!%lrIM`+zEY{YOSzy{q0n@sV*_pUjY?;0b!!DQI}Zji z{KgY}(1qy_eCL{my26VKxN-%IzDJ3^Nbz)QB$t;-6KW6FV_dEuNW~fF(o_ZH@J*r% zo1sW0FBhR4ed|%Drb(!o{im)Ah`n228KVt0XYJ0Hf`4%bxsBHxfboYhW=nC@!fvQDi}Ma}k=n>V$q?u%P& zPd3&QUyyYUfi(DHTShp?V1H?=h=+9htA!tTIXBG*9P%7?na%kWJZ8KlUJFuihI)Z% ze`)-UO{hoi9U7-v-VIq1oOWJT;NW3m$c6)n;nZ1MM1NJQf$h$79s+1gOTYEC8;}R~5s&ATeA? zL(NW%e0TE-iKvLJM8uk;M8k!2b`54h?e3Z?yBWA_-Y6`Cpgx1oURXFI=sEh*Li!f~ zX~qg~C+X_*Sw9ddT`Oq$JGiM-q6Bx-3J#A;RDWML({>7uj5@2HrpIuH{_$%-I=JqF zg3)F;Z*02UZpl1;=)9nL=x#UM%j3aCEv8lRn{(REW09RU_!mC=}-vaU0pZ{cR z`&%qCH>U@d88UPXDiXyZv798Gwh#dL*w2~tHhMyPxIY3jcy?fezmZYJ02bS^-)GNl z2oSb2ON$wS^cOp5uy)7C*EjsX1UA3por(M!gnl|7CBM#X`E4E17~`*3df>KV@QWYG z!|C3%egM!M7gP{#0s6MX*R;lu02t18aTZc*>Y^2z6hQVCOf5m(OKZdea9|eh)qqsbvBvMIs1Kk2%eFelb4>@( zY)d4|?&PAOu@=M^F(aA$0QFBbM_8%1#`z;%(!t=$FLv#gfDPd&JOrLn?s~8VY^M?rbEpvNKpmc_Euc`)0Jl>&!iiZvvlXEN-juEh2x4>}b&{fYG+K)ll~X?e5J|>1S3@2hq{_}R8u1esX$p+~VT^1)k|&5}CmzL}4T z@0gY=Z=1AyLsTr9&nPeGV>;bgG7dnaGH6on1?Knw^w;Ggc;~h8{oM}b#!!ESb-IBDje7A_Yp} z%3~y(HwlEPfIX=HbPDe}HOJ`)pWTO+fq6GC9szFQqwL@W!Lk|tV+k4YRDrgyD-8i1 z36Kxt50B$dGW*k@V;>W*0GB`$fM*OIGA}+QaN@&r5T2xXPn1C(WJ z-@SX6@EjeVweJgj?#=LVkR`+Hzr*dBc#=6u1A}oWc-pTy+^tz;7u%jB@2i1T2SVrm6@>5)&-QxI zbSYOjEL>I?`|QQ7Uzyh3`t}yD;g^7d0uXO->J|li;a|}W?3*YDhkWr;cQ*e0re%k> zw^k;)Bu}5e_GEX%O6Rb&W3fDmRB+3m{Qr-0rlsJXWw8f4G;*r;IfKm1aR63ahGf7=+%N00~5>()zoa zweLbXVk!Pmg4a~wa?bnlRPkSrtWJ;y>QF{O_~?_dlIGdq_B1Y65GwBx` z{M+AO0YqkwLc3T;^!^+YUr0CurGPCU2m>`MHJ{o0@781mKR}QM1p}ZiPlX5@jinJJTFVjY5||YCpa>BCc_f>!5!HoVoSHf z{mpXQsne%VyNvs?G(~cA6T`SoL4nf*&+UR>`rl>db}wT%X?QUb+*us9!xTom&+4rm zRk$-W0iOe%Reu=`0s?&DCEhv^5a7FL{xSmuzcolx=8@qa=nfQK{y%x)h%6;^`MY5* zN9B!i*h6vTk z`)W@-k&sA!^}?^JVxyX(`-^V==8H1F(`*h}2uZD;97_a(T{ADcmzd(bO5WzMX8q|6 zF18oji_`njHZ%rjs0_pxmnv(cE1h|xqa#lEiA@;}XiRBrucV_ERlEV~K zsO^yC<+JWc`L;Y(TTcb=GxPK3E8kbIPVt>3BD#AuWawT&^6uTMaMcnfeX!!koH1Ka z)FwFvqPJjjxMNK1^7hgF!we*X4NL7lB#Qe8bP?GVa!{LZt!@P#$m+1c@H zdjI}%3Nbtc1CJnlwV$ETz@FN>>F3X%puYFV!lCa7%h=tVUD4U$c2sn&C>bf1wBrPp{2ULn>-6&F9(Q_s8*@y{BYNLg z>kVb?lA#_A-<3*9K8l2hC>q)R`>%#PO(y2CpMNU5@$WzR^`@?GLq+VIg6FD(Fr|Em z>AWBb370z5-JLqRRHIfJx=GAZ;v%O)8L6qMGqBO&yyFNAiijkF8<~@a zUY#VGh5ESHSa5y@JK#_Zy1^-<6U5`su2~vG zfkys!sA9Ng8U>f6orTWm-*=7~q@#QR;j}a~%Z^Yw?pHp}`06G0DC0cz*lp}MVJ#&g z0mu1d2lVEZsFJ4Cron+gEHc9V@Ib^OAu%zTsu6DTb#@p@r|-Mx*#y!|8GhaN6RLPf zC8-d?AUQuj4`Bw;X-n6YaW8d7VfP(A&_v@jut!~e><`ey{Ex4*J=H#sYb5&bk0lwK zW>!>jvHhJp1X3;8dZiJwFfrLrC#@(ye`R?&xV;`6Fw_e8#ipw!!)Cn+<4S7fI(e4H zjI?!R1bNVR!d4RM%%McqpK6bm$4L6Y1De@-1^{_aQK&kJlE;0D?`|in25lh_i297Y z5V)L_11Zc1yn#b6fdmt_?_+G4Ohp#T+E{pT6$-vvk6* z#%$nuZEz>W5ZQV{>!1tnpKa{I2^yVK6?+pP+0|i?)06fT80)BZEe7kwRVKA&P={t;* zjL7^#X8YD7Bu^qZ2p*MxTBGwA6!yi_>#N672uY}zyeHUjgy*9GOh%b`f5J$*VWd;9 zddq{~DwGc9Qjd?j#GFhV5JE#k)1Iot{XNbFs;K&l%z2HQlKhk?!5w~C2S|1uQRQO) z6yk%LYR4ryO}!NPOP%pIpNc2*i)Z08n=V-Z3as-FSg&ID^>WnY@PMwwQ)d)VBlm~&*x*{7AFzYo z-cx7zE0qWd2@Q?b${k0Rm!Awd-X8zHPM7Erwa)7UIYG>);p9S1UY|*{H@37@|m>iFfPa`GkD{o zf>VhrDIV?YZLhI`a$cHLdm9`OoOnZ0w4{)OutE$U1L9>yt}$he-LsF+>I_0)ioo7O zW|&y7o}T-x=IMmDY!xwx!>a+cKgmq9dF4YtLEB0k1)KV{v1>I3^?pzz zp{m|ACM}CZ9NL4Qk6iZP-|=&2;xPwI?Ua1mY5BdnCnbGnkwus6g~Qx^;18e|Ij!$! z22sSTIfm0ja)e^U$4lB1J8s0Hdqp?IV0eh9wU z?X?RN7Yr}TI^t*nd-00>-u1Ik%B%nvtnDQAGsh#uINa1lE(5|F3u9v|*e_Sf0j4s^ zQ#x6Paq3Z!J2AncT7px6(Ndf1>n#zS)F(-#TQ2b!MG06@t(qw-|CVy>*s(T(*3)`= z1K@;VNQ7V;n7o6vR>k9bz?5;^pJRcnbuFXSVm`97Y|s)lef>&U;aSV@?eoQCIFl69uR4 zD9#R$7vLmScToh~$z=FSHG8Yg;A!(G0Qx#vYSECaIG^e`5h4Yl41~0wmhb6rgCRRp zjRu)FptbvwlI0mSxvX$N!N3br9YRFHhV%OE)VYgN6Ij-xQmKmXl=0pFi$2LE$cpGw zBF{j<7CIYQLx&qTUI3fyr1O!SzA#uD>aJ_A6Is=XSv>8Dq97vVAMa!g$XdquxU5t- zm38zNTlyO_1IKXJ6HMeoVegX=1AI-uB}=0$pmkM*^3Gbq0jshY^~%zz)%?B zoRyW8$L(bGi8Q#|+{)@5n9j-<_Vc}@nSTEMuFIoGt}gE+s>W}x!t``M@|FUlMuz>- z(S{Jl`p7+lcYxI7w;Y0nuOrNYuOluS__ZF{obO{qAfF!WISY%{nG&~0t2oKM@o-uN z0Auz{&^3fB$$nhMt2bK}jdFrX3@W}|yZ!dj$Z^<9z;pT+~S@L`f^uB^K`Y3L5v zi#mnGgkMhkwrIo3;wwklGdr+j`_td3E4@a)om>*3>NpYi9Xy1+ogML+Gj(%Pz^Z_4 z1C0sS@iUc5ySuxYqHnlaBsrGQ1&WF8TepvCumso?|yhlzmn(6e9z~ET)6)+Dc)Y&VJC*5X6 zjJA1yAUJ9btW|xw6SRaP!;V7`XJBQ9{rNLZ6?xOEv=mIi1#W#B{)?B=I)lmq86=1> zK)THnco*2q@2@JF$| z98T@0%i8MW$+dlbI&zpT=#7>1Mu3r#5xODxqON=!s&LGOOO0}=1$GTgwT8OE`4?9_ zpj4f#`X0V!ZJz(A4AlHcLYo6YPEO9^B;rbriJ!jaOyqD7$~`Mx5Og3Ae@Jrlw>(xU-|>s48UdD5^k9RA5nKD!X$nt7|zj)%12#4 zGU2fjsN`g$S7a6v?1Sa_85vRlpsZ)=N zfnT8r2PWk_@|A!^XBs#a|0Wn6KeoRLF+}ecn5tJz5F0^3%^^@H<0&d_z~n?KF#@^@ib>J#@#?JFb)G=s1P~ zfJ*>F3tuDoP3(mvluE__=|OF>FD)$vI>8(Y)*@Vd z7j#a_wO6tsh*QRymw{I?yW7g~BHV7=gHg|)JfWVr4X*N}r)TYV%n?$r6tXB3uvN>m zEb;o(A*Pgyh7Gsc3u}QO-rhCY-hI0rpIL%f`C()Pf3+T^sLg6gUW1$i)Q@*wWLHvB zGVkx|I#^nzsSd`=KHb|tHt=L+Z}G8%7`V_lp{VFLBZ??#NooWS+IRfpu3 z2t?Fv>yia*Vy!EA3AzNN(w4w`nEAu}h;?1*gHM?wuYi?mQ76ywz7!;eZAoW(b_U^LI2{rNcK&7sZzrUDRNG8hl-VK? zo$^QYB{x#42E>&7u#ys)Bbt)Y2aP<*h)gtPCK6IWP^SekFLqqjmDq33> zoE~%)=PG#ocyFfz%!1{%m zXEQD{TxPCeZu@;+wQv5SQ@#cr#;6Bc9>sFZB;ruJ*IhF+_TfXuWD0Z0M3VS-4SbVV z65a`QeOzYzys>e(>&MMM_d&)1#g_Olo90x+4?z85!$_G#T&y>71gQhlY?2P{n>_04 zg;Qd#D4!V-{O|;-W1tp1cY9x71ztBo`{ic|hadX1tAgnb6B6bytNc1M<8xrgpzj-D zor-OR&Efjdj7lxH)6+pVvee{Fiv23kI4RTxdc^PLE>dZhgBNRRD<`c@R+y3dZyGL) z!XfY3|Kx_eV#`TNb$3ntM6VWsum?#iz&7S!p(8zm*PB_x*~F};Tr+WD+A&Gu+BbWL z;=d%<2OXXb=&s5n?+6VLAdvVeoX@}g2@2E4E+H{15R|ZeBIbMh|IYRmGxP=ugzMVw zx>&vH-#Eix54$e;f8qToo9y2{#h^;ozY&5?`4~k7q`F-M5cUnYEYM>ZM%XEH9@S0y zK2*#Zm|b`i^qi1sA*L?1>6$YDopC3CBotQxm}L2Y&vpiNdeAHhpul!c0I9BHlWyY8HW z*;ik!-*vpd{M2N6ymKtp(bLtw$l z0@*~l*$P@`h6B@wOBnt1w4;MwSfd+bV7xI?)2LDotP|=I%B(Hrz@^_pNam73CTc}w zFG1aS3bfE>#~Y|!LkKmG0l5pp#-z}Da&FS@i}n4(75Rb|MGkK;^uy`L7}Lqv@R5j~ z`^1PltV<$}NrF6d1j10WcbWxg!9(c5&ss;z@oscfB#Utcv}Q%+lek?z;b%Hc2904C zN4Yj76EGbMvO!Yt45rU4;CC>hmZL&xO* ze7HXBCCT#U20%1aeSY6Z3=Jnx7^0dBJk#DtqEOC2R}m>GueD1U`89dP@T8ieC&9%b zN~J<8$=#^7jCZ{-abqytyTGQNigB|?Oc-6#M)Dx?8dRMyx^z!tV-`~;Mn-thN;XM5 zgz4JT>gnkbNg=7JZxCEnUjSLyxw%!r3eh>YS#Xq>xs%;fQLOp4in{#*@Ke2$5;hlv zbFejP00fyoac){AGGmb$GIXvu0I3+YBST3wG@mgZ{gxXmK9xP(k-B-B@?gs$FVu1A z!+2^?P(9Kp6)i3knzA?XuiQ%on@egpMXlOSVNQ@QSFc)?aRvkppeR_yDQ`w@?x+gX zm~nv%gz9gjumAhe)GW1A6{azWi}IEmUo2F=fXq-EG9m|G}T0G0I>0rRCIN<`j!)@~FkHVQDu{@Ahbhc3@= zotGo(X1-ON>}$lr#%*Z`*>q0!(lc=st?&#;d?aLB)5sjrnPUB856az09O~pH>^Q5> ze$(XsVfo0q`RUKwUf`-?yRe#oc^}XHal9SE*LK6=rJtP9ic;|tbfw!rpbC9@di6kM zyz1VwU#F%nzs$BL6NH3XIy(z6>mvO~tFA`(jK(k5z$G@ZGhpihg%_6QCvb4pc*6%`-7oh- zoO~59FzJnBQOCZn`_>V|V;lpsP8%8H#FNC8X!xfvB(X&~dT;PX$6ZfkoSv^J@(&0Y zPH3S*B~sh_W?-NO^*KuN<(eZ2&K=j~NrFR6yLOdfweNOsNx{bP?@ZT7=gF}{d`v3G zF2isOM%LIpkE*H~8+jyAFJem9tG>P{4ykk)G6uJMA}X5ZTdTOZxU6C7j+m$!9jIBx zayAlqt$FD)^^*&|hFhs?250UG&hRqF`3!g}MzJST91muxItD>!$bv1g)s!_LaQ_x( z$uGg&NhYyb={ss;F7M~QqHQ-ikxfk7ROv>g*xgu)F29;ppeSp*|Ikm;1q3g?q=t+{ zzIW)xZr}Z#4q#>wx;)I7x14HC(`-}STx={FQ6l_mh=@2`Y?tn#z)y#`i$Lmis3xUzbSWLC(mrQYsN(}I5MZaSH_NA*-s?CG!=W5Ui*E9aM+e$BPFfzOY^O-fk@%u;@S!)mh;WSP`e=x4g87i5i*f8IUrra#~wTv5ytR zyK>{#^!)j2#__F7pC1j^EQKHYAzG~N*GIy)$A5qOiJFs>6Q!bI;S>LoXhn*uaJqdM zg-#m}MGf-1F4QOIJAN3jOFeNTyN}*g@7qf6ebtb*aw5fur~LeuCkAYFR%|zWyM>(M zNASBI7TE_#I{%$K#1m*!R-Un0CfiI_kU%h!zVCg0=D-#G-$2H22Y(7x%ZzNR*_Xf8>($f0cdxS$)gjJR z>Z)gj{2}>Y2IUb^!CHNvDef3W@D&moLF#CIbpZ3Jh3{P^VOlC``3vDF5xJH8&E@A0 zQoWUx2jAcM(x%c{s;us%31NpAN|o~R@-RIP^9pL7m6lxf2sHB{l>gw(YpZSYH!LQs&JxA)Gb zNdUD)NC$xA_m5OLySv9Yv`}~5IpFK%bty8k;R0|;3xoF7mTml+k=iM;W5LwS6iW^2 zPDjU2FtFN$96x@Xj&tW;JAswOH>Dlvy&7TXDmwZUud4`glLZnRToO=|n+g*X6E8{M zM!_EW-Rt{ECyG-7k+<^J4&)lLErKiPidXQA$ex15rniM>&zX2piye?pt5bATP#k|h znIvxBmiP5!LYO`cTp?55hPmcahCfDMY^Uu9{grz;b<+!=Gk`BWnx1S!HPNu*b9~j= z>J$PYJ?RFl5txD0u(QV?+kj6W15!ItcpI5$V=>;n;<9_)Ml z+Luhy$lT8kD<2CDfmrO?XQa?PCYx(`F$#=jG)=kQU3645ugs)%5>3YkO~q_xJa! zXEu3vK019uLgK;tWRQwE-DFk&^SQdI;?PC-vt+Vp%*l7DKt`lU2;pxHwoT z;HMBt10{668y_E^(fxs7|g8S2AviLINNR>kQE@=p%7#h352uBvJ}X>FYvkUONj&t!5qODCiq z&x%ISPGBUU@Ej`lc#jdj+m#+Vq3ZY2#$E-p)d?PqB7w*3%iq6-Cs$EK@#>|DSfXrPE!l7H+s+EO} zZDwj%Pl}t7k*_2Y=drbMXWhB@y&H)$<8LAofsy?ew@`c9!4OV91Eya?* zOj!qw?EW5ef6)Tne?NGCIApMA{tTXU*?V3MOy8L1dD(lk)##s|@L~+mE6+$FIMf-O|-K(zw#(^R6$um!1W; zo6}nTl|uIH*NGylir8Hhbs@j0vT$^dsjlgGTe({1?86x!Zduz87X<4+iSG@#qoS7h zWshV>pvE_GVrP?t9E;n>ShBSqPaYDwXk z2-}3L{|8dOTK0sDgW_nxrgVz|>MKhRD`wBxdP7}C&f9%#TTgxPrR!twk6+|TuU@^%(!)Xs>ZU~n-R95IGxer62oyh0&aL>Cp`Xmu zE=erzk6E$_Rq}P{-f}jgtGD-EmbuRPK|%Eu*+vgm9=C<67JI_r$LvS9q>jXG3_YT+ z96J(;8)(yMukRqbo-+~o+TZtQBvL9I1@MQ(2EbYp2TsPM6aah>cyx`?Wi~oGt z@hS%$%VnK+xQ%VqH=C>2+BD3ZmYO53=nFOx-zlf}HL_E;N6V-}g<5SZ0#)C?q2Cn^pLX)48LIK4Xi1 z-)3?vyl&HtYESrUp-79+P7UkI@7~_wl+=v8~fw-&WMH3j%yLrUszsw z^C+@(jmmhGyz{UT0W*Yu}O zu@0z{57#yHKW#TMz9BBslPx&>d9ljwq+| zsh1^=cKm?~VB@up>-F1qIorQ+#vuLcddkV)D0t|GBJ%X$g)jI>g0jTk-5ED4gx9}c zDYbgn{Jx}IDdIwzb*F6-kCN}otj{IW+BV1f{My#6Sz(-*XT+-aW9(JRe5h)g^Nhwl#Z2ERRxLr@_syNm1bQx@iLguU@ za2i%w&mP&S8T@!3mSpqp-TK{bZ)ACw?jDC;-rmp@=6#-SuV>qJi|SgT#;40=mao0Z z>K?Y#6aw^MJ%#t>*QbOTmYR3Z9a5|A^|+(m;ia-!ZqxVJO+}yZ;Nj!^n&Gl}4`;uQ zbXn>9F14FoUlJf`u$KMYqZQRS*#6KvwdMo^=UgZ^la)clu>#eB7`w*OkYrMAr9Ld&nm9o!rdFV57lL!XhHo~E@T-cJ$35gjoy<{jZW}Ny+3N0*E*|b`{04AV{rN%Y#`@8Mppq`$u{|_WYI#=<21f?}Ln) z+1WuKRe(^l`w5u@YFpf>nwlE0d<2&f{9Qn-L=5i-nxY`m1{Beh+trzw`8hF7z2?-U zs{R1z9VB}EwN1)r6}@OoFPMZ|WbMDoum4@_?`bf^2w*a6f9l=r@87;XjTgoxIQQUZ zOJ}O6L$e5SGT^O`m)Bj>yjM^(L9(TQ7X@WU$F0!a*qDiGoL~)u`xnes6b-WgwHOf@ z2{e&aRaFJn4(iz8L~AkGD>%Fwj^@9rr@02;%e>rNJoc?@1{xZ**i%2I0PoTOeld(0 zdbnZuVM8D-FlgC3Gig|#MG|xs|4P=Vf#NZmL12x>FdRatC?=N{{fHzDR4kcmDaGON za3;uf67xU)3(Pjkut}R6%G=ZhgF3pav`2arD2lU71NP_4wG^I^OwNHX$9fU!=s!_~ zP@Z`KA>ikCg7kWDdbr{5DHu7)Jz$V1GS5%6UH~d%g&~0)%t4fBOU{g_i`&^?!Ug6H zof4thu5T(!wAq0Xk5=;Pvi#%8&d0>W0Q;flKqahzr^7pxJ>GA=<|3F?7!t9?++P*v zfDJmz$R1ag5TH-YQ$8_Uw-rjpEtE z`Rw?DNe76cpDe$L2n%eO-@H*lbNFRJik+PF+x0B;|MgAnh~8fFqY2@X)800fS|AlZ zP%tQdN3q_^Fb(dm3m>!v2U=1p3go4K@($j5|*U8ef;=fjz|RW={=5A(#HEx87+-L&+=G8$nt z`n1O&t6fUO7BZ4GdWiNQvoaQg2d;N7PWzwKbFC?i%@eGu4 zVfbCxI5`P^YTzu$46E7Q3@y1-0WPLTi{8i;pAt=jV1-f#auGZ-N++;)1wJr71z8;c8X!% zbgn#OcN*kN&^h{j*Qz%}3q;?U+Ia8JzhdwcCPtKrzGJThUwr@4_lT%Xz`W_% zgBOlwl0gYS{kRbGNClD3ld%Gw1V4g=J(_3f>iPv?G@<0C<0W|c zIi~a~8!HreczB@b?(04nS-rlQS<9qyP_>TcVR#rVsKrK{+4yK&ead1|V$aP2LWoxGcmhcY+1#Ev5J@Vhuo_TUia@_L*CSc#VVej$bYOMNpabLE|&1P@F-AhK3vu zaS%C~q3PA1;uc;{cNQ~gfei*8`ZB03V{@5;HWAcgZ!zZh9^tYJ>D|rE4bEkr8nNY| z6_l=?gr9W=xjuN|NubuHdlukez_#G0<{i7`1^ukot03RM>u6^#()ua0rfXK16b{}?AaU94lIeK(7f*sTpEx=3 ztEb@8b^=vItHK4h%qKli+@Zk3LjE;}e8^fub5 zE3QcZZy4TmnTn3nS`ePF23hTd-WBBLJmesIM@MXxPzTVCmwPU{shL~C*bStTu@=5g zN{5mddDZy_63J+TJRBUAU`~q5*?xZ*BB{M7jaJ9=Z=aP_Z}ca|l&e(`kS90KF+Lr2wKA~-{5S3EmeT9Q;eC_Qsyrs8pb%5)Iqa3Y| zAAuKes2h@$z@g#azTV#Pq>%4##^70Ey=k@==|9uE06#Zpau=?ulY*U%jft8$b*(%;{rVtP;Wv2s_zk*gqPC;XuaL4@vdMcg;QiP(z>z7vbgB4egUx}A z(&&&P>{qyelM!|^TD3B^uKp9A^*t5gAilHFI>Wp3mda`?D=R5>Nb%t0q=UK0-_{9m z(atiugR`RNlEXQyz=6C6s_pu<%uCB(1_2E~t))uYT`VqzZeHX6g9In`A#oEbDk|Lh zVXjvhcIi@`cm#v<3Jan42hJR`rv#@JaCd&_GZ=`I)4Rm(!_2s8MOO$3F$fgvMeB2h zO2^+m^u<=Xt)IMuqa66^4J=bTY1XfQJa_22RHj}f26!s2T5U>#asv>QWB-yUO04XL zKW@Rc8`K+)4*ru^5APFra?77{t-OuCHL&R-Cn=^O>I=_-MA6RGkF_=4pCp+ErgE@8 z3YMljx(TC5n%Xpo4TG!M?5P^=z;1d>hh1cPn@F#*t7MNALL_=V7TpgP?`Y)N)-^ab zCf4DVmT^%nPx0rDFYALk-!j7OaS=v3iUqXS4btI4EQB+FXRYj-VKUMN;F|=DU1_Qz z8|4x8rQym-IF4$a@s6=cwt>pwdfw#@A*@62B~WU;uV2Fzam2V4VlqSWy4mOi3c-5) zVF(!;bjTe}AGDqFR!fgUy8+M3>;hssHEe^(bjgqt$iw|Ru?pq7pOXzmrnQ8yR2uZ2 zUS4%okNkfa?R?&$?sr{(J&#?%?EZX?TTX!rMf|j;#lCA8;|gpl=R`e^U*;&(!_pfF zS38>eYO~X@i9TLIJgX5}2RB^)$B_}IVcJa%mtkeq+ZD$~5fjAx=f0-pz6kWNGEW)Y zCIMp8P`E~XAp*tARgYoIB=49jI`3}vSZmC8C3D;4r&sZZg+5Qsu9 zHQQlfk)d~{Gc8t5?PH|c{%ZGyu$Ep)B1at}Mds57)7d!oBwx)<)c?y5{%SPK@icJB zPhTXrMN69hxhTJkHaby3mO-pz2ONEZYcI`2L!p)Z-Gb)K)yLG&k#W=JESgNn<+vvcomef~VL^#$#YSq`K8&Dwj_T4t zQ}m@vn2!+rNK+h&dBh{X+5PfEe&w*^;!=Zzl5k4$&kFgMPpFIVQI?-i=>mV`1(QLx zYgbm(BcpMoCnt}Iw|n#%U@xbn7xO}U9q{Av`-adPydi>8B;zm4!2ycaI>)1KgUPlr zSB|~^=NtL8h)g661PFAyEy7%qQwT#Lc3_Vb@dIK8uw!Ni16oL1vnUN5)vNyHWyQye zQ_>=GG)xBO-RfFej%`T=00jh!^Lv;=(L0onpQ)0HF`WJVtIz7gxYk`{NoO$u?Y=a7GFxenrUK1L+ zPGqx;bFt@;rXeMXAqd=-BBA}s3TBCMMz?&M^x7~+evtLikBo0?M&WJ2pK}*V1+W(T z>K$rTvH!^ zY)?su2++-lpu8^JcGA6uii&ax3EWWxV&oR(YG5AQ9%R?JjmHP7#WEwXl)HEDJ^&|c z0?ggo{OA*(oSc+Lp$8}nU)~SheHjtu<#K3%M|T2`oQE~ci2L{N*XEy0`F0a}4R3)c z@Er;{Xm`Sf>1#wV3Bk_ABmH1iJ zrdanI*D3O?P(i0j`c zg1&5Mg)1Cg^OEix-fn_|XgumIb-Z8BkkSZ|5sgitORhum!M?K4%yKZE(vpS6Uw-_DdC%gw$if868k;l4?MeQs{V8ixeE z`CYfXde#iIvg&TPT#{ZsaCM7bK)K&x!s&?|(_! zbep;7a~Y!0wo?9nXXE?E_cI2C+~|`G@0#;*-9(Z7j$@oyvoXQ1=jZUV)S=1Ds;NZB z*){1?OBEYW58sYR8C{)?M=YI& zmR6u$CFGw}Z}y~_>FEU0+K*Yl>IW42i8#v{O{(D6o^N&O$%R_1aCV_QyTAAt^X0v; zJH-mYZS6DOpJQ}~#&e&1JPc{T)3%jJkAokCGHA#}aR8`xoAGN1nKW(fv3xLx6spi^zJ^x4z(vQ3$x2J;rN!d^O+Agn6+b)Yp1m|ga zsae;nRG;zq7W+1pl%=yck=8zOoV)#U-?6tD1H&#HBVB1nqFOJ#kaagGs?@7E_;9(F z)8|2Q%=GNH8G~XSs^9M!fVpY!uK^s5VgArIIn0N$6p1@sdkuD;MShGXgKlD*oW13Y z`3{tv<}C$M4`0)4u?{h^qaL@#K{JmjDVYYOgg^Ap7^DH0F3g_3r?Ea2WQ8I@S|usFftjbe3stH^Ld zP|AnT{%2+S-*z}GeRx0Bnl*iP{PUar&KpN|9eWaK$YBz3ZQIq^5VymkGMeYY6^m$? zwMxb6a|1TBi7mNwhrdh6J{>sU+piqTcP}`1+VAAzEvKN0l+_L+v&yr`o8hDDX@Bl{ zy>lt=jwACfvmB-$X(J3jKD5VnZJd#m%GPRj79Y+y^JPcflUE@JS?=w-*}+3?P#LZ! zvTRQsN)syR^kcyM@ZCtcQo7JRr>6Uxug+Ye*H~a?P7l6+=-gYzuJu>+93zj6&E7FS z@#C>(Tu8oyfZz`;DQ$!7epa~ych&V@6}oN}i3`kd7%8OF%SvM!%hVnF>*N5q%VSi7 z@q;4)FfNH$Q0i6(bRnw`J%0Q+m`s_8GRVwyy7KYJGT^&y3G%TrTG8Ugk^LNx9u*AW zl|1$J9qN|ecofEda;Xg57yW8u!`^3%dSIvqo%A7o`&yBp!a+i5F)>ak?liXOI)fZ>(6peGA-`Dyfe&y|jqBNI$nijkC zB?E&MGpp7a+>L7u$)84U;pmS@Idru>e#?m*-c6MY<{M7)zRy)UGHxO5`2F|*7oCT_ zx?*p+cV%iGU9?G*2y1JC$WJ@6ZT9z+9gl4Lm`E3L^73MiTSZd`$9m6{uGfefHGUTN z;)m_r8Dqkhbcu~JfyeqJZ`++&VAV_GInX}dXpZZ55gH6^d$Pt2AkkV%t`By;7;6^AnqM{m~@{R-7icZBJb92as>jF(~o^|X-wq=wD1=xu`2rVtG#@;;&3YGRxE}OHK z)@snD6x%j#Xo#d-ScHbUmKB3MCY6~q#m8{d9mW};!DsH0h%nJw>k_*}^je@jBDgG! z%NVRi>&i9dz$Mf{Hmh3^2hbTq=44uX@QddTu}TK#lfpH#-|iBtjB|Rl<&PUT+}RVy4$V$v42I;c9F~r z#k{GHi`?tejZIA%n<*6$X=6{>0msNYcFKFCH`EV*-M1VtGC9-3NNb*}ZreuHkG!Q@Bw{8cMpw;PPc=P~1kzd&NZ4pj%Gp)IZ64=%HifsE#LXpPs z-kbE(E~eoocfuAbPCTmiT;KI@)h9mg_xZQ?a7IXfK5}IDIq}`)FSdxq*))9ONJ^01 z=bWL-b!p4gFxzL^*X0}x%H*>pEPrD5c=TwUnEIe26)@C9ckEUm+N{ZCmFT#Va%P~V z^@rUKjM${v(xEq$$_%~KIO&-Kqned4D))d2kKG0M(5TB&^CAG{9by5$w4(oK5DU7olM$j49zoGA_48yFFaUwZ-i?Vn?CX7aojno(>4i^M!t)?bz z&l@YTSjxU zS=I&_((1%aEKG21GnCBNRO-Ae&bBP0Yb<_!CX{32(=H9>T`%?bZ+fJ7E$`4ynfHnQ zdVp{}Rs53T&6<~j`p2#&PCW}#moJ&WDa;?ZnByfHE9g~oe_ofOZEw_C z$N6+f&kvbB(&KW+ThqfS=T`*X;Fh|TA{1sn(vLjP+obqtqex=iUMs!IUPR`~V+{|8 zjhxTltd}ep_3o@by1J7|g?_vL8F|;KEsM-&&xS;+y^Aa5({s3!JVa&3xX$lkpsI&orM!5+XLZ=0zbB9Rbu;ZaWL0iH z8o`CT1;PK53Dux3li0b}l~l&}M12m6;mx?fM?N=RW1a3jsk%Bqp~dkg26d zSsYz^m zj027j1c_X)3sf^`>{;!3q--_YJbo0!vz)K40xbUA%Q)okXUDE&;U>Lna+6_mPt<66 zWlQKw?X^=+tEHlwrPO&l=cXTinz@g8Bq0k2Z#}$o!SKYf<%@mH0f8-74_SU|%Itb6 zcJ1fZ!Hv=RauKvgM_X17S`TIu-i5dBzT-o{_^a#dkAxcti05k6R)sya!wXboiWSUw zSWkbSD7W_E*1+}q7OEr$>CRaE6d3(-W4SPPF3avOsPL-^M+Ps*N|Zq&*!G-ws<}jK z2IC?l$B_ZK;*)*)wBl0whj#`DTnrN1+=J|Tt}~;XY3OCYd|{#)!vUE@?t33IN=uhA zkE%twNd3&U%n?-$F<(6{m~tZP#o0if4I8rOza1DX+cAEVMrHo}VwEo-XxkN>BE8DT zu`Kl?7R>%zt`7v+cstH!ZS&20m2YqPF*15C#lx=p{vWTf_Yrmd&lVS-C21}Db2K)e zrR6fzTvNV%GWZbsmiQ`N>qE^3FYBkI>wVWfKPdYK_=lR(ad+L>44^0)-5L0P)oFFP z+ORD&KF+V#NVNri%c z)o-EZ`SLn{{q)*zADONmg$G1p>CMe;j-@~i1-uF`Yu}CowU}?Xr@F;v^G1tQY576E zGT%j20WQ}uoV*biMOtt9JDtj6^a`>^2Zk^Sh*iR}; zJ$^{)9+3Wh<;AdygIe@*NFE6s-Iv16r>V^XX}|E?t2X*kcH@TA>EjcYt{gjPn`${W zZF^L`a0qNpkvO;K{0Dk2!5<9a4R?1{`-LVBJ|`*{mIS_>8RRW<@8f+xasKv-v|#wg<#2(g)?AdRECNhn!-sYDsgkec0tmFwOmW-_!bDMZC+~TU(M>RXe5)jpPE+|H3;c$iGj@9Ed337EbcNX~)RV^RC4gV1IkU8K@ zx{4TiC>{ZL^NXBl8Qv^{Zg}h78l4egW81)!Ki&~$^!`Csd2i}29LdBsNjak>dQHhl zmtFJ7pPb+yY?$=4N**jxOP zsQw50_y3zdyHfniotN51edh2FUS866U(kgn{U!9yfANI^ZV+%u62EvvhQEDXlDIKW z_z(8#R^s0%$hC~gO#hesFkDu?JJydWM?cx3C&QumF+uBQSWB1`5t18jGe3(@@c+O+ b7Pf47?>l6E)`&7bbA^t^e)apR)))Q{IeBo+ diff --git a/install/DATABASE.md b/install/DATABASE.md new file mode 100644 index 0000000..bee2a16 --- /dev/null +++ b/install/DATABASE.md @@ -0,0 +1,983 @@ +### VEBA Database: + + +#### Profile HMM Sources: +Please cite the following sources if these marker sets are used in any way: + +``` +* Archaea_76.hmm.gz - (Anvi'o) Lee, https://doi.org/10.1093/bioinformatics/btz188 (https://github.com/merenlab/anvio/tree/master/anvio/data/hmm/Archaea_76) + +* Bacteria_71.hmm.gz - (Anvi'o) Lee modified, https://doi.org/10.1093/bioinformatics/btz188 (https://github.com/merenlab/anvio/tree/master/anvio/data/hmm/Bacteria_71) + +* Protista_83.hmm.gz - (Anvi'o) Delmont, http://merenlab.org/delmont-euk-scgs (https://github.com/merenlab/anvio/tree/master/anvio/data/hmm/Protista_83) + +* Fungi_593.hmm.gz - (FGMP) https://bmcbioinformatics.biomedcentral.com/articles/10.1186/s12859-019-2782-9 + +* CPR_43.hmm.gz - (CheckM) https://github.com/Ecogenomics/CheckM/tree/master/custom_marker_sets + +* eukaryota_odb10.hmm.gz - (BUSCO) https://busco-data.ezlab.org/v5/data/lineages/eukaryota_odb10.2020-09-10.tar.gz +``` + +Espinoza, Josh (2022): Profile HMM marker sets. figshare. Dataset. https://doi.org/10.6084/m9.figshare.19616016.v1 + +#### Microeukaryotic protein database: +A protein database is required not only for eukaryotic gene calls using MetaEuk but can also be used for MAG annotation. Many eukaryotic protein databases exist such as MMETSP, EukZoo, and EukProt, yet these are limited to marine environments, include prokaryotic sequences, or include eukaryotic sequences for organisms that would not be expected to be binned out of metagenomes such as metazoans. We combined and dereplicated MMETSP, EukZoo, EukProt, and NCBI non-redundant to include only microeukaryotes such as protists and fungi. This optimized microeukaryotic database ensures that only eukaryotic exons expected to be represented in metagenomes are utilized for eukaryotic gene modeling and the resulting MetaEuk reference targets are used for eukaryotic MAG classification. VEBA’s microeukaryotic protein database includes 48,006,918 proteins from 42,922 microeukaryotic strains. + +**Current:** + +* [VDB-Microeukaryotic\_v2.1](https://zenodo.org/record/7485114) available on Zenodo + +**Deprecated:** + +* [VDB-Microeukaryotic\_v1](https://figshare.com/articles/dataset/Microeukaryotic_Protein_Database/19668855) available on FigShare + +#### Database Structure: + +**Current:** +*VEBA Database* version: `VDB_v5.1` + +* `VDB_v5` → `VDB_v5.1` updates `GTDB` database from `r207_v2` → `r214`. +* Changes `${VEBA_DATABASE}/Classify/GTDBTk` → `${VEBA_DATABASE}/Classify/GTDB`. +* Adds `gtdb_r214.msh` to `${VEBA_DATABASE}/Classify/GTDB/mash/` for ANI screens. [Available on Zenodo:8048187](https://zenodo.org/record/8048187) +* Adds `VFDB` to `${VEBA_DATABASE}/Annotate/VFDB/VFDB_setA_pro.dmnd` for virulence factor annotation. + +``` +tree -L 3 . +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── MIBiG +│   │   └── mibig_v3.1.dmnd +│   ├── NCBIfam-AMRFinder +│   │   ├── NCBIfam-AMRFinder.changelog.txt +│   │   ├── NCBIfam-AMRFinder.hmm.gz +│   │   └── NCBIfam-AMRFinder.tsv +│   ├── Pfam +│   │   ├── Pfam-A.hmm.gz +│   │   └── relnotes.txt +│   └── UniRef +│   ├── uniref50.dmnd +│   ├── uniref50.release_note +│   ├── uniref90.dmnd +│   └── uniref90.release_note +├── Classify +│   ├── CheckM2 +│   │   └── uniref100.KO.1.dmnd +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── geNomad +│   │   ├── genomad_db +│   │   ├── genomad_db.dbtype +│   │   ├── genomad_db_h +│   │   ├── genomad_db_h.dbtype +│   │   ├── genomad_db_h.index +│   │   ├── genomad_db.index +│   │   ├── genomad_db.lookup +│   │   ├── genomad_db_mapping +│   │   ├── genomad_db.source +│   │   ├── genomad_db_taxonomy +│   │   ├── genomad_integrase_db +│   │   ├── genomad_integrase_db.dbtype +│   │   ├── genomad_integrase_db_h +│   │   ├── genomad_integrase_db_h.dbtype +│   │   ├── genomad_integrase_db_h.index +│   │   ├── genomad_integrase_db.index +│   │   ├── genomad_integrase_db.lookup +│   │   ├── genomad_integrase_db.source +│   │   ├── genomad_marker_metadata.tsv +│   │   ├── genomad_mini_db -> genomad_db +│   │   ├── genomad_mini_db.dbtype +│   │   ├── genomad_mini_db_h -> genomad_db_h +│   │   ├── genomad_mini_db_h.dbtype -> genomad_db_h.dbtype +│   │   ├── genomad_mini_db_h.index -> genomad_db_h.index +│   │   ├── genomad_mini_db.index +│   │   ├── genomad_mini_db.lookup -> genomad_db.lookup +│   │   ├── genomad_mini_db_mapping -> genomad_db_mapping +│   │   ├── genomad_mini_db.source -> genomad_db.source +│   │   ├── genomad_mini_db_taxonomy -> genomad_db_taxonomy +│   │   ├── mini_set_ids +│   │   ├── names.dmp +│   │   ├── nodes.dmp +│   │   ├── plasmid_hallmark_annotation.txt +│   │   ├── version.txt +│   │   └── virus_hallmark_annotation.txt +│   ├── GTDB +│   │   ├── fastani +│   │   ├── markers +│   │   ├── mash +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── humann_uniref50_annotations.tsv.gz +│   │   ├── md5_checksums +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10 +│   │   ├── microeukaryotic.eukaryota_odb10.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h +│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h.index +│   │   ├── microeukaryotic.eukaryota_odb10.index +│   │   ├── microeukaryotic.eukaryota_odb10.lookup +│   │   ├── microeukaryotic.eukaryota_odb10.source +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.eukaryota_odb10.list +│   │   ├── RELEASE_NOTES +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   └── readme.txt +├── Contamination +│   ├── AntiFam +│   │   ├── AntiFam.hmm.gz +│   │   ├── relnotes +│   │   └── version +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +└── MarkerSets + ├── Archaea_76.hmm.gz + ├── Bacteria_71.hmm.gz + ├── CPR_43.hmm.gz + ├── eukaryota_odb10.hmm.gz + ├── eukaryota_odb10.scores_cutoff.tsv.gz + ├── Fungi_593.hmm.gz + ├── Protista_83.hmm.gz + └── README +``` + +**Deprecated:** + +

+ *VEBA Database* version: `VDB_v5` + +`VDB_v4` → `VDB_v5` replaces `nr` with `UniRef90` and `UniRef50`. Also includes `MiBIG` database. + +``` +tree -L 3 . +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── MIBiG +│   │   └── mibig_v3.1.dmnd +│   ├── NCBIfam-AMRFinder +│   │   ├── NCBIfam-AMRFinder.changelog.txt +│   │   ├── NCBIfam-AMRFinder.hmm.gz +│   │   └── NCBIfam-AMRFinder.tsv +│   ├── Pfam +│   │   ├── Pfam-A.hmm.gz +│   │   └── relnotes.txt +│   └── UniRef +│   ├── uniref50.dmnd +│   └── uniref90.dmnd +├── Classify +│   ├── CheckM2 +│   │   └── uniref100.KO.1.dmnd +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── geNomad +│   │   ├── genomad_db +│   │   ├── genomad_db.dbtype +│   │   ├── genomad_db_h +│   │   ├── genomad_db_h.dbtype +│   │   ├── genomad_db_h.index +│   │   ├── genomad_db.index +│   │   ├── genomad_db.lookup +│   │   ├── genomad_db_mapping +│   │   ├── genomad_db.source +│   │   ├── genomad_db_taxonomy +│   │   ├── genomad_integrase_db +│   │   ├── genomad_integrase_db.dbtype +│   │   ├── genomad_integrase_db_h +│   │   ├── genomad_integrase_db_h.dbtype +│   │   ├── genomad_integrase_db_h.index +│   │   ├── genomad_integrase_db.index +│   │   ├── genomad_integrase_db.lookup +│   │   ├── genomad_integrase_db.source +│   │   ├── genomad_marker_metadata.tsv +│   │   ├── genomad_mini_db -> genomad_db +│   │   ├── genomad_mini_db.dbtype +│   │   ├── genomad_mini_db_h -> genomad_db_h +│   │   ├── genomad_mini_db_h.dbtype -> genomad_db_h.dbtype +│   │   ├── genomad_mini_db_h.index -> genomad_db_h.index +│   │   ├── genomad_mini_db.index +│   │   ├── genomad_mini_db.lookup -> genomad_db.lookup +│   │   ├── genomad_mini_db_mapping -> genomad_db_mapping +│   │   ├── genomad_mini_db.source -> genomad_db.source +│   │   ├── genomad_mini_db_taxonomy -> genomad_db_taxonomy +│   │   ├── mini_set_ids +│   │   ├── names.dmp +│   │   ├── nodes.dmp +│   │   ├── plasmid_hallmark_annotation.txt +│   │   ├── version.txt +│   │   └── virus_hallmark_annotation.txt +│   ├── GTDBTk +│   │   ├── fastani +│   │   ├── markers +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── humann_uniref50_annotations.tsv.gz +│   │   ├── md5_checksums +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10 +│   │   ├── microeukaryotic.eukaryota_odb10.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h +│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h.index +│   │   ├── microeukaryotic.eukaryota_odb10.index +│   │   ├── microeukaryotic.eukaryota_odb10.lookup +│   │   ├── microeukaryotic.eukaryota_odb10.source +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.eukaryota_odb10.list +│   │   ├── RELEASE_NOTES +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── images.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   └── readme.txt +├── Contamination +│   ├── AntiFam +│   │   ├── AntiFam.hmm.gz +│   │   ├── relnotes +│   │   └── version +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +└── MarkerSets + ├── Archaea_76.hmm.gz + ├── Bacteria_71.hmm.gz + ├── CPR_43.hmm.gz + ├── eukaryota_odb10.hmm.gz + ├── eukaryota_odb10.scores_cutoff.tsv.gz + ├── Fungi_593.hmm.gz + ├── Protista_83.hmm.gz + └── README +``` +
+ +
+ *VEBA Database* version: `VDB_v4` + + +`VDB_v4` is `VDB_v3.1` with the following changes: 1) `CheckM1` database swapped for `CheckM2` database; includes `geNomad` database; and 3) updates `CheckV` database. Refer to [development log](https://github.com/jolespin/veba/blob/main/DEVELOPMENT.md#release-v11-currently-testing-before-official-release) for specifics. + +``` +tree -L 3 . +. +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── NCBIfam-AMRFinder +│   │   ├── NCBIfam-AMRFinder.changelog.txt +│   │   ├── NCBIfam-AMRFinder.hmm.gz +│   │   └── NCBIfam-AMRFinder.tsv +│   ├── nr +│   │   └── nr.dmnd +│   └── Pfam +│   ├── Pfam-A.hmm.gz +│   └── relnotes.txt +├── Classify +│   ├── CheckM2 +│   │   └── uniref100.KO.1.dmnd +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── geNomad +│   │   ├── genomad_db +│   │   ├── genomad_db.dbtype +│   │   ├── genomad_db_h +│   │   ├── genomad_db_h.dbtype +│   │   ├── genomad_db_h.index +│   │   ├── genomad_db.index +│   │   ├── genomad_db.lookup +│   │   ├── genomad_db_mapping +│   │   ├── genomad_db.source +│   │   ├── genomad_db_taxonomy +│   │   ├── genomad_integrase_db +│   │   ├── genomad_integrase_db.dbtype +│   │   ├── genomad_integrase_db_h +│   │   ├── genomad_integrase_db_h.dbtype +│   │   ├── genomad_integrase_db_h.index +│   │   ├── genomad_integrase_db.index +│   │   ├── genomad_integrase_db.lookup +│   │   ├── genomad_integrase_db.source +│   │   ├── genomad_marker_metadata.tsv +│   │   ├── genomad_mini_db -> genomad_db +│   │   ├── genomad_mini_db.dbtype +│   │   ├── genomad_mini_db_h -> genomad_db_h +│   │   ├── genomad_mini_db_h.dbtype -> genomad_db_h.dbtype +│   │   ├── genomad_mini_db_h.index -> genomad_db_h.index +│   │   ├── genomad_mini_db.index +│   │   ├── genomad_mini_db.lookup -> genomad_db.lookup +│   │   ├── genomad_mini_db_mapping -> genomad_db_mapping +│   │   ├── genomad_mini_db.source -> genomad_db.source +│   │   ├── genomad_mini_db_taxonomy -> genomad_db_taxonomy +│   │   ├── mini_set_ids +│   │   ├── names.dmp +│   │   ├── nodes.dmp +│   │   ├── plasmid_hallmark_annotation.txt +│   │   ├── version.txt +│   │   └── virus_hallmark_annotation.txt +│   ├── GTDBTk +│   │   ├── fastani +│   │   ├── markers +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── humann_uniref50_annotations.tsv.gz +│   │   ├── md5_checksums +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10 +│   │   ├── microeukaryotic.eukaryota_odb10.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h +│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h.index +│   │   ├── microeukaryotic.eukaryota_odb10.index +│   │   ├── microeukaryotic.eukaryota_odb10.lookup +│   │   ├── microeukaryotic.eukaryota_odb10.source +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.eukaryota_odb10.list +│   │   ├── RELEASE_NOTES +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   └── readme.txt +├── Contamination +│   ├── AntiFam +│   │   ├── AntiFam.hmm.gz +│   │   ├── relnotes +│   │   └── version +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +└── MarkerSets + ├── Archaea_76.hmm.gz + ├── Bacteria_71.hmm.gz + ├── CPR_43.hmm.gz + ├── eukaryota_odb10.hmm.gz + ├── eukaryota_odb10.scores_cutoff.tsv.gz + ├── Fungi_593.hmm.gz + ├── Protista_83.hmm.gz + └── README + +31 directories, 96 files +``` +
+ + +
+ *VEBA Database* version: VDB_v3.1 + +The same as `VDB_v3` but updates `VDB-Microeukaryotic_v2` to `VDB-Microeukaryotic_v2.1` which has a `reference.eukaryota_odb10.list` containing only the subset of identifiers that core eukaryotic markers (useful for classification). + +``` +tree -L 3 . +. +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── nr +│   │   └── nr.dmnd +│   └── Pfam +│   └── Pfam-A.hmm.gz +├── Classify +│   ├── CheckM +│   │   ├── distributions +│   │   ├── genome_tree +│   │   ├── hmms +│   │   ├── hmms_ssu +│   │   ├── img +│   │   ├── pfam +│   │   ├── selected_marker_sets.tsv +│   │   ├── taxon_marker_sets.tsv +│   │   └── test_data +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── GTDBTk +│   │   ├── fastani +│   │   ├── markers +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── humann_uniref50_annotations.tsv.gz +│   │   ├── md5_checksums +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10 +│   │   ├── microeukaryotic.eukaryota_odb10.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h +│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h.index +│   │   ├── microeukaryotic.eukaryota_odb10.index +│   │   ├── microeukaryotic.eukaryota_odb10.lookup +│   │   ├── microeukaryotic.eukaryota_odb10.source +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.eukaryota_odb10.list +│   │   ├── reference.eukaryota_odb10.list.md5 +│   │   ├── reference.faa.gz +│   │   ├── RELEASE_NOTES +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   ├── readme.txt +│   ├── taxa.sqlite +│   └── taxa.sqlite.traverse.pkl +├── Contamination +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +├── MarkerSets +│   ├── Archaea_76.hmm +│   ├── Bacteria_71.hmm +│   ├── CPR_43.hmm +│   ├── eukaryota_odb10.hmm +│   ├── eukaryota_odb10.scores_cutoff.tsv.gz +│   ├── Fungi_593.hmm +│   ├── Protista_83.hmm +│   └── README +└── SIZE + +35 directories, 60 files +``` +
+ + +
+ *VEBA Database* version: VDB_v3 + +``` +tree -L 3 . +. +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── nr +│   │   └── nr.dmnd +│   └── Pfam +│   └── Pfam-A.hmm.gz +├── Classify +│   ├── CheckM +│   │   ├── distributions +│   │   ├── genome_tree +│   │   ├── hmms +│   │   ├── hmms_ssu +│   │   ├── img +│   │   ├── pfam +│   │   ├── selected_marker_sets.tsv +│   │   ├── taxon_marker_sets.tsv +│   │   └── test_data +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── GTDBTk +│   │   ├── fastani +│   │   ├── markers +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── humann_uniref50_annotations.tsv.gz +│   │   ├── md5_checksums +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.faa.gz +│   │   ├── RELEASE_NOTES +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   ├── readme.txt +│   ├── taxa.sqlite +│   └── taxa.sqlite.traverse.pkl +├── Contamination +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +├── MarkerSets +│   ├── Archaea_76.hmm +│   ├── Bacteria_71.hmm +│   ├── CPR_43.hmm +│   ├── eukaryota_odb10.hmm +│   ├── eukaryota_odb10.scores_cutoff.tsv.gz +│   ├── Fungi_593.hmm +│   ├── Protista_83.hmm +│   └── README +└── SIZE + +35 directories, 50 files +``` + +
+ + +
+ *VEBA Database* version: VDB_v2 + +* Compatible with *VEBA* version: `v1.0.2a+` + + +``` +tree -L 3 . +. +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── nr +│   │   └── nr.dmnd +│   └── Pfam +│   └── Pfam-A.hmm.gz +├── Classify +│   ├── CheckM +│   │   ├── distributions +│   │   ├── genome_tree +│   │   ├── hmms +│   │   ├── hmms_ssu +│   │   ├── img +│   │   ├── pfam +│   │   ├── selected_marker_sets.tsv +│   │   ├── taxon_marker_sets.tsv +│   │   └── test_data +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── GTDBTk +│   │   ├── fastani +│   │   ├── markers +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.rmdup.iupac.relabeled.no_deprecated.complete_lineage.faa.gz +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   ├── readme.txt +│   ├── taxa.sqlite +│   └── taxa.sqlite.traverse.pkl +├── Contamination +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +├── MarkerSets +│   ├── Archaea_76.hmm +│   ├── Bacteria_71.hmm +│   ├── CPR_43.hmm +│   ├── eukaryota_odb10.hmm +│   ├── eukaryota_odb10.scores_cutoff.tsv.gz +│   ├── Fungi_593.hmm +│   ├── Protista_83.hmm +│   └── README +└── SIZE + +35 directories, 47 files + +``` +
+ + +
+ *VEBA Database* version: VDB_v1 + + +* Compatible with *VEBA* version: `v1.0.0`, `v1.0.1` + + +``` +tree -L 3 . +. +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── nr +│   │   └── nr.dmnd +│   └── Pfam +│   └── Pfam-A.hmm.gz +├── Classify +│   ├── CheckM +│   │   ├── distributions +│   │   ├── genome_tree +│   │   ├── hmms +│   │   ├── hmms_ssu +│   │   ├── img +│   │   ├── pfam +│   │   ├── selected_marker_sets.tsv +│   │   ├── taxon_marker_sets.tsv +│   │   └── test_data +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── GTDBTk +│   │   ├── fastani +│   │   ├── markers +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── humann_uniref50_annotations.tsv.gz +│   │   ├── md5_checksums +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10 +│   │   ├── microeukaryotic.eukaryota_odb10.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h +│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h.index +│   │   ├── microeukaryotic.eukaryota_odb10.index +│   │   ├── microeukaryotic.eukaryota_odb10.lookup +│   │   ├── microeukaryotic.eukaryota_odb10.source +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.eukaryota_odb10.list +│   │   ├── reference.eukaryota_odb10.list.md5 +│   │   ├── reference.faa.gz +│   │   ├── RELEASE_NOTES +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   ├── readme.txt +│   ├── taxa.sqlite +│   └── taxa.sqlite.traverse.pkl +├── Contamination +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +├── MarkerSets +│   ├── Archaea_76.hmm +│   ├── Bacteria_71.hmm +│   ├── CPR_43.hmm +│   ├── eukaryota_odb10.hmm +│   ├── eukaryota_odb10.scores_cutoff.tsv.gz +│   ├── Fungi_593.hmm +│   ├── Protista_83.hmm +│   └── README +└── SIZE + +35 directories, 60 files +``` + +``` +tree -L 3 . +. +. +├── ACCESS_DATE +├── Annotate +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── nr +│   │   └── nr.dmnd +│   └── Pfam +│   └── Pfam-A.hmm.gz +├── Classify +│   ├── CheckM +│   │   ├── distributions +│   │   ├── genome_tree +│   │   ├── hmms +│   │   ├── hmms_ssu +│   │   ├── img +│   │   ├── pfam +│   │   ├── selected_marker_sets.tsv +│   │   ├── taxon_marker_sets.tsv +│   │   └── test_data +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── GTDBTk +│   │   ├── fastani +│   │   ├── manifest.tsv +│   │   ├── markers +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   └── taxonomy +│   ├── Microeukaryotic +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.rmdup.iupac.relabeled.no_deprecated.complete_lineage.faa.gz +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   ├── readme.txt +│   ├── taxa.sqlite +│   └── taxa.sqlite.traverse.pkl +├── Contamination +│   ├── grch38 +│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.1.bt2 +│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.2.bt2 +│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.3.bt2 +│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.4.bt2 +│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.rev.1.bt2 +│   │   └── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +└── MarkerSets + ├── Archaea_76.hmm + ├── Bacteria_71.hmm + ├── CPR_43.hmm + ├── eukaryota_odb10.hmm + ├── eukaryota_odb10.scores_cutoff.tsv.gz + ├── Fungi_593.hmm + ├── Protista_83.hmm + └── README + +33 directories, 47 files +``` + +
+ + +____________________________________________________________ + +#### Version Notes: + +* For the human contamination, if you use `KneadData` and already have a `Bowtie2` index for human then you can use that instead. The only module that uses this is `preprocess.py` and you have to specify this directly when running (i.e., it's optional) so it doesn't matter if it's in the database directory or not (same with ribokmers.fa.gz). +* `CheckM2` only has 1 database version at this time so it isn't an issue. +* `KOFAM` and `Pfam` just uses these as annotations so any version should work perfectly. +* If you are low on disk space and already have these installed then just symlink them with the structure above. If so, them just comment out those sections of `download_databases.sh`. + + +_______________________________________________________ + +If you have any issues, please create a GitHub issue with the prefix `[Database]` followed by the question. \ No newline at end of file diff --git a/install/README.md b/install/README.md index 5592b5e..7a06a62 100644 --- a/install/README.md +++ b/install/README.md @@ -3,9 +3,9 @@ ____________________________________________________________ #### Software installation One issue with having large-scale pipeline suites with open-source software is the issue of dependencies. One solution for this is to have a modular software structure where each module has its own `conda` environment. This allows for minimizing dependency constraints as this software suite uses an array of diverse packages from different developers. -The basis for these environments is creating a separate environment for each module with the `VEBA-` prefix and `_env` as the suffix. For example `VEBA-assembly_env` or `VEBA-binning-prokaryotic_env`. Because of this, `VEBA` is currently not available as a `conda` package but each module will be in the near future. In the meantime, please use the `veba/install/install_veba.sh` script which installs each environment from the yaml files in `veba/install/environments/`. After installing the environments, use the `veba/install/download_databases` script to download and configure the databases while also adding the environment variables to the activate/deactivate scripts in each environment. To install anything manually, just read the scripts as they are well documented and refer to different URL and paths for specific installation options. +The basis for these environments is creating a separate environment for each module with the `VEBA-` prefix and `_env` as the suffix. For example `VEBA-assembly_env` or `VEBA-binning-prokaryotic_env`. Because of this, `VEBA` is currently not available as a `conda` package but each module will be in the near future. In the meantime, please use the `veba/install/install_veba.sh` script which installs each environment from the yaml files in `veba/install/environments/`. After installing the environments, use the `veba/install/download_databases.sh` script to download and configure the databases while also adding the environment variables to the activate/deactivate scripts in each environment. To install anything manually, just read the scripts as they are well documented and refer to different URL and paths for specific installation options. -The majority of the time taken to build database is downloading/decompressing large archives, `Diamond` database creation of UniRef, and `MMSEQS2` database creation of microeukaryotic protein database. +The majority of the time taken to build database is downloading/decompressing large archives, `Diamond` database creation of `UniRef`, and `MMSEQS2` database creation of microeukaryotic protein database. Total size is `214 GB` but if you have certain databases installed already then you can just symlink them so the `VEBA_DATABASE` path has the correct structure. Note, the exact size may vary as Pfam and UniRef are updated regularly. @@ -16,14 +16,14 @@ Each major version will be packaged as a [release](https://github.com/jolespin/v ____________________________________________________________ -⚠️ **Read before updating from `v1.0.x `→ `v1.1.x`:** +### VEBA Database: -If you are updating from `v1.0.x` → `v1.1.x` it is strongly recommended to uninstall your current installation and install from the beginning as this removes all the existing `VEBA` `conda` environments. In `v1.1.x` many modules are trimmed down and more efficient in their dependencies. To be clear, `v1.1.x` also uses an updated database which is `vDB_v4`. -____________________________________________________________ +Please refer to the [VEBA Database](DATABASE.md) documentation. +___________________ ### Install: -Currently, **Conda environments for VEBA are ONLY configured for Linux**. MacOS configs for certain environments will be available for v2.x.x. Due to the large databases, this software was designed to be used via HPC. +Currently, **Conda environments for VEBA are ONLY configured for Linux** and, due to the large databases, this software was designed to be used via HPC. **There are 3 steps to install *VEBA*:** @@ -83,7 +83,7 @@ The `VEBA` installation is going to configure some `conda` environments for you ``` # For stable version, download and decompress the tarball: -VERSION="1.1.2" +VERSION="1.2.0" wget https://github.com/jolespin/veba/archive/refs/tags/v${VERSION}.tar.gz tar -xvf v${VERSION}.tar.gz && mv veba-${VERSION} veba @@ -103,7 +103,7 @@ cd veba/install **Recommended resource allocatation:** 4 hours with 15 GB memory (include extra time for variable I/O speed for various hosts) -For `v1.1.0+`, **this should take ~1.75 hours (~108 minutes) with ~15 GB memory allocated**. The update from `CheckM1` -> `CheckM2` and installation of `antiSMASH` require more memory and may require grid access if head node is limited. +The update from `CheckM1` -> `CheckM2` and installation of `antiSMASH` require more memory and may require grid access if head node is limited. ``` bash install_veba.sh @@ -113,7 +113,9 @@ bash install_veba.sh **Recommended resource allocatation:** 48 GB memory (time is dependent on I/O of database repositories) -⚠️ **This step should use up to 48 GB memory** and should be run using a compute grid via SLURM or SunGridEngine. If this command is run on the head node it will likely fail or timeout if a connection is interrupted. The most computationally intensive steps are creating a `Diamond` database of NCBI's non-redundant reference and a `MMSEQS2` database of the microeukaryotic protein database. Note the duration will depend on several factors including your internet connection speed and the i/o of public repositories. +⚠️ **This step should use ~48 GB memory** and should be run using a compute grid via SLURM or SunGridEngine. If this command is run on the head node it will likely fail or timeout if a connection is interrupted. The most computationally intensive steps are creating a `Diamond` database of `UniRef` and a `MMSEQS2` database of the microeukaryotic protein database. Note the duration will depend on several factors including your internet connection speed and the I/O of public repositories. + +**Future releases will split the downloading and configuration to better make use of resources.** If issues arise, please [submit a GitHub issue](https://github.com/jolespin/veba/issues) prefixed with `[Database]`. We are here to help :) @@ -200,6 +202,8 @@ bash update_environment_variables.sh Please refer to the [adapting commands for Docker walkthrough](https://github.com/jolespin/veba/blob/main/walkthroughs/adapting_commands_for_docker.md). +Docker containers are now available (starting with `v1.1.2`) for all modules via [DockerHub](https://hub.docker.com/repositories/jolespin) + ____________________________________________________________ @@ -224,1027 +228,7 @@ ____________________________________________________________ There are currently 2 ways to update veba: -1. Basic uninstall reinstall - You can uninstall and reinstall using the scripts in `veba/install/` directory. It's recomended to do a fresh reinstall when updating from `v1.0.x` → `v1.1.x`. +1. Basic uninstall reinstall - You can uninstall and reinstall using the scripts in `veba/install/` directory. It's recomended to do a fresh reinstall when updating from `v1.0.x` → `v1.2.x`. 2. Patching existing installation - Complete reinstalls of *VEBA* environments and databases is time consuming so [we've detailed how to do specific patches **for advanced users**](PATCHES.md). If you don't feel comfortable running these commands, then just do a fresh install if you would like to update. -### VEBA Containers [Coming Soon]: - - -| Environment | Environment File | Resources | Description | Recommended Threads | Description | -|------------------------------|----------------------------------|------------------------|--------------------------------------------------------------------------------------------------------------------------|---------------------|-----------------------------------------------------------------------------------------------------------------| -| VEBA-annotate_env | VEBA-annotate_env.yml | 16GB-128GB | Annotating proteins with Diamond, HMMER, and KOFAM | 4 | Fastq quality trimming, adapter removal, decontamination, and read statistics calculations | -| VEBA-assembly_env | VEBA-assembly_env.yml | 32GB-128GB | Assembling metagenomes/metatranscriptomes, mapping reads to assemblies, and preparing for genome binning | 16 | Assemble reads, align reads to assembly, and count mapped reads | -| VEBA-biosynthetic_env | VEBA-biosynthetic_env.yml | 16GB | BGC detection with antiSMASH, convert genbank files to tabular format, assess novelty of BGC | 16 | Align reads to (concatenated) reference and counts mapped reads | -| VEBA-preprocess_env | VEBA-preprocess_env.yml | 4GB-16GB | Preprocess reads by quality filtering, removing adapters, removing human contamination, and properly pairing | 4 | Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment | -| VEBA-binning-eukaryotic_env | VEBA-binning-eukaryotic_env.yml | 128GB | Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment | 4 | Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment | -| VEBA-binning-prokaryotic_env | VEBA-binning-prokaryotic_env.yml | 16GB | Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment | 4 | Detection of viral genomes and quality assessment | -| VEBA-binning-viral_env | VEBA-binning-viral_env.yml | 16GB | Detection of viral genomes and quality assessment | 32 | Taxonomic classification and candidate phyla radiation adjusted quality | -| VEBA-classify_env | VEBA-classify_env.yml | 16GB-64GB | Classify eukaryotic, prokaryotic, and viral genomes | 1 | Taxonomic classification of eukaryotic genomes | -| VEBA-cluster_env | VEBA-cluster_env.yml | 32GB | Species-level clustering of genomes and lineage-specific orthogroup detection. | 4 | Taxonomic classification and isolation source of viral genomes | -| VEBA-database_env | VEBA-database_env.yml | 64GB | Contains all the programs needed to download and build the VEBA database | 32 | Species-level clustering of genomes and lineage-specific orthogroup detection | -| VEBA-mapping_env | VEBA-mapping_env.yml | 16GB | Aligns reads to local or global index of genomes. By default uses Bowtie2 but future versions will use Salmon as well. | 32 | Annotates translated gene calls against UniRef, Pfam, and KOFAM | -| VEBA-phylogeny_env | VEBA-phylogeny_env.yml | 16+GB | Constructs phylogenetic trees given a marker set using concatenated protein alignments | 32 | Constructs phylogenetic trees given a marker set | -| Stable | VEBA-mapping_env | index.py | 16GB | 4 | Builds local or global index for alignment to genomes | -| Stable | VEBA-mapping_env | mapping.py | 16GB | 4 | Aligns reads to local or global index of genomes | -| Developmental | VEBA-biosynthetic_env | biosynthetic.py | 16GB | 16 | Identify biosynthetic gene clusters in prokaryotes and fungi | -| Developmental | VEBA-assembly_env | assembly-sequential.py | 32GB-128GB+ | 16 | Assemble metagenomes sequentially | -| Developmental | VEBA-amplicon_env | amplicon.py | 96GB | 16 | Automated read trim position detection, DADA2 ASV detection, taxonomic classification, and file conversion | -____________________________________________________________ - -### VEBA Database: - - -#### Profile HMM Sources: -Please cite the following sources if these marker sets are used in any way: - -``` -* Archaea_76.hmm.gz - (Anvi'o) Lee, https://doi.org/10.1093/bioinformatics/btz188 (https://github.com/merenlab/anvio/tree/master/anvio/data/hmm/Archaea_76) - -* Bacteria_71.hmm.gz - (Anvi'o) Lee modified, https://doi.org/10.1093/bioinformatics/btz188 (https://github.com/merenlab/anvio/tree/master/anvio/data/hmm/Bacteria_71) - -* Protista_83.hmm.gz - (Anvi'o) Delmont, http://merenlab.org/delmont-euk-scgs (https://github.com/merenlab/anvio/tree/master/anvio/data/hmm/Protista_83) - -* Fungi_593.hmm.gz - (FGMP) https://bmcbioinformatics.biomedcentral.com/articles/10.1186/s12859-019-2782-9 - -* CPR_43.hmm.gz - (CheckM) https://github.com/Ecogenomics/CheckM/tree/master/custom_marker_sets - -* eukaryota_odb10.hmm.gz - (BUSCO) https://busco-data.ezlab.org/v5/data/lineages/eukaryota_odb10.2020-09-10.tar.gz -``` - -Espinoza, Josh (2022): Profile HMM marker sets. figshare. Dataset. https://doi.org/10.6084/m9.figshare.19616016.v1 - -#### Microeukaryotic protein database: -A protein database is required not only for eukaryotic gene calls using MetaEuk but can also be used for MAG annotation. Many eukaryotic protein databases exist such as MMETSP, EukZoo, and EukProt, yet these are limited to marine environments, include prokaryotic sequences, or include eukaryotic sequences for organisms that would not be expected to be binned out of metagenomes such as metazoans. We combined and dereplicated MMETSP, EukZoo, EukProt, and NCBI non-redundant to include only microeukaryotes such as protists and fungi. This optimized microeukaryotic database ensures that only eukaryotic exons expected to be represented in metagenomes are utilized for eukaryotic gene modeling and the resulting MetaEuk reference targets are used for eukaryotic MAG classification. VEBA’s microeukaryotic protein database includes 48,006,918 proteins from 42,922 microeukaryotic strains. - -**Current:** - -* [VDB-Microeukaryotic\_v2.1](https://zenodo.org/record/7485114) available on Zenodo - -**Deprecated:** - -* [VDB-Microeukaryotic\_v1](https://figshare.com/articles/dataset/Microeukaryotic_Protein_Database/19668855) available on FigShare - -#### Database Structure: - -**Current:** -*VEBA Database* version: `VDB_v5.1` - -* `VDB_v5` → `VDB_v5.1` updates `GTDB` database from `r207_v2` → `r214`. -* Changes `${VEBA_DATABASE}/Classify/GTDBTk` → `${VEBA_DATABASE}/Classify/GTDB`. -* Adds `gtdb_r214.msh` to `${VEBA_DATABASE}/Classify/GTDB/mash/` for ANI screens. -* Adds `VFDB` to `${VEBA_DATABASE}/Annotate/VFDB/VFDB_setA_pro.dmnd` for virulence factor annotation. - -``` -tree -L 3 . -├── ACCESS_DATE -├── Annotate -│   ├── KOFAM -│   │   ├── ko_list -│   │   └── profiles -│   ├── MIBiG -│   │   └── mibig_v3.1.dmnd -│   ├── NCBIfam-AMRFinder -│   │   ├── NCBIfam-AMRFinder.changelog.txt -│   │   ├── NCBIfam-AMRFinder.hmm.gz -│   │   └── NCBIfam-AMRFinder.tsv -│   ├── Pfam -│   │   ├── Pfam-A.hmm.gz -│   │   └── relnotes.txt -│   └── UniRef -│   ├── uniref50.dmnd -│   ├── uniref50.release_note -│   ├── uniref90.dmnd -│   └── uniref90.release_note -├── Classify -│   ├── CheckM2 -│   │   └── uniref100.KO.1.dmnd -│   ├── CheckV -│   │   ├── genome_db -│   │   ├── hmm_db -│   │   └── README.txt -│   ├── geNomad -│   │   ├── genomad_db -│   │   ├── genomad_db.dbtype -│   │   ├── genomad_db_h -│   │   ├── genomad_db_h.dbtype -│   │   ├── genomad_db_h.index -│   │   ├── genomad_db.index -│   │   ├── genomad_db.lookup -│   │   ├── genomad_db_mapping -│   │   ├── genomad_db.source -│   │   ├── genomad_db_taxonomy -│   │   ├── genomad_integrase_db -│   │   ├── genomad_integrase_db.dbtype -│   │   ├── genomad_integrase_db_h -│   │   ├── genomad_integrase_db_h.dbtype -│   │   ├── genomad_integrase_db_h.index -│   │   ├── genomad_integrase_db.index -│   │   ├── genomad_integrase_db.lookup -│   │   ├── genomad_integrase_db.source -│   │   ├── genomad_marker_metadata.tsv -│   │   ├── genomad_mini_db -> genomad_db -│   │   ├── genomad_mini_db.dbtype -│   │   ├── genomad_mini_db_h -> genomad_db_h -│   │   ├── genomad_mini_db_h.dbtype -> genomad_db_h.dbtype -│   │   ├── genomad_mini_db_h.index -> genomad_db_h.index -│   │   ├── genomad_mini_db.index -│   │   ├── genomad_mini_db.lookup -> genomad_db.lookup -│   │   ├── genomad_mini_db_mapping -> genomad_db_mapping -│   │   ├── genomad_mini_db.source -> genomad_db.source -│   │   ├── genomad_mini_db_taxonomy -> genomad_db_taxonomy -│   │   ├── mini_set_ids -│   │   ├── names.dmp -│   │   ├── nodes.dmp -│   │   ├── plasmid_hallmark_annotation.txt -│   │   ├── version.txt -│   │   └── virus_hallmark_annotation.txt -│   ├── GTDB -│   │   ├── fastani -│   │   ├── markers -│   │   ├── mash -│   │   ├── masks -│   │   ├── metadata -│   │   ├── mrca_red -│   │   ├── msa -│   │   ├── pplacer -│   │   ├── radii -│   │   ├── split -│   │   ├── taxonomy -│   │   └── temp -│   ├── Microeukaryotic -│   │   ├── humann_uniref50_annotations.tsv.gz -│   │   ├── md5_checksums -│   │   ├── microeukaryotic -│   │   ├── microeukaryotic.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10 -│   │   ├── microeukaryotic.eukaryota_odb10.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h -│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h.index -│   │   ├── microeukaryotic.eukaryota_odb10.index -│   │   ├── microeukaryotic.eukaryota_odb10.lookup -│   │   ├── microeukaryotic.eukaryota_odb10.source -│   │   ├── microeukaryotic_h -│   │   ├── microeukaryotic_h.dbtype -│   │   ├── microeukaryotic_h.index -│   │   ├── microeukaryotic.index -│   │   ├── microeukaryotic.lookup -│   │   ├── microeukaryotic.source -│   │   ├── reference.eukaryota_odb10.list -│   │   ├── RELEASE_NOTES -│   │   ├── source_taxonomy.tsv.gz -│   │   ├── source_to_lineage.dict.pkl.gz -│   │   └── target_to_source.dict.pkl.gz -│   └── NCBITaxonomy -│   ├── citations.dmp -│   ├── delnodes.dmp -│   ├── division.dmp -│   ├── gc.prt -│   ├── gencode.dmp -│   ├── merged.dmp -│   ├── names.dmp -│   ├── nodes.dmp -│   ├── prot.accession2taxid.FULL.gz -│   └── readme.txt -├── Contamination -│   ├── AntiFam -│   │   ├── AntiFam.hmm.gz -│   │   ├── relnotes -│   │   └── version -│   ├── chm13v2.0 -│   │   ├── chm13v2.0.1.bt2 -│   │   ├── chm13v2.0.2.bt2 -│   │   ├── chm13v2.0.3.bt2 -│   │   ├── chm13v2.0.4.bt2 -│   │   ├── chm13v2.0.rev.1.bt2 -│   │   └── chm13v2.0.rev.2.bt2 -│   └── kmers -│   └── ribokmers.fa.gz -└── MarkerSets - ├── Archaea_76.hmm.gz - ├── Bacteria_71.hmm.gz - ├── CPR_43.hmm.gz - ├── eukaryota_odb10.hmm.gz - ├── eukaryota_odb10.scores_cutoff.tsv.gz - ├── Fungi_593.hmm.gz - ├── Protista_83.hmm.gz - └── README -``` - -**Deprecated:** - -
- *VEBA Database* version: `VDB_v5` - -`VDB_v4` → `VDB_v5` replaces `nr` with `UniRef90` and `UniRef50`. Also includes `MiBIG` database. - -``` -tree -L 3 . -├── ACCESS_DATE -├── Annotate -│   ├── KOFAM -│   │   ├── ko_list -│   │   └── profiles -│   ├── MIBiG -│   │   └── mibig_v3.1.dmnd -│   ├── NCBIfam-AMRFinder -│   │   ├── NCBIfam-AMRFinder.changelog.txt -│   │   ├── NCBIfam-AMRFinder.hmm.gz -│   │   └── NCBIfam-AMRFinder.tsv -│   ├── Pfam -│   │   ├── Pfam-A.hmm.gz -│   │   └── relnotes.txt -│   └── UniRef -│   ├── uniref50.dmnd -│   └── uniref90.dmnd -├── Classify -│   ├── CheckM2 -│   │   └── uniref100.KO.1.dmnd -│   ├── CheckV -│   │   ├── genome_db -│   │   ├── hmm_db -│   │   └── README.txt -│   ├── geNomad -│   │   ├── genomad_db -│   │   ├── genomad_db.dbtype -│   │   ├── genomad_db_h -│   │   ├── genomad_db_h.dbtype -│   │   ├── genomad_db_h.index -│   │   ├── genomad_db.index -│   │   ├── genomad_db.lookup -│   │   ├── genomad_db_mapping -│   │   ├── genomad_db.source -│   │   ├── genomad_db_taxonomy -│   │   ├── genomad_integrase_db -│   │   ├── genomad_integrase_db.dbtype -│   │   ├── genomad_integrase_db_h -│   │   ├── genomad_integrase_db_h.dbtype -│   │   ├── genomad_integrase_db_h.index -│   │   ├── genomad_integrase_db.index -│   │   ├── genomad_integrase_db.lookup -│   │   ├── genomad_integrase_db.source -│   │   ├── genomad_marker_metadata.tsv -│   │   ├── genomad_mini_db -> genomad_db -│   │   ├── genomad_mini_db.dbtype -│   │   ├── genomad_mini_db_h -> genomad_db_h -│   │   ├── genomad_mini_db_h.dbtype -> genomad_db_h.dbtype -│   │   ├── genomad_mini_db_h.index -> genomad_db_h.index -│   │   ├── genomad_mini_db.index -│   │   ├── genomad_mini_db.lookup -> genomad_db.lookup -│   │   ├── genomad_mini_db_mapping -> genomad_db_mapping -│   │   ├── genomad_mini_db.source -> genomad_db.source -│   │   ├── genomad_mini_db_taxonomy -> genomad_db_taxonomy -│   │   ├── mini_set_ids -│   │   ├── names.dmp -│   │   ├── nodes.dmp -│   │   ├── plasmid_hallmark_annotation.txt -│   │   ├── version.txt -│   │   └── virus_hallmark_annotation.txt -│   ├── GTDBTk -│   │   ├── fastani -│   │   ├── markers -│   │   ├── masks -│   │   ├── metadata -│   │   ├── mrca_red -│   │   ├── msa -│   │   ├── pplacer -│   │   ├── radii -│   │   ├── split -│   │   ├── taxonomy -│   │   └── temp -│   ├── Microeukaryotic -│   │   ├── humann_uniref50_annotations.tsv.gz -│   │   ├── md5_checksums -│   │   ├── microeukaryotic -│   │   ├── microeukaryotic.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10 -│   │   ├── microeukaryotic.eukaryota_odb10.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h -│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h.index -│   │   ├── microeukaryotic.eukaryota_odb10.index -│   │   ├── microeukaryotic.eukaryota_odb10.lookup -│   │   ├── microeukaryotic.eukaryota_odb10.source -│   │   ├── microeukaryotic_h -│   │   ├── microeukaryotic_h.dbtype -│   │   ├── microeukaryotic_h.index -│   │   ├── microeukaryotic.index -│   │   ├── microeukaryotic.lookup -│   │   ├── microeukaryotic.source -│   │   ├── reference.eukaryota_odb10.list -│   │   ├── RELEASE_NOTES -│   │   ├── source_taxonomy.tsv.gz -│   │   ├── source_to_lineage.dict.pkl.gz -│   │   └── target_to_source.dict.pkl.gz -│   └── NCBITaxonomy -│   ├── citations.dmp -│   ├── delnodes.dmp -│   ├── division.dmp -│   ├── gc.prt -│   ├── gencode.dmp -│   ├── images.dmp -│   ├── merged.dmp -│   ├── names.dmp -│   ├── nodes.dmp -│   ├── prot.accession2taxid.FULL.gz -│   └── readme.txt -├── Contamination -│   ├── AntiFam -│   │   ├── AntiFam.hmm.gz -│   │   ├── relnotes -│   │   └── version -│   ├── chm13v2.0 -│   │   ├── chm13v2.0.1.bt2 -│   │   ├── chm13v2.0.2.bt2 -│   │   ├── chm13v2.0.3.bt2 -│   │   ├── chm13v2.0.4.bt2 -│   │   ├── chm13v2.0.rev.1.bt2 -│   │   └── chm13v2.0.rev.2.bt2 -│   └── kmers -│   └── ribokmers.fa.gz -└── MarkerSets - ├── Archaea_76.hmm.gz - ├── Bacteria_71.hmm.gz - ├── CPR_43.hmm.gz - ├── eukaryota_odb10.hmm.gz - ├── eukaryota_odb10.scores_cutoff.tsv.gz - ├── Fungi_593.hmm.gz - ├── Protista_83.hmm.gz - └── README -``` - -
- *VEBA Database* version: `VDB_v4` - - -`VDB_v4` is `VDB_v3.1` with the following changes: 1) `CheckM1` database swapped for `CheckM2` database; includes `geNomad` database; and 3) updates `CheckV` database. Refer to [development log](https://github.com/jolespin/veba/blob/main/DEVELOPMENT.md#release-v11-currently-testing-before-official-release) for specifics. - -``` -tree -L 3 . -. -├── ACCESS_DATE -├── Annotate -│   ├── KOFAM -│   │   ├── ko_list -│   │   └── profiles -│   ├── NCBIfam-AMRFinder -│   │   ├── NCBIfam-AMRFinder.changelog.txt -│   │   ├── NCBIfam-AMRFinder.hmm.gz -│   │   └── NCBIfam-AMRFinder.tsv -│   ├── nr -│   │   └── nr.dmnd -│   └── Pfam -│   ├── Pfam-A.hmm.gz -│   └── relnotes.txt -├── Classify -│   ├── CheckM2 -│   │   └── uniref100.KO.1.dmnd -│   ├── CheckV -│   │   ├── genome_db -│   │   ├── hmm_db -│   │   └── README.txt -│   ├── geNomad -│   │   ├── genomad_db -│   │   ├── genomad_db.dbtype -│   │   ├── genomad_db_h -│   │   ├── genomad_db_h.dbtype -│   │   ├── genomad_db_h.index -│   │   ├── genomad_db.index -│   │   ├── genomad_db.lookup -│   │   ├── genomad_db_mapping -│   │   ├── genomad_db.source -│   │   ├── genomad_db_taxonomy -│   │   ├── genomad_integrase_db -│   │   ├── genomad_integrase_db.dbtype -│   │   ├── genomad_integrase_db_h -│   │   ├── genomad_integrase_db_h.dbtype -│   │   ├── genomad_integrase_db_h.index -│   │   ├── genomad_integrase_db.index -│   │   ├── genomad_integrase_db.lookup -│   │   ├── genomad_integrase_db.source -│   │   ├── genomad_marker_metadata.tsv -│   │   ├── genomad_mini_db -> genomad_db -│   │   ├── genomad_mini_db.dbtype -│   │   ├── genomad_mini_db_h -> genomad_db_h -│   │   ├── genomad_mini_db_h.dbtype -> genomad_db_h.dbtype -│   │   ├── genomad_mini_db_h.index -> genomad_db_h.index -│   │   ├── genomad_mini_db.index -│   │   ├── genomad_mini_db.lookup -> genomad_db.lookup -│   │   ├── genomad_mini_db_mapping -> genomad_db_mapping -│   │   ├── genomad_mini_db.source -> genomad_db.source -│   │   ├── genomad_mini_db_taxonomy -> genomad_db_taxonomy -│   │   ├── mini_set_ids -│   │   ├── names.dmp -│   │   ├── nodes.dmp -│   │   ├── plasmid_hallmark_annotation.txt -│   │   ├── version.txt -│   │   └── virus_hallmark_annotation.txt -│   ├── GTDBTk -│   │   ├── fastani -│   │   ├── markers -│   │   ├── masks -│   │   ├── metadata -│   │   ├── mrca_red -│   │   ├── msa -│   │   ├── pplacer -│   │   ├── radii -│   │   ├── split -│   │   ├── taxonomy -│   │   └── temp -│   ├── Microeukaryotic -│   │   ├── humann_uniref50_annotations.tsv.gz -│   │   ├── md5_checksums -│   │   ├── microeukaryotic -│   │   ├── microeukaryotic.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10 -│   │   ├── microeukaryotic.eukaryota_odb10.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h -│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h.index -│   │   ├── microeukaryotic.eukaryota_odb10.index -│   │   ├── microeukaryotic.eukaryota_odb10.lookup -│   │   ├── microeukaryotic.eukaryota_odb10.source -│   │   ├── microeukaryotic_h -│   │   ├── microeukaryotic_h.dbtype -│   │   ├── microeukaryotic_h.index -│   │   ├── microeukaryotic.index -│   │   ├── microeukaryotic.lookup -│   │   ├── microeukaryotic.source -│   │   ├── reference.eukaryota_odb10.list -│   │   ├── RELEASE_NOTES -│   │   ├── source_taxonomy.tsv.gz -│   │   ├── source_to_lineage.dict.pkl.gz -│   │   └── target_to_source.dict.pkl.gz -│   └── NCBITaxonomy -│   ├── citations.dmp -│   ├── delnodes.dmp -│   ├── division.dmp -│   ├── gc.prt -│   ├── gencode.dmp -│   ├── merged.dmp -│   ├── names.dmp -│   ├── nodes.dmp -│   ├── prot.accession2taxid.FULL.gz -│   └── readme.txt -├── Contamination -│   ├── AntiFam -│   │   ├── AntiFam.hmm.gz -│   │   ├── relnotes -│   │   └── version -│   ├── chm13v2.0 -│   │   ├── chm13v2.0.1.bt2 -│   │   ├── chm13v2.0.2.bt2 -│   │   ├── chm13v2.0.3.bt2 -│   │   ├── chm13v2.0.4.bt2 -│   │   ├── chm13v2.0.rev.1.bt2 -│   │   └── chm13v2.0.rev.2.bt2 -│   └── kmers -│   └── ribokmers.fa.gz -└── MarkerSets - ├── Archaea_76.hmm.gz - ├── Bacteria_71.hmm.gz - ├── CPR_43.hmm.gz - ├── eukaryota_odb10.hmm.gz - ├── eukaryota_odb10.scores_cutoff.tsv.gz - ├── Fungi_593.hmm.gz - ├── Protista_83.hmm.gz - └── README - -31 directories, 96 files -``` -
- - -
- *VEBA Database* version: VDB_v3.1 - -The same as `VDB_v3` but updates `VDB-Microeukaryotic_v2` to `VDB-Microeukaryotic_v2.1` which has a `reference.eukaryota_odb10.list` containing only the subset of identifiers that core eukaryotic markers (useful for classification). - -``` -tree -L 3 . -. -├── ACCESS_DATE -├── Annotate -│   ├── KOFAM -│   │   ├── ko_list -│   │   └── profiles -│   ├── nr -│   │   └── nr.dmnd -│   └── Pfam -│   └── Pfam-A.hmm.gz -├── Classify -│   ├── CheckM -│   │   ├── distributions -│   │   ├── genome_tree -│   │   ├── hmms -│   │   ├── hmms_ssu -│   │   ├── img -│   │   ├── pfam -│   │   ├── selected_marker_sets.tsv -│   │   ├── taxon_marker_sets.tsv -│   │   └── test_data -│   ├── CheckV -│   │   ├── genome_db -│   │   ├── hmm_db -│   │   └── README.txt -│   ├── GTDBTk -│   │   ├── fastani -│   │   ├── markers -│   │   ├── masks -│   │   ├── metadata -│   │   ├── mrca_red -│   │   ├── msa -│   │   ├── pplacer -│   │   ├── radii -│   │   ├── split -│   │   ├── taxonomy -│   │   └── temp -│   ├── Microeukaryotic -│   │   ├── humann_uniref50_annotations.tsv.gz -│   │   ├── md5_checksums -│   │   ├── microeukaryotic -│   │   ├── microeukaryotic.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10 -│   │   ├── microeukaryotic.eukaryota_odb10.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h -│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h.index -│   │   ├── microeukaryotic.eukaryota_odb10.index -│   │   ├── microeukaryotic.eukaryota_odb10.lookup -│   │   ├── microeukaryotic.eukaryota_odb10.source -│   │   ├── microeukaryotic_h -│   │   ├── microeukaryotic_h.dbtype -│   │   ├── microeukaryotic_h.index -│   │   ├── microeukaryotic.index -│   │   ├── microeukaryotic.lookup -│   │   ├── microeukaryotic.source -│   │   ├── reference.eukaryota_odb10.list -│   │   ├── reference.eukaryota_odb10.list.md5 -│   │   ├── reference.faa.gz -│   │   ├── RELEASE_NOTES -│   │   ├── source_taxonomy.tsv.gz -│   │   ├── source_to_lineage.dict.pkl.gz -│   │   └── target_to_source.dict.pkl.gz -│   └── NCBITaxonomy -│   ├── citations.dmp -│   ├── delnodes.dmp -│   ├── division.dmp -│   ├── gc.prt -│   ├── gencode.dmp -│   ├── merged.dmp -│   ├── names.dmp -│   ├── nodes.dmp -│   ├── prot.accession2taxid.FULL.gz -│   ├── readme.txt -│   ├── taxa.sqlite -│   └── taxa.sqlite.traverse.pkl -├── Contamination -│   ├── chm13v2.0 -│   │   ├── chm13v2.0.1.bt2 -│   │   ├── chm13v2.0.2.bt2 -│   │   ├── chm13v2.0.3.bt2 -│   │   ├── chm13v2.0.4.bt2 -│   │   ├── chm13v2.0.rev.1.bt2 -│   │   └── chm13v2.0.rev.2.bt2 -│   └── kmers -│   └── ribokmers.fa.gz -├── MarkerSets -│   ├── Archaea_76.hmm -│   ├── Bacteria_71.hmm -│   ├── CPR_43.hmm -│   ├── eukaryota_odb10.hmm -│   ├── eukaryota_odb10.scores_cutoff.tsv.gz -│   ├── Fungi_593.hmm -│   ├── Protista_83.hmm -│   └── README -└── SIZE - -35 directories, 60 files -``` -
- - -
- *VEBA Database* version: VDB_v3 - -``` -tree -L 3 . -. -├── ACCESS_DATE -├── Annotate -│   ├── KOFAM -│   │   ├── ko_list -│   │   └── profiles -│   ├── nr -│   │   └── nr.dmnd -│   └── Pfam -│   └── Pfam-A.hmm.gz -├── Classify -│   ├── CheckM -│   │   ├── distributions -│   │   ├── genome_tree -│   │   ├── hmms -│   │   ├── hmms_ssu -│   │   ├── img -│   │   ├── pfam -│   │   ├── selected_marker_sets.tsv -│   │   ├── taxon_marker_sets.tsv -│   │   └── test_data -│   ├── CheckV -│   │   ├── genome_db -│   │   ├── hmm_db -│   │   └── README.txt -│   ├── GTDBTk -│   │   ├── fastani -│   │   ├── markers -│   │   ├── masks -│   │   ├── metadata -│   │   ├── mrca_red -│   │   ├── msa -│   │   ├── pplacer -│   │   ├── radii -│   │   ├── split -│   │   ├── taxonomy -│   │   └── temp -│   ├── Microeukaryotic -│   │   ├── humann_uniref50_annotations.tsv.gz -│   │   ├── md5_checksums -│   │   ├── microeukaryotic -│   │   ├── microeukaryotic.dbtype -│   │   ├── microeukaryotic_h -│   │   ├── microeukaryotic_h.dbtype -│   │   ├── microeukaryotic_h.index -│   │   ├── microeukaryotic.index -│   │   ├── microeukaryotic.lookup -│   │   ├── microeukaryotic.source -│   │   ├── reference.faa.gz -│   │   ├── RELEASE_NOTES -│   │   ├── source_taxonomy.tsv.gz -│   │   ├── source_to_lineage.dict.pkl.gz -│   │   └── target_to_source.dict.pkl.gz -│   └── NCBITaxonomy -│   ├── citations.dmp -│   ├── delnodes.dmp -│   ├── division.dmp -│   ├── gc.prt -│   ├── gencode.dmp -│   ├── merged.dmp -│   ├── names.dmp -│   ├── nodes.dmp -│   ├── prot.accession2taxid.FULL.gz -│   ├── readme.txt -│   ├── taxa.sqlite -│   └── taxa.sqlite.traverse.pkl -├── Contamination -│   ├── chm13v2.0 -│   │   ├── chm13v2.0.1.bt2 -│   │   ├── chm13v2.0.2.bt2 -│   │   ├── chm13v2.0.3.bt2 -│   │   ├── chm13v2.0.4.bt2 -│   │   ├── chm13v2.0.rev.1.bt2 -│   │   └── chm13v2.0.rev.2.bt2 -│   └── kmers -│   └── ribokmers.fa.gz -├── MarkerSets -│   ├── Archaea_76.hmm -│   ├── Bacteria_71.hmm -│   ├── CPR_43.hmm -│   ├── eukaryota_odb10.hmm -│   ├── eukaryota_odb10.scores_cutoff.tsv.gz -│   ├── Fungi_593.hmm -│   ├── Protista_83.hmm -│   └── README -└── SIZE - -35 directories, 50 files -``` - -
- - -
- *VEBA Database* version: VDB_v2 - -* Compatible with *VEBA* version: `v1.0.2a+` - - -``` -tree -L 3 . -. -├── ACCESS_DATE -├── Annotate -│   ├── KOFAM -│   │   ├── ko_list -│   │   └── profiles -│   ├── nr -│   │   └── nr.dmnd -│   └── Pfam -│   └── Pfam-A.hmm.gz -├── Classify -│   ├── CheckM -│   │   ├── distributions -│   │   ├── genome_tree -│   │   ├── hmms -│   │   ├── hmms_ssu -│   │   ├── img -│   │   ├── pfam -│   │   ├── selected_marker_sets.tsv -│   │   ├── taxon_marker_sets.tsv -│   │   └── test_data -│   ├── CheckV -│   │   ├── genome_db -│   │   ├── hmm_db -│   │   └── README.txt -│   ├── GTDBTk -│   │   ├── fastani -│   │   ├── markers -│   │   ├── masks -│   │   ├── metadata -│   │   ├── mrca_red -│   │   ├── msa -│   │   ├── pplacer -│   │   ├── radii -│   │   ├── split -│   │   ├── taxonomy -│   │   └── temp -│   ├── Microeukaryotic -│   │   ├── microeukaryotic -│   │   ├── microeukaryotic.dbtype -│   │   ├── microeukaryotic_h -│   │   ├── microeukaryotic_h.dbtype -│   │   ├── microeukaryotic_h.index -│   │   ├── microeukaryotic.index -│   │   ├── microeukaryotic.lookup -│   │   ├── microeukaryotic.source -│   │   ├── reference.rmdup.iupac.relabeled.no_deprecated.complete_lineage.faa.gz -│   │   ├── source_taxonomy.tsv.gz -│   │   ├── source_to_lineage.dict.pkl.gz -│   │   └── target_to_source.dict.pkl.gz -│   └── NCBITaxonomy -│   ├── citations.dmp -│   ├── delnodes.dmp -│   ├── division.dmp -│   ├── gc.prt -│   ├── gencode.dmp -│   ├── merged.dmp -│   ├── names.dmp -│   ├── nodes.dmp -│   ├── prot.accession2taxid.FULL.gz -│   ├── readme.txt -│   ├── taxa.sqlite -│   └── taxa.sqlite.traverse.pkl -├── Contamination -│   ├── chm13v2.0 -│   │   ├── chm13v2.0.1.bt2 -│   │   ├── chm13v2.0.2.bt2 -│   │   ├── chm13v2.0.3.bt2 -│   │   ├── chm13v2.0.4.bt2 -│   │   ├── chm13v2.0.rev.1.bt2 -│   │   └── chm13v2.0.rev.2.bt2 -│   └── kmers -│   └── ribokmers.fa.gz -├── MarkerSets -│   ├── Archaea_76.hmm -│   ├── Bacteria_71.hmm -│   ├── CPR_43.hmm -│   ├── eukaryota_odb10.hmm -│   ├── eukaryota_odb10.scores_cutoff.tsv.gz -│   ├── Fungi_593.hmm -│   ├── Protista_83.hmm -│   └── README -└── SIZE - -35 directories, 47 files - -``` -
- - -
- *VEBA Database* version: VDB_v1 - - -* Compatible with *VEBA* version: `v1.0.0`, `v1.0.1` - - -``` -tree -L 3 . -. -├── ACCESS_DATE -├── Annotate -│   ├── KOFAM -│   │   ├── ko_list -│   │   └── profiles -│   ├── nr -│   │   └── nr.dmnd -│   └── Pfam -│   └── Pfam-A.hmm.gz -├── Classify -│   ├── CheckM -│   │   ├── distributions -│   │   ├── genome_tree -│   │   ├── hmms -│   │   ├── hmms_ssu -│   │   ├── img -│   │   ├── pfam -│   │   ├── selected_marker_sets.tsv -│   │   ├── taxon_marker_sets.tsv -│   │   └── test_data -│   ├── CheckV -│   │   ├── genome_db -│   │   ├── hmm_db -│   │   └── README.txt -│   ├── GTDBTk -│   │   ├── fastani -│   │   ├── markers -│   │   ├── masks -│   │   ├── metadata -│   │   ├── mrca_red -│   │   ├── msa -│   │   ├── pplacer -│   │   ├── radii -│   │   ├── split -│   │   ├── taxonomy -│   │   └── temp -│   ├── Microeukaryotic -│   │   ├── humann_uniref50_annotations.tsv.gz -│   │   ├── md5_checksums -│   │   ├── microeukaryotic -│   │   ├── microeukaryotic.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10 -│   │   ├── microeukaryotic.eukaryota_odb10.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h -│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype -│   │   ├── microeukaryotic.eukaryota_odb10_h.index -│   │   ├── microeukaryotic.eukaryota_odb10.index -│   │   ├── microeukaryotic.eukaryota_odb10.lookup -│   │   ├── microeukaryotic.eukaryota_odb10.source -│   │   ├── microeukaryotic_h -│   │   ├── microeukaryotic_h.dbtype -│   │   ├── microeukaryotic_h.index -│   │   ├── microeukaryotic.index -│   │   ├── microeukaryotic.lookup -│   │   ├── microeukaryotic.source -│   │   ├── reference.eukaryota_odb10.list -│   │   ├── reference.eukaryota_odb10.list.md5 -│   │   ├── reference.faa.gz -│   │   ├── RELEASE_NOTES -│   │   ├── source_taxonomy.tsv.gz -│   │   ├── source_to_lineage.dict.pkl.gz -│   │   └── target_to_source.dict.pkl.gz -│   └── NCBITaxonomy -│   ├── citations.dmp -│   ├── delnodes.dmp -│   ├── division.dmp -│   ├── gc.prt -│   ├── gencode.dmp -│   ├── merged.dmp -│   ├── names.dmp -│   ├── nodes.dmp -│   ├── prot.accession2taxid.FULL.gz -│   ├── readme.txt -│   ├── taxa.sqlite -│   └── taxa.sqlite.traverse.pkl -├── Contamination -│   ├── chm13v2.0 -│   │   ├── chm13v2.0.1.bt2 -│   │   ├── chm13v2.0.2.bt2 -│   │   ├── chm13v2.0.3.bt2 -│   │   ├── chm13v2.0.4.bt2 -│   │   ├── chm13v2.0.rev.1.bt2 -│   │   └── chm13v2.0.rev.2.bt2 -│   └── kmers -│   └── ribokmers.fa.gz -├── MarkerSets -│   ├── Archaea_76.hmm -│   ├── Bacteria_71.hmm -│   ├── CPR_43.hmm -│   ├── eukaryota_odb10.hmm -│   ├── eukaryota_odb10.scores_cutoff.tsv.gz -│   ├── Fungi_593.hmm -│   ├── Protista_83.hmm -│   └── README -└── SIZE - -35 directories, 60 files -``` - -``` -tree -L 3 . -. -. -├── ACCESS_DATE -├── Annotate -│   ├── KOFAM -│   │   ├── ko_list -│   │   └── profiles -│   ├── nr -│   │   └── nr.dmnd -│   └── Pfam -│   └── Pfam-A.hmm.gz -├── Classify -│   ├── CheckM -│   │   ├── distributions -│   │   ├── genome_tree -│   │   ├── hmms -│   │   ├── hmms_ssu -│   │   ├── img -│   │   ├── pfam -│   │   ├── selected_marker_sets.tsv -│   │   ├── taxon_marker_sets.tsv -│   │   └── test_data -│   ├── CheckV -│   │   ├── genome_db -│   │   ├── hmm_db -│   │   └── README.txt -│   ├── GTDBTk -│   │   ├── fastani -│   │   ├── manifest.tsv -│   │   ├── markers -│   │   ├── masks -│   │   ├── metadata -│   │   ├── mrca_red -│   │   ├── msa -│   │   ├── pplacer -│   │   ├── radii -│   │   └── taxonomy -│   ├── Microeukaryotic -│   │   ├── microeukaryotic -│   │   ├── microeukaryotic.dbtype -│   │   ├── microeukaryotic_h -│   │   ├── microeukaryotic_h.dbtype -│   │   ├── microeukaryotic_h.index -│   │   ├── microeukaryotic.index -│   │   ├── microeukaryotic.lookup -│   │   ├── microeukaryotic.source -│   │   ├── reference.rmdup.iupac.relabeled.no_deprecated.complete_lineage.faa.gz -│   │   ├── source_taxonomy.tsv.gz -│   │   ├── source_to_lineage.dict.pkl.gz -│   │   └── target_to_source.dict.pkl.gz -│   └── NCBITaxonomy -│   ├── citations.dmp -│   ├── delnodes.dmp -│   ├── division.dmp -│   ├── gc.prt -│   ├── gencode.dmp -│   ├── merged.dmp -│   ├── names.dmp -│   ├── nodes.dmp -│   ├── prot.accession2taxid.FULL.gz -│   ├── readme.txt -│   ├── taxa.sqlite -│   └── taxa.sqlite.traverse.pkl -├── Contamination -│   ├── grch38 -│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.1.bt2 -│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.2.bt2 -│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.3.bt2 -│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.4.bt2 -│   │   ├── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.rev.1.bt2 -│   │   └── GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.rev.2.bt2 -│   └── kmers -│   └── ribokmers.fa.gz -└── MarkerSets - ├── Archaea_76.hmm - ├── Bacteria_71.hmm - ├── CPR_43.hmm - ├── eukaryota_odb10.hmm - ├── eukaryota_odb10.scores_cutoff.tsv.gz - ├── Fungi_593.hmm - ├── Protista_83.hmm - └── README - -33 directories, 47 files -``` - -
- - -____________________________________________________________ - -#### Version Notes: - -* The `nr.dmnd` should be built using NCBI's taxonomy info. The `download_database.sh` takes care of this but if you are using a prebuilt `nr.dmnd` database then use following command for reference: `diamond makedb --in ${DATABASE_DIRECTORY}/nr.gz --db ${DATABASE_DIRECTORY}/Annotate/nr.dmnd --taxonmap ${DATABASE_DIRECTORY}/Classify/NCBITaxonomy/prot.accession2taxid.FULL.gz --taxonnodes ${DATABASE_DIRECTORY}/Classify/NCBITaxonomy/nodes.dmp --taxonnames ${DATABASE_DIRECTORY}/Classify/NCBITaxonomy/names.dmp` -* The NCBI taxonomy should be downloaded on the same date as NR to make sure the identifiers match up between datasets. NCBI deprecates taxonomy identifiers and adds new ones between versions so downloading on the same day should minimize that discrepancy. One caveat NCBI NR and taxonomy databases is the versioning is difficult to discern. -* For the human contamination, if you use `KneadData` and already have a `Bowtie2` index for human then you can use that instead. The only module that uses this is `preprocess.py` and you have to specify this directly when running (i.e., it's optional) so it doesn't matter if it's in the database directory or not (same with ribokmers.fa.gz). -* `CheckM2` only has 1 database version at this time so it isn't an issue. -* `KOFAM` and `Pfam` just uses these as annotations so any version should work perfectly. -* Again, if you are low on disk space and already have these installed then just symlink them with the structure above. If so, them just comment out those sections of `download_databases.sh`. - -_______________________________________________________ -**VEBA v1.1.0 Specific Versions:** -* As of `v1.1.0` *VEBA* requires `CheckM2` database instead of `CheckM` database, the `geNomad` database, and v1.5 `CheckV` database. - - -**VEBA v1.0.2a Specific Versions:** - -* As of `v1.0.2a` *VEBA* uses *GTDB-Tk v2.x* which requires *GTDB R207_v2* and above. If you are using *GTDB-Tk v1.0.0/1* then you will need to use either *GTDB R202/R207*. - -**VEBA v1.0.0/1 Specific Versions:** - -* The only mandatory datbase version is `R202` for `GTDBTk` because `VEBA` is built on `v1.5.0 ≤ GTDBTk ≤ v1.70`. The next major `VEBA` update will upgrade to `≥ GTDBTk v2.0.0` and the associated `R207_v2` database. - -_______________________________________________________ - -If you have any issues, please create a GitHub issue with the prefix `[Database]` followed by the question. \ No newline at end of file diff --git a/install/check_installation.sh b/install/check_installation.sh index ed596d4..d7b94eb 100644 --- a/install/check_installation.sh +++ b/install/check_installation.sh @@ -1,5 +1,5 @@ #!/bin/bash -# __VERSION__ = "2023.3.14" +# __version__ = "2023.3.14" eval "$(conda shell.bash hook)" diff --git a/install/docker/CONTAINER_RESOURCES.tsv b/install/docker/CONTAINER_RESOURCES.tsv deleted file mode 100644 index f924d5d..0000000 --- a/install/docker/CONTAINER_RESOURCES.tsv +++ /dev/null @@ -1,13 +0,0 @@ -Environment Environment File Resources Description -VEBA-annotate_env VEBA-annotate_env.yml 16GB-128GB Annotating proteins with Diamond, HMMER, and KOFAM -VEBA-assembly_env VEBA-assembly_env.yml 32GB-128GB Assembling metagenomes/metatranscriptomes, mapping reads to assemblies, and preparing for genome binning -VEBA-biosynthetic_env VEBA-biosynthetic_env.yml 16GB BGC detection with antiSMASH, convert genbank files to tabular format, assess novelty of BGC -VEBA-preprocess_env VEBA-preprocess_env.yml 4GB-16GB Preprocess reads by quality filtering, removing adapters, removing human contamination, and properly pairing -VEBA-binning-eukaryotic_env VEBA-binning-eukaryotic_env.yml 128GB Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment -VEBA-binning-prokaryotic_env VEBA-binning-prokaryotic_env.yml 16GB Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment -VEBA-binning-viral_env VEBA-binning-viral_env.yml 16GB Detection of viral genomes and quality assessment -VEBA-classify_env VEBA-classify_env.yml 16GB-64GB Classify eukaryotic, prokaryotic, and viral genomes -VEBA-cluster_env VEBA-cluster_env.yml 32GB Species-level clustering of genomes and lineage-specific orthogroup detection. -VEBA-database_env VEBA-database_env.yml 64GB Contains all the programs needed to download and build the VEBA database -VEBA-mapping_env VEBA-mapping_env.yml 16GB Aligns reads to local or global index of genomes. By default uses Bowtie2 but future versions will use Salmon as well. -VEBA-phylogeny_env VEBA-phylogeny_env.yml 16+GB Constructs phylogenetic trees given a marker set using concatenated protein alignments \ No newline at end of file diff --git a/install/docker/build_docker_image.sh b/install/docker/build_docker_image.sh index 2a85930..7dc45a2 100644 --- a/install/docker/build_docker_image.sh +++ b/install/docker/build_docker_image.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# __version__ = "2023.7.11" + ENV_NAME=$1; VERSION=$(head -n 1 ../../VERSION) diff --git a/install/docker/dockerize_environments.sh b/install/docker/dockerize_environments.sh index 3db5a86..2b09536 100644 --- a/install/docker/dockerize_environments.sh +++ b/install/docker/dockerize_environments.sh @@ -1,7 +1,9 @@ +#!/usr/bin/env bash +# __version__ = "2023.7.11" + for ENV_NAME in $(ls ../environments/ | grep ".yml"); do ENV_NAME=$(echo $ENV_NAME | cut -f1 -d ".") echo $ENV_NAME - time(bash build_docker_image.sh ${ENV_NAME}) done diff --git a/install/download_databases.sh b/install/download_databases.sh index 8ea1e35..1e8eb62 100644 --- a/install/download_databases.sh +++ b/install/download_databases.sh @@ -1,5 +1,5 @@ #!/bin/bash -# __VERSION__ = "2023.6.20" +# __version__ = "2023.6.20" # VEBA_DATABASE_VERSION = "VDB_v5.1" # MICROEUKAYROTIC_DATABASE_VERSION = "VDB-Microeukaryotic_v2.1" diff --git a/install/install_veba.sh b/install/install_veba.sh index 47bf3df..8c8fa6d 100644 --- a/install/install_veba.sh +++ b/install/install_veba.sh @@ -1,5 +1,6 @@ #!/bin/bash -# __VERSION__ = "2023.3.27" +# __version__ = "2023.3.27" + SCRIPT_PATH=$(realpath $0) PREFIX=$(echo $SCRIPT_PATH | python -c "import sys; print('/'.join(sys.stdin.read().split('/')[:-1]))") CONDA_BASE=$(conda run -n base bash -c "echo \${CONDA_PREFIX}") diff --git a/install/uninstall_veba.sh b/install/uninstall_veba.sh index ceb27f5..ada8f8a 100644 --- a/install/uninstall_veba.sh +++ b/install/uninstall_veba.sh @@ -1,5 +1,6 @@ #!/bin/bash -# __VERSION__ = "2023.5.15" +# __version__ = "2023.5.15" + CONDA_BASE=$(conda run -n base bash -c "echo \${CONDA_PREFIX}") for FP in ${CONDA_BASE}/envs/VEBA-*_env; do diff --git a/install/update_environment_scripts.sh b/install/update_environment_scripts.sh index 207a76e..2c98bc7 100644 --- a/install/update_environment_scripts.sh +++ b/install/update_environment_scripts.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# __version__ = 2023.01.05 +# __version__ = "2023.01.05" # Usage: git clone https://github.com/jolespin/veba && update_environment_scripts.sh /path/to/veba_repository echo "-----------------------------------------------------------------------------------------------------" @@ -36,4 +36,4 @@ for ENV_PREFIX in ${CONDA_BASE}/envs/VEBA-*; do cp ${VEBA_REPOSITORY_DIRECTORY}/src/*.py ${ENV_PREFIX}/bin/ cp -r ${VEBA_REPOSITORY_DIRECTORY}/src/scripts/ ${ENV_PREFIX}/bin/ ln -sf ${ENV_PREFIX}/bin/scripts/* ${ENV_PREFIX}/bin/ - done \ No newline at end of file + done diff --git a/install/update_environment_variables.sh b/install/update_environment_variables.sh index 84acb26..74f9ddb 100644 --- a/install/update_environment_variables.sh +++ b/install/update_environment_variables.sh @@ -1,5 +1,5 @@ #!/bin/bash -# __VERSION__ = "2023.6.14" +# __version__ = "2023.6.14" # Create database DATABASE_DIRECTORY=${1:-"."} diff --git a/src/MODULE_RESOURCES b/src/MODULE_RESOURCES index 458972f..a30553e 100644 --- a/src/MODULE_RESOURCES +++ b/src/MODULE_RESOURCES @@ -5,14 +5,14 @@ Stable VEBA-assembly_env coverage.py 24GB 16 Align reads to (concatenated) refer Stable VEBA-binning-prokaryotic_env binning-prokaryotic.py 16GB 4 Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment Stable VEBA-binning-eukaryotic_env binning-eukaryotic.py 128GB 4 Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment Stable VEBA-binning-viral_env binning-viral.py 16GB 4 Detection of viral genomes and quality assessment -Stable VEBA-classify_env classify-prokaryotic.py 64GB 32 Taxonomic classification and candidate phyla radiation adjusted quality  +Stable VEBA-classify_env classify-prokaryotic.py 64GB 32 Taxonomic classification of prokaryotic genomes  Stable VEBA-classify_env classify-eukaryotic.py 32GB 1 Taxonomic classification of eukaryotic genomes -Stable VEBA-classify_env classify-viral.py 16GB 4 Taxonomic classification and isolation source of viral genomes +Stable VEBA-classify_env classify-viral.py 16GB 4 Taxonomic classification of viral genomes Stable VEBA-cluster_env cluster.py 32GB+ 32 Species-level clustering of genomes and lineage-specific orthogroup detection Stable VEBA-annotate_env annotate.py 64GB 32 Annotates translated gene calls against NR, Pfam, and KOFAM Stable VEBA-phylogeny_env phylogeny.py 16GB+ 32 Constructs phylogenetic trees given a marker set Stable VEBA-mapping_env index.py 16GB 4 Builds local or global index for alignment to genomes Stable VEBA-mapping_env mapping.py 16GB 4 Aligns reads to local or global index of genomes -Developmental VEBA-biosynthetic_env biosynthetic.py 16GB 16 Identify biosynthetic gene clusters in prokaryotes and fungi +Stable VEBA-biosynthetic_env biosynthetic.py 16GB 16 Identify biosynthetic gene clusters in prokaryotes and fungi Developmental VEBA-assembly_env assembly-sequential.py 32GB-128GB+ 16 Assemble metagenomes sequentially Developmental VEBA-amplicon_env amplicon.py 96GB 16 Automated read trim position detection, DADA2 ASV detection, taxonomic classification, and file conversion \ No newline at end of file diff --git a/src/README.md b/src/README.md index 06c7e98..6f3a264 100755 --- a/src/README.md +++ b/src/README.md @@ -1,3 +1,4 @@ +
# Modules [![Schematic](../images/Schematic.png)](../images/Schematic.pdf) @@ -10,24 +11,28 @@ | Stable | VEBA-binning-prokaryotic_env | binning-prokaryotic.py | 16GB | 4 | Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment | | Stable | VEBA-binning-eukaryotic_env | binning-eukaryotic.py | 128GB | 4 | Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment | | Stable | VEBA-binning-viral_env | binning-viral.py | 16GB | 4 | Detection of viral genomes and quality assessment | -| Stable | VEBA-classify_env | classify-prokaryotic.py | 72GB | 32 | Taxonomic classification and candidate phyla radiation adjusted quality | +| Stable | VEBA-classify_env | classify-prokaryotic.py | 72GB | 32 | Taxonomic classification of prokaryotic genomes | | Stable | VEBA-classify_env | classify-eukaryotic.py | 32GB | 1 | Taxonomic classification of eukaryotic genomes | -| Stable | VEBA-classify_env | classify-viral.py | 16GB | 4 | Taxonomic classification and isolation source of viral genomes | +| Stable | VEBA-classify_env | classify-viral.py | 16GB | 4 | Taxonomic classification of viral genomes | | Stable | VEBA-cluster_env | cluster.py | 32GB+ | 32 | Species-level clustering of genomes and lineage-specific orthogroup detection | -| Stable | VEBA-annotate_env | annotate.py | 64GB | 32 | Annotates translated gene calls against NR, Pfam, and KOFAM | +| Stable | VEBA-annotate_env | annotate.py | 64GB | 32 | Annotates translated gene calls against UniRef, MiBIG, VFDB, Pfam, AntiFam, and KOFAM | | Stable | VEBA-phylogeny_env | phylogeny.py | 16GB+ | 32 | Constructs phylogenetic trees given a marker set | | Stable | VEBA-mapping_env | index.py | 16GB | 4 | Builds local or global index for alignment to genomes | | Stable | VEBA-mapping_env | mapping.py | 16GB | 4 | Aligns reads to local or global index of genomes | -| Developmental | VEBA-biosynthetic_env | biosynthetic.py | 16GB | 16 | Identify biosynthetic gene clusters in prokaryotes and fungi | +| Stable | VEBA-biosynthetic_env | biosynthetic.py | 16GB | 16 | Identify biosynthetic gene clusters in prokaryotes and fungi | | Developmental | VEBA-assembly_env | assembly-sequential.py | 32GB-128GB+ | 16 | Assemble metagenomes sequentially | | Developmental | VEBA-amplicon_env | amplicon.py | 96GB | 16 | Automated read trim position detection, DADA2 ASV detection, taxonomic classification, and file conversion | +

^__^

+ ______________________ ### Stable -#### preprocess – Fastq quality trimming, adapter removal, decontamination, and read statistics calculations -The preprocess module is a wrapper around our [fastq_preprocessor](https://github.com/jolespin/fastq_preprocessor) which is a modernized reimplementation of [KneadData](https://github.com/biobakery/kneaddata) that relies on fastp for ultra-fast automated adapter removal and quality trimming. Pairing of the trimmed reads is assessed and corrected using [BBTools’ repair.sh](https://sourceforge.net/projects/bbmap). If the user provides a contamination database (e.g., the human reference genome), then trimmed reads are aligned using Bowtie2 (Langmead & Salzberg, 2012) and reads that do not map to the contamination database are stored. If the --retain_contaminated_reads flag is used then the contaminated reads are stored as well. Similarly, if a k-mer reference database is provided (e.g., ribosomal k-mers) then the trimmed or decontaminated reads are aligned against the reference database using BBTools’ bbduk.sh with an option for storing. By default, the none of the contaminated or k-mer analyzed reads are stored but regardless of the choice for retaining reads, the read sets are quantified using seqkit (Shen, Le, Li, & Hu, 2016) for accounting purposes (e.g., % contamination or % ribosomal). All sequences included were downloaded using Kingfisher (https://github.com/wwood/kingfisher-download), included in the preprocess environment, which a fast and flexible program for the procurement of sequencing files and their annotations from public data sources including ENA, NCBI SRA, Amazon AWS, and Google Cloud. +#### *preprocess.py* +**Fastq quality trimming, adapter removal, decontamination, and read statistics calculations** + +The preprocess module is a wrapper around our [`fastq_preprocessor`](https://github.com/jolespin/fastq_preprocessor) which is a modernized reimplementation of [`KneadData`](https://github.com/biobakery/kneaddata) that relies on fastp for ultra-fast automated adapter removal and quality trimming. Pairing of the trimmed reads is assessed and corrected using `BBTools’ repair.sh`. If the user provides a contamination database (e.g., the human reference genome), then trimmed reads are aligned using `Bowtie2` and reads that do not map to the contamination database are stored. If the `--retain_contaminated_reads` flag is used then the contaminated reads are stored as well. Similarly, if a k-mer reference database is provided (e.g., ribosomal k-mers) then the trimmed or decontaminated reads are aligned against the reference database using `BBTools’ bbduk.sh` with an option for storing. By default, the none of the contaminated or k-mer analyzed reads are stored but regardless of the choice for retaining reads, the read sets are quantified using `seqkit` for accounting purposes (e.g., % contamination or % ribosomal). All sequences included were downloaded using `Kingfisher`, included in the preprocess environment, which a fast and flexible program for the procurement of sequencing files and their annotations from public data sources including ENA, NCBI SRA, Amazon AWS, and Google Cloud. **Conda Environment:** `conda activate VEBA-preprocess_env` @@ -35,7 +40,7 @@ The preprocess module is a wrapper around our [fastq_preprocessor](https://githu usage: preprocess.py -1 -2 -n -o |Optional| -x -k Wrapper around github.com/jolespin/fastq_preprocessor - Running: preprocess.py v2022.01.19 via Python v3.7.11 | /usr/local/devel/ANNOTATION/jespinoz/anaconda3/envs/VEBA-preprocess_env/bin/python + Running: preprocess.py v2023.5.8 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit @@ -71,7 +76,7 @@ Fastp arguments: Bowtie2 arguments: -x CONTAMINATION_INDEX, --contamination_index CONTAMINATION_INDEX Bowtie2 | path/to/contamination_index - (e.g., Human GRCh38 from ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/000/001/405/GCA_000001405.15_GRCh38/seqs_for_alignment_pipelines.ucsc_ids//GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index.tar.gz) + (e.g., Human T2T CHM13 v2 in $VEBA_DATABASE/Contamination/chm13v2.0/chm13v2.0) --retain_trimmed_reads RETAIN_TRIMMED_READS Retain fastp trimmed fastq after decontamination. 0=No, 1=yes [Default: 0] --retain_contaminated_reads RETAIN_CONTAMINATED_READS @@ -83,7 +88,7 @@ Bowtie2 arguments: BBDuk arguments: -k KMER_DATABASE, --kmer_database KMER_DATABASE BBDuk | path/to/kmer_database - (e.g., ribokmers.fa.gz from https://drive.google.com/file/d/0B3llHR93L14wS2NqRXpXakhFaEk/view?usp=sharing) + (e.g., Ribokmers in $VEBA_DATABASE/Contamination/kmers/ribokmers.fa.gz) --kmer_size KMER_SIZE BBDuk | k-mer size [Default: 31] --retain_kmer_hits RETAIN_KMER_HITS @@ -92,6 +97,7 @@ BBDuk arguments: Retain reads that do not map to k-mer database. 0=No, 1=yes [Default: 0] --bbduk_options BBDUK_OPTIONS BBDuk | More options (e.g., --arg 1) [Default: ''] + ``` **Output:** @@ -100,15 +106,19 @@ BBDuk arguments: * cleaned_2.fastq.gz - Cleaned and trimmed fastq file (reverse) * seqkit_stats.concatenated.tsv - Concatenated read statistics for all intermediate steps (e.g., fastp, bowtie2 removal of contaminated reads if provided, bbduk.sh removal of contaminated reads if provided) -#### assembly – Assemble reads, align reads to assembly, and count mapped reads -The assembly module optimizes the output for typical metagenomics workflows. In particular, the module does the following: 1) assembles reads using either metaSPAdes (Nurk, Meleshko, Korobeynikov, & Pevzner, 2017), SPAdes (A et al., 2012), rnaSPAdes (Bushmanova, Antipov, Lapidus, & Prjibelski, 2019), any of the other task-specific assemblers installed with the SPAdes package (Antipov, Raiko, Lapidus, & Pevzner, 2020; Meleshko, Hajirasouliha, & Korobeynikov, 2021), or, as of 2022.11.14, MEGAHIT; 2) builds a Bowtie2 index for the scaffolds.fasta (or transcripts.fasta if rnaSPAdes is used); 3) aligns the reads using Bowtie2 to the assembly; 4) pipes the alignment file into Samtools (Li et al., 2009) to produce a sorted BAM file (necessary for many coverage applications); 5) counts the reads mapping to each scaffold via featureCounts (Liao, Smyth, & Shi, 2014); and 6) seqkit for useful assembly statistics such as N50, number of scaffolds, and total assembly size. This module automates many critical yet overlooked workflows dealing with assemblies. +

^__^

+ +#### *assembly.py* +**Assemble reads, align reads to assembly, and count mapped reads** + +The assembly module optimizes the output for typical metagenomics workflows. In particular, the module does the following: 1) assembles reads using either `metaSPAdes`, `SPAdes`, `rnaSPAdes`, any of the other task-specific assemblers installed with the `SPAdes` package or `MEGAHIT`; 2) builds a `Bowtie2` index for the scaffolds.fasta (or transcripts.fasta if `rnaSPAdes` is used); 3) aligns the reads using `Bowtie2` to the assembly; 4) pipes the alignment file into `Samtools` to produce a sorted BAM file (necessary for many coverage applications); 5) counts the reads mapping to each scaffold via `featureCounts`; and 6) `seqkit` for useful assembly statistics such as N50, number of scaffolds, and total assembly size. This module automates many critical yet overlooked workflows dealing with assemblies. **Conda Environment**: `conda activate VEBA-assembly_env` ``` usage: assembly.py -1 -2 -n -o - Running: assembly.py v2022.11.14 via Python v3.9.7 | /Users/jespinoz/anaconda3/bin/python + Running: assembly.py v2023.5.15 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit @@ -137,6 +147,10 @@ Utility arguments: Assembler arguments: -P PROGRAM, --program PROGRAM Assembler | {spades.py, metaspades.py, rnaspades.py, megahit, metaplasmidspades.py, plasmidspades.py, coronaspades.py}} [Default: 'metaspades.py'] + -s SCAFFOLD_PREFIX, --scaffold_prefix SCAFFOLD_PREFIX + Assembler | Special options: Use NAME to use --name. Use NONE to not include a prefix. [Default: 'NAME__'] + -m MINIMUM_CONTIG_LENGTH, --minimum_contig_length MINIMUM_CONTIG_LENGTH + Minimum contig length. Should be lenient here because longer thresholds can be used for binning downstream. Recommended for metagenomes to use 1000 here. [Default: 1] --assembler_options ASSEMBLER_OPTIONS Assembler options for SPAdes-based programs and MEGAHIT (e.g. --arg 1 ) [Default: ''] @@ -157,9 +171,7 @@ Bowtie2 arguments: featureCounts arguments: --featurecounts_options FEATURECOUNTS_OPTIONS featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/ - -Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org) - + ``` **Output:** @@ -172,9 +184,12 @@ Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org) * scaffolds.fasta.saf - SAF formatted file for contig-level counts with featureCounts * seqkit_stats.tsv.gz - Assembly statistics +

^__^

+ +#### *coverage.py* +**Align reads to (concatenated) reference and counts mapped reads** -#### coverage – Align reads to (concatenated) reference and counts mapped reads -The coverage module further optimizes the output for typical metagenomics workflows. In particular, the module does the following: 1) filters contigs based on a size filter (default 1500 bp); 2) builds a Bowtie2 index for the coassembly.fasta; 3) aligns the reads from all provided samples using Bowtie2 to the assembly; 4) pipes the alignment file into Samtools to produce a sorted BAM file; 5) counts the reads mapping to each scaffold via featureCounts; and 6) seqkit for useful assembly statistics such as N50, number of scaffolds, and total assembly size (Shen et al., 2016). The preferred usage for this module is after prokaryotic, eukaryotic, and viral binning has been performed and the unbinned contigs are merged into a single coassembly used as input. The outputs of this module are expected to be used as a final pass through prokaryotic and eukaryotic binning modules. +The coverage module further optimizes the output for typical metagenomics workflows. In particular, the module does the following: 1) filters contigs based on a size filter (default 1500 bp); 2) builds a `Bowtie2` index for the reference.fasta; 3) aligns the reads from all provided samples using `Bowtie2` to the assembly; 4) pipes the alignment file into `Samtools` to produce a sorted BAM file; 5) counts the reads mapping to each scaffold via `featureCounts`; and 6) `seqkit` for useful assembly statistics such as N50, number of scaffolds, and total assembly size. The preferred usage for this module is after prokaryotic, eukaryotic, and viral binning has been performed and the unbinned contigs are merged into a single coassembly used as input. The outputs of this module are expected to be used as a final pass through prokaryotic and eukaryotic binning modules. **Conda Environment**: `conda activate VEBA-assembly_env` @@ -182,16 +197,16 @@ The coverage module further optimizes the output for typical metagenomics workfl ``` usage: coverage.py -f -r -o - Running: coverage.py v2022.04.12 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: coverage.py v2023.5.16 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit Required I/O arguments: -f FASTA, --fasta FASTA - path/to/reference.fasta [Required] + path/to/reference.fasta. Recommended usage is for merging unbinned contigs. [Required] -r READS, --reads READS - path/to/reads_table.tsv with the following format: [id_sample][path/to/r1.fastq.gz][path/to/r2.fastq.gz], No header + path/to/reads_table.tsv with the following format: [id\_sample][path/to/r1.fastq.gz][path/to/r2.fastq.gz], No header -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY path/to/project_directory [Default: veba_output/assembly/multisample] @@ -216,12 +231,14 @@ SeqKit seq arguments: Bowtie2 arguments: --bowtie2_index_options BOWTIE2_INDEX_OPTIONS bowtie2-build | More options (e.g. --arg 1 ) [Default: ''] + --one_task_per_cpu Use GNU parallel to run GNU parallel with 1 task per CPU. Useful if all samples are roughly the same size but inefficient if depth varies. --bowtie2_options BOWTIE2_OPTIONS bowtie2 | More options (e.g. --arg 1 ) [Default: ''] featureCounts arguments: --featurecounts_options FEATURECOUNTS_OPTIONS featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/ + ``` **Output:** @@ -232,16 +249,17 @@ featureCounts arguments: * reference.fasta.saf - SAF formatted file for contig-level counts with featureCounts * seqkit_stats.tsv - Assembly statistics -#### binning-prokaryotic – Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment -The prokaryotic binning module implements a novel iterative consensus binning procedure that uses CoverM (https://github.com/wwood/CoverM) for fast coverage calculations, multiple binning algorithms (MaxBin2 (marker set = 107); MaxBin2 (marker set = 40) (Wu et al., 2016); MetaBat2 (Kang et al., 2019); and CONCOCT (Alneberg et al., 2014), consensus dereplication and aggregate binning with DAS Tool (Sieber et al., 2018), the consensus domain wrapper for Tiara (Karlicki et al., 2022) for removing eukaryotes at the MAG level, and CheckM for quality assessment where poor quality MAGs are removed (e.g., completeness < 50% and/or contamination ≥ 10). The novelty of this procedure is that the unbinned contigs are stored and fed back into the input of the binning procedure using a separate random seed state allowing for an exhaustive, yet effective, approach in extracting high quality and difficult to bin genomes; number of iterations specified by --n\_iter option. Gene calls are performed using Prodigal (Hyatt et al., 2010) and the gene models (GFF3 Format) are modified to include gene and contig identifiers for use with downstream feature counting software. Although CheckM can handle CPR it cannot do so with the typical, and recommended, lineage_wf directly in the current version but instead with a separate workflow. The prokaryotic binning module allows for basal bacteria to filter through intermediate genome quality checks, runs GTDB-Tk (Chaumeil et al., 2020) for genome classification, reruns CheckM CPR workflow for said genomes, and then updates the genome set with adjusted completeness and contamination scores. The input alignment file is utilized using featureCounts to produce counts tables for the gene models and MAGs. Lastly, genome statistics such as N50, number of scaffolds, and genome size are calculated using seqkit. Utility scripts, installed with VEBA, are run in the backend to modify prodigal gene models, consensus domain classification of MAGs using Tiara contig predictions, along with several fasta and pre/post-processing scripts. The input to this module is a fasta file (typically the scaffolds.fasta from metaSPAdes) and sorted BAM while the output includes the prokaryotic MAGs via Prodigal, gene models, identifier mappings, counts tables, CheckM output, GTDB-Tk output, and unbinned fasta. MAG naming scheme for prokaryotes follows [SAMPLE]\_\_\[ALGORITHM\]\_\_P.\[ITERATION\]\_\_\[NAME] (e.g., SRR17458623\_\_METABAT2\_\_P.1\_\_bin.1) +

^__^

-**⚠️Notes:** +#### *binning-prokaryotic.py* +**Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment** -1) If you have a lot of samples and a lot of contigs then use the `--skip_maxbin2` flag because it takes MUCH longer to run. For the *Plastisphere* it was going to take 40 hours per `MaxBin2` run (there are 2 `MaxBin2` runs) per iteration. `Metabat2` and `CONCOCT` can do the heavy lifting much faster and often with better results so it's recommended to skip `MaxBin2` for larger datasets. +The prokaryotic binning module implements a novel iterative consensus binning procedure that uses `CoverM` for fast coverage calculations, multiple binning algorithms (MaxBin2 (marker set = 107); `MaxBin2` (marker set = 40); `MetaBat2`; and `CONCOCT`, consensus dereplication and aggregate binning with `DAS Tool`, the consensus domain wrapper for `Tiara` for removing eukaryotes at the MAG level, and `CheckM2` for quality assessment where poor quality MAGs are removed (e.g., completeness < 50% and/or contamination ≥ 10) which has direct support for candidate phyla radiation (CPR). The novelty of this procedure is that the unbinned contigs are stored and fed back into the input of the binning procedure using a separate random seed state allowing for an exhaustive, yet effective, approach in extracting high quality and difficult to bin genomes; number of iterations specified by `--n_iter` option. Gene calls are performed using `Pyrodigal` and the gene models (GFF3 Format) are modified to include gene and contig identifiers for use with downstream feature counting software. `BARRNAP` and `tRNAscan-SE` are used for rRNA and tRNA detetion, respectively. Lastly, genome assembly a gene statistics such as GC, N50, number of scaffolds, and genome size are calculated using `seqkit`. MAG naming scheme for prokaryotes follows `[SAMPLE]__[ALGORITHM]__P.[ITERATION]__[NAME]` (e.g., `SRR17458623__METABAT2__P.1__bin.1`) -2) The penultimate step `step-number]__cpr_adjustment]` is the most memory intensive stage as this uses `GTDB-Tk` to identify CPR bacteria. Approaches to properly allocate resources are explained in FAQ. +**⚠️Notes:** + +* If you have a lot of samples and a lot of contigs then use the `--skip_maxbin2` flag because it takes MUCH longer to run. For the *Plastisphere* it was going to take 40 hours per `MaxBin2` run (there are 2 `MaxBin2` runs) per iteration. `Metabat2` and `CONCOCT` can do the heavy lifting much faster and often with better results so it's recommended to skip `MaxBin2` for larger datasets. -Please refer to [FAQ](https://github.com/jolespin/veba/blob/main/FAQ.md) for more details. In particular, refer to items: 15, 18, 20, and 21. **Conda Environment**: `conda activate VEBA-binning-prokaryotic_env` @@ -249,7 +267,7 @@ Please refer to [FAQ](https://github.com/jolespin/veba/blob/main/FAQ.md) for mor ``` usage: binning-prokaryotic.py -f -b -n -o - Running: binning-prokaryotic.py v2022.7.8 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: binning-prokaryotic.py v2023.7.7 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit @@ -291,6 +309,7 @@ Binning arguments: --concoct_overlap_length CONCOCT_OVERLAP_LENGTH CONCOCT | Fragment overlap length [Default: 0] --skip_maxbin2 MaxBin2 | Skip MaxBin2. Useful for large datasets + --skip_concoct CONCOCT | Skip CONCOCT. Useful when there's a lot of samples --maxbin2_options MAXBIN2_OPTIONS MaxBin2 | More options (e.g. --arg 1 ) [Default: ''] | https://sourceforge.net/projects/maxbin/ --metabat2_options METABAT2_OPTIONS @@ -299,63 +318,83 @@ Binning arguments: CONCOCT | More options (e.g. --arg 1 ) [Default: ''] Gene model arguments: - --prodigal_genetic_code PRODIGAL_GENETIC_CODE - Prodigal -g translation table [Default: 11] + --pyrodigal_minimum_gene_length PYRODIGAL_MINIMUM_GENE_LENGTH + Pyrodigal | Minimum gene length [Default: 90] + --pyrodigal_minimum_edge_gene_length PYRODIGAL_MINIMUM_EDGE_GENE_LENGTH + Pyrodigal | Minimum edge gene length [Default: 60] + --pyrodigal_maximum_gene_overlap_length PYRODIGAL_MAXIMUM_GENE_OVERLAP_LENGTH + Pyrodigal | Maximum gene overlap length [Default: 60] + --pyrodigal_genetic_code PYRODIGAL_GENETIC_CODE + Pyrodigal -g translation table [Default: 11] Evaluation arguments: --dastool_searchengine DASTOOL_SEARCHENGINE DAS_Tool searchengine. [Default: diamond] | https://github.com/cmks/DAS_Tool + --dastool_minimum_score DASTOOL_MINIMUM_SCORE + DAS_Tool score_threshold. Score threshold until selection algorithm will keep selecting bins. This is set to a relaxed setting because CheckM2 is run post hoc. [Default: 0.1] | https://github.com/cmks/DAS_Tool --dastool_options DASTOOL_OPTIONS DAS_Tool | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/cmks/DAS_Tool - --pplacer_threads PPLACER_THREADS - Number of threads used for pplacer. Multithreaded uses a lot memory so don't use unless you have the resources [Default: 1] - --checkm_tree CHECKM_TREE - CheckM tree type either 'reduced' or 'full' [Default: reduced] - --checkm_completeness CHECKM_COMPLETENESS - CheckM completeness threshold [Default: 50] - --checkm_contamination CHECKM_CONTAMINATION - CheckM contamination threshold [Default: 10] - --checkm_strain_heterogeneity CHECKM_STRAIN_HETEROGENEITY - CheckM strain hetereogeneity threshold - --checkm_options CHECKM_OPTIONS + --checkm2_completeness CHECKM2_COMPLETENESS + CheckM2 completeness threshold [Default: 50.0] + --checkm2_contamination CHECKM2_CONTAMINATION + CheckM2 contamination threshold [Default: 10.0] + --checkm2_options CHECKM2_OPTIONS CheckM lineage_wf | More options (e.g. --arg 1 ) [Default: ''] - --gtdbtk_options GTDBTK_OPTIONS - GTDB-Tk | classify_wf options (e.g. --arg 1 ) [Default: ''] + +barrnap arguments: + --barrnap_length_cutoff BARRNAP_LENGTH_CUTOFF + barrnap | Proportional length threshold to label as partial [Default: 0.8] + --barrnap_reject BARRNAP_REJECT + barrnap | Proportional length threshold to reject prediction [Default: 0.25] + --barrnap_evalue BARRNAP_EVALUE + barrnap | Similarity e-value cut-off [Default: 1e-6] + +tRNAscan-SE arguments: + --trnascan_options TRNASCAN_OPTIONS + tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE featureCounts arguments: + --long_reads featureCounts | Use this if long reads are being used --featurecounts_options FEATURECOUNTS_OPTIONS featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/ Domain classification arguments: --logit_transform LOGIT_TRANSFORM - Transformation for consensus_domain_classification: {softmax, tss} [Default: softmax + Transformation for consensus_domain_classification: {softmax, tss} [Default: softmax] --tiara_minimum_length TIARA_MINIMUM_LENGTH Tiara | Minimum contig length. Anything lower than 3000 is not recommended. [Default: 3000] --tiara_options TIARA_OPTIONS Tiara | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/ibe-uw/tiara - + ``` **Output:** * binned.list - List of binned contigs * bins.list - List of MAG identifiers -* checkm_output.filtered.tsv - Filtered CheckM output +* checkm2_results.filtered.tsv - Filtered CheckM2 output * featurecounts.orfs.tsv.gz - ORF-level counts table * genome_statistics.tsv - Genome assembly statistics +* gene_statistics.cds.tsv - Gene sequence statistics (CDS) +* gene_statistics.rRNA.tsv - Gene sequence statistics (rRNA) +* gene_statistics.tRNA.tsv - Gene sequence statistics (tRNA) * genomes/ - MAG subdirectory -* genomes/\*.fa - MAG assembly fasta -* genomes/\*.faa - MAG protein fasta -* genomes/\*.ffn - MAG CDS fasta -* genomes/\*.gff - MAG gene models -* genomes/identifier_mapping.tsv - Identifier mapping between [id_orf, id_contig, id_mag] -* gtdbtk_output.filtered.tsv - Filtered GTDBTk output -* scaffolds_to_bins.tsv - Identifier mapping between [id_contig, id_mag] +* genomes/[id\_genome].fa - MAG assembly fasta +* genomes/[id\_genome].faa - MAG protein fasta +* genomes/[id\_genome].ffn - MAG CDS fasta +* genomes/[id\_genome].gff - MAG gene models for assembly, CDS, rRNA, and tRNA +* genomes/[id\_genome].rRNA - MAG rRNA fasta +* genomes/[id\_genome].tRNA - MAG tRNA fasta +* genomes/identifier\_mapping.tsv - Identifier mapping between [id\_orf, id\_contig, id_mag] +* scaffolds\_to\_bins.tsv - Identifier mapping between [id\_contig, id_mag] * unbinned.fasta - Fasta of unbinned contigs that have passed length thresholding * unbinned.list - List of unbinned contigs -#### binning-eukaryotic – Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment -The eukaryotic binning module uses several checks and state-of-the-art software to ensure high quality genomes. In particular, non-prokaryotic-biased binning algorithms MetaBat2 [default] (coverage calculated with CoverM) or CONCOCT (coverage calculated using CONCOCT scripts) is used for binning out genomes followed by a genome size filter (2,000,000 bp is the default). The preliminary bins are run through the consensus domain wrapper for Tiara to predict eukaryotic MAGs. We found Tiara to be much more effective than EukRep (even when using an exhaustive parameter grid for EukRep) that we tested on known microeukaryotic contigs/transcripts from published assemblies and transcriptomes (Karlicki et al., 2022; West, Probst, Grigoriev, Thomas, & Banfield, 2018). Contigs from the eukaryotic MAGs are input into MetaEuk easy-predict workflow (Levy Karin et al., 2020) using our custom consensus eukaryotic database (see Database section in Methods). Although MetaEuk is a high-quality software suite, the identifiers from MetaEuk are very complex, long, and contain characters that are often problematic for downstream applications including parsing, file naming systems, and certain programs with simplified identifier requirements such as Anvi’o (Eren et al., 2015). In addition, the gene model GFF files are not intuitive, compatible with Prodigal GFF files or featureCounts without major modification. Therefore, we developed an essential wrapper for MetaEuk that simplifies identifiers (i.e., [ContigID]\_[GeneStart]:\[GeneEnd]([strand])), ensuring no duplicates are produced, creates a GFF file that can be concatenated with the Prodigal GFF file for use with featureCounts, and several identifier mapping tables to seamless convert between original and modified identifiers. Lineage-specific genome quality estimation is performed using BUSCO (Manni et al., 2021) where poor quality MAGs are removed (e.g., completeness < 50%). Gene counts are computed using featureCounts at the gene level. Lastly, genome statistics such as N50, number of scaffolds, and genome size are calculated using seqkit. The input to this module is a fasta file (typically the unbinned.fasta from the prokaryotic binning module) and sorted BAM while the output includes the eukaryotic MAGs, gene models via MetaEuk, identifier mappings, BUSCO output, counts tables, and unbinned fasta. Iterative binning is not currently available as no consensus binning tool is available therefore iterative binning would result in diminishing returns. MAG naming scheme for eukaryotes follows \[SAMPLE]\_\_\[ALGORITHM]\_\_E.[ITERATION]\_\_\[NAME] (e.g., ERR2002407\_\_METABAT2\_\_E.1\_\_bin.2). +

^__^

+ +#### *binning-eukaryotic.py* +**Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment** +The eukaryotic binning module uses several checks and state-of-the-art software to ensure high quality genomes. In particular, non-prokaryotic-biased binning algorithms `MetaBat2` [default] (coverage calculated with `CoverM`) or `CONCOCT` (coverage calculated using `CONCOCT` scripts) is used for binning out genomes followed by a genome size filter (2,000,000 bp is the default). The preliminary bins are run through the consensus domain wrapper for `Tiara` to predict eukaryotic MAGs. Contigs from the eukaryotic MAGs are input into `MetaEuk` easy-predict workflow using our custom consensus eukaryotic database. Although `MetaEuk` is a high-quality software suite, the identifiers from `MetaEuk` are very complex, long, and contain characters that are often problematic for downstream applications including parsing, file naming systems, and certain programs with simplified identifier requirements such as `Anvi’o`. In addition, the gene model GFF files are not intuitive, compatible with `P(y)rodigal(-gv)` GFF files or `featureCounts` without major modification. Therefore, we developed an essential wrapper for MetaEuk that simplifies identifiers (i.e., `[ContigID]_[GeneStart]:[GeneEnd]([strand])`), ensuring no duplicates are produced, creates a GFF file that can be concatenated with the `Pyrodigal` GFF file for use with `featureCounts`, and several identifier mapping tables to seamless convert between original and modified identifiers. `BARRNAP` and `tRNAscan-SE` are used for rRNA and tRNA detetion, respectively. `Tiara` is used to identfy mitochondrion and plastid contigs to run separate workflows for modeling genes (CDS, rRNA, and tRNA). Lineage-specific genome quality estimation is performed using `BUSCO` where poor quality MAGs are removed (e.g., completeness < 50%, contamination > 10). Gene counts are computed using `featureCounts` at the gene level. Lastly, genome and gene statistics such as N50, number of scaffolds, and genome size are calculated using `seqkit`. Iterative binning is not currently available as no consensus binning tool is available therefore iterative binning would result in diminishing returns. MAG naming scheme for eukaryotes follows `[SAMPLE]__[ALGORITHM]__E.[ITERATION]__[NAME]` (e.g., `ERR2002407__METABAT2__E.1__bin.2`). **Conda Environment**: `conda activate VEBA-binning-eukaryotic_env` @@ -363,7 +402,7 @@ The eukaryotic binning module uses several checks and state-of-the-art software ``` usage: binning-eukaryotic.py -f -b -n -o - Running: binning-eukaryotic.py v2022.7.8 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: binning-eukaryotic.py v2023.7.6 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit @@ -426,6 +465,18 @@ MetaEuk arguments: --metaeuk_options METAEUK_OPTIONS MetaEuk | More options (e.g. --arg 1 ) [Default: ''] https://github.com/soedinglab/metaeuk +Pyrodigal arguments (Mitochondria): + --pyrodigal_minimum_gene_length PYRODIGAL_MINIMUM_GENE_LENGTH + Pyrodigal | Minimum gene length [Default: 90] + --pyrodigal_minimum_edge_gene_length PYRODIGAL_MINIMUM_EDGE_GENE_LENGTH + Pyrodigal | Minimum edge gene length [Default: 60] + --pyrodigal_maximum_gene_overlap_length PYRODIGAL_MAXIMUM_GENE_OVERLAP_LENGTH + Pyrodigal | Maximum gene overlap length [Default: 60] + --pyrodigal_mitochondrial_genetic_code PYRODIGAL_MITOCHONDRIAL_GENETIC_CODE + Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 4] (The Mold, Protozoan, and Coelenterate Mitochondrial Code and the Mycoplasma/Spiroplasma Code)) + --pyrodigal_plastid_genetic_code PYRODIGAL_PLASTID_GENETIC_CODE + Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 11] (The Bacterial, Archaeal and Plant Plastid Code)) + BUSCO arguments: --busco_completeness BUSCO_COMPLETENESS BUSCO completeness [Default: 50.0] @@ -434,7 +485,28 @@ BUSCO arguments: --busco_evalue BUSCO_EVALUE BUSCO | E-value cutoff for BLAST searches. Allowed formats, 0.001 or 1e-03 [Default: 1e-03] +barrnap arguments: + --barrnap_length_cutoff BARRNAP_LENGTH_CUTOFF + barrnap | Proportional length threshold to label as partial [Default: 0.8] + --barrnap_reject BARRNAP_REJECT + barrnap | Proportional length threshold to reject prediction [Default: 0.25] + --barrnap_evalue BARRNAP_EVALUE + barrnap | Similarity e-value cut-off [Default: 1e-6] + +tRNAscan-SE arguments: + --trnascan_nuclear_options TRNASCAN_NUCLEAR_OPTIONS + tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE + --trnascan_mitochondrial_searchmode TRNASCAN_MITOCHONDRIAL_SEARCHMODE + tRNAscan-SE | Search mode [Default: '-O'] | Current best option according to developer: https://github.com/UCSC-LoweLab/tRNAscan-SE/issues/24 + --trnascan_mitochondrial_options TRNASCAN_MITOCHONDRIAL_OPTIONS + tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE + --trnascan_plastid_searchmode TRNASCAN_PLASTID_SEARCHMODE + tRNAscan-SE | Search mode [Default: '-O'] | https://github.com/UCSC-LoweLab/tRNAscan-SE + --trnascan_plastid_options TRNASCAN_PLASTID_OPTIONS + tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE + featureCounts arguments: + --long_reads featureCounts | Use this if long reads are being used --featurecounts_options FEATURECOUNTS_OPTIONS featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/ @@ -447,26 +519,36 @@ featureCounts arguments: * busco_results.filtered.tsv - Filtered BUSCO output * featurecounts.orfs.tsv.gz - ORF-level counts table * genome_statistics.tsv - Genome assembly statistics +* gene_statistics.cds.tsv - Gene sequence statistics (CDS) +* gene_statistics.rRNA.tsv - Gene sequence statistics (rRNA) +* gene_statistics.tRNA.tsv - Gene sequence statistics (tRNA) * genomes/ - MAG subdirectory -* genomes/\*.fa - MAG assembly fasta -* genomes/\*.faa - MAG protein fasta -* genomes/\*.ffn - MAG CDS fasta -* genomes/\*.gff - MAG gene models -* genomes/identifier_mapping.tsv - Identifier mapping between [id_orf, id_contig, id_mag] -* identifier_mapping.metaeuk.tsv - Identifier mapping between original MetaEuk identifiers and modified identifiers. Includes fully parsed MetaEuk identifiers. -* scaffolds_to_bins.tsv - Identifier mapping between [id_contig, id_mag] +* genomes/[id\_genome].fa - MAG assembly fasta +* genomes/[id\_genome].faa - MAG protein fasta +* genomes/[id\_genome].ffn - MAG CDS fasta +* genomes/[id\_genome].gff - MAG gene models for assembly, CDS, rRNA, and tRNA +* genomes/[id\_genome].rRNA - MAG rRNA fasta +* genomes/[id\_genome].tRNA - MAG tRNA fasta +* genomes/[id\_genome].seqtype.tsv - Identifier mapping between [id\_contig, sequence_type] {nuclear, mitochondrion, plastid} +* genomes/identifier\_mapping.tsv - Identifier mapping between [id\_orf, id\_contig, id_mag] +* identifier\_mapping.metaeuk.tsv - Identifier mapping between original MetaEuk identifiers and modified identifiers. Includes fully parsed MetaEuk identifiers. +* scaffolds\_to\_bins.tsv - Identifier mapping between [id\_contig, id_mag] * unbinned.fasta - Fasta of unbinned contigs that have passed length thresholding * unbinned.list - List of unbinned contigs -#### binning-viral – Detection of viral genomes and quality assessment -Viral binning is performed using VirFinder (Ren et al., 2017) to extract potential viral contigs (e.g., P < 0.05). The potential viral contigs are then input into CheckV (Nayfach et al., 2020) where quality assessment removes poor quality or low confidence viral predictions. The filtering scheme is based on author recommendations (Nayfach, 2021) in which a viral contig is considered if it meets the following criteria: 1) number of viral genes ≥ 5 x number of host genes; 2) completeness ≥ 50%; 3) CheckV quality is either medium-quality, high-quality, or complete; and 4) MIUViG quality is either medium-quality, high-quality, or complete (Roux et al., 2018). Proviruses can be included by using the --include_proviruses flag. After poor quality viral contigs are removed, Prodigal is used for gene modeling and seqkit is used for useful genome statistics. The input to this module is a fasta file (typically the unbinned.fasta from the eukaryotic binning module) while the output includes the viral MAGs, gene models via Prodigal, identifier mappings, and CheckV output. Iterative binning is not applicable for viral detection as algorithms are executed on a per-contig basis and all viral genomes will be identified on first pass. MAG naming scheme for viruses follows [SAMPLE]\_\_\[ALGORITHM]\_\_\[NAME] (e.g., SRR9668957\_\_VIRFINDER\_\_Virus.1). +

^__^

+ +#### *binning-viral.py* +**Detection of viral genomes and quality assessment** + +Viral binning is performed using either `geNomad` [default] or `VirFinder` to extract potential viral contigs (e.g., P < 0.05). The potential viral contigs are then input into `CheckV` where quality assessment removes poor quality or low confidence viral predictions. The filtering scheme is based on author recommendations in which a viral contig is considered if it meets the following criteria: 1) number of viral genes ≥ 5 x number of host genes; 2) completeness ≥ 50%; 3) `CheckV` quality is either medium-quality, high-quality, or complete; and 4) `MIUViG` quality is either medium-quality, high-quality, or complete. Proviruses can be included by using the `--include_proviruses` flag. After poor quality viral contigs are removed, `Prodigal-GV` is used for gene modeling and `seqkit` is used for useful genome statistics. Iterative binning is not applicable for viral detection as algorithms are executed on a per-contig basis and all viral genomes will be identified on first pass. MAG naming scheme for viruses follows `[SAMPLE]__[ALGORITHM]__[NAME]` (e.g., `SRR9668957__GENOMAD__Virus.1`). **Conda Environment**: `conda activate VEBA-binning-viral_env` ``` -usage: binning-viral.py -f -l -n -o +usage: binning-viral.py -f -l -n -o [Requires at least 20GB] - Running: binning-viral.py v2022.7.8 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: binning-viral.py v2023.7.7 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit @@ -475,8 +557,6 @@ Required I/O arguments: -f FASTA, --fasta FASTA path/to/scaffolds.fasta -n NAME, --name NAME Name of sample - -l CONTIG_IDENTIFIERS, --contig_identifiers CONTIG_IDENTIFIERS - path/to/contigs.list [Optional] -o PROJECT_DIRECTORY, --project_directory PROJECT_DIRECTORY path/to/project_directory [Default: veba_output/binning/viral] -b BAM [BAM ...], --bam BAM [BAM ...] @@ -492,27 +572,59 @@ Utility arguments: --restart_from_checkpoint RESTART_FROM_CHECKPOINT Restart from a particular checkpoint [Default: None] -v, --version show program's version number and exit - --remove_temporary_fasta - If contig identifiers were provided and a fasta is generated, remove this file Database arguments: --veba_database VEBA_DATABASE VEBA database location. [Default: $VEBA_DATABASE environment variable] Binning arguments: + -a ALGORITHM, --algorithm ALGORITHM + Binning algorithm to use: {genomad, virfinder} [Default: genomad] -m MINIMUM_CONTIG_LENGTH, --minimum_contig_length MINIMUM_CONTIG_LENGTH Minimum contig length. [Default: 1500] + --include_provirus_detection + Include provirus viral detection Gene model arguments: --prodigal_genetic_code PRODIGAL_GENETIC_CODE - Prodigal -g translation table [Default: 11] - -Virus arguments: - --include_provirus Include provirus viral detection + Prodigal-GV -g translation table (https://github.com/apcamargo/prodigal-gv) [Default: 11] + +geNomad arguments +Using --relaxed mode by default. Adjust settings according to the following table: https://portal.nersc.gov/genomad/post_classification_filtering.html#default-parameters-and-presets: + --genomad_qvalue GENOMAD_QVALUE + Maximum accepted false discovery rate. [Default: 1.0; 0.0 < x ≤ 1.0] + --sensitivity SENSITIVITY + MMseqs2 marker search sensitivity. Higher values will annotate more proteins, but the search will be slower and consume more memory. [Default: 4.0; x ≥ 0.0] + --splits SPLITS Split the data for the MMseqs2 search. Higher values will reduce memory usage, but will make the search slower. If the MMseqs2 search is failing, try to increase the number of splits. Also used for VirFinder. [Default: 0; x ≥ 0] + --composition COMPOSITION + Method for estimating sample composition. (auto|metagenome|virome) [Default: auto] + --minimum_score MINIMUM_SCORE + Minimum score to flag a sequence as virus or plasmid. By default, the sequence is classified as virus/plasmid if its virus/plasmid score is higher than its chromosome score, regardless of the value. [Default: 0; 0.0 ≤ x ≤ 1.0] + --minimum_plasmid_marker_enrichment MINIMUM_PLASMID_MARKER_ENRICHMENT + Minimum allowed value for the plasmid marker enrichment score, which represents the total enrichment of plasmid markers in the sequence. Sequences with multiple plasmid markers will have higher values than the ones that encode few or no markers.[Default: -100] + --minimum_virus_marker_enrichment MINIMUM_VIRUS_MARKER_ENRICHMENT + Minimum allowed value for the virus marker enrichment score, which represents the total enrichment of plasmid markers in the sequence. Sequences with multiple plasmid markers will have higher values than the ones that encode few or no markers. [Default: -100] + --minimum_plasmid_hallmarks MINIMUM_PLASMID_HALLMARKS + Minimum number of plasmid hallmarks in the identified plasmids. [Default: 0; x ≥ 0] + --minimum_virus_hallmarks MINIMUM_VIRUS_HALLMARKS + Minimum number of virus hallmarks in the identified viruses. [Default: 0; x ≥ 0] + --maximum_universal_single_copy_genes MAXIMUM_UNIVERSAL_SINGLE_COPY_GENES + Maximum allowed number of universal single copy genes (USCGs) in a virus or a plasmid. Sequences with more than this number of USCGs will not be classified as viruses or plasmids, regardless of their score. [Default: 100] + --genomad_options GENOMAD_OPTIONS + geNomad | More options (e.g. --arg 1 ) [Default: ''] + +VirFinder arguments: --virfinder_pvalue VIRFINDER_PVALUE - VirFinder p-value threshold [Default: 0.05] + VirFinder statistical test threshold [Default: 0.05] + --mmseqs2_evalue MMSEQS2_EVALUE + Maximum accepted E-value in the MMseqs2 search. Used by genomad annotate when VirFinder is used as binning algorithm [Default: 1e-3] + --use_qvalue Use qvalue (FDR) instead of pvalue + --use_minimal_database_for_taxonomy + Use a smaller marker database to annotate proteins. This will make execution faster but sensitivity will be reduced. --virfinder_options VIRFINDER_OPTIONS VirFinder | More options (e.g. --arg 1 ) [Default: ''] + +CheckV arguments: --checkv_options CHECKV_OPTIONS CheckV | More options (e.g. --arg 1 ) [Default: ''] --multiplier_viral_to_host_genes MULTIPLIER_VIRAL_TO_HOST_GENES @@ -524,43 +636,54 @@ Virus arguments: --miuvig_quality MIUVIG_QUALITY Comma-separated string of acceptable arguments between {High-quality,Medium-quality,Complete} [Default: High-quality,Medium-quality,Complete] +featureCounts arguments: + --long_reads featureCounts | Use this if long reads are being used + --featurecounts_options FEATURECOUNTS_OPTIONS + featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/ + ``` **Output:** * binned.list - List of binned contigs * bins.list - List of MAG identifiers -* quality_summary.filtered.tsv - Filtered CheckV output +* checkv_results.filtered.tsv - Filtered CheckV output * featurecounts.orfs.tsv.gz - ORF-level counts table (If --bam file is provided) * genome_statistics.tsv - Genome assembly statistics +* gene_statistics.cds.tsv - Gene sequence statistics (CDS) * genomes/ - MAG subdirectory -* genomes/\*.fa - MAG assembly fasta -* genomes/\*.faa - MAG protein fasta -* genomes/\*.ffn - MAG CDS fasta -* genomes/\*.gff - MAG gene models -* genomes/identifier_mapping.tsv - Identifier mapping between [id_orf, id_contig, id_mag] -* scaffolds_to_bins.tsv - Identifier mapping between [id_contig, id_mag] +* genomes/[id\_genome].fa - MAG assembly fasta +* genomes/[id\_genome].faa - MAG protein fasta +* genomes/[id\_genome].ffn - MAG CDS fasta +* genomes/[id\_genome].gff - MAG gene models +* genomes/identifier\_mapping.tsv - Identifier mapping between [id\_orf, id\_contig, id_mag] +* scaffolds\_to\_bins.tsv - Identifier mapping between [id\_contig, id_mag] * unbinned.fasta - Fasta of unbinned contigs that have passed length thresholding * unbinned.list - List of unbinned contigs +

^__^

+ +#### *classify-prokaryotic.py* +**Taxonomic classification of prokaryotic genomes** -#### classify-prokaryotic – Taxonomic classification and candidate phyla radiation adjusted quality assessment of prokaryotic genomes -The prokaryotic classification module is a useful wrapper around GTDB-Tk which either combines the resulting archaea and bacteria summary tables or runs GTDB-Tk lineage_wf from the beginning. If genome clusters are provided, then it performs consensus lineage classification. +The prokaryotic classification module is a useful wrapper around `GTDB-Tk` which either combines the resulting archaea and bacteria summary tables or runs `GTDB-Tk lineage_wf` from the beginning. If genome clusters are provided, then it performs consensus lineage classification. `Krona` plots are generated showing taxonomic levels. **Conda Environment**: `conda activate VEBA-classify_env` ``` -usage: classify-prokaryotic.py -i -o +usage: classify-prokaryotic.py -i |-g -o - Running: classify-prokaryotic.py v2022.06.07 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: classify-prokaryotic.py v2023.6.16 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit Required I/O arguments: -i PROKARYOTIC_BINNING_DIRECTORY, --prokaryotic_binning_directory PROKARYOTIC_BINNING_DIRECTORY - path/to/prokaryotic_binning_directory + path/to/prokaryotic_binning_directory [Cannot be used with --genomes] + -g GENOMES, --genomes GENOMES + path/to/genomes.list [Cannot be ued with --prokaryotic_binning_directory] -c CLUSTERS, --clusters CLUSTERS path/to/clusters.tsv, Format: [id_mag][id_cluster], No header. -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY @@ -571,7 +694,7 @@ Required I/O arguments: Utility arguments: --path_config PATH_CONFIG path/to/config.tsv [Default: CONDA_PREFIX] - --tmpdir TMPDIR path/to/TMPDIR [Default: /var/folders/fl/bh6r17mn52d33ycwvk70z2bc0003c0/T/] + --tmpdir TMPDIR path/to/TMPDIR -p N_JOBS, --n_jobs N_JOBS Number of threads [Default: 1] --random_state RANDOM_STATE @@ -585,6 +708,7 @@ Database arguments: VEBA database location. [Default: $VEBA_DATABASE environment variable] GTDB-Tk arguments: + --skip_ani_screen Skip ANI screen [Default: Don't skip ANI screen] --gtdbtk_options GTDBTK_OPTIONS GTDB-Tk | classify_wf options (e.g. --arg 1 ) [Default: ''] @@ -592,35 +716,44 @@ Consensus genome classification arguments: -l LENIENCY, --leniency LENIENCY Leniency parameter. Lower value means more conservative weighting. A value of 1 indiciates no weight bias. A value greater than 1 puts higher weight on higher level taxonomic assignments. A value less than 1 puts lower weights on higher level taxonomic assignments. [Default: 1.382] - ``` **Output:** -* prokaryotic_taxonomy.tsv - Prokaryotic genome classification based on GTDBTk -* prokaryotic_taxonomy.clusters.tsv - Prokaryotic cluster classification (If --clusters are provided) - -#### classify-eukaryotic – Taxonomic classification of eukaryotic genomes -The eukaryotic classification module utilizes the target field of MetaEuk gene identifiers and the taxonomic lineage associated with each source genome. The default marker set is eukaryote_odb10 from BUSCO but custom marker sets are support along with the inclusion of all genes not just marker genes. An option to include marker-specific noise cutoff scores is also available using the --scores_cutoff parameter which is default behavior with BUSCO’s eukaryote_odb10 provided noise thresholds. For each MAG, bitscores are accumulated for each taxonomic level and taxonomy is assigned with leniency specified by the leniency parameter with high leniency resulting higher order taxonomic assignments. If genome clusters are provided, then it performs consensus lineage classification. EUKulele (Krinos, Hu, Cohen, & Alexander, 2021) was attempted for this stage but a custom database could not be created with the software, likely due to the dependency of supergroup and division fields that were missing for certain taxa. However, future implementations of VEBA may use this if such issues are resolved. +* taxonomy.tsv - Prokaryotic genome classification based on GTDBTk +* taxonomy.clusters.tsv - Prokaryotic cluster classification (If --clusters are provided) +* krona.html - Krona plot for various taxonomic levels + +

^__^

+ + +#### *classify-eukaryotic.py* +**Taxonomic classification of eukaryotic genomes** + +The eukaryotic classification module can be performed on de novo genomes or utilize the target field of `MetaEuk` gene identifiers and the taxonomic lineage associated with each source genome. The default marker set is `eukaryote_odb10` from `BUSCO` but custom marker sets are support along with the inclusion of all genes not just marker genes. An option to include marker-specific noise cutoff scores is also available using the `--scores_cutoff` parameter which is default behavior with `BUSCO’s eukaryote_odb10` provided noise thresholds. For each MAG, bitscores are accumulated for each taxonomic level and taxonomy is assigned with leniency specified by the leniency parameter with high leniency resulting higher order taxonomic assignments. If genome clusters are provided, then it performs consensus lineage classification. `Krona` plots are generated showing taxonomic levels. **Conda Environment**: `conda activate VEBA-classify_env` ``` -usage: classify-eukaryotic.py -i -o +usage: classify-eukaryotic.py -i |-g -o - Running: classify-eukaryotic.py v2022.7.8 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: classify-eukaryotic.py v2023.6.12 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit Required I/O arguments: -i EUKARYOTIC_BINNING_DIRECTORY, --eukaryotic_binning_directory EUKARYOTIC_BINNING_DIRECTORY - path/to/eukaryotic_binng_directory + path/to/eukaryotic_binning_directory [Cannot be used with --genomes] + -g GENOMES, --genomes GENOMES + path/to/genomes.list where each line is a path to a genome.fasta [Cannot be ued with --eukaryotic_binning_directory] -c CLUSTERS, --clusters CLUSTERS path/to/clusters.tsv, Format: [id_mag][id_cluster], No header. -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY path/to/output_directory [Default: veba_output/classify/eukaryotic] + -x EXTENSION, --extension EXTENSION + path/to/output_directory. Does not support gzipped. [Default: fa] Utility arguments: --path_config PATH_CONFIG @@ -644,7 +777,6 @@ HMMSearch arguments: Database arguments: --veba_database VEBA_DATABASE VEBA database location. [Default: $VEBA_DATABASE environment variable] - --include_all_genes Use if you want to include all genes for taxonomy classification instead of only core markers from BUSCO's eukaryota_odb10 Consensus genome arguments: -l LENIENCY, --leniency LENIENCY @@ -662,20 +794,24 @@ Consensus genome arguments: **Output:** -* eukaryotic_taxonomy.tsv - Eukaryotic genome classification based on microeukaryotic protein database and BUSCO's eukaryota_odb10 marker set -* eukaryotic_taxonomy.clusters.tsv - Eukaryotic cluster classification (If --clusters are provided) -* gene-source_lineage.tsv - Gene source lineage and scores for classifying MAGs [id_gene, id_scaffold, id_mag, id_target, id_source, lineage, bitscore] +* taxonomy.tsv - Eukaryotic genome classification based on microeukaryotic protein database and BUSCO's eukaryota_odb10 marker set +* taxonomy.clusters.tsv - Eukaryotic cluster classification (If --clusters are provided) +* gene-source\_lineage.tsv - Gene source lineage and scores for classifying MAGs [id_gene, id_scaffold, id_mag, id_target, id_source, lineage, bitscore] +* krona.html - Krona plot for various taxonomic levels + +

^__^

+#### *classify-viral.py* +**Taxonomic classification for viral genomes** -#### classify-viral – Taxonomic classification and isolation source of viral genomes -The viral classification module utilizes the CheckV database along with the best hit lineage and source habitat information from the CheckV output. This includes a look up of CheckV identifiers based on direct terminal repeats and GenBank identifiers when applicable. If genome clusters are provided, then it performs consensus lineage classification and consensus habitat annotation. +Viral classification uses `geNomad's taxonomy` module. **Conda Environment**: `conda activate VEBA-classify_env` ``` -usage: classify-viral.py -i -o +usage: classify-viral.py -i |-g -o - Running: classify-viral.py v2022.7.8 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: classify-viral.py v2023.5.8 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit @@ -683,14 +819,20 @@ optional arguments: Required I/O arguments: -i VIRAL_BINNING_DIRECTORY, --viral_binning_directory VIRAL_BINNING_DIRECTORY Either: path/to/checkv/quality_summary.tsv or directory of veba_output/binning/viral + -g GENOMES, --genomes GENOMES + path/to/genomes.list where each line is a path to a genome.fasta [Cannot be ued with --viral_binning_directory] -c CLUSTERS, --clusters CLUSTERS path/to/clusters.tsv, Format: [id_mag][id_cluster], No header. -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY path/to/output_directory [Default: veba_output/classify/viral] + -x EXTENSION, --extension EXTENSION + path/to/output_directory. Does not support gzipped. [Default: fa] Utility arguments: --path_config PATH_CONFIG path/to/config.tsv [Default: CONDA_PREFIX] + -p N_JOBS, --n_jobs N_JOBS + Number of threads [Default: 1] --restart_from_checkpoint RESTART_FROM_CHECKPOINT Restart from a particular checkpoint [Default: None] -v, --version show program's version number and exit @@ -699,103 +841,115 @@ Database arguments: --veba_database VEBA_DATABASE VEBA database location. [Default: $VEBA_DATABASE environment variable] -Consensus habitat/isolation source arguments: - --similarity_threshold SIMILARITY_THRESHOLD - Threshold for similarity analysis [Default: 0.8] - --retain_unannotated RETAIN_UNANNOTATED - Consider unannotations (i.e., blank functions) in the scording system [Default: 1] - --unannotated_weight UNANNOTATED_WEIGHT - Weight for unannotations (i.e., blank functions) in the scording system? [Default: 0.382] - --representative_threshold REPRESENTATIVE_THRESHOLD - Score to consider as representative [Default: 0.618] +Consensus genome classification arguments: + -t THRESHOLD, --threshold THRESHOLD + Fraction of classifications for consensus [Default: 0.5] ``` **Output:** -* viral\_taxonomy.tsv - Viral genome classification based on CheckV classifications -* viral\_taxonomy.clusters.tsv - Viral cluster classification (If --clusters are provided) -* viral\_isolation\_source.clusters.tsv - Viral isolation source consensus (If --clusters are provided) +* taxonomy.tsv - Viral genome classification based on geNomad classifications +* taxonomy.clusters.tsv - Viral cluster classification (If --clusters are provided) -#### cluster – Species-level clustering of genomes and lineage-specific orthogroup detection -To leverage intra-sample genome analysis in an inter-sample analytical paradigm, genome clustering and lineage-specific orthogroup detection is necessary. The cluster module first uses FastANI (Jain, Rodriguez-R, Phillippy, Konstantinidis, & Aluru, 2018) to compute pairwise ANI and these are used to construct a NetworkX graph object where nodes are genomes and edges are ANI values (Hagberg, Schult, & Swart, 2008). This graph is converted into subgraphs of connected components whose edges are connected by a particular threshold such as 95% ANI [default] as recommended by the authors for species-level clustering. These species-level clusters (SLC) are then partitioned and OrthoFinder (Emms & Kelly, 2019) is then run on each SLC panproteome. The input is a list of genome paths and list of protein fasta paths while the output includes identifier mappings between genomes, SLCs, scaffolds, proteins, and orthogroups. +

^__^

+ +#### *cluster.py* +**Species-level clustering of genomes and lineage-specific orthogroup detection** + +To leverage intra-sample genome analysis in an inter-sample analytical paradigm, genome clustering and lineage-specific orthogroup detection is necessary. The cluster module utilizes 2 separate wrappers for global (inter-sample) and local (intra-sample) clustering. `FastANI` is used to compute pairwise ANI and these are used to construct a `NetworkX graph` object where nodes are genomes and edges are ANI values. This graph is converted into subgraphs of connected components whose edges are connected by a particular threshold such as 95% ANI [default] as recommended by the authors for species-level clustering. These species-level clusters (SLC) are then partitioned and `MMSEQS2` is then run on each SLC panproteome to SLC-specific Protein Clusters (SSPC) previously referred to as Sample-specific Orthogroups (SSO). Pangenome tables are created for each SLC which includes the number of proteins for a protein-cluster are detected in each genome. Each domain (i.e., prokaryotic, eukaryotic, viral) are clustered separately. **Conda Environment**: `conda activate VEBA-cluster_env` ``` usage: cluster.py -m -a -o -t 95 - Running: cluster.py v2022.02.24 via Python v3.9.10 | /usr/local/devel/ANNOTATION/jespinoz/anaconda3/envs/VEBA-mapping_env/bin/python + Running: cluster.py v2023.6.14 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit Required I/O arguments: - -i SCAFFOLDS_TO_BINS, --scaffolds_to_bins SCAFFOLDS_TO_BINS - path/to/scaffolds_to_bins.tsv, Format: [id_scaffold][id_bin], No header - -m MAGS, --mags MAGS Tab-seperated value table of [id_mag][path/to/genome.fasta] - -a PROTEINS, --proteins PROTEINS - Tab-seperated value table of [id_mag][path/to/protein.fasta] + -i INPUT, --input INPUT + path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id\_sample][id_mag][genome][proteins] but can include additional columns to the right (e.g., [cds][gene_models]). Suggested input is from `compile_genomes_table.py` script. -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY path/to/project_directory [Default: veba_output/cluster] - --mags_extension MAGS_EXTENSION - Fasta file extension for --mags if a list is provided [Default: fa] - --proteins_extension PROTEINS_EXTENSION - Fasta file extension for proteins if a list is provided [Default: faa] + -e, --no_singletons Exclude singletons + --no_local_clustering + Only do global clustering Utility arguments: --path_config PATH_CONFIG path/to/config.tsv [Default: CONDA_PREFIX] -p N_JOBS, --n_jobs N_JOBS Number of threads [Default: 1] - --cluster_only Only run FastANI - --random_state RANDOM_STATE - Random state [Default: 0] --restart_from_checkpoint RESTART_FROM_CHECKPOINT Restart from a particular checkpoint [Default: None] -v, --version show program's version number and exit FastANI arguments: - -t ANI_THRESHOLD, --ani_threshold ANI_THRESHOLD - FastANI | Species-level clustering threshold [Default: 95.0] + -A ANI_THRESHOLD, --ani_threshold ANI_THRESHOLD + FastANI | Species-level cluster (SLC) ANI threshold (Range (0.0, 100.0]) [Default: 95.0] + --genome_cluster_prefix GENOME_CLUSTER_PREFIX + Cluster prefix [Default: 'SLC- + --genome_cluster_suffix GENOME_CLUSTER_SUFFIX + Cluster suffix [Default: ' + --genome_cluster_prefix_zfill GENOME_CLUSTER_PREFIX_ZFILL + Cluster prefix zfill. Use 7 to match identifiers from OrthoFinder. Use 0 to add no zfill. [Default: 0] --fastani_options FASTANI_OPTIONS FastANI | More options (e.g. --arg 1 ) [Default: ''] - --cluster_prefix CLUSTER_PREFIX - Cluster prefix [Default: 'SLC - --cluster_suffix CLUSTER_SUFFIX - Cluster suffix [Default: ' - --copy_proteins Copy instead of symlink - --clone_label CLONE_LABEL - Singleton clone label [Default: '___clone -OrthoFinder arguments: - --orthofinder_options ORTHOFINDER_OPTIONS - OrthoFinder | More options (e.g. --arg 1 ) [Default: ''] +MMSEQS2 arguments: + -a ALGORITHM, --algorithm ALGORITHM + MMSEQS2 | {easy-cluster, easy-linclust} [Default: easy-cluster] + -t MINIMUM_IDENTITY_THRESHOLD, --minimum_identity_threshold MINIMUM_IDENTITY_THRESHOLD + MMSEQS2 | SLC-Specific Protein Cluster (SSPC, previously referred to as SSO) percent identity threshold (Range (0.0, 100.0]) [Default: 50.0] + -c MINIMUM_COVERAGE_THRESHOLD, --minimum_coverage_threshold MINIMUM_COVERAGE_THRESHOLD + MMSEQS2 | SSPC coverage threshold (Range (0.0, 1.0]) [Default: 0.8] + --protein_cluster_prefix PROTEIN_CLUSTER_PREFIX + Cluster prefix [Default: 'SSPC- + --protein_cluster_suffix PROTEIN_CLUSTER_SUFFIX + Cluster suffix [Default: ' + --protein_cluster_prefix_zfill PROTEIN_CLUSTER_PREFIX_ZFILL + Cluster prefix zfill. Use 7 to match identifiers from OrthoFinder. Use 0 to add no zfill. [Default: 0] + --mmseqs2_options MMSEQS2_OPTIONS + MMSEQS2 | More options (e.g. --arg 1 ) [Default: ''] ``` **Output:** -* clusters.tsv - Identifier mapping between MAGs and clusters [id_mag, id_cluster] -* identifier_mapping.orthogroups.tsv - Identifier mapping between ORFs, contigs, orthogroups, and clusters -* proteins_to_orthogroups.tsv - Identifier mapping between ORFs and orthogroups [id_orf, id_orthogroup] -* scaffolds_to_clusters.tsv - Identifier mapping between contigs and clusters [id_contig, id_cluster] +* global/feature\_compression\_ratios.tsv - Feature compression ratios for each domain +* global/genome\_clusters.tsv - Machine-readable table for genome clusters `[id_genome_cluster, number_of_components, number_of_samples_of_origin, components, samples_of_origin]` +* global/identifier\_mapping.genomes.tsv - Identifier mapping for genomes `[id_genome, organism_type, sample_of_origin, id_genome_cluster, number_of_proteins, number_of_singleton_protein_clusters, ratio_of_protein_cluster_are_singletons]` +* global/identifier\_mapping.proteins.tsv - Identifier mapping for proteins `[id_protein, organism_type, id_genome, sample_of_origin, id_genome_cluster, id_protein_cluster]` +* global/identifier\_mapping.scaffolds.tsv - Identifier mapping for contigs `[id_scaffold, organism_type, id_genome, sample_of_origin, id_genome_cluster]` +* global/mags\_to\_slcs.tsv +* global/protein\_clusters.tsv - Machine-readable table for protein clusters `[id_protein_cluster, number_of_components, number_of_samples_of_origin, components, samples_of_origin]` +* global/proteins\_to\_orthogroups.tsv - Identifier mapping between proteins and protein clusters `[id_protein, id_protein-cluster]` +* global/representative\_sequences.faa - Protein sequences for cluster representatives. Header follows the following format: `id_protein-cluster id_original_protein` +* global/scaffolds\_to\_mags.tsv - Identifier mapping between contigs and genomes `[id\_contig, id_genome]` +* global/scaffolds\_to\_slcs.tsv - Identifier mapping between contigs and genome clusters `[id\_contig, id_genome-cluster]` +* global/pangenome_tables/*.tsv.gz - Pangenome tables for each SLC with prevalence values +* global/serialization/*.dict.pkl - Python dictionaries for clusters +* global/serialization/*.networkx_graph.pkl - NetworkX graphs for clusters +* local/* - If `--no_local_clustering` is not selected then all of the files are generated for local clustering + +

^__^

-*Note: This should be run separately for prokaryotes, eukaryotes, and viruses. -Here is the suggested directory structure: -* veba_output/cluster/prokaryotic -* veba_output/cluster/eukaryotic -* veba_output/cluster/viral +#### *annotate.py* +**Annotates translated gene calls against UniRef, Pfam, KOFAM, VFDB, MiBIG, AMRFinder, and AntiFam** + +Annotation is performed using best hit annotations and profile HMMs. Proteins are aligned against `UniRef50/90`, `MiBIG`, and `VFDB` using `Diamond`. Protein domains are identified for `Pfam`, `NCBIfam-AMRFinder`, and `AntiFam` using `HMMER3` and `KEGG` orthology using `KOFAMSCAN`. Functionality for annotating all proteins or only protein cluster representatives and propogating annotations across cluster membership. -#### annotate – Annotates translated gene calls against NR, Pfam, and KOFAM -Annotation is performed using best hit annotations and profile HMMs. First proteins are aligned against NCBI non-redundant protein database (other databases are supported) using Diamond (Buchfink, Reuter, & Drost, 2021; Buchfink, Xie, & Huson, 2014). After annotation, protein domains are identified using the Pfam database (Mistry et al., 2021) via HMMER (Mistry, Finn, Eddy, Bateman, & Punta, 2013) and KEGG orthology is characterized via KOFAMSCAN (Aramaki et al., 2020). Note, the `lineage_predictions.*.tsv` files generated here are experimental. **Conda Environment**: `conda activate VEBA-annotate_env` ``` -usage: annotate.py -i -a -o +usage: annotate.py -a -o |Optional: -i ] +Warnings:Proteins >100k will cause HMMSearch to crash. Please prefilter using `seqkit seq -M 100000` - Running: annotate.py v2021.7.8 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: annotate.py v2023.6.20 via Python v3.9.15 | /expanse/projects/jcl110/anaconda3/envs/VEBA-preprocess_env/bin/python optional arguments: -h, --help show this help message and exit @@ -803,10 +957,12 @@ optional arguments: Required I/O arguments: -a PROTEINS, --proteins PROTEINS Either path/to/proteins.faa or a directory of fasta files using [-x] - -i IDENTIFIER_MAPPING, --identifier_mapping IDENTIFIER_MAPPING - Tab-seperated value table of [id_orf][id_contig][id_mag] -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY path/to/project_directory [Default: veba_output/annotation] + -i IDENTIFIER_MAPPING, --identifier_mapping IDENTIFIER_MAPPING + Tab-seperated value table of [id_protein][id\_contig][id\_genome], No header. Cannot be used with --protein_clusters + -c PROTEIN_CLUSTERS, --protein_clusters PROTEIN_CLUSTERS + Tab-seperated value table of [id_protein][id_protein_cluster]. Use this if the --proteins are representative sequences. Cannot be used with --identifier_mapping -x EXTENSION, --extension EXTENSION Fasta file extension for proteins if a directory is provided for --proteins [Default: faa] @@ -824,6 +980,8 @@ Utility arguments: -v, --version show program's version number and exit Database arguments: + -u UNIREF, --uniref UNIREF + UniRef database to use {uniref90, uniref50}. uniref90 receommended for well-characterized systems and uniref50 for less characterized systems [Default: uniref90] --veba_database VEBA_DATABASE VEBA database location. [Default: $VEBA_DATABASE environment variable] @@ -836,8 +994,6 @@ Diamond arguments: Diamond | More options (e.g. --arg 1 ) [Default: ''] HMMSearch arguments: - --hmmsearch_threshold HMMSEARCH_THRESHOLD - HMMSearch | Threshold {cut_ga, cut_nc, gut_tc} [Default: cut_ga] --hmmsearch_options HMMSEARCH_OPTIONS Diamond | More options (e.g. --arg 1 ) [Default: ''] @@ -849,14 +1005,15 @@ KOFAMSCAN arguments: **Output:** -* annotations.orfs.tsv.gz - Concatenated annotations from Diamond (NR), HMMSearch (Pfam), and KOFAMSCAN (KOFAM) -* lineage_predictions.contigs.tsv.gz - [Experimental] Lineage predictions for contigs based on NR annotations. This should be used only for experimentation as many lineages aren't defined properly. Contig-level classifications. -* lineage_predictions.mags.tsv.gz - [Experimental] Lineage predictions for contigs based on NR annotations. This should be used only for experimentation as many lineages aren't defined properly. MAG-level classifications. +* annotations.tsv.gz - Concatenated annotations from Diamond (UniRef, MiBIG, VFDB), HMMSearch (Pfam, AntiFam), and KOFAMSCAN (KEGG) +* annotations.proteins.tsv.gz - Propogated annotations if clusters are provided +

^__^

+#### *phylogeny.py* +**Constructs phylogenetic trees given a marker set** -#### phylogeny – Constructs phylogenetic trees given a marker set -The phylogeny module is a tool used for phylogenetic inference and constructing phylogenetic trees for genomes given a reference marker set (see Databases section of Methods). This is performed by the following method: 1) identifying marker proteins using HMMSearch from the HMMER3 suite; 2) creating protein alignments for each marker identified MUSCLE (Edgar, 2004); 3) trimming the alignments using ClipKIT (Steenwyk, Buida, Li, Shen, & Rokas, 2020); 4) concatenating the alignments; 5) approximately-maximum-likelihood phylogenetic inference using FastTree2 (Price, Dehal, & Arkin, 2010); and 6) optional maximum likelihood phylogenetic inference using IQ-TREE2 (Minh et al., 2020). An option to include marker-specific noise cutoff scores is also available using the --scores_cutoff parameter. Poor-quality genomes that do not meet a threshold in the proportion of markers in the reference are removed using the --minimum_markers_aligned_ratio parameter. Similarly, non-informative markers that are not prevalent in the query genomes are removed using the --minimum_genomes_aligned_ratio parameter. +The phylogeny module is a tool used for phylogenetic inference and constructing phylogenetic trees for genomes given a reference marker set. This is performed by the following method: 1) identifying marker proteins using `HMMSearch` from the `HMMER3` suite; 2) creating protein alignments for each marker identified `MUSCLE`; 3) trimming the alignments using `ClipKIT`; 4) concatenating the alignments; 5) approximately-maximum-likelihood phylogenetic inference using `FastTree2` ; and 6) optional maximum likelihood phylogenetic inference using `IQ-TREE2`. An option to include marker-specific noise cutoff scores is also available using the `--scores_cutoff` parameter. Poor-quality genomes that do not meet a threshold in the proportion of markers in the reference are removed using the `--minimum_markers_aligned_ratio` parameter. Similarly, non-informative markers that are not prevalent in the query genomes are removed using the `--minimum_genomes_aligned_ratio` parameter. **Conda Environment**: `conda activate VEBA-phylogeny_env` @@ -864,16 +1021,16 @@ The phylogeny module is a tool used for phylogenetic inference and constructing ``` usage: phylogeny.py -d -a -o - Running: phylogeny.py v2022.06.22 via Python v3.8.5 | /Users/jespinoz/anaconda3/bin/python + Running: phylogeny.py v2023.6.12 via Python v3.11.0 | /expanse/projects/jcl110/anaconda3/envs/VEBA-phylogeny_env/bin/python -optional arguments: +options: -h, --help show this help message and exit Required I/O arguments: -d DATABASE_HMM, --database_hmm DATABASE_HMM path/to/HMM database of markers -a PROTEINS, --proteins PROTEINS - Can be the following format: 1) Tab-seperated value table of [id_mag][path/to/protein.fasta]; 2) List of filepaths [path/to/protein.fasta]; or 3) Directory of protein fasta using --protein_extension + Can be the following format: 1) Tab-seperated value table of [id_mag][path/to/protein.fasta] (No header); 2) Files with list of filepaths [path/to/protein.fasta] (uses --extension); or 3) Directory of protein fasta (uses --extension) -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY path/to/project_directory [Default: veba_output/phylogeny] -x EXTENSION, --extension EXTENSION @@ -892,17 +1049,19 @@ Utility arguments: HMMSearch arguments: --hmmsearch_threshold HMMSEARCH_THRESHOLD - HMMSearch | Threshold {cut_ga, cut_nc, gut_tc, e} [Default: e] + HMMER | Threshold {cut_ga, cut_nc, gut_tc, e} [Default: e] --hmmsearch_evalue HMMSEARCH_EVALUE - Diamond | E-Value [Default: 10.0] + HMMER | E-Value [Default: 10.0] --hmmsearch_options HMMSEARCH_OPTIONS - Diamond | More options (e.g. --arg 1 ) [Default: ''] + HMMER | More options (e.g. --arg 1 ) [Default: ''] -f HMM_MARKER_FIELD, --hmm_marker_field HMM_MARKER_FIELD HMM reference type (accession, name) [Default: accession -s SCORES_CUTOFF, --scores_cutoff SCORES_CUTOFF path/to/scores_cutoff.tsv. No header. [id_hmm][score] Alignment arguments: + -A ALIGNMENT_ALGORITHM, --alignment_algorithm ALIGNMENT_ALGORITHM + Muscle alignment algorithm. Align large input using Super5 algorithm if -align is too expensive. {align,super5} [Default: align] -g MINIMUM_GENOMES_ALIGNED_RATIO, --minimum_genomes_aligned_ratio MINIMUM_GENOMES_ALIGNED_RATIO Minimum ratio of genomes include in alignment. This removes markers that are under represented. [Default: 0.95] -m MINIMUM_MARKERS_ALIGNED_RATIO, --minimum_markers_aligned_ratio MINIMUM_MARKERS_ALIGNED_RATIO @@ -937,26 +1096,28 @@ Tree arguments: * prefiltered_alignment_table.tsv.gz - Prefiltered alignment table of (n = genomes, m = markers, ij=fasta alignment) * output.treefile - IQTREE2 newick format based on concatenated alignment (if --no_iqtree is not selected) +

^__^

+#### *index.py* +**Builds local or global index for alignment to genomes** -#### index – Builds local or global index for alignment to genomes -The index module creates reference indices for alignments in both local or global paradigms. In the local paradigm, an index is created for all the assembled genomes concatenated together for each sample. This is useful in situations where perfectly paired metagenomics and metatranscriptomics are available where the metatranscriptomics can be mapped directly to the de novo reference generated from the metagenomics. However, this is not applicable in all cases such as when there is not a perfect overlap between metagenomics and metatranscriptomics. In this global paradigm, assembled genomes are concatenated across all samples and an alignment index is created for this concatenated reference. Currently, Bowtie2 (Langmead & Salzberg, 2012) is the only alignment software packages supported. +The index module creates reference indices for alignments in both local or global paradigms. In the local paradigm, an index is created for all the assembled genomes concatenated together for each sample. This is useful in situations where perfectly paired metagenomics and metatranscriptomics are available where the metatranscriptomics can be mapped directly to the de novo reference generated from the metagenomics. However, this is not applicable in all cases such as when there is not a perfect overlap between metagenomics and metatranscriptomics. In this global paradigm, assembled genomes are concatenated across all samples and an alignment index is created for this concatenated reference. Currently, `Bowtie2` is the only alignment software packages supported. -**Conda Environment**: `conda activate VEBA-index_env` +**Conda Environment**: `conda activate VEBA-mapping_env` ``` usage: index.py -i -o --heatmap_output - Running: index.py v2022.02.17 via Python v3.9.10 | /usr/local/devel/ANNOTATION/jespinoz/anaconda3/envs/VEBA-mapping_env/bin/python + Running: index.py v2023.5.8 via Python v3.11.0 | /expanse/projects/jcl110/anaconda3/envs/VEBA-phylogeny_env/bin/python -optional arguments: +options: -h, --help show this help message and exit Required I/O arguments: -r REFERENCES, --references REFERENCES - local mode: [id_sample][path/to/reference.fa] and global mode: [path/to/reference.fa] + local mode: [id\_sample][path/to/reference.fa] and global mode: [path/to/reference.fa] -g GENE_MODELS, --gene_models GENE_MODELS - local mode: [id_sample][path/to/reference.gff] and global mode: [path/to/reference.gff] + local mode: [id\_sample][path/to/reference.gff] and global mode: [path/to/reference.gff] -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY path/to/project_directory [Default: veba_output/index] -m MINIMUM_CONTIG_LENGTH, --minimum_contig_length MINIMUM_CONTIG_LENGTH @@ -989,22 +1150,26 @@ Bowtie2 Index arguments: **Output (local):** -* [id_sample]/reference.fa.gz - Concatenated reference fasta -* [id_sample]/reference.fa.gz.\*.bt2 - Bowtie2 index of reference fasta -* [id_sample]/reference.gff - Concatenated gene models -* [id_sample]/reference.saf - SAF format for reference +* [id\_sample]/reference.fa.gz - Concatenated reference fasta +* [id\_sample]/reference.fa.gz.\*.bt2 - Bowtie2 index of reference fasta +* [id\_sample]/reference.gff - Concatenated gene models +* [id\_sample]/reference.saf - SAF format for reference + +

^__^

+ +#### *mapping.py* +**Aligns reads to local or global index of genomes** -#### mapping – Aligns reads to local or global index of genomes -The mapping module uses local or global reference indices generated by the index module and aligns reads using Bowtie2. The alignment files are sorted to produce sorted BAM files using Samtools which are then indexed. Coverage is calculated for contigs via Samtools and genome spatial coverage (i.e., ratio of bases covered in genome) is provided. Reads from the sorted BAM files are then fed into featureCounts to produce gene-level counts, orthogroup-level counts, MAG-level counts, and SLC-level counts. +The mapping module uses local or global reference indices generated by the index module and aligns reads using `Bowtie2`. The alignment files are sorted to produce sorted BAM files using `Samtools` which are then indexed. Coverage is calculated for contigs via `Samtools` and genome spatial coverage (i.e., ratio of bases covered in genome) is provided. Reads from the sorted BAM files are then fed into featureCounts to produce gene-level counts, orthogroup-level counts, MAG-level counts, and SLC-level counts. **Conda Environment**: `conda activate VEBA-mapping_env` ``` usage: mapping.py -1 -2 -n -o -x - Running: mapping.py v2022.8.17 via Python v3.9.7 | /Users/jespinoz/anaconda3/bin/python + Running: mapping.py v2023.5.15 via Python v3.11.0 | /expanse/projects/jcl110/anaconda3/envs/VEBA-phylogeny_env/bin/python -optional arguments: +options: -h, --help show this help message and exit Required I/O arguments: @@ -1055,15 +1220,13 @@ featureCounts arguments: featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/ Identifier arguments: - --orfs_to_orthogroups ORFS_TO_ORTHOGROUPS - path/to/orf_to_orthogroup.tsv, [id_orf][id_orthogroup], No header + --proteins_to_orthogroups PROTEINS_TO_ORTHOGROUPS + path/to/protein_to_orthogroup.tsv, [id\_orf][id_orthogroup], No header --scaffolds_to_bins SCAFFOLDS_TO_BINS path/to/scaffold_to_bins.tsv, [id_scaffold][id_bin], No header --scaffolds_to_clusters SCAFFOLDS_TO_CLUSTERS path/to/scaffold_to_cluster.tsv, [id_scaffold][id_cluster], No header -Copyright 2022 Josh L. Espinoza (jespinoz@jcvi.org) - ``` **Output:** @@ -1081,33 +1244,27 @@ Copyright 2022 Josh L. Espinoza (jespinoz@jcvi.org) * unmapped_1.fastq.gz - Unmapped reads (forward) * unmapped_2.fastq.gz - Unmapped reads (reverse) +

^__^

-___________________________________________________________________ -## Developmental - -#### biosynthetic – Identify biosynthetic gene clusters in prokaryotes and fungi +#### *biosynthetic.py* +**Identify biosynthetic gene clusters in prokaryotes and fungi** -The biosynthetic module is a wrapper around antiSMASH. It produces a tabular output that is machie-readale and easier to parse than the GBK and JSON files produced by antiSMASH. +The biosynthetic module is a wrapper around `antiSMASH`. It produces a tabular output that is machine-readale and easier to parse than the GBK and JSON files produced by antiSMASH. Novelty scores are calculated using the ratio of proteins that align to `MiBIG`. ``` -usage: biosynthetic.py -m -g -o -t bacteria +usage: biosynthetic.py -i -o -t bacteria | Suggested input is from `compile_genomes_table.py` script. Use cut -f3,4,7 - Running: biosynthetic.py v2022.10.16 via Python v3.9.7 | /Users/jespinoz/anaconda3/bin/python + Running: biosynthetic.py v2023.7.10 via Python v3.9.9 | /Users/jespinoz/anaconda3/bin/python optional arguments: -h, --help show this help message and exit Required I/O arguments: - -m MAGS, --mags MAGS Tab-seperated value table of [id_mag][path/to/genome.fasta] - -g GENE_MODELS, --gene_models GENE_MODELS - Tab-seperated value table of [id_mag][path/to/gene_models.gff]. Must be gff3. + -i INPUT, --input INPUT + path/to/input.tsv, Format: Must include the follow columns (No header) [id_mag][genome][gene_models]. Suggested input is from `compile_genomes_table.py` script. Use cut -f3,4,7 -o OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY - path/to/project_directory [Default: veba_output/cluster] - --mags_extension MAGS_EXTENSION - Fasta file extension for --mags if a list is provided [Default: fa] - --gene_models_extension GENE_MODELS_EXTENSION - File extension for gene models if a list is provided [Default: gff] + path/to/project_directory [Default: veba_output/biosynthetic] Utility arguments: --path_config PATH_CONFIG @@ -1120,6 +1277,10 @@ Utility arguments: Restart from a particular checkpoint [Default: None] -v, --version show program's version number and exit +Database arguments: + --veba_database VEBA_DATABASE + VEBA database location. [Default: $VEBA_DATABASE environment variable] + antiSMASH arguments: -t TAXON, --taxon TAXON Taxonomic classification of input sequence {bacteria,fungi} [Default: bacteria] @@ -1134,90 +1295,40 @@ antiSMASH arguments: --antismash_options ANTISMASH_OPTIONS antiSMASH | More options (e.g. --arg 1 ) [Default: ''] -Copyright 2022 Josh L. Espinoza (jespinoz@jcvi.org) -``` - -**Output:** - -* biosynthetic\_gene\_clusters.features.tsv - All of the BGC features in tabular format organized by genome, contig, region, and gene. -* biosynthetic\_gene\_clusters.type_counts.tsv - Summary of BGCs detected organized by type. Also includes summary of BGCs that are NOT on contig edge. - -#### assembly-sequential – Assemble metagenomes sequentially - -This method first uses biosyntheticSPAdes followed by either 1) [default] metaSPAdes; or 2) metaplasmidSPAdes and metaSPAdes. The reads that are not mapped to the scaffolds in step N are used for assembly in step N+1. The contigs/scaffolds are concatenated for the finally assembly. The purpose of this module is to properly handle biosynthetic gene clusters in addition to metagenomes. - -``` -usage: assembly-sequential.py -1 -2 -n -o - - Running: assembly-sequential.py v2022.11.14 via Python v3.9.7 | /Users/jespinoz/anaconda3/bin/python - -optional arguments: - -h, --help show this help message and exit - -Required I/O arguments: - -1 FORWARD_READS, --forward_reads FORWARD_READS - path/to/forward_reads.fq - -2 REVERSE_READS, --reverse_reads REVERSE_READS - path/to/reverse_reads.fq - -n NAME, --name NAME Name of sample - -o PROJECT_DIRECTORY, --project_directory PROJECT_DIRECTORY - path/to/project_directory [Default: veba_output/assembly_sequential] - -Utility arguments: - --path_config PATH_CONFIG - path/to/config.tsv [Default: CONDA_PREFIX] - -p N_JOBS, --n_jobs N_JOBS - Number of threads [Default: 1] - --random_state RANDOM_STATE - Random state [Default: 0] - --restart_from_checkpoint RESTART_FROM_CHECKPOINT - Restart from a particular checkpoint [Default: None] - -v, --version show program's version number and exit - --tmpdir TMPDIR Set temporary directory - -S, --remove_intermediate_scaffolds - Remove intermediate scaffolds.fasta.*. If this option is chosen, output files are not validated [Default is to keep] - -B, --remove_intermediate_bam - Remove intermediate mapped.sorted.bam.*. If this option is chosen, output files are not validated [Default is to keep] - -SPAdes arguments: - --run_metaplasmidspades - SPAdes | Run metaplasmidSPAdes. This may sacrifice MAG completeness for plasmid completeness. - -m MEMORY, --memory MEMORY - SPAdes | RAM limit in Gb (terminates if exceeded). [Default: 250] - --spades_options SPADES_OPTIONS - SPAdes | More options (e.g. --arg 1 ) [Default: ''] - http://cab.spbu.ru/files/release3.11.1/manual.html - -Bowtie2 arguments: - --bowtie2_index_options BOWTIE2_INDEX_OPTIONS - bowtie2-build | More options (e.g. --arg 1 ) [Default: ''] - --bowtie2_options BOWTIE2_OPTIONS - bowtie2 | More options (e.g. --arg 1 ) [Default: ''] +Diamond arguments: + --diamond_sensitivity DIAMOND_SENSITIVITY + Diamond | Sensitivity [Default: ''] + --diamond_evalue DIAMOND_EVALUE + Diamond | E-Value [Default: 0.001] + --diamond_options DIAMOND_OPTIONS + Diamond | More options (e.g. --arg 1 ) [Default: ''] -featureCounts arguments: - --featurecounts_options FEATURECOUNTS_OPTIONS - featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/ +Novelty score threshold arguments: + --pident PIDENT pident lower bound [float:0 ≤ x < 100] [Default: 0] + --qcovhsp QCOVHSP qcovhsp lower bound [float:0 ≤ x < 100] [Default: 0] + --scovhsp SCOVHSP scovhsp lower bound [float:0 ≤ x < 100] [Default: 0] + --evalue EVALUE e-value lower bound [float:0 < x < 1] [Default: 1e-3] -Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org) ``` **Output:** -* featurecounts.tsv.gz - featureCounts output for contig-level counts -* mapped.sorted.bam - Sorted BAM -* mapped.sorted.bam.bai - Sorted BAM index -* scaffolds.fasta - Assembly scaffolds (preferred over contigs by SPAdes documentation) -* scaffolds.fasta.\*.bt2 - Bowtie2 index of scaffolds -* scaffolds.fasta.saf - SAF formatted file for contig-level counts with featureCounts -* seqkit_stats.tsv.gz - Assembly statistics +* bgc.components.tsv - All of the BGC components (i.e., genes in BGC) in tabular format organized by genome, contig, region, and gene. +* bgc.synopsis.tsv - All of the BGCs in tabular format organized by genome, contig, region, and gene. +* bgc.type_counts.tsv - Summary of BGCs detected organized by type. Also includes summary of BGCs that are NOT on contig edge. +* components/*.faa.gz - BGC components in fasta format +* genbanks/[id\_genome]/*.gbk - Genbank formatted antiSMASH results + +

^__^

___________________________________________________________________ +## Developmental -### ⚠️EXPERIMENTAL -#### **amplicon** - Automated read trim position detection, DADA2 ASV detection, taxonomic classification, and file conversion +#### *amplicon.py* +**Automated read trim position detection, DADA2 ASV detection, taxonomic classification, and file conversion** -The amplicon module is a wrapper around QIIME2's implementation of the DADA2 ASV pipeline which has been fairly standardized. This works exclusively on paired-end short reads and is not designed for single-end reads nor long reads (the latter may be adapted later). The experimental portion of this module is the automatic detection of forward and reverse trim. This module first imports reads into a QIIME2 Artifact object, summarizes reads, and gets position-specific fastq statistics. The amplicon module uses the position-specific fastq statistics to suggest forward and reverse trim positions (this part is experimental, please use --inspect_trim_regions to manually check the quality plots to ensure it is where you would cut). Next ASVs are detected via DADA2 and denoising statistics are calculated. After ASVs are detected, taxonomy is classified using classification modules provided by user (e.g., [silva-138-99-nb-classifier.qza](https://data.qiime2.org/2022.8/common/silva-138-99-nb-classifier.qza) followed by phylogenetic inference. Finally, QIIME2 and BIOM formatted files are converted into tab-separated value tables and fasta files. +The amplicon module is a wrapper around `QIIME2`'s implementation of the `DADA2` ASV pipeline which has been fairly standardized. This works exclusively on paired-end short reads and is not designed for single-end reads nor long reads (the latter may be adapted later). The experimental portion of this module is the automatic detection of forward and reverse trim. This module first imports reads into a `QIIME2 Artifact` object, summarizes reads, and gets position-specific fastq statistics. The amplicon module uses the position-specific fastq statistics to suggest forward and reverse trim positions (this part is experimental, please use `--inspect_trim_regions` to manually check the quality plots to ensure it is where you would cut). Next ASVs are detected via `DADA2` and denoising statistics are calculated. After ASVs are detected, taxonomy is classified using classification modules provided by user (e.g., [silva-138-99-nb-classifier.qza](https://data.qiime2.org/2022.8/common/silva-138-99-nb-classifier.qza) followed by phylogenetic inference. Finally, `QIIME2` and `BIOM` formatted files are converted into tab-separated value tables and fasta files. ``` usage: amplicon.py -i -c -o @@ -1288,4 +1399,77 @@ Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org) * taxonomy.tsv - Taxonomy table [ASV][Lineage][Confidence] * tree.nwk - Newick formatted tree +

^__^

+ +#### *assembly-sequential.py* +**Assemble metagenomes sequentially** + +This method first uses biosyntheticSPAdes followed by either 1) [default] metaSPAdes; or 2) metaplasmidSPAdes and metaSPAdes. The reads that are not mapped to the scaffolds in step N are used for assembly in step N+1. The contigs/scaffolds are concatenated for the finally assembly. The purpose of this module is to properly handle biosynthetic gene clusters in addition to metagenomes. + +``` +usage: assembly-sequential.py -1 -2 -n -o + + Running: assembly-sequential.py v2022.11.14 via Python v3.9.7 | /Users/jespinoz/anaconda3/bin/python + +optional arguments: + -h, --help show this help message and exit + +Required I/O arguments: + -1 FORWARD_READS, --forward_reads FORWARD_READS + path/to/forward_reads.fq + -2 REVERSE_READS, --reverse_reads REVERSE_READS + path/to/reverse_reads.fq + -n NAME, --name NAME Name of sample + -o PROJECT_DIRECTORY, --project_directory PROJECT_DIRECTORY + path/to/project_directory [Default: veba_output/assembly_sequential] + +Utility arguments: + --path_config PATH_CONFIG + path/to/config.tsv [Default: CONDA_PREFIX] + -p N_JOBS, --n_jobs N_JOBS + Number of threads [Default: 1] + --random_state RANDOM_STATE + Random state [Default: 0] + --restart_from_checkpoint RESTART_FROM_CHECKPOINT + Restart from a particular checkpoint [Default: None] + -v, --version show program's version number and exit + --tmpdir TMPDIR Set temporary directory + -S, --remove_intermediate_scaffolds + Remove intermediate scaffolds.fasta.*. If this option is chosen, output files are not validated [Default is to keep] + -B, --remove_intermediate_bam + Remove intermediate mapped.sorted.bam.*. If this option is chosen, output files are not validated [Default is to keep] + +SPAdes arguments: + --run_metaplasmidspades + SPAdes | Run metaplasmidSPAdes. This may sacrifice MAG completeness for plasmid completeness. + -m MEMORY, --memory MEMORY + SPAdes | RAM limit in Gb (terminates if exceeded). [Default: 250] + --spades_options SPADES_OPTIONS + SPAdes | More options (e.g. --arg 1 ) [Default: ''] + http://cab.spbu.ru/files/release3.11.1/manual.html + +Bowtie2 arguments: + --bowtie2_index_options BOWTIE2_INDEX_OPTIONS + bowtie2-build | More options (e.g. --arg 1 ) [Default: ''] + --bowtie2_options BOWTIE2_OPTIONS + bowtie2 | More options (e.g. --arg 1 ) [Default: ''] + +featureCounts arguments: + --featurecounts_options FEATURECOUNTS_OPTIONS + featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/ + +Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org) +``` + +**Output:** + +* featurecounts.tsv.gz - featureCounts output for contig-level counts +* mapped.sorted.bam - Sorted BAM +* mapped.sorted.bam.bai - Sorted BAM index +* scaffolds.fasta - Assembly scaffolds (preferred over contigs by SPAdes documentation) +* scaffolds.fasta.\*.bt2 - Bowtie2 index of scaffolds +* scaffolds.fasta.saf - SAF formatted file for contig-level counts with featureCounts +* seqkit_stats.tsv.gz - Assembly statistics + +

^__^

diff --git a/src/SCRIPT_VERSIONS b/src/SCRIPT_VERSIONS index 8636761..40359ce 100644 --- a/src/SCRIPT_VERSIONS +++ b/src/SCRIPT_VERSIONS @@ -1,25 +1,26 @@ -VEBA __version__ 1.1.2 -amplicon.py __version__ = "2023.5.8" -annotate.py __version__ = "2023.5.14" -assembly-sequential.py __version__ = "2023.5.15" +VEBA __version__ = "1.2.0" +VEBA_DATABASE __version__ = "VDB_v5.1" +amplicon.py __version__ = "2023.5.23" +annotate.py __version__ = "2023.6.20" assembly.py __version__ = "2023.5.15" -binning-eukaryotic.py __version__ = "2023.5.15" -binning-prokaryotic.py __version__ = "2023.5.15" -binning-viral.py __version__ = "2023.5.15" -biosynthetic.py __version__ = "2023.5.16" -classify-eukaryotic.py __version__ = "2023.5.15" -classify-prokaryotic.py __version__ = "2023.5.15" +binning-eukaryotic.py __version__ = "2023.7.6" +binning-prokaryotic.py __version__ = "2023.7.7" +binning-viral.py __version__ = "2023.7.10" +biosynthetic.py __version__ = "2023.7.11" +classify-eukaryotic.py __version__ = "2023.6.12" +classify-prokaryotic.py __version__ = "2023.6.16" classify-viral.py __version__ = "2023.5.8" -cluster.py __version__ = "2023.5.15" +cluster.py __version__ = "2023.6.14" coverage.py __version__ = "2023.5.16" index.py __version__ = "2023.5.8" mapping.py __version__ = "2023.5.15" -phylogeny.py __version__ = "2023.5.15" +phylogeny.py __version__ = "2023.6.12" preprocess.py __version__ = "2023.5.8" scripts/antismash_genbanks_to_table.py __version__ = "2023.5.1" -scripts/append_geneid_to_prodigal_gff.py __version__ = "2021.06.19" +scripts/append_geneid_to_barrnap_gff.py __version__ = "2023.6.30" +scripts/append_geneid_to_prodigal_gff.py __version__ = "2023.6.29" scripts/append_geneid_to_transdecoder_gff.py __version__ = "2023.2.22" -scripts/bgc_novelty_scorer.py __version__ = "2023.3.9" +scripts/bgc_novelty_scorer.py __version__ = "2023.7.11" scripts/binning_wrapper.py __version__ = "2023.5.8" scripts/bowtie2_wrapper.py __version__ = "2023.5.15" scripts/check_fasta_duplicates.py __version__ = "2023.4.17" @@ -27,8 +28,10 @@ scripts/check_scaffolds_to_bins.py __version__ = "2021.08.20" scripts/compile_binning.py __version__ = "2022.03.23" scripts/compile_eukaryotic_classifications.py __version__ = "2023.3.20" scripts/compile_genomes_table.py __version__ = "2023.2.9" -scripts/compile_krona.py __version__ = "2023.5.1" +scripts/compile_gff.py __version__ = "2023.7.7" +scripts/compile_krona.py __version__ = "2023.6.12" scripts/compile_metaeuk_identifiers.py __version__ = "2022.12.07" +scripts/compile_protein_cluster_prevalence_table.py __version__ = "2023.5.18" scripts/compile_reads_table.py __version__ = "2023.1.26" scripts/compile_star_statistics.py __version__ = "2023.3.13" scripts/compile_veba_synopsis.py __version__ = "2023.2.9" @@ -36,49 +39,52 @@ scripts/concatenate_dataframes.py __version__ = "2022.5.10" scripts/concatenate_fasta.py __version__ = "2022.02.17" scripts/concatenate_gff.py __version__ = "2022.02.17" scripts/consensus_domain_classification.py __version__ = "2022.02.28" -scripts/consensus_genome_classification.py __version__ = "2023.2.13" -scripts/consensus_genome_classification_unranked.py __version__ = "2023.2.28" +scripts/consensus_genome_classification.py __version__ = "2023.6.12" +scripts/consensus_genome_classification_unranked.py __version__ = "2023.6.7" scripts/consensus_orthogroup_annotation.py __version__ = "2022.02.02" scripts/convert_counts_table.py __version__ = "2023.5.8" +scripts/convert_table_to_fasta.py __version__ = "2023.5.17" scripts/cut_table_by_column_index.py __version__ = "2023.2.9" scripts/cut_table_by_column_labels.py __version__ = "2023.2.15" scripts/determine_trim_position.py __version__ = "2022.8.11" scripts/drop_missing_values.py __version__ = "2023.1.31" scripts/edgelist_to_clusters.py __version__ = "2023.4.17" +scripts/eukaryotic_gene_modeling_wrapper.py __version__ = "2023.7.4" scripts/fasta_to_saf.py __version__ = "2021.04.04" scripts/fasta_utility.py __version__ = "2021.07.31" -scripts/fastq_position_statistics.py __version__ = "2022.10.24" -scripts/filter_busco_results.py __version__ = "2022.12.07" +scripts/fastq_position_statistics.py __version__ = "2023.5.23" +scripts/filter_busco_results.py __version__ = "2023.7.7" scripts/filter_checkm2_results.py __version__ = "2023.1.25" scripts/filter_checkv_results.py __version__ = "2023.2.14" scripts/filter_hmmsearch_results.py __version__ = "2023.4.18" scripts/genomad_taxonomy_wrapper.py __version__ = "2023.5.8" scripts/genome_coverage_from_spades.py __version__ = "2022.7.14" scripts/genome_spatial_coverage.py __version__ = "2022.08.17" -scripts/global_clustering.py __version__ = "2023.5.12" +scripts/global_clustering.py __version__ = "2023.6.15" scripts/groupby_table.py __version__ = "2022.08.17" scripts/hmmer_to_proteins.py __version__ = "2021.08.03" scripts/hmmer_wrapper.py __version__ = "2023.5.8" scripts/insert_column_to_table.py __version__ = "2022.03.24" -scripts/local_clustering.py __version__ = "2023.5.12" -scripts/merge_annotations.py __version__ = "2023.5.14" -scripts/merge_busco_json.py __version__ = "2022.03.10" +scripts/local_clustering.py __version__ = "2023.6.15" +scripts/merge_annotations.py __version__ = "2023.6.20" +scripts/merge_busco_json.py __version__ = "2023.7.5" scripts/merge_contig_mapping.py __version__ = "2022.5.12" scripts/merge_fastq_statistics.py __version__ = "2022.03.08" -scripts/merge_generalized_mapping.py __version__ = "2022.4.12" +scripts/merge_generalized_mapping.py __version__ = "2022.5.23" scripts/merge_genome_spatial_coverage.py __version__ = "2022.12.12" scripts/merge_gtdbtk.py __version__ = "2022.03.24" scripts/merge_msa.py __version__ = "2022.06.21" scripts/merge_orf_mapping.py __version__ = "2021.5.12" scripts/merge_taxonomy_classifications.py __version__ = "2021.5.12" scripts/metaeuk_wrapper.py __version__ = "2023.5.8" -scripts/mmseqs2_wrapper.py __version__ = "2023.5.8" +scripts/mmseqs2_wrapper.py __version__ = "2023.6.13" scripts/partition_gene_models.py __version__ = "2022.11.07" scripts/partition_hmmsearch.py __version__ = "2023.3.1" scripts/partition_multisplit_bins.py __version__ = "2023.2.6" +scripts/partition_organelle_sequences.py __version__ = "2023.6.28" scripts/partition_unbinned.py __version__ = "2021.08.05" scripts/propagate_annotations_from_representatives.py __version__ = "2023.5.14" -scripts/reformat_representative_sequences.py __version__ = "2023.3.17" +scripts/reformat_representative_sequences.py __version__ = "2023.6.13" scripts/replace_fasta_descriptions.py __version__ = "2022.11.05" scripts/scaffolds_to_bins.py __version__ = "2023.5.11" scripts/scaffolds_to_clusters.py __version__ = "2023.2.6" @@ -86,7 +92,15 @@ scripts/scaffolds_to_samples.py __version__ = "2023.2.6" scripts/star_wrapper.py __version__ = "2023.5.15" scripts/subset_table.py __version__ = "2023.3.14" scripts/subset_table_by_column.py __version__ = "2022.04.20" -scripts/table_to_fasta.py __version__ = "2023.4.19" +scripts/table_to_fasta.py __version__ = "2023.6.14" scripts/transcripts_to_genes.py __version__ = "2023.2.20" scripts/transdecoder_wrapper.py __version__ = "2023.5.8" scripts/virfinder_wrapper.r # __version__ = "2023.2.23" +../install/check_installation.sh # __version__ = "2023.3.14" +../install/download_databases.sh # __version__ = "2023.6.20" +../install/install_veba.sh # __version__ = "2023.3.27" +../install/uninstall_veba.sh # __version__ = "2023.5.15" +../install/update_environment_scripts.sh # __version__ = "2023.01.05" +../install/update_environment_variables.sh # __version__ = "2023.6.14" +../install/docker/build_docker_image.sh # __version__ = "2023.7.11" +../install/docker/dockerize_environments.sh # __version__ = "2023.7.11" diff --git a/src/annotate.py b/src/annotate.py index f25c738..b291bbd 100755 --- a/src/annotate.py +++ b/src/annotate.py @@ -662,7 +662,7 @@ def main(args=None): # Databases parser_databases = parser.add_argument_group('Database arguments') - parser_databases.add_argument("-u", "--uniref", type=str, default="uniref90", help="UniRef database to use {uniref90, uniref50}. Use uniref90 better characterized systems and uniref50 in less characterized systems [Default: uniref90]") + parser_databases.add_argument("-u", "--uniref", type=str, default="uniref90", help="UniRef database to use {uniref90, uniref50}. uniref90 receommended for well-characterized systems and uniref50 for less characterized systems [Default: uniref90]") parser_databases.add_argument("--veba_database", type=str, help=f"VEBA database location. [Default: $VEBA_DATABASE environment variable]") # Diamond diff --git a/src/binning-viral.py b/src/binning-viral.py index 3f4a6fd..352e9ea 100755 --- a/src/binning-viral.py +++ b/src/binning-viral.py @@ -13,7 +13,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.7.7" +__version__ = "2023.7.10" # geNomad def get_genomad_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): @@ -384,6 +384,23 @@ def get_output_cmd(input_filepaths, output_filepaths, output_directory, director ">", os.path.join(output_directory,"genome_statistics.tsv"), + "&&", + + # CDS + os.environ["seqkit"], + "stats", + "-a", + "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "genomes", "*.ffn"), + + "|", + + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x[:-4]); df.to_csv(sys.stdout, sep="\t")'""" + ">", + os.path.join(output_directory,"gene_statistics.cds.tsv"), + "&&", "rm -rf {}".format(os.path.join(directories["tmp"], "*")), diff --git a/src/biosynthetic.py b/src/biosynthetic.py index af6f74b..82f0840 100755 --- a/src/biosynthetic.py +++ b/src/biosynthetic.py @@ -12,15 +12,16 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.16" +__version__ = "2023.7.11" # antiSMASH def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): # Command cmd = [ """ -mkdir -p %s - +OUTPUT_DIRECTORY=%s +INTERMEDIATE_DIRECTORY=%s +mkdir -p ${INTERMEDIATE_DIRECTORY} n=1 while IFS= read -r LINE do read -r -a ARRAY <<< $LINE @@ -28,7 +29,7 @@ def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, dire GENOME=${ARRAY[1]} GENE_MODELS=${ARRAY[2]} - CHECKPOINT="%s/${ID}/ANTISMASH_CHECKPOINT" + CHECKPOINT="${INTERMEDIATE_DIRECTORY}/${ID}/ANTISMASH_CHECKPOINT" CWD=${PWD} @@ -37,40 +38,52 @@ def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, dire echo "[Running ${ID}]" # Remove directory - rm -rf %s/${ID} + rm -rf ${INTERMEDIATE_DIRECTORY}/${ID} START_TIME=${SECONDS} # Run antiSMASH - %s --allow-long-headers --verbose --skip-zip-file -c %d --output-dir %s/${ID} --html-title ${ID} --taxon %s --minlength %d --databases %s --hmmdetection-strictness %s --logfile %s/${ID}/log.txt --genefinding-gff3 ${GENE_MODELS} ${GENOME} + %s --allow-long-headers --verbose --skip-zip-file -c %d --output-dir ${INTERMEDIATE_DIRECTORY}/${ID} --html-title ${ID} --taxon %s --minlength %d --databases %s --hmmdetection-strictness %s --logfile ${INTERMEDIATE_DIRECTORY}/${ID}/log.txt --genefinding-gff3 ${GENE_MODELS} ${GENOME} # Genbanks to table - %s -i %s/${ID} -o %s/${ID}/antismash_features.tsv.gz -s %s/${ID}/synopsis.tsv.gz -t %s/${ID}/type_counts.tsv.gz --fasta_output %s/${ID}/bgc.features.faa.gz + %s -i ${INTERMEDIATE_DIRECTORY}/${ID} -o ${INTERMEDIATE_DIRECTORY}/${ID}/antismash_components.tsv.gz -s ${INTERMEDIATE_DIRECTORY}/${ID}/synopsis.tsv.gz -t ${INTERMEDIATE_DIRECTORY}/${ID}/type_counts.tsv.gz --fasta_output ${INTERMEDIATE_DIRECTORY}/${ID}/bgc.components.faa.gz # Compile table for Krona graph - %s -i %s/${ID}/type_counts.tsv.gz -m biosynthetic-local -o %s/${ID}/krona.tsv + %s -i ${INTERMEDIATE_DIRECTORY}/${ID}/type_counts.tsv.gz -m biosynthetic-local -o ${INTERMEDIATE_DIRECTORY}/${ID}/krona.tsv # Create Krona graph - %s -i %s/${ID}/krona.tsv -o %s/${ID}/krona.html -n ${ID} + %s -o ${INTERMEDIATE_DIRECTORY}/${ID}/krona.html -n ${ID} ${INTERMEDIATE_DIRECTORY}/${ID}/krona.tsv # Symlink proteins - SRC=%s/${ID}/bgc.features.faa.gz - DST=%s/ - SRC=$(realpath --relative-to ${DST} ${SRC}) - [ -f "$SRC" ] && ln -sf ${SRC} ${DST}/${ID}.bgc_features.faa.gz - + FASTA_DIRECTORY=${OUTPUT_DIRECTORY}/fasta/ + SRC_FILES=${INTERMEDIATE_DIRECTORY}/${ID}/bgc.components.faa.gz + DST=${FASTA_DIRECTORY}/ + + for SRC in $(ls ${SRC_FILES}) + do + if [ -f "${SRC}" ]; then + SRC=$(realpath --relative-to ${DST} ${SRC}) + ln -sf ${SRC} ${DST}/${ID}.faa.gz + fi + done + # Symlink genbanks - mkdir -p %s/${ID} - SRC=%s/${ID}/*.region*.gbk - DST=%s/ - SRC=$(realpath --relative-to ${DST} ${SRC}) - [ -f "$SRC" ] && ln -sf ${SRC} ${DST} + GENBANK_DIRECTORY=${OUTPUT_DIRECTORY}/genbanks/ + mkdir -p ${GENBANK_DIRECTORY}/${ID} + SRC_FILES=${INTERMEDIATE_DIRECTORY}/${ID}/*.region*.gbk + DST=${GENBANK_DIRECTORY}/${ID}/ + + for SRC in $(ls ${SRC_FILES}) + do + SRC=$(realpath --relative-to ${DST} ${SRC}) + ln -sf ${SRC} ${DST} + done # Completed - echo "Completed: $(date)" > %s/${ID}/ANTISMASH_CHECKPOINT + echo "Completed: $(date)" > ${INTERMEDIATE_DIRECTORY}/${ID}/ANTISMASH_CHECKPOINT # Remove large assembly files - rm -rf %s/${ID}/assembly.* + rm -rf ${INTERMEDIATE_DIRECTORY}/${ID}/assembly.* END_TIME=${SECONDS} RUN_TIME=$((END_TIME-START_TIME)) @@ -85,85 +98,59 @@ def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, dire done < %s # Concatenate tables -%s -a 0 -e %s/*/antismash_features.tsv.gz | gzip > %s/bgc.features.tsv.gz -%s -a 0 -e %s/*/type_counts.tsv.gz | gzip > %s/bgc.type_counts.tsv.gz -%s -a 0 -e %s/*/synopsis.tsv.gz | gzip > %s/bgc.synopsis.tsv.gz +%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/antismash_components.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/bgc.components.tsv.gz +%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/type_counts.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/bgc.type_counts.tsv.gz +%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/synopsis.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/bgc.synopsis.tsv.gz # Krona # Compile table for Krona graph -%s -i %s/bgc.type_counts.tsv.gz -m biosynthetic-global -o %s/krona.tsv +%s -i ${OUTPUT_DIRECTORY}/bgc.type_counts.tsv.gz -m biosynthetic-global -o ${OUTPUT_DIRECTORY}/krona.tsv # Create Krona graph -%s %s/krona.tsv -o %s/krona.html -n 'antiSMASH' +%s ${OUTPUT_DIRECTORY}/krona.tsv -o ${OUTPUT_DIRECTORY}/krona.html -n 'antiSMASH' """%( # Args - output_directory, - output_directory, + directories["output"], output_directory, # antiSMASH os.environ["antismash"], opts.n_jobs, - output_directory, opts.taxon, opts.minimum_contig_length, opts.antismash_database, opts.hmmdetection_strictness, - output_directory, # Summary table os.environ["antismash_genbanks_to_table.py"], - output_directory, - output_directory, - output_directory, - output_directory, - output_directory, # Krona (Local) os.environ["compile_krona.py"], - output_directory, - output_directory, os.environ["ktImportText"], - output_directory, - output_directory, # Symlink (proteins) - output_directory, - directories[("output", "features")], # Symlink (genbanks) - directories[("output", "genbanks")], - output_directory, - directories[("output", "genbanks")], - - output_directory, # Remove large assembly - output_directory, + + # Input input_filepaths[0], # Concatenate os.environ["concatenate_dataframes.py"], - output_directory, - directories["output"], os.environ["concatenate_dataframes.py"], - output_directory, - directories["output"], os.environ["concatenate_dataframes.py"], - output_directory, - directories["output"], # Krona (Global) os.environ["compile_krona.py"], - directories["output"], - directories["output"], + os.environ["ktImportText"], - directories["output"], - directories["output"], + ), ] @@ -180,22 +167,23 @@ def get_diamond_cmd( input_filepaths, output_filepaths, output_directory, direct "&&", "cat", - os.path.join(input_filepaths[0], "*", "bgc.features.faa.gz"), + os.path.join(input_filepaths[0], "*", "bgc.components.faa.gz"), "|", "gzip -d", ">", - os.path.join(directories["tmp"], "features.concatenated.faa"), + os.path.join(directories["tmp"], "components.concatenated.faa"), + # MiBIG "&&", os.environ["diamond"], "blastp", "--db {}".format(input_filepaths[1]), - "--query {}".format(os.path.join(directories["tmp"], "features.concatenated.faa")), + "--query {}".format(os.path.join(directories["tmp"], "components.concatenated.faa")), "--threads {}".format(opts.n_jobs), "-f 6 {}".format(" ".join(fields)), "--evalue {}".format(opts.diamond_evalue), - "-o {}".format(os.path.join(output_directory, "homology.mibig.no_header.tsv")), + "-o {}".format(os.path.join(output_directory, "diamond_output.mibig.no_header.tsv")), "--max-target-seqs 1", "--tmpdir {}".format(os.path.join(directories["tmp"], "diamond")), # "--header 2", @@ -206,37 +194,79 @@ def get_diamond_cmd( input_filepaths, output_filepaths, output_directory, direct ] cmd += [ opts.diamond_options, - ] - cmd += [ - "&&", + "&&", "echo", "'{}'".format("\t".join(fields)), ">", - os.path.join(directories["output"], "homology.mibig.tsv"), + os.path.join(output_directory, "diamond_output.mibig.tsv"), "&&", "cat", - os.path.join(output_directory, "homology.mibig.no_header.tsv"), + os.path.join(output_directory, "diamond_output.mibig.no_header.tsv"), ">>", - os.path.join(directories["output"], "homology.mibig.tsv"), + os.path.join(output_directory, "diamond_output.mibig.tsv"), + # VFDB + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "diamond", "*")), - "&&", + "&&", + + os.environ["diamond"], + "blastp", + "--db {}".format(input_filepaths[2]), + "--query {}".format(os.path.join(directories["tmp"], "components.concatenated.faa")), + "--threads {}".format(opts.n_jobs), + "-f 6 {}".format(" ".join(fields)), + "--evalue {}".format(opts.diamond_evalue), + "-o {}".format(os.path.join(output_directory, "diamond_output.vfdb.no_header.tsv")), + "--max-target-seqs 1", + "--tmpdir {}".format(os.path.join(directories["tmp"], "diamond")), + # "--header 2", + ] + if bool(opts.diamond_sensitivity): + cmd += [ + "--{}".format(opts.diamond_sensitivity), + ] + cmd += [ + opts.diamond_options, + + "&&", - "pigz", - "-f", - "-p {}".format(opts.n_jobs), - os.path.join(directories["output"], "homology.mibig.tsv"), + "echo", + "'{}'".format("\t".join(fields)), + ">", + os.path.join(output_directory, "diamond_output.vfdb.tsv"), + + "&&", + + "cat", + os.path.join(output_directory, "diamond_output.vfdb.no_header.tsv"), + ">>", + os.path.join(output_directory, "diamond_output.vfdb.tsv"), + + ] + + cmd += [ + + "&&", + + os.environ["concatenate_dataframes.py"], + "-a 1", + "--prepend_column_levels MiBIG,VFDB", + "-o {}".format(os.path.join(directories["output"], "homology.tsv.gz")), + os.path.join(output_directory, "diamond_output.mibig.tsv"), + os.path.join(output_directory, "diamond_output.vfdb.tsv"), "&&", "rm", - os.path.join(directories["tmp"], "features.concatenated.faa"), - os.path.join(output_directory, "homology.mibig.no_header.tsv"), + os.path.join(directories["tmp"], "components.concatenated.faa"), + os.path.join(output_directory, "*.no_header.tsv"), ] @@ -248,7 +278,7 @@ def get_novelty_score_cmd(input_filepaths, output_filepaths, output_directory, d # Command cmd = [ os.environ["bgc_novelty_scorer.py"], - "-i {}".format(input_filepaths[0]), + "-c {}".format(input_filepaths[0]), "-s {}".format(input_filepaths[1]), "-d {}".format(input_filepaths[2]), "-o {}".format(output_filepaths[0]), @@ -280,7 +310,6 @@ def add_executables_to_environment(opts): "antismash", # 2 "diamond", - # Krona "ktImportText", @@ -343,11 +372,12 @@ def create_pipeline(opts, directories, f_cmds): opts.input, ] output_filenames = [ - "bgc.features.tsv.gz", + "bgc.components.tsv.gz", "bgc.type_counts.tsv.gz", "bgc.synopsis.tsv.gz", - "features/*.features.faa.gz", "krona.html", + "fasta/", + "genbanks/", ] output_filepaths = list(map(lambda filename: os.path.join(directories["output"], filename), output_filenames)) @@ -385,14 +415,15 @@ def create_pipeline(opts, directories, f_cmds): output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) # Info - description = "Diamond [MIBiG]" + description = "Diamond [MIBiG, VFDB]" # i/o input_filepaths = [ directories[("intermediate", "1__antismash")], os.path.join(opts.veba_database, "Annotate", "MIBiG", "mibig_v3.1.dmnd"), + os.path.join(opts.veba_database, "Annotate", "VFDB", "VFDB_setA_pro.dmnd"), ] - output_filenames = ["homology.mibig.tsv.gz"] + output_filenames = ["homology.tsv.gz"] output_filepaths = list(map(lambda filename: os.path.join(directories["output"], filename), output_filenames)) params = { @@ -429,9 +460,9 @@ def create_pipeline(opts, directories, f_cmds): # i/o input_filepaths = [ - os.path.join(directories["output"], "bgc.features.tsv.gz"), + os.path.join(directories["output"], "bgc.components.tsv.gz"), os.path.join(directories["output"], "bgc.synopsis.tsv.gz"), - os.path.join(directories["output"], "homology.mibig.tsv.gz"), + os.path.join(directories["output"], "homology.tsv.gz"), ] output_filenames = ["bgc.synopsis.tsv.gz"] # Overwriting @@ -555,11 +586,11 @@ def main(args=None): parser_diamond.add_argument("--diamond_options", type=str, default="", help="Diamond | More options (e.g. --arg 1 ) [Default: '']") # Novelty - parser_thresholds = parser.add_argument_group('Optional thresholds arguments') - parser.add_argument("--pident", type=float, default=0.0, help = "pident lower bound [float:0 ≤ x < 100] [Default: 0]") - parser.add_argument("--qcovhsp", type=float, default=0.0, help = "qcovhsp lower bound [float:0 ≤ x < 100] [Default: 0]") - parser.add_argument("--scovhsp", type=float, default=0.0, help = "scovhsp lower bound [float:0 ≤ x < 100] [Default: 0]") - parser.add_argument("--evalue", type=float, default=1e-3, help = "e-value lower bound [float:0 < x < 1] [Default: 1e-3]") + parser_noveltyscore = parser.add_argument_group('Novelty score threshold arguments') + parser_noveltyscore.add_argument("--pident", type=float, default=0.0, help = "pident lower bound [float:0 ≤ x < 100] [Default: 0]") + parser_noveltyscore.add_argument("--qcovhsp", type=float, default=0.0, help = "qcovhsp lower bound [float:0 ≤ x < 100] [Default: 0]") + parser_noveltyscore.add_argument("--scovhsp", type=float, default=0.0, help = "scovhsp lower bound [float:0 ≤ x < 100] [Default: 0]") + parser_noveltyscore.add_argument("--evalue", type=float, default=1e-3, help = "e-value lower bound [float:0 < x < 1] [Default: 1e-3]") # Options @@ -588,7 +619,7 @@ def main(args=None): directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) directories["intermediate"] = create_directory(os.path.join(directories["project"], "intermediate")) os.environ["TMPDIR"] = directories["tmp"] - directories[("output", "features")] = create_directory(os.path.join(directories["output"], "features")) + directories[("output", "fasta")] = create_directory(os.path.join(directories["output"], "fasta")) directories[("output", "genbanks")] = create_directory(os.path.join(directories["output"], "genbanks")) diff --git a/src/get_script_versions.sh b/src/get_script_versions.sh index 72c4817..038fb73 100755 --- a/src/get_script_versions.sh +++ b/src/get_script_versions.sh @@ -1,3 +1,10 @@ -VEBA_VERSION=$(cat ../VERSION) -echo "VEBA __version__ ${VEBA_VERSION}" > SCRIPT_VERSIONS -for FP in *.py scripts/*.py scripts/*.r; do V=$(grep "__version__ =" ${FP}); echo "${FP} ${V}"; done >> SCRIPT_VERSIONS +# __version__ = "2023.7.11" + +VEBA_VERSION=$(head -n 1 ../VERSION) +VDB_VERSION=$(head -n 2 ../VERSION | tail -n 1) + +>SCRIPT_VERSIONS +echo "VEBA __version__ = \"${VEBA_VERSION}\"" >> SCRIPT_VERSIONS +echo "VEBA_DATABASE __version__ = \"${VDB_VERSION}\"" >> SCRIPT_VERSIONS + +for FP in *.py scripts/*.py scripts/*.r ../install/*.sh ../install/docker/*.sh; do V=$(grep "__version__ =" ${FP}); echo "${FP} ${V}"; done >> SCRIPT_VERSIONS diff --git a/src/scripts/bgc_novelty_scorer.py b/src/scripts/bgc_novelty_scorer.py index a6182e3..3713be0 100755 --- a/src/scripts/bgc_novelty_scorer.py +++ b/src/scripts/bgc_novelty_scorer.py @@ -3,7 +3,7 @@ import pandas as pd __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.3.9" +__version__ = "2023.7.11" def main(args=None): # Path info @@ -12,19 +12,19 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -i -s -d -o ".format(__program__) + usage = "{} -i -s -d -o ".format(__program__) epilog = "Copyright 2022 Josh L. Espinoza (jespinoz@jcvi.org)" # Parser parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') - parser_io.add_argument("-i","--features", type=str, required=True, help = "path/to/biosynthetic_gene_clusters.features.tsv.gz") - parser_io.add_argument("-s","--synopsis", type=str, required=True, help = "path/to/biosynthetic_gene_clusters.synopsis.tsv.gz") - parser_io.add_argument("-d","--diamond", type=str, required=True, help = "path/to/homology.mibig.tsv.gz") + parser_io.add_argument("-d","--diamond", type=str, required=True, help = "path/to/homology.tsv.gz") + parser_io.add_argument("-c","--components", type=str, required=True, help = "path/to/bgc.components.tsv.gz") + parser_io.add_argument("-s","--synopsis", type=str, required=True, help = "path/to/bgc.synopsis.tsv.gz") parser_io.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.tsv [Default: stdout]") - parser_thresholds = parser.add_argument_group('Optional thresholds arguments') + parser_thresholds = parser.add_argument_group('Novelty score threshold arguments') parser.add_argument("--pident", type=float, default=0.0, help = "pident lower bound [float:0 ≤ x < 100] [Default: 0]") parser.add_argument("--qcovhsp", type=float, default=0.0, help = "qcovhsp lower bound [float:0 ≤ x < 100] [Default: 0]") parser.add_argument("--scovhsp", type=float, default=0.0, help = "scovhsp lower bound [float:0 ≤ x < 100] [Default: 0]") @@ -40,25 +40,38 @@ def main(args=None): opts.output = sys.stdout # Load tables - df_features = pd.read_csv(opts.features, sep="\t", index_col=None) + df_components = pd.read_csv(opts.components, sep="\t", index_col=None) df_synopsis = pd.read_csv(opts.synopsis, sep="\t", index_col=0) - df_diamond = pd.read_csv(opts.diamond, sep="\t", index_col=0) + df_diamond = pd.read_csv(opts.diamond, sep="\t", index_col=0, header=[0,1]) + # Gene -> BGC - gene_to_bgc = dict(zip(df_features["gene_id"], df_features["bgc_id"])) + gene_to_bgc = dict(zip(df_components["gene_id"], df_components["bgc_id"])) - # Filter diamond - df_diamond = df_diamond.query("pident >= {}".format(opts.pident)) - df_diamond = df_diamond.query("qcovhsp >= {}".format(opts.qcovhsp)) - df_diamond = df_diamond.query("scovhsp >= {}".format(opts.scovhsp)) - df_diamond = df_diamond.query("evalue <= {}".format(opts.evalue)) + # MiBIG + df_mibig = df_diamond["MiBIG"].dropna(how="all", axis=0) + df_mibig = df_mibig.query("pident >= {}".format(opts.pident)) + df_mibig = df_mibig.query("qcovhsp >= {}".format(opts.qcovhsp)) + df_mibig = df_mibig.query("scovhsp >= {}".format(opts.scovhsp)) + df_mibig = df_mibig.query("evalue <= {}".format(opts.evalue)) - # BGC -> Number of hits - bgc_to_nhits = pd.Series([0]*df_synopsis.shape[0], df_synopsis.index) - bgc_to_nhits.update(df_diamond.index.map(lambda x: gene_to_bgc[x]).value_counts()) - df_synopsis["number_of_mibig_hits"] = bgc_to_nhits + bgc_to_mibignhits = pd.Series([0]*df_synopsis.shape[0], df_synopsis.index) + bgc_to_mibignhits.update(df_mibig.index.map(lambda x: gene_to_bgc[x]).value_counts()) + df_synopsis["number_of_mibig_hits"] = bgc_to_mibignhits df_synopsis["novelty_score"] = 1 - df_synopsis["number_of_mibig_hits"]/df_synopsis["number_of_genes"] + # VFDB + df_vfdb = df_diamond["VFDB"].dropna(how="all", axis=0) + df_vfdb = df_vfdb.query("pident >= {}".format(opts.pident)) + df_vfdb = df_vfdb.query("qcovhsp >= {}".format(opts.qcovhsp)) + df_vfdb = df_vfdb.query("scovhsp >= {}".format(opts.scovhsp)) + df_vfdb = df_vfdb.query("evalue <= {}".format(opts.evalue)) + + bgc_to_vfdbnhits = pd.Series([0]*df_synopsis.shape[0], df_synopsis.index) + bgc_to_vfdbnhits.update(df_vfdb.index.map(lambda x: gene_to_bgc[x]).value_counts()) + df_synopsis["number_of_vfdb_hits"] = bgc_to_mibignhits + df_synopsis["virulence_ratio"] = df_synopsis["number_of_vfdb_hits"]/df_synopsis["number_of_genes"] + # Output df_synopsis.to_csv(opts.output, sep="\t") diff --git a/walkthroughs/adapting_commands_for_docker.md b/walkthroughs/adapting_commands_for_docker.md index f9fe942..99d2c3d 100644 --- a/walkthroughs/adapting_commands_for_docker.md +++ b/walkthroughs/adapting_commands_for_docker.md @@ -24,7 +24,7 @@ Refer to the [Docker documentation](https://docs.docker.com/engine/install/). Let's say you wanted to use the `preprocess` module. Download the Docker image as so: ``` -VERSION=1.1.2 +VERSION=1.2.0 docker image pull jolespin/veba_preprocess:${VERSION} ``` @@ -36,7 +36,7 @@ For example, here's how we would run the `preprocess.py` module. First let's ju ```bash # Version -VERSION=1.1.2 +VERSION=1.2.0 # Image DOCKER_IMAGE="jolespin/veba_preprocess:${VERSION}" @@ -66,12 +66,12 @@ LOCAL_WORKING_DIRECTORY=$(pwd) LOCAL_WORKING_DIRECTORY=$(realpath -m ${LOCAL_WORKING_DIRECTORY}) LOCAL_OUTPUT_PARENT_DIRECTORY=../ LOCAL_OUTPUT_PARENT_DIRECTORY=$(realpath -m ${LOCAL_OUTPUT_PARENT_DIRECTORY}) -#LOCAL_DATABASE_DIRECTORY=~/VDB-test/ -#LOCAL_DATABASE_DIRECTORY=$(realpath -m ${LOCAL_DATABASE_DIRECTORY}) +LOCAL_DATABASE_DIRECTORY=${VEBA_DATABASE} +LOCAL_DATABASE_DIRECTORY=$(realpath -m ${LOCAL_DATABASE_DIRECTORY}) CONTAINER_INPUT_DIRECTORY=/volumes/input/ CONTAINER_OUTPUT_DIRECTORY=/volumes/output/ -#CONTAINER_DATABASE_DIRECTORY=/volumes/database/ +CONTAINER_DATABASE_DIRECTORY=/volumes/database/ # Parameters ID=S1 @@ -84,13 +84,19 @@ RELATIVE_OUTPUT_DIRECTORY=veba_output/preprocess/ CMD="preprocess.py -1 ${CONTAINER_INPUT_DIRECTORY}/${R1} -2 ${CONTAINER_INPUT_DIRECTORY}/${R2} -n ${ID} -o ${CONTAINER_OUTPUT_DIRECTORY}/${RELATIVE_OUTPUT_DIRECTORY} -x ${CONTAINER_DATABASE_DIRECTORY}/Contamination/chm13v2.0/chm13v2.0" # Docker -DOCKER_IMAGE="jolespin/veba_preprocess:1.1.2" +# Version +VERSION=1.2.0 + +# Image +DOCKER_IMAGE="jolespin/veba_preprocess:${VERSION}" + +# Run docker run \ --name ${NAME} \ --rm \ --volume ${LOCAL_WORKING_DIRECTORY}:${CONTAINER_INPUT_DIRECTORY}:ro \ --volume ${LOCAL_OUTPUT_PARENT_DIRECTORY}:${CONTAINER_OUTPUT_DIRECTORY}:rw \ - #--volume ${LOCAL_DATABASE_DIRECTORY}:${CONTAINER_DATABASE_DIRECTORY}:ro \ + --volume ${LOCAL_DATABASE_DIRECTORY}:${CONTAINER_DATABASE_DIRECTORY}:ro \ ${DOCKER_IMAGE} \ -c "${CMD}" @@ -111,8 +117,3 @@ or just the output: ``` ls -lhS ${LOCAL_OUTPUT_PARENT_DIRECTORY}/veba_output/preprocess/${ID}/output ``` - - -#### Next steps: - -Whatever you want to do. \ No newline at end of file diff --git a/walkthroughs/commands/SunGridEngine/cmd_annotate.sh b/walkthroughs/commands/SunGridEngine/cmd_annotate.sh new file mode 100644 index 0000000..bb4bac2 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_annotate.sh @@ -0,0 +1,23 @@ +CODE=0714 +N_JOBS=1 + +#for ID in $(cat identifiers.list); +# do N="annotate__${ID}"; +# rm -f logs/${N}.* +# OUT_DIR="veba_output/annotation/${ID}" +# mkdir -p ${OUT_DIR} +# #PROTEIN=veba_output/binning/prokaryotic/${ID}/intermediate/2__prodigal/gene_models.faa +# PROTEIN=veba_output/binning/prokaryotic/${ID}/output/genomes/ +# MAPPING=veba_output/binning/prokaryotic/${ID}/output/genomes/identifier_mapping.tsv +# CMD="source activate VEBA-annotate_env && python ~/Algorithms/Pipelines/veba_pipeline/src/annotate.py -a ${PROTEIN} -o ${OUT_DIR} -p ${N_JOBS} -i ${MAPPING}" +# qsub -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" +# done + +N="annotate" +rm -f logs/${N}.* +OUT_DIR="veba_output/annotation/" +mkdir -p ${OUT_DIR} +PROTEIN=all_proteins.faa +MAPPING=identifier_mapping.tsv +CMD="source activate VEBA-annotate_env && python ~/Algorithms/Pipelines/veba_pipeline/src/annotate.py -a ${PROTEIN} -o ${OUT_DIR} -p ${N_JOBS} -i ${MAPPING}" +qsub -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" diff --git a/walkthroughs/commands/SunGridEngine/cmd_assembly.sh b/walkthroughs/commands/SunGridEngine/cmd_assembly.sh new file mode 100644 index 0000000..ad1ea73 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_assembly.sh @@ -0,0 +1,15 @@ +CODE=0714 +N_JOBS=8 +OUT_DIR=veba_output/assembly + +mkdir -p logs +mkdir -p ${OUT_DIR} + +for ID in $(cat identifiers.list); + do N="assembly__${ID}"; + rm -f logs/${N}.* + R1=veba_output/preprocess/${ID}/output/cleaned_1.fastq.gz + R2=veba_output/preprocess/${ID}/output/cleaned_2.fastq.gz + CMD="source activate VEBA-assembly_env && python ~/Algorithms/Pipelines/veba_pipeline/src/assembly.py -1 ${R1} -2 ${R2} -n ${ID} -o ${OUT_DIR} -p ${N_JOBS} -m 1024" + qsub -l himem7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" + done diff --git a/walkthroughs/commands/SunGridEngine/cmd_binning_eukaryotic.sh b/walkthroughs/commands/SunGridEngine/cmd_binning_eukaryotic.sh new file mode 100644 index 0000000..cfa9917 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_binning_eukaryotic.sh @@ -0,0 +1,18 @@ +CODE=0714 +N_JOBS=4 +#DB=/usr/local/scratch/CORE/jespinoz/db/veba/eukaryotic_classification/eukaryotic +DB=/usr/local/scratch/CORE/jespinoz/db/veba/v1.0/Classify/Eukaryotic/eukaryotic + +for ID in $(cat identifiers.list); +#for ID in $(cat identifiers.rerun.list); +#for ID in SRR17458614 SRR17458615 SRR17458630 SRR17458638; +#for ID in SRR17458613; + do N="binning-eukaryotic-metabat2__${ID}"; + rm -f logs/${N}.* + #rm -rf veba_output/binning/eukaryotic/${ID} + #FASTA=veba_output/assembly/${ID}/output/scaffolds.fasta + FASTA=veba_output/binning/prokaryotic/${ID}/output/unbinned.fasta + BAM=veba_output/assembly/${ID}/output/mapped.sorted.bam + CMD="source activate VEBA-binning-eukaryotic_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-eukaryotic.py -f ${FASTA} -b ${BAM} -n ${ID} -p ${N_JOBS} -m 1500 -a metabat2 --metaeuk_database $DB -o veba_output/binning/eukaryotic" + qsub -V -l himem7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" + done diff --git a/walkthroughs/commands/SunGridEngine/cmd_binning_prokaryotic.sh b/walkthroughs/commands/SunGridEngine/cmd_binning_prokaryotic.sh new file mode 100644 index 0000000..028f1a5 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_binning_prokaryotic.sh @@ -0,0 +1,13 @@ +CODE=0714 +N_JOBS=16 + +for ID in $(cat identifiers.list); + do N="binning-prokaryotic__${ID}"; + rm -f logs/${N}.* + #FASTA=veba_output/assembly/${ID}/output/scaffolds.fasta + #BAM=veba_output/assembly/${ID}/output/mapped.sorted.bam + FASTA=veba_output/assembly/${ID}/intermediate/1__assembly/scaffolds.fasta + BAM=veba_output/assembly/${ID}/intermediate/2__alignment/mapped.sorted.bam + CMD="source activate VEBA-binning-prokaryotic_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-prokaryotic.py -f ${FASTA} -b ${BAM} -n ${ID} -p ${N_JOBS} -m 1500 -I 10" + qsub -V -l himem7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" + done diff --git a/walkthroughs/commands/SunGridEngine/cmd_binning_viral.sh b/walkthroughs/commands/SunGridEngine/cmd_binning_viral.sh new file mode 100644 index 0000000..227caf4 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_binning_viral.sh @@ -0,0 +1,10 @@ +CODE=0714 +N_JOBS=4 + +for ID in $(cat identifiers.list); + do N="binning-viral__${ID}"; + rm -f logs/${N}.* + FASTA=veba_output/binning/eukaryotic/${ID}/output/unbinned.fasta + CMD="source activate VEBA-binning_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-viral.py -f ${FASTA} -n ${ID} -p ${N_JOBS} -m 1500 -o veba_output/binning/viral" + qsub -V -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" + done diff --git a/walkthroughs/commands/SunGridEngine/cmd_classify_eukaryotic.sh b/walkthroughs/commands/SunGridEngine/cmd_classify_eukaryotic.sh new file mode 100644 index 0000000..99332f2 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_classify_eukaryotic.sh @@ -0,0 +1,6 @@ +CODE=0714 +N=classify-eukaryotic +rm -rf logs/${N}.* +qsub -M jespinoz@jcvi.org -m e -l centos7 -N ${N} -o logs/${N}.o -e logs/${N}.e -cwd -P ${CODE} "source activate VEBA-classify_env && python ~/Algorithms/Pipelines/veba_pipeline/src/classify-eukaryotic.py -i veba_output/binning/eukaryotic/ -o veba_output/classify/eukaryotic -l 1.0" + + diff --git a/walkthroughs/commands/SunGridEngine/cmd_classify_prokaryotic.sh b/walkthroughs/commands/SunGridEngine/cmd_classify_prokaryotic.sh new file mode 100644 index 0000000..07ef2e8 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_classify_prokaryotic.sh @@ -0,0 +1,11 @@ +N_JOBS=4 +CODE=0714 +N=classify-prokaryotic +rm -f logs/${N}.* +#GENOMES=Output/Prokaryotic +INPUT=veba_output/binning/prokaryotic +#CLUSTERS=veba_output/cluster/output/clusters.tsv +qsub -l centos7 -N ${N} -o logs/${N}.o -e logs/${N}.e -cwd -P ${CODE} -pe threaded ${N_JOBS} -V -M jespinoz@jcvi.org -m e "source activate VEBA-classify_env && python ~/Algorithms/Pipelines/veba_pipeline/src/classify-prokaryotic.py -i $INPUT -p ${N_JOBS} -o veba_output/classify/prokaryotic" + + + diff --git a/walkthroughs/commands/SunGridEngine/cmd_classify_viral.sh b/walkthroughs/commands/SunGridEngine/cmd_classify_viral.sh new file mode 100644 index 0000000..700fab3 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_classify_viral.sh @@ -0,0 +1,6 @@ +CODE=0714 +N=classify-viral +rm -rf logs/${N}.* +qsub -M jespinoz@jcvi.org -m e -l centos7 -N ${N} -o logs/${N}.o -e logs/${N}.e -cwd -P ${CODE} "source activate VEBA-classify_env && python ~/Algorithms/Pipelines/veba_pipeline/src/classify-viral.py -i veba_output/binning/viral -o veba_output/classify/viral" + + diff --git a/walkthroughs/commands/SunGridEngine/cmd_cluster.sh b/walkthroughs/commands/SunGridEngine/cmd_cluster.sh new file mode 100644 index 0000000..5f2f0fe --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_cluster.sh @@ -0,0 +1,11 @@ +N_JOBS=8 +CODE=0714 +#for NAME in prokaryotic eukaryotic viral; do +for NAME in eukaryotic; do + N=cluster-50_${NAME} + rm -f logs/${N}.* + PREFIX_CHAR=$(echo $NAME | cut -c1) + qsub -l himem7 -N ${N} -o logs/${N}.o -e logs/${N}.e -cwd -P ${CODE} -pe threaded ${N_JOBS} -V -M jespinoz@jcvi.org -m e "source activate VEBA-cluster_env && python ~/Algorithms/Pipelines/veba_pipeline/src/cluster.py -i completeness_50/${NAME}_scaffolds_to_bins.tsv -m completeness_50/${NAME}_genomes.list -a completeness_50/${NAME}_proteins.list -o veba_output/cluster/completeness_50/${NAME} -p ${N_JOBS} --cluster_prefix ${PREFIX_CHAR^^}SLC" + done + + diff --git a/walkthroughs/commands/SunGridEngine/cmd_index.sh b/walkthroughs/commands/SunGridEngine/cmd_index.sh new file mode 100644 index 0000000..c2b9cc8 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_index.sh @@ -0,0 +1,9 @@ +N_JOBS=16 +N=index-global +rm -f logs/${N}.* +#qsub -N ${N} -o logs/${N}.o -e logs/${N}.e -l centos7 -cwd -P 0714 -pe threaded ${N_JOBS} -M jespinoz@jcvi.org -m e "source activate VEBA-mapping_env && python ~/Algorithms/Pipelines/veba_pipeline/src/index.py -r genomes.list -g gene_models.list -o veba_output/index/global/ -p ${N_JOBS}" + +N=index-local +rm -f logs/${N}.* +qsub -N ${N} -o logs/${N}.o -e logs/${N}.e -l centos7 -cwd -P 0714 -pe threaded ${N_JOBS} -M jespinoz@jcvi.org -m e "source activate VEBA-mapping_env && python ~/Algorithms/Pipelines/veba_pipeline/src/index.py -r sample_to_genome.tsv -g sample_to_gff.tsv -o veba_output/index/local/ -p ${N_JOBS}" + diff --git a/walkthroughs/commands/SunGridEngine/cmd_mapping_global.sh b/walkthroughs/commands/SunGridEngine/cmd_mapping_global.sh new file mode 100644 index 0000000..485d113 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_mapping_global.sh @@ -0,0 +1,16 @@ +CODE=0714 +N_JOBS=4 + +mkdir -p logs +mkdir -p ${OUT_DIR} + +for ID in $(cat identifiers.list); + do N="mapping-global__${ID}"; + rm -f logs/${N}.* + R1=veba_output/preprocess/${ID}/output/cleaned_1.fastq.gz + R2=veba_output/preprocess/${ID}/output/cleaned_2.fastq.gz + INDEX=veba_output/index/global/output/ + OUT_DIR=veba_output/mapping/global/${ID} + CMD="source activate VEBA-mapping_env && python ~/Algorithms/Pipelines/veba_pipeline/src/mapping.py -1 ${R1} -2 ${R2} -n ${ID} -o ${OUT_DIR} -p ${N_JOBS} -x ${INDEX}" + qsub -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" + done diff --git a/walkthroughs/commands/SunGridEngine/cmd_mapping_local.sh b/walkthroughs/commands/SunGridEngine/cmd_mapping_local.sh new file mode 100644 index 0000000..641a8c3 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_mapping_local.sh @@ -0,0 +1,16 @@ +CODE=0714 +N_JOBS=4 + +mkdir -p logs +mkdir -p ${OUT_DIR} + +for ID in $(cat identifiers.list); + do N="mapping-local__${ID}"; + rm -f logs/${N}.* + R1=veba_output/preprocess/${ID}/output/cleaned_1.fastq.gz + R2=veba_output/preprocess/${ID}/output/cleaned_2.fastq.gz + OUT_DIR=veba_output/mapping/local/${ID} + INDEX=veba_output/index/local/output/${ID} + CMD="source activate VEBA-mapping_env && python ~/Algorithms/Pipelines/veba_pipeline/src/mapping.py -1 ${R1} -2 ${R2} -n ${ID} -o ${OUT_DIR} -p ${N_JOBS} -x ${INDEX}" + qsub -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" + done diff --git a/walkthroughs/commands/SunGridEngine/cmd_pipline_binning.sh b/walkthroughs/commands/SunGridEngine/cmd_pipline_binning.sh new file mode 100644 index 0000000..1649910 --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_pipline_binning.sh @@ -0,0 +1,12 @@ +ID=$1 +N_JOBS=$2 + +FASTA=veba_output/assembly/${ID}/intermediate/1__assembly/scaffolds.fasta +BAM=veba_output/assembly/${ID}/intermediate/2__alignment/mapped.sorted.bam +source activate VEBA-binning-prokaryotic_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-prokaryotic.py -f ${FASTA} -b ${BAM} -n ${ID} -p ${N_JOBS} -m 1500 -I 10 + +UNBINNED=veba_output/binning/prokaryotic/${ID}/output/unbinned.fasta +source activate VEBA-binning-eukaryotic_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-eukaryotic.py -f ${UNBINNED} -b ${BAM} -n ${ID} -p ${N_JOBS} + +UNBINNED=veba_output/binning/eukaryotic/${ID}/output/unbinned.fasta +source activate VEBA-binning-viral_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-eukaryotic.py -f ${UNBINNED} -n ${ID} -p ${N_JOBS} diff --git a/walkthroughs/commands/SunGridEngine/cmd_preprocess.sh b/walkthroughs/commands/SunGridEngine/cmd_preprocess.sh new file mode 100644 index 0000000..08b278f --- /dev/null +++ b/walkthroughs/commands/SunGridEngine/cmd_preprocess.sh @@ -0,0 +1,13 @@ + +N_JOBS=4 +DB_HUMAN=/usr/local/scratch/CORE/jespinoz/db/genomes/human/GRCh38.p13/GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index +DB_RIBO=/usr/local/scratch/CORE/jespinoz/db/bbtools/ribokmers.fa.gz +for ID in $(cat identifiers.list); + do echo $ID; + R1=Fastq/${ID}_1.fastq.gz + R2=Fastq/${ID}_2.fastq.gz + N=preprocessing__${ID} + rm -f logs/${N}.* + qsub -l himem7 -o logs/${N}.o -e logs/${N}.e -cwd -P 0714 -N ${N} -j y -pe threaded ${N_JOBS} "source activate VEBA-preprocess_env && python ~/Algorithms/Pipelines/veba_pipeline/src/preprocess.py -n ${ID} -1 ${R1} -2 ${R2} -p ${N_JOBS} -x ${DB_HUMAN} -k ${DB_RIBO}" + done + diff --git a/walkthroughs/end-to-end_metagenomics.md b/walkthroughs/end-to-end_metagenomics.md index fe038c8..7fc5f41 100644 --- a/walkthroughs/end-to-end_metagenomics.md +++ b/walkthroughs/end-to-end_metagenomics.md @@ -119,16 +119,17 @@ for ID in $(cat identifiers.list); * binned.list - List of binned contigs * bins.list - List of MAG identifiers -* quality_summary.filtered.tsv - Filtered CheckV output +* checkv_results.filtered.tsv - Filtered CheckV output * featurecounts.orfs.tsv.gz - ORF-level counts table (If --bam file is provided) * genome_statistics.tsv - Genome assembly statistics +* gene_statistics.cds.tsv - Gene sequence statistics (CDS) * genomes/ - MAG subdirectory -* genomes/\*.fa - MAG assembly fasta -* genomes/\*.faa - MAG protein fasta -* genomes/\*.ffn - MAG CDS fasta -* genomes/\*.gff - MAG gene models +* genomes/[id_genome].fa - MAG assembly fasta +* genomes/[id_genome].faa - MAG protein fasta +* genomes/[id_genome].ffn - MAG CDS fasta +* genomes/[id_genome].gff - MAG gene models * genomes/identifier_mapping.tsv - Identifier mapping between [id_orf, id_contig, id_mag] -* scaffolds_to_bins.tsv - Identifier mapping between [id_contig, id_mag] +* scaffolds\_to\_bins.tsv - Identifier mapping between [id_contig, id_mag] * unbinned.fasta - Fasta of unbinned contigs that have passed length thresholding * unbinned.list - List of unbinned contigs @@ -171,16 +172,20 @@ for ID in $(cat identifiers.list); do * binned.list - List of binned contigs * bins.list - List of MAG identifiers -* checkm_output.filtered.tsv - Filtered CheckM output +* checkm2_results.filtered.tsv - Filtered CheckM2 output * featurecounts.orfs.tsv.gz - ORF-level counts table * genome_statistics.tsv - Genome assembly statistics +* gene_statistics.cds.tsv - Gene sequence statistics (CDS) +* gene_statistics.rRNA.tsv - Gene sequence statistics (rRNA) +* gene_statistics.tRNA.tsv - Gene sequence statistics (tRNA) * genomes/ - MAG subdirectory -* genomes/\*.fa - MAG assembly fasta -* genomes/\*.faa - MAG protein fasta -* genomes/\*.ffn - MAG CDS fasta -* genomes/\*.gff - MAG gene models -* genomes/identifier\_mapping.tsv - Identifier mapping between [id\_orf, id\_contig, id\_mag] -* gtdbtk\_output.filtered.tsv - Filtered GTDBTk output +* genomes/[id_genome].fa - MAG assembly fasta +* genomes/[id_genome].faa - MAG protein fasta +* genomes/[id_genome].ffn - MAG CDS fasta +* genomes/[id_genome].gff - MAG gene models for assembly, CDS, rRNA, and tRNA +* genomes/[id_genome].rRNA - MAG rRNA fasta +* genomes/[id_genome].tRNA - MAG tRNA fasta +* genomes/identifier_mapping.tsv - Identifier mapping between [id_orf, id_contig, id_mag] * scaffolds_to_bins.tsv - Identifier mapping between [id_contig, id_mag] * unbinned.fasta - Fasta of unbinned contigs that have passed length thresholding * unbinned.list - List of unbinned contigs @@ -218,13 +223,19 @@ for ID in $(cat identifiers.list); do * busco_results.filtered.tsv - Filtered BUSCO output * featurecounts.orfs.tsv.gz - ORF-level counts table * genome_statistics.tsv - Genome assembly statistics +* gene_statistics.cds.tsv - Gene sequence statistics (CDS) +* gene_statistics.rRNA.tsv - Gene sequence statistics (rRNA) +* gene_statistics.tRNA.tsv - Gene sequence statistics (tRNA) * genomes/ - MAG subdirectory -* genomes/\*.fa - MAG assembly fasta -* genomes/\*.faa - MAG protein fasta -* genomes/\*.ffn - MAG CDS fasta -* genomes/\*.gff - MAG gene models -* genomes/identifier_mapping.tsv - Identifier mapping between [id_orf, id_contig, id_mag] -* identifier_mapping.metaeuk.tsv - Identifier mapping between original MetaEuk identifiers and modified identifiers. Includes fully parsed MetaEuk identifiers. +* genomes/[id_genome].fa - MAG assembly fasta +* genomes/[id_genome].faa - MAG protein fasta +* genomes/[id_genome].ffn - MAG CDS fasta +* genomes/[id_genome].gff - MAG gene models for assembly, CDS, rRNA, and tRNA +* genomes/[id_genome].rRNA - MAG rRNA fasta +* genomes/[id_genome].tRNA - MAG tRNA fasta +* genomes/[id_genome].seqtype.tsv - Identifier mapping between [id_contig, sequence_type] {nuclear, mitochondrion, plastid} +* genomes/identifier\_mapping.tsv - Identifier mapping between [id_orf, id_contig, id_mag] +* identifier\_mapping.metaeuk.tsv - Identifier mapping between original MetaEuk identifiers and modified identifiers. Includes fully parsed MetaEuk identifiers. * scaffolds_to_bins.tsv - Identifier mapping between [id_contig, id_mag] * unbinned.fasta - Fasta of unbinned contigs that have passed length thresholding * unbinned.list - List of unbinned contigs @@ -408,15 +419,21 @@ CMD="source activate VEBA-cluster_env && cluster.py -i veba_output/misc/genomes_ `veba_output/cluster/local` -* genome\_clusters.tsv - Table with genome cluster, number of components, number of samples of origin, component identifiers, and sample identifiers of origin. -* identifier\_mapping.genomes.tsv - Identifier mapping between genome, organism-type, sample of origin, id\_genome\_cluster, and number of proteins -* identifier\_mapping.proteins.tsv - Identifier mapping between id\_protein, organism-type, id\_genome, sample of origin, id\_genome\_cluster, and id\_protein\_cluster -* identifier\_mapping.scaffolds.tsv - Identifier mapping between id_scaffold, organism-type, id\_genome, sample of origin, and id\_genome\_cluster -* mags\_to\_slcs.tsv - Identifier mapping between MAGs and genome clusters [id\_genome, id\_genome\_cluster] -* feature\_compression\_ratios.tsv - Genomic and functional feature compression ratios (FCR) for each domain -* protein\_clusters.tsv - Table with protein cluster, number of components, number of samples of origin, component identifiers, and sample identifiers of origin. -* proteins\_to\_orthogroups.tsv - Identifier mapping between id\_protein and id\_protein\_cluster -* scaffolds\_to\_slcs.tsv - Identifier mapping between id_scaffold and id\_genome\_cluster +* global/feature\_compression\_ratios.tsv - Feature compression ratios for each domain +* global/genome\_clusters.tsv - Machine-readable table for genome clusters `[id_genome_cluster, number_of_components, number_of_samples_of_origin, components, samples_of_origin]` +* global/identifier\_mapping.genomes.tsv - Identifier mapping for genomes `[id_genome, organism_type, sample_of_origin, id_genome_cluster, number_of_proteins, number_of_singleton_protein_clusters, ratio_of_protein_cluster_are_singletons]` +* global/identifier\_mapping.proteins.tsv - Identifier mapping for proteins `[id_protein, organism_type, id_genome, sample_of_origin, id_genome_cluster, id_protein_cluster]` +* global/identifier\_mapping.scaffolds.tsv - Identifier mapping for contigs `[id_scaffold, organism_type, id_genome, sample_of_origin, id_genome_cluster]` +* global/mags\_to\_slcs.tsv +* global/protein\_clusters.tsv - Machine-readable table for protein clusters `[id_protein_cluster, number_of_components, number_of_samples_of_origin, components, samples_of_origin]` +* global/proteins\_to\_orthogroups.tsv - Identifier mapping between proteins and protein clusters `[id_protein, id_protein-cluster]` +* global/representative\_sequences.faa - Protein sequences for cluster representatives. Header follows the following format: `id_protein-cluster id_original_protein` +* global/scaffolds\_to\_mags.tsv - Identifier mapping between contigs and genomes `[id_contig, id_genome]` +* global/scaffolds\_to\_slcs.tsv - Identifier mapping between contigs and genome clusters `[id_contig, id_genome-cluster]` +* global/pangenome_tables/*.tsv.gz - Pangenome tables for each SLC with prevalence values +* global/serialization/*.dict.pkl - Python dictionaries for clusters +* global/serialization/*.networkx_graph.pkl - NetworkX graphs for clusters +* local/* - If `--no_local_clustering` is not selected then all of the files are generated for local clustering #### 10. Classify viral genomes diff --git a/walkthroughs/recovering_viruses_from_metatranscriptomics.md b/walkthroughs/recovering_viruses_from_metatranscriptomics.md index 5606147..9890813 100644 --- a/walkthroughs/recovering_viruses_from_metatranscriptomics.md +++ b/walkthroughs/recovering_viruses_from_metatranscriptomics.md @@ -78,7 +78,7 @@ The main one we need is `transcripts.fasta` which we will use for binning. `NODE_1_length_117535_cov_30.642667_g0_i0` -Where `g0` refers to the predicted gene and `i0` refers to the isoform transcript for `g0`. Node, length, and coverage refer to the backend node in the assembly graph, transcript length, and transcript coverage, respectively. This can be useful for tools like [*TransDecoder*](https://github.com/TransDecoder/TransDecoder) that take a `--gene_trans_map` argument (TransDecoder.LongOrfs) and a `--single_best_only` (TransDecoder.Predict) as we implemented when investigating [coral holobiomes](https://www.science.org/doi/10.1126/sciadv.abg3088). Although, *VEBA* has [a wrapper script for *TransDecoder* and automated homology searches](https://github.com/jolespin/veba/blob/main/src/scripts/transdecoder_wrapper.py), this tutorial will use *Prodigal* instead. +Where `g0` refers to the predicted gene and `i0` refers to the isoform transcript for `g0`. Node, length, and coverage refer to the backend node in the assembly graph, transcript length, and transcript coverage, respectively. This can be useful for tools like [*TransDecoder*](https://github.com/TransDecoder/TransDecoder) that take a `--gene_trans_map` argument (TransDecoder.LongOrfs) and a `--single_best_only` (TransDecoder.Predict) as we implemented when investigating [coral holobiomes](https://www.science.org/doi/10.1126/sciadv.abg3088). Although, *VEBA* has [a wrapper script for *TransDecoder* and automated homology searches](https://github.com/jolespin/veba/blob/main/src/scripts/transdecoder_wrapper.py), this tutorial will use *Prodigal-GV* instead. #### 3. Recover viruses from metatranscriptomic assemblies We use a similar approach to the metagenomics with *geNomad* and *CheckV* but using the assembled transcripts instead. Again, the criteria for high-quality viral genomes are described by the [*CheckV* author](https://scholar.google.com/citations?user=gmKnjNQAAAAJ&hl=en) [here in this Bitbucket Issue (#38)](https://bitbucket.org/berkeleylab/checkv/issues/38/recommended-cutoffs-for-analyzing-checkv). @@ -103,20 +103,21 @@ for ID in $(cat identifiers.list); **The following output files will produced for each sample:** -* binned.list - List of binned transcripts +* binned.list - List of binned contigs * bins.list - List of MAG identifiers -* quality_summary.filtered.tsv - Filtered CheckV output +* checkv_results.filtered.tsv - Filtered CheckV output * featurecounts.orfs.tsv.gz - ORF-level counts table (If --bam file is provided) * genome_statistics.tsv - Genome assembly statistics +* gene_statistics.cds.tsv - Gene sequence statistics (CDS) * genomes/ - MAG subdirectory -* genomes/\*.fa - MAG assembly fasta -* genomes/\*.faa - MAG protein fasta -* genomes/\*.ffn - MAG CDS fasta -* genomes/\*.gff - MAG gene models -* genomes/identifier_mapping.tsv - Identifier mapping between [id_orf, id_transcript, id_mag] -* scaffolds\_to\_bins.tsv - Identifier mapping between [id_transcript, id_mag] -* unbinned.fasta - Fasta of unbinned transcripts that have passed length thresholding -* unbinned.list - List of unbinned transcripts +* genomes/[id_genome].fa - MAG assembly fasta +* genomes/[id_genome].faa - MAG protein fasta +* genomes/[id_genome].ffn - MAG CDS fasta +* genomes/[id_genome].gff - MAG gene models +* genomes/identifier_mapping.tsv - Identifier mapping between [id_orf, id_contig, id_mag] +* scaffolds\_to\_bins.tsv - Identifier mapping between [id_contig, id_mag] +* unbinned.fasta - Fasta of unbinned contigs that have passed length thresholding +* unbinned.list - List of unbinned contigs #### 4. Cluster genomes and proteins @@ -151,15 +152,21 @@ CMD="source activate VEBA-cluster_env && cluster.py -i veba_output/misc/genomes_ `veba_output/cluster/local` -* genome\_clusters.tsv - Table with genome cluster, number of components, number of samples of origin, component identifiers, and sample identifiers of origin. -* identifier\_mapping.genomes.tsv - Identifier mapping between genome, organism-type, sample of origin, id\_genome\_cluster, and number of proteins -* identifier\_mapping.proteins.tsv - Identifier mapping between id\_protein, organism-type, id\_genome, sample of origin, id\_genome\_cluster, and id\_protein\_cluster -* identifier\_mapping.scaffolds.tsv - Identifier mapping between id_scaffold, organism-type, id\_genome, sample of origin, and id\_genome\_cluster -* mags\_to\_slcs.tsv - Identifier mapping between MAGs and genome clusters [id\_genome, id\_genome\_cluster] -* feature\_compression\_ratios.tsv - Genomic and functional feature compression ratios (FCR) for each domain -* protein\_clusters.tsv - Table with protein cluster, number of components, number of samples of origin, component identifiers, and sample identifiers of origin. -* proteins\_to\_orthogroups.tsv - Identifier mapping between id\_protein and id\_protein\_cluster -* scaffolds\_to\_slcs.tsv - Identifier mapping between id_scaffold and id\_genome\_cluster +* global/feature\_compression\_ratios.tsv - Feature compression ratios for each domain +* global/genome\_clusters.tsv - Machine-readable table for genome clusters `[id_genome_cluster, number_of_components, number_of_samples_of_origin, components, samples_of_origin]` +* global/identifier\_mapping.genomes.tsv - Identifier mapping for genomes `[id_genome, organism_type, sample_of_origin, id_genome_cluster, number_of_proteins, number_of_singleton_protein_clusters, ratio_of_protein_cluster_are_singletons]` +* global/identifier\_mapping.proteins.tsv - Identifier mapping for proteins `[id_protein, organism_type, id_genome, sample_of_origin, id_genome_cluster, id_protein_cluster]` +* global/identifier\_mapping.scaffolds.tsv - Identifier mapping for contigs `[id_scaffold, organism_type, id_genome, sample_of_origin, id_genome_cluster]` +* global/mags\_to\_slcs.tsv +* global/protein\_clusters.tsv - Machine-readable table for protein clusters `[id_protein_cluster, number_of_components, number_of_samples_of_origin, components, samples_of_origin]` +* global/proteins\_to\_orthogroups.tsv - Identifier mapping between proteins and protein clusters `[id_protein, id_protein-cluster]` +* global/representative\_sequences.faa - Protein sequences for cluster representatives. Header follows the following format: `id_protein-cluster id_original_protein` +* global/scaffolds\_to\_mags.tsv - Identifier mapping between contigs and genomes `[id_contig, id_genome]` +* global/scaffolds\_to\_slcs.tsv - Identifier mapping between contigs and genome clusters `[id_contig, id_genome-cluster]` +* global/pangenome_tables/*.tsv.gz - Pangenome tables for each SLC with prevalence values +* global/serialization/*.dict.pkl - Python dictionaries for clusters +* global/serialization/*.networkx_graph.pkl - NetworkX graphs for clusters +* local/* - If `--no_local_clustering` is not selected then all of the files are generated for local clustering From eb9f8cf85df0cea044b3658a8de2e7b0e320b133 Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Tue, 11 Jul 2023 17:17:16 -0700 Subject: [PATCH 12/16] Removing commands --- .../commands/SunGridEngine/cmd_annotate.sh | 23 ------------------- .../commands/SunGridEngine/cmd_assembly.sh | 15 ------------ .../SunGridEngine/cmd_binning_eukaryotic.sh | 18 --------------- .../SunGridEngine/cmd_binning_prokaryotic.sh | 13 ----------- .../SunGridEngine/cmd_binning_viral.sh | 10 -------- .../SunGridEngine/cmd_classify_eukaryotic.sh | 6 ----- .../SunGridEngine/cmd_classify_prokaryotic.sh | 11 --------- .../SunGridEngine/cmd_classify_viral.sh | 6 ----- .../commands/SunGridEngine/cmd_cluster.sh | 11 --------- .../commands/SunGridEngine/cmd_index.sh | 9 -------- .../SunGridEngine/cmd_mapping_global.sh | 16 ------------- .../SunGridEngine/cmd_mapping_local.sh | 16 ------------- .../SunGridEngine/cmd_pipline_binning.sh | 12 ---------- .../commands/SunGridEngine/cmd_preprocess.sh | 13 ----------- 14 files changed, 179 deletions(-) delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_annotate.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_assembly.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_binning_eukaryotic.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_binning_prokaryotic.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_binning_viral.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_classify_eukaryotic.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_classify_prokaryotic.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_classify_viral.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_cluster.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_index.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_mapping_global.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_mapping_local.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_pipline_binning.sh delete mode 100644 walkthroughs/commands/SunGridEngine/cmd_preprocess.sh diff --git a/walkthroughs/commands/SunGridEngine/cmd_annotate.sh b/walkthroughs/commands/SunGridEngine/cmd_annotate.sh deleted file mode 100644 index bb4bac2..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_annotate.sh +++ /dev/null @@ -1,23 +0,0 @@ -CODE=0714 -N_JOBS=1 - -#for ID in $(cat identifiers.list); -# do N="annotate__${ID}"; -# rm -f logs/${N}.* -# OUT_DIR="veba_output/annotation/${ID}" -# mkdir -p ${OUT_DIR} -# #PROTEIN=veba_output/binning/prokaryotic/${ID}/intermediate/2__prodigal/gene_models.faa -# PROTEIN=veba_output/binning/prokaryotic/${ID}/output/genomes/ -# MAPPING=veba_output/binning/prokaryotic/${ID}/output/genomes/identifier_mapping.tsv -# CMD="source activate VEBA-annotate_env && python ~/Algorithms/Pipelines/veba_pipeline/src/annotate.py -a ${PROTEIN} -o ${OUT_DIR} -p ${N_JOBS} -i ${MAPPING}" -# qsub -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" -# done - -N="annotate" -rm -f logs/${N}.* -OUT_DIR="veba_output/annotation/" -mkdir -p ${OUT_DIR} -PROTEIN=all_proteins.faa -MAPPING=identifier_mapping.tsv -CMD="source activate VEBA-annotate_env && python ~/Algorithms/Pipelines/veba_pipeline/src/annotate.py -a ${PROTEIN} -o ${OUT_DIR} -p ${N_JOBS} -i ${MAPPING}" -qsub -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" diff --git a/walkthroughs/commands/SunGridEngine/cmd_assembly.sh b/walkthroughs/commands/SunGridEngine/cmd_assembly.sh deleted file mode 100644 index ad1ea73..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_assembly.sh +++ /dev/null @@ -1,15 +0,0 @@ -CODE=0714 -N_JOBS=8 -OUT_DIR=veba_output/assembly - -mkdir -p logs -mkdir -p ${OUT_DIR} - -for ID in $(cat identifiers.list); - do N="assembly__${ID}"; - rm -f logs/${N}.* - R1=veba_output/preprocess/${ID}/output/cleaned_1.fastq.gz - R2=veba_output/preprocess/${ID}/output/cleaned_2.fastq.gz - CMD="source activate VEBA-assembly_env && python ~/Algorithms/Pipelines/veba_pipeline/src/assembly.py -1 ${R1} -2 ${R2} -n ${ID} -o ${OUT_DIR} -p ${N_JOBS} -m 1024" - qsub -l himem7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" - done diff --git a/walkthroughs/commands/SunGridEngine/cmd_binning_eukaryotic.sh b/walkthroughs/commands/SunGridEngine/cmd_binning_eukaryotic.sh deleted file mode 100644 index cfa9917..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_binning_eukaryotic.sh +++ /dev/null @@ -1,18 +0,0 @@ -CODE=0714 -N_JOBS=4 -#DB=/usr/local/scratch/CORE/jespinoz/db/veba/eukaryotic_classification/eukaryotic -DB=/usr/local/scratch/CORE/jespinoz/db/veba/v1.0/Classify/Eukaryotic/eukaryotic - -for ID in $(cat identifiers.list); -#for ID in $(cat identifiers.rerun.list); -#for ID in SRR17458614 SRR17458615 SRR17458630 SRR17458638; -#for ID in SRR17458613; - do N="binning-eukaryotic-metabat2__${ID}"; - rm -f logs/${N}.* - #rm -rf veba_output/binning/eukaryotic/${ID} - #FASTA=veba_output/assembly/${ID}/output/scaffolds.fasta - FASTA=veba_output/binning/prokaryotic/${ID}/output/unbinned.fasta - BAM=veba_output/assembly/${ID}/output/mapped.sorted.bam - CMD="source activate VEBA-binning-eukaryotic_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-eukaryotic.py -f ${FASTA} -b ${BAM} -n ${ID} -p ${N_JOBS} -m 1500 -a metabat2 --metaeuk_database $DB -o veba_output/binning/eukaryotic" - qsub -V -l himem7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" - done diff --git a/walkthroughs/commands/SunGridEngine/cmd_binning_prokaryotic.sh b/walkthroughs/commands/SunGridEngine/cmd_binning_prokaryotic.sh deleted file mode 100644 index 028f1a5..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_binning_prokaryotic.sh +++ /dev/null @@ -1,13 +0,0 @@ -CODE=0714 -N_JOBS=16 - -for ID in $(cat identifiers.list); - do N="binning-prokaryotic__${ID}"; - rm -f logs/${N}.* - #FASTA=veba_output/assembly/${ID}/output/scaffolds.fasta - #BAM=veba_output/assembly/${ID}/output/mapped.sorted.bam - FASTA=veba_output/assembly/${ID}/intermediate/1__assembly/scaffolds.fasta - BAM=veba_output/assembly/${ID}/intermediate/2__alignment/mapped.sorted.bam - CMD="source activate VEBA-binning-prokaryotic_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-prokaryotic.py -f ${FASTA} -b ${BAM} -n ${ID} -p ${N_JOBS} -m 1500 -I 10" - qsub -V -l himem7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" - done diff --git a/walkthroughs/commands/SunGridEngine/cmd_binning_viral.sh b/walkthroughs/commands/SunGridEngine/cmd_binning_viral.sh deleted file mode 100644 index 227caf4..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_binning_viral.sh +++ /dev/null @@ -1,10 +0,0 @@ -CODE=0714 -N_JOBS=4 - -for ID in $(cat identifiers.list); - do N="binning-viral__${ID}"; - rm -f logs/${N}.* - FASTA=veba_output/binning/eukaryotic/${ID}/output/unbinned.fasta - CMD="source activate VEBA-binning_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-viral.py -f ${FASTA} -n ${ID} -p ${N_JOBS} -m 1500 -o veba_output/binning/viral" - qsub -V -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" - done diff --git a/walkthroughs/commands/SunGridEngine/cmd_classify_eukaryotic.sh b/walkthroughs/commands/SunGridEngine/cmd_classify_eukaryotic.sh deleted file mode 100644 index 99332f2..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_classify_eukaryotic.sh +++ /dev/null @@ -1,6 +0,0 @@ -CODE=0714 -N=classify-eukaryotic -rm -rf logs/${N}.* -qsub -M jespinoz@jcvi.org -m e -l centos7 -N ${N} -o logs/${N}.o -e logs/${N}.e -cwd -P ${CODE} "source activate VEBA-classify_env && python ~/Algorithms/Pipelines/veba_pipeline/src/classify-eukaryotic.py -i veba_output/binning/eukaryotic/ -o veba_output/classify/eukaryotic -l 1.0" - - diff --git a/walkthroughs/commands/SunGridEngine/cmd_classify_prokaryotic.sh b/walkthroughs/commands/SunGridEngine/cmd_classify_prokaryotic.sh deleted file mode 100644 index 07ef2e8..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_classify_prokaryotic.sh +++ /dev/null @@ -1,11 +0,0 @@ -N_JOBS=4 -CODE=0714 -N=classify-prokaryotic -rm -f logs/${N}.* -#GENOMES=Output/Prokaryotic -INPUT=veba_output/binning/prokaryotic -#CLUSTERS=veba_output/cluster/output/clusters.tsv -qsub -l centos7 -N ${N} -o logs/${N}.o -e logs/${N}.e -cwd -P ${CODE} -pe threaded ${N_JOBS} -V -M jespinoz@jcvi.org -m e "source activate VEBA-classify_env && python ~/Algorithms/Pipelines/veba_pipeline/src/classify-prokaryotic.py -i $INPUT -p ${N_JOBS} -o veba_output/classify/prokaryotic" - - - diff --git a/walkthroughs/commands/SunGridEngine/cmd_classify_viral.sh b/walkthroughs/commands/SunGridEngine/cmd_classify_viral.sh deleted file mode 100644 index 700fab3..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_classify_viral.sh +++ /dev/null @@ -1,6 +0,0 @@ -CODE=0714 -N=classify-viral -rm -rf logs/${N}.* -qsub -M jespinoz@jcvi.org -m e -l centos7 -N ${N} -o logs/${N}.o -e logs/${N}.e -cwd -P ${CODE} "source activate VEBA-classify_env && python ~/Algorithms/Pipelines/veba_pipeline/src/classify-viral.py -i veba_output/binning/viral -o veba_output/classify/viral" - - diff --git a/walkthroughs/commands/SunGridEngine/cmd_cluster.sh b/walkthroughs/commands/SunGridEngine/cmd_cluster.sh deleted file mode 100644 index 5f2f0fe..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_cluster.sh +++ /dev/null @@ -1,11 +0,0 @@ -N_JOBS=8 -CODE=0714 -#for NAME in prokaryotic eukaryotic viral; do -for NAME in eukaryotic; do - N=cluster-50_${NAME} - rm -f logs/${N}.* - PREFIX_CHAR=$(echo $NAME | cut -c1) - qsub -l himem7 -N ${N} -o logs/${N}.o -e logs/${N}.e -cwd -P ${CODE} -pe threaded ${N_JOBS} -V -M jespinoz@jcvi.org -m e "source activate VEBA-cluster_env && python ~/Algorithms/Pipelines/veba_pipeline/src/cluster.py -i completeness_50/${NAME}_scaffolds_to_bins.tsv -m completeness_50/${NAME}_genomes.list -a completeness_50/${NAME}_proteins.list -o veba_output/cluster/completeness_50/${NAME} -p ${N_JOBS} --cluster_prefix ${PREFIX_CHAR^^}SLC" - done - - diff --git a/walkthroughs/commands/SunGridEngine/cmd_index.sh b/walkthroughs/commands/SunGridEngine/cmd_index.sh deleted file mode 100644 index c2b9cc8..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_index.sh +++ /dev/null @@ -1,9 +0,0 @@ -N_JOBS=16 -N=index-global -rm -f logs/${N}.* -#qsub -N ${N} -o logs/${N}.o -e logs/${N}.e -l centos7 -cwd -P 0714 -pe threaded ${N_JOBS} -M jespinoz@jcvi.org -m e "source activate VEBA-mapping_env && python ~/Algorithms/Pipelines/veba_pipeline/src/index.py -r genomes.list -g gene_models.list -o veba_output/index/global/ -p ${N_JOBS}" - -N=index-local -rm -f logs/${N}.* -qsub -N ${N} -o logs/${N}.o -e logs/${N}.e -l centos7 -cwd -P 0714 -pe threaded ${N_JOBS} -M jespinoz@jcvi.org -m e "source activate VEBA-mapping_env && python ~/Algorithms/Pipelines/veba_pipeline/src/index.py -r sample_to_genome.tsv -g sample_to_gff.tsv -o veba_output/index/local/ -p ${N_JOBS}" - diff --git a/walkthroughs/commands/SunGridEngine/cmd_mapping_global.sh b/walkthroughs/commands/SunGridEngine/cmd_mapping_global.sh deleted file mode 100644 index 485d113..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_mapping_global.sh +++ /dev/null @@ -1,16 +0,0 @@ -CODE=0714 -N_JOBS=4 - -mkdir -p logs -mkdir -p ${OUT_DIR} - -for ID in $(cat identifiers.list); - do N="mapping-global__${ID}"; - rm -f logs/${N}.* - R1=veba_output/preprocess/${ID}/output/cleaned_1.fastq.gz - R2=veba_output/preprocess/${ID}/output/cleaned_2.fastq.gz - INDEX=veba_output/index/global/output/ - OUT_DIR=veba_output/mapping/global/${ID} - CMD="source activate VEBA-mapping_env && python ~/Algorithms/Pipelines/veba_pipeline/src/mapping.py -1 ${R1} -2 ${R2} -n ${ID} -o ${OUT_DIR} -p ${N_JOBS} -x ${INDEX}" - qsub -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" - done diff --git a/walkthroughs/commands/SunGridEngine/cmd_mapping_local.sh b/walkthroughs/commands/SunGridEngine/cmd_mapping_local.sh deleted file mode 100644 index 641a8c3..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_mapping_local.sh +++ /dev/null @@ -1,16 +0,0 @@ -CODE=0714 -N_JOBS=4 - -mkdir -p logs -mkdir -p ${OUT_DIR} - -for ID in $(cat identifiers.list); - do N="mapping-local__${ID}"; - rm -f logs/${N}.* - R1=veba_output/preprocess/${ID}/output/cleaned_1.fastq.gz - R2=veba_output/preprocess/${ID}/output/cleaned_2.fastq.gz - OUT_DIR=veba_output/mapping/local/${ID} - INDEX=veba_output/index/local/output/${ID} - CMD="source activate VEBA-mapping_env && python ~/Algorithms/Pipelines/veba_pipeline/src/mapping.py -1 ${R1} -2 ${R2} -n ${ID} -o ${OUT_DIR} -p ${N_JOBS} -x ${INDEX}" - qsub -l centos7 -N ${N} -cwd -P ${CODE} -o logs/${N}.o -e logs/${N}.e -pe threaded ${N_JOBS} "${CMD}" - done diff --git a/walkthroughs/commands/SunGridEngine/cmd_pipline_binning.sh b/walkthroughs/commands/SunGridEngine/cmd_pipline_binning.sh deleted file mode 100644 index 1649910..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_pipline_binning.sh +++ /dev/null @@ -1,12 +0,0 @@ -ID=$1 -N_JOBS=$2 - -FASTA=veba_output/assembly/${ID}/intermediate/1__assembly/scaffolds.fasta -BAM=veba_output/assembly/${ID}/intermediate/2__alignment/mapped.sorted.bam -source activate VEBA-binning-prokaryotic_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-prokaryotic.py -f ${FASTA} -b ${BAM} -n ${ID} -p ${N_JOBS} -m 1500 -I 10 - -UNBINNED=veba_output/binning/prokaryotic/${ID}/output/unbinned.fasta -source activate VEBA-binning-eukaryotic_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-eukaryotic.py -f ${UNBINNED} -b ${BAM} -n ${ID} -p ${N_JOBS} - -UNBINNED=veba_output/binning/eukaryotic/${ID}/output/unbinned.fasta -source activate VEBA-binning-viral_env && python ~/Algorithms/Pipelines/veba_pipeline/src/binning-eukaryotic.py -f ${UNBINNED} -n ${ID} -p ${N_JOBS} diff --git a/walkthroughs/commands/SunGridEngine/cmd_preprocess.sh b/walkthroughs/commands/SunGridEngine/cmd_preprocess.sh deleted file mode 100644 index 08b278f..0000000 --- a/walkthroughs/commands/SunGridEngine/cmd_preprocess.sh +++ /dev/null @@ -1,13 +0,0 @@ - -N_JOBS=4 -DB_HUMAN=/usr/local/scratch/CORE/jespinoz/db/genomes/human/GRCh38.p13/GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.bowtie_index -DB_RIBO=/usr/local/scratch/CORE/jespinoz/db/bbtools/ribokmers.fa.gz -for ID in $(cat identifiers.list); - do echo $ID; - R1=Fastq/${ID}_1.fastq.gz - R2=Fastq/${ID}_2.fastq.gz - N=preprocessing__${ID} - rm -f logs/${N}.* - qsub -l himem7 -o logs/${N}.o -e logs/${N}.e -cwd -P 0714 -N ${N} -j y -pe threaded ${N_JOBS} "source activate VEBA-preprocess_env && python ~/Algorithms/Pipelines/veba_pipeline/src/preprocess.py -n ${ID} -1 ${R1} -2 ${R2} -p ${N_JOBS} -x ${DB_HUMAN} -k ${DB_RIBO}" - done - From eba6bc2628a4fd028a6907965d1ddf1ab593e59b Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Tue, 12 Sep 2023 10:56:54 -0700 Subject: [PATCH 13/16] v1.2.1 * [2023.9.11] - Added presets for `MEGAHIT` using the `--megahit_preset` option. #! Need to test * [2023.9.11] - The change for using `--mash_db` with `GTDB-Tk` violated the assumption that all prokaryotic classifications had a `msa_percent` field which caused the cluster-level taxonomy to fail. `compile_prokaryotic_genome_cluster_classification_scores_table.py` fixes this by uses `fastani_ani` as the weight when genomes were classified using ANI and `msa_percent` for everything else. Initial error caused unclassified prokaryotic for all cluster-level classifications. * [2023.9.8] - Fixed small error where empty gff files with an asterisk in the name were created for samples that didn't have any prokaryotic MAGs. * [2023.9.8] - Fixed critical error where descriptions in header were not being removed in ``eukaryota.scaffolds.list` and did not remove eukaryotic scaffolds in `seqkit grep` so `DAS_Tool` output eukaryotic MAGs in `identifier_mapping.tsv` and `__DASTool_scaffolds2bin.no_eukaryota.txt` * [2023.9.5] - Fixed `krona.html` in `biosynthetic.py` which was being created incorrectly from `compile_krona.py` script. * [2023.8.30] - Create `pangenome_core_sequences` in `global_clustering.py` and `local_clustering.py` which writes both protein and CDS sequences for each SLC. Also made default in `cluster.py` to NOT do local clustering switching `--no_local_clustering` to `--local_clustering`. * [2023.8.30] - `pandas.errors.InvalidIndexError: Reindexing only valid with uniquely valued Index objects` in `biosynthetic.py` when `Diamond` finds multiple regions in one hit that matches. Added `--sort_by` and `--ascending` to `concatenate_dataframes.py` along with automatic detection and removal of duplicate indices. Also added `--sort_by bitscore` in `biosynthetic.py`. * [2023.8.28] - Added core pangenome and singleton hits to clustering output * [2023.8.25] - Updated `--megahit_memory` default from 0.9 to 0.99 * [2023.8.16] - Fixed error in `genomad_taxonomy_wrapper.py` where `viral_taxonomy.tsv` should have been `taxonomy.tsv`. #! NEED TO TEST * [2023.7.26] - Fixed minor error in `assembly.py` that was preventing users from using `SPAdes` programs that were not `spades.py`, `metaspades.py`, or `rnaspades.py` that was the result of using an incorrect string formatting. * [2023.7.25] - Updated `bowtie2` in preprocess, assembly, and mapping modules. Updated `fastp` and `fastq_preprocessor` in preprocess module. --- .DS_Store | Bin 0 -> 12292 bytes CHANGELOG.md | 20 +- CITATIONS.md | 86 +- DEPENDENCIES.xlsx | Bin 81877 -> 40189 bytes FAQ.md | 19 +- README.md | 5 +- VERSION | 2 +- install/README.md | 2 +- install/condarc | 9 + install/docker/Dockerfile | 57 +- install/docker/check_containers.sh | 65 ++ ...nments.sh => containerize_environments.sh} | 0 install/docker/deprecated/Dockerfile | 56 ++ install/docker/push_docker_images.sh | 6 + install/environments/VEBA-mapping_env.yml | 44 +- install/environments/VEBA-preprocess_env.yml | 14 +- .../environments/devel/VEBA-amplicon_env.yml | 872 ++++++------------ src/README.md | 36 +- src/assembly.py | 16 +- src/binning-prokaryotic.py | 35 +- src/biosynthetic.py | 3 +- src/classify-prokaryotic.py | 24 +- src/cluster.py | 13 +- src/scripts/.DS_Store | Bin 0 -> 10244 bytes src/scripts/compile_genomes_table.py | 7 +- src/scripts/compile_krona.py | 10 +- ...ome_cluster_classification_scores_table.py | 106 +++ src/scripts/compile_reads_table.py | 8 +- src/scripts/compile_veba_synopsis.py | 42 - src/scripts/concatenate_dataframes.py | 22 +- src/scripts/genomad_taxonomy_wrapper.py | 12 +- src/scripts/global_clustering.py | 74 +- src/scripts/local_clustering.py | 69 +- walkthroughs/README.md | 2 +- walkthroughs/adapting_commands_for_aws.md | 146 +++ walkthroughs/adapting_commands_for_docker.md | 16 +- 36 files changed, 1059 insertions(+), 839 deletions(-) create mode 100644 .DS_Store create mode 100644 install/condarc create mode 100644 install/docker/check_containers.sh rename install/docker/{dockerize_environments.sh => containerize_environments.sh} (100%) create mode 100644 install/docker/deprecated/Dockerfile create mode 100644 install/docker/push_docker_images.sh create mode 100644 src/scripts/.DS_Store create mode 100755 src/scripts/compile_prokaryotic_genome_cluster_classification_scores_table.py delete mode 100755 src/scripts/compile_veba_synopsis.py create mode 100644 walkthroughs/adapting_commands_for_aws.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..620218bf4e72e05a38ab7e59cf643147bd549a3c GIT binary patch literal 12292 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 L&yVcaG5-GtW*Df| literal 0 HcmV?d00001 diff --git a/CHANGELOG.md b/CHANGELOG.md index de4c700..7fd7378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -237,8 +237,13 @@ ________________________________________________________________ #### Path to `v2.0.0`: +**Critical:** + + **Definitely:** +* Script that gets marker genes from clustering results +* Split `download_databases.sh` into `download_databases.sh` (low memory, high threads) and `configure_databases.sh` (high memory, low-to-mid threads). Use `aria2` in parallel instead of `wget`. * `NextFlow` support * Consistent usage of the following terms: 1) dataframe vs. table; 2) protein-cluster vs. orthogroup. * Add support for `FAMSA` in `phylogeny.py` @@ -252,6 +257,8 @@ ________________________________________________________________ **Probably (Yes)?:** +* Run `cmsearch` before `tRNAscan-SE` +* DN/DS from pangeome analysis * Add [iPHoP](https://bitbucket.org/srouxjgi/iphop/src/main/) to `binning-viral.py`. * Add a `metabolic.py` module * Swap [`TransDecoder`](https://github.com/TransDecoder/TransDecoder) for [`TransSuite`](https://github.com/anonconda/TranSuite) @@ -270,7 +277,18 @@ ________________________________________________________________
**Daily Change Log:** - +* [2023.9.11] - Added presets for `MEGAHIT` using the `--megahit_preset` option. +* [2023.9.11] - The change for using `--mash_db` with `GTDB-Tk` violated the assumption that all prokaryotic classifications had a `msa_percent` field which caused the cluster-level taxonomy to fail. `compile_prokaryotic_genome_cluster_classification_scores_table.py` fixes this by uses `fastani_ani` as the weight when genomes were classified using ANI and `msa_percent` for everything else. Initial error caused unclassified prokaryotic for all cluster-level classifications. +* [2023.9.8] - Fixed small error where empty gff files with an asterisk in the name were created for samples that didn't have any prokaryotic MAGs. +* [2023.9.8] - Fixed critical error where descriptions in header were not being removed in ``eukaryota.scaffolds.list` and did not remove eukaryotic scaffolds in `seqkit grep` so `DAS_Tool` output eukaryotic MAGs in `identifier_mapping.tsv` and `__DASTool_scaffolds2bin.no_eukaryota.txt` +* [2023.9.5] - Fixed `krona.html` in `biosynthetic.py` which was being created incorrectly from `compile_krona.py` script. +* [2023.8.30] - Create `pangenome_core_sequences` in `global_clustering.py` and `local_clustering.py` which writes both protein and CDS sequences for each SLC. Also made default in `cluster.py` to NOT do local clustering switching `--no_local_clustering` to `--local_clustering`. +* [2023.8.30] - `pandas.errors.InvalidIndexError: Reindexing only valid with uniquely valued Index objects` in `biosynthetic.py` when `Diamond` finds multiple regions in one hit that matches. Added `--sort_by` and `--ascending` to `concatenate_dataframes.py` along with automatic detection and removal of duplicate indices. Also added `--sort_by bitscore` in `biosynthetic.py`. +* [2023.8.28] - Added core pangenome and singleton hits to clustering output +* [2023.8.25] - Updated `--megahit_memory` default from 0.9 to 0.99 +* [2023.8.16] - Fixed error in `genomad_taxonomy_wrapper.py` where `viral_taxonomy.tsv` should have been `taxonomy.tsv`. #! NEED TO TEST +* [2023.7.26] - Fixed minor error in `assembly.py` that was preventing users from using `SPAdes` programs that were not `spades.py`, `metaspades.py`, or `rnaspades.py` that was the result of using an incorrect string formatting. +* [2023.7.25] - Updated `bowtie2` in preprocess, assembly, and mapping modules. Updated `fastp` and `fastq_preprocessor` in preprocess module. * [2023.7.7] - Added `compile_gff.py` to merge CDS, rRNA, and tRNA GFF files. Used in `binning-prokaryotic.py` and `binning-viral.py`. `binning-eukaryotic.py` uses the source of this in the backend of `filter_busco_results.py`. Includes GC content for contigs and various tags. * [2023.7.6] - Updated `BUSCO v5.3.2 -> v5.4.3` which changes the json output structure and made the appropriate changes in `filter_busco_results.py`. * [2023.7.3] - Added `eukaryotic_gene_modeling_wrapper.py` which 1) splits nuclear, mitochondrial, and plastid genomes; 2) performs gene modeling via `MetaEuk` and `Pyrodigal`; 3) performs rRNA detection via `BARRNAP`; 4) performs tRNA detection via `tRNAscan-SE`; 5) merges processed GFF files; and 5) calculates sequences statistics. diff --git a/CITATIONS.md b/CITATIONS.md index 718b836..51ff3c5 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -1,42 +1,44 @@ -| Dependency | Citation | -|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| antismash | Blin K, Shaw S, Kloosterman AM, Charlop-Powers Z, van Wezel GP, Medema MH, Weber T. antiSMASH 6.0: improving cluster detection and comparison capabilities. Nucleic Acids Res. 2021 Jul 2;49(W1):W29-W35. doi: 10.1093/nar/gkab335. PMID: 33978755; PMCID: PMC8262755. | -| bbtools | https://sourceforge.net/projects/bbmap/ | -| bowtie2 | Langmead B, Salzberg SL. Fast gapped-read alignment with Bowtie 2. Nat Methods. 2012 Mar 4;9(4):357-9. doi: 10.1038/nmeth.1923. PMID: 22388286; PMCID: PMC3322381. | -| busco | Manni M, Berkeley MR, Seppey M, Simão FA, Zdobnov EM. BUSCO Update: Novel and Streamlined Workflows along with Broader and Deeper Phylogenetic Coverage for Scoring of Eukaryotic, Prokaryotic, and Viral Genomes. Mol Biol Evol. 2021 Sep 27;38(10):4647-4654. doi: 10.1093/molbev/msab199. PMID: 34320186; PMCID: PMC8476166. | -| checkm2 | Alex Chklovski, Donovan H. Parks, Ben J. Woodcroft, Gene W. Tyson bioRxiv 2022.07.11.499243; doi: https://doi.org/10.1101/2022.07.11.499243 | -| checkv | Nayfach S, Camargo AP, Schulz F, Eloe-Fadrosh E, Roux S, Kyrpides NC. CheckV assesses the quality and completeness of metagenome-assembled viral genomes. Nat Biotechnol. 2021 May;39(5):578-585. doi: 10.1038/s41587-020-00774-7. Epub 2020 Dec 21. PMID: 33349699; PMCID: PMC8116208. | -| clipkit | Steenwyk JL, Buida TJ 3rd, Li Y, Shen XX, Rokas A. ClipKIT: A multiple sequence alignment trimming software for accurate phylogenomic inference. PLoS Biol. 2020 Dec 2;18(12):e3001007. doi: 10.1371/journal.pbio.3001007. PMID: 33264284; PMCID: PMC7735675. | -| concoct | Alneberg J, Bjarnason BS, de Bruijn I, Schirmer M, Quick J, Ijaz UZ, Lahti L, Loman NJ, Andersson AF, Quince C. Binning metagenomic contigs by coverage and composition. Nat Methods. 2014 Nov;11(11):1144-6. doi: 10.1038/nmeth.3103. Epub 2014 Sep 14. PMID: 25218180. | -| coverm | https://github.com/wwood/CoverM | -| dada2 | Callahan BJ, McMurdie PJ, Rosen MJ, Han AW, Johnson AJ, Holmes SP. DADA2: High-resolution sample inference from Illumina amplicon data. Nat Methods. 2016 Jul;13(7):581-3. doi: 10.1038/nmeth.3869. Epub 2016 May 23. PMID: 27214047; PMCID: PMC4927377. | -| das_tool | Sieber CMK, Probst AJ, Sharrar A, Thomas BC, Hess M, Tringe SG, Banfield JF. Recovery of genomes from metagenomes via a dereplication, aggregation and scoring strategy. Nat Microbiol. 2018 Jul;3(7):836-843. doi: 10.1038/s41564-018-0171-1. Epub 2018 May 28. PMID: 29807988; PMCID: PMC6786971. | -| diamond | Buchfink B, Reuter K, Drost HG. Sensitive protein alignments at tree-of-life scale using DIAMOND. Nat Methods. 2021 Apr;18(4):366-368. doi: 10.1038/s41592-021-01101-x. Epub 2021 Apr 7. PMID: 33828273; PMCID: PMC8026399. | -| fastani | Jain C, Rodriguez-R LM, Phillippy AM, Konstantinidis KT, Aluru S. High throughput ANI analysis of 90K prokaryotic genomes reveals clear species boundaries. Nat Commun. 2018 Nov 30;9(1):5114. doi: 10.1038/s41467-018-07641-9. PMID: 30504855; PMCID: PMC6269478. | -| fastp | Chen S, Zhou Y, Chen Y, Gu J. fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics. 2018 Sep 1;34(17):i884-i890. doi: 10.1093/bioinformatics/bty560. PMID: 30423086; PMCID: PMC6129281. | -| fastq_preprocessor | Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. doi: 10.1186/s12859-022-04973-8. PMID: 36224545; PMCID: PMC9554839. | -| fasttree | Price MN, Dehal PS, Arkin AP. FastTree 2--approximately maximum-likelihood trees for large alignments. PLoS One. 2010 Mar 10;5(3):e9490. doi: 10.1371/journal.pone.0009490. PMID: 20224823; PMCID: PMC2835736. | -| featurecounts | Liao Y, Smyth GK, Shi W. featureCounts: an efficient general purpose program for assigning sequence reads to genomic features. Bioinformatics. 2014 Apr 1;30(7):923-30. doi: 10.1093/bioinformatics/btt656. Epub 2013 Nov 13. PMID: 24227677. | -| genomad | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | -| gtdbtk | Chaumeil PA, Mussig AJ, Hugenholtz P, Parks DH. GTDB-Tk v2: memory friendly classification with the genome taxonomy database. Bioinformatics. 2022 Nov 30;38(23):5315-5316. doi: 10.1093/bioinformatics/btac672. PMID: 36218463; PMCID: PMC9710552. | -| hmmer | Eddy SR. Accelerated Profile HMM Searches. PLoS Comput Biol. 2011 Oct;7(10):e1002195. doi: 10.1371/journal.pcbi.1002195. Epub 2011 Oct 20. PMID: 22039361; PMCID: PMC3197634. | -| iqtree | Minh BQ, Schmidt HA, Chernomor O, Schrempf D, Woodhams MD, von Haeseler A, Lanfear R. IQ-TREE 2: New Models and Efficient Methods for Phylogenetic Inference in the Genomic Era. Mol Biol Evol. 2020 May 1;37(5):1530-1534. doi: 10.1093/molbev/msaa015. Erratum in: Mol Biol Evol. 2020 Aug 1;37(8):2461. PMID: 32011700; PMCID: PMC7182206. | -| kofamscan | Aramaki T, Blanc-Mathieu R, Endo H, Ohkubo K, Kanehisa M, Goto S, Ogata H. KofamKOALA: KEGG Ortholog assignment based on profile HMM and adaptive score threshold. Bioinformatics. 2020 Apr 1;36(7):2251-2252. doi: 10.1093/bioinformatics/btz859. PMID: 31742321; PMCID: PMC7141845. | -| krona | Ondov BD, Bergman NH, Phillippy AM. Interactive metagenomic visualization in a Web browser. BMC Bioinformatics. 2011 Sep 30;12:385. doi: 10.1186/1471-2105-12-385. PMID: 21961884; PMCID: PMC3190407. | -| maxbin2 | Wu YW, Tang YH, Tringe SG, Simmons BA, Singer SW. MaxBin: an automated binning method to recover individual genomes from metagenomes using an expectation-maximization algorithm. Microbiome. 2014 Aug 1;2:26. doi: 10.1186/2049-2618-2-26. PMID: 25136443; PMCID: PMC4129434. | -| megahit | Li D, Liu CM, Luo R, Sadakane K, Lam TW. MEGAHIT: an ultra-fast single-node solution for large and complex metagenomics assembly via succinct de Bruijn graph. Bioinformatics. 2015 May 15;31(10):1674-6. doi: 10.1093/bioinformatics/btv033. Epub 2015 Jan 20. PMID: 25609793. | -| metabat2 | Kang DD, Li F, Kirton E, Thomas A, Egan R, An H, Wang Z. MetaBAT 2: an adaptive binning algorithm for robust and efficient genome reconstruction from metagenome assemblies. PeerJ. 2019 Jul 26;7:e7359. doi: 10.7717/peerj.7359. PMID: 31388474; PMCID: PMC6662567. | -| metaeuk | Levy Karin E, Mirdita M, Söding J. MetaEuk-sensitive, high-throughput gene discovery, and annotation for large-scale eukaryotic metagenomics. Microbiome. 2020 Apr 3;8(1):48. doi: 10.1186/s40168-020-00808-x. PMID: 32245390; PMCID: PMC7126354. | -| metaspades | Nurk S, Meleshko D, Korobeynikov A, Pevzner PA. metaSPAdes: a new versatile metagenomic assembler. Genome Res. 2017 May;27(5):824-834. doi: 10.1101/gr.213959.116. Epub 2017 Mar 15. PMID: 28298430; PMCID: PMC5411777. | -| mmseqs2 | Steinegger M, Söding J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. Nat Biotechnol. 2017 Nov;35(11):1026-1028. doi: 10.1038/nbt.3988. Epub 2017 Oct 16. PMID: 29035372. | -| muscle | Edgar RC. Muscle5: High-accuracy alignment ensembles enable unbiased assessments of sequence homology and phylogeny. Nat Commun. 2022 Nov 15;13(1):6968. doi: 10.1038/s41467-022-34630-w. PMID: 36379955; PMCID: PMC9664440. | -| prodigal | Hyatt D, Chen GL, Locascio PF, Land ML, Larimer FW, Hauser LJ. Prodigal: prokaryotic gene recognition and translation initiation site identification. BMC Bioinformatics. 2010 Mar 8;11:119. doi: 10.1186/1471-2105-11-119. PMID: 20211023; PMCID: PMC2848648. | -| prodigal-gv | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | -| pyrodigal | Larralde, M., (2022). Pyrodigal: Python bindings and interface to Prodigal, an efficient method for gene prediction in prokaryotes. Journal of Open Source Software, 7(72), 4296, https://doi.org/10.21105/joss.04296 | -| qiime2 | Bolyen E, Rideout JR, Dillon MR, Bokulich NA, Abnet CC, Al-Ghalith GA, Alexander H, Alm EJ, Arumugam M, Asnicar F, Bai Y, Bisanz JE, Bittinger K, Brejnrod A, Brislawn CJ, Brown CT, Callahan BJ, Caraballo-Rodríguez AM, Chase J, Cope EK, Da Silva R, Diener C, Dorrestein PC, Douglas GM, Durall DM, Duvallet C, Edwardson CF, Ernst M, Estaki M, Fouquier J, Gauglitz JM, Gibbons SM, Gibson DL, Gonzalez A, Gorlick K, Guo J, Hillmann B, Holmes S, Holste H, Huttenhower C, Huttley GA, Janssen S, Jarmusch AK, Jiang L, Kaehler BD, Kang KB, Keefe CR, Keim P, Kelley ST, Knights D, Koester I, Kosciolek T, Kreps J, Langille MGI, Lee J, Ley R, Liu YX, Loftfield E, Lozupone C, Maher M, Marotz C, Martin BD, McDonald D, McIver LJ, Melnik AV, Metcalf JL, Morgan SC, Morton JT, Naimey AT, Navas-Molina JA, Nothias LF, Orchanian SB, Pearson T, Peoples SL, Petras D, Preuss ML, Pruesse E, Rasmussen LB, Rivers A, Robeson MS 2nd, Rosenthal P, Segata N, Shaffer M, Shiffer A, Sinha R, Song SJ, Spear JR, Swafford AD, Thompson LR, Torres PJ, Trinh P, Tripathi A, Turnbaugh PJ, Ul-Hasan S, van der Hooft JJJ, Vargas F, Vázquez-Baeza Y, Vogtmann E, von Hippel M, Walters W, Wan Y, Wang M, Warren J, Weber KC, Williamson CHD, Willis AD, Xu ZZ, Zaneveld JR, Zhang Y, Zhu Q, Knight R, Caporaso JG. Reproducible, interactive, scalable and extensible microbiome data science using QIIME 2. Nat Biotechnol. 2019 Aug;37(8):852-857. doi: 10.1038/s41587-019-0209-9. Erratum in: Nat Biotechnol. 2019 Sep;37(9):1091. PMID: 31341288; PMCID: PMC7015180. | -| rnaspades | Bushmanova E, Antipov D, Lapidus A, Prjibelski AD. rnaSPAdes: a de novo transcriptome assembler and its application to RNA-Seq data. Gigascience. 2019 Sep 1;8(9):giz100. doi: 10.1093/gigascience/giz100. PMID: 31494669; PMCID: PMC6736328. | -| samtools | Li H, Handsaker B, Wysoker A, Fennell T, Ruan J, Homer N, Marth G, Abecasis G, Durbin R; 1000 Genome Project Data Processing Subgroup. The Sequence Alignment/Map format and SAMtools. Bioinformatics. 2009 Aug 15;25(16):2078-9. doi: 10.1093/bioinformatics/btp352. Epub 2009 Jun 8. PMID: 19505943; PMCID: PMC2723002. | -| seqkit | Shen W, Le S, Li Y, Hu F. SeqKit: A Cross-Platform and Ultrafast Toolkit for FASTA/Q File Manipulation. PLoS One. 2016 Oct 5;11(10):e0163962. doi: 10.1371/journal.pone.0163962. PMID: 27706213; PMCID: PMC5051824. | -| spades | Bankevich A, Nurk S, Antipov D, Gurevich AA, Dvorkin M, Kulikov AS, Lesin VM, Nikolenko SI, Pham S, Prjibelski AD, Pyshkin AV, Sirotkin AV, Vyahhi N, Tesler G, Alekseyev MA, Pevzner PA. SPAdes: a new genome assembly algorithm and its applications to single-cell sequencing. J Comput Biol. 2012 May;19(5):455-77. doi: 10.1089/cmb.2012.0021. Epub 2012 Apr 16. PMID: 22506599; PMCID: PMC3342519. | -| tiara | Karlicki M, Antonowicz S, Karnkowska A. Tiara: deep learning-based classification system for eukaryotic sequences. Bioinformatics. 2022 Jan 3;38(2):344-350. doi: 10.1093/bioinformatics/btab672. PMID: 34570171; PMCID: PMC8722755. | -| virfinder | Ren J, Ahlgren NA, Lu YY, Fuhrman JA, Sun F. VirFinder: a novel k-mer based tool for identifying viral sequences from assembled metagenomic data. Microbiome. 2017 Jul 6;5(1):69. doi: 10.1186/s40168-017-0283-5. PMID: 28683828; PMCID: PMC5501583. | \ No newline at end of file +| Dependency | Citation | +|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| antismash | Blin K, Shaw S, Kloosterman AM, Charlop-Powers Z, van Wezel GP, Medema MH, Weber T. antiSMASH 6.0: improving cluster detection and comparison capabilities. Nucleic Acids Res. 2021 Jul 2;49(W1):W29-W35. doi: 10.1093/nar/gkab335. PMID: 33978755; PMCID: PMC8262755. | +| barrnap | Seemann, T. barrnap 0.9 : rapid ribosomal RNA prediction. https://github.com/tseemann/barrnap | +| bbtools | https://sourceforge.net/projects/bbmap/ | +| bowtie2 | Langmead B, Salzberg SL. Fast gapped-read alignment with Bowtie 2. Nat Methods. 2012 Mar 4;9(4):357-9. doi: 10.1038/nmeth.1923. PMID: 22388286; PMCID: PMC3322381. | +| busco | Manni M, Berkeley MR, Seppey M, Simão FA, Zdobnov EM. BUSCO Update: Novel and Streamlined Workflows along with Broader and Deeper Phylogenetic Coverage for Scoring of Eukaryotic, Prokaryotic, and Viral Genomes. Mol Biol Evol. 2021 Sep 27;38(10):4647-4654. doi: 10.1093/molbev/msab199. PMID: 34320186; PMCID: PMC8476166. | +| checkm2 | Alex Chklovski, Donovan H. Parks, Ben J. Woodcroft, Gene W. Tyson bioRxiv 2022.07.11.499243; doi: https://doi.org/10.1101/2022.07.11.499243 | +| checkv | Nayfach S, Camargo AP, Schulz F, Eloe-Fadrosh E, Roux S, Kyrpides NC. CheckV assesses the quality and completeness of metagenome-assembled viral genomes. Nat Biotechnol. 2021 May;39(5):578-585. doi: 10.1038/s41587-020-00774-7. Epub 2020 Dec 21. PMID: 33349699; PMCID: PMC8116208. | +| clipkit | Steenwyk JL, Buida TJ 3rd, Li Y, Shen XX, Rokas A. ClipKIT: A multiple sequence alignment trimming software for accurate phylogenomic inference. PLoS Biol. 2020 Dec 2;18(12):e3001007. doi: 10.1371/journal.pbio.3001007. PMID: 33264284; PMCID: PMC7735675. | +| concoct | Alneberg J, Bjarnason BS, de Bruijn I, Schirmer M, Quick J, Ijaz UZ, Lahti L, Loman NJ, Andersson AF, Quince C. Binning metagenomic contigs by coverage and composition. Nat Methods. 2014 Nov;11(11):1144-6. doi: 10.1038/nmeth.3103. Epub 2014 Sep 14. PMID: 25218180. | +| coverm | https://github.com/wwood/CoverM | +| dada2 | Callahan BJ, McMurdie PJ, Rosen MJ, Han AW, Johnson AJ, Holmes SP. DADA2: High-resolution sample inference from Illumina amplicon data. Nat Methods. 2016 Jul;13(7):581-3. doi: 10.1038/nmeth.3869. Epub 2016 May 23. PMID: 27214047; PMCID: PMC4927377. | +| das_tool | Sieber CMK, Probst AJ, Sharrar A, Thomas BC, Hess M, Tringe SG, Banfield JF. Recovery of genomes from metagenomes via a dereplication, aggregation and scoring strategy. Nat Microbiol. 2018 Jul;3(7):836-843. doi: 10.1038/s41564-018-0171-1. Epub 2018 May 28. PMID: 29807988; PMCID: PMC6786971. | +| diamond | Buchfink B, Reuter K, Drost HG. Sensitive protein alignments at tree-of-life scale using DIAMOND. Nat Methods. 2021 Apr;18(4):366-368. doi: 10.1038/s41592-021-01101-x. Epub 2021 Apr 7. PMID: 33828273; PMCID: PMC8026399. | +| fastani | Jain C, Rodriguez-R LM, Phillippy AM, Konstantinidis KT, Aluru S. High throughput ANI analysis of 90K prokaryotic genomes reveals clear species boundaries. Nat Commun. 2018 Nov 30;9(1):5114. doi: 10.1038/s41467-018-07641-9. PMID: 30504855; PMCID: PMC6269478. | +| fastp | Chen S, Zhou Y, Chen Y, Gu J. fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics. 2018 Sep 1;34(17):i884-i890. doi: 10.1093/bioinformatics/bty560. PMID: 30423086; PMCID: PMC6129281. | +| fastq_preprocessor | Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. doi: 10.1186/s12859-022-04973-8. PMID: 36224545; PMCID: PMC9554839. | +| fasttree | Price MN, Dehal PS, Arkin AP. FastTree 2--approximately maximum-likelihood trees for large alignments. PLoS One. 2010 Mar 10;5(3):e9490. doi: 10.1371/journal.pone.0009490. PMID: 20224823; PMCID: PMC2835736. | +| featurecounts | Liao Y, Smyth GK, Shi W. featureCounts: an efficient general purpose program for assigning sequence reads to genomic features. Bioinformatics. 2014 Apr 1;30(7):923-30. doi: 10.1093/bioinformatics/btt656. Epub 2013 Nov 13. PMID: 24227677. | +| genomad | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | +| gtdbtk | Chaumeil PA, Mussig AJ, Hugenholtz P, Parks DH. GTDB-Tk v2: memory friendly classification with the genome taxonomy database. Bioinformatics. 2022 Nov 30;38(23):5315-5316. doi: 10.1093/bioinformatics/btac672. PMID: 36218463; PMCID: PMC9710552. | +| hmmer | Eddy SR. Accelerated Profile HMM Searches. PLoS Comput Biol. 2011 Oct;7(10):e1002195. doi: 10.1371/journal.pcbi.1002195. Epub 2011 Oct 20. PMID: 22039361; PMCID: PMC3197634. | +| iqtree | Minh BQ, Schmidt HA, Chernomor O, Schrempf D, Woodhams MD, von Haeseler A, Lanfear R. IQ-TREE 2: New Models and Efficient Methods for Phylogenetic Inference in the Genomic Era. Mol Biol Evol. 2020 May 1;37(5):1530-1534. doi: 10.1093/molbev/msaa015. Erratum in: Mol Biol Evol. 2020 Aug 1;37(8):2461. PMID: 32011700; PMCID: PMC7182206. | +| kofamscan | Aramaki T, Blanc-Mathieu R, Endo H, Ohkubo K, Kanehisa M, Goto S, Ogata H. KofamKOALA: KEGG Ortholog assignment based on profile HMM and adaptive score threshold. Bioinformatics. 2020 Apr 1;36(7):2251-2252. doi: 10.1093/bioinformatics/btz859. PMID: 31742321; PMCID: PMC7141845. | +| krona | Ondov BD, Bergman NH, Phillippy AM. Interactive metagenomic visualization in a Web browser. BMC Bioinformatics. 2011 Sep 30;12:385. doi: 10.1186/1471-2105-12-385. PMID: 21961884; PMCID: PMC3190407. | +| maxbin2 | Wu YW, Tang YH, Tringe SG, Simmons BA, Singer SW. MaxBin: an automated binning method to recover individual genomes from metagenomes using an expectation-maximization algorithm. Microbiome. 2014 Aug 1;2:26. doi: 10.1186/2049-2618-2-26. PMID: 25136443; PMCID: PMC4129434. | +| megahit | Li D, Liu CM, Luo R, Sadakane K, Lam TW. MEGAHIT: an ultra-fast single-node solution for large and complex metagenomics assembly via succinct de Bruijn graph. Bioinformatics. 2015 May 15;31(10):1674-6. doi: 10.1093/bioinformatics/btv033. Epub 2015 Jan 20. PMID: 25609793. | +| metabat2 | Kang DD, Li F, Kirton E, Thomas A, Egan R, An H, Wang Z. MetaBAT 2: an adaptive binning algorithm for robust and efficient genome reconstruction from metagenome assemblies. PeerJ. 2019 Jul 26;7:e7359. doi: 10.7717/peerj.7359. PMID: 31388474; PMCID: PMC6662567. | +| metaeuk | Levy Karin E, Mirdita M, Söding J. MetaEuk-sensitive, high-throughput gene discovery, and annotation for large-scale eukaryotic metagenomics. Microbiome. 2020 Apr 3;8(1):48. doi: 10.1186/s40168-020-00808-x. PMID: 32245390; PMCID: PMC7126354. | +| metaspades | Nurk S, Meleshko D, Korobeynikov A, Pevzner PA. metaSPAdes: a new versatile metagenomic assembler. Genome Res. 2017 May;27(5):824-834. doi: 10.1101/gr.213959.116. Epub 2017 Mar 15. PMID: 28298430; PMCID: PMC5411777. | +| mmseqs2 | Steinegger M, Söding J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. Nat Biotechnol. 2017 Nov;35(11):1026-1028. doi: 10.1038/nbt.3988. Epub 2017 Oct 16. PMID: 29035372. | +| muscle | Edgar RC. Muscle5: High-accuracy alignment ensembles enable unbiased assessments of sequence homology and phylogeny. Nat Commun. 2022 Nov 15;13(1):6968. doi: 10.1038/s41467-022-34630-w. PMID: 36379955; PMCID: PMC9664440. | +| prodigal | Hyatt D, Chen GL, Locascio PF, Land ML, Larimer FW, Hauser LJ. Prodigal: prokaryotic gene recognition and translation initiation site identification. BMC Bioinformatics. 2010 Mar 8;11:119. doi: 10.1186/1471-2105-11-119. PMID: 20211023; PMCID: PMC2848648. | +| prodigal-gv | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | +| pyrodigal | Larralde, M., (2022). Pyrodigal: Python bindings and interface to Prodigal, an efficient method for gene prediction in prokaryotes. Journal of Open Source Software, 7(72), 4296, https://doi.org/10.21105/joss.04296 | +| qiime2 | Bolyen E, Rideout JR, Dillon MR, Bokulich NA, Abnet CC, Al-Ghalith GA, Alexander H, Alm EJ, Arumugam M, Asnicar F, Bai Y, Bisanz JE, Bittinger K, Brejnrod A, Brislawn CJ, Brown CT, Callahan BJ, Caraballo-Rodríguez AM, Chase J, Cope EK, Da Silva R, Diener C, Dorrestein PC, Douglas GM, Durall DM, Duvallet C, Edwardson CF, Ernst M, Estaki M, Fouquier J, Gauglitz JM, Gibbons SM, Gibson DL, Gonzalez A, Gorlick K, Guo J, Hillmann B, Holmes S, Holste H, Huttenhower C, Huttley GA, Janssen S, Jarmusch AK, Jiang L, Kaehler BD, Kang KB, Keefe CR, Keim P, Kelley ST, Knights D, Koester I, Kosciolek T, Kreps J, Langille MGI, Lee J, Ley R, Liu YX, Loftfield E, Lozupone C, Maher M, Marotz C, Martin BD, McDonald D, McIver LJ, Melnik AV, Metcalf JL, Morgan SC, Morton JT, Naimey AT, Navas-Molina JA, Nothias LF, Orchanian SB, Pearson T, Peoples SL, Petras D, Preuss ML, Pruesse E, Rasmussen LB, Rivers A, Robeson MS 2nd, Rosenthal P, Segata N, Shaffer M, Shiffer A, Sinha R, Song SJ, Spear JR, Swafford AD, Thompson LR, Torres PJ, Trinh P, Tripathi A, Turnbaugh PJ, Ul-Hasan S, van der Hooft JJJ, Vargas F, Vázquez-Baeza Y, Vogtmann E, von Hippel M, Walters W, Wan Y, Wang M, Warren J, Weber KC, Williamson CHD, Willis AD, Xu ZZ, Zaneveld JR, Zhang Y, Zhu Q, Knight R, Caporaso JG. Reproducible, interactive, scalable and extensible microbiome data science using QIIME 2. Nat Biotechnol. 2019 Aug;37(8):852-857. doi: 10.1038/s41587-019-0209-9. Erratum in: Nat Biotechnol. 2019 Sep;37(9):1091. PMID: 31341288; PMCID: PMC7015180. | +| rnaspades | Bushmanova E, Antipov D, Lapidus A, Prjibelski AD. rnaSPAdes: a de novo transcriptome assembler and its application to RNA-Seq data. Gigascience. 2019 Sep 1;8(9):giz100. doi: 10.1093/gigascience/giz100. PMID: 31494669; PMCID: PMC6736328. | +| samtools | Li H, Handsaker B, Wysoker A, Fennell T, Ruan J, Homer N, Marth G, Abecasis G, Durbin R; 1000 Genome Project Data Processing Subgroup. The Sequence Alignment/Map format and SAMtools. Bioinformatics. 2009 Aug 15;25(16):2078-9. doi: 10.1093/bioinformatics/btp352. Epub 2009 Jun 8. PMID: 19505943; PMCID: PMC2723002. | +| seqkit | Shen W, Le S, Li Y, Hu F. SeqKit: A Cross-Platform and Ultrafast Toolkit for FASTA/Q File Manipulation. PLoS One. 2016 Oct 5;11(10):e0163962. doi: 10.1371/journal.pone.0163962. PMID: 27706213; PMCID: PMC5051824. | +| spades | Bankevich A, Nurk S, Antipov D, Gurevich AA, Dvorkin M, Kulikov AS, Lesin VM, Nikolenko SI, Pham S, Prjibelski AD, Pyshkin AV, Sirotkin AV, Vyahhi N, Tesler G, Alekseyev MA, Pevzner PA. SPAdes: a new genome assembly algorithm and its applications to single-cell sequencing. J Comput Biol. 2012 May;19(5):455-77. doi: 10.1089/cmb.2012.0021. Epub 2012 Apr 16. PMID: 22506599; PMCID: PMC3342519. | +| tiara | Karlicki M, Antonowicz S, Karnkowska A. Tiara: deep learning-based classification system for eukaryotic sequences. Bioinformatics. 2022 Jan 3;38(2):344-350. doi: 10.1093/bioinformatics/btab672. PMID: 34570171; PMCID: PMC8722755. | +| trnascan-se | Chan PP, Lin BY, Mak AJ, Lowe TM. tRNAscan-SE 2.0: improved detection and functional classification of transfer RNA genes. Nucleic Acids Res. 2021 Sep 20;49(16):9077-9096. doi: 10.1093/nar/gkab688. PMID: 34417604; PMCID: PMC8450103. | +| virfinder | Ren J, Ahlgren NA, Lu YY, Fuhrman JA, Sun F. VirFinder: a novel k-mer based tool for identifying viral sequences from assembled metagenomic data. Microbiome. 2017 Jul 6;5(1):69. doi: 10.1186/s40168-017-0283-5. PMID: 28683828; PMCID: PMC5501583. | \ No newline at end of file diff --git a/DEPENDENCIES.xlsx b/DEPENDENCIES.xlsx index 2cc20f25550e07a7740ad60a2b397cdb505d946c..ce9258a3eb5310277b4d72d8ff8fd2467b0c22f1 100644 GIT binary patch delta 32208 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 literal 81877 zcmeFYWmlYAvo4GVNq}I%gA?4{2^QSlEx5Y}NN{&|3-0djPH@-Y?(*KvTF<-p8E5~4 zefZFKk2!1BEV=5c>K=`(1SAw1*gG&-FfcGeFb|8wPp05tV42WhV5nfQ5E_EkRt`p1 z4mygiHb(YZv@Vtw1ldp! zvJoEsKdFnl$v;!~ua2Fu9YZH6vMW}YYX`ZPS*!WWsgs6P%T}-u;6%{U`A^rvj1(^FcOGCqr+rlYGx2SE%r7r#=$D9y8v@gY z5k~^kM?dXTk=QYj3WkW%cQ^?6nbGf2tNYPCS0kb52dW-NyZ#jGPPykuUn)OoSI(R^$LT z6c^&p*)a(d32**m9y48J^_!*hvisAtKAP!%23GEKJBbHL|9Xav<}0#j?j;ftkIY&$ ziM=i)eHp@fV}u?G&$i|;*a6Es@!)DmkGa);fm;)lR8jF*uzqJf3@l5>6EWDYGiT`7 zS_!AaQB(Wdb%H6*@QMNT-|jgdpuoUhUm?L{|Bni-RiY=p0$?Wz6ySTHLUrtnEbQrM zL9hR>GXEEs<9~C#Bw9+QoBo6U>9?nVf!o=Y$d6wooCUr$6DoTB5L-s74a*_HS!w@* z`%w|w4^q^-)$3_!X@xuNV1V#uler`W1&xci&bib-;lb7shLX%KUc|O!qX*e(_Imav zNmSgG+^IE!vbdozOJZ=1SY-NKsPf}DtqL|AY96ltC!Qo9^*$-JRsFj%@Y$~l$EE%i z^=z33G2Z*B#)+&&OgKy{^waG)iClFIEjliR zQrc0UJ!qs;2jq#EQSKPWMf*uJuf5f4n9qmPTzlByx{C+S27N>D3s-?=^?w)1D%>+U z1h6v$2?mA@1`Fswo!gBsa?Y6cwWW`OMf?!T6paWeojeW!k{ev_WI z(N;#WoUNe(EYA zIzxMvY=DLR*U|M4!VtGqJ>%2t5B+?K@Hk(a5>^6fzlgC{^X4Wi2@l&H!3PrrREg>&{exsdJp|?5+id=W3A$A{-G&nMODuW=`#)y60usMAm6-j}_H|W9 z_Ya`;_k#Zrb{+k^m3)IM14nmaK+*5K7?8T6fByb|LQvgfO#D26o}X}FU`PNo00jMa z?35@?TIH~#dFq(Hf}cx=!9_x`r2PVGj!TxC{l2(RFa-X^#f;uuGTDOVwQY_h*OW`$ zhzp1G-O`r;`||PDRPV*uWmSa*dO0ackMAk(N*SZ7%~%-PM-Gov4^3wivVg8Y6M)o~R5szTT5Sm8Jp3F?Wb4-39nmlm`-ONl(Y!xlGD4Mvx z&QlRZIf=`kKfQ}EULt-{)zDIms+Kq^78Uv;#dlU}T={#!M4S+BU2R-Y)w^KmElEo* z`Dya%bvq?Eu}`>NtGeU!-|imHx>-L+zmm+m-4;e0$usN!9{YiPEYu!?zXZFbLNVVm z-f1dH8|^T9h4ooc?A5R62{PgJtN(Gv_io+NF)DD9Q$LGUffNJnR648zXRbr++%mox z`3y8R1ZnKJZOKUweMCLV^!;M!g+Fq`>__@b-DN?vh)!+sBGL)DJ7$O?Cx5~B9}W7a zztM{fpvR%hIZ#qU^Dg&aSEnB~6#N1Uz+t;8j~9dg1^wcysz;*6|7ny(?$@L^FT-TQ zJ73W?6NPH=r?9g5uPBYe3bcz)%07-NXwS4g!PBZ$EVXXi3-uPGUK#@D=VKYwrIX;Y z11et`m0}T^zO(FTi^$b$gcx8mT|DCX-PhfbE$y6=Y5r@CAwYA-}v3#;DLo}Nrs?rV-6O-8E>4>K*Q z(lTMAJjHhBGr~2;r#v{D?gy+VYGuJe9GH!C=H9Y(l>DWBmD(gpz6$9v(AxsT&NY2O z_qb9DY<*)h@LFtj%au6ldfP&O=CLB4CO!-6B|0olL^YI2`3;N4w0LnjTh`muky zN1|5GS>N$)7>RWf`QSw=$SkXKe|2gUk|+OhwtLgAzQVbq_yN&MKkfFp{T0FG{iMAc z5qm2aGW#=k%D8loHtA)+I;WV+f4*NpXB~Y$MyUI+BIwA9nAkk z0-YEi+oXY=J_69Ep*Iq6Ffp<;qI-K~05y>#wb2k9Hnet(2fh#X&QB~`5u~f@;}$V1 zB!+3RxV3dha>`7M(JeTr5TqQ}^5nUB316*wVyE~a5n8Vz;7Drz;!l1VohL5cAxW_i zS9d}P5q)VbD7a1ca=UampK48X6HR>I6&i#|Tlf+VnmmRX@AZ-6U;`yy6 zexk~gFU2H|zSH*Amv#4KBqTmUh!gjA|J@?9!4UNoA(VVgrKplLt8%Nm>TBSyefbkS z!o>@{bOx@^o9a}pC*;bgm&Jk30h^oGB!%;dZqFF!XN)b)4K5casdin&F+A-?*+z(bD3y58r^de$J(;>SLU)pVs;Jww7+W2oEsO> z4ZHTkQCC0s3A9F2jLStL6d%beNU9RC?Wl*IF!F-^fRHB<+Z^ouATt45)eu6?vEH*p zIt`0r?T;R&l^d?lgdRZp_dIpoiLp_*Nc*e&(3Vp zC0AlD8-tpICZfzwXvIswZuginRW{`p!YYh*5rR>I^4UA56xu5VL-C561p{~Hco;V& zD}t_0K9>6hPLm4S>5|IR)(Gkc4tE(5{9L>kU9~LlE;OIz@h~LT1MHyp3jDp(H*7*H zcO=gt#K3~r7Gk)~Xy@H2N>)ZkBIFiKart0C{ZnF>GvFFqpdJ3h7UJV)tsd5B3A_EN z#t=-VC?2QzFj;Q*i}mgP6EDFMr1^He)I{g!W;&*Ynz|PUtZW?=?=U z5b4I@9f_Av4U8#Wr|Np|DGUr82rbkF~5YwzlS7(*yxD5)+!wiey zrv7I1qnx=fx1K*N{0EE`-$3*iO`)`6b&HeOl=&1^BuF$7~?XceQpsFuV&O@J_ z!R_BcM`)Zs#&V{R8|?}CRKwlkQ=7Y)q%U{aM3>nAaaFR{p1pg0Ec!mJ&0B6XW}bAZ z-gkX-Ent?^;^8wyc6za&F4I!E!$cT+G7R0@{MFeLs%0`1hQ4q~uIWpo8Tc5{T{24| z^>QpR&6X`0-xtQkh=~vCf<`MHslk%hiY`oKnFhH5j#TIw6Qz&Rmo5=u)6&L`B~8h{ z-$O^=yHIF`2_}f=@2m5iu9nUmdhIJZt0lZ6UzogVCyM~na%G}_c6QYT;4Sj&uCy|D>OJ^iKScJIqSt^VN?YO z#d2!KNDsYZh)Q&i?V-`3@s2}ap2W?zq*D3ukym{ynV$+j#rC5E*MJLSC$Xvj3Z`7T zG``PR+$wx{MjM z`Ms0}GY&C-1Gu&kiZ29bswnfUo~3E*%FJmS5m9Dc#alLJJVPKm>1i@X7^T!&q`QNw ztL2tzn5bbt$jq?bSvrca^PHD`;TuRBQFr90J`_*8dl*`7nf(sq?8EJ5n zj+992x>0hPh|`A;;F@|9!cL~;{d2S@TRSwPzmP*hBy~nGYBwG>+cXzEY)_F1(jIGv zGSw`d%zk~ZfBY18x5sJigAN9^Q4a=&`Cl`*y@`>L zgFW5bg9+4wQ)48|x9HJ+-9kPiiMWf#$GG647BomnG~*0${|WZM3)$Zg9}3ZXdDP@0 zp)o{-|I`+L<~$x1zIzK(nvKU!S4shgLrMZcY(X>HMAGiX+s_f?v7Wy>giSt(%-yK7 z?3r>}x^(x6KXmcVZe8j};zI!HgNFvnz=CkOKbE|`|jUQ@yOV$pJ=d4ylS zZdmEf=SM-rD_gK6+W4}|a%Xz24UJ`(sxSLaFI(1WQJ0*2tc(`--vT3z`qX>4sM@ar z?}?{Cli^$}5n)*MDAO(^lYd`;kR-NbkTm(_WTV2;M_@A#cEX#QprnKDboS zM8~Wo0OLRl75ywXGcv=rGF{Mg+nun+=l$|vcEB_wroe{d_TfJ)P0^bUD4& z-m~_C;{7_#!ux!a(EfZoH?HIL_;g#|{&aO`|M+(+V6E<|{QPyR-6P2R_59rIvU}Xy z!rwi3b&x{%vqF1#^Q}ujeK^Le z;A_J_b4%>U-9)?ajV!Vg|9kb0Q0iDS&&4euGT*ICp4XHAn*yH-MJ8f;qa?JYtsD1x z@}X(EOQ6EHvr(fVyAXwMHquOvjlC2kE(J+V%bQ%=V7JWI(o~Kd9XzEebB2Km@$2c| z#XUiSGE8}GfMCO$AX>Btu0lfT%nsOXO}#5Op|qN>dlSVqjWl5=&&tfsr-I1!bgKiq zkkd%i3{Dx{f-0Z^L5XE zTmKu>BRJ*vu*&aYwuU_;P&BF6*3SoWRMynL$S=>U#^LJm3t3S}<(VSt9}INiog zCd*-fI?UQZEJdk0pQNp}Mw;(b)3qQ?xwRv_IMp0dEq2>11$n6!-;CRxazW;L?qQa4 zPdz6zG@TaG%mP7ZigP z`lrDM^J_s1upBALemJ1~Sfp3)`&C3mPoy*^hZW|*RutWAhLb~FFQ?ch=g**A@P(#E zS}>rVeO!rOCa3L}L{xfC8&lrcFNwc?B^_D_%~QN(%t~RA@n0)(VHqhaOW# zmeSb8e$`je)3=PuNk7=KD4yo&rBzZYfUX5elpIr5l*+XOXJdj&r4wYr6Gh{wq7j>tcVy1;Y5_ze=d~nr z{o`-`vKIo;#%CF5yK5K@ae*=d+26Sjez-ezO2BdaZF1;Fbsx=Vfp9yGV)*R?B)eE& z%~Jj{X&%(h*qP6*PrIixOvrbl(4#Ryz?y)W;=~j0F~;$;0ykxYbeY0I60Cd<B?_oqRe18br(e-fP!%|%HAni4hKml@OB`d!{wWEAUd}t0inBqrh+`_ zw>R+W2Q?eo>PiJhq6!7+LF@RG)A9vjeT8AAUWpeqfGg=6nNH%+G!!x!edM(&MRB8` z`;v)_MLjXRa~Q;d;FIoLfPz;0bqw$u>XlxmJ+c}wSW2KQr|pyiSSpfDir)m3=M&7y zy`k6+dVWAA15o3uDVoMeD*9VXyOTgXpIL-r0R2NY6shQcp1+R`EQ?RwgN=E}?LGlJ zd8g#HR5X}EaN-8BUACp`4WPiFhe{3qsR7-%(U2pJ6x^1B7XJR7+zACxSq-`$Nr5`D zbTc=a(*T)Zvn*HO4#Way4FY3(_}>+vKE{Q>z?2GHrJ4Y|5*e2sc^}R&P0qXn6eh_0 z*IjQnBmsC0H;3+bz@0kE#P_0v%BxT8t3zhsR&NTA>%7a=7QRW-Z@rsIz9x@B;r30F% zl|g$KP@=v1weafsd%(H3y)5odxk^<_Un@sSFQEQYD{IpHfDi!Uk`m|tp3A!f+@R`l zow?=7OAk^lQBI0STE*ZHKi&#a+P}!$f1>-vZh+ndWb3!5M=Q}7hKH!*FZKvdAXR|I zRwC(xqgc>-~@sWc(#r=Gp2Gqt?9`6{? z?*PQx%)e&`WRWGzcmHD8uooH=DR(MPaL2u!-{LxOkO3&R|Jrx!9pKrot#E+kt9gHI zxpmt5jRE@l@!s^k?Lg8?;IiVrW4D?Y#>JPzYQXd=6q1 zgIIl!R{A!;SFJZpPnF1QG-&_#OF)HjXf(W%X;0P!P(~VK9)ma@<-+jqXoEpf2vdv)$I56#_P$yO#RK-vT-P(3=wu3`EboVViR24Cw(T2T7R0 zqW_e*i=szp0?^3KhF8cnPJmQ?fNlf{bBL!=Vc~Cqm=xZw*V!Hsxb%F|EV%swKnv*f zwWkcg*SzXV`8%EnJPIU*s!iHCT2}oh!sf44kRR`Pb3gwIFRyO7H^2ZG%kl=)JlNwi0p_-2h37~C@T+5)FFo8C9pgr} z2{{Z!Bgzl(Kp+w|I&;J$u;#yL=RC6}ZuTEtAY#O08#-wa`!|)K(Pi5rcE$Eh7!Sm* zb^z7qtT%`O5uq_WsKHg{0A&pT@$JqukTnqL3jo-~Q|1`bi;Edc!UT%pjYe!VJ+^Zb zu5#^xYyZ_mWSkRO4QLXKs{V(BIdFcZN`XS0`cDD9;R!mgmbZ3?woTMdOb&1w7#6Yu*03;IYLpXVWNu8Z?_xN8nA6ZhG zc*5e~3YBB9RuQYp7I1usxjbFwASp3ea&*kzKsHj4Mmxt2}GaL#%1Q#FZ&-y3P3=z!DO1!d;;M2rYNbagl;!+ zR{$Lwl=UJS=eL1@q6dp<%1%b_+Jh2A=%5C?nF`baC~kK?iD&)u8qW3gY#1W+C90rw1rg>T8NXQ&f;uPm&E}BICTD6$P%G8~{~1W}eA5 zwXvi0gJpQ4Q6gt4b2Jdmcz_sp!r6e286sm<95z!N|41VRfA<7BFCb0}Tmm%J>v~Cy zf5PzAYOpzmcRK)nU?7wwjaw@S0W^Kp_5IY-2(*IVCflF^i3$}^X{WrGZBM3uq@0kE zu=D9(1t6}P98mM(v<6B;5tvj?fr;`hjiA2j)2-IA0UXNzFF32+oYR290wfw#%Mbw! zUir4(0N*P3bpOL)3~iVP;`!@^n8rVE{~dEu&jau0Qo>Eo~z^2{g_im7wXB78r}68WAR00Tm=7#~22%3z#RIgrCne4T003%7 zC;%Ojrhf`1CMow#N?)C4fUm$_pY9LKGupf!?)d)e`?o5JZ;~?CQq+i`|ow-Z#=~iS}_y#A5GDlVp4D8o27#P|=U)$Q7=-C+=Dmd7gS{d7ezOo%j)3C!4 ztIA*Nx48M(7cd=-C9o+17TaVV%BCe2L{lZc@@z*9P7*{7@$C}|fiR7(MI8g{_$4RK ze%grm?jsM8j}VzEbQ&+x^dDs+I0DA5Zo2l@6Xd%~??+aw<^Y#?(!W=9Tw$#b;UNWn z4vCW1uT__q*D31O{YIB2oS}VxnO}y-6jG0$>N8#rz3=VgNAWV!zI!iVjqcr+-`rMr zlBWwTXuILdxbBdpNo8}ik6GPTKdRv(RLkLxf7yEe-nzPwAvEaFk1*Bi@RE?G{ixed zlRs!3a@X^kH_fqp|94#NVmf1wFRd?>-B4Y|do|&)|)16iM?cpcCxz3ADwBc8Kp7vogQVA zqC|?Gu36LbV+yNCkp_>*1THBxFs1gy{L3OJQ`jX6(C=5r0-jxkqpXM)l;oqhQW(li zYF4>3dgI!^v|BzL>ZUGR%Q_&~=`1|GhzNJ;F60TM8oA%1SL1|YEO4h=xGQcAVU7Y(spF9sAow zs!e0_6r4S35B)2-(rKb~3Ql-JutKA;UPiQzn%ZfjJJYtFPrbw1nloN2565yQxo!8J zevnG7e9`8L{%eSRxF6zDUfOdT6AV3VbC*~ z^kP@3v1X;JUxhbDgA>EwK+)!?~Rgcuku-?t@Hzo z@%yc!^zcruo+o?vWD4S-S#VDFy>6N&t36K~%u1!pYcfk#*x4=q9E^&TuA0o?)LIY! zx|eCTYAaKI4{hS4A&kC1o09ftL(k~**Cdv$-z7v^)C;TCMtlf(4)a(&r$H^DTv z=c7`L-*Rh@@Nih1ZbPu#4dolsgi%Geof+FS8X-h&v_3W)7=P>-Rq&)utJu@CaCl-D zi}?-nVX))SLnGTJY_9Yn<=c6gLy>p0_?1udI?W$fm$_Dz{y`HvVhi@9x}>@g=1$U+>*aQM(nhPOV#S9;;j0vqs$1tn*Ov7;3=bUE;{DOH{R%Gf!8u;fcvy+Ddka&bA@$;d2qv zlYxvj6DMzzla#`&3FgXDwxDVkuk|!m+n>XI>SNUYkx4NX z%Fiwzjl}XZWH6X=H``js0%s0SN-Y@U|GZ)&?Vd0vi#`a0@r!uW?yrqa^*(>*`dIzk znHzBdTj!0uGJGQNL^fWW^WG0VMY7pzKm1dDneADV>sj`s>wS^h>CGR(MY63nYrK!x zeuMOV0lhODc&!AL4G*1q?xS-o&7+^LhU74~iJ-67 zlW0p~_%WT%GzlvC&WX2L+u`$)d64C>mry+n(qk1Gd_!m6;C!Tzr?^#LK4!3`px0?U z+=S7#EI8m#^!zzgJ{3k?{_0|d_mqVc@sLKV4|g-kyZ;Agg++A!aI+5yv54G%0{Gxg zz3zf51ydVh!YjV{E42qpTK>UCav-?Sod}TyN z#8`xOV}!BwTGKeDEWGpwy;1rU4AeHC_4ZISUuubRL1mVZ6%XbRCSIOp!}p_ZpLaRo z?MHXP+o~yqbvBx%`p#OvoJMm+Sv4=1mBIa9GGY5x#Ma|#tnIiVt$KG)6z5Quc#d}w zsW}$3HrMkUQe7{4x~h|cqxJ35JpaM&-Btsulz_h)r?$)9w-$WSY6yLt{^{-xevY5scaWgBj`8?>PztfhMK4UF*IDMlb;%ZQmfCHAl2y z5OVb6uXe9Z>n%)sdzFs8VdU(c>*0lRF}}~ng$Kxj+Pp@@VRl#VaiV?@U+Q?d28-y@ zKixW}`iPJCL~J!vdj;b!L!&l?;<-%LkiPO2un zy>)@DK9)?RwxGTaq-3&HqBL7M)o^JKzUwcMQ! zQKzj*s?CpH9P!xcI_UWcp^?oTzfSKpIONnpBTczlg+XQWct#(0;x^pV%MQcNn1OCAr~Yf{;P-M#lo}4`1Y2HILc-v3=N6Jq+zNgoVQNpaSDSE z%90p3Kd)d$UZgh(8|EYhgF+p~9c1OlMDYR5IB>0vXXvyk9OpViLBkh`DTBFp55i9( z6QpQ=iSRVOZ;XBRa1&53@Tgr-!i|Hma9&(dZ?>16DdUv3i4K1}?|vZ(c|eeFF@m+s zN;wn)i>wBKSTsrK<%06H120PwW6%ON%FOp^?-iN&=ttw%`I3V8ox#&G9Mbw6Y&}}7 zNEj>`s2FELof9O%hCf=#$a9aVehz(77p_~QCF>LC zs$zBdEvm0sK6}{eCKF^kQTV;ubAnTosldvQ8xv0X_eY|+!F{V#-u2A$bN&Jt{tub! zjL9oTLsejN?)##ghE8nuzP|Ai11;~uWbiJZFKApRexCo(qV_EJbeTtEu8pLv;`XE2 z!^r6q-WXt0T-8ZmR3eJ@Z;*-=33>pVB$j=dB~Ko;!QXfJ79K9sQN8YDxm>IGG4=gA zUwA&7829HUB!U&XT((7A(}KJ$TjeoZ#*kTL0c=Y82y~0xUWHbPb%;dNw=-Us3&ck#q4V^4x*kKDXS~H7E%t!YP0FEgp9;r zwW?X;|4BBSsU>?YX($mXp4J}sTMQ#hl2e*n;}N!JY}LBLOY<_H!TY;GAhtp(#>Txn zp|xMm4rLT={c)$+1e|GotP;#&;@gu7Ldg*r3$X-8%8|AWtV^Rjwgq45Ty&?hX*sANjX&0D_=QW=Na1Vr9AKM#%!;z zKhz$;)nSy@j{3p>3ybOT`*;C;oPVxR$+A$S2DvcpM~)T`}a$fn{J7srimE|@}5tE_69d#U1=$y5hUFSQGm3e~V-N6>1%ys5chC-gu zco>$Vbob#FrjA%0MHBjtX#3WBYf{6e8*a$3qhG?&Alq_7tm9dEM}MHQ2g!VKpGxRl zp?in&jO>@|%_v2o61x8697XoapD>tT8f~^dH zwGsa%nE$$Nm>J434P(t50&ly7HW(TL6!_#O^JjMOiI33gr8j!Ucvd$*^hVv3FsO~< zv3g*M5@I{y(D;dbto1Oq)V})CV-j)+4|fJak>BMUK8Z1X;IYHEfh-7dXLob{O1hO$ZNRuSAT>p3(H|OoUcoA3{v>GiB`3bB z`iPp)in^8b8v$B)MFuXq(AF1Zc@weDkR#6h4YekScP}gElnQVv#9SGs{RwK_PPoXWppvpj?f!kZXI17lG2fA^M+4JR``AW)_P1tYKCq4D zlbVCt{vCC$(i1|ix;h^`BEx3&lg4lPXd5qPCpOU^exLt9n4iTOA%A#U&`#`)dBlO1 z0_SIRll|1ry^gUeY@2`J6Ny_+fY<3G=Uv;VuUK7Xy6mvG`xHvWHC7 zY7*qDOd}(~UsPk3OY%|;v5lFg%FK!~$>G#4nvBFuw!q}hGWJk^6_%A;h7PdlNLI|N z;PhW%{$y~cS3DjY%8OQIZn{RPIAJ-LiaT=)I`t#?HOd~g^My{h1k=;b;jcu_oZ_%% zwX$DQVqPY3=G@&NmaMvDVOG>SDe+G$BbJGSV22^4NZ6*nMz@iP!ggMlNnbl2i}D4r zIP226XN~yvtc8=fPRQ9gWk<(`bC-uZAz?I8*OqVoQOz&w>eYH6{`k(}D9IZ|gx zSi>}PG6@g^C7bz<<{5eS$UD{Cin0(xicaW{9pubdN#m;hNM{U1i1mdGg`AQb)qUPW z*E6QxZF59vs^^MY__WHA4;8DNCY@plTYE4>4a9Q~(TK)_$3!pMkDFs^wv{(zV?m73 z55oCy6qS##Jf6(jBcHm!?eaDQE_?3xrypw_KmXy2?_?RY5qUWODC&ZYEo{SxWeWX^ ze6GOW3|1KuF)~Q zW{qB{AIZHW(iK)71mJ!s6+4BepVqm_&+eUJ7HfnS)1u|uTX}iPwALD7QnyI@26>TZ zv!eVly?(#wK80L$5R0%yv_&P$q)WmG^;#RwdG713PztO=f=R=$yj1@os|ex?f7eS1 zWt8yQ)`%w1L16Lh$|#TBpBE)UTK&bO!!>n^mfsxB=y+qpidBnB&bsxh7+$0Pg@RL@JRm3>#fFO|4|+ zJ0jwDlRPeHx+ih^K3yNV!`}_k`$6!_z?LKE)Of7$dYq&0+%0mKcv_-|G;PM`Pg=a* z&>qxZiJzb#HuJzUm>)xrj`b~$U__CTznFY*1zS`Kgd*_?U!@iyOF9J($bg;2 z*gtToZ&UdrC&yx#PRFD(6Emx;+#$^HS&%bX{Wc8Uum1v91!`$JXPWrKmhUR1b6pHCaB?x%-mqX@ zymqQ5AH5;OqfT)-b$IHri_4Bq$gc>;&-9Y{>&AcF3xQwYjAk_8m}epSY#J)qqlO=5 zZrzE0Q14cZQK%SIm92@nJn6DImYZMQYiKJ`D>6H60nY+{PY6MST*jAg#{PW5d$zx2 z7_Os|*}WNId|s}=j=a%sFk+3%gYtPsUYHA-Z{u!`_4MbIdB%#QIXuDZ(^@b=V@PI_ zKT`UH*x}fW9a^VcSk|3o&_R>jGKZA0MaDIr)?#^xhTH10#&;>(jHs30&vzXW>O2e2 zAF=hw~%p9F!P z;K7!a@HUgAe*hoF)|LC-04)NlZuU-pg$Unupr1W|`T&Z;Gc+bhe^m59?St#DGd}5{ zIzs|wnCRv2W@e9x!Drbe`Az4dQ{|FODoFS-Fcob(JbPdY&rv>@58R#hKHFp9hUv1x zsAJQo;t{S5i!=jRBq~Lm6c^Qeqq1lzN4;RuSlMEi4VWz$AuhBE;r+3BCGg)^`OD|S z^akHI6;$q0g+-~Oj==2RQ*^OREE@3f3aN!j^0QKYd7#KU2*XaT?#}qDo=qwMNsuPd z;maB>qt!=IH|9h%IE?8w)?>#J6132FuQm25*TMw<2Hj{tC&jjmm)!=#HU!2qD>i?F zwoKC=euCwdiOr%MJ9Ku+=Mkz4NB6ftr<^{Y+mNH-fk)ovP@Kz=yB-$S`7Ck9H)Bi` zB9sEp^8VDzp<2@xS1!3m;$wQg<>CozD0V4j&zX1(%=10lW38X1;z4MJqeBW#qn>vi z!|nXul~z4h!+fuM&e-a0bKyCdRggHZq^)TwyH#Gp$0m71{M7|IISk1$zrBB^6SvN( zM;xJ#Om?LrE#og#mmhQyiN*G|DohowMSbRbvD|n$N1BH?IGM@N-BDZPo2N-Md#ojvY_G3Peb-Xz1MBzE~BXUucaxBJYjn<3Lo<57Z-gXGCTdv1%hMZ1%=( z&{dQ&(m%BWztK&2jYVy!7s?U#Mp~|Wz5C|hZ0*<+vbUO!MHTvMxzGJ)t!JzNkH&&m z`}oWa6~gMrrRl<{TTgU9rUKbWu#q( zaDe+kF;%v;ngY#3iiVkFN8$QIs|MxgQ=~HSXKk@5CYSl4Qb%xo#{#@R&CHr}NW&b0 zh(TQ9pCL)C>Ty=2JxkLfXmO*DDREPa?FEX9KI^zX{xzne3?^vTyb@r5?MG~CfFWP0 zf}&g1*(*=b@y!d5D3`_)wBY=b12b8c9ULBpN{giB^5^TNyD$=UYeH0yRg>Bm$t6zX}TsYT$o znyH!#+-7`9w-|oSE>`GTMKymy>BAo$oosgW_)-uqI>*;CQazhEmzeKdul5JqjJP1% zrm3!6X9|=D&F}|~24F7nRvH*vXnORD9E~3P`)j^iHMhK$US!<4JMdudqJGPN z6aYh~dC7T}Phj0A9*XR3!z)qOm}TmINmsU<&h_e~Ey=x6DX3LG?$Itz!}sRY zfqQj@mfan6{mp^^h7-ni-0t1r-C=K5dZY2d>-7kyaIcZgbW+q;{V$V~mNqs5LoMCj ziO;2XUV=m4DLS4qdw z!_PMoXV|4$bL=(oX+g<^>|*mPR$bfjG8GOvjqbJ=Q@jNzt3`DO-#m544Xtp^k`Ql} z+r#$Cy9fJHj{2w{*R1x+>)n@=lBr^me%zR#CP$KQ29u*8B?h325khV_J*HAdNeBsl zQ*}Yk{@(nBJb$tynG3?q2eP65ug4riKI%z3I>Xl#M(gDK8lxC9-o9=|gKNPh-6iSd zkH06X^YXtLyvh=?48ryizMBvi+QLly{8TZJUe)!T1BW(MqR)?Zf(@?tm+EKJm7yP9 z*}}6b^!`%04pM1D*5FZ*tYa5}ez6uQoPnIN@rJ~ap_H))i&-yUy@g7Qe>7iV4j3PS z!?Q{nYix*<={dGVGfXCBBZM^78{m|5mJdxrS7=H=8&3B24}xLv$V0k8kR)>l;A+Zx ze`zsbmm{5()v!D2U$#F{4JgYOhumx4kz*PAZqpIYkUNF+ql)787(&`_ZR=u-YDpzM zY(DtM2MZ+P^Hoks#heOBD)2Jb%IslbEODDT2d3yNKA#&kOS4hx|O8zqCQCO z$m%KO7H{+pxMcRBj}!9UpC2*@JC_L&F2WQG^0l44|dWYRK+ z%O`<%_~xH8JgxDnrvJ_95cgG456_KAbibfdwZh7&R6s-;$U+^}GLW7f)BHSLN3sv& zUnWc=+P+ojN5dbH^*4isfgJeeEzQz#T@kCX#YbIZ9!%=+cI3zJK<`^|Yk~aeXV-{} zK)k6lffQEe>g(|lo7v%kpL>((yC~;WTl-L5{7Xaf6SQhj1NMIXQTt;WP83XVp}&|s zuCh>aIz8_;RQDKfVqt{iA1$E5@Nm<#Qby~#xVpYPnE~L zAiFQfyV-mq-M=vO$RfT8gTx(=uin_=zoO{fVDTnwgbw?GEN2o*HTboxE5gjM>OCAY z(kAJp()gudEZ72}#5T8wB-SV9EW5Ai#2+YN@MmuVeWpW7=&NT^s}AYw8GN_obRq<_Fwh$ zL4T3BMnHC9iQ*?GAESaU<4)y^p?9l@88k9q{X@KcS;2k2RAIMos*!h`E#qQJFL#?5 zE834Dm>P!_bU1eqg}5WbmFAyNUbdpXHzJdDTO8$0B4iD%5ufgkU^7B>u&Eb7(PW8R zpeu4Gqi#@xB6cUWr8~bkW_}DjB+}QtpV*ciCU!d^YhZAbQs9J=>~opR)yKbb z#K9`JUmkY#)%obzR;ky0DRdF7?js*^_DEh7`WHcdY!s1@hb#OtiqaX~V`QJ={=Hp= zSuWKs2cLf1-JL-8z6`{!Mj2XS3C%)|)i3QsVa<0!zXlNyhs+Jw;ay`WV;&MN3JZR7h#B-K1lWk@{lVRm zZZJo6-OWy2FbxW(F@MB7ef~e%&M8Qgpxe@I+qP}nwr$(CPTRIm+qP}nw%v2?Klg3! z#5~PoMIv@(MeNGRo$FhJR#_?zH_*RFr?3KJ94@HfgOowlX}&ofxN)~0ExcqY}?clU(>e{9s}F7|Yb>S!E$1S{Qxl+wtdp|C3_Y>6#Oe#&ybByMBdFAh3e z#Cy5b6pi4pe8?l%z*^TLmr*-mY;iE^KAF^*>mZZOu@{2wZmMw!Hf1P!<0ytkmUGBh zg=T5#A6&pCq|GF3?lCHPq7Hs|OQES!Q%k9!F@adYoTJl1kM{WY)ur+P<vF27$(%Vm;IT44E+;6~xZoERob64cgr+UFy}r zc2*5;v>egnog2L+_>L0t4at|trfDOKqj_DYF)^H8YPSq7QftRuTU&7=2ZKE5OfbVr z`GfqVmYih^N6{<_*H;bKNA8Ab*-Q$V9W=w^nX1+@&sAIlvUn3M*2FuZ`ojy!qkq?x(~pc}BNLXwJ}j{uE0sqK=71R+7g`9O`Zyr1CGV2HKEYMW)=4U>kU^q#$PB>UX!?02 z*a3-zAi4XBV#jP>&+7h0l)h=UO-Qec7YaIh)4&Sn}R4qB$c^_i!aqq5e7#7 z?-+@O>IPJQm=cee>|3wl_+ZiprZJFw%bwy7HHG6e%xUo1)uxi9We~(@x7^-}AIQlK z3epJfE?y7XN(>fbPhz>rY6gf&FJ-JWdJsOf7d!Kh3Ek8~yFL35h4RE;md+$gNwRGY zBedd z#J8cN;Y9=L7>-4oMD|fji9;5S3Kih|8F6pIQqITpE}Zfo*?1#iu+)Uy_-5*L>>*>M zS`cT|R*ryE8Jx(CeJUXJ_gx z%Ix-p&0Q16k7EdEY7nZer{~wl_fUDM%6{|y=bofE0NtPu%+9?QGVCQxLb9u!o3 z98UYtCMWZCWQL@a&e+wFW$CUS;_2I*BPUBys@m00=ifU6y9UW-rOS+}c>PhV9O#x- z=8S(&CgL4hJP`RLo`*ebAbYE#6l6Utf+sU^)SWe^1)9qu|LuRaDyEM4!>lzt>2HM~ z;0B`2ENyV(`f7jUNpm#RiM%Y;Nj%7VPQnvB?(0v2g6Bq4I;AI@Hj_7$%4;;EX126` z98`?i`j*zLbVUcZQ4IArCRjWisnoa#nf2$tne^wAgqR)}f8-F_e;>-#7>iNy#n7BJ z0asZ4GP(Vf?f@l7TYVRyd5`5$f6A{CZDyA$MU9ncdrjtQP?O+7&_#Z#(?4NZ`@0lS zo}?6p**fDR>IMH5UeIaVy9ny_P!lp^t!QYgvvG)n*r(#RRAU8Vwl0?RR^uNcCRK_9 zWYT!z7D1KWqLjE0ct|@{z`Y^9euk$(d4+*IV{E#8$h+$s?hrIk3IptAJu=1WsHn2j z%q6q#a}JIQdr_=osd-QO>Up$1;Hz9phO&j}3K^qXll*(6S?e|e-HkiiXU@&g-s0bh zM{mCTQpmNk_bbE&uuYqXx0-(=3hXyTBjkn@RsL07a?;?;QF+xZ6S-M}I6KGmSrEBU zc|MI?`ZsJ=bM^G9-MM}rjHZ%8d{vA(@N`l7*!xl8Vm;(6HcuBVd}Q{`1THzz%(LfK zu9gv(h4qbG9mqxMi11slB$EwK=ug*UH1=Q#ij@3dd89e=YpT!=eI>xb{ydP3inHs& z>9n(S^f6jvu>>5`_~?}%6CorPiHsBE+-&`|e%3OYbAj>fH5@~IHA;&^C@k((C8Ja@ zQ5kcAjk8qJ%USPB6-Wfll;X}>R8#ZPaU90OdXJI zr~1}PlV+8I;MEeB~#KBYUs|qF1!b4qOP&_ckr>sWwzFfyI25|RV z)C!oH$WLz;J?+wHD-a(hLXp(w@Uy_zUWN+A6e?2dFZF4SB9zA~Mx0PyR`TI38v>>% z(O?oD(YgfTG>PS>ITI||t$Ijz+VwQi>;PU=o=)8PGUe2sTCLb>uYo&O3{L0RhCK$@ zp2$TnftYKqzP;r5nzk#aTFc|f>)8KmFWp_wBXQWV0j-~nEB*>G(x z0J)vS9HCWHqVe8wnr;g&nX7w_CQ&&wbX_|8(hV#++A)UbYp+(#!N6^&xrICf|HLI> z?m$a%tvm&Mt;6n*uRvIBm0Rn;38nf|W;mw*ibJ;~X%9+2K_!J`r0W_OXs$ib9B;Jmm~~^u z2&Mz{(KZ5C@L}D1R^Eu5BxbMjXG)BC_NS+(_sb?b$G&U%+N*=mB7KM2rl|MF2*?hW z5b$t<>xN}x?i}-CU5#R@d5essS_PgKS~YwsQ^OHXC}o!BZL|aMm?bDl7v8k9N10AZ zKih?iMQ7bpOi2<)fdB76@B`1Po5Xlq%MeeSd?2aZ4(g_>i1Zt=Yp&u~ zs)-OlwNlkid`d>ScXj6e`MYS$WBi!9&s!oC9N|VoHSkh$hA4K>_@Zx1TStL)^AiyA zUmq>`12=zk>wHnzV_6rS4q+|8+Em9N zuGtcQp!|U{%kbWp!nA?3at>M8F-iPa$%wVa3}5A9xPXG`-dJen04Mgx&E(_}JI)05 za?{kHu(xI5-F%+=+xA>^WLwOn5kNm)sT@tbzGuWbYsU*0^6hUh;Ra%MX-<>1#x^8f z-R}-gTTkxGD-A_-wY3s=K)`wlSO8`;)j=ra9znl_rjM|T&!gIjRh7p-()buHy}hn? z6Pz|9l)#k1qsr<46c&1Lm+e@8(wMltGB7rdc)(1gW*PmFz$6&x)HBd`kILL3CTEL1 znHxaRhwnE=oUfJxy*-3N7v8yO(aRv!cDLt1H9XU8!YK66Ita>9M19@5da^J5 zx|3FB^TX2cBj*kAOa>jr7xh|Aq)mUe9+tQ&*ZNPw?#G{ku()ryV)h8!AUv%?1mA=b z0{p{mPqcsG`}I+}f(D=2>K83(GwzA4Xc7{9kG+}p;`=<4#u3bjj-0(v+h=XPF3UMIxmgQ8*3`buTHBL< z;CVbij#@1Iu}3KpB}y+P;$moVAl?%2IoQJ?mjxeMmEU5=u8}X_~n& zWLFoVUQ+1L43-l}ol7$AIj_)76_=(i(B3R#MJcRmWJ@n8NP|obKkiP~ zw?ZH@cr(nl;!rie$mc;Nw-afu^Zu5il?>keWa^2_Nh6_Wfpd2E;uV{ppV*3SEqYQl z0bG-!{a`)QDn_mYUk_B3XAuUeks`Er#1E&k3Xv!4dPJK0WLVloRv-_L#k9&s%;kZm z1{Mqao-CdbKU47fQXQ%Wwypt1EnBix-aL;6@hFWTeKqC>(c|T*O>5F!Wlw>p1(|Ji zp4)62)5fa=ZprIJ?;U?SxErO)n4O#JlMHJnb(qB;E-2qZZdg(1^TUWO>Xqd4D1$l5 z%7I0uWycA8i{0|F1>A?d+CH_EZcS!)y!(aGjGFWPcM3n~@ORfI!w3yf!>9(?uW zQF9vu6y<)~B|uMua~_Th`m5NwX(}1Ye*IyNudhzwbzIpT2mbRydU2`Ewg}}@Na`9o zy=?}6Sw{)d^?dJ(=eaIu8BqEZdk~>Rkou2 zZ12vmZqi5`PB;788aRSfgixbr8%9LJ9Vj4>*G8^S%+)4I(plQ9omHhLn!&`?J z*W4@P#RQdnT}70dEd9aFGkcM23tFZtW0;I(1}5efB}Q8rPxZLx`DJRz*^6gDGd^uT zw=ZY);%~v%WkwyK0Xy5g1d%_$+8C=ZWX#R&+ML}zx8X|s6yQz|8*Q7$g@u4J;^~rh+WRYR7^Qs2EoR->SMpgy)1|fr#f=;t|=yb zXqN=8OlPro{^<_rBMHe8kWJr{eRUN|lJ8va!h+ta0;a@7^n3E+R*NFFiLh5p}$Wpj|NN|DuRB**vqdAJ)?EEMJtUvBmm@88{e1E{<>MGt=J7{!`%ojZMw*?0rPqA}i zh%?-=rB2T&c1rLXUgAh|C5#2w-CCk0%%>}Ms&lp+Zq210Lmp!I;`?`@aE>U86JOH? z##ny>8Bw*2zyPJtDr$$Pvle{JdjS`}3@#obfnBLCtD*KQt<~9FBs5C^+VYW;xCkl4 z#%mx|ty`Z^is}HYPTO3pwsv{cQe{j>23_X!7U2%MmTjlIk zs#@|s7_H2=9zUj2Mir!}vtk~GKcnN}%Mq%zNdIV##(_9o_i#-VPHZ(x5-~ZqYxjFz zGL6o7`(8urq8jwbuLfRpuGgo3nMlp6R$b{|Pxy=)s?Ga{uteLn*(1B|@&s}H+qo#D#FC%gBUun4m<EKV_8{z9YaE*RAT(I>eYw#QAv8s-(Kq-gz-As_{RmnDQOD1}NkTQ?IQtx}!ww_R;P5qA>tF9`+*7a6Ne zLG1})4~hW9N?$Vsn}tdAOa@s%io(d4ocQq0b7s0+P~Dz;G~i!L1zC0-pO}~&Pl~hf zCjSBE%t$%TXQ+WfaPwwv*?tF(j&p!i1Ab$wP*PRUU5D=$i@4SVA%P%Aoe{^B$ro+J zaOIRQ!^Kv@RpGVvp_AIigKbk&WDiLYR42R7!W3d5k1in3gHrWiONg@xR;+V3L#;q;zNi)!qyUp0+Ma4r@@6T#Y;=%A^g znxhbd`5=W?4259Kp(`hS5Ym+FwL3_{(os(v86enzv|K1A1Om)piEVO~F)oe|3LC-j zz14WV08s|;TsZCRAKfbComxx*4)-YJzO(okyn@1WAdj1`>K1{y_{Pd%28!?nX!a2G z!U+0zzY1vR9WAhrHx|-zLh6mq?GWAtLhx#wk<;Y$R#^DpkI$A5&}qxmlHK(NcvE8t zZ+r~5T3c@SX(=su=#+4Vy6@js9KiKTatss{-GIcFH~N7`(CXSrAmDcfI5C_ew7B6E zsL;8V#_?A-hUmr~RmQmQhL@>B*9AkuiO@2olCY`sf@Ne9tvJ#vM7d6sqB8DyjYgYX zv|I4ymI)y=;I*|u03++f0A6b&snMScxtA!&HK7CYQa~j_@Y^QzIAtxaX1e1{ATubh zO-}6SuHZF^N^}oi@ItbD zYGWsK=atMxnVN{PCI0QkqN+G!qRYXEwNK0Z4)|4e&^@&pDigeYnRQs*AK%E#n#a!)|>!>=He^e6BS5lh=qNEyKzNlEa_!K zy0zN0M993+d{0|Z-fD^|EhDdvEdPeQLSAU!DQ@Ze1=Q-d?iDhTSQ=0p4d$U^#Y#n! zOaY~tQ`D-gX7cf=0i0xsTRGGhP@p42w>%Ys_{gQzgu97W;SD}{wG^3$B8v4$gF(2{ zB&{c?^(!D2WwOdf9Fh58CgaUg%xMtV_)INzXcNlC+Ev|%CKLlWk9LHn9Ox%LM1#iV z1HnCIR;3ry2tP#9V!}128r+4SOzWrAc|EowsXa>T8Zq$JMb5bI^f`}JE*QgXz0B`#!W?w zDh~%eBdXhT%c>~ABWyxll++)){vD(59ODl}a6taZ42iZz9Pk%TSXvDVzi}eYlmj`V z3W`WzahvVkLBJ6q;vm|3;g!OlCZl%MiQwV27Fr0ex!#`T8bD2MW6;iOQ6B7u(CMke z>N_BlYDf}fhGN4MjAD<$N!?6a`f+s;r?BpIs~(9399@5*d<|qUnXqUe(e5qY1F|if z%J8SQ&^{FX*0xz3PzDtm3^Eang#ojhu5%Immdu~iFE(VH&t;&9eW5a6)%~F4N_!1a z(GE3Om_MqEPk!7FIs?@Oyy}cf{6%vq78ktn^W~OkUli{B9?ohT zYN=RbMFu=r$D1%(*Blo4EgoH7X#5>Ty6$5@je>YxGRF&)llal!=F~8d+-l_l$7XnC zhx+#hs8@oq`k9^D8>{M7jd_@Zx;plH^@Qq_g@bz?SSkk@s@h%|FIn2S zbp5?CjkVEPcbr7k)nu4%c;^kU8&e#5qQ@W+zP z0nr^~S4|*2#I*6L3$(sHh2cnqE5{QAC}1h7H*g>z`sdmlR7&@Z~Rq6Tks>R3G+CD{x;@y!jLZ>{&1T25^ye5JD3SU zSzZaYQ3P%@nbA({BQiIIWGaS9wJG_m^`^u@gVTbtkBkOtxxOz(SsEXZXgl`S*3aiS zzDuwvzt+AyLq?^|crTsG&t(aJ=BGbsUN zU=nAop9v^J?o?ATqX=l%^4V1?Os5ie&d;P}&l~c9;i(>xQ1GZCoW%wI#OV~E+`pm6 zX9k~C122a#q#KBJ6dGd6Nk%m88pOAymAzO0@;i0_XJE1gv$!!RAk@4X2jQUK&t;np zBZEx=V_-=&a@%X?l~lX`3{q*ixB|82JrfE?F#1ok-dHXzJ!V@4g&yT7@2ItL*o>?U z-?-d-VhC;1;{a5iDQ?r{0b4ybo#pQY2l;AT<#QcoW$I48@(hWBwb$Ni_XhFM?|w-^ z4CRezz(gFbdbMuh1^bXFW@{Rm97RHGHQSv626wmXdrgreqf>qr1|1xXJ2RZOg=Zo! zb@++|Sbz<(eJ7H;mq2dlBj;Q9mPAnxy7^qddh!=>O3#d$=#vd6{M&!22;fE z9T-GgwM4;6kIXUJy9Y>7ZoU_UD^X9=jl}hxgNO{DfKO~KF%U9vkI*#+M#eJx8m0>C z3i$BNG)B)s2hx5}pCr^gG`916BOEQ)h@&%s z3hpnHAHNk>0nGlGt1V#k<+SJrlqGZvP)xR`84%S;zsQxX5ipMq>vTrRj9k0Xd}}fv z5IUF(q9@nDcoLLIE&cVze}oIK6eTM&lIF{13*-MbRWthA?PED zm*ek+T3)Cy;v#Ov2XwwUy@{(NU5+u$20IOx#ft+HXkOF_8l7*w2(8r=2N?=xN6EX4BFSzcoE0D<&mA2U1C zjZ|f@2XyjP|1p(nSq_LF*Y(Wd?SUvkLYY!_aB%bYRmoh*O|MDSak2eVw7UZ7?wG4y zKP>A|x5UF6v}E`%YygKq*37EV^t+eaY=6%>3X~lH(#9`I6WcZiXTi9tjYhiWUrd{z)XIq}Y21w7WWiwe;?x2uerH zJrgguCSE^})z{#Sc6!W<;NfEE)`RV}cdeena}Gdem(RYQY8kIXd*fvYn+#!LLgcR_ zs=FU;C`*$9G{ArqEanLjo9W!4jf3hYR7E?F^bw+OCyCv_%H{0Y_7m6ud5ESNo-l+U zws~Me1ZWZ?{STG+HJr(dFpT~2n0c4ELkYh&w^CMjWc3G(Z(jO3iD8n%;P{MC0np?Z zY_^H0Ob8cVt<2i-*U$bRFI3=++Gtzg%`OCPIU}LpKmhAhTu)JD+n4sab>M=xUXB-6 zXr(uBH=LhH@xjOAeGL`DHkN;4yFI=) zeju!NL*7)XUD_VZLha*xGcIFHnT7BjBcs~;ahLOn65Dc{P9*KviErjJbQe4JS}S)1 zGp;O>AZqVzMWuUU+dG{`N?&_F~l5FQ&5|m`F+%a&1g$z zj54^BzqycCL%?p*@*vcN&DvI?5R8ax^9$OVHvL|%bz?wZX-Q@d(cBqUL1;uxmFTAF0b2L8 z*W3yh#5umFda@x7cL(^{>af;y^s2lHPQ?zrFgXO`0+&Oh}qnJan6SYq~&8_&34lJPDhg+e!hWa3b}PXIe#s3*@^o>a8Y-4K~&ZVdM@h(v!_%8e?ii za{)~+(?q==R5Y{EVR)n$);%)^u#6_M8lO_*Nqm!zI+LI(I2da`@BlVFITv9XN^{uU zk-wKq^QQQe!4dZC+x*@U>K=FvMO#pwN%KS>k9NDEJ3*F(aQTFcG1qhr{GTQA(@x|HAkhg-chPQ@z*TYw9DK~Mkz(!kF|()sBm zr9kwTmw?arP{XhU^DTVR(u=jdF?Fc6E{ERRs!ny=4|x~icgy)1@S5~IsHHLoV8Y|&QA900sMs>i@fZ1Y1D7blI|^S zw;Y=Xp&&rh`BVMBBKJ9!Awk+o@ zS=<(U+15VMYC#z9zSt{6FSN)*r7EvDym8cbWdO07!6J2a8CDA*g;t6!@mfvj!u&B$ zA7Iks_<`v_F+z{vHD0c6g&6rYMXRwpWOsR+b8|I=xL8Qf9R&w0BB1)a4L|$?1h6L! zXFp`T(Docc_;8hqu9*aER{PiM8(Rb1#rvZac7) zXalV-0?iacI@Ky1I`ULTToVY5h0J4MlWq~{Ui8)ioCWG}+K9)&IRm)sv{kcPn~+>- z0#q$CXt@U4)%u%QK8$a<0&-mffPjF*)LwU}fce@*%TAlD^NI}m%-5u2CnkM;w$Uil zMn2oD$KwMNPEPOTD)bNM&s$e+efHDq2b&JPR`Kb6({|>h$H(<22PX#Wto0+_m(K53 zL+>8DoxGMuvp~VL+p@|Q&1&+m@AEFt-t3yPN8}j)HjZ8$IeEF&nYhomeZtTuZnRb zojg3lbngwp`E%)J3%UHYRLxybDC)b2%?3#LPm2U5LUgf+xEvHG8a!%_wbg9yC9qDnYUMww& zivCBu>7kkD^A%olf%dzK+4LvOc0Uc@tDrAQLfbAS>5bXhG}^eXWR1s0yYlr~k;=)3 zBOtFSVtxE1{oO+`8Z72fMdys5gP@-djQ*>7H4}l4!F`ftpX{6(cQkMg>!lse2tar+ z^mp?|HcksQOykE}69w`co}JlKdaRX?FAfvGz=ET;K>;&YE+^DBFs88n`^Id@5V-WT zwW*M5oHw9!oVBw&W1MCqy_0DT*taUPDOP6CI8wBV;z`dT#Fzf?S~EuLGX=QCa%AD* z=+fnAKOo?JD>ZcaHl@zo8g{YCn=YOz+jOhsJl7{4OgXxHZmzYk^y@&Iv-D>@cTL|4 zvg$Q2RJsC`)}^EAWBY%hK~Cc*2Poy9^M~VPV|N63g&yB*hmlHaC@!k!0{lpdZsFbr zP-=kg!oIDmtCy)*Od_-Cv^RLBN)cwQ;uLIJrQlT*U*1ZF#Ek1xH&B|nbmA5Uav2AI z+=PJ)i~QH2suXaDb@+rt60%mR%PX2UmT@7MbYNh`{*E^s^@nTPSltITC&t}GZw z_#pcnEw26HY(!5?uY2yN7&dXZlYe#J@ivP3X+1+x`2k0lFN6vf;DX7t_X~OTVWf9m zV}KXMKrWGdqVMkr&JouEGE_;ur~L98kL=^bG14Dd+Yy_=@&(HpVX2_ahl5CJvHKEv_4UqesxP`k#7J3_x z4^pGcXeLL~Oa(CPW$_`~H)t+qKT>c6=k(I#rhd!OSae2SbI3UjcR(j0m zuEOFQWcC7`)Q{~=y&>dsBn-!k1-!y4Yd@HNo1`mHSWn3rZ@Un+F-tpsBL*V>V6c#? zIpE}M0=3d`YFUv6@pc8hmm_q3VaYN)`KCN{FMn1O(1lj_Zu9W4)xXE@`}^}XuV;(* zJA|lzwE$<2)m4zv^)pG^Y(VfiN%>qDxw4Zvp$8zAKzwQxrJlt_={H0xu`^44!D^R} z04P}Eb^01jtc1z=UhoJSqrpGbKn3fe9mn|ZGw-p5q7Xm2weIh}JDWS2Iv{1JXwL(< z3d7>9&lqNZ!|d|>6}^SG{90zW)wkyBn%C-wmrw5x#vL9@FW`c2RkXD6syRI!D%D8t zxWJwEgHr9vty{+Zn677RUGD^@322F(osO30pLoV7n zH+>IE{%HIsK&XK0pvDyiuksc{W^Bu^-|Ccg0q9wRh@Y_3(GkuL_e)E%j~0RU*4rvP zeB9?hd*J_p!1xW0IYa-?J$Mom008#C35+hDHm3hUFD_|n$7OM#`04-s1$@uhN0LaQ zDBh3(vn^@SvNS8{;WA)Si^0VtC+)oOzh#UMVJVQMa4EyG1TgkLeQbYsVDe2Wn_5H4 zQ0xaj+FBnY;6Y+Mt_1jgM9CyJF@bu-VD6U;a3c}6H>>?gH?tZYJ7gxD0D#B^J=6UA zJk-!|mQl>niEKTvFH2++iOMsPl2T3D?^udnGUa6JZBdf$q(UUKaE29+GAW#)E^&$(%x$IL(`I_$(tkSxm`qYDv5T*bhz1~h(iiy!kPmrL7h(h`Zzfq-=jh=5VU%u z+2WPH@jp&q+gPS7aeoL4N;(|N=^0|IveHc3Xfz$^axkPEU)XfP3CsYW3u-FWJ{OCY#3r zonwJh|JMMfL_7Cw>M1X*s_w`)(E5!ieE+0Tu9vVdoAspREZe(3ayITAno}sH48e5+r*JQ?PF}Ge>zv?%YX~c zH&t%?qkk3ZiZL{uevEHQARA}IE@)DA!%tj_639f?DubKo8^qzT?GlHiwUEsvw zFUQHWZ&0=aGmkjp zA+BGjW-tGYxBgGID`**Gt{Er*fbzf0@_(`9`j0JUcG6~S76U@qE%h@l%`P%9q$XNu z)dX3&w|cc#o7)sZ)mr+-jHK#rH!$awR6>OX=^aeVNfzeU?A3Wb>UyB-LKA0<7$NB# zl=8S+&LaNqBtglL=Z>0A2AY!>hqh@B4b9wd@h+TzG3o$=HkFcx>>MZqZY z=K4(UuO{mgF6b_-pe7?n47rSxh7xiivs=UTj#MBtF&oyVACUU|w$Tq=Vs7^WOGkJ) z=gxJvK!y(CAt|c!IN)R?+J-b&YZ`sZt4{>kHUjV{`RLoXM60YD!$Qc$x}eSNMxRV4ei+enRa=MU0}$no@licg0g0$eEUd@IBxZIczQtI8k z^1Wgeg~#fLTC-o&qmKJF?qNCazb4C$fO7d_^~s7`Wr$*g$m)fr{MHnVei&Cj(LVVm zpfhO=sR^Yhxvw<{O_ z^zi5Kd-dXQuHDY{*VC_aRGz*^dmHb5utl5KZkL{4?)JRb?s*gTbaJwlzjpQW_yeu? zviD8aeM^4X&m&zExSFM)9I8>Z$ccZNOrj*vIE^tiKw z(fQAb4!@v-*oJmi*zw*N4{o*w-jx@Qli7yJYUlA82d%s~d8&9gZ8RtGwa%^EWIwjB zgOMl0iCH1;y+T?r?SsXbEX~w27+7`!n@1G{bZqGNlMFdpMdb>O>JKIHwM?9=lE$40 zgd7dXVFdF+tViWlSq>pby@EDIHeySj)qW#l=T5u~LzPqYI?sBj;d9ASGw|Q}E%8nv>#@At&=<-J2LS zE3`OH$_9mQ9Vyug8>N@D#X=?9!BWQ7bvE>4H({K`U9#%5G6>kl+c&Zs^4dQ|;5RS248e_bun)-bOZ{7m83r zuGI$Fn2f6_5o0p$#+Vf(t*X=w1)ib0Dd@ooo%+mHjTnO~=z2okxy)4Cd9ABa(bWJc zS{Q0)+jd&5M`+$mipNkg+4Y^BLe?O|8O763!^4$^e=QBl&TA;>VW!mb1pn3a-d3+p zErHETMJ`$QZ$>=cgXI5eEJ34D`?n&8H*^q5nM98U30VCO5&MEl7-ne4e+V?H#29c$ z4g!^5th7rEq0lN3Vb+aij+x8cB0!^-A;N4Ft=4_60G3xajhYFn+4d>b(p%VfEuHP^ zwc@8YD6C}Jtczak2928Q3|MVepa!c-St9$5{>2&B52%HJR;^+CU$N9-^=L@0a=XY_ zO<2LB{Y8W^$GE8*s57KC!~7QzNGiumvCs(5$`o8yzTT>V6XGw&GF}4~C}M6{ zZqo$|%;Q%6c@Uk%dMie0AjA4e*%W9_Oc+30Rz(Ok#99hrKNHcIAcRp&&2G>Zd}XzM zf+W%uWqGP~>r#zXTO@4D92@oJE-17Etlvjnokff6%A~t(jfpo=?%xv6M!3Kw{Svvu z=EnT|3RRl^iz1c00q*2*@qYXkWq^pVk)$`n4-uRs&_HencNZPGvx1cSQgsFswF@Eb zovxXtbK{v%z<;^EZ4-&^ffWAkf>fR4LG;S9?qi5rlo<#kmWy^xu6B{Fsv3s37g&EK79pEE511r5nZB90A@i^R3bP_nueA20qY z6}55h8llXHQ$QPb7HN!MCYdg_*pI-$1s!-sP{1Eo#4r-}5b3j^D&&&l(ZnO6;TZwP z^tzb9+}A@3QW#A0)s|>4FkrzZ=PTI<6D{HLB-e zujD++l{QiY56Rt`Sv=V10}Kz&otJ4+tXS_{A=$C9{!dBTvey+!KZO;$8|+-^DVt3{ zqfumNuAampoH|MQDU-oCWUI!`Ix zSBAU*AT#Y$$`V5|sD5_pE$jM4F9v=x2)Gd}n`d@@F>aUmbJRgH*}L$)M|}O(DJfa& zBI^Hh+WUQo|I7OGqVNBCnfLq1|1*pK^GMI%^|25C55naC`RecY-urXBY|sC-|J(C% zZtwq_=Kt;gZ_wB0Kby(kpNGM?-@|8k|L@1$+uE4VsNaV{`hR^Ve`e_~{ePcF`G4q>b@f*Ixr>A?rKE1TQ*w5t8#be!IvYc$9ZETs^Y_OPYpsj4F+HA0#Y@zLJ zx!QEHn7n^IUXAjvQ(yhnbMMu^Ki=W_s_gY^JBZV_I~?rx@pY#Ey`BWt+up7A|IG9M z)qlr-{mrw#%fq|5`&Ra8zZ%7#c_ovdO#AhCJM{lPK8ZcZ%(J`sm}6z1Wu9kc50!PD zp6*?fS>^f3^0oD>vEI@7ebw5_+_n3uD#<-9yFArA_q6-rv(|Y$X}vsMKl}GEqpj=w z__}_c$*=pX>HYoM(SDNO`&hd=>&&D3cu`Q^x%hD6*M9Lj{n~}Tt^bvVulMWo-q7~m z`1oqA_kF%UThkKVBD29{J6T2h7=C>3`miax8}(9ds;y?L*=o9&YOC#LyWV;{mwIUV ze*SQM_AG1UUKT~i5BK$vzP3#J{UAdBd*|eTzsjCB>OY{)zvCCesq5k>zsdCfUd-n- z_mA+`%YTEq9B$>{JG+8w{d|1PJ3B)CKXSD_{O_k}eViYu#ui!8-Ro$JXp_Q^&sSHU zKv$Uwe3-N$>u0-_zUCefuxaT^4`uF9K)R&0d|y2lbopVfNPVmzq4f z_O?q~O!&#gSC0ie*8jYH3(ZD@&141bWs8m8o%>GDUgkD*&3Y^U_g#=Uu7B?d_ANvi zd54eq+xh+Axhkf9ji)E(EBbCv*{(`PnKj*l#iYgLk)h`aYyq#;N~)(enyqGw>2j*8 zHk<8wi|J~ruQr@5XKrdG`ez(_p5tx~-Nyqy{4E4O|IecS_5!`WU)NXaj=qnxwEnIS zTh_lB?JTv$w8?BU_-N#Jk2JlPx@eWfdKxT#t$_Q(Goj7|=uX&3b~UxHdub^4)$T3#QnN2< z>}j4wpCrZJZR@w-?oKNADI2KBm2I!ByP3`IyPvtepjv)I{;@1evcH>GCtoewEphAS z)ynN;XC&3#yg_SQ&pQWMnwb53ud}%wmzqqx>KeJ`#W{I#_lxpb4`ciG)pT55*^_j> zVRoXGiAi7gTmiqcB{D^v*mZPPifWO002jaU6RWR~y`4F)Kc`id&rW5A_vf^!ir8D2 zQ`xGr9mRb)J(|bouTCCRy(Pno^I31l_U&M$9rCE{z5UkCY<8@$ft^xN_$YjTmx+D< z;x^SQV_7lB%FJW>B71XZGu;!TlH9b~)HsHFUg6>5y25?;s^pL39A$~?DjQa(Tz=4A z7^KYJuj5t9V`WI1v~)cvE;XB{PzB>TH~dTZOxZK9-_S1hbt)g9!WdwE~e(c?u9ajg{zNgK7= z+X)Ooo<90kyZBmlBwNxK_Mk0@&owmMS*dT|pp7YPeymUL;#JEx$QmvBjA#RqSDDB3 zIT}?K`!=&(pVOf_$iA%VIwpRkVs+k^45}PuyJ}@Ag^!D5_Kx#A`cqQ{Q-~Gr|8s>; z<4D}Pc)!cn%PLb;svUfN!_yO1%U8<2mbWxosuMKv;>7xjf?h|iPpxcU;bTjgz3Xwl zu;07ZUeIcYSM+fAc_xmm%=)-EMq`Gxu;AgVc6KST|_pXUB|LdMAUR&Z74fy z7~N)=a{qpApQsg&O-;({qcgdu-%4v$HLJd7tC(f_#T}|l_KUl_6B(zmlo>{3GhK5# zY|~|}t%ditIhOUyg8IHDQnxKHZizbK_)e}&+t_wE`r#t|`dv)Bmy^Rlgev^r{L3_R z@xapG%fzuA*Ihho8)q}Siw1dz_NW`PUDSym-qrQ$6K-5O=_)H}=(^T()C<4SHIr6G zycG^Eq5Z`Vi@s%H{SDC;u{A|3&2RFC*nHDw-IgpOq6F``C_dZ4QY3u1+&Us&(!Xx7 z&C#ov-NozTDdjv_Afbj28wVnv>L9N4RCa&Eb8M5hO-_ne#uc?{!y5No&VAHozPJCl zQEn9xOW5Bqw~6q-rTWj^i$k6UlkD%|-H_jJ=n^eFYB>8Mar^swudz=YFpufpFyNTm zSMYVcSKBB}<91!=l0D+SI}z>sEUNn(W=UQ_{{bQcvqug@UhWhPXp3ywEN)bHk>O_f zg~Y0THK7Me&*cp)9!$7#e}~|*=n!{FizSzeNk zkn&#=e11Lqi@Fc@w3s)wRy@8LZa6;UY?bxK_^AHmi^Qo5gshEiE_T!8@{Y$(7^&Y? z{?D227d5LM*?7v%)lK^~EjvH|5&L|Kl{#upPj_o7`&WLFQ+q<$B}oUNxqFdujYU0| zpUUp;%<`fKDelks{hvvmY|-0klwDUP>#h7XwS%#?Kd!q>GT5h+3_LmY4pyNqreaY? zn?+u5^)E4y|%1NvsmZKS=P7kS+ zh5VJ|0cmk%7K{IU&)Ch<;&duKKg3 zTv2StZFcsK#EMtiO45P!r9mwz%hIz1wLI7B-1PAy1joNtdExTNOr2hL{&CwWuO#{R z>_cqAOw8p$Mb>R8sZVqkAaj{HZLq%3 zcjT(LL!BPiQ^atKFHI|HsV;9{q%auq1^SlihOQbHp8?S^Rt2H3R|Qh{?jIu$WwD~J z;8FQH>9S)+XKu?P<5l*=MH`3%wj+A9jHOlSDRWDsi z@aLZHV#+duho74a5dy1LV#4*2WD zI;F)I&an7T@8}k>x`viaK|)pcV<(q&wvp*Qs*2dUwVA)9T&_)AscGBgl~h(Ge?;8N zO;zSrPc7tC8!S(H|H--%doBjO$?{B7pS!o_s;n-ttfzZ}K-1Nc(yuidK5oGyQN^Xj zu>|KX5X(AcxcBmWvFaZsRCJHKQfmml)c3iIg?6@mbfUg)QDOGDVAd)XKXKW~qbj3p zzM+G-Ydzl5l9xKTaeLrE`KyQ-t1lTWs%qgHv|}z1U&j+Ni7!_k#Wsahbx+Gm8B^Ed zC}Z}_9q#TM-+b>;HZrQRJ7IUyRuu6lVH|jbjhjidxkQ^sw1q@lLbRnsvnARJqOB&{ z#~xwgUaOexZ@ytXY1FFmKThAdA|rW~$u?8!)OQ&liFP(mzp*0oUmO@c)r>JmCE9tzB5?9EIb3cStOdoXz@u#~a)Q1Vb< z1j98Wc_=WG;hLE|6u=m+2a|^aOBk+)l7|9sGHzug4+Z8iZe=D91*S4?9ZVhy*fMS% zN*)RrF|0F^hXOMg)|tsefn}nIEvA~^exAxWyV6A5TyHwH=3w%_IarsOoZDPKj}f!o zbV%K)jF>H^bqAA)Well%D7mM(-j)%QVp=zpt`Vd6GgD?p^0?;uA$2p8Q=02%FnYI| zGKbQAi_!aqDf3`5ySaWy-9yRc&Gm~Iy|ALa zw{`jW33J{V{_(1eZKGCA`24+hcCI$g-@1I_ggG;Y^Hyc-7m^sXers zDeU2anbICKW?FlAXhvm&){M@EE;9}rbY?gkdd;M4(3>gP@Yqbr27{TF4HS&(0K+l5 z0~li*2N;Rr4loLnI>2a5;Q(VXr308^S_c@9QP%+#qpt%qjI$09hOdK(m~Iig!##&O9nND^5BLS6d%#7E;{hcY?g3XY zsR#UuDLmjBru2a8nAQV+!>FF1#OR)I6XSS7C5C&#EllbO)tJH)YA~fI)L~jrsK=;Y za2KO{!99%Q1&tW)1HM_l0*6#}{TH+!y8`sV}^T6u$62Qu@Mt zr1gabi0TJ)ME8Tmh~o#A2=@bPB=rLZQuu)lQu+ZCY5ib1qWZ%Ji0%)o5XT?ZAlx55 zLQ;SD1S$OCQ>635j_Y*h!X_S2oHi7Bn^T%qzHlp zqzr;Yqz!`4hzIt;Mnn&WFAygfHX}S3wjyaTe2Emn@D)-9!`Da~3_B5(2U0}m!MBLR zgWU+{!CoZg!G5IR!2zV?K|0d%AOlfDAQRC;;CsXgfh>fFz>i280!NS{1b#xw5IBys zA#egwLqUP)p^$?(p>PJ_p>P&SLm?k2LZJ{TL!k(1L*YE4hQTk09tIZ?Ck#pu9tKyC zGz@-4iZHl_lwoikX~W<*L=6WeqKCsx#0iH=gonc|Bn^jZqzH!^qzs2Tqz#99MCHR> zMCZdj#Nk6D!uimQqQ$~O(O&bB@Y1Bxd62EN%Ga4rn z5Dkxni8N^>Orj|wVG2zd3DaoWNSIEe;_wcQj>Ee&4i2+uI1Y1YQXJl+DR6k7ro>@B zO^d?<8dV5%8eIsBX&fO~(r_VI)1*RR&=f+jp(%yHq-lk)oJJMF2Q<0}R?#>jSVO}_ z@DWWaf=_4)5qwHhiol+x6@ded8U-90Jqny?oG5Un;Zfj9lSY9XO%VkiG-VWc(X>(E zO`}GGFO41z{xnWB1k&(m2&PG+A%v!ghA^5k8u&DAG(^y-V!&y1@m%4ap}^yIUqiRA z!@g4Pf)e$D67Iqh^}-S^y+lnf;VvpsFDl_KE>SNo;VvmrFDc<#mZ&XDxK<@HE-@K03kU@CWIuz zYa@jI!LZvV!zc^J8ci3jl_jlML}A7-#Ox+bOO0(>fn3-l(xhLnRprH#gJeQTGW?rD zxRJO0x~E8!DWs(pG}o+dn$HlELu4jMPLNU{O@Z{nf9(L#SF1rNCKGyk_Z1~GLVJ$Pp42e`%vx)S~{}>N1it!tNF-kzW?%S zKC;%1tU4r%Ey-d_ve=R=w*220TYl1XT%;j^%mK**QU;_IkWTp8IH6n9@hA-mWDZCk zkTM{xfONvw#tGLn@kJUE$Q+P7AZ0*W0qKOVjT1UI@uM^(kU1cEK+1r$0@4Xz8z;1H z(ug!9kU1cEK+53XwF2gC-3kh2>;=k|yUHf+uVX(OM?SNUtZ@84s&FKqct<|yk9_1I zS$#@YpOV$5|NH7wW!~S?3P>PxK=Oc;0ci!K6TUW1SeX}^RzL!o1Cj@%3`i>=o$$4B z!W(&$(+Wr+b3pQdlmTf4q!Ye2PFRuWo>o8tnFEpsqzp(aAf52FaY9wzva|vc$Q+P7 zAZ75{SwTVhnRD%K|vp?Y5AyByH!aiW0i>XzH<&hw6iJZra^82`lyhGvPp@ z-g@9cq`ptwQn0A7GbyO|QiG(J81A+n&`0Xa8oF*@ZV{)bI?UxP8~db$1BNcUIDJ=q z)nmOXXfVD!$JyM6W51`2M5aH?bh6#J$dAcdbi)(;?DBsR$9wF zvKQ^h|1LfD@OQsfu3VhA^=E3J$iwxk(P?9llh>$gdu=I~H?(Lub4OREY~Hck^Hk`_ zPyOmYYmw*Q9LVB$9x_buJhk_YX`8clD)#;y`M0Z?(v82|D=#}crNi?}8r{#PR4`)s zGS43(wkG_d>79NqrBE~G$#RV-4r`5!%_t)&qlo1LrCDflC6m|<+lWFLPZ>4LU*P9} z3nGLTJ`oY~W5UCxXBrQilR+Uq@&EgEV3(cX_JXmSJB)PZV{0epl{t+4{HIJim#*J@ zhe@M@?6Uk0D*U&8y7NI*%9(YSdE)9F<`zGGXfZQN7@Tls%NO%Ef8cQ)|7OLBlOiAY zt$WHYToQi+`y3{j8=2i5HHY6-WfAh7@0e{QTx&Qdt?}s-2KepSGgZafR*1 zmIv-{?{PS4J5T35H*0LIx#h!8{EOfGz{Gj$xsR_K`<}PAd{^?o{rm5eE_H2QLOCdX zf8v)C>k50n9=r1RVJ1z#@Xu3z*&)2RW5&4NQ*QfK4NgyIZ;Rjjx%hnETGJzQ>qhBM zbcVHTi~lOUcfuFfbH<5V!+rC|*<8JqGiyrT!Zqyqg?H8#Z0p%vFB#Fa`t)(TjnZ7p zywy6?UDt<8Y(0PSVGKK)_$4K~5r z_~8`FglBo}C%^+pV*f+TUr9aJURW@3Q^$zGm8Fu@#4Dm7+Ls5on2xYA>-az$J;u`f z-5dPDx}4gOfYDyFnhM$1V?RE5Yx}HTW4}D<%ug~Wj+lPf*Mq+{XM@Fs$!4w}zB^-w z)FIQzHRs3q7Nd^{kBqf>`-h*cO)Ve3%M%`1nE zLo(a_Ja0<*3Cf2PBaOx99a2__R?!*nbZ_CsQ4UReaGFNYr)y+#=FnRUPsOulBr z`hxnC$CqyM_WRi^=5F(u-^aH(Cq7zY5*^bl%b&t(ENH&4$vUggWT73ymr;JI?TtsqKaNh&c^l7OiAoP5~ONX`32ROZc!Hh2A zomCLYes7)pwZvb#-BIK!8YS@$_xc%c4x)_ue6gbri~` zb_#Je&%Rc@_cV$y*ar{rcNF0~evt5G5e7`&zHVVmp)ElmIHrCWGuB5sC{@bNoIp?s7&;)N)Mn_e1){<3X6f4hHX7nJo@ r!zeG_{9ioH=S}t5+4C6B%>KK@I;2?2|u&L8ok -![Maintainer](https://img.shields.io/badge/Maintainer-@jolespin-blue) ![License](https://img.shields.io/badge/License-GNU AGPLv3-blue) ![DOI:10.1186/s12859-022-04973-8](https://zenodo.org/badge/DOI/10.1186/s12859-022-04973-8.svg) +![Maintainer](https://img.shields.io/badge/Maintainer-@jolespin-blue) ![License](https://img.shields.io/badge/License-AGPLv3-blue) ![DOI:10.1186/s12859-022-04973-8](https://zenodo.org/badge/DOI/10.1186/s12859-022-04973-8.svg) [![Forks][forks-shield]][forks-url] [![Stargazers][stars-shield]][stars-url] @@ -54,6 +54,7 @@ ___________________________________________________________________ * [Virulence factor database](http://www.mgc.ac.cn/VFs/main.htm) (`VFDB`) is now included in annotations * [UniRef50/90](https://www.uniprot.org/help/uniref) is now included in annotations * `Krona` plots are generated for taxonomy classifications and biosynthetic gene cluster detection + * Fixed a minor issue in `biosynthetic.py` where the fasta and genbank files were not properly symlinked. Also added virulence factor results to synopsis. * **`VEBA` Database**: @@ -83,7 +84,7 @@ ___________________________________________________________________ ### Getting started with *VEBA* -[Usage and resource requirements guide](src/README.md) for parameters and module descriptions +[*Usage and Resource Requirements Guide*](src/README.md) for parameters and module descriptions [*Walkthrough Guides*](walkthroughs/README.md) for tutorials and workflows on how to get started diff --git a/VERSION b/VERSION index 5988a6f..51301ca 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -1.2.0 +1.2.1 VDB_v5.1 diff --git a/install/README.md b/install/README.md index 7a06a62..28c5be0 100644 --- a/install/README.md +++ b/install/README.md @@ -38,7 +38,7 @@ Currently, **Conda environments for VEBA are ONLY configured for Linux** and, du The `VEBA` installation is going to configure some `conda` environments for you and some of them have quite a bit of packages. To minimize the likelihood of [weird errors](https://forum.qiime2.org/t/valueerror-unsupported-format-character-t-0x54-at-index-3312-when-creating-environment-from-environment-file/25237), it's recommended to do the following: -* Use this as your [`~/.condarc`](https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html). If you're not familiar with the `.condarc` file, then you probably don't have one configured. You can use an editor like [nano](https://anaconda.org/conda-forge/nano) (which is what I use), [vim](https://anaconda.org/conda-forge/vim), or [emacs](https://anaconda.org/conda-forge/emacs) to copy/paste the following into `~/.condarc`. +* Use this as your [`~/.condarc`](https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html). If you're not familiar with the `.condarc` file, then you probably don't have one configured. You can use an editor like [nano](https://anaconda.org/conda-forge/nano) (which is what I use), [vim](https://anaconda.org/conda-forge/vim), or [emacs](https://anaconda.org/conda-forge/emacs) to copy/paste the following into [`~/.condarc`](condarc). ``` channel_priority: flexible diff --git a/install/condarc b/install/condarc new file mode 100644 index 0000000..fa86a87 --- /dev/null +++ b/install/condarc @@ -0,0 +1,9 @@ +channel_priority: flexible +channels: + - conda-forge + - bioconda + - jolespin + - defaults + - qiime2 + +report_errors: true \ No newline at end of file diff --git a/install/docker/Dockerfile b/install/docker/Dockerfile index 9c946c3..28c6980 100644 --- a/install/docker/Dockerfile +++ b/install/docker/Dockerfile @@ -1,57 +1,36 @@ -# v2023.6.6 +# v2023.8.22 # ================================= -# Miniconda3 -# ================================= - -FROM continuumio/miniconda3 +FROM mambaorg/micromamba:1.4.9 ARG ENV_NAME -SHELL ["/bin/bash","-l", "-c"] +SHELL ["/usr/local/bin/_dockerfile_shell.sh"] -WORKDIR /root/ +WORKDIR /tmp/ # Data +USER root +RUN mkdir -p /volumes/ RUN mkdir -p /volumes/input RUN mkdir -p /volumes/output RUN mkdir -p /volumes/database - # Retrieve VEBA repository RUN mkdir -p veba/ -COPY ./install/ veba/install/ -COPY ./src/ veba/src/ -COPY ./VERSION veba/VERSION -COPY ./LICENSE veba/LICENSE - -# Install Miniconda -RUN /opt/conda/bin/conda init bash && \ - /opt/conda/bin/conda config --add channels jolespin && \ - /opt/conda/bin/conda config --add channels bioconda && \ - /opt/conda/bin/conda config --add channels conda-forge && \ - /opt/conda/bin/conda update conda -y && \ - # /opt/conda/bin/conda install -c conda-forge mamba -y && \ # Mamba adds about 450 MB to image - # /opt/conda/bin/mamba init bash && \ - /opt/conda/bin/conda clean -afy - -# ================================= - -# Add conda bin to path -ENV PATH /opt/conda/bin:$PATH +USER $MAMBA_USER +COPY --chown=$MAMBA_USER:$MAMBA_USER ./install/ veba/install/ +COPY --chown=$MAMBA_USER:$MAMBA_USER ./src/ veba/src/ +COPY --chown=$MAMBA_USER:$MAMBA_USER ./VERSION veba/VERSION +COPY --chown=$MAMBA_USER:$MAMBA_USER ./LICENSE veba/LICENSE -# Create environment -RUN conda env create -n ${ENV_NAME} -f veba/install/environments/${ENV_NAME}.yml -# RUN mamba env create -n ${ENV_NAME} -f veba/install/environments/${ENV_NAME}.yml +# Install dependencies +RUN micromamba install -y -n base -f veba/install/environments/${ENV_NAME}.yml && \ + micromamba clean -a -y -f # Add environment scripts to environment bin -RUN /bin/bash veba/install/update_environment_scripts.sh veba/ - -# # Add contents to path -# ENV PATH /opt/conda/envs/${ENV_NAME}/bin:$PATH - -# Set up environment -RUN echo "conda activate ${ENV_NAME}" >> ~/.bashrc +RUN cp -rf veba/src/* /opt/conda/bin/ && \ + ln -sf /opt/conda/bin/scripts/*.py /opt/conda/bin/ && \ + ln -sf /opt/conda/bin/scripts/*.r /opt/conda/bin/ -# Set entrypoint to bash -ENTRYPOINT ["bash", "-l", "-c"] +ENTRYPOINT ["/usr/local/bin/_entrypoint.sh"] diff --git a/install/docker/check_containers.sh b/install/docker/check_containers.sh new file mode 100644 index 0000000..f5aa6e5 --- /dev/null +++ b/install/docker/check_containers.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# __version__ = "2023.7.12" + +TAG="1.2.0" + + +# Assembly +NAME="VEBA-assembly" +DOCKER_IMAGE="jolespin/veba_assembly:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && (assembly.py -h >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Annotate +NAME="VEBA-annotate" +DOCKER_IMAGE="jolespin/veba_annotate:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && (annotate.py -h >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Binning Eukaryotic +NAME="VEBA-binning-eukaryotic" +DOCKER_IMAGE="jolespin/veba_binning-eukaryotic:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && (binning-eukaryotic.py -h >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Binning Prokaryotic +NAME="VEBA-binning-prokaryotic" +DOCKER_IMAGE="jolespin/veba_binning-prokaryotic:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && (binning-prokaryotic.py -h >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Binning Viral +NAME="VEBA-binning-viral" +DOCKER_IMAGE="jolespin/veba_binning-viral:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && (binning-prokaryotic.py -h >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Classify +NAME="VEBA-classify" +DOCKER_IMAGE="jolespin/veba_classify:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && ((classify-eukaryotic.py -h && classify-prokaryotic.py -h && classify-viral.py) >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Cluster +NAME="VEBA-cluster" +DOCKER_IMAGE="jolespin/veba_cluster:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && (cluster.py -h >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Mapping +NAME="VEBA-mapping" +DOCKER_IMAGE="jolespin/veba_mapping:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && ((index.py -h && mapping.py -h) >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Phylogeny +NAME="VEBA-phylogeny" +DOCKER_IMAGE="jolespin/veba_phylogeny:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && (phylogeny.py -h >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" + +# Preprocess +NAME="VEBA-preprocess" +DOCKER_IMAGE="jolespin/veba_preprocess:${TAG}" +CMD="export VEBA_DATABASE=/volumes/database/ && (preprocess.py -h >/dev/null && echo '${NAME} Passed') || (echo '! ${NAME}' Failed)" +docker run --name ${NAME} --rm -it ${DOCKER_IMAGE} -c "${CMD}" diff --git a/install/docker/dockerize_environments.sh b/install/docker/containerize_environments.sh similarity index 100% rename from install/docker/dockerize_environments.sh rename to install/docker/containerize_environments.sh diff --git a/install/docker/deprecated/Dockerfile b/install/docker/deprecated/Dockerfile new file mode 100644 index 0000000..5c34664 --- /dev/null +++ b/install/docker/deprecated/Dockerfile @@ -0,0 +1,56 @@ +# v2023.8.21 +# ================================= +# Miniconda3 +# ================================= + +FROM continuumio/miniconda3 + +ARG ENV_NAME + +SHELL ["/bin/bash","-l", "-c"] + +WORKDIR /tmp/ + +# Data +RUN mkdir -p /volumes/input +RUN mkdir -p /volumes/output +RUN mkdir -p /volumes/database + +# Retrieve VEBA repository +RUN mkdir -p veba/ +COPY ./install/ veba/install/ +COPY ./src/ veba/src/ +COPY ./VERSION veba/VERSION +COPY ./LICENSE veba/LICENSE + +# Install Miniconda +RUN /opt/conda/bin/conda init bash && \ + /opt/conda/bin/conda config --add channels jolespin && \ + /opt/conda/bin/conda config --add channels bioconda && \ + /opt/conda/bin/conda config --add channels conda-forge && \ + /opt/conda/bin/conda update conda -y && \ + # /opt/conda/bin/conda install -c conda-forge mamba -y && \ # Mamba adds about 450 MB to image + # /opt/conda/bin/mamba init bash && \ + /opt/conda/bin/conda clean -afy + +# ================================= + +# Add conda bin to path +ENV PATH /opt/conda/bin:$PATH + +# Create environment +RUN conda env create -n ${ENV_NAME} -f veba/install/environments/${ENV_NAME}.yml +# RUN mamba env create -n ${ENV_NAME} -f veba/install/environments/${ENV_NAME}.yml + +# Add environment scripts to environment bin +RUN /bin/bash veba/install/update_environment_scripts.sh veba/ + +# # Add contents to path +# ENV PATH /opt/conda/envs/${ENV_NAME}/bin:$PATH + +# Set up environment +RUN echo "conda activate ${ENV_NAME}" >> ~/.bashrc + +# Set entrypoint to bash +ENTRYPOINT ["bash", "-l", "-c"] + diff --git a/install/docker/push_docker_images.sh b/install/docker/push_docker_images.sh new file mode 100644 index 0000000..5e747d6 --- /dev/null +++ b/install/docker/push_docker_images.sh @@ -0,0 +1,6 @@ +for DOCKER_IMAGE in $(docker images --format "{{.Repository}}:{{.Tag}}" | grep "veba") +do + echo $DOCKER_IMAGE + docker push $DOCKER_IMAGE + sleep 1 +done diff --git a/install/environments/VEBA-mapping_env.yml b/install/environments/VEBA-mapping_env.yml index a312051..5af32f1 100644 --- a/install/environments/VEBA-mapping_env.yml +++ b/install/environments/VEBA-mapping_env.yml @@ -1,4 +1,4 @@ -name: VEBA-mapping_env__v2023.5.15 +name: VEBA-mapping_env__v2023.7.25 channels: - conda-forge - bioconda @@ -11,34 +11,36 @@ dependencies: - bbmap=38.95=h5c4e2a8_1 - biom-format=2.1.14=py39h72bdee0_2 - biopython=1.79=py39h3811e60_1 - - bowtie2=2.4.5=py39hd2f7db1_2 + - bowtie2=2.5.1=py39h6fed5c7_2 - brotlipy=0.7.0=py39h3811e60_1003 - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2022.12.7=ha878542_0 + - ca-certificates=2023.7.22=hbcca054_0 - cached-property=1.5.2=hd8ed1ab_1 - cached_property=1.5.2=pyha770c72_1 - - certifi=2022.12.7=pyhd8ed1ab_0 + - certifi=2023.7.22=pyhd8ed1ab_0 - cffi=1.15.0=py39h4bc2ebd_0 - charset-normalizer=2.0.12=pyhd8ed1ab_0 - click=8.1.3=unix_pyhd8ed1ab_2 - colorama=0.4.4=pyh9f0ad1d_0 - coreutils=9.3=h0b41bf4_0 - - cryptography=36.0.1=py39h95dcef6_0 + - cryptography=41.0.2=py39hd4f0224_0 - genopype=2023.5.15=py_0 - h5py=3.7.0=nompi_py39h63b1161_100 - - hdf5=1.12.1=nompi_h2386368_104 - - htslib=1.14=h9753748_2 + - hdf5=1.12.1=nompi_h4df4325_104 + - htslib=1.17=h81da01d_2 + - icu=72.1=hcb278e6_0 - idna=3.3=pyhd8ed1ab_0 - importlib-metadata=6.3.0=pyha770c72_0 - importlib_metadata=6.3.0=hd8ed1ab_0 - - krb5=1.19.2=hcc1bbae_3 + - keyutils=1.6.1=h166bdaf_0 + - krb5=1.21.1=h659d440_0 - ld_impl_linux-64=2.36.1=hea4e1c9_2 - libblas=3.9.0=13_linux64_openblas - libcblas=3.9.0=13_linux64_openblas - - libcurl=7.81.0=h2574ce0_0 - - libdeflate=1.10=h7f98852_0 + - libcurl=8.2.0=hca28451_0 + - libdeflate=1.18=h0b41bf4_0 - libedit=3.1.20191231=he28a2e2_2 - libev=4.33=h516909a_1 - libffi=3.4.2=h7f98852_5 @@ -46,20 +48,24 @@ dependencies: - libgfortran-ng=11.2.0=h69a702a_12 - libgfortran5=11.2.0=h5c6108e_12 - libgomp=12.2.0=h65d4601_19 + - libhwloc=2.9.1=nocuda_h7313eea_6 + - libiconv=1.17=h166bdaf_0 - liblapack=3.9.0=13_linux64_openblas - - libnghttp2=1.47.0=h727a467_0 + - libnghttp2=1.52.0=h61bc06f_0 - libnsl=2.0.0=h7f98852_0 - libopenblas=0.3.18=pthreads_h8fe5266_0 - - libssh2=1.10.0=ha56f1ee_2 + - libsqlite=3.42.0=h2797004_0 + - libssh2=1.11.0=h0841786_0 - libstdcxx-ng=12.2.0=h46fd767_19 - libuuid=2.32.1=h7f98852_1000 + - libxml2=2.11.4=h0d562d8_0 - libzlib=1.2.13=h166bdaf_4 - lz4-c=1.9.3=h9c3ff4c_1 - natsort=8.3.1=pyhd8ed1ab_0 - ncurses=6.3=h9c3ff4c_0 - numpy=1.24.2=py39h7360e5f_0 - openjdk=8.0.312=h7f98852_0 - - openssl=1.1.1t=h0b41bf4_0 + - openssl=3.1.1=hd590300_1 - packaging=23.0=pyhd8ed1ab_0 - pandas=1.4.1=py39hde0f152_0 - pathlib2=2.3.7.post1=py39hf3d152e_0 @@ -67,17 +73,17 @@ dependencies: - perl=5.32.1=2_h7f98852_perl5 - pip=22.0.3=pyhd8ed1ab_0 - pycparser=2.21=pyhd8ed1ab_0 - - pyopenssl=22.0.0=pyhd8ed1ab_0 + - pyopenssl=23.2.0=pyhd8ed1ab_1 - pysocks=1.7.1=py39hf3d152e_4 - - python=3.9.10=h85951f9_2_cpython + - python=3.9.16=h2782a2a_0_cpython - python-dateutil=2.8.2=pyhd8ed1ab_0 - python-tzdata=2021.5=pyhd8ed1ab_0 - python_abi=3.9=2_cp39 - pytz=2021.3=pyhd8ed1ab_0 - pytz-deprecation-shim=0.1.0.post0=py39hf3d152e_1 - - readline=8.1=h46c0cb4_0 + - readline=8.2=h8228510_1 - requests=2.27.1=pyhd8ed1ab_0 - - samtools=1.15=h3843a85_0 + - samtools=1.17=hd87286a_1 - scandir=1.10.0=py39h3811e60_4 - scipy=1.9.3=py39hddc5342_2 - setuptools=60.9.3=py39hf3d152e_0 @@ -86,7 +92,7 @@ dependencies: - sqlite=3.37.0=h9cd32fc_0 - star=2.7.10a=h9ee0642_0 - subread=2.0.3=h7132678_1 - - tbb=2020.2=h4bd325d_4 + - tbb=2021.9.0=hf52228f_0 - tk=8.6.12=h27826a3_0 - tqdm=4.62.3=pyhd8ed1ab_0 - typing_extensions=4.5.0=pyha770c72_0 @@ -94,7 +100,7 @@ dependencies: - tzlocal=4.1=py39hf3d152e_1 - urllib3=1.26.8=pyhd8ed1ab_1 - wheel=0.37.1=pyhd8ed1ab_0 - - xz=5.2.5=h516909a_1 + - xz=5.2.6=h166bdaf_0 - zipp=3.15.0=pyhd8ed1ab_0 - zlib=1.2.13=h166bdaf_4 - zstd=1.5.2=ha95c52a_0 diff --git a/install/environments/VEBA-preprocess_env.yml b/install/environments/VEBA-preprocess_env.yml index 4f550e1..d2f59b2 100644 --- a/install/environments/VEBA-preprocess_env.yml +++ b/install/environments/VEBA-preprocess_env.yml @@ -1,4 +1,4 @@ -name: VEBA-preprocess_env__v2023.5.15 +name: VEBA-preprocess_env__v2023.8.21 channels: - conda-forge - bioconda @@ -28,14 +28,14 @@ dependencies: - bbmap=39.01=h5c4e2a8_0 - bird_tool_utils_python=0.4.1=pyhdfd78af_0 - botocore=1.29.23=pyhd8ed1ab_0 - - bowtie2=2.5.0=py39h3321a2d_0 + - bowtie2=2.5.1=py39h3321a2d_0 - brotlipy=0.7.0=py39hb9d737c_1005 - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2023.5.7=hbcca054_0 + - ca-certificates=2023.7.22=hbcca054_0 - cairo=1.16.0=ha61ee94_1014 - - certifi=2023.5.7=pyhd8ed1ab_0 + - certifi=2023.7.22=pyhd8ed1ab_0 - cffi=1.15.1=py39he91dace_2 - charset-normalizer=2.1.1=pyhd8ed1ab_0 - colorama=0.4.4=pyh9f0ad1d_0 @@ -45,8 +45,8 @@ dependencies: - docutils=0.16=py39hf3d152e_3 - expat=2.5.0=h27087fc_0 - extern=0.4.1=py_0 - - fastp=0.23.2=h5f740d0_3 - - fastq_preprocessor=2023.5.17=py_0 + - fastp=0.23.4=h5f740d0_0 + - fastq_preprocessor=2023.7.24=py_0 - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 - font-ttf-inconsolata=3.000=h77eed37_0 - font-ttf-source-code-pro=2.038=h77eed37_0 @@ -121,7 +121,7 @@ dependencies: - ncurses=6.3=h27087fc_1 - numpy=1.23.5=py39h3d75532_0 - openjdk=17.0.3=hafdced1_4 - - openssl=1.1.1t=h0b41bf4_0 + - openssl=1.1.1u=hd590300_0 - orc=1.8.0=h09e0d61_0 - ossuuid=1.6.2=hf484d3e_1000 - pandas=1.5.2=py39h4661b88_0 diff --git a/install/environments/devel/VEBA-amplicon_env.yml b/install/environments/devel/VEBA-amplicon_env.yml index 7adf5c6..68914de 100644 --- a/install/environments/devel/VEBA-amplicon_env.yml +++ b/install/environments/devel/VEBA-amplicon_env.yml @@ -1,685 +1,407 @@ -name: VEBA-amplicon_env__2023.5.22 +name: VEBA-amplicon_env__2023.8.11 channels: - conda-forge - bioconda - jolespin - defaults - qiime2 + dependencies: - - _ipython_minor_entry_point=8.7.0=h3b92ee0_0 - _libgcc_mutex=0.1=conda_forge - _openmp_mutex=4.5=2_gnu - _r-mutex=1.0.1=anacondar_1 - - alsa-lib=1.2.8=h166bdaf_0 - - anyio=3.6.2=pyhd8ed1ab_0 - - argcomplete=2.0.0=pyhd8ed1ab_0 - - argon2-cffi=21.3.0=pyhd8ed1ab_0 - - argon2-cffi-bindings=21.2.0=py38h0a891b7_3 - - astor=0.8.1=pyh9f0ad1d_0 + - alsa-lib=1.2.9=hd590300_0 + - appdirs=1.4.4=pyh9f0ad1d_0 + - argcomplete=3.1.1=pyhd8ed1ab_0 - asttokens=2.2.1=pyhd8ed1ab_0 - atpublic=3.0.1=pyhd8ed1ab_0 - attr=2.5.1=h166bdaf_1 - - attrs=22.2.0=pyh71513ae_0 - backcall=0.2.0=pyh9f0ad1d_0 - backports=1.0=pyhd8ed1ab_3 - - backports.functools_lru_cache=1.6.4=pyhd8ed1ab_0 - - beautifulsoup4=4.11.1=pyha770c72_0 + - backports.functools_lru_cache=1.6.5=pyhd8ed1ab_0 + - bcrypt=3.2.2=py38h0a891b7_1 - bibtexparser=1.4.0=pyhd8ed1ab_0 - - binutils_impl_linux-64=2.39=he00db2b_1 - - bioconductor-ancombc=2.0.1=r42hdfd78af_0 - - bioconductor-beachmat=2.14.0=r42hc247a5b_0 - - bioconductor-biobase=2.58.0=r42hc0cfd56_0 - - bioconductor-biocbaseutils=1.0.0=r42hdfd78af_0 - - bioconductor-biocgenerics=0.44.0=r42hdfd78af_0 - - bioconductor-biocneighbors=1.16.0=r42hc247a5b_0 - - bioconductor-biocparallel=1.32.0=r42hc247a5b_0 - - bioconductor-biocsingular=1.14.0=r42hc247a5b_0 - - bioconductor-biomformat=1.26.0=r42hdfd78af_0 - - bioconductor-biostrings=2.66.0=r42hc0cfd56_0 - - bioconductor-dada2=1.26.0=r42hc247a5b_0 - - bioconductor-data-packages=20221112=hdfd78af_0 - - bioconductor-decipher=2.26.0=r42hc0cfd56_0 - - bioconductor-decontam=1.18.0=r42hdfd78af_0 - - bioconductor-delayedarray=0.24.0=r42hc0cfd56_0 - - bioconductor-delayedmatrixstats=1.20.0=r42hdfd78af_0 - - bioconductor-dirichletmultinomial=1.40.0=r42hda872b5_0 - - bioconductor-genomeinfodb=1.34.1=r42hdfd78af_0 - - bioconductor-genomeinfodbdata=1.2.9=r42hdfd78af_0 - - bioconductor-genomicalignments=1.34.0=r42hc0cfd56_0 - - bioconductor-genomicranges=1.50.0=r42hc0cfd56_0 - - bioconductor-iranges=2.32.0=r42hc0cfd56_0 - - bioconductor-matrixgenerics=1.10.0=r42hdfd78af_0 - - bioconductor-mia=1.6.0=r42hdfd78af_0 - - bioconductor-multiassayexperiment=1.24.0=r42hdfd78af_0 - - bioconductor-multtest=2.54.0=r42hc0cfd56_0 - - bioconductor-phyloseq=1.42.0=r42hdfd78af_0 - - bioconductor-rhdf5=2.42.0=r42hbe1951d_1 - - bioconductor-rhdf5filters=1.10.0=r42hc247a5b_0 - - bioconductor-rhdf5lib=1.20.0=r42hc0cfd56_0 - - bioconductor-rhtslib=2.0.0=r42hc0cfd56_0 - - bioconductor-rsamtools=2.14.0=r42hc247a5b_0 - - bioconductor-s4vectors=0.36.0=r42hc0cfd56_0 - - bioconductor-scaledmatrix=1.6.0=r42hdfd78af_0 - - bioconductor-scater=1.26.0=r42hdfd78af_0 - - bioconductor-scuttle=1.8.0=r42hc247a5b_0 - - bioconductor-shortread=1.56.0=r42hc247a5b_0 - - bioconductor-singlecellexperiment=1.20.0=r42hdfd78af_0 - - bioconductor-sparsematrixstats=1.10.0=r42hc247a5b_0 - - bioconductor-summarizedexperiment=1.28.0=r42hdfd78af_0 - - bioconductor-treeio=1.22.0=r42hdfd78af_0 - - bioconductor-treesummarizedexperiment=2.6.0=r42hdfd78af_0 - - bioconductor-xvector=0.38.0=r42hc0cfd56_0 - - bioconductor-zlibbioc=1.44.0=r42hc0cfd56_0 + - binutils_impl_linux-64=2.40=hf600244_0 + - bioconductor-biobase=2.60.0=r43ha9d7317_0 + - bioconductor-biocgenerics=0.46.0=r43hdfd78af_0 + - bioconductor-biocparallel=1.34.2=r43hf17093f_0 + - bioconductor-biostrings=2.68.1=r43ha9d7317_0 + - bioconductor-dada2=1.28.0=r43hf17093f_0 + - bioconductor-data-packages=20230718=hdfd78af_1 + - bioconductor-decontam=1.20.0=r43hdfd78af_0 + - bioconductor-delayedarray=0.26.6=r43ha9d7317_0 + - bioconductor-genomeinfodb=1.36.1=r43hdfd78af_0 + - bioconductor-genomeinfodbdata=1.2.10=r43hdfd78af_0 + - bioconductor-genomicalignments=1.36.0=r43ha9d7317_0 + - bioconductor-genomicranges=1.52.0=r43ha9d7317_0 + - bioconductor-iranges=2.34.1=r43ha9d7317_0 + - bioconductor-matrixgenerics=1.12.2=r43hdfd78af_0 + - bioconductor-rhtslib=2.2.0=r43ha9d7317_0 + - bioconductor-rsamtools=2.16.0=r43hf17093f_0 + - bioconductor-s4arrays=1.0.4=r43ha9d7317_0 + - bioconductor-s4vectors=0.38.1=r43ha9d7317_0 + - bioconductor-shortread=1.58.0=r43hf17093f_0 + - bioconductor-summarizedexperiment=1.30.2=r43hdfd78af_0 + - bioconductor-xvector=0.40.0=r43ha9d7317_0 + - bioconductor-zlibbioc=1.46.0=r43ha9d7317_0 - biom-format=2.1.12=py38h26c90d9_2 - biopython=1.81=py38h1de0b5d_0 - - blast=2.13.0=hf3cf87c_0 - - bleach=5.0.1=pyhd8ed1ab_0 - - bokeh=3.0.3=pyhd8ed1ab_0 - - bowtie2=2.5.0=py38h77f66f0_0 - - brotli=1.0.9=h166bdaf_8 - - brotli-bin=1.0.9=h166bdaf_8 - - brotlipy=0.7.0=py38h0a891b7_1005 + - blast=2.14.0=pl5321h6f7f691_2 + - bowtie2=2.5.1=py38he00c5e5_2 + - brotli=1.0.9=h166bdaf_9 + - brotli-bin=1.0.9=h166bdaf_9 + - brotli-python=1.0.9=py38hfa26641_9 - bwidget=1.9.14=ha770c72_1 - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2023.5.7=hbcca054_0 - - cachecontrol=0.12.11=pyhd8ed1ab_0 + - c-ares=1.19.1=hd590300_0 + - ca-certificates=2023.7.22=hbcca054_0 + - cachecontrol=0.13.1=pyhd8ed1ab_0 + - cached-property=1.5.2=hd8ed1ab_1 - cached_property=1.5.2=pyha770c72_1 - - cairo=1.16.0=ha61ee94_1014 - - certifi=2023.5.7=pyhd8ed1ab_0 + - cairo=1.16.0=hbbf8b49_1016 + - certifi=2023.7.22=pyhd8ed1ab_0 - cffi=1.15.1=py38h4a40e3a_3 - - charset-normalizer=2.1.1=pyhd8ed1ab_0 - - click=8.1.3=unix_pyhd8ed1ab_2 + - charset-normalizer=3.2.0=pyhd8ed1ab_0 + - click=8.1.6=unix_pyh707e725_0 - colorama=0.4.6=pyhd8ed1ab_0 - - comm=0.1.2=pyhd8ed1ab_0 - - contourpy=1.0.6=py38h43d8883_0 - - cryptography=38.0.4=py38h2b5fc30_0 - - curl=7.87.0=h6312ad2_0 - - cutadapt=4.2=py38hbff2b2d_0 + - comm=0.1.4=pyhd8ed1ab_0 + - contourpy=1.1.0=py38h7f3f72f_0 + - cryptography=41.0.3=py38hcdda232_0 + - curl=8.2.1=hca28451_0 - cycler=0.11.0=pyhd8ed1ab_0 - - cython=0.29.32=py38hfa26641_1 + - cython=3.0.0=py38h17151c0_0 - dbus=1.13.6=h5008d03_3 - - deblur=1.1.1=pyhdfd78af_0 - - debugpy=1.6.4=py38hfa26641_0 - decorator=4.4.2=py_0 - - defusedxml=0.7.1=pyhd8ed1ab_0 - - dendropy=4.5.2=pyh3252c3a_0 - - dill=0.3.6=pyhd8ed1ab_1 - - dnaio=0.10.0=py38hbff2b2d_0 - - emperor=1.0.3=py38h578d9bd_0 + - dill=0.3.7=pyhd8ed1ab_0 - entrez-direct=16.2=he881be0_1 - - entrypoints=0.4=pyhd8ed1ab_0 - - exceptiongroup=1.0.4=pyhd8ed1ab_0 + - exceptiongroup=1.1.2=pyhd8ed1ab_0 - executing=1.2.0=pyhd8ed1ab_0 - - expat=2.5.0=h27087fc_0 - - fastcluster=1.2.6=py38h8f669ce_2 - - fasttree=2.1.11=hec16e2b_1 - - fftw=3.3.10=nompi_hf0379b8_106 - - flit-core=3.8.0=pyhd8ed1ab_0 + - expat=2.5.0=hcb278e6_1 + - fasttree=2.1.11=h031d066_2 - flufl.lock=7.1=pyhd8ed1ab_0 - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 - font-ttf-inconsolata=3.000=h77eed37_0 - font-ttf-source-code-pro=2.038=h77eed37_0 - font-ttf-ubuntu=0.83=hab24e00_0 - - fontconfig=2.14.1=hc2a2eb6_0 + - fontconfig=2.14.2=h14ed4e7_0 - fonts-conda-ecosystem=1=0 - fonts-conda-forge=1=0 - - fonttools=4.38.0=py38h0a891b7_1 - - formulaic=0.5.2=pyhd8ed1ab_0 + - fonttools=4.42.0=py38h01eb140_0 - freetype=2.12.1=hca18f0e_1 - fribidi=1.0.10=h36c2ea0_0 - - future=0.18.2=pyhd8ed1ab_6 - - gcc_impl_linux-64=12.2.0=hcc96c02_19 + - future=0.18.3=pyhd8ed1ab_0 + - gcc_impl_linux-64=13.1.0=hc4be1a9_0 - genopype=2023.5.15=py_0 - gettext=0.21.1=h27087fc_0 - - gfortran_impl_linux-64=12.2.0=h55be85b_19 - - giflib=5.2.1=h36c2ea0_2 - - glib=2.74.1=h6239696_1 - - glib-tools=2.74.1=h6239696_1 - - glpk=5.0=h445213a_0 - - gmp=6.2.1=h58526e2_0 - - gneiss=0.4.6=py_0 + - gfortran_impl_linux-64=13.1.0=hd511a9b_0 + - glib=2.76.4=hfc55251_0 + - glib-tools=2.76.4=hfc55251_0 + - globus-sdk=3.26.0=pyhd8ed1ab_0 - graphite2=1.3.13=h58526e2_1001 - - graphlib-backport=1.0.3=pyhd8ed1ab_0 - gsl=2.7=he838d99_0 - - gst-plugins-base=1.21.3=h4243ec0_1 - - gstreamer=1.21.3=h25f0c4b_1 - - gstreamer-orc=0.4.33=h166bdaf_0 - - gxx_impl_linux-64=12.2.0=hcc96c02_19 - - h5py=2.10.0=nompi_py38h9915d05_106 - - harfbuzz=6.0.0=h8e241bc_0 - - hdf5=1.10.6=nompi_h6a2412b_1114 + - gst-plugins-base=1.22.5=hf7dbed1_0 + - gstreamer=1.22.5=h98fc4e7_0 + - gxx_impl_linux-64=13.1.0=hc4be1a9_0 + - h5py=3.9.0=nompi_py38hc4a6f91_101 + - harfbuzz=7.3.0=hdb3a94d_0 + - hdf5=1.14.1=nompi_h4f84152_100 - hdmedians=0.14.2=py38h26c90d9_3 - - hmmer=3.1b2=3 - - htslib=1.16=h6bc39ce_0 - - icu=70.1=h27087fc_0 + - htslib=1.17=h81da01d_2 + - icu=72.1=hcb278e6_0 - idna=3.4=pyhd8ed1ab_0 - - ijson=3.1.3=pyhd8ed1ab_1 - - importlib-metadata=4.8.3=py38h578d9bd_0 - - importlib_metadata=4.8.3=hd8ed1ab_0 - - importlib_resources=5.10.1=pyhd8ed1ab_1 - - iniconfig=1.1.1=pyh9f0ad1d_0 - - interface_meta=1.3.0=pyhd8ed1ab_0 - - iow=1.0.5=py38h8ded8fe_1 - - ipykernel=6.19.4=pyh210e3f2_0 - - ipython=8.7.0=pyh41d4057_0 - - ipython_genutils=0.2.0=py_1 - - ipywidgets=8.0.3=pyhd8ed1ab_0 - - iqtree=2.2.0.3=hb97b32f_1 - - isa-l=2.30.0=ha770c72_4 - - jack=1.9.21=h583fa2b_2 - - jedi=0.18.2=pyhd8ed1ab_0 + - ijson=3.2.3=pyhd8ed1ab_0 + - iniconfig=2.0.0=pyhd8ed1ab_0 + - ipython=8.12.2=pyh41d4057_0 + - ipywidgets=8.1.0=pyhd8ed1ab_0 + - iqtree=2.2.2.9=h21ec9f0_0 + - jedi=0.19.0=pyhd8ed1ab_0 - jinja2=3.1.2=pyhd8ed1ab_1 - - joblib=1.2.0=pyhd8ed1ab_0 - - jpeg=9e=h166bdaf_2 - - jq=1.6=h36c2ea0_1000 - - jsonschema=4.17.3=pyhd8ed1ab_0 - - jupyter_client=7.4.8=pyhd8ed1ab_0 - - jupyter_core=5.1.0=py38h578d9bd_0 - - jupyter_events=0.5.0=pyhd8ed1ab_1 - - jupyter_server=2.0.3=pyhd8ed1ab_0 - - jupyter_server_terminals=0.4.3=pyhd8ed1ab_0 - - jupyterlab_pygments=0.2.2=pyhd8ed1ab_0 - - jupyterlab_widgets=3.0.4=pyhd8ed1ab_0 - - kernel-headers_linux-64=2.6.32=he073ed8_15 + - joblib=1.3.2=pyhd8ed1ab_0 + - jq=1.5=0 + - jupyterlab_widgets=3.0.8=pyhd8ed1ab_0 + - kernel-headers_linux-64=2.6.32=he073ed8_16 - keyutils=1.6.1=h166bdaf_0 - kiwisolver=1.4.4=py38h43d8883_1 - - krb5=1.20.1=hf9c8cef_0 + - krb5=1.21.1=h659d440_0 - lame=3.100=h166bdaf_1003 - - lcms2=2.14=h6ed2654_0 - - ld_impl_linux-64=2.39=hcc3a1bd_1 + - lcms2=2.15=haa2dc70_1 + - ld_impl_linux-64=2.40=h41732ed_0 - lerc=4.0.0=h27087fc_0 - - libblas=3.9.0=16_linux64_openblas - - libbrotlicommon=1.0.9=h166bdaf_8 - - libbrotlidec=1.0.9=h166bdaf_8 - - libbrotlienc=1.0.9=h166bdaf_8 - - libcap=2.66=ha37c62d_0 - - libcblas=3.9.0=16_linux64_openblas - - libclang=15.0.6=default_h2e3cab8_0 - - libclang13=15.0.6=default_h3a83d3e_0 - - libcups=2.3.3=h36d4200_3 - - libcurl=7.87.0=h6312ad2_0 - - libdb=6.2.32=h9c3ff4c_0 - - libdeflate=1.13=h166bdaf_0 + - libaec=1.0.6=hcb278e6_1 + - libblas=3.9.0=17_linux64_openblas + - libbrotlicommon=1.0.9=h166bdaf_9 + - libbrotlidec=1.0.9=h166bdaf_9 + - libbrotlienc=1.0.9=h166bdaf_9 + - libcap=2.69=h0f662aa_0 + - libcblas=3.9.0=17_linux64_openblas + - libclang=15.0.7=default_h7634d5b_3 + - libclang13=15.0.7=default_h9986a30_3 + - libcups=2.3.3=h4637d8d_4 + - libcurl=8.2.1=hca28451_0 + - libdeflate=1.18=h0b41bf4_0 - libedit=3.1.20191231=he28a2e2_2 - libev=4.33=h516909a_1 - - libevent=2.1.10=h9b69904_4 + - libevent=2.1.12=hf998b51_1 + - libexpat=2.5.0=hcb278e6_1 - libffi=3.4.2=h7f98852_5 - - libflac=1.4.2=h27087fc_0 - - libgcc-devel_linux-64=12.2.0=h3b97bd3_19 - - libgcc-ng=12.2.0=h65d4601_19 + - libflac=1.4.3=h59595ed_0 + - libgcc=7.2.0=h69d50b8_2 + - libgcc-devel_linux-64=13.1.0=he3cc6c4_0 + - libgcc-ng=13.1.0=he5830b7_0 - libgcrypt=1.10.1=h166bdaf_0 - - libgfortran-ng=12.2.0=h69a702a_19 - - libgfortran5=12.2.0=h337968e_19 - - libglib=2.74.1=h606061b_1 - - libgomp=12.2.0=h65d4601_19 - - libgpg-error=1.45=hc0c96e0_0 - - libhwloc=2.8.0=h32351e8_1 + - libgfortran-ng=13.1.0=h69a702a_0 + - libgfortran5=13.1.0=h15d22d2_0 + - libglib=2.76.4=hebfc3b9_0 + - libgomp=13.1.0=he5830b7_0 + - libgpg-error=1.47=h71f35ed_0 + - libhwloc=2.9.2=nocuda_h7313eea_1008 - libiconv=1.17=h166bdaf_0 - libidn2=2.3.4=h166bdaf_0 - - liblapack=3.9.0=16_linux64_openblas - - liblapacke=3.9.0=16_linux64_openblas - - libllvm11=11.1.0=he0ac6c6_5 - - libllvm15=15.0.6=h63197d8_0 - - libnghttp2=1.47.0=hdcd2b5c_1 + - libjpeg-turbo=2.1.5.1=h0b41bf4_0 + - liblapack=3.9.0=17_linux64_openblas + - libllvm15=15.0.7=h5cf9203_3 + - libnghttp2=1.52.0=h61bc06f_0 - libnsl=2.0.0=h7f98852_0 - libogg=1.3.4=h7f98852_1 - - libopenblas=0.3.21=pthreads_h78a6416_3 + - libopenblas=0.3.23=pthreads_h80387f5_0 - libopus=1.3.1=h7f98852_1 - libpng=1.6.39=h753d276_0 - - libpq=15.1=h2baec63_3 - - libsanitizer=12.2.0=h46fd767_19 - - libsndfile=1.1.0=hcb278e6_1 + - libpq=15.4=hfc447b1_0 + - libsanitizer=13.1.0=hfd8a6a1_0 + - libsndfile=1.2.0=hb75c966_0 - libsodium=1.0.18=h36c2ea0_1 - - libsqlite=3.40.0=h753d276_0 - - libssh2=1.10.0=haa6b8db_3 - - libstdcxx-devel_linux-64=12.2.0=h3b97bd3_19 - - libstdcxx-ng=12.2.0=h46fd767_19 - - libsystemd0=252=h2a991cd_0 - - libtiff=4.4.0=h0e0dad5_3 - - libtool=2.4.6=h9c3ff4c_1008 - - libudev1=252=h166bdaf_0 + - libsqlite=3.42.0=h2797004_0 + - libssh2=1.11.0=h0841786_0 + - libstdcxx-devel_linux-64=13.1.0=he3cc6c4_0 + - libstdcxx-ng=13.1.0=hfd8a6a1_0 + - libsystemd0=254=h3516f8a_0 + - libtiff=4.5.1=h8b53f26_0 - libunistring=0.9.10=h7f98852_0 - - libuuid=2.32.1=h7f98852_1000 + - libuuid=2.38.1=h0b41bf4_0 - libvorbis=1.3.7=h9c3ff4c_0 - - libwebp-base=1.2.4=h166bdaf_0 - - libxcb=1.13=h7f98852_1004 - - libxkbcommon=1.0.3=he3ba5ed_0 - - libxml2=2.10.3=h7463322_0 - - libxslt=1.1.37=h873f0b0_0 - - libzlib=1.2.13=h166bdaf_4 - - llvmlite=0.39.1=py38h38d86a4_1 + - libwebp-base=1.3.1=hd590300_0 + - libxcb=1.15=h0b41bf4_0 + - libxkbcommon=1.5.0=h5d7e998_3 + - libxml2=2.11.5=h0d562d8_0 + - libzlib=1.2.13=hd590300_5 - lockfile=0.12.2=py_1 - - lxml=4.9.2=py38h215a2d7_0 - - lz4=4.0.2=py38h1bf946c_0 - - lz4-c=1.9.3=h9c3ff4c_1 - - mafft=7.508=hec16e2b_0 + - lz4-c=1.9.4=hcb278e6_0 + - mafft=7.520=h031d066_2 - make=4.3=hd18ef5c_1 - - markupsafe=2.1.1=py38h0a891b7_2 + - markupsafe=2.1.3=py38h01eb140_0 - matplotlib=3.6.0=py38h578d9bd_0 - matplotlib-base=3.6.0=py38hb021067_0 - matplotlib-inline=0.1.6=pyhd8ed1ab_0 - - mistune=2.0.4=pyhd8ed1ab_0 - - mpfr=4.1.0=h9202a9a_1 - - mpg123=1.31.1=h27087fc_0 - - msgpack-python=1.0.4=py38h43d8883_1 - - munkres=1.1.4=pyh9f0ad1d_0 - - mysql-common=8.0.31=haf5c9bc_0 - - mysql-libs=8.0.31=h28c427c_0 - - natsort=8.2.0=pyhd8ed1ab_0 - - nbclassic=0.4.8=pyhd8ed1ab_0 - - nbclient=0.7.2=pyhd8ed1ab_0 - - nbconvert=7.2.7=pyhd8ed1ab_0 - - nbconvert-core=7.2.7=pyhd8ed1ab_0 - - nbconvert-pandoc=7.2.7=pyhd8ed1ab_0 - - nbformat=5.7.1=pyhd8ed1ab_0 - - ncurses=6.3=h27087fc_1 - - nest-asyncio=1.5.6=pyhd8ed1ab_0 - - networkx=2.8.8=pyhd8ed1ab_0 - - nlopt=2.7.1=py38hca016a5_3 - - nose=1.3.7=py_1006 - - notebook=6.5.2=pyha770c72_1 - - notebook-shim=0.2.2=pyhd8ed1ab_0 + - mpg123=1.31.3=hcb278e6_0 + - msgpack-python=1.0.5=py38hfbd4bf9_0 + - munkres=1.0.7=py_1 + - mysql-common=8.0.33=hf1915f5_2 + - mysql-libs=8.0.33=hca2cd23_2 + - natsort=8.4.0=pyhd8ed1ab_0 + - ncbi-vdb=3.0.0=pl5321h87f3376_0 + - ncurses=6.4=hcb278e6_0 + - networkx=3.1=pyhd8ed1ab_0 - nspr=4.35=h27087fc_0 - - nss=3.82=he02c5a1_0 - - numba=0.56.4=py38h9a4aae9_0 - - numpy=1.23.5=py38h7042d01_0 - - oniguruma=6.9.8=h166bdaf_0 - - openjdk=17.0.3=h58dac75_5 - - openjpeg=2.5.0=h7d73246_1 - - openssl=1.1.1t=h0b41bf4_0 - - packaging=22.0=pyhd8ed1ab_0 - - pandas=1.5.2=py38hdc8b05c_2 - - pandoc=2.19.2=h32600fe_1 - - pandocfilters=1.5.0=pyhd8ed1ab_0 - - pango=1.50.12=hd33c08f_1 + - nss=3.89=he45b914_0 + - numpy=1.24.4=py38h59b608b_0 + - openjpeg=2.5.0=hfec8fc6_2 + - openssl=3.1.2=hd590300_0 + - packaging=23.1=pyhd8ed1ab_0 + - pandas=1.5.3=py38hdc8b05c_1 + - pango=1.50.14=heaa33ce_1 + - paramiko=3.3.1=pyhd8ed1ab_0 + - parsl=2023.8.7=pyhd8ed1ab_0 - parso=0.8.3=pyhd8ed1ab_0 - pathlib2=2.3.7.post1=py38h578d9bd_2 - patsy=0.5.3=pyhd8ed1ab_0 - - pbzip2=1.1.13=0 - pcre=8.45=h9c3ff4c_0 - pcre2=10.40=hc3806b6_0 - - perl=5.32.1=2_h7f98852_perl5 + - perl=5.32.1=4_hd590300_perl5 - perl-archive-tar=2.40=pl5321hdfd78af_0 - - perl-carp=1.50=pl5321hd8ed1ab_0 - - perl-common-sense=3.75=pl5321hd8ed1ab_0 - - perl-compress-raw-bzip2=2.201=pl5321h166bdaf_0 - - perl-compress-raw-zlib=2.202=pl5321h166bdaf_0 - - perl-encode=3.19=pl5321h166bdaf_0 - - perl-exporter=5.74=pl5321hd8ed1ab_0 - - perl-exporter-tiny=1.002002=pl5321hd8ed1ab_0 - - perl-extutils-makemaker=7.64=pl5321hd8ed1ab_0 - - perl-io-compress=2.201=pl5321h87f3376_0 - - perl-io-zlib=1.11=pl5321hdfd78af_0 + - perl-carp=1.38=pl5321hdfd78af_4 + - perl-common-sense=3.75=pl5321hdfd78af_0 + - perl-compress-raw-bzip2=2.201=pl5321h87f3376_1 + - perl-compress-raw-zlib=2.105=pl5321h87f3376_0 + - perl-encode=3.19=pl5321hec16e2b_1 + - perl-exporter=5.72=pl5321hdfd78af_2 + - perl-exporter-tiny=1.002002=pl5321hdfd78af_0 + - perl-extutils-makemaker=7.70=pl5321hd8ed1ab_0 + - perl-io-compress=2.201=pl5321hdbdd923_2 + - perl-io-zlib=1.14=pl5321hdfd78af_0 - perl-json=4.10=pl5321hdfd78af_0 - - perl-json-xs=2.34=pl5321h9f5acd7_5 + - perl-json-xs=2.34=pl5321h4ac6f70_6 - perl-list-moreutils=0.430=pl5321hdfd78af_0 - - perl-list-moreutils-xs=0.430=pl5321hec16e2b_1 - - perl-parent=0.239=pl5321hd8ed1ab_0 - - perl-pathtools=3.75=pl5321h166bdaf_0 - - perl-scalar-list-utils=1.63=pl5321h166bdaf_0 - - perl-storable=3.15=pl5321h166bdaf_0 + - perl-list-moreutils-xs=0.430=pl5321h031d066_2 + - perl-parent=0.236=pl5321hdfd78af_2 + - perl-pathtools=3.75=pl5321hec16e2b_3 + - perl-scalar-list-utils=1.62=pl5321hec16e2b_1 - perl-types-serialiser=1.01=pl5321hdfd78af_0 - pexpect=4.8.0=pyh1a96a4e_2 - pickleshare=0.7.5=py_1003 - - pigz=2.6=h27826a3_0 - - pillow=9.2.0=py38h9eb91d8_3 - - pip=22.3.1=pyhd8ed1ab_0 + - pillow=10.0.0=py38h885162f_0 + - pip=23.2.1=pyhd8ed1ab_0 - pixman=0.40.0=h36c2ea0_0 - - pkgutil-resolve-name=1.3.10=pyhd8ed1ab_0 - - platformdirs=2.6.0=pyhd8ed1ab_0 - - pluggy=1.0.0=pyhd8ed1ab_5 + - pluggy=1.2.0=pyhd8ed1ab_0 - ply=3.11=py_1 - - prometheus_client=0.15.0=pyhd8ed1ab_0 - - prompt-toolkit=3.0.36=pyha770c72_0 - - psutil=5.9.4=py38h0a891b7_0 + - prompt-toolkit=3.0.39=pyha770c72_0 + - prompt_toolkit=3.0.39=hd8ed1ab_0 + - psutil=5.9.5=py38h1de0b5d_0 - pthread-stubs=0.4=h36c2ea0_1001 - ptyprocess=0.7.0=pyhd3deb0d_0 - - pulseaudio=16.1=h4a94279_0 + - pulseaudio-client=16.1=hb77b528_4 - pure_eval=0.2.2=pyhd8ed1ab_0 - pycparser=2.21=pyhd8ed1ab_0 - - pygments=2.13.0=pyhd8ed1ab_0 - - pynndescent=0.5.8=pyh1a96a4e_0 - - pyopenssl=22.1.0=pyhd8ed1ab_0 - - pyparsing=3.0.9=pyhd8ed1ab_0 - - pyqt=5.15.7=py38h7492b6b_2 - - pyqt5-sip=12.11.0=py38hfa26641_2 - - pyrsistent=0.19.2=py38h0a891b7_0 + - pygments=2.16.1=pyhd8ed1ab_0 + - pyjwt=2.8.0=pyhd8ed1ab_0 + - pynacl=1.5.0=py38h0a891b7_2 + - pyparsing=3.1.1=pyhd8ed1ab_0 + - pyqt=5.15.9=py38hffdaa6c_4 + - pyqt5-sip=12.12.2=py38h17151c0_4 - pysocks=1.7.1=pyha2e5f31_6 - - pytest=7.2.0=pyhd8ed1ab_2 - - python=3.8.15=h257c98d_0_cpython + - pytest=7.4.0=pyhd8ed1ab_0 + - python=3.8.17=he550d4f_0_cpython - python-dateutil=2.8.2=pyhd8ed1ab_0 - - python-fastjsonschema=2.16.2=pyhd8ed1ab_0 - - python-isal=1.1.0=py38h0a891b7_1 - - python-json-logger=2.0.4=pyhd8ed1ab_0 - python_abi=3.8=3_cp38 - - pytz=2022.7.1=pyhd8ed1ab_0 + - pytz=2023.3=pyhd8ed1ab_0 - pyyaml=6.0=py38h0a891b7_5 - - pyzmq=24.0.1=py38hfc09fa9_1 - - q2-alignment=2022.11.1=py38_0 - - q2-composition=2022.11.1=py38_0 - - q2-cutadapt=2022.11.1=py38_0 - - q2-dada2=2022.11.1=py38_0 - - q2-deblur=2022.11.1=py38_0 - - q2-demux=2022.11.1=py38_0 - - q2-diversity=2022.11.1=py38_0 - - q2-diversity-lib=2022.11.1=py38_0 - - q2-emperor=2022.11.1=py38_0 - - q2-feature-classifier=2022.11.1=py38_0 - - q2-feature-table=2022.11.1=py38_0 - - q2-fragment-insertion=2022.11.1=py38_0 - - q2-gneiss=2022.11.1=py38_0 - - q2-longitudinal=2022.11.1=py38_0 - - q2-metadata=2022.11.1=py38_0 - - q2-mystery-stew=2022.11.1=py38_0 - - q2-phylogeny=2022.11.1=py38_0 - - q2-quality-control=2022.11.1=py38_0 - - q2-quality-filter=2022.11.1=py38_0 - - q2-sample-classifier=2022.11.1=py38_0 - - q2-taxa=2022.11.1=py38_0 - - q2-types=2022.11.1=py38_0 - - q2-vsearch=2022.11.1=py38_0 - - q2cli=2022.11.1=py38_0 - - q2galaxy=2022.11.1=py38_0 - - q2templates=2022.11.1=py38_0 - - qiime2=2022.11.1=py38_0 - - qt-main=5.15.6=h62441b5_5 - - r-acepack=1.4.1=r42h8da6f51_1007 - - r-ade4=1.7_20=r42h5f7b363_0 - - r-ape=5.6_2=r42h9f5de39_1 - - r-askpass=1.1=r42h06615bd_3 - - r-assertthat=0.2.1=r42hc72bb7e_3 - - r-backports=1.4.1=r42h06615bd_1 - - r-base=4.2.2=h6b4767f_2 - - r-base64enc=0.1_3=r42h06615bd_1005 - - r-beeswarm=0.4.0=r42h06615bd_2 - - r-bh=1.78.0_0=r42hc72bb7e_1 - - r-bibtex=0.5.0=r42hc72bb7e_0 - - r-bit=4.0.5=r42h06615bd_0 - - r-bit64=4.0.5=r42h06615bd_1 - - r-bitops=1.0_7=r42h06615bd_1 - - r-blob=1.2.3=r42hc72bb7e_1 - - r-boot=1.3_28.1=r42hc72bb7e_0 - - r-broom=1.0.2=r42hc72bb7e_0 - - r-bslib=0.4.2=r42hc72bb7e_0 - - r-cachem=1.0.6=r42h06615bd_1 - - r-cairo=1.6_0=r42h06615bd_1 - - r-callr=3.7.3=r42hc72bb7e_0 - - r-cellranger=1.1.0=r42hc72bb7e_1005 - - r-checkmate=2.1.0=r42h06615bd_1 - - r-class=7.3_20=r42h06615bd_1 - - r-cli=3.5.0=r42h38f115c_1 - - r-clipr=0.8.0=r42hc72bb7e_1 - - r-cluster=2.1.4=r42h8da6f51_0 - - r-codetools=0.2_18=r42hc72bb7e_1 - - r-colorspace=2.0_3=r42h06615bd_1 - - r-cpp11=0.4.3=r42hc72bb7e_0 - - r-crayon=1.5.2=r42hc72bb7e_1 - - r-curl=4.3.3=r42h06615bd_1 - - r-cvxr=1.0_11=r42h7525677_0 - - r-data.table=1.14.6=r42h06615bd_0 - - r-dbi=1.1.3=r42hc72bb7e_1 - - r-dbplyr=2.2.1=r42hc72bb7e_1 - - r-deldir=1.0_6=r42h8da6f51_1 - - r-desctools=0.99.47=r42hb13c81a_0 - - r-digest=0.6.31=r42h38f115c_0 - - r-doparallel=1.0.17=r42hc72bb7e_1 - - r-dorng=1.8.3=r42hc72bb7e_0 - - r-dplyr=1.0.10=r42h7525677_1 - - r-dqrng=0.3.0=r42h7525677_1 - - r-dtplyr=1.2.2=r42hc72bb7e_2 - - r-e1071=1.7_12=r42h7525677_0 - - r-ecosolver=0.5.4=r42h06615bd_1 - - r-ellipsis=0.3.2=r42h06615bd_1 - - r-emmeans=1.8.3=r42hc72bb7e_0 - - r-energy=1.7_10=r42h7525677_1 - - r-estimability=1.4.1=r42hc72bb7e_1 - - r-evaluate=0.19=r42hc72bb7e_0 - - r-exact=3.2=r42hc72bb7e_1 - - r-expm=0.999_6=r42hb361e29_1 - - r-fansi=1.0.3=r42h06615bd_1 - - r-farver=2.1.1=r42h7525677_1 - - r-fastmap=1.1.0=r42h7525677_1 - - r-fnn=1.1.3.1=r42h7525677_1 - - r-forcats=0.5.2=r42hc72bb7e_1 - - r-foreach=1.5.2=r42hc72bb7e_1 - - r-foreign=0.8_84=r42h133d619_0 - - r-formatr=1.12=r42hc72bb7e_1 - - r-formula=1.2_4=r42hc72bb7e_1 - - r-frictionless=1.0.2=r42hc72bb7e_0 - - r-fs=1.5.2=r42h7525677_2 - - r-futile.logger=1.4.3=r42hc72bb7e_1004 - - r-futile.options=1.0.1=r42hc72bb7e_1003 - - r-gargle=1.2.1=r42hc72bb7e_1 - - r-gbrd=0.4_11=r42hc72bb7e_1004 - - r-generics=0.1.3=r42hc72bb7e_1 - - r-getopt=1.20.3=r42ha770c72_3 - - r-ggbeeswarm=0.7.1=r42hc72bb7e_0 - - r-ggplot2=3.4.0=r42hc72bb7e_1 - - r-ggrastr=1.0.1=r42hc72bb7e_1 - - r-ggrepel=0.9.2=r42h7525677_0 - - r-gld=2.6.6=r42h06615bd_0 - - r-glue=1.6.2=r42h06615bd_1 - - r-gmp=0.6_9=r42h38f115c_0 - - r-googledrive=2.0.0=r42hc72bb7e_1 - - r-googlesheets4=1.0.1=r42h785f33e_1 - - r-gridextra=2.3=r42hc72bb7e_1004 - - r-gsl=2.1_7.1=r42h9a97003_1 - - r-gtable=0.3.1=r42hc72bb7e_1 - - r-haven=2.5.1=r42h7525677_0 - - r-highr=0.9=r42hc72bb7e_1 - - r-hmisc=4.7_2=r42h8da6f51_0 - - r-hms=1.1.2=r42hc72bb7e_1 - - r-htmltable=2.4.1=r42hc72bb7e_1 - - r-htmltools=0.5.4=r42h38f115c_0 - - r-htmlwidgets=1.6.0=r42hc72bb7e_0 - - r-httr=1.4.4=r42hc72bb7e_1 - - r-hwriter=1.3.2.1=r42hc72bb7e_1 - - r-ids=1.0.1=r42hc72bb7e_2 - - r-igraph=1.3.5=r42hb34fc8a_0 - - r-interp=1.1_3=r42h7525677_1 - - r-irlba=2.3.5.1=r42h5f7b363_0 - - r-isoband=0.2.7=r42h38f115c_1 - - r-iterators=1.0.14=r42hc72bb7e_1 - - r-jpeg=0.1_10=r42h06615bd_0 - - r-jquerylib=0.1.4=r42hc72bb7e_1 - - r-jsonlite=1.8.4=r42h133d619_0 - - r-knitr=1.41=r42hc72bb7e_0 - - r-labeling=0.4.2=r42hc72bb7e_2 - - r-lambda.r=1.2.4=r42hc72bb7e_2 - - r-lattice=0.20_45=r42h06615bd_1 - - r-latticeextra=0.6_30=r42hc72bb7e_1 - - r-lazyeval=0.2.2=r42h06615bd_3 - - r-lifecycle=1.0.3=r42hc72bb7e_1 - - r-lme4=1.1_31=r42h7525677_0 - - r-lmertest=3.1_3=r42hc72bb7e_1 - - r-lmom=2.9=r42h8da6f51_1 - - r-lubridate=1.9.0=r42h133d619_0 - - r-magrittr=2.0.3=r42h06615bd_1 - - r-mass=7.3_58.1=r42h06615bd_1 - - r-matrix=1.5_3=r42h5f7b363_0 - - r-matrixstats=0.63.0=r42h06615bd_0 - - r-memoise=2.0.1=r42hc72bb7e_1 - - r-mgcv=1.8_41=r42h5f7b363_0 - - r-mime=0.12=r42h06615bd_1 - - r-minqa=1.2.5=r42hb13c81a_0 - - r-modelr=0.1.10=r42hc72bb7e_0 - - r-munsell=0.5.0=r42hc72bb7e_1005 - - r-mvtnorm=1.1_3=r42h8da6f51_1 - - r-nlme=3.1_161=r42hac0b197_0 - - r-nloptr=2.0.3=r42hb13c81a_1 - - r-nnet=7.3_18=r42h06615bd_1 - - r-numderiv=2016.8_1.1=r42hc72bb7e_4 - - r-openssl=2.0.5=r42hb1dc35e_0 - - r-optparse=1.7.3=r42hc72bb7e_1 - - r-osqp=0.6.0.7=r42h7525677_0 - - r-permute=0.9_7=r42hc72bb7e_1 - - r-pheatmap=1.0.12=r42hc72bb7e_3 - - r-pillar=1.8.1=r42hc72bb7e_1 - - r-pixmap=0.4_12=r42hc72bb7e_1 - - r-pkgconfig=2.0.3=r42hc72bb7e_2 - - r-pkgmaker=0.32.2=r42hc72bb7e_1 - - r-plogr=0.2.0=r42hc72bb7e_1004 - - r-plyr=1.8.8=r42h7525677_0 - - r-png=0.1_8=r42h10cf519_0 - - r-prettyunits=1.1.1=r42hc72bb7e_2 - - r-processx=3.8.0=r42h06615bd_0 - - r-progress=1.2.2=r42hc72bb7e_3 - - r-proxy=0.4_27=r42h06615bd_1 - - r-ps=1.7.2=r42h06615bd_0 - - r-purrr=1.0.0=r42h133d619_0 - - r-r6=2.5.1=r42hc72bb7e_1 - - r-ragg=1.2.4=r42hc1f6985_0 - - r-rappdirs=0.3.3=r42h06615bd_1 - - r-rbibutils=2.2.11=r42h133d619_0 - - r-rcolorbrewer=1.1_3=r42h785f33e_1 - - r-rcpp=1.0.9=r42h7525677_2 - - r-rcppannoy=0.0.20=r42h7525677_0 - - r-rcppeigen=0.3.3.9.3=r42h9f5de39_0 - - r-rcpphnsw=0.4.1=r42h7525677_1 - - r-rcppml=0.3.7=r42h7525677_0 - - r-rcppparallel=5.1.5=r42h7525677_1 - - r-rcppprogress=0.4.2=r42hc72bb7e_2 - - r-rcurl=1.98_1.9=r42h06615bd_1 - - r-rdpack=2.4=r42hc72bb7e_1 - - r-readr=2.1.3=r42h7525677_1 - - r-readxl=1.4.1=r42h3ebcfa7_1 - - r-registry=0.5_1=r42hc72bb7e_3 - - r-rematch=1.0.1=r42hc72bb7e_1005 - - r-rematch2=2.1.2=r42hc72bb7e_2 - - r-reprex=2.0.2=r42hc72bb7e_1 - - r-reshape2=1.4.4=r42h7525677_2 - - r-rlang=1.0.6=r42h7525677_1 - - r-rmarkdown=2.19=r42hc72bb7e_0 - - r-rmpfr=0.8_9=r42h9d0a025_1 - - r-rngtools=1.5.2=r42hc72bb7e_1 - - r-rootsolve=1.8.2.3=r42h8da6f51_1 - - r-rpart=4.1.19=r42h06615bd_0 - - r-rspectra=0.16_1=r42h9f5de39_1 - - r-rsqlite=2.2.19=r42h7525677_0 - - r-rstudioapi=0.14=r42hc72bb7e_1 - - r-rsvd=1.0.5=r42hc72bb7e_1 - - r-rtsne=0.16=r42h37cf8d7_1 - - r-rvest=1.0.3=r42hc72bb7e_1 - - r-sass=0.4.4=r42h7525677_0 - - r-scales=1.2.1=r42hc72bb7e_1 - - r-scs=3.0_1=r42h5f7b363_1 - - r-selectr=0.4_2=r42hc72bb7e_2 - - r-sitmo=2.0.2=r42h7525677_1 - - r-snow=0.4_4=r42hc72bb7e_1 - - r-sp=1.5_1=r42h06615bd_0 - - r-statmod=1.4.37=r42hc3ea6d6_1 - - r-stringi=1.7.8=r42h30a9eb7_1 - - r-stringr=1.5.0=r42h785f33e_0 - - r-survival=3.4_0=r42h06615bd_1 - - r-sys=3.4.1=r42h06615bd_0 - - r-systemfonts=1.0.4=r42h0ff29ef_1 - - r-textshaping=0.3.6=r42hbb20487_4 - - r-tibble=3.1.8=r42h06615bd_1 - - r-tidyr=1.2.1=r42h7525677_1 - - r-tidyselect=1.2.0=r42hc72bb7e_0 - - r-tidytree=0.4.2=r42hc72bb7e_0 - - r-tidyverse=1.3.2=r42hc72bb7e_1 - - r-timechange=0.1.1=r42h38f115c_1 - - r-tinytex=0.43=r42hc72bb7e_0 - - r-tzdb=0.3.0=r42h7525677_1 - - r-utf8=1.2.2=r42h06615bd_1 - - r-uuid=1.1_0=r42h06615bd_1 - - r-uwot=0.1.14=r42h7525677_1 - - r-vctrs=0.5.1=r42h7525677_0 - - r-vegan=2.6_4=r42hb20cf53_0 - - r-vipor=0.4.5=r42hc72bb7e_1004 - - r-viridis=0.6.2=r42hc72bb7e_1 - - r-viridislite=0.4.1=r42hc72bb7e_1 - - r-vroom=1.6.0=r42h7525677_1 - - r-withr=2.5.0=r42hc72bb7e_1 - - r-xfun=0.36=r42h38f115c_0 - - r-xml2=1.3.3=r42h044e5c7_2 - - r-xtable=1.8_4=r42hc72bb7e_4 - - r-yaml=2.3.6=r42h06615bd_0 - - r-yulab.utils=0.0.6=r42hc72bb7e_0 - - raxml=8.2.12=hec16e2b_4 - - readline=8.1.2=h0f457ee_0 - - requests=2.28.1=pyhd8ed1ab_1 - - samtools=1.16.1=h6899075_1 + - pyzmq=25.1.1=py38h509eb50_0 + - q2-alignment=2023.7.0.dev0=py38_0 + - q2-dada2=2023.7.0.dev0=py38_0 + - q2-demux=2023.7.0.dev0=py38_0 + - q2-feature-classifier=2023.7.0.dev0=py38_0 + - q2-feature-table=2023.7.0.dev0+2.g12ce240=py38_0 + - q2-phylogeny=2023.7.0.dev0=py38_0 + - q2-quality-control=2023.7.0.dev0=py38_0 + - q2-taxa=2023.7.0.dev0=py38_0 + - q2-types=2023.7.0.dev0+2.gaeea97d=py38_0 + - q2cli=2023.7.0.dev0+2.g946ddf5=py38_0 + - q2templates=2023.7.0.dev0=py38_0 + - qiime2=2023.7.0.dev0+11.g9d8f7b8=py38_0 + - qt-main=5.15.8=h7fe3ca9_15 + - r-base=4.3.1=h29c4799_3 + - r-bh=1.81.0_1=r43hc72bb7e_1 + - r-bitops=1.0_7=r43h57805ef_2 + - r-cli=3.6.1=r43ha503ecb_1 + - r-codetools=0.2_19=r43hc72bb7e_1 + - r-colorspace=2.1_0=r43h57805ef_1 + - r-cpp11=0.4.6=r43hc72bb7e_0 + - r-crayon=1.5.2=r43hc72bb7e_2 + - r-deldir=1.0_9=r43h61816a4_1 + - r-ellipsis=0.3.2=r43h57805ef_2 + - r-fansi=1.0.4=r43h57805ef_1 + - r-farver=2.1.1=r43ha503ecb_2 + - r-formatr=1.14=r43hc72bb7e_1 + - r-futile.logger=1.4.3=r43hc72bb7e_1005 + - r-futile.options=1.0.1=r43hc72bb7e_1004 + - r-getopt=1.20.3=r43ha770c72_4 + - r-ggplot2=3.4.2=r43hc72bb7e_1 + - r-glue=1.6.2=r43h57805ef_2 + - r-gtable=0.3.3=r43hc72bb7e_1 + - r-hwriter=1.3.2.1=r43hc72bb7e_2 + - r-interp=1.1_4=r43ha503ecb_1 + - r-isoband=0.2.7=r43ha503ecb_2 + - r-jpeg=0.1_10=r43h0de940f_3 + - r-labeling=0.4.2=r43hc72bb7e_3 + - r-lambda.r=1.2.4=r43hc72bb7e_3 + - r-lattice=0.21_8=r43h57805ef_1 + - r-latticeextra=0.6_30=r43hc72bb7e_2 + - r-lifecycle=1.0.3=r43hc72bb7e_2 + - r-magrittr=2.0.3=r43h57805ef_2 + - r-mass=7.3_60=r43h57805ef_1 + - r-matrix=1.6_0=r43h316c678_0 + - r-matrixstats=1.0.0=r43h57805ef_1 + - r-mgcv=1.9_0=r43h316c678_0 + - r-munsell=0.5.0=r43hc72bb7e_1006 + - r-nlme=3.1_163=r43h61816a4_0 + - r-optparse=1.7.3=r43hc72bb7e_2 + - r-pillar=1.9.0=r43hc72bb7e_1 + - r-pkgconfig=2.0.3=r43hc72bb7e_3 + - r-plyr=1.8.8=r43ha503ecb_1 + - r-png=0.1_8=r43h81d01c5_1 + - r-r6=2.5.1=r43hc72bb7e_2 + - r-rcolorbrewer=1.1_3=r43h785f33e_2 + - r-rcpp=1.0.11=r43h7df8631_0 + - r-rcppeigen=0.3.3.9.3=r43h08d816e_1 + - r-rcppparallel=5.1.6=r43ha503ecb_1 + - r-rcurl=1.98_1.12=r43hf9611b0_2 + - r-reshape2=1.4.4=r43ha503ecb_3 + - r-rlang=1.1.1=r43ha503ecb_1 + - r-scales=1.2.1=r43hc72bb7e_2 + - r-snow=0.4_4=r43hc72bb7e_2 + - r-stringi=1.7.12=r43hc0c3e09_2 + - r-stringr=1.5.0=r43h785f33e_1 + - r-tibble=3.2.1=r43h57805ef_2 + - r-utf8=1.2.3=r43h57805ef_1 + - r-vctrs=0.6.3=r43ha503ecb_0 + - r-viridislite=0.4.2=r43hc72bb7e_1 + - r-withr=2.5.0=r43hc72bb7e_2 + - raxml=8.2.12=h031d066_6 + - readline=8.2=h8228510_1 + - requests=2.31.0=pyhd8ed1ab_0 + - rocm-smi=5.6.0=h59595ed_1 + - samtools=1.17=hd87286a_1 - scandir=1.10.0=py38h0a891b7_6 - scikit-bio=0.5.7=py38h6c62de6_0 - scikit-learn=0.24.1=py38h658cfdd_0 - scipy=1.8.1=py38h8ce737c_3 - - seaborn=0.12.1=hd8ed1ab_0 - - seaborn-base=0.12.1=pyhd8ed1ab_0 + - seaborn=0.12.2=hd8ed1ab_0 + - seaborn-base=0.12.2=pyhd8ed1ab_0 - sed=4.8=he412f7d_0 - - send2trash=1.8.0=pyhd8ed1ab_0 - - sepp=4.3.10=py38h3252c3a_2 - - setuptools=65.6.3=pyhd8ed1ab_0 - - sip=6.7.5=py38hfa26641_0 + - seqkit=2.5.1=h9ee0642_0 + - setproctitle=1.3.2=py38h0a891b7_1 + - setuptools=68.0.0=pyhd8ed1ab_0 + - sip=6.7.11=py38h17151c0_0 - six=1.16.0=pyh6c4a22f_0 - - sniffio=1.3.0=pyhd8ed1ab_0 - - soothsayer_utils=2022.2.9=py_0 - - sortmerna=2.0=he860b03_4 - - soupsieve=2.3.2.post1=pyhd8ed1ab_0 + - soothsayer_utils=2022.6.24=py_0 - stack_data=0.6.2=pyhd8ed1ab_0 - - statsmodels=0.13.5=py38h26c90d9_2 - - sysroot_linux-64=2.12=he073ed8_15 - - tbb=2021.7.0=h924138e_1 - - terminado=0.17.1=pyh41d4057_0 - - threadpoolctl=3.1.0=pyh8a188c0_0 - - tinycss2=1.2.1=pyhd8ed1ab_0 + - statsmodels=0.14.0=py38h31356c5_1 + - sysroot_linux-64=2.12=he073ed8_16 + - tbb=2021.10.0=h00ab1b0_0 + - tblib=1.7.0=pyhd8ed1ab_0 + - threadpoolctl=3.2.0=pyha21a80b_0 - tk=8.6.12=h27826a3_0 - tktable=2.10=hb7b940f_3 - toml=0.10.2=pyhd8ed1ab_0 - tomli=2.0.1=pyhd8ed1ab_0 - - tornado=6.2=py38h0a891b7_1 - - tqdm=4.64.1=pyhd8ed1ab_0 - - traitlets=5.8.0=pyhd8ed1ab_0 - - typing-extensions=4.4.0=hd8ed1ab_0 - - typing_extensions=4.4.0=pyha770c72_0 + - tomlkit=0.12.1=pyha770c72_0 + - tornado=6.3.2=py38h01eb140_0 + - tqdm=4.66.1=pyhd8ed1ab_0 + - traitlets=5.9.0=pyhd8ed1ab_0 + - typeguard=2.13.3=pyhd8ed1ab_0 + - typing-extensions=4.7.1=hd8ed1ab_0 + - typing_extensions=4.7.1=pyha770c72_0 - tzlocal=2.1=pyh9f0ad1d_0 - - umap-learn=0.5.3=py38h578d9bd_0 - unicodedata2=15.0.0=py38h0a891b7_0 - - unifrac=1.1.1=py38h17adfb0_1 - - unifrac-binaries=1.1.1=h15a0faf_4 - - urllib3=1.26.13=pyhd8ed1ab_0 - - vsearch=2.22.1=hf1761c0_0 - - wcwidth=0.2.5=pyh9f0ad1d_2 - - webencodings=0.5.1=py_1 - - websocket-client=1.4.2=pyhd8ed1ab_0 - - wget=1.20.3=ha56f1ee_1 - - wheel=0.38.4=pyhd8ed1ab_0 - - widgetsnbextension=4.0.4=pyhd8ed1ab_0 - - wrapt=1.14.1=py38h0a891b7_1 - - xcb-util=0.4.0=h166bdaf_0 - - xcb-util-image=0.4.0=h166bdaf_0 - - xcb-util-keysyms=0.4.0=h166bdaf_0 - - xcb-util-renderutil=0.3.9=h166bdaf_0 - - xcb-util-wm=0.4.1=h166bdaf_0 + - urllib3=2.0.4=pyhd8ed1ab_0 + - vsearch=2.23.0=h6a68c12_0 + - wcwidth=0.2.6=pyhd8ed1ab_0 + - wget=1.20.3=ha35d2d1_1 + - wheel=0.41.1=pyhd8ed1ab_0 + - widgetsnbextension=4.0.8=pyhd8ed1ab_0 + - xcb-util=0.4.0=hd590300_1 + - xcb-util-image=0.4.0=h8ee46fc_1 + - xcb-util-keysyms=0.4.0=h8ee46fc_1 + - xcb-util-renderutil=0.3.9=hd590300_1 + - xcb-util-wm=0.4.1=h8ee46fc_1 + - xkeyboard-config=2.39=hd590300_0 - xmltodict=0.13.0=pyhd8ed1ab_0 - - xopen=1.7.0=py38h578d9bd_0 - - xorg-fixesproto=5.0=h7f98852_1002 - - xorg-inputproto=2.3.2=h7f98852_1002 - xorg-kbproto=1.0.7=h7f98852_1002 - - xorg-libice=1.0.10=h7f98852_0 - - xorg-libsm=1.2.3=hd9c2040_1000 - - xorg-libx11=1.7.2=h7f98852_0 - - xorg-libxau=1.0.9=h7f98852_0 + - xorg-libice=1.1.1=hd590300_0 + - xorg-libsm=1.2.4=h7391055_0 + - xorg-libx11=1.8.6=h8ee46fc_0 + - xorg-libxau=1.0.11=hd590300_0 - xorg-libxdmcp=1.1.3=h7f98852_0 - - xorg-libxext=1.3.4=h7f98852_1 - - xorg-libxfixes=5.0.3=h7f98852_1004 - - xorg-libxi=1.7.10=h7f98852_0 - - xorg-libxrender=0.9.10=h7f98852_1003 - - xorg-libxt=1.2.1=h7f98852_2 - - xorg-libxtst=1.2.3=h7f98852_1002 - - xorg-recordproto=1.14.2=h7f98852_1002 + - xorg-libxext=1.3.4=h0b41bf4_2 + - xorg-libxrender=0.9.11=hd590300_0 + - xorg-libxt=1.3.0=hd590300_1 - xorg-renderproto=0.11.1=h7f98852_1002 - - xorg-xextproto=7.3.0=h7f98852_1002 + - xorg-xextproto=7.3.0=h0b41bf4_1003 + - xorg-xf86vidmodeproto=2.3.1=h7f98852_1002 - xorg-xproto=7.0.31=h7f98852_1007 - - xyzservices=2022.9.0=pyhd8ed1ab_0 - xz=5.2.6=h166bdaf_0 - yaml=0.2.5=h7f98852_2 - - yq=3.1.0=pyhd8ed1ab_0 + - yq=3.2.2=pyhd8ed1ab_0 - zeromq=4.3.4=h9c3ff4c_1 - - zipp=3.11.0=pyhd8ed1ab_0 - - zlib=1.2.13=h166bdaf_4 - - zstandard=0.19.0=py38h5945529_1 - - zstd=1.5.2=h3eb15da_5 + - zlib=1.2.13=hd590300_5 + - zstd=1.5.2=hfc55251_7 diff --git a/src/README.md b/src/README.md index 6f3a264..6ad49e8 100755 --- a/src/README.md +++ b/src/README.md @@ -5,23 +5,23 @@ | Status | Environment | Module | Resources | Recommended Threads | Description | |---------------|------------------------------|-------------------------|-------------|---------------------|-----------------------------------------------------------------------------------------------------------------| -| Stable | VEBA-preprocess_env | preprocess.py | 4GB-16GB | 4 | Fastq quality trimming, adapter removal, decontamination, and read statistics calculations | -| Stable | VEBA-assembly_env | assembly.py | 32GB-128GB+ | 4-16 | Assemble reads, align reads to assembly, and count mapped reads | -| Stable | VEBA-assembly_env | coverage.py | 24GB | 16 | Align reads to (concatenated) reference and counts mapped reads | -| Stable | VEBA-binning-prokaryotic_env | binning-prokaryotic.py | 16GB | 4 | Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment | -| Stable | VEBA-binning-eukaryotic_env | binning-eukaryotic.py | 128GB | 4 | Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment | -| Stable | VEBA-binning-viral_env | binning-viral.py | 16GB | 4 | Detection of viral genomes and quality assessment | -| Stable | VEBA-classify_env | classify-prokaryotic.py | 72GB | 32 | Taxonomic classification of prokaryotic genomes | -| Stable | VEBA-classify_env | classify-eukaryotic.py | 32GB | 1 | Taxonomic classification of eukaryotic genomes | -| Stable | VEBA-classify_env | classify-viral.py | 16GB | 4 | Taxonomic classification of viral genomes | -| Stable | VEBA-cluster_env | cluster.py | 32GB+ | 32 | Species-level clustering of genomes and lineage-specific orthogroup detection | -| Stable | VEBA-annotate_env | annotate.py | 64GB | 32 | Annotates translated gene calls against UniRef, MiBIG, VFDB, Pfam, AntiFam, and KOFAM | -| Stable | VEBA-phylogeny_env | phylogeny.py | 16GB+ | 32 | Constructs phylogenetic trees given a marker set | -| Stable | VEBA-mapping_env | index.py | 16GB | 4 | Builds local or global index for alignment to genomes | -| Stable | VEBA-mapping_env | mapping.py | 16GB | 4 | Aligns reads to local or global index of genomes | -| Stable | VEBA-biosynthetic_env | biosynthetic.py | 16GB | 16 | Identify biosynthetic gene clusters in prokaryotes and fungi | -| Developmental | VEBA-assembly_env | assembly-sequential.py | 32GB-128GB+ | 16 | Assemble metagenomes sequentially | -| Developmental | VEBA-amplicon_env | amplicon.py | 96GB | 16 | Automated read trim position detection, DADA2 ASV detection, taxonomic classification, and file conversion | +| Stable | [VEBA-preprocess_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-preprocess_env.yml) | [preprocess.py](https://github.com/jolespin/veba/tree/main/src#preprocesspy) | 4GB-16GB | 4 | Fastq quality trimming, adapter removal, decontamination, and read statistics calculations | +| Stable | [VEBA-assembly_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-assembly_env.yml) | [assembly.py](https://github.com/jolespin/veba/tree/main/src#assemblypy) | 32GB-128GB+ | 4-16 | Assemble reads, align reads to assembly, and count mapped reads | +| Stable | [VEBA-assembly_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-assembly_env.yml) | [coverage.py](https://github.com/jolespin/veba/tree/main/src#coveragepy) | 24GB | 16 | Align reads to (concatenated) reference and counts mapped reads | +| Stable | [VEBA-binning-prokaryotic_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-binning-prokaryotic_env.yml) | [binning-prokaryotic.py](https://github.com/jolespin/veba/tree/main/src#binning-prokaryoticpy) | 16GB | 4 | Iterative consensus binning for recovering prokaryotic genomes with lineage-specific quality assessment | +| Stable | [VEBA-binning-eukaryotic_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-binning-eukaryotic_env.yml) | [binning-eukaryotic.py](https://github.com/jolespin/veba/tree/main/src#binning-eukaryoticpy) | 128GB | 4 | Binning for recovering eukaryotic genomes with exon-aware gene modeling and lineage-specific quality assessment | +| Stable | [VEBA-binning-viral_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-binning-viral_env.yml) | [binning-viral.py](https://github.com/jolespin/veba/tree/main/src#binning-viralpy) | 16GB | 4 | Detection of viral genomes and quality assessment | +| Stable | [VEBA-classify_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-classify_env.yml) | [classify-prokaryotic.py](https://github.com/jolespin/veba/tree/main/src#classify-prokaryoticpy) | 72GB | 32 | Taxonomic classification of prokaryotic genomes | +| Stable | [VEBA-classify_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-classify_env.yml) | [classify-eukaryotic.py](https://github.com/jolespin/veba/tree/main/src#classify-eukaryoticpy) | 32GB | 1 | Taxonomic classification of eukaryotic genomes | +| Stable | [VEBA-classify_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-classify_env.yml) | [classify-viral.py](https://github.com/jolespin/veba/tree/main/src#classify-viralpy) | 16GB | 4 | Taxonomic classification of viral genomes | +| Stable | [VEBA-cluster_env](https://github.com/jolespin/veba/blob/main/install/environments/[VEBA-cluster_env.yml) | [cluster.py](https://github.com/jolespin/veba/tree/main/src#clusterpy) | 32GB+ | 32 | Species-level clustering of genomes and lineage-specific orthogroup detection | +| Stable | [VEBA-annotate_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-annotate_env.yml) | [annotate.py](https://github.com/jolespin/veba/tree/main/src#annotatepy) | 64GB | 32 | Annotates translated gene calls against UniRef, MiBIG, VFDB, Pfam, AntiFam, and KOFAM | +| Stable | [VEBA-phylogeny_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-phylogeny_env.yml) | [phylogeny.py](https://github.com/jolespin/veba/tree/main/src#phylogenypy) | 16GB+ | 32 | Constructs phylogenetic trees given a marker set | +| Stable | [VEBA-mapping_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-mapping_env.yml) | [index.py](https://github.com/jolespin/veba/tree/main/src#indexpy) | 16GB | 4 | Builds local or global index for alignment to genomes | +| Stable | [VEBA-mapping_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-mapping_env.yml) | [mapping.py](https://github.com/jolespin/veba/tree/main/src#mappingpy) | 16GB | 4 | Aligns reads to local or global index of genomes | +| Stable | [VEBA-biosynthetic_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-biosynthetic_env.yml) | [biosynthetic.py](https://github.com/jolespin/veba/tree/main/src#biosyntheticpy) | 16GB | 16 | Identify biosynthetic gene clusters in prokaryotes and fungi | +| Developmental | [VEBA-assembly_env](https://github.com/jolespin/veba/blob/main/install/environments/VEBA-assembly_env.yml) | [assembly-sequential.py](https://github.com/jolespin/veba/tree/main/src#assembly-sequentialpy) | 32GB-128GB+ | 16 | Assemble metagenomes sequentially | +| Developmental | [VEBA-amplicon_env](https://github.com/jolespin/veba/blob/main/install/environments/devel/VEBA-amplicon_env.yml) | [amplicon.py](https://github.com/jolespin/veba/tree/main/src#ampliconpy) | 96GB | 16 | Automated read trim position detection, DADA2 ASV detection, taxonomic classification, and file conversion |

^__^

@@ -1316,7 +1316,7 @@ Novelty score threshold arguments: * bgc.components.tsv - All of the BGC components (i.e., genes in BGC) in tabular format organized by genome, contig, region, and gene. * bgc.synopsis.tsv - All of the BGCs in tabular format organized by genome, contig, region, and gene. * bgc.type_counts.tsv - Summary of BGCs detected organized by type. Also includes summary of BGCs that are NOT on contig edge. -* components/*.faa.gz - BGC components in fasta format +* fasta/*.faa.gz - BGC components in fasta format * genbanks/[id\_genome]/*.gbk - Genbank formatted antiSMASH results

^__^

diff --git a/src/assembly.py b/src/assembly.py index 6053539..c3e1d85 100755 --- a/src/assembly.py +++ b/src/assembly.py @@ -12,11 +12,10 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.9.11" # Assembly def get_assembly_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): - # Command # MEGAHIT if opts.program == "megahit": @@ -38,6 +37,7 @@ def get_assembly_cmd( input_filepaths, output_filepaths, output_directory, direc "--tmp-dir {}".format(directories["tmp"]), "--num-cpu-threads {}".format(opts.n_jobs), "--memory {}".format(opts.megahit_memory), + "--presets {}".format(opts.megahit_preset) if bool(opts.megahit_preset) else "", opts.assembler_options, ")", "&&", @@ -370,6 +370,7 @@ def add_executables_to_environment(opts): os.environ[name] = executable.strip() print("", file=sys.stdout) + # Pipeline def create_pipeline(opts, directories, f_cmds): @@ -650,9 +651,12 @@ def configure_parameters(opts, directories): assert opts.forward_reads != opts.reverse_reads, "You probably mislabeled the input files because `forward_reads` should not be the same as `reverse_reads`: {}".format(opts.forward_reads) assert_acceptable_arguments(opts.program, {"spades.py", "metaspades.py", "rnaspades.py", "megahit", "metaplasmidspades.py", "plasmidspades.py", "coronaspades.py"}) - if opts.program in {"metaplasmidspades.py", "plasmidspades.py", "coronaspades.py"}: - print("UserWarning: {} has not been thoroughly tested with VEBA. If any issues arise, please use one of the following instead: {spades.py, metaspades.py, rnaspades.py, megahit}".format(opts.program), file=sys.stdout) + if opts.program in {"metaplasmidspades.py", "plasmidspades.py", "coronaspades.py"}: + print("UserWarning: {} has not been thoroughly tested with VEBA. If any issues arise, please use one of the following instead: [spades.py, metaspades.py, rnaspades.py, megahit]".format(opts.program), file=sys.stdout) + if opts.megahit_preset: + assert_acceptable_arguments(opts.megahit_preset, {"meta-sensitive", "meta-large"}) + assert "--presets" not in opts.assembler_options, "Cannot have --presets in --assembler_options and set it using --megahit_preset" # Scaffold prefix if opts.scaffold_prefix == "NONE": opts.scaffold_prefix = "" @@ -660,6 +664,7 @@ def configure_parameters(opts, directories): if "NAME" in opts.scaffold_prefix: opts.scaffold_prefix = opts.scaffold_prefix.replace("NAME", opts.name) print("Using the following prefix for all {} scaffolds: {}".format(opts.program, opts.scaffold_prefix), file=sys.stdout) + # Set environment variables add_executables_to_environment(opts=opts) @@ -704,7 +709,8 @@ def main(args=None): # MEGAHIT parser_megahit = parser.add_argument_group('MEGAHIT arguments') - parser_megahit.add_argument("--megahit_memory", type=float, default=0.9, help="MEGAHIT | RAM limit in Gb (terminates if exceeded). [Default: 0.9]") + parser_megahit.add_argument("--megahit_memory", type=float, default=0.99, help="MEGAHIT | Max memory in byte to be used in SdBG construction. If set between 0-1, fraction of the machine's total memory. [Default: 0.99]") + parser_megahit.add_argument("--megahit_preset", type=str, help="MEGAHIT | meta-sensitive: '--min-count 1 --k-list 21,29,39,49,...,129,141' meta-large: '--k-min 27 --k-max 127 --k-step 10' (large & complex metagenomes, like soil)") # Aligner parser_aligner = parser.add_argument_group('Bowtie2 arguments') diff --git a/src/binning-prokaryotic.py b/src/binning-prokaryotic.py index c88805b..d98233c 100755 --- a/src/binning-prokaryotic.py +++ b/src/binning-prokaryotic.py @@ -12,7 +12,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.7.7" +__version__ = "2023.9.10" # Assembly def get_coverage_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -298,7 +298,10 @@ def get_dastool_cmd(input_filepaths, output_filepaths, output_directory, directo "grep", '"^>"', "|", + 'cut -f1 -d " "', + "|", "cut -c2-", + ">", os.path.join(output_directory, "__DASTool_bins", "eukaryota", "eukaryota.scaffolds.list"), @@ -519,17 +522,20 @@ def get_consolidate_cmd(input_filepaths, output_filepaths, output_directory, dir cmd = [ - "rm -rf {}".format(os.path.join(output_directory, "*")), + """ +rm -rf {} +mkdir -p {} +S2B=$(ls {}) || (echo 'No genomes have been detected' && exit 1) + +""".format( + os.path.join(output_directory, "*"), + os.path.join(output_directory, "genomes"), + os.path.join(directories["intermediate"], "*__checkm2", "filtered", "scaffolds_to_bins.tsv"), + ), ] cmd += [ - "&&", - - "mkdir -p {}".format(os.path.join(output_directory, "genomes")), - - "&&", - # scaffolds_to_bins.tsv "cat", os.path.join(directories["intermediate"], "*__checkm2", "filtered", "scaffolds_to_bins.tsv"), ">", @@ -1378,15 +1384,16 @@ def create_pipeline(opts, directories, f_cmds): # i/o input_filepaths = [ - os.path.join(directories["intermediate"], "*__checkm2", "filtered", "scaffolds_to_bins.tsv"), - os.path.join(directories["intermediate"], "*__checkm2", "filtered", "bins.list"), - os.path.join(directories["intermediate"], "*__checkm2", "filtered", "binned.list"), + # os.path.join(directories["intermediate"], "*__checkm2", "filtered", "scaffolds_to_bins.tsv"), + # os.path.join(directories["intermediate"], "*__checkm2", "filtered", "bins.list"), + # os.path.join(directories["intermediate"], "*__checkm2", "filtered", "binned.list"), os.path.join(directories["intermediate"], "*__checkm2", "filtered", "checkm2_results.filtered.tsv"), os.path.join(directories["intermediate"], "*__checkm2", "filtered", "genomes", "*"), - os.path.join(directories[("intermediate", "{}__trnascan-se".format(step-2))], "*.tRNA"), - os.path.join(directories[("intermediate", "{}__barrnap".format(step-3))], "*.rRNA"), os.path.join(directories[("intermediate", "{}__featurecounts".format(step-1))], "featurecounts.orfs.tsv.gz"), + # Can't assume these are not empty + # os.path.join(directories[("intermediate", "{}__trnascan-se".format(step-2))], "*.tRNA"), + # os.path.join(directories[("intermediate", "{}__barrnap".format(step-3))], "*.rRNA"), ] output_filenames = [ @@ -1423,7 +1430,7 @@ def create_pipeline(opts, directories, f_cmds): cmd=cmd, input_filepaths = input_filepaths, output_filepaths = output_filepaths, - validate_inputs=False, + validate_inputs=True, validate_outputs=True, log_prefix=program_label, ) diff --git a/src/biosynthetic.py b/src/biosynthetic.py index 82f0840..5553fad 100755 --- a/src/biosynthetic.py +++ b/src/biosynthetic.py @@ -12,7 +12,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.7.11" +__version__ = "2023.8.30" # antiSMASH def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -258,6 +258,7 @@ def get_diamond_cmd( input_filepaths, output_filepaths, output_directory, direct os.environ["concatenate_dataframes.py"], "-a 1", "--prepend_column_levels MiBIG,VFDB", + "--sort_by bitscore", "-o {}".format(os.path.join(directories["output"], "homology.tsv.gz")), os.path.join(output_directory, "diamond_output.mibig.tsv"), os.path.join(output_directory, "diamond_output.vfdb.tsv"), diff --git a/src/classify-prokaryotic.py b/src/classify-prokaryotic.py index d083d3b..063cffe 100755 --- a/src/classify-prokaryotic.py +++ b/src/classify-prokaryotic.py @@ -14,7 +14,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.16" +__version__ = "2023.9.11" # GTDB-Tk def get_gtdbtk_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -133,20 +133,9 @@ def get_consensus_cluster_classification_cmd( input_filepaths, output_filepaths, # Command cmd = [ - - # cat gtdbtk.summary.tsv | cut -f1,3,18 | tail -n +2 | - "cat", - input_filepaths[0], #[id_mag][id_genome_cluster][classification][weight] - "|", - os.environ["cut_table_by_column_labels.py"], - "-f classification,msa_percent" - "|", - "tail -n +2", - "|", - os.environ["insert_column_to_table.py"], - "-c {}".format(opts.clusters), - "-n id_genome_cluster", - "-i 0", + os.environ["compile_prokaryotic_genome_cluster_classification_scores_table.py"], + "-i {}".format(input_filepaths[0]), + "-c {}".format(input_filepaths[1]), "|", os.environ["consensus_genome_classification.py"], "--leniency {}".format(opts.leniency), @@ -165,10 +154,11 @@ def add_executables_to_environment(opts): Adapted from Soothsayer: https://github.com/jolespin/soothsayer """ accessory_scripts = set([ - "cut_table_by_column_labels.py", + "compile_prokaryotic_genome_cluster_classification_scores_table.py", + # "cut_table_by_column_labels.py", "concatenate_dataframes.py", "consensus_genome_classification.py", - "insert_column_to_table.py", + # "insert_column_to_table.py", "compile_krona.py", ]) diff --git a/src/cluster.py b/src/cluster.py index be73e82..51132d8 100755 --- a/src/cluster.py +++ b/src/cluster.py @@ -12,7 +12,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.14" +__version__ = "2023.8.30" # Global clustering def get_global_clustering_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -194,7 +194,7 @@ def create_pipeline(opts, directories, f_cmds): # ========== # Local clustering # ========== - if not opts.no_local_clustering: + if opts.local_clustering: step = 2 program = "local_clustering" @@ -236,9 +236,6 @@ def create_pipeline(opts, directories, f_cmds): errors_ok=False, ) - - - return pipeline # Configure parameters @@ -255,17 +252,17 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -m -a -o -t 95".format(__program__) + usage = "{} -i -o -A 95 -a easy-cluster".format(__program__) epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" # Parser parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') - parser_io.add_argument("-i", "--input", type=str, required=True, help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins] but can include additional columns to the right (e.g., [cds][gene_models]). Suggested input is from `compile_genomes_table.py` script.") + parser_io.add_argument("-i", "--input", type=str, default="stdin", help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") parser_io.add_argument("-o","--output_directory", type=str, default="veba_output/cluster", help = "path/to/project_directory [Default: veba_output/cluster]") parser_io.add_argument("-e", "--no_singletons", action="store_true", help="Exclude singletons") #isPSLC-1_SSO-3345__SRR178126 - parser_io.add_argument("--no_local_clustering", action="store_true", help = "Only do global clustering") + parser_io.add_argument("-l", "--local_clustering", action="store_true", help = "Perform local clustering after global clustering") # Utility parser_utility = parser.add_argument_group('Utility arguments') diff --git a/src/scripts/.DS_Store b/src/scripts/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d8bf018fae65eb0ca722d1f58fda56dad86952b4 GIT binary patch literal 10244 zcmeHMeQXp(6rZFkz|EfnY?hl>?#X@NqaP(i$2i%<#{xzaB_&b`}(ZrIyBZuhPr zL93vDs4<#o)Tl&*CjKKLe#Dq))M!G~Xkv(pCdQA%fAkMc_{*sHW@iuXN}Cu>l;BRX z`*z;EH#_s2-b&?+v&dx$`YK#0Hv1lazd;>Bb%kfU7sTL<3!6aZy8 zs{Mk`EFEAzY9OP59OV*QumE>O$z35xF@U?HUJdM`fgI(MyAueK4+!22L52eU+p&Mu zHzyF~GQ5Wfga}-U01Fohvvq@HNVYhCPmZ{mq~#>B{q*s6hnv(xZOfaR)D?+Yrvt&3 zK&FhD-hC6zO?OjP635Ey$DO>jcUe5;q%(epWxq_C$S&=4b_O8PcR#uPx8wBv_K9uv zzigWsE_#1?GaJUZED7b)G9a_p^H zgSGdk94nW$-Gp47(yUa@(mbQxvf}1#hCARH2R!yP{?lwqs%RMaodZ&RIZ}~xum&2XL<+uI}~-k zNaa^|>NvnHj?Q}2{T4-07f`x`={vFp-nm**8Wy30*6ZRtf?+OMDvGk(yAOk!yGku9 zs3_~(EiGwSfogRds=E909N$2B!#awiZyLa8OXGZ8u2y0ll**@fvv+UST(2i%*cV0> z+d%1%JFI1mo)O$t3Lf2*`fG-I=CWQlJtxZnQI#G_2U%zBx{m$GvukJFvQ1HNg-knd z;vR+z&bKo8Q%Y%FbP-=1A5^g}>PH}|C{f4~{4KPR?DTicBjkSa6gfs-Ag__P$Z7I1 zIZM7IUy-lL_v9yXj{FMcPyv-dp&I7ERWKLkK_jeymCyi7Y@Q*a0HIRJ@5cL3Xj3#@FYA3C*Vam38&y4_y9hHkKl9o2EK*w;2iu8f53S` z6s8LcgzJQQp+Q(AvREe^@aW8#Y^O-4N})-5ie&a zvk0l4KBH#lyt?|u%Ujn(FOZ1SuE4Zn2K%Hfp}J36f1Ss&4P!}}Bh8I$M^+mLF2)d# zsX~Owm5jfX5ly*TnjeuXkoUZLt+X&APeRV~YD1(kA`2L5yt*XPEXidUNTToC5^0oV zpuDn5YLn!03@Kh+E3K1c!lInl+anzjnFYw-pz2HH4e~Zp^$b$=2l6BNnVd&@PK8>e z=0d24255rCNYB;K25Vp~tcMM-5jG)Nd!Y~dVGxEO1qM=e7);252RX>Y9dIWS_9)zq zbbSyWf`{P|I0jF{GjJT9^@;m3yfTK0n@Xt&ZnHyI{|cRbIIR$Y5CMh& zly}BESt3|S634Vzd`Tw6hJP~97 literal 0 HcmV?d00001 diff --git a/src/scripts/compile_genomes_table.py b/src/scripts/compile_genomes_table.py index 924d9c1..6158492 100755 --- a/src/scripts/compile_genomes_table.py +++ b/src/scripts/compile_genomes_table.py @@ -9,7 +9,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.2.9" +__version__ = "2023.8.28" def main(args=None): @@ -34,6 +34,7 @@ def main(args=None): # parser_io.add_argument("--protein_fasta_extension", default="faa", type=str, help = "File extension. Include the period/fullstop/. [Default: faa]") parser_io.add_argument("-a", "--absolute", action="store_true", help = "Use absolute paths instead of relative paths") parser_io.add_argument("-e", "--allow_missing_files", action="store_true", help = "Allow missing files") + parser_io.add_argument("--volume_prefix", type=str, help = "Docker container prefix to volume path") parser_io.add_argument("--header", action="store_true", help = "Write header") # Options @@ -70,6 +71,10 @@ def main(args=None): if opts.absolute: df_output = df_output.applymap(lambda fp: os.path.abspath(fp)) + # Docker volume prefix + if opts.volume_prefix: + df_output = df_output.applymap(lambda fp: os.path.join(opts.volume_prefix, fp) if pd.notnull(fp) else fp) + if opts.output == "stdout": opts.output = sys.stdout diff --git a/src/scripts/compile_krona.py b/src/scripts/compile_krona.py index 59905c1..a4673d5 100755 --- a/src/scripts/compile_krona.py +++ b/src/scripts/compile_krona.py @@ -7,7 +7,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.12" +__version__ = "2023.9.5" def main(args=None): @@ -100,8 +100,8 @@ def main(args=None): # if opts.remove_genome_column: # df_output = df_output.drop("genome_id", axis=1) - df_output = df_input.reset_index().loc[:,["id_genome", "bgc_type", "number_of_bgcs"]] - df_output.columns = ["id_genome", "bgc_type", "count"] + df_output = df_input.reset_index().loc[:,["number_of_bgcs", "bgc_type","id_genome"]] + df_output.columns = [ "count", "bgc_type","id_genome"] if opts.mode == "biosynthetic-local": # if opts.remove_incomplete: @@ -112,8 +112,8 @@ def main(args=None): # if opts.remove_genome_column: # df_output = df_output.drop("genome_id", axis=1) - df_output = df_input.reset_index().loc[:,["bgc_type", "number_of_bgcs"]] - df_output.columns = ["bgc_type", "count"] + df_output = df_input.reset_index().loc[:,[ "number_of_bgcs", "bgc_type"]] + df_output.columns = ["count", "bgc_type"] df_output.sort_values("count", ascending=False).to_csv(opts.output, sep="\t", header=None, index=None) diff --git a/src/scripts/compile_prokaryotic_genome_cluster_classification_scores_table.py b/src/scripts/compile_prokaryotic_genome_cluster_classification_scores_table.py new file mode 100755 index 0000000..863c915 --- /dev/null +++ b/src/scripts/compile_prokaryotic_genome_cluster_classification_scores_table.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +import sys, os, glob, argparse, warnings +from collections import OrderedDict +import pandas as pd + +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.9.11" + +def main(argv=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -c -o ".format(__program__) + epilog = "Copyright 2022 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + + # Pipeline + + # I/O + parser_io = parser.add_argument_group('I/O arguments') + parser_io.add_argument("-i","--gtdbtk_results", type=str, required=True, help = "path/to/gtdbtk_results.tsv for when MASH_DB is used. [Required]") + parser_io.add_argument("-c","--clusters", type=str, required=True, help = "path/to/clusters.tsv where [id_genome][id_genome-cluster], No header. [Required]") + parser_io.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.tsv [Default: stdout]") + parser_io.add_argument("--prioritize", type=str, default="msa", help = "Prioritize {msa, ani} [Default: msa]") + parser_io.add_argument("--fill_missing_weight", type=float, help = "Fill missing weight between [0, 100.0]. [Default is to throw error if value is missing]") + parser_io.add_argument("--header", action="store_true", help = "Include header") + + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Checks + assert opts.prioritize in {"msa", "ani"}, "--prioritize must be either msa or ani" + if opts.fill_missing_weight: + assert 0 <= opts.fill_missing_weight <= 100.0, "--fill_missing_weight must be between 0 and 100" + + # Output + if opts.output == "stdout": + opts.output = sys.stdout + + # Inputs + df_gtdbtk_results = pd.read_csv(opts.gtdbtk_results, sep="\t", index_col=0) + genome_to_genomecluster = pd.read_csv(opts.clusters, sep="\t", index_col=0, header=None).iloc[:,0] + + # Check overlap between genomes + A = set(df_gtdbtk_results.index) + B = set(genome_to_genomecluster.index) + C = A & B + D = (A | B) - C + assert A == B, "Not all genomes overlap between --gtdbtk_results and --clusters.\n * Genomes only in --gtdbtk_results: {}\n * Genomes only in clusters: {}".format(A - B, B - A) + + # Get classifications and weight for genomes + genome_to_weight = dict() + genome_to_classification = dict() + for id_genome, row in df_gtdbtk_results.iterrows(): + classification, ani, msa = row[["classification", "fastani_ani", "msa_percent"]] + assert pd.notnull(classification), "Missing classification for `{}`".format(id_genome) + genome_to_classification[id_genome] = classification + + ani_not_null = pd.notnull(ani) + msa_not_null = pd.notnull(msa) + if all([ani_not_null, msa_not_null]): + warnings.warn(f"`{id_genome}` has values for both `fastani_ani` and `msa_percent`. Prioritizing by `{opts.prioritize}`") + if opts.prioritize == "msa": + genome_to_weight[id_genome] = msa + if opts.prioritize == "ani": + genome_to_weight[id_genome] = ani + else: + if msa_not_null: + genome_to_weight[id_genome] = msa + if ani_not_null: + genome_to_weight[id_genome] = ani + genome_to_weight = pd.Series(genome_to_weight) + genome_to_classification = pd.Series(genome_to_classification) + + # Missing weights + null_weights = genome_to_weight.isnull() + if null_weights.sum() >= 1: + warnings.warn("Missing weights for the following genomes: {}".format(null_weights.index[null_weights].tolist())) + if not opts.fill_missing_weight: + raise Exception("Please ensure all genomes have a weight attribute for msa_percent or fastani_ani. The alternative is to fill missing values with --fill_missing_weight") + else: + genome_to_weight[null_weights] = opts.fill_missing_weight + + df_output = genome_to_genomecluster.to_frame("id_genome-cluster") + df_output["classification"] = genome_to_classification + df_output["weight"] = genome_to_weight + df_output.index.name = "id_genome" + + df_output.to_csv(opts.output, sep="\t", header=bool(opts.header)) + + + + +if __name__ == "__main__": + main() + + + diff --git a/src/scripts/compile_reads_table.py b/src/scripts/compile_reads_table.py index 9c887ba..3b3edd8 100755 --- a/src/scripts/compile_reads_table.py +++ b/src/scripts/compile_reads_table.py @@ -7,7 +7,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.1.26" +__version__ = "2023.8.28" def parse_basename(query: str, naming_scheme: str): """ @@ -56,6 +56,7 @@ def main(args=None): parser_output.add_argument("-1", "--forward_label", default="forward-absolute-filepath", type=str, help = "Forward filepath column label [Default: forward-absolute-filepath]") parser_output.add_argument("-2", "--reverse_label", default="reverse-absolute-filepath", type=str, help = "Reverse filepath column label [Default: reverse-absolute-filepath]") parser_output.add_argument("--header", action="store_true", help = "Write header") + parser_output.add_argument("--volume_prefix", type=str, help = "Docker container prefix to volume path") # Options opts = parser.parse_args() @@ -101,8 +102,13 @@ def main(args=None): if "absolute" in opts.reverse_label.lower(): print("You've selected --relative and may want to either not use a header or remove 'absolute' from the --reverse_label: {}".format(opts.reverse_label), file=sys.stderr) + # Docker volume prefix + if opts.volume_prefix: + df_output = df_output.applymap(lambda fp: os.path.join(opts.volume_prefix, fp) if pd.notnull(fp) else fp) + if opts.output == "stdout": opts.output = sys.stdout + df_output.to_csv(opts.output, sep="\t", header=bool(opts.header)) if __name__ == "__main__": diff --git a/src/scripts/compile_veba_synopsis.py b/src/scripts/compile_veba_synopsis.py deleted file mode 100755 index a7a4d7f..0000000 --- a/src/scripts/compile_veba_synopsis.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function, division -import sys, os, argparse, glob -from collections import defaultdict -import numpy as np -import pandas as pd -from tqdm import tqdm - -pd.options.display.max_colwidth = 100 -# from tqdm import tqdm -__program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.2.9" - - -def main(args=None): - # Path info - script_directory = os.path.dirname(os.path.abspath( __file__ )) - script_filename = __program__ - # Path info - description = """ - Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -i -o ".format(__program__) - epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" - - # Parser - parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) - # Pipeline - - parser.add_argument("-i","--veba_directory", type=str, help = "path/to/hmmsearch_tblout.tsv", required=True) - parser.add_argument("-o","--output_directory", type=str, default="veba_output/synopsis", help = "Output directory [Default: veba_output/synopsis]") - parser.add_argument("-a", "--include_assembly", action="store_true", help="Output query identifiers only") - parser.add_argument("-b", "--include_assembly", action="store_true", help="Output query identifiers only") - - # Options - opts = parser.parse_args() - opts.script_directory = script_directory - opts.script_filename = script_filename - - - -if __name__ == "__main__": - main() diff --git a/src/scripts/concatenate_dataframes.py b/src/scripts/concatenate_dataframes.py index 0555e91..d36a27a 100755 --- a/src/scripts/concatenate_dataframes.py +++ b/src/scripts/concatenate_dataframes.py @@ -3,7 +3,7 @@ import pandas as pd __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2022.5.10" +__version__ = "2023.8.30" def main(argv=None): # Path info @@ -28,6 +28,8 @@ def main(argv=None): parser.add_argument("-e", "--allow_empty_or_missing_files", action="store_true", help = "Allow empty or missing files") parser.add_argument("--prepend_index_levels", type=str, help = "Comma-separeted list to prepend to index of each dataframe.") parser.add_argument("--prepend_column_levels", type=str, help = "Comma-separeted list to prepend to header of each dataframe. Must match the number of dataframes exactly.") + parser.add_argument("--sort_by", type=str, help = "Sort by values. This happens before (to remove duplicates) and after concatenation.") + parser.add_argument("--ascending", action="store_true", help = "Ascending order instead of descending") # Options opts = parser.parse_args(argv) @@ -66,6 +68,15 @@ def main(argv=None): df = None try: df = pd.read_csv(fp, sep=opts.delimiter, index_col=opts.index_column, header=opts.header) + + # Handle duplicates + if opts.sort_by: + df = df.sort_values(by=opts.sort_by, ascending=bool(opts.ascending)) + number_of_duplicates = len(df.index.value_counts()[lambda x: x > 1].index) + if number_of_duplicates > 0: + warnings.warn(f"{fp} contains {number_of_duplicates} duplicates. Removing them now and keeping first instance.") + df = df[~df.index.duplicated(keep='first')] + if opts.prepend_column_levels is not None: df.columns = df.columns.map(lambda x: (opts.prepend_column_levels[i], x)) if opts.prepend_index_levels is not None: @@ -78,6 +89,15 @@ def main(argv=None): dataframes = list() for i, fp in enumerate(opts.dataframes): df = pd.read_csv(fp, sep=opts.delimiter, index_col=opts.index_column, header=opts.header) + + # Handle duplicates + if opts.sort_by: + df = df.sort_values(by=opts.sort_by, ascending=bool(opts.ascending)) + number_of_duplicates = len(df.index.value_counts()[lambda x: x > 1].index) + if number_of_duplicates > 0: + warnings.warn(f"{fp} contains {number_of_duplicates} duplicates. Removing them now and keeping first instance.") + df = df[~df.index.duplicated(keep='first')] + if opts.prepend_column_levels is not None: df.columns = df.columns.map(lambda x: (opts.prepend_column_levels[i], x)) if opts.prepend_index_levels is not None: diff --git a/src/scripts/genomad_taxonomy_wrapper.py b/src/scripts/genomad_taxonomy_wrapper.py index 394ad08..4992aaf 100755 --- a/src/scripts/genomad_taxonomy_wrapper.py +++ b/src/scripts/genomad_taxonomy_wrapper.py @@ -12,7 +12,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.8" +__version__ = "2023.8.16" # MetaEuk @@ -53,7 +53,7 @@ def get_genomad_taxonomy_cmd(input_filepaths, output_filepaths, output_directory "cp -rf", os.path.join(output_directory, "intermediate", "input_annotate", "input_taxonomy.tsv"), - os.path.join(output_directory, "viral_taxonomy.tsv"), + os.path.join(output_directory, "taxonomy.tsv"), "&&", @@ -103,7 +103,7 @@ def create_pipeline(opts, directories, f_cmds): output_directory = directories["output"] output_filepaths = [ - os.path.join(output_directory, "viral_taxonomy.tsv"), + os.path.join(output_directory, "taxonomy.tsv"), ] params = { @@ -289,7 +289,7 @@ def main(args=None): # shutil.rmtree(directories["intermediate"]) update_table = False - df_genomad_taxonomy = pd.read_csv(os.path.join(directories["output"], "viral_taxonomy.tsv"), sep="\t", index_col=0) + df_genomad_taxonomy = pd.read_csv(os.path.join(directories["output"], "taxonomy.tsv"), sep="\t", index_col=0) if not opts.remove_unclassified: @@ -323,12 +323,12 @@ def main(args=None): update_table = True if update_table: - df_genomad_taxonomy.to_csv(os.path.join(directories["output"], "viral_taxonomy.tsv"), sep="\t") + df_genomad_taxonomy.to_csv(os.path.join(directories["output"], "taxonomy.tsv"), sep="\t") # if (not opts.remove_unclassified) or (opts.scaffolds_to_bins): - # validate_file_existence([os.path.join(directories["output"], "viral_taxonomy.tsv")], prologue="Validating the following updated files:") + # validate_file_existence([os.path.join(directories["output"], "taxonomy.tsv")], prologue="Validating the following updated files:") diff --git a/src/scripts/global_clustering.py b/src/scripts/global_clustering.py index 2e11f78..c534bf9 100755 --- a/src/scripts/global_clustering.py +++ b/src/scripts/global_clustering.py @@ -14,7 +14,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.15" +__version__ = "2023.8.30" def get_basename(x): _, fn = os.path.split(x) @@ -116,10 +116,11 @@ def main(args=None): parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') - parser_io.add_argument("-i", "--input", type=str, default="stdin", help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins] but can include additional columns to the right (e.g., [cds][gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") + parser_io.add_argument("-i", "--input", type=str, default="stdin", help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") parser_io.add_argument("-o","--output_directory", type=str, default="global_clustering_output", help = "path/to/project_directory [Default: global_clustering_output]") parser_io.add_argument("-e", "--no_singletons", action="store_true", help="Exclude singletons") #isPSLC-1_SSPC-3345__SRR178126 - parser_io.add_argument("-r", "--no_representative_sequences", action="store_true", help="Exclude representative sequences") #isPSLC-1_SSPC-3345__SRR178126 + parser_io.add_argument("-r", "--no_representative_sequences", action="store_true", help="Do not write representative sequences to fasta") #isPSLC-1_SSPC-3345__SRR178126 + parser_io.add_argument("-C", "--no_core_sequences", action="store_true", help="Do not write core pagenome sequences to fasta") #isPSLC-1_SSPC-3345__SRR178126 # Utility parser_utility = parser.add_argument_group('Utility arguments') @@ -163,6 +164,8 @@ def main(args=None): directories["project"] = create_directory(opts.output_directory) directories["output"] = create_directory(os.path.join(directories["project"], "output")) directories["pangenome_tables"] = create_directory(os.path.join(directories["output"], "pangenome_tables")) + if not opts.no_representative_sequences: + directories["pangenome_core_sequences"] = create_directory(os.path.join(directories["output"], "pangenome_core_sequences")) directories["serialization"] = create_directory(os.path.join(directories["output"], "serialization")) # directories[("misc", "pangenomes", "networkx_graphs")] = create_directory(os.path.join(directories[("misc", "pangenomes")], "networkx_graphs")) @@ -190,9 +193,9 @@ def main(args=None): if opts.input == "stdin": opts.input = sys.stdin df_genomes = pd.read_csv(opts.input, sep="\t", header=None) - assert df_genomes.shape[1] >= 5, "Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins] but can include additional columns to the right (e.g., [cds][gene_models]). Suggested input is from `compile_genomes_table.py` script. You have provided a table with {} columns.".format(df_genomes.shape[1]) - df_genomes = df_genomes.iloc[:,:5] - df_genomes.columns = ["organism_type", "id_sample", "id_mag", "genome", "proteins"] + assert df_genomes.shape[1] >= 6, "Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. You have provided a table with {} columns.".format(df_genomes.shape[1]) + df_genomes = df_genomes.iloc[:,:6] + df_genomes.columns = ["organism_type", "id_sample", "id_mag", "genome", "proteins", "cds"] assert not np.any(df_genomes.isnull()), "Input has missing values. Please correct this.\n{}".format(df_genomes.loc[df_genomes.isnull().sum(axis=1)[lambda x: x > 0].index].to_string()) # Make directories @@ -223,6 +226,7 @@ def main(args=None): protein_to_mag = dict() protein_to_sample = dict() protein_to_sequence = dict() + protein_to_cds = dict() for i, row in pv(df_genomes.iterrows(), "Organizing identifiers"): id_mag = row["id_mag"] @@ -245,6 +249,12 @@ def main(args=None): protein_to_sequence[id_protein] = seq mag_to_numberofproteins[id_mag] += 1 + with get_file_object(row["cds"], "read", verbose=False) as f: + for header, seq in SimpleFastaParser(f): + id_protein = header.split(" ")[0] + assert id_protein in protein_to_sequence, "CDS sequence identifier must be in protein fasta: {} from {}".format(id_protein, row["cds"]) + protein_to_cds[id_protein] = seq + mag_to_numberofscaffolds = pd.Series(mag_to_numberofscaffolds) mag_to_numberofproteins = pd.Series(mag_to_numberofproteins) mag_to_sample = pd.Series(mag_to_sample) @@ -254,6 +264,7 @@ def main(args=None): protein_to_mag = pd.Series(protein_to_mag) protein_to_sample = pd.Series(protein_to_sample) protein_to_sequence = pd.Series(protein_to_sequence) + protein_to_cds = pd.Series(protein_to_cds) # Commands f_cmds = open(os.path.join(opts.output_directory, "commands.sh"), "w") @@ -486,15 +497,45 @@ def main(args=None): # print(format_header(" * ({}) Compiling pangenome protein cluster prevalence tables:".format(format_duration(t0))), file=sys.stdout) genome_to_number_of_singletons = list() genome_to_ratio_of_singletons = list() - for id_genomecluster, df in pv(df_proteins.groupby("id_genome_cluster"), description=" * ({}) Writing protein prevalence pangenome tables and counting singletons".format(format_duration(t0)), unit="genome cluster", total=df_proteins["id_genome_cluster"].nunique()): + protein_to_number_of_genomes_detected = dict() + protein_to_ratio_of_genomes_detected = dict() + genomecluster_to_corepangenome = dict() + genomecluster_to_singletons = dict() + + for id_genomecluster, df in pv(df_proteins.groupby("id_genome_cluster"), description=" * ({}) Writing protein prevalence pangenome tables, counting singletons, and core pangenome sequences".format(format_duration(t0)), unit="genome cluster", total=df_proteins["id_genome_cluster"].nunique()): # Prevalence df = df.reset_index().loc[:,["id_genome", "id_protein", "id_protein_cluster"]] df_prevalence = get_protein_cluster_prevalence(df) df_prevalence.to_csv(os.path.join(directories["pangenome_tables"], f"{id_genomecluster}.tsv.gz"), sep="\t") + # Detected/Not-detected + df_prevalence = df_prevalence > 0 + number_of_genomes_detected = df_prevalence.sum(axis=0) + ratio_of_genomes_detected = df_prevalence.mean(axis=0) + protein_to_number_of_genomes_detected.update(number_of_genomes_detected.to_dict()) + protein_to_ratio_of_genomes_detected.update(ratio_of_genomes_detected.to_dict()) + + # Core + core_proteinclusters = ratio_of_genomes_detected[lambda x: x == 1].index + genomecluster_to_corepangenome[id_genomecluster] = set(core_proteinclusters) + if not opts.no_core_sequences: + with open(os.path.join(directories["pangenome_core_sequences"], f"{id_genomecluster}.faa"), "w") as f_core: + for id_proteincluster, id_representative in proteincluster_to_representative[core_proteinclusters].items(): + seq = protein_to_sequence[id_representative] + header = f"{id_proteincluster} {id_representative}" + print(f">{header}\n{seq}", file=f_core) + + with open(os.path.join(directories["pangenome_core_sequences"], f"{id_genomecluster}.ffn"), "w") as f_core: + for id_proteincluster, id_representative in proteincluster_to_representative[core_proteinclusters].items(): + seq = protein_to_cds[id_representative] + header = f"{id_proteincluster} {id_representative}" + print(f">{header}\n{seq}", file=f_core) + + # Singletons + genomecluster_to_singletons[id_genomecluster] = set(number_of_genomes_detected[lambda x: x == 1].index) + if df_prevalence.shape[0] > 1: - df_prevalence = df_prevalence > 0 number_of_singletons = df_prevalence.loc[:,df_prevalence.sum(axis=0)[lambda x: x == 1].index].sum(axis=1) ratio_of_singletons = number_of_singletons/df_prevalence.sum(axis=1) genome_to_number_of_singletons.append(number_of_singletons) @@ -506,6 +547,23 @@ def main(args=None): else: warnings.warn("No clusters with more than 1 genome so singleton analysis does not apply") + genomecluster_to_corepangenome = pd.Series(genomecluster_to_corepangenome) + genomecluster_to_singletons = pd.Series(genomecluster_to_singletons) + + + # Add detection number and ratios + df_proteinclusters["number_of_genomes_detected"] = pd.Series(protein_to_number_of_genomes_detected) + df_proteinclusters["ratio_of_genomes_detected"] = pd.Series(protein_to_ratio_of_genomes_detected) + df_proteinclusters["core_pangenome"] = df_proteinclusters["ratio_of_genomes_detected"] == 1 + df_proteinclusters["singleton"] = df_proteinclusters["number_of_genomes_detected"] == 1 + + + df_genomeclusters["number_of_proteins_in_core_pangenome"] = genomecluster_to_corepangenome.map(len) + df_genomeclusters["core_pangenome"] = genomecluster_to_corepangenome + df_genomeclusters["number_of_singleton_proteins"] = genomecluster_to_singletons.map(len) + df_genomeclusters["singletons"] = genomecluster_to_singletons + + # # Symlink pangenome graphs # print(format_header(" * ({}) Symlinking pangenome protein cluster NetworkX Graphs:".format(format_duration(t0))), file=sys.stdout) # for src in glob.glob(os.path.join(directories["intermediate"], "*", "clusters", "*","output", "protein_clusters.networkx_graph.pkl")): diff --git a/src/scripts/local_clustering.py b/src/scripts/local_clustering.py index 8f1fca6..6ee100b 100755 --- a/src/scripts/local_clustering.py +++ b/src/scripts/local_clustering.py @@ -14,7 +14,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.15" +__version__ = "2023.8.30" def get_basename(x): _, fn = os.path.split(x) @@ -106,7 +106,7 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -f -d -i -o ".format(__program__) + usage = "{} -i -o -A 95 -a easy-cluster".format(__program__) epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" @@ -114,10 +114,11 @@ def main(args=None): parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') - parser_io.add_argument("-i", "--input", type=str, default="stdin", help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins] but can include additional columns to the right (e.g., [cds][gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") + parser_io.add_argument("-i", "--input", type=str, default="stdin", help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") parser_io.add_argument("-o","--output_directory", type=str, default="local_clustering_output", help = "path/to/project_directory [Default: local_clustering_output]") parser_io.add_argument("-e", "--no_singletons", action="store_true", help="Exclude singletons") #isPSLC-1_SSPC-3345__SRR178126 parser_io.add_argument("-r", "--no_representative_sequences", action="store_true", help="Exclude representative sequences") #isPSLC-1_SSPC-3345__SRR178126 + parser_io.add_argument("-C", "--no_core_sequences", action="store_true", help="Do not write core pagenome sequences to fasta") #isPSLC-1_SSPC-3345__SRR178126 # Utility parser_utility = parser.add_argument_group('Utility arguments') @@ -161,6 +162,8 @@ def main(args=None): directories["project"] = create_directory(opts.output_directory) directories["output"] = create_directory(os.path.join(directories["project"], "output")) directories["pangenome_tables"] = create_directory(os.path.join(directories["output"], "pangenome_tables")) + if not opts.no_representative_sequences: + directories["pangenome_core_sequences"] = create_directory(os.path.join(directories["output"], "pangenome_core_sequences")) directories["serialization"] = create_directory(os.path.join(directories["output"], "serialization")) directories["log"] = create_directory(os.path.join(directories["project"], "log")) directories["tmp"] = create_directory(os.path.join(directories["project"], "tmp")) @@ -186,9 +189,9 @@ def main(args=None): if opts.input == "stdin": opts.input = sys.stdin df_genomes = pd.read_csv(opts.input, sep="\t", header=None) - assert df_genomes.shape[1] >= 5, "Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins] but can include additional columns to the right (e.g., [cds][gene_models]). Suggested input is from `compile_genomes_table.py` script. You have provided a table with {} columns.".format(df_genomes.shape[1]) - df_genomes = df_genomes.iloc[:,:5] - df_genomes.columns = ["organism_type", "id_sample", "id_mag", "genome", "proteins"] + assert df_genomes.shape[1] >= 6, "Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. You have provided a table with {} columns.".format(df_genomes.shape[1]) + df_genomes = df_genomes.iloc[:,:6] + df_genomes.columns = ["organism_type", "id_sample", "id_mag", "genome", "proteins", "cds"] assert not np.any(df_genomes.isnull()), "Input has missing values. Please correct this.\n{}".format(df_genomes.loc[df_genomes.isnull().sum(axis=1)[lambda x: x > 0].index].to_string()) # Make directories @@ -223,6 +226,7 @@ def main(args=None): protein_to_mag = dict() protein_to_sample = dict() protein_to_sequence = dict() + protein_to_cds = dict() for i, row in pv(df_genomes.iterrows(), "Organizing identifiers"): id_mag = row["id_mag"] @@ -245,6 +249,12 @@ def main(args=None): protein_to_sequence[id_protein] = seq mag_to_numberofproteins[id_mag] += 1 + with get_file_object(row["cds"], "read", verbose=False) as f: + for header, seq in SimpleFastaParser(f): + id_protein = header.split(" ")[0] + assert id_protein in protein_to_sequence, "CDS sequence identifier must be in protein fasta: {} from {}".format(id_protein, row["cds"]) + protein_to_cds[id_protein] = seq + mag_to_numberofscaffolds = pd.Series(mag_to_numberofscaffolds) mag_to_numberofproteins = pd.Series(mag_to_numberofproteins) mag_to_sample = pd.Series(mag_to_sample) @@ -254,6 +264,7 @@ def main(args=None): protein_to_mag = pd.Series(protein_to_mag) protein_to_sample = pd.Series(protein_to_sample) protein_to_sequence = pd.Series(protein_to_sequence) + protein_to_cds = pd.Series(protein_to_cds) # Commands f_cmds = open(os.path.join(opts.output_directory, "commands.sh"), "w") @@ -493,15 +504,44 @@ def main(args=None): # print(format_header(" * ({}) Compiling pangenome protein cluster prevalence tables:".format(format_duration(t0))), file=sys.stdout) genome_to_number_of_singletons = list() genome_to_ratio_of_singletons = list() + protein_to_number_of_genomes_detected = dict() + protein_to_ratio_of_genomes_detected = dict() + genomecluster_to_corepangenome = dict() + genomecluster_to_singletons = dict() + for id_genomecluster, df in pv(df_proteins.groupby("id_genome_cluster"), description=" * ({}) Writing protein prevalence pangenome tables and counting singletons".format(format_duration(t0)), unit="genome cluster", total=df_proteins["id_genome_cluster"].nunique()): # Prevalence df = df.reset_index().loc[:,["id_genome", "id_protein", "id_protein_cluster"]] df_prevalence = get_protein_cluster_prevalence(df) df_prevalence.to_csv(os.path.join(directories["pangenome_tables"], f"{id_genomecluster}.tsv.gz"), sep="\t") + # Detected/Not-detected + df_prevalence = df_prevalence > 0 + number_of_genomes_detected = df_prevalence.sum(axis=0) + ratio_of_genomes_detected = df_prevalence.mean(axis=0) + protein_to_number_of_genomes_detected.update(number_of_genomes_detected.to_dict()) + protein_to_ratio_of_genomes_detected.update(ratio_of_genomes_detected.to_dict()) + + # Core + core_proteinclusters = ratio_of_genomes_detected[lambda x: x == 1].index + genomecluster_to_corepangenome[id_genomecluster] = set(core_proteinclusters) + if not opts.no_core_sequences: + with open(os.path.join(directories["pangenome_core_sequences"], f"{id_genomecluster}.faa"), "w") as f_core: + for id_proteincluster, id_representative in proteincluster_to_representative[core_proteinclusters].items(): + seq = protein_to_sequence[id_representative] + header = f"{id_proteincluster} {id_representative}" + print(f">{header}\n{seq}", file=f_core) + + with open(os.path.join(directories["pangenome_core_sequences"], f"{id_genomecluster}.ffn"), "w") as f_core: + for id_proteincluster, id_representative in proteincluster_to_representative[core_proteinclusters].items(): + seq = protein_to_cds[id_representative] + header = f"{id_proteincluster} {id_representative}" + print(f">{header}\n{seq}", file=f_core) + # Singletons + genomecluster_to_singletons[id_genomecluster] = set(number_of_genomes_detected[lambda x: x == 1].index) + if df_prevalence.shape[0] > 1: - df_prevalence = df_prevalence > 0 number_of_singletons = df_prevalence.loc[:,df_prevalence.sum(axis=0)[lambda x: x == 1].index].sum(axis=1) ratio_of_singletons = number_of_singletons/df_prevalence.sum(axis=1) genome_to_number_of_singletons.append(number_of_singletons) @@ -513,6 +553,21 @@ def main(args=None): else: warnings.warn("No clusters with more than 1 genome so singleton analysis does not apply") + genomecluster_to_corepangenome = pd.Series(genomecluster_to_corepangenome) + genomecluster_to_singletons = pd.Series(genomecluster_to_singletons) + + # Add detection number and ratios + df_proteinclusters["number_of_genomes_detected"] = pd.Series(protein_to_number_of_genomes_detected) + df_proteinclusters["ratio_of_genomes_detected"] = pd.Series(protein_to_ratio_of_genomes_detected) + df_proteinclusters["core_pangenome"] = df_proteinclusters["ratio_of_genomes_detected"] == 1 + df_proteinclusters["singleton"] = df_proteinclusters["number_of_genomes_detected"] == 1 + + + df_genomeclusters["number_of_proteins_in_core_pangenome"] = genomecluster_to_corepangenome.map(len) + df_genomeclusters["core_pangenome"] = genomecluster_to_corepangenome + df_genomeclusters["number_of_singleton_proteins"] = genomecluster_to_singletons.map(len) + df_genomeclusters["singletons"] = genomecluster_to_singletons + # Writing output files print(format_header(" * ({}) Writing Output Tables:".format(format_duration(t0))), file=sys.stdout) df_mags.to_csv(os.path.join(directories["output"], "identifier_mapping.genomes.tsv"), sep="\t") diff --git a/walkthroughs/README.md b/walkthroughs/README.md index 3b66c45..ec7bb46 100644 --- a/walkthroughs/README.md +++ b/walkthroughs/README.md @@ -40,7 +40,7 @@ sbatch -J ${N} -N 1 -c ${N_JOBS} --ntasks-per-node=1 -o logs/${N}.o -e logs/${N} * **[Bioprospecting for biosynthetic gene clusters](bioprospecting_for_biosynthetic_gene_clusters.md)** - Detecting biosynthetic gene clusters (BGC) with and scoring novelty of BGCs. * **[Converting counts tables](converting_counts_tables.md)** - Convert your counts table (with or without metadata) to [anndata](https://anndata.readthedocs.io/en/latest/index.html) or [biom](https://biom-format.org/) format. Also supports [Pandas pickle](https://pandas.pydata.org/docs/reference/api/pandas.read_pickle.html) format. * **[Adapting commands for Docker](adapting_commands_for_docker.md)** - Explains how to download and use Docker for running VEBA. - +* **[Adapting commands for AWS](adapting_commands_for_aws.md)** - Explains how to download and use Docker for running VEBA specifically on AWS. ___________________________________________ diff --git a/walkthroughs/adapting_commands_for_aws.md b/walkthroughs/adapting_commands_for_aws.md new file mode 100644 index 0000000..ac80761 --- /dev/null +++ b/walkthroughs/adapting_commands_for_aws.md @@ -0,0 +1,146 @@ +### Adapting commands for use with Docker and AWS +Containerization is a solution to using VEBA on any system and portability for resources such as AWS or Google Cloud. Here is the guide for using these containers specifically with AWS. + +_____________________________________________________ + +#### Steps: + +1. Set up AWS infrastructure +2. Create and register a job definition +3. Submit job definition + +_____________________________________________________ + + +#### 1. Set up AWS infrastructure + +Out of scope for this tutorial but essentially you need to do the following: + +* Set up AWS EFS (Elastic File System) via Terraform to read/write/mount data +* Compile database in EFS +* Create compute environment +* Create job queue linked to compute environment + + +#### 2. Create and register a job definition + +Once the job queue is properly set up, next is to create a job definition and then submit the job definition to the queue. + +The preferred way to submit jobs with AWS Batch is using json files for the job definition through Fargate. + +Here is a template you can use for a job definition. + +This job definition pulls the [jolespin/veba_preprocess](https://hub.docker.com/r/jolespin/veba_preprocess/tags) Docker image and mounts EFS directories to volumes within the Docker container. The actual job runs the [preprocess.py module](https://github.com/jolespin/veba/tree/main/src#preprocesspy) of VEBA for a sample called [S1](https://zenodo.org/record/7946802). + + +```json +{ + "jobDefinitionName": "preprocess__S1", + "type": "container", + "containerProperties": { + "image": "jolespin/veba_preprocess:1.2.0", + "command": [ + "preprocess.py", + "-1", + "/volumes/input/Fastq/S1_1.fastq.gz", + "-2", + "/volumes/input/Fastq/S1_2.fastq.gz", + "-n", + "1", + "-o", + "/volumes/output/veba_output/preprocess", + "-p", + "16" + "-x", + "/volumes/database/Contamination/chm13v2.0/chm13v2.0" + ], + "jobRoleArn": "arn:aws:iam::xxx:role/ecsTaskExecutionRole", + "executionRoleArn": "arn:aws:iam::xxx:role/ecsTaskExecutionRole", + "volumes": [ + { + "name": "efs-volume-database", + "efsVolumeConfiguration": { + "fileSystemId": "fs-xxx", + "transitEncryption": "ENABLED", + "rootDirectory": "databases/veba/VDB_v5.1/" + } + }, + { + "name": "efs-volume-input", + "efsVolumeConfiguration": { + "fileSystemId": "fs-xxx", + "transitEncryption": "ENABLED", + "rootDirectory": "path/to/efs/input/" + } + }, + { + "name": "efs-volume-output", + "efsVolumeConfiguration": { + "fileSystemId": "fs-xxx", + "transitEncryption": "ENABLED", + "rootDirectory": "path/to/efs/output/" + } + } + ], + "mountPoints": [ + { + "sourceVolume": "efs-volume-database", + "containerPath": "/volumes/database", + "readOnly": true + }, + { + "sourceVolume": "efs-volume-input", + "containerPath": "/volumes/input", + "readOnly": true + }, + { + "sourceVolume": "efs-volume-output", + "containerPath": "/volumes/output", + "readOnly": false + } + ], + "environment": [], + "ulimits": [], + "resourceRequirements": [ + { + "value": "16.0", + "type": "VCPU" + }, + { + "value": "8000", + "type": "MEMORY" + } + ], + "networkConfiguration": { + "assignPublicIp": "ENABLED" + }, + "fargatePlatformConfiguration": { + "platformVersion": "LATEST" + }, + "ephemeralStorage": { + "sizeInGiB": 40 + } + }, + "tags": { + "Name": "preprocess__S1" + }, + "platformCapabilities": [ + "FARGATE" + ] +} +``` + +Now register the job definition: + +``` +FILE=/path/to/preprocess/S1.json +aws batch register-job-definition --cli-input-json file://${FILE} +``` + +#### 3. Run Docker container + +Next step is to submit the job to the queue. + +``` +aws batch submit-job --job-definition ${JOB} --job-name ${JOB} --job-queue ${QUEUE} +``` \ No newline at end of file diff --git a/walkthroughs/adapting_commands_for_docker.md b/walkthroughs/adapting_commands_for_docker.md index 99d2c3d..a61683a 100644 --- a/walkthroughs/adapting_commands_for_docker.md +++ b/walkthroughs/adapting_commands_for_docker.md @@ -41,13 +41,13 @@ VERSION=1.2.0 # Image DOCKER_IMAGE="jolespin/veba_preprocess:${VERSION}" -docker run --name VEBA-preprocess --rm -it ${DOCKER_IMAGE} -c "preprocess.py -h" +docker run --name VEBA-preprocess --rm -it ${DOCKER_IMAGE} preprocess.py -h ``` If we wanted to run it interactively, start the container with `bash` (it automatically loads the appropriate `conda` environment): ``` -docker run --name VEBA-preprocess --rm -it ${DOCKER_IMAGE} -c "bash" +docker run --name VEBA-preprocess --rm -it ${DOCKER_IMAGE} bash ``` Though, it's the `preprocess.py` module so it you're running anything other than a toy dataset, then you probably want to run it on the grid so you can go to do something else. @@ -58,7 +58,12 @@ For the output directory, it requires an additional step. We first need to spec We link these with the `--volume` argument so any file in the `LOCAL_WORKING_DIRECTORY` will be mirrored in the `CONTAINER_INPUT_DIRECTORY` and any files created in the `CONTAINER_OUTPUT_DIRECTORY` (i.e., the `RELATIVE_OUTPUT_DIRECTORY`) will be mirrored in the `LOCAL_OUTPUT_PARENT_DIRECTORY`. -Note: If we don't link the local and container output directories then the output files will be stranded in the container. +For some modules, you will need the VEBA Database. To set the `LOCAL_DATABASE_DIRECTORY` to the `${VEBA_DATABASE}` environment variable or alternatively the path to the VEBA Database (refer to the [*VEBA Database Documentation*](https://github.com/jolespin/veba/blob/main/install/DATABASE.md#database-structure)). We mount this to `CONTAINER_DATABASE_DIRECTORY` which is `/volumes/database/` volume in the container. + + +**Note:** + +If we don't link the local and container output directories then the output files will be stranded in the container. ```bash # Directories @@ -66,7 +71,7 @@ LOCAL_WORKING_DIRECTORY=$(pwd) LOCAL_WORKING_DIRECTORY=$(realpath -m ${LOCAL_WORKING_DIRECTORY}) LOCAL_OUTPUT_PARENT_DIRECTORY=../ LOCAL_OUTPUT_PARENT_DIRECTORY=$(realpath -m ${LOCAL_OUTPUT_PARENT_DIRECTORY}) -LOCAL_DATABASE_DIRECTORY=${VEBA_DATABASE} +LOCAL_DATABASE_DIRECTORY=${VEBA_DATABASE} # /path/to/VEBA_DATABASE/ LOCAL_DATABASE_DIRECTORY=$(realpath -m ${LOCAL_DATABASE_DIRECTORY}) CONTAINER_INPUT_DIRECTORY=/volumes/input/ @@ -98,10 +103,11 @@ docker run \ --volume ${LOCAL_OUTPUT_PARENT_DIRECTORY}:${CONTAINER_OUTPUT_DIRECTORY}:rw \ --volume ${LOCAL_DATABASE_DIRECTORY}:${CONTAINER_DATABASE_DIRECTORY}:ro \ ${DOCKER_IMAGE} \ - -c "${CMD}" + ${CMD} ``` + #### 4. Get the results Now that the container has finished running the commands. Let's view the results. If you don't have `tree` in your environment, you should download it because it's useful. `mamba install -c conda-forge tree` From e0be1ac4d602c3d3466851d616600478ecba097e Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Tue, 12 Sep 2023 11:02:19 -0700 Subject: [PATCH 14/16] CHANGELOG formatting --- .DS_Store | Bin 12292 -> 0 bytes CHANGELOG.md | 20 ++++++++++---------- src/scripts/.DS_Store | Bin 10244 -> 0 bytes 3 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 .DS_Store delete mode 100644 src/scripts/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 620218bf4e72e05a38ab7e59cf643147bd549a3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12292 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 L&yVcaG5-GtW*Df| diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fd7378..380d60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ ________________________________________________________________
- **Release v1.1.2:** + **Release v1.1.2:** * Created Docker images for all modules * Replaced all absolute path symlinks with relative symlinks @@ -48,7 +48,7 @@ ________________________________________________________________
- **Release v1.1.1:** + **Release v1.1.1:** * Most important update includes fixing a broken VEBA-`binning-viral.yml` install recipe which had package conflicts for `aria2` 30e8b0a. * Fixes on conda-related environment variables in the install scripts. @@ -62,7 +62,7 @@ ________________________________________________________________
- **Release v1.1.0:** + **Release v1.1.0:** * **Modules**: * `annotate.py` @@ -172,7 +172,7 @@ ________________________________________________________________
- **Release v1.0.4:** + **Release v1.0.4:** * Added `biopython` to `VEBA-assembly_env` which is needed when running `MEGAHIT` as the scaffolds are rewritten and [an error](https://github.com/jolespin/veba/issues/17) was raised. [aea51c3](https://github.com/jolespin/veba/commit/aea51c3e0b775aec90f7343f01cad6911f526f0a) * Updated Microeukaryotic protein database to exclude a few higher eukaryotes that were present in database, changed naming scheme to hash identifiers (from `cat reference.faa | seqkit fx2tab -s -n > id_to_hash.tsv`). Switching database from [FigShare](https://figshare.com/articles/dataset/Microeukaryotic_Protein_Database/19668855) to [Zenodo](https://zenodo.org/record/7485114#.Y6vZO-zMKDU). Uses database version `VDB_v3` which has the updated microeukaryotic protein database (`VDB-Microeukaryotic_v2`) [0845ba6](https://github.com/jolespin/veba/commit/0845ba6be65f3486d61fe7ae21a2937efeb42ee9) @@ -180,7 +180,7 @@ ________________________________________________________________
- **Release v1.0.3e:** + **Release v1.0.3e:** * Patch fix for `install_veba.sh` where `install/environments/VEBA-assembly_env.yml` raised [a compatibilty error](https://github.com/jolespin/veba/issues/15) when creating the `VEBA-assembly_env` environment. [c2ab957](https://github.com/jolespin/veba/commit/c2ab957be132d34e6b99d6dea394be4572b83066) * Patch fix for `VirFinder_wrapper.R` where `__version__ = ` variable was throwing [an R error](https://github.com/jolespin/veba/issues/13) when running `binning-viral.py` module. [19e8f38](https://github.com/jolespin/veba/commit/19e8f38a5050328b7ba88b2271f0221073748cbb) @@ -201,7 +201,7 @@ ________________________________________________________________
- **Release v1.0.2a:** + **Release v1.0.2a:** * Updated *GTDB-Tk* in `VEBA-binning-prokaryotic_env` from `1.x` to `2.x` (this version uses much less memory): [f3507dd](https://github.com/jolespin/veba/commit/f3507dd13a42960e3671c9f8a106c9974fbfce21) * Updated the *GTDB-Tk* database from `R202` to `R207_v2` to be compatible with *GTDB-Tk v2.x*: [f3507dd](https://github.com/jolespin/veba/commit/f3507dd13a42960e3671c9f8a106c9974fbfce21) @@ -217,7 +217,7 @@ ________________________________________________________________
- **Release v1.0.1:** + **Release v1.0.1:** * Fixed the fatal binning-eukaryotic.py error: [7c5addf](https://github.com/jolespin/veba/commit/7c5addf9ed6e8e45502274dd353f20b211838a41) * Fixed the minor file naming in cluster.py: [5803845](https://github.com/jolespin/veba/commit/58038451dac0791899aa7fca3f9d79454cb9ed46) @@ -227,7 +227,7 @@ ________________________________________________________________
- **Release v1.0.0:** + **Release v1.0.0:** * Released with *BMC Bionformatics* publication (doi:10.1186/s12859-022-04973-8). @@ -275,7 +275,7 @@ ________________________________________________________________
- **Daily Change Log:** + **Daily Change Log:** * [2023.9.11] - Added presets for `MEGAHIT` using the `--megahit_preset` option. * [2023.9.11] - The change for using `--mash_db` with `GTDB-Tk` violated the assumption that all prokaryotic classifications had a `msa_percent` field which caused the cluster-level taxonomy to fail. `compile_prokaryotic_genome_cluster_classification_scores_table.py` fixes this by uses `fastani_ani` as the weight when genomes were classified using ANI and `msa_percent` for everything else. Initial error caused unclassified prokaryotic for all cluster-level classifications. @@ -286,7 +286,7 @@ ________________________________________________________________ * [2023.8.30] - `pandas.errors.InvalidIndexError: Reindexing only valid with uniquely valued Index objects` in `biosynthetic.py` when `Diamond` finds multiple regions in one hit that matches. Added `--sort_by` and `--ascending` to `concatenate_dataframes.py` along with automatic detection and removal of duplicate indices. Also added `--sort_by bitscore` in `biosynthetic.py`. * [2023.8.28] - Added core pangenome and singleton hits to clustering output * [2023.8.25] - Updated `--megahit_memory` default from 0.9 to 0.99 -* [2023.8.16] - Fixed error in `genomad_taxonomy_wrapper.py` where `viral_taxonomy.tsv` should have been `taxonomy.tsv`. #! NEED TO TEST +* [2023.8.16] - Fixed error in `genomad_taxonomy_wrapper.py` where `viral_taxonomy.tsv` should have been `taxonomy.tsv`. * [2023.7.26] - Fixed minor error in `assembly.py` that was preventing users from using `SPAdes` programs that were not `spades.py`, `metaspades.py`, or `rnaspades.py` that was the result of using an incorrect string formatting. * [2023.7.25] - Updated `bowtie2` in preprocess, assembly, and mapping modules. Updated `fastp` and `fastq_preprocessor` in preprocess module. * [2023.7.7] - Added `compile_gff.py` to merge CDS, rRNA, and tRNA GFF files. Used in `binning-prokaryotic.py` and `binning-viral.py`. `binning-eukaryotic.py` uses the source of this in the backend of `filter_busco_results.py`. Includes GC content for contigs and various tags. diff --git a/src/scripts/.DS_Store b/src/scripts/.DS_Store deleted file mode 100644 index d8bf018fae65eb0ca722d1f58fda56dad86952b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMeQXp(6rZFkz|EfnY?hl>?#X@NqaP(i$2i%<#{xzaB_&b`}(ZrIyBZuhPr zL93vDs4<#o)Tl&*CjKKLe#Dq))M!G~Xkv(pCdQA%fAkMc_{*sHW@iuXN}Cu>l;BRX z`*z;EH#_s2-b&?+v&dx$`YK#0Hv1lazd;>Bb%kfU7sTL<3!6aZy8 zs{Mk`EFEAzY9OP59OV*QumE>O$z35xF@U?HUJdM`fgI(MyAueK4+!22L52eU+p&Mu zHzyF~GQ5Wfga}-U01Fohvvq@HNVYhCPmZ{mq~#>B{q*s6hnv(xZOfaR)D?+Yrvt&3 zK&FhD-hC6zO?OjP635Ey$DO>jcUe5;q%(epWxq_C$S&=4b_O8PcR#uPx8wBv_K9uv zzigWsE_#1?GaJUZED7b)G9a_p^H zgSGdk94nW$-Gp47(yUa@(mbQxvf}1#hCARH2R!yP{?lwqs%RMaodZ&RIZ}~xum&2XL<+uI}~-k zNaa^|>NvnHj?Q}2{T4-07f`x`={vFp-nm**8Wy30*6ZRtf?+OMDvGk(yAOk!yGku9 zs3_~(EiGwSfogRds=E909N$2B!#awiZyLa8OXGZ8u2y0ll**@fvv+UST(2i%*cV0> z+d%1%JFI1mo)O$t3Lf2*`fG-I=CWQlJtxZnQI#G_2U%zBx{m$GvukJFvQ1HNg-knd z;vR+z&bKo8Q%Y%FbP-=1A5^g}>PH}|C{f4~{4KPR?DTicBjkSa6gfs-Ag__P$Z7I1 zIZM7IUy-lL_v9yXj{FMcPyv-dp&I7ERWKLkK_jeymCyi7Y@Q*a0HIRJ@5cL3Xj3#@FYA3C*Vam38&y4_y9hHkKl9o2EK*w;2iu8f53S` z6s8LcgzJQQp+Q(AvREe^@aW8#Y^O-4N})-5ie&a zvk0l4KBH#lyt?|u%Ujn(FOZ1SuE4Zn2K%Hfp}J36f1Ss&4P!}}Bh8I$M^+mLF2)d# zsX~Owm5jfX5ly*TnjeuXkoUZLt+X&APeRV~YD1(kA`2L5yt*XPEXidUNTToC5^0oV zpuDn5YLn!03@Kh+E3K1c!lInl+anzjnFYw-pz2HH4e~Zp^$b$=2l6BNnVd&@PK8>e z=0d24255rCNYB;K25Vp~tcMM-5jG)Nd!Y~dVGxEO1qM=e7);252RX>Y9dIWS_9)zq zbbSyWf`{P|I0jF{GjJT9^@;m3yfTK0n@Xt&ZnHyI{|cRbIIR$Y5CMh& zly}BESt3|S634Vzd`Tw6hJP~97 From 55ae7dc2ac29e65a3c46c8e1678f0b0b094dcc2e Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Mon, 16 Oct 2023 18:11:44 -0700 Subject: [PATCH 15/16] v1.3.0b (pre-testing) --- CHANGELOG.md | 34 +- VERSION | 2 +- .../environments/VEBA-biosynthetic_env.yml | 12 +- install/environments/VEBA-phylogeny_env.yml | 104 +- install/environments/VEBA-profile_env.yml | 228 +++ src/README.md | 18 +- src/amplicon.py | 4 +- src/annotate.py | 5 +- src/assembly.py | 4 +- src/binning-eukaryotic.py | 4 +- src/binning-prokaryotic.py | 18 +- src/binning-viral.py | 4 +- src/biosynthetic.py | 349 +++- src/classify-eukaryotic.py | 4 +- src/classify-prokaryotic.py | 4 +- src/classify-viral.py | 4 +- src/cluster.py | 24 +- src/coverage.py | 22 +- src/devel/assembly-long.py | 1083 ++++++++++++ src/devel/assembly-sequential.py | 1092 ++++++++++++ src/devel/index.py | 594 +++++++ src/index.py | 4 +- src/mapping.py | 4 +- src/phylogeny.py | 40 +- src/preprocess.py | 4 +- src/profile-pathway.py | 643 +++++++ src/scripts/antismash_genbanks_to_table.py | 321 +++- src/scripts/bgc_novelty_scorer.py | 28 +- src/scripts/compile_core_pangenome_table.py | 78 + ...custom_humann_database_from_annotations.py | 119 ++ src/scripts/compile_genomes_table.py | 52 +- src/scripts/compile_krona.py | 10 +- ...ome_cluster_classification_scores_table.py | 5 +- ...ompile_protein_cluster_prevalence_table.py | 13 +- src/scripts/devel/compile_veba_synopsis.py | 42 + src/scripts/devel/plot_reads_preprocessing.py | 399 +++++ .../prokaryotic_gene_modeling_wrapper.py | 1474 +++++++++++++++++ src/scripts/get_longest_isoform_from_gff.py | 117 ++ src/scripts/global_clustering.py | 41 +- src/scripts/local_clustering.py | 36 +- src/scripts/marker_gene_clustering.py | 412 +++++ src/scripts/merge_annotations.py | 11 +- .../merge_genome_quality_assessments.py | 154 ++ src/scripts/merge_taxonomy_classifications.py | 16 +- src/scripts/mmseqs2_wrapper.py | 17 +- .../prokaryotic_gene_modeling_wrapper.py | 1410 ++++++++++++++++ 46 files changed, 8819 insertions(+), 244 deletions(-) create mode 100644 install/environments/VEBA-profile_env.yml create mode 100755 src/devel/assembly-long.py create mode 100755 src/devel/assembly-sequential.py create mode 100644 src/devel/index.py create mode 100755 src/profile-pathway.py create mode 100755 src/scripts/compile_core_pangenome_table.py create mode 100755 src/scripts/compile_custom_humann_database_from_annotations.py create mode 100755 src/scripts/devel/compile_veba_synopsis.py create mode 100644 src/scripts/devel/plot_reads_preprocessing.py create mode 100755 src/scripts/devel/prokaryotic_gene_modeling_wrapper.py create mode 100755 src/scripts/get_longest_isoform_from_gff.py create mode 100755 src/scripts/marker_gene_clustering.py create mode 100755 src/scripts/merge_genome_quality_assessments.py create mode 100644 src/scripts/prokaryotic_gene_modeling_wrapper.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 380d60b..45450e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -237,12 +237,27 @@ ________________________________________________________________ #### Path to `v2.0.0`: +**Check:** + +* Start/end positions on MetaEuk gene ID might be off. + **Critical:** +* Dereplcate CDS sequences in GFF from MetaEuk for antiSMASH to work for eukaryotic genomes +* Component clustering needs to be with respect to BGC not genome -**Definitely:** -* Script that gets marker genes from clustering results +Error with `amplicon.py` that works when run manually... +``` +There was a problem importing veba_output/misc/reads_table.tsv: + + Missing one or more files for SingleLanePerSamplePairedEndFastqDirFmt: '.+_.+_L[0-9][0-9][0-9]_R[12]_001\\.fastq\\.gz' +``` + +**Definitely:** +* Modify behavior of `annotate.py` to allow for skipping Pfam and/or KOFAM since they take a long time. +* Add `compile_custom_humann_database.py` +* Add coding density to GFF files * Split `download_databases.sh` into `download_databases.sh` (low memory, high threads) and `configure_databases.sh` (high memory, low-to-mid threads). Use `aria2` in parallel instead of `wget`. * `NextFlow` support * Consistent usage of the following terms: 1) dataframe vs. table; 2) protein-cluster vs. orthogroup. @@ -257,6 +272,7 @@ ________________________________________________________________ **Probably (Yes)?:** +* Convert HMMs to MMSEQS2 (https://github.com/soedinglab/MMseqs2/wiki#how-to-create-a-target-profile-database-from-pfam)? * Run `cmsearch` before `tRNAscan-SE` * DN/DS from pangeome analysis * Add [iPHoP](https://bitbucket.org/srouxjgi/iphop/src/main/) to `binning-viral.py`. @@ -277,7 +293,19 @@ ________________________________________________________________
**Daily Change Log:** -* [2023.9.11] - Added presets for `MEGAHIT` using the `--megahit_preset` option. +* [2023.10.16] - Added `profile-pathway.py` module and `VEBA-profile_env` environments which is a wrapper around `HUMAnN` for the custom database created from `annotate.py` and `compile_custom_humann_database_from_annotations.py` +* [2023.10.16] - Added `GenoPype version` to log output +* [2023.10.16] - Added `merge_genome_quality.py` which combines `CheckV`, `CheckM2`, and `BUSCO` results. +* [2023.10.11] - Added `compile_custom_humann_database_from_annotations.py` which compiles a `HUMAnN` protein database table from the output of `annotate.py` and taxonomy classifications. +* [2023.10.11] - Added functionality to `merge_taxonomy_classifications.py` to allow for `--no_domain` and `--no_header` which will serve as input to `compile_custom_humann_database_from_annotations.py` +* +* [2023.10.5] - Added `marker_gene_clustering.py` script which gets core marker genes unique to each SLC (i.e., pangenome). `average_number_of_copies_per_genome` to protein clusters. +* [2023.10.5] - Added `--minimum_core_prevalence` in `global_clustering.py`, `local_clustering.py`, and `cluster.py` which indicates prevalence ratio of protein clusters in a SLC will be considered core. Also remove `--no_singletons` from `cluster.py` to avoid complications with marker genes. Relabeled `--input` to `--genomes_table` in clustering scripts/module. +* [2023.9.21] - Added a check in `coverage.py` to see if the `mapped.sorted.bam` files are created, if they are then skip them. Not yet implemented for GNU parallel option. +* [2023.9.15] - Changed default representative sequence format from table to fasta for `mmseqs2_wrapper.py`. +* [2023.9.12] - Added `--nucleotide_fasta_output` to `antismash_genbank_to_table.py` which outputs the actual BGC DNA sequence. Changed `--fasta_output` to `--protein_fasta_output` and added output to `biosynthetic.py`. Changed BGC component identifiers to `[bgc_id]_[position_in_bgc]|[start]:[end]([strand])` to match with `MetaEuk` identifiers. Changed `bgc_type` to `protocluster_type`. `biosynthetic.py` now supports GFF files from `MetaEuk` (exon and gene features not supported by `antiSMASH`). Fixed error related to `antiSMASH` adding CDS (i.e., `allorf_[start]_[end]`) that are not in GFF so `antismash_genbank_to_table.py` failed in those cases. +* [2023.9.12] - Added `ete3` to `VEBA-phylogeny_env.yml` and automatically renders trees to PDF. #! Need to test +* [2023.9.11] - Added presets for `MEGAHIT` using the `--megahit_preset` option. #! Need to test * [2023.9.11] - The change for using `--mash_db` with `GTDB-Tk` violated the assumption that all prokaryotic classifications had a `msa_percent` field which caused the cluster-level taxonomy to fail. `compile_prokaryotic_genome_cluster_classification_scores_table.py` fixes this by uses `fastani_ani` as the weight when genomes were classified using ANI and `msa_percent` for everything else. Initial error caused unclassified prokaryotic for all cluster-level classifications. * [2023.9.8] - Fixed small error where empty gff files with an asterisk in the name were created for samples that didn't have any prokaryotic MAGs. * [2023.9.8] - Fixed critical error where descriptions in header were not being removed in ``eukaryota.scaffolds.list` and did not remove eukaryotic scaffolds in `seqkit grep` so `DAS_Tool` output eukaryotic MAGs in `identifier_mapping.tsv` and `__DASTool_scaffolds2bin.no_eukaryota.txt` diff --git a/VERSION b/VERSION index 51301ca..bf44aa0 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -1.2.1 +1.3.0b VDB_v5.1 diff --git a/install/environments/VEBA-biosynthetic_env.yml b/install/environments/VEBA-biosynthetic_env.yml index 88b20a7..f4640bd 100644 --- a/install/environments/VEBA-biosynthetic_env.yml +++ b/install/environments/VEBA-biosynthetic_env.yml @@ -1,4 +1,4 @@ -name: VEBA-biosynthetic_env__v2023.6.23 +name: VEBA-biosynthetic_env__v2023.9.15 channels: - conda-forge - bioconda @@ -10,6 +10,7 @@ dependencies: - alsa-lib=1.2.6.1=h7f98852_0 - antismash=6.1.1=pyhdfd78af_0 - appdirs=1.4.4=pyh9f0ad1d_0 + - aria2=1.36.0=h1e4e653_3 - arrow-cpp=11.0.0=ha770c72_13_cpu - attrs=22.2.0=pyh71513ae_0 - aws-c-auth=0.6.26=hdca2abe_0 @@ -38,9 +39,9 @@ dependencies: - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - c-ares=1.18.1=h7f98852_0 - - ca-certificates=2023.5.7=hbcca054_0 + - ca-certificates=2023.7.22=hbcca054_0 - cairo=1.16.0=h18b612c_1001 - - certifi=2023.5.7=pyhd8ed1ab_0 + - certifi=2023.7.22=pyhd8ed1ab_0 - cffi=1.15.1=py38h4a40e3a_3 - charset-normalizer=2.1.1=pyhd8ed1ab_0 - colorama=0.4.6=pyhd8ed1ab_0 @@ -62,6 +63,7 @@ dependencies: - fonts-conda-forge=1=0 - fonttools=4.38.0=py38h0a891b7_1 - freetype=2.12.1=hca18f0e_1 + - gawk=5.1.0=h7f98852_0 - genopype=2023.5.15=py_0 - gettext=0.21.1=h27087fc_0 - gflags=2.2.2=he1b5a44_1004 @@ -143,13 +145,15 @@ dependencies: - markupsafe=2.1.1=py38h0a891b7_2 - matplotlib-base=3.6.2=py38hb021067_0 - meme=4.11.2=py38pl5321h88f601c_8 + - mmseqs2=14.7e284=pl5321h6a68c12_2 - munkres=1.1.4=pyh9f0ad1d_0 - muscle=3.8.1551=h7d875b9_6 - ncurses=6.3=h27087fc_1 + - networkx=3.1=pyhd8ed1ab_0 - numpy=1.23.5=py38h7042d01_0 - openjdk=11.0.15=hc6918da_0 - openjpeg=2.5.0=hfec8fc6_2 - - openssl=3.1.0=hd590300_3 + - openssl=3.1.2=hd590300_0 - orc=1.8.3=hfdbbad2_0 - packaging=22.0=pyhd8ed1ab_0 - pandas=1.5.2=py38h8f669ce_0 diff --git a/install/environments/VEBA-phylogeny_env.yml b/install/environments/VEBA-phylogeny_env.yml index 70f27b6..92c2f11 100644 --- a/install/environments/VEBA-phylogeny_env.yml +++ b/install/environments/VEBA-phylogeny_env.yml @@ -1,4 +1,4 @@ -name: VEBA-phylogeny_env__v2023.5.15 +name: VEBA-phylogeny_env__v2023.9.12 channels: - conda-forge - bioconda @@ -7,49 +7,116 @@ channels: dependencies: - _libgcc_mutex=0.1=conda_forge - _openmp_mutex=4.5=2_gnu + - alsa-lib=1.2.8=h166bdaf_0 + - attr=2.5.1=h166bdaf_1 - biopython=1.80=py311hd4cff14_0 - brotlipy=0.7.0=py311hd4cff14_1005 - bz2file=0.98=py_0 - bzip2=1.0.8=h7f98852_4 - - ca-certificates=2022.12.7=ha878542_0 - - certifi=2022.12.7=pyhd8ed1ab_0 + - ca-certificates=2023.7.22=hbcca054_0 + - cairo=1.16.0=h35add3b_1015 + - certifi=2023.7.22=pyhd8ed1ab_0 - cffi=1.15.1=py311h409f033_3 - charset-normalizer=2.1.1=pyhd8ed1ab_0 - clipkit=1.3.0=pyhdfd78af_0 - colorama=0.4.6=pyhd8ed1ab_0 - coreutils=9.3=h0b41bf4_0 - cryptography=39.0.0=py311h9b4c7bb_0 + - dbus=1.13.6=h5008d03_3 + - ete3=3.1.3=pyhd8ed1ab_0 + - expat=2.5.0=hcb278e6_1 - fasttree=2.1.11=hec16e2b_1 + - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 + - font-ttf-inconsolata=3.000=h77eed37_0 + - font-ttf-source-code-pro=2.038=h77eed37_0 + - font-ttf-ubuntu=0.83=hab24e00_0 + - fontconfig=2.14.2=h14ed4e7_0 + - fonts-conda-ecosystem=1=0 + - fonts-conda-forge=1=0 + - freetype=2.12.1=hca18f0e_1 - genopype=2023.5.15=py_0 + - gettext=0.21.1=h27087fc_0 + - glib=2.78.0=hfc55251_0 + - glib-tools=2.78.0=hfc55251_0 + - graphite2=1.3.13=h58526e2_1001 + - gst-plugins-base=1.22.0=h4243ec0_2 + - gstreamer=1.22.0=h25f0c4b_2 + - harfbuzz=6.0.0=h3ff4399_1 - hmmer=3.3.2=h87f3376_2 + - icu=72.1=hcb278e6_0 - idna=3.4=pyhd8ed1ab_0 - iqtree=2.2.0.3=hb97b32f_1 + - keyutils=1.6.1=h166bdaf_0 + - krb5=1.20.1=h81ceb04_0 + - lame=3.100=h166bdaf_1003 - ld_impl_linux-64=2.39=hcc3a1bd_1 - libblas=3.9.0=16_linux64_openblas + - libcap=2.69=h0f662aa_0 - libcblas=3.9.0=16_linux64_openblas + - libclang=16.0.3=default_h1cdf331_2 + - libclang13=16.0.3=default_h4d60ac6_2 + - libcups=2.3.3=h36d4200_3 + - libedit=3.1.20191231=he28a2e2_2 + - libevent=2.1.10=h28343ad_4 + - libexpat=2.5.0=hcb278e6_1 - libffi=3.4.2=h7f98852_5 + - libflac=1.4.3=h59595ed_0 - libgcc-ng=12.2.0=h65d4601_19 + - libgcrypt=1.10.1=h166bdaf_0 - libgfortran-ng=12.2.0=h69a702a_19 - libgfortran5=12.2.0=h337968e_19 + - libglib=2.78.0=hebfc3b9_0 - libgomp=12.2.0=h65d4601_19 + - libgpg-error=1.47=h71f35ed_0 + - libiconv=1.17=h166bdaf_0 + - libjpeg-turbo=2.1.5.1=h0b41bf4_0 - liblapack=3.9.0=16_linux64_openblas + - libllvm16=16.0.3=hbf9e925_1 - libnsl=2.0.0=h7f98852_0 + - libogg=1.3.4=h7f98852_1 - libopenblas=0.3.21=pthreads_h78a6416_3 + - libopus=1.3.1=h7f98852_1 + - libpng=1.6.39=h753d276_0 + - libpq=15.3=hbcd7760_1 + - libsndfile=1.2.2=hbc2eb40_0 - libsqlite=3.40.0=h753d276_0 - libstdcxx-ng=12.2.0=h46fd767_19 + - libsystemd0=254=h3516f8a_0 - libuuid=2.32.1=h7f98852_1000 + - libvorbis=1.3.7=h9c3ff4c_0 + - libxcb=1.13=h7f98852_1004 + - libxkbcommon=1.5.0=h79f4944_1 + - libxml2=2.10.4=hfdac1af_0 + - libxslt=1.1.37=h873f0b0_0 - libzlib=1.2.13=h166bdaf_4 + - lxml=4.9.2=py311h14a6109_0 + - lz4-c=1.9.4=hcb278e6_0 + - mpg123=1.31.3=hcb278e6_0 - muscle=5.1=h9f5acd7_1 + - mysql-common=8.0.33=hf1915f5_4 + - mysql-libs=8.0.33=hca2cd23_4 - ncurses=6.3=h27087fc_1 + - nspr=4.35=h27087fc_0 + - nss=3.89=he45b914_0 - numpy=1.24.1=py311hbde0eaa_0 - - openssl=3.0.7=h0b41bf4_1 + - openssl=3.1.2=hd590300_0 + - packaging=23.1=pyhd8ed1ab_0 - pandas=1.5.2=py311h8b32b4d_0 - parallel=20170422=pl5.22.0_0 - pathlib2=2.3.7.post1=py311h38be061_2 + - pcre2=10.40=hc3806b6_0 - perl=5.22.0.1=0 - pip=22.3.1=pyhd8ed1ab_0 + - pixman=0.40.0=h36c2ea0_0 + - platformdirs=3.10.0=pyhd8ed1ab_0 + - ply=3.11=py_1 + - pooch=1.7.0=pyha770c72_3 + - pthread-stubs=0.4=h36c2ea0_1001 + - pulseaudio-client=16.1=hb77b528_5 - pycparser=2.21=pyhd8ed1ab_0 - pyopenssl=23.0.0=pyhd8ed1ab_0 + - pyqt=5.15.9=py311hf0fb5b6_4 + - pyqt5-sip=12.12.2=py311hb755f60_4 - pysocks=1.7.1=pyha2e5f31_6 - python=3.11.0=ha86cf86_0_cpython - python-dateutil=2.8.2=pyhd8ed1ab_0 @@ -57,17 +124,44 @@ dependencies: - python_abi=3.11=3_cp311 - pytz=2022.7=pyhd8ed1ab_0 - pytz-deprecation-shim=0.1.0.post0=py311h38be061_3 + - qt-main=5.15.8=h5c52f38_10 - readline=8.1.2=h0f457ee_0 - requests=2.28.1=pyhd8ed1ab_1 - scandir=1.10.0=py311hd4cff14_6 + - scipy=1.10.1=py311h64a7726_3 - seqkit=2.3.1=h9ee0642_0 - setuptools=65.6.3=pyhd8ed1ab_0 + - sip=6.7.11=py311hb755f60_0 - six=1.16.0=pyh6c4a22f_0 - soothsayer_utils=2022.6.24=py_0 - tk=8.6.12=h27826a3_0 + - toml=0.10.2=pyhd8ed1ab_0 + - tomli=2.0.1=pyhd8ed1ab_0 - tqdm=4.64.1=pyhd8ed1ab_0 + - typing-extensions=4.7.1=hd8ed1ab_0 + - typing_extensions=4.7.1=pyha770c72_0 - tzdata=2022g=h191b570_0 - tzlocal=4.2=py311h38be061_2 - urllib3=1.26.13=pyhd8ed1ab_0 - wheel=0.38.4=pyhd8ed1ab_0 - - xz=5.2.6=h166bdaf_0 \ No newline at end of file + - xcb-util=0.4.0=h516909a_0 + - xcb-util-image=0.4.0=h166bdaf_0 + - xcb-util-keysyms=0.4.0=h516909a_0 + - xcb-util-renderutil=0.3.9=h166bdaf_0 + - xcb-util-wm=0.4.1=h516909a_0 + - xkeyboard-config=2.38=h0b41bf4_0 + - xorg-kbproto=1.0.7=h7f98852_1002 + - xorg-libice=1.0.10=h7f98852_0 + - xorg-libsm=1.2.3=hd9c2040_1000 + - xorg-libx11=1.8.4=h0b41bf4_0 + - xorg-libxau=1.0.11=hd590300_0 + - xorg-libxdmcp=1.1.3=h7f98852_0 + - xorg-libxext=1.3.4=h0b41bf4_2 + - xorg-libxrender=0.9.10=h7f98852_1003 + - xorg-renderproto=0.11.1=h7f98852_1002 + - xorg-xextproto=7.3.0=h0b41bf4_1003 + - xorg-xf86vidmodeproto=2.3.1=h7f98852_1002 + - xorg-xproto=7.0.31=h7f98852_1007 + - xz=5.2.6=h166bdaf_0 + - zlib=1.2.13=h166bdaf_4 + - zstd=1.5.5=hfc55251_0 \ No newline at end of file diff --git a/install/environments/VEBA-profile_env.yml b/install/environments/VEBA-profile_env.yml new file mode 100644 index 0000000..bccbda2 --- /dev/null +++ b/install/environments/VEBA-profile_env.yml @@ -0,0 +1,228 @@ +name: VEBA-profile_env__v2023.10.16 +channels: + - conda-forge + - bioconda + - jolespin + - defaults +dependencies: + - _libgcc_mutex=0.1=conda_forge + - _openmp_mutex=4.5=2_gnu + - alsa-lib=1.2.8=h166bdaf_0 + - bbmap=39.01=h92535d8_1 + - bcbio-gff=0.7.0=pyh7cba7a3_0 + - biom-format=2.1.15=py310h2372a71_1 + - biopython=1.81=py310h1fa729e_0 + - blast=2.14.1=pl5321h6f7f691_0 + - bowtie2=2.5.1=py310h8d7afc0_0 + - brotli=1.1.0=hd590300_1 + - brotli-bin=1.1.0=hd590300_1 + - brotli-python=1.1.0=py310hc6cd4ac_1 + - bx-python=0.10.0=py310h551a815_0 + - bz2file=0.98=py_0 + - bzip2=1.0.8=h7f98852_4 + - c-ares=1.20.1=hd590300_0 + - ca-certificates=2023.7.22=hbcca054_0 + - cached-property=1.5.2=hd8ed1ab_1 + - cached_property=1.5.2=pyha770c72_1 + - cairo=1.16.0=hb05425b_5 + - capnproto=0.9.1=ha19adfc_4 + - certifi=2023.7.22=pyhd8ed1ab_0 + - charset-normalizer=3.3.0=pyhd8ed1ab_0 + - click=8.1.7=unix_pyh707e725_0 + - cmseq=1.0.4=pyhb7b1952_0 + - colorama=0.4.6=pyhd8ed1ab_0 + - contourpy=1.1.1=py310hd41b1e2_1 + - curl=8.4.0=hca28451_0 + - cycler=0.12.1=pyhd8ed1ab_0 + - dendropy=4.6.1=pyhdfd78af_0 + - diamond=2.1.8=h43eeafb_0 + - entrez-direct=16.2=he881be0_1 + - expat=2.5.0=hcb278e6_1 + - fasttree=2.1.11=h031d066_2 + - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 + - font-ttf-inconsolata=3.000=h77eed37_0 + - font-ttf-source-code-pro=2.038=h77eed37_0 + - font-ttf-ubuntu=0.83=hab24e00_0 + - fontconfig=2.14.2=h14ed4e7_0 + - fonts-conda-ecosystem=1=0 + - fonts-conda-forge=1=0 + - fonttools=4.43.1=py310h2372a71_0 + - freetype=2.12.1=h267a509_2 + - genopype=2023.5.15=py_0 + - gettext=0.21.1=h27087fc_0 + - giflib=5.2.1=h0b41bf4_3 + - glib=2.78.0=hfc55251_0 + - glib-tools=2.78.0=hfc55251_0 + - glpk=4.57=0 + - graphite2=1.3.13=h58526e2_1001 + - gsl=2.7=he838d99_0 + - h5py=3.10.0=nompi_py310ha2ad45a_100 + - harfbuzz=6.0.0=h8e241bc_0 + - hdf5=1.14.2=nompi_h4f84152_100 + - htslib=1.18=h81da01d_0 + - humann=3.8=pyh7cba7a3_0 + - icu=70.1=h27087fc_0 + - idna=3.4=pyhd8ed1ab_0 + - iqtree=2.2.5=h21ec9f0_0 + - keyutils=1.6.1=h166bdaf_0 + - kiwisolver=1.4.5=py310hd41b1e2_1 + - krb5=1.21.2=h659d440_0 + - lcms2=2.15=h7f713cb_2 + - ld_impl_linux-64=2.40=h41732ed_0 + - lerc=4.0.0=h27087fc_0 + - libaec=1.1.2=h59595ed_1 + - libblas=3.9.0=18_linux64_openblas + - libbrotlicommon=1.1.0=hd590300_1 + - libbrotlidec=1.1.0=hd590300_1 + - libbrotlienc=1.1.0=hd590300_1 + - libcblas=3.9.0=18_linux64_openblas + - libcups=2.3.3=h4637d8d_4 + - libcurl=8.4.0=hca28451_0 + - libdeflate=1.18=h0b41bf4_0 + - libedit=3.1.20191231=he28a2e2_2 + - libev=4.33=h516909a_1 + - libexpat=2.5.0=hcb278e6_1 + - libffi=3.4.2=h7f98852_5 + - libgcc=7.2.0=h69d50b8_2 + - libgcc-ng=13.2.0=h807b86a_2 + - libgfortran-ng=13.2.0=h69a702a_2 + - libgfortran5=13.2.0=ha4646dd_2 + - libglib=2.78.0=hebfc3b9_0 + - libgomp=13.2.0=h807b86a_2 + - libhwloc=2.8.0=h32351e8_1 + - libiconv=1.17=h166bdaf_0 + - libidn2=2.3.4=h166bdaf_0 + - libjpeg-turbo=2.1.5.1=hd590300_1 + - liblapack=3.9.0=18_linux64_openblas + - libnghttp2=1.52.0=h61bc06f_0 + - libnsl=2.0.0=hd590300_1 + - libopenblas=0.3.24=pthreads_h413a1c8_0 + - libpng=1.6.39=h753d276_0 + - libsqlite=3.43.2=h2797004_0 + - libssh2=1.11.0=h0841786_0 + - libstdcxx-ng=13.2.0=h7e041cc_2 + - libtiff=4.6.0=h8b53f26_0 + - libunistring=0.9.10=h7f98852_0 + - libuuid=2.38.1=h0b41bf4_0 + - libwebp-base=1.3.2=hd590300_0 + - libxcb=1.15=h0b41bf4_0 + - libxml2=2.9.14=h22db469_4 + - libzlib=1.2.13=hd590300_5 + - mafft=7.520=h031d066_2 + - mash=2.3=ha9a2dd8_3 + - matplotlib-base=3.8.0=py310h62c0568_2 + - metaphlan=4.0.6=pyhca03a8a_0 + - munkres=1.0.7=py_1 + - muscle=5.1=h4ac6f70_3 + - ncbi-vdb=3.0.8=hdbdd923_0 + - ncurses=6.4=hcb278e6_0 + - numpy=1.26.0=py310hb13e2d6_0 + - openjdk=17.0.3=h4335b31_6 + - openjpeg=2.5.0=h488ebb8_3 + - openssl=3.1.3=hd590300_0 + - ossuuid=1.6.2=hf484d3e_1000 + - packaging=23.2=pyhd8ed1ab_0 + - pandas=2.1.1=py310hcc13569_1 + - pathlib2=2.3.7.post1=py310hff52083_3 + - patsy=0.5.3=pyhd8ed1ab_0 + - pbzip2=1.1.13=h1fcc475_2 + - pcre=8.45=h9c3ff4c_0 + - pcre2=10.40=hc3806b6_0 + - perl=5.32.1=4_hd590300_perl5 + - perl-alien-build=2.48=pl5321hec16e2b_0 + - perl-alien-libxml2=0.17=pl5321hec16e2b_0 + - perl-archive-tar=2.40=pl5321hdfd78af_0 + - perl-business-isbn=3.007=pl5321hdfd78af_0 + - perl-business-isbn-data=20210112.006=pl5321hdfd78af_0 + - perl-capture-tiny=0.48=pl5321hdfd78af_2 + - perl-carp=1.38=pl5321hdfd78af_4 + - perl-common-sense=3.75=pl5321hdfd78af_0 + - perl-compress-raw-bzip2=2.201=pl5321h87f3376_1 + - perl-compress-raw-zlib=2.105=pl5321h87f3376_0 + - perl-constant=1.33=pl5321hdfd78af_2 + - perl-data-dumper=2.183=pl5321hec16e2b_1 + - perl-encode=3.19=pl5321hec16e2b_1 + - perl-exporter=5.72=pl5321hdfd78af_2 + - perl-exporter-tiny=1.002002=pl5321hdfd78af_0 + - perl-extutils-makemaker=7.70=pl5321hd8ed1ab_0 + - perl-ffi-checklib=0.28=pl5321hdfd78af_0 + - perl-file-chdir=0.1010=pl5321hdfd78af_3 + - perl-file-path=2.18=pl5321hd8ed1ab_0 + - perl-file-temp=0.2304=pl5321hd8ed1ab_0 + - perl-file-which=1.24=pl5321hd8ed1ab_0 + - perl-importer=0.026=pl5321hdfd78af_0 + - perl-io-compress=2.201=pl5321hdbdd923_2 + - perl-io-zlib=1.14=pl5321hdfd78af_0 + - perl-json=4.10=pl5321hdfd78af_0 + - perl-json-xs=2.34=pl5321h4ac6f70_6 + - perl-list-moreutils=0.430=pl5321hdfd78af_0 + - perl-list-moreutils-xs=0.430=pl5321h031d066_2 + - perl-mime-base64=3.16=pl5321hec16e2b_2 + - perl-parent=0.236=pl5321hdfd78af_2 + - perl-path-tiny=0.122=pl5321hdfd78af_0 + - perl-pathtools=3.75=pl5321hec16e2b_3 + - perl-scalar-list-utils=1.62=pl5321hec16e2b_1 + - perl-scope-guard=0.21=pl5321hdfd78af_3 + - perl-sub-info=0.002=pl5321hdfd78af_1 + - perl-term-table=0.016=pl5321hdfd78af_0 + - perl-test2-suite=0.000145=pl5321hdfd78af_0 + - perl-types-serialiser=1.01=pl5321hdfd78af_0 + - perl-uri=5.12=pl5321hdfd78af_0 + - perl-xml-libxml=2.0207=pl5321h661654b_0 + - perl-xml-namespacesupport=1.12=pl5321hdfd78af_1 + - perl-xml-sax=1.02=pl5321hdfd78af_1 + - perl-xml-sax-base=1.09=pl5321hdfd78af_1 + - phylophlan=3.0.3=pyhdfd78af_0 + - pillow=10.0.1=py310h29da1c1_1 + - pip=23.2.1=pyhd8ed1ab_0 + - pixman=0.42.2=h59595ed_0 + - pthread-stubs=0.4=h36c2ea0_1001 + - pyparsing=3.1.1=pyhd8ed1ab_0 + - pysam=0.21.0=py310h41dec4a_1 + - pysocks=1.7.1=pyha2e5f31_6 + - python=3.10.12=hd12c33a_0_cpython + - python-dateutil=2.8.2=pyhd8ed1ab_0 + - python-tzdata=2023.3=pyhd8ed1ab_0 + - python_abi=3.10=4_cp310 + - pytz=2023.3.post1=pyhd8ed1ab_0 + - raxml=8.2.13=h031d066_0 + - readline=8.2=h8228510_1 + - requests=2.31.0=pyhd8ed1ab_0 + - samtools=1.18=h50ea8bc_1 + - scandir=1.10.0=py310h2372a71_7 + - scipy=1.11.3=py310hb13e2d6_1 + - seaborn=0.13.0=hd8ed1ab_0 + - seaborn-base=0.13.0=pyhd8ed1ab_0 + - seqkit=2.5.1=h9ee0642_0 + - setuptools=68.2.2=pyhd8ed1ab_0 + - six=1.16.0=pyh6c4a22f_0 + - soothsayer_utils=2022.6.24=py_0 + - statsmodels=0.14.0=py310h1f7b6fc_2 + - tbb=2021.7.0=h924138e_1 + - tk=8.6.13=h2797004_0 + - tqdm=4.66.1=pyhd8ed1ab_0 + - trimal=1.4.1=h4ac6f70_8 + - tzdata=2023c=h71feb2d_0 + - tzlocal=5.1=py310hff52083_0 + - unicodedata2=15.1.0=py310h2372a71_0 + - urllib3=2.0.6=pyhd8ed1ab_0 + - wget=1.20.3=ha35d2d1_1 + - wheel=0.41.2=pyhd8ed1ab_0 + - xorg-fixesproto=5.0=h7f98852_1002 + - xorg-inputproto=2.3.2=h7f98852_1002 + - xorg-kbproto=1.0.7=h7f98852_1002 + - xorg-libx11=1.8.7=h8ee46fc_0 + - xorg-libxau=1.0.11=hd590300_0 + - xorg-libxdmcp=1.1.3=h7f98852_0 + - xorg-libxext=1.3.4=h0b41bf4_2 + - xorg-libxfixes=5.0.3=h7f98852_1004 + - xorg-libxi=1.7.10=h7f98852_0 + - xorg-libxrender=0.9.11=hd590300_0 + - xorg-libxtst=1.2.3=h7f98852_1002 + - xorg-recordproto=1.14.2=h7f98852_1002 + - xorg-renderproto=0.11.1=h7f98852_1002 + - xorg-xextproto=7.3.0=h0b41bf4_1003 + - xorg-xproto=7.0.31=h7f98852_1007 + - xz=5.2.6=h166bdaf_0 + - zlib=1.2.13=hd590300_5 + - zstd=1.5.5=hfc55251_0 \ No newline at end of file diff --git a/src/README.md b/src/README.md index 6ad49e8..574149f 100755 --- a/src/README.md +++ b/src/README.md @@ -1313,11 +1313,21 @@ Novelty score threshold arguments: **Output:** -* bgc.components.tsv - All of the BGC components (i.e., genes in BGC) in tabular format organized by genome, contig, region, and gene. -* bgc.synopsis.tsv - All of the BGCs in tabular format organized by genome, contig, region, and gene. -* bgc.type_counts.tsv - Summary of BGCs detected organized by type. Also includes summary of BGCs that are NOT on contig edge. -* fasta/*.faa.gz - BGC components in fasta format +* bgc\_clusters.tsv - BGC to BGC nucleotide cluster +* bgc\_protocluster-types.tsv.gz - Summary of BGCs detected organized by type. Also includes summary of BGCs that are NOT on contig edge. +* bgcs.representative\_sequences.fasta.gz - Full length BGC nucleotide cluster representatives +* component\_clusters.tsv - BGC protein to BGC protein cluster +* components.representative\_sequences.faa.gz - BGC protein cluster representatives +* fasta/[id/_genome].faa/fasta.gz - BGC sequences in protein and nucleotide space * genbanks/[id\_genome]/*.gbk - Genbank formatted antiSMASH results +* homology.tsv.gz - Diamond results for MIBiG and VFDB +* identifier\_mapping.bgcs.tsv.gz - All of the BGCs in tabular format organized by genome, contig, region, and gene. +* identifier\_mapping.components.tsv.gz - All of the BGC components (i.e., genes in BGC) in tabular format organized by genome, contig, region, and gene. +* krona.html - HTML showing Krona plot for number of BGCs per protocluster-type. +* krona.tsv - Data to produce Krona plot +* prevalence\_tables/bgcs.tsv.gz - Genome vs. BGC nucleotide cluster prevalence table +* prevalence\_tables/components.tsv.gz - Genome vs. BGC protein cluster prevalence table +

^__^

diff --git a/src/amplicon.py b/src/amplicon.py index b417a00..c673ee7 100755 --- a/src/amplicon.py +++ b/src/amplicon.py @@ -8,12 +8,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.23" +__version__ = "2023.10.16" # Reads archive def get_reads_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -621,6 +622,7 @@ def main(args=None): # print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/annotate.py b/src/annotate.py index b291bbd..48aff64 100755 --- a/src/annotate.py +++ b/src/annotate.py @@ -7,13 +7,14 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * from soothsayer_utils.soothsayer_utils import assert_acceptable_arguments pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.20" +__version__ = "2023.10.16" def get_diamond_cmd( input_filepaths, output_filepaths, output_directory, directories, opts, program): tmp = os.path.join(directories["tmp"], program) @@ -679,6 +680,7 @@ def main(args=None): # KOFAMSCAN parser_kofamscan = parser.add_argument_group('KOFAMSCAN arguments') + parser_kofamscan.add_argument("--kofamscan_options", type=str, default="", help="Diamond | More options (e.g. --arg 1 ) [Default: '']") # Options @@ -712,6 +714,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("VEBA Database:", opts.veba_database, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) diff --git a/src/assembly.py b/src/assembly.py index c3e1d85..5156eff 100755 --- a/src/assembly.py +++ b/src/assembly.py @@ -7,12 +7,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.9.11" +__version__ = "2023.10.16" # Assembly def get_assembly_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -753,6 +754,7 @@ def main(args=None): print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/binning-eukaryotic.py b/src/binning-eukaryotic.py index 000c3d5..f8cfaf2 100755 --- a/src/binning-eukaryotic.py +++ b/src/binning-eukaryotic.py @@ -8,12 +8,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.7.6" +__version__ = "2023.10.16" # DATABASE_METAEUK="/usr/local/scratch/CORE/jespinoz/db/veba/v1.0/Classify/Eukaryotic/eukaryotic" @@ -1091,6 +1092,7 @@ def main(args=None): print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("VEBA Database:", opts.veba_database, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) diff --git a/src/binning-prokaryotic.py b/src/binning-prokaryotic.py index d98233c..30753e5 100755 --- a/src/binning-prokaryotic.py +++ b/src/binning-prokaryotic.py @@ -7,12 +7,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.9.10" +__version__ = "2023.10.16" # Assembly def get_coverage_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -453,6 +454,20 @@ def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directo # tRNAscan-SE def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + + # os.environ["cmsearch"], + # "-o {}".format(os.path.join(output_directory, "{}.out".format(id_model))), + # "-A {}".format(os.path.join(output_directory, "{}.aln".format(id_model))), + # "--tblout {}".format(os.path.join(output_directory, "{}.tblout".format(id_model))), + # "--cpu {}".format(opts.n_jobs), + # "-g", + # "--notrunc", + # "--mid", + # opts.cmsearch_options, + # filepath, + # input_filepaths[0], + cmd = [ "cat", input_filepaths[0], @@ -1654,6 +1669,7 @@ def main(args=None): print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("VEBA Database:", opts.veba_database, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) diff --git a/src/binning-viral.py b/src/binning-viral.py index 352e9ea..f109b01 100755 --- a/src/binning-viral.py +++ b/src/binning-viral.py @@ -8,12 +8,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.7.10" +__version__ = "2023.10.16" # geNomad def get_genomad_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): @@ -947,6 +948,7 @@ def main(args=None): print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("VEBA Database:", opts.veba_database, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) diff --git a/src/biosynthetic.py b/src/biosynthetic.py index 5553fad..5c1cb77 100755 --- a/src/biosynthetic.py +++ b/src/biosynthetic.py @@ -7,12 +7,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.8.30" +__version__ = "2023.10.16" # antiSMASH def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -21,6 +22,7 @@ def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, dire """ OUTPUT_DIRECTORY=%s INTERMEDIATE_DIRECTORY=%s +TMP=%s mkdir -p ${INTERMEDIATE_DIRECTORY} n=1 while IFS= read -r LINE @@ -42,23 +44,29 @@ def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, dire START_TIME=${SECONDS} + # Extract CDS records (or else antiSMASH will fail) + GENE_MODELS_CDS_ONLY=${TMP}/gene_models.cds.gff + grep "CDS" ${GENE_MODELS} > ${GENE_MODELS_CDS_ONLY} + # Run antiSMASH - %s --allow-long-headers --verbose --skip-zip-file -c %d --output-dir ${INTERMEDIATE_DIRECTORY}/${ID} --html-title ${ID} --taxon %s --minlength %d --databases %s --hmmdetection-strictness %s --logfile ${INTERMEDIATE_DIRECTORY}/${ID}/log.txt --genefinding-gff3 ${GENE_MODELS} ${GENOME} + %s --allow-long-headers --verbose --skip-zip-file -c %d --output-dir ${INTERMEDIATE_DIRECTORY}/${ID} --html-title ${ID} --taxon %s --minlength %d --databases %s --hmmdetection-strictness %s --logfile ${INTERMEDIATE_DIRECTORY}/${ID}/log.txt --genefinding-gff3 ${GENE_MODELS_CDS_ONLY} ${GENOME} || (echo "antiSMASH for ${ID} failed" && exit 1) + rm ${GENE_MODELS_CDS_ONLY} # Genbanks to table - %s -i ${INTERMEDIATE_DIRECTORY}/${ID} -o ${INTERMEDIATE_DIRECTORY}/${ID}/antismash_components.tsv.gz -s ${INTERMEDIATE_DIRECTORY}/${ID}/synopsis.tsv.gz -t ${INTERMEDIATE_DIRECTORY}/${ID}/type_counts.tsv.gz --fasta_output ${INTERMEDIATE_DIRECTORY}/${ID}/bgc.components.faa.gz + %s -i ${INTERMEDIATE_DIRECTORY}/${ID} -o ${INTERMEDIATE_DIRECTORY}/${ID}/veba_formatted_output # Compile table for Krona graph - %s -i ${INTERMEDIATE_DIRECTORY}/${ID}/type_counts.tsv.gz -m biosynthetic-local -o ${INTERMEDIATE_DIRECTORY}/${ID}/krona.tsv + %s -i ${INTERMEDIATE_DIRECTORY}/${ID}/veba_formatted_output/bgc_protocluster-types.tsv.gz -m biosynthetic-local -o ${INTERMEDIATE_DIRECTORY}/${ID}/veba_formatted_output/krona.tsv # Create Krona graph - %s -o ${INTERMEDIATE_DIRECTORY}/${ID}/krona.html -n ${ID} ${INTERMEDIATE_DIRECTORY}/${ID}/krona.tsv + %s -o ${INTERMEDIATE_DIRECTORY}/${ID}/veba_formatted_output/krona.html -n ${ID} ${INTERMEDIATE_DIRECTORY}/${ID}/veba_formatted_output/krona.tsv - # Symlink proteins + # Symlink sequences FASTA_DIRECTORY=${OUTPUT_DIRECTORY}/fasta/ - SRC_FILES=${INTERMEDIATE_DIRECTORY}/${ID}/bgc.components.faa.gz DST=${FASTA_DIRECTORY}/ + # Proteins + SRC_FILES=${INTERMEDIATE_DIRECTORY}/${ID}/veba_formatted_output/fasta/components.faa.gz for SRC in $(ls ${SRC_FILES}) do if [ -f "${SRC}" ]; then @@ -67,6 +75,16 @@ def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, dire fi done + # DNA + SRC_FILES=${INTERMEDIATE_DIRECTORY}/${ID}/veba_formatted_output/fasta/bgcs.fasta.gz + for SRC in $(ls ${SRC_FILES}) + do + if [ -f "${SRC}" ]; then + SRC=$(realpath --relative-to ${DST} ${SRC}) + ln -sf ${SRC} ${DST}/${ID}.fasta.gz + fi + done + # Symlink genbanks GENBANK_DIRECTORY=${OUTPUT_DIRECTORY}/genbanks/ mkdir -p ${GENBANK_DIRECTORY}/${ID} @@ -98,13 +116,13 @@ def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, dire done < %s # Concatenate tables -%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/antismash_components.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/bgc.components.tsv.gz -%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/type_counts.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/bgc.type_counts.tsv.gz -%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/synopsis.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/bgc.synopsis.tsv.gz +%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/veba_formatted_output/identifier_mapping.components.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/identifier_mapping.components.tsv.gz +%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/veba_formatted_output/bgc_protocluster-types.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/bgc_protocluster-types.tsv.gz +%s -a 0 -e ${INTERMEDIATE_DIRECTORY}/*/veba_formatted_output/identifier_mapping.bgcs.tsv.gz | gzip > ${OUTPUT_DIRECTORY}/identifier_mapping.bgcs.tsv.gz # Krona # Compile table for Krona graph -%s -i ${OUTPUT_DIRECTORY}/bgc.type_counts.tsv.gz -m biosynthetic-global -o ${OUTPUT_DIRECTORY}/krona.tsv +%s -i ${OUTPUT_DIRECTORY}/bgc_protocluster-types.tsv.gz -m biosynthetic-global -o ${OUTPUT_DIRECTORY}/krona.tsv # Create Krona graph %s ${OUTPUT_DIRECTORY}/krona.tsv -o ${OUTPUT_DIRECTORY}/krona.html -n 'antiSMASH' @@ -112,6 +130,7 @@ def get_antismash_cmd( input_filepaths, output_filepaths, output_directory, dire # Args directories["output"], output_directory, + directories["tmp"], # antiSMASH os.environ["antismash"], @@ -167,7 +186,7 @@ def get_diamond_cmd( input_filepaths, output_filepaths, output_directory, direct "&&", "cat", - os.path.join(input_filepaths[0], "*", "bgc.components.faa.gz"), + os.path.join(input_filepaths[0], "*","veba_formatted_output", "fasta", "components.faa.gz"), "|", "gzip -d", ">", @@ -266,6 +285,7 @@ def get_diamond_cmd( input_filepaths, output_filepaths, output_directory, direct "&&", "rm", + "-rf", os.path.join(directories["tmp"], "components.concatenated.faa"), os.path.join(output_directory, "*.no_header.tsv"), @@ -280,7 +300,7 @@ def get_novelty_score_cmd(input_filepaths, output_filepaths, output_directory, d cmd = [ os.environ["bgc_novelty_scorer.py"], "-c {}".format(input_filepaths[0]), - "-s {}".format(input_filepaths[1]), + "-b {}".format(input_filepaths[1]), "-d {}".format(input_filepaths[2]), "-o {}".format(output_filepaths[0]), "--pident {}".format(opts.pident), @@ -290,6 +310,165 @@ def get_novelty_score_cmd(input_filepaths, output_filepaths, output_directory, d ] return cmd +# MMSEQS2 +def get_mmseqs2_protein_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + "cat", + os.path.join(input_filepaths[0],"*","veba_formatted_output", "fasta", "components.faa.gz"), + "|", + "gzip", + "-d", + ">", + os.path.join(directories["tmp"], "components.concatenated.faa"), + + "&&", + + "cat", + os.path.join(directories["tmp"], "components.concatenated.faa"), + "|", + 'grep "^>"', + "|", + "cut -c2-", + "|", + 'cut -f1 -d " "', # Should the original gene names be used or the new component id? + ">", + os.path.join(directories["tmp"], "components.concatenated.faa.list"), + + "&&", + + os.environ["mmseqs2_wrapper.py"], + "--fasta {}".format(os.path.join(directories["tmp"], "components.concatenated.faa")), + "--output_directory {}".format(output_directory), + "--no_singletons" if bool(opts.no_singletons) else "", + "--algorithm {}".format(opts.algorithm), + "--n_jobs {}".format(opts.n_jobs), + "--minimum_identity_threshold {}".format(opts.protein_minimum_identity_threshold), + "--minimum_coverage_threshold {}".format(opts.protein_minimum_coverage_threshold), + "--mmseqs2_options='{}'" if bool(opts.mmseqs2_options) else "", + "--cluster_prefix {}".format(opts.protein_cluster_prefix), + "--cluster_suffix {}".format(opts.protein_cluster_suffix) if bool(opts.protein_cluster_suffix) else "", + "--cluster_prefix_zfill {}".format(opts.protein_cluster_prefix_zfill), + "--basename clusters", + "--identifiers {}".format(os.path.join(directories["tmp"], "components.concatenated.faa.list")), + "--no_sequences_and_header", + "--representative_output_format fasta", + + "&&", + + "DST={}; SRC={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST/component_clusters.tsv".format( + directories["output"], + os.path.join(output_directory, "output/clusters.tsv"), + ), + + "&&", + + "SRC={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST/components.representative_sequences.faa.gz".format( + os.path.join(output_directory, "output/representative_sequences.fasta.gz"), + ), + + "&&", + + "cat", + os.path.join(output_directory, "output/clusters.tsv"), + "|", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", header=None); df.insert(0, -1, df[0].map(lambda x: "_".join(x.split("_")[:-1]))); df.to_csv(sys.stdout, sep="\t", index=None, header=None)'""", + "|", + os.environ["compile_protein_cluster_prevalence_table.py"], + "--dtype bool", + "--rows_name id_bgc", + "-b", + "|", + "gzip", + ">", + os.path.join(directories[("output", "prevalence_tables")], "components.tsv.gz"), + + "&&", + + "rm -rf", + os.path.join(directories["tmp"], "components.*"), + + ] + return cmd + +def get_mmseqs2_nucleotide_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + "cat", + os.path.join(input_filepaths[0],"*","veba_formatted_output", "fasta", "bgcs.fasta.gz"), + "|" + "gzip", + "-d", + ">", + os.path.join(directories["tmp"], "bgcs.concatenated.fasta"), + + "&&", + + "cat", + os.path.join(directories["tmp"], "bgcs.concatenated.fasta"), + "|", + 'grep "^>"', + "|", + "cut -c2-", + "|", + 'cut -f1 -d " "', # Should the original gene names be used or the new component id? + ">", + os.path.join(directories["tmp"], "bgcs.concatenated.fasta.list"), + + "&&", + + os.environ["mmseqs2_wrapper.py"], + "--fasta {}".format(os.path.join(directories["tmp"], "bgcs.concatenated.fasta")), + "--output_directory {}".format(output_directory), + "--no_singletons" if bool(opts.no_singletons) else "", + "--algorithm {}".format(opts.algorithm), + "--n_jobs {}".format(opts.n_jobs), + "--minimum_identity_threshold {}".format(opts.nucleotide_minimum_identity_threshold), + "--minimum_coverage_threshold {}".format(opts.nucleotide_minimum_coverage_threshold), + "--mmseqs2_options='{}'" if bool(opts.mmseqs2_options) else "", + "--cluster_prefix {}".format(opts.nucleotide_cluster_prefix), + "--cluster_suffix {}".format(opts.nucleotide_cluster_suffix) if bool(opts.nucleotide_cluster_suffix) else "", + "--cluster_prefix_zfill {}".format(opts.nucleotide_cluster_prefix_zfill), + "--basename clusters", + "--identifiers {}".format(os.path.join(directories["tmp"], "bgcs.concatenated.fasta.list")), + "--no_sequences_and_header", + "--representative_output_format fasta", + + "&&", + + "DST={}; SRC={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST/bgc_clusters.tsv".format( + directories["output"], + os.path.join(output_directory, "output/clusters.tsv"), + ), + + "&&", + + "SRC={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST/bgcs.representative_sequences.fasta.gz".format( + os.path.join(output_directory, "output/representative_sequences.fasta.gz"), + ), + + "&&", + + "cat", + os.path.join(output_directory, "output/clusters.tsv"), + "|", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", header=None); df.insert(0, -1, df[0].map(lambda x: x.split("|")[0])); df.to_csv(sys.stdout, sep="\t", index=None, header=None)'""", + "|", + os.environ["compile_protein_cluster_prevalence_table.py"], + "--dtype bool", + "--columns_name id_bgc-cluster", + "-b", + "|", + "gzip", + ">", + os.path.join(directories[("output", "prevalence_tables")], "bgcs.tsv.gz"), + + "&&", + + "rm -rf", + os.path.join(directories["tmp"], "bgcs.*"), + + ] + return cmd + # ============ # Run Pipeline # ============ @@ -304,6 +483,8 @@ def add_executables_to_environment(opts): "concatenate_dataframes.py", "bgc_novelty_scorer.py", "compile_krona.py", + "mmseqs2_wrapper.py", + "compile_protein_cluster_prevalence_table.py", } required_executables={ @@ -313,6 +494,8 @@ def add_executables_to_environment(opts): "diamond", # Krona "ktImportText", + # + "mmseqs", } | accessory_scripts @@ -372,10 +555,11 @@ def create_pipeline(opts, directories, f_cmds): input_filepaths = [ opts.input, ] + output_filenames = [ - "bgc.components.tsv.gz", - "bgc.type_counts.tsv.gz", - "bgc.synopsis.tsv.gz", + "identifier_mapping.components.tsv.gz", + "bgc_protocluster-types.tsv.gz", + "identifier_mapping.bgcs.tsv.gz", "krona.html", "fasta/", "genbanks/", @@ -461,12 +645,12 @@ def create_pipeline(opts, directories, f_cmds): # i/o input_filepaths = [ - os.path.join(directories["output"], "bgc.components.tsv.gz"), - os.path.join(directories["output"], "bgc.synopsis.tsv.gz"), + os.path.join(directories["output"], "identifier_mapping.components.tsv.gz"), + os.path.join(directories["output"], "identifier_mapping.bgcs.tsv.gz"), os.path.join(directories["output"], "homology.tsv.gz"), ] - output_filenames = ["bgc.synopsis.tsv.gz"] # Overwriting + output_filenames = ["identifier_mapping.bgcs.tsv.gz"] # Overwriting output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) @@ -490,6 +674,108 @@ def create_pipeline(opts, directories, f_cmds): validate_outputs=True, ) + # ========== + # MMSEQS2 (Proteins) + # ========== + if not opts.no_protein_clustering: + step += 1 + + program = "mmseqs2_protein" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "MMSEQS2 clustering of biosynthetic gene clusters (proteins)" + + + # i/o + input_filepaths = [ + directories[("intermediate", "1__antismash")], + ] + output_filenames = [ + "output/clusters.tsv", + ] + if opts.representative_output_format == "table": + output_filenames += ["output/representative_sequences.tsv.gz"] + if opts.representative_output_format == "fasta": + output_filenames += ["output/representative_sequences.fasta.gz"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_mmseqs2_protein_cmd(**params) + + + pipeline.add_step( + id=program, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=True, + ) + + # ========== + # MMSEQS2 (Nucleotide) + # ========== + if not opts.no_nucleotide_clustering: + step += 1 + + program = "mmseqs2_nucleotide" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "MMSEQS2 clustering of biosynthetic gene clusters (nucleotides)" + + + # i/o + input_filepaths = [ + directories[("intermediate", "1__antismash")], + ] + output_filenames = [ + "output/clusters.tsv", + ] + if opts.representative_output_format == "table": + output_filenames += ["output/representative_sequences.tsv.gz"] + if opts.representative_output_format == "fasta": + output_filenames += ["output/representative_sequences.fasta.gz"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_mmseqs2_nucleotide_cmd(**params) + + + pipeline.add_step( + id=program, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=True, + ) + # # ============= # # Symlink # # ============= @@ -593,6 +879,29 @@ def main(args=None): parser_noveltyscore.add_argument("--scovhsp", type=float, default=0.0, help = "scovhsp lower bound [float:0 ≤ x < 100] [Default: 0]") parser_noveltyscore.add_argument("--evalue", type=float, default=1e-3, help = "e-value lower bound [float:0 < x < 1] [Default: 1e-3]") + # MMSEQS2 + parser_mmseqs2 = parser.add_argument_group('MMSEQS2 arguments') + parser_mmseqs2.add_argument("-a", "--algorithm", type=str, default="easy-cluster", help="MMSEQS2 | {easy-cluster, easy-linclust} [Default: easy-cluster]") + parser_mmseqs2.add_argument("-f","--representative_output_format", type=str, default="fasta", help = "Format of output for representative sequences: {table, fasta} [Default: fasta]") # Should fasta be the new default? + + + parser_mmseqs2.add_argument("--protein_minimum_identity_threshold", type=float, default=50.0, help="MMSEQS2 | Protein cluster percent identity threshold (Range (0.0, 100.0]) [Default: 50.0]") + parser_mmseqs2.add_argument("--protein_minimum_coverage_threshold", type=float, default=0.8, help="MMSEQS2 | Protein coverage threshold (Range (0.0, 1.0]) [Default: 0.8]") + parser_mmseqs2.add_argument("--protein_cluster_prefix", type=str, default="BGCPC-", help="Protein cluster prefix [Default: 'BGCPC-") + parser_mmseqs2.add_argument("--protein_cluster_suffix", type=str, default="", help="Protein cluster suffix [Default: '") + parser_mmseqs2.add_argument("--protein_cluster_prefix_zfill", type=int, default=0, help="Protein cluster prefix zfill. Use 7 to match identifiers from OrthoFinder. Use 0 to add no zfill. [Default: 0]") #7 + parser_mmseqs2.add_argument("--no_protein_clustering", action="store_true", help="No protein clustering") + + parser_mmseqs2.add_argument("--nucleotide_minimum_identity_threshold", type=float, default=90.0, help="MMSEQS2 | Nucleotide cluster percent identity threshold (Range (0.0, 100.0]) [Default: 90.0]") + parser_mmseqs2.add_argument("--nucleotide_minimum_coverage_threshold", type=float, default=0.8, help="MMSEQS2 | Nucleotide coverage threshold (Range (0.0, 1.0]) [Default: 0.8]") + parser_mmseqs2.add_argument("--nucleotide_cluster_prefix", type=str, default="BGCNC-", help="Nucleotide cluster prefix [Default: 'BGCNC-") + parser_mmseqs2.add_argument("--nucleotide_cluster_suffix", type=str, default="", help="Nucleotide cluster suffix [Default: '") + parser_mmseqs2.add_argument("--nucleotide_cluster_prefix_zfill", type=int, default=0, help="Nucleotide cluster prefix zfill. Use 0 to add no zfill. [Default: 0]") #7 + parser_mmseqs2.add_argument("--no_nucleotide_clustering", action="store_true", help="No nucleotide clustering") + + parser_mmseqs2.add_argument("--no_singletons", action="store_true", help="Exclude singletons (not recommended)") + parser_mmseqs2.add_argument("--mmseqs2_options", type=str, default="", help="MMSEQS2 | More options (e.g. --arg 1 ) [Default: '']") + # Options opts = parser.parse_args() @@ -622,6 +931,7 @@ def main(args=None): os.environ["TMPDIR"] = directories["tmp"] directories[("output", "fasta")] = create_directory(os.path.join(directories["output"], "fasta")) directories[("output", "genbanks")] = create_directory(os.path.join(directories["output"], "genbanks")) + directories[("output", "prevalence_tables")] = create_directory(os.path.join(directories["output"], "prevalence_tables")) # Info @@ -629,6 +939,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/classify-eukaryotic.py b/src/classify-eukaryotic.py index 82fd60f..216c26c 100755 --- a/src/classify-eukaryotic.py +++ b/src/classify-eukaryotic.py @@ -8,12 +8,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.12" +__version__ = "2023.10.16" # Assembly def get_concatenate_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -692,6 +693,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("VEBA Database:", opts.veba_database, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) diff --git a/src/classify-prokaryotic.py b/src/classify-prokaryotic.py index 063cffe..b5abb15 100755 --- a/src/classify-prokaryotic.py +++ b/src/classify-prokaryotic.py @@ -9,12 +9,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.9.11" +__version__ = "2023.10.16" # GTDB-Tk def get_gtdbtk_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -437,6 +438,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("VEBA Database:", opts.veba_database, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) diff --git a/src/classify-viral.py b/src/classify-viral.py index 283be43..ed0da0f 100755 --- a/src/classify-viral.py +++ b/src/classify-viral.py @@ -7,13 +7,14 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.8" +__version__ = "2023.10.16" def get_concatenate_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -353,6 +354,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("VEBA Database:", opts.veba_database, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) diff --git a/src/cluster.py b/src/cluster.py index 51132d8..d098b17 100755 --- a/src/cluster.py +++ b/src/cluster.py @@ -7,12 +7,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.8.30" +__version__ = "2023.10.16" # Global clustering def get_global_clustering_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -22,7 +23,7 @@ def get_global_clustering_cmd( input_filepaths, output_filepaths, output_directo os.environ["global_clustering.py"], "-i {}".format(input_filepaths[0]), "-o {}".format(output_directory), - "--no_singletons" if bool(opts.no_singletons) else "", + # "--no_singletons" if bool(opts.no_singletons) else "", "-p {}".format(opts.n_jobs), "--ani_threshold {}".format(opts.ani_threshold), @@ -37,6 +38,7 @@ def get_global_clustering_cmd( input_filepaths, output_filepaths, output_directo "--protein_cluster_suffix {}".format(opts.protein_cluster_suffix) if bool(opts.protein_cluster_suffix) else "", "--protein_cluster_prefix_zfill {}".format(opts.protein_cluster_prefix_zfill) if bool(opts.protein_cluster_prefix_zfill) else "", "--mmseqs2_options {}".format(opts.mmseqs2_options) if bool(opts.mmseqs2_options) else "", + "--minimum_core_prevalence {}".format(opts.minimum_core_prevalence), "&&", @@ -56,9 +58,8 @@ def get_local_clustering_cmd( input_filepaths, output_filepaths, output_director os.environ["local_clustering.py"], "-i {}".format(input_filepaths[0]), "-o {}".format(output_directory), - "--no_singletons" if bool(opts.no_singletons) else "", + # "--no_singletons" if bool(opts.no_singletons) else "", "-p {}".format(opts.n_jobs), - "--ani_threshold {}".format(opts.ani_threshold), "--genome_cluster_prefix {}".format(opts.genome_cluster_prefix) if bool(opts.genome_cluster_prefix) else "", "--genome_cluster_suffix {}".format(opts.genome_cluster_suffix) if bool(opts.genome_cluster_suffix) else "", @@ -71,7 +72,7 @@ def get_local_clustering_cmd( input_filepaths, output_filepaths, output_director "--protein_cluster_suffix {}".format(opts.protein_cluster_suffix) if bool(opts.protein_cluster_suffix) else "", "--protein_cluster_prefix_zfill {}".format(opts.protein_cluster_prefix_zfill) if bool(opts.protein_cluster_prefix_zfill) else "", "--mmseqs2_options {}".format(opts.mmseqs2_options) if bool(opts.mmseqs2_options) else "", - + "--minimum_core_prevalence {}".format(opts.minimum_core_prevalence), "&&", @@ -161,7 +162,7 @@ def create_pipeline(opts, directories, f_cmds): description = "Global clustering of genomes (FastANI) and proteins (MMSEQS2)" # i/o - input_filepaths = [opts.input] + input_filepaths = [opts.genomes_table] output_filenames = [ "output/*.tsv", @@ -206,7 +207,7 @@ def create_pipeline(opts, directories, f_cmds): description = "Local clustering of genomes (FastANI) and proteins (MMSEQS2)" # i/o - input_filepaths = [opts.input] + input_filepaths = [opts.genomes_table] output_filenames = [ "output/*.tsv", @@ -259,9 +260,9 @@ def main(args=None): parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') - parser_io.add_argument("-i", "--input", type=str, default="stdin", help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") + parser_io.add_argument("-i", "--genomes_table", type=str, default="stdin", help = "path/to/genomes_table.tsv, Format: Must include the following columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") parser_io.add_argument("-o","--output_directory", type=str, default="veba_output/cluster", help = "path/to/project_directory [Default: veba_output/cluster]") - parser_io.add_argument("-e", "--no_singletons", action="store_true", help="Exclude singletons") #isPSLC-1_SSO-3345__SRR178126 + # parser_io.add_argument("-e", "--no_singletons", action="store_true", help="Exclude singletons") #isPSLC-1_SSO-3345__SRR178126 parser_io.add_argument("-l", "--local_clustering", action="store_true", help = "Perform local clustering after global clustering") # Utility @@ -290,6 +291,10 @@ def main(args=None): parser_mmseqs2.add_argument("--protein_cluster_prefix_zfill", type=int, default=0, help="Cluster prefix zfill. Use 7 to match identifiers from OrthoFinder. Use 0 to add no zfill. [Default: 0]") #7 parser_mmseqs2.add_argument("--mmseqs2_options", type=str, default="", help="MMSEQS2 | More options (e.g. --arg 1 ) [Default: '']") + # Pangenome + parser_pangenome = parser.add_argument_group('Pangenome arguments') + parser_pangenome.add_argument("--minimum_core_prevalence", type=float, default=1.0, help="Minimum ratio of genomes detected in a SLC for a SSPC to be considered core (Range (0.0, 1.0]) [Default: 1.0]") + # Options opts = parser.parse_args() opts.script_directory = script_directory @@ -316,6 +321,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/coverage.py b/src/coverage.py index b1c36e5..77c0131 100755 --- a/src/coverage.py +++ b/src/coverage.py @@ -7,12 +7,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.16" +__version__ = "2023.10.16" # ............................................................................. # Notes @@ -144,9 +145,15 @@ def get_alignment_cmd(input_filepaths, output_filepaths, output_directory, direc # Create subdirectory mkdir -p %s - # Bowtie2 - %s -x %s -1 $R1 -2 $R2 --threads %d --seed %d --no-unal %s | %s sort --threads %d --reference %s -T %s > %s && %s index -@ %d %s + OUTPUT_BAM="%s" + # Bowtie2 + if [[ -e "$OUTPUT_BAM" && -s "$OUTPUT_BAM" ]]; then + echo "[Skipping (Exists)] [Bowtie2] [$ID_SAMPLE]" + else + echo "[Running] [Bowtie2] [$ID_SAMPLE]" + %s -x %s -1 $R1 -2 $R2 --threads %d --seed %d --no-unal %s | %s sort --threads %d --reference %s -T %s > $OUTPUT_BAM && %s index -@ %d $OUTPUT_BAM + fi done < $READ_TABLE """%( @@ -159,6 +166,10 @@ def get_alignment_cmd(input_filepaths, output_filepaths, output_directory, direc # Make directory os.path.join(output_directory, "${ID_SAMPLE}"), + # Output BAM + os.path.join(output_directory, "${ID_SAMPLE}", "mapped.sorted.bam"), + + # Bowtie2 os.environ["bowtie2"], input_filepaths[1], @@ -171,12 +182,12 @@ def get_alignment_cmd(input_filepaths, output_filepaths, output_directory, direc opts.n_jobs, input_filepaths[0], os.path.join(directories["tmp"], "samtools_sort_${ID_SAMPLE}"), - os.path.join(output_directory, "${ID_SAMPLE}", "mapped.sorted.bam"), + # os.path.join(output_directory, "${ID_SAMPLE}", "mapped.sorted.bam"), # Samtools index os.environ["samtools"], opts.n_jobs, - os.path.join(output_directory, "${ID_SAMPLE}", "mapped.sorted.bam"), + # os.path.join(output_directory, "${ID_SAMPLE}", "mapped.sorted.bam"), ), ] @@ -557,6 +568,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/devel/assembly-long.py b/src/devel/assembly-long.py new file mode 100755 index 0000000..a9a50a6 --- /dev/null +++ b/src/devel/assembly-long.py @@ -0,0 +1,1083 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob +from collections import OrderedDict, defaultdict + +import pandas as pd + +# Soothsayer Ecosystem +from genopype import * +from soothsayer_utils import * + +pd.options.display.max_colwidth = 100 +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2022.11.14" + +# biosyntheticspades +def get_biosyntheticspades_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + cmd = [ + + # SPAdes + "(", + os.environ["spades.py"], + ] + if "restart-from" not in str(opts.spades_options): + cmd += [ + "-1 {}".format(input_filepaths[0]), + "-2 {}".format(input_filepaths[1]), + ] + cmd += [ + "--bio --meta", + "-o {}".format(output_directory), + "--tmp-dir {}".format(os.path.join(directories["tmp"])), + "--threads {}".format(opts.n_jobs), + "--memory {}".format(opts.memory), + opts.spades_options, + ")", + + + # Clear temporary directory just in case + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + "&&", + + # Bowtie2 Index + "(", + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_index_options, + os.path.join(output_directory, "scaffolds.fasta"), # Reference + os.path.join(output_directory, "scaffolds.fasta"), # Index + ")", + + "&&", + + # Bowtie2 + "(", + os.environ["bowtie2"], + "-x {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-1 {}".format(input_filepaths[0]), + "-2 {}".format(input_filepaths[1]), + "--threads {}".format(opts.n_jobs), + # "--un-conc-gz {}".format(os.path.join(directories["tmp"], "biosyntheticspades", "unmapped_%.fastq.gz")), + # "--un-gz {}".format(os.path.join(output_directory, "unmapped_singletons_%{}.gz".format(unmapped_ext))), + "--seed {}".format(opts.random_state), + "--no-unal", + opts.bowtie2_options, + ")", + + # Convert to sorted BAM + "|", + + "(", + os.environ["samtools"], + "sort", + "--threads {}".format(opts.n_jobs), + "--reference {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-T {}".format(os.path.join(directories["tmp"], "samtools_sort")), + ">", + os.path.join(output_directory, "mapped.sorted.bam"), + ")", + + "&&", + + # Get mapped reads + "(", + os.environ["samtools"], + "view", + os.path.join(output_directory, "mapped.sorted.bam"), + "|", + "cut -f1", + ">", + os.path.join(output_directory, "reads.mapped.list"), + ")", + + # Remove temporary files + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + ] + + # Remove intermediate SPAdes files + for fn in [ + "before_rr.fasta", + "K*", + "misc", + "corrected", + "first_pe_contigs.fasta", + ]: + cmd += [ + "&&", + "rm -rf {}".format(os.path.join(output_directory, fn)), + ] + return cmd + + +# metaplasmidspades +def get_metaplasmidspades_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + cmd = [ + + # Get unmapped reads and repair them + "(", + os.environ["filterbyname.sh"], + "in1={}".format(input_filepaths[0]), + "in2={}".format(input_filepaths[1]), + "names={}".format(input_filepaths[2]), + "out=stdout.fastq", + "|", + os.environ["repair.sh"], + "overwrite=t", + "in=stdin.fastq", + "out1={}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), + "out2={}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), + ")", + + "&&", + + # SPAdes + "(", + os.environ["spades.py"], + ] + + if "restart-from" not in str(opts.spades_options): + cmd += [ + "-1 {}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), + "-2 {}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), + ] + cmd += [ + "--metaplasmid", + "-o {}".format(output_directory), + "--tmp-dir {}".format(os.path.join(directories["tmp"])), + "--threads {}".format(opts.n_jobs), + "--memory {}".format(opts.memory), + opts.spades_options, + ")", + + + # Clear temporary directory just in case + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + "&&", + + # Bowtie2 Index + "(", + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_index_options, + os.path.join(output_directory, "scaffolds.fasta"), # Reference + os.path.join(output_directory, "scaffolds.fasta"), # Index + ")", + + "&&", + + # Bowtie2 + "(", + os.environ["bowtie2"], + "-x {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-1 {}".format(input_filepaths[0]), + "-2 {}".format(input_filepaths[1]), + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + "--no-unal", + opts.bowtie2_options, + ")", + + # Convert to sorted BAM + "|", + + "(", + os.environ["samtools"], + "sort", + "--threads {}".format(opts.n_jobs), + "--reference {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-T {}".format(os.path.join(directories["tmp"], "samtools_sort")), + ">", + os.path.join(output_directory, "mapped.sorted.bam"), + ")", + + "&&", + + # Get mapped reads + "(", + os.environ["samtools"], + "view", + os.path.join(output_directory, "mapped.sorted.bam"), + "|", + "cut -f1", + ">", + os.path.join(output_directory, "reads.mapped.list"), + ")", + + # Aggregated reads + "&&", + "cat", + input_filepaths[2], + os.path.join(output_directory, "reads.mapped.list"), + ">", + os.path.join(output_directory, "reads.mapped.aggregated.list"), + + + + # Remove temporary files + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + ] + + # Remove intermediate SPAdes files + for fn in [ + "before_rr.fasta", + "K*", + "misc", + "corrected", + "first_pe_contigs.fasta", + ]: + cmd += [ + "&&", + "rm -rf {}".format(os.path.join(output_directory, fn)), + ] + return cmd + +# metaspades +def get_metaspades_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + cmd = [ + + # Get unmapped reads and repair them + "(", + os.environ["filterbyname.sh"], + "in1={}".format(input_filepaths[0]), + "in2={}".format(input_filepaths[1]), + "names={}".format(input_filepaths[2]), + "out=stdout.fastq", + "|", + os.environ["repair.sh"], + "overwrite=t", + "in=stdin.fastq", + "out1={}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), + "out2={}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), + ")", + + "&&", + + # SPAdes + "(", + os.environ["spades.py"], + ] + if "restart-from" not in str(opts.spades_options): + cmd += [ + "-1 {}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), + "-2 {}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), + ] + cmd += [ + "--meta", + "-o {}".format(output_directory), + "--tmp-dir {}".format(os.path.join(directories["tmp"])), + "--threads {}".format(opts.n_jobs), + "--memory {}".format(opts.memory), + opts.spades_options, + ")", + + + # Clear temporary directory just in case + "&&", + + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + + "&&", + + # Bowtie2 Index + "(", + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_index_options, + os.path.join(output_directory, "scaffolds.fasta"), # Reference + os.path.join(output_directory, "scaffolds.fasta"), # Index + ")", + + "&&", + + # Bowtie2 + "(", + os.environ["bowtie2"], + "-x {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-1 {}".format(input_filepaths[0]), + "-2 {}".format(input_filepaths[1]), + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + "--no-unal", + opts.bowtie2_options, + ")", + + # Convert to sorted BAM + "|", + + "(", + os.environ["samtools"], + "sort", + "--threads {}".format(opts.n_jobs), + "--reference {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-T {}".format(os.path.join(directories["tmp"], "samtools_sort")), + ">", + os.path.join(output_directory, "mapped.sorted.bam"), + ")", + + # Remove temporary files + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + ] + + # Remove intermediate SPAdes files + for fn in [ + "before_rr.fasta", + "K*", + "misc", + "corrected", + "first_pe_contigs.fasta", + ]: + cmd += [ + "&&", + "rm -rf {}".format(os.path.join(output_directory, fn)), + ] + + return cmd + +# Concatenate assemblies and BAM files +def get_concatenate_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + # Command + cmd = [ + # Add assembler to descriptions + "(", + os.environ["replace_fasta_descriptions.py"], + "-f {}".format(os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta")), + "-d biosyntheticSPAdes", + "-o {}".format(os.path.join(directories["tmp"], "scaffold.biosyntheticspades.fasta")), + + ] + if opts.run_metaplasmidspades: + cmd += [ + "&&", + os.environ["replace_fasta_descriptions.py"], + "-f {}".format(os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta")), + "-d metaplasmidSPAdes", + "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaplasmidspades.fasta")), + + "&&", + + os.environ["replace_fasta_descriptions.py"], + "-f {}".format(os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta")), + "-d metaSPAdes", + "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaspades.fasta")), + ] + else: + cmd += [ + "&&", + os.environ["replace_fasta_descriptions.py"], + "-f {}".format(os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta")), + "-d metaSPAdes", + "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaspades.fasta")), + + ] + + cmd += [")"] + + + # Concatenate scaffolds + cmd += [ + "&&", + "cat {} > {}".format(os.path.join(directories["tmp"], "scaffold.*.fasta"), os.path.join(output_directory, "scaffolds.fasta")), + "&&", + ] + + # Concatenate mapped.sorted.bam + if opts.run_metaplasmidspades: + cmd += [ + "(", + os.environ["samtools"], + "merge", + "-@ {}".format(opts.n_jobs), + "-o {}".format(os.path.join(output_directory, "mapped.sorted.bam")), + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), + ")", + ] + else: + cmd += [ + "(", + os.environ["samtools"], + "merge", + "-@ {}".format(opts.n_jobs), + "-o {}".format(os.path.join(output_directory, "mapped.sorted.bam")), + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), + ")", + ] + + + # Bowtie2 Index + cmd += [ + "&&", + "(", + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_index_options, + os.path.join(output_directory, "scaffolds.fasta"), # Reference + os.path.join(output_directory, "scaffolds.fasta"), # Index + ")", + + # Samtools Index + "&&", + "(", + os.environ["samtools"], + "index", + "-@ {}".format(opts.n_jobs), + os.path.join(output_directory, "mapped.sorted.bam"), + ")", + + # Create SAF file + "&&", + "(", + os.environ["fasta_to_saf.py"], + "-i", + os.path.join(output_directory, "scaffolds.fasta"), + ">", + os.path.join(output_directory, "scaffolds.fasta.saf"), + ")", + + # Remove temporary files + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"],"*")), + ] + return cmd + + +# featureCounts +def get_featurecounts_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + + # ORF-Level Counts + cmd = [ + "mkdir -p {}".format(os.path.join(directories["tmp"], "featurecounts")), + "&&", + "(", + os.environ["featureCounts"], + # "-G {}".format(input_filepaths[0]), + "-a {}".format(input_filepaths[1]), + "-o {}".format(os.path.join(output_directory, "featurecounts.tsv")), + "-F SAF", + "--tmpDir {}".format(os.path.join(directories["tmp"], "featurecounts")), + "-T {}".format(opts.n_jobs), + opts.featurecounts_options, + input_filepaths[2], + ")", + "&&", + "gzip -f {}".format(os.path.join(output_directory, "featurecounts.tsv")), + ] + return cmd + +# seqkit +def get_seqkit_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + + # ORF-Level Counts + cmd = [ + + os.environ["seqkit"], + "stats", + "-a", + "-j {}".format(opts.n_jobs), + "-T", + # "-b", + " ".join(input_filepaths), + "|", + "gzip", + ">", + output_filepaths[0], + ] + return cmd + +# Output +def get_output_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + # Command + + # Symlinks + cmd = ["("] + for filepath in input_filepaths: + cmd.append("ln -f -s {} {}".format(os.path.realpath(filepath), output_directory)) + cmd.append("&&") + cmd[-1] = ")" + + # Cleanup intermediate files + if opts.run_metaplasmidspades: + if opts.remove_intermediate_scaffolds: + cmd += [ + "&&", + "rm -rf {} {} {} {} {} {}".format( + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "*.fasta"), + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta.*.bt2"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "*.fasta"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta.*.bt2"), + os.path.join(directories[("intermediate", "3__metaspades")], "*.fasta"), + os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta.*.bt2"), + ) + ] + if opts.remove_intermediate_bam: + cmd += [ + "&&", + "rm -rf {} {} {}".format( + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), + ) + ] + else: + if opts.remove_intermediate_scaffolds: + cmd += [ + "&&", + "rm -rf {} {} {} {}".format( + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "*.fasta"), + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta.*.bt2"), + os.path.join(directories[("intermediate", "2__metaspades")], "*.fasta"), + os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta.*.bt2"), + ) + ] + if opts.remove_intermediate_bam: + cmd += [ + "&&", + "rm -rf {} {}".format( + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), + ) + ] + + return cmd + +# def get_output_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): +# # Command + + # cmd += [ + # "&&", + # os.environ["fasta_to_saf.py"], + # "-i", + # os.path.join(output_directory, "scaffolds.fasta"), + # ">", + # os.path.join(output_directory, "scaffolds.fasta.saf"), + # ] + + # "&&", + # "(", + # os.environ["samtools"], + # "index", + # "-@ {}".format(opts.n_jobs), + # output_filepaths[0], + # ")", + + + +# ============ +# Run Pipeline +# ============ +# Set environment variables +def add_executables_to_environment(opts): + """ + Adapted from Soothsayer: https://github.com/jolespin/soothsayer + """ + accessory_scripts = { + "fasta_to_saf.py", + "replace_fasta_descriptions.py", + } + + required_executables={ + "filterbyname.sh", + "bowtie2-build", + "bowtie2", + "samtools", + "repair.sh", + "spades.py", + "featureCounts", + "seqkit", + } | accessory_scripts + + if opts.path_config == "CONDA_PREFIX": + executables = dict() + for name in required_executables: + executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) + else: + if opts.path_config is None: + opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") + opts.path_config = format_path(opts.path_config) + assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) + assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) + df_config = pd.read_csv(opts.path_config, sep="\t") + assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) + df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) + # Get executable paths + executables = OrderedDict(zip(df_config["name"], df_config["executable"])) + assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) + + # Display + for name in sorted(accessory_scripts): + executables[name] = "python " + os.path.join(opts.script_directory, "scripts", name) + print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) + for name, executable in executables.items(): + if name in required_executables: + print(name, executable, sep = " --> ", file=sys.stdout) + os.environ[name] = executable.strip() + print("", file=sys.stdout) + +# Pipeline +def create_pipeline(opts, directories, f_cmds): + + # ................................................................. + # Primordial + # ................................................................. + # Commands file + pipeline = ExecutablePipeline(name=__program__, description=opts.name, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + + # ========== + # biosyntheticspades + # ========== + + step = 1 + + # Info + program = "biosyntheticspades" + program_label = "{}__{}".format(step, program) + description = "Assembling paired-end reads using biosyntheticSPAdes" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # i/o + input_filepaths = [opts.forward_reads, opts.reverse_reads] + output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "reads.mapped.list"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_biosyntheticspades_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + log_prefix=program_label, + + ) + + # ========== + # metaplasmidspades + # ========== + if opts.run_metaplasmidspades: + + step += 1 + + # Info + program = "metaplasmidspades" + program_label = "{}__{}".format(step, program) + description = "Assembling paired-end reads using metaplasmidSPAdes" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # i/o + input_filepaths = [opts.forward_reads, opts.reverse_reads, output_filepaths[-1]] # output_filepaths[2] is reads.mapped.list + + output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "reads.mapped.aggregated.list"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_metaplasmidspades_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + log_prefix=program_label, + + ) + + # ========== + # metaspades + # ========== + + step += 1 + + # Info + program = "metaspades" + program_label = "{}__{}".format(step, program) + description = "Assembling paired-end reads using metaSPAdes" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # i/o + input_filepaths = [opts.forward_reads, opts.reverse_reads, output_filepaths[-1]] # output_filepaths[2] is reads.mapped.aggregated.list + output_filenames = ["scaffolds.fasta", "mapped.sorted.bam"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_metaspades_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + log_prefix=program_label, + + ) + + # ========== + # concatenate + # ========== + + step += 1 + + # Info + program = "concatenate" + program_label = "{}__{}".format(step, program) + description = "Concatenate assemblies and BAM files" + + # Add to directories + output_directory = directories["output"] + + # i/o + if opts.run_metaplasmidspades: + input_filepaths = [ + # scaffolds.fasta + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta"), + # mapped.sorted.bam + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), + ] + else: + input_filepaths = [ + # scaffolds.fasta + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta"), + # mapped.sorted.bam + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), + ] + + output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "mapped.sorted.bam.bai", "scaffolds.fasta.saf", "scaffolds.fasta.*.bt2"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_concatenate_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), # Adjust to allow for assemblies that don't find BGCs or plasmids? + validate_outputs=True, + log_prefix=program_label, + + ) + + + # ========== + # featureCounts + # ========== + step += 1 + + # Info + program = "featurecounts" + program_label = "{}__{}".format(step, program) + description = "Counting reads" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # i/o + + input_filepaths = [ + os.path.join(directories["output"], "scaffolds.fasta"), + os.path.join(directories["output"], "scaffolds.fasta.saf"), + os.path.join(directories["output"], "mapped.sorted.bam"), + ] + + output_filenames = ["featurecounts.tsv.gz"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_featurecounts_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + ) + + # ========== + # stats + # ========== + step += 1 + + # Info + program = "seqkit" + program_label = "{}__{}".format(step, program) + description = "Assembly statistics" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # i/o + if opts.run_metaplasmidspades: + input_filepaths = [ + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta"), + os.path.join(directories["output"], "scaffolds.fasta"), + ] + else: + input_filepaths = [ + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta"), + os.path.join(directories["output"], "scaffolds.fasta"), + ] + + output_filenames = ["seqkit_stats.tsv.gz"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_seqkit_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + validate_outputs=True, + log_prefix=program_label, + + ) + + + # ============= + # Output + # ============= + step += 1 + + # Info + program = "output" + program_label = "{}__{}".format(step, program) + description = "Symlinking relevant output files and removing intermediate" + + # Add to directories + output_directory = directories["output"] + + # i/o + + input_filepaths = [ + os.path.join(directories[("intermediate", "{}__featurecounts".format(step-2))], "featurecounts.tsv.gz"), + os.path.join(directories[("intermediate", "{}__seqkit".format(step-1))], "seqkit_stats.tsv.gz"), + ] + + output_filenames = map(lambda fp: fp.split("/")[-1], input_filepaths) + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_output_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + return pipeline + +# Configure parameters +def configure_parameters(opts, directories): + # os.environ[] + + assert opts.forward_reads != opts.reverse_reads, "You probably mislabeled the input files because `forward_reads` should not be the same as `reverse_reads`: {}".format(opts.forward_reads) + # assert not bool(opts.unpaired_reads), "Cannot have --unpaired_reads if --forward_reads. Note, this behavior may be changed in the future but it's an adaptation of interleaved reads." + + # Set environment variables + add_executables_to_environment(opts=opts) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -1 -2 -n -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser_io = parser.add_argument_group('Required I/O arguments') + parser_io.add_argument("-1","--forward_reads", type=str, help = "path/to/forward_reads.fq") + parser_io.add_argument("-2","--reverse_reads", type=str, help = "path/to/reverse_reads.fq") + parser_io.add_argument("-n", "--name", type=str, help="Name of sample", required=True) + parser_io.add_argument("-o","--project_directory", type=str, default="veba_output/assembly_sequential", help = "path/to/project_directory [Default: veba_output/assembly_sequential]") + + # Utility + parser_utility = parser.add_argument_group('Utility arguments') + parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future + parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") + parser_utility.add_argument("--random_state", type=int, default=0, help = "Random state [Default: 0]") + parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") + parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) + parser_utility.add_argument("--tmpdir", type=str, help="Set temporary directory") #site-packges in future + parser_utility.add_argument("-S", "--remove_intermediate_scaffolds", action="store_true", help="Remove intermediate scaffolds.fasta.*. If this option is chosen, output files are not validated [Default is to keep]") + parser_utility.add_argument("-B", "--remove_intermediate_bam", action="store_true", help="Remove intermediate mapped.sorted.bam.*. If this option is chosen, output files are not validated [Default is to keep]") + + # Assembler + parser_assembler = parser.add_argument_group('SPAdes arguments') + parser_assembler.add_argument("--run_metaplasmidspades", action="store_true", help="SPAdes | Run metaplasmidSPAdes. This may sacrifice MAG completeness for plasmid completeness. Will fail if there are no extrachromosomal contigs assembled.") + # parser_assembler.add_argument("--run_metaviralspades", action="store_true", help="SPAdes | Run metaviralSPAdes. This will result in a separete set of scaffolds.") + parser_assembler.add_argument("-m", "--memory", type=int, default=250, help="SPAdes | RAM limit in Gb (terminates if exceeded). [Default: 250]") + parser_assembler.add_argument("--spades_options", type=str, default="", help="SPAdes | More options (e.g. --arg 1 ) [Default: '']\nhttp://cab.spbu.ru/files/release3.11.1/manual.html") + + # Aligner + parser_aligner = parser.add_argument_group('Bowtie2 arguments') + parser_aligner.add_argument("--bowtie2_index_options", type=str, default="", help="bowtie2-build | More options (e.g. --arg 1 ) [Default: '']") + parser_aligner.add_argument("--bowtie2_options", type=str, default="", help="bowtie2 | More options (e.g. --arg 1 ) [Default: '']") + + # featureCounts + parser_featurecounts = parser.add_argument_group('featureCounts arguments') + parser_featurecounts.add_argument("--featurecounts_options", type=str, default="", help="featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/") + + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Directories + directories = dict() + directories["project"] = create_directory(opts.project_directory) + directories["sample"] = create_directory(os.path.join(directories["project"], opts.name)) + directories["output"] = create_directory(os.path.join(directories["sample"], "output")) + directories["log"] = create_directory(os.path.join(directories["sample"], "log")) + if not opts.tmpdir: + opts.tmpdir = os.path.join(directories["sample"], "tmp") + directories["tmp"] = create_directory(opts.tmpdir) + directories["checkpoints"] = create_directory(os.path.join(directories["sample"], "checkpoints")) + directories["intermediate"] = create_directory(os.path.join(directories["sample"], "intermediate")) + os.environ["TMPDIR"] = directories["tmp"] + + # Info + print(format_header(__program__, "="), file=sys.stdout) + print(format_header("Configuration:", "-"), file=sys.stdout) + print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) + print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) + print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("Script version:", __version__, file=sys.stdout) + print("Moment:", get_timestamp(), file=sys.stdout) + print("Directory:", os.getcwd(), file=sys.stdout) + print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) + configure_parameters(opts, directories) + sys.stdout.flush() + + # Run pipeline + with open(os.path.join(directories["sample"], "commands.sh"), "w") as f_cmds: + pipeline = create_pipeline( + opts=opts, + directories=directories, + f_cmds=f_cmds, + ) + pipeline.compile() + pipeline.execute(restart_from_checkpoint=opts.restart_from_checkpoint) + +if __name__ == "__main__": + main() diff --git a/src/devel/assembly-sequential.py b/src/devel/assembly-sequential.py new file mode 100755 index 0000000..dccb34c --- /dev/null +++ b/src/devel/assembly-sequential.py @@ -0,0 +1,1092 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob +from collections import OrderedDict, defaultdict + +import pandas as pd + +# Soothsayer Ecosystem +from genopype import * +from soothsayer_utils import * + +pd.options.display.max_colwidth = 100 +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.5.15" + +# biosyntheticspades +def get_biosyntheticspades_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + cmd = [ + + # SPAdes + "(", + os.environ["spades.py"], + ] + if "restart-from" not in str(opts.spades_options): + cmd += [ + "-1 {}".format(input_filepaths[0]), + "-2 {}".format(input_filepaths[1]), + ] + cmd += [ + "--bio --meta", + "-o {}".format(output_directory), + "--tmp-dir {}".format(os.path.join(directories["tmp"])), + "--threads {}".format(opts.n_jobs), + "--memory {}".format(opts.memory), + opts.spades_options, + ")", + + + # Clear temporary directory just in case + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + "&&", + + # Bowtie2 Index + "(", + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_index_options, + os.path.join(output_directory, "scaffolds.fasta"), # Reference + os.path.join(output_directory, "scaffolds.fasta"), # Index + ")", + + "&&", + + # Bowtie2 + "(", + os.environ["bowtie2"], + "-x {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-1 {}".format(input_filepaths[0]), + "-2 {}".format(input_filepaths[1]), + "--threads {}".format(opts.n_jobs), + # "--un-conc-gz {}".format(os.path.join(directories["tmp"], "biosyntheticspades", "unmapped_%.fastq.gz")), + # "--un-gz {}".format(os.path.join(output_directory, "unmapped_singletons_%{}.gz".format(unmapped_ext))), + "--seed {}".format(opts.random_state), + "--no-unal", + opts.bowtie2_options, + ")", + + # Convert to sorted BAM + "|", + + "(", + os.environ["samtools"], + "sort", + "--threads {}".format(opts.n_jobs), + "--reference {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-T {}".format(os.path.join(directories["tmp"], "samtools_sort")), + ">", + os.path.join(output_directory, "mapped.sorted.bam"), + ")", + + "&&", + + # Get mapped reads + "(", + os.environ["samtools"], + "view", + os.path.join(output_directory, "mapped.sorted.bam"), + "|", + "cut -f1", + ">", + os.path.join(output_directory, "reads.mapped.list"), + ")", + + # Remove temporary files + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + ] + + # Remove intermediate SPAdes files + for fn in [ + "before_rr.fasta", + "K*", + "misc", + "corrected", + "first_pe_contigs.fasta", + ]: + cmd += [ + "&&", + "rm -rf {}".format(os.path.join(output_directory, fn)), + ] + return cmd + + +# metaplasmidspades +def get_metaplasmidspades_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + cmd = [ + + # Get unmapped reads and repair them + "(", + os.environ["filterbyname.sh"], + "in1={}".format(input_filepaths[0]), + "in2={}".format(input_filepaths[1]), + "names={}".format(input_filepaths[2]), + "out=stdout.fastq", + "|", + os.environ["repair.sh"], + "overwrite=t", + "in=stdin.fastq", + "out1={}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), + "out2={}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), + ")", + + "&&", + + # SPAdes + "(", + os.environ["spades.py"], + ] + + if "restart-from" not in str(opts.spades_options): + cmd += [ + "-1 {}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), + "-2 {}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), + ] + cmd += [ + "--metaplasmid", + "-o {}".format(output_directory), + "--tmp-dir {}".format(os.path.join(directories["tmp"])), + "--threads {}".format(opts.n_jobs), + "--memory {}".format(opts.memory), + opts.spades_options, + ")", + + + # Clear temporary directory just in case + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + "&&", + + # Bowtie2 Index + "(", + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_index_options, + os.path.join(output_directory, "scaffolds.fasta"), # Reference + os.path.join(output_directory, "scaffolds.fasta"), # Index + ")", + + "&&", + + # Bowtie2 + "(", + os.environ["bowtie2"], + "-x {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-1 {}".format(input_filepaths[0]), + "-2 {}".format(input_filepaths[1]), + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + "--no-unal", + opts.bowtie2_options, + ")", + + # Convert to sorted BAM + "|", + + "(", + os.environ["samtools"], + "sort", + "--threads {}".format(opts.n_jobs), + "--reference {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-T {}".format(os.path.join(directories["tmp"], "samtools_sort")), + ">", + os.path.join(output_directory, "mapped.sorted.bam"), + ")", + + "&&", + + # Get mapped reads + "(", + os.environ["samtools"], + "view", + os.path.join(output_directory, "mapped.sorted.bam"), + "|", + "cut -f1", + ">", + os.path.join(output_directory, "reads.mapped.list"), + ")", + + # Aggregated reads + "&&", + "cat", + input_filepaths[2], + os.path.join(output_directory, "reads.mapped.list"), + ">", + os.path.join(output_directory, "reads.mapped.aggregated.list"), + + + + # Remove temporary files + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + ] + + # Remove intermediate SPAdes files + for fn in [ + "before_rr.fasta", + "K*", + "misc", + "corrected", + "first_pe_contigs.fasta", + ]: + cmd += [ + "&&", + "rm -rf {}".format(os.path.join(output_directory, fn)), + ] + return cmd + +# metaspades +def get_metaspades_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + cmd = [ + + # Get unmapped reads and repair them + "(", + os.environ["filterbyname.sh"], + "in1={}".format(input_filepaths[0]), + "in2={}".format(input_filepaths[1]), + "names={}".format(input_filepaths[2]), + "out=stdout.fastq", + "|", + os.environ["repair.sh"], + "overwrite=t", + "in=stdin.fastq", + "out1={}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), + "out2={}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), + ")", + + "&&", + + # SPAdes + "(", + os.environ["spades.py"], + ] + if "restart-from" not in str(opts.spades_options): + cmd += [ + "-1 {}".format(os.path.join(directories["tmp"], "unmapped_1.repaired.fastq.gz")), + "-2 {}".format(os.path.join(directories["tmp"], "unmapped_2.repaired.fastq.gz")), + ] + cmd += [ + "--meta", + "-o {}".format(output_directory), + "--tmp-dir {}".format(os.path.join(directories["tmp"])), + "--threads {}".format(opts.n_jobs), + "--memory {}".format(opts.memory), + opts.spades_options, + ")", + + + # Clear temporary directory just in case + "&&", + + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + + "&&", + + # Bowtie2 Index + "(", + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_index_options, + os.path.join(output_directory, "scaffolds.fasta"), # Reference + os.path.join(output_directory, "scaffolds.fasta"), # Index + ")", + + "&&", + + # Bowtie2 + "(", + os.environ["bowtie2"], + "-x {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-1 {}".format(input_filepaths[0]), + "-2 {}".format(input_filepaths[1]), + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + "--no-unal", + opts.bowtie2_options, + ")", + + # Convert to sorted BAM + "|", + + "(", + os.environ["samtools"], + "sort", + "--threads {}".format(opts.n_jobs), + "--reference {}".format(os.path.join(output_directory, "scaffolds.fasta")), + "-T {}".format(os.path.join(directories["tmp"], "samtools_sort")), + ">", + os.path.join(output_directory, "mapped.sorted.bam"), + ")", + + # Remove temporary files + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"], "*")), + ] + + # Remove intermediate SPAdes files + for fn in [ + "before_rr.fasta", + "K*", + "misc", + "corrected", + "first_pe_contigs.fasta", + ]: + cmd += [ + "&&", + "rm -rf {}".format(os.path.join(output_directory, fn)), + ] + + return cmd + +# Concatenate assemblies and BAM files +def get_concatenate_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + # Command + cmd = [ + # Add assembler to descriptions + "(", + os.environ["replace_fasta_descriptions.py"], + "-f {}".format(os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta")), + "-d biosyntheticSPAdes", + "-o {}".format(os.path.join(directories["tmp"], "scaffold.biosyntheticspades.fasta")), + + ] + if opts.run_metaplasmidspades: + cmd += [ + "&&", + os.environ["replace_fasta_descriptions.py"], + "-f {}".format(os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta")), + "-d metaplasmidSPAdes", + "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaplasmidspades.fasta")), + + "&&", + + os.environ["replace_fasta_descriptions.py"], + "-f {}".format(os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta")), + "-d metaSPAdes", + "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaspades.fasta")), + ] + else: + cmd += [ + "&&", + os.environ["replace_fasta_descriptions.py"], + "-f {}".format(os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta")), + "-d metaSPAdes", + "-o {}".format(os.path.join(directories["tmp"], "scaffold.metaspades.fasta")), + + ] + + cmd += [")"] + + + # Concatenate scaffolds + cmd += [ + "&&", + "cat {} > {}".format(os.path.join(directories["tmp"], "scaffold.*.fasta"), os.path.join(output_directory, "scaffolds.fasta")), + "&&", + ] + + # Concatenate mapped.sorted.bam + if opts.run_metaplasmidspades: + cmd += [ + "(", + os.environ["samtools"], + "merge", + "-@ {}".format(opts.n_jobs), + "-o {}".format(os.path.join(output_directory, "mapped.sorted.bam")), + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), + ")", + ] + else: + cmd += [ + "(", + os.environ["samtools"], + "merge", + "-@ {}".format(opts.n_jobs), + "-o {}".format(os.path.join(output_directory, "mapped.sorted.bam")), + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), + ")", + ] + + + # Bowtie2 Index + cmd += [ + "&&", + "(", + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_index_options, + os.path.join(output_directory, "scaffolds.fasta"), # Reference + os.path.join(output_directory, "scaffolds.fasta"), # Index + ")", + + # Samtools Index + "&&", + "(", + os.environ["samtools"], + "index", + "-@ {}".format(opts.n_jobs), + os.path.join(output_directory, "mapped.sorted.bam"), + ")", + + # Create SAF file + "&&", + "(", + os.environ["fasta_to_saf.py"], + "-i", + os.path.join(output_directory, "scaffolds.fasta"), + ">", + os.path.join(output_directory, "scaffolds.fasta.saf"), + ")", + + # Remove temporary files + "&&", + "rm -rf {}".format(os.path.join(directories["tmp"],"*")), + ] + return cmd + + +# featureCounts +def get_featurecounts_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + + # ORF-Level Counts + cmd = [ + "mkdir -p {}".format(os.path.join(directories["tmp"], "featurecounts")), + "&&", + "(", + os.environ["featureCounts"], + # "-G {}".format(input_filepaths[0]), + "-a {}".format(input_filepaths[1]), + "-o {}".format(os.path.join(output_directory, "featurecounts.tsv")), + "-F SAF", + "--tmpDir {}".format(os.path.join(directories["tmp"], "featurecounts")), + "-T {}".format(opts.n_jobs), + "-p --countReadPairs", + opts.featurecounts_options, + input_filepaths[2], + ")", + "&&", + "gzip -f {}".format(os.path.join(output_directory, "featurecounts.tsv")), + ] + return cmd + +# seqkit +def get_seqkit_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Command + + # ORF-Level Counts + cmd = [ + + os.environ["seqkit"], + "stats", + "-a", + "-j {}".format(opts.n_jobs), + "-T", + # "-b", + " ".join(input_filepaths), + "|", + "gzip", + ">", + output_filepaths[0], + ] + return cmd + +# Output +def get_output_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + # Command + + # Symlinks + cmd = [ + "DST={}; (for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + " ".join(input_filepaths), + ) + ] + + # Cleanup intermediate files + if opts.run_metaplasmidspades: + if opts.remove_intermediate_scaffolds: + cmd += [ + "&&", + "rm -rf {} {} {} {} {} {}".format( + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "*.fasta"), + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta.*.bt2"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "*.fasta"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta.*.bt2"), + os.path.join(directories[("intermediate", "3__metaspades")], "*.fasta"), + os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta.*.bt2"), + ) + ] + if opts.remove_intermediate_bam: + cmd += [ + "&&", + "rm -rf {} {} {}".format( + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), + ) + ] + else: + if opts.remove_intermediate_scaffolds: + cmd += [ + "&&", + "rm -rf {} {} {} {}".format( + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "*.fasta"), + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta.*.bt2"), + os.path.join(directories[("intermediate", "2__metaspades")], "*.fasta"), + os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta.*.bt2"), + ) + ] + if opts.remove_intermediate_bam: + cmd += [ + "&&", + "rm -rf {} {}".format( + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), + ) + ] + + return cmd + +# def get_output_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): +# # Command + + # cmd += [ + # "&&", + # os.environ["fasta_to_saf.py"], + # "-i", + # os.path.join(output_directory, "scaffolds.fasta"), + # ">", + # os.path.join(output_directory, "scaffolds.fasta.saf"), + # ] + + # "&&", + # "(", + # os.environ["samtools"], + # "index", + # "-@ {}".format(opts.n_jobs), + # output_filepaths[0], + # ")", + + + +# ============ +# Run Pipeline +# ============ +# Set environment variables +def add_executables_to_environment(opts): + """ + Adapted from Soothsayer: https://github.com/jolespin/soothsayer + """ + accessory_scripts = { + "fasta_to_saf.py", + "replace_fasta_descriptions.py", + } + + required_executables={ + "filterbyname.sh", + "bowtie2-build", + "bowtie2", + "samtools", + "repair.sh", + "spades.py", + "featureCounts", + "seqkit", + } | accessory_scripts + + if opts.path_config == "CONDA_PREFIX": + executables = dict() + for name in required_executables: + executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) + else: + if opts.path_config is None: + opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") + opts.path_config = format_path(opts.path_config) + assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) + assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) + df_config = pd.read_csv(opts.path_config, sep="\t") + assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) + df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) + # Get executable paths + executables = OrderedDict(zip(df_config["name"], df_config["executable"])) + assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) + + # Display + for name in sorted(accessory_scripts): + executables[name] = "'{}'".format(os.path.join(opts.script_directory, "scripts", name)) # Can handle spaces in path + + print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) + for name, executable in executables.items(): + if name in required_executables: + print(name, executable, sep = " --> ", file=sys.stdout) + os.environ[name] = executable.strip() + print("", file=sys.stdout) + +# Pipeline +def create_pipeline(opts, directories, f_cmds): + + # ................................................................. + # Primordial + # ................................................................. + # Commands file + pipeline = ExecutablePipeline(name=__program__, description=opts.name, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + + # ========== + # biosyntheticspades + # ========== + + step = 1 + + # Info + program = "biosyntheticspades" + program_label = "{}__{}".format(step, program) + description = "Assembling paired-end reads using biosyntheticSPAdes" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # i/o + input_filepaths = [opts.forward_reads, opts.reverse_reads] + output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "reads.mapped.list"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_biosyntheticspades_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + log_prefix=program_label, + + ) + + # ========== + # metaplasmidspades + # ========== + if opts.run_metaplasmidspades: + + step += 1 + + # Info + program = "metaplasmidspades" + program_label = "{}__{}".format(step, program) + description = "Assembling paired-end reads using metaplasmidSPAdes" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # i/o + input_filepaths = [opts.forward_reads, opts.reverse_reads, output_filepaths[-1]] # output_filepaths[2] is reads.mapped.list + + output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "reads.mapped.aggregated.list"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_metaplasmidspades_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + log_prefix=program_label, + + ) + + # ========== + # metaspades + # ========== + + step += 1 + + # Info + program = "metaspades" + program_label = "{}__{}".format(step, program) + description = "Assembling paired-end reads using metaSPAdes" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # i/o + input_filepaths = [opts.forward_reads, opts.reverse_reads, output_filepaths[-1]] # output_filepaths[2] is reads.mapped.aggregated.list + output_filenames = ["scaffolds.fasta", "mapped.sorted.bam"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_metaspades_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + log_prefix=program_label, + + ) + + # ========== + # concatenate + # ========== + + step += 1 + + # Info + program = "concatenate" + program_label = "{}__{}".format(step, program) + description = "Concatenate assemblies and BAM files" + + # Add to directories + output_directory = directories["output"] + + # i/o + if opts.run_metaplasmidspades: + input_filepaths = [ + # scaffolds.fasta + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta"), + # mapped.sorted.bam + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "3__metaspades")], "mapped.sorted.bam"), + ] + else: + input_filepaths = [ + # scaffolds.fasta + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta"), + # mapped.sorted.bam + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "mapped.sorted.bam"), + os.path.join(directories[("intermediate", "2__metaspades")], "mapped.sorted.bam"), + ] + + output_filenames = ["scaffolds.fasta", "mapped.sorted.bam", "mapped.sorted.bam.bai", "scaffolds.fasta.saf", "scaffolds.fasta.*.bt2"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_concatenate_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), # Adjust to allow for assemblies that don't find BGCs or plasmids? + validate_outputs=True, + log_prefix=program_label, + + ) + + + # ========== + # featureCounts + # ========== + step += 1 + + # Info + program = "featurecounts" + program_label = "{}__{}".format(step, program) + description = "Counting reads" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # i/o + + input_filepaths = [ + os.path.join(directories["output"], "scaffolds.fasta"), + os.path.join(directories["output"], "scaffolds.fasta.saf"), + os.path.join(directories["output"], "mapped.sorted.bam"), + ] + + output_filenames = ["featurecounts.tsv.gz"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_featurecounts_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + ) + + # ========== + # stats + # ========== + step += 1 + + # Info + program = "seqkit" + program_label = "{}__{}".format(step, program) + description = "Assembly statistics" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # i/o + if opts.run_metaplasmidspades: + input_filepaths = [ + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "2__metaplasmidspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "3__metaspades")], "scaffolds.fasta"), + os.path.join(directories["output"], "scaffolds.fasta"), + ] + else: + input_filepaths = [ + os.path.join(directories[("intermediate", "1__biosyntheticspades")], "scaffolds.fasta"), + os.path.join(directories[("intermediate", "2__metaspades")], "scaffolds.fasta"), + os.path.join(directories["output"], "scaffolds.fasta"), + ] + + output_filenames = ["seqkit_stats.tsv.gz"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_seqkit_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=not (opts.remove_intermediate_scaffolds or opts.remove_intermediate_bam), + validate_outputs=True, + log_prefix=program_label, + + ) + + + # ============= + # Output + # ============= + step += 1 + + # Info + program = "output" + program_label = "{}__{}".format(step, program) + description = "Symlinking relevant output files and removing intermediate" + + # Add to directories + output_directory = directories["output"] + + # i/o + + input_filepaths = [ + os.path.join(directories[("intermediate", "{}__featurecounts".format(step-2))], "featurecounts.tsv.gz"), + os.path.join(directories[("intermediate", "{}__seqkit".format(step-1))], "seqkit_stats.tsv.gz"), + ] + + output_filenames = map(lambda fp: fp.split("/")[-1], input_filepaths) + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_output_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + return pipeline + +# Configure parameters +def configure_parameters(opts, directories): + # os.environ[] + + assert opts.forward_reads != opts.reverse_reads, "You probably mislabeled the input files because `forward_reads` should not be the same as `reverse_reads`: {}".format(opts.forward_reads) + # assert not bool(opts.unpaired_reads), "Cannot have --unpaired_reads if --forward_reads. Note, this behavior may be changed in the future but it's an adaptation of interleaved reads." + + # Set environment variables + add_executables_to_environment(opts=opts) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -1 -2 -n -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser_io = parser.add_argument_group('Required I/O arguments') + parser_io.add_argument("-1","--forward_reads", type=str, help = "path/to/forward_reads.fq") + parser_io.add_argument("-2","--reverse_reads", type=str, help = "path/to/reverse_reads.fq") + parser_io.add_argument("-n", "--name", type=str, help="Name of sample", required=True) + parser_io.add_argument("-o","--project_directory", type=str, default="veba_output/assembly_sequential", help = "path/to/project_directory [Default: veba_output/assembly_sequential]") + + # Utility + parser_utility = parser.add_argument_group('Utility arguments') + parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future + parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") + parser_utility.add_argument("--random_state", type=int, default=0, help = "Random state [Default: 0]") + parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") + parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) + parser_utility.add_argument("--tmpdir", type=str, help="Set temporary directory") #site-packges in future + parser_utility.add_argument("-S", "--remove_intermediate_scaffolds", action="store_true", help="Remove intermediate scaffolds.fasta.*. If this option is chosen, output files are not validated [Default is to keep]") + parser_utility.add_argument("-B", "--remove_intermediate_bam", action="store_true", help="Remove intermediate mapped.sorted.bam.*. If this option is chosen, output files are not validated [Default is to keep]") + + # Assembler + parser_assembler = parser.add_argument_group('SPAdes arguments') + parser_assembler.add_argument("--run_metaplasmidspades", action="store_true", help="SPAdes | Run metaplasmidSPAdes. This may sacrifice MAG completeness for plasmid completeness. Will fail if there are no extrachromosomal contigs assembled.") + # parser_assembler.add_argument("--run_metaviralspades", action="store_true", help="SPAdes | Run metaviralSPAdes. This will result in a separete set of scaffolds.") + parser_assembler.add_argument("-m", "--memory", type=int, default=250, help="SPAdes | RAM limit in Gb (terminates if exceeded). [Default: 250]") + parser_assembler.add_argument("--spades_options", type=str, default="", help="SPAdes | More options (e.g. --arg 1 ) [Default: '']\nhttp://cab.spbu.ru/files/release3.11.1/manual.html") + + # Aligner + parser_aligner = parser.add_argument_group('Bowtie2 arguments') + parser_aligner.add_argument("--bowtie2_index_options", type=str, default="", help="bowtie2-build | More options (e.g. --arg 1 ) [Default: '']") + parser_aligner.add_argument("--bowtie2_options", type=str, default="", help="bowtie2 | More options (e.g. --arg 1 ) [Default: '']") + + # featureCounts + parser_featurecounts = parser.add_argument_group('featureCounts arguments') + parser_featurecounts.add_argument("--featurecounts_options", type=str, default="", help="featureCounts | More options (e.g. --arg 1 ) [Default: ''] | http://bioinf.wehi.edu.au/featureCounts/") + + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Threads + if opts.n_jobs == -1: + from multiprocessing import cpu_count + opts.n_jobs = cpu_count() + assert opts.n_jobs >= 1, "--n_jobs must be ≥ 1. To select all available threads, use -1." + + # Directories + directories = dict() + directories["project"] = create_directory(opts.project_directory) + directories["sample"] = create_directory(os.path.join(directories["project"], opts.name)) + directories["output"] = create_directory(os.path.join(directories["sample"], "output")) + directories["log"] = create_directory(os.path.join(directories["sample"], "log")) + if not opts.tmpdir: + opts.tmpdir = os.path.join(directories["sample"], "tmp") + directories["tmp"] = create_directory(opts.tmpdir) + directories["checkpoints"] = create_directory(os.path.join(directories["sample"], "checkpoints")) + directories["intermediate"] = create_directory(os.path.join(directories["sample"], "intermediate")) + os.environ["TMPDIR"] = directories["tmp"] + + # Info + print(format_header(__program__, "="), file=sys.stdout) + print(format_header("Configuration:", "-"), file=sys.stdout) + print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) + print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) + print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("Script version:", __version__, file=sys.stdout) + print("Moment:", get_timestamp(), file=sys.stdout) + print("Directory:", os.getcwd(), file=sys.stdout) + print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) + configure_parameters(opts, directories) + sys.stdout.flush() + + # Run pipeline + with open(os.path.join(directories["sample"], "commands.sh"), "w") as f_cmds: + pipeline = create_pipeline( + opts=opts, + directories=directories, + f_cmds=f_cmds, + ) + pipeline.compile() + pipeline.execute(restart_from_checkpoint=opts.restart_from_checkpoint) + +if __name__ == "__main__": + main() diff --git a/src/devel/index.py b/src/devel/index.py new file mode 100644 index 0000000..faac8a0 --- /dev/null +++ b/src/devel/index.py @@ -0,0 +1,594 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob +import pandas as pd +from genopype import * +from soothsayer_utils import * + +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2022.05.10" +# This version is build STAR index. The limiting factor here is getting an analog to exon in the prodigal generated GTF for this option -sjdbGTFfeatureExon + + +# ============== +# Agostic commands +# ============== + +# concatenate fasta +def get_concatenate_fasta_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + os.environ["TMPDIR"] = directories["tmp"] + # Command + cmd = [ + os.environ["concatenate_fasta.py"], + "-i {}".format(input_filepaths[0]), + "-o {}".format(output_directory), + "-m {}".format(opts.minimum_contig_length), + "-x {}".format("fa.gz"), + "-b reference", + "-M {}".format(opts.mode), + + + ] + return cmd + +# concatenate gene models +def get_concatenate_gff_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + os.environ["TMPDIR"] = directories["tmp"] + # Command + cmd = [ + os.environ["concatenate_gff.py"], + "-i {}".format(input_filepaths[0]), + "-o {}".format(output_directory), + "-x {}".format("gff"), + "-b reference", + "-M {}".format(opts.mode), + + ] + return cmd + +# ============== +# Local commands +# ============== +def get_bowtie2_local_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + os.environ["TMPDIR"] = directories["tmp"] + # Command + cmd = [ +""" + +for ID_SAMPLE in $(cut -f1 %s); + do %s --threads %d --seed %d %s/${ID_SAMPLE}/reference.fa.gz %s/${ID_SAMPLE}/reference.fa.gz + done +"""%( + opts.references, + os.environ["bowtie2-build"], + opts.n_jobs, + opts.random_state, + output_directory, + output_directory, + ), + ] + + return cmd + +# ============== +# Global commands +# ============== +def get_bowtie2_global_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + os.environ["TMPDIR"] = directories["tmp"] + # Command + cmd = [ + os.environ["bowtie2-build"], + "--threads {}".format(opts.n_jobs), + "--seed {}".format(opts.random_state), + opts.bowtie2_build_options, + input_filepaths[0], + input_filepaths[0], + ] + return cmd + +def get_star_global_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + # STAR --runThreadN 4 --runMode genomeGenerate --genomeSAindexNbases 12 --genomeDir . --genomeFastaFiles {} --sjdbOverhang 150 --sjdbGTFfile ${GTF} + os.environ["TMPDIR"] = directories["tmp"] + # Command + cmd = [ + "NUMBER_OF_CONTIGS=$(grep -c '^>' {})".format(input_filepaths[0]), + "&&", + "GENOME_SIZE=$(grep -v '^>' {} | wc -m)".format(input_filepaths[0]), + "&&", + 'AVERAGE_CONTIG_LENGTH=$(echo "${GENOME_SIZE} ${NUMBER_OF_CONTIGS}" | python -c "import sys; from math import log2; a, b = map(float, sys.stdin.read().split()); print(log2(a/b))")', + "&&", + 'echo "Number of contigs: ${NUMBER_OF_CONTIGS}"', + "&&", + 'echo "Metagenome size: ${GENOME_SIZE}"', + "&&", + 'echo "Average contig length: ${AVERAGE_CONTIG_LENGTH}" [Setting --genomeChrBinNbits to average contig length]', + "&&", + + os.environ["STAR"], + "--runMode genomeGenerate", + "--runThreadN {}".format(opts.n_jobs), + "--genomeChrBinNbits ${AVERAGE_CONTIG_LENGTH}", + "--sjdbOverhang {}".format(opts.read_length - 1), + "--genomeFastaFiles {}".format(input_filepaths[0]), + "--sjdbGTFfile {}".format( input_filepaths[1]), + + opts.star_index_options, + input_filepaths[0], + input_filepaths[0], + ] + return cmd + +# Pipeline +def create_local_pipeline(opts, directories, f_cmds): + + # ................................................................. + # Primordial + # ................................................................. + # Commands file + pipeline = ExecutablePipeline(name=__program__, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + + + # ========== + # concatenate fasta + # ========== + + step = 1 + + program = "concatenate_fasta" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories["output"]# = create_directory(os.path.join(directories["intermediate"], "concatenated")) + + + # Info + description = "Concatenate fasta files" + # i/o + input_filepaths = [ + opts.references, + ] + + output_filenames = [ + "*/reference.fa.gz", + "*/reference.saf", + + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_concatenate_fasta_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + # ======================= + # concatenate gene models + # ======================= + + step = 2 + + program = "concatenate_gff" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories["output"]# = create_directory(os.path.join(directories["intermediate"], "concatenated")) + + + # Info + description = "Concatenate gene models" + # i/o + input_filepaths = [ + opts.gene_models, + ] + + output_filenames = [ + "*/reference.gff", + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_concatenate_gff_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + # ========== + # Bowtie2 index + # ========== + step = 3 + + program = "bowtie2" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories["output"] #= create_directory(os.path.join(directories["intermediate"], "concatenated")) + + + # Info + description = "Build mapping index" + # i/o + input_filepaths = list( + map(lambda id_sample: os.path.join(directories["output"], id_sample, "reference.fa.gz"), + opts.samples, + ), + ) + + + output_filepaths = list( + map(lambda fp: "{}.*.bt2".format(fp), input_filepaths), + ) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_bowtie2_local_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + + return pipeline + +def create_global_pipeline(opts, directories, f_cmds): + # ................................................................. + # Primordial + # ................................................................. + # Commands file + pipeline = ExecutablePipeline(name=__program__, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + + + # ========== + # concatenate fasta + # ========== + + step = 1 + + program = "concatenate_fasta" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories["output"] #= create_directory(os.path.join(directories["intermediate"], "concatenated")) + + + # Info + description = "Concatenate fasta files" + # i/o + input_filepaths = [ + opts.references, + ] + + output_filenames = [ + "reference.fa.gz", + "reference.saf", + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_concatenate_fasta_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + # ========== + # concatenate gff + # ========== + + step = 2 + + program = "concatenate_gff" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories["output"] #= create_directory(os.path.join(directories["intermediate"], "concatenated")) + + + # Info + description = "Concatenate gff files" + # i/o + input_filepaths = [ + opts.gene_models, + ] + + output_filenames = [ + "reference.gff", + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_concatenate_gff_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + # ========== + # Bowtie2 index + # ========== + + step = 3 + + program = "bowtie2" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories["output"] #= create_directory(os.path.join(directories["intermediate"], "concatenated")) + + + # Info + description = "Build mapping index" + # i/o + input_filepaths = [ + os.path.join(directories["output"], "reference.fa.gz"), + ] + + output_filenames = [ + "reference.fa.gz.*.bt2", + ] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_bowtie2_global_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + # acceptable_returncodes= {0,1}, + + ) + + + return pipeline + +# ============ +# Run Pipeline +# ============ +# Set environment variables +def add_executables_to_environment(opts): + """ + Adapted from Soothsayer: https://github.com/jolespin/soothsayer + """ + accessory_scripts = set([ + "concatenate_fasta.py", + "concatenate_gff.py", + # "fasta_to_saf.py", + ]) + + + required_executables = set([ + "bowtie2-build", + "STAR", + ])| accessory_scripts + + if opts.path_config == "CONDA_PREFIX": + executables = dict() + for name in required_executables: + executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) + else: + if opts.path_config is None: + opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") + opts.path_config = format_path(opts.path_config) + assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) + assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) + df_config = pd.read_csv(opts.path_config, sep="\t") + assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) + df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) + # Get executable paths + executables = OrderedDict(zip(df_config["name"], df_config["executable"])) + assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) + + # Display + for name in sorted(accessory_scripts): + executables[name] = "python " + os.path.join(opts.script_directory, "scripts", name) + print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) + for name, executable in executables.items(): + if name in required_executables: + print(name, executable, sep = " --> ", file=sys.stdout) + os.environ[name] = executable.strip() + print("", file=sys.stdout) + +# Configure parameters +def configure_parameters(opts, directories): + df_references = pd.read_csv(opts.references, sep="\t", header=None) + df_gene_models = pd.read_csv(opts.gene_models, sep="\t", header=None) + + assert df_references.shape[1] == df_gene_models.shape[1], "--references and --gene_models must have the same number of columns" + + m = df_references.shape[1] + if opts.mode == "infer": + assert_acceptable_arguments(m, {1,2}) + opts.mode = {1:"global", 2:"local"}[m] + print("Inferring mode is {}".format(opts.mode), file=sys.stderr) + + if opts.mode == "global": + assert m == 1, "There should be only one column if mode='global'" + if opts.mode == "local": + assert m == 2, "There should be two columns if mode='local'" + if opts.mode == "local": + opts.samples = sorted(set(df_references.iloc[:,0])) + + + + # Set environment variables + add_executables_to_environment(opts=opts) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -o --heatmap_output ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser_io = parser.add_argument_group('Required I/O arguments') + parser_io.add_argument("-r","--references", type=str, required=True, help = "local mode: [id_sample][path/to/reference.fa] and global mode: [path/to/reference.fa]") + parser_io.add_argument("-g","--gene_models", type=str, required=True, help = "local mode: [id_sample][path/to/reference.gff] and global mode: [path/to/reference.gff]") + parser_io.add_argument("-o","--output_directory", type=str, default="veba_output/index", help = "path/to/project_directory [Default: veba_output/index]") + parser_io.add_argument("-m", "--minimum_contig_length", type=int, default=1500, help="Minimum contig length [Default: 1500]") + parser_io.add_argument("-M", "--mode", type=str, default="infer", help="Concatenate all references with global and build index or build index for each reference {global, local, infer}") + # parser_io.add_argument("-c", "--copy_files", action="store_true", help="Copy files instead of symlinking. Only applies to global.") + + # Utility + parser_utility = parser.add_argument_group('Utility arguments') + parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future + parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") + parser_utility.add_argument("--random_state", type=int, default=0, help = "Use -1 for completely random. Use 0 for consecutive random states. Use any other positive integer for the same random state for all iterations [Default: 0]") + parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") + # parser_utility.add_argument("--skip_concatenation", action="store_true", help="Skip concatenation step. Useful when references are concatenated before hand (e.g., already ran for bowtie2 but want STAR index as well)") + parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) + + # Utility + parser_bowtie2 = parser.add_argument_group('Bowtie2 Index arguments') + parser_bowtie2.add_argument("--bowtie2_build_options", type=str, default="", help="bowtie2-build | More options (e.g. --arg 1 ) [Default: '']") + + parser_star = parser.add_argument_group('STAR arguments') + parser_star.add_argument("--read_length", type=int, default=151, help = "Read length [Default: 151]") + parser_star.add_argument("--star_index_options", type=str, default="", help="bowtie2-build | More options (e.g. --arg 1 ) [Default: '']") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Directories + directories = dict() + directories["project"] = create_directory(opts.output_directory) + directories["output"] = create_directory(os.path.join(directories["project"], "output")) + directories["log"] = create_directory(os.path.join(directories["project"], "log")) + directories["tmp"] = create_directory(os.path.join(directories["project"], "tmp")) + directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) + os.environ["TMPDIR"] = directories["tmp"] + + # Info + print(format_header(__program__, "="), file=sys.stdout) + print(format_header("Configuration:", "-"), file=sys.stdout) + print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) + print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("Script version:", __version__, file=sys.stdout) + print("Moment:", get_timestamp(), file=sys.stdout) + print("Directory:", os.getcwd(), file=sys.stdout) + print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) + configure_parameters(opts, directories) + sys.stdout.flush() + + # Run pipeline + with open(os.path.join(directories["project"], "commands.sh"), "w") as f_cmds: + if opts.mode == "local": + pipeline = create_local_pipeline( + opts=opts, + directories=directories, + f_cmds=f_cmds, + ) + if opts.mode == "global": + pipeline = create_global_pipeline( + opts=opts, + directories=directories, + f_cmds=f_cmds, + ) + pipeline.compile() + pipeline.execute(restart_from_checkpoint=opts.restart_from_checkpoint) + + + +if __name__ == "__main__": + main() diff --git a/src/index.py b/src/index.py index e6cf70f..8f532d4 100755 --- a/src/index.py +++ b/src/index.py @@ -3,10 +3,11 @@ import sys, os, argparse, glob import pandas as pd from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.8" +__version__ = "2023.10.16" # ============== # Agostic commands @@ -554,6 +555,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/mapping.py b/src/mapping.py index 4daef1e..8db61cc 100755 --- a/src/mapping.py +++ b/src/mapping.py @@ -7,12 +7,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.15" +__version__ = "2023.10.16" # Bowtie2 @@ -553,6 +554,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/phylogeny.py b/src/phylogeny.py index 80ad34a..dbe90cd 100755 --- a/src/phylogeny.py +++ b/src/phylogeny.py @@ -8,12 +8,13 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.12" +__version__ = "2023.10.16" # Assembly def preprocess( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -156,6 +157,19 @@ def get_fasttree_cmd(input_filepaths, output_filepaths, output_directory, direct input_filepaths[0], ">", output_filepaths[0], + + "&&", + + os.environ["ete3"], + "view", + "-t", + output_filepaths[0], + "-i", + output_filepaths[1], + + + + ] return cmd @@ -175,6 +189,16 @@ def get_iqtree_cmd(input_filepaths, output_filepaths, output_directory, director "-bb {}".format(opts.iqtree_bootstraps), "-pre {}".format(os.path.join(output_directory, "output")), "--seed {}".format(opts.random_state), + + "&&", + + os.environ["ete3"], + "view", + "-t", + output_filepaths[0], + "-i", + output_filepaths[1], + ] return cmd @@ -213,6 +237,7 @@ def add_executables_to_environment(opts): "parallel", "fasttree", "iqtree", + "ete3", } | accessory_scripts @@ -398,7 +423,7 @@ def create_pipeline(opts, directories, f_cmds): input_filepaths = [ os.path.join(directories[("intermediate", "2__msa")], "concatenated_alignment.fasta"), ] - output_filenames = ["concatenated_alignment.fasttree.nw"] + output_filenames = ["concatenated_alignment.fasttree.nw", "concatenated_alignment.fasttree.nw.pdf"] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) params = { @@ -441,7 +466,7 @@ def create_pipeline(opts, directories, f_cmds): input_filepaths = [ os.path.join(directories[("intermediate", "2__msa")], "concatenated_alignment.fasta"), ] - output_filenames = ["output.treefile"] + output_filenames = ["output.treefile", "output.treefile.pdf"] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) params = { @@ -482,9 +507,15 @@ def create_pipeline(opts, directories, f_cmds): os.path.join(directories[("intermediate", "2__msa")], "alignment_table.boolean.tsv.gz"), os.path.join(directories[("intermediate", "2__msa")], "concatenated_alignment.fasta"), os.path.join(directories[("intermediate", "3__fasttree")], "concatenated_alignment.fasttree.nw"), + os.path.join(directories[("intermediate", "3__fasttree")], "concatenated_alignment.fasttree.nw.pdf"), + ] if not opts.no_iqtree: - input_filepaths += [os.path.join(directories[("intermediate", "4__iqtree")], "output.treefile")] + input_filepaths += [ + os.path.join(directories[("intermediate", "4__iqtree")], "output.treefile"), + os.path.join(directories[("intermediate", "4__iqtree")], "output.treefile.pdf"), + + ] output_filenames = map(lambda fp: fp.split("/")[-1], input_filepaths) @@ -604,6 +635,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/preprocess.py b/src/preprocess.py index dcc2d3b..d28ccc2 100755 --- a/src/preprocess.py +++ b/src/preprocess.py @@ -7,12 +7,14 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version + from soothsayer_utils import * import fastq_preprocessor __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.8" +__version__ = "2023.10.16" # ============ # Run Pipeline diff --git a/src/profile-pathway.py b/src/profile-pathway.py new file mode 100755 index 0000000..3f674f4 --- /dev/null +++ b/src/profile-pathway.py @@ -0,0 +1,643 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob, gzip +from collections import OrderedDict, defaultdict + +import pandas as pd + +# Soothsayer Ecosystem +from genopype import * +from genopype import __version__ as genopype_version +from soothsayer_utils import * + +pd.options.display.max_colwidth = 100 +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.10.16" + +DIAMOND_DATABASE_SUFFIX = "_v201901b.dmnd" + +# Preprocess reads +def get_preprocess_reads_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + # Command + if opts.input_reads_format == "paired": + cmd = [ + os.environ["repair.sh"], + "in1={}".format(opts.forward_reads), + "in2={}".format(opts.reverse_reads), + "out=stdout.fastq", + "|", + os.environ["bbmerge.sh"], + "minoverlap={}".format(opts.minimum_merge_overlap), + "int=t", + "in=stdin.fastq", + "out={}".format(os.path.join(output_directory, "joined.fastq.gz")), + opts.bbmerge_options, + + "&&", + + os.environ["seqkit"], + "stats", + "-T", + "-j {}".format(opts.n_jobs), + opts.forward_reads, + opts.reverse_reads, + os.path.join(output_directory, "joined.fastq.gz"), + ">", + os.path.join(output_directory, "reads.seqkit_stats.tsv"), + + ] + + if opts.input_reads_format == "bam": + cmd = [ + os.environ["samtools"], + "fastq", + "--threads {}".format(opts.n_jobs), + opts.bam, + "|", + os.environ["repair.sh"], + "in=stdin.fastq", + "int=t", + "out=stdout.fastq", + "|", + os.environ["bbmerge.sh"], + "minoverlap={}".format(opts.minimum_merge_overlap), + "int=t", + "in=stdin.fastq", + "out={}".format(os.path.join(output_directory, "joined.fastq.gz")), + opts.bbmerge_options, + + "&&", + + os.environ["seqkit"], + "stats", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "joined.fastq.gz"), + ">", + os.path.join(output_directory, "reads.seqkit_stats.tsv"), + ] + + if opts.input_reads_format == "joined": + cmd = [ + "echo", + '"[Skipping] Reads are already joined: {}"'.format(opts.joined_reads), + + "&&", + + os.environ["seqkit"], + "stats", + "-T", + "-j {}".format(opts.n_jobs), + opts.joined_reads, + ">", + os.path.join(output_directory, "reads.seqkit_stats.tsv"), + ] + + return cmd + +def get_preprocess_database_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + # Command + + if opts.database_format == "fasta": + cmd = [ + os.environ["diamond"], + "makedb", + "--threads {}".format(opts.n_jobs), + "--in {}".format(opts.fasta), + "--db {}".format(os.path.join(output_directory, "database{}".format(DIAMOND_DATABASE_SUFFIX))) + ] + + if opts.database_format == "diamond_database": + cmd = [ + "DST={}; SRC={}; SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST/{}".format( + output_directory, + opts.diamond_database, + "database{}".format(DIAMOND_DATABASE_SUFFIX), + ) + ] + + cmd += [ + + "&&", + + os.environ["diamond"], + "dbinfo", + "--threads {}".format(opts.n_jobs), + "--db {}".format(os.path.join(output_directory, "database{}".format(DIAMOND_DATABASE_SUFFIX))), + ">", + os.path.join(output_directory, "database{}.dbinfo.txt".format(DIAMOND_DATABASE_SUFFIX)), + ] + + return cmd + +def get_humann_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + # Command + cmd = [ + os.environ["humann"], + "-i {}".format(input_filepaths[0]), + "-o {}".format(output_directory), + "--threads {}".format(opts.n_jobs), + "-v", + "--search-mode {}".format(opts.search_mode), + "--memory-use {}".format(opts.humann_memory), + "--input-format {}".format("fastq.gz" if input_filepaths[0].endswith(".gz") else "fastq"), + "--bypass-nucleotide-search", + "--diamond {}".format(os.path.split(os.environ["diamond"])[0]), + "--evalue {}".format(opts.evalue), + "--protein-database {}".format(directories[("intermediate", "2__diamond_database")]), + "--translated-identity-threshold {}".format(opts.translated_identity_threshold) if opts.translated_identity_threshold else "", + "--translated-query-coverage-threshold {}".format(opts.translated_query_coverage_threshold), + "--translated-subject-coverage-threshold {}".format(opts.translated_subject_coverage_threshold), + "--output-basename humann", + "--pathways {}".format(opts.pathways), + "--id-mapping {}".format(opts.identifier_mapping), + "--o-log {}".format(os.path.join(output_directory, "humann.log")), + "--remove-column-description-output", + opts.humann_options, + + "&&", + + # humann_diamond_unaligned.fa + "mv", + os.path.join(output_directory, "humann_humann_temp", "humann_diamond_unaligned.fa"), + output_directory, + + "&&", + + # humann_diamond_aligned.tsv + "echo", # Write headers + "-e", + '"qseqid\tsseqid\tpident\tlength\tmismatch\tgapopen\tqstart\tqend\tsstart\tsend\tevalue\tbitscore"', + ">", + os.path.join(output_directory, "humann_diamond_aligned.tsv"), + + "&&", + + "cat", + os.path.join(output_directory, "humann_humann_temp", "humann_diamond_aligned.tsv"), + ">>", + os.path.join(output_directory, "humann_diamond_aligned.tsv"), #blast6 format + + "&&", + + "rm -rf", + os.path.join(output_directory, "humann_humann_temp"), + + "&&", + + "gzip", + os.path.join(output_directory, "humann.log"), + os.path.join(output_directory, "humann_diamond_aligned.tsv"), + os.path.join(output_directory, "humann_diamond_unaligned.fa"), + ] + + return cmd + + +# Symlink +def get_symlink_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + # Command + cmd = [ + "DST={}; (for SRC in {}; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + " ".join(input_filepaths), + ) + ] + return cmd + +# ============ +# Run Pipeline +# ============ +# Set environment variables +def add_executables_to_environment(opts): + """ + Adapted from Soothsayer: https://github.com/jolespin/soothsayer + """ + accessory_scripts = set([ + ] + ) + + required_executables={ + "bbmerge.sh", + "repair.sh", + "samtools", + "humann", + # "humann_rename_table", # humann_rename_table --input demo_fastq/rxn-cpm.tsv --output demo_fastq/rxn-cpm-named.tsv --names metacyc-rxn + "seqkit", + "diamond", + + } | accessory_scripts + + if opts.path_config == "CONDA_PREFIX": + executables = dict() + for name in required_executables: + executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) + else: + if opts.path_config is None: + opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") + opts.path_config = format_path(opts.path_config) + assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) + assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) + df_config = pd.read_csv(opts.path_config, sep="\t") + assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) + df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) + # Get executable paths + executables = OrderedDict(zip(df_config["name"], df_config["executable"])) + assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) + + # Display + for name in sorted(accessory_scripts): + executables[name] = "'{}'".format(os.path.join(opts.script_directory, "scripts", name)) # Can handle spaces in path + + print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) + for name, executable in executables.items(): + if name in required_executables: + print(name, executable, sep = " --> ", file=sys.stdout) + os.environ[name] = executable.strip() + print("", file=sys.stdout) + + +# Pipeline +def create_pipeline(opts, directories, f_cmds): + + # ................................................................. + # Primordial + # ................................................................. + # Commands file + pipeline = ExecutablePipeline(name=__program__, description=opts.name, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + + # ========== + # Preprocess reads + # ========== + + step = 1 + + # Info + program = "preprocess_reads" + program_label = "{}__{}".format(step, program) + description = "Preprocessing input reads" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # i/o + if opts.input_reads_format == "paired": + input_filepaths = [opts.forward_reads, opts.reverse_reads] + output_filepaths = [ + os.path.join(output_directory, "joined.fastq.gz"), + ] + + if opts.input_reads_format == "bam": + input_filepaths = [opts.bam] + output_filepaths = [ + os.path.join(output_directory, "joined.fastq.gz"), + ] + + if opts.input_reads_format == "joined": + input_filepaths = [opts.joined_reads] + output_filepaths = [opts.joined_reads] + + output_filepaths += [os.path.join(output_directory, "reads.seqkit_stats.tsv")] + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_preprocess_reads_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + # ========== + # Preprocess database + # ========== + + step = 2 + + # Info + program = "diamond_database" + program_label = "{}__{}".format(step, program) + description = "Preprocessing Diamond database" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # i/o + if opts.database_format == "fasta": + input_filepaths = [opts.fasta] + if opts.database_format == "diamond_database": + input_filepaths = [opts.diamond_database] + + output_filepaths = [ + os.path.join(output_directory, "database{}".format(DIAMOND_DATABASE_SUFFIX)), + os.path.join(output_directory, "database{}.dbinfo.txt".format(DIAMOND_DATABASE_SUFFIX)), + ] + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_preprocess_database_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + # ========== + # HUMAnN + # ========== + + step = 3 + + # Info + program = "humann" + program_label = "{}__{}".format(step, program) + description = "HUMAnN pathway profiling" + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # i/o + if opts.input_reads_format == "joined": + input_filepaths = [opts.joined_reads] + else: + input_filepaths = [os.path.join(directories[("intermediate", "1__preprocess_reads")], "joined.fastq.gz")] + input_filepaths += [ + os.path.join(directories[("intermediate", "2__diamond_database")], "database{}".format(DIAMOND_DATABASE_SUFFIX)), + opts.identifier_mapping, + ] + + + output_filenames = [ + "humann_pathabundance.tsv", + "humann_pathcoverage.tsv", + "humann_genefamilies.tsv", + "humann.log.gz", + "humann_diamond_unaligned.fa.gz", + "humann_diamond_aligned.tsv.gz", + ] + + output_filepaths = list(map(lambda fn:os.path.join(output_directory, fn), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_humann_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + + # ============= + # Symlink + # ============= + step = 4 + + # Info + program = "symlink" + program_label = "{}__{}".format(step, program) + description = "Symlinking relevant output files" + + # Add to directories + output_directory = directories["output"] + + # i/o + input_filepaths = [ + os.path.join(directories[("intermediate", "1__preprocess_reads")], "reads.seqkit_stats.tsv"), + os.path.join(directories[("intermediate", "3__humann")], "humann_pathabundance.tsv"), + os.path.join(directories[("intermediate", "3__humann")], "humann_pathcoverage.tsv"), + os.path.join(directories[("intermediate", "3__humann")], "humann_genefamilies.tsv"), + os.path.join(directories[("intermediate", "3__humann")], "humann_diamond_unaligned.fa.gz"), + os.path.join(directories[("intermediate", "3__humann")], "humann_diamond_aligned.tsv.gz"), + ] + + output_filenames = map(lambda fp: fp.split("/")[-1], input_filepaths) + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_symlink_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + return pipeline + +# Configure parameters +def configure_parameters(opts, directories): + # os.environ[] + + # --input_reads_format + assert_acceptable_arguments(opts.input_reads_format, {"paired", "joined", "bam", "auto"}) + if opts.input_reads_format == "auto": + if any([opts.forward_reads, opts.reverse_reads]): + assert opts.forward_reads != opts.reverse_reads, "You probably mislabeled the input files because `forward_reads` should not be the same as `reverse_reads`: {}".format(opts.forward_reads) + assert opts.forward_reads is not None, "If running in --input_reads_format paired mode, --forward_reads and --reverse_reads are needed." + assert opts.reverse_reads is not None, "If running in --input_reads_format paired mode, --forward_reads and --reverse_reads are needed." + opts.input_reads_format = "paired" + if opts.joined_reads is not None: + assert opts.forward_reads is None, "If running in --input_reads_format joined mode, you cannot provide --forward_reads, --reverse_reads, or --bam." + assert opts.reverse_reads is None, "If running in --input_reads_format joined mode, you cannot provide --forward_reads, --reverse_reads, or --bam." + assert opts.bam is None, "If running in --input_reads_format joined mode, you cannot provide --forward_reads, --reverse_reads, or --bam." + opts.input_reads_format = "joined" + if opts.bam is not None: + assert opts.forward_reads is None, "If running in --input_reads_format joined mode, you cannot provide --forward_reads, --reverse_reads, or --joined_reads." + assert opts.reverse_reads is None, "If running in --input_reads_format joined mode, you cannot provide --forward_reads, --reverse_reads, or --joined_reads." + assert opts.joined_reads is None, "If running in --input_reads_format joined mode, you cannot provide --forward_reads, --reverse_reads, or --joined_reads." + opts.input_reads_format = "bam" + print("Auto detecting reads format: {}".format(opts.input_reads_format), file=sys.stdout) + assert_acceptable_arguments(opts.input_reads_format, {"paired", "joined", "bam"}) + + # --search_mode + assert_acceptable_arguments(opts.search_mode, {"uniref50", "uniref90", "auto"}) + if opts.search_mode == "auto": + if opts.identifier_mapping.endswith(".gz"): + f = gzip.open(opts.identifier_mapping, "rt") + else: + f = open(opts.identifier_mapping, "r") + line = f.readline().strip('\n') + f.close() + + fields = line.split("\t") + id_uniref = fields[1] + assert id_uniref.startswith("UniRef"), "--identifier_mapping is not formatted correctly. The 2nd column should have UniRef identifiers: {}".format(id_uniref) + opts.search_mode = id_uniref.split("_")[0].lower() + assert_acceptable_arguments(opts.search_mode, {"uniref50", "uniref90"}) + + # --database and --fasta + assert any([opts.fasta, opts.diamond_database]), "Either --diamond_database (preferred) or --fasta must be provided but not both" + assert not all([opts.fasta, opts.diamond_database]), "Either --diamond_database (preferred) or --fasta must be provided but not both" + if opts.fasta: + opts.database_format = "fasta" + if opts.diamond_database: + opts.database_format = "diamond_database" + + # --pathways + assert_acceptable_arguments(opts.pathways, {"metacyc", "unipathway"}) + + # Set environment variables + add_executables_to_environment(opts=opts) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -1 -2 -n -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser_reads = parser.add_argument_group('Required reads arguments') + parser_reads.add_argument("-1","--forward_reads", type=str, help = "path/to/forward_reads.fq (Requires --reverse_reads, cannot be used with --joined_reads or --bam)") + parser_reads.add_argument("-2","--reverse_reads", type=str, help = "path/to/reverse_reads.fq (Requires --forward_reads, cannot be used with --joined_reads or --bam)") + parser_reads.add_argument("-j","--joined_reads", type=str, help = "path/to/joined_reads.fq (e.g., bbmerge.sh output) (Cannot be used with --forward_reads, --reverse_reads, or --bam)") + parser_reads.add_argument("-b","--bam", type=str, help = "path/to/mapped.sorted.bam file aligned to genomes (Cannot be used with --forward_reads, --reverse_reads, or --joined_reads)") + parser_reads.add_argument("-F", "--input_reads_format", type=str, default="auto", help = "Input reads format {paired, joined, bam} [Default: auto]") + + parser_database = parser.add_argument_group('Required database arguments') + parser_database.add_argument("-i", "--identifier_mapping", type=str, required=True, help = "Identifier mapping which includes [id_protein][id_uniref][length][lineage]. In VEBA, you can use `compile_custom_humann_database_from_annotations.py`. \nhttps://github.com/biobakery/humann#custom-reference-database-annotations ") + parser_database.add_argument("-f", "--fasta", type=str, help = "Protein fasta to build database") + parser_database.add_argument("-d","--diamond_database", type=str, help = "Diamond database with all proteins from --identifier_mapping") #! Future versions allow multiple databases + + + parser_io = parser.add_argument_group('Required I/O arguments') + parser_io.add_argument("-n", "--name", type=str, help="Name of sample", required=True) + parser_io.add_argument("-o","--project_directory", type=str, default="veba_output/profiling/pathways", help = "path/to/project_directory [Default: veba_output/profiling/pathways]") + + # Utility + parser_utility = parser.add_argument_group('Utility arguments') + parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future + parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") + parser_utility.add_argument("--random_state", type=int, default=0, help = "Random state [Default: 0]") + parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") + parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) + parser_utility.add_argument("--tmpdir", type=str, help="Set temporary directory") #site-packges in future + + # BBMerge + parser_bbmerge = parser.add_argument_group('bbmerge.sh arguments') + parser_bbmerge.add_argument("--minimum_merge_overlap", type=int, default=12, help="bbmerge.sh | Minimum number of overlapping bases to allow merging. [Default: 12]") + parser_bbmerge.add_argument("--bbmerge_options", type=str, default="", help="bbmerge.sh options (e.g. --arg 1 ) [Default: '']") + + # HUMAnN + parser_humann = parser.add_argument_group('HUMAnN arguments') + parser_humann.add_argument("--search_mode", type=str, default="auto", help="HUMAnN | Search for uniref50 or uniref90 gene families {uniref50, uniref90, auto} [Default: 'auto']") + parser_humann.add_argument("--pathways", type=str, default="metacyc", help="HUMAnN | The database to use for pathway computations {metacyc, unipathway} [Default: 'metacyc']") + parser_humann.add_argument("-e", "--evalue", type=float, default=1.0, help="HUMAnN | The evalue threshold to use with the translated search [Default: 1.0]") + parser_humann.add_argument("-m", "--translated_identity_threshold", type=float, help="HUMAnN | Identity threshold for translated alignments [Default: Tuned automatically (based on uniref mode) unless a custom value is specified]") + parser_humann.add_argument("-q", "--translated_query_coverage_threshold", type=float, default=90.0, help="HUMAnN | Query coverage threshold for translated alignments [Default: 90.0]") + parser_humann.add_argument("-s", "--translated_subject_coverage_threshold", type=float, default=50.0, help="HUMAnN | Subject coverage threshold for translated alignments [Default: 50.0]") + parser_humann.add_argument("--humann_memory", type=str, default="minimum", help="HUMAnN | Memory use mode {minimum, maximum} [Default: 'minimum']") + parser_humann.add_argument("--humann_options", type=str, default="", help="HUMAnN options (e.g. --arg 1 ) [Default: '']") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Threads + if opts.n_jobs == -1: + from multiprocessing import cpu_count + opts.n_jobs = cpu_count() + assert opts.n_jobs >= 1, "--n_jobs must be ≥ 1. To select all available threads, use -1." + + + # Directories + directories = dict() + directories["project"] = create_directory(opts.project_directory) + directories["sample"] = create_directory(os.path.join(directories["project"], opts.name)) + directories["output"] = create_directory(os.path.join(directories["sample"], "output")) + + directories["log"] = create_directory(os.path.join(directories["sample"], "log")) + if not opts.tmpdir: + opts.tmpdir = os.path.join(directories["sample"], "tmp") + directories["tmp"] = create_directory(opts.tmpdir) + directories["checkpoints"] = create_directory(os.path.join(directories["sample"], "checkpoints")) + directories["intermediate"] = create_directory(os.path.join(directories["sample"], "intermediate")) + os.environ["TMPDIR"] = directories["tmp"] + + # Info + print(format_header(__program__, "="), file=sys.stdout) + print(format_header("Configuration:", "-"), file=sys.stdout) + print(format_header("Name: {}".format(opts.name), "."), file=sys.stdout) + print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) + print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] + print("Script version:", __version__, file=sys.stdout) + print("Moment:", get_timestamp(), file=sys.stdout) + print("Directory:", os.getcwd(), file=sys.stdout) + print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) + configure_parameters(opts, directories) + sys.stdout.flush() + + # Run pipeline + with open(os.path.join(directories["sample"], "commands.sh"), "w") as f_cmds: + pipeline = create_pipeline( + opts=opts, + directories=directories, + f_cmds=f_cmds, + ) + pipeline.compile() + pipeline.execute(restart_from_checkpoint=opts.restart_from_checkpoint) + +if __name__ == "__main__": + main() diff --git a/src/scripts/antismash_genbanks_to_table.py b/src/scripts/antismash_genbanks_to_table.py index 9e15cc6..46637c7 100755 --- a/src/scripts/antismash_genbanks_to_table.py +++ b/src/scripts/antismash_genbanks_to_table.py @@ -1,12 +1,29 @@ #!/usr/bin/env python +from asyncio import protocols +from audioop import reverse import sys, os, argparse, gzip, glob -from collections import OrderedDict +from collections import OrderedDict, defaultdict import pandas as pd from Bio import SeqIO from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.1" +__version__ = "2023.9.17" + +def gc_content(seq): + seq = seq.upper() + number_of_gc = seq.count("G") + seq.count("C") + return number_of_gc/len(seq) + +# Reverse Complement + +def reverse_complement(sequence, conversion = str.maketrans('ACGTacgt','TGCAtgca')): + """ + Inputs a sequence of DNA and returns the reverse complement se$ + Outputs a reversed string of the input where A>T, T>A, G>C, an$ + """ + complement = sequence.translate(conversion) + return complement[::-1] def main(args=None): # Path info @@ -15,7 +32,7 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -i -o ".format(__program__) + usage = "{} -i -o ".format(__program__) epilog = "Copyright 2022 Josh L. Espinoza (jespinoz@jcvi.org)" # Parser @@ -23,13 +40,15 @@ def main(args=None): # Pipeline parser.add_argument("-i","--antismash_directory", type=str, help = "path/to/antismash_directory") parser.add_argument("-n","--name", type=str, help = "Genome name") - parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.tsv [Default: stdout]") - parser.add_argument("-e","--exclude_contig_edges", action="store_true", help = "Exclude clusters that are on contig edges") - parser.add_argument("-s","--synopsis", type=str, help = "Summary file output") - parser.add_argument("-t","--type_counts", type=str, help = "Type counts summary file output") - parser.add_argument("-f","--fasta_output", type=str, help = "Fasta file output") + parser.add_argument("-o","--output_directory", type=str, help = "path/to/output_directory [Default: --antismash_directory]") + parser.add_argument("-e","--exclude_contig_edges", action="store_true", help = "Exclude BGCs that are on contig edges") + parser.add_argument("--no_protein_fasta", type=str, help = "No protein fasta file output") + parser.add_argument("--no_nucleotide_fasta", type=str, help = "No nucleotide fasta file output") parser.add_argument("--sample", type=str, help = "Sample of origin") - parser.add_argument("--sep", type=str, default=" ", help = "Seperator between [id_gene][bgc_description]. The space makes it a id and description. If duplicate identifiers, you can separate with underscores (e.g., '__') [Default: ]") + parser.add_argument("--use_original_gene_ids", action="store_true", help = "Use original gene ids for the proteins in fasta/bgcs.faa.gz file") + parser.add_argument("-g", "--gzipped_genbanks", action="store_true", help = "Use if genbanks are gzipped") + + # parser.add_argument("--separator_in_protein_header", type=str, default=" ", help = "Seperator between [id_gene][bgc_description]. The space makes it a id and description. If duplicate identifiers, you can separate with underscores (e.g., '__') [Default: ]") # Options opts = parser.parse_args() @@ -37,11 +56,18 @@ def main(args=None): opts.script_filename = script_filename # Output - if opts.output == "stdout": - opts.output = sys.stdout + if not opts.output_directory: + opts.output_directory = opts.antismash_directory + + os.makedirs(opts.output_directory, exist_ok=True) # Wildcard genbank files - genbank_region_filepaths = glob.glob(os.path.join(opts.antismash_directory, "*region*.gbk")) + bgc_to_dna = dict() + if opts.gzipped_genbanks: + genbank_region_filepaths = glob.glob(os.path.join(opts.antismash_directory, "*region*.gbk.gz")) + else: + genbank_region_filepaths = glob.glob(os.path.join(opts.antismash_directory, "*region*.gbk")) + if len(genbank_region_filepaths): output = list() for fp in genbank_region_filepaths: @@ -51,28 +77,76 @@ def main(args=None): else: id_genome = fp.split("/")[-2] - id_region = fp.split(".")[-2] + if opts.gzipped_genbanks: + id_region = fp.split(".")[-3] + else: + id_region = fp.split(".")[-2] + + + # id_contig = fp[:-14] # Won't handle ids that have illegal characters + # Iterate through sequence records - for seq_record in SeqIO.parse(fp, "genbank"): + if opts.gzipped_genbanks: + f = gzip.open(fp, "rt") + else: + f = open(fp, "r") + for seq_record in SeqIO.parse(f, "genbank"): # Iterate through sequence features - id_contig = seq_record.id.strip() + id_contig = seq_record.name.strip() #seq_record.id.strip() corrupts the ID (https://github.com/antismash/antismash/issues/651) cds_features = list() product = None contig_edge = None + + id_bgc = "|".join([id_genome, id_contig, id_region]) + for feature in tqdm(seq_record.features, "Parsing genbank file: {}".format(fp), unit=" features"): - if feature.type == "CDS": - data = {"genome_id":id_genome, "region_id":id_region, "start":int(feature.location.start), "end":int(feature.location.end), "strand":feature.location.strand} - for k,v in feature.qualifiers.items(): - if isinstance(v,list): - if len(v) == 1: - v = v[0] - data[k] = v - cds_features.append(pd.Series(data)) - elif feature.type == "region": + start = int(feature.location.start) + end = int(feature.location.end) + strand = {1:"+", -1:"-"}[feature.location.strand] + + if feature.type == "region": product = feature.qualifiers["product"][0] contig_edge = feature.qualifiers["contig_edge"][0] + + + sequence = str(seq_record.seq)[start:end] + if strand == "-": + sequence = reverse_complement(sequence) + bgc_to_dna[id_bgc] = sequence + else: + if feature.type == "CDS": + data = {"genome_id":id_genome, "contig_id":id_contig, "region_id":id_region, "start":start, "end":end, "strand":strand} + for k,v in feature.qualifiers.items(): + + if isinstance(v,list): + if len(v) == 1: + v = v[0] + + data[k] = v + cds_features.append(pd.Series(data)) + + df = pd.DataFrame(cds_features) - + + # Fix missing values on "allorfs" genes predicted within antiSMASH that are not in GFF + if "locus_tag" in df.columns: + locus_tags_with_allorf = df["locus_tag"].dropna() + index_with_allorf = locus_tags_with_allorf.index[locus_tags_with_allorf.map(lambda x: x.startswith("allorf"))] + + # Excluding the allorfs, which gene name fields are in the genbank + if not index_with_allorf.empty: + df_noallorfs = df.drop(index_with_allorf, axis=0).dropna(how="all", axis=1) + gene_name_columns = df_noallorfs.loc[:,list(set(df_noallorfs.columns) & set([ "Name", "ID", "locus_tag", "protein_id", "gene", "gene_id"]))].columns + # missing_gene_name_columns = + for i, row in df.loc[index_with_allorf].iterrows(): + id_allorf = row["locus_tag"] + _, start, end = id_allorf.split("_") + # strand = {"1":"+", "-1":"-"}[str(row["strand"])] + strand = row["strand"] + id_gene = "{}_antiSMASH-{}:{}({})".format(id_contig, start, end, strand) + for id_field in gene_name_columns: + df.loc[i,id_field] = id_gene + # NCBI Genbank # Add gene id preferentially if one doesn't exist: gene_id, Name, ID, and locus_tag in that order. if "gene_id" not in df.columns: @@ -87,121 +161,190 @@ def main(args=None): genes = df["protein_id"].strip() elif "gene" in df.columns: genes = df["gene"].strip() - + assert genes is not None, "Cannot identify gene identifiers for contig: {}".format(id_contig) df["gene_id"] = genes - # If no contig identifiers, add it. This prioritizes gff fields. - if "contig_id" not in df.columns: - df["contig_id"] = id_contig.strip() - df.insert(0, "bgc_type", product) + # # If no contig identifiers, add it. This prioritizes gff fields. + # if "contig_id" not in df.columns: + # df["contig_id"] = id_contig.strip() + + df.insert(0, "protocluster_type", product) df.insert(1, "cluster_on_contig_edge", contig_edge) + output.append(df) - df_bgcs = pd.concat(output, axis=0).sort_values(["genome_id", "contig_id", "start", "end"]) + + f.close() + + df_components = pd.concat(output, axis=0).sort_values(["genome_id", "contig_id", "start", "end"]) + for field in ["genome_id", "contig_id", "region_id", "gene_id"]: - df_bgcs[field] = df_bgcs[field].map(lambda x: x.strip().replace(" ","")) + df_components[field] = df_components[field].map(lambda x: x.strip().replace(" ","")) - # for field in ["start","end"]: - # df_bgcs[field] = df_bgcs[field].astype(int) + for field in ["start","end"]: + df_components[field] = df_components[field].astype(int) - df_bgcs = df_bgcs.set_index(["genome_id", "contig_id", "region_id", "gene_id"]).sort_index() - df_bgcs["translation"] = df_bgcs.pop("translation") + df_components = df_components.set_index(["genome_id", "contig_id", "region_id", "gene_id"]).sort_index() + df_components["translation"] = df_components.pop("translation") # Add position on BGC i_to_position = OrderedDict() - for (id_genome, id_contig, id_region), df in tqdm(df_bgcs.groupby(["genome_id", "contig_id", "region_id"])): + for (id_genome, id_contig, id_region), df in df_components.groupby(["genome_id", "contig_id", "region_id"]): df = df.sort_values(["start", "end"]) d = dict(zip(df.index, range(1, df.shape[0] + 1))) i_to_position.update(d) i_to_position = pd.Series(i_to_position).astype(int) - j = df_bgcs.columns.get_loc("start") - df_bgcs.insert(loc=j, column="position_in_bgc", value=i_to_position) + j = df_components.columns.get_loc("start") + df_components.insert(loc=j, column="position_in_bgc", value=i_to_position) # BGC ID i_to_bgc = OrderedDict() - for (id_genome, id_contig, id_region), df in tqdm(df_bgcs.groupby(["genome_id", "contig_id", "region_id"])): + for (id_genome, id_contig, id_region), df in df_components.groupby(["genome_id", "contig_id", "region_id"]): id_bgc = "|".join([id_genome, id_contig, id_region]) d = dict(zip(df.index, [id_bgc]*df.shape[0])) i_to_bgc.update(d) i_to_bgc = pd.Series(i_to_bgc) - df_bgcs.insert(loc=0, column="bgc_id", value=i_to_bgc) + df_components.insert(loc=0, column="bgc_id", value=i_to_bgc) + + # BGC to number of components + bgc_to_numberofcomponents = df_components["bgc_id"].value_counts() + + # BGC to contig edge + bgc_to_contigedge = dict(zip(df_components["bgc_id"], df_components["cluster_on_contig_edge"])) # Component i_to_component = OrderedDict() - for i, row in df_bgcs.iterrows(): - id_component = "{}_{}|{}-{}({})".format(*row[["bgc_id", "position_in_bgc", "start", "end"]], {1:"+",-1:"-"}[row["strand"]]) + for i, row in df_components.iterrows(): + id_component = "{}_{}|{}:{}({})".format(*row[["bgc_id", "position_in_bgc", "start", "end"]], row["strand"]) i_to_component[i] = id_component i_to_component = pd.Series(i_to_component) - df_bgcs.insert(loc=1, column="component_id", value=i_to_component) + df_components.insert(loc=1, column="component_id", value=i_to_component) # Add sample of origin if provided if opts.sample: - df_bgcs["sample_of_origin"] = opts.sample + df_components["sample_of_origin"] = opts.sample + # Protocluster-type + # ================== + # With BGCs on edge + tmp = df_components.reset_index().set_index(["genome_id", "contig_id", "region_id", "protocluster_type"]).index.unique().value_counts() + + value_counts = defaultdict(int) + for (id_genome, _, _, protocluster_type), count in tmp.items(): + value_counts[(id_genome, protocluster_type)] += 1 + value_counts = pd.Series(value_counts) + + df_typecounts_with_edges = pd.DataFrame([[id_genome, protocluster_type, count] for ((id_genome, protocluster_type), count) in value_counts.items()], columns=["id_genome", "protocluster_type", "number_of_bgcs"]).set_index(["id_genome", "protocluster_type"]).sort_values("number_of_bgcs", ascending=False) - # df_bgcs = df_bgcs.set_index(drop=False) + # With only BGCs not on edge + tmp = df_components.loc[~df_components["cluster_on_contig_edge"].map(eval).values].reset_index().set_index(["genome_id", "contig_id", "region_id", "protocluster_type"]).index.unique().value_counts() + value_counts = defaultdict(int) + for (id_genome, _, _, protocluster_type), count in tmp.items(): + value_counts[(id_genome, protocluster_type)] += 1 + value_counts = pd.Series(value_counts) + df_typecounts_noedges = pd.DataFrame([[id_genome, protocluster_type, count] for ((id_genome, protocluster_type), count) in value_counts.items()], columns=["id_genome", "protocluster_type", "number_of_bgcs(not_on_edge)"]).set_index(["id_genome", "protocluster_type"]).sort_values("number_of_bgcs(not_on_edge)", ascending=False) - if opts.type_counts: - tmp = df_bgcs.reset_index().set_index(["genome_id", "contig_id", "region_id", "bgc_type"]).index.unique().value_counts() - value_counts = tmp.groupby(lambda x: (x[0], x[3])).sum() - df_typecounts_with_edges = pd.DataFrame([[*x[0], x[1]] for x in value_counts.items()], columns=["id_genome", "bgc_type", "number_of_bgcs"]).set_index(["id_genome", "bgc_type"]).sort_values("number_of_bgcs", ascending=False) + # Merging + df_type_counts = pd.concat([df_typecounts_with_edges, df_typecounts_noedges], axis=1).fillna(0).astype(int) - tmp = df_bgcs.loc[~df_bgcs["cluster_on_contig_edge"].map(eval).values].reset_index().set_index(["genome_id", "contig_id", "region_id", "bgc_type"]).index.unique().value_counts() - value_counts = tmp.groupby(lambda x: (x[0], x[3])).sum() - df_typecounts_noedges = pd.DataFrame([[*x[0], x[1]] for x in value_counts.items()], columns=["id_genome", "bgc_type", "number_of_bgcs(not_on_edge)"]).set_index(["id_genome", "bgc_type"]).sort_values("number_of_bgcs(not_on_edge)", ascending=False) + # Add sample of origin if provided + if opts.sample: + df_type_counts["sample_of_origin"] = opts.sample + + df_type_counts.to_csv(os.path.join(opts.output_directory, "bgc_protocluster-types.tsv.gz"), sep="\t") - df_type_counts = pd.concat([df_typecounts_with_edges, df_typecounts_noedges], axis=1).fillna(0).astype(int) + # Output BGC summary + # ================== + df = df_components.reset_index(drop=False).set_index("bgc_id") - # Add sample of origin if provided - if opts.sample: - df_synopsis["sample_of_origin"] = opts.sample - - df_type_counts.to_csv(opts.type_counts, sep="\t") + df_bgcs = df_components.reset_index(drop=False).set_index("component_id")["bgc_id"].value_counts().to_frame("number_of_genes") + for field in ["genome_id", "contig_id", "region_id", "protocluster_type", "cluster_on_contig_edge"]: + df_bgcs[field] = pd.Series(df[field].to_dict()) + df_bgcs.index.name = "bgc_id" - if opts.synopsis: - df = df_bgcs.reset_index(drop=False).set_index("bgc_id") + fields = ['genome_id', 'contig_id', 'region_id', 'protocluster_type','cluster_on_contig_edge', 'number_of_genes'] + df_bgcs = df_bgcs.loc[:,fields] + + # Add sample of origin if provided + if opts.sample: + df_bgcs["sample_of_origin"] = opts.sample - df_synopsis = df_bgcs.reset_index(drop=False).set_index("component_id")["bgc_id"].value_counts().to_frame("number_of_genes") - for field in ["genome_id", "contig_id", "region_id", "bgc_type", "cluster_on_contig_edge"]: - df_synopsis[field] = pd.Series(df[field].to_dict()) - df_synopsis.index.name = "bgc_id" + # Exclude contig edges + if opts.exclude_contig_edges: + df_bgcs = df_bgcs.loc[~df_bgcs["cluster_on_contig_edge"].map(eval).values] - fields = ['genome_id', 'contig_id', 'region_id', 'bgc_type','cluster_on_contig_edge', 'number_of_genes'] - df_synopsis = df_synopsis.loc[:,fields] - - # Add sample of origin if provided - if opts.sample: - df_synopsis["sample_of_origin"] = opts.sample - df_synopsis.to_csv(opts.synopsis, sep="\t") + print("Number of BGCs:", df_bgcs.shape[0], file=sys.stderr) + df_bgcs.to_csv(os.path.join(opts.output_directory, "identifier_mapping.bgcs.tsv.gz"), sep="\t") + # Output components summary + # ========================= # Exclude contig edges if opts.exclude_contig_edges: - df_bgcs = df_bgcs.loc[~df_bgcs["cluster_on_contig_edge"].map(eval).values] + df_components = df_components.loc[~df_components["cluster_on_contig_edge"].map(eval).values] + + print("Number of components:", df_components.shape[0], file=sys.stderr) + df_components.reset_index(drop=False).set_index("component_id").to_csv(os.path.join(opts.output_directory, "identifier_mapping.components.tsv.gz"), sep="\t") + + # Fasta (Protein) + # =============== + if not opts.no_protein_fasta: + os.makedirs(os.path.join(opts.output_directory, "fasta"), exist_ok=True) - # Output - df_bgcs.reset_index(drop=False).set_index(["bgc_id", "component_id"]).to_csv(opts.output, sep="\t") + fp = os.path.join(opts.output_directory, "fasta", "components.faa.gz") + + f = gzip.open(fp, "wt") + + if opts.use_original_gene_ids: + for (id_genome, id_contig, id_region, id_gene), row in tqdm(df_components.iterrows(), "Writing genes to fasta file: {}".format(fp), total=df_components.shape[0]): + id_component = row["component_id"] + seq = row["translation"] + + header = "{}{}{}".format( + id_gene, + opts.separator_in_protein_header, + id_component, + ) + print(">{}\n{}".format(header, seq), file=f) - # Fasta - if opts.fasta_output: - if opts.fasta_output.endswith(".gz"): - f = gzip.open(opts.fasta_output, "wt") else: - f = open(opts.fasta_output, "w") - for (id_genome, id_contig, id_region, id_gene), row in tqdm(df_bgcs.iterrows(), "Writing genes to fasta file: {}".format(opts.fasta_output)): - id_component = row["component_id"] - seq = row["translation"] - - header = "{}{}{}".format( - id_gene, - opts.sep, - id_component, + for (id_genome, id_contig, id_region, id_gene), row in tqdm(df_components.iterrows(), "Writing genes to fasta file: {}".format(fp), total=df_components.shape[0]): + id_component = row["component_id"] + seq = row["translation"] + + header = "{} {}".format( + id_component, + id_gene, + ) + print(">{}\n{}".format(header, seq), file=f) + f.close() + + # Fasta (Nucleotide) + # ========== + if not opts.no_nucleotide_fasta: + os.makedirs(os.path.join(opts.output_directory, "fasta"), exist_ok=True) + fp = os.path.join(opts.output_directory, "fasta", "bgcs.fasta.gz") + + f = gzip.open(fp, "wt") + + for (id_bgc, seq) in tqdm(bgc_to_dna.items(), "Writing nucleotides to fasta file: {}".format(fp)): + + description = "len={};gc={:.3};n_genes={};edge={}".format( + len(seq), + gc_content(seq), + bgc_to_numberofcomponents[id_bgc], + bgc_to_contigedge[id_bgc], + ) + + header = "{} {}".format( + id_bgc, + description, ) print(">{}\n{}".format(header, seq), file=f) f.close() else: - print("No BGC regions detected", file=sys.stderr) + print("No BGC regions detected: {}".format(opts.antismash_directory), file=sys.stderr) if __name__ == "__main__": main() diff --git a/src/scripts/bgc_novelty_scorer.py b/src/scripts/bgc_novelty_scorer.py index 3713be0..b6d2cc2 100755 --- a/src/scripts/bgc_novelty_scorer.py +++ b/src/scripts/bgc_novelty_scorer.py @@ -3,7 +3,7 @@ import pandas as pd __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.7.11" +__version__ = "2023.9.15" def main(args=None): # Path info @@ -20,8 +20,8 @@ def main(args=None): # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') parser_io.add_argument("-d","--diamond", type=str, required=True, help = "path/to/homology.tsv.gz") - parser_io.add_argument("-c","--components", type=str, required=True, help = "path/to/bgc.components.tsv.gz") - parser_io.add_argument("-s","--synopsis", type=str, required=True, help = "path/to/bgc.synopsis.tsv.gz") + parser_io.add_argument("-c","--components", type=str, required=True, help = "path/to/identifier_mapping.components.tsv.gz") + parser_io.add_argument("-b","--bgcs", type=str, required=True, help = "path/to/identifier_mapping.bgcs.tsv.gz") parser_io.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.tsv [Default: stdout]") parser_thresholds = parser.add_argument_group('Novelty score threshold arguments') @@ -41,12 +41,12 @@ def main(args=None): # Load tables df_components = pd.read_csv(opts.components, sep="\t", index_col=None) - df_synopsis = pd.read_csv(opts.synopsis, sep="\t", index_col=0) + df_bgcs = pd.read_csv(opts.bgcs, sep="\t", index_col=0) df_diamond = pd.read_csv(opts.diamond, sep="\t", index_col=0, header=[0,1]) # Gene -> BGC - gene_to_bgc = dict(zip(df_components["gene_id"], df_components["bgc_id"])) + component_to_bgc = dict(zip(df_components["component_id"], df_components["bgc_id"])) # MiBIG df_mibig = df_diamond["MiBIG"].dropna(how="all", axis=0) @@ -55,10 +55,10 @@ def main(args=None): df_mibig = df_mibig.query("scovhsp >= {}".format(opts.scovhsp)) df_mibig = df_mibig.query("evalue <= {}".format(opts.evalue)) - bgc_to_mibignhits = pd.Series([0]*df_synopsis.shape[0], df_synopsis.index) - bgc_to_mibignhits.update(df_mibig.index.map(lambda x: gene_to_bgc[x]).value_counts()) - df_synopsis["number_of_mibig_hits"] = bgc_to_mibignhits - df_synopsis["novelty_score"] = 1 - df_synopsis["number_of_mibig_hits"]/df_synopsis["number_of_genes"] + bgc_to_mibignhits = pd.Series([0]*df_bgcs.shape[0], df_bgcs.index) + bgc_to_mibignhits.update(df_mibig.index.map(lambda x: component_to_bgc[x]).value_counts()) + df_bgcs["number_of_mibig_hits"] = bgc_to_mibignhits + df_bgcs["novelty_score"] = 1 - df_bgcs["number_of_mibig_hits"]/df_bgcs["number_of_genes"] # VFDB df_vfdb = df_diamond["VFDB"].dropna(how="all", axis=0) @@ -67,13 +67,13 @@ def main(args=None): df_vfdb = df_vfdb.query("scovhsp >= {}".format(opts.scovhsp)) df_vfdb = df_vfdb.query("evalue <= {}".format(opts.evalue)) - bgc_to_vfdbnhits = pd.Series([0]*df_synopsis.shape[0], df_synopsis.index) - bgc_to_vfdbnhits.update(df_vfdb.index.map(lambda x: gene_to_bgc[x]).value_counts()) - df_synopsis["number_of_vfdb_hits"] = bgc_to_mibignhits - df_synopsis["virulence_ratio"] = df_synopsis["number_of_vfdb_hits"]/df_synopsis["number_of_genes"] + bgc_to_vfdbnhits = pd.Series([0]*df_bgcs.shape[0], df_bgcs.index) + bgc_to_vfdbnhits.update(df_vfdb.index.map(lambda x: component_to_bgc[x]).value_counts()) + df_bgcs["number_of_vfdb_hits"] = bgc_to_mibignhits + df_bgcs["virulence_ratio"] = df_bgcs["number_of_vfdb_hits"]/df_bgcs["number_of_genes"] # Output - df_synopsis.to_csv(opts.output, sep="\t") + df_bgcs.to_csv(opts.output, sep="\t") if __name__ == "__main__": main() diff --git a/src/scripts/compile_core_pangenome_table.py b/src/scripts/compile_core_pangenome_table.py new file mode 100755 index 0000000..47dade5 --- /dev/null +++ b/src/scripts/compile_core_pangenome_table.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob +from collections import defaultdict +import numpy as np +import pandas as pd +from tqdm import tqdm + +pd.options.display.max_colwidth = 100 +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.10.3" + + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + + parser.add_argument("-i","--global_clustering_directory", type=str, default="veba_output/cluster/output/global", help = "path/to/global_clustering_directory [Default: veba_output/cluster/output/global/]") + parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/core_pangenomes_table.tsv [Default: stdout]") + # parser.add_argument("-t","--organism_type", type=str, default="infer", help = "organism type [Default: infer]") + # parser.add_argument("--genome_fasta_extension", default="fa", type=str, help = "File extension. Don't include period/fullstop/. [Default: fa]") + # parser.add_argument("--protein_fasta_extension", default="faa", type=str, help = "File extension. Include the period/fullstop/. [Default: faa]") + parser.add_argument("-a", "--absolute", action="store_true", help = "Use absolute paths instead of relative paths") + parser.add_argument("-e", "--allow_missing_files", action="store_true", help = "Allow missing files") + parser.add_argument("--volume_prefix", type=str, help = "Docker container prefix to volume path") + parser.add_argument("--header", action="store_true", help = "Write header") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + output = defaultdict(dict) + # Build table from binning directory + for fp in tqdm(glob.glob(os.path.join(opts.global_clustering_directory, "pangenome_core_sequences", "*")), "Reading files in {}".format(opts.global_clustering_directory)): + + id_genome_cluster = ".".join(fp.split("/")[-1].split(".")[:-1]) + + if fp.endswith(".faa"): + output[id_genome_cluster]["proteins"] = fp + if fp.endswith(".ffn"): + output[id_genome_cluster]["cds"] = fp + + + df_output = pd.DataFrame(output).reindex([ "proteins", "cds"]).T.sort_index() + assert not df_output.empty, "Did not find any matches in the following directory: {}".format(opts.global_clustering_directory) + df_output.index.name = "id_genome_cluster" + + # Check missing values + if not opts.allow_missing_files: + assert not np.any(df_output.isnull()), "Missing files detected. Check error or allow with --allow_missing_files: \n{}".format(df_output.loc[df_output.isnull().sum(axis=1) > 0].to_string()) + + # Absolute paths + if opts.absolute: + df_output = df_output.applymap(lambda fp: os.path.abspath(fp)) + + # Docker volume prefix + if opts.volume_prefix: + df_output = df_output.applymap(lambda fp: os.path.join(opts.volume_prefix, fp) if pd.notnull(fp) else fp) + + if opts.output == "stdout": + opts.output = sys.stdout + + df_output.to_csv(opts.output, sep="\t", header=bool(opts.header)) + +if __name__ == "__main__": + main() diff --git a/src/scripts/compile_custom_humann_database_from_annotations.py b/src/scripts/compile_custom_humann_database_from_annotations.py new file mode 100755 index 0000000..a644bb5 --- /dev/null +++ b/src/scripts/compile_custom_humann_database_from_annotations.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +from __future__ import print_function, division +from email import header +import sys, os, argparse, gzip +from collections import defaultdict +import numpy as np +import pandas as pd +from tqdm import tqdm +from Bio.SeqIO.FastaIO import SimpleFastaParser + +pd.options.display.max_colwidth = 100 +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.10.11" + + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -a -t -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser.add_argument("-i","--identifier_mapping", default="stdin", type=str, help = "path/to/identifier_mapping.tsv[.gz] [id_protein][id_genome] (No header) [Default: stdin]") + parser.add_argument("-a","--annotations", type=str, required=True, help = "path/to/annotations.tsv[.gz] Output from annotations.py. Multi-level header that contains (UniRef, sseqid)") + parser.add_argument("-t","--taxonomy", type=str, required=True, help = "path/to/taxonomy.tsv[.gz] [id_genome][classification] (No header). Use output from `merge_taxonomy_classifications.py` with --no_header and --no_domain") + parser.add_argument("-s","--sequences", type=str, required=True, help = "path/to/proteins.fasta[.gz]") + parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/humann_uniref_annotations.tsv[.gz] [Default: stdout]") + parser.add_argument("--sep", default=";", help = "Separator for taxonomic levels [Default: ;]") + # parser.add_argument("--mandatory_taxonomy_prefixes", help = "Comma-separated values for mandatory prefix levels. (e.g., 'c__,f__,g__,s__')") + # parser.add_argument("--discarded_file", help = "Proteins that have been discarded due to incomplete lineage") + parser.add_argument("-g", "--no_append_genome_identifier", action="store_true", help = "Don't add genome to taxonomic lineage") + parser.add_argument("--genome_prefix", type=str, default="t__", help = "Taxonomic level prefix for genome") + parser.add_argument("--header", action="store_true", help = "Write header") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + if opts.identifier_mapping == "stdin": + opts.identifier_mapping = sys.stdin + + if opts.output == "stdout": + opts.output = sys.stdout + + # Proteins to genomes + protein_to_genome = pd.read_csv(opts.identifier_mapping, sep="\t", index_col=0, header=None).iloc[:,0] + A1 = set(protein_to_genome.index) + A2= set(protein_to_genome.values) + print("--identifier_mapping", opts.identifier_mapping, file=sys.stderr) + print(" * {} proteins".format(len(A1)), file=sys.stderr) + print(" * {} genomes".format(len(A2)), file=sys.stderr) + + # Annotations + df_annotations = pd.read_csv(opts.annotations, sep="\t", index_col=0, header=[0,1]) + assert "UniRef" in df_annotations.columns.get_level_values(0), "--annotations must have a 2 level header (i.e., Pandas MultiIndex with 2 levels) where the first level has 'UniRef' as created by `annotate.py`" + df_annotations = df_annotations["UniRef"] + protein_to_uniref = df_annotations["sseqid"].dropna() + B1 = set(protein_to_uniref.index) + B2 = set(protein_to_uniref.values) + + print("--annotations", opts.annotations, file=sys.stderr) + print(" * {} proteins".format(len(B1)), file=sys.stderr) + print(" * {} UniRef hits".format(len(B2)), file=sys.stderr) + # Taxonomy + genome_to_taxonomy = pd.read_csv(opts.taxonomy, sep="\t", index_col=0, header=None).iloc[:,0] + C1 = set(genome_to_taxonomy.index) + C2 = set(genome_to_taxonomy.values) + + print("--taxonomy", opts.taxonomy, file=sys.stderr) + print(" * {} genomes".format(len(C1)), file=sys.stderr) + print(" * {} taxonomic classifications".format(len(C2)), file=sys.stderr) + + if opts.sequences.endswith(".gz"): + f = gzip.open(opts.sequences, "rt") + else: + f = open(opts.sequences, "r") + + protein_to_length = dict() + for header, seq in tqdm(SimpleFastaParser(f), "Calculating length of proteins: {}".format(opts.sequences), unit=" Proteins"): + id = header.split(" ")[0] + protein_to_length[id] = len(seq) + protein_to_length = pd.Series(protein_to_length) + D = set(protein_to_length.index) + + # Checks + assert A1 >= B1, "Not all proteins in --annotations are in --identifier_mapping." + assert A2 == C1, "Genomes in --identifier_mapping do not match genomes in --taxonomy.\n\nThe following genomes are specific to --identifier_mapping: {}\n\nThe following genomes are specific to --taxonomy".format("\n".join(A2 - C1), "\n".join(C1 - A2)) + assert B1 <= D, "Not all proteins in --annotations are in --sequences." + + # Append genome to taxonomy + if not opts.no_append_genome_identifier: + tmp = dict() + for id_genome, taxonomy in genome_to_taxonomy.items(): + tmp[id_genome] = "{}{}{}{}".format(taxonomy, opts.sep, opts.genome_prefix, id_genome) + genome_to_taxonomy = pd.Series(tmp) + + + + # id_protein, uniref_hit, len, lineage + df_output = protein_to_uniref.to_frame("UniRef") + df_output["Length"] = protein_to_length[protein_to_uniref.index] + df_output["Taxonomy"] = protein_to_uniref.index.map(lambda id_protein: genome_to_taxonomy[protein_to_genome[id_protein]]) + df_output.index.name = "id_protein" + + + # if opts.discarded_file: + + df_output.to_csv(opts.output, sep="\t", header=bool(opts.header)) + +if __name__ == "__main__": + main() diff --git a/src/scripts/compile_genomes_table.py b/src/scripts/compile_genomes_table.py index 6158492..718dd93 100755 --- a/src/scripts/compile_genomes_table.py +++ b/src/scripts/compile_genomes_table.py @@ -9,7 +9,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.8.28" +__version__ = "2023.10.3" def main(args=None): @@ -19,23 +19,22 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -i -b cleaned > ".format(__program__) + usage = "{} -i -o ".format(__program__) epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" # Parser parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) # Pipeline - parser_io = parser.add_argument_group('[Mode 1] Preprocess Directory arguments') - parser_io.add_argument("-i","--binning_directory", type=str, help = "path/to/binning_directory (e.g., veba_output/binning)") - parser_io.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.tsv [Default: stdout]") - # parser_io.add_argument("-t","--organism_type", type=str, default="infer", help = "organism type [Default: infer]") - # parser_io.add_argument("--genome_fasta_extension", default="fa", type=str, help = "File extension. Don't include period/fullstop/. [Default: fa]") - # parser_io.add_argument("--protein_fasta_extension", default="faa", type=str, help = "File extension. Include the period/fullstop/. [Default: faa]") - parser_io.add_argument("-a", "--absolute", action="store_true", help = "Use absolute paths instead of relative paths") - parser_io.add_argument("-e", "--allow_missing_files", action="store_true", help = "Allow missing files") - parser_io.add_argument("--volume_prefix", type=str, help = "Docker container prefix to volume path") - parser_io.add_argument("--header", action="store_true", help = "Write header") + parser.add_argument("-i","--binning_directory", type=str, default = "veba_output/binning", help = "path/to/binning_directory [Default: veba_output/binning]") + parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/genomes_table.tsv [Default: stdout]") + # parser.add_argument("-t","--organism_type", type=str, default="infer", help = "organism type [Default: infer]") + # parser.add_argument("--genome_fasta_extension", default="fa", type=str, help = "File extension. Don't include period/fullstop/. [Default: fa]") + # parser.add_argument("--protein_fasta_extension", default="faa", type=str, help = "File extension. Include the period/fullstop/. [Default: faa]") + parser.add_argument("-a", "--absolute", action="store_true", help = "Use absolute paths instead of relative paths") + parser.add_argument("-e", "--allow_missing_files", action="store_true", help = "Allow missing files") + parser.add_argument("--volume_prefix", type=str, help = "Docker container prefix to volume path") + parser.add_argument("--header", action="store_true", help = "Write header") # Options opts = parser.parse_args() @@ -43,23 +42,24 @@ def main(args=None): opts.script_filename = script_filename output = defaultdict(dict) + # Build table from binning directory - if opts.binning_directory: - for fp in tqdm(glob.glob(os.path.join(opts.binning_directory, "*", "*", "output", "genomes", "*")), "Reading files in {}".format(opts.binning_directory)): - organism_type = fp.split("/")[-5] - id_sample = fp.split("/")[-4] - id_mag = ".".join(fp.split("/")[-1].split(".")[:-1]) + for fp in tqdm(glob.glob(os.path.join(opts.binning_directory, "*", "*", "output", "genomes", "*")), "Reading files in {}".format(opts.binning_directory)): + organism_type = fp.split("/")[-5] + id_sample = fp.split("/")[-4] + id_mag = ".".join(fp.split("/")[-1].split(".")[:-1]) - if fp.endswith(".fa"): - output[(organism_type, id_sample, id_mag)]["genome"] = fp - if fp.endswith(".faa"): - output[(organism_type, id_sample, id_mag)]["proteins"] = fp - if fp.endswith(".ffn"): - output[(organism_type, id_sample, id_mag)]["cds"] = fp - if fp.endswith(".gff"): - output[(organism_type, id_sample, id_mag)]["gene_models"] = fp + if fp.endswith(".fa"): + output[(organism_type, id_sample, id_mag)]["genome"] = fp + if fp.endswith(".faa"): + output[(organism_type, id_sample, id_mag)]["proteins"] = fp + if fp.endswith(".ffn"): + output[(organism_type, id_sample, id_mag)]["cds"] = fp + if fp.endswith(".gff"): + output[(organism_type, id_sample, id_mag)]["gene_models"] = fp - df_output = pd.DataFrame(output).reindex([ "genome", "proteins", "cds", "gene_models"]).T + df_output = pd.DataFrame(output).reindex([ "genome", "proteins", "cds", "gene_models"]).T.sort_index() + assert not df_output.empty, "Did not find any matches in the following directory: {}".format(opts.binning_directory) df_output.index.names = ["organism_type", "id_sample", "id_mag"] diff --git a/src/scripts/compile_krona.py b/src/scripts/compile_krona.py index a4673d5..0657639 100755 --- a/src/scripts/compile_krona.py +++ b/src/scripts/compile_krona.py @@ -7,7 +7,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.9.5" +__version__ = "2023.10.1" def main(args=None): @@ -100,8 +100,8 @@ def main(args=None): # if opts.remove_genome_column: # df_output = df_output.drop("genome_id", axis=1) - df_output = df_input.reset_index().loc[:,["number_of_bgcs", "bgc_type","id_genome"]] - df_output.columns = [ "count", "bgc_type","id_genome"] + df_output = df_input.reset_index().loc[:,["number_of_bgcs", "protocluster_type", "id_genome"]] + df_output.columns = [ "count", "protocluster_type", "id_genome"] if opts.mode == "biosynthetic-local": # if opts.remove_incomplete: @@ -112,8 +112,8 @@ def main(args=None): # if opts.remove_genome_column: # df_output = df_output.drop("genome_id", axis=1) - df_output = df_input.reset_index().loc[:,[ "number_of_bgcs", "bgc_type"]] - df_output.columns = ["count", "bgc_type"] + df_output = df_input.reset_index().loc[:,[ "number_of_bgcs", "protocluster_type"]] + df_output.columns = ["count", "protocluster_type"] df_output.sort_values("count", ascending=False).to_csv(opts.output, sep="\t", header=None, index=None) diff --git a/src/scripts/compile_prokaryotic_genome_cluster_classification_scores_table.py b/src/scripts/compile_prokaryotic_genome_cluster_classification_scores_table.py index 863c915..1e4a031 100755 --- a/src/scripts/compile_prokaryotic_genome_cluster_classification_scores_table.py +++ b/src/scripts/compile_prokaryotic_genome_cluster_classification_scores_table.py @@ -4,7 +4,7 @@ import pandas as pd __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.9.11" +__version__ = "2023.9.27" def main(argv=None): # Path info @@ -54,7 +54,8 @@ def main(argv=None): B = set(genome_to_genomecluster.index) C = A & B D = (A | B) - C - assert A == B, "Not all genomes overlap between --gtdbtk_results and --clusters.\n * Genomes only in --gtdbtk_results: {}\n * Genomes only in clusters: {}".format(A - B, B - A) + assert A <= B, "Not all genomes from --gtdbtk_results are in --clusters.\n * Genomes only in --gtdbtk_results: {}\n".format(A - B) + genome_to_genomecluster = genome_to_genomecluster.loc[df_gtdbtk_results.index] # Get classifications and weight for genomes genome_to_weight = dict() diff --git a/src/scripts/compile_protein_cluster_prevalence_table.py b/src/scripts/compile_protein_cluster_prevalence_table.py index 84183e4..03309b5 100755 --- a/src/scripts/compile_protein_cluster_prevalence_table.py +++ b/src/scripts/compile_protein_cluster_prevalence_table.py @@ -7,7 +7,7 @@ pd.options.display.max_colwidth = 100 __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.5.18" +__version__ = "2023.9.15" def main(args=None): @@ -34,12 +34,17 @@ def main(args=None): parser.add_argument("-i","--input", type=str, default="stdin", help = "path/to/input.tsv [id_genome][id_protein][id_protein-cluster](No header) [Default: stdin]") parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/output.tsv [Default: stdout]") parser.add_argument("-b","--boolean", action="store_true", help = "Return True/False instead of integer counts") + parser.add_argument("--dtype", type=str, default="bool", help = "Dtype for boolean output {bool, int} [Default: bool]") + parser.add_argument("-c", "--columns_name", type=str, default="id_protein-cluster", help = "Columns name [Default: id_protein-cluster]") + parser.add_argument("-r", "--rows_name", type=str, default="id_genome", help = "Rows name [Default: id_genome]") # Options opts = parser.parse_args() opts.script_directory = script_directory opts.script_filename = script_filename + assert opts.dtype in {"bool", "int"}, "--bool must be either {bool, int}" + # I/O if opts.input == "stdin": opts.input = sys.stdin @@ -61,11 +66,13 @@ def main(args=None): # Create output df_output = pd.DataFrame(A, index=genomes, columns=clusters) - df_output.index.name = "id_genome" - df_output.columns.name = "id_protein-cluster" + df_output.index.name = opts.rows_name + df_output.columns.name = opts.columns_name if opts.boolean: df_output = df_output > 0 + if opts.dtype == "int": + df_output = df_output.astype(int) df_output.to_csv(opts.output, sep="\t") diff --git a/src/scripts/devel/compile_veba_synopsis.py b/src/scripts/devel/compile_veba_synopsis.py new file mode 100755 index 0000000..a7a4d7f --- /dev/null +++ b/src/scripts/devel/compile_veba_synopsis.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob +from collections import defaultdict +import numpy as np +import pandas as pd +from tqdm import tqdm + +pd.options.display.max_colwidth = 100 +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.2.9" + + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + + parser.add_argument("-i","--veba_directory", type=str, help = "path/to/hmmsearch_tblout.tsv", required=True) + parser.add_argument("-o","--output_directory", type=str, default="veba_output/synopsis", help = "Output directory [Default: veba_output/synopsis]") + parser.add_argument("-a", "--include_assembly", action="store_true", help="Output query identifiers only") + parser.add_argument("-b", "--include_assembly", action="store_true", help="Output query identifiers only") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + + +if __name__ == "__main__": + main() diff --git a/src/scripts/devel/plot_reads_preprocessing.py b/src/scripts/devel/plot_reads_preprocessing.py new file mode 100644 index 0000000..3eb3a9a --- /dev/null +++ b/src/scripts/devel/plot_reads_preprocessing.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, re +from collections import defaultdict, OrderedDict +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns + +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.8.8" + +# Parse Basename +def parse_basename(query: str, naming_scheme: str): # "[ID]_R[DIRECTION]_001.fastq.gz" + """ + Adapted from the following source: + * @s-b + * https://stackoverflow.com/questions/73296040/how-to-parse-out-two-fields-out-of-string-using-regex-in-python + """ + def repl(match_object): + inside_bracket = match_object.group(1) + if inside_bracket == "DIRECTION": + return r"(?P[12])" + if inside_bracket == "ID": + return r"(?P[-.\w]+)" + pattern = re.sub(r"\[(.*?)\]", repl, naming_scheme) + match = re.match(pattern, query) + return match["ID"], match["DIRECTION"] + + +def concatenate_seqkit_stats_dataframes(filepaths): + """ + Input: Path(s) to at least one TSV file. + Output: Merged input TSV files as a DataFrame. + """ + + # Check if at least one TSV file is provided + if not filepaths: + raise Excecption("At least 1 seqkit stats TSV file is required") + # assert len(filepaths) > 0, "At least 1 seqkit stats TSV file is required" + + # Initialize an empty list to store dataframes + dataframes = [] + + for fp in filepaths: + df = pd.read_csv(fp, sep='\t', index_col=0) + assert "num_seqs" in df.columns, "Please ensure that input filepaths are from `seqkit stats -a -T`" + dataframes.append(df) + # except (FileNotFoundError: # This will be handled by pd.read_csv + + # Concatenate all dataframes into a single dataframe + df_concat = pd.concat(dataframes, axis=0) # This will catch errors on empty lists where there's no dataframes to concatenate. Also good to be explicit w/ which axis you are concatenating along for readability + + return df_concat + +# Format seqkit stats output +def format_read_preprocessing( + number_of_sequences:pd.Series, + naming_scheme, + sort_by="samples", + ascending=True, + ): + assert sort_by in {"total", "trimmed", "discarded", "samples"} + + output = defaultdict(dict) + + # Format read prepocessing + for fp, number_of_reads in number_of_sequences.items(): + # From preprocessing module (i.e., fastq_preprocessor) + if "intermediate" in fp: + if "_1.fastq.gz" in fp: + id_sample = fp.split("/")[-4] + fn = fp.split("/")[-1].split("_1.fastq.gz")[0] + output[id_sample][fn] = number_of_reads + else: + # Unprocessed reads + basename = fp.split("/")[-1] + id_sample, direction = parse_basename(basename, naming_scheme=naming_scheme) + if str(direction) == "1": + output[id_sample]["unprocessed"] = number_of_reads + + df_read_summary = pd.DataFrame(output).T + df_read_summary.index.name = "id_sample" + + # Add discarded reads + df_read_summary["discarded"] = df_read_summary["unprocessed"] - df_read_summary["trimmed"] + + if sort_by == "samples": + df_read_summary = df_read_summary.sort_index(ascending=ascending) + if sort_by == "total": + df_read_summary = df_read_summary.sort_values("unprocessed", ascending=ascending) + if sort_by == "trimmed": + df_read_summary = df_read_summary.sort_values("trimmed", ascending=ascending) + if sort_by == "discarded": + df_read_summary = df_read_summary.sort_values("discarded", ascending=ascending) + + return df_read_summary + + +# (base) [jespinoz@login01 TestVEBA]$ cat veba_output/preprocess/S1/output/seqkit_stats.concatenated.tsv +# file format type num_seqs sum_len min_len avg_len max_len +# veba_output/preprocess/S1/intermediate/3__bbduk/kmer_hits_1.fastq.gz FASTQ DNA 2903 436935 81 150.5 151 +# veba_output/preprocess/S1/intermediate/3__bbduk/kmer_hits_2.fastq.gz FASTQ DNA 2903 436849 81 150.5 151 +# veba_output/preprocess/S1/intermediate/3__bbduk/non-kmer_hits_1.fastq.gz FASTQ DNA 1414483 212887364 75 150.5 151 +# veba_output/preprocess/S1/intermediate/3__bbduk/non-kmer_hits_2.fastq.gz FASTQ DNA 1414483 212888714 75 150.5 151 +# Fastq/S1_1.fastq.gz FASTQ DNA 1474203 221880932 75 150.5 151 +# Fastq/S1_2.fastq.gz FASTQ DNA 1460132 219765860 75 150.5 151 +# veba_output/preprocess/S1/intermediate/1__fastp/trimmed_1.fastq.gz FASTQ DNA 1417386 213324299 75 150.5 151 +# veba_output/preprocess/S1/intermediate/1__fastp/trimmed_2.fastq.gz FASTQ DNA 1417386 213325563 75 150.5 151 +# veba_output/preprocess/S1/intermediate/1__fastp/trimmed_singletons.fastq.gz FASTQ DNA 84846 12748323 75 150.3 151 +# veba_output/preprocess/S1/intermediate/2__bowtie2/cleaned_1.fastq.gz FASTQ DNA 1417386 213324299 75 150.5 151 +# veba_output/preprocess/S1/intermediate/2__bowtie2/cleaned_2.fastq.gz FASTQ DNA 1417386 213325563 75 150.5 151 +# veba_output/preprocess/S1/intermediate/2__bowtie2/cleaned_singletons.fastq.gz 0 0 0 0.0 0 +# veba_output/preprocess/S1/intermediate/2__bowtie2/contaminated_1.fastq.gz 0 0 0 0.0 0 +# veba_output/preprocess/S1/intermediate/2__bowtie2/contaminated_2.fastq.gz 0 0 0 0.0 0 +# veba_output/preprocess/S1/intermediate/2__bowtie2/contaminated_singletons.fastq.gz 0 0 0 0.0 0 + + + +def format_colors(ran_bowtie2=False, ran_bbduk=False, palette="pastel", reverse_color_order=False): + """ + Scheme: + Trimmed + Discarded = Unprocessed + Cleaned + Contaminated = Trimmed + Kmer hits + Non-kmer hits = Cleaned + """ + + labels = None + + colors = sns.color_palette(palette=palette, n_colors=4) + + if reverse_color_order: + colors = colors[::-1] + + label_to_color = pd.Series(dict(zip(["discarded", "contaminated", "kmer_hits", "non-kmer_hits"], colors))) + + if not any([ + ran_bowtie2, + ran_bbduk, + ]): + labels = ["discarded", "trimmed"] + + if all([ + ran_bowtie2, + not ran_bbduk, + ]): + labels = ["discarded", "contaminated", "cleaned"] + + + if all([ + not ran_bowtie2, + ran_bbduk, + ]): + labels = ["discarded", "kmer_hits", "non-kmer_hits"] + + + if all([ + ran_bowtie2, + ran_bbduk, + ]): + labels = ["discarded", "contaminated", "kmer_hits", "non-kmer_hits"] + + assert labels is not None + + return label_to_color[labels].to_dict(into=OrderedDict) + +def format_proportions(df:pd.DataFrame, percentage=False, ran_bowtie2=False, ran_bbduk=False): + """ + Scheme: + Trimmed + Discarded = Unprocessed + Cleaned + Contaminated = Trimmed + Kmer hits + Non-kmer hits = Cleaned + """ + assert set(df.columns) >= {"discarded", "trimmed", "unprocessed"}, "Please ensure the `seqkit stats -a -T` tables provided are from either VEBA's preprocessing module or fastq_preprocessor. User provided the following columns in DataFrame: {}".format(", ".join(df.columns)) + + df_proportions = pd.DataFrame() + + if not any([ + ran_bowtie2, + ran_bbduk, + ]): + df_proportions["trimmed"] = df["trimmed"]/df["unprocessed"] + df_proportions["discarded"] = df["discarded"]/df["unprocessed"] + + if all([ + ran_bowtie2, + not ran_bbduk, + ]): + df_proportions["cleaned"] = df["cleaned"]/df["trimmed"] + df_proportions["contaminated"] = df["contaminated"]/df["trimmed"] + df_proportions["discarded"] = df["contaminated"]/df["unprocessed"] + + if all([ + not ran_bowtie2, + ran_bbduk, + ]): + df_proportions["discarded"] = df["discarded"]/df["unprocessed"] + df_proportions["kmer_hits"] = df["kmer_hits"]/df["trimmed"] + df_proportions["non-kmer_hits"] = df["non-kmer_hits"]/df["trimmed"] + + if all([ + ran_bowtie2, + ran_bbduk, + ]): + df_proportions["discarded"] = df["discarded"]/df["unprocessed"] + df_proportions["kmer_hits"] = df["kmer_hits"]/df["cleaned"] + df_proportions["non-kmer_hits"] = df["non-kmer_hits"]/df["cleaned"] + df_proportions["contaminated"] = df["contaminated"]/df["trimmed"] + + assert not df_proportions.empty, "Please ensure the `seqkit stats -a -T` tables provided are from either VEBA's preprocessing module or fastq_preprocessor. User provided the following columns in DataFrame: {}".format(", ".join(df.columns)) + + if percentage: + df_proportions = df_proportions * 100 + total = df_proportions.sum(axis=1) + assert np.allclose(total.values, np.asarray([100]*total).size), "Percentages don't add up to 100%" + else: + total = df_proportions.sum(axis=1) + assert np.allclose(total.values, np.asarray([1.0]*total).size), "Proportions don't add up to 1.0" + + return df_proportions + +def plot_read_preprocessing( + df:pd.DataFrame, + percentage=False, + kmer_hits_relabel=None, + nonkmer_hits_relabel="other", + + palette="pastel", + border_color="white", + reverse_color_order=False, + figsize=(13,5), + title=None, + style="seaborn-white", + + show_xgrid=True, + show_ygrid=True, + show_legend=True, + ylabel="Number of reads", + xlabel="Samples", + legend_kws=dict(), + legend_title=None, + + title_kws=dict(), + barchart_kws=dict(), + legend_title_kws=dict(), + axis_label_kws=dict(), + fig_kws=dict(), + + ): + """ + Input: A DataFrame that is meant to be plotted, as well as a version of it containing proportional data. + Output: 3 barcharts of the data in the input DataFrame. + + Scheme: + Trimmed + Discarded = Unprocessed + Cleaned + Contaminated = Trimmed + Kmer hits + Non-kmer hits = Cleaned + """ + + _fig_kws = {"figsize":figsize} + _fig_kws.update(fig_kws) + _title_kws = {"fontsize":14, "fontweight":"bold"} + _title_kws.update(title_kws) + _barchart_kws ={"edgecolor":border_color} + _barchart_kws.update(barchart_kws) + _legend_kws = {'fontsize': 10}#, 'loc': 'center left', 'bbox_to_anchor': (1, 0.5)} + _legend_kws.update(legend_kws) + _legend_title_kws = {"size":12, "weight":"bold"} + _legend_title_kws.update(legend_title_kws) + _axis_label_kws = {"fontsize":14} + _axis_label_kws.update(axis_label_kws) + + # Checks + ran_bowtie2 = "contaminated" in df.columns #True/False + ran_bbduk = all([ #True/False + "kmer_hits" in df.columns, + "non-kmer_hits" in df.columns, + ]) + + # Make a copy so if and/or when we relabel the dataframe it doesn't change the original outside of the function + df = df.copy() + + # Get a color label dictionary + label_to_color = format_colors(ran_bowtie2=ran_bowtie2, ran_bbduk=ran_bbduk, palette=palette, reverse_color_order=reverse_color_order) + + # Get proportions + df_proportions = format_proportions(df, percentage=percentage, ran_bowtie2=ran_bowtie2, ran_bbduk=ran_bbduk) + + if kmer_hits_relabel is not None: + df.columns = df.columns.map(lambda column_name: kmer_hits_relabel if column_name == "kmer_hits" else column_name) + df_proportions.columns = df_proportions.columns.map(lambda column_name: kmer_hits_relabel if column_name == "kmer_hits" else column_name) + + if nonkmer_hits_relabel is not None: + df.columns = df.columns.map(lambda column_name: nonkmer_hits_relabel if column_name == "non-kmer_hits" else column_name) + df_proportions.columns = df_proportions.columns.map(lambda column_name: nonkmer_hits_relabel if column_name == "non-kmer_hits" else column_name) + + # Create the figure/axes for subplots and set the color palette + with plt.style.context(style): + fig, axes = plt.subplots(**_fig_kws, nrows=3) + + + + + + + # # Plot the first subplot (counts data) + # barchart_df.plot(kind='bar', stacked=True, ax=axes[0]) + # axes[0].set_title('Preprocessing Summary', fontsize=15) + # axes[0].set_ylabel('Counts', fontsize=13) + # axes[0].set_xticklabels([]) + + # # Plot the second subplot (log transformed counts data) + # barchart_df.plot(kind='bar', stacked=True, ax=axes[1]) + # axes[1].set_ylabel('Log Counts', fontsize=13) + # axes[1].set_xticklabels([]) + # axes[1].set_yscale(LogScale(axis=0,base=10)) + # axes[1].get_legend().remove() + + # # Plot the third subplot (proportions) + # barchart_df_proportions.plot(kind='bar', stacked=True, ax=axes[2]) + # axes[2].set_ylabel('Proportions', fontsize=13) + # axes[2].get_legend().remove() + + # # Remove the X-axis labels + # for ax in axes: + # ax.set_xlabel('') + + # # Adjust the spacing between subplots, legend, and X-label + # plt.subplots_adjust(hspace=0.1) + # axes[0].legend(title='Categories', loc='upper left', bbox_to_anchor=(1, 1)) + # plt.xlabel('Samples', fontsize=13) + + + + return fig, axes, df_proportions + +# +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -t -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser.add_argument("-i","--input", default="stdin", nargs="+", type=str, help = "path/to/seqkit_stats.tsv. Can be multiple files or one file [Default: stdin]") + parser.add_argument("-o","--output", required=True, type=str, help = "path/to/figure.[pdf|png|svg]") + parser.add_argument("-c","--color_palette", default="pastel", help = "Color palette [Default: pastel]") + parser.add_argument("-s","--figsize", type=str, default="13,5", help = "Comma separated string width,height [Default: 13,5]") + parser.add_argument("-t","--tick_fontsize", type=str, default="12", help = "xtick fontsize [Default: 12]") + parser.add_argument("-l","--label_fontsize", type=str, default="14", help = "label fontsize [Default: 14]") + parser.add_argument("-n","--naming_scheme", default="[ID]_R[DIRECTION]_001.fastq.gz", type=str, help = "Naming scheme. Use [ID] for identifier name and [DIRECTION] for 1 or 2. [Default: [ID]_R[DIRECTION]_001.fastq.gz]") + parser.add_argument("-p","--percentage", action = "store_true", help = "Use percentages instead of proportions") # Set the default naming scheme and help message + parser.add_argument("--sort_by", type=str, default="samples", help = "Sort samples by {total, trimmed, discarded, samples} [Default: samples]") + # parser.add_argument("-m", "--mode", type=str, default="short", help = "Mode for fastq {short, long} [Default: short]") # Throw an error if long reads are selected. Say it will be implemented in the future. + + # Options + opts = parser.parse_args() + + opts.script_directory = script_directory + opts.script_filename = script_filename + + # I/O + if opts.input == "stdin": + opts.input = sys.stdin + df_seqkit_stats = pd.read_csv(opts.input, sep="\t", index_col=0) + assert "num_seqs" in df_seqkit_stats.columns, "Please ensure that input is from `seqkit stats -a -T`" + else: + df_seqkit_stats = concatenate_seqkit_stats_dataframes(opts.input) + + assert df_seqkit_stats.index.nunique() == df_seqkit_stats.shape[0] + + # For read preprocessing table + df_read_summary = format_read_preprocessing(df_seqkit_stats["num_seqs"], naming_scheme=opts.naming_scheme, sort_by=opts.sort_by) + + fig, ax, df_proportions = plot_read_preprocessing( + df_read_summary, + percentage=opts.percentage, + # The rest of the kwargs + ) + fig.savefig(opts.output, dpi=300, bbox_inches="tight") + + # Output table to stdout + df_read_summary.columns = df_read_summary.columns.map(lambda x: ("counts", x)) + if opts.percentage: + df_proportions.columns = df_proportions.columns.map(lambda x: ("percentages", x)) + else: + df_proportions.columns = df_proportions.columns.map(lambda x: ("proportions", x)) + + df_output = pd.concat([df_read_summary, df_proportions], axis=1) + df_output.to_csv(sys.stdout, sep="\t") + +if __name__ == "__main__": + main() diff --git a/src/scripts/devel/prokaryotic_gene_modeling_wrapper.py b/src/scripts/devel/prokaryotic_gene_modeling_wrapper.py new file mode 100755 index 0000000..8740247 --- /dev/null +++ b/src/scripts/devel/prokaryotic_gene_modeling_wrapper.py @@ -0,0 +1,1474 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob, shutil +from collections import OrderedDict, defaultdict + +import pandas as pd +import numpy as np + +# Soothsayer Ecosystem +from genopype import * +from soothsayer_utils import * + +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.8.28" + +# Pyrodigal +def get_pyrodigal_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + cmd = [ + "cat", + input_filepaths[0], + "|", + os.environ["seqkit"], + "seq", + "-m {}".format(opts.minimum_contig_length), + ">", + os.path.join(directories["tmp"], "tmp.fasta"), + + "&&", + + os.environ["pyrodigal"], + "-p meta", + "-i {}".format(os.path.join(directories["tmp"], "tmp.fasta")), + "-g {}".format(opts.pyrodigal_genetic_code), + "-f gff", + "-d {}".format(os.path.join(output_directory, "gene_models.ffn")), + "-a {}".format(os.path.join(output_directory, "gene_models.faa")), + "--min-gene {}".format(opts.pyrodigal_minimum_gene_length), + "--min-edge-gene {}".format(opts.pyrodigal_minimum_edge_gene_length), + "--max-overlap {}".format(opts.pyrodigal_maximum_gene_overlap_length), + # "-j {}".format(opts.n_jobs), + ">", + os.path.join(directories["tmp"], "tmp.gff"), + + "&&", + + "cat", + os.path.join(directories["tmp"], "tmp.gff"), + "|", + os.environ["append_geneid_to_prodigal_gff.py"], + "-a gene_id", + ">", + os.path.join(output_directory, "gene_models.gff"), + + "&&", + + "rm", + os.path.join(directories["tmp"], "tmp.*") + + ] + return cmd + + +# Prodigal +def get_prodigal_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + "cat", + os.path.join(input_filepaths[0], "*.fa"), + "|", + os.environ["prodigal-gv"], + "-p meta", + "-g {}".format(opts.prodigal_genetic_code), + "-f gff", + "-d {}".format(os.path.join(output_directory, "gene_models.ffn")), + "-a {}".format(os.path.join(output_directory, "gene_models.faa")), + "|", + os.environ["append_geneid_to_prodigal_gff.py"], + "-a gene_id", + ">", + os.path.join(output_directory, "gene_models.gff"), + + "&&", + + os.environ["partition_gene_models.py"], + "-i {}".format(input_filepaths[1]), + "-g {}".format(os.path.join(output_directory, "gene_models.gff")), + "-d {}".format(os.path.join(output_directory, "gene_models.ffn")), + "-a {}".format(os.path.join(output_directory, "gene_models.faa")), + "-o {}".format(os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes")), + +""" + +OUTPUT_DIRECTORY={} + +for GENOME_FASTA in {}; +do + ID=$(basename $GENOME_FASTA .fa) + DIR_GENOME=$(dirname $GENOME_FASTA) + GFF_CDS=$DIR_GENOME/$ID.gff + GFF_OUTPUT=$OUTPUT_DIRECTORY/$ID.gff + >$GFF_OUTPUT.tmp + {} -f $GENOME_FASTA -o $GFF_OUTPUT.tmp -n $ID -c $GFF_CDS -d Virus + mv $GFF_OUTPUT.tmp $GFF_OUTPUT +done + +""".format( + os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes"), + os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes", "*.fa"), + os.environ["compile_gff.py"], + ), + + + "rm -rf", + os.path.join(output_directory, "gene_models.gff"), + os.path.join(output_directory, "gene_models.ffn"), + os.path.join(output_directory, "gene_models.faa"), + + ] + return cmd + + +# MetaEuk +def get_metaeuk_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + cmd = [ + # Get eukaryotic sequences + "cat", + opts.fasta, + "|", + os.environ["seqkit"], + "grep", + "-f {}".format(input_filepaths[0]), + ">", + os.path.join(directories["tmp"], "tmp.fasta"), + + + "&&", + + # Placeholder + "OUTPUT_DIRECTORY={}; for ID in $(cat {}); do >$OUTPUT_DIRECTORY/$ID.faa; >$OUTPUT_DIRECTORY/$ID.ffn; >$OUTPUT_DIRECTORY/$ID.gff; done".format(output_directory, input_filepaths[1]), + + "&&", + + # Run MetaEuk + os.environ["metaeuk"], + "easy-predict", + "--threads {}".format(opts.n_jobs), + "-s {}".format(opts.metaeuk_sensitivity), + "-e {}".format(opts.metaeuk_evalue), + opts.metaeuk_options, + os.path.join(directories["tmp"], "tmp.fasta"), + opts.metaeuk_database, # db + os.path.join(output_directory, "metaeuk"), # output prefix + os.path.join(directories["tmp"],"metaeuk"), + + # Convert MetaEuk identifiers + "&&", + + os.environ["compile_metaeuk_identifiers.py"], + "--cds {}".format(os.path.join(output_directory, "metaeuk.codon.fas")), + "--protein {}".format(os.path.join(output_directory, "metaeuk.fas")), + "-o {}".format(output_directory), + "-b {}".format(opts.basename), + ] + + # Remove temporary files + cmd += [ + + "&&", + + "rm -rf {} {} {}".format( + os.path.join(output_directory, "*.fas"), # output prefix + os.path.join(output_directory, "metaeuk.gff"), # output prefix + os.path.join(directories["tmp"],"metaeuk", "*"), + os.path.join(directories["tmp"], "tmp.fasta"), + ), + ] + + if opts.scaffolds_to_bins: + cmd += [ + # Partition the gene models and genomes + "&&", + + os.environ["partition_gene_models.py"], + "-i {}".format(opts.scaffolds_to_bins), + # "-f {}".format(opts.fasta), + "-g {}".format(os.path.join(output_directory, "{}.gff".format(opts.basename))), + "-d {}".format(os.path.join(output_directory, "{}.ffn".format(opts.basename))), + "-a {}".format(os.path.join(output_directory, "{}.faa".format(opts.basename))), + "-o {}".format(os.path.join(output_directory)), + "--use_mag_as_description", + + "&&", + + "rm -rf", + os.path.join(output_directory, "{}.*".format(opts.basename)), + + ] + return cmd + +# Pyrodigal +def get_pyrodigal_cmd(input_filepaths, output_filepaths, output_directory, directories, opts, genetic_code): + + cmd = [ + "cat", + opts.fasta, + "|", + os.environ["seqkit"], + "grep", + "-f {}".format(input_filepaths[0]), + ">", + os.path.join(directories["tmp"], "tmp.fasta"), + + "&&", + + # Placeholder + "OUTPUT_DIRECTORY={}; for ID in $(cat {}); do >$OUTPUT_DIRECTORY/$ID.faa; >$OUTPUT_DIRECTORY/$ID.ffn; >$OUTPUT_DIRECTORY/$ID.gff; done".format(output_directory, input_filepaths[1]), + + "&&", + + # Run analysis + os.environ["pyrodigal"], + "-p meta", + "-i {}".format(os.path.join(directories["tmp"], "tmp.fasta")), + "-g {}".format(genetic_code), + "-f gff", + "-d {}".format(os.path.join(output_directory, "{}.ffn".format(opts.basename))), + "-a {}".format(os.path.join(output_directory, "{}.faa".format(opts.basename))), + "--min-gene {}".format(opts.pyrodigal_minimum_gene_length), + "--min-edge-gene {}".format(opts.pyrodigal_minimum_edge_gene_length), + "--max-overlap {}".format(opts.pyrodigal_maximum_gene_overlap_length), + # "-j {}".format(opts.n_jobs), + ">", + os.path.join(directories["tmp"], "tmp.gff"), + + "&&", + + "cat", + os.path.join(directories["tmp"], "tmp.gff"), + "|", + os.environ["append_geneid_to_prodigal_gff.py"], + "-a gene_id", + ">", + os.path.join(output_directory, "{}.gff".format(opts.basename)), + + "&&", + + "rm", + os.path.join(directories["tmp"], "tmp.*"), + ] + + if opts.scaffolds_to_bins: + cmd += [ + # Partition the gene models and genomes + "&&", + + os.environ["partition_gene_models.py"], + "-i {}".format(opts.scaffolds_to_bins), + # "-f {}".format(opts.fasta), + "-g {}".format(os.path.join(output_directory, "{}.gff".format(opts.basename))), + "-d {}".format(os.path.join(output_directory, "{}.ffn".format(opts.basename))), + "-a {}".format(os.path.join(output_directory, "{}.faa".format(opts.basename))), + "-o {}".format(os.path.join(output_directory)), + "--use_mag_as_description", + + "&&", + + "rm -rf", + os.path.join(output_directory, "{}.*".format(opts.basename)), + + ] + + return cmd + +# barrnap +def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directories, opts, kingdom): + cmd = [ + +""" +# Placeholder +OUTPUT_DIRECTORY={} +for ID in $(cat {}) +do + >$OUTPUT_DIRECTORY/$ID.rRNA + >$OUTPUT_DIRECTORY/$ID.rRNA.gff +done + +# Run analysis +for GENOME_FASTA in {} +do + ID=$(basename $GENOME_FASTA .fa) + {} --kingdom {} --threads {} --lencutoff {} --reject {} --evalue {} --outseq $OUTPUT_DIRECTORY/$ID.rRNA $GENOME_FASTA | {} > $OUTPUT_DIRECTORY/$ID.rRNA.gff + rm -rf $GENOME_FASTA.fai +done +""".format( + output_directory, + input_filepaths[0], + + input_filepaths[1], + os.environ["barrnap"], + kingdom, + opts.n_jobs, + opts.barrnap_length_cutoff, + opts.barrnap_reject, + opts.barrnap_evalue, + os.environ["append_geneid_to_barrnap_gff.py"], + ), + ] + return cmd + +# tRNAscan-SE +def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, directories, opts, search_mode, trnascan_options): + cmd = [ + +""" + +# Placeholder +OUTPUT_DIRECTORY={} +for ID in $(cat {}) +do + >$OUTPUT_DIRECTORY/$ID.tRNA + >$OUTPUT_DIRECTORY/$ID.tRNA.gff + >$OUTPUT_DIRECTORY/$ID.tRNA.struct + >$OUTPUT_DIRECTORY/$ID.tRNA.txt + +done + +# Run analysis +for GENOME_FASTA in {} +do + ID=$(basename $GENOME_FASTA .fa) + {} {} --forceow --progress --thread {} --fasta $OUTPUT_DIRECTORY/$ID.tRNA --gff $OUTPUT_DIRECTORY/$ID.tRNA.gff --struct $OUTPUT_DIRECTORY/$ID.tRNA.struct {} $GENOME_FASTA > $OUTPUT_DIRECTORY/$ID.tRNA.txt +done +""".format( + output_directory, + input_filepaths[0], + + input_filepaths[1], + os.environ["tRNAscan-SE"], + search_mode, + opts.n_jobs, + trnascan_options, + ), + ] + return cmd + +def get_symlink_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Nuclear + cmd = [ + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.metaeuk.tsv $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "2__metaeuk")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "5__barrnap-nuclear")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "8__trnascan-nuclear")], + ), + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "2__metaeuk")], + directories[("intermediate", "5__barrnap-nuclear")], + directories[("intermediate", "8__trnascan-nuclear")], + output_directory, + ) + + + ] + + # Mitochondrion + cmd += [ + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "3__pyrodigal-mitochondrion")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "6__barrnap-mitochondrion")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "9__trnascan-mitochondrion")], + ), + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "3__pyrodigal-mitochondrion")], + directories[("intermediate", "6__barrnap-mitochondrion")], + directories[("intermediate", "9__trnascan-mitochondrion")], + os.path.join(output_directory, "mitochondrion"), + ) + ] + + # Plastid + cmd += [ + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "4__pyrodigal-plastid")], + ), + + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "7__barrnap-plastid")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "10__trnascan-plastid")], + ), + + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "4__pyrodigal-plastid")], + directories[("intermediate", "7__barrnap-plastid")], + directories[("intermediate", "10__trnascan-plastid")], + os.path.join(output_directory, "plastid"), + ) + ] + + return cmd + +def get_stats_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Genomes + cmd = [ + + os.environ["seqkit"], + "stats", + "-a", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.fa"), + os.path.join(output_directory, "*", "*.fa"), + + "|", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-3]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-3]); df.to_csv(sys.stdout, sep="\t")'""", + + ">", + os.path.join(output_directory,"genome_statistics.tsv"), + + + # CDS + + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.ffn"), + os.path.join(output_directory,"*", "*.ffn"), + + "|", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-4]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-4]); df.to_csv(sys.stdout, sep="\t")'""", + + ">", + os.path.join(output_directory,"gene_statistics.cds.tsv"), + + + # rRNA + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.rRNA"), + os.path.join(output_directory,"*", "*.rRNA"), + + "|", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-5]); df.to_csv(sys.stdout, sep="\t")'""", + + ">", + os.path.join(output_directory,"gene_statistics.rRNA.tsv"), + + + # tRNA + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.tRNA"), + os.path.join(output_directory,"*", "*.tRNA"), + + "|", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-5]); df.to_csv(sys.stdout, sep="\t")'""", + + ">", + os.path.join(output_directory,"gene_statistics.tRNA.tsv"), + + + ] + + return cmd + + +# ============ +# Run Pipeline +# ============ + + + +def create_pipeline(opts, directories, f_cmds): + + # ................................................................. + # Primordial + # ................................................................. + # Commands file + pipeline = ExecutablePipeline(name=__program__, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + + # ============ + # Partitioning + # ============ + if opts.tiara_results: + output_filepaths = [opts.tiara_results] + else: + + step = 0 + + program = "tiara" + + program_label = "{}__{}".format(step, program) + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # Info + description = "Predict taxonmic domain of contigs" + + # i/o + input_filepaths = [ + opts.fasta, + ] + + output_filepaths = [ + os.path.join(output_directory, "tiara_output.tsv"), + ] + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_tiara_cmd(**params) + + + # Add step to pipeline + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + ) + + + + # ========== + # Partition sequences + # ========== + step = 1 + + program = "partition" + + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "Partitioning nuclear, mitochondrial, and plastid sequences" + + # i/o + + input_filepaths = [ + opts.fasta, + output_filepaths[0], + ] + + + + output_filepaths = [ + os.path.join(directories["output"],"*.fa"), + os.path.join(output_directory,"eukaryotic_contigs.list"), + os.path.join(output_directory,"genomes.list"), + + ] + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + if opts.scaffolds_to_bins: + cmd = get_partition_organelle_sequences_multiple_cmd(**params) + else: + cmd = get_partition_organelle_sequences_single_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + acceptable_returncodes={0}, + log_prefix=program_label, + # whitelist_empty_output_files=[ + # "mitochondrion/*.fa", + # "plastid/*.fa", + # ] + ) + + # ============= + # MetaEuk + # ============= + step = 2 + + program = "metaeuk" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = directories["output"] + + # Info + description = "Ab initio eukaryotic gene prediction" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "eukaryotic_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + "identifier_mapping.metaeuk.tsv", + "metaeuk.headersMap.tsv", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_metaeuk_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Pyrodigal + # ============= + step = 3 + + program = "pyrodigal-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = os.path.join(directories["output"], "mitochondrion") + + + # Info + description = "Ab initio prokaryotic gene prediction [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "mitochondrion_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "genetic_code":opts.pyrodigal_mitochondrial_genetic_code, + } + + cmd = get_pyrodigal_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Pyrodigal + # ============= + step = 4 + + program = "pyrodigal-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = os.path.join(directories["output"], "plastid") + + # Info + description = "Ab initio prokaryotic gene prediction [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "plastid_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "genetic_code":opts.pyrodigal_plastid_genetic_code, + } + + cmd = get_pyrodigal_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 5 + + program = "barrnap-nuclear" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Nuclear]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "*.fa"), + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"euk", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 6 + + program = "barrnap-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "mitochondrion", "*.fa"), + + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"mito", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 7 + + program = "barrnap-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "plastid", "*.fa"), + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"bac", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 8 + + program = "trnascan-nuclear" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Nuclear]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":"-E", + "trnascan_options":opts.trnascan_nuclear_options, + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 9 + + program = "trnascan-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "mitochondrion", "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":opts.trnascan_mitochondrial_searchmode, + "trnascan_options":opts.trnascan_mitochondrial_options, + + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 10 + + program = "trnascan-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "plastid", "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":opts.trnascan_plastid_searchmode, + "trnascan_options":opts.trnascan_plastid_options, + + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Output + # ============= + step = 11 + + program = "symlink" + program_label = "{}__{}".format(step, program) + description = "Merging and symlinking results for output" + + # Add to directories + output_directory = directories["output"] + + # i/o + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + ] + + output_filenames = [ + # "*.faa", + # "*.ffn", + "*.gff", + + + ] + + + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_symlink_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + # ============= + # Output + # ============= + step = 12 + + program = "stats" + program_label = "{}__{}".format(step, program) + description = "Calculating statistics for sequences" + + # Add to directories + output_directory = directories["output"] + + # i/o + input_filepaths = [ + os.path.join(directories["output"], "*.fa"), + # os.path.join(directories["output"], "mitochondrion"), + # os.path.join(directories["output"], "plastid"), + + ] + + output_filenames = [ + "genome_statistics.tsv", + "gene_statistics.cds.tsv", + "gene_statistics.rRNA.tsv", + "gene_statistics.tRNA.tsv", + + + ] + + + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_stats_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + + + + + + + return pipeline + + + +# Set environment variables +def add_executables_to_environment(opts): + """ + Adapted from Soothsayer: https://github.com/jolespin/soothsayer + """ + accessory_scripts = { + "partition_gene_models.py", + "compile_metaeuk_identifiers.py", + "partition_organelle_sequences.py", + "append_geneid_to_prodigal_gff.py", + "append_geneid_to_barrnap_gff.py", + } + + required_executables={ + "seqkit", + "metaeuk", + "pyrodigal", + "barrnap", + "tRNAscan-SE", + + } + if not opts.tiara_results: + required_executables |= {"tiara"} + + + required_executables |= accessory_scripts + + if opts.path_config == "CONDA_PREFIX": + executables = dict() + for name in sorted(required_executables): + if name not in accessory_scripts: + executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) + else: + if opts.path_config is None: + opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") + opts.path_config = format_path(opts.path_config) + assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) + assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) + df_config = pd.read_csv(opts.path_config, sep="\t") + assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) + df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) + # Get executable paths + executables = OrderedDict(zip(df_config["name"], df_config["executable"])) + assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) + + # Display + + for name in sorted(accessory_scripts): + executables[name] = "'{}'".format(os.path.join(opts.script_directory, name)) # Can handle spaces in path + + + + print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) + for name, executable in executables.items(): + if name in required_executables: + print(name, executable, sep = " --> ", file=sys.stdout) + os.environ[name] = executable.strip() + print("", file=sys.stdout) + + +# Configure parameters +def configure_parameters(opts, directories): + assert bool(opts.name) != bool(opts.scaffolds_to_bins), "--name and --scaffolds_to_bins are mutually exclusive. Use --name if you are modeling genes on a single assembly and --scaffolds_to_bins in batch (faster for multiple assemblies)" + if opts.name: + opts.basename = opts.name + else: + opts.basename = "gene_models" + + # Set environment variables + add_executables_to_environment(opts=opts) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -f -d -i -o ".format(__program__) + + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + + # Pipeline + parser_io = parser.add_argument_group('I/O arguments') + parser_io.add_argument("-f","--fasta", type=str, required=True, help = "path/to/scaffolds.fasta") + parser_io.add_argument("-t","--tiara_results", type=str, required=True, help = "path/to/scaffolds.fasta") + parser_io.add_argument("-n","--name", type=str, required=False, help = "path/to/scaffolds.fasta [Cannot be used with --scaffolds_to_bins]") + parser_io.add_argument("-i","--scaffolds_to_bins", type=str, required=False, help = "path/to/scaffolds_to_bins.tsv, [Optional] Format: [id_scaffold][id_bin], No header. [Cannot be used with --name]") + parser_io.add_argument("-o","--output_directory", type=str, default="eukaryotic_gene_modeling_output", help = "path/to/project_directory [Default: eukaryotic_gene_modeling_output]") + parser_io.add_argument("-d", "--metaeuk_database", type=str, required=True, help=f"MetaEuk/MMSEQS2 database (E.g., $VEBA_DATABASE/Classify/Microeukaryotic/microeukaryotic)") + + # Utility + parser_utility = parser.add_argument_group('Utility arguments') + parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future + parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") + # parser_utility.add_argument("--random_state", type=int, default=0, help = "Random state [Default: 0]") + parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") + parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) + + # Tiara + parser_organelle = parser.add_argument_group('Organelle arguments') + parser_organelle.add_argument("-u","--unknown_organelle_prediction", type=str, default="nuclear", help = "{unknown, nuclear} [Default: nuclear]") + # parser_organelle.add_argument("--mitochondrion_suffix", type=str, default=".mtDNA", help = "Mitochondrion suffix [Default: .mtDNA]") + # parser_organelle.add_argument("--plastid_suffix", type=str, default=".plastid", help = "Plastid suffix [Default: .plastid]") + # parser_organelle.add_argument("--unknown_suffix", type=str, default=".unknown", help = "Unknown suffix [Default: .unknown]") + parser_organelle.add_argument("--tiara_options", type=str, default="", help="Tiara | More options (e.g. --arg 1 ) [Default: '']") + + # MetaEuk + parser_metaeuk = parser.add_argument_group('MetaEuk arguments') + parser_metaeuk.add_argument("--metaeuk_sensitivity", type=float, default=4.0, help="MetaEuk | Sensitivity: 1.0 faster; 4.0 fast; 7.5 sensitive [Default: 4.0]") + parser_metaeuk.add_argument("--metaeuk_evalue", type=float, default=0.01, help="MetaEuk | List matches below this E-value (range 0.0-inf) [Default: 0.01]") + parser_metaeuk.add_argument("--metaeuk_options", type=str, default="", help="MetaEuk | More options (e.g. --arg 1 ) [Default: ''] https://github.com/soedinglab/metaeuk") + + # Pyrodigal + parser_pyrodigal = parser.add_argument_group('Pyrodigal arguments (Mitochondria)') + parser_pyrodigal.add_argument("--pyrodigal_minimum_gene_length", type=int, default=90, help="Pyrodigal | Minimum gene length [Default: 90]") + parser_pyrodigal.add_argument("--pyrodigal_minimum_edge_gene_length", type=int, default=60, help="Pyrodigal | Minimum edge gene length [Default: 60]") + parser_pyrodigal.add_argument("--pyrodigal_maximum_gene_overlap_length", type=int, default=60, help="Pyrodigal | Maximum gene overlap length [Default: 60]") + parser_pyrodigal.add_argument("--pyrodigal_mitochondrial_genetic_code", type=int, default=4, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 4] (The Mold, Protozoan, and Coelenterate Mitochondrial Code and the Mycoplasma/Spiroplasma Code))") + parser_pyrodigal.add_argument("--pyrodigal_plastid_genetic_code", type=int, default=11, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 11] (The Bacterial, Archaeal and Plant Plastid Code))") + + # rRNA + parser_barrnap = parser.add_argument_group('barrnap arguments') + parser_barrnap.add_argument("--barrnap_length_cutoff", type=float, default=0.8, help="barrnap | Proportional length threshold to label as partial [Default: 0.8]") + parser_barrnap.add_argument("--barrnap_reject", type=float, default=0.25, help="barrnap | Proportional length threshold to reject prediction [Default: 0.25]") + parser_barrnap.add_argument("--barrnap_evalue", type=float, default=1e-6, help="barrnap | Similarity e-value cut-off [Default: 1e-6]") + + # tRNA + parser_trnascan = parser.add_argument_group('tRNAscan-SE arguments') + parser_trnascan.add_argument("--trnascan_nuclear_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_mitochondrial_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | Current best option according to developer: https://github.com/UCSC-LoweLab/tRNAscan-SE/issues/24") + parser_trnascan.add_argument("--trnascan_mitochondrial_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_plastid_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_plastid_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + + + # Options + opts = parser.parse_args() + + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Threads + if opts.n_jobs == -1: + from multiprocessing import cpu_count + opts.n_jobs = cpu_count() + assert opts.n_jobs >= 1, "--n_jobs must be ≥ 1. To select all available threads, use -1." + + # Directories + directories = dict() + directories["project"] = create_directory(opts.output_directory) + directories["output"] = create_directory(os.path.join(directories["project"], "output")) + directories["log"] = create_directory(os.path.join(directories["project"], "log")) + directories["tmp"] = create_directory(os.path.join(directories["project"], "tmp")) + directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) + directories["intermediate"] = create_directory(os.path.join(directories["project"], "intermediate")) + os.environ["TMPDIR"] = directories["tmp"] + + # Temporary + opts.mitochondrion_suffix = "" + opts.plastid_suffix = "" + opts.unknown_suffix = "" + + # Info + print(format_header(__program__, "="), file=sys.stdout) + print(format_header("Configuration:", "-"), file=sys.stdout) + print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) + print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("Script version:", __version__, file=sys.stdout) + print("Moment:", get_timestamp(), file=sys.stdout) + print("Directory:", os.getcwd(), file=sys.stdout) + print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) + configure_parameters(opts, directories) + sys.stdout.flush() + + + # Run pipeline + with open(os.path.join(directories["project"], "commands.sh"), "w") as f_cmds: + pipeline = create_pipeline( + opts=opts, + directories=directories, + f_cmds=f_cmds, + ) + pipeline.compile() + pipeline.execute(restart_from_checkpoint=opts.restart_from_checkpoint) + + # shutil.rmtree(directories["intermediate"]) + + + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/src/scripts/get_longest_isoform_from_gff.py b/src/scripts/get_longest_isoform_from_gff.py new file mode 100755 index 0000000..8546cfc --- /dev/null +++ b/src/scripts/get_longest_isoform_from_gff.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +import sys, os, argparse +from tqdm import tqdm +from collections import defaultdict + +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.9.18" + +def get_longest_interval(intervals): + """ + Implementation credits to @yep-d from the following source: + * https://stackoverflow.com/questions/77116236/find-longest-interval-between-overlapping-intervals-in-python + """ + #get sorted list of intervals by starting point + aux = sorted(intervals, key=lambda x: x[0]) + new_group = [aux[0]] + output = [] + for interval in aux[1:]: + if interval[0] > new_group[-1][-1]: + #if the interval does not overlap with the last interval we start a new group + output.append( + #find interval of maximum length + max(new_group, key=lambda interval: interval[1] - interval[0]) + ) + #start new group + new_group = [interval] + else: + #otherwise just append it onto the same group if it overlaps + new_group.append(interval) + #take care of the last group, doing the same as above + output.append(max(new_group, key=lambda interval: interval[1] - interval[0])) + return output + + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -s -d -o ".format(__program__) + epilog = "Copyright 2022 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser.add_argument("-i","--input_gff", type=str, default="stdin", help = "path/to/input.gff [Default: stdin]") + parser.add_argument("-o","--output_gff", type=str, default="stdout", help = "path/to/output.gff [Default: stdout]") + parser.add_argument("-f","--field", type=str, default = "CDS", help = "Field to dereplicate [Default: CDS]") + parser.add_argument("--no_comment", action = "store_true", help = "Don't include commented fields in output") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Input + if opts.input_gff == "stdin": + f_in = sys.stdin + else: + f_in = open(opts.input_gff, "r") + + # Output + if opts.output_gff == "stdout": + f_out = sys.stdout + else: + f_out = open(opts.output_gff, "w") + + # Parse input + contigstrand_to_locations_to_record = defaultdict(dict) + if opts.no_comment: + for line in tqdm(f_in, "Reading input GFF: {}".format(opts.input_gff)): + line = line.strip() + if opts.field in line: + fields = line.split("\t") + id_contig = fields[0] + start = int(fields[3]) + end = int(fields[4]) + strand = fields[6] + contigstrand_to_locations_to_record[(id_contig, strand)][(start,end)] = line + + else: + for line in tqdm(f_in, "Reading input GFF: {}".format(opts.input_gff)): + line = line.strip() + if line.startswith("#"): + print(line, file=f_out) + if opts.field in line: + fields = line.split("\t") + id_contig = fields[0] + start = int(fields[3]) + end = int(fields[4]) + strand = fields[6] + contigstrand_to_locations_to_record[(id_contig, strand)][(start,end)] = line + if f_in != sys.stdin: + f_in.close() + + # Dereplicate output + records = list() + for (id_contig, strand), d1 in contigstrand_to_locations_to_record.items(): + longest_intervals = get_longest_interval(d1.keys()) + for interval in longest_intervals: + start, end = interval + data = [id_contig, start, end, d1[interval]] + records.append(data) + records = sorted(records, key=lambda x: (x[0], x[1], x[2])) + + # Write output + for (id_contig, start, end, record) in tqdm(records, "Writing output GFF with longest CDS: {}".format(opts.output_gff)): + print(record, file=f_out) + if f_out != sys.stdout: + f_out.close() + +if __name__ == "__main__": + main() + + diff --git a/src/scripts/global_clustering.py b/src/scripts/global_clustering.py index c534bf9..ba0be7f 100755 --- a/src/scripts/global_clustering.py +++ b/src/scripts/global_clustering.py @@ -14,7 +14,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.8.30" +__version__ = "2023.10.5" def get_basename(x): _, fn = os.path.split(x) @@ -97,7 +97,7 @@ def add_executables_to_environment(opts): def configure_parameters(opts, directories): assert_acceptable_arguments(opts.algorithm, {"easy-cluster", "easy-linclust"}) - + assert 0 < opts.minimum_core_prevalence <= 1.0, "--minimum_core_prevalence must be a float between (0.0,1.0])" # Set environment variables add_executables_to_environment(opts=opts) @@ -108,7 +108,7 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -f -d -i -o ".format(__program__) + usage = "{} -i -o -A 95 -a easy-cluster".format(__program__) epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" @@ -116,11 +116,12 @@ def main(args=None): parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') - parser_io.add_argument("-i", "--input", type=str, default="stdin", help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") + parser_io.add_argument("-i", "--genomes_table", type=str, default="stdin", help = "path/to/genomes_table.tsv, Format: Must include the following columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") parser_io.add_argument("-o","--output_directory", type=str, default="global_clustering_output", help = "path/to/project_directory [Default: global_clustering_output]") parser_io.add_argument("-e", "--no_singletons", action="store_true", help="Exclude singletons") #isPSLC-1_SSPC-3345__SRR178126 - parser_io.add_argument("-r", "--no_representative_sequences", action="store_true", help="Do not write representative sequences to fasta") #isPSLC-1_SSPC-3345__SRR178126 + parser_io.add_argument("-R", "--no_representative_sequences", action="store_true", help="Do not write representative sequences to fasta") #isPSLC-1_SSPC-3345__SRR178126 parser_io.add_argument("-C", "--no_core_sequences", action="store_true", help="Do not write core pagenome sequences to fasta") #isPSLC-1_SSPC-3345__SRR178126 + # parser_io.add_argument("-M", "--no_marker_sequences", action="store_true", help="Do not write core pagenome sequences to fasta") #isPSLC-1_SSPC-3345__SRR178126 # Utility parser_utility = parser.add_argument_group('Utility arguments') @@ -148,6 +149,11 @@ def main(args=None): parser_mmseqs2.add_argument("--protein_cluster_prefix_zfill", type=int, default=0, help="Cluster prefix zfill. Use 7 to match identifiers from OrthoFinder. Use 0 to add no zfill. [Default: 0]") #7 parser_mmseqs2.add_argument("--mmseqs2_options", type=str, default="", help="MMSEQS2 | More options (e.g. --arg 1 ) [Default: '']") + # Pangenome + parser_pangenome = parser.add_argument_group('Pangenome arguments') + parser_pangenome.add_argument("--minimum_core_prevalence", type=float, default=1.0, help="Minimum ratio of genomes detected in a SLC for a SSPC to be considered core (Range (0.0, 1.0]) [Default: 1.0]") + + # Options opts = parser.parse_args() @@ -188,19 +194,21 @@ def main(args=None): configure_parameters(opts, directories) sys.stdout.flush() - # Load input + + # Make directories t0 = time.time() - if opts.input == "stdin": - opts.input = sys.stdin - df_genomes = pd.read_csv(opts.input, sep="\t", header=None) + print(format_header(" " .join(["* ({}) Creating directories:".format(format_duration(t0)), directories["intermediate"]])), file=sys.stdout) + os.makedirs(opts.output_directory, exist_ok=True) + + # Load input + if opts.genomes_table == "stdin": + opts.genomes_table = sys.stdin + df_genomes = pd.read_csv(opts.genomes_table, sep="\t", header=None) assert df_genomes.shape[1] >= 6, "Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. You have provided a table with {} columns.".format(df_genomes.shape[1]) df_genomes = df_genomes.iloc[:,:6] df_genomes.columns = ["organism_type", "id_sample", "id_mag", "genome", "proteins", "cds"] - assert not np.any(df_genomes.isnull()), "Input has missing values. Please correct this.\n{}".format(df_genomes.loc[df_genomes.isnull().sum(axis=1)[lambda x: x > 0].index].to_string()) + assert not np.any(df_genomes.isnull()), "--genomes_table has missing values. Please correct this.\n{}".format(df_genomes.loc[df_genomes.isnull().sum(axis=1)[lambda x: x > 0].index].to_string()) - # Make directories - print(format_header(" " .join(["* ({}) Creating directories:".format(format_duration(t0)), directories["intermediate"]])), file=sys.stdout) - os.makedirs(opts.output_directory, exist_ok=True) for organism_type, df_1 in df_genomes.groupby("organism_type"): # Organism directories @@ -373,6 +381,7 @@ def main(args=None): "--basename protein_clusters", "--identifiers {}".format(os.path.join(genomecluster_directory, "protein_identifiers.list")), "--no_sequences_and_header", + "--representative_output_format table", "&&", @@ -554,7 +563,7 @@ def main(args=None): # Add detection number and ratios df_proteinclusters["number_of_genomes_detected"] = pd.Series(protein_to_number_of_genomes_detected) df_proteinclusters["ratio_of_genomes_detected"] = pd.Series(protein_to_ratio_of_genomes_detected) - df_proteinclusters["core_pangenome"] = df_proteinclusters["ratio_of_genomes_detected"] == 1 + df_proteinclusters["core_pangenome"] = df_proteinclusters["ratio_of_genomes_detected"] >= opts.minimum_core_prevalence df_proteinclusters["singleton"] = df_proteinclusters["number_of_genomes_detected"] == 1 @@ -563,6 +572,10 @@ def main(args=None): df_genomeclusters["number_of_singleton_proteins"] = genomecluster_to_singletons.map(len) df_genomeclusters["singletons"] = genomecluster_to_singletons + # Number of copies of SSPC per genome + number_of_gene_copies_per_sspc_per_genome = df_proteins.groupby(["id_genome", "id_protein_cluster"]).size() + df_proteinclusters["average_number_of_copies_per_genome"] = number_of_gene_copies_per_sspc_per_genome.groupby(lambda x: x[1]).mean() + # # Symlink pangenome graphs # print(format_header(" * ({}) Symlinking pangenome protein cluster NetworkX Graphs:".format(format_duration(t0))), file=sys.stdout) diff --git a/src/scripts/local_clustering.py b/src/scripts/local_clustering.py index 6ee100b..c03f48a 100755 --- a/src/scripts/local_clustering.py +++ b/src/scripts/local_clustering.py @@ -14,7 +14,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.8.30" +__version__ = "2023.10.5" def get_basename(x): _, fn = os.path.split(x) @@ -95,6 +95,7 @@ def add_executables_to_environment(opts): # Configure parameters def configure_parameters(opts, directories): assert_acceptable_arguments(opts.algorithm, {"easy-cluster", "easy-linclust"}) + assert 0 < opts.minimum_core_prevalence <= 1.0, "--minimum_core_prevalence must be a float between (0.0,1.0])" # Set environment variables add_executables_to_environment(opts=opts) @@ -114,10 +115,10 @@ def main(args=None): parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) # Pipeline parser_io = parser.add_argument_group('Required I/O arguments') - parser_io.add_argument("-i", "--input", type=str, default="stdin", help = "path/to/input.tsv, Format: Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") + parser_io.add_argument("-i", "--genomes_table", type=str, default="stdin", help = "path/to/genomes_table.tsv, Format: Must include the following columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. [Default: stdin]") parser_io.add_argument("-o","--output_directory", type=str, default="local_clustering_output", help = "path/to/project_directory [Default: local_clustering_output]") parser_io.add_argument("-e", "--no_singletons", action="store_true", help="Exclude singletons") #isPSLC-1_SSPC-3345__SRR178126 - parser_io.add_argument("-r", "--no_representative_sequences", action="store_true", help="Exclude representative sequences") #isPSLC-1_SSPC-3345__SRR178126 + parser_io.add_argument("-R", "--no_representative_sequences", action="store_true", help="Exclude representative sequences") #isPSLC-1_SSPC-3345__SRR178126 parser_io.add_argument("-C", "--no_core_sequences", action="store_true", help="Do not write core pagenome sequences to fasta") #isPSLC-1_SSPC-3345__SRR178126 # Utility @@ -146,6 +147,10 @@ def main(args=None): parser_mmseqs2.add_argument("--protein_cluster_prefix_zfill", type=int, default=0, help="Cluster prefix zfill. Use 7 to match identifiers from OrthoFinder. Use 0 to add no zfill. [Default: 0]") #7 parser_mmseqs2.add_argument("--mmseqs2_options", type=str, default="", help="MMSEQS2 | More options (e.g. --arg 1 ) [Default: '']") + # Pangenome + parser_pangenome = parser.add_argument_group('Pangenome arguments') + parser_pangenome.add_argument("--minimum_core_prevalence", type=float, default=1.0, help="Minimum ratio of genomes detected in a SLC for a SSPC to be considered core (Range (0.0, 1.0]) [Default: 1.0]") + # Options opts = parser.parse_args() @@ -184,19 +189,21 @@ def main(args=None): configure_parameters(opts, directories) sys.stdout.flush() - # Load input + # Make directories t0 = time.time() - if opts.input == "stdin": - opts.input = sys.stdin - df_genomes = pd.read_csv(opts.input, sep="\t", header=None) + print(format_header(" " .join(["* ({}) Creating directories:".format(format_duration(t0)), directories["intermediate"]])), file=sys.stdout) + os.makedirs(opts.output_directory, exist_ok=True) + + # Load input + if opts.genomes_table == "stdin": + opts.genomes_table = sys.stdin + df_genomes = pd.read_csv(opts.genomes_table, sep="\t", header=None) assert df_genomes.shape[1] >= 6, "Must include the follow columns (No header) [organism_type][id_sample][id_mag][genome][proteins][cds] but can include additional columns to the right (e.g., [gene_models]). Suggested input is from `compile_genomes_table.py` script. You have provided a table with {} columns.".format(df_genomes.shape[1]) df_genomes = df_genomes.iloc[:,:6] df_genomes.columns = ["organism_type", "id_sample", "id_mag", "genome", "proteins", "cds"] - assert not np.any(df_genomes.isnull()), "Input has missing values. Please correct this.\n{}".format(df_genomes.loc[df_genomes.isnull().sum(axis=1)[lambda x: x > 0].index].to_string()) + assert not np.any(df_genomes.isnull()), "--genomes_table has missing values. Please correct this.\n{}".format(df_genomes.loc[df_genomes.isnull().sum(axis=1)[lambda x: x > 0].index].to_string()) + - # Make directories - print(format_header(" " .join(["* ({}) Creating directories:".format(format_duration(t0)), directories["intermediate"]])), file=sys.stdout) - os.makedirs(opts.output_directory, exist_ok=True) for organism_type, df_1 in df_genomes.groupby("organism_type"): # Organism directories @@ -375,6 +382,7 @@ def main(args=None): "--basename protein_clusters", "--identifiers {}".format(os.path.join(genomecluster_directory, "protein_identifiers.list")), "--no_sequences_and_header", + "--representative_output_format table", "&&", @@ -559,7 +567,7 @@ def main(args=None): # Add detection number and ratios df_proteinclusters["number_of_genomes_detected"] = pd.Series(protein_to_number_of_genomes_detected) df_proteinclusters["ratio_of_genomes_detected"] = pd.Series(protein_to_ratio_of_genomes_detected) - df_proteinclusters["core_pangenome"] = df_proteinclusters["ratio_of_genomes_detected"] == 1 + df_proteinclusters["core_pangenome"] = df_proteinclusters["ratio_of_genomes_detected"] >= opts.minimum_core_prevalence df_proteinclusters["singleton"] = df_proteinclusters["number_of_genomes_detected"] == 1 @@ -568,6 +576,10 @@ def main(args=None): df_genomeclusters["number_of_singleton_proteins"] = genomecluster_to_singletons.map(len) df_genomeclusters["singletons"] = genomecluster_to_singletons + # Number of copies of SSPC per genome + number_of_gene_copies_per_sspc_per_genome = df_proteins.groupby(["id_genome", "id_protein_cluster"]).size() + df_proteinclusters["average_number_of_copies_per_genome"] = number_of_gene_copies_per_sspc_per_genome.groupby(lambda x: x[1]).mean() + # Writing output files print(format_header(" * ({}) Writing Output Tables:".format(format_duration(t0))), file=sys.stdout) df_mags.to_csv(os.path.join(directories["output"], "identifier_mapping.genomes.tsv"), sep="\t") diff --git a/src/scripts/marker_gene_clustering.py b/src/scripts/marker_gene_clustering.py new file mode 100755 index 0000000..94bdc32 --- /dev/null +++ b/src/scripts/marker_gene_clustering.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob, shutil, time, gzip, warnings +from multiprocessing import cpu_count +from collections import OrderedDict, defaultdict +from tqdm import tqdm + +import pandas as pd +import numpy as np +from Bio.SeqIO.FastaIO import SimpleFastaParser + +# Soothsayer Ecosystem +from genopype import * +from soothsayer_utils import * + +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.10.6" + +PROTEIN_MINIMUM_IDENTITY_THRESHOLD = 50.0 +NUCLEOTIDE_MINIMUM_IDENTITY_THRESHOLD = 75.0 + +def get_basename(x): + _, fn = os.path.split(x) + if fn.endswith(".gz"): + fn = fn[:-3] + return ".".join(fn.split(".")[:-1]) + +def get_marker_gene_cluster_prevalence(df_input:pd.DataFrame): # genome, protein_cluster, marker_gene_cluster + # Read Input + genome_clusters = sorted(df_input.iloc[:,0].unique()) + protein_clusters = sorted(df_input.iloc[:,1].unique()) + marker_gene_clusters = sorted(df_input.iloc[:,2].unique()) + + # Create array + A = np.zeros((len(genome_clusters), len(marker_gene_clusters)), dtype=int) + + for _, (id_genome_cluster, id_protein_cluster, id_marker_gene_cluster) in df_input.iterrows(): + i = genome_clusters.index(id_genome_cluster) + j = marker_gene_clusters.index(id_marker_gene_cluster) + A[i,j] += 1 + + # Create output + df_output = pd.DataFrame(A, index=genome_clusters, columns=marker_gene_clusters) + df_output.index.name = "id_genome_cluster" + df_output.columns.name = "id_marker_gene_cluster" + + return df_output + +# Set environment variables +def add_executables_to_environment(opts): + """ + Adapted from Soothsayer: https://github.com/jolespin/soothsayer + """ + accessory_scripts = { + "edgelist_to_clusters.py", + "mmseqs2_wrapper.py", + # "table_to_fasta.py", + } + + required_executables={ + # "fastANI", + "mmseqs", + } + + + required_executables |= accessory_scripts + + if opts.path_config == "CONDA_PREFIX": + executables = dict() + for name in sorted(required_executables): + if name not in accessory_scripts: + executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) + else: + if opts.path_config is None: + opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") + opts.path_config = format_path(opts.path_config) + assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) + assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) + df_config = pd.read_csv(opts.path_config, sep="\t") + assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) + df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) + # Get executable paths + executables = OrderedDict(zip(df_config["name"], df_config["executable"])) + assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) + + # Display + for name in sorted(accessory_scripts): + executables[name] = "'{}'".format(os.path.join(opts.script_directory, name)) # Can handle spaces in path + + print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) + for name, executable in executables.items(): + if name in required_executables: + print(name, executable, sep = " --> ", file=sys.stdout) + os.environ[name] = executable.strip() + print("", file=sys.stdout) + + +# Configure parameters +def configure_parameters(opts, directories): + + assert_acceptable_arguments(opts.algorithm, {"easy-cluster", "easy-linclust"}) + assert_acceptable_arguments(opts.sequence_space, {"protein", "nucleotide"}) + if opts.minimum_identity_threshold is None: + if opts.sequence_space == "protein": + opts.minimum_identity_threshold = PROTEIN_MINIMUM_IDENTITY_THRESHOLD + if opts.sequence_space == "nucleotide": + opts.minimum_identity_threshold = NUCLEOTIDE_MINIMUM_IDENTITY_THRESHOLD + + # Set environment variables + add_executables_to_environment(opts=opts) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -o -A 95 -a easy-cluster".format(__program__) + + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser_io = parser.add_argument_group('Required I/O arguments') + parser_io.add_argument("-i", "--core_pangenomes_table", type=str, default="stdin", help = "path/to/core_pangenomes_table.tsv, Format: Must include the follow columns (No header) [id_genome-cluster][core_pangenome(protein.fasta)][core_pangenome(cds.fasta)] but can include additional columns to the right (e.g., [metadata]). Suggested input is from `compile_core_pangenome_table.py` script. [Default: stdin]") + parser_io.add_argument("-n", "--protein_cluster_table", type=str, required=True, help = "path/to/protein_clusters.tsv, Format: Must include the following column [average_number_of_copies_per_genome] but can include additional columns. Suggested input is `protein_clusters.tsv` from the `cluster.py` module.") + parser_io.add_argument("-o","--output_directory", type=str, default="veba_output/markers", help = "path/to/project_directory [Default: veba_output/markers]") + + # Utility + parser_utility = parser.add_argument_group('Utility arguments') + parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future + parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") + parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") + parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) + # parser_utility.add_argument("--verbose", action='store_true') + + # MMSEQS2 + parser_mmseqs2 = parser.add_argument_group('MMSEQS2 arguments') + parser_mmseqs2.add_argument("-a", "--algorithm", type=str, default="easy-cluster", help="MMSEQS2 | {easy-cluster, easy-linclust} [Default: easy-cluster]") + parser_mmseqs2.add_argument("-s", "--sequence_space", type=str, default="nucleotide", help="MMSEQS2 | {protein, nucleotide} [Default: nucleotide]") + parser_mmseqs2.add_argument("-t", "--minimum_identity_threshold", type=float, help="MMSEQS2 | SLC-Specific Marker Cluster percent identity threshold (Range (0.0, 100.0]) [Default: {} for protein-space and {} for nucleotide-space]".format(PROTEIN_MINIMUM_IDENTITY_THRESHOLD, NUCLEOTIDE_MINIMUM_IDENTITY_THRESHOLD)) + parser_mmseqs2.add_argument("-c", "--minimum_coverage_threshold", type=float, default=0.8, help="MMSEQS2 | SSPC coverage threshold (Range (0.0, 1.0]) [Default: 0.8]") + parser_mmseqs2.add_argument("--marker_cluster_prefix", type=str, default="MGC-", help="Marker cluster prefix [Default: 'MGC-]") + parser_mmseqs2.add_argument("--marker_cluster_suffix", type=str, default="", help="Marker cluster suffix [Default: '") + parser_mmseqs2.add_argument("--marker_cluster_prefix_zfill", type=int, default=0, help="Cluster prefix zfill. Use 7 to match identifiers from OrthoFinder. Use 0 to add no zfill. [Default: 0]") #7 + parser_mmseqs2.add_argument("--mmseqs2_options", type=str, default="", help="MMSEQS2 | More options (e.g. --arg 1 ) [Default: '']") + + # Prevalence + parser_prevalence = parser.add_argument_group('Prevalence arguments') + parser_prevalence.add_argument("-M", "--maximum_number_of_copies_per_genome", type=float, default=1.0, help="Maximum number of copies per genome for a marker gene to be considered [Default: 1.0]") + parser_prevalence.add_argument("-m", "--minimum_number_of_copies_per_genome", type=float, default=1.0, help="Minimum number of copies per genome for a marker gene to be considered. If coreness is relaxed (not recommended) then this will need to be adjusted [Default: 1.0]") + + # Options + opts = parser.parse_args() + + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Threads + if opts.n_jobs == -1: + opts.n_jobs = cpu_count() + assert opts.n_jobs >= 1, "--n_jobs must be ≥ 1 (or -1 to use all available threads)" + + # Directories + directories = dict() + directories["project"] = create_directory(opts.output_directory) + directories["output"] = create_directory(os.path.join(directories["project"], "output")) + # if not opts.no_representative_sequences: + directories["marker_sequences"] = create_directory(os.path.join(directories["output"], "marker_sequences")) + # directories["serialization"] = create_directory(os.path.join(directories["output"], "serialization")) + # directories[("misc", "pangenomes", "networkx_graphs")] = create_directory(os.path.join(directories[("misc", "pangenomes")], "networkx_graphs")) + directories["log"] = create_directory(os.path.join(directories["project"], "log")) + directories["tmp"] = create_directory(os.path.join(directories["project"], "tmp")) + directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) + directories["intermediate"] = create_directory(os.path.join(directories["project"], "intermediate")) + os.environ["TMPDIR"] = directories["tmp"] + + # Make directories + t0 = time.time() + print(format_header(" " .join(["* ({}) Creating directories:".format(format_duration(t0)), directories["intermediate"]])), file=sys.stdout) + os.makedirs(opts.output_directory, exist_ok=True) + + # Info + print(format_header(__program__, "="), file=sys.stdout) + print(format_header("Configuration:", "-"), file=sys.stdout) + print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) + print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("Script version:", __version__, file=sys.stdout) + print("Moment:", get_timestamp(), file=sys.stdout) + print("Directory:", os.getcwd(), file=sys.stdout) + print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) + configure_parameters(opts, directories) + sys.stdout.flush() + + # Load core pangenome table + if opts.core_pangenomes_table == "stdin": + opts.core_pangenomes_table = sys.stdin + df_core_pangenomes = pd.read_csv(opts.core_pangenomes_table, sep="\t", header=None) + assert df_core_pangenomes.shape[1] >= 3, "Must include the follow columns (No header) [id_genome_cluster][proteins][cds] but can include additional columns to the right (e.g., [metadata]). Suggested input is from `compile_core_pangenome_table.py` script. You have provided a table with {} columns.".format(df_core_pangenomes.shape[1]) + df_core_pangenomes = df_core_pangenomes.iloc[:,:3] + df_core_pangenomes.columns = ["id_genome_cluster", "proteins", "cds"] + df_core_pangenomes = df_core_pangenomes.set_index("id_genome_cluster") + assert not np.any(df_core_pangenomes.isnull()), "--core_pangenomes_table has missing values. Please correct this.\n{}".format(df_core_pangenomes.loc[df_core_pangenomes.isnull().sum(axis=1)[lambda x: x > 0].index].to_string()) + + # Load protein clusters + df_protein_clusters = pd.read_csv(opts.protein_cluster_table, sep="\t", index_col=0) + assert "average_number_of_copies_per_genome" in df_protein_clusters.columns, "--protein_cluster_table must have a column called `average_number_of_copies_per_genome` where each entry is a float ≥ 1.0" + proteincluster_to_copy_number = df_protein_clusters["average_number_of_copies_per_genome"] + # assert np.all(proteincluster_to_copies >= opts.minimum_number_of_copies_per_genome), "Some of the protein clusters are in less than 1 copy per genome" + + # Check overlap between inputs + cluster_to_protein_sequence = dict() + cluster_to_nucleotide_sequence = dict() + proteincluster_to_genomecluster = dict() + + for id_genome_cluster, row in tqdm(df_core_pangenomes.iterrows(), "Reading panproteomes from genome clusters", total=df_core_pangenomes.shape[0], file=sys.stdout, unit=" Pangenomes"): + + # Proteins + with get_file_object(row["proteins"], mode="read", compression="infer", safe_mode="infer", verbose=False) as f: + for header, seq in SimpleFastaParser(f): + id = header.split(" ")[0] + assert id not in cluster_to_protein_sequence, f"{id} from {id_genome_cluster} is a duplicate in protein-space" + cluster_to_protein_sequence[id] = seq + proteincluster_to_genomecluster[id] = id_genome_cluster + # Nucleotides + with get_file_object(row["cds"], mode="read", compression="infer", safe_mode="infer", verbose=False) as f: + for header, seq in SimpleFastaParser(f): + id = header.split(" ")[0] + assert id not in cluster_to_nucleotide_sequence, f"{id} from {id_genome_cluster} is a duplicate in nucleotide-space" + cluster_to_nucleotide_sequence[id] = seq + + # Overlap between protein and nucleotide space + clusters_in_protein_space = set(cluster_to_protein_sequence.keys()) + clusters_in_nucleotide_space = set(cluster_to_nucleotide_sequence.keys()) + A = len(clusters_in_protein_space - clusters_in_nucleotide_space) + B = len(clusters_in_nucleotide_space - clusters_in_protein_space) + assert clusters_in_protein_space == clusters_in_nucleotide_space, "Identifiers from core pangenomes table do not overlap in protein and nucleotide space.\n\nNumber of unique identifiers in protein space: {} \nNumber of unique identifiers in nucleotide space: {}".format(A, B) + proteincluster_identifiers = clusters_in_protein_space | clusters_in_nucleotide_space + del clusters_in_protein_space + del clusters_in_nucleotide_space + + # Overlap between core protein clusters and copy number table + clusters_with_average_copy_number = set(proteincluster_to_copy_number.index) + assert proteincluster_identifiers <= clusters_with_average_copy_number, "Not all core protein clusters from --core_pangenomes_table are in --protein_clusters: {} protein clusters".format(len(proteincluster_identifiers - clusters_with_average_copy_number)) + del clusters_with_average_copy_number + proteincluster_identifiers = list(proteincluster_identifiers) + + # Index only the core protein clusters + proteincluster_to_copy_number = proteincluster_to_copy_number[proteincluster_identifiers] + + # Remove protein clusters that aren't within the range of accepted copy numbers + f_passed = open(os.path.join(directories["intermediate"], "passed_qc.list"), "w") + f_failed = open(os.path.join(directories["intermediate"], "failed_qc.list"), "w") + + for id, n in proteincluster_to_copy_number.items(): + if opts.minimum_number_of_copies_per_genome <= n <= opts.maximum_number_of_copies_per_genome: + print(id, file=f_passed) + else: + print(id, file=f_failed) + del cluster_to_protein_sequence[id] + del cluster_to_nucleotide_sequence[id] + del proteincluster_to_copy_number[id] + del proteincluster_to_genomecluster[id] + + f_passed.close() + f_failed.close() + + cluster_to_protein_sequence = pd.Series(cluster_to_protein_sequence) + cluster_to_nucleotide_sequence = pd.Series(cluster_to_nucleotide_sequence) + + # Write sequences to use for clustering + if opts.sequence_space == "protein": + write_fasta(cluster_to_protein_sequence, os.path.join(directories["tmp"], "sequences.fasta")) + if opts.sequence_space == "nucleotide": + write_fasta(cluster_to_nucleotide_sequence, os.path.join(directories["tmp"], "sequences.fasta")) + + # MMSEQS2 + f_cmds = open(os.path.join(opts.output_directory, "commands.sh"), "w") + + print(format_header(" * ({}) Running MMSEQS2:".format(format_duration(t0))), file=sys.stdout) + + # Run MMSEQS2 + name = "mmseqs2__{}-space".format(opts.sequence_space) + description = "[Program = MMSEQS2] [Sequence Space = {}]".format(opts.sequence_space) + + cmd = Command([ + os.environ["mmseqs2_wrapper.py"], + "--fasta {}".format(os.path.join(directories["tmp"], "sequences.fasta" )), + "--output_directory {}".format(directories["intermediate"]), + "--algorithm {}".format(opts.algorithm), + "--n_jobs {}".format(opts.n_jobs), + "--minimum_identity_threshold {}".format(opts.minimum_identity_threshold), + "--minimum_coverage_threshold {}".format(opts.minimum_coverage_threshold), + "--mmseqs2_options='{}'" if bool(opts.mmseqs2_options) else "", + "--cluster_prefix {}".format(opts.marker_cluster_prefix), + "--cluster_suffix {}".format(opts.marker_cluster_suffix) if bool(opts.marker_cluster_suffix) else "", + "--cluster_prefix_zfill {}".format(opts.marker_cluster_prefix_zfill), + "--basename marker_clusters", + "--identifiers {}".format(os.path.join(directories["intermediate"], "passed_qc.list")), + "--no_sequences_and_header", + "--representative_output_format table", + + "&&", + + "rm -rf", + os.path.join(directories["tmp"], "sequences.fasta" ), + ], + name=name, + f_cmds=f_cmds, + ) + + # Run command + cmd.run( + checkpoint_message_notexists="[Running ({})] | {}".format(format_duration(t0), description), + checkpoint_message_exists="[Loading Checkpoint ({})] | {}".format(format_duration(t0), description), + write_stdout=os.path.join(directories["log"], "{}.o".format(name)), + write_stderr=os.path.join(directories["log"], "{}.e".format(name)), + write_returncode=os.path.join(directories["log"], "{}.returncode".format(name)), + checkpoint=os.path.join(directories["checkpoints"], name), + ) + if hasattr(cmd, "returncode_"): + if cmd.returncode_ != 0: + print("[Error] | {}".format(description), file=sys.stdout) + print("Check the following files:\ncat {}".format(os.path.join(directories["log"], "{}.*".format(name))), file=sys.stdout) + sys.exit(cmd.returncode_) + + f_cmds.close() + + # Marker gene cluster prevalence + print(format_header(" * ({}) Calculating marker prevalence:".format(format_duration(t0))), file=sys.stdout) + + marker_to_representative = pd.read_csv(os.path.join(directories["intermediate"],"output", "representative_sequences.tsv.gz"), sep="\t", index_col=0, header=None).iloc[:,0] + + df_markers = pd.read_csv(os.path.join(directories["intermediate"],"output", "marker_clusters.tsv"), sep="\t", header=None) + df_markers.columns = ["id_protein_cluster", "id_marker_gene_cluster"] + df_markers.insert(0, "id_genome_cluster", df_markers["id_protein_cluster"].map(lambda x: proteincluster_to_genomecluster[x])) + + df_prevalence = get_marker_gene_cluster_prevalence(df_markers) + df_prevalence.to_csv(os.path.join(directories["output"], "prevalence_table.tsv.gz"), sep="\t") + + maximum_prevalence = df_prevalence.max(axis=0) + maximum_prevalence_gt1 = maximum_prevalence > 1 + + marker_proteins_with_maximum_prevalence_gt1 = maximum_prevalence.index[maximum_prevalence_gt1] + + if len(marker_proteins_with_maximum_prevalence_gt1) > 1: + warnings.warn( + """ + When clustering at {} percent identity and {} coverage in {}-space, {}/{} of the markers have a prevalence > 1. + You may need to increase your --minimum_identity_threshold or --minimum_coverage_threshold or switch sequence-spaces. + If you used VEBA for pangenomes, then the genes were clustered in protein-space and there isn't a 1-to-1 overlap between protein-space and nucleotide-space cutoffs. + """.format( + opts.minimum_identity_threshold, + opts.minimum_coverage_threshold, + opts.sequence_space, + len(marker_proteins_with_maximum_prevalence_gt1), + maximum_prevalence.size, + ) + ) + + with open(os.path.join(directories["intermediate"], "multiple_copy_marker_gene_clusters.list"), "w") as f: + for id_marker_cluster in marker_proteins_with_maximum_prevalence_gt1: + print(id_marker_cluster, file=f) + + df_prevalence = df_prevalence.drop(marker_proteins_with_maximum_prevalence_gt1, axis=1) + + df_prevalence = df_prevalence.applymap(lambda x: opts.minimum_number_of_copies_per_genome <= x <= opts.maximum_number_of_copies_per_genome).astype(int) + marker_clusters = df_prevalence.sum(axis=0)[lambda x: x == 1].index + markercluster_to_genomecluster = df_prevalence.loc[:,marker_clusters].idxmax(axis=0) + + # Identifier mapping for protein clusters + print(format_header(" * ({}) Writing output tables:".format(format_duration(t0))), file=sys.stdout) + + proteincluster_to_markercluster = pd.read_csv(os.path.join(directories["intermediate"], "output", "marker_clusters.tsv"), sep="\t", index_col=0, header=None).iloc[:,0] + df_proteinclusters = proteincluster_to_markercluster.loc[proteincluster_to_markercluster.map(lambda x: x in marker_clusters)].to_frame("id_marker_cluster") + df_proteinclusters["id_genome_cluster"] = df_proteinclusters["id_marker_cluster"].map(lambda x: markercluster_to_genomecluster[x]) + df_proteinclusters.index.name = "id_protein_cluster" + df_proteinclusters.to_csv(os.path.join(directories["output"], "identifier_mapping.protein_clusters.tsv"), sep="\t") + + # Identifier mapping for marker clusters + df_markers = markercluster_to_genomecluster.to_frame("id_genome_cluster") + df_markers["id_protein_cluster_representative"] = marker_to_representative[df_markers.index] + df_markers.to_csv(os.path.join(directories["output"], "identifier_mapping.marker_clusters.tsv"), sep="\t") + + # Pangenome clusters + df_pangenome_markers = markercluster_to_genomecluster.groupby(markercluster_to_genomecluster).apply(lambda x: set(x.index)).to_frame("markers") + df_pangenome_markers.insert(0, "number_of_markers", df_pangenome_markers["markers"].map(len)) + df_pangenome_markers.index.name = "id_genome_cluster" + df_pangenome_markers.to_csv(os.path.join(directories["output"], "pangenome_markers.tsv"), sep="\t") + + # Marker sequences + print(format_header(" * ({}) Writing marker gene sequences:".format(format_duration(t0))), file=sys.stdout) + for id_genome_cluster, marker_clusters in tqdm(df_pangenome_markers["markers"].items(), total=df_pangenome_markers.shape[0], unit=" Pangenomes", file=sys.stdout): + f_proteins = open(os.path.join(directories["marker_sequences"], f"{id_genome_cluster}.faa"), "w") + f_cds = open(os.path.join(directories["marker_sequences"], f"{id_genome_cluster}.ffn"), "w") + + for id_marker_cluster in sorted(marker_clusters): + id_protein_cluster_representative = marker_to_representative[id_marker_cluster] + id_genome_cluster = markercluster_to_genomecluster[id_marker_cluster] + header = "{} {} {}".format(id_marker_cluster, id_genome_cluster, id_protein_cluster_representative) + + # Proteins + print(">{}\n{}".format(header, cluster_to_protein_sequence[id_protein_cluster_representative]), file=f_proteins) + print(">{}\n{}".format(header, cluster_to_nucleotide_sequence[id_protein_cluster_representative]), file=f_cds) + f_proteins.close() + f_cds.close() + +if __name__ == "__main__": + main(sys.argv[1:]) \ No newline at end of file diff --git a/src/scripts/merge_annotations.py b/src/scripts/merge_annotations.py index 73be360..2117437 100755 --- a/src/scripts/merge_annotations.py +++ b/src/scripts/merge_annotations.py @@ -6,7 +6,7 @@ from soothsayer_utils import read_hmmer, pv, get_file_object, assert_acceptable_arguments, format_header __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.20" +__version__ = "2023.10.10" # disclaimer = format_header("DISCLAIMER: Lineage predictions are NOT robust and DO NOT USE CORE MARKERS. Please only use for exploratory suggestions.") @@ -202,7 +202,14 @@ def main(args=None): print(" *!* HMMSearch table [AntiFam] is empty", file=sys.stderr) print(" * Reading KOFAMSCAN table [KEGG]: {}".format(opts.kofam), file=sys.stderr) - df_kofamscan = pd.read_csv(opts.kofam, sep="\t", index_col=None, header=None) + columns = ["significance", "id_protein", "id_hmm", "score","score2", "evalue", "name_hmm"] + try: + df_kofamscan = pd.read_csv(opts.kofam, sep="\t", index_col=None, header=None) + df_kofamscan.columns = columns + except pd.errors.EmptyDataError: + df_kofamscan = pd.DataFrame(columns=columns) + + if not df_kofamscan.empty: proteins = proteins | set(df_kofamscan.iloc[:,1]) else: diff --git a/src/scripts/merge_genome_quality_assessments.py b/src/scripts/merge_genome_quality_assessments.py new file mode 100755 index 0000000..a9a8be7 --- /dev/null +++ b/src/scripts/merge_genome_quality_assessments.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +import sys, os, glob, argparse +from collections import defaultdict +import pandas as pd + +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2021.10.16" + +def get_prokaryotic_description(x, fields=["Completeness_Model_Used", "Additional_Notes"]): + output = list() + for k, v in x.reindex(fields).items(): + if pd.notnull(v): + if k == "Completeness_Model_Used": + k = "checkm2_model" + if k == "Additional_Notes": + k = "warnings" + label = "[{}={}]".format(k,v) + output.append(label) + return "".join(output) + +def get_viral_description(x, fields=["checkv_quality", "miuvig_quality", "aai_confidence", "hmm_completeness_upper", "virus_score"]): + output = list() + for k, v in x.reindex(fields).items(): + if pd.notnull(v): + label = "[{}={}]".format(k,v) + output.append(label) + return "".join(output) + +def get_eukaryotic_description(x, fields=["one_line_summary", "dataset_name"]): + output = list() + for k, v in x.reindex(fields).items(): + if pd.notnull(v): + if k == "one_line_summary": + k = "busco_notation" + if k == "dataset_name": + k = "busco_marker_set" + label = "[{}={}]".format(k,v) + output.append(label) + return "".join(output) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + parser.add_argument("-i","--binning_directory", type=str, default="veba_output/binning", help = "path/to/binning_directory. Assumes directory structure is [binning_directory]/[domain]/[sample]/output/[quality].tsv [Default: veba_output/binning]") + parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/merge_genome_quality.tsv [Default: stdout]") + parser.add_argument("-V","--viral_subdirectory_name", type=str, default="viral", help = "Viral subdirectory label in --binning_directory [Default: viral]") + parser.add_argument("-P","--prokaryotic_subdirectory_name", type=str, default="prokaryotic", help = "Prokaryotic subdirectory label in --binning_directory [Default: prokaryotic]") + parser.add_argument("-E","--eukaryotic_subdirectory_name", type=str, default="eukaryotic", help = "Eukaryotic subdirectory label in --binning_directory [Default: eukaryotic]") + parser.add_argument("--no_header", action="store_true", help = "Don't write header") + + + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Output + if opts.output == "stdout": + opts.output = sys.stdout + + # Concatenated + genome_quality_dataframes = list() + + # Prokaryotic + prokaryotic_genome_quality_files = glob.glob(os.path.join(opts.binning_directory, opts.prokaryotic_subdirectory_name, "*", "output", "checkm2_results.filtered.tsv")) + if prokaryotic_genome_quality_files: + print("* Compiling prokaryotic genome quality from following files:", *prokaryotic_genome_quality_files, sep="\n ", file=sys.stdout) + + for fp in prokaryotic_genome_quality_files: + id_domain = fp.split("/")[-4] + id_sample = fp.split("/")[-3] + df_checkm2 = pd.read_csv(fp, sep="\t", index_col=0) + df_unified = df_checkm2.loc[:,["Completeness", "Contamination"]] + df_unified["description"] = df_checkm2.apply(get_prokaryotic_description, axis=1) + df_unified.columns = ["completeness", "contamination", "description"] + df_unified.insert(0, "domain", id_domain) + df_unified.insert(1, "id_sample", id_sample) + df_unified.index.name = "id_genome" + genome_quality_dataframes.append(df_unified) + + else: + print("Could not find any prokaryotic genome assessment tables from CheckM2 in the following directory: {}".format(opts.binning_directory), file=sys.stdout) + + # Viral + viral_genome_quality_files = glob.glob(os.path.join(opts.binning_directory, opts.viral_subdirectory_name, "*", "output", "checkmv_results.filtered.tsv")) + if viral_genome_quality_files: + print("* Compiling viral genome quality from following files:", *viral_genome_quality_files, sep="\n ", file=sys.stdout) + + for fp in viral_genome_quality_files: + id_domain = fp.split("/")[-4] + id_sample = fp.split("/")[-3] + df_checkv = pd.read_csv(fp, sep="\t", index_col=0) + df_unified = df_checkv.loc[:,["completeness", "contamination"]] + df_unified["description"] = df_checkv.apply(get_viral_description, axis=1) + df_unified.columns = ["completeness", "contamination", "description"] + df_unified.insert(0, "domain", id_domain) + df_unified.insert(1, "id_sample", id_sample) + df_unified.index.name = "id_genome" + genome_quality_dataframes.append(df_unified) + + else: + print("Could not find any viral genome assessment tables from CheckV in the following directory: {}".format(opts.binning_directory), file=sys.stdout) + + # Eukaryotic + eukaryotic_genome_quality_files = glob.glob(os.path.join(opts.binning_directory, opts.eukaryotic_subdirectory_name, "*", "output", "busco_results.filtered.tsv")) + if eukaryotic_genome_quality_files: + print("* Compiling eukaryotic genome quality from following files:", *eukaryotic_genome_quality_files, sep="\n ", file=sys.stdout) + + for fp in eukaryotic_genome_quality_files: + id_domain = fp.split("/")[-4] + id_sample = fp.split("/")[-3] + df_busco = pd.read_csv(fp, sep="\t", index_col=0, header=[0,1]) + use_generic = True + if "specific" in df_busco.columns.get_level_values(0): + if not df_busco[("specific", "Complete")].dropna().empty: + use_generic = False + if use_generic: + df_busco = df_busco["generic"] + else: + df_busco = df_busco["specific"] + + df_unified = df_busco.loc[:,["Complete", "Multi copy"]] + df_unified["description"] = df_busco.apply(get_eukaryotic_description, axis=1) + + df_unified.columns = ["completeness", "contamination", "description"] + df_unified.insert(0, "domain", id_domain) + df_unified.insert(1, "id_sample", id_sample) + df_unified.index.name = "id_genome" + genome_quality_dataframes.append(df_unified) + + else: + print("Could not find any eukaryotic genome assessment tables from BUSCO in the following directory: {}".format(opts.binning_directory), file=sys.stdout) + + assert len(genome_quality_dataframes) > 0, "No genome quality assessment tables were found in the following directory: {}".format(opts.binning_directory) + df_genome_quality = pd.concat(genome_quality_dataframes, axis=0) + df_genome_quality.to_csv(opts.output, sep="\t", header=not bool(opts.no_header)) + + +if __name__ == "__main__": + main() + + + diff --git a/src/scripts/merge_taxonomy_classifications.py b/src/scripts/merge_taxonomy_classifications.py index 908bd13..46a2c9d 100755 --- a/src/scripts/merge_taxonomy_classifications.py +++ b/src/scripts/merge_taxonomy_classifications.py @@ -5,7 +5,7 @@ from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2021.5.12" +__version__ = "2021.10.11" def main(args=None): # Path info @@ -22,6 +22,9 @@ def main(args=None): # Pipeline parser.add_argument("-i","--classify_directory", type=str, default="veba_output/classify", help = "path/to/classify_directory. Assumes directory structure is [classification_directory]/[domain]/output/taxonomy.tsv/[taxonomy.clusters.tsv] [Default: veba_output/classify]") parser.add_argument("-o","--output_directory", type=str, help = "path/to/output_directory [Default: veba_output/classify]", default="veba_output/classify") + parser.add_argument("-d","--no_domain", action="store_true", help = "Don't domain in output") + parser.add_argument("--no_header", action="store_true", help = "Don't write header") + # Options opts = parser.parse_args() @@ -39,12 +42,13 @@ def main(args=None): for fp in cluster_taxonomy: id_domain = fp.split("/")[-3] df = pd.read_csv(fp, sep="\t", index_col=0) - df.insert(loc=0, column="domain", value=id_domain) + if not opts.no_domain: + df.insert(loc=0, column="domain", value=id_domain) cluster_dataframes.append(df) df_taxonomy_clusters = pd.concat(cluster_dataframes, axis=0) df_taxonomy_clusters.index.name = "id_genome_cluster" print("\tWriting genome cluster taxonomy output for {} clusters: {}".format(df_taxonomy_clusters.shape[0], os.path.join(opts.output_directory, "taxonomy_classifications.clusters.tsv")), file=sys.stdout) - df_taxonomy_clusters.to_csv(os.path.join(opts.output_directory, "taxonomy_classifications.clusters.tsv"), sep="\t") + df_taxonomy_clusters.to_csv(os.path.join(opts.output_directory, "taxonomy_classifications.clusters.tsv"), sep="\t", header=not bool(opts.no_header)) else: print("Could not find any cluster taxonomy tables in the following directory: {}".format(opts.classify_directory), file=sys.stdout) @@ -69,6 +73,10 @@ def main(args=None): genome_to_data[id_genome] = {"domain":id_domain, "taxonomy_classification":taxonomy} df_taxonomy_genomes = pd.DataFrame(genome_to_data).T df_taxonomy_genomes = df_taxonomy_genomes.loc[:,["domain", "taxonomy_classification"]] + if opts.no_domain: + df_taxonomy_genomes = df_taxonomy_genomes.drop(["domain"], axis=1) + + df_taxonomy_genomes.index.name = "id_genome" # if df_taxonomy_clusters is not None: # mag_to_slc = dict() @@ -79,7 +87,7 @@ def main(args=None): # mag_to_slc = pd.Series(mag_to_slc) # df_taxonomy_genomes.insert(loc=1, column="id_genome_cluster", value=mag_to_slc) print("\tWriting genome taxonomy output for {} genomes: {}".format(df_taxonomy_genomes.shape[0], os.path.join(opts.output_directory, "taxonomy_classifications.tsv")), file=sys.stdout) - df_taxonomy_genomes.to_csv(os.path.join(opts.output_directory, "taxonomy_classifications.tsv"), sep="\t") + df_taxonomy_genomes.to_csv(os.path.join(opts.output_directory, "taxonomy_classifications.tsv"), sep="\t", header=not bool(opts.no_header)) else: print("Could not find any taxonomy tables in the following directory: {}".format(opts.classify_directory), file=sys.stdout) diff --git a/src/scripts/mmseqs2_wrapper.py b/src/scripts/mmseqs2_wrapper.py index 4db4fd7..bf31c15 100755 --- a/src/scripts/mmseqs2_wrapper.py +++ b/src/scripts/mmseqs2_wrapper.py @@ -12,7 +12,7 @@ # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.6.13" +__version__ = "2023.9.15" # Check def get_check_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -77,9 +77,10 @@ def get_compile_cmd(input_filepaths, output_filepaths, output_directory, directo os.environ["reformat_representative_sequences.py"], "-c {}".format(os.path.join(output_directory, "{}.tsv".format(opts.basename))), "-i {}".format(input_filepaths[1]), - "-f table", - "-o {}".format(os.path.join(output_directory, "representative_sequences.tsv.gz")), + "-f {}".format(opts.representative_output_format), + "-o {}".format(output_filepaths[1]), ] + if opts.no_sequences_and_header: cmd += [ "--no_sequences", @@ -241,10 +242,11 @@ def create_pipeline(opts, directories, f_cmds): input_filepaths = output_filepaths output_filenames = [ "{}.tsv".format(opts.basename), - # "{}.networkx_graph.pkl".format(opts.basename), - # "{}.dict.pkl".format(opts.basename), - "representative_sequences.tsv.gz", ] + if opts.representative_output_format == "table": + output_filenames += ["representative_sequences.tsv.gz"] + if opts.representative_output_format == "fasta": + output_filenames += ["representative_sequences.fasta.gz"] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) params = { @@ -274,7 +276,7 @@ def create_pipeline(opts, directories, f_cmds): def configure_parameters(opts, directories): assert_acceptable_arguments(opts.algorithm, {"easy-cluster", "easy-linclust"}) - + assert_acceptable_arguments(opts.representative_output_format, {"table", "fasta"}) # Set environment variables add_executables_to_environment(opts=opts) @@ -317,6 +319,7 @@ def main(args=None): parser_mmseqs2.add_argument("--mmseqs2_options", type=str, default="", help="MMSEQS2 | More options (e.g. --arg 1 ) [Default: '']") parser_mmseqs2.add_argument("--identifiers", type=str, help = "Identifiers to include for `edgelist_to_clusters.py`. If missing identifiers and singletons are allowed, then they will be included as singleton clusters with weight of np.inf") parser_mmseqs2.add_argument("--no_sequences_and_header", action="store_true", help = "Don't include sequences or header in table. Useful for concatenation and reduced redundancy of sequences") + parser_mmseqs2.add_argument("-f","--representative_output_format", type=str, default="fasta", help = "Format of output for representative sequences: {table, fasta} [Default: fasta]") # Should fasta be the new default? # Options opts = parser.parse_args() diff --git a/src/scripts/prokaryotic_gene_modeling_wrapper.py b/src/scripts/prokaryotic_gene_modeling_wrapper.py new file mode 100644 index 0000000..c1aed67 --- /dev/null +++ b/src/scripts/prokaryotic_gene_modeling_wrapper.py @@ -0,0 +1,1410 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob, shutil +from collections import OrderedDict, defaultdict + +import pandas as pd +import numpy as np + +# Soothsayer Ecosystem +from genopype import * +from soothsayer_utils import * + +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.8.28" + +# Pyrodigal +def get_pyrodigal_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + cmd = [ + "cat", + input_filepaths[0], + "|", + os.environ["seqkit"], + "seq", + "-m {}".format(opts.minimum_contig_length), + ">", + os.path.join(directories["tmp"], "tmp.fasta"), + + "&&", + + os.environ["pyrodigal"], + "-p meta", + "-i {}".format(os.path.join(directories["tmp"], "tmp.fasta")), + "-g {}".format(opts.pyrodigal_genetic_code), + "-f gff", + "-d {}".format(os.path.join(output_directory, "gene_models.ffn")), + "-a {}".format(os.path.join(output_directory, "gene_models.faa")), + "--min-gene {}".format(opts.pyrodigal_minimum_gene_length), + "--min-edge-gene {}".format(opts.pyrodigal_minimum_edge_gene_length), + "--max-overlap {}".format(opts.pyrodigal_maximum_gene_overlap_length), + # "-j {}".format(opts.n_jobs), + ">", + os.path.join(directories["tmp"], "tmp.gff"), + + "&&", + + "cat", + os.path.join(directories["tmp"], "tmp.gff"), + "|", + os.environ["append_geneid_to_prodigal_gff.py"], + "-a gene_id", + ">", + os.path.join(output_directory, "gene_models.gff"), + + "&&", + + "rm", + os.path.join(directories["tmp"], "tmp.*") + + ] + return cmd + + +# Prodigal +def get_prodigal_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + "cat", + os.path.join(input_filepaths[0], "*.fa"), + "|", + os.environ["prodigal-gv"], + "-p meta", + "-g {}".format(opts.prodigal_genetic_code), + "-f gff", + "-d {}".format(os.path.join(output_directory, "gene_models.ffn")), + "-a {}".format(os.path.join(output_directory, "gene_models.faa")), + "|", + os.environ["append_geneid_to_prodigal_gff.py"], + "-a gene_id", + ">", + os.path.join(output_directory, "gene_models.gff"), + + "&&", + + os.environ["partition_gene_models.py"], + "-i {}".format(input_filepaths[1]), + "-g {}".format(os.path.join(output_directory, "gene_models.gff")), + "-d {}".format(os.path.join(output_directory, "gene_models.ffn")), + "-a {}".format(os.path.join(output_directory, "gene_models.faa")), + "-o {}".format(os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes")), + +""" + +OUTPUT_DIRECTORY={} + +for GENOME_FASTA in {}; +do + ID=$(basename $GENOME_FASTA .fa) + DIR_GENOME=$(dirname $GENOME_FASTA) + GFF_CDS=$DIR_GENOME/$ID.gff + GFF_OUTPUT=$OUTPUT_DIRECTORY/$ID.gff + >$GFF_OUTPUT.tmp + {} -f $GENOME_FASTA -o $GFF_OUTPUT.tmp -n $ID -c $GFF_CDS -d Virus + mv $GFF_OUTPUT.tmp $GFF_OUTPUT +done + +""".format( + os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes"), + os.path.join(directories[("intermediate", "2__checkv")],"filtered", "genomes", "*.fa"), + os.environ["compile_gff.py"], + ), + + + "rm -rf", + os.path.join(output_directory, "gene_models.gff"), + os.path.join(output_directory, "gene_models.ffn"), + os.path.join(output_directory, "gene_models.faa"), + + ] + return cmd + + +# Pyrodigal +def get_pyrodigal_cmd(input_filepaths, output_filepaths, output_directory, directories, opts, genetic_code): + + cmd = [ + "cat", + opts.fasta, + "|", + os.environ["seqkit"], + "grep", + "-f {}".format(input_filepaths[0]), + ">", + os.path.join(directories["tmp"], "tmp.fasta"), + + "&&", + + # Placeholder + "OUTPUT_DIRECTORY={}; for ID in $(cat {}); do >$OUTPUT_DIRECTORY/$ID.faa; >$OUTPUT_DIRECTORY/$ID.ffn; >$OUTPUT_DIRECTORY/$ID.gff; done".format(output_directory, input_filepaths[1]), + + "&&", + + # Run analysis + os.environ["pyrodigal"], + "-p meta", + "-i {}".format(os.path.join(directories["tmp"], "tmp.fasta")), + "-g {}".format(genetic_code), + "-f gff", + "-d {}".format(os.path.join(output_directory, "{}.ffn".format(opts.basename))), + "-a {}".format(os.path.join(output_directory, "{}.faa".format(opts.basename))), + "--min-gene {}".format(opts.pyrodigal_minimum_gene_length), + "--min-edge-gene {}".format(opts.pyrodigal_minimum_edge_gene_length), + "--max-overlap {}".format(opts.pyrodigal_maximum_gene_overlap_length), + # "-j {}".format(opts.n_jobs), + ">", + os.path.join(directories["tmp"], "tmp.gff"), + + "&&", + + "cat", + os.path.join(directories["tmp"], "tmp.gff"), + "|", + os.environ["append_geneid_to_prodigal_gff.py"], + "-a gene_id", + ">", + os.path.join(output_directory, "{}.gff".format(opts.basename)), + + "&&", + + "rm", + os.path.join(directories["tmp"], "tmp.*"), + ] + + if opts.scaffolds_to_bins: + cmd += [ + # Partition the gene models and genomes + "&&", + + os.environ["partition_gene_models.py"], + "-i {}".format(opts.scaffolds_to_bins), + # "-f {}".format(opts.fasta), + "-g {}".format(os.path.join(output_directory, "{}.gff".format(opts.basename))), + "-d {}".format(os.path.join(output_directory, "{}.ffn".format(opts.basename))), + "-a {}".format(os.path.join(output_directory, "{}.faa".format(opts.basename))), + "-o {}".format(os.path.join(output_directory)), + "--use_mag_as_description", + + "&&", + + "rm -rf", + os.path.join(output_directory, "{}.*".format(opts.basename)), + + ] + + return cmd + +# barrnap +def get_barrnap_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + "cat", + input_filepaths[0], + ">", + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + +""" +OUTPUT_DIRECTORY={} +FP={} +for DOMAIN in $(cut -f2 $FP | sort -u); +do + DOMAIN_ABBREVIATION=$(echo $DOMAIN | python -c 'import sys; print(sys.stdin.read().lower()[:3])') + + # Get MAGs for each domain (not all will have passed QC) + for ID in $(cat $FP | grep $DOMAIN | cut -f1) + do + GENOME_FASTA=$(ls {}) || GENOME_FASTA="" + if [ -e "$GENOME_FASTA" ]; then + >$OUTPUT_DIRECTORY/$ID.rRNA + >$OUTPUT_DIRECTORY/$ID.rRNA.gff + {} --kingdom $DOMAIN_ABBREVIATION --threads {} --lencutoff {} --reject {} --evalue {} --outseq $OUTPUT_DIRECTORY/$ID.rRNA $GENOME_FASTA | {} > $OUTPUT_DIRECTORY/$ID.rRNA.gff + rm $GENOME_FASTA.fai + fi + done +done + +rm -f {} +""".format( + output_directory, + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + os.path.join(os.path.split(input_filepaths[1])[0],"$ID.fa"), + os.environ["barrnap"], + opts.n_jobs, + opts.barrnap_length_cutoff, + opts.barrnap_reject, + opts.barrnap_evalue, + os.environ["append_geneid_to_barrnap_gff.py"], + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + ), + ] + return cmd + +# tRNAscan-SE +def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + cmd = [ + "cat", + input_filepaths[0], + ">", + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + + +""" +OUTPUT_DIRECTORY={} +FP={} +for DOMAIN in $(cut -f2 $FP | sort -u); +do + DOMAIN_ABBREVIATION=$(echo $DOMAIN | python -c 'import sys; print(sys.stdin.read().upper()[:1])') + + # Get MAGs for each domain (not all will have passed QC) + for ID in $(cat $FP | grep $DOMAIN | cut -f1) + do + GENOME_FASTA=$(ls {}) || GENOME_FASTA="" + if [ -e "$GENOME_FASTA" ]; then + >$OUTPUT_DIRECTORY/$ID.tRNA + >$OUTPUT_DIRECTORY/$ID.tRNA.gff + >$OUTPUT_DIRECTORY/$ID.tRNA.struct + >$OUTPUT_DIRECTORY/$ID.tRNA.txt + {} -$DOMAIN_ABBREVIATION --forceow --progress --threads {} --fasta $OUTPUT_DIRECTORY/$ID.tRNA --gff $OUTPUT_DIRECTORY/$ID.tRNA.gff --struct $OUTPUT_DIRECTORY/$ID.tRNA.struct {} $GENOME_FASTA > $OUTPUT_DIRECTORY/$ID.tRNA.txt + fi + done +done + +rm -f {} +""".format( + output_directory, + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + os.path.join(os.path.split(input_filepaths[1])[0],"$ID.fa"), + os.environ["tRNAscan-SE"], + opts.n_jobs, + opts.trnascan_options, + os.path.join(directories["tmp"], "genomes_to_domain.tsv"), + ) + ] + return cmd + +def get_symlink_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Nuclear + cmd = [ + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.metaeuk.tsv $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "2__metaeuk")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "5__barrnap-nuclear")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + output_directory, + directories[("intermediate", "8__trnascan-nuclear")], + ), + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "2__metaeuk")], + directories[("intermediate", "5__barrnap-nuclear")], + directories[("intermediate", "8__trnascan-nuclear")], + output_directory, + ) + + + ] + + # Mitochondrion + cmd += [ + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "3__pyrodigal-mitochondrion")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "6__barrnap-mitochondrion")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"mitochondrion"), + directories[("intermediate", "9__trnascan-mitochondrion")], + ), + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "3__pyrodigal-mitochondrion")], + directories[("intermediate", "6__barrnap-mitochondrion")], + directories[("intermediate", "9__trnascan-mitochondrion")], + os.path.join(output_directory, "mitochondrion"), + ) + ] + + # Plastid + cmd += [ + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.faa $SRC_DIRECTORY/*.ffn $SRC_DIRECTORY/identifier_mapping.tsv; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "4__pyrodigal-plastid")], + ), + + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.rRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "7__barrnap-plastid")], + ), + + "&&", + + "DST={}; SRC_DIRECTORY={}; (for SRC in $SRC_DIRECTORY/*.tRNA; do SRC=$(realpath --relative-to $DST $SRC); ln -sf $SRC $DST; done)".format( + os.path.join(output_directory,"plastid"), + directories[("intermediate", "10__trnascan-plastid")], + ), + + + "&&", + + "for ID in $(cat {}); do cat {}/$ID.gff {}/$ID.rRNA.gff {}/$ID.tRNA.gff > {}/$ID.gff; done".format( + input_filepaths[0], + directories[("intermediate", "4__pyrodigal-plastid")], + directories[("intermediate", "7__barrnap-plastid")], + directories[("intermediate", "10__trnascan-plastid")], + os.path.join(output_directory, "plastid"), + ) + ] + + return cmd + +def get_stats_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): + + # Genomes + cmd = [ + + os.environ["seqkit"], + "stats", + "-a", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.fa"), + os.path.join(output_directory, "*", "*.fa"), + + "|", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-3]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-3]); df.to_csv(sys.stdout, sep="\t")'""", + + ">", + os.path.join(output_directory,"genome_statistics.tsv"), + + + # CDS + + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.ffn"), + os.path.join(output_directory,"*", "*.ffn"), + + "|", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-4]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-4]); df.to_csv(sys.stdout, sep="\t")'""", + + ">", + os.path.join(output_directory,"gene_statistics.cds.tsv"), + + + # rRNA + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.rRNA"), + os.path.join(output_directory,"*", "*.rRNA"), + + "|", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-5]); df.to_csv(sys.stdout, sep="\t")'""", + + ">", + os.path.join(output_directory,"gene_statistics.rRNA.tsv"), + + + # tRNA + "&&", + + os.environ["seqkit"], + "stats", + "-a", + # "-b", + "-T", + "-j {}".format(opts.n_jobs), + os.path.join(output_directory, "*.tRNA"), + os.path.join(output_directory,"*", "*.tRNA"), + + "|", + # """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: "/".join(x.split("/")[2:])[:-5]); df.to_csv(sys.stdout, sep="\t")'""", + """python -c 'import sys, pandas as pd; df = pd.read_csv(sys.stdin, sep="\t", index_col=0); df.index = df.index.map(lambda x: x.split("output/")[-1][:-5]); df.to_csv(sys.stdout, sep="\t")'""", + + ">", + os.path.join(output_directory,"gene_statistics.tRNA.tsv"), + + + ] + + return cmd + + +# ============ +# Run Pipeline +# ============ + + + +def create_pipeline(opts, directories, f_cmds): + + # ................................................................. + # Primordial + # ................................................................. + # Commands file + pipeline = ExecutablePipeline(name=__program__, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + + # ============ + # Partitioning + # ============ + if opts.tiara_results: + output_filepaths = [opts.tiara_results] + else: + + step = 0 + + program = "tiara" + + program_label = "{}__{}".format(step, program) + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + + # Info + description = "Predict taxonmic domain of contigs" + + # i/o + input_filepaths = [ + opts.fasta, + ] + + output_filepaths = [ + os.path.join(output_directory, "tiara_output.tsv"), + ] + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_tiara_cmd(**params) + + + # Add step to pipeline + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + ) + + + + # ========== + # Partition sequences + # ========== + step = 1 + + program = "partition" + + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "Partitioning nuclear, mitochondrial, and plastid sequences" + + # i/o + + input_filepaths = [ + opts.fasta, + output_filepaths[0], + ] + + + + output_filepaths = [ + os.path.join(directories["output"],"*.fa"), + os.path.join(output_directory,"eukaryotic_contigs.list"), + os.path.join(output_directory,"genomes.list"), + + ] + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + if opts.scaffolds_to_bins: + cmd = get_partition_organelle_sequences_multiple_cmd(**params) + else: + cmd = get_partition_organelle_sequences_single_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + acceptable_returncodes={0}, + log_prefix=program_label, + # whitelist_empty_output_files=[ + # "mitochondrion/*.fa", + # "plastid/*.fa", + # ] + ) + + # ============= + # MetaEuk + # ============= + step = 2 + + program = "metaeuk" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = directories["output"] + + # Info + description = "Ab initio eukaryotic gene prediction" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "eukaryotic_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + "identifier_mapping.metaeuk.tsv", + "metaeuk.headersMap.tsv", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_metaeuk_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Pyrodigal + # ============= + step = 3 + + program = "pyrodigal-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = os.path.join(directories["output"], "mitochondrion") + + + # Info + description = "Ab initio prokaryotic gene prediction [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "mitochondrion_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "genetic_code":opts.pyrodigal_mitochondrial_genetic_code, + } + + cmd = get_pyrodigal_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Pyrodigal + # ============= + step = 4 + + program = "pyrodigal-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + # output_directory = os.path.join(directories["output"], "plastid") + + # Info + description = "Ab initio prokaryotic gene prediction [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "plastid_contigs.list"), + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + opts.fasta, + + ] + if opts.scaffolds_to_bins: + input_filepaths += [ + opts.scaffolds_to_bins, + ] + + + output_filenames = [ + "*.fa", + "*.faa", + "*.gff", + "*.ffn", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "genetic_code":opts.pyrodigal_plastid_genetic_code, + } + + cmd = get_pyrodigal_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 5 + + program = "barrnap-nuclear" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Nuclear]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "*.fa"), + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"euk", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 6 + + program = "barrnap-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "mitochondrion", "*.fa"), + + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"mito", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # BARRNAP + # ============= + step = 7 + + program = "barrnap-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "rRNA gene detection [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "plastid", "*.fa"), + ] + + output_filenames = [ + "*.rRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "kingdom":"bac", + } + + cmd = get_barrnap_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 8 + + program = "trnascan-nuclear" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Nuclear]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":"-E", + "trnascan_options":opts.trnascan_nuclear_options, + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 9 + + program = "trnascan-mitochondrion" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Mitochondrion]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "mitochondrion", "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":opts.trnascan_mitochondrial_searchmode, + "trnascan_options":opts.trnascan_mitochondrial_options, + + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # tRNAscan-SE + # ============= + step = 10 + + program = "trnascan-plastid" + program_label = "{}__{}".format(step, program) + + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "tRNA gene detection [Plastid]" + + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + os.path.join( directories["output"], "plastid", "*.fa"), + ] + + output_filenames = [ + "*.tRNA", + ] + + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "search_mode":opts.trnascan_plastid_searchmode, + "trnascan_options":opts.trnascan_plastid_options, + + } + + cmd = get_trnascan_cmd(**params) + + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=False, + validate_outputs=False, + errors_ok=False, + log_prefix=program_label, + + ) + + # ============= + # Output + # ============= + step = 11 + + program = "symlink" + program_label = "{}__{}".format(step, program) + description = "Merging and symlinking results for output" + + # Add to directories + output_directory = directories["output"] + + # i/o + input_filepaths = [ + os.path.join( directories[("intermediate", "1__partition")], "genomes.list"), + ] + + output_filenames = [ + # "*.faa", + # "*.ffn", + "*.gff", + + + ] + + + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_symlink_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + # ============= + # Output + # ============= + step = 12 + + program = "stats" + program_label = "{}__{}".format(step, program) + description = "Calculating statistics for sequences" + + # Add to directories + output_directory = directories["output"] + + # i/o + input_filepaths = [ + os.path.join(directories["output"], "*.fa"), + # os.path.join(directories["output"], "mitochondrion"), + # os.path.join(directories["output"], "plastid"), + + ] + + output_filenames = [ + "genome_statistics.tsv", + "gene_statistics.cds.tsv", + "gene_statistics.rRNA.tsv", + "gene_statistics.tRNA.tsv", + + + ] + + + output_filepaths = list(map(lambda fn:os.path.join(directories["output"], fn), output_filenames)) + + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + } + + cmd = get_stats_cmd(**params) + pipeline.add_step( + id=program_label, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + log_prefix=program_label, + + ) + + + + + + + + return pipeline + + + +# Set environment variables +def add_executables_to_environment(opts): + """ + Adapted from Soothsayer: https://github.com/jolespin/soothsayer + """ + accessory_scripts = { + "partition_gene_models.py", + "compile_metaeuk_identifiers.py", + "partition_organelle_sequences.py", + "append_geneid_to_prodigal_gff.py", + "append_geneid_to_barrnap_gff.py", + } + + required_executables={ + "seqkit", + "metaeuk", + "pyrodigal", + "barrnap", + "tRNAscan-SE", + + } + if not opts.tiara_results: + required_executables |= {"tiara"} + + + required_executables |= accessory_scripts + + if opts.path_config == "CONDA_PREFIX": + executables = dict() + for name in sorted(required_executables): + if name not in accessory_scripts: + executables[name] = os.path.join(os.environ["CONDA_PREFIX"], "bin", name) + else: + if opts.path_config is None: + opts.path_config = os.path.join(opts.script_directory, "veba_config.tsv") + opts.path_config = format_path(opts.path_config) + assert os.path.exists(opts.path_config), "config file does not exist. Have you created one in the following directory?\n{}\nIf not, either create one, check this filepath:{}, or give the path to a proper config file using --path_config".format(opts.script_directory, opts.path_config) + assert os.stat(opts.path_config).st_size > 1, "config file seems to be empty. Please add 'name' and 'executable' columns for the following program names: {}".format(required_executables) + df_config = pd.read_csv(opts.path_config, sep="\t") + assert {"name", "executable"} <= set(df_config.columns), "config must have `name` and `executable` columns. Please adjust file: {}".format(opts.path_config) + df_config = df_config.loc[:,["name", "executable"]].dropna(how="any", axis=0).applymap(str) + # Get executable paths + executables = OrderedDict(zip(df_config["name"], df_config["executable"])) + assert required_executables <= set(list(executables.keys())), "config must have the required executables for this run. Please adjust file: {}\nIn particular, add info for the following: {}".format(opts.path_config, required_executables - set(list(executables.keys()))) + + # Display + + for name in sorted(accessory_scripts): + executables[name] = "'{}'".format(os.path.join(opts.script_directory, name)) # Can handle spaces in path + + + + print(format_header( "Adding executables to path from the following source: {}".format(opts.path_config), "-"), file=sys.stdout) + for name, executable in executables.items(): + if name in required_executables: + print(name, executable, sep = " --> ", file=sys.stdout) + os.environ[name] = executable.strip() + print("", file=sys.stdout) + + +# Configure parameters +def configure_parameters(opts, directories): + assert bool(opts.name) != bool(opts.scaffolds_to_bins), "--name and --scaffolds_to_bins are mutually exclusive. Use --name if you are modeling genes on a single assembly and --scaffolds_to_bins in batch (faster for multiple assemblies)" + if opts.name: + opts.basename = opts.name + else: + opts.basename = "gene_models" + + # Set environment variables + add_executables_to_environment(opts=opts) + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -f -d -i -o ".format(__program__) + + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + + # Pipeline + parser_io = parser.add_argument_group('I/O arguments') + parser_io.add_argument("-f","--fasta", type=str, required=True, help = "path/to/scaffolds.fasta") + parser_io.add_argument("-t","--tiara_results", type=str, required=True, help = "path/to/scaffolds.fasta") + parser_io.add_argument("-n","--name", type=str, required=False, help = "path/to/scaffolds.fasta [Cannot be used with --scaffolds_to_bins]") + parser_io.add_argument("-i","--scaffolds_to_bins", type=str, required=False, help = "path/to/scaffolds_to_bins.tsv, [Optional] Format: [id_scaffold][id_bin], No header. [Cannot be used with --name]") + parser_io.add_argument("-o","--output_directory", type=str, default="eukaryotic_gene_modeling_output", help = "path/to/project_directory [Default: eukaryotic_gene_modeling_output]") + parser_io.add_argument("-d", "--metaeuk_database", type=str, required=True, help=f"MetaEuk/MMSEQS2 database (E.g., $VEBA_DATABASE/Classify/Microeukaryotic/microeukaryotic)") + + # Utility + parser_utility = parser.add_argument_group('Utility arguments') + parser_utility.add_argument("--path_config", type=str, default="CONDA_PREFIX", help="path/to/config.tsv [Default: CONDA_PREFIX]") #site-packges in future + parser_utility.add_argument("-p", "--n_jobs", type=int, default=1, help = "Number of threads [Default: 1]") + # parser_utility.add_argument("--random_state", type=int, default=0, help = "Random state [Default: 0]") + parser_utility.add_argument("--restart_from_checkpoint", type=str, default=None, help = "Restart from a particular checkpoint [Default: None]") + parser_utility.add_argument("-v", "--version", action='version', version="{} v{}".format(__program__, __version__)) + + # Tiara + parser_organelle = parser.add_argument_group('Organelle arguments') + parser_organelle.add_argument("-u","--unknown_organelle_prediction", type=str, default="nuclear", help = "{unknown, nuclear} [Default: nuclear]") + # parser_organelle.add_argument("--mitochondrion_suffix", type=str, default=".mtDNA", help = "Mitochondrion suffix [Default: .mtDNA]") + # parser_organelle.add_argument("--plastid_suffix", type=str, default=".plastid", help = "Plastid suffix [Default: .plastid]") + # parser_organelle.add_argument("--unknown_suffix", type=str, default=".unknown", help = "Unknown suffix [Default: .unknown]") + parser_organelle.add_argument("--tiara_options", type=str, default="", help="Tiara | More options (e.g. --arg 1 ) [Default: '']") + + # MetaEuk + parser_metaeuk = parser.add_argument_group('MetaEuk arguments') + parser_metaeuk.add_argument("--metaeuk_sensitivity", type=float, default=4.0, help="MetaEuk | Sensitivity: 1.0 faster; 4.0 fast; 7.5 sensitive [Default: 4.0]") + parser_metaeuk.add_argument("--metaeuk_evalue", type=float, default=0.01, help="MetaEuk | List matches below this E-value (range 0.0-inf) [Default: 0.01]") + parser_metaeuk.add_argument("--metaeuk_options", type=str, default="", help="MetaEuk | More options (e.g. --arg 1 ) [Default: ''] https://github.com/soedinglab/metaeuk") + + # Pyrodigal + parser_pyrodigal = parser.add_argument_group('Pyrodigal arguments (Mitochondria)') + parser_pyrodigal.add_argument("--pyrodigal_minimum_gene_length", type=int, default=90, help="Pyrodigal | Minimum gene length [Default: 90]") + parser_pyrodigal.add_argument("--pyrodigal_minimum_edge_gene_length", type=int, default=60, help="Pyrodigal | Minimum edge gene length [Default: 60]") + parser_pyrodigal.add_argument("--pyrodigal_maximum_gene_overlap_length", type=int, default=60, help="Pyrodigal | Maximum gene overlap length [Default: 60]") + parser_pyrodigal.add_argument("--pyrodigal_mitochondrial_genetic_code", type=int, default=4, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 4] (The Mold, Protozoan, and Coelenterate Mitochondrial Code and the Mycoplasma/Spiroplasma Code))") + parser_pyrodigal.add_argument("--pyrodigal_plastid_genetic_code", type=int, default=11, help="Pyrodigal -g translation table (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi/) [Default: 11] (The Bacterial, Archaeal and Plant Plastid Code))") + + # rRNA + parser_barrnap = parser.add_argument_group('barrnap arguments') + parser_barrnap.add_argument("--barrnap_length_cutoff", type=float, default=0.8, help="barrnap | Proportional length threshold to label as partial [Default: 0.8]") + parser_barrnap.add_argument("--barrnap_reject", type=float, default=0.25, help="barrnap | Proportional length threshold to reject prediction [Default: 0.25]") + parser_barrnap.add_argument("--barrnap_evalue", type=float, default=1e-6, help="barrnap | Similarity e-value cut-off [Default: 1e-6]") + + # tRNA + parser_trnascan = parser.add_argument_group('tRNAscan-SE arguments') + parser_trnascan.add_argument("--trnascan_nuclear_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_mitochondrial_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | Current best option according to developer: https://github.com/UCSC-LoweLab/tRNAscan-SE/issues/24") + parser_trnascan.add_argument("--trnascan_mitochondrial_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_plastid_searchmode", type=str, default="-O", help="tRNAscan-SE | Search mode [Default: '-O'] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + parser_trnascan.add_argument("--trnascan_plastid_options", type=str, default="", help="tRNAscan-SE | More options (e.g. --arg 1 ) [Default: ''] | https://github.com/UCSC-LoweLab/tRNAscan-SE") + + + # Options + opts = parser.parse_args() + + opts.script_directory = script_directory + opts.script_filename = script_filename + + # Threads + if opts.n_jobs == -1: + from multiprocessing import cpu_count + opts.n_jobs = cpu_count() + assert opts.n_jobs >= 1, "--n_jobs must be ≥ 1. To select all available threads, use -1." + + # Directories + directories = dict() + directories["project"] = create_directory(opts.output_directory) + directories["output"] = create_directory(os.path.join(directories["project"], "output")) + directories["log"] = create_directory(os.path.join(directories["project"], "log")) + directories["tmp"] = create_directory(os.path.join(directories["project"], "tmp")) + directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) + directories["intermediate"] = create_directory(os.path.join(directories["project"], "intermediate")) + os.environ["TMPDIR"] = directories["tmp"] + + # Temporary + opts.mitochondrion_suffix = "" + opts.plastid_suffix = "" + opts.unknown_suffix = "" + + # Info + print(format_header(__program__, "="), file=sys.stdout) + print(format_header("Configuration:", "-"), file=sys.stdout) + print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) + print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("Script version:", __version__, file=sys.stdout) + print("Moment:", get_timestamp(), file=sys.stdout) + print("Directory:", os.getcwd(), file=sys.stdout) + print("Commands:", list(filter(bool,sys.argv)), sep="\n", file=sys.stdout) + configure_parameters(opts, directories) + sys.stdout.flush() + + + # Run pipeline + with open(os.path.join(directories["project"], "commands.sh"), "w") as f_cmds: + pipeline = create_pipeline( + opts=opts, + directories=directories, + f_cmds=f_cmds, + ) + pipeline.compile() + pipeline.execute(restart_from_checkpoint=opts.restart_from_checkpoint) + + # shutil.rmtree(directories["intermediate"]) + + + + +if __name__ == "__main__": + main(sys.argv[1:]) From 57b9a3400ce94f34861da6026e76692eb1df5e0c Mon Sep 17 00:00:00 2001 From: "Josh L. Espinoza" Date: Fri, 27 Oct 2023 14:05:19 -0700 Subject: [PATCH 16/16] v1.3.0 **Release v1.3.0:** * **`VEBA` Modules:** * Added `profile-pathway.py` module and associated scripts for building `HUMAnN` databases from *de novo* genomes and annotations. Essentially, a reads-based functional profiling method via `HUMAnN` using binned genomes as the database. * Added `marker_gene_clustering.py` script which identifies core marker proteins that are present in all genomes within a genome cluster (i.e., pangenome) and unique to only that genome cluster. Clusters in either protein or nucleotide space. * Added `module_completion_ratios.py` script which calculates KEGG module completion ratios for genomes and pangenomes. Automatically run in backend of `annotate.py`. * Updated `annotate.py` and `merge_annotations.py` to provide better annotations for clustered proteins. * Added `merge_genome_quality.py` and `merge_taxonomy_classifications.py` which compiles genome quality and taxonomy, respectively, for all organisms. * Added BGC clustering in protein and nucleotide space to `biosynthetic.py`. Also, produces prevalence tables that can be used for further clustering of BGCs. * Added `pangenome_core_sequences` in `cluster.py` writes both protein and CDS sequences for each genome cluster. * Added PDF visualization of newick trees in `phylogeny.py`. * **`VEBA` Database (`VDB_v5.2`)**: * Added `CAZy` * Added `MicrobeAnnotator-KEGG`
**Release v1.3.0 Details** * Update `annotate.py` and `merge_annotations.py` to handle `CAZy`. They also properly address clustered protein annotations now. * Added `module_completion_ratio.py` script which is a fork of `MicrobeAnnotator` [`ko_mapper.py`](https://github.com/cruizperez/MicrobeAnnotator/blob/master/microbeannotator/pipeline/ko_mapper.py). Also included a database [Zenodo: 10020074](https://zenodo.org/records/10020074) which will be included in `VDB_v5.2` * Added a checkpoint for `tRNAscan-SE` in `binning-prokaryotic.py` and `eukaryotic_gene_modeling_wrapper.py`. * Added `profile-pathway.py` module and `VEBA-profile_env` environments which is a wrapper around `HUMAnN` for the custom database created from `annotate.py` and `compile_custom_humann_database_from_annotations.py` * Added `GenoPype version` to log output * Added `merge_genome_quality.py` which combines `CheckV`, `CheckM2`, and `BUSCO` results. * Added `compile_custom_humann_database_from_annotations.py` which compiles a `HUMAnN` protein database table from the output of `annotate.py` and taxonomy classifications. * Added functionality to `merge_taxonomy_classifications.py` to allow for `--no_domain` and `--no_header` which will serve as input to `compile_custom_humann_database_from_annotations.py` * Added `marker_gene_clustering.py` script which gets core marker genes unique to each SLC (i.e., pangenome). `average_number_of_copies_per_genome` to protein clusters. * Added `--minimum_core_prevalence` in `global_clustering.py`, `local_clustering.py`, and `cluster.py` which indicates prevalence ratio of protein clusters in a SLC will be considered core. Also remove `--no_singletons` from `cluster.py` to avoid complications with marker genes. Relabeled `--input` to `--genomes_table` in clustering scripts/module. * Added a check in `coverage.py` to see if the `mapped.sorted.bam` files are created, if they are then skip them. Not yet implemented for GNU parallel option. * Changed default representative sequence format from table to fasta for `mmseqs2_wrapper.py`. * Added `--nucleotide_fasta_output` to `antismash_genbank_to_table.py` which outputs the actual BGC DNA sequence. Changed `--fasta_output` to `--protein_fasta_output` and added output to `biosynthetic.py`. Changed BGC component identifiers to `[bgc_id]_[position_in_bgc]|[start]:[end]([strand])` to match with `MetaEuk` identifiers. Changed `bgc_type` to `protocluster_type`. `biosynthetic.py` now supports GFF files from `MetaEuk` (exon and gene features not supported by `antiSMASH`). Fixed error related to `antiSMASH` adding CDS (i.e., `allorf_[start]_[end]`) that are not in GFF so `antismash_genbank_to_table.py` failed in those cases. * Added `ete3` to `VEBA-phylogeny_env.yml` and automatically renders trees to PDF. * Added presets for `MEGAHIT` using the `--megahit_preset` option. * The change for using `--mash_db` with `GTDB-Tk` violated the assumption that all prokaryotic classifications had a `msa_percent` field which caused the cluster-level taxonomy to fail. `compile_prokaryotic_genome_cluster_classification_scores_table.py` fixes this by uses `fastani_ani` as the weight when genomes were classified using ANI and `msa_percent` for everything else. Initial error caused unclassified prokaryotic for all cluster-level classifications. * Fixed small error where empty gff files with an asterisk in the name were created for samples that didn't have any prokaryotic MAGs. * Fixed critical error where descriptions in header were not being removed in `eukaryota.scaffolds.list` and did not remove eukaryotic scaffolds in `seqkit grep` so `DAS_Tool` output eukaryotic MAGs in `identifier_mapping.tsv` and `__DASTool_scaffolds2bin.no_eukaryota.txt` * Fixed `krona.html` in `biosynthetic.py` which was being created incorrectly from `compile_krona.py` script. * Create `pangenome_core_sequences` in `global_clustering.py` and `local_clustering.py` which writes both protein and CDS sequences for each SLC. Also made default in `cluster.py` to NOT do local clustering switching `--no_local_clustering` to `--local_clustering`. * `pandas.errors.InvalidIndexError: Reindexing only valid with uniquely valued Index objects` in `biosynthetic.py` when `Diamond` finds multiple regions in one hit that matches. Added `--sort_by` and `--ascending` to `concatenate_dataframes.py` along with automatic detection and removal of duplicate indices. Also added `--sort_by bitscore` in `biosynthetic.py`. * Added core pangenome and singleton hits to clustering output * Updated `--megahit_memory` default from 0.9 to 0.99 * Fixed error in `genomad_taxonomy_wrapper.py` where `viral_taxonomy.tsv` should have been `taxonomy.tsv`. * Fixed minor error in `assembly.py` that was preventing users from using `SPAdes` programs that were not `spades.py`, `metaspades.py`, or `rnaspades.py` that was the result of using an incorrect string formatting. * Updated `bowtie2` in preprocess, assembly, and mapping modules. Updated `fastp` and `fastq_preprocessor` in preprocess module.
--- .DS_Store | Bin 0 -> 14340 bytes CHANGELOG.md | 125 +++- CITATIONS.md | 109 ++-- DEPENDENCIES.xlsx | Bin 40189 -> 0 bytes README.md | 34 +- SOURCES.xlsx | Bin 0 -> 45015 bytes VERSION | 4 +- install/DATABASE.md | 176 +++++- install/README.md | 5 +- install/download_databases.sh | 17 +- install/uninstall_veba.sh | 4 +- src/annotate.py | 269 +++++++-- src/binning-prokaryotic.py | 13 +- src/cluster.py | 6 +- src/phylogeny.py | 21 +- src/scripts/compile_ko_from_annotations.py | 68 +++ src/scripts/concatenate_dataframes.py | 4 +- src/scripts/convert_table_to_fasta.py | 0 .../eukaryotic_gene_modeling_wrapper.py | 13 +- src/scripts/global_clustering.py | 21 +- src/scripts/local_clustering.py | 21 +- src/scripts/merge_annotations.py | 206 ++++++- src/scripts/merge_generalized_mapping.py | 6 +- src/scripts/module_completion_ratios.py | 543 ++++++++++++++++++ .../prokaryotic_gene_modeling_wrapper.py | 0 walkthroughs/README.md | 1 + ...specting_for_biosynthetic_gene_clusters.md | 17 +- walkthroughs/end-to-end_metagenomics.md | 77 +-- .../metabolic_profiling_de-novo_genomes.md | 107 ++++ walkthroughs/phylogenetic_inference.md | 2 + 30 files changed, 1620 insertions(+), 249 deletions(-) create mode 100644 .DS_Store delete mode 100644 DEPENDENCIES.xlsx create mode 100644 SOURCES.xlsx create mode 100755 src/scripts/compile_ko_from_annotations.py mode change 100644 => 100755 src/scripts/convert_table_to_fasta.py create mode 100755 src/scripts/module_completion_ratios.py mode change 100644 => 100755 src/scripts/prokaryotic_gene_modeling_wrapper.py create mode 100644 walkthroughs/metabolic_profiling_de-novo_genomes.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..2acc3c3d9016a743084c15eeef0ac3f152049a42 GIT binary patch literal 14340 zcmeHN3se->8NUDUnh67puXR~jKtRy2!CgU~23J4?K~Z_54KC~oTbEsA7BEs9(sztW z-#vXrk4>BOq}F)an4@j&Q$0yj)5Z{6O=_Fgq^2h|J#9}s05-Ch)N(Tfv5y-E(uU<$3slyW%L@AKvV)z2?QlT z{X-_6w6tsGI4_y&zzYo#Xg>`R*n#&L2e7tGt+Z?9I4{{$oWrJK$)^GfL+Cs<9m`BQ z980@aj`NaFC%~r@(0&#&df(O`H5xqnu5S2g#38Y|~7TbIJY|XvCfk8GZ z-+U^hM;)o|@%VahXhF}Q$F2IU*0UhQ#Ky%Zh|+C|a+0aFt+l;F?p9`wxLu=7gMPQq ztG2m@R4lud6t&+sTHovQt{ZaE{`OvVh83m@tpan<0X)!Dj*gLEKy zZrc0>w=XU#tE^tt&@_>v%$O<9mJRJAZq?o6akY-Ro&Nq#huZ7%I^EvBF1OR=^}DyY zT|<(xUuy9>23!*}l^H3R$*CL?CDxOb zZZd78J=H@lygIcBxp^kJMa4BL4sBq*MV4E(y1hwN`ux|ULl)0a3crsjnx@o(VCarO{Tl(FE=_=f777Li*NOm_VYE^*QNG5hFndf zUe}P;T8FJ{^RS&xTIqEAeSywKTu3K<+zO|YErW{CwBFP#DyPJ5MR9va+}1HIsZZJ>f5hT((o5%?H<96kY` zhA+Wa;H&TuJOYoyci_A51RRB5z_aj6_!XRi^Y9A13U3G?2tte?3kG4XU=-#HSwfDm zKqwW;gesvySSNJyR~Ix;B3*$sYVEWhur76EklT>(Xk;5otE;bD-SEF-LuZodmd~0! z$1pdOno{YVRYA4mdKys8P=#k&9%dU%%b70d)N=xjYhIdJ&X~$7ThTmR+eND5N&+99 zCFh7}9!eaq=E;jhGz=x4S1oA;BATKiu&QiJE0oaSlo(#Mq?Jgb0=!Z#FO$&pl-P-R zsU~CPa#>agTE4^|RFhUKqV*`^RjTzF@&|H({FS_oYMqPb)L&*wy$2qJZ^C2nE%-J(35Vf(@O}6J z9ETI|6g&;jpqjq`=irawrdjj{4o;F0CZe@10&+B)%(ZjI`=+kk?rgqdK-)NO@*y~i; z+-BY(N}|-Q7{|tnY*tHVp{;bMC3CE73EmeK7Vq3CiF2|SS8V9s>KX9*cF|?wG6Ieu ziPwYI`|)nd^@Ra%LNQINPdzpW#lo8bS^Op-3rR{eG`F<2q5IWC;Cln%Aml~{*a#pm zNeARPri|=`cPuGcwqj-dnu#QI50W%%cEETi!IXn*D5Od=pp#>2QcAL7m}^KC5le%( z%AaL0r=xiu3iBPT$6zpLiDIae(I({P;RFZsINIyvTO_n<=5hE`$!ZfN#mW7Z*0i)3 zn;HVnOKU8z5b-T7K@Z294a@B!Is(1pqH8x8me+`42&x8V)QO0XNQuHwb%HJ2Y-kk4 zNmQLsQj(L_HW>^N@Dx!Nolj&ay^QZ5w5AdZsU)=sQN6^AmbM$=^+ECwd4wDwhsh7g z)8yym8S*T7p1eeUOMXXwPhKN`BY!9VK*)^&8IsUHnIIc-U;+9l3oJ#*EkVewgjMLP z>Y;&w?nc;z-f9c@n3wXSkK%y43jz0Q@GyKG;r1{bL7zmu&}sNJybOPaci=w)VPKmm zC_*Z?njomQ2d#+R2$VfcXCDEW-4@9{%(biP>TigBoRO(kFu+WwW>Q>X4=U5OflI?& z1WYN-%wbV)Zvi`05~v=F6LfTjG~cYmq5kt~j+ARw;!*c`b&)yWtOyh^+28`RRZ?Qm zpd?l;H0MhSAX}vmDv`<%Dk)&H>N2TPQV6wbUSDCZHX~M2zIoLuyN^6Z4x-8)LzO*E zUPMKGgS<)p4I(P20Rc1>S)mznVIi`@e5R-+2&m;y0X0yI%32Sr8K|~F2XrDsbb^a1 z>^2zESmHR`i%jvO2&$h#Q2i`?4n7ZiP=`;^OhCDN z@nZ|z4&=g~5bk>^rW)skQ)b#K zryDaRgzs?5%*Ll1b99uMO~^AYq7gBt%&f&C7f@K|l$lMn$%Qn;)+jS;Dwa!Wm>s0d ztg%AAlZEC%mcz1aM!T_!5&=DBW>f6O+HlIOwXTjxnOSGLacvl7rb)SV>y5@KDKk|$ zdV~`ZDH3NklU!1Ye?YX5Ef|g7i=25crlXD`TRw@@;~Y6pE|Ry%CGs|T2a{5X$dZ#G z1u4jU$YMNs36hRwa3@j@9>=%AMn*UKpdTao5g3KLVF$+W_rWfV;U8c$V=v>$`{59a z-H%|*{v4cw=P_dc6I_7T;9o+l5GTY_o-9~|0%5UW6>LJeP%YF6^?YSP<{T*# zrlm4yT1sxb>22xyrK32%)~ARk`8ChE(&W|rEIC7oW9eF2&CSDVJWJHlYQ7~SLlMwE zMV3^u+A?G%h9!V$?@|j^DZi$bvU1En#j;c|t?JTI)iwAa#;&JIN4-b&evX_)UVQq01iA`JIxG2Cl_Mpy%sL3g1_Z$bv$2X1&jY()k=0^6Bde;64w&9&YK zpJ5F83$PdVu@LYZZ~zVlv#vjapTJMyXK)NoVnFyJyrfg&NNFjZ5{E+drHi{#7IJQV z*{DfIJ@u$CxK7$kUq<&F9bhMsK}=R5Ny5|1VB3!n?xP?c0^T*R$*p~NGs3nx;~?GUY2HY;Ic`@fYDzW=}F1QK0vR06l41R%Di zxuyyi9{BfCF9X`XS!<;C<9K4phU0~Oiiaip6b~!(Q@m0 + **Release v1.3.0 Details** + +* Update `annotate.py` and `merge_annotations.py` to handle `CAZy`. They also properly address clustered protein annotations now. +* Added `module_completion_ratio.py` script which is a fork of `MicrobeAnnotator` [`ko_mapper.py`](https://github.com/cruizperez/MicrobeAnnotator/blob/master/microbeannotator/pipeline/ko_mapper.py). Also included a database [Zenodo: 10020074](https://zenodo.org/records/10020074) which will be included in `VDB_v5.2` +* Added a checkpoint for `tRNAscan-SE` in `binning-prokaryotic.py` and `eukaryotic_gene_modeling_wrapper.py`. +* Added `profile-pathway.py` module and `VEBA-profile_env` environments which is a wrapper around `HUMAnN` for the custom database created from `annotate.py` and `compile_custom_humann_database_from_annotations.py` +* Added `GenoPype version` to log output +* Added `merge_genome_quality.py` which combines `CheckV`, `CheckM2`, and `BUSCO` results. +* Added `compile_custom_humann_database_from_annotations.py` which compiles a `HUMAnN` protein database table from the output of `annotate.py` and taxonomy classifications. +* Added functionality to `merge_taxonomy_classifications.py` to allow for `--no_domain` and `--no_header` which will serve as input to `compile_custom_humann_database_from_annotations.py` +* Added `marker_gene_clustering.py` script which gets core marker genes unique to each SLC (i.e., pangenome). `average_number_of_copies_per_genome` to protein clusters. +* Added `--minimum_core_prevalence` in `global_clustering.py`, `local_clustering.py`, and `cluster.py` which indicates prevalence ratio of protein clusters in a SLC will be considered core. Also remove `--no_singletons` from `cluster.py` to avoid complications with marker genes. Relabeled `--input` to `--genomes_table` in clustering scripts/module. +* Added a check in `coverage.py` to see if the `mapped.sorted.bam` files are created, if they are then skip them. Not yet implemented for GNU parallel option. +* Changed default representative sequence format from table to fasta for `mmseqs2_wrapper.py`. +* Added `--nucleotide_fasta_output` to `antismash_genbank_to_table.py` which outputs the actual BGC DNA sequence. Changed `--fasta_output` to `--protein_fasta_output` and added output to `biosynthetic.py`. Changed BGC component identifiers to `[bgc_id]_[position_in_bgc]|[start]:[end]([strand])` to match with `MetaEuk` identifiers. Changed `bgc_type` to `protocluster_type`. `biosynthetic.py` now supports GFF files from `MetaEuk` (exon and gene features not supported by `antiSMASH`). Fixed error related to `antiSMASH` adding CDS (i.e., `allorf_[start]_[end]`) that are not in GFF so `antismash_genbank_to_table.py` failed in those cases. +* Added `ete3` to `VEBA-phylogeny_env.yml` and automatically renders trees to PDF. +* Added presets for `MEGAHIT` using the `--megahit_preset` option. +* The change for using `--mash_db` with `GTDB-Tk` violated the assumption that all prokaryotic classifications had a `msa_percent` field which caused the cluster-level taxonomy to fail. `compile_prokaryotic_genome_cluster_classification_scores_table.py` fixes this by uses `fastani_ani` as the weight when genomes were classified using ANI and `msa_percent` for everything else. Initial error caused unclassified prokaryotic for all cluster-level classifications. +* Fixed small error where empty gff files with an asterisk in the name were created for samples that didn't have any prokaryotic MAGs. +* Fixed critical error where descriptions in header were not being removed in `eukaryota.scaffolds.list` and did not remove eukaryotic scaffolds in `seqkit grep` so `DAS_Tool` output eukaryotic MAGs in `identifier_mapping.tsv` and `__DASTool_scaffolds2bin.no_eukaryota.txt` +* Fixed `krona.html` in `biosynthetic.py` which was being created incorrectly from `compile_krona.py` script. +* Create `pangenome_core_sequences` in `global_clustering.py` and `local_clustering.py` which writes both protein and CDS sequences for each SLC. Also made default in `cluster.py` to NOT do local clustering switching `--no_local_clustering` to `--local_clustering`. +* `pandas.errors.InvalidIndexError: Reindexing only valid with uniquely valued Index objects` in `biosynthetic.py` when `Diamond` finds multiple regions in one hit that matches. Added `--sort_by` and `--ascending` to `concatenate_dataframes.py` along with automatic detection and removal of duplicate indices. Also added `--sort_by bitscore` in `biosynthetic.py`. +* Added core pangenome and singleton hits to clustering output +* Updated `--megahit_memory` default from 0.9 to 0.99 +* Fixed error in `genomad_taxonomy_wrapper.py` where `viral_taxonomy.tsv` should have been `taxonomy.tsv`. +* Fixed minor error in `assembly.py` that was preventing users from using `SPAdes` programs that were not `spades.py`, `metaspades.py`, or `rnaspades.py` that was the result of using an incorrect string formatting. +* Updated `bowtie2` in preprocess, assembly, and mapping modules. Updated `fastp` and `fastq_preprocessor` in preprocess module. + +
+ + +**Release v1.2.0 Highlights:** + +* **`VEBA` Modules:** + * Updated `GTDB-Tk` now uses `Mash` for ANI screening to speed up classification (now provided in `VDB_v5.1` database) + * rRNA and tRNA are identified for prokaryotic and eukaryotic genomes via `BARRNAP` and `tRNAscan-SE` + * Eukaryotic genes (CDS, rRNA, tRNA) are analyzed separately for nuclear, mitochondrion, and plastid sequences + * Genome GFF files include contigs, CDS, rRNA, and tRNA with tags for mitochondrion and plastids when applicable + * Clustering automatically generates pangenome protein prevalence tables for each genome cluster + * Ratios of singletons in each genome are now calculated + * [Virulence factor database](http://www.mgc.ac.cn/VFs/main.htm) (`VFDB`) is now included in annotations + * [UniRef50/90](https://www.uniprot.org/help/uniref) is now included in annotations + * `Krona` plots are generated for taxonomy classifications and biosynthetic gene cluster detection + * Fixed a minor issue in `biosynthetic.py` where the fasta and genbank files were not properly symlinked. Also added virulence factor results to synopsis. + + +* **`VEBA` Database (`VDB_v5.1`) **: + * Added `VFDB` + * Updated `GTDB v207_v2 → v214.1` + * Changed `NR → UniRef50/90` + * Deprecated [`RefSeq non-redundant`](https://www.ncbi.nlm.nih.gov/refseq/about/nonredundantproteins/) in place of `UniRef` + +
+ **Release v1.2.0 Details** * Fixed minor error in `binning-prokaryotic.py` where the `--veba_database` argument wasn't utilized and only the environment variable `VEBA_DATABASE` could be used. * Updated the Docker images to have `/volumes/input`, `/volumes/output`, and `/volumes/database` directories to mount. @@ -22,9 +94,11 @@ ________________________________________________________________ * Compiled and pushed `gtdb_r214.msh` mash file to [Zenodo:8048187](https://zenodo.org/record/8048187) which is now used by default in `classify-prokaryotic.py`. It is now included in `VDB_v5.1`. * Cleaned up global and local clustering intermediate files. Added pangenome tables and singelton information to outputs. +
+
- **Release v1.1.2:** + **Release v1.1.2 Details** * Created Docker images for all modules * Replaced all absolute path symlinks with relative symlinks @@ -48,7 +122,7 @@ ________________________________________________________________
- **Release v1.1.1:** + **Release v1.1.1 Details** * Most important update includes fixing a broken VEBA-`binning-viral.yml` install recipe which had package conflicts for `aria2` 30e8b0a. * Fixes on conda-related environment variables in the install scripts. @@ -62,7 +136,7 @@ ________________________________________________________________
- **Release v1.1.0:** + **Release v1.1.0 Details** * **Modules**: * `annotate.py` @@ -172,7 +246,7 @@ ________________________________________________________________
- **Release v1.0.4:** + **Release v1.0.4 Details** * Added `biopython` to `VEBA-assembly_env` which is needed when running `MEGAHIT` as the scaffolds are rewritten and [an error](https://github.com/jolespin/veba/issues/17) was raised. [aea51c3](https://github.com/jolespin/veba/commit/aea51c3e0b775aec90f7343f01cad6911f526f0a) * Updated Microeukaryotic protein database to exclude a few higher eukaryotes that were present in database, changed naming scheme to hash identifiers (from `cat reference.faa | seqkit fx2tab -s -n > id_to_hash.tsv`). Switching database from [FigShare](https://figshare.com/articles/dataset/Microeukaryotic_Protein_Database/19668855) to [Zenodo](https://zenodo.org/record/7485114#.Y6vZO-zMKDU). Uses database version `VDB_v3` which has the updated microeukaryotic protein database (`VDB-Microeukaryotic_v2`) [0845ba6](https://github.com/jolespin/veba/commit/0845ba6be65f3486d61fe7ae21a2937efeb42ee9) @@ -180,7 +254,7 @@ ________________________________________________________________
- **Release v1.0.3e:** + **Release v1.0.3e Details** * Patch fix for `install_veba.sh` where `install/environments/VEBA-assembly_env.yml` raised [a compatibilty error](https://github.com/jolespin/veba/issues/15) when creating the `VEBA-assembly_env` environment. [c2ab957](https://github.com/jolespin/veba/commit/c2ab957be132d34e6b99d6dea394be4572b83066) * Patch fix for `VirFinder_wrapper.R` where `__version__ = ` variable was throwing [an R error](https://github.com/jolespin/veba/issues/13) when running `binning-viral.py` module. [19e8f38](https://github.com/jolespin/veba/commit/19e8f38a5050328b7ba88b2271f0221073748cbb) @@ -201,7 +275,7 @@ ________________________________________________________________
- **Release v1.0.2a:** + **Release v1.0.2a Details** * Updated *GTDB-Tk* in `VEBA-binning-prokaryotic_env` from `1.x` to `2.x` (this version uses much less memory): [f3507dd](https://github.com/jolespin/veba/commit/f3507dd13a42960e3671c9f8a106c9974fbfce21) * Updated the *GTDB-Tk* database from `R202` to `R207_v2` to be compatible with *GTDB-Tk v2.x*: [f3507dd](https://github.com/jolespin/veba/commit/f3507dd13a42960e3671c9f8a106c9974fbfce21) @@ -217,7 +291,7 @@ ________________________________________________________________
- **Release v1.0.1:** + **Release v1.0.1 Details** * Fixed the fatal binning-eukaryotic.py error: [7c5addf](https://github.com/jolespin/veba/commit/7c5addf9ed6e8e45502274dd353f20b211838a41) * Fixed the minor file naming in cluster.py: [5803845](https://github.com/jolespin/veba/commit/58038451dac0791899aa7fca3f9d79454cb9ed46) @@ -227,7 +301,7 @@ ________________________________________________________________
- **Release v1.0.0:** + **Release v1.0.0 Details** * Released with *BMC Bionformatics* publication (doi:10.1186/s12859-022-04973-8). @@ -239,15 +313,14 @@ ________________________________________________________________ **Check:** -* Start/end positions on MetaEuk gene ID might be off. +* Start/end positions on `MetaEuk` gene ID might be off. **Critical:** -* Dereplcate CDS sequences in GFF from MetaEuk for antiSMASH to work for eukaryotic genomes -* Component clustering needs to be with respect to BGC not genome - +* Genome checkpoints in `tRNAscan-SE` aren't working properly. +* Dereplcate CDS sequences in GFF from `MetaEuk` for `antiSMASH` to work for eukaryotic genomes +* Error with `amplicon.py` that works when run manually... -Error with `amplicon.py` that works when run manually... ``` There was a problem importing veba_output/misc/reads_table.tsv: @@ -255,36 +328,32 @@ There was a problem importing veba_output/misc/reads_table.tsv: ``` **Definitely:** -* Modify behavior of `annotate.py` to allow for skipping Pfam and/or KOFAM since they take a long time. -* Add `compile_custom_humann_database.py` + +* Add representative to `identifier_mapping.proteins.tsv.gz` * Add coding density to GFF files * Split `download_databases.sh` into `download_databases.sh` (low memory, high threads) and `configure_databases.sh` (high memory, low-to-mid threads). Use `aria2` in parallel instead of `wget`. * `NextFlow` support * Consistent usage of the following terms: 1) dataframe vs. table; 2) protein-cluster vs. orthogroup. * Add support for `FAMSA` in `phylogeny.py` * Create a `assembly-longreads.py` module that uses `MetaFlye` -* Expand Microeukaryotic Protein Database to include more fungi (`Mycocosm`) -* Add MAG-level counts to prokaryotic and eukaryotic. Add optional bam file for viral binning, if so then add MAG-level counts +* Expand Microeukaryotic Protein Database to include more microeukaryotes (`Mycocosm` and `PhycoCosm` from `JGI`) * Install each module via `bioconda` * Add support for `Salmon` in `mapping.py` and `index.py`. This can be used instead of `STAR` which will require adding the `exon` field to `Prodigal` GFF file (`MetaEuk` modified GFF files already have exon ids). -* Build Metaphlan (and HUMAnN) database from genomes. **Probably (Yes)?:** -* Convert HMMs to MMSEQS2 (https://github.com/soedinglab/MMseqs2/wiki#how-to-create-a-target-profile-database-from-pfam)? +* Convert HMMs to `MMSEQS2` (https://github.com/soedinglab/MMseqs2/wiki#how-to-create-a-target-profile-database-from-pfam)? * Run `cmsearch` before `tRNAscan-SE` * DN/DS from pangeome analysis * Add [iPHoP](https://bitbucket.org/srouxjgi/iphop/src/main/) to `binning-viral.py`. * Add a `metabolic.py` module * Swap [`TransDecoder`](https://github.com/TransDecoder/TransDecoder) for [`TransSuite`](https://github.com/anonconda/TranSuite) -* Reimplement `KOFAMSCAN` (which creates thousands of intermediate files) using `hmmer_wrapper.py`. -* Build a clustered version of the Microeukaryotic Protein Database that is more efficient to run. +* Build a clustered version of the Microeukaryotic Protein Database that is more efficient to run. Similar to UniRef100, UniRef90, UniRef50. **...Maybe (Not)?** -* Add `VAMB` as an option for `binning-prokaryotic.py` (requires python >= 3.7,<3.8) -* Add support for `Anvi'o` object export in `cluster.py`. Installation is quite involved as of 2023.6.12 +* Modify behavior of `annotate.py` to allow for skipping Pfam and/or KOFAM since they take a long time. ________________________________________________________________ @@ -293,22 +362,24 @@ ________________________________________________________________
**Daily Change Log:** +* [2023.10.27] - Update `annotate.py` and `merge_annotations.py` to handle `CAZy`. They also properly address clustered protein annotations now. +* [2023.10.18] - Added `module_completion_ratio.py` script which is a fork of `MicrobeAnnotator` [`ko_mapper.py`](https://github.com/cruizperez/MicrobeAnnotator/blob/master/microbeannotator/pipeline/ko_mapper.py). Also included a database [Zenodo: 10020074](https://zenodo.org/records/10020074) which will be included in `VDB_v5.2` +* [2023.10.16] - Added a checkpoint for `tRNAscan-SE` in `binning-prokaryotic.py` and `eukaryotic_gene_modeling_wrapper.py`. * [2023.10.16] - Added `profile-pathway.py` module and `VEBA-profile_env` environments which is a wrapper around `HUMAnN` for the custom database created from `annotate.py` and `compile_custom_humann_database_from_annotations.py` * [2023.10.16] - Added `GenoPype version` to log output * [2023.10.16] - Added `merge_genome_quality.py` which combines `CheckV`, `CheckM2`, and `BUSCO` results. * [2023.10.11] - Added `compile_custom_humann_database_from_annotations.py` which compiles a `HUMAnN` protein database table from the output of `annotate.py` and taxonomy classifications. * [2023.10.11] - Added functionality to `merge_taxonomy_classifications.py` to allow for `--no_domain` and `--no_header` which will serve as input to `compile_custom_humann_database_from_annotations.py` -* * [2023.10.5] - Added `marker_gene_clustering.py` script which gets core marker genes unique to each SLC (i.e., pangenome). `average_number_of_copies_per_genome` to protein clusters. * [2023.10.5] - Added `--minimum_core_prevalence` in `global_clustering.py`, `local_clustering.py`, and `cluster.py` which indicates prevalence ratio of protein clusters in a SLC will be considered core. Also remove `--no_singletons` from `cluster.py` to avoid complications with marker genes. Relabeled `--input` to `--genomes_table` in clustering scripts/module. * [2023.9.21] - Added a check in `coverage.py` to see if the `mapped.sorted.bam` files are created, if they are then skip them. Not yet implemented for GNU parallel option. * [2023.9.15] - Changed default representative sequence format from table to fasta for `mmseqs2_wrapper.py`. * [2023.9.12] - Added `--nucleotide_fasta_output` to `antismash_genbank_to_table.py` which outputs the actual BGC DNA sequence. Changed `--fasta_output` to `--protein_fasta_output` and added output to `biosynthetic.py`. Changed BGC component identifiers to `[bgc_id]_[position_in_bgc]|[start]:[end]([strand])` to match with `MetaEuk` identifiers. Changed `bgc_type` to `protocluster_type`. `biosynthetic.py` now supports GFF files from `MetaEuk` (exon and gene features not supported by `antiSMASH`). Fixed error related to `antiSMASH` adding CDS (i.e., `allorf_[start]_[end]`) that are not in GFF so `antismash_genbank_to_table.py` failed in those cases. * [2023.9.12] - Added `ete3` to `VEBA-phylogeny_env.yml` and automatically renders trees to PDF. #! Need to test -* [2023.9.11] - Added presets for `MEGAHIT` using the `--megahit_preset` option. #! Need to test +* [2023.9.11] - Added presets for `MEGAHIT` using the `--megahit_preset` option. * [2023.9.11] - The change for using `--mash_db` with `GTDB-Tk` violated the assumption that all prokaryotic classifications had a `msa_percent` field which caused the cluster-level taxonomy to fail. `compile_prokaryotic_genome_cluster_classification_scores_table.py` fixes this by uses `fastani_ani` as the weight when genomes were classified using ANI and `msa_percent` for everything else. Initial error caused unclassified prokaryotic for all cluster-level classifications. * [2023.9.8] - Fixed small error where empty gff files with an asterisk in the name were created for samples that didn't have any prokaryotic MAGs. -* [2023.9.8] - Fixed critical error where descriptions in header were not being removed in ``eukaryota.scaffolds.list` and did not remove eukaryotic scaffolds in `seqkit grep` so `DAS_Tool` output eukaryotic MAGs in `identifier_mapping.tsv` and `__DASTool_scaffolds2bin.no_eukaryota.txt` +* [2023.9.8] - Fixed critical error where descriptions in header were not being removed in `eukaryota.scaffolds.list` and did not remove eukaryotic scaffolds in `seqkit grep` so `DAS_Tool` output eukaryotic MAGs in `identifier_mapping.tsv` and `__DASTool_scaffolds2bin.no_eukaryota.txt` * [2023.9.5] - Fixed `krona.html` in `biosynthetic.py` which was being created incorrectly from `compile_krona.py` script. * [2023.8.30] - Create `pangenome_core_sequences` in `global_clustering.py` and `local_clustering.py` which writes both protein and CDS sequences for each SLC. Also made default in `cluster.py` to NOT do local clustering switching `--no_local_clustering` to `--local_clustering`. * [2023.8.30] - `pandas.errors.InvalidIndexError: Reindexing only valid with uniquely valued Index objects` in `biosynthetic.py` when `Diamond` finds multiple regions in one hit that matches. Added `--sort_by` and `--ascending` to `concatenate_dataframes.py` along with automatic detection and removal of duplicate indices. Also added `--sort_by bitscore` in `biosynthetic.py`. diff --git a/CITATIONS.md b/CITATIONS.md index 51ff3c5..d8ac6cd 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -1,44 +1,65 @@ -| Dependency | Citation | -|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| antismash | Blin K, Shaw S, Kloosterman AM, Charlop-Powers Z, van Wezel GP, Medema MH, Weber T. antiSMASH 6.0: improving cluster detection and comparison capabilities. Nucleic Acids Res. 2021 Jul 2;49(W1):W29-W35. doi: 10.1093/nar/gkab335. PMID: 33978755; PMCID: PMC8262755. | -| barrnap | Seemann, T. barrnap 0.9 : rapid ribosomal RNA prediction. https://github.com/tseemann/barrnap | -| bbtools | https://sourceforge.net/projects/bbmap/ | -| bowtie2 | Langmead B, Salzberg SL. Fast gapped-read alignment with Bowtie 2. Nat Methods. 2012 Mar 4;9(4):357-9. doi: 10.1038/nmeth.1923. PMID: 22388286; PMCID: PMC3322381. | -| busco | Manni M, Berkeley MR, Seppey M, Simão FA, Zdobnov EM. BUSCO Update: Novel and Streamlined Workflows along with Broader and Deeper Phylogenetic Coverage for Scoring of Eukaryotic, Prokaryotic, and Viral Genomes. Mol Biol Evol. 2021 Sep 27;38(10):4647-4654. doi: 10.1093/molbev/msab199. PMID: 34320186; PMCID: PMC8476166. | -| checkm2 | Alex Chklovski, Donovan H. Parks, Ben J. Woodcroft, Gene W. Tyson bioRxiv 2022.07.11.499243; doi: https://doi.org/10.1101/2022.07.11.499243 | -| checkv | Nayfach S, Camargo AP, Schulz F, Eloe-Fadrosh E, Roux S, Kyrpides NC. CheckV assesses the quality and completeness of metagenome-assembled viral genomes. Nat Biotechnol. 2021 May;39(5):578-585. doi: 10.1038/s41587-020-00774-7. Epub 2020 Dec 21. PMID: 33349699; PMCID: PMC8116208. | -| clipkit | Steenwyk JL, Buida TJ 3rd, Li Y, Shen XX, Rokas A. ClipKIT: A multiple sequence alignment trimming software for accurate phylogenomic inference. PLoS Biol. 2020 Dec 2;18(12):e3001007. doi: 10.1371/journal.pbio.3001007. PMID: 33264284; PMCID: PMC7735675. | -| concoct | Alneberg J, Bjarnason BS, de Bruijn I, Schirmer M, Quick J, Ijaz UZ, Lahti L, Loman NJ, Andersson AF, Quince C. Binning metagenomic contigs by coverage and composition. Nat Methods. 2014 Nov;11(11):1144-6. doi: 10.1038/nmeth.3103. Epub 2014 Sep 14. PMID: 25218180. | -| coverm | https://github.com/wwood/CoverM | -| dada2 | Callahan BJ, McMurdie PJ, Rosen MJ, Han AW, Johnson AJ, Holmes SP. DADA2: High-resolution sample inference from Illumina amplicon data. Nat Methods. 2016 Jul;13(7):581-3. doi: 10.1038/nmeth.3869. Epub 2016 May 23. PMID: 27214047; PMCID: PMC4927377. | -| das_tool | Sieber CMK, Probst AJ, Sharrar A, Thomas BC, Hess M, Tringe SG, Banfield JF. Recovery of genomes from metagenomes via a dereplication, aggregation and scoring strategy. Nat Microbiol. 2018 Jul;3(7):836-843. doi: 10.1038/s41564-018-0171-1. Epub 2018 May 28. PMID: 29807988; PMCID: PMC6786971. | -| diamond | Buchfink B, Reuter K, Drost HG. Sensitive protein alignments at tree-of-life scale using DIAMOND. Nat Methods. 2021 Apr;18(4):366-368. doi: 10.1038/s41592-021-01101-x. Epub 2021 Apr 7. PMID: 33828273; PMCID: PMC8026399. | -| fastani | Jain C, Rodriguez-R LM, Phillippy AM, Konstantinidis KT, Aluru S. High throughput ANI analysis of 90K prokaryotic genomes reveals clear species boundaries. Nat Commun. 2018 Nov 30;9(1):5114. doi: 10.1038/s41467-018-07641-9. PMID: 30504855; PMCID: PMC6269478. | -| fastp | Chen S, Zhou Y, Chen Y, Gu J. fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics. 2018 Sep 1;34(17):i884-i890. doi: 10.1093/bioinformatics/bty560. PMID: 30423086; PMCID: PMC6129281. | -| fastq_preprocessor | Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. doi: 10.1186/s12859-022-04973-8. PMID: 36224545; PMCID: PMC9554839. | -| fasttree | Price MN, Dehal PS, Arkin AP. FastTree 2--approximately maximum-likelihood trees for large alignments. PLoS One. 2010 Mar 10;5(3):e9490. doi: 10.1371/journal.pone.0009490. PMID: 20224823; PMCID: PMC2835736. | -| featurecounts | Liao Y, Smyth GK, Shi W. featureCounts: an efficient general purpose program for assigning sequence reads to genomic features. Bioinformatics. 2014 Apr 1;30(7):923-30. doi: 10.1093/bioinformatics/btt656. Epub 2013 Nov 13. PMID: 24227677. | -| genomad | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | -| gtdbtk | Chaumeil PA, Mussig AJ, Hugenholtz P, Parks DH. GTDB-Tk v2: memory friendly classification with the genome taxonomy database. Bioinformatics. 2022 Nov 30;38(23):5315-5316. doi: 10.1093/bioinformatics/btac672. PMID: 36218463; PMCID: PMC9710552. | -| hmmer | Eddy SR. Accelerated Profile HMM Searches. PLoS Comput Biol. 2011 Oct;7(10):e1002195. doi: 10.1371/journal.pcbi.1002195. Epub 2011 Oct 20. PMID: 22039361; PMCID: PMC3197634. | -| iqtree | Minh BQ, Schmidt HA, Chernomor O, Schrempf D, Woodhams MD, von Haeseler A, Lanfear R. IQ-TREE 2: New Models and Efficient Methods for Phylogenetic Inference in the Genomic Era. Mol Biol Evol. 2020 May 1;37(5):1530-1534. doi: 10.1093/molbev/msaa015. Erratum in: Mol Biol Evol. 2020 Aug 1;37(8):2461. PMID: 32011700; PMCID: PMC7182206. | -| kofamscan | Aramaki T, Blanc-Mathieu R, Endo H, Ohkubo K, Kanehisa M, Goto S, Ogata H. KofamKOALA: KEGG Ortholog assignment based on profile HMM and adaptive score threshold. Bioinformatics. 2020 Apr 1;36(7):2251-2252. doi: 10.1093/bioinformatics/btz859. PMID: 31742321; PMCID: PMC7141845. | -| krona | Ondov BD, Bergman NH, Phillippy AM. Interactive metagenomic visualization in a Web browser. BMC Bioinformatics. 2011 Sep 30;12:385. doi: 10.1186/1471-2105-12-385. PMID: 21961884; PMCID: PMC3190407. | -| maxbin2 | Wu YW, Tang YH, Tringe SG, Simmons BA, Singer SW. MaxBin: an automated binning method to recover individual genomes from metagenomes using an expectation-maximization algorithm. Microbiome. 2014 Aug 1;2:26. doi: 10.1186/2049-2618-2-26. PMID: 25136443; PMCID: PMC4129434. | -| megahit | Li D, Liu CM, Luo R, Sadakane K, Lam TW. MEGAHIT: an ultra-fast single-node solution for large and complex metagenomics assembly via succinct de Bruijn graph. Bioinformatics. 2015 May 15;31(10):1674-6. doi: 10.1093/bioinformatics/btv033. Epub 2015 Jan 20. PMID: 25609793. | -| metabat2 | Kang DD, Li F, Kirton E, Thomas A, Egan R, An H, Wang Z. MetaBAT 2: an adaptive binning algorithm for robust and efficient genome reconstruction from metagenome assemblies. PeerJ. 2019 Jul 26;7:e7359. doi: 10.7717/peerj.7359. PMID: 31388474; PMCID: PMC6662567. | -| metaeuk | Levy Karin E, Mirdita M, Söding J. MetaEuk-sensitive, high-throughput gene discovery, and annotation for large-scale eukaryotic metagenomics. Microbiome. 2020 Apr 3;8(1):48. doi: 10.1186/s40168-020-00808-x. PMID: 32245390; PMCID: PMC7126354. | -| metaspades | Nurk S, Meleshko D, Korobeynikov A, Pevzner PA. metaSPAdes: a new versatile metagenomic assembler. Genome Res. 2017 May;27(5):824-834. doi: 10.1101/gr.213959.116. Epub 2017 Mar 15. PMID: 28298430; PMCID: PMC5411777. | -| mmseqs2 | Steinegger M, Söding J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. Nat Biotechnol. 2017 Nov;35(11):1026-1028. doi: 10.1038/nbt.3988. Epub 2017 Oct 16. PMID: 29035372. | -| muscle | Edgar RC. Muscle5: High-accuracy alignment ensembles enable unbiased assessments of sequence homology and phylogeny. Nat Commun. 2022 Nov 15;13(1):6968. doi: 10.1038/s41467-022-34630-w. PMID: 36379955; PMCID: PMC9664440. | -| prodigal | Hyatt D, Chen GL, Locascio PF, Land ML, Larimer FW, Hauser LJ. Prodigal: prokaryotic gene recognition and translation initiation site identification. BMC Bioinformatics. 2010 Mar 8;11:119. doi: 10.1186/1471-2105-11-119. PMID: 20211023; PMCID: PMC2848648. | -| prodigal-gv | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | -| pyrodigal | Larralde, M., (2022). Pyrodigal: Python bindings and interface to Prodigal, an efficient method for gene prediction in prokaryotes. Journal of Open Source Software, 7(72), 4296, https://doi.org/10.21105/joss.04296 | -| qiime2 | Bolyen E, Rideout JR, Dillon MR, Bokulich NA, Abnet CC, Al-Ghalith GA, Alexander H, Alm EJ, Arumugam M, Asnicar F, Bai Y, Bisanz JE, Bittinger K, Brejnrod A, Brislawn CJ, Brown CT, Callahan BJ, Caraballo-Rodríguez AM, Chase J, Cope EK, Da Silva R, Diener C, Dorrestein PC, Douglas GM, Durall DM, Duvallet C, Edwardson CF, Ernst M, Estaki M, Fouquier J, Gauglitz JM, Gibbons SM, Gibson DL, Gonzalez A, Gorlick K, Guo J, Hillmann B, Holmes S, Holste H, Huttenhower C, Huttley GA, Janssen S, Jarmusch AK, Jiang L, Kaehler BD, Kang KB, Keefe CR, Keim P, Kelley ST, Knights D, Koester I, Kosciolek T, Kreps J, Langille MGI, Lee J, Ley R, Liu YX, Loftfield E, Lozupone C, Maher M, Marotz C, Martin BD, McDonald D, McIver LJ, Melnik AV, Metcalf JL, Morgan SC, Morton JT, Naimey AT, Navas-Molina JA, Nothias LF, Orchanian SB, Pearson T, Peoples SL, Petras D, Preuss ML, Pruesse E, Rasmussen LB, Rivers A, Robeson MS 2nd, Rosenthal P, Segata N, Shaffer M, Shiffer A, Sinha R, Song SJ, Spear JR, Swafford AD, Thompson LR, Torres PJ, Trinh P, Tripathi A, Turnbaugh PJ, Ul-Hasan S, van der Hooft JJJ, Vargas F, Vázquez-Baeza Y, Vogtmann E, von Hippel M, Walters W, Wan Y, Wang M, Warren J, Weber KC, Williamson CHD, Willis AD, Xu ZZ, Zaneveld JR, Zhang Y, Zhu Q, Knight R, Caporaso JG. Reproducible, interactive, scalable and extensible microbiome data science using QIIME 2. Nat Biotechnol. 2019 Aug;37(8):852-857. doi: 10.1038/s41587-019-0209-9. Erratum in: Nat Biotechnol. 2019 Sep;37(9):1091. PMID: 31341288; PMCID: PMC7015180. | -| rnaspades | Bushmanova E, Antipov D, Lapidus A, Prjibelski AD. rnaSPAdes: a de novo transcriptome assembler and its application to RNA-Seq data. Gigascience. 2019 Sep 1;8(9):giz100. doi: 10.1093/gigascience/giz100. PMID: 31494669; PMCID: PMC6736328. | -| samtools | Li H, Handsaker B, Wysoker A, Fennell T, Ruan J, Homer N, Marth G, Abecasis G, Durbin R; 1000 Genome Project Data Processing Subgroup. The Sequence Alignment/Map format and SAMtools. Bioinformatics. 2009 Aug 15;25(16):2078-9. doi: 10.1093/bioinformatics/btp352. Epub 2009 Jun 8. PMID: 19505943; PMCID: PMC2723002. | -| seqkit | Shen W, Le S, Li Y, Hu F. SeqKit: A Cross-Platform and Ultrafast Toolkit for FASTA/Q File Manipulation. PLoS One. 2016 Oct 5;11(10):e0163962. doi: 10.1371/journal.pone.0163962. PMID: 27706213; PMCID: PMC5051824. | -| spades | Bankevich A, Nurk S, Antipov D, Gurevich AA, Dvorkin M, Kulikov AS, Lesin VM, Nikolenko SI, Pham S, Prjibelski AD, Pyshkin AV, Sirotkin AV, Vyahhi N, Tesler G, Alekseyev MA, Pevzner PA. SPAdes: a new genome assembly algorithm and its applications to single-cell sequencing. J Comput Biol. 2012 May;19(5):455-77. doi: 10.1089/cmb.2012.0021. Epub 2012 Apr 16. PMID: 22506599; PMCID: PMC3342519. | -| tiara | Karlicki M, Antonowicz S, Karnkowska A. Tiara: deep learning-based classification system for eukaryotic sequences. Bioinformatics. 2022 Jan 3;38(2):344-350. doi: 10.1093/bioinformatics/btab672. PMID: 34570171; PMCID: PMC8722755. | -| trnascan-se | Chan PP, Lin BY, Mak AJ, Lowe TM. tRNAscan-SE 2.0: improved detection and functional classification of transfer RNA genes. Nucleic Acids Res. 2021 Sep 20;49(16):9077-9096. doi: 10.1093/nar/gkab688. PMID: 34417604; PMCID: PMC8450103. | -| virfinder | Ren J, Ahlgren NA, Lu YY, Fuhrman JA, Sun F. VirFinder: a novel k-mer based tool for identifying viral sequences from assembled metagenomic data. Microbiome. 2017 Jul 6;5(1):69. doi: 10.1186/s40168-017-0283-5. PMID: 28683828; PMCID: PMC5501583. | \ No newline at end of file +### Software Dependencies: + +| Dependency | Citation +|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|------|----------|---------|---------|---------|-------| +| antismash | Blin K, Shaw S, Kloosterman AM, Charlop-Powers Z, van Wezel GP, Medema MH, Weber T. antiSMASH 6.0: improving cluster detection and comparison capabilities. Nucleic Acids Res. 2021 Jul 2;49(W1):W29-W35. doi: 10.1093/nar/gkab335. PMID: 33978755; PMCID: PMC8262755. | FASTQ | DNA | 1474203 | 75 | 150.5 | 151 | 46.91 | +| barrnap | Seemann, T. barrnap 0.9 : rapid ribosomal RNA prediction. https://github.com/tseemann/barrnap | FASTQ | DNA | 1638398 | 75 | 150.5 | 151 | 46.92 | +| bbtools | https://sourceforge.net/projects/bbmap/ | FASTQ | DNA | 2389989 | 75 | 150.4 | 151 | 56.38 | +| bowtie2 | Langmead B, Salzberg SL. Fast gapped-read alignment with Bowtie 2. Nat Methods. 2012 Mar 4;9(4):357-9. doi: 10.1038/nmeth.1923. PMID: 22388286; PMCID: PMC3322381. | FASTQ | DNA | 3142566 | 75 | 150.5 | 151 | 46.34 | +| busco | Manni M, Berkeley MR, Seppey M, Simão FA, Zdobnov EM. BUSCO Update: Novel and Streamlined Workflows along with Broader and Deeper Phylogenetic Coverage for Scoring of Eukaryotic, Prokaryotic, and Viral Genomes. Mol Biol Evol. 2021 Sep 27;38(10):4647-4654. doi: 10.1093/molbev/msab199. PMID: 34320186; PMCID: PMC8476166. | | | | | | | | +| checkm2 | Alex Chklovski, Donovan H. Parks, Ben J. Woodcroft, Gene W. Tyson bioRxiv 2022.07.11.499243; doi: https://doi.org/10.1101/2022.07.11.499243 | | | | | | | | +| checkv | Nayfach S, Camargo AP, Schulz F, Eloe-Fadrosh E, Roux S, Kyrpides NC. CheckV assesses the quality and completeness of metagenome-assembled viral genomes. Nat Biotechnol. 2021 May;39(5):578-585. doi: 10.1038/s41587-020-00774-7. Epub 2020 Dec 21. PMID: 33349699; PMCID: PMC8116208. | | | | | | | | +| clipkit | Steenwyk JL, Buida TJ 3rd, Li Y, Shen XX, Rokas A. ClipKIT: A multiple sequence alignment trimming software for accurate phylogenomic inference. PLoS Biol. 2020 Dec 2;18(12):e3001007. doi: 10.1371/journal.pbio.3001007. PMID: 33264284; PMCID: PMC7735675. | | | | | | | | +| concoct | Alneberg J, Bjarnason BS, de Bruijn I, Schirmer M, Quick J, Ijaz UZ, Lahti L, Loman NJ, Andersson AF, Quince C. Binning metagenomic contigs by coverage and composition. Nat Methods. 2014 Nov;11(11):1144-6. doi: 10.1038/nmeth.3103. Epub 2014 Sep 14. PMID: 25218180. | | | | | | | | +| coverm | https://github.com/wwood/CoverM | | | | | | | | +| dada2 | Callahan BJ, McMurdie PJ, Rosen MJ, Han AW, Johnson AJ, Holmes SP. DADA2: High-resolution sample inference from Illumina amplicon data. Nat Methods. 2016 Jul;13(7):581-3. doi: 10.1038/nmeth.3869. Epub 2016 May 23. PMID: 27214047; PMCID: PMC4927377. | | | | | | | | +| das_tool | Sieber CMK, Probst AJ, Sharrar A, Thomas BC, Hess M, Tringe SG, Banfield JF. Recovery of genomes from metagenomes via a dereplication, aggregation and scoring strategy. Nat Microbiol. 2018 Jul;3(7):836-843. doi: 10.1038/s41564-018-0171-1. Epub 2018 May 28. PMID: 29807988; PMCID: PMC6786971. | | | | | | | | +| dbcan2 | Han Zhang, Tanner Yohe, Le Huang, Sarah Entwistle, Peizhi Wu, Zhenglu Yang, Peter K Busk, Ying Xu, Yanbin Yin, dbCAN2: a meta server for automated carbohydrate-active enzyme annotation, Nucleic Acids Research, Volume 46, Issue W1, 2 July 2018, Pages W95–W101, https://doi.org/10.1093/nar/gky418 | | | | | | | | +| diamond | Buchfink B, Reuter K, Drost HG. Sensitive protein alignments at tree-of-life scale using DIAMOND. Nat Methods. 2021 Apr;18(4):366-368. doi: 10.1038/s41592-021-01101-x. Epub 2021 Apr 7. PMID: 33828273; PMCID: PMC8026399. | | | | | | | | +| fastani | Jain C, Rodriguez-R LM, Phillippy AM, Konstantinidis KT, Aluru S. High throughput ANI analysis of 90K prokaryotic genomes reveals clear species boundaries. Nat Commun. 2018 Nov 30;9(1):5114. doi: 10.1038/s41467-018-07641-9. PMID: 30504855; PMCID: PMC6269478. | | | | | | | | +| fastp | Chen S, Zhou Y, Chen Y, Gu J. fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics. 2018 Sep 1;34(17):i884-i890. doi: 10.1093/bioinformatics/bty560. PMID: 30423086; PMCID: PMC6129281. | | | | | | | | +| fastq_preprocessor | Espinoza JL, Dupont CL. VEBA: a modular end-to-end suite for in silico recovery, clustering, and analysis of prokaryotic, microeukaryotic, and viral genomes from metagenomes. BMC Bioinformatics. 2022 Oct 12;23(1):419. doi: 10.1186/s12859-022-04973-8. PMID: 36224545; PMCID: PMC9554839. | | | | | | | | +| fasttree | Price MN, Dehal PS, Arkin AP. FastTree 2--approximately maximum-likelihood trees for large alignments. PLoS One. 2010 Mar 10;5(3):e9490. doi: 10.1371/journal.pone.0009490. PMID: 20224823; PMCID: PMC2835736. | | | | | | | | +| featurecounts | Liao Y, Smyth GK, Shi W. featureCounts: an efficient general purpose program for assigning sequence reads to genomic features. Bioinformatics. 2014 Apr 1;30(7):923-30. doi: 10.1093/bioinformatics/btt656. Epub 2013 Nov 13. PMID: 24227677. | | | | | | | | +| genomad | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | | | | | | | | +| gtdbtk | Chaumeil PA, Mussig AJ, Hugenholtz P, Parks DH. GTDB-Tk v2: memory friendly classification with the genome taxonomy database. Bioinformatics. 2022 Nov 30;38(23):5315-5316. doi: 10.1093/bioinformatics/btac672. PMID: 36218463; PMCID: PMC9710552. | | | | | | | | +| hmmer | Eddy SR. Accelerated Profile HMM Searches. PLoS Comput Biol. 2011 Oct;7(10):e1002195. doi: 10.1371/journal.pcbi.1002195. Epub 2011 Oct 20. PMID: 22039361; PMCID: PMC3197634. | | | | | | | | +| iqtree | Minh BQ, Schmidt HA, Chernomor O, Schrempf D, Woodhams MD, von Haeseler A, Lanfear R. IQ-TREE 2: New Models and Efficient Methods for Phylogenetic Inference in the Genomic Era. Mol Biol Evol. 2020 May 1;37(5):1530-1534. doi: 10.1093/molbev/msaa015. Erratum in: Mol Biol Evol. 2020 Aug 1;37(8):2461. PMID: 32011700; PMCID: PMC7182206. | | | | | | | | +| kofamscan | Aramaki T, Blanc-Mathieu R, Endo H, Ohkubo K, Kanehisa M, Goto S, Ogata H. KofamKOALA: KEGG Ortholog assignment based on profile HMM and adaptive score threshold. Bioinformatics. 2020 Apr 1;36(7):2251-2252. doi: 10.1093/bioinformatics/btz859. PMID: 31742321; PMCID: PMC7141845. | | | | | | | | +| krona | Ondov BD, Bergman NH, Phillippy AM. Interactive metagenomic visualization in a Web browser. BMC Bioinformatics. 2011 Sep 30;12:385. doi: 10.1186/1471-2105-12-385. PMID: 21961884; PMCID: PMC3190407. | | | | | | | | +| maxbin2 | Wu YW, Tang YH, Tringe SG, Simmons BA, Singer SW. MaxBin: an automated binning method to recover individual genomes from metagenomes using an expectation-maximization algorithm. Microbiome. 2014 Aug 1;2:26. doi: 10.1186/2049-2618-2-26. PMID: 25136443; PMCID: PMC4129434. | | | | | | | | +| megahit | Li D, Liu CM, Luo R, Sadakane K, Lam TW. MEGAHIT: an ultra-fast single-node solution for large and complex metagenomics assembly via succinct de Bruijn graph. Bioinformatics. 2015 May 15;31(10):1674-6. doi: 10.1093/bioinformatics/btv033. Epub 2015 Jan 20. PMID: 25609793. | | | | | | | | +| metabat2 | Kang DD, Li F, Kirton E, Thomas A, Egan R, An H, Wang Z. MetaBAT 2: an adaptive binning algorithm for robust and efficient genome reconstruction from metagenome assemblies. PeerJ. 2019 Jul 26;7:e7359. doi: 10.7717/peerj.7359. PMID: 31388474; PMCID: PMC6662567. | | | | | | | | +| metaeuk | Levy Karin E, Mirdita M, Söding J. MetaEuk-sensitive, high-throughput gene discovery, and annotation for large-scale eukaryotic metagenomics. Microbiome. 2020 Apr 3;8(1):48. doi: 10.1186/s40168-020-00808-x. PMID: 32245390; PMCID: PMC7126354. | | | | | | | | +| metaspades | Nurk S, Meleshko D, Korobeynikov A, Pevzner PA. metaSPAdes: a new versatile metagenomic assembler. Genome Res. 2017 May;27(5):824-834. doi: 10.1101/gr.213959.116. Epub 2017 Mar 15. PMID: 28298430; PMCID: PMC5411777. | | | | | | | | +| microbeannotator | Ruiz-Perez, C.A., Conrad, R.E. & Konstantinidis, K.T. MicrobeAnnotator: a user-friendly, comprehensive functional annotation pipeline for microbial genomes. BMC Bioinformatics 22, 11 (2021). https://doi.org/10.1186/s12859-020-03940-5 | | | | | | | | +| mmseqs2 | Steinegger M, Söding J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. Nat Biotechnol. 2017 Nov;35(11):1026-1028. doi: 10.1038/nbt.3988. Epub 2017 Oct 16. PMID: 29035372. | | | | | | | | +| muscle | Edgar RC. Muscle5: High-accuracy alignment ensembles enable unbiased assessments of sequence homology and phylogeny. Nat Commun. 2022 Nov 15;13(1):6968. doi: 10.1038/s41467-022-34630-w. PMID: 36379955; PMCID: PMC9664440. | | | | | | | | +| prodigal | Hyatt D, Chen GL, Locascio PF, Land ML, Larimer FW, Hauser LJ. Prodigal: prokaryotic gene recognition and translation initiation site identification. BMC Bioinformatics. 2010 Mar 8;11:119. doi: 10.1186/1471-2105-11-119. PMID: 20211023; PMCID: PMC2848648. | | | | | | | | +| prodigal-gv | Antonio Pedro Camargo, Simon Roux, Frederik Schulz, Michal Babinski, Yan Xu, Bin Hu, Patrick S. G. Chain, Stephen Nayfach, Nikos C. Kyrpides bioRxiv 2023.03.05.531206; doi: https://doi.org/10.1101/2023.03.05.531206 | | | | | | | | +| pyrodigal | Larralde, M., (2022). Pyrodigal: Python bindings and interface to Prodigal, an efficient method for gene prediction in prokaryotes. Journal of Open Source Software, 7(72), 4296, https://doi.org/10.21105/joss.04296 | | | | | | | | +| qiime2 | Bolyen E, Rideout JR, Dillon MR, Bokulich NA, Abnet CC, Al-Ghalith GA, Alexander H, Alm EJ, Arumugam M, Asnicar F, Bai Y, Bisanz JE, Bittinger K, Brejnrod A, Brislawn CJ, Brown CT, Callahan BJ, Caraballo-Rodríguez AM, Chase J, Cope EK, Da Silva R, Diener C, Dorrestein PC, Douglas GM, Durall DM, Duvallet C, Edwardson CF, Ernst M, Estaki M, Fouquier J, Gauglitz JM, Gibbons SM, Gibson DL, Gonzalez A, Gorlick K, Guo J, Hillmann B, Holmes S, Holste H, Huttenhower C, Huttley GA, Janssen S, Jarmusch AK, Jiang L, Kaehler BD, Kang KB, Keefe CR, Keim P, Kelley ST, Knights D, Koester I, Kosciolek T, Kreps J, Langille MGI, Lee J, Ley R, Liu YX, Loftfield E, Lozupone C, Maher M, Marotz C, Martin BD, McDonald D, McIver LJ, Melnik AV, Metcalf JL, Morgan SC, Morton JT, Naimey AT, Navas-Molina JA, Nothias LF, Orchanian SB, Pearson T, Peoples SL, Petras D, Preuss ML, Pruesse E, Rasmussen LB, Rivers A, Robeson MS 2nd, Rosenthal P, Segata N, Shaffer M, Shiffer A, Sinha R, Song SJ, Spear JR, Swafford AD, Thompson LR, Torres PJ, Trinh P, Tripathi A, Turnbaugh PJ, Ul-Hasan S, van der Hooft JJJ, Vargas F, Vázquez-Baeza Y, Vogtmann E, von Hippel M, Walters W, Wan Y, Wang M, Warren J, Weber KC, Williamson CHD, Willis AD, Xu ZZ, Zaneveld JR, Zhang Y, Zhu Q, Knight R, Caporaso JG. Reproducible, interactive, scalable and extensible microbiome data science using QIIME 2. Nat Biotechnol. 2019 Aug;37(8):852-857. doi: 10.1038/s41587-019-0209-9. Erratum in: Nat Biotechnol. 2019 Sep;37(9):1091. PMID: 31341288; PMCID: PMC7015180. | | | | | | | | +| rnaspades | Bushmanova E, Antipov D, Lapidus A, Prjibelski AD. rnaSPAdes: a de novo transcriptome assembler and its application to RNA-Seq data. Gigascience. 2019 Sep 1;8(9):giz100. doi: 10.1093/gigascience/giz100. PMID: 31494669; PMCID: PMC6736328. | | | | | | | | +| samtools | Li H, Handsaker B, Wysoker A, Fennell T, Ruan J, Homer N, Marth G, Abecasis G, Durbin R; 1000 Genome Project Data Processing Subgroup. The Sequence Alignment/Map format and SAMtools. Bioinformatics. 2009 Aug 15;25(16):2078-9. doi: 10.1093/bioinformatics/btp352. Epub 2009 Jun 8. PMID: 19505943; PMCID: PMC2723002. | | | | | | | | +| seqkit | Shen W, Le S, Li Y, Hu F. SeqKit: A Cross-Platform and Ultrafast Toolkit for FASTA/Q File Manipulation. PLoS One. 2016 Oct 5;11(10):e0163962. doi: 10.1371/journal.pone.0163962. PMID: 27706213; PMCID: PMC5051824. | | | | | | | | +| spades | Bankevich A, Nurk S, Antipov D, Gurevich AA, Dvorkin M, Kulikov AS, Lesin VM, Nikolenko SI, Pham S, Prjibelski AD, Pyshkin AV, Sirotkin AV, Vyahhi N, Tesler G, Alekseyev MA, Pevzner PA. SPAdes: a new genome assembly algorithm and its applications to single-cell sequencing. J Comput Biol. 2012 May;19(5):455-77. doi: 10.1089/cmb.2012.0021. Epub 2012 Apr 16. PMID: 22506599; PMCID: PMC3342519. | | | | | | | | +| tiara | Karlicki M, Antonowicz S, Karnkowska A. Tiara: deep learning-based classification system for eukaryotic sequences. Bioinformatics. 2022 Jan 3;38(2):344-350. doi: 10.1093/bioinformatics/btab672. PMID: 34570171; PMCID: PMC8722755. | | | | | | | | +| trnascan-se | Chan PP, Lin BY, Mak AJ, Lowe TM. tRNAscan-SE 2.0: improved detection and functional classification of transfer RNA genes. Nucleic Acids Res. 2021 Sep 20;49(16):9077-9096. doi: 10.1093/nar/gkab688. PMID: 34417604; PMCID: PMC8450103. | | | | | | | | +| virfinder | Ren J, Ahlgren NA, Lu YY, Fuhrman JA, Sun F. VirFinder: a novel k-mer based tool for identifying viral sequences from assembled metagenomic data. Microbiome. 2017 Jul 6;5(1):69. doi: 10.1186/s40168-017-0283-5. PMID: 28683828; PMCID: PMC5501583. | | | | | | | | + +### Databases: +| Database | Citation | | | | | | | | +|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|---|---|---|---|---|---| +| antifam | Ruth Y. Eberhardt, Daniel H. Haft, Marco Punta, Maria Martin, Claire O'Donovan, Alex Bateman, AntiFam: a tool to help identify spurious ORFs in protein annotation, Database, Volume 2012, 2012, bas003, https://doi.org/10.1093/database/bas003 | | | | | | | | +| cazy | Elodie Drula, Marie-Line Garron, Suzan Dogan, Vincent Lombard, Bernard Henrissat, Nicolas Terrapon, The carbohydrate-active enzyme database: functions and literature, Nucleic Acids Research, Volume 50, Issue D1, 7 January 2022, Pages D571–D577, https://doi.org/10.1093/nar/gkab1045 | | | | | | | | +| eukprot | Richter, Daniel J.; Berney, Cédric; Strassert, Jürgen F. H.; Poh, Yu-Ping; Herman, Emily K.; Muñoz-Gómez, Sergio A.; Wideman, Jeremy G.; Burki, Fabien; de Vargas, Colomban. EukProt: A database of genome-scale predicted proteins across the diversity of eukaryotes. Peer Community Journal, Volume 2 (2022), article no. e56. doi : 10.24072/pcjournal.173. https://peercommunityjournal.org/articles/10.24072/pcjournal.173/ | | | | | | | | +| kofam | Takuya Aramaki, Romain Blanc-Mathieu, Hisashi Endo, Koichi Ohkubo, Minoru Kanehisa, Susumu Goto, Hiroyuki Ogata, KofamKOALA: KEGG Ortholog assignment based on profile HMM and adaptive score threshold, Bioinformatics, Volume 36, Issue 7, April 2020, Pages 2251–2252, https://doi.org/10.1093/bioinformatics/btz859 | | | | | | | | +| mibig | Barbara R Terlouw, Kai Blin, Jorge C Navarro-Muñoz, Nicole E Avalon, Marc G Chevrette, Susan Egbert, Sanghoon Lee, David Meijer, Michael J J Recchia, Zachary L Reitz, Jeffrey A van Santen, Nelly Selem-Mojica, Thomas Tørring, Liana Zaroubi, Mohammad Alanjary, Gajender Aleti, César Aguilar, Suhad A A Al-Salihi, Hannah E Augustijn, J Abraham Avelar-Rivas, Luis A Avitia-Domínguez, Francisco Barona-Gómez, Jordan Bernaldo-Agüero, Vincent A Bielinski, Friederike Biermann, Thomas J Booth, Victor J Carrion Bravo, Raquel Castelo-Branco, Fernanda O Chagas, Pablo Cruz-Morales, Chao Du, Katherine R Duncan, Athina Gavriilidou, Damien Gayrard, Karina Gutiérrez-García, Kristina Haslinger, Eric J N Helfrich, Justin J J van der Hooft, Afif P Jati, Edward Kalkreuter, Nikolaos Kalyvas, Kyo Bin Kang, Satria Kautsar, Wonyong Kim, Aditya M Kunjapur, Yong-Xin Li, Geng-Min Lin, Catarina Loureiro, Joris J R Louwen, Nico L L Louwen, George Lund, Jonathan Parra, Benjamin Philmus, Bita Pourmohsenin, Lotte J U Pronk, Adriana Rego, Devasahayam Arokia Balaya Rex, Serina Robinson, L Rodrigo Rosas-Becerra, Eve T Roxborough, Michelle A Schorn, Darren J Scobie, Kumar Saurabh Singh, Nika Sokolova, Xiaoyu Tang, Daniel Udwary, Aruna Vigneshwari, Kristiina Vind, Sophie P J M Vromans, Valentin Waschulin, Sam E Williams, Jaclyn M Winter, Thomas E Witte, Huali Xie, Dong Yang, Jingwei Yu, Mitja Zdouc, Zheng Zhong, Jérôme Collemare, Roger G Linington, Tilmann Weber, Marnix H Medema, MIBiG 3.0: a community-driven effort to annotate experimentally validated biosynthetic gene clusters, Nucleic Acids Research, Volume 51, Issue D1, 6 January 2023, Pages D603–D610, https://doi.org/10.1093/nar/gkac1049 | | | | | | | | +| mmetsp | Keeling PJ, Burki F, Wilcox HM, Allam B, Allen EE, et al. (2014) The Marine Microbial Eukaryote Transcriptome Sequencing Project (MMETSP): Illuminating the Functional Diversity of Eukaryotic Life in the Oceans through Transcriptome Sequencing. PLOS Biology 12(6): e1001889. https://doi.org/10.1371/journal.pbio.1001889 | | | | | | | | +| mycocosm | Igor V. Grigoriev, Roman Nikitin, Sajeet Haridas, Alan Kuo, Robin Ohm, Robert Otillar, Robert Riley, Asaf Salamov, Xueling Zhao, Frank Korzeniewski, Tatyana Smirnova, Henrik Nordberg, Inna Dubchak, Igor Shabalov, MycoCosm portal: gearing up for 1000 fungal genomes, Nucleic Acids Research, Volume 42, Issue D1, 1 January 2014, Pages D699–D704, https://doi.org/10.1093/nar/gkt1183 | | | | | | | | +| ncbi non-redundant | Kim D. Pruitt, Tatiana Tatusova, Donna R. Maglott, NCBI Reference Sequence (RefSeq): a curated non-redundant sequence database of genomes, transcripts and proteins, Nucleic Acids Research, Volume 33, Issue suppl_1, 1 January 2005, Pages D501–D504, https://doi.org/10.1093/nar/gki025 | | | | | | | | +| ncbifam-amr | Feldgarden, M., Brover, V., Gonzalez-Escalona, N. et al. AMRFinderPlus and the Reference Gene Catalog facilitate examination of the genomic links among antimicrobial resistance, stress response, and virulence. Sci Rep 11, 12728 (2021). https://doi.org/10.1038/s41598-021-91456-0 | | | | | | | | +| pfam | Jaina Mistry, Sara Chuguransky, Lowri Williams, Matloob Qureshi, Gustavo A Salazar, Erik L L Sonnhammer, Silvio C E Tosatto, Lisanna Paladin, Shriya Raj, Lorna J Richardson, Robert D Finn, Alex Bateman, Pfam: The protein families database in 2021, Nucleic Acids Research, Volume 49, Issue D1, 8 January 2021, Pages D412–D419, https://doi.org/10.1093/nar/gkaa913 | | | | | | | | +| phycocosm | Igor V Grigoriev, Richard D Hayes, Sara Calhoun, Bishoy Kamel, Alice Wang, Steven Ahrendt, Sergey Dusheyko, Roman Nikitin, Stephen J Mondo, Asaf Salamov, Igor Shabalov, Alan Kuo, PhycoCosm, a comparative algal genomics resource, Nucleic Acids Research, Volume 49, Issue D1, 8 January 2021, Pages D1004–D1011, https://doi.org/10.1093/nar/gkaa898 | | | | | | | | +| uniref | Baris E. Suzek, Hongzhan Huang, Peter McGarvey, Raja Mazumder, Cathy H. Wu, UniRef: comprehensive and non-redundant UniProt reference clusters, Bioinformatics, Volume 23, Issue 10, May 2007, Pages 1282–1288, https://doi.org/10.1093/bioinformatics/btm098 | | | | | | | | +| vfdb | Lihong Chen, Jian Yang, Jun Yu, Zhijian Yao, Lilian Sun, Yan Shen, Qi Jin, VFDB: a reference database for bacterial virulence factors, Nucleic Acids Research, Volume 33, Issue suppl_1, 1 January 2005, Pages D325–D328, https://doi.org/10.1093/nar/gki008 \ No newline at end of file diff --git a/DEPENDENCIES.xlsx b/DEPENDENCIES.xlsx deleted file mode 100644 index ce9258a3eb5310277b4d72d8ff8fd2467b0c22f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40189 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,b 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 A%m4rY literal 0 HcmV?d00001 diff --git a/VERSION b/VERSION index bf44aa0..0a7e926 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -1.3.0b -VDB_v5.1 +1.3.0 +VDB_v5.2 diff --git a/install/DATABASE.md b/install/DATABASE.md index bee2a16..9bb0c5b 100644 --- a/install/DATABASE.md +++ b/install/DATABASE.md @@ -34,7 +34,170 @@ A protein database is required not only for eukaryotic gene calls using MetaEuk #### Database Structure: **Current:** -*VEBA Database* version: `VDB_v5.1` +*VEBA Database* version: `VDB_v5.2` (243 GB) + +* Added `MicrobeAnnotator-KEGG` [Zenodo: 10020074](https://zenodo.org/records/10020074) which includes KEGG module pathway information from [`MicrobeAnnotator`](https://doi.org/10.1186/s12859-020-03940-5). +* Added `CAZy` protein sequences from [`dbCAN2`](https://academic.oup.com/nar/article/46/W1/W95/4996582) + +``` +tree -L 3 . +. +├── ACCESS_DATE +├── Annotate +│   ├── CAZy +│   │   └── CAZyDB.07262023.dmnd +│   ├── KOFAM +│   │   ├── ko_list +│   │   └── profiles +│   ├── MIBiG +│   │   └── mibig_v3.1.dmnd +│   ├── MicrobeAnnotator-KEGG +│   │   ├── KEGG_Bifurcating_Module_Information.pkl +│   │   ├── KEGG_Bifurcating_Module_Information.pkl.md5 +│   │   ├── KEGG_Module_Information.txt +│   │   ├── KEGG_Module_Information.txt.md5 +│   │   ├── KEGG_Regular_Module_Information.pkl +│   │   ├── KEGG_Regular_Module_Information.pkl.md5 +│   │   ├── KEGG_Structural_Module_Information.pkl +│   │   └── KEGG_Structural_Module_Information.pkl.md5 +│   ├── MicrobeAnnotator-KEGG.tar.gz +│   ├── NCBIfam-AMRFinder +│   │   ├── NCBIfam-AMRFinder.changelog.txt +│   │   ├── NCBIfam-AMRFinder.hmm.gz +│   │   └── NCBIfam-AMRFinder.tsv +│   ├── Pfam +│   │   ├── Pfam-A.hmm.gz +│   │   └── relnotes.txt +│   ├── UniRef +│   │   ├── uniref50.dmnd +│   │   ├── uniref50.release_note +│   │   ├── uniref90.dmnd +│   │   └── uniref90.release_note +│   └── VFDB +│   └── VFDB_setA_pro.dmnd +├── Classify +│   ├── CheckM2 +│   │   └── uniref100.KO.1.dmnd +│   ├── CheckV +│   │   ├── genome_db +│   │   ├── hmm_db +│   │   └── README.txt +│   ├── geNomad +│   │   ├── genomad_db +│   │   ├── genomad_db.dbtype +│   │   ├── genomad_db_h +│   │   ├── genomad_db_h.dbtype +│   │   ├── genomad_db_h.index +│   │   ├── genomad_db.index +│   │   ├── genomad_db.lookup +│   │   ├── genomad_db_mapping +│   │   ├── genomad_db.source +│   │   ├── genomad_db_taxonomy +│   │   ├── genomad_integrase_db +│   │   ├── genomad_integrase_db.dbtype +│   │   ├── genomad_integrase_db_h +│   │   ├── genomad_integrase_db_h.dbtype +│   │   ├── genomad_integrase_db_h.index +│   │   ├── genomad_integrase_db.index +│   │   ├── genomad_integrase_db.lookup +│   │   ├── genomad_integrase_db.source +│   │   ├── genomad_marker_metadata.tsv +│   │   ├── genomad_mini_db -> genomad_db +│   │   ├── genomad_mini_db.dbtype +│   │   ├── genomad_mini_db_h -> genomad_db_h +│   │   ├── genomad_mini_db_h.dbtype -> genomad_db_h.dbtype +│   │   ├── genomad_mini_db_h.index -> genomad_db_h.index +│   │   ├── genomad_mini_db.index +│   │   ├── genomad_mini_db.lookup -> genomad_db.lookup +│   │   ├── genomad_mini_db_mapping -> genomad_db_mapping +│   │   ├── genomad_mini_db.source -> genomad_db.source +│   │   ├── genomad_mini_db_taxonomy -> genomad_db_taxonomy +│   │   ├── mini_set_ids +│   │   ├── names.dmp +│   │   ├── nodes.dmp +│   │   ├── plasmid_hallmark_annotation.txt +│   │   ├── version.txt +│   │   └── virus_hallmark_annotation.txt +│   ├── GTDB +│   │   ├── fastani +│   │   ├── markers +│   │   ├── mash +│   │   ├── masks +│   │   ├── metadata +│   │   ├── mrca_red +│   │   ├── msa +│   │   ├── pplacer +│   │   ├── radii +│   │   ├── split +│   │   ├── taxonomy +│   │   └── temp +│   ├── Microeukaryotic +│   │   ├── clean_ftp.sh +│   │   ├── humann_uniref50_annotations.tsv.gz +│   │   ├── md5_checksums +│   │   ├── microeukaryotic +│   │   ├── microeukaryotic.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10 +│   │   ├── microeukaryotic.eukaryota_odb10.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h +│   │   ├── microeukaryotic.eukaryota_odb10_h.dbtype +│   │   ├── microeukaryotic.eukaryota_odb10_h.index +│   │   ├── microeukaryotic.eukaryota_odb10.index +│   │   ├── microeukaryotic.eukaryota_odb10.lookup +│   │   ├── microeukaryotic.eukaryota_odb10.source +│   │   ├── microeukaryotic_h +│   │   ├── microeukaryotic_h.dbtype +│   │   ├── microeukaryotic_h.index +│   │   ├── microeukaryotic.index +│   │   ├── microeukaryotic.lookup +│   │   ├── microeukaryotic.source +│   │   ├── reference.eukaryota_odb10.list +│   │   ├── RELEASE_NOTES +│   │   ├── source_taxonomy.tsv.gz +│   │   ├── source_to_lineage.dict.pkl.gz +│   │   └── target_to_source.dict.pkl.gz +│   └── NCBITaxonomy +│   ├── citations.dmp +│   ├── delnodes.dmp +│   ├── division.dmp +│   ├── gc.prt +│   ├── gencode.dmp +│   ├── merged.dmp +│   ├── names.dmp +│   ├── nodes.dmp +│   ├── prot.accession2taxid.FULL.gz +│   └── readme.txt +├── Contamination +│   ├── AntiFam +│   │   ├── AntiFam.hmm.gz +│   │   ├── relnotes +│   │   └── version +│   ├── chm13v2.0 +│   │   ├── chm13v2.0.1.bt2 +│   │   ├── chm13v2.0.2.bt2 +│   │   ├── chm13v2.0.3.bt2 +│   │   ├── chm13v2.0.4.bt2 +│   │   ├── chm13v2.0.rev.1.bt2 +│   │   └── chm13v2.0.rev.2.bt2 +│   └── kmers +│   └── ribokmers.fa.gz +└── MarkerSets + ├── Archaea_76.hmm.gz + ├── Bacteria_71.hmm.gz + ├── CPR_43.hmm.gz + ├── eukaryota_odb10.hmm.gz + ├── eukaryota_odb10.scores_cutoff.tsv.gz + ├── Fungi_593.hmm.gz + ├── Protista_83.hmm.gz + └── README + +37 directories, 112 files +``` + +**Deprecated:** + +
+ *VEBA Database* version: `VDB_v5.1` * `VDB_v5` → `VDB_v5.1` updates `GTDB` database from `r207_v2` → `r214`. * Changes `${VEBA_DATABASE}/Classify/GTDBTk` → `${VEBA_DATABASE}/Classify/GTDB`. @@ -177,8 +340,7 @@ tree -L 3 . ├── Protista_83.hmm.gz └── README ``` - -**Deprecated:** +
*VEBA Database* version: `VDB_v5` @@ -464,7 +626,7 @@ tree -L 3 .
- *VEBA Database* version: VDB_v3.1 + *VEBA Database* version: `VDB_v3.1` The same as `VDB_v3` but updates `VDB-Microeukaryotic_v2` to `VDB-Microeukaryotic_v2.1` which has a `reference.eukaryota_odb10.list` containing only the subset of identifiers that core eukaryotic markers (useful for classification). @@ -573,7 +735,7 @@ tree -L 3 .
- *VEBA Database* version: VDB_v3 + *VEBA Database* version: `VDB_v3` ``` tree -L 3 . @@ -671,7 +833,7 @@ tree -L 3 .
- *VEBA Database* version: VDB_v2 + *VEBA Database* version: `VDB_v2` * Compatible with *VEBA* version: `v1.0.2a+` @@ -769,7 +931,7 @@ tree -L 3 .
- *VEBA Database* version: VDB_v1 + *VEBA Database* version: `VDB_v1` * Compatible with *VEBA* version: `v1.0.0`, `v1.0.1` diff --git a/install/README.md b/install/README.md index 28c5be0..d7d56b0 100644 --- a/install/README.md +++ b/install/README.md @@ -7,7 +7,7 @@ The basis for these environments is creating a separate environment for each mod The majority of the time taken to build database is downloading/decompressing large archives, `Diamond` database creation of `UniRef`, and `MMSEQS2` database creation of microeukaryotic protein database. -Total size is `214 GB` but if you have certain databases installed already then you can just symlink them so the `VEBA_DATABASE` path has the correct structure. Note, the exact size may vary as Pfam and UniRef are updated regularly. +Total size is `243 GB` but if you have certain databases installed already then you can just symlink them so the `VEBA_DATABASE` path has the correct structure. Note, the exact size may vary as Pfam and UniRef are updated regularly. Each major version will be packaged as a [release](https://github.com/jolespin/veba/releases) which will include a log of module and script versions. @@ -83,7 +83,7 @@ The `VEBA` installation is going to configure some `conda` environments for you ``` # For stable version, download and decompress the tarball: -VERSION="1.2.0" +VERSION="1.3.0" wget https://github.com/jolespin/veba/archive/refs/tags/v${VERSION}.tar.gz tar -xvf v${VERSION}.tar.gz && mv veba-${VERSION} veba @@ -181,6 +181,7 @@ VEBA-database_env VEBA-mapping_env VEBA-phylogeny_env VEBA-preprocess_env +VEBA-profile_env ``` All the environments should have the `VEBA_DATABASE` environment variable set. If not, then add it manually to ~/.bash_profile: `export VEBA_DATABASE=/path/to/veba_database`. diff --git a/install/download_databases.sh b/install/download_databases.sh index 1e8eb62..12833fd 100644 --- a/install/download_databases.sh +++ b/install/download_databases.sh @@ -1,6 +1,6 @@ #!/bin/bash -# __version__ = "2023.6.20" -# VEBA_DATABASE_VERSION = "VDB_v5.1" +# __version__ = "2023.10.23" +# VEBA_DATABASE_VERSION = "VDB_v5.2" # MICROEUKAYROTIC_DATABASE_VERSION = "VDB-Microeukaryotic_v2.1" # Create database @@ -110,13 +110,18 @@ rm -rf ${DATABASE_DIRECTORY}/MarkerSets.tar.gz # KOFAMSCAN echo ". .. ... ..... ........ ............." -echo "vii * Processing KOFAMSCAN profile HMM marker sets" +echo "vii * Processing KEGG profile HMM marker sets" echo ". .. ... ..... ........ ............." mkdir -v -p ${DATABASE_DIRECTORY}/Annotate/KOFAM wget -v -O - ftp://ftp.genome.jp/pub/db/kofam/ko_list.gz | gzip -d > ${DATABASE_DIRECTORY}/Annotate/KOFAM/ko_list wget -v -c ftp://ftp.genome.jp/pub/db/kofam/profiles.tar.gz -O - | tar -xz mv profiles ${DATABASE_DIRECTORY}/Annotate/KOFAM/ +wget -v -O ${DATABASE_DIRECTORY}/MicrobeAnnotator-KEGG.tar.gz https://zenodo.org/records/10020074/files/MicrobeAnnotator-KEGG.tar.gz?download=1 +tar xvzf ${DATABASE_DIRECTORY}/MicrobeAnnotator-KEGG.tar.gz -C ${DATABASE_DIRECTORY}/Annotate --no-xattrs +rm -rf ${DATABASE_DIRECTORY}/Annotate/._MicrobeAnnotator-KEGG +rm -rf ${DATABASE_DIRECTORY}/MicrobeAnnotator-KEGG.tar.gz + # Pfam echo ". .. ... ..... ........ ............." echo "viii * Processing Pfam profile HMM marker sets" @@ -183,6 +188,12 @@ wget -v -P ${DATABASE_DIRECTORY} http://www.mgc.ac.cn/VFs/Down/VFDB_setA_pro.fas diamond makedb --in ${DATABASE_DIRECTORY}/VFDB_setA_pro.fas.gz --db ${DATABASE_DIRECTORY}/Annotate/VFDB/VFDB_setA_pro.dmnd rm -rf ${DATABASE_DIRECTORY}/VFDB_setA_pro.fas.gz +# CAZy +mkdir -v -p ${DATABASE_DIRECTORY}/Annotate/CAZy +wget -v -P ${DATABASE_DIRECTORY} https://bcb.unl.edu/dbCAN2/download/CAZyDB.07262023.fa +diamond makedb --in ${DATABASE_DIRECTORY}/CAZyDB.07262023.fa --db ${DATABASE_DIRECTORY}/Annotate/CAZy/CAZyDB.07262023.dmnd +rm -rf ${DATABASE_DIRECTORY}/CAZyDB.07262023.fa + # Contamination echo ". .. ... ..... ........ ............." echo "xi * Processing contamination databases" diff --git a/install/uninstall_veba.sh b/install/uninstall_veba.sh index ada8f8a..125bf31 100644 --- a/install/uninstall_veba.sh +++ b/install/uninstall_veba.sh @@ -1,5 +1,5 @@ #!/bin/bash -# __version__ = "2023.5.15" +# __version__ = "2023.10.18" CONDA_BASE=$(conda run -n base bash -c "echo \${CONDA_PREFIX}") @@ -12,4 +12,4 @@ echo -e " _ _ _______ ______ _______\n \ / |______ |_____] |_____|\n \/ echo -e "..............................." echo -e " Uninstall Complete " echo -e "..............................." -echo -e "Don't forget to remove the VEBA database directory." +echo -e "Don't forget to remove the VEBA database directory if you don't need it anymore. If you're doing a reinstall, then think twice about this." diff --git a/src/annotate.py b/src/annotate.py index 48aff64..c050e86 100755 --- a/src/annotate.py +++ b/src/annotate.py @@ -1,9 +1,10 @@ #!/usr/bin/env python from __future__ import print_function, division -import sys, os, argparse, glob, shutil +import sys, os, argparse, glob, shutil, gzip from collections import OrderedDict, defaultdict import pandas as pd +from Bio.SeqIO.FastaIO import SimpleFastaParser # Soothsayer Ecosystem from genopype import * @@ -14,7 +15,22 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.10.16" +__version__ = "2023.10.25" + +def get_preprocess_cmd( input_filepaths, output_filepaths, output_directory, directories, opts, program): + cmd = [ + "cat", + " ".join(input_filepaths), + "|", + os.environ["seqkit"], + "seq", + "-M", + opts.maximum_protein_length, + "--only-id", + ">", + os.path.join(directories["tmp"], "proteins.faa"), + ] + return cmd def get_diamond_cmd( input_filepaths, output_filepaths, output_directory, directories, opts, program): tmp = os.path.join(directories["tmp"], program) @@ -128,33 +144,71 @@ def get_merge_annotations_cmd( input_filepaths, output_filepaths, output_directo "--diamond_uniref {}".format(input_filepaths[0]), "--diamond_mibig {}".format(input_filepaths[1]), "--diamond_vfdb {}".format(input_filepaths[2]), - "--hmmsearch_pfam {}".format(input_filepaths[3]), - "--hmmsearch_amr {}".format(input_filepaths[4]), - "--hmmsearch_antifam {}".format(input_filepaths[5]), - "--kofam {}".format(input_filepaths[6]), + "--diamond_cazy {}".format(input_filepaths[3]), + "--hmmsearch_pfam {}".format(input_filepaths[4]), + "--hmmsearch_amr {}".format(input_filepaths[5]), + "--hmmsearch_antifam {}".format(input_filepaths[6]), + "--kofam {}".format(input_filepaths[7]), # "--veba_database {}".format(opts.veba_database), "--fasta {}".format(opts.proteins), - "-o {}".format(output_directory) + "-o {}".format(output_directory), + '--composite_name_joiner="{}"'.format(opts.composite_name_joiner), ] if opts.identifier_mapping: cmd += [ "-i {}".format(opts.identifier_mapping), + # "--genome_cluster_column_label {}".format(opts.genome_cluster_column_label), + # "--protein_cluster_column_label {}".format(opts.protein_cluster_column_label), ] return cmd -def get_propagate_annotations_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + +def get_mcr_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): # Command cmd = [ - os.environ["propagate_annotations_from_representatives.py"], + os.environ["compile_ko_from_annotations.py"], "-i {}".format(input_filepaths[0]), - "-c {}".format(opts.protein_clusters), - "-o {}".format(output_filepaths[0]), - ] + "-o {}".format(os.path.join(output_directory, "kos.genomes.tsv")), + "-l genome", + + "&&", + + os.environ["module_completion_ratios.py"], + "-i {}".format(os.path.join(output_directory, "kos.genomes.tsv")), + "-o {}".format(os.path.join(output_directory, "module_completion_ratios.genomes.tsv")), + "--database_directory {}".format(os.path.join(opts.veba_database, "Annotate", "MicrobeAnnotator-KEGG")), + + "&&", + + os.environ["compile_ko_from_annotations.py"], + "-i {}".format(input_filepaths[0]), + "-o {}".format(os.path.join(output_directory, "kos.genome_clusters.tsv")), + "-l genome_cluster", + + "&&", + + os.environ["module_completion_ratios.py"], + "-i {}".format(os.path.join(output_directory, "kos.genome_clusters.tsv")), + "-o {}".format(os.path.join(output_directory, "module_completion_ratios.genome_clusters.tsv")), + "--database_directory {}".format(os.path.join(opts.veba_database, "Annotate", "MicrobeAnnotator-KEGG")), + ] return cmd +# def get_propagate_annotations_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): + +# # Command +# cmd = [ +# os.environ["propagate_annotations_from_representatives.py"], +# "-i {}".format(input_filepaths[0]), +# "-c {}".format(opts.protein_clusters), +# "-o {}".format(output_filepaths[0]), +# ] + +# return cmd + # ============ # Run Pipeline # ============ @@ -165,7 +219,11 @@ def add_executables_to_environment(opts): """ accessory_scripts = { "merge_annotations.py", - "propagate_annotations_from_representatives.py", + # "propagate_annotations_from_representatives.py", + "compile_ko_from_annotations.py", + "module_completion_ratios.py", + "merge_annotations.py", + "compile_custom_humann_database_from_annotations.py", } required_executables={ @@ -216,6 +274,49 @@ def create_pipeline(opts, directories, f_cmds): pipeline = ExecutablePipeline(name=__program__, f_cmds=f_cmds, checkpoint_directory=directories["checkpoints"], log_directory=directories["log"]) + # # ========== + # # Diamond [nr] + # # ========== + # step = 0 + + # program = "preprocess" + # program_label = "{}__{}".format(step, program) + # # Add to directories + + # # Info + # description = "Preprocess sequences" + # if os.path.isdir(opts.proteins): + # input_filepaths = glob.glob(os.path.join(opts.proteins, "*.{}".format(opts.extension))) + # else: + # input_filepaths = [opts.proteins] + # opts.protein_files = input_filepaths + + # # i/o + + # output_filepaths = [os.path.join(directories["tmp"], "proteins.faa")] + + # params = { + # "input_filepaths":input_filepaths, + # "output_filepaths":output_filepaths, + # "output_directory":output_directory, + # "opts":opts, + # "directories":directories, + # "program":program, + # } + + # cmd = get_preprocess_cmd(**params) + + # pipeline.add_step( + # id=program, + # description = description, + # step=step, + # cmd=cmd, + # input_filepaths = input_filepaths, + # output_filepaths = output_filepaths, + # validate_inputs=True, + # validate_outputs=True, + # ) + # ========== # Diamond [nr] # ========== @@ -232,7 +333,7 @@ def create_pipeline(opts, directories, f_cmds): # i/o input_filepaths = [ opts.proteins, - os.path.join(opts.veba_database, "Annotate", "UniRef", "{}.dmnd".format(opts.uniref)), + os.path.join(opts.veba_database, "Annotate", "UniRef", "{}.dmnd".format(opts.uniref)), ] output_filenames = ["output.tsv.gz"] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) @@ -344,11 +445,55 @@ def create_pipeline(opts, directories, f_cmds): validate_inputs=True, validate_outputs=True, ) + # ========== - # HMMSearch-Pfam + # Diamond # ========== step = 4 + program = "diamond-cazy" + program_label = "{}__{}".format(step, program) + # Add to directories + output_directory = directories[("intermediate", program_label)] = create_directory(os.path.join(directories["intermediate"], program_label)) + + # Info + description = "Diamond [CAZy]" + + # i/o + input_filepaths = [ + opts.proteins, + os.path.join(opts.veba_database, "Annotate", "CAZy", "CAZyDB.07262023.dmnd"), + ] + output_filenames = ["output.tsv.gz"] + output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) + + params = { + "input_filepaths":input_filepaths, + "output_filepaths":output_filepaths, + "output_directory":output_directory, + "opts":opts, + "directories":directories, + "program":program, + } + + cmd = get_diamond_cmd(**params) + + pipeline.add_step( + id=program, + description = description, + step=step, + cmd=cmd, + input_filepaths = input_filepaths, + output_filepaths = output_filepaths, + validate_inputs=True, + validate_outputs=True, + ) + + # ========== + # HMMSearch-Pfam + # ========== + step = 5 + program = "hmmsearch-pfam" program_label = "{}__{}".format(step, program) # Add to directories @@ -389,7 +534,7 @@ def create_pipeline(opts, directories, f_cmds): # ============= # HMMSearch-AMR # ============= - step = 5 + step = 6 program = "hmmsearch-amr" program_label = "{}__{}".format(step, program) @@ -431,7 +576,7 @@ def create_pipeline(opts, directories, f_cmds): # ================= # HMMSearch-AntiFam # ================= - step = 6 + step = 7 program = "hmmsearch-antifam" program_label = "{}__{}".format(step, program) @@ -472,7 +617,7 @@ def create_pipeline(opts, directories, f_cmds): # ========== # KOFAMSCAN # ========== - step = 7 + step = 8 program = "kofamscan" program_label = "{}__{}".format(step, program) @@ -514,7 +659,7 @@ def create_pipeline(opts, directories, f_cmds): # ========== # Merge annotations # ========== - step = 8 + step = 9 program = "merge_annotations" program_label = "{}__{}".format(step, program) @@ -529,14 +674,18 @@ def create_pipeline(opts, directories, f_cmds): os.path.join(directories[("intermediate", "1__diamond-uniref")], "output.tsv.gz"), os.path.join(directories[("intermediate", "2__diamond-mibig")], "output.tsv.gz"), os.path.join(directories[("intermediate", "3__diamond-vfdb")], "output.tsv.gz"), - os.path.join(directories[("intermediate", "4__hmmsearch-pfam")], "output.tsv.gz"), - os.path.join(directories[("intermediate", "5__hmmsearch-amr")], "output.tsv.gz"), - os.path.join(directories[("intermediate", "6__hmmsearch-antifam")], "output.tsv.gz"), - os.path.join(directories[("intermediate", "7__kofamscan")], "output.tsv.gz"), + os.path.join(directories[("intermediate", "4__diamond-cazy")], "output.tsv.gz"), + os.path.join(directories[("intermediate", "5__hmmsearch-pfam")], "output.tsv.gz"), + os.path.join(directories[("intermediate", "6__hmmsearch-amr")], "output.tsv.gz"), + os.path.join(directories[("intermediate", "7__hmmsearch-antifam")], "output.tsv.gz"), + os.path.join(directories[("intermediate", "8__kofamscan")], "output.tsv.gz"), ] - output_filenames = ["annotations.tsv.gz"] + output_filenames = ["annotations.proteins.tsv.gz"] + + if opts.identifier_mapping: + output_filenames += ["annotations.protein_clusters.tsv.gz"] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) @@ -564,24 +713,22 @@ def create_pipeline(opts, directories, f_cmds): # ========== # Propagate annotations # ========== - if opts.protein_clusters: - step = 9 + if opts.identifier_mapping: + step = 10 - program = "propogate_annotations" + program = "module_completion_ratios" program_label = "{}__{}".format(step, program) # Add to directories output_directory = directories["output"] # Info - description = "Propagate annotation results" + description = "Calculate module completion ratios" # i/o input_filepaths = [ - os.path.join(directories["output"], "annotations.tsv.gz"), - opts.protein_clusters, + os.path.join(directories["output"], "annotations.proteins.tsv.gz"), ] - output_filenames = ["annotations.proteins.tsv.gz"] - + output_filenames = ["kos.genomes.tsv", "kos.genome_clusters.tsv", "module_completion_ratios.genomes.tsv", "module_completion_ratios.genome_clusters.tsv"] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) params = { @@ -592,7 +739,7 @@ def create_pipeline(opts, directories, f_cmds): "directories":directories, } - cmd = get_propagate_annotations_cmd(**params) + cmd = get_mcr_cmd(**params) pipeline.add_step( id=program, @@ -607,28 +754,34 @@ def create_pipeline(opts, directories, f_cmds): return pipeline + + # Configure parameters def configure_parameters(opts, directories): assert_acceptable_arguments(opts.diamond_sensitivity, {"", "fast", "mid-sensitive", "sensitive", "more-sensitive", "very-sensitive", "ultra-sensitive"}) assert_acceptable_arguments(opts.uniref, {"uniref90","uniref50"}) - if opts.identifier_mapping or opts.protein_clusters: - assert bool(opts.identifier_mapping) != bool(opts.protein_clusters), "--identifier_mapping is for protein-level annotations and cannot be used with --protein_clusters" + # if opts.identifier_mapping or opts.protein_cluster_column_label: + # assert bool(opts.identifier_mapping) == bool(opts.protein_cluster_column_label), "--identifier_mapping must be provided with --protein_cluster_column_label" + assert opts.maximum_protein_length < 100000, "Must be < 100k because sequences this length or greater will cause HMMSearch to crash" # Set environment variables add_executables_to_environment(opts=opts) - # Concatenate proteins if a directory is provided - if os.path.isdir(opts.proteins): - fp_protein_cat = os.path.join(directories["tmp"], "proteins.faa") - with open(fp_protein_cat, "w") as f_out: - for fp in glob.glob(os.path.join(opts.proteins,"*.{}".format(opts.extension))): - with open(fp, "r") as f_in: - shutil.copyfileobj(f_in, f_out) - print("Concatenating files from --proteins {}".format(opts.proteins), file=sys.stdout) - print("Changing --proteins to {}".format(fp_protein_cat), file=sys.stdout) - opts.proteins = fp_protein_cat +def check_protein_sequence_lengths(path, maximum_protein_length): + if path.endswith(".gz"): + f = gzip.open(path, "rt") + else: + f = open(path, "r") + + failed_identifiers = list() + for header, seq in pv(SimpleFastaParser(f), "Checking sequence lengths"): + n = len(seq) + if n > maximum_protein_length: + failed_identifiers.append(header.split(" ")[0]) + assert len(failed_identifiers) == 0, "Please remove sequences longer than {}\nYou can do this by running `seqkit seq -M {} {}\n\nThe following identifiers are too long: {}".format(maximum_protein_length, maximum_protein_length, path, "\n\t".join(failed_identifiers)) + def main(args=None): @@ -638,19 +791,24 @@ def main(args=None): # Path info description = """ Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) - usage = "{} -a -o |Optional: -i ]\nWarnings:Proteins >100k will cause HMMSearch to crash. Please prefilter using `seqkit seq -M 100000`".format(__program__) + usage = "{} -a -o |Optional: -i $OUTPUT_DIRECTORY/$ID.tRNA.gff >$OUTPUT_DIRECTORY/$ID.tRNA.struct >$OUTPUT_DIRECTORY/$ID.tRNA.txt - {} -$DOMAIN_ABBREVIATION --forceow --progress --threads {} --fasta $OUTPUT_DIRECTORY/$ID.tRNA --gff $OUTPUT_DIRECTORY/$ID.tRNA.gff --struct $OUTPUT_DIRECTORY/$ID.tRNA.struct {} $GENOME_FASTA > $OUTPUT_DIRECTORY/$ID.tRNA.txt - fi + + TRNA_FASTA=$OUTPUT_DIRECTORY/$ID.tRNA + + if [[ -s "$TRNA_FASTA" ]]; + then + echo "[Skipping] [tRNAscan-SE] $GENOME_FASTA because tRNA fasta exists and is not empty" + else + echo "[Running] [tRNAscan-SE] $GENOME_FASTA" + {} -$DOMAIN_ABBREVIATION --forceow --progress --threads {} --fasta $OUTPUT_DIRECTORY/$ID.tRNA --gff $OUTPUT_DIRECTORY/$ID.tRNA.gff --struct $OUTPUT_DIRECTORY/$ID.tRNA.struct {} $GENOME_FASTA > $OUTPUT_DIRECTORY/$ID.tRNA.txt + fi + fi done done diff --git a/src/cluster.py b/src/cluster.py index d098b17..320ef00 100755 --- a/src/cluster.py +++ b/src/cluster.py @@ -13,7 +13,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.10.16" +__version__ = "2023.10.24" # Global clustering def get_global_clustering_cmd( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -165,7 +165,9 @@ def create_pipeline(opts, directories, f_cmds): input_filepaths = [opts.genomes_table] output_filenames = [ + "output/*.tsv.gz", "output/*.tsv", + ] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) @@ -210,7 +212,9 @@ def create_pipeline(opts, directories, f_cmds): input_filepaths = [opts.genomes_table] output_filenames = [ + "output/*.tsv.gz", "output/*.tsv", + ] output_filepaths = list(map(lambda filename: os.path.join(output_directory, filename), output_filenames)) diff --git a/src/phylogeny.py b/src/phylogeny.py index dbe90cd..002cce6 100755 --- a/src/phylogeny.py +++ b/src/phylogeny.py @@ -14,7 +14,7 @@ pd.options.display.max_colwidth = 100 # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.10.16" +__version__ = "2023.10.27" # Assembly def preprocess( input_filepaths, output_filepaths, output_directory, directories, opts): @@ -166,11 +166,13 @@ def get_fasttree_cmd(input_filepaths, output_filepaths, output_directory, direct output_filepaths[0], "-i", output_filepaths[1], + ] + if not opts.no_show_support: + cmd += ["--ss"] + if not opts.no_show_branch_length: + cmd += ["--sbl"] - - - ] return cmd @@ -198,8 +200,13 @@ def get_iqtree_cmd(input_filepaths, output_filepaths, output_directory, director output_filepaths[0], "-i", output_filepaths[1], - ] + + if not opts.no_show_support: + cmd += ["--ss"] + if not opts.no_show_branch_length: + cmd += ["--sbl"] + return cmd @@ -607,6 +614,8 @@ def main(args=None): parser_tree.add_argument("--iqtree_mset", type=str, default="WAG,LG", help="IQTree | Model set to choose from [Default: WAG,LG]") parser_tree.add_argument("--iqtree_bootstraps", type=int, default=1000, help="IQTree | Bootstraps [Default: 1000]") parser_tree.add_argument("--iqtree_options", type=str, default="", help="IQTree | More options (e.g. --arg 1 ) [Default: '']") + parser_tree.add_argument("--no_show_support", action="store_true", help="ETE3 | Don't show branch bootstrap/support values") + parser_tree.add_argument("--no_show_branch_length", action="store_true", help="ETE3 | Don't show branch lengths") # Options opts = parser.parse_args() @@ -629,6 +638,8 @@ def main(args=None): directories["checkpoints"] = create_directory(os.path.join(directories["project"], "checkpoints")) directories["intermediate"] = create_directory(os.path.join(directories["project"], "intermediate")) os.environ["TMPDIR"] = directories["tmp"] + os.environ["QT_QPA_PLATFORM"] = "offscreen" + # export QT_QPA_PLATFORM=offscreen; # Info print(format_header(__program__, "="), file=sys.stdout) diff --git a/src/scripts/compile_ko_from_annotations.py b/src/scripts/compile_ko_from_annotations.py new file mode 100755 index 0000000..7d64137 --- /dev/null +++ b/src/scripts/compile_ko_from_annotations.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +from __future__ import print_function, division +import sys, os, argparse, glob +from collections import defaultdict +# import numpy as np +import pandas as pd +from tqdm import tqdm + +pd.options.display.max_colwidth = 100 +# from tqdm import tqdm +__program__ = os.path.split(sys.argv[0])[-1] +__version__ = "2023.10.23" + + +def main(args=None): + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i -l genome -o ".format(__program__) + epilog = "Copyright 2021 Josh L. Espinoza (jespinoz@jcvi.org)" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + # Pipeline + + parser.add_argument("-i","--annotation_results", type=str, default = "stdin", help = "path/to/annotation.tsv from annotate.py [Default: stdin]") + parser.add_argument("-o","--output", type=str, default="stdout", help = "path/to/genomes_table.tsv [Default: stdout]") + parser.add_argument("-l","--level", type=str, default="genome", help = "level {genome, genome_cluster} [Default: genome]") + parser.add_argument("-p", "--include_protein_identifiers", action="store_true", help = "Write protein identifiers") + parser.add_argument("--header", action="store_true", help = "Write header") + + # Options + opts = parser.parse_args() + opts.script_directory = script_directory + opts.script_filename = script_filename + + assert opts.level in {"genome", "genome_cluster"}, "--level must be either {genome, genome_cluster}" + + if opts.level == "genome": + level_field = ("Identifiers", "id_genome") + if opts.level == "genome_cluster": + level_field = ("Identifiers", "id_genome_cluster") + + if opts.annotation_results == "stdin": + opts.annotation_results = sys.stdin + if opts.output == "stdout": + opts.output = sys.stdout + + df_annotations = pd.read_csv(opts.annotation_results, sep="\t", index_col=0, header=[0,1]) + + output = list() + for id_protein, (id_organism, ko_ids) in tqdm(df_annotations.loc[:,[level_field, ("KOFAM", "ids")]].iterrows(), "Compiling KO identifiers", total=df_annotations.shape[0]): + ko_ids = eval(ko_ids) + if len(ko_ids): + for id_ko in ko_ids: + output.append([id_protein, id_organism, id_ko]) + df_output = pd.DataFrame(output, columns=["id_protein", level_field[1], "id_kegg-ortholog"]) + + if not opts.include_protein_identifiers: + df_output = df_output.drop("id_protein", axis=1) + + df_output.to_csv(opts.output, sep="\t", header=bool(opts.header), index=False) + +if __name__ == "__main__": + main() diff --git a/src/scripts/concatenate_dataframes.py b/src/scripts/concatenate_dataframes.py index d36a27a..c58b020 100755 --- a/src/scripts/concatenate_dataframes.py +++ b/src/scripts/concatenate_dataframes.py @@ -3,7 +3,7 @@ import pandas as pd __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.8.30" +__version__ = "2023.10.23" def main(argv=None): # Path info @@ -82,7 +82,7 @@ def main(argv=None): if opts.prepend_index_levels is not None: df.index = df.index.map(lambda x: (*opts.prepend_index_levels, x)) except (pd.errors.EmptyDataError, FileNotFoundError) as e: - print("[Skipping] {}".format(e), file=sys.stderr) + print("[Skipping] {}: {}".format(e, fp), file=sys.stderr) if df is not None: dataframes.append(df) else: diff --git a/src/scripts/convert_table_to_fasta.py b/src/scripts/convert_table_to_fasta.py old mode 100644 new mode 100755 diff --git a/src/scripts/eukaryotic_gene_modeling_wrapper.py b/src/scripts/eukaryotic_gene_modeling_wrapper.py index 0051cd0..83591e7 100755 --- a/src/scripts/eukaryotic_gene_modeling_wrapper.py +++ b/src/scripts/eukaryotic_gene_modeling_wrapper.py @@ -8,11 +8,12 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.7.4" +__version__ = "2023.10.16" # Tiara def get_tiara_cmd(input_filepaths, output_filepaths, output_directory, directories, opts): @@ -312,7 +313,14 @@ def get_trnascan_cmd(input_filepaths, output_filepaths, output_directory, direct for GENOME_FASTA in {} do ID=$(basename $GENOME_FASTA .fa) - {} {} --forceow --progress --thread {} --fasta $OUTPUT_DIRECTORY/$ID.tRNA --gff $OUTPUT_DIRECTORY/$ID.tRNA.gff --struct $OUTPUT_DIRECTORY/$ID.tRNA.struct {} $GENOME_FASTA > $OUTPUT_DIRECTORY/$ID.tRNA.txt + TRNA_FASTA=$OUTPUT_DIRECTORY/$ID.tRNA + if [[ -s "$TRNA_FASTA" ]]; + then + echo "[Skipping] [tRNAscan-SE] $GENOME_FASTA because tRNA fasta exists and is not empty" + else + echo "[Running] [tRNAscan-SE] $GENOME_FASTA" + {} {} --forceow --progress --thread {} --fasta $OUTPUT_DIRECTORY/$ID.tRNA --gff $OUTPUT_DIRECTORY/$ID.tRNA.gff --struct $OUTPUT_DIRECTORY/$ID.tRNA.struct {} $GENOME_FASTA > $OUTPUT_DIRECTORY/$ID.tRNA.txt + fi done """.format( output_directory, @@ -1429,6 +1437,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) diff --git a/src/scripts/global_clustering.py b/src/scripts/global_clustering.py index ba0be7f..4783794 100755 --- a/src/scripts/global_clustering.py +++ b/src/scripts/global_clustering.py @@ -10,11 +10,12 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.10.5" +__version__ = "2023.10.24" def get_basename(x): _, fn = os.path.split(x) @@ -38,7 +39,7 @@ def get_protein_cluster_prevalence(df_input:pd.DataFrame): # Create output df_output = pd.DataFrame(A, index=genomes, columns=clusters) df_output.index.name = "id_genome" - df_output.columns.name = "id_protein-cluster" + df_output.columns.name = "id_protein_cluster" return df_output @@ -187,6 +188,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) @@ -495,6 +497,9 @@ def main(args=None): a = pd.read_csv(fp, sep="\t", index_col=0, header=None).iloc[:,0] proteincluster_to_representative.append(a) proteincluster_to_representative = pd.concat(proteincluster_to_representative) + representatives = set(proteincluster_to_representative.values) + + df_proteins["protein_cluster_representative"] = df_proteins.index.map(lambda x: x in representatives) with open(os.path.join(directories["output"], "representative_sequences.faa"), "w") as f_representatives: for id_proteincluster, id_representative in pv(proteincluster_to_representative.items(), description=" * ({}) Writing protein cluster representative sequences".format(format_duration(t0)), unit="sequence", total=proteincluster_to_representative.size): @@ -590,12 +595,12 @@ def main(args=None): # Writing output files print(format_header(" * ({}) Writing Output Tables:".format(format_duration(t0))), file=sys.stdout) - df_mags.to_csv(os.path.join(directories["output"], "identifier_mapping.genomes.tsv"), sep="\t") - df_scaffolds.to_csv(os.path.join(directories["output"], "identifier_mapping.scaffolds.tsv"), sep="\t") - df_proteins.to_csv(os.path.join(directories["output"], "identifier_mapping.proteins.tsv"), sep="\t") - df_genomeclusters.to_csv(os.path.join(directories["output"], "genome_clusters.tsv"), sep="\t") - df_proteinclusters.to_csv(os.path.join(directories["output"], "protein_clusters.tsv"), sep="\t") - df_fcr.to_csv(os.path.join(directories["output"], "feature_compression_ratios.tsv"), sep="\t") + df_mags.to_csv(os.path.join(directories["output"], "identifier_mapping.genomes.tsv.gz"), sep="\t") + df_scaffolds.to_csv(os.path.join(directories["output"], "identifier_mapping.scaffolds.tsv.gz"), sep="\t") + df_proteins.to_csv(os.path.join(directories["output"], "identifier_mapping.proteins.tsv.gz"), sep="\t") + df_genomeclusters.to_csv(os.path.join(directories["output"], "genome_clusters.tsv.gz"), sep="\t") + df_proteinclusters.to_csv(os.path.join(directories["output"], "protein_clusters.tsv.gz"), sep="\t") + df_fcr.to_csv(os.path.join(directories["output"], "feature_compression_ratios.tsv.gz"), sep="\t") df_mags["id_genome_cluster"].to_frame().dropna(how="any", axis=0).to_csv(os.path.join(directories["output"], "mags_to_slcs.tsv"), sep="\t", header=None) df_scaffolds["id_genome"].to_frame().dropna(how="any", axis=0).to_csv(os.path.join(directories["output"], "scaffolds_to_mags.tsv"), sep="\t", header=None) df_scaffolds["id_genome_cluster"].to_frame().dropna(how="any", axis=0).to_csv(os.path.join(directories["output"], "scaffolds_to_slcs.tsv"), sep="\t", header=None) diff --git a/src/scripts/local_clustering.py b/src/scripts/local_clustering.py index c03f48a..198f8c9 100755 --- a/src/scripts/local_clustering.py +++ b/src/scripts/local_clustering.py @@ -10,11 +10,12 @@ # Soothsayer Ecosystem from genopype import * +from genopype import __version__ as genopype_version from soothsayer_utils import * # from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.10.5" +__version__ = "2023.10.24" def get_basename(x): _, fn = os.path.split(x) @@ -38,7 +39,7 @@ def get_protein_cluster_prevalence(df_input:pd.DataFrame): # Create output df_output = pd.DataFrame(A, index=genomes, columns=clusters) df_output.index.name = "id_genome" - df_output.columns.name = "id_protein-cluster" + df_output.columns.name = "id_protein_cluster" return df_output @@ -182,6 +183,7 @@ def main(args=None): print(format_header("Configuration:", "-"), file=sys.stdout) print("Python version:", sys.version.replace("\n"," "), file=sys.stdout) print("Python path:", sys.executable, file=sys.stdout) #sys.path[2] + print("GenoPype version:", genopype_version, file=sys.stdout) #sys.path[2] print("Script version:", __version__, file=sys.stdout) print("Moment:", get_timestamp(), file=sys.stdout) print("Directory:", os.getcwd(), file=sys.stdout) @@ -501,6 +503,9 @@ def main(args=None): a = pd.read_csv(fp, sep="\t", index_col=0, header=None).iloc[:,0] proteincluster_to_representative.append(a) proteincluster_to_representative = pd.concat(proteincluster_to_representative) + representatives = set(proteincluster_to_representative.values) + + df_proteins["protein_cluster_representative"] = df_proteins.index.map(lambda x: x in representatives) with open(os.path.join(directories["output"], "representative_sequences.faa"), "w") as f_representatives: for id_proteincluster, id_representative in pv(proteincluster_to_representative.items(), description=" * ({}) Writing protein cluster representative sequences".format(format_duration(t0)), unit="sequence", total=proteincluster_to_representative.size): @@ -582,12 +587,12 @@ def main(args=None): # Writing output files print(format_header(" * ({}) Writing Output Tables:".format(format_duration(t0))), file=sys.stdout) - df_mags.to_csv(os.path.join(directories["output"], "identifier_mapping.genomes.tsv"), sep="\t") - df_scaffolds.to_csv(os.path.join(directories["output"], "identifier_mapping.scaffolds.tsv"), sep="\t") - df_proteins.to_csv(os.path.join(directories["output"], "identifier_mapping.proteins.tsv"), sep="\t") - df_genomeclusters.to_csv(os.path.join(directories["output"], "genome_clusters.tsv"), sep="\t") - df_proteinclusters.to_csv(os.path.join(directories["output"], "protein_clusters.tsv"), sep="\t") - df_fcr.to_csv(os.path.join(directories["output"], "feature_compression_ratios.tsv"), sep="\t") + df_mags.to_csv(os.path.join(directories["output"], "identifier_mapping.genomes.tsv.gz"), sep="\t") + df_scaffolds.to_csv(os.path.join(directories["output"], "identifier_mapping.scaffolds.tsv.gz"), sep="\t") + df_proteins.to_csv(os.path.join(directories["output"], "identifier_mapping.proteins.tsv.gz"), sep="\t") + df_genomeclusters.to_csv(os.path.join(directories["output"], "genome_clusters.tsv.gz"), sep="\t") + df_proteinclusters.to_csv(os.path.join(directories["output"], "protein_clusters.tsv.gz"), sep="\t") + df_fcr.to_csv(os.path.join(directories["output"], "feature_compression_ratios.tsv.gz"), sep="\t") df_mags["id_genome_cluster"].to_frame().dropna(how="any", axis=0).to_csv(os.path.join(directories["output"], "mags_to_slcs.tsv"), sep="\t", header=None) df_scaffolds["id_genome"].to_frame().dropna(how="any", axis=0).to_csv(os.path.join(directories["output"], "scaffolds_to_mags.tsv"), sep="\t", header=None) df_scaffolds["id_genome_cluster"].to_frame().dropna(how="any", axis=0).to_csv(os.path.join(directories["output"], "scaffolds_to_slcs.tsv"), sep="\t", header=None) diff --git a/src/scripts/merge_annotations.py b/src/scripts/merge_annotations.py index 2117437..50515fd 100755 --- a/src/scripts/merge_annotations.py +++ b/src/scripts/merge_annotations.py @@ -3,10 +3,10 @@ from collections import defaultdict, OrderedDict import pandas as pd import numpy as np -from soothsayer_utils import read_hmmer, pv, get_file_object, assert_acceptable_arguments, format_header +from soothsayer_utils import read_hmmer, pv, get_file_object, assert_acceptable_arguments, format_header, flatten __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2023.10.10" +__version__ = "2023.10.26" # disclaimer = format_header("DISCLAIMER: Lineage predictions are NOT robust and DO NOT USE CORE MARKERS. Please only use for exploratory suggestions.") @@ -66,6 +66,81 @@ def parse_uniref(stitle): # Print the four fields return [field1, field2, field3, field4] +def compile_identifiers(df, id_protein_cluster): + # Organism type + organism_types = set(df["organism_type"]) + if len(organism_types) == 1: + organism_types = list(organism_types)[0] + + # Genomes + genomes = set(df["id_genome"]) + + # Samples + samples = set(df["sample_of_origin"]) + + # Genome clusters + genome_clusters = set(df["id_genome_cluster"]) + if len(genome_clusters) == 1: + genome_clusters = list(genome_clusters)[0] + + data = OrderedDict([ + ("id_genome_cluster", genome_clusters), + ("organism_type", organism_types), + ("genomes", genomes), + ("samples_of_origin", samples), + ], + ) + data = pd.Series(data, name=id_protein_cluster) + data.index = data.index.map(lambda x: ("Identifiers", x)) + return data + +def compile_uniref(df, id_protein_cluster): + df = df.dropna(how="all", axis=0) + unique_identifiers = set(df["sseqid"].unique()) + data = OrderedDict([ + ("number_of_proteins", df.shape[0]), + ("number_of_unique_hits", len(unique_identifiers)), + ("ids", unique_identifiers), + ("names", set(df["product"].unique())), + ], + ) + data = pd.Series(data, name=id_protein_cluster) + data.index = data.index.map(lambda x: ("UniRef", x)) + return data + +def compile_nonuniref_diamond(df, id_protein_cluster, label): + df = df.dropna(how="all", axis=0) + unique_identifiers = set(df["sseqid"].unique()) + data = OrderedDict( + [ + ("number_of_proteins", df.shape[0]), + ("number_of_unique_hits", len(unique_identifiers)), + ("ids", unique_identifiers), + ("names", np.nan), + ], + ) + data = pd.Series(data, name=id_protein_cluster) + data.index = data.index.map(lambda x: (label, x)) + return data + +def compile_hmmsearch(df, id_protein_cluster, label): + df = df.dropna(how="all", axis=0).query("number_of_hits > 0") + unique_identifiers = flatten(df["ids"], into=set) + unique_names = flatten(df["names"], into=set) + + data = OrderedDict( + [ + ("number_of_proteins", df.shape[0]), + ("number_of_unique_hits", len(unique_identifiers)), + ("ids", unique_identifiers), + ("names", unique_names), + ], + ) + data = pd.Series(data, name=id_protein_cluster) + data.index = data.index.map(lambda x: (label, x)) + return data + + def main(args=None): # Path info script_directory = os.path.dirname(os.path.abspath( __file__ )) @@ -81,16 +156,22 @@ def main(args=None): # Pipeline parser_required = parser.add_argument_group('Required I/O arguments') parser_required.add_argument("-o","--output_directory", type=str, default="annotation_output", help = "Output directory for annotations [Default: annotation_output]") - parser_required.add_argument("-d","--diamond_uniref", type=str, required=True, help = "path/to/diamond_uniref.blast6 in blast6 format (No header, cannot be empty) with the following fields: \nqseqid sseqid stitle pident length mismatch qlen qstart qend slen sstart send evalue bitscore qcovhsp scovhsp") + parser_required.add_argument("-u","--diamond_uniref", type=str, required=True, help = "path/to/diamond_uniref.blast6 in blast6 format (No header, cannot be empty) with the following fields: \nqseqid sseqid stitle pident length mismatch qlen qstart qend slen sstart send evalue bitscore qcovhsp scovhsp") parser_required.add_argument("-m","--diamond_mibig", type=str, required=True, help = "path/to/diamond_mibig.blast6 in blast6 format (No header) with the following fields: \nqseqid sseqid stitle pident length mismatch qlen qstart qend slen sstart send evalue bitscore qcovhsp scovhsp") parser_required.add_argument("-b","--diamond_vfdb", type=str, required=True, help = "path/to/diamond_vfdb.blast6 in blast6 format (No header) with the following fields: \nqseqid sseqid stitle pident length mismatch qlen qstart qend slen sstart send evalue bitscore qcovhsp scovhsp") + parser_required.add_argument("-c","--diamond_cazy", type=str, required=True, help = "path/to/diamond_cazy.blast6 in blast6 format (No header) with the following fields: \nqseqid sseqid stitle pident length mismatch qlen qstart qend slen sstart send evalue bitscore qcovhsp scovhsp") parser_required.add_argument("-k","--kofam", type=str, required=True, help = "path/to/kofamscan.tblout hmmsearch results in tblout format") parser_required.add_argument("-p", "--hmmsearch_pfam", type=str, required=True, help = "path/to/hmmsearch.tblout hmmsearch results in tblout format") parser_required.add_argument("-n", "--hmmsearch_amr", type=str, required=True, help = "path/to/hmmsearch.tblout hmmsearch results in tblout format") parser_required.add_argument("-a", "--hmmsearch_antifam", type=str, required=True, help = "path/to/hmmsearch.tblout hmmsearch results in tblout format") parser_optional = parser.add_argument_group('Optional arguments') - parser_optional.add_argument("-i","--identifier_mapping", type=str, required=False, help = "path/to/identifier_mapping.tsv, Format: [id_protein][id_contig][id_genome], No header [Optional]") + parser_optional.add_argument("-i","--identifier_mapping", type=str, required=False, help = "Tab-seperated value table (identifier_mapping.proteins.tsv created by cluster.py) Requirements: 1) contain headers; 2) first column must be protein identifiers; and 3) contains these columns to the right in any order. Format: [id_protein][organism_type][id_genome][sample_of_origin][id_genome_cluster][id_protein_cluster] with headers [Optional]") + # parser_optional.add_argument("--genome_cluster_column_label", type=str, default="id_genome_cluster", help = "--genome_cluster_column_label must be in --identifier_mapping header [Default: id_genome_cluster]") + # parser_optional.add_argument("--protein_cluster_column_label", type=str, help = "--protein_cluster_column_label must be in --identifier_mapping header") + parser_optional.add_argument("-j", "--composite_name_joiner", type=str, required=False, default=";", help = "Composite label separator [Default: ; ]") + # parser_optional.add_argument("--no_merge_composite_labels", action="store_true", help = "Do not merge composite labels") + parser_optional.add_argument("-f","--fasta", type=str, required=False, help = "path/to/gene_models.faa|ffn of ORFs [Optional]") # parser_optional.add_argument("-t","--threshold", default=0.5, type=float, help = "taxopy fraction of classifications for consensus for weighted majority vote [Default: 0.5]") # parser_optional.add_argument("-V", "--veba_database", type=str, required=False, help = "path/to/VEBA_DATABASE") @@ -125,9 +206,14 @@ def main(args=None): # Read identifier mapping table if opts.identifier_mapping: print(" * Reading identifier mapping table: {}".format(opts.identifier_mapping), file=sys.stderr) - df_identifier_mapping = pd.read_csv(opts.identifier_mapping, header=None, sep="\t") - df_identifier_mapping.columns = ["id_protein", "id_contig", "id_genome"] - df_identifier_mapping = df_identifier_mapping.set_index("id_protein") + df_identifier_mapping = pd.read_csv(opts.identifier_mapping, sep="\t", index_col=0) + fields_required = set(["organism_type", "id_genome", "sample_of_origin", "id_genome_cluster", "id_protein_cluster"]) + columns = set(df_identifier_mapping.columns) + assert fields_required <= columns, "--identifier_mapping is missing the following columns: {}".format(fields_required - columns) + df_identifier_mapping.columns = df_identifier_mapping.columns.map(lambda x: ("Identifiers", x)) + + # df_identifier_mapping.columns = ["id_protein", "id_contig", "id_genome"] + # df_identifier_mapping = df_identifier_mapping.set_index("id_protein") proteins = proteins | set(df_identifier_mapping.index) print(" * Reading Diamond table [UniRef]: {}".format(opts.diamond_uniref), file=sys.stderr) @@ -177,6 +263,17 @@ def main(args=None): df_diamond_vfdb = df_diamond_vfdb.drop(["stitle"], axis=1) proteins = proteins | set(df_diamond_vfdb.index) + print(" * Reading Diamond table [CAZy]: {}".format(opts.diamond_cazy), file=sys.stderr) + columns = ["qseqid","sseqid", "stitle", "pident","length","mismatch","qlen","qstart","qend", "slen", "sstart","send", "evalue","bitscore","qcovhsp","scovhsp"] + try: + df_diamond_cazy = pd.read_csv(opts.diamond_cazy, sep="\t", index_col=None, header=None) + except pd.errors.EmptyDataError: + df_diamond_cazy = pd.DataFrame(columns=columns) + df_diamond_cazy.columns = columns + df_diamond_cazy = df_diamond_cazy.set_index("qseqid") + df_diamond_cazy = df_diamond_cazy.drop(["stitle"], axis=1) + proteins = proteins | set(df_diamond_cazy.index) + print(" * Reading HMMSearch table [Pfam]: {}".format(opts.hmmsearch_pfam), file=sys.stderr) df_hmmsearch_pfam = read_hmmer(opts.hmmsearch_pfam, program="hmmsearch", format="tblout", verbose=False) if not df_hmmsearch_pfam.empty: @@ -252,7 +349,7 @@ def main(args=None): df_hmms_pfam = df_hmms_pfam.applymap(lambda x: x if isinstance(x,list) else []) df_hmms_pfam.insert(loc=0, column="number_of_hits", value=df_hmms_pfam["ids"].map(len)) - df_hmms_pfam.index.name = "id_protein-representative" + df_hmms_pfam.index.name = "id_protein" # AMR if not df_hmmsearch_amr.empty: @@ -280,7 +377,7 @@ def main(args=None): else: df_hmms_amr = pd.DataFrame(index=proteins, columns=["hit", "ids", "names", "evalues", "scores"]) df_hmms_amr = df_hmms_amr.applymap(lambda x: x if isinstance(x,list) else []) - df_hmms_amr.index.name = "id_protein-representative" + df_hmms_amr.index.name = "id_protein" df_hmms_amr.insert(loc=0, column="number_of_hits", value=df_hmms_amr["ids"].map(len)) @@ -312,7 +409,7 @@ def main(args=None): df_hmms_antifam = pd.DataFrame(index=proteins, columns=["hit", "ids", "names", "evalues", "scores"]) df_hmms_antifam["hit"] = df_hmms_antifam["hit"].fillna(False) df_hmms_antifam = df_hmms_antifam.applymap(lambda x: x if isinstance(x,list) else []) - df_hmms_antifam.index.name = "id_protein-representative" + df_hmms_antifam.index.name = "id_protein" df_hmms_antifam.insert(loc=0, column="number_of_hits", value=df_hmms_antifam["ids"].map(len)) # KOFAMSCAN @@ -347,12 +444,12 @@ def main(args=None): # Output table dataframes = list() - for (name, df) in zip([ "UniRef", "MIBiG", "VFDB", "Pfam", "NCBIfam-AMR", "AntiFam", "KOFAM"],[df_diamond_uniref, df_diamond_mibig, df_diamond_vfdb, df_hmms_pfam, df_hmms_amr, df_hmms_antifam, df_hmms_kofam]): + for (name, df) in zip([ "UniRef", "MIBiG", "VFDB", "CAZy", "Pfam", "NCBIfam-AMR", "AntiFam", "KOFAM"],[df_diamond_uniref, df_diamond_mibig, df_diamond_vfdb, df_diamond_cazy, df_hmms_pfam, df_hmms_amr, df_hmms_antifam, df_hmms_kofam]): df = df.copy() df.columns = df.columns.map(lambda x: (name,x)) dataframes.append(df) df_annotations = pd.concat(dataframes, axis=1) - df_annotations.index.name = "id_protein-representative" + df_annotations.index.name = "id_protein" # Composite label protein_to_labels = dict() @@ -360,28 +457,93 @@ def main(args=None): labels = list() name = row["UniRef"]["product"] - labels.append(name) + if name not in labels: + labels.append(name) kofam_names = row["KOFAM"]["names"] for name in kofam_names: - labels.append(name) - + if name not in labels: + labels.append(name) + pfam_names = row["Pfam"]["names"] for name in pfam_names: - labels.append(name) + if name not in labels: + labels.append(name) - - protein_to_labels[id_protein] = "|".join(filter(lambda x: isinstance(x,str), labels)) + composite_name = sorted(filter(lambda x: isinstance(x,str), labels)) + protein_to_labels[id_protein] = opts.composite_name_joiner.join(composite_name) protein_to_labels = pd.Series(protein_to_labels) + + # if not opts.no_merge_composite_labels: + # protein_to_labels = protein_to_labels.map(lambda x: opts.composite_name_joiner.join(x)) + df_annotations.insert(loc=0, column=("Consensus", "composite_name"), value=protein_to_labels) if opts.identifier_mapping: - df = df_identifier_mapping.copy() - df.columns = df.columns.map(lambda x: ("Identifiers", x)) - df_annotations = pd.concat([df, df_annotations], axis=1) + # df = df_identifier_mapping.copy() + # df.columns = df.columns.map(lambda x: ("Identifiers", x)) + df_annotations = pd.concat([df_identifier_mapping, df_annotations], axis=1) - df_annotations.to_csv(os.path.join(opts.output_directory, "annotations.tsv.gz"), sep="\t") + df_annotations.to_csv(os.path.join(opts.output_directory, "annotations.proteins.tsv.gz"), sep="\t") + if opts.identifier_mapping: + # Protein clusters + protein_to_proteincluster = df_annotations[("Identifiers", "id_protein_cluster")] + protein_cluster_annotations = list() + for id_protein_cluster, df in pv(df_annotations.groupby(protein_to_proteincluster), description="Compiling consensus annotations for protein clusters", total=protein_to_proteincluster.nunique(), unit=" Protein Clusters"): + # Identifiers + data_identifiers = compile_identifiers(df["Identifiers"], id_protein_cluster) + + # UniRef + data_uniref = compile_uniref(df["UniRef"], id_protein_cluster) + + # MIBiG + data_mibig = compile_nonuniref_diamond(df["MIBiG"], id_protein_cluster, "MIBiG") + + # VFDB + data_vfdb = compile_nonuniref_diamond(df["VFDB"], id_protein_cluster, "VFDB") + + # CAZy + data_cazy = compile_nonuniref_diamond(df["CAZy"], id_protein_cluster, "CAZy") + + # Pfam + data_pfam = compile_hmmsearch(df["Pfam"], id_protein_cluster, "Pfam") + + # NCBIfam-AMR + data_amr = compile_hmmsearch(df["NCBIfam-AMR"], id_protein_cluster, "NCBIfam-AMR") + + # KOFAM + data_kofam = compile_hmmsearch(df["KOFAM"], id_protein_cluster, "KOFAM") + + # AntiFam + data_antifam = compile_hmmsearch(df["AntiFam"], id_protein_cluster, "AntiFam") + + # Composite name + composite_name = list() + composite_name += list(data_uniref[("UniRef","names")]) + composite_name += list(data_kofam[("KOFAM", "names")]) + composite_name += list(data_pfam[("Pfam","names")]) + composite_name = opts.composite_name_joiner.join(composite_name) + data_consensus = pd.Series(composite_name, index=[("Consensus", "composite_name")]) + + # Concatenate + data_concatenated = pd.concat([ + data_identifiers, + data_consensus, + data_uniref, + data_mibig, + data_vfdb, + data_cazy, + data_pfam, + data_amr, + data_kofam, + data_antifam, + ]) + data_concatenated.name = id_protein_cluster + protein_cluster_annotations.append(data_concatenated) + + df_annotations_proteinclusters = pd.DataFrame(protein_cluster_annotations) + df_annotations_proteinclusters.to_csv(os.path.join(opts.output_directory, "annotations.protein_clusters.tsv.gz"), sep="\t") diff --git a/src/scripts/merge_generalized_mapping.py b/src/scripts/merge_generalized_mapping.py index dcaff75..21662b0 100755 --- a/src/scripts/merge_generalized_mapping.py +++ b/src/scripts/merge_generalized_mapping.py @@ -6,7 +6,7 @@ from tqdm import tqdm __program__ = os.path.split(sys.argv[0])[-1] -__version__ = "2022.5.23" +__version__ = "2023.10.26" def main(args=None): # Path info @@ -31,6 +31,8 @@ def main(args=None): parser.add_argument("-a", "--allow_missing_values", action="store_true", help = "Allow missing values instead of filling with zeros") parser.add_argument("-e", "--remove_empty_features", action="store_true", help = "Remove empty features") parser.add_argument("--pickle", type=str, help = "path/to/pandas.pkl output") + parser.add_argument("--comment", type=str, default="#", help = "Comments prefixed with this character [Default: #]") + # Options opts = parser.parse_args() @@ -44,7 +46,7 @@ def main(args=None): output = dict() for fp in tqdm(opts.files, "Reading processed featureCounts outputs"): id_sample = fp.split("/")[opts.sample_index] - counts = pd.read_csv(fp, sep="\t", index_col=0, header=None).iloc[:,-1] + counts = pd.read_csv(fp, sep="\t", index_col=0, header=None, comment=opts.comment).iloc[:,-1] if opts.remove_empty_features: counts = counts[counts > 0] output[id_sample] = counts diff --git a/src/scripts/module_completion_ratios.py b/src/scripts/module_completion_ratios.py new file mode 100755 index 0000000..a209a57 --- /dev/null +++ b/src/scripts/module_completion_ratios.py @@ -0,0 +1,543 @@ +#!/usr/bin/env python + +""" +# Original Source: +# https://github.com/cruizperez/MicrobeAnnotator/blob/master/microbeannotator/pipeline/ko_mapper.py +# Forked on 2023.10.18 + +If you use this software, please cite the original publication: + Ruiz-Perez, C.A., Conrad, R.E. & Konstantinidis, K.T. + MicrobeAnnotator: a user-friendly, comprehensive functional annotation pipeline for microbial genomes. + BMC Bioinformatics 22, 11 (2021). https://doi.org/10.1186/s12859-020-03940-5 + +######################################################################## +# Author: Carlos A. Ruiz Perez +# Email: cruizperez3@gatech.edu +# Intitution: Georgia Institute of Technology +# Version: 1.0.0 +# Date: Nov 13, 2020 + +# Description: Maps protein KO information with their respective modules +# and calculates the completeness percentage of each module present. +######################################################################## +""" + +################################################################################ +"""---0.0 Import Modules---""" + +import sys, os, re, pickle, argparse, gzip +from collections import OrderedDict, defaultdict +import pandas as pd + +__version__ = "2023.10.23" +__program__ = os.path.split(sys.argv[0])[-1] + +################################################################################ +def load_pickle(path): + with open(path,"rb") as f: + obj = pickle.load(f) + return obj + +def ko_match(string): + """ + Looks if string has the form K[0-9]{5} + + Arguments: + string {string} -- String to test + + Returns: + [bool] -- String has form or not + """ + if re.search(r'^K[0-9]{5}$', string) is not None: + return 1 + else: + return 0 + +def split_compound(string, comp_type): + """[summary] + + Arguments: + string {[type]} -- [description] + comp_type {[type]} -- [description] + + Returns: + [type] -- [description] + """ + if comp_type == 'compound': + return re.split('[-+]', string) + elif comp_type == 'and_comp': + return string.split('_') + elif comp_type == 'or_comp': + return string.split(',') + +def process_compounds(string, comp_type, ko_list): + string = split_compound(string, comp_type) + proteins_required = len(string) + proteins_present = 0 + for option in string: + if option == '': + proteins_required -= 1 + elif '+' in option or '-' in option: + compound = split_compound(option, 'compound') + proteins_in_compound = len(compound) + present_in_compound = 0 + for sub_option in compound: + if ko_match(sub_option) > 0 and sub_option in ko_list: + present_in_compound += 1 + proteins_present += present_in_compound/proteins_in_compound + else: + if ko_match(option) > 0 and option in ko_list: + proteins_present += 1 + return proteins_present, proteins_required + + + +def get_kegg_module_information(path): + + # Get genome and module ids + module_information = OrderedDict() + + # Get module correspondence + with open(path, "r") as f: + for line in f: + line = line.strip() + id_module, name, group, color = line.split("\t") + module_information[id_module] = {"module_name":name, "pathway_group":group} + + return pd.DataFrame(module_information).T + +def read_ko_list(path): + ko_list = [] + + if path.endswith(".gz"): + f = gzip.open(path, "rt") + else: + f = open(path, "r") + for line in f: + ko_list.append(line.strip()) + f.close() + return set(ko_list) + +def regular_module_mapper(ko_list, module_dictionary):#, ko_list_file): + # ko_list = read_ko_list(ko_list_file) + regular_module_completenes = [] + for module, final_steps in module_dictionary.items(): + complete_steps = 0 + total_module_steps = len(final_steps) + for proteins in final_steps.values(): + score_for_step = 0 + steps_per_option = 100 + match = False + for option in proteins: + match = False + # Search for single gene option + if ko_match(option) > 0: + if option in ko_list: + score_for_step = 1 + match = True + if match == True: + steps_per_option = 1 + elif 1 < steps_per_option: + steps_per_option = 1 + elif '%' in option: + option = option.replace(')', '') + option = option.split('-%') + mandatory = split_compound(option[0], 'compound') + proteins_required = len(mandatory) + 1 + proteins_present = 0 + for prot in mandatory: + if ko_match(prot) > 0 and prot in ko_list: + proteins_present += 1 + for prot in option[1].split(','): + if ko_match(prot) > 0 and prot in ko_list: + proteins_present += 1 + break + if proteins_present/proteins_required > score_for_step: + score_for_step = proteins_present/proteins_required + match = True + if match == True: + steps_per_option = 1 + elif 1 < steps_per_option: + steps_per_option = 1 + elif '_' in option and ',' in option: + option = sorted(split_compound(option, 'and_comp'), key=len) + highest_score = 0 + for element in option: + if ko_match(element) > 0 and element in ko_list: + highest_score += 1 + elif ',' in element: + element = sorted(element.split(","), key=len) + for sub_element in element: + if ko_match(sub_element) > 0 and sub_element in ko_list: + highest_score += 1 + break + elif '+' in sub_element: + proteins_present, proteins_required = process_compounds(sub_element, + 'compound', ko_list) + highest_score += proteins_present/proteins_required + if highest_score > score_for_step: + score_for_step = highest_score + match = True + if match == True: + steps_per_option = len(option) + elif len(option) < steps_per_option: + steps_per_option = len(option) + elif len(option.split(",")) > 1: + match = False + option = sorted(option.split(","), key=len) + highest_score = 0 + steps_to_add = 0 + for sub_option in option: + if ko_match(sub_option) > 0 and sub_option in ko_list: + if 1 > highest_score: + highest_score = 1 + steps_to_add = 1 + elif '+' in sub_option or '-' in sub_option: + proteins_present, proteins_required = process_compounds(sub_option, 'compound', ko_list) + if proteins_present/proteins_required > highest_score: + highest_score = proteins_present/proteins_required + steps_to_add = 1 + if highest_score > score_for_step: + score_for_step = highest_score + match = True + if match == True: + steps_per_option = steps_to_add + elif steps_to_add< steps_per_option: + steps_per_option = steps_to_add + elif '_' in option: + match = False + proteins_present, proteins_required = process_compounds(option, 'and_comp', ko_list) + if proteins_present/proteins_required > score_for_step: + score_for_step = proteins_present + match = True + if match == True: + steps_per_option = proteins_required + elif proteins_required < steps_per_option: + steps_per_option = proteins_required + elif "+" in option or "-" in option: + match = False + highest_score = 0 + proteins_present, proteins_required = process_compounds(option, 'compound', ko_list) + if proteins_present/proteins_required > score_for_step: + score_for_step = proteins_present/proteins_required + match = True + if match == True: + steps_per_option = 1 + elif proteins_required < steps_per_option: + steps_per_option = 1 + else: + print("Unrecognized module {}. Check your database.".format(option), file=sys.stderr) + complete_steps += score_for_step + if steps_per_option > 50: + steps_per_option = 1 + if steps_per_option > 1: + total_module_steps += steps_per_option - 1 + regular_module_completenes.append((module, (complete_steps/total_module_steps))) + return regular_module_completenes + +def bifurcating_module_mapper(ko_list, module_dictionary):#, ko_list_file): + # ko_list = read_ko_list(ko_list_file) + bifurcating_module_completenes = [] + for module, versions in module_dictionary.items(): + module_highest = 0 + for version, total_steps in versions.items(): + completed_steps = 0 + total_version_steps = len(total_steps) + for proteins in total_steps.values(): + score_for_step = 0 + steps_per_option = 100 + match = False + if isinstance(proteins, (list)): + protein = sorted(proteins, key=len) + for option in protein: + match = False + if ko_match(option) > 0: + if option in ko_list: + score_for_step = 1 + match = True + if match == True: + steps_per_option = 1 + elif 1 < steps_per_option: + steps_per_option = 1 + elif '_' in option and ',' in option: + option = sorted(split_compound(option, 'and_comp'), key=len) + highest_score = 0 + for element in option: + if ko_match(element) > 0 and element in ko_list: + highest_score += 1 + elif ',' in element: + element = sorted(element.split(","), key=len) + score_sub_option = 0 + for sub_element in element: + if ko_match(sub_element) > 0 and sub_element in ko_list: + highest_score += 1 + break + elif '+' in sub_element: + proteins_present, proteins_required = process_compounds(sub_element, + 'compound', ko_list) + highest_score += proteins_present/proteins_required + if highest_score > score_for_step: + score_for_step = highest_score + match = True + if match == True: + steps_per_option = len(option) + elif '_' in option: + match = False + proteins_present, proteins_required = process_compounds(option, 'and_comp', ko_list) + if proteins_present/proteins_required > score_for_step: + score_for_step = proteins_present + match = True + if match == True: + steps_per_option = proteins_required + elif proteins_required < steps_per_option: + steps_per_option = proteins_required + elif '+' in option or '-' in option: + match = False + highest_score = 0 + proteins_present, proteins_required = process_compounds(option, 'compound', ko_list) + if proteins_present/proteins_required > score_for_step: + score_for_step = proteins_present/proteins_required + match = True + if match == True: + steps_per_option = 1 + elif proteins_required < steps_per_option: + steps_per_option = 1 + elif ko_match(proteins) > 0: + match = False + if proteins in ko_list: + score_for_step = 1 + match = True + if match == True: + steps_per_option = 1 + elif 1 < steps_per_option: + steps_per_option = 1 + elif ',' in proteins: + options = split_compound(proteins, 'or_comp') + for option in options: + if ko_match(option) > 0 and option in ko_list: + score_for_step = 1 + match = True + if match == True: + steps_per_option = 1 + elif 1 < steps_per_option: + steps_per_option = 1 + elif '+' in proteins or '-' in proteins: + match = False + highest_score = 0 + proteins_present, proteins_required = process_compounds(proteins, 'compound', ko_list) + if proteins_present/proteins_required > score_for_step: + score_for_step = proteins_present/proteins_required + match = True + if match == True: + steps_per_option = 1 + elif proteins_required < steps_per_option: + steps_per_option = 1 + else: + print("Unreccognized module {}. Check your database.".format(proteins), file=sys.stderr) + completed_steps += score_for_step + if steps_per_option > 50: + steps_per_option = 1 + if steps_per_option > 1: + total_version_steps += steps_per_option - 1 + if completed_steps/total_version_steps > module_highest: + module_highest = completed_steps/total_version_steps + bifurcating_module_completenes.append((module, module_highest)) + return bifurcating_module_completenes + +def structural_module_mapper(ko_list, module_dictionary):#, ko_list_file): + # ko_list = read_ko_list(ko_list_file) + structural_module_completeness = [] + for module, components in module_dictionary.items(): + score_for_components = 0 + module_proteins_present = 0 + module_proteins_required = 0 + for proteins in components: + if isinstance(proteins, (list)): + highest_score = 0 + proteins_to_add = 0 + steps_to_add = 100 + for option in proteins: + if '_' in option and ',' in option: + option = sorted(split_compound(option, 'and_comp'), key=len) + proteins_present_option = 0 + proteins_required_option = 0 + for element in option: + if ko_match(element) > 0: + if element in ko_list: + proteins_present_option += 1 + proteins_required_option += 1 + else: + proteins_required_option += 1 + elif ',' in element: + element = sorted(element.split(","), key=len) + score_sub_element = 0 + proteins_present_sub_element = 0 + proteins_required_sub_element = 0 + for sub_element in element: + if ko_match(sub_element) > 0: + if sub_element in ko_list: + score_sub_element = 1 + proteins_present_sub_element += 1 + proteins_required_sub_element += 1 + elif 1 < proteins_required_sub_element and score_sub_element == 0: + proteins_required_sub_element = 1 + elif '+' in sub_element or '-' in sub_element: + proteins_present, proteins_required = process_compounds(sub_element, + 'compound', ko_list) + if proteins_present/proteins_required > score_sub_element: + score_sub_element = proteins_present/proteins_required + proteins_present_sub_element = proteins_present + proteins_required_sub_element = proteins_required + elif proteins_required < proteins_required_sub_element and score_sub_element == 0: + proteins_required_sub_element = proteins_required + proteins_present_option += proteins_present_sub_element + proteins_required_option += proteins_required_sub_element + if proteins_present_option/proteins_required_option > highest_score: + highest_score = proteins_present_option/proteins_required_option + proteins_to_add = proteins_present_option + steps_to_add = proteins_required_option + elif proteins_required_option < steps_to_add and highest_score == 0: + steps_to_add = proteins_required_option + elif '+' in option or '-' in option: + proteins_present, proteins_required = process_compounds(option, 'compound', ko_list) + if proteins_present/proteins_required > highest_score: + highest_score = proteins_present/proteins_required + steps_to_add = proteins_required + proteins_to_add = proteins_present + elif proteins_required < steps_to_add and highest_score == 0: + steps_to_add = proteins_required + module_proteins_present += proteins_to_add + module_proteins_required += steps_to_add + elif ko_match(proteins) > 0: + if proteins in ko_list: + module_proteins_present += 1 + module_proteins_required += 1 + else: + module_proteins_required += 1 + elif ',' in proteins: + proteins = sorted(proteins.split(","), key=len) + score = 0 + proteins_present_option = 0 + proteins_required_option = 100 + for element in proteins: + if ko_match(element) > 0: + if element in ko_list: + proteins_required_option = 1 + proteins_present_option = 1 + break + elif 1 < proteins_required_option and score == 0: + proteins_required_option = 1 + elif '+' in element or '-' in element: + proteins_present, proteins_required = process_compounds(element, 'compound', ko_list) + if proteins_present/proteins_required > score: + score = proteins_present/proteins_required + proteins_present_option = proteins_present + proteins_required_option = proteins_required + elif proteins_required < proteins_required_option and score == 0: + proteins_required_option = proteins_required + module_proteins_present += proteins_present_option + module_proteins_required += proteins_required_option + elif '+' in proteins or '-' in proteins: + proteins_present, proteins_required = process_compounds(proteins, 'compound', ko_list) + module_proteins_present += proteins_present + module_proteins_required += proteins_required + else: + print("Unreccognized module {}. Check your database.".format(proteins), file=sys.stderr) + score_for_components = module_proteins_present/module_proteins_required + structural_module_completeness.append((module, score_for_components)) + return structural_module_completeness + + + +################################################################################ +"""---2.0 Main Function---""" + +def main(): + # Setup parser for arguments. + + # Path info + script_directory = os.path.dirname(os.path.abspath( __file__ )) + script_filename = __program__ + # Path info + description = """ + Running: {} v{} via Python v{} | {}""".format(__program__, __version__, sys.version.split(" ")[0], sys.executable) + usage = "{} -i ko_table.tsv [-k -d -x ko_list".format(__program__) + + epilog = "Josh L. Espinoza's fork from MicrobeAnnotator. Please cite the following: https://doi.org/10.1186/s12859-020-03940-5" + + # Parser + parser = argparse.ArgumentParser(description=description, usage=usage, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + + + parser.add_argument('-i', '--ko_table', help='path/to/ko_table.tsv following [id_genome][id_ko], No header. Cannot be used with --ko_lists') + parser.add_argument('-k', '--ko_lists', nargs='+', help='Space-delimited list of filepaths where each file represents a genome and each line in the file is a KO id. Cannot be used with --ko_table') + parser.add_argument('-o', '--output', default="stdout", help='Output file for module completion ratios [Default: stdout]') + parser.add_argument("-d", '--database_directory', required=True, help='path/to/database_directory with pickle files') + parser.add_argument("-x", '--extension', default = "ko_list", help='File extension for --ko_lists [Default: ko_list (e.g., MAG_1.ko_list)]') + parser.add_argument("-t", '--transpose', action='store_true', help='Transpose output format. Default rows=MCR, columns=Genomes.') + parser.add_argument("-m", '--no_missing_modules', action='store_true', help='Do not include modules that are missing from all genomes') + parser.add_argument('--no_module_names', action='store_true', help='Do not include module names in output') + parser.add_argument('--no_pathway_groups', action='store_true', help='Do not include pathway group names in output') + + + opts = parser.parse_args() + + assert bool(opts.ko_table) != bool(opts.ko_lists), "Must provide KOs as either a tsv table (--ko_table) or a list of KO ids in different files (--ko_lists)" + if opts.ko_table == "stdin": + opts.ko_table = sys.stdin + + if opts.output == "stdout": + opts.output = sys.stdout + + # ---------------------------- + # Import all modules from dictionaries + regular_modules = load_pickle(os.path.join(opts.database_directory, "KEGG_Regular_Module_Information.pkl")) + bifurcating_modules = load_pickle(os.path.join(opts.database_directory, "KEGG_Bifurcating_Module_Information.pkl")) + structural_modules = load_pickle(os.path.join(opts.database_directory, "KEGG_Structural_Module_Information.pkl")) + + # Get KEGG ortholog lists + if opts.ko_table: + genome_to_kos = defaultdict(set) + df = pd.read_csv(opts.ko_table, sep="\t", index_col=None, header=None).iloc[:,:2] + for _, (id_genome, id_ko) in df.iterrows(): + id_genome = str(id_genome) + genome_to_kos[id_genome].add(id_ko) + else: + genome_to_kos = OrderedDict() + for path in opts.ko_lists: + id_genome = path[:-(len(opts.extension) + 1)] + id_genome = str(id_genome) + genome_to_kos[id_genome] = read_ko_list(path) + + # Get module information + df_module_information = get_kegg_module_information( + path=os.path.join(opts.database_directory, "KEGG_Module_Information.txt"), + ) + + # Calculate module completion ratios + module_completion_ratios = OrderedDict() + for id_genome, ko_list in genome_to_kos.items(): + regular_completeness = regular_module_mapper(ko_list, regular_modules) + bifurcating_completeness = bifurcating_module_mapper(ko_list, bifurcating_modules) + structural_completeness = structural_module_mapper(ko_list, structural_modules) + final_completeness = regular_completeness + bifurcating_completeness + structural_completeness + module_completion_ratios[id_genome] = pd.Series(dict(final_completeness)) + df_mcr = pd.DataFrame(module_completion_ratios) + + # Format output + df_output = pd.concat([df_module_information, df_mcr], axis=1) + if opts.no_missing_modules: + df_output = df_output.loc[df_mcr.sum(axis=1)[lambda x: x > 0].index] + df_output.index.name = "id_kegg-ortholog" + + if opts.no_module_names: + df_output = df_output.drop(["module_name"], axis=1) + if opts.no_pathway_groups: + df_output = df_output.drop(["pathway_group"], axis=1) + else: + df_output = df_output.reset_index(drop=False).set_index(["pathway_group", "id_kegg-ortholog"]) + + df_output.to_csv(opts.output, sep="\t") + +if __name__ == "__main__": + main() diff --git a/src/scripts/prokaryotic_gene_modeling_wrapper.py b/src/scripts/prokaryotic_gene_modeling_wrapper.py old mode 100644 new mode 100755 diff --git a/walkthroughs/README.md b/walkthroughs/README.md index ec7bb46..3586d83 100644 --- a/walkthroughs/README.md +++ b/walkthroughs/README.md @@ -41,6 +41,7 @@ sbatch -J ${N} -N 1 -c ${N_JOBS} --ntasks-per-node=1 -o logs/${N}.o -e logs/${N} * **[Converting counts tables](converting_counts_tables.md)** - Convert your counts table (with or without metadata) to [anndata](https://anndata.readthedocs.io/en/latest/index.html) or [biom](https://biom-format.org/) format. Also supports [Pandas pickle](https://pandas.pydata.org/docs/reference/api/pandas.read_pickle.html) format. * **[Adapting commands for Docker](adapting_commands_for_docker.md)** - Explains how to download and use Docker for running VEBA. * **[Adapting commands for AWS](adapting_commands_for_aws.md)** - Explains how to download and use Docker for running VEBA specifically on AWS. +* **[Metabolic Profiling *de novo* genomes](metabolic_profiling_de-novo_genomes.md)** - Explains how to build and align reads to custom `HUMAnN` databases from *de novo* genomes and annotations. ___________________________________________ diff --git a/walkthroughs/bioprospecting_for_biosynthetic_gene_clusters.md b/walkthroughs/bioprospecting_for_biosynthetic_gene_clusters.md index 6697d20..23be35e 100644 --- a/walkthroughs/bioprospecting_for_biosynthetic_gene_clusters.md +++ b/walkthroughs/bioprospecting_for_biosynthetic_gene_clusters.md @@ -62,11 +62,18 @@ CMD="source activate VEBA-biosynthetic_env && biosynthetic.py -i ${GENOMES} -o $ The following output files will produced: -* bgc.features.tsv.gz - `antiSMASH` genbank files for BGCs compiled into a table format which includes all information included in the genbank files in addition to identifiers for BGC, component, genome, contig, region, gene, and BGC-type. BGC identifiers are formatted as the following: `[id_genome]|[id_contig]|[id_region]` and component identifiers follow the format: `[id_genome]|[id_contig]|[id_region]_[position_of_gene_in_BGC]|[start_on_contig]-[end_on_contig]([strand])`. One-to-one mapping for component id and gene id called via `Prodigal` and augmented with `VEBA`. Rows are with respect to gene/component. -* bgc.synopsis.tsv.gz - Synopsis for BGCs with identifiers, bgc_type, whether or not cluster is on contig edge, number of genes, number of hits to MIBiG, and novety score. -* homology.mibig.tsv.gz - Diamond alignment results (with header) using `MIBiG` as database. -* features/[id_genome].bgc\_features.faa.gz - Protein sequences for BGC components with the header following: `[id_gene] [id_component]`. +* identifier\_mapping.bgcs.tsv.gz - Identifier mapping and synopsis for BGCs with identifiers, bgc_type, whether or not cluster is on contig edge, number of genes, number of hits to MIBiG, and novety score. +* identifier\_mapping.components.tsv.gz - Identifier mapping and synopsis for `antiSMASH` genbank files for BGCs compiled into a table format which includes all information included in the genbank files in addition to identifiers for BGC, component, genome, contig, region, gene, and BGC-type. BGC identifiers are formatted as the following: `[id_genome]|[id_contig]|[id_region]` and component identifiers follow the format: `[id_genome]|[id_contig]|[id_region]_[position_of_gene_in_BGC]|[start_on_contig]-[end_on_contig]([strand])`. One-to-one mapping for component id and gene id called via `Prodigal` and augmented with `VEBA`. Rows are with respect to gene/component. +* homology.tsv.gz - Diamond alignment results to `MIBiG` and `VFDB` as database. +* krona.html - Krona plot of BGC protocluster types +* genbanks/*.gbk - BGC genbank files from `antiSMASH` +* fasta/[id\_genome].faa - BGCs in protein space. Fasta header following: `[id_gene] [id_component]`. +* fasta/[id\_genome].fasta - BGCs in nucleotide space. Fasta header following: `[id_bgc] len={},gc={},n_genes={},edge={}`. +* prevalence_tables/bgcs.tsv.gz - Prevalence tables (row=genome, columns=BGC nucleotide cluster) +* prevalence_tables/components.tsv.gz - Prevalence tables (row=BGC, columns=BGC protein cluster) +* bgc_clusters.tsv - `MMSEQS2` clustering in nucleotide space +* component_clusters.tsv - `MMSEQS2` clustering in protein space #### Next steps: -Synthesize products, preserve the ecosystem, and save humanity. \ No newline at end of file +Cluster the prevalence tables, synthesize products, preserve the ecosystem, and save humanity. \ No newline at end of file diff --git a/walkthroughs/end-to-end_metagenomics.md b/walkthroughs/end-to-end_metagenomics.md index 7fc5f41..baa22b8 100644 --- a/walkthroughs/end-to-end_metagenomics.md +++ b/walkthroughs/end-to-end_metagenomics.md @@ -419,11 +419,11 @@ CMD="source activate VEBA-cluster_env && cluster.py -i veba_output/misc/genomes_ `veba_output/cluster/local` -* global/feature\_compression\_ratios.tsv - Feature compression ratios for each domain +* global/feature\_compression\_ratios.tsv.gz - Feature compression ratios for each domain * global/genome\_clusters.tsv - Machine-readable table for genome clusters `[id_genome_cluster, number_of_components, number_of_samples_of_origin, components, samples_of_origin]` -* global/identifier\_mapping.genomes.tsv - Identifier mapping for genomes `[id_genome, organism_type, sample_of_origin, id_genome_cluster, number_of_proteins, number_of_singleton_protein_clusters, ratio_of_protein_cluster_are_singletons]` -* global/identifier\_mapping.proteins.tsv - Identifier mapping for proteins `[id_protein, organism_type, id_genome, sample_of_origin, id_genome_cluster, id_protein_cluster]` -* global/identifier\_mapping.scaffolds.tsv - Identifier mapping for contigs `[id_scaffold, organism_type, id_genome, sample_of_origin, id_genome_cluster]` +* global/identifier\_mapping.genomes.tsv.gz - Identifier mapping for genomes `[id_genome, organism_type, sample_of_origin, id_genome_cluster, number_of_proteins, number_of_singleton_protein_clusters, ratio_of_protein_cluster_are_singletons]` +* global/identifier\_mapping.proteins.tsv.gz - Identifier mapping for proteins `[id_protein, organism_type, id_genome, sample_of_origin, id_genome_cluster, id_protein_cluster]` +* global/identifier\_mapping.scaffolds.tsv.gz - Identifier mapping for contigs `[id_scaffold, organism_type, id_genome, sample_of_origin, id_genome_cluster]` * global/mags\_to\_slcs.tsv * global/protein\_clusters.tsv - Machine-readable table for protein clusters `[id_protein_cluster, number_of_components, number_of_samples_of_origin, components, samples_of_origin]` * global/proteins\_to\_orthogroups.tsv - Identifier mapping between proteins and protein clusters `[id_protein, id_protein-cluster]` @@ -552,25 +552,20 @@ The following output files will produced: * taxonomy_classifications.clusters.tsv - Taxonomy with respect to genome clusters #### 14. Annotate proteins -Now that allf of the MAGs are recovered and classified, let's annotate the proteins using best-hit against UniRef, Pfam, and KOFAM. +Now that all of the MAGs are recovered and classified, let's annotate the proteins using best-hit against UniRef,MiBIG,VFDB,CAZy Pfam, AntiFam, AMRFinder, and KOFAM. HMMSearch will fail with sequences ≥ 100k so we need to remove any that are that long (there probably aren't but just to be safe). **Conda Environment:** `conda activate VEBA-annotate_env` ``` # Let's merge all of the proteins. -cat veba_output/binning/*/*/output/genomes/*.faa > veba_output/misc/all_genomes.proteins.faa - -# Let's merge all of the identifier mappings. -# Note, this is optional but it's helpful for diving further into annotations -# especially for viruses -cat veba_output/binning/*/*/output/genomes/identifier_mapping.tsv > veba_output/misc/all_genomes.identifier_mapping.tsv +cat veba_output/binning/*/*/output/genomes/*.faa | seqkit seq -M 99999 > veba_output/misc/all_genomes.all_proteins.lt100k.faa ``` -**Simple usage by running all at once** +**Recommended usage by running all at once** -For sake of simplicity, let's just annotate everything at once (see next section to speed this up): +For sake of simplicity, let's just annotate everything at once (see next section to split this up). Let's also use UniRef50 since this is an environmental dataset. By annotating all of the proteins and providing clusters, we will be able to calculate KEGG module completion ratios both at the genome and pangenome (i.e., genome cluster) levels. ``` @@ -583,15 +578,40 @@ N="annotate" rm -f logs/${N}.* # Input filepaths -PROTEINS=veba_output/misc/all_genomes.proteins.faa -IDENTIFIER_MAPPING=veba_output/misc/all_genomes.identifier_mapping.tsv -CMD="source activate VEBA-annotate_env && annotate.py -a ${PROTEINS} -i ${IDENTIFIER_MAPPING} -o veba_output/annotation -p ${N_JOBS}" +PROTEINS=veba_output/misc/all_genomes.all_proteins.lt100k.faa +IDENTIFIER_MAPPING=veba_output/cluster/output/global/identifier_mapping.proteins.tsv.gz + +# Command +CMD="source activate VEBA-annotate_env && annotate.py -a ${PROTEINS} -i ${IDENTIFIER_MAPPING} -o veba_output/annotation -p ${N_JOBS} -u uniref50" # Either run this command or use SunGridEnginge/SLURM ``` -**Advanced usage by splitting up fasta into multiple files** +The following output files will produced: + +* annotations.proteins.tsv.gz - Concatenated annotations from Diamond (NR), Diamond (MiBIG), Diamond (VFDB), Diamond (CAZy), HMMSearch (Pfam), HMMSearch (NCBIfam-AMRFinder), HMMSearch (AntiFam), and KOFAMSCAN (KOFAM). +* annotations.protein_clusters.tsv.gz - Consensus annotations for protein clusters. +* If `-i/--identifier_mapping` is provided, the the following is also included: + * Identifers in `annotations.proteins.tsv.gz` and `annotations.protein_clusters.tsv.gz` + * kos.genomes.tsv and kos.genome_clusters.tsv which contain replicated `[id_genome][id_ko]` + * module\_completion\_ratios.genomes.tsv and module\_compleition\_ratios.genome_clusters.tsv which contain KEGG module completion ratios (MCR) for each genome and genome cluster. + + +**(Advanced) Annotating only representative sequences of each cluster** + +If you are restricted by resources or time you may want to do just annotate the representatives of each protein cluster. You won't be able to use the `-i/--identifier_mapping` argument. + +``` +# Input filepaths +PROTEINS=veba_output/cluster/output/global/representative_sequences.faa + +# Command +CMD="source activate VEBA-annotate_env && annotate.py -a ${PROTEINS} -o veba_output/annotation -p ${N_JOBS} -u uniref50" + +``` + +**(Advanced) Splitting up fasta into multiple files** If you are restricted by resources or time you may want to do this in batches. You can split up the proteins into separate batches with `SeqKit`. @@ -602,18 +622,13 @@ This would make 100 files: stdin.part_001.fasta - stdin.part_100.fasta N_PARTITIONS=100 PARTITION_DIRECTORY=veba_output/misc/partitions/ mkdir -p ${PARTITION_DIRECTORY} -cat veba_output/misc/all_genomes.proteins.faa | seqkit split2 -p ${N_PARTITIONS} -O ${PARTITION_DIRECTORY} +cat veba_output/binning/*/*/output/genomes/*.faa | seqkit seq -M 99999 | seqkit split2 -p ${N_PARTITIONS} -O ${PARTITION_DIRECTORY} -# Now get the identifiers for each partition -IDENTIFIER_MAPPING=veba_output/misc/all_genomes.identifier_mapping.tsv +``` -for i in $(seq -f "%03g" 1 ${N_PARTITIONS}); do # This iterates through 1-100 and zero pads - PROTEINS=${PARTITION_DIRECTORY}/stdin.part_${i}.fasta - IDENTIFIERS=${PARTITION_DIRECTORY}/stdin.part_${i}.list - cat ${PROTEINS} | grep "^>" | cut -c2- | cut -f1 -d " " > ${IDENTIFIERS} - subset_table.py -i ${IDENTIFIERS} -t ${IDENTIFIER_MAPPING} -o ${PARTITION_DIRECTORY}/stdin.part_${i}.identifier_mapping.tsv - done +Since proteins will be split up, you should use the `-i/--identifier_mapping` arguments. +``` # Drop down the number of threads used since we are running 100 jobs now N_JOBS=1 @@ -626,21 +641,13 @@ for i in $(seq -f "%03g" 1 ${N_PARTITIONS}); do N="annotate-${i}" rm -f logs/${N}.* FAA=${PARTITION_DIRECTORY}/stdin.part_${i}.fasta - IDS=${PARTITION_DIRECTORY}/stdin.part_${i}.identifier_mapping.tsv - CMD="source activate VEBA-annotate_env && annotate.py -a ${FAA} -i ${IDS} -o ${OUT_DIR}/${i} -p ${N_JOBS}" + CMD="source activate VEBA-annotate_env && annotate.py -a ${FAA} -o ${OUT_DIR}/${i} -p ${N_JOBS} -u uniref50" # Either run this command or use SunGridEnginge/SLURM done - ``` -The following output files will produced: - -* annotations.tsv.gz - Concatenated annotations from Diamond (NR), HMMSearch (Pfam), HMMSearch (NCBIfam-AMRFinder), HMMSearch (AntiFam), and KOFAMSCAN (KOFAM) -* lineage.weighted_majority_vote.contigs.tsv.gz - [Experimental] Lineage predictions for contigs based on NR annotations. This should be used only for experimentation as lineages are not determined using core markers. Contig-level classifications. -* lineage.weighted_majority_vote.genomes.tsv.gz - [Experimental] Lineage predictions for contigs based on NR annotations. This should be used only for experimentation as lineages are not determined using core markers. MAG-level classifications. - _____________________________________________________ #### Next steps: diff --git a/walkthroughs/metabolic_profiling_de-novo_genomes.md b/walkthroughs/metabolic_profiling_de-novo_genomes.md new file mode 100644 index 0000000..fc10d50 --- /dev/null +++ b/walkthroughs/metabolic_profiling_de-novo_genomes.md @@ -0,0 +1,107 @@ +### Metabolic profiling of *de novo* genomes +If you build a comprehensive database, you may want to use a read-based approach to functionally profile a large set of samples. This tutorial will show you how to build a custom HUMAnN database from your annotations and how to profile your samples where there is full accounting of reads and your genomes. + +What you'll end up with at the end of this is a merged taxonomy table, a custom HUMAnN annotation table, and HUMAnN profiles. + +Please refer to the [end-to-end metagenomics](end-to-end_metagenomics.md) or [recovering viruses from metatranscriptomics](recovering_viruses_from_metatranscriptomics.md) workflows for details on binning, clustering, and annotation. + +_____________________________________________________ + +#### Steps: + +1. Merge taxonomy from all domains +2. Compile custom `HUMAnN` database from *de novo* genomes +3. Functional profiling using `HUMAnN` of custom database +4. Merge the tables + +**Conda Environment:** `conda activate VEBA-profile_env` + + +#### 1. Merge taxonomy from all domains + +At this point, it's assumed you have the following: + +* A concatenated protein fasta (preferably with proteins ≥ 100k removed `seqkit seq -M 99999`) called `veba_output/misc/all_genomes.all_proteins.lt100k.faa` +* Taxonomy classifications for all domains +* Annotations results from the `annotate.py` module +* Clustering results from the `cluster.py` module +* A directory of preprocessed reads: `veba_output/preprocess/${ID}/output/cleaned_1.fastq.gz` and `veba_output/preprocess/${ID}/output/cleaned_2.fastq.gz` where `${ID}` represents the identifiers in `identifiers.list`. + +```bash +merge_taxonomy_classifications.py -i veba_output/classify/ -o veba_output/classify/ --no_header --no_domain +``` + +#### 2. Compile custom `HUMAnN` database from *de novo* genomes + +Here we are going to compile all the `UniRef` annotations and taxonomy identifiers to build a custom `HUMAnN` database. The script supports piping to stdin so we are going to cut some columns from `identifier_mapping.proteins.tsv.gz` + + +``` +zcat veba_output/cluster/output/global/identifier_mapping.proteins.tsv.gz | cut -f1,3 | tail -n +2 | compile_custom_humann_database_from_annotations.py -a veba_output/annotation/output/annotations.proteins.tsv.gz -s veba_output/misc/all_genomes.all_proteins.lt100k.faa -o veba_output/misc/humann_uniref_annotations.tsv -t veba_output/classify/taxonomy_classifications.tsv + +``` + +This generates a table that has the following columns (no header): + +* `[id_protein][id_uniref][length][lineage]` +* e.g., `S1__NODE_1648_length_2909_cov_4.311843_4 UniRef50_A0A1C7D7V0 214 d__Bacteria;p__Pseudomonadota;c__Alphaproteobacteria;o__Sphingomonadales;f__Sphingomonadaceae;g__Erythrobacter;s__Erythrobacter sp003149575;t__S1__CONCOCT__P.1__24` + +#### 3. Functional profiling using `HUMAnN` of custom database + +Now it's time to align reads. Since `HUMAnN` takes in single reads, we join the reads using `bbmerge.sh` in the backend. You could also use the aligned reads in the form of a `bam` file from the `mapping.py` module. + +``` +N_JOBS=4 +OUT_DIR=veba_output/profiling/pathways +UNIREF_ANNOTATIONS=veba_output/misc/humann_uniref_annotations.tsv +FASTA=veba_output/misc/all_genomes.all_proteins.lt100k.faa + +mkdir -p logs + +for ID in $(cat identifiers.list); +do + N="profile-pathway__${ID}"; + rm -f logs/${N}.* + R1=veba_output/preprocess/${ID}/output/cleaned_1.fastq.gz + R2=veba_output/preprocess/${ID}/output/cleaned_2.fastq.gz + CMD="source activate VEBA-profile_env && profile-pathway.py -1 ${R1} -2 ${R2} -n ${ID} -o ${OUT_DIR} -p ${N_JOBS} -i ${UNIREF_ANNOTATIONS} -f ${FASTA}" + + # Either run this command or use SunGridEnginge/SLURM + +done + +``` + + +The following output files will produced for each sample: + +* reads.seqkit_stats.tsv - Sequence stats for input reads +* humann\_pathabundance.tsv - Stratified abundance of taxa-specific metabolic pathways +* humann\_pathcoverage.tsv - Stratified pathway completion ratio of taxa-specific metabolic pathways +* humann\_genefamilies.tsv - Stratified abundance of taxa-specific gene families +* humann\_diamond\_unaligned.fa.gz - Joined reads that did not align to database +* humann\_diamond\_aligned.tsv.gz - Aligned reads from translated blast search to database (blast6 format) + + +#### 4. Merge the tables + +``` +merge_generalized_mapping.py -o veba_output/profiling/pathways/merged. humann_pathcoverage.tsv veba_output/profiling/pathways/*/output/humann_pathcoverage.tsv + +merge_generalized_mapping.py -o veba_output/profiling/pathways/merged.humann_pathabundance.tsv veba_output/profiling/pathways/*/output/humann_pathabundance.tsv + +merge_generalized_mapping.py -o veba_output/profiling/pathways/merged.humann_genefamilies.tsv veba_output/profiling/pathways/*/output/humann_genefamilies.tsv +``` + +The following output files will produced for each sample: + +* merged.humann\_pathabundance.tsv - Stratified abundance of taxa-specific metabolic pathways of all samples +* meged.humann\_pathcoverage.tsv - Stratified pathway completion ratio of taxa-specific metabolic of all samplespathways +* meged.humann\_genefamilies.tsv - Stratified abundance of taxa-specific gene families of all samples + + +_____________________________________________________ + +#### Next steps: + +Subset stratified tables by their respective levels. diff --git a/walkthroughs/phylogenetic_inference.md b/walkthroughs/phylogenetic_inference.md index 35d9f99..6e7f4d7 100644 --- a/walkthroughs/phylogenetic_inference.md +++ b/walkthroughs/phylogenetic_inference.md @@ -270,8 +270,10 @@ The following output files will produced: * alignment_table.boolean.tsv.gz - Alignment table of (n = genomes, m = markers, ij=fasta pass qc) * concatenated_alignment.fasta - Concatenated protein alignment of all marker hits * concatenated_alignment.fasttree.nw - FastTree newick format based on concatenated alignment +* concatenated_alignment.fasttree.nw.pdf - PDF visualization of newick tree by ETE3 * prefiltered_alignment_table.tsv.gz - Prefiltered alignment table of (n = genomes, m = markers, ij=fasta alignment) * output.treefile - IQTREE2 newick format based on concatenated alignment (if --no_iqtree is not selected) +* output.treefile.pdf - PDF visualization of newick tree by ETE3 #### Next steps: