File size: 4,228 Bytes
35cdf53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135


"""A Python wrapper for hmmalign from the HMMER Suite."""

from collections.abc import Mapping, Sequence
import os
import tempfile

from flax_model.alphafold3.data import parsers
from flax_model.alphafold3.data.tools import subprocess_utils


def _to_a3m(sequences: Sequence[str], name_prefix: str = 'sequence') -> str:
  a3m = ''
  for i, sequence in enumerate(sequences, 1):
    a3m += f'> {name_prefix} {i}\n{sequence}\n'
  return a3m


class Hmmalign:
  """Python wrapper of the hmmalign binary."""

  def __init__(self, binary_path: str):
    """Initializes the Python hmmalign wrapper.

    Args:
      binary_path: Path to the hmmalign binary.

    Raises:
      RuntimeError: If hmmalign binary not found within the path.
    """
    self._binary_path = binary_path

    subprocess_utils.check_binary_exists(path=self._binary_path, name='hmmalign')

  def align_sequences(
      self,
      sequences: Sequence[str],
      profile: str,
      extra_flags: Mapping[str, str] | None = None,
  ) -> str:
    """Aligns sequence list to the profile and returns the alignment in A3M."""
    return self.align(
        a3m_str=_to_a3m(sequences, name_prefix='query'),
        profile=profile,
        extra_flags=extra_flags,
    )

  def align(
      self,
      a3m_str: str,
      profile: str,
      extra_flags: Mapping[str, str] | None = None,
  ) -> str:
    """Aligns sequences in A3M to the profile and returns the alignment in A3M.

    Args:
      a3m_str: A list of sequence strings.
      profile: A hmm file with the hmm profile to align the sequences to.
      extra_flags: Dictionary with extra flags, flag_name: flag_value, that are
        added to hmmalign.

    Returns:
      An A3M string with the aligned sequences.

    Raises:
      RuntimeError: If hmmalign fails.
    """
    with tempfile.TemporaryDirectory() as query_tmp_dir:
      input_profile = os.path.join(query_tmp_dir, 'profile.hmm')
      input_sequences = os.path.join(query_tmp_dir, 'sequences.a3m')
      output_a3m_path = os.path.join(query_tmp_dir, 'output.a3m')

      with open(input_profile, 'w') as f:
        f.write(profile)

      with open(input_sequences, 'w') as f:
        f.write(a3m_str)

      cmd = [
          self._binary_path,
          *('-o', output_a3m_path),
          *('--outformat', 'A2M'),  # A2M is A3M in the HMMER suite.
      ]
      if extra_flags:
        for flag_name, flag_value in extra_flags.items():
          cmd.extend([flag_name, flag_value])
      cmd.extend([input_profile, input_sequences])

      subprocess_utils.run(
          cmd=cmd,
          cmd_name='hmmalign',
          log_stdout=False,
          log_stderr=True,
          log_on_process_error=True,
      )

      with open(output_a3m_path, encoding='utf-8') as f:
        a3m = f.read()

    return a3m

  def align_sequences_to_profile(self, profile: str, sequences_a3m: str) -> str:
    """Aligns the sequences to profile and returns the alignment in A3M string.

    Uses hmmalign to align the sequences to the profile, then ouputs the
    sequence contatenated at the beginning of the sequences in the A3M format.
    As the sequences are represented by an alignment with possible gaps ('-')
    and insertions (lowercase characters), the method first removes the gaps,
    then uppercases the insertions to prepare the sequences for realignment.
    Sequences with gaps cannot be aligned, as '-'s are not a valid symbol to
    align; lowercase characters must be uppercased to preserve the original
    sequences before realignment.

    Args:
      profile: The Hmmbuild profile to align the sequences to.
      sequences_a3m: Sequences in A3M format to align to the profile.

    Returns:
      An A3M string with the aligned sequences.

    Raises:
      RuntimeError: If hmmalign fails.
    """
    deletion_table = str.maketrans('', '', '-')
    sequences_no_gaps_a3m = []
    for seq, desc in parsers.lazy_parse_fasta_string(sequences_a3m):
      sequences_no_gaps_a3m.append(f'>{desc}')
      sequences_no_gaps_a3m.append(seq.translate(deletion_table))
    sequences_no_gaps_a3m = '\n'.join(sequences_no_gaps_a3m)

    aligned_sequences = self.align(sequences_no_gaps_a3m, profile)

    return aligned_sequences