From 5962f0ab2e46b1facbb89f8fe1e3d2baf0541a4f Mon Sep 17 00:00:00 2001 From: Kanwei Li Date: Fri, 10 Dec 2010 14:59:43 -0500 Subject: [PATCH] trackster: Add bigWig display to trackster. Automatically converts wig to bigwig if needed (NOTE: datatypes_conf.xml.sample has been edited to add the new converter, you must update datatypes_conf.xml to use it). The converter requires that wigToBigWig be in the PATH, but no other tools are needed to view bigwig files as they are provided by bx_python. - Fix track preferences not being applied - Fix chroms not being selectable when a new track browser is created - Fix ReferenceTrack not working with filters - Fix visual analytics error when tool configuration has changed --- datatypes_conf.xml.sample | 1 + eggs.ini | 4 +- lib/galaxy/datatypes/binary.py | 2 + .../converters/wig_to_bigwig_converter.xml | 14 +++++ lib/galaxy/datatypes/interval.py | 2 +- .../visualization/tracks/data_providers.py | 60 ++++++++++++++++++- .../visualization/tracks/visual_analytics.py | 2 +- lib/galaxy/web/controllers/tracks.py | 24 +++++--- static/scripts/packed/trackster.js | 2 +- static/scripts/trackster.js | 54 ++++++++++------- templates/tracks/browser.mako | 4 +- 11 files changed, 130 insertions(+), 39 deletions(-) create mode 100644 lib/galaxy/datatypes/converters/wig_to_bigwig_converter.xml diff --git a/datatypes_conf.xml.sample b/datatypes_conf.xml.sample index 1536ccd041e..27214ada5ba 100644 --- a/datatypes_conf.xml.sample +++ b/datatypes_conf.xml.sample @@ -121,6 +121,7 @@ + diff --git a/eggs.ini b/eggs.ini index 6e1a5583fad..05225c80abe 100644 --- a/eggs.ini +++ b/eggs.ini @@ -12,7 +12,7 @@ repository = http://eggs.g2.bx.psu.edu/new no_auto = pbs_python DRMAA_python [eggs:platform] -bx_python = 0.5.0 +bx_python = 0.7.0 Cheetah = 2.2.2 ctypes = 1.0.2 DRMAA_python = 0.2 @@ -65,7 +65,7 @@ Whoosh = 0.3.18 psycopg2 = _8.4.2_static pysqlite = _3.6.17_static MySQL_python = _5.1.41_static -bx_python = _dev_f74aec067563 +bx_python = _14b6a6c95da6 GeneTrack = _dev_48da9e998f0caf01c5be731e926f4b0481f658f0 SQLAlchemy = _dev_r6498 pysam = _kanwei_90e03180969d diff --git a/lib/galaxy/datatypes/binary.py b/lib/galaxy/datatypes/binary.py index 732ac88a798..7b043d9aa53 100644 --- a/lib/galaxy/datatypes/binary.py +++ b/lib/galaxy/datatypes/binary.py @@ -230,6 +230,8 @@ class BigWig(Binary): return dataset.peek except: return "Binary UCSC %s file (%s)" % ( self._name, data.nice_size( dataset.get_size() ) ) + def get_track_type( self ): + return "LineTrack", {"data_standalone": "bigwig"} class BigBed(BigWig): """BigBed support from UCSC.""" diff --git a/lib/galaxy/datatypes/converters/wig_to_bigwig_converter.xml b/lib/galaxy/datatypes/converters/wig_to_bigwig_converter.xml new file mode 100644 index 00000000000..0dcf7b51469 --- /dev/null +++ b/lib/galaxy/datatypes/converters/wig_to_bigwig_converter.xml @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py index f4070ef2e9c..778de245fff 100644 --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -1169,7 +1169,7 @@ class Wiggle( Tabular, _RemoteCallMixin ): resolution = max( resolution, 1 ) return resolution def get_track_type( self ): - return "LineTrack", {"data": "array_tree"} + return "LineTrack", {"data": "bigwig"} class CustomTrack ( Tabular ): """UCSC CustomTrack""" diff --git a/lib/galaxy/visualization/tracks/data_providers.py b/lib/galaxy/visualization/tracks/data_providers.py index f997ea95801..35e93b144a3 100644 --- a/lib/galaxy/visualization/tracks/data_providers.py +++ b/lib/galaxy/visualization/tracks/data_providers.py @@ -8,6 +8,7 @@ pkg_resources.require( "bx-python" ); pkg_resources.require( "pysam" ); pkg_reso from galaxy.datatypes.util.gff_util import * from bx.interval_index_file import Indexes from bx.arrays.array_tree import FileArrayTreeDict +from bx.bbi.bigwig_file import BigWigFile from galaxy.util.lrucache import LRUCache from galaxy.visualization.tracks.summary import * from galaxy.datatypes.tabular import Vcf @@ -16,6 +17,13 @@ from pysam import csamtools MAX_VALS = 5000 # only display first MAX_VALS features +# Return None instead of NaN to pass jQuery 1.4's strict JSON +def float_nan(n): + if n != n: # NaN != NaN + return None + else: + return float(n) + class TracksDataProvider( object ): """ Base class for tracks data providers. """ @@ -340,6 +348,50 @@ class ArrayTreeDataProvider( TracksDataProvider ): f.close() return results + +class BigWigDataProvider( TracksDataProvider ): + """ + BigWig data provider for the Galaxy track browser. + """ + + def get_data( self, chrom, start, end, **kwargs ): + # Bigwig has the possibility of it being a standalone bigwig file, in which case we use + # original_dataset, or coming from wig->bigwig conversion in which we use converted_dataset + if self.converted_dataset is not None: + f = open( self.converted_dataset.file_name ) + else: + f = open( self.original_dataset.file_name ) + bw = BigWigFile(file=f) + + if 'stats' in kwargs: + all_dat = bw.query(chrom, 0, 2147483647, 1) + f.close() + if all_dat is None: + return None + + all_dat = all_dat[0] # only 1 summary + return { 'max': float( all_dat['max'] ), \ + 'min': float( all_dat['min'] ), \ + 'total_frequency': float( all_dat['coverage'] ) } + + + start = int(start) + end = int(end) + num_points = 2000 + if (end - start) < num_points: + num_points = end - start + + data = bw.query(chrom, start, end, num_points) + f.close() + + pos = start + step_size = (end - start) / num_points + result = [] + for dat_dict in data: + result.append( (pos, float_nan(dat_dict['mean']) ) ) + pos += step_size + + return result class IntervalIndexDataProvider( TracksDataProvider ): """ @@ -448,7 +500,8 @@ dataset_type_name_to_data_provider = { "array_tree": ArrayTreeDataProvider, "interval_index": { "vcf": VcfDataProvider, "default" : IntervalIndexDataProvider }, "bai": BamDataProvider, - "summary_tree": SummaryTreeDataProvider + "summary_tree": SummaryTreeDataProvider, + "bigwig": BigWigDataProvider } dataset_type_to_data_provider = { @@ -477,7 +530,10 @@ def get_data_provider( name=None, original_dataset=None ): # Get data provider mapping and data provider for 'data'. If # provider available, use it; otherwise use generic provider. _ , data_provider_mapping = original_dataset.datatype.get_track_type() - data_provider_name = data_provider_mapping[ 'data' ] + if 'data_standalone' in data_provider_mapping: + data_provider_name = data_provider_mapping[ 'data_standalone' ] + else: + data_provider_name = data_provider_mapping[ 'data' ] if data_provider_name: data_provider = get_data_provider( name=data_provider_name, original_dataset=original_dataset ) else: diff --git a/lib/galaxy/visualization/tracks/visual_analytics.py b/lib/galaxy/visualization/tracks/visual_analytics.py index f20c90d874e..bb09804ba00 100644 --- a/lib/galaxy/visualization/tracks/visual_analytics.py +++ b/lib/galaxy/visualization/tracks/visual_analytics.py @@ -26,7 +26,7 @@ def get_tool_def( trans, hda ): # Get list of tool parameters that can be interactively modified. tool_params = [] tool_param_values = dict( [ ( p.name, p.value ) for p in job.parameters ] ) - tool_param_values = tool.params_from_strings( tool_param_values, trans.app ) + tool_param_values = tool.params_from_strings( tool_param_values, trans.app, ignore_errors=True ) for name, input in tool.inputs.items(): if type( input ) == IntegerToolParameter: tool_params.append( { 'name' : name, 'label': input.label, 'type': 'int', \ diff --git a/lib/galaxy/web/controllers/tracks.py b/lib/galaxy/web/controllers/tracks.py index 75571ae61e8..be9a5cdad23 100644 --- a/lib/galaxy/web/controllers/tracks.py +++ b/lib/galaxy/web/controllers/tracks.py @@ -120,7 +120,6 @@ class TracksController( BaseController, UsesVisualization, UsesHistoryDatasetAss @web.require_login() def index( self, trans, **kwargs ): config = {} - return trans.fill_template( "tracks/browser.mako", config=config, add_dataset=kwargs.get("dataset_id", None), \ default_dbkey=kwargs.get("default_dbkey", None) ) @@ -267,7 +266,7 @@ class TracksController( BaseController, UsesVisualization, UsesHistoryDatasetAss return msg # Get converted datasets. - data_sources, message = self._get_converted_datasets( trans, dataset ) + data_sources, message = self._get_datasource_info( trans, dataset ) if not data_sources: return message @@ -286,9 +285,14 @@ class TracksController( BaseController, UsesVisualization, UsesHistoryDatasetAss return { 'dataset_type': data_sources['index'], 'data': frequencies, 'max': max_v, 'avg': avg_v, 'delta': delta } # Get data provider. - tracks_dataset_type = data_sources['data'] - data_provider_class = get_data_provider( name=tracks_dataset_type, original_dataset=dataset ) - data_provider = data_provider_class( dataset.get_converted_dataset(trans, tracks_dataset_type), dataset ) + if "data_standalone" in data_sources: + tracks_dataset_type = data_sources['data_standalone'] + data_provider_class = get_data_provider( name=tracks_dataset_type, original_dataset=dataset ) + data_provider = data_provider_class( original_dataset=dataset ) + else: + tracks_dataset_type = data_sources['data'] + data_provider_class = get_data_provider( name=tracks_dataset_type, original_dataset=dataset ) + data_provider = data_provider_class( dataset.get_converted_dataset(trans, tracks_dataset_type), dataset ) # Get and return data from data_provider. data = data_provider.get_data( chrom, low, high, **kwargs ) @@ -502,13 +506,17 @@ class TracksController( BaseController, UsesVisualization, UsesHistoryDatasetAss return messages.PENDING return None - def _get_converted_datasets( self, trans, dataset ): + def _get_datasource_info( self, trans, dataset ): """ - Returns (a) converted datasets for a dataset and (b) dictionary of + Returns (a) datasource info for a dataset and (b) dictionary of any messages based on or derived from the conversion. """ track_type, data_sources = dataset.datatype.get_track_type() + for source_type, data_source in data_sources.iteritems(): + if source_type == "data_standalone": + break + try: converted_dataset = dataset.get_converted_dataset(trans, data_source) except ValueError: @@ -523,4 +531,4 @@ class TracksController( BaseController, UsesVisualization, UsesHistoryDatasetAss if not converted_dataset or converted_dataset.state != model.Dataset.states.OK: return None, messages.PENDING - return data_sources, None \ No newline at end of file + return data_sources, None diff --git a/static/scripts/packed/trackster.js b/static/scripts/packed/trackster.js index 2cc21c94b27..b9a736ca428 100644 --- a/static/scripts/packed/trackster.js +++ b/static/scripts/packed/trackster.js @@ -1 +1 @@ -var DENSITY=200,FEATURE_LEVELS=10,MAX_FEATURE_DEPTH=50,CONNECTOR_COLOR="#ccc",DATA_ERROR="There was an error in indexing this dataset.",DATA_NOCONVERTER="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",DATA_NONE="No data for this chrom/contig.",DATA_PENDING="Currently indexing... please wait",DATA_LOADING="Loading data...",FILTERABLE_CLASS="filterable",CACHED_TILES_FEATURE=10,CACHED_TILES_LINE=5,CACHED_DATA=5,DUMMY_CANVAS=document.createElement("canvas"),RIGHT_STRAND,LEFT_STRAND;if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(DUMMY_CANVAS)}CONTEXT=DUMMY_CANVAS.getContext("2d");PX_PER_CHAR=CONTEXT.measureText("A").width;var right_img=new Image();right_img.src=image_path+"/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src=image_path+"/visualization/strand_left.png";left_img.onload=function(){LEFT_STRAND=CONTEXT.createPattern(left_img,"repeat")};var right_img_inv=new Image();right_img_inv.src=image_path+"/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND_INV=CONTEXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src=image_path+"/visualization/strand_left_inv.png";left_img_inv.onload=function(){LEFT_STRAND_INV=CONTEXT.createPattern(left_img_inv,"repeat")};function round_1000(a){return Math.round(a*1000)/1000}var Cache=function(a){this.num_elements=a;this.clear()};$.extend(Cache.prototype,{get:function(b){var a=this.key_ary.indexOf(b);if(a!=-1){this.key_ary.splice(a,1);this.key_ary.push(b)}return this.obj_cache[b]},set:function(b,c){if(!this.obj_cache[b]){if(this.key_ary.length>=this.num_elements){var a=this.key_ary.shift();delete this.obj_cache[a]}this.key_ary.push(b)}this.obj_cache[b]=c;return c},clear:function(){this.obj_cache={};this.key_ary=[]}});var View=function(a,d,c,b,e){this.container=a;this.chrom=null;this.vis_id=c;this.dbkey=b;this.title=d;this.tracks=[];this.label_tracks=[];this.max_low=0;this.max_high=0;this.num_tracks=0;this.track_id_counter=0;this.zoom_factor=3;this.min_separation=30;this.has_changes=false;this.init(e);this.reset()};$.extend(View.prototype,{init:function(d){var c=this.container,a=this;this.top_container=$("
").addClass("top-container").appendTo(c);this.content_div=$("
").addClass("content").css("position","relative").appendTo(c);this.bottom_container=$("
").addClass("bottom-container").appendTo(c);this.top_labeltrack=$("
").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("
").addClass("viewport-container").addClass("viewport-container").appendTo(this.content_div);this.intro_div=$("
").addClass("intro").text("Select a chrom from the dropdown below").hide();this.nav_labeltrack=$("
").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("
").addClass("nav-container").prependTo(this.top_container);this.nav=$("
").addClass("nav").appendTo(this.nav_container);this.overview=$("
").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("
").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("Close Overview").addClass("overview-close").hide().appendTo(this.overview_viewport);this.overview_highlight=$("
").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("
").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("
").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("
").addClass("nav-controls").appendTo(this.nav);this.chrom_form=$("
").attr("action",function(){}).appendTo(this.nav_controls);this.chrom_select=$("").addClass("nav-input").hide().bind("keypress focusout",b).appendTo(this.chrom_form);this.location_span=$("").addClass("location").appendTo(this.chrom_form);this.location_span.bind("click",function(){a.location_span.hide();a.chrom_select.hide();a.nav_input.css("display","inline-block");a.nav_input.select();a.nav_input.focus()});if(this.vis_id!==undefined){this.hidden_input=$("").attr("type","hidden").val(this.vis_id).appendTo(this.chrom_form)}this.zo_link=$("").click(function(){a.zoom_out();a.redraw()}).appendTo(this.chrom_form);this.zi_link=$("").click(function(){a.zoom_in();a.redraw()}).appendTo(this.chrom_form);$.ajax({url:chrom_url,data:(this.vis_id!==undefined?{vis_id:this.vis_id}:{dbkey:this.dbkey}),dataType:"json",success:function(f){if(f.reference){a.add_label_track(new ReferenceTrack(a))}a.chrom_data=f.chrom_info;var j='';for(var h=0,e=a.chrom_data.length;h'+g+""}a.chrom_select.html(j);a.intro_div.show();a.chrom_select.bind("change",function(){a.change_chrom(a.chrom_select.val())});if(d){d()}},error:function(){alert("Could not load chroms for this dbkey:",a.dbkey)}});this.content_div.bind("dblclick",function(f){a.zoom_in(f.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(f){this.current_x=f.offsetX}).bind("drag",function(f){var h=f.offsetX-this.current_x;this.current_x=f.offsetX;var g=Math.round(h/a.viewport_container.width()*(a.max_high-a.max_low));a.move_delta(-g)});this.overview_close.bind("click",function(){for(var f=0,e=a.tracks.length;f").css({height:a.content_div.height()+a.top_labeltrack.height()+a.nav_labeltrack.height(),top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(j){var g=Math.min(j.clientX,this.drag_origin_x)-a.container.offset().left,f=Math.max(j.clientX,this.drag_origin_x)-a.container.offset().left,i=(a.high-a.low),h=a.viewport_container.width();a.update_location(Math.round(g/h*i)+a.low,Math.round(f/h*i)+a.low);this.drag_div.css({left:g+"px",width:(f-g)+"px"})}).bind("dragend",function(k){var g=Math.min(k.clientX,this.drag_origin_x),f=Math.max(k.clientX,this.drag_origin_x),i=(a.high-a.low),h=a.viewport_container.width(),j=a.low;a.low=Math.round(g/h*i)+j;a.high=Math.round(f/h*i)+j;this.drag_div.remove();a.redraw()});this.add_label_track(new LabelTrack(this,this.top_labeltrack));this.add_label_track(new LabelTrack(this,this.nav_labeltrack));$(window).bind("resize",function(){a.resize_window()});$(document).bind("redraw",function(){a.redraw()});this.reset();$(window).trigger("resize")},update_location:function(a,b){this.location_span.text(commatize(a)+" - "+commatize(b));this.nav_input.val(this.chrom+":"+commatize(a)+"-"+commatize(b))},change_chrom:function(e,b,g){var d=this;var f=$.grep(d.chrom_data,function(j,k){return j.chrom===e})[0];if(f===undefined){return}if(e!==d.chrom){d.chrom=e;if(!d.chrom){d.intro_div.show()}else{d.intro_div.hide()}d.chrom_select.val(d.chrom);d.max_high=f.len;d.reset();d.redraw(true);for(var h=0,a=d.tracks.length;ha.max_high){a.high=a.max_high;a.low=a.max_high-b}else{a.high-=c;a.low-=c}}a.redraw()},add_track:function(a){a.view=this;a.track_id=this.track_id_counter;this.tracks.push(a);if(a.init){a.init()}a.container_div.attr("id","track_"+a.track_id);this.track_id_counter+=1;this.num_tracks+=1},add_label_track:function(a){a.view=this;this.label_tracks.push(a)},remove_track:function(a){this.has_changes=true;a.container_div.fadeOut("slow",function(){$(this).remove()});delete this.tracks[this.tracks.indexOf(a)];this.num_tracks-=1},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},redraw:function(h){var g=this.high-this.low,f=this.low,b=this.high;if(fthis.max_high){b=this.max_high}if(this.high!==0&&gthis.index){return true}return false},keep:function(a){if(!this.applies_to(a)){return true}return(a[this.index]>=this.low&&a[this.index]<=this.high)},update_attrs:function(b){var a=false;if(!this.applies_to(b)){return a}if(b[this.index]this.slider_max){this.slider_max=b[this.index];a=false}return a},update_ui_elt:function(){var b=this.slider.slider("option","min"),a=this.slider.slider("option","max");if(this.slider_mina){this.slider.slider("option","min",this.slider_min);this.slider.slider("option","max",this.slider_max);this.slider.slider("option","values",[this.slider_min,this.slider_max])}}});var get_filters_from_dict=function(a){var g=[];for(var d=0;d").addClass("track").css("position","relative");if(!this.hidden){this.header_div=$("
").appendTo(this.container_div);if(this.view.editor){this.drag_div=$("
").appendTo(this.header_div)}this.name_div=$("