Mercurial > repos > peterjc > tmhmm_and_signalp
view tools/protein_analysis/rxlr_motifs.py @ 21:238eae32483c draft
"Check this is up to date with all 2020 changes (black etc)"
author | peterjc |
---|---|
date | Thu, 17 Jun 2021 08:21:06 +0000 |
parents | a19b3ded8f33 |
children | e1996f0f4e85 |
line wrap: on
line source
#!/usr/bin/env python """Implements assorted RXLR motif methods from the literature. This script takes exactly four command line arguments: * Protein FASTA filename * Number of threads * Model name (Bhattacharjee2006, Win2007, Whisson2007) * Output tabular filename The model names are: * Bhattacharjee2006: Simple regular expression search for RXLR with additional requirements for positioning and signal peptide. * Win2007: Simple regular expression search for RXLR, but with different positional requirements. * Whisson2007: As Bhattacharjee2006 but with a more complex regular expression to look for RXLR-EER domain, and additionally calls HMMER. See the help text in the accompanying Galaxy tool XML file for more details including the full references. Note ---- Bhattacharjee et al. (2006) and Win et al. (2007) used SignalP v2.0, which is no longer available. The current release is SignalP v3.0 (Mar 5, 2007). We have therefore opted to use the NN Ymax position for the predicted cleavage site, as this is expected to be more accurate. Also note that the HMM score values have changed from v2.0 to v3.0. Whisson et al. (2007) used SignalP v3.0 anyway. Whisson et al. (2007) used HMMER 2.3.2, and althought their HMM model can still be used with hmmsearch from HMMER 3, sadly this does give slightly different results. We expect the hmmsearch from HMMER 2.3.2 (the last stable release of HMMER 2) to be present on the path under the name hmmsearch2 (allowing it to co-exist with HMMER 3). If using Conda, you should therefore install the special "hmmer2" package from BioConda which provides "hmmsearch2" etc:: conda install -c bioconda hmmer2 See https://bioconda.github.io/recipes/hmmer2/README.html and https://anaconda.org/bioconda/hmmer2 """ from __future__ import print_function import os import re import subprocess import sys from seq_analysis_utils import fasta_iterator if "-v" in sys.argv: print("RXLR Motifs v0.0.16") sys.exit(0) if len(sys.argv) != 5: sys.exit( "Requires four arguments: protein FASTA filename, threads, " "model, and output filename" ) fasta_file, threads, model, tabular_file = sys.argv[1:] hmm_output_file = tabular_file + ".hmm.tmp" signalp_input_file = tabular_file + ".fasta.tmp" signalp_output_file = tabular_file + ".tabular.tmp" min_signalp_hmm = 0.9 hmmer_search = "hmmsearch2" if model == "Bhattacharjee2006": signalp_trunc = 70 re_rxlr = re.compile("R.LR") min_sp = 10 max_sp = 40 max_sp_rxlr = 100 min_rxlr_start = 1 # Allow signal peptide to be at most 40aa, and want RXLR to be # within 100aa, therefore for the prescreen the max start is 140: max_rxlr_start = max_sp + max_sp_rxlr elif model == "Win2007": signalp_trunc = 70 re_rxlr = re.compile("R.LR") min_sp = 10 max_sp = 40 min_rxlr_start = 30 max_rxlr_start = 60 # No explicit limit on separation of signal peptide clevage # and RXLR, but shortest signal peptide is 10, and furthest # away RXLR is 60, so effectively limit is 50. max_sp_rxlr = max_rxlr_start - min_sp + 1 elif model == "Whisson2007": signalp_trunc = 0 # zero for no truncation re_rxlr = re.compile("R.LR.{,40}[ED][ED][KR]") min_sp = 10 max_sp = 40 max_sp_rxlr = 100 min_rxlr_start = 1 max_rxlr_start = max_sp + max_sp_rxlr else: sys.exit( "Did not recognise the model name %r\n" "Use Bhattacharjee2006, Win2007, or Whisson2007" % model ) def get_hmmer_version(exe, required=None): try: child = subprocess.Popen( [exe, "-h"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except OSError: raise ValueError("Could not run %s" % exe) stdout, stderr = child.communicate() if required: return required in stdout elif "HMMER 2" in stdout: return 2 elif "HMMER 3" in stdout: return 3 else: raise ValueError("Could not determine version of %s" % exe) # Run hmmsearch for Whisson et al. (2007) if model == "Whisson2007": hmm_file = os.path.join( os.path.split(sys.argv[0])[0], "whisson_et_al_rxlr_eer_cropped.hmm" ) if not os.path.isfile(hmm_file): sys.exit("Missing HMM file for Whisson et al. (2007)") if not get_hmmer_version(hmmer_search, "HMMER 2.3.2 (Oct 2003)"): sys.exit("Missing HMMER 2.3.2 (Oct 2003) binary, %s" % hmmer_search) hmm_hits = set() valid_ids = set() for title, seq in fasta_iterator(fasta_file): name = title.split(None, 1)[0] if name in valid_ids: sys.exit("Duplicated identifier %r" % name) else: valid_ids.add(name) if not valid_ids: # Special case, don't need to run HMMER if there are no sequences pass else: # I've left the code to handle HMMER 3 in situ, in case # we revisit the choice to insist on HMMER 2. hmmer3 = 3 == get_hmmer_version(hmmer_search) # Using zero (or 5.6?) for bitscore threshold if hmmer3: # The HMMER3 table output is easy to parse # In HMMER3 can't use both -T and -E cmd = "%s -T 0 --tblout %s --noali %s %s > /dev/null" % ( hmmer_search, hmm_output_file, hmm_file, fasta_file, ) else: # For HMMER2 we are stuck with parsing stdout # Put 1e6 to effectively have no expectation threshold (otherwise # HMMER defaults to 10 and the calculated e-value depends on the # input FASTA file, and we can loose hits of interest). cmd = "%s -T 0 -E 1e6 %s %s > %s" % ( hmmer_search, hmm_file, fasta_file, hmm_output_file, ) return_code = os.system(cmd) if return_code: sys.stderr.write("Error %i from hmmsearch:\n%s\n" % (return_code, cmd)) sys.exit(return_code) handle = open(hmm_output_file) for line in handle: if not line.strip(): # We expect blank lines in the HMMER2 stdout continue elif line.startswith("#"): # Header continue else: name = line.split(None, 1)[0] # Should be a sequence name in the HMMER3 table output. # Could be anything in the HMMER2 stdout. if name in valid_ids: hmm_hits.add(name) elif hmmer3: sys.exit("Unexpected identifer %r in hmmsearch output" % name) handle.close() # if hmmer3: # print "HMMER3 hits for %i/%i" % (len(hmm_hits), len(valid_ids)) # else: # print "HMMER2 hits for %i/%i" % (len(hmm_hits), len(valid_ids)) # print "%i/%i matched HMM" % (len(hmm_hits), len(valid_ids)) os.remove(hmm_output_file) del valid_ids # Prepare short list of candidates containing RXLR to pass to SignalP assert min_rxlr_start > 0, "Min value one, since zero based counting" count = 0 total = 0 handle = open(signalp_input_file, "w") for title, seq in fasta_iterator(fasta_file): total += 1 name = title.split(None, 1)[0] match = re_rxlr.search(seq[min_rxlr_start - 1 :].upper()) if match and min_rxlr_start - 1 + match.start() + 1 <= max_rxlr_start: # This is a potential RXLR, depending on the SignalP results. # Might as well truncate the sequence now, makes the temp file smaller if signalp_trunc: handle.write(">%s (truncated)\n%s\n" % (name, seq[:signalp_trunc])) else: # Does it matter we don't line wrap? handle.write(">%s\n%s\n" % (name, seq)) count += 1 handle.close() # print "Running SignalP on %i/%i potentials." % (count, total) # Run SignalP (using our wrapper script to get multi-core support etc) signalp_script = os.path.join(os.path.split(sys.argv[0])[0], "signalp3.py") if not os.path.isfile(signalp_script): sys.exit("Error - missing signalp3.py script") cmd = "python '%s' 'euk' '%i' '%s' '%s' '%s'" % ( signalp_script, signalp_trunc, threads, signalp_input_file, signalp_output_file, ) return_code = os.system(cmd) if return_code: sys.exit("Error %i from SignalP:\n%s" % (return_code, cmd)) # print "SignalP done" def parse_signalp(filename): """Parse SignalP output, yield tuples of values. Returns tuples of ID, HMM_Sprob_score and NN predicted signal peptide length. For signal peptide length we use NN_Ymax_pos (minus one). """ handle = open(filename) line = handle.readline() assert line.startswith("#ID\t"), line for line in handle: parts = line.rstrip("\t").split("\t") assert len(parts) == 20, repr(line) yield parts[0], float(parts[18]), int(parts[5]) - 1 handle.close() # Parse SignalP results and apply the strict RXLR criteria total = 0 tally = {} handle = open(tabular_file, "w") handle.write("#ID\t%s\n" % model) signalp_results = parse_signalp(signalp_output_file) for title, seq in fasta_iterator(fasta_file): total += 1 rxlr = "N" name = title.split(None, 1)[0] match = re_rxlr.search(seq[min_rxlr_start - 1 :].upper()) if match and min_rxlr_start - 1 + match.start() + 1 <= max_rxlr_start: del match # This was the criteria for calling SignalP, # so it will be in the SignalP results. sp_id, sp_hmm_score, sp_nn_len = next(signalp_results) assert name == sp_id, "%s vs %s" % (name, sp_id) if sp_hmm_score >= min_signalp_hmm and min_sp <= sp_nn_len <= max_sp: match = re_rxlr.search(seq[sp_nn_len:].upper()) if match and match.start() + 1 <= max_sp_rxlr: # 1-based counting rxlr_start = sp_nn_len + match.start() + 1 if min_rxlr_start <= rxlr_start <= max_rxlr_start: rxlr = "Y" if model == "Whisson2007": # Combine the signalp with regular expression heuristic and the HMM if name in hmm_hits and rxlr == "N": rxlr = "hmm" # HMM only elif rxlr == "N": rxlr = "neither" # Don't use N (no) elif name not in hmm_hits and rxlr == "Y": rxlr = "re" # Heuristic only # Now have a four way classifier: Y, hmm, re, neither # and count is the number of Y results (both HMM and heuristic) handle.write("%s\t%s\n" % (name, rxlr)) try: tally[rxlr] += 1 except KeyError: tally[rxlr] = 1 handle.close() assert sum(tally.values()) == total # Check the iterator is finished try: next(signalp_results) assert False, "Unexpected data in SignalP output" except StopIteration: pass # Cleanup os.remove(signalp_input_file) os.remove(signalp_output_file) # Short summary to stdout for Galaxy's info display print("%s for %i sequences:" % (model, total)) print(", ".join("%s = %i" % kv for kv in sorted(tally.items())))