Skip to content

Commit

Permalink
Add combobox selection for VCF to validate against
Browse files Browse the repository at this point in the history
Also start the slow process of renaming run -> vcf.

Add pdiff test (but not the screenshots)

fixes #520
  • Loading branch information
ihodes committed Mar 13, 2015
1 parent b98b9b3 commit a9a0424
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 42 deletions.
9 changes: 5 additions & 4 deletions cycledash/genotypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def contigs(vcf_id):
return [contig for (contig,) in results]


def get(vcf_id, query, with_stats=True, truth_vcf_id=None):
def get(vcf_id, query, with_stats=True):
"""Return a list of genotypes in a vcf conforming to the given query, as
well as a dict of stats calculated on them.
Expand All @@ -84,14 +84,15 @@ def get(vcf_id, query, with_stats=True, truth_vcf_id=None):
}
"""
query = _annotate_query_with_types(query, spec(vcf_id))
validate_vcf_id = query.get('selectedVcfId')
with tables(db, 'genotypes') as (con, g):
if truth_vcf_id:
if validate_vcf_id:
# We consider a genotype validated if a truth genotype exists at its
# location (contig/position) with the same ref/alts. This isn't
# entirely accurate: for example, it handles SVs very poorly.
gt = g.alias()
joined_q = outerjoin(g, gt, and_(
gt.c.vcf_id == truth_vcf_id,
gt.c.vcf_id == validate_vcf_id,
g.c.contig == gt.c.contig,
g.c.position == gt.c.position,
g.c.reference == gt.c.reference,
Expand All @@ -112,7 +113,7 @@ def get(vcf_id, query, with_stats=True, truth_vcf_id=None):
q = _add_ordering(q, g, 'Integer', 'position', 'asc')

genotypes = [dict(g) for g in con.execute(q).fetchall()]
stats = calculate_stats(vcf_id, truth_vcf_id, query) if with_stats else {}
stats = calculate_stats(vcf_id, validate_vcf_id, query) if with_stats else {}
return {'records': genotypes, 'stats': stats}


Expand Down
28 changes: 19 additions & 9 deletions cycledash/runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,29 @@ class CollisionError(Exception):
pass


def get_run(run_id):
"""Return a run with a given ID, and the spec and list of contigs for that
def get_vcf(vcf_id):
"""Return a vcf with a given ID, and the spec and list of contigs for that
run for use by the /examine page."""
with tables(db, 'vcfs') as (con, vcfs):
q = select(vcfs.c).where(vcfs.c.id == run_id)
run = dict(con.execute(q).fetchone())
run['spec'] = genotypes.spec(run_id)
run['contigs'] = genotypes.contigs(run_id)
return run
q = select(vcfs.c).where(vcfs.c.id == vcf_id)
vcf = dict(con.execute(q).fetchone())
vcf['spec'] = genotypes.spec(vcf_id)
vcf['contigs'] = genotypes.contigs(vcf_id)
return vcf


def create_run():
"""Create a new run, inserting it into the vcfs table and starting workers.
def get_related_vcfs(vcf):
"""Return a list of vcfs in the same project as vcf."""
with tables(db, 'vcfs') as (con, vcfs):
q = select(vcfs.c).where(
vcfs.c.project_id == vcf['project_id']).where(
vcfs.c.id != vcf['id'])
vcfs = [dict(v) for v in con.execute(q).fetchall()]
return vcfs


def create_vcf():
"""Create a new vcf, inserting it into the vcfs table and starting workers.
This raises an exception if anything goes wrong.
"""
Expand Down
9 changes: 9 additions & 0 deletions cycledash/static/css/examine.css
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,15 @@ table.vcf-table {
}
}

/* VCFValidation */
#vcf-validations {
margin: 11px;
}
#vcf-validations select {
width: 500px;
margin-left: 13px;
}

/* ExamineInformation */
.examine-information {
max-width: 600px;
Expand Down
7 changes: 7 additions & 0 deletions cycledash/static/js/examine/RecordActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var ACTION_TYPES = {
DELETE_COMMENT: 'DELETE_COMMENT',
REQUEST_PAGE: 'REQUEST_PAGE',
SELECT_RECORD: 'SELECT_RECORD',
SELECT_VALIDATION_VCF: 'SELECT_VALIDATION_VCF',
SET_COMMENT: 'SET_COMMENT',
SET_QUERY: 'SET_QUERY',
SET_VIEWER_OPEN: 'SET_VIEWER_OPEN',
Expand Down Expand Up @@ -36,6 +37,12 @@ function getRecordActions(dispatcher) {
record
});
},
selectValidationVcf: function(validationVcfId) {
dispatcher.dispatch({
actionType: ACTION_TYPES.SELECT_VALIDATION_VCF,
validationVcfId
});
},
setViewerOpen: function(isOpen) {
dispatcher.dispatch({
actionType: ACTION_TYPES.SET_VIEWER_OPEN,
Expand Down
20 changes: 14 additions & 6 deletions cycledash/static/js/examine/RecordStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ function createRecordStore(run, igvHttpfsUrl, dispatcher, opt_testDataSource) {
selectedRecord = null,
isViewerOpen = false,

selectedVcfId = null,

filters = [],
sortBys = DEFAULT_SORT_BYS,
range = ENTIRE_GENOME,
Expand Down Expand Up @@ -89,6 +91,10 @@ function createRecordStore(run, igvHttpfsUrl, dispatcher, opt_testDataSource) {
selectedRecord = action.record;
notifyChange();
break;
case ACTION_TYPES.SELECT_VALIDATION_VCF:
selectedVcfId = parseInt(action.validationVcfId);
updateGenotypes({append: false});
break;
case ACTION_TYPES.SET_VIEWER_OPEN:
isViewerOpen = action.isOpen;
notifyChange();
Expand Down Expand Up @@ -133,8 +139,8 @@ function createRecordStore(run, igvHttpfsUrl, dispatcher, opt_testDataSource) {
page = 0;
}

var query = queryFrom(range, filters, sortBys, page, limit);
setSearchStringToQuery(query);
var query = queryFrom(range, filters, sortBys, page, limit, selectedVcfId);
setQueryStringToQuery(query);

// If we're not just appending records, reset the selected records (as the
// table is now invalidated).
Expand Down Expand Up @@ -343,7 +349,7 @@ function createRecordStore(run, igvHttpfsUrl, dispatcher, opt_testDataSource) {
}

// Returns a JS object query for sending to the backend.
function queryFrom(range, filters, sortBy, page, limit) {
function queryFrom(range, filters, sortBy, page, limit, selectedVcfId) {
if (sortBy[0].columnName == 'position') {
sortBy = DEFAULT_SORT_BYS.map(sb => {
sb.order = sortBys[0].order;
Expand All @@ -355,7 +361,8 @@ function createRecordStore(run, igvHttpfsUrl, dispatcher, opt_testDataSource) {
filters,
sortBy,
page,
limit
limit,
selectedVcfId
};
}

Expand All @@ -369,7 +376,7 @@ function createRecordStore(run, igvHttpfsUrl, dispatcher, opt_testDataSource) {
return decodeURIComponent(q.replace(/\+/g, '%20'));
}

function setSearchStringToQuery(query) {
function setQueryStringToQuery(query) {
var queryString = encodeURIPlus(QueryLanguage.toString(query));
window.history.replaceState(null, null, '?query=' + queryString);
}
Expand Down Expand Up @@ -439,7 +446,7 @@ function createRecordStore(run, igvHttpfsUrl, dispatcher, opt_testDataSource) {

return {
getState: function() {
var query = queryFrom(range, filters, sortBys, page, limit);
var query = queryFrom(range, filters, sortBys, page, limit, selectedVcfId);
return {
columns,
contigs,
Expand All @@ -453,6 +460,7 @@ function createRecordStore(run, igvHttpfsUrl, dispatcher, opt_testDataSource) {
range,
records,
selectedRecord,
selectedVcfId,
sortBys,
stats
};
Expand Down
2 changes: 0 additions & 2 deletions cycledash/static/js/examine/components/ExamineInformation.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ var ExamineInformation = React.createClass({
return (
<div className="examine-information">
<dl>
<dt>Run ID</dt> <dd>{run.id}</dd>
<dt>VCF URI</dt> <dd>{run.uri}</dd>
<dt>Caller</dt> <dd>{run.caller_name}</dd>
<dt>Dataset</dt> <dd>{run.dataset_name}</dd>
<dt>Submitted At</dt> <dd>{run.created_at}</dd>
</dl>
</div>
Expand Down
27 changes: 18 additions & 9 deletions cycledash/static/js/examine/components/ExaminePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ var React = require('react'),
VCFTable = require('./VCFTable'),
QueryBox = require('./QueryBox'),
Downloads = require('./Downloads'),
ExamineInformation = require('./ExamineInformation');
ExamineInformation = require('./ExamineInformation'),
VCFValidation = require('./VCFValidation');


// The root component of the page.
var ExaminePage = React.createClass({
propTypes: {
recordStore: React.PropTypes.object.isRequired,
recordActions: React.PropTypes.object.isRequired,
run: React.PropTypes.object,
igvHttpfsUrl: React.PropTypes.string.isRequired
vcf: React.PropTypes.object,
igvHttpfsUrl: React.PropTypes.string.isRequired,
vcfs: React.PropTypes.arrayOf(React.PropTypes.object)
},
getInitialState: function() {
return this.props.recordStore.getState();
Expand Down Expand Up @@ -47,6 +49,9 @@ var ExaminePage = React.createClass({
handlePreviousRecord: function() {
this.moveSelectionInDirection(-1);
},
handleValidationVcfChange: function(selectedVcfId) {
this.props.recordActions.selectValidationVcf(selectedVcfId);
},
moveSelectionInDirection: function(delta) {
if (!this.state.selectedRecord) return;
var records = this.state.records,
Expand All @@ -72,13 +77,18 @@ var ExaminePage = React.createClass({
<div className="examine-page">
<StatsSummary hasLoaded={state.hasLoaded}
stats={state.stats} />
<ExamineInformation run={props.run}/>
<ExamineInformation run={props.vcf}/>
{props.vcfs ?
<VCFValidation vcfs={props.vcfs}
selectedVcfId={state.selectedVcfId}
handleValidationVcfChange={this.handleValidationVcfChange} /> :
null}
<QueryBox columns={state.columns}
hasPendingRequest={state.hasPendingRequest}
loadError={state.loadError}
query={state.query}
handleQueryChange={this.handleQueryChange} />
<Downloads query={state.query} run_id={props.run.id} />
<Downloads query={state.query} run_id={props.vcf.id} />
<VCFTable ref="vcfTable"
hasLoaded={state.hasLoaded}
records={state.records}
Expand All @@ -94,9 +104,9 @@ var ExaminePage = React.createClass({
handleOpenViewer={this.handleOpenViewer}
handleSetComment={this.handleSetComment}
handleDeleteComment={this.handleDeleteComment} />
<BioDalliance vcfPath={props.run.uri}
normalBamPath={props.run.normal_bam_uri}
tumorBamPath={props.run.tumor_bam_uri}
<BioDalliance vcfPath={props.vcf.uri}
normalBamPath={props.vcf.normal_bam_uri}
tumorBamPath={props.vcf.tumor_bam_uri}
igvHttpfsUrl={props.igvHttpfsUrl}
selectedRecord={state.selectedRecord}
isOpen={state.isViewerOpen}
Expand All @@ -109,4 +119,3 @@ var ExaminePage = React.createClass({
});

module.exports = ExaminePage;

30 changes: 30 additions & 0 deletions cycledash/static/js/examine/components/VCFValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use strict";

var React = require('react');

var VCFValidation = React.createClass({
propTypes: {
vcfs: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
selectedVcfId: React.PropTypes.number,
handleValidationVcfChange: React.PropTypes.func.isRequired
},
handleSelect: function(evt) {
this.props.handleValidationVcfChange(evt.target.value);
},
render: function() {
var vcfs = [{id: null, uri: '---'}].concat(
this.props.vcfs).map(
v => <option value={v.id} key={v.uri}>{v.uri}</option>);
return (
<div id='vcf-validations'>
Validate against:
<select onChange={this.handleSelect}
value={this.props.selectedVcfId}>
{vcfs}
</select>
</div>
);
}
});

module.exports = VCFValidation;
7 changes: 4 additions & 3 deletions cycledash/static/js/examine/examine.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ var React = require('react'),
getRecordActions = require('./RecordActions').getRecordActions;


window.renderExaminePage = function(el, run, igvHttpfsUrl) {
window.renderExaminePage = function(el, vcf, vcfs, igvHttpfsUrl) {
var dispatcher = new Dispatcher();
var recordActions = getRecordActions(dispatcher);
var recordStore = createRecordStore(run, igvHttpfsUrl, dispatcher);
var recordStore = createRecordStore(vcf, igvHttpfsUrl, dispatcher);
React.render(<ExaminePage recordStore={recordStore}
recordActions={recordActions}
run={run}
vcfs={vcfs}
vcf={vcf}
igvHttpfsUrl={igvHttpfsUrl} />, el);
};
6 changes: 4 additions & 2 deletions cycledash/static/js/runs/components/RunsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ var RunsPage = React.createClass({
var projectTables = _.chain(this.filteredProjects())
.sortBy(project => {
var vcf = project.vcfs[0];
// to sort by the descending ID, assumuning project.runs is already sorted by descending ID
// Sort by the descending ID, assuming project.runs is already sorted
// by descending ID.
return vcf ? -vcf.id : -100;
}).map(function(project) {
return <ProjectTable key={project.name}
Expand All @@ -62,7 +63,8 @@ var RunsPage = React.createClass({
<h1>
Data Directory
{!this.state.displayProjectForm ?
<button className='btn btn-default' id='new-project' onClick={() => this.setDisplayProjectForm(true)}>
<button className='btn btn-default' id='new-project'
onClick={() => this.setDisplayProjectForm(true)}>
New Project
</button> : null}
</h1>
Expand Down
3 changes: 2 additions & 1 deletion cycledash/templates/examine.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
<script type="text/javascript" src="/static/js/dist/bundled.js"></script>
<script>
window.renderExaminePage(document.getElementById('examine'),
{{ run | tojson }},
{{ vcf | tojson }},
{{ vcfs | tojson }},
{{ config['IGV_HTTPFS_URL'] | tojson }});
</script>
{% endblock%}
9 changes: 6 additions & 3 deletions cycledash/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ def about():
@app.route('/runs', methods=['POST', 'GET'])
def list_runs():
if request.method == 'POST':
return cycledash.runs.create_run()
return cycledash.runs.create_vcf()
elif request.method == 'GET':
return cycledash.projects.get_projects_tree()


@app.route('/tasks/<run_id>', methods=['GET', 'DELETE'])
@app.route('/tasks/<vcf_id>', methods=['GET', 'DELETE'])
def get_tasks(run_id):
if request.method == 'GET':
tasks = cycledash.tasks.get_tasks(run_id)
Expand Down Expand Up @@ -108,7 +108,10 @@ def bam(bam_id):

@app.route('/runs/<run_id>/examine')
def examine(run_id):
return render_template('examine.html', run=cycledash.runs.get_run(run_id))
vcf = cycledash.runs.get_vcf(run_id)
return render_template('examine.html',
vcf=vcf,
vcfs=cycledash.runs.get_related_vcfs(vcf))


@app.route('/runs/<run_id>/genotypes')
Expand Down
Loading

0 comments on commit a9a0424

Please sign in to comment.