MAJ terminée. Nous sommes passés en version 14.6.2 . Pour consulter les "releases notes" associées c'est ici :

https://about.gitlab.com/releases/2022/01/11/security-release-gitlab-14-6-2-released/
https://about.gitlab.com/releases/2022/01/04/gitlab-14-6-1-released/

Commit 10586e8c authored by Mathieu Giraud's avatar Mathieu Giraud
Browse files

merge -- better clustering experience, updated .csv export

Closes #2335 and #2336.
See also #2326 and #2327.

See merge request !13.
parents 2688fa4c c859998a
......@@ -140,14 +140,22 @@
<div class="menu" id="cluster_menu" onmouseover="showSelector('clusterSelector');" > cluster
<div id="clusterSelector" class="selector"><div>
<a class="buttonSelector" onclick="m.restoreClusters();">clones merged by user</a>
<a class="buttonSelector" onclick="m.resetClusters();">reset all user clones</a>
<a class="buttonSelector" onclick="m.clusterBy(function(id){return m.clone(id).getGene(5)});">cluster by V/5' </a>
<a class="buttonSelector" onclick="m.clusterBy(function(id){return m.clone(id).getGene(3)});">cluster by J/3' </a>
<a class="buttonSelector" onclick="m.clusterBy(function(id){return m.clone(id).germline});">cluster by locus </a>
<a class="buttonSelector devel-mode" onclick="m.similarity_builder.DBscan(95,0.001);">cluster by DBscan (experimental) </a>
<a class="buttonSelector" onclick="m.similarity_builder.cluster_me(95,0.1);">cluster by similarity </a>
<div id="clusterby_button"> </div>
<div class="menu_box">
<a class="buttonSelector" onclick="m.restoreClusters();">revert to previous clusters</a>
</div>
<div class="menu_box">
<a class="buttonSelector" onclick="m.merge();">cluster selected clones</a>
<a class="buttonSelector" onclick="m.clusterBy(function(id){return m.clone(id).getGene(5)});">cluster by V/5' </a>
<a class="buttonSelector" onclick="m.clusterBy(function(id){return m.clone(id).getGene(3)});">cluster by J/3' </a>
<a class="buttonSelector" onclick="m.clusterBy(function(id){return m.clone(id).germline});">cluster by locus </a>
<a class="buttonSelector devel-mode" onclick="m.similarity_builder.DBscan(95,0.001);">cluster by DBscan (experimental) </a>
<a class="buttonSelector" onclick="m.similarity_builder.cluster_me(95,0.1);">cluster by similarity </a>
<div id="clusterby_button"> </div>
</div>
<div class="menu_box">
<a class="buttonSelector" onclick="m.break();">break selected clusters</a>
<a class="buttonSelector" onclick="m.resetClusters();">break all clusters</a>
</div>
</div></div>
</div>
......
......@@ -316,8 +316,8 @@ Clone.prototype = {
* @return {string} name
* */
getName: function () {
if (this.m.clusters[this.index].name){
return this.m.clusters[this.index].name;
if (this.getCluster().name){
return this.getCluster().name;
}else if (this.c_name) {
return this.c_name;
} else if (this.name) {
......@@ -382,10 +382,12 @@ Clone.prototype = {
}, //fin changeName,
/**
* @return {integer} cluster number
*/
getCluster: function () {
return this.m.clusters[this.index]
},
/**
* compute the clone size ( ratio of all clones clustered ) at a given time
......@@ -637,7 +639,7 @@ Clone.prototype = {
time = this.m.getTime(time)
var result = 0;
var cluster = this.m.clusters[this.index]
var cluster = this.getCluster()
for (var j = 0; j < cluster.length; j++) {
result += this.m.clone(cluster[j]).reads[time];
}
......@@ -831,7 +833,7 @@ Clone.prototype = {
this.color = "";
}else if (this.m.colorMethod == "abundance") {
var size = this.getSize()
if (this.m.clusters[this.index].length==0){ size = this.getSequenceSize() }
if (this.getCluster().length==0){ size = this.getSequenceSize() }
if (size == 0){
this.color = "";
}else{
......@@ -1016,7 +1018,7 @@ Clone.prototype = {
*
* */
getHtmlInfo: function () {
var isCluster = this.m.clusters[this.index].length
var isCluster = this.getCluster().length
var time_length = this.m.samples.order.length
var html = ""
......@@ -1198,13 +1200,34 @@ Clone.prototype = {
return html
},
toCSV: function () {
var csv = this.getName() + "," + this.id + "," + this.get('germline') + "," + this.getTagName() + ","
+ this.getGene("5") + "," + this.getGene("4") + "," + this.getGene("3") + "," + this.getSequence()
toCSVheader: function (m) {
var csv = [
"cluster", "name", "id",
"system", "tag",
"v", "d", "j",
"productivity",
"sequence"
]
for (var i=0; i<m.samples.order.length; i++) csv.push("reads_"+i)
for (var i=0; i<m.samples.order.length; i++) csv.push(",ratio_"+i)
for (var i=0; i<m.samples.order.length; i++) csv.push(",ratios_"+i)
return csv
},
for (var i=0; i<this.m.samples.order.length; i++) csv += "," + this.getReads(this.m.samples.order[i])
for (var i=0; i<this.m.samples.order.length; i++) csv += "," + this.getSize(this.m.samples.order[i])
for (var i=0; i<this.m.samples.order.length; i++) csv += "," + this.getPrintableSize(this.m.samples.order[i]).replace(/,/g, ';')
toCSV: function () {
var csv = [
this.getCluster().join("+"), this.getName(), this.id,
this.get('germline'), this.getTagName(),
this.getGene("5"), this.getGene("4"), this.getGene("3"),
this.getProductivityName(),
this.getSequence()
]
for (var i=0; i<this.m.samples.order.length; i++) csv.push(this.getReads(this.m.samples.order[i]))
for (var i=0; i<this.m.samples.order.length; i++) csv.push(this.getSize(this.m.samples.order[i]))
for (var i=0; i<this.m.samples.order.length; i++) csv.push(this.getPrintableSize(this.m.samples.order[i]).replace(/,/g, ';'))
return csv
},
......
......@@ -143,6 +143,7 @@ Model.prototype = {
this.orderedSelectedClones=[];
this.clusters = [];
this.clusters_copy = [];
this.clones = [];
this.data = {}; // external data
this.data_info = {};
......@@ -182,7 +183,6 @@ Model.prototype = {
this.nbr = 0;
this.nodes = null;
this.edges = null;
this.cluster_key = ""
//segmented status
this.segmented_mesg = ["?",
......@@ -1161,15 +1161,20 @@ changeCloneNotation: function(cloneNotationType) {
console.log("merge clones " + list)
this.saveClusters()
for (var i = 0; i < list.length; i++) {
if (this.clone(list[i]).top < top) {
leader = list[i];
top = this.clone(list[i]).top;
}
new_cluster = new_cluster.concat(this.clusters[list[i]]);
// All cluster lists of these clones are now empty...
this.clusters[list[i]] = [];
}
// ... except for the leader, who takes the list of all clones
this.clusters[leader] = new_cluster;
this.unselectAll()
this.updateElem(list)
......@@ -1194,10 +1199,13 @@ changeCloneNotation: function(cloneNotationType) {
nlist.splice(index, 1);
//le cluster retrouve sa liste de clones -1
this.saveClusters()
// The cluster has now a list with one less clone
this.clusters[clusterID] = nlist;
if (this.clusters[clusterID].length <= 1) this.clone(clusterID).split = false;
//le clone forme un cluster de 1 clone
// The unmerged clone has now its own 1-clone cluster
this.clusters[cloneID] = [cloneID];
this.updateElem([cloneID, clusterID]);
......@@ -1205,17 +1213,12 @@ changeCloneNotation: function(cloneNotationType) {
/**
* cluster clones who produce the same result with the function given in parameter <br>
* save a copy of clusters made by user
* @param {function} fct
* */
clusterBy: function (fct) {
var self = this;
//save user cluster
if ( this.cluster_key==""){
this.clusters_copy = this.clusters
this.clusters = []
}
this.saveClusters()
var tmp = {}
for (var i = 0; i < this.clones.length - this.system_available.length; i++) {
......@@ -1257,32 +1260,63 @@ changeCloneNotation: function(cloneNotationType) {
this.clusters[tmp[i][j]] = []
}
}
this.cluster_key = fct
this.update()
},
/**
* break the given clusters into 1-clone clusters
* @param {integer list} clusters
* */
break: function (clusters) {
console.log("break(" + clusters+ ")")
if (clusters == undefined)
clusters = this.getSelected();
console.log("break(" + clusters+ ")")
this.saveClusters()
for (var i = 0; i < clusters.length; i++) {
var list = this.clusters[clusters[i]]
console.log("b " + list)
for (var j = 0; j < list.length; j++) {
this.clusters[list[j]] = [list[j]]
}
this.updateElem(list);
}
},
/**
* reset clusters to default
* break all clusters to default 1-clone clusters
* */
resetClusters: function () {
//reset cluster
this.cluster_key = ""
this.saveClusters()
for (var i = 0; i < this.clones.length; i++) {
this.clusters[i] = [i]
}
this.update()
},
/**
* save clusters
* */
saveClusters: function () {
this.clusters_copy.push(this.clusters.slice())
},
/**
* restore clusters made by user
* restore previously saved clusters
* */
restoreClusters: function () {
this.cluster_key = ""
if ( typeof this.clusters_copy != 'undefined'){
this.clusters = this.clusters_copy
if (this.clusters_copy.length > 0){
this.clusters = this.clusters_copy.pop()
this.update()
}
......@@ -1915,16 +1949,13 @@ changeCloneNotation: function(cloneNotationType) {
* */
toCSV: function () {
//header
var csv = "name,id,system,tag,v,d,j,sequence"
for (var i=0; i<this.samples.order.length; i++) csv += ",reads_"+i
for (var i=0; i<this.samples.order.length; i++) csv += ",ratio_"+i
for (var i=0; i<this.samples.order.length; i++) csv += ",ratios_"+i
var csv = Clone.prototype.toCSVheader(this).join(',')
csv += "\n"
//only non-empty active clones and virtual clones
for (var i=0; i<this.clusters.length; i++){
if ( (this.clusters[i].length != 0 && this.clone(i).isActive()) || this.clone(i).isVirtual() ){
csv += this.clone(i).toCSV()
csv += this.clone(i).toCSV().join(',')
csv += "\n"
}
}
......
......@@ -116,12 +116,12 @@ Segment.prototype = {
//merge button
var span = document.createElement('span');
span.id = "merge"
span.setAttribute('title', 'Merge the clones into a unique clone')
span.setAttribute('title', 'Merge the selected clones into a unique cluster')
span.className = "button"
span.onclick = function () {
self.m.merge()
}
span.appendChild(document.createTextNode("merge"));
span.appendChild(document.createTextNode("cluster"));
div_menu.appendChild(span)
//align button
......
......@@ -337,12 +337,31 @@ QUnit.test("export", function(assert) {
var m = new Model();
m.parseJsonData(json_data)
var c3 = new Clone(json_clone3, m, 0)
var c4 = new Clone(json_clone4, m, 1)
m.initClones()
assert.equal(c3.getPrintableSegSequence(), "aaaaa\naaaaatttt\nttttt", "getPrintableSegSequence() : Ok");
console.log(c3.getFasta())
assert.equal(c3.getFasta(), ">id3 19 nt, 10 reads (5.000%)\naaaaa\naaaaatttt\nttttt\n", "getFasta() : Ok");
var res3 = [
"0", "custom name", "id3",
"TRG", "-/-",
"undefined V", "IGHD2*03", "IGHV4*01",
"not productive",
"AAAAAAAAAATTTTTTTTT",
10, 10, 15, 15,
0.05, 0.1, 0.075, 0.15,
"19 nt; 10 reads (5.000%)", "19 nt; 10 reads (10.00%)", "19 nt; 15 reads (7.500%)", "19 nt; 15 reads (15.00%)"
]
assert.deepEqual(c3.toCSV(), res3, ".toCSV()")
assert.equal(c3.toCSVheader(m).length, c3.toCSV().length, ".toCSVheader() length")
m.select(0)
m.select(1)
m.merge()
assert.equal(c3.toCSV()[0], "0+1", ".toCSV(), merged clone")
});
......
......@@ -207,6 +207,12 @@ QUnit.test("cluster", function(assert) {
m.merge()
assert.deepEqual(m.clusters[0], [0,1,2], "merge [0,1] and 2: build cluster [0,1,2]");
assert.equal(m.clone(0).getSize(), 0.275, "cluster [0,1,2] : getsize = 0.275");
m.merge([0,4])
assert.deepEqual(m.clusters[0], [0,1,2,4], "merge [0,1,2] and 4 -> [0,1,2,4]");
m.restoreClusters()
assert.deepEqual(m.clusters[0], [0,1,2], "restore previous clusters -> [0,1,2]");
m.split(0,1)
assert.deepEqual(m.clusters[0], [0,2], "remove clone 1 from cluster [0,1,2]: build cluster [0,2]");
......@@ -214,9 +220,13 @@ QUnit.test("cluster", function(assert) {
m.clusterBy(function(id){return m.clone(id).germline})
assert.deepEqual(m.clusters[0], [0,1,3], "clusterBy germline");
m.break([0])
assert.deepEqual(m.clusters[1], [1], "break [0] -> [1] is alone");
m.restoreClusters()
m.restoreClusters()
assert.deepEqual(m.clusters[0], [0,2], "restore previous clusters (made by user with merge whithout using clusterby function)");
assert.deepEqual(m.clusters[0], [0,2], "restore previous clusters -> [0,2]");
m.resetClusters()
assert.deepEqual(m.clusters, [[0],[1],[2],[3],[4],[5],[6]], "resetClusters");
......
......@@ -129,7 +129,7 @@ Après ce renommage, vous pouvez voir que ce clone est déjà sélectionné.
\section{Analyse des populations clonales}
\subsection{Regroupement de clones («~merge~») par l'inspection de leur
\subsection{Regroupement de clones («~cluster~») par l'inspection de leur
séquence}
La première chose à faire est de voir si certains clones peuvent et
......@@ -171,7 +171,7 @@ erreurs de séquençage ou de PCR. Ces artefacts (mutations,
homopolymères, insertions, délétions) sont dépendants du séquenceur
utilisé et de la technique de PCR.
\question{Regroupez tous les clones en un seul en cliquant sur le bouton \com{merge}
\question{Regroupez tous les clones en un seul en cliquant sur le bouton \com{cluster}
puis sur le bouton \com{align}.}
Toutes les séquences ainsi regroupées apparaissent sous un seul et même
......@@ -204,7 +204,7 @@ des N insérés.
\question{Dans le menu \com{plot} (coin haut gauche de la grille),
dans le champ \com{preset}, sélectionnez \com{V/N length}. Vous pouvez
continuer de regrouper les clones en les alignant puis en cliquant sur
\com{merge} si besoin.}
\com{cluster} si besoin.}
\question{%
Vous pouvez aussi utiliser le preset \com{clone consensus
length/GC content} qui a tendance à bien séparer les clones distincts.}
......
......@@ -7,7 +7,7 @@
\maketitle
\textit{The goal of this practical session is to learn
common ways to visualise, filter, analyse and merge clones
common ways to visualise, filter, analyse and cluster clones
on the Vidjil web application.
%
These clones may have been computed by the Vidjil algorithm or by any other algorithm.}
......@@ -108,11 +108,11 @@ After this rename, you can see that the clone is still selected.
\section{Analysing clone populations}
\subsection{Merging clones through inspection of their sequences}
\subsection{Clustering clones through inspection of their sequences}
The first thing to be done is to see if some clones should be merged (because
The first thing to be done is to see if some clones should be clustered (because
of sequencing or PCR errors for instance). This step could be automatised
but, in any case, the automatic merge would need to be checked by an expert
but, in any case, the automatic clustering would need to be checked by an expert
eye.
By default in the bottom plot (the \textit{grid}), the clones
......@@ -144,18 +144,18 @@ differences should be due to sequencing or PCR errors.
These artifacts (mutations, homopolymers, insertions, deletions)
depend on the sequencer and the PCR technique.
\question{Merge all those clones in a single clone by clicking on the merge
button, next to the align button.}
\question{Cluster all those clones in a single clone by clicking on the ``cluster''
button, next to the ``align'' button.}
All the clustered sequences now appear within a same clone. That can be seen
in the list: the clone which hosts the subclones appears with a $+$ on its
left. You can click on the $+$ to see the subclones that have been merged in
left. You can click on the $+$ to see the subclones that have been clustered in
the main one.
\question{Click on the $+$ and observe the changes in the grid.}
As you may have noticed the subclones appear again in the grid. You can
compare their sequences again if you'd like (for example to double check that
you were right to merge them). You can also remove some subclones from the
you were right to cluster them). You can also remove some subclones from the
cluster by clicking on the cross at their left in the list.
\question{For the sake of the exercise, remove the last clone of the cluster.}
......@@ -171,7 +171,7 @@ insertions.
\question{Go to the \com{plot} menu (in the upper left corner of the grid),
and in the preset box choose \com{V/N length}.}
Then you can continue aligning and merging clones if necessary.
Then you can continue aligning and clustering clones if necessary.
\question{You can also try the preset \com{clone consensus length/GC content}
which tends to separate quite nicely the distinct clones.}
......@@ -344,8 +344,8 @@ You can also change the designation shown on the web application.
want to change the designation. In the segmentation part, click the edit
button. Choose what you would like to modify.}
Beware: none of the modification you made (name changes, clone merges, clone
tagging, samplê reordering\dots) will be automatically saved. You have to save
Beware: none of the modification you made (name changes, clusters, clone
tagging, sample reordering\dots) will be automatically saved. You have to save
your changes by yourself either by clicking on \com{save patient} in the top left menu (where the
``patient'' name is written) or by using the \texttt{Ctrl+S} keyboard
shortcut.
......
......@@ -60,10 +60,10 @@ Otherwise, such =.vidjil= files can be obtained:
You can select such clones (for example those sharing a same V and a same J), then:
- inspect the sequences in the lower panel (possibly using the “align” function),
- remove some of these sequences from the selection (clicking on their name in the lower panel)
- merge them (button “merge”) in a unique clone.
Once several clones are merged, you can still visualize them by clicking on “+” in the list of clones.
- cluster them (button “cluster”) in a unique clone.
Once several clones are clustered, you can still visualize them by clicking on “+” in the list of clones.
- Your analysis (clone tagging, renaming, merging) can be saved:
- Your analysis (clone tagging, renaming, clustering) can be saved:
- either with “patients”/“save analysis” if you are connected to a patient/experiment database
- or with “file”/“export .analysis”
......@@ -96,8 +96,8 @@ to learn the essential features of Vidjil.
- The “i” button displays additional information on each clone.
- The list can be sorted on V genes, J genes or clone abundance.
The “+” and “-” allow respectively to un-merge or re-merge all clones that have
already been merged.
The “+” and “-” allow respectively to un-cluster or re-cluster all clones that have
already been clustered.
- Clones can be searched (“search” box) by either their name, their custom name,
or their DNA sequence.
......@@ -284,7 +284,7 @@ The different permissions that can be attributed are:
clones: they may be due to sequencing error that lead to different windows.
However those small differences can also be due to a real biological process
inside the cells. Therefore we let the user choose whether the clones should
be manually merged or not.
be manually clustered or not.
In *MiXCR*, clones are defined based on the amino-acid CDR3 sequence, on the V
gene used and on the hypermutations.
......
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