docs: def toHTML(self, pathogenPanelFilename=None, minProteinFraction=0.0,
                 pathogenType='viral', sampleIndexFilename=None,
                 pathogenIndexFilename=None):
          """
          Produce an HTML string representation of the pathogen summary.
  
          @param pathogenPanelFilename: If not C{None}, a C{str} filename to
              write a pathogen panel PNG image to.
          @param minProteinFraction: The C{float} minimum fraction of proteins
              in a pathogen that must be matched by a sample in order for that
              pathogen to be displayed for that sample.
          @param pathogenType: A C{str} giving the type of the pathogen involved,
              either 'bacterial' or 'viral'.
          @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.
          @param pathogenIndexFilename: A C{str} filename to write a pathogen
              index file to. Lines in the file will have an integer index, a
              space, and then the pathogen name.
          @return: An HTML C{str} suitable for printing.
          """
          if pathogenType not in ('bacterial', 'viral'):
              raise ValueError(
                  "Unrecognized pathogenType argument: %r. Value must be either "
                  "'bacterial' or 'viral'." % pathogenType)
  
          highlightSymbol = '&starf;'
          self._computeUniqueReadCounts()
  
          if pathogenPanelFilename:
              self.pathogenPanel(pathogenPanelFilename)
  
          if sampleIndexFilename:
              with open(sampleIndexFilename, 'w') as fp:
                  self.pathogenSampleFiles.writeSampleIndex(fp)
  
          if pathogenIndexFilename:
              with open(pathogenIndexFilename, 'w') as fp:
                  self.pathogenSampleFiles.writePathogenIndex(fp)
  
          toDelete = defaultdict(list)
          for pathogenName in self.pathogenNames:
              proteinCount = self._pathogenProteinCount[pathogenName]
              for s in self.pathogenNames[pathogenName]:
                  if proteinCount:
                      sampleProteinFraction = (
                          len(self.pathogenNames[pathogenName][s]['proteins']) /
                          proteinCount)
                  else:
                      sampleProteinFraction = 1.0
                  if sampleProteinFraction < minProteinFraction:
                      toDelete[pathogenName].append(s)
  
          for pathogenName in toDelete:
              for sample in toDelete[pathogenName]:
                  del self.pathogenNames[pathogenName][sample]
  
          pathogenNames = sorted(
              pathogenName for pathogenName in self.pathogenNames
              if len(self.pathogenNames[pathogenName]) > 0)
          nPathogenNames = len(pathogenNames)
          sampleNames = sorted(self.sampleNames)
  
          result = [
              '<html>',
              '<head>',
              '<title>',
              'Summary of pathogens',
              '</title>',
              '<meta charset="UTF-8">',
              '</head>',
              '<body>',
              '<style>',
              '''\
              body {
                  margin-left: 2%;
                  margin-right: 2%;
              }
              hr {
                  display: block;
                  margin-top: 0.5em;
                  margin-bottom: 0.5em;
                  margin-left: auto;
                  margin-right: auto;
                  border-style: inset;
                  border-width: 1px;
              }
              p.pathogen {
                  margin-top: 10px;
                  margin-bottom: 3px;
              }
              p.sample {
                  margin-top: 10px;
                  margin-bottom: 3px;
              }
              .significant {
                  color: red;
                  margin-right: 2px;
              }
              .sample {
                  margin-top: 5px;
                  margin-bottom: 2px;
              }
              ul {
                  margin-bottom: 2px;
              }
              .indented {
                  margin-left: 2em;
              }
              .sample-name {
                  font-size: 125%;
                  font-weight: bold;
              }
              .pathogen-name {
                  font-size: 125%;
                  font-weight: bold;
              }
              .index-name {
                  font-weight: bold;
              }
              .index {
                  font-size: small;
              }
              .protein-name {
                  font-family: "Courier New", Courier, monospace;
              }
              .stats {
                  font-family: "Courier New", Courier, monospace;
                  white-space: pre;
              }
              .protein-list {
                  margin-top: 2px;
              }''',
              '</style>',
              '</head>',
              '<body>',
          ]
  
          proteinFieldsDescription = [
              '<p>',
              'In all bullet point protein lists below, there are the following '
              'fields:',
              '<ol>',
              '<li>Coverage fraction.</li>',
              '<li>Median bit score.</li>',
              '<li>Best bit score.</li>',
              '<li>Read count.</li>',
              '<li>HSP count (a read can match a protein more than once).</li>',
              '<li>Protein length (in AAs).</li>',
              '<li>Index (just ignore this).</li>',
          ]
  
          if self._saveReadLengths:
              proteinFieldsDescription.append(
                  '<li>All read lengths (in parentheses).</li>')
  
          proteinFieldsDescription.extend([
              '<li>Protein name.</li>',
              '</ol>',
              '</p>',
          ])
  
          append = result.append
  
          append('<h1>Summary of pathogens</h1>')
          append('<p>')
          append(self._title())
  
          if self._pathogenProteinCount:
              percent = minProteinFraction * 100.0
              if nPathogenNames < len(self.pathogenNames):
                  if nPathogenNames == 1:
                      append('Pathogen protein fraction filtering has been '
                             'applied, so information on only 1 pathogen is '
                             'displayed. This is the only pathogen for which at '
                             'least one sample matches at least %.2f%% of the '
                             'pathogen proteins.' % percent)
                  else:
                      append('Pathogen protein fraction filtering has been '
                             'applied, so information on only %d pathogens is '
                             'displayed. These are the only pathogens for which '
                             'at least one sample matches at least %.2f%% of '
                             'the pathogen proteins.' % (nPathogenNames,
                                                         percent))
              else:
                  append('Pathogen protein fraction filtering has been applied, '
                         'but all pathogens have at least %.2f%% of their '
                         'proteins matched by at least one sample.' % percent)
  
              append('Samples that match a pathogen (and pathogens with a '
                     'matching sample) with at least this protein fraction are '
                     'highlighted using <span class="significant">%s</span>.' %
                     highlightSymbol)
  
          append('</p>')
  
          if pathogenPanelFilename:
              append('<p>')
              append('<a href="%s">Panel showing read count per pathogen, per '
                     'sample.</a>' % pathogenPanelFilename)
              append('Red vertical bars indicate samples with an unusually high '
                     'read count.')
              append('</p>')
  
          result.extend(proteinFieldsDescription)
  
          # Write a linked table of contents by pathogen.
          append('<p><span class="index-name">Pathogen index:</span>')
          append('<span class="index">')
          for pathogenName in pathogenNames:
              append('<a href="#pathogen-%s">%s</a>' % (pathogenName,
                                                        pathogenName))
              append('&middot;')
          # Get rid of final middle dot and add a period.
          result.pop()
          result[-1] += '.'
          append('</span></p>')
  
          # Write a linked table of contents by sample.
          append('<p><span class="index-name">Sample index:</span>')
          append('<span class="index">')
          for sampleName in sampleNames:
              append('<a href="#sample-%s">%s</a>' % (sampleName, sampleName))
              append('&middot;')
          # Get rid of final middle dot and add a period.
          result.pop()
          result[-1] += '.'
          append('</span></p>')
  
          # Write all pathogens (with samples (with proteins)).
          append('<hr>')
          append('<h1>Pathogens by sample</h1>')
  
          for pathogenName in pathogenNames:
              samples = self.pathogenNames[pathogenName]
              sampleCount = len(samples)
              pathogenProteinCount = self._pathogenProteinCount[pathogenName]
              if pathogenType == 'viral':
                  pathogenNameHTML = '<a href="%s%s">%s</a>' % (
                      self.VIRALZONE, quote(pathogenName), pathogenName)
              else:
                  pathogenNameHTML = pathogenName
              append(
                  '<a id="pathogen-%s"></a>'
                  '<p class="pathogen"><span class="pathogen-name">%s</span>'
                  '%s, was matched by %d sample%s:</p>' %
                  (pathogenName, pathogenNameHTML,
                   ((' (with %d protein%s)' %
                     (pathogenProteinCount,
                      '' if pathogenProteinCount == 1 else 's'))
                    if pathogenProteinCount else ''),
                   sampleCount,
                   '' if sampleCount == 1 else 's'))
              for sampleName in sorted(samples):
                  readsFileName = self.pathogenSampleFiles.lookup(
                      pathogenName, sampleName)
                  proteins = samples[sampleName]['proteins']
                  proteinCount = len(proteins)
                  uniqueReadCount = samples[sampleName]['uniqueReadCount']
                  if pathogenProteinCount and (
                          proteinCount / pathogenProteinCount >=
                          minProteinFraction):
                      highlight = ('<span class="significant">%s</span>' %
                                   highlightSymbol)
                  else:
                      highlight = ''
  
                  append(
                      '<p class="sample indented">'
                      '%sSample <a href="#sample-%s">%s</a> '
                      '(%d protein%s, <a href="%s">%d de-duplicated (by id) '
                      'read%s</a>, <a href="%s">panel</a>):</p>' %
                      (highlight, sampleName, sampleName,
                       proteinCount, '' if proteinCount == 1 else 's',
                       readsFileName,
                       uniqueReadCount, '' if uniqueReadCount == 1 else 's',
                       self.sampleNames[sampleName]))
                  append('<ul class="protein-list indented">')
                  for proteinName in sorted(proteins):
                      proteinMatch = proteins[proteinName]
                      append(
                          '<li>'
                          '<span class="stats">'
                          '%(coverage).2f %(medianScore)6.2f %(bestScore)6.2f '
                          '%(readCount)5d %(hspCount)5d %(proteinLength)4d '
                          '%(index)3d '
                          % proteinMatch
                      )
  
                      if self._saveReadLengths:
                          append('(%s) ' % ', '.join(
                              map(str, sorted(proteinMatch['readLengths']))))
  
                      append(
                          '</span> '
                          '<span class="protein-name">'
                          '%(proteinName)s'
                          '</span> '
                          '(<a href="%(bluePlotFilename)s">blue plot</a>, '
                          '<a href="%(readsFilename)s">reads</a>'
                          % proteinMatch)
  
                      if proteinMatch['proteinURL']:
                          # 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>' %
                                         proteinMatch['proteinURL'])
                      result[-1] += ')'
  
                      append('</li>')
  
                  append('</ul>')
  
          # Write all samples (with pathogens (with proteins)).
          append('<hr>')
          append('<h1>Samples by pathogen</h1>')
  
          for sampleName in sampleNames:
              samplePathogenNames = [
                  pathName for pathName in self.pathogenNames
                  if sampleName in self.pathogenNames[pathName]]
  
              append(
                  '<a id="sample-%s"></a>'
                  '<p class="sample">Sample <span class="sample-name">%s</span> '
                  'matched proteins from %d pathogen%s, '
                  '<a href="%s">panel</a>:</p>' %
                  (sampleName, sampleName, len(samplePathogenNames),
                   '' if len(samplePathogenNames) == 1 else 's',
                   self.sampleNames[sampleName]))
  
              for pathogenName in sorted(samplePathogenNames):
                  readsFileName = self.pathogenSampleFiles.lookup(pathogenName,
                                                                  sampleName)
                  proteins = self.pathogenNames[pathogenName][sampleName][
                      'proteins']
                  uniqueReadCount = self.pathogenNames[
                      pathogenName][sampleName]['uniqueReadCount']
                  proteinCount = len(proteins)
                  pathogenProteinCount = self._pathogenProteinCount[pathogenName]
  
                  highlight = ''
                  if pathogenProteinCount:
                      proteinCountStr = '%d/%d protein%s' % (
                          proteinCount, pathogenProteinCount,
                          '' if pathogenProteinCount == 1 else 's')
                      if (proteinCount / pathogenProteinCount >=
                              minProteinFraction):
                          highlight = ('<span class="significant">%s</span>' %
                                       highlightSymbol)
                  else:
                      proteinCountStr = '%d protein%s' % (
                          proteinCount, '' if proteinCount == 1 else 's')
  
                  append(
                      '<p class="sample indented">'
                      '%s<a href="#pathogen-%s">%s</a> %s, '
                      '<a href="%s">%d de-duplicated (by id) read%s</a>:</p>' %
                      (highlight, pathogenName, pathogenName,
                       proteinCountStr, readsFileName,
                       uniqueReadCount, '' if uniqueReadCount == 1 else 's'))
                  append('<ul class="protein-list indented">')
                  for proteinName in sorted(proteins):
                      proteinMatch = proteins[proteinName]
                      append(
                          '<li>'
                          '<span class="stats">'
                          '%(coverage).2f %(medianScore)6.2f %(bestScore)6.2f '
                          '%(readCount)5d %(hspCount)5d %(proteinLength)4d '
                          '%(index)3d '
                          '</span> '
                          '<span class="protein-name">'
                          '%(proteinName)s'
                          '</span> '
                          '(<a href="%(bluePlotFilename)s">blue plot</a>, '
                          '<a href="%(readsFilename)s">reads</a>'
                          % proteinMatch)
  
                      if proteinMatch['proteinURL']:
                          # 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>' %
                                         proteinMatch['proteinURL'])
                      result[-1] += ')'
  
                      append('</li>')
  
                  append('</ul>')
  
          append('</body>')
          append('</html>')
  
          return '\n'.join(result)