| name | bakta-genome-annotation |
| description | Annotate bacterial and archaeal genomes and plasmids with Bakta's Prodigal/HMM/diamond pipeline. Identifies CDS, ncRNA, tRNA, rRNA, tmRNA, sORFs, CRISPR arrays, oriC/oriV/oriT, and gaps against a curated UniRef-derived database. Produces NCBI-compatible GFF3, GenBank, EMBL, JSON, FASTA, TSV, and a circular genome plot. Use Prokka for legacy pipelines or non-bacterial kingdoms; PGAP for NCBI GenBank submission. |
| license | GPL-3.0 |
Bakta Genome Annotation
Overview
Bakta is a command-line pipeline for rapid, standardized annotation of bacterial and archaeal genomes and plasmids. It combines Prodigal for CDS prediction, tRNAscan-SE/Aragorn/Barrnap/Infernal for non-coding RNA, PILER-CR/PILERCR for CRISPR detection, and a tiered DIAMOND/HMM search against a curated UniRef100 + IPS/UPS database to assign gene names, EC numbers, GO terms, and COG categories. Bakta produces NCBI-compatible outputs (GFF3, GenBank, EMBL, INSDC-formatted FASTA, plus a JSON summary and a circular Circos plot) for a typical 5 Mb genome in 5–15 minutes on 8 CPUs.
When to Use
- Annotating bacterial or archaeal genome assemblies (Illumina, PacBio, Nanopore) with NCBI-compatible locus tags and product names
- Annotating plasmids and other circular replicons separately with
--plasmid and --complete flags
- Producing JSON-structured annotation outputs that can be parsed without GenBank or GFF3 detours
- Generating a publication-ready circular genome plot via the bundled
bakta_plot command
- Annotating MAGs (metagenome-assembled genomes) with
--meta to disable Prodigal training
- Use Prokka instead when you need viral/mitochondrial kingdoms or when you must reproduce a legacy Prokka pipeline exactly
- Use PGAP instead when submitting to NCBI GenBank with full standards compliance
- Use Bakta when you want faster runs, regularly updated UniRef-derived databases, AMRFinderPlus integration, and a JSON summary out of the box
Prerequisites
- Software: Bakta ≥ 1.9, Python 3.8+, Prodigal, tRNAscan-SE, Aragorn, Barrnap, Infernal, DIAMOND, HMMER3, PILER-CR, BLAST+, AMRFinderPlus
- Database: Bakta DB (full ~70 GB, or light ~3 GB) downloaded once with
bakta_db download
- Python packages (for output parsing):
biopython, pandas, matplotlib
- Input: assembled genome in FASTA format (one or more contigs)
- Hardware: ≥ 16 GB RAM for full DB, ≥ 4 GB RAM for light DB; ≥ 8 CPUs recommended
Check before installing: The tool may already be available in the current environment (e.g., inside a pixi / conda env). Run command -v bakta first and skip the install commands below if it returns a path. When running inside a pixi project, invoke the tool via pixi run bakta rather than bare bakta.
mamba install -c conda-forge -c bioconda bakta
bakta --version
bakta_db download --output db/ --type light
pip install biopython pandas matplotlib
Quick Start
bakta genome.fasta \
--db db/bakta_db_light \
--output results/ \
--prefix sample1 \
--threads 8
python -c "
import json
with open('results/sample1.json') as f:
d = json.load(f)
print('Genus:', d['genome'].get('genus'))
print('Length:', d['genome']['size'], 'bp')
print('CDS:', sum(1 for f in d['features'] if f['type'] == 'cds'))
print('tRNA:', sum(1 for f in d['features'] if f['type'] == 'tRNA'))
"
Workflow
Step 1: Install Bakta and Download the Database
Install Bakta and prepare the reference database. The database download is one-time and reused across runs.
mamba create -n bakta_env -c conda-forge -c bioconda bakta python=3.11 -y
mamba activate bakta_env
bakta --version
bakta --help | head -20
mkdir -p db/
bakta_db download --output db/ --type light
ls db/bakta_db_light/
amrfinder -u
pip install biopython pandas matplotlib
Step 2: Prepare the Input Assembly
Bakta requires clean FASTA headers without spaces or special characters. Pre-clean and optionally filter short contigs.
from Bio import SeqIO
import re
input_fasta = "genome.fasta"
records = list(SeqIO.parse(input_fasta, "fasta"))
print(f"Input assembly: {len(records)} contigs")
total_bases = sum(len(r) for r in records)
print(f"Total bases: {total_bases:,}")
print(f"Largest contig: {max(len(r) for r in records):,} bp")
cleaned = []
for i, rec in enumerate(records, 1):
new_id = f"contig_{i:04d}"
new_rec = rec.__class__(rec.seq, id=new_id, description="")
cleaned.append(new_rec)
SeqIO.write(cleaned, "genome_clean.fasta", "fasta")
print(f"Wrote genome_clean.fasta with {len(cleaned)} contigs")
awk 'BEGIN{RS=">"; ORS=""} NR>1 {n=split($0, a, "\n"); seq=""; for(i=2;i<=n;i++) seq=seq a[i]; if (length(seq) >= 200) print ">" $0}' \
genome_clean.fasta > genome_filtered.fasta
echo "Filtered assembly: $(grep -c '>' genome_filtered.fasta) contigs"
Step 3: Run Standard Bakta Annotation
Run Bakta with genus/species hints. Locus tags are auto-generated from the strain field.
bakta genome_clean.fasta \
--db db/bakta_db_light \
--output annotation/ \
--prefix E_coli_K12 \
--genus Escherichia \
--species coli \
--strain K12 \
--locus-tag ECOLI \
--threads 8 \
--keep-contig-headers
echo "Bakta annotation outputs:"
ls annotation/
Step 4: Parse the JSON Summary
Bakta's JSON output is the canonical, machine-readable annotation. Parse it directly for downstream pipelines.
import json
import pandas as pd
from collections import Counter
with open("annotation/E_coli_K12.json") as f:
bakta = json.load(f)
genome = bakta["genome"]
print(f"Organism: {genome.get('genus')} {genome.get('species')} {genome.get('strain')}")
print(f"Size: {genome['size']:,} bp across {len(bakta['sequences'])} sequences")
print(f"GC content: {genome['gc']:.2%}")
features = bakta["features"]
type_counts = Counter(f["type"] for f in features)
print("\nFeature counts:")
for ftype, n in sorted(type_counts.items(), key=lambda x: -x[1]):
print(f" {ftype:>10}: {n}")
cds_rows = []
for f in features:
if f["type"] != :
cds_rows.append({
: f.get(, ),
: f.get(, ),
: f.get(),
: f.get(),
: f.get(),
: f.get(, ),
: f.get(, ),
: (f.get(, )),
})
cds_df = pd.DataFrame(cds_rows)
()
(cds_df.head().to_string(index=))
Step 5: Parse the TSV Feature Table
The TSV output is convenient for spreadsheet workflows and quick filtering.
import pandas as pd
df = pd.read_csv("annotation/E_coli_K12.tsv", sep="\t", comment="#",
names=["sequence_id", "type", "start", "stop", "strand",
"locus_tag", "gene", "product", "dbxrefs"])
print(f"Total features: {len(df)}")
print(f"Feature types: {df['type'].value_counts().to_dict()}")
cds = df[df["type"] == "cds"].copy()
hypothetical = cds["product"].str.contains("hypothetical", case=False, na=True)
print(f"\nCDS with assigned function: {(~hypothetical).sum()} / {len(cds)}")
print(f"Hypothetical proteins: {hypothetical.sum()}")
def split_xrefs(xref_str):
if not isinstance(xref_str, str) xref_str (, ):
[]
[x.strip() x xref_str.split()]
cds[] = cds[].apply(split_xrefs)
ec_hits = cds[cds[].apply( xs: (x.startswith() x xs))]
()
(ec_hits[[, , ]].head().to_string(index=))
Step 6: Render the Circular Genome Plot
Bakta emits a Circos-style PNG/SVG by default. Regenerate with custom styling using bakta_plot.
bakta_plot --output annotation/ \
--prefix E_coli_K12_replot \
--type cog \
--dpi 300 \
annotation/E_coli_K12.json
ls annotation/E_coli_K12_replot.*
python <<'PY'
from pathlib import Path
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread("annotation/E_coli_K12.png")
fig, ax = plt.subplots(figsize=(6, 6))
ax.imshow(img)
ax.axis("off")
ax.set_title("Bakta circular genome annotation")
plt.savefig("bakta_circular_thumbnail.png", dpi=120, bbox_inches="tight")
print("Saved bakta_circular_thumbnail.png")
PY
Step 7: Compute Annotation Quality Statistics
Summarize hypothetical-protein rate, gene density, and feature coverage to assess annotation quality.
import json
import pandas as pd
import matplotlib.pyplot as plt
with open("annotation/E_coli_K12.json") as f:
bakta = json.load(f)
genome_size = bakta["genome"]["size"]
features = bakta["features"]
counts = {}
for f in features:
counts[f["type"]] = counts.get(f["type"], 0) + 1
density = {k: v / (genome_size / 1e6) for k, v in counts.items()}
cds = [f for f in features if f["type"] == "cds"]
n_cds = len(cds)
n_hypo = sum(1 for f in cds if "hypothetical" in (f.get("product") or "").lower())
n_known = n_cds - n_hypo
coding_density = sum(abs(f["stop"] - f["start"] + 1) for f in cds) / genome_size
print(f"Genome: {genome_size:,} bp")
print(f"CDS: {n_cds} ({density.get(, ):f} per Mb)")
()
()
()
()
fig, ax = plt.subplots(figsize=(, ))
items = (counts.items(), key= x: -x[])
labels = [k k, _ items]
values = [v _, v items]
bars = ax.bar(labels, values, color=, edgecolor=)
bar, v (bars, values):
ax.text(bar.get_x() + bar.get_width() / , v + (values) * ,
(v), ha=, fontsize=)
ax.set_ylabel()
ax.set_title()
plt.xticks(rotation=)
plt.tight_layout()
plt.savefig(, dpi=, bbox_inches=)
()
Step 8: Batch Annotation Across Multiple Genomes
Run Bakta over a directory of assemblies and aggregate per-sample summary statistics.
#!/bin/bash
INPUT_DIR="genomes/"
OUTPUT_DIR="annotations/"
DB="db/bakta_db_light"
mkdir -p "$OUTPUT_DIR"
for FASTA in "$INPUT_DIR"/*.fasta; do
SAMPLE=$(basename "$FASTA" .fasta)
echo "Annotating: $SAMPLE"
bakta "$FASTA" \
--db "$DB" \
--output "${OUTPUT_DIR}/${SAMPLE}" \
--prefix "$SAMPLE" \
--threads 4 \
--skip-plot \
--force
done
echo "Batch annotation complete."
from pathlib import Path
import json
import pandas as pd
annotation_dir = Path("annotations/")
rows = []
for json_file in sorted(annotation_dir.glob("*/*.json")):
with open(json_file) as f:
bakta = json.load(f)
sample = json_file.stem
counts = {}
for feat in bakta["features"]:
counts[feat["type"]] = counts.get(feat["type"], 0) + 1
rows.append({
"sample": sample,
"size": bakta["genome"]["size"],
"gc": bakta["genome"]["gc"],
"CDS": counts.get("cds", 0),
"tRNA": counts.get("tRNA", 0),
"rRNA": counts.get("rRNA", 0),
"ncRNA": counts.get("ncRNA", 0),
"crispr": counts.get("crispr", 0),
})
summary_df = pd.DataFrame(rows)
print(f"Annotated genomes: {len(summary_df)}")
print(summary_df.to_string(index=))
summary_df.to_csv(, index=)
()
Key Parameters
| Parameter | Default | Range / Options | Effect |
|---|
--db | — | path to Bakta DB directory | Required; selects light or full reference DB |
--genus | — | any genus name string | Sets organism metadata in GenBank/EMBL output |
--species | — | any species name string | Combined with --genus for organism qualifier |
--strain | — | any string | Adds strain qualifier to organism metadata |
--locus-tag | auto | 3–24 alphanumeric chars | Prefix for locus tags (e.g., ECOLI_00001) |
--complete | off | flag | Treat all input contigs as complete circular replicons |
--plasmid | off | flag | Treat all input contigs as plasmids (circular) |
--meta | off | flag | Disable Prodigal training (use for MAGs / metagenomic bins) |
--translation-table | 11 | NCBI table IDs (1, 4, 11, 25, …) | Genetic code used by Prodigal for CDS translation |
--min-contig-length | 1 | any integer (bp) | Skip contigs shorter than this length |
--threads | 1 | 1–CPU count | Parallel threads for DIAMOND/HMMER searches |
--skip-plot | off | flag | Skip the slow circular plot rendering step |
--keep-contig-headers | off | flag | Preserve original contig IDs instead of renaming |
--proteins | — | path to GenBank or FASTA file | Custom expert protein DB used before UniRef search |
Common Recipes
Recipe: Plasmid-only Annotation
When to use: A finished circular plasmid sequence that should be annotated without chromosome assumptions.
bakta plasmid.fasta \
--db db/bakta_db_light \
--output plasmid_annotation/ \
--prefix pBR322 \
--plasmid \
--complete \
--threads 4
grep -E "rep|inc" plasmid_annotation/pBR322.tsv | head -10
Recipe: MAG (Metagenome-Assembled Genome) Annotation
When to use: Annotating a bin from metagenomic assembly where Prodigal cannot train on the full sequence.
bakta MAG_bin_42.fasta \
--db db/bakta_db_light \
--output mag_annotation/ \
--prefix MAG_bin_42 \
--meta \
--min-contig-length 500 \
--skip-plot \
--threads 8
cat mag_annotation/MAG_bin_42.txt | head -20
Recipe: Annotation with Custom Expert Protein Database
When to use: You have curated reference proteins (e.g., a virulence factor catalog) that should take priority over UniRef.
bakta genome.fasta \
--db db/bakta_db_light \
--proteins virulence_factors.faa \
--output annotation_custom/ \
--prefix novel_strain \
--threads 8
echo "Annotations referencing custom DB:"
grep "User-provided" annotation_custom/novel_strain.tsv | head
Recipe: Convert Bakta GFF3 to a Pandas DataFrame
When to use: You need feature coordinates and qualifiers for downstream coordinate-overlap analyses.
import pandas as pd
def parse_gff3(gff_file):
rows = []
with open(gff_file) as f:
for line in f:
if line.startswith("##FASTA"):
break
if line.startswith("#") or not line.strip():
continue
parts = line.rstrip("\n").split("\t")
if len(parts) < 9:
continue
attrs = {}
for item in parts[8].split(";"):
if "=" in item:
k, v = item.split("=", 1)
attrs[k] = v
rows.append({
"seqname": parts[0],
"feature": parts[2],
"start": int(parts[3]),
"end": int(parts[4]),
"strand": parts[6],
"locus_tag": attrs.get("locus_tag", attrs.get("ID", "")),
: attrs.get(, ),
: attrs.get(, ),
})
pd.DataFrame(rows)
gff_df = parse_gff3()
()
(gff_df[gff_df[] == ].head().to_string(index=))
Expected Outputs
| Output File | Format | Description |
|---|
{prefix}.gff3 | GFF3 | Genome annotation with feature coordinates and attributes; INSDC-compliant |
{prefix}.gbff | GenBank Flat File | Annotated GenBank record for use in Geneious, BioPython, NCBI submission prep |
{prefix}.embl | EMBL | EMBL-format annotation for ENA submission |
{prefix}.fna | FASTA | Nucleotide sequences of the input contigs |
{prefix}.ffn | FASTA | Nucleotide sequences of all annotated features |
{prefix}.faa | FASTA | Protein sequences for all CDS features |
{prefix}.hypotheticals.faa | FASTA | Protein sequences flagged as hypothetical (for further investigation) |
{prefix}.hypotheticals.tsv | TSV | Detailed table for hypothetical CDS with low-confidence hits |
{prefix}.tsv | TSV | Full feature table: seqid, type, start, stop, strand, locus_tag, gene, product, dbxrefs |
{prefix}.json | JSON | Machine-readable summary with genome metadata + every feature with full qualifiers |
{prefix}.png / .svg | Image | Circos circular genome plot colored by COG category |
{prefix}.txt | Text | Plain-text summary of feature counts and per-replicon stats |
{prefix}.log | Text | Full Bakta run log including timing per step |
Troubleshooting
| Problem | Cause | Solution |
|---|
Error: database not found at <path> | DB path incorrect, or DB not extracted | Re-run bakta_db download --output db/ --type light; pass full path to --db |
KILLED during DIAMOND step | Out of memory on full DB | Switch to --type light, reduce --threads, or run on a host with ≥ 32 GB RAM |
| Very high hypothetical-protein rate (>60 %) | Divergent strain or light DB without close hits | Re-run with the full DB or supply a curated --proteins reference set |
tRNAscan-SE: Error opening file | Missing tRNAscan-SE installation in conda env | mamba install -c bioconda trnascan-se and rerun |
| Bakta complains about contig headers | Spaces or special characters in FASTA IDs | Pre-clean headers with awk '/^>/{print ">contig_"++i; next}{print}' |
| Plot generation hangs or fails | Circos / matplotlib backend issue | Re-run with --skip-plot; render later via bakta_plot from the JSON file |
AMRFinderPlus: database not up-to-date warning | AMRFinderPlus DB stale | amrfinder -u to refresh, or pass --skip-amr to bypass |
| Different locus tag prefix than expected | --locus-tag not set, default uses random prefix | Explicitly pass --locus-tag MYORG to control prefix |
| Run is much slower than reported | Default --threads 1 | Set --threads to physical core count; use --skip-plot for batch jobs |
References