diff --git a/bin/proteins-to-pathogens.py b/bin/proteins-to-pathogens.py
index 2cc75d2..ed0bbd8 100755
--- a/bin/proteins-to-pathogens.py
+++ b/bin/proteins-to-pathogens.py
@@ -149,6 +149,14 @@ if __name__ == '__main__':
               'puts its HTML, plots and FASTA or FASTQ files, needed for '
               'using --html'))
 
+    parser.add_argument(
+        '--title', default='Summary of pathogens',
+        help='The title to show at the top of the output.')
+
+    parser.add_argument(
+        '--preamble',
+        help='Optional preamble text to show after the title.')
+
     args = parser.parse_args()
 
     if args.sampleName and args.sampleNameRegex:
@@ -199,6 +207,7 @@ if __name__ == '__main__':
         print(grouper.toHTML(args.pathogenPanelFilename,
                              minProteinFraction=args.minProteinFraction,
                              pathogenType=args.pathogenType,
+                             title=args.title, preamble=args.preamble,
                              sampleIndexFilename=args.sampleIndexFilename,
                              pathogenIndexFilename=args.pathogenIndexFilename))
     else:
diff --git a/dark/__init__.py b/dark/__init__.py
index 8d91d2b..8df318a 100644
--- a/dark/__init__.py
+++ b/dark/__init__.py
@@ -8,4 +8,4 @@ if sys.version_info < (2, 7):
 #
 # Remember to update ../CHANGELOG.md describing what's new in each version.
 
-__version__ = '3.0.68'
+__version__ = '3.0.69'
diff --git a/dark/html.py b/dark/html.py
index 6dbeb47..6fbde75 100644
--- a/dark/html.py
+++ b/dark/html.py
@@ -5,35 +5,38 @@ from IPython.display import HTML
 from dark.fastq import FastqReads
 
 
-def NCBISequenceLinkURL(title, default=None):
+def NCBISequenceLinkURL(title, field=None, delim='|'):
     """
-    Given a sequence title, like "gi|42768646|gb|AY516849.1| Homo sapiens",
+    Given a sequence title, like
+        "acc|GENBANK|AY516849.1|GENBANK|42768646 Homo sapiens",
     return the URL of a link to the info page at NCBI.
 
-    title: the sequence title to produce a link URL for.
-    default: the value to return if the title cannot be parsed.
+    @param title: The C{str} sequence title to produce a link URL for.
+    @param field: The C{int} field number to use (as delimited by C{delim})
+        or C{None} if no field splitting should be done.
+    @param delim: The C{str} to split the title on (if C{field} is not
+        C{None}).
+    @return: A C{str} URL.
     """
-    try:
-        ref = title.split('|')[3].split('.')[0]
-    except IndexError:
-        return default
-    else:
-        return 'http://www.ncbi.nlm.nih.gov/nuccore/%s' % (ref,)
+    return 'http://www.ncbi.nlm.nih.gov/nuccore/' + (
+        title if field is None else title.split(delim)[field])
 
 
-def NCBISequenceLink(title, default=None):
+def NCBISequenceLink(title, field=None, delim='|'):
     """
-    Given a sequence title, like "gi|42768646|gb|AY516849.1| Homo sapiens",
-    return an HTML A tag dispalying a link to the info page at NCBI.
-
-    title: the sequence title to produce an HTML link for.
-    default: the value to return if the title cannot be parsed.
+    Given a sequence title, like
+        "acc|GENBANK|AY516849.1|GENBANK|42768646 Homo sapiens",
+    return an HTML <A> tag displaying a link to the info page at NCBI.
+
+    @param title: The C{str} sequence title to produce a link URL for.
+    @param field: The C{int} field number to use (as delimited by C{delim})
+        or C{None} if no field splitting should be done.
+    @param delim: The C{str} to split the title on (if C{field} is not
+        C{None}).
+    @return: A C{str} HTML <A> tag.
     """
-    url = NCBISequenceLinkURL(title)
-    if url is None:
-        return default
-    else:
-        return '<a href="%s" target="_blank">%s</a>' % (url, title)
+    return '<a href="%s" target="_blank">%s</a>' % (
+        NCBISequenceLinkURL(title, field, delim), title)
 
 
 def _sortHTML(titlesAlignments, by, limit=None):
diff --git a/dark/proteins.py b/dark/proteins.py
index 106e2c2..1cef243 100644
--- a/dark/proteins.py
+++ b/dark/proteins.py
@@ -316,7 +316,8 @@ class ProteinGrouper(object):
                 'outDir': outDir,
                 'proteinLength': int(proteinLength),
                 'proteinName': proteinName,
-                'proteinURL': NCBISequenceLinkURL(proteinName),
+                'proteinURL': NCBISequenceLinkURL(proteinName, 2),
+                'genomeURL': NCBISequenceLinkURL(proteinName, 4),
                 'readCount': int(readCount),
             }
 
@@ -337,10 +338,13 @@ class ProteinGrouper(object):
             for sampleName in samples:
                 self.pathogenSampleFiles.add(pathogenName, sampleName)
 
-    def toStr(self):
+    def toStr(self, title='Summary of pathogens', preamble=None):
         """
         Produce a string representation of the pathogen summary.
 
+        @param title: The C{str} title for the output.
+        @param preamble: The C{str} descriptive preamble for the HTML page, or
+            C{None} if no preamble is needed.
         @return: A C{str} suitable for printing.
         """
         # Note that the string representation contains much less
@@ -352,8 +356,10 @@ class ProteinGrouper(object):
         result = []
         append = result.append
 
-        append(self._title())
-        append('')
+        result.extend((title, ''))
+        if preamble:
+            result.extend((preamble, ''))
+        result.extend((self._title(), ''))
 
         for pathogenName in sorted(self.pathogenNames):
             samples = self.pathogenNames[pathogenName]
@@ -380,7 +386,8 @@ class ProteinGrouper(object):
         return '\n'.join(result)
 
     def toHTML(self, pathogenPanelFilename=None, minProteinFraction=0.0,
-               pathogenType='viral', sampleIndexFilename=None,
+               pathogenType='viral', title='Summary of pathogens',
+               preamble=None, sampleIndexFilename=None,
                pathogenIndexFilename=None):
         """
         Produce an HTML string representation of the pathogen summary.
@@ -392,6 +399,9 @@ class ProteinGrouper(object):
             pathogen to be displayed for that sample.
         @param pathogenType: A C{str} giving the type of the pathogen involved,
             either 'bacterial' or 'viral'.
+        @param title: The C{str} title for the HTML page.
+        @param preamble: The C{str} descriptive preamble for the HTML page, or
+            C{None} if no preamble is needed.
         @param sampleIndexFilename: A C{str} filename to write a sample index
             file to. Lines in the file will have an integer index, a space, and
             then the sample name.
@@ -446,7 +456,7 @@ class ProteinGrouper(object):
             '<html>',
             '<head>',
             '<title>',
-            'Summary of pathogens',
+            title,
             '</title>',
             '<meta charset="UTF-8">',
             '</head>',
@@ -543,11 +553,13 @@ class ProteinGrouper(object):
 
         append = result.append
 
-        append('<h1>Summary of pathogens</h1>')
+        append('<h1>%s</h1>' % title)
+        if preamble:
+            append('<p>%s</p>' % preamble)
         append('<p>')
         append(self._title())
 
-        if self._pathogenProteinCount:
+        if self._pathogenProteinCount and minProteinFraction:
             percent = minProteinFraction * 100.0
             if nPathogenNames < len(self.pathogenNames):
                 if nPathogenNames == 1:
@@ -564,7 +576,7 @@ class ProteinGrouper(object):
                            'the pathogen proteins.' % (nPathogenNames,
                                                        percent))
             else:
-                append('Pathogen protein fraction filtering has been applied, '
+                append('Pathogen protein fraction filtering was applied, '
                        'but all pathogens have at least %.2f%% of their '
                        'proteins matched by at least one sample.' % percent)
 
@@ -685,8 +697,15 @@ class ProteinGrouper(object):
                         # Append this directly to the last string in result, to
                         # avoid introducing whitespace when we join result
                         # using '\n'.
-                        result[-1] += (', <a href="%s">NCBI</a>' %
+                        result[-1] += (', <a href="%s">NCBI protein</a>' %
                                        proteinMatch['proteinURL'])
+
+                    if proteinMatch['genomeURL']:
+                        # Append this directly to the last string in result, to
+                        # avoid introducing whitespace when we join result
+                        # using '\n'.
+                        result[-1] += (', <a href="%s">NCBI genome</a>' %
+                                       proteinMatch['genomeURL'])
                     result[-1] += ')'
 
                     append('</li>')
@@ -762,8 +781,16 @@ class ProteinGrouper(object):
                         # Append this directly to the last string in result, to
                         # avoid introducing whitespace when we join result
                         # using '\n'.
-                        result[-1] += (', <a href="%s">NCBI</a>' %
+                        result[-1] += (', <a href="%s">NCBI protein</a>' %
                                        proteinMatch['proteinURL'])
+
+                    if proteinMatch['genomeURL']:
+                        # Append this directly to the last string in result, to
+                        # avoid introducing whitespace when we join result
+                        # using '\n'.
+                        result[-1] += (', <a href="%s">NCBI genome</a>' %
+                                       proteinMatch['genomeURL'])
+
                     result[-1] += ')'
 
                     append('</li>')
diff --git a/dark/titles.py b/dark/titles.py
index cfeb6d5..26a70f8 100644
--- a/dark/titles.py
+++ b/dark/titles.py
@@ -1,7 +1,7 @@
 from collections import defaultdict, Counter
 
 from dark.utils import median
-from dark.filter import ReadSetFilter
+from dark.filter import ReadSetFilter, TitleFilter
 from dark.intervals import ReadIntervals
 
 
@@ -320,7 +320,8 @@ class TitlesAlignments(dict):
 
     def filter(self, minMatchingReads=None, minMedianScore=None,
                withScoreBetterThan=None, minNewReads=None, minCoverage=None,
-               maxTitles=None, sortOn='maxScore'):
+               maxTitles=None, sortOn='maxScore', titleRegex=None,
+               negativeTitleRegex=None):
         """
         Filter the titles in self to create another TitlesAlignments.
 
@@ -342,6 +343,8 @@ class TitlesAlignments(dict):
         @param sortOn: A C{str} attribute to sort on, used only if C{maxTitles}
             is not C{None}. See the C{sortTitles} method below for the legal
             values.
+        @param titleRegex: A regex that read ids must match.
+        @param negativeTitleRegex: A regex that read ids must not match.
         @raise: C{ValueError} if C{maxTitles} is less than zero or the value of
             C{sortOn} is unknown.
         @return: A new L{TitlesAlignments} instance containing only the
@@ -376,11 +379,22 @@ class TitlesAlignments(dict):
         else:
             titles = self.keys()
 
+        if (titleRegex or negativeTitleRegex):
+            titleFilter = TitleFilter(
+                positiveRegex=titleRegex, negativeRegex=negativeTitleRegex)
+        else:
+            titleFilter = None
+
         for title in titles:
             # Test max titles up front, as it may be zero.
             if maxTitles is not None and len(result) == maxTitles:
                 break
 
+            # Test positive and negative regexps.
+            if (titleFilter and
+                    titleFilter.accept(title) == TitleFilter.REJECT):
+                continue
+
             titleAlignments = self[title]
             if (minMatchingReads is not None and
                     titleAlignments.readCount() < minMatchingReads):

Add a way to pass a title to proteins-to-pathogens.py for HTML output
diff --git a/test/blast/test_titles.py b/test/blast/test_titles.py
index 0dae0f9..70cded1 100644
--- a/test/blast/test_titles.py
+++ b/test/blast/test_titles.py
@@ -889,6 +889,81 @@ class TestTitlesAlignmentsFiltering(TestCase):
                 ],
                 sorted(result.keys()))
 
+    def testTitleRegex(self):
+        """
+        The filter function must return only titles that match a passed regex.
+        """
+        mockOpener = mockOpen(read_data=(
+            dumps(PARAMS) + '\n' + dumps(RECORD0) + '\n' +
+            dumps(RECORD1) + '\n' + dumps(RECORD2) + '\n' +
+            dumps(RECORD3) + '\n'))
+        with patch.object(builtins, 'open', mockOpener):
+            reads = Reads()
+            reads.add(Read('id0', 'A' * 70))
+            reads.add(Read('id1', 'A' * 70))
+            reads.add(Read('id2', 'A' * 70))
+            reads.add(Read('id3', 'A' * 70))
+            readsAlignments = BlastReadsAlignments(reads, 'file.json')
+            titlesAlignments = TitlesAlignments(readsAlignments)
+            result = titlesAlignments.filter(titleRegex='squirrelpox')
+            self.assertEqual(
+                [
+                    'gi|887699|gb|DQ37780 Squirrelpox virus 1296/99',
+                    'gi|887699|gb|DQ37780 Squirrelpox virus 55',
+                ],
+                sorted(result.keys()))
+
+    def testTitleNegativeRegex(self):
+        """
+        The filter function must not return titles that match a passed negative
+        regex.
+        """
+        mockOpener = mockOpen(read_data=(
+            dumps(PARAMS) + '\n' + dumps(RECORD0) + '\n' +
+            dumps(RECORD1) + '\n' + dumps(RECORD2) + '\n' +
+            dumps(RECORD3) + '\n'))
+        with patch.object(builtins, 'open', mockOpener):
+            reads = Reads()
+            reads.add(Read('id0', 'A' * 70))
+            reads.add(Read('id1', 'A' * 70))
+            reads.add(Read('id2', 'A' * 70))
+            reads.add(Read('id3', 'A' * 70))
+            readsAlignments = BlastReadsAlignments(reads, 'file.json')
+            titlesAlignments = TitlesAlignments(readsAlignments)
+            result = titlesAlignments.filter(negativeTitleRegex='squirrelpox')
+            self.assertEqual(
+                [
+                    'gi|887699|gb|DQ37780 Cowpox virus 15',
+                    'gi|887699|gb|DQ37780 Monkeypox virus 456',
+                    'gi|887699|gb|DQ37780 Mummypox virus 3000 B.C.',
+                ],
+                sorted(result.keys()))
+
+    def testTitleRegexThenNegativeRegex(self):
+        """
+        The filter function must return only titles that match a passed
+        positive title regex and then a negative regex.
+        """
+        mockOpener = mockOpen(read_data=(
+            dumps(PARAMS) + '\n' + dumps(RECORD0) + '\n' +
+            dumps(RECORD1) + '\n' + dumps(RECORD2) + '\n' +
+            dumps(RECORD3) + '\n'))
+        with patch.object(builtins, 'open', mockOpener):
+            reads = Reads()
+            reads.add(Read('id0', 'A' * 70))
+            reads.add(Read('id1', 'A' * 70))
+            reads.add(Read('id2', 'A' * 70))
+            reads.add(Read('id3', 'A' * 70))
+            readsAlignments = BlastReadsAlignments(reads, 'file.json')
+            titlesAlignments = TitlesAlignments(readsAlignments)
+            result = titlesAlignments.filter(
+                titleRegex='squirrelpox').filter(negativeTitleRegex='1296')
+            self.assertEqual(
+                [
+                    'gi|887699|gb|DQ37780 Squirrelpox virus 55',
+                ],
+                sorted(result.keys()))
+
 
 class TestTitleSorting(TestCase):
     """
diff --git a/test/test_html.py b/test/test_html.py
index 8abab2a..d4733eb 100644
--- a/test/test_html.py
+++ b/test/test_html.py
@@ -1,4 +1,6 @@
 from unittest import TestCase
+from six import assertRaisesRegex
+
 from dark.html import NCBISequenceLinkURL, NCBISequenceLink
 
 
@@ -7,60 +9,62 @@ class TestNCBISequenceLinkURL(TestCase):
     Test the NCBISequenceLinkURL function.
     """
 
-    def testGenericTitle1(self):
-        title = 'gi|323924|gb|M15204.1|FCVMYCCA Feline leukemia virus myc gene'
-        self.assertEqual('http://www.ncbi.nlm.nih.gov/nuccore/M15204',
-                         NCBISequenceLinkURL(title))
-
-    def testGenericTitle2(self):
-        title = 'gi|37955203|gb|AY253278.1| Homo sapiens clone AL-11 HIV-1'
-        self.assertEqual('http://www.ncbi.nlm.nih.gov/nuccore/AY253278',
-                         NCBISequenceLinkURL(title))
-
-    def testUnparseableTitleReturnsNone(self):
+    def testNoField(self):
         """
-        When an unparseable title is passed, the default return value of
-        NCBISequenceLinkURL is None.
+        If no field is passed, the passed title must be used.
         """
-        self.assertEqual(None, NCBISequenceLinkURL(''))
+        self.assertEqual('http://www.ncbi.nlm.nih.gov/nuccore/xxx',
+                         NCBISequenceLinkURL('xxx'))
 
-    def testUnparseableTitleWithSpecificDefault(self):
+    def testFieldNumber(self):
+        title = 'gi|323924|gb|M15204.1|FCVMYCCA Feline leukemia virus myc gene'
+        self.assertEqual('http://www.ncbi.nlm.nih.gov/nuccore/M15204.1',
+                         NCBISequenceLinkURL(title, 3))
+
+    def testAlternateDelimiter(self):
+        title = 'gi+37955203+gb+AY253278.1+ Homo sapiens clone AL-11 HIV-1'
+        self.assertEqual('http://www.ncbi.nlm.nih.gov/nuccore/AY253278.1',
+                         NCBISequenceLinkURL(title, 3, '+'))
+
+    def testFieldOutOfRange(self):
         """
-        When an unparseable title is passed, the default return value passed to
-        NCBISequenceLinkURL must be returned.
+        If the field number passed does not exist, IndexError must be raised.
         """
-        default = object()
-        self.assertIs(default, NCBISequenceLinkURL('xxx', default))
+        title = 'gi|37955203|gb|AY253278.1| Homo sapiens clone AL-11 HIV-1'
+        error = r"^list index out of range$"
+        assertRaisesRegex(self, IndexError, error, NCBISequenceLinkURL, title,
+                          10)
 
 
 class TestNCBISequenceLink(TestCase):
     """
     Test the NCBISequenceLink function.
     """
+    def testNoField(self):
+        """
+        If no field is passed, the passed title must be used.
+        """
+        self.assertEqual(
+            '<a href="http://www.ncbi.nlm.nih.gov/nuccore/xxx" ' +
+            'target="_blank">xxx</a>', NCBISequenceLink('xxx'))
 
-    def testGenericTitle1(self):
+    def testFieldNumber(self):
         title = 'gi|323924|gb|M15204.1|FCVMYCCA Feline leukemia virus myc gene'
         self.assertEqual(
-            '<a href="http://www.ncbi.nlm.nih.gov/nuccore/M15204" ' +
-            'target="_blank">' + title + '</a>', NCBISequenceLink(title))
+            '<a href="http://www.ncbi.nlm.nih.gov/nuccore/M15204.1" ' +
+            'target="_blank">' + title + '</a>', NCBISequenceLink(title, 3))
 
-    def testGenericTitle2(self):
-        title = 'gi|37955203|gb|AY253278.1| Homo sapiens clone AL-11 HIV-1'
+    def testAlternateDelimiter(self):
+        title = 'gi+37955203+gb+AY253278.1+ Homo sapiens clone AL-11 HIV-1'
         self.assertEqual(
-            '<a href="http://www.ncbi.nlm.nih.gov/nuccore/AY253278" ' +
-            'target="_blank">' + title + '</a>', NCBISequenceLink(title))
+            '<a href="http://www.ncbi.nlm.nih.gov/nuccore/AY253278.1" ' +
+            'target="_blank">' + title + '</a>',
+            NCBISequenceLink(title, 3, '+'))
 
-    def testUnparseableTitleReturnsNone(self):
+    def testFieldOutOfRange(self):
         """
-        When an unparseable title is passed, the default return value of
-        NCBISequenceLink is None.
+        If the field number passed does not exist, IndexError must be raised.
         """
-        self.assertEqual(None, NCBISequenceLink(''))
-
-    def testUnparseableTitleWithSpecificDefault(self):
-        """
-        When an unparseable title is passed, the default return value passed to
-        NCBISequenceLink must be returned.
-        """
-        default = object()
-        self.assertIs(default, NCBISequenceLink('xxx', default))
+        title = 'gi|37955203|gb|AY253278.1| Homo sapiens clone AL-11 HIV-1'
+        error = r"^list index out of range$"
+        assertRaisesRegex(self, IndexError, error, NCBISequenceLink, title, 10)
diff --git a/test/test_proteins.py b/test/test_proteins.py
index 009f0bf..3cd8d09 100644
--- a/test/test_proteins.py
+++ b/test/test_proteins.py
@@ -217,13 +217,14 @@ class TestProteinGrouper(TestCase):
         If a protein grouper is given duplicate information for a
         pathogen/protein/sample combination it must raise a ValueError.
         """
-        fp = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327|X|I44.6 ubiquitin [Lausannevirus]\n')
+        fp = StringIO('0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+                      'ubiquitin [Lausannevirus]\n')
         pg = ProteinGrouper()
         pg.addFile('sample', fp)
         fp.seek(0)
-        error = ("^Protein 'gi\\|327\\|X\\|I44.6 ubiquitin' already seen for "
-                 "pathogen 'Lausannevirus' sample 'sample'\\.$")
+        error = ("^Protein 'acc\\|GENBANK\\|I44.6\\|GENBANK\\|J77|"
+                 "ubiquitin' already seen for pathogen 'Lausannevirus' "
+                 "sample 'sample'\\.$")
         assertRaisesRegex(self, ValueError, error, pg.addFile, 'sample', fp)
 
     def testOneLineInOneFile(self):
@@ -231,8 +232,8 @@ class TestProteinGrouper(TestCase):
         If a protein grouper is given one file with one line, its pathogenNames
         dict must be as expected.
         """
-        fp = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327|X|I44.6 ubiquitin [Lausannevirus]\n')
+        fp = StringIO('0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+                      'ubiquitin [Lausannevirus]\n')
         pg = ProteinGrouper()
         pg.addFile('sample-filename', fp)
         self.assertEqual(
@@ -240,7 +241,7 @@ class TestProteinGrouper(TestCase):
                 'Lausannevirus': {
                     'sample-filename': {
                         'proteins': {
-                            'gi|327|X|I44.6 ubiquitin': {
+                            'acc|GENBANK|I44.6|GENBANK|J77|ubiquitin': {
                                 'bestScore': 48.1,
                                 'bluePlotFilename': 'out/0.png',
                                 'coverage': 0.77,
@@ -250,9 +251,13 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 46.6,
                                 'outDir': 'out',
                                 'proteinLength': 74,
-                                'proteinName': 'gi|327|X|I44.6 ubiquitin',
+                                'proteinName': ('acc|GENBANK|I44.6|GENBANK|'
+                                                'J77|ubiquitin'),
                                 'proteinURL': (
-                                    'http://www.ncbi.nlm.nih.gov/nuccore/I44'),
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/'
+                                    'I44.6'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J77'),
                                 'readCount': 5,
                             },
                         },
@@ -267,8 +272,8 @@ class TestProteinGrouper(TestCase):
         If a protein grouper is given a different assetDir name,
         the outDir needs to have that same name, as expected.
         """
-        fp = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327|X|I44.6 ubiquitin [Lausannevirus]\n')
+        fp = StringIO('0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+                      'ubiquitin [Lausannevirus]\n')
         pg = ProteinGrouper(assetDir='differentname')
         pg.addFile('sample-filename', fp)
         self.assertEqual(
@@ -276,7 +281,7 @@ class TestProteinGrouper(TestCase):
                 'Lausannevirus': {
                     'sample-filename': {
                         'proteins': {
-                            'gi|327|X|I44.6 ubiquitin': {
+                            'acc|GENBANK|I44.6|GENBANK|J77|ubiquitin': {
                                 'bestScore': 48.1,
                                 'bluePlotFilename': 'differentname/0.png',
                                 'coverage': 0.77,
@@ -286,9 +291,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 46.6,
                                 'outDir': 'differentname',
                                 'proteinLength': 74,
-                                'proteinName': 'gi|327|X|I44.6 ubiquitin',
-                                'proteinURL': (
-                                    'http://www.ncbi.nlm.nih.gov/nuccore/I44'),
+                                'proteinName': ('acc|GENBANK|I44.6|GENBANK|'
+                                                'J77|ubiquitin'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.6'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J77'),
                                 'readCount': 5,
                             },
                         },
@@ -303,8 +311,8 @@ class TestProteinGrouper(TestCase):
         If a protein grouper is given one file with one line, its pathogenNames
         dict must be as expected, including for a FASTQ file.
         """
-        fp = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327|X|I44.6 ubiquitin [Lausannevirus]\n')
+        fp = StringIO('0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+                      'ubiquitin [Lausannevirus]\n')
         pg = ProteinGrouper(format_='fastq')
         pg.addFile('sample-filename', fp)
         self.assertEqual(
@@ -312,7 +320,7 @@ class TestProteinGrouper(TestCase):
                 'Lausannevirus': {
                     'sample-filename': {
                         'proteins': {
-                            'gi|327|X|I44.6 ubiquitin': {
+                            'acc|GENBANK|I44.6|GENBANK|J77|ubiquitin': {
                                 'bestScore': 48.1,
                                 'bluePlotFilename': 'out/0.png',
                                 'coverage': 0.77,
@@ -322,9 +330,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 46.6,
                                 'outDir': 'out',
                                 'proteinLength': 74,
-                                'proteinName': 'gi|327|X|I44.6 ubiquitin',
-                                'proteinURL': (
-                                    'http://www.ncbi.nlm.nih.gov/nuccore/I44'),
+                                'proteinName': ('acc|GENBANK|I44.6|GENBANK|'
+                                                'J77|ubiquitin'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.6'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J77'),
                                 'readCount': 5,
                             },
                         },
@@ -339,8 +350,8 @@ class TestProteinGrouper(TestCase):
         If a protein grouper is given one file with one line, its _title method
         must return the expected string.
         """
-        fp = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327|X|I44.6 ubiquitin [Lausannevirus]\n')
+        fp = StringIO('0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+                      'ubiquitin [Lausannevirus]\n')
         pg = ProteinGrouper()
         pg.addFile('sample-filename', fp)
         self.assertEqual(
@@ -354,8 +365,10 @@ class TestProteinGrouper(TestCase):
         string.
         """
         fp = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327|X|I44.6 ubiquitin [Lausannevirus]\n'
-            '0.77 46.6 48.1 5 6 74 gi|327|X|I44.6 ubiquitin [X Virus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+            'ubiquitin [Lausannevirus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+            'ubiquitin [X Virus]\n'
         )
         pg = ProteinGrouper()
         pg.addFile('sample-filename', fp)
@@ -369,8 +382,10 @@ class TestProteinGrouper(TestCase):
         pathogen, its pathogenNames dict must be as expected.
         """
         fp = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [Lausannevirus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Lausannevirus]\n'
         )
         pg = ProteinGrouper()
         pg.addFile('sample-filename', fp)
@@ -379,7 +394,7 @@ class TestProteinGrouper(TestCase):
                 'Lausannevirus': {
                     'sample-filename': {
                         'proteins': {
-                            'gi|327410| protein 77': {
+                            'acc|GENBANK|I44.6|GENBANK|J77|VP1': {
                                 'bestScore': 44.2,
                                 'bluePlotFilename': 'out/0.png',
                                 'coverage': 0.63,
@@ -389,11 +404,15 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 41.3,
                                 'outDir': 'out',
                                 'proteinLength': 12,
-                                'proteinName': 'gi|327410| protein 77',
-                                'proteinURL': None,
+                                'proteinName': ('acc|GENBANK|I44.6|GENBANK|'
+                                                'J77|VP1'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.6'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J77'),
                                 'readCount': 9,
                             },
-                            'gi|327409| ubiquitin': {
+                            'acc|GENBANK|I44.7|GENBANK|J78|VP2': {
                                 'bestScore': 48.1,
                                 'bluePlotFilename': 'out/1.png',
                                 'coverage': 0.77,
@@ -403,8 +422,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 46.6,
                                 'outDir': 'out',
                                 'proteinLength': 74,
-                                'proteinName': 'gi|327409| ubiquitin',
-                                'proteinURL': None,
+                                'proteinName': ('acc|GENBANK|I44.7|GENBANK|'
+                                                'J78|VP2'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.7'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J78'),
                                 'readCount': 5,
                             },
                         },
@@ -420,8 +443,10 @@ class TestProteinGrouper(TestCase):
         pathogens, its pathogenNames dict must be as expected.
         """
         fp = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [Hepatitis B virus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Hepatitis B virus]\n'
         )
         pg = ProteinGrouper()
         pg.addFile('sample-filename', fp)
@@ -430,7 +455,7 @@ class TestProteinGrouper(TestCase):
                 'Lausannevirus': {
                     'sample-filename': {
                         'proteins': {
-                            'gi|327410| protein 77': {
+                            'acc|GENBANK|I44.6|GENBANK|J77|VP1': {
                                 'bestScore': 44.2,
                                 'bluePlotFilename': 'out/0.png',
                                 'coverage': 0.63,
@@ -440,8 +465,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 41.3,
                                 'outDir': 'out',
                                 'proteinLength': 12,
-                                'proteinName': 'gi|327410| protein 77',
-                                'proteinURL': None,
+                                'proteinName': (
+                                    'acc|GENBANK|I44.6|GENBANK|J77|VP1'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.6'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J77'),
                                 'readCount': 9,
                             },
                         },
@@ -451,7 +480,7 @@ class TestProteinGrouper(TestCase):
                 'Hepatitis B virus': {
                     'sample-filename': {
                         'proteins': {
-                            'gi|327409| ubiquitin': {
+                            'acc|GENBANK|I44.7|GENBANK|J78|VP2': {
                                 'bestScore': 48.1,
                                 'bluePlotFilename': 'out/1.png',
                                 'coverage': 0.77,
@@ -461,8 +490,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 46.6,
                                 'outDir': 'out',
                                 'proteinLength': 74,
-                                'proteinName': 'gi|327409| ubiquitin',
-                                'proteinURL': None,
+                                'proteinName': (
+                                    'acc|GENBANK|I44.7|GENBANK|J78|VP2'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.7'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J78'),
                                 'readCount': 5,
                             },
                         },
@@ -478,10 +511,12 @@ class TestProteinGrouper(TestCase):
         same pathogen, its pathogenNames dict must be as expected.
         """
         fp1 = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
         )
         fp2 = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [Lausannevirus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Lausannevirus]\n'
         )
         pg = ProteinGrouper()
         pg.addFile('sample-filename-1', fp1)
@@ -491,7 +526,7 @@ class TestProteinGrouper(TestCase):
                 'Lausannevirus': {
                     'sample-filename-1': {
                         'proteins': {
-                            'gi|327410| protein 77': {
+                            'acc|GENBANK|I44.6|GENBANK|J77|VP1': {
                                 'bestScore': 44.2,
                                 'bluePlotFilename': 'out/0.png',
                                 'coverage': 0.63,
@@ -501,8 +536,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 41.3,
                                 'outDir': 'out',
                                 'proteinLength': 12,
-                                'proteinName': 'gi|327410| protein 77',
-                                'proteinURL': None,
+                                'proteinName': (
+                                    'acc|GENBANK|I44.6|GENBANK|J77|VP1'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.6'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J77'),
                                 'readCount': 9,
                             },
                         },
@@ -510,7 +549,7 @@ class TestProteinGrouper(TestCase):
                     },
                     'sample-filename-2': {
                         'proteins': {
-                            'gi|327409| ubiquitin': {
+                            'acc|GENBANK|I44.7|GENBANK|J78|VP2': {
                                 'bestScore': 48.1,
                                 'bluePlotFilename': 'out/0.png',
                                 'coverage': 0.77,
@@ -520,8 +559,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 46.6,
                                 'outDir': 'out',
                                 'proteinLength': 74,
-                                'proteinName': 'gi|327409| ubiquitin',
-                                'proteinURL': None,
+                                'proteinName': (
+                                    'acc|GENBANK|I44.7|GENBANK|J78|VP2'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.7'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J78'),
                                 'readCount': 5,
                             },
                         },
@@ -537,10 +580,12 @@ class TestProteinGrouper(TestCase):
         same pathogen, its _title method must return the expected string.
         """
         fp1 = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
         )
         fp2 = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [Lausannevirus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Lausannevirus]\n'
         )
         pg = ProteinGrouper()
         pg.addFile('sample-filename-1', fp1)
@@ -556,10 +601,12 @@ class TestProteinGrouper(TestCase):
         must be as expected.
         """
         fp1 = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
         )
         fp2 = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [Hepatitis B virus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Hepatitis B virus]\n'
         )
         pg = ProteinGrouper()
         pg.addFile('dir-1/sample-filename-1', fp1)
@@ -569,7 +616,7 @@ class TestProteinGrouper(TestCase):
                 'Lausannevirus': {
                     'dir-1/sample-filename-1': {
                         'proteins': {
-                            'gi|327410| protein 77': {
+                            'acc|GENBANK|I44.6|GENBANK|J77|VP1': {
                                 'bestScore': 44.2,
                                 'bluePlotFilename': 'dir-1/out/0.png',
                                 'coverage': 0.63,
@@ -579,8 +626,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 41.3,
                                 'outDir': 'dir-1/out',
                                 'proteinLength': 12,
-                                'proteinName': 'gi|327410| protein 77',
-                                'proteinURL': None,
+                                'proteinName': (
+                                    'acc|GENBANK|I44.6|GENBANK|J77|VP1'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.6'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J77'),
                                 'readCount': 9,
                             },
                         },
@@ -590,7 +641,7 @@ class TestProteinGrouper(TestCase):
                 'Hepatitis B virus': {
                     'dir-2/sample-filename-2': {
                         'proteins': {
-                            'gi|327409| ubiquitin': {
+                            'acc|GENBANK|I44.7|GENBANK|J78|VP2': {
                                 'bestScore': 48.1,
                                 'bluePlotFilename': 'dir-2/out/0.png',
                                 'coverage': 0.77,
@@ -600,8 +651,12 @@ class TestProteinGrouper(TestCase):
                                 'medianScore': 46.6,
                                 'outDir': 'dir-2/out',
                                 'proteinLength': 74,
-                                'proteinName': 'gi|327409| ubiquitin',
-                                'proteinURL': None,
+                                'proteinName': (
+                                    'acc|GENBANK|I44.7|GENBANK|J78|VP2'),
+                                'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                               'nuccore/I44.7'),
+                                'genomeURL': (
+                                    'http://www.ncbi.nlm.nih.gov/nuccore/J78'),
                                 'readCount': 5,
                             },
                         },
@@ -617,10 +672,12 @@ class TestProteinGrouper(TestCase):
         different pathogens, its _title method must return the expected string.
         """
         fp1 = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
         )
         fp2 = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [HBV]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Hepatitis B virus]\n'
         )
         pg = ProteinGrouper()
         pg.addFile('sample-filename-1', fp1)
@@ -636,24 +693,55 @@ class TestProteinGrouper(TestCase):
         """
         pg = ProteinGrouper()
         self.assertEqual(
+            'Summary of pathogens\n'
+            '\n'
             'Overall, proteins from 0 pathogens were found in 0 samples.\n',
             pg.toStr())
 
+    def testNoFilesToStrWithTitle(self):
+        """
+        If no files have been given to a protein grouper, its text string
+        format (including a passed title) must as expected.
+        """
+        pg = ProteinGrouper()
+        self.assertEqual(
+            'The title...\n'
+            '\n'
+            'Overall, proteins from 0 pathogens were found in 0 samples.\n',
+            pg.toStr(title='The title...'))
+
+    def testNoFilesToStrWithTitleAndPreamble(self):
+        """
+        If no files have been given to a protein grouper, its text string
+        format (including a passed title and preamble) must as expected.
+        """
+        pg = ProteinGrouper()
+        self.assertEqual(
+            'The title...\n'
+            '\n'
+            'The preamble...\n'
+            '\n'
+            'Overall, proteins from 0 pathogens were found in 0 samples.\n',
+            pg.toStr(title='The title...', preamble='The preamble...'))
+
     def testOneLineInOneFileToStr(self):
         """
         If a protein grouper is given one file with one line, its toStr method
         must produce the expected result.
         """
-        fp = StringIO(
-            '0.77 46.6 48.1 5 6 74 gi|32|X|I4 protein X [HBV]\n')
+        fp = StringIO('0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+                      'VP1 [Lausannevirus]\n')
         pg = ProteinGrouper()
         pg.addFile('sample-filename', fp)
         self.assertEqual(
+            'Summary of pathogens\n'
+            '\n'
             'Overall, proteins from 1 pathogen were found in 1 sample.\n'
             '\n'
-            'HBV (in 1 sample)\n'
+            'Lausannevirus (in 1 sample)\n'
             '  sample-filename (1 protein, 5 reads)\n'
-            '    0.77\t46.60\t48.10\t   5\t   6\t  0\tgi|32|X|I4 protein X\n',
+            '    0.77\t46.60\t48.10\t   5\t   6\t  0\tacc|'
+            'GENBANK|I44.6|GENBANK|J77|VP1\n',
             pg.toStr())
 
 
@@ -700,9 +788,8 @@ class TestPathogenSampleFiles(TestCase):
                         'Filename: %r, Args: %r, Keyword args: %r.' %
                         (filename, args, kwargs))
 
-        fp = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
-        )
+        fp = StringIO('0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.6|GENBANK|J77|'
+                      'VP1 [Lausannevirus]\n')
         fastaIO = StringIO()
 
         @contextmanager
@@ -754,8 +841,10 @@ class TestPathogenSampleFiles(TestCase):
                         return self.manager
 
         fp = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [Lausannevirus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Lausannevirus]\n'
         )
         fastaIO = StringIO()
 
@@ -803,8 +892,10 @@ class TestPathogenSampleFiles(TestCase):
                         'Keyword args: %r.' % (filename, args, kwargs))
 
         fp = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [Lausannevirus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Lausannevirus]\n'
         )
         fastaIO = StringIO()
 
@@ -823,34 +914,42 @@ class TestPathogenSampleFiles(TestCase):
         self.assertEqual(
             {
                 'proteins': {
-                    'gi|327409| ubiquitin': {
+                    'acc|GENBANK|I44.6|GENBANK|J77|VP1': {
+                        'bestScore': 44.2,
+                        'bluePlotFilename': 'out/0.png',
+                        'coverage': 0.63,
+                        'readsFilename': 'out/0.fasta',
+                        'hspCount': 9,
+                        'index': 0,
+                        'medianScore': 41.3,
+                        'outDir': 'out',
+                        'proteinLength': 12,
+                        'proteinName': (
+                            'acc|GENBANK|I44.6|GENBANK|J77|VP1'),
+                        'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                       'nuccore/I44.6'),
+                        'genomeURL': (
+                            'http://www.ncbi.nlm.nih.gov/nuccore/J77'),
+                        'readCount': 9,
+                    },
+                    'acc|GENBANK|I44.7|GENBANK|J78|VP2': {
                         'bestScore': 48.1,
                         'bluePlotFilename': 'out/1.png',
                         'coverage': 0.77,
+                        'readsFilename': 'out/1.fasta',
                         'hspCount': 6,
                         'index': 1,
                         'medianScore': 46.6,
                         'outDir': 'out',
                         'proteinLength': 74,
-                        'proteinName': 'gi|327409| ubiquitin',
-                        'proteinURL': None,
+                        'proteinName': (
+                            'acc|GENBANK|I44.7|GENBANK|J78|VP2'),
+                        'proteinURL': ('http://www.ncbi.nlm.nih.gov/'
+                                       'nuccore/I44.7'),
+                        'genomeURL': (
+                            'http://www.ncbi.nlm.nih.gov/nuccore/J78'),
                         'readCount': 5,
-                        'readsFilename': 'out/1.fasta',
                     },
-                    'gi|327410| protein 77': {
-                        'bestScore': 44.2,
-                        'bluePlotFilename': 'out/0.png',
-                        'coverage': 0.63,
-                        'hspCount': 9,
-                        'index': 0,
-                        'medianScore': 41.3,
-                        'outDir': 'out',
-                        'proteinLength': 12,
-                        'proteinName': 'gi|327410| protein 77',
-                        'proteinURL': None,
-                        'readCount': 9,
-                        'readsFilename': 'out/0.fasta',
-                    }
                 },
                 'uniqueReadCount': 3,
             },
@@ -882,8 +981,10 @@ class TestPathogenSampleFiles(TestCase):
                         'Keyword args: %r.' % (filename, args, kwargs))
 
         fp = StringIO(
-            '0.63 41.3 44.2 9 9 12 gi|327410| protein 77 [Lausannevirus]\n'
-            '0.77 46.6 48.1 5 6 74 gi|327409| ubiquitin [Lausannevirus]\n'
+            '0.63 41.3 44.2 9 9 12 acc|GENBANK|I44.6|GENBANK|J77|VP1 '
+            '[Lausannevirus]\n'
+            '0.77 46.6 48.1 5 6 74 acc|GENBANK|I44.7|GENBANK|J78|VP2 '
+            '[Lausannevirus]\n'
         )
         fastaIO = StringIO()
 
@@ -901,10 +1002,11 @@ class TestPathogenSampleFiles(TestCase):
 
         # Read lengths must be saved correctly.
         proteins = pg.pathogenNames['Lausannevirus']['filename-1']['proteins']
-        self.assertEqual((4,),
-                         proteins['gi|327410| protein 77']['readLengths'])
-        self.assertEqual((2, 7),
-                         proteins['gi|327409| ubiquitin']['readLengths'])
+        self.assertEqual(
+            (4,), proteins['acc|GENBANK|I44.6|GENBANK|J77|VP1']['readLengths'])
+        self.assertEqual(
+            (2, 7),
+            proteins['acc|GENBANK|I44.7|GENBANK|J78|VP2']['readLengths'])
 
     def testWriteSampleIndex(self):
         """

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
backcall==0.2.0
biopython==1.79
bz2file==0.98
certifi==2021.5.30
charset-normalizer==2.0.12
cycler==0.11.0
-e git+https://github.com/acorg/dark-matter.git@2de1c45cb41468ce844728e9fd5502cd05e635ea#egg=dark_matter
decorator==5.1.1
ete3==3.1.3
flake8==5.0.4
idna==3.10
importlib-metadata==4.2.0
iniconfig==1.1.1
ipython==7.16.3
ipython-genutils==0.2.0
jedi==0.17.2
kiwisolver==1.3.1
matplotlib==3.3.4
mccabe==0.7.0
mysql-connector-python==8.0.11
numpy==1.19.5
packaging==21.3
parso==0.7.1
pexpect==4.9.0
pickleshare==0.7.5
Pillow==8.4.0
pluggy==1.0.0
prompt-toolkit==3.0.36
protobuf==3.19.6
ptyprocess==0.7.0
py==1.11.0
pycodestyle==2.9.1
pyfaidx==0.5.0
pyflakes==2.5.0
Pygments==2.14.0
pyparsing==3.1.4
pysam==0.23.0
pytest==7.0.1
python-dateutil==2.9.0.post0
pyzmq==25.1.2
requests==2.27.1
simplejson==3.20.1
six==1.17.0
tomli==1.2.3
traitlets==4.3.3
typing_extensions==4.1.1
urllib3==1.26.20
wcwidth==0.2.13
zipp==3.6.0

name: dark-matter
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - backcall==0.2.0
      - biopython==1.79
      - bz2file==0.98
      - charset-normalizer==2.0.12
      - cycler==0.11.0
      - decorator==5.1.1
      - ete3==3.1.3
      - flake8==5.0.4
      - idna==3.10
      - importlib-metadata==4.2.0
      - iniconfig==1.1.1
      - ipython==7.16.3
      - ipython-genutils==0.2.0
      - jedi==0.17.2
      - kiwisolver==1.3.1
      - matplotlib==3.3.4
      - mccabe==0.7.0
      - mysql-connector-python==8.0.11
      - numpy==1.19.5
      - packaging==21.3
      - parso==0.7.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pillow==8.4.0
      - pluggy==1.0.0
      - prompt-toolkit==3.0.36
      - protobuf==3.19.6
      - ptyprocess==0.7.0
      - py==1.11.0
      - pycodestyle==2.9.1
      - pyfaidx==0.5.0
      - pyflakes==2.5.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pysam==0.23.0
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pyzmq==25.1.2
      - requests==2.27.1
      - simplejson==3.20.1
      - six==1.17.0
      - tomli==1.2.3
      - traitlets==4.3.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - wcwidth==0.2.13
      - zipp==3.6.0
prefix: /opt/conda/envs/dark-matter

test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testTitleNegativeRegex test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testTitleRegex test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testTitleRegexThenNegativeRegex test/test_html.py::TestNCBISequenceLinkURL::testAlternateDelimiter test/test_html.py::TestNCBISequenceLinkURL::testFieldNumber test/test_html.py::TestNCBISequenceLinkURL::testFieldOutOfRange test/test_html.py::TestNCBISequenceLinkURL::testNoField test/test_html.py::TestNCBISequenceLink::testAlternateDelimiter test/test_html.py::TestNCBISequenceLink::testFieldNumber test/test_html.py::TestNCBISequenceLink::testFieldOutOfRange test/test_html.py::TestNCBISequenceLink::testNoField test/test_proteins.py::TestProteinGrouper::testNoFilesToStr test/test_proteins.py::TestProteinGrouper::testNoFilesToStrWithTitle test/test_proteins.py::TestProteinGrouper::testNoFilesToStrWithTitleAndPreamble test/test_proteins.py::TestProteinGrouper::testOneLineInEachOfTwoFilesDifferentPathogens test/test_proteins.py::TestProteinGrouper::testOneLineInEachOfTwoFilesSamePathogen test/test_proteins.py::TestProteinGrouper::testOneLineInOneFile test/test_proteins.py::TestProteinGrouper::testOneLineInOneFileFASTQ test/test_proteins.py::TestProteinGrouper::testOneLineInOneFileToStr test/test_proteins.py::TestProteinGrouper::testOneLineInOneFileWithDifferentAssetDir test/test_proteins.py::TestProteinGrouper::testTwoLinesInOneFileDifferentPathogens test/test_proteins.py::TestProteinGrouper::testTwoLinesInOneFileSamePathogen
test/test_proteins.py::TestPathogenSampleFiles::testIdenticalReadsRemoved test/test_proteins.py::TestPathogenSampleFiles::testOpenNotCalledOnRepeatedCall test/test_proteins.py::TestPathogenSampleFiles::testProteinsSavedCorrectly test/test_proteins.py::TestPathogenSampleFiles::testReadLengthsAdded
test/blast/test_titles.py::TestTitleCounts::testDuplicatedTitle test/blast/test_titles.py::TestTitleCounts::testEmpty test/blast/test_titles.py::TestTitleCounts::testThreeRecords test/blast/test_titles.py::TestTitlesAlignments::testAddTitle test/blast/test_titles.py::TestTitlesAlignments::testAddTitleRepeat test/blast/test_titles.py::TestTitlesAlignments::testEmpty test/blast/test_titles.py::TestTitlesAlignments::testExpectedTitleDetails test/blast/test_titles.py::TestTitlesAlignments::testExpectedTitles test/blast/test_titles.py::TestTitlesAlignments::testHsps test/blast/test_titles.py::TestTitlesAlignments::testTitleCollection test/blast/test_titles.py::TestTitlesAlignments::testToDict test/blast/test_titles.py::TestTitlesAlignments::testTwoJSONInputsWithSubjectInCommon test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testCoverageExcludesAll test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testCoverageIncludesAll test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testCoverageIncludesSome test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testFilterWithNoArguments test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testMaxTitlesNegative test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testMaxTitlesOne test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testMaxTitlesTwoSortOnLength test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testMaxTitlesZero test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testMinMatchingReads test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testMinMedianScore_Bits test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testMinMedianScore_EValue test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testReadSetFilterAllowAnything test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testReadSetFilterStrict test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testUnknownSortOn test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testWithScoreBetterThan_Bits test/blast/test_titles.py::TestTitlesAlignmentsFiltering::testWithScoreBetterThan_EValue test/blast/test_titles.py::TestTitleSorting::testEmpty test/blast/test_titles.py::TestTitleSorting::testLength test/blast/test_titles.py::TestTitleSorting::testMaxScore_Bits test/blast/test_titles.py::TestTitleSorting::testMaxScore_EValue test/blast/test_titles.py::TestTitleSorting::testMedianScore_Bits test/blast/test_titles.py::TestTitleSorting::testMedianScore_EValue test/blast/test_titles.py::TestTitleSorting::testReadCount test/blast/test_titles.py::TestTitleSorting::testTitle test/blast/test_titles.py::TestTitleSorting::testUnknown test/test_proteins.py::TestSplitNames::testNestedBrackets test/test_proteins.py::TestSplitNames::testNoBrackets test/test_proteins.py::TestSplitNames::testNormalCase test/test_proteins.py::TestSplitNames::testTwoSetsOfBrackets test/test_proteins.py::TestSplitNames::testWhitespaceStripping test/test_proteins.py::TestGetPathogenProteinCounts::testExpected test/test_proteins.py::TestGetPathogenProteinCounts::testExpectedWithTwoFiles test/test_proteins.py::TestGetPathogenProteinCounts::testNone test/test_proteins.py::TestProteinGrouper::testAssetDir test/test_proteins.py::TestProteinGrouper::testDuplicatePathogenProteinSample test/test_proteins.py::TestProteinGrouper::testNoAssetDir test/test_proteins.py::TestProteinGrouper::testNoFiles test/test_proteins.py::TestProteinGrouper::testNoRegex test/test_proteins.py::TestProteinGrouper::testNoSampleName test/test_proteins.py::TestProteinGrouper::testOneLineInEachOfTwoFilesDifferentPathogensTitle test/test_proteins.py::TestProteinGrouper::testOneLineInEachOfTwoFilesSamePathogenTitle test/test_proteins.py::TestProteinGrouper::testOneLineInOneFileTitle test/test_proteins.py::TestProteinGrouper::testTwoLinesInOneFileTitle test/test_proteins.py::TestProteinGrouper::testUnknownFormat test/test_proteins.py::TestProteinGrouper::testUnknownPathogenType test/test_proteins.py::TestPathogenSampleFiles::testUnknownFormat test/test_proteins.py::TestPathogenSampleFiles::testWritePathogenIndex test/test_proteins.py::TestPathogenSampleFiles::testWriteSampleIndex
bin/proteins-to-pathogens.py dark/html.py dark/titles.py dark/__init__.py dark/proteins.py
bin/proteins-to-pathogens.py dark/html.py dark/titles.py dark/__init__.py dark/proteins.py