diff --git a/README.md b/README.md
index 8910b439..101d0850 100644
--- a/README.md
+++ b/README.md
@@ -164,6 +164,65 @@ or run a `uwsgi` process:
uwsgi --ini server/uwsgi/local.ini --protocol http --socket 127.0.0.1:8000 --check-static _site
```
+## Optional JBrowse Setup
+
+Install JBrowse in $project_root/jbrowse
+
+```bash
+curl -O http://jbrowse.org/releases/JBrowse-x.x.x.zip (ie 1.11.3)
+unzip JBrowse-x.x.x.zip -d jbrowse
+cd jbrowse
+./setup.sh
+```
+
+Download data files (ie BAM, GFF, VCF) to /your/directory/of/files/ and add the following to nginx.conf
+
+```bash
+location /files/ {
+ alias /your/directory/of/files/;
+ internal;
+}
+```
+
+Load reference sequences and load features tracks (in that order) using JBrowse loading scripts
+
+```bash
+sudo ./bin/prepare-refseqs.pl --fasta ../files/chr1.fa
+...
+sudo ./bin/prepare-refseqs.pl --fasta ../files/chrY.fa
+sudo ./bin/flatfile-to-json.pl --gff ../files/ALL_COSMIC_POINT_MUTS_v65.gff3 --trackLabel Cosmic --trackType CanvasFeatures
+...
+
+Note: Segment large Cosmic GFF files with synchronization marks, a line containing '###', to prevent (really) slow loading
+```
+
+Run bgzip and tabix (via samtools/htslib) on VCF files
+
+```bash
+git clone git://github.com/samtools/samtools.git
+bgzip my.vcf
+tabix -p vcf my.vcf.gz
+```
+
+Make one edit to JBrowse source (BAM.js), regex change related to custom URL
+
+```bash
+/\/([^/\#\?]+)($|[\#\?])/
+
+to
+
+/.*filename=([^&]+)/
+```
+
+Lastly, save and load filenames correctly! Currently, the sample section key values of the manifest are concatenated to build filenames in the JBrowse URL
+
+```bash
+[sample]
+batch = Pseq_batch9
+sample = P-Pseq_0019-P-A
+version = 1
+```
+
## Makefile Commands
- `build` - builds and initializes all submodules, compiles SCSS and optimizes JavaScript
diff --git a/varify/conf/global_settings.py b/varify/conf/global_settings.py
index 44f4dd69..337b9934 100644
--- a/varify/conf/global_settings.py
+++ b/varify/conf/global_settings.py
@@ -167,7 +167,6 @@
TEMPLATE_CONTEXT_PROCESSORS += (
'django.core.context_processors.request',
'varify.context_processors.static',
- 'varify.context_processors.alamut',
)
@@ -187,7 +186,7 @@
LOGOUT_URL = '/logout/'
LOGIN_REDIRECT_URL = '/workspace/'
-ALAMUT_URL = 'http://localhost:10000'
+JBROWSE_HOST = 'localhost'
# For non-publicly accessible applications, the siteauth app can be used to
# restrict access site-wide.
diff --git a/varify/context_processors.py b/varify/context_processors.py
index 5424b8c8..6c787e13 100644
--- a/varify/context_processors.py
+++ b/varify/context_processors.py
@@ -14,9 +14,3 @@ def static(request):
'IMAGES_URL': os.path.join(static_url, 'images'),
'JAVASCRIPT_URL': os.path.join(static_url, 'js', prefix),
}
-
-
-def alamut(request):
- return {
- 'ALAMUT_URL': settings.ALAMUT_URL,
- }
diff --git a/varify/samples/formatters.py b/varify/samples/formatters.py
index 933e3bcd..7d9d7e79 100644
--- a/varify/samples/formatters.py
+++ b/varify/samples/formatters.py
@@ -8,26 +8,6 @@
from django.conf import settings
-class AlamutFormatter(HTMLFormatter):
- href = settings.ALAMUT_URL + '/show?request={0}'
- request = 'chr{chr}:g.{pos}{ref}>{alt}'
-
- def to_html(self, values, **context):
- request = self.request.format(**values)
- href = self.href.format(request)
- return '{label}'.format(
- href=href, label=request)
- to_html.process_multiple = True
-
- def to_excel(self, values, **context):
- request = self.request.format(**values)
- href = self.href.format(request)
-
- return '=HYPERLINK("{href}", "{label}")'.format(
- href=href, label=request)
- to_excel.process_multiple = True
-
-
class SampleFormatter(HTMLFormatter):
def to_html(self, values, **kwargs):
return values['sample__label']
@@ -105,7 +85,6 @@ def to_excel(self, value, **context):
to_csv = to_excel
-formatters.register(AlamutFormatter, 'Alamut Query Link')
formatters.register(SampleFormatter, 'Sample')
formatters.register(ReadDepthFormatter, 'Read Depth')
formatters.register(CohortsFormatter, 'Cohorts')
diff --git a/varify/samples/resources.py b/varify/samples/resources.py
index 8c01413a..29f32169 100644
--- a/varify/samples/resources.py
+++ b/varify/samples/resources.py
@@ -16,7 +16,7 @@
from varify.variants.resources import VariantResource
from varify import api
from vdw.assessments.models import Assessment
-from vdw.samples.models import Sample, Result, ResultScore
+from vdw.samples.models import Sample, SampleManifest, Result, ResultScore
from restlib2.http import codes
log = logging.getLogger(__name__)
@@ -218,6 +218,34 @@ def _cache_data(self, request, pk, key):
data['variant'] = VariantResource.get(request, data['variant_id'])
data.pop('variant_id')
+ # Integrate the SampleManifest data
+ path = SampleManifest.objects.get(sample__id=data['sample']['id']).path
+ manifest = open(path, 'r')
+ found = False
+ filestem = ''
+
+ # Add JBrowse data requirements, including sample_manifest which is
+ # constructed by appending batch, sample, and version
+ for line in manifest:
+ if '[sample]' in line:
+ found = True
+
+ if found:
+ if 'batch' in line or 'sample' in line or 'version' in line:
+ chunks = line.split('=')
+
+ if len(chunks) > 1:
+ if 'batch' in line:
+ filestem += chunks[1].strip()
+ else:
+ filestem += '_' + chunks[1].strip()
+
+ if 'version' in line:
+ break
+
+ data['sample_manifest'] = filestem
+ data['jbrowse_host'] = settings.JBROWSE_HOST
+
try:
score = ResultScore.objects.get(result=result)
data['score'] = {
@@ -254,10 +282,15 @@ class ResultsResource(ThrottledResource):
template = api.templates.SampleResultVariant
def post(self, request):
- if (not request.data.get('ids') or
- not isinstance(request.data['ids'], list)):
- return HttpResponse(status=codes.unprocessable_entity,
- content='Array of "ids" is required')
+ ids_not_found = 'ids' not in request.data
+ not_a_list = not isinstance(request.data['ids'], list)
+
+ if ids_not_found or not_a_list:
+ return self.render(
+ request,
+ {'message': 'An array of "ids" is required'},
+ status=codes.unprocessable_entity
+ )
data = []
resource = SampleResultResource()
@@ -342,6 +375,43 @@ def get(self, request, year, month, day, name):
return response
+
+class JbrowseResource(ThrottledResource):
+ def get(self, request):
+ data = request.GET
+
+ if 'id' not in data or 'filename' not in data:
+ return self.render(
+ request,
+ {'message': 'An "id" and "filename" are required'},
+ status=codes.unprocessable_entity
+ )
+
+ try:
+ if Sample.objects.get(id=data['id']):
+ response = HttpResponse()
+ f = data['filename']
+
+ if '.bai' in f or '.tbi' in f:
+ response['Content-Type'] = 'application/octet-stream'
+ else:
+ response['Content-Type'] = 'text/plain'
+
+ # Control access to files hosted by nginx
+ response['X-Accel-Redirect'] = '/files/' + f
+ # Control access to files hosted by Apache
+ response['X-Sendfile'] = '/files/' + f
+
+ response['Content-Disposition'] = 'attachment;filename=' + f
+ except Exception:
+ return self.render(
+ request,
+ {'message': 'No sample found for "id"'},
+ status=codes.unprocessable_entity
+ )
+
+ return response
+
sample_resource = never_cache(SampleResource())
samples_resource = never_cache(SamplesResource())
named_sample_resource = never_cache(NamedSampleResource())
@@ -350,6 +420,7 @@ def get(self, request, year, month, day, name):
results_resource = never_cache(ResultsResource())
phenotype_resource = never_cache(PhenotypeResource())
pedigree_resource = never_cache(PedigreeResource())
+jbrowse_resource = never_cache(JbrowseResource())
urlpatterns = patterns(
'',
@@ -360,6 +431,7 @@ def get(self, request, year, month, day, name):
url(r'^(?P\d+)/variants/$', sample_results_resource, name='variants'),
url(r'^variants/(?P\d+)/$', sample_result_resource, name='variant'),
url(r'^variants/$', results_resource, name='results_resource'),
+ url(r'^jbrowse/$', jbrowse_resource, name='jbrowse_resource'),
url(r'^(?P.+)/phenotypes/$', phenotype_resource,
name='phenotype'),
url(r'^pedigrees/(?P\d+)/(?P\d+)/(?P\d+)/(?P.+)$',
diff --git a/varify/static/js/src/ui/modals/result.js b/varify/static/js/src/ui/modals/result.js
index f49a7b4c..64d19a2f 100644
--- a/varify/static/js/src/ui/modals/result.js
+++ b/varify/static/js/src/ui/modals/result.js
@@ -134,6 +134,41 @@ define([
return content.join('');
},
+ jbrowseUrl: function(variantAttrs, resultAttrs) {
+ var host = resultAttrs.jbrowse_host;
+ var url = 'http://' + host + '/jbrowse/?loc=chr' + variantAttrs.chr + ':';
+
+ url += parseInt(variantAttrs.pos-25) + '..' + parseInt(variantAttrs.pos+25);
+ url += '&tracks=DNA,Cosmic,VCF,BAM&highlight=&addTracks=';
+
+ var addTracks ='[{"label":"BAM","store":"testStore","type":';
+
+ addTracks += '"JBrowse/View/Track/Alignments2"},{"label":"VCF","store":';
+ addTracks += '"testStoreTwo","type":"JBrowse/View/Track/CanvasVariants"}]';
+
+ url += encodeURIComponent(addTracks) + '&addStores=';
+
+ var sm = resultAttrs.sample_manifest;
+ var sid = resultAttrs.sample.id;
+ var addStores = '{"testStore":{"baiUrlTemplate"';
+
+ addStores += ':"http://' + host + '/api/samples/jbrowse/?id=';
+ addStores += sid + "&filename=" + sm + '.sorted.mdup.bai","type"';
+ addStores += ':"JBrowse/Store/SeqFeature/BAM","urlTemplate":';
+ addStores += '"http://' + host + '/api/samples/jbrowse/?id=';
+ addStores += sid + "&filename=" + sm;
+ addStores += '.sorted.mdup.bam"},"testStoreTwo":{"tbiUrlTemplate"';
+ addStores += ':"http://' + host + '/api/samples/jbrowse/?id=';
+ addStores += sid + "&filename=" + sm + '.var_raw.vcf.gz.tbi","type"';
+ addStores += ':"JBrowse/Store/SeqFeature/VCFTabix","urlTemplate":';
+ addStores += '"http://' + host + '/api/samples/jbrowse/?id=';
+ addStores += sid + "&filename=" + sm + '.var_raw.vcf.gz"}}';
+
+ url += encodeURIComponent(addStores);
+
+ return url;
+ },
+
renderSummary: function(resultAttrs, variantAttrs) {
var bases, content, hgmdLinks, key, labelClass;
@@ -200,10 +235,13 @@ define([
}
content.push('');
- content.push('Query Alamut');
+
+ url = this.jbrowseUrl(variantAttrs, resultAttrs);
+
+ html = 'View JBrowse';
+
+ content.push(html);
return content.join('');
},