Collapsed changeset: Trackster plugged into galaxy with behind-the-scenes indexing of Wiggle, coverage and interval datasets.

This commit is contained in:
Ian Schenck
2009-03-11 00:29:01 +00:00
parent 20c20ef3be
commit 985f7c63fa
52 changed files with 11045 additions and 1172 deletions
+25 -19
View File
@@ -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
+7 -2
View File
@@ -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"/>
+2
View File
@@ -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
+7 -1
View File
@@ -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:
+2
View File
@@ -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 )
+39
View File
@@ -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"
+4
View File
@@ -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):
+81
View File
@@ -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&gt;&amp;1
</command>
<inputs>
<page>
<param format="coverage" name="input_dataset" type="data" label="Choose coverage"/>
</page>
</inputs>
<help>
</help>
</tool>
+67
View File
@@ -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&gt;&amp;1
</command>
<inputs>
<page>
<param format="interval" name="input_dataset" type="data" label="Choose intervals"/>
</page>
</inputs>
<help>
</help>
</tool>
+72
View File
@@ -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()
+12
View File
@@ -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&gt;&amp;1
</command>
<inputs>
<page>
<param format="wiggle" name="input_dataset" type="data" label="Choose wiggle"/>
</page>
</inputs>
<help>
</help>
</tool>
+58
View File
@@ -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"
+36 -1
View File
@@ -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 )
+2
View File
@@ -0,0 +1,2 @@
1 2
3 4
View File
+4
View File
@@ -0,0 +1,4 @@
PENDING = "pending"
NO_DATA = "no data"
NO_CHROMOSOME = "no chromosome"
DATA = "data"
+75
View File
@@ -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
+15 -4
View File
@@ -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__',
+178
View File
@@ -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
+181
View File
@@ -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()
Regular → Executable
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

+126
View File
@@ -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
Regular → Executable
+8970 -1140
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -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);
File diff suppressed because one or more lines are too long
+273 -1
View File
File diff suppressed because one or more lines are too long
+353
View File
@@ -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 );
}
});
+106
View File
@@ -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 {
}
+4 -2
View File
@@ -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
+22
View File
@@ -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>
+30
View File
@@ -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>
+28
View File
@@ -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>
+30
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
${data_resource}
+111
View File
@@ -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();">&lt;&lt;</a>
<a href="#" onclick="javascript:view.left(2);tracks.redraw();">&lt;</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();">&gt;</a>
<a href="#" onclick="javascript:view.right(5);tracks.redraw();">&gt;&gt;</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>
+88
View File
@@ -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();">&lt;&lt;</a>
<a href="#" onclick="javascript:view.left(2);tracks.redraw();">&lt;</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();">&gt;</a>
<a href="#" onclick="javascript:view.right(5);tracks.redraw();">&gt;&gt;</a>
</div>
</div>
<div id="viewport">
</div>
</div>
</body>
</html>
+1 -1
View File
@@ -1,4 +1,4 @@
#! /usr/bin/perl -w
#!/usr/bin/perl -w
use strict;
use warnings;
+2
View File
@@ -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