def combine_sj_out(
    fns, 
    external_db, 
    total_jxn_cov_cutoff=20, 
    define_sample_name=None,
    verbose=False,
):
    """Combine SJ.out.tab files from STAR by filtering based on coverage and
    comparing to an external annotation to discover novel junctions.

    Parameters
    ----------
    fns : list of strings 
        Filenames of SJ.out.tab files to combine.

    external_db : str
        Filename of splice junction information from external database. The file
        should have a header and contained the following columns  'gene',
        'chrom', 'start', 'end', 'strand', 'chrom:start', 'chrom:end', 'donor',
        'acceptor', 'intron'.

    total_jxn_cov_cutoff : int
        Discard junctions with less than this many reads summed over all
        samples.

    define_sample_name : function
        A function mapping the SJ.out.tab filenames to sample names.

    Returns
    -------
    countDF :  pandas.DataFrame
        Number of unique junction spanning reads for each junction that passed
        filtering criteria.

    annotDF : pandas.DataFrame
        Annotation information for junctions that passed filtering criteria.

    stats : list of strings
        Human readable statistics.
    
    """
    if verbose:
        import sys
    # I'll start by figuring out which junctions we will keep.
    counts = _total_jxn_counts(fns) 
    jxns = set(counts[counts >= total_jxn_cov_cutoff].index)
    if verbose:
        sys.stderr.write('Counting done\n')

    stats = []
    sj_outD = _make_sj_out_dict(fns, jxns=jxns,
                                define_sample_name=define_sample_name)
    stats.append('Number of junctions in SJ.out file per sample')
    for k in sj_outD.keys():
        stats.append('{0}\t{1:,}'.format(k, sj_outD[k].shape[0]))
    stats.append('')
    if verbose:
        sys.stderr.write('Dict done\n')

    sj_outP, annotDF = _make_sj_out_panel(sj_outD, total_jxn_cov_cutoff)
    stats.append('SJ.out panel size\t{0}'.format(sj_outP.shape))
    stats.append('')
    if verbose:
        sys.stderr.write('Panel done\n')

    extDF, ext_stats = read_external_annotation(external_db)
    stats += ext_stats
    stats.append('')
    if verbose:
        sys.stderr.write('DB read done\n')

    countsDF, annotDF, filter_stats = _filter_jxns_donor_acceptor(sj_outP, 
                                                                  annotDF, 
                                                                  extDF)
    if verbose:
        sys.stderr.write('Filter done\n')

    annotDF = _find_novel_donor_acceptor_dist(annotDF, extDF)
    if verbose:
        sys.stderr.write('Dist done\n')

    stats += filter_stats
    return countsDF, annotDF, stats
Combine SJ.out.tab files from STAR by filtering based on coverage and
    comparing to an external annotation to discover novel junctions.

    Parameters
    ----------
    fns : list of strings 
        Filenames of SJ.out.tab files to combine.

    external_db : str
        Filename of splice junction information from external database. The file
        should have a header and contained the following columns  'gene',
        'chrom', 'start', 'end', 'strand', 'chrom:start', 'chrom:end', 'donor',
        'acceptor', 'intron'.

    total_jxn_cov_cutoff : int
        Discard junctions with less than this many reads summed over all
        samples.

    define_sample_name : function
        A function mapping the SJ.out.tab filenames to sample names.

    Returns
    -------
    countDF :  pandas.DataFrame
        Number of unique junction spanning reads for each junction that passed
        filtering criteria.

    annotDF : pandas.DataFrame
        Annotation information for junctions that passed filtering criteria.

    stats : list of strings
        Human readable statistics.