Collapsed changeset: Trackster plugged into galaxy with behind-the-scenes indexing of Wiggle, coverage and interval datasets.
@@ -1,23 +1,29 @@
|
||||
# use glob syntax.
|
||||
syntax: glob
|
||||
|
||||
# Downloaded and locally built eggs
|
||||
eggs
|
||||
scripts/scramble/build
|
||||
scripts/scramble/lib
|
||||
scripts/scramble/archives
|
||||
|
||||
# Database stuff
|
||||
database/beaker_sessions
|
||||
database/compiled_templates
|
||||
database/files
|
||||
database/*.sqlite
|
||||
|
||||
# Python bytecode
|
||||
*.pyc
|
||||
|
||||
# Config files
|
||||
universe_wsgi.ini
|
||||
reports_wsgi.ini
|
||||
.hgignore
|
||||
.project
|
||||
.pydevproject
|
||||
database
|
||||
datatypes_conf.xml
|
||||
eggs
|
||||
*.pyc
|
||||
*~
|
||||
ignore_this
|
||||
reports_wsgi.ini
|
||||
run25.sh
|
||||
run_functional_tests.html
|
||||
run_unit_tests.html
|
||||
scripts/scramble/archives/
|
||||
scripts/scramble/build/
|
||||
scripts/scramble/lib/ez_setup.py
|
||||
scripts/scramble/lib/setuptools-0.6c9-py2.5.egg
|
||||
scripts/scramble/lib/setuptools-0.6c9-py2.6.egg
|
||||
static/.DS_Store
|
||||
static/images/.DS_Store
|
||||
static/scripts/.DS_Store
|
||||
static/scripts/packed/.DS_Store
|
||||
static/tracks/.DS_Store
|
||||
tool-data/*.loc
|
||||
tool_conf.xml
|
||||
|
||||
universe_wsgi.ini
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
<converter file="interval_to_coverage.xml" target_datatype="coverage"/>
|
||||
</datatype>
|
||||
<datatype extension="binseq.zip" type="galaxy.datatypes.images:Binseq" mimetype="application/zip" display_in_upload="true"/>
|
||||
<datatype extension="coverage" type="galaxy.datatypes.coverage:LastzCoverage" display_in_upload="true"/>
|
||||
<datatype extension="coverage" type="galaxy.datatypes.coverage:LastzCoverage" display_in_upload="true">
|
||||
<indexer file="coverage.xml" />
|
||||
</datatype>
|
||||
<datatype extension="customtrack" type="galaxy.datatypes.interval:CustomTrack"/>
|
||||
<datatype extension="csfasta" type="galaxy.datatypes.sequence:csFasta" display_in_upload="true"/>
|
||||
<datatype extension="data" type="galaxy.datatypes.data:Data" mimetype="application/octet-stream"/>
|
||||
@@ -29,6 +31,7 @@
|
||||
<datatype extension="html" type="galaxy.datatypes.images:Html" mimetype="text/html"/>
|
||||
<datatype extension="interval" type="galaxy.datatypes.interval:Interval" display_in_upload="true">
|
||||
<converter file="interval_to_bed_converter.xml" target_datatype="bed"/>
|
||||
<indexer file="interval.xml" />
|
||||
</datatype>
|
||||
<datatype extension="jpg" type="galaxy.datatypes.images:Image" mimetype="image/jpeg"/>
|
||||
<datatype extension="laj" type="galaxy.datatypes.images:Laj"/>
|
||||
@@ -46,7 +49,9 @@
|
||||
<datatype extension="txt" type="galaxy.datatypes.data:Text" display_in_upload="true"/>
|
||||
<datatype extension="blastxml" type="galaxy.datatypes.xml:BlastXml" display_in_upload="true"/>
|
||||
<datatype extension="txtseq.zip" type="galaxy.datatypes.images:Txtseq" mimetype="application/zip" display_in_upload="true"/>
|
||||
<datatype extension="wig" type="galaxy.datatypes.interval:Wiggle" display_in_upload="true"/>
|
||||
<datatype extension="wig" type="galaxy.datatypes.interval:Wiggle" display_in_upload="true">
|
||||
<indexer file="wiggle.xml" />
|
||||
</datatype>
|
||||
<!-- EMBOSS TOOLS -->
|
||||
<datatype extension="acedb" type="galaxy.datatypes.data:Text"/>
|
||||
<datatype extension="asn1" type="galaxy.datatypes.data:Text"/>
|
||||
|
||||
@@ -23,6 +23,7 @@ pysqlite = 2.3.5
|
||||
python_lzo = 1.08
|
||||
threadframe = 0.2
|
||||
guppy = 0.1.8
|
||||
numpy = 1.2.1
|
||||
|
||||
[eggs:noplatform]
|
||||
Beaker = 0.5
|
||||
@@ -60,6 +61,7 @@ nose = .dev_r101
|
||||
|
||||
; source location, necessary for scrambling
|
||||
[source]
|
||||
numpy = http://downloads.sourceforge.net/numpy/numpy-1.2.1.tar.gz
|
||||
bx_python = http://dist.g2.bx.psu.edu/bx-python_dist-r460.tar.bz2
|
||||
Cheetah = http://voxel.dl.sourceforge.net/sourceforge/cheetahtemplate/Cheetah-1.0.tar.gz
|
||||
DRMAA_python = http://gridengine.sunsource.net/files/documents/7/36/DRMAA-python-0.2.tar.gz
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import sys, os, atexit
|
||||
|
||||
from galaxy import config, jobs, util, tools, web
|
||||
from galaxy.tracks import store
|
||||
from galaxy.web import security
|
||||
import galaxy.model
|
||||
import galaxy.model.mapping
|
||||
@@ -32,8 +33,10 @@ class UniverseApplication( object ):
|
||||
self.security = security.SecurityHelper( id_secret=self.config.id_secret )
|
||||
# Initialize the tools
|
||||
self.toolbox = tools.ToolBox( self.config.tool_config, self.config.tool_path, self )
|
||||
#Load datatype converters
|
||||
# Load datatype converters
|
||||
self.datatypes_registry.load_datatype_converters( self.toolbox )
|
||||
# Load datatype indexers
|
||||
self.datatypes_registry.load_datatype_indexers( self.toolbox )
|
||||
#Load security policy
|
||||
self.security_agent = self.model.security_agent
|
||||
# Heartbeat and memdump for thread / heap profiling
|
||||
@@ -60,6 +63,9 @@ class UniverseApplication( object ):
|
||||
# FIXME: These are exposed directly for backward compatibility
|
||||
self.job_queue = self.job_manager.job_queue
|
||||
self.job_stop_queue = self.job_manager.job_stop_queue
|
||||
# Track Store
|
||||
self.track_store = store.TrackStoreManager( self.config.track_store_path )
|
||||
|
||||
def shutdown( self ):
|
||||
self.job_manager.shutdown()
|
||||
if self.heartbeat:
|
||||
|
||||
@@ -31,6 +31,8 @@ class Configuration( object ):
|
||||
# Where dataset files are stored
|
||||
self.file_path = resolve_path( kwargs.get( "file_path", "database/files" ), self.root )
|
||||
self.new_file_path = resolve_path( kwargs.get( "new_file_path", "database/tmp" ), self.root )
|
||||
# dataset Track files
|
||||
self.track_store_path = kwargs.get( "track_store_path", "${extra_files_path}/tracks")
|
||||
self.tool_path = resolve_path( kwargs.get( "tool_path", "tools" ), self.root )
|
||||
self.tool_data_path = resolve_path( kwargs.get( "tool_data_path", "tool-data" ), os.getcwd() )
|
||||
self.test_conf = resolve_path( kwargs.get( "test_conf", "" ), self.root )
|
||||
|
||||
@@ -16,6 +16,7 @@ from bx.intervals.io import *
|
||||
from galaxy.datatypes import metadata
|
||||
from galaxy.datatypes.metadata import MetadataElement
|
||||
from galaxy.datatypes.tabular import Tabular
|
||||
import math
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,3 +29,41 @@ class LastzCoverage( Tabular ):
|
||||
MetadataElement( name="reverseCol", desc="Optional reverse read column", param=metadata.ColumnParameter, optional=True, no_value=0 )
|
||||
MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True, visible=False )
|
||||
|
||||
|
||||
def get_track_window(self, dataset, data, start, end):
|
||||
"""
|
||||
Assumes we have a numpy file.
|
||||
"""
|
||||
# Maybe if we import here people will still be able to use Galaxy when numpy kills it
|
||||
pkg_resources.require("numpy>=1.2.1")
|
||||
#from numpy.lib import format
|
||||
import numpy
|
||||
|
||||
range = end - start
|
||||
# Determine appropriate resolution to plot ~1000 points
|
||||
resolution = ( 10 ** math.ceil( math.log10( range / 1000 ) ) )
|
||||
# Restrict to valid range
|
||||
resolution = min( resolution, 10000 )
|
||||
resolution = max( resolution, 1 )
|
||||
# Memory map the array (don't load all the data)
|
||||
data = numpy.load( data )
|
||||
# Grab just what we need
|
||||
t_start = math.floor( start / resolution )
|
||||
t_end = math.ceil( end / resolution )
|
||||
x = numpy.arange( t_start, t_end ) * resolution
|
||||
y = data[ t_start : t_end ]
|
||||
|
||||
return zip(x.tolist(), y.tolist())
|
||||
|
||||
def get_track_resolution( self, dataset, start, end):
|
||||
range = end - start
|
||||
# Determine appropriate resolution to plot ~1000 points
|
||||
resolution = math.ceil( 10 ** math.ceil( math.log10( range / 1000 ) ) )
|
||||
# Restrict to valid range
|
||||
resolution = min( resolution, 10000 )
|
||||
resolution = max( resolution, 1 )
|
||||
return resolution
|
||||
|
||||
def get_track_type( self ):
|
||||
return "LineTrack"
|
||||
|
||||
|
||||
@@ -254,6 +254,10 @@ class Data( object ):
|
||||
"""This function is called on the dataset after metadata is edited."""
|
||||
dataset.clear_associated_files( metadata_safe = True )
|
||||
|
||||
@property
|
||||
def has_resolution(self):
|
||||
return False
|
||||
|
||||
class Text( Data ):
|
||||
|
||||
def write_from_stream(self, dataset, stream):
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Read a chromosome of coverage data, and write it as a npy array, as
|
||||
well as averages over regions of progessively larger size in powers of 10
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import sys
|
||||
from galaxy import eggs
|
||||
import pkg_resources; pkg_resources.require( "bx-python" )
|
||||
import bx.wiggle
|
||||
from bx.cookbook import doc_optparse
|
||||
from bx import misc
|
||||
max2 = max
|
||||
pkg_resources.require("numpy>=1.2.1")
|
||||
from numpy import *
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
def write_chrom(max, out_base, instream):
|
||||
|
||||
scores = zeros( max, float32 ) * nan
|
||||
# Fill array from wiggle
|
||||
max_value = 0
|
||||
min_value = 0
|
||||
for line in instream:
|
||||
line = line.rstrip("\n\r")
|
||||
(chrom, pos, val) = line.split("\t")
|
||||
pos, val = int(pos), float(val)
|
||||
scores[pos] = val
|
||||
|
||||
# Write ra
|
||||
fname = "%s_%d" % ( out_base, 1 )
|
||||
save( fname, scores )
|
||||
os.rename( fname+".npy", fname )
|
||||
|
||||
# Write average
|
||||
for window in 10, 100, 1000, 10000:
|
||||
input = scores.copy()
|
||||
size = len( input )
|
||||
input.resize( ( ( size / window ), window ) )
|
||||
masked = ma.masked_array( input, isnan( input ) )
|
||||
averaged = mean( masked, 1 )
|
||||
averaged.set_fill_value( nan )
|
||||
fname = "%s_%d" % ( out_base, window )
|
||||
save( fname, averaged.filled() )
|
||||
del masked, averaged
|
||||
os.rename( fname+".npy", fname )
|
||||
|
||||
def main():
|
||||
max = int( 512*1024*1024 )
|
||||
# get chroms and lengths
|
||||
chroms = {}
|
||||
LEN = {}
|
||||
for line in open(sys.argv[1],"r"):
|
||||
line = line.rstrip("\r\n")
|
||||
fields = line.split("\t")
|
||||
(chrom, pos, forward) = fields[0:3]
|
||||
reverse = 0
|
||||
if len(fields) == 4: reverse = int(fields[3])
|
||||
forward = int(forward)+reverse
|
||||
pos = int(pos)
|
||||
chrom_file = chroms.get(chrom, None)
|
||||
if not chrom_file:
|
||||
chrom_file = chroms[chrom] = tempfile.NamedTemporaryFile()
|
||||
chrom_file.write("%s\t%s\t%s\n" % (chrom,pos,forward))
|
||||
LEN[chrom] = max2( LEN.get(chrom,0), pos+1 )
|
||||
for chrom, stream in chroms.items():
|
||||
stream.seek(0)
|
||||
prefix = os.path.join(sys.argv[2], chrom)
|
||||
write_chrom( LEN[chrom], prefix, stream )
|
||||
|
||||
manifest_file = open( os.path.join( sys.argv[2], "manifest.tab" ),"w" )
|
||||
for key, value in LEN.items():
|
||||
print >> manifest_file, "%s\t%s" % (key, value)
|
||||
manifest_file.close()
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
||||
@@ -0,0 +1,12 @@
|
||||
<tool id="INDEXER_Coverage_0" name="Index Coverage for Track Viewer">
|
||||
<!-- Used internally to generate track indexes -->
|
||||
<command interpreter="python">coverage.py $input_dataset $store_path 2>&1
|
||||
</command>
|
||||
<inputs>
|
||||
<page>
|
||||
<param format="coverage" name="input_dataset" type="data" label="Choose coverage"/>
|
||||
</page>
|
||||
</inputs>
|
||||
<help>
|
||||
</help>
|
||||
</tool>
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Generate indices for track browsing of an interval file.
|
||||
|
||||
usage: %prog bed_file out_directory
|
||||
-1, --cols1=N,N,N,N: Columns for chrom, start, end, strand in interval file
|
||||
"""
|
||||
import sys
|
||||
from galaxy import eggs
|
||||
import pkg_resources; pkg_resources.require( "bx-python" )
|
||||
from bx.intervals import io
|
||||
from bx.cookbook import doc_optparse
|
||||
import psyco_full
|
||||
import commands
|
||||
import os
|
||||
from os import environ
|
||||
import tempfile
|
||||
from bisect import bisect
|
||||
|
||||
def divide( intervals, out_path ):
|
||||
manifest = {}
|
||||
current_file = None
|
||||
lastchrom = ""
|
||||
for line in intervals:
|
||||
try:
|
||||
chrom = line.chrom
|
||||
except AttributeError, e:
|
||||
continue
|
||||
manifest[chrom] = max(manifest.get(chrom,0),line.end)
|
||||
if not lastchrom == chrom:
|
||||
if current_file:
|
||||
current_file.flush()
|
||||
current_file.close()
|
||||
current_file = open( os.path.join( out_path, "%s" % chrom), "a" )
|
||||
print >> current_file, "\t".join(line)
|
||||
lastchrom = chrom
|
||||
current_file.flush()
|
||||
current_file.close()
|
||||
return manifest
|
||||
|
||||
if __name__ == "__main__":
|
||||
options, args = doc_optparse.parse( __doc__ )
|
||||
try:
|
||||
chr_col_1, start_col_1, end_col_1, strand_col_1 = [int(x)-1 for x in options.cols1.split(',')]
|
||||
in_fname, out_path = args
|
||||
except:
|
||||
doc_optparse.exception()
|
||||
|
||||
# Sort through a tempfile first
|
||||
temp_file = tempfile.NamedTemporaryFile(mode="r")
|
||||
environ['LC_ALL'] = 'POSIX'
|
||||
commandline = "sort -f -n -k %d -k %d -k %d -o %s %s" % (chr_col_1+1,start_col_1+1,end_col_1+1, temp_file.name, in_fname)
|
||||
errorcode, stdout = commands.getstatusoutput(commandline)
|
||||
|
||||
temp_file.seek(0)
|
||||
interval = io.NiceReaderWrapper( temp_file,
|
||||
chrom_col=chr_col_1,
|
||||
start_col=start_col_1,
|
||||
end_col=end_col_1,
|
||||
strand_col=strand_col_1,
|
||||
fix_strand=True )
|
||||
manifest = divide( interval, out_path )
|
||||
manifest_file = open( os.path.join( out_path, "manifest.tab" ),"w" )
|
||||
for key, value in manifest.items():
|
||||
print >> manifest_file, "%s\t%s" % (key, value)
|
||||
manifest_file.close()
|
||||
temp_file.close()
|
||||
@@ -0,0 +1,14 @@
|
||||
<tool id="INDEXER_Interval_0" name="Index Interval for Track Viewer">
|
||||
<!-- Used internally to generate track indexes -->
|
||||
<command interpreter="python">interval.py $input_dataset
|
||||
-1 ${input_dataset.metadata.chromCol},${input_dataset.metadata.startCol},${input_dataset.metadata.endCol},${input_dataset.metadata.strandCol}
|
||||
$store_path 2>&1
|
||||
</command>
|
||||
<inputs>
|
||||
<page>
|
||||
<param format="interval" name="input_dataset" type="data" label="Choose intervals"/>
|
||||
</page>
|
||||
</inputs>
|
||||
<help>
|
||||
</help>
|
||||
</tool>
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Read a chromosome of wiggle data, and write it as a npy array, as
|
||||
well as averages over regions of progessively larger size in powers of 10
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import sys
|
||||
from galaxy import eggs
|
||||
import pkg_resources; pkg_resources.require( "bx-python" )
|
||||
import bx.wiggle
|
||||
from bx.cookbook import doc_optparse
|
||||
from bx import misc
|
||||
max2 = max
|
||||
pkg_resources.require("numpy>=1.2.1")
|
||||
from numpy import *
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
def write_chrom(max, out_base, instream):
|
||||
|
||||
scores = zeros( max, float32 ) * nan
|
||||
# Fill array from wiggle
|
||||
for line in instream:
|
||||
line = line.rstrip("\n\r")
|
||||
(chrom, pos, val) = line.split("\t")
|
||||
pos, val = int(pos), float(val)
|
||||
scores[pos] = val
|
||||
|
||||
# Write ra
|
||||
fname = "%s_%d" % ( out_base, 1 )
|
||||
save( fname, scores )
|
||||
os.rename( fname+".npy", fname )
|
||||
|
||||
# Write average
|
||||
for window in 10, 100, 1000, 10000:
|
||||
input = scores.copy()
|
||||
size = len( input )
|
||||
input.resize( ( ( size / window ), window ) )
|
||||
masked = ma.masked_array( input, isnan( input ) )
|
||||
averaged = mean( masked, 1 )
|
||||
averaged.set_fill_value( nan )
|
||||
fname = "%s_%d" % ( out_base, window )
|
||||
save( fname, averaged.filled() )
|
||||
del masked, averaged
|
||||
os.rename( fname+".npy", fname )
|
||||
|
||||
def main():
|
||||
max = int( 512*1024*1024 )
|
||||
# get chroms and lengths
|
||||
chroms = {}
|
||||
LEN = {}
|
||||
for (chrom, pos, val) in bx.wiggle.Reader( open(sys.argv[1],"r") ):
|
||||
chrom_file = chroms.get(chrom, None)
|
||||
if not chrom_file:
|
||||
chrom_file = chroms[chrom] = tempfile.NamedTemporaryFile()
|
||||
chrom_file.write("%s\t%s\t%s\n" % (chrom,pos,val))
|
||||
LEN[chrom] = max2( LEN.get(chrom,0), pos+1 )
|
||||
for chrom, stream in chroms.items():
|
||||
stream.seek(0)
|
||||
prefix = os.path.join(sys.argv[2], chrom)
|
||||
write_chrom( LEN[chrom], prefix, stream )
|
||||
|
||||
manifest_file = open( os.path.join( sys.argv[2], "manifest.tab" ),"w" )
|
||||
for key, value in LEN.items():
|
||||
print >> manifest_file, "%s\t%s" % (key, value)
|
||||
manifest_file.close()
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
||||
@@ -0,0 +1,12 @@
|
||||
<tool id="INDEXER_Wiggle_0" name="Index Wiggle for Track Viewer">
|
||||
<!-- Used internally to generate track indexes -->
|
||||
<command interpreter="python">wiggle.py $input_dataset $store_path 2>&1
|
||||
</command>
|
||||
<inputs>
|
||||
<page>
|
||||
<param format="wiggle" name="input_dataset" type="data" label="Choose wiggle"/>
|
||||
</page>
|
||||
</inputs>
|
||||
<help>
|
||||
</help>
|
||||
</tool>
|
||||
@@ -16,6 +16,7 @@ from bx.intervals.io import *
|
||||
from galaxy.datatypes import metadata
|
||||
from galaxy.datatypes.metadata import MetadataElement
|
||||
from galaxy.datatypes.tabular import Tabular
|
||||
import math
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -287,6 +288,26 @@ class Interval( Tabular ):
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def get_track_window(self, dataset, data, start, end):
|
||||
"""
|
||||
Assumes the incoming track data is sorted already.
|
||||
"""
|
||||
window = list()
|
||||
for record in data:
|
||||
fields = record.rstrip("\n\r").split("\t")
|
||||
record_chrom = fields[dataset.metadata.chromCol-1]
|
||||
record_start = int(fields[dataset.metadata.startCol-1])
|
||||
record_end = int(fields[dataset.metadata.endCol-1])
|
||||
if record_start < end and record_end > start:
|
||||
window.append( (record_chrom, record_start, record_end) ) #Yes I did want to use a generator here, but it doesn't work downstream
|
||||
return window
|
||||
|
||||
def get_track_resolution( self, dataset, start, end):
|
||||
return None
|
||||
|
||||
def get_track_type( self ):
|
||||
return "FeatureTrack"
|
||||
|
||||
class Bed( Interval ):
|
||||
"""Tab delimited data in BED format"""
|
||||
@@ -758,6 +779,43 @@ class Wiggle( Tabular ):
|
||||
except:
|
||||
return False
|
||||
|
||||
def get_track_window(self, dataset, data, start, end):
|
||||
"""
|
||||
Assumes we have a numpy file.
|
||||
"""
|
||||
# Maybe if we import here people will still be able to use Galaxy when numpy kills it
|
||||
pkg_resources.require("numpy>=1.2.1")
|
||||
#from numpy.lib import format
|
||||
import numpy
|
||||
|
||||
range = end - start
|
||||
# Determine appropriate resolution to plot ~1000 points
|
||||
resolution = ( 10 ** math.ceil( math.log10( range / 1000 ) ) )
|
||||
# Restrict to valid range
|
||||
resolution = min( resolution, 10000 )
|
||||
resolution = max( resolution, 1 )
|
||||
# Memory map the array (don't load all the data)
|
||||
data = numpy.load( data )
|
||||
# Grab just what we need
|
||||
t_start = math.floor( start / resolution )
|
||||
t_end = math.ceil( end / resolution )
|
||||
x = numpy.arange( t_start, t_end ) * resolution
|
||||
y = data[ t_start : t_end ]
|
||||
|
||||
return zip(x.tolist(), y.tolist())
|
||||
|
||||
def get_track_resolution( self, dataset, start, end):
|
||||
range = end - start
|
||||
# Determine appropriate resolution to plot ~1000 points
|
||||
resolution = math.ceil( 10 ** math.ceil( math.log10( range / 1000 ) ) )
|
||||
# Restrict to valid range
|
||||
resolution = min( resolution, 10000 )
|
||||
resolution = max( resolution, 1 )
|
||||
return resolution
|
||||
|
||||
def get_track_type( self ):
|
||||
return "LineTrack"
|
||||
|
||||
class CustomTrack ( Tabular ):
|
||||
"""UCSC CustomTrack"""
|
||||
file_ext = "customtrack"
|
||||
|
||||
@@ -16,7 +16,9 @@ class Registry( object ):
|
||||
self.datatypes_by_extension = {}
|
||||
self.mimetypes_by_extension = {}
|
||||
self.datatype_converters = odict()
|
||||
self.datatype_indexers = odict()
|
||||
self.converters = []
|
||||
self.indexers = []
|
||||
self.sniff_order = []
|
||||
self.upload_file_formats = []
|
||||
if root_dir and config:
|
||||
@@ -27,8 +29,11 @@ class Registry( object ):
|
||||
self.log.debug( 'Loading datatypes from %s' % config )
|
||||
registration = root.find( 'registration' )
|
||||
self.datatype_converters_path = os.path.join( root_dir, registration.get( 'converters_path', 'lib/galaxy/datatypes/converters' ) )
|
||||
self.datatype_indexers_path = os.path.join( root_dir, registration.get( 'indexers_path', 'lib/galaxy/datatypes/indexers' ) )
|
||||
if not os.path.isdir( self.datatype_converters_path ):
|
||||
raise ConfigurationError( "Directory does not exist: %s" % self.datatype_converters_path )
|
||||
if not os.path.isdir( self.datatype_indexers_path ):
|
||||
raise ConfigurationError( "Directory does not exist: %s" % self.datatype_indexers_path )
|
||||
for elem in registration.findall( 'datatype' ):
|
||||
try:
|
||||
extension = elem.get( 'extension', None )
|
||||
@@ -57,6 +62,11 @@ class Registry( object ):
|
||||
target_datatype = converter.get( 'target_datatype', None )
|
||||
if converter_config and target_datatype:
|
||||
self.converters.append( ( converter_config, extension, target_datatype ) )
|
||||
for indexer in elem.findall( 'indexer' ):
|
||||
# Build the list of datatype indexers for track building
|
||||
indexer_config = indexer.get( 'file', None )
|
||||
if indexer_config:
|
||||
self.indexers.append( (indexer_config, extension) )
|
||||
except Exception, e:
|
||||
self.log.warning( 'Error loading datatype "%s", problem: %s' % ( extension, str( e ) ) )
|
||||
# Load datatype sniffers from the config
|
||||
@@ -221,6 +231,16 @@ class Registry( object ):
|
||||
self.datatype_converters[source_datatype][target_datatype] = converter
|
||||
self.log.debug( "Loaded converter: %s", converter.id )
|
||||
|
||||
def load_datatype_indexers( self, toolbox ):
|
||||
"""Adds indexers from self.indexers to the toolbox from app"""
|
||||
for elem in self.indexers:
|
||||
tool_config = elem[0]
|
||||
datatype = elem[1]
|
||||
indexer = toolbox.load_tool( os.path.join( self.datatype_indexers_path, tool_config ) )
|
||||
toolbox.tools_by_id[indexer.id] = indexer
|
||||
self.datatype_indexers[datatype] = indexer
|
||||
self.log.debug( "Loaded indexer: %s", indexer.id )
|
||||
|
||||
def get_converters_by_datatype(self, ext):
|
||||
"""Returns available converters by source type"""
|
||||
converters = odict()
|
||||
@@ -233,12 +253,27 @@ class Registry( object ):
|
||||
if ext in self.datatype_converters.keys():
|
||||
converters.update(self.datatype_converters[ext])
|
||||
return converters
|
||||
|
||||
def get_indexers_by_datatype( self, ext ):
|
||||
"""Returns indexers based on datatype"""
|
||||
class_chain = list()
|
||||
source_datatype = type(self.get_datatype_by_extension(ext))
|
||||
for ext_spec in self.datatype_indexers.keys():
|
||||
datatype = type(self.get_datatype_by_extension(ext_spec))
|
||||
if issubclass( source_datatype, datatype ):
|
||||
class_chain.append( ext_spec )
|
||||
# Prioritize based on class chain
|
||||
ext2type = lambda x: self.get_datatype_by_extension(x)
|
||||
class_chain = sorted(class_chain, lambda x,y: issubclass(ext2type(x),ext2type(y)) and -1 or 1)
|
||||
return [self.datatype_indexers[x] for x in class_chain]
|
||||
|
||||
def get_converter_by_target_type(self, source_ext, target_ext):
|
||||
"""Returns a converter based on source and target datatypes"""
|
||||
converters = self.get_converters_by_datatype(source_ext)
|
||||
if target_ext in converters.keys():
|
||||
return converters[target_ext]
|
||||
return None
|
||||
|
||||
def find_conversion_destination_for_dataset_by_extensions( self, dataset, accepted_formats, converter_safe = True ):
|
||||
"""Returns ( target_ext, exisiting converted dataset )"""
|
||||
for convert_ext in self.get_converters_by_datatype( dataset.ext ):
|
||||
@@ -251,4 +286,4 @@ class Registry( object ):
|
||||
else:
|
||||
ret_data = None
|
||||
return ( convert_ext, ret_data )
|
||||
return ( None, None )
|
||||
return ( None, None )
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
1 2
|
||||
3 4
|
||||
@@ -0,0 +1,4 @@
|
||||
PENDING = "pending"
|
||||
NO_DATA = "no data"
|
||||
NO_CHROMOSOME = "no chromosome"
|
||||
DATA = "data"
|
||||
@@ -0,0 +1,75 @@
|
||||
import os
|
||||
from string import Template
|
||||
|
||||
class TemplateSubber( object ):
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
def get( self, key, default=None ):
|
||||
return getattr(self.obj, key, default)
|
||||
def __getitem__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
class TrackStoreManager( object ):
|
||||
def __init__(self, path=""):
|
||||
self.path = path
|
||||
|
||||
def get( self, dataset ):
|
||||
s = Template(self.path)
|
||||
return TrackStore( path=s.substitute(TemplateSubber(dataset)) )
|
||||
|
||||
class TrackStore( object ):
|
||||
MANIFEST_NAME = "manifest.tab"
|
||||
|
||||
def __init__(self, path=""):
|
||||
self.path = path
|
||||
|
||||
def get_manifest( self ):
|
||||
if not self.exists: raise self.DoesNotExist("TrackStore at %s does not exist." % self.path)
|
||||
manifest_path = os.path.join( self.path, self.MANIFEST_NAME )
|
||||
if os.path.exists( manifest_path ):
|
||||
manifest = {}
|
||||
for line in open( manifest_path ):
|
||||
line = line.rstrip("\n\r")
|
||||
fields = line.split("\t")
|
||||
manifest[fields[0]] = fields[1:]
|
||||
return manifest
|
||||
else:
|
||||
raise self.DoesNotExist( "Manifest for TrackStore object could not be found." )
|
||||
|
||||
def get(self, chrom="chr1", resolution=None, **kwargs):
|
||||
if not self.exists: raise self.DoesNotExist("TrackStore at %s does not exist." % self.path)
|
||||
object_path = self._get_object_path( chrom, resolution )
|
||||
if os.path.exists( object_path ):
|
||||
return open( object_path, "rb" )
|
||||
else:
|
||||
try:
|
||||
return kwargs['default']
|
||||
except KeyError:
|
||||
raise self.DoesNotExist("TrackStore object at %s does not exist." % object_path )
|
||||
|
||||
def set(self, chrom="chr1", resolution=None, data=None):
|
||||
if not self.exists: self._build_path( self.path )
|
||||
if not data: return
|
||||
object_path = self._get_object_path( chrom, resolution )
|
||||
fd = open( object_path, "wb" )
|
||||
fd.write( data )
|
||||
fd.close()
|
||||
|
||||
def _get_object_path( self, chrom, resolution ):
|
||||
object_name = chrom
|
||||
if resolution: object_name += "_%d" % resolution
|
||||
return os.path.join( self.path, object_name )
|
||||
|
||||
def _build_path( self, path ):
|
||||
try:
|
||||
os.mkdir( path )
|
||||
except OSError:
|
||||
self._build_path( os.path.dirname( path ) )
|
||||
os.mkdir( path )
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
return os.path.exists( self.path )
|
||||
|
||||
class DoesNotExist( Exception ):
|
||||
pass
|
||||
@@ -3,7 +3,18 @@ Utility functions used systemwide.
|
||||
|
||||
"""
|
||||
import logging
|
||||
import threading, sets, random, string, md5, re, binascii, pickle, time, datetime, math, re, os, sys
|
||||
import threading, random, string, re, binascii, pickle, time, datetime, math, re, os
|
||||
|
||||
# Older py compatibility
|
||||
try:
|
||||
set()
|
||||
except:
|
||||
from sets import Set as set
|
||||
|
||||
try:
|
||||
from hashlib import md5
|
||||
except ImportError:
|
||||
from md5 import new as md5
|
||||
|
||||
import pkg_resources
|
||||
|
||||
@@ -57,11 +68,11 @@ def unique_id(KEY_SIZE=128):
|
||||
Genenerates a unique ids
|
||||
|
||||
>>> ids = [ unique_id() for i in range(1000) ]
|
||||
>>> len(sets.Set(ids))
|
||||
>>> len(set(ids))
|
||||
1000
|
||||
"""
|
||||
id = str( random.getrandbits( KEY_SIZE ) )
|
||||
return md5.new(id).hexdigest()
|
||||
return md5(id).hexdigest()
|
||||
|
||||
def parse_xml(fname):
|
||||
"""Returns an parsed xml tree"""
|
||||
@@ -74,7 +85,7 @@ def xml_to_string(elem):
|
||||
return text
|
||||
|
||||
# characters that are valid
|
||||
valid_chars = sets.Set(string.letters + string.digits + " -=_.()/+*^,:?!")
|
||||
valid_chars = set(string.letters + string.digits + " -=_.()/+*^,:?!")
|
||||
|
||||
# characters that are allowed but need to be escaped
|
||||
mapped_chars = { '>' :'__gt__',
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
from mako import exceptions
|
||||
from mako.template import Template
|
||||
from mako.lookup import TemplateLookup
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.web.framework import simplejson
|
||||
from galaxy import web
|
||||
from galaxy.tracks import messages
|
||||
import mimeparse
|
||||
from galaxy.util.json import to_json_string
|
||||
import math
|
||||
|
||||
class MultiResponse(object):
|
||||
"""
|
||||
Shamelessly ripped off of a django snippet.
|
||||
"""
|
||||
def __init__(self, handlers):
|
||||
self.handlers = handlers
|
||||
|
||||
def __call__(self, view_func):
|
||||
def wrapper(that, trans, *args, **kwargs):
|
||||
data_resource = view_func(that, trans, *args, **kwargs)
|
||||
content_type = mimeparse.best_match(self.handlers.keys(),
|
||||
trans.request.environ['HTTP_ACCEPT'])
|
||||
response = self.handlers[content_type](data_resource, trans)
|
||||
trans.response.headers['Content-Type'] = "%s" % content_type
|
||||
return response
|
||||
return wrapper
|
||||
|
||||
@classmethod
|
||||
def JSON( cls, data_resource, trans ):
|
||||
return simplejson.dumps( data_resource )
|
||||
|
||||
class XML( object ):
|
||||
def __call__(self, data_resource, trans ):
|
||||
raise NotImplementedError( "XML MultiResponse handler is not implemented." )
|
||||
|
||||
class AMF( object ):
|
||||
def __call__(self, data_resource, trans ):
|
||||
raise NotImplementedError( "XML MultiResponse handler is not implemented." )
|
||||
|
||||
class HTML( object ):
|
||||
def __init__(self, template ):
|
||||
self.template = template
|
||||
|
||||
def __call__(self, data_resource, trans ):
|
||||
return trans.fill_template( self.template, data_resource=data_resource, trans=trans )
|
||||
|
||||
class WebRoot( BaseController ):
|
||||
|
||||
@web.expose
|
||||
@MultiResponse( {'text/html': MultiResponse.HTML( "tracks/dbkeys.mako"),
|
||||
'text/javascript':MultiResponse.JSON} )
|
||||
def dbkeys(self, trans ):
|
||||
return list(set([x.metadata.dbkey for x in trans.get_history().datasets if not x.deleted]))
|
||||
|
||||
@web.expose
|
||||
@MultiResponse( {'text/html':MultiResponse.HTML( "tracks/chroms.mako" ),
|
||||
'text/javascript':MultiResponse.JSON} )
|
||||
def chroms(self, trans, dbkey=None):
|
||||
return self.chroms_handler( trans, dbkey )
|
||||
|
||||
@web.expose
|
||||
@MultiResponse( {'text/html':MultiResponse.HTML( "tracks/datasets.mako" ),
|
||||
'text/javascript':MultiResponse.JSON} )
|
||||
def list(self, trans, dbkey=None ):
|
||||
trans.session["track_dbkey"] = dbkey
|
||||
trans.session.save()
|
||||
datasets = trans.app.model.HistoryDatasetAssociation.filter_by(deleted=False, history_id=trans.history.id).all()
|
||||
dataset_list = {}
|
||||
for dataset in datasets:
|
||||
if dataset.metadata.dbkey == dbkey and trans.app.datatypes_registry.get_indexers_by_datatype( dataset.extension ):
|
||||
dataset_list[dataset.id] = dataset.name
|
||||
return dataset_list
|
||||
|
||||
@web.expose
|
||||
@MultiResponse( {'text/html':MultiResponse.JSON,
|
||||
'text/javascript':MultiResponse.JSON} )
|
||||
def data(self, trans, dataset_id=None, chr="", low="", high=""):
|
||||
return self.data_handler( trans, dataset_id, chrom=chr, low=low, high=high )
|
||||
|
||||
@web.expose
|
||||
def build( self, trans, **kwargs ):
|
||||
trans.session["track_sets"] = list(kwargs.keys())
|
||||
trans.session.save()
|
||||
waiting = False
|
||||
for id, value in kwargs.items():
|
||||
status = self.data_handler( trans, id )
|
||||
if status == messages.PENDING:
|
||||
waiting = True
|
||||
if not waiting:
|
||||
return trans.response.send_redirect( web.url_for( controller='tracks', action='chroms', dbkey=trans.session["track_dbkey"]) )
|
||||
return trans.fill_template( 'tracks/build.mako' )
|
||||
|
||||
@web.expose
|
||||
def index(self, trans, **kwargs):
|
||||
tracks = []
|
||||
for track in trans.session["track_sets"]:
|
||||
dataset = trans.app.model.HistoryDatasetAssociation.get( track )
|
||||
tracks.append({
|
||||
"type": dataset.datatype.get_track_type(),
|
||||
"name": dataset.name,
|
||||
"id": dataset.id
|
||||
})
|
||||
chrom = kwargs.get("chrom","")
|
||||
LEN = self.chroms_handler(trans, trans.session["track_dbkey"]).get(chrom,0)
|
||||
return trans.fill_template( 'tracks/index.mako',
|
||||
tracks=tracks, chrom=chrom,
|
||||
LEN=LEN )
|
||||
|
||||
def chroms_handler(self, trans, dbkey ):
|
||||
db_manifest = os.path.join( trans.app.config.tool_data_path, 'shared','ucsc','chrom', "%s.len" % dbkey )
|
||||
manifest = {}
|
||||
if os.path.exists( db_manifest ):
|
||||
for line in open( db_manifest ):
|
||||
line = line.rstrip("\r\n")
|
||||
fields = line.split("\t")
|
||||
manifest[fields[0]] = int(fields[1])
|
||||
else:
|
||||
# try to fake a manifest by reading track stores
|
||||
datasets = trans.app.model.HistoryDatasetAssociation.filter_by(deleted=False, history_id=trans.history.id).all()
|
||||
for dataset in datasets:
|
||||
if not dataset.metadata.dbkey == dbkey: continue
|
||||
track_store = trans.app.track_store.get( dataset )
|
||||
if track_store.exists:
|
||||
try:
|
||||
for chrom, fields in track_store.get_manifest().items():
|
||||
manifest[chrom] = max(manifest.get(chrom, 0), int(fields[0]))
|
||||
except track_store.DoesNotExist:
|
||||
pass
|
||||
return manifest
|
||||
|
||||
def data_handler( self, trans, dataset_id, chrom="", low="", high="" ):
|
||||
dataset = trans.app.model.HistoryDatasetAssociation.get( dataset_id )
|
||||
if not dataset: return messages.NO_DATA
|
||||
if dataset.state == trans.app.model.Job.states.ERROR:
|
||||
return messages.NO_DATA
|
||||
if not dataset.state == trans.app.model.Job.states.OK:
|
||||
return messages.PENDING
|
||||
track_store = trans.app.track_store.get( dataset )
|
||||
if not track_store.exists:
|
||||
# Test if we can make a track
|
||||
indexers = trans.app.datatypes_registry.get_indexers_by_datatype( dataset.extension )
|
||||
if indexers:
|
||||
tool = indexers[0] # They are sorted by class chain so use the top one
|
||||
# If we can, return pending and launch job
|
||||
job = trans.app.model.Job()
|
||||
job.session_id = trans.get_galaxy_session().id
|
||||
job.history_id = trans.history.id
|
||||
job.tool_id = tool.id
|
||||
job.tool_version = "1.0.0"
|
||||
job.add_input_dataset( "input_dataset", dataset )
|
||||
job.add_parameter( "input_dataset", to_json_string( dataset.id ) )
|
||||
# This is odd
|
||||
job.add_output_dataset( "input_dataset", dataset )
|
||||
# create store path, this is rather unclear?
|
||||
track_store.set()
|
||||
job.add_parameter( "store_path", to_json_string( track_store.path ) )
|
||||
job.flush()
|
||||
trans.app.job_manager.job_queue.put( job.id, tool )
|
||||
return messages.PENDING
|
||||
else:
|
||||
return messages.NO_DATA
|
||||
else:
|
||||
if chrom and low and high:
|
||||
low = math.floor(float(low))
|
||||
high = math.ceil(float(high))
|
||||
resolution = dataset.datatype.get_track_resolution( dataset, low, high )
|
||||
try:
|
||||
data = track_store.get( chrom, resolution )
|
||||
except track_store.DoesNotExist:
|
||||
# Data for that chromosome or resolution does not exist
|
||||
return messages.NO_DATA
|
||||
window = dataset.datatype.get_track_window( dataset, data, low, high )
|
||||
glob = {"data":window, "type":dataset.datatype.get_track_type()};
|
||||
if resolution: glob["resolution"] = resolution
|
||||
return window
|
||||
else:
|
||||
return messages.DATA
|
||||
@@ -0,0 +1,181 @@
|
||||
"""MIME-Type Parser
|
||||
|
||||
This module provides basic functions for handling mime-types. It can handle
|
||||
matching mime-types against a list of media-ranges. See section 14.1 of
|
||||
the HTTP specification [RFC 2616] for a complete explanation.
|
||||
|
||||
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
|
||||
|
||||
Contents:
|
||||
- parse_mime_type(): Parses a mime-type into its component parts.
|
||||
- parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
|
||||
- quality(): Determines the quality ('q') of a mime-type when compared against a list of media-ranges.
|
||||
- quality_parsed(): Just like quality() except the second parameter must be pre-parsed.
|
||||
- best_match(): Choose the mime-type with the highest quality ('q') from a list of candidates.
|
||||
"""
|
||||
|
||||
__version__ = "0.1.2"
|
||||
__author__ = 'Joe Gregorio'
|
||||
__email__ = "joe@bitworking.org"
|
||||
__credits__ = ""
|
||||
|
||||
def parse_mime_type(mime_type):
|
||||
"""Carves up a mime-type and returns a tuple of the
|
||||
(type, subtype, params) where 'params' is a dictionary
|
||||
of all the parameters for the media range.
|
||||
For example, the media range 'application/xhtml;q=0.5' would
|
||||
get parsed into:
|
||||
|
||||
('application', 'xhtml', {'q', '0.5'})
|
||||
"""
|
||||
parts = mime_type.split(";")
|
||||
params = dict([tuple([s.strip() for s in param.split("=")])\
|
||||
for param in parts[1:] ])
|
||||
full_type = parts[0].strip()
|
||||
# Java URLConnection class sends an Accept header that includes a single "*"
|
||||
# Turn it into a legal wildcard.
|
||||
if full_type == '*': full_type = '*/*'
|
||||
(type, subtype) = full_type.split("/")
|
||||
return (type.strip(), subtype.strip(), params)
|
||||
|
||||
def parse_media_range(range):
|
||||
"""Carves up a media range and returns a tuple of the
|
||||
(type, subtype, params) where 'params' is a dictionary
|
||||
of all the parameters for the media range.
|
||||
For example, the media range 'application/*;q=0.5' would
|
||||
get parsed into:
|
||||
|
||||
('application', '*', {'q', '0.5'})
|
||||
|
||||
In addition this function also guarantees that there
|
||||
is a value for 'q' in the params dictionary, filling it
|
||||
in with a proper default if necessary.
|
||||
"""
|
||||
(type, subtype, params) = parse_mime_type(range)
|
||||
if not params.has_key('q') or not params['q'] or \
|
||||
not float(params['q']) or float(params['q']) > 1\
|
||||
or float(params['q']) < 0:
|
||||
params['q'] = '1'
|
||||
return (type, subtype, params)
|
||||
|
||||
def fitness_and_quality_parsed(mime_type, parsed_ranges):
|
||||
"""Find the best match for a given mime-type against
|
||||
a list of media_ranges that have already been
|
||||
parsed by parse_media_range(). Returns a tuple of
|
||||
the fitness value and the value of the 'q' quality
|
||||
parameter of the best match, or (-1, 0) if no match
|
||||
was found. Just as for quality_parsed(), 'parsed_ranges'
|
||||
must be a list of parsed media ranges. """
|
||||
best_fitness = -1
|
||||
best_fit_q = 0
|
||||
(target_type, target_subtype, target_params) =\
|
||||
parse_media_range(mime_type)
|
||||
for (type, subtype, params) in parsed_ranges:
|
||||
if (type == target_type or type == '*' or target_type == '*') and \
|
||||
(subtype == target_subtype or subtype == '*' or target_subtype == '*'):
|
||||
param_matches = reduce(lambda x, y: x+y, [1 for (key, value) in \
|
||||
target_params.iteritems() if key != 'q' and \
|
||||
params.has_key(key) and value == params[key]], 0)
|
||||
fitness = (type == target_type) and 100 or 0
|
||||
fitness += (subtype == target_subtype) and 10 or 0
|
||||
fitness += param_matches
|
||||
if fitness > best_fitness:
|
||||
best_fitness = fitness
|
||||
best_fit_q = params['q']
|
||||
|
||||
return best_fitness, float(best_fit_q)
|
||||
|
||||
def quality_parsed(mime_type, parsed_ranges):
|
||||
"""Find the best match for a given mime-type against
|
||||
a list of media_ranges that have already been
|
||||
parsed by parse_media_range(). Returns the
|
||||
'q' quality parameter of the best match, 0 if no
|
||||
match was found. This function bahaves the same as quality()
|
||||
except that 'parsed_ranges' must be a list of
|
||||
parsed media ranges. """
|
||||
return fitness_and_quality_parsed(mime_type, parsed_ranges)[1]
|
||||
|
||||
def quality(mime_type, ranges):
|
||||
"""Returns the quality 'q' of a mime-type when compared
|
||||
against the media-ranges in ranges. For example:
|
||||
|
||||
>>> quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
|
||||
0.7
|
||||
|
||||
"""
|
||||
parsed_ranges = [parse_media_range(r) for r in ranges.split(",")]
|
||||
return quality_parsed(mime_type, parsed_ranges)
|
||||
|
||||
def best_match(supported, header):
|
||||
"""Takes a list of supported mime-types and finds the best
|
||||
match for all the media-ranges listed in header. The value of
|
||||
header must be a string that conforms to the format of the
|
||||
HTTP Accept: header. The value of 'supported' is a list of
|
||||
mime-types.
|
||||
|
||||
>>> best_match(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1')
|
||||
'text/xml'
|
||||
"""
|
||||
parsed_header = [parse_media_range(r) for r in header.split(",")]
|
||||
weighted_matches = [(fitness_and_quality_parsed(mime_type, parsed_header), mime_type)\
|
||||
for mime_type in supported]
|
||||
weighted_matches.sort()
|
||||
return weighted_matches[-1][0][1] and weighted_matches[-1][1] or ''
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
|
||||
class TestMimeParsing(unittest.TestCase):
|
||||
|
||||
def test_parse_media_range(self):
|
||||
self.assert_(('application', 'xml', {'q': '1'}) == parse_media_range('application/xml;q=1'))
|
||||
self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml'))
|
||||
self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml;q='))
|
||||
self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml ; q='))
|
||||
self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=1;b=other'))
|
||||
self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=2;b=other'))
|
||||
# Java URLConnection class sends an Accept header that includes a single *
|
||||
self.assertEqual(('*', '*', {'q': '.2'}), parse_media_range(" *; q=.2"))
|
||||
|
||||
def test_rfc_2616_example(self):
|
||||
accept = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"
|
||||
self.assertEqual(1, quality("text/html;level=1", accept))
|
||||
self.assertEqual(0.7, quality("text/html", accept))
|
||||
self.assertEqual(0.3, quality("text/plain", accept))
|
||||
self.assertEqual(0.5, quality("image/jpeg", accept))
|
||||
self.assertEqual(0.4, quality("text/html;level=2", accept))
|
||||
self.assertEqual(0.7, quality("text/html;level=3", accept))
|
||||
|
||||
def test_best_match(self):
|
||||
mime_types_supported = ['application/xbel+xml', 'application/xml']
|
||||
# direct match
|
||||
self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml'), 'application/xbel+xml')
|
||||
# direct match with a q parameter
|
||||
self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml; q=1'), 'application/xbel+xml')
|
||||
# direct match of our second choice with a q parameter
|
||||
self.assertEqual(best_match(mime_types_supported, 'application/xml; q=1'), 'application/xml')
|
||||
# match using a subtype wildcard
|
||||
self.assertEqual(best_match(mime_types_supported, 'application/*; q=1'), 'application/xml')
|
||||
# match using a type wildcard
|
||||
self.assertEqual(best_match(mime_types_supported, '*/*'), 'application/xml')
|
||||
|
||||
mime_types_supported = ['application/xbel+xml', 'text/xml']
|
||||
# match using a type versus a lower weighted subtype
|
||||
self.assertEqual(best_match(mime_types_supported, 'text/*;q=0.5,*/*; q=0.1'), 'text/xml')
|
||||
# fail to match anything
|
||||
self.assertEqual(best_match(mime_types_supported, 'text/html,application/atom+xml; q=0.9'), '')
|
||||
|
||||
# common AJAX scenario
|
||||
mime_types_supported = ['application/json', 'text/html']
|
||||
self.assertEqual(best_match(mime_types_supported, 'application/json, text/javascript, */*'), 'application/json')
|
||||
# verify fitness ordering
|
||||
self.assertEqual(best_match(mime_types_supported, 'application/json, text/html;q=0.9'), 'application/json')
|
||||
|
||||
def test_support_wildcards(self):
|
||||
mime_types_supported = ['image/*', 'application/xml']
|
||||
# match using a type wildcard
|
||||
self.assertEqual(best_match(mime_types_supported, 'image/png'), 'image/*')
|
||||
# match using a wildcard for both requested and supported
|
||||
self.assertEqual(best_match(mime_types_supported, 'image/*'), 'image/*')
|
||||
|
||||
unittest.main()
|
||||
|
After Width: | Height: | Size: 463 B |
|
After Width: | Height: | Size: 434 B |
|
After Width: | Height: | Size: 121 B |
|
After Width: | Height: | Size: 124 B |
|
After Width: | Height: | Size: 339 B |
|
After Width: | Height: | Size: 356 B |
|
After Width: | Height: | Size: 712 B |
|
After Width: | Height: | Size: 111 B |
|
After Width: | Height: | Size: 151 B |
|
After Width: | Height: | Size: 95 B |
|
After Width: | Height: | Size: 120 B |
@@ -0,0 +1,126 @@
|
||||
/*!
|
||||
jquery.event.drag.js ~ v1.4 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
|
||||
Liscensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
|
||||
*/
|
||||
;(function($){ // secure $ jQuery alias
|
||||
/*******************************************************************************************/
|
||||
// Created: 2008-06-04 | Updated: 2009-01-26
|
||||
/*******************************************************************************************/
|
||||
// Events: drag, dragstart, dragend
|
||||
/*******************************************************************************************/
|
||||
|
||||
// jquery method
|
||||
$.fn.drag = function( fn1, fn2, fn3 ){
|
||||
if ( fn2 ) this.bind('dragstart', fn1 ); // 2+ args
|
||||
if ( fn3 ) this.bind('dragend', fn3 ); // 3 args
|
||||
return !fn1 ? this.trigger('drag') // 0 args
|
||||
: this.bind('drag', fn2 ? fn2 : fn1 ); // 1+ args
|
||||
};
|
||||
|
||||
// local refs
|
||||
var $event = $.event, $special = $event.special,
|
||||
|
||||
// special event configuration
|
||||
drag = $special.drag = {
|
||||
not: ':input', // don't begin to drag on event.targets that match this selector
|
||||
distance: 0, // distance dragged before dragstart
|
||||
which: 1, // mouse button pressed to start drag sequence
|
||||
setup: function( data ){
|
||||
data = $.extend({
|
||||
distance: drag.distance,
|
||||
which: drag.which,
|
||||
not: drag.not
|
||||
}, data || {});
|
||||
data.distance = squared( data.distance ); // x² + y² = distance²
|
||||
$event.add( this, "mousedown", handler, data );
|
||||
},
|
||||
teardown: function(){
|
||||
$event.remove( this, "mousedown", handler );
|
||||
if ( this === drag.dragging ) drag.dragging = drag.proxy = null; // deactivate element
|
||||
selectable( this, true ); // enable text selection
|
||||
}
|
||||
};
|
||||
|
||||
// handle drag-releatd DOM events
|
||||
function handler ( event ){
|
||||
var elem = this, returned, data = event.data || {};
|
||||
// mousemove or mouseup
|
||||
if ( data.elem ){
|
||||
// update event properties...
|
||||
elem = event.dragTarget = data.elem; // drag source element
|
||||
event.dragProxy = drag.proxy || elem; // proxy element or source
|
||||
event.cursorOffsetX = data.pageX - data.left; // mousedown offset
|
||||
event.cursorOffsetY = data.pageY - data.top; // mousedown offset
|
||||
event.offsetX = event.pageX - event.cursorOffsetX; // element offset
|
||||
event.offsetY = event.pageY - event.cursorOffsetY; // element offset
|
||||
}
|
||||
// mousedown, check some initial props to avoid the switch statement
|
||||
else if ( drag.dragging || ( data.which>0 && event.which!=data.which ) ||
|
||||
$( event.target ).is( data.not ) ) return;
|
||||
// handle various events
|
||||
switch ( event.type ){
|
||||
// mousedown, left click, event.target is not restricted, init dragging
|
||||
case 'mousedown':
|
||||
$.extend( data, $( elem ).offset(), {
|
||||
elem: elem, target: event.target,
|
||||
pageX: event.pageX, pageY: event.pageY
|
||||
}); // store some initial attributes
|
||||
$event.add( document, "mousemove mouseup", handler, data );
|
||||
selectable( elem, false ); // disable text selection
|
||||
return false; // prevents text selection in safari
|
||||
// mousemove, check distance, start dragging
|
||||
case !drag.dragging && 'mousemove':
|
||||
if ( squared( event.pageX-data.pageX )
|
||||
+ squared( event.pageY-data.pageY ) // x² + y² = distance²
|
||||
< data.distance ) break; // distance tolerance not reached
|
||||
event.target = data.target; // force target from "mousedown" event (fix distance issue)
|
||||
returned = hijack( event, "dragstart", elem ); // trigger "dragstart", return proxy element
|
||||
if ( returned !== false ){ // "dragstart" not rejected
|
||||
drag.dragging = elem; // activate element
|
||||
drag.proxy = event.dragProxy = $( returned || elem )[0]; // set proxy
|
||||
}
|
||||
// mousemove, dragging
|
||||
case 'mousemove':
|
||||
if ( drag.dragging ){
|
||||
returned = hijack( event, "drag", elem ); // trigger "drag"
|
||||
if ( $special.drop ){ // manage drop events
|
||||
$special.drop.allowed = ( returned !== false ); // prevent drop
|
||||
$special.drop.handler( event ); // "dropstart", "dropend"
|
||||
}
|
||||
if ( returned !== false ) break; // "drag" not rejected, stop
|
||||
event.type = "mouseup"; // helps "drop" handler behave
|
||||
}
|
||||
// mouseup, stop dragging
|
||||
case 'mouseup':
|
||||
$event.remove( document, "mousemove mouseup", handler ); // remove page events
|
||||
if ( drag.dragging ){
|
||||
if ( $special.drop ) $special.drop.handler( event ); // "drop"
|
||||
hijack( event, "dragend", elem ); // trigger "dragend"
|
||||
}
|
||||
selectable( elem, true ); // enable text selection
|
||||
drag.dragging = drag.proxy = data.elem = null; // deactivate element
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// set event type to custom value, and handle it
|
||||
function hijack ( event, type, elem ){
|
||||
event.type = type; // force the event type
|
||||
var result = $event.handle.call( elem, event );
|
||||
return result===false ? false : result || event.result;
|
||||
};
|
||||
|
||||
// return the value squared
|
||||
function squared ( value ){ return Math.pow( value, 2 ); };
|
||||
|
||||
// toggles text selection attributes
|
||||
function selectable ( elem, bool ){
|
||||
if ( !elem ) return; // maybe element was removed ?
|
||||
elem.unselectable = bool ? "off" : "on"; // IE
|
||||
elem.onselectstart = function(){ return bool; }; // IE
|
||||
if ( document.selection && document.selection.empty ) document.selection.empty(); // IE
|
||||
if ( elem.style ) elem.style.MozUserSelect = bool ? "" : "none"; // FF
|
||||
};
|
||||
|
||||
/*******************************************************************************************/
|
||||
})( jQuery ); // confine scope
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
jquery.event.drag.js ~ v1.4 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
|
||||
Liscensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
|
||||
*/
|
||||
(function(H){H.fn.drag=function(K,J,I){if(J){this.bind("dragstart",K)}if(I){this.bind("dragend",I)}return !K?this.trigger("drag"):this.bind("drag",J?J:K)};var D=H.event,B=D.special,F=B.drag={not:":input",distance:0,which:1,setup:function(I){I=H.extend({distance:F.distance,which:F.which,not:F.not},I||{});I.distance=G(I.distance);D.add(this,"mousedown",E,I)},teardown:function(){D.remove(this,"mousedown",E);if(this===F.dragging){F.dragging=F.proxy=null}C(this,true)}};function E(K){var J=this,I,L=K.data||{};if(L.elem){J=K.dragTarget=L.elem;K.dragProxy=F.proxy||J;K.cursorOffsetX=L.pageX-L.left;K.cursorOffsetY=L.pageY-L.top;K.offsetX=K.pageX-K.cursorOffsetX;K.offsetY=K.pageY-K.cursorOffsetY}else{if(F.dragging||(L.which>0&&K.which!=L.which)||H(K.target).is(L.not)){return }}switch(K.type){case"mousedown":H.extend(L,H(J).offset(),{elem:J,target:K.target,pageX:K.pageX,pageY:K.pageY});D.add(document,"mousemove mouseup",E,L);C(J,false);return false;case !F.dragging&&"mousemove":if(G(K.pageX-L.pageX)+G(K.pageY-L.pageY)<L.distance){break}K.target=L.target;I=A(K,"dragstart",J);if(I!==false){F.dragging=J;F.proxy=K.dragProxy=H(I||J)[0]}case"mousemove":if(F.dragging){I=A(K,"drag",J);if(B.drop){B.drop.allowed=(I!==false);B.drop.handler(K)}if(I!==false){break}K.type="mouseup"}case"mouseup":D.remove(document,"mousemove mouseup",E);if(F.dragging){if(B.drop){B.drop.handler(K)}A(K,"dragend",J)}C(J,true);F.dragging=F.proxy=L.elem=null;break}}function A(L,J,K){L.type=J;var I=D.handle.call(K,L);return I===false?false:I||L.result}function G(I){return Math.pow(I,2)}function C(J,I){if(!J){return }J.unselectable=I?"off":"on";J.onselectstart=function(){return I};if(document.selection&&document.selection.empty){document.selection.empty()}if(J.style){J.style.MozUserSelect=I?"":"none"}}})(jQuery);
|
||||
@@ -0,0 +1,353 @@
|
||||
var DENSITY = 1000;
|
||||
|
||||
var View = function( chr, length, low, high ) {
|
||||
this.chr = chr;
|
||||
this.length = length;
|
||||
this.low = low;
|
||||
this.high = high;
|
||||
};
|
||||
$.extend( View.prototype, {
|
||||
move: function ( new_low, new_high ) {
|
||||
this.low = Math.max( 0, Math.floor( new_low ) );
|
||||
this.high = Math.min( this.length, Math.ceil( new_high ) );
|
||||
},
|
||||
zoom_in: function ( factor ) {
|
||||
var center = ( this.low + this.high ) / 2;
|
||||
var range = this.high - this.low;
|
||||
var diff = range / factor / 2;
|
||||
this.low = Math.floor( center - diff );
|
||||
this.high = Math.ceil( center + diff );
|
||||
if (this.high - this.low < 1 ) {
|
||||
this.high = this.low + 1;
|
||||
}
|
||||
},
|
||||
zoom_out: function ( factor ) {
|
||||
var center = ( this.low + this.high ) / 2;
|
||||
var range = this.high - this.low;
|
||||
var diff = range * factor / 2;
|
||||
this.low = Math.floor( Math.max( 0, center - diff ) );
|
||||
this.high = Math.ceil( Math.min( this.length, center + diff ) );
|
||||
},
|
||||
left: function( factor ) {
|
||||
var range = this.high - this.low;
|
||||
var diff = Math.floor( range / factor );
|
||||
if ( this.low - diff < 0 ) {
|
||||
this.low = 0;
|
||||
this.high = this.low + range;
|
||||
} else {
|
||||
this.low -= diff;
|
||||
this.high -= diff;
|
||||
}
|
||||
},
|
||||
right: function ( factor ) {
|
||||
var range = this.high - this.low;
|
||||
var diff = Math.floor( range / factor );
|
||||
if ( this.high + diff > this.length ) {
|
||||
this.high = this.length;
|
||||
this.low = this.high - range;
|
||||
} else {
|
||||
this.low += diff;
|
||||
this.high += diff;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var Track = function ( name, view, parent_element ) {
|
||||
this.name = name;
|
||||
this.view = view;
|
||||
this.parent_element = parent_element;
|
||||
this.make_container();
|
||||
};
|
||||
$.extend( Track.prototype, {
|
||||
make_container : function () {
|
||||
this.header_div = $("<div class='track-header'>");
|
||||
this.header_div.text( this.name );
|
||||
this.content_div = $("<div class='track-content'>");
|
||||
this.container_div = $("<div class='track'></div>");
|
||||
this.container_div.append( this.header_div );
|
||||
this.container_div.append( this.content_div );
|
||||
this.parent_element.append( this.container_div );
|
||||
}
|
||||
});
|
||||
|
||||
var TiledTrack = function( name, view, parent_element ) {
|
||||
Track.call( this, name, view, parent_element );
|
||||
// For caching
|
||||
this.last_resolution = null;
|
||||
this.last_w_scale = null;
|
||||
this.tile_cache = {};
|
||||
};
|
||||
$.extend( TiledTrack.prototype, Track.prototype, {
|
||||
draw: function() {
|
||||
var low = this.view.low,
|
||||
high = this.view.high,
|
||||
range = high - low;
|
||||
|
||||
var resolution = Math.pow( 10, Math.ceil( Math.log( range / DENSITY ) / Math.log( 10 ) ) );
|
||||
resolution = Math.max( resolution, 1 );
|
||||
resolution = Math.min( resolution, 10000 );
|
||||
|
||||
var parent_element = $("<div style='position: relative;'></div>");
|
||||
this.content_div.children( ":first" ).remove();
|
||||
this.content_div.append( parent_element );
|
||||
|
||||
var w = this.content_div.width(),
|
||||
h = this.content_div.height(),
|
||||
w_scale = w / range,
|
||||
old_tiles = {},
|
||||
new_tiles = {};
|
||||
|
||||
// If resolution and scale are unchanged, try to reuse tiles
|
||||
if ( this.last_resolution == resolution && this.last_w_scale == w_scale ) {
|
||||
old_tiles = this.tile_cache;
|
||||
}
|
||||
|
||||
var tile_element;
|
||||
// Index of first tile that overlaps visible region
|
||||
var tile_index = Math.floor( low / resolution / DENSITY );
|
||||
var max_height = 0;
|
||||
while ( ( tile_index * 1000 * resolution ) < high ) {
|
||||
// Check in cache
|
||||
if ( tile_index in old_tiles ) {
|
||||
// console.log( "tile from cache" );
|
||||
tile_element = old_tiles[tile_index];
|
||||
var tile_low = tile_index * DENSITY * resolution;
|
||||
tile_element.css( {
|
||||
left: ( tile_low - this.view.low ) * w_scale
|
||||
});
|
||||
// Our responsibility to move the element to the new parent
|
||||
parent_element.append( tile_element );
|
||||
} else {
|
||||
// console.log( "new tile" );
|
||||
tile_element = this.draw_tile( resolution, tile_index, parent_element, w_scale, h );
|
||||
}
|
||||
if ( tile_element ) {
|
||||
new_tiles[tile_index] = tile_element;
|
||||
max_height = Math.max( max_height, tile_element.height() );
|
||||
}
|
||||
tile_index += 1;
|
||||
}
|
||||
|
||||
parent_element.css( "height", max_height );
|
||||
|
||||
this.last_resolution = resolution;
|
||||
this.last_w_scale = w_scale;
|
||||
this.tile_cache = new_tiles;
|
||||
}
|
||||
});
|
||||
|
||||
var DataCache = function( type, track, view ) {
|
||||
this.type = type;
|
||||
this.track = track;
|
||||
this.view = view;
|
||||
this.cache = Object();
|
||||
};
|
||||
$.extend( DataCache.prototype, {
|
||||
get: function( resolution, position ) {
|
||||
var cache = this.cache;
|
||||
if ( ! ( cache[resolution] && cache[resolution][position] ) ) {
|
||||
if ( ! cache[resolution] ) {
|
||||
cache[resolution] = Object();
|
||||
}
|
||||
var low = position * DENSITY * resolution;
|
||||
var high = ( position + 1 ) * DENSITY * resolution;
|
||||
cache[resolution][position] = { state: "loading" };
|
||||
$.getJSON( "data" + this.type, { chr: this.view.chr, low: low, high: high, dataset_id: this.track.dataset_id }, function ( data ) {
|
||||
cache[resolution][position] = { state: "loaded", values: data };
|
||||
$(document).trigger( "redraw" );
|
||||
});
|
||||
}
|
||||
return cache[resolution][position];
|
||||
}
|
||||
});
|
||||
|
||||
var LineTrack = function ( name, view, parent_element, dataset_id ) {
|
||||
Track.call( this, name, view, parent_element );
|
||||
this.container_div.addClass( "line-track" );
|
||||
this.dataset_id = dataset_id;
|
||||
this.cache = new DataCache( "", this, view );
|
||||
};
|
||||
$.extend( LineTrack.prototype, TiledTrack.prototype, {
|
||||
make_container: function () {
|
||||
Track.prototype.make_container.call( this );
|
||||
this.content_div.css( "height", 100 );
|
||||
},
|
||||
draw_tile: function( resolution, tile_index, parent_element, w_scale, h_scale ) {
|
||||
var tile_low = tile_index * DENSITY * resolution,
|
||||
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
|
||||
tile_length = DENSITY * resolution;
|
||||
var chunk = this.cache.get( resolution, tile_index );
|
||||
var element;
|
||||
if ( chunk.state == "loading" ) {
|
||||
element = $("<div class='loading tile'></div>");
|
||||
} else {
|
||||
element = $("<canvas class='tile'></canvas>");
|
||||
}
|
||||
element.css( {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: ( tile_low - this.view.low ) * w_scale,
|
||||
width: Math.ceil( tile_length * w_scale ),
|
||||
height: 100
|
||||
});
|
||||
parent_element.append( element );
|
||||
// Chunk is still loading, do noting
|
||||
if ( chunk.state == "loading" ) {
|
||||
in_path = false;
|
||||
return null;
|
||||
}
|
||||
var canvas = element;
|
||||
canvas.get(0).width = canvas.width();
|
||||
canvas.get(0).height = canvas.height();
|
||||
var ctx = canvas.get(0).getContext("2d");
|
||||
var in_path = false;
|
||||
ctx.beginPath();
|
||||
var data = chunk.values;
|
||||
for ( var i = 0; i < data.length - 1; i++ ) {
|
||||
var x1 = data[i][0] - tile_low;
|
||||
var y1 = data[i][1];
|
||||
var x2 = data[i+1][0] - tile_low;
|
||||
var y2 = data[i+1][1];
|
||||
// Missing data causes us to stop drawing
|
||||
if ( isNaN( y1 ) || isNaN( y2 ) ) {
|
||||
in_path = false;
|
||||
} else {
|
||||
// Translate
|
||||
x1 = x1 * w_scale;
|
||||
x2 = x2 * w_scale;
|
||||
y1 = h_scale - y1 * ( h_scale );
|
||||
y2 = h_scale - y2 * ( h_scale );
|
||||
if ( in_path ) {
|
||||
ctx.lineTo( x1, y1, x2, y2 );
|
||||
} else {
|
||||
ctx.moveTo( x1, y1, x2, y2 );
|
||||
in_path = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
return element;
|
||||
}
|
||||
});
|
||||
|
||||
var LabelTrack = function ( view, parent_element ) {
|
||||
Track.call( this, null, view, parent_element );
|
||||
this.container_div.addClass( "label-track" );
|
||||
};
|
||||
$.extend( LabelTrack.prototype, Track.prototype, {
|
||||
draw: function() {
|
||||
var view = this.view,
|
||||
range = view.high - view.low,
|
||||
tickDistance = Math.floor( Math.pow( 10, Math.floor( Math.log( range ) / Math.log( 10 ) ) ) ),
|
||||
position = Math.floor( view.low / tickDistance ) * tickDistance,
|
||||
width = this.content_div.width(),
|
||||
new_div = $("<div style='position: relative; height: 1.3em;'></div>");
|
||||
while ( position < view.high ) {
|
||||
var screenPosition = ( position - view.low ) / range * width;
|
||||
new_div.append( $("<div class='label'>" + position + "</div>").css( {
|
||||
position: "absolute",
|
||||
// Reduce by one to account for border
|
||||
left: screenPosition - 1
|
||||
}) );
|
||||
position += tickDistance;
|
||||
}
|
||||
this.content_div.children( ":first" ).remove();
|
||||
this.content_div.append( new_div );
|
||||
}
|
||||
});
|
||||
|
||||
var itemHeight = 13,
|
||||
itemPad = 3,
|
||||
thinHeight = 7,
|
||||
thinOffset = 3;
|
||||
|
||||
var FeatureTrack = function ( name, view, parent_element,dataset_id ) {
|
||||
Track.call( this, name, view, parent_element );
|
||||
this.container_div.addClass( "feature-track" );
|
||||
this.dataset_id = dataset_id;
|
||||
this.cache = new DataCache( "", this, view );
|
||||
};
|
||||
$.extend( FeatureTrack.prototype, TiledTrack.prototype, {
|
||||
get_data_async: function() {
|
||||
var track = this;
|
||||
$.getJSON( "data", { chr: this.view.chr, dataset_id: this.dataset_id }, function ( data ) {
|
||||
track.values = data;
|
||||
track.draw();
|
||||
});
|
||||
},
|
||||
draw_tile: function( resolution, tile_index, parent_element, w_scale, h_scale ) {
|
||||
var tile_low = tile_index * DENSITY * resolution,
|
||||
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
|
||||
tile_length = DENSITY * resolution;
|
||||
|
||||
var view = this.view,
|
||||
range = view.high - view.low,
|
||||
width = this.content_div.width(),
|
||||
slots = [],
|
||||
new_div = $("<div class='tile' style='position: relative;'></div>");
|
||||
|
||||
var chunk = this.cache.get( resolution, tile_index );
|
||||
if ( chunk.state == "loading" ) {
|
||||
return null;
|
||||
}
|
||||
var values = chunk.values;
|
||||
|
||||
for ( var index in values ) {
|
||||
var value = values[index];
|
||||
var start = value[1], end = value[2], strand = value[5];
|
||||
// Determine slot based on entire feature and label
|
||||
var screenStart = ( start - tile_low ) * w_scale;
|
||||
var screenEnd = ( end - tile_low ) * w_scale;
|
||||
var screenWidth = screenEnd - screenStart;
|
||||
var screenStartWithLabel = screenStart;
|
||||
// Determine slot
|
||||
var slot = slots.length;
|
||||
for ( i in slots ) {
|
||||
if ( slots[i] < screenStartWithLabel ) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
slots[slot] = Math.ceil( screenEnd );
|
||||
var feature_div = $("<div class='feature'></div>").css( {
|
||||
position: 'absolute',
|
||||
left: screenStart,
|
||||
top: (slot*(itemHeight+itemPad)),
|
||||
height: itemHeight,
|
||||
width: Math.max( screenWidth, 1 )
|
||||
});
|
||||
new_div.append( feature_div );
|
||||
}
|
||||
new_div.css( {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: ( tile_low - this.view.low ) * w_scale,
|
||||
width: Math.ceil( tile_length * w_scale ),
|
||||
height: slots.length * ( itemHeight + itemPad ) + itemPad
|
||||
});
|
||||
parent_element.append( new_div );
|
||||
return new_div;
|
||||
}
|
||||
});
|
||||
|
||||
var TrackLayout = function ( view ) {
|
||||
this.view = view;
|
||||
this.tracks = [];
|
||||
};
|
||||
$.extend( TrackLayout.prototype, {
|
||||
add: function ( track ) {
|
||||
this.tracks.push( track );
|
||||
},
|
||||
redraw : function () {
|
||||
for ( var index in this.tracks ) {
|
||||
this.tracks[index].draw();
|
||||
}
|
||||
// Overview
|
||||
$("#overview-box").css( {
|
||||
left: ( this.view.low / this.view.length ) * $("#overview-viewport").width(),
|
||||
width: Math.max( 1, ( ( this.view.high - this.view.low ) / this.view.length ) * $("#overview-viewport").width() )
|
||||
}).show();
|
||||
$("#low").text( this.view.low );
|
||||
$("#high").text( this.view.high );
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
body {
|
||||
margin: 4em 0;
|
||||
padding: 0;
|
||||
font-family: verdana;
|
||||
font-size: 75%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#center {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#nav {
|
||||
padding: 1em;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background: rgb( 64, 64, 64 );
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#nav > div
|
||||
{
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
#nav-controls a {
|
||||
color: white;
|
||||
padding: 0.1em 0.4em;
|
||||
margin: 0 0.1em;
|
||||
text-decoration: none;
|
||||
background: black;
|
||||
-webkit-border-radius: 1em;
|
||||
-moz-border-radius: 1em;
|
||||
}
|
||||
|
||||
#overview {
|
||||
width: 100%;
|
||||
padding: 1em 0;
|
||||
background: rgb( 64, 64, 64 );
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#overview-viewport {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
border-top: solid gray 1px;
|
||||
border-bottom: solid gray 1px;
|
||||
}
|
||||
#overview-box {
|
||||
position: absolute;
|
||||
height: 10px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#viewport {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
#viewport-canvas {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.track {
|
||||
border-top: solid gray 1px;
|
||||
border-bottom: solid gray 1px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.track-header {
|
||||
text-align: center;
|
||||
padding: 0.1 0 0.3em 0;
|
||||
}
|
||||
|
||||
.track-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background: #DDDDDD;
|
||||
}
|
||||
|
||||
.label-track .label {
|
||||
border-left: solid grey 1px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.feature-track .feature {
|
||||
background: brown;
|
||||
}
|
||||
|
||||
.feature-track .feature .forward {
|
||||
|
||||
}
|
||||
|
||||
.feature-track .feature .reverse {
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
self.overlay_visible=False
|
||||
self.message_box_class=""
|
||||
self.active_view=None
|
||||
self.body_class=""
|
||||
%>
|
||||
|
||||
<%def name="init()">
|
||||
@@ -103,6 +104,8 @@
|
||||
<span class="${cls}" style="${style}"><a target="${target}" href="${href}">${display}</a></span>
|
||||
</%def>
|
||||
|
||||
${tab( "tracks", "View Data", h.url_for( controller='tracks', action='dbkeys' ), target="galaxy_main")}
|
||||
|
||||
${tab( "analysis", "Analyze Data", h.url_for( controller='root', action='index' ))}
|
||||
|
||||
${tab( "workflow", "Workflow", h.url_for( controller='workflow', action='index' ))}
|
||||
@@ -209,7 +212,7 @@
|
||||
${self.stylesheets()}
|
||||
</head>
|
||||
|
||||
<body scroll="no">
|
||||
<body scroll="no" class="${self.body_class}">
|
||||
## Background displays first
|
||||
<div id="background"></div>
|
||||
## Layer iframes over backgrounds
|
||||
@@ -240,7 +243,6 @@
|
||||
</div>
|
||||
%endif
|
||||
## Allow other body level elements
|
||||
${next.body()}
|
||||
</body>
|
||||
## Scripts can be loaded later since they progressively add features to
|
||||
## the panels, but do not change layout
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
|
||||
<%def name="init()">
|
||||
<%
|
||||
self.active_view="view"
|
||||
self.has_left_panel=False
|
||||
%>
|
||||
</%def>
|
||||
|
||||
<script type="text/javascript">
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 5000 );
|
||||
</script>
|
||||
|
||||
<div class="donemessage">
|
||||
<p>
|
||||
Please wait while we index your tracks for viewing. You will be
|
||||
automatically redirected to choose a chromosome to view after indices
|
||||
are built.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
|
||||
<%def name="init()">
|
||||
<%
|
||||
self.active_view="view"
|
||||
self.has_left_panel=False
|
||||
%>
|
||||
</%def>
|
||||
|
||||
<div class="form">
|
||||
<div class="form-title">Select Chromosome/Contig/Scaffold/etc.</div>
|
||||
<div id="dbkey" class="form-body">
|
||||
<form action="/tracks/index" method="GET" target="_parent">
|
||||
<div class="form-row">
|
||||
<label for="dbkey">Chrom: </label>
|
||||
<div class="form-row-input">
|
||||
<select name="chrom" id="chrom">
|
||||
%for chrom in data_resource:
|
||||
<option value="${chrom}">${chrom}</option>
|
||||
%endfor
|
||||
</select>
|
||||
</div>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="submit" value="View" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
|
||||
<%def name="init()">
|
||||
<%
|
||||
self.active_view="view"
|
||||
self.has_left_panel=False
|
||||
%>
|
||||
</%def>
|
||||
|
||||
<div class="form">
|
||||
<div class="form-title">Select Datasets to View</div>
|
||||
<div id="dbkey" class="form-body">
|
||||
<form action="/tracks/build" method="GET">
|
||||
%for key,value in data_resource.items():
|
||||
<div class="form-row">
|
||||
<label for="${key}">${value}</label>
|
||||
<div class="form-row-input">
|
||||
<input type="checkbox" name="${key}" />
|
||||
</div>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
%endfor
|
||||
<div class="form-row">
|
||||
<input type="submit" value="Build..." />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
|
||||
<%def name="init()">
|
||||
<%
|
||||
self.active_view="view"
|
||||
self.has_left_panel=False
|
||||
%>
|
||||
</%def>
|
||||
|
||||
<div class="form">
|
||||
<div class="form-title">Select DBKey</div>
|
||||
<div id="dbkey" class="form-body">
|
||||
<form action="/tracks/list" method="GET">
|
||||
<div class="form-row">
|
||||
<label for="dbkey">DBKey: </label>
|
||||
<div class="form-row-input">
|
||||
<select name="dbkey" id="dbkey">
|
||||
%for dbkey in data_resource:
|
||||
<option value="${dbkey}">${dbkey}</option>
|
||||
%endfor
|
||||
</select>
|
||||
</div>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="submit" value="Select Datasets..."/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
${data_resource}
|
||||
@@ -0,0 +1,111 @@
|
||||
<%inherit file="/base_panels.mako"/>
|
||||
|
||||
<%def name="init()">
|
||||
<%
|
||||
self.active_view="tracks"
|
||||
self.has_left_panel=False
|
||||
self.has_right_panel=False
|
||||
%>
|
||||
</%def>
|
||||
|
||||
<%def name="stylesheets()">
|
||||
${parent.stylesheets()}
|
||||
<link rel="stylesheet" type="text/css" href="/static/trackster.css" />
|
||||
</%def>
|
||||
|
||||
<%def name="late_javascripts()">
|
||||
${parent.late_javascripts()}
|
||||
<script type="text/javascript" src="/static/scripts/jquery.event.drag.js"></script>
|
||||
<script type="text/javascript" src="/static/scripts/trackster.js"></script>
|
||||
<script>
|
||||
|
||||
var view = new View( "${chrom}", ${LEN}, 0, ${LEN} );
|
||||
var tracks = new TrackLayout( view );
|
||||
|
||||
$(function() {
|
||||
|
||||
tracks.add( new LabelTrack( view, $("#viewport" ) ) );
|
||||
%for track in tracks:
|
||||
tracks.add( new ${track["type"]}( "${track["name"]}", view, $("#viewport" ), ${track["id"]} ) );
|
||||
%endfor
|
||||
|
||||
$(document).bind( "redraw", function( e ) {
|
||||
tracks.redraw();
|
||||
});
|
||||
|
||||
$(window).resize( function( e ) {
|
||||
tracks.redraw();
|
||||
});
|
||||
|
||||
$("#viewport").bind( "dragstart", function ( e ) {
|
||||
this.original_low = view.low;
|
||||
}).bind( "drag", function( e ) {
|
||||
var move_amount = ( e.offsetX - this.offsetLeft ) / this.offsetWidth;
|
||||
var range = view.high - view.low;
|
||||
var move_bases = Math.round( range * move_amount );
|
||||
var new_low = this.original_low - move_bases;
|
||||
if ( new_low < 0 ) {
|
||||
new_low = 0;
|
||||
}
|
||||
var new_high = new_low + range;
|
||||
if ( new_high > view.length ) {
|
||||
new_high = view.length;
|
||||
new_low = new_high - range;
|
||||
}
|
||||
view.low = new_low;
|
||||
view.high = new_high;
|
||||
tracks.redraw();
|
||||
});
|
||||
|
||||
tracks.redraw();
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="center_panel()">
|
||||
<div id="content">
|
||||
|
||||
<div id="overview">
|
||||
<div id="overview-viewport">
|
||||
<div id="overview-box"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="viewport">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="nav">
|
||||
|
||||
<div id="nav-controls">
|
||||
<a href="#" onclick="javascript:view.left(5);tracks.redraw();"><<</a>
|
||||
<a href="#" onclick="javascript:view.left(2);tracks.redraw();"><</a>
|
||||
|
||||
<span style="display: inline-block; width: 30em; text-align: center;">Viewing ${chrom}:<span id="low">0</span>-<span id="high">180857866</span></span>
|
||||
|
||||
<span style="display: inline-block; width: 10em;">
|
||||
<a href="#" onclick="javascript:view.zoom_in(2);tracks.redraw();">+</a>
|
||||
<a href="#" onclick="javascript:view.zoom_out(2);tracks.redraw();">-</a>
|
||||
</span>
|
||||
|
||||
<a href="#" onclick="javascript:view.right(2);tracks.redraw();">></a>
|
||||
<a href="#" onclick="javascript:view.right(5);tracks.redraw();">>></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="right_panel()">
|
||||
<div class="unified-panel-header" unselectable="on">
|
||||
<div class="unified-panel-header-inner">
|
||||
<div style="float: right">
|
||||
<a class='panel-header-button' href="${h.url_for( controller='root', action='history_options' )}" target="galaxy_main"><span>Options</span></a>
|
||||
</div>
|
||||
<div class="panel-header-text">History</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unified-panel-body" style="overflow: hidden;">
|
||||
<iframe name="galaxy_history" width="100%" height="100%" frameborder="0" style="position: absolute; margin: 0; border: 0 none; height: 100%;" src="${h.url_for( controller='root', action='history' )}"></iframe>
|
||||
</div>
|
||||
</%def>
|
||||
@@ -0,0 +1,88 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/s/css/trackster.css" />
|
||||
|
||||
<script type="text/javascript" src="/static/scripts/jquery.js"></script>
|
||||
<script type="text/javascript" src="/static/scripts/jquery.event.drag.js"></script>
|
||||
<script type="text/javascript" src="/static/scripts/trackster.js"></script>
|
||||
<script>
|
||||
|
||||
var view = new View( "chr5", 180857866, 0, 180857866 );
|
||||
var tracks = new TrackLayout( view );
|
||||
|
||||
$(function() {
|
||||
|
||||
tracks.add( new LabelTrack( view, $("#viewport" ) ) );
|
||||
tracks.add( new LineTrack( "phastCons44way", view, $("#viewport" ) ) );
|
||||
tracks.add( new FeatureTrack( "knownGene", view, $("#viewport" ) ) );
|
||||
|
||||
$(document).bind( "redraw", function( e ) {
|
||||
tracks.redraw();
|
||||
});
|
||||
|
||||
$(window).resize( function( e ) {
|
||||
tracks.redraw();
|
||||
});
|
||||
|
||||
$("#viewport").bind( "dragstart", function ( e ) {
|
||||
this.original_low = view.low;
|
||||
}).bind( "drag", function( e ) {
|
||||
var move_amount = ( e.offsetX - this.offsetLeft ) / this.offsetWidth;
|
||||
var range = view.high - view.low;
|
||||
var move_bases = Math.round( range * move_amount );
|
||||
var new_low = this.original_low - move_bases;
|
||||
if ( new_low < 0 ) {
|
||||
new_low = 0;
|
||||
}
|
||||
var new_high = new_low + range;
|
||||
if ( new_high > view.length ) {
|
||||
new_high = view.length;
|
||||
new_low = new_high - range;
|
||||
}
|
||||
view.low = new_low;
|
||||
view.high = new_high;
|
||||
tracks.redraw();
|
||||
});
|
||||
|
||||
tracks.redraw();
|
||||
});
|
||||
</script>
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
|
||||
<div id="overview">
|
||||
<div id="overview-viewport">
|
||||
<div id="overview-box"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="nav">
|
||||
|
||||
<div id="nav-controls">
|
||||
<a href="#" onclick="javascript:view.left(5);tracks.redraw();"><<</a>
|
||||
<a href="#" onclick="javascript:view.left(2);tracks.redraw();"><</a>
|
||||
|
||||
<span style="display: inline-block; width: 30em; text-align: center;">Viewing chr5:<span id="low">0</span>-<span id="high">180857866</span></span>
|
||||
|
||||
<span style="display: inline-block; width: 10em;">
|
||||
<a href="#" onclick="javascript:view.zoom_in(2);tracks.redraw();">+</a>
|
||||
<a href="#" onclick="javascript:view.zoom_out(2);tracks.redraw();">-</a>
|
||||
</span>
|
||||
|
||||
<a href="#" onclick="javascript:view.right(2);tracks.redraw();">></a>
|
||||
<a href="#" onclick="javascript:view.right(5);tracks.redraw();">>></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="viewport">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
#! /usr/bin/perl -w
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@@ -40,6 +40,8 @@ tool_data_path = tool-data
|
||||
datatype_converters_config_file = datatype_converters_conf.xml
|
||||
datatype_converters_path = %(here)s/lib/galaxy/datatypes/converters
|
||||
|
||||
# Datatype indexers
|
||||
datatype_indexers_path = %(here)s/lib/galaxy/datatypes/indexers
|
||||
# Metadata
|
||||
set_metadata_externally = False
|
||||
|
||||
|
||||