From c3a7788dbd3e5881877c3e5bba5ce19eb0b8959c Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:30:59 -0500 Subject: [PATCH 01/11] feat: Add _cigar(::Variation) function Add a function that can convert the information contained in an Insertion or a Deletion into a CIGAR operator. --- src/Variation.jl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Variation.jl b/src/Variation.jl index 8af22bd..af9f4df 100644 --- a/src/Variation.jl +++ b/src/Variation.jl @@ -120,6 +120,21 @@ function Base.in(v::Variation, var::Haplotype) return any(v.edit == edit for edit in var.edits) end +""" + _cigar(var::Variation{S,T}) where {S,T} + +Returns a CIGAR operation for `var`. Only supports insertions and deletions. + +See also [`_cigar_between`](@ref) +""" +function _cigar(var::Variation{S,T}) where {S,T} + mut = mutation(var) + mut isa Union{Deletion,Insertion} || + throw(ArgumentError("var must be an Insertion or Deletion")) + cigar_letter = mut isa Deletion ? 'D' : 'I' + return string(length(mut), cigar_letter) +end + """ translate(var::Variation{S,T}, aln::PairwiseAlignment{S,S}) where {S,T} From e84f765357f4ff4fed39260d063f366395561af9 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:32:21 -0500 Subject: [PATCH 02/11] feat: Add _cigar_between(::Variation, ::Variation) function Add a function that can calculate the matching bases between two (non-matching) Variations and return a matching (M) CIGAR operation --- src/Variation.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Variation.jl b/src/Variation.jl index af9f4df..851650a 100644 --- a/src/Variation.jl +++ b/src/Variation.jl @@ -135,6 +135,26 @@ function _cigar(var::Variation{S,T}) where {S,T} return string(length(mut), cigar_letter) end +""" + _cigar_between(x::Variation{S,T}, y::Variation{S,T}) where {S,T} + +Returns a CIGAR operation for the (assumed) matching bases between `x` and `y`. + +See also [`_cigar`](@ref) +""" +function _cigar_between(x::Variation{S,T}, y::Variation{S,T}) where {S,T} + x == y && return "" + match_length = leftposition(y) - rightposition(x) + if mutation(y) isa Insertion + match_length -= 1 + end + if mutation(y) isa Deletion + match_length += 1 + end + match_length > 0 || return "" + return "$(match_length)M" +end + """ translate(var::Variation{S,T}, aln::PairwiseAlignment{S,S}) where {S,T} From 719a1239927addf4f66bb7b65fe8a83ebe2f1cd4 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:34:35 -0500 Subject: [PATCH 03/11] feat: Add cigar(::Haplotype) function --- src/Haplotype.jl | 29 +++++++++++++++++++++++++++++ src/SequenceVariation.jl | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Haplotype.jl b/src/Haplotype.jl index 6d1274d..f5cf985 100644 --- a/src/Haplotype.jl +++ b/src/Haplotype.jl @@ -221,6 +221,35 @@ function reconstruct(h::Haplotype) return seq end +""" + cigar(hap::Haplotype{S,T}) where {S,T} + +Constructs a CIGAR string representing the alignment of the sequence of `hap` to its +reference. +""" +function BioAlignments.cigar(hap::Haplotype{S,T}) where {S,T} + cigar_string = String[] + + mismatch_vars = filter(var -> !isa(mutation(var), Substitution), variations(hap)) + + length(mismatch_vars) > 0 || return "$(length(reference(hap)))M" + + lastvar = first(mismatch_vars) + + leftposition(lastvar) > 1 && push!(cigar_string, "$(leftposition(lastvar))M") + + for var in mismatch_vars + push!(cigar_string, _cigar_between(lastvar, var)) + push!(cigar_string, _cigar(var)) + lastvar = var + end #for + + remaining_bases = length(reference(hap)) - rightposition(lastvar) + remaining_bases > 0 && push!(cigar_string, "$(remaining_bases)M") + + return join(cigar_string, "") +end + """ translate(hap::Haplotype{S,T}, aln::PairwiseAlignment{S,S}) where {S,T} diff --git a/src/SequenceVariation.jl b/src/SequenceVariation.jl index 0e8cadd..37b0a42 100644 --- a/src/SequenceVariation.jl +++ b/src/SequenceVariation.jl @@ -20,7 +20,7 @@ TODO now: * Add tests """ -using BioAlignments: BioAlignments, PairwiseAlignment, OP_SOFT_CLIP, sequence +using BioAlignments: BioAlignments, PairwiseAlignment, OP_SOFT_CLIP, cigar, sequence using BioGenerics: BioGenerics, leftposition, rightposition using BioSequences: BioSequences, BioSequence, NucleotideSeq, LongSequence, isgap using BioSymbols: BioSymbol From 95ef4fdc620c102be07505e5b3f5d3d7bc42bb28 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:36:27 -0500 Subject: [PATCH 04/11] test: Add tests for cigar(::Haplotype) --- test/runtests.jl | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 18ba9f1..5a5ca1b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,7 +31,8 @@ using BioAlignments: pairalign, Alignment, AlignedSequence, - PairwiseAlignment + PairwiseAlignment, + cigar using BioSequences: BioSequence, @dna_str, ungap! using BioSymbols: DNA_A using SequenceVariation @@ -127,6 +128,25 @@ end @test Variation(seq2, "A3T") < Variation(seq2, "T4A") end +@testset "CIGAR" begin + reference = dna"TGATGCGTGTAGCAACACTTATAGCG" + reference_genotype = Haplotype( + reference, Variation{typeof(reference),eltype(reference)}[] + ) + genotype = Haplotype( + reference, + [ + Variation(reference, "Δ1-2"), + Variation(reference, "10T"), + Variation(reference, "Δ17-18"), + Variation(reference, "A23C"), + ], + ) + + @test cigar(reference_genotype) == "26M" + @test cigar(genotype) == "2D7M1I7M2D8M" +end + @testset "HaplotypeTranslation" begin ref1 = seq2 ref2 = seq3 From 4817c8016add1e47e88f8eff9782407488d38a90 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:50:24 -0500 Subject: [PATCH 05/11] feat: Add alignment(::Haplotype) function --- src/Haplotype.jl | 11 +++++++++++ src/SequenceVariation.jl | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Haplotype.jl b/src/Haplotype.jl index f5cf985..9164455 100644 --- a/src/Haplotype.jl +++ b/src/Haplotype.jl @@ -250,6 +250,17 @@ function BioAlignments.cigar(hap::Haplotype{S,T}) where {S,T} return join(cigar_string, "") end +""" + alignment(hap::Haplotype) + +Gets a `PairwiseAlignment` of the mutated sequence of `hap` mapped to its refernce sequence +""" +function BioAlignments.alignment(hap::Haplotype) + return PairwiseAlignment( + AlignedSequence(reconstruct(hap), Alignment(cigar(hap))), reference(hap) + ) +end + """ translate(hap::Haplotype{S,T}, aln::PairwiseAlignment{S,S}) where {S,T} diff --git a/src/SequenceVariation.jl b/src/SequenceVariation.jl index 37b0a42..e8e7210 100644 --- a/src/SequenceVariation.jl +++ b/src/SequenceVariation.jl @@ -20,7 +20,15 @@ TODO now: * Add tests """ -using BioAlignments: BioAlignments, PairwiseAlignment, OP_SOFT_CLIP, cigar, sequence +using BioAlignments: + BioAlignments, + Alignment, + AlignedSequence, + PairwiseAlignment, + OP_SOFT_CLIP, + alignment, + cigar, + sequence using BioGenerics: BioGenerics, leftposition, rightposition using BioSequences: BioSequences, BioSequence, NucleotideSeq, LongSequence, isgap using BioSymbols: BioSymbol From db170730f0069df9141f286f1e10a919dc4545e0 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Tue, 4 Apr 2023 12:32:13 -0500 Subject: [PATCH 06/11] test: Add test for alignment(::Haplotype) --- test/runtests.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 5a5ca1b..f9ad9f2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -32,6 +32,7 @@ using BioAlignments: Alignment, AlignedSequence, PairwiseAlignment, + alignment, cigar using BioSequences: BioSequence, @dna_str, ungap! using BioSymbols: DNA_A @@ -147,6 +148,12 @@ end @test cigar(genotype) == "2D7M1I7M2D8M" end +@testset "HaplotypeAlignment" begin + # This test is broken until we get a way to remove sequence info from alignments + # See: https://github.com/BioJulia/BioAlignments.jl/issues/90 + @test_broken alignment(var) == align(seq1, seq2) +end + @testset "HaplotypeTranslation" begin ref1 = seq2 ref2 = seq3 From 2d16c0b7b68d11bb02a40c23debc2c49aa71f2a9 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Sat, 8 Apr 2023 20:40:17 -0500 Subject: [PATCH 07/11] docs: Rename 'Variants' header to 'Haplotypes' --- docs/src/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/api.md b/docs/src/api.md index ef8121e..0836254 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -15,7 +15,7 @@ Deletion Insertion ``` -## Variants +## Haplotypes ```@docs Haplotype From 8458f74053058af5cb389ceb110d537b9794b6a8 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Sat, 8 Apr 2023 20:43:30 -0500 Subject: [PATCH 08/11] docs: Add autodocs for alignment --- docs/src/api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/api.md b/docs/src/api.md index 0836254..f479c15 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -22,6 +22,7 @@ Haplotype reference(::Haplotype) variations reconstruct +BioAlignments.alignment translate(::Haplotype{S,T}, ::PairwiseAlignment{S,S}) where {S,T} ``` From 341faa45e3056fa81085cea46fb3c75ba4e48a67 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Sat, 8 Apr 2023 20:43:50 -0500 Subject: [PATCH 09/11] docs: Add autodocs for cigar --- docs/src/api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/api.md b/docs/src/api.md index f479c15..86277f0 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -23,6 +23,7 @@ reference(::Haplotype) variations reconstruct BioAlignments.alignment +BioAlignment.cigar translate(::Haplotype{S,T}, ::PairwiseAlignment{S,S}) where {S,T} ``` From 6b2bd018e2e6b5cdd1817b886d0fbe868305d1f7 Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Sat, 8 Apr 2023 20:44:21 -0500 Subject: [PATCH 10/11] docs: Add examples of alignment getters --- docs/src/haplotypes.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/src/haplotypes.md b/docs/src/haplotypes.md index 4694b45..b462dba 100644 --- a/docs/src/haplotypes.md +++ b/docs/src/haplotypes.md @@ -39,6 +39,24 @@ human2 == bovine human2 == human ``` +## Alignment reconstruction + +Just like [Sequence reconstruction](@ref), alignments can also be reconstructed +from `Haplotype`s using the extension of the [`BioAlignments.alignment`](@ref) +function. + +```@repl call_variants +human_alignment = alignment(bos_human_haplotype) +human_alignment == bos_human_alignment +``` + +Alternatively, you can get the information in CIGAR format using the +extension of the [`BioAlignments.cigar`](@ref) function. + +```@repl call_variants +cigar(bos_human_haplotype) +``` + ## Reference switching All variations within a haplotype can be mapped to a new reference sequence From 373e485802031d4b7eb29fae8943f575ae36cabb Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Sat, 8 Apr 2023 20:51:09 -0500 Subject: [PATCH 11/11] chore: Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 656a9a3..e7c07f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `alignment` and `cigar` getters for `Haplotype`s ([#39](https://github.com/BioJulia/SequenceVariation.jl/issues/39)/[#42](https://github.com/BioJulia/SequenceVariation.jl/pull/42)) + ## [0.2.2] - 2023-01-28 ### Fixed