From 3086e4b07fb88cebed6bc51e666a78f4124c829e Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Thu, 21 May 2026 00:51:10 +0200 Subject: [PATCH 01/23] updated README --- CONTRIBUTING.md | 32 ++ README.md | 354 +++++------------- .../docs/source/images/metagraph_logo.png | Bin 0 -> 34533 bytes 3 files changed, 133 insertions(+), 253 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 metagraph/docs/source/images/metagraph_logo.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..ce9ac179c3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +# Contributing to MetaGraph + +## Building from source + +The full installation guide (custom alphabets, debug builds, dependencies) lives in the [online docs](https://metagraph.ethz.ch/static/docs/installation.html#install-from-source). + +### Build a docker container + +```bash +docker build . +``` + +### Makefile shortcuts + +The top-level `Makefile` wraps the common build / test invocations. Useful arguments: + +- `env`: `""` (host) or `docker` +- `alphabet`: e.g. `DNA`, `DNA5`, `Protein` (default `DNA`) +- `additional_cmake_args`: extra flags forwarded to CMake + +Example: + +```bash +# compile in a docker container for the DNA alphabet +make build-metagraph env=docker alphabet=DNA +``` + +## Releases + +1. Bump the version in `package.json`. +2. Tag the commit with the new version. +3. Create a GitHub release. diff --git a/README.md b/README.md index d6ec44b2db..aa87d7ee3e 100644 --- a/README.md +++ b/README.md @@ -1,316 +1,164 @@ -# Metagenome Graph Project +

+ MetaGraph +

+[![Linux](https://img.shields.io/badge/Linux-supported-brightgreen?logo=linux&logoColor=white)](#-quick-start) +[![macOS](https://img.shields.io/badge/macOS-supported-brightgreen?logo=apple&logoColor=white)](#-quick-start) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/ratschlab/metagraph)](https://github.com/ratschlab/metagraph/releases) +[![Bioconda version](https://img.shields.io/conda/vn/bioconda/metagraph)](https://bioconda.github.io/recipes/metagraph/README.html) [![bioconda downloads](https://img.shields.io/conda/dn/bioconda/metagraph?color=blue)](https://bioconda.github.io/recipes/metagraph/README.html) -[![install with conda](https://img.shields.io/badge/install%20with-conda-brightgreen.svg?style=flat)](#conda) -[![install with docker](https://img.shields.io/badge/install%20with-docker-brightgreen)](#docker) -[![install from source](https://img.shields.io/badge/install%20from-source-lightgrey)](#install-from-sources) -[![documentation](https://img.shields.io/badge/-online%20docs-grey)](https://metagraph.ethz.ch/static/docs/index.html) +[![License: GPLv3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +[![DOI](https://img.shields.io/badge/DOI-10.1038%2Fs41586--025--09603--w-blue)](https://doi.org/10.1038/s41586-025-09603-w) +[![documentation](https://img.shields.io/badge/πŸ“–-online%20docs-blue.svg)](https://metagraph.ethz.ch/static/docs/index.html) -MetaGraph is a tool for scalable construction of annotated genome graphs and sequence-to-graph alignment. +**Scalable indexing and querying of annotated genome graphs β€” from a handful of genomes to petabase-scale sequence repositories.** -The default index representations in MetaGraph are extremely scalable and support building graphs with trillions of nodes and millions of annotation labels. -At the same time, the provided workflows and their careful implementation, combined with low-level optimizations of the core data structures, enable exceptional query and alignment performance. +MetaGraph builds compact, searchable indexes over sequencing data: assemblies, raw reads, transcripts, whole bacterial collections. Once indexed, you can query for sequences in milliseconds, align reads against trillions of k-mers, and recover where every match came from. -#### Main features: -* Large-scale indexing of sequences -* [Python API](https://metagraph.ethz.ch/static/docs/api.html) for querying in the server mode -* Encoding [**k-mer counts**](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) (e.g., expression values) and [**k-mer coordinates**](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates) in source sequences (e.g., for lossless encoding of genomes) -* **Sequence alignment** against very large annotated graphs (sub-k seeding allows using arbitrarily short seeds) -* Scalable cleaning of very large de Bruijn graphs (to remove sequencing errors) -* Support for custom alphabets (e.g., {A,C,G,T,N} or amino acids) -* Algorithms for [differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly) +### Features -#### Design choices in MetaGraph: -* Use of succinct data structures and efficient representation schemes for extremely high scalability -* Algorithmic choices that work efficiently with succinct data structures (e.g., always prefer batched operations) -* Modular support of different graph and annotation representations -* Use of generic and extensible interfaces to support adding custom index representations / algorithms with little code overhead. +- ⚑ **Scale.** Indexes with trillions of k-mers and millions of annotation labels; petabase-scale collections of public sequencing data have been indexed end-to-end. +- πŸ”’ **[k-mer counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts)** and πŸ“ **[k-mer coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates).** Optional payloads alongside k-mer presence β€” for expression levels, alignment-position recovery, or losslessly encoding source genomes. +- 🧬 **Sequence alignment** against the full annotated graph, with sub-k seeding for arbitrarily short queries. +- 🧹 **Scalable graph cleaning** to strip sequencing errors out of very large de Bruijn graphs. +- πŸ”€ **Custom alphabets** β€” `{A,C,G,T}`, `{A,C,G,T,N}`, protein, or your own. +- πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly)** β€” extract sequences present in one group of samples and absent from another. -## Documentation -Online documentation is available at https://metagraph.ethz.ch/static/docs/index.html. Offline sources are [here](metagraph/docs/source). +> πŸ“– **Full documentation:** -## Citation +## 🌐 MetaGraph Online -If you are using MetaGraph or the index resources for your work, please cite: +Try MetaGraph without installing anything: hosts a public search engine over 50+ petabases of DNA, RNA, and protein sequences, indexing SRA, ENA, DRA, RefSeq, UniProt, and more. -> Karasikov M, Mustafa H, Danciu D, Kulkov O, Zimmermann M, Barber C, RΓ€tsch G, Kahles A. Efficient and accurate search in petabase-scale sequence repositories. *Nature*. 2025;647: 1036–1044. -> https://www.nature.com/articles/s41586-025-09603-w +## πŸš€ Quick start -
-BibTeX - -```bibtex -@article{karasikov2025metagraph, - title={Efficient and accurate search in petabase-scale sequence repositories}, - author={Karasikov, Mikhail and Mustafa, Harun and Danciu, Daniel and Kulkov, Oleksandr and Zimmermann, Marc and Barber, Christopher and R{\"a}tsch, Gunnar and Kahles, Andr{\'e}}, - journal={Nature}, - volume={647}, - number={8091}, - pages={1036--1044}, - year={2025}, - publisher={Nature Publishing Group}, - doi={10.1038/s41586-025-09603-w} -} -``` -
- -## Install - -### Conda - -Install the [latest release](https://github.com/ratschlab/metagraph/releases/latest) on Linux or Mac OS X with Anaconda: +### 1. Install -``` +```bash +conda create -n metagraph python +conda activate metagraph conda install -c bioconda -c conda-forge metagraph +pip install --force-reinstall "git+https://github.com/ratschlab/metagraph.git#subdirectory=metagraph/workflows" ``` -### Docker +### 2. Build an index -If docker is available on the system, immediately get started with +Clone the repo for the bundled test data, then build: +```bash +git clone https://github.com/ratschlab/metagraph.git && cd metagraph +metagraph-workflows build <(ls metagraph/tests/data/*.fa) -o out/ --primary ``` -docker pull ghcr.io/ratschlab/metagraph:master -docker run -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master \ - metagraph build -v -k 10 -o /mnt/transcripts_1000 /mnt/transcripts_1000.fa -``` -and replace `${HOME}` with a directory on the host system to map it under `/mnt` in the container. -By default, it executes the binary compiled for the `DNA` alphabet {A,C,G,T}. -To run the binary compiled for the `DNA5` or `Protein` alphabet, just replace `metagraph` with `metagraph_DNA5` or `metagraph_Protein`, respectively, e.g.: -``` -docker run -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master \ - metagraph_Protein build -v -k 10 -o /mnt/graph /mnt/protein.fa -``` +The positional argument accepts a file list (one path per line), a directory, or β€” as above β€” process substitution. -One can see that running MetaGraph with docker is very easy. Also, the following command (or similar) may be handy to see what directory is mounted in the container: -``` -docker run -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master ls /mnt -``` +For real workloads, supply your own sample list and a hardware budget: -For more complex workflows, consider running docker in the interactive mode: -``` -$ docker run -it --entrypoint /bin/bash -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master - -root@5c42291cc9cf:/# ls /mnt/ -root@5c42291cc9cf:/# metagraph --version +```bash +metagraph-workflows build samples.txt -o out/ --primary \ + -p 34 --mem-gb 70 --disk-swap-dir /scratch/swap ``` -All different versions of the container image are listed [here](https://github.com/ratschlab/metagraph/pkgs/container/metagraph). - -### Install From Sources +Per-stage memory caps, thread packing, and BRWT parameters are derived from the budget. See `metagraph-workflows build --help` for all options. -To compile from source (e.g., for builds with custom alphabet or other configurations), see [documentation online](https://metagraph.ethz.ch/static/docs/installation.html#install-from-source). +Outputs: `graph.dbg`, `graph_small.dbg`, and one `.annodbg` per requested format. +Under the hood, the workflow runs four `metagraph` stages β€” see [the pipeline docs](https://metagraph.ethz.ch/static/docs/quick_start.html) for each: -## Quick start: build an index in one command +1. `metagraph build` β€” graph construction +2. `metagraph annotate` β€” per-sample annotation +3. `metagraph transform_anno --anno-type row_diff` β€” row-diff transform (stages 0–2) +4. `metagraph transform_anno --anno-type *_brwt` β€” BRWT clustering -For most users, the easiest entry point is the Snakemake wrapper, which -runs the full indexing pipeline β€” graph construction, annotation, and -all row-diff / BRWT transforms β€” as a single command. +### 3. Query -The wrapper ships as a separate Python package; the `metagraph` conda -recipe only installs the C++ binary, so the workflow CLI needs an extra -`pip install` step: +The default `--anno-type` produces `.relax.row_diff_brwt.annodbg`. Query it against the graph (any fasta works as input β€” the bundled test data is fine for a smoke test): ```bash -conda install -c bioconda -c conda-forge metagraph # the metagraph binary -pip install -U "git+https://github.com/ratschlab/metagraph.git#subdirectory=metagraph/workflows" +metagraph query --query-mode matches -p 8 \ + -i out/graph.dbg -a out/graph.relax.row_diff_brwt.annodbg \ + metagraph/tests/data/transcripts_100.fa ``` -Then run the full pipeline as: +For the [Python API](https://metagraph.ethz.ch/static/docs/api.html), see the online docs. -```bash -metagraph-workflows build samples.txt -o out/ --primary -``` +
+πŸ”§ Direct metagraph CLI (align / assemble / stats / ...) -`samples.txt` is a text file listing your input sample paths (one per -line); a directory of sample files works just as well. `out/` will -contain `graph.dbg`, `graph_small.dbg`, and the requested annotation -artifacts. You can also feed a list inline with process substitution: +The workflow wrapper covers the common build path. If you'd rather invoke `metagraph` directly: ```bash -metagraph-workflows build <(ls /data/samples/*.fa) -o out/ --primary -``` +# Build a graph from a fasta (single sample, simplest case) +metagraph build -v -p 8 -k 31 --mem-cap-gb 10 -o graph data.fa.gz -Tell the workflow how much hardware to use; everything else (memory -caps per stage, per-column buffer sizing, BRWT clustering parameters) -is derived automatically: - -```bash -metagraph-workflows build samples.txt -o out/ --primary \ - -p 34 --mem-gb 70 --disk-swap-dir /scratch/swap -``` +# Build using disk swap (limits RAM at the cost of disk I/O) +metagraph build -v -p 8 -k 31 --mem-cap-gb 10 --disk-swap /scratch/swap -o graph data.fa.gz -Useful switches: -- `-p N` β€” maximum CPU cores to use (defaults to all cores) -- `--mem-gb GB` β€” approximate RAM budget per rule (default 16) -- `--disk-swap-dir DIR` β€” directory for on-disk spill buffers -- `--primary` β€” build a primary graph (recommended for most workloads) -- `--anno-type FMT` β€” request a specific annotation format - (repeat for multiple outputs); the default is `relax.row_diff_brwt` -- `--with-counts` / `--with-coords` β€” count- or coordinate-aware - annotation (mutually exclusive) -- `--graph EXISTING.dbg` β€” reuse an already-built graph and only run - the annotation + transforms - -See `metagraph/workflows/README.rst` for setup and the full option -list (`metagraph-workflows build --help`). - - -## Typical workflow -1. Build de Bruijn graph from Fasta files, FastQ files, or [KMC k-mer counters](https://github.com/refresh-bio/KMC/):\ -`./metagraph build` -2. Annotate graph using the column compressed annotation:\ -`./metagraph annotate` -3. Transform the built annotation to a different annotation scheme:\ -`./metagraph transform_anno` -4. Query annotated graph\ -`./metagraph query` - -### Example -``` -DATA="../tests/data/transcripts_1000.fa" +# Annotate a graph with file-based labels +metagraph annotate -v -p 8 --anno-filename -i graph.dbg -o annotation data.fa.gz -./metagraph build -k 12 -o transcripts_1000 $DATA +# Align reads against a graph +metagraph align -v -i graph.dbg query.fa -./metagraph annotate -i transcripts_1000.dbg --anno-filename -o transcripts_1000 $DATA +# Assemble unitigs from a graph +metagraph assemble -v graph.dbg -o assembled.fa --unitigs -./metagraph query -i transcripts_1000.dbg -a transcripts_1000.column.annodbg $DATA +# Differential assembly (extract sequences present in some labels, absent in others) +metagraph assemble -v graph.dbg --unitigs \ + -a annotation.column.annodbg \ + --diff-assembly-rules diff_assembly_rules.json \ + -o diff_assembled.fa -./metagraph stats -a transcripts_1000.column.annodbg transcripts_1000.dbg +# Stats for graph + annotation +metagraph stats -a annotation.column.annodbg graph.dbg ``` -### Print usage -`./metagraph` +See the [online docs](https://metagraph.ethz.ch/static/docs/index.html) for the full subcommand reference, alphabet builds, and the row-diff / BRWT clustering pipeline. -### Build graph - -* #### Simple build -```bash -./metagraph build -v --parallel 30 -k 20 --mem-cap-gb 10 \ - -o /graph /*.fasta.gz \ -2>&1 | tee /log.txt -``` - -* #### Build with disk swap (use to limit the RAM usage) -```bash -./metagraph build -v --parallel 30 -k 20 --mem-cap-gb 10 --disk-swap \ - -o /graph /*.fasta.gz \ -2>&1 | tee /log.txt -``` - -#### Build from k-mers filtered with KMC -```bash -K=20 -./KMC/kmc -ci5 -t4 -k$K -m5 -fm .fasta.gz .cutoff_5 ./KMC -./metagraph build -v -p 4 -k $K --mem-cap-gb 10 -o graph .cutoff_5.kmc_pre -``` - -### Annotate graph -```bash -./metagraph annotate -v --anno-type row --fasta-anno \ - -i primates.dbg \ - -o primates \ - ~/fasta_zurich/refs_chimpanzee_primates.fa -``` - -### Convert annotation to Multi-BRWT -1) Cluster columns -```bash -./metagraph transform_anno -v --linkage --greedy \ - -o linkage.txt \ - --subsample R \ - -p NCORES \ - primates.column.annodbg -``` -Requires `N*R/8 + 6*N^2` bytes of RAM, where `N` is the number of columns and `R` is the number of rows subsampled. - -2) Construct Multi-BRWT -```bash -./metagraph transform_anno -v -p NCORES --anno-type brwt \ - --linkage-file linkage.txt \ - -o primates \ - --parallel-nodes V \ - -p NCORES \ - primates.column.annodbg -``` -Requires `M*V/8 + Size(BRWT)` bytes of RAM, where `M` is the number of rows in the annotation and `V` is the number of nodes merged concurrently. +
-### Query graph -```bash -./metagraph query -v -i /graph.dbg \ - -a /annotation.column.annodbg \ - --min-kmers-fraction-label 0.8 --labels-delimiter ", " \ - query_seq.fa -``` +## πŸ“¦ Install -### Align to graph -```bash -./metagraph align -v -i /graph.dbg query_seq.fa -``` +The recommended conda + pip install is covered in [Quick start](#-quick-start). Alternative install methods: -### Assemble sequences -```bash -./metagraph assemble -v /graph.dbg \ - -o assembled.fa \ - --unitigs -``` +### 🐳 Docker -### Assemble differential sequences ```bash -./metagraph assemble -v /graph.dbg \ - --unitigs \ - -a /annotation.column.annodbg \ - --diff-assembly-rules diff_assembly_rules.json \ - -o diff_assembled.fa +docker pull ghcr.io/ratschlab/metagraph:master +docker run -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master \ + metagraph build -v -k 10 -o /mnt/transcripts_1000 /mnt/transcripts_1000.fa ``` -See [`metagraph/tests/data/example.diff.json`](metagraph/tests/data/example.diff.json) and [`metagraph/tests/data/example_simple.diff.json`](metagraph/tests/data/example_simple.diff.json) for sample files. +Replace `${HOME}` with the host directory you want to expose under `/mnt`. The default image targets the `DNA` alphabet `{A,C,G,T}`; the `DNA5` and `Protein` variants are invoked as `metagraph_DNA5` / `metagraph_Protein`. All published images are listed [here](https://github.com/ratschlab/metagraph/pkgs/container/metagraph). -### Get stats -Stats for graph -```bash -./metagraph stats graph.dbg -``` -Stats for annotation -```bash -./metagraph stats -a annotation.column.annodbg -``` -Stats for both -```bash -./metagraph stats -a annotation.column.annodbg graph.dbg -``` - -## Developer Notes +### πŸ› οΈ Install from sources -### Build a docker container +See the [installation guide](https://metagraph.ethz.ch/static/docs/installation.html#install-from-source) for custom alphabet / debug builds. -Simply run `docker build .` +## πŸ“ Citation -### Makefile +If MetaGraph or its index resources are useful in your work, please cite: -The `Makefile` in the top level source directory can be used to build and test `metagraph` more conveniently. The following -arguments are supported: -* `env`: environment in which to compile/run (`""`: on the host, `docker`: in a docker container) -* `alphabet`: compile metagraph for a certain alphabet (e.g. `DNA` or `Protein`, default `DNA`) -* `additional_cmake_args`: additional arguments to pass to cmake. +> Karasikov M, Mustafa H, Danciu D, Kulkov O, Zimmermann M, Barber C, RΓ€tsch G, Kahles A. Efficient and accurate search in petabase-scale sequence repositories. *Nature*. 2025;647: 1036–1044. -Examples: +
+BibTeX +```bibtex +@article{karasikov2025metagraph, + title={Efficient and accurate search in petabase-scale sequence repositories}, + author={Karasikov, Mikhail and Mustafa, Harun and Danciu, Daniel and Kulkov, Oleksandr and Zimmermann, Marc and Barber, Christopher and R{\"a}tsch, Gunnar and Kahles, Andr{\'e}}, + journal={Nature}, + volume={647}, + number={8091}, + pages={1036--1044}, + year={2025}, + publisher={Nature Publishing Group}, + doi={10.1038/s41586-025-09603-w} +} ``` -# compiles metagraph in a docker container for the `DNA` alphabet -make build-metagraph env=docker alphabet=DNA -``` - -### Update and create a new release - -Creating a new version release is done in three steps: +
-1. Update package.json and set the version -2. Add a tag with that new version -3. Make a new release on github +## βš–οΈ License -## License -Metagraph is distributed under the GPLv3 License (see LICENSE). -Please find further information in the AUTHORS and COPYRIGHTS files. +MetaGraph is distributed under the GPLv3 License (see [LICENSE](LICENSE)). See also [AUTHORS](AUTHORS) and [COPYRIGHTS](COPYRIGHTS). diff --git a/metagraph/docs/source/images/metagraph_logo.png b/metagraph/docs/source/images/metagraph_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7d2d27176c444e8c4e5af65503bcdf75948f1092 GIT binary patch literal 34533 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 MTE?3AJI??3KTJuSApigX literal 0 HcmV?d00001 From 3b02c614d95bb67d4de5cf96be610c11fe1df19c Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Thu, 21 May 2026 01:07:42 +0200 Subject: [PATCH 02/23] README: add minimal CLI walkthrough and restore advanced recipes Re-introduces content trimmed from master as collapsible sections (design choices, direct CLI, KMC build, manual Multi-BRWT with memory formulas, common query flags, advanced Docker, contributing pointer, offline docs). Adds a visible "Minimal example" section with a 4-command end-to-end demo on the bundled transcripts_1000.fa, plus a sample query output showing paralog matching. Aligns terminology with the project (de Bruijn graph, annotation matrix, labels, ColumnCompressed/RowDiff, BOSS). Co-Authored-By: Claude Opus 4.7 --- README.md | 202 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 186 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index aa87d7ee3e..fa9b7273d8 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,29 @@ **Scalable indexing and querying of annotated genome graphs β€” from a handful of genomes to petabase-scale sequence repositories.** -MetaGraph builds compact, searchable indexes over sequencing data: assemblies, raw reads, transcripts, whole bacterial collections. Once indexed, you can query for sequences in milliseconds, align reads against trillions of k-mers, and recover where every match came from. +A MetaGraph index has two components: a **de Bruijn graph** that stores all *k*-mers extracted from the input sequences, and an **annotation matrix** that links each *k*-mer to its source labels (sample, sequence header, expression level, position, …). Once indexed, you can query for sequences in milliseconds, align reads against trillions of *k*-mers, and recover where every match came from. ### Features -- ⚑ **Scale.** Indexes with trillions of k-mers and millions of annotation labels; petabase-scale collections of public sequencing data have been indexed end-to-end. -- πŸ”’ **[k-mer counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts)** and πŸ“ **[k-mer coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates).** Optional payloads alongside k-mer presence β€” for expression levels, alignment-position recovery, or losslessly encoding source genomes. -- 🧬 **Sequence alignment** against the full annotated graph, with sub-k seeding for arbitrarily short queries. +- ⚑ **Scale.** Indexes with trillions of *k*-mers and millions of annotation labels; petabase-scale collections of public sequencing data have been indexed end-to-end. +- πŸ”’ **[k-mer counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts)** and πŸ“ **[k-mer coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates).** Optional payloads alongside *k*-mer presence β€” for expression levels, alignment-position recovery, or losslessly encoding source genomes. +- 🧬 **Sequence alignment** against the full annotated graph, with sub-*k* seeding for arbitrarily short queries. - 🧹 **Scalable graph cleaning** to strip sequencing errors out of very large de Bruijn graphs. - πŸ”€ **Custom alphabets** β€” `{A,C,G,T}`, `{A,C,G,T,N}`, protein, or your own. - πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly)** β€” extract sequences present in one group of samples and absent from another. +
+Design choices under the hood + +- **Succinct data structures** β€” the default `succinct` (BOSS) graph representation uses only 2–4 bits per *k*-mer. +- **Batched algorithms** β€” operations are designed around the access patterns succinct structures favor (batched lookups over random access). +- **Modular representations** β€” multiple graph and annotation formats (`succinct`, `hash`, `bitmap`, …; `column`, `brwt`, `row_diff`, `rbfish`, `row_sparse`, …) plug into the same pipeline. +- **Extensible interfaces** β€” adding a new index representation requires little code overhead. + +
+ > πŸ“– **Full documentation:** +>   Β·   Offline docs: [`metagraph/docs/source`](metagraph/docs/source) ## 🌐 MetaGraph Online @@ -41,6 +52,8 @@ conda install -c bioconda -c conda-forge metagraph pip install --force-reinstall "git+https://github.com/ratschlab/metagraph.git#subdirectory=metagraph/workflows" ``` +`metagraph` ships the compiled binary; `metagraph-workflows` is the Python wrapper that drives the full build pipeline. + ### 2. Build an index Clone the repo for the bundled test data, then build: @@ -61,14 +74,14 @@ metagraph-workflows build samples.txt -o out/ --primary \ Per-stage memory caps, thread packing, and BRWT parameters are derived from the budget. See `metagraph-workflows build --help` for all options. -Outputs: `graph.dbg`, `graph_small.dbg`, and one `.annodbg` per requested format. +Outputs: `graph.dbg` (the de Bruijn graph), `graph_small.dbg` (the same graph in the smaller `--state small` representation), and one `.annodbg` per requested annotation format (default: `RowDiff`). Under the hood, the workflow runs four `metagraph` stages β€” see [the pipeline docs](https://metagraph.ethz.ch/static/docs/quick_start.html) for each: -1. `metagraph build` β€” graph construction -2. `metagraph annotate` β€” per-sample annotation +1. `metagraph build` β€” de Bruijn graph construction +2. `metagraph annotate` β€” per-sample annotation columns 3. `metagraph transform_anno --anno-type row_diff` β€” row-diff transform (stages 0–2) -4. `metagraph transform_anno --anno-type *_brwt` β€” BRWT clustering +4. `metagraph transform_anno --anno-type *_brwt` β€” Multi-BRWT clustering ### 3. Query @@ -80,21 +93,59 @@ metagraph query --query-mode matches -p 8 \ metagraph/tests/data/transcripts_100.fa ``` -For the [Python API](https://metagraph.ethz.ch/static/docs/api.html), see the online docs. +For the [Python API](https://metagraph.ethz.ch/static/docs/api.html) (server mode + HTTP), see the online docs. -
-πŸ”§ Direct metagraph CLI (align / assemble / stats / ...) +## πŸ’‘ Minimal example -The workflow wrapper covers the common build path. If you'd rather invoke `metagraph` directly: +For a guaranteed-working end-to-end demo with the bundled test data, using `metagraph` directly (no workflow wrapper, no row-diff/BRWT β€” just the column-compressed annotation, which is plenty at this scale): ```bash -# Build a graph from a fasta (single sample, simplest case) +cd metagraph/tests/data + +# 1. Build a de Bruijn graph (k-mer index) with k=31 +metagraph build -v -p 4 -k 31 -o transcripts_1000 transcripts_1000.fa + +# 2. Construct an annotation matrix linking each k-mer to its source label. +# --anno-header β†’ one label per fasta header (1000 labels here). +metagraph annotate -v -p 4 -i transcripts_1000.dbg --anno-header \ + -o transcripts_1000 transcripts_1000.fa + +# 3. Query: for each query sequence, report all labels whose k-mers cover β‰₯80% of it. +metagraph query -i transcripts_1000.dbg -a transcripts_1000.column.annodbg \ + --min-kmers-fraction-label 0.8 \ + transcripts_1000.fa + +# 4. Print graph + annotation stats. +metagraph stats -a transcripts_1000.column.annodbg transcripts_1000.dbg +``` + +Outputs: +- `transcripts_1000.dbg` β€” the de Bruijn graph (613,859 *k*-mers at k=31). +- `transcripts_1000.column.annodbg` β€” annotation in the `ColumnCompressed` format (1,000 labels). + +Sample query output (tab-separated: query index, query header, matching labels joined by `:`): + +``` +3 ENST00000619216.1|...|MIR6859-1|68|miRNA| ENST00000619216.1|...|MIR6859-1|68|miRNA|:ENST00000612080.1|...|MIR6859-2|68|miRNA| +``` + +Here `MIR6859-1` matches both itself *and* its paralog `MIR6859-2` β€” they share enough *k*-mers to clear the 80% threshold. + +For larger indexes, the column-compressed annotation gets transformed into more compact representations (`RowDiff`, `RowDiff`, `Rainbowfish`, …). The `metagraph-workflows build` command in [Quick start](#-quick-start) chains those transforms automatically. + +## πŸ”§ More recipes + +
+Direct CLI: align / assemble / differential assembly / stats + +```bash +# Build a de Bruijn graph from a fasta (single sample) metagraph build -v -p 8 -k 31 --mem-cap-gb 10 -o graph data.fa.gz # Build using disk swap (limits RAM at the cost of disk I/O) metagraph build -v -p 8 -k 31 --mem-cap-gb 10 --disk-swap /scratch/swap -o graph data.fa.gz -# Annotate a graph with file-based labels +# Annotate a graph with file-based labels (one label per input file) metagraph annotate -v -p 8 --anno-filename -i graph.dbg -o annotation data.fa.gz # Align reads against a graph @@ -103,13 +154,18 @@ metagraph align -v -i graph.dbg query.fa # Assemble unitigs from a graph metagraph assemble -v graph.dbg -o assembled.fa --unitigs -# Differential assembly (extract sequences present in some labels, absent in others) +# Differential assembly β€” sequences present in some labels and absent in others metagraph assemble -v graph.dbg --unitigs \ -a annotation.column.annodbg \ --diff-assembly-rules diff_assembly_rules.json \ -o diff_assembled.fa +# Sample rule files: +# metagraph/tests/data/example.diff.json +# metagraph/tests/data/example_simple.diff.json -# Stats for graph + annotation +# Stats β€” graph only, annotation only, or both +metagraph stats graph.dbg +metagraph stats -a annotation.column.annodbg metagraph stats -a annotation.column.annodbg graph.dbg ``` @@ -117,6 +173,89 @@ See the [online docs](https://metagraph.ethz.ch/static/docs/index.html) for the
+
+Build from KMC k-mer counters (e.g., to filter low-abundance k-mers) + +[KMC](https://github.com/refresh-bio/KMC) is an extremely efficient *k*-mer counter that can pre-process inputs and filter by abundance. Build a graph from *k*-mers occurring at least 5 times: + +```bash +K=31 +# Count k-mers with KMC (drops k-mers with abundance < 5) +./KMC/kmc -ci5 -t4 -k$K -m5 -fq input.fastq.gz input.cutoff_5 ./KMC + +# Build the graph directly from the KMC counter +metagraph build -v -p 4 -k $K -o graph input.cutoff_5.kmc_pre +``` + +Add `--count-kmers` to keep the KMC abundance counts as a weight vector alongside the graph (`graph.dbg.weights`). The weighted graph is the input for [`metagraph clean`](https://metagraph.ethz.ch/static/docs/quick_start.html#graph-cleaning) and for indexing *k*-mer counts. + +
+ +
+Manual Multi-BRWT construction (with memory formulas) + +The `metagraph-workflows` wrapper does this automatically. If you're driving the steps yourself: + +**1. Cluster columns** to compute a linkage tree: + +```bash +metagraph transform_anno -v -p NCORES --linkage --greedy \ + --subsample R \ + -o linkage.txt \ + annotation.column.annodbg +``` + +RAM: `NΒ·R/8 + 6Β·NΒ²` bytes, where `N` is the number of columns and `R` is the number of subsampled rows. (Default `R = 1,000,000` is enough even for very large annotations.) + +**2. Construct Multi-BRWT** from the clustering: + +```bash +metagraph transform_anno -v -p NCORES --anno-type brwt \ + --linkage-file linkage.txt \ + --parallel-nodes V \ + -o annotation \ + annotation.column.annodbg +``` + +RAM: `MΒ·V/8 + Size(BRWT)` bytes, where `M` is the number of rows in the annotation and `V` is the number of BRWT nodes merged concurrently. + +**3. Relax the BRWT** to enhance compression by increasing internal-node arity (recommended): + +```bash +metagraph relax_brwt -v -p NCORES --relax-arity 32 \ + -o annotation_relaxed \ + annotation.brwt.annodbg +``` + +For `RowDiff` (the workflow default), do the three-stage row-diff transform first; see the [annotation transform docs](https://metagraph.ethz.ch/static/docs/quick_start.html#transform-annotation). + +
+ +
+Common query flags + +```bash +# Filter labels with low k-mer coverage (default 0.7) +metagraph query --min-kmers-fraction-label 0.8 ... + +# Pick a label-list separator other than ":" +metagraph query --labels-delimiter ", " ... + +# Per-k-mer presence/absence bitmask per matching label +metagraph query --query-mode signature ... + +# Indexed k-mer counts (requires count-aware annotation, see online docs) +metagraph query --query-mode counts ... + +# k-mer coordinates / per-target positions (requires coordinate-aware annotation) +metagraph query --query-mode coords ... + +# Server mode (Python API / HTTP queries) +metagraph server_query -i graph.dbg -a annotation.annodbg --port 5555 --parallel 8 +``` + +
+ ## πŸ“¦ Install The recommended conda + pip install is covered in [Quick start](#-quick-start). Alternative install methods: @@ -131,10 +270,41 @@ docker run -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master \ Replace `${HOME}` with the host directory you want to expose under `/mnt`. The default image targets the `DNA` alphabet `{A,C,G,T}`; the `DNA5` and `Protein` variants are invoked as `metagraph_DNA5` / `metagraph_Protein`. All published images are listed [here](https://github.com/ratschlab/metagraph/pkgs/container/metagraph). +
+Advanced Docker usage + +Inspect what's mounted in the container: + +```bash +docker run -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master ls /mnt +``` + +Run the `DNA5` / `Protein` variants: + +```bash +docker run -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master \ + metagraph_Protein build -v -k 10 -o /mnt/graph /mnt/protein.fa +``` + +Drop into an interactive shell for multi-step workflows: + +```bash +docker run -it --entrypoint /bin/bash -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master +# inside container: +metagraph --version +metagraph build -v -k 31 -o /mnt/graph /mnt/data.fa.gz +``` + +
+ ### πŸ› οΈ Install from sources See the [installation guide](https://metagraph.ethz.ch/static/docs/installation.html#install-from-source) for custom alphabet / debug builds. +## 🀝 Contributing + +Development notes β€” docker build, Makefile shortcuts, and the release process β€” live in [CONTRIBUTING.md](CONTRIBUTING.md). + ## πŸ“ Citation If MetaGraph or its index resources are useful in your work, please cite: From ed851a9041353813b860d86510b8b09c8a073e1a Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 22 May 2026 00:27:23 +0200 Subject: [PATCH 03/23] README: lead with outcomes, add pipeline diagram, surface buried features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add a Mermaid diagram (FASTA β†’ build/annotate β†’ graph + annotation β†’ query/align β†’ matches) as a visual anchor for the two-component model. - Insert a "What you can do" use-case block before Features (search SRA, index a cohort, lossless source recovery). - Split k-mer counts/coordinates into distinct Feature bullets; promote Python API & HTTP server to a Feature; move Custom alphabets into the Design choices collapsible. - Add memory-mapped loading to Design choices. - Gloss --primary inline; collapse the 4-stage pipeline list to a one-liner; add a "What next" subsection after Step 3 (align / Python / diff assembly / docs). - Add a plain-English definition of "label" at the top of Minimal example. - Drop the Manual Multi-BRWT recipe (with memory formulas); leave a link to the annotation-transform docs. - Add a sysreqs + alphabet-picker line to Install (Linux/macOS x86-64/arm64, RAM scaling, bioconda binary is DNA-only). Net: 334 -> 315 lines; first paint reframed from engineering inventory to outcomes plus a working example. Co-Authored-By: Claude Opus 4.7 --- README.md | 111 ++++++++++++++++++++++-------------------------------- 1 file changed, 46 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index fa9b7273d8..bff1fac4ba 100644 --- a/README.md +++ b/README.md @@ -13,23 +13,45 @@ **Scalable indexing and querying of annotated genome graphs β€” from a handful of genomes to petabase-scale sequence repositories.** -A MetaGraph index has two components: a **de Bruijn graph** that stores all *k*-mers extracted from the input sequences, and an **annotation matrix** that links each *k*-mer to its source labels (sample, sequence header, expression level, position, …). Once indexed, you can query for sequences in milliseconds, align reads against trillions of *k*-mers, and recover where every match came from. +Think of MetaGraph as a search engine for sequencing data: index your reads or assemblies once, then query, align, or recover source positions in milliseconds. + +A MetaGraph index has two components: a **de Bruijn graph** that stores all *k*-mers extracted from the input sequences, and an **annotation matrix** that links each *k*-mer to its source labels (sample, sequence header, expression level, position, …). + +```mermaid +flowchart LR + F["FASTA / FASTQ
reads Β· assemblies Β· transcripts"] + F -->|build| G[("de Bruijn graph
graph.dbg")] + F -->|annotate| M[("annotation matrix
*.annodbg")] + G --> Q(("query / align")) + M --> Q + Qseq["query sequence"] --> Q + Q --> R["matches
labels Β· counts Β· positions"] +``` + +### What you can do + +- πŸ”Ž **Search public sequencing data.** Find a CRISPR spacer, AMR gene, or variant across 50+ PB of public reads via the hosted instance at [metagraph.ethz.ch](https://metagraph.ethz.ch). +- πŸ“Š **Index your own cohort.** Build a *k*-mer index over an RNA-seq, metagenome, or pangenome cohort and recover per-sample expression or presence. +- πŸ“ **Lossless source recovery.** Index *k*-mer coordinates and queries come back with per-target hit positions β€” the index *is* the data. ### Features - ⚑ **Scale.** Indexes with trillions of *k*-mers and millions of annotation labels; petabase-scale collections of public sequencing data have been indexed end-to-end. -- πŸ”’ **[k-mer counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts)** and πŸ“ **[k-mer coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates).** Optional payloads alongside *k*-mer presence β€” for expression levels, alignment-position recovery, or losslessly encoding source genomes. +- πŸ”’ **[k-mer counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts).** Optional abundance payload β€” for expression levels, depth-of-coverage, or weighted graph cleaning. +- πŸ“ **[k-mer coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates).** Optional position payload β€” losslessly encodes source sequences and returns per-target hit positions. - 🧬 **Sequence alignment** against the full annotated graph, with sub-*k* seeding for arbitrarily short queries. - 🧹 **Scalable graph cleaning** to strip sequencing errors out of very large de Bruijn graphs. -- πŸ”€ **Custom alphabets** β€” `{A,C,G,T}`, `{A,C,G,T,N}`, protein, or your own. -- πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly)** β€” extract sequences present in one group of samples and absent from another. +- πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly).** Extract sequences present in one group of samples and absent from another, driven by user-defined JSON rules. +- 🐍 **[Python API & HTTP server](https://metagraph.ethz.ch/static/docs/api.html).** Drive MetaGraph from Python or query a running instance over HTTP.
Design choices under the hood - **Succinct data structures** β€” the default `succinct` (BOSS) graph representation uses only 2–4 bits per *k*-mer. +- **Memory-mapped loading** β€” near-zero RAM at query time and instant cold start; supported across most graph and annotation representations. - **Batched algorithms** β€” operations are designed around the access patterns succinct structures favor (batched lookups over random access). -- **Modular representations** β€” multiple graph and annotation formats (`succinct`, `hash`, `bitmap`, …; `column`, `brwt`, `row_diff`, `rbfish`, `row_sparse`, …) plug into the same pipeline. +- **Modular representations** β€” multiple annotation formats (`ColumnCompressed`, `RowDiff`, `RowSparse`, `Rainbowfish`, plus count- and coordinate-aware variants) trade compression vs. query speed; pick the one that fits your scale. +- **Custom alphabets** β€” `{A,C,G,T}`, `{A,C,G,T,N}`, case-sensitive DNA, protein, or compile-time custom alphabets. - **Extensible interfaces** β€” adding a new index representation requires little code overhead.
@@ -39,7 +61,7 @@ A MetaGraph index has two components: a **de Bruijn graph** that stores all *k*- ## 🌐 MetaGraph Online -Try MetaGraph without installing anything: hosts a public search engine over 50+ petabases of DNA, RNA, and protein sequences, indexing SRA, ENA, DRA, RefSeq, UniProt, and more. +Try MetaGraph without installing anything: hosts a public search engine over 50+ petabases of DNA, RNA, and protein sequences, indexing SRA, ENA, DRA, RefSeq, UniProt, and more. Paste a query sequence at the top of the page and pick which indexed datasets to search against. ## πŸš€ Quick start @@ -63,7 +85,7 @@ git clone https://github.com/ratschlab/metagraph.git && cd metagraph metagraph-workflows build <(ls metagraph/tests/data/*.fa) -o out/ --primary ``` -The positional argument accepts a file list (one path per line), a directory, or β€” as above β€” process substitution. +`--primary` builds a *primary* graph β€” one strand kept per *k*-mer pair, about half the size of indexing both strands; recommended for DNA data. The positional argument accepts a file list (one path per line), a directory, or β€” as above β€” process substitution. For real workloads, supply your own sample list and a hardware budget: @@ -74,18 +96,11 @@ metagraph-workflows build samples.txt -o out/ --primary \ Per-stage memory caps, thread packing, and BRWT parameters are derived from the budget. See `metagraph-workflows build --help` for all options. -Outputs: `graph.dbg` (the de Bruijn graph), `graph_small.dbg` (the same graph in the smaller `--state small` representation), and one `.annodbg` per requested annotation format (default: `RowDiff`). - -Under the hood, the workflow runs four `metagraph` stages β€” see [the pipeline docs](https://metagraph.ethz.ch/static/docs/quick_start.html) for each: - -1. `metagraph build` β€” de Bruijn graph construction -2. `metagraph annotate` β€” per-sample annotation columns -3. `metagraph transform_anno --anno-type row_diff` β€” row-diff transform (stages 0–2) -4. `metagraph transform_anno --anno-type *_brwt` β€” Multi-BRWT clustering +Outputs: `graph.dbg` (the de Bruijn graph), `graph_small.dbg` (a more compressed, slower-to-load representation of the same graph), and `graph.relax.row_diff_brwt.annodbg` (the default `RowDiff` annotation). Internally this chains `metagraph build β†’ annotate β†’ transform_anno --anno-type row_diff β†’ transform_anno --anno-type *_brwt` β€” see [the pipeline docs](https://metagraph.ethz.ch/static/docs/quick_start.html) for each stage. ### 3. Query -The default `--anno-type` produces `.relax.row_diff_brwt.annodbg`. Query it against the graph (any fasta works as input β€” the bundled test data is fine for a smoke test): +Query the index (any fasta works as input β€” the bundled test data is fine for a smoke test): ```bash metagraph query --query-mode matches -p 8 \ @@ -93,11 +108,18 @@ metagraph query --query-mode matches -p 8 \ metagraph/tests/data/transcripts_100.fa ``` -For the [Python API](https://metagraph.ethz.ch/static/docs/api.html) (server mode + HTTP), see the online docs. +### What next + +- 🧬 **Align reads** instead of querying β€” see [Minimal example](#-minimal-example) and `metagraph align --help`. +- 🐍 **Drive from Python or HTTP** β€” load the index with `metagraph server_query`, then call it from the [Python API](https://metagraph.ethz.ch/static/docs/api.html). +- πŸ”€ **Differential assembly** β€” extract sequences present in some labels and absent in others (see [More recipes](#-more-recipes) below). +- πŸ“š **Full docs** β€” . ## πŸ’‘ Minimal example -For a guaranteed-working end-to-end demo with the bundled test data, using `metagraph` directly (no workflow wrapper, no row-diff/BRWT β€” just the column-compressed annotation, which is plenty at this scale): +For a guaranteed-working end-to-end demo with the bundled test data, using `metagraph` directly (no workflow wrapper, no row-diff/BRWT β€” just the column-compressed annotation, which is plenty at this scale). + +A *label* is whatever tag you want each *k*-mer associated with β€” a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record (1000 labels for 1000 transcripts). ```bash cd metagraph/tests/data @@ -106,7 +128,6 @@ cd metagraph/tests/data metagraph build -v -p 4 -k 31 -o transcripts_1000 transcripts_1000.fa # 2. Construct an annotation matrix linking each k-mer to its source label. -# --anno-header β†’ one label per fasta header (1000 labels here). metagraph annotate -v -p 4 -i transcripts_1000.dbg --anno-header \ -o transcripts_1000 transcripts_1000.fa @@ -154,14 +175,12 @@ metagraph align -v -i graph.dbg query.fa # Assemble unitigs from a graph metagraph assemble -v graph.dbg -o assembled.fa --unitigs -# Differential assembly β€” sequences present in some labels and absent in others +# Differential assembly β€” JSON rules define which label groups must be present vs. absent. +# Sample rule files: metagraph/tests/data/example.diff.json, example_simple.diff.json. metagraph assemble -v graph.dbg --unitigs \ -a annotation.column.annodbg \ --diff-assembly-rules diff_assembly_rules.json \ -o diff_assembled.fa -# Sample rule files: -# metagraph/tests/data/example.diff.json -# metagraph/tests/data/example_simple.diff.json # Stats β€” graph only, annotation only, or both metagraph stats graph.dbg @@ -191,46 +210,6 @@ Add `--count-kmers` to keep the KMC abundance counts as a weight vector alongsid
-
-Manual Multi-BRWT construction (with memory formulas) - -The `metagraph-workflows` wrapper does this automatically. If you're driving the steps yourself: - -**1. Cluster columns** to compute a linkage tree: - -```bash -metagraph transform_anno -v -p NCORES --linkage --greedy \ - --subsample R \ - -o linkage.txt \ - annotation.column.annodbg -``` - -RAM: `NΒ·R/8 + 6Β·NΒ²` bytes, where `N` is the number of columns and `R` is the number of subsampled rows. (Default `R = 1,000,000` is enough even for very large annotations.) - -**2. Construct Multi-BRWT** from the clustering: - -```bash -metagraph transform_anno -v -p NCORES --anno-type brwt \ - --linkage-file linkage.txt \ - --parallel-nodes V \ - -o annotation \ - annotation.column.annodbg -``` - -RAM: `MΒ·V/8 + Size(BRWT)` bytes, where `M` is the number of rows in the annotation and `V` is the number of BRWT nodes merged concurrently. - -**3. Relax the BRWT** to enhance compression by increasing internal-node arity (recommended): - -```bash -metagraph relax_brwt -v -p NCORES --relax-arity 32 \ - -o annotation_relaxed \ - annotation.brwt.annodbg -``` - -For `RowDiff` (the workflow default), do the three-stage row-diff transform first; see the [annotation transform docs](https://metagraph.ethz.ch/static/docs/quick_start.html#transform-annotation). - -
-
Common query flags @@ -256,9 +235,11 @@ metagraph server_query -i graph.dbg -a annotation.annodbg --port 5555 --parallel
+For manual Multi-BRWT clustering (linkage trees, memory formulas, column-count tradeoffs), see [the annotation-transform docs](https://metagraph.ethz.ch/static/docs/quick_start.html#transform-annotation). + ## πŸ“¦ Install -The recommended conda + pip install is covered in [Quick start](#-quick-start). Alternative install methods: +The recommended conda + pip install is covered in [Quick start](#-quick-start). MetaGraph runs on Linux and macOS (x86-64 and arm64); the Minimal example needs < 1 GB RAM, while petabase-scale builds use disk swap and 70+ GB. The bioconda install gives a single `metagraph` binary built for the `DNA` alphabet β€” for `DNA5`, `Protein`, or custom alphabets, use the Docker image (which bundles all three variants) or build from source. ### 🐳 Docker @@ -268,7 +249,7 @@ docker run -v ${HOME}:/mnt ghcr.io/ratschlab/metagraph:master \ metagraph build -v -k 10 -o /mnt/transcripts_1000 /mnt/transcripts_1000.fa ``` -Replace `${HOME}` with the host directory you want to expose under `/mnt`. The default image targets the `DNA` alphabet `{A,C,G,T}`; the `DNA5` and `Protein` variants are invoked as `metagraph_DNA5` / `metagraph_Protein`. All published images are listed [here](https://github.com/ratschlab/metagraph/pkgs/container/metagraph). +Replace `${HOME}` with the host directory you want to expose under `/mnt`. The default image targets the `DNA` alphabet `{A,C,G,T}`; the `DNA5` and `Protein` variants are invoked as `metagraph_DNA5` / `metagraph_Protein` from the same image. All published images are listed [here](https://github.com/ratschlab/metagraph/pkgs/container/metagraph).
Advanced Docker usage From 40c3806f5b891ef72a7a17af637bbe9f49878ee7 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 22 May 2026 01:05:50 +0200 Subject: [PATCH 04/23] README: correctness pass + simplify Features/Install Cross-checked every flag, default, output name, and behavioral claim against the code and docs. Corrections: - Memory-mapped loading reframed as opt-in --mmap (was implied default); noted NVMe recommended, SSD slower. - graph_small.dbg: "slower at access" not "slower-to-load" (BOSS SMALL state is smaller, slower-access). - --primary rationale: "appropriate when read strand orientation is unknown" instead of "recommended for DNA data". - Bioconda: ships DNA + Protein alphabets (metagraph symlinked to metagraph_DNA), not a single DNA binary; Docker image adds DNA5. - MetaGraph Online datasets: list per resources.rst (RefSeq, UHGG, Tara Oceans, UniParc), not the previous SRA/ENA/DRA/UniProt guess. - Pipeline chain now includes BRWT relaxation step (explains the .relax. prefix in the default annotation filename). Simplifications: - Merge "What you can do" + "Features" into one outcome-framed list (6 bullets, was 3 + 7). - Trim Design choices ("Under the hood") to 4 sub-bullets; move Custom alphabets out (covered in Install). - Move the real-workload build command (file list + hardware budget) into a collapsible. - Replace "What next" 4-bullet subsection with a one-sentence post-script after Step 3. - Tighten Install intro to a single sentence (Linux/macOS, no arch claims). Net: -20 lines (315 -> 295). Co-Authored-By: Claude Opus 4.7 --- README.md | 64 +++++++++++++++++++------------------------------------ 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index bff1fac4ba..41f8e69bc9 100644 --- a/README.md +++ b/README.md @@ -28,40 +28,30 @@ flowchart LR Q --> R["matches
labels Β· counts Β· positions"] ``` -### What you can do - -- πŸ”Ž **Search public sequencing data.** Find a CRISPR spacer, AMR gene, or variant across 50+ PB of public reads via the hosted instance at [metagraph.ethz.ch](https://metagraph.ethz.ch). -- πŸ“Š **Index your own cohort.** Build a *k*-mer index over an RNA-seq, metagenome, or pangenome cohort and recover per-sample expression or presence. -- πŸ“ **Lossless source recovery.** Index *k*-mer coordinates and queries come back with per-target hit positions β€” the index *is* the data. - ### Features -- ⚑ **Scale.** Indexes with trillions of *k*-mers and millions of annotation labels; petabase-scale collections of public sequencing data have been indexed end-to-end. -- πŸ”’ **[k-mer counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts).** Optional abundance payload β€” for expression levels, depth-of-coverage, or weighted graph cleaning. -- πŸ“ **[k-mer coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates).** Optional position payload β€” losslessly encodes source sequences and returns per-target hit positions. +- πŸ”Ž **Search public archives.** [metagraph.ethz.ch](https://metagraph.ethz.ch) hosts a search engine over 50+ petabases of public DNA, RNA, and protein data β€” think BLAST at petabase scale. +- πŸ“Š **Index your own data.** Build a *k*-mer index over reads, assemblies, or transcripts; queries return matching labels with optional [counts (abundances)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) or [coordinates (positions in source)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates). - 🧬 **Sequence alignment** against the full annotated graph, with sub-*k* seeding for arbitrarily short queries. - 🧹 **Scalable graph cleaning** to strip sequencing errors out of very large de Bruijn graphs. -- πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly).** Extract sequences present in one group of samples and absent from another, driven by user-defined JSON rules. +- πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly).** Extract sequences present in one group of samples and absent from another, driven by JSON rules. - 🐍 **[Python API & HTTP server](https://metagraph.ethz.ch/static/docs/api.html).** Drive MetaGraph from Python or query a running instance over HTTP.
-Design choices under the hood +Under the hood - **Succinct data structures** β€” the default `succinct` (BOSS) graph representation uses only 2–4 bits per *k*-mer. -- **Memory-mapped loading** β€” near-zero RAM at query time and instant cold start; supported across most graph and annotation representations. -- **Batched algorithms** β€” operations are designed around the access patterns succinct structures favor (batched lookups over random access). -- **Modular representations** β€” multiple annotation formats (`ColumnCompressed`, `RowDiff`, `RowSparse`, `Rainbowfish`, plus count- and coordinate-aware variants) trade compression vs. query speed; pick the one that fits your scale. -- **Custom alphabets** β€” `{A,C,G,T}`, `{A,C,G,T,N}`, case-sensitive DNA, protein, or compile-time custom alphabets. -- **Extensible interfaces** β€” adding a new index representation requires little code overhead. +- **Modular annotation formats** β€” `ColumnCompressed`, `RowDiff`, `RowSparse`, `Rainbowfish`, plus count- and coordinate-aware variants. Pick the compression/speed tradeoff that fits your scale. +- **Memory-mapped loading** β€” pass `--mmap` to any subcommand for fast cold start and low query-time RAM (NVMe recommended; SSD works but slower). +- **Scales to trillions of *k*-mers and millions of labels** β€” petabase-scale collections have been indexed end-to-end.
-> πŸ“– **Full documentation:** ->   Β·   Offline docs: [`metagraph/docs/source`](metagraph/docs/source) +> πŸ“– **Full documentation:**  Β·  Offline: [`metagraph/docs/source`](metagraph/docs/source) ## 🌐 MetaGraph Online -Try MetaGraph without installing anything: hosts a public search engine over 50+ petabases of DNA, RNA, and protein sequences, indexing SRA, ENA, DRA, RefSeq, UniProt, and more. Paste a query sequence at the top of the page and pick which indexed datasets to search against. +Try MetaGraph without installing anything: hosts a search engine over 50+ petabases of public DNA, RNA, and protein archives β€” RefSeq, UHGG, Tara Oceans, UniParc, and more (see the [databases list](https://metagraph.ethz.ch/indexes)). Paste a query sequence and pick the indexes to search. ## πŸš€ Quick start @@ -74,7 +64,7 @@ conda install -c bioconda -c conda-forge metagraph pip install --force-reinstall "git+https://github.com/ratschlab/metagraph.git#subdirectory=metagraph/workflows" ``` -`metagraph` ships the compiled binary; `metagraph-workflows` is the Python wrapper that drives the full build pipeline. +`metagraph` is the compiled binary; `metagraph-workflows` is the Python wrapper that drives the full build pipeline. ### 2. Build an index @@ -85,18 +75,21 @@ git clone https://github.com/ratschlab/metagraph.git && cd metagraph metagraph-workflows build <(ls metagraph/tests/data/*.fa) -o out/ --primary ``` -`--primary` builds a *primary* graph β€” one strand kept per *k*-mer pair, about half the size of indexing both strands; recommended for DNA data. The positional argument accepts a file list (one path per line), a directory, or β€” as above β€” process substitution. +`--primary` indexes one strand per *k*-mer pair (about half the size; appropriate when read strand orientation is unknown, e.g. typical short-read sequencing). -For real workloads, supply your own sample list and a hardware budget: +Internally this chains `metagraph build β†’ annotate β†’ row-diff transform β†’ BRWT clustering β†’ BRWT relaxation` and produces `graph.dbg`, the more compact `graph_small.dbg` (smaller, slower at access β€” useful when RAM or storage is tight), and the default annotation `graph.relax.row_diff_brwt.annodbg` (`RowDiff`). See the [pipeline docs](https://metagraph.ethz.ch/static/docs/quick_start.html) for each stage. + +
+Real-workload example with file list and hardware budget ```bash metagraph-workflows build samples.txt -o out/ --primary \ -p 34 --mem-gb 70 --disk-swap-dir /scratch/swap ``` -Per-stage memory caps, thread packing, and BRWT parameters are derived from the budget. See `metagraph-workflows build --help` for all options. +The positional argument accepts a file list (one path per line), a directory, or process substitution. Per-stage memory caps, thread packing, and BRWT parameters are derived from the budget. See `metagraph-workflows build --help` for all options. -Outputs: `graph.dbg` (the de Bruijn graph), `graph_small.dbg` (a more compressed, slower-to-load representation of the same graph), and `graph.relax.row_diff_brwt.annodbg` (the default `RowDiff` annotation). Internally this chains `metagraph build β†’ annotate β†’ transform_anno --anno-type row_diff β†’ transform_anno --anno-type *_brwt` β€” see [the pipeline docs](https://metagraph.ethz.ch/static/docs/quick_start.html) for each stage. +
### 3. Query @@ -108,18 +101,11 @@ metagraph query --query-mode matches -p 8 \ metagraph/tests/data/transcripts_100.fa ``` -### What next - -- 🧬 **Align reads** instead of querying β€” see [Minimal example](#-minimal-example) and `metagraph align --help`. -- 🐍 **Drive from Python or HTTP** β€” load the index with `metagraph server_query`, then call it from the [Python API](https://metagraph.ethz.ch/static/docs/api.html). -- πŸ”€ **Differential assembly** β€” extract sequences present in some labels and absent in others (see [More recipes](#-more-recipes) below). -- πŸ“š **Full docs** β€” . +Other ways to use the index: [`metagraph align`](https://metagraph.ethz.ch/static/docs/quick_start.html#query-index) for sequence-to-graph alignment, [`metagraph server_query`](https://metagraph.ethz.ch/static/docs/api.html) for Python/HTTP queries. The [Minimal example](#-minimal-example) below walks through each step on a smaller dataset. ## πŸ’‘ Minimal example -For a guaranteed-working end-to-end demo with the bundled test data, using `metagraph` directly (no workflow wrapper, no row-diff/BRWT β€” just the column-compressed annotation, which is plenty at this scale). - -A *label* is whatever tag you want each *k*-mer associated with β€” a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record (1000 labels for 1000 transcripts). +For a hands-on demo using `metagraph` directly (no workflow wrapper, no row-diff/BRWT β€” just the column-compressed annotation, which is plenty at this scale). A *label* is whatever tag you want each *k*-mer associated with β€” a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record (1000 labels for 1000 transcripts). ```bash cd metagraph/tests/data @@ -140,19 +126,13 @@ metagraph query -i transcripts_1000.dbg -a transcripts_1000.column.annodbg \ metagraph stats -a transcripts_1000.column.annodbg transcripts_1000.dbg ``` -Outputs: -- `transcripts_1000.dbg` β€” the de Bruijn graph (613,859 *k*-mers at k=31). -- `transcripts_1000.column.annodbg` β€” annotation in the `ColumnCompressed` format (1,000 labels). - -Sample query output (tab-separated: query index, query header, matching labels joined by `:`): +Outputs `transcripts_1000.dbg` (the de Bruijn graph, 613,859 *k*-mers at k=31) and `transcripts_1000.column.annodbg` (`ColumnCompressed` annotation, 1000 labels). Sample query output (tab-separated: query index, query header, matching labels joined by `:`): ``` 3 ENST00000619216.1|...|MIR6859-1|68|miRNA| ENST00000619216.1|...|MIR6859-1|68|miRNA|:ENST00000612080.1|...|MIR6859-2|68|miRNA| ``` -Here `MIR6859-1` matches both itself *and* its paralog `MIR6859-2` β€” they share enough *k*-mers to clear the 80% threshold. - -For larger indexes, the column-compressed annotation gets transformed into more compact representations (`RowDiff`, `RowDiff`, `Rainbowfish`, …). The `metagraph-workflows build` command in [Quick start](#-quick-start) chains those transforms automatically. +`MIR6859-1` matches both itself *and* its paralog `MIR6859-2` β€” they share enough *k*-mers to clear the 80% threshold. For larger indexes, the column-compressed annotation gets transformed into more compact representations (`RowDiff`, `RowDiff`, …) β€” `metagraph-workflows build` chains those transforms automatically. ## πŸ”§ More recipes @@ -239,7 +219,7 @@ For manual Multi-BRWT clustering (linkage trees, memory formulas, column-count t ## πŸ“¦ Install -The recommended conda + pip install is covered in [Quick start](#-quick-start). MetaGraph runs on Linux and macOS (x86-64 and arm64); the Minimal example needs < 1 GB RAM, while petabase-scale builds use disk swap and 70+ GB. The bioconda install gives a single `metagraph` binary built for the `DNA` alphabet β€” for `DNA5`, `Protein`, or custom alphabets, use the Docker image (which bundles all three variants) or build from source. +Covered in [Quick start](#-quick-start). MetaGraph runs on Linux and macOS. Bioconda ships the `DNA` and `Protein` alphabets (`metagraph` is symlinked to `metagraph_DNA`); the Docker image adds `DNA5`. For other alphabets, build from source. ### 🐳 Docker From 726d5a8e0e6960c7838c124b234d5e4461e74fc0 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 22 May 2026 01:12:57 +0200 Subject: [PATCH 05/23] README: clarify 50+ PB framing; minor icon/header tweaks - Reword "search engine over 50+ petabases of ... data" -> "search engine over indexes built from 50+ petabases of ... sequencing data" in both places. The 50+ PB is the input to indexing, not the search target. - Drop the lightbulb emoji from the Minimal example header (update the one cross-reference anchor accordingly). - Use a file-cabinet icon (database-like) for "Index your own data". Co-Authored-By: Claude Opus 4.7 --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 41f8e69bc9..55c1e9024f 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ flowchart LR ### Features -- πŸ”Ž **Search public archives.** [metagraph.ethz.ch](https://metagraph.ethz.ch) hosts a search engine over 50+ petabases of public DNA, RNA, and protein data β€” think BLAST at petabase scale. -- πŸ“Š **Index your own data.** Build a *k*-mer index over reads, assemblies, or transcripts; queries return matching labels with optional [counts (abundances)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) or [coordinates (positions in source)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates). +- πŸ”Ž **Search public archives.** [metagraph.ethz.ch](https://metagraph.ethz.ch) hosts a search engine over indexes built from 50+ petabases of public DNA, RNA, and protein sequencing data β€” think BLAST at petabase scale. +- πŸ—„οΈ **Index your own data.** Build a *k*-mer index over reads, assemblies, or transcripts; queries return matching labels with optional [counts (abundances)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) or [coordinates (positions in source)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates). - 🧬 **Sequence alignment** against the full annotated graph, with sub-*k* seeding for arbitrarily short queries. - 🧹 **Scalable graph cleaning** to strip sequencing errors out of very large de Bruijn graphs. - πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly).** Extract sequences present in one group of samples and absent from another, driven by JSON rules. @@ -51,7 +51,7 @@ flowchart LR ## 🌐 MetaGraph Online -Try MetaGraph without installing anything: hosts a search engine over 50+ petabases of public DNA, RNA, and protein archives β€” RefSeq, UHGG, Tara Oceans, UniParc, and more (see the [databases list](https://metagraph.ethz.ch/indexes)). Paste a query sequence and pick the indexes to search. +Try MetaGraph without installing anything: hosts a search engine over indexes built from 50+ petabases of public DNA, RNA, and protein sequencing data β€” RefSeq, UHGG, Tara Oceans, UniParc, and more (see the [databases list](https://metagraph.ethz.ch/indexes)). Paste a query sequence and pick the indexes to search. ## πŸš€ Quick start @@ -101,9 +101,9 @@ metagraph query --query-mode matches -p 8 \ metagraph/tests/data/transcripts_100.fa ``` -Other ways to use the index: [`metagraph align`](https://metagraph.ethz.ch/static/docs/quick_start.html#query-index) for sequence-to-graph alignment, [`metagraph server_query`](https://metagraph.ethz.ch/static/docs/api.html) for Python/HTTP queries. The [Minimal example](#-minimal-example) below walks through each step on a smaller dataset. +Other ways to use the index: [`metagraph align`](https://metagraph.ethz.ch/static/docs/quick_start.html#query-index) for sequence-to-graph alignment, [`metagraph server_query`](https://metagraph.ethz.ch/static/docs/api.html) for Python/HTTP queries. The [Minimal example](#minimal-example) below walks through each step on a smaller dataset. -## πŸ’‘ Minimal example +## Minimal example For a hands-on demo using `metagraph` directly (no workflow wrapper, no row-diff/BRWT β€” just the column-compressed annotation, which is plenty at this scale). A *label* is whatever tag you want each *k*-mer associated with β€” a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record (1000 labels for 1000 transcripts). From fa38c8309e55fbff1bffb9423916ed9e058277f3 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 22 May 2026 01:16:21 +0200 Subject: [PATCH 06/23] README: restore install-method badges; tweak Index your data icon - Bring back the three install-method badges from master (install with conda / install with docker / install from source), with anchors updated to the current section IDs. - Switch the Index your own data icon from file-cabinet to construction, matching the bullet's Build a k-mer index verb. Co-Authored-By: Claude Opus 4.7 --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 55c1e9024f..1bb260c653 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/ratschlab/metagraph)](https://github.com/ratschlab/metagraph/releases) [![Bioconda version](https://img.shields.io/conda/vn/bioconda/metagraph)](https://bioconda.github.io/recipes/metagraph/README.html) [![bioconda downloads](https://img.shields.io/conda/dn/bioconda/metagraph?color=blue)](https://bioconda.github.io/recipes/metagraph/README.html) +[![install with conda](https://img.shields.io/badge/install%20with-conda-brightgreen.svg?style=flat)](#1-install) +[![install with docker](https://img.shields.io/badge/install%20with-docker-brightgreen)](#-docker) +[![install from source](https://img.shields.io/badge/install%20from-source-lightgrey)](#-install-from-sources) [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![DOI](https://img.shields.io/badge/DOI-10.1038%2Fs41586--025--09603--w-blue)](https://doi.org/10.1038/s41586-025-09603-w) [![documentation](https://img.shields.io/badge/πŸ“–-online%20docs-blue.svg)](https://metagraph.ethz.ch/static/docs/index.html) @@ -31,7 +34,7 @@ flowchart LR ### Features - πŸ”Ž **Search public archives.** [metagraph.ethz.ch](https://metagraph.ethz.ch) hosts a search engine over indexes built from 50+ petabases of public DNA, RNA, and protein sequencing data β€” think BLAST at petabase scale. -- πŸ—„οΈ **Index your own data.** Build a *k*-mer index over reads, assemblies, or transcripts; queries return matching labels with optional [counts (abundances)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) or [coordinates (positions in source)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates). +- πŸ—οΈ **Index your own data.** Build a *k*-mer index over reads, assemblies, or transcripts; queries return matching labels with optional [counts (abundances)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) or [coordinates (positions in source)](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates). - 🧬 **Sequence alignment** against the full annotated graph, with sub-*k* seeding for arbitrarily short queries. - 🧹 **Scalable graph cleaning** to strip sequencing errors out of very large de Bruijn graphs. - πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly).** Extract sequences present in one group of samples and absent from another, driven by JSON rules. From fdb3c5ac000c511a74b6733e562c22a399c11864 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 22 May 2026 01:25:31 +0200 Subject: [PATCH 07/23] README: fix install-from-source badge anchor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub's slugger left the U+FE0F variation-selector from the wrench emoji as an invisible character in the generated anchor for '### πŸ› οΈ Install from sources'. Drop the emoji from the header and point the badge at the clean #install-from-sources anchor. Co-Authored-By: Claude Opus 4.7 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1bb260c653..cfa41a6ef6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![bioconda downloads](https://img.shields.io/conda/dn/bioconda/metagraph?color=blue)](https://bioconda.github.io/recipes/metagraph/README.html) [![install with conda](https://img.shields.io/badge/install%20with-conda-brightgreen.svg?style=flat)](#1-install) [![install with docker](https://img.shields.io/badge/install%20with-docker-brightgreen)](#-docker) -[![install from source](https://img.shields.io/badge/install%20from-source-lightgrey)](#-install-from-sources) +[![install from source](https://img.shields.io/badge/install%20from-source-lightgrey)](#install-from-sources) [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![DOI](https://img.shields.io/badge/DOI-10.1038%2Fs41586--025--09603--w-blue)](https://doi.org/10.1038/s41586-025-09603-w) [![documentation](https://img.shields.io/badge/πŸ“–-online%20docs-blue.svg)](https://metagraph.ethz.ch/static/docs/index.html) @@ -261,7 +261,7 @@ metagraph build -v -k 31 -o /mnt/graph /mnt/data.fa.gz
-### πŸ› οΈ Install from sources +### Install from sources See the [installation guide](https://metagraph.ethz.ch/static/docs/installation.html#install-from-source) for custom alphabet / debug builds. From 433231e8407385592ff12a6fae2f9f39fb74bd23 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 22 May 2026 01:29:59 +0200 Subject: [PATCH 08/23] README: rewrite Minimal example with simpler test data Switch from transcripts_1000.fa (1000 Ensembl headers with pipe-separated metadata) to metasub_fake_data_simple.fa (3 short sample-name labels: kl_sample / zh_sample / tk_sample). Query output is now human-readable at a glance. Also drop the closing BRWT/RowDiff paragraph - operator-tier detail that doesn't belong in a minimal example. Smoke-tested: output in README matches the actual command output exactly. Co-Authored-By: Claude Opus 4.7 --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index cfa41a6ef6..c3a2f22ae8 100644 --- a/README.md +++ b/README.md @@ -108,34 +108,36 @@ Other ways to use the index: [`metagraph align`](https://metagraph.ethz.ch/stati ## Minimal example -For a hands-on demo using `metagraph` directly (no workflow wrapper, no row-diff/BRWT β€” just the column-compressed annotation, which is plenty at this scale). A *label* is whatever tag you want each *k*-mer associated with β€” a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record (1000 labels for 1000 transcripts). +A hands-on demo using `metagraph` directly (no workflow wrapper). A *label* is whatever tag you want each *k*-mer associated with β€” a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record. ```bash cd metagraph/tests/data # 1. Build a de Bruijn graph (k-mer index) with k=31 -metagraph build -v -p 4 -k 31 -o transcripts_1000 transcripts_1000.fa +metagraph build -v -p 4 -k 31 -o samples metasub_fake_data_simple.fa # 2. Construct an annotation matrix linking each k-mer to its source label. -metagraph annotate -v -p 4 -i transcripts_1000.dbg --anno-header \ - -o transcripts_1000 transcripts_1000.fa +metagraph annotate -v -p 4 -i samples.dbg --anno-header \ + -o samples metasub_fake_data_simple.fa # 3. Query: for each query sequence, report all labels whose k-mers cover β‰₯80% of it. -metagraph query -i transcripts_1000.dbg -a transcripts_1000.column.annodbg \ +metagraph query -i samples.dbg -a samples.column.annodbg \ --min-kmers-fraction-label 0.8 \ - transcripts_1000.fa + metasub_fake_data_simple.fa # 4. Print graph + annotation stats. -metagraph stats -a transcripts_1000.column.annodbg transcripts_1000.dbg +metagraph stats -a samples.column.annodbg samples.dbg ``` -Outputs `transcripts_1000.dbg` (the de Bruijn graph, 613,859 *k*-mers at k=31) and `transcripts_1000.column.annodbg` (`ColumnCompressed` annotation, 1000 labels). Sample query output (tab-separated: query index, query header, matching labels joined by `:`): +Outputs `samples.dbg` (the de Bruijn graph) and `samples.column.annodbg` (3 labels, one per fasta record). Sample query output (tab-separated: query index, query header, matching labels joined by `:`): ``` -3 ENST00000619216.1|...|MIR6859-1|68|miRNA| ENST00000619216.1|...|MIR6859-1|68|miRNA|:ENST00000612080.1|...|MIR6859-2|68|miRNA| +0 kl_sample kl_sample +1 zh_sample kl_sample:zh_sample +2 tk_sample kl_sample:tk_sample ``` -`MIR6859-1` matches both itself *and* its paralog `MIR6859-2` β€” they share enough *k*-mers to clear the 80% threshold. For larger indexes, the column-compressed annotation gets transformed into more compact representations (`RowDiff`, `RowDiff`, …) β€” `metagraph-workflows build` chains those transforms automatically. +Each query matches at least its own label; `zh_sample` and `tk_sample` also match `kl_sample` because most of their *k*-mers are contained in it. ## πŸ”§ More recipes From 5abaec9ce3d3e81b4745b447fc8375f67c79b49f Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 22 May 2026 01:35:58 +0200 Subject: [PATCH 09/23] README: use --query-mode matches in Minimal example Default query mode (labels) just lists matching labels without counts. Switching to --query-mode matches gives
@@ -283,7 +282,25 @@ See the [installation guide](https://metagraph.ethz.ch/static/docs/installation. ## 🀝 Contributing -Development notes β€” docker build, Makefile shortcuts, and the release process β€” live in [CONTRIBUTING.md](CONTRIBUTING.md). +
+Developer notes β€” docker build, Makefile, releases + +**Build a docker container.** Run `docker build .` + +**Makefile.** The top-level `Makefile` conveniently wraps the common build / test invocations. Supported arguments: + +- `env`: `""` (host) or `docker` +- `alphabet`: e.g. `DNA`, `DNA5`, `Protein` (default `DNA`) +- `cmake_args`: extra CMake flags forwarded to the build (overrides the default `-DBUILD_KMC=OFF`) + +```bash +# compile in a docker container for the DNA alphabet +make build-metagraph env=docker alphabet=DNA +``` + +**Releases.** Three steps: 1) bump the version in `package.json`; 2) tag the commit with that version; 3) create a GitHub release. + +
## πŸ“ Citation From c4fecea28a3390a6610b63a6f8fd3eaa8ecd2db5 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 29 May 2026 01:03:06 +0200 Subject: [PATCH 15/23] =?UTF-8?q?README:=20final=20polish=20=E2=80=94=20AW?= =?UTF-8?q?S=20Open=20Data=20pointer,=20icons/wording,=20consistent=20Feat?= =?UTF-8?q?ures=20punctuation,=20trim=20logo=20margins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a MetaGraph Online pointer to the AWS Open Data preconstructed indexes (download + offline query). Use a globe icon for the public-archive search bullet (reworded to 'Search in public archives') and a cloud icon for the Python API & HTTP server bullet. Unify the Features bullets to a single 'Title. Sentence.' punctuation style. Trim transparent margins from the logo PNG (1138x569 -> 631x399). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 12 +++++++----- .../docs/source/images/metagraph_logo.png | Bin 34533 -> 12734 bytes 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1d809ca931..361b1a68b3 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ flowchart LR ### Features -- πŸ”Ž **Search public archives.** [metagraph.ethz.ch](https://metagraph.ethz.ch) hosts a search engine over 56 petabases of public sequencing data β€” see [MetaGraph Online](#-metagraph-online). +- 🌐 **Search in public archives.** [metagraph.ethz.ch](https://metagraph.ethz.ch) hosts a search engine over 56 petabases of public sequencing data β€” see [MetaGraph Online](#-metagraph-online). - πŸ—οΈ **Index your own data.** Build a *k*-mer index over reads, assemblies, or transcripts; query for matching labels. -- πŸ”’ **Optional per-*k*-mer payloads** β€” attach [counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) (abundance, e.g. expression or coverage) or [coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates) (positions β€” losslessly encode source sequences and return per-target hit positions). -- 🧬 **Sequence alignment** against the full annotated graph, with sub-*k* seeding for arbitrarily short queries. -- 🧹 **Scalable graph cleaning** to strip sequencing errors out of very large de Bruijn graphs. +- πŸ”’ **Optional per-*k*-mer payloads.** Attach [counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) (abundance, e.g. expression or coverage) or [coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates) (positions β€” losslessly encode source sequences and return per-target hit positions). +- 🧬 **Sequence alignment.** Align sequences to the full annotated graph, with sub-*k* seeding for arbitrarily short queries. +- 🧹 **Scalable graph cleaning.** Strip sequencing errors out of very large de Bruijn graphs. - πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly).** Extract sequences present in one group of samples and absent from another. -- 🐍 **[Python API & HTTP server](https://metagraph.ethz.ch/static/docs/api.html).** Drive MetaGraph from Python or query a running instance over HTTP. +- ☁️ **[Python API & HTTP server](https://metagraph.ethz.ch/static/docs/api.html).** Drive MetaGraph from Python or query a running instance over HTTP.
Under the hood @@ -56,6 +56,8 @@ flowchart LR Try MetaGraph without installing anything: hosts a search engine over indexes built from 56 petabases of public DNA, RNA, and protein sequencing data β€” RefSeq, UHGG, Tara Oceans, UniParc, and more (see the [databases list](https://metagraph.ethz.ch/indexes)). Paste a query sequence and pick the indexes to search. +Prefer local analysis? The prebuilt indexes are also published on [AWS Open Data](https://registry.opendata.aws/metagraph/) for download (no AWS account needed) and offline querying β€” see [Preconstructed indexes](https://metagraph.ethz.ch/static/docs/resources.html#preconstructed-indexes). + ## πŸš€ Quick start ### 1. Install diff --git a/metagraph/docs/source/images/metagraph_logo.png b/metagraph/docs/source/images/metagraph_logo.png index 7d2d27176c444e8c4e5af65503bcdf75948f1092..73b04678356e33833c72c2b00e44d397fafc8b99 100644 GIT binary patch literal 12734 zcmZ{LcR1YLx9=dL1yK`W#2dXwqQ$5YJ!;g5PV~-gsgVrw*mrgjOc%w7*nWZ07kk#Pcr1ppwl`7bQ z_J_<-HwuhWA%fj3Cr|AdrEoQ%#i}~yCCfI<(lBg`>}CfTl6l4z58x)fr?X(n3G}nT zdeUjiPnE@U64(waOdp95+!^-k+k^&B`JEeSKN#wbXjWU0wXr3dLuqb=Y(K_TRp^Qn zS{u9f#S2)seLE;TV)@Eu7Fv;z7hyzr7c=Pu`V{AL1is@S#lI^e0cDp)SRip~4f2+q z(>~dV_1-IKPH1(lcbvsVC)oKsB?KgyA# z>NfA8&ZdCg4}*h+!jWuFB0(#})pzTBd)j777DYI}Kh3dQYWv8;<8&dy``6GXv0Q(D zXRd6WK@vn}@rMkwA6gn2H9It4{h~|$ALim)HxdYHW}B;I9FApp+XYAQHle=y+%O^i z@rB(4INzwslK5ttS8)M!PG`Dan&d`>;`fRtDcOxWcvbTMgsbLYf?T?xcrI^D0{C6c4YSP$+qbpGK+h=` z$fQ3%a#bjdEZ2X6&xER)1u+n_q41TZ0kr`HNbg0ls`U;wH1=|a%{;!%ny z=PRC+mpC^wXb8Vkvg@QA1y%pM!YZ=MMhi@NBL;x`%2NK7;4#j-2lP1D&4Ta~5bsA= zjc5#?gnl5sk&Kwv_4x-T7rYy<=?+<>G^IN1`sx<+0f23#^dq(%5hWfcH=G-J46&PS zL2fiKAF3a7^_Pj_OZg37wZGo7389Tz|)wG z^vPXL6(!;n6T9VsMd$*Vi$eA?e-)T2b#mAA64$ekUw-e#Au72jR5IE#5z_t-US-{k zJib>5nA|^#?AgY+2G3_)Ai8O@(W+!*`+;w?i>J|)i9k|}5p`jCB$MU@?8v;P4Z?~) zA6l-zc+2DQjfHKRrMuYbVVq(~`t0Wb6mHa>k`lqHHeap}5XQ14z0S-mJ?81j0oWDm z#ioV{0D=Ay3iszS*!blYk%^$E=%*>3OymSdkzw6tcP~$r62|mS?FO*HKQ^QK40hGE zD?2G)0Sc;`m41b9{z&YiV#yz17pvuLe9EftMAOXqt?#j5jIn&2h;zMhr4gfj^FL!` z?FA(}l>h-zz;ge*-RL%`I)--}@^0Fe` zEviYhdm0H4&|(etK~^0FCm5o@1Aw#?%2oeeDK!^wEeSgn5A|EKN@@BZIRa5;T9tgP zW5{men@3W$H(2;E0rmpiGJO1apB+1-5DEA(mf-8J{3)V1e)nuny=(u=#-vn{kjXFOE}Ru2b8*7jC<~sP!1YWcckhy74OU1Jg6XTu=)2&w*x?VjsHp8_EPQcI*$~ z3}O1_{#X+hObDK7d5O77{w=O!LRqnFELLNwQ|@mG8=uhpO;7qk*-jhfL495HN7!je z>#-v(R)5p@G`App?jR0XbXC1Q^L#K?%Vk;w^gkkIahVGAS48BKTqAExWjE|zDzjCk z0;0bA^MWjJrXpnaIS^uU>~dsd=k-q~e&7XdvvBQ%MLybo-Ol$93k62wYcQ(OPX>>$ z{cX1pjUF==aJ7|t?l}wR&RfJF)>9Uhs=g;|dp$woN-JF@i4$2JEDW7I#5O~w$LqoebyNx4=UJQ`8 ztwY|d5M+}s0KN+XnVQBvx;aM$GUO5hkV}sNW(y#11zAx= z0cn0{4Tx_;1Iesbsdq6!!a`33eLxQM&uV!UCc%Io6Z7>e&{aV|`oej5D!|Tf=YK-C z?zvlll16MjtiC(^#4_M$=U{UP`iqK`cn;B%_(*_LGrOC!P;y{4Z2`pzq6`&kaP4L} znk2=7b$WEya}pnM_);TjrFx(7gRWXKs1EaA9NHTsTvp9c^glP5#Q1yR(o&@ z)!xWf(rawpjAEt&+B)R<^MB%Km%cKiKhcRoN}_m47*0TPp!Neb2c4^g#qxX1u=Yb? ze>OV2U7f;pkGq&eRv?NG5l3!(wm}uF;b^mz6~Uu9e6Sv?Lt?kq@5W9S#N}!4uq00{ zh0T2Td}}VZgmi&Z9YNw;#z6}X5=UAoH)DH9=e3hhul`71c^0J}XGYEc_|Af^^RW@h zoVy}p3O+T_ z8%h(uVRGi07ND^xo$cBWt@@*%fC%z_sby;P{m!i+wc!1J3bhAUaBKo&Dw@-*O(>@4 zy)|U)KlU_=Hah($7MY_=>g^jzhqF1wpH~U8TPVjM*SzYL4Atpav-}-h^XO{Q0QFLE zpqt=T-Ef<`n4w2isKz%RqPmYkmChU@;~CN4RV}gUoNKK=Zh7DZy18XD!ia#WAP&{U zHAE$nKmo)~j$vs`S$1ZGXAWqRLTuXXWf!mp8@Fh4!xn*4Y%!6h#B+A6U=MG(S-82r zY9J3fZ*n_l^>LKbR< zOyyeGNd1_)*x*1McW;sr)VE$_0QX1CWAHd?GzN(VA?RX$kP^zP zk{4p<)2w@s>ffk~k>`UHEaUbw$Py6}4sicPWOL+kAlG;(t_w;dU^eD%(dmc{_Mob~ zg%c;Sg>$2Ve9F_t7)5o|3wa>Cn2}sEocj(9p~p2_`)Y8;K)WeB;FbhLWDGV^k@(cq zgqa(0WcO>GO2{#*d%+`0r~wlgao@7*UUHb;hv0~7>un<%i+S5V0t-5EW$Ub8a8m$s zPqS$w{dC-b$VXO*)(@@!OnvYHghil>5G}#Hbdw;^D0Wp9qQ&%j4 zTnjpB6oGK7FQLEof?`Js4%m9Ktk)<3GcgVE(0F(|kPo#=Keg4&{%4I6AeFfpsW9p= z{`Dou`BpEiQ~Tv-mm9>dzK@vOs@JOGqn>}CgNO_|&H@DcvMN8Qx@|;zdspevN^1WI z)eW1Fio>pqXt}0EGV|;n@T54n7O}i6NazyaUXs^!Fw(+ud2AN^)kd9 z7#SeIAt-k}jqRH43hDz+FX~j_Wu*Ab8TRUxHk!Vz`O>`$FjTuvf>%=vsjO@AI{*&$ zA?2TV>{U#7{=q}oBrQ#DX}l!CUu1iNrRSZqKfA)Hsh#lxBxZE?pyr0oN; zLm!Lz6}d+JhdAvrEv%>n9rz1rE~Iz)2q&-sBIRC0PgnU?xWG9Mhn(IUT8G|%`&jfm z7ReLYjL}C8;ZD*<5?yAvW8;#K=1abEO%onDyh%Yy6;}j{MqdRjkX-XMeg(SJE%doR zQRd(>&0&PIva{Oli92_Idy{^rd+M_AG2`T$3-o`p!mB&P>U3kPKcAZFoY1CFS{#w- z{{pyI8nRG+d9Erz`K3gf!r%2-J&}2xi$}6ccoVK{xVal!gu0ucAB7w~56%4_i*7tJ z8359jn4YUPEylfNs*o<7>B+_e?y&hHeSa2jD|WpOk!FW}OC zvzSlZRV~#1C~`m6Z6w(9l{#-@WQ+@e;n}m^0BHf&Mcg$|jy!YSJeI{`Vtk9|GR$|n zSlLavi#|0;E3yN}Bwh+YU0dqy&PJ{y6z3m-Lv+JWjhMr~Y_!Z(?O{L=Bq8Q3mRDNd zQv?z|o7myoma%&((;!L`|8uSn-Ko|@Fb*#2kJ0|le}61utZNO`#d8O(BgI>kWe~EO zMW5bmAr(sv2{)utEiQ$V!e2!LQ94(}IVFMQSV){UBEkOH^S$$pU3qBfBA+SPq_%!? zpJuBPM*;-!*nRG<;l!&S+L&asqs-_ed}_+ZXi}{g+N2qcb7e99S=PIcuxGu)w+kP& zQ7`b?NV}TgjpH?I3BMz!zO@w6dsSvA=l=+Gik>`5+Ibt%Us3UAtL_%V zb=laUrUHgXq-Sxzjlim)2=S)qwn^+>mxc&Ue*sBqrj8P@8pa zV_7$pl9GX4>@v>oPYv=PwD@*4LtZMxkpJ;N7=T-^7Y&Ll5UWdFT{Ao!Q@wUaY@K9s z=M&T_JzD=^wJ}xxmqCJAOefLf(0F|lcBTEW*{R5uilly!UHe1(M1D!Nw=(RAKA;a*pSTo>KWaQ27f1UmqRM9T zeS}kLtJh*H8pVgX1J5nDeXbN5E>3*oC1tX%ejOamwVjnawX*6-aQnTJI~SvMZuN;b zK54qnW|vxzG`u=**tv?k>JFF6ZM%p*S>Ihi?)&{o=6XyM>N4S%lh+wNcNi9JoF~HA z>N}%x@}XwMJN~zj)Leo!OMF|HPpd z_(J%D$5igXCc&KhTz!rcw{zei_hGp6nMP)VF-s;~l>izS&F}rWyyX}?6V_Dre2yON zrIVS^>-=QY?h)%qBsng%l4sIvpcZTMAVIPZr zI@PJh1D@Ffg2CVMdKkmA&a7kdg3Ug?%cI-|FolZFUnesH=;}a?$A|)KW2;80$tjy?uc38 zLzJd34Ihpl1s+9-AMw1^s})8$ZZ4`A>xxb){WiQt6ihT9d&+l%76t}CZq^(BZMMEg z2Cc!_!CMu7Yj)0&$t3-yp(phERHx~DSW4*Z-QbpZ@x8VytuITUfN-Q#z(@@V0g|-h z0#3dzl*%qYgTpWP+U42tv^`o=bkcFMW|cSkwx({X<0>JnU$}w&H0|j_r)8CKr}G59 zz#e@08Qh&)iR1O>-Ehd@Z`JEFNp6!v*x-d+<^cX{FSnNY(7_4rz*Sk@*0nUL3t>lw zlnv3=)t0~n-?&+E^?=`0N9TH%!DD9%hyy`X%dYp}c(mjR*xtlrxHB-NfI=Rr)--cg z7oEur_iKYYi=AGHe}QFq8R`!jU+%LToac6DdLx^h5teg?x_}oBR6R95OT1@zo~zfg zsP)An*kv_d!+iy^E#}^}kt%UQf?oO)g*rc1DS@_a%~-=TR=t9mOp&AnUi($_vBG zRGMwlOp$?;G&6zCN8ak!UJKhY$PDPTt(x=01+qqXz#`cL2@r?@P|IiwdzQoH)3%dA zcLjC%Br)ZKXev%T@t?tmD&tZI0N%glHoWBUz6+cY_L>8mH@_?BUw0gGqjN_S`j{uw ztIoXD(|Ae;GdwPnoKDEY$y}s`ZdcE5<<6LuP1q(Q!4}tt5 zhh<&%@Nerj6_oM#{1k{aM*f;b51*)4pINOk2+S}Fj-!UI)(6GW0F`>L$1cD4a^N5H z$OGov?ENnpeL3AO885f57MdFv9-->GzX5-Sse^}d4j8SrqmQ%BY}CqswzTa)Z1x!b zMpGS)(MvU~8F3=NtGJ(RqWuop$6MFtzNilwR(w8>neyxaZ=*^#r4G#iQ1qb-bO|)D zKonUqxo%(AZT$lTytOoXr^P=}220)&J%e8xHty%yjPiT$Yp)5Dz;Q3kETG(BAXLrSK;~hPlW7suTk#W2ds!;agF4^} z@Jg)0-SzLT(e%iS{RQg|;uzj^x}rYN9~gp!(%FA2?Anxci#H1YW)EdCN* zHE?mVYP*e5!jNCiy;2+2i4Sa^yWTV07xV$gUF)VPP8U5ZIYvuJ=E6-*KdVWtayG4w zn*6z%XTRLlhP!RA-ZP3z&b#JXXM={G966+}@FZQ0K`^5t@#1>J6=fR<@!}ch?@t03 zwOTe{ZHWb$Mw}}&4P%PGca)}&Q!WjiB?`|Uei-mQ8##(l5Eq{aFRwsU}VqwA5DjuJnD?uGtU%fXlCENcTWaxY(*1CMz=!eb#&!Ngg1mcm4^!%f5Ac zCp6LRYkkS}n$~5vUZ6kbOOF%slGO*@N_F&Y(~UHOfkJ)W7MYnR5k2Se4WIw_&eh!J z(agOT56rnFbP2t28FXj8aefuQ9CmHq5S9hR@d5A%c?IIPlAO_B9HH zheQ78i-fDIB^A;RBNo17*LSl&?D^CWW7f3cr;`MS-@9i^AQ*Ak>s`F{6g=4GilLLy zitixJ7wD();5u<<6p+6Y@m%CEiczsjf)y`g1>yUi;1U8MQa}SDJ|$KDoHhGD%9Ip3b??UKta@ zwuxZ$%p5v8O&=`4hKCL%g@JfcO?9{%bavD__jecT!+?vK1dg-ibb8PP``kb5Z@ljA z7Fri_&#OAzf^@*M78AKlxcIuvs{)i_jpK8>Vq}!q$I0ZP8E`6M$il|HQFXxES_}W8epy$^LmarxMqO}5_(DqybfO!yUmWK_`*?` zo%yxegYHfl^;`;aUtFjhU+7eLM?w zMMr|~mvmTHQeoS+;%?YJ=D*$B|J~9Bnl1mImM*qV`5$fH|5*6nP0asXc+(pGk2(L- z3%;pyW-I;M5U$Eo)cSNO@}V~6znOu;@UvU(#4brwR+ zT1sFjP{PMnUfero^K#Kc>?k2+krMiDLRei`)77tl!gC?EKiIOq1u~ri6KCsFRN%ne zVZ_X07;~2bQ#|3$G3rROFFGfUu(v6ncbbC2hx! z#2x3fRGf#GczddfG+PEhomh_GGx1!6#)x++0SjE+b+kBZJ3K#Rv#**==4Uy;mW%on z`_ZRIP_+-Ck)4UqR}6JK(Fu9n4-nh-Q}qg4@<8SHge3$yzGmyQzC#?93)QUWPZM`E zlN+HB$7Pg8ytn8jHqH6exf~UT${HN2H%;}a<`>2_{^L&|IL4_##?*g!P4AD^CC&T&&%QUGr8M zl{Gt~;Uj?tl21Y!jUws{qlcI=*JE^9$W#?wDuUNvRe_y-%Pq(y{Hzn+mqj3GptsBe zsgWXymFv(hT#pq7Pr>6G(=O*2kzg0{_=oPY#T$&g)UQz(v(OpU4?H^vMNTn`;4LXN z2h&rpZG*WVk%&gztH;mCA)(J;+e!KtJha{T`DfK4atj~w+&%ql>C+eZ1|-p8$qw{5 zz%im`vT6{=Pc)ye=Fs3=5Y8{cWoxLAf@hpM3$*o3(bchn3uKig*6Z%&+Ct1`RM9xj zI^vDEOsH>79z7=&`gSqVA;^|3J}_AgGL2Q4(HB-7a1>{j50}U9F^;B3*sz2EkCuejjk$BkFwaScjB+czkj-2 z=N zJqzx&n$w}T_Yqmk%X0+Vi&4@hGdyx|upXK}?mpzZm^AmjYw2k>1qmQ{(#Aa%e&$zd zip(r{^Ahuuu`o^Lnyvd=F0`Z8DX$O8LImq4hxNXDk-R*_zv~~_xlsO5DAP)?_v039 zMl4uF0+nnqeo&8$IU7lSsB-b0Hb9|ZaVM_fJ}_x7oPXx3pgT&bg-@R&~YKBm_uXaz3b#7hPPn$*yW)@9A=Dij@2SrciRW#CBP zbfTC7=20~BTg~XrnlhIOduhmPC}J~3Gzv-Kf!6`QUMPQ6X~ywLqe>+ypP*&iWrFi` zJ{|U4#P&n_eavE7uMe`xW{FaLt)~#~R{Kx=yEk+Te3hbZ!cMOK^%R(Q#CSUz#|2L( z31KosUpxEFY877=05&O-a%&(oR@^bkS2Nldn4E8q4c~8(Os_Ao7xF(z z`&H01DRw=sb9>c(gj5xXceB+pyuThKSTQ8hZ&DwDiJ|Fu#`9+W3_s^;bo;82rU+ru zKjEVno_!nRH5cBU083QhvFn&UAsr;>FLY-9*G(I|i?yw74LX%#a~hgia>q+{aKl^y znmMfz>O{K0N8`XCq7w>1Z`MTWCk)}I zdO50jAl1aZ(uZ_<{1G^1XcwX&sHbLNau?%QG1$7+H(0`?Lg33NwUVXXXW|o+DmwKY zzoAGCPo?ETc8bXKU?;NLO`t_^cw|rKS9+;Mj!2u4!C+Vk$EL%lfC;-}?RJo z(3<|E#;9HKq49W16`o5ct8G6MOzb@P^VIc6-s!}+ujn>mzvjHyrH{kuU~%dswwPX^$Q6;JlPw&M%w9ffjB(NXb3%}Ut2?Y#-R~m8F&U6n&+8xg z3?})&{?r@B{p|g#VHcfL#kU``Pl_gY15AD|-$u{ax?FNMZErr6n8-b9HC)&cZ2Z-x z_H;Sv(tX3oaHGaC{H;7w>b79ll%#;`=M6BJ-v@KQXR9ikepNyqBhJwHF^c#140qA5 z&IbyZ+fOz<(;gK6)d+Z{fRU$(7j(cJ=^dT>duNtbFvFHg)wv0~5Dro*t^M$;JBWw6r(2F?aE5BOwC zfU~4CtY1rO9Jv0GMQj#b^`*DG^UU7}?}fd6Zmj5E{{ZtyrA^`qD$Y!GfvFuxV3S97Aq z<9{8}*>?0L$UAOU=6J*AO7YoIV`ja(#i&PC6u)>IG9N8fzRqyP6atZo6@(vDIUpie z521-+p~%U3=WyBv3ysI9uFSC>*JEYw!gHi8?rU4;U{ir?Ny9Pjg@c!wT-17gjMbi) z$7B&GjfINZ`QXZA=`O~y=Ll}-`{?R@vcezIdg6t3GK;>$6(fGIVvgO!lcN|koC^Da({4y|H;K=kY6LL78m<(0J z2W_~wz$c$biInc1#J`!Q3xNd2YWvO2W-lxZ{0Yhw^%zpnUJB$4kJbCA9>CEpBeeMg z&t&rekvAwrq^grr_R8~v%c?@BFuy4;gbwD8AQ@{uh2~hC7p}=XtQG$$_rx%y>1FsG zb%q?T)>2;1qp9_S4JpA3cr)w`Qze5*5-oQVs%I~eJUw^86`W=Fklxt-u9cNe zQ<*+=TdXdYC-ix|9nz(Nf=8G8OwHjTCJjh!emnbXFKk|STNDc)(HU2PF)`EkJHRc| z-t3uq?dWp;!Xyi*lKrhTzGXM~MY^x$1B|W&5ysQ7K(g}{RR`E197zMty6bR$j=f+& zi!S`BCttkfUR60(*zrk(-fxB0@mRjfT3`j5Mox2)?B%=X^Cp#sOOt6OvN}1EzK^ht z{zBsfp@=3f=z6eAct96>ho@?mz4zBVe^O%DYP3qts5NY(#umg!)!mbbFsuh_@dna+ zCzv+aw1O}0sWg@Tp+a-`ew_;JetJDuvfDtWv*qOBI6X%;7IwBJ?SCwUiY0>WK0|F6 zt@)-scq_6+`6Zt1c8b)_dqw|JpJy*kL+j<oRFBAgl7q9`wIGp4C5$C^}IuK%&Zt6Use^jytID6@lx%1y;?}l zhA)q{36#yb!pifXVlOm#_1VuFt)fA;rW6P}o1<$<>IzV6Qu&PYtM0vx&2B@gfmq}1 z-asWYUXnn7GSexJ4x}fbzr(rAu;Rx~=FfpOcy-W`Tymf-|InV5J|9)#AauiNVo~z( zX{lK{H})xxb*BtL;HLgt=43n)Qoo7HRSAw=k?;rl{Ei`$KZApQe>{cP)XXJ&rkVH6 z&{=tIvKv^N%VkT>)O2gn-MO>LhJAzr)Oxki^Uf2N!q*&$Qys(S+FSbrdHL0hbSa+Z z`s3xJ-{O`B4M#kvVM(5}&)+}Uu!3ju1Gmr(_r}{#kY~dmr`{l$fp;Q3FP3Ar5Yu-_ zErrSLVBD z>45eV*mQVY5O?+_GAqv^&@lV)c>=oHA!3a_1#av8<2IV=-WE>U zRcqw_;N_a-j>u_iZJ1B(9^^fKjwjOpuJ)UO$OY%w4WyB1X8P3z4Mz!Lai{M~8IWwn z1hiWzRz)`vlYh5mNW04%*;dOxb;sAKbFC9cXTgT{&@+9wsXY;0%EETl0+fjxInfqB zXj`qJ>Cq2L{Rz;1E}avVuMzDt)9$$b7Dq~(J|dFwobCkisLWP z$P1__zOZg0t1+!@3(j$JuHU{)sY?YyC}fa+0oGv)cUiU2DB=DjRJYnXOgQHK&S>JW zdOSO1F!SftN6Hj1#hSL?^JcdZXTH}W5E_Tg7bB<1$Uj%3Vax^uETf0jdWPz`qpQo) z3JisG{(d~HGes~SG=9_CgqEx zJUeA$)hE|Ei$}?ZD#~}XXRTWFo%lk3|2T87kIZATU+sMjWp~{Y-$H3|gdmTBd(=^B z_sVO;j7@|OO-Qr$7{B_5pBIsI`S#rMtRbm;?=&OZ=W8HBbvS0wH#)OZpSC?bxHh~5 zg;1x)S&j`v;J)p_>_2Bpzyl8+Wge0pg|=z-0v6RS_e{PN+evBH8Lf5o@nAZE-Enh@Y| zyei5`P$2_u`K8n>vi5J4Bteb5Ltfd#x>pUBE3kgwms39V+g2AT8r=_VY56AOWT%Al zrg|Jv`%MTKO`WcfdBvi literal 34533 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 MTE?3AJI??3KTJuSApigX From fb93baf549bc831dc9a7238b5532ac145a3e9146 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 29 May 2026 01:05:38 +0200 Subject: [PATCH 16/23] README: shrink header logo to width=260 After trimming the logo's transparent margins it rendered ~266px tall at width=420; reduce to 260 (~164px) so the header logo is proportionate. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 361b1a68b3..b7111c1f1b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- MetaGraph + MetaGraph

[![Platform: Linux | macOS](https://img.shields.io/badge/platform-Linux%20%7C%20macOS-brightgreen)](#-quick-start) From 98d2c679c4824213bb669cd5ed3771ba88234b95 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 29 May 2026 01:18:25 +0200 Subject: [PATCH 17/23] README: collapse Minimal example; link docs Quick start for index construction Fold the hands-on Minimal example into a collapsed
(heading kept so the #minimal-example cross-link still resolves). Relabel the build-section docs pointer as the 'Quick start guide' describing index construction, and note the git clone is only to fetch the example *.fa files. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b7111c1f1b..d852397a41 100644 --- a/README.md +++ b/README.md @@ -76,13 +76,15 @@ pip install --force-reinstall "git+https://github.com/ratschlab/metagraph.git#su Clone the repo for the bundled test data, then build: ```bash +# Only to fetch the bundled example *.fa files β€” skip this if you have your own data git clone https://github.com/ratschlab/metagraph.git && cd metagraph + metagraph-workflows build <(ls metagraph/tests/data/*.fa) -o out/ --primary ``` `--primary` indexes one strand per *k*-mer pair (about half the size; appropriate when read strand orientation is unknown, e.g. typical short-read sequencing). -Internally this chains `metagraph build β†’ annotate β†’ row-diff transform β†’ BRWT clustering β†’ BRWT relaxation` and produces `graph.dbg`, the more compact `graph_small.dbg` (smaller, slower at access β€” useful when RAM or storage is tight), and the default annotation `graph.relax.row_diff_brwt.annodbg` (`RowDiff`). See the [pipeline docs](https://metagraph.ethz.ch/static/docs/quick_start.html) for each stage. +Internally this chains `metagraph build β†’ annotate β†’ row-diff transform β†’ BRWT clustering β†’ BRWT relaxation` and produces `graph.dbg`, the more compact `graph_small.dbg` (smaller, slower at access β€” useful when RAM or storage is tight), and the default annotation `graph.relax.row_diff_brwt.annodbg` (`RowDiff`). See the [Quick start guide](https://metagraph.ethz.ch/static/docs/quick_start.html) in the docs for a step-by-step of index construction.
Real-workload example with file list and hardware budget @@ -110,6 +112,9 @@ Other ways to use the index: [`metagraph align`](https://metagraph.ethz.ch/stati ## Minimal example +
+Step-by-step walkthrough with the metagraph CLI (build β†’ annotate β†’ query β†’ stats) + A hands-on demo using `metagraph` directly (no workflow wrapper). A *label* is whatever tag you want each *k*-mer associated with β€” a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record. ```bash @@ -142,6 +147,8 @@ Outputs `samples.dbg` (the de Bruijn graph) and `samples.column.annodbg` (3 labe Each query matches at least its own label. `zh_sample`'s 243 *k*-mers are fully contained in `kl_sample` (243/243), and same for `tk_sample` (207/207) β€” they share enough content to clear the 80% threshold. `kl_sample` matches only itself: its 330 *k*-mers aren't fully covered by either of the shorter samples. +
+ ## πŸ”§ More recipes
From 9b7341b107812f491b6136cfd6e63f48a890354a Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 29 May 2026 01:19:39 +0200 Subject: [PATCH 18/23] README: restore "extensible by design" point to Under the hood Brings back the contributor-facing design-choice from master (generic, extensible interfaces for adding custom representations/algorithms), closing the last completeness gap vs the old README. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d852397a41..a10be0ce55 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ flowchart LR - **Succinct data structures** β€” the default `succinct` (BOSS) graph representation uses only 2–4 bits per *k*-mer. - **Modular annotation formats** β€” `ColumnCompressed`, `RowDiff`, `RowSparse`, `Rainbowfish`, plus count- and coordinate-aware variants. Pick the compression/speed tradeoff that fits your scale. - **Custom alphabets** β€” `{A,C,G,T}`, `{A,C,G,T,N}`, amino acids, case-sensitive DNA, or compile-time custom alphabets. +- **Extensible by design** β€” generic interfaces let developers add new graph/annotation representations or algorithms with little code. - **Memory-mapped loading** β€” pass `--mmap` to any subcommand for fast cold start and low query-time RAM (NVMe recommended; SSD works but slower). - **Scales to trillions of *k*-mers and millions of labels** β€” petabase-scale collections have been indexed end-to-end. From 20dcdbd93446137ed4cbbc681ff9033cfda5e1b5 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Sun, 31 May 2026 00:49:21 +0200 Subject: [PATCH 19/23] README: de-LLM prose, itemize "other ways", theme-aware SVG logo Address PR #635 review: remove decorative emoji and the em-dash/ellipsis/middot characters flagged as AI-style, keeping legitimate symbols (math/arrow/en-dash). Itemize "Other ways to use the index" and clarify metagraph align vs query --align, and that server_query starts an HTTP server. Replace the raster logo with a vector SVG (from the source PDF) plus a white dark-mode variant, served via a prefers-color-scheme swap; remove the now-unused PNG. Drop the Makefile shortcuts from the developer notes (the Makefile is used only to build the Docker image). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 106 ++++++++--------- .../docs/source/images/metagraph_logo.png | Bin 12734 -> 0 bytes .../docs/source/images/metagraph_logo.svg | 111 ++++++++++++++++++ .../source/images/metagraph_logo_dark.svg | 111 ++++++++++++++++++ 4 files changed, 274 insertions(+), 54 deletions(-) delete mode 100644 metagraph/docs/source/images/metagraph_logo.png create mode 100644 metagraph/docs/source/images/metagraph_logo.svg create mode 100644 metagraph/docs/source/images/metagraph_logo_dark.svg diff --git a/README.md b/README.md index a10be0ce55..b309d6b8c4 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,68 @@

- MetaGraph + + + MetaGraph +

-[![Platform: Linux | macOS](https://img.shields.io/badge/platform-Linux%20%7C%20macOS-brightgreen)](#-quick-start) +[![Platform: Linux | macOS](https://img.shields.io/badge/platform-Linux%20%7C%20macOS-brightgreen)](#quick-start) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/ratschlab/metagraph)](https://github.com/ratschlab/metagraph/releases) [![Bioconda version](https://img.shields.io/conda/vn/bioconda/metagraph)](https://bioconda.github.io/recipes/metagraph/README.html) [![bioconda downloads](https://img.shields.io/conda/dn/bioconda/metagraph?color=blue)](https://bioconda.github.io/recipes/metagraph/README.html) [![install with conda](https://img.shields.io/badge/install%20with-conda-brightgreen.svg?style=flat)](#1-install) -[![install with docker](https://img.shields.io/badge/install%20with-docker-brightgreen)](#-docker) +[![install with docker](https://img.shields.io/badge/install%20with-docker-brightgreen)](#docker) [![install from source](https://img.shields.io/badge/install%20from-source-lightgrey)](#install-from-sources) [![DOI](https://img.shields.io/badge/DOI-10.1038%2Fs41586--025--09603--w-blue)](https://doi.org/10.1038/s41586-025-09603-w) -[![documentation](https://img.shields.io/badge/πŸ“–-online%20docs-blue.svg)](https://metagraph.ethz.ch/static/docs/index.html) +[![documentation](https://img.shields.io/badge/docs-online-blue.svg)](https://metagraph.ethz.ch/static/docs/index.html) -**Scalable indexing and querying of annotated genome graphs β€” from a handful of genomes to petabase-scale sequence repositories.** +**Scalable indexing and querying of annotated genome graphs, from a handful of genomes to petabase-scale sequence repositories.** Think of MetaGraph as a search engine for sequencing data: index your reads or assemblies once, then query, align, or recover source positions in milliseconds. -A MetaGraph index has two components: a **de Bruijn graph** that stores all *k*-mers extracted from the input sequences, and an **annotation matrix** that links each *k*-mer to its source labels (sample, sequence header, expression level, position, …). +A MetaGraph index has two components: a **de Bruijn graph** that stores all *k*-mers extracted from the input sequences, and an **annotation matrix** that links each *k*-mer to its source labels (for example the sample, sequence header, expression level, or position). ```mermaid flowchart LR - F["FASTA / FASTQ
reads Β· assemblies Β· transcripts"] + F["FASTA / FASTQ
reads, assemblies, transcripts"] F -->|build| G[("de Bruijn graph
graph.dbg")] F -->|annotate| M[("annotation matrix
*.annodbg")] G --> Q(("query / align")) M --> Q Qseq["query sequence"] --> Q - Q --> R["matches
labels Β· counts Β· positions"] + Q --> R["matches
labels, counts, positions"] ``` ### Features -- 🌐 **Search in public archives.** [metagraph.ethz.ch](https://metagraph.ethz.ch) hosts a search engine over 56 petabases of public sequencing data β€” see [MetaGraph Online](#-metagraph-online). -- πŸ—οΈ **Index your own data.** Build a *k*-mer index over reads, assemblies, or transcripts; query for matching labels. -- πŸ”’ **Optional per-*k*-mer payloads.** Attach [counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) (abundance, e.g. expression or coverage) or [coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates) (positions β€” losslessly encode source sequences and return per-target hit positions). -- 🧬 **Sequence alignment.** Align sequences to the full annotated graph, with sub-*k* seeding for arbitrarily short queries. -- 🧹 **Scalable graph cleaning.** Strip sequencing errors out of very large de Bruijn graphs. -- πŸ”€ **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly).** Extract sequences present in one group of samples and absent from another. -- ☁️ **[Python API & HTTP server](https://metagraph.ethz.ch/static/docs/api.html).** Drive MetaGraph from Python or query a running instance over HTTP. +- **Search in public archives.** [metagraph.ethz.ch](https://metagraph.ethz.ch) hosts a search engine over 56 petabases of public sequencing data. See [MetaGraph Online](#metagraph-online). +- **Index your own data.** Build a *k*-mer index over reads, assemblies, or transcripts; query for matching labels. +- **Optional per-*k*-mer payloads.** Attach [counts](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-counts) (abundance, e.g. expression or coverage) or [coordinates](https://metagraph.ethz.ch/static/docs/quick_start.html#index-k-mer-coordinates) (positions that losslessly encode source sequences and return per-target hit positions). +- **Sequence alignment.** Align sequences to the full annotated graph, with sub-*k* seeding for arbitrarily short queries. +- **Scalable graph cleaning.** Strip sequencing errors out of very large de Bruijn graphs. +- **[Differential assembly](https://metagraph.ethz.ch/static/docs/sequence_assembly.html#differential-assembly).** Extract sequences present in one group of samples and absent from another. +- **[Python API & HTTP server](https://metagraph.ethz.ch/static/docs/api.html).** Drive MetaGraph from Python or query a running instance over HTTP.
Under the hood -- **Succinct data structures** β€” the default `succinct` (BOSS) graph representation uses only 2–4 bits per *k*-mer. -- **Modular annotation formats** β€” `ColumnCompressed`, `RowDiff`, `RowSparse`, `Rainbowfish`, plus count- and coordinate-aware variants. Pick the compression/speed tradeoff that fits your scale. -- **Custom alphabets** β€” `{A,C,G,T}`, `{A,C,G,T,N}`, amino acids, case-sensitive DNA, or compile-time custom alphabets. -- **Extensible by design** β€” generic interfaces let developers add new graph/annotation representations or algorithms with little code. -- **Memory-mapped loading** β€” pass `--mmap` to any subcommand for fast cold start and low query-time RAM (NVMe recommended; SSD works but slower). -- **Scales to trillions of *k*-mers and millions of labels** β€” petabase-scale collections have been indexed end-to-end. +- **Succinct data structures**: the default `succinct` (BOSS) graph representation uses only 2–4 bits per *k*-mer. +- **Modular annotation formats**: `ColumnCompressed`, `RowDiff`, `RowSparse`, `Rainbowfish`, plus count- and coordinate-aware variants. Pick the compression/speed tradeoff that fits your scale. +- **Custom alphabets**: `{A,C,G,T}`, `{A,C,G,T,N}`, amino acids, case-sensitive DNA, or compile-time custom alphabets. +- **Extensible by design**: generic interfaces let developers add new graph/annotation representations or algorithms with little code. +- **Memory-mapped loading**: pass `--mmap` to any subcommand for fast cold start and low query-time RAM (NVMe recommended; SSD works but slower). +- **Scales to trillions of *k*-mers and millions of labels**: petabase-scale collections have been indexed end-to-end.
-> πŸ“– **Full documentation:**  Β·  Offline: [`metagraph/docs/source`](metagraph/docs/source) +> **Full documentation:** (offline copy in [`metagraph/docs/source`](metagraph/docs/source)). -## 🌐 MetaGraph Online +## MetaGraph Online -Try MetaGraph without installing anything: hosts a search engine over indexes built from 56 petabases of public DNA, RNA, and protein sequencing data β€” RefSeq, UHGG, Tara Oceans, UniParc, and more (see the [databases list](https://metagraph.ethz.ch/indexes)). Paste a query sequence and pick the indexes to search. +Try MetaGraph without installing anything: hosts a search engine over indexes built from 56 petabases of public DNA, RNA, and protein sequencing data: RefSeq, UHGG, Tara Oceans, UniParc, and more (see the [databases list](https://metagraph.ethz.ch/indexes)). Paste a query sequence and pick the indexes to search. -Prefer local analysis? The prebuilt indexes are also published on [AWS Open Data](https://registry.opendata.aws/metagraph/) for download (no AWS account needed) and offline querying β€” see [Preconstructed indexes](https://metagraph.ethz.ch/static/docs/resources.html#preconstructed-indexes). +Prefer local analysis? The prebuilt indexes are also published on [AWS Open Data](https://registry.opendata.aws/metagraph/) for download (no AWS account needed) and offline querying. See [Preconstructed indexes](https://metagraph.ethz.ch/static/docs/resources.html#preconstructed-indexes). -## πŸš€ Quick start +## Quick start ### 1. Install @@ -77,7 +80,7 @@ pip install --force-reinstall "git+https://github.com/ratschlab/metagraph.git#su Clone the repo for the bundled test data, then build: ```bash -# Only to fetch the bundled example *.fa files β€” skip this if you have your own data +# Only to fetch the bundled example *.fa files; skip this if you have your own data git clone https://github.com/ratschlab/metagraph.git && cd metagraph metagraph-workflows build <(ls metagraph/tests/data/*.fa) -o out/ --primary @@ -85,7 +88,7 @@ metagraph-workflows build <(ls metagraph/tests/data/*.fa) -o out/ --primary `--primary` indexes one strand per *k*-mer pair (about half the size; appropriate when read strand orientation is unknown, e.g. typical short-read sequencing). -Internally this chains `metagraph build β†’ annotate β†’ row-diff transform β†’ BRWT clustering β†’ BRWT relaxation` and produces `graph.dbg`, the more compact `graph_small.dbg` (smaller, slower at access β€” useful when RAM or storage is tight), and the default annotation `graph.relax.row_diff_brwt.annodbg` (`RowDiff`). See the [Quick start guide](https://metagraph.ethz.ch/static/docs/quick_start.html) in the docs for a step-by-step of index construction. +Internally this chains `metagraph build β†’ annotate β†’ row-diff transform β†’ BRWT clustering β†’ BRWT relaxation` and produces `graph.dbg`, the more compact `graph_small.dbg` (smaller and slower at access, useful when RAM or storage is tight), and the default annotation `graph.relax.row_diff_brwt.annodbg` (`RowDiff`). See the [Quick start guide](https://metagraph.ethz.ch/static/docs/quick_start.html) in the docs for a step-by-step of index construction.
Real-workload example with file list and hardware budget @@ -101,7 +104,7 @@ The positional argument accepts a file list (one path per line), a directory, or ### 3. Query -Query the index (any fasta works as input β€” the bundled test data is fine for a smoke test): +Query the index (any fasta works as input; the bundled test data is fine for a smoke test): ```bash metagraph query --query-mode matches -p 8 \ @@ -109,14 +112,20 @@ metagraph query --query-mode matches -p 8 \ metagraph/tests/data/transcripts_100.fa ``` -Other ways to use the index: [`metagraph align`](https://metagraph.ethz.ch/static/docs/sequence_search.html#sequence-to-graph-alignment) for sequence-to-graph alignment (acts as a read mapper when given a coordinate-aware annotator); `metagraph query --align` to find labels via alignment scoring instead of exact *k*-mer matching (useful for divergent or noisy queries); [`metagraph server_query`](https://metagraph.ethz.ch/static/docs/api.html) for Python/HTTP queries. The [Minimal example](#minimal-example) below walks through each step on a smaller dataset. +Other ways to use the index: + +- [`metagraph align`](https://metagraph.ethz.ch/static/docs/sequence_search.html#sequence-to-graph-alignment): align sequences to the graph and report the alignment itself. With a coordinate-aware annotator it acts as a read mapper, returning source positions. +- `metagraph query --align`: align each query to the graph first, then report the *labels* of the best-scoring alignment instead of requiring exact *k*-mer matches. Useful for divergent or noisy queries. (So `align` returns alignments/positions; `query --align` returns labels found via alignment.) +- [`metagraph server_query`](https://metagraph.ethz.ch/static/docs/api.html): start an HTTP server that answers queries over a REST API (also drivable from the Python client). + +The [Minimal example](#minimal-example) below walks through each step on a smaller dataset. ## Minimal example
Step-by-step walkthrough with the metagraph CLI (build β†’ annotate β†’ query β†’ stats) -A hands-on demo using `metagraph` directly (no workflow wrapper). A *label* is whatever tag you want each *k*-mer associated with β€” a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record. +A hands-on demo using `metagraph` directly (no workflow wrapper). A *label* is whatever tag you want each *k*-mer associated with: a filename, a fasta header, or a custom string. `--anno-header` below produces one label per fasta record. ```bash cd metagraph/tests/data @@ -146,11 +155,11 @@ Outputs `samples.dbg` (the de Bruijn graph) and `samples.column.annodbg` (3 labe 2 tk_sample :207 :207 ``` -Each query matches at least its own label. `zh_sample`'s 243 *k*-mers are fully contained in `kl_sample` (243/243), and same for `tk_sample` (207/207) β€” they share enough content to clear the 80% threshold. `kl_sample` matches only itself: its 330 *k*-mers aren't fully covered by either of the shorter samples. +Each query matches at least its own label. `zh_sample`'s 243 *k*-mers are fully contained in `kl_sample` (243/243), and the same holds for `tk_sample` (207/207); they share enough content to clear the 80% threshold. `kl_sample` matches only itself: its 330 *k*-mers aren't fully covered by either of the shorter samples.
-## πŸ”§ More recipes +## More recipes
Direct CLI: align / assemble / differential assembly / stats @@ -168,7 +177,7 @@ metagraph annotate -v -p 8 --anno-filename -i graph.dbg -o annotation data.fa.gz # Align sequences to the graph (plain sequence-to-graph alignment, no labels). metagraph align -v -i graph.dbg query.fa -# Labeled alignment: with -a, the walk is label-trace-consistent β€” every +# Labeled alignment: with -a, the walk is label-trace-consistent, i.e. every # k-mer of the reported path lies in every reported label. metagraph align -v -i graph.dbg -a annotation.row_diff_brwt.annodbg reads.fq @@ -185,14 +194,14 @@ metagraph query --align -i graph.dbg -a annotation.row_diff_brwt.annodbg query.f # Assemble unitigs from a graph (writes assembled.fasta.gz) metagraph assemble -v graph.dbg -o assembled --unitigs -# Differential assembly β€” JSON rules define which label groups must be present vs. absent. +# Differential assembly: JSON rules define which label groups must be present vs. absent. # Sample rule files: metagraph/tests/data/example.diff.json, example_simple.diff.json. metagraph assemble -v graph.dbg --unitigs \ -a annotation.column.annodbg \ --diff-assembly-rules diff_assembly_rules.json \ -o diff_assembled -# Stats β€” graph only, annotation only, or both +# Stats: graph only, annotation only, or both metagraph stats graph.dbg metagraph stats -a annotation.column.annodbg metagraph stats -a annotation.column.annodbg graph.dbg @@ -245,11 +254,11 @@ metagraph server_query -i graph.dbg -a annotation.row_diff_brwt.annodbg --port 5
-## πŸ“¦ More install options +## More install options -The recommended conda install is in [Quick start](#-quick-start). MetaGraph runs on Linux and macOS. Bioconda ships the `DNA` and `Protein` alphabets (`metagraph` is symlinked to `metagraph_DNA`); the Docker image adds `DNA5`. For other alphabets, build from source. +The recommended conda install is in [Quick start](#quick-start). MetaGraph runs on Linux and macOS. Bioconda ships the `DNA` and `Protein` alphabets (`metagraph` is symlinked to `metagraph_DNA`); the Docker image adds `DNA5`. For other alphabets, build from source. -### 🐳 Docker +### Docker ```bash docker pull ghcr.io/ratschlab/metagraph:master @@ -290,29 +299,18 @@ metagraph build -v -k 31 -o /mnt/graph /mnt/data.fa.gz See the [installation guide](https://metagraph.ethz.ch/static/docs/installation.html#install-from-source) for custom alphabet / debug builds. -## 🀝 Contributing +## Contributing
-Developer notes β€” docker build, Makefile, releases +Developer notes: docker build, releases **Build a docker container.** Run `docker build .` -**Makefile.** The top-level `Makefile` conveniently wraps the common build / test invocations. Supported arguments: - -- `env`: `""` (host) or `docker` -- `alphabet`: e.g. `DNA`, `DNA5`, `Protein` (default `DNA`) -- `cmake_args`: extra CMake flags forwarded to the build (overrides the default `-DBUILD_KMC=OFF`) - -```bash -# compile in a docker container for the DNA alphabet -make build-metagraph env=docker alphabet=DNA -``` - **Releases.** Three steps: 1) bump the version in `package.json`; 2) tag the commit with that version; 3) create a GitHub release.
-## πŸ“ Citation +## Citation If MetaGraph or its index resources are useful in your work, please cite: @@ -337,6 +335,6 @@ If MetaGraph or its index resources are useful in your work, please cite:
-## βš–οΈ License +## License MetaGraph is distributed under the GPLv3 License (see [LICENSE](LICENSE)). See also [AUTHORS](AUTHORS) and [COPYRIGHT](COPYRIGHT). diff --git a/metagraph/docs/source/images/metagraph_logo.png b/metagraph/docs/source/images/metagraph_logo.png deleted file mode 100644 index 73b04678356e33833c72c2b00e44d397fafc8b99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12734 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 diff --git a/metagraph/docs/source/images/metagraph_logo.svg b/metagraph/docs/source/images/metagraph_logo.svg new file mode 100644 index 0000000000..8dd4605e67 --- /dev/null +++ b/metagraph/docs/source/images/metagraph_logo.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/metagraph/docs/source/images/metagraph_logo_dark.svg b/metagraph/docs/source/images/metagraph_logo_dark.svg new file mode 100644 index 0000000000..5561ed9d9b --- /dev/null +++ b/metagraph/docs/source/images/metagraph_logo_dark.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d9a8074039b57d71387bda3cdc155bae0ad9064a Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Sun, 31 May 2026 00:49:21 +0200 Subject: [PATCH 20/23] Update AUTHORS and COPYRIGHT Add Oleksandr Kulkov, Marc Zimmermann, and Thomas Zhou to AUTHORS; bump the COPYRIGHT year to 2026 and add Oleksandr Kulkov to the copyright holders. Co-Authored-By: Claude Opus 4.8 (1M context) --- AUTHORS | 3 +++ COPYRIGHT | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 2fee14e35f..ece0e9df3a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,9 +5,12 @@ Metagraph was conceived and written by: Andre Kahles Further contributors/collaborators: + Oleksandr Kulkov Daniel Danciu + Marc Zimmermann Christopher Barber Contributing students: Sara Javadzadeh No Jan Studeny + Thomas Zhou diff --git a/COPYRIGHT b/COPYRIGHT index 1768c99bc3..a8b0d9dfdd 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,6 +1,6 @@ Metagraph is provided free of charge under the GPLv3 license: -Copyright (c) 2014-2019, Mikhail Karasikov, Harun Mustafa, Andre Kahles, Gunnar Raetsch +Copyright (c) 2014-2026, Mikhail Karasikov, Harun Mustafa, Oleksandr Kulkov, Andre Kahles, Gunnar Raetsch (for further author information, please refer to the AUTHORS file) All rights reserved. From aca5635f798ce6d52148e73740227ec0a9084af9 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Sun, 31 May 2026 00:53:51 +0200 Subject: [PATCH 21/23] README: add project title; soften dark-mode logo color Add an H1 title "MetaGraph: The Metagenome Graph Project" under the logo. Recolor the dark-mode logo variant from pure white to a soft light gray (#c9d1d9); pure white (~21:1 contrast) read as too aggressive on dark backgrounds. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 2 + .../source/images/metagraph_logo_dark.svg | 76 +++++++++---------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index b309d6b8c4..486bcf311e 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@

+

MetaGraph: The Metagenome Graph Project

+ [![Platform: Linux | macOS](https://img.shields.io/badge/platform-Linux%20%7C%20macOS-brightgreen)](#quick-start) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/ratschlab/metagraph)](https://github.com/ratschlab/metagraph/releases) [![Bioconda version](https://img.shields.io/conda/vn/bioconda/metagraph)](https://bioconda.github.io/recipes/metagraph/README.html) diff --git a/metagraph/docs/source/images/metagraph_logo_dark.svg b/metagraph/docs/source/images/metagraph_logo_dark.svg index 5561ed9d9b..295fbf6fad 100644 --- a/metagraph/docs/source/images/metagraph_logo_dark.svg +++ b/metagraph/docs/source/images/metagraph_logo_dark.svg @@ -38,74 +38,74 @@ - + - + - + - + - - - + + + - + - + - + - - + + - + - + - + - + - + - + - + - + - - - - + + + + - + - - - - - - - + + + + + + + - + - - - + + + - + - + From f21d54afe45dd46c8aa3cb565b5022ebb47ae4ae Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Sun, 31 May 2026 00:57:51 +0200 Subject: [PATCH 22/23] Fix header formatting in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 486bcf311e..556ada50b7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

-

MetaGraph: The Metagenome Graph Project

+# MetaGraph: Metagenome Graph Project [![Platform: Linux | macOS](https://img.shields.io/badge/platform-Linux%20%7C%20macOS-brightgreen)](#quick-start) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/ratschlab/metagraph)](https://github.com/ratschlab/metagraph/releases) From 07e6f54382670c2b264aaa02c2ad9d15c98d7966 Mon Sep 17 00:00:00 2001 From: Mikhail Karasikov Date: Fri, 12 Jun 2026 22:56:48 +0200 Subject: [PATCH 23/23] README: fix labels-delimiter note, drop --force-reinstall from pip step - Correct the "Common query flags" comment: --labels-delimiter applies to --query-mode labels (default mode is matches), and show the flag with it. - Drop --force-reinstall from the workflows pip install: the env is freshly created just above, so it only risks clobbering conda-managed deps. Co-Authored-By: Claude Opus 4.8 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 556ada50b7..f0d4e9aabd 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Prefer local analysis? The prebuilt indexes are also published on [AWS Open Data conda create -n metagraph python conda activate metagraph conda install -c bioconda -c conda-forge metagraph -pip install --force-reinstall "git+https://github.com/ratschlab/metagraph.git#subdirectory=metagraph/workflows" +pip install "git+https://github.com/ratschlab/metagraph.git#subdirectory=metagraph/workflows" ``` `metagraph` is the compiled binary; `metagraph-workflows` is the Python wrapper that drives the full build pipeline. @@ -238,8 +238,8 @@ Add `--count-kmers` to keep the KMC abundance counts as a weight vector alongsid # Filter labels with low k-mer coverage (default 0.7) metagraph query --min-kmers-fraction-label 0.8 ... -# Change the separator joining labels in the default `labels` mode (default ":") -metagraph query --labels-delimiter ", " ... +# Delimiter joining labels in `--query-mode labels` output (default ":") +metagraph query --query-mode labels --labels-delimiter ", " ... # Per-k-mer presence/absence bitmask per matching label metagraph query --query-mode signature ...