#!/usr/bin/env bash # Reference: Panaroo 1.5.1+, PPanGGOLiN 2.2+, Bakta 1.10+, BUSCO 5.7+, Python 3.10+, pandas 2.2+ | Verify API if version differs # Bacterial pangenome construction with Panaroo + PPanGGOLiN cross-validation. set -euo pipefail GENOMES_DIR=${1:?usage: $0 GENOMES_DIR OUTPUT_DIR} OUTPUT_DIR=${2:?missing output dir} THREADS=${THREADS:-16} GENUS=${GENUS:-Escherichia} SPECIES=${SPECIES:-coli} mkdir -p "$OUTPUT_DIR"/{annotated,panaroo,ppanggolin} echo "[1/5] Annotate all genomes with Bakta" for fa in "$GENOMES_DIR"/*.fa; do name=$(basename "$fa" .fa) if [ ! -f "$OUTPUT_DIR/annotated/${name}/${name}.gff3" ]; then bakta --db "${BAKTA_DB:-/path/to/bakta-db}" --threads "$THREADS" \ --output "$OUTPUT_DIR/annotated/${name}" --prefix "$name" \ --genus "$GENUS" --species "$SPECIES" \ "$fa" fi done echo "[2/5] Run Panaroo in strict mode" panaroo -i "$OUTPUT_DIR/annotated"/*/*.gff3 \ -o "$OUTPUT_DIR/panaroo" -t "$THREADS" \ --clean-mode strict --remove-invalid-genes echo "[3/5] Extract Tettelin partition (core/shell/cloud)" python3 - <= 0.95: return 'core' if f >= 0.15: return 'shell' return 'cloud' presence['fraction'] = fraction presence['class'] = fraction.apply(classify) counts = presence['class'].value_counts() print('\nTettelin partition:') print(counts) print(f'\nTotal pangene families: {len(presence)}') print(f'Core (>=95%): {counts.get("core", 0)}') print(f'Shell (15-95%): {counts.get("shell", 0)}') print(f'Cloud (<15%): {counts.get("cloud", 0)}') PY echo "[4/5] PPanGGOLiN HMM partition (cross-validation)" # PPanGGOLiN --anno expects a TSV index (namepath), not a glob. for f in "$OUTPUT_DIR/annotated"/*/*.gff3; do printf "%s\t%s\n" "$(basename "$f" .gff3)" "$(realpath "$f")" done > "$OUTPUT_DIR/ppanggolin/gff_index.tsv" ppanggolin all --anno "$OUTPUT_DIR/ppanggolin/gff_index.tsv" \ -o "$OUTPUT_DIR/ppanggolin" -c "$THREADS" echo "[5/5] Heaps law fitting" python3 - <