diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml new file mode 100644 index 0000000..e3a48c2 --- /dev/null +++ b/.github/workflows/python-tests.yml @@ -0,0 +1,57 @@ +name: Python unit tests +# Runs the pytest suite in tests/ against the FastOMA python package. +# Tests marked `slow` only run on the nightly schedule and on manual dispatch. +on: + push: + branches: + - dev + pull_request: + schedule: + - cron: "17 3 * * *" + workflow_dispatch: + +concurrency: + group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" + cancel-in-progress: true + +jobs: + pytest: + # pinned: the golden snapshots of tests/test_fungi30.py need the mafft/FastTree versions of this image + runs-on: ubuntu-24.04 + name: "pytest | python ${{ matrix.python-version }}" + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.12"] + + steps: + - name: Check out code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ matrix.python-version }} + + # same external tools as the runtime stage of the Dockerfile + - name: Install external tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends fasttree mafft mmseqs2 + mafft --version || true + FastTree -expert 2>&1 | head -1 || true + + - name: Install FastOMA + run: | + python -m pip install --upgrade pip + python -m pip install -e . pytest + + - name: Run tests + env: + # fail rather than skip the golden snapshots if the tool versions changed + FASTOMA_REQUIRE_GOLDEN: "1" + run: | + if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + python -m pytest -ra + else + python -m pytest -ra -m "not slow" + fi diff --git a/.gitignore b/.gitignore index ecb3eae..cab7718 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ archive .gitignore __pycache__ *.orig + +# nf-test working files +.nf-test/ +.nf-test.log diff --git a/nf-test.config b/nf-test.config index 7187828..9bd1e38 100644 --- a/nf-test.config +++ b/nf-test.config @@ -2,6 +2,9 @@ config { // location for all nf-tests testsDir "./" + // groovy helper classes available in all tests (e.g. FastomaOutput) + libDir "nf-tests/lib" + // nf-test directory including temporary files for each test workDir System.getenv("NFT_WORKDIR") ?: ".nf-test" diff --git a/nf-tests/default.nf.test b/nf-tests/default.nf.test index 51de684..144de9c 100644 --- a/nf-tests/default.nf.test +++ b/nf-tests/default.nf.test @@ -15,48 +15,26 @@ nextflow_pipeline { } then { - - // Custom function to filter timestamp lines - def filterTimestamps = { file -> - return file.readLines() - .findAll { !it.contains('!date-run') && !it.contains('originVersion') } - } + def out = file(params.output_folder).toPath() // stable_name: All files + folders in ${params.output_folder}/ with a stable name def stable_name = getAllFilesFromDir(params.output_folder, relative: true, includeDir: true, ignore: ['stats/*.{html,json,txt}']) // stable_path: All files in ${params.output_folder}/ with stable content def stable_path = getAllFilesFromDir(params.output_folder, ignoreFile: 'nf-tests/.nftignore') - def timestamp_files = getAllFilesFromDir(params.output_folder, include:['hogmap/*.hogmap', "FastOMA_HOGs.orthoxml"]).collect { file -> - [name: file.name, md5: listToMD5(filterTimestamps(file))] - } - def csv_files = getAllFilesFromDir(params.output_folder, include:['RootHOGs.tsv', 'OrthologousGroups.tsv', 'orthologs.tsv.gz']).collect { file -> - def csv_data = path(file.path).csv(sep: '\t', header: file.name != 'orthologs.tsv.gz', decompress: file.name.endsWith('.gz')) - def header = csv_data.getColumnNames() - def sorted_rows = csv_data.sort().rows - - // Convert back to list of strings - def lines = [] - lines.add(header.join('\t')) // Add header line - sorted_rows.each { row -> - lines.add(header.collect { col -> row[col] ?: '' }.join('\t')) - } - return [name: file.name, md5: listToMD5(lines), lineCount: lines.size()] - } - def fixed_line_number_files = getAllFilesFromDir(params.output_folder, include:['RootHOGs.tsv', 'OrthologousGroups.tsv', 'orthologs.tsv.gz', "FastOMA_HOGs.orthoxml", 'hogmap/*.hogmap']).collect { file -> - def line_count - if (file.name.endsWith('.gz')) { - // Use try-with-resources for proper cleanup - new java.util.zip.GZIPInputStream(file.newInputStream()).withCloseable { gzipStream -> - line_count = gzipStream.readLines().size() - } - } else { - line_count = file.readLines().size() - } - return [name: file.name, lineCount: line_count] + // hogmap scores have precision issues across platforms, so only their size is pinned + def hogmap_line_counts = getAllFilesFromDir(params.output_folder, include: ['hogmap/*.hogmap']).collect { f -> + [name: f.name, lineCount: f.readLines().size()] } + // HOG/OG numbers and row order are not stable, so the inferred orthology is + // compared through order- and id-independent views (nf-tests/lib/FastomaOutput.groovy) + def orthoxml = out.resolve('FastOMA_HOGs.orthoxml') + def geneRefCounts = FastomaOutput.geneRefCounts(orthoxml) + assertAll( - { assert workflow.success}, + { assert workflow.success }, + // every protein is in at most one HOG + { assert geneRefCounts.findAll { it.value > 1 } == [:] }, { assert snapshot( // Number of successful tasks workflow.trace.succeeded().size(), @@ -64,13 +42,14 @@ nextflow_pipeline { stable_name, // All files with stable contents stable_path, - // List of all files with timestamps filtered out - // does not yet work, as hogmap files have percision issues - // and FastOMA_HOGs is not completely deterministic - // timestamp_files - - // files with fixed number of lines - fixed_line_number_files + hogmap_line_counts, + [ + orthoxml_elements : FastomaOutput.elementCounts(orthoxml), + hogs : FastomaOutput.canonicalHogs(orthoxml), + roothogs : FastomaOutput.groupPartition(out.resolve('RootHOGs.tsv')), + orthologous_groups: FastomaOutput.groupPartition(out.resolve('OrthologousGroups.tsv')), + ortholog_pairs : FastomaOutput.pairs(out.resolve('orthologs.tsv.gz')), + ] ).match() } ) } diff --git a/nf-tests/default.nf.test.snap b/nf-tests/default.nf.test.snap index 5131a83..82a84d0 100644 --- a/nf-tests/default.nf.test.snap +++ b/nf-tests/default.nf.test.snap @@ -48,18 +48,6 @@ "species_tree_checked.nwk:md5,4c82a2f2ffa72a595aaf2fd3deddeff7" ], [ - { - "name": "FastOMA_HOGs.orthoxml", - "lineCount": 194 - }, - { - "name": "OrthologousGroups.tsv", - "lineCount": 28 - }, - { - "name": "RootHOGs.tsv", - "lineCount": 28 - }, { "name": "AQUAE.fa.hogmap", "lineCount": 20 @@ -71,17 +59,161 @@ { "name": "MYCGE.fa.hogmap", "lineCount": 14 - }, - { - "name": "orthologs.tsv.gz", - "lineCount": 18 } - ] + ], + { + "orthoxml_elements": { + "orthologGroup": 15, + "paralogGroup": 0, + "gene": 28, + "geneRef": 27 + }, + "hogs": [ + "OG[inter1](sp|O66686|TPIS_AQUAE, sp|O84332|TPIS_CHLTR)", + "OG[inter1](sp|O66778|ENO_AQUAE, sp|O84591|ENO_CHLTR)", + "OG[inter1](sp|O67547|SUCD_AQUAE, tr|O84829|O84829_CHLTR)", + "OG[inter1](sp|O67618|LEPA_AQUAE, sp|O84067|LEPA_CHLTR)", + "OG[inter1](sp|O67736|FOLD_AQUAE, sp|O84081|FOLD_CHLTR)", + "OG[inter1](sp|O67943|CH60_AQUAE, sp|P0C0Z7|CH60_CHLTR)", + "OG[inter2](OG[inter1](sp|O66429|EFTU_AQUAE, sp|P0CD71|EFTU_CHLTR), sp|P13927|EFTU_MYCGE)", + "OG[inter2](OG[inter1](sp|O67032|RF1_AQUAE, sp|O84026|RF1_CHLTR), sp|P47500|RF1_MYCGE)", + "OG[inter2](OG[inter1](sp|O67161|G3P_AQUAE, sp|P0CE13|G3P_CHLTR), sp|P47543|G3P_MYCGE)", + "OG[inter2](sp|O66907|ATPA_AQUAE, sp|P47641|ATPA_MYCGE)", + "OG[inter2](sp|O67828|ATPB_AQUAE, sp|P47639|ATPB_MYCGE)", + "OG[inter2](sp|P17821|DNAK_CHLTR, sp|P47547|DNAK_MYCGE)" + ], + "roothogs": [ + [ + "sp|O66429|EFTU_AQUAE", + "sp|P0CD71|EFTU_CHLTR", + "sp|P13927|EFTU_MYCGE" + ], + [ + "sp|O66686|TPIS_AQUAE", + "sp|O84332|TPIS_CHLTR" + ], + [ + "sp|O66778|ENO_AQUAE", + "sp|O84591|ENO_CHLTR" + ], + [ + "sp|O66907|ATPA_AQUAE", + "sp|P47641|ATPA_MYCGE" + ], + [ + "sp|O67032|RF1_AQUAE", + "sp|O84026|RF1_CHLTR", + "sp|P47500|RF1_MYCGE" + ], + [ + "sp|O67161|G3P_AQUAE", + "sp|P0CE13|G3P_CHLTR", + "sp|P47543|G3P_MYCGE" + ], + [ + "sp|O67547|SUCD_AQUAE", + "tr|O84829|O84829_CHLTR" + ], + [ + "sp|O67618|LEPA_AQUAE", + "sp|O84067|LEPA_CHLTR" + ], + [ + "sp|O67736|FOLD_AQUAE", + "sp|O84081|FOLD_CHLTR" + ], + [ + "sp|O67828|ATPB_AQUAE", + "sp|P47639|ATPB_MYCGE" + ], + [ + "sp|O67943|CH60_AQUAE", + "sp|P0C0Z7|CH60_CHLTR" + ], + [ + "sp|P17821|DNAK_CHLTR", + "sp|P47547|DNAK_MYCGE" + ] + ], + "orthologous_groups": [ + [ + "sp|O66429|EFTU_AQUAE", + "sp|P0CD71|EFTU_CHLTR", + "sp|P13927|EFTU_MYCGE" + ], + [ + "sp|O66686|TPIS_AQUAE", + "sp|O84332|TPIS_CHLTR" + ], + [ + "sp|O66778|ENO_AQUAE", + "sp|O84591|ENO_CHLTR" + ], + [ + "sp|O66907|ATPA_AQUAE", + "sp|P47641|ATPA_MYCGE" + ], + [ + "sp|O67032|RF1_AQUAE", + "sp|O84026|RF1_CHLTR", + "sp|P47500|RF1_MYCGE" + ], + [ + "sp|O67161|G3P_AQUAE", + "sp|P0CE13|G3P_CHLTR", + "sp|P47543|G3P_MYCGE" + ], + [ + "sp|O67547|SUCD_AQUAE", + "tr|O84829|O84829_CHLTR" + ], + [ + "sp|O67618|LEPA_AQUAE", + "sp|O84067|LEPA_CHLTR" + ], + [ + "sp|O67736|FOLD_AQUAE", + "sp|O84081|FOLD_CHLTR" + ], + [ + "sp|O67828|ATPB_AQUAE", + "sp|P47639|ATPB_MYCGE" + ], + [ + "sp|O67943|CH60_AQUAE", + "sp|P0C0Z7|CH60_CHLTR" + ], + [ + "sp|P17821|DNAK_CHLTR", + "sp|P47547|DNAK_MYCGE" + ] + ], + "ortholog_pairs": [ + "sp|O66429|EFTU_AQUAE <-> sp|P0CD71|EFTU_CHLTR", + "sp|O66429|EFTU_AQUAE <-> sp|P13927|EFTU_MYCGE", + "sp|O66686|TPIS_AQUAE <-> sp|O84332|TPIS_CHLTR", + "sp|O66778|ENO_AQUAE <-> sp|O84591|ENO_CHLTR", + "sp|O66907|ATPA_AQUAE <-> sp|P47641|ATPA_MYCGE", + "sp|O67032|RF1_AQUAE <-> sp|O84026|RF1_CHLTR", + "sp|O67032|RF1_AQUAE <-> sp|P47500|RF1_MYCGE", + "sp|O67161|G3P_AQUAE <-> sp|P0CE13|G3P_CHLTR", + "sp|O67161|G3P_AQUAE <-> sp|P47543|G3P_MYCGE", + "sp|O67547|SUCD_AQUAE <-> tr|O84829|O84829_CHLTR", + "sp|O67618|LEPA_AQUAE <-> sp|O84067|LEPA_CHLTR", + "sp|O67736|FOLD_AQUAE <-> sp|O84081|FOLD_CHLTR", + "sp|O67828|ATPB_AQUAE <-> sp|P47639|ATPB_MYCGE", + "sp|O67943|CH60_AQUAE <-> sp|P0C0Z7|CH60_CHLTR", + "sp|O84026|RF1_CHLTR <-> sp|P47500|RF1_MYCGE", + "sp|P0CD71|EFTU_CHLTR <-> sp|P13927|EFTU_MYCGE", + "sp|P0CE13|G3P_CHLTR <-> sp|P47543|G3P_MYCGE", + "sp|P17821|DNAK_CHLTR <-> sp|P47547|DNAK_MYCGE" + ] + } ], + "timestamp": "2026-10-20T14:21:18.662761", "meta": { - "nf-test": "0.9.3", - "nextflow": "26.04.6" - }, - "timestamp": "2026-08-25T18:07:32.287389" + "nf-test": "0.9.5", + "nextflow": "24.10.2" + } } } \ No newline at end of file diff --git a/nf-tests/helpers/input_archive.tar.gz b/nf-tests/helpers/input_archive.tar.gz new file mode 100644 index 0000000..f3261eb Binary files /dev/null and b/nf-tests/helpers/input_archive.tar.gz differ diff --git a/nf-tests/helpers/splice_in/AQUAE.splice b/nf-tests/helpers/splice_in/AQUAE.splice new file mode 100644 index 0000000..18ef280 --- /dev/null +++ b/nf-tests/helpers/splice_in/AQUAE.splice @@ -0,0 +1,2 @@ +sp|O66429|EFTU_AQUAE;sp|O67618|LEPA_AQUAE +sp|O66778|ENO_AQUAE diff --git a/nf-tests/lib/FastomaOutput.groovy b/nf-tests/lib/FastomaOutput.groovy new file mode 100644 index 0000000..8eccc4a --- /dev/null +++ b/nf-tests/lib/FastomaOutput.groovy @@ -0,0 +1,119 @@ +// Order- and id-independent views of FastOMA output files for nf-test snapshots. +// +// HOG/OG numbers and the row order of the TSV files depend on task completion and set +// iteration order, so the snapshots compare group memberships, ortholog pairs and the +// nesting of the orthoXML groups instead of raw file content. + +import java.nio.file.Path +import java.util.zip.GZIPInputStream +import javax.xml.parsers.DocumentBuilderFactory +import org.w3c.dom.Element +import org.w3c.dom.Node + +class FastomaOutput { + + static List readLines(Path file) { + if (file.toString().endsWith('.gz')) { + return new GZIPInputStream(file.newInputStream()).withReader { it.readLines() } + } + return file.readLines() + } + + // Groups of a "groupmember" TSV (RootHOGs.tsv, OrthologousGroups.tsv) as a sorted + // list of sorted member lists; the group names are dropped. + static List> groupPartition(Path tsv) { + def groups = [:].withDefault { [] as SortedSet } + readLines(tsv).drop(1).findAll { it }.each { line -> + def cols = line.split('\t') + groups[cols[0]] << cols[1] + } + return groups.values().collect { it as List }.sort { it.join(',') } + } + + // Unordered pairs of a two-column TSV (orthologs.tsv.gz) as sorted "a <-> b" strings. + static List pairs(Path tsv) { + return readLines(tsv).findAll { it }.collect { line -> + def p = line.split('\t')[0..1].sort() + "${p[0]} <-> ${p[1]}".toString() + }.sort() + } + + private static Element parseOrthoxml(Path orthoxml) { + def factory = DocumentBuilderFactory.newInstance() + factory.setNamespaceAware(true) + return factory.newDocumentBuilder().parse(orthoxml.toFile()).documentElement + } + + private static List childElements(Node node) { + def out = [] + def children = node.childNodes + for (int i = 0; i < children.length; i++) { + if (children.item(i) instanceof Element) { + out << (Element) children.item(i) + } + } + return out + } + + private static Map geneIdToProtId(Element root) { + def map = [:] + def genes = root.getElementsByTagNameNS('*', 'gene') + for (int i = 0; i < genes.length; i++) { + def g = (Element) genes.item(i) + map[g.getAttribute('id')] = g.getAttribute('protId') + } + return map + } + + private static String canonicalGroup(Element elem, Map id2prot) { + switch (elem.localName) { + case 'geneRef': + return id2prot.get(elem.getAttribute('id'), elem.getAttribute('id')) + case 'orthologGroup': + case 'paralogGroup': + def taxRange = childElements(elem).find { + it.localName == 'property' && it.getAttribute('name') == 'TaxRange' + }?.getAttribute('value') + def children = childElements(elem) + .findAll { it.localName in ['orthologGroup', 'paralogGroup', 'geneRef'] } + .collect { canonicalGroup(it, id2prot) } + .sort() + def label = elem.localName == 'orthologGroup' ? "OG[${taxRange}]" : (taxRange ? "PG[${taxRange}]" : 'PG') + return "${label}(${children.join(', ')})".toString() + default: + return '' + } + } + + // One string per rootHOG describing the nesting of ortholog/paralog groups and their + // genes (as protIds); group ids, scores and properties other than TaxRange are dropped. + static List canonicalHogs(Path orthoxml) { + def root = parseOrthoxml(orthoxml) + def id2prot = geneIdToProtId(root) + def groups = childElements(root).find { it.localName == 'groups' } + if (groups == null) { + return [] + } + return childElements(groups).collect { canonicalGroup(it, id2prot) }.sort() + } + + // How often each protein is referenced from the groups; a protein must appear at most once. + static Map geneRefCounts(Path orthoxml) { + def root = parseOrthoxml(orthoxml) + def id2prot = geneIdToProtId(root) + def counts = [:].withDefault { 0 } + def refs = root.getElementsByTagNameNS('*', 'geneRef') + for (int i = 0; i < refs.length; i++) { + def id = ((Element) refs.item(i)).getAttribute('id') + counts[id2prot.get(id, id)] += 1 + } + return counts + } + + static Map elementCounts(Path orthoxml) { + def root = parseOrthoxml(orthoxml) + return ['orthologGroup', 'paralogGroup', 'gene', 'geneRef'].collectEntries { name -> + [name, root.getElementsByTagNameNS('*', name).length] + } + } +} diff --git a/nf-tests/options.nf.test b/nf-tests/options.nf.test new file mode 100644 index 0000000..f7b8f43 --- /dev/null +++ b/nf-tests/options.nf.test @@ -0,0 +1,107 @@ +nextflow_pipeline { + + name "Test pipeline FastOMA with non-default options" + script "../FastOMA.nf" + tag "pipeline" + tag "pipeline_options" + + test("id transformer, MSA/gene tree output and disabled scores") { + + when { + params { + outdir = "$outputDir" + output_folder = "$outputDir" + msa_folder = "$outputDir/msa" + genetrees_folder = "$outputDir/genetrees" + fasta_header_id_transformer = "UniProt" + write_msas = true + write_genetrees = true + // the report needs the CompletenessScore, so only the other two are disabled here + disable_implied_losses_score = true + disable_tcs_score = true + } + } + + then { + def out = file(params.output_folder).toPath() + def orthoxml = out.resolve('FastOMA_HOGs.orthoxml') + def orthoxml_text = orthoxml.text + def roothogs = FastomaOutput.groupPartition(out.resolve('RootHOGs.tsv')) + + assertAll( + { assert workflow.success }, + // UniProt transformer: "sp|P13927|EFTU_MYCGE" -> "P13927" in all outputs + { assert roothogs.flatten().every { it ==~ /[A-Z0-9]{6,10}/ } }, + { assert FastomaOutput.canonicalHogs(orthoxml).every { !it.contains('|') } }, + { assert orthoxml_text.contains('CompletenessScore') }, + { assert !orthoxml_text.contains('ImpliedLosses') }, + { assert !orthoxml_text.contains('TCSScore') }, + { assert file("${params.msa_folder}").list().size() > 0 }, + { assert file("${params.genetrees_folder}").list().size() > 0 }, + { assert snapshot( + roothogs, + FastomaOutput.groupPartition(out.resolve('OrthologousGroups.tsv')), + FastomaOutput.pairs(out.resolve('orthologs.tsv.gz')), + ).match() } + ) + } + } + + test("archive input with precomputed hogmaps") { + + when { + params { + outdir = "$outputDir" + output_folder = "$outputDir" + hogmap_folder = "$outputDir/hogmap" + input = "${projectDir}/nf-tests/helpers/input_archive.tar.gz" + } + } + + then { + def out = file(params.output_folder).toPath() + def hogmaps = file("${params.hogmap_folder}").listFiles().findAll { it.name.endsWith('.hogmap') } + + assertAll( + { assert workflow.success }, + { assert hogmaps.size() == 3 }, + // the precomputed hogmaps from the archive are used, omamer is not re-run + { assert hogmaps.every { it.text.contains('precomputed-fixture-marker') } }, + { assert snapshot( + FastomaOutput.groupPartition(out.resolve('RootHOGs.tsv')), + FastomaOutput.canonicalHogs(out.resolve('FastOMA_HOGs.orthoxml')), + ).match() } + ) + } + } + + test("splice files select one isoform per gene") { + + when { + params { + outdir = "$outputDir" + output_folder = "$outputDir" + // AQUAE.splice declares EFTU_AQUAE and LEPA_AQUAE as isoforms of one gene + splice_folder = "${projectDir}/nf-tests/helpers/splice_in" + } + } + + then { + def out = file(params.output_folder).toPath() + def orthoxml = out.resolve('FastOMA_HOGs.orthoxml') + def referenced = FastomaOutput.geneRefCounts(orthoxml).keySet() + def isoforms = ['sp|O66429|EFTU_AQUAE', 'sp|O67618|LEPA_AQUAE'] + + assertAll( + { assert workflow.success }, + // exactly one isoform of the gene ends up in the HOGs + { assert referenced.intersect(isoforms).size() == 1 }, + { assert orthoxml.text.contains('main_isoform=') }, + { assert snapshot( + FastomaOutput.groupPartition(out.resolve('RootHOGs.tsv')), + FastomaOutput.canonicalHogs(orthoxml), + ).match() } + ) + } + } +} diff --git a/nf-tests/options.nf.test.snap b/nf-tests/options.nf.test.snap new file mode 100644 index 0000000..ee0be17 --- /dev/null +++ b/nf-tests/options.nf.test.snap @@ -0,0 +1,288 @@ +{ + "id transformer, MSA/gene tree output and disabled scores": { + "content": [ + [ + [ + "O66429", + "P0CD71", + "P13927" + ], + [ + "O66686", + "O84332" + ], + [ + "O66778", + "O84591" + ], + [ + "O66907", + "P47641" + ], + [ + "O67032", + "O84026", + "P47500" + ], + [ + "O67161", + "P0CE13", + "P47543" + ], + [ + "O67547", + "O84829" + ], + [ + "O67618", + "O84067" + ], + [ + "O67736", + "O84081" + ], + [ + "O67828", + "P47639" + ], + [ + "O67943", + "P0C0Z7" + ], + [ + "P17821", + "P47547" + ] + ], + [ + [ + "O66429", + "P0CD71", + "P13927" + ], + [ + "O66686", + "O84332" + ], + [ + "O66778", + "O84591" + ], + [ + "O66907", + "P47641" + ], + [ + "O67032", + "O84026", + "P47500" + ], + [ + "O67161", + "P0CE13", + "P47543" + ], + [ + "O67547", + "O84829" + ], + [ + "O67618", + "O84067" + ], + [ + "O67736", + "O84081" + ], + [ + "O67828", + "P47639" + ], + [ + "O67943", + "P0C0Z7" + ], + [ + "P17821", + "P47547" + ] + ], + [ + "O66429 <-> P0CD71", + "O66429 <-> P13927", + "O66686 <-> O84332", + "O66778 <-> O84591", + "O66907 <-> P47641", + "O67032 <-> O84026", + "O67032 <-> P47500", + "O67161 <-> P0CE13", + "O67161 <-> P47543", + "O67547 <-> O84829", + "O67618 <-> O84067", + "O67736 <-> O84081", + "O67828 <-> P47639", + "O67943 <-> P0C0Z7", + "O84026 <-> P47500", + "P0CD71 <-> P13927", + "P0CE13 <-> P47543", + "P17821 <-> P47547" + ] + ], + "timestamp": "2026-10-20T14:23:51.250062", + "meta": { + "nf-test": "0.9.5", + "nextflow": "24.10.2" + } + }, + "archive input with precomputed hogmaps": { + "content": [ + [ + [ + "sp|O66429|EFTU_AQUAE", + "sp|P0CD71|EFTU_CHLTR", + "sp|P13927|EFTU_MYCGE" + ], + [ + "sp|O66686|TPIS_AQUAE", + "sp|O84332|TPIS_CHLTR" + ], + [ + "sp|O66778|ENO_AQUAE", + "sp|O84591|ENO_CHLTR" + ], + [ + "sp|O66907|ATPA_AQUAE", + "sp|P47641|ATPA_MYCGE" + ], + [ + "sp|O67032|RF1_AQUAE", + "sp|O84026|RF1_CHLTR", + "sp|P47500|RF1_MYCGE" + ], + [ + "sp|O67161|G3P_AQUAE", + "sp|P0CE13|G3P_CHLTR", + "sp|P47543|G3P_MYCGE" + ], + [ + "sp|O67547|SUCD_AQUAE", + "tr|O84829|O84829_CHLTR" + ], + [ + "sp|O67618|LEPA_AQUAE", + "sp|O84067|LEPA_CHLTR" + ], + [ + "sp|O67736|FOLD_AQUAE", + "sp|O84081|FOLD_CHLTR" + ], + [ + "sp|O67828|ATPB_AQUAE", + "sp|P47639|ATPB_MYCGE" + ], + [ + "sp|O67943|CH60_AQUAE", + "sp|P0C0Z7|CH60_CHLTR" + ], + [ + "sp|P17821|DNAK_CHLTR", + "sp|P47547|DNAK_MYCGE" + ] + ], + [ + "OG[inter1](sp|O66686|TPIS_AQUAE, sp|O84332|TPIS_CHLTR)", + "OG[inter1](sp|O66778|ENO_AQUAE, sp|O84591|ENO_CHLTR)", + "OG[inter1](sp|O67547|SUCD_AQUAE, tr|O84829|O84829_CHLTR)", + "OG[inter1](sp|O67618|LEPA_AQUAE, sp|O84067|LEPA_CHLTR)", + "OG[inter1](sp|O67736|FOLD_AQUAE, sp|O84081|FOLD_CHLTR)", + "OG[inter1](sp|O67943|CH60_AQUAE, sp|P0C0Z7|CH60_CHLTR)", + "OG[inter2](OG[inter1](sp|O66429|EFTU_AQUAE, sp|P0CD71|EFTU_CHLTR), sp|P13927|EFTU_MYCGE)", + "OG[inter2](OG[inter1](sp|O67032|RF1_AQUAE, sp|O84026|RF1_CHLTR), sp|P47500|RF1_MYCGE)", + "OG[inter2](OG[inter1](sp|O67161|G3P_AQUAE, sp|P0CE13|G3P_CHLTR), sp|P47543|G3P_MYCGE)", + "OG[inter2](sp|O66907|ATPA_AQUAE, sp|P47641|ATPA_MYCGE)", + "OG[inter2](sp|O67828|ATPB_AQUAE, sp|P47639|ATPB_MYCGE)", + "OG[inter2](sp|P17821|DNAK_CHLTR, sp|P47547|DNAK_MYCGE)" + ] + ], + "timestamp": "2026-10-20T14:24:13.332548", + "meta": { + "nf-test": "0.9.5", + "nextflow": "24.10.2" + } + }, + "splice files select one isoform per gene": { + "content": [ + [ + [ + "sp|O66686|TPIS_AQUAE", + "sp|O84332|TPIS_CHLTR" + ], + [ + "sp|O66778|ENO_AQUAE", + "sp|O84591|ENO_CHLTR" + ], + [ + "sp|O66907|ATPA_AQUAE", + "sp|P47641|ATPA_MYCGE" + ], + [ + "sp|O67032|RF1_AQUAE", + "sp|O84026|RF1_CHLTR", + "sp|P47500|RF1_MYCGE" + ], + [ + "sp|O67161|G3P_AQUAE", + "sp|P0CE13|G3P_CHLTR", + "sp|P47543|G3P_MYCGE" + ], + [ + "sp|O67547|SUCD_AQUAE", + "tr|O84829|O84829_CHLTR" + ], + [ + "sp|O67618|LEPA_AQUAE", + "sp|O84067|LEPA_CHLTR" + ], + [ + "sp|O67736|FOLD_AQUAE", + "sp|O84081|FOLD_CHLTR" + ], + [ + "sp|O67828|ATPB_AQUAE", + "sp|P47639|ATPB_MYCGE" + ], + [ + "sp|O67943|CH60_AQUAE", + "sp|P0C0Z7|CH60_CHLTR" + ], + [ + "sp|P0CD71|EFTU_CHLTR", + "sp|P13927|EFTU_MYCGE" + ], + [ + "sp|P17821|DNAK_CHLTR", + "sp|P47547|DNAK_MYCGE" + ] + ], + [ + "OG[inter1](sp|O66686|TPIS_AQUAE, sp|O84332|TPIS_CHLTR)", + "OG[inter1](sp|O66778|ENO_AQUAE, sp|O84591|ENO_CHLTR)", + "OG[inter1](sp|O67547|SUCD_AQUAE, tr|O84829|O84829_CHLTR)", + "OG[inter1](sp|O67618|LEPA_AQUAE, sp|O84067|LEPA_CHLTR)", + "OG[inter1](sp|O67736|FOLD_AQUAE, sp|O84081|FOLD_CHLTR)", + "OG[inter1](sp|O67943|CH60_AQUAE, sp|P0C0Z7|CH60_CHLTR)", + "OG[inter2](OG[inter1](sp|O67032|RF1_AQUAE, sp|O84026|RF1_CHLTR), sp|P47500|RF1_MYCGE)", + "OG[inter2](OG[inter1](sp|O67161|G3P_AQUAE, sp|P0CE13|G3P_CHLTR), sp|P47543|G3P_MYCGE)", + "OG[inter2](sp|O66907|ATPA_AQUAE, sp|P47641|ATPA_MYCGE)", + "OG[inter2](sp|O67828|ATPB_AQUAE, sp|P47639|ATPB_MYCGE)", + "OG[inter2](sp|P0CD71|EFTU_CHLTR, sp|P13927|EFTU_MYCGE)", + "OG[inter2](sp|P17821|DNAK_CHLTR, sp|P47547|DNAK_MYCGE)" + ] + ], + "timestamp": "2026-10-20T14:24:53.930715", + "meta": { + "nf-test": "0.9.5", + "nextflow": "24.10.2" + } + } +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ee77fb8..952b7b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,3 +52,11 @@ Homepage = "https://github.com/DessimozLab/FastOMA" include = [ "/FastOMA", ] + +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = [ + "tools(*names): needs external binaries (mafft, FastTree, mmseqs, omamer); skipped if missing", + "slow: takes more than a few seconds; deselect with -m 'not slow'", +] +xfail_strict = true diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5e3cc21 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,137 @@ +"""Shared pytest fixtures for the FastOMA unit tests. + +Markers (registered in pyproject.toml): + tools -- needs external binaries (mafft, FastTree, mmseqs, omamer); skipped if missing + slow -- takes more than a few seconds; deselect with ``-m "not slow"`` +""" +import os +import shutil +import sys +from pathlib import Path + +import pytest + +TESTS_DIR = Path(__file__).parent +REPO_DIR = TESTS_DIR.parent +DATA_DIR = TESTS_DIR / "data" +TESTDATA_DIR = REPO_DIR / "testdata" + +# make tests/fastoma_testutils.py importable from every test module +sys.path.insert(0, str(TESTS_DIR)) + +REQUIRED_TOOLS = {"mafft": "mafft", "FastTree": "FastTree", "mmseqs": "mmseqs", "omamer": "omamer"} + + +def pytest_collection_modifyitems(config, items): + """Skip tests marked ``tools`` when the binaries they name are missing. + + Usage: ``@pytest.mark.tools("mafft", "FastTree")``; without arguments all tools are required. + """ + for item in items: + marker = item.get_closest_marker("tools") + if marker is None: + continue + needed = marker.args or tuple(REQUIRED_TOOLS) + missing = [t for t in needed if shutil.which(REQUIRED_TOOLS.get(t, t)) is None] + if missing: + item.add_marker(pytest.mark.skip(reason="missing external tool(s): " + ", ".join(missing))) + + +class _ParsedArgs(Exception): + def __init__(self, conf): + self.conf = conf + + +def _capture_cli_conf(monkeypatch, module, entry_point, argv): + """Run a fastoma CLI entry point only up to argument parsing and return its Namespace. + + All entry points call ``setup_logging(conf.v, ...)`` right after ``parse_args()``; replacing it + with a function that raises lets us get the real argparse defaults without running anything. + """ + def stop(*args, **kwargs): + raise _ParsedArgs(stop.conf) + + import argparse + orig_parse_args = argparse.ArgumentParser.parse_args + + def parse_args(self, *args, **kwargs): + stop.conf = orig_parse_args(self, *args, **kwargs) + return stop.conf + + monkeypatch.setattr(argparse.ArgumentParser, "parse_args", parse_args) + monkeypatch.setattr(module, "setup_logging", stop) + monkeypatch.setattr(sys, "argv", [entry_point] + list(argv)) + try: + getattr(module, entry_point.replace("-", "_"))() + except _ParsedArgs as e: + return e.conf + raise AssertionError(f"{entry_point} did not call setup_logging after parsing arguments") + + +@pytest.fixture +def subhog_conf(monkeypatch): + """Factory for the configuration Namespace of fastoma-infer-subhogs with the real CLI defaults. + + Keyword arguments override single fields, e.g. ``subhog_conf(number_of_samples_per_hog=3)``. + """ + from FastOMA import infer_subhogs + + with monkeypatch.context() as m: + base = _capture_cli_conf(m, infer_subhogs, "fastoma-infer-subhogs", + ["--input-rhog-folder", "rhogs", "--species-tree", "species_tree.nwk"]) + + def make(**overrides): + conf = type(base)(**vars(base)) + for key, value in overrides.items(): + if not hasattr(conf, key): + raise AttributeError(f"infer-subhogs has no option {key!r}") + setattr(conf, key, value) + return conf + return make + + +@pytest.fixture +def roothog_conf(monkeypatch): + """Factory for the configuration Namespace of fastoma-infer-roothogs with the real CLI defaults.""" + from FastOMA import infer_roothogs + + with monkeypatch.context() as m: + base = _capture_cli_conf(m, infer_roothogs, "fastoma-infer-roothogs", + ["--proteomes", "proteome", "--out-rhog-folder", "omamer_rhogs"]) + + def make(**overrides): + conf = type(base)(**vars(base)) + for key, value in overrides.items(): + if not hasattr(conf, key): + raise AttributeError(f"infer-roothogs has no option {key!r}") + setattr(conf, key, value) + return conf + return make + + +@pytest.fixture(autouse=True) +def reset_hogid_counter(): + """HOG ids come from a class-level counter; reset it so every test sees the same ids.""" + from FastOMA._hog_class import HOG + saved = HOG._hogid_iter + HOG._hogid_iter = 10000 + yield + HOG._hogid_iter = saved + + +@pytest.fixture +def in_tmp_dir(tmp_path, monkeypatch): + """Run the test inside a fresh temporary directory (many functions write into the CWD).""" + monkeypatch.chdir(tmp_path) + return tmp_path + + +@pytest.fixture +def fixed_hashseed(monkeypatch): + """Environment for subprocesses with a fixed PYTHONHASHSEED. + + Several FastOMA steps iterate over sets, so their output depends on the hash seed + (see test plan). Subprocess-based tests use this to stay reproducible. + """ + env = dict(os.environ, PYTHONHASHSEED="0") + return env diff --git a/tests/data/fungi30/README.md b/tests/data/fungi30/README.md new file mode 100644 index 0000000..045eeaa --- /dev/null +++ b/tests/data/fungi30/README.md @@ -0,0 +1,41 @@ +# fungi-30 rootHOG fixture + +23 rootHOGs (about 1,700 proteins) taken from a full FastOMA run on the fungi-30 dataset (`-profile fungi`, +FastOMA 0.5.1, `dessimozlab/fastoma:dev` image, November 2026) and used by `tests/test_fungi30.py`. + +- `rhogs/HOG_.fa.gz`: the rootHOG FASTA files exactly as `fastoma-infer-roothogs` wrote them in that run + (protein ids `||||`). The sequences are from UniProt reference proteomes + (CC BY 4.0). +- `species_tree.nwk`: the `species_tree_checked.nwk` of that run (all 30 proteomes, with polytomies). +- `families.tsv`: why each rootHOG was selected, its tier (`fast` runs on every PR, `slow` nightly), whether + it was run with `--parallel` (`par`, i.e. a `hog_big` rootHOG) and its kind: + - `golden`: the output is compared with `golden/.txt`. + - `drops`: `fastoma-infer-subhogs` drops proteins of this rootHOG (known bug). It is only checked for the + "no protein dropped" invariant, which is a strict xfail until the bug is fixed. How many proteins are + dropped changes with small MSA differences (e.g. 367 vs 11 of 496 for F0412257 with mafft 7.505 vs + 7.526), so these rootHOGs are not snapshotted. +- `golden/`: one text snapshot per golden rootHOG (nesting, TaxRange and scores of the groups passed on to + `collect-subhogs`) and `ENVIRONMENT.json` with the tool versions they were generated with. + +## Selection + +Every rootHOG of the run was classified from the `hog_big`/`hog_rest` task logs (conflict resolution, +split_hog, MSA trimming, fragment detection, dropped proteins, runtime) and the final orthoXML (nesting +depth, paralogGroups, scores). In that run, 414 of 22,878 rootHOGs dropped 8,113 of 265,464 proteins. + +Running these rootHOGs in the `dessimozlab/fastoma:dev` image reproduced the results of the full run +exactly. With mafft 7.526 instead of 7.505, clust12762 and all large rootHOGs gave different results. + +## Snapshots are not correctness + +The snapshots record current behaviour, including known bugs (e.g. the ImpliedLosses values). When a change +of the output is intended, regenerate them and review the diff: + +```bash +python tests/fungi30.py regenerate # needs docker; --tier fast|slow|all +git diff tests/data/fungi30/golden +``` + +Regeneration runs the working-tree code inside the container image, so the snapshots match CI (Ubuntu 24.04 +ships the same mafft 7.505 / FastTree 2.1.11 builds). With other tool versions, the snapshot tests are skipped +(or fail with `FASTOMA_REQUIRE_GOLDEN=1`, as in CI). diff --git a/tests/data/fungi30/families.tsv b/tests/data/fungi30/families.tsv new file mode 100644 index 0000000..b0fe0ff --- /dev/null +++ b/tests/data/fungi30/families.tsv @@ -0,0 +1,24 @@ +rhog tier mode kind reason +F0489906 fast seq golden conflict resolved without dropping proteins +F0459413 fast seq golden two conflicts resolved, nesting depth 10, 12 paralogGroups +F0476910 fast seq golden four conflicts resolved +F0413304 fast seq golden split_hog succeeds +F1085561 fast seq golden split_hog succeeds six times +F0477229 fast seq golden MSA row trimming removes six sequences +F0478382 fast seq golden fragments detected in the merged MSA +F0848294 fast seq golden 1:1 family over all 30 species, nesting depth 9 +F0480898 fast seq golden eight nested duplications +clust12762 fast seq golden linclust rootHOG with eight duplications; sensitive to the mafft version +F0485529 fast seq golden single-species rootHOG of two in-paralogs; nothing is reported +F0455853 fast seq drops conflict resolution drops 1 of 12 proteins +F0453717 fast seq drops three conflicts, drops 2 of 19 proteins +F0457125 fast seq drops MSA row trimming, drops 2 of 9 proteins +F0477170 fast seq drops MSA row trimming, drops 4 of 17 proteins +F0450975 fast seq drops drops 1 of 7 proteins without conflict or row trimming +F0455646 fast seq drops drops 1 of 9 proteins without conflict or row trimming +F0848191 slow seq golden large family over all 30 species (175 proteins) +F0453151 slow par golden performance canary: 101 proteins took 157s in the fungi-30 run +F0489395 slow seq drops two conflicts, drops 143 of 193 proteins in the fungi-30 run +F0442015 slow seq drops MSA row trimming, drops 90 of 186 proteins in the fungi-30 run +F0412257 slow par drops a single conflict drops 367 of 496 proteins in the fungi-30 run +F0470062 slow par drops hog_big rootHOG run with --parallel, drops 53 of 221 proteins in the fungi-30 run diff --git a/tests/data/fungi30/golden/ENVIRONMENT.json b/tests/data/fungi30/golden/ENVIRONMENT.json new file mode 100644 index 0000000..2dcaec8 --- /dev/null +++ b/tests/data/fungi30/golden/ENVIRONMENT.json @@ -0,0 +1,5 @@ +{ + "mafft": "v7.505 (2022/Apr/10)", + "FastTree": "2.1.11 Double precision (No SSE3)", + "image": "dessimozlab/fastoma:dev" +} diff --git a/tests/data/fungi30/golden/F0413304.txt b/tests/data/fungi30/golden/F0413304.txt new file mode 100644 index 0000000..eba9aca --- /dev/null +++ b/tests/data/fungi30/golden/F0413304.txt @@ -0,0 +1,10 @@ +# 4 input proteins, 2 groups, 4 proteins in groups + +orthologGroup Fungi CompletenessScore=0.0667 ImpliedLosses=2 TCSScore=0.0 + tr|A0A1Y2ETJ6|A0A1Y2ETJ6_9FUNG + tr|A0AA35IW45|A0AA35IW45_SACMI + +orthologGroup Fungi CompletenessScore=0.0667 ImpliedLosses=3 TCSScore=0.0 + paralogGroup + tr|A0A1Y2AI63|A0A1Y2AI63_9FUNG + tr|A0ABQ8PQ38|A0ABQ8PQ38_9FUNG diff --git a/tests/data/fungi30/golden/F0453151.txt b/tests/data/fungi30/golden/F0453151.txt new file mode 100644 index 0000000..a72b62e --- /dev/null +++ b/tests/data/fungi30/golden/F0453151.txt @@ -0,0 +1,137 @@ +# 101 input proteins, 1 groups, 86 proteins in groups + +orthologGroup Fungi CompletenessScore=0.6667 ImpliedLosses=120 TCSScore=20.314 + orthologGroup Dikarya CompletenessScore=0.6923 ImpliedLosses=118 TCSScore=17.4933 + orthologGroup Basidiomycota CompletenessScore=0.375 ImpliedLosses=3 TCSScore=2.2 + paralogGroup + orthologGroup Ustilaginomycotina CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.6667 + paralogGroup + tr|A0A0F7S8K2|A0A0F7S8K2_9BASI + tr|A0A0F7SAF1|A0A0F7SAF1_9BASI + tr|A0A0P1BDW4|A0A0P1BDW4_9BASI + tr|A0A0F7S400|A0A0F7S400_9BASI + tr|M7XCB9|M7XCB9_RHOT1 + paralogGroup + orthologGroup Sordariomycetes CompletenessScore=0.4444 ImpliedLosses=4 TCSScore=1.5 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A7J6IG66|A0A7J6IG66_COLFN + tr|A0AAX4IHJ5|A0AAX4IHJ5_9PEZI + orthologGroup Sordariomycetidae CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5HUN9|A0A2P5HUN9_DIAHE + tr|A0ABQ8NNB3|A0ABQ8NNB3_PYRGI + orthologGroup leotiomyceta CompletenessScore=0.25 ImpliedLosses=8 TCSScore=4.25 + orthologGroup Hypocreales CompletenessScore=0.75 ImpliedLosses=2 TCSScore=0.6667 + orthologGroup Fusarium CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SPL0|A0A395SPL0_FUSSP + tr|A0A8H5KVI5|A0A8H5KVI5_GIBSU + tr|G9N0Q0|G9N0Q0_HYPVG + tr|F2PYR0|F2PYR0_TRIEC + orthologGroup leotiomyceta CompletenessScore=0.9375 ImpliedLosses=101 TCSScore=5.0161 + paralogGroup + orthologGroup Eurotiomycetes CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.4 + orthologGroup Eurotiomycetidae CompletenessScore=1.0 ImpliedLosses=3 TCSScore=1.0 + orthologGroup Aspergillaceae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1L9VRU9|A0A1L9VRU9_ASPGL + tr|A0A1V6PLF3|A0A1V6PLF3_PENDC + orthologGroup Onygenales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1B7NX54|A0A1B7NX54_9EURO + tr|F2Q0I0|F2Q0I0_TRIEC + tr|A0A0D2FPJ6|A0A0D2FPJ6_CLAB1 + paralogGroup + orthologGroup Eurotiomycetidae CompletenessScore=0.75 ImpliedLosses=6 TCSScore=0.8 + paralogGroup + tr|A0A1B7NXM4|A0A1B7NXM4_9EURO + tr|F2PIH5|F2PIH5_TRIEC + tr|F2PJ36|F2PJ36_TRIEC + tr|F2Q5N8|F2Q5N8_TRIEC + tr|A0A1L9VJB7|A0A1L9VJB7_ASPGL + tr|A0A1L9V5W5|A0A1L9V5W5_ASPGL + tr|A0A1L9V9J8|A0A1L9V9J8_ASPGL + tr|A0A1L9VX51|A0A1L9VX51_ASPGL + tr|F2PKQ3|F2PKQ3_TRIEC + tr|F2PLE6|F2PLE6_TRIEC + tr|F2PM61|F2PM61_TRIEC + tr|F2PV36|F2PV36_TRIEC + paralogGroup + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395S0Z2|A0A395S0Z2_FUSSP + tr|A0A8H5P5V9|A0A8H5P5V9_GIBSU + tr|A0A9P9JQS6|A0A9P9JQS6_FUSSL + orthologGroup Hypocreales CompletenessScore=0.75 ImpliedLosses=2 TCSScore=1.0 + orthologGroup Fusarium CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SR55|A0A395SR55_FUSSP + tr|A0A8H5PCB0|A0A8H5PCB0_GIBSU + paralogGroup + tr|G9MUQ4|G9MUQ4_HYPVG + tr|G9MZ61|G9MZ61_HYPVG + orthologGroup Hypocreomycetidae CompletenessScore=0.5 ImpliedLosses=4 TCSScore=0.6667 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A7J6ID17|A0A7J6ID17_COLFN + tr|A0AAX4J184|A0AAX4J184_9PEZI + tr|A0A395S871|A0A395S871_FUSSP + orthologGroup Sordariomycetes CompletenessScore=0.2222 ImpliedLosses=4 TCSScore=1.3333 + paralogGroup + tr|A0ABQ8NJB1|A0ABQ8NJB1_PYRGI + tr|A0ABQ8NNL9|A0ABQ8NNL9_PYRGI + tr|G9MVA5|G9MVA5_HYPVG + orthologGroup Sordariomycetes CompletenessScore=0.3333 ImpliedLosses=9 TCSScore=2.4 + paralogGroup + orthologGroup Hypocreales CompletenessScore=0.5 ImpliedLosses=2 TCSScore=0.0 + tr|A0A9P9KUY9|A0A9P9KUY9_FUSSL + tr|G9MKA1|G9MKA1_HYPVG + tr|G9MUY2|G9MUY2_HYPVG + tr|G9N4C5|G9N4C5_HYPVG + tr|A0ABQ8NPS7|A0ABQ8NPS7_PYRGI + orthologGroup Sordariomycetes CompletenessScore=0.5556 ImpliedLosses=5 TCSScore=3.4 + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SQY0|A0A395SQY0_FUSSP + tr|A0A8H5Q5X1|A0A8H5Q5X1_GIBSU + tr|A0A9P9HA30|A0A9P9HA30_FUSSL + tr|G9MWP4|G9MWP4_HYPVG + tr|A0A2P5IFJ7|A0A2P5IFJ7_DIAHE + orthologGroup Sordariomycetes CompletenessScore=0.6667 ImpliedLosses=6 TCSScore=2.3333 + orthologGroup Hypocreomycetidae CompletenessScore=0.6667 ImpliedLosses=4 TCSScore=1.0 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4IDB4|A0AAX4IDB4_9PEZI + tr|L2FJK3|L2FJK3_COLFN + orthologGroup Hypocreales CompletenessScore=0.5 ImpliedLosses=2 TCSScore=0.0 + tr|A0A9P9H1B9|A0A9P9H1B9_FUSSL + tr|G9N2Z7|G9N2Z7_HYPVG + orthologGroup Sordariomycetidae CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0ABQ8NQ86|A0ABQ8NQ86_PYRGI + tr|Q1K8N5|Q1K8N5_NEUCR + paralogGroup + orthologGroup Hypocreales CompletenessScore=0.5 ImpliedLosses=2 TCSScore=0.75 + paralogGroup + tr|G9MPI7|G9MPI7_HYPVG + tr|G9MXP9|G9MXP9_HYPVG + tr|G9N3N0|G9N3N0_HYPVG + tr|A0A395SC72|A0A395SC72_FUSSP + tr|A0A8H5KYU1|A0A8H5KYU1_GIBSU + paralogGroup + tr|A0AAX4HWZ4|A0AAX4HWZ4_9PEZI + tr|G9MWW6|G9MWW6_HYPVG + tr|A0A2P5I048|A0A2P5I048_DIAHE + tr|A0A395S4M2|A0A395S4M2_FUSSP + tr|A0A395SBK7|A0A395SBK7_FUSSP + tr|A0A9P9KU18|A0A9P9KU18_FUSSL + tr|A0AAX4IC69|A0AAX4IC69_9PEZI + tr|A0AAX4J3X1|A0AAX4J3X1_9PEZI + tr|A0ABQ8NIE5|A0ABQ8NIE5_PYRGI + paralogGroup + tr|A0A7C8I9Y5|A0A7C8I9Y5_9PLEO + tr|A0A7C8IAS7|A0A7C8IAS7_9PLEO + orthologGroup Fungiincertaesedis CompletenessScore=0.5 ImpliedLosses=1 TCSScore=0.9091 + paralogGroup + tr|A0A1Y1YU83|A0A1Y1YU83_9FUNG + tr|A0A1Y2A814|A0A1Y2A814_9FUNG + tr|A0A1Y2AH12|A0A1Y2AH12_9FUNG + tr|A0A1Y2B4U0|A0A1Y2B4U0_9FUNG + tr|A0A1Y2B4U1|A0A1Y2B4U1_9FUNG + tr|A0A1Y2B531|A0A1Y2B531_9FUNG + tr|A0A1Y2B5M5|A0A1Y2B5M5_9FUNG + tr|A0A1Y2BEC1|A0A1Y2BEC1_9FUNG + tr|A0A1Y2C3D0|A0A1Y2C3D0_9FUNG + tr|A0A1Y2DMF1|A0A1Y2DMF1_9FUNG + tr|A0ABN7V2T2|A0ABN7V2T2_GIGMA diff --git a/tests/data/fungi30/golden/F0459413.txt b/tests/data/fungi30/golden/F0459413.txt new file mode 100644 index 0000000..9ef5d78 --- /dev/null +++ b/tests/data/fungi30/golden/F0459413.txt @@ -0,0 +1,51 @@ +# 26 input proteins, 1 groups, 26 proteins in groups + +orthologGroup Dikarya CompletenessScore=0.4615 ImpliedLosses=25 TCSScore=20.6538 + orthologGroup Basidiomycota CompletenessScore=0.25 ImpliedLosses=1 TCSScore=0.6667 + paralogGroup + tr|M7WKC3|M7WKC3_RHOT1 + tr|M7XDE6|M7XDE6_RHOT1 + tr|A0A067TG69|A0A067TG69_GALM3 + paralogGroup + orthologGroup leotiomyceta CompletenessScore=0.4375 ImpliedLosses=7 TCSScore=4.1111 + orthologGroup Sordariomycetes CompletenessScore=0.4444 ImpliedLosses=4 TCSScore=4.0 + orthologGroup Hypocreales CompletenessScore=0.75 ImpliedLosses=2 TCSScore=1.0 + orthologGroup Fusarium CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A8H5NQD1|A0A8H5NQD1_GIBSU + tr|A0A9P9KTM1|A0A9P9KTM1_FUSSL + paralogGroup + tr|G9MRY1|G9MRY1_HYPVG + tr|G9MWX3|G9MWX3_HYPVG + tr|A0A2P5HH10|A0A2P5HH10_DIAHE + paralogGroup + tr|A0A0D2GGZ5|A0A0D2GGZ5_CLAB1 + tr|A0A1L9V7F2|A0A1L9V7F2_ASPGL + paralogGroup + tr|A0A7C8IAK2|A0A7C8IAK2_9PLEO + tr|A0A7C8MMZ9|A0A7C8MMZ9_9PLEO + orthologGroup leotiomyceta CompletenessScore=0.5625 ImpliedLosses=16 TCSScore=9.5 + paralogGroup + orthologGroup Sordariomycetes CompletenessScore=0.6667 ImpliedLosses=7 TCSScore=7.3333 + paralogGroup + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=3 TCSScore=2.2857 + paralogGroup + orthologGroup Fusarium CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SGZ3|A0A395SGZ3_FUSSP + tr|A0A9P9KT16|A0A9P9KT16_FUSSL + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.5 + paralogGroup + tr|A0A9P9HBT0|A0A9P9HBT0_FUSSL + tr|A0A9P9KT27|A0A9P9KT27_FUSSL + tr|A0A395RUQ0|A0A395RUQ0_FUSSP + tr|A0A8H5L0G4|A0A8H5L0G4_GIBSU + tr|G9MYJ3|G9MYJ3_HYPVG + tr|A0AAX4I6A1|A0AAX4I6A1_9PEZI + tr|A0A2P5HEK2|A0A2P5HEK2_DIAHE + paralogGroup + paralogGroup + tr|A0A7J6J8J1|A0A7J6J8J1_COLFN + tr|A0AAX4ITF3|A0AAX4ITF3_9PEZI + tr|A0AAX4IYH9|A0AAX4IYH9_9PEZI + paralogGroup + tr|A0A0D2EF97|A0A0D2EF97_CLAB1 + tr|A0A1L9VXN8|A0A1L9VXN8_ASPGL diff --git a/tests/data/fungi30/golden/F0476910.txt b/tests/data/fungi30/golden/F0476910.txt new file mode 100644 index 0000000..64dde85 --- /dev/null +++ b/tests/data/fungi30/golden/F0476910.txt @@ -0,0 +1,52 @@ +# 34 input proteins, 1 groups, 33 proteins in groups + +orthologGroup Fungi CompletenessScore=0.3667 ImpliedLosses=19 TCSScore=12.1515 + paralogGroup + orthologGroup Dikarya CompletenessScore=0.3077 ImpliedLosses=7 TCSScore=9.0 + orthologGroup leotiomyceta CompletenessScore=0.375 ImpliedLosses=4 TCSScore=1.8571 + orthologGroup Sordariomycetes CompletenessScore=0.3333 ImpliedLosses=2 TCSScore=2.0 + orthologGroup Fusarium CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SBE1|A0A395SBE1_FUSSP + tr|A0A9P9R990|A0A9P9R990_FUSSL + tr|Q7SD43|Q7SD43_NEUCR + paralogGroup + tr|A0A7C8M6G8|A0A7C8M6G8_9PLEO + tr|A0A7C8ME07|A0A7C8ME07_9PLEO + tr|A0A0D2HEG1|A0A0D2HEG1_CLAB1 + tr|A0A165G1Y1|A0A165G1Y1_XYLHT + paralogGroup + orthologGroup Agaricales CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=1.0 + paralogGroup + tr|A0A067S5K1|A0A067S5K1_GALM3 + tr|A0A067S7G5|A0A067S7G5_GALM3 + tr|A0A067S7J5|A0A067S7J5_GALM3 + tr|A0A067S7N9|A0A067S7N9_GALM3 + tr|A0A067S839|A0A067S839_GALM3 + tr|A0A067S9Z7|A0A067S9Z7_GALM3 + tr|A0A067SDK3|A0A067SDK3_GALM3 + tr|A0A067SEH4|A0A067SEH4_GALM3 + tr|A0A067SGA6|A0A067SGA6_GALM3 + tr|A0A067TCL5|A0A067TCL5_GALM3 + tr|A0A067TCR6|A0A067TCR6_GALM3 + paralogGroup + tr|A0A0C2YMZ1|A0A0C2YMZ1_HEBCY + tr|A0A0C3CHJ8|A0A0C3CHJ8_HEBCY + tr|A0A0C3CIW7|A0A0C3CIW7_HEBCY + tr|A0A067STW1|A0A067STW1_GALM3 + orthologGroup leotiomyceta CompletenessScore=0.125 ImpliedLosses=3 TCSScore=0.0 + tr|A0A164Z8X8|A0A164Z8X8_XYLHT + tr|A0A1V6NNK1|A0A1V6NNK1_PENDC + orthologGroup leotiomyceta CompletenessScore=0.25 ImpliedLosses=3 TCSScore=4.2 + orthologGroup Eurotiomycetes CompletenessScore=0.6 ImpliedLosses=2 TCSScore=3.0 + orthologGroup Aspergillaceae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.6667 + paralogGroup + tr|A0A1L9V458|A0A1L9V458_ASPGL + tr|A0A1L9VLC0|A0A1L9VLC0_ASPGL + tr|A0A1V6NR88|A0A1V6NR88_PENDC + tr|A0A0D2HK19|A0A0D2HK19_CLAB1 + tr|A0A7C8MDA3|A0A7C8MDA3_9PLEO + tr|A0A0D2HJK2|A0A0D2HJK2_CLAB1 + paralogGroup + tr|A0ABM8W3Y2|A0ABM8W3Y2_GIGMA + tr|A0ABN7V5G4|A0ABN7V5G4_GIGMA + tr|A0ABN7WS42|A0ABN7WS42_GIGMA diff --git a/tests/data/fungi30/golden/F0477229.txt b/tests/data/fungi30/golden/F0477229.txt new file mode 100644 index 0000000..838bf74 --- /dev/null +++ b/tests/data/fungi30/golden/F0477229.txt @@ -0,0 +1,36 @@ +# 27 input proteins, 1 groups, 21 proteins in groups + +orthologGroup Dikarya CompletenessScore=0.4615 ImpliedLosses=15 TCSScore=16.0476 + orthologGroup Agaricales CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.6667 + paralogGroup + tr|A0A067S4V6|A0A067S4V6_GALM3 + tr|A0A067T8Z9|A0A067T8Z9_GALM3 + tr|A0A0C3CZU8|A0A0C3CZU8_HEBCY + orthologGroup leotiomyceta CompletenessScore=0.625 ImpliedLosses=13 TCSScore=6.1111 + orthologGroup Onygenales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.75 + paralogGroup + tr|F2PXP9|F2PXP9_TRIEC + tr|F2PYJ4|F2PYJ4_TRIEC + tr|F2Q4D0|F2Q4D0_TRIEC + tr|A0A1B7P4W4|A0A1B7P4W4_9EURO + paralogGroup + orthologGroup Sordariomycetes CompletenessScore=0.6667 ImpliedLosses=5 TCSScore=4.1 + orthologGroup Hypocreomycetidae CompletenessScore=0.6667 ImpliedLosses=3 TCSScore=2.25 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.8333 + paralogGroup + tr|A0A7J6ICM4|A0A7J6ICM4_COLFN + tr|A0A7J6IHN8|A0A7J6IHN8_COLFN + tr|A0A7J6IMY3|A0A7J6IMY3_COLFN + tr|A0A7J6JBZ8|A0A7J6JBZ8_COLFN + tr|L2GDX0|L2GDX0_COLFN + tr|A0AAX4I0N0|A0AAX4I0N0_9PEZI + orthologGroup Hypocreales CompletenessScore=0.5 ImpliedLosses=1 TCSScore=0.0 + tr|A0A9P9JYY1|A0A9P9JYY1_FUSSL + tr|G9MTS0|G9MTS0_HYPVG + orthologGroup Sordariomycetidae CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5ID70|A0A2P5ID70_DIAHE + tr|A0ABQ8NUF1|A0ABQ8NUF1_PYRGI + tr|G9MRU2|G9MRU2_HYPVG + tr|G9N670|G9N670_HYPVG + tr|A0A165GUZ4|A0A165GUZ4_XYLHT + tr|A0A7C8M9S8|A0A7C8M9S8_9PLEO diff --git a/tests/data/fungi30/golden/F0478382.txt b/tests/data/fungi30/golden/F0478382.txt new file mode 100644 index 0000000..0519f65 --- /dev/null +++ b/tests/data/fungi30/golden/F0478382.txt @@ -0,0 +1,7 @@ +# 4 input proteins, 1 groups, 3 proteins in groups + +orthologGroup leotiomyceta CompletenessScore=0.1875 ImpliedLosses=2 TCSScore=1.3333 + orthologGroup Hypocreomycetidae CompletenessScore=0.3333 ImpliedLosses=1 TCSScore=0.0 + tr|A0A8H5V9V0|A0A8H5V9V0_GIBSU + tr|A0AAX4IL06|A0AAX4IL06_9PEZI + tr|A0A7C8ICJ0|A0A7C8ICJ0_9PLEO diff --git a/tests/data/fungi30/golden/F0480898.txt b/tests/data/fungi30/golden/F0480898.txt new file mode 100644 index 0000000..6f11392 --- /dev/null +++ b/tests/data/fungi30/golden/F0480898.txt @@ -0,0 +1,45 @@ +# 24 input proteins, 1 groups, 24 proteins in groups + +orthologGroup leotiomyceta CompletenessScore=0.625 ImpliedLosses=21 TCSScore=6.5833 + paralogGroup + orthologGroup Eurotiomycetidae CompletenessScore=0.5 ImpliedLosses=1 TCSScore=1.5 + paralogGroup + tr|A0A1L9V4W9|A0A1L9V4W9_ASPGL + tr|A0A1L9V7S2|A0A1L9V7S2_ASPGL + tr|A0A1L9VLF5|A0A1L9VLF5_ASPGL + tr|A0A1B7P0W3|A0A1B7P0W3_9EURO + tr|A0A1L9V624|A0A1L9V624_ASPGL + paralogGroup + orthologGroup Sordariomycetes CompletenessScore=0.3333 ImpliedLosses=7 TCSScore=2.8 + paralogGroup + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A7J6JGH0|A0A7J6JGH0_COLFN + tr|A0AAX4I0Q8|A0AAX4I0Q8_9PEZI + tr|L2FWK4|L2FWK4_COLFN + paralogGroup + tr|A0ABQ8NRX1|A0ABQ8NRX1_PYRGI + tr|A0ABQ8NYR1|A0ABQ8NYR1_PYRGI + orthologGroup Sordariomycetes CompletenessScore=0.7778 ImpliedLosses=8 TCSScore=3.9091 + orthologGroup Sordariomycetidae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5I609|A0A2P5I609_DIAHE + tr|A0ABQ8NGB4|A0ABQ8NGB4_PYRGI + tr|V5IKJ7|V5IKJ7_NEUCR + paralogGroup + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A7J6J4I6|A0A7J6J4I6_COLFN + tr|A0AAX4I0H8|A0AAX4I0H8_9PEZI + orthologGroup Hypocreomycetidae CompletenessScore=0.6667 ImpliedLosses=4 TCSScore=2.0 + orthologGroup Fusarium CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A8H5Q8E8|A0A8H5Q8E8_GIBSU + tr|A0A9P9L841|A0A9P9L841_FUSSL + paralogGroup + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4ICN6|A0AAX4ICN6_9PEZI + tr|L2FCD7|L2FCD7_COLFN + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4ITE2|A0AAX4ITE2_9PEZI + tr|L2FSM0|L2FSM0_COLFN + tr|A0A7J6J952|A0A7J6J952_COLFN + paralogGroup + tr|A0A7C8MBL4|A0A7C8MBL4_9PLEO + tr|A0A7C8MVB6|A0A7C8MVB6_9PLEO diff --git a/tests/data/fungi30/golden/F0485529.txt b/tests/data/fungi30/golden/F0485529.txt new file mode 100644 index 0000000..8fd3dfa --- /dev/null +++ b/tests/data/fungi30/golden/F0485529.txt @@ -0,0 +1,3 @@ +# 2 input proteins, 0 groups, 0 proteins in groups + + diff --git a/tests/data/fungi30/golden/F0489906.txt b/tests/data/fungi30/golden/F0489906.txt new file mode 100644 index 0000000..7ec79fa --- /dev/null +++ b/tests/data/fungi30/golden/F0489906.txt @@ -0,0 +1,13 @@ +# 6 input proteins, 1 groups, 6 proteins in groups + +orthologGroup Dikarya CompletenessScore=0.1923 ImpliedLosses=5 TCSScore=8.1667 + paralogGroup + orthologGroup leotiomyceta CompletenessScore=0.25 ImpliedLosses=3 TCSScore=1.75 + orthologGroup Eurotiomycetes CompletenessScore=0.6 ImpliedLosses=2 TCSScore=0.6667 + orthologGroup Eurotiomycetidae CompletenessScore=0.5 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1B7NPW6|A0A1B7NPW6_9EURO + tr|A0A1L9VKP1|A0A1L9VKP1_ASPGL + tr|A0A0D2ESI3|A0A0D2ESI3_CLAB1 + tr|A0A9P9G213|A0A9P9G213_FUSSL + tr|A0A9P9JYK9|A0A9P9JYK9_FUSSL + tr|A0A0P1B925|A0A0P1B925_9BASI diff --git a/tests/data/fungi30/golden/F0848191.txt b/tests/data/fungi30/golden/F0848191.txt new file mode 100644 index 0000000..4b35b7a --- /dev/null +++ b/tests/data/fungi30/golden/F0848191.txt @@ -0,0 +1,290 @@ +# 175 input proteins, 4 groups, 167 proteins in groups + +orthologGroup Fungi CompletenessScore=0.7333 ImpliedLosses=32 TCSScore=22.3611 + orthologGroup Dikarya CompletenessScore=0.7308 ImpliedLosses=28 TCSScore=18.8387 + orthologGroup Ascomycota CompletenessScore=1.0 ImpliedLosses=25 TCSScore=13.6333 + orthologGroup saccharomyceta CompletenessScore=1.0 ImpliedLosses=24 TCSScore=9.1034 + paralogGroup + orthologGroup Sordariomycetes CompletenessScore=0.3333 ImpliedLosses=4 TCSScore=1.3333 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4I2Q2|A0AAX4I2Q2_9PEZI + tr|L2FU61|L2FU61_COLFN + tr|A0A2P5HLA9|A0A2P5HLA9_DIAHE + orthologGroup Sordariomycetes CompletenessScore=1.0 ImpliedLosses=6 TCSScore=3.3333 + orthologGroup Hypocreomycetidae CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.0 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4I2R5|A0AAX4I2R5_9PEZI + tr|L2G588|L2G588_COLFN + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SEL3|A0A395SEL3_FUSSP + tr|A0A8H5PFR4|A0A8H5PFR4_GIBSU + tr|A0A9P9RCW9|A0A9P9RCW9_FUSSL + tr|G9N6T3|G9N6T3_HYPVG + orthologGroup Sordariomycetidae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5HL47|A0A2P5HL47_DIAHE + tr|Q7S836|Q7S836_NEUCR + tr|Q96UW1|Q96UW1_PYRGI + orthologGroup leotiomyceta CompletenessScore=1.0 ImpliedLosses=11 TCSScore=5.125 + orthologGroup Eurotiomycetes CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.4 + orthologGroup Eurotiomycetidae CompletenessScore=1.0 ImpliedLosses=3 TCSScore=1.0 + orthologGroup Aspergillaceae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1L9VYU4|A0A1L9VYU4_ASPGL + tr|A0A1V6P8M2|A0A1V6P8M2_PENDC + orthologGroup Onygenales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1B7NPZ8|A0A1B7NPZ8_9EURO + tr|F2PUU8|F2PUU8_TRIEC + tr|A0A0D2ELH6|A0A0D2ELH6_CLAB1 + orthologGroup Sordariomycetes CompletenessScore=1.0 ImpliedLosses=6 TCSScore=3.3333 + orthologGroup Hypocreomycetidae CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.0 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4I8K4|A0AAX4I8K4_9PEZI + tr|L2FXL1|L2FXL1_COLFN + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395RLI0|A0A395RLI0_FUSSP + tr|A0A8H5UZ13|A0A8H5UZ13_GIBSU + tr|A0A9P9HJ80|A0A9P9HJ80_FUSSL + tr|G9MGW2|G9MGW2_HYPVG + orthologGroup Sordariomycetidae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5I8E1|A0A2P5I8E1_DIAHE + tr|A0ABQ8N4J5|A0ABQ8N4J5_PYRGI + tr|Q7S7W1|Q7S7W1_NEUCR + tr|A0A165JSQ7|A0A165JSQ7_XYLHT + tr|A0A7C8M810|A0A7C8M810_9PLEO + tr|A0AA35IRL9|A0AA35IRL9_SACMI + sp|Q9UT43|ATC8_SCHPO + tr|A0AAW0G776|A0AAW0G776_9APHY + orthologGroup Fungiincertaesedis CompletenessScore=0.75 ImpliedLosses=3 TCSScore=1.2 + paralogGroup + tr|A0A1Y1ZPJ8|A0A1Y1ZPJ8_9FUNG + tr|A0A1Y2B225|A0A1Y2B225_9FUNG + paralogGroup + tr|A0ABM8VX59|A0ABM8VX59_GIGMA + tr|A0ABN7UHV8|A0ABN7UHV8_GIGMA + tr|A0ABQ8PV12|A0ABQ8PV12_9FUNG + +orthologGroup Fungi CompletenessScore=0.9667 ImpliedLosses=22 TCSScore=15.2188 + orthologGroup Dikarya CompletenessScore=0.9615 ImpliedLosses=19 TCSScore=13.2 + orthologGroup Ascomycota CompletenessScore=1.0 ImpliedLosses=13 TCSScore=11.7222 + orthologGroup saccharomyceta CompletenessScore=1.0 ImpliedLosses=12 TCSScore=8.1176 + orthologGroup leotiomyceta CompletenessScore=1.0 ImpliedLosses=11 TCSScore=5.125 + orthologGroup Eurotiomycetes CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.4 + orthologGroup Eurotiomycetidae CompletenessScore=1.0 ImpliedLosses=3 TCSScore=1.0 + orthologGroup Aspergillaceae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1L9VMD5|A0A1L9VMD5_ASPGL + tr|A0A1V6PLY2|A0A1V6PLY2_PENDC + orthologGroup Onygenales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1B7P7I3|A0A1B7P7I3_9EURO + tr|F2PM24|F2PM24_TRIEC + tr|A0A0D2IQY6|A0A0D2IQY6_CLAB1 + orthologGroup Sordariomycetes CompletenessScore=1.0 ImpliedLosses=6 TCSScore=3.3333 + orthologGroup Hypocreomycetidae CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.0 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A7J6J338|A0A7J6J338_COLFN + tr|A0AAX4HZ44|A0AAX4HZ44_9PEZI + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SAR2|A0A395SAR2_FUSSP + tr|A0A8H5PDA4|A0A8H5PDA4_GIBSU + tr|A0A9P9L1T3|A0A9P9L1T3_FUSSL + tr|G9MMK6|G9MMK6_HYPVG + orthologGroup Sordariomycetidae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5IDA2|A0A2P5IDA2_DIAHE + tr|A0ABQ8P235|A0ABQ8P235_PYRGI + tr|Q7RZL3|Q7RZL3_NEUCR + tr|A0A165FY10|A0A165FY10_XYLHT + tr|A0A7C8MRI7|A0A7C8MRI7_9PLEO + tr|A0AA35IX53|A0AA35IX53_SACMI + sp|O94296|ATC3_SCHPO + orthologGroup Basidiomycota CompletenessScore=0.875 ImpliedLosses=5 TCSScore=1.7143 + orthologGroup Agaricomycetes CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Agaricales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A067SYV3|A0A067SYV3_GALM3 + tr|A0A0C3C8P4|A0A0C3C8P4_HEBCY + tr|B0D0Z2|B0D0Z2_LACBS + tr|A0AAW0GDM5|A0AAW0GDM5_9APHY + orthologGroup Ustilaginomycotina CompletenessScore=0.6667 ImpliedLosses=2 TCSScore=0.0 + tr|A0A0F7S3X1|A0A0F7S3X1_9BASI + tr|A0A316UVR8|A0A316UVR8_9BASI + tr|M7XXB6|M7XXB6_RHOT1 + orthologGroup Fungiincertaesedis CompletenessScore=1.0 ImpliedLosses=2 TCSScore=1.4286 + orthologGroup Mucoromycota CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.6667 + paralogGroup + tr|A0AAD7V2H0|A0AAD7V2H0_9FUNG + tr|A0AAD7XWI9|A0AAD7XWI9_9FUNG + tr|A0ABN7VMC2|A0ABN7VMC2_GIGMA + paralogGroup + tr|A0A1Y2AWR0|A0A1Y2AWR0_9FUNG + tr|A0A1Y2F4G6|A0A1Y2F4G6_9FUNG + tr|A0A1Y2FFW8|A0A1Y2FFW8_9FUNG + tr|A0ABQ8PU82|A0ABQ8PU82_9FUNG + +orthologGroup Fungi CompletenessScore=1.0 ImpliedLosses=28 TCSScore=13.175 + orthologGroup Dikarya CompletenessScore=1.0 ImpliedLosses=19 TCSScore=12.6071 + orthologGroup Ascomycota CompletenessScore=1.0 ImpliedLosses=13 TCSScore=11.7222 + orthologGroup saccharomyceta CompletenessScore=1.0 ImpliedLosses=12 TCSScore=8.1176 + orthologGroup leotiomyceta CompletenessScore=1.0 ImpliedLosses=11 TCSScore=5.125 + orthologGroup Eurotiomycetes CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.4 + orthologGroup Eurotiomycetidae CompletenessScore=1.0 ImpliedLosses=3 TCSScore=1.0 + orthologGroup Aspergillaceae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1L9VD89|A0A1L9VD89_ASPGL + tr|A0A1V6PG37|A0A1V6PG37_PENDC + orthologGroup Onygenales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1B7P7T3|A0A1B7P7T3_9EURO + tr|F2PZY6|F2PZY6_TRIEC + tr|A0A0D2GF51|A0A0D2GF51_CLAB1 + orthologGroup Sordariomycetes CompletenessScore=1.0 ImpliedLosses=6 TCSScore=3.3333 + orthologGroup Hypocreomycetidae CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.0 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4I8R9|A0AAX4I8R9_9PEZI + tr|L2G5Z9|L2G5Z9_COLFN + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SM71|A0A395SM71_FUSSP + tr|A0A8H5PY59|A0A8H5PY59_GIBSU + tr|A0A9P9KN43|A0A9P9KN43_FUSSL + tr|G9MH84|G9MH84_HYPVG + orthologGroup Sordariomycetidae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5IDQ5|A0A2P5IDQ5_DIAHE + tr|A0ABQ8NXB0|A0ABQ8NXB0_PYRGI + tr|Q1K7F7|Q1K7F7_NEUCR + tr|A0A165FUL6|A0A165FUL6_XYLHT + tr|A0A7C8MF34|A0A7C8MF34_9PLEO + tr|A0AA35J0Z5|A0AA35J0Z5_SACMI + sp|Q10309|ATC7_SCHPO + orthologGroup Basidiomycota CompletenessScore=1.0 ImpliedLosses=5 TCSScore=2.4 + orthologGroup Agaricomycetes CompletenessScore=1.0 ImpliedLosses=2 TCSScore=1.0 + orthologGroup Agaricales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A067TE99|A0A067TE99_GALM3 + tr|A0A0C3C0S6|A0A0C3C0S6_HEBCY + tr|B0DWL6|B0DWL6_LACBS + paralogGroup + tr|A0AAW0FB63|A0AAW0FB63_9APHY + tr|A0AAW0G052|A0AAW0G052_9APHY + orthologGroup Ustilaginomycotina CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.6667 + orthologGroup Exobasidiomycetes CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A0P1BME0|A0A0P1BME0_9BASI + tr|A0A316ULH2|A0A316ULH2_9BASI + tr|A0A0F7S4M1|A0A0F7S4M1_9BASI + paralogGroup + tr|M7WHX4|M7WHX4_RHOT1 + tr|M7XR38|M7XR38_RHOT1 + paralogGroup + orthologGroup Fungiincertaesedis CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.8333 + orthologGroup Mucoromycota CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAD7UTJ8|A0AAD7UTJ8_9FUNG + tr|A0ABN7UYE4|A0ABN7UYE4_GIGMA + paralogGroup + tr|A0A1Y2AE51|A0A1Y2AE51_9FUNG + tr|A0A1Y2AHY8|A0A1Y2AHY8_9FUNG + tr|A0A1Y2F6L3|A0A1Y2F6L3_9FUNG + tr|A0ABQ8PEK3|A0ABQ8PEK3_9FUNG + tr|A0A1Y2A3Z8|A0A1Y2A3Z8_9FUNG + tr|A0A1Y2DPB3|A0A1Y2DPB3_9FUNG + tr|A0A1Y2DPB8|A0A1Y2DPB8_9FUNG + tr|A0A1Y2E2P9|A0A1Y2E2P9_9FUNG + tr|A0A1Y2E329|A0A1Y2E329_9FUNG + tr|A0A1Y2E5M3|A0A1Y2E5M3_9FUNG + +orthologGroup Fungi CompletenessScore=1.0 ImpliedLosses=44 TCSScore=12.7797 + paralogGroup + orthologGroup Basidiomycota CompletenessScore=1.0 ImpliedLosses=8 TCSScore=2.5455 + orthologGroup Ustilaginomycotina CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.6667 + orthologGroup Exobasidiomycetes CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A0P1BQC6|A0A0P1BQC6_9BASI + tr|A0A316UXC2|A0A316UXC2_9BASI + tr|A0A0F7RS93|A0A0F7RS93_9BASI + paralogGroup + orthologGroup Agaricales CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A067T192|A0A067T192_GALM3 + tr|A0A0C2YFW7|A0A0C2YFW7_HEBCY + orthologGroup Agaricomycetes CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Agaricales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A067SUC6|A0A067SUC6_GALM3 + tr|A0A0C3CJ14|A0A0C3CJ14_HEBCY + tr|B0DQ40|B0DQ40_LACBS + tr|A0AAW0GKU4|A0AAW0GKU4_9APHY + tr|A0AAW0GN29|A0AAW0GN29_9APHY + tr|M7WXI4|M7WXI4_RHOT1 + orthologGroup Dikarya CompletenessScore=0.9615 ImpliedLosses=22 TCSScore=13.0667 + orthologGroup Ascomycota CompletenessScore=1.0 ImpliedLosses=13 TCSScore=11.3333 + orthologGroup saccharomyceta CompletenessScore=1.0 ImpliedLosses=12 TCSScore=8.1053 + orthologGroup leotiomyceta CompletenessScore=1.0 ImpliedLosses=11 TCSScore=5.3529 + orthologGroup Eurotiomycetes CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.4 + orthologGroup Eurotiomycetidae CompletenessScore=1.0 ImpliedLosses=3 TCSScore=1.0 + orthologGroup Aspergillaceae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1L9V8A8|A0A1L9V8A8_ASPGL + tr|A0A1V6PFJ5|A0A1V6PFJ5_PENDC + orthologGroup Onygenales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1B7P546|A0A1B7P546_9EURO + tr|F2PX54|F2PX54_TRIEC + tr|A0A0D2HRY1|A0A0D2HRY1_CLAB1 + orthologGroup Sordariomycetes CompletenessScore=1.0 ImpliedLosses=6 TCSScore=3.5 + orthologGroup Hypocreomycetidae CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.0 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A7J6IT22|A0A7J6IT22_COLFN + tr|A0AAX4IIC0|A0AAX4IIC0_9PEZI + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SBU8|A0A395SBU8_FUSSP + tr|A0A8H5V8Q5|A0A8H5V8Q5_GIBSU + tr|A0A9P9KZI6|A0A9P9KZI6_FUSSL + tr|G9MQ06|G9MQ06_HYPVG + orthologGroup Sordariomycetidae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.5 + paralogGroup + tr|A0A2P5HKR8|A0A2P5HKR8_DIAHE + tr|A0A2P5I0U7|A0A2P5I0U7_DIAHE + tr|A0ABQ8NZZ0|A0ABQ8NZZ0_PYRGI + tr|Q7SF11|Q7SF11_NEUCR + tr|A0A165FCM4|A0A165FCM4_XYLHT + tr|A0A7C8I895|A0A7C8I895_9PLEO + paralogGroup + tr|A0AA35IW22|A0AA35IW22_SACMI + tr|A0AA35IX89|A0AA35IX89_SACMI + paralogGroup + sp|O36028|ATCZ_SCHPO + sp|Q09891|ATCX_SCHPO + paralogGroup + orthologGroup Basidiomycota CompletenessScore=0.875 ImpliedLosses=6 TCSScore=2.25 + orthologGroup Ustilaginomycotina CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.6667 + orthologGroup Exobasidiomycetes CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A0P1BP52|A0A0P1BP52_9BASI + tr|A0A316V1J6|A0A316V1J6_9BASI + tr|A0A0F7S488|A0A0F7S488_9BASI + paralogGroup + orthologGroup Agaricomycetes CompletenessScore=0.75 ImpliedLosses=2 TCSScore=0.6667 + orthologGroup Agaricales CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A067TK84|A0A067TK84_GALM3 + tr|A0A0C3CJD5|A0A0C3CJD5_HEBCY + tr|A0AAW0GHG7|A0AAW0GHG7_9APHY + tr|A0AAW0GDP4|A0AAW0GDP4_9APHY + tr|M7XNP1|M7XNP1_RHOT1 + tr|A0AAW0FWL5|A0AAW0FWL5_9APHY + paralogGroup + orthologGroup Fungiincertaesedis CompletenessScore=0.5 ImpliedLosses=2 TCSScore=0.6667 + paralogGroup + tr|A0A1Y2AY62|A0A1Y2AY62_9FUNG + tr|A0A1Y2CGT1|A0A1Y2CGT1_9FUNG + tr|A0ABM8W4B7|A0ABM8W4B7_GIGMA + orthologGroup Fungiincertaesedis CompletenessScore=0.75 ImpliedLosses=2 TCSScore=1.75 + orthologGroup Mucoromycota CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.6667 + paralogGroup + tr|A0ABN7UQ65|A0ABN7UQ65_GIGMA + tr|A0ABN7VFB1|A0ABN7VFB1_GIGMA + tr|A0AAD7V148|A0AAD7V148_9FUNG + tr|A0ABQ8PP77|A0ABQ8PP77_9FUNG + orthologGroup Fungiincertaesedis CompletenessScore=0.75 ImpliedLosses=2 TCSScore=2.0 + orthologGroup Mucoromycota CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.75 + paralogGroup + tr|A0AAD7UYQ7|A0AAD7UYQ7_9FUNG + tr|A0AAD7VBQ1|A0AAD7VBQ1_9FUNG + tr|A0AAD7XRT1|A0AAD7XRT1_9FUNG + tr|A0ABN7URS3|A0ABN7URS3_GIGMA + tr|A0ABQ8PX09|A0ABQ8PX09_9FUNG + tr|A0A1Y2D4B8|A0A1Y2D4B8_9FUNG + tr|A0A1Y2DBV3|A0A1Y2DBV3_9FUNG + tr|A0A1Y2DMD0|A0A1Y2DMD0_9FUNG + tr|A0A1Y2ESZ3|A0A1Y2ESZ3_9FUNG + tr|A0A1Y2F3B3|A0A1Y2F3B3_9FUNG + tr|A0A1Y2FVJ1|A0A1Y2FVJ1_9FUNG diff --git a/tests/data/fungi30/golden/F0848294.txt b/tests/data/fungi30/golden/F0848294.txt new file mode 100644 index 0000000..99f4225 --- /dev/null +++ b/tests/data/fungi30/golden/F0848294.txt @@ -0,0 +1,54 @@ +# 30 input proteins, 1 groups, 30 proteins in groups + +orthologGroup Fungi CompletenessScore=1.0 ImpliedLosses=22 TCSScore=16.1333 + orthologGroup Dikarya CompletenessScore=1.0 ImpliedLosses=19 TCSScore=13.0385 + orthologGroup Ascomycota CompletenessScore=1.0 ImpliedLosses=13 TCSScore=11.7222 + orthologGroup saccharomyceta CompletenessScore=1.0 ImpliedLosses=12 TCSScore=8.1176 + orthologGroup leotiomyceta CompletenessScore=1.0 ImpliedLosses=11 TCSScore=5.125 + orthologGroup Eurotiomycetes CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.4 + orthologGroup Eurotiomycetidae CompletenessScore=1.0 ImpliedLosses=3 TCSScore=1.0 + orthologGroup Aspergillaceae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1L9VWY3|A0A1L9VWY3_ASPGL + tr|A0A1V6PIE3|A0A1V6PIE3_PENDC + orthologGroup Onygenales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A1B7P792|A0A1B7P792_9EURO + tr|F2PQY5|F2PQY5_TRIEC + tr|A0A0D2HIH0|A0A0D2HIH0_CLAB1 + orthologGroup Sordariomycetes CompletenessScore=1.0 ImpliedLosses=6 TCSScore=3.3333 + orthologGroup Hypocreomycetidae CompletenessScore=1.0 ImpliedLosses=4 TCSScore=2.0 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4HY75|A0AAX4HY75_9PEZI + tr|L2G5M7|L2G5M7_COLFN + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395RH59|A0A395RH59_FUSSP + tr|A0A8H5NX32|A0A8H5NX32_GIBSU + tr|A0A9P9RC40|A0A9P9RC40_FUSSL + tr|G9N112|G9N112_HYPVG + orthologGroup Sordariomycetidae CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5HL36|A0A2P5HL36_DIAHE + tr|A0A6P8AUH6|A0A6P8AUH6_PYRGI + tr|Q7RYM3|Q7RYM3_NEUCR + tr|A0A165FZQ7|A0A165FZQ7_XYLHT + tr|A0A7C8MU77|A0A7C8MU77_9PLEO + tr|A0AA35IZ99|A0AA35IZ99_SACMI + sp|Q1MTQ9|NIP7_SCHPO + orthologGroup Basidiomycota CompletenessScore=1.0 ImpliedLosses=5 TCSScore=2.125 + orthologGroup Agaricomycetes CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.75 + orthologGroup Agaricales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A067TQ63|A0A067TQ63_GALM3 + tr|A0A0C3CUS6|A0A0C3CUS6_HEBCY + tr|B0CR22|B0CR22_LACBS + tr|A0AAW0GQL6|A0AAW0GQL6_9APHY + orthologGroup Ustilaginomycotina CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.6667 + orthologGroup Exobasidiomycetes CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A0P1BJE5|A0A0P1BJE5_9BASI + tr|A0A316UL47|A0A316UL47_9BASI + tr|A0A0F7RSB9|A0A0F7RSB9_9BASI + tr|M7XIG2|M7XIG2_RHOT1 + orthologGroup Fungiincertaesedis CompletenessScore=1.0 ImpliedLosses=2 TCSScore=0.5 + orthologGroup Mucoromycota CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAD7VCH9|A0AAD7VCH9_9FUNG + tr|A0ABN7VXY4|A0ABN7VXY4_GIGMA + tr|A0A1Y2AHI4|A0A1Y2AHI4_9FUNG + tr|A0ABQ8PD51|A0ABQ8PD51_9FUNG diff --git a/tests/data/fungi30/golden/F1085561.txt b/tests/data/fungi30/golden/F1085561.txt new file mode 100644 index 0000000..26e2893 --- /dev/null +++ b/tests/data/fungi30/golden/F1085561.txt @@ -0,0 +1,53 @@ +# 28 input proteins, 2 groups, 28 proteins in groups + +orthologGroup leotiomyceta CompletenessScore=0.625 ImpliedLosses=12 TCSScore=5.0 + paralogGroup + orthologGroup Sordariomycetes CompletenessScore=0.2222 ImpliedLosses=4 TCSScore=0.0 + tr|A0A2P5I218|A0A2P5I218_DIAHE + tr|G9N994|G9N994_HYPVG + orthologGroup Sordariomycetes CompletenessScore=0.7778 ImpliedLosses=6 TCSScore=2.8571 + orthologGroup Hypocreomycetidae CompletenessScore=0.8333 ImpliedLosses=4 TCSScore=1.6 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4IGD7|A0AAX4IGD7_9PEZI + tr|L2FZ33|L2FZ33_COLFN + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SGQ3|A0A395SGQ3_FUSSP + tr|A0A8H5V7B3|A0A8H5V7B3_GIBSU + tr|A0A9P9L341|A0A9P9L341_FUSSL + orthologGroup Sordariomycetidae CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0ABQ8N552|A0ABQ8N552_PYRGI + tr|Q7S4T5|Q7S4T5_NEUCR + tr|A0A0D2G4K5|A0A0D2G4K5_CLAB1 + +orthologGroup leotiomyceta CompletenessScore=0.625 ImpliedLosses=18 TCSScore=8.1111 + paralogGroup + orthologGroup Sordariomycetes CompletenessScore=0.5556 ImpliedLosses=6 TCSScore=1.8 + orthologGroup Hypocreomycetidae CompletenessScore=0.5 ImpliedLosses=4 TCSScore=0.6667 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4J405|A0AAX4J405_9PEZI + tr|L2FYR0|L2FYR0_COLFN + tr|A0A9P9HDS3|A0A9P9HDS3_FUSSL + orthologGroup Sordariomycetidae CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5HMI4|A0A2P5HMI4_DIAHE + tr|A7UWI4|A7UWI4_NEUCR + orthologGroup Sordariomycetes CompletenessScore=0.7778 ImpliedLosses=8 TCSScore=5.8182 + orthologGroup Hypocreomycetidae CompletenessScore=1.0 ImpliedLosses=6 TCSScore=3.1 + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0AAX4J1E3|A0AAX4J1E3_9PEZI + tr|L2FT04|L2FT04_COLFN + paralogGroup + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395RWT8|A0A395RWT8_FUSSP + tr|A0A8H5KT52|A0A8H5KT52_GIBSU + tr|A0A9P9KTM8|A0A9P9KTM8_FUSSL + orthologGroup Hypocreales CompletenessScore=1.0 ImpliedLosses=2 TCSScore=1.0 + orthologGroup Fusarium CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A395SBD1|A0A395SBD1_FUSSP + tr|A0A8H5QE56|A0A8H5QE56_GIBSU + tr|A0A9P9K4X7|A0A9P9K4X7_FUSSL + paralogGroup + tr|G9MPB5|G9MPB5_HYPVG + tr|G9NAG7|G9NAG7_HYPVG + tr|A0A2P5HYD3|A0A2P5HYD3_DIAHE + tr|A0ABQ8NQ87|A0ABQ8NQ87_PYRGI + tr|A0A1B7P2U9|A0A1B7P2U9_9EURO diff --git a/tests/data/fungi30/golden/clust12762.txt b/tests/data/fungi30/golden/clust12762.txt new file mode 100644 index 0000000..ed2d387 --- /dev/null +++ b/tests/data/fungi30/golden/clust12762.txt @@ -0,0 +1,55 @@ +# 32 input proteins, 1 groups, 32 proteins in groups + +orthologGroup Fungi CompletenessScore=0.4667 ImpliedLosses=27 TCSScore=13.1875 + orthologGroup Dikarya CompletenessScore=0.5 ImpliedLosses=26 TCSScore=9.0645 + orthologGroup leotiomyceta CompletenessScore=0.5 ImpliedLosses=8 TCSScore=3.2 + orthologGroup Eurotiomycetes CompletenessScore=0.4 ImpliedLosses=1 TCSScore=0.0 + tr|A0A0D2EY06|A0A0D2EY06_CLAB1 + tr|A0A1L9V481|A0A1L9V481_ASPGL + orthologGroup Sordariomycetes CompletenessScore=0.5556 ImpliedLosses=6 TCSScore=2.0 + orthologGroup Sordariomycetidae CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A2P5HSU6|A0A2P5HSU6_DIAHE + tr|A0ABQ8NCJ7|A0ABQ8NCJ7_PYRGI + paralogGroup + orthologGroup Colletotrichum CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.0 + tr|A0A7J6J7J9|A0A7J6J7J9_COLFN + tr|A0AAX4I094|A0AAX4I094_9PEZI + orthologGroup Hypocreomycetidae CompletenessScore=0.3333 ImpliedLosses=2 TCSScore=0.0 + tr|A0A7J6IMQ8|A0A7J6IMQ8_COLFN + tr|A0A8H5QA53|A0A8H5QA53_GIBSU + paralogGroup + tr|A0A7C8IE91|A0A7C8IE91_9PLEO + tr|A0A7C8MPZ1|A0A7C8MPZ1_9PLEO + paralogGroup + orthologGroup Basidiomycota CompletenessScore=0.5 ImpliedLosses=7 TCSScore=1.8333 + paralogGroup + orthologGroup Agaricales CompletenessScore=0.6667 ImpliedLosses=1 TCSScore=0.0 + tr|A0A067T9C5|A0A067T9C5_GALM3 + tr|A0A0C3CLX1|A0A0C3CLX1_HEBCY + orthologGroup Agaricomycetes CompletenessScore=0.5 ImpliedLosses=2 TCSScore=0.0 + tr|A0A067TSK1|A0A067TSK1_GALM3 + tr|A0AAW0FQ88|A0AAW0FQ88_9APHY + tr|A0A067TFH6|A0A067TFH6_GALM3 + tr|A0A0F7RU98|A0A0F7RU98_9BASI + paralogGroup + orthologGroup Agaricomycetes CompletenessScore=1.0 ImpliedLosses=2 TCSScore=2.0 + orthologGroup Agaricales CompletenessScore=1.0 ImpliedLosses=1 TCSScore=0.8571 + paralogGroup + tr|A0A0C2XVI8|A0A0C2XVI8_HEBCY + tr|A0A0C3BYI6|A0A0C3BYI6_HEBCY + tr|A0A0C3C663|A0A0C3C663_HEBCY + paralogGroup + tr|B0D5Z8|B0D5Z8_LACBS + tr|B0DKP1|B0DKP1_LACBS + tr|B0DYK6|B0DYK6_LACBS + tr|A0A067SZQ2|A0A067SZQ2_GALM3 + paralogGroup + tr|A0AAW0F875|A0AAW0F875_9APHY + tr|A0AAW0FB02|A0AAW0FB02_9APHY + tr|A0AAW0FH18|A0AAW0FH18_9APHY + tr|A0AAW0FKT5|A0AAW0FKT5_9APHY + tr|A0AAW0GUV6|A0AAW0GUV6_9APHY + tr|A0A067SZ40|A0A067SZ40_GALM3 + tr|A0A067TAY0|A0A067TAY0_GALM3 + tr|A0A067TB36|A0A067TB36_GALM3 + tr|A0ABQ8PTL8|A0ABQ8PTL8_9FUNG diff --git a/tests/data/fungi30/rhogs/HOG_F0412257.fa.gz b/tests/data/fungi30/rhogs/HOG_F0412257.fa.gz new file mode 100644 index 0000000..40dc9a9 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0412257.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0413304.fa.gz b/tests/data/fungi30/rhogs/HOG_F0413304.fa.gz new file mode 100644 index 0000000..3b6d28e Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0413304.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0442015.fa.gz b/tests/data/fungi30/rhogs/HOG_F0442015.fa.gz new file mode 100644 index 0000000..3beefa6 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0442015.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0450975.fa.gz b/tests/data/fungi30/rhogs/HOG_F0450975.fa.gz new file mode 100644 index 0000000..3cab9d6 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0450975.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0453151.fa.gz b/tests/data/fungi30/rhogs/HOG_F0453151.fa.gz new file mode 100644 index 0000000..2b8da2e Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0453151.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0453717.fa.gz b/tests/data/fungi30/rhogs/HOG_F0453717.fa.gz new file mode 100644 index 0000000..1f92bc7 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0453717.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0455646.fa.gz b/tests/data/fungi30/rhogs/HOG_F0455646.fa.gz new file mode 100644 index 0000000..516203c Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0455646.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0455853.fa.gz b/tests/data/fungi30/rhogs/HOG_F0455853.fa.gz new file mode 100644 index 0000000..70c65ec Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0455853.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0457125.fa.gz b/tests/data/fungi30/rhogs/HOG_F0457125.fa.gz new file mode 100644 index 0000000..2503026 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0457125.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0459413.fa.gz b/tests/data/fungi30/rhogs/HOG_F0459413.fa.gz new file mode 100644 index 0000000..b33de47 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0459413.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0470062.fa.gz b/tests/data/fungi30/rhogs/HOG_F0470062.fa.gz new file mode 100644 index 0000000..52adb8c Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0470062.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0476910.fa.gz b/tests/data/fungi30/rhogs/HOG_F0476910.fa.gz new file mode 100644 index 0000000..0b93a67 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0476910.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0477170.fa.gz b/tests/data/fungi30/rhogs/HOG_F0477170.fa.gz new file mode 100644 index 0000000..fdd23de Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0477170.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0477229.fa.gz b/tests/data/fungi30/rhogs/HOG_F0477229.fa.gz new file mode 100644 index 0000000..8128971 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0477229.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0478382.fa.gz b/tests/data/fungi30/rhogs/HOG_F0478382.fa.gz new file mode 100644 index 0000000..5c0ced9 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0478382.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0480898.fa.gz b/tests/data/fungi30/rhogs/HOG_F0480898.fa.gz new file mode 100644 index 0000000..72b9d6e Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0480898.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0485529.fa.gz b/tests/data/fungi30/rhogs/HOG_F0485529.fa.gz new file mode 100644 index 0000000..d2d7e21 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0485529.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0489395.fa.gz b/tests/data/fungi30/rhogs/HOG_F0489395.fa.gz new file mode 100644 index 0000000..ed2f153 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0489395.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0489906.fa.gz b/tests/data/fungi30/rhogs/HOG_F0489906.fa.gz new file mode 100644 index 0000000..538c7da Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0489906.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0848191.fa.gz b/tests/data/fungi30/rhogs/HOG_F0848191.fa.gz new file mode 100644 index 0000000..9a682c6 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0848191.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F0848294.fa.gz b/tests/data/fungi30/rhogs/HOG_F0848294.fa.gz new file mode 100644 index 0000000..b02a83d Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F0848294.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_F1085561.fa.gz b/tests/data/fungi30/rhogs/HOG_F1085561.fa.gz new file mode 100644 index 0000000..4e4d7ef Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_F1085561.fa.gz differ diff --git a/tests/data/fungi30/rhogs/HOG_clust12762.fa.gz b/tests/data/fungi30/rhogs/HOG_clust12762.fa.gz new file mode 100644 index 0000000..fb4bf23 Binary files /dev/null and b/tests/data/fungi30/rhogs/HOG_clust12762.fa.gz differ diff --git a/tests/data/fungi30/species_tree.nwk b/tests/data/fungi30/species_tree.nwk new file mode 100644 index 0000000..6efb04a --- /dev/null +++ b/tests/data/fungi30/species_tree.nwk @@ -0,0 +1 @@ +((((((((UP000091918:1,UP000009169:1)Onygenales:1,(UP000191522:1,UP000184300:1)Aspergillaceae:1)Eurotiomycetidae:1,UP000053789:1)Eurotiomycetes:1,((UP000094444:1,UP001059893:1,UP000001805:1)Sordariomycetidae:1,(((UP000266152:1,UP000736672:1,UP000547976:1)Fusarium:1,UP000007115:1)Hypocreales:1,(UP001322277:1,UP000011096:1)Colletotrichum:1)Hypocreomycetidae:1)Sordariomycetes:1,UP000076632:1,UP000481861:1)leotiomyceta:1,UP001161438:1)saccharomyceta:1,UP000002485:1)Ascomycota:1,(((UP000054845:1,UP000245884:1)Exobasidiomycetes:1,UP000242770:1)Ustilaginomycotina:1,((UP000053424:1,UP000027222:1,UP000001194:1)Agaricales:1,UP001385951:1)Agaricomycetes:1,UP000016926:1)Basidiomycota:1)Dikarya:1,((UP000789901:1,UP001234581:1)Mucoromycota:1,UP000193920:1,UP001151295:1)Fungiincertaesedis:1)Fungi:1; \ No newline at end of file diff --git a/tests/data/orthoxml-0.5.xsd b/tests/data/orthoxml-0.5.xsd new file mode 100644 index 0000000..ca0ee8e --- /dev/null +++ b/tests/data/orthoxml-0.5.xsd @@ -0,0 +1,494 @@ + + + + + OrthoXML Schema + + This Schema defines the OrthoXML version 0.5rc1. + Author(s): Sanjit Roopra, Dave Messina, Fabian Schreiber, Thomas Schmitt, and Erik Sonnhammer. + SBC - Stockholm Bioinformatics Centre. 2011. More info at http://orthoxml.org + + + + + + + The OrthoXML root element. + + + + + + + + + + + + + + The source program/database of the file for + instance OMA or InParanoid. + + + + + + + The version number of the file. + + + + + + + The version or release number of the source + program/database at time the file was generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The species element contains all sequences of one + species. + + + + + + + + + + The NCBI Taxonomy identifier of the species to + identify it + unambiguously. + + + + + + + The internal taxonomy identifier of the species to + identify it unambiguously. Must match the id attributes of + the leaf taxon nodes in the taxonomy element. + + + + + + + + The name of the species. + + + + + + + + + A database element contains all genes from a single + database/source. + + + + + + + + + A Uniform Resource Identifier (URI) pointing to + the gene. In the simplest case one could imagine a URL which in + concatenation with the gene identifier links to the website of the + gene in the source database. However, how this is used depends on + the source of the orthoXML file. + + + + + + + Name of the database. + + + + + + + A Uniform Resource Identifier (URI) pointing to + the protein. + + + + + + + A Uniform Resource Identifier (URI) pointing to + the transcript. + + + + + + + Version number of the database. + + + + + + + + + A gene element represents a list of genes. + + + + + + + + + + + The gene element represents a single gene, protein + or transcript. It is in fact a set of identifiers: one internal + identifier that is used to link from geneRef elements in ortholog + clusters and gene identifiers, transcript identifiers and protein + identifiers to identify the molecule. The proper term for this + element would therefore rather be molecule. However, as the general + purpose of orthoXML is to represent orthology data for genes the + term gene is used instead. Gene, protein and transcipt identifiers + are optional but at least one of the three should be given. The + source database of the gene is defined through the database element + in which the gene element lies and the identifiers should stem from + this source. + + + + + + Identifier of the gene in the source database. + Multiple splice forms are possible by having the same geneId more + than once. + + + + + + + Internal identifier to link to the gene via the geneRef elements. + + + + + + + Identifier of the protein in the source database. + + + + + + + Identifier of the transcript in the source database. + + + + + + + + + The taxonomy of the species analysed in this orthoxml file. Either you specify + the taxonomy as a nested grouping of taxon elements, or link to the ncbi taxonomy + by defining an ncbitaxonomy element within the taxonomy element. + + + + + + + + + + + + A node in the taxonomy, identifying a clade of species + + + + + + + + + (Internal) identifier of the clade / taxon. + + + + + + + Name of the clade / taxon. + + + + + + + + + crosslink to the ncbi taxonomy + + + + + + A link to the used ncbi taxonomy. + + + + + + + The version of the ncbi taxonomy that is used. + + + + + + + + + A list of score definitions. + + + + + + + + + + + Represents the list of ortholog groups. Normally + the top level is an ortholog group, but in order to express + a duplication at the root, a paralog group may also be the top level. + + + + + + + + + + + + + + A group of genes or nested groups. In case of a + orothologGroup element, all genes in the group or in the nested groups are + orthologs to each other i.e. stem from the same gene in the last common + ancester of the species. In case of a paralogGroup the genes are + paralogs to each other. Subgroups within the group allow the + represention of phylogenetic trees. For more details and examples + see http://orthoxml.org/orthoxml_doc.html. + + + + + + + + + A group can may contain two or more of the three alternatives + geneRef, paralogGroup, and orthologGroup. By combining these, + complex phylogenies are possible. + + + + + + + + + + + + Identifier for the group in context of the resource. This attribute is + not required but if your resource provides identifiers for the ortholog + groups we strongly recommend to use it at least for the top level groups. + + + + + + + Taxonomy identifier that defines the speciation event of reference for this + group. The taxonId references either a node in the defined taxonomy structure + (where the nummeric IDs can be defined as needed) or it should be the taxonomy + identifier in the NCBI taxonomy. + + + + + + + + + The geneRef element is a link to the gene + definition under the species element. It defines the members of an + ortholog or paralog group. The same gene can be referenced muliple + times. The geneRef element can have multiple score elements and a + notes elements as children. The notes element can for instance be + used for special, ortholog-database-specific information (with InParanoid, + for example, we could use it to mark the seed orthologs). + + + + + + + + + + Internal identifier for a gene element defined under the species element. + + + + + + + + + The scoreDef element defines a score. One of the + concepts of orthoXML is to be as flexible as possible but still + uniformly parsable. Part of this is to allow every + ortholog resource to give their own types of scores for groups or + group members, which is done using score elements. Score elements + can be defined to apply to either groups or geneRefs. It is possible to define multiple scores. + + + + + + An internal identifier to link to the + scoreDef from a score element. + + + + + + + Description of the score. + + + + + + + + + The score element gives the value of a score and + links it to the scoreDef element, which defines the score. It can be + child of a group or a geneRef element to allow scoring on + different levels. + + + + + + An identifier linking to the scoreDef element, + which defines the score. + + + + + + + The actual value of the score. For instance a + confidence score of a group member. + + + + + + + + + Key-value pair for group annotations, for instance + statistics about the group members. + + + + + + The key of the key-value annotation pair. + + + + + + + The value of the key-value annotation pair. Optional + to allow flag like annotations. + + + + + + + + + The notes element is a special element, which + allows adding information that is not general enough to be part + of the standard. I.e. something specific to a particular ortholog + database or algorithm. Notes elements will not be validated, + so any child elements are legal. Notes elements can be children of the + root element orthoXML, the species element, the orthologGroup + element, the paralogGroup element, or the geneRef element. + + + + + + + diff --git a/tests/data/subhog/conflict/HOG_D0625237.fa b/tests/data/subhog/conflict/HOG_D0625237.fa new file mode 100644 index 0000000..6eb3bb8 --- /dev/null +++ b/tests/data/subhog/conflict/HOG_D0625237.fa @@ -0,0 +1,246 @@ +>SALSA97694||8030||1226039127 SALSA97694 transcript_id=ENSSSAT00000139120.1 +ALSTMRPTLRKVVLIAQMVGDRIARMDNSYREPISPVEHLAICLRFLATGDSYRTIGVGR +STVAGIVPSVAQAIWDCLVGEYMPVPKEEAWRAIAPQCLGSQFYNYKGTYSVVLLAVVDG +IYCFRVVDVGAYDKGSDGGILRDSAFGQLDGLFTDGFHHLHYSLGLGPVPHVFVGDEAFP +LIPNLMRPYVGRQLPLPKPVRIFNKRLSRARLVVECACGILAARPRVLHNYLRMSFQEPL +QMEKGTTNGYLPDVTREGANNAPRQALQVREKLNTYLSSPAGEVPWQNAVE +>SALSA119682||8030||1226046831 SALSA119682 transcript_id=ENSSSAT00000124775.1 +SDQYYVILICFILIFFLSKTKTFLSDPNLLNGSVYYNLPSWLDARMIIFSCISILSDWFP +QNLLQCIMETCGAIERRMLVTHLPQPTEETWRQTSQEFNEKWDFPNCLGSDDGKHIMITK +PPNSGRHFLNYKLTFFVVLLALVDAKYRFTAIQVGDLGRNSDSGIYANSPLGRAMASKTL +QVSQDAFPPVGEDLGNIPNTMVGVTLKKSEEKALGILAARWRILYKEINLLPCKVDTLVV +AMCILHNFLTKPCDVEMWLQQSGAGMRQDMRSMAIQANRAGQEVVSVREKFTKYFTSVAG +RIDFQNRMVVARPA +>GADMO15129||8049||1306005642 GADMO15129 transcript_id=ENSGMOT00000033255.1 +MHGSVRISDSTRVNLWVHDINRGRQQYGAFHSLIQELRFDNARFSAYFRLDKSQFETLLR +IGAPSIIRQDTHMRQAISPEERLRRLLHFLLLHGRLLENLNFRVGISTVSSIVPDVATAI +WDCLVEEFMAQWIFTLCCGALDGKHVVIKAPAHSGSMYFNYKGTFSLVLLAVVDAEYCFR +VIAVGGYGRTSDGGILANSAFGAALRSGTLQLPADLPLTGADHRGPQPHVFVADEAFPLR +NKLMRPFPGRNLTREPRLVVENAFGILSSQWRIYRRALEVKVEVAEKCVKATCVLHNFMR +KTTPPPSALRGKIPAGEVDPPPGLGRVAANNSAREAIRIREAFTSYFPTEGTVPWQDNV +>GADMO16096||8049||1306006028 GADMO16096 transcript_id=ENSGMOT00000033168.1 +MSAPKFDDLLSRVIRYLPDSHQNHRNPIGASQRLAVTLRYLATGIGLQALSASYKMGTST +VSGIVEEMCSAIWDALQGEFMAFPSRGQWQDIAEEFWRQWQFPLCVGAIDGKHVRLRAPA +RSGSDYYNNKGFFSIALMAAADANYRFIYVDVGAYGRESDGGVLGRSAFGSRLAEGNLNL +PAPAVLPGTTTPSPYFFLGDEAFPLQVNLIVPIQDRKRIYNYRQARARRVVENAFGILAA +RFRIFGRLIDCQPEKAVKIVKACVALHNFLASTERARYMPASFVDTTADTGEVQPGEWRQ +ARPSQRAALVREQLADYFLSDAGVFPHQYNVLRLGTDQ +>GADMO18221||8049||1306006826 GADMO18221 transcript_id=ENSGMOT00000041767.1 +MANQLQLIALALAFEEEEGEEQPRRRLWVHDILRNRESQGAFGHLIQELRLDDARFKNFF +RLDKSQFEDLLRRIGPEISKKQTHLRETISPDERLCICLRYLATGNSYRSIAFHFRVGIS +TVAGIVRSVCEAIWDCLSGEFLAFPTEAEWRKIAEDFARMWAFPNCVGAMDGKHVVIEAP +PLSGSLYFNYKKSFSIVLLAVVDAHYRFRVVDIGAYGKNSDGGTLAASAFGSALRQVTLN +LPGDAPLPGAENLGPIPHVFLADEAFPLRRNLLRPYPGHTHGEKRVFNYRLSHARRMVEC +TFAESVVKATCILHNFLRWGGATEALTGPAEPSAGMRSIGRVGSNNASNEAITVRTNFTT +YFSSEAGQVPWQNRPLHIPHALQ +>GADMO44357||8049||1306016784 GADMO44357 uncharacterized LOC115546003 [Source:NCBI gene (formerly Entrezgene);Acc:115546003]; transcript_id=ENSGMOT00000048881.1 +MSRMRKLALVLALNDNDEDNDEPSRLWVHDINRGRQRYGAFHSLIQELRFDNARFSAYFR +LDKSQFETLLRIVAPSIIRQDTHMRQAISPEERLSICLRYLATGDSFRSIAFAFRMGAST +VAGIVHQVCTAIWDCLLPDYMPIPDKAAWRKIATGFQQLDFPNCIGAMDGKHVVIEAPPS +SGTLYYNYKGTFSIVLLAVVDARYCFRLVDIGAYGRNSDGGTLSSSAFGTALRQNTLGIP +DDSILPGAEHLGPMPHVFLADEAFPLRRNIMRPYPGYNTGEKKVFNTRLSHVRRMVECAF +GILASQWRVYRRVLCVSPEVAENVVKATCMLHNFLRWDASTTIPARSSTGTSTAAPSQGL +QNAPRLGTNNAGRDAVAVREKFAQYFMSEAGRVPWQDNI +>GADMO52829||8049||1306020037 GADMO52829 transcript_id=ENSGMOT00000028584.1 +MCCRFLASGDSFRTIANSFRVGTSTVASIVSDVVTAIWDCLVEEFMAVPTAEEWRVVAER +FEERWNFPLCCGAIDGKHVVLKAPANSGSQFFNYKGTFSIVLLAVVDADYCFRVIDVGGY +GRTSDGGILANSAFGQALHAGDLQLPADRALPGAEHRGPLPHVFVADEAFPLRTNLMRPF +PGRVLPPERRVFNYRLSRARLVVEDAFGILSSQWRIYRRVIEVRPELAERCIKATCVLHN +FLRRTAPTAAVRECIPGGAVEPLPRLGRVAANNAGREAIRIRETFTSYFSEEGTVSWQDD +I +>GADMO52865||8049||1306020050 GADMO52865 transcript_id=ENSGMOT00000032388.1 +MTELLLLYRYLATGDSFRSVAFCFRVGASTAAGIVNEVCQPKPDAAEWRKIAAEFSHLDF +PNCLGAMDGKHVVIEAPPSSGSLYYNYKGTFSIVLLAVVDAKYRFRVVDIGAYGRNSDGG +TLSASAFGTALRQNTLGIPADSPLPGAEHLGPMPHVFLADEAFPLRQNIMRPYPGHNTGE +KNVFNLRLSHVRRMVECAFGILASQWRVYRRVLGVSPKVAENIVKATCMLHNFLRWDGNP +GLPGGAAHVVRDKLCQYFNSPLGSVQWQNRMV +>GADMO52890||8049||1306020064 GADMO52890 transcript_id=ENSGMOT00000019214.2 +MARNRQLALALALADDEEKHVPRRLWLHNITRGRQQQGAYHNLVQELRFDDARFAAYFRL +NKCQFEQLLRIVAPSIAKLDTKFTQAISPEERLCICLRYLATGDSFRSVAFCFRVGASTA +AGIVNEVCQPKPDAAEWRKIAAEFSHLDFPNCLGAMDGKHVVIEAPPSSGSLYYNYKGTF +SIVLLAVVDAKYRFRVVDIGAYGRNSDGGTLSASAFGTALRQNTLGIPADSPLPGAEHLG +PMPHVFLADEAFPLRQNIMRPYPGHNTGEKNVFNLRLSHVRRMVECAFGILASQWRVYRR +VLGVSPKVAENIVKATCMLHNFLRWDGNPGLPGGAAHVVRDKLCQYFNSPLGSVQWQNRM +V +>ORYJA15604||123683||1611006442 ORYJA15604 transcript_id=ENSOJAT00000033332.1 +MHQSLLRVPRFFPIFLCPCSWLHHSRNGPVLAVPTIVMFLLFFKELVSIHIYYITNLSEC +YYLRGRAPLPPGQKLTITLRYLATGHSYRSLAFEFRVAHNTMSQFLPRVCRAIYDEYRRE +LFHRPDNPDEWREVATRFGQKWNFHHCCGAIDGKHVAIKKPKKSGSLYYNYKGFHSIILL +AVVDADYRFIWANVGANGSASDCGVFNRSELHTKLEENTLDVPYFLVGDDAFPLRTFMMK +PFSQRGLTNAERIFNYRLSRVRRVVENAFGILAHRWRCLLTTLQQTPDNCTEIVLGCMTL +HNLLRSRAPGLQAGEVDTEDADGNVVPGAWRQTVDPTDFPTVGGRRPTRAGKLQRVYLSD +YYNAHFCQLYSYLCVLVICVIYM +>PYGNA33577||42514||1649023172 PYGNA33577 transcript_id=ENSPNAT00000016801.1 +WLSGPSFLHCAQEQWPKNPDPGMLDIDDLEIRRSSQLGQKHNYSSTQTQKPMHSPAVHAQ +HAFLWRLKHPVLGGGHRGQIIPSVNRIGLNVNSREVCVAVPVAQKKRLSRRWGVWPLNRS +RWKDRECVSLVRPMRGLDVERHFQYFRMTAAKYDALLHRVHAYITRGKSHCSPLSASERL +AVTVRYLATGMSHRALSTIYKLGSSTLGQAIWVALDGDFVPYPSEWRGTAHDFWKLWNFP +QCLGTVDGKHVRITAPPNLGSHFFSITKVTFQLFSWQHVVPPTDFDTSTSVLSGGKKGNF +LCLRRIFSRGTDRQSPYVFAGDEAFPLKENLLRPYPGRQGCNYILSEVDANILSLLQKME +TRNNFPASFWRPSSAAPICAAYAAYPLFAPLQVDV +>ESOLU01315||8010||2320000641 ESOLU01315 transcript_id=ENSELUT00000004604.2 +MSVNKFEALLQMLAPHLSRQRTNFREPIAPEQRLAVCLSTEDSFTSIAASFRLGVSTVGK +MVKDTCNVIWEHMREAFLPSPSEDLWKNTARRLQEMWDFPNGIGALDGKHIFHNYKGTFS +VVLLALVDADYRFLAVDVSAFGSNSDGGIFANSRLGRDLQSGVWCHHHLPSHLHQSYEAF +PMKPYLLRPYPGRRIPEEMHIFNCRLSRARRISENAFGILTQRWRVYQHQMQVGPDTADG +IIKASYILCNYLRPEAHHQGEEDMSRDEVEDESVLGPIRSLWGNRASAEVLSVRDTFKNY +FVSPVLTGSLAVWPCAQLNNISTHKLV +>ESOLU30140||8010||2320010645 ESOLU30140 transcript_id=ENSELUT00000038154.2 +VFLSSGDSFTTIAANRFRLGVSTVGKIVKDTCNVIWEHMRETFLPSPSVDLWKKTARRFQ +ERWNFPNYIEGLDGKHVVIQAPASSGSQFHNYKRTFSVVLLVQVDTVYRFLAVDVGAFGS +NSDGGIFANSRLGRDLQSGVLHVPLPSPLPSAPELGPVPFVMVGDEAFPMKPYLLRPYPG +QGIPEEMGIFNYRLSRRRMQVCPDTADGIIKASCILCNYLWPEVHHQGEEDMSRDEVEDD +SVLGPIRSLRGNQASAEALRVRDTFRNYFLSPVRQVPWQYNHLSDGQFSVVPGKCSKL +>ESOLU70085||8010||2320024190 ESOLU70085 zgc:194221 [Source:ZFIN;Acc:ZDB-GENE-081022-77]; transcript_id=ENSELUT00000061388.1 +MYSIYFSHASMVIITNILLNRSYVHTILQARKEPGYYHHLVQKLRLDCARFRYYYGMDTT +QFHMLLSKVGPFIERQNTFSREPMKLGQRPAIYLRTIAYSYNVGISTVVGIVAEVCRVIW +EGLEMGYMPEPTTEDWRTVEREFRKLWNFPNCVGALDAKHVAIRCPRPFRVPVFYYKEGY +SIVLLAVVDAGSLHLHAGARLPGAEHLGPTPHVIVTSQLNFNHSLSRARMTWQVYQRAMA +VSPDKADWIVKDTCMLYNLLHQEPRSTHGIGAYAGHSAGNQGTVEALAVRDKLTVYFSSP +AGALQ +>ESOLU71203||8010||2320024633 ESOLU71203 transcript_id=ENSELUT00000047242.1 +MSQSAFTKHLALNPKEQTTIVLKFSVENLKCLCFFYIFLTSFHLGVSTVEKMVKDTCNVT +WEHMRETFLPSPSEDLWKNTARRFQERWNFPNCIGALDSKHVVIQAPGNSGSQIHNYKGT +FSVVLLALVDTDYRFLAVDVGAFGSNSDGVIFAKSCLGRDLQSGVWHVPPPSPLPSAPEL +GLPMKPYHLRPYTGRCIPEEMRIFNYSLSRACRISENAFVIFTQSWRVYQCPDTADGIIK +ASCILISLIKIDPCAGKNGSVRGQEEFKDHHQGHPDESGQLWYQHLKEDTPVDTVQGWSL +WTPTKEDSTSPSHTNKVYLQIASQGRYLIWGFLNIKLCPLMFAIGQFGDILV +>tr|B7QMA6|B7QMA6_IXOSC||IXOSC||2498019800 tr|B7QMA6|B7QMA6_IXOSC DDE Tnp4 domain-containing protein OS=Ixodes scapularis (Black-legged tick) (Deer tick) OX=6945 GN=IscW_ISCW013628 PE=3 SV=1 +QTHQRRILNYRLSRVRRVVENTFGIMSQRWRILRRSFKATNENVVRIVSACVALHNFLLQ +ESAVSHAA +>tr|A0A3B3H866|A0A3B3H866_ORYLA||ORYLA||2923001177 tr|A0A3B3H866|A0A3B3H866_ORYLA DDE Tnp4 domain-containing protein OS=Oryzias latipes (Japanese rice fish) (Japanese killifish) OX=8090 GN=ENSORLG00000023068 PE=3 SV=1 +TTEKYIACCPVVRRKRRRFWVHPIITRREEHGEFYRLIQELKMYHERFRGYFRMSISEFE +NLLQQLAPSLTKEQTHYRKTIDPEQRLAVCLRFLSTGDSYRTIASSFRLGVSTVASIVRE +TCDALWHCLRDEHLPVPTEEMWRSTARRFHERWNFPNCLGAMNGKHIFIQAPANSGSLYF +NYKGTFSVVLLALVDADYRFLVVDVGSYGSNSDGGIFANSGLGKALRDGTLNVSPPSELP +GAPELGKLNHVIVADEAFPLKPYLLRPYPGRCLPTEKRIFNYRLSRARRISENVFGILSQ +RSRVFQRTLQVQPSVIDKVVKAACGLCNYLRPKGEDQNHATEDDHDCEQPLQGFEHCRGQ +RASVEAQNVRELYKEYFNSPAGEVAWQYEHVNHALGNR +>tr|A0A3B3HNM8|A0A3B3HNM8_ORYLA||ORYLA||2923004237 tr|A0A3B3HNM8|A0A3B3HNM8_ORYLA DDE Tnp4 domain-containing protein OS=Oryzias latipes (Japanese rice fish) (Japanese killifish) OX=8090 GN=ENSORLG00000029368 PE=3 SV=1 +MDISSTSSSSSSSSDEEIIVLLNAERPRKRRRFWVHPIITRREEHGEFYRLIQELKMYHE +RFRGYFRMSVSEFENLLQQLAPSLTKEQTHYRKSIDPEQRLAVCLRFLSTGDSYCTIASS +FRLGVSTVASIVRETCDALWHCLRDEHLPNFPNCLGAMDWKHIFIQAPANSSSLYFNYKG +TFSVVLLALVDADYRLLVVDVGSYGSNSDGGIFANSGLGKALRDGTLNVPPPSELQGAPE +FGKLNHVIVADEAFPLKPYLLRPYPGRRLPTEKRIFNYRLSRARRISENVFGILSQRFRV +FQRTLQVQPSVVDKVVKAACVLCNYLRPKGEDQNHATEDDHDCEQPLQGFEHCRGQRASV +EAQNVRELYKEYFNSPAGEVAWHYPVPIEEKAAP +>tr|A0A3B3HVD3|A0A3B3HVD3_ORYLA||ORYLA||2923005577 tr|A0A3B3HVD3|A0A3B3HVD3_ORYLA DDE Tnp4 domain-containing protein OS=Oryzias latipes (Japanese rice fish) (Japanese killifish) OX=8090 GN=LOC110017219 PE=3 SV=1 +FTKKKRACIYARRKRRRFWVHPINTRREEHGEFYRLIQELKMYHERFRGYFRMSVSEFEN +LLQQLAPSLTKEQTHYRKPIDPEQRLAVCLRFLSTGDSYRTIASSFRLGVSTVASIVRET +CDALWHCLRDEHLPVPTEEMWRSTARRFHERWNFPNCLGAMDGKRISIQATANSGSLYFN +YKGTFSVVLLALVDADYRFLMVDVGSYGSNSDGGIFANSGLGKALRDGTLNVPPPSELPG +APELGKVNHVIVADEAFPLKPYLLRPYPGRRLPTDKRVFNYRLSRARRISENVFGILSQR +FRVFQRTLQVQPSVVDKVVKAACVLCNYLRPKGEDQNHATEDDRDCEQPLQGFEHCRGQR +ASVEAQNVRELYKEYFNSPAGEVA +>tr|A0A3B3HX08|A0A3B3HX08_ORYLA||ORYLA||2923005892 tr|A0A3B3HX08|A0A3B3HX08_ORYLA DDE Tnp4 domain-containing protein OS=Oryzias latipes (Japanese rice fish) (Japanese killifish) OX=8090 GN=ENSORLG00000026918 PE=3 SV=1 +WVHPIVTRREEHGEFYRLVQERKNNVRQTDTLPSFTLLFTSINVVKTCVNFLSTGDSYRT +IVSSFRLGVSTVASIVSETCDALWHCLRDEHLPVPTEEMWRSTARRFHERWNFPNCFRAL +GGKHIFIQAPANSGSLYFNYKGTFSVVLLALVDADYRFLVVDVGSYGSNSDGGIFANSVL +GKALRDGTLNVPPPSELPGAPELGKVNHVIVADEAFPLKPYHLRQRFRVFQRTLQVQPSV +VDRVVKAACALCNYLRPNGNDQNCAAEDDHDCEQPLQGFEHCRGQRASVEAQNVRELYKE +YLNSLPGEVLPPVPGSLSLLGR +>tr|A0A3B3IE60|A0A3B3IE60_ORYLA||ORYLA||2923009276 tr|A0A3B3IE60|A0A3B3IE60_ORYLA DDE Tnp4 domain-containing protein OS=Oryzias latipes (Japanese rice fish) (Japanese killifish) OX=8090 GN=ENSORLG00000029737 PE=3 SV=1 +GSWIRCLLKSALGSTPVFCFVLHSVKAGKRGVEPPEWNEQVFMRLSESYKENMNISSTSS +SSSSSSDEEIIVLLNDERRRKRRRFRVHPIVTRREENGEFNRLVQELKMYHERFGGYFRM +SVSEFENLLQQLFGRVFTVSLFTLLFTSMNAVKTYVNFLSTVDSYRTIASSFRLGVSTVA +SIVSETCDALWHCLRDEHLPVPTEEMWRSTARRFHERWNFPNCLGAMDGKHIFIQAPANS +GSLYFNYKGTFSVVLLALFNADYRLMVVDVGSYGSNSDGGIFFNSVLGKALRDGTLNVPP +PSELPGAPELGKVNHVIVADEAFPLKQYLLRPYPGRRLPTDKRIFNYRLSRARRISENVF +GILSQRFRVFQRTLQVQPSVVDKVVKAACALCNYLWQWASVEAQNVRELYKEYFYSPAGE +VAWQYDHVNHALGNR +>tr|A0A3B3IL14|A0A3B3IL14_ORYLA||ORYLA||2923010609 tr|A0A3B3IL14|A0A3B3IL14_ORYLA DDE Tnp4 domain-containing protein OS=Oryzias latipes (Japanese rice fish) (Japanese killifish) OX=8090 GN=ENSORLG00000027214 PE=3 SV=1 +MTTKQIYIEYPPSFSRQTISCCLISYCLLQSDCSKEQTHYHKPIDPEQRLAVCLSSIVSE +TCDASWHCRRDEHLPLPTEEMWRSTARRFHERWNFPNCLGAMDGKRIFIQAPANSGSLYF +NYKDYRFLVVDVGSYGSNSDGGIFANFVLGKALRDGTLNVPPPSELPVAPELGRVNNVIV +ADEAFPLKPYLLRPYPGHHLSTDKRIFNYCLSRARTLQVQPSVVDKVVKAACALCNYLRP +KGEDQNHATEDDHDCGQPLQGFEHCRGQRASVEEYFNSPAGEVAWQYEHVNHALGNR +>ICTPU26284||7998||3169017180 ICTPU26284 transcript_id=ENSIPUT00000007877.1 +MALIETQKYAGTQGGAAQLFKRPRKRRRYWIHPILKRREEHGEFHRLIQELKLYHEGFQR +YFGMSVSDFENLLQMLAPSLRKEHTHYRKPIEPEQRLTVCLRFLITGDSYHTIASSFRLG +VSTVSLIVSETCDAIWHCLRDEQLPVPTEEMWRNTARRFNDRWNFPNCLGAMDGKRVLCN +YKGTFSVGLLALVDADCRFLMVDVGSFGSNSDGDIFADSALGKALRDGSLNVPPPSELPG +APELGKVKHVIVADEAFPLKPYLLRSYPGRRLPMDMRIFNYRLSRARHVSENAFGILSRR +FRIFRRSLQVRPAVVDKVVKAACALCNYLRSDGSDQDHAPEDDHDYARTLQAFKRCRGRR +ATAEAQNVRELYKDYFNSPAGEVAWQYDHVNHSSEI +>ICTPU35886||7998||3169023446 ICTPU35886 transcript_id=ENSIPUT00000015975.1 +AFSIELDCCVSNYLQRKCERRSVWVHKIIQIRKQHGEYHRLVQELQLDEVQFQQYFRLDR +TQFDELLLKEDTKCHRAISPGQRLTICLRYIATGDSYHSIAFSYWVGHSTVAGIVAAVAK +AIWDSLVEDYMPFHHRWNSPNCVGYIDGKHVVIQNPDNYGSLYYNYKGTFSIVLLAVVDA +KYSFCIIEVVVMGGPVMPFPGSTLPNKQNNTFGILAAHPANAETCVLYKFLRRVGTNNAA +WEAMRVRETIEDYFCAEGAVPCQPRMLPLSLSLHFI +>BUFBU03090||8384||3171001724 BUFBU03090 protein ALP1-like +MTRGVYSTLYQELRKHPVKFAEYLRVSVASFDDLSQRLRRRLRRKDTKFSLSVTPEERLM +VTLRFLASGESFSSLHFQFRLGKSIISYIVRDTCRAIWNLMREEFLPHPTCQQWISIADK +FHEVTQFPNCIGAVDGKHVRLLKPPRSGSSFFNYKKYFSVILMAIVDANYKFVAVDIGAF +GRTNDSRVFKNSNMGRRLYSGNFGIPEPRPLPGTTNPALPFVLVGDEAFQMCGNLLKPYA +SRGLDYRKRVFNYRLTRARRMVECAFGILTAKWRIFTRAIQLKTDTVDDVIKACVVLHNY +VLTKEPLPTEPLSLNPPGDHIETGPRSTVAVSRMRDQFAEYLISSAGSVLWQDEHI +>BUFBU03484||8384||3171001940 BUFBU03484 protein ALP1-like +MPNDTLYQELRKHPVKFSEYLCVSVASFDDLSQRLRRRLRRKDTKFRRSVTLEERLMVTL +RFLATGESFSSLHFQFRLGKSTISYIVKDTCQAIWNLLREEFIPHPTCQQWISIADKFYE +VTQFPNCIGAVDGNHVRVLKPPRSGSSYFNYKKFFSVILMAIVDAHYKFVAVDIGAFGRT +NDSRVFKNFNMGRRLYLGNFGIPEPRPLPGTTNPALPFVLVGDEAFQMCGNLLKPYASRG +LDYRKKVFNYRLTHAIRMVECAFGILTAKWRIFTRAIQLKTDTVDDVIKACVVLHNYVLT +KEPLPTEPLSLNPPGDHIETGPRSTAAVSRMRDQFAEYFISSAGSRPWQDEHI +>BUFBU05594||8384||3171003097 BUFBU05594 protein ALP1-like +MTRGVYSTLYQELRKHPVKFAEYLRVSVASFDDLSQRLRRQLRRKDTKFRRSVTPEERLM +VTLRFLASGESFFSLHFQFRLGKSTISYIVKDTCRTIWNLLREEFLPHPTCQQWISIADK +FYEVTQFPNCIGAVDGKHVRVLKPPRSGSSFFNYKKYFSVILMAIVDANYKFVAVDIGAF +GRTNDSRVFKNSNMGRRLYSGSFGIPEPRPLPGTTNPALPFVLVGDEAFQMCGNLLKPYA +SRGLDYRKRVFNYRLTRARRMVECAFGILTAKWRIFTRAIQLKTDTVDDVIKACVVLHNY +VLTKEPLPTEPLSLNPPGDHIETGPRSTVAVSRMRDQFAEYFISSAGSVLWQDEHI +>BUFBU09183||8384||3171005325 BUFBU09183 protein ALP1-like +MTARVEAMVKLPRPGTLQDLRKFLGVANFMREFIEDFAAKAKPLYELLRGEEPSIKGCFD +ELLGLLGTRLECQDTFMRDSIPPAERLIITLRYLATGHSLISLHYTFLIGKSTASVIVKE +TCIAIWHVLHAAVMAQPNKEEWLKIADTFQQTCNFPNCVGAIDGKHIRVLKPMRSGSQFF +NYKKYFSVVLFAVCDANYCFRYIDVGSYGSSSDSAVFGHSDFGQMLITNALDLPGNTTLP +GTTYPSMPFVFVDDEAFALGEHLMRPYSSRGLSIARRVFNNRLTRARRVVKCAFGILSNK +WRVLHSPIHLKLETAISAVKAACALHNFVRQRDGFNFEDSLSHSMESPNRSGVRGTTHGA +AIRDHFTAYFMSPQGELPWQM +>BUFBU13753||8384||3171007900 BUFBU13753 protein ALP1-like +MRMSSMKRRRRYWVHPITSCRMTQGVYSTLYQELRKHPVKFTEYLCVSVASFDDLSQRLR +RRLRRKDTKFRRSVTPEERLMFTLRFLASGESFSSLHFQFRLGKSTISYIVKDTCRAIWN +LLREEFLPHPTCQQWISIADKFYEVTQFPNCIGAVDGKHVRLLKPPRSGSSFFNYKKYFS +VILMAIVDANYKFVAADIGAFGRTNDSRVFKNSNMGRRLYSGNFGIPEPRPLPGTTNPAL +PFVLVGDEAFQMCGNLLKPYASRGLDYRKRVFNYRLTRARRMVECAFGILTAKWQIFTRA +IQLKTDTVDDVIKACVVLHNYVLTKEPLPTEPLSLNPPGDHIETGPRSTVAGPICYDYHV +SSWLMQADKCTIITHISETSLKLI +>BUFBU17198||8384||3171009886 BUFBU17198 protein ALP1-like +MSINSFDELLGLMSIHLQWQETFMRDSIAPVERMLITLRYLATGQSLVNLHYAFMIGKST +ASDIIRETCSAIWDVLHAVVMSKPNKDEWQKIAHTFYQTCNFPNCVGALDGKHNRVIKPK +RSVSRFFNYKKYFSFVLFVVSDANYCFRYIDVGSYGSSSDSAVFAHSEFGQMLNTNALDL +PGNTKLPGTADPAMPFVFVGDEAFALGRGLCLERRVLNYRLSRARRVVECAFGILSNKWR +FLHSPINLKLENAISAVKAVCCLHNFVRQRDGFNFEDSLVHSMESTNCTGVRGITHGATI +RDKFAAYFISPEGEVPWQLQAIQA +>BUFBU21770||8384||3171012486 BUFBU21770 protein ALP1-like +MTRGVYSTLYQELRKHPVKFAEYLCVSVASFDDLSQRLHRRLRRKDTKFSRSVTPEERLM +VTLRFLASGESFSSLHFQFRLGKSTISYIVKDTCRAIWNLLREEFLPHPTCQQWISIADK +FYEVTQFPNCIGAVDGKHVRVLKPPRSGSSFFNYKKYFSVILMAIVDANYKFVAVDIGAF +GRTNDSRIFKNSNMGRRLYSGNFGIPEPRPLLGTTNPALPFVLVGDEAFQMCGNLLKPYA +SRGLDYRKRVFNYRLTRARRMVECAFGILTAKWRIFTRAIQLKTDTVDDVIKACVVLHNY +VLTKEPLPTEPLSLNPPGDHIETGPRSTVAVSRMRDQFAEYFISSAGSVLWQDEHI +>BUFBU33069||8384||3171018739 BUFBU33069 protein ALP1-like +MTRGVYLTLYQELRKHSVKFAEYLRVSVASFDDLSQRLRRRLRRKDTKFRRNVTPEERLM +VTLRFLASGESFSSLHFQFRLGKSTISYIVKDTCRAIWNLLREEFLPHPTCQQWISIADK +FYEVTQFPNCIGAVDGKHVRLLKPPRSGSSFFNYKKYFSVILMAIVDANYKFVAVDIGAF +GRTNDSRVFKNSNMGRRLYSGNFGILEPRPLPGTTNPALPFVLVGDEAFQMCGNLLKPYA +SRGLDYRKRVFNYRLTRARRMVECAFGILTAKWRIFTCAIQLKTDTVDDVIKACVVLHNY +VLTKEPLPTEPLSLNPPGDHIETGPRSTVALSRMRDQFAEYFISSAGSVLWQDEHI +>SINGR96692||75366||3278038889 SINGR96692 zgc:194221 [Source:ZFIN;Acc:ZDB-GENE-081022-77]; transcript_id=ENSSGRT00000087432.1 +MLTQCRVLALVLALDGGVVEPRRKKGRTRRSVWVHEILGARKQLSEYHRLVQELKLDVSR +FKEYFRMSRHVAIQAPSSTGSMYYNYKGSFSIVLLAVVDARYLFRIVDVGAYGKSSDGGT +LYASDFGKALHQGGLDLPEDAPLPGAEDLQPVPHVFVGDEAFPLKKNLLRPYPENHLDRE +KRVFNYRLSRAMRMVECTFGILAAQWRLYHRVLGVSPKVADAVVKATCILHNFLQEESCV +PLPLAQQLAMQPIQRVGSNNASREALEIRQRYTSYFSSPAGEVAWQHSVL +>SALTR91004||8032||3317034283 SALTR91004 transcript_id=ENSSTUT00000072610.1 +MGKRTQTLDRGTLPRRPASRSHLFPVDVETPLSILVRASLHCSVKKRLEAIERRMLATHL +PRPTEETWRHISQEFNEKWDLPNCLGLVDGKHIMITKPANSRSRFFYNKGTFSMVLLALV +DAKYRFTAIQVGGFERNSDGGVYANSPLGRSMASKTLMIVEKAFRILAARWRILYKKINQ +LASKFDTRVVAMCILHNFLTKPCDVEMCLQQSGAGMRQDMRSIAIQANRAGQDAVCQGKV +ERFSPLWQAEWASRTEWL +>SALTR95034||8032||3317035869 SALTR95034 transcript_id=ENSSTUT00000007594.1 +SDQYYVILICFILIFFLSKTKTFLSDPNLLNGTWHLETLCRLAFTYRLGFQTVLQCIMET +CGAIERRMLVTHLPQPTEETWRQTSQEFNEKWDFPNCLGSDDGKHVMITKPPNSGRHFLN +YKLTFFVVLLGLVDAKYHFTAIQVGDLGRNSDSGIYVSQDAFPPVGEDLGNLPNTMVGGD +TAFPLKPDLMRSYEKALGILAARWRILYKEINLLPCEVDTLVVAMCILHNFLTKPCDVEM +WLQQSGAGMRQDMRSIAIQANRAGQEVVSVRGKFTKYFTSVAGRIDFQNRMVVARPA diff --git a/tests/data/subhog/conflict/HOG_D0662268.fa b/tests/data/subhog/conflict/HOG_D0662268.fa new file mode 100644 index 0000000..4e49b5f --- /dev/null +++ b/tests/data/subhog/conflict/HOG_D0662268.fa @@ -0,0 +1,52 @@ +>tr|T1EI68|T1EI68_HELRO||HELRO||1014001840 tr|T1EI68|T1EI68_HELRO RING-type domain-containing protein OS=Helobdella robusta (Californian leech) OX=6412 GN=20196268 PE=4 SV=1 +SRFQLRTLNQYFLCSICNGYIHNATTITECLHTFCKKCILDYTKFYLNCPICQELIHPTN +PMEEIMHDSPIQDLLYKLMPSVYEEELRRERLF +>tr|T1EIL7|T1EIL7_HELRO||HELRO||1014001986 tr|T1EIL7|T1EIL7_HELRO RING-type domain-containing protein OS=Helobdella robusta (Californian leech) OX=6412 GN=20196417 PE=4 SV=1 +KVNLTSINKYITCSLCGGYYIDATAIVACQHTFCRSCILLYLQTSSYCPVCDRQVIVSSE +ICTSLSDTTLQMLVYKLLPNIM +>tr|T1EIN4|T1EIN4_HELRO||HELRO||1014002003 tr|T1EIN4|T1EIN4_HELRO RING-type domain-containing protein OS=Helobdella robusta (Californian leech) OX=6412 GN=20196434 PE=4 SV=1 +KVQMQSINQFFICSICNGYLHMATTITECLHTFCKGCLIKHLEYNLHCPKCLTIIHPTDP +KINIRHDSMIQDLLYKVLP +>tr|T1EJS0|T1EJS0_HELRO||HELRO||1014002386 tr|T1EJS0|T1EJS0_HELRO RING-type domain-containing protein OS=Helobdella robusta (Californian leech) OX=6412 GN=20196820 PE=4 SV=1 +LRDVNANLCCVLCSGYLVDATTIIECLHTFCRSCILRYLESSDTCPVCDASVHPADPTMG +IKADKTLQALVYKIVPGLYQSKSR +>tr|T1FZW4|T1FZW4_HELRO||HELRO||1014019866 tr|T1FZW4|T1FZW4_HELRO zf-C3HC4 domain-containing protein OS=Helobdella robusta (Californian leech) OX=6412 GN=20214362 PE=4 SV=1 +MTRSGRKIIKDMNDLITCSLCSGYLVDATTITHCMHTCCPKCLKRTAASSNSDAKKNNYT +TTTSKKIKLRSDKTMQNLVYKMVPGLYQNELNRRINFAKLHPNWGI +>tr|T1G3T2|T1G3T2_HELRO||HELRO||1014021219 tr|T1G3T2|T1G3T2_HELRO RING-type domain-containing protein OS=Helobdella robusta (Californian leech) OX=6412 GN=20215730 PE=4 SV=1 +MIKLRIRDINPHIVCSLCAGYFIEATTITECLHTFCKSCIVRFLQTSKCCPQCGLKVHET +QPLLNLKPDRVLQDIIFKLVPGLYESRFK +>tr|T1G7Q0|T1G7Q0_HELRO||HELRO||1014022581 tr|T1G7Q0|T1G7Q0_HELRO RING-type domain-containing protein OS=Helobdella robusta (Californian leech) OX=6412 GN=20217097 PE=4 SV=1 +MNLLPDTENKIKVMDLNPHLLCSLCSGYMINASTIIECLHSFCKSCIYSYVESNKYCPIC +DVQIHKTKPHTNIKPDKTLQTLIYRLVPNLLKNEMQRRQTFFDENPYVG +>tr|A7RY94|A7RY94_NEMVE||NEMVE||1029006478 tr|A7RY94|A7RY94_NEMVE RING-type domain-containing protein OS=Nematostella vectensis (Starlet sea anemone) OX=45351 GN=NEMVEDRAFT_v1g97430 PE=4 SV=1 +MLRTLSLKELNPHIICVLCGGYLVDATTIIECLHSFCRGCIVRYLDTSYRCPVCDAEIHK +TRPLLNIRADNVLQDIVYKVVPGMYFGERSH +>tr|A7RY97|A7RY97_NEMVE||NEMVE||1029006481 tr|A7RY97|A7RY97_NEMVE RING-type domain-containing protein OS=Nematostella vectensis (Starlet sea anemone) OX=45351 GN=NEMVEDRAFT_v1g97494 PE=4 SV=1 +MLRTLSLKELNPHIICVLCGGYLVDATTIIECLHSFCRCCIVRYLETSYRCPVCDAEIHK +TRPLLNIRADNVLQDIVYKVVPGMYFGEKSH +>tr|A7S0J2|A7S0J2_NEMVE||NEMVE||1029007272 tr|A7S0J2|A7S0J2_NEMVE RING-type domain-containing protein OS=Nematostella vectensis (Starlet sea anemone) OX=45351 GN=NEMVEDRAFT_v1g100141 PE=4 SV=1 +LRVKVTDLNPHFICKLCNGYLINPVTITECIHTFCKSCLLRHISLVNRCPVCNEVIHETT +PIYNIRVDRTMQDIINKIFPKLEKEENRRERKFYRTKGL +>tr|A7SGP2|A7SGP2_NEMVE||NEMVE||1029012888 tr|A7SGP2|A7SGP2_NEMVE RING-type domain-containing protein OS=Nematostella vectensis (Starlet sea anemone) OX=45351 GN=v1g117625 PE=4 SV=1 +DLNPFITCGLCEGYLIKPTTITECLHTFCKSCIVTYLQDSEDNTCPSCNTVIHETNPFDL +LRSDQTLEDIVFKLVPGLQQGSHAIPSPPQHL +>tr|A7SGY9|A7SGY9_NEMVE||NEMVE||1029012984 tr|A7SGY9|A7SGY9_NEMVE Predicted protein OS=Nematostella vectensis (Starlet sea anemone) OX=45351 GN=NEMVEDRAFT_v1g212137 PE=4 SV=1 +MLRRMALKDLNPHIICVLCGGYLVDATTIVECLHSCHEGKEKYRWVNELIQIDHEGTTKV +SKGQRVYTERLRRIQKYRRGNEFIPKDHEGIQKYRRLNELSPICHEGIQKYRRGNEFELD +RGPIVAWLRILIRLFCPAVCRSCIVSWLQASYHCPVCDTEVHKTRPFLNIRPDCVLQDIV +YKLVPGLYQRELNYREEFKTKLQENDPTRVEPKDEVKGDSNNKKHEDDVIEDPVCLTLEY +YGARRNWRDQPVFPTRYLRCSSTMTIGVLKKFFLMKFSIPPSHEAEIVRSDEILSDHLSM +KEVAGIYGLHSKSFIDLQYVFMAIDNDNDPDSKIPRTSLTNSGNTRLMAQISKNSARDTF +TKSREKKRTARERKATLRDQHSPSHDVKNAVPRNHSNASEGPNSTSCEENIHLKNTLDPH +LLSHKHLHTNESLIDRIRVEPRQGLSKYEANGTIPTMVDTFECPDTPESDKQEKFFPGEF +SPSGNMEPEISTVVM +>tr|B7QK09|B7QK09_IXOSC||IXOSC||1043019004 tr|B7QK09|B7QK09_IXOSC RING-type domain-containing protein OS=Ixodes scapularis (Black-legged tick) (Deer tick) OX=6945 GN=8042408 PE=4 SV=1 +MPRALLRDLNAALSCMLCGGYLVNAATLVDCLHSFCKVCVVRYLDTSKLCPICDVPVHKS +RPLSCLSPTETIEPEDDGKEGVADSADGAVS +>tr|F6ZZ26|F6ZZ26_CIOIN||CIOIN||1022008235 tr|F6ZZ26|F6ZZ26_CIOIN RING-type domain-containing protein OS=Ciona intestinalis (Transparent sea squirt) (Ascidia intestinalis) OX=7719 GN=ENSCING00000002776 PE=4 SV=2 +MDDSEAAKGPELDDISFPNLVDFLQLYSIPANESRFFTRSQVFIAYRAYVSRVNAGAVVT +MDYIKFGKHLRKLAENLAIRTKSLGKIVYYELKVPEMKENERQPNAVEAKPNDDEIKIKL +ADLNIHISCKICCGYLVDATTITECLHSFCKSCITKHLAVYLTCPICDVKLQNANIYSSV +KADIVLQDIVDKLLPGIQTMENRNFLEFHKNPDSDLQTKLIYCPYKMVTVYFDWVGPQLC +STPAEDLESNFIRTSSKATVGHFGQFVLD diff --git a/tests/data/subhog/small/HOG_D0111149.fa b/tests/data/subhog/small/HOG_D0111149.fa new file mode 100644 index 0000000..19d8a1c --- /dev/null +++ b/tests/data/subhog/small/HOG_D0111149.fa @@ -0,0 +1,25 @@ +>EP00365_Plasmodium_falciparum.2979.||5833||1140002979 EP00365_Plasmodium_falciparum.2979. XP_001350777.1 pre-mRNA-splicing factor CWC22, putative [Plasmodium falciparum 3D7] +MSSSGKNEKKKRHKDHKRNVDNNYNERIKREKKEEFGSSSSYSTDYVNKRKRMRTPSSSY +DSSLEIRKREEKKKRRNNSYPSSNSYNSNTENDSNYRKEKIKKKKDEKELKVKVASHIRK +DNNKIPDGSMSSTHSSKDKVKGKKNKKYSYEESNSISSDKYNNKEKKGSSKRSREKKKEW +SSSDNSSVYNMKRNKKDKRKGSYEGKNVYIRRSVSSRSVSSDSNKSESNKIESNESVSSR +SVSSESNKSESNKSESNKSESNESESNKSESNESESNKSESNKSESNKSESNESESNKSE +SNESESNESYSKKSISNKKYKNKRYKNKRDKNKRYKNKRYKSKRYKSKSKSSLSSCDEKK +KKGYEEFEEDDKNTEETLEINEDLIEYAKQKISNVTIDDIGKAGGVYIPPFKLERLKTEV +TNKKSVLYQKQEWMKLKKKINNIVNKVNIDNISEVCYELFECNLIRGKGLFSRALIHAQL +SSPAFTNVFTCLLCIVNSKFPNIGLLTIQRIILHFRRAYKRNDKILCFNTVKFIAHMINQ +RIVHEIVGLQLCSLLLQNITNDSVQVCTYFLAEVGELYTNICRKGLDIIFDRLKDIIQEG +QINIKTQYDIEKLWNYRKNNFKDFPSVHDDLNLIDEEDKIVHEIDILDESFNSQEELNIF +REVPYEQYEEEENEWKHISEELLFGDDSADEKKNKKTKKQKKKEKNESERENESENESES +EKLGNDDLDENNNSSEDSEAEKSDSNDDDEDENNDSDNHEEIHDMTEQYLINLRKNVYLS +IMSSLSYEECVHKLLKLNIKKGYEIEICNMLIDCCCMEKTFQKFYALQAERLCKLKKIYQ +ENFEKCFDNSFNTAHRLETAKLRNCSKFFAHLLYTDAISWSIFKIIKLTEEDTTSSTRIF +IKILLQELTNNLGLQAFYHKINHPAISPFLIGLFPTNNAQNIRFCINFFTAIGLGALTSS +LRKLLLT +>XP_050702163.1||95602||1739016408 XP_050702163.1 microtubule-actin cross-linking factor 1, isoforms 6/7-like [Eriocheir sinensis] +MAPIQQFVSTAASNSRSTSDLTTGSTRDFVNSVHKVKENYSSNSSSISSSSHSDAAHSSM +TGSRPGDATQQETLLPISSRGHFFSDAFFEDARQHFESAVRKVLERRGHSRSSVHDDLTS +YRMLRQADLSESTQAATVTENATCHQVVMDVRDFMAGDVKVKVVDEDEVVVEGSVGHRSG +GSVSQKSFRRSFTFPGLVKAADITSTMSSDGVLTVTVPKKEHVGKITNITVQEYNTPSRP +AAGLPTASRQDADFPTASRPSVCRPTPSRPAACWRPAAGLPTASREVADFPTASRPVVSR +PTPSRPAACWRPAAGLPTASREVADFPTASRPVISRPTPSKQAACWRPAAGKPSC diff --git a/tests/data/subhog/small/HOG_D0455823.fa b/tests/data/subhog/small/HOG_D0455823.fa new file mode 100644 index 0000000..2ab9a1a --- /dev/null +++ b/tests/data/subhog/small/HOG_D0455823.fa @@ -0,0 +1,1226 @@ +>Smp_141860.2||6183||1012005797 Smp_141860.2 pep scaffold:Smansoni_v7:SM_V7_ZW:24414502:24502333:-1 gene:Smp_141860 transcript:Smp_141860.2 gene_biotype:protein_coding transcript_biotype:protein_coding description:Putative heat containing protein [Source:UniProtKB/TrEMBL;Acc:A0A5K4EPS6] +MTSTKPGEWVMQALLRQLTNVAQLKIEKALGDGQETNLVKSMKLNEDTAYEHLISAFGVA +AEFALPSILSTLSLWYRSQHSSGRYYQLHQFQSLSSANEYIGKVTSMAVQRPLANDSNHV +VESQQVSYLAANKSEKTIKCASESSSLSKSHNLPLPELSVLFERRDLAIDIIFCHVMLAV +LKQLPFHPGHNDVIESILEQCFGRFNYREDLQPKNSENINLVADLYAEIVGLLFQSRFAL +VRHKFMSCLNDLRSKENSQNNRASIVSLIMGMKFFRVKMHPIEDFVKCFTFLQELGQYYL +EVKEIEIKHVLSDLFVEILLPVAAVARQEVNIPALKTFVENLYPTSLELANKKKHIPALF +PLVTCLLCVGTKSFFLNNWTNFLSICLSHLKNRTSKVALVSLQSLSCILWVYIVRIKGEK +HTETQTKLHTIINSLFPKNQKIILPKDAPINIFVRIIQFIAHEKLEFAMKEVIIERLGVN +GLQKTLVMPERINVGICAFLLIAHGLQQKEGEPPMPQQCIGAGGVGGGFRQAVIKRSFHG +TNLNEALGSLLGIQSYLVPVRRAFEHILRQLDTQVCRPMMLPKQEVTQKEFDELMTADRK +PKLDLLKTCVACIPRLLPIDMTKQEILEILAKVCLHVDEDVRKMAQQAMANLIVELPAYR +VKTIQVFIQFIQKNVADTSPHQLDSCLKTLFHLLNNWKLALQKDGAVTPNMSEKSALYEA +EGFALVMLCSCRLITRRLSVHILRKCRALSNLIQSATYDQTLKNPNDSRELCCIDILDRS +VPIILKHVLPILPLSERSALSSVSNMDFSVFTERSHPVWFSGSTPPVYSPVVLPANYLNN +TQYSGLMSNNDVQQSEEVRFNGLSNLHDLNKLNNSNNNNNNCNTNNSPTVKNPVTGNTIE +KSKPDESVVQESFTKIVRPQVSGLLIPSKSFDFRNIQTSKSYLSTTPVHRRASQYNQQIQ +HTQQAYIMQPIQERVAVTDVWATCLSIVFNSSNLPTSCPMAIKSAWSILFQRINTIFPLI +DPSAQMAENRASSLLRTGSKKPANEREQFIPLWHNYVVVGCCIAPSSTGLRISKSPDTRY +QPGFHLQKHSPDFPTKGDSNDCDNNRSPTQSDEVVNQTIDLNANSSSNRPVVSNSSCTNW +SNSPDLKYSTSFGKVNARDLIKLLVPLLRCEQPEIRDSAIGGLGRINPAAFRDVLEDLQP +LLKESFDRKQENLRRRRRRDGLRSALVKTLSLMAQTGVFAYPESNIVTPQGSLICQLLDY +MDGMRGYLESVSDQLLSPINYTHQLCTSLNLLVPIPSGSTSQQVTAVGSISSNHQGGGSI +SANSVIASLSTPTTTGPSLTSYSSEFYLLGEIRLHFCIFIKELIRRLPKQSRTALLPPTL +RHQLFILLSHWSAHFDHIMGTYLGSEGPVLTESYSLDSNENIVGQPSSLNGIVGFSPSNE +IPYGIFGIHHQLSNDNFLQSLNSLGVNSAPSSGSIECNANMSVSSGQTSQSPTIQQKTCE +GEQIDRIESVEYCLWIDLIWLSNQAMAALVCCGAIFDRSALLPQDSGKLNQIILGVGSDA +SSFPGPERLNSGSGSNVRTPSSGYVLRWISGLLVARDTALSSSPWLWFCPITGRGLLHST +LTPGGASLCGNSHTRSRTIAAGGGRRLDSLLRQLGEETLVLMLDFNSDNTCLFNWVIDQC +YTASNISLSTACFECICLTMSNMPNFACDFIAMITLCFVFINHPSRFLSDRAFYLLRILY +QRFIIEPGEILFKQAHPTEIDKSFVPNEAANSQSSPNLCIPGGCDCSQLCKYLQINKLFY +HSFNRWSPCEVVRQFSAQHPEFTLTIFSEVSKRLENAREGLRITLLHLLSIWLVNVELVD +LVEGDLFDATNHSMDKDDDDFSDIDKDSFVCPSNNLEKSGNLPESVGIPTFKAYNTTNLL +DTSEQQQRKDCSDYRTRPSKRYKCSKNFLNSESLYDHEDADADSDNIVNDRNFKSLTLLR +MRARQTMVDEHADIYSPRACHLHNHFKKNGCKYNTNTNNNELHWSTVPPTLRSRGWGSKK +ATEFVLTNLFYLSLRLADCPTSSEVIKQLWLTLIRYRSTNLRVILRYLIIITSIAPGTLL +VHVKRLLTYLASEQAERVIGELIVEMQTIDGIGLVVEPISSLPFFRVSTSTQHQHHRQSI +EKLKSPKFQSCVPNNSLSNTVNNSSQRVNDSLLANQEGIGQPDDELVALGLGSKPCSNTT +AQVRTLHSAIGGSYDYHSIDYNLSVKSPSSNVTVRPKSATCQPSTVTISTNRPDQQTTLN +ESRIQFTTISSTSKDNNNNHSSSESPQLEKYHESSATLSNYQLVEGSDGEPEIDLDSFEG +FTKCGSNLDDKYATLRAKDIANLLTLPNSKDFQSSDSDSNCDIPNLYRRGTTFPIVSDAY +ERSVFSSTLSNPLNETNCLPTTGRISRQPKYFREYKEKRSRWSNNSKVDAVESLRLDPFH +HSASLPPSLAALPPLPLPLPASISSILSLSSRGSTGSNTCILSISPVTVDRKSTTSSDKS +DIKPCVRRISNVEVSHSSNKSYFDYNSKPLPMPANGGYHAPLSLWLSEPLVIGGSVVFSG +SWPPAGPRAPLVLILAGSLTQSRDIHVNWSEHLPLMLLGVFLGIDHCRALVQEESKHLLV +SLLDQVCPHHDILRLLRLELEANLHRLAYPSGFPGPRVQDYRLTLDGGPYREKFSMVINP +FDSIERDQLFPSPLVSCTTTKHDGNSNFKNRSPAEDEETLQTTSSDLIPLKDINVTTSSA +ASPSSPSISPPIVENFQTHPTLLTNQGRLSGSMFSLTSTATLIPRHHESNLFESQGCNDS +SISKRNSVCYNLTSNSSEKEFLRNKTLTSAVRPSSVYSKSRSSKNVASNCVPFNNHCSLK +VEKTKRIKKSDLKLLEEAVQQLKSLLLNSSGQAIWTWEDFNIQHNSFSTTFQYYQNEIHQ +IINNEKVENKESVIKPMTNVNPLEQLVKLVARILALSEAGSEQAENCLQCLHGPPHNHNS +QSLTFADDDYKDTIIDHSNYCMVARLSRIALNTGLSCPSRHLASRSLQIHRALGGHLDKK +SFSQLLARLGETVSDGSEEIQGYVAELFLTQEAAIHHLKSKSDSLLESVSYLTSESTSPS +SSSLNGNGRKSVQNIVSISDRRHFHRHSKSSSLIGNRPCHLSSVMPINDCGLSKLAQSTS +PNLEYASPTSSFPSDSNHLSKISGTSNTSTPKPNNNNTNNVCLLPPEERADLIIGIFWTG +VLLLESDFEHEYLVGLKLLNLVIPSVCASNLGQSSDGSKINLSDRAQKMLNQLQWNPCFP +GILSLVIKGCSSPNLVDQSCRLLVLLIPFLTHPLIVPTGRMGLSNQLSFPIVLLTLMPML +VTAWDEDPSTSVPLNPYDTNAEQLSFLQGGFLGRGVCGSLSVIPRALGSWTSCFCTSYTS +YTNPNIASNSKLRTDSIGLFANCPSNSLGTFSAHESTSNLNAIATGSTTRDTLLNSKSPI +LRPKNPICIQAAESLAQAALKTDPIQFTNLALILRLYASGSFSKDVDQWSRCVICYLLEG +CSSNISRLLKHINMLLTSGPPCLQLPLLKVAHLFLQQVDINQRDMEYSLQNFITSIVNQF +IGTCLWTHVLPILQVTVSRSAVLNSVPETPAYSLPGLDPSGSGRVLDYAIASAAVAVPLP +DSEPPRLELGGPVLDFTFNVLQEAPLLAPQYAPITSSHSTSDNISKVDIPTFDHTSSSKG +TKKDFIDVSDIGLTEIFVSASSGWNKPGNSQIRLREKLYCLVSCYGLPPQQCISAPRSPS +VIFSQSTETLDPQLSIHSSSETTSVVDISNSDDIHLDDTSSGEQTAVFRDLDTYLDAQLM +NINFLGVPDCRLASTNASMSEEEPRRPWGVRGQAITCHFDFNGDGNESVANESVGGLSRT +VQLTDKTTSSLAYSNTKSDYFTAAYQHHHSEKEHQNRTHSLPHLTCVTNNLSDMKDTESN +SSSTNRYSTITISNNYTDDNVDDRYSSASYSEQQQPLPQSEYKKPSTSLAWRSFAKEKSN +FRDNDLMNDEFTGKERFNEDMQVVKYPLVTVRNSRSERVTFNLDGDKNEYDEVGRQDDAH +RKNKISMDDGMEKINDRCMPKSCKGILINRSNHDYVKGLQHNELFQSDTVLVDNSRMRSA +STQSLNNDGNYNETNNSNQNLRSRIYFPRCMSSNRNEVRAKYINHDLLTNEPKQAVDNHH +YHDHFSVNTPQKHLVGQIKRSLSTSELLLTRQHFTLNNKSNDYKSLTNSTVFSPLTYRNK +VSYHVNDKYFTSFSSSLSSTRSLSPVNRHNQFEQMAFNLIDTNDKDHFNSNDNNTDLSKT +ISCRPNSLCSSSVLPQSIPPSLSPMLTAPSGVSVHSSHLSLHCIEMIRKSSDLPVIPRKH +KATSDSHLNCFNKQILCNHQKLHQSNSHHRCYHFSYVNESTHSVNSSECTSLNKIVSVQP +IQRLCNWKYPLITEQKWIALEPLSNSTCLHLNEQQKLISLLMELPQLYQELISRYINAIN +DTIDLSVCCWTKSSQMDRLGKLVEKMKTYLRPPWFFCRLCQDDLTETVHSYLISNVSHLL +EKHQEIQKQFEKLYNLLQSENTRDTFIQLFSNMFTNVIEFVKYSYSSAEIMCANLSMLKK +NEVLLPAILDRSCCFQYLLKCILSGLDLTNDINNHDDNSDVPIKPAICNEPAALKNQLCE +FLANMLASFPNDELLTSINQFNPENCKQIIRLYKQLSHKSLDA +>EP00108_Taenia_solium.2649.||6204||1052002649 EP00108_Taenia_solium.2649. TsM_000990500 transcript=TsM_000990500 gene=TsM_000990500 +MEFYSTCNYHFFFSEAGITSGQGTLLPALLDFIEGTRVYLESLNEPSAMVVTMFSSNTAT +ATTFGVGLGRPATVVASSSSVNSVTASSLPPATTTAVATSINSPVGAAPGGGIMVGTVAT +CGVPGAPASTVVGGAGGGVGGGGGSGGGGIGASGGAAGVTGSSVQNPSVAAATTTPSATA +AASTAPAHAPTGVDPMVLTEMRLYFCVFIRDLIRHLPRERRQRLLPPATRRNLVLLIARW +SGFYEHMYNARCPIFSNFRDAKWGQPPWTDHLSGSPTEGKYVNPPPPSLPPPLAPAGMDQ +VISGDLVLSISGVHLNSTPQDPFIAASFLDNSIWLELLWYANQSIAALVCCGAIYDTLAL +FTIPTATHRLAEASDTALSGSGVGHQLHQISTNTSATAGTGAATSTSSSNATSSTTVLAS +GVVVEEECGPLTLSTVATTPLVPTSAQALSSVAITTPMGPGFLSSRGQPAPGYIFHWLKG +LLLCRDGKLSISPWLWGLPVVNFGVCSTALKGPHAKTGMRNIRLEDSLRSIVIAAAAGRR +LDSLFRQLGEETLVLLLDMNPDLPGLLEILVDQCYTGPLNVIQAFFVIITQHFIKNPKTH +CDKFAMLTLAMVFCEHSVPVIRENAIFLLQTLYYRFFLLPVRELHHQMHPECQQIVTSTP +VTGNSGELNDSHTGLCLSALADELLWREIGHWKPCDVLRQFCAQHPTYTLPMISGKHSAK +RSLSIFSRPDSLLVWFVEEICRRLATASSEVRCRLIKLLHYWTINIELVDLFAQGYGSKN +NTACGLGDGIMETYSGCPEDASTVATLEPISPSGKTDVTTTSEPSSASDLEVEDESVNRQ +RSSISSNSSVSSSLASSSISSSDSSSSCASHLSSISTGSSNIDLAECGFGASTTSNLVAL +GKPRRNHKPNHRTHRNHEDTCCRNQRRISEHSFKPDLDGDRQKTSELWASVPLTLRRSGY +GCLKATEMVLNNFFYLTIRFADKPDEASVLGDLWVLLIRYRPTNLRFILRYLIVAASLAP +ATLCSHVNRVVSFLTNEDPSSVVDVLMTELQTIDGAGLMIEPSPFPPFFHIAFLSDLVEH +SLASSNGVTNAANTTAGSVSAVTATEGRRRRTNLSEAITFSQRRNEVHGLLIDRGGVMPV +DTAHQAASVRSVSACSGGGQLNRGDDETAIVPSKSTVEPSPLATTTSAISRLTNRFASSA +TAMGRRALRTALHPGGGRQSKTSANLDQGQPQELDIDSLDGEAEEECGVVRTTADTMPTL +QNQNTATAEALITAKSRTLPYTAASRERYSKQTVVGMDGTPLSRPSRLRKILSRRRAAGG +DKGGSGGRRNPFRSIFRGSGSGGGGSGGCGDTAKTSRIVAKVSDSARLCTTSLPPNLASL +QPLPLPLHSHSISHINDYRRKRKGGRHRRSSTRKLRHHHGGACNEGGGNQRVRMPLCNTS +LLLQQPLPMPKEGNVYHAPLRDWFTEPFVINGSLVFSGMWSPTGARSPLVLVLVGEILKS +HNKHRVAWRKHMPLLLLCLFLGLDHCRPLVQEESKQLLVNMLRVICPRIDLLRLLSLQLE +LDAQWIVRAINSFALRGQQFCLTLDGGPSVPKFYLPASTDTPVTEVSPCSTWTTCCVPTS +SSVCALSATGTTAAPPAFVASATPPSGLSPLADVPPLTVTTTTASGSGGIVSRSSLVAHH +TAAVRPSYVFGSTYSLLSTATLIPAGLSAEQESKRRSVVLSDPPPPPPTSQPPTSSAQTV +GTVADKASATSVDVMAVATAPYDCEAKHLHLAAERIGQLLEEVQDRSIWTWEDITPSRFQ +QATLLTLRSTLPPHPSTLPQPRNSFLLLRSTCFLDRFVRCVAEAFAEAEAGCRCEEAASC +LRCHHGAPHRHLNNGEVDEVDTDNLGGGGGPISEGSALFLSLRPGCTVTRFAQAAFNMGI +TCPNRHYASRSLQIYRALDAQLDEKSLSEVLVRLAESVSDANEEVQSYMAELFLTLEAAV +GHIQCRGIVLSPCTVSIGQFATRSRSSAPDSPLVASKPPQPPQSAPPFLLPPQAPPSTPP +VHERRHSRRGSSSSLSSLLSYPAAVPAPSLASMQQTVGLTVPPIKRPLMPPHAEAVSEEA +DVVPLAVPSMSTVGPMKPTPMPPLPPNANKCSNTGAVAITANDGRHKSIAGDPVIDPSAA +GTCRLSPEEQADLLAGIFWIGVCLLDSDYEYEYLAGIRLLSRLLPCVCSTLNTASTTTTA +TTTSSSNRKWTPQQQQHQNLPSLHDHSLKLLHRLKWTTGTEDGDYPGLLGLLLKGCFSST +LIDTSCRLLVHLIPAVKSPIVDPNVETRQYRRAAVSGNSRSTGSGTPYPGSLPSLVIALL +PMLLTAWDEDPESPASTVPEAEVCDACVEGSGSPAVASSFSPPSSFSPPPPLGSWVHRSV +VLSETTVPSPSSGLLSLITAILISGAVAKKAPQLRPRNPVCILAADQLAELALQMDAYRF +NNLAIVLRLYANGTFSKDVNQWAKCVTRYLMDGCAGLGPRILTFLTTFLTLGPPCVQLPL +LHFGYWFLQAVELSQSEMSSRIRTFITATSERFVNTPLWPEVTLLYQVVVARSATLTVAP +SNPEIYSLPGLDPSGSGRVLDSLAAAAAAAVPLPEAEPQRIALAGRVLDFDSSVLLSAPL +IAGGRGQASPHLGATSAIGQNQHRSPARTSHLVEVCGLSAFSFSSFFRLGAWKTPWNRQN +SSKQKLLPSSINDGHFDDTSSAERAAVFHALDTYLDAQLMNINFLDLPDAAWEDQSRCQW +GVRGHSITSHAEFHEEGDDQQQSESQPQSQKQQQQANHPPNLFSVDDDTSFPNTASTAGE +PFVIPPLVHSQFQHQHPPEGEELEVEDVEEEEEDEDDEEEEELERERGDFLAVGAPTVAG +AVAERQSQLSLQLSASSSAAPIAAIDPAPNAALLAPSTLHRRFNSQLLIAWTSRDDSPVK +WLTDASPTADMNAENALSISQIMQGCQGTESIPRKFTLSTHPQAVCSRRRRQILATFGGS +LDSLPSQTNGQSTTVLKRRSCALPKYFQQSSSTSSFIKVINIASNGVTNIVEATVPSVKP +PRRHRRFRRSLELGTQQRPDLSLRASRSSNALVCPSEGVKALSTASITPQQLLIPTPSET +AWMTCQRQLKSLQQSSMGFAMASAQADRLRLLLTSLRSTFSGMLRRTTLQAVKLANLAFY +QCPPSSDLDPEIRISNLVRIADVMDDVLPLPFFAIHPNSVVLIDEHSVSLCLELGTAYEE +WEEAVRQLAAHERICRDLQGVLQRLLLFFRSSTDVLTSAYRHLVESSVGVADFSASVVEP +LFARLRPYILTPPPPLPPNSQEVGGMEAWKTRCARLLLWPPYSSEDVGTKRVLAIYAAAM +KSTAPISIDDVMTILEACCEHLWTGANCNSGVALMFAQSRAIESHRFEAFTSATSPVSPS +TKASALFFATDDLLVSDGRATLPPLELETRNTE +>EP00108_Taenia_solium.5130.||6204||1052005130 EP00108_Taenia_solium.5130. TsM_001139700 transcript=TsM_001139700 gene=TsM_001139700 +AQAAENRASSLLRSTSKKAPPSERDQLLPLWHNYVTLACCIAPSSTGVCVVDRKRPTYDT +ESASIAIRRRNMLKHISNPSTGYHECELESYRVPNQNEKSLDKDPDTVGFGDVNSHLIEN +TRSYIRRRCIQVKRKSGLCTLKSIILDRRARIERLRVNHSRNAMCSSADIALLPSYESNT +AITGSGEPELSSGMSPRQHQHLANSKLFISSKEKARDLVKIIIPLLRCEHPELRESVICG +LSRIQPAAFRDVLEDLHPILRESIDSKQKNVQRRRRRDTLRSSLIRLLALMAQNAVFQHE +>EP00108_Taenia_solium.5971.||6204||1052005971 EP00108_Taenia_solium.5971. TsM_000926400 transcript=TsM_000926400 gene=TsM_000926400 +MTGAASSRGNDLVKCMRLQEDMAFQQLLAALHTCAEHALPCLLQTINKWYDTQHSSGALY +PFRRTTSKTSGTRSHTLSAATGQTPAMAGGHDLLIRSMHVEGSSAVTAFGSDGASGTLSN +SSGGVAVAGEMGNSVLPLSATFAIPQESATSTSWLAMSTSVRENMAERRDLCIDILYCQA +LTSVLKQLPYHPGHDEVINKILDRSFKHFEYKEKSFLTVGPFDSLQAKPNAENINTVADM +YAKVVGELSQTRFNLVRQHFSTRLEHLRVKESSPHTMHSIISLLMGMKFFRVKMHPIEEF +VNYFTFLHELGQYFLEVKEKEIRHAMTCLFVEILLPVAAVVRHEVNIPALKNFVDLLYNP +AFELANKKKHALALFPMVTCLLCVSTKVFFLNNWPPFMSLCLSQLKNRDLGQVSVESLFR +LLWVYVVRIKCEKHTDTQNKLLSIVNGLFPKGSKSVLPKNVSSTVFVKIIHFIAQEKLDF +AMTEIIFDLLGVNRLQKLVFTPERMNIGLRAFLLIAHGLQQKDGEPPMPQNQPLAGCGGV +GGNAYPSAYPRGPGNVRRSFHGTTLDDALCERLGIRPYLVPVRKAFEAILRLLEPQVCRS +MMPPKQGAPIKDGDDSAPGDRKPKIDLLKTCVNCIPRLLPYEMSRTELIEMLARVSLNVD +EEIRTMAQQAMVNLIVESPAYRQRIIQAFIQFIQKYVPDTAPHQLDSCLKTLHTLLVNWK +IALQRVCLSDSSSTYMSTIRIIRSIKKTGKRSSLPCYLQLNLRLLKAYNVVTFYIFTIVI +NLILQDAAVTTSLSEKSALVEAEGFALVMLCQCRPLARRLAVHILRESRAVLRLINQAAA +AAAQQKQEQGHEDTIQNISSSTSAAAASNTSVIDILDSLAPTILERVLPLLPPNERHELT +NLSTVDFKSLAERATPAWLCGPRQPSMWNHKPVLGSTNLTISTALPCVQRFSSSGLGSTK +ASTGPKASLSTTATITSASKSSLETSPSRGGRKSQTVPPPPQTVAGSTALRPSAFSQCLL +SPYLLQPQLERTQLTDVWATVLAVSLSTSSALSLHPAMLYTWNTAFQRLTQVFSLIDPKC +VSDFV +>VDN18778.1||60516||1202013238 VDN18778.1 unnamed protein product, partial [Dibothriocephalus latus] +MPSSSETASINDASNIDDGRLDDTSSVERAAVFHDLDTYLDAQLMNINFLDLPDGTWPNP +ISANGAAGDGIVDEDLSATTNSADLAPARRDSISAHSNSSVSVGRPRGISLLAPVLNRQF +SSELMVAWVS +>VDN29794.1||60516||1202015398 VDN29794.1 unnamed protein product, partial [Dibothriocephalus latus] +MFALCVCFLNRSNSEAGTCRLSQDEQAELVSRIFWIGVCLLDSDYEYEYLAGLRLLNRLL +P +>VDN43651.1||60516||1202019110 VDN43651.1 unnamed protein product, partial [Dibothriocephalus latus] +MLVLAMVFTESAVSSIREQATYLLQTLYHLFFLQDTVTSNNPAEAFKPSVSVNNCCLPSP +SDSESQSCLLEPCLSALADELLWRAIGHWKPSDVI +>VDN97521.1||102285||1253001648 VDN97521.1 unnamed protein product [Rodentolepis nana] +MEPKFYLPAPTDTALSEVSSCPTWAAASYSASPNNAQSLFSSTGFLNLVPTSTGGATGNG +FSPVTEGPSSIVAPHPLRTSLQQPIPARPNYIFGSTYSLMSTATLIPSAAISSGEAEPTN +RRSLILSNPISPSQLPPPPPSTTLHPVSQAQASRTPKDDCESRHLHAAAELINQMLVEVN +DRSIWTWEDITSSRFQQATLLSLRSRKSPILLLRSARFLDKFVKCVASAFAEAEVGGKCA +EASECLQCHHGPPHRHCELESEEENCLQQPTCTVTRFAQAAFNMGITCPNRHYASRSLQI +YRALGAQVDEKALSEVLVRLVESVSDLNDEVQGYTIELLLTLEAAVEHIRLRGTILSGGG +QRNSTLIRSSAPESPMAISKPPQTPPVHERRHSRRGSSSSLSSLLIIPNGGGNSSGGAAV +LGNVGVGCVGTQASLPPIKRPLMPPHAEAIGEENESSASNTAVGTVMGPMKPTPSKDTMP +PLPPSATSKPSKLTSLPSLPVETPPETKPVSTNGCEIKSRAESNNAVEDTSMIGTCRLSA +EEQADLVSGIFWIGVCLLDSDYEYEYLAGIRILSRLLPSVCSVPQSSIFKGLPDFHEHIL +KLHHRLKWATGSSEEADFPGLLGLLLKGCFSSTLVDASCHLLATLIPLVKSPVVDPFAEA +RQNRRGPGLSGTAARSFGTPTSSSNSGSNYPGPLPTLIIALLPLLLTAWDEDPESTPSSI +APETTDGCEMCAEGTPGNPLVTPSLSLNSPPPPLGSWVHRSVVQSENAVPSPSSGHLSLV +TLTIISGAAAKKAPQVKPRNPLCVQAAENLAELAVQFDAYRFNNLAVVLRLYACGSFSKD +VNQWAKCVTRYLMDGCSNLGSKILPYLTAFLTLGPPCAQLPLLHFAYWFFQSVDLSQSDI +STKIRSFITSTSERFVNTPLWPEVTLLYQVIVARSATLTTAPSNPAGVFSLPGLDPSGSG +RVLDSLAAAAAAAVPLPEAEPQRIALAGRVLDFDTSILASTPLIAGGKGQASPRLDCTNS +AAAGVGGIQYRSSTRMPHLAEGCNVPVGAFAYTPFFFRLGAWKTPWNRQSRLRSLFTNLL +NCYGSHLEHITAPRSPSVIFSQSTETLDRQLSLHSSSETTSINDASNPDDANFDDTSSDE +RAAVFHDLDTYLDAQLMNINFLDLPDQNWEDQRRCPRWGVRGHSITSHSEFHEDGDDTQH +HQQQPQEQLAKHFQNPSGDIATSSTVDNFANIPFQRGEDENLIVDEDLVANNFEDVLDAN +TATNGADFLSYSESHRIPPQRQSQLSIQLSTASSVNSASFAAPPPPQSRSWQSKDDSSLK +WLPDITSIVPPVESPPQSRKRTLSMQPPIVCSRRRRKNLASLGGSLDFLPSQLHPLSVLK +RKISAPQQQNSVASGSTRRINTQNSSSTVSSVSSASFKVKEAPQLQPPAKPVRRKHRPSR +QSLELTTLDEPPAVPLRLSASSTAISKTPTPAETAWIACQRHLKTLQHASSSSGIVSRGE +MTSGQRDRLRLLLTGLRSVFAGMLQRTTKGAVEMATLAFSRCPPSSDLDPEIRISYLVKI +AEIVRDFLPSLFFALHPTSVSVLNDQIVSQCLHLGSMYEEWEESVNRLSLTCAPVDSPDW +NQHERICRDLQTVLQRLVNFFSYSADILFKVGNDLLLSGITDFSHNLIEPLFNQLRQYLL +SAPPKSHIEILDGIDTWKVHCGRRLISKTPDDSDVTAALGIYSEAMKSNTLISMDDIMVI +LEACCDQLWLERKSESGFALLFAHGRIPEMKAFELLTSNQSMSSLVFTTDNLLESNGQSV +LSPSELTAKLQKLLEC +>VDN97553.1||102285||1253001680 VDN97553.1 unnamed protein product [Rodentolepis nana] +MICSNPGELVVHSLLKQFASMCQSTVENALDEKADNDLLKCLRMSEDTAFQQLLTALYSC +AEHALPSLLQTITKWYDTQHSSGSLYPFRRTPSKAGLRSQTLSAATGQAVSNTPPGGREL +LIRSMVVESSLTTTGVTQGVAVSSQEVNNALPSSANTTQDPPIPAMSSLARENMAERRDL +CIDILYCQTLASVLKQLPYHPGHDDIINKILNQAFRHFEYKENELMVLFFQNGSLQAKLN +AGNINTVADAYAKVVGELSQTRFNLVRQHFSTRLEHLRAKESSPHTMHSIISLLMGMKFF +RVKMHPIEEFVNYFTFLHEMGQYFLEVKEKEIRHAMTCLFVEILLPVAAVVRHEVNIPAL +KNFVDLLYPPSFELANKKKHVLALFPMVTCLLCVGTKTFFLNNWSPFMHLCLSQLKSRDF +GQVSVESLFRLLWVYVVRIKCEKHADTLNKLLSIVNGLFPKGSKSVLPKNASSTVHVKII +HFIAQEKLDFAMTEIIFDLLGVNRLQKLVFNPEVSCRCNYGDKHLKSILRNVPHFGPCLA +VVQSVFALNHTCDYVVINPAMILERVCCISLTGNEKSVTGIKK +>VDO01780.1||102285||1253005855 VDO01780.1 unnamed protein product [Rodentolepis nana] +MNIGLRAFLLIAHGLQQKDGEPPMPQNQSVGVCGVYPTVHLQRGFLPACSTGVVRRSFHG +TTLDDTLCERLGIRPYLVPVRKAFEAILRLLEPQIIRSMMPPKQGAAIKDGDDSAPLVHF +LFGFNLIVTFCVKITLHCHWCNLSLYGVPTFTQILL +>VDO03726.1||102285||1253007781 VDO03726.1 unnamed protein product [Rodentolepis nana] +MSRTELIELLARVSLNVDEEIRMMAQQTMVNLIIESPAYRQRTIQAFIQFTQKYVPDTFP +YQLDSCLKTLHTLLVNWKIALQRDAAVTTSLSEKSALVEAEGFALVMLCQCRPLARRLAV +HILRESRAILRLIDQAAAQQQQGVGTETSHGESPQNIATSISAAASASNVAVIDILDNLA +PAILERVLPILPQNERHELSSLQTVDFKSLAERSNPVWLCGIRQPSTWNHKPLLGSNNFS +TPVAVTTADHSSSGNKVASSTMKHGGFDAASPSRTTRKSAQQLSTLRPSTFSQCLLTPYL +LQPQLERTQLTDVWATALSVALSTSSALSQHPALFYAWNTVFHRMSQVFTLIDPSAQAAE +NRASSLLRSTSKKAPPTERDVLVPLWHNYVTLACCIAPSSTGVCIVDRKRPTYDTEGAAS +AIRRRNLHHISNPSTGSADIALLPSHEKAATQSDPKTPVQVQNPRQHQHLVNSKLFISAK +EKARDLVKLIIPLLRCCDHPELRESVICGLSRIQPAAFRDVLEDLHPILRESIDLKQKNV +QRRRRKDTLRSSLIRLLALMAQNATELTILLVFDFSEAGITTTQGTLLPTLIEFIEGTRV +YFESLNEPSAMVVTMFGSSAGVGSITTVNHSSSNANDIPGGSSINPPTVGQNMNSPTGPS +QSNVGTMNSATVSSGASMAGVSGSGAVSSTSGAQNPTANSASGPTSATAATSTAPAHAPS +GVDPVLLTEMRLYFCVFVRDLIRHLPKERRQKLIPPTVRRNLVLLISRWSGFYEHIFNAR +DVKWAQPPWTDDSSSSLAQDAKQSSSIRSASVGASVGSLPSPPGMDQVISGDLVLSISGV +HLESTPQAPFISASFLDASIWLELLWYANQSIAALICCGSIYEHVAIFSNPRIVETTEVA +SNATASSSHQLAGSSANLGGNQMLSNLSANLPNSATQPNSVPTTLLSNDDDVTAPTTQVV +STVSIATTNPSTVNASRGHPIPGYIFHWLKGLLLCRDAKLSISPWLWGLPVVNFGICSTA +LKGPHGKTGVRSIRLEDSLRSIVIAAAAGRRLDSLFRQLGEETFYILLDMNPDLPGLLEL +LIDQCYSGSLNVIQAFFVIIAQRFIKNPRMNCDRFTMLTLAMVFCEHPVPVIREHATFLL +QTLYYRFFLLPSRELHQRMHSDCQKTVESVNEESRDSGDQTEFCLSALADELLWREIGHW +KPCDVLRQFCAQHPTCTLPMISEICRRLATASKEVRCRLISLLHYWAINIELVDLFSRNY +KSDGFMDFFHQHRNNPQLASASLLEKVELSNPVSAINAESESELEIDDRMEKTQNGVGGD +EEEIIKCPSGPSGSESSSSSTSLSSSSESSHNSSISSDIDSSDCDGPVLINNMASSASAS +RKPRAGRRQHQDNRRRRLQQGRTSDQFLKPDQDGEKQKEAANELWASVPLTLRRSGWGCL +EATEMVLNNFFYLTIRFANEPNESVVLGDLWVLMIRYRPSNLRYILRYLIVAASLAPATL +CSHVNRVVSFLSNEDPSEVVEILMTELQTIDGAGLVIEPSPLPPFFHISLISQTNLTEPS +SDQIGDPDENASRRILENESCLGQQTGSSIRPLSTISATGGDLQVIPIKPGVDSSPAQPI +STSANTSAISRLTNRFASSASAMGRRALRTALHPGSGGGRQSKNSMNLDQEQPPELDIDS +LDGEGEEESGNLRQAADTMPTPQSHSTPSADALVTARSQTLPYTAASRERYSKQTIVGLD +NQVVLSNRPSRFRKILSRRRTGGGGRRNPFRSIFGGGGGGGGSGSIVGGALNGSIKTSSE +LVQNISEETKLYTTSLPPNLASLQPLPLPLHSHSISHLNDFRKKRGGRRRRSSSASGRNH +QHESRRHRRRRRRRNNTNEGGVMEDVADQHVKVAFYETSLMLQQPLPMPKDGSVYYAPMR +NWLTEPFVSNGSLVYSGMWSPTGARSPLVLLLVGEILKSHNKNRVTWRKHMPLLLLCLFL +GLDHCRPLVQEESKQLLVNMLRVICPRIDLLQILSLQLEIDSQWIARAVAGFSTHVPQLF +LILDDGPSVVRNRRDF +>VDP74363.1||27848||1259005036 VDP74363.1 unnamed protein product [Echinostoma caproni] +MSTLMMNSGNVRMTMGSNPSHGPGACQLGDTYGPAGYTLDPGADALLQTGTSPTSSASGP +ATNVMTESTTSVTGVSSSNLNAATATAVGLMTGSGSVHTVGSSLDAHWESRLWADLMWLA +NQAMGALACCGPIYDPHALFADHMMMGADPSTSSLSGSEFQSTIGSQPSPGYILRWVSGL +LIARDVVLSSSPWLWHCPIVDTGLCXXRRLDSLLRQLGEETLTLLLDLNQASYTLLNWTI +DQCYTSNNVALANACFFAISRVICNIPDFPCDFIPLLTLAMVHLDHPQKSLSDRAVHLLR +VLYYRFVIKPGMLIHEHVHLNEATAHPCLWERDSSVSPQSGHETISSLCAETKVNLQLWQ +SILRWSPAEVVRQFAAQHPDFTLPIFSEITRQMGTARSGLRVPLLRLLLPWMINVELIDL +IGDSEADNHGDGPIKNRGLSGHMAGDCSDDEPNELRPRKCRRQRQAAPRSGRLGGHASVH +DRVDGEDEADDDNDNDIVNYENDREEDEDVDDRLVAGANVGVIDMGSETEDSEEDEMAND +DDSAFVPSHFTQKTVDYHVLDQHYTERIQTSQPHTCSRGDEHAPVYSDVLDSDADNELEV +STTLLGSRFRSIMTERSKAETVPLVGPHGEADLKHGVRPRWLTDSARRPHGSSFRNTYHA +DFWSTVPPTLRTDGWGSKQATELVLNNLFYLTLRLAECPASSDCLTQLWTTLVRYRPTNL +RVILRYLIVITSIAPATLLLHAKRLVTYLACEHPESVLEELVFEMQTIDGIGLTVERTAK +LPFFRITTNASRARDMMESTTETVAATVNRSASPGPTTDHLTNQLPAGSDQSTKSIQAVL +KAVQRESTHSVDPELVAIDLAVSENIGALKPERKFGRNRGSALPFFHNKRHTTEETGDGS +GRLIKRRPKSATSHPSPRSSSATAQHNKLPETALLSTETNAVSIDQRAASTDSNADRTRA +GSVPNQLTSSVPPSRLPTVLPPLTPDTDGDVELDVDAPYVISNEGLGGARGLHEFSPEDK +YATLRAKDFARLLFTPVESKDPTFSAGGASEPELLPDERGPPLTIRRGTLPSTEDSRERY +YQSGAAQCSSGVIGVSDPLGRSSFTRSHAQRRCKVKTRSQPRGQPKTPLHTVSLPPNIAS +LTPIPLPLPSSIPSLPSVWLTSMPDRLSTSSGSSPRSTGKRASGKSQAGPSASITMDAIP +LGKARSQPLAMPTNGGYQAPLGTWLAEPLVIGGSVVFTGSWPLSGARSPLVLLLAGGLTQ +CTSVHLDWNAHLPLLLLCVLLGMDHCRALVQEECKQLLVSLLNLACPDQDALRLLRLELE +ATLLATAYPSSSSSSPRKPNYRLTVDGGPYRERFDLFAYIPPITVQCDVPSPQSQPTQAI +HEKSIPDIAFSQYVPPRKPERRFVGSPGTVRDVTPQVHPFSGENSPPPGVVNVSQAVTVQ +SPLNGAQWTGSTFSLISTATLIPGREECTIPDVRHTLCDVDEQPNDKPRAVTGPERRFSG +QQIPPDSVSVARPKAQILRTKQTRATKPVRSVNTVAADPLARSARGRTLPIDPKPLLSAI +AELRELLLSSSGQALWTWEDFNLQRFQHTTTQHLTHPVTGTTTTGSNAYPTRTDHGGSET +DQSGSSTVSRVSVECLVRIVARILALSNASSAEEADSCLLCHHGSPHSHSARPTRLGRFS +TSPNNHCMVARLSQAALSMGLSCPNRHYASRSLQIFRVLGAHLDPKTLTKLVARLGETIA +DANEELQGYVAELFLTQEAAIEHLRTKSNCITEIVSPIPVSNSQPRTAPTSPHCVGRIGM +TNESNHTALVTVSDRRRPQQRHSTSTGLTAGLTNSWTRPNGDLSPRQPSQIMDDAGSVNE +SCDNVSPLVVPSGRHNQQLSQLSYPITLLTLMPMLLTAWDEDPATLSPNFPEPTCGDSQQ +QPSVLQGGFLGRDVCGFSNTNTVPRALGSWAMVAIPPRSASVSTGTSSTCAPQAQALRLG +RADSIGTGMIGAFGPTGSGLNLTNLGGTTNFPRSMLPTPLRPRNPICIQAADALAQAALQ +LDSNRLYNLALILRLYANGAFSKDVDQWAKCVVCYLLEGYAQYTSQLLHHLTTLLTTGPT +CLQMPLLKVAYLLLDQVDLPNTDLGYTLQNFITTITGQFLGTVCWPEVTRILQVIISRSA +VLATVSPPSAYTLAGLDPSGSGRVLDLAAAAAAVAVPLPESEPPRLELAGPVVDCSFNML +HEAPLLAPQLALPIGSMSTGSSLFAAGNGPNDYEQQAAGPESLALAPSLRRSDPTRSMDS +ALAELFVSAASSWNKSGICQARVRERLYRLIGCYGLPAEHRISVPRSPSVSDYYPRLLRT +SSVTGAKRPRALKIHHALR +>VDP82603.1||27848||1259008025 VDP82603.1 unnamed protein product [Echinostoma caproni] +MYFRHFADDIRLDETSSMEQAAVFRDLDTYLDAQLMNINFLGVPDCRLEDEPRRPWGVRG +QSITCHTDFTDDNIIEQVTDLLPSMRIPLEQSTLKASSPPRNTRKRKVHAPNLRTVEPGC +SVESSRLDEIENKIDWTGPVPTYAEPDQRLDDLVQNCSFLEPHTSLQFTPMRQRYTEAEG +VDVDAVTEEMGVDLHTSPVDYVKPLESATVVDEPASDNPPSAMTVSLYDPVSPSRKPRRG +RTSSGTSLLVAHARSSPPVQLPSSMNIQEHPCKTLTKKTTFESPTYSQCSIASDPMELVS +HGFVDPIQSPSSCDGDHSKRIRGSPMSAHHSGSLPLDTERIRPLSPSSSDSATKHHLVIK +RPNTLIPTNTGVVTVSQGLQSGQQRPLPVMRVKIPTHYARFGKFQTRFTSSSSASSGFEA +VTQIAAHRSLSTSNLSGVSPGSPKTTTVAPSGTDTGPVPIQLGRFQSPVLKFHKNFTSKR +QSLSAEDLTRLIRQPKVSFLVPTCALEVPGQEETETVSVIVPKSPMLSSSQSFPGKGSAS +SLHSPPTDSVAEAKITRSPQALNRLGLSRHSNSLPEPVSSRRMDQIGTTTQRSPANITPV +VYHKLMTRTSLFSSVTYPWTEMVPIERTWLNSLKKLGLDESSNDTMDQLTSALSDLPSVF +SALALRCQNGVRAALNLAGFSWDEASETTRLRTFCCFLAVSVSELLTTTVRPPYLLYTLK +KPSNLTEREQSAIQSGTALLLESYEETGTAIQHLAKLAFDRDGTSIQRGCVTLLFKLASF +LQATHTVAEQLYRACSPQRTARELSERLQRALDLLASSMNHYYSGSNSNYSHQGSTCSTA +PTDPTEFTSMLVDQMDTLLRELSELGELGGTDPVPESLAVRLFKLVDLYHSRHISDSDRR +LLTIDCVLGVFSLYVTQYFTRSENADSDATVVYLVVPSIGLGRTGSPSPLDQCSYLKQQL +DTLC +>VDP88536.1||27848||1259011459 VDP88536.1 unnamed protein product, partial [Echinostoma caproni] +MDLLKTCVACIPRLLPNDMSHPELLELLAKVCLHVDEDLRKMAQQAMANLIVELPAFRVK +TIHVFIQFIQKNVADTSPQKLDSCLKTLFHLLNNWKLALQKDGAVSPSINDKSALYEAEG +FALVMLCHCRSITRRLAVHILRKCRALIHLIDAATSAGDVPINSRNSEELCCIDVLDRLV +PQLLERILPLIPVHERLSSAVHLDFSLLAERCSPVWLSGSAPVVTYPAVVVPTNFVSNQY +SSLMPMNDLERNSSKDVSEQFRRRTQFSMYPVDARAGPINRDATEDVHKIMASSLYDRTT +SPMHMFQRDSRRSDPPTTHAATFPHSVGPGLFRRASQNQHHPHHPVQHAQQAYIVQPVPE +RVILNDVWATCLSIAFSPDHLVTACPMAIKYAWSVLYQRLSQLFPIIDPSAQIVENRASS +ILRSSSKKPTMEREQFFPLWHNYAVLGCCIAPNSAGSRMNAPRTTYYQANASVAVAPGST +KLGTRYTPSSNSQADSSEPGGALRSSTGHSASPLSSATTVNPAIITESRVTTTSGSAASP +PPP +>VDP89014.1||27848||1259011827 VDP89014.1 unnamed protein product [Echinostoma caproni] +MMTIKPGELVVSTLLRQFVSLTKSKIEKALEEGHENNLMKSMRLSEDAAFEQLLDSFYVS +AEFALPSLLRALIQWYQSQHSSGTNYAAHRKRHLTAKEENPMMTSCLIMGSAKDNPRHVN +TSAAPYVSGKSEKIIPNISDIQCTTKSTCILSSEMRVLAERRDLAIDIVFCQVVLSVLRQ +LSYHPGNNQLIETILDQSFRRFKYREDLQPQNSENVNLVADLYAEIVGVLSQSRFALVRS +RFTRHLNELRAKENSPNVKPSIVSLIMGMKFFRVKMHPIEDFVNCFTFLQELGQYFLEVK +EPEIKHVLSDLFVEILLPVAAVARQEVNIPALKNFVENLYPTSLELANKKKHIPALFPLV +TCLLCVGTKSFFLNNWANFMTICLSHLKNRTPKVALVSLQSLSCLLWVYIVRIRGEKHSE +TQSKLHHIVNSLFPKNQKFVVPKDAPINIFVRIIQFIAHVSSILS +>VDP93274.1||27848||1259015996 VDP93274.1 unnamed protein product [Echinostoma caproni] +MFMPERMNIGLCAFLLIAYGLQQKAGAPPMPQQCIGGGTSGVAGAAGGLRQTVIKRSFHG +TNLNDALGHNLGIQNYLVPVRRAFESILRQLDTQVCRVMMLPKQEVSQKEVEELMT +>CUT98841.1||6211||1308009609 CUT98841.1 protein furry [Echinococcus multilocularis] +MVGTNPGELVLHSLLKQFASICQSKVESAIGEKVSNDLIKCMRLQEDMAFQQLLGALYTC +AEHALPCLLQTINKWYDTQHSSGGLYPFRRAASKTSGTRSHTLSAATGQAPALACGHDLL +IRSMHVEGSSAVVTSGAGDVSDNIAYSSGGAAIAGEAGNAVLPLSGASTTPVESTTSTSW +LAMSTSVRENMAERRDLCIDILYCQALTSVLKQLPYHPGHDEIINKILDRAFKHFEYKEN +LQTKPNAENINTVADMYAKVVGELSQTRFNLVRQHFSTRLEHLRAKESSSHTMHSIISLL +MGMKFFRVKMHPIEEFVSYFAFLHELGQYFLEVKEKEIRHAMTCLFVEILLPVAAVVRHE +VNIPALKNFVDLLYNPAFELANKKKHALALFPMVTCLLCVSTKVFFLNNWPPFMSLCLSQ +LKNRDLGQVSVESLFRLLWVYVVRIKCEKHADTQNKLLSIVNGLFPKGSKSVLPKNVSST +VFVKIIHFIAQEKLDFAMTEIIFDLLGVNRLQKLVLTPERMNIGLRAFLLIAHGLQQKDG +EPPMPQNQPLAGCGGGGPYSSVHPRGSGNVRRSFHGTTLDDALCERLGIRPYLVPVRKAF +EAILRLLEPQVCRSIMPPKQGAPIKDDDAAPNDRKPKIDLLKTCVNCIPRLLPYEMSRTE +LIELLARVSLNVDEEIRMMAQQTMVNLIIESPAYRQRTIQAFIQFIQKYVPDTAPHQLDS +CLKTLHTLLVNWKIALQRDATVTTSLSEKSALVEAEGFALVMLCQCRPLARRLAVHILRE +SRAVLRLINQVAAAAAQQHQEQGHEDSIQNISSSVSVAAASNTSVIDILDSLAPTILERI +LPLLPPNERHELTILSTVDFKSLAERATPVWLCGPRQPSTWNHKPVLCTTSVTTSTTPSY +VQRFSSGGLRSAKTSTDQSPGTKASRSATAATTSAGKSSLETSPSRGGRRSQIVPPPPLT +VTGSAALRPSAFCQCLLSPYLLQPQMEHIQLTDVWATALSVSLSTSSALSLHPAMLYTWN +TVFQRLAQVFSLIDPNAQAAENRASSLLRSTSKKAPPSERDQLLPLWHNYVTLACCIAPS +STGVCVVDRNPPTYDSESAFTVIRRRNILRHISTPSTGSANIALLPSCESTAPITGSGEL +ELNSGMNPRQQQHLVNSKLFISAKEKARDLVKIIIPLLRCEHPELRESVICGLSRIQPAA +FRDVLEDLHPILRESIDLKQKNVQRRRRRDVLRSSLIRLLALMAQNAVFQYDEAGITSSQ +GTLLPTILDFIEGTRVYLESLNEPSAMVVTMFSSNTASATTTVGFGLGGSTTVVSPSSSV +TAVTASSLSSATTTAVATSINSPVAAAPGGGMVVGTVATCGVPGAPTSNVVGGVGGGVSG +GGGGGGGSGGGAAGVSGGSAQNPSVAAATTTPSATAAASAVPAHAPTGVDPIVLTEMRLY +FCVFIRDLIRHLPRERRQRLLPPAIRRNLVLLIARWSGFYEHMHNARDIKWGQPPWVDHL +SGSLTESKHPNPPPPAGMDQVISGDLVLSIPGVHLDSTPQDPFIAASFLYNSIWLELLWY +ANQSIAALVCCGAIYDTFALFTVPTVTNRLAEAFDPSLSGSGVGHQLHQTSTTAGTGAAT +STSSSNAASSVTVVEDECSPLTLSTVATTPLVSTSTQALSSTVITTPPGSGFLSSGGQPA +PGYIFHWLKGLLLCRDGKLSISPWLWGLPVVNFGVCSTALKGPHAKTGVWNVRLEDNLRS +IVIAAAAGRRLDSLFRQLGEETLVLLLDMNSDLPGLLEALINQCYTGSLNVIQAFFVIIT +QHFIKNPKTHCNKFVMLTLAMVFSEHSVPVIREHAVFLLQTLYYHFFLLPARELHQRMHP +ECKQMITSTPVAGDSDELNDSQTELCLSALADELLWREISHWKPCDVLRQFCAQHPTCTL +PMISEICRRLATASSEVRCRLIKLLHYWTINIELVDLFARGYSGKNNTSRGLGDGFMETY +SGCLEDASTVATLEPIFLSGKIDVVTTSGPSSGSEVDEEDANRQHSSSASSDSSASSSSA +SSSVSSSDSSSSSHSSSISTGSSNINSGECGSNAAATSNVAVLRKPRREHKSIHQPYHNH +EDTRRNNQRRISDQSSKPEIEGERQKASELWASVPLTLRRSGWGCLKATKMVLNNFFYLT +IRFANEPSEASVLEDLWVFLIRYRPANLRFILRYLIVAASLAPATLCSHVNRVVSFLTNE +DPSSVVDVLMTELQTIDGAGLMIEPSPFPPFFHIALLSDLTEHSSAASTSATNAATTTAA +SILTVAGTECRQRLTNPSEAITFSQRNSEVHGLLADGGSVTTVDADHQAASVRPVSACSG +GWQLKGGGDESTIVSSKSTVEPSSLANTTSAISRLTNRFASSATAMGRRALRTALHPGGL +RQSKTSINHDQEQHQEIDTDSLDGEVEEECGMVRTTADTMPTPQDQITTTAEALITARSQ +TLPYTAASRERYSKQAIIGLDGTHLSRPSRLRKILSRRRAGGGDKGGGRRNPFRSIFRGS +SSGGGGGGSGGCDDAIKTEIVTKVSNSARLYTTSLPPNLASLQPLPLPLHSHSISHINDY +RRKHKGGRRRRSSPRKLRRHHGSSRGGGDVDVNQRAEVPFSNTGLLLQQPLPMPKEGSVY +HAPLRDWFTEPFVTNGSLVFSGMWSPTGARSPLVLVLVGEILKSHNKHRVAWRKHMPLLL +LCLFLGLDHCRPLVQEESKQLLVNMLRVICPRIDLLRLLSLQLEIDSQWIARAMNSFSLR +GPQFCLTLDDGPSVPKFYLPAPTDTPLTEISTCPTWATYCVPASSSIGALTVVGTSVAPP +AFASSATPPSGLSPLTDAPPLTVTTATIGGGGIMSRSSLVAQQTVGARPSYLFGSTYSLM +STATLIPAGLSTEQESKRRSIVLSDLPPPPTSSQPPTSSAQTVGAVADKALAVSANEMVV +VAAPYDCESKHLQQAAKRIGQLLEEVQDRSIWTWEDITPSRFQQVTLVTLRSILPPYPPT +SPQQPCKSILLLRSACFLDRFVRCVAEAFAEAEAGCRCEEAASCLRCHHGAPHRHSYDGE +ADEVDTDNLGVSGDPSAEGSALFLFLRSGCTVTRFSQAAFNMGITCPNRHYASRSLQIYR +ALDAQLDKKSLSEVLVRLVESVSDANEEVQGYMVELFLTLEAAVGHIQYRGTVLSSSTAT +NIGQFTTRSRSSAPDSPLITSKPPQPPQSASPLLLAPQAPSSTPPVHERRHSRRGSSSSL +SSLLGYPSGIAGTAAVSAPSLVPMQPTVGLTVPPIKRPLMPPHAEAVSEETDVTPLAALS +MSTVGPVKPTPNAIRFSNSTGAATVVTNNSRSIYEDPVTDTSTVGTCRLAPEEQADLLAG +IFWIGVCLLDSDYEYEYLAGIRLLSRLLPCVCSLSNTASTATITTTNSSNNRKWTPQQQQ +QNLPSLHDHVLKLFHRLKWTTNTEDGDYPGLLGLLLKGCFSSALIDTSCRLLVHLILAVK +SPIVDPNAETRQYRRAAMSGTSCSTGSSTSYPGSLPSLVIALLPMLLTAWDEDPDSPTST +IPEPAIPAAEVCEACAEGSGSPAMASPFSSPSSSSPPPPLGSWVHRSVVLSETAVPSPSS +GLLSLITAILASGAVAKKAPQLRPRNPICVLAAEQLAELAVQTDAYRFNNLAIVLRLYSN +GTFSKDVNQWAKCVTRYLMDGCAGLGPRILTFLTAFLTLGPPCVQLSLLHFGYWFLQTVD +LNQSEMSSRIRAFITATSERFVNTPLWPEVTLLYQVVVARSATLTAAPSNPEVYTLPGLD +PSGSGRVLDSLAAAAAAAVPLPEAEPQRIALAGRVLDFDSSVLQSAPLIAGGRGQTSPRP +GAASATDQSQHRSPARTAHLVEACGLSAFSFSSFFRLGAWKTPWNRQARLRGLFTNLLNC +YGSELDHLTAPRSPSVIFSQSTETLDRQLSMHSSSETNSINDASNPDDGHLDDTSSAERA +AVFHDLDTYLDAQLMNINFLDLPDAAWEDHSRCPWGVRGHSITSHTEFHEEGDDQQQSEP +QLQSQGQQQQTNQPSNLFSVGEDSSLPNTTIATGEPFVISPPVHLQPRRQFPHEGEELEV +EDVEEEEEDEDDDEEEEELEGEGGDFLAVALPTVPSAVAERQSQLSLQLSTSSSAAPVAA +IDSAPNIALLAPSTLHRRFNSQLLVAWTSRDESPVKWLTDASPTADVTVGTVSTISPAMQ +RYQIAPSTLRKLTLSIQPQAVCSRRRRRALTTFGGSLDSLHGQTNGQPTAVFKRGSYAVL +KSFQRSSSTSSFLKIPTITSNGVSDVVEAPMAPLKPPRRHRRFGRSLELGTQQPPNLSSR +MSRSSCALVSPPEADKASSTAGITPEQLLTPTPSETAWTTCQRQLKSLQQSSMRVTMTSA +QADRLRLLLTSLRGTFSGMLRRTTLQAVELANLAFFRCPPSSDLDSEIRISNLNKISAIM +GDALPVPFFAIHPNSVPLIDEHSVSLCLELGTAYEEWEGAMQKLATTCKSTAASDWLQHE +RICRDLQEVLQRLLLFFRSSADLLTSAYRHLVDSSVGVTDFSASGVEPLFARLRPYILTP +PPSLPPNSQEVGGMEAWRARCARLLLWPPYNSLRSEEVDAKRALAIYAAAMKSTTPISMD +DIMTVVEACCEHLWTGANCNSGVALMFAQGRAIESRRFEAFAVASKKSSTLFFTTDDFLA +SDGEATLPPLELVGRLLRLLEA +>CAH8433161.1||57078||1309000265 CAH8433161.1 unnamed protein product [Dicrocoelium dendriticum] +MQTPFRLLEPTTCSPLATFPFRVYIVRIRGERHSETQSKLHHIVNSLFPKNQKFIVPKDA +PVNIFVRIIQFIAHEKLEFAVKEIIIDRLGVNGLQKNLYMPERMNIGLCAFLLIAHGLQQ +KEGAPPMPQQSIGGGGGGGGAGLRQTVIKRSFHGTNLNDALGHILGIQNYLSPIRRAFEL +ILRQLDSQVCRSMMLSKPEVTQKEVEELMTAERKPKMDLLKTCVACIPRLLPNDMTKPEL +LELLAKICLHVDEDVRKMAQQAMANLIVELPAFRVKTIQVFIQFIQKNIADTNPTQLDSY +LKTLFHLLNNWNLALQKDGAISPNSTEKAVLYEAEGFALVMLCHCRTITRRLAVHILRKC +RSLLNLINMAMADSSAVQSQELCCIDVLDRSVPAILESVLPQIPPNERHAFSNPYNLDFF +AFAERSSPIWLGSPTPLISSNPVALPAHLTGPPNSGLLPMNSLRSSPTGRVSSPQSKESS +DVVISKDTRCAQCGMFVDTGCPGHTDVGTRIQEDLKQPGRALTSCSDQAAHPTPFAIVDS +QNQEPTTHFQHSAQPNTHQGQSSQQAYVLQPVQERIIIGDIWATCLSIAFSPSRLISMCP +QTVKYAWNVLFQRLTAVFPFIDPSAQIAENRASSILRSSSKKPSTERDQLLPLWHNYAVL +ACCIAPSSTGLRVSKPYDNRYLPKSSITALSTVSKPVSRPGLPTKICGPPDNVSDSRTSA +MHPTPNTSMNAAAGPPQPSVTNVDSKSHTSPVVIRDTARDLVKLLVPLLRCEQSDIRDSA +VGALGRINPASFRDVLEELSPLLRESVDRKQENVRRRRRRDILRVSLIRVLALMSQTGLF +AYPESNALTSQGCLIPQLTDYLDGMRVYLDSVSDQATSLACIGVTGSPNVIYPSVPSTAS +PGSLLMGAAGSITPMTSSFSSNAGSAPAITAVNAASGISLNEASVLTEIRLHFSIFILEL +IRHLPKEKRTQLLPTAMRYQLFLLLSHWSGYHDHVMGLCSEVSSTGFSILFTPGNGPSKS +MQTINATSGSSPVPAVSYSAVDSSSQHGLCSDSNQSALDANAQLNIPVLPQTYENTPSVS +ASAYRVVSSASQTNTPTTAYPPHTIPDPTAGQLTLTFLDSWALRLWTDLLWSANQAMGAL +LTCGPIYDQQALFNDSRDYATSEHSSTVDKQIIPSPGYVLRWISDLLVARDSGASATPWL +WYCPIVDPSFGRSNRSPVGNCYCPVYGTPPRTALAAGERRMDTLLRQLGEETLVLLLDLN +QASPALLTWSIEQCYTASNASLSEACFASICRVIANIADFPCDLIPLLVLAFVHLNHPQK +TISDRAFYLLHVIYYRFVIKPSSMIHERMHSNASNRGHVGDSCAMPQNPRSTSSLPLCVE +LKTHLRFWQSFTRWAPSEVIRQFAAQHPDLTLEVFSEITKRLEVSRECWRVPLLHLLVPW +IVNIELVDLISGIARAAPKFACDGDLDATDPDIPDAERKYKSCHEQASKSDGKRNDDPTD +SDDADEHLIHSNPTVRKPTHHRSAAQRPLPSQSTIGSDLDSDTDLGSGQRRLGETFSCIR +TSKADTVPLAKPNAARSRRAQEVHRRSVTFPTHSLPSEDFWFSIPLTLRCNGWGSKQSTE +LILNNLFYLTLRLAECPTAAEALKQVWVVLIRHRPANLSVILRYIIILTSLAPASLLTHA +KRLVTSLASEQPESVVEELMVEMQLIDGLGLIVERTPTLPFFRVSTADGLGTAPTVMDVV +DSANRHSLSPPATVLQCQQGTALDDTTERKSALQSVDRELVGQIDPELVAIGLGSRSRDK +PSESTAVRSRKSALPFVGNQYPHADDELNTAPSEQPHFRRPKSAVGQSSTTTVFMENGRN +ALSGDAYLHVASQETSTKPSTFDRGNDEIDPELDVDAFGSHRTTRLSPEDKYATLRAKDL +QRLLSAATDTCIEEVSESDCVPLRTDAQVRSGTLPTTQVSRERYLIGSRLSKPAKRDSSD +DFDPLVRSGTRYSKRAKVKTRSQPRLVDRRDIPLKVPPQTVSLPPSLARLSPLILPLPFQ +VPSLSNMLVSEERVCRSGSNSPQGIQIPKTSPPCLASDFVPMTSAPRHPRPLAMPTHGCY +KAPLDAWLSEPLVIGGSVVFSGAWPTASARSPLVLLLAGALTQCPNIRVDWFQHLPLMLL +CSLLGLDHCRALVQEECKQLLVSMLSLACSEQYSLRLLQLELESDSLACTHPSTLPGPRQ +PQYRLTLDGGPFKEKFANHLSSAPSSGPPISDCSQHSPPKKPNRRDIIETPCNPQIGPLA +PVHTTVPVDLSPIPYSLPSIRPRSAWTGSTYSLISTATLIPGHDETEVVSPPRDYGSASL +HPAEEESNHCNILSSPDKRPAGHAVWSWDERDSVRSQSQSTLHLPIPPRSTPTDCNATNS +TKQTSVDRLVRVIARILALAHSKPDDAEHCLYCHHGPPHEHAFAHRLKSQPAPILPTPVP +NLCVVARLSQAALNMGLSCPNRHYASRSLQIFRILGAHLDRKNLSHLLARLGETISDTNE +ELQGYVTELFDTLEAAVNHIQTKSDYFCESYIKQTASVTGQARTAPASPRSMNHGIHGEF +AHPAVAVSDRRRSQRQSSSVNPSAEGSCAPGSWFRPHPPTHHSALNDLPTVSSSVNLSVS +PSSGSADMVICRKPICRLPTDEQADLLCGIFWTGIFLLESDFEHEYLAGVRIVVPLIPYV +CHNSSSGSTRSAGTSQISLSRRVEKFLSQLHLDPPFSGLLHLAIKGCSSANLVHSACQLL +VSIVPFLCHPLVVPPRSNQIASQLSFPVVLVTLMPMLLTAWDEDPSTVGSSSNAHPGDVS +VEQPSVLQEGFLGRGVCAATHAIPRTLGSWASTPGAPVRGPSDFAHSSVKTPRLGYVDSI +SFPGTCSVGGFGAANGGGFFPNTTGSPAPHRSAQHTPLRPRNPVCIQAASALAQASFQID +SERLACLTHVLRLYAAGTFSKDVDQWTKCIVCYLLDGYTQYISHVLLYLTTLLTTGPLCL +KRPLLKMVCLFVEQVDLNTPDLSSTLQHFITSVTAQFLGTEFWTETMNILRVLVSRSSSL +TVAPPPSMYTLSGLDPFGSGRVLDFAAAAAAVAVPLPESEPPHLELAGPVLDFSFNLLQE +ASLLAPQFSPAVTSLAADNLDSPTIEKGATREEVQLPDSELCTVFASSATVWDRPGTCQG +RLRHRLYNLVGCYGLPAEHRLGAPRSPSVIFSQSTETLDPQLSVHSSSEATSMVDISNSD +DIRIDETTSMEQAAVFRDLDTYLDAQLMNINFLGVPDGRLGSTSASASEDEPRRPWGVRG +QSITCHQDLNDDTFEQAVDLLPTVRAPLPHQMREAEAAVSWVYRHRSYPAALGSEVSHEN +ERISATNWFGPPSARSEAMDICPSKSTSPAARDSPPLVSLCDNNEGISGPTSNEVCTSSS +SSPRIQRLLSPLQTDAPTPDNKLSDERFINVSVGKYSNLRHDRTSVGSDSQLLSERGDHS +EPLSDLVKQTDSGSPNFQQTSLPTRSTSSVKVRTSPSSICESSLPYQTRDRVSEKLSSST +SLPQMVPTRRQSPTLIVYEQDQSPKTRPTTLALIPAASCVSNHSPTRSHRPSGPQGKIRT +TMCTSSSGLTKSNGATDQLMTVPHRSVSMSNLTVQTKRMEPVMKSVYAKQKVVRSPAKRF +PFSGVLSRLSSPLFKLHRSRATKRQSYSAENLYELTKPKVTFASPVDESERPHSNIGDSE +ATSFKGSCLSLHSASETSNTPSIDDRNKISPLATSTDNISRVGSADAVQTQVVHFNVQAP +FFAWSAPEAPITMDQLAVGPLLTQLVDRFNETQAALYRIAKMLSSRNSAPVPQTISRLIR +TLIDFVLLSCTVTEEVCSRMEPFLASTATHQLNTMMRRLLDQLLSSTSFKNDALGFSRLT +KDDLSMSAIGNELVKALSKEKSSSDSVRQETVNVFYRAIVSFRLNASNAKTTNHSIPVAD +ALNGVLDLLMREKWKSDRSSDIIFAYLAIPVLIPSDHSTNVLSFCTQLRNQLGRSHRALS +QS +>CAH8433210.1||57078||1309000268 CAH8433210.1 unnamed protein product, partial [Dicrocoelium dendriticum] +MLPGRSGELVVCSLLRQFIALTKSKIDKALNDRQENNLLKCMQLSEDVAYEQLLDALYAS +AEFALPSLLRIFLQWYHSQHSTGTQYSVHRRRCQSSGAQTYLADSMTSSYFSGSADKENQ +RSIDVPAVTPLPTGRPDKMVKSVCEMSSVLRSICLTSSEARTLAERRDLAIDIIFCQVLL +SILRQLSYHPGHNEEIELVLEQSFKRFKYREDLLSQNTENVNIVADLYAEIVGLLSQTRF +PLVRCRFMRCLNELRSKENSPNVRQSIISLIMGMKFFRVKMHPIEDFVSCFVFLQELGQY +FLEAKEPEIKHVISDLFVEILLPVAAVARQEVNIPALKQFVENLYPTSVDLVNKKKHIPA +LFPLVTCLLCVGTKSFFLNNWTNFLTICLSHLKNRNPKIALVSLQSLSCLL +>CAH8871380.1||157069||1310018167 CAH8871380.1 unnamed protein product [Trichobilharzia regenti] +MRLNEDPAYENLISAFGVAAEFALPSLVSTLSLWYRSQHSSGKYYQLHQLHCFASPNKLN +ENDIPVATQRPSEDSHIFDVTQATSVPPNKSVKCASESASLSKSNHVFPPDASVIFERRD +LAIDIIFCHVILAVLRQLPFHPGHNDVIELILEQCFRRFNYREDLQPKNSENINLVADLY +AEIVGLLFQSRFALVRHKFMGCLNDLRSKENSPNNRASIVSLIMGMKFFRVKMHPIEDFV +NCFNFLQELGQYFLEVKEIEIKHVLSDLFVEILLPVAAVARQEVNIPALKTFVENLYPTS +LELASKKKHVPALFPLVTCLLCVGTKPFFLNNWTNFLSICLSHLKNRTSKVALVSLQSLS +CILWVYIVRIKGEKHTETQNKLHTIINSLFPKNQKIILPKDAPINIFVRIIQFIAHEKLD +FAMKEVIIDRLGVNGSQKTLVMPERINVGLCAFLLIAHGLQQKDGEPPMPQQCIGAGGVG +GGFRQAVIKRSFHGTNLNEALGSLLGIQSYLVPVRRAFELILRQLDTQVCRTMMLPKQEV +TQKEFDELMTADRKPKLDLLKTCVACIPRLLPIDMTKQEILEILAKVCLHVDEDVRKMAQ +QAMANLIVELPAYRVKTIQVFIHFIQKICLKTLFHLLNNWKLALQKDGAVTPNMAEKSAL +YEAEGFALVMLCSCRSITRRLAVYILRKCRALLNLIQSATYDPTLKQSNDVREMCCIDVL +DRSVPMVLKRLLPILPLSERSALSSVSTLDFSVFTERSHPVWFSGSTAPTYSPIVLPANC +LNNNQYSGLMSGNDVQHNETRFGNLSSRHDLNRPNYHNNNSPCGGDSVTANISEKSKLDE +TSVQETFTKIVRPSISGLLATSRTLDSQNVQTSKPCSSTTPVHRRPSQYNQQVQHTQQAY +IMQPTQERVVLADVWGTCLSVVFSSSSLPASCPMAIKSAWSILFQRISALFPLIDPSAQI +AENRASSLLRTASKKPANEREQFVPLWHNYVVVGCCIAPSSTGIRISKPPDTCYQPELRV +QKHSPDCGTKRDTLHCRDSSRSPTQPDEIQNQTTDSNANSSTSRATMSNSPCSNWPNSTD +LKHSASYGKVNARDLVKLLVPLLRCEQSEIRDSAIGGLGRINPAAFRDVLEDLQPLLKES +FDRKQENLRRRRRRDGLRSALIKILSLMAQTGVFAHPESNSVTPQGALIPQLLDYIDGMR +SYLESVSDQLLSPVSHTHTSSASLNILASMPSASSSQQVGTSGSNTSNQVSGSISSGSVV +NSLPTPLSSGPSVSEAYLLNEIRLHFCIFTKELIRRLPKQSRANLLPPTLRHQLFILLSH +WSAHYDHVMGTYFGSEGPVPIQSCFNNSNVDFTEHTTGSSGAVGVSPSNEISPGFIGPHH +QLVNDNFLQSVNNLSLNNVTPSGSTDSTANLTIPLNQASHSPTFQQKVSNNEQTEGLDSL +EYCLWIDLIWLSNQAMAALGAAVPGSDSVYGSCSDRSNGTSCSNVCTPSPGYVLRWISGL +LVARDTALSSSPWLWFCPITSRGLLHNTLNPGGASLCGNSHTRSRTIAAGGGRRLDSLLR +QLGEETLVLLLDFNIDNSGLFNWVVDQCYTSSNISLSTACFECICLIMSNIPNFTCDHTV +LITLCLIFIDHPSRFLSDRAFYLLRVLYRRFFIERAEILYKQVHSVESKKPYSDKDSPQS +QSSTCNQPNCECTQLSTYLRINKLFYRSFNYWLPCEVVRQFAAQHPDYTLTIFSEVSRRL +ENAREGLRVPLLHLLSPWFVNVELVDLIEGEVFDSPDHNLDMDDDFSYTEKENDPFSMSN +YEKPSDIVDVSNSNNNTSDQQQQHVRKDNPEYRIRLSKRYKRSKSLLNAESLRYDPDDTD +ADSDNINHQRSFKSLSLLRMRARQAMMGENTDLHSPRSFRSSNLCYNNSHFRNGCKATYA +TNNAALWSTVPPTLRSKGWGSKQATEFVLTNLFYLTLRLADCPTSSEALKQLWITLIRHR +SANLRVIVRYLIIVTSLAPGTLLIHVKRILAYLASEQPESVIGELMVEMQTIDGIGLIVE +PSPNLPFFHVIASIPQRKSNEDIQSSKVKSFIKHYSSSKMFEDSLKKPTSHTLPSQDGLV +QPDAELIALGLDSAHNSNTTTRTRTLRSAVGGKYDCHNVNGTSNTSTVTPAVTMRPKSAT +CQPCGNRSSKDDPQIEMRNTGKHSTSISPASGTANDNLTSMSETPGTQKSHCLPLSSNCQ +LVEGSDGEPEIDVDTFEGLGKRGLPLDDKYATLRAKDIANLLIQPIPKDFESSDSVSDCD +LPNVSHRGTTFPNTYAAQERSGFSSTLPNQVNEISRLQMCGRTLRQPKYLKEFRERRFKW +FSSSKNETISSLAMDAHLHSASLPPNLTDLPPLPLALPTSISTILSLSSRGSTGSNTCIL +PVNLSSADKRSNTGSDKMDNKPNSVRIRTCSNSVGKISFDCNSKPLPMPINGGYQAPLGS +WLSEPLVIGGSVVFSGGWPPAGPRSPLVLILAGGLTQCRDIRIDWTQHLPVMLLGVFLGI +DHCRALIQEESKQLLVSLLDQVCPHHDALHLLRLELEANLRRLAHPSALPGPRVQDYRLT +FDGGPYCEKFNLVIDPFMTIESDNLFSSPIVSSRTVKYDDTEQFKNMPSTEKEPDGDSHT +TSLDLLPLKDIDVTASLAASPSSPNCSPPMITQLQTNPTPLIKQGRLSDSMFSLTSTATL +IPRHQDVNVSEPQYFNDTTMHKSNIVNNSNNQTIDVSEKHVLRSKTLSNATRPSSVYTSS +QPSKTATTNHVQTSNYHPLKMEKSKKVKESDLKLLEEAIQQLRCILLDGLGQALWTWEDF +NMQRNSFNSTIQSTYQYECNQIFSNEKPEIKESTIKTKMNMNSLEQLVKLVARILSLSEA +GSEEAENCLQCLHGPPHDHNKRGVVVDQDDNCKDTWSDHSNYCLVARLSQAALNTGLSCP +SRHYASRSLQIHRALGAHLDKKSLSQLLARLGETVSDGNEEMQGYVAELFVTQEAAIHHL +KTRSDSLLEPVSTLAPDPASPSSSSISGNGRKSIQIIISNRKSSVHLNTVVPVNDSPTLK +SDQANSPSSERVSSSPSSIPSTPSHFTKKSHTSVTSSPKLINQNNSVCLLPAAERADLIV +GIFWTGVLLLESDFEHEYSVGLKLLSLVIPSVCASNPRHSSTAHGSRINLSDRAQKMLNQ +LRWNPYFPGILSLVIKGCSSPNLVDQACRLLIILIPFLTHPLVVPCGRMGLPIQISLPIV +LLTLMPMLTTAWDEDPSTSVPINPYEMSSEQFSFLQGGFLGRGVSSSLNIIPRALGSWTS +CYCTAHTSFANPNSTVASNSKMRTDSVGLFGNYSSNSLGVFSGAESTLNLNMIGTGSINR +DVNSIPKSPVLRPKNPVCIQAAESLAQAALKIDPVQFSNLALVLRLYASGNFSKDVDQWS +KCVICYLLEGCGSNAPRMLKHINMLLTSGPPCLQLPLLKVAYLFLQQVDINQPDLGSSLQ +NFITSIIGQFLGTNLWSYVIPILQVTVSRSAVLSTVPETPAYTLPGLDPSGSGRILDFAV +AAAAVAVPLPESEPPRLELAGPVLDFTFNVFQEAPLLAPEFAPSSSSNANLENISKVDTA +SYDFSPLKGNRKDYIDASDIGLTETFVSASGGWSKPGSCQSRLREKLYRLVGCYGMPAQQ +CISAPRSPSVIFSQSTETLDPQLSVHSSSETTSVVDISNSDDIRLDDTSSGEQTAVFRDL +DTYLDAQLMNINFLGVPDCRLASTSASMSEDEPRRPWGVRGQAITCHFDLNGDGSESIAN +ETFAGLSRPIQTIETKIITPAESDYLSSTYHQSRTNSLPQLTAFIDNRSDGVKHTDSKNL +NTNRYSTITISNNYTDCNVDNRYSTGSRDEQQLHMDIQGASSSLIWKSFTEESNTAVQLD +RADNDYMSLLYTTRKSDTLSEEDCEMKSSKTSQTKKRLNVNMRNSRAEHVSFDLGDGTEE +VEGEDSEVENTTRTTTTIGGSVQNYEEENRQSNAKGILVSHSHCQSHKNAHTTLQSNAVH +LPSASIVRFASIQVLNNSNCDYIDYSAAKSRPNIYFPRCYVSCRNELGLRDKQHIQILKK +SEHQSTNHRYRVCNTRNSLLGQIKRSASTSELSVNKQQYILIESSDGNKSFTNSLLLSPL +SYRSEVNSNRDRNTFGQSSSSLSSSRSSSPKLSNNEFKQPISDLIITDNNNNGDNDDNNN +TSTVKAVSSWTCSSSLISPQSIPFSPSLSVSSSSAAASVHSSHSSLHYTETGGRNKKSNH +YFPAVTKMHRSTSDSSINCSGKKIIYVRRQQQHEHGEQHSDQQQHHKRHSAHINENAQNF +ASSECASLNKIVSVQSSTSLCNWRNPLTTEQKWLELESILHSTTCSRLNEQQMLMCMLNE +LPQSYQELVSRYIIALKQTINLSGYSWTESNRFSSLAAIH +>KAF7259496.1||59628||1385009414 KAF7259496.1 hypothetical protein EG68_02984 [Paragonimus skrjabini miyazakii] +MDLLKTCVACIPRLLPNDMTKPELLELLAKVCLHVDEDVRKMAQQAMANLIVELPAFRVK +TIQVFIQFIQKYVPDTSSHQLDSCLKTLFHLLNNWNLALQKDGAVSPNITEKAVLYEAEG +FALVMLCHCRTIARRLALHILRKCRSLLNQINLAKSDPLTRHNRSPHELCCIDVLDRCVP +IMLQRVLPLLPPNERHGLLNVTNLDFSALADRGGPVWLGGAVPVYPCPPVVLPTHVVSPQ +HSCLIPVGGVSSASTAVERPQSKDACDVSKNAACTGQYGLFPEGSTLDTATRFSDEHHQS +DSHRPDSQTHISSGQVATGRQSQVQQPTSHHPGQYTQQAYIVQPVQDRILLGDVWGTCLS +VAFSPDHLVTACPEAIKYAWGMVYQRLSALFPLVDPSTQMAENRASSLLRSSSKKPTTER +DQLIPLWHNYATLGCCIAPSSTGSRILKPQSRDYLLDFDRTQRPSSLNGTVQTYNSSCSQ +TPVPSTESSTNTPSTTTVATSRPTQPQPVSSDSKCPVPVLKETARDLVKLLVPFLRCEQQ +EVRDSAVGAIGRINPAAFRDALEELHPLLKESVDRKQENVRRRRRRDGLRSSLIRVLALM +SQSGLFAYPESNALTAQGYLIPQLTDYLDGMRVYLDSVADQATNPACIGSNAAVSNQLSI +VTSSPSVGGSSTGSLLVGTTGSATPVVTSYSATTVNTSAGLAATSYQNVISTDAYVLMEI +RLNLCIFVLELVRQLPKEKRDQLLPTMMRHQLFLLISRWSGFYDNVMGVSLSETEAAYPS +FFGATRQNFYRLSRSSVLNSSLSPIAANQLSANDSASLNSLSLDPSQPSLEVANPSNPST +TAGPGLSTVDTNAAQVTPLVSLTLPGSGPDLPSAQPDSWELRLWTDLVWTANQAMGALVC +CGPVYDQQALFGDPGELLSTERSIGMEKQSVPAAGYILRWIAGLLVARDPALCASPWLWH +CPITDASLKYAPMSHSGSGGGLTFGTQPRLTFVGVDRRMDTLLRQLGEETLVLLLDINQA +SSGLLNWAVDQCFTATNVSLSTACFFSISRILENFPDFACDFVCLLILSFVFLDHPQKAI +SNRAFYLLRVIYHRFIIKNNENVNLVADLYAEIVGLLSQTRFALVRCRFMRYLNELRSKE +NTPNTRSSIVSLIMGMKFFRVKMHPIEDFVNCFTFLQELGQYFLEVKEPEIKHVLSDLFV +EILLPVAAVARQEVNIPALKQFVESLYPASLELVNKKKHVPALFPLVTCLLCVGTKSFFL +SNWTNFLNICLSQLKNRNPKIALVSLQSLSCLLWVYIVRIRGEKHTDTQSKLHHIVNSIF +PKHQKFVVPKDAPMNIFVRIIQFIAHPGAIIHDRLHNREANLNSFGIVPDSSESPTSDCS +SQVCAEFSVNFQFWKSMSRWAPAEVVRQFAAQHPDLTLSVFSEITKRMESCREALRAPLL +RLLIPWIINVELVDLIGDVSPSIQAVNGEAKKNVTPRVMSSGSCARRLFTDEIVHTEEDA +VTDGEVDDDDEEEEEEDSCEVGDQDGIILQESDFNRSSVIDRHSMDFRSSALKRVRPRPE +SLVVLGSAPSDADSEYEMASGIKSHPVPYMRTRKADSVPLTRPALNRLSRSGYISGGYRN +KSTVAPSRSGVCDQFWYTVPPTLRTSGWGSKQATELVLNNLFYLTLRLTECLTTTEVLRQ +LWVTLIHHRPPNLRVILRYLIVLTSVAPATLLAHSKRIITYLASEDAGSVVEELIIEMQT +IDGIGLIVERIGCLPFFRITTAASVARANDGLEANPSNPTPTGTLAEKSTTEYEESSRIP +TDPSVVDSSLPHQALLQSVNPDEHLEQIDSELVAIGLERERLAVLKRKKVTHTRATAFPF +GGSQRRSTIEVSTETKSHTPSRRPKSATCQPSTGSDVDNACPRRIIDDSPSSIPQTPDNT +FLITSTDSTAPVSSNRTEPELDVDLFKSRPVNPEDKYGTLRAKDFSRLLSITDTRESEEA +SDLDPIPARNDYQARCGTLPMTADSRERYYTRTLPDAELRGSPGRCASQLEPSTQLPSSQ +QLRHSKRVKPKTRTHPRPVDSTKFVLQTVSLPPSLARLAPLPLSLPMGVPSLPSLWLSSA +TERTDSITSGSPQGATPVPRAISGQSGTRQSATVLQSKLSPITSQGSLPGKPPLQPLAMP +PGGCYQAPLGSWLSEPLVIGGSVVYTGSWPASGARSPLVLILAGELTQSPTIRVDWNPYL +PLMLLCTFLGIDHCRALVQEECKQLLVSLLSLTCPEQESLGLLRLELEDCLVSCAYPSAF +PGPRLPKYRLTVDGGPCKEKFNRLPLVHLPVSCNFSATGLTSVARTKKPERRIGGKQICD +QSLSHGPAPNITLVSDSVLASIPNRCGSPPLTLFPSSYTQDVDKSGWNGSSYSLLSTDTL +IPGQDDIGPSSRGPFDCVPRGKLKDESSHLQSAFPSKKIRLRPPKSIPPKPARHSFGPVL +SSAPEADPLLFELQEPNKTNSKLLNESVEELRDVLISSAGQAVWTCEDYDVQRSRFYYSP +LCCHRSIEREADAAGEINQRQLNDSSLGRLVRVVARILALTHSEPEDVEQCLQCSCSPLH +GCSSTHRFRSHQLETHDPSSNCVVARLSQAALNMGLSCANRHYANRSLQIFRILGAHLDR +QSLSRLLARLGETVSDMNEELQGYVTELFYTLESAISHLQPKNELSLDHAFKRRANPSQT +RTAPTSPRSAGRWPCTDPSQALVAVSDRRRTQRHSTSANLITVDPGGALGGFRSNVNANL +PHAVSLCISNGVSIPVDSVTRHSFMSKAAVSNTKQAACLSAEEQSDLLCGIFLTGIFLLE +SDFEHEYMAGVRLLTTLIPRMLGEGTPCWGTAAASTAATTLSLFGRAEAILAQARLNPPF +PGLLNLAIKGCSSPNLVDPACQLLVCLIPALSNPLVVPPNKTTLASHLSYPIMALTLLPM +LLTAWDEDPSVNLNGLCPPVDGTTEQPSVLQGGFLGRGVCTATYAVPRALGSWASTSVAL +SPVRNASVATGSSTRPNRLCRTDSVGFPGTCPTGVFSTLSHPGPIAPQSTVYGASIIPKL +TTQTPLRPKNPVCIQAANALAQAASQIDSNRLANLELVLKQYASGSFSKDIDQWAKCVVC +YMLEGYNRHASHLLRHLTTLLTIGPVCLQFSLLKVAHLFLEQVELNTPDLNDTLQQFITA +LSDQFLGTDFWVEITKIHQVLVSRSAVLNAPPPPSVHTLSGLDPSGSGRVLDFVAAAAAI +AVPLPETEPPRLELAGPQLEFHFDLMHNAPLLAPHLALSELVGLSNQTGPDLNPSLEVDQ +QKTETRLPDSGLGDLFVTAVSSWNRSGACQVHLRERLYRLIESYGLPSNHRVGAPRSPSV +IFNDIRLDETSSMEQAEVFRDLDTYLDAQLMNINFLGVPDGRLEDEPRRPWGVRGQSITC +HQEFQDDVLEQATDLLPTVRAPLFQASEHANCHIRWRSRHRSFPSVACEGATAESEPSEA +NDTIGRGWIDKVSVNNSLGESRLYTLTSSAVLSEGSSTHFASMRQHTGCESRALVPLHCD +LNDISSSSSSLSPDALDFMPLDATDITIMANDTSPCAHTSAVLEHESGIPCLVSDFRGKS +NGNDMFHVSSCLMEGMELPQPHPPQYSDTSSRSPSHGSVFIVPSHHSPTSSLHLISTDEH +EDRGLRKMNSSASLPQMLLLPPTFRACDSALLTASQSRPTQIVNRTTVQLSAETEKPSEQ +VLIGKSNDVANASGLTENRNPTHRSLSTSNLLFKGEPVENGVKFYGPRGKPLRSPTKRFV +TSGVLNRISTPLLKFHKTFTSKRQSCSAEDLLHLVKPKVSFIMPTKEAVCESPQNELHDQ +HIVSQPPRNKSPRGSAYPFRIESEGFDCSSQLTMLCGAQQSTLGDQPGVSSPTPDPDKLI +PMSRDLLSCTWLDLHTLETDWLYDVRRAVDRSPVRARITCFTNLSNVFARLANRCYAAVQ +TTINLAGCSWDDANEFRQLASIARLIANGLRPPFVVYYLEQDMLLDSDQLAIGSPLSRLI +ELHNQVQAYLYQICQFITTHDAQSSVSILIRALISLLKFLRIACTSSEDVCRYLELSVRP +SVAQNELTHKLKCLMDRVLPLLPEYSTNHPHPSGSCGKCDWRPVLDDVFTQLLEVEATDP +PLRPDVVDRLNRAVAVYRQRMIGFDETMANHRALVDRVTGILEWYLRQRWKTVPDPDVRL +VHLILPAVDAPDGSTDVLGCCSQLCQQLNKLLNQFES +>KAF7259497.1||59628||1385009415 KAF7259497.1 hypothetical protein EG68_02985 [Paragonimus skrjabini miyazakii] +MAYACRIFQLIFYVQEKLEFAVKEIIIDRLGVSGLQKNLYMPERMNIGLCAFLLVAHGLQ +QKEGAPPMPQQSTGGCGLRQTVLKRSFHGTNLNDALGHILGIQNYLVPIRRAFEMILRQL +DIQVCRTMMLSKQEISQKEFEELMT +>VZI53646.1||64606||1416021950 VZI53646.1 unnamed protein product [Sparganum proliferum] +MRYCLLRFLHYWAINVELVDLFARAAEPSVTHSTRYRTTSSHYSVRRSLLTADTSTPFIE +PEAAVKASRGASSSNTQQQQQQHQSPPEAVDVAGAAVVSTGPGGNDTSSSSSDSSVDDGG +SEDSSNCDSDADMVGGQPTVAGARPTSLRSDLAYARRRVQPLPNNEAWASVPLTLRCSGW +GSQQATEMILNNFLYLTIRFGNQPSECAALKELWILLIRYRPENLRYILHYLIVVVSLAP +ATLSSHL +>VEL34069.1||117903||1437027472 VEL34069.1 unnamed protein product [Protopolystoma xenopodis] +MVFAANQAMAALVCCGPIYDPVALLSNAFVTYGHAGSSSCVGPTASSGSQIASGTWLISH +VPTSIDCSAAISIGTGAGLSISCLPTSFSGTGSTSGLISTGYVLRWLAGLLSARDALLDS +SPWLWRCPVVGMGLLAESFAQEATSAAVGAGSFMRSPVIAYGGGRRLDTLIRQLGEETLV +LLLELNPGLPGLLEWVVDKCYTAANPALTEACFVAVFRILTHNMNYPCDSTSLLTLSLVF +MEHSKLLLRERAFYLFQLLYYRFIVKSLLVQHDAIHRGTPSDQPHNKKRDSAAASSGDVP +SSHEMHAATQNTAQGIRMCSFGKKDGQMEPCSELCHQVAVNLQFWRCLSASWTLVDVSRQ +FAAQHPEYTLFVFSGL +>VEL40899.1||117903||1437034296 VEL40899.1 unnamed protein product [Protopolystoma xenopodis] +MGIAQLAENRASSLLRTTNKKPNTERDQMIPLWHNYIVMGCCIAPSSTGVRVLEPLSVGR +SSSLSVRNVKQNQMHQRSPQYCQFQPSVQQPNLQQQQHSHKYHLQKPQPHHRPE +>VZI50194.1||99802||1469025244 VZI50194.1 unnamed protein product, partial [Spirometra erinaceieuropaei] +MSADDVLYSEEDETRRPWGVRGHSITSHTDFHEDGNETAGMLVANESTSCPDPRNRKNLT +LELSSKGTLEHPLGSWSPPLDSSPTQPNLSAISANGAGGGGGIADEDLSATTSADLAPAR +RNSISVHSNSSSVSVGTRGISLLAPVLNRQFTSELMVAWVSQDSSPTDWLGPSVLSSAGP +TKLKSSKKTSAGAASQSPSH +>CAH8639752.1||39320||1506013587 CAH8639752.1 unnamed protein product [Heterobilharzia americana] +MISVFFLSVLLLDKPVVDSVCIVFLRMNYGLLSVASVIIVTTNKFLRLIMAVTKPGEWAM +QVLLRQLTSLARSKIEKALEDRQETNLVKSMKLNEDPAYEQLISAFGVVAEFSLPSLVST +LSLWYRSQHSSGKYYQLHQRHCFTSPSKSNEVISTATQHLSEDSNHSFESHPNTSLYIAS +LPKSYHLTSPDASVLFERRDLAIDIIFCHTMLAILRQLPFHPGHNDVIELILEQCFRRFN +YREDLQPKNSENINLVADLYAEIVGLLFQSRFALVRHKFMGCLNDLRAKENSPNNRASIV +SLIMGMKFFE +>CAH8639769.1||39320||1506013588 CAH8639769.1 unnamed protein product [Heterobilharzia americana] +MHPIEDFVNCFNFLQELGQYFLEVKEIEIKHVLSDLFVEILLPVAAVARQEVNIPALKTF +VENLYPTSLELASKKKHVPALFPLVTCLLCVGTKPFFLNNWTNFLSICLSHLKNRTSKVA +LVSLQSLSCILWVYIVRIKGEKHTETQNKLHTIINSLFPKNQKIILPKDAPINIFVRIIQ +FIAHEKLEFAMKEVIIDRLGVNGSQKALVMPERINVGLCAFLLIAHGLQQKEGEPPMPQQ +CIGAGGVSSGFRQAVIKRSFHGTNLNEALGSLLGIQSYLVPVRRAFELILRQLDTQVCRT +MMLPKQEVTQKEFDELMTADRKPKLDLLKTCVACIPRLLPIDMTKQEILEILAKVCLHVD +EDVRKMAQQAMANLIVELPAYRVKTIQVFIQFIQKNVADTSSHQLDSCLKTLFHLLNNWK +LALQKDGAVTSNMAEKSALYEAEGFALVMLCSCRLITRRLAVYILRKCRALLNLIHSATY +DPTLKQSNDLREMCCIDILDRSVPLVLNRLLPILPLSERSALSSVSSLDFSILTERSHPI +WFSGSVAAAYSPIALPANCLTNNQYSSLISANDVQQNDPRYGDSPNHHDLNQSNNHNNNG +SSRGDSVVINSNEKSRLDDASTQETFTKIVRPSISGLLGTSRPIDFHNPQTSKFGSSTTP +IHRRPSQYNQQVQHTQQAYIMQPTQERVVLTDVWGTCLSVVFNPSNLPANCPIAIKFAWS +ILFQRISALFPLIDPSTQMAENRASSLLRTASKKPANEREQFLPLWHNYVVVGCCIAPSS +TGLRISKPAETCYQPELHLQKNSPILRQSRMVFIVATIPDRLLNLKHSASYGKVNARELV +KLLVPLLRCEQPEIRDSAIGGLGRINPAAFRDVLEDLQPLLKESFDRKQENLRRRRRRDG +LRSALIKILSLMAQSGVFAHHESNVVTPQGSLIPQLLDYVDGMRSYLESVSDQLSSPLTH +AHTSSASLNILASIPSATSSSNSGSVVNSLPTPTSTGPSLPIHSSESYLLNEIRLHFCIF +IKELIRHLPKRNRTNLLPPTLKHQLFILLSHWSAHYDHVMGTYFESEGPTSTQFRLAGSA +VGFNDHSSSSNFAVGLSPSSEISPGFVGPHHQSSSDNFPPSTNGLGLNSVALSGSVESSA +NLTVLPSQNSHSPTFQQKVSSGEQTEGSESLEYCLWIDLIWLSNQAMAALVCCGPIYDRS +GLLPNDNSKTNQVISGAGFDSSSSSSYVERSNGTSCLSICTPSSGYVLRWISGLLVARDT +ALSSSPWLWFCPVTGRGLLHNTLNPGGASLCGNSHTRSRIIAAGGGRRLDSLLRQLGEET +LVLLLDFNPDNAGLFNWVIDQCYTASNISLSTACFECICLIMSNIPDFACDHIGLITLCL +VFVGHPSRFLSDRAFYLLRVLYQRFFIEPGETLFMRMHGDSGDSEKTCLGSEMPLSQSSA +TTCNRLDCDCSHLCTYLRANKLFYRSFHHWSPCEVVRQFAAQHPDYTLTIFSEVSRRLEN +AREGLRVPLLRLLSPWLINIELIDLIEGTTFDTTEHNLDMDDDFSDNEPSPNSYEKPDNV +SDFTEIPTCKANNAGLLLGSEQQQRNNSRKDNTEYRIRLSKKYKRSKSLLNAESVRDDID +ETDADSDNVIHHRNFKSLSLLRMRARQAQMNEHAGMYSFRSFRPSNASYVHCRNGCKSTG +SMNNTALWSTVPPTLRSRGWGSKQATEFVLTNLFYLTLRLADCPTSSEALKQLWVILVRH +RSSNLRVILRYLIIITSLAPCTLLIHVKRLLTYLASENPENVVGELMVEMQTIDSIGLVV +EPASNLPFFHVSASTQQRKSNEEMLTSEVKPYVYHHSSSKSTEDPAKKPFLNSVTSQDVI +GQPDAELIALGLDNTPRSNTTVQTRRLRSAVGGRYDCRNVIGTSNTNAIPPTIGMRPKSA +TCQPSLNSVGKSDQTTVDANKKHFTSTSSSKIIHDNHITTSLSSKTEKNHDLPSTSNCQL +VEESDGEPEIDVDSFEGLNKRGPPFDDKYATLRAKDIANLLTPPIPRDFESSDSVSDCDV +PNLSHPDAIFNSQDRSIFSTTLPNQVTEVNRAQMSNRISRPPKYLKEYRERRFKWFSNSK +SDTVTSLGIDPHQHSASLPPNLTDLPPLPLPLPTSISSILSLSSKGSTGPNTCILPLNVS +NTDKKNAISSDKTDIKPSVSVRMCTCSNVQTGHAASQSSFDFNPKPLPMPVNGGYQAPLG +LWLSEPLVISGSVVFSGSWPPAGPRSPLVLILAGGLTHSRDIRMDWTQHIPLMLLGVFLG +IDHCRALVQEECKQLLVSLLDQVCPHHDALHLLRLELEANLRRLAHPSALPGPRVQDYRL +TFDGGPYREKFSLVIDPFIPDQRDNVFSSPLISSRAVKCEDSENYRNISSTENEAAYHTT +SPDLLPLKDIDIISPLAASPSSPNSSPPIIGNFQTHSTTLIKHGRLSDSTFSLTSTATLI +PRHQEVNTSESICYNDQPVHNSNQNSDVSEKYLSLSKTPTNAFGPTSVYTNSRSLKTVNP +NRIQSTNGHSLKAEKTKTFKESDLKLLEESIQKLKSVLLECSGQALWTWEDFNLQRNPSN +STMQSTYQYEFNQFITSEKSEMRDSTIKTKSSMNSLEQLVKLVARILALSEVGSEEAEKC +LQCLHGPPHDHNRRGITNQDDYNKDTNSNYSNYCLVARLSQAALNTGLSCPSRHYASRSL +QIHRALGAHLDKKSLSRLLARLGETVSDGNEEMQGYVAELLVTQEAAIHHLKFKSDSLLE +PVSTFSVDPASPTSGSMNGNGRKSVQNIVSVSDRRHFHRHSKSSSFIGNRSCHLSSVVPV +SDPPMLKSSQATTPTLENTSPTSVASTSSHLTRKSIASMTSTPKLNSCSSNNKSSVCLLS +AGERADLVIGIFWTGVLLLESDFEHEYLVGLKLLSLVIPSVCASDLNQLSMAYAESNGPK +INLSDRAQKMLSQLHWNPHFPGILSLVIKGCSSPNLVDQACRLLIILIPFLTHPLVVPYG +RMGIPIQISFPIVLLTLMPMLITAWDEDPSTAVPINPYDANSEQISFLQGGFLGRGVCGS +LNVIPRALGSWTSCSYTPHTSFSNSTSSLVSNLKMRTDSIGLLVTVHQMAFSVTGNNNRD +ANSIPKSPVLRPKNPVCIQAAESLAQAALKIDPVLFNNLALILRLYACGNFSKDVDQWSK +CVICYLLEGCGSNTPRMLKHINMLLVSGPPCLQLPLLKVAHLLLQQADINQADLEFSLQN +FIASVVSQFLRTNLWSYVIPILQVTVSRSSVLTSVPETPAYALPGLDPSGSGRILDYAVA +AAAVAVPLPESEPPRLELAGPVLDFTFDVFQEAPLLAPEYAPNSSSQITPENAPKLDGTV +CDLPPLKGNKRDFNDVSDIGLVETFVSASGTWNKPGTVRYL +>CAH8639784.1||39320||1506013589 CAH8639784.1 unnamed protein product [Heterobilharzia americana] +MVKLSQSRLREKLYRLVGCFGLPTQQCISAPRSPSVIFSQSTETLDPQLSVHSSSETTSV +IDISNSDDIRLDDTSSGEQTAVFRDLDTYLDAQLMNINFLGVPDCRLASTSASMSEEEPR +RPWGVRGQAITCHFDFNGDGNESVMNESFPGLSRTLQAAETTNSVAHNGTESDYLISAYH +QSRTNSLPHLTSLRNNHSTDFKHTESKSLNTNRYSTITISNNYTDCNVADKQLTELNDER +QLQLECQKPSRSFMWKSLTRGDDTSIKVDHTGSDYVNLLYTTKKDNMFTGGHFKTKPSRN +LQIIKRSNVNLRNSRSEHVSFDLGDEKNQDDDDVDGEDSELEEAESGAATTCDSVRDRQY +TQFDGKRILANYSSYETYEKIRNTLQSDSVHLPSASVVRFASTQALISVNGGDINDDEDV +NDLVPKSRRNIYLPRSYLAYQNELTMQDKTLRQLTQESGQSSITHRPHCINRVEDSVMGQ +MKRSASASVLLANKYYAVSESSCDKSLVSPLLLSPVSYQNEADCRVDVNNHTQYSSLSSS +RSVSPSFSSHAEFKLPVLDNNQYDDDNPTATVKAVSSWPYSCSLVSPQSVPLSSSLSVSS +SSSAATSVHSSQASVYYISSCGKIKKANWILSGINKKHKSTSDLSVNCSHKK +>HmN_000239500.2||85433||1612003148 HmN_000239500.2 pep scaffold:HMN_v3:HMN_05_pilon:16475703:16514547:-1 gene:HmN_000239500 transcript:HmN_000239500.2 gene_biotype:protein_coding transcript_biotype:protein_coding +MICSNPGELVVHSLLKQFASMCQSTVENALDEKADNDLLKCLRMSEDTAFQQLLTALYSC +AEHALPSLLQTITKWYDTQHSSGSLYPFRRTPSKSGVRSQTLSAATGQAVSNSPPGGREL +LIRSMVVESSLNTTGATQGVAISSQEANNTLPSSANTGPDPQILAMSSLARENMAERRDL +CIDILYCQTLVSVLKQLPYHPGHSDIINKILNQAFRHFEYKENLQAKLNAVNINTVADAY +AKVVGELSQTRFNLVRQHFLTRLEHLRAKESSPHTMHSIISLLMGMKFFRVKMHPIEEFV +SYFTFLHELGQYFLEVKEKEIRHAMTCLFVEILLPVAAVVRHEVNIPALKNFVDLLYPPS +FELANKKKHVLALFPMVTCLLCVGTKTFFLNNWPPFMHLCLSQLKSRDFGQVSVESLFRL +LWVYVVRIKCEKHADTVNKLLSIVNGLFPKGSKSVLPKNVSSTVHVKIIHFIAQEKLDFA +MTEIIFDLLGVNRLQKLVFNPEAGTVMDFYRDAPAYIDKDTINVLYAQHWNKGSIKRMNI +GLRAFLLIAHGLQQKDGEPPMPQNQSLGACGVYPTVYLQRGFLPACSTGVVRRSFHGTTL +DDALCERLGIRPYLVPVRKAFEAILRLLEPQIIRSMMPPKQDAVIKDGDDSAPGDRKPKI +DLLKTCVNCIPRLLPYEMSRTELIELLARVSLNVDEEIRMMAHQTMVNLIVESPTYRQRT +IQDSVLFSLLLLLLLLLLLLAFIQFIQKYVPDTFPHQLDSCLKTLHTLLVNWKIALQRDA +AVTTSLSEKSALVETEGFALVMLCQCRPLTRRLAVHILRESRAVLRLIDQAAVQQQQGVE +NETNHGESPQNIATSVSAAASASNVAVIDVLDSLAPAILERVLPILPQNERHDLSSLQTV +DFKSLAERSNPVWLCGLRQPSTWNHKPLLGSNNFSTPVAVAAADHSSGNKVASATMKYGG +FDATSPSRTARKSMQQLSALRPSAFSQYLLTPYLLQPQLERTQLTDVWATALSVALSTSS +ALSLHPALFYAWNTVFHRMSQVFILIDPSAQAAENRASSLLRSTSKKAPPTERDVLIPLW +HNYVTLACCIAPSSTGVCIVDRKRPAYDTEGAAAAIRRRNLHHISNPSTGSADIALLQSQ +EKTATQSDPKITAQVQNPRQHQHLANSKLFISAKEKARDLVKLIIPLLRCCDHPELRDSV +ICGLSRIQPTAFRDVLEDLHPILRESIDLKQKNVQRRRRKDSLRSSLIRLLALMAQNSVF +QHDEAGITTSQGTLLPTLIDFIEGTRVYFESLNEPSAMVVTMFGSSAGVGSITTVNHSSS +NANDISGGTTINPPTVGQNMNSPTGPSQSTVGTVNSATVSGGASMAGVAGSGAVSSTGGA +QNPTANSSSGPTSATAATSTAPAHAPSGVDPVLLTEMRLYFCVFVRDLIRHLPKERRQKL +LPPTVRRNLVLLISRWSGFYEHIFNARDVKWAQPQWTDNASSSLAQNGRQSSSIRGASVG +AAVGNLPSPPGMDQVVSGDLVLSISGVHLESTPQAPFISASFLDTSIWLELLWYANQSIA +ALICCGSIYEHVAIFSNPQIVETTELASNTAASSSHQLAGSNVNLGGNPVPSNLSSNLPN +PATQPNTVPATLPSNDDDVTAPNNQGVNTVPIVTTNPSTVKASRGHPIPGYIFHWLKGLL +LCRDAKLSISPWLWGLPVVNFGVCSTALKGPHGKTGVRSIRLEDSLRSIVIAAAAGRRLD +SLFRQLGEETFYILLDMNPDLPGLLELLIDQCYSGSLNVIQAFFVIIAQRFIKNPKMHCD +RFTMLTLAMVFCEHSVPVIKEHATFLLQTLYYRFFLLPSRELHQRMHSNCQKTVESINEE +PRDSGDQTEFCLSALADELLWREIGHWKPCDVLRQFCAQHPTCTLPMISEICSRLATASK +EVRCRLISLLHYWAINIELVDLFSRNYKDEGFMDYFHQHHTNLQLVNASLLEKVELSNPV +STINAESESEFEIDDRMEKTQNGVGGDEEEIIKCPSGSSGSGSASSPTSLSSSSESPHNS +SISSDIDSDIDRDGPVIITNMANSASASRKPRVGRHLHQDNRRRRLQQRHTSDQFLKPDQ +DGEKQKEAANELWASVPLTLRRSGWGCLEATEMVLNNFFYLTIRFANEPNESVVLGDLWV +LMIRYRPSNLRYILRYLIVAASLAPATLCSHVNRVVSFLSNEDPSEVVEILMTELQTIDG +AGLVIEPSPLPPFFHISLFSQTNLTEPSSDQIGDTNENTSRRILEKESYLGQQTGSSTRL +LSTISVTGDDHLPATPIKPGVDISTAQPTTTSANTSAISRLTNRFASSASAMGRRALRTA +LHPGSGGGRQSKNSVNLDQEQPPELDIDSLDGEGEEESANPRQAADTMPTPQNYTTPSTD +ALIAARSQTLPYTAASRERYSKQTVVGLDNQVALSNRPSRFRKILSRRRTGGGGRRNPFR +SIFGGGGGGGGSGSIVGGALNESIKASSELAQNISEATKLYTTSLPPNLASLQPLPLPLH +SHSISHLNDLRKKRGGRRRRSSSASGRNHRHGSRKHHRRRRRRNNTNGSGVMEDIPGQHV +KVAFYDDSLMLQQPLPMPKDGSVYYAPMRNWLTEPFVSNGSLVYSGMWSPTGARSPLVLL +LVGEILKSHNKNRVTWRKHMPLLLLCLFLGLDHCRPLVQEESKQLLVNMLRVICPRIDLL +QILSLQLEIDSQWIARAVAGFSPHVPQQFLILDDGPSVPKFYLPAPTDTALSEVSSCPTW +AAASYSASSNNAQSLFSSTGLLNIAPTSTGGGTGNGFSPMTDGPSSGGPHPLRSSLQQPI +PARPNYIFGSTYSLMSTATLIPSAAISSVEAEPTNRRSLILSNPISPSQLPPPPPSTPLH +SVPQVQASRIPKDDCESRHLHAAAELINQMLVEVNDRSIWTWEDITSSRFQQATLLTLRS +RKSSILLLRSARFLDKFVKCVAGAFAEAEVGGKCAEATECLQCHHGPPHRHWELESEEEN +CLQQPSCTVTRFAQAAFNMGITCPNRHYASRSLQIYRALGAQVDEKALSEVLVRLVESVS +DTNDEVQGYTIELLLTLEAAVEHIQLRGTILSGAGHRNGTLIRPSAPESPMAISKHPQTP +PVHERRHSRRGSSSSLSSLLIIPNSGGNSSGGAAVLGNVGVGGVCPQASLPPIKRPLMPP +HAEAIGEENETSASNIVVNTAIGPIKTTLSKDTMPPLPPNATSKPSKLTSLQSLPVVTPP +ETKPVSISGWEINSRTDSNNAVEDTSMIGTCRLTAEEQADLLSGIFWIGVCLLDSDYEYE +YLAGIRILSRLLPSVCSLPQSSILKSLPDFHEHILKLHHRLKWSTGSSEEADFPGLLGLL +LKGCFSSNLVDASCHLLVSLIPLVKSPVVDPFAEARQNRRSSGISGTAARSLGISTSSSN +SGSNYPGPLPTLIIALLPLLLTAWDEDPESTPSSITPEITDGCEMCAEGTPGNPLMIPSL +SLNSPPPSLGSWVHRSVVQSENSVPSPSSGHLSLVTLTIISGAAAKKAPQVKPRNPLCIQ +AAENLAELAVQCDAYRFNNLAVVLRLYACGGFSKDVNQWAKCVTRYLIDGCSNLGSKILP +YLTAFLTLGPPCAQLPLLHFAYWFFQAVELNQSDISTRIRSFITSTSERFVNTPLWPEVT +LLYQVIVARSATLTTAPSNPAGALSLPGLDPSGSGRVLDSLAAAAAAAVPLPEAEPQRIA +LAGRVLDFDTSILASAPLIAGGKGQASPRLSCTNSAAANVGGIQYRSSTRMSHLAEGCNV +PGGAFAYTPFFFRLGAWKTPWNRQLRLRSLFTNLLNCYGSHLEHITAPRSPSVIFSQSTE +TLDRQLSMHSSSETTSINDASNPDDGNFDDTSSDERAAVFHDLDTYLDAQLMNINFLDLP +DQNWEDQTRCPRWGVRGHSITSHTEFHEDGDDTQQQQPQEELAKHFQNPVGDIAISTAGD +NFVNIPFQRGEDDNLMVDEDLEVNHFEDVLDANTTTSGADFFSYSESRRIPLRHQSQLSI +QLSTASSVNSASFTAPQPPQSRSWQSKDDSDLKWLPDSTATIPPVASPPHSRKRTLSMQP +PIVCSRRRRQNLASLGGSLDFLPSQLHPLSSPRRKVSGPQQQHSVVSGSTRKINTQHSSS +TVSSVSSASFKVKEAPQLQPPAKPARRKQRPSRQSLELITLDEPPVVPLRLSASSNAILK +TPISSKTSFNEPDLTPTPAETAWIACHRQLKTLQQASSSSAIASRGEMTSGQRDRLRLLL +TGLRCVFAGMLRRTTKGAVEMANLAFSRCPPSSDLDPEIRISYLVKIAEIVHDFLPSLFF +TLHPDSVSMLNHQVMQHCLQLGSMYETWDESINRLSMTCAPMDSPDWNQHERICRDLQTV +LQRLVNFFSCSADVLFKVANDLLLSGITDFSHNLIEPLFNHLRQYLLSVPPKSHLEILGG +IDSWKVHCGRRLIFKTPDDSDVTTALSIYSEAMKSNDPISMDDIMIILEACCDQLWLERK +SESGFALLFAHGKIPEVRVFELLTSNQSMPSLVLTTDNPLESNGHSVLSPSELTAKLQKL +LEC +>VDD81314.1||53468||1613007294 VDD81314.1 unnamed protein product [Mesocestoides corti] +MVGTSPGELVLHSLLKQFSSICQTKVEGALDDKAENDLVKCMRLQEDVAFQQLLTALSTC +AEHALPSLLQTITKWYDTQHSSGALYPFRRPAAQTLTSIRDAPISPKPSTSAAGRSQTLS +AATGAASAGQLPSGRDQLVRSMHVDSFSAGFASAEAAPSNTTSALSQPIETQESSAPWVS +LSNSARENMAERRDLSIDILYCQVLTSVLKQLPYHPGHDEIINKILDQAFKRFEYKENLQ +SRRNAENINLVADMYAKVVGELSQTRFGLVRQHFTSRLAQLRAKESSSYTTHSIISLLMG +MKFFRVKMHPIEEFVGYFTFLHELGQYFLEVKEKEIRHAMTCLFVEILLPVAAVVRHEVN +IPALKNFVDLLYNPAFELASKKKHALALFPMVTCLLCVGTKSFFLTNWPPFMSLCLSQLK +NRDLGQVSIESLFRLLWVYVVRIKCEKHTDTQNKLQSIVSGLFPKGSKSVLPKNVSSTVF +VKIIHFIAQEKLDFAMTEIIFDLLGVNRLQKLVLTPERMNIGLRAFLLIAHGLQQKDGEP +PMPQNQPLGVGACSVPGAPHARAGGVRRSFHGTTLDDTLCERLGIRPYLVHVRKAFEAIL +RLLDAQVCRNMMPPKQEVTIKDGDEAAPVDRKPKIDLLKTCVNCIPRLLPYEMSRTELIE +FLARVSLNVDEEIRMMAQQTMVNLIIESPAYRQKTIQAFIQFIQKYVPDTSPHQLDSCLK +TLHTLLVNWKIALQRDAAVTTSLSEKVALVEAEGFALVMLCQCRPLARRLAVHILRESRA +VLHLINQAAANSRHEDQQLTLLAAANAGGDVCCIDILDSMAPIILERVLPLLPPHERHDL +TNLSTVDFKSLAERSSPAWFCGLRQPSTWNHKPVAVPAPLLIPPSPPPHAASNPTSTAMS +CPLRFDKSPKTPSGSNLLQNGTADLLCGRNVNEHSPMSKTTASKKHHLEVSPEGRRIPPH +SPPMQPPIRPSAFCQSLLSPYILQPRLERTQLTDVWATALSVALSTRSPLSCHPAMLYTW +NIVFHRLAQVFALIDPNAQAAENRTSSLLRSTSKKAPLTERDQLLPLWHNYVTLACCIAP +SSTGVSVVDRKKPGYDVESADLLSGAVVGRRTAPKHASNSSTGSAEIAPSPNHETNATST +AEGTPQHPVCEPSAMLSPRQQQHLANSKLFIFAKEKARDLVKLIITLLRCDNSDLRESVI +CGLSRIQPGAFRDVLEDLHPILRESIDVKQKNVQRRRRKDTLRSSLIRLLALMAQNAVFQ +HDEAGITSSQGGLLPALVDFIEGTRVYLESLTEPSAIVVTMFGSSSSGTAGSSNAPAAVS +MANQVNSAGIGVGAGSVGSTSISGSVTSGVVGGAVGGSTATGAGVGANPPNASGAPTTPS +ASAATSTAPSHAPTGVDPALLTEMRLYFCVFIRDLIRHLPRERRQRLLTPSMRRNLVLLI +SRWSGFYEHIHNSRDIKWSQPLWIDHPETPSEAKYPTTAIGTDQVISGDLVLSISGVRLD +SGCQDPLISASFLDTSIWLELIWYANQSMAALVCCGSIYDTCALFNAPACQFPEVSTELA +GQQSQQPQQFSSSVVEEDANMGTVSSATAALLSTPVLGSQSLSAVPMATQTNPAGSTFLG +CLKHGGKPAPGYIFHWLIGLLLCRDAKLIISPWLWGLPVVNFGVPSTVLKGPHAKTGVRN +IRLEDNLRSIVIAAVGGRRLDSLFRQLGEETLFLLLDMNADLPGFLDHLIDRCYTGTLNV +TQALFVVITQRFIKNPRMACDQFTMLTLAMVFSEHSVPVIREHAIFLLQTLYYRFFLSPS +RELHERIHPGCKRTEGVNLSSLNESGTTAPTSTPASTVVGGASPGANNHAEFCLSALADE +LLWREIGHWMPSDVIRQFCAQHPTCTLPMISEICRRLETASTDVRCRLLKLLHYWTINIE +LVDLFARPSGFHAFGETLIESSSGDGGVVAAGVEDASTAATLEPVSSSGKTEIGDNLATT +TGPSSGSEEDAGQTEKVVVNKGNESGEKGVLCPSPAASESSSSSSTFNSSSNIDSSDCDS +DLGDTGAPDGTGVVKKRGHQQRRPSDQSYKPEINEDKQKVNELWASVPLTLRRSGWGSLK +ATEMVLNNFLYLTIRFGNKTNEALALSDLWVLLIRYRPSNLRYIMRYLIVAASLAPATLS +SHSNPNLALSSLTFVSQVNRVVNYLANEDPGAVVDVLMTELQSIDGAGLMIEPDPAPPYF +RISVASDPSGQSCGGASGTSGSVAVAENATPSDRRQRRVDGGHLPDLITFSQRDDEVYGL +FRDRELCGNQRKSMFVESNLSQGIAVASKGRPRSACGGPEQNGADGDAASPKIGEPSSTA +IVPASSSSAISRLTNRFASSATAMGRRALRTALHGGGRQTKVSTAHDQEQLQEIDVDSLD +ETTVFNEDESSVLRTAADTMPQSQNYSTEAVITARSQTLPYTAASRERYNKQAVVGMDGT +PLNRPSRLRKILSRRRTITGEKEARSSGGRRNPFRSIFRSAGIGGGSKASQALPASSSST +RLYTTSLPPNLATLQPLPLPLHSHSISNINDYRKKRASRRKRTPSRGRHLRVEAPLLATS +LLLQQPLPMPNKASVYHAPMRDWFTEPFVTNASLVFSGMWSPTGARSPLVLVLVGEILKS +HNKHRVDWRRHMPLLLLCLFLGLDHCRPLVQEESKKLLVNMLRVTCPRIDLLRLLSLKLE +IDLHSVSRAMAGFTSRSPQFCLTIDDGPCVPKFSLATPTDDPLCEVSTCPTWASCCSTES +YFPPNAAAGGGQPTVVPSAGLAHFSHSSSGTPPGGISPMTEAPAGSAVAMTRSLIVAPQS +HNRPSYAFGSTYSLMSTATLIPAGLTDQEQKRRSIIDPSTTAQMIHENTEAQFLRNTSSD +HLPLQSNATTAAAMGTAVEKSPVMTAFDQMVVATAPHDCEARHLHQVSERLKQLLIEVQD +RSVWYWEDISTARFQQATLASLRSSSFISTSANGSSSSSPPPPHRESLILLRSARFLDRF +VRCVVEAFAEAECDGRCAEAVVCLQCHHGPPHQHSSVAFFDCTVTRFAQAAFNMGITCPN +RHYASRSLQIYRALEAQLDEKSLSEVLVRLVESISDSNEEVQGYTVELFLTLEAAVEHIQ +FRGTVLPISVHPNATASQSGRVGFTRPSTVVPPSDSPRLVSSLNRRNSIEEVPVAGKPPT +PPTAMTSSNYQPTPPMVHERRHSRRSSASSLSNLIGVSVGAPSLRPSSQYGGSTASLGSP +HPGAVPPIKRPLMPPHVEAASEDADTLNAVTAPIIPHSSPLKPTLAAIGVKTLSPSKPTA +SSDARSSSVEASAPSSATGTCRLAPEEQADLLAGIFWIGVCLLDSDFEYEYLAGVRLLSR +LLPSVCMSPTPASGNRKWTPQQQQQLHPDLHEHVTKILNRLKWTTLSGTDDNEYPGLLSL +LGCSSTNLIDPACRLLVQITPIVKSPIIDPNGATGELRLYRRATNTRGGSVGGGNCNTYP +GSLPTLIITLLPMLLMSWDEDPESSALTHETITTTEAADWPVASGCSEVASSPAVVSPSP +VGSWTHRGGGEGSVPPPPPSSGLLSLVTATIVSGAVAKKAPQLRPRNPICILAAENLAEL +ALQTDGYRFTNLAVVLRLYATGSFSKDVNQWAKCVTRYLMDGCGGLGARILSFLTAFLTL +GPPCVHLPLLHFAYWFLQTVELNQADMCSRIRVFIKATSERFVNTPLWPEVTLLYQVVVS +RSATLTAAPANQNVYTLPGLDPSGSGRVLDSLAAAAAAAVPLPEAEPQRIALAGRVLEFD +TSVLQSAPLIGGGRGQASPCLTTQHRSPARTNHLVETILLDSGKSFFRVGAWKTPWNRQA +RMRGLFTNLLNCYGSQLDHLAAPRSPSVIFSQSTETLDRQLSLHSSSETTSINDASNPDD +GHFDDTSSAERAAVFHDLDTYLDAQLMNINFLDLPDGTWEDQTRCPWGVRGHSITSHAEF +HEEGGDQQQQQPEGSAGESMGGGGCGVDGGALPLTSELTVVTEPINAASVNLGPLSYSPK +PTGDVLTTSNAAVAGRQSQLSMQLSSASSCASGTGNVTLLAPMTSNRPFSSQLLVAWRSR +GTSPTRWLPDGPSSTISSSNVSPPLLSRQDGVPISRKRTLSVQPPPVSPRKRLQDRAMLG +GSLDSLPNQSDIQFTPVFKSKPILTPTKAPVPLSSAGSSISCSSLNEQEVVLMPVPPMPS +PPPAKPPRKRRHSRGSFELSVRDPPPVPLRLSRSSHMIPQALGSGGGGGGGGGFTSSSSV +EVAVDSKQLLTPSPSETGWVACMRHLKSTQWKAAEGARLTGARCDRLRLVLTGVRATFAG +MLRRSAVQAVQFANTAFCKCPPSSDLDSEIRISNLATIAEFMQDVLPVSFFAIHPDTVYL +LDDKLVRLCLRLGDVYQQWEDSLHRLSSTCTSSEAAGGRQHEIICRDLQSVLLLVITFFR +LGVEVITGVRTHLADLHILEISDCSETLETLFECLKPHIGTVIQPTEAARSRASNDLDAW +KPQCFEILHVGPDKVKLLSVEETRRALAVYTAAMKSSSPITMEDVMAILEAYCDQLWTNL +DSGVAVVFARSPNSGVKRFEAFTSFTTPTTSASSSFFTTDDFLHPDSREPLPPLQLAAQL +QRLLQS +>KAA0185233.1||27845||1630001259 KAA0185233.1 Protein furry [Fasciolopsis buski] +MGTARNLFNVPLLRLLLPWILNIELVDLIGDPDDITDENHKTARYPGSFGFGVKEDSNEN +RNTFYLKKSIPQRSTVYRPGRFVRHVCGQCHPDQSETVLADRDEDDEGDEGCLMSSGIRT +TSDLDHSDEEEDDEIEDEDNAFLPNHFIPRRDYRPEEKLSPKGVVHHWCACSRLGNVALD +SDAVNSDADNELDFSSDALGSRYRCPKAGTVPLVGPRGEADLRHGVRPRWMTDSVRKPHE +NSLRGTCDADFWFTVPPTLRTNGWGSKKATELVLNNLFYLTLHLAECPSATDYLKQLWIT +LVRYRPSNLRVILRYLIVITSVAPATLLSHAKRLVTYVAAEHPESVLEELVVELQTIDGI +GLTVERTTKLPFFRVTTSASRARDATETSNEKTATLVKSTLLPDDSHSSVTCQLVETDPS +SNSIQAVLKAVQQESAQSIDPELATLDLVASENARTLKPERKFGRNRASALPFFHSKRHV +IDEVGDGLNRLTKRRPKSATSHPSSRLEAKTCQPGKFADIAVTPTSCATSTAVDEPLNID +QSGDRTRGGSVPNEVPSIQQMVPDTEGDVELDVDSYYTNVDDTRRNLRHLDEFSPEDKYA +TLRAKDFARLLSSSIENKNPVSCAGGASEPEILAEERDPRLSIRRGTLPSTEDSRERYYQ +GEAVRCTVCHVPGKSDTCISDPLGREPIRSHLPRRFKVKARSQLRGHTRTPLHAVSLPPN +IASLAPIPLPLPSGVPSLPSLWLISMCGRTSASSGSSQRSTAKQVAGKTLFNSRVTPTMD +PIPIGKAYSQPLSMPTDGGYQAPLGTWLSEPLVIGGSVVFTGSWPVAGARSPLVLLLAGG +LTQCTSVHLDWAAHLPFLLLCVLLGMDHCRALVQEECKQLLVSLLNLACPEQDALRLLRL +ELEATFLATAYPSSSSTSARRPNYRLTLDGGPYRERFNVFAKLLPTVPMCGIPSPPPPLL +KPAPVIASNKLSLDNYSSQIVPPKKPERRFVSPTRVNREPSRTLLPATDTSSPPQVAMSS +SQTGAVQSSLYGAQLSGSTFSLISTATLIPGREDYTQVETRHPLDDLNERSDDFPQNIPP +KNLSGPHHSPDITAGNFTRSYALRTKSGSAPKSNRIPHKVMTDPGAKPAMERKALVDSKP +LLDAIDELRELLLLSSGQALWTWEDFNLQRFQYLTVQNLMRSFYGTAWHNKSVNDNLIPR +NDHCLPELTDQSTSSTPRVLVEDLVRIVARVLALSYSDCSEDAENCLLCHHGPPHSHSTR +HHRSGTDHRSLSASTNHCMVARLSQCALNMGLSCPNRHYASRSLQIFRVLGAHLDSKTLS +KLVARLGETIADSNEELQGYVAELFLTAEGAIQHLRTKSDCLTEVAVIPPQTSYPRTAPT +SPNCIGRGINADSNHAALVIVSDRRRQQQRHSTSAGLTTNSWARPIGDLSPRLPSRLLDD +VGSAKTSGDTIHPSNSTPEEERPLKRNSVCRLSVDEQMDLVCGIFWTAVFLLESNFEHEY +LAGVRLLSHLVPSVIGATGPGQTRAAQNNCPSLGVRAEKMLNQMHFNPPFPGLLSLAIKG +CSSAQLVDPACRLLVSLIPFLSSPLVVPPGRHYQQFAQLSYPITLLTLMPLLLTAWDEDP +ATLSTTFPEPTLSDLQQQPSVLQGGFLGRDVCCLTNAIVTPRALGSWTMVGATPRSASAN +ASTAVSSSQTPALRLARADSIGTGMSNAFGAVGSGWNLSNCGSSNFSASILPTALRPRNP +VCIQAADALAQAALQLDATHLNNLALILRLYANGAFSKDVDQWAKCVICYLLEGYAQYTS +HLLRHLTTLITTGPTCFQMPLLKLACLLLEQVDLTNADLVCTLQHFIITVTGQFLGTVCW +PEITRILQVVVSRSAVLTAVSPPSAYTLAGLDPSGSGRVLDLTAAAAAVAVPLPESEPPR +LELAGPVVDCQFNMLAEAPLLAPRFAFANTSGVNGGRATRGGLSDQEQPETPVDSLTTGP +FIRRGEPSKIMDSDLGELFASAASSWNKPGACQARVRERLYRLIGCYGIPEEHRISVPRS +PSVIFSQSTETLDPQLSVHSSSETTSMIDVSNSALLPWIDLSCEDEPRRPWGVRGQSITC +HTDFTEDNIIEQVTDLLPSMRIPLEQSTIRATVSPPKTARKRKTHTHPRAVAVGVPTDAP +NIVSESTLEWKQSEFGCSEPGKRLADSVQSRSLADQTMLQFTPIHHRHRDPEEPDTEEDE +DDDDDEEEENSKTNLVRYPIGFSRNAPRETADGFGETTTNAPLSSMSVSLYDPISPMHAG +RRGRTSSGTNLLIDQMCLSPQNPPVVDATSELCTKTPVKKLMSDALACSESSVVNDPTEL +ASRDNTSVESPTAFMGGRFARPDASHMPSDQFRSSSQDTDPNRSLSPSCSSPVFKYSRIV +KRPDTLIPTQANLNTSSISLQQRTPFMRAKVQTFAKYGKLPARFASPSPAVSCIEINYQL +SVHRSLSTSNLFSVQNRRKFEVTGSPESVKATVISIPPVSSSSGRLQSPVLKFHKNFTNK +RQSLSAEDLPWLIKQPKVSFLIPTSRMEVTDPDVFVIQKEPICVRKSLALSSSQSFTGKD +SGAFLHSSQTDSLSESKPPRSPLSLTRLELTRSSNSLPEPIGSKSQLAATTQRSPASITP +VVYHKLMTRTSLFSSVTYPWTERVAIERTWMTKLKKIVSDELVDQVTPLLSDLSTVFTAL +AVRSQNGVRASLNVSGFSWDEASETSRLISVSELLTTTVRPPYLFYPVKSNELTEREKST +IQLGMDMLSESYEETDNAINQLAKLAFERDGIAVQRGCINLLFKLVSFLQTTHVVAEQLY +RACPPERVARRLSERLQNALDQLASSMSQYNSGRTSSQNDSADTSQCSPYPARQTEFTSH +LIDRMNTLLRNLSELTETGDPEPPPKQLAAQFFELIDLYHSQFAPDSGRLLTVDCVLGVF +SLYVSEYFAHVAHRDPDPAVVYLVLPSVGLGRTGSPNPLDQCHYLKHQLGTLG +>KAA0194641.1||27845||1630007430 KAA0194641.1 Protein furry [Fasciolopsis buski] +MTSSCIDVIGGRDNQRPSNNVPVPTNALNKSDKASQSVTESTYTSKSVYSVSSEMRVLAE +RRDLAIDIIFCQVLLSLLRQLSYHPGHNHLIEMILDQSFRRFKYREDLQPQNSENVNLVA +DLYAEIVGVLSQSRFALVRSRFTRHLNELRAKESSPNVKPSIVSLIMGMKFFRVKMHPIE +DFVNCFTFLQELGQYFLEVKEPEIKHVLSDLFVEILLPVAAVARQEVNIPALKNFVENLY +PTSLELANKKKHIPVRLSFIMPPS +>KAA0194642.1||27845||1630007431 KAA0194642.1 Protein furry [Fasciolopsis buski] +MTSSCIDVIGGRDNQRPSNNVPVPTNALNKSDKASQSVTESTYTSKSVYSVSSEMRVLAE +RRDLAIDIIFCQVLLSLLRQLSYHPGHNHLIEMILDQSFRRFKYREDLQPQNSENVNLVA +DLYAEIVGVLSQSRFALVRSRFTRHLNELRAKESSPNVKPSIVSLIMGMKFFRVKMHPIE +DFVNCFTFLQELGQYFLEVKEPEIKHVLSDLFVEILLPVAAVARQEVNIPALKNFVENLY +PTSLELANKKKHIPVRLSFIMPPS +>KAA0198307.1||27845||1630009939 KAA0198307.1 Protein furry [Fasciolopsis buski] +MNIGLCAFLLIAYGLQQKAGAPPMPQQCIDGATSGVTSAGCGGAIVSAVTGAGAGAGGVT +RQTVIKRSFVGTNLNDALGHNLGIQNYLAPIRRAFESILRQLDTQVCRVMILPKQEVSQK +EVEELMTAERKPKLDLLKTCVACIPRLLPNDMSQPELLELLAKVCLHVDEDLRKMAQQAM +ANLIVELPAFRVKTIQVFIQFIQKNVPDTAPQKLDGCLKTLFHLLNNWKLALQKDGAVSP +NVSDKSVLYKAEGFALVMLCHCRSITRRLSVHILRKCRALLRLINAATSVGDASACGRAN +DGGDGGGKELCCIDVLDRLVPQLLERVLPLIPVHERSAFSNNSAHLDFSMLTERFSPVWL +NGSAPIPSYPAVVMPANFVSNQYSSLMPMNDLDRGQVKDMSEPLTRRTQFSVYPMDSGAG +VGKVNSNSNTNNNENNNNNSNNNNNKEGTDAVLKAMASSLYDHTTSPAHPPQRDSRRVDQ +QNAGHAATFSHSGNSGLFRRASQNQPHHPHHPVQHAQQAYIVQPVPERVILNDVWATCLS +IAFSPDNLVTACPMPIKYAWSVLYQRLSQLFPIIDPSAQIVENRASSILRSSSKKPTMER +EQFFPLWHNYTVLGCCIAPNSAGSCLSTSRATRYQTASPVEVGASGPSTGARVGGRYPSP +SSVPNADPSESNAVPRSSLVSPNSSSALSANIASETKSTPSSVPANAPTPTPTSPSSPTG +TRETARDLVRLLVPLLRCEQAEIRDSAIGGLGRINPLAFRESNCLTGQGCLISQLSDYLD +GMRLYFESVADQATNPTCPVYFGSGMGTSISGPSNMTVVSPQGGSTTQVANSVLGPSNVS +VGGVVVGSNVGTAGSTGATAPSGVGMSSSVSAVSASTATAVTTAAANPVLTEAHLLVEMR +LHLCIFLFEMIRQLPKDKRTQLLPTAMRHQLFLLISRWSGYHDHVMGLCPDSDTFSSSVG +NLRMNIGQTSVGVNSCHLNETYGPSGYSLDPLVDSAFQSGSSPTSSSAVSSSGNVMTEST +SSAVLSNTGSASGHGVESVSDQATASMHSVPIQDSWELRLWADLMWLANQAMAALACCGP +IYDPHALFTDHVIGQSNSSLNGEFSSSGSNPNPGYILRWVSGLLISRDVVLSSSPWLWHC +PVVDGGLCHVNLSPIGTSGCGVFNGASILTGCTGRERRLDSVRCYSILKLR +>KAG5444725.1||79923||1648003785 KAG5444725.1 Protein furry, partial [Clonorchis sinensis] +FALVRTRFMWHLNELRSKENSPNVRPSIVSLIMGMKFFRVKMHPIEDFVNFFTFLQELGQ +YFLEVKEPEIKHVLSDLFVEILLPVAAVARQEVNIPALKQFVELLYPTSLELAGKKKHVP +ALFPLVTCLLCVGTKSFFLTHWTNFLTICLSQLKSRTPKVALVSLQSLSCLVWVYIVRIG +GEKHLETQTKLHHIVNSLFPKNQKFVVPKDAPVNIFVRIIQFIAHEKLEFAIREIIIDRL +GVNGLQKNLYMPERMNIGLCAFLLIAHGLQQKEGAPPMPQPCLGAGGFRQAVIKRSFHGT +NLNETLGHRLGIQNYLLPIRRAFELILRQLDTQVCRTMMLSKLDVSQKEFEELMSAERKP +KMDLLKTCVACIPRLLPNDMTKPELLELLAKVCLHLDEDVRNMAQQAMANLIVELPAFRV +KTIQVFIQFIQKNVPDTSPHQLDSSLKTLFHLLNNWNLALQKDGAVSPNITEKTALHETE +GFALVMLCHCRTITRRLALHILRKCRSLLSLINTATCDPNARQPREELCCIDVLDQSVPA +ILERVLPLIPLHERHLFSNTNGLDFSVFAERSSPVWLSGPSPVPPCPPVVIPTSFVSPQH +SGLMPISNLSSTTTRDSSTTEHDDQKHTSEALTRNSVCGQCSMFTDGSHPGALGVTGRQQ +DEQLQSQQCTYKASGTPEQSCTLPGDQTTTAAHVARRPSQRQPQQYSSAQYTQAYIIQPF +QERILLGDVWGTCLSCACSPERLLTCSPQAIRYAWNVLFQRLSTLFPLIDPSAQIAENRA +SSLLRSSSKKPTNEREQLFPLWHNYAVLGCCIAPSSTGSRVSKPRENRYDSSVDFTPPRA +TPQPTASASQPSNSICESPEPRVSSAQSNSGSPATQVGTPLRPTSTNSMDTKFTSPIVTR +ETARDLVRLLVPLLRCEQPEIRDSTVGALGRISPVAFKDALEELNPLLKESVDRKQENVR +RRKRRDGLRASLIRVLALMSQNGVFAYPESNALTPQGCLVSQLTDYLDGMRIYLDSVADQ +AINPTAGACVVSGSGSMVIPAATTGSASTGSLLVPAVVGGSASTVCTPYSAPAAPLAAVT +SSTSLASLASTESHVLVEIRLHFSIFILEVIRQLPKDKRAQLLPNVMRHQLFLLISHWSG +YHDHVLGVHSELGSSDVSVFGTAGTALSKQGPSTTSNLGMSPVSSGPFTGPDSSVPVCFS +VDPSQSNLDTAMHTALTTQVSSANLLPTGESAAGVASGCRAPSGSQHNITSQGYSSSNTL +TATDQMIRRSAVATQDIWEQRLWIDLLWFANQAMAALICSGPIYDHQALFGELTDLYGEH +SATGEKTTAPHPGYILRWISRLLLARDPILDSSPWLWHCPVVDTSFGYSALMSTRSSSCS +ASAIQTRTTLVGGELRLDALLRQLGEETLVLLLDRNQNLPFLLSWVVDQCYTNNNISSST +ACFLCLCRALSNIPDFNCDFVSLLVLAFVFLDHPQKTLSDRAFHLLRVLYHRFILQPGSV +IHERYHSPCDQDLDLSASSRSTCSSSVCTQLKINVHFWHSIRHWAPSEIVRQFAAQHPDL +TLTIFSEITRRLETCRECLRSGLLRLLVPWIINIELVDLIGDTSVTKLDKAGEELCWETL +ELDPVNLRKPADSVFQQIAPHADMIDAVEDDSASSDADIVSDEELLPESTRKKSHLIGRR +STEGRPGTYGRGRQHVDFFGSEEDSDNVSGDLLKSRHTIPSSALRHRVRKADTVPLPKSN +RARRTPDTGQKSTTGLSGACEEFWYSVPPTLRSTGWGSKQTTELVLNNLFYLTLRFAECP +TTTESLKHLWIVLIRHRPSNLRALLRYVIVLTSVAPATLLAHAKRLITYLATEQAESVVE +ELMVEIQTIDGIGLVVERTTVFPFFRISVAASSLGRPNETTDVRRPADSAVTVMANQTLV +KTRSNTSPGDGRQHPTDQTSTFPPTVLQSMSREFVDQMDAELVAINLESTGGAKPTGTIS +DERILDPSAKPCTEINYRMRSQASGQRSELTAGRSTHETNVDLPDPSLAFPTQIAGNRPL +SPSLLYEPDGIASVRPEALEKDVNQDSPKIRFSPEDIYATLRAKDVARVLSGSNAGESDG +GSEPDRTPFRTEAQLRSGTLPRTEDSRERYFNQAGQSRQKTTLGQADSATRTFVRHSKRV +KSKSKSHMRPADLVGGTPKVPHQTVSLPPSLAGLAPLPIPLPSAIPSLPSLKLFSSIGAE +KQNANDSGLSSSAATGKLSRTESQKVEFTLDATAIEKQAQHQQHVQPLAMPLSGGYQAPL +GCWLCEPLVVGGSVVFTGSWPVAGARAPLVLLLAGGLTHCQMVQVDWSPHIPLMILCALL +GMDHCRALVQEECKLLLVSLLNLVCPDQDTLRLLRLELEGGMLARAYPSALPGPRQPRFR +LALDGGPYAETFGCLSSPVAMTEAIPQTGSQPRYSPTEGARATPPRKPNRRSFSERLSLS +PPNPIIQVSVGEPDGLPSHSPPIITSVPPSSTLLVQKNGWSGSAYSLLSTDTLIPGPEEG +GVRTRPDSFVISKDAPQLCPSASAAPVSSVKSRLTTTRTTPPRTSRHSIGSSQGIMSTVV +EEADSPVVKTDAKLLQTAVHDLREVLLSTLGQAVWTWDEYRFQRAHTSPVVYLTQHHHSR +TTECDTDITSECSDKRSLLARLVQLVARILALANTEPSDAEGCLLCHHGPPHEHAFAHRL +KHSSDCRTATSPVTNYCVVARLSQISLNMGLSCPNRHYASRCLQIFRILGAHLDRKTLSH +LLSRLGETVSDANEELQDYVAELFLTLEAAIAHVKTKSNSLLVGRTSKQGDSVSGPWLDK +QTRTAPTSPHSVGGHVPNVDLTISDRRRSQRHSTSTNYCILDGPSSWFKPQYTDEPKQES +EPLLVTRESSRLNHILGSEESASRPRTPRLMVCRLPKDEQVDLVCGIFWTGVFLLESDFE +HEYIAGLRILTTLVPSVCADAVAEDNERFSSRALSAVPICLPVRAEQIRTQLHLSSPVAG +VLNLAIKGCSSASLVDPACQLLVCLIPFLNSPLVVSPSKSHLTSQLSFPIVLITLLPMLL +TAWDEDPATAGTSGLLETTVEQTNVLQGGFLGRGVCTNTNATPRALGSWVSSATTRSATV +ATHISSHSSRLGRPDSLSHFPATGGFGTISAVTPGPYCPSTSSTCIHKSSQPVPLRPKNP +ICIQAADALAQKALQLDPSHLANLALILRLYASGTFSKDVDQWAKCVVCYLLEGYPKYAA +YLLQHISTLLTAGPLCLKTPLLKIAYLFLQQVELTSPELSNTVQHFISSITSHFLGTEFW +SEVTRVIQIVVSRSAVLSAAPPPCVYTLSGLDPSGSGRVLDFAAAAAAVAVPLPESEPLR +MELAGPVLDFTFNTLYEAPLVAAHFAPATFSATTEEICKSSGVHGVDIGSSKKTDFFRLS +DSGLTDLFSVAASSWNKAGSRQLRLRDRLFRLVACYGLPSNRRFAAPRSPSVIFSQSTET +LDPQLSVHSSSETASMVDMSNSDDIRLDETSSMEQAAVFRDLDTFLDAQLMNINFLGVPD +GRLASASASASEDEPRRPWGVRGQSITCHQEFSDDGVEQVADMFPTVRAPLSKSFTQHSS +DEPLPWTNKRRSFPASYSTEVHVAPESDEPDWVPHAPCCTSSGEPWQHALASSTVGAEGI +PTQHPPLRNAQVDAYATVRCVTENNNLILPSFPNNGDQPGVEVVTNTHLATVKRLSFDRI +VEPSTPSLIRTPSRDDDAFILDRPASPVDKIDLTRTRQVRFERTSKRLSPTLSHHSVLSD +PIKTYPSLALLDTNLSQPKMGSSASLPQMLISELKQPSKSAGPEISSAFEPICHSGAQDI +SSTLSVHAELSSSPSQTSLTQDTPVSSRTNHEFDAPHTSSSTSILLAPNDSAAENVLKLA +YSRPKPLRSPIKRLYATGLPSRISAPPLVKIHKAFTIKRHSQSAEDLPQLAKPKVSFLVP +GATDEQPSETSQLLSHSHSLKDLPVTLSSDTIIKSQFAEDGSSESNIHTALNYYNQTPEM +QQIVSRDQPSGSSCSIASITDKVIVVSDAPPGHSWIELLNGEKDWITMASGPLAIPPVPA +QLDVINRLSSVCGCLASRCYACVVTASNLSGSTWEDEQNEFRKFISAAELIVNGLRPPYF +FSRLSQDGLDEHLRSTLDSVSVRLADTYNGVQSVLYRISKVLCARNLQATLEILAQLLER +MVEFLKLACMTTHQLSSALEQSVPSQSDLLERIDFFVSRVVPHLVESPILYPVPPAINAH +GCVEDDNCIILKRMVEYFDCVNVKTVDTIASELFGSLVALYRRRWKYFGTRRILAPVVDC +AIGILDLYVRYKLGTLDVPGPKVIYVTLPVMDSPNDDDSSVLTACTRLLQRLSSDIN +>TGZ62726.1||147828||1741007467 TGZ62726.1 hypothetical protein CRM22_007302 [Opisthorchis felineus] +MSTFKPGELAVCTLLRQFTALAKCKIEQALEEKQEHNLMKSMRLSEDIAFDQLLDAFYVS +AEFALPSLLSTVIQWYQSQHSSGPQYAAHRRSAQLSNGTPKSTEAEMASMTNSCFVVASS +DRDSQLAFSMGKPEQTIKSACDARVGVLKSICMNPTEVRILAERRDLAIDIIFCQVLLSI +LRQLPYHPGHNDQVEVILEQSFRRFNYRQDIQPQNSENVNLVADMYAEIVGLLSQTRFAL +VRTRFMWHLNELRSKENSPNVRPSIVSLIMGMKFFRVKMHPIEDFVNFFTFLQELGQYFL +EVKEPEIKHVLSDLFVEILLPVAAVARQEVNIPALKQFVELLYPTSLELAGKKKHVPALF +PLVTCLLCVGTKSFFLTHWTNFLTICLSQLKSRTPKVALVSLQSLSCLVWVYIVRIGGEK +HLETQTKLHHIVNSLFPKNQKFVVPKDAPVNIFVRIIQFIAHEKLEFAIREIIIDRLGVN +GLQKNLYMPERMNIGLCAFLLIAHGLQQKEGAPPMPQPCLGAGGFRQAVIKRSFHGTNLN +ETLGHRLGIQNYLLPIRRAFELILRQLDTQVCRTMMLSKLDVSQKEFEELMSAERKPKMD +LLKTCVACIPRLLPNDMTKPELLELLAKVCLHLDEDVRNMAQQAMANLIVELPAFRVKTI +QVFIQFIQKNVPDTSPHQLDSSLKTLFHLLNNWNLALQKDGAVSPNITEKAALHETEGFA +LVMLCHCRTITRRLALHILRKCRSLLSLINAATCDPNARQPREEVCCIDVLDQSVPAILD +RVLPLIPLHERHLFSNTNGLDFSVFAERSSPVWLSGPSPVPPCPPVVIPTSFVSPQHSGL +MPISNLSSTTTRDSSATERDDQKHTSEALTRNSVCGQCSMFTDGSHPGALGVTGRQQDEQ +LQPQQCTYKASSNPEQSCTLPGDQTTAAAHVARRPSQRQPQQYSSAQYTQAYIIQPFQER +ILLGDVWGTCLSCACSPERLLTCSPQAIRYAWNVLFQRLSTLFPLIDPSAQIAENRASSL +LRSSSKKPTNEREQLFPLWHNYAVLGCCIAPSSTGLRVSKPRDNRYDSSVDFTPPRATPQ +FTASASQPSNSICESPEPRFSSAQSNSGIPASQVGTPLRPTSTNSLDTKFTSPTVTRETA +RDLVRLLVPLLRCEQPEIRDSTVGALGRISPAAFKDALEELNPLLKESVDRKQENVRRRK +RRDGLRASLIRVLALMSQNGVFAYPESNALTPQGCLVSQLTDYLDGMRTYLDSVADQAIN +PTAGACVVSGSGSMVIPATTTGSVSTGSLLVPAVVGGTASTVCTPYSAPTAPLAPVTSNT +SLASLASTESHVLVEIRLHFSIFILEVIRQLPKDKRAQLLPNVMRHQLFILISHWSGYHD +HVLGVHSELGSSDVSVFGTAGTALSKQGPSTTSSLGMSPVSSGPFTGPDSSVPVCFSVDP +SQSNLDPAMHTALTTQASSANLLPTGESAAGVASGCRAHSGSQHNITSQGYSSSNTITAT +DQMIRRSAAATQDIWEQRLWIDLLWFANQAMAALICSGPIYDHQALFGELTDLYGEHSAT +GEKTMAPHPGYILRWVSRLLLARDPILDSSPWLWHCPVVDTSFGYSALMSTRSSSCSASA +IQTRTTLVGGELRLDALLRQLGEETLVLLLDRNQNLPFLLSWVVDQCYTSNNISSSTACF +LCLCRALSNIPDFSCDFVSLLVLAFVFLDHPQKTLSDRAFHLLRVLYHRFILQPGSVIHE +RYHSPCDQDLDLSASSRSTCSSTVCAQLKINVHFWHSIRHWAPSEIVRQFAAQHPDLTLT +IFSEITRRLETCRECLRSALLRLLVPWIINIELVDLIGDTSVTKLDKVGEELCWETLELD +PVNSRKPGDGVFQQIAHHADMIDAVEGDSASSDADIVSDEELLPESTRKKSHLIGRRPTD +GRPGTYGRGRQHVDFFGSEEDSDNVSADLLKSRHTIPPSTLRHRVRKADTVPLPKSNRAR +RTPDIAQKSTAGPSGACEEFWYSVPPTLRSTGWGSKQTTELVLNNLFYLTLRFAECPTTT +ESLKHLWIVLIRHRPSNLRALLRYIIVLTSVAPATLLAHAKRLITYLASEQAESVVDELI +VEIQTIDGIGLVVERTTVFPFFRISVAASSVGRPNETTDVRRPADSAVTVMANQTLVKSG +SNTSPADGRQHPTDQISTFPPTVLQSMSREFVDQMDAELVAINLESTCGAKPTSTIRDER +VFDPSTKPYTESNYCMSSQASGQPPESTAGRGTHEMSMDLPDPSLAFPTQIAGNRPHSPN +LLHESDGITSVRPEALEKDMNQDSPKARFSPEDIYATLRAKDVARVLSGSNAGESDGGSE +PDRTPFRTEAQLRSGTLPRTEDSRERYFNQAGQSRQKITLGQADSATRTFVRHSKRVKSK +SKSHMRPADLVGGTPKVPHQTVSLPPSLAGLAPLPIPLPSAIPSLPSLKLFSSIGAEKQN +PNDSGLSSSAAIGKLSRAESQKAEFTLDATAIEKQALHVQPLAMPLSGGYQAPLGCWLCE +PLVVGGSVVFTGSWPVAGARAPLVLLLAGGLTHCQMVQVDWSPHIPLMILCALLGMDHCR +ALVQEECKLLLVSLLNLVCPDQDTLRLLRMELEGGMLARAYPSALPGPRRPRFRLTLDGG +PYAETFGCLSSPVPMAEDIPQTSFQPRYSPTEGARATPPRKSNRRSFSERLSLSPPNPTI +QVSVGEPDGLPSHSPPIITSVPPSSTLLVQKNGWSGSAYSLLSTDTLIPGPEESGARTRP +DGFVITKDAPQLCPSASAAPVSSVKSRLTTARTTPPRTSRHSIGSSQVMMSTVVEEADSP +VVTTDAKLLQAAVYDLREVLLSTLGQAVWTWDEYRFQRAHTSPVVYLTQQHHSRTTECDT +DITSEFSDKRSLLARLVQLVARILALANTEPSDAESCLLCHHGPPHEHAFAHRLKHSSDC +RTATSPVTNYCVVARLSQISLNMGLSCPNRHYASRCLQIFRILGAHLDRKTLSHLLSRLG +ETVSDANEELQDYVAELFLTLEAAIAHVKTKSNSLLVGRTSKQGDSVSGPWPDKQTRTAP +TSPRSVGGHVPNVDLTISDRRRSQRHSTSTNYCILDGPSSWFKPQYTDEPKQESETLLVT +HESSRLNHILGSEESASRPRTPRLVVCRLPKDEQVDLVCGIFWTGVFLLESDFEHEYIAG +LRILTTLVPSVCADAVAEDNERFSSRALSAVPICLPTRAEQIRTQLHLSSPVAGVLNLAI +KGCSSASLVDPACQLLVCLIPFLNSPLVVSPSKSHLTSQLSFPIVLITLLPMLLTAWDED +PATAGPSGLLETTVEQTNVLQGGFLGRGVCTNTNATPRALGSWVSSATTRSATVATHISH +SSRLGRPDSFSHFPATGGFGTISAVTPGPYCPSTSSTCIHKSLQPVPLRPKNPICIQAAD +ALAQRALQLDPSHLANLALILRLYASGTFSKDVDQWAKCVVCYLLEGYPKYAAYLLQHIS +TLLTAGPLCLKTPLLKIAYLFLQQVELTSPELSNTVQHFISSITSHFLGTEFWSEVTRVI +QIVVSRSAVLSAAPPPCVYKLSGLDPSGSGRVLDFAAAAAAVAVPLPESEPLRMELAGPV +LDFTFNTLYEAPLVAAHFAPATFSATTEEICKSSGVHGVDIGSSKKTDFFRLSDSGLTDL +FSVAASSWNKAGSRQLRLRDRLFRLVACYGLPSNRRFAAPRSPSVIFSQSTETLDPQLSV +HSSSETASMVDMSNSDDIRLDETSSMEQAAVFRDLDTFLDAQLMNINFLGVPDGRLASAS +ASASEDEPRRPWGVRGQSITCHQEFSDDGVEQAADMFPTVRAPLSKSFTQHSSDEPLPWT +NKRRSFPASYSTEVQVAPGSDEPDWVPHAPCYTSSGEPWQHALASSTVDAEGIPTQHPPL +RNAQVDAYATVRCVTEDNNLILPSFPNNGDQPGVEIVTNTHLATVKRLSFERIVEPSTPS +LIRTPSRDDDAFILDQPASPVDKIDLTRIRQVRFERTSKPLSPTLSHHSVLSDPIKTYTS +LAVLDTNLSQPKMGSSASLPQMLISELKQPSKSAGCEISSPFEPICHSGAQDISSTLSVH +AELSSSPSQTSLTQDTPVSSRTNHELDTPHRSLSTSILLTPNDYAAENVLKLAYSRPKPL +RSPIKRLYATGLPSRISAPPLVKIHKAFTIKRHSQSAEDLPQLAQPKVSFLIPGEADEQP +SETSQLLSHSHSLKDLPVTLSSDAIIKSQFAEDGSSESNIHTGLNYYNQPPAMQQIVSRD +QPSGSSCSIASITDKVILVSDAPPGHSWIELLDGEKDWITMASGPLAIPPVPAQIDVINR +LSSVCGCLASRCYACVVTASNLSGSTWEDEQNEFRKFISAAELIVNGLRPPYFFSRLSQD +SLDEHLRSILDSVSVRLADTYNGVQSMLHRMSKVLCAQNLQATLEMLAQLLERMVEFLKL +ACTTTQQLPSALEQSIPSQGDLLERIVFFVSRVVPHLVESPILYPVPPAINAHGCVEDDN +CIILKRMDEYFDCVNVKTVDTIASELFGSLVALYRRRWKYFGTRRILAPVVDCAIGILDL +YVRYKLRTLDVPGPTVIHVTLPAMGSPNDGDSSVLTACTRLLQRLSSDIN +>VDM18555.1||6205||1763001805 VDM18555.1 unnamed protein product [Hydatigera taeniaeformis] +MLRAPIFPNFRDAKWGQPPWTDHLSGSLTESKYANLPLHPPPPPLPSVGMDQVISGDLVL +SISGVHLDSTPQDPFIAASFLDNSIWLELLWYANQSIAALVCCGAIYDTLALFTTPTATH +RVVESSDTALSGSGVGNHFHQISNNTSVTACTGVATSTPSSNATSSTTMLASGVMAEEEC +GPLTLSTIATNSLVSTPAQALNSVSLTTPIGPGFSSSEGQPAPGYIFHWLKGLLLCQDGK +LSISPWLWGLPVVNFGVCSTALKGPHAKTGARNIRLEDSLRSIVIAAAAGRRLDSLFRQL +GEETLTLLLDMNCDLPGLLEILVDQCYTGSLNVIQAFFVIITQHFIKNPKTHCDKFTMLT +LAMVFCEHTVPVIRENAMFLLQTLYYRFILLPVRELHQRMHPECPKMVTSTPVTSISGEL +NDSHTGMCLSALADELLWREIGHWKPCDVLRQFCAQHPTYTLPMVSEICRRLATASSEVR +CRLINLLHYWTINIELVDLFTQGSSSKNNTPCGDGFIENYSGCPEDASTVATLEPISPSG +RTDAVTTSEPSSGSDVEIEGESVNKQNSSISSNSSVSSSPASSISSSDSSSSCTSNSTSI +STDSSNIDLGECGSGAQVTSNLMELRKSRRDHKPNHRLHRNHEETRLRNQRRILEHSLKP +VLDGEGQKSSELWASVPLTLRRSGYGCLKATEMVLNNFFYLTIRFANEPNEASVLGDLWV +LLIRYRPANLQFILRYLIVVASLAPATLCSHVNRVVSFLTNEDPSSVVDVLMTELQTIDG +AGLMIEPLPFPPFFHIAFLSDLIEHSFTAPTGVTNAATTTTGSALTVTATEDRQRRNVIP +ETITFSQRSSVMTVDPDCQSVSARPVSSCSGGWQLNGGDDETAIVSLKSTVEASPLANTT +SAISRLTNRFASSATAMGRRALRTALHPGGGRQSKTSVTLDQGQPQELDIDSLDGEVEEE +CRGARTTADTMPTPQNQNTTTAEALITAKSQTLPYTAASRERYNKQAIVSMDGTPLSRPS +RLRKILSRRRAAAGEKGGSGGGRRNPFRSMFRGSGGGGGGGGSGGRGDAAKISEIVAKVS +DSARLYTTSLPPNLASLQPLPLPLHSHSISHINDYRRKRKGGRHRCSSPRNLRRHHGNAH +GDGDVNQRAKVPLSDTSLLLQQPLPMPKEDNVYHAPLRDWFTEPFVTNGSLVFSGMWSPT +GARSPLVLVLVGEILKSHNKHRVAWRKHMPLLLLCLFLGLDHCRPLVQEESKQLLVNMLR +VICPRIDLLRLLSLQLEIDSQWISHAMHNFSLRGQQFCLTLDDGPSVPKFYLPAPTDAPL +TEVSNCPIWSTYCVPNSSSIGALAVTGTPAAPSGFGTSATPPTGLSPLTDAPPLIATTST +TSGGGGILSRSSLVAHQPTASRPSYVFGSTYSLMSTATLIPAGLSMEQESKRRSVVLNDL +PPPPSSSQPPTSSAHTVGEAGDKSAAMSMDEMAVVTAPFDCEAKHLHLAAERIGQLLEEV +QDRSIWTWEDITPNRFQQATLVTLRSILPPHPSTSPQLRKSFLLLRSACFLDRFVRCVAE +AFAEAEAGCRCEEAASCLCCHHGDPHRHPNDGGVDETNTENLGGGGPSSDGSAFFLFLRS +GCTITRFAQAAFNMGITCPNRHYASRSLQIYRALDAQLDEKSLSEVLVRLAESISDTSEE +LQSYMVELFLTLEAAVGHIQCRGTVLSPFIISNGQFAARSRSSAPNSPLVTSKPPQPPQS +ASPLLLTPQAPPPTPPVHERRHSRRGSSSSLSSLLGYPASIVGTAVVPTPSLVPIQQNTG +LTVPPIKRPLMPPHAEAVSEETDVALASAPSTSALGPVKPTPMPPFPSNAYRSSNNTGVV +PVATNDARWD +>VDM18614.1||6205||1763001864 VDM18614.1 unnamed protein product [Hydatigera taeniaeformis] +MLYSGHVCRVYVVRIKCEKHADTQNKLLSIVNGLFPKGSKSVLPKNVSSTVFVKIIHFIA +QEKLDFAMTEIIFDLLGVNRLQKLRMNIGLRAFLLIAHGLQQKDGEPPMPQNQSLAVCGV +GGVNAYPSVHSRGPGNVRRSFHGTTLDDALCERLGIRPYLVPVRKAFEAILRLLDTFQFD +MKEMRRQRHFSPFRSGDRKPKIDLLKTCVNCIPRLLPYEMSRTELIELLARVSLNVDEEI +RMMAQQTMVNLIIESPAYRQPFIQFIQKYVPDTAPHQLDSCLKTLHTLLVNWKIALQRVC +VSNSFSTSTSTIKIIW +>VDM18652.1||6205||1763001902 VDM18652.1 unnamed protein product [Hydatigera taeniaeformis] +MLHFGYWFLQTVELNQSEMGSRIRAFITATSERFVNTPLWPEVTLLYQVVVARSAVLTAA +PSNPEIYSLPGLDPSGSGRVLDSLAAAAAAAVPLPEAEPQRIALAGRVLDFDSSVLQSAP +LIAGGRGQASPRLGAISVTSQSQHRSPARASHLVEICGLSSSSLSSFFRLGAWKTPWNRQ +ARLRGLFANLLSCYGSELDHLTAPRSPSVIFSQSTETLDRQLSMHSSSETTSINDASNPG +NFLDDGQFDDTSSAERAAVFHDLDTYLDAQLMNINFLDLPDTAWEDQSRCPWGVRGHSIT +SHTEFHEEGDDQQQSESQTQSQKQPDQVNQAPNPFGVSEDIGFPNIAVATEEPLVSPPSI +TSQHQSQHLPEGEELEVDVEEEEDDEYEEEEDEELERGGGGVGGNFLAVGASTLPEAATE +RQSQLSLQLSVSSSAVPAAVSDSTPNAALLAPSTLHRKFNSQLLIAWTSRDDSPVKWLTD +APPAADVNVVNVADISSAMQTYQGTQSIPRKLTLPMHPRAVCSRRRLQALTTFGGSLESL +PSQPNVQSTTAIKPRSCAVPRSFQQSSSAFSFAKVPAITSNGMTDVDATGPPIKPPRRHR +RFRQSLELGTQQQSDLSRRISRSSNALTFSSEDAKAFSTIGTTPQQLLIPVETPSETAWM +ACQRQFKSFQQSSTGVVTGSAQADRLHLLLTSLRSTFSGMLRRTTLQAVKLANLAFCRCP +PGSDLDPEIRISNLIKIADAMGDTLPLPFFALHPNCVALIDEHSVSLCLELGTSYEEWED +AMRQLAVTCASSGTTQWLQHESICRDLQEVLRRLLLFFRSSTDLLTSTYRQLVNPPLSVT +DFTASGVEPLFARLRPYILAPPPSLPPNSQEIGGMEAWRARCARLLLWPPYGSLRSEEVD +AKRALAIYAAAMKSKAPISLDDVMTILEACCEHLWNGMNCDSGVALVFAQGPTIELHTSE +AFASATSTVLAPTPFFTTDDLLTPDGQATLSPLELVDRLLRLLEA +>VDM21492.1||6205||1763002898 VDM21492.1 unnamed protein product [Hydatigera taeniaeformis] +MIGTSPGELVLHSLLKQFASICQCKVEGAIDDKVDNDLVKCMRLQEDTAFQQLLAALHTC +AEHALPCLLQTINKWYDTQHSSGALYPFRRVTSKTLGTRCHTLSAATGHAPSLAGGHDLL +IRSMHVEGSSAVTGTGADVTPGTTAHPGGAVAVAGESGNIVLPLSATFATPQESNTIKHG +LSTSVRENMAERRDLCIDILYCQALTSVLKQLPYHPGHDEIINRILDRSFKHFEYKENLQ +AKPNAENINTVADMYAKVVGELSQTRFNLVRQHFSTRLEHLRAKESNPHTMHSIISLLMG +MKFFRVKASMAN +>VDM21823.1||6205||1763003032 VDM21823.1 unnamed protein product [Hydatigera taeniaeformis] +MRLLNPQLYIVQFRHKFNFGDPVIDRSAVGTCRLAPEEQADLLAGIFWIGVCLLDSDYEY +EYFAGIRLLSRLLPCVCSPLNTTSTTATAAPTASSSNRKWSSQQQQQSFISLHDHVLRLL +HRLKWTTGTEDGDYPGLLGLLLKGCFSSTLIDTSCRLLVHLISAVKSPIVDPNAEIRQYR +RAAVSGTSRSTGSSTSYPGILPTLIIVLLPMLLTAWDEDPDSPASTVPEPAISVGEVGEA +CAEGSGSPAVTSSFSPQSSSSLPPPLGSWVHRSVVISETAAPSPSSGLLSLITTILVSGA +VAKKAPQLRPRNPICTLAAEQLAELALQMDAYRFNNLAIVLRLYANGTFSKDVNQWAKCV +TRYLMDGCAGLGPRILTFLTPVNPTTLKLMGNI +>VDM22809.1||6205||1763003508 VDM22809.1 unnamed protein product [Hydatigera taeniaeformis] +MPTLLEFIEGTRVYLESLNEPSAMVVTMFSSSTATATTAFGVGHGGPAAVVAPSTSVTAV +TAPSLPSATTAAVATSMSSPVGTSPGSGLMVGAVATCGVSGAPASTVVGGASGGAGGSGS +GSGGIGAGGGPAGVSGGSVQNPSVAAAATTPSATAAASAAPAHAPTGVDPMVLTEMRLYF +CVFIRDLIRHLPRERRQRLLPPATRRNLVLLIARWSGFYEHIHNNRFTDIPATWSSARLF +LTGVKVPFLRSLDWHSISCH +>VDM23019.1||6205||1763003610 VDM23019.1 unnamed protein product [Hydatigera taeniaeformis] +MHPIEEFVSYFTFLHELGQYFLEVKEKEIRHAMTCLFVEILLPVAAVVRHEVNIPALKNF +VDLLYNPAFELANKKKHALVSDFEFDRNYRLTGITFFIYVTNITYIQI +>VDM23705.1||6205||1763003877 VDM23705.1 unnamed protein product [Hydatigera taeniaeformis] +MCSSADIALLPSYESSAAISGSGEFELNPGMSPRQHQHLVNSKLFISAKEKARDLVKLII +PLLRCDHPELRESIICGLSRIQPGAFRDVLEDLHPILRESIDLKQKNVQRRRRRDTLRSS +LIRLLALMAQNAVFQHE diff --git a/tests/data/subhog/species_tree_conflict.nwk b/tests/data/subhog/species_tree_conflict.nwk new file mode 100644 index 0000000..338c321 --- /dev/null +++ b/tests/data/subhog/species_tree_conflict.nwk @@ -0,0 +1 @@ +(((IXOSC:1,HELRO:1)33317:1,((8384:1,(((8010:1,(8030:1,8032:1)8028:1)41705:1,(8049:1,(123683:1,ORYLA:1)8089:1)123368:1)1489388:1,((7998:1,42514:1)186628:1,75366:1)186626:1)186625:1)117571:1,CIOIN:1)7711:1)33213:1,NEMVE:1)internal_30:1; diff --git a/tests/data/subhog/species_tree_small.nwk b/tests/data/subhog/species_tree_small.nwk new file mode 100644 index 0000000..c77d0a7 --- /dev/null +++ b/tests/data/subhog/species_tree_small.nwk @@ -0,0 +1 @@ +((95602:1,((((27845:1,27848:1)404429:1,57078:1,59628:1)27871:1,(79923:1,147828:1)6196:1,(39320:1,157069:1,6183:1)31245:1)6179:1,(((85433:1,102285:1)6214:1,(6204:1,6205:1,6211:1)6208:1,53468:1)6201:1,(99802:1,60516:1,64606:1)28843:1)6200:1,117903:1)6157:1)33317:1,5833:1)internal_0:1; diff --git a/tests/fastoma_testutils.py b/tests/fastoma_testutils.py new file mode 100644 index 0000000..1cb40c1 --- /dev/null +++ b/tests/fastoma_testutils.py @@ -0,0 +1,125 @@ +"""Helpers to compare FastOMA outputs by meaning rather than by bytes. + +HOG/OG numbering, TSV row order and the order of elements in the orthoXML can change +between runs (task completion order, set iteration order) without any change in the +inferred orthology. These helpers reduce outputs to order- and id-independent forms. +""" +import csv +import gzip +from collections import Counter, defaultdict +from pathlib import Path + +from lxml import etree + +ORTHOXML_NS = "http://orthoXML.org/2011/" +NS = {"o": ORTHOXML_NS} +ORTHOXML_XSD = Path(__file__).parent / "data" / "orthoxml-0.5.xsd" + + +def _open_text(path): + path = Path(path) + if path.suffix == ".gz": + return gzip.open(path, "rt") + return open(path) + + +def read_group_partition(tsv_path, group_col=0, member_col=1, header=True): + """Partition of members into groups from a 'groupmember' TSV, ignoring group names and row order. + + Works for RootHOGs.tsv and OrthologousGroups.tsv. + Returns a frozenset of frozensets. + """ + groups = defaultdict(set) + with _open_text(tsv_path) as fh: + reader = csv.reader(fh, delimiter="\t") + if header: + next(reader, None) + for row in reader: + if row: + groups[row[group_col]].add(row[member_col]) + return frozenset(frozenset(m) for m in groups.values()) + + +def read_pairs(tsv_path): + """Set of unordered pairs from a two-column TSV (e.g. orthologs.tsv.gz).""" + with _open_text(tsv_path) as fh: + return {frozenset(line.rstrip("\n").split("\t")[:2]) for line in fh if line.strip()} + + +def _parse(orthoxml): + if isinstance(orthoxml, etree._ElementTree): + return orthoxml + if isinstance(orthoxml, etree._Element): + return orthoxml.getroottree() + return etree.parse(str(orthoxml)) + + +def gene_id_to_protid(orthoxml): + tree = _parse(orthoxml) + return {g.get("id"): g.get("protId") for g in tree.iterfind(".//o:gene", NS)} + + +def geneRef_counts(orthoxml, use_protid=True): + """Counter of how often each gene is referenced from groups (a protein must appear at most once).""" + tree = _parse(orthoxml) + id2prot = gene_id_to_protid(tree) if use_protid else {} + return Counter(id2prot.get(r.get("id"), r.get("id")) for r in tree.iterfind(".//o:groups//o:geneRef", NS)) + + +def canonical_group(elem, id2prot): + """Order- and id-independent nested representation of an orthologGroup/paralogGroup. + + Keeps: group type, TaxRange property, and the nesting of genes (as protIds). + Drops: group ids, scores, other properties, taxonIds. + """ + tag = etree.QName(elem).localname + if tag == "geneRef": + return ("gene", id2prot.get(elem.get("id"), elem.get("id"))) + taxrange = None + children = [] + for child in elem: + ctag = etree.QName(child).localname + if ctag == "property" and child.get("name") == "TaxRange": + taxrange = child.get("value") + elif ctag in ("orthologGroup", "paralogGroup", "geneRef"): + children.append(canonical_group(child, id2prot)) + return (tag, taxrange, tuple(sorted(children, key=repr))) + + +def canonical_orthoxml(orthoxml): + """Canonical form of a whole orthoXML file: sorted tuple of canonical top-level groups.""" + tree = _parse(orthoxml) + id2prot = gene_id_to_protid(tree) + groups = tree.find(".//o:groups", NS) + if groups is None: + return () + return tuple(sorted((canonical_group(g, id2prot) for g in groups), key=repr)) + + +def orthoxml_element_counts(orthoxml): + tree = _parse(orthoxml) + return { + "orthologGroup": len(tree.findall(".//o:orthologGroup", NS)), + "paralogGroup": len(tree.findall(".//o:paralogGroup", NS)), + "gene": len(tree.findall(".//o:gene", NS)), + "geneRef": len(tree.findall(".//o:geneRef", NS)), + } + + +_schema = None + + +def orthoxml_schema(): + global _schema + if _schema is None: + _schema = etree.XMLSchema(etree.parse(str(ORTHOXML_XSD))) + return _schema + + +def orthoxml_validation_errors(orthoxml): + """List of schema violations (empty if valid against OrthoXML 0.5).""" + schema = orthoxml_schema() + tree = _parse(orthoxml) + if schema.validate(tree): + return [] + return [f"line {e.line}: {e.message}" for e in schema.error_log] diff --git a/tests/fungi30.py b/tests/fungi30.py new file mode 100644 index 0000000..89e59a7 --- /dev/null +++ b/tests/fungi30.py @@ -0,0 +1,242 @@ +"""Golden-snapshot fixture built from rootHOGs of the fungi-30 run (tests/data/fungi30). + +The rootHOGs listed in families.tsv were selected from a full fungi-30 run because they reach specific +code paths of fastoma-infer-subhogs (conflict resolution, split_hog, MSA trimming, fragments, deep +nesting, ...). They come in two kinds: + +golden the per-rootHOG output (structure and scores) is compared with a stored snapshot in golden/. + The snapshot captures current behaviour, it is not a statement of correctness. +drops fastoma-infer-subhogs drops proteins of these rootHOGs (a known bug). How many it drops depends + on small differences in the MSA, so they are only checked for the invariant "every input protein + is in a final HOG" (strict xfail until the bug is fixed) and are not snapshotted. + +The output depends on the mafft/FastTree versions, so the snapshots are only compared in the +environment they were generated in (golden/ENVIRONMENT.json, i.e. the dessimozlab/fastoma image and the +apt packages of Ubuntu 24.04 used in CI). + +Regenerate the snapshots after an intended change (needs docker; runs the working-tree code with the +tools of the container image): + + python tests/fungi30.py regenerate [--tier fast|slow|all] [--image dessimozlab/fastoma:dev] +""" +import argparse +import csv +import gzip +import json +import os +import pickle +import re +import shlex +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path + +TESTS_DIR = Path(__file__).parent +REPO_DIR = TESTS_DIR.parent +DATA = TESTS_DIR / "data" / "fungi30" +SPECIES_TREE = DATA / "species_tree.nwk" +GOLDEN = DATA / "golden" +ENVIRONMENT_FILE = GOLDEN / "ENVIRONMENT.json" + +# keep the per-level pickles so the final HOG objects can be inspected for the invariant +RUNNER = ("import FastOMA._infer_subhog as m; m.keep_subhog_each_pickle = True; " + "from FastOMA.infer_subhogs import fastoma_infer_subhogs; fastoma_infer_subhogs()") + + +def families(): + with open(DATA / "families.tsv") as fh: + return list(csv.DictReader(fh, delimiter="\t")) + + +def groups(): + """families grouped by (tier, mode); each group is one fastoma-infer-subhogs run.""" + out = {} + for fam in families(): + out.setdefault((fam["tier"], fam["mode"]), []).append(fam["rhog"]) + return out + + +# ---------------------------------------------------------------- environment + +def _first_line(cmd): + res = subprocess.run(cmd, stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=30) + return (res.stdout + res.stderr).strip().splitlines()[0] + + +def parse_tool_versions(mafft_line, fasttree_line): + """normalise `mafft --version` and the first line of `FastTree -expert`. + + The number of OpenMP threads is dropped, but whether FastTree was built with OpenMP is kept.""" + fasttree = fasttree_line.replace("Detailed usage for FastTree", "").strip().rstrip(":") + fasttree = re.sub(r"OpenMP \(\d+ threads\)", "OpenMP", fasttree) + return {"mafft": mafft_line.strip(), "FastTree": fasttree} + + +def tool_versions(): + return parse_tool_versions(_first_line(["mafft", "--version"]), _first_line(["FastTree", "-expert"])) + + +def golden_environment(): + with open(ENVIRONMENT_FILE) as fh: + return json.load(fh) + + +# ---------------------------------------------------------------- running + +def prepare_input(rhogids, dest): + dest = Path(dest) + dest.mkdir(parents=True, exist_ok=True) + for rhogid in rhogids: + with gzip.open(DATA / "rhogs" / f"HOG_{rhogid}.fa.gz", "rb") as src, open(dest / f"HOG_{rhogid}.fa", "wb") as out: + shutil.copyfileobj(src, out) + return dest + + +def infer_subhogs_args(input_dir, species_tree, parallel): + args = ["--input-rhog-folder", str(input_dir), "--species-tree", str(species_tree), + "--output-pickles", "pickles"] + return args + (["--parallel"] if parallel else []) + + +def run_infer_subhogs(input_dir, workdir, parallel): + """runs the working-tree fastoma-infer-subhogs on input_dir inside workdir.""" + env = dict(os.environ, PYTHONHASHSEED="0", PYTHONPATH=str(REPO_DIR)) + cmd = [sys.executable, "-c", RUNNER] + infer_subhogs_args(input_dir, SPECIES_TREE, parallel) + res = subprocess.run(cmd, cwd=workdir, env=env, capture_output=True, text=True) + if res.returncode != 0: + raise RuntimeError(f"fastoma-infer-subhogs failed:\n{res.stdout[-3000:]}\n{res.stderr[-3000:]}") + + +# ---------------------------------------------------------------- reading results + +def input_records(rhogid): + with gzip.open(DATA / "rhogs" / f"HOG_{rhogid}.fa.gz", "rt") as fh: + return [line[1:].split()[0] for line in fh if line.startswith(">")] + + +def final_members(workdir, rhogid): + """all proteins in the final HOG objects of a rootHOG (singletons included).""" + from Bio.SeqRecord import SeqRecord + from Bio.Seq import Seq + from ete3 import Tree + from FastOMA._utils_subhog import prepare_species_tree + + recs = [SeqRecord(Seq(""), id=i) for i in input_records(rhogid)] + root = prepare_species_tree(recs, Tree(str(SPECIES_TREE), format=1), rhogid)[0] + with open(Path(workdir) / f"rhog_{rhogid}" / f"{root.name}.pickle", "rb") as fh: + hogs = pickle.load(fh) + return [p for h in hogs for m in h.get_members() for p in m.split("_|_")] + + +def output_groups(workdir, rhogid): + """the orthoXML group elements that fastoma-infer-subhogs passes on to collect-subhogs.""" + with open(Path(workdir) / "pickles" / f"file_{rhogid}.pickle", "rb") as fh: + return pickle.load(fh) + + +# ---------------------------------------------------------------- rendering + +_SCORE_ORDER = ("CompletenessScore", "ImpliedLosses", "TCSScore") + + +def _render(elem, id2prot): + """an indented, order-independent text form of an orthoXML group (lines list).""" + if elem.tag == "geneRef": + return [id2prot.get(elem.get("id"), elem.get("id"))] + props = {p.get("name"): p.get("value") for p in elem.findall("property")} + scores = {s.get("id"): s.get("value") for s in elem.findall("score")} + if elem.tag == "orthologGroup": + head = "orthologGroup " + props.get("TaxRange", "?") + else: + head = "paralogGroup" + (f" [{props['Type']}]" if "Type" in props else "") + score_txt = " ".join(f"{k}={scores[k]}" for k in _SCORE_ORDER if k in scores) + if score_txt: + head += " " + score_txt + children = [_render(c, id2prot) for c in elem if c.tag in ("orthologGroup", "paralogGroup", "geneRef")] + children.sort(key=lambda lines: "\n".join(lines)) + return [head] + [" " + line for child in children for line in child] + + +def golden_text(groups_, input_ids): + """snapshot text of one rootHOG: header line, then one block per top-level group.""" + id2prot = {i.split("||")[2]: i.split("||")[0] for i in input_ids} + blocks = sorted("\n".join(_render(g, id2prot)) for g in groups_) + n_ref = sum(1 for g in groups_ for _ in g.iter("geneRef")) + header = f"# {len(input_ids)} input proteins, {len(groups_)} groups, {n_ref} proteins in groups" + return header + "\n\n" + "\n\n".join(blocks) + "\n" + + +def golden_path(rhogid): + return GOLDEN / f"{rhogid}.txt" + + +# ---------------------------------------------------------------- regeneration + +def _docker_run(image, mount, workdir, args): + # run as the calling user, otherwise a Linux docker daemon leaves root-owned files in the mounted + # repository that the caller cannot remove; HOME must then point to a writable folder + user = ["--user", f"{os.getuid()}:{os.getgid()}", "-e", "HOME=/tmp"] if hasattr(os, "getuid") else [] + cmd = ["docker", "run", "--rm", "--platform", "linux/amd64", *user, "-v", f"{mount}:{mount}", "-w", str(workdir), + "-e", "PYTHONHASHSEED=0", "-e", f"PYTHONPATH={REPO_DIR}", "-e", "PYTHONDONTWRITEBYTECODE=1", + "--entrypoint", "sh", image, "-c", args] + return subprocess.run(cmd, capture_output=True, text=True) + + +def container_command(input_dir, parallel): + """shell command (for `sh -c` in the container) running the working-tree infer-subhogs; quoted, so + paths with spaces survive.""" + return shlex.join(["/app/bin/python", "-c", RUNNER] + infer_subhogs_args(input_dir, SPECIES_TREE, parallel)) + + +def _tools(env): + return {k: v for k, v in env.items() if k != "image"} + + +def regenerate(tier, image): + GOLDEN.mkdir(parents=True, exist_ok=True) + fams = {f["rhog"]: f for f in families()} + res = _docker_run(image, REPO_DIR, REPO_DIR, "mafft --version &1; FastTree -expert &1 | head -1") + lines = res.stdout.strip().splitlines() + env = parse_tool_versions(lines[0], lines[-1]) + env["image"] = image + print("environment:", env) + # ENVIRONMENT.json is shared by all snapshots: a partial regeneration with other tool versions would + # leave the other tier's snapshots from the old tools while claiming the new ones + if tier != "all" and ENVIRONMENT_FILE.exists() and _tools(golden_environment()) != _tools(env): + sys.exit(f"the tool versions of {image} ({_tools(env)}) differ from those of the existing snapshots " + f"({_tools(golden_environment())}); regenerate all tiers with --tier all") + # docker can only see the repository (mounted at the same path), so work in a folder inside it + with tempfile.TemporaryDirectory(dir=TESTS_DIR, prefix=".fungi30-") as tmp: + for (grp_tier, mode), rhogids in groups().items(): + if tier != "all" and grp_tier != tier: + continue + golden_ids = [r for r in rhogids if fams[r]["kind"] == "golden"] + if not golden_ids: + continue + workdir = Path(tmp) / f"{grp_tier}_{mode}" + workdir.mkdir() + prepare_input(golden_ids, workdir / "in") + args = container_command(workdir / "in", mode == "par") + print(f"running {grp_tier}/{mode}: {', '.join(golden_ids)}", flush=True) + res = _docker_run(image, REPO_DIR, workdir, args) + if res.returncode != 0: + sys.exit(f"fastoma-infer-subhogs failed in {image}:\n{res.stdout[-3000:]}\n{res.stderr[-3000:]}") + for rhogid in golden_ids: + golden_path(rhogid).write_text(golden_text(output_groups(workdir, rhogid), input_records(rhogid))) + print(" wrote", golden_path(rhogid).relative_to(REPO_DIR)) + with open(ENVIRONMENT_FILE, "w") as fh: + json.dump(env, fh, indent=2) + fh.write("\n") + + +if __name__ == "__main__": + sys.path.insert(0, str(REPO_DIR)) + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + sub = parser.add_subparsers(dest="cmd", required=True) + p = sub.add_parser("regenerate", help="regenerate golden snapshots with docker") + p.add_argument("--tier", choices=("fast", "slow", "all"), default="all") + p.add_argument("--image", default="dessimozlab/fastoma:dev") + conf = parser.parse_args() + regenerate(conf.tier, conf.image) diff --git a/tests/output_fixtures.py b/tests/output_fixtures.py new file mode 100644 index 0000000..897e5e3 --- /dev/null +++ b/tests/output_fixtures.py @@ -0,0 +1,143 @@ +"""Hand-made inputs for fastoma-collect-subhogs (the output stage). + +Species tree (((A,B)AB,C)ABC,D)root. Proteins are named a1..a7, b1..b7, c1..c7, d1..d7; +the numeric gene id of e.g. b3 is 2003, its protId 'sp|P02003|B3_B'. + +rootHOG E0000001 (nested duplications, like _hog_class.HOG.to_orthoxml produces them): + + og root + paralogGroup + og ABC + paralogGroup + og AB [a1 b1] + og AB [a2 b2] + c1 + og ABC + paralogGroup + og AB [a3 b3] + og AB [a4 b4] + c2 + d1 + +rootHOG E0000002 (root-level duplication, wrapped as in _infer_subhog.py:119-128, plus a +fragment paralogGroup with a Type property as in _hog_class.py:520-527): + + og AB + paralogGroup + og AB [a5 b5] + og AB [paralogGroup(Type=Dubiousfragment)[a6 a7] b6] + +Proteins a7 can optionally be marked as a minor isoform of a1. b7, c3.. etc. are in the +header but in no group. +""" +import pickle +import xml.etree.ElementTree as ET +from pathlib import Path + +from FastOMA._utils_roothog import Gene + +SPECIES_TREE = "(((A,B)AB,C)ABC,D)root;" +SPECIES_IDX = {"A": 1, "B": 2, "C": 3, "D": 4} +PROTEIN_NAMES = [f"{s}{i}" for s in "abcd" for i in range(1, 8)] +RHOG1_MEMBERS = ["a1", "a2", "a3", "a4", "b1", "b2", "b3", "b4", "c1", "c2", "d1"] +RHOG2_MEMBERS = ["a5", "a6", "a7", "b5", "b6"] + + +def species(name): + return name[0].upper() + + +def gene_id(name): + return SPECIES_IDX[species(name)] * 1000 + int(name[1:]) + + +def prot_id(name): + return f"sp|P{gene_id(name):05d}|{name.upper()}_{species(name)}" + + +def accession(name): + return f"P{gene_id(name):05d}" + + +def sequence(name): + return "MKV" + name.upper() + + +def _og(tax, *children, id_="x", score=True): + e = ET.Element("orthologGroup", {"id": id_}) + if score: + ET.SubElement(e, "score", {"id": "CompletenessScore", "value": "1.0"}) + ET.SubElement(e, "property", {"name": "TaxRange", "value": tax}) + for c in children: + e.append(c) + return e + + +def _pg(*children, prop=None): + e = ET.Element("paralogGroup") + if prop: + ET.SubElement(e, "property", {"name": "Type", "value": prop}) + for c in children: + e.append(c) + return e + + +def _g(name): + return ET.Element("geneRef", {"id": str(gene_id(name))}) + + +def rhog1_hog(score=True): + def og(*a, **kw): + return _og(*a, score=score, **kw) + return og("root", + _pg(og("ABC", _pg(og("AB", _g("a1"), _g("b1")), og("AB", _g("a2"), _g("b2"))), _g("c1")), + og("ABC", _pg(og("AB", _g("a3"), _g("b3")), og("AB", _g("a4"), _g("b4"))), _g("c2"))), + _g("d1"), id_="HOG_E0000001_sub10001") + + +def rhog2_hog(score=True): + def og(*a, **kw): + return _og(*a, score=score, **kw) + return og("AB", + _pg(og("AB", _g("a5"), _g("b5")), + og("AB", _pg(_g("a6"), _g("a7"), prop="Dubiousfragment"), _g("b6"))), + id_="HOG_E0000002_sub10002") + + +def build_collect_inputs(folder, scores=True, isoforms=False): + """Write the inputs of fastoma-collect-subhogs into `folder` and return their paths. + + pickles/rhog_/file_HOG_E000000.pickle, gene_id_dic_xml.pickle, species_tree.nwk, + rhogs/HOG_E000000.fa + """ + folder = Path(folder) + folder.mkdir(parents=True, exist_ok=True) + tree = folder / "species_tree.nwk" + tree.write_text(SPECIES_TREE) + + main_of = {"a7": "a1"} if isoforms else {} + gene_map = {sp: [Gene(gene_id(n), prot_id(n), gene_id(main_of[n]) if n in main_of else None) + for n in PROTEIN_NAMES if species(n) == sp] + for sp in "ABCD"} + if isoforms: + # the main isoform references itself (_utils_roothog.save_gene_id_mapping) + gene_map["A"][0] = Gene(gene_id("a1"), prot_id("a1"), gene_id("a1")) + gene_pickle = folder / "gene_id_dic_xml.pickle" + with open(gene_pickle, "wb") as fh: + pickle.dump(gene_map, fh) + + pickles = folder / "pickles" + for nr, hog in ((1, rhog1_hog(scores)), (2, rhog2_hog(scores))): + sub = pickles / f"rhog_{nr}" + sub.mkdir(parents=True, exist_ok=True) + with open(sub / f"file_HOG_E000000{nr}.pickle", "wb") as fh: + pickle.dump([hog], fh) + + rhogs = folder / "rhogs" + rhogs.mkdir(exist_ok=True) + for fn, members in (("HOG_E0000001.fa", RHOG1_MEMBERS), ("HOG_E0000002.fa", RHOG2_MEMBERS)): + with open(rhogs / fn, "w") as fh: + for n in members: + fh.write(f">{prot_id(n)}||{species(n)}||{gene_id(n)} desc of {n}\n{sequence(n)}\n") + return {"folder": folder, "species_tree": tree, "gene_id_pickle": gene_pickle, + "pickle_folder": pickles, "rhogs": rhogs} diff --git a/tests/roothog_helpers.py b/tests/roothog_helpers.py new file mode 100644 index 0000000..719e578 --- /dev/null +++ b/tests/roothog_helpers.py @@ -0,0 +1,63 @@ +"""Small builders for rootHOG-stage tests: hogmap hits, hogmap/FASTA files and rootHOG partitions.""" +from pathlib import Path + +from Bio.Seq import Seq +from Bio.SeqRecord import SeqRecord + +from FastOMA._utils_roothog import HOGMapData + +HOGMAP_HEADER = ("qseqid", "hogid", "hoglevel", "family_p", "family_count", "family_normcount", + "subfamily_score", "subfamily_count", "qseqlen", "subfamily_medianseqlen", "qseq_overlap") + + +def hit(hogid, score, seqlen=100, medlen=100): + """One omamer hit as stored by parse_hogmap_omamer (all fields are strings).""" + return HOGMapData(hogid, str(score), str(seqlen), str(medlen)) + + +def rec(prot_id, seq="MKVLAAGIVLLLAAGCSSHE"): + return SeqRecord(Seq(seq), id=prot_id, description="") + + +def write_fasta(path, records): + """records: iterable of (id, sequence).""" + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("".join(f">{pid}\n{seq}\n" for pid, seq in records)) + return path + + +def write_hogmap(path, rows, comment=True): + """rows: iterable of (qseqid, hogid, family_p); hogid 'N/A' marks an unmapped protein (omamer v2).""" + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + lines = ["!omamer-version: 2.0.0"] if comment else [] + lines.append("\t".join(HOGMAP_HEADER)) + for qseqid, hogid, score in rows: + if hogid == "N/A": + vals = [qseqid, "N/A", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A", "100", "N/A", "N/A"] + else: + vals = [qseqid, hogid, "LUCA", str(score), "10", "1.0", "1.0", "10", "100", "100", "1.0"] + lines.append("\t".join(vals)) + path.write_text("\n".join(lines) + "\n") + return path + + +def fasta_ids(path): + """Protein ids (header up to the first space and '||') of a FASTA file.""" + ids = [] + with open(path) as fh: + for line in fh: + if line.startswith(">"): + ids.append(line[1:].split()[0].split("||")[0]) + return ids + + +def read_rhog_partition(folder, pattern="HOG_*.fa"): + """Partition of protein ids over the rootHOG FASTA files in folder, ignoring file names and order.""" + return frozenset(frozenset(fasta_ids(f)) for f in Path(folder).glob(pattern)) + + +def partition(rhogs_prots): + """Order-independent form of a rhogs_prots dict ({rhogid: [(species, prot), ...]}).""" + return frozenset(frozenset(v) for v in rhogs_prots.values()) diff --git a/tests/subhog_helpers.py b/tests/subhog_helpers.py new file mode 100644 index 0000000..5e41d6a --- /dev/null +++ b/tests/subhog_helpers.py @@ -0,0 +1,67 @@ +"""Builders for small HOG / species tree / MSA fixtures used by the subHOG-stage tests. + +Protein ids follow the FastOMA convention ``||||``. +""" +from Bio.Align import MultipleSeqAlignment +from Bio.Seq import Seq +from Bio.SeqRecord import SeqRecord +from ete3 import Tree + +from FastOMA._hog_class import HOG, Representative + + +def pid(name, species, num): + return f"{name}||{species}||{num}" + + +def rec(seq_id, seq="ACDEFGHIKL"): + return SeqRecord(Seq(seq), id=seq_id) + + +def msa(*records): + """MSA from SeqRecords or (id, seq) tuples.""" + return MultipleSeqAlignment([r if isinstance(r, SeqRecord) else rec(*r) for r in records]) + + +def species_tree(nwk="((A,B)AB,C)root;", with_size=True): + """ete3 species tree with named internal nodes; optionally annotated with ``size`` + (number of leaves), as prepare_species_tree does for the scores.""" + t = Tree(nwk, format=1) + if with_size: + for n in t.traverse(): + n.add_feature("size", len(n)) + return t + + +def species_of(seq_id): + return seq_id.split("||")[1] + + +def leaf_hog(seq_id, sptree, rhogid="r", seq="ACDEFGHIKL"): + """Singleton HOG of one protein at its species' leaf node of ``sptree``.""" + return HOG(rec(seq_id, seq), sptree & species_of(seq_id), rhogid) + + +def merged_hog(subhogs, taxnode, rep_ids=None, rhogid="r", conf=None, aln=None): + """HOG at ``taxnode`` merging ``subhogs``. + + The MSA defaults to the stacked rows of the subhogs' MSAs; representatives default to + all representatives of the subhogs (or the given ``rep_ids``).""" + if aln is None: + aln = MultipleSeqAlignment([r for h in subhogs for r in h.get_msa()]) + by_id = {r.id: r for r in aln} + if rep_ids is None: + rep_ids = [r.get_id() for h in subhogs for r in h.get_representatives()] + reps = [Representative(by_id[i]) for i in rep_ids] + return HOG(list(subhogs), taxnode, rhogid, msa=aln, representatives=reps, conf_infer_subhhogs=conf) + + +def leaf_members(hog): + """Members collected from the leaves of the subhog hierarchy (should equal hog.get_members()).""" + if not hog._subhogs: + return set(hog.get_members()) + return set().union(*(leaf_members(sh) for sh in hog._subhogs)) + + +def members_of(hogs): + return set().union(set(), *(h.get_members() for h in hogs)) diff --git a/tests/test_batch_roothogs.py b/tests/test_batch_roothogs.py new file mode 100644 index 0000000..1575819 --- /dev/null +++ b/tests/test_batch_roothogs.py @@ -0,0 +1,58 @@ +"""Tests for splitting rootHOG FASTA files into big/rest batches (batch_roothogs.py).""" +import pytest + +from FastOMA import batch_roothogs as B + + +def make_files(folder, sizes): + for name, size in sizes.items(): + path = folder / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"A" * size) + + +def batches(folder): + return {d.name: sorted(f.name for f in d.iterdir()) for d in sorted(folder.iterdir())} + + +def run(tmp_path, tag="out"): + big, rest = tmp_path / tag / "big", tmp_path / tag / "rest" + B.folder_1h_rhog(tmp_path / "in", big, rest) + return batches(big), batches(rest) + + +def test_big_files_get_own_batch_in_descending_size(tmp_path): + make_files(tmp_path / "in", {"big1.fa": 500_000, "big2.fa": 450_000, "huge.fa": 1_300_000, + "edge.fa": 400_000, "s1.fa": 300_000, "sub/s2.fa": 200, "x.txt": 10}) + big, rest = run(tmp_path) + assert big == {"0": ["huge.fa"], "1": ["big1.fa"], "2": ["big2.fa"]} + # threshold is strict; subfolders are searched; non-.fa files ignored + assert rest == {"0": ["edge.fa", "s1.fa", "s2.fa"]} + + +def test_rest_batches_close_after_exceeding_max_size(tmp_path): + make_files(tmp_path / "in", {f"s{i}.fa": 390_000 for i in range(7)}) + _, rest = run(tmp_path) + # the file that pushes the batch above 2 MB stays in that batch + assert [len(v) for v in rest.values()] == [6, 1] + + +def test_empty_input(tmp_path): + (tmp_path / "in").mkdir() + assert run(tmp_path) == ({}, {}) + + +def test_rerun_into_existing_output_fails(tmp_path): + # characterises current behaviour: batch folders are created with mkdir() without exist_ok + make_files(tmp_path / "in", {"s.fa": 10}) + run(tmp_path) + with pytest.raises(FileExistsError): + run(tmp_path) + + +@pytest.mark.xfail(strict=True, reason="BUG: files with the same name in different subfolders overwrite " + "each other in a batch (shutil.copy at batch_roothogs.py:45)") +def test_same_basename_in_subfolders_kept(tmp_path): + make_files(tmp_path / "in", {"a/HOG_1.fa": 10, "b/HOG_1.fa": 20}) + _, rest = run(tmp_path) + assert sum(len(v) for v in rest.values()) == 2 diff --git a/tests/test_check_input.py b/tests/test_check_input.py new file mode 100644 index 0000000..05ddc0c --- /dev/null +++ b/tests/test_check_input.py @@ -0,0 +1,152 @@ +"""Tests for the input validation of fastoma-check-input (check_input.py).""" +import logging + +import pytest +from ete3 import Tree + +from FastOMA import check_input as C +from conftest import TESTDATA_DIR +from roothog_helpers import rec + +PROT = "MKVLAAGIVLLLAAGCSSHE" + + +def make_files(folder, names): + folder.mkdir(exist_ok=True) + for n in names: + (folder / n).write_text(">p\nMKV\n") + return str(folder) + + +class TestCheckProteomeFiles: + def test_species_names(self, tmp_path): + res = C.check_proteome_files(make_files(tmp_path / "p", ["A.fa", "B.fa", "Homo.sapiens.fa"])) + assert sorted(res) == ["A", "B", "Homo.sapiens"] + + def test_needs_three_proteomes(self, tmp_path): + assert C.check_proteome_files(make_files(tmp_path / "p", ["A.fa", "B.fa"])) is False + + def test_mixed_extensions_rejected(self, tmp_path): + assert C.check_proteome_files(make_files(tmp_path / "p", ["A.fa", "B.fa", "C.fasta"])) is False + + @pytest.mark.xfail(strict=True, reason="BUG: hidden files are taken as proteomes, e.g. the ._A.fa AppleDouble " + "files of archives made with macOS tar (check_input.py:39); check-input " + "then reports '._A' as missing from the species tree") + def test_hidden_files_ignored(self, tmp_path): + folder = make_files(tmp_path / "p", ["A.fa", "B.fa", "C.fa", "._A.fa", ".DS_Store"]) + assert sorted(C.check_proteome_files(folder)) == ["A", "B", "C"] + + +class TestCheckProteome: + @staticmethod + def check(recs): + return C.check_proteome(["A"], {"A": recs}, "folder") + + def test_ok(self): + assert self.check([rec(f"p{i}", PROT) for i in range(3)]) is True + + @pytest.mark.parametrize("recs", [ + pytest.param([rec("p", PROT)] * 3, id="duplicate-ids"), + pytest.param([rec("x" * 61, PROT), rec("b", PROT), rec("c", PROT)], id="id-longer-than-60"), + pytest.param([rec(f"p{i}", "ACGTACGTAC") for i in range(3)], id="dna"), + pytest.param([rec("a", PROT), rec("b", PROT)], id="two-proteins"), + ]) + def test_rejected(self, recs): + assert self.check(recs) is False + + @pytest.mark.xfail(strict=True, reason="BUG: empty proteome raises ZeroDivisionError (check_input.py:65) " + "instead of being reported as invalid") + def test_empty_proteome_rejected(self): + assert self.check([]) is False + + +class TestCheckHogmapFiles: + def test_species_names(self, tmp_path): + folder = make_files(tmp_path / "h", ["A.fa.hogmap", "B.fasta.hogmap", "notes.txt"]) + assert sorted(C.check_hogmap_files(folder)) == ["A", "B"] + + @pytest.mark.xfail(strict=True, reason="BUG: hidden ._A.fa.hogmap files (macOS tar) count as hogmaps " + "(check_input.py:91), so the hogmap set never equals the proteomes") + def test_hidden_files_ignored(self, tmp_path): + assert C.check_hogmap_files(make_files(tmp_path / "h", ["A.fa.hogmap", "._A.fa.hogmap"])) == ["A"] + + @pytest.mark.xfail(strict=True, reason="BUG: dots are dropped from species names (check_input.py:91), " + "so they never match the proteome names") + def test_dotted_species_name(self, tmp_path): + assert C.check_hogmap_files(make_files(tmp_path / "h", ["Homo.sapiens.fa.hogmap"])) == ["Homo.sapiens"] + + +class TestSpeciesTreeChecks: + def test_leaves_ok(self): + assert C.check_speciestree_leaves(Tree("((A,B),C);"), ["A", "B"]) is True + + def test_leaves_missing_species(self): + assert C.check_speciestree_leaves(Tree("((A,B),C);"), ["A", "D"]) is False + + def test_leaves_duplicated(self): + assert C.check_speciestree_leaves(Tree("((A,B),A);"), ["A", "B"]) is False + + @pytest.mark.xfail(strict=True, reason="BUG: name list is reset inside the loop (check_input.py:100), " + "duplicate internal names are never reported") + def test_internalnode_duplicates_reported(self, caplog): + with caplog.at_level(logging.WARNING, logger="FastOMA"): + C.check_speciestree_internalnode(Tree("((A,B)x,(C,D)x)r;", format=1)) + assert any("unique" in r.message for r in caplog.records) + + @pytest.mark.xfail(strict=True, reason="BUG: UnboundLocalError for trees without internal nodes " + "(check_input.py:106)") + def test_internalnode_single_leaf(self): + C.check_speciestree_internalnode(Tree("A;")) + + +class TestAddInternalNodePrune: + @staticmethod + def prune(tmp_path, nwk, species): + out = tmp_path / "tree.nwk" + C.add_internal_node_prune(Tree(nwk, format=1), species, str(out)) + return out.read_text().strip() + + @pytest.mark.parametrize("nwk, species, expected", [ + ("((A,B),(C,D));", "ABCD", "((A:1,B:1)internal_0:1,(C:1,D:1)internal_1:1)internal_2:0;"), + ("((A,B)inner,(C,D)inner)root;", "ABCD", "((A:1,B:1)inner:1,(C:1,D:1)internal_0:1)root:0;"), + ("((A,B)abc,(C,D)'a.bc')root;", "ABCD", "((A:1,B:1)abc:1,(C:1,D:1)abc_0:1)root:0;"), + # names shorter than 3 characters are replaced + ("((A,B)ab,(C,D)cd)root;", "ABC", "((A:1,B:1)internal_0:1,C:1)root:0;"), + ]) + def test_rename_and_prune(self, tmp_path, nwk, species, expected): + assert self.prune(tmp_path, nwk, list(species)) == expected + + @pytest.mark.xfail(strict=True, reason="BUG: a name made only of special chars becomes empty " + "(check_input.py:168)") + def test_special_char_only_name(self, tmp_path): + tree = Tree(self.prune(tmp_path, "((A,B)'!!!',(C,D)cde)root;", list("ABCD")), format=1) + assert all(n.name for n in tree.traverse()) + + @pytest.mark.xfail(strict=True, reason="BUG: internal node named like a leaf makes ete3 prune raise " + "'Ambiguous node name' (check_input.py:180)") + def test_internal_node_named_like_leaf(self, tmp_path): + res = self.prune(tmp_path, "((AQUAE,CHLTR)MYCGE,MYCGE)root;", ["AQUAE", "CHLTR", "MYCGE"]) + assert {n.name for n in Tree(res, format=1).get_leaves()} == {"AQUAE", "CHLTR", "MYCGE"} + + +def test_check_omamer_db(tmp_path): + small = tmp_path / "small.h5" + small.write_bytes(b"x" * 10) + assert C.check_omamer_db(None) is True + assert C.check_omamer_db(str(tmp_path / "missing.h5")) is False + assert C.check_omamer_db(str(small)) is False + assert C.check_omamer_db(str(TESTDATA_DIR / "test.h5")) is True + + +class TestCheckSplice: + def test_ok(self): + assert C.check_splice({"A": [["a1", "a2"], ["b1"]], "B": []}) is None + + def test_no_isoforms_exits(self): + with pytest.raises(SystemExit): + C.check_splice({"A": [["a1"], ["b1"]]}) + + @pytest.mark.xfail(strict=True, reason="BUG: species with a single splice line are ignored " + "(len(isoforms)>1 at check_input.py:195)") + def test_single_gene_with_isoforms(self): + assert C.check_splice({"A": [["a1", "a2"]]}) is None diff --git a/tests/test_collect_subhogs.py b/tests/test_collect_subhogs.py new file mode 100644 index 0000000..b3035ff --- /dev/null +++ b/tests/test_collect_subhogs.py @@ -0,0 +1,357 @@ +"""Tests for the output stage, FastOMA/collect_subhogs.py (fastoma-collect-subhogs).""" +import csv +import gzip +import os +import pickle +import xml.etree.ElementTree as ET +from pathlib import Path + +import pytest +from Bio import SeqIO +from ete3 import Tree +from lxml import etree + +from FastOMA import collect_subhogs as cs +from FastOMA.transformer import header_transformer +from fastoma_testutils import NS, geneRef_counts, orthoxml_validation_errors +import output_fixtures as fx +from output_fixtures import accession, prot_id + + +_REAL_WALK = os.walk + + +def _walk_in_order(order): + """os.walk replacement that visits the pickle sub folders in the given order.""" + def walk(top, **kwargs): + entries = list(_REAL_WALK(top, **kwargs)) + root, rest = entries[0], entries[1:] + rest.sort(key=lambda e: order(Path(e[0]).name)) + yield root + yield from rest + return walk + + +@pytest.fixture +def sorted_walk(monkeypatch): + # HOG numbering follows the os.walk order (see TestHogNumbering); fix it to rhog_1, rhog_2 + monkeypatch.setattr(cs.os, "walk", _walk_in_order(lambda name: name)) + + +def _collect(inputs, transform="noop", out="FastOMA_HOGs.orthoxml"): + tr = header_transformer(transform) + cs.write_hog_orthoxml(str(inputs["pickle_folder"]), out, inputs["gene_id_pickle"], tr, + str(inputs["species_tree"])) + cs.write_roothogs(Path(out), inputs["rhogs"], output_file_roothog_tsv="RootHOGs.tsv", id_transformer=tr) + cs.write_group_files(Path(out), inputs["rhogs"], output_file_og_tsv="OrthologousGroups.tsv", id_transformer=tr) + return Path(out) + + +@pytest.fixture +def inputs(in_tmp_dir): + return fx.build_collect_inputs(in_tmp_dir / "in") + + +@pytest.fixture +def collected(inputs, sorted_walk): + return _collect(inputs) + + +def _taxonomy(): + return cs.convert_speciestree_to_orthoxml_taxonomy(Tree(fx.SPECIES_TREE, format=1)) + + +def _ids(elem): + return [e.get("id") for e in elem.iter("orthologGroup")] + + +def _tsv_rows(path): + with open(path, newline="") as fh: + reader = csv.reader(fh, delimiter="\t") + return next(reader), {tuple(r) for r in reader} + + +class TestTaxonomy: + def test_preorder_ids(self): + root, name2taxid = _taxonomy() + assert name2taxid == {"root": 1, "ABC": 2, "AB": 3, "A": 4, "B": 5, "C": 6, "D": 7} + assert [(t.get("id"), t.get("name")) for t in root.iter("taxon")][:3] == [("1", "root"), ("2", "ABC"), ("3", "AB")] + ab = root.find("./taxon/taxon/taxon") + assert [c.get("name") for c in ab] == ["A", "B"] + + def test_unnamed_internal_nodes_collide(self): + # characterises current behaviour: relies on check_input naming all internal nodes + root, name2taxid = cs.convert_speciestree_to_orthoxml_taxonomy(Tree("((A,B),C);", format=1)) + assert len(list(root.iter("taxon"))) == 5 + assert name2taxid == {"": 2, "A": 3, "B": 4, "C": 5} + + +class TestUpdateHogids: + def test_nested_duplications(self): + _, name2taxid = _taxonomy() + hog = cs.update_hogids(1, fx.rhog1_hog(), name2taxid) + assert _ids(hog) == [ + "HOG:0000001_1", + "HOG:0000001.1a_2", "HOG:0000001.1a.1a_3", "HOG:0000001.1a.1b_3", + # the duplication counter is per depth over the whole rootHOG, not per parent + "HOG:0000001.1b_2", "HOG:0000001.1b.2a_3", "HOG:0000001.1b.2b_3", + ] + assert [e.get("taxonId") for e in hog.iter("orthologGroup")] == ["1", "2", "3", "3", "2", "3", "3"] + + def test_root_level_duplication(self): + _, name2taxid = _taxonomy() + hog = cs.update_hogids(42, fx.rhog2_hog(), name2taxid) + assert _ids(hog) == ["HOG:0000042_3", "HOG:0000042.1a_3", "HOG:0000042.1b_3"] + assert all(p.get("id") is None for p in hog.iter("paralogGroup")) + + def test_letter_overflow_after_z(self): + _, name2taxid = _taxonomy() + pg = fx._pg(*[fx._og("AB", fx._g("a1"), fx._g("b1"), score=False) for _ in range(30)]) + hog = cs.update_hogids(7, fx._og("root", pg, fx._g("c1"), id_="HOG_E1_sub1", score=False), name2taxid) + ids = [o.get("id") for o in pg] + assert ids[0] == "HOG:0000007.1a_3" + assert ids[25] == "HOG:0000007.1z_3" + assert ids[26:] == ["HOG:0000007.1aa_3", "HOG:0000007.1ab_3", "HOG:0000007.1ac_3", "HOG:0000007.1ad_3"] + assert len(set(ids)) == 30 + + def test_property_in_paralog_group_shifts_letters(self): + # characterises current behaviour: the child of a fragment paralogGroup + # (_hog_class.py:521) takes index 0, so its orthologGroup children start at 'b'. + # _hog_class only puts geneRefs into such groups today, so no id is affected yet. + _, name2taxid = _taxonomy() + pg = fx._pg(fx._og("AB", fx._g("a1"), fx._g("b1")), fx._og("AB", fx._g("a2"), fx._g("b2")), + prop="Dubiousfragment") + cs.update_hogids(1, fx._og("root", pg, fx._g("c1"), id_="HOG_E1_sub1"), name2taxid) + assert [o.get("id") for o in pg.iter("orthologGroup")] == ["HOG:0000001.1b_3", "HOG:0000001.1c_3"] + + def test_omamer_roothog_property_after_scores(self): + _, name2taxid = _taxonomy() + hog = cs.update_hogids(1, fx.rhog1_hog(), name2taxid) + assert [(c.tag, c.get("id") or c.get("name")) for c in hog][:3] == [ + ("score", "CompletenessScore"), ("property", "OMAmerRootHOG"), ("property", "TaxRange")] + assert hog[1].get("value") == "HOG:E0000001" + + def test_omamer_roothog_property_without_scores(self): + _, name2taxid = _taxonomy() + hog = cs.update_hogids(1, fx.rhog1_hog(score=False), name2taxid) + assert (hog[0].get("name"), hog[0].get("value")) == ("OMAmerRootHOG", "HOG:E0000001") + + def test_omamer_roothog_from_unexpected_id(self): + # characterises current behaviour: the first two '_' separated fields joined by ':' + _, name2taxid = _taxonomy() + hog = cs.update_hogids(1, fx._og("AB", fx._g("a1"), fx._g("b1"), id_="weird"), name2taxid) + assert hog.find("./property[@name='OMAmerRootHOG']").get("value") == "weird" + + def test_missing_taxrange_raises(self): + hog = ET.Element("orthologGroup", {"id": "HOG_E1_sub1"}) + with pytest.raises(AttributeError): + cs.update_hogids(1, hog, {"root": 1}) + + +class TestWriteHogOrthoxml: + def test_header(self, collected): + root = etree.parse(str(collected)).getroot() + assert root.tag == "{http://orthoXML.org/2011/}orthoXML" + assert root.get("version") == "0.5" + assert root.get("origin").startswith("FastOMA ") + assert root.get("originVersion") # a timestamp + species = {s.get("name"): (s.get("taxonId"), s.get("NCBITaxId")) for s in root.iterfind("o:species", NS)} + assert species == {"A": ("4", "0"), "B": ("5", "0"), "C": ("6", "0"), "D": ("7", "0")} + genes = {g.get("id"): g.get("protId") for g in root.iterfind(".//o:gene", NS)} + assert len(genes) == 28 and genes["2003"] == "sp|P02003|B3_B" + assert all(g.get("main_isoform") is None for g in root.iterfind(".//o:gene", NS)) + + def test_groups_reference_every_placed_protein_once(self, collected): + counts = geneRef_counts(collected) + assert set(counts) == {prot_id(n) for n in fx.RHOG1_MEMBERS + fx.RHOG2_MEMBERS} + assert max(counts.values()) == 1 + + def test_hog_ids_in_file(self, collected): + tree = etree.parse(str(collected)) + tops = tree.findall("./o:groups/o:orthologGroup", NS) + assert [t.get("id") for t in tops] == ["HOG:0000001_1", "HOG:0000002_3"] + assert [t.find("o:property[@name='OMAmerRootHOG']", NS).get("value") for t in tops] == [ + "HOG:E0000001", "HOG:E0000002"] + assert len(tree.findall(".//o:orthologGroup", NS)) == 10 + + def test_score_defs_only_for_present_scores(self, collected): + root = etree.parse(str(collected)).getroot() + assert [d.get("id") for d in root.iterfind("o:scores/o:scoreDef", NS)] == ["CompletenessScore"] + + def test_no_scores_element_without_scores(self, in_tmp_dir, sorted_walk): + inputs = fx.build_collect_inputs(in_tmp_dir / "in", scores=False) + out = _collect(inputs) + assert etree.parse(str(out)).find("o:scores", NS) is None + + def test_main_isoform_attribute(self, in_tmp_dir, sorted_walk): + inputs = fx.build_collect_inputs(in_tmp_dir / "in", isoforms=True) + out = _collect(inputs) + genes = {g.get("id"): g.get("main_isoform") for g in etree.parse(str(out)).iterfind(".//o:gene", NS)} + assert genes["1007"] == "1001" and genes["1001"] == "1001" and genes["1002"] is None + + def test_nested_duplications_are_schema_valid(self, in_tmp_dir, sorted_walk): + inputs = fx.build_collect_inputs(in_tmp_dir / "in") + (inputs["pickle_folder"] / "rhog_2" / "file_HOG_E0000002.pickle").unlink() + assert orthoxml_validation_errors(_collect(inputs)) == [] + + @pytest.mark.xfail(strict=True, reason="BUG: root-level duplication is wrapped in an orthologGroup with a " + "single paralogGroup child, schema needs >= 2 (_infer_subhog.py:112-121)") + def test_root_level_duplication_is_schema_valid(self, collected): + assert orthoxml_validation_errors(collected) == [] + + @pytest.mark.xfail(strict=True, reason="BUG: gene@main_isoform is not allowed by the OrthoXML 0.5 schema " + "(collect_subhogs.py:208-209)") + def test_main_isoform_output_is_schema_valid(self, in_tmp_dir, sorted_walk): + inputs = fx.build_collect_inputs(in_tmp_dir / "in", isoforms=True) + (inputs["pickle_folder"] / "rhog_2" / "file_HOG_E0000002.pickle").unlink() + assert orthoxml_validation_errors(_collect(inputs)) == [] + + def test_only_file_pickles_are_read(self, inputs, sorted_walk): + with open(inputs["pickle_folder"] / "rhog_1" / "other.pickle", "wb") as fh: + pickle.dump([fx.rhog2_hog()], fh) + assert [h.get("id") for h in cs.iter_hogs(inputs["pickle_folder"])] == [ + "HOG_E0000001_sub10001", "HOG_E0000002_sub10002"] + + def test_empty_pickle_folder(self, inputs, sorted_walk, tmp_path): + empty = tmp_path / "empty" + empty.mkdir() + inputs["pickle_folder"] = empty + out = _collect(inputs, out="empty.orthoxml") + tree = etree.parse(str(out)) + assert tree.find("o:groups", NS) is not None and len(tree.find("o:groups", NS)) == 0 + assert _tsv_rows("RootHOGs.tsv")[1] == set() + + +class TestHogNumbering: + def _top_ids(self, out): + return {t.find("o:property[@name='OMAmerRootHOG']", NS).get("value"): t.get("id") + for t in etree.parse(str(out)).findall("./o:groups/o:orthologGroup", NS)} + + def test_numbering_follows_pickle_listing_order(self, inputs, monkeypatch): + # characterises current behaviour: rootHOGs are numbered in os.walk order (collect_subhogs.py:223) + monkeypatch.setattr(cs.os, "walk", _walk_in_order(lambda name: name), raising=True) + assert self._top_ids(_collect(inputs, out="a.orthoxml")) == {"HOG:E0000001": "HOG:0000001_1", + "HOG:E0000002": "HOG:0000002_3"} + monkeypatch.setattr(cs.os, "walk", _walk_in_order(lambda name: [-ord(c) for c in name])) + assert self._top_ids(_collect(inputs, out="b.orthoxml")) == {"HOG:E0000001": "HOG:0000002_1", + "HOG:E0000002": "HOG:0000001_3"} + + @pytest.mark.xfail(strict=True, reason="BUG?/contract undecided: HOG ids depend on the directory listing " + "order of the pickle folders, i.e. on Nextflow task completion " + "(collect_subhogs.py:51,223, FastOMA.nf collect_subhogs stageAs)") + def test_numbering_independent_of_listing_order(self, inputs, monkeypatch): + monkeypatch.setattr(cs.os, "walk", _walk_in_order(lambda name: name)) + first = self._top_ids(_collect(inputs, out="a.orthoxml")) + monkeypatch.setattr(cs.os, "walk", _walk_in_order(lambda name: [-ord(c) for c in name])) + assert self._top_ids(_collect(inputs, out="b.orthoxml")) == first + + +class TestRootHogFiles: + def test_tsv(self, collected): + header, rows = _tsv_rows("RootHOGs.tsv") + assert header == ["RootHOG", "Protein", "OMAmerRootHOG"] + assert rows == ({("HOG:0000001", prot_id(n), "HOG:E0000001") for n in fx.RHOG1_MEMBERS} + | {("HOG:0000002", prot_id(n), "HOG:E0000002") for n in fx.RHOG2_MEMBERS}) + + def test_fasta_files(self, collected): + assert sorted(os.listdir("RootHOGsFasta")) == ["HOG0000001.fa.gz", "HOG0000002.fa.gz"] + with gzip.open("RootHOGsFasta/HOG0000002.fa.gz", "rt") as fh: + recs = {r.id: str(r.seq) for r in SeqIO.parse(fh, "fasta")} + assert recs == {prot_id(n): fx.sequence(n) for n in fx.RHOG2_MEMBERS} + + def test_fasta_header_format(self, collected): + # characterises current behaviour: the internal 'id||species||numericId' stays in the + # description because only rec.id is replaced (collect_subhogs.py:305-306) + with gzip.open("RootHOGsFasta/HOG0000001.fa.gz", "rt") as fh: + headers = {line.rstrip("\n") for line in fh if line.startswith(">")} + assert f">{prot_id('a1')} {prot_id('a1')}||A||1001 desc of a1 [A]" in headers + assert len(headers) == len(fx.RHOG1_MEMBERS) + + +class TestOrthologousGroupFiles: + def test_tsv(self, collected): + header, rows = _tsv_rows("OrthologousGroups.tsv") + assert header == ["Group", "Protein"] + # heaviest paralogGroup child wins, ties go to the first child (zoo/hog/extract_groups.py:143-148) + assert rows == ({("OG_0000001", prot_id(n)) for n in ("a1", "b1", "c1", "d1")} + | {("OG_0000002", prot_id(n)) for n in ("a5", "b5")}) + + def test_heavier_paralog_child_wins(self, in_tmp_dir, sorted_walk): + inputs = fx.build_collect_inputs(in_tmp_dir / "in") + hog = fx._og("root", fx._pg(fx._og("AB", fx._g("a1"), fx._g("b1")), + fx._og("ABC", fx._g("a2"), fx._g("b2"), fx._g("c2"))), + fx._g("d1"), id_="HOG_E0000001_sub1") + with open(inputs["pickle_folder"] / "rhog_1" / "file_HOG_E0000001.pickle", "wb") as fh: + pickle.dump([hog], fh) + _collect(inputs) + rows = _tsv_rows("OrthologousGroups.tsv")[1] + assert {p for g, p in rows if g == "OG_0000001"} == {prot_id(n) for n in ("a2", "b2", "c2", "d1")} + + def test_at_most_one_protein_per_species(self, collected): + groups = {} + for g, p in _tsv_rows("OrthologousGroups.tsv")[1]: + groups.setdefault(g, []).append(p.rsplit("_", 1)[1]) + assert all(len(sp) == len(set(sp)) >= 2 for sp in groups.values()) + + def test_groups_smaller_than_two_are_dropped(self, in_tmp_dir, sorted_walk): + inputs = fx.build_collect_inputs(in_tmp_dir / "in") + # a duplication of A only, as wrapped by _infer_subhog: the marker group would be {a5} + hog = fx._og("A", fx._pg(fx._g("a5"), fx._g("a6")), id_="HOG_E0000002_sub1") + with open(inputs["pickle_folder"] / "rhog_2" / "file_HOG_E0000002.pickle", "wb") as fh: + pickle.dump([hog], fh) + _collect(inputs) + assert {g for g, _ in _tsv_rows("OrthologousGroups.tsv")[1]} == {"OG_0000001"} + assert {g for g, _, _ in _tsv_rows("RootHOGs.tsv")[1]} == {"HOG:0000001", "HOG:0000002"} + assert sorted(os.listdir("OrthologousGroupsFasta")) == ["OG_0000001.fa.gz"] + + def test_fasta_files(self, collected): + assert sorted(os.listdir("OrthologousGroupsFasta")) == ["OG_0000001.fa.gz", "OG_0000002.fa.gz"] + with gzip.open("OrthologousGroupsFasta/OG_0000001.fa.gz", "rt") as fh: + recs = list(SeqIO.parse(fh, "fasta")) + assert {r.id for r in recs} == {prot_id(n) for n in ("a1", "b1", "c1", "d1")} + assert {r.description.rsplit(" ", 1)[1] for r in recs} == {"[A]", "[B]", "[C]", "[D]"} + + def test_protein_missing_from_roothog_fasta_is_silently_dropped(self, in_tmp_dir, sorted_walk): + # characterises current behaviour: no error if a group member is not in the rootHOG fasta + inputs = fx.build_collect_inputs(in_tmp_dir / "in") + fa = inputs["rhogs"] / "HOG_E0000002.fa" + fa.write_text("".join(line for line in fa.read_text().splitlines(keepends=True) + if "A5_A" not in line and line != "MKVA5\n")) + _collect(inputs) + with gzip.open("OrthologousGroupsFasta/OG_0000002.fa.gz", "rt") as fh: + assert [r.id for r in SeqIO.parse(fh, "fasta")] == [prot_id("b5")] + assert ("OG_0000002", prot_id("a5")) in _tsv_rows("OrthologousGroups.tsv")[1] + + +class TestUniProtIdTransform: + @pytest.fixture + def collected_up(self, inputs, sorted_walk): + return _collect(inputs, transform="UniProt") + + def test_orthoxml_protids(self, collected_up): + prot_ids = {g.get("protId") for g in etree.parse(str(collected_up)).iterfind(".//o:gene", NS)} + assert prot_ids == {accession(n) for n in fx.PROTEIN_NAMES} + + def test_tsvs_and_fastas_use_the_same_ids(self, collected_up): + roothog_prots = {p for _, p, _ in _tsv_rows("RootHOGs.tsv")[1]} + assert roothog_prots == {accession(n) for n in fx.RHOG1_MEMBERS + fx.RHOG2_MEMBERS} + og_prots = {p for g, p in _tsv_rows("OrthologousGroups.tsv")[1] if g == "OG_0000002"} + assert og_prots == {accession("a5"), accession("b5")} + with gzip.open("OrthologousGroupsFasta/OG_0000002.fa.gz", "rt") as fh: + assert {r.id for r in SeqIO.parse(fh, "fasta")} == og_prots + with gzip.open("RootHOGsFasta/HOG0000001.fa.gz", "rt") as fh: + assert {r.id for r in SeqIO.parse(fh, "fasta")} == {accession(n) for n in fx.RHOG1_MEMBERS} + + +class TestCli: + def test_end_to_end(self, inputs, sorted_walk, monkeypatch): + import sys + monkeypatch.setattr(sys, "argv", [ + "fastoma-collect-subhogs", "--pickle-folder", str(inputs["pickle_folder"]), + "--roothogs-folder", str(inputs["rhogs"]), "--gene-id-pickle-file", str(inputs["gene_id_pickle"]), + "--out", "FastOMA_HOGs.orthoxml", "--marker-groups-fasta", "OrthologousGroups.tsv", + "--roothog-tsv", "RootHOGs.tsv", "--species-tree", str(inputs["species_tree"])]) + cs.fastoma_collect_subhogs() + for name in ("FastOMA_HOGs.orthoxml", "RootHOGs.tsv", "OrthologousGroups.tsv", + "RootHOGsFasta", "OrthologousGroupsFasta"): + assert Path(name).exists(), name diff --git a/tests/test_fragment_detection.py b/tests/test_fragment_detection.py new file mode 100644 index 0000000..4bc9160 --- /dev/null +++ b/tests/test_fragment_detection.py @@ -0,0 +1,96 @@ +import numpy as np +import pytest + +from FastOMA._utils_frag_SO_detection import read_msa, split_candidates, compute_identity, find_prot_dubious_msa +from subhog_helpers import pid, msa + +H1, H2, H3 = pid("h1", "HUMAN", 1), pid("h2", "HUMAN", 2), pid("h3", "HUMAN", 3) +M1 = pid("m1", "MOUSE", 4) + + +def test_read_msa_ids_and_coordinates(): + ids, seqs, species, coords = read_msa(msa((H1, "aa--"), (M1, "-AA-"))) + assert list(ids) == [H1, M1] + assert seqs.shape == (2, 4) + # sequences are upper-cased + assert seqs[0].tobytes() == b"AA--" + assert coords.tolist() == [[0, 1], [1, 2]] + + +def test_read_msa_all_gap_row(): + _, _, _, coords = read_msa(msa((H1, "----"), (M1, "AAAA"))) + assert coords.tolist() == [[0, 0], [0, 3]] + + +@pytest.mark.xfail(strict=True, reason="BUG: read_msa strips the last character of species names " + "(_utils_frag_SO_detection.py:36)") +def test_read_msa_species_names(): + _, _, species, _ = read_msa(msa((pid("a", "AQUAE", 1), "AA"), (pid("b", "AQUAF", 2), "AA"))) + assert list(species) == ["AQUAE", "AQUAF"] + + +def test_compute_identity(): + s1 = np.frombuffer(b"AC-D", dtype="S1") + s2 = np.frombuffer(b"AGWD", dtype="S1") + assert compute_identity(s1, s2) == (pytest.approx(2 / 3), pytest.approx(2 / 4)) + gap = np.frombuffer(b"--", dtype="S1") + assert compute_identity(gap, gap) == (1, 1) + + +def test_split_candidates_non_overlapping_fragments(): + cands = split_candidates(read_msa(msa((H2, "-----AAAA"), (H1, "AAAA-----"), (M1, "AAAAAAAAA"))), 0.15) + # ordered by start coordinate + assert cands == [((H1, H2), [(1, 1)])] + + +def test_split_candidates_small_overlap_within_margin(): + cands = split_candidates(read_msa(msa((H1, "AAAAAAAAAAW----------"), (H2, "---------AWAAAAAAAAAA"))), 0.15) + assert len(cands) == 1 + assert cands[0][0] == (H1, H2) + + +def test_split_candidates_large_overlap(): + assert split_candidates(read_msa(msa((H1, "AAAAAA----"), (H2, "--AAAAAA--"))), 0.15) == [] + + +def test_split_candidates_third_full_length_copy_blocks_merge(): + aln = msa((H1, "AAAA------"), (H2, "------AAAA"), (H3, "AAAAAAAAAA")) + assert split_candidates(read_msa(aln), 0.15) == [] + + +def test_split_candidates_identity_is_not_used(): + # characterises current behaviour: identity of the overlap is computed but does not prevent a merge + cands = split_candidates(read_msa(msa((H1, "AAAAAA----"), (H2, "----WWWWWW"))), 0.5) + assert cands == [((H1, H2), [(0.0, 0.0)])] + + +def test_split_candidates_margin_must_be_fraction(): + with pytest.raises(AssertionError): + split_candidates(read_msa(msa((H1, "AA"), (H2, "AA"))), 2) + + +def test_find_prot_dubious_msa(subhog_conf): + aln = msa((H1, "AAAA------"), (H2, "------AAAA"), (M1, "AAAAAAAAAA")) + for r in aln: + r.name = r.id # as for records parsed by SeqIO (mafft output) + prots, seqs = find_prot_dubious_msa(aln, subhog_conf()) + assert prots == [[H1, H2]] + assert [[r.id for r in s] for s in seqs] == [[H1, H2]] + + +def test_find_prot_dubious_msa_matches_sequences_by_name(subhog_conf): + # characterises current behaviour: sequences are looked up by record.name, not record.id + # (_utils_frag_SO_detection.py:142) + aln = msa((H1, "AAAA------"), (H2, "------AAAA")) + assert find_prot_dubious_msa(aln, subhog_conf()) == ([[H1, H2]], [[]]) + + +def test_find_prot_dubious_msa_none(subhog_conf): + assert find_prot_dubious_msa(msa((H1, "AAAA"), (M1, "AAAA")), subhog_conf()) == ([], []) + + +@pytest.mark.xfail(strict=True, reason="BUG: species names differing only in the last character are treated as one " + "species (_utils_frag_SO_detection.py:36)") +def test_find_prot_dubious_msa_distinct_species(subhog_conf): + aln = msa((pid("a", "HUMAN1", 1), "AAAA------"), (pid("b", "HUMAN2", 2), "------AAAA")) + assert find_prot_dubious_msa(aln, subhog_conf())[0] == [] diff --git a/tests/test_fungi30.py b/tests/test_fungi30.py new file mode 100644 index 0000000..c7d202e --- /dev/null +++ b/tests/test_fungi30.py @@ -0,0 +1,113 @@ +"""fastoma-infer-subhogs on rootHOGs selected from the fungi-30 run (see tests/fungi30.py). + +golden families: the output must equal the stored snapshot (only in the snapshot's tool environment). +drops families: known to drop proteins; the invariant test is a strict xfail until that is fixed. +All families: every input protein must end up in a final HOG. + +Set FASTOMA_REQUIRE_GOLDEN=1 (as CI does) to fail instead of skip when the local mafft/FastTree differ +from the snapshot environment. +""" +import difflib +import os + +import pytest + +import fungi30 as F + +pytestmark = pytest.mark.tools("mafft", "FastTree") + +FAMILIES = F.families() + + +def _params(kind=None, xfail_drops=False): + params = [] + for fam in FAMILIES: + if kind and fam["kind"] != kind: + continue + marks = [pytest.mark.slow] if fam["tier"] == "slow" else [] + if xfail_drops and fam["kind"] == "drops": + marks.append(pytest.mark.xfail(strict=True, reason="BUG: fastoma-infer-subhogs drops proteins of this " + "rootHOG: " + fam["reason"])) + params.append(pytest.param(fam["rhog"], id=fam["rhog"], marks=marks)) + return params + + +_runs = {} + + +@pytest.fixture(scope="module") +def run_of(tmp_path_factory): + """work dir of the (cached) fastoma-infer-subhogs run that contains the given rootHOG.""" + by_rhog = {r: key for key, rhogids in F.groups().items() for r in rhogids} + + def get(rhogid): + key = by_rhog[rhogid] + if key not in _runs: + workdir = tmp_path_factory.mktemp("fungi30_{}_{}".format(*key)) + F.prepare_input(F.groups()[key], workdir / "in") + F.run_infer_subhogs(workdir / "in", workdir, parallel=key[1] == "par") + _runs[key] = workdir + return _runs[key] + yield get + _runs.clear() + + +@pytest.fixture(scope="module") +def golden_env(): + expected, actual = F.golden_environment(), F.tool_versions() + expected_tools = {k: v for k, v in expected.items() if k in actual} + if expected_tools != actual: + msg = (f"tool versions {actual} differ from the golden snapshot environment {expected}; " + "regenerate with `python tests/fungi30.py regenerate` if this is intended") + if os.environ.get("FASTOMA_REQUIRE_GOLDEN"): + pytest.fail(msg) + pytest.skip(msg) + + +@pytest.mark.parametrize("rhogid", _params(xfail_drops=True)) +def test_no_protein_dropped(run_of, rhogid): + members = F.final_members(run_of(rhogid), rhogid) + assert sorted(members) == sorted(F.input_records(rhogid)) + + +@pytest.mark.parametrize("rhogid", _params(kind="golden")) +def test_matches_golden_snapshot(golden_env, run_of, rhogid): + actual = F.golden_text(F.output_groups(run_of(rhogid), rhogid), F.input_records(rhogid)) + expected = F.golden_path(rhogid).read_text() + if actual != expected: + diff = "".join(difflib.unified_diff(expected.splitlines(True), actual.splitlines(True), + "golden", "actual", n=2)) + pytest.fail(f"output of {rhogid} differs from tests/data/fungi30/golden/{rhogid}.txt:\n{diff[:6000]}") + + +def test_every_family_has_input_and_golden_snapshot(): + for fam in FAMILIES: + assert (F.DATA / "rhogs" / f"HOG_{fam['rhog']}.fa.gz").exists(), fam["rhog"] + assert F.golden_path(fam["rhog"]).exists() == (fam["kind"] == "golden"), fam["rhog"] + + +def test_partial_regeneration_rejects_other_tool_versions(monkeypatch): + class Result: + stdout = "v7.526 (2024/Apr/26)\nDetailed usage for FastTree 2.1.11 Double precision (No SSE3):\n" + monkeypatch.setattr(F, "_docker_run", lambda *args, **kwargs: Result()) + before = F.ENVIRONMENT_FILE.read_text() + with pytest.raises(SystemExit, match="--tier all"): + F.regenerate("fast", "image-with-other-tools") + assert F.ENVIRONMENT_FILE.read_text() == before + + +def test_container_command_survives_paths_with_spaces(tmp_path): + import subprocess + input_dir = tmp_path / "FastOMA checkout" / "in" + cmd = F.container_command(input_dir, parallel=True) + # let sh parse the command exactly as `sh -c` in the container does, and print the resulting argv + res = subprocess.run(["sh", "-c", 'set -- ' + cmd + '; printf "%s\\n" "$@"'], capture_output=True, text=True, + check=True) + assert res.stdout.splitlines() == ["/app/bin/python", "-c", F.RUNNER] + \ + F.infer_subhogs_args(input_dir, F.SPECIES_TREE, True) + + +def test_parse_tool_versions(): + v = F.parse_tool_versions("v7.505 (2022/Apr/10)", + "Detailed usage for FastTree 2.1.11 Double precision (No SSE3), OpenMP (10 threads):") + assert v == {"mafft": "v7.505 (2022/Apr/10)", "FastTree": "2.1.11 Double precision (No SSE3), OpenMP"} diff --git a/tests/test_gene_tree_rooting.py b/tests/test_gene_tree_rooting.py new file mode 100644 index 0000000..9305a47 --- /dev/null +++ b/tests/test_gene_tree_rooting.py @@ -0,0 +1,129 @@ +import random + +import pytest +from ete3 import Tree +from ete3.parser.newick import NewickError + +from FastOMA import _wrappers +from FastOMA._infer_subhog import LevelHOGProcessor +from FastOMA._utils_subhog import find_outlier_leaves, midpoint_rooting_outgroup +from subhog_helpers import species_tree, leaf_hog + +P1 = "sp|P1|G3P_MYCGE||MYCGE||1000000005" +Q2 = "tr|Q2|X_Y_AQUAE||AQUAE||1001" +P3 = "sp|P3|Z_CHLTR||CHLTR||1002" +P4 = "sp|P4|W_CHLTR||CHLTR||1003" +IDS = [P1, Q2, P3, P4] +SPECIES_NWK = "(MYCGE,(AQUAE,CHLTR)AC)root;" +# unrooted (trifurcating) FastTree-like tree; longest path P4 - Q2 = 3.55 +UNROOTED = "('%s':0.1,'%s':0.5,('%s':0.2,'%s':3.0):0.05);" % tuple(IDS) + + +@pytest.fixture +def make_lp(subhog_conf): + def make(method): + sp = species_tree(SPECIES_NWK) + hogs = [leaf_hog(i, sp) for i in IDS] + return LevelHOGProcessor(sp, hogs, "r", subhog_conf(gene_rooting_method=method)) + return make + + +def gtree(nwk=UNROOTED): + return Tree(nwk, format=0, quoted_node_names=True) + + +def root_split(t): + return sorted(sorted(c.get_leaf_names()) for c in t.children) + + +@pytest.mark.parametrize("method", ["midpoint", "midpoint-dendropy"]) +def test_midpoint_rooting(make_lp, method): + t = make_lp(method).infer_rooted_genetree(gtree()) + assert sorted(t.get_leaf_names()) == sorted(IDS) + assert len(t.children) == 2 + assert root_split(t) == [[P1, P3, Q2], [P4]] + + +def test_midpoint_ete3_splits_root_edge_in_half(make_lp): + t = make_lp("midpoint").infer_rooted_genetree(gtree()) + # characterises current behaviour: ete3 places the root in the middle of the edge, not at the + # true midpoint (1.775 from P4); this affects the representative selection downstream + assert sorted(c.dist for c in t.children) == pytest.approx([1.5, 1.5]) + + +def test_midpoint_dendropy_uses_true_midpoint(make_lp): + t = make_lp("midpoint-dendropy").infer_rooted_genetree(gtree()) + assert sorted(c.dist for c in t.children) == pytest.approx([1.225, 1.775]) + + +@pytest.mark.parametrize("method", ["midpoint", "midpoint-dendropy"]) +def test_midpoint_two_leaves(make_lp, method): + t = make_lp(method).infer_rooted_genetree(gtree("('%s':1,'%s':2);" % (P1, Q2))) + assert sorted(t.get_leaf_names()) == sorted([P1, Q2]) + assert sorted(c.dist for c in t.children) == pytest.approx([1.5, 1.5]) + + +def test_midpoint_zero_branch_lengths(make_lp): + t = make_lp("midpoint").infer_rooted_genetree(gtree("('%s':0,'%s':0,'%s':0);" % (P1, Q2, P3))) + assert sorted(t.get_leaf_names()) == sorted([P1, Q2, P3]) + assert len(t.children) == 2 + + +def test_outlier_rooting_ignores_long_branch(make_lp): + random.seed(1) + t = make_lp("outlier").infer_rooted_genetree(gtree()) + assert sorted(t.get_leaf_names()) == sorted(IDS) + # P4 is excluded from the midpoint computation, so the root moves away from its branch + assert root_split(t) == [[P1, P3, P4], [Q2]] + + +def test_nevers_rooting_reads_species_tree_from_cwd(make_lp, in_tmp_dir): + # characterises current behaviour: the species tree is read from ./species_tree.nwk (_infer_subhog.py:507) + with pytest.raises(NewickError): + make_lp("Nevers_rooting").infer_rooted_genetree(gtree()) + (in_tmp_dir / "species_tree.nwk").write_text(SPECIES_NWK + "\n") + t = make_lp("Nevers_rooting").infer_rooted_genetree(gtree()) + assert sorted(t.get_leaf_names()) == sorted(IDS) + assert len(t.children) == 2 + + +@pytest.mark.xfail(strict=True, reason="BUG: mad rooting passes the TreeNode, but _wrappers.mad_rooting expects a " + "file path (_infer_subhog.py:512 vs _wrappers.py:138)") +def test_mad_rooting_gets_a_file(make_lp, monkeypatch): + seen = [] + monkeypatch.setattr(_wrappers, "mad_rooting", lambda arg: seen.append(arg) or gtree()) + make_lp("mad").infer_rooted_genetree(gtree()) + assert isinstance(seen[0], str) + + +def test_invalid_rooting_method(make_lp): + with pytest.raises(ValueError): + make_lp("bogus").infer_rooted_genetree(gtree()) + + +# outlier helpers + +def test_find_outlier_leaves_long_branch(): + t = Tree("((a:1,b:1):1,(c:1,(d:1,e:30):1):1);") + assert [n.name for n in find_outlier_leaves(t)] == ["e"] + + +def test_find_outlier_leaves_balanced_tree(): + t = Tree("((a:1,b:1):1,(c:1,d:1):1);") + assert find_outlier_leaves(t) == [] + + +@pytest.mark.parametrize("seed", [0, 1, 2]) +def test_midpoint_rooting_outgroup_with_excluded_leaf(seed): + random.seed(seed) + t = Tree("((a:1,b:1):1,(c:1,(d:1,e:30):1):1);") + og = midpoint_rooting_outgroup(t, leaves_to_exclude=[t & "e"]) + t.set_outgroup(og) + assert root_split(t) == [["a", "b"], ["c", "d", "e"]] + + +def test_midpoint_rooting_outgroup_two_leaves(): + random.seed(0) + t = Tree("(a:1,b:1);") + og = midpoint_rooting_outgroup(t) + assert og.name in ("a", "b") diff --git a/tests/test_hog_class.py b/tests/test_hog_class.py new file mode 100644 index 0000000..1b1c837 --- /dev/null +++ b/tests/test_hog_class.py @@ -0,0 +1,309 @@ +import xml.etree.ElementTree as ET + +import pytest + +from FastOMA._hog_class import HOG, Representative, split_hog, ScoreFlags +from subhog_helpers import pid, rec, msa, species_tree, leaf_hog, merged_hog, leaf_members + +NO_SCORES = ScoreFlags(False, False, False) + +A1, A2, A3 = pid("a1", "A", 1), pid("a2", "A", 2), pid("a3", "A", 3) +B1, B2 = pid("b1", "B", 11), pid("b2", "B", 12) +C1 = pid("c1", "C", 21) + + +@pytest.fixture +def sp(): + return species_tree() + + +def test_leaf_hog(sp): + h = leaf_hog(A1, sp) + assert h.get_members() == {A1} + assert len(h) == 1 + assert h.taxname == "A" and h.taxlevel is sp & "A" + assert h.hogid == "HOG_r_sub10001" + assert [r.get_id() for r in h.get_representatives()] == [A1] + assert [r.id for r in h.get_msa()] == [A1] + assert h._subhogs == [] and h.get_dubious_members() == set() + assert h.active + + +def test_hogids_are_sequential_per_process(sp): + ids = [leaf_hog(x, sp).hogid for x in (A1, A2)] + assert ids == ["HOG_r_sub10001", "HOG_r_sub10002"] + + +def test_merged_hog_members_are_union_of_subhogs(sp): + subs = [leaf_hog(A1, sp), leaf_hog(B1, sp), leaf_hog(C1, sp)] + h = merged_hog(subs, sp, rep_ids=[A1, C1]) + assert h.get_members() == {A1, B1, C1} == leaf_members(h) + assert h._subhogs == subs + # the HOG keeps only the representatives' rows of the MSA + assert sorted(r.id for r in h.get_msa()) == [A1, C1] + assert A1 in h and h.get_representatives()[0] in h and subs[1] in h + assert "x||A||9" not in h + + +def test_get_members_returns_a_copy(sp): + h = leaf_hog(A1, sp) + h.get_members().add("x") + assert h.get_members() == {A1} + + +def test_merged_hog_rejects_duplicate_representatives(sp): + subs = [leaf_hog(A1, sp), leaf_hog(B1, sp)] + with pytest.raises(AssertionError, match="Non-unique"): + HOG(subs, sp, "r", msa=msa(rec(A1), rec(B1)), representatives=[Representative(rec(A1)), Representative(rec(A1))]) + + +def test_merged_hog_rejects_representative_not_member(sp): + subs = [leaf_hog(A1, sp), leaf_hog(B1, sp)] + with pytest.raises(AssertionError, match="subset"): + HOG(subs, sp, "r", msa=msa(rec(A1), rec(B1), rec(C1)), representatives=[Representative(rec(C1))]) + + +def test_merged_hog_with_empty_msa_is_rejected(sp): + # characterises current behaviour: an empty MSA is falsy, so the constructor hits its generic error branch + with pytest.raises(AssertionError): + HOG([leaf_hog(A1, sp)], sp, "r", msa=msa(), representatives=[Representative(rec(A1))]) + + +def test_merged_hog_without_representatives_argument_fails(sp): + # characterises current behaviour: the default of `representatives` is a typing object + # (_hog_class.py:280), so callers always have to pass it + with pytest.raises(TypeError): + HOG([leaf_hog(A1, sp)], sp, "r", msa=msa(rec(A1))) + + +@pytest.mark.xfail(strict=True, reason="BUG: HOG.__init__ column filter can drop all representative rows " + "(_hog_class.py:333 -> _utils_subhog.py:485); the HOG is then lost at the next level") +def test_merged_hog_column_filter_keeps_representatives(sp, subhog_conf): + s1 = "A" * 300 + "-" * 300 + s2 = "-" * 300 + "A" * 300 + subs = [leaf_hog(A1, sp, seq=s1), leaf_hog(B1, sp, seq=s2)] + h = merged_hog(subs, sp, conf=subhog_conf()) + assert len(h) == 2 + assert len(h.get_msa()) == 2 + + +def test_merged_hog_column_filter_on_wide_msa(sp, subhog_conf): + s1 = "A" * 550 + "-" * 50 + s2 = "A" * 600 + subs = [leaf_hog(A1, sp, seq=s1), leaf_hog(B1, sp, seq=s2)] + h = merged_hog(subs, sp, conf=subhog_conf()) + # columns with gap ratio 0.5 are removed (threshold is strict) + assert [len(r) for r in h.get_msa()] == [550, 550] + + +def test_get_subhog_path(sp): + a1, b1, c1 = leaf_hog(A1, sp), leaf_hog(B1, sp), leaf_hog(C1, sp) + ab = merged_hog([a1, b1], sp & "AB") + top = merged_hog([ab, c1], sp) + assert top.get_subhog_path(B1) == [ab, b1] + assert top.get_subhog_path(B1, max_depth=1) == [ab] + assert top.get_subhog_path(C1) == [c1] + with pytest.raises(KeyError): + top.get_subhog_path("x||A||9") + + +# Representative + +def test_representative_from_seqrecord(): + r = Representative(rec(A1)) + assert r.get_id() == A1 + assert r.get_subelements() == {A1} + assert r.get_species() == {"A"} + assert r.enabled + + +def test_representative_with_elements_collects_subelements_and_species(): + a, b = Representative(rec(A1)), Representative(rec(B1)) + r = Representative(a, [a, b]) + assert r.get_id() == A1 + assert r.get_subelements() == {A1, B1} + assert r.get_species() == {"A", "B"} + + +@pytest.mark.xfail(strict=True, reason="BUG: Representative(rep, elements) mutates rep's subelement/species sets in place " + "(_hog_class.py:249-259 alias the sets)") +def test_representative_with_elements_does_not_mutate_source(): + a, b = Representative(rec(A1)), Representative(rec(B1)) + Representative(a, [a, b]) + assert a.get_subelements() == {A1} + assert a.get_species() == {"A"} + + +# find_representative + +def test_find_representative_by_id(sp): + h = merged_hog([leaf_hog(A1, sp), leaf_hog(B1, sp)], sp) + assert h.find_representative(B1).get_id() == B1 + + +@pytest.mark.xfail(strict=True, raises=AttributeError, + reason="BUG: find_representative calls the non-existent Representative.get_members (_hog_class.py:383)") +def test_find_representative_of_non_representative_member(sp): + a1, b1 = leaf_hog(A1, sp), leaf_hog(B1, sp) + rep = Representative(rec(A1), [Representative(rec(B1))]) + h = HOG([a1, b1], sp, "r", msa=msa(rec(A1), rec(B1)), representatives=[rep]) + assert h.find_representative(B1) is rep + + +# split_hog + +def _two_level_hog(sp): + """root HOG = [AB HOG (a1, b1; only a1 is a representative), c1].""" + a1, b1, c1 = leaf_hog(A1, sp), leaf_hog(B1, sp), leaf_hog(C1, sp) + ab = merged_hog([a1, b1], sp & "AB", rep_ids=[A1], aln=msa(rec(A1), rec(B1), rec(C1))) + top = merged_hog([ab, c1], sp, rep_ids=[A1, C1], aln=msa(rec(A1), rec(B1), rec(C1))) + return top, ab, c1 + + +def test_split_hog_on_disjoint_first_level_subhogs(sp): + top, ab, c1 = _two_level_hog(sp) + parts = split_hog(top, "root", [A1], [C1]) + assert len(parts) == 2 + assert [h.get_members() for h in parts] == [{A1, B1}, {C1}] + assert [h._subhogs for h in parts] == [[ab], [c1]] + assert all(h.taxlevel is top.taxlevel for h in parts) + assert [[r.get_id() for r in h.get_representatives()] for h in parts] == [[A1], [C1]] + + +def test_split_hog_requires_two_partitions(sp): + top, _, _ = _two_level_hog(sp) + with pytest.raises(ValueError): + split_hog(top, "root", [A1]) + + +def test_split_hog_rejects_overlapping_partitions(sp): + top, _, _ = _two_level_hog(sp) + with pytest.raises(ValueError): + split_hog(top, "root", [A1], [A1, C1]) + + +def test_split_hog_conflicting_partitions_returns_none(sp): + a1, b1, b2 = leaf_hog(A1, sp), leaf_hog(B1, sp), leaf_hog(B2, sp) + ab = merged_hog([a1, b1], sp & "AB") + top = merged_hog([ab, b2], sp) + # characterises current behaviour: a1 and b1 were merged below this level, so the split is + # "not implemented" and the caller (_resolve_conflicts) has to deal with it + assert split_hog(top, "root", [A1], [B1, B2]) is None + + +@pytest.mark.xfail(strict=True, reason="BUG: split_hog drops subhogs not reached by any partition's representative " + "(_hog_class.py:636-643); their proteins disappear") +def test_split_hog_keeps_all_members(sp): + a1, a2, b1 = leaf_hog(A1, sp), leaf_hog(A2, sp), leaf_hog(B1, sp) + ab = merged_hog([a1, a2, b1], sp & "AB", rep_ids=[A1, A2]) + parts = split_hog(ab, "AB", [A1], [A2]) + assert set().union(*(h.get_members() for h in parts)) == {A1, A2, B1} + + +# to_orthoxml + +def _tags(elem): + return [c.tag for c in elem] + + +def _taxrange(elem): + return [p.get("value") for p in elem.findall("property") if p.get("name") == "TaxRange"] + + +def test_to_orthoxml_nested(sp): + top, ab, c1 = _two_level_hog(sp) + e = top.to_orthoxml(sp, NO_SCORES) + assert e.tag == "orthologGroup" and e.get("id") == top.hogid + assert _taxrange(e) == ["root"] + assert _tags(e) == ["property", "orthologGroup", "geneRef"] + inner = e.find("orthologGroup") + assert inner.get("id") == ab.hogid and _taxrange(inner) == ["AB"] + assert sorted(g.get("id") for g in inner.findall("geneRef")) == ["1", "11"] + assert e.find("geneRef").get("id") == "21" + + +def test_to_orthoxml_with_scores(sp): + top, _, _ = _two_level_hog(sp) + e = top.to_orthoxml(sp) + assert [s.get("id") for s in e.findall("score")] == ["CompletenessScore", "ImpliedLosses", "TCSScore"] + assert _tags(e)[:4] == ["score", "score", "score", "property"] + + +@pytest.mark.xfail(strict=True, reason="BUG: ImpliedLosses compares species tree nodes by identity " + "(_count_untouched_clades, _hog_class.py:82-86), but the scoring tree " + "(full_species_tree) and the trees of the separately pickled levels are " + "different copies, so every nested level counts one extra loss") +@pytest.mark.parametrize("pickled", [False, True], ids=["in-memory", "pickled-per-level"]) +@pytest.mark.parametrize("present,expected_losses", [(["A", "B", "C", "D"], 0), (["A", "C"], 2)], + ids=["complete", "two-clades-lost"]) +def test_implied_losses_against_full_species_tree(present, expected_losses, pickled): + # as in read_infer_xml_rhog: HOGs live on the pruned working tree, scores use an unpruned copy, and + # infer_hogs_this_level writes each level to its own pickle file + import pickle + full = species_tree("((A,B)AB,(C,D)CD)root;") + work = full.copy() + work.prune(present) + + def level(h): + return pickle.loads(pickle.dumps([h]))[0] if pickled else h + + leaves = [level(leaf_hog(pid(s.lower(), s, i), work)) for i, s in enumerate(present, 1)] + if len(present) == 4: + top = merged_hog([level(merged_hog(leaves[:2], work & "AB")), level(merged_hog(leaves[2:], work & "CD"))], work) + else: + top = merged_hog(leaves, work) + e = level(top).to_orthoxml(full) + assert e.find("score[@id='ImpliedLosses']").get("value") == str(expected_losses) + + +def test_to_orthoxml_inparalogs_give_paralog_group(sp): + h = merged_hog([leaf_hog(A1, sp), leaf_hog(A2, sp)], sp & "A") + e = h.to_orthoxml(sp, NO_SCORES) + assert e.tag == "paralogGroup" + assert sorted(g.get("id") for g in e) == ["1", "2"] + + +def test_to_orthoxml_duplication_below_speciation(sp): + a1, a2, b1 = leaf_hog(A1, sp), leaf_hog(A2, sp), leaf_hog(B1, sp) + h = merged_hog([a1, a2, b1], sp & "AB") + e = h.to_orthoxml(sp, NO_SCORES) + assert e.tag == "orthologGroup" and _taxrange(e) == ["AB"] + assert _tags(e) == ["property", "paralogGroup", "geneRef"] + + +def test_to_orthoxml_taxrange_is_mrca_of_members(sp): + # HOG lives at the root level but only has members from A and B + h = merged_hog([leaf_hog(A1, sp), leaf_hog(B1, sp)], sp) + e = h.to_orthoxml(sp, NO_SCORES) + assert _taxrange(e) == ["AB"] + + +def test_to_orthoxml_single_subhog_is_not_wrapped(sp): + a1, b1 = leaf_hog(A1, sp), leaf_hog(B1, sp) + ab = merged_hog([a1, b1], sp & "AB") + top = merged_hog([ab], sp) + e = top.to_orthoxml(sp, NO_SCORES) + assert e.get("id") == ab.hogid + + +def test_to_orthoxml_singleton_is_generef(sp): + e = leaf_hog(A1, sp).to_orthoxml(sp, NO_SCORES) + assert e.tag == "geneRef" and e.get("id") == "1" + + +def test_to_orthoxml_merged_fragments_leaf(sp): + h = leaf_hog(A1 + "_|_" + A2, sp) + e = h.to_orthoxml(sp, NO_SCORES) + assert e.tag == "paralogGroup" + assert [(p.get("name"), p.get("value")) for p in e.findall("property")] == [("Type", "DubiousMergedfragment")] + assert [g.get("id") for g in e.findall("geneRef")] == ["1", "2"] + + +def test_to_orthoxml_every_member_once(sp): + a1, a2, b1, b2, c1 = (leaf_hog(x, sp) for x in (A1, A2, B1, B2, C1)) + ab1 = merged_hog([a1, b1], sp & "AB") + ab2 = merged_hog([a2, b2], sp & "AB") + top = merged_hog([ab1, ab2, c1], sp) + xml = ET.tostring(top.to_orthoxml(sp)) + ids = [g.get("id") for g in ET.fromstring(xml).iter("geneRef")] + assert sorted(ids) == sorted(m.split("||")[2] for m in top.get_members()) diff --git a/tests/test_level_processor.py b/tests/test_level_processor.py new file mode 100644 index 0000000..92d6f5a --- /dev/null +++ b/tests/test_level_processor.py @@ -0,0 +1,253 @@ +import pickle + +import pytest +from ete3 import Tree + +from FastOMA import _infer_subhog +from FastOMA._infer_subhog import LevelHOGProcessor +from subhog_helpers import pid, rec, msa, species_tree, leaf_hog, merged_hog, members_of + +A1, A2, A3 = pid("a1", "A", 1), pid("a2", "A", 2), pid("a3", "A", 3) +B1, B2, B3 = pid("b1", "B", 11), pid("b2", "B", 12), pid("b3", "B", 13) +C1 = pid("c1", "C", 21) + + +def q(*names): + """newick leaf names need quoting because of the '|' characters.""" + return tuple(f"'{n}'" for n in names) + + +@pytest.fixture +def sp(): + return species_tree() + + +@pytest.fixture +def make_lp(sp, subhog_conf): + def make(ids, level="AB", frozen=(), **conf): + hogs = [leaf_hog(i, sp) for i in ids] + for i in frozen: + h = leaf_hog(i, sp) + h.active = False + hogs.append(h) + return LevelHOGProcessor(sp & level, hogs, "r", subhog_conf(**conf)) + return make + + +def genetree(nwk): + return Tree(nwk, format=0, quoted_node_names=True) + + +def test_constructor_separates_frozen_hogs(make_lp): + lp = make_lp([A1, B1], frozen=[A3]) + assert {h.get_members().pop() for h in lp.subhogs.values()} == {A1, B1} + assert {h.get_members().pop() for h in lp._frozen_hogs_before} == {A3} + assert set(lp._rep_lookup) == {A1, B1} + + +def test_invalid_msa_filter_method(make_lp): + with pytest.raises(ValueError): + make_lp([A1, B1], msa_filter_method="nope") + + +# infer_reconciliation + +def test_reconciliation_labels(make_lp): + lp = make_lp([A1, A2, B1, B2]) + t = genetree("((%s,%s),(%s,%s));" % q(A1, B1, A2, B2)) + lp.infer_reconciliation(t, sos_threshold=0.1) + assert (t.evoltype, t.sos, t.so_tuple, t.name) == ("D", 1.0, (2, 2), "D1_2_2") + assert sorted((c.name, c.evoltype, c.sos) for c in t.children) == [("S1", "S", 0.0), ("S2", "S", 0.0)] + leaf = t & A1 + assert leaf.hogid == lp._rep_lookup[A1].hog.hogid + # species sets are only kept on the leaves + assert not hasattr(t, "species") and not hasattr(t.children[0], "species") + + +@pytest.mark.parametrize("threshold,expected", [(1 / 3, "S"), (0.3, "D")]) +def test_reconciliation_threshold_is_exclusive(make_lp, threshold, expected): + lp = make_lp([A1, A2, B1, C1], level="root") + # root: {A,B} vs {A,C} -> sos = 1/3 + t = genetree("((%s,%s),(%s,%s));" % q(A1, B1, A2, C1)) + lp.infer_reconciliation(t, sos_threshold=threshold) + assert t.sos == pytest.approx(1 / 3) + assert t.evoltype == expected + + +@pytest.mark.xfail(strict=True, reason="BUG: species overlap of a multifurcation intersects all children, " + "so a polytomy with two copies of a species is labelled S (_infer_subhog.py:546)") +def test_reconciliation_polytomy_with_duplicated_species(make_lp): + lp = make_lp([A1, A2, B1]) + t = genetree("(%s,%s,%s);" % q(A1, A2, B1)) + lp.infer_reconciliation(t, sos_threshold=0.1) + assert t.evoltype == "D" + + +# merge_subhogs / process + +def _reconciled(lp, nwk): + t = genetree(nwk) + lp.infer_reconciliation(t, sos_threshold=lp.conf.threshold_dubious_sd) + return t + + +def test_merge_subhogs_two_orthogroups(make_lp, sp): + lp = make_lp([A1, A2, B1, B2]) + t = _reconciled(lp, "((%s:1,%s:1):1,(%s:1,%s:1):1);" % q(A1, B1, A2, B2)) + new = lp.merge_subhogs(t, msa(rec(A1), rec(A2), rec(B1), rec(B2))) + assert sorted(sorted(h.get_members()) for h in new) == [[A1, B1], [A2, B2]] + assert all(h.taxlevel is sp & "AB" for h in new) + for h in new: + assert {r.get_id() for r in h.get_representatives()} <= h.get_members() + + +def test_merge_subhogs_conserves_members(make_lp): + ids = [A1, A2, A3, B1, B2] + lp = make_lp(ids) + t = _reconciled(lp, "(((%s:1,%s:1):1,%s:2):1,(%s:1,%s:1):1);" % q(A1, B1, A3, A2, B2)) + new = lp.merge_subhogs(t, msa(*(rec(i) for i in ids))) + assert members_of(new) == set(ids) + assert sum(len(h) for h in new) == len(ids) + + +def _patch_pipeline(lp, monkeypatch, aln, tree_nwk=None): + monkeypatch.setattr(lp, "align_subhogs", lambda: aln) + if tree_nwk is not None: + monkeypatch.setattr(lp, "infer_genetree_from_msa", lambda m: genetree(tree_nwk)) + else: + def fail(m): + raise AssertionError("gene tree should not be inferred") + monkeypatch.setattr(lp, "infer_genetree_from_msa", fail) + + +def test_process_keeps_frozen_hogs(make_lp, monkeypatch): + lp = make_lp([A1, A2, B1, B2], frozen=[A3]) + aln = msa(rec(A1), rec(A2), rec(B1), rec(B2)) + _patch_pipeline(lp, monkeypatch, aln, "((%s:1,%s:1):1,(%s:1,%s:1):1);" % q(A1, B1, A2, B2)) + new = lp.process() + assert members_of(new) == {A1, A2, A3, B1, B2} + assert sorted(sorted(h.get_members()) for h in new) == [[A1, B1], [A2, B2], [A3]] + + +def _all_gappy_msa(): + # every column has a gap ratio of 0.5 -> column filter removes all columns + return msa((A1, "AAAA----"), (B1, "----AAAA"), (A2, "AAAA----"), (B2, "----AAAA")) + + +def test_process_when_filter_empties_msa_returns_unmerged_subhogs(make_lp, monkeypatch): + lp = make_lp([A1, A2, B1, B2], min_col_trim=0) + active = list(lp.subhogs.values()) + _patch_pipeline(lp, monkeypatch, _all_gappy_msa()) + # characterises current behaviour: the whole level becomes a no-op (seen on real data, e.g. D0040139) + assert lp.process() == active + + +@pytest.mark.xfail(strict=True, reason="BUG: process() returns early without the frozen HOGs when filtering empties " + "the MSA (_infer_subhog.py:690); their proteins are lost") +def test_process_when_filter_empties_msa_keeps_frozen_hogs(make_lp, monkeypatch): + lp = make_lp([A1, A2, B1, B2], frozen=[A3], min_col_trim=0) + _patch_pipeline(lp, monkeypatch, _all_gappy_msa()) + assert members_of(lp.process()) == {A1, A2, A3, B1, B2} + + +def test_filter_msa_freezes_hogs_without_representatives(make_lp): + lp = make_lp([A1, A2, B1, B2], min_col_trim=0) + b2_hog = lp._rep_lookup[B2].hog + aln = msa((A1, "ACDEFGHIKL"), (A2, "ACDEFGHIKL"), (B1, "ACDEFGHIKL"), (B2, "A---------")) + filtered = lp.filter_msa(aln) + assert [r.id for r in filtered] == [A1, A2, B1] + assert b2_hog.hogid not in lp.subhogs and not b2_hog.active + assert b2_hog in lp._frozen_hogs_before + assert B2 not in lp._rep_lookup + + +# find_most_divergent_representatives_from_genetree + +DIVERGENT_TREE = "(((%s:1,%s:1.2):0.5,%s:3):0.2,((%s:0.4,%s:0.8):2,%s:1):0.3);" + + +@pytest.mark.parametrize("n", [1, 2, 3, 5]) +def test_representatives_invariants(make_lp, n): + ids = [A1, B1, C1, A2, B2, B3] + lp = make_lp(ids, level="root", number_of_samples_per_hog=n) + t = genetree(DIVERGENT_TREE % q(*ids)) + reps = lp.find_most_divergent_representatives_from_genetree(t) + rep_ids = [r.get_id() for r in reps] + assert 1 <= len(reps) <= n + assert len(set(rep_ids)) == len(rep_ids) + assert set(rep_ids) <= set(ids) + assert set().union(*(r.get_subelements() for r in reps)) == set(ids) + + +def test_representatives_small_tree_keeps_all_leaves(make_lp): + ids = [A1, B1, C1] + lp = make_lp(ids, level="root", number_of_samples_per_hog=5) + t = genetree("((%s:1,%s:1):1,%s:1);" % q(*ids)) + reps = lp.find_most_divergent_representatives_from_genetree(t) + assert sorted(r.get_id() for r in reps) == sorted(ids) + + +def test_representatives_do_not_modify_input_tree(make_lp): + ids = [A1, B1, C1, A2, B2, B3] + lp = make_lp(ids, level="root", number_of_samples_per_hog=2) + t = genetree(DIVERGENT_TREE % q(*ids)) + before = t.write() + lp.find_most_divergent_representatives_from_genetree(t) + assert t.write() == before + + +# read_infer_xml_rhog: wrapping of root-level paralogGroups + +@pytest.fixture +def rhog_run(in_tmp_dir, subhog_conf, monkeypatch): + ids = [A1, A2, A3, B1, C1] + with open("HOG_r.fa", "w") as fh: + for i in ids: + fh.write(f">{i}\nACDEFGHIKL\n") + with open("species_tree.nwk", "w") as fh: + fh.write("((A,B)AB,C)root;\n") + + def fake_inference(sptree, rhogid, pickles_subhog_folder_all, rhogs_fa_folder, conf): + a1, a2, a3, b1, c1 = (leaf_hog(i, sptree) for i in ids) + # the in-paralogs a1, a2 end up at the root level (as when nothing merges with them) + hogs = [merged_hog([a1, a2], sptree), merged_hog([b1, c1], sptree), a3] + with open(f"{pickles_subhog_folder_all}/rhog_{rhogid}/{sptree.name}.pickle", "wb") as fh: + pickle.dump(hogs, fh) + + monkeypatch.setattr(_infer_subhog, "infer_hogs_for_rhog_levels_recursively", fake_inference) + (in_tmp_dir / "out").mkdir() + n = _infer_subhog.read_infer_xml_rhog("r", False, "out", str(in_tmp_dir), str(in_tmp_dir), + subhog_conf(species_tree="species_tree.nwk", v=0)) + with open("out/file_r.pickle", "rb") as fh: + return n, pickle.load(fh) + + +def test_read_infer_xml_rhog_skips_singletons(rhog_run, in_tmp_dir): + n, groups = rhog_run + assert n == len(groups) == 2 + ids = sorted(g.get("id") for e in groups for g in e.iter("geneRef")) + assert ids == ["1", "11", "2", "21"] + # the per-level pickles are removed again + assert not (in_tmp_dir / "rhog_r").exists() + + +def test_read_infer_xml_rhog_wraps_root_paralog_group(rhog_run): + _, groups = rhog_run + wrapped = groups[0] + assert wrapped.tag == "orthologGroup" + assert [c.tag for c in wrapped if c.tag not in ("score", "property")] == ["paralogGroup"] + + +@pytest.mark.xfail(strict=True, reason="BUG: the root-level wrapper uses the HOG's taxonomic level as TaxRange instead of " + "the MRCA of its members like HOG.to_orthoxml (_infer_subhog.py:120)") +def test_read_infer_xml_rhog_wrapper_taxrange_is_mrca(rhog_run): + _, groups = rhog_run + assert [p.get("value") for p in groups[0].findall("property") if p.get("name") == "TaxRange"] == ["A"] + + +@pytest.mark.xfail(strict=True, reason="BUG: a root-level paralogGroup is wrapped in an orthologGroup with a single " + "child (_infer_subhog.py:112-122); orthoXML groups need >= 2 children") +def test_read_infer_xml_rhog_groups_have_two_children(rhog_run): + _, groups = rhog_run + for e in groups: + assert len([c for c in e if c.tag not in ("score", "property")]) >= 2 diff --git a/tests/test_msa_filter.py b/tests/test_msa_filter.py new file mode 100644 index 0000000..f2d318e --- /dev/null +++ b/tests/test_msa_filter.py @@ -0,0 +1,124 @@ +import pytest + +from FastOMA._utils_subhog import MSAFilter, MSAFilterElbow, MSAFilterTrimAL +from subhog_helpers import pid, msa + +A1, B1, C1, D1 = pid("a1", "A", 1), pid("b1", "B", 2), pid("c1", "C", 3), pid("d1", "D", 4) + + +@pytest.fixture +def flt(subhog_conf): + return MSAFilter(None, subhog_conf(min_col_trim=0)) + + +def seqs(aln): + return [str(r.seq) for r in aln] + + +def test_defaults_from_conf(subhog_conf): + f = MSAFilter(None, subhog_conf()) + assert (f.gap_ratio_row, f.gap_ratio_col, f.min_col_trim) == (0.3, 0.5, 50) + + +def test_gap_ratios_count_all_gap_symbols(flt): + assert list(flt._get_gap_ratios(msa((A1, "-.~?XA"), (B1, "AAAAAA")))) == [0.5, 0.5, 0.5, 0.5, 0, 0] + + +def test_column_threshold_is_exclusive(flt): + # gap ratio exactly 0.5 is removed + assert seqs(flt.msa_filter_col(msa((A1, "A-A"), (B1, "AAA")))) == ["AA", "AA"] + + +def test_column_filter_keeps_msa_without_gappy_columns(flt): + aln = msa((A1, "ACD"), (B1, "AC-"), (C1, "ACD")) + assert flt.msa_filter_col(aln) is aln + + +def test_row_threshold_is_min_nongap_fraction(flt): + # gap_ratio_row is the minimal fraction of non-gap characters; exactly 0.3 is kept + aln = msa((A1, "AAA-------"), (B1, "AA--------")) + assert [r.id for r in flt.msa_filter_row(aln)] == [A1] + + +def test_filter_msa_reports_removed_rows(flt): + aln = msa((A1, "ACDEFGHIKL"), (B1, "ACDEFGHIKL"), (C1, "A---------"), (D1, "ACDEFGHIKL")) + filtered, removed = flt.filter_msa(aln) + assert [r.id for r in filtered] == [A1, B1, D1] + assert removed == {C1} + + +def test_filter_msa_removes_empty_columns_after_row_filter(subhog_conf): + f = MSAFilter(None, subhog_conf(min_col_trim=0, gap_ratio_col=0.7, gap_ratio_row=0.9)) + E1 = pid("e1", "E", 5) + aln = msa((A1, "ACDEFGHIKL-"), (B1, "ACDEFGHIKL-"), (C1, "A---------W"), (D1, "A---------W"), (E1, "A---------W")) + # the last column survives the column filter, but only the removed rows have residues there + filtered, removed = f.filter_msa(aln) + assert seqs(filtered) == ["ACDEFGHIKL", "ACDEFGHIKL"] + assert removed == {C1, D1, E1} + + +def test_filter_msa_skips_short_alignments(subhog_conf): + f = MSAFilter(None, subhog_conf(min_col_trim=5)) + aln = msa((A1, "A---"), (B1, "AAAA")) + filtered, removed = f.filter_msa(aln) + assert filtered is aln and removed == set() + + +def test_filter_msa_single_sequence(flt): + filtered, removed = flt.filter_msa(msa((A1, "AA--AA"))) + assert seqs(filtered) == ["AAAA"] and removed == set() + + +def test_filter_msa_without_gaps_is_unchanged(flt): + aln = msa((A1, "ACDE"), (B1, "ACDE")) + filtered, removed = flt.filter_msa(aln) + assert seqs(filtered) == ["ACDE", "ACDE"] and removed == set() + + +def test_column_filter_with_single_column_left_drops_rows(flt): + # characterises current behaviour: records shorter than 2 columns are dropped (_utils_subhog.py:485) + assert len(flt.msa_filter_col(msa((A1, "AA-"), (B1, "-AA")))) == 0 + + +@pytest.mark.xfail(strict=True, reason="BUG: rows dropped by the column filter are not reported as removed " + "(_utils_subhog.py:485 and :456)") +def test_filter_msa_all_columns_gappy_reports_rows(flt): + aln = msa((A1, "AA--"), (B1, "--AA")) + filtered, removed = flt.filter_msa(aln) + assert {r.id for r in filtered} | removed == {A1, B1} + + +def test_filter_msa_empty_msa(flt): + # characterises current behaviour: an empty MSA is not handled + with pytest.raises(IndexError): + flt.filter_msa(msa()) + + +# MSAFilterElbow + +def test_elbow_without_gaps_keeps_everything(subhog_conf): + f = MSAFilterElbow(None, subhog_conf(min_col_trim=0)) + assert seqs(f.msa_filter_col(msa((A1, "ACDE"), (B1, "ACDE")))) == ["ACDE", "ACDE"] + + +def test_elbow_never_uses_threshold_below_gap_ratio_col(subhog_conf): + f = MSAFilterElbow(None, subhog_conf(min_col_trim=0, gap_ratio_col=0.5)) + aln = msa((A1, "ACDEFGHI"), (B1, "ACDEFG--"), (C1, "ACDEF---"), (D1, "ACDE----")) + # gap ratios: 0,0,0,0,0.25,0.5,0.75,0.75 -> at least everything below 0.5 is kept + assert len(f.msa_filter_col(aln)[0]) >= 5 + + +def test_elbow_removes_all_gap_columns(subhog_conf): + f = MSAFilterElbow(None, subhog_conf(min_col_trim=0)) + aln = msa((A1, "AAA-"), (B1, "AAA-"), (C1, "AAA-")) + assert seqs(f.msa_filter_col(aln)) == ["AAA"] * 3 + + +# MSAFilterTrimAL + +@pytest.mark.tools("trimal") +def test_trimal_filter_reports_removed_sequences(subhog_conf): + f = MSAFilterTrimAL(None, subhog_conf()) + aln = msa(*((pid(f"p{i}", "S", i), "MKVLAAGIVGLLLAAPLAQAKTEEAPKAEEGHHHH") for i in range(4))) + filtered, removed = f.filter_msa(aln) + assert {r.id for r in filtered} | removed == {r.id for r in aln} diff --git a/tests/test_pairwise_relations.py b/tests/test_pairwise_relations.py new file mode 100644 index 0000000..6317fba --- /dev/null +++ b/tests/test_pairwise_relations.py @@ -0,0 +1,138 @@ +"""Tests for pairwise relation extraction (fastoma-helper pw-rel, FastOMA/zoo/hog/transform.py).""" +import gzip +import io +import subprocess +import sys +from itertools import combinations, product + +import dendropy +import pytest +from lxml import etree + +from FastOMA import collect_subhogs as cs +from FastOMA.zoo.hog import transform +from FastOMA.transformer import header_transformer +from conftest import TESTDATA_DIR +from fastoma_testutils import read_pairs +import output_fixtures as fx +from output_fixtures import prot_id + +EXPECTED = TESTDATA_DIR / "expected_output" + + +def _cross(xs, ys): + return {frozenset((prot_id(x), prot_id(y))) for x, y in product(xs, ys)} + + +def _within(xs): + return {frozenset((prot_id(x), prot_id(y))) for x, y in combinations(xs, 2)} + + +EXPECTED_ORTHOLOGS = ( + set().union(*(_cross([f"a{i}"], [f"b{i}"]) for i in "1234")) + | _cross(["c1"], ["a1", "b1", "a2", "b2"]) + | _cross(["c2"], ["a3", "b3", "a4", "b4"]) + | _cross(["d1"], ["a1", "b1", "a2", "b2", "a3", "b3", "a4", "b4", "c1", "c2"]) + | _cross(["a5"], ["b5"]) + | _cross(["a6", "a7"], ["b6"]) +) +EXPECTED_PARALOGS = ( + _cross(["a1", "b1"], ["a2", "b2"]) + | _cross(["a3", "b3"], ["a4", "b4"]) + | _cross(["a1", "b1", "a2", "b2", "c1"], ["a3", "b3", "a4", "b4", "c2"]) + | _within(["a6", "a7"]) # fragment paralogGroup + | _cross(["a5", "b5"], ["a6", "a7", "b6"]) +) + + +@pytest.fixture +def fixture_orthoxml(tmp_path): + inputs = fx.build_collect_inputs(tmp_path / "in") + out = tmp_path / "hogs.orthoxml" + cs.write_hog_orthoxml(str(inputs["pickle_folder"]), str(out), inputs["gene_id_pickle"], + header_transformer("noop"), str(inputs["species_tree"])) + return out + + +def _pairs(orthoxml, rel_type, **kw): + if not isinstance(orthoxml, etree._ElementTree): + orthoxml = etree.parse(str(orthoxml)) + return list(transform.iter_pairwise_relations(orthoxml, rel_type=rel_type, **kw)) + + +def _xml(groups, genes=''): + return etree.parse(io.BytesIO( + f'' + f'{genes}' + f'{groups}'.encode())) + + +class TestOrthoxmlRelations: + def test_orthologs(self, fixture_orthoxml): + pairs = _pairs(fixture_orthoxml, "ortholog", id_attribute="protId") + assert len(pairs) == 25 + assert {frozenset(p) for p in pairs} == EXPECTED_ORTHOLOGS + + def test_paralogs(self, fixture_orthoxml): + pairs = _pairs(fixture_orthoxml, "paralog", id_attribute="protId") + assert len(pairs) == 40 + assert {frozenset(p) for p in pairs} == EXPECTED_PARALOGS + + def test_orthologs_and_paralogs_are_disjoint(self): + assert not EXPECTED_ORTHOLOGS & EXPECTED_PARALOGS + + def test_default_is_ortholog_and_internal_ids(self, fixture_orthoxml): + pairs = {frozenset(p) for p in _pairs(fixture_orthoxml, None)} + assert frozenset(("1005", "2005")) in pairs and len(pairs) == 25 + + def test_invalid_rel_type(self, fixture_orthoxml): + with pytest.raises(ValueError): + _pairs(fixture_orthoxml, "xenolog") + + def test_rejects_other_objects(self): + with pytest.raises(ValueError): + list(transform.iter_pairwise_relations("not an orthoxml")) + + def test_properties_and_scores_are_ignored(self): + doc = _xml('' + '') + assert _pairs(doc, "ortholog", id_attribute="protId") == [("a1", "b1")] + + def test_gene_without_attribute_gives_none(self): + # characterises current behaviour: the KeyError branch (transform.py:82-85) is dead code + doc = _xml('', + genes='') + assert _pairs(doc, "ortholog", id_attribute="protId") == [("a1", None)] + + @pytest.mark.xfail(strict=True, reason="BUG: an orthologGroup without children raises TypeError in " + "set.union() (zoo/hog/transform.py:52)") + def test_empty_group_has_no_pairs(self): + doc = _xml('') + assert _pairs(doc, "ortholog", id_attribute="protId") == [("a1", "b1")] + + +class TestTreeRelations: + def test_docstring_example(self): + tree = dendropy.Tree.get_from_string('(1,(2,(3,4))[&&NHX:Ev=D],(5,6));', schema='newick') + assert sorted(transform.iter_pairwise_relations(tree, rel_type='paralog')) == [('2', '3'), ('2', '4')] + + +class TestHelperCli: + def test_expected_output_orthologs(self, tmp_path, fixed_hashseed): + out = tmp_path / "orthologs.tsv.gz" + subprocess.run([sys.executable, "-m", "FastOMA.helper_scripts", "pw-rel", "--orthoxml", + str(EXPECTED / "FastOMA_HOGs.orthoxml"), "--out", str(out)], + check=True, env=fixed_hashseed) + with gzip.open(out, "rt") as fh: + lines = fh.read().splitlines() + assert all(len(line.split("\t")) == 2 for line in lines) # no header, two protIds + assert read_pairs(out) == read_pairs(EXPECTED / "orthologs.tsv") + assert len(lines) == len(read_pairs(out)) + + def test_paralog_type_on_fixture(self, tmp_path, fixture_orthoxml, monkeypatch): + from FastOMA import helper_scripts + out = tmp_path / "paralogs.tsv" + monkeypatch.setattr(sys, "argv", ["fastoma-helper", "pw-rel", "--type", "paralog", + "--orthoxml", str(fixture_orthoxml), "--out", str(out)]) + helper_scripts.main() + assert read_pairs(out) == EXPECTED_PARALOGS diff --git a/tests/test_report_contract.py b/tests/test_report_contract.py new file mode 100644 index 0000000..bb57adc --- /dev/null +++ b/tests/test_report_contract.py @@ -0,0 +1,98 @@ +"""What the report notebook (FastOMA/fastoma_notebook_stat.ipynb) expects from the pipeline outputs. + +The notebook is not executed here (papermill is an optional extra); these tests pin the input +formats it parses via FastOMA/zoo/hog/extract_hog_info.py and the names it reads. +""" +import io +import json +import re + +import pytest + +from FastOMA import collect_subhogs as cs +from FastOMA.transformer import header_transformer +from FastOMA.zoo.hog import extract_hog_info +from conftest import REPO_DIR, TESTDATA_DIR +import output_fixtures as fx + +NOTEBOOK = REPO_DIR / "FastOMA" / "fastoma_notebook_stat.ipynb" + + +def _code_cells(): + nb = json.loads(NOTEBOOK.read_text()) + return [c for c in nb["cells"] if c["cell_type"] == "code"] + + +@pytest.fixture +def fixture_orthoxml(tmp_path): + inputs = fx.build_collect_inputs(tmp_path / "in", isoforms=True) + out = tmp_path / "FastOMA_HOGs.orthoxml" + cs.write_hog_orthoxml(str(inputs["pickle_folder"]), str(out), inputs["gene_id_pickle"], + header_transformer("noop"), str(inputs["species_tree"])) + return out + + +def _parse(path_or_text): + analyser = extract_hog_info.SpeciesAnalyser() + fh = io.StringIO(path_or_text) if isinstance(path_or_text, str) else open(path_or_text) + with fh: + records = list(extract_hog_info.parse_orthoxml(fh, analyser)) + return records, analyser + + +class TestHogInfoParsing: + def test_records(self, fixture_orthoxml): + records, _ = _parse(fixture_orthoxml) + assert len(records) == 10 + assert all(set(r) == {"id", "level", "CompletenessScore", "nr_members", "is_roothog"} for r in records) + roots = {r["id"]: r for r in records if r["is_roothog"]} + assert {(r["level"], r["nr_members"]) for r in roots.values()} == {("root", 11), ("AB", 5)} + assert all(isinstance(r["CompletenessScore"], float) for r in records) + + def test_species_summary_counts_main_isoforms(self, fixture_orthoxml): + _, analyser = _parse(fixture_orthoxml) + summary = {s["species"]: s for s in analyser.summary()} + # a7 is a minor isoform of a1; A has 7 proteins, 6 genes, a7 is in a group + assert summary["A"] == {"species": "A", "genes": 6, "not_in_group": 0, "proteins": 7, "minor_splice": 1} + assert summary["B"] == {"species": "B", "genes": 7, "not_in_group": 1, "proteins": 7, "minor_splice": 0} + assert summary["D"]["not_in_group"] == 6 + + def test_expected_output_parses(self): + records, analyser = _parse(TESTDATA_DIR / "expected_output" / "FastOMA_HOGs.orthoxml") + assert sum(r["is_roothog"] for r in records) == 12 + assert {s["species"] for s in analyser.summary()} == {"AQUAE", "CHLTR", "MYCGE"} + + def test_requires_version_0_5(self, fixture_orthoxml): + text = fixture_orthoxml.read_text().replace('version="0.5"', 'version="0.4"', 1) + with pytest.raises(RuntimeError, match="0.5"): + _parse(text) + + def test_without_scores_there_is_no_completeness_column(self, fixture_orthoxml): + text = re.sub(r"\s*]*/>", "", fixture_orthoxml.read_text()) + records, _ = _parse(text) + assert all("CompletenessScore" not in r for r in records) + + +class TestNotebook: + def test_parameters_cell(self): + # FastOMA.nf (fastoma_report) passes -p output_folder, min_sequence_length, proteome_folder + params = [c for c in _code_cells() if "parameters" in c.get("metadata", {}).get("tags", [])] + assert len(params) == 1 + source = "".join(params[0]["source"]) + for name in ("output_folder", "min_sequence_length", "proteome_folder"): + assert re.search(rf"^{name}\s*=", source, re.M), name + + def test_reads_these_outputs(self): + source = "\n".join("".join(c["source"]) for c in _code_cells()) + assert '"FastOMA_HOGs.orthoxml"' in source + assert '"species_tree_checked.nwk"' in source + assert '"phylostratigraphy.html"' in source + + @pytest.mark.xfail(strict=True, reason="BUG: the report plots CompletenessScore unconditionally, so " + "--disable_completeness_score with --report fails " + "(fastoma_notebook_stat.ipynb, roothog_df/level_df plots)") + def test_completeness_plots_are_guarded(self): + for cell in _code_cells(): + source = "".join(cell["source"]) + if "x=\"CompletenessScore\"" in source or "y='CompletenessScore'" in source: + assert "CompletenessScore\" in" in source or "'CompletenessScore' in" in source, source diff --git a/tests/test_roothog_example.py b/tests/test_roothog_example.py deleted file mode 100644 index dd34ce8..0000000 --- a/tests/test_roothog_example.py +++ /dev/null @@ -1,6 +0,0 @@ -from unittest import TestCase - -class RootHOGExampleTestCase(TestCase): - - def setUpClass(cls): - pass \ No newline at end of file diff --git a/tests/test_roothog_grouping.py b/tests/test_roothog_grouping.py new file mode 100644 index 0000000..44e6d35 --- /dev/null +++ b/tests/test_roothog_grouping.py @@ -0,0 +1,224 @@ +"""Tests for the in-memory rootHOG grouping logic of fastoma-infer-roothogs (_utils_roothog.py).""" +import os +import subprocess +import sys +from collections import Counter + +import networkx as nx +import pytest + +from FastOMA import _utils_roothog as U +from roothog_helpers import hit, partition + + +@pytest.mark.parametrize("hogid, expected", [ + ("HOG:E0990677.124f", "E0990677"), + ("HOG:E0990677", "E0990677"), + ("HOG:C0886513.1b.2a", "C0886513"), +]) +def test_extract_root_hog_id(hogid, expected): + assert U.extract_root_hog_id(hogid) == expected + + +def test_extract_root_hog_id_requires_prefix(): + # characterises current behaviour: ids without the 'HOG:' prefix are not supported + with pytest.raises(IndexError): + U.extract_root_hog_id("E0990677.1a") + + +def test_group_prots_by_roothogs_uses_first_hit_only(): + hogmaps = {"A": {"a": [hit("HOG:X1.1a", 1), hit("HOG:Y1", 900)]}, + "B": {"b": [hit("HOG:X1", 1)]}} + assert U.group_prots_by_roothogs(hogmaps) == {"X1": [("A", "a"), ("B", "b")]} + + +class TestResolveSingletons: + def test_singleton_moves_to_multi_member_alt_rhog(self, roothog_conf): + hogmaps = {"S1": {"p": [hit("HOG:A1", 500)], "q": [hit("HOG:A1", 500)], + "s": [hit("HOG:B1", 500), hit("HOG:A1.3", 300)]}} + res = U.resolve_singletons(U.group_prots_by_roothogs(hogmaps), hogmaps, roothog_conf()) + assert res == {"A1": [("S1", "p"), ("S1", "q"), ("S1", "s")]} + + @pytest.mark.parametrize("score, moved", [(70, True), (69.9, False)]) + def test_alt_hit_threshold_is_inclusive(self, roothog_conf, score, moved): + hogmaps = {"S1": {"p": [hit("HOG:A1", 500)], "q": [hit("HOG:A1", 500)], + "s": [hit("HOG:B1", 500), hit("HOG:A1", score)]}} + res = U.resolve_singletons(U.group_prots_by_roothogs(hogmaps), hogmaps, + roothog_conf(mergHOG_fscore_thresh=70)) + assert (("S1", "s") in res["A1"]) == moved + assert ("B1" in res) != moved + + def test_alt_hit_to_singleton_rhog_does_not_move(self, roothog_conf): + # characterises current behaviour: q's alternative hit is p's best rootHOG, but since A1 is a + # singleton too, p and q are not merged (only singletons sharing an *alternative* rootHOG are) + hogmaps = {"S1": {"p": [hit("HOG:A1.1a", 500)], + "q": [hit("HOG:B1", 500), hit("HOG:A1.2b", 300)]}} + res = U.resolve_singletons(U.group_prots_by_roothogs(hogmaps), hogmaps, roothog_conf()) + assert partition(res) == {frozenset({("S1", "p")}), frozenset({("S1", "q")})} + + def test_singletons_sharing_alt_rhog_are_merged(self, roothog_conf): + hogmaps = {"S1": {"p": [hit("HOG:A1", 500), hit("HOG:C1", 300)]}, + "S2": {"q": [hit("HOG:B1", 500), hit("HOG:C1", 300)]}, + "S3": {"r": [hit("HOG:D1", 500), hit("HOG:C1", 10)]}} + res = U.resolve_singletons(U.group_prots_by_roothogs(hogmaps), hogmaps, roothog_conf()) + assert partition(res) == {frozenset({("S1", "p"), ("S2", "q")}), frozenset({("S3", "r")})} + # the merged group is named after one of the members' rootHOGs (which one depends on the hash seed) + assert (set(res) - {"D1"}) <= {"A1", "B1"} + + +class TestFindRhogCandidatePairs: + @staticmethod + def hogmaps(n_shared=12, alt_score=300): + # n_shared proteins with best hit A1 and an alternative hit to B1, plus two B1-only proteins + hm = {"S1": {f"p{i}": [hit("HOG:A1.1a", 500), hit("HOG:B1", alt_score)] for i in range(n_shared)}} + hm["S1"].update({f"b{i}": [hit("HOG:B1", 500)] for i in range(2)}) + return hm + + def candidates(self, conf, **kw): + hm = self.hogmaps(**kw) + return U.find_rhog_candidate_pairs(hm, U.group_prots_by_roothogs(hm), conf) + + def test_pair_found_bigger_first(self, roothog_conf): + # sizes count all hits above threshold: A1=12, B1=14 + assert self.candidates(roothog_conf()) == [("B1", "A1")] + + def test_fscore_threshold_is_strict(self, roothog_conf): + # characterises current behaviour: strict '>' here, while resolve_singletons uses '>=' + assert self.candidates(roothog_conf(mergHOG_fscore_thresh=300)) == [] + + def test_shared_count_must_exceed_threshold(self, roothog_conf): + assert self.candidates(roothog_conf(), n_shared=10) == [] + + def test_big_rhogs_are_not_merged(self, roothog_conf): + # rootHOGs must be smaller than big_rhog_size / 6 (here 10) + assert self.candidates(roothog_conf(big_rhog_size=60)) == [] + + def test_pairs_with_non_best_rhog_are_skipped(self, roothog_conf): + hm = self.hogmaps() + rhogs_prots = U.group_prots_by_roothogs(hm) + del rhogs_prots["B1"] + assert U.find_rhog_candidate_pairs(hm, rhogs_prots, roothog_conf()) == [] + + +def test_cluster_rhogs_connected_components(): + clusters = U.cluster_rhogs([("a", "b"), ("c", "d"), ("b", "e")]) + assert {frozenset(c) for c in clusters} == {frozenset("abe"), frozenset("cd")} + assert all(isinstance(c, list) for c in clusters) + + +class TestHCS: + def test_two_triangles_joined_by_bridge_are_split(self): + pairs = [("a", "b"), ("b", "c"), ("a", "c"), ("d", "e"), ("e", "f"), ("d", "f"), ("c", "d")] + clusters = U.cluster_rhogs_nx([list("abcdef")], pairs) + assert {frozenset(c) for c in clusters} == {frozenset("abc"), frozenset("def")} + + def test_complete_graph_stays_together(self): + nodes = list("abcd") + pairs = [(x, y) for i, x in enumerate(nodes) for y in nodes[i + 1:]] + assert [set(c) for c in U.cluster_rhogs_nx([nodes], pairs)] == [set(nodes)] + + def test_path_of_three_keeps_one_pair(self): + # characterises current behaviour: HCS cuts a path, the leftover node is not merged at all + clusters = U.cluster_rhogs_nx([list("abc")], [("a", "b"), ("b", "c")]) + assert len(clusters) == 1 and len(clusters[0]) == 2 and "b" in clusters[0] + + def test_highly_connected(self): + g = nx.complete_graph(4) + assert U.highly_connected(g, nx.minimum_edge_cut(g)) + assert not U.highly_connected(nx.path_graph(3), {(0, 1)}) + + def test_clusters_of_1000_rhogs_are_not_merged(self): + nodes = [f"R{i}" for i in range(1000)] + clusters = U.cluster_rhogs_nx([nodes], [("R0", "R1")]) + # characterises current behaviour: huge clusters are returned as singletons instead of dropped + assert len(clusters) == 1000 and all(len(c) == 1 for c in clusters) + + @pytest.mark.slow + @pytest.mark.xfail(strict=True, reason="BUG: merge partition depends on PYTHONHASHSEED via set order " + "fed into HCS (_utils_roothog.py:576/1028)") + def test_merge_partition_independent_of_hash_seed(self): + code = ("from FastOMA import _utils_roothog as U\n" + "p=[('R1','R2'),('R2','R3'),('R3','R4'),('R4','R5')]\n" + "print(sorted(sorted(c) for c in U.cluster_rhogs_nx(U.cluster_rhogs(p), p)))") + results = {subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, check=True, + env=dict(os.environ, PYTHONHASHSEED=str(seed))).stdout + for seed in range(8)} + assert len(results) == 1 + + +class TestMergeRhogs2: + @staticmethod + def patch_clusters(monkeypatch, clusters): + monkeypatch.setattr(U, "find_rhog_candidate_pairs", lambda *a: []) + monkeypatch.setattr(U, "cluster_rhogs_nx", lambda cl, cp: [list(c) for c in clusters]) + + def test_cluster_merged_into_smallest_id(self, in_tmp_dir, monkeypatch, roothog_conf): + self.patch_clusters(monkeypatch, [["B", "A"]]) + rp = {"A": [("s", "a1")], "B": [("s", "b1"), ("s", "b2")], "C": [("s", "c1")]} + res = U.merge_rhogs2({}, rp, roothog_conf()) + assert set(res) == {"A", "C"} + assert sorted(res["A"]) == [("s", "a1"), ("s", "b1"), ("s", "b2")] + assert (in_tmp_dir / "merging_rhogs.txt").read_text().splitlines()[1] == "A\tB\t" + + def test_merge_with_real_candidates(self, in_tmp_dir, roothog_conf): + hm = TestFindRhogCandidatePairs.hogmaps() + res = U.merge_rhogs2(hm, U.group_prots_by_roothogs(hm), roothog_conf()) + assert list(res) == ["A1"] and len(res["A1"]) == 14 + + @pytest.mark.xfail(strict=True, reason="BUG: `newhost` is not reset per cluster and the overflow host " + "loses its own proteins (_utils_roothog.py:667-713)") + @pytest.mark.parametrize("clusters", [[["A", "B", "C"]], [["A", "B", "C", "D"], ["W", "X", "Y", "Z"]]]) + def test_overflowing_clusters_keep_all_proteins(self, in_tmp_dir, monkeypatch, roothog_conf, clusters): + self.patch_clusters(monkeypatch, clusters) + rp = {k: [("s", k + str(i)) for i in range(2)] for c in clusters for k in c} + before = Counter(p for v in rp.values() for p in v) + res = U.merge_rhogs2({}, rp, roothog_conf(big_rhog_size=3)) + assert Counter(p for v in res.values() for p in v) == before + # and no rootHOG mixes proteins of different clusters + cluster_of = {k: i for i, c in enumerate(clusters) for k in c} + for members in res.values(): + assert len({cluster_of[p[1][0]] for p in members}) == 1 + + +class TestFilterBigRoothogs: + def test_low_score_proteins_dropped_from_big_rhog(self, roothog_conf): + hm = {"A": {"a": [hit("HOG:X1.1a", 96)], "b": [hit("HOG:X1", 95)], + "c": [hit("HOG:Y1", 50), hit("HOG:X1", 99)], "z": [hit("HOG:Z1", 1)]}} + rp = {"X1": [("A", "a"), ("A", "b"), ("A", "c")], "Z1": [("A", "z")]} + res = U.filter_big_roothogs(hm, rp, roothog_conf(big_rhog_size=2, big_fscore_thresh=95)) + # strict '>' on the score; the score of the *first* hit is used even if it belongs to another + # rootHOG (c was moved here from Y1) -- characterises current behaviour + assert res == {"X1": [("A", "a")], "Z1": [("A", "z")]} + + def test_big_rhog_removed_if_nothing_passes(self, roothog_conf): + hm = {"A": {p: [hit("HOG:X1", 10)] for p in "abc"}} + rp = {"X1": [("A", p) for p in "abc"]} + assert U.filter_big_roothogs(hm, rp, roothog_conf(big_rhog_size=2)) == {} + + def test_small_rhogs_untouched(self, roothog_conf): + hm = {"A": {p: [hit("HOG:X1", 10)] for p in "ab"}} + rp = {"X1": [("A", "a"), ("A", "b")]} + assert U.filter_big_roothogs(hm, rp, roothog_conf()) == rp + + def test_filter_nonchild_rootHOG(self, roothog_conf, monkeypatch): + monkeypatch.setattr(U, "filter_nonchild_rootHOG", True) + hm = {"A": {"a": [hit("HOG:X1.1a", 99)], "b": [hit("HOG:X1", 99)], "c": [hit("HOG:X1.2b", 99)]}} + rp = {"X1": [("A", p) for p in "abc"]} + res = U.filter_big_roothogs(hm, rp, roothog_conf(big_rhog_size=2)) + assert res == {"X1": [("A", "a"), ("A", "c")]} + + +def test_create_root_hogs(in_tmp_dir, roothog_conf): + hm = {"S1": {"a": [hit("HOG:A1.1a", 500)], + "p": [hit("HOG:P1", 500), hit("HOG:Q1", 300)]}, + "S2": {"a": [hit("HOG:A1", 500)], + "q": [hit("HOG:R1", 500), hit("HOG:Q1.4c", 300)]}, + "S3": {"s": [hit("HOG:Z1", 500), hit("HOG:A1.2b", 400)], + "u": [hit("HOG:U1", 500)]}} + res = U.create_root_hogs(hm, roothog_conf()) + assert partition(res) == { + frozenset({("S1", "a"), ("S2", "a"), ("S3", "s")}), # s resolved via its alternative hit + frozenset({("S1", "p"), ("S2", "q")}), # singletons sharing alternative Q1 + frozenset({("S3", "u")}), # remains a singleton + } + assert set(res) >= {"A1", "U1"} diff --git a/tests/test_roothog_io.py b/tests/test_roothog_io.py new file mode 100644 index 0000000..b93ee03 --- /dev/null +++ b/tests/test_roothog_io.py @@ -0,0 +1,215 @@ +"""Tests for the file parsing/writing helpers of fastoma-infer-roothogs (_utils_roothog.py).""" +import pickle + +import pytest +from Bio import SeqIO + +from FastOMA import _utils_roothog as U +from roothog_helpers import fasta_ids, hit, rec, write_fasta, write_hogmap + +SEQ = "MKVLAAGIVLLLAAGCSSHE" + + +def test_add_species_name_prot_id_numbering(): + recs = {"AQUAE": [rec("a1"), rec("a2")], "CHLTR": [rec("c1")]} + res = U.add_species_name_prot_id(recs) + assert {sp: {k: r.id for k, r in d.items()} for sp, d in res.items()} == { + "AQUAE": {"a1": "a1||AQUAE||1000000001", "a2": "a2||AQUAE||1000000002"}, + "CHLTR": {"c1": "c1||CHLTR||1001000001"}, + } + # the input records are modified in place + assert recs["CHLTR"][0].id == "c1||CHLTR||1001000001" + + +def test_add_species_name_prot_id_truncates_long_ids(): + res = U.add_species_name_prot_id({"A": [rec("L" * 240)]}) + assert list(res["A"]) == ["L" * 230] + + +class TestParseProteomes: + def test_sorted_species_and_min_length(self, tmp_path): + write_fasta(tmp_path / "B.fa", [("b1", SEQ), ("b_short", "MKV")]) + write_fasta(tmp_path / "A.fa", [("a1", SEQ)]) + species, recs, ext = U.parse_proteomes(str(tmp_path), min_sequence_length=10) + assert species == ["A", "B"] + assert ext == "fa" + assert {sp: [r.id for r in rs] for sp, rs in recs.items()} == {"A": ["a1"], "B": ["b1"]} + + def test_fasta_extension(self, tmp_path): + write_fasta(tmp_path / "A.fasta", [("a1", SEQ)]) + assert U.parse_proteomes(str(tmp_path))[2] == "fasta" + + @pytest.mark.xfail(strict=True, reason="BUG: files without an extension crash parse_proteomes " + "(file.rsplit at _utils_roothog.py:166); check_input only warns") + def test_non_fasta_files_ignored(self, tmp_path): + write_fasta(tmp_path / "A.fa", [("a1", SEQ)]) + (tmp_path / "README").write_text("x") + assert U.parse_proteomes(str(tmp_path))[0] == ["A"] + + @pytest.mark.xfail(strict=True, reason="BUG: hidden files such as the ._A.fa AppleDouble files of macOS tar " + "archives are parsed as proteomes (_utils_roothog.py:165-170)") + def test_hidden_files_ignored(self, tmp_path): + write_fasta(tmp_path / "A.fa", [("a1", SEQ)]) + (tmp_path / "._A.fa").write_bytes(b"\x00\x05\x16\x07\x00\x02\x00\x00Mac OS X ") + assert U.parse_proteomes(str(tmp_path))[0] == ["A"] + + def test_mixed_extensions_fail(self, tmp_path): + # characterises current behaviour: only the last seen extension is used; check_input rejects this + write_fasta(tmp_path / "A.fa", [("a1", SEQ)]) + write_fasta(tmp_path / "B.fasta", [("b1", SEQ)]) + with pytest.raises(FileNotFoundError): + U.parse_proteomes(str(tmp_path)) + + +class TestParseHogmapOmamer: + @staticmethod + def parse(tmp_path, proteome_ids, rows): + write_hogmap(tmp_path / "A.fa.hogmap", rows) + recs = {"A": [rec(i) for i in proteome_ids]} + U.add_species_name_prot_id(recs) # parse_hogmap_omamer expects the renamed records + return U.parse_hogmap_omamer(recs, "fa", folder=str(tmp_path)) + + def test_hits_unmapped_and_unknown(self, tmp_path): + hogmaps, unmapped = self.parse(tmp_path, ["p1", "p2", "p3"], [ + ("p1", "HOG:A1.1a", 500.5), ("p1", "HOG:B1", 30), + ("p2", "N/A", None), + ("other", "HOG:C1", 100), + ]) + assert dict(hogmaps["A"]) == {"p1": [hit("HOG:A1.1a", 500.5), hit("HOG:B1", 30)]} + assert dict(unmapped) == {"A": ["p2"]} + + def test_hogmap_values_are_strings(self, tmp_path): + hogmaps, _ = self.parse(tmp_path, ["p1"], [("p1", "HOG:A1", 12.5)]) + assert hogmaps["A"]["p1"][0].score == "12.5" + + def test_protein_missing_from_hogmap(self, tmp_path): + # characterises current behaviour: a protein without any hogmap row is neither mapped nor + # unmapped, so it is silently dropped from the pipeline (see test_roothog_stage.py) + hogmaps, unmapped = self.parse(tmp_path, ["p1", "p2"], [("p1", "HOG:A1", 100)]) + assert "p2" not in hogmaps["A"] and "p2" not in unmapped["A"] + + @pytest.mark.xfail(strict=True, reason="BUG: ids >230 chars are truncated (_utils_roothog.py:256) and then " + "no longer match the hogmap qseqid (:289), the protein vanishes") + def test_long_ids_are_kept(self, tmp_path): + long_id = "L" * 240 + hogmaps, unmapped = self.parse(tmp_path, [long_id], [(long_id, "HOG:A1", 100)]) + assert len(hogmaps["A"]) + len(unmapped["A"]) == 1 + + +def test_write_rhog(tmp_path): + prot_recs_all = U.add_species_name_prot_id({"A": [rec("a1"), rec("a2")], "B": [rec("b1")]}) + rhogs = {"X1": [("A", "a1"), ("B", "b1")], "Y1": [("A", "a2")], + "Z1": [("A", "a2"), ("B", "too_short")]} # too_short is not in prot_recs_all + out = tmp_path / "rhogs" + written = U.write_rhog(rhogs, prot_recs_all, str(out), min_rhog_size=2) + assert written == ["X1"] + assert sorted(p.name for p in out.iterdir()) == ["HOG_X1.fa"] + assert [r.id for r in SeqIO.parse(out / "HOG_X1.fa", "fasta")] == ["a1||A||1000000001", "b1||B||1001000001"] + + +def test_collect_unmapped_singleton(tmp_path): + prot_recs_all = U.add_species_name_prot_id({"A": [rec("a1"), rec("a2"), rec("a3")], "B": [rec("b1")]}) + rhogs = {"X1": [("A", "a1"), ("B", "b1")], "Y1": [("A", "a2")]} + unmapped = {"A": ["a3", "short_and_removed"]} + out = tmp_path / "singletons.fa" + assert U.collect_unmapped_singleton(rhogs, unmapped, prot_recs_all, str(out)) == 2 + assert fasta_ids(out) == ["a3", "a2"] + + +class TestWriteClusters: + MMSEQS_OUT = (">r1\n>a||S1||1 desc\nMKV\n>b||S2||2\nMKA\n" # 2 species -> written + ">r2\n>c||S1||3\nMKK\n" # singleton -> skipped + ">r3\n>d||S1||4\nMAA\n>e||S1||5\nMCC\n" # single species -> discarded + ">r4\n>f||S1||6\nMAA\n>g||S2||7\nMCC\n") # last cluster -> written + + def test_write_clusters(self, tmp_path): + clustered = tmp_path / "all_seqs.fasta" + clustered.write_text(self.MMSEQS_OUT) + out = tmp_path / "rhogs" + out.mkdir() + assert U.write_clusters(str(clustered), str(out), 2) == 2 + assert {p.name: fasta_ids(p) for p in out.iterdir()} == { + "HOG_clust10000.fa": ["a", "b"], "HOG_clust10001.fa": ["f", "g"]} + + def test_empty_file(self, tmp_path): + clustered = tmp_path / "all_seqs.fasta" + clustered.write_text("") + assert U.write_clusters(str(clustered), str(tmp_path), 2) == 0 + + +@pytest.mark.tools("mmseqs") +def test_run_linclust_and_write_clusters(in_tmp_dir, proteome_recs): + prot_recs_all = U.add_species_name_prot_id(proteome_recs) + unmapped = {sp: list(d) for sp, d in prot_recs_all.items()} + assert U.collect_unmapped_singleton({}, unmapped, prot_recs_all) == 28 + clustered = U.run_linclust() + (in_tmp_dir / "rhogs").mkdir() + n = U.write_clusters(clustered, "rhogs", 2) + files = list((in_tmp_dir / "rhogs").glob("HOG_clust*.fa")) + assert n == len(files) > 0 + for f in files: # every written cluster spans at least two species + with open(f) as fh: + species = {line.split()[0].split("||")[1] for line in fh if line.startswith(">")} + assert len(species) >= 2 + + +@pytest.fixture +def proteome_recs(): + from conftest import TESTDATA_DIR + return U.parse_proteomes(str(TESTDATA_DIR / "in_folder" / "proteome"), 50)[1] + + +class TestIsoforms: + def test_parse_isoform_files(self, tmp_path): + (tmp_path / "A.splice").write_text("i1;i2\ni3\n\n") + assert U.parse_isoform_files(["A", "B"], str(tmp_path)) == { + "A": [["i1", "i2"], ["i3"], [""]], # blank lines become [''] -- characterises current behaviour + "B": []} + + def test_select_best_isoform_uses_max_score_over_all_hits(self): + hm = {"A": {"i1": [hit("HOG:X1", 100), hit("HOG:Y1", 900)], "i2": [hit("HOG:Z1", 500)]}} + assert U.select_best_isoforms(["A"], {"A": [["i1", "i2"]]}, hm) == ({"A": ["i1"]}, {"A": ["i2"]}) + + def test_select_best_isoform_tie_takes_last(self): + hm = {"A": {"i1": [hit("HOG:X1", 5)], "i2": [hit("HOG:X1", 5)]}} + assert U.select_best_isoforms(["A"], {"A": [["i1", "i2"]]}, hm) == ({"A": ["i2"]}, {"A": ["i1"]}) + + def test_select_best_isoform_none_mapped_takes_first(self): + assert U.select_best_isoforms(["A"], {"A": [["i1", "i2"]]}, {"A": {}}) == ({"A": ["i1"]}, {"A": ["i2"]}) + + def test_filter_hogmaps_by_isoforms(self): + hm = {"A": {"i1": 1, "i2": 2, "x": 3}, "B": {"b": 4}} + assert U.filter_hogmaps_by_isoforms(hm, {"A": ["i2"], "B": []}) == {"A": {"i1": 1, "x": 3}, "B": {"b": 4}} + + def test_handle_splice_variants(self, tmp_path): + (tmp_path / "A.splice").write_text("i1;i2\n") + hm = {"A": {"i1": [hit("HOG:X1", 10)], "i2": [hit("HOG:X1", 20)], "x": [hit("HOG:X1", 1)]}} + res = U.handle_splice_variants(["A"], hm, str(tmp_path)) + assert res["selected_isoforms"] == {"A": ["i2"]} + assert res["non_selected"] == {"A": ["i1"]} + assert set(res["filtered_hogmaps"]["A"]) == {"i2", "x"} + + def test_write_selected_isoforms(self, in_tmp_dir): + recs = {"A": [rec("i1"), rec("i2"), rec("x")]} + U.add_species_name_prot_id(recs) + data = {"selected_isoforms": {"A": ["i2"]}, "isoform_by_gene": {"A": [["i1", "i2"]]}} + U.write_selected_isoforms(data, recs) + assert (in_tmp_dir / "selected_isoforms" / "A_selected_isoforms.fa").read_text() == \ + f">i2 minor_isoforms=i1\n{SEQ}\n>x \n{SEQ}\n" + + +class TestSaveGeneIdMapping: + def test_with_isoforms(self, tmp_path): + prot_recs_all = U.add_species_name_prot_id({"A": [rec("g1a"), rec("g1b"), rec("g2")]}) + iso = {"selected_isoforms": {"A": ["g1b"]}, "isoform_by_gene": {"A": [["g1a", "g1b"]]}} + out = tmp_path / "map.pickle" + res = U.save_gene_id_mapping(prot_recs_all, iso, out_path=str(out)) + assert res == {"A": [U.Gene(1000000001, "g1a", 1000000002), U.Gene(1000000002, "g1b", 1000000002), + U.Gene(1000000003, "g2", None)]} + with open(out, "rb") as fh: + assert pickle.load(fh) == res + + def test_without_isoforms(self, tmp_path): + prot_recs_all = U.add_species_name_prot_id({"A": [rec("g1")]}) + res = U.save_gene_id_mapping(prot_recs_all, None, out_path=str(tmp_path / "m.pickle")) + assert res == {"A": [U.Gene(1000000001, "g1", None)]} diff --git a/tests/test_roothog_stage.py b/tests/test_roothog_stage.py new file mode 100644 index 0000000..a00b109 --- /dev/null +++ b/tests/test_roothog_stage.py @@ -0,0 +1,161 @@ +"""Stage-level characterisation tests for fastoma-infer-roothogs, run as a subprocess. + +The rootHOG partition depends on set iteration order (see test_roothog_grouping.py), so the +subprocesses run with a fixed PYTHONHASHSEED and we compare partitions, never file names. +""" +import os +import random +import subprocess +import sys + +import pytest + +from conftest import TESTDATA_DIR +from roothog_helpers import fasta_ids, read_rhog_partition, write_fasta, write_hogmap + + +def run_infer_roothogs(workdir, proteomes, hogmaps, *extra): + cmd = [sys.executable, "-m", "FastOMA.infer_roothogs", "--proteomes", str(proteomes), + "--hogmap", str(hogmaps), "--out-rhog-folder", "omamer_rhogs", *map(str, extra)] + subprocess.run(cmd, cwd=workdir, check=True, capture_output=True, + env=dict(os.environ, PYTHONHASHSEED="0")) + return workdir / "omamer_rhogs" + + +def test_testdata_partition(tmp_path): + out = run_infer_roothogs(tmp_path, TESTDATA_DIR / "in_folder" / "proteome", + TESTDATA_DIR / "expected_output" / "hogmap") + expected = [ + {"sp|O67618|LEPA_AQUAE", "sp|O66429|EFTU_AQUAE", "sp|O84067|LEPA_CHLTR", "sp|P0CD71|EFTU_CHLTR", + "sp|P13927|EFTU_MYCGE"}, + {"sp|O67118|DNAK_AQUAE", "sp|P17821|DNAK_CHLTR", "sp|P47547|DNAK_MYCGE"}, + {"sp|O67032|RF1_AQUAE", "sp|O84026|RF1_CHLTR", "sp|P47500|RF1_MYCGE"}, + {"sp|O67828|ATPB_AQUAE", "sp|O66907|ATPA_AQUAE", "sp|P47639|ATPB_MYCGE", "sp|P47641|ATPA_MYCGE"}, + {"sp|O67943|CH60_AQUAE", "sp|P0C0Z7|CH60_CHLTR"}, + {"sp|O66778|ENO_AQUAE", "sp|O84591|ENO_CHLTR"}, + {"sp|O67736|FOLD_AQUAE", "sp|O84081|FOLD_CHLTR"}, + {"sp|O67161|G3P_AQUAE", "sp|P0CE13|G3P_CHLTR", "sp|P47543|G3P_MYCGE"}, + {"sp|O67547|SUCD_AQUAE", "tr|O84829|O84829_CHLTR"}, + {"sp|O66686|TPIS_AQUAE", "sp|O84332|TPIS_CHLTR"}, + ] + assert read_rhog_partition(out) == frozenset(frozenset(g) for g in expected) + assert len(list(out.glob("HOG_*.fa"))) == len(expected) + + +# --- synthetic dataset exercising all branches of the stage ------------------------------------------ +# +# Building and running takes a few seconds (mmseqs), hence `slow`. +# CLI options: --big-rhog-size 30 (so merge candidates must be < 5 proteins), --mergHOG-shared-thresh 1 +SPECIES = ["S1", "S2", "S3", "S4"] +BIG = [f"{sp}_b{i}" for sp in SPECIES for i in range(8)] +EXPECTED_PARTITION = frozenset(frozenset(g) for g in [ + {"S1_a", "S2_a", "S3_a", "S4_x"}, # S4_x is a singleton resolved via its alternative hit to A1 + {"S1_p", "S2_q"}, # singletons sharing the alternative rootHOG Q1 + {"S1_m", "S2_m", "S3_m", "S4_m"}, # rootHOGs M1 and M2 merged + set(BIG[0::2]), # big rootHOG B1, low-score half filtered out +]) +SENT_TO_LINCLUST = {"S3_u", "S4_n", "S2_n1", "S2_n2"} +EXPECTED_TO_VANISH = {"S1_short", "S1_a2"} | set(BIG[1::2]) # too short / minor isoform / big-rhog filter +KNOWN_BUG_VANISH = {"S2_missing"} # absent from the hogmap + + +def build_synthetic(root): + rng = random.Random(42) + aa = "ACDEFGHIKLMNPQRSTVWY" + + def seq(): + return "M" + "".join(rng.choice(aa) for _ in range(79)) + + hits = {sp: [] for sp in SPECIES} + prots = {sp: [] for sp in SPECIES} + + def add(sp, prot, *rows, sequence=None): + prots[sp].append((prot, sequence or seq())) + hits[sp].extend((prot, hogid, score) for hogid, score in rows) + + add("S1", "S1_a", ("HOG:A1.1a", 500)) + add("S1", "S1_a2", ("HOG:A1", 200)) # minor isoform of S1_a + add("S2", "S2_a", ("HOG:A1", 500)) + add("S3", "S3_a", ("HOG:A1.2b", 500)) + add("S4", "S4_x", ("HOG:Z9", 500), ("HOG:A1.3", 300)) + add("S1", "S1_p", ("HOG:P1", 500), ("HOG:Q1", 300)) + add("S2", "S2_q", ("HOG:R1", 500), ("HOG:Q1.1a", 300)) + add("S1", "S1_m", ("HOG:M1", 500), ("HOG:M2", 300)) + add("S2", "S2_m", ("HOG:M1", 500), ("HOG:M2", 300)) + add("S3", "S3_m", ("HOG:M2", 500), ("HOG:M1", 300)) + add("S4", "S4_m", ("HOG:M2", 500), ("HOG:M1", 300)) + for i, prot in enumerate(BIG): + add(prot[:2], prot, ("HOG:B1.1a", 99 if i % 2 == 0 else 90)) + shared = seq() # identical sequences: linclust must group them + add("S3", "S3_u", ("HOG:U1", 500), sequence=shared) + add("S4", "S4_n", ("N/A", None), sequence=shared) + add("S2", "S2_n1", ("N/A", None)) + add("S2", "S2_n2", ("N/A", None)) # minor isoform of S2_n1, both unmapped + add("S1", "S1_short", ("HOG:A1", 500), sequence="MKVLAAGIVL") + prots["S2"].append(("S2_missing", seq())) + + for sp in SPECIES: + write_fasta(root / "proteome" / f"{sp}.fa", prots[sp]) + write_hogmap(root / "hogmap" / f"{sp}.fa.hogmap", hits[sp]) + (root / "splice").mkdir() + (root / "splice" / "S1.splice").write_text("S1_a;S1_a2\n") + (root / "splice" / "S2.splice").write_text("S2_n1;S2_n2\n") + return {p for sp in SPECIES for p, _ in prots[sp]} + + +@pytest.fixture(scope="module") +def synthetic_run(tmp_path_factory): + root = tmp_path_factory.mktemp("synthetic") + all_proteins = build_synthetic(root) + out = run_infer_roothogs(root, root / "proteome", root / "hogmap", "--splice", root / "splice", + "--big-rhog-size", 30, "--mergHOG-shared-thresh", 1) + return root, out, all_proteins + + +@pytest.mark.slow +def test_synthetic_partition(synthetic_run): + _, out, _ = synthetic_run + assert read_rhog_partition(out, "HOG_[!c]*.fa") == EXPECTED_PARTITION + + +@pytest.mark.slow +def test_synthetic_selected_isoforms_written(synthetic_run): + root, _, _ = synthetic_run + assert fasta_ids(root / "selected_isoforms" / "S1_selected_isoforms.fa").count("S1_a2") == 0 + + +@pytest.mark.slow +@pytest.mark.tools("mmseqs") +def test_synthetic_linclust(synthetic_run): + root, out, _ = synthetic_run + assert set(fasta_ids(root / "singleton_unmapped.fa")) == SENT_TO_LINCLUST + assert read_rhog_partition(out, "HOG_clust*.fa") == {frozenset({"S3_u", "S4_n"})} + + +@pytest.mark.slow +@pytest.mark.tools("mmseqs") +def test_synthetic_every_protein_accounted_for(synthetic_run): + root, out, all_proteins = synthetic_run + in_rhogs = {p for g in read_rhog_partition(out) for p in g} + in_singletons = set(fasta_ids(root / "singleton_unmapped.fa")) + unaccounted = all_proteins - in_rhogs - in_singletons - EXPECTED_TO_VANISH + assert unaccounted == KNOWN_BUG_VANISH + + +@pytest.mark.slow +@pytest.mark.tools("mmseqs") +@pytest.mark.xfail(strict=True, reason="BUG: proteins without any hogmap row are dropped silently " + "(parse_hogmap_omamer, _utils_roothog.py:264)") +def test_synthetic_protein_missing_from_hogmap_not_lost(synthetic_run): + root, out, _ = synthetic_run + kept = {p for g in read_rhog_partition(out) for p in g} | set(fasta_ids(root / "singleton_unmapped.fa")) + assert KNOWN_BUG_VANISH <= kept + + +@pytest.mark.slow +@pytest.mark.tools("mmseqs") +@pytest.mark.xfail(strict=True, reason="BUG: unmapped proteins are not filtered by isoform selection, minor " + "isoforms reach linclust (infer_roothogs.py:50-60, :93)") +def test_synthetic_minor_isoforms_not_clustered(synthetic_run): + root, _, _ = synthetic_run + assert len({"S2_n1", "S2_n2"} & set(fasta_ids(root / "singleton_unmapped.fa"))) == 1 diff --git a/tests/test_species_tree_prep.py b/tests/test_species_tree_prep.py new file mode 100644 index 0000000..b35b7f0 --- /dev/null +++ b/tests/test_species_tree_prep.py @@ -0,0 +1,78 @@ +import pytest +from ete3 import Tree + +from FastOMA._utils_subhog import prepare_species_tree, read_species_tree, list_rhog_fastas +from subhog_helpers import pid, rec + +TREE = "((A,B)AB,(C,D)CD)root;" + + +def recs(*species): + return [rec(pid(f"p{i}", s, 100 + i)) for i, s in enumerate(species)] + + +def nwk(t): + return t.write(format=9) + + +def test_prepare_species_tree_returns_mrca_subtree(): + sp = Tree(TREE, format=1) + pruned, species, prots, full = prepare_species_tree(recs("A", "B", "A"), sp, "r") + assert pruned.name == "AB" + assert sorted(pruned.get_leaf_names()) == ["A", "B"] + assert species == ["A", "B", "A"] + assert prots == ["100", "101", "102"] + # characterises current behaviour: the subtree stays attached to the input tree + assert pruned.up is not None and pruned.get_tree_root() is sp + + +def test_prepare_species_tree_prunes_absent_species(): + sp = Tree(TREE, format=1) + pruned, _, _, full = prepare_species_tree(recs("A", "B", "C"), sp, "r") + assert pruned.write(format=8, format_root_node=True) == "((A,B)AB,C)root;" + # the full tree is an unpruned copy with the original clade sizes + assert sorted(full.get_leaf_names()) == ["A", "B", "C", "D"] + assert {n.name: n.size for n in full.traverse()} == {"root": 4, "AB": 2, "CD": 2, "A": 1, "B": 1, "C": 1, "D": 1} + assert pruned.size == 4 and (pruned & "C").size == 1 + + +def test_prepare_species_tree_unknown_species(): + with pytest.raises(ValueError): + prepare_species_tree(recs("A", "Q"), Tree(TREE, format=1), "r") + + +def test_prepare_species_tree_empty_roothog(): + with pytest.raises(AssertionError): + prepare_species_tree([], Tree(TREE, format=1), "r") + + +@pytest.mark.xfail(strict=True, reason="BUG: for a single-species rootHOG get_common_ancestor({'A'}) returns the root, " + "giving a unary root instead of the species leaf (_utils_subhog.py:283)") +def test_prepare_species_tree_single_species(): + pruned, _, _, _ = prepare_species_tree(recs("A", "A"), Tree(TREE, format=1), "r") + assert pruned.is_leaf() and pruned.name == "A" + + +def test_read_species_tree_newick_with_internal_names(tmp_path): + p = tmp_path / "sp.nwk" + p.write_text(TREE + "\n") + t = read_species_tree(str(p)) + assert [n.name for n in t.traverse("preorder")] == ["root", "AB", "A", "B", "CD", "C", "D"] + + +def test_read_species_tree_missing_file(tmp_path): + with pytest.raises(SystemExit): + read_species_tree(str(tmp_path / "missing.nwk")) + + +def test_read_species_tree_unknown_extension(tmp_path): + p = tmp_path / "sp.tree" + p.write_text(TREE + "\n") + with pytest.raises(SystemExit): + read_species_tree(str(p)) + + +def test_list_rhog_fastas(tmp_path): + for name in ["HOG_D0001.fa", "HOG_E0002.fa", "HOG_D0003.fa.gz", "HOG_D0004.fa.bak", "notes.txt"]: + (tmp_path / name).write_text("") + assert sorted(list_rhog_fastas(str(tmp_path))) == ["D0001", "E0002"] diff --git a/tests/test_subhog_stage.py b/tests/test_subhog_stage.py new file mode 100644 index 0000000..aeb012e --- /dev/null +++ b/tests/test_subhog_stage.py @@ -0,0 +1,201 @@ +"""Property tests of fastoma-infer-subhogs on real rootHOGs (tests/data/subhog). + +small/ D0455823 (45 proteins, 19 species), D0111149 (2 proteins) +conflict/ D0625237 (35 proteins; split_hog loses proteins), D0662268 (14 proteins; conflict resolution) +The species trees are the original trees pruned to the species of the rootHOGs (branch lengths set to 1). +""" +import os +import pickle +import subprocess +import sys +from pathlib import Path + +import pytest +from Bio import SeqIO +from ete3 import Tree +from lxml import etree + +from FastOMA._utils_subhog import prepare_species_tree +from fastoma_testutils import NS, canonical_orthoxml, geneRef_counts, orthoxml_validation_errors + +DATA = Path(__file__).parent / "data" / "subhog" + +# run the CLI, but keep the per-level pickles so the final HOG objects can be inspected +RUNNER = ("import FastOMA._infer_subhog as m; m.keep_subhog_each_pickle = True; " + "from FastOMA.infer_subhogs import fastoma_infer_subhogs; fastoma_infer_subhogs()") + +_ROOTHOGS = [("small", "D0455823"), ("small", "D0111149"), ("conflict", "D0625237"), ("conflict", "D0662268")] + + +def roothogs(xfail=None): + """parametrisation over the rootHOGs; ``xfail`` maps rootHOG ids to the reason of a known bug.""" + params = [] + for dataset, rhogid in _ROOTHOGS: + marks = [pytest.mark.slow] if dataset == "conflict" else [] + if xfail and rhogid in xfail: + marks.append(pytest.mark.xfail(strict=True, reason=xfail[rhogid])) + params.append(pytest.param(dataset, rhogid, id=rhogid, marks=marks)) + return params + + +ROOTHOGS = roothogs() + +pytestmark = pytest.mark.tools("mafft", "FastTree") + + +class Run: + def __init__(self, dataset, workdir, parallel): + self.dataset = dataset + self.workdir = workdir + self.species_tree_path = DATA / f"species_tree_{dataset}.nwk" + cmd = [sys.executable, "-c", RUNNER, + "--input-rhog-folder", str(DATA / dataset), + "--species-tree", str(self.species_tree_path), + "--output-pickles", "pickles", "-vv"] + if parallel: + cmd.append("--parallel") + # same environment as the fixed_hashseed fixture (which is function scoped) + env = dict(os.environ, PYTHONHASHSEED="0") + res = subprocess.run(cmd, cwd=workdir, env=env, capture_output=True, text=True) + assert res.returncode == 0, res.stdout[-3000:] + res.stderr[-3000:] + + def orthoxml(self, rhogid): + return etree.parse(str(self.workdir / f"ortho_{rhogid}.orthoxml")) + + def input_ids(self, rhogid): + return [r.id for r in SeqIO.parse(str(DATA / self.dataset / f"HOG_{rhogid}.fa"), "fasta")] + + def final_hogs(self, rhogid): + recs = list(SeqIO.parse(str(DATA / self.dataset / f"HOG_{rhogid}.fa"), "fasta")) + root = prepare_species_tree(recs, Tree(str(self.species_tree_path), format=1), rhogid)[0] + with open(self.workdir / f"rhog_{rhogid}" / f"{root.name}.pickle", "rb") as fh: + return pickle.load(fh) + + +_runs = {} + + +@pytest.fixture(scope="module") +def infer_run(tmp_path_factory): + def get(dataset, parallel=False): + key = (dataset, parallel) + if key not in _runs: + workdir = tmp_path_factory.mktemp(f"subhogs_{dataset}_{'par' if parallel else 'seq'}") + _runs[key] = Run(dataset, workdir, parallel) + return _runs[key] + yield get + _runs.clear() + + +def gene_species(tree): + return {g.get("id"): sp.get("name") for sp in tree.iterfind(".//o:species", NS) for g in sp.iterfind(".//o:gene", NS)} + + +def taxrange(elem): + p = elem.find("o:property[@name='TaxRange']", NS) + return None if p is None else p.get("value") + + +def ortholog_groups(tree): + return tree.findall(".//o:groups//o:orthologGroup", NS) + + +@pytest.mark.parametrize("dataset,rhogid", ROOTHOGS) +def test_no_protein_twice(infer_run, dataset, rhogid): + counts = geneRef_counts(infer_run(dataset).orthoxml(rhogid)) + assert counts and max(counts.values()) == 1 + + +@pytest.mark.parametrize("dataset,rhogid", roothogs(xfail={ + "D0625237": "BUG: split_hog drops subhogs not reached by any partition (_hog_class.py:636-643)"})) +def test_all_input_proteins_in_final_hogs(infer_run, dataset, rhogid): + run = infer_run(dataset) + members = [p for h in run.final_hogs(rhogid) for m in h.get_members() for p in m.split("_|_")] + assert sorted(members) == sorted(run.input_ids(rhogid)) + + +@pytest.mark.parametrize("dataset,rhogid", ROOTHOGS) +def test_written_genes_are_final_hogs_with_two_or_more_members(infer_run, dataset, rhogid): + run = infer_run(dataset) + hogs = run.final_hogs(rhogid) + expected = {m.split("||")[2] for h in hogs if len(h) >= 2 for m in h.get_members()} + written = set(geneRef_counts(run.orthoxml(rhogid), use_protid=False)) + assert written == expected + + +@pytest.mark.parametrize("dataset,rhogid", ROOTHOGS) +def test_top_level_groups_have_two_or_more_genes(infer_run, dataset, rhogid): + tree = infer_run(dataset).orthoxml(rhogid) + for group in tree.find(".//o:groups", NS): + assert len(group.findall(".//o:geneRef", NS)) >= 2 + + +@pytest.mark.parametrize("dataset,rhogid", ROOTHOGS) +def test_taxrange_nesting_follows_species_tree(infer_run, dataset, rhogid): + run = infer_run(dataset) + sptree = Tree(str(run.species_tree_path), format=1) + nodes = {n.name: n for n in sptree.traverse()} + for group in ortholog_groups(run.orthoxml(rhogid)): + parent = group.getparent() + while parent is not None and etree.QName(parent).localname == "paralogGroup": + parent = parent.getparent() + if etree.QName(parent).localname != "orthologGroup": + continue + child_node, parent_node = nodes[taxrange(group)], nodes[taxrange(parent)] + if group.getparent() is parent: + # directly nested orthologGroups are at strictly lower levels + assert parent_node in child_node.get_ancestors() + else: + # below a duplication the copies can span the same clade + assert child_node is parent_node or parent_node in child_node.get_ancestors() + + +@pytest.mark.parametrize("dataset,rhogid", roothogs(xfail={ + "D0625237": "BUG: root-level wrapper uses the HOG's level as TaxRange instead of the MRCA (_infer_subhog.py:120)"})) +def test_taxrange_is_mrca_of_member_species(infer_run, dataset, rhogid): + run = infer_run(dataset) + sptree = Tree(str(run.species_tree_path), format=1) + tree = run.orthoxml(rhogid) + species = gene_species(tree) + for group in ortholog_groups(tree): + member_species = {species[g.get("id")] for g in group.iterfind(".//o:geneRef", NS)} + mrca = sptree.get_common_ancestor(*member_species) if len(member_species) > 1 else sptree & member_species.pop() + assert taxrange(group) == mrca.name, group.get("id") + + +@pytest.mark.parametrize("dataset,rhogid", roothogs(xfail={ + "D0625237": "BUG: root-level paralogGroup wrapped in an orthologGroup with a single child (_infer_subhog.py:112-122)"})) +def test_orthoxml_schema(infer_run, dataset, rhogid): + tree = infer_run(dataset).orthoxml(rhogid) + # characterises current behaviour: the per-rootHOG debug file has no version/originVersion attributes + tree.getroot().set("version", "0.5") + tree.getroot().set("originVersion", "test") + assert orthoxml_validation_errors(tree) == [] + + +@pytest.mark.parametrize("dataset,rhogid", ROOTHOGS) +def test_parallel_and_sequential_give_same_hogs(infer_run, dataset, rhogid): + seq = infer_run(dataset).orthoxml(rhogid) + par = infer_run(dataset, parallel=True).orthoxml(rhogid) + assert canonical_orthoxml(seq) == canonical_orthoxml(par) + + +@pytest.mark.parametrize("dataset,rhogid", ROOTHOGS) +def test_output_pickle_matches_orthoxml(infer_run, dataset, rhogid): + run = infer_run(dataset) + with open(run.workdir / "pickles" / f"file_{rhogid}.pickle", "rb") as fh: + groups = pickle.load(fh) + assert len(groups) == len(run.orthoxml(rhogid).find(".//o:groups", NS)) + + +def test_cli_defaults(subhog_conf): + conf = subhog_conf() + assert conf.threshold_dubious_sd == pytest.approx(0.1) + assert conf.number_of_samples_per_hog == 5 + assert conf.overlap_fragments == pytest.approx(0.15) + assert conf.gene_rooting_method == "midpoint" + assert conf.msa_filter_method == "col-row-threshold" + assert (conf.gap_ratio_row, conf.gap_ratio_col, conf.min_col_trim) == (0.3, 0.5, 50) + assert (conf.store_completeness_score, conf.store_implied_losses_score, conf.store_tcs_score) == (True, True, True) + assert not conf.parallel and not conf.msa_write and not conf.gene_trees_write + assert conf.output_pickles == "pickle_hogs" diff --git a/tests/test_testutils.py b/tests/test_testutils.py new file mode 100644 index 0000000..4b8e748 --- /dev/null +++ b/tests/test_testutils.py @@ -0,0 +1,122 @@ +"""Tests for the shared test infrastructure (conftest fixtures and fastoma_testutils).""" +import gzip + +import pytest +from lxml import etree + +from conftest import TESTDATA_DIR +from fastoma_testutils import ( + canonical_orthoxml, + geneRef_counts, + orthoxml_element_counts, + orthoxml_validation_errors, + read_group_partition, + read_pairs, +) + +EXPECTED = TESTDATA_DIR / "expected_output" + +ORTHOXML_TEMPLATE = """ + + + + + + + + + {children} + + +""" + + +def _xml(gid, children): + return etree.fromstring(ORTHOXML_TEMPLATE.format(gid=gid, children=children).encode()).getroottree() + + +class TestConfFixtures: + def test_subhog_conf_has_cli_defaults(self, subhog_conf): + conf = subhog_conf() + assert conf.number_of_samples_per_hog == 5 + assert conf.msa_filter_method == "col-row-threshold" + assert conf.gap_ratio_row == 0.3 and conf.gap_ratio_col == 0.5 + assert conf.store_tcs_score is True + + def test_subhog_conf_overrides_and_rejects_unknown_options(self, subhog_conf): + assert subhog_conf(number_of_samples_per_hog=3).number_of_samples_per_hog == 3 + assert subhog_conf().number_of_samples_per_hog == 5, "overrides must not leak into the base conf" + with pytest.raises(AttributeError): + subhog_conf(no_such_option=1) + + def test_roothog_conf_has_cli_defaults(self, roothog_conf): + conf = roothog_conf() + assert conf.mergHOG_fscore_thresh == 70 + assert conf.big_rhog_size == 50_000 + assert conf.min_sequence_length == 50 + + +class TestPartitionsAndPairs: + def test_partition_ignores_group_names_and_row_order(self, tmp_path): + a = tmp_path / "a.tsv" + b = tmp_path / "b.tsv" + a.write_text("RootHOG\tProtein\nHOG:1\tx\nHOG:1\ty\nHOG:2\tz\n") + b.write_text("RootHOG\tProtein\nHOG:7\tz\nHOG:9\ty\nHOG:9\tx\n") + assert read_group_partition(a) == read_group_partition(b) + assert read_group_partition(a) == {frozenset({"x", "y"}), frozenset({"z"})} + + def test_pairs_are_unordered_and_read_gzip(self, tmp_path): + p = tmp_path / "pairs.tsv.gz" + with gzip.open(p, "wt") as fh: + fh.write("a\tb\nc\td\n") + assert read_pairs(p) == {frozenset("ab"), frozenset("dc")} + + +class TestCanonicalOrthoxml: + def test_independent_of_ids_and_child_order(self): + x1 = _xml("HOG:1", '') + x2 = _xml("HOG:99", '') + assert canonical_orthoxml(x1) == canonical_orthoxml(x2) + + def test_detects_changed_nesting(self): + flat = _xml("HOG:1", '') + dup = _xml("HOG:1", '') + assert canonical_orthoxml(flat) != canonical_orthoxml(dup) + + def test_geneRef_counts_use_protids(self): + x = _xml("HOG:1", '') + assert geneRef_counts(x) == {"a1": 1, "b1": 1} + + def test_schema_rejects_invalid_document(self): + # a paralogGroup with a single child violates the OrthoXML 0.5 schema + bad = _xml("HOG:1", '') + assert orthoxml_validation_errors(bad) + + +class TestExpectedOutputConsistency: + """testdata/expected_output is what the README promises; its files must agree with each other.""" + + def test_orthoxml_is_schema_valid(self): + assert orthoxml_validation_errors(EXPECTED / "FastOMA_HOGs.orthoxml") == [] + + def test_every_protein_is_referenced_exactly_once(self): + counts = geneRef_counts(EXPECTED / "FastOMA_HOGs.orthoxml") + assert counts and max(counts.values()) == 1 + assert orthoxml_element_counts(EXPECTED / "FastOMA_HOGs.orthoxml")["gene"] == len(counts) + + def test_roothogs_match_orthoxml_top_level_groups(self): + tree = etree.parse(str(EXPECTED / "FastOMA_HOGs.orthoxml")) + canonical = canonical_orthoxml(tree) + + def genes(group): + if group[0] == "gene": + return {group[1]} + return set().union(*(genes(c) for c in group[2])) + + from_xml = frozenset(frozenset(genes(g)) for g in canonical) + assert from_xml == read_group_partition(EXPECTED / "RootHOGs.tsv") + + def test_orthologous_groups_are_within_roothogs(self): + roothogs = read_group_partition(EXPECTED / "RootHOGs.tsv") + for og in read_group_partition(EXPECTED / "OrthologousGroups.tsv"): + assert any(og <= rh for rh in roothogs), og diff --git a/tests/test_transformer.py b/tests/test_transformer.py new file mode 100644 index 0000000..8201d1a --- /dev/null +++ b/tests/test_transformer.py @@ -0,0 +1,45 @@ +"""Tests for the fasta header -> protein id transformers (FastOMA/transformer.py).""" +import pytest + +from FastOMA.transformer import ( + ExtractUniProtAccessionFastaHeaderTransformer, + NoOpFastaHeaderTransformer, + header_transformer, +) + + +@pytest.mark.parametrize("name, cls", [("noop", NoOpFastaHeaderTransformer), ("NOOP", NoOpFastaHeaderTransformer), + ("UniProt", ExtractUniProtAccessionFastaHeaderTransformer), + ("uniprot", ExtractUniProtAccessionFastaHeaderTransformer)]) +def test_factory(name, cls): + assert isinstance(header_transformer(name), cls) + + +def test_factory_unknown_name_returns_none(): + # characterises current behaviour: argparse choices guard the CLI, the factory itself does not + assert header_transformer("foo") is None + + +def test_noop(): + assert header_transformer("noop").transform("sp|P68250|1433B_BOVIN") == "sp|P68250|1433B_BOVIN" + + +@pytest.mark.parametrize("header, expected", [ + ("sp|P68250|1433B_BOVIN", "P68250"), + ("tr|A0A0N7KCI6|A0A0N7KCI6_ORYSJ", "A0A0N7KCI6"), + ("sp|Q9XYZ1|", "Q9XYZ1"), + ("ts|P68250|x", "P68250"), # prefix is any two of [sptr] +]) +def test_uniprot_extracts_accession(header, expected): + assert header_transformer("UniProt").transform(header) == expected + + +@pytest.mark.parametrize("header", [ + "sp|P68250-2|1433B_BOVIN", # isoform accessions are not recognised + "P68250", + "sp|p68250|x", + "ENSP00000354587", +]) +def test_uniprot_keeps_unrecognised_headers(header, caplog): + assert header_transformer("UniProt").transform(header) == header + assert "cannot extract uniprot accession" in caplog.text diff --git a/tests/test_unionfind.py b/tests/test_unionfind.py new file mode 100644 index 0000000..f67fe5f --- /dev/null +++ b/tests/test_unionfind.py @@ -0,0 +1,60 @@ +"""Tests for the UnionFind data structure (zoo/unionfind.py and its copy in _utils_roothog.py).""" +import pytest + +from FastOMA import _utils_roothog +from FastOMA.zoo import unionfind + + +@pytest.fixture(params=[unionfind.UnionFind, _utils_roothog.UnionFind], ids=["zoo", "utils_roothog"]) +def UF(request): + return request.param + + +def components(uf): + return {frozenset(c) for c in uf.get_components()} + + +def test_union_and_find(UF): + uf = UF() + uf.union("a", "b") + uf.union("c", "d", "e") + uf.union("b", "e") + uf.union("x") + assert components(uf) == {frozenset("abcde"), frozenset("x")} + assert uf["a"] == uf.find("d") + + +def test_initial_elements_are_singletons(UF): + uf = UF([1, 2, 3]) + uf.union(1, 2) + assert components(uf) == {frozenset({1, 2}), frozenset({3})} + + +def test_find_unknown_creates_singleton(UF): + uf = UF() + assert uf.find("q") == "q" + assert list(uf) == ["q"] + + +def test_union_without_arguments(UF): + # characterises current behaviour + with pytest.raises(ValueError): + UF().union() + + +def test_remove_non_root(UF): + uf = UF() + uf.union("a", "b", "c") + member = next(x for x in "abc" if x != uf["a"]) + uf.remove(member) + assert components(uf) == {frozenset(set("abc") - {member})} + + +@pytest.mark.xfail(strict=True, reason="BUG: removing the representative of a set breaks find() for the " + "other members (KeyError, zoo/unionfind.py:78)") +def test_remove_root(UF): + uf = UF() + uf.union("a", "b", "c") + root = uf["a"] + uf.remove(root) + assert components(uf) == {frozenset(set("abc") - {root})}