Commit 14b17206 authored by Hoogenboom, Jerry's avatar Hoogenboom, Jerry

FDSTools v1.0.1.dev3

* General changes in v1.0.1:
  * Fixed crash that occurred when using the -i option to run the same command
    on multiple input files.
  * The usage string now always starts with 'fdstools', even if FDSTools was
    invoked using some other command (e.g. on Windows, FDSTools gets invoked
    through a file called 'fdstools-script.py').
  * Fixed bug with the -d/--debug option being ignored if placed before the
    tool name on systems running Python 2.7.9 or later.
  * FDSTools library files may now contain IUPAC ambiguous bases in the
    prefix and suffix sequences of STR markers (except the first sequence,
    as it is used as the reference).  Additionally, optional bases may be
    represented by lowercase letters.
  * If no explicit prefix/suffix is given for an alias, the prefix/suffix of
    the corresponding marker is assumed instead. This situation was not
    handled correctly when converting from raw sequences to TSSV or allelename
    format, which resulted in the alias remaining unused.
* Stuttermodelvis v2.0.2:
  * Added filtering option for the stutter amount (-1, +1, -2, etc.).
  * Added filtering option for the coefficient of determination (r squared
    value) of the fit functions.
* Libconvert v1.1.1:
  * Adjustments for supporting IUPAC notation in prefix and suffix sequences
    when converting from FDSTools to TSSV library format.
* Library v1.0.2:
  * Added documentation for IUPAC support to the descriptive comment of the
    [prefix] section.
parent 98c434ed
......@@ -24,7 +24,7 @@ including tools for characterisation and filtering of PCR stutter artefacts and
other systemic noise, and for automatic detection of the alleles in a sample.
"""
__version_info__ = ('1', '0', '1', 'dev2')
__version_info__ = ('1', '0', '1', 'dev3')
__version__ = '.'.join(__version_info__)
usage = __doc__.split("\n\n\n")
......
......@@ -72,9 +72,10 @@ def main():
"""
Main entry point.
"""
parser = argparse.ArgumentParser(formatter_class=_HelpFormatter,
prog = os.path.splitext(os.path.basename(__file__))[0]
parser = argparse.ArgumentParser(formatter_class=_HelpFormatter, prog=prog,
add_help=False, description=usage[0])
parser.version = version(parser.prog)
parser.version = version(prog)
parser.add_argument('-h', '--help', action=_HelpAction,
default=argparse.SUPPRESS, nargs=argparse.REMAINDER,
help="show this help message, or help for the "
......@@ -100,9 +101,10 @@ def main():
formatter_class=_HelpFormatter,
help=module.__doc__.split("\n\n", 1)[0],
description=module.__doc__,
version=version(parser.prog, name, module.__version__))
version=version(prog, name, module.__version__))
__tools__[name] = subparser
subparser.add_argument('-d', "--debug", action="store_true",
default=argparse.SUPPRESS,
help="if specified, additional debug output is given")
module.add_arguments(subparser)
subparser.set_defaults(func=module.run)
......
......@@ -28,6 +28,7 @@ from StringIO import StringIO
# Patterns that match entire sequences.
PAT_SEQ_RAW = re.compile("^[ACGT]*$")
PAT_SEQ_IUPAC = re.compile("^[ACGTUWSMKRYBDHVNacgtuwsmkrybdhvn]*$")
PAT_SEQ_TSSV = re.compile("^(?:[ACGT]+\(\d+\))*$")
PAT_SEQ_ALLELENAME_STR = re.compile( # First line: n_ACT[m] or alias.
"^(?:(?:(?:CE)?-?\d+(?:\.\d+)?_(?:[ACGT]+\[\d+\])*)|((?!_).+?))"
......@@ -92,6 +93,17 @@ COMPL = {"A": "T", "T": "A", "U": "A", "G": "C", "C": "G", "R": "Y", "Y": "R",
"a": "t", "t": "a", "u": "a", "g": "c", "c": "g", "r": "y", "y": "r",
"k": "m", "m": "k", "b": "v", "v": "b", "d": "h", "h": "d"}
# IUPAC Table of ambiguous bases.
IUPAC = {"A": "A", "C": "C", "G": "G", "T": "T", "U": "T", "W": "AT",
"S": "CG", "M": "AC", "K": "GT", "R": "AG", "Y": "CT", "B": "CGT",
"D": "AGT", "H": "ACT", "V": "ACG", "N": "ACGT",
"a": ("A", ""), "c": ("C", ""), "g": ("G", ""), "t": ("T", ""),
"u": ("T", ""), "w": ("A", "T", ""), "s": ("C", "G", ""),
"m": ("A", "C", ""), "k": ("G", "T", ""), "r": ("A", "G", ""),
"y": ("C", "T", ""), "b": ("C", "G", "T", ""),
"d": ("A", "G", "T", ""), "h": ("A", "C", "T", ""),
"v": ("A", "C", "G", ""), "n": ("A", "C", "G", "T", "")}
# Special values that may appear in the place of a sequence.
SEQ_SPECIAL_VALUES = ("No data", "Other sequences")
......@@ -143,7 +155,7 @@ def call_variants(template, sequence, location=("?", 1), cache=True,
integer for the position, all variants are given as substitutions in
the form posX>Y. Insertions and deletions are written as pos.1->Y
and posX>-, respectively. The given position is that of the first
base in the template. With the location set to "suffix", a plus
base in the template. With the location set to "suffix", a plus
sign is prepended to position numbers and the first base in the
template is pos=1. With location set to "prefix", a minus sign is
prepended and bases are counted from right to left instead.
......@@ -422,6 +434,21 @@ def mutate_sequence(seq, variants, location=None):
#mutate_sequence
def iupac_expand_ambiguous(seq):
"""Return generator for all possible values of ambiguous seq."""
return ("".join(x) for x in itertools.product(
*((b for b in IUPAC[a]) for a in seq)))
#iupac_expand_ambiguous
def iupac_pattern_ambiguous(seq):
"""Return regex pattern that matches the ambiguous seq."""
return "".join(
(("[%s]" if len(IUPAC[x.upper()]) > 1 else "%s") +
("?" if x.islower() else "")) % IUPAC[x.upper()] for x in seq)
#iupac_expand_ambiguous
def parse_library(libfile, stream=False):
try:
if not stream:
......@@ -483,6 +510,7 @@ def parse_library_tsv(handle):
library["blocks_middle"][marker] = [
(block[0], int(block[1]), int(block[2])) for block in
PAT_STR_DEF_BLOCK.findall(line[3])]
# NOTE: Libconvert tool expects "(seq){num,num}" blocks ONLY!
library["regex"][marker] = re.compile(
"".join(("^", "".join(
"(%s){%s,%s}" % x for x in PAT_STR_DEF_BLOCK.findall(line[3])),
......@@ -533,11 +561,12 @@ def parse_library_ini(handle):
raise ValueError(
"A prefix was defined for non-STR marker %s" % marker)
values = PAT_SPLIT.split(value)
for value in values:
if PAT_SEQ_RAW.match(value) is None:
for i in range(len(values)):
if (not i and PAT_SEQ_RAW.match(values[i]) is None or
i and PAT_SEQ_IUPAC.match(values[i]) is None):
raise ValueError(
"Prefix sequence '%s' of marker %s is invalid" %
(value, marker))
(values[i], marker))
library["prefix"][marker] = values
markers.add(marker)
elif section_low == "suffix":
......@@ -545,11 +574,12 @@ def parse_library_ini(handle):
raise ValueError(
"A suffix was defined for non-STR marker %s" % marker)
values = PAT_SPLIT.split(value)
for value in values:
if PAT_SEQ_RAW.match(value) is None:
for i in range(len(values)):
if (not i and PAT_SEQ_RAW.match(values[i]) is None or
i and PAT_SEQ_IUPAC.match(values[i]) is None):
raise ValueError(
"Suffix sequence '%s' of marker %s is invalid" %
(value, marker))
(values[i], marker))
library["suffix"][marker] = values
markers.add(marker)
elif section_low == "genome_position":
......@@ -692,20 +722,31 @@ def parse_library_ini(handle):
(marker, reflength, length))
# Compile regular expressions.
# NOTE: The libconvert tool expects "(seq){num,num}" blocks ONLY!
for marker in markers:
parts = []
blocksm = []
if marker in library["prefix"]:
parts += ("(%s){0,1}" % x for x in library["prefix"][marker])
parts += ("(%s){0,1}" % iupac_pattern_ambiguous(x)
for x in library["prefix"][marker])
elif (marker in library["aliases"] and
library["aliases"][marker]["marker"] in library["prefix"]):
parts += ("(%s){0,1}" % iupac_pattern_ambiguous(x)
for x in library["prefix"][
library["aliases"][marker]["marker"]])
if marker in library["aliases"]:
blocksm.append((library["aliases"][marker]["sequence"], 0, 1))
elif marker in library["regex"]:
blocksm += [(block[0], int(block[1]), int(block[2])) for block in
PAT_STR_DEF_BLOCK.findall(library["regex"][marker])]
parts += ["(%s){%s,%s}" % x for x in blocksm]
parts += ("(%s){%s,%s}" % x for x in blocksm)
if marker in library["suffix"]:
parts += ("(%s){0,1}" % x for x in library["suffix"][marker])
parts += ("(%s){0,1}" % iupac_pattern_ambiguous(x)
for x in library["suffix"][marker])
elif (marker in library["aliases"] and
library["aliases"][marker]["marker"] in library["suffix"]):
parts += ("(%s){0,1}" % iupac_pattern_ambiguous(x)
for x in library["suffix"][
library["aliases"][marker]["marker"]])
if parts:
library["regex"][marker] = re.compile(
"".join(["^"] + parts + ["$"]))
......@@ -912,8 +953,7 @@ def convert_sequence_raw_tssv(seq, library, marker, return_alias=False):
match = None
if "aliases" in library:
for alias in library["aliases"]:
if (library["aliases"][alias]["marker"] == marker and
alias in library["regex"]):
if library["aliases"][alias]["marker"] == marker:
match = library["regex"][alias].match(seq)
if match is not None:
marker = alias
......@@ -930,13 +970,15 @@ def convert_sequence_raw_tssv(seq, library, marker, return_alias=False):
# Find explictily provided prefix and/or suffix if present.
pre_suf = ["", ""]
if "prefix" in library and marker in library["prefix"]:
for prefix in library["prefix"][marker]:
for prefix in (x for x in library["prefix"][marker]
for x in iupac_expand_ambiguous(x)):
if seq.startswith(prefix):
pre_suf[0] = prefix
seq = seq[len(prefix):]
break
if "suffix" in library and marker in library["suffix"]:
for suffix in library["suffix"][marker]:
for suffix in (x for x in library["suffix"][marker]
for x in iupac_expand_ambiguous(x)):
if seq.endswith(suffix):
pre_suf[1] = suffix
seq = seq[:-len(suffix)]
......@@ -1695,8 +1737,8 @@ def get_input_output_files(args, single=False, batch_support=False):
# Each yielded tuple should cause a separate run of the tool.
# Glob args.infiles in case the shell didn't (e.g, on Windows).
infiles = [x for x in infiles for x in glob_path(x)] if "infiles" in \
args and args.infiles is not None else [args.infile]
infiles = [x for x in args.infiles for x in glob_path(x)] if "infiles"\
in args and args.infiles is not None else [args.infile]
if infiles == ["-"] and sys.stdin.isatty():
return False # No input specified.
......
......@@ -44,15 +44,13 @@ import sys
import re
import os.path
from ..lib import parse_library, INI_COMMENT
from ..lib import parse_library, iupac_expand_ambiguous, INI_COMMENT
from library import make_empty_library_ini
from ConfigParser import RawConfigParser
__version__ = "1.1.0"
__version__ = "1.1.1"
def convert_library(infile, outfile, aliases=False):
pattern_reverse = re.compile("\(([ACGT]+)\)\{(\d+),(\d+)\}")
if infile is not None:
library = parse_library(infile, stream=True)
else:
......@@ -94,19 +92,30 @@ def convert_library(infile, outfile, aliases=False):
library["aliases"][marker]["marker"]]
else:
continue # Worthless, no flanks.
if marker in library["regex"]:
pattern = pattern_reverse.findall(
library["regex"][marker].pattern)
if marker in library["prefix"]:
pattern.append((library["prefix"][marker][0], 0, 1))
elif library["aliases"][marker]["marker"] in library["prefix"]:
pattern.append((library["prefix"][
library["aliases"][marker]["marker"]][0], 0, 1))
pattern.append((library["aliases"][marker]["sequence"], 0, 1))
if marker in library["suffix"]:
pattern.append((library["suffix"][marker][0], 0, 1))
elif library["aliases"][marker]["marker"] in library["suffix"]:
pattern.append((library["suffix"][
library["aliases"][marker]["marker"]][0], 0, 1))
elif aliases or marker not in marker_aliases:
# Normal marker, or separately from its aliases.
if marker not in library["flanks"]:
continue # Worthless, no flanks.
flanks = library["flanks"][marker]
if marker in library["regex"]:
pattern = pattern_reverse.findall(
library["regex"][marker].pattern)
elif marker in library["nostr_reference"]:
pattern = [(library["nostr_reference"][marker], "1", "1")]
if marker in library["prefix"]:
pattern += ((x, 0, 1) for x in library["prefix"][marker])
if marker in library["blocks_middle"]:
pattern += (library["blocks_middle"][marker])
if marker in library["suffix"]:
pattern += ((x, 0, 1) for x in library["suffix"][marker])
if marker in library["nostr_reference"]:
pattern.append((library["nostr_reference"][marker], 1, 1))
else:
# Merge marker with its aliases.
flanks = False
......@@ -120,16 +129,11 @@ def convert_library(infile, outfile, aliases=False):
if not flanks:
continue # Worthless, no flanks.
prefixes = []
suffixes = []
if marker in library["prefix"]:
prefixes += library["prefix"][marker]
if marker in library["suffix"]:
suffixes += library["suffix"][marker]
middle = []
if marker in library["regex"]:
# This marker has a regex next to its aliases.
if marker in library["blocks_middle"]:
middle += library["blocks_middle"][marker]
# Check if the aliases fit the regex without change.
unmatched = []
for alias in marker_aliases[marker]:
......@@ -143,20 +147,21 @@ def convert_library(infile, outfile, aliases=False):
if library["regex"][marker].match(allele) is None:
unmatched.append(
library["aliases"][alias]["sequence"])
middle = pattern_reverse.findall(
library["regex"][marker].pattern)[len(prefixes):]
if len(suffixes):
middle = middle[:-len(suffixes)]
if unmatched:
middle = [(x, "0", "1") for x in unmatched] + \
[(x[0], "0", x[2]) for x in middle]
middle = [(x, 0, 1) for x in unmatched] + \
[(x[0], 0, x[2]) for x in middle]
elif marker in library["nostr_reference"]:
middle = [(library["nostr_reference"][marker],
"0" if marker in marker_aliases else "1", "1")]
middle.append((library["nostr_reference"][marker],
0 if marker in marker_aliases else 1, 1))
# Add prefixes and suffixes of aliases.
# Gather prefixes and suffixes including aliases.
prefixes = []
suffixes = []
if marker in library["prefix"]:
prefixes += library["prefix"][marker]
if marker in library["suffix"]:
suffixes += library["suffix"][marker]
if marker in marker_aliases:
for alias in marker_aliases[marker]:
if alias in library["prefix"]:
......@@ -168,22 +173,25 @@ def convert_library(infile, outfile, aliases=False):
library["nostr_reference"][marker] !=
library["aliases"][alias]["sequence"]):
middle.append((
library["aliases"][alias]["sequence"],
"0", "1"))
library["aliases"][alias]["sequence"], 0, 1))
# Final regex is prefixes + middle + suffixes.
pattern = []
for i in range(len(prefixes)):
if i == prefixes.index(prefixes[i]):
pattern.append((prefixes[i], "0", "1"))
pattern.append((prefixes[i], 0, 1))
pattern += middle
for i in range(len(suffixes)):
if i == suffixes.index(suffixes[i]):
pattern.append((suffixes[i], "0", "1"))
pattern.append((suffixes[i], 0, 1))
outfile.write("%s\t%s\t%s\t%s\n" % (
marker, flanks[0], flanks[1],
" ".join("%s %s %s" % x for x in pattern)))
# Write marker to outfile.
outfile.write("%s\t%s\t%s\t" % (marker, flanks[0], flanks[1]))
space = ""
for p in pattern:
for s in iupac_expand_ambiguous(p[0]):
outfile.write(space + "%s %i %i" % (s, p[1], p[2]))
space = " "
outfile.write("\n")
else:
# TSSV -> FDSTools
......@@ -192,6 +200,7 @@ def convert_library(infile, outfile, aliases=False):
ini = make_empty_library_ini("full", aliases)
# Enter flanking sequences and STR definitions.
pattern_reverse = re.compile("\(([ACGT]+)\)\{(\d+),(\d+)\}")
fmt = "%%-%is" % reduce(max, map(len,
set(library["flanks"].keys() + library["regex"].keys())), 0)
for marker in sorted(library["flanks"]):
......
......@@ -57,7 +57,7 @@ from ..lib import INI_COMMENT
from ConfigParser import RawConfigParser
from types import MethodType
__version__ = "1.0.1"
__version__ = "1.0.2"
def ini_add_comment(ini, section, comment):
......@@ -112,7 +112,10 @@ def make_empty_library_ini(type, aliases=False):
"flank and the repeat and is omitted from allele names. The first "
"sequence listed is used as the reference sequence for that "
"marker when generating allele names. Deviations from the "
"reference are expressed as variants.")
"reference are expressed as variants. IUPAC notation for "
"ambiguous bases (e.g., 'R' for 'A or G') is supported, except "
"for the first sequence given. Lowercase letters represent "
"optional bases.")
ini.set("prefix", ";CSF1P0 = CTAAGTACTTC")
ini.add_section("suffix")
ini.add_comment("suffix",
......
......@@ -24,7 +24,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<title>Stutter Model Visualisation - FDSTools</title>
<!-- VERSION 2.0.0 -->
<!-- VERSION 2.0.2 -->
<!-- BEGIN_LIBRARIES -->
<script src="https://vega.github.io/vega-editor/vendor/d3.min.js"></script>
<script src="https://vega.github.io/vega/vega.min.js"></script>
......@@ -451,11 +451,15 @@
<span class="help" title="Type AG to show all repeat units containing 'AG'. Type =AG to show the 'AG' repeat unit only. Separate multiple values with spaces.">?</span>
</label>
</td>
</tr><!--
</tr>
<tr>
<td><label for="foldFilter">Stutter amount</label></td>
<td><input type="text" value="" id="foldFilter" size="3"></td>
</tr>-->
<td><label for="foldSelect">Stutter amount</label></td>
<td>
<select id="foldSelect">
<option value="" class="special-option" selected>Show all</option>
</select>
</td>
</tr>
<tr>
<td><label for="markerFilter">Marker name</label></td>
<td>
......@@ -465,6 +469,15 @@
</label>
</td>
</tr>
<tr>
<td><label for="r2Filter">Minimum <i>r</i><sup>2</sup></label></td>
<td>
<label>
<input type="text" id="r2Filter" size="20">
<span class="help" title="R squared, the coefficient of determination, is 1 if the fit explains the data as good as possible and lower otherwise.">?</span>
</label>
</td>
</tr>
<tr>
<td><label for="allDataFilter">Fit to all data</label></td>
<td><label><input type="checkbox" id="allDataFilter" checked> show</label></td>
......@@ -642,6 +655,37 @@ function makeUnitSelectionOptions(data){
}
}
function makeFoldSelectionOptions(data){
var sel = document.getElementById("foldSelect");
var val = sel.value;
removeChildren(sel);
var opt = document.createElement("option");
opt.setAttribute("value", "");
opt.setAttribute("class", "special-option");
opt.innerText = "Show all";
sel.appendChild(opt);
var folds = {}
var validValue = false;
data.forEach(function(datum){
folds[datum.stutter] = true;
});
Object.keys(folds).sort(function(a, b){
return parseFloat(a) - parseFloat(b);
}).forEach(function(fold){
opt = document.createElement("option");
opt.setAttribute("value", fold);
opt.innerText = (fold < 0? "\u2212" + (-fold) : "+" + fold) + " stutter";
sel.appendChild(opt);
if(val == fold)
validValue = true;
});
sel.value = validValue? val : "";
if(!validValue)
if(graph) // Don't overwrite the filter_fold on initial data load.
setSignalValue("filter_fold", "");
}
var currentlyLoadedFitFunctionsFile = "no/file/loaded";
function loadFitFunctions(filename, data){
if(filename == currentlyLoadedFitFunctionsFile)
......@@ -654,6 +698,7 @@ function loadFitFunctions(filename, data){
data_values = vg.util.read(data, data_format);
graph_spec.data[0].values = data_values;
makeUnitSelectionOptions(data_values);
makeFoldSelectionOptions(data_values);
return true;
}
var currentlyLoadedRawDataFile = "no/file/loaded";
......@@ -820,6 +865,9 @@ function onLoadSpec(has_data, has_rawdata){
}
setSignalValue("filter_unit", value);
}, false);
document.getElementById("foldSelect").addEventListener('change', function(){
setSignalValue("filter_fold", this.value);
}, false);
document.getElementById("markerFilter").addEventListener('change', function(){
var value = this.value;
try{
......@@ -830,6 +878,12 @@ function onLoadSpec(has_data, has_rawdata){
}
setSignalValue("filter_marker", value);
}, false);
document.getElementById("r2Filter").addEventListener('change', function(){
var value = parseFloat(this.value);
if(isNaN(value))
return;
setSignalValue("filter_r2", value);
}, false);
document.getElementById("allDataFilter").addEventListener('change', function(){
setSignalValue("show_all_data", this.checked);
}, false);
......@@ -847,15 +901,22 @@ function onLoadSpec(has_data, has_rawdata){
}, false);
//Sync graph_spec and display.
var uf = getSignalValue("filter_unit");
document.getElementById("unitFilter").value = uf;
var us = document.getElementById("unitSelect");
us.value = "CUSTOM";
for(i = 0; i < us.options.length; i++)
if(uf == us.options.item(i).value)
us.value = uf;
var val = getSignalValue("filter_unit");
document.getElementById("unitFilter").value = val;
var sel = document.getElementById("unitSelect");
sel.value = "CUSTOM";
for(i = 0; i < sel.options.length; i++)
if(val == sel.options.item(i).value)
sel.value = val;
val = getSignalValue("filter_fold");
sel = document.getElementById("foldSelect");
sel.value = "";
for(i = 0; i < sel.options.length; i++)
if(val == sel.options.item(i).value)
sel.value = val;
document.getElementById("unitFilterRow").setAttribute("class", us.value == "CUSTOM"? "" : "hidden");
document.getElementById("markerFilter").value = getSignalValue("filter_marker");
document.getElementById("r2Filter").value = getSignalValue("filter_r2");
document.getElementById("allDataFilter").checked = getSignalValue("show_all_data");
document.getElementById("graphwidth").value = getSignalValue("width");
document.getElementById("graphheight").value = getSignalValue("graphheight");
......
{
"fdstools_visversion": "2.0.1",
"fdstools_visversion": "2.0.2",
"width": 600,
"height": 10,
"signals": [
......@@ -21,6 +21,14 @@
"init": {"expr": "regexp('(?:' + replace(replace(replace(filter_marker, /^ *(.*?) *$/, '$1'), /(^| )=(.*?)(?= |$)/g, '$1^$2$$'), / +/g, ')|(?:') + ')')"},
"expr": "regexp('(?:' + replace(replace(replace(filter_marker, /^ *(.*?) *$/, '$1'), /(^| )=(.*?)(?= |$)/g, '$1^$2$$'), / +/g, ')|(?:') + ')')"
},
{
"name": "filter_r2",
"init": 0
},
{
"name": "filter_fold",
"init": ""
},
{
"name": "show_all_data",
"init": true
......@@ -47,6 +55,7 @@
"parse": {
"lbound": "number",
"stutter": "number",
"r2": "number",
"min": "number",
"max": "number",
"a": "number",
......@@ -64,7 +73,7 @@
"transform": [
{
"type": "filter",
"test": "((datum.marker != 'All data' && test(marker_regex, datum.marker)) || (datum.marker == 'All data' && show_all_data)) && test(unit_regex, datum.unit)"
"test": "((datum.marker != 'All data' && test(marker_regex, datum.marker)) || (datum.marker == 'All data' && show_all_data)) && test(unit_regex, datum.unit) && datum.r2 >= filter_r2 && (filter_fold == '' || datum.stutter == filter_fold)"
},
{
"type": "formula",
......
To-do:
* Group tools by function in the command line help and put Pipeline on top.
* Produce empty output on empty input (instead of 'column not found' error).
* Samplevis:
* Detect whether correction was performed; hide related columns if not.
* A PctOfAlleles column in the tables would be useful for mixtures.
* Option to choose complete table download (all columns, not all rows).
* Option to freely adjust the sorting (currently CE length toggle only).
* Some of the media query breakpoints overlap, fix this.
......@@ -14,6 +16,7 @@ To-do:
* Add options for exporting data in CODIS format (and possibly others?).
* Add grouping, show/hide options, and target coverage for BGAnalyseVis to the
Vis tool.
* Add r2 filter and stutter amount for Stuttermodelvis to the Vis tool.
* Add per-marker allele calling settings to Samplestats.
* Exceptions to general mtDNA nomenclature: http://empop.online/methods
* Reduce noise profile memory usage:
......@@ -50,27 +53,6 @@ To-do:
* Add tool to summarise various statistics about the entire analysis pipeline:
(TODO: Write this list)
Open Vega issues:
* Bug in aggregate transform w.r.t. signals.
https://github.com/vega/vega/issues/530
* [Feat] Lookup transform onKey parameter only takes simple field names.
https://github.com/vega/vega/issues/526
* [Docs] Multi-column sorting relies on the rank transform.
https://github.com/vega/vega/issues/509
* [Feat] Concatenation of data sources.
https://github.com/vega/vega/issues/484
* [Feat] Id-based refs for Force transform's source and target.
https://github.com/vega/vega/issues/471
* Tick labels of log scale axes don't respect number formatting.
Proposed fix in https://github.com/vega/vega/pull/568
https://github.com/vega/vega/issues/470
* Legend and axis corruption on signal changes.
https://github.com/vega/vega/issues/446
* [Feat] Feature request for lines (or arbitrary shapes) for legend items.
https://github.com/vega/vega/issues/408
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Inclusion of tools developed during the project into FDSTools:
......
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