Commit 942645fa authored by Mikaël Salson's avatar Mikaël Salson
Browse files

Merge branch 'feature-c/2056-display-synonymous-mutations' into 'dev'

Feature c/2056 display synonymous mutations

Closes #2056

See merge request !232
parents e4858e93 25f4b24a
Pipeline #41226 failed with stages
in 3 minutes and 43 seconds
......@@ -660,13 +660,19 @@ line {
word-spacing: normal;
}
span .substitution,
span .indel {
span .indel,
span .insertion,
span .deletion {
font-weight: bold;
color: #800;
}
span .substitution {
border-bottom: 1px solid #800;
}
span .silent {
border-bottom: 3px double #008;
color: #008;
}
/*tag*/
.tagSelector {
padding: 5px;
......
......@@ -660,13 +660,19 @@ line {
word-spacing: normal;
}
span .substitution,
span .indel {
span .indel,
span .insertion,
span .deletion {
font-weight: bold;
color: #800;
}
span .substitution {
border-bottom: 1px solid #800;
}
span .silent {
border-bottom: 3px double #008;
color: #008;
}
/*tag*/
.tagSelector {
padding: 5px;
......
......@@ -660,13 +660,19 @@ line {
word-spacing: normal;
}
span .substitution,
span .indel {
span .indel,
span .insertion,
span .deletion {
font-weight: bold;
color: #800;
}
span .substitution {
border-bottom: 1px solid #800;
}
span .silent {
border-bottom: 3px double #008;
color: #008;
}
/*tag*/
.tagSelector {
padding: 5px;
......
......@@ -659,13 +659,19 @@ line {
word-spacing: normal;
}
span .substitution,
span .indel {
span .silent,
span .indel,
span .insertion,
span .deletion {
font-weight: bold;
color: #800;
}
span .substitution {
border-bottom: 1px solid #800;
}
span .silent {
border: 1px solid #800;
}
/*tag*/
.tagSelector {
padding: 5px;
......
......@@ -660,13 +660,19 @@ line {
word-spacing: normal;
}
span .substitution,
span .indel {
span .indel,
span .insertion,
span .deletion {
font-weight: bold;
color: #800;
}
span .substitution {
border-bottom: 1px solid #800;
}
span .silent {
border-bottom: 3px double #008;
color: #008;
}
/*tag*/
.tagSelector {
padding: 5px;
......
......@@ -808,13 +808,17 @@ line {
width: 0px;
word-spacing: normal;
}
span .substitution, span .indel {
span .substitution, span .indel, span .insertion, span .deletion {
font-weight: bold;
color: #800;
}
span .substitution {
border-bottom: 1px solid #800;
}
span .silent {
border-bottom: 3px double #008;
color: #008;
}
/*tag*/
......
......@@ -1230,15 +1230,15 @@ genSeq.prototype= {
/**
* compare sequence with another string and surround change
* TODO !
* @param {char} self
* @param {char} other
* @return {string}
* */
spanify_mutation: function (self, other) {
if (this.segmenter.aligned && self != other) {
spanify_mutation: function (self, other, mutation) {
if (mutation != undefined) {
var span = document.createElement('span');
span.className = (self == '-' || other == '-' ? 'indel' : 'substitution');
span.className = mutation
span.setAttribute('other', other + '-' + this.segmenter.first_clone);
span.appendChild(document.createTextNode(self));
return span;
......@@ -1280,6 +1280,25 @@ genSeq.prototype= {
result = document.createElement('span');
currentSpan = document.createElement('span');
var isProductive = (! this.segmenter.amino &&
this.m.clones.hasOwnProperty(this.segmenter.first_clone) &&
this.m.clones[this.segmenter.first_clone].getProductivityName() == "productive"); // TODO : isProductive method
var reference_phase = (isProductive) ? (this.m.clones[this.segmenter.first_clone].seg.junction.start - 1) % 3 : undefined;
var mutations = {};
var ref = '';
var seq = '';
if (this.segmenter.amino) {
seq = this.seqAA;
ref = this.segmenter.sequence[this.segmenter.first_clone].seqAA;
} else {
seq = this.seq;
ref = this.segmenter.sequence[this.segmenter.first_clone].seq;
}
if (this.segmenter.aligned) {
mutations = get_mutations(ref, seq, reference_phase);
}
for (var i = 0; i < this.seq.length; i++) {
for (var m in highlights){
var h = highlights[m];
......@@ -1316,12 +1335,7 @@ genSeq.prototype= {
}
}
// one character
if (this.segmenter.amino){
currentSpan.appendChild(this.spanify_mutation(this.seqAA[l], this.segmenter.sequence[this.segmenter.first_clone].seqAA[l]));
}else{
currentSpan.appendChild(this.spanify_mutation(this.seq[i], this.segmenter.sequence[this.segmenter.first_clone].seq[i]))
}
currentSpan.appendChild(this.spanify_mutation(seq[i], ref[i], mutations.hasOwnProperty(i) ? mutations[i] : undefined))
}
result.appendChild(currentSpan);
var marge = ""
......
var SILENT="silent";
var SUBST="substitution";
var INS="insertion";
var DEL="deletion";
/**
* Get codons from two aligned sequences
* @pre both sequences are aligned together ref.length == seq.length
* @param ref: reference the sequence
* @param seq: the sequence aligned to ref
* @param frame: the frame in the reference sequence
* 0: first codon starts at first position, etc.
* @return an object containing a property ref and a property seq whose values
* are a list of codons. Some “codons” from seq may have a length ≠ 3.
* All codons from ref have 0 or 3 nucleotides (apart from the first/last codons), but may
* contain dashes.
* The total number of characters in both lists are equal and are also
* equal to the number of characters in the original sequences.
*/
function get_codons(ref, seq, frame) {
var codons_ref = [];
var codons_seq = [];
var current_codon_ref = '';
var current_codon_seq = '';
var pos = 0;
if (frame == undefined) {
return {ref: [ref], seq: [seq]};
}
// Search first nucleotide pos
for (pos; pos < ref.length; pos++) {
if (ref[pos] != '-') {
if (frame == 0)
break;
frame--;
}
current_codon_seq += seq[pos];
current_codon_ref += ref[pos];
}
if (current_codon_seq != '' || current_codon_ref != '') {
codons_ref.push(current_codon_ref);
codons_seq.push(current_codon_seq);
current_codon_ref = '';
current_codon_seq = '';
}
var nb_nuc = 0;
for (; pos < ref.length; pos++) {
if (nb_nuc == 3 ||
(ref[pos] != '-' && current_codon_seq.length > 0 &&
nb_nuc == 0)) {
codons_ref.push(current_codon_ref);
codons_seq.push(current_codon_seq);
current_codon_ref = '';
current_codon_seq = '';
nb_nuc = 0;
}
if (ref[pos] == '-') {
current_codon_seq += seq[pos];
current_codon_ref += '-';
} else {
current_codon_ref += ref[pos];
current_codon_seq += seq[pos];
nb_nuc ++;
}
}
if (current_codon_ref.length > 0)
codons_ref.push(current_codon_ref);
if (current_codon_seq.length > 0)
codons_seq.push(current_codon_seq);
return {ref : codons_ref, seq : codons_seq};
}
/**
* Get positions of mutations and their type between two aligned sequences
* @pre both sequences are aligned together
* @param ref: reference the sequence
* @param seq: the sequence aligned to ref
* @param frame: the frame in the reference sequence
* 0: first codon starts at first position, etc.
* @return a dictionary whose keys are positions of mutations in the alignment
* and whose values are a type of mutation either SUBST/SILENT/INS/DEL
*/
function get_mutations(ref, seq, frame) {
var codons = get_codons(ref, seq, frame);
var mutations = {};
var nb_pos = 0;
console.log(codons);
for (var i = 0; i < codons.ref.length ; i++) {
for (var p = 0; p < codons.ref[i].length; p++) {
if (codons.ref[i][p] != codons.seq[i][p]) {
if (codons.ref[i][p] == '-') {
mutations[nb_pos] = INS;
} else if (codons.seq[i][p] == '-') {
mutations[nb_pos] = DEL;
} else {
var codon1 = codons.ref[i];
var codon2 = codons.seq[i];
if (codon1.length > 3 && codon1.length == codon2.length) {
codon1 = '';
codon2 = '';
// We may have common gaps (due to alignment with
// other sequences) that need to be ignored
for (var j = 0; j < codons.ref[i].length; j++) {
if (codons.ref[i][j] != codons.seq[i][j] ||
codons.ref[i][j] != '-') {
codon1 += codons.ref[i][j];
codon2 += codons.seq[i][j];
}
}
}
if (codon1.length == 3 &&
codon2.length == 3 &&
frame != undefined &&
tableAA.hasOwnProperty(codon2) &&
tableAA[codon1] == tableAA[codon2]) {
mutations[nb_pos] = SILENT;
} else {
mutations[nb_pos] = SUBST;
}
}
}
nb_pos++;
}
}
return mutations;
}
/**
* Find the position of the nth occurence of needle
*
......
QUnit.module("Tools", {
});
QUnit.test("test get_codons", function(assert) {
var r = 'ATGATAGAC';
var s = 'AAACCCGGG';
var codons = get_codons(r, s, 0);
assert.deepEqual(codons, {ref : ['ATG', 'ATA', 'GAC'], seq : ['AAA', 'CCC', 'GGG']});
codons = get_codons(r, s, 1);
assert.deepEqual(codons, {ref : ['A', 'TGA', 'TAG', 'AC'], seq : ['A', 'AAC', 'CCG', 'GG']});
codons = get_codons(r, s, 2);
assert.deepEqual(codons, {ref : ['AT', 'GAT', 'AGA', 'C'], seq : ['AA', 'ACC', 'CGG', 'G']});
r = 'ATG--ATAGACAG';
s = 'AAACCCG-GGGTT';
codons = get_codons(r, s, 0);
assert.deepEqual(codons, {ref : ['ATG', '--', 'ATA', 'GAC', 'AG'],
seq : ['AAA', 'CC', 'CG-', 'GGG', 'TT']});
codons = get_codons(r, s, 1);
assert.deepEqual(codons, {ref : ['A', 'TG--A', 'TAG', 'ACA', 'G'],
seq : ['A', 'AACCC', 'G-G', 'GGT', 'T']});
codons = get_codons(r, s, 2);
assert.deepEqual(codons, {ref : ['AT', 'G--AT', 'AGA', 'CAG'],
seq : ['AA', 'ACCCG', '-GG', 'GTT']});
r = '-----ATG--ATAGA--CAG';
s = '--GACAAACCCG-GG--GTT';
codons = get_codons(r, s, 0);
assert.deepEqual(codons, {ref : ['-----', 'ATG', '--', 'ATA', 'GA--C', 'AG'],
seq : ['--GAC', 'AAA', 'CC', 'CG-', 'GG--G', 'TT']});
codons = get_codons(r, s, 1);
assert.deepEqual(codons, {ref : ['-----A', 'TG--A', 'TAG', 'A--CA', 'G'],
seq : ['--GACA', 'AACCC', 'G-G', 'G--GT', 'T']});
codons = get_codons(r, s, 2);
assert.deepEqual(codons, {ref : ['-----AT', 'G--AT', 'AGA', '--', 'CAG'],
seq : ['--GACAA', 'ACCCG', '-GG', '--', 'GTT']});
});
QUnit.test("test get_mutations", function(assert) {
var r = 'ATAGATAGATAG';
var mutations = get_mutations(r, r, 0);
assert.equal (Object.keys(mutations).length, 0, "No mutation");
mutations = get_mutations(r, r, 1);
assert.equal (Object.keys(mutations).length, 0, "No mutation");
mutations = get_mutations(r, r, 2);
assert.equal (Object.keys(mutations).length, 0, "No mutation");
// GAT > GAc (give same D AA)
var s = 'ATAGACAGATAG';
// 0123456789
mutations = get_mutations(r, s, 0);
assert.equal (Object.keys(mutations).length, 1, "Single mutation");
assert.equal (mutations[5], SILENT, "Silent mutation");
// ATA > AcA (I > T)
mutations = get_mutations(r, s, 1);
assert.equal (Object.keys(mutations).length, 1, "Single mutation");
assert.equal (mutations[5], SUBST, "Silent mutation");
// TAG > cAG (* > Q)
mutations = get_mutations(r, s, 2);
assert.equal (Object.keys(mutations).length, 1, "Single mutation");
assert.equal (mutations[5], SUBST, "Silent mutation");
r = 'ATAGATAG-TAG';
s = 'ATA-ATCGATAG';
// 0123456789
// AG-T > cGAT (no base in the reference → can't tell if the mutation is silent)
mutations = get_mutations(r, s, 0);
assert.equal(Object.keys(mutations).length, 3, "Three mutations");
assert.deepEqual(mutations, {3 : DEL, 6 : SUBST, 8 : INS});
// ATA > ATc (I)
mutations = get_mutations(r, s, 1);
assert.equal(Object.keys(mutations).length, 3, "Three mutations");
assert.deepEqual(mutations, {3 : DEL, 6 : SILENT, 8 : INS});
// TAG > TcG (* > S)
mutations = get_mutations(r, s, 2);
assert.equal(Object.keys(mutations).length, 3, "Three mutations");
assert.deepEqual(mutations, {3 : DEL, 6 : SUBST, 8 : INS});
// Same example as before but with common indels to check that they are ignored
r = 'ATAGAT-AG-TA--G';
s = 'ATA-AT-CGATA--G';
// 0123456789
// AG-T > cGAT
mutations = get_mutations(r, s, 0);
assert.equal(Object.keys(mutations).length, 3, "Three mutations");
assert.deepEqual(mutations, {3 : DEL, 7 : SUBST, 9 : INS});
// ATA > ATC (I)
mutations = get_mutations(r, s, 1);
assert.equal(Object.keys(mutations).length, 3, "Three mutations");
assert.deepEqual(mutations, {3 : DEL, 7 : SILENT, 9 : INS});
// TAG > TcG (* > S)
mutations = get_mutations(r, s, 2);
assert.equal(Object.keys(mutations).length, 3, "Three mutations");
assert.deepEqual(mutations, {3 : DEL, 7 : SUBST, 9 : INS});
mutations = get_mutations(r, s);
assert.equal(Object.keys(mutations).length, 3, "Three mutations without phase");
assert.deepEqual(mutations, {3 : DEL, 7 : SUBST, 9 : INS});
});
QUnit.test("test nth_ocurrence", function(assert) {
var str = "needle needle needle needle";
var m = nth_ocurrence(str, 'n', 3);
......
......@@ -164,7 +164,7 @@ or their “N length” (that is N1-D-N2 in the case of VDJ recombinations).
The sequence view displays nucleotide sequences from selected clones.
- See "[What is the sequence displayed for each clone ?](#what-is-the-sequence-displayed-for-each-clone)" below
- Sequences can be aligned together (“align” button), identifying substitutions, insertions and deletions.
- Sequences can be aligned together (“align” button), identifying substitutions, insertions and deletions. Silent mutations are identified, as soon as a CDR3 is detected, and represented with a double border in blue.
- You can remove sequences from the aligner (and the selection) by clicking on the “X” at the left.
- You can further analyze the sequences with IMGT/V-QUEST, IgBlast or Blast. This opens another window/tab.
- You can unselect all sequences by clicking on the background of the grid.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment