diff --git a/docs/about/citation.html b/docs/about/citation.html
index 6f0aa83..899cb2c 100644
--- a/docs/about/citation.html
+++ b/docs/about/citation.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,56 +42,62 @@
-
+
PathoGenOmics LabGPL-3.0AMAP 1.0.1
diff --git a/docs/about/contributing.html b/docs/about/contributing.html
index 7bbf99e..a230775 100644
--- a/docs/about/contributing.html
+++ b/docs/about/contributing.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,110 +42,116 @@
-
+
PathoGenOmics LabGPL-3.0AMAP 1.0.1
diff --git a/docs/assets/search-index.js b/docs/assets/search-index.js
index a44967e..c569a52 100644
--- a/docs/assets/search-index.js
+++ b/docs/assets/search-index.js
@@ -1,1239 +1,2 @@
/* Generated by tools/build_docs.py: the documentation, section by section, for the search box. */
-window.AMAP_DOCS_INDEX = [
-{
-"page": "Tutorial",
-"section": "",
-"url": "getting-started/tutorial.html",
-"text": "Every command spelled out, nothing assumed. It uses a real genome you can download in one line, so you can follow along and compare your numbers against the ones printed here. Those numbers come from actual runs of these commands, not from memory, made on 18 October 2026 with AMAP at commit 947a99d. The runs skipped eggNOG, with --skip_functional_annotation. None of the numbers depends on it, and the one table shown here that is written after it was made by hand from the same run, as the sample check explains. The genome is Mycobacterium tuberculosis CDC1551, corrected against the H37Rv refere"
-},
-{
-"page": "Tutorial",
-"section": "1. What you need",
-"url": "getting-started/tutorial.html#1-what-you-need",
-"text": "Nextflow and a container engine. Every tool the pipeline itself calls lives inside the AMAP image, so there is nothing else to install. The easiest way to get Nextflow is Conda. If you do not have it, Miniforge is the one to install: conda create -n nextflow-env -c conda-forge -c bioconda nextflow conda activate nextflow-env Nextflow needs Java 17 or newer. Check what you have: java -version nextflow -version Java 8 is not enough Nextflow will refuse to start on Java 8 with a message about an unsupported class file version. Install a newer JDK, for example conda install -c conda-forge openjdk="
-},
-{
-"page": "Tutorial",
-"section": "The machine",
-"url": "getting-started/tutorial.html#the-machine",
-"text": "Worth checking before you start, because this is what stops a run rather than anything you will have got wrong. Disk goes first. The container is 12 GB unpacked. The eggNOG database is built on first use and cached: AMAP skips the 60 GB Diamond core that eggNOG-mapper would otherwise fetch and builds a bacteria-only index of about 3 GB instead, on top of the metadata it downloads. Nextflow keeps every intermediate under work/, which for one genome is a few GB and grows with the sample sheet. Memory decides whether the run finishes at all. Two steps are heavy: eggNOG annotation 24 50 GB eggNOG "
-},
-{
-"page": "Tutorial",
-"section": "2. Check the plumbing before touching real data",
-"url": "getting-started/tutorial.html#2-check-the-plumbing-before-touching-real-data",
-"text": "The test profile annotates the reference genome that ships inside the container, so it needs no data of your own: nextflow run PathoGenOmics-Lab/AMAP -profile docker,test The first run pulls the AMAP image. It is around 3.4 GB compressed and 12 GB on disk, because it carries the Bakta database and the H37Rv reference files. That is a one-off; later runs reuse it. If this finishes without an error, the whole chain works."
-},
-{
-"page": "Tutorial",
-"section": "3. Get a genome",
-"url": "getting-started/tutorial.html#3-get-a-genome",
-"text": "Make a working directory and fetch an assembly: mkdir amap-tutorial && cd amap-tutorial NCBI=https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF ASM=000/008/585/GCF_000008585.1_ASM858v1/GCF_000008585.1_ASM858v1_genomic curl -sL -o cdc1551.fna.gz \"$NCBI/$ASM.fna.gz\" gunzip cdc1551.fna.gz Have a look at what you got: grep '^>' cdc1551.fna >NC_002755.2 Mycobacterium tuberculosis CDC1551, complete sequence One closed contig, 4,403,837 bp. AMAP takes assembled genomes, so reads are not an input here."
-},
-{
-"page": "Tutorial",
-"section": "4. Write the sample sheet",
-"url": "getting-started/tutorial.html#4-write-the-sample-sheet",
-"text": "A tab-separated file with exactly two columns: printf 'sampleId\\tfasta\\nCDC1551\\t%s/cdc1551.fna\\n' \"$PWD\" > samples.tsv cat samples.tsv sampleId\tfasta CDC1551\t/path/to/amap-tutorial/cdc1551.fna It has to be a real tab Spaces will not do, and this is the single most common reason a sample sheet is rejected. Check with cat -t samples.tsv: real tabs show up as ^I. (cat -A does the same on Linux but is not accepted by the cat that ships with macOS.) Keep sample IDs to letters, digits and underscores. Several scripts cut the name at the first dot, so G26689.1 becomes G26689."
-},
-{
-"page": "Tutorial",
-"section": "5. Validate before committing to a long run",
-"url": "getting-started/tutorial.html#5-validate-before-committing-to-a-long-run",
-"text": "nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv --validate_only The checks write their verdict to results/pipeline_info/validation.log, which ends: tail -n 8 results/pipeline_info/validation.log --- FASTA Validation (1 samples) --- ============================================================ Results: 1 OK, 0 warnings, 0 failed Total: 0 error(s), 0 warning(s) ============================================================ ✓ PASSED — all 1 samples look good This costs seconds and catches the mistakes that would otherwise surface minutes in. Preparing inputs lists eve"
-},
-{
-"page": "Tutorial",
-"section": "6. Run it",
-"url": "getting-started/tutorial.html#6-run-it",
-"text": "nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv What happens, in order: lineage typing, Bakta annotation, alignment of the H37Rv proteome onto your genome, case classification, correction, eggNOG functional annotation, duplication analysis, pangenome, report. The first run also downloads and builds the eggNOG database, which is several gigabytes and dominates the wall clock. Point --eggnog_db at somewhere persistent so the next run reuses it: nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv --eggnog_db ~/databases/eggnog If the run dies partway,"
-},
-{
-"page": "Tutorial",
-"section": "7. Look at what came out",
-"url": "getting-started/tutorial.html#7-look-at-what-came-out",
-"text": "ls -F results/ AMAP/ BAKTA/ CDS_CASES/ DUPLICATIONS/ PANGENE/ PATHOTYPR/ amap_report.html final_report.html pipeline_info/ A run without eggNOG, like the ones behind this page, has no DUPLICATIONS/, PANGENE/ or final_report.html. Open results/final_report.html first. It is self-contained, so it travels by email. results/amap_report.html goes through the same run question by question, and a run without eggNOG still gets it."
-},
-{
-"page": "Tutorial",
-"section": "Did the correction do anything?",
-"url": "getting-started/tutorial.html#did-the-correction-do-anything",
-"text": "column -t -s$'\\t' results/CDS_CASES/summary/*-summary.tsv Type Number case2 2813 coordinates matched the reference exactly case3 24 coordinates matched, alignment length differed case4 1100 overlapped the reference but disagreed, resolved case1_initial 141 Bakta called it, the reference has nothing t Read that as: of 4077 Bakta calls, 2813 already agreed with the reference, 1100 had to be adjudicated, and 97 reference genes Bakta had missed were rescued. Only 11 of the 141 apparently novel genes survived scrutiny, which is what you would"
-},
-{
-"page": "Tutorial",
-"section": "Sanity check the sample",
-"url": "getting-started/tutorial.html#sanity-check-the-sample",
-"text": "column -t -s$'\\t' results/AMAP//-AMAP.qc.tsv Six of its eleven columns: genome_size n_contigs gc_pct total_cds pseudogene_count coding_density_pct 4403837 1 65.61 4014 123 91.3 65.6% GC and 91.3% coding density are what a healthy MTBC genome looks like. A number well outside that is worth investigating before trusting the rest. This table is written after eggNOG, so a run without it has none. The line above was made by running that step's own script, GET_OUTPUTS, on the corrected genes of the run behind this page, with an eggNOG table that has no rows. "
-},
-{
-"page": "Tutorial",
-"section": "The files you will actually use",
-"url": "getting-started/tutorial.html#the-files-you-will-actually-use",
-"text": "Lineage typing puts the lineage in front of the sample ID, so CDC1551 comes out as L4_CDC1551: Corrected annotation results/AMAP/L4_CDC1551/L4_CDC1551-AMAP.gff3 Same, GenBank results/AMAP/L4_CDC1551/L4_CDC1551-AMAP.gbk Proteins for downstream tools results/AMAP/L4_CDC1551/L4_CDC1551-AMAP.clean.faa Everything known per gene results/AMAP/L4_CDC1551/L4_CDC1551-AMAPmastertable.tsv Tool versions for your methods results/pipeline_info/software_versions.yml Two protein files, and they differ on purpose *-AMAP.faa has one entry per annotated gene, pseudogenes included, so it matches the GFF3 gene for "
-},
-{
-"page": "Tutorial",
-"section": "8. More than one genome",
-"url": "getting-started/tutorial.html#8-more-than-one-genome",
-"text": "Add rows. The pipeline parallelises across samples, and the pangenome and the cross-genome duplication analysis only become meaningful with several: sampleId\tfasta CDC1551\t/data/cdc1551.fna Erdman\t/data/erdman.fna Beijing_1\t/data/beijing_1.fna Past a couple of dozen, move to a cluster: nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm \\ --tsv samples.tsv --eggnog_db /shared/databases/eggnog Deployment on HPC covers scratch, shared filesystems and where things should live."
-},
-{
-"page": "Tutorial",
-"section": "9. The same thing for another organism",
-"url": "getting-started/tutorial.html#9-the-same-thing-for-another-organism",
-"text": "Everything above assumed MTBC. For anything else you supply the trusted reference yourself, and turn off the two MTBC-specific steps. Fetch a reference and a sample. Here, E. coli O157:H7 Sakai corrected against K-12 MG1655: NCBI=https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF K12=000/005/845/GCF_000005845.2_ASM584v2/GCF_000005845.2_ASM584v2_genomic SAKAI=000/008/865/GCF_000008865.2_ASM886v2/GCF_000008865.2_ASM886v2_genomic # the reference: annotation plus the genome it describes curl -sL -o ref.gbk.gz \"$NCBI/$K12.gbff. nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv ecoli.tsv "
-},
-{
-"page": "Tutorial",
-"section": "When something goes wrong",
-"url": "getting-started/tutorial.html#when-something-goes-wrong",
-"text": "Nextflow prints the work directory of the failed task. Everything you need is in it: work/ab/cd1234.../ ├── .command.sh the exact script that ran ├── .command.err stderr on its own, almost always the answer └── .exitcode Troubleshooting is organised by the error you are looking at."
-},
-{
-"page": "Tutorial",
-"section": "Where to go next",
-"url": "getting-started/tutorial.html#where-to-go-next",
-"text": "Parameters: every flag and its default How it works: what the correction actually does, and the thresholds behind it Outputs: every file the run produces"
-},
-{
-"page": "Installation",
-"section": "",
-"url": "getting-started/installation.html",
-"text": "AMAP needs two things on your machine: Nextflow, which runs the pipeline, and a container engine, which supplies every tool the pipeline calls. You do not install Bakta, Miniprot, eggNOG-mapper or any of the rest yourself; they all live inside the AMAP image. You also do not need to clone this repository. Nextflow pulls the pipeline straight from GitHub."
-},
-{
-"page": "Installation",
-"section": "1. Nextflow",
-"url": "getting-started/installation.html#1-nextflow",
-"text": "Nextflow needs Java 17 or newer, and Conda brings both: conda create -n nextflow-env conda activate nextflow-env conda install -c bioconda nextflow If you do not have Conda, Miniconda is a lightweight installer. Any other Nextflow installation works equally well, see the Nextflow documentation. AMAP requires Nextflow 24.04 or newer, which is enforced by the pipeline manifest."
-},
-{
-"page": "Installation",
-"section": "2. A container engine",
-"url": "getting-started/installation.html#2-a-container-engine",
-"text": "Pick whichever your machine allows. Docker Best on a laptop or workstation where you have root. Follow the Docker installation guide for your platform. Singularity / Apptainer The usual choice on an HPC cluster, where Docker is normally not permitted. Follow the Apptainer installation guide. Many clusters already provide it as a module: module load singularity # or: module load apptainer"
-},
-{
-"page": "Installation",
-"section": "3. Check the installation",
-"url": "getting-started/installation.html#3-check-the-installation",
-"text": "nextflow -version docker --version # or: singularity --version"
-},
-{
-"page": "Installation",
-"section": "4. Run the test profile",
-"url": "getting-started/installation.html#4-run-the-test-profile",
-"text": "Before pointing AMAP at real data, confirm the whole chain works. The test profile annotates the reference genome bundled in the container, so it needs no data of your own and nothing to download beyond the image: nextflow run PathoGenOmics-Lab/AMAP -profile docker,test The first run downloads the AMAP image, which is several gigabytes because it bundles the Bakta database and the H37Rv reference files. Expect that to take a while. Later runs reuse the cached image. Check inputs before a long run Once you have your own samples, --validate_only checks every FASTA and database path without execu"
-},
-{
-"page": "Installation",
-"section": "Running without a container",
-"url": "getting-started/installation.html#running-without-a-container",
-"text": "Possible but not recommended: you take on installing Bakta, Miniprot, eggNOG-mapper, Diamond, pangene, pathotypr, R and the report packages yourself, and pointing AMAP at each reference file with the reference arguments. The repository ships an environment.yml that reproduces the software stack under Conda: conda env create -f environment.yml conda activate amap You will still need to supply the Bakta database and the reference files, which the container would otherwise provide. See containers for what the image contains."
-},
-{
-"page": "Installation",
-"section": "Next steps",
-"url": "getting-started/installation.html#next-steps",
-"text": "Quickstart: your first real run Preparing inputs: the sample sheet and what makes a genome pass validation"
-},
-{
-"page": "Quickstart",
-"section": "",
-"url": "getting-started/quickstart.html",
-"text": "This walks from an installed Nextflow to an open report. If Nextflow or a container engine is still missing, start at installation."
-},
-{
-"page": "Quickstart",
-"section": "1. Write a sample sheet",
-"url": "getting-started/quickstart.html#1-write-a-sample-sheet",
-"text": "AMAP reads a tab-separated file with two columns, sampleId and fasta: sampleId\tfasta G26689\t/data/genomes/G26689.fasta G26690\t/data/genomes/G26690.fasta The separator must be a real tab. Paths can be absolute, or relative to where you launch Nextflow. See preparing inputs for the details and the checks AMAP runs over them."
-},
-{
-"page": "Quickstart",
-"section": "2. Check the inputs",
-"url": "getting-started/quickstart.html#2-check-the-inputs",
-"text": "Cheap, and it catches the mistakes that would otherwise surface twenty minutes into a run: nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv --validate_only"
-},
-{
-"page": "Quickstart",
-"section": "3. Run it",
-"url": "getting-started/quickstart.html#3-run-it",
-"text": "Pick what you have, and copy the command: Or start from one of these: nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv Docker nextflow run PathoGenOmics-Lab/AMAP -profile singularity --tsv samples.tsv Singularity nextflow run PathoGenOmics-Lab/AMAP \\ -profile singularity,slurm --tsv samples.tsv SLURM cluster Without a scheduler profile every task is capped at the machine's CPUs and 16 GB, and with the scheduler profiles at 48 CPUs and 128 GB, whichever order the profiles come in; a larger request, such as the 50 GB the eggNOG annotation asks for, is lowered to the cap."
-},
-{
-"page": "Quickstart",
-"section": "4. Read the results",
-"url": "getting-started/quickstart.html#4-read-the-results",
-"text": "Everything lands under results/ unless you set --outdir. The three things to open first: The full interactive report results/final_report.html Corrected annotation, per sample results/AMAP/{sampleId}/*-AMAP.gff3 Protein sequences for downstream tools results/AMAP/{sampleId}/*-AMAP.clean.faa Which protein file do I want? *-AMAP.faa holds every gene, pseudogenes included, so it mirrors the annotation exactly. *-AMAP.clean.faa drops pseudogenes and sequences with internal stop codons, which is what orthology, phylogenomics and dN/dS tools expect. Reach for the clean one unless you specifically ne"
-},
-{
-"page": "Quickstart",
-"section": "5. Resume an interrupted run",
-"url": "getting-started/quickstart.html#5-resume-an-interrupted-run",
-"text": "Nextflow caches completed tasks. If a run dies, restart it with -resume and it picks up where it stopped: nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv -resume Keep the same parameters. Changing an input invalidates the cache for every task downstream of it, which is what you want, but it does mean the run redoes that work."
-},
-{
-"page": "Quickstart",
-"section": "Common variations",
-"url": "getting-started/quickstart.html#common-variations",
-"text": "A non-MTBC organism. Lineage typing and Mycobrowser are specific to tuberculosis, so turn them off and supply your own reference: nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv \\ --skip_typing --skip_mycobrowser \\ --genus Escherichia --species \"Escherichia coli\" \\ --ref_annotation my_reference.gbk --ref_genome my_reference.fasta See custom reference. Just the corrected genes. If you do not need functional annotation, duplications, the pangenome or the report, stop after the correction step. It is much faster, and leaves the corrected genes as a table wi"
-},
-{
-"page": "Quickstart",
-"section": "Next steps",
-"url": "getting-started/quickstart.html#next-steps",
-"text": "Running AMAP: profiles, schedulers and larger runs Parameters: the full reference How it works: what the correction actually does"
-},
-{
-"page": "Preparing inputs",
-"section": "",
-"url": "getting-started/inputs.html",
-"text": "AMAP takes assembled genomes. It does not do assembly, so reads are not an input here."
-},
-{
-"page": "Preparing inputs",
-"section": "The sample sheet",
-"url": "getting-started/inputs.html#the-sample-sheet",
-"text": "A tab-separated file with a header row and exactly two columns: sampleId\tfasta G26689\t/data/genomes/G26689.fasta G26690\t/data/genomes/G26690.fasta L4_G12345\t./assemblies/L4_G12345.fa sampleId: The name carried through every output directory and file. Must be unique. Lineage typing puts the lineage in front, so G26689 comes out as L4_G26689. fasta: Path to the assembled genome. Absolute, or relative to the directory you launch Nextflow from. The separator has to be an actual tab. Spaces will not do, and this is the most common reason a sample sheet is rejected. Avoid dots in sample IDs Several "
-},
-{
-"page": "Preparing inputs",
-"section": "Pre-flight validation",
-"url": "getting-started/inputs.html#pre-flight-validation",
-"text": "VALIDATE_INPUTS runs before any computation, on every run. To run only the checks and stop: nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv --validate_only Results are written to results/pipeline_info/validation.log."
-},
-{
-"page": "Preparing inputs",
-"section": "What fails the run",
-"url": "getting-started/inputs.html#what-fails-the-run",
-"text": "Genome size below 100 kb, or above 20 Mb N content above 10% GC content below 20% or above 80% Empty sequences any record with no bases Duplicate contig headers any repeated identifier Sample sheet missing sampleId or fasta column, no data rows, duplicate or empty sample IDs FASTA path file does not exist or cannot be parsed Bakta database not found, or no version.json or bakta.db in it eggNOG database eggnog_db.ok in it while the database is incomplete, since Nextflow then never builds it again Reference files --ref_annotation, --ref_genome or a reference argument that the run reads but that "
-},
-{
-"page": "Preparing inputs",
-"section": "What only warns",
-"url": "getting-started/inputs.html#what-only-warns",
-"text": "Genome size below 500 kb, or above 10 Mb N content above 1% GC content below 25% or above 75% Assembly fragmentation more than 100 contigs, and again above 500 Short contigs any contig under 200 bp in a multi-contig assembly Non-standard characters anything outside ATGCNRYSWKMBDHV eggNOG database a bacteria.dmnd under 100 MB, which EGGNOG_DB takes for a truncated download and builds again Mycobrowser table --mycobrowser_ref neither on the host nor inside the container, so the master table goes without Mycobrowser categories Warnings do not stop a run. Add --strict_validation to have them stop "
-},
-{
-"page": "Preparing inputs",
-"section": "Assembly quality",
-"url": "getting-started/inputs.html#assembly-quality",
-"text": "The correction compares your annotation against a reference proteome, so assembly quality propagates directly into the result. Contiguity matters. A gene broken across two contigs cannot be recovered. The more fragmented the assembly, the more reference genes turn up as missing rather than corrected. Ns break coding sequences. A run of Ns inside a gene stops it translating, so it is dropped or flagged as a pseudogene. The 1% warning is worth taking seriously. Contig names are kept AMAP runs Bakta with --keep-contig-headers, so the sequence names you supplied are the ones in BAKTA/ and in the c"
-},
-{
-"page": "Preparing inputs",
-"section": "Reference data",
-"url": "getting-started/inputs.html#reference-data",
-"text": "For MTBC you need nothing else. The container bundles the H37Rv proteome, the Bakta light database, the Mycobrowser table and the pathotypr lineage markers. See containers for the full list. For any other organism, supply your own reference with --ref_annotation and --ref_genome. See custom reference."
-},
-{
-"page": "Preparing inputs",
-"section": "Next steps",
-"url": "getting-started/inputs.html#next-steps",
-"text": "Quickstart: run it Parameters: the full flag reference"
-},
-{
-"page": "Parameters",
-"section": "",
-"url": "user-guide/parameters.html",
-"text": "Every AMAP parameter, what it defaults to, and when you would change it. Run nextflow run PathoGenOmics-Lab/AMAP --help for the same list at the terminal. Pipeline parameters take two dashes (--tsv). Nextflow's own options take one (-profile, -resume)."
-},
-{
-"page": "Parameters",
-"section": "Input and output",
-"url": "user-guide/parameters.html#input-and-output",
-"text": "--tsv Sample sheet with sampleId and fasta columns, tab separated. Not needed with -profile test. file samples.tsv Yes --outdir Directory for results. string results No"
-},
-{
-"page": "Parameters",
-"section": "Skip options",
-"url": "user-guide/parameters.html#skip-options",
-"text": "--skip_typing Skip pathotypr lineage typing. Required for non-MTBC organisms. boolean false --skip_functional_annotation Stop after annotation correction. Skips eggNOG, duplications, pangenome and the report. boolean false --skip_mycobrowser Skip Mycobrowser functional categories. Auto-enabled when --genus is not Mycobacterium and a custom reference is set. boolean false Their effect on the output tree is covered under outputs."
-},
-{
-"page": "Parameters",
-"section": "Annotation",
-"url": "user-guide/parameters.html#annotation",
-"text": "--genus Genus passed to Bakta. string \"Mycobacterium\" --species Species passed to Bakta. string \"Mycobacterium tuberculosis\" Bakta uses these to pick naming conventions and reference sets, so setting them correctly improves the baseline annotation that AMAP then corrects."
-},
-{
-"page": "Parameters",
-"section": "Resources",
-"url": "user-guide/parameters.html#resources",
-"text": "--max_cpus Ceiling on CPUs for any single process. integer the machine's CPUs --max_memory Ceiling on memory for any single process. string 16.GB --max_time Ceiling on wall-clock time for any single process. string 72.h --scratch Use node-local scratch for I/O-heavy processes. On by default in the HPC profiles. boolean false These are ceilings, not requests. Each process asks for what it needs and is then clamped to these values, so lowering them makes AMAP fit a smaller machine rather than making it run faster. The defaults are a single machine's. The HPC profiles raise them to 48 CPUs and 12"
-},
-{
-"page": "Parameters",
-"section": "Validation and information",
-"url": "user-guide/parameters.html#validation-and-information",
-"text": "--validate_only Run input validation and stop. boolean false --strict_validation Stop on validation warnings too, not only on errors. boolean false --help Print the help text and exit. boolean false --version Print pipeline and Nextflow versions and exit. boolean false"
-},
-{
-"page": "Parameters",
-"section": "Custom reference",
-"url": "user-guide/parameters.html#custom-reference",
-"text": "Supply both to annotate any prokaryote against your own trusted annotation. --ref_annotation Trusted reference annotation, GFF3 or GenBank. file null --ref_genome Trusted reference genome, FASTA. file null They must be given together; setting only one is an error. See custom reference for what AMAP does with them."
-},
-{
-"page": "Parameters",
-"section": "Reference and database arguments",
-"url": "user-guide/parameters.html#reference-and-database-arguments",
-"text": "You almost certainly do not need these Every file below ships inside the Docker and Singularity images, under /reference/. Running with -profile docker or -profile singularity picks them up automatically. They exist for running without a container, for swapping in updated reference files, or for adapting the pipeline by hand. --bakta_db Bakta database directory, inside the container or on the host. /data/bakta_db/db-light --eggnog_db Pre-formatted eggNOG database directory. eggnog_db --pathotyper_snps Lineage marker SNPs for pathotypr. /reference/snps_lineages.tsv --pathotyper_ref Reference FA"
-},
-{
-"page": "Parameters",
-"section": "Nextflow options worth knowing",
-"url": "user-guide/parameters.html#nextflow-options-worth-knowing",
-"text": "These belong to Nextflow rather than AMAP, so they take a single dash. -profile Selects execution profiles, comma separated: docker, singularity, slurm, sge, pbs, test. -resume Reuse cached results from the previous run. -work-dir Where intermediate files go. Defaults to ./work, and it gets large. -process.queue Override the scheduler queue or partition. -process.clusterOptions Extra flags passed to the scheduler, such as '--account=mygroup'. See running AMAP for how the profiles combine."
-},
-{
-"page": "Running AMAP",
-"section": "",
-"url": "user-guide/running.html",
-"text": "Nextflow runs the pipeline straight from GitHub, so there is nothing to clone and nothing to keep up to date by hand. nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv"
-},
-{
-"page": "Running AMAP",
-"section": "Profiles",
-"url": "user-guide/running.html#profiles",
-"text": "A profile is a bundle of settings. You normally combine one container profile with one executor profile. docker container Runs every process in the AMAP Docker image. singularity container Same image via Singularity or Apptainer. standard container No container. Every tool must already be on PATH. slurm executor Submits each process as a SLURM job. sge executor Submits to Sun Grid Engine or Univa Grid Engine. pbs executor Submits to PBS or Torque. lsf executor Submits to IBM Spectrum LSF. test data Small bundled dataset, for checking the installation. highmem resources Raises the ceilings to 4"
-},
-{
-"page": "Running AMAP",
-"section": "On a laptop or workstation",
-"url": "user-guide/running.html#on-a-laptop-or-workstation",
-"text": "nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv CPUs are auto-detected and memory is capped at 16 GB. Raise both if the machine allows: nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv --max_cpus 8 --max_memory 32.GB A handful of genomes is fine locally. Beyond about twenty, a cluster is worth the setup."
-},
-{
-"page": "Running AMAP",
-"section": "On an HPC cluster",
-"url": "user-guide/running.html#on-an-hpc-cluster",
-"text": "nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv Each process becomes its own scheduler job, with up to 200 running at once, so Bakta, Miniprot and eggNOG parallelise across all your samples. Defaults reach 48 CPUs and 128 GB per job, and every process carries a time limit so nothing sits in the queue forever. To match your site's queues and accounting: nextflow run PathoGenOmics-Lab/AMAP \\ -profile singularity,slurm \\ --tsv samples.tsv \\ --max_cpus 64 --max_memory 256.GB \\ -process.queue long \\ -process.clusterOptions '--account=mygroup' Dep"
-},
-{
-"page": "Running AMAP",
-"section": "Non-MTBC organisms",
-"url": "user-guide/running.html#non-mtbc-organisms",
-"text": "Lineage typing and Mycobrowser categories are specific to tuberculosis. Turn them off and bring your own reference: nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv \\ --skip_typing --skip_mycobrowser \\ --genus Escherichia --species \"Escherichia coli\" \\ --ref_annotation my_reference.gbk --ref_genome my_reference.fasta See custom reference."
-},
-{
-"page": "Running AMAP",
-"section": "Resuming",
-"url": "user-guide/running.html#resuming",
-"text": "Nextflow caches every completed task, so an interrupted run picks up where it stopped: nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm \\ --tsv samples.tsv -resume Keep the parameters identical. Changing an input invalidates that task and everything downstream, which is correct behaviour but does mean the work is redone. The cache lives in the work directory, so do not delete it between a run and its resume."
-},
-{
-"page": "Running AMAP",
-"section": "Running only part of the pipeline",
-"url": "user-guide/running.html#running-only-part-of-the-pipeline",
-"text": "--skip_functional_annotation stops after the correction step. You get the corrected genes as a table with their proteins and sequences (*.AMAP.tsv, .faa, .fna) and the case tables, and skip eggNOG, duplications, the pangenome and the report. The GFF3 and GenBank files are written after eggNOG, so they are skipped too. Much faster when the corrected genes are all you are after: nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv --skip_functional_annotation"
-},
-{
-"page": "Running AMAP",
-"section": "What a run produces",
-"url": "user-guide/running.html#what-a-run-produces",
-"text": "Beyond the results themselves, results/pipeline_info/ holds the execution report, the timeline, the DAG, a trace with per-process resource usage, software_versions.yml with the exact version of every tool, and params.yml with the parameters used. Those last two are what you want when writing up methods."
-},
-{
-"page": "Running AMAP",
-"section": "Next steps",
-"url": "user-guide/running.html#next-steps",
-"text": "Outputs: what every file is Troubleshooting: when a run fails"
-},
-{
-"page": "Outputs",
-"section": "",
-"url": "user-guide/outputs.html",
-"text": "Everything lands under --outdir, results/ by default, in one subdirectory per pipeline stage. {sampleId} below is the sample's name as lineage typing leaves it, L4_G26689 for sample G26689, or the sample ID alone under --skip_typing."
-},
-{
-"page": "Outputs",
-"section": "Where to look first",
-"url": "user-guide/outputs.html#where-to-look-first",
-"text": "The whole analysis, interactive results/final_report.html Each genome and each gene, question by question results/amap_report.html Corrected annotation for a sample results/AMAP/{sampleId}/*-AMAP.gff3 Proteins for downstream tools results/AMAP/{sampleId}/*-AMAP.clean.faa Everything known about every gene results/AMAP/{sampleId}/*-AMAPmastertable.tsv Tool versions for your methods section results/pipeline_info/software_versions.yml"
-},
-{
-"page": "Outputs",
-"section": "The full tree",
-"url": "user-guide/outputs.html#the-full-tree",
-"text": "results/ ├── AMAP/ │ └── {sampleId}/ │ ├── *-AMAP.faa # Corrected proteins, all genes including pseudogenes │ ├── *-AMAP.clean.faa # No pseudogenes, no internal stops │ ├── *-AMAP.fna # Corrected nucleotide sequences │ ├── *-AMAP"
-},
-{
-"page": "Outputs",
-"section": "The key files",
-"url": "user-guide/outputs.html#the-key-files",
-"text": ""
-},
-{
-"page": "Outputs",
-"section": "*-AMAP.gff3 and *-AMAP.gbk",
-"url": "user-guide/outputs.html#-amapgff3-and--amapgbk",
-"text": "The corrected annotation, which is the point of the pipeline. The GFF3 is spec-compliant: gene features parent their CDS features, attributes are URL-escaped, ##sequence-region pragmas are emitted per contig, and eggNOG annotation rides along as Ontology_term, eC_number, COG_category, KEGG_ko, KEGG_Pathway and PFAMs. The sequences are appended after ##FASTA. Two kinds of gene carry marks, written the way NCBI writes them: Pseudogenes have pseudo=true in the GFF3 and /pseudo in the GenBank, with the reason in Note and /note: internal stop codons, a frameshift or lost stop that makes the frame r"
-},
-{
-"page": "Outputs",
-"section": "*-AMAP.faa versus *-AMAP.clean.faa",
-"url": "user-guide/outputs.html#-amapfaa-versus--amapcleanfaa",
-"text": "*-AMAP.faa mirrors the annotation exactly, pseudogenes included, so gene counts match the GFF3. *-AMAP.clean.faa drops the genes AMAP calls pseudogenes, those with pseudo=true in the GFF3, and any sequence with an internal stop codon, and strips the terminal stop. This is the file for orthology, phylogenomics, pangenome tools and dN/dS, all of which expect valid translations. It has fewer sequences than the GFF3 has genes, by design."
-},
-{
-"page": "Outputs",
-"section": "*-AMAPmastertable.tsv",
-"url": "user-guide/outputs.html#-amapmastertabletsv",
-"text": "One row per gene, joining the corrected coordinates, the Bakta annotation, the eggNOG assignment (COG category, description, preferred name, GO, EC, KEGG, PFAMs, CAZy) and, for MTBC, the Mycobrowser functional category. This is the table to load when you want to ask a question about the annotation."
-},
-{
-"page": "Outputs",
-"section": "*-AMAP.qc.tsv",
-"url": "user-guide/outputs.html#-amapqctsv",
-"text": "One row per sample: genome size, contig count, N50, GC%, total CDS, how many of them AMAP calls pseudogenes (those with pseudo=true in the GFF3), hypothetical protein percentage, mean and median gene length, coding density, and the eggNOG annotation rate. Useful for spotting the sample that went wrong."
-},
-{
-"page": "Outputs",
-"section": "CDS_CASES/",
-"url": "user-guide/outputs.html#cds_cases",
-"text": "The audit trail for the correction. Each case table lists the genes that fell into it, so you can see exactly what was changed and why. The correction algorithm explains the cases; *-summary.tsv aggregates the counts across samples and feeds the report. Its last three columns say which sample the counts are for: Sequence is the name its files carry (L4_G26689), Lineage what typing called it (Untyped under --skip_typing) and Sample its ID in the sample sheet (G26689)."
-},
-{
-"page": "Outputs",
-"section": "What a healthy summary looks like",
-"url": "user-guide/outputs.html#what-a-healthy-summary-looks-like",
-"text": "Two real runs, those of the tutorial, to calibrate expectations. On the left, M. tuberculosis CDC1551 against the bundled H37Rv reference. On the right, E. coli O157:H7 Sakai against K-12 MG1655, using a custom reference. case2, exact match 2813 3324 case3, length differs 24 31 case4, resolved conflict 1100 507 case1_kept, genuinely novel 11 1387 rv_missing, rescued 97 133 total_cds before correction 4077 5367 The one number that differs by two orders of magnitude is case 1, and that is biology: CDC1551 is the same species as the reference so almost nothing is genuinely new, while O157:H7 carr"
-},
-{
-"page": "Outputs",
-"section": "PANGENE/pangenome.Rtab",
-"url": "user-guide/outputs.html#pangenepangenomertab",
-"text": "The gene presence/absence matrix of the whole run, the one both reports draw the pangenome from: one row per gene, one column per sample, 1 where pangene found the gene in that sample's genome. Every genome is searched with the same proteins, the reference proteome and the genes no reference gene matches, so a gene with a 1 in every column is one all the samples share. A gene is named after its reference gene (Rv1506c) or, when no reference gene matches it, after one sample's copy of it (L4_CDC1551_01396, the ID in that sample's master table)."
-},
-{
-"page": "Outputs",
-"section": "final_report.html",
-"url": "user-guide/outputs.html#final_reporthtml",
-"text": "Self-contained, so it travels by email. Covers per-sample quality, missing reference genes by lineage, PE/PPE family distribution, KEGG pathways, duplication communities and network topology, the pangenome (core, softcore, shell, cloud, frequency spectrum, presence/absence heatmaps), COG and Mycobrowser distributions, case statistics and the associated tests, plus a searchable master-table explorer."
-},
-{
-"page": "Outputs",
-"section": "amap_report.html",
-"url": "user-guide/outputs.html#amap_reporthtml",
-"text": "One self-contained file, organised by the questions a run raises: how each genome annotated, where genes are missing or broken on the reference, one gene in every genome, what the correction changed, the pseudogenes by lineage, what lies outside the reference, and the functional profile. Written with or without the functional annotation. The interactive report goes through it."
-},
-{
-"page": "Outputs",
-"section": "Effect of the skip options",
-"url": "user-guide/outputs.html#effect-of-the-skip-options",
-"text": "--skip_typing: No PATHOTYPR/ directory. Samples keep their original IDs instead of being renamed by lineage. --skip_functional_annotation: The run stops after correction. No DUPLICATIONS/, PANGENE/ or final_report.html, and AMAP/ holds only the corrected table and sequences, *.AMAP.tsv, *.AMAP.faa and *.AMAP.fna, without the GFF3, the GenBank or the master table. You still get BAKTA/ and CDS_CASES/, including *_corrected.tsv with the corrected coordinates, and amap_report.html, without its eggNOG sections. --skip_mycobrowser: *-AMAP.MycoBrowser.tsv is written empty and the Mycobrowser sections"
-},
-{
-"page": "Outputs",
-"section": "Next steps",
-"url": "user-guide/outputs.html#next-steps",
-"text": "How it works: what produced all this Duplication metrics: reading the network numbers"
-},
-{
-"page": "The report",
-"section": "",
-"url": "user-guide/report.html",
-"text": "Every run ends in one file, results/final_report.html. It is self-contained: all the figures, tables and JavaScript are embedded, so it opens with no network and survives being emailed to someone who has never heard of Nextflow. To see what the sections look like before running anything, open the example report: a rendered report for twelve MTBC genomes across four lineages. The example uses generated data The twelve samples in it are synthetic. The distributions are drawn around a real M. tuberculosis CDC1551 run, so the shapes are representative, but no conclusion should be drawn from the nu"
-},
-{
-"page": "The report",
-"section": "What is in it",
-"url": "user-guide/report.html#what-is-in-it",
-"text": ""
-},
-{
-"page": "The report",
-"section": "Overview",
-"url": "user-guide/report.html#overview",
-"text": "Dataset composition, then a per-sample quality panel: CDS counts, correction rates and missing reference genes. The annotation quality tab carries coding density, hypothetical percentage and genome size, which is where an outlier sample declares itself before you have gone any further."
-},
-{
-"page": "The report",
-"section": "Annotation results",
-"url": "user-guide/report.html#annotation-results",
-"text": "CDS distribution per lineage, the genes with no counterpart in the reference, and the correction summary: how many genes fell into each case and what the adjudication did with them. Read this alongside the correction algorithm. Missing reference genes get both a heatmap, for spotting a gene absent from a whole lineage rather than one sample, and a searchable table."
-},
-{
-"page": "The report",
-"section": "Functional annotation",
-"url": "user-guide/report.html#functional-annotation",
-"text": "COG categories from eggNOG, Mycobrowser functional categories for MTBC, and a comparison against the reference distribution. Each has its own per-category breakdown in counts and percentages, with the statistical tests alongside. PE/PPE gene families get their own section, since they are both the hardest part of an MTBC genome to annotate and usually the most interesting. The pseudogenes close it: how many AMAP called in each sample, lineage by lineage, a sample with none at 0, the same genes the QC table counts and the GFF3 marks pseudo=true. The master table's note column says why each is a "
-},
-{
-"page": "The report",
-"section": "KEGG pathways",
-"url": "user-guide/report.html#kegg-pathways",
-"text": "The most represented metabolic pathways, and how they differ between lineages."
-},
-{
-"page": "The report",
-"section": "Duplications",
-"url": "user-guide/report.html#duplications",
-"text": "Community sizes, cross-genome homologous pairs and the network topology metrics. Duplication metrics explains what each number means."
-},
-{
-"page": "The report",
-"section": "Pangenome",
-"url": "user-guide/report.html#pangenome",
-"text": "Core, softcore, shell and cloud classification, the gene frequency spectrum, and presence/absence heatmaps."
-},
-{
-"page": "The report",
-"section": "CDS statistics",
-"url": "user-guide/report.html#cds-statistics",
-"text": "How many genes fall in each case, lineage by lineage: box and violin plots with Dunn's tests between lineages, the share of each case in every lineage, a heatmap of the means, and a table per case with the count, mean, median and spread of each lineage."
-},
-{
-"page": "The report",
-"section": "Master table explorer",
-"url": "user-guide/report.html#master-table-explorer",
-"text": "Every gene of every sample in one searchable, sortable table, with the functional annotation attached. Useful for answering a specific question without leaving the report."
-},
-{
-"page": "The report",
-"section": "What changes when steps are skipped",
-"url": "user-guide/report.html#what-changes-when-steps-are-skipped",
-"text": "--skip_typing: Everything still renders, but every sample is grouped under Untyped, so the per-lineage comparisons have a single group and the statistical tests drop out. --skip_mycobrowser: The Mycobrowser and PE/PPE sections are omitted. COG, KEGG, pangenome and duplications are unaffected. This is automatic for non-MTBC organisms. --skip_functional_annotation: No report at all. The run stops after correction."
-},
-{
-"page": "The report",
-"section": "Rendering it yourself",
-"url": "user-guide/report.html#rendering-it-yourself",
-"text": "The report is bin/report/report_AMAP.Rmd, rendered by the FINAL_REPORT process. To iterate on it without running the pipeline: python3 bin/report/demo/make_demo.py demo_inputs docker run --rm -v \"$PWD:/w\" -w /w paururo/amap:latest \\ Rscript -e 'rmarkdown::render(\"bin/report/report_AMAP.Rmd\", output_file=\"/w/demo.html\", params=list(version=\"v1.0.1\", cog_file=\"/w/demo_inputs/input_cog.txt\", Keep it self-contained Nothing in the Rmd, header.html or styles.css may point at a remote URL. pandoc will fetch and inline a remote resource when the machine rendering ha"
-},
-{
-"page": "The interactive report",
-"section": "",
-"url": "user-guide/amap-report.html",
-"text": "Next to final_report.html, every run now writes results/amap_report.html: one file that answers, section by section, the questions a run raises. Did each genome annotate well? Where on the reference are genes missing or broken? What happened to one gene in every genome? What did the correction change? Every table filters, sorts and saves as CSV, every figure saves as SVG, and a click on a genome or a gene opens it. It is written with or without the functional annotation. It needs only what the correction leaves, so a run with --skip_functional_annotation, which gets no final_report.html, still"
-},
-{
-"page": "The interactive report",
-"section": "The sections",
-"url": "user-guide/amap-report.html#the-sections",
-"text": "1. Did each genome annotate well? One row per genome: lineage, genome size, contigs and N50, Bakta's calls and AMAP's genes, the share of the reference genes it has, and how many are missing, pseudogenes, partial, found more than once or outside the reference. A value far from those of comparable genomes is flagged (see How a value is flagged). A click on a genome opens it: its numbers, and its genes in each state, each list searchable and saved as CSV. 2. Where on the reference are genes missing or broken? Every reference gene at its place on the reference, one row per genome, the genomes gro"
-},
-{
-"page": "The interactive report",
-"section": "Gene states",
-"url": "user-guide/amap-report.html#gene-states",
-"text": "Each reference gene gets one state in each genome, from the genes that name it in the genome's final annotation, AMAP//.AMAP.tsv. Intact A gene names it, and nothing below applies Missing No gene names it Pseudogene The best gene naming it is flagged as a pseudogene: an internal stop, a lost stop or a frame that runs on past the reference protein Partial The best gene naming it runs into a contig end and is cut there More copies More than one gene names it alone Length differs One intact copy, of another length than the reference gene Shared copy Only a gene that names several refe"
-},
-{
-"page": "The interactive report",
-"section": "How a value is flagged",
-"url": "user-guide/amap-report.html#how-a-value-is-flagged",
-"text": "Each genome is compared with its own lineage when that lineage has five genomes or more, and with every other genome otherwise. A value is flagged when it lies more than 3.5 robust standard deviations (1.4826 times the median absolute deviation) from their median, on the side that is bad: more missing genes, pseudogenes, partial genes, contigs or genes outside the reference, fewer reference genes found, a smaller N50, a genome size either way. Each also has to be further from the median than a minimum (five genes, five contigs, half a point of reference genes found, 1% of the genome size or 5%"
-},
-{
-"page": "The interactive report",
-"section": "What each section needs",
-"url": "user-guide/amap-report.html#what-each-section-needs",
-"text": ".AMAP.tsv FIX_CDS No report The reference proteome the reference No report Case summaries and tables TYPE_CASES No lineages, no correction section Bakta's folder BAKTA_ANNOTATOR No genome sizes, contigs or products of Bakta's calls Mycobrowser table the reference, MTBC only Names and products from the genomes' own annotation; no categories or drug resistance Master tables GET_OUTPUTS, functional annotation No COG categories or KEGG pathways Pangenome matrix REPORT_INPUTS, functional annotation No pangenome Homologous communities GET_ALLDUP, functional annotation No communities params.yml"
-},
-{
-"page": "The interactive report",
-"section": "Size",
-"url": "user-guide/amap-report.html#size",
-"text": "The data of each genome is a few kilobytes: its genes are stored as how far each lies from where the previous gene and the reference predict it, which for a genome collinear with its reference is mostly nothing. Up to 100 genomes, the report also lists every Bakta call AMAP did not keep, with its coordinates; past that it only counts them, to keep the file small, and CDS_CASES// has them for any run. 15 0.5 MB, measured 100 1.5 to 1.7 MB 300 1.9 to 2.1 MB 500 2.9 to 3.3 MB The sizes past fifteen are worked out from the fifteen MTBC genomes of eight lineages the report was measured on, th"
-},
-{
-"page": "The interactive report",
-"section": "Writing one by hand",
-"url": "user-guide/amap-report.html#writing-one-by-hand",
-"text": "bin/report/amap_report.py needs only Python 3.9 or later. Pointed at a results/ folder it reads the files where the pipeline publishes them: python3 bin/report/amap_report.py \\ --amap results/AMAP \\ --cases results/CDS_CASES \\ --bakta results/BAKTA \\ --master results/AMAP \\ --ref-proteome ref_proteome.faa \\ --mycobrowser Mycobacterium_tuberculosis_H37Rv_txt_v4.txt \\ --versions results/pipeline_info/software_ve The reference proteome is the one the run used: for H37Rv, the AMAP container has it at /reference/aa_H37Rv.translated.fas, and the Mycobrowser table at /refe"
-},
-{
-"page": "Custom reference",
-"section": "",
-"url": "user-guide/custom-reference.html",
-"text": "AMAP corrects an annotation by refereeing it against a trusted one. For MTBC that trusted annotation is H37Rv, bundled in the image. Point --ref_annotation and --ref_genome at your own and the same machinery works for any prokaryote. nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv \\ --skip_typing --skip_mycobrowser \\ --genus Escherichia --species \"Escherichia coli\" \\ --ref_annotation my_reference.gbk \\ --ref_genome my_reference.fasta Both must be given together. Setting one without the other stops the run immediately."
-},
-{
-"page": "Custom reference",
-"section": "What counts as a good reference",
-"url": "user-guide/custom-reference.html#what-counts-as-a-good-reference",
-"text": "A closed, well-curated genome of a close relative, ideally the type strain or a reference assembly from RefSeq. The correction assumes the reference genes are right, so its quality is the ceiling on your result. --ref_annotation: GFF3 (.gff, .gff3) or GenBank (.gbk, .gb, .genbank, .gbff). Detected from the extension, with GenBank tried first for anything else. --ref_genome: FASTA of the same genome. It must be the same assembly the annotation describes, since coordinates are taken at face value."
-},
-{
-"page": "Custom reference",
-"section": "What AMAP does with it",
-"url": "user-guide/custom-reference.html#what-amap-does-with-it",
-"text": "PREPARE_REFERENCE runs once, before any sample is processed, and does three things. 1. Extracts the reference proteome. Every CDS becomes a protein, headed GENE_ID_START-END contig=SEQUENCE, SEQUENCE being the name --ref_genome gives the sequence the gene is on. Those embedded coordinates are not decoration: the paralog step reads them back to predict where each protein belongs in the sample, which tells its own locus from a paralog's, so the header format matters. A CDS across the origin of a circular sequence ends past the sequence's last base, the way GFF3 writes it: tagA on plasmid pO157 ("
-},
-{
-"page": "Custom reference",
-"section": "What you must turn off",
-"url": "user-guide/custom-reference.html#what-you-must-turn-off",
-"text": "--skip_typing: pathotypr assigns MTBC lineages from tuberculosis-specific marker SNPs. Meaningless elsewhere, and it will fail. AMAP warns when --genus is not Mycobacterium and this is not set. --skip_mycobrowser: Mycobrowser functional categories are an MTBC resource. Auto-enabled when --ref_annotation is set and --genus is not Mycobacterium, but setting it explicitly is clearer. Also set --genus and --species to match your organism, since Bakta uses them for naming and reference selection."
-},
-{
-"page": "Custom reference",
-"section": "What stays the same",
-"url": "user-guide/custom-reference.html#what-stays-the-same",
-"text": "Everything else. Case classification, conflict resolution, missing-gene rescue, the stop codon check, eggNOG functional annotation, duplication analysis, the pangenome and the report all work identically. The report simply omits the lineage and Mycobrowser sections, and the pangenome calls each genome by its sample ID where a typed run would use its lineage name."
-},
-{
-"page": "Custom reference",
-"section": "What to expect from a real run",
-"url": "user-guide/custom-reference.html#what-to-expect-from-a-real-run",
-"text": "E. coli O157:H7 Sakai corrected against K-12 MG1655, both straight from RefSeq, in the tutorial's run: reference proteins extracted from the K-12 GenBank 4318 CDS Bakta called on that same reference genome 4296 Bakta false positives derived from the difference 142 case2 3324 case3 31 case4 The 142 are the automatically derived equivalent of the hand-curated MTBC false-positive list, and 1387 case 1 genes is what you would expect between two E. coli strains that genuinely differ. If "
-},
-{
-"page": "Custom reference",
-"section": "Worked example",
-"url": "user-guide/custom-reference.html#worked-example",
-"text": "E. coli K-12 MG1655 against the RefSeq reference: # Fetch the reference datasets download genome accession GCF_000005845.2 --include genome,gff3 unzip ncbi_dataset.zip nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv \\ --skip_typing --skip_mycobrowser \\ --genus Escherichia --species \"Escherichia coli\" \\ --ref"
-},
-{
-"page": "Custom reference",
-"section": "Things that trip people up",
-"url": "user-guide/custom-reference.html#things-that-trip-people-up",
-"text": "The annotation and the genome must match Coordinates from the annotation are used directly against the FASTA. An annotation from a different assembly of the same organism produces nonsense: extracted proteins will not translate, and the reference proteome will be mostly empty. PREPARE_REFERENCE prints the count of extracted proteins, so check it looks like a genome's worth. Reference genes need identifiers A GFF3 whose CDS features carry no locus_tag, ID, gene or Name is skipped entirely, and the run fails with \"No proteins extracted\". This happens with minimal GFF3 exports from some annotatio"
-},
-{
-"page": "Custom reference",
-"section": "Next steps",
-"url": "user-guide/custom-reference.html#next-steps",
-"text": "How it works: what the correction does with the reference Parameters: the full flag reference"
-},
-{
-"page": "Overview",
-"section": "",
-"url": "how-it-works/index.html",
-"text": "AMAP does not annotate genomes from scratch. It takes an annotation that already exists, compares it against one that is trusted, and adjudicates the differences. The premise is that de novo annotators are good at finding genes and less good at their exact boundaries. Bakta will call a start codon a few codons late, or split one gene into two, or invent a short ORF that is not really there. If a curated reference for a close relative exists, those errors are visible: align the reference proteins onto the new genome, and where the two disagree, decide which is more likely right. On MTBC genomes"
-},
-{
-"page": "Overview",
-"section": "The three inputs to a decision",
-"url": "how-it-works/index.html#the-three-inputs-to-a-decision",
-"text": "The Bakta annotation. Every CDS Bakta called on your genome, with coordinates and a locus tag. The Miniprot alignments. Every reference protein aligned onto your genome, with the coordinates where it landed. The reference proteins carry their own coordinates in their headers (Rv0001_1-1524), which lets a later step tell a real locus from a paralogous placement. The false-positive set. Proteins that a de novo annotator calls on the reference genome itself but that are not in the curated annotation. Bakta's characteristic mistakes, in other words. For MTBC this is a curated file; with a custom r"
-},
-{
-"page": "Overview",
-"section": "The pipeline",
-"url": "how-it-works/index.html#the-pipeline",
-"text": "Ten steps, from genomes to report: eggNOG database setup, skipped if it is already built Lineage typing with pathotypr, MTBC only Annotation with Bakta Protein alignment with Miniprot Case classification: every CDS sorted by how it relates to the reference Correction: conflicts resolved, missing genes rescued, stop codons checked Functional annotation with eggNOG-mapper Duplication analysis: within and across genomes Pangenome with pangene Report The workflow page walks through each with its inputs and outputs."
-},
-{
-"page": "Overview",
-"section": "The correction itself",
-"url": "how-it-works/index.html#the-correction-itself",
-"text": "Steps 5 and 6 are what makes AMAP more than a wrapper. Every CDS is sorted into one of five outcomes: Case 1 Bakta called it, the reference has nothing there. Potentially novel. Case 2 Coordinates match the reference exactly. Kept. Case 3 Coordinates match but alignment length differs. Kept. Case 4 Overlaps a reference gene, but the coordinates conflict. Must be resolved. Missing The reference has a gene there, Bakta missed it. Rescued. Cases 1 and 4 are where the work is: deciding which novel genes are real, and which set of coordinates wins a conflict. The correction algorithm gives the rule"
-},
-{
-"page": "Overview",
-"section": "In this section",
-"url": "how-it-works/index.html#in-this-section",
-"text": "The workflow: the ten steps in detail The correction algorithm: case classification, conflict resolution, quality control Duplication metrics: what the network numbers mean"
-},
-{
-"page": "The workflow",
-"section": "",
-"url": "how-it-works/workflow.html",
-"text": "The pipeline as a metro map: stations are the processes of main.nf, lines are what travels between them. Pick a station for what it does and where its files go, or switch an option to see what a run leaves out."
-},
-{
-"page": "The workflow",
-"section": "1. eggNOG database setup",
-"url": "how-it-works/workflow.html#1-eggnog-database-setup",
-"text": "Checks whether the eggNOG database exists at --eggnog_db and builds it if not. A bacteria.dmnd under 100 MB is treated as a truncated download and rebuilt. The database is large, so point --eggnog_db at persistent shared storage on a cluster. Skipped entirely under --skip_functional_annotation."
-},
-{
-"page": "The workflow",
-"section": "2. Lineage typing",
-"url": "how-it-works/workflow.html#2-lineage-typing",
-"text": "pathotypr assigns each genome to an MTBC lineage using phylogenetic marker SNPs, and the genomes are renamed after their sample ID with the lineage in front (L4_G26689 for sample G26689). That prefix carries through every downstream file and is what lets the report group results by lineage. pathotypr types each contig of a draft on its own, and the lineage of the draft is the one the markers of all its contigs add up to. A genome of one sequence has that sequence renamed after the sample and keeps the description that follows the name in its header, so Bakta still reads a closed genome as comp"
-},
-{
-"page": "The workflow",
-"section": "3. Annotation with Bakta",
-"url": "how-it-works/workflow.html#3-annotation-with-bakta",
-"text": "Bakta produces the baseline annotation: CDS, tRNA, rRNA and other features. This is the annotation AMAP will correct, and it is published unmodified under BAKTA/ so you can compare. --genus and --species are passed through, so setting them correctly improves the starting point."
-},
-{
-"page": "The workflow",
-"section": "4. Protein alignment with Miniprot",
-"url": "how-it-works/workflow.html#4-protein-alignment-with-miniprot",
-"text": "Miniprot aligns the reference proteome onto each genome, spliced and frameshift-aware. Two alignments are produced: the reference proteome, and the false-positive set that will filter novel-gene candidates. Output is PAF. Coordinates are converted from 0-based to 1-based on read, and for a protein hitting several positions, the best hit per position is kept so multi-copy families such as PE/PPE keep all their placements. In a draft assembly the genes a contig end cuts get a second pass. Miniprot places the larger piece of such a gene only, so what that placement lacks is aligned again as a pro"
-},
-{
-"page": "The workflow",
-"section": "5. Case classification",
-"url": "how-it-works/workflow.html#5-case-classification",
-"text": "Every Bakta CDS is compared against the alignments and sorted into cases 1 to 4, plus the reference genes Bakta missed. Comparisons are strand-aware throughout: a gene at (100, 200, +) and one at (100, 200, -) are different genes and are never merged. Tables land in CDS_CASES/, one per case, plus interactive overlap plots. The correction algorithm covers the classification."
-},
-{
-"page": "The workflow",
-"section": "6. Correction",
-"url": "how-it-works/workflow.html#6-correction",
-"text": "The cases are resolved into one corrected coordinate set: conflicts adjudicated, novel genes filtered, missing reference genes rescued, duplicates removed. Then check_cds.py translates every surviving CDS. A gene that does not end in a stop codon has its coordinates extended to the next in-frame stop, up to 300 bp. Genes with internal stops are flagged as pseudogenes rather than discarded. Anything that cannot be translated at all is dropped, so the annotation table and the FASTA files always agree. Outputs are the corrected gene table and its protein and nucleotide files under AMAP/ (*.AMAP.t"
-},
-{
-"page": "The workflow",
-"section": "7. Functional annotation",
-"url": "how-it-works/workflow.html#7-functional-annotation",
-"text": "eggNOG-mapper assigns COG categories, KEGG orthologs and pathways, GO terms, EC numbers, PFAM domains and CAZy families. This runs once over all samples together, not once per sample. With closely related genomes most proteins are identical across samples, so they are deduplicated first, annotated once, and the results propagated back. For a few hundred MTBC genomes that cuts the Diamond search by an order of magnitude and avoids hundreds of jobs contending for the same database on a shared filesystem. The results are merged into *-AMAPmastertable.tsv and into the GFF3 attributes."
-},
-{
-"page": "The workflow",
-"section": "8. Duplication analysis",
-"url": "how-it-works/workflow.html#8-duplication-analysis",
-"text": "Each proteome is searched against itself with Diamond to find within-genome duplications, which are grouped into families by connected components. Then the proteins of every sample's families are pooled and searched all against all to build homologous communities across genomes, with network metrics computed per community. Duplication metrics explains the numbers."
-},
-{
-"page": "The workflow",
-"section": "9. Pangenome",
-"url": "how-it-works/workflow.html#9-pangenome",
-"text": "pangene builds one pangenome graph for the whole run and a gene presence/absence matrix, which the report turns into core, softcore, shell and cloud classifications, a frequency spectrum and presence/absence heatmaps. Every genome is searched with the same proteins: the reference proteome, and the genes of each sample that no reference gene matches. miniprot aligns them to every sample's genome, its own included: the ones PATHOTYPR renamed or, with --skip_typing, the genomes of the sample sheet, each under its sample ID. A row of the matrix is then a gene, named after its reference gene or, ou"
-},
-{
-"page": "The workflow",
-"section": "10. Report",
-"url": "how-it-works/workflow.html#10-report",
-"text": "A single self-contained HTML file built from every output above. Sample quality, missing reference genes by lineage, PE/PPE families, KEGG pathways, duplication communities and topology, the pangenome, COG and Mycobrowser distributions, case statistics with the associated tests, and a searchable table of every gene. Alongside it, pipeline_info/ collects the Nextflow execution report, timeline, DAG and trace, plus software_versions.yml and params.yml for your methods section."
-},
-{
-"page": "The workflow",
-"section": "11. Interactive report",
-"url": "how-it-works/workflow.html#11-interactive-report",
-"text": "amap_report.html, one self-contained file organised by question: how each genome annotated, where reference genes are missing or broken, one gene in every genome, what the correction changed and the pseudogenes by lineage. It reads the correction's outputs directly, so it is written with or without the functional annotation, and the functional steps add their sections when they run. The interactive report goes through it."
-},
-{
-"page": "The workflow",
-"section": "Next steps",
-"url": "how-it-works/workflow.html#next-steps",
-"text": "The correction algorithm: the rules behind step 6 Outputs: every file this produces"
-},
-{
-"page": "The correction algorithm",
-"section": "",
-"url": "how-it-works/correction-algorithm.html",
-"text": "The core of AMAP. It referees between the initial Bakta annotation and the Miniprot alignments of a trusted reference proteome, classifying every candidate gene and then applying a set of rules to resolve the disagreements. Coordinates throughout are 1-based inclusive, so a gene's length is end - start + 1. Every comparison is strand-aware and contig-aware: genes on opposite strands at the same position are different genes and never conflict, and nor are genes on different contigs of a draft assembly, where every contig starts at 1. Bakta runs with --keep-contig-headers so that it names the co"
-},
-{
-"page": "The correction algorithm",
-"section": "0. Placements",
-"url": "how-it-works/correction-algorithm.html#0-placements",
-"text": ""
-},
-{
-"page": "The correction algorithm",
-"section": "Small proteins",
-"url": "how-it-works/correction-algorithm.html#small-proteins",
-"text": "Miniprot needs a seed of several residues and enough score to report an alignment, which a protein of a few dozen residues rarely gives it: annotating E. coli K-12 with its own proteome, it placed none of 33 proteins of 8 to 29 residues, and it put the toxin IbsC (b4665), 19 residues three of which differ from its neighbour IbsD's, on that neighbour only. Every protein without an exact, complete placement is looked for in the six-frame translation of the genome, preceded by a start codon and followed by a stop, and each exact copy found where Miniprot did not already place the protein becomes "
-},
-{
-"page": "The correction algorithm",
-"section": "Pieces of cut genes",
-"url": "how-it-works/correction-algorithm.html#pieces-of-cut-genes",
-"text": "A draft assembly cuts the genes its contig breaks run through. Miniprot reports only the placements within 5% of a protein's best, so it places the larger piece of such a gene and not the smaller one at the end of another contig: in CDC1551 cut into 40 contigs, none of the 25 smaller pieces was annotated. A placement was cut by its contig when it lacks residues at one end of its protein, ends within 30 bp of the contig's edge on that side, and those residues would not fit in the bases left. What it lacks, if 8 residues or more, is aligned again as a protein of its own, and its best placement i"
-},
-{
-"page": "The correction algorithm",
-"section": "Paralog placements",
-"url": "how-it-works/correction-algorithm.html#paralog-placements",
-"text": "Miniprot places a reference protein everywhere it scores within 5% of its best, so a protein with a paralog is placed at both loci. Before anything is classified, each placement is checked against where the protein belongs. The reference proteome carries each protein's coordinates in its header (Rv1150_1278269-1278820), but those are reference coordinates, and any indel upstream shifts every position after it. So each placement's reference position is predicted from the nearest proteins placed only once, which follow the sample's local offset from the reference, and the placement closest to wh"
-},
-{
-"page": "The correction algorithm",
-"section": "1. Classification",
-"url": "how-it-works/correction-algorithm.html#1-classification",
-"text": "Every Bakta CDS is compared against the Miniprot alignments. Case 2, perfect match. Same start, same end, same strand as an alignment, and the same length. Correct, kept as is. Case 3, imperfect match. Same coordinates, but the aligned length differs, typically from indels. Reliable enough, kept. Case 4, conflicting overlap. A Bakta CDS and an alignment overlap on the same strand, but their coordinates do not match. This is the conflict that has to be adjudicated. Case 1, potentially novel. A Bakta CDS with no overlap to any alignment. Either a genuine gene absent from the reference, or someth"
-},
-{
-"page": "The correction algorithm",
-"section": "2. Filtering case 1",
-"url": "how-it-works/correction-algorithm.html#2-filtering-case-1",
-"text": "Novel-gene candidates are scrutinised before being believed. A candidate is removed if either holds: it overlaps the false-positive set by 80% or more of its own length, meaning it is a call the annotator is known to make wrongly on the reference genome itself it aligns to the reference at 80% or more match identity, meaning it is not novel at all but a shifted version of a gene that is already there Candidates that pass both, and candidates with no reference alignment to judge by, are kept as case1_kept."
-},
-{
-"page": "The correction algorithm",
-"section": "3. Resolving case 4",
-"url": "how-it-works/correction-algorithm.html#3-resolving-case-4",
-"text": ""
-},
-{
-"page": "The correction algorithm",
-"section": "Single overlaps",
-"url": "how-it-works/correction-algorithm.html#single-overlaps",
-"text": "One Bakta CDS against one reference alignment, on the same strand of the same contig. Lengths are compared, and: the reference wins by default the Bakta version wins only if it is longer by more than 550 bp, and the alignment is not complete in the call's reading frame The asymmetry is deliberate. A reference alignment is usually the better boundary, but a much longer Bakta call normally means the alignment fragmented, and discarding a full-length gene in favour of a partial alignment would be the worse error. A complete alignment, one that covers the whole reference protein down to its stop c"
-},
-{
-"page": "The correction algorithm",
-"section": "Multiple overlaps",
-"url": "how-it-works/correction-algorithm.html#multiple-overlaps",
-"text": "Several Bakta CDS against one alignment, the signature of a gene that has been split. The best-overlapping Bakta CDS is picked first, then compared to the alignment on length: the reference wins if it is at least as long, if the Bakta CDS beats it by less than 500 bp, if the alignment is complete in the call's frame, or if the call overlaps other alignments as well, which makes it several reference genes read as one (Rv3022c and Rv3021c are one reading frame in CDC1551) otherwise the Bakta CDS wins"
-},
-{
-"page": "The correction algorithm",
-"section": "4. Deduplication on insert",
-"url": "how-it-works/correction-algorithm.html#4-deduplication-on-insert",
-"text": "Each winner is checked against the genes already accepted. Where a new coordinate overlaps an existing one by 70% or more on the same strand: the new gene displaces the existing one if it is longer by more than 550 bp it also displaces it if the existing gene is shorter by less than that margin and is not a reference gene otherwise the existing gene stands and the new coordinate is dropped A reference gene is one whose identifier is a reference protein's. The test used to look for coordinates embedded in the identifier, which are stripped as the alignments are read, so no gene qualified and a "
-},
-{
-"page": "The correction algorithm",
-"section": "5. Rescuing missing genes",
-"url": "how-it-works/correction-algorithm.html#5-rescuing-missing-genes",
-"text": "A reference protein is missing when no gene accounts for any of its placements, and then it goes back in at its best one. A gene accounts for a placement when it sits on it: same contig and strand, the same reading frame, covering at least 50% of it, and not known to be another reference gene. The frame matters because genes that really overlap sit in different frames (Rv0724A and Rv0725c); another reference gene in the same frame does not count either, since some are nested (E. coli hokC inside mokC). Placements on other genes' loci have already been dropped in step 0, so what is left to resc"
-},
-{
-"page": "The correction algorithm",
-"section": "6. Final deduplication",
-"url": "how-it-works/correction-algorithm.html#6-final-deduplication",
-"text": "Where one gene ends up at several coordinates, entries fully contained inside a different gene are dropped, always keeping at least one entry per gene so nothing disappears entirely. After the stop codon check, deduplicate_rvs.py ranks the genes that share a reference protein: a non-pseudogene over a pseudogene, then the longer one, then one Bakta also called at exactly those coordinates, then the one nearer where the protein belongs by synteny. The first keeps its place. The others stay as further copies of the gene when they are at least 90% as long as the first, clear of the copies kept, an"
-},
-{
-"page": "The correction algorithm",
-"section": "7. Stop codon check",
-"url": "how-it-works/correction-algorithm.html#7-stop-codon-check",
-"text": "check_cds.py translates every surviving CDS. It is given the reference proteome, which settles what a translation alone cannot. No terminal stop codon. Coordinates are extended to the next in-frame stop, searching 300 bp in the right direction for the strand, or as far as the reference protein says is missing when that is further. If the extension introduces internal stops or finds nothing, the original coordinates are kept and the gene is flagged as a pseudogene. An extension goes no further than the reference protein. A gene may be extended to a stop at most 10 residues past the end of its r"
-},
-{
-"page": "The correction algorithm",
-"section": "Threshold summary",
-"url": "how-it-works/correction-algorithm.html#threshold-summary",
-"text": "False-positive overlap case 1 filtering 80% Reference match identity case 1 filtering 80% Bakta length advantage single overlaps 550 bp Bakta length advantage multiple overlaps 500 bp Overlap for deduplication on insert 70% Length margin for displacement on insert 550 bp Distance from the predicted position paralog placements 10 kb Proteins placed once, each side, for the local offset paralog placements 5 Cover to claim another protein's placement paralog placements 50% Shortest protein looked for exactly small proteins 8 residues Longest protein looked for with substitutions small proteins 50"
-},
-{
-"page": "The correction algorithm",
-"section": "Next steps",
-"url": "how-it-works/correction-algorithm.html#next-steps",
-"text": "The workflow: where this sits in the pipeline Outputs: the case tables that record every decision"
-},
-{
-"page": "Duplication metrics",
-"section": "",
-"url": "how-it-works/duplications.html",
-"text": "AMAP looks at gene duplication twice: within each genome, and across the whole set."
-},
-{
-"page": "Duplication metrics",
-"section": "How the families are found",
-"url": "how-it-works/duplications.html#how-the-families-are-found",
-"text": "Each corrected proteome is searched against itself with Diamond, excluding self-hits. A pair is kept when the identical residues of the alignment cover 80% or more of either the query or the subject, so a short domain match between two otherwise unrelated proteins is not counted as duplication. Kept pairs become links in a graph, and its connected pieces become the duplication families, listed in DUPLICATIONS/{sample}/duplications_{sample}_groups.tsv. Because the rule takes the larger of the two shares, a fragment joins the family of the gene it came from: a match that covers almost all of a s"
-},
-{
-"page": "Duplication metrics",
-"section": "How the communities are built",
-"url": "how-it-works/duplications.html#how-the-communities-are-built",
-"text": "The proteins of every sample's families are then pooled and searched all against all. Within each genome, the pairs that pass the same rule make a network, which the Louvain method splits into communities, with a fixed seed so that a rerun splits it the same way. Two communities are homologous when at least one hit joins them, and each homologous pair is one row of DUPLICATIONS/homologous_communities.tsv, with an interactive network in the matching HTML file."
-},
-{
-"page": "Duplication metrics",
-"section": "The columns",
-"url": "how-it-works/duplications.html#the-columns",
-"text": "A row describes two communities: the columns ending in 1 are the first, those ending in 2 the second. The first is the one whose sample comes first by name, or the one with the lower number when both are in the same sample, and the rows come in that order too, so a rerun writes the same file. For each: Genome, Community: the sample, and the community's number in it. Size: how many proteins it holds. Elements: the eggNOG COG categories of its proteins, with how many have each, the largest first. A protein eggNOG gave no category counts as -, eggNOG's mark for none. AvgDegree: links per protein,"
-},
-{
-"page": "Duplication metrics",
-"section": "Reading them together",
-"url": "how-it-works/duplications.html#reading-them-together",
-"text": "Density and clustering at 1 mean copies that all match each other: a recent expansion of near-identical copies. In CDC1551 these are the transposases of insertion sequences, the ESAT-6-like Esx proteins and PE proteins. Lower density and longer paths mean copies that have drifted apart, each still matching some of the others but not all, as M. bovis's copies of the transposases above have. Or, less happily, an artefact of assembly fragmentation, where one gene broken across contigs looks like several partial copies. Cross-checking against the contig count in *-AMAP.qc.tsv tells the two apart. "
-},
-{
-"page": "Duplication metrics",
-"section": "Next steps",
-"url": "how-it-works/duplications.html#next-steps",
-"text": "Outputs: where the duplication files live The workflow: where this step sits"
-},
-{
-"page": "Containers",
-"section": "",
-"url": "deployment/containers.html",
-"text": "Every tool AMAP calls lives in one image, paururo/amap, published on Docker Hub. Both the docker and singularity profiles use it, so nothing needs installing beyond a container engine. nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv nextflow run PathoGenOmics-Lab/AMAP -profile singularity --tsv samples.tsv Nextflow pulls the image on first use and caches it. Singularity converts it to a .sif on the way, which takes a few minutes once."
-},
-{
-"page": "Containers",
-"section": "What is inside",
-"url": "deployment/containers.html#what-is-inside",
-"text": "Tools: Bakta 1.12.0, Miniprot 0.18, Diamond 2.1.24, eggNOG-mapper 2.1.13, pangene 1.1, pathotypr 1.0.0, gfatools. Runtimes: Python 3.9 with pandas, NumPy, Biopython, matplotlib, plotly, NetworkX, intervaltree, pyvis and python-louvain. R 4.4 with the report packages. Bundled data under /reference/, so a standard MTBC run needs no downloads: MTB_ancestor_reference.fasta MTBC ancestral reference genome aa_H37Rv.translated.fas H37Rv reference proteome bad.bakta.translated.fas Curated Bakta false positives new_refH37Rv.fasta H37Rv reference genome sequence.gb H37Rv GenBank, for Bakta Mycobacterium"
-},
-{
-"page": "Containers",
-"section": "Platform",
-"url": "deployment/containers.html#platform",
-"text": "The image is linux/amd64 only, and every Dockerfile stage pins it. On Apple Silicon it runs under emulation, which works but is slow. For anything past a couple of genomes, use an amd64 machine or a cluster."
-},
-{
-"page": "Containers",
-"section": "Pinning a version",
-"url": "deployment/containers.html#pinning-a-version",
-"text": "latest tracks the newest release. For a reproducible analysis, pin the version in nextflow.config or on the command line: nextflow run PathoGenOmics-Lab/AMAP -profile docker \\ --tsv samples.tsv \\ -process.container 'paururo/amap:1.0.1' results/pipeline_info/software_versions.yml records the exact version of every tool a run used, which is what belongs in a methods section."
-},
-{
-"page": "Containers",
-"section": "Building it yourself",
-"url": "deployment/containers.html#building-it-yourself",
-"text": "Only needed if you are changing what is in the image. git clone https://github.com/PathoGenOmics-Lab/AMAP cd AMAP docker build --platform linux/amd64 -f docker/Dockerfile -t amap:dev . The build takes about an hour: it downloads the Bakta database and solves a conda environment with R. Check the result before using it: docker/smoke_test.sh amap:dev That verifies the tools, the Python and R libraries, the bundled reference data, and runs the repository test suite with the image interpreter. The last part matters, since a library update can break the pipeline scripts without the image looking an"
-},
-{
-"page": "Containers",
-"section": "Singularity on a cluster",
-"url": "deployment/containers.html#singularity-on-a-cluster",
-"text": "Set a shared cache so every job reuses one converted image instead of each building its own: export NXF_SINGULARITY_CACHEDIR=/shared/singularity_cache Put it in your shell profile or the launch script. Without it, a large run can have dozens of jobs converting the same image at once. More cluster specifics in deployment on HPC."
-},
-{
-"page": "Containers",
-"section": "Next steps",
-"url": "deployment/containers.html#next-steps",
-"text": "HPC deployment: scratch, shared filesystems, schedulers Troubleshooting: when a run fails"
-},
-{
-"page": "HPC clusters",
-"section": "",
-"url": "deployment/hpc.html",
-"text": "Past a couple of dozen genomes, a scheduler is worth the setup. Combine a container profile with an executor profile: nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv"
-},
-{
-"page": "HPC clusters",
-"section": "The executor profiles",
-"url": "deployment/hpc.html#the-executor-profiles",
-"text": "slurm SLURM 100 per minute sge Sun Grid Engine, Univa 20 per minute pbs PBS, Torque 20 per minute lsf IBM Spectrum LSF 20 per minute All four queue up to 200 jobs at once, default to 48 CPUs and 128 GB per job, and enable node-local scratch. SGE additionally requests memory through -l h_vmem, and uses the smp parallel environment. Tune the concurrency to your site's policy if 200 is too aggressive: nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm \\ --tsv samples.tsv -process.queueSize 50"
-},
-{
-"page": "HPC clusters",
-"section": "Fitting your site",
-"url": "deployment/hpc.html#fitting-your-site",
-"text": "nextflow run PathoGenOmics-Lab/AMAP \\ -profile singularity,slurm \\ --tsv samples.tsv \\ --max_cpus 64 --max_memory 256.GB \\ -process.queue long \\ -process.clusterOptions '--account=mygroup' -process.queue sets the partition or queue, and -process.clusterOptions passes anything else straight through, which is where accounting flags, QoS and node constraints go. On SLURM, Bakta's jobs, the longest a run sends, also ask for --qos=medium. The slurm profile sets that for Bakta alone, and for Bakta it takes the place of -process.clusterOptions, so give Bakta your site's options in"
-},
-{
-"page": "HPC clusters",
-"section": "Scratch",
-"url": "deployment/hpc.html#scratch",
-"text": "--scratch is on by default in every HPC profile, and it matters. Without it, every process reads and writes directly on the shared filesystem, and Bakta and eggNOG in particular generate enough metadata traffic to slow a cluster down for everyone. With it, work happens on node-local disk and only the results are copied back. Turn it off only if your nodes have no local disk: nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm \\ --tsv samples.tsv --scratch false"
-},
-{
-"page": "HPC clusters",
-"section": "Where to put things",
-"url": "deployment/hpc.html#where-to-put-things",
-"text": "The Singularity cache. Set it once, somewhere shared: export NXF_SINGULARITY_CACHEDIR=/shared/singularity_cache Otherwise the singularity profile caches under --outdir, and a large run can have many jobs converting the same image at once. The eggNOG database. Large, built once, reused forever. Point it at persistent shared storage rather than somewhere per-run: nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm \\ --tsv samples.tsv --eggnog_db /shared/databases/eggnog The work directory. It gets large, tens of gigabytes for a big run. Put it on a filesystem with room and a gener"
-},
-{
-"page": "HPC clusters",
-"section": "Launching",
-"url": "deployment/hpc.html#launching",
-"text": "Nextflow's own process must survive the whole run, which will outlive an SSH session. Either run it under tmux or screen, or submit it as a small job of its own: #!/bin/bash #SBATCH --job-name=amap #SBATCH --cpus-per-task=2 #SBATCH --mem=8G #SBATCH --time=48:00:00 #SBATCH --output=amap_%j.log module load singularity export NXF_SINGULARITY_CACHEDIR=/shared/singularity_cache nextflow run PathoGenOmics-Lab/AMAP \\ -profile singularity,slurm \\ --tsv sam The launcher itself needs very little: it submits and waits while the real work happens in the jobs it spawns. Give it plenty of wall tim"
-},
-{
-"page": "HPC clusters",
-"section": "Memory and retries",
-"url": "deployment/hpc.html#memory-and-retries",
-"text": "A process killed by the out-of-memory killer is resubmitted with more memory, scaling with the attempt number up to --max_memory. That handles the occasional unusually large genome without you having to size everything for the worst case. eggNOG is the memory-hungry step. It runs once over all samples rather than once per sample, which keeps a shared filesystem from being hammered but does mean one large job. The highmem profile exists for this: nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm,highmem \\ --tsv samples.tsv"
-},
-{
-"page": "HPC clusters",
-"section": "Watching a run",
-"url": "deployment/hpc.html#watching-a-run",
-"text": "squeue -u $USER shows the jobs. When it finishes, pipeline_info/ holds the execution report, the timeline, and a trace with per-process CPU, memory and runtime, which is what to look at before adjusting resources for the next run."
-},
-{
-"page": "HPC clusters",
-"section": "Next steps",
-"url": "deployment/hpc.html#next-steps",
-"text": "Troubleshooting: when something fails Containers: what the image holds"
-},
-{
-"page": "Troubleshooting",
-"section": "",
-"url": "deployment/troubleshooting.html",
-"text": ""
-},
-{
-"page": "Troubleshooting",
-"section": "Reading a failure",
-"url": "deployment/troubleshooting.html#reading-a-failure",
-"text": "When a process fails, Nextflow prints the work directory for the failed task. That directory has everything you need: work/ab/cd1234.../ ├── .command.sh # the exact script that ran ├── .command.log # everything it printed ├── .command.err # stderr on its own └── .exitcode # what it exited with .command.err is almost always where the answer is. Re-running .command.sh inside the container reproduces the failure by hand. results/pipeline_info/execution_trace_*.txt shows every task with its status, CPU, memory and runtime, which is the fastest way to see whether a failure was r"
-},
-{
-"page": "Troubleshooting",
-"section": "Before the pipeline starts",
-"url": "deployment/troubleshooting.html#before-the-pipeline-starts",
-"text": "\"Unexpected input: '('\" in nextflow.config: Nextflow 25.10 and later read pipelines with a stricter parser, which older copies of AMAP do not pass. Nextflow keeps the copy it downloaded first, so fetch the current one with nextflow pull PathoGenOmics-Lab/AMAP and run again. To run an older AMAP release as it is, export NXF_SYNTAX_PARSER=v1 first. \"Input TSV file not found\": The path in --tsv is wrong, or relative to somewhere other than where you launched Nextflow. \"Invalid TSV format\": The header must be exactly sampleId and fasta, separated by a real tab. Spaces are the usual culprit. cat -t"
-},
-{
-"page": "Troubleshooting",
-"section": "Container problems",
-"url": "deployment/troubleshooting.html#container-problems",
-"text": "The image will not pull. Check network access from the compute nodes, which often differs from the login node. Pre-pulling on the login node and setting NXF_SINGULARITY_CACHEDIR sidesteps it: export NXF_SINGULARITY_CACHEDIR=/shared/singularity_cache singularity pull docker://paururo/amap:latest Slow on Apple Silicon. The image is amd64 only, so it emulates. Fine for the test profile, painful beyond that. Use an amd64 machine. Docker permission denied. Your user is not in the docker group. Either add it, or use Singularity."
-},
-{
-"page": "Troubleshooting",
-"section": "Memory",
-"url": "deployment/troubleshooting.html#memory",
-"text": "A process is killed with exit 137 or 140. Out of memory. AMAP retries with more, scaling by attempt, so a single occurrence in the log that later succeeds is normal. Persistent failures mean raising the ceiling: --max_memory 256.GB On a cluster, that is the whole answer. On a macOS or Windows laptop it is the wrong one: Docker Desktop runs containers inside a VM with its own memory ceiling, commonly 8 GB, and that ceiling is what killed the process. Nextflow sized the request against the host and never saw it. Raising --max_memory only makes the request larger than a limit it was already over."
-},
-{
-"page": "Troubleshooting",
-"section": "Filesystem",
-"url": "deployment/troubleshooting.html#filesystem",
-"text": "\"No space left on device\". Usually the work directory, which grows to tens of gigabytes. Move it somewhere with room: -work-dir /scratch/$USER/amap_work Also check your $TMPDIR quota, since Diamond and Bakta both use it heavily. Everything is slow on a shared filesystem. Confirm --scratch is on. It defaults to true in the HPC profiles, but a standard or docker profile on a cluster will not have it. Cache misses on resume. NFS timestamp drift across nodes invalidates Nextflow's cache. The slurm profile already sets process.cache = 'lenient' for this. If you are using a different executor, add i"
-},
-{
-"page": "Troubleshooting",
-"section": "The eggNOG database",
-"url": "deployment/troubleshooting.html#the-eggnog-database",
-"text": "It downloads every run. --eggnog_db is pointing somewhere per-run. Give it persistent shared storage. It rebuilds itself unexpectedly. A bacteria.dmnd under 100 MB is treated as a truncated download and rebuilt. Usually means an earlier download was interrupted. \"has eggnog_db.ok, so EGGNOG_DB will not run again\". eggnog_db.ok tells Nextflow the database is built, so it never runs EGGNOG_DB again, but what it needs is not all there. Delete the files the message names and the next run builds it again."
-},
-{
-"page": "Troubleshooting",
-"section": "Results that look wrong",
-"url": "deployment/troubleshooting.html#results-that-look-wrong",
-"text": "Far fewer genes than expected. Check the assembly. A fragmented assembly loses genes across contig boundaries, and they turn up in *-rvmissing.tsv rather than being corrected. *-AMAP.qc.tsv has the contig count and N50. \"Genes lost to (Start, End, Strand, Contig) collisions\" in the log. Two of the classification's sources listed a gene at exactly the same place, and one entry was kept. Genes on different contigs never collide, since the contig is part of the key. *-AMAP.clean.faa has fewer sequences than the GFF3 has genes. Expected. The clean proteome drops pseudogenes and internal-stop seque"
-},
-{
-"page": "Troubleshooting",
-"section": "Still stuck",
-"url": "deployment/troubleshooting.html#still-stuck",
-"text": "Open an issue at github.com/PathoGenOmics-Lab/AMAP/issues with: the command you ran the failing task's .command.err results/pipeline_info/software_versions.yml results/pipeline_info/params.yml The last two pin down the exact versions and parameters, which is usually what the answer turns on."
-},
-{
-"page": "Changelog",
-"section": "",
-"url": "about/changelog.html",
-"text": "All notable changes to AMAP are documented in this file."
-},
-{
-"page": "Changelog",
-"section": "[Unreleased]",
-"url": "about/changelog.html#unreleased",
-"text": "Annotating H37Rv against its own reference now gives back every CDS of the AL123456.3 GenBank record at exactly its coordinates, strand and name: 4031 of 4031, against 4020 before. The changes apply to every genome, so results are not directly comparable with 1.0.1."
-},
-{
-"page": "Changelog",
-"section": "🔬 Correctness",
-"url": "about/changelog.html#-correctness",
-"text": "Paralog copies no longer take another gene's place: a protein with a paralog was placed at both loci, and which copy survived came down to insertion order. On H37Rv itself that lost Rv1150, Rv1945 and Rv2828c; on M. bovis it reported genes the strain lacks (the mce3 operon of RD7, the phiRv2 prophage of RD11, plcBC of RD5) as copies sitting on their paralogs. Each placement is now checked against where its protein belongs, predicted from the sample's own offset to the reference so that indels do not throw it off Strand of a resolved conflict: taken from the protein's first placement anywhere i"
-},
-{
-"page": "Changelog",
-"section": "🐛 Pipeline",
-"url": "about/changelog.html#-pipeline",
-"text": "Pseudogenes in the GFF3 and GenBank: the GFF3 never said which genes were pseudogenes, and the GenBank only for genes with an internal stop, though the master table knew. Both now carry AMAP's flag with its reason (pseudo=true and Note, /pseudo and /note), and mark the ends a contig cut off (partial=true with start_range or end_range, < or >); the master table and *-AMAP.tsv have them as their last columns The default H37Rv reference under Docker: its files live inside the container and were handed to processes as paths, so Nextflow mounted /reference from the host, which Docker Desktop refuse"
-},
-{
-"page": "Changelog",
-"section": "📊 Report",
-"url": "about/changelog.html#-report",
-"text": "A report organised by question, results/amap_report.html: next to final_report.html, a new process, AMAP_REPORT, writes one self-contained file from each genome's own outputs: a table of the genomes with the values far from comparable genomes flagged, a map of every reference gene in every genome where regions of difference show as blocks, a lookup of one gene in every genome, the correction from Bakta's calls to AMAP's genes with the genes behind each step, the pseudogenes by lineage, what lies outside the reference, and the functional profile, the categories that differ most between lineages"
-},
-{
-"page": "Changelog",
-"section": "🧹 Code",
-"url": "about/changelog.html#-code",
-"text": "reconcile_coordinates is gone: it read reference coordinates from protein identifiers that have not carried them since March 2026, so it changed nothing. The paralog check does its job with the sample's own offsets"
-},
-{
-"page": "Changelog",
-"section": "📚 Documentation",
-"url": "about/changelog.html#-documentation",
-"text": "A website in plain HTML, drawn with real genes: MkDocs is gone. The front page starts a newcomer in three steps and builds the command for their machine, the pipeline is a metro map whose stations are main.nf's processes, and the four cases, the missing genes and the harder cases (paralogs, small proteins, contig ends, frameshifts, copies) are drawn on genes of CDC1551, M. bovis and E. coli from the benchmark. The tutorial and user guide stay markdown, readable on GitHub, and tools/build_docs.py turns them into pages of the same site, dark by default The site is checked against the code: tools"
-},
-{
-"page": "Changelog",
-"section": "🧪 Testing",
-"url": "about/changelog.html#-testing",
-"text": "tests/selftest/h37rv_selftest.sh annotates H37Rv with itself inside the container and fails unless every GenBank CDS comes back exactly tests/test_cache_keys.py fails when a process runs AMAP code that is not in its cache key, or when its script names a bin/ entry as a bare word without declaring it tests/benchmark/benchmark.sh scores the correction algorithm on seven genomes whose right answer is known (H37Rv twice, CDC1551 complete and in 40 contigs, M. bovis, E. coli K-12 and Sakai), downloading them and running Bakta and miniprot in the container the first time, and lists every gene that c"
-},
-{
-"page": "Changelog",
-"section": "[1.0.1] - 2026-07-25",
-"url": "about/changelog.html#101---2026-07-25",
-"text": "Bug fixes, most of them found by running the pipeline on real genomes rather than by reading it. Several changed what the pipeline outputs, so results from 1.0.0 are not directly comparable."
-},
-{
-"page": "Changelog",
-"section": "🔬 Correctness",
-"url": "about/changelog.html#-correctness-1",
-"text": "Genes no longer vanish during overlap resolution: _insert_with_dedup queued entries for eviction while scanning for conflicts, and applied them even when it then abandoned the insert, so genes were deleted with nothing put in their place Reference pseudogenes are recognised for any organism: the check was gated on the identifier starting with Rv, so on every other organism every reference pseudogene was extended to the next stop and lost its flag. The same gene came out differently depending on what it was called The extracted reference proteome carries its stop codon: extract_ref_proteins.py "
-},
-{
-"page": "Changelog",
-"section": "🐛 Crashes",
-"url": "about/changelog.html#-crashes",
-"text": "--skip_mycobrowser and organism-agnostic mode: the placeholder frame was built with a duplicate Locus column, so pd.merge refused to run and GET_OUTPUTS failed on every run without Mycobrowser Miniprot PAF parsing: pandas inferred the column count from the first row, and miniprot varies its trailing tags per alignment, so any file whose first row was narrower aborted the parse Biopython 1.85 and newer: SeqFeature.strand was removed; to_panaroo.py and extract_ref_proteins.py now read it off the location -profile test: pointed at a sample sheet naming paths that are not in this repository. It no"
-},
-{
-"page": "Changelog",
-"section": "📉 Silent data loss",
-"url": "about/changelog.html#-silent-data-loss",
-"text": "First Diamond hit: read_and_create left header inference on while stripping # comments, so the first alignment became the column names and disappeared from the duplication analysis Multi-part gene names in rename_fastas.py: headers were cut at the third underscore, collapsing every PE/PPE gene of a sample onto one identifier eggNOG annotation rate: counted with notna(), and emapper writes - for fields it could not fill, so the QC metric read close to 100% whatever the run had done"
-},
-{
-"page": "Changelog",
-"section": "⚡ Performance",
-"url": "about/changelog.html#-performance",
-"text": "reconcile_coordinates is linear rather than quadratic: it scanned every protein and every reference for each gene, calling a regex on each candidate. Indexed by gene name up front, a 4000-gene genome goes from 18.3 s to 0.011 s with identical output over 200 randomised genomes deduplicate: list.count() per element and a full scan per duplicated gene, replaced with a Counter and a single grouping pass"
-},
-{
-"page": "Changelog",
-"section": "📊 Report",
-"url": "about/changelog.html#-report-1",
-"text": "Layout: the stylesheet was losing to the one rmarkdown inlines afterwards, so the article sat underneath the table of contents and the left logo on top of the title Self-contained without a network at render time: jQuery came from a CDN and the second logo from GitHub. pandoc inlines those when the render machine has a network and leaves them dangling when it does not, so a report rendered on an isolated node arrived with a broken image Half the size: 8.2 MB to 4.4 MB, by shipping only the plotly traces actually used and quantising the figures The version in the report is the version that prod"
-},
-{
-"page": "Changelog",
-"section": "📚 Documentation and infrastructure",
-"url": "about/changelog.html#-documentation-and-infrastructure",
-"text": "Documentation site built with MkDocs Material, published to GitHub Pages, including a tutorial with the numbers from real runs and a rendered example report Docker image workflow: builds, smoke tests and publishes on a v* tag or on demand Tests workflow enabled: it had been sitting disabled and would have failed, since its dependency list was missing plotly and its syntax check had drifted five files behind requirements-test.txt: one dependency list for CI, the docs and a local checkout. environment.yml was missing the Python plotly, so a conda environment built from it could not start classif"
-},
-{
-"page": "Changelog",
-"section": "[1.0.0] - 2026-04-01",
-"url": "about/changelog.html#100---2026-04-01",
-"text": ""
-},
-{
-"page": "Changelog",
-"section": "🔬 Correctness",
-"url": "about/changelog.html#-correctness-2",
-"text": "Strand-aware gene dictionary: combined_dict keys changed from (Start, End) to (Start, End, Strand): genes on opposite strands at the same coordinates no longer collide silently Consistent 1-based lengths: all length calculations now use End - Start + 1 (15 sites fixed) Robust ID cleaning: regex _\\d+-\\d+$ replaces brittle 4-part underscore rule, so PE_PGRS genes are handled correctly Exact gene name matching: prevents false matches (Rv3508 ≠ Rv3508c, Rv1 ≠ Rv10) IntervalTree half-open intervals: all 9 operations corrected from [Start:End] to [Start:End+1] Cross-strand gene preservation: merge o"
-},
-{
-"page": "Changelog",
-"section": "🛡️ Robustness",
-"url": "about/changelog.html#-robustness",
-"text": "Pre-flight validation: new VALIDATE_INPUTS process checks FASTA format, N-content, GC%, genome size, duplicate headers and database paths, before any computation --validate_only mode: check all inputs without executing the pipeline eggNOG DB integrity: bacteria.dmnd <100 MB detected as corrupted → automatic rebuild Bakta DB pre-check: actionable error message with download command OOM retry: memory scales with task.attempt (e.g., 50 GB → 100 GB → 150 GB) 0-protein handling: eggNOG batch handles empty inputs gracefully Completion handler: prints duration, succeeded/failed/ignored counts"
-},
-{
-"page": "Changelog",
-"section": "📋 Reproducibility",
-"url": "about/changelog.html#-reproducibility",
-"text": "nextflow.manifest: version 1.0.0, Nextflow ≥23.04.0, DOI, homepage SOFTWARE_VERSIONS: YAML + TSV with exact versions of 12 tools DUMP_PARAMS: complete parameter dump per run Diamond sorted output: deterministic results regardless of thread count environment.yml: Conda environment with pinned versions Louvain seed: fixed to 42 for reproducible community detection"
-},
-{
-"page": "Changelog",
-"section": "⚡ Performance & HPC",
-"url": "about/changelog.html#-performance--hpc",
-"text": "Dynamic resource scaling: memory grows with each retry attempt Node-local scratch: --scratch true (default on SLURM/SGE/PBS/LSF) Diamond block size tuning: auto-calculated from available RAM Bakta local temp: --tmp-dir on node-local disk (avoids NFS metadata storms) eggNOG batch mode: single run for all samples (10× faster, 50× less filesystem I/O) Thread utilization: Bakta 12, Diamond 6-24, Miniprot 2×3, Pangene 2×N SLURM/SGE/PBS/LSF profiles: with time limits, queue sizes, rate limiting storeDir for eggNOG DB: persists across runs, survives nextflow clean"
-},
-{
-"page": "Changelog",
-"section": "📐 Scalability",
-"url": "about/changelog.html#-scalability",
-"text": "find -print0 | xargs -0: replaces echo|tr|grep in REPORT_INPUTS (handles 500+ files) publishDir mode: 'link': hardlinks instead of copies for per-sample outputs cache 'lenient': tolerates NFS timestamp drift between cluster nodes awk close(): prevents file handle exhaustion on 500+ sample eggNOG splits JVM tuning docs: NXF_OPTS guidance for large runs"
-},
-{
-"page": "Changelog",
-"section": "🧬 Organism-Agnostic",
-"url": "about/changelog.html#-organism-agnostic",
-"text": "Custom reference: --ref_annotation + --ref_genome replaces built-in H37Rv Auto-detect mode: --skip_mycobrowser auto-enabled when genus ≠ Mycobacterium Dynamic reference comparison: report uses median distribution (not hardcoded H37Rv) Auto-extending color palette: non-MTBC lineages get colors automatically Generic labels: \"Ratio to Median\" instead of \"Ratio to H37Rv\" Manifest updated: \"Assisted Microbial Annotator Pipeline\""
-},
-{
-"page": "Changelog",
-"section": "📊 Output Quality",
-"url": "about/changelog.html#-output-quality",
-"text": "Clean proteome: {sample}-AMAP.clean.faa without pseudogenes or internal stops Annotation QC: per-sample metrics (genome size, GC%, coding density, hypothetical %, annotation rate) GFF3 spec compliance: ##sequence-region pragmas, attribute escaping, sorted features QC report section: interactive table + coding density / hypothetical % / genome size plots"
-},
-{
-"page": "Changelog",
-"section": "🧪 Code Quality",
-"url": "about/changelog.html#-code-quality",
-"text": "58 unit tests: covering IDs, overlaps, reconciliation, validation, clean proteome, QC, merge GitHub Actions CI: Python 3.9 + 3.11 matrix (currently disabled, ready to activate) Type hints: core modules with Coord, Coord3, CoordDict aliases Module docstrings: coordinate conventions, naming schemes documented"
-},
-{
-"page": "Changelog",
-"section": "🖥️ Usability",
-"url": "about/changelog.html#-usability",
-"text": "Structured startup banner: shows all configuration in a formatted box --version flag: prints pipeline + Nextflow version Progress logging: ▸ [BAKTA] Annotating G26689... for every major step Actionable error messages: boxed errors with cause + fix + re-run command Sample count at startup: confirms TSV was parsed correctly"
-},
-{
-"page": "Changelog",
-"section": "📝 Documentation",
-"url": "about/changelog.html#-documentation-1",
-"text": "README: updated for v1.0.0 with all new parameters, outputs, and organism-agnostic usage CHANGELOG: this file Output tree: documents .clean.faa, .qc.tsv, pipeline_info/ new files"
-},
-{
-"page": "Changelog",
-"section": "🐛 Bug Fixes (85 total)",
-"url": "about/changelog.html#-bug-fixes-85-total",
-"text": "20 critical (🔴) + 65 improvements (🟡) See commit history on fix branch for full details"
-},
-{
-"page": "Changelog",
-"section": "[0.1.0] - 2025",
-"url": "about/changelog.html#010---2025",
-"text": "Initial release MTBC-focused annotation pipeline Bakta + Miniprot + eggNOG integration HTML report with COG, Mycobrowser, pangenome analysis"
-},
-{
-"page": "Citation",
-"section": "",
-"url": "about/citation.html",
-"text": "Ruiz-Rodriguez, P., & Coscolla, M. (2026). AMAP (Version 1.0.1) [Computer software]. @software{Ruiz-Rodriguez_AMAP_2026, author = {Ruiz-Rodriguez, Paula and Coscolla, Mireia}, doi = {10.5281/zenodo.8024446}, license = {GPL-3.0}, month = apr, title = {{AMAP}}, url = {https://github.com/PathoGenOmics-Lab/AMAP}, version = {1.0.1}, year = {2026}} The repository also carries a CITATION.cff, which GitHub reads to offer a formatted citation from the sidebar."
-},
-{
-"page": "Citation",
-"section": "Authors",
-"url": "about/citation.html#authors",
-"text": "Paula Ruiz-Rodriguez and Mireia Coscolla I2SysBio, University of Valencia-CSIC, FISABIO Joint Research Unit Infection and Public Health, Valencia, Spain."
-},
-{
-"page": "Citation",
-"section": "Cite the tools too",
-"url": "about/citation.html#cite-the-tools-too",
-"text": "AMAP orchestrates other people's work. If a result depends on one of these, cite it as well. Nextflow Runs the pipeline Bakta Baseline annotation Miniprot Reference proteome alignment eggNOG-mapper Functional annotation Diamond Duplication searches pangene Pangenome graph pathotypr MTBC lineage typing The exact version of each one used by a given run is recorded in results/pipeline_info/software_versions.yml."
-},
-{
-"page": "Citation",
-"section": "Reference data",
-"url": "about/citation.html#reference-data",
-"text": "The MTBC reference files bundled in the container come from public repositories. The pathotypr lineage markers are archived on Zenodo under 10.5281/zenodo.19210044, and Mycobrowser functional categories come from mycobrowser.epfl.ch."
-},
-{
-"page": "Citation",
-"section": "Licence",
-"url": "about/citation.html#licence",
-"text": "AMAP is released under the GPL-3.0 licence. See LICENSE."
-},
-{
-"page": "Contributing",
-"section": "",
-"url": "about/contributing.html",
-"text": "Bug reports, fixes and suggestions are all welcome. The full guidelines live in CONTRIBUTING.md, and the project follows a code of conduct."
-},
-{
-"page": "Contributing",
-"section": "Reporting a bug",
-"url": "about/contributing.html#reporting-a-bug",
-"text": "Open an issue at github.com/PathoGenOmics-Lab/AMAP/issues with the command you ran, the failing task's .command.err, and both software_versions.yml and params.yml from pipeline_info/. Those last two pin the exact versions and parameters, which is usually what the answer turns on. Troubleshooting covers the common cases first."
-},
-{
-"page": "Contributing",
-"section": "Working on the code",
-"url": "about/contributing.html#working-on-the-code",
-"text": "git clone https://github.com/PathoGenOmics-Lab/AMAP cd AMAP pip install -r requirements-test.txt python -m pytest tests/ -v requirements-test.txt covers everything bin/ imports, not only what the tests import directly, and CI installs from the same file. A shorter list appears to work until a test touches classify_cases.py, which pulls in plotly through classify/plotting.py. The suite covers ID handling, overlap arithmetic, case classification, the rules that resolve and recover genes, and the parsers, and it runs in about a second. Anything touching bin/classify/ should come with a test, sinc"
-},
-{
-"page": "Contributing",
-"section": "Annotating H37Rv with itself",
-"url": "about/contributing.html#annotating-h37rv-with-itself",
-"text": "A change to the correction algorithm should also pass the self-test, which has exactly one right answer: annotating H37Rv against its own reference must give back every CDS of the AL123456.3 GenBank record, at exactly its coordinates and strand, under its own name. docker run --rm --platform linux/amd64 -v \"$PWD:/amap\" -w /amap \\ paururo/amap:latest bash tests/selftest/h37rv_selftest.sh GenBank CDS: 4031 AMAP genes: 4031 exact and named right: 4031 not at exactly their coordinates: 0 named wrong: 0 AMAP genes that are no GenBank CDS: 0 The first run spends a couple of minutes on "
-},
-{
-"page": "Contributing",
-"section": "Pipeline code and -resume",
-"url": "about/contributing.html#pipeline-code-and--resume",
-"text": "Nextflow hashes a task's inputs and script, not the files the script calls by path, so python3 ${projectDir}/bin/check_cds.py alone would let -resume hand back what the old check_cds.py produced. Every process that runs AMAP code therefore lists those files in ext code, and its script names task.ext.code in a comment, which puts them in the cache key (Nextflow 24.04 and later): ext code: { files(\"${projectDir}/bin/{check_cds.py,deduplicate_rvs.py,classify/*.py}\") } A script that imports classify needs classify/*.py in the list. The reverse also bites: Nextflow adds to a task's cache key any bi"
-},
-{
-"page": "Contributing",
-"section": "Working on the container",
-"url": "about/contributing.html#working-on-the-container",
-"text": "docker build --platform linux/amd64 -f docker/Dockerfile -t amap:dev . docker/smoke_test.sh amap:dev The smoke test checks the tools, the Python and R libraries, the bundled reference data, and runs the test suite with the image interpreter. Worth doing before proposing a change to the Dockerfile, since a library update can break the pipeline scripts without the image looking any different."
-},
-{
-"page": "Contributing",
-"section": "Working on these docs",
-"url": "about/contributing.html#working-on-these-docs",
-"text": "The website is plain HTML in docs/, with nothing to build on the server: GitHub Pages serves the folder as it is (docs/.nojekyll keeps it from running Jekyll over the markdown), and it opens from a clone too. The long pages are the markdown files, which GitHub also renders, and tools/build_docs.py writes each one's .html next to it, with the site's header, page list, contents and search. python3 tools/build_docs.py # after editing any .md under docs/ python3 tools/check_site.py # what CI runs python3 -m http.server -d docs 8000 # then open http://localhost:8000 The front pag"
-},
-{
-"page": "Contributing",
-"section": "Style",
-"url": "about/contributing.html#style",
-"text": "Keep to what is already there: short paragraphs, GitHub's alerts (> [!WARNING]) for the things that actually bite, and concrete numbers rather than \"large\" or \"slow\". Where a threshold appears in the docs it should match the constant in the code, so if you change one, change the other; check_site.py holds the ones it knows to the code."
-}
-];
+window.AMAP_DOCS_INDEX = [{"page":"Installation","section":"","url":"getting-started/installation.html","text":"AMAP needs two things on your machine: Nextflow, which runs the pipeline, and a container engine, which supplies every tool the pipeline calls. You do not install Bakta, Miniprot, eggNOG-mapper or any of the rest yourself; they all live inside the AMAP image. You also do not need to clone this repository. Nextflow pulls the pipeline straight from GitHub.","kind":"page"},{"page":"Installation","section":"1. Nextflow","url":"getting-started/installation.html#1-nextflow","text":"Nextflow needs Java 17 or newer, and Conda brings both: If you do not have Conda, Miniconda is a lightweight installer. Any other Nextflow installation works equally well, see the Nextflow documentation. AMAP requires Nextflow 24.04 or newer, which is enforced by the pipeline manifest.","code":"conda create -n nextflow-env activate install -c bioconda nextflow","kind":"section"},{"page":"Installation","section":"2. A container engine","url":"getting-started/installation.html#2-a-container-engine","text":"Pick whichever your machine allows. Docker Best on a laptop or workstation where you have root. Follow the Docker installation guide for your platform. Singularity / Apptainer The usual choice on an HPC cluster, where Docker is normally not permitted. Follow the Apptainer installation guide. Many clusters already provide it as a module:","code":"module load singularity or: apptainer","kind":"section"},{"page":"Installation","section":"3. Check the installation","url":"getting-started/installation.html#3-check-the-installation","text":"","code":"nextflow -version docker --version or: singularity","kind":"section"},{"page":"Installation","section":"4. Run the test profile","url":"getting-started/installation.html#4-run-the-test-profile","text":"Before pointing AMAP at real data, confirm the whole chain works. The test profile annotates the reference genome bundled in the container, so it needs no data of your own and nothing to download beyond the image: The first run downloads the AMAP image, which is several gigabytes because it bundles the Bakta database and the H37Rv reference files. Expect that to take a while. Later runs reuse the cached image. Check inputs before a long run Once you have your own samples, --validate_only checks every FASTA and database path without executing anything: See inputs for what it looks for.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker,test docker --tsv samples.tsv --validate_only","kind":"section"},{"page":"Installation","section":"Running without a container","url":"getting-started/installation.html#running-without-a-container","text":"Possible but not recommended: you take on installing Bakta, Miniprot, eggNOG-mapper, Diamond, pangene, pathotypr, R and the report packages yourself, and pointing AMAP at each reference file with the reference arguments. The repository ships an environment.yml that reproduces the software stack under Conda: You will still need to supply the Bakta database and the reference files, which the container would otherwise provide. See containers for what the image contains.","code":"conda env create -f environment.yml activate amap","kind":"section"},{"page":"Installation","section":"Next steps","url":"getting-started/installation.html#next-steps","text":"Quickstart: your first real run Preparing inputs: the sample sheet and what makes a genome pass validation","kind":"section"},{"page":"Installation","section":"Check inputs before a long run","url":"getting-started/installation.html#note-check-inputs-before-a-long-run","text":"Once you have your own samples, --validate_only checks every FASTA and database path without executing anything:","kind":"section"},{"page":"Quickstart","section":"","url":"getting-started/quickstart.html","text":"This walks from an installed Nextflow to an open report. If Nextflow or a container engine is still missing, start at installation.","kind":"page"},{"page":"Quickstart","section":"1. Write a sample sheet","url":"getting-started/quickstart.html#1-write-a-sample-sheet","text":"AMAP reads a tab-separated file with two columns, sampleId and fasta: The separator must be a real tab. Paths can be absolute, or relative to where you launch Nextflow. See preparing inputs for the details and the checks AMAP runs over them.","code":"sampleId fasta G26689 /data/genomes/G26689.fasta G26690 /data/genomes/G26690.fasta","kind":"section"},{"page":"Quickstart","section":"2. Check the inputs","url":"getting-started/quickstart.html#2-check-the-inputs","text":"Cheap, and it catches the mistakes that would otherwise surface twenty minutes into a run:","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --validate_only","kind":"section"},{"page":"Quickstart","section":"3. Run it","url":"getting-started/quickstart.html#3-run-it","text":"Pick what you have, and copy the command: Or start from one of these: Docker Singularity SLURM cluster Without a scheduler profile every task is capped at the machine's CPUs and 16 GB, and with the scheduler profiles at 48 CPUs and 128 GB, whichever order the profiles come in; a larger request, such as the 50 GB the eggNOG annotation asks for, is lowered to the cap. Nextflow refuses to start a task asking for more memory than the machine has, so on a smaller machine, lower the ceiling: The tutorial has the per-step figures and what to do when the machine cannot reach them.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv singularity singularity,slurm --max_cpus 4 --max_memory 12.GB","kind":"section"},{"page":"Quickstart","section":"4. Read the results","url":"getting-started/quickstart.html#4-read-the-results","text":"Everything lands under results/ unless you set --outdir. The three things to open first: The full interactive report results/final_report.html Corrected annotation, per sample results/AMAP/{sampleId}/*-AMAP.gff3 Protein sequences for downstream tools results/AMAP/{sampleId}/*-AMAP.clean.faa Which protein file do I want? *-AMAP.faa holds every gene, pseudogenes included, so it mirrors the annotation exactly. *-AMAP.clean.faa drops pseudogenes and sequences with internal stop codons, which is what orthology, phylogenomics and dN/dS tools expect. Reach for the clean one unless you specifically need the complete set. The outputs page covers every directory.","kind":"section"},{"page":"Quickstart","section":"5. Resume an interrupted run","url":"getting-started/quickstart.html#5-resume-an-interrupted-run","text":"Nextflow caches completed tasks. If a run dies, restart it with -resume and it picks up where it stopped: Keep the same parameters. Changing an input invalidates the cache for every task downstream of it, which is what you want, but it does mean the run redoes that work.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv -resume","kind":"section"},{"page":"Quickstart","section":"Common variations","url":"getting-started/quickstart.html#common-variations","text":"A non-MTBC organism. Lineage typing and Mycobrowser are specific to tuberculosis, so turn them off and supply your own reference: See custom reference. Just the corrected genes. If you do not need functional annotation, duplications, the pangenome or the report, stop after the correction step. It is much faster, and leaves the corrected genes as a table with their proteins and sequences; the GFF3 and GenBank files come after eggNOG, so there are none:","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --skip_typing --skip_mycobrowser --genus Escherichia --species \"Escherichia coli\" --ref_annotation my_reference.gbk --ref_genome my_reference.fasta --skip_functional_annotation","kind":"section"},{"page":"Quickstart","section":"Next steps","url":"getting-started/quickstart.html#next-steps","text":"Running AMAP: profiles, schedulers and larger runs Parameters: the full reference How it works: what the correction actually does","kind":"section"},{"page":"Quickstart","section":"Which protein file do I want?","url":"getting-started/quickstart.html#note-which-protein-file-do-i-want","text":"*-AMAP.faa holds every gene, pseudogenes included, so it mirrors the annotation exactly.","kind":"section"},{"page":"Tutorial","section":"","url":"getting-started/tutorial.html","text":"Every command spelled out, nothing assumed. It uses a real genome you can download in one line, so you can follow along and compare your numbers against the ones printed here. Those numbers come from actual runs of these commands, not from memory, made on 18 October 2026 with AMAP at commit 947a99d. The runs skipped eggNOG, with --skip_functional_annotation. None of the numbers depends on it, and the one table shown here that is written after it was made by hand from the same run, as the sample check explains. The genome is Mycobacterium tuberculosis CDC1551, corrected against the H37Rv reference that ships inside the AMAP container. At the end there is a second pass showing the same thing for a non-MTBC organism. The correction itself takes minutes on one genome. What dominates the first run is setup: pulling a 12 GB image and building the eggNOG database, both of which happen once and are reused afterwards. Expect the first run to take considerably longer than every run after it, and do not read a long silence during the database build as a failure.","kind":"page"},{"page":"Tutorial","section":"1. What you need","url":"getting-started/tutorial.html#1-what-you-need","text":"Nextflow and a container engine. Every tool the pipeline itself calls lives inside the AMAP image, so there is nothing else to install. The easiest way to get Nextflow is Conda. If you do not have it, Miniforge is the one to install: Nextflow needs Java 17 or newer. Check what you have: Java 8 is not enough Nextflow will refuse to start on Java 8 with a message about an unsupported class file version. Install a newer JDK, for example conda install -c conda-forge openjdk=17. Then a container engine. Docker on a laptop, Singularity or Apptainer on a cluster: Full details, including running without a container, are on the installation page. On an Apple Silicon Mac, read this first The AMAP image is linux/amd64 only, so on an M-series Mac every step runs under emulation. The test profile in step 2 is fine. A real genome is slow enough that a working run can look like a hung one, which is the part worth knowing before you start wondering what you broke. It is a reasonable way to learn what the pipeline does and to check your sample sheet. For results you intend to use, move to an amd64 machine or a cluster.","code":"conda create -n nextflow-env -c conda-forge bioconda nextflow activate java -version docker --version or: singularity","kind":"section"},{"page":"Tutorial","section":"The machine","url":"getting-started/tutorial.html#the-machine","text":"Worth checking before you start, because this is what stops a run rather than anything you will have got wrong. Disk goes first. The container is 12 GB unpacked. The eggNOG database is built on first use and cached: AMAP skips the 60 GB Diamond core that eggNOG-mapper would otherwise fetch and builds a bacteria-only index of about 3 GB instead, on top of the metadata it downloads. Nextflow keeps every intermediate under work/, which for one genome is a few GB and grows with the sample sheet. Memory decides whether the run finishes at all. Two steps are heavy: eggNOG annotation 24 50 GB eggNOG database build 16 45 GB Pangenome 16 16 GB Duplication analysis 6 to 13 10 GB Report 1 8 GB Bakta annotation 12 6 GB Everything else 1 to 8 6 GB or less Nextflow will not start a task that asks for more memory than the machine has, so on a laptop these requests are the thing that fails, not your data. Lower the ceiling and every request is clamped to it: eggNOG will be slower and may still run out on a genuinely small machine. The alternative is to skip functional annotation altogether: You still get the corrected genes, which is what AMAP exists to produce, as a table with their proteins and sequences, and amap_report.html. What you lose is eggNOG categories, the GFF3 and GenBank files written after it, the pangenome, the duplication analysis and final_report.html. The heaviest step left asks for 6 GB. Docker Desktop has its own memory limit, and it is the one that counts On macOS and Windows, Docker runs your containers inside a virtual machine with a memory ceiling of its own, often 8 GB by default no matter how much the machine has. Nextflow sizes its requests against the host, so it will happily start a 12 GB task that the VM then kills. You get exit code 137 and nothing that mentions Docker. Raising --max_memory makes this worse, not better. Raise the VM instead, in Docker Desktop under Settings, Resources, Memory, and keep --max_memory at or below what you set there. Linux has no such layer. Containers use host memory directly and this does not apply.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --max_cpus 4 --max_memory 12.GB --skip_functional_annotation","kind":"section"},{"page":"Tutorial","section":"2. Check the plumbing before touching real data","url":"getting-started/tutorial.html#2-check-the-plumbing-before-touching-real-data","text":"The test profile annotates the reference genome that ships inside the container, so it needs no data of your own: The first run pulls the AMAP image. It is around 3.4 GB compressed and 12 GB on disk, because it carries the Bakta database and the H37Rv reference files. That is a one-off; later runs reuse it. If this finishes without an error, the whole chain works.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker,test","kind":"section"},{"page":"Tutorial","section":"3. Get a genome","url":"getting-started/tutorial.html#3-get-a-genome","text":"Make a working directory and fetch an assembly: Have a look at what you got: One closed contig, 4,403,837 bp. AMAP takes assembled genomes, so reads are not an input here.","code":"mkdir amap-tutorial cd NCBI=https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF ASM=000/008/585/GCF_000008585.1_ASM858v1/GCF_000008585.1_ASM858v1_genomic curl -sL -o cdc1551.fna.gz \"$NCBI/$ASM.fna.gz\" gunzip grep cdc1551.fna >NC_002755.2 Mycobacterium tuberculosis CDC1551, complete sequence","kind":"section"},{"page":"Tutorial","section":"4. Write the sample sheet","url":"getting-started/tutorial.html#4-write-the-sample-sheet","text":"A tab-separated file with exactly two columns: It has to be a real tab Spaces will not do, and this is the single most common reason a sample sheet is rejected. Check with cat -t samples.tsv: real tabs show up as ^I. (cat -A does the same on Linux but is not accepted by the cat that ships with macOS.) Keep sample IDs to letters, digits and underscores. Several scripts cut the name at the first dot, so G26689.1 becomes G26689.","code":"printf 'sampleId\\tfasta\\nCDC1551\\t%s/cdc1551.fna\\n' \"$PWD\" samples.tsv cat sampleId fasta CDC1551 /path/to/amap-tutorial/cdc1551.fna","kind":"section"},{"page":"Tutorial","section":"5. Validate before committing to a long run","url":"getting-started/tutorial.html#5-validate-before-committing-to-a-long-run","text":"The checks write their verdict to results/pipeline_info/validation.log, which ends: This costs seconds and catches the mistakes that would otherwise surface minutes in. Preparing inputs lists every check and its threshold.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --validate_only tail -n 8 results/pipeline_info/validation.log FASTA Validation (1 samples) Results: 1 OK, 0 warnings, failed Total: error(s), warning(s) PASSED all samples look good","kind":"section"},{"page":"Tutorial","section":"6. Run it","url":"getting-started/tutorial.html#6-run-it","text":"What happens, in order: lineage typing, Bakta annotation, alignment of the H37Rv proteome onto your genome, case classification, correction, eggNOG functional annotation, duplication analysis, pangenome, report. The first run also downloads and builds the eggNOG database, which is several gigabytes and dominates the wall clock. Point --eggnog_db at somewhere persistent so the next run reuses it: If the run dies partway, -resume picks up from the last completed step.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --eggnog_db ~/databases/eggnog","kind":"section"},{"page":"Tutorial","section":"7. Look at what came out","url":"getting-started/tutorial.html#7-look-at-what-came-out","text":"A run without eggNOG, like the ones behind this page, has no DUPLICATIONS/, PANGENE/ or final_report.html. Open results/final_report.html first. It is self-contained, so it travels by email. results/amap_report.html goes through the same run question by question, and a run without eggNOG still gets it.","code":"ls -F results/ AMAP/ BAKTA/ CDS_CASES/ DUPLICATIONS/ PANGENE/ PATHOTYPR/ amap_report.html final_report.html pipeline_info/","kind":"section"},{"page":"Tutorial","section":"Did the correction do anything?","url":"getting-started/tutorial.html#did-the-correction-do-anything","text":"Read that as: of 4077 Bakta calls, 2813 already agreed with the reference, 1100 had to be adjudicated, and 97 reference genes Bakta had missed were rescued. Only 11 of the 141 apparently novel genes survived scrutiny, which is what you would expect for a strain of the same species as the reference.","code":"column -t -s$'\\t' results/CDS_CASES/summary/*-summary.tsv Type Number case2 2813 coordinates matched the reference exactly case3 24 matched, alignment length differed case4 1100 overlapped but disagreed, resolved case1_initial 141 Bakta called it, has nothing there rv_missing 97 a gene there, missed it total_cds 4077 what before correction case1_removed 130 \"novel\" genes that were not really novel case1_kept 11 kept as genuinely absent from H37Rv","kind":"section"},{"page":"Tutorial","section":"Sanity check the sample","url":"getting-started/tutorial.html#sanity-check-the-sample","text":"Six of its eleven columns: 65.6% GC and 91.3% coding density are what a healthy MTBC genome looks like. A number well outside that is worth investigating before trusting the rest. This table is written after eggNOG, so a run without it has none. The line above was made by running that step's own script, GET_OUTPUTS, on the corrected genes of the run behind this page, with an eggNOG table that has no rows. None of the six columns depends on eggNOG; the one that does, eggnog_annotation_pct, is left out.","code":"column -t -s$'\\t' results/AMAP/*/*-AMAP.qc.tsv genome_size n_contigs gc_pct total_cds pseudogene_count coding_density_pct 4403837 1 65.61 4014 123 91.3","kind":"section"},{"page":"Tutorial","section":"The files you will actually use","url":"getting-started/tutorial.html#the-files-you-will-actually-use","text":"Lineage typing puts the lineage in front of the sample ID, so CDC1551 comes out as L4_CDC1551: Corrected annotation results/AMAP/L4_CDC1551/L4_CDC1551-AMAP.gff3 Same, GenBank results/AMAP/L4_CDC1551/L4_CDC1551-AMAP.gbk Proteins for downstream tools results/AMAP/L4_CDC1551/L4_CDC1551-AMAP.clean.faa Everything known per gene results/AMAP/L4_CDC1551/L4_CDC1551-AMAPmastertable.tsv Tool versions for your methods results/pipeline_info/software_versions.yml Two protein files, and they differ on purpose *-AMAP.faa has one entry per annotated gene, pseudogenes included, so it matches the GFF3 gene for gene. *-AMAP.clean.faa drops pseudogenes and anything with an internal stop codon, which is what orthology, phylogenomics and dN/dS tools expect. It is legitimately shorter. Every directory is documented under outputs.","kind":"section"},{"page":"Tutorial","section":"8. More than one genome","url":"getting-started/tutorial.html#8-more-than-one-genome","text":"Add rows. The pipeline parallelises across samples, and the pangenome and the cross-genome duplication analysis only become meaningful with several: Past a couple of dozen, move to a cluster: Deployment on HPC covers scratch, shared filesystems and where things should live.","code":"sampleId fasta CDC1551 /data/cdc1551.fna Erdman /data/erdman.fna Beijing_1 /data/beijing_1.fna nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv --eggnog_db /shared/databases/eggnog","kind":"section"},{"page":"Tutorial","section":"9. The same thing for another organism","url":"getting-started/tutorial.html#9-the-same-thing-for-another-organism","text":"Everything above assumed MTBC. For anything else you supply the trusted reference yourself, and turn off the two MTBC-specific steps. Fetch a reference and a sample. Here, E. coli O157:H7 Sakai corrected against K-12 MG1655: --skip_typing because lineage typing is specific to tuberculosis, and --skip_mycobrowser because those functional categories are an MTBC resource. Before annotating anything, AMAP extracts the proteome from your reference, annotates that same reference genome with Bakta, and takes the difference to work out which genes Bakta invents. For MTBC that list was curated by hand; here it is derived automatically. On this pair it found 142. The case counts come out differently from the MTBC run, and the difference is biology rather than a problem: Those 1387 are the prophages and the LEE island that O157:H7 carries and K-12 lacks. Against a reference of your own species you should expect a large case 1 when the strains genuinely differ, and a small one when they do not. The annotation and the genome must be the same assembly Coordinates from the annotation are used directly against the FASTA. An annotation from a different assembly produces nonsense: proteins that do not translate, and a nearly empty reference proteome. AMAP writes how many proteins it extracted into the work directory, where grep -h 'Curated proteins' work///.command.out finds it (4318 on this pair), so check the number looks like a genome's worth. Custom reference goes into what makes a good reference and what changes when you use one.","code":"NCBI=https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF K12=000/005/845/GCF_000005845.2_ASM584v2/GCF_000005845.2_ASM584v2_genomic SAKAI=000/008/865/GCF_000008865.2_ASM886v2/GCF_000008865.2_ASM886v2_genomic the reference: annotation plus genome it describes curl -sL -o ref.gbk.gz \"$NCBI/$K12.gbff.gz\" ref.fna.gz \"$NCBI/$K12.fna.gz\" sample sakai.fna.gz \"$NCBI/$SAKAI.fna.gz\" gunzip printf 'sampleId\\tfasta\\nSakai\\t%s/sakai.fna\\n' \"$PWD\" ecoli.tsv nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv --skip_typing --skip_mycobrowser --genus Escherichia --species \"Escherichia coli\" --ref_annotation ref.gbk --ref_genome ref.fna case2 3324 case3 31 case4 507 case1_kept 1387 genes O157:H7 has and K-12 does not rv_missing 133","kind":"section"},{"page":"Tutorial","section":"When something goes wrong","url":"getting-started/tutorial.html#when-something-goes-wrong","text":"Nextflow prints the work directory of the failed task. Everything you need is in it: Troubleshooting is organised by the error you are looking at.","code":"work/ab/cd1234.../ .command.sh the exact script that ran .command.err stderr on its own, almost always answer .exitcode","kind":"section"},{"page":"Tutorial","section":"Where to go next","url":"getting-started/tutorial.html#where-to-go-next","text":"Parameters: every flag and its default How it works: what the correction actually does, and the thresholds behind it Outputs: every file the run produces","kind":"section"},{"page":"Tutorial","section":"Java 8 is not enough","url":"getting-started/tutorial.html#note-java-8-is-not-enough","text":"Nextflow will refuse to start on Java 8 with a message about an unsupported class file version.","kind":"section"},{"page":"Tutorial","section":"On an Apple Silicon Mac, read this first","url":"getting-started/tutorial.html#note-on-an-apple-silicon-mac-read-this-first","text":"The AMAP image is linux/amd64 only, so on an M-series Mac every step runs under emulation.","kind":"section"},{"page":"Tutorial","section":"Docker Desktop has its own memory limit, and it is the one that counts","url":"getting-started/tutorial.html#note-docker-desktop-has-its-own-memory-limit-and-it-is-the-one-that-counts","text":"On macOS and Windows, Docker runs your containers inside a virtual machine with a memory ceiling of its own, often 8 GB by default no matter how much the...","kind":"section"},{"page":"Tutorial","section":"It has to be a real tab","url":"getting-started/tutorial.html#note-it-has-to-be-a-real-tab","text":"Spaces will not do, and this is the single most common reason a sample sheet is rejected.","kind":"section"},{"page":"Tutorial","section":"Two protein files, and they differ on purpose","url":"getting-started/tutorial.html#note-two-protein-files-and-they-differ-on-purpose","text":"*-AMAP.faa has one entry per annotated gene, pseudogenes included, so it matches the GFF3 gene for gene.","kind":"section"},{"page":"Tutorial","section":"The annotation and the genome must be the same assembly","url":"getting-started/tutorial.html#note-the-annotation-and-the-genome-must-be-the-same-assembly","text":"Coordinates from the annotation are used directly against the FASTA.","kind":"section"},{"page":"Preparing inputs","section":"","url":"getting-started/inputs.html","text":"AMAP takes assembled genomes. It does not do assembly, so reads are not an input here.","kind":"page"},{"page":"Preparing inputs","section":"The sample sheet","url":"getting-started/inputs.html#the-sample-sheet","text":"A tab-separated file with a header row and exactly two columns: sampleId: The name carried through every output directory and file. Must be unique. Lineage typing puts the lineage in front, so G26689 comes out as L4_G26689. fasta: Path to the assembled genome. Absolute, or relative to the directory you launch Nextflow from. The separator has to be an actual tab. Spaces will not do, and this is the most common reason a sample sheet is rejected. Avoid dots in sample IDs Several scripts derive the sample name by cutting at the first dot, so G26689.1 becomes G26689 and two such samples would collide. Stick to letters, digits and underscores.","code":"sampleId fasta G26689 /data/genomes/G26689.fasta G26690 /data/genomes/G26690.fasta L4_G12345 ./assemblies/L4_G12345.fa","kind":"section"},{"page":"Preparing inputs","section":"Pre-flight validation","url":"getting-started/inputs.html#pre-flight-validation","text":"VALIDATE_INPUTS runs before any computation, on every run. To run only the checks and stop: Results are written to results/pipeline_info/validation.log.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --validate_only","kind":"section"},{"page":"Preparing inputs","section":"What fails the run","url":"getting-started/inputs.html#what-fails-the-run","text":"Genome size below 100 kb, or above 20 Mb N content above 10% GC content below 20% or above 80% Empty sequences any record with no bases Duplicate contig headers any repeated identifier Sample sheet missing sampleId or fasta column, no data rows, duplicate or empty sample IDs FASTA path file does not exist or cannot be parsed Bakta database not found, or no version.json or bakta.db in it eggNOG database eggnog_db.ok in it while the database is incomplete, since Nextflow then never builds it again Reference files --ref_annotation, --ref_genome or a reference argument that the run reads but that is neither on the host nor inside the container","kind":"section"},{"page":"Preparing inputs","section":"What only warns","url":"getting-started/inputs.html#what-only-warns","text":"Genome size below 500 kb, or above 10 Mb N content above 1% GC content below 25% or above 75% Assembly fragmentation more than 100 contigs, and again above 500 Short contigs any contig under 200 bp in a multi-contig assembly Non-standard characters anything outside ATGCNRYSWKMBDHV eggNOG database a bacteria.dmnd under 100 MB, which EGGNOG_DB takes for a truncated download and builds again Mycobrowser table --mycobrowser_ref neither on the host nor inside the container, so the master table goes without Mycobrowser categories Warnings do not stop a run. Add --strict_validation to have them stop it too. An eggNOG database that does not exist yet is neither: the first run that annotates builds it at --eggnog_db. The thresholds are deliberately wide, since AMAP is not restricted to tuberculosis. A 4.4 Mb MTBC genome at 65% GC sits comfortably inside them, and so does most of the bacterial world.","kind":"section"},{"page":"Preparing inputs","section":"Assembly quality","url":"getting-started/inputs.html#assembly-quality","text":"The correction compares your annotation against a reference proteome, so assembly quality propagates directly into the result. Contiguity matters. A gene broken across two contigs cannot be recovered. The more fragmented the assembly, the more reference genes turn up as missing rather than corrected. Ns break coding sequences. A run of Ns inside a gene stops it translating, so it is dropped or flagged as a pseudogene. The 1% warning is worth taking seriously. Contig names are kept AMAP runs Bakta with --keep-contig-headers, so the sequence names you supplied are the ones in BAKTA/ and in the corrected annotation, and the corrected tables say which contig each gene is on. Without that option Bakta relabels everything contig_1, contig_2 and so on, and classify_cases.py stops with an error on a multi-contig assembly rather than compare genes across contigs. Closed genomes are read as circular from their header Bakta takes a sequence for a complete, circular replicon when the description after its name says so: complete, as NCBI's \"complete genome\" and \"complete sequence\" do, or circular=true, as Unicycler's does. It then joins the two ends, and a gene across the origin comes out whole. Lineage typing renames a genome of one sequence after its sample and keeps that description. A closed genome whose header says neither is annotated as linear, and a gene across its origin is cut in two there; add circular=true after its name to have it read as circular. Draft assemblies Every contig of a draft assembly is numbered from 1, so the same coordinates turn up on several contigs. The correction keys every gene on its contig as well as its start, end and strand, only ever compares genes on the same contig, and translates each one from its own contig. Cut into 40 contigs, CDC1551 gets every gene that lies within one contig exactly as the closed genome does.","kind":"section"},{"page":"Preparing inputs","section":"Reference data","url":"getting-started/inputs.html#reference-data","text":"For MTBC you need nothing else. The container bundles the H37Rv proteome, the Bakta light database, the Mycobrowser table and the pathotypr lineage markers. See containers for the full list. For any other organism, supply your own reference with --ref_annotation and --ref_genome. See custom reference.","kind":"section"},{"page":"Preparing inputs","section":"Next steps","url":"getting-started/inputs.html#next-steps","text":"Quickstart: run it Parameters: the full flag reference","kind":"section"},{"page":"Preparing inputs","section":"Avoid dots in sample IDs","url":"getting-started/inputs.html#note-avoid-dots-in-sample-ids","text":"Several scripts derive the sample name by cutting at the first dot, so G26689.1 becomes G26689 and two such samples would collide.","kind":"section"},{"page":"Preparing inputs","section":"Contig names are kept","url":"getting-started/inputs.html#note-contig-names-are-kept","text":"AMAP runs Bakta with --keep-contig-headers, so the sequence names you supplied are the ones in BAKTA/ and in the corrected annotation, and the corrected tables...","kind":"section"},{"page":"Preparing inputs","section":"Closed genomes are read as circular from their header","url":"getting-started/inputs.html#note-closed-genomes-are-read-as-circular-from-their-header","text":"Bakta takes a sequence for a complete, circular replicon when the description after its name says so:","kind":"section"},{"page":"Preparing inputs","section":"Draft assemblies","url":"getting-started/inputs.html#note-draft-assemblies","text":"Every contig of a draft assembly is numbered from 1, so the same coordinates turn up on several contigs.","kind":"section"},{"page":"Parameters","section":"","url":"user-guide/parameters.html","text":"Every AMAP parameter, what it defaults to, and when you would change it. Run nextflow run PathoGenOmics-Lab/AMAP --help for the same list at the terminal. Pipeline parameters take two dashes (--tsv). Nextflow's own options take one (-profile, -resume).","kind":"page"},{"page":"Parameters","section":"Input and output","url":"user-guide/parameters.html#input-and-output","text":"","kind":"section"},{"page":"Parameters","section":"Skip options","url":"user-guide/parameters.html#skip-options","text":"Their effect on the output tree is covered under outputs.","kind":"section"},{"page":"Parameters","section":"Annotation","url":"user-guide/parameters.html#annotation","text":"Bakta uses these to pick naming conventions and reference sets, so setting them correctly improves the baseline annotation that AMAP then corrects.","kind":"section"},{"page":"Parameters","section":"Resources","url":"user-guide/parameters.html#resources","text":"These are ceilings, not requests. Each process asks for what it needs and is then clamped to these values, so lowering them makes AMAP fit a smaller machine rather than making it run faster. The defaults are a single machine's. The HPC profiles raise them to 48 CPUs and 128 GB, -profile highmem to 48 CPUs, 256 GB and 120 h, and -profile test sets 4 CPUs, 8 GB and 1 h, in whatever order -profile names them. Memory scales on retry: a process killed by the out-of-memory killer is resubmitted with more, up to the ceiling.","kind":"section"},{"page":"Parameters","section":"Validation and information","url":"user-guide/parameters.html#validation-and-information","text":"","kind":"section"},{"page":"Parameters","section":"Custom reference","url":"user-guide/parameters.html#custom-reference","text":"Supply both to annotate any prokaryote against your own trusted annotation. They must be given together; setting only one is an error. See custom reference for what AMAP does with them.","kind":"section"},{"page":"Parameters","section":"Reference and database arguments","url":"user-guide/parameters.html#reference-and-database-arguments","text":"You almost certainly do not need these Every file below ships inside the Docker and Singularity images, under /reference/. Running with -profile docker or -profile singularity picks them up automatically. They exist for running without a container, for swapping in updated reference files, or for adapting the pipeline by hand. A database or a file of your own on the host, the full Bakta database or a newer Mycobrowser table for instance, is mounted into the tasks that use it, so it works with the container profiles too. A path that is not on the host is read inside the container, where the defaults are, and validation stops a run whose file is in neither place. The eggNOG database is the one exception: it is not bundled, because of its size. AMAP downloads and builds it on the first run and caches it at --eggnog_db, so point that at somewhere persistent and shared if you run on a cluster.","kind":"section"},{"page":"Parameters","section":"Nextflow options worth knowing","url":"user-guide/parameters.html#nextflow-options-worth-knowing","text":"These belong to Nextflow rather than AMAP, so they take a single dash. -profile Selects execution profiles, comma separated: docker, singularity, slurm, sge, pbs, test. -resume Reuse cached results from the previous run. -work-dir Where intermediate files go. Defaults to ./work, and it gets large. -process.queue Override the scheduler queue or partition. -process.clusterOptions Extra flags passed to the scheduler, such as '--account=mygroup'. See running AMAP for how the profiles combine.","kind":"section"},{"page":"Parameters","section":"You almost certainly do not need these","url":"user-guide/parameters.html#note-you-almost-certainly-do-not-need-these","text":"Every file below ships inside the Docker and Singularity images, under /reference/.","kind":"section"},{"page":"Parameters","section":"--tsv","url":"user-guide/parameters.html#opt-tsv","text":"Sample sheet with sampleId and fasta columns, tab separated. Not needed with -profile test. Type: file. Default: samples.tsv. Required: Yes. In Input and output.","kind":"option"},{"page":"Parameters","section":"--outdir","url":"user-guide/parameters.html#opt-outdir","text":"Directory for results. Type: string. Default: results. Required: No. In Input and output.","kind":"option"},{"page":"Parameters","section":"--skip_typing","url":"user-guide/parameters.html#opt-skip_typing","text":"Skip pathotypr lineage typing. Required for non-MTBC organisms. Type: boolean. Default: false. In Skip options.","kind":"option"},{"page":"Parameters","section":"--skip_functional_annotation","url":"user-guide/parameters.html#opt-skip_functional_annotation","text":"Stop after annotation correction. Skips eggNOG, duplications, pangenome and the report. Type: boolean. Default: false. In Skip options.","kind":"option"},{"page":"Parameters","section":"--skip_mycobrowser","url":"user-guide/parameters.html#opt-skip_mycobrowser","text":"Skip Mycobrowser functional categories. Auto-enabled when --genus is not Mycobacterium and a custom reference is set. Type: boolean. Default: false. In Skip options.","kind":"option"},{"page":"Parameters","section":"--genus","url":"user-guide/parameters.html#opt-genus","text":"Genus passed to Bakta. Type: string. Default: \"Mycobacterium\". In Annotation.","kind":"option"},{"page":"Parameters","section":"--species","url":"user-guide/parameters.html#opt-species","text":"Species passed to Bakta. Type: string. Default: \"Mycobacterium tuberculosis\". In Annotation.","kind":"option"},{"page":"Parameters","section":"--max_cpus","url":"user-guide/parameters.html#opt-max_cpus","text":"Ceiling on CPUs for any single process. Type: integer. Default: the machine's CPUs. In Resources.","kind":"option"},{"page":"Parameters","section":"--max_memory","url":"user-guide/parameters.html#opt-max_memory","text":"Ceiling on memory for any single process. Type: string. Default: 16.GB. In Resources.","kind":"option"},{"page":"Parameters","section":"--max_time","url":"user-guide/parameters.html#opt-max_time","text":"Ceiling on wall-clock time for any single process. Type: string. Default: 72.h. In Resources.","kind":"option"},{"page":"Parameters","section":"--scratch","url":"user-guide/parameters.html#opt-scratch","text":"Use node-local scratch for I/O-heavy processes. On by default in the HPC profiles. Type: boolean. Default: false. In Resources.","kind":"option"},{"page":"Parameters","section":"--validate_only","url":"user-guide/parameters.html#opt-validate_only","text":"Run input validation and stop. Type: boolean. Default: false. In Validation and information.","kind":"option"},{"page":"Parameters","section":"--strict_validation","url":"user-guide/parameters.html#opt-strict_validation","text":"Stop on validation warnings too, not only on errors. Type: boolean. Default: false. In Validation and information.","kind":"option"},{"page":"Parameters","section":"--help","url":"user-guide/parameters.html#opt-help","text":"Print the help text and exit. Type: boolean. Default: false. In Validation and information.","kind":"option"},{"page":"Parameters","section":"--version","url":"user-guide/parameters.html#opt-version","text":"Print pipeline and Nextflow versions and exit. Type: boolean. Default: false. In Validation and information.","kind":"option"},{"page":"Parameters","section":"--ref_annotation","url":"user-guide/parameters.html#opt-ref_annotation","text":"Trusted reference annotation, GFF3 or GenBank. Type: file. Default: null. In Custom reference.","kind":"option"},{"page":"Parameters","section":"--ref_genome","url":"user-guide/parameters.html#opt-ref_genome","text":"Trusted reference genome, FASTA. Type: file. Default: null. In Custom reference.","kind":"option"},{"page":"Parameters","section":"--bakta_db","url":"user-guide/parameters.html#opt-bakta_db","text":"Bakta database directory, inside the container or on the host. Default inside the container: /data/bakta_db/db-light. In Reference and database arguments.","kind":"option"},{"page":"Parameters","section":"--eggnog_db","url":"user-guide/parameters.html#opt-eggnog_db","text":"Pre-formatted eggNOG database directory. Default inside the container: eggnog_db. In Reference and database arguments.","kind":"option"},{"page":"Parameters","section":"--pathotyper_snps","url":"user-guide/parameters.html#opt-pathotyper_snps","text":"Lineage marker SNPs for pathotypr. Default inside the container: /reference/snps_lineages.tsv. In Reference and database arguments.","kind":"option"},{"page":"Parameters","section":"--pathotyper_ref","url":"user-guide/parameters.html#opt-pathotyper_ref","text":"Reference FASTA for pathotypr. Default inside the container: /reference/MTB_ancestor_reference.fasta. In Reference and database arguments.","kind":"option"},{"page":"Parameters","section":"--miniprot_ref_aa","url":"user-guide/parameters.html#opt-miniprot_ref_aa","text":"Reference proteome aligned onto each genome. Default inside the container: /reference/aa_H37Rv.translated.fas. In Reference and database arguments.","kind":"option"},{"page":"Parameters","section":"--miniprot_ref_bad_bakta","url":"user-guide/parameters.html#opt-miniprot_ref_bad_bakta","text":"Known problematic annotations, used to filter novel-gene candidates. Default inside the container: /reference/bad.bakta.translated.fas. In Reference and database arguments.","kind":"option"},{"page":"Parameters","section":"--miniprot_ref_h37rv","url":"user-guide/parameters.html#opt-miniprot_ref_h37rv","text":"Reference genome FASTA. Default inside the container: /reference/new_refH37Rv.fasta. In Reference and database arguments.","kind":"option"},{"page":"Parameters","section":"--bakta_proteins","url":"user-guide/parameters.html#opt-bakta_proteins","text":"Reference proteins GenBank file for Bakta. Default inside the container: /reference/sequence.gb. In Reference and database arguments.","kind":"option"},{"page":"Parameters","section":"--mycobrowser_ref","url":"user-guide/parameters.html#opt-mycobrowser_ref","text":"Mycobrowser functional categories table. Default inside the container: /reference/Mycobacterium_tuberculosis_H37Rv_txt_v4.txt. In Reference and database arguments.","kind":"option"},{"page":"Running AMAP","section":"","url":"user-guide/running.html","text":"Nextflow runs the pipeline straight from GitHub, so there is nothing to clone and nothing to keep up to date by hand.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv","kind":"page"},{"page":"Running AMAP","section":"Profiles","url":"user-guide/running.html#profiles","text":"A profile is a bundle of settings. You normally combine one container profile with one executor profile. docker container Runs every process in the AMAP Docker image. singularity container Same image via Singularity or Apptainer. standard container No container. Every tool must already be on PATH. slurm executor Submits each process as a SLURM job. sge executor Submits to Sun Grid Engine or Univa Grid Engine. pbs executor Submits to PBS or Torque. lsf executor Submits to IBM Spectrum LSF. test data Small bundled dataset, for checking the installation. highmem resources Raises the ceilings to 48 CPUs, 256 GB, 120 h. Combine them with commas: The order does not matter: each profile sets its own kind of thing. The ceilings on what any one task may ask for come from which profiles are on, not from their order: 48 CPUs, 256 GB and 120 h with highmem, 4 CPUs, 8 GB and 1 h with test, 48 CPUs, 128 GB and 72 h with an executor profile, and otherwise the machine's CPUs, 16 GB and 72 h. Without an executor profile everything runs on the local machine.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv","kind":"section"},{"page":"Running AMAP","section":"On a laptop or workstation","url":"user-guide/running.html#on-a-laptop-or-workstation","text":"CPUs are auto-detected and memory is capped at 16 GB. Raise both if the machine allows: A handful of genomes is fine locally. Beyond about twenty, a cluster is worth the setup.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --max_cpus 8 --max_memory 32.GB","kind":"section"},{"page":"Running AMAP","section":"On an HPC cluster","url":"user-guide/running.html#on-an-hpc-cluster","text":"Each process becomes its own scheduler job, with up to 200 running at once, so Bakta, Miniprot and eggNOG parallelise across all your samples. Defaults reach 48 CPUs and 128 GB per job, and every process carries a time limit so nothing sits in the queue forever. To match your site's queues and accounting: Deployment on HPC goes into the details that actually bite: shared filesystems, scratch, and where the eggNOG database should live. Large datasets Past a hundred genomes, use a scheduler. The eggNOG step in particular runs once over all samples at a time rather than once per sample, precisely so a shared filesystem is not hammered by hundreds of jobs reading the same Diamond database.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv --max_cpus 64 --max_memory 256.GB -process.queue long -process.clusterOptions '--account=mygroup'","kind":"section"},{"page":"Running AMAP","section":"Non-MTBC organisms","url":"user-guide/running.html#non-mtbc-organisms","text":"Lineage typing and Mycobrowser categories are specific to tuberculosis. Turn them off and bring your own reference: See custom reference.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --skip_typing --skip_mycobrowser --genus Escherichia --species \"Escherichia coli\" --ref_annotation my_reference.gbk --ref_genome my_reference.fasta","kind":"section"},{"page":"Running AMAP","section":"Resuming","url":"user-guide/running.html#resuming","text":"Nextflow caches every completed task, so an interrupted run picks up where it stopped: Keep the parameters identical. Changing an input invalidates that task and everything downstream, which is correct behaviour but does mean the work is redone. The cache lives in the work directory, so do not delete it between a run and its resume.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv -resume","kind":"section"},{"page":"Running AMAP","section":"Running only part of the pipeline","url":"user-guide/running.html#running-only-part-of-the-pipeline","text":"--skip_functional_annotation stops after the correction step. You get the corrected genes as a table with their proteins and sequences (*.AMAP.tsv, .faa, .fna) and the case tables, and skip eggNOG, duplications, the pangenome and the report. The GFF3 and GenBank files are written after eggNOG, so they are skipped too. Much faster when the corrected genes are all you are after:","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --skip_functional_annotation","kind":"section"},{"page":"Running AMAP","section":"What a run produces","url":"user-guide/running.html#what-a-run-produces","text":"Beyond the results themselves, results/pipeline_info/ holds the execution report, the timeline, the DAG, a trace with per-process resource usage, software_versions.yml with the exact version of every tool, and params.yml with the parameters used. Those last two are what you want when writing up methods.","kind":"section"},{"page":"Running AMAP","section":"Next steps","url":"user-guide/running.html#next-steps","text":"Outputs: what every file is Troubleshooting: when a run fails","kind":"section"},{"page":"Running AMAP","section":"Large datasets","url":"user-guide/running.html#note-large-datasets","text":"Past a hundred genomes, use a scheduler.","kind":"section"},{"page":"Outputs","section":"","url":"user-guide/outputs.html","text":"Everything lands under --outdir, results/ by default, in one subdirectory per pipeline stage. {sampleId} below is the sample's name as lineage typing leaves it, L4_G26689 for sample G26689, or the sample ID alone under --skip_typing.","kind":"page"},{"page":"Outputs","section":"Where to look first","url":"user-guide/outputs.html#where-to-look-first","text":"The whole analysis, interactive results/final_report.html Each genome and each gene, question by question results/amap_report.html Corrected annotation for a sample results/AMAP/{sampleId}/*-AMAP.gff3 Proteins for downstream tools results/AMAP/{sampleId}/*-AMAP.clean.faa Everything known about every gene results/AMAP/{sampleId}/*-AMAPmastertable.tsv Tool versions for your methods section results/pipeline_info/software_versions.yml","kind":"section"},{"page":"Outputs","section":"The full tree","url":"user-guide/outputs.html#the-full-tree","text":"","code":"results/ AMAP/ {sampleId}/ *-AMAP.faa Corrected proteins, all genes including pseudogenes *-AMAP.clean.faa No pseudogenes, no internal stops *-AMAP.fna nucleotide sequences *-AMAP.gff3 annotation, GFF3 *-AMAP.gbk GenBank *-AMAP.qc.tsv Genome size, GC%, coding density, annotation rate *-AMAPmastertable.tsv Coordinates plus eggNOG Mycobrowser, one row per gene *-AMAP.COG.tsv COG category counts *-AMAP.MycoBrowser.tsv Mycobrowser BAKTA/ The uncorrected Bakta for comparison *.faa *.gbff *.gff3 *.tsv *.hypotheticals.faa *.hypotheticals.tsv *.png map *.txt summary statistics CDS_CASES/ summary/ *-summary.tsv Case counts, samples *-case[1-4].tsv One table case *-rvmissing.tsv Reference did not find *.html Interactive overlap plots *_corrected.tsv Final corrected coordinates DUPLICATIONS/ duplications* Raw Diamond output, within-genome duplications_*_groups.tsv Duplicated grouped into families *.dup protein homologous_communities.tsv Communities across samples, with network metrics homologous_communities.html PANGENE/ pangenome.gfa Pangenome graph of the whole run pangenome.bubbles.txt Structural variation in pangenome.Rtab Gene presence/absence, a column sample pangenome.stat Graph PATHOTYPR/ typing/*_summary.tsv Lineage calls, contig markers fastas/*.fasta Each genome as {lineage}_{sampleId}.fasta pipeline_info/ execution_report_*.html Nextflow execution report execution_timeline_*.html Timeline pipeline_dag_*.html Dependency execution_trace_*.txt Per-process resource usage software_versions.yml .tsv Exact tool versions params.yml Parameters used this validation.log Pre-flight validation results run_summary.txt Status, duration, final_report.html full interactive amap_report.html by question, also without","kind":"section"},{"page":"Outputs","section":"The key files","url":"user-guide/outputs.html#the-key-files","text":"","kind":"section"},{"page":"Outputs","section":"*-AMAP.gff3 and *-AMAP.gbk","url":"user-guide/outputs.html#-amapgff3-and--amapgbk","text":"The corrected annotation, which is the point of the pipeline. The GFF3 is spec-compliant: gene features parent their CDS features, attributes are URL-escaped, ##sequence-region pragmas are emitted per contig, and eggNOG annotation rides along as Ontology_term, eC_number, COG_category, KEGG_ko, KEGG_Pathway and PFAMs. The sequences are appended after ##FASTA. Two kinds of gene carry marks, written the way NCBI writes them: Pseudogenes have pseudo=true in the GFF3 and /pseudo in the GenBank, with the reason in Note and /note: internal stop codons, a frameshift or lost stop that makes the frame run on past the end of the reference protein, no stop at all, or a stop the reference itself lacks. A /pseudo CDS has no /translation. Genes a contig end cut short, in a draft assembly, have partial=true, with start_range=.,START or end_range=END,. on the open end, and < or > on that end of the GenBank location. They are not pseudogenes. Each piece of a cut gene is there under the gene's name, the first in the assembly with the gene's identifier and the others with _2, _3. The same three facts, pseudogene, partial (5', 3' or both) and note, are the last columns of the master table and of *-AMAP.tsv. A gene across the origin of a circular sequence is written the way Bakta writes it. Bakta takes a sequence for a circular replicon when its FASTA description says complete, as NCBI's \"complete genome\" and \"complete sequence\" do, or circular=true, as Unicycler's does. In *-AMAP.tsv and the master table such a gene has its start after its end (stcE on plasmid pO157 of E. coli O157:H7 Sakai is 92563 to 2502); in the GenBank it is join(92563..92721,1..2502) in a record marked circular; and in the GFF3 it ends past the sequence's last base, by the sequence's length (92563 to 95223 on the 92721 bp plasmid), on a region feature marked Is_circular=true, as the GFF3 specification and Bakta's own GFF3 have it.","kind":"section"},{"page":"Outputs","section":"*-AMAP.faa versus *-AMAP.clean.faa","url":"user-guide/outputs.html#-amapfaa-versus--amapcleanfaa","text":"*-AMAP.faa mirrors the annotation exactly, pseudogenes included, so gene counts match the GFF3. *-AMAP.clean.faa drops the genes AMAP calls pseudogenes, those with pseudo=true in the GFF3, and any sequence with an internal stop codon, and strips the terminal stop. This is the file for orthology, phylogenomics, pangenome tools and dN/dS, all of which expect valid translations. It has fewer sequences than the GFF3 has genes, by design.","kind":"section"},{"page":"Outputs","section":"*-AMAPmastertable.tsv","url":"user-guide/outputs.html#-amapmastertabletsv","text":"One row per gene, joining the corrected coordinates, the Bakta annotation, the eggNOG assignment (COG category, description, preferred name, GO, EC, KEGG, PFAMs, CAZy) and, for MTBC, the Mycobrowser functional category. This is the table to load when you want to ask a question about the annotation.","kind":"section"},{"page":"Outputs","section":"*-AMAP.qc.tsv","url":"user-guide/outputs.html#-amapqctsv","text":"One row per sample: genome size, contig count, N50, GC%, total CDS, how many of them AMAP calls pseudogenes (those with pseudo=true in the GFF3), hypothetical protein percentage, mean and median gene length, coding density, and the eggNOG annotation rate. Useful for spotting the sample that went wrong.","kind":"section"},{"page":"Outputs","section":"CDS_CASES/","url":"user-guide/outputs.html#cds_cases","text":"The audit trail for the correction. Each case table lists the genes that fell into it, so you can see exactly what was changed and why. The correction algorithm explains the cases; *-summary.tsv aggregates the counts across samples and feeds the report. Its last three columns say which sample the counts are for: Sequence is the name its files carry (L4_G26689), Lineage what typing called it (Untyped under --skip_typing) and Sample its ID in the sample sheet (G26689).","kind":"section"},{"page":"Outputs","section":"What a healthy summary looks like","url":"user-guide/outputs.html#what-a-healthy-summary-looks-like","text":"Two real runs, those of the tutorial, to calibrate expectations. On the left, M. tuberculosis CDC1551 against the bundled H37Rv reference. On the right, E. coli O157:H7 Sakai against K-12 MG1655, using a custom reference. case2, exact match 2813 3324 case3, length differs 24 31 case4, resolved conflict 1100 507 case1_kept, genuinely novel 11 1387 rv_missing, rescued 97 133 total_cds before correction 4077 5367 The one number that differs by two orders of magnitude is case 1, and that is biology: CDC1551 is the same species as the reference so almost nothing is genuinely new, while O157:H7 carries prophages and the LEE island that K-12 does not have. What should worry you is the opposite: case 2 at or near zero. Every gene disagreeing with the reference by a few bases means something systematic is wrong with the reference, not with 4000 individual genes. The usual cause is a reference proteome whose coordinates do not describe the sequence its proteins were translated from.","kind":"section"},{"page":"Outputs","section":"PANGENE/pangenome.Rtab","url":"user-guide/outputs.html#pangenepangenomertab","text":"The gene presence/absence matrix of the whole run, the one both reports draw the pangenome from: one row per gene, one column per sample, 1 where pangene found the gene in that sample's genome. Every genome is searched with the same proteins, the reference proteome and the genes no reference gene matches, so a gene with a 1 in every column is one all the samples share. A gene is named after its reference gene (Rv1506c) or, when no reference gene matches it, after one sample's copy of it (L4_CDC1551_01396, the ID in that sample's master table).","kind":"section"},{"page":"Outputs","section":"final_report.html","url":"user-guide/outputs.html#final_reporthtml","text":"Self-contained, so it travels by email. Covers per-sample quality, missing reference genes by lineage, PE/PPE family distribution, KEGG pathways, duplication communities and network topology, the pangenome (core, softcore, shell, cloud, frequency spectrum, presence/absence heatmaps), COG and Mycobrowser distributions, case statistics and the associated tests, plus a searchable master-table explorer.","kind":"section"},{"page":"Outputs","section":"amap_report.html","url":"user-guide/outputs.html#amap_reporthtml","text":"One self-contained file, organised by the questions a run raises: how each genome annotated, where genes are missing or broken on the reference, one gene in every genome, what the correction changed, the pseudogenes by lineage, what lies outside the reference, and the functional profile. Written with or without the functional annotation. The interactive report goes through it.","kind":"section"},{"page":"Outputs","section":"Effect of the skip options","url":"user-guide/outputs.html#effect-of-the-skip-options","text":"--skip_typing: No PATHOTYPR/ directory. Samples keep their original IDs instead of being renamed by lineage. --skip_functional_annotation: The run stops after correction. No DUPLICATIONS/, PANGENE/ or final_report.html, and AMAP/ holds only the corrected table and sequences, *.AMAP.tsv, *.AMAP.faa and *.AMAP.fna, without the GFF3, the GenBank or the master table. You still get BAKTA/ and CDS_CASES/, including *_corrected.tsv with the corrected coordinates, and amap_report.html, without its eggNOG sections. --skip_mycobrowser: *-AMAP.MycoBrowser.tsv is written empty and the Mycobrowser sections drop out of the report. Everything else is unaffected.","kind":"section"},{"page":"Outputs","section":"Next steps","url":"user-guide/outputs.html#next-steps","text":"How it works: what produced all this Duplication metrics: reading the network numbers","kind":"section"},{"page":"The full report","section":"","url":"user-guide/report.html","text":"Every run ends in one file, results/final_report.html. It is self-contained: all the figures, tables and JavaScript are embedded, so it opens with no network and survives being emailed to someone who has never heard of Nextflow. To see what the sections look like before running anything, open the example report: a rendered report for twelve MTBC genomes across four lineages. The example uses generated data The twelve samples in it are synthetic. The distributions are drawn around a real M. tuberculosis CDC1551 run, so the shapes are representative, but no conclusion should be drawn from the numbers themselves. bin/report/demo/make_demo.py builds that dataset if you want to render the report yourself without waiting for a pipeline run.","kind":"page"},{"page":"The full report","section":"What is in it","url":"user-guide/report.html#what-is-in-it","text":"","kind":"section"},{"page":"The full report","section":"Overview","url":"user-guide/report.html#overview","text":"Dataset composition, then a per-sample quality panel: CDS counts, correction rates and missing reference genes. The annotation quality tab carries coding density, hypothetical percentage and genome size, which is where an outlier sample declares itself before you have gone any further.","kind":"section"},{"page":"The full report","section":"Annotation results","url":"user-guide/report.html#annotation-results","text":"CDS distribution per lineage, the genes with no counterpart in the reference, and the correction summary: how many genes fell into each case and what the adjudication did with them. Read this alongside the correction algorithm. Missing reference genes get both a heatmap, for spotting a gene absent from a whole lineage rather than one sample, and a searchable table.","kind":"section"},{"page":"The full report","section":"Functional annotation","url":"user-guide/report.html#functional-annotation","text":"COG categories from eggNOG, Mycobrowser functional categories for MTBC, and a comparison against the reference distribution. Each has its own per-category breakdown in counts and percentages, with the statistical tests alongside. PE/PPE gene families get their own section, since they are both the hardest part of an MTBC genome to annotate and usually the most interesting. The pseudogenes close it: how many AMAP called in each sample, lineage by lineage, a sample with none at 0, the same genes the QC table counts and the GFF3 marks pseudo=true. The master table's note column says why each is a pseudogene.","kind":"section"},{"page":"The full report","section":"KEGG pathways","url":"user-guide/report.html#kegg-pathways","text":"The most represented metabolic pathways, and how they differ between lineages.","kind":"section"},{"page":"The full report","section":"Duplications","url":"user-guide/report.html#duplications","text":"Community sizes, cross-genome homologous pairs and the network topology metrics. Duplication metrics explains what each number means.","kind":"section"},{"page":"The full report","section":"Pangenome","url":"user-guide/report.html#pangenome","text":"Core, softcore, shell and cloud classification, the gene frequency spectrum, and presence/absence heatmaps.","kind":"section"},{"page":"The full report","section":"CDS statistics","url":"user-guide/report.html#cds-statistics","text":"How many genes fall in each case, lineage by lineage: box and violin plots with Dunn's tests between lineages, the share of each case in every lineage, a heatmap of the means, and a table per case with the count, mean, median and spread of each lineage.","kind":"section"},{"page":"The full report","section":"Master table explorer","url":"user-guide/report.html#master-table-explorer","text":"Every gene of every sample in one searchable, sortable table, with the functional annotation attached. Useful for answering a specific question without leaving the report.","kind":"section"},{"page":"The full report","section":"What changes when steps are skipped","url":"user-guide/report.html#what-changes-when-steps-are-skipped","text":"--skip_typing: Everything still renders, but every sample is grouped under Untyped, so the per-lineage comparisons have a single group and the statistical tests drop out. --skip_mycobrowser: The Mycobrowser and PE/PPE sections are omitted. COG, KEGG, pangenome and duplications are unaffected. This is automatic for non-MTBC organisms. --skip_functional_annotation: No report at all. The run stops after correction.","kind":"section"},{"page":"The full report","section":"Rendering it yourself","url":"user-guide/report.html#rendering-it-yourself","text":"The report is bin/report/report_AMAP.Rmd, rendered by the FINAL_REPORT process. To iterate on it without running the pipeline: Keep it self-contained Nothing in the Rmd, header.html or styles.css may point at a remote URL. pandoc will fetch and inline a remote resource when the machine rendering has a network, which hides the problem, and leave it dangling when it does not. A report rendered on an isolated compute node then arrives with a broken image and a browser trying to reach the internet. Both logos live next to the Rmd for this reason, and the image zoom is plain DOM rather than jQuery from a CDN.","code":"python3 bin/report/demo/make_demo.py demo_inputs docker run --rm -v \"$PWD:/w\" -w /w paururo/amap:latest Rscript -e 'rmarkdown::render(\"bin/report/report_AMAP.Rmd\", output_file=\"/w/demo.html\", params=list(version=\"v1.0.1\", cog_file=\"/w/demo_inputs/input_cog.txt\", myco_file=\"/w/demo_inputs/input_myco.txt\", summary_file=\"/w/demo_inputs/input_summary.txt\", empty_file=\"/w/demo_inputs/input_noref.txt\", rvmissing_file=\"/w/demo_inputs/input_rvmissing.txt\", mastertable_file=\"/w/demo_inputs/input_mastertables.txt\", pangene_file=\"/w/demo_inputs/input_pangene.txt\", dup_file=\"/w/demo_inputs/homologous_communities.tsv\", qc_file=\"/w/demo_inputs/input_qc.txt\"))'","kind":"section"},{"page":"The full report","section":"The example uses generated data","url":"user-guide/report.html#note-the-example-uses-generated-data","text":"The twelve samples in it are synthetic.","kind":"section"},{"page":"The full report","section":"Keep it self-contained","url":"user-guide/report.html#note-keep-it-self-contained","text":"Nothing in the Rmd, header.html or styles.css may point at a remote URL.","kind":"section"},{"page":"The report by question","section":"","url":"user-guide/amap-report.html","text":"Next to final_report.html, every run now writes results/amap_report.html: one file that answers, section by section, the questions a run raises. Did each genome annotate well? Where on the reference are genes missing or broken? What happened to one gene in every genome? What did the correction change? Every table filters, sorts and saves as CSV, every figure saves as SVG, and a click on a genome or a gene opens it. It is written with or without the functional annotation. It needs only what the correction leaves, so a run with --skip_functional_annotation, which gets no final_report.html, still gets this one; the eggNOG sections then say that the run skipped them. It is self-contained: the data travels inside the file, compressed, and nothing is fetched from anywhere, so it opens with no network and survives being emailed. Fifteen MTBC genomes make a file of about half a megabyte, against 4.4 MB for final_report.html with twelve. It is dark, like the website, with a switch to light at the top right.","kind":"page"},{"page":"The report by question","section":"The sections","url":"user-guide/amap-report.html#the-sections","text":"1. Did each genome annotate well? One row per genome: lineage, genome size, contigs and N50, Bakta's calls and AMAP's genes, the share of the reference genes it has, and how many are missing, pseudogenes, partial, found more than once or outside the reference. A value far from those of comparable genomes is flagged (see How a value is flagged). A click on a genome opens it: its numbers, and its genes in each state, each list searchable and saved as CSV. 2. Where on the reference are genes missing or broken? Every reference gene at its place on the reference, one row per genome, the genomes grouped by lineage. A missing gene fills its row; a pseudogene is a band through the middle; a second copy a band on top. Deletions that genomes share show as blocks: with MTBC genomes, the regions of difference. Drag across the map to zoom into a region; its genes are then listed with how many genomes have each in each state. Below the map, Blocks of missing genes lists every run of two or more consecutive reference genes missing from a genome, grouped where genomes overlap, and says which lineages lack each block in all their genomes. 3. What happened to one gene in every genome? Type a locus, a gene name or part of a product. The card gives the reference's own coordinates, length, product, functional category and, from Mycobrowser, whether the reference calls it a pseudogene and which drugs it has resistance mutations for. The table gives, for every genome, its state, locus tag, contig, coordinates, strand, length against the reference, the note of a pseudogene and how AMAP got the gene. 4. What did AMAP change in Bakta's annotation? A waterfall from Bakta's calls to AMAP's genes: the case 1 calls the filter removed, the case 4 calls the reference replaced, then the genes placed from the reference over a call and those added where Bakta called nothing. For one genome a click on a bar lists its genes; for the calls AMAP did not keep, Bakta's coordinates next to those of the gene that took their place. The correction algorithm says what each case means. 5. Which genes are broken, and in which lineages? Every gene AMAP flags as a pseudogene in at least one genome, against the genomes, with the reason. First the genes broken in every genome of some lineages and in no other, then those broken in every genome, then those broken in all but one or two, then the rest. With Mycobrowser, each is compared with the reference's own pseudogenes. 6. What do the genomes carry that the reference does not? How many genomes carry each reference gene, with the genes some genomes lack; the genes that name no reference gene, grouped by product across genomes; the reference genes with more than one copy; and, when the run has them, the pangenome and the homologous communities. 7. Do the lineages differ in function? The genes each lineage loses (missing or pseudogene) by Mycobrowser category, and with the functional annotation, genes by COG category and KEGG pathway. One line per category, a dot per lineage, the categories that differ most between lineages first. 8. How was this made? AMAP's version, the command, the reference, every parameter (pipeline_info/params.yml), every tool's version (pipeline_info/software_versions.tsv), and what each state means. The bar under the top one filters every section at once: a lineage's pill shows or hides it (Alt or Cmd and a click keeps that lineage alone), and the box keeps the genomes whose name contains what is typed.","kind":"section"},{"page":"The report by question","section":"Gene states","url":"user-guide/amap-report.html#gene-states","text":"Each reference gene gets one state in each genome, from the genes that name it in the genome's final annotation, AMAP//.AMAP.tsv. Intact A gene names it, and nothing below applies Missing No gene names it Pseudogene The best gene naming it is flagged as a pseudogene: an internal stop, a lost stop or a frame that runs on past the reference protein Partial The best gene naming it runs into a contig end and is cut there More copies More than one gene names it alone Length differs One intact copy, of another length than the reference gene Shared copy Only a gene that names several reference genes at once names it, such as an IS6110 copy that matches all seventeen; counted as found When several apply, the first in the table wins: a pseudogene that is also cut by a contig end is a pseudogene. \"Reference genes found\" counts every state but missing.","kind":"section"},{"page":"The report by question","section":"How a value is flagged","url":"user-guide/amap-report.html#how-a-value-is-flagged","text":"Each genome is compared with its own lineage when that lineage has five genomes or more, and with every other genome otherwise. A value is flagged when it lies more than 3.5 robust standard deviations (1.4826 times the median absolute deviation) from their median, on the side that is bad: more missing genes, pseudogenes, partial genes, contigs or genes outside the reference, fewer reference genes found, a smaller N50, a genome size either way. Each also has to be further from the median than a minimum (five genes, five contigs, half a point of reference genes found, 1% of the genome size or 5% of the N50), so that identical genomes do not flag a gene or two of difference. Fewer than four genomes are not compared at all. A flag says a genome is unusual, not that it is wrong. M. bovis among M. tuberculosis genomes is flagged for its missing genes, which are its regions of difference. A genome with twice the pseudogenes of the others is worth a look at its assembly: frameshifts from sequencing errors look like pseudogenes.","kind":"section"},{"page":"The report by question","section":"What each section needs","url":"user-guide/amap-report.html#what-each-section-needs","text":".AMAP.tsv FIX_CDS No report The reference proteome the reference No report Case summaries and tables TYPE_CASES No lineages, no correction section Bakta's folder BAKTA_ANNOTATOR No genome sizes, contigs or products of Bakta's calls Mycobrowser table the reference, MTBC only Names and products from the genomes' own annotation; no categories or drug resistance Master tables GET_OUTPUTS, functional annotation No COG categories or KEGG pathways Pangenome matrix REPORT_INPUTS, functional annotation No pangenome Homologous communities GET_ALLDUP, functional annotation No communities params.yml, software_versions.tsv DUMP_PARAMS, SOFTWARE_VERSIONS No parameters or versions A section without its inputs says which and why instead of drawing anything. With a custom reference, under --skip_typing and --skip_mycobrowser, every genome is Untyped and the names and products come from Bakta and eggNOG. Genomes of the sample sheet that produced no annotation, because the pipeline ignored a task that kept failing, are named at the top of the report.","kind":"section"},{"page":"The report by question","section":"Size","url":"user-guide/amap-report.html#size","text":"The data of each genome is a few kilobytes: its genes are stored as how far each lies from where the previous gene and the reference predict it, which for a genome collinear with its reference is mostly nothing. Up to 100 genomes, the report also lists every Bakta call AMAP did not keep, with its coordinates; past that it only counts them, to keep the file small, and CDS_CASES// has them for any run. 15 0.5 MB, measured 100 1.5 to 1.7 MB 300 1.9 to 2.1 MB 500 2.9 to 3.3 MB The sizes past fifteen are worked out from the fifteen MTBC genomes of eight lineages the report was measured on, the larger figure with each genome's data compressed on its own and the smaller with the sharing measured between those fifteen. In the browser, five hundred genomes open in about three seconds.","kind":"section"},{"page":"The report by question","section":"Writing one by hand","url":"user-guide/amap-report.html#writing-one-by-hand","text":"bin/report/amap_report.py needs only Python 3.9 or later. Pointed at a results/ folder it reads the files where the pipeline publishes them: The reference proteome is the one the run used: for H37Rv, the AMAP container has it at /reference/aa_H37Rv.translated.fas, and the Mycobrowser table at /reference/Mycobacterium_tuberculosis_H37Rv_txt_v4.txt. The page reads its data with the browser's own decompression, which every current browser has: Chrome and Edge 80, Firefox 113 and Safari 16.4, and later. An older one says so instead of drawing the report.","code":"python3 bin/report/amap_report.py --amap results/AMAP --cases results/CDS_CASES --bakta results/BAKTA --master --ref-proteome ref_proteome.faa --mycobrowser Mycobacterium_tuberculosis_H37Rv_txt_v4.txt --versions results/pipeline_info/software_versions.tsv --params results/pipeline_info/params.yml --out amap_report.html","kind":"section"},{"page":"Custom reference","section":"","url":"user-guide/custom-reference.html","text":"AMAP corrects an annotation by refereeing it against a trusted one. For MTBC that trusted annotation is H37Rv, bundled in the image. Point --ref_annotation and --ref_genome at your own and the same machinery works for any prokaryote. Both must be given together. Setting one without the other stops the run immediately.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --skip_typing --skip_mycobrowser --genus Escherichia --species \"Escherichia coli\" --ref_annotation my_reference.gbk --ref_genome my_reference.fasta","kind":"page"},{"page":"Custom reference","section":"What counts as a good reference","url":"user-guide/custom-reference.html#what-counts-as-a-good-reference","text":"A closed, well-curated genome of a close relative, ideally the type strain or a reference assembly from RefSeq. The correction assumes the reference genes are right, so its quality is the ceiling on your result. --ref_annotation: GFF3 (.gff, .gff3) or GenBank (.gbk, .gb, .genbank, .gbff). Detected from the extension, with GenBank tried first for anything else. --ref_genome: FASTA of the same genome. It must be the same assembly the annotation describes, since coordinates are taken at face value.","kind":"section"},{"page":"Custom reference","section":"What AMAP does with it","url":"user-guide/custom-reference.html#what-amap-does-with-it","text":"PREPARE_REFERENCE runs once, before any sample is processed, and does three things. 1. Extracts the reference proteome. Every CDS becomes a protein, headed GENE_ID_START-END contig=SEQUENCE, SEQUENCE being the name --ref_genome gives the sequence the gene is on. Those embedded coordinates are not decoration: the paralog step reads them back to predict where each protein belongs in the sample, which tells its own locus from a paralog's, so the header format matters. A CDS across the origin of a circular sequence ends past the sequence's last base, the way GFF3 writes it: tagA on plasmid pO157 (92721 bp) of E. coli O157:H7 Sakai, join(92527..92721,1..2502) in its GenBank record, is pO157p01_92527-95223. From GenBank the existing /translation qualifier is used when present, otherwise the sequence is extracted and translated. From GFF3 the lines of each CDS are joined first, as GenBank's join() is, and translated with the bacterial code, reading transl_except (a selenocysteine) where it is given; on E. coli K-12 the two formats give the same 4318 proteins, and on Sakai the same 5155. A GFF3 protein under 8 amino acids is left out. The gene ID comes from locus_tag, then gene, then protein_id for GenBank; locus_tag, ID, gene, then Name for GFF3. A CDS with none of these is skipped. 2. Annotates the reference genome with Bakta. The same Bakta that will annotate your samples, run on a genome whose true annotation is known, keeping the FASTA's names for its sequences (--keep-contig-headers). 3. Works out what Bakta invents. Any Bakta CDS that does not overlap a curated gene of its own sequence is a false positive on this genome, and by extension a call Bakta is prone to make on your samples too. These become the bad_bakta set used to filter novel-gene candidates in case 1. Bakta writes a call across the origin of a sequence whose FASTA description says complete from its start to its stop, start after end (stcE on pO157 is 92563..2502), and it is compared with the genes on both sides of the origin. The calls on a sequence the annotation has no gene on are not judged: the annotation says nothing about them. For MTBC this set was curated by hand. With a custom reference AMAP derives the equivalent automatically, which is what makes organism-agnostic mode work rather than merely run.","kind":"section"},{"page":"Custom reference","section":"What you must turn off","url":"user-guide/custom-reference.html#what-you-must-turn-off","text":"--skip_typing: pathotypr assigns MTBC lineages from tuberculosis-specific marker SNPs. Meaningless elsewhere, and it will fail. AMAP warns when --genus is not Mycobacterium and this is not set. --skip_mycobrowser: Mycobrowser functional categories are an MTBC resource. Auto-enabled when --ref_annotation is set and --genus is not Mycobacterium, but setting it explicitly is clearer. Also set --genus and --species to match your organism, since Bakta uses them for naming and reference selection.","kind":"section"},{"page":"Custom reference","section":"What stays the same","url":"user-guide/custom-reference.html#what-stays-the-same","text":"Everything else. Case classification, conflict resolution, missing-gene rescue, the stop codon check, eggNOG functional annotation, duplication analysis, the pangenome and the report all work identically. The report simply omits the lineage and Mycobrowser sections, and the pangenome calls each genome by its sample ID where a typed run would use its lineage name.","kind":"section"},{"page":"Custom reference","section":"What to expect from a real run","url":"user-guide/custom-reference.html#what-to-expect-from-a-real-run","text":"E. coli O157:H7 Sakai corrected against K-12 MG1655, both straight from RefSeq, in the tutorial's run: The 142 are the automatically derived equivalent of the hand-curated MTBC false-positive list, and 1387 case 1 genes is what you would expect between two E. coli strains that genuinely differ. If your case 2 comes out at or near zero, stop and look at the reference rather than the sample. Every gene missing an exact match by a constant offset points at the reference, not at 4000 coincidences.","code":"reference proteins extracted from the K-12 GenBank 4318 CDS Bakta called on that same genome 4296 false positives derived difference 142 case2 3324 case3 31 case4 507 case1_kept 1387 rv_missing 133 final annotation 5335 genes","kind":"section"},{"page":"Custom reference","section":"Worked example","url":"user-guide/custom-reference.html#worked-example","text":"E. coli K-12 MG1655 against the RefSeq reference:","code":"Fetch the reference datasets download genome accession GCF_000005845.2 --include genome,gff3 unzip ncbi_dataset.zip nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv --skip_typing --skip_mycobrowser --genus Escherichia --species \"Escherichia coli\" --ref_annotation ncbi_dataset/data/GCF_000005845.2/genomic.gff --ref_genome ncbi_dataset/data/GCF_000005845.2/GCF_000005845.2_ASM584v2_genomic.fna","kind":"section"},{"page":"Custom reference","section":"Things that trip people up","url":"user-guide/custom-reference.html#things-that-trip-people-up","text":"The annotation and the genome must match Coordinates from the annotation are used directly against the FASTA. An annotation from a different assembly of the same organism produces nonsense: extracted proteins will not translate, and the reference proteome will be mostly empty. PREPARE_REFERENCE prints the count of extracted proteins, so check it looks like a genome's worth. Reference genes need identifiers A GFF3 whose CDS features carry no locus_tag, ID, gene or Name is skipped entirely, and the run fails with \"No proteins extracted\". This happens with minimal GFF3 exports from some annotation tools. Distance to the reference The correction expects your samples to be the same species as the reference. The further away they are, the more genes fall into case 1 (nothing in the reference to compare against) and the less AMAP has to say. It is a correction tool, not an ortholog finder.","kind":"section"},{"page":"Custom reference","section":"Next steps","url":"user-guide/custom-reference.html#next-steps","text":"How it works: what the correction does with the reference Parameters: the full flag reference","kind":"section"},{"page":"Custom reference","section":"The annotation and the genome must match","url":"user-guide/custom-reference.html#note-the-annotation-and-the-genome-must-match","text":"Coordinates from the annotation are used directly against the FASTA.","kind":"section"},{"page":"Custom reference","section":"Reference genes need identifiers","url":"user-guide/custom-reference.html#note-reference-genes-need-identifiers","text":"A GFF3 whose CDS features carry no locus_tag, ID, gene or Name is skipped entirely, and the run fails with \"No proteins extracted\".","kind":"section"},{"page":"Custom reference","section":"Distance to the reference","url":"user-guide/custom-reference.html#note-distance-to-the-reference","text":"The correction expects your samples to be the same species as the reference.","kind":"section"},{"page":"Overview","section":"","url":"how-it-works/index.html","text":"AMAP does not annotate genomes from scratch. It takes an annotation that already exists, compares it against one that is trusted, and adjudicates the differences. The premise is that de novo annotators are good at finding genes and less good at their exact boundaries. Bakta will call a start codon a few codons late, or split one gene into two, or invent a short ORF that is not really there. If a curated reference for a close relative exists, those errors are visible: align the reference proteins onto the new genome, and where the two disagree, decide which is more likely right. On MTBC genomes that adjudication changes roughly 30% of genes.","kind":"page"},{"page":"Overview","section":"The three inputs to a decision","url":"how-it-works/index.html#the-three-inputs-to-a-decision","text":"The Bakta annotation. Every CDS Bakta called on your genome, with coordinates and a locus tag. The Miniprot alignments. Every reference protein aligned onto your genome, with the coordinates where it landed. The reference proteins carry their own coordinates in their headers (Rv0001_1-1524), which lets a later step tell a real locus from a paralogous placement. The false-positive set. Proteins that a de novo annotator calls on the reference genome itself but that are not in the curated annotation. Bakta's characteristic mistakes, in other words. For MTBC this is a curated file; with a custom reference AMAP derives it by annotating the reference and taking the difference.","kind":"section"},{"page":"Overview","section":"The pipeline","url":"how-it-works/index.html#the-pipeline","text":"Ten steps, from genomes to report: eggNOG database setup, skipped if it is already built Lineage typing with pathotypr, MTBC only Annotation with Bakta Protein alignment with Miniprot Case classification: every CDS sorted by how it relates to the reference Correction: conflicts resolved, missing genes rescued, stop codons checked Functional annotation with eggNOG-mapper Duplication analysis: within and across genomes Pangenome with pangene Report The workflow page walks through each with its inputs and outputs.","kind":"section"},{"page":"Overview","section":"The correction itself","url":"how-it-works/index.html#the-correction-itself","text":"Steps 5 and 6 are what makes AMAP more than a wrapper. Every CDS is sorted into one of five outcomes: Case 1 Bakta called it, the reference has nothing there. Potentially novel. Case 2 Coordinates match the reference exactly. Kept. Case 3 Coordinates match but alignment length differs. Kept. Case 4 Overlaps a reference gene, but the coordinates conflict. Must be resolved. Missing The reference has a gene there, Bakta missed it. Rescued. Cases 1 and 4 are where the work is: deciding which novel genes are real, and which set of coordinates wins a conflict. The correction algorithm gives the rules and the thresholds.","kind":"section"},{"page":"Overview","section":"In this section","url":"how-it-works/index.html#in-this-section","text":"The workflow: the ten steps in detail The correction algorithm: case classification, conflict resolution, quality control Duplication metrics: what the network numbers mean","kind":"section"},{"page":"The workflow","section":"","url":"how-it-works/workflow.html","text":"The pipeline as a metro map: stations are the processes of main.nf, lines are what travels between them. Pick a station for what it does and where its files go, or switch an option to see what a run leaves out.","kind":"page"},{"page":"The workflow","section":"1. eggNOG database setup","url":"how-it-works/workflow.html#1-eggnog-database-setup","text":"Checks whether the eggNOG database exists at --eggnog_db and builds it if not. A bacteria.dmnd under 100 MB is treated as a truncated download and rebuilt. The database is large, so point --eggnog_db at persistent shared storage on a cluster. Skipped entirely under --skip_functional_annotation.","kind":"section"},{"page":"The workflow","section":"2. Lineage typing","url":"how-it-works/workflow.html#2-lineage-typing","text":"pathotypr assigns each genome to an MTBC lineage using phylogenetic marker SNPs, and the genomes are renamed after their sample ID with the lineage in front (L4_G26689 for sample G26689). That prefix carries through every downstream file and is what lets the report group results by lineage. pathotypr types each contig of a draft on its own, and the lineage of the draft is the one the markers of all its contigs add up to. A genome of one sequence has that sequence renamed after the sample and keeps the description that follows the name in its header, so Bakta still reads a closed genome as complete and circular when the description says so (NCBI's \"complete genome\" and \"complete sequence\", Unicycler's circular=true); the contigs of a draft keep their names. A genome with no lineage markers at all is named NA_G26689. MTBC-specific. Turn it off with --skip_typing for anything else, and sample IDs stay as you wrote them.","kind":"section"},{"page":"The workflow","section":"3. Annotation with Bakta","url":"how-it-works/workflow.html#3-annotation-with-bakta","text":"Bakta produces the baseline annotation: CDS, tRNA, rRNA and other features. This is the annotation AMAP will correct, and it is published unmodified under BAKTA/ so you can compare. --genus and --species are passed through, so setting them correctly improves the starting point.","kind":"section"},{"page":"The workflow","section":"4. Protein alignment with Miniprot","url":"how-it-works/workflow.html#4-protein-alignment-with-miniprot","text":"Miniprot aligns the reference proteome onto each genome, spliced and frameshift-aware. Two alignments are produced: the reference proteome, and the false-positive set that will filter novel-gene candidates. Output is PAF. Coordinates are converted from 0-based to 1-based on read, and for a protein hitting several positions, the best hit per position is kept so multi-copy families such as PE/PPE keep all their placements. In a draft assembly the genes a contig end cuts get a second pass. Miniprot places the larger piece of such a gene only, so what that placement lacks is aligned again as a protein of its own, and its placement at the end of another contig is the rest of the gene (pieces of cut genes).","kind":"section"},{"page":"The workflow","section":"5. Case classification","url":"how-it-works/workflow.html#5-case-classification","text":"Every Bakta CDS is compared against the alignments and sorted into cases 1 to 4, plus the reference genes Bakta missed. Comparisons are strand-aware throughout: a gene at (100, 200, +) and one at (100, 200, -) are different genes and are never merged. Tables land in CDS_CASES/, one per case, plus interactive overlap plots. The correction algorithm covers the classification.","kind":"section"},{"page":"The workflow","section":"6. Correction","url":"how-it-works/workflow.html#6-correction","text":"The cases are resolved into one corrected coordinate set: conflicts adjudicated, novel genes filtered, missing reference genes rescued, duplicates removed. Then check_cds.py translates every surviving CDS. A gene that does not end in a stop codon has its coordinates extended to the next in-frame stop, up to 300 bp. Genes with internal stops are flagged as pseudogenes rather than discarded. Anything that cannot be translated at all is dropped, so the annotation table and the FASTA files always agree. Outputs are the corrected gene table and its protein and nucleotide files under AMAP/ (*.AMAP.tsv, .faa, .fna). The GFF3 and GenBank files follow once eggNOG has run, in step 7.","kind":"section"},{"page":"The workflow","section":"7. Functional annotation","url":"how-it-works/workflow.html#7-functional-annotation","text":"eggNOG-mapper assigns COG categories, KEGG orthologs and pathways, GO terms, EC numbers, PFAM domains and CAZy families. This runs once over all samples together, not once per sample. With closely related genomes most proteins are identical across samples, so they are deduplicated first, annotated once, and the results propagated back. For a few hundred MTBC genomes that cuts the Diamond search by an order of magnitude and avoids hundreds of jobs contending for the same database on a shared filesystem. The results are merged into *-AMAPmastertable.tsv and into the GFF3 attributes.","kind":"section"},{"page":"The workflow","section":"8. Duplication analysis","url":"how-it-works/workflow.html#8-duplication-analysis","text":"Each proteome is searched against itself with Diamond to find within-genome duplications, which are grouped into families by connected components. Then the proteins of every sample's families are pooled and searched all against all to build homologous communities across genomes, with network metrics computed per community. Duplication metrics explains the numbers.","kind":"section"},{"page":"The workflow","section":"9. Pangenome","url":"how-it-works/workflow.html#9-pangenome","text":"pangene builds one pangenome graph for the whole run and a gene presence/absence matrix, which the report turns into core, softcore, shell and cloud classifications, a frequency spectrum and presence/absence heatmaps. Every genome is searched with the same proteins: the reference proteome, and the genes of each sample that no reference gene matches. miniprot aligns them to every sample's genome, its own included: the ones PATHOTYPR renamed or, with --skip_typing, the genomes of the sample sheet, each under its sample ID. A row of the matrix is then a gene, named after its reference gene or, outside the reference, after one sample's copy of it, and a column a sample, so the genes the samples share are the rows found in every column.","kind":"section"},{"page":"The workflow","section":"10. Report","url":"how-it-works/workflow.html#10-report","text":"A single self-contained HTML file built from every output above. Sample quality, missing reference genes by lineage, PE/PPE families, KEGG pathways, duplication communities and topology, the pangenome, COG and Mycobrowser distributions, case statistics with the associated tests, and a searchable table of every gene. Alongside it, pipeline_info/ collects the Nextflow execution report, timeline, DAG and trace, plus software_versions.yml and params.yml for your methods section.","kind":"section"},{"page":"The workflow","section":"11. Interactive report","url":"how-it-works/workflow.html#11-interactive-report","text":"amap_report.html, one self-contained file organised by question: how each genome annotated, where reference genes are missing or broken, one gene in every genome, what the correction changed and the pseudogenes by lineage. It reads the correction's outputs directly, so it is written with or without the functional annotation, and the functional steps add their sections when they run. The interactive report goes through it.","kind":"section"},{"page":"The workflow","section":"Next steps","url":"how-it-works/workflow.html#next-steps","text":"The correction algorithm: the rules behind step 6 Outputs: every file this produces","kind":"section"},{"page":"The correction algorithm","section":"","url":"how-it-works/correction-algorithm.html","text":"The core of AMAP. It referees between the initial Bakta annotation and the Miniprot alignments of a trusted reference proteome, classifying every candidate gene and then applying a set of rules to resolve the disagreements. Coordinates throughout are 1-based inclusive, so a gene's length is end - start + 1. Every comparison is strand-aware and contig-aware: genes on opposite strands at the same position are different genes and never conflict, and nor are genes on different contigs of a draft assembly, where every contig starts at 1. Bakta runs with --keep-contig-headers so that it names the contigs as the FASTA and Miniprot do; by default it renames them contig_N.","kind":"page"},{"page":"The correction algorithm","section":"0. Placements","url":"how-it-works/correction-algorithm.html#0-placements","text":"","kind":"section"},{"page":"The correction algorithm","section":"Small proteins","url":"how-it-works/correction-algorithm.html#small-proteins","text":"Miniprot needs a seed of several residues and enough score to report an alignment, which a protein of a few dozen residues rarely gives it: annotating E. coli K-12 with its own proteome, it placed none of 33 proteins of 8 to 29 residues, and it put the toxin IbsC (b4665), 19 residues three of which differ from its neighbour IbsD's, on that neighbour only. Every protein without an exact, complete placement is looked for in the six-frame translation of the genome, preceded by a start codon and followed by a stop, and each exact copy found where Miniprot did not already place the protein becomes a complete placement. Proteins shorter than 8 residues are not searched. A protein of up to 50 residues with no exact copy is looked for again, allowing substitutions in at most a fifth of its residues after the first, which varies with the start codon; a stop codon in place of a residue counts as one, and the gene is then flagged as a pseudogene in step 7. E. coli O157:H7 Sakai carries ten of K-12's micro-proteins with one to four residues changed, and none was annotated. A copy with an insertion or a deletion is not found this way.","kind":"section"},{"page":"The correction algorithm","section":"Pieces of cut genes","url":"how-it-works/correction-algorithm.html#pieces-of-cut-genes","text":"A draft assembly cuts the genes its contig breaks run through. Miniprot reports only the placements within 5% of a protein's best, so it places the larger piece of such a gene and not the smaller one at the end of another contig: in CDC1551 cut into 40 contigs, none of the 25 smaller pieces was annotated. A placement was cut by its contig when it lacks residues at one end of its protein, ends within 30 bp of the contig's edge on that side, and those residues would not fit in the bases left. What it lacks, if 8 residues or more, is aligned again as a protein of its own, and its best placement is the piece at the other contig end; this pass runs with -L 10 -n 2, so that Miniprot indexes and chains the short reading frames an edge leaves. A placement of that protein which runs off a contig edge on the side of the cut, within 30 bp, is a piece of the gene and becomes a placement of the gene's protein. In the CDC1551 draft that places 22 of the 25 smaller pieces; the other three are five or six codons long.","kind":"section"},{"page":"The correction algorithm","section":"Paralog placements","url":"how-it-works/correction-algorithm.html#paralog-placements","text":"Miniprot places a reference protein everywhere it scores within 5% of its best, so a protein with a paralog is placed at both loci. Before anything is classified, each placement is checked against where the protein belongs. The reference proteome carries each protein's coordinates in its header (Rv1150_1278269-1278820), but those are reference coordinates, and any indel upstream shifts every position after it. So each placement's reference position is predicted from the nearest proteins placed only once, which follow the sample's local offset from the reference, and the placement closest to where the protein belongs is its syntenic one. A locus that several proteins are placed at, exactly, predicts nothing: it is a repeat the reference has in several copies and the sample in one, like the single IS6110 of M. bovis, and the reference positions of its proteins are spread over the whole genome. A placement is then dropped when all of these hold: another protein's syntenic placement covers at least 50% of it, on the same strand and in the same reading frame that protein matches there at least as well the two proteins live in different places in the reference, so they are not simply neighbours that overlap What this removes is a protein sitting on someone else's locus: Rv1945 on Rv1148c, the mce3 genes M. bovis has lost (RD7) on their mce1 paralogs, or the sixteen other IS6110 names on each IS6110 copy. What it leaves alone is a copy at a locus no protein claims, such as an IS6110 insertion at a site the reference lacks or a duplication in the sample, and genes that genuinely overlap, which sit in different frames (Rv1041c and Rv1042c). When two proteins both look syntenic at one locus, which happens when a deletion removes a gene and leaves its paralog next door as the nearest place left, the better match keeps the locus. CDC1551 lacks Rv3427c and Rv3428c, and without this Rv3425 and Rv3426 would sit on Rv3429.","kind":"section"},{"page":"The correction algorithm","section":"1. Classification","url":"how-it-works/correction-algorithm.html#1-classification","text":"Every Bakta CDS is compared against the Miniprot alignments. Case 2, perfect match. Same start, same end, same strand as an alignment, and the same length. Correct, kept as is. Case 3, imperfect match. Same coordinates, but the aligned length differs, typically from indels. Reliable enough, kept. Case 4, conflicting overlap. A Bakta CDS and an alignment overlap on the same strand, but their coordinates do not match. This is the conflict that has to be adjudicated. Case 1, potentially novel. A Bakta CDS with no overlap to any alignment. Either a genuine gene absent from the reference, or something Bakta invented. Missing reference genes. An alignment with no overlapping Bakta CDS. The reference has a gene there and the annotator missed it. Alignments with no aligned bases at all are discarded first. Each set is written to its own table under CDS_CASES/, so the whole decision is auditable.","kind":"section"},{"page":"The correction algorithm","section":"2. Filtering case 1","url":"how-it-works/correction-algorithm.html#2-filtering-case-1","text":"Novel-gene candidates are scrutinised before being believed. A candidate is removed if either holds: it overlaps the false-positive set by 80% or more of its own length, meaning it is a call the annotator is known to make wrongly on the reference genome itself it aligns to the reference at 80% or more match identity, meaning it is not novel at all but a shifted version of a gene that is already there Candidates that pass both, and candidates with no reference alignment to judge by, are kept as case1_kept.","kind":"section"},{"page":"The correction algorithm","section":"3. Resolving case 4","url":"how-it-works/correction-algorithm.html#3-resolving-case-4","text":"","kind":"section"},{"page":"The correction algorithm","section":"Single overlaps","url":"how-it-works/correction-algorithm.html#single-overlaps","text":"One Bakta CDS against one reference alignment, on the same strand of the same contig. Lengths are compared, and: the reference wins by default the Bakta version wins only if it is longer by more than 550 bp, and the alignment is not complete in the call's reading frame The asymmetry is deliberate. A reference alignment is usually the better boundary, but a much longer Bakta call normally means the alignment fragmented, and discarding a full-length gene in favour of a partial alignment would be the worse error. A complete alignment, one that covers the whole reference protein down to its stop codon, has not fragmented: a longer call in its frame ends at the same stop and only starts further upstream (plcD, Rv1755c, 843 bp in H37Rv and 1545 bp in Bakta's call on CDC1551). A call in another frame is another gene, and keeps its chance to win on length; the alignment then comes back in step 5. A call that wins in the alignment's frame is the protein's gene, longer than in the reference, and takes its name in step 5. If the Bakta CDS overlaps more than one alignment, the reference wins regardless.","kind":"section"},{"page":"The correction algorithm","section":"Multiple overlaps","url":"how-it-works/correction-algorithm.html#multiple-overlaps","text":"Several Bakta CDS against one alignment, the signature of a gene that has been split. The best-overlapping Bakta CDS is picked first, then compared to the alignment on length: the reference wins if it is at least as long, if the Bakta CDS beats it by less than 500 bp, if the alignment is complete in the call's frame, or if the call overlaps other alignments as well, which makes it several reference genes read as one (Rv3022c and Rv3021c are one reading frame in CDC1551) otherwise the Bakta CDS wins","kind":"section"},{"page":"The correction algorithm","section":"4. Deduplication on insert","url":"how-it-works/correction-algorithm.html#4-deduplication-on-insert","text":"Each winner is checked against the genes already accepted. Where a new coordinate overlaps an existing one by 70% or more on the same strand: the new gene displaces the existing one if it is longer by more than 550 bp it also displaces it if the existing gene is shorter by less than that margin and is not a reference gene otherwise the existing gene stands and the new coordinate is dropped A reference gene is one whose identifier is a reference protein's. The test used to look for coordinates embedded in the identifier, which are stripped as the alignments are read, so no gene qualified and a call of similar length could displace any of them: Bakta's version of Rv1614, one codon longer, replaced it.","kind":"section"},{"page":"The correction algorithm","section":"5. Rescuing missing genes","url":"how-it-works/correction-algorithm.html#5-rescuing-missing-genes","text":"A reference protein is missing when no gene accounts for any of its placements, and then it goes back in at its best one. A gene accounts for a placement when it sits on it: same contig and strand, the same reading frame, covering at least 50% of it, and not known to be another reference gene. The frame matters because genes that really overlap sit in different frames (Rv0724A and Rv0725c); another reference gene in the same frame does not count either, since some are nested (E. coli hokC inside mokC). Placements on other genes' loci have already been dropped in step 0, so what is left to rescue sits where the gene belongs or at a locus nobody claims. A gene that stands for no reference protein accounts for any placement in its frame: the Bakta call that won its locus in step 3 over a shorter alignment. When no gene carries the protein's name, that call takes it, as long as the alignment spans at least half of the protein. Otherwise the gene came out with no name and the protein was missing from the table. In M. bovis, H37Rv's mmpL6 (Rv1557) is the 3' end of a 2904 bp gene that the TbD1 deletion cut short in H37Rv; CDC1551 and M. bovis carry a 4821 bp DNA methyltransferase gene whose first 506 residues are H37Rv's Rv2024c. A remnant names nothing: the RD12 deletion of M. bovis leaves the last 67 of cyp141's (Rv3121) 400 residues at the end of a 321-codon gene that is not cyp141. The pieces of a cut gene are parts of its protein, not copies of it, so each is missing on its own, and one in place does not keep another out. In the CDC1551 draft a 7.5 kb Bakta call displaced the larger piece of Rv0100, which comes back here though the smaller piece is in.","kind":"section"},{"page":"The correction algorithm","section":"6. Final deduplication","url":"how-it-works/correction-algorithm.html#6-final-deduplication","text":"Where one gene ends up at several coordinates, entries fully contained inside a different gene are dropped, always keeping at least one entry per gene so nothing disappears entirely. After the stop codon check, deduplicate_rvs.py ranks the genes that share a reference protein: a non-pseudogene over a pseudogene, then the longer one, then one Bakta also called at exactly those coordinates, then the one nearer where the protein belongs by synteny. The first keeps its place. The others stay as further copies of the gene when they are at least 90% as long as the first, clear of the copies kept, and not covered by another reference gene; a pseudogene must also be 90% as long as the reference protein, which a piece of a split gene is not, and anything else is a remnant of the gene and goes. A genome can carry a gene more than once, and these copies are what the duplication analysis counts: E. coli O157:H7 Sakai keeps 34 that used to be deleted. The pieces of a cut gene stay as well, when the first gene is one of them: each is marked partial at its cut end, no two are open at the same end alone (a gene has one first piece and one last, and any number between that a contig holds whole), they are clear of each other, and together they are at most 110% of the reference gene's length. Copies and pieces only Miniprot placed are all named after their protein, so later ones get _2, _3 to keep identifiers unique.","kind":"section"},{"page":"The correction algorithm","section":"7. Stop codon check","url":"how-it-works/correction-algorithm.html#7-stop-codon-check","text":"check_cds.py translates every surviving CDS. It is given the reference proteome, which settles what a translation alone cannot. No terminal stop codon. Coordinates are extended to the next in-frame stop, searching 300 bp in the right direction for the strand, or as far as the reference protein says is missing when that is further. If the extension introduces internal stops or finds nothing, the original coordinates are kept and the gene is flagged as a pseudogene. An extension goes no further than the reference protein. A gene may be extended to a stop at most 10 residues past the end of its reference protein: that completes an alignment that stopped short. A frame that runs on further has lost its stop or shifted, and the C-terminus it would add is not the gene's; the gene keeps its extent and is flagged as a pseudogene, with the reason, as PGAP and Bakta treat a frameshift. Often that frame runs on into the next gene, whose stop is then the next one (in M. bovis Rv0590 into Rv0590A and Rv3373 into Rv3374), and extending would annotate that gene twice. Genes a contig cut short are partial. In a draft assembly, a gene whose frame reaches the end of its contig with no stop, from within 30 bp of it, was cut by the assembly: it runs to the last codon there and is marked 3' partial. One that opens within a codon of the contig's edge without a start codon is 5' partial, and so is one that opens there with a codon that can start a gene but whose protein begins inside its reference protein: a cut leaves whatever codon it falls before at the edge, and one in six codons inside H37Rv genes is one a gene can start with. Neither is a pseudogene. Each piece of a cut gene is annotated this way, under the gene's name. Reference pseudogenes keep their extent. When the reference protein spans its annotated stretch codon for codon with no stop (PE21, Rv2099c; PPE48, Rv3022c) and the gene ends where it does, the reference annotates it without a stop, and the gene is left as it is and flagged. A reference pseudogene annotated with a partial final codon (oxyR', Rv2427A) gets that base back, since an alignment only covers whole codons. Clipped N-termini are restored. When a gene ends in a stop but is short of its reference protein at the start, and the codons upstream open with a start codon and translate to the missing residues, the gene is extended to cover them. A gene that already starts where its reference does is shorter somewhere inside, and is left alone. Internal stop codons. Flagged as a pseudogene and kept, with the flag propagated to the master table and the GFF3. *-AMAP.clean.faa excludes them. Untranslatable genes are dropped from the annotation table as well as the FASTA files, so the two never disagree.","kind":"section"},{"page":"The correction algorithm","section":"Threshold summary","url":"how-it-works/correction-algorithm.html#threshold-summary","text":"False-positive overlap case 1 filtering 80% Reference match identity case 1 filtering 80% Bakta length advantage single overlaps 550 bp Bakta length advantage multiple overlaps 500 bp Overlap for deduplication on insert 70% Length margin for displacement on insert 550 bp Distance from the predicted position paralog placements 10 kb Proteins placed once, each side, for the local offset paralog placements 5 Cover to claim another protein's placement paralog placements 50% Shortest protein looked for exactly small proteins 8 residues Longest protein looked for with substitutions small proteins 50 residues Residues after the first that may differ small proteins 20% Distance from a contig's edge for a placement, or a piece, to be cut there pieces of cut genes 30 bp Shortest part of a protein looked for at the other contig ends pieces of cut genes 8 residues Cover for a gene to account for a placement rescuing missing genes 50% Share of its protein an alignment spans to name a longer call rescuing missing genes 50% Length of a further copy, next to the first final deduplication 90% Cover by another reference gene that rules a copy out final deduplication 50% Length of a gene's pieces together, next to the reference gene final deduplication 110% Stop codon search quality control 300 bp, or what the reference protein is missing Residues an extension may run past the end of the reference protein quality control 10 Distance from a contig end within which a gene counts as cut quality control 30 bp at the 3' end, a codon at the 5' end Identity to restore a clipped N-terminus quality control 90% These are constants in bin/classify/, tuned on MTBC. Another organism may want different values, particularly the 550 bp length margins, which assume genes of roughly bacterial size.","kind":"section"},{"page":"The correction algorithm","section":"Next steps","url":"how-it-works/correction-algorithm.html#next-steps","text":"The workflow: where this sits in the pipeline Outputs: the case tables that record every decision","kind":"section"},{"page":"Duplication metrics","section":"","url":"how-it-works/duplications.html","text":"AMAP looks at gene duplication twice: within each genome, and across the whole set.","kind":"page"},{"page":"Duplication metrics","section":"How the families are found","url":"how-it-works/duplications.html#how-the-families-are-found","text":"Each corrected proteome is searched against itself with Diamond, excluding self-hits. A pair is kept when the identical residues of the alignment cover 80% or more of either the query or the subject, so a short domain match between two otherwise unrelated proteins is not counted as duplication. Kept pairs become links in a graph, and its connected pieces become the duplication families, listed in DUPLICATIONS/{sample}/duplications_{sample}_groups.tsv. Because the rule takes the larger of the two shares, a fragment joins the family of the gene it came from: a match that covers almost all of a short protein counts, however little of the long one it covers.","kind":"section"},{"page":"Duplication metrics","section":"How the communities are built","url":"how-it-works/duplications.html#how-the-communities-are-built","text":"The proteins of every sample's families are then pooled and searched all against all. Within each genome, the pairs that pass the same rule make a network, which the Louvain method splits into communities, with a fixed seed so that a rerun splits it the same way. Two communities are homologous when at least one hit joins them, and each homologous pair is one row of DUPLICATIONS/homologous_communities.tsv, with an interactive network in the matching HTML file.","kind":"section"},{"page":"Duplication metrics","section":"The columns","url":"how-it-works/duplications.html#the-columns","text":"A row describes two communities: the columns ending in 1 are the first, those ending in 2 the second. The first is the one whose sample comes first by name, or the one with the lower number when both are in the same sample, and the rows come in that order too, so a rerun writes the same file. For each: Genome, Community: the sample, and the community's number in it. Size: how many proteins it holds. Elements: the eggNOG COG categories of its proteins, with how many have each, the largest first. A protein eggNOG gave no category counts as -, eggNOG's mark for none. AvgDegree: links per protein, on average. In a community of n proteins it reaches n - 1 when every protein is linked to every other. Density: the links it has as a share of the pairs it could have. 1 means every protein matches every other. AvgClustering: for each protein, how often two of its partners are linked to each other as well, averaged over the community. 1 when every protein is linked to every other, 0 for a chain or a star. AvgPathLength: how many links separate two of its proteins, on average, over its largest connected piece. Nodes: the proteins themselves.","kind":"section"},{"page":"Duplication metrics","section":"Reading them together","url":"how-it-works/duplications.html#reading-them-together","text":"Density and clustering at 1 mean copies that all match each other: a recent expansion of near-identical copies. In CDC1551 these are the transposases of insertion sequences, the ESAT-6-like Esx proteins and PE proteins. Lower density and longer paths mean copies that have drifted apart, each still matching some of the others but not all, as M. bovis's copies of the transposases above have. Or, less happily, an artefact of assembly fragmentation, where one gene broken across contigs looks like several partial copies. Cross-checking against the contig count in *-AMAP.qc.tsv tells the two apart. Duplications and assembly quality Repetitive families are exactly what short-read assemblers collapse or fragment. A genome with markedly fewer duplications than its relatives may have collapsed them rather than lost them. The metrics describe the assembly you provided, not necessarily the organism.","kind":"section"},{"page":"Duplication metrics","section":"Next steps","url":"how-it-works/duplications.html#next-steps","text":"Outputs: where the duplication files live The workflow: where this step sits","kind":"section"},{"page":"Duplication metrics","section":"Duplications and assembly quality","url":"how-it-works/duplications.html#note-duplications-and-assembly-quality","text":"Repetitive families are exactly what short-read assemblers collapse or fragment.","kind":"section"},{"page":"Containers","section":"","url":"deployment/containers.html","text":"Every tool AMAP calls lives in one image, paururo/amap, published on Docker Hub. Both the docker and singularity profiles use it, so nothing needs installing beyond a container engine. Nextflow pulls the image on first use and caches it. Singularity converts it to a .sif on the way, which takes a few minutes once.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv singularity","kind":"page"},{"page":"Containers","section":"What is inside","url":"deployment/containers.html#what-is-inside","text":"Tools: Bakta 1.12.0, Miniprot 0.18, Diamond 2.1.24, eggNOG-mapper 2.1.13, pangene 1.1, pathotypr 1.0.0, gfatools. Runtimes: Python 3.9 with pandas, NumPy, Biopython, matplotlib, plotly, NetworkX, intervaltree, pyvis and python-louvain. R 4.4 with the report packages. Bundled data under /reference/, so a standard MTBC run needs no downloads: MTB_ancestor_reference.fasta MTBC ancestral reference genome aa_H37Rv.translated.fas H37Rv reference proteome bad.bakta.translated.fas Curated Bakta false positives new_refH37Rv.fasta H37Rv reference genome sequence.gb H37Rv GenBank, for Bakta Mycobacterium_tuberculosis_H37Rv_txt_v4.txt Mycobrowser functional categories snps_lineages.tsv pathotypr lineage markers Plus the Bakta light database at /data/bakta_db. The eggNOG database is not bundled, because of its size. AMAP downloads and builds it on first use and caches it at --eggnog_db.","kind":"section"},{"page":"Containers","section":"Platform","url":"deployment/containers.html#platform","text":"The image is linux/amd64 only, and every Dockerfile stage pins it. On Apple Silicon it runs under emulation, which works but is slow. For anything past a couple of genomes, use an amd64 machine or a cluster.","kind":"section"},{"page":"Containers","section":"Pinning a version","url":"deployment/containers.html#pinning-a-version","text":"latest tracks the newest release. For a reproducible analysis, pin the version in nextflow.config or on the command line: results/pipeline_info/software_versions.yml records the exact version of every tool a run used, which is what belongs in a methods section.","code":"nextflow run PathoGenOmics-Lab/AMAP -profile docker --tsv samples.tsv -process.container 'paururo/amap:1.0.1'","kind":"section"},{"page":"Containers","section":"Building it yourself","url":"deployment/containers.html#building-it-yourself","text":"Only needed if you are changing what is in the image. The build takes about an hour: it downloads the Bakta database and solves a conda environment with R. Check the result before using it: That verifies the tools, the Python and R libraries, the bundled reference data, and runs the repository test suite with the image interpreter. The last part matters, since a library update can break the pipeline scripts without the image looking any different. Publishing is automated: the Docker image GitHub Actions workflow builds, smoke tests and pushes on a v* tag or on demand. Reference downloads can expire Several reference files are fetched from Google Drive during the build. Those links have expired before. If a build fails on a wget in the data stage, that is why, and the URL needs replacing. The Zenodo-hosted files (MTB_ancestor_reference.fasta, the pathotypr markers) are not affected.","code":"git clone https://github.com/PathoGenOmics-Lab/AMAP cd AMAP docker build --platform linux/amd64 -f docker/Dockerfile -t amap:dev docker/smoke_test.sh","kind":"section"},{"page":"Containers","section":"Singularity on a cluster","url":"deployment/containers.html#singularity-on-a-cluster","text":"Set a shared cache so every job reuses one converted image instead of each building its own: Put it in your shell profile or the launch script. Without it, a large run can have dozens of jobs converting the same image at once. More cluster specifics in deployment on HPC.","code":"export NXF_SINGULARITY_CACHEDIR=/shared/singularity_cache","kind":"section"},{"page":"Containers","section":"Next steps","url":"deployment/containers.html#next-steps","text":"HPC deployment: scratch, shared filesystems, schedulers Troubleshooting: when a run fails","kind":"section"},{"page":"Containers","section":"Reference downloads can expire","url":"deployment/containers.html#note-reference-downloads-can-expire","text":"Several reference files are fetched from Google Drive during the build.","kind":"section"},{"page":"HPC clusters","section":"","url":"deployment/hpc.html","text":"Past a couple of dozen genomes, a scheduler is worth the setup. Combine a container profile with an executor profile:","code":"nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv","kind":"page"},{"page":"HPC clusters","section":"The executor profiles","url":"deployment/hpc.html#the-executor-profiles","text":"slurm SLURM 100 per minute sge Sun Grid Engine, Univa 20 per minute pbs PBS, Torque 20 per minute lsf IBM Spectrum LSF 20 per minute All four queue up to 200 jobs at once, default to 48 CPUs and 128 GB per job, and enable node-local scratch. SGE additionally requests memory through -l h_vmem, and uses the smp parallel environment. Tune the concurrency to your site's policy if 200 is too aggressive:","code":"nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv -process.queueSize 50","kind":"section"},{"page":"HPC clusters","section":"Fitting your site","url":"deployment/hpc.html#fitting-your-site","text":"-process.queue sets the partition or queue, and -process.clusterOptions passes anything else straight through, which is where accounting flags, QoS and node constraints go. On SLURM, Bakta's jobs, the longest a run sends, also ask for --qos=medium. The slurm profile sets that for Bakta alone, and for Bakta it takes the place of -process.clusterOptions, so give Bakta your site's options in a config passed with -c:","code":"nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv --max_cpus 64 --max_memory 256.GB -process.queue long -process.clusterOptions '--account=mygroup' process withName: 'BAKTA_ANNOTATOR' clusterOptions '--qos=medium --account=mygroup'","kind":"section"},{"page":"HPC clusters","section":"Scratch","url":"deployment/hpc.html#scratch","text":"--scratch is on by default in every HPC profile, and it matters. Without it, every process reads and writes directly on the shared filesystem, and Bakta and eggNOG in particular generate enough metadata traffic to slow a cluster down for everyone. With it, work happens on node-local disk and only the results are copied back. Turn it off only if your nodes have no local disk:","code":"nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv --scratch false","kind":"section"},{"page":"HPC clusters","section":"Where to put things","url":"deployment/hpc.html#where-to-put-things","text":"The Singularity cache. Set it once, somewhere shared: Otherwise the singularity profile caches under --outdir, and a large run can have many jobs converting the same image at once. The eggNOG database. Large, built once, reused forever. Point it at persistent shared storage rather than somewhere per-run: The work directory. It gets large, tens of gigabytes for a big run. Put it on a filesystem with room and a generous quota: Keep it until you are sure the run is finished, since -resume needs it.","code":"export NXF_SINGULARITY_CACHEDIR=/shared/singularity_cache nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv --eggnog_db /shared/databases/eggnog -work-dir /scratch/$USER/amap_work","kind":"section"},{"page":"HPC clusters","section":"Launching","url":"deployment/hpc.html#launching","text":"Nextflow's own process must survive the whole run, which will outlive an SSH session. Either run it under tmux or screen, or submit it as a small job of its own: The launcher itself needs very little: it submits and waits while the real work happens in the jobs it spawns. Give it plenty of wall time, though, since it has to outlast everything it submits.","code":"#!/bin/bash #SBATCH --job-name=amap --cpus-per-task=2 --mem=8G --time=48:00:00 --output=amap_%j.log module load singularity export NXF_SINGULARITY_CACHEDIR=/shared/singularity_cache nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm --tsv samples.tsv --eggnog_db /shared/databases/eggnog -work-dir /scratch/$USER/amap_work -resume","kind":"section"},{"page":"HPC clusters","section":"Memory and retries","url":"deployment/hpc.html#memory-and-retries","text":"A process killed by the out-of-memory killer is resubmitted with more memory, scaling with the attempt number up to --max_memory. That handles the occasional unusually large genome without you having to size everything for the worst case. eggNOG is the memory-hungry step. It runs once over all samples rather than once per sample, which keeps a shared filesystem from being hammered but does mean one large job. The highmem profile exists for this:","code":"nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm,highmem --tsv samples.tsv","kind":"section"},{"page":"HPC clusters","section":"Watching a run","url":"deployment/hpc.html#watching-a-run","text":"squeue -u $USER shows the jobs. When it finishes, pipeline_info/ holds the execution report, the timeline, and a trace with per-process CPU, memory and runtime, which is what to look at before adjusting resources for the next run.","kind":"section"},{"page":"HPC clusters","section":"Next steps","url":"deployment/hpc.html#next-steps","text":"Troubleshooting: when something fails Containers: what the image holds","kind":"section"},{"page":"Troubleshooting","section":"","url":"deployment/troubleshooting.html","text":"","kind":"page"},{"page":"Troubleshooting","section":"Reading a failure","url":"deployment/troubleshooting.html#reading-a-failure","text":"When a process fails, Nextflow prints the work directory for the failed task. That directory has everything you need: .command.err is almost always where the answer is. Re-running .command.sh inside the container reproduces the failure by hand. results/pipeline_info/execution_trace_*.txt shows every task with its status, CPU, memory and runtime, which is the fastest way to see whether a failure was resources or logic.","code":"work/ab/cd1234.../ .command.sh the exact script that ran .command.log everything it printed .command.err stderr on its own .exitcode what exited with","kind":"section"},{"page":"Troubleshooting","section":"Before the pipeline starts","url":"deployment/troubleshooting.html#before-the-pipeline-starts","text":"\"Unexpected input: '('\" in nextflow.config: Nextflow 25.10 and later read pipelines with a stricter parser, which older copies of AMAP do not pass. Nextflow keeps the copy it downloaded first, so fetch the current one with nextflow pull PathoGenOmics-Lab/AMAP and run again. To run an older AMAP release as it is, export NXF_SYNTAX_PARSER=v1 first. \"Input TSV file not found\": The path in --tsv is wrong, or relative to somewhere other than where you launched Nextflow. \"Invalid TSV format\": The header must be exactly sampleId and fasta, separated by a real tab. Spaces are the usual culprit. cat -t samples.tsv shows tabs as ^I, and works on both Linux and macOS. \"FASTA file not found\": A path in the sample sheet does not resolve. Absolute paths avoid the ambiguity. \"not found, neither on the host nor inside the container\": A reference file the run reads is in neither place: a typo in its path, or one of the defaults, which ship in the container, on a run without one. Give the path to your own copy with its reference argument. Validation failures: results/pipeline_info/validation.log says which check failed and for which sample. Thresholds are in preparing inputs.","kind":"section"},{"page":"Troubleshooting","section":"Container problems","url":"deployment/troubleshooting.html#container-problems","text":"The image will not pull. Check network access from the compute nodes, which often differs from the login node. Pre-pulling on the login node and setting NXF_SINGULARITY_CACHEDIR sidesteps it: Slow on Apple Silicon. The image is amd64 only, so it emulates. Fine for the test profile, painful beyond that. Use an amd64 machine. Docker permission denied. Your user is not in the docker group. Either add it, or use Singularity.","code":"export NXF_SINGULARITY_CACHEDIR=/shared/singularity_cache singularity pull docker://paururo/amap:latest","kind":"section"},{"page":"Troubleshooting","section":"Memory","url":"deployment/troubleshooting.html#memory","text":"A process is killed with exit 137 or 140. Out of memory. AMAP retries with more, scaling by attempt, so a single occurrence in the log that later succeeds is normal. Persistent failures mean raising the ceiling: On a cluster, that is the whole answer. On a macOS or Windows laptop it is the wrong one: Docker Desktop runs containers inside a VM with its own memory ceiling, commonly 8 GB, and that ceiling is what killed the process. Nextflow sized the request against the host and never saw it. Raising --max_memory only makes the request larger than a limit it was already over. Raise the VM in Docker Desktop under Settings, Resources, Memory, then keep --max_memory at or below it. eggNOG runs out of memory. It is the hungriest step, requesting 50 GB, and it runs once over all samples together. On a machine that cannot reach that, --skip_functional_annotation drops it along with the pangenome and the duplication analysis and still gives you the corrected annotation. On a cluster, use the highmem profile, or point it at a large-memory partition:","code":"--max_memory 256.GB nextflow run PathoGenOmics-Lab/AMAP -profile singularity,slurm,highmem --tsv samples.tsv","kind":"section"},{"page":"Troubleshooting","section":"Filesystem","url":"deployment/troubleshooting.html#filesystem","text":"\"No space left on device\". Usually the work directory, which grows to tens of gigabytes. Move it somewhere with room: Also check your $TMPDIR quota, since Diamond and Bakta both use it heavily. Everything is slow on a shared filesystem. Confirm --scratch is on. It defaults to true in the HPC profiles, but a standard or docker profile on a cluster will not have it. Cache misses on resume. NFS timestamp drift across nodes invalidates Nextflow's cache. The slurm profile already sets process.cache = 'lenient' for this. If you are using a different executor, add it:","code":"-work-dir /scratch/$USER/amap_work -process.cache lenient","kind":"section"},{"page":"Troubleshooting","section":"The eggNOG database","url":"deployment/troubleshooting.html#the-eggnog-database","text":"It downloads every run. --eggnog_db is pointing somewhere per-run. Give it persistent shared storage. It rebuilds itself unexpectedly. A bacteria.dmnd under 100 MB is treated as a truncated download and rebuilt. Usually means an earlier download was interrupted. \"has eggnog_db.ok, so EGGNOG_DB will not run again\". eggnog_db.ok tells Nextflow the database is built, so it never runs EGGNOG_DB again, but what it needs is not all there. Delete the files the message names and the next run builds it again.","kind":"section"},{"page":"Troubleshooting","section":"Results that look wrong","url":"deployment/troubleshooting.html#results-that-look-wrong","text":"Far fewer genes than expected. Check the assembly. A fragmented assembly loses genes across contig boundaries, and they turn up in *-rvmissing.tsv rather than being corrected. *-AMAP.qc.tsv has the contig count and N50. \"Genes lost to (Start, End, Strand, Contig) collisions\" in the log. Two of the classification's sources listed a gene at exactly the same place, and one entry was kept. Genes on different contigs never collide, since the contig is part of the key. *-AMAP.clean.faa has fewer sequences than the GFF3 has genes. Expected. The clean proteome drops pseudogenes and internal-stop sequences. Use *-AMAP.faa if you need one protein per annotated gene. Almost everything is case 1. Your samples are too far from the reference for the alignments to find anything, so there is nothing to correct against. Use a closer reference. See custom reference. The Mycobrowser sections are empty. Expected with --skip_mycobrowser, or when the Mycobrowser file is missing. The pipeline warns and continues.","kind":"section"},{"page":"Troubleshooting","section":"Still stuck","url":"deployment/troubleshooting.html#still-stuck","text":"Open an issue at github.com/PathoGenOmics-Lab/AMAP/issues with: the command you ran the failing task's .command.err results/pipeline_info/software_versions.yml results/pipeline_info/params.yml The last two pin down the exact versions and parameters, which is usually what the answer turns on.","kind":"section"},{"page":"Changelog","section":"","url":"about/changelog.html","text":"All notable changes to AMAP are documented in this file.","kind":"page"},{"page":"Changelog","section":"On main, not yet released","url":"about/changelog.html#unreleased","text":"Annotating H37Rv against its own reference now gives back every CDS of the AL123456.3 GenBank record at exactly its coordinates, strand and name: 4031 of 4031, against 4020 before. The changes apply to every genome, so results are not directly comparable with 1.0.1.","kind":"changelog"},{"page":"Changelog","section":"Unreleased / Correctness","url":"about/changelog.html#unreleased-correctness","text":"Paralog copies no longer take another gene's place: a protein with a paralog was placed at both loci, and which copy survived came down to insertion order. On H37Rv itself that lost Rv1150, Rv1945 and Rv2828c; on M. bovis it reported genes the strain lacks (the mce3 operon of RD7, the phiRv2 prophage of RD11, plcBC of RD5) as copies sitting on their paralogs. Each placement is now checked against where its protein belongs, predicted from the sample's own offset to the reference so that indels do not throw it off Strand of a resolved conflict: taken from the protein's first placement anywhere in the genome instead of the one being resolved, so esxM (Rv1792), Rv3466 and two IS6110 copies came out at their own coordinates on the wrong strand; a Bakta call chosen over an alignment also took the alignment's strand Reference pseudogenes keep their extent: PE21 (Rv2099c) and PPE48 (Rv3022c), which the reference annotates without a stop, were extended to the next one, and oxyR' (Rv2427A) and Rv3216 lost the partial final codon the reference gives them The 1.0.1 fix for reference pseudogenes never fired: it looked for reference coordinates in a column that has not carried them since March 2026, and its tests wrote them in by hand. check_cds.py now reads the reference proteome instead Alignments that stop short are completed: miniprot can miss a protein's N-terminus (Rv3611 on CP110619) or leave a repetitive C-terminus unaligned further from its stop than the 300 bp searched; the reference protein now says how much is missing Ties between copies of a gene go to the one Bakta also called, then to the one where the protein belongs by synteny, not the one that comes first in the genome (E. coli K-12 b2635) Draft assemblies: genes were compared on coordinates alone, and every contig starts at 1, so cut into 40 contigs CDC1551 had 87% of its proteins translated from the wrong contig. Every gene is now matched, translated and written on its own contig, and Bakta runs with --keep-contig-headers so that its contig names match the FASTA's Case 4 is grouped strand by strand: about 8% of its groups (101 of 1255 on H37Rv) put an alignment on one strand together with a Bakta call on the other A complete alignment is not displaced by a longer call in its frame: such a call ends at the same stop and only starts further upstream (plcD on CDC1551). Nor does a call that spans several reference genes win against them (Rv3022c and Rv3021c, one reading frame in CDC1551) Reference genes are protected again: the check looked for coordinates in identifiers that no longer carry them, so any call of similar length could displace a reference gene (Rv1614) Missing genes are recovered by what covers them: genes stored under Bakta locus tags looked missing by name, so a placement a codon off its gene went back in as a second copy. With the case 4 changes, ORFs annotated twice fall from 10 to 3 on CDC1551 and from 16 to 6 on M. bovis, where the reference has none Real copies of a gene are kept: deduplicate_rvs.py deleted every copy of a gene but one, 34 genes on E. coli O157:H7 Sakai, which were then missing from the annotation and from the duplication analysis. Further copies get identifiers of their own Genes that lost their stop no longer swallow the next gene: check_cds.py extended a gene without a stop to the next one in its frame, which after an indel is the next gene's, so that gene was annotated a second time in the same frame (M. bovis Rv0590 over Rv0590A, Rv3373 over Rv3374, Rv2293c over Rv2292c). Such a gene now keeps its extent as a pseudogene, unless the extension only restores residues its reference protein has. ORFs annotated twice fall from 3 to 2 on CDC1551 and from 6 to 3 on M. bovis, and none of those left comes from an extension Small proteins: miniprot placed none of the 33 proteins of 8 to 29 residues in E. coli K-12, and put the toxin IbsC (b4665) only on its near-identical neighbour IbsD. Every protein without an exact placement is now looked for in the six-frame translation of the genome, which takes K-12 against itself from 4252 to all 4286 of its genes, its alternative-start isoforms counted with their gene, and Sakai from 198 to 220 of 220 Frameshifts and lost stops: a gene was extended to the next stop in its frame however far that was, and came out as a complete gene with a C-terminus its reference lacks; 18 in CDC1551 and 20 in M. bovis. It now keeps its extent and is flagged as a pseudogene when that stop lies more than ten residues past the end of the reference protein, as PGAP and Bakta treat a frameshift, which also covers a frame that runs into the next gene Genes a contig end cuts: in a draft assembly they came out as pseudogenes, or as complete genes, without a word. A gene whose frame runs off the contig, or that opens at its edge without a start codon, is now marked partial at that end. In CDC1551 cut into 40 contigs, the larger piece of each of the 25 genes a cut runs through is marked so The other pieces of a cut gene: miniprot reports only the placements within 5% of a protein's best, so only the larger piece of a gene a contig end cuts was annotated. What that placement lacks is now aligned again as a protein of its own, and its placement at the end of another contig is annotated under the gene's name and marked partial: 47 of the 50 pieces in the CDC1551 draft instead of 25, the other three being five or six codons long. A gene that opens at a contig's edge with a codon that can also start one, as one in six codons inside H37Rv genes can, is marked partial too when its protein begins inside its reference protein Disrupted copies of a gene: deduplication kept further copies only when intact, so a whole copy that is a pseudogene was deleted with the pieces of split genes; it now stays when nearly as long as the reference protein GFF3 references: each CDS line was translated on its own, so a CDS of several lines came out in fragments, and proteins under 11 residues were dropped to hide them. The lines are joined per CDS and translated with the bacterial code, reading transl_except, and E. coli K-12's GFF3 gives the same 4318 proteins as its GenBank record, 24 of which were missing Genes longer than their reference keep its name: a Bakta call that won its locus over a shorter alignment in its frame kept no name, and the protein was missing from the table: Rv2024c in CDC1551 and M. bovis, mmpL6 (Rv1557, whole in M. bovis where TbD1 cut it in H37Rv), Rv1068c, Rv1917c and Rv3135 in M. bovis, fliD (b1923) and b0282 in Sakai. The call now takes the name when the alignment spans at least half of the protein, so a remnant does not name the gene it ends (the 67 residues of cyp141 that RD12 leaves in M. bovis) Small proteins with a few substitutions: only exact copies were looked for, so Sakai's ten K-12 micro-proteins with one to four residues changed were not annotated. A protein of up to 50 residues with no exact copy is now looked for with up to a fifth of its residues changed; a stop codon in place of a residue counts, and the gene is then a pseudogene (b1229) Synteny no longer hangs on the order of the rows: the anchors were sorted by start and searched by midpoint, so two anchors starting together sent a placement to whichever window the row order gave, and adding unrelated rows moved Sakai's b2127 24 kb and handed its locus to the paralog b1162. A locus several proteins are placed at exactly no longer anchors either: the single IS6110 of M. bovis made 34 anchors with reference positions all over the genome, and came out under all 17 IS6110 names of each of its genes; they are now Rv2814c and Rv2815c, its own Genes without a COG category are counted once: count_annot.py filled in a missing category as Unassigned and split it into letters, as it splits JK into J and K, so every gene eggNOG gave no category was counted once in A, D, E, G, I and U and twice in N and S, in *-AMAP.COG.tsv and in the report's COG figures and statistics, where it also shrank every other category's share. A test-profile run with a stand-in for eggNOG that gave none of its 4036 genes a category reported 4036 genes in each of those six and 8072 in N and S. Such a gene now counts once, as NaN, which the report shows as None, and its percentage is of all the genome's genes like every other category's COG categories R, W, X, Y and Z in the report: its COG figures took their categories from a list that ran from A to V without R, so a gene eggNOG put in R, W, X, Y or Z was plotted as NA in the overview and in none of the per-category figures, which stayed empty for those five, X - Mobilome (prophages and transposons) among them. The categories now come from the report's own list of figures, A to Z and None, so each is plotted in its own. The statistics table never used that list and is unchanged The QC table counts the pseudogenes AMAP called: annotation_qc.py read the first of Is_Pseudogene, pseudogene and is_pseudogene that the master table has, and every master table has Mycobrowser's Is_Pseudogene, empty when Mycobrowser is off. So pseudogene_count in *-AMAP.qc.tsv and the report's QC table was 0 in every organism-agnostic run (E. coli Sakai against K-12, where AMAP called 129 pseudogenes), and in an MTBC run the number of the sample's genes named after one of the 13 that Mycobrowser marks as pseudogenes in H37Rv, whatever AMAP made of them: 13 for CDC1551, where AMAP calls 123 and finds 6 of those 13 intact. It now counts AMAP's own call, the master table's pseudogene column, the same genes the GFF3 marks pseudo=true and the GenBank /pseudo The report's pseudogene chart counts AMAP's calls, sample by sample: its Pseudogene Detection section counted the master table's genes flagged in Mycobrowser's Is_Pseudogene or in AMAP's pseudogene column, lineage by lineage, as \"Pseudogenes per Lineage (Mycobrowser annotation)\". Is_Pseudogene says which genes are pseudogenes in H37Rv, not in the sample, so CDC1551 came out at 129 where AMAP calls 123 and the QC table says 123, the other 6 being genes Mycobrowser marks that CDC1551 has intact (Rv0947c, Rv1150, Rv2023A, Rv2098c, Rv3021c, Rv3324A), and without Mycobrowser the chart showed AMAP's calls under Mycobrowser's name. It also left out a lineage with none, and added up a lineage's samples, so a lineage with more samples looked as if it had more pseudogenes. REPORT_INPUTS now hands the report every master table row with the sample it is from, and the chart shows how many pseudogenes AMAP called in each sample, by lineage, a sample with none at 0, next to a table of them. The master table explorer shows the note that says why a gene is a pseudogene A sample with no gene in a category counts 0 in the report: count_annot.py writes a row for each COG or Mycobrowser category a sample has genes in, and the report plotted the rows as they came, so a sample with no gene in a category was left out of that category's figures, of the Dunn tests between lineages run on them, of the overview and of the statistics table, instead of counting 0. Its lineage's box, mean and median were those of the samples that have some, a lineage none of whose samples has any dropped out of the comparison, and the comparison with the median never showed a sample that had lost a category. Each sample now counts 0 in every category another sample of the run has genes in, and the statistics table's Count is every sample of the lineage. A category no sample has keeps its empty figure, and *-AMAP.COG.tsv and *-AMAP.MycoBrowser.tsv are unchanged The clean proteome leaves out the pseudogenes AMAP called: export_clean_proteome.py took them from the first of Is_Pseudogene, pseudogene and is_pseudogene that the master table has, which is Mycobrowser's, and looked each protein up by its whole FASTA ID, L4_CDC1551_Rv0001;+;0-1524 where the master table has L4_CDC1551_Rv0001, so it never found one and its log said 0 pseudogenes. Only the proteins with an internal stop codon were left out, and the pseudogenes without one stayed in *-AMAP.clean.faa, which the docs say has none: 42 of the 123 AMAP calls in CDC1551 (a frameshift or lost stop codon, no stop codon within reach, no stop codon as in the reference) and 27 of the 129 in E. coli Sakai against K-12. It now leaves out the genes whose pseudogene is yes, found by the ID before the ;, and on both genomes holds the CDS the GFF3 does not mark pseudo=true, 3891 and 5194, with the proteins the GenBank translates for them. A master table without that column stops it, where a warning let every pseudogene through","kind":"changelog"},{"page":"Changelog","section":"Unreleased / Pipeline","url":"about/changelog.html#unreleased-pipeline","text":"Pseudogenes in the GFF3 and GenBank: the GFF3 never said which genes were pseudogenes, and the GenBank only for genes with an internal stop, though the master table knew. Both now carry AMAP's flag with its reason (pseudo=true and Note, /pseudo and /note), and mark the ends a contig cut off (partial=true with start_range or end_range, < or >); the master table and *-AMAP.tsv have them as their last columns The default H37Rv reference under Docker: its files live inside the container and were handed to processes as paths, so Nextflow mounted /reference from the host, which Docker Desktop refuses and Docker on Linux mounts empty over the container's own. BUNDLED_REFERENCE copies them out first -profile test: VALIDATE_INPUTS still required the sample sheet the test profile does without. 1.0.1 had only been checked with -preview, which runs no task -resume kept results made by older code: Nextflow hashes a task's inputs and script, not the files it runs by path, so after an edit to the classifier or any script under bin/, -resume reused the old results. Every process that runs AMAP code now lists those files in ext code, which puts them in its cache key; edit bin/classify/cases.py and TYPE_CASES and FIX_CDS run again while PATHOTYPR and Bakta stay cached PATHOTYPR no longer depends on the classifier: its script said pathotypr classify, and Nextflow adds any bin/ entry a script names as a bare word to the cache key, so a change in bin/classify/ re-ran PATHOTYPR, Bakta and everything after them. The subcommand is quoted Nextflow 25.10 and later: the pipeline stopped before running anything, because the strict parser those versions use by default rejects the helper functions in nextflow.config, the statements outside the workflow in main.nf, the bare env(SAMPLE_NAME) output and publishDir paths that name a task input. Nextflow 26.10 also refuses the return that ended a --validate_only run. The resource caps are now the resourceLimits directive, which still takes --max_cpus, --max_memory and --max_time from the command line, a profile or a -c config. Nextflow 24.04, where that directive first appeared, is the new minimum --skip_mycobrowser in organism-agnostic mode: a custom reference from another genus turned it on by reassigning the parameter, which current Nextflow ignores with a warning, so the run went ahead with Mycobrowser enabled. It is now worked out in the parameter's default Tasks leave the pipeline untouched: the first time a copy of AMAP ran, Python cached the bytecode of AMAP's own modules inside it, in bin/classify/_pycache_, and with pathotypr classify naming that directory in PATHOTYPR's script, the first -resume re-ran PATHOTYPR, Bakta and everything after them. Tasks now run with PYTHONDONTWRITEBYTECODE=1 Samples with no Bakta call outside cases 2 and 3: when every Bakta call had an alignment at exactly its coordinates, which a small input can do, TYPE_CASES stopped with a KeyError, because the empty column of overlaps was read as a list of column names. Case 1 and case 4 now come out empty and the rest is classified as usual. An empty miniprot table stopped it the same way Samples Bakta calls no CDS on: with no contig names from Bakta to compare, the contig check took them for different names and stopped the run, and a Bakta table with nothing but its header did not read. The genes miniprot places now come out as missing from Bakta, like any gene it did not call --version: --help and the parameter docs have listed it since 1.0.0, but nothing read it, so nextflow run PathoGenOmics-Lab/AMAP --version started a run. It now prints the pipeline and Nextflow versions and exits before any task Drafts named after one of their contigs: pathotypr writes a summary row for each contig that carries lineage markers, and PATHOTYPR took the sample's name from the first, so CDC1551 cut into 40 contigs, sample CDC1551d, came out as L4_contig_14, and so did every file after it. Two drafts whose first row named the same contig became one sample, the results of one overwriting the other's. A genome is now named _ from the sample sheet, the lineage of a draft being the one the markers of all its contigs add up to, and the contigs of a draft keep their names instead of the first one taking the sample's. A closed genome whose header is its sample ID comes out as before Headers with a description: the name was the whole header line, so the tutorial's CDC1551 (NC_002755.2 Mycobacterium tuberculosis CDC1551, complete sequence) was named with spaces in it, Bakta failed on it three times and the run finished without it, and without failing. Such a genome now takes its sample ID like any other, and so does the test profile's: L4_H37Rv_test, where it was L4_CP110619_H37Rv An input could be emptied: PATHOTYPR wrote the renamed genome next to the link Nextflow stages its input as, so when the new name was the file's own, sample G26689 in L4_G26689.fasta for instance, the input was overwritten with nothing and the run went on without it. The input is now staged in a directory of its own Sample IDs with underscores: the steps after the correction told a sample's lineage from its sample ID by splitting its name at underscores, though typing only puts the lineage in front and sample IDs may hold underscores of their own. L4_H37Rv_test was counted as genome H37Rv, the tutorial's Beijing_1 and a Beijing_2 as one genome Beijing in the category counts, the report and the duplication network, an untyped H37Rv_test as lineage H37Rv, and each gene of an untyped sample with no underscore as a genome of its own in the network. PATHOTYPR now says which lineage it named a genome after and every step is handed the sample ID and the lineage. The case summary gains a Sample column and names an untyped sample as it is, not Untyped_, and the report no longer reads the first gene not in the reference as a header, nor stops when every gene has one Genomes kept outside the run's folder: VALIDATE_INPUTS staged only the sample sheet and opened the paths written in it from inside its container, which sees only the folder that the work directory and the sheet share. Genomes anywhere else, /data/genomes while running from ~/project for instance, were \"FASTA file not found\" and the run stopped before anything ran, and a relative path, which the sheet may use, was read from the task directory with or without a container. The genomes are now inputs of the process, each in a directory of its own so that two with the same file name do not collide, and each is read where Nextflow staged it Runs with functional annotation stopped at validation: VALIDATE_INPUTS looked for --eggnog_db in its own task directory, where the default eggnog_db, relative to the launch directory, never is, and took a database not built yet for an error, though EGGNOG_DB builds it. It now looks where EGGNOG_DB builds it, staged when it exists, and stops the run only when eggnog_db.ok is there without a complete database, since Nextflow then never runs EGGNOG_DB again A Bakta database on the host: --bakta_db reached the tasks as a path the container does not mount. With -profile docker, validation called the database missing and stopped the run, and where nothing validated it, as under -profile test, every sample failed three times and was ignored, and the run ended with nothing annotated and exit 0. A database on the host is now staged into BAKTA_ANNOTATOR, PREPARE_REFERENCE and VALIDATE_INPUTS like any other input; the default inside the container is used as before A warning on every run: validation expected a db.sqlite3 that no Bakta database has. It now checks for version.json and bakta.db, which Bakta cannot run without, so their absence stops the run, and names the database's type and version --strict_validation: the documentation said validation warnings could be made to stop the run, but nothing passed the script's --strict on. The new parameter does eggNOG's database under Docker: EGGNOG_DB built it in a folder it worked out for itself from --eggnog_db, and having no inputs, its container saw that folder only when Nextflow happened to mount it with the work directory and the pipeline's bin/, through a parent folder they share. Otherwise, as when running from /data/project with the pipeline in ~/.nextflow, the database was downloaded and built inside the container and lost with it, and only eggnog_db.ok reached the host: later runs skipped EGGNOG_DB, and ANNOT_EGGNOGMAP_BATCH found no bacteria.dmnd. A database already there without eggnog_db.ok was built again. main.nf now makes the folder on the host and hands it to EGGNOG_DB as an input. A folder left with only eggnog_db.ok needs that file deleted, as validation says eggNOG's database in a folder of any name: ANNOT_EGGNOGMAP_BATCH asked eggNOG-mapper for eggnog_db/bacteria.dmnd and --data_dir eggnog_db, but Nextflow stages a folder under its own name, so with a --eggnog_db called anything else, as the HPC guide's /shared/databases/eggnog or the tutorial's ~/databases/eggnog, EGGNOG_DB built the database and the annotation stopped at \"DIAMOND database eggnog_db/bacteria.dmnd not present\", with or without a container. The folder is now staged under that one name, whatever it is called. The task's tag and first log line also gave a single sample's FAA size in bytes as the number of samples (batch_1519647_samples under -profile test) Reference files on the host: BAKTA_ANNOTATOR gave Bakta --ref_annotation, or --bakta_proteins, as a path written into its script, which a container does not see, nor a task directory when it is relative, so the organism-agnostic mode failed under -profile docker: with K-12 as the reference for Sakai, Bakta called the \"user proteins file\" not valid three times, the sample was ignored and the run ended with exit 0. Likewise --pathotyper_snps and --pathotyper_ref on the host left every genome untyped, as NA_, --mycobrowser_ref left the Mycobrowser categories out, and one --miniprot_ref_* on the host while the others kept their defaults made BUNDLED_REFERENCE fail, so nothing was corrected. A file on the host is now staged into the tasks that read it, each in a directory of its own, and a default is read inside the container as before Reference files that are nowhere: a typo in one of those paths, or a default on a run without the container, went unnoticed until the samples were ignored or left untyped. Validation now checks each reference file the run reads and that is not on the host, inside the container where the tasks read it, and stops the run when it is not there. A missing Mycobrowser table only warns, since GET_OUTPUTS goes on without it The pangenome without lineage typing: PANGENE aligned each sample's proteins onto the genomes PATHOTYPR renamed, so with --skip_typing, which every custom reference from another genus needs, it had none and wrote empty files for every sample. It now aligns them onto the sample sheet's genomes, each copied to .fasta first, so that two files of the same name (assembly.fasta from two assemblies) stay apart and a .fna is not passed over Genes not in the reference, sample by sample: the report counted only the samples that had some, so a sample whose genes all matched the reference dropped out of its lineage's box instead of counting 0, and when no sample had any (H37Rv against itself, as in -profile test) the section was an empty plot. Every sample is now counted, the section says so when none has any, and each point is drawn at its count, on an axis from 0, where it was jittered up and down by as much as 0.4 The report's pangenome section lost a genome: pangene names each genome #0, and the report read the merged matrix with R's default comment character, so its header stopped at the first #. One genome column was dropped and the gene names became row names, so the core/accessory and frequency plots used a single column and the heatmap never appeared. The missing-gene and duplication tables, which carry the reference annotation's gene IDs as given, are now read without a comment character too Genes across the origin of a circular sequence: Bakta reads a sequence as a complete replicon when its FASTA description says complete (NCBI's \"complete genome\" and \"complete sequence\") or circular=true (Unicycler), joins its ends and calls a gene across its origin from its start to its stop, start after end. TYPE_CASES stopped on such a gene: E. coli O157:H7 Sakai with NCBI's headers, whose stcE runs across the origin of plasmid pO157 (92563..2502), failed three times and was left out, and the run finished without failing. Past it, check_cds.py dropped such a gene as invalid coordinates, deduplication and the QC table gave it a negative length, the GFF3 wrote it backwards, the GenBank writer stopped on it and the Panaroo GFF3 spanned it over the whole sequence. It now goes through whole and is written as Bakta writes it: start after end in the tables, join() across the origin in a circular GenBank record, and in the GFF3 its end plus the sequence's length on a region marked Is_circular=true. A gene near the origin of such a sequence is read across it too, so Rop on plasmid pOSAK1, of which Bakta called the first 141 bp, comes out whole. Annotated against K-12, Sakai with NCBI's headers now has 5331 genes in common with the same genome under bare names, which Bakta reads as linear, and differs in stcE and Rop, whole where the linear reading has 804 of stcE's 886 residues and no Rop, and in two phage genes Bakta starts elsewhere on a complete genome Typed closed genomes were annotated as linear: PATHOTYPR renamed a genome of one sequence to _ and nothing else, so the description Bakta reads a complete, circular replicon from never reached it, whatever the header said, and a gene across the origin of a closed genome came out as two pieces under one name, marked 5' and 3' partial. Only the sequence's ID is replaced now, and the description after it stays, as with --skip_typing. Typed from NCBI's headers, CDC1551, CP110619, AL123456.3 and M. bovis get the same genes as before, in place, name, flags and protein, though Bakta starts a few of its own calls elsewhere on a complete genome (dnaA at 1 instead of 34), so the gene IDs that are Bakta locus tags can be numbered differently. H37Rv turned so that the origin cuts gyrB, gyrA or pknB gives back all 4031 GenBank CDS, where it gave 4030 with the cut gene in two pieces. The GenBank record of such a genome is marked circular, with the description in its definition, and its GFF3 has a region marked Is_circular=true The report's duplication summary: it printed the number of homologous community pairs as markdown from a chunk that did not pass its output on as markdown, so the report showed ## 48 homologous community pairs detected across all genomes. in a code box. It is now a sentence. The example report's duplication table had columns GET_ALLDUP never writes, so its Network Metrics tab said there were none; the demo data now has the columns GET_ALLDUP writes and the example is rendered again Modularity in homologous_communities.tsv: Modularity1 and Modularity2 were computed with the whole community as its only group, which makes them 0 whatever the community, as on all 29 rows of CDC1551 against M. bovis. The two columns are gone COG categories in homologous_communities.tsv: with numpy 2, as in the container, Elements1 and Elements2 read {'L': np.int64(4)}, and they counted each protein eggNOG gave no category in category + or -, since its ID keeps the strand FIX_CDS wrote. They are now plain counts, the largest first and ties in alphabetical order rather than in an order that changed from run to run, and a protein without a category counts as -, eggNOG's mark for none The order of -profile: from Nextflow 25.10, whose strict parser applies the profiles in the order -profile names them and lets the last one win, the laptop caps of the container profiles took over from the others: -profile slurm,singularity held every task of a cluster run to the launching machine's CPUs and 16 GB, where -profile singularity,slurm gave it 48 CPUs and 128 GB, -profile test,docker ran the test with 16 GB and -profile highmem,slurm with 128 GB. No profile sets a cap now: they are set after the profiles, from whether highmem, test or a scheduler profile is on, so every order gives what Nextflow 24.04 gave. The parameter docs gave 24 CPUs and 50 GB as the defaults, which no run got The duplication files are the same from one run to the next: homologous_communities.tsv listed its pairs of communities in the order of a set, which changes with Python's hash seed from one run to the next, and with it which community of a pair came first and the last digit of AvgClustering, an average over the community's proteins that networkx went through in the order of their set: 0.5833333333333333 or 0.5833333333333334 for the same pair of CDC1551 and M. bovis. The pairs now come in the order of the communities, the sample first by name on the left, and the metrics go through the proteins in alphabetical order. Each family in duplications_*_groups.tsv lists its proteins in alphabetical order, where they too came in the order of a set, and each sample's two network drawings no longer start from a random layout and a random partition, so every file both steps write is the same run after run No web browser: both duplication steps wrote their networks with pyvis' show(), which also opens the file in a web browser, so a run without a container on a desktop opened a browser tab for every network. They are written as before, without being opened A custom reference with a gene across the origin, or with several sequences: PREPARE_REFERENCE named each reference protein after where its pieces lie, so tagA on plasmid pO157 of E. coli O157:H7 Sakai, join(92527..92721,1..2502) in NCBI's GenBank record, became pO157p01_1-92721, the whole plasmid, and lost its stop; from NCBI's GFF3, which writes it 92527..95223, it came out as its first 65 of 898 residues. Bakta's stcE on the reference, 92563..2502, matched no gene and was listed among the false positives, and the calls were compared with the genes of every sequence at once: seven calls on pO157 and two on the chromosome counted as matched by genes of the other at the same numbers, and the GenBank's 1-92721 hid two more. With Sakai as its own reference, the sample with NCBI's headers came out with tagA as 1..2502, 65 codons short and not marked partial (GenBank), or from Bakta's later start (GFF3), and with bare headers the GFF3 lost the 2502 bp piece altogether. A gene across the origin now ends past the sequence's last base, as GFF3 writes it (pO157p01_92527-95223, with its stop), each protein names its sequence (contig=), Bakta keeps the FASTA's names on the reference, and each call is compared with the genes of its own sequence, on both sides of the origin; calls on a sequence the annotation has no gene on are not judged. Both formats now give the same 5155 proteins and the same 260 false positives, and the sample with NCBI's headers gets tagA exactly, 92527..2502. K-12's 142 false positives are unchanged pipeline_info/params.yml is the run's own: with -resume it came back from the cache, so it held the first run's parameters whatever later runs were given, since its script reads params as a whole, which Nextflow 26.10's default parser leaves out of the task's hash. And all but the first parameter fell outside params:, with a stray YAML line at the end, because Nextflow strips a script's indentation once the script is filled in. DUMP_PARAMS now never comes from the cache, and writes each value as a JSON string under params: Bakta's SLURM QoS only on SLURM: Bakta's jobs asked for --qos=medium under every scheduler, though only SLURM's sbatch takes it, and under -profile sge it took the place of the memory request the sge profile gives every task. The slurm profile now sets it for Bakta The Mycobrowser table is checked whenever it is read: validation checked --mycobrowser_ref only with the functional annotation, though the interactive report reads it in every run, so under --skip_functional_annotation a wrong path went unwarned and the report came out without Mycobrowser's categories The duplication networks draw offline: both duplication steps loaded their networks' drawing library from a CDN, so without the internet the pages drew nothing. The library now goes inside each page, about 0.7 MB environment.yml pins eggNOG-mapper 2.1.13, the version the container ships, where it said 2.1.12 The pangenome shared no gene between samples: PANGENE aligned each sample's own proteins to the other samples' genomes, ran pangene once per sample and named each column _#0, and REPORT_INPUTS merged those matrices on gene names that carry the sample's name and coordinates (A4_Mbovis_Rv0001;+;0-1524, L4_CDC1551_Rv0001;+;0-1524), so no row was ever in two samples and no gene was ever core. With N samples the matrix had N(N-1) columns: on the first 2 Mb of CDC1551, M. bovis and H37Rv the report showed 5379 gene families in 6 samples, all shell, with 200 genes of one genome as its heatmap, and as no genome was searched for its own genes, the interactive report found no gene in one genome alone. PANGENE now uses pangene as it is meant to be used: one set of proteins, the reference proteome and each sample's genes that no reference gene matches, aligned to every genome, the sample's own included, and one graph for the run, N alignments where there were N(N-1). A row is a reference gene (Rv1506c) or, outside the reference, one sample's copy of a gene (L4_CDC1551_01396), and a column a sample, so the same run shows 1810 genes, 1760 of them in all three, RD4 and RD10 missing from M. bovis only, the phiRv1 prophage from CDC1551 only, and three genes of the RvD2 region that CDC1551 and M. bovis carry and H37Rv lacks. Its files are now PANGENE/pangenome.{gfa,bubbles.txt,Rtab,stat}, one set for the run instead of one per sample, the graph statistics, which were never published, among them","kind":"changelog"},{"page":"Changelog","section":"Unreleased / Report","url":"about/changelog.html#unreleased-report","text":"A report organised by question, results/amap_report.html: next to final_report.html, a new process, AMAP_REPORT, writes one self-contained file from each genome's own outputs: a table of the genomes with the values far from comparable genomes flagged, a map of every reference gene in every genome where regions of difference show as blocks, a lookup of one gene in every genome, the correction from Bakta's calls to AMAP's genes with the genes behind each step, the pseudogenes by lineage, what lies outside the reference, and the functional profile, the categories that differ most between lineages first. It needs only what the correction leaves, so a run with --skip_functional_annotation, which gets no final_report.html, gets this one, and it names the genomes of the sample sheet that produced no annotation. Fifteen MTBC genomes make 0.5 MB (final_report.html is 4.4 MB for twelve); it draws everything with its own JavaScript and needs only Python's standard library to write","kind":"changelog"},{"page":"Changelog","section":"Unreleased / Code","url":"about/changelog.html#unreleased-code","text":"reconcile_coordinates is gone: it read reference coordinates from protein identifiers that have not carried them since March 2026, so it changed nothing. The paralog check does its job with the sample's own offsets","kind":"changelog"},{"page":"Changelog","section":"Unreleased / Documentation","url":"about/changelog.html#unreleased-documentation","text":"A website in plain HTML, drawn with real genes: MkDocs is gone. The front page starts a newcomer in three steps and builds the command for their machine, the pipeline is a metro map whose stations are main.nf's processes, and the four cases, the missing genes and the harder cases (paralogs, small proteins, contig ends, frameshifts, copies) are drawn on genes of CDC1551, M. bovis and E. coli from the benchmark. The tutorial and user guide stay markdown, readable on GitHub, and tools/build_docs.py turns them into pages of the same site, dark by default The site is checked against the code: tools/check_site.py runs every drawn window through bin/, compares the thresholds on the pages with the constants in the code, the metro map with main.nf and every option with the pipeline's parameters, so a change that makes the site wrong fails the docs workflow. tools/figure_data.py redraws the windows from a benchmark run What the docs said that the pipeline does not do: with --skip_functional_annotation a run still writes AMAP/, with the corrected table and sequences, where the outputs page said it did not; no step writes PATHOTYPR/renamed_fastas.tsv; and the 19-residue toxin the algorithm page names is IbsC (b4665), not an ldr toxin A drawing on every page that explains something: the duplication family and communities of CDC1551 and M. bovis, from a real run of both duplication steps; the sample sheet with its tabs and the messages the validator prints; the ranges a genome is validated against; the container and what it holds; a run on a cluster; what happens to a task that fails; the report section by section; the five outcomes and the three inputs of a decision; the command builder on the quickstart. Most are built from the code or the config, so they change when it does The benchmark on the front page: the seven genomes whose answer is known, and how many of their genes AMAP finds, checked against tests/benchmark/README.md. A shared link to any page shows a card drawn from the front page's genes More that the docs got wrong: the duplication page listed ten network metrics, the table has four, and its columns ending in 1 and 2 are the two communities of a pair, not two levels; Nextflow needs Java 17, not 11; --skip_functional_annotation leaves no GFF3 or GenBank, which come after eggNOG; tasks are capped at 16 GB on one machine and at 128 GB under a scheduler profile, not 50 GB; and lineage typing is not skipped on its own for other species, only Mycobrowser is The tutorial's numbers are today's: its case counts and QC line, and their copies on the front page, the outputs page and the custom-reference page, came from runs in July. They now come from the tutorial's own commands run again on 18 October 2026 with --skip_functional_annotation, the QC line from GET_OUTPUTS' own script run on that run's corrected genes: CDC1551 has 4014 genes and 123 pseudogenes as AMAP calls them, where the old line counted Mycobrowser's 13, and Sakai keeps 1387 genes K-12 lacks, not 1356. The tutorial also says where --validate_only writes its verdict and where the number of reference proteins extracted is, as neither reaches the console The README in the site's style: the metro map and the five outcomes in light and dark, the benchmark's scores, and the links a newcomer needs first The README opens with the logo, and its drawing plays: the picture at the top was a still of the front page's drawing, dark whatever GitHub's theme. It now plays the drawing's four steps, in light or dark as the theme is, made by tools/pictures.mjs from the front page's own figure. The README's metro map gains the interactive report's station One DOI: the README, CITATION.cff and every page of the website cited the DOI of one version on Zenodo, and the manifest, main.nf and the interactive report the concept DOI, which always resolves to the latest version. All of them cite the concept DOI now Bakta's options on a SLURM cluster: the HPC page says how to give Bakta a site's options, since for Bakta the QoS the slurm profile sets takes the place of -process.clusterOptions The website is served as it is built: GitHub Pages serves docs/ from main, and by default runs Jekyll over it first, which would have made a page of each markdown file with front matter at the address of the page tools/build_docs.py builds from it. docs/.nojekyll turns Jekyll off, and tools/check_site.py fails without it","kind":"changelog"},{"page":"Changelog","section":"Unreleased / Testing","url":"about/changelog.html#unreleased-testing","text":"tests/selftest/h37rv_selftest.sh annotates H37Rv with itself inside the container and fails unless every GenBank CDS comes back exactly tests/test_cache_keys.py fails when a process runs AMAP code that is not in its cache key, or when its script names a bin/ entry as a bare word without declaring it tests/benchmark/benchmark.sh scores the correction algorithm on seven genomes whose right answer is known (H37Rv twice, CDC1551 complete and in 40 contigs, M. bovis, E. coli K-12 and Sakai), downloading them and running Bakta and miniprot in the container the first time, and lists every gene that changed since an earlier run. A gene whose sequence changed counts as found when a gene with its name covers half of its own bases there, not half of the stretch it lands on, which an insertion can make far longer (63 kb with a prophage inside for b1004 in Sakai); a gene mostly deleted is not counted. Changed genes found: 50 of 50 in CP110619, 860 of 862 in CDC1551, 1515 of 1515 in M. bovis and 3363 of 3367 in Sakai, and the README says why each of the other six is not tests/test_name_by_lineage.py runs PATHOTYPR's own script, rendered as Nextflow renders it, with a stand-in for pathotypr that writes the summaries pathotypr wrote for CDC1551 complete and in 40 contigs, and fails unless Bakta would read a renamed genome as circular exactly when it reads the original so tests/test_sample_ids.py runs TYPE_CASES, GET_OUTPUTS, GET_COUNT, GET_DUPLICATIONS, REPORT_INPUTS and GET_ALLDUP as Nextflow renders them, chained as the pipeline chains them, for a typed and an untyped run whose sample IDs hold underscores, and then the report's own R code where R is installed, and FINAL_REPORT's own script on a run's tables and on one that leaves every table it can empty tests/test_validate_inputs.py runs VALIDATE_INPUTS's own script, rendered as Nextflow renders it, in a task directory laid out as Nextflow lays one out: genomes in a folder the task cannot see, relative paths, two genomes with the same file name and a sheet of 500 samples; and checks both databases, eggNOG's in each state EGGNOG_DB can leave it in tests/test_eggnog_db.py runs EGGNOG_DB's own script, rendered as Nextflow renders it, with its folder staged as Nextflow stages an input and stand-ins for eggNOG-mapper's download and build: the database is built in that folder whatever it is called, one already there is used as it is, and a short bacteria.dmnd is built again tests/test_eggnog_annotation.py runs ANNOT_EGGNOGMAP_BATCH's own script the same way, with a stand-in for emapper.py that looks for the database as eggNOG-mapper does: the database is found whatever its folder is called, a folder called split, where the script writes each sample's annotations, is left as it was, and the tag and log count a single sample as one tests/test_reference_files.py runs the scripts of BAKTA_ANNOTATOR, PATHOTYPR, GET_OUTPUTS and BUNDLED_REFERENCE, rendered as Nextflow renders them, their own Groovy included, with stand-ins for Bakta and pathotypr and each reference file on the host, under a relative path or inside the container; and fails when a process reads a reference file's param other than as what it falls back on when nothing is staged; and checks the Mycobrowser table whenever a step reads it tests/test_pangene_genomes.py runs PANGENE_GENOME and PANGENE's scripts as Nextflow renders them on real genomes of CDC1551, M. bovis and H37Rv cut around RD4 and RvD2 (tests/data/pangenome/, cut again by its make_fixture.py), with stand-ins for miniprot, pangene, k8 and gfatools that hand names on as the real ones do, and fails unless every genome, the sample's own included, is searched with the same proteins, each column is named after its genome, typed or not, a genome none of them is found in keeps a column of zeros, and a protein two samples share goes in once. Where the real tools are installed, as in the container, RD4 must come out missing from M. bovis only and the three RvD2 genes in CDC1551 and M. bovis only, one row each tests/test_report_tables.py merges the matrix PANGENE writes as REPORT_INPUTS does and checks that the report reads every table a # can reach without a comment character; where R is installed, as in the container, it also runs the report's pangenome code on it and counts the genes all its samples share tests/test_cog_counts.py counts COG categories as count_annot.py does, for genes in none, in two and in lowercase, then runs the steps after the correction as tests/test_sample_ids.py chains them, with eggNOG giving some genes no category, then no gene one, then some samples no gene in a category another sample has genes in, and reads what the report's own overview, per-COG figures and statistics table show where R is installed: each category in a figure of its own, with every sample, at 0 where it has no gene. The Mycobrowser figures, statistics and ratios to the median are read the same way for a sample with no gene in one of their categories tests/test_duplication_report.py runs GET_DUPLICATIONS and GET_ALLDUP as Nextflow renders them, chained as the pipeline chains them, with eggNOG giving one copy of a duplicated gene a category, another none and the last no row at all, and checks the columns and COG counts of homologous_communities.tsv; where R is installed, it knits the report's duplication summary on that table and on the one GET_ALLDUP writes when nothing is duplicated. It also fails when a chunk of the report prints markdown without passing it on as markdown tests/test_profile_caps.py fails when a profile sets a cap, or when the order of -profile changes one: it reads nextflow.config and applies its profiles in every order, as both of Nextflow's parsers do, and with Nextflow on PATH also asks nextflow config tests/test_qc_pseudogenes.py runs the steps after the correction as tests/test_sample_ids.py chains them, with FIX_CDS calling some genes pseudogenes and Mycobrowser marking another, with Mycobrowser on and off, and checks that *-AMAP.qc.tsv, REPORT_INPUTS and the report's own QC table count the pseudogenes AMAP called, as many as the GFF3 and the GenBank mark tests/test_duplication_reproducible.py runs get_duplications.py and network_genomes.py under three hash seeds, on the Diamond tables of three genomes with a family whose average clustering changes with the order of its proteins, and fails unless every file they write is the same under all three; it also fails when either script opens a web browser, with hits or without, as a stand-in browser that records what it is asked to open tells, and when a network page asks a CDN for its drawing library tests/test_custom_reference.py extracts a reference whose plasmid has a gene across its origin from a GenBank record and from GFF3 written either way, on either strand, compares Bakta's calls with it sequence by sequence, and runs PREPARE_REFERENCE's own script, rendered as Nextflow renders it, with a stand-in for Bakta that names sequences as Bakta does tests/test_amap_report.py writes the report from four genomes of a real run cut to two stretches of the reference (tests/data/report/, rebuilt by its make_fixture.py), where M. bovis lacks RD4 and a contig end cuts dnaE2, plus a gene across the origin of a circular plasmid, and checks that every gene, and every Bakta call AMAP did not keep, comes back out of the page as it went in, with Python and, where node is installed, with the page's own JavaScript, along with the states, the flags, the correction's steps and what the process hands the script tests/test_dump_params.py runs DUMP_PARAMS with Nextflow where it is installed, under both parsers, with values holding quotes, $, backticks, a backslash and a line break, and again with -resume and one value changed; everywhere, it fails if the step can come from the cache tests/test_cluster_options.py reads which profile gives Bakta the QoS, and with Nextflow resolves Bakta's and every task's cluster options for each scheduler, in both orders with a container and under both parsers tests/test_environment_pins.py fails when environment.yml pins a tool at a version docker/Dockerfile does not install tests/test_citation.py fails when anything cites AMAP with a DOI other than the concept DOI of the manifest tests/test_clean_proteome.py runs the steps after the correction as tests/test_sample_ids.py chains them, with FIX_CDS calling some genes pseudogenes, one with a stop codon in its protein and the others without, and Mycobrowser marking a gene AMAP finds intact, with Mycobrowser on and off, and checks that *-AMAP.clean.faa holds every protein FIX_CDS wrote but the pseudogenes', as it wrote them: the CDS the GFF3 does not mark pseudo=true, with the GenBank's translations tests/test_report_pseudogenes.py runs the steps after the correction as tests/test_sample_ids.py chains them, with FIX_CDS calling a different number of pseudogenes in each sample, none in one, and Mycobrowser marking a gene that every sample has intact, with Mycobrowser on and off, and checks that REPORT_INPUTS hands over each master table row with its sample; where R is installed, it runs the report's own pseudogene section on what REPORT_INPUTS made and checks that it draws each sample at the number AMAP called, the QC table's, a sample with none at 0, under a title that does not name Mycobrowser CI's Python 3.9 job runs on Ubuntu 24.04: GitHub's ubuntu-latest became Ubuntu 26.04, for which setup-python has no Python 3.9, so the job stopped at setup before running a test. 3.9 is the container's Python, so it now runs on 24.04; 3.11 stays on the latest 753 tests, up from 92","kind":"changelog"},{"page":"Changelog","section":"1.0.1, 25 July 2026","url":"about/changelog.html#v1-0-1","text":"Bug fixes, most of them found by running the pipeline on real genomes rather than by reading it. Several changed what the pipeline outputs, so results from 1.0.0 are not directly comparable.","kind":"changelog"},{"page":"Changelog","section":"1.0.1 / Correctness","url":"about/changelog.html#v1-0-1-correctness","text":"Genes no longer vanish during overlap resolution: _insert_with_dedup queued entries for eviction while scanning for conflicts, and applied them even when it then abandoned the insert, so genes were deleted with nothing put in their place Reference pseudogenes are recognised for any organism: the check was gated on the identifier starting with Rv, so on every other organism every reference pseudogene was extended to the next stop and lost its flag. The same gene came out differently depending on what it was called The extracted reference proteome carries its stop codon: extract_ref_proteins.py wrote coordinates spanning the stop but a protein without it, so miniprot's alignment ended three bases short of every Bakta CDS and no gene could ever reach case 2. On E. coli O157:H7 this moved 3325 genes out of conflict resolution Mycobrowser join key: built with rvs.split('_')[0], which returned the whole string for a gene with several references (Rv0094c, Rv3467) and truncated any name containing an underscore (PE_PGRS1 became PE). 44 more genes get a functional category on a real MTBC sample, none lose one Annotation table and FASTA files agree: check_cds.py wrote the FASTA files from dictionaries filled before its final deduplication, and deduplicate_rvs.py matched FASTA headers on the gene id alone, so removing one copy of a multi-copy gene deleted all of them Untranslatable genes are dropped from the table, not left in it with no sequence anywhere","kind":"changelog"},{"page":"Changelog","section":"1.0.1 / Crashes","url":"about/changelog.html#v1-0-1-crashes","text":"--skip_mycobrowser and organism-agnostic mode: the placeholder frame was built with a duplicate Locus column, so pd.merge refused to run and GET_OUTPUTS failed on every run without Mycobrowser Miniprot PAF parsing: pandas inferred the column count from the first row, and miniprot varies its trailing tags per alignment, so any file whose first row was narrower aborted the parse Biopython 1.85 and newer: SeqFeature.strand was removed; to_panaroo.py and extract_ref_proteins.py now read it off the location -profile test: pointed at a sample sheet naming paths that are not in this repository. It now annotates the reference genome bundled in the container, so it needs no data and nothing downloaded","kind":"changelog"},{"page":"Changelog","section":"1.0.1 / Silent data loss","url":"about/changelog.html#v1-0-1-silent-data-loss","text":"First Diamond hit: read_and_create left header inference on while stripping # comments, so the first alignment became the column names and disappeared from the duplication analysis Multi-part gene names in rename_fastas.py: headers were cut at the third underscore, collapsing every PE/PPE gene of a sample onto one identifier eggNOG annotation rate: counted with notna(), and emapper writes - for fields it could not fill, so the QC metric read close to 100% whatever the run had done","kind":"changelog"},{"page":"Changelog","section":"1.0.1 / Performance","url":"about/changelog.html#v1-0-1-performance","text":"reconcile_coordinates is linear rather than quadratic: it scanned every protein and every reference for each gene, calling a regex on each candidate. Indexed by gene name up front, a 4000-gene genome goes from 18.3 s to 0.011 s with identical output over 200 randomised genomes deduplicate: list.count() per element and a full scan per duplicated gene, replaced with a Counter and a single grouping pass","kind":"changelog"},{"page":"Changelog","section":"1.0.1 / Report","url":"about/changelog.html#v1-0-1-report","text":"Layout: the stylesheet was losing to the one rmarkdown inlines afterwards, so the article sat underneath the table of contents and the left logo on top of the title Self-contained without a network at render time: jQuery came from a CDN and the second logo from GitHub. pandoc inlines those when the render machine has a network and leaves them dangling when it does not, so a report rendered on an isolated node arrived with a broken image Half the size: 8.2 MB to 4.4 MB, by shipping only the plotly traces actually used and quantising the figures The version in the report is the version that produced it: it was written by hand in three places in the Rmd, so any release that forgot to update them published a report naming the wrong version. It now comes from the manifest in nextflow.config","kind":"changelog"},{"page":"Changelog","section":"1.0.1 / Documentation and infrastructure","url":"about/changelog.html#v1-0-1-documentation-and-infrastructure","text":"Documentation site built with MkDocs Material, published to GitHub Pages, including a tutorial with the numbers from real runs and a rendered example report Docker image workflow: builds, smoke tests and publishes on a v* tag or on demand Tests workflow enabled: it had been sitting disabled and would have failed, since its dependency list was missing plotly and its syntax check had drifted five files behind requirements-test.txt: one dependency list for CI, the docs and a local checkout. environment.yml was missing the Python plotly, so a conda environment built from it could not start classify_cases.py 92 tests, up from 58","kind":"changelog"},{"page":"Changelog","section":"1.0.0, 1 April 2026","url":"about/changelog.html#v1-0-0","text":"","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Correctness","url":"about/changelog.html#v1-0-0-correctness","text":"Strand-aware gene dictionary: combined_dict keys changed from (Start, End) to (Start, End, Strand): genes on opposite strands at the same coordinates no longer collide silently Consistent 1-based lengths: all length calculations now use End - Start + 1 (15 sites fixed) Robust ID cleaning: regex _\\d+-\\d+$ replaces brittle 4-part underscore rule, so PE_PGRS genes are handled correctly Exact gene name matching: prevents false matches (Rv3508 ≠ Rv3508c, Rv1 ≠ Rv10) IntervalTree half-open intervals: all 9 operations corrected from [Start:End] to [Start:End+1] Cross-strand gene preservation: merge operations now include Strand to prevent loss Paralog preservation: multi-copy PE/PPE genes no longer collapsed during groupby Pseudogene propagation: Is_Pseudogene column now reaches master table and report","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Robustness","url":"about/changelog.html#v1-0-0-robustness","text":"Pre-flight validation: new VALIDATE_INPUTS process checks FASTA format, N-content, GC%, genome size, duplicate headers and database paths, before any computation --validate_only mode: check all inputs without executing the pipeline eggNOG DB integrity: bacteria.dmnd <100 MB detected as corrupted → automatic rebuild Bakta DB pre-check: actionable error message with download command OOM retry: memory scales with task.attempt (e.g., 50 GB → 100 GB → 150 GB) 0-protein handling: eggNOG batch handles empty inputs gracefully Completion handler: prints duration, succeeded/failed/ignored counts","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Reproducibility","url":"about/changelog.html#v1-0-0-reproducibility","text":"nextflow.manifest: version 1.0.0, Nextflow ≥23.04.0, DOI, homepage SOFTWARE_VERSIONS: YAML + TSV with exact versions of 12 tools DUMP_PARAMS: complete parameter dump per run Diamond sorted output: deterministic results regardless of thread count environment.yml: Conda environment with pinned versions Louvain seed: fixed to 42 for reproducible community detection","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Performance & HPC","url":"about/changelog.html#v1-0-0-performance--hpc","text":"Dynamic resource scaling: memory grows with each retry attempt Node-local scratch: --scratch true (default on SLURM/SGE/PBS/LSF) Diamond block size tuning: auto-calculated from available RAM Bakta local temp: --tmp-dir on node-local disk (avoids NFS metadata storms) eggNOG batch mode: single run for all samples (10× faster, 50× less filesystem I/O) Thread utilization: Bakta 12, Diamond 6-24, Miniprot 2×3, Pangene 2×N SLURM/SGE/PBS/LSF profiles: with time limits, queue sizes, rate limiting storeDir for eggNOG DB: persists across runs, survives nextflow clean","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Scalability","url":"about/changelog.html#v1-0-0-scalability","text":"find -print0 | xargs -0: replaces echo|tr|grep in REPORT_INPUTS (handles 500+ files) publishDir mode: 'link': hardlinks instead of copies for per-sample outputs cache 'lenient': tolerates NFS timestamp drift between cluster nodes awk close(): prevents file handle exhaustion on 500+ sample eggNOG splits JVM tuning docs: NXF_OPTS guidance for large runs","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Organism-Agnostic","url":"about/changelog.html#v1-0-0-organism-agnostic","text":"Custom reference: --ref_annotation + --ref_genome replaces built-in H37Rv Auto-detect mode: --skip_mycobrowser auto-enabled when genus ≠ Mycobacterium Dynamic reference comparison: report uses median distribution (not hardcoded H37Rv) Auto-extending color palette: non-MTBC lineages get colors automatically Generic labels: \"Ratio to Median\" instead of \"Ratio to H37Rv\" Manifest updated: \"Assisted Microbial Annotator Pipeline\"","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Output Quality","url":"about/changelog.html#v1-0-0-output-quality","text":"Clean proteome: {sample}-AMAP.clean.faa without pseudogenes or internal stops Annotation QC: per-sample metrics (genome size, GC%, coding density, hypothetical %, annotation rate) GFF3 spec compliance: ##sequence-region pragmas, attribute escaping, sorted features QC report section: interactive table + coding density / hypothetical % / genome size plots","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Code Quality","url":"about/changelog.html#v1-0-0-code-quality","text":"58 unit tests: covering IDs, overlaps, reconciliation, validation, clean proteome, QC, merge GitHub Actions CI: Python 3.9 + 3.11 matrix (currently disabled, ready to activate) Type hints: core modules with Coord, Coord3, CoordDict aliases Module docstrings: coordinate conventions, naming schemes documented","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Usability","url":"about/changelog.html#v1-0-0-usability","text":"Structured startup banner: shows all configuration in a formatted box --version flag: prints pipeline + Nextflow version Progress logging: ▸ [BAKTA] Annotating G26689... for every major step Actionable error messages: boxed errors with cause + fix + re-run command Sample count at startup: confirms TSV was parsed correctly","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Documentation","url":"about/changelog.html#v1-0-0-documentation","text":"README: updated for v1.0.0 with all new parameters, outputs, and organism-agnostic usage CHANGELOG: this file Output tree: documents .clean.faa, .qc.tsv, pipeline_info/ new files","kind":"changelog"},{"page":"Changelog","section":"1.0.0 / Bug Fixes (85 total)","url":"about/changelog.html#v1-0-0-bug-fixes-85-total","text":"20 critical (🔴) + 65 improvements (🟡) See commit history on fix branch for full details","kind":"changelog"},{"page":"Changelog","section":"0.1.0, 2025","url":"about/changelog.html#v0-1-0","text":"Initial release MTBC-focused annotation pipeline Bakta + Miniprot + eggNOG integration HTML report with COG, Mycobrowser, pangenome analysis","kind":"changelog"},{"page":"Citation","section":"","url":"about/citation.html","text":"Ruiz-Rodriguez, P., & Coscolla, M. (2026). AMAP (Version 1.0.1) [Computer software]. The repository also carries a CITATION.cff, which GitHub reads to offer a formatted citation from the sidebar.","code":"@software{Ruiz-Rodriguez_AMAP_2026, author {Ruiz-Rodriguez, Paula and Coscolla, Mireia}, doi {10.5281/zenodo.8024446}, license {GPL-3.0}, month apr, title {{AMAP}}, url {https://github.com/PathoGenOmics-Lab/AMAP}, version {1.0.1}, year {2026}}","kind":"page"},{"page":"Citation","section":"Authors","url":"about/citation.html#authors","text":"Paula Ruiz-Rodriguez and Mireia Coscolla I2SysBio, University of Valencia-CSIC, FISABIO Joint Research Unit Infection and Public Health, Valencia, Spain.","kind":"section"},{"page":"Citation","section":"Cite the tools too","url":"about/citation.html#cite-the-tools-too","text":"AMAP orchestrates other people's work. If a result depends on one of these, cite it as well. Nextflow Runs the pipeline Bakta Baseline annotation Miniprot Reference proteome alignment eggNOG-mapper Functional annotation Diamond Duplication searches pangene Pangenome graph pathotypr MTBC lineage typing The exact version of each one used by a given run is recorded in results/pipeline_info/software_versions.yml.","kind":"section"},{"page":"Citation","section":"Reference data","url":"about/citation.html#reference-data","text":"The MTBC reference files bundled in the container come from public repositories. The pathotypr lineage markers are archived on Zenodo under 10.5281/zenodo.19210044, and Mycobrowser functional categories come from mycobrowser.epfl.ch.","kind":"section"},{"page":"Citation","section":"Licence","url":"about/citation.html#licence","text":"AMAP is released under the GPL-3.0 licence. See LICENSE.","kind":"section"},{"page":"Contributing","section":"","url":"about/contributing.html","text":"Bug reports, fixes and suggestions are all welcome. The full guidelines live in CONTRIBUTING.md, and the project follows a code of conduct.","kind":"page"},{"page":"Contributing","section":"Reporting a bug","url":"about/contributing.html#reporting-a-bug","text":"Open an issue at github.com/PathoGenOmics-Lab/AMAP/issues with the command you ran, the failing task's .command.err, and both software_versions.yml and params.yml from pipeline_info/. Those last two pin the exact versions and parameters, which is usually what the answer turns on. Troubleshooting covers the common cases first.","kind":"section"},{"page":"Contributing","section":"Working on the code","url":"about/contributing.html#working-on-the-code","text":"requirements-test.txt covers everything bin/ imports, not only what the tests import directly, and CI installs from the same file. A shorter list appears to work until a test touches classify_cases.py, which pulls in plotly through classify/plotting.py. The suite covers ID handling, overlap arithmetic, case classification, the rules that resolve and recover genes, and the parsers, and it runs in about a second. Anything touching bin/classify/ should come with a test, since the correction logic is where a silent change does the most damage.","code":"git clone https://github.com/PathoGenOmics-Lab/AMAP cd AMAP pip install -r requirements-test.txt python -m pytest tests/ -v","kind":"section"},{"page":"Contributing","section":"Annotating H37Rv with itself","url":"about/contributing.html#annotating-h37rv-with-itself","text":"A change to the correction algorithm should also pass the self-test, which has exactly one right answer: annotating H37Rv against its own reference must give back every CDS of the AL123456.3 GenBank record, at exactly its coordinates and strand, under its own name. The first run spends a couple of minutes on Bakta and miniprot and keeps their output under selftest_h37rv/. After that it only runs classify_cases.py, check_cds.py and deduplicate_rvs.py, in seconds, and exits non-zero with the offending genes listed if anything is missing, misplaced or misnamed. H37Rv is one genome, and the one the defaults were tuned on. The accuracy benchmark in tests/benchmark/ scores six more whose right answer is known: another H37Rv assembly, CDC1551 complete and cut into 40 contigs, M. bovis, and E. coli K-12 and Sakai against K-12 as a custom reference. Give a run a name, and the next one lists every gene that changed: tests/benchmark/README.md explains how the truth is built and what to expect. The quickest way to run the whole pipeline is the test profile:","code":"docker run --rm --platform linux/amd64 -v \"$PWD:/amap\" -w /amap paururo/amap:latest bash tests/selftest/h37rv_selftest.sh GenBank CDS: 4031 AMAP genes: exact and named right: not at exactly their coordinates: 0 wrong: genes that are no tests/benchmark/benchmark.sh benchmark_work before change the algorithm, then after nextflow -profile docker,test","kind":"section"},{"page":"Contributing","section":"Pipeline code and -resume","url":"about/contributing.html#pipeline-code-and--resume","text":"Nextflow hashes a task's inputs and script, not the files the script calls by path, so python3 ${projectDir}/bin/check_cds.py alone would let -resume hand back what the old check_cds.py produced. Every process that runs AMAP code therefore lists those files in ext code, and its script names task.ext.code in a comment, which puts them in the cache key (Nextflow 24.04 and later): A script that imports classify needs classify/*.py in the list. The reverse also bites: Nextflow adds to a task's cache key any bin/ entry its script names as a bare word, which is why PATHOTYPR quotes its 'classify' subcommand. tests/test_cache_keys.py fails when a process runs a file it does not list, lists one that does not exist, or names a bin/ entry it does not declare.","code":"ext code: files(\"${projectDir}/bin/{check_cds.py,deduplicate_rvs.py,classify/*.py}\")","kind":"section"},{"page":"Contributing","section":"Working on the container","url":"about/contributing.html#working-on-the-container","text":"The smoke test checks the tools, the Python and R libraries, the bundled reference data, and runs the test suite with the image interpreter. Worth doing before proposing a change to the Dockerfile, since a library update can break the pipeline scripts without the image looking any different.","code":"docker build --platform linux/amd64 -f docker/Dockerfile -t amap:dev docker/smoke_test.sh","kind":"section"},{"page":"Contributing","section":"Working on these docs","url":"about/contributing.html#working-on-these-docs","text":"The website is plain HTML in docs/, with nothing to build on the server: GitHub Pages serves the folder as it is (docs/.nojekyll keeps it from running Jekyll over the markdown), and it opens from a clone too. The long pages are the markdown files, which GitHub also renders, and tools/build_docs.py writes each one's .html next to it, with the site's header, page list, contents and search. The front page (docs/index.html) and the illustrated walk through the correction (docs/how-it-works.html) are written by hand. Their drawings come from docs/assets/figures.js and how.js, and every gene in them from the windows of real runs in docs/assets/figure-data.js. A line in a markdown page puts one of those drawings there, and GitHub shows nothing in its place. Some drawings are made from the code itself when the page is built, so they cannot drift from it: the sample sheet and its error messages run bin/validate_inputs.py, the validation ranges read its limits, the container reads docker/Dockerfile, and the cluster and failure drawings read nextflow.config. After changing any of those, run build_docs.py again. check_site.py fails when a page no longer matches its markdown, a link or an anchor is broken, a page uses an option the pipeline does not have, a threshold on a page differs from the code, the metro map's stations are out of the order main.nf runs them in, or a drawing shows something bin/ would not do: it runs every window of figure-data.js through the classification, check_cds.py and deduplicate_rvs.py. When a change to the algorithm changes a drawn window, rerun the benchmark, write the windows again, and read the words of the drawing against what changed: The duplications page draws a real family and a pair of communities from docs/assets/dup-data.js, which check_site.py puts back through get_duplications.py and network_genomes.py. tools/dup_run.sh runs the two duplication steps in the container on corrected proteomes, and tools/dup_data.py writes the data from what they found (both explain their arguments). The front page's benchmark scores are checked against tests/benchmark/README.md. The pictures, the example report's sections, the card a shared link shows and the README's light and dark drawings, are screenshots of the site taken by headless Chrome; take them again after the report or a drawing changes: The workflow in .github/workflows/docs.yml runs both checks on every pull request that touches the docs, bin/, modules/, main.nf, nextflow.config, docker/Dockerfile or the benchmark's expected results.","code":"python3 tools/build_docs.py after editing any .md under docs/ tools/check_site.py what CI runs -m http.server -d docs 8000 then open http://localhost:8000 tools/figure_data.py benchmark_work final --reference /reference node tools/pictures.mjs","kind":"section"},{"page":"Contributing","section":"Style","url":"about/contributing.html#style","text":"Keep to what is already there: short paragraphs, GitHub's alerts (> [!WARNING]) for the things that actually bite, and concrete numbers rather than \"large\" or \"slow\". Where a threshold appears in the docs it should match the constant in the code, so if you change one, change the other; check_site.py holds the ones it knows to the code.","kind":"section"},{"page":"Front page","section":"","url":"index.html","text":"AMAP annotates assembled bacterial genomes with Bakta, aligns a trusted reference proteome onto them with miniprot, and referees every disagreement. Built for the Mycobacterium tuberculosis complex, usable with any reference.","kind":"page"},{"page":"Front page","section":"From nothing to a corrected genome","url":"index.html#start","text":"From nothing to a corrected genome Every tool AMAP calls lives in one container image, so Nextflow and a container engine are all you install.","kind":"section"},{"page":"Front page","section":"Each call, held up against the reference","url":"index.html#cases","text":"Each call, held up against the reference Every Bakta call is compared with where the reference proteins align, on the same strand of the same contig. That puts it in one of four cases, and a reference gene with no call at all is a fifth. These are real genes of CDC1551: point at one for its coordinates.","kind":"section"},{"page":"Front page","section":"Checked against genomes whose answer is known","url":"index.html#proof","text":"Checked against genomes whose answer is known The accuracy benchmark runs the correction on seven genomes and scores it against an annotation AMAP did not make: the reference's own genes, carried onto the other genomes by a whole-genome alignment. Every gene a genome carries unchanged is found at exactly its coordinates, under its name. Of the genes that changed, which have no exact answer, 99.8 to 100% still sit where the alignment puts them, under their name.","kind":"section"},{"page":"Front page","section":"Where each step happens","url":"index.html#pipeline","text":"Where each step happens Stations are the processes of main.nf ; lines are what travels between them. Pick a station, or flip an option to see what a run leaves out.","kind":"section"},{"page":"Front page","section":"Files you can open, and the reasons behind them","url":"index.html#outputs","text":"Files you can open, and the reasons behind them Everything lands under results/ , one folder per step. These answer most questions; Outputs has every file.","kind":"section"},{"page":"Front page","section":"The whole run on one page","url":"index.html#report","text":"The whole run on one page This one is the example report: twelve made-up genomes across four lineages, built around a real CDC1551 run. Synthetic data , so read the layout, not the numbers. Click to explore.","kind":"section"},{"page":"Front page","section":"The questions people ask first","url":"index.html#faq","text":"The questions people ask first","kind":"section"},{"page":"Front page","section":"The sample sheet is rejected","url":"index.html#faq","text":"It has to be separated by a real tab: spaces will not do, and this is the most common reason. Check with cat -t samples.tsv , where real tabs show up as ^I . Keep sample IDs to letters, digits and underscores.","kind":"faq"},{"page":"Front page","section":"Nextflow will not start: \"unsupported class file version\"","url":"index.html#faq","text":"That is Java 8. Nextflow needs Java 17 or newer: conda install -c conda-forge openjdk=17 .","kind":"faq"},{"page":"Front page","section":"A task dies with exit code 137","url":"index.html#faq","text":"On macOS and Windows, Docker runs inside a virtual machine with a memory limit of its own, often 8 GB. Raise it in Docker Desktop under Settings, Resources, Memory, and keep --max_memory at or below it. What each step needs .","kind":"faq"},{"page":"Front page","section":"The first run seems stuck","url":"index.html#faq","text":"The first run pulls a 12 GB image and builds the eggNOG database, which dominates the wall clock; both happen once. A long silence during the database build is not a failure. Point --eggnog_db somewhere you keep, and -resume picks a stopped run up where it left off.","kind":"faq"},{"page":"Front page","section":"Case 2 is at or near zero","url":"index.html#faq","text":"Every gene disagreeing with the reference by a few bases means something systematic is wrong with the reference, not with 4000 genes. The usual cause is a reference annotation from another assembly than the genome given with it. What a healthy summary looks like .","kind":"faq"},{"page":"Front page","section":"Does it work for anything but tuberculosis?","url":"index.html#faq","text":"Yes. Give it a trusted annotation and the genome it describes with --ref_annotation and --ref_genome , and turn off the two steps that are for the tuberculosis complex only. Using your own reference .","kind":"faq"},{"page":"Front page","section":"A task failed. Where do I look?","url":"index.html#faq","text":"Nextflow prints the work directory of the failed task. Its .command.err is almost always the answer, and .command.sh is the exact script that ran. Troubleshooting, by error .","kind":"faq"},{"page":"Step by step, illustrated","section":"","url":"how-it-works.html","text":"One gene at a time, from Bakta's call to what AMAP keeps.","kind":"page"},{"page":"Step by step, illustrated","section":"Agrees","url":"how-it-works.html#agree","text":"Case 2. The four cases. Bakta calls dnaN from 2,052 to 3,260 on the plus strand. miniprot aligns H37Rv's Rv0002, dnaN, onto CDC1551: the whole protein, from 2,052 to 3,260, stop codon included. Same start, same end, same strand and the same length: case 2, a perfect match. Nothing to settle. The call stays as Bakta made it, and is tied to Rv0002. It ends in a stop codon: nothing to change. One gene for each reference protein here: nothing to sort out. It keeps Bakta's locus tag and name, with Rv0002 in its Dbxref.","kind":"story"},{"page":"Step by step, illustrated","section":"Starts late","url":"how-it-works.html#late-start","text":"Case 4. The four cases. Bakta calls dnaA from base 34 to 1,524. H37Rv's Rv0001, dnaA, aligns end to end from base 1 to 1,524. Same strand, same end, but a start 33 bp apart: case 4, a conflicting overlap. One call against one alignment. With a single overlap the reference wins, unless the call is longer by more than 550 bp and the alignment is not complete in its frame. Here the alignment is the longer of the two: AMAP keeps 1 to 1,524. It ends in a stop codon: nothing to change. One gene for each reference protein here: nothing to sort out. No Bakta call has these coordinates, so it is written under the reference protein's name.","kind":"story"},{"page":"Step by step, illustrated","section":"One gene, two calls","url":"how-it-works.html#split","text":"Case 4. The four cases. Bakta calls two short genes, from 9,963 to 10,160 and from 10,523 to 10,828, both hypothetical proteins. H37Rv's Rv0007 aligns end to end over both, from 9,914 to 10,828. Both calls overlap the one alignment with other coordinates: case 4, with several calls on one alignment. The call that overlaps the alignment most is weighed against it. The reference wins if it is at least as long, if the call is longer by less than 500 bp, if the alignment is complete in the call's frame, or if the call spans other reference genes too. Rv0007 is longer than either call: one gene goes in, 9,914 to 10,828. It ends in a stop codon: nothing to change. One gene for each reference protein here: nothing to sort out. Written under Rv0007, as no Bakta call has its coordinates.","kind":"story"},{"page":"Step by step, illustrated","section":"Much longer","url":"how-it-works.html#longer","text":"Case 4. The four cases. Bakta calls a 4,821 bp site-specific DNA methyltransferase on the minus strand. H37Rv's Rv2024c, 516 residues, aligns over 1,518 bp at one end of it: residues 1 to 506, short of the protein's end, with no stop codon. One call against one alignment, with other coordinates: case 4. A call longer by more than 550 bp wins over an alignment that is not complete in its frame: such an alignment stopped short, and dropping a whole gene for it would be the worse mistake. Bakta's call, 3,303 bp longer, is kept. It ends in a stop codon: nothing to change. One gene for each reference protein here: nothing to sort out. It keeps Bakta's locus tag and product, and takes Rv2024c's name in its Dbxref: no other gene carries it, and the alignment spans more than half of the protein.","kind":"story"},{"page":"Step by step, illustrated","section":"Not in the reference","url":"how-it-works.html#new","text":"Case 1. The four cases. Bakta calls four genes in a row: a mannosyltransferase, a sulfite oxidase-like oxidoreductase, an MMPL transporter and a hypothetical protein. No H37Rv protein aligns anywhere along them. A call with no alignment over it on its strand is case 1, potentially novel. A case 1 call goes if Bakta's known false positives cover 80% of it, or if its protein matches the H37Rv genome at 80% identity or more. None is covered, three of the proteins do not align to H37Rv at all and the fourth matches at 60%: all four are kept. They end in stop codons: nothing to change. No reference protein to share: nothing to sort out. They keep Bakta's locus tags and products, with no reference name.","kind":"story"},{"page":"Step by step, illustrated","section":"A known false call","url":"how-it-works.html#false","text":"Case 1. The four cases. Bakta calls a 108 bp hypothetical protein on the minus strand. No H37Rv protein aligns here. Bakta's known false positives are aligned too: calls Bakta makes on H37Rv itself that its curated annotation does not have. One covers this call end to end. Case 1, potentially novel. Covered 100% by a known false positive, more than the 80% that rules a call out: it is dropped. Nothing left here to translate. Nothing to sort out. Nothing is written here.","kind":"story"},{"page":"Step by step, illustrated","section":"Missed by Bakta","url":"how-it-works.html#missed","text":"Missing. The four cases. Bakta calls a gene from 33,168 to 33,497 and bioF2 from 34,239, and nothing between them. H37Rv's Rv0031, 71 residues, aligns between the two, end to end, from 33,526 to 33,738. An alignment with no call over it on its strand: a missing reference gene. The two calls match Rv0030 and Rv0032 exactly, case 2. A protein is missing when no gene accounts for any of its placements: none sits on it, in the same frame, covering half of it. So Rv0031 goes back in at its best placement. It ends in a stop codon: nothing to change. One gene for each reference protein here: nothing to sort out. Written under Rv0031.","kind":"story"},{"page":"Step by step, illustrated","section":"On its paralog's locus","url":"how-it-works.html#paralog","text":"Paralogs. The harder ones. Two loci of CDC1551, about 208 kb apart. Bakta calls a REP13E12 repeat protein at each, from 400,321 and from 607,987. H37Rv's Rv0336 and Rv0515 are paralogs, and miniprot places each at both loci, as it reports every placement within 5% of a protein's best. The proteins placed only once around each locus, five on each side, say where it sits in H37Rv: the first at about 400 kb, where Rv0336 belongs, the second at about 607 kb, where Rv0515 does. Each protein's placement on the other's locus is dropped. Each REP13E12 call now overlaps one alignment, starting 69 bp after it: case 4 at both loci. The calls around them match their proteins exactly, case 2. The reference wins both: Rv0336 at 400,252 and Rv0515 at 607,918. Both end in stop codons: nothing to change. One gene for each reference protein here: nothing to sort out. Each locus is written under its own protein.","kind":"story"},{"page":"Step by step, illustrated","section":"Too small for miniprot","url":"how-it-works.html#small","text":"Exact search. The harder ones. E. coli K-12 against its own annotation. Bakta calls fau and serA, and nothing in the 388 bp between them. miniprot places fau and serA. IbsC, a toxic peptide of 19 residues, is too small to seed an alignment of its own: miniprot placed it only at 3,194,766, on its neighbour IbsD's locus, where 3 of its residues differ. Every protein of 8 residues or more without an exact, complete placement is looked for in the six-frame translation of the genome, a start codon, its residues, then a stop: IbsC is here, from 3,056,890 to 3,056,949 on the minus strand. fau: Bakta's call starts 54 bp before the alignment, case 4. serA matches exactly, case 2. IbsC has no call over it: a missing reference gene. fau takes the reference's start, and IbsC goes in where the exact search found it. All three end in stop codons: nothing to change. One gene for each reference protein here: nothing to sort out. IbsC is written under its own name, b4665.","kind":"story"},{"page":"Step by step, illustrated","section":"Cut by a contig end","url":"how-it-works.html#contig-end","text":"Partial. The harder ones. CDC1551 cut into 40 contigs. Near the end of contig_10 Bakta calls Rv1240's gene and two short genes on the minus strand, and nothing on the plus strand. At the end of contig_9 it calls sugC and a short hypothetical protein. H37Rv's Rv1239c, 367 residues, aligns on the plus strand from 101,478 to the very end of contig_10, 102,146: its first 223 residues. Its last 144, which miniprot does not report beside the larger piece, are aligned again on their own, and land at the very end of contig_9: the other piece. Rv1240's call and sugC's match exactly, case 2. The two short calls on contig_10 have nothing aligned, case 1. The short call on contig_9 overlaps the piece with other coordinates, case 4. On contig_10 Rv1239c has no call over it on its strand: a missing reference gene. Rv1239c goes back in at its placement on contig_10, and on contig_9 the piece wins over the shorter call, as the reference does. The two short calls on contig_10 go: their proteins match the H37Rv genome at 98 and 99% identity, so they are not new. Each piece's frame reaches its contig's end with no stop codon, from within 30 bp of it: the assembly cut the gene. The piece on contig_10 is marked partial at its 3' end, the one on contig_9 at its 5' end. Neither is a pseudogene. Two genes carry Rv1239c, but they are the pieces of one cut gene, not copies, and both stay. Their IDs must differ: one is written Rv1239c and the other Rv1239c_2, both with Rv1239c in their Dbxref. The cut ends are written the way NCBI writes them: partial=true with end_range in the GFF3, and a > before the cut end in the GenBank location.","kind":"story"},{"page":"Step by step, illustrated","section":"Runs into the next gene","url":"how-it-works.html#frameshift","text":"Pseudogene. The harder ones. M. bovis AF2122/97. Bakta calls one gene from 689,275 to 690,306, between mce2A's and mce2C's. Two H37Rv proteins align inside it: Rv0590 end to end, with one base too many for its frame, and Rv0590A after it. The call overlaps both alignments with other coordinates: case 4. A call over more than one alignment is several reference genes read as one, and the reference wins: Rv0590 and Rv0590A go in as two genes. Rv0590 has no stop codon where its protein ends. The next stop in its frame is 67 residues past the end of the protein, at 690,306, the end of Rv0590A and of Bakta's call. An extension may reach 10 residues past the protein. So Rv0590 keeps its extent and is flagged a pseudogene, with the reason. One gene for each reference protein here: nothing to sort out. The pseudogene is written the way NCBI writes one: pseudo=true and the reason in Note, /pseudo and /note in the GenBank, with no translation.","kind":"story"},{"page":"Step by step, illustrated","section":"Two real copies","url":"how-it-works.html#copies","text":"Copies. The harder ones. E. coli O157:H7 Sakai, against K-12. Bakta calls a 4.2 kb RhsD gene at 617,058 and another at 2,046,050, 1.4 Mb apart. K-12's RhsD (b0497) aligns at both, over most of each gene but short of its end. Each RhsD call overlaps its alignment with other coordinates, case 4. The other calls have nothing aligned, case 1: the three at the second locus are kept, and the short one at the first goes, as one of the calls Bakta makes on K-12 itself covers it. Each call is longer than its alignment by less than 550 bp: the reference wins at both. Neither alignment reaches a stop codon. Each gene is extended to the next stop in its frame, which completes the protein and is where Bakta's calls end. Two genes now carry b0497. A genome can have a gene twice: the second stays, as it is at least 90% as long as the first, clear of it and on no other reference gene's locus. The copy later in the genome is named b0497_2, and the duplication analysis counts it. Both copies are written, each with an ID of its own.","kind":"story"}];
diff --git a/docs/assets/site.css b/docs/assets/site.css
index 79b45aa..9d0cd4d 100644
--- a/docs/assets/site.css
+++ b/docs/assets/site.css
@@ -149,12 +149,15 @@ pre {
font-size: 0.88em;
}
+/* A chip of code never splits inside a word: short ones (.nw) do not wrap at
+ all, and the docs builder lets a long one wrap only after a /, _ or .
+ (see the Code section). */
:not(pre) > code {
background: var(--bg-subtle);
border: 1px solid var(--line);
border-radius: 6px;
padding: 0.08em 0.38em;
- overflow-wrap: anywhere;
+ overflow-wrap: normal;
}
h1,
@@ -1039,6 +1042,13 @@ p {
color: var(--code-ink);
border-radius: var(--radius-sm);
border: 1px solid var(--code-border);
+ /* The colour a block's sideways scroll shadow covers with, and the shadow. */
+ --cover: var(--code-bg);
+ --shade: rgba(255, 255, 255, 0.18);
+}
+
+:root[data-theme="light"] .code {
+ --shade: rgba(16, 27, 24, 0.2);
}
.code pre {
@@ -1048,22 +1058,58 @@ p {
line-height: 1.6;
font-size: 0.86rem;
tab-size: 8;
+ /* A block wider than its box shades the edge it continues past. The covers
+ scroll with the text and the shadows stay put, so a shadow shows only
+ where there is more to see. */
+ background:
+ linear-gradient(to right, var(--cover) 40%, transparent) left / 28px 100% no-repeat local,
+ linear-gradient(to left, var(--cover) 40%, transparent) right / 28px 100% no-repeat local,
+ radial-gradient(farthest-side at 0 50%, var(--shade), transparent) left / 12px 100% no-repeat scroll,
+ radial-gradient(farthest-side at 100% 50%, var(--shade), transparent) right / 12px 100% no-repeat scroll;
}
-/* On a phone every block wraps rather than run under the copy button; what
- is copied is the text, not the wrapping. */
+/* On the front page a block wraps on a phone rather than run under the copy
+ button; the docs' blocks keep their layout (see below). */
@media (max-width: 600px) {
.code pre {
white-space: pre-wrap;
- overflow-wrap: anywhere;
}
}
-/* A one-line command that would hide under the copy button wraps instead;
- what is copied is the text, not the wrapping. */
+/* A command wraps at the spaces between its words and never inside one, and
+ a line that wraps hangs the rest of itself four characters further in than
+ it starts (--i is the line's own indent). What is copied is the text, not
+ the wrapping. */
+.code.cmd pre,
.code.one-line pre {
white-space: pre-wrap;
- overflow-wrap: anywhere;
+}
+
+.code .ln {
+ display: block;
+ padding-left: calc((var(--i, 0) + 4) * 1ch);
+ text-indent: calc((var(--i, 0) + 4) * -1ch);
+}
+
+.code :is(.t, .f, .v, .kv),
+code.nw,
+code .nw {
+ white-space: nowrap;
+}
+
+/* A path too long for a phone's line comes apart only after a /. */
+.code :is(.t, .f, .v).path {
+ white-space: inherit;
+}
+
+.code .path .nw {
+ white-space: nowrap;
+}
+
+/* Files, trees and program output keep their layout at every width, and
+ scroll. */
+.prose .code:not(.cmd) pre {
+ white-space: pre;
}
.code .p {
@@ -1083,6 +1129,31 @@ p {
color: var(--code-value);
}
+/* A tab of a tab-separated file, drawn as the sample sheet drawing draws it;
+ the character underneath is a real tab, which is what Copy takes. */
+.code .tsv-tab {
+ position: relative;
+}
+
+.code .tsv-tab::before {
+ content: "\21E5";
+ position: absolute;
+ left: 0.2em;
+ top: 50%;
+ transform: translateY(-50%);
+ padding: 0 0.35em;
+ border-radius: 4px;
+ font-size: 0.85em;
+ line-height: 1.35;
+ color: var(--code-value);
+ background: color-mix(in srgb, var(--code-value) 14%, transparent);
+ pointer-events: none;
+}
+
+.code.tsv pre {
+ tab-size: 6;
+}
+
.copy {
position: absolute;
top: 10px;
@@ -1105,16 +1176,90 @@ p {
border-color: currentColor;
}
+/* A block's head: what it is (a terminal, a file, a command's output), with
+ Copy at its right. A file name is written as it is spelled. */
.code-title {
- font: 600 0.75rem var(--font);
- text-transform: uppercase;
- letter-spacing: 0.06em;
+ display: flex;
+ align-items: center;
+ min-height: 36px;
+ padding: 0 128px 0 18px;
+ border-bottom: 1px solid var(--code-border);
+ font: 600 0.76rem var(--font);
+ letter-spacing: 0.02em;
color: var(--code-muted);
- padding: 10px 18px 0;
+}
+
+.code-title.fname {
+ font: 600 0.8rem var(--mono);
+ letter-spacing: 0;
+ color: var(--code-ink);
}
.code-title + pre {
- padding-top: 8px;
+ padding-top: 12px;
+ padding-right: 18px;
+}
+
+.code-title ~ .copy {
+ top: 4px;
+ right: 6px;
+ padding: 4px 10px;
+}
+
+/* A tab-separated file says what Copy keeps. */
+.code.tsv .copy::after {
+ content: " with tabs";
+}
+
+/* What a command prints: the front page's dashed frame, and nothing to copy. */
+.code.out {
+ --cover: var(--bg-subtle);
+ margin-top: 0;
+ padding: 0;
+ background: var(--bg-subtle);
+ border: 1px dashed var(--line-strong);
+ color: var(--ink-soft);
+ white-space: normal;
+}
+
+.code.out pre {
+ color: var(--ink-soft);
+}
+
+.code.out .code-title {
+ border-bottom: 1px dashed var(--line-strong);
+}
+
+.code.out .copy {
+ display: none;
+}
+
+/* A command and what it prints read as one. */
+.prose .code.cmd + .code.out {
+ margin-top: -0.5em;
+}
+
+/* On a phone a block with no head keeps a strip above its text for Copy,
+ instead of a margin down the right of every line. */
+@media (max-width: 600px) {
+ .prose .code.bare {
+ padding-top: 30px;
+ }
+
+ .prose .code.bare .copy {
+ top: 3px;
+ right: 6px;
+ padding: 3px 10px;
+ }
+
+ .prose .code pre {
+ padding-right: 16px;
+ padding-left: 14px;
+ }
+
+ .code-title {
+ padding-left: 14px;
+ }
}
kbd {
@@ -2019,7 +2164,7 @@ kbd {
.foot {
border-top: 1px solid var(--line);
- padding: 40px 0 56px;
+ padding: 40px 0 28px;
font-size: 0.9rem;
color: var(--muted);
}
@@ -2046,6 +2191,17 @@ kbd {
gap: 6px;
}
+/* Quiet links: the footer is a list of ways out, not a paragraph. */
+.foot a {
+ color: var(--ink-soft);
+ text-decoration: none;
+}
+
+.foot a:hover {
+ color: var(--accent);
+ text-decoration: underline;
+}
+
.foot blockquote {
margin: 0;
color: var(--ink-soft);
@@ -2055,6 +2211,22 @@ kbd {
margin: 10px 0 0;
}
+/* The bottom row, under a hairline: who makes it, its licence, its version. */
+.foot .wrap.foot-base {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px 20px;
+ margin-top: 30px;
+ font-size: 0.8rem;
+}
+
+.foot .wrap.foot-base::before {
+ content: "";
+ flex: 1 0 100%;
+ border-top: 1px solid var(--line);
+ margin-bottom: 14px;
+}
+
@media (max-width: 760px) {
.foot .wrap {
grid-template-columns: minmax(0, 1fr);
@@ -2081,14 +2253,31 @@ kbd {
}
.ref-section {
- padding: 48px 0 8px;
+ padding: 56px 0 72px;
+}
+
+.ref-section .note + .deep {
+ margin-top: 18px;
}
+/* A table wider than its box scrolls, and shades the edge it continues past
+ (the same covers and shadows as a block of code). */
.table-wrap {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: var(--radius);
- background: var(--bg-elev);
+ --cover: var(--bg-elev);
+ --shade: rgba(255, 255, 255, 0.16);
+ background:
+ linear-gradient(to right, var(--cover) 40%, transparent) left / 28px 100% no-repeat local,
+ linear-gradient(to left, var(--cover) 40%, transparent) right / 28px 100% no-repeat local,
+ radial-gradient(farthest-side at 0 50%, var(--shade), transparent) left / 12px 100% no-repeat scroll,
+ radial-gradient(farthest-side at 100% 50%, var(--shade), transparent) right / 12px 100% no-repeat scroll,
+ var(--bg-elev);
+}
+
+:root[data-theme="light"] .table-wrap {
+ --shade: rgba(16, 27, 24, 0.16);
}
table {
@@ -2164,21 +2353,29 @@ th.num {
/* ── Documentation pages ── */
+/* The docs take a wider frame than the front page, so the text column keeps
+ its width next to the page list and the contents; header, columns and
+ footer share its edges. Running text keeps a reading measure inside the
+ column, while code, tables and drawings use all of it. */
+.docs .wrap {
+ max-width: 1380px;
+}
+
.docs-layout {
display: grid;
- grid-template-columns: 230px minmax(0, 1fr) 210px;
- gap: 44px;
+ grid-template-columns: 220px minmax(0, 1fr) 200px;
+ column-gap: 40px;
padding-top: 32px;
padding-bottom: 72px;
}
-.docs-home {
- grid-template-columns: 230px minmax(0, 1fr);
+.wrap.docs-home {
+ grid-template-columns: 220px minmax(0, 1fr);
}
-@media (max-width: 1180px) {
- .docs-layout {
- grid-template-columns: 210px minmax(0, 1fr);
+@media (max-width: 1279px) {
+ .wrap.docs-layout {
+ grid-template-columns: 220px minmax(0, 1fr);
}
.docs-toc {
@@ -2187,8 +2384,8 @@ th.num {
}
@media (max-width: 860px) {
- .docs-layout,
- .docs-home {
+ .wrap.docs-layout,
+ .wrap.docs-home {
grid-template-columns: minmax(0, 1fr);
gap: 18px;
padding-top: 18px;
@@ -2330,10 +2527,11 @@ th.num {
.docs-section {
font-size: 0.75rem;
font-weight: 700;
+ line-height: 1.3;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
- margin: 16px 0 6px;
+ margin: 10px 0 2px;
}
.docs-nav ul,
@@ -2343,9 +2541,11 @@ th.num {
padding: 0;
}
+/* Rows of about 28 px, so the whole list fits a laptop's screen. */
.docs-nav li a {
display: block;
- padding: 5px 10px;
+ padding: 3px 10px;
+ line-height: 1.45;
border-radius: 7px;
color: var(--ink-soft);
text-decoration: none;
@@ -2394,28 +2594,120 @@ th.num {
color: var(--ink);
}
-/* Long-form text. */
+/* The contents where the contents column does not show: folded under the
+ h1, its sections only. */
+.toc-inline {
+ display: none;
+}
+
+@media (max-width: 1279px) {
+ .prose .toc-inline {
+ display: block;
+ max-width: 68ch;
+ margin: 0 0 1.6em;
+ padding: 0 14px;
+ border: 1px solid var(--line);
+ border-radius: 10px;
+ background: var(--bg-elev);
+ font-size: 0.92rem;
+ }
+}
+
+.toc-inline summary {
+ cursor: pointer;
+ padding: 9px 0;
+ font-weight: 650;
+ color: var(--ink);
+}
+
+.prose .toc-inline ul {
+ list-style: none;
+ margin: 0;
+ padding: 0 0 10px;
+}
+
+.prose .toc-inline li {
+ margin: 0;
+}
+
+.toc-inline a {
+ display: block;
+ padding: 4px 0 4px 10px;
+ border-left: 2px solid var(--line);
+ color: var(--ink-soft);
+ text-decoration: none;
+ line-height: 1.35;
+}
+
+.toc-inline a:hover,
+.toc-inline a.active {
+ border-left-color: var(--accent);
+ color: var(--ink);
+}
+
+/* Long-form text: headings, bold and table heads in full ink, the rest a
+ step softer, so a page of text does not glare. */
.prose {
min-width: 0;
- max-width: 780px;
+ max-width: 820px;
+ color: var(--ink-soft);
+ line-height: 1.62;
+}
+
+.prose :is(h1, h2, h3, h4, h5, h6, strong, b, th) {
+ color: var(--ink);
+}
+
+/* A reading measure for running text; code, tables and drawings take the
+ whole column. */
+.prose > :is(p, ul, ol, blockquote),
+.prose .callout {
+ max-width: 68ch;
+}
+
+.prose :is(p, li) {
+ text-wrap: pretty;
}
-.prose .crumb {
+.prose .crumb-row {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: baseline;
+ gap: 2px 18px;
+ margin-bottom: 8px;
font-size: 0.85rem;
color: var(--muted);
- margin-bottom: 8px;
}
-.prose .crumb a {
+.prose .crumb-row p {
+ margin: 0;
+}
+
+.prose .crumb-row a {
color: var(--muted);
}
+.prose .crumb-row a:hover {
+ color: var(--accent);
+}
+
+.prose .ver {
+ font-size: 0.8rem;
+}
+
.prose h1 {
font-size: clamp(2rem, 4vw, 2.6rem);
font-weight: 800;
letter-spacing: -0.03em;
+ margin-bottom: 10px;
+}
+
+.prose .doc-meta {
margin-bottom: 18px;
+ font-size: 0.85rem;
+ color: var(--muted);
}
.prose h2 {
@@ -2435,30 +2727,78 @@ th.num {
margin: 1.5em 0 0.5em;
}
+/* A heading and its # link: the link sits in the gutter to the left of the
+ heading, and on a narrow screen just after its text. The heading's margins
+ move to the pair, sized as the heading's. Anchored headings land under the
+ sticky header through html's scroll-padding-top alone. */
+.prose .hx {
+ position: relative;
+ display: flex;
+ align-items: baseline;
+ gap: 0.35em;
+}
+
+.prose .hx-2 {
+ font-size: 1.5rem;
+ margin: 2.2em 0 0.7em;
+}
+
+.prose .hx-3 {
+ font-size: 1.15rem;
+ margin: 1.8em 0 0.6em;
+}
+
+.prose :is(.hx-4, .hx-5, .hx-6) {
+ font-size: 1rem;
+ margin: 1.5em 0 0.5em;
+}
+
+.prose .hx > :is(h2, h3, h4, h5, h6) {
+ min-width: 0;
+ margin: 0;
+}
+
.prose h1,
.prose h2,
.prose h3,
.prose h4 {
- scroll-margin-top: 80px;
/* Headings that are file names have no spaces to break at. */
- overflow-wrap: anywhere;
+ overflow-wrap: break-word;
}
.prose .anchor {
- margin-left: 8px;
- color: var(--line-strong);
+ flex: none;
+ color: var(--muted);
text-decoration: none;
- opacity: 0;
font-weight: 500;
+ line-height: 1.15;
+ opacity: 0;
}
-.prose h2:hover .anchor,
-.prose h3:hover .anchor,
-.prose h4:hover .anchor,
-.prose .anchor:focus {
+.prose .hx:hover .anchor,
+.prose .anchor:focus-visible {
opacity: 1;
}
+.prose .anchor:hover {
+ color: var(--accent);
+}
+
+@media (min-width: 861px) {
+ .prose .hx .anchor {
+ position: absolute;
+ left: -1.1em;
+ top: 0;
+ }
+}
+
+/* On a touch screen there is no hover to show it. */
+@media (hover: none) {
+ .prose .anchor {
+ opacity: 1;
+ }
+}
+
.prose p,
.prose ul,
.prose ol,
@@ -2511,8 +2851,120 @@ th.num {
font-size: 0.9rem;
}
-.prose td code {
- white-space: normal;
+/* A type is a word to read, not a chip of code. */
+.prose td.type {
+ color: var(--muted);
+ font-size: 0.86rem;
+ white-space: nowrap;
+}
+
+/* An option's row, opened from a link to it. */
+.prose tr:target {
+ background: var(--accent-soft);
+}
+
+.prose tr:target > td:first-child {
+ box-shadow: inset 3px 0 0 var(--accent);
+}
+
+/* A chip naming an option links to its row of the parameters page. */
+.prose a.opt-link {
+ text-decoration: none;
+}
+
+.prose a.opt-link code {
+ color: var(--accent);
+ border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
+}
+
+.prose a.opt-link:hover code {
+ border-color: var(--accent);
+ color: var(--accent-hover);
+}
+
+@media (max-width: 600px) {
+ .prose th,
+ .prose td {
+ padding: 10px;
+ }
+
+ .prose th {
+ letter-spacing: 0.03em;
+ }
+
+ /* A table of four columns or more becomes one card per row: the first
+ cell as its title, the second as its text, the rest on one muted line,
+ each with its column's name. */
+ .prose .table-wrap.cards-wrap {
+ overflow: visible;
+ border: 0;
+ border-radius: 0;
+ background: none;
+ }
+
+ .prose table.cards,
+ .prose table.cards tbody,
+ .prose table.cards tr,
+ .prose table.cards td {
+ display: block;
+ }
+
+ .prose table.cards thead {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ }
+
+ .prose table.cards tr {
+ margin-bottom: 10px;
+ padding: 12px 14px;
+ border: 1px solid var(--line);
+ border-radius: 10px;
+ background: var(--bg-elev);
+ }
+
+ .prose table.cards tr:target {
+ background: var(--accent-soft);
+ border-color: var(--accent);
+ }
+
+ .prose table.cards td {
+ padding: 0;
+ border: 0;
+ }
+
+ .prose table.cards td:first-child {
+ margin-bottom: 4px;
+ font-weight: 600;
+ color: var(--ink);
+ }
+
+ .prose table.cards tr:target > td:first-child {
+ box-shadow: none;
+ }
+
+ .prose table.cards td:nth-child(2) {
+ margin-bottom: 6px;
+ }
+
+ .prose table.cards td:nth-child(n + 3) {
+ display: inline;
+ margin-right: 14px;
+ font-size: 0.82rem;
+ color: var(--muted);
+ }
+
+ .prose table.cards td:nth-child(n + 3):empty {
+ display: none;
+ }
+
+ .prose table.cards td:nth-child(n + 3)::before {
+ content: attr(data-label) ": ";
+ font-weight: 600;
+ }
}
.prose .figure img {
@@ -2532,11 +2984,20 @@ th.num {
display: none;
}
+.prose .to-top {
+ margin-top: 2em;
+ font-size: 0.9rem;
+}
+
.prose .edit {
margin-top: 2.5em;
font-size: 0.9rem;
}
+.prose .to-top + .edit {
+ margin-top: 0.6em;
+}
+
.prose .edit a {
display: inline-flex;
align-items: center;
@@ -2548,11 +3009,12 @@ th.num {
height: 16px;
}
-/* GitHub's alerts (> [!WARNING]) as callouts. */
+/* GitHub's alerts (> [!WARNING]) as callouts, which say their kind in words
+ as well as in colour. */
.callout {
- border: 1px solid var(--line);
+ border: 1px solid color-mix(in srgb, var(--c) 30%, var(--line));
border-left: 4px solid var(--c);
- background: color-mix(in srgb, var(--c) 8%, var(--bg-elev));
+ background: color-mix(in srgb, var(--c) 5%, var(--bg-elev));
border-radius: 0 10px 10px 0;
padding: 12px 16px 2px;
--c: var(--info);
@@ -2580,35 +3042,161 @@ th.num {
margin-bottom: 6px !important;
}
+.callout-kind {
+ display: block;
+ margin-bottom: 1px;
+ font-size: 0.72rem;
+ font-weight: 700;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--c);
+}
+
.callout > p:last-child,
.callout > .code:last-child {
margin-bottom: 12px;
}
-/* Commands wrap in a column of text rather than run under the copy button;
- files and trees keep their layout and show the button on hover. */
-.prose .code[data-lang="bash"] pre,
-.prose .code[data-lang="sh"] pre,
-.prose .code[data-lang="console"] pre {
- white-space: pre-wrap;
- overflow-wrap: anywhere;
-}
+/* A block with no head row shows Copy when pointed at; with a head row, or
+ on a touch screen, it is always there. */
+@media (hover: hover) and (pointer: fine) {
+ .prose .code.bare .copy {
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ }
-.prose .code:not([data-lang="bash"]):not([data-lang="sh"]):not([data-lang="console"]) .copy {
- opacity: 0;
- transition: opacity 0.15s ease;
+ .prose .code.bare:hover .copy,
+ .prose .code .copy:focus-visible,
+ .prose .code .copy.done {
+ opacity: 1;
+ }
}
-.prose .code:hover .copy,
-.prose .code .copy:focus,
-.prose .code .copy.done {
- opacity: 1 !important;
+@media (hover: none) {
+ .prose .code .copy {
+ opacity: 1;
+ }
}
.tabset .tabs {
margin-bottom: 8px;
}
+/* The Docs tab: the questions people come with, before the list of pages. */
+.prose .hub-tasks {
+ margin: 20px 0 6px;
+}
+
+.prose .hub-label {
+ margin-bottom: 8px;
+ font-size: 0.75rem;
+ font-weight: 700;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+.prose .hub-tasks ul {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.prose .hub-tasks li {
+ margin: 0;
+}
+
+.prose .hub-tasks a {
+ display: inline-block;
+ padding: 7px 14px;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ background: var(--bg-elev);
+ color: var(--ink);
+ font-size: 0.9rem;
+ font-weight: 600;
+ text-decoration: none;
+}
+
+.prose .hub-tasks a:hover {
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+/* On paper: light colours, the text alone, and the address of every link
+ that leaves the site. */
+@media print {
+ .docs {
+ --bg: #ffffff;
+ --bg-elev: #ffffff;
+ --bg-subtle: #f3f5f4;
+ --ink: #111816;
+ --ink-soft: #242e2b;
+ --muted: #4f5b58;
+ --line: #d5dcda;
+ --line-strong: #b9c3c0;
+ --accent: #055c4d;
+ --accent-soft: #e2f3ef;
+ --code-bg: #f5f7f6;
+ --code-border: #d5dcda;
+ --code-ink: #111816;
+ --code-muted: #4f5b58;
+ --code-flag: #055c4d;
+ --code-value: #7a3f00;
+ color-scheme: light;
+ background: #ffffff;
+ color: var(--ink);
+ }
+
+ .docs :is(.top, .docs-nav, .docs-toc, .toc-inline, .copy, .docs-pager, .edit, .to-top, .foot, .skip, .crumb-row, .anchor) {
+ display: none !important;
+ }
+
+ .wrap.docs-layout {
+ display: block;
+ max-width: none;
+ padding: 0;
+ }
+
+ .prose,
+ .prose > :is(p, ul, ol, blockquote),
+ .prose .callout {
+ max-width: none;
+ }
+
+ .prose .code pre,
+ .prose .code:not(.cmd) pre {
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ overflow: visible;
+ background: none;
+ padding-right: 14px;
+ }
+
+ .table-wrap {
+ overflow: visible;
+ background: none;
+ }
+
+ .prose :is(.code, .fig, figure, .callout, tr, .tabset) {
+ break-inside: avoid;
+ }
+
+ .prose :is(.hx, h2, h3, h4) {
+ break-after: avoid;
+ }
+
+ .prose a[href^="http"]::after {
+ content: " (" attr(href) ")";
+ font-size: 0.85em;
+ color: var(--muted);
+ overflow-wrap: anywhere;
+ }
+}
+
/* A drawing in a page of text. */
.prose .fig {
margin-top: 1.4em;
@@ -4182,6 +4770,37 @@ th.num {
color: var(--muted);
}
+/* The drawing keeps a size at which its words read (at least 9 px) and
+ scrolls sideways on a phone instead of shrinking. */
+.lost-scroll {
+ overflow-x: auto;
+}
+
+.lost-scroll svg {
+ display: block;
+ width: 100%;
+ min-width: 520px;
+}
+
+.lost-hero {
+ padding-bottom: 72px;
+}
+
+.lost-hero .nearest {
+ margin-top: 18px;
+ font-size: 1.1rem;
+ color: var(--ink-soft);
+}
+
+.lost-hero .nearest a {
+ font-weight: 650;
+}
+
+.lost-search {
+ max-width: 420px;
+ margin-top: 18px;
+}
+
/* ── Duplications: a family and a pair of communities, as networks ── */
.fig.dup-fig {
diff --git a/docs/deployment/containers.html b/docs/deployment/containers.html
index 6c09bcd..9a51d18 100644
--- a/docs/deployment/containers.html
+++ b/docs/deployment/containers.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,95 +42,102 @@
-
diff --git a/docs/deployment/hpc.html b/docs/deployment/hpc.html
index 70740be..089a902 100644
--- a/docs/deployment/hpc.html
+++ b/docs/deployment/hpc.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,125 +42,132 @@
-
+
PathoGenOmics LabGPL-3.0AMAP 1.0.1
diff --git a/docs/deployment/troubleshooting.html b/docs/deployment/troubleshooting.html
index 7c2b30f..2ac6ff5 100644
--- a/docs/deployment/troubleshooting.html
+++ b/docs/deployment/troubleshooting.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,104 +42,111 @@
-
+
PathoGenOmics LabGPL-3.0AMAP 1.0.1
diff --git a/docs/documentation.html b/docs/documentation.html
index e246cee..6ec2675 100644
--- a/docs/documentation.html
+++ b/docs/documentation.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,71 +42,75 @@
-
+
PathoGenOmics LabGPL-3.0AMAP 1.0.1
diff --git a/docs/getting-started/inputs.html b/docs/getting-started/inputs.html
index d576b49..547e148 100644
--- a/docs/getting-started/inputs.html
+++ b/docs/getting-started/inputs.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,101 +42,107 @@
-
diff --git a/docs/getting-started/installation.html b/docs/getting-started/installation.html
index fb12cd6..8ad5d7a 100644
--- a/docs/getting-started/installation.html
+++ b/docs/getting-started/installation.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,94 +42,101 @@
diff --git a/docs/user-guide/running.html b/docs/user-guide/running.html
index 5360303..64e485d 100644
--- a/docs/user-guide/running.html
+++ b/docs/user-guide/running.html
@@ -19,9 +19,9 @@
-
+
-
+
Skip to the content
@@ -42,105 +42,112 @@
-
diff --git a/tools/build_docs.py b/tools/build_docs.py
index c9f58d4..db2fb7e 100644
--- a/tools/build_docs.py
+++ b/tools/build_docs.py
@@ -25,6 +25,7 @@
from __future__ import annotations
import argparse
+import functools
import html
import json
import re
@@ -38,15 +39,15 @@
# a .md file is built into a page; an .html path is a page written by hand and
# only gets its place in the list.
PAGES = [
- ("Getting started", "getting-started/tutorial.md", "Tutorial", "From an empty folder to a corrected genome, with the numbers of a real run."),
("Getting started", "getting-started/installation.md", "Installation", "Nextflow, a container engine, and running without one."),
- ("Getting started", "getting-started/quickstart.md", "Quickstart", "From an installed Nextflow to an open report."),
+ ("Getting started", "getting-started/quickstart.md", "Quickstart", "The front page's three steps as a page, about 10 minutes once installed."),
+ ("Getting started", "getting-started/tutorial.md", "Tutorial", "One real genome, start to finish, with every number."),
("Getting started", "getting-started/inputs.md", "Preparing inputs", "The sample sheet, the genomes, and the checks run on them."),
("User guide", "user-guide/parameters.md", "Parameters", "Every option, its default, and when to change it."),
("User guide", "user-guide/running.md", "Running AMAP", "Profiles, schedulers and larger runs."),
("User guide", "user-guide/outputs.md", "Outputs", "Every folder and file, and what a healthy run looks like."),
- ("User guide", "user-guide/report.md", "The report", "What each section of final_report.html shows."),
- ("User guide", "user-guide/amap-report.md", "The interactive report", "amap_report.html, question by question, with or without eggNOG."),
+ ("User guide", "user-guide/report.md", "The full report", "What each section of final_report.html shows."),
+ ("User guide", "user-guide/amap-report.md", "The report by question", "amap_report.html, question by question, with or without eggNOG."),
("User guide", "user-guide/custom-reference.md", "Custom reference", "Any organism, against a trusted annotation of your own."),
("How it works", "how-it-works.html", "Step by step, illustrated", "One gene at a time, from Bakta's call to what AMAP keeps."),
("How it works", "how-it-works/index.md", "Overview", "What goes into a decision, and the five outcomes."),
@@ -79,10 +80,13 @@
'l1.4-1.4M17.7 6.3l1.4-1.4"/>'
)
THEME_BOOT = (
- ''
+ ''
)
INLINE_TAGS = re.compile(r"?(kbd|br|sup|sub|strong|em|b|i)\s*/?>", re.I)
+# What figures.js looks for to draw: a page with none of these needs neither it nor its data.
+FIGURE_HOOK = re.compile(r"\bdata-(fig|inputs|outcomes|metro|dup)(?=[\s=>])")
ALERTS = {"NOTE": "Note", "TIP": "Tip", "IMPORTANT": "Important", "WARNING": "Warning", "CAUTION": "Caution"}
FIGURE_MARK = re.compile(r"^$")
@@ -343,13 +347,260 @@ def strip_front_matter(lines: list[str]) -> list[str]:
return lines
+# ── Inline code ──
+#
+# A chip of code never splits inside a word. Up to SHORT_CODE characters it
+# does not wrap at all; a longer one wraps only at a space or after a /, _ or
+# ., so a path or a long option name comes apart where it reads in pieces.
+# Each piece is a span that does not wrap, with a between pieces: the
+# browser's own rules would also break after a hyphen, '--|skip_typing'.
+
+SHORT_CODE = 24
+
+
+def code_pieces(text: str) -> str:
+ out = []
+ for token in re.split(r"( +)", text):
+ if not token:
+ continue
+ if token.isspace():
+ out.append(token)
+ continue
+ pieces = re.findall(r"[^/_.]*[/_.]+|[^/_.]+", token)
+ out.append("".join(f'{html.escape(p)}' for p in pieces))
+ return "".join(out)
+
+
+def code_chip(text: str) -> str:
+ if len(text) <= SHORT_CODE:
+ return f'{html.escape(text)}'
+ return f"{code_pieces(text)}"
+
+
+def chips_in(fragment: str) -> str:
+ """The same treatment for the of a figure written as HTML."""
+ return re.sub(r"([^<]*)", lambda m: code_chip(html.unescape(m.group(1))), fragment)
+
+
+# ── Options ──
+#
+# Every --option of the parameters page has a row there with an id, and a
+# chip of code naming one anywhere else in the docs links to it.
+
+PARAMETERS = "user-guide/parameters.md"
+
+
+@functools.lru_cache(maxsize=None)
+def parameter_rows() -> list[dict]:
+ """The --option rows of the parameters page: flag, section, cells."""
+ rows, section, head = [], "", []
+ for line in (DOCS / PARAMETERS).read_text(encoding="utf-8").splitlines():
+ heading = re.match(r"^##\s+(.*)$", line)
+ if heading:
+ section = heading.group(1).strip()
+ continue
+ if not line.startswith("|"):
+ head = []
+ continue
+ cells = [c.strip() for c in re.split(r"(? set[str]:
+ return {row["flag"] for row in parameter_rows()}
+
+
+def switches() -> set[str]:
+ """Options that take no value: what follows one on a command line is not its value."""
+ return {row["flag"] for row in parameter_rows() if row["cells"].get("Type") == "boolean"} | {"--rm", "--help", "--version"}
+
+
+# Nextflow's own options that take a value.
+NF_VALUED = {"-profile", "-work-dir", "-c", "-r"}
+
+
+# ── Code blocks ──
+
+SHELL = {"bash", "sh", "console", "shell"}
+TOKEN = re.compile(r"""(?:[^\s'"]|'[^']*'|"(?:\\.|[^"\\])*")+|\S+""")
+LONG_TOKEN = 36
+
+
+def open_quote(line: str, quote: str | None) -> str | None:
+ """The quote still open at the end of a line of shell, given the one open
+ at its start."""
+ i = 0
+ while i < len(line):
+ ch = line[i]
+ if quote is None:
+ if ch == "\\":
+ i += 2
+ continue
+ if ch == "#" and (i == 0 or line[i - 1].isspace()):
+ return None
+ if ch in "'\"":
+ quote = ch
+ elif ch == quote:
+ quote = None
+ elif ch == "\\" and quote == '"':
+ i += 2
+ continue
+ i += 1
+ return quote
+
+
+def shell_token(token: str, kind: str) -> str:
+ """One word of a command, which never wraps inside itself; a long path
+ may, after a /."""
+ if len(token) > LONG_TOKEN and "/" in token:
+ pieces = re.findall(r"[^/]*/+|[^/]+", token)
+ inner = "".join(f'{html.escape(p)}' for p in pieces)
+ return f'{inner}'
+ return f'{html.escape(token)}'
+
+
+def shell_line(line: str) -> str:
+ """A line of shell as words: flags (.f), their values (.v), other words
+ (.t) and a comment (.c). The spaces stay between the words, so a long
+ line wraps there and only there."""
+ out, at, tokens = [], 0, list(TOKEN.finditer(line))
+ k = 0
+ while k < len(tokens):
+ m = tokens[k]
+ out.append(html.escape(line[at:m.start()]))
+ word = m.group(0)
+ if word.startswith("#"):
+ out.append(f'{html.escape(line[m.start():])}')
+ at = len(line)
+ break
+ if re.match(r"^--?[A-Za-z]", word):
+ nxt = tokens[k + 1] if k + 1 < len(tokens) else None
+ valued = (
+ (word.startswith("--") and "=" not in word and word not in switches()) or word in NF_VALUED or word.startswith("-process.")
+ )
+ if valued and nxt and not re.match(r"^(-|#|\\$|\||&&|;|>)", nxt.group(0)):
+ value = nxt.group(0)
+ pair = shell_token(word, "f") + html.escape(line[m.end():nxt.start()]) + shell_token(value, "v")
+ out.append(f'{pair}' if len(word) + len(value) < 32 else pair)
+ at = nxt.end()
+ k += 2
+ continue
+ out.append(shell_token(word, "f"))
+ else:
+ out.append(shell_token(word, "t"))
+ at = m.end()
+ k += 1
+ out.append(html.escape(line[at:]))
+ return "".join(out)
+
+
+def shell_html(lines: list[str], prompts: bool) -> str:
+ """Each line of a shell block as a .ln, which wraps with a hanging
+ indent. A line that starts a command gets a $ prompt, which Copy leaves
+ out; a line that carries one on, after a backslash or inside a quote or
+ a loop, does not."""
+ out = []
+ carried = False
+ quote = None
+ depth = 0
+ for n, line in enumerate(lines):
+ stripped = line.strip()
+ indent = len(line) - len(line.lstrip(" "))
+ starts = prompts and stripped and not carried and quote is None and depth == 0 and not stripped.startswith("#")
+ prompt = '$ ' if starts else ""
+ # Inside a quote that spans lines is another program's text (an R
+ # call, say): its words still wrap only between them, uncoloured.
+ body = shell_line(line) if quote is None else re.sub(r"\S+", lambda m: shell_token(m.group(0), "t"), line)
+ style = f' style="--i:{indent}"' if indent else ""
+ end = "\n" if n + 1 < len(lines) else ""
+ out.append(f'{prompt}{body}{end}')
+ quote = open_quote(line, quote)
+ carried = stripped.endswith("\\")
+ if quote is None and not carried and stripped:
+ first = stripped.split()[0]
+ closes = re.search(r"\b(done|fi|esac)\b", stripped)
+ if first in ("for", "while", "until", "if", "case") and not closes:
+ depth += 1
+ elif first in ("done", "fi", "esac"):
+ depth = max(depth - 1, 0)
+ return "".join(out)
+
+
+def tsv_html(lines: list[str]) -> str:
+ """A tab-separated file, with each tab drawn. Copy takes the real tabs."""
+ return "\n".join(
+ '\t'.join(html.escape(cell) for cell in line.split("\t")) for line in lines
+ )
+
+
+CHANGELOG_EMOJI = re.compile("[\U0001F000-\U0001FAFF☀-➿⬀-⯿️]")
+MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
+
+
+def version_heading(text: str) -> tuple[str, str, str]:
+ """A version of the changelog as the page names it: '[1.0.1] - 2026-07-25'
+ becomes ('1.0.1, 25 July 2026', 'v1-0-1', '1.0.1')."""
+ if re.fullmatch(r"\[?Unreleased\]?", text.strip(), re.I):
+ return "On main, not yet released", "unreleased", "Unreleased"
+ m = re.match(r"^\[?([\d.]+)\]?(?:\s*-\s*(\d{4})(?:-(\d{2})-(\d{2}))?)?", text.strip())
+ if not m:
+ return text, slugify(text, {}), text
+ version, year, month, day = m.groups()
+ when = f"{int(day)} {MONTHS[int(month) - 1]} {year}" if day else (year or "")
+ return (f"{version}, {when}" if when else version), "v" + version.replace(".", "-"), version
+
+
+# The room a table has on a 360 px phone (326 px), with a margin for an
+# estimate that runs about 10% wide: measured, tables estimated at 341 fit
+# and those at 391 overflow. Then what a character takes there, code or
+# text, and a cell's padding.
+PHONE_TABLE = 360
+PHONE_CHAR = 7.8
+PHONE_CELL = 20
+
+
+def phone_width(head: list[str], body: list[list[str]]) -> float:
+ """About the narrowest a table can be: each column as wide as its widest
+ word or unbreakable chip, the first column's chips whole."""
+ columns = [0.0] * len(head)
+ for row in [head] + body:
+ for k, cell in enumerate(row[: len(head)]):
+ units = []
+ for m in re.finditer(r"`([^`]+)`", cell):
+ code = m.group(1).strip()
+ if k == 0 or len(code) <= SHORT_CODE:
+ units.append(len(code) + 1.5)
+ else:
+ units.append(max(len(p) for t in code.split() for p in re.findall(r"[^/_.]*[/_.]+|[^/_.]+", t)) + 1.5)
+ units += [len(w) for w in plain(re.sub(r"`[^`]+`", " ", cell)).split()]
+ columns[k] = max([columns[k]] + units)
+ return sum(c * PHONE_CHAR + PHONE_CELL for c in columns)
+
+
+def first_sentence(text: str, limit: int = 160) -> str:
+ m = re.match(r"(.+?[.:])(\s|$)", text)
+ cut = m.group(1) if m else text
+ return cut if len(cut) <= limit else cut[:limit].rsplit(" ", 1)[0] + "..."
+
+
class Renderer:
def __init__(self, source: str):
self.source = source # markdown path relative to docs/, e.g. user-guide/outputs.md
+ self.root = "../" * source.count("/")
+ self.changelog = source == CHANGELOG[0]
+ self.version: tuple[str, str] | None = None # the changelog's version being read: (slug, label)
self.seen: dict[str, int] = {}
self.headings: list[tuple[int, str, str]] = [] # (level, text, slug)
self.sections: list[dict] = [] # for the search index
+ self.extra: list[dict] = [] # search entries of their own: callouts
self.figures: list[str] = []
+ self.words = 0 # of running text, for the reading time
# Links
@@ -373,20 +624,37 @@ def link(self, target: str) -> str:
# Inline
- def inline(self, text: str) -> str:
+ def inline(self, text: str, links: bool = True, whole: bool = False) -> str:
+ """An inline span as HTML. With links, a chip of code that names an
+ option of the parameters page links to its row there; whole keeps
+ every chip on one line, as the name of a table's row."""
keep: list[str] = []
+ unlinked: dict[str, str] = {}
def stash(fragment: str) -> str:
keep.append(fragment)
return f"\x00{len(keep) - 1}\x00"
- text = re.sub(r"(`+)(.+?)\1", lambda m: stash(f"{html.escape(m.group(2).strip())}"), text)
+ def code(m: re.Match) -> str:
+ raw = m.group(2).strip()
+ chip = f'{html.escape(raw)}' if whole else code_chip(raw)
+ flag = raw.split(" ")[0]
+ if links and flag in options() and self.source != PARAMETERS:
+ marker = stash(f'{chip}')
+ unlinked[marker] = chip
+ return marker
+ return stash(chip)
+
+ text = re.sub(r"(`+)(.+?)\1", code, text)
text = re.sub(r"\\([\\`*_{}\[\]()#+\-.!|<>])", lambda m: stash(html.escape(m.group(1))), text)
text = re.sub(r"<(https?://[^>\s]+)>", lambda m: stash(f'{html.escape(m.group(1))}'), text)
text = INLINE_TAGS.sub(lambda m: stash(m.group(0)), text)
def link(m: re.Match) -> str:
label, target = m.group(1), m.group(2).strip().split(" ")[0]
+ # A link's own text keeps its chips plain: no link inside a link.
+ for marker, chip in unlinked.items():
+ label = label.replace(marker, stash(chip))
return stash(f'') + label + stash("")
text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", link, text)
@@ -403,23 +671,29 @@ def link(m: re.Match) -> str:
def blocks(self, lines: list[str]) -> str:
out: list[str] = []
i = 0
+ # A text block right after a shell block is what that command prints.
+ after_shell = False
while i < len(lines):
line = lines[i]
stripped = line.strip()
if not stripped:
i += 1
continue
+ shell = False
figure = FIGURE_MARK.match(stripped)
if figure:
name = figure.group(1)
if name in DERIVED:
- out.append(DERIVED[name](self, lines))
+ out.append(chips_in(DERIVED[name](self, lines)))
elif name in FIGURES:
- self.figures.append(name)
- out.append(FIGURES[name])
+ # Only a drawing that figures.js draws needs it loaded.
+ if FIGURE_HOOK.search(FIGURES[name]):
+ self.figures.append(name)
+ out.append(chips_in(FIGURES[name]))
else:
raise SystemExit(f"docs/{self.source}: no figure called {name!r}")
i += 1
+ after_shell = False
continue
if stripped.startswith("\n"
+ + (note or "\n")
+ body
+ f'\n'
+ f'\n'
+ (f'\n' if figures else "")
+ "".join(f'\n' for d in data)
+ (f'\n' if figures else "")
+ + tail
+ "\n\n"
)
@@ -1286,60 +1678,115 @@ def source_lines(path: str) -> list[str]:
return strip_front_matter((DOCS / path).read_text(encoding="utf-8").splitlines())
+def version_line(root: str) -> str:
+ """Which AMAP the pages describe: main, while it is ahead of the last release."""
+ ahead = any(name.lower() == "unreleased" and notes for name, notes in releases())
+ version = released()
+ if ahead:
+ return (f'
Describes AMAP on main, ahead of {version}: '
+ f'what changed
'
for level, text, slug in renderer.headings
- if level in (2, 3)
+ if level in levels
)
+ h2s = [(text, slug) for level, text, slug in renderer.headings if level == 2]
+ # Under the h1: how long the page takes to read, and below the width where
+ # the contents column shows, the contents themselves, folded.
+ minutes = max(1, round(renderer.words / 200))
+ under = f'