docs: def phylogeny(sequences=None, project_dir=None, name=None, aln_file=None, tree_file=None,
          seq_field=None, name_field=None, aa=False, species='human', unrooted=False, ladderize=True,
          root=None, root_name=None, show_root_name=False, color_dict=None, color_function=None,
          order_dict=None, order_function=None, color_node_labels=False, label_colors=None,
          scale=None, branch_vert_margin=None, fontsize=12, show_names=True, show_scale=False,
          mirror=False, min_order_fraction=0.1, figname_prefix=None, figname_suffix=None,
          # linked_alignment=None, alignment_fontsize=11, alignment_height=50, alignment_width=50, 
          compact_alignment=False, scale_factor=1, rename_function=None, linewidth=1.0,
          delete_nodes=None, quiet=True):
      '''
      Generates a lineage phylogeny figure.
  
      Args:
  
          sequences (list(Sequence)): A list of ``Sequence`` objects from which a phylogeny
              will be calculated. Strictly speaking, they do not need to be ``Sequence`` objects,
              rather, any object that contains the sequence name as the ``id`` attribute (or
              by dictionary-style lookup using the provided ``name_field``) and contains the
              sequence as the ``sequence`` attribute (or by dictionary-stype lookup using the
              provided ``seq_field``).
  
          project_dir (str): directory into which all phylogeny files will be deposited,
              including alignment, tree and figure files.
  
          name (str): Name to be used for naming alignment, tree, and phylogeny files. If not
              provided, a random name will be generated.
  
          aln_file (str): if a multiple sequence alignment has already been calculated,
              passing the path to the alignment file (in FASTA format) will force Lineage.phylogeny()
              to use the supplied msa instead of computing a new one.
  
          tree_file (str): if a tree file has already been calculated, passing the path
              to the pre-computed tree file will force ``phylogeny()`` to use
              the supplied tree file instead of computing a new one. It is important to note that
              only sequence names will be parsed from the tree_file, so if ``order_function`` or
              ``color_function`` is also provided, ensure that these functions only require the
              sequence ID rather than the entire sequence.
  
          aa (bool): if True, use amino acid sequences to compute the phylogeny.
              Default is False.
  
          root (Sequence, str: The root can be provided either as a ``Sequence`` object (if ``sequences``
              are being provided) or as the name of a sequence that can be found either in
              ``sequences`` or in the provided ``aln_file`` or ``tree_file``. Note that if
              either ``aln_file`` or ``tree_file`` are provided, the root must be provided
              as the sequence name, not as a ``Sequence`` object (as the root sequence must
              already be included in either ``aln_file`` or ``tree_file``. If the root is not
              provided, the germline V-gene sequence of the
  
          color_dict (dict): Dictionary with sequence IDs as keys and colors (hex format) as values. If any
              sequence IDs are not found in the dict, they will be colored black. If neither ``color_dict`` nor
              ``color_function`` is provided, all leaves will be colored black.
  
          color_function (func): Function that that accepts a ``Sequence`` object and returns the color
              (as a hex code). If ``color_dict`` is also provided, ``color_function`` is ignored. Additionally,
              ``color_function`` will only be used if ``sequences`` are provided. If ``sequences`` are not provided
              (instead using ``aln_file` or ``tree_file``), ``color_dict`` must be used instead of ``color_function``.
  
          orders: a dictionary with sequence IDs as keys and orders (integers) as values.
              If not provided, only the leaf branches will be colored (if <colors> or
              <color_function> is provided).
  
          chain: build a phylogeny using the given chain ('heavy' or 'light').
              Default is 'heavy'.
  
          filter_function: function used to filter sequences (identity-based clustering, for
              example). The function should accept a list of Sequence objects and return
              a list of Sequence objects.
  
          just_pairs: if True, compute the phylogeny using only paired sequences.
              Default (False) will use all sequences of the appropriate chain, paired or not.
  
          scale (float): passed to ete3.TreeStyle() to set the scale of the tree figure. Increased
              scale results in a wider tree.
  
          branch_vert_margin (int): passed to ete3.TreeStyle() to set the branch_vertical_margin of
              the tree figure. Increased branch_vert_margin results in a taller tree.
  
          fontsize: size of the leaf labels. Default is 12.
  
          show_names: show names of leaf nodes. Options are True (show labels for all leaf nodes),
              False (don't show labels for any leaf nodes) or a list of sequence IDs for which
              labels should be shown. Default is True.
  
          mirror: flip the orientation of the tree. Default is to draw the tree from left to right.
              Setting mirror to True results in the tree being drawn from right to left.
  
          min_order_fraction: minimum fraction of downstream leaves requried to color a branch.
              When coloring non-leaf nodes, the earliest 'order' with at least <min_order_fraction>
              leaf nodes is used. Default is 0.1 (which corresponds to 10%).
  
          figname_prefix: by default, figures will be named <lineage_id>.pdf. If prefix='prefix_' and
              the lineage ID is 'ABC123', the figure file will be named 'prefix_ABC123.pdf'.
  
          figname_suffix: by default, figures will be named <lineage_id>.pdf. If suffix='_suffix' and
              the lineage ID is 'ABC123', the figure file will be named 'ABC123_suffix.pdf'.
      '''
  
      if project_dir is None:
          print('\nERROR: project_dir is required\n')
          sys.exit(1)
      else:
          project_dir = os.path.abspath(project_dir)
  
      # make a name if one isn't provided
      if name is None:
          name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8))
  
      # if sequences are provided, need to process them
      if sequences is not None and all([arg is None for arg in [aln_file, tree_file]]):
          sequences = deepcopy(sequences)
          root = copy(root)
  
          # if custom seq_field is specified, copy to the .sequence attribute
          if seq_field is not None:
              if not all([seq_field in list(s.annotations.keys()) for s in sequences]):
                  print('\nERROR: {} is not present in all of the supplied sequences.\n'.format(seq_field))
                  sys.exit(1)
              for s in sequences:
                  s.alignment_sequence = s[seq_field]
          else:
              for s in sequences:
                  s.alignment_sequence = s.sequence
  
          # if custom name_field is specified, copy to the .id attribute
          if name_field is not None:
              if not all([name_field in list(s.annotations.keys()) for s in sequences]):
                  print('\nERROR: {} is not present in all of the supplied sequences.\n'.format(name_field))
                  sys.exit(1)
              for s in sequences:
                  s.alignment_id = s[name_field]
          else:
              for s in sequences:
                  s.alignment_id = s.id
  
          # parse the root sequence
          if unrooted:
              root = None
              root_name = None
          elif root is None:
              if not quiet:
                  print('\nRoot sequence was was not provided. Using the germline V-gene.')
              if not all(['v_gene' in list(s.annotations.keys()) for s in sequences]):
                  print('\nInput sequences to not appear to be AbStar annotated. Annotating now...')
                  sequences = abstar.run(*[(s.id, s.sequence) for s in sequences])
                  print('Done.')
              if not all(['full' in list(s['v_gene'].keys()) for s in sequences]):
                  print('\nInput sequences to not appear to be AbStar annotated. Annotating now...')
                  sequences = abstar.run(*[(s.id, s.sequence) for s in sequences])
                  print('Done.')
              top_vgene = sorted(list(Counter([s['v_gene']['full'] for s in sequences]).items()),
                                 key=lambda x: x[1],
                                 reverse=True)[0][0]
              vgene = get_imgt_germlines(species, 'V', gene=top_vgene)
              if aa:
                  root = Sequence(vgene.ungapped_aa_sequence, id=top_vgene)
              else:
                  root = Sequence(vgene.ungapped_nt_sequence, id=top_vgene)
              root.alignment_id = root.id
              root.alignment_sequence = root.sequence
              if not quiet:
                  print('Top V-gene: {}'.format(root.alignment_id))
                  print(root.alignment_sequence)
          elif type(root) in STR_TYPES:
              root = [s for s in sequences if s.alignment_id == root][0]
              if not root:
                  print('\nERROR: The name of the root sequence ({}) was not found in the list of input sequences.'.format(root))
                  print('\n')
                  sys.exit(1)
              sequences = [s for s in sequences if s.alignment_id != root.alignment_id]
          elif type(root) == Sequence:
              if seq_field is not None:
                  if seq_field not in list(root.anotations.keys()):
                      print('\nERROR: {} is not present in the supplied root sequence.\n'.format(seq_field))
                      sys.exit(1)
                  root.alignment_sequence = root[seq_field]
              if name_field is not None:
                  if name_field not in list(root.anotations.keys()):
                      print('\nERROR: {} is not present in the supplied root sequence.\n'.format(name_field))
                      sys.exit(1)
                  root.alignment_id = root[name_field]
              sequences = [s for s in sequences if s.alignment_id != root.alignment_id]
          else:
              print('\nERROR: If root is provided, it must be the name of a sequence \
              found in the supplied list of sequences or it must be a Sequence object.')
              print('\n')
              sys.exit(1)
          if not unrooted:
              if root_name is not None:
                  root.alignment_id = root_name
              else:
                  root_name = root.alignment_id
              sequences.append(root)
  
      # parse sequences from aln_file, if provided
      elif aln_file is not None:
          if not unrooted and type(root) not in STR_TYPES:
              print('\nERROR: If providing an aln_file, the name of the root sequence must \
              be provided (as a string) using the root keyword argument')
              print('\n')
              sys.exit(1)
          _sequences = []
          _root = None
          for rec in AlignIO.read(open(aln_file), 'fasta'):
              s = str(rec.seq).replace('-', '')
              if rec.id == root:
                  _root = Sequence(s, rec.id)
                  _root.alignment_id = _root.id
              else:
                  _s = Sequence(s, id=rec.id)
                  _s.alignment_id = rec.id
                  _sequences.append(_s)
          if sequences is None:
              sequences = _sequences
          else:
              sequence_ids = [s.id for s in sequences]
              if any([_s.alignment_id not in sequence_ids for _s in _sequences]):
                  print('\nWARNING: Sequences were found in the alignment file that were not included \
                        in the input sequence list. This may cause problems.')
              for s in sequences:
                  s.alignment_id = s.id
                  s.alignment_sequence = s.sequence
          if unrooted:
              root = None
              root_name = None
          else:
              if _root is None:
                  print('\nERROR: The specified root ({}) was not found in the provided alignment file.'.format(root))
                  print('\n')
                  sys.exit(1)
              root = _root
              if root_name is not None:
                  root.alignment_id = root_name
              else:
                  root_name = root.alignment_id
              sequences = [s for s in sequences if all([s.alignment_id != name for name in [root.id, root.alignment_id]])]
              sequences.append(root)
  
      # parse sequences from tree_file, if provided
      elif tree_file is not None:
          if not unrooted and type(root) not in STR_TYPES:
              print('\nERROR: If providing a tree_file, the name of the root sequence must \
              be provided (as a string) using the root keyword argument')
              print('\n')
              sys.exit(1)
          _sequences = []
          _root = None
          tree = Phylo.read(open(tree_file), 'newick')
          for leaf in tree.get_terminals():
              s = ''
              if leaf.name == root:
                  _root = Sequence(s, leaf.name)
                  _root.alignment_id = _root.id
              else:
                  _s = Sequence(s, id=leaf.name)
                  _s.alignment_id = leaf.name
                  _sequences.append(_s)
          if sequences is None:
              sequences = _sequences
          else:
              sequence_ids = [s.id for s in sequences]
              if any([_s.alignment_id not in sequence_ids for _s in _sequences]):
                  print('\nWARNING: Sequences were found in the alignment file that were not included \
                        in the input sequence list. This may cause problems.')
              for s in sequences:
                  s.alignment_id = s.id
                  s.alignment_sequence = s.sequence
          if unrooted:
              root = None
              root_name = None
          elif _root is None:
              print('\nERROR: The specified root ({}) was not found in the provided tree file.'.format(root))
              print('\n')
              sys.exit(1)
          else:
              root = _root
              if root_name is not None:
                  root.alignment_id = root_name
              else:
                  root_name = root.alignment_id
              sequences = [s for s in sequences if all([s.alignment_id != name for name in [root.id, root.alignment_id]])]
              sequences.append(root)
  
      # set up colors and color ordering
      if order_dict is None:
          if order_function is not None:
              order_dict = {seq.alignment_id: order_function(seq) for seq in sequences}
      if color_dict is None:
          if color_function is not None:
              color_dict = {seq.alignment_id: color_function(seq) for seq in sequences}
      if color_dict is None:
          color_dict = {}
  
      # make msa (if necessary)
      if all([aln_file is None, tree_file is None]):
          aln_file = os.path.abspath(os.path.join(project_dir, '{}.aln'.format(name)))
          # muscle(seqs, aln_file, as_file=True)
          do_print = False if quiet else True
          if do_print:
              print('\n')
          seqs = [(s.alignment_id, s.alignment_sequence) for s in sequences]
          mafft(seqs, aln_file, as_file=True, print_stdout=do_print, print_stderr=do_print)
  
      # make treefile (if necessary)
      if tree_file is None:
          tree_file = os.path.abspath(os.path.join(project_dir, '{}.nw'.format(name)))
          fasttree(aln_file, tree_file, is_aa=aa, quiet=quiet)
  
      # make phylogeny
      prefix = '' if figname_prefix is None else figname_prefix
      suffix = '' if figname_suffix is None else figname_suffix
      fig_file = os.path.join(project_dir, '{}{}{}.pdf'.format(prefix, name, suffix))
      _make_tree_figure(tree_file,
                        fig_file,
                        color_dict,
                        order_dict,
                        None if root is None else root.alignment_id,
                        rename_function=rename_function,
                        show_names=show_names,
                        name_field=name_field,
                        branch_vert_margin=branch_vert_margin,
                        scale=scale,
                        color_node_labels=color_node_labels,
                        label_colors=label_colors,
                        show_root_name=show_root_name,
                        tree_orientation=1 if mirror else 0,
                        fontsize=fontsize,
                        min_order_fraction=min_order_fraction,
                      #   linked_alignment=linked_alignment,
                      #   alignment_fontsize=alignment_fontsize,
                      #   alignment_height=alignment_height,
                      #   alignment_width=alignment_width,
                        show_scale=show_scale,
                        compact_alignment=compact_alignment,
                        scale_factor=scale_factor,
                        linewidth=linewidth,
                        ladderize=ladderize,
                        delete_nodes=delete_nodes)