commit/galaxy-central: jgoecks: Add feature search to Trackster; typing in location box will search tracks in visualization for features that start with entered text. Works with both GFF/GTF and BED datasets. Fixes #611
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/d5626302446d/ changeset: d5626302446d user: jgoecks date: 2012-09-05 23:26:43 summary: Add feature search to Trackster; typing in location box will search tracks in visualization for features that start with entered text. Works with both GFF/GTF and BED datasets. Fixes #611 affected #: 12 files diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e datatypes_conf.xml.sample --- a/datatypes_conf.xml.sample +++ b/datatypes_conf.xml.sample @@ -19,6 +19,7 @@ <converter file="bed_to_bgzip_converter.xml" target_datatype="bgzip"/><converter file="bed_to_tabix_converter.xml" target_datatype="tabix" depends_on="bgzip"/><converter file="bed_to_summary_tree_converter.xml" target_datatype="summary_tree"/> + <converter file="bed_to_fli_converter.xml" target_datatype="fli"/><!-- <display file="ucsc/interval_as_bed.xml" /> --><display file="genetrack.xml" /><display file="igb/bed.xml" /> diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e lib/galaxy/datatypes/converters/bed_to_fli_converter.xml --- /dev/null +++ b/lib/galaxy/datatypes/converters/bed_to_fli_converter.xml @@ -0,0 +1,13 @@ +<tool id="CONVERTER_bed_to_fli_0" name="Convert BED to Feature Location Index"> + <!-- <description>__NOT_USED_CURRENTLY_FOR_CONVERTERS__</description> --> + <!-- Used on the metadata edit page. --> + <command interpreter="python">interval_to_fli.py -B $input1 $output1</command> + <inputs> + <param format="bed" name="input1" type="data" label="Choose BED file"/> + </inputs> + <outputs> + <data format="fli" name="output1"/> + </outputs> + <help> + </help> +</tool> diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e lib/galaxy/datatypes/converters/gff_to_fli.py --- a/lib/galaxy/datatypes/converters/gff_to_fli.py +++ /dev/null @@ -1,57 +0,0 @@ -''' -Creates a feature location index for a given GFF file. -''' - -import sys -from galaxy import eggs -from galaxy.datatypes.util.gff_util import read_unordered_gtf, convert_gff_coords_to_bed - -def main(): - # Process arguments. - in_fname = sys.argv[1] - out_fname = sys.argv[2] - - # Create dict of name-location pairings. - name_loc_dict = {} - for feature in read_unordered_gtf( open( in_fname, 'r' ) ): - for name in feature.attributes: - val = feature.attributes[ name ] - try: - float( val ) - continue - except: - convert_gff_coords_to_bed( feature ) - # Value is not a number, so it can be indexed. - if val not in name_loc_dict: - # Value is not in dictionary. - name_loc_dict[ val ] = { - 'contig': feature.chrom, - 'start': feature.start, - 'end': feature.end - } - else: - # Value already in dictionary, so update dictionary. - loc = name_loc_dict[ val ] - if feature.start < loc[ 'start' ]: - loc[ 'start' ] = feature.start - if feature.end > loc[ 'end' ]: - loc[ 'end' ] = feature.end - - # Print name, loc in sorted order. - out = open( out_fname, 'w' ) - max_len = 0 - entries = [] - for name in sorted( name_loc_dict.iterkeys() ): - loc = name_loc_dict[ name ] - entry = '%s\t%s' % ( name, '%s:%i-%i' % ( loc[ 'contig' ], loc[ 'start' ], loc[ 'end' ] ) ) - if len( entry ) > max_len: - max_len = len( entry ) - entries.append( entry ) - - out.write( str( max_len + 1 ).ljust( max_len ) + '\n' ) - for entry in entries: - out.write( entry.ljust( max_len ) + '\n' ) - out.close() - -if __name__ == '__main__': - main() \ No newline at end of file diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e lib/galaxy/datatypes/converters/gff_to_fli_converter.xml --- a/lib/galaxy/datatypes/converters/gff_to_fli_converter.xml +++ b/lib/galaxy/datatypes/converters/gff_to_fli_converter.xml @@ -1,7 +1,7 @@ <tool id="CONVERTER_gff_to_fli_0" name="Convert GFF to Feature Location Index"><!-- <description>__NOT_USED_CURRENTLY_FOR_CONVERTERS__</description> --><!-- Used on the metadata edit page. --> - <command interpreter="python">gff_to_fli.py $input1 $output1</command> + <command interpreter="python">interval_to_fli.py -G $input1 $output1</command><inputs><param format="gff" name="input1" type="data" label="Choose GFF file"/></inputs> diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e lib/galaxy/datatypes/converters/interval_to_fli.py --- /dev/null +++ b/lib/galaxy/datatypes/converters/interval_to_fli.py @@ -0,0 +1,81 @@ +''' +Creates a feature location index (FLI) for a given BED/GFF file. +FLI index has the form: + [line_length] + <symbol1_in_lowercase><tab><symbol1><tab><location> + <symbol2_in_lowercase><tab><symbol2><tab><location> + ... +where location is formatted as: + contig:start-end +and symbols are sorted in lexigraphical order. +''' + +import sys, optparse +from galaxy import eggs +from galaxy.datatypes.util.gff_util import read_unordered_gtf, convert_gff_coords_to_bed + +def main(): + # Process arguments. + parser = optparse.OptionParser() + parser.add_option( '-B', '--bed', action="store_true", dest="bed_input" ) + parser.add_option( '-G', '--gff', action="store_true", dest="gff_input" ) + (options, args) = parser.parse_args() + in_fname, out_fname = args + + + # Create dict of name-location pairings. + name_loc_dict = {} + if options.gff_input: + # GFF format + for feature in read_unordered_gtf( open( in_fname, 'r' ) ): + for name in feature.attributes: + val = feature.attributes[ name ] + try: + float( val ) + continue + except: + convert_gff_coords_to_bed( feature ) + # Value is not a number, so it can be indexed. + if val not in name_loc_dict: + # Value is not in dictionary. + name_loc_dict[ val ] = { + 'contig': feature.chrom, + 'start': feature.start, + 'end': feature.end + } + else: + # Value already in dictionary, so update dictionary. + loc = name_loc_dict[ val ] + if feature.start < loc[ 'start' ]: + loc[ 'start' ] = feature.start + if feature.end > loc[ 'end' ]: + loc[ 'end' ] = feature.end + else: + # BED format. + for line in open( in_fname, 'r' ): + fields = line.split() + name_loc_dict[ fields[3] ] = { + 'contig': fields[0], + 'start': int( fields[1] ), + 'end': int ( fields[2] ) + } + + # Create sorted list of entries. + out = open( out_fname, 'w' ) + max_len = 0 + entries = [] + for name in sorted( name_loc_dict.iterkeys() ): + loc = name_loc_dict[ name ] + entry = '%s\t%s\t%s' % ( name.lower(), name, '%s:%i-%i' % ( loc[ 'contig' ], loc[ 'start' ], loc[ 'end' ] ) ) + if len( entry ) > max_len: + max_len = len( entry ) + entries.append( entry ) + + # Write padded entries. + out.write( str( max_len + 1 ).ljust( max_len ) + '\n' ) + for entry in entries: + out.write( entry.ljust( max_len ) + '\n' ) + out.close() + +if __name__ == '__main__': + main() \ No newline at end of file diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e lib/galaxy/datatypes/interval.py --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -512,7 +512,7 @@ except: return False def get_track_type( self ): - return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + return "FeatureTrack", {"data": "tabix", "index": "summary_tree", "feature_search": "fli"} class BedStrict( Bed ): """Tab delimited data in strict BED format - no non-standard columns allowed""" @@ -785,7 +785,7 @@ return False def get_track_type( self ): - return "FeatureTrack", {"data": "interval_index", "index": "summary_tree"} + return "FeatureTrack", {"data": "interval_index", "index": "summary_tree", "feature_search": "fli"} class Gff3( Gff ): @@ -962,10 +962,6 @@ return True except: return False - - def get_track_type( self ): - return "FeatureTrack", {"data": "interval_index", "index": "summary_tree"} - class Wiggle( Tabular, _RemoteCallMixin ): """Tab delimited data in wiggle format""" diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e lib/galaxy/model/__init__.py --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1188,6 +1188,8 @@ return None def get_converter_types(self): return self.datatype.get_converter_types( self, datatypes_registry ) + def can_convert_to(self, format): + return format in self.get_converter_types() def find_conversion_destination( self, accepted_formats, **kwd ): """Returns ( target_ext, existing converted dataset )""" return self.datatype.find_conversion_destination( self, accepted_formats, datatypes_registry, **kwd ) diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e lib/galaxy/visualization/tracks/data_providers.py --- a/lib/galaxy/visualization/tracks/data_providers.py +++ b/lib/galaxy/visualization/tracks/data_providers.py @@ -73,6 +73,7 @@ textloc_file = open( self.converted_dataset.file_name, 'r' ) line_len = int( textloc_file.readline() ) file_len = os.path.getsize( self.converted_dataset.file_name ) + query = query.lower() # Find query in file using binary search. low = 0 @@ -84,7 +85,6 @@ # Compare line with query and update low, high. line = textloc_file.readline() - print '--', mid, line if line < query: low = mid + 1 else: @@ -93,14 +93,14 @@ position = low * line_len # At right point in file, generate hits. - result = [ ] + result = [] while True: line = textloc_file.readline() if not line.startswith( query ): break if line[ -1: ] == '\n': line = line[ :-1 ] - result.append( line.split() ) + result.append( line.split()[1:] ) textloc_file.close() return result diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py +++ b/lib/galaxy/web/controllers/tracks.py @@ -347,18 +347,20 @@ return { "status": messages.DATA, "valid_chroms": valid_chroms } @web.json - def feature_loc( self, trans, hda_ldda, dataset_id, query ): + def search_features( self, trans, hda_ldda, dataset_id, query ): """ Returns features, locations in dataset that match query. Format is a list of features; each feature is a list itself: [name, location] """ dataset = self.get_hda_or_ldda( trans, hda_ldda, dataset_id ) - converted_dataset = dataset.get_converted_dataset( trans, "fli" ) - data_provider = FeatureLocationIndexDataProvider( converted_dataset=converted_dataset ) - if data_provider: - return data_provider.get_data( query ) - else: - return 'None' + if dataset.can_convert_to( "fli" ): + converted_dataset = dataset.get_converted_dataset( trans, "fli" ) + if converted_dataset: + data_provider = FeatureLocationIndexDataProvider( converted_dataset=converted_dataset ) + if data_provider: + return data_provider.get_data( query ) + + return [] @web.json def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=None, **kwargs ): diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e static/scripts/viz/trackster.js --- a/static/scripts/viz/trackster.js +++ b/static/scripts/viz/trackster.js @@ -574,18 +574,26 @@ icon_dict.on_click_fn, icon_dict.prepend, icon_dict.hide); } }, + /** * Update icons. */ update_icons: function() {}, + /** * Hide drawable's contents. */ hide_contents: function () {}, + /** * Show drawable's contents. */ - show_contents: function() {} + show_contents: function() {}, + + /** + * Returns a shallow copy of all drawables in this drawable. + */ + get_drawables: function() {} }); /** @@ -611,6 +619,7 @@ this.add_drawable(drawable); } }, + /** * Init each drawable in the collection. */ @@ -619,6 +628,7 @@ this.drawables[i].init(); } }, + /** * Draw each drawable in the collection. */ @@ -627,6 +637,7 @@ this.drawables[i]._draw(); } }, + /** * Returns representation of object in a dictionary for easy saving. * Use from_dict to recreate object. @@ -643,6 +654,7 @@ drawables: dictified_drawables }; }, + /** * Add a drawable to the end of the collection. */ @@ -651,6 +663,7 @@ drawable.container = this; this.changed(); }, + /** * Add a drawable before another drawable. */ @@ -663,6 +676,7 @@ } return false; }, + /** * Replace one drawable with another. */ @@ -677,6 +691,7 @@ } return index; }, + /** * Remove drawable from this collection. */ @@ -691,6 +706,7 @@ } return false; }, + /** * Move drawable to another location in collection. */ @@ -705,6 +721,13 @@ return true; } return false; + }, + + /** + * Returns all drawables in this drawable. + */ + get_drawables: function() { + return this.drawables; } }); @@ -1070,7 +1093,7 @@ this.default_overview_height = this.overview_box.height(); this.nav_controls = $("<div/>").addClass("nav-controls").appendTo(this.nav); - this.chrom_select = $("<select/>").attr({ "name": "chrom"}).css("width", "15em").addClass("no-autocomplete").append("<option value=''>Loading</option>").appendTo(this.nav_controls); + this.chrom_select = $("<select/>").attr({ "name": "chrom"}).css("width", "15em").append("<option value=''>Loading</option>").appendTo(this.nav_controls); var submit_nav = function(e) { if (e.type === "focusout" || (e.keyCode || e.which) === 13 || (e.keyCode || e.which) === 27 ) { if ((e.keyCode || e.which) !== 27) { // Not escape key @@ -1091,6 +1114,28 @@ view.nav_input.css("display", "inline-block"); view.nav_input.select(); view.nav_input.focus(); + // Set up autocomplete for tracks' features. + view.nav_input.autocomplete({ + source: function(request, response) { + // Using current text, query each track and create list of all matching features. + var all_features = [], + feature_search_deferreds = $.map(view.get_drawables(), function(drawable) { + return drawable.data_manager.search_features(request.term).success(function(dataset_features) { + all_features = all_features.concat(dataset_features); + }); + }); + + // When all searching is done, fill autocomplete. + $.when.apply($, feature_search_deferreds).done(function() { + response($.map(all_features, function(feature) { + return { + label: feature[0], + value: feature[1] + }; + })); + }); + } + }); }); if (this.vis_id !== undefined) { this.hidden_input = $("<input/>").attr("type", "hidden").val(this.vis_id).appendTo(this.nav_controls); @@ -1285,9 +1330,8 @@ data: url_parms, dataType: "json", success: function (result) { - // Show error if could not load chroms. + // Do nothing if could not load chroms. if (result.chrom_info.length === 0) { - alert("Invalid chromosome: " + url_parms.chrom); return; } @@ -2935,6 +2979,7 @@ this.data_url_extra_params = {}; this.data_query_wait = ('data_query_wait' in obj_dict ? obj_dict.data_query_wait : DEFAULT_DATA_QUERY_WAIT); this.dataset_check_url = ('converted_datasets_state_url' in obj_dict ? obj_dict.converted_datasets_state_url : converted_datasets_state_url); + this.feature_search_url = ('feature_search_url' in obj_dict ? obj_dict.feature_search_url : feature_search_url); // A little ugly creating data manager right now due to transition to Backbone-based objects. var track = this, @@ -2948,6 +2993,7 @@ dataset: dataset, data_url: track.data_url, dataset_state_url: track.dataset_check_url, + feature_search_url: track.feature_search_url, data_mode_compatible: this.data_and_mode_compatible, can_subset: this.can_subset })); @@ -3319,7 +3365,14 @@ /** * Additional initialization required before drawing track for the first time. */ - predraw_init: function() {} + predraw_init: function() {}, + + /** + * Returns all drawables in this drawable. + */ + get_drawables: function() { + return this; + } }); var TiledTrack = function(view, container, obj_dict) { diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e static/scripts/viz/visualization.js --- a/static/scripts/viz/visualization.js +++ b/static/scripts/viz/visualization.js @@ -169,6 +169,7 @@ filters_manager: null, data_url: null, dataset_state_url: null, + feature_search_url: null, genome_wide_summary_data: null, data_mode_compatible: function(entry, mode) { return true; }, can_subset: function(entry) { return false; } @@ -199,9 +200,22 @@ }); return ready_deferred; }, + + /** + * Perform a feature search from server; returns Deferred object that resolves when data is available. + */ + search_features: function(query) { + var dataset = this.get('dataset'), + params = { + query: query, + dataset_id: dataset.id, + hda_ldda: dataset.get('hda_ldda') + }; + return $.getJSON(this.get('feature_search_url'), params); + }, /** - * Load data from server; returns AJAX object so that use of Deferred is possible. + * Load data from server; returns Deferred object that resolves when data is available. */ load_data: function(region, mode, resolution, extra_params) { // Setup data request params. @@ -211,7 +225,7 @@ "high": region.get('end'), "mode": mode, "resolution": resolution - }; + }, dataset = this.get('dataset'); // ReferenceDataManager does not have dataset. diff -r 382c9cb6d6016ea58a50d5b2fd5a02ca484f4f6e -r d5626302446db334804820f0a29fc4d0c7d72a0e templates/tracks/browser.mako --- a/templates/tracks/browser.mako +++ b/templates/tracks/browser.mako @@ -39,6 +39,7 @@ chrom_url = "${h.url_for( action='chroms' )}", dataset_state_url = "${h.url_for( action='dataset_state' )}", converted_datasets_state_url = "${h.url_for( action='converted_datasets_state' )}", + feature_search_url = "${h.url_for( action='search_features' )}", view, browser_router; Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.
participants (1)
-
Bitbucket