diff --git a/.github/workflows/deploy-docs.yaml b/.github/workflows/deploy-docs.yaml new file mode 100644 index 0000000..3e4da89 --- /dev/null +++ b/.github/workflows/deploy-docs.yaml @@ -0,0 +1,103 @@ +name: Build & Deploy MkDocs (gh-pages with PR previews) + +on: + workflow_dispatch: + pull_request: + branches: [ main ] + types: [opened, synchronize, reopened, closed] + push: + branches: [ main ] + +permissions: + contents: write + pages: write + +jobs: + build: + # Run for push, workflow dispatch, PRs from SAME repo that are not closed + if: | + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.fork == false && + github.event.action != 'closed') + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install deps + run: | + python -m pip install --upgrade pip + pip install '.[docs]' + - name: Build with MkDocs + run: mkdocs build + - name: Upload built site as artifact + uses: actions/upload-artifact@v4 + with: + name: site + path: ./site + + deploy: + needs: build + # Deploy on push to main (root) or PRs from SAME repo (not closed) -> pr-/ + if: | + github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.fork == false && + github.event.action != 'closed') + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Download built site + uses: actions/download-artifact@v4 + with: + name: site + path: ./site + - name: Deploy to gh-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: ./site + keep_files: true + destination_dir: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }} + + cleanup: + # Only when a same-repo PR closes + if: > + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.fork == false && + github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - name: Checkout gh-pages + uses: actions/checkout@v4 + with: + ref: gh-pages + fetch-depth: 0 + - name: Configure git author + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Remove preview folder + shell: bash + run: | + set -euo pipefail + PR_DIR="pr-${{ github.event.number }}" + echo "Attempting to remove $PR_DIR" + if [ -d "$PR_DIR" ]; then + git rm -r "$PR_DIR" + git commit -m "Remove preview for PR #${{ github.event.number }}" + git push origin gh-pages + else + echo "No preview folder $PR_DIR found; nothing to do." + fi diff --git a/docs/_assets/taxonopy_banner.svg b/docs/_assets/taxonopy_banner.svg new file mode 100755 index 0000000..488f2b2 --- /dev/null +++ b/docs/_assets/taxonopy_banner.svg @@ -0,0 +1,150 @@ + + + + diff --git a/docs/_assets/taxonopy_logo.svg b/docs/_assets/taxonopy_logo.svg new file mode 100755 index 0000000..471f7af --- /dev/null +++ b/docs/_assets/taxonopy_logo.svg @@ -0,0 +1,129 @@ + + + + diff --git a/docs/_scripts/gen_cli_help_docs.py b/docs/_scripts/gen_cli_help_docs.py new file mode 100644 index 0000000..e9be7f5 --- /dev/null +++ b/docs/_scripts/gen_cli_help_docs.py @@ -0,0 +1,61 @@ +"""Generate CLI help pages for MkDocs without shell execution.""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +import mkdocs_gen_files + +ROOT = Path(__file__).resolve().parents[2] +SRC = ROOT / "src" +if str(SRC) not in sys.path: + sys.path.insert(0, str(SRC)) + +from taxonopy.cli import create_parser # noqa: E402 + + +def get_subparser(parser: argparse.ArgumentParser, name: str) -> argparse.ArgumentParser: + for action in parser._actions: + if isinstance(action, argparse._SubParsersAction): + if name in action.choices: + return action.choices[name] + raise KeyError(f"Subparser '{name}' not found") + + +def render_section(title: str, help_text: str) -> str: + return f"## `{title}`\n\n```console\n{help_text.rstrip()}\n```\n" + + +def main() -> None: + parser = create_parser() + parser.prog = "taxonopy" + resolve_parser = get_subparser(parser, "resolve") + trace_parser = get_subparser(parser, "trace") + trace_entry_parser = get_subparser(trace_parser, "entry") + common_parser = get_subparser(parser, "common-names") + + resolve_parser.prog = "taxonopy resolve" + trace_parser.prog = "taxonopy trace" + trace_entry_parser.prog = "taxonopy trace entry" + common_parser.prog = "taxonopy common-names" + + sections = [ + ("taxonopy --help", parser.format_help()), + ("taxonopy resolve --help", resolve_parser.format_help()), + ("taxonopy trace --help", trace_parser.format_help()), + ("taxonopy trace entry --help", trace_entry_parser.format_help()), + ("taxonopy common-names --help", common_parser.format_help()), + ] + + with mkdocs_gen_files.open("command_line_usage/help.md", "w") as file_handle: + file_handle.write("# Help\n\n") + file_handle.write("Command reference for the TaxonoPy CLI.\n\n") + for title, help_text in sections: + file_handle.write(render_section(title, help_text)) + file_handle.write("\n") + + +if __name__ in {"__main__", ""}: + main() diff --git a/docs/acknowledgements.md b/docs/acknowledgements.md new file mode 100644 index 0000000..ffe3664 --- /dev/null +++ b/docs/acknowledgements.md @@ -0,0 +1,3 @@ +# Acknowledgments + +The [Imageomics Institute](https://imageomics.org/) is supported by the National Science Foundation under [Award No. 2118240](https://www.nsf.gov/awardsearch/showAward?AWD_ID=2118240) "HDR Institute: Imageomics: A New Frontier of Biological Information Powered by Knowledge-Guided Machine Learning." Any opinions, findings and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation. \ No newline at end of file diff --git a/docs/command_line_usage/help.md b/docs/command_line_usage/help.md new file mode 100644 index 0000000..f6e5b40 --- /dev/null +++ b/docs/command_line_usage/help.md @@ -0,0 +1,29 @@ +# Help +You may view the help for the command line interface by running: + +```bash +taxonopy --help +``` +This will show you the available commands and options: + +``` +usage: taxonopy [-h] [--cache-dir CACHE_DIR] [--show-cache-path] [--cache-stats] [--clear-cache] [--show-config] [--version] {resolve,trace,common-names} ... + +TaxonoPy: Resolve taxonomic names using GNVerifier and trace data provenance. + +positional arguments: + {resolve,trace,common-names} + resolve Run the taxonomic resolution workflow + trace Trace data provenance of TaxonoPy objects + common-names Merge vernacular names (post-process) into resolved outputs + +options: + -h, --help show this help message and exit + --cache-dir CACHE_DIR + Directory for TaxonoPy cache (can also be set with TAXONOPY_CACHE_DIR environment variable) (default: None) + --show-cache-path Display the current cache directory path and exit (default: False) + --cache-stats Display statistics about the cache and exit (default: False) + --clear-cache Clear the TaxonoPy object cache. May be used in isolation. (default: False) + --show-config Show current configuration and exit (default: False) + --version Show version number and exit +``` \ No newline at end of file diff --git a/docs/command_line_usage/tutorial.md b/docs/command_line_usage/tutorial.md new file mode 100644 index 0000000..a699f45 --- /dev/null +++ b/docs/command_line_usage/tutorial.md @@ -0,0 +1,90 @@ +# Command Line Tutorial + +**Command ```resolve```:** +The ```resolve``` command is used to perform taxonomic resolution on a dataset. It takes a directory of Parquet partitions as input and outputs a directory of resolved Parquet partitions. +``` +usage: taxonopy resolve [-h] -i INPUT -o OUTPUT_DIR [--output-format {csv,parquet}] [--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [--log-file LOG_FILE] [--force-input] [--batch-size BATCH_SIZE] [--all-matches] + [--capitalize] [--fuzzy-uninomial] [--fuzzy-relaxed] [--species-group] [--refresh-cache] + +options: + -h, --help show this help message and exit + -i, --input INPUT Path to input Parquet or CSV file/directory + -o, --output-dir OUTPUT_DIR + Directory to save resolved and unsolved output files + --output-format {csv,parquet} + Output file format + --log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL} + Set logging level + --log-file LOG_FILE Optional file to write logs to + --force-input Force use of input metadata without resolution + +GNVerifier Settings: + --batch-size BATCH_SIZE + Max number of name queries per GNVerifier API/subprocess call + --all-matches Return all matches instead of just the best one + --capitalize Capitalize the first letter of each name + --fuzzy-uninomial Enable fuzzy matching for uninomial names + --fuzzy-relaxed Relax fuzzy matching criteria + --species-group Enable group species matching + +Cache Management: + --refresh-cache Force refresh of cached objects (input parsing, grouping) before running. +``` +It is recommended to keep GNVerifier settings at their defaults. + +**Command ```trace```**: +The ```trace``` command is used to trace the provenance of a taxonomic entry. It takes a UUID and an input path as arguments and outputs the full path of the entry through TaxonoPy. +``` +usage: taxonopy trace [-h] {entry} ... + +positional arguments: + {entry} + entry Trace an individual taxonomic entry by UUID + +options: + -h, --help show this help message and exit + +usage: taxonopy trace entry [-h] --uuid UUID --from-input FROM_INPUT [--format {json,text}] [--verbose] + +options: + -h, --help show this help message and exit + --uuid UUID UUID of the taxonomic entry + --from-input FROM_INPUT + Path to the original input dataset + --format {json,text} Output format + --verbose Show full details including all UUIDs in group +``` + +**Command ```common-names```:** +The ```common-names``` command is used to merge vernacular names into the resolved output. It takes a directory of resolved Parquet partitions as input and outputs a directory of resolved Parquet partitions with common names. + +``` +usage: taxonopy common-names [-h] --resolved-dir ANNOTATION_DIR --output-dir OUTPUT_DIR + +options: + -h, --help show this help message and exit + --resolved-dir ANNOTATION_DIR + Directory containing your *.resolved.parquet files + --output-dir OUTPUT_DIR + Directory to write annotated .parquet files +``` + +Note that the ```common-names``` command is a post-processing step and should be run after the ```resolve``` command. + +## Example Usage +To perform taxonomic resolution on a dataset with subsequent common name annotation, run: +``` +taxonopy resolve \ + --input /path/to/formatted/input \ + --output-dir /path/to/resolved/output +``` +``` +taxonopy common-names \ + --resolved-dir /path/to/resolved/output \ + --output-dir /path/to/resolved_with_common-names/output +``` +TaxonoPy creates a cache of the objects associated with input entries for use with the ```trace``` command. By default, this cache is stored in the ```~/.cache/taxonopy``` directory. + +## Development + +See the [Wiki Development Page](https://github.com/Imageomics/TaxonoPy/wiki/Development) for development instructions. \ No newline at end of file diff --git a/docs/development/contributing/index.md b/docs/development/contributing/index.md new file mode 100644 index 0000000..be90067 --- /dev/null +++ b/docs/development/contributing/index.md @@ -0,0 +1,5 @@ +# Contributing + +We welcome contributions to TaxonoPy. More detailed guidance will be added here. + +If you have suggestions or run into a bug, please open an issue at [https://github.com/Imageomics/TaxonoPy/issues](https://github.com/Imageomics/TaxonoPy/issues). diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..c4468ac --- /dev/null +++ b/docs/index.md @@ -0,0 +1,68 @@ +--- +title: Home +hide: + - title +--- + +# TaxonoPy {: .taxonopy-home-title } + +![TaxonoPy banner](_assets/taxonopy_banner.svg) + +

Reproducibly Aligned Biological Taxonomies

+
+ + DOI + + + PyPI - Version + + + PyPI - Python Version + +
+ +TaxonoPy (taxon-o-py) is a command-line tool for creating reproducibly aligned biological taxonomies using the [Global Names Verifier (gnverifier)](https://github.com/gnames/gnverifier). + +## Package Purpose +TaxonoPy aligns data to a single, internally consistent 7-rank Linnaean taxonomic hierarchy across large biodiversity datasets assembled from multiple providers, each of which may use overlapping but nonuniform taxonomies. The goal is AI-ready biodiversity data with clean, aligned taxonomy. + +Its development has been driven by its application in the [TreeOfLife-200M dataset](https://huggingface.co/datasets/imageomics/TreeOfLife-200M). This dataset contains over 200 million labeled images of organisms from four core data providers: + +- [The Global Biodiversity Information Facility (GBIF)](https://www.gbif.org/) +- [BIOSCAN-5M](https://biodiversitygenomics.net/projects/5m-insects/) +- [FathomNet](https://www.fathomnet.org/) +- [The Encyclopedia of Life (EOL)](https://eol.org/) + +Across these resources, taxon names and classifications often conflict. TaxonoPy resolves those differences into a coherent, standardized taxonomy for the combined dataset. + +## Challenges +The taxonomy information is provided by each data provider and original sources, but the classification can be: + +- **Inconsistent** — between and within sources (e.g., kingdom *Metazoa* vs. *Animalia*) +- **Incomplete** — missing ranks or containing "holes" +- **Incorrect** — spelling errors, nonstandard terms, or outdated classifications +- **Ambiguous** — homonyms, synonyms, and terms with multiple interpretations + +Taxonomic authorities exist to standardize classification, but: + +- There are multiple authorities +- They may disagree +- A given organism may be missing from some + +## Solution +TaxonoPy uses the the taxonomic lineages provided by diverse sources to submit batched queries to GNVerifier and resolve to a standardized classification path for each sample in the dataset. It is currently configured to prioritize alignment to the [GBIF Backbone Taxonomy](https://verifier.globalnames.org/data_sources/11). Where GBIF misses, backup sources of the [Catalogue of Life](https://verifier.globalnames.org/data_sources/1) and [Open Tree of Life (OTOL) Reference Taxonomy](https://verifier.globalnames.org/data_sources/179) are used. + +## Getting Started +To get started with TaxonoPy, see the [Quick Reference](user-guide/quick-reference.md) guide. + +--- + +!!! warning + Taxonomic classifications are human-constructed models of biological diversity, not direct representations of biological reality. + Names and ranks reflect taxonomic concepts that may vary between authorities, evolve over time, and differ in scope or interpretation. + + TaxonoPy aims to produce a **consistent, transparent, and fit-for-purpose classification** suitable for large-scale data integration and AI workflows. + It prioritizes internal coherence and interoperability across datasets and providers by aligning source data to a selected reference taxonomy. + + It is a progressive effort to improve taxonomic alignment in an evolving landscape. + If you have suggestions or encounter bugs, please see the [Contributing](development/contributing/index.md) page. \ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..eeb4204 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,123 @@ +/* Brand palette aligned to the TaxonoPy banner/logo. */ + +/* Light mode */ +:root, +[data-md-color-scheme="default"] { + /* Deep forest green from the wordmark */ + --md-primary-fg-color: #16381f; + --md-primary-fg-color--light: #2a5a3b; + --md-primary-fg-color--dark: #0f2a17; + + /* Teal-blue from the branches/"Py" */ + --md-accent-fg-color: #2d94b8; + --md-accent-fg-color--transparent: rgba(45, 148, 184, 0.16); + + /* Link styling: green by default, blue on hover for clarity. */ + --taxonopy-link-color: #2e7a45; + --taxonopy-link-hover-color: #2d94b8; + --md-typeset-a-color: var(--taxonopy-link-color); + + /* Subtle brand tint for UI surfaces */ + --taxonopy-surface-tint: #dcebd2; + + /* Resolution table highlights */ + --taxonopy-cell-added-bg: rgba(46, 122, 69, 0.22); + --taxonopy-cell-changed-bg: rgba(255, 193, 7, 0.24); +} + +/* Dark mode tweaks to keep contrast crisp on slate */ +[data-md-color-scheme="slate"] { + --md-primary-fg-color: #1f5a32; + --md-primary-fg-color--light: #2e7a45; + --md-primary-fg-color--dark: #163d23; + + --md-accent-fg-color: #2d94b8; + --md-accent-fg-color--transparent: rgba(45, 148, 184, 0.20); + + /* Link styling: green by default, blue on hover for clarity. */ + --taxonopy-link-color: #43a463; + --taxonopy-link-hover-color: #2d94b8; + --md-typeset-a-color: var(--taxonopy-link-color); + + --taxonopy-surface-tint: #1b2a20; + + /* Resolution table highlights */ + --taxonopy-cell-added-bg: rgba(88, 214, 141, 0.28); + --taxonopy-cell-changed-bg: rgba(255, 214, 102, 0.28); +} + +/* Apply the tint very lightly to header/nav surfaces. */ +.md-header, +.md-tabs { + background-color: var(--md-primary-fg-color); +} + +.md-tabs { + box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.08); +} + +/* Make the header logo slightly larger. */ +.md-header__button.md-logo img { + height: 1.9rem; +} + +/* Hide the home page title so the banner is the first visible element. */ +.taxonopy-home-title { + display: none; +} + +/* Ensure link hover color matches the accent in both themes. */ +.md-typeset a:hover, +.md-typeset a:focus { + color: var(--taxonopy-link-hover-color); +} + +/* Tighten row height in the quick-reference table. */ +.table-cell-scroll table th, +.table-cell-scroll table td { + padding: 0.25rem 0.5rem; + line-height: 1.15; + vertical-align: top; +} + +/* Attempt per-cell horizontal scrolling without extra markup. */ +.table-cell-scroll table { + table-layout: fixed; + width: 100%; +} + +.table-cell-scroll th, +.table-cell-scroll td { + max-width: 16ch; + overflow-x: auto; + white-space: nowrap; +} + +.table-cell-scroll th::-webkit-scrollbar, +.table-cell-scroll td::-webkit-scrollbar { + height: 6px; +} + +figure.table-caption > figcaption { + margin: 0 0 0.45rem; + text-align: left; + font-style: normal; + font-size: 0.95em; + color: var(--md-default-fg-color--light); +} + +/* Highlight resolved output differences vs input. */ +.cell-added, +.cell-changed { + display: inline-block; + padding: 0 0.2rem; + border-radius: 0.2rem; +} + +.cell-added { + background: var(--taxonopy-cell-added-bg, rgba(46, 122, 69, 0.18)); +} + +.cell-changed { + background: var(--taxonopy-cell-changed-bg, rgba(255, 193, 7, 0.22)); +} diff --git a/docs/user-guide/cache.md b/docs/user-guide/cache.md new file mode 100644 index 0000000..91a8e80 --- /dev/null +++ b/docs/user-guide/cache.md @@ -0,0 +1,36 @@ +# Cache + +TaxonoPy caches intermediate results (like parsed inputs and grouped entries) to +speed up repeated runs on the same dataset. + +## Location + +By default, the cache lives under: + +- `~/.cache/taxonopy` + +You can override this with: + +- `TAXONOPY_CACHE_DIR` environment variable, or +- `--cache-dir` CLI flag. + +## Namespaces and Reproducibility + +Each `resolve` run uses a cache namespace derived from: + +- the command name, +- the TaxonoPy version, and +- a fingerprint of the input files (paths + size + modified time). + +This keeps caches isolated across datasets and releases. + +## Useful CLI Flags + +- `--show-cache-path` — print the active cache directory and exit. +- `--cache-stats` — show cache statistics and exit. +- `--clear-cache` — remove cached objects. +- `--refresh-cache` (resolve only) — ignore cached parse/group results. +- `--full-rerun` (resolve only) — clears cache for the input and overwrites outputs. + +If you change input files or want to force a clean run, use `--refresh-cache` or +`--full-rerun`. diff --git a/docs/user-guide/installation.md b/docs/user-guide/installation.md new file mode 100644 index 0000000..d3b2181 --- /dev/null +++ b/docs/user-guide/installation.md @@ -0,0 +1,26 @@ +# Installation + +TaxonoPy can be installed with pip after setting up a virtual environment. + +```console +pip install taxonopy +``` + +## GNVerifier Dependency + +TaxonoPy relies on the GNVerifier CLI to resolve taxonomic names. When you run +`taxonopy resolve`, it will automatically try the following: + +1. **Docker (recommended).** If Docker is available, TaxonoPy checks for the + configured GNVerifier image (default: `gnames/gnverifier:v1.2.5`) and pulls it + if needed. The first run may take a bit longer while the image downloads. + See [gnames/gnverifier on Docker Hub](https://hub.docker.com/r/gnames/gnverifier). +2. **Local GNVerifier.** If Docker is unavailable or the image pull fails, + TaxonoPy looks for a local `gnverifier` binary on your `PATH`. The version + used will be whatever is installed on your system, which may differ from the + pinned container version. For install instructions, see the GNVerifier README: + [gnverifier installation](https://github.com/gnames/gnverifier?tab=readme-ov-file#installation). + +If neither Docker nor a local GNVerifier is available, TaxonoPy will raise an +error when you attempt to resolve names. In that case, install Docker or install +GNVerifier locally and ensure the `gnverifier` command is available. diff --git a/docs/user-guide/io/index.md b/docs/user-guide/io/index.md new file mode 100644 index 0000000..9600f4f --- /dev/null +++ b/docs/user-guide/io/index.md @@ -0,0 +1,9 @@ +# IO + +TaxonoPy accepts CSV or Parquet inputs with the same schema. Use the pages below +for the exact input columns, the structure of resolved/unsolved outputs, and how +the cache supports provenance and transparency throughout the resolution process. + +- [Input](input.md) +- [Output](output.md) +- [Cache](../cache.md) diff --git a/docs/user-guide/io/input.md b/docs/user-guide/io/input.md new file mode 100644 index 0000000..13b60eb --- /dev/null +++ b/docs/user-guide/io/input.md @@ -0,0 +1,14 @@ +# Input + +Provide a file or directory containing 7-rank Linnaean taxonomic metadata. +Inputs may be CSV or Parquet. Expected columns include: + +- **uuid**: a unique identifier for each sample (required). +- **kingdom, phylum, class, order, family, genus, species**: the taxonomic ranks of the organism (required, may have sparsity). +- **scientific_name**: the scientific name to the most specific rank available (optional). +- **common_name**: the common (i.e. vernacular) name of the organism (optional). + +## Example Files + +- [sample.parquet](https://raw.githubusercontent.com/Imageomics/TaxonoPy/main/examples/input/sample.parquet) +- [sample.csv](https://raw.githubusercontent.com/Imageomics/TaxonoPy/main/examples/input/sample.csv) diff --git a/docs/user-guide/io/output.md b/docs/user-guide/io/output.md new file mode 100644 index 0000000..614e5e8 --- /dev/null +++ b/docs/user-guide/io/output.md @@ -0,0 +1,21 @@ +# Output + +When you run `taxonopy resolve`, TaxonoPy writes two outputs for each input file: + +- **Resolved**: `.resolved.` +- **Unsolved**: `.unsolved.` + +The output directory mirrors the input directory structure. Output format is +controlled by the `--output-format` flag (`csv` or `parquet`). + +## What’s Inside + +Each output row corresponds to one input record. Resolved entries contain the +standardized taxonomy where available, while unsolved entries preserve the +original input ranks. Both outputs include resolution metadata such as status +and strategy information. + +## Example Files + +- `examples/resolved/sample.resolved.parquet` (generated with `taxonopy resolve`) +- `examples/resolved_with_common_names/sample.resolved.parquet` (generated with `taxonopy common-names`) diff --git a/docs/user-guide/quick-reference.md b/docs/user-guide/quick-reference.md new file mode 100644 index 0000000..1f504e4 --- /dev/null +++ b/docs/user-guide/quick-reference.md @@ -0,0 +1,130 @@ +# Quick Reference + +## Install + +```console +pip install taxonopy +``` + +For detailed setup instructions including GNVerifier and troubleshooting, see [Installation](installation.md). + +## Sample Input + +Download the same sample dataset in either format and place it in `examples/input/`: + +- [sample.parquet](https://raw.githubusercontent.com/Imageomics/TaxonoPy/main/examples/input/sample.parquet) +- [sample.csv](https://raw.githubusercontent.com/Imageomics/TaxonoPy/main/examples/input/sample.csv) + +_**Sample input**: Note the divergence in kingdoms (Metazoa vs Animalia), missing interior ranks, and fully null entry._ +
+ +| uuid | kingdom | phylum | class | order | family | genus | species | scientific_name | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| bc2a3f9f-c1f9-48df-9b01-d045475b9d5f | Metazoa | Chordata | Mammalia | Primates | Hominidae | Homo | Homo sapiens | Homo sapiens | +| 21ed76d8-9a3b-406e-a1a3-ef244422bf8e | Plantae | Tracheophyta | `null` | Fagales | Fagaceae | Quercus | Quercus alba | Quercus alba | +| 4d166a61-b6e5-4709-91ba-b623111014e9 | Animalia | `null` | `null` | Hymenoptera | Apidae | Apis | Apis mellifera | Apis mellifera | +| 85b96dc2-70ab-446e-afb5-6a4b92b0a450 | `null` | `null` | `null` | `null` | `null` | `null` | Amanita muscaria | `null` | +| 38327554-ffbf-4180-b4cf-63c311a26f4e | Animalia | `null` | `null` | `null` | `null` | `null` | Laelia rosea | `null` | +| 8f688a17-1f7a-42b2-b3dc-bd4c8fc0eee3 | Plantae | `null` | `null` | `null` | `null` | `null` | Laelia rosea | `null` | +| a95f3e29-ed48-41f4-9577-64d4243a0396 | `null` | `null` | `null` | `null` | `null` | `null` | `null` | `null` | + +
+ +In the final example entry, there is no available taxonomic data, which can happen in large datasets where there may be a corresponding image but incomplete annotation. + +## Execute a Basic Resolution + +```console +taxonopy resolve --input examples/input --output-dir examples/output +``` + +!!! note "Input values" + There are three kinds of values you can pass to `--input`: + + - A single file path (CSV or Parquet). + - A flat directory of partitioned files (TaxonoPy will glob everything inside). + - A directory tree (TaxonoPy will glob recursively and preserve the folder structure in the output). + + In all three cases, the base filename is preserved in the output. That is, the output keeps the original filename(s) and adds `.resolved` / `.unsolved` before the extension. + + If you download both `sample.csv` and `sample.parquet` into `examples/input/`, resolve will fail due to mixed input formats; keep only one format per input directory. + +The command above will read in the sample data from `examples/input/`, execute resolution, and write the results to `examples/output/`. + +By default, outputs are written to Parquet format, whether the input is CSV or Parquet. To set the output format to CSV, use the `--output-format csv` flag. + +The output files consist of: + +- `sample.resolved.parquet` +- `sample.unsolved.parquet` +- `resolution_stats.json` + +The `sample.resolved.parquet` file contains all the entries where some resolution strategy was applied. In this example, it contains: + + +_**Sample resolved output (selected columns)**: Green highlights show values added during resolution. Yellow highlights indicate values that changed from the input._ +
+ +| uuid | kingdom | phylum | class | order | family | genus | species | scientific_name | common_name | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| bc2a3f9f-c1f9-48df-9b01-d045475b9d5f | Animalia[?](https://verifier.globalnames.org/?all_matches=on&capitalize=on&ds=11&format=html&names=Homo+sapiens "The input lineage here mirrors what the Encyclopedia of Life provides (Metazoa as the clade that maps to the kingdom rank); when queried against GNVerifier, this rank maps to Animalia. Click to see the GNVerifier result.") | Chordata | Mammalia | Primates | Hominidae | Homo | Homo sapiens | Homo sapiens | `null` | +| 21ed76d8-9a3b-406e-a1a3-ef244422bf8e | Plantae | Tracheophyta | Magnoliopsida | Fagales | Fagaceae | Quercus | Quercus alba | Quercus alba | `null` | +| 4d166a61-b6e5-4709-91ba-b623111014e9 | Animalia | Arthropoda | Insecta | Hymenoptera | Apidae | Apis | Apis mellifera | Apis mellifera | `null` | +| 85b96dc2-70ab-446e-afb5-6a4b92b0a450 | Fungi | Basidiomycota | Agaricomycetes | Agaricales | Amanitaceae | Amanita | Amanita muscaria | `null` | `null` | +| 38327554-ffbf-4180-b4cf-63c311a26f4e | Animalia | Arthropoda | Insecta | Lepidoptera | Erebidae | Laelia | Laelia rosea | `null` | `null` | +| 8f688a17-1f7a-42b2-b3dc-bd4c8fc0eee3 | Plantae | Tracheophyta | Liliopsida | Asparagales | Orchidaceae | Laelia | Laelia rosea | `null` | `null` | + +
+ +The `sample.unsolved.parquet` file contains entries that could not be resolved (for example, rows with no usable taxonomy information). In this example, it contains: + + +_**Sample unsolved output: Sequestered entries with no usable taxonomy information.**_ +
+ +| uuid | kingdom | phylum | class | order | family | genus | species | scientific_name | common_name | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| a95f3e29-ed48-41f4-9577-64d4243a0396 | `null` | `null` | `null` | `null` | `null` | `null` | `null` | `null` | `null` | + +
+ +The `resolution_stats.json` file summarizes counts of how many entries from the input fell into each final status across the `resolved` and `unsolved` files. + +TaxonoPy also writes cache data to disk (default: `~/.cache/taxonopy`) so it can trace provenance and avoid reprocessing. Use `--show-cache-path`, `--cache-stats`, or `--clear-cache` if you want to inspect or manage it, or see the [Cache](cache.md) guide for details. + +## Trace an Entry + +You can trace how a single UUID was resolved. For example, let's trace one of the _Laelia rosea_ entries: + +```console +taxonopy trace entry --uuid 8f688a17-1f7a-42b2-b3dc-bd4c8fc0eee3 --from-input examples/input/sample.csv +``` + +TaxonoPy uses whatever rank context you provide (even if sparse) to disambiguate identical names. _Laelia rosea_ resolves differently under Animalia vs. Plantae as a hemihomonym. If higher ranks are missing, TaxonoPy would not have been able to disambiguate. + +Excerpt (incomplete) from the trace output: + +```json +{ + "query_plan": { + "term": "Laelia rosea", + "rank": "species", + "source_id": 11 + }, + "resolution_attempts": [ + { + "status": "EXACT_MATCH_PRIMARY_SOURCE_ACCEPTED_INNER_RANK_DISAMBIGUATION", + "resolution_strategy_name": "ExactMatchPrimarySourceAcceptedInnerRankDisambiguation", + "resolved_classification": { + "kingdom": "Plantae", + "phylum": "Tracheophyta", + "class_": "Liliopsida", + "order": "Asparagales", + "family": "Orchidaceae", + "genus": "Laelia", + "species": "Laelia rosea" + } + } + ] +} +``` diff --git a/examples/input/sample.csv b/examples/input/sample.csv new file mode 100644 index 0000000..9fc00ab --- /dev/null +++ b/examples/input/sample.csv @@ -0,0 +1,8 @@ +uuid,kingdom,phylum,class,order,family,genus,species,scientific_name +bc2a3f9f-c1f9-48df-9b01-d045475b9d5f,Metazoa,Chordata,Mammalia,Primates,Hominidae,Homo,Homo sapiens,Homo sapiens +21ed76d8-9a3b-406e-a1a3-ef244422bf8e,Plantae,Tracheophyta,,Fagales,Fagaceae,Quercus,Quercus alba,Quercus alba +4d166a61-b6e5-4709-91ba-b623111014e9,Animalia,,,Hymenoptera,Apidae,Apis,Apis mellifera,Apis mellifera +85b96dc2-70ab-446e-afb5-6a4b92b0a450,,,,,,,Amanita muscaria, +38327554-ffbf-4180-b4cf-63c311a26f4e,Animalia,,,,,,Laelia rosea, +8f688a17-1f7a-42b2-b3dc-bd4c8fc0eee3,Plantae,,,,,,Laelia rosea, +a95f3e29-ed48-41f4-9577-64d4243a0396,,,,,,,, diff --git a/examples/input/sample.parquet b/examples/input/sample.parquet index 2369663..c9eda3a 100644 Binary files a/examples/input/sample.parquet and b/examples/input/sample.parquet differ diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..52ea42c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,81 @@ +site_name: TaxonoPy User Guide +site_description: Documentation for the TaxonoPy package + +repo_url: https://github.com/Imageomics/taxonopy +repo_name: Imageomics/taxonopy + +nav: + - TaxonoPy: + - User guide: index.md + - Quick reference: user-guide/quick-reference.md + - CLI help reference: command_line_usage/help.md + - Installation: user-guide/installation.md + - IO: + - user-guide/io/index.md + - Input: user-guide/io/input.md + - Output: user-guide/io/output.md + - Cache: user-guide/cache.md + - Development: + - Contributing: + - development/contributing/index.md + - Acknowledgements: acknowledgements.md + +theme: + name: material + logo: _assets/taxonopy_logo.svg + favicon: _assets/taxonopy_logo.svg + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: green + accent: blue + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: green + accent: blue + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.tabs + - navigation.tabs.sticky + - navigation.indexes + - content.code.copy + - content.code.annotate + - content.tooltips + +extra_css: +- stylesheets/extra.css + +plugins: +- search +- mkdocstrings: + handlers: + python: + paths: [src] # search packages in the src folder + options: + docstring_style: google + merge_init_into_class: true +- gen-files: + scripts: + # Generates command_line_usage/help.md from argparse during build. + - docs/_scripts/gen_cli_help_docs.py +markdown_extensions: + - admonition + - attr_list + - md_in_html + - pymdownx.blocks.caption: + types: + - name: figure-caption + prefix: "" + - name: table-caption + prefix: "" + classes: "table-caption" + auto: false + prepend: true + - pymdownx.details + - pymdownx.highlight + - pymdownx.superfences diff --git a/pyproject.toml b/pyproject.toml index 13ba73a..5e57402 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,13 @@ nb = [ "jupyterlab", "marimo[recommended]" ] +docs = [ + "mkdocs", + "mkdocs-material", + "mkdocs-material-extensions", + "mkdocstrings-python", + "mkdocs-gen-files" +] [project.urls] Documentation = "https://github.com/Imageomics/TaxonoPy"