gippa 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gippa/__init__.py +3 -0
- gippa/annotate/__init__.py +1 -0
- gippa/annotate/antismash.py +82 -0
- gippa/annotate/antismash_cmd.py +35 -0
- gippa/annotate/bakta.py +324 -0
- gippa/annotate/bakta_cmd.py +64 -0
- gippa/annotate/bakta_layers.py +108 -0
- gippa/annotate/codon_usage.py +182 -0
- gippa/annotate/genomad.py +144 -0
- gippa/annotate/genomad_cmd.py +25 -0
- gippa/annotate/layers.py +433 -0
- gippa/annotate/operons.py +209 -0
- gippa/annotate/ostir.py +116 -0
- gippa/annotate/ostir_cmd.py +14 -0
- gippa/annotate/precomputed.py +189 -0
- gippa/annotate/promotech_cmd.py +46 -0
- gippa/annotate/promoter_calculator.py +145 -0
- gippa/annotate/promoter_calculator_cmd.py +15 -0
- gippa/annotate/promoters.py +299 -0
- gippa/annotate/regions.py +330 -0
- gippa/annotate/terminatornet_cmd.py +26 -0
- gippa/annotate/terminators.py +340 -0
- gippa/annotate/tools.py +246 -0
- gippa/annotate/transtermhp_cmd.py +14 -0
- gippa/annotate/uniop_adapter.py +112 -0
- gippa/annotate/uniop_cmd.py +23 -0
- gippa/annotate/user_annotation.py +340 -0
- gippa/cli.py +1583 -0
- gippa/compare/__init__.py +1 -0
- gippa/compare/abundance.py +123 -0
- gippa/compare/detection.py +62 -0
- gippa/compare/stability.py +120 -0
- gippa/compare/versions.py +156 -0
- gippa/config/__init__.py +128 -0
- gippa/config/defaults.yaml +150 -0
- gippa/config/model.py +437 -0
- gippa/context/__init__.py +1 -0
- gippa/context/autocorrelation.py +353 -0
- gippa/context/diagnostics.py +110 -0
- gippa/context/islands.py +664 -0
- gippa/context/oriter.py +496 -0
- gippa/context/regions.py +561 -0
- gippa/context/regions_table.py +340 -0
- gippa/context/sensitivity.py +314 -0
- gippa/context/summary.py +356 -0
- gippa/context/support.py +81 -0
- gippa/context/tables.py +131 -0
- gippa/context/vocab.py +84 -0
- gippa/data/__init__.py +1 -0
- gippa/data/build_mass_equivalents.py +45 -0
- gippa/data/calibration.py +17 -0
- gippa/data/calibration.yaml +43 -0
- gippa/data/cog_categories.tsv +28 -0
- gippa/data/db_sources.yaml +121 -0
- gippa/data/demo/PROVENANCE.md +19 -0
- gippa/data/demo/layers/bakta.json.gz +0 -0
- gippa/data/demo/layers/operon_pairs.tsv.gz +0 -0
- gippa/data/demo/layers/promoters.tsv.gz +0 -0
- gippa/data/demo/layers/rbs.tsv.gz +0 -0
- gippa/data/demo/layers/rbs_novel.tsv.gz +0 -0
- gippa/data/demo/layers/regions.tsv.gz +0 -0
- gippa/data/demo/layers/terminators.tsv.gz +0 -0
- gippa/data/demo/layers/tx_rates.tsv.gz +0 -0
- gippa/data/demo/sim100k.fna.gz +0 -0
- gippa/data/demo/sim100k_peptides.tsv.gz +0 -0
- gippa/data/mass_equivalents.tsv +154 -0
- gippa/data/modifications.tsv +18 -0
- gippa/data/residue_masses.tsv +25 -0
- gippa/determinants/__init__.py +11 -0
- gippa/determinants/attribution.py +260 -0
- gippa/determinants/features.py +265 -0
- gippa/determinants/models.py +405 -0
- gippa/determinants/null.py +207 -0
- gippa/determinants/sensitivity.py +108 -0
- gippa/determinants/support.py +104 -0
- gippa/exitcodes.py +14 -0
- gippa/function/__init__.py +1 -0
- gippa/function/collections.py +228 -0
- gippa/function/elim.py +82 -0
- gippa/function/gsea.py +665 -0
- gippa/function/ora.py +476 -0
- gippa/function/running.py +80 -0
- gippa/function/sensitivity.py +73 -0
- gippa/function/summary.py +108 -0
- gippa/genetable/__init__.py +5 -0
- gippa/genetable/abundance.py +339 -0
- gippa/genetable/assemble.py +261 -0
- gippa/genetable/contract.py +641 -0
- gippa/genetable/covariates.py +232 -0
- gippa/genetable/detectability.py +412 -0
- gippa/genetable/detection.py +458 -0
- gippa/genetable/eligibility.py +16 -0
- gippa/genetable/evidence.py +308 -0
- gippa/io/__init__.py +1 -0
- gippa/io/_lines.py +37 -0
- gippa/io/_open.py +51 -0
- gippa/io/abundance.py +204 -0
- gippa/io/bed.py +130 -0
- gippa/io/cog_categories.py +22 -0
- gippa/io/compare_io.py +159 -0
- gippa/io/context_tsv.py +150 -0
- gippa/io/converters/__init__.py +24 -0
- gippa/io/converters/base.py +38 -0
- gippa/io/converters/cascadia.py +172 -0
- gippa/io/converters/diann.py +203 -0
- gippa/io/converters/generic.py +91 -0
- gippa/io/converters/strip.py +30 -0
- gippa/io/depth.py +59 -0
- gippa/io/determinants_tables.py +88 -0
- gippa/io/events.py +43 -0
- gippa/io/fasta.py +112 -0
- gippa/io/fgsea_r.py +519 -0
- gippa/io/function_resources.py +61 -0
- gippa/io/function_tables.py +59 -0
- gippa/io/genes_tsv.py +124 -0
- gippa/io/gff3.py +220 -0
- gippa/io/image_key.py +144 -0
- gippa/io/issues.py +33 -0
- gippa/io/manifest_sources.py +204 -0
- gippa/io/novel_tables.py +94 -0
- gippa/io/operon_tables.py +59 -0
- gippa/io/output_checks.py +135 -0
- gippa/io/parquet.py +32 -0
- gippa/io/peptides.py +402 -0
- gippa/io/plot_files.py +193 -0
- gippa/io/plot_support.py +48 -0
- gippa/io/probed.py +79 -0
- gippa/io/protein_fasta.py +141 -0
- gippa/io/regions_tsv.py +63 -0
- gippa/io/run_view.py +362 -0
- gippa/io/sensitivity_tsv.py +30 -0
- gippa/io/summary_tsv.py +63 -0
- gippa/io/tsv.py +111 -0
- gippa/io/variant_tables.py +70 -0
- gippa/io/vcf.py +265 -0
- gippa/io/work_tables.py +233 -0
- gippa/makedb/__init__.py +4 -0
- gippa/makedb/build.py +437 -0
- gippa/manifest.py +462 -0
- gippa/mapping/__init__.py +1 -0
- gippa/mapping/chance.py +84 -0
- gippa/mapping/contaminants.py +307 -0
- gippa/mapping/decoys.py +101 -0
- gippa/mapping/denovo_precision.py +205 -0
- gippa/mapping/diffops.py +98 -0
- gippa/mapping/digest.py +61 -0
- gippa/mapping/exact.py +206 -0
- gippa/mapping/frameshift.py +780 -0
- gippa/mapping/hits.py +46 -0
- gippa/mapping/initiator.py +230 -0
- gippa/mapping/loci.py +537 -0
- gippa/mapping/masses.py +116 -0
- gippa/mapping/miniprot.py +226 -0
- gippa/mapping/outputs.py +353 -0
- gippa/mapping/placement_fdr.py +564 -0
- gippa/mapping/protein_assign.py +239 -0
- gippa/mapping/qgram.py +236 -0
- gippa/mapping/readthrough.py +250 -0
- gippa/mapping/sixframe.py +436 -0
- gippa/mapping/tblastn.py +378 -0
- gippa/mapping/tolerant.py +343 -0
- gippa/mapping/tolerant_dp.py +342 -0
- gippa/model/__init__.py +43 -0
- gippa/model/coords.py +129 -0
- gippa/model/genes.py +176 -0
- gippa/model/genetic_code.py +77 -0
- gippa/novel/__init__.py +1 -0
- gippa/novel/candidates.py +352 -0
- gippa/novel/class_call.py +194 -0
- gippa/novel/evidence.py +376 -0
- gippa/novel/fdr.py +430 -0
- gippa/novel/outputs.py +367 -0
- gippa/operons/__init__.py +1 -0
- gippa/operons/detection_limit.py +554 -0
- gippa/operons/discordance.py +319 -0
- gippa/operons/flags.py +269 -0
- gippa/operons/missed.py +263 -0
- gippa/operons/pairs.py +293 -0
- gippa/operons/tu_table.py +84 -0
- gippa/preflight/__init__.py +1 -0
- gippa/preflight/checks.py +59 -0
- gippa/preflight/environment.py +1075 -0
- gippa/preflight/evidence.py +467 -0
- gippa/preflight/gbff.py +80 -0
- gippa/preflight/genetic_code.py +205 -0
- gippa/preflight/genome.py +317 -0
- gippa/preflight/report.py +189 -0
- gippa/preflight/run.py +668 -0
- gippa/preflight/tool_genetic_code.py +113 -0
- gippa/py.typed +0 -0
- gippa/report/__init__.py +1 -0
- gippa/report/build.py +372 -0
- gippa/report/checks.py +154 -0
- gippa/report/compare.py +66 -0
- gippa/report/content.py +378 -0
- gippa/report/snippets/islands.html +3 -0
- gippa/report/tables.py +41 -0
- gippa/report/templates/compare.html.j2 +97 -0
- gippa/report/templates/plotly_block.html.j2 +10 -0
- gippa/report/templates/report.css +16 -0
- gippa/report/templates/report.html.j2 +191 -0
- gippa/report/templates/tables.js +47 -0
- gippa/rng.py +16 -0
- gippa/runner/__init__.py +1 -0
- gippa/runner/local.py +393 -0
- gippa/runner/nextflow.py +284 -0
- gippa/runner/prereqs.py +155 -0
- gippa/runner/profiles.py +253 -0
- gippa/runner/workflow_path.py +16 -0
- gippa/schemas/__init__.py +17 -0
- gippa/schemas/annotation/id_map.schema.json +15 -0
- gippa/schemas/annotation/operon_pairs.schema.json +11 -0
- gippa/schemas/annotation/regions.schema.json +17 -0
- gippa/schemas/annotation/terminators_crosscheck.schema.json +12 -0
- gippa/schemas/annotation/tus.schema.json +16 -0
- gippa/schemas/config.schema.json +1407 -0
- gippa/schemas/datapackage.json +21 -0
- gippa/schemas/db_manifest.schema.json +52 -0
- gippa/schemas/plot_support/determinants_pd.json +12 -0
- gippa/schemas/plot_support/determinants_shap.json +11 -0
- gippa/schemas/plot_support/gc_skew.json +13 -0
- gippa/schemas/plot_support/gsea_running.json +14 -0
- gippa/schemas/plot_support/null_determinants.json +11 -0
- gippa/schemas/plot_support/null_islands.json +12 -0
- gippa/schemas/plot_support/null_regions.json +13 -0
- gippa/schemas/plot_support/null_regions_grid.json +13 -0
- gippa/schemas/plot_support/replicon_landmarks.json +14 -0
- gippa/schemas/plot_support/variant_windows.json +13 -0
- gippa/schemas/preflight.schema.json +114 -0
- gippa/schemas/run_manifest.schema.json +293 -0
- gippa/schemas/tables/autocorrelation.schema.json +24 -0
- gippa/schemas/tables/category_summary.schema.json +20 -0
- gippa/schemas/tables/compare_abundance.schema.json +21 -0
- gippa/schemas/tables/compare_detection.schema.json +15 -0
- gippa/schemas/tables/compare_stability.schema.json +16 -0
- gippa/schemas/tables/detection_summary.schema.json +16 -0
- gippa/schemas/tables/determinants_attribution.schema.json +19 -0
- gippa/schemas/tables/determinants_features.schema.json +44 -0
- gippa/schemas/tables/determinants_model.schema.json +16 -0
- gippa/schemas/tables/enrichment_detection.schema.json +33 -0
- gippa/schemas/tables/enrichment_gsea.schema.json +29 -0
- gippa/schemas/tables/genes.schema.json +82 -0
- gippa/schemas/tables/islands.schema.json +32 -0
- gippa/schemas/tables/mapping_fdr.schema.json +22 -0
- gippa/schemas/tables/novel_orfs.schema.json +34 -0
- gippa/schemas/tables/operon_flags.schema.json +19 -0
- gippa/schemas/tables/operons.schema.json +19 -0
- gippa/schemas/tables/pairs.schema.json +37 -0
- gippa/schemas/tables/peptide_hits.schema.json +30 -0
- gippa/schemas/tables/pileup.schema.json +18 -0
- gippa/schemas/tables/protein_hits.schema.json +22 -0
- gippa/schemas/tables/regions_stats.schema.json +32 -0
- gippa/schemas/tables/sensitivity.schema.json +14 -0
- gippa/schemas/tables/unmapped.schema.json +13 -0
- gippa/schemas/tables/variants.schema.json +28 -0
- gippa/setupdb/__init__.py +50 -0
- gippa/setupdb/dbdir.py +116 -0
- gippa/setupdb/images.py +259 -0
- gippa/setupdb/installed.py +29 -0
- gippa/setupdb/items.py +342 -0
- gippa/setupdb/manifest.py +133 -0
- gippa/setupdb/run.py +719 -0
- gippa/setupdb/space.py +262 -0
- gippa/setupdb/uniop_build.py +429 -0
- gippa/stats/__init__.py +1 -0
- gippa/stats/_fgsea_kernels.py +110 -0
- gippa/stats/autocorr.py +504 -0
- gippa/stats/bootstrap.py +168 -0
- gippa/stats/cutoffs.py +537 -0
- gippa/stats/decoy_fdr.py +394 -0
- gippa/stats/fgsea.py +226 -0
- gippa/stats/mayu.py +167 -0
- gippa/stats/multitest.py +38 -0
- gippa/stats/permutation.py +156 -0
- gippa/stats/poisson_binomial.py +281 -0
- gippa/stats/robust.py +130 -0
- gippa/stats/scan.py +299 -0
- gippa/stats/scores.py +54 -0
- gippa/stats/smooth.py +237 -0
- gippa/stats/tda.py +143 -0
- gippa/stats/tdist.py +348 -0
- gippa/stats/wallenius.py +110 -0
- gippa/steps/__init__.py +14 -0
- gippa/steps/annotation.py +151 -0
- gippa/steps/codon.py +87 -0
- gippa/steps/common.py +56 -0
- gippa/steps/context.py +165 -0
- gippa/steps/context_stats.py +344 -0
- gippa/steps/detectability.py +111 -0
- gippa/steps/determinants.py +242 -0
- gippa/steps/function.py +193 -0
- gippa/steps/gene_table.py +164 -0
- gippa/steps/manifest.py +165 -0
- gippa/steps/mapping.py +631 -0
- gippa/steps/novel.py +543 -0
- gippa/steps/operons.py +334 -0
- gippa/steps/plots.py +298 -0
- gippa/steps/promoters.py +96 -0
- gippa/steps/rbs.py +68 -0
- gippa/steps/regions.py +162 -0
- gippa/steps/registry.py +329 -0
- gippa/steps/sensitivity.py +302 -0
- gippa/steps/terminators.py +148 -0
- gippa/steps/transcription.py +55 -0
- gippa/steps/variants.py +243 -0
- gippa/variants/__init__.py +1 -0
- gippa/variants/align.py +373 -0
- gippa/variants/error_rates.py +124 -0
- gippa/variants/flags.py +211 -0
- gippa/variants/pileup.py +357 -0
- gippa/variants/qc.py +66 -0
- gippa/variants/vcf_records.py +201 -0
- gippa/viz/__init__.py +1 -0
- gippa/viz/base.py +218 -0
- gippa/viz/checklist.py +371 -0
- gippa/viz/circos.py +640 -0
- gippa/viz/compare.py +301 -0
- gippa/viz/correlogram.py +212 -0
- gippa/viz/detection.py +409 -0
- gippa/viz/determinants.py +425 -0
- gippa/viz/diagnostics.py +280 -0
- gippa/viz/enrichment.py +557 -0
- gippa/viz/interactive.py +60 -0
- gippa/viz/landscape.py +302 -0
- gippa/viz/locus.py +593 -0
- gippa/viz/mapping_fdr.py +332 -0
- gippa/viz/mapping_sankey.py +231 -0
- gippa/viz/novel_orfs.py +216 -0
- gippa/viz/null_panels.py +236 -0
- gippa/viz/operon_pairs.py +328 -0
- gippa/viz/qc.py +313 -0
- gippa/viz/style.py +147 -0
- gippa/viz/support.py +211 -0
- gippa/viz/twins.py +304 -0
- gippa/viz/twins_determinants.py +109 -0
- gippa/viz/twins_panels.py +286 -0
- gippa/viz/variants.py +368 -0
- gippa/workflow/assets/ps/ps +16 -0
- gippa/workflow/assets/stub/bakta.json +52 -0
- gippa/workflow/bin/gippa_fgsea.R +144 -0
- gippa/workflow/bin/gippa_terminatornet_shard +292 -0
- gippa/workflow/conf/base.config +73 -0
- gippa/workflow/conf/container_common.config +14 -0
- gippa/workflow/conf/containers.config +50 -0
- gippa/workflow/conf/farmshare.config +41 -0
- gippa/workflow/conf/farmshare_local.config +24 -0
- gippa/workflow/conf/images.json +65 -0
- gippa/workflow/conf/prebuilt_envs.config +20 -0
- gippa/workflow/conf/slurm.config +18 -0
- gippa/workflow/conf/standard.config +2 -0
- gippa/workflow/conf/test.config +4 -0
- gippa/workflow/containers/bactermfinder/Dockerfile +26 -0
- gippa/workflow/containers/fgsea/Dockerfile +18 -0
- gippa/workflow/containers/ostir/Dockerfile +18 -0
- gippa/workflow/containers/promotech/Dockerfile +31 -0
- gippa/workflow/containers/promoter_calculator/Dockerfile +18 -0
- gippa/workflow/containers/step/Dockerfile +42 -0
- gippa/workflow/containers/step/Dockerfile.dockerignore +7 -0
- gippa/workflow/containers/step/build-requirements.lock +7 -0
- gippa/workflow/containers/step/licences.allow +37 -0
- gippa/workflow/containers/step/requirements.lock +1871 -0
- gippa/workflow/containers/step-r/Dockerfile +46 -0
- gippa/workflow/containers/step-r/Dockerfile.dockerignore +7 -0
- gippa/workflow/containers/terminatornet/Dockerfile +26 -0
- gippa/workflow/containers/uniop/Dockerfile +19 -0
- gippa/workflow/containers/uniop/uniop.def +30 -0
- gippa/workflow/envs/antismash.yml +5 -0
- gippa/workflow/envs/bactermfinder.yml +19 -0
- gippa/workflow/envs/bakta.yml +5 -0
- gippa/workflow/envs/blast.yml +5 -0
- gippa/workflow/envs/cascadia.yml +47 -0
- gippa/workflow/envs/fgsea.yml +11 -0
- gippa/workflow/envs/genomad.yml +5 -0
- gippa/workflow/envs/locks/bactermfinder.explicit.txt +64 -0
- gippa/workflow/envs/locks/bactermfinder.pip.txt +50 -0
- gippa/workflow/envs/locks/cascadia.explicit.txt +254 -0
- gippa/workflow/envs/locks/cascadia.pip.txt +142 -0
- gippa/workflow/envs/locks/fgsea.explicit.txt +127 -0
- gippa/workflow/envs/locks/miniprot.explicit.txt +9 -0
- gippa/workflow/envs/locks/ostir.explicit.txt +48 -0
- gippa/workflow/envs/locks/ostir.pip.txt +3 -0
- gippa/workflow/envs/locks/promotech.explicit.txt +154 -0
- gippa/workflow/envs/locks/promotech.pip.txt +96 -0
- gippa/workflow/envs/locks/promoter_calculator.explicit.txt +49 -0
- gippa/workflow/envs/locks/promoter_calculator.pip.txt +9 -0
- gippa/workflow/envs/locks/terminatornet.explicit.txt +49 -0
- gippa/workflow/envs/locks/terminatornet.pip.txt +7 -0
- gippa/workflow/envs/locks/timsconvert.explicit.txt +30 -0
- gippa/workflow/envs/locks/timsconvert.pip.txt +37 -0
- gippa/workflow/envs/locks/uniop.explicit.txt +53 -0
- gippa/workflow/envs/locks/uniop.pip.txt +16 -0
- gippa/workflow/envs/miniprot.yml +5 -0
- gippa/workflow/envs/ostir.yml +10 -0
- gippa/workflow/envs/promotech.yml +28 -0
- gippa/workflow/envs/promoter_calculator.yml +13 -0
- gippa/workflow/envs/terminatornet.yml +12 -0
- gippa/workflow/envs/timsconvert.yml +20 -0
- gippa/workflow/envs/transtermhp.yml +5 -0
- gippa/workflow/envs/uniop.yml +11 -0
- gippa/workflow/main.nf +150 -0
- gippa/workflow/modules/local/antismash/main.nf +41 -0
- gippa/workflow/modules/local/antismash/meta.yml +17 -0
- gippa/workflow/modules/local/bactermfinder/main.nf +44 -0
- gippa/workflow/modules/local/bactermfinder/meta.yml +7 -0
- gippa/workflow/modules/local/bakta/main.nf +52 -0
- gippa/workflow/modules/local/bakta/meta.yml +11 -0
- gippa/workflow/modules/local/genomad/main.nf +39 -0
- gippa/workflow/modules/local/genomad/meta.yml +7 -0
- gippa/workflow/modules/local/gippa_step/main.nf +119 -0
- gippa/workflow/modules/local/gippa_step/meta.yml +32 -0
- gippa/workflow/modules/local/miniprot/main.nf +40 -0
- gippa/workflow/modules/local/miniprot/meta.yml +27 -0
- gippa/workflow/modules/local/ostir/main.nf +35 -0
- gippa/workflow/modules/local/ostir/meta.yml +7 -0
- gippa/workflow/modules/local/promotech/main.nf +40 -0
- gippa/workflow/modules/local/promotech/meta.yml +7 -0
- gippa/workflow/modules/local/promoter_calculator/main.nf +44 -0
- gippa/workflow/modules/local/promoter_calculator/meta.yml +7 -0
- gippa/workflow/modules/local/tblastn/main.nf +40 -0
- gippa/workflow/modules/local/tblastn/meta.yml +27 -0
- gippa/workflow/modules/local/terminatornet/main.nf +36 -0
- gippa/workflow/modules/local/terminatornet/meta.yml +7 -0
- gippa/workflow/modules/local/terminatornet_chunk/main.nf +24 -0
- gippa/workflow/modules/local/terminatornet_merge/main.nf +36 -0
- gippa/workflow/modules/local/terminatornet_plan/main.nf +26 -0
- gippa/workflow/modules/local/transtermhp/main.nf +34 -0
- gippa/workflow/modules/local/transtermhp/meta.yml +7 -0
- gippa/workflow/modules/local/uniop/main.nf +36 -0
- gippa/workflow/modules/local/uniop/meta.yml +7 -0
- gippa/workflow/nextflow.config +88 -0
- gippa/workflow/subworkflows/local/annotate.nf +273 -0
- gippa/workflow/subworkflows/local/context.nf +81 -0
- gippa/workflow/subworkflows/local/determinants.nf +39 -0
- gippa/workflow/subworkflows/local/function.nf +37 -0
- gippa/workflow/subworkflows/local/genetable.nf +58 -0
- gippa/workflow/subworkflows/local/mapping.nf +82 -0
- gippa/workflow/subworkflows/local/novel.nf +101 -0
- gippa/workflow/subworkflows/local/operons.nf +37 -0
- gippa/workflow/subworkflows/local/report.nf +26 -0
- gippa/workflow/subworkflows/local/sensitivity.nf +38 -0
- gippa/workflow/subworkflows/local/terminatornet_sharded.nf +23 -0
- gippa/workflow/subworkflows/local/variants.nf +75 -0
- gippa-1.0.0.dist-info/METADATA +164 -0
- gippa-1.0.0.dist-info/RECORD +447 -0
- gippa-1.0.0.dist-info/WHEEL +4 -0
- gippa-1.0.0.dist-info/entry_points.txt +2 -0
- gippa-1.0.0.dist-info/licenses/LICENSE +21 -0
gippa/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Module 1 tool wrappers: build tool inputs and parse tool outputs (never import the tool)."""
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""antiSMASH 8.0.4 JSON parser: BGC regions (GIPPA_SPEC.md §7.1.1, §7.1.7; M2-18).
|
|
2
|
+
|
|
3
|
+
``records[].id`` = contig; ``records[].areas[]`` ``start``/``end`` are 0-based half-open;
|
|
4
|
+
``start > end`` on a circular record is origin-crossing and stored unwrapped (``end + length``),
|
|
5
|
+
on a linear record it is corrupt input. Products keep antiSMASH order, duplicates collapsed.
|
|
6
|
+
Core genes: ``locus_tag`` of CDS features wholly inside the area whose ``gene_kind`` qualifier
|
|
7
|
+
contains exactly ``biosynthetic`` (M2-18 lead decision 1), in feature order.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from gippa.model.coords import Interval, Replicon, check_interval
|
|
20
|
+
|
|
21
|
+
# Biopython location parts: ``[a:b](±)``, fuzzy ``[<a:b]`` / ``[a:>b]``, inside ``join{…}``.
|
|
22
|
+
_PART = re.compile(r"\[<?(\d+):>?(\d+)\]")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class BgcRegion:
|
|
27
|
+
contig: str
|
|
28
|
+
interval: Interval
|
|
29
|
+
products: tuple[str, ...]
|
|
30
|
+
core_genes: tuple[str, ...]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _area_interval(area: Mapping[str, Any], rep: Replicon) -> Interval:
|
|
34
|
+
start, end = int(area["start"]), int(area["end"])
|
|
35
|
+
if start > end:
|
|
36
|
+
if rep.topology != "circular":
|
|
37
|
+
raise ValueError(f"antiSMASH: area {rep.id}:{start}-{end} wraps on a linear contig")
|
|
38
|
+
end += rep.length
|
|
39
|
+
iv = Interval(rep.id, start, end, ".")
|
|
40
|
+
try:
|
|
41
|
+
check_interval(iv, rep)
|
|
42
|
+
except ValueError as exc:
|
|
43
|
+
raise ValueError(f"antiSMASH: area on contig {rep.id!r} out of range: {exc}") from exc
|
|
44
|
+
return iv
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _inside(location: str, area: Interval, length: int) -> bool:
|
|
48
|
+
parts = [(int(a), int(b)) for a, b in _PART.findall(location)]
|
|
49
|
+
if not parts:
|
|
50
|
+
raise ValueError(f"antiSMASH: cannot parse CDS location {location!r}")
|
|
51
|
+
return all(
|
|
52
|
+
any(area.start <= a + s and b + s <= area.end for s in (0, length)) for a, b in parts
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _core_genes(features: list[Mapping[str, Any]], area: Interval, length: int) -> tuple[str, ...]:
|
|
57
|
+
tags: dict[str, None] = {}
|
|
58
|
+
for f in features:
|
|
59
|
+
q = f.get("qualifiers", {})
|
|
60
|
+
if f.get("type") != "CDS" or "biosynthetic" not in q.get("gene_kind", []):
|
|
61
|
+
continue
|
|
62
|
+
if _inside(f["location"], area, length):
|
|
63
|
+
tags.update(dict.fromkeys(q.get("locus_tag", [])))
|
|
64
|
+
return tuple(tags)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_antismash_json(path: Path, replicons: Mapping[str, Replicon]) -> list[BgcRegion]:
|
|
68
|
+
"""BGC regions of an antiSMASH JSON, in record then area order."""
|
|
69
|
+
data = json.loads(path.read_text())
|
|
70
|
+
out: list[BgcRegion] = []
|
|
71
|
+
for rec in data.get("records", []):
|
|
72
|
+
rid = rec["id"]
|
|
73
|
+
if rid not in replicons:
|
|
74
|
+
raise ValueError(f"antiSMASH: record {rid!r} not in the genome")
|
|
75
|
+
rep = replicons[rid]
|
|
76
|
+
for area in rec.get("areas", []):
|
|
77
|
+
iv = _area_interval(area, rep)
|
|
78
|
+
products = tuple(dict.fromkeys(area.get("products", [])))
|
|
79
|
+
out.append(
|
|
80
|
+
BgcRegion(rid, iv, products, _core_genes(rec.get("features", []), iv, rep.length))
|
|
81
|
+
)
|
|
82
|
+
return out
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""antiSMASH command line (GIPPA_SPEC.md §7.1.1; M2-18).
|
|
2
|
+
|
|
3
|
+
antiSMASH is AGPL-3.0: it is never imported, only run as a separate process (R5). Kept in sync
|
|
4
|
+
with ``workflow/modules/local/antismash/main.nf``, whose task shell passes
|
|
5
|
+
``--databases <db_dir>/antismash`` only when that path is a directory with content and otherwise
|
|
6
|
+
leaves antiSMASH on the image's bundled databases. This supersedes M2-18 lead decisions 2 and 4
|
|
7
|
+
(``--databases`` always passed and required) per #515.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def antismash_command(gbff: Path, outdir: Path, threads: int, databases: Path | None) -> list[str]:
|
|
16
|
+
"""``antismash --genefinding-tool none --minimal [--databases <db>] --cpus <n> --output-dir
|
|
17
|
+
<outdir> <gbff>``.
|
|
18
|
+
|
|
19
|
+
``databases`` stays a required argument; ``None`` omits the flag (the databases of the image,
|
|
20
|
+
which is what ``gippa setup`` records under a container profile). Supersedes M2-18 lead
|
|
21
|
+
decisions 2 and 4 per #515.
|
|
22
|
+
"""
|
|
23
|
+
flag = [] if databases is None else ["--databases", str(databases)]
|
|
24
|
+
return [
|
|
25
|
+
"antismash",
|
|
26
|
+
"--genefinding-tool",
|
|
27
|
+
"none",
|
|
28
|
+
"--minimal",
|
|
29
|
+
*flag,
|
|
30
|
+
"--cpus",
|
|
31
|
+
str(threads),
|
|
32
|
+
"--output-dir",
|
|
33
|
+
str(outdir),
|
|
34
|
+
str(gbff),
|
|
35
|
+
]
|
gippa/annotate/bakta.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Defensive parser for Bakta 1.12.2 JSON (GIPPA_SPEC.md §7.1.1, §7.1.2, §6.5; M2-03).
|
|
2
|
+
|
|
3
|
+
``parse_bakta_json`` returns the shared M3-01 ``GeneModel`` plus the contig sequences, the Bakta
|
|
4
|
+
and DB versions and a count of skipped (unknown) feature types. Field-name variations are
|
|
5
|
+
tolerated: a feature's contig is ``sequence`` or ``contig``; sequence records are ``sequences``
|
|
6
|
+
or ``contigs`` with nucleotides in ``nt`` or ``sequence``. Bakta's 1-based closed ``start``/``stop``
|
|
7
|
+
become 0-based half-open intervals; ``start > stop`` (``edge``) is unwrapped to ``end > length`` on
|
|
8
|
+
circular replicons and is an error on linear ones. Every malformed input raises
|
|
9
|
+
``BaktaParseError`` (a ``ValueError``) naming the feature.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import re
|
|
16
|
+
from collections import Counter
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from types import MappingProxyType
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
21
|
+
|
|
22
|
+
from gippa.io._open import read_json
|
|
23
|
+
from gippa.model.coords import (
|
|
24
|
+
Interval,
|
|
25
|
+
Replicon,
|
|
26
|
+
RepliconKind,
|
|
27
|
+
Strand,
|
|
28
|
+
from_one_based_closed,
|
|
29
|
+
interval_sort_key,
|
|
30
|
+
)
|
|
31
|
+
from gippa.model.genes import _NC_TYPES, Gene, GeneModel, GeneType, NcFeature
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from _typeshed import StrPath
|
|
35
|
+
|
|
36
|
+
DbType = Literal["full", "light", "unknown"]
|
|
37
|
+
|
|
38
|
+
_HYPOTHETICAL = re.compile(r"^hypothetical protein")
|
|
39
|
+
_COMP = str.maketrans("ACGTNacgtn", "TGCANtgcan")
|
|
40
|
+
_GENE_TYPES = ("cds", "sorf")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_log = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BaktaParseError(ValueError):
|
|
47
|
+
"""Malformed or inconsistent Bakta JSON."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AnnotationTableMismatch(ValueError):
|
|
51
|
+
"""The JSON's ``genome.translation_table`` differs from the run's table (M11-16 item 3.4)."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _check_table(data: Mapping[str, Any], table: int) -> None:
|
|
55
|
+
genome = data.get("genome")
|
|
56
|
+
recorded = genome.get("translation_table") if isinstance(genome, dict) else None
|
|
57
|
+
if recorded is None:
|
|
58
|
+
_log.info(
|
|
59
|
+
"annotation.table_unrecorded: the annotation JSON records no translation table; "
|
|
60
|
+
"assuming %d",
|
|
61
|
+
table,
|
|
62
|
+
extra={"event": "annotation.table_unrecorded", "data": {"translation_table": table}},
|
|
63
|
+
)
|
|
64
|
+
return
|
|
65
|
+
if not isinstance(recorded, int) or isinstance(recorded, bool):
|
|
66
|
+
raise BaktaParseError(
|
|
67
|
+
f"annotation JSON genome.translation_table must be an integer, got {recorded!r:.100}"
|
|
68
|
+
)
|
|
69
|
+
if recorded != table:
|
|
70
|
+
raise AnnotationTableMismatch(
|
|
71
|
+
f"annotation JSON was made with translation table {recorded}; this run uses {table}: "
|
|
72
|
+
f"re-run Bakta with --translation-table {table} or set "
|
|
73
|
+
f"annotation.translation_table: {recorded}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True, slots=True)
|
|
78
|
+
class BaktaAnnotation:
|
|
79
|
+
model: GeneModel
|
|
80
|
+
sequences: Mapping[str, str]
|
|
81
|
+
bakta_version: str
|
|
82
|
+
db_version: str
|
|
83
|
+
db_type: DbType
|
|
84
|
+
skipped_types: Mapping[str, int]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _revcomp(s: str) -> str:
|
|
88
|
+
return s.translate(_COMP)[::-1]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def interval_nt(seq: str, iv: Interval) -> str:
|
|
92
|
+
"""Strand-aware, origin-aware nucleotides of ``iv`` (``end`` may exceed ``len(seq)``)."""
|
|
93
|
+
n = len(seq)
|
|
94
|
+
s = seq[iv.start : iv.end] if iv.end <= n else seq[iv.start :] + seq[: iv.end - n]
|
|
95
|
+
return _revcomp(s) if iv.strand == "-" else s
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _record_nt(rec: Mapping[str, Any], cid: str) -> str | None:
|
|
99
|
+
nt = rec.get("nt")
|
|
100
|
+
if nt is None:
|
|
101
|
+
nt = rec.get("sequence")
|
|
102
|
+
if nt is not None and not isinstance(nt, str):
|
|
103
|
+
raise BaktaParseError(f"sequence {cid!r}: nt must be a string, got {type(nt).__name__}")
|
|
104
|
+
return nt or None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _replicons_from_json(
|
|
108
|
+
records: list[Any],
|
|
109
|
+
replicons: Mapping[str, Replicon] | None,
|
|
110
|
+
genome: Mapping[str, str] | None,
|
|
111
|
+
) -> tuple[dict[str, Replicon], dict[str, str]]:
|
|
112
|
+
reps: dict[str, Replicon] = {}
|
|
113
|
+
seqs: dict[str, str] = {}
|
|
114
|
+
for rec in records:
|
|
115
|
+
if not isinstance(rec, dict) or not rec.get("id"):
|
|
116
|
+
raise BaktaParseError(f"sequence record without id: {rec!r:.200}")
|
|
117
|
+
cid = str(rec["id"])
|
|
118
|
+
nt = _record_nt(rec, cid)
|
|
119
|
+
if replicons is not None and cid not in replicons:
|
|
120
|
+
raise BaktaParseError(f"sequence {cid!r} is not among the given replicons")
|
|
121
|
+
if nt is None and genome is not None and cid in genome:
|
|
122
|
+
nt = genome[cid]
|
|
123
|
+
if nt is not None:
|
|
124
|
+
seqs[cid] = nt
|
|
125
|
+
elif replicons is None:
|
|
126
|
+
raise BaktaParseError(
|
|
127
|
+
f"sequence {cid!r} has no nucleotides in the JSON and no genome was given"
|
|
128
|
+
)
|
|
129
|
+
kind = str(rec.get("type", "")).lower()
|
|
130
|
+
topology = str(rec.get("topology") or "linear").lower()
|
|
131
|
+
if topology not in ("circular", "linear"):
|
|
132
|
+
raise BaktaParseError(f"sequence {cid!r}: unknown topology {topology!r}")
|
|
133
|
+
length = replicons[cid].length if replicons is not None else len(seqs[cid])
|
|
134
|
+
declared = rec.get("length")
|
|
135
|
+
if declared is not None and declared != length:
|
|
136
|
+
raise BaktaParseError(
|
|
137
|
+
f"sequence {cid!r}: JSON length {declared!r} != genome sequence length {length}"
|
|
138
|
+
)
|
|
139
|
+
if cid in seqs and len(seqs[cid]) != length:
|
|
140
|
+
raise BaktaParseError(
|
|
141
|
+
f"sequence {cid!r}: nucleotide length {len(seqs[cid])} != replicon length {length}"
|
|
142
|
+
)
|
|
143
|
+
reps[cid] = Replicon(
|
|
144
|
+
cid,
|
|
145
|
+
length,
|
|
146
|
+
"circular" if topology == "circular" else "linear",
|
|
147
|
+
cast(RepliconKind, kind if kind in ("chromosome", "plasmid") else "unknown"),
|
|
148
|
+
)
|
|
149
|
+
if replicons is None:
|
|
150
|
+
return reps, seqs
|
|
151
|
+
for cid in replicons:
|
|
152
|
+
if cid not in seqs and genome is not None and cid in genome:
|
|
153
|
+
seqs[cid] = genome[cid]
|
|
154
|
+
return dict(replicons), seqs
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _int(f: Mapping[str, Any], key: str, name: str) -> int:
|
|
158
|
+
v = f.get(key)
|
|
159
|
+
if isinstance(v, bool) or not isinstance(v, int):
|
|
160
|
+
raise BaktaParseError(f"feature {name!r}: {key} must be an int, got {v!r}")
|
|
161
|
+
return v
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _opt_str(f: Mapping[str, Any], key: str, name: str) -> str | None:
|
|
165
|
+
v = f.get(key)
|
|
166
|
+
if v is not None and not isinstance(v, str):
|
|
167
|
+
raise BaktaParseError(f"feature {name!r}: {key} must be a string, got {type(v).__name__}")
|
|
168
|
+
return v
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _expert(f: Mapping[str, Any], name: str) -> tuple[str, ...]:
|
|
172
|
+
raw = f.get("expert")
|
|
173
|
+
if raw is None:
|
|
174
|
+
return ()
|
|
175
|
+
if not isinstance(raw, list) or not all(isinstance(e, dict) for e in raw):
|
|
176
|
+
raise BaktaParseError(
|
|
177
|
+
f"feature {name!r}: expert must be a list of objects, got {raw!r:.200}"
|
|
178
|
+
)
|
|
179
|
+
out: list[str] = []
|
|
180
|
+
for e in raw:
|
|
181
|
+
src = e.get("type") or e.get("source")
|
|
182
|
+
if src and str(src) not in out:
|
|
183
|
+
out.append(str(src))
|
|
184
|
+
return tuple(out)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _interval(
|
|
188
|
+
f: Mapping[str, Any], name: str, replicons: Mapping[str, Replicon], *, coding: bool
|
|
189
|
+
) -> Interval:
|
|
190
|
+
contig = f.get("sequence", f.get("contig"))
|
|
191
|
+
if contig is None:
|
|
192
|
+
raise BaktaParseError(f"feature {name!r}: no 'sequence' or 'contig' field")
|
|
193
|
+
if contig not in replicons:
|
|
194
|
+
raise BaktaParseError(f"feature {name!r}: unknown contig {contig!r}")
|
|
195
|
+
raw = f.get("strand")
|
|
196
|
+
if raw not in ("+", "-"):
|
|
197
|
+
if coding or raw not in (None, "?", "."):
|
|
198
|
+
raise BaktaParseError(f"feature {name!r}: invalid strand {raw!r}")
|
|
199
|
+
raw = "."
|
|
200
|
+
start, stop = _int(f, "start", name), _int(f, "stop", name)
|
|
201
|
+
try:
|
|
202
|
+
return from_one_based_closed(contig, start, stop, cast(Strand, raw), replicons[contig])
|
|
203
|
+
except ValueError as exc:
|
|
204
|
+
raise BaktaParseError(f"feature {name!r}: {exc}") from exc
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _gene(f: Mapping[str, Any], replicons: Mapping[str, Replicon], seqs: Mapping[str, str]) -> Gene:
|
|
208
|
+
if not f.get("locus"):
|
|
209
|
+
raise BaktaParseError(f"{f.get('type')} feature without locus: {f!r:.200}")
|
|
210
|
+
locus = str(f["locus"])
|
|
211
|
+
iv = _interval(f, locus, replicons, coding=True)
|
|
212
|
+
ftype = cast(GeneType, str(f["type"]).lower())
|
|
213
|
+
if ftype == "cds" and (f.get("pseudo") or "pseudogene" in f):
|
|
214
|
+
ftype = "pseudogene"
|
|
215
|
+
xrefs = f.get("db_xrefs") or []
|
|
216
|
+
if not isinstance(xrefs, list | tuple):
|
|
217
|
+
raise BaktaParseError(f"feature {locus!r}: db_xrefs must be a list, got {xrefs!r}")
|
|
218
|
+
nt = _opt_str(f, "nt", locus) or (
|
|
219
|
+
interval_nt(seqs[iv.contig], iv) if iv.contig in seqs else None
|
|
220
|
+
)
|
|
221
|
+
product = f.get("product") or ""
|
|
222
|
+
hypothetical = (
|
|
223
|
+
bool(f["hypothetical"]) if "hypothetical" in f else bool(_HYPOTHETICAL.match(product))
|
|
224
|
+
)
|
|
225
|
+
rep = replicons[iv.contig]
|
|
226
|
+
at_end = rep.topology == "linear" and (iv.start == 0 or iv.end == rep.length)
|
|
227
|
+
aa = _opt_str(f, "aa", locus) or ""
|
|
228
|
+
start_type = f.get("start_type")
|
|
229
|
+
# Pyrodigal partial start ("Edge") is not a start codon (M3-01 Q3).
|
|
230
|
+
start_codon = None if start_type == "Edge" else start_type or (nt[:3] if nt else None)
|
|
231
|
+
return Gene(
|
|
232
|
+
locus_tag=locus,
|
|
233
|
+
feature_type=ftype,
|
|
234
|
+
interval=iv,
|
|
235
|
+
aa=aa[:-1] if aa.endswith("*") else aa,
|
|
236
|
+
nt=nt,
|
|
237
|
+
start_codon=start_codon,
|
|
238
|
+
gene=f.get("gene"),
|
|
239
|
+
product=product,
|
|
240
|
+
partial=bool(f.get("truncated")) or at_end,
|
|
241
|
+
hypothetical=hypothetical,
|
|
242
|
+
db_xrefs=tuple(str(x) for x in xrefs),
|
|
243
|
+
expert=_expert(f, locus),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _nc(f: Mapping[str, Any], replicons: Mapping[str, Replicon]) -> NcFeature:
|
|
248
|
+
fid = str(f.get("id") or f.get("locus") or "")
|
|
249
|
+
anticodon = f.get("anti_codon")
|
|
250
|
+
return NcFeature(
|
|
251
|
+
id=fid,
|
|
252
|
+
type=_NC_TYPES[str(f["type"]).lower()],
|
|
253
|
+
interval=_interval(f, fid, replicons, coding=False),
|
|
254
|
+
gene=f.get("gene"),
|
|
255
|
+
product=f.get("product") or "",
|
|
256
|
+
amino_acid=f.get("amino_acid"),
|
|
257
|
+
anticodon=str(anticodon).upper().replace("U", "T") if anticodon else None,
|
|
258
|
+
nt=_opt_str(f, "nt", fid),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _versions(data: Mapping[str, Any]) -> tuple[str, str, DbType]:
|
|
263
|
+
version = data.get("version")
|
|
264
|
+
if not isinstance(version, dict):
|
|
265
|
+
return "", "", "unknown"
|
|
266
|
+
raw_db = version.get("db")
|
|
267
|
+
db: dict[str, Any] = raw_db if isinstance(raw_db, dict) else {}
|
|
268
|
+
db_type = str(db.get("type", "")).lower()
|
|
269
|
+
return (
|
|
270
|
+
str(version.get("bakta") or ""),
|
|
271
|
+
str(db.get("version") or ""),
|
|
272
|
+
cast(DbType, db_type if db_type in ("full", "light") else "unknown"),
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def parse_bakta_json(
|
|
277
|
+
path: StrPath,
|
|
278
|
+
replicons: Mapping[str, Replicon] | None = None,
|
|
279
|
+
genome: Mapping[str, str] | None = None,
|
|
280
|
+
*,
|
|
281
|
+
translation_table: int | None = None,
|
|
282
|
+
) -> BaktaAnnotation:
|
|
283
|
+
"""Parse a (possibly gzipped) Bakta JSON; ``replicons`` (preflight's table) wins if given.
|
|
284
|
+
|
|
285
|
+
With ``translation_table``, a recorded ``genome.translation_table`` that differs raises
|
|
286
|
+
``AnnotationTableMismatch``; an unrecorded one is assumed equal (logged at INFO).
|
|
287
|
+
"""
|
|
288
|
+
data = read_json(path)
|
|
289
|
+
if not isinstance(data, dict):
|
|
290
|
+
raise BaktaParseError(f"{path}: top-level JSON must be an object")
|
|
291
|
+
if translation_table is not None:
|
|
292
|
+
_check_table(data, translation_table)
|
|
293
|
+
records = data.get("sequences", data.get("contigs")) or []
|
|
294
|
+
reps, seqs = _replicons_from_json(records, replicons, genome)
|
|
295
|
+
genes: list[Gene] = []
|
|
296
|
+
features: list[NcFeature] = []
|
|
297
|
+
skipped: Counter[str] = Counter()
|
|
298
|
+
seen: set[str] = set()
|
|
299
|
+
for f in data.get("features") or []:
|
|
300
|
+
if not isinstance(f, dict):
|
|
301
|
+
raise BaktaParseError(f"feature must be an object: {f!r:.200}")
|
|
302
|
+
t = str(f.get("type", "")).lower()
|
|
303
|
+
if t in _GENE_TYPES:
|
|
304
|
+
g = _gene(f, reps, seqs)
|
|
305
|
+
if g.locus_tag in seen:
|
|
306
|
+
raise BaktaParseError(f"duplicate locus tag {g.locus_tag}")
|
|
307
|
+
seen.add(g.locus_tag)
|
|
308
|
+
genes.append(g)
|
|
309
|
+
elif t in _NC_TYPES:
|
|
310
|
+
features.append(_nc(f, reps))
|
|
311
|
+
else:
|
|
312
|
+
skipped[str(f.get("type", ""))] += 1
|
|
313
|
+
order = {c: k for k, c in enumerate(reps)}
|
|
314
|
+
genes.sort(key=lambda g: interval_sort_key(g.interval, order, g.locus_tag))
|
|
315
|
+
features.sort(key=lambda x: interval_sort_key(x.interval, order, x.id))
|
|
316
|
+
bakta_version, db_version, db_type = _versions(data)
|
|
317
|
+
return BaktaAnnotation(
|
|
318
|
+
model=GeneModel(replicons=reps, genes=tuple(genes), features=tuple(features)),
|
|
319
|
+
sequences=MappingProxyType(seqs),
|
|
320
|
+
bakta_version=bakta_version,
|
|
321
|
+
db_version=db_version,
|
|
322
|
+
db_type=db_type,
|
|
323
|
+
skipped_types=MappingProxyType(dict(skipped)),
|
|
324
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Bakta 1.12.2 command line and database selection (GIPPA_SPEC.md §7.1.1, §5.2; M2-03).
|
|
2
|
+
|
|
3
|
+
Builds ``bakta --db DB --output OUT --prefix P --threads N --keep-contig-headers
|
|
4
|
+
[--complete] [--translation-table T] --replicons replicons.tsv [--regions regions.gff3]
|
|
5
|
+
[extra args] genome.norm.fa``.
|
|
6
|
+
Bakta itself is never imported (R5); it runs as a separate process.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
DbType = Literal["full", "light"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def bakta_db_path(db_setting: str, db_dir: Path) -> tuple[Path, DbType]:
|
|
19
|
+
"""Resolve ``annotation.bakta.db``: ``auto`` picks ``bakta/db`` if present, else ``db-light``.
|
|
20
|
+
|
|
21
|
+
``full``/``light`` select the matching directory under ``db_dir/bakta``; anything else is a
|
|
22
|
+
path to a Bakta DB, typed ``light`` when its name contains ``light``.
|
|
23
|
+
"""
|
|
24
|
+
base = db_dir / "bakta"
|
|
25
|
+
if db_setting == "auto":
|
|
26
|
+
return (base / "db", "full") if (base / "db").is_dir() else (base / "db-light", "light")
|
|
27
|
+
if db_setting == "full":
|
|
28
|
+
return base / "db", "full"
|
|
29
|
+
if db_setting == "light":
|
|
30
|
+
return base / "db-light", "light"
|
|
31
|
+
path = Path(db_setting)
|
|
32
|
+
return path, "light" if "light" in path.name else "full"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def bakta_command(
|
|
36
|
+
*,
|
|
37
|
+
genome: Path,
|
|
38
|
+
outdir: Path,
|
|
39
|
+
prefix: str,
|
|
40
|
+
db: Path,
|
|
41
|
+
threads: int,
|
|
42
|
+
all_circular: bool,
|
|
43
|
+
complete: bool | Literal["auto"],
|
|
44
|
+
replicons_tsv: Path,
|
|
45
|
+
regions_gff3: Path | None,
|
|
46
|
+
extra_args: Sequence[str],
|
|
47
|
+
translation_table: int,
|
|
48
|
+
) -> list[str]:
|
|
49
|
+
"""Bakta argv; ``--replicons`` always, ``--complete`` iff forced or (auto) all circular.
|
|
50
|
+
|
|
51
|
+
``--translation-table T`` only when ``T != 11`` (Bakta's default; M11-16 D2), right before
|
|
52
|
+
``--replicons``.
|
|
53
|
+
"""
|
|
54
|
+
cmd = ["bakta", "--db", str(db), "--output", str(outdir), "--prefix", prefix]
|
|
55
|
+
cmd += ["--threads", str(threads), "--keep-contig-headers"]
|
|
56
|
+
use_complete = all_circular if complete == "auto" else bool(complete)
|
|
57
|
+
cmd += ["--complete"] if use_complete else []
|
|
58
|
+
cmd += [] if translation_table == 11 else ["--translation-table", str(translation_table)]
|
|
59
|
+
cmd += ["--replicons", str(replicons_tsv)]
|
|
60
|
+
if regions_gff3 is not None:
|
|
61
|
+
cmd += ["--regions", str(regions_gff3)]
|
|
62
|
+
cmd += [str(a) for a in extra_args]
|
|
63
|
+
cmd.append(str(genome))
|
|
64
|
+
return cmd
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Pure layers derived from a parsed Bakta annotation (GIPPA_SPEC.md §7.1.2, §9.3; M2-04).
|
|
2
|
+
|
|
3
|
+
Ribosomal-protein set (CAI reference), 16S rRNA selection and anti-Shine-Dalgarno 9-mer, tRNA
|
|
4
|
+
copy numbers per anticodon (tAI), db_xref splitting, ``annotation_source`` and origin-aware
|
|
5
|
+
nearest genes (TE6). No file I/O; the ``parse_bakta`` step in ``gippa.steps.annotation`` writes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from collections import Counter
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
from gippa.model.coords import Interval, Replicon
|
|
16
|
+
from gippa.model.genes import Gene, NcFeature
|
|
17
|
+
|
|
18
|
+
RIBOSOMAL_PRODUCT: re.Pattern[str] = re.compile(r"^(30S|50S) ribosomal protein")
|
|
19
|
+
RIBOSOMAL_GENE: re.Pattern[str] = re.compile(r"^(rps|rpl|rpm)[A-Z]")
|
|
20
|
+
DEFAULT_ANTI_SD = "ACCTCCTTA"
|
|
21
|
+
ANTI_SD_DEFAULT_FLAG = "anti_sd_default"
|
|
22
|
+
|
|
23
|
+
_DNA9 = re.compile(r"^[ACGT]{9}$")
|
|
24
|
+
|
|
25
|
+
AnnotationSource = Literal["bakta_full", "bakta_light", "user"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_ribosomal_protein(g: Gene) -> bool:
|
|
29
|
+
"""§7.1.2: product ``^(30S|50S) ribosomal protein`` or gene ``^(rps|rpl|rpm)[A-Z]``."""
|
|
30
|
+
return bool(RIBOSOMAL_PRODUCT.match(g.product or "")) or bool(
|
|
31
|
+
g.gene and RIBOSOMAL_GENE.match(g.gene)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def rrna16s(features: Sequence[NcFeature]) -> list[NcFeature]:
|
|
36
|
+
"""The 16S rRNA copies (product contains ``16S`` or gene ``rrs``), in input order."""
|
|
37
|
+
return [f for f in features if f.type == "rrna" and ("16S" in f.product or f.gene == "rrs")]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def anti_sd(rrna16s_nts: Sequence[str], configured: str = "auto") -> tuple[str, tuple[str, ...]]:
|
|
41
|
+
"""Anti-SD 9-mer and run flags (§7.1.2, §5.2).
|
|
42
|
+
|
|
43
|
+
``configured`` other than ``auto`` must match ``^[ACGT]{9}$`` and is used as is. Otherwise
|
|
44
|
+
the most common 3' 9-mer across 16S copies (ties: first copy in input order; copies shorter
|
|
45
|
+
than 9 nt are skipped) is used if it contains ``CCTCC``, else the E. coli default and the
|
|
46
|
+
``anti_sd_default`` flag.
|
|
47
|
+
"""
|
|
48
|
+
if configured != "auto":
|
|
49
|
+
if not _DNA9.match(configured):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"annotation.ostir.anti_sd must be 'auto' or 9 nt ACGT: {configured!r}"
|
|
52
|
+
)
|
|
53
|
+
return configured, ()
|
|
54
|
+
tails = [s[-9:].upper() for s in rrna16s_nts if len(s) >= 9]
|
|
55
|
+
if tails:
|
|
56
|
+
counts = Counter(tails) # insertion-ordered: most_common keeps first-seen order on ties
|
|
57
|
+
best = counts.most_common(1)[0][0]
|
|
58
|
+
if "CCTCC" in best:
|
|
59
|
+
return best, ()
|
|
60
|
+
return DEFAULT_ANTI_SD, (ANTI_SD_DEFAULT_FLAG,)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def trna_counts(features: Sequence[NcFeature]) -> dict[str, int]:
|
|
64
|
+
"""tRNA gene copies per anticodon (§7.1.2), keys sorted."""
|
|
65
|
+
counts = Counter(f.anticodon for f in features if f.type == "trna" and f.anticodon)
|
|
66
|
+
return {k: counts[k] for k in sorted(counts)}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def split_xrefs(genes: Sequence[Gene]) -> list[tuple[str, str, str]]:
|
|
70
|
+
"""``(locus_tag, db, id)`` per db_xref; db is text before the first ``:``; GO keeps it."""
|
|
71
|
+
rows: list[tuple[str, str, str]] = []
|
|
72
|
+
for g in genes:
|
|
73
|
+
for x in g.db_xrefs:
|
|
74
|
+
db, _, ident = x.partition(":")
|
|
75
|
+
rows.append((g.locus_tag, db, x if db == "GO" else ident))
|
|
76
|
+
return rows
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def annotation_source(db_type: str) -> AnnotationSource:
|
|
80
|
+
"""§9.3 enum from the Bakta DB type (M2 lead decision 7): ``user`` when unknown."""
|
|
81
|
+
if db_type == "full":
|
|
82
|
+
return "bakta_full"
|
|
83
|
+
if db_type == "light":
|
|
84
|
+
return "bakta_light"
|
|
85
|
+
return "user"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _gap(a: Interval, b: Interval, rep: Replicon) -> int:
|
|
89
|
+
"""Gap in bp between two intervals (0 when overlapping or adjacent); wraps if circular."""
|
|
90
|
+
if rep.topology != "circular":
|
|
91
|
+
return max(0, b.start - a.end, a.start - b.end)
|
|
92
|
+
n = rep.length
|
|
93
|
+
la, lb = a.end - a.start, b.end - b.start
|
|
94
|
+
if (b.start - a.start) % n < la or (a.start - b.start) % n < lb:
|
|
95
|
+
return 0
|
|
96
|
+
return min((b.start - a.end) % n, (a.start - b.end) % n)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def nearest_genes(
|
|
100
|
+
feature: NcFeature, genes: Sequence[Gene], replicon: Replicon, n: int = 2
|
|
101
|
+
) -> list[tuple[str, int]]:
|
|
102
|
+
"""The ``n`` genes on ``feature``'s contig closest to it: ``(locus_tag, gap_bp)`` ascending.
|
|
103
|
+
|
|
104
|
+
Ties keep the input order. Circular replicons are origin-aware; linear contigs never wrap.
|
|
105
|
+
"""
|
|
106
|
+
on = [g for g in genes if g.interval.contig == feature.interval.contig]
|
|
107
|
+
dist = [(g.locus_tag, _gap(feature.interval, g.interval, replicon)) for g in on]
|
|
108
|
+
return sorted(dist, key=lambda x: x[1])[:n]
|