Repository navigation
Give every variant its H37Rv coordinate and numbering, on any reference - #9
Merged
Merged
Conversation
The H37Rv coordinate of each variant was lifted once, from the first reference's sequence, for every reference of the run, and the map was applied by position alone. On the cohort mapped against two assemblies, 15,653 of the second reference's 16,296 variant sites (97%) got a wrong coordinate. LIFT_VARIANTS now runs once per reference, from its own sequence and for the positions on its own contigs, and the report looks each variant up by contig and position: 97% of the second reference's sites lift to the coordinate minimap2 gives them, and the rest drop. The liftover read only the first contig of each FASTA, so on a draft assembly a variant of any other contig was lifted with the first contig's sequence: cut into ten contigs, one of the cohort's references had half its positions misplaced. Every contig is now read and chained on its own, in whichever orientation it lies in the target, and the map names both contigs and the strand. The draft lifts as well as the complete assembly, a single-contig reference gets the same map as before, and the blind-spot mask reaches every contig. The canonical annotation ran the mapping-reference VCF through the H37Rv snpEff database in place: another reference's position was read as an H37Rv one, and since the database calls H37Rv's chromosome 'Chromosome' and a mapping reference is called something else, nothing was annotated. The unlifted position was then shown as the H37Rv coordinate, over the lift. lift_vcf.py now moves each SNP to its H37Rv position first, with H37Rv's base as REF and the allele complemented where the reference lies reversed, under the database's name for the chromosome (--canonical_chrom), and keeps the mapping coordinate in OPOS. A record lends only the coordinate it was lifted to, and the report keeps H37Rv's gene name beside its amino acid.
The SNP-dynamics panel compared a variant's HGVS change (p.Ile66Met) with the resistance catalogue's short form (I66M), so no trajectory was ever tagged with its catalogue entry, on any reference. Both are now read in one form, a stop also as the catalogue's '!', and a stop or frameshift matches the gene's LoF entry. The H37Rv gene and numbering are tried first, since the catalogue is written in them and a reference of another lineage names and numbers its genes its own way.
The lift interpolated only across an exactly colinear gap of at most ~2k between two shared unique k-mers, so a position beside an indel, or in a stretch without a shared unique k-mer, was dropped: 4 to 5% of the variant sites of the cohort's two references. With --align-gaps, which LIFT_VARIANTS now passes, the stretch between the two anchors is aligned instead, and a position is placed when a gap-free run of the alignment joins it to one of the anchors and its own context agrees. A position inside a stretch H37Rv lacks is written as such, and the report shows it as not in H37Rv. Checked against minimap2's alignment of each reference to H37Rv, every position of 27 resistance genes lifts to minimap2's coordinate, and so do 99.8% of the variant sites. None of those that differ fits H37Rv worse: they are ties in identical repeat units, bases at the junction of a structural difference, or places where minimap2 aligned another copy. About 0.2% are left without a coordinate, in PE_PGRS-type repeats where a position could sit on either side of an indel.
5 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed and why
On a cohort mapped against references other than H37Rv, the H37Rv coordinate and numbering the pipeline gives each variant were wrong or missing. On the 185-sample cohort mapped against two assemblies (E1ASM0035, L7; E1ASM0057, A4), the report showed a wrong H37Rv coordinate for 15,653 of E1ASM0057's 16,296 variant sites (97%). Five faults:
LIFT_VARIANTSnow runs once per reference, each from its own sequence and for the positions on its own contigs, and the report looks each variant up by contig and position.--annotate_canonical, on in the TB profile). Each VCF was annotated in place against the H37Rv snpEff database: another reference's position was read as an H37Rv one, and since the database calls the chromosomeChromosome, nothing was annotated. The unlifted position was then shown as the H37Rv coordinate, over the lift.bin/lift_vcf.pynow moves each SNP to its H37Rv position first (H37Rv's base as REF, the allele complemented where the reference lies reversed, the mapping coordinate kept inINFO/OPOS), under the database's chromosome name (--canonical_chrom).p.Ile66Met) was compared with the catalogue's short form (I66M), so no trajectory was ever tagged with its catalogue entry. Both are now read in one form, in H37Rv's gene and numbering first, and a stop or frameshift matches the gene'sLoFentry.--align-gapsthe stretch between the two anchors is aligned instead; a position inside a stretch H37Rv lacks is reported as such and shown as not in H37Rv.Checked against minimap2's alignment of each reference to H37Rv:
None of the placements that differ fits H37Rv worse than minimap2's coordinate: they are ties in identical repeat units, bases at the junction of a structural difference, or places where minimap2 aligned another copy. About 0.2% are left without a coordinate, in PE_PGRS-type repeats where a position could sit on either side of an indel.
Outputs and defaults
LIFT_VARIANTSruns once per reference instead of once per run: about 25 s and 2.5 GB for a 4.4 Mb reference, on 1 CPU (was 4).--annotate_canonicalnow runs the liftover as well, since it annotates each SNP at its lifted position.*.canonical.ann.vcf.gzholds SNPs at their H37Rv position (CHROMChromosome), each with its mapping coordinate inINFO/OPOS. A SNP whose allele is H37Rv's own base is left out, since in H37Rv numbering nothing changed.--canonical_chrom(defaultChromosome, the name the bundled H37Rv snpEff database uses).pathotypr_liftover.py lift --global-chainwrites a contig-aware map (src_contig src_pos tgt_contig tgt_pos strand,.as the target of a position H37Rv lacks), and has new options--align-gaps,--align-min-identityand--max-align-gap.--contigand--source-contigdefault to the FASTA's own names. A single-contig reference gets the same map as before.How it was verified
The coordinates were checked against minimap2 on the two real references as in the table, and rpoB S450L, katG S315T and atpE I66M written in A4 coordinates come out of
lift_vcf.pyat 761155, 2155168 and 1461242 with H37Rv's base.Not run here: Nextflow, so the new wiring is left to CI's stub run, and snpEff, so the annotation of the lifted records against the H37Rv database needs a run on the cluster.
Checklist
tests/run_tests.shpasses locally (unit, js and lint; the pipeline leg needs Nextflow, which is not installed here)ruff check .is clean, and I did not restyle code the change does not touchmodules/has astub:block (not applicable: no new process)tests/data/was not edited by hand (fixtures come frommake_test_data.py)CHANGELOG.mdentry and, where relevant, a docs update