Commit e0eef88d authored by jhoogenboom's avatar jhoogenboom
Browse files

Introducing StuttermodelVis (not complete yet)

* Added StuttermodelVis HTML file and JSON spec. The rendering
  works, but some of the options are not implemented yet. It is
  also not yet added to the Vis tool.
* Changed the order of stuttermodel's coefficients: 'a' used to be
  the most significant coefficient, now it is the least significant
  coefficient (the shift). The benefit of this is that when moving
  to higher-order polynomials, the extra coefficients do not change
  the meaning of the others. So 'a' is now always the shift, 'b' is
  the linear component, 'c' the quadratic, etc.
* Added some development notes (including todo list) that I had
  kept outside of the project until now.
parent 03fc3d49
......@@ -88,7 +88,8 @@ def parse_stuttermodel(stuttermodel, min_r2=0, use_all_data=False):
r2 = float(line[colid_r2])
if r2 < min_r2:
continue
coefs = [float(line[colid_coef]) for colid_coef in colids_coefs]
coefs = [float(line[colid_coef])
for colid_coef in reversed(colids_coefs)]
if marker not in model:
model[marker] = {}
if not seq or not PAT_SEQ_RAW.match(seq):
......
......@@ -127,7 +127,8 @@ def print_fit(outfile, fit, lengths, seq, marker, stutter_fold, direction,
outfile.write("%s\t%s\t%+i\t%i\t%i\t%i\t%s\t%0.3f\t" %
(seq, marker, stutter_fold, lower_bound, min(lengths),
max(lengths), direction, r2))
outfile.write("\t".join("%.3e" % x for x in fit.tolist()) + "\n")
outfile.write("\t".join("%.3e" % x for x in reversed(fit.tolist())) +
"\n")
#print_fit
......
......@@ -197,7 +197,7 @@
//Load the data (input is a fileList object; only the first file is loaded).
function loadDataset(fileList){
if(!graph_spec)
if(!graph_spec || !fileList || !fileList.length)
return;
var reader = new FileReader();
reader.onload = function(theFile){
......
......@@ -194,7 +194,7 @@
//Load the data (input is a fileList object; only the first file is loaded).
function loadDataset(fileList){
if(!graph_spec)
if(!graph_spec || !fileList || !fileList.length)
return;
var reader = new FileReader();
reader.onload = function(theFile){
......
......@@ -197,7 +197,7 @@
//Load the data (input is a fileList object; only the first file is loaded).
function loadDataset(fileList){
if(!graph_spec)
if(!graph_spec || !fileList || !fileList.length)
return;
var reader = new FileReader();
reader.onload = function(theFile){
......
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Stutter Model Visualisation - FDSTools</title/>
<!-- BEGIN_LIBRARIES -->
<script src="http://vega.github.io/vega-editor/vendor/d3.min.js"></script>
<!--<script src="../vega.js"></script>-->
<script src="http://vega.github.io/vega/vega.min.js"></script>
<!-- END_LIBRARIES -->
<style>
* {
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
font-size: 10pt;
}
body {
margin: 0px;
}
div.options {
position: absolute;
padding: 5px;
background-color: rgba(255, 255, 255, 0.8);
z-index: 10;
}
table.optiongroup {
padding: 10px 0px 0px 0px;
margin: 0px;
border-spacing: 0px;
border: none;
}
table.optiongroup td {
padding-right: 10px;
}
table.optiongroup th {
text-align: left;
font-weight: bold;
padding-top: 5px;
}
#optionsheader {
cursor: pointer;
font-variant: small-caps;
border-bottom: 1px dashed black;
}
div#vis {
position: absolute;
overflow: auto;
bottom: 0px;
top: 0px;
right: 0px;
left: 0px;
text-align: right;
}
</style>
</head>
<body>
<div class="options">
<strong id="optionsheader">Options</strong><br>
<div id="options">
<table class="optiongroup" id="fileselectgroup" style="display: none">
<tr>
<th>Input file</th>
</tr>
<tr>
<td>Raw background noise data file:</td>
</tr>
<tr>
<td><input id="fileselect" type="file"></td>
</tr>
<tr>
<td>(or drag a file onto this page)</td>
</tr>
</table>
<table class="optiongroup">
<tr>
<th colspan="2">Filtering options</th>
</tr>
<tr>
<td>Require at least</td>
<td><input type="text" value="15" id="minN" size="3"> reads</td>
</tr>
<tr>
<td>Require at least</td>
<td><input type="text" value="0.5" id="minP" size="3">% of highest allele per marker</td>
</tr>
<tr>
<td>Marker name</td>
<td><input type="text" id="markerFilter" size="20" title="Supports regular expression syntax: e.g., use '.*' to match anything."></td>
</tr>
</table>
<table class="optiongroup">
<tr>
<th colspan="3">Display options</th>
</tr>
<tr>
<td>Graph dimensions</td>
<td colspan="2"><input type="text" value="400" id="graphheight" size="3">x<input type="text" value="600" id="graphwidth" size="3"> px</td>
</tr>
<tr>
<td>Subgraph spacing</td>
<td colspan="2"><input type="text" value="70" id="subgraphoffset" size="3"> px</td>
</tr>
<tr>
<td>Render as:</td>
<td><input type="radio" name="renderer" value="svg" id="renderSVG" checked> SVG</td>
<td><input type="radio" name="renderer" value="canvas" id="renderCanvas"> Canvas</td>
</tr>
</table>
<a id="saveLink" href="javascript:void(saveImage())" style="display: none">Save image</a>
</div>
</div> <!--
Select unit:
<select id="units">
<option>A</option>
<option>C</option>
<option>AC</option>
<option>AG</option>
<option>AT</option>
<option>AAC</option>
<option>AAG</option>
<option>ACT</option>
<option>AGC</option>
<option>AGG</option>
<option>ATC</option>
<option>AAAG</option>
<option>AACG</option>
<option>AAGG</option>
<option>AATC</option>
<option>AATG</option>
<option>ACAG</option>
<option>ACCT</option>
<option selected>AGAT</option>
<option>AGGC</option>
<option>AGGG</option>
<option>ATCC</option>
<option>AAAAG</option>
<option>AATCT</option>
</select>
Stutter type:
<select id="fold">
<option value="f2">-2 stutter</option>
<option value="f1" selected>-1 stutter</option>
<option value="pf1">+1 stutter</option>
<option value="pf2">+2 stutter</option>
</select>
<input type="checkbox" name="filterMarker" value="All data" checked>Show fit to all homozygotes data
<input type="checkbox" id="showMarkerFits" checked>Show fits per marker<br>
Show:
<input type="checkbox" id="showPoints" checked>Raw homozygotes data (as circles),
<input type="checkbox" id="showFromProfiles" checked>Profile means (as diamonds and for AGAT only);
Dithering: <select id="dither">
<option value="0.0">Off</option>
<option>0.1</option>
<option>0.2</option>
<option>0.3</option>
<option>0.4</option>
<option selected>0.5</option>
<option>0.6</option>
<option>0.7</option>
<option>0.8</option>
<option>0.9</option>
</select> -->
<div id="vis"></div>
<script type="text/javascript">
var graph = false;
function parse(){
vg.parse.spec(graph_spec, function(chart){
var rendererName = "canvas";
if(document.getElementById("renderSVG").checked)
rendererName="svg";
graph = chart({el: "#vis", renderer: rendererName});
graph.update();
document.getElementById("saveLink").style.display = "inline";
//Scroll to the right; the graph is more interesting than the long labels.
var visdiv = document.getElementById("vis");
visdiv.scrollLeft = visdiv.scrollWidth;
});
}
function setDataFormulaTransformValue(dataname, fieldname, value){
if(!graph_spec)
return false;
for(i in graph_spec["data"]){
if(graph_spec["data"][i]["name"] == dataname){
for(j in graph_spec["data"][i]["transform"]){
if(graph_spec["data"][i]["transform"][j]["type"] == "formula" && graph_spec["data"][i]["transform"][j]["field"] == fieldname){
graph_spec["data"][i]["transform"][j]["expr"] = "" + value;
return true;
}
}
}
}
return false;
}
function getDataFormulaTransformValue(dataname, fieldname){
if(!graph_spec)
return false;
for(i in graph_spec["data"]){
if(graph_spec["data"][i]["name"] == dataname){
for(j in graph_spec["data"][i]["transform"]){
if(graph_spec["data"][i]["transform"][j]["type"] == "formula" && graph_spec["data"][i]["transform"][j]["field"] == fieldname){
return graph_spec["data"][i]["transform"][j]["expr"];
}
}
}
}
return false;
}
function setRenderer(value){
if(graph)
graph.renderer(value);
}
//Load the data (input is a fileList object; only the first file is loaded).
function loadDataset(fileList){
if(!graph_spec || !fileList || !fileList.length)
return;
var reader = new FileReader();
reader.onload = function(theFile){
graph_spec["data"][0]["values"] = reader.result;
parse();
};
reader.readAsText(fileList[0]);
}
//Save image function.
function saveImage(){
var link = document.getElementById("saveLink");
if(document.getElementById("renderSVG").checked){
var svg = document.querySelector("svg.marks");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); //Vega does not set this for us
link.setAttribute("href", "data:image/svg+xml," + encodeURIComponent(svg.outerHTML));
link.setAttribute("download", "graph.svg");
}
else{
link.setAttribute("href", document.getElementsByClassName('marks')[0].toDataURL());
link.setAttribute("download", "graph.png");
}
link.click();
link.setAttribute("href", "javascript:void(saveImage())");
return false;
}
function onLoadSpec(has_data){
if(!has_data){
//Allow files to be dragged onto the page.
document.addEventListener('dragover', function(evt){
evt.stopPropagation();
evt.preventDefault();
}, false);
document.addEventListener('drop', function(evt){
evt.stopPropagation();
evt.preventDefault();
loadDataset(evt.dataTransfer.files);
}, false);
//Handle files from the file input.
document.getElementById("fileselect").addEventListener('change', function(){
loadDataset(document.getElementById("fileselect").files);
}, false);
document.getElementById("fileselectgroup").style.display = "table";
}
//Update graph when rendering mode is changed.
document.getElementById("renderCanvas").addEventListener('change', function(){
setRenderer(this.value);
}, false);
document.getElementById("renderSVG").addEventListener('change', function(){
setRenderer(this.value);
}, false);
document.getElementById("graphwidth").addEventListener('change', function(){
var value = parseFloat(this.value);
if(isNaN(value))
return;
graph_spec["width"] = value;
if(graph)
parse();
}, false);
document.getElementById("graphheight").addEventListener('change', function(){
var value = parseFloat(this.value);
if(isNaN(value))
return;
if(setDataFormulaTransformValue("yscale", "graphheight", value) && graph)
parse();
}, false);
document.getElementById("subgraphoffset").addEventListener('change', function(){
var value = parseFloat(this.value);
if(isNaN(value))
return;
if(setDataFormulaTransformValue("yscale", "subgraphoffset", value) && graph)
parse();
}, false);
document.getElementById("minN").addEventListener('change', function(){
var value = parseFloat(this.value);
if(isNaN(value))
return;
if(setDataFormulaTransformValue("table", "amplitude_threshold", value) && graph)
parse();
}, false);
document.getElementById("minP").addEventListener('change', function(){
var value = parseFloat(this.value);
if(isNaN(value))
return;
if(setDataFormulaTransformValue("table", "amplitude_pct_threshold", value) && graph)
parse();
}, false);
document.getElementById("markerFilter").addEventListener('change', function(){
if(setDataFormulaTransformValue("table", "filter_marker", "'" + this.value + "'") && graph)
parse();
}, false);
//Toggle options visibility.
document.getElementById("optionsheader").addEventListener('click', function(){
var opts = document.getElementById("options");
if(opts.style.display == "none")
document.getElementById("options").style.display = "block";
else
document.getElementById("options").style.display = "none";
}, false);
//Sync graph_spec and display.
//FIXME: document.getElementById("markerFilter").value = getDataFormulaTransformValue("table", "filter_marker").replace(/^(['"]?)(.*(?=\1$))\1$/, '$2');
document.getElementById("graphwidth").value = graph_spec["width"];
document.getElementById("graphheight").value = getDataFormulaTransformValue("yscale", "graphheight");
document.getElementById("subgraphoffset").value = getDataFormulaTransformValue("yscale", "subgraphoffset");
document.getElementById("minN").value = getDataFormulaTransformValue("table", "amplitude_threshold");
document.getElementById("minP").value = getDataFormulaTransformValue("table", "amplitude_pct_threshold");
if(has_data){
document.getElementById("options").style.display = "none";
parse();
}
}
</script>
<!-- BEGIN_LOAD_SCRIPT -->
<script type="text/javascript">
var graph_spec = false;
vg.util.load({url: "stuttermodelvis.json"}, function(err, result){
graph_spec = JSON.parse(result);
onLoadSpec(false);
});
</script>
<!-- END_LOAD_SCRIPT -->
</body>
</html>
{
"width": 600,
"height": 10,
"data": [
{
"name": "fitfunctions",
"values": "VALUES HERE",
"format": {
"type": "tsv",
"parse": {
"lbound": "number",
"stutter": "number",
"min": "number",
"max": "number",
"a": "number",
"b": "number",
"c": "number",
"d": "number",
"e": "number",
"f": "number",
"g": "number",
"h": "number",
"i": "number",
"j": "number"
}
},
"transform": [
{
"type": "formula",
"field": "subgraph",
"expr": "(datum.stutter > 0? '+' : '') + datum.stutter + ' stutter in ' + datum.unit + ' repeats'"
}
]
},
{
"name": "subgraphpadding",
"source": "fitfunctions",
"transform": [
{
"type": "cross",
"diagonal": false
},
{
"type": "filter",
"test": "length(datum.b.unit) < length(datum.a.unit) || (length(datum.b.unit) == length(datum.a.unit) && datum.b.unit < datum.a.unit) || (datum.b.unit == datum.a.unit && datum.b.stutter < datum.a.stutter)"
},
{
"type": "formula",
"field": "subgraph",
"expr": "datum.a.subgraph"
},
{
"type": "aggregate",
"groupby": ["subgraph"],
"summarize": [{"field": "b.subgraph", "ops": ["distinct"], "as": ["cumulpadding"]}]
}
]
},
{
"name": "yscale",
"source": "fitfunctions",
"transform": [
{
"type": "aggregate",
"groupby": ["subgraph"],
"summarize": []
},
{
"type": "lookup",
"on": "subgraphpadding",
"onKey": "subgraph",
"keys": ["subgraph"],
"as": ["paddingobj"],
"default": {"cumulpadding": 0}
},
{
"type": "formula",
"field": "graphheight",
"expr": "400"
},
{
"type": "formula",
"field": "subgraphoffset",
"expr": "70"
},
{
"type": "formula",
"field": "offset",
"expr": "(datum.graphheight+datum.subgraphoffset)*datum.paddingobj.cumulpadding"
},
{
"type": "formula",
"field": "end",
"expr": "datum.offset + datum.graphheight"
}
]
},
{
"name": "lengths",
"values": [
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,
27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50
]
},
{
"name": "table",
"source": "fitfunctions",
"transform": [
{
"type": "aggregate",
"summarize": {"max": "max"}
},
{
"type": "formula",
"field": "amount",
"expr": "ceil(sqrt(datum.max_max+1))"
},
{
"type": "cross",
"with": "lengths"
},
{
"type": "filter",
"test": "datum.b.data < datum.a.amount"
},
{
"type": "cross"
},
{
"type": "formula",
"field": "length",
"expr": "datum.a.a.amount * datum.a.b.data + datum.b.b.data"
},
{
"type": "filter",
"test": "datum.length <= datum.a.a.max_max"
},
{
"type": "cross",
"with": "fitfunctions",
"output": {
"left": "len",
"right": "func"
}
},
{
"type": "formula",
"field": "length",
"expr": "datum.len.length"
},
{
"type": "filter",
"test": "datum.func.lbound <= datum.length && datum.func.max >= datum.length"
},
{
"type": "formula",
"field": "value",
"expr": "max(0, datum.func.a + datum.func.b*datum.length + datum.func.c*pow(datum.length, 2) + datum.func.d*pow(datum.length, 3) + datum.func.e*pow(datum.length, 4) + datum.func.f*pow(datum.length, 5) + datum.func.g*pow(datum.length, 6) + datum.func.h*pow(datum.length, 7) + datum.func.i*pow(datum.length, 8) + datum.func.j*pow(datum.length, 9))"
},
{
"type": "formula",
"field": "markerdirection",
"expr": "datum.func.marker + ' ' + datum.func.direction"
},
{
"type": "formula",
"field": "subgraph",
"expr": "datum.func.subgraph"
},
{
"type": "sort",
"by": ["markerdirection", "length"]
}
]
}
],
"marks": [
{
"type": "text",
"from": {
"data": "yscale"
},
"properties": {
"enter": {
"x": {"field": {"group": "width"}, "mult": 0.5},
"y": {"field": "offset"},
"fontWeight": {"value": "bold"},
"text": {"field": "subgraph"},
"align": {"value": "center"},
"baseline": {"value": "bottom"},
"fill": {"value": "black"}