Repository: biopython/biopython

Issue:
How to get the alignment line from HmmerIO / hmmer3_text
### Setup

I am reporting a problem with Biopython version, Python version, and operating
system as follows:

```python
import sys; print(sys.version)
3.10.0 | packaged by conda-forge | (default, Nov 20 2021, 02:27:15) [Clang 11.1.0 ]

import platform; print(platform.python_implementation()); print(platform.platform())
CPython
macOS-13.5.2-arm64-arm-64bit

import Bio; print(Bio.__version__)
1.81
```

### Expected behaviour
Hi, 

I'm executing hmmsearch with the following command:
```bash
hmmsearch --domtblout hmmsearch_domtable.txt -E 0.00001 -o output.txt --cpu 5 --notextw MFS_efflux_PexA-NCBIFAM.HMM orf_predicted.faa

```
 and I would like to parse the file called `output.txt` to get the lines where there are the alignments
```
>> contig_204290_orf_3  
   #    score  bias  c-Evalue  i-Evalue hmmfrom  hmm to    alifrom  ali to    envfrom  env to     acc
 ---   ------ ----- --------- --------- ------- -------    ------- -------    ------- -------    ----
   1 !  142.2  30.0   3.4e-43   2.5e-39      21     394 ..       7     374 ..       1     387 [. 0.91

  Alignments for each domain:
  == domain 1  score: 142.2 bits;  conditional E-value: 3.4e-43
  MFS_efflux_PexA-NCBIFAM  21 illgcglwpemellvpslpdmqrafniqdaqiqqlltanfvgfligvlfagplcdsagrrtvmmigtigylvssvlcpfcndfvllmiarffqglfmtgpviaggvllmeategvkqifwmsignaaitfcmaagpivgswintgfgyvgnlwsililgligclpalflvpes.lpvekraafhpkllfkgyfallkdfrfmclaipmcalaaaywiyvgvsalymvnqlgiaqemfgryqgpivgcfsiislgsskllqrfglmkclragivsmftgmllllgmsilsldhavattvfmmffvggmapicsmlfpyalghlpvdlkgnaqamvqairlffasigtslvgvfyksaflpvalimfaillfscyfl 394
                              i++ +    e++l+ ps+p++q  f +    ++ ll+ nfvg+ +g+lf g l+d  gr+ ++++g   +++ s++c +   + +l++ rf+qgl +++p     +++ ++     q +w+s+ n    +++  +p+ gs+in  f + gn+ ++l+lg+i ++ +++++p+  lp +     + kllfkg++ +++   +m l i +  +   ywi+ g+s l  +++lg++  +fg yqg +  +f++ s+  + +++++   k l    +  ++ ++ +   + l++d  +  t+ ++ f+       s+l+p +l + p   k++  a+++   l+++++   + g +y+ +f  + +i+  + +f+  f+
      contig_204290_orf_3   7 IMMDVLSGAEIDLIAPSFPEIQAQFGLTPFWVEALLSINFVGICLGLLFVGSLADHYGRKQIILLGLTIFILGSIFCLVPSSYNFLLVGRFLQGLGVSAPSTLSFLIIADSYSTKDQQYWLSMLNGVFNIIVGIAPVAGSYINIYFHWQGNFMTLLLLGIITAFMTIIFIPKYkLPEN-----KEKLLFKGFLPIFQSKPLMQLIICITLIVIPYWIFSGMSPLLYIEDLGVSLAIFGFYQGVLATIFALGSILFAMIIHKYDDKKWLNISCIIGLSSLVCIGVTTYLNIDTPLIITLSIIPFIISQIIPSSLLYPICLNFQPK-AKAKISALLKGGNLILSAVSLQIAGYYYQGSFQNIGIILS-VTIFAGLFM 374
                              4555555689********************************************************************************************************************************************************9999998887515554.....479**********************************************************************************9988887777777666789*****99999888766666555589********9995.79999*****************************998864.555655554 PP

```

In particular, I'm interested in getting the line with the "+" symbols in order to count the identical and similar matches.
```
i++ +    e++l+ ps+p++q  f +    ++ ll+ nfvg+ +g+lf g l+d  gr+ ++++g   +++ s++c +   + +l++ rf+qgl +++p     +++ ++     q +w+s+ n    +++  +p+ gs+in  f + gn+ ++l+lg+i ++ +++++p+  lp +     + kllfkg++ +++   +m l i +  +   ywi+ g+s l  +++lg++  +fg yqg +  +f++ s+  + +++++   k l    +  ++ ++ +   + l++d  +  t+ ++ f+       s+l+p +l + p   k++  a+++   l+++++   + g +y+ +f  + +i+  + +f+  f+
```

### Actual behaviour / Steps to reproduce

I've successfully read the `output.txt` file through this code
```python
from Bio import SearchIO

with open ("output.txt",'rU') as handle: 
    for record in SearchIO.parse(handle, 'hmmer3-text'):
        hits = record.hits
...
```
But I wasn't able to get that line. 
Do you know how to do it? Do you have other suggestions?

I appreciate any help you can provide.
Best regards





Discussion:
I don't think the parser tries to capture the "match" string (this is something which varies considerably between file formats). I think you'd be better off counting matching letter explicitly.

Resolving patch:
diff --git a/Bio/SearchIO/HmmerIO/hmmer3_text.py b/Bio/SearchIO/HmmerIO/hmmer3_text.py
--- a/Bio/SearchIO/HmmerIO/hmmer3_text.py
+++ b/Bio/SearchIO/HmmerIO/hmmer3_text.py
@@ -7,7 +7,7 @@
 
 import re
 
-from Bio.SearchIO._utils import read_forward
+from Bio.SearchIO._utils import read_forward, removesuffix
 from Bio.SearchIO._model import QueryResult, Hit, HSP, HSPFragment
 
 from ._base import _BaseHmmerTextIndexer
@@ -341,6 +341,7 @@ def _parse_aln_block(self, hid, hsp_list):
             hmmseq = ""
             aliseq = ""
             annot = {}
+            aln_prefix_len = None
             self.line = self.handle.readline()
 
             # parse all the alignment blocks in the hsp
@@ -352,6 +353,12 @@ def _parse_aln_block(self, hid, hsp_list):
                 # to anticipate special cases where query id == hit id
                 regx = re.search(_HRE_ID_LINE, self.line)
                 if regx:
+                    # Try to capture the block prefix len, to parse the similarity
+                    # string later.
+                    if aln_prefix_len is None:
+                        aln_prefix_len = len(regx.group(1))
+                    else:
+                        assert aln_prefix_len == len(regx.group(1))
                     # the first hit/query self.line we encounter is the hmmseq
                     if len(hmmseq) == len(aliseq):
                         hmmseq += regx.group(2)
@@ -377,11 +384,11 @@ def _parse_aln_block(self, hid, hsp_list):
                     hmmseq = ""
                     aliseq = ""
                     annot = {}
+                    aln_prefix_len = None
                     break
-                # otherwise check if it's an annotation line and parse it
+                # check if it's an annotation line and parse it
                 # len(hmmseq) is only != len(aliseq) when the cursor is parsing
-                # the similarity character. Since we're not parsing that, we
-                # check for when the condition is False (i.e. when it's ==)
+                # the similarity character.
                 elif len(hmmseq) == len(aliseq):
                     regx = re.search(_HRE_ANNOT_LINE, self.line)
                     if regx:
@@ -390,6 +397,22 @@ def _parse_aln_block(self, hid, hsp_list):
                             annot[annot_name] += regx.group(2)
                         else:
                             annot[annot_name] = regx.group(2)
+                # otherwise, assume we are seeing the similarity string (the string
+                # between the two alignments). We do that by checking if we have
+                # defined the aln_prefix_len variable, which is the length of string
+                # from the beginning of the line to the start of the actual alignment
+                # (sans the IDs). We need this string to see how many characters we
+                # should strip from the beginning of the line. We can't call `strip()`
+                # since the similarity string might start with empty characters.
+                elif aln_prefix_len is not None:
+                    similarity = removesuffix(
+                        removesuffix(self.line[aln_prefix_len:], "\n"),
+                        "\r",
+                    )
+                    if "similarity" not in annot:
+                        annot["similarity"] = similarity
+                    else:
+                        annot["similarity"] += similarity
 
                 self.line = self.handle.readline()
 
diff --git a/Bio/SearchIO/_utils.py b/Bio/SearchIO/_utils.py
--- a/Bio/SearchIO/_utils.py
+++ b/Bio/SearchIO/_utils.py
@@ -5,6 +5,8 @@
 # package.
 """Common SearchIO utility functions."""
 
+import sys
+
 
 def getattr_str(obj, attr, fmt=None, fallback="?"):
     """Return string of the given object's attribute.
@@ -165,3 +167,16 @@ def setter(self, value):
             setattr(seq, attr, value)
 
     return property(fget=getter, fset=setter, doc=doc)
+
+
+def removesuffix(string, suffix):
+    """Remove the suffix from the string, if it exists."""
+    # This method is a compatibility wrapper for Python 3.8. It should be
+    # removed when support for Python 3.8 is dropped. At the time of writing,
+    # 3.8 is already the oldest supported version.
+    major, minor, *_ = sys.version_info
+    if major == 3 and minor == 8:
+        if suffix and string.endswith(suffix):
+            return string[: -len(suffix)]
+        return string
+    return string.removesuffix(suffix)


Test patch:
diff --git a/Tests/test_SearchIO_hmmer3_text.py b/Tests/test_SearchIO_hmmer3_text.py
--- a/Tests/test_SearchIO_hmmer3_text.py
+++ b/Tests/test_SearchIO_hmmer3_text.py
@@ -97,6 +97,10 @@ def test_31b1_hmmscan_001(self):
             "67899******************************************************************96",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "++ ++ele+fak +kq+ri+Lg+tqadvg +lg+l+Gk+fsqttIcrFEalqLslknmckL+pllekW+eea+",
+            hsp.aln_annotation["similarity"],
+        )
 
         # last hit
         hit = qresult[4]
@@ -139,6 +143,10 @@ def test_31b1_hmmscan_001(self):
             "345666667778888899************************99..9999999988876554",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ +++ + +++++   +++ ++l cP  sl++++++a++l  +k    v+++ +  r+  ++",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test if we've properly finished iteration
         self.assertRaises(StopIteration, next, qresults)
@@ -217,6 +225,10 @@ def test_30_hmmscan_001(self):
             "5789*********************************************************************...6899***********************999998",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+++lv   w+kv+a+++ +g+e+l rlfk +p+t ++F kf+ l+  +++k s+++k+h+++vl al+ ++k+   ++ ++a++k l+++Ha+++ ++ ++ + ++e++",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test third result
         qresult = next(qresults)
@@ -273,6 +285,10 @@ def test_30_hmmscan_001(self):
             "8************************99..78888888****************************************9",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "kP++s++psp+v+sggnv L+C ++     + +s +     +++   +++++ ++ss +++++ +v+++ +  Y+C+a",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[-1]
         self.assertEqual("Ig_2", hit.id)
@@ -313,6 +329,10 @@ def test_30_hmmscan_001(self):
             "799998885779*************85.899***9988655554443320...134455543444669************88443344588888888766",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "kp+l+a+p +vv++g nv L+C ++    + +++ k+g +e +      +            + +  vs++    Y+C+a    + +e++ +s+ +el v",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test fourth result
         qresult = next(qresults)
@@ -370,6 +390,10 @@ def test_30_hmmscan_001(self):
             "89******************************************************99..79*********************************99*****************************************8889*********************8",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+++++ l+++++e++++e+p++Wp+++ +l  l++++++++el++ iL++l+e++++f  ++l  +rr++++++l++++++i+++ll+ l+++v+k+               ++++  a+L++l+ +++w+++++i +++  ll++l+ lLn++el+  A+ecL",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[-1]
         self.assertEqual(2, hsp.domain_index)
@@ -392,6 +416,9 @@ def test_30_hmmscan_001(self):
         self.assertEqual("swvswidiglivnspllsllfqlLndpe", hsp.hit.seq)
         self.assertEqual("SFVQWEAMTLFLES-VITQMFRTLNREE", hsp.query.seq)
         self.assertEqual("899*********98.8888899998776", hsp.aln_annotation["PP"])
+        self.assertEqual(
+            "s+v+w  ++l+++s +++ +f+ Ln++e", hsp.aln_annotation["similarity"]
+        )
 
         hit = qresult[-1]
         self.assertEqual("IBN_N", hit.id)
@@ -437,6 +464,10 @@ def test_30_hmmscan_001(self):
             "56788886699*********.6555899******************........999999****99999887",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            " ++++++  P ++++ l+++ +k+    vR++++++L++ ++ +W+         ++  ek +++n++ +l+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[-1]
         self.assertEqual(2, hsp.domain_index)
@@ -459,6 +490,7 @@ def test_30_hmmscan_001(self):
         self.assertEqual("qqlpeeekelIrnnllnll", hsp.hit.seq)
         self.assertEqual("QTLPPQRRRDIQQTLTQNM", hsp.query.seq)
         self.assertEqual("6899*******99998865", hsp.aln_annotation["PP"])
+        self.assertEqual("q+lp++ + +I ++l + +", hsp.aln_annotation["similarity"])
 
         # test fifth result
         qresult = next(qresults)
@@ -515,6 +547,10 @@ def test_30_hmmscan_001(self):
             "67899******************************************************************96",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "++ ++ele+fak +kq+ri+Lg+tqadvg +lg+l+Gk+fsqttIcrFEalqLslknmckL+pllekW+eea+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[1]
         self.assertEqual("Homeobox", hit.id)
@@ -558,6 +594,10 @@ def test_30_hmmscan_001(self):
             "79****************************************************997",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+rkRt++++     Le +F k+++ps ++++++A++lgL++++V+vWF+NrR+k k+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[2]
         self.assertEqual("HTH_31", hit.id)
@@ -592,6 +632,10 @@ def test_30_hmmscan_001(self):
         self.assertEqual(
             "6999***********************************99", hsp.aln_annotation["PP"]
         )
+        self.assertEqual(
+            "+++ +L++ R + G tq++v+  lg      +S++t++r E",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[1]
         self.assertEqual(2, hsp.domain_index)
@@ -613,6 +657,7 @@ def test_30_hmmscan_001(self):
         self.assertEqual("rgrpsaavlaalaralgldpaera", hsp.hit.seq)
         self.assertEqual("CPKPSLQQITHIANQLGLEKDVVR", hsp.query.seq)
         self.assertEqual("678**************9988765", hsp.aln_annotation["PP"])
+        self.assertEqual("++ ps+++++ +a+ lgl+ + ++", hsp.aln_annotation["similarity"])
 
         hit = qresult[3]
         self.assertEqual("Homeobox_KN", hit.id)
@@ -645,6 +690,10 @@ def test_30_hmmscan_001(self):
         self.assertEqual("hnPYPskevkeelakqTglsrkqidnWFiNaRr", hsp.hit.seq)
         self.assertEqual("KCPKPSLQQITHIANQLGLEKDVVRVWFCNRRQ", hsp.query.seq)
         self.assertEqual("56779*************************996", hsp.aln_annotation["PP"])
+        self.assertEqual(
+            "+ P Ps +++  +a+q gl  + +  WF N R ",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[4]
         self.assertEqual("DUF521", hit.id)
@@ -686,6 +735,10 @@ def test_30_hmmscan_001(self):
             "345666667778888899************************99..9999999988876554",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ +++ + +++++   +++ ++l cP  sl++++++a++l  +k    v+++ +  r+  ++",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test if we've properly finished iteration
         self.assertRaises(StopIteration, next, qresults)
@@ -774,6 +827,10 @@ def test_30_hmmscan_003(self):
             "5789*********************************************************************...6899***********************999998",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+++lv   w+kv+a+++ +g+e+l rlfk +p+t ++F kf+ l+  +++k s+++k+h+++vl al+ ++k+   ++ ++a++k l+++Ha+++ ++ ++ + ++e++",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test if we've properly finished iteration
         self.assertRaises(StopIteration, next, qresults)
@@ -840,6 +897,10 @@ def test_30_hmmscan_004(self):
             "8************************99..78888888****************************************9",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "kP++s++psp+v+sggnv L+C ++     + +s +     +++   +++++ ++ss +++++ +v+++ +  Y+C+a",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[-1]
         self.assertEqual("Ig_2", hit.id)
@@ -880,6 +941,10 @@ def test_30_hmmscan_004(self):
             "799998885779*************85.899***9988655554443320...134455543444669************88443344588888888766",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "kp+l+a+p +vv++g nv L+C ++    + +++ k+g +e +      +            + +  vs++    Y+C+a    + +e++ +s+ +el v",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test if we've properly finished iteration
         self.assertRaises(StopIteration, next, qresults)
@@ -947,6 +1012,10 @@ def test_30_hmmscan_005(self):
             "89******************************************************99..79*********************************99*****************************************8889*********************8",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+++++ l+++++e++++e+p++Wp+++ +l  l++++++++el++ iL++l+e++++f  ++l  +rr++++++l++++++i+++ll+ l+++v+k+               ++++  a+L++l+ +++w+++++i +++  ll++l+ lLn++el+  A+ecL",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[-1]
         self.assertEqual(2, hsp.domain_index)
@@ -969,6 +1038,9 @@ def test_30_hmmscan_005(self):
         self.assertEqual("swvswidiglivnspllsllfqlLndpe", hsp.hit.seq)
         self.assertEqual("SFVQWEAMTLFLES-VITQMFRTLNREE", hsp.query.seq)
         self.assertEqual("899*********98.8888899998776", hsp.aln_annotation["PP"])
+        self.assertEqual(
+            "s+v+w  ++l+++s +++ +f+ Ln++e", hsp.aln_annotation["similarity"]
+        )
 
         hit = qresult[-1]
         self.assertEqual("IBN_N", hit.id)
@@ -1014,6 +1086,10 @@ def test_30_hmmscan_005(self):
             "56788886699*********.6555899******************........999999****99999887",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            " ++++++  P ++++ l+++ +k+    vR++++++L++ ++ +W+         ++  ek +++n++ +l+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[-1]
         self.assertEqual(2, hsp.domain_index)
@@ -1036,6 +1112,7 @@ def test_30_hmmscan_005(self):
         self.assertEqual("qqlpeeekelIrnnllnll", hsp.hit.seq)
         self.assertEqual("QTLPPQRRRDIQQTLTQNM", hsp.query.seq)
         self.assertEqual("6899*******99998865", hsp.aln_annotation["PP"])
+        self.assertEqual("q+lp++ + +I ++l + +", hsp.aln_annotation["similarity"])
 
         # test if we've properly finished iteration
         self.assertRaises(StopIteration, next, qresults)
@@ -1102,6 +1179,10 @@ def test_30_hmmscan_006(self):
             "67899******************************************************************96",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "++ ++ele+fak +kq+ri+Lg+tqadvg +lg+l+Gk+fsqttIcrFEalqLslknmckL+pllekW+eea+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[1]
         self.assertEqual("Homeobox", hit.id)
@@ -1145,6 +1226,10 @@ def test_30_hmmscan_006(self):
             "79****************************************************997",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+rkRt++++     Le +F k+++ps ++++++A++lgL++++V+vWF+NrR+k k+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[2]
         self.assertEqual("HTH_31", hit.id)
@@ -1179,6 +1264,10 @@ def test_30_hmmscan_006(self):
         self.assertEqual(
             "6999***********************************99", hsp.aln_annotation["PP"]
         )
+        self.assertEqual(
+            "+++ +L++ R + G tq++v+  lg      +S++t++r E",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[1]
         self.assertEqual(2, hsp.domain_index)
@@ -1200,6 +1289,7 @@ def test_30_hmmscan_006(self):
         self.assertEqual("rgrpsaavlaalaralgldpaera", hsp.hit.seq)
         self.assertEqual("CPKPSLQQITHIANQLGLEKDVVR", hsp.query.seq)
         self.assertEqual("678**************9988765", hsp.aln_annotation["PP"])
+        self.assertEqual("++ ps+++++ +a+ lgl+ + ++", hsp.aln_annotation["similarity"])
 
         hit = qresult[3]
         self.assertEqual("Homeobox_KN", hit.id)
@@ -1232,6 +1322,10 @@ def test_30_hmmscan_006(self):
         self.assertEqual("hnPYPskevkeelakqTglsrkqidnWFiNaRr", hsp.hit.seq)
         self.assertEqual("KCPKPSLQQITHIANQLGLEKDVVRVWFCNRRQ", hsp.query.seq)
         self.assertEqual("56779*************************996", hsp.aln_annotation["PP"])
+        self.assertEqual(
+            "+ P Ps +++  +a+q gl  + +  WF N R ",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[4]
         self.assertEqual("DUF521", hit.id)
@@ -1273,6 +1367,10 @@ def test_30_hmmscan_006(self):
             "345666667778888899************************99..9999999988876554",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ +++ + +++++   +++ ++l cP  sl++++++a++l  +k    v+++ +  r+  ++",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test if we've properly finished iteration
         self.assertRaises(StopIteration, next, qresults)
@@ -1528,6 +1626,10 @@ def test_30_hmmscan_008(self):
             "89******************************************************99..79*********************************99*****************************************8889*********************8",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+++++ l+++++e++++e+p++Wp+++ +l  l++++++++el++ iL++l+e++++f  ++l  +rr++++++l++++++i+++ll+ l+++v+k+               ++++  a+L++l+ +++w+++++i +++  ll++l+ lLn++el+  A+ecL",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[-1]
         self.assertEqual(2, hsp.domain_index)
@@ -1550,6 +1652,10 @@ def test_30_hmmscan_008(self):
         self.assertEqual("swvswidiglivnspllsllfqlLndpe", hsp.hit.seq)
         self.assertEqual("SFVQWEAMTLFLES-VITQMFRTLNREE", hsp.query.seq)
         self.assertEqual("899*********98.8888899998776", hsp.aln_annotation["PP"])
+        self.assertEqual(
+            "s+v+w  ++l+++s +++ +f+ Ln++e",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[-1]
         self.assertEqual("IBN_N", hit.id)
@@ -1595,6 +1701,10 @@ def test_30_hmmscan_008(self):
             "56788886699*********.6555899******************........999999****99999887",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            " ++++++  P ++++ l+++ +k+    vR++++++L++ ++ +W+         ++  ek +++n++ +l+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[-1]
         self.assertEqual(2, hsp.domain_index)
@@ -1617,6 +1727,7 @@ def test_30_hmmscan_008(self):
         self.assertEqual("qqlpeeekelIrnnllnll", hsp.hit.seq)
         self.assertEqual("QTLPPQRRRDIQQTLTQNM", hsp.query.seq)
         self.assertEqual("6899*******99998865", hsp.aln_annotation["PP"])
+        self.assertEqual("q+lp++ + +I ++l + +", hsp.aln_annotation["similarity"])
 
         # test if we've properly finished iteration
         self.assertRaises(StopIteration, next, qresults)
@@ -1807,6 +1918,10 @@ def test_31b1_hmmsearch_001(self):
             "67899**********7666611155667*****************99999****************************************************************************************************************************.******************************...999999999999999998866....99******************9999999*****997",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ell+ lG+Gs+GkV+ ++k   ++ g+ +A+K+lkk + k + + +++ E  il++++Hp+ivkl+ +f+t+ +lyl+l++++ggdlf+ l+ke  ++ee++k+++ +++ +l++lH  gii+rDLKpeNiLld++g++ki+DFGl+k+++ +++++++++gt eYmAPEv++ ++++t+++D+Ws+Gv+++e+ltg+lpf+g+   d++e+++ ilk kl  ++  s    +e+++l++ l++++p +Rl      +eei++hp++",
+            hsp.aln_annotation["similarity"],
+        )
 
     def test_30_hmmsearch_001(self):
         """Parsing hmmersearch 3.0 (text_30_hmmsearch_001)."""
@@ -1893,6 +2008,10 @@ def test_30_hmmsearch_002(self):
             "67899**********7666611155667*****************99999****************************************************************************************************************************.******************************...999999999999999998866....99******************9999999*****997",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ell+ lG+Gs+GkV+ ++k   ++ g+ +A+K+lkk + k + + +++ E  il++++Hp+ivkl+ +f+t+ +lyl+l++++ggdlf+ l+ke  ++ee++k+++ +++ +l++lH  gii+rDLKpeNiLld++g++ki+DFGl+k+++ +++++++++gt eYmAPEv++ ++++t+++D+Ws+Gv+++e+ltg+lpf+g+   d++e+++ ilk kl  ++  s    +e+++l++ l++++p +Rl      +eei++hp++",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[1]
         self.assertEqual(2, hsp.domain_index)
@@ -1927,6 +2046,10 @@ def test_30_hmmsearch_002(self):
             "7899***********************************98......9*******99**************************************************************************98544444888**********************************.***************************************************************************************7",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "ye++e +G Gs+++++++++k t  ++AvKi++k++++ ++      E++il +  +Hpni++l +v+ + +++ylv+e+++gg+l d + +++++se+e+ +++++i++ ++ylHs+g++HrDLKp+NiL  +++      +i+DFG+ak+l  +++ l t + t +++APEvl+ +++y++++DvWslG++ly++l g +pf +  +++ +e++++i ++k+  +  +++s s+ +kd+++k+l+ dp++Rlta ++lkhpw+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[-1]
         self.assertEqual("sp|P18654|KS6A3_MOUSE", hit.id)
@@ -1975,6 +2098,10 @@ def test_30_hmmsearch_002(self):
             "67899**********8888876655455****************999999****************************************************************************************************9***********************.******************************...999999999999988888866....9******************9888888999999886",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ell+ lG+GsfGkV+ +kk + ++    +A+K+lkk + k + + +++ E  il +++Hp+ivkl+ +f+t+ +lyl+l++++ggdlf+ l+ke  ++ee++k+++ +++ +l++lHs gii+rDLKpeNiLld++g++k++DFGl+k+   +++k+++++gt eYmAPEv++ ++++t+++D+Ws+Gv+++e+ltg+lpf+g+   d++e++ +ilk kl  ++  s    +e+++l++ l++++pa+Rl      +eei++h+++",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[1]
         self.assertEqual(2, hsp.domain_index)
@@ -2009,6 +2136,10 @@ def test_30_hmmsearch_002(self):
             "7899***********************************88......9*******99**********************************9***************************************98554444888**********************************.***************************************************************************************7",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "ye++e +G Gs+++++++++k t+ ++AvKi++k++++ ++      E++il +  +Hpni++l +v+ + +++y+v+e+++gg+l d + +++ +se+e+  ++ +i + +eylH +g++HrDLKp+NiL  +++      +i+DFG+ak+l  +++ l t + t +++APEvl+ +++y++++D+WslGv+ly++ltg +pf +  +++ +e++++i ++k + +   ++s s+++kdl++k+l+ dp++Rlta+ +l+hpw+",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test if we've properly finished iteration
         # self.assertRaises(StopIteration, next, qresults)
@@ -2201,6 +2332,10 @@ def test_30_hmmsearch_004(self):
             "67899**********7666611155667*****************99999****************************************************************************************************************************.******************************...999999999999999998866....99******************9999999*****997",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ell+ lG+Gs+GkV+ ++k   ++ g+ +A+K+lkk + k + + +++ E  il++++Hp+ivkl+ +f+t+ +lyl+l++++ggdlf+ l+ke  ++ee++k+++ +++ +l++lH  gii+rDLKpeNiLld++g++ki+DFGl+k+++ +++++++++gt eYmAPEv++ ++++t+++D+Ws+Gv+++e+ltg+lpf+g+   d++e+++ ilk kl  ++  s    +e+++l++ l++++p +Rl      +eei++hp++",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[1]
         self.assertEqual(2, hsp.domain_index)
@@ -2235,6 +2370,10 @@ def test_30_hmmsearch_004(self):
             "7899***********************************98......9*******99**************************************************************************98544444888**********************************.***************************************************************************************7",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "ye++e +G Gs+++++++++k t  ++AvKi++k++++ ++      E++il +  +Hpni++l +v+ + +++ylv+e+++gg+l d + +++++se+e+ +++++i++ ++ylHs+g++HrDLKp+NiL  +++      +i+DFG+ak+l  +++ l t + t +++APEvl+ +++y++++DvWslG++ly++l g +pf +  +++ +e++++i ++k+  +  +++s s+ +kd+++k+l+ dp++Rlta ++lkhpw+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[-1]
         self.assertEqual("sp|P18654|KS6A3_MOUSE", hit.id)
@@ -2283,6 +2422,10 @@ def test_30_hmmsearch_004(self):
             "67899**********8888876655455****************999999****************************************************************************************************9***********************.******************************...999999999999988888866....9******************9888888999999886",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ell+ lG+GsfGkV+ +kk + ++    +A+K+lkk + k + + +++ E  il +++Hp+ivkl+ +f+t+ +lyl+l++++ggdlf+ l+ke  ++ee++k+++ +++ +l++lHs gii+rDLKpeNiLld++g++k++DFGl+k+   +++k+++++gt eYmAPEv++ ++++t+++D+Ws+Gv+++e+ltg+lpf+g+   d++e++ +ilk kl  ++  s    +e+++l++ l++++pa+Rl      +eei++h+++",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[1]
         self.assertEqual(2, hsp.domain_index)
@@ -2317,6 +2460,10 @@ def test_30_hmmsearch_004(self):
             "7899***********************************88......9*******99**********************************9***************************************98554444888**********************************.***************************************************************************************7",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "ye++e +G Gs+++++++++k t+ ++AvKi++k++++ ++      E++il +  +Hpni++l +v+ + +++y+v+e+++gg+l d + +++ +se+e+  ++ +i + +eylH +g++HrDLKp+NiL  +++      +i+DFG+ak+l  +++ l t + t +++APEvl+ +++y++++D+WslGv+ly++ltg +pf +  +++ +e++++i ++k + +   ++s s+++kdl++k+l+ dp++Rlta+ +l+hpw+",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test if we've properly finished iteration
         # self.assertRaises(StopIteration, next, qresults)
@@ -2399,6 +2546,10 @@ def test_30_hmmsearch_005(self):
             "67899**********7666611155667*****************99999****************************************************************************************************************************.******************************...999999999999999998866....99******************9999999*****997",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ell+ lG+Gs+GkV+ ++k   ++ g+ +A+K+lkk + k + + +++ E  il++++Hp+ivkl+ +f+t+ +lyl+l++++ggdlf+ l+ke  ++ee++k+++ +++ +l++lH  gii+rDLKpeNiLld++g++ki+DFGl+k+++ +++++++++gt eYmAPEv++ ++++t+++D+Ws+Gv+++e+ltg+lpf+g+   d++e+++ ilk kl  ++  s    +e+++l++ l++++p +Rl      +eei++hp++",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[1]
         self.assertEqual(2, hsp.domain_index)
@@ -2433,6 +2584,10 @@ def test_30_hmmsearch_005(self):
             "7899***********************************98......9*******99**************************************************************************98544444888**********************************.***************************************************************************************7",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "ye++e +G Gs+++++++++k t  ++AvKi++k++++ ++      E++il +  +Hpni++l +v+ + +++ylv+e+++gg+l d + +++++se+e+ +++++i++ ++ylHs+g++HrDLKp+NiL  +++      +i+DFG+ak+l  +++ l t + t +++APEvl+ +++y++++DvWslG++ly++l g +pf +  +++ +e++++i ++k+  +  +++s s+ +kd+++k+l+ dp++Rlta ++lkhpw+",
+            hsp.aln_annotation["similarity"],
+        )
 
         hit = qresult[-1]
         self.assertEqual("sp|P18654|KS6A3_MOUSE", hit.id)
@@ -2481,6 +2636,10 @@ def test_30_hmmsearch_005(self):
             "67899**********8888876655455****************999999****************************************************************************************************9***********************.******************************...999999999999988888866....9******************9888888999999886",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "+ell+ lG+GsfGkV+ +kk + ++    +A+K+lkk + k + + +++ E  il +++Hp+ivkl+ +f+t+ +lyl+l++++ggdlf+ l+ke  ++ee++k+++ +++ +l++lHs gii+rDLKpeNiLld++g++k++DFGl+k+   +++k+++++gt eYmAPEv++ ++++t+++D+Ws+Gv+++e+ltg+lpf+g+   d++e++ +ilk kl  ++  s    +e+++l++ l++++pa+Rl      +eei++h+++",
+            hsp.aln_annotation["similarity"],
+        )
 
         hsp = hit.hsps[1]
         self.assertEqual(2, hsp.domain_index)
@@ -2515,6 +2674,10 @@ def test_30_hmmsearch_005(self):
             "7899***********************************88......9*******99**********************************9***************************************98554444888**********************************.***************************************************************************************7",
             hsp.aln_annotation["PP"],
         )
+        self.assertEqual(
+            "ye++e +G Gs+++++++++k t+ ++AvKi++k++++ ++      E++il +  +Hpni++l +v+ + +++y+v+e+++gg+l d + +++ +se+e+  ++ +i + +eylH +g++HrDLKp+NiL  +++      +i+DFG+ak+l  +++ l t + t +++APEvl+ +++y++++D+WslGv+ly++ltg +pf +  +++ +e++++i ++k + +   ++s s+++kdl++k+l+ dp++Rlta+ +l+hpw+",
+            hsp.aln_annotation["similarity"],
+        )
 
         # test if we've properly finished iteration
         self.assertRaises(StopIteration, next, qresults)
@@ -2585,6 +2748,10 @@ def test_31b2_phmmer_001(self):
             "89***************************************************************************************",
             hsp.aln_annotation["PP"][:89],
         )
+        self.assertEqual(
+            "mafsaedvlkeydrrrrmealllslyypndrklldykewspprvqvecpkapvewnnppsekglivghfsgikykgekaqasevdvnkm",
+            hsp.aln_annotation["similarity"][:89],
+        )
 
         # last query, last hit
         qresult = qresults[-1]
@@ -2633,6 +2800,10 @@ def test_31b2_phmmer_001(self):
         self.assertEqual("cpqswygspqlereivckmsgaphypnyyp", hsp.query.seq)
         self.assertEqual("YPDCSYGMSQLERSIVVACEGSPYVPVHFD", hsp.hit.seq)
         self.assertEqual("68889******************9887764", hsp.aln_annotation["PP"])
+        self.assertEqual(
+            " p   yg  qler iv    g+p+ p ++ ",
+            hsp.aln_annotation["similarity"],
+        )
 
 
 if __name__ == "__main__":