├── cli.js ├── index.js ├── .gitignore ├── .npmignore ├── test ├── test-leap.bb ├── data.json └── bionode-bbi.js ├── README.md ├── .travis.yml ├── LICENSE ├── src ├── node-fetchable.js ├── spans.js ├── binutils.js └── bbi.js ├── package.json ├── karma.conf.js ├── CODE_OF_CONDUCT.md └── lib └── bionode-bbi.js /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | console.log('TODO') 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/bionode-bbi') 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | coverage 4 | tmp 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | coverage 4 | tmp 5 | -------------------------------------------------------------------------------- /test/test-leap.bb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bionode/bionode-bbi/master/test/test-leap.bb -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fetcher for BBI files (bigWig and bigBed). 2 | 3 | Based on code from [Biodalliance](http://www.biodalliance.org/) 4 | 5 | Still needs some node-side tests etc. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | after_script: 5 | - npm run coveralls 6 | notifications: 7 | irc: 8 | channels: 9 | - "chat.freenode.net#bionode" 10 | template: 11 | - "%{message}: %{repository}#%{build_number}: %{commit_message} (%{branch} - %{commit} : %{author})" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /src/node-fetchable.js: -------------------------------------------------------------------------------- 1 | // from @bmpvieira 2 | 3 | "use strict"; 4 | 5 | // Imports 6 | var fs = require('fs'); 7 | var concat = require('concat-stream'); 8 | 9 | var Promise = require('es6-promise').Promise; 10 | 11 | function NodeFetchable(blob, opts) { 12 | this.blob = blob; 13 | this.opts = opts; 14 | } 15 | 16 | NodeFetchable.prototype.slice = function(start, length) { 17 | // Both start and end are inclusive and start at 0 (http://nodejs.org/api/fs.html) 18 | var opts = {start: start}; 19 | if (length) { opts.end = start + length - 1}; 20 | return new NodeFetchable(this.blob, opts); 21 | } 22 | 23 | NodeFetchable.prototype.fetch = function() { 24 | var self = this; 25 | return new Promise(function(resolve, reject) { 26 | var read = fs.createReadStream(self.blob, self.opts); 27 | var write = concat(function(data) { 28 | resolve(toArrayBuffer(data)); 29 | }); 30 | read.pipe(write); 31 | }); 32 | } 33 | 34 | function toArrayBuffer(buffer) { 35 | // Node 0.12+ has a built-in buffer.toArrayBuffer() method that will deprecate this 36 | var ab = new ArrayBuffer(buffer.length); 37 | var view = new Uint8Array(ab); 38 | for (var i = 0; i < buffer.length; ++i) { 39 | view[i] = buffer[i]; 40 | } 41 | return ab; 42 | } 43 | 44 | module.exports = { 45 | NodeFetchable: NodeFetchable 46 | } 47 | 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genomics-fetchers", 3 | "version": "0.1.1", 4 | "description": "Common back-ends for Javascript genomics tools.", 5 | "homepage": "https://www.biodalliance.org/", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:biodalliance/genomics-fetchers.git" 9 | }, 10 | "dependencies": { 11 | "es6-promise": "~0.1.2", 12 | "jszlib": "dasmoth/jszlib", 13 | "concat-stream": "~1.4.6", 14 | "through2": "~0.5.1", 15 | "xhr2": "0.0.7" 16 | }, 17 | "devDependencies": { 18 | "gulp-concat": "~2.2.0", 19 | "gulp": "~3.6.2", 20 | "gulp-browserify": "~0.5.0", 21 | "karma": "~0.12.1", 22 | "karma-chrome-launcher": "~0.1.2", 23 | "karma-jasmine": "~0.1.5", 24 | "gulp-rename": "~1.2.0", 25 | "watchify": "~0.8.3", 26 | "karma-bro": "~0.2.2", 27 | "should": "~4.0.4", 28 | "mocha": "~1.21.3", 29 | "istanbul": "~0.3.0" 30 | }, 31 | "main": "index.js", 32 | "bin": { 33 | "bionode-bbi": "cli.js" 34 | }, 35 | "scripts": { 36 | "test": "mocha --reporter spec", 37 | "build-docs": "docco ./lib/bionode-bbi.js", 38 | "coverage": "istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -R spec && rm -rf ./coverage", 39 | "coveralls": "istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf ./coverage" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "test1" : { 3 | "zoomLevels" : [ 4 | { 5 | "dataOffset" : 6738, 6 | "reduction" : 40960000, 7 | "indexOffset" : 6809 8 | } 9 | ], 10 | "maxID" : 2, 11 | "chromTreeOffset" : 344, 12 | "chromsToIDs" : { 13 | "22" : 2, 14 | "chr1" : 0, 15 | "1" : 0, 16 | "chr2" : 1, 17 | "chr22" : 2, 18 | "2" : 1 19 | }, 20 | "uncompressBufSize" : 16384, 21 | "asOffset" : 0, 22 | "numZoomLevels" : 1, 23 | "unzoomedDataOffset" : 419, 24 | "extHeaderOffset" : 0, 25 | "data" : { 26 | "opts" : {}, 27 | "url" : "http://www.biodalliance.org/datasets/tests/test-leap.bb", 28 | "start" : 0 29 | }, 30 | "unzoomedIndexOffset" : 518, 31 | "fieldCount" : 3, 32 | "totalSummaryOffset" : 304, 33 | "idsToChroms" : { 34 | "1" : "chr2", 35 | "0" : "chr1", 36 | "2" : "chr22" 37 | }, 38 | "definedFieldCount" : 3, 39 | "type" : "bigbed" 40 | }, 41 | "test2" : [ 42 | { 43 | "_chromId" : 0, 44 | "min" : 10000001, 45 | "max" : 10010000, 46 | "seqName" : "chr1" 47 | }, 48 | { 49 | "_chromId" : 0, 50 | "min" : 20000001, 51 | "max" : 20010000, 52 | "seqName" : "chr1" 53 | }, 54 | { 55 | "_chromId" : 0, 56 | "min" : 30000001, 57 | "max" : 30010000, 58 | "seqName" : "chr1" 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /test/bionode-bbi.js: -------------------------------------------------------------------------------- 1 | var bin = require('../src/binutils') 2 | var bbi = require('../src/bbi') 3 | var nf = require('../src/node-fetchable') 4 | var data = require('./data') 5 | 6 | 7 | var should = require('should') 8 | 9 | require('mocha') 10 | 11 | describe("Parse from URL", function() { 12 | this.timeout(1300000); 13 | var bb 14 | it("Can get bbi", function(done) { 15 | var url = 'http://www.biodalliance.org/datasets/tests/test-leap.bb' 16 | var file = new bin.URLFetchable(url) 17 | bbi.connectBBI(file).then(success).catch(failure) 18 | function success(result) { 19 | result.should.eql(data.test1) 20 | bb = result 21 | done() 22 | } 23 | }) 24 | it("Can get bbi features", function(done) { 25 | bb.getUnzoomedView().fetch('chr1', 1, 100000000).then(success).catch(failure) 26 | function success(result) { 27 | result.should.eql(data.test2) 28 | done() 29 | } 30 | }) 31 | }) 32 | 33 | describe("Parse from local file", function() { 34 | this.timeout(1300000); 35 | var bb 36 | it("Can get bbi", function(done) { 37 | var file = new nf.NodeFetchable('./test/test-leap.bb') 38 | data.test1.data ={ blob: './test/test-leap.bb', opts: undefined } 39 | bbi.connectBBI(file).then(success).catch(failure) 40 | function success(result) { 41 | result.should.eql(data.test1) 42 | bb = result 43 | done() 44 | } 45 | }) 46 | it("Can get bbi features", function(done) { 47 | bb.getUnzoomedView().fetch('chr1', 1, 100000000).then(success).catch(failure) 48 | function success(result) { 49 | result.should.eql(data.test2) 50 | done() 51 | } 52 | }) 53 | }) 54 | 55 | function failure(error) { console.error(error) } 56 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Oct 26 2013 18:39:09 GMT+0100 (BST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | frameworks: ['browserify', 'jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'test/*.js', 18 | 'src/*.js' 19 | ], 20 | 21 | 22 | // list of files to exclude 23 | exclude: [ 24 | ], 25 | 26 | 27 | // test results reporter to use 28 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 29 | reporters: ['progress'], 30 | 31 | 32 | // web server port 33 | port: 9875, 34 | 35 | 36 | // enable / disable colors in the output (reporters and logs) 37 | colors: true, 38 | 39 | 40 | // level of logging 41 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 42 | logLevel: config.LOG_INFO, 43 | 44 | 45 | // enable / disable watching file and executing tests whenever any file changes 46 | autoWatch: true, 47 | 48 | 49 | // Start these browsers, currently available: 50 | // - Chrome 51 | // - ChromeCanary 52 | // - Firefox 53 | // - Opera 54 | // - Safari (only Mac) 55 | // - PhantomJS 56 | // - IE (only Windows) 57 | browsers: ['Chrome'], 58 | 59 | 60 | // If browser does not capture in given timeout [ms], kill it 61 | captureTimeout: 60000, 62 | 63 | 64 | // Continuous Integration mode 65 | // if true, it capture browsers, run tests and exit 66 | singleRun: false, 67 | 68 | preprocessors: {'*/*.js': ['browserify']}, 69 | 70 | browserify: { 71 | debug: true 72 | } 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at mail@bionode.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /lib/bionode-bbi.js: -------------------------------------------------------------------------------- 1 | // # bionode-bwa 2 | // > A Node.js wrapper for the Burrow-Wheeler Aligner (BWA). 3 | // > 4 | // > doi: [?](?) 5 | // > author: [Bruno Vieira](http://bmpvieira.com) 6 | // > email: 7 | // > license: [MIT](https://raw.githubusercontent.com/bionode/bionode-bwa/master/LICENSE) 8 | // 9 | // --- 10 | // 11 | // ## Usage 12 | // This module can be used in Node.js as described further below, or as a command line tool. 13 | // Check https://github.com/lh3/bwa for a list of parameters that can be passed to bwa. 14 | // Examples: 15 | // 16 | // $ npm install -g bionode-bwa 17 | // 18 | // # bionode-bwa [operation] [arguments...] 19 | // $ bionode-bwa mem reference.fasta.gz reads.fastq.gz alignment.sam SRR1509835.bwa 20 | 21 | // var fs = require('fs') 22 | // var zlib = require('zlib') 23 | // var path = require('path') 24 | // var spawn = require('child_process').spawn 25 | var through = require('through2') 26 | var dalibbi = require('../src/bbi.js') 27 | var nf = require('../src/node-fetchable'); 28 | 29 | // var split = require('split') 30 | // var debug = require('debug')('bionode-bbi') 31 | 32 | module.exports = exports = BBI 33 | 34 | // ## BWA 35 | // Takes a BWA operation and returns a Stream that accepts arguments for it. 36 | // For example, for ```mem```, arguments can be a string of space separated filenames 37 | // like ```reference.fasta reads.fastq alignment.sam``` or an array with filenames or variables 38 | // ```[referenceFasta, 'reads.fastq', outputPath]```. 39 | // If the output for sam is omitted, the current directory will be used. 40 | // 41 | // var bwa = require('bionode-bwa') 42 | // var mem = bwa('mem') 43 | // mem('ref.fasta.gz, reads.fastq.gz') 44 | // .on('data', console.log) 45 | // => { operation: 'mem', 46 | // status: 'processing', 47 | // reference: 'reference.fasta.gz', 48 | // progress: { total: 11355, current: 0, percent: 0 }, 49 | // sequences: [ 'reads.fastq.gz' ], 50 | // sam: 'reads.sam' } 51 | // [...] 52 | // { operation: 'mem', 53 | // status: 'finished', 54 | // reference: 'reference.fasta.gz', 55 | // progress: { total: 11355, current: 11355, percent: 100 }, 56 | // sequences: [ 'reads.fastq.gz' ], 57 | // sam: 'reads.sam' } 58 | // 59 | // A callback style can also be used: 60 | // 61 | // var mem = bwa('mem -x pacbio') 62 | // mem([ref, reads, alignment], function(err, data) { 63 | // console.log(data) 64 | // }) 65 | // => { operation: 'mem', 66 | // status: 'finished', 67 | // reference: 'reference.fasta.gz', 68 | // progress: { total: 11355, current: 11355, percent: 100 }, 69 | // sequences: [ 'reads.fastq.gz' ], 70 | // sam: 'alignment.sam' } 71 | // 72 | // Or pipes, for example, from a file with just a list of string like ```reference.fasta reads.fastq alignment.sam``` . 73 | // 74 | // var split = require('split') 75 | // var mem = bwa() // when operation is omitted, 'mem' is used as default 76 | // fs.createReadStream('filenamesList.txt') 77 | // .pipe(split()) 78 | // .pipe(mem()) 79 | // .on('data', console.log) 80 | 81 | 82 | function BBI(params, callback) { 83 | var stream = through.obj(transform) 84 | console.log(params) 85 | console.log('dalibbi') 86 | // if (params) { stream.write(params); stream.end() } 87 | // if (callback) { 88 | // var result 89 | // stream.on('data', function(data) { 90 | // result = data 91 | // }) 92 | // stream.on('end', function() { 93 | // callback(null, result) 94 | // }) 95 | // stream.on('error', callback) 96 | // } 97 | return stream 98 | } 99 | function transform(obj, enc, next) { 100 | var self = this 101 | var file = new nf.NodeFetchable(obj, {start: 0, lenght: 511}) 102 | console.log(file) 103 | dalibbi.connectBBI(file).then(gotConnected, failure).catch(failure) 104 | function gotConnected(connection) { 105 | console.log('connected') 106 | console.log('blah') 107 | var u = connection.getUnzoomedView() 108 | var t = u.fetch() 109 | console.log('buh') 110 | console.dir(Object.getPrototypeOf(u)) 111 | console.log('fim') 112 | u.fetch('chr1', 1, 100000000) 113 | .then(gotFetched, failure).catch(failure) 114 | // connection._readChromTree().then(gotFetched, failure) 115 | // connection._readChromTree().fetch('chr1', 1, 100000000).then(gotFetched, failure) 116 | function gotFetched(data) { 117 | console.log('ola') 118 | console.log(data) 119 | self.push(data) 120 | next() 121 | } 122 | } 123 | } 124 | 125 | 126 | function failure(error) { 127 | console.log(error.stack) 128 | } 129 | -------------------------------------------------------------------------------- /src/spans.js: -------------------------------------------------------------------------------- 1 | // Temporary code from Dalliance to get BBI fetcher working, 2 | // probably doesn't actually belong here. 3 | 4 | "use strict"; 5 | 6 | function Range(min, max) 7 | { 8 | if (typeof(min) != 'number' || typeof(max) != 'number') 9 | throw 'Bad range ' + min + ',' + max; 10 | this._min = min; 11 | this._max = max; 12 | } 13 | 14 | Range.prototype.min = function() { 15 | return this._min; 16 | } 17 | 18 | Range.prototype.max = function() { 19 | return this._max; 20 | } 21 | 22 | Range.prototype.contains = function(pos) { 23 | return pos >= this._min && pos <= this._max; 24 | } 25 | 26 | Range.prototype.isContiguous = function() { 27 | return true; 28 | } 29 | 30 | Range.prototype.ranges = function() { 31 | return [this]; 32 | } 33 | 34 | Range.prototype._pushRanges = function(ranges) { 35 | ranges.push(this); 36 | } 37 | 38 | Range.prototype.toString = function() { 39 | return '[' + this._min + '-' + this._max + ']'; 40 | } 41 | 42 | function _Compound(ranges) { 43 | this._ranges = ranges; 44 | // assert sorted? 45 | } 46 | 47 | _Compound.prototype.min = function() { 48 | return this._ranges[0].min(); 49 | } 50 | 51 | _Compound.prototype.max = function() { 52 | return this._ranges[this._ranges.length - 1].max(); 53 | } 54 | 55 | _Compound.prototype.contains = function(pos) { 56 | // FIXME implement bsearch if we use this much. 57 | for (var s = 0; s < this._ranges.length; ++s) { 58 | if (this._ranges[s].contains(pos)) { 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | _Compound.prototype.isContiguous = function() { 66 | return this._ranges.length > 1; 67 | } 68 | 69 | _Compound.prototype.ranges = function() { 70 | return this._ranges; 71 | } 72 | 73 | _Compound.prototype._pushRanges = function(ranges) { 74 | for (var ri = 0; ri < this._ranges.length; ++ri) 75 | ranges.push(this._ranges[ri]); 76 | } 77 | 78 | _Compound.prototype.toString = function() { 79 | var s = ''; 80 | for (var r = 0; r < this._ranges.length; ++r) { 81 | if (r>0) { 82 | s = s + ','; 83 | } 84 | s = s + this._ranges[r].toString(); 85 | } 86 | return s; 87 | } 88 | 89 | function union(s0, s1) { 90 | if (! (s0 instanceof Array)) { 91 | s0 = [s0]; 92 | if (s1) 93 | s0.push(s1); 94 | } 95 | 96 | if (s0.length == 0) 97 | return null; 98 | else if (s0.length == 1) 99 | return s0[0]; 100 | 101 | var ranges = []; 102 | for (var si = 0; si < s0.length; ++si) 103 | s0[si]._pushRanges(ranges); 104 | ranges = ranges.sort(_rangeOrder); 105 | 106 | var oranges = []; 107 | var current = ranges[0]; 108 | current = new Range(current._min, current._max); // Copy now so we don't have to later. 109 | 110 | for (var i = 1; i < ranges.length; ++i) { 111 | var nxt = ranges[i]; 112 | if (nxt._min > (current._max + 1)) { 113 | oranges.push(current); 114 | current = new Range(nxt._min, nxt._max); 115 | } else { 116 | if (nxt._max > current._max) { 117 | current._max = nxt._max; 118 | } 119 | } 120 | } 121 | oranges.push(current); 122 | 123 | if (oranges.length == 1) { 124 | return oranges[0]; 125 | } else { 126 | return new _Compound(oranges); 127 | } 128 | } 129 | 130 | function intersection(s0, s1) { 131 | var r0 = s0.ranges(); 132 | var r1 = s1.ranges(); 133 | var l0 = r0.length, l1 = r1.length; 134 | var i0 = 0, i1 = 0; 135 | var or = []; 136 | 137 | while (i0 < l0 && i1 < l1) { 138 | var s0 = r0[i0], s1 = r1[i1]; 139 | var lapMin = Math.max(s0.min(), s1.min()); 140 | var lapMax = Math.min(s0.max(), s1.max()); 141 | if (lapMax >= lapMin) { 142 | or.push(new Range(lapMin, lapMax)); 143 | } 144 | if (s0.max() > s1.max()) { 145 | ++i1; 146 | } else { 147 | ++i0; 148 | } 149 | } 150 | 151 | if (or.length == 0) { 152 | return null; // FIXME 153 | } else if (or.length == 1) { 154 | return or[0]; 155 | } else { 156 | return new _Compound(or); 157 | } 158 | } 159 | 160 | function coverage(s) { 161 | var tot = 0; 162 | var rl = s.ranges(); 163 | for (var ri = 0; ri < rl.length; ++ri) { 164 | var r = rl[ri]; 165 | tot += (r.max() - r.min() + 1); 166 | } 167 | return tot; 168 | } 169 | 170 | 171 | 172 | function rangeOrder(a, b) 173 | { 174 | if (a.min() < b.min()) { 175 | return -1; 176 | } else if (a.min() > b.min()) { 177 | return 1; 178 | } else if (a.max() < b.max()) { 179 | return -1; 180 | } else if (b.max() > a.max()) { 181 | return 1; 182 | } else { 183 | return 0; 184 | } 185 | } 186 | 187 | function _rangeOrder(a, b) 188 | { 189 | if (a._min < b._min) { 190 | return -1; 191 | } else if (a._min > b._min) { 192 | return 1; 193 | } else if (a._max < b._max) { 194 | return -1; 195 | } else if (b._max > a._max) { 196 | return 1; 197 | } else { 198 | return 0; 199 | } 200 | } 201 | 202 | if (typeof(module) !== 'undefined') { 203 | module.exports = { 204 | Range: Range, 205 | union: union, 206 | intersection: intersection, 207 | coverage: coverage, 208 | rangeOver: rangeOrder, 209 | _rangeOrder: _rangeOrder 210 | } 211 | } -------------------------------------------------------------------------------- /src/binutils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Imports 4 | 5 | var Promise = require('es6-promise').Promise; 6 | var XMLHttpRequest = require('xhr2') 7 | 8 | function BlobFetchable(b) { 9 | this.blob = b; 10 | } 11 | 12 | BlobFetchable.prototype.slice = function(start, length) { 13 | var b; 14 | 15 | if (this.blob.slice) { 16 | if (length) { 17 | b = this.blob.slice(start, start + length); 18 | } else { 19 | b = this.blob.slice(start); 20 | } 21 | } else { 22 | if (length) { 23 | b = this.blob.webkitSlice(start, start + length); 24 | } else { 25 | b = this.blob.webkitSlice(start); 26 | } 27 | } 28 | return new BlobFetchable(b); 29 | } 30 | 31 | if (typeof(FileReaderSync) !== 'undefined') { 32 | BlobFetchable.prototype.fetch = function() { 33 | return new Promise(function(resolve, reject) { 34 | var reader = new FileReaderSync(); 35 | resolve(reader.readAsArrayBuffer(this.blob)); 36 | }); 37 | } 38 | } else { 39 | BlobFetchable.prototype.fetch = function() { 40 | return new Promise(function(resolve, reject) { 41 | var reader = new FileReader(); 42 | reader.onloadend = function(ev) { 43 | if (reader.error) 44 | reject(reader.error) 45 | else 46 | resolve(reader.result); 47 | }; 48 | reader.readAsArrayBuffer(this.blob); 49 | }); 50 | } 51 | } 52 | 53 | function URLFetchable(url, start, end, opts) { 54 | if (!opts) { 55 | if (typeof start === 'object') { 56 | opts = start; 57 | start = undefined; 58 | } else { 59 | opts = {}; 60 | } 61 | } 62 | 63 | this.url = url; 64 | this.start = start || 0; 65 | if (end) { 66 | this.end = end; 67 | } 68 | this.opts = opts; 69 | } 70 | 71 | URLFetchable.prototype.slice = function(s, l) { 72 | if (s < 0) { 73 | throw Error('Bad slice ' + s); 74 | } 75 | 76 | var ns = this.start, ne = this.end; 77 | if (ns && s) { 78 | ns = ns + s; 79 | } else { 80 | ns = s || ns; 81 | } 82 | if (l && ns) { 83 | ne = ns + l - 1; 84 | } else { 85 | ne = ne || l - 1; 86 | } 87 | return new URLFetchable(this.url, ns, ne, this.opts); 88 | } 89 | 90 | /* 91 | URLFetchable.prototype.fetchAsText = function(callback) { 92 | var req = new XMLHttpRequest(); 93 | var length; 94 | var url = this.url; 95 | req.open('GET', url, true); 96 | 97 | if (this.end) { 98 | if (this.end - this.start > 100000000) { 99 | throw 'Monster fetch!'; 100 | } 101 | req.setRequestHeader('Range', 'bytes=' + this.start + '-' + this.end); 102 | length = this.end - this.start + 1; 103 | } 104 | 105 | req.onreadystatechange = function() { 106 | if (req.readyState == 4) { 107 | if (req.status == 200 || req.status == 206) { 108 | return callback(req.responseText); 109 | } else { 110 | return callback(null); 111 | } 112 | } 113 | }; 114 | if (this.opts.credentials) { 115 | req.withCredentials = true; 116 | } 117 | req.send(''); 118 | } */ 119 | 120 | URLFetchable.prototype.fetch = function() { 121 | var self = this; 122 | 123 | return new Promise(function(resolve, reject) { 124 | var req = new XMLHttpRequest(); 125 | var url = self.url; 126 | req.open('GET', url, true); 127 | req.overrideMimeType('text/plain; charset=x-user-defined'); 128 | if (self.end) { 129 | if (self.end - self.start > 100000000) { 130 | throw Error('Monster fetch!'); 131 | } 132 | req.setRequestHeader('Range', 'bytes=' + self.start + '-' + self.end); 133 | } 134 | req.responseType = 'arraybuffer'; 135 | req.onreadystatechange = function() { 136 | if (req.readyState == 4) { 137 | if (req.status == 200 || req.status == 206) { 138 | if (req.response) { 139 | resolve(req.response); 140 | } else if (req.mozResponseArrayBuffer) { 141 | resolve(req.mozResponseArrayBuffer); 142 | } else { 143 | resolve(bstringToBuffer(req.responseText)); 144 | } 145 | } else { 146 | reject('status=' + req.status); 147 | } 148 | } 149 | }; 150 | if (self.opts.credentials) { 151 | req.withCredentials = true; 152 | } 153 | req.send(); 154 | }); 155 | }; 156 | 157 | // Read from Uint8Array 158 | 159 | (function(global) { 160 | var convertBuffer = new ArrayBuffer(8); 161 | var ba = new Uint8Array(convertBuffer); 162 | var fa = new Float32Array(convertBuffer); 163 | 164 | global.readFloat = function(buf, offset) { 165 | ba[0] = buf[offset]; 166 | ba[1] = buf[offset+1]; 167 | ba[2] = buf[offset+2]; 168 | ba[3] = buf[offset+3]; 169 | return fa[0]; 170 | }; 171 | }(this)); 172 | 173 | function readInt64(ba, offset) { 174 | return (ba[offset + 7] << 24) | (ba[offset + 6] << 16) | (ba[offset + 5] << 8) | (ba[offset + 4]); 175 | } 176 | 177 | function readInt(ba, offset) { 178 | return (ba[offset + 3] << 24) | (ba[offset + 2] << 16) | (ba[offset + 1] << 8) | (ba[offset]); 179 | } 180 | 181 | function readShort(ba, offset) { 182 | return (ba[offset + 1] << 8) | (ba[offset]); 183 | } 184 | 185 | function readByte(ba, offset) { 186 | return ba[offset]; 187 | } 188 | 189 | function readIntBE(ba, offset) { 190 | return (ba[offset] << 24) | (ba[offset + 1] << 16) | (ba[offset + 2] << 8) | (ba[offset + 3]); 191 | } 192 | 193 | // Exports if we are being used as a module 194 | 195 | if (typeof(module) !== 'undefined') { 196 | module.exports = { 197 | BlobFetchable: BlobFetchable, 198 | URLFetchable: URLFetchable, 199 | 200 | readInt: readInt, 201 | readIntBE: readIntBE, 202 | readInt64: readInt64, 203 | readShort: readShort, 204 | readByte: readByte, 205 | readFloat: this.readFloat 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/bbi.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var Promise = require('es6-promise').Promise; 3 | 4 | var spans = require('./spans'); 5 | var Range = spans.Range; 6 | var union = spans.union; 7 | 8 | var bin = require('./binutils'); 9 | 10 | var jszlib = require('jszlib'); 11 | var jszlib_inflate_buffer = jszlib.inflateBuffer; 12 | var arrayCopy = jszlib.arrayCopy; 13 | 14 | var M1 = 256; 15 | var M2 = 256*256; 16 | var M3 = 256*256*256; 17 | var M4 = 256*256*256*256; 18 | 19 | var BIG_WIG_MAGIC = 0x888FFC26; 20 | var BIG_WIG_MAGIC_BE = 0x26FC8F88; 21 | var BIG_BED_MAGIC = 0x8789F2EB; 22 | var BIG_BED_MAGIC_BE = 0xEBF28987; 23 | 24 | var BIG_WIG_TYPE_GRAPH = 1; 25 | var BIG_WIG_TYPE_VSTEP = 2; 26 | var BIG_WIG_TYPE_FSTEP = 3; 27 | 28 | function bwg_readOffset(ba, o) { 29 | return ba[o] + ba[o+1]*M1 + ba[o+2]*M2 + ba[o+3]*M3 + ba[o+4]*M4; 30 | } 31 | 32 | function shallowCopy(o) { 33 | var n = {}; 34 | for (var k in o) { 35 | n[k] = o[k]; 36 | } 37 | return n; 38 | } 39 | 40 | function connectBBI(f, opts) { 41 | opts = opts || {}; 42 | 43 | var bwg = new BBISource(); 44 | bwg.data = f; 45 | 46 | return f.slice(0, 512).fetch() 47 | .then(function(header) { 48 | var ba = new Uint8Array(header); 49 | var sa = new Int16Array(header); 50 | var la = new Int32Array(header); 51 | var magic = ba[0] + (M1 * ba[1]) + (M2 * ba[2]) + (M3 * ba[3]); 52 | if (magic == BIG_WIG_MAGIC) { 53 | bwg.type = 'bigwig'; 54 | } else if (magic == BIG_BED_MAGIC) { 55 | bwg.type = 'bigbed'; 56 | } else if (magic == BIG_WIG_MAGIC_BE || magic == BIG_BED_MAGIC_BE) { 57 | throw Error("Currently don't support big-endian BBI files"); 58 | } else { 59 | throw Error("Not a supported format, magic=0x" + magic.toString(16)); 60 | } 61 | 62 | var version = sa[2]; 63 | if (version < 3 || version > 4) { 64 | throw Error("Unsupported BBI version " + version); 65 | } 66 | bwg.numZoomLevels = sa[3]; // 6 67 | bwg.chromTreeOffset = bwg_readOffset(ba, 8); 68 | bwg.unzoomedDataOffset = bwg_readOffset(ba, 16); 69 | bwg.unzoomedIndexOffset = bwg_readOffset(ba, 24); 70 | bwg.fieldCount = sa[16]; // 32 71 | bwg.definedFieldCount = sa[17]; // 34 72 | bwg.asOffset = bwg_readOffset(ba, 36); 73 | bwg.totalSummaryOffset = bwg_readOffset(ba, 44); 74 | bwg.uncompressBufSize = la[13]; // 52 75 | bwg.extHeaderOffset = bwg_readOffset(ba, 56); 76 | 77 | bwg.zoomLevels = []; 78 | for (var zl = 0; zl < bwg.numZoomLevels; ++zl) { 79 | var zlReduction = la[zl*6 + 16] 80 | var zlData = bwg_readOffset(ba, zl*24 + 72); 81 | var zlIndex = bwg_readOffset(ba, zl*24 + 80); 82 | bwg.zoomLevels.push({reduction: zlReduction, dataOffset: zlData, indexOffset: zlIndex}); 83 | } 84 | 85 | return bwg; 86 | }) 87 | .then(function(bwg) { 88 | return bwg._readChromTree(); 89 | }) 90 | .then(function(bwg) { 91 | return bwg._readAutoSQL(); 92 | }); 93 | } 94 | 95 | function BBISource() { 96 | 97 | } 98 | 99 | BBISource.prototype._readChromTree = function() { 100 | var thisB = this; 101 | this.chromsToIDs = {}; 102 | this.idsToChroms = {}; 103 | this.maxID = 0; 104 | 105 | var udo = this.unzoomedDataOffset; 106 | var eb = (udo - this.chromTreeOffset) & 3; 107 | udo = udo + 4 - eb; 108 | 109 | return this.data.slice(this.chromTreeOffset, udo - this.chromTreeOffset).fetch() 110 | .then(function(bpt) { 111 | var ba = new Uint8Array(bpt); 112 | var sa = new Int16Array(bpt); 113 | var la = new Int32Array(bpt); 114 | var bptMagic = la[0]; 115 | var blockSize = la[1]; 116 | var keySize = la[2]; 117 | var valSize = la[3]; 118 | var itemCount = bwg_readOffset(ba, 16); 119 | var rootNodeOffset = 32; 120 | 121 | var bptReadNode = function(offset) { 122 | var nodeType = ba[offset]; 123 | var cnt = sa[(offset/2) + 1]; 124 | offset += 4; 125 | for (var n = 0; n < cnt; ++n) { 126 | if (nodeType == 0) { 127 | offset += keySize; 128 | var childOffset = bwg_readOffset(ba, offset); 129 | offset += 8; 130 | childOffset -= thisB.chromTreeOffset; 131 | bptReadNode(childOffset); 132 | } else { 133 | var key = ''; 134 | for (var ki = 0; ki < keySize; ++ki) { 135 | var charCode = ba[offset++]; 136 | if (charCode != 0) { 137 | key += String.fromCharCode(charCode); 138 | } 139 | } 140 | var chromId = (ba[offset+3]<<24) | (ba[offset+2]<<16) | (ba[offset+1]<<8) | (ba[offset+0]); 141 | var chromSize = (ba[offset + 7]<<24) | (ba[offset+6]<<16) | (ba[offset+5]<<8) | (ba[offset+4]); 142 | offset += 8; 143 | 144 | thisB.chromsToIDs[key] = chromId; 145 | if (key.indexOf('chr') == 0) { 146 | thisB.chromsToIDs[key.substr(3)] = chromId; 147 | } 148 | thisB.idsToChroms[chromId] = key; 149 | thisB.maxID = Math.max(thisB.maxID, chromId); 150 | } 151 | } 152 | }; 153 | bptReadNode(rootNodeOffset); 154 | 155 | return thisB; 156 | }); 157 | } 158 | 159 | BBISource.prototype._readAutoSQL = function() { 160 | return this; 161 | } 162 | 163 | BBISource.prototype.getUnzoomedView = function() { 164 | return new BBIView(this, this.unzoomedIndexOffset); 165 | } 166 | 167 | function BBIView(bbi, cirTreeOffset) { 168 | this.bbi = bbi; 169 | this.cirTreeOffset = cirTreeOffset; 170 | } 171 | 172 | BBIView.prototype.fetch = function(seqName, min, max) { 173 | var self = this; 174 | var chr = this.bbi.chromsToIDs[seqName]; 175 | if (chr === undefined) { 176 | // Not an error because some files won't have data for all chromosomes. 177 | return Promise.resolve([]); 178 | } else { 179 | return self.readWigDataById(chr, min, max); 180 | } 181 | } 182 | 183 | BBIView.prototype.readWigDataById = function(chr, min, max) { 184 | var thisB = this; 185 | if (!this.cirHeader) { 186 | return this.bbi.data.slice(this.cirTreeOffset, 48).fetch() 187 | .then(function(result) { 188 | thisB.cirHeader = result; 189 | var la = new Int32Array(thisB.cirHeader); 190 | thisB.cirBlockSize = la[1]; 191 | return thisB.readWigDataById(chr, min, max); 192 | }); 193 | return; 194 | } 195 | 196 | return new Promise(function(resolve, reject) { 197 | var blocksToFetch = []; 198 | var outstanding = 0; 199 | 200 | var filter = function(chromId, fmin, fmax, toks) { 201 | return ((chr < 0 || chromId == chr) && fmin <= max && fmax >= min); 202 | } 203 | 204 | var cirFobRecur = function(offset, level) { 205 | outstanding += offset.length; 206 | 207 | if (offset.length == 1 && offset[0] - thisB.cirTreeOffset == 48 && thisB.cachedCirRoot) { 208 | cirFobRecur2(thisB.cachedCirRoot, 0, level); 209 | --outstanding; 210 | if (outstanding == 0) { 211 | resolve(thisB.fetchFeatures(filter, blocksToFetch)); 212 | } 213 | return; 214 | } 215 | 216 | var maxCirBlockSpan = 4 + (thisB.cirBlockSize * 32); // Upper bound on size, based on a completely full leaf node. 217 | var spans; 218 | for (var i = 0; i < offset.length; ++i) { 219 | var blockSpan = new Range(offset[i], offset[i] + maxCirBlockSpan); 220 | spans = spans ? union(spans, blockSpan) : blockSpan; 221 | } 222 | 223 | var fetchRanges = spans.ranges(); 224 | for (var r = 0; r < fetchRanges.length; ++r) { 225 | var fr = fetchRanges[r]; 226 | cirFobStartFetch(offset, fr, level); 227 | } 228 | } 229 | 230 | var cirFobStartFetch = function(offset, fr, level, attempts) { 231 | var length = fr.max() - fr.min(); 232 | thisB.bbi.data.slice(fr.min(), fr.max() - fr.min()).fetch(). 233 | then(function(resultBuffer) { 234 | for (var i = 0; i < offset.length; ++i) { 235 | if (fr.contains(offset[i])) { 236 | cirFobRecur2(resultBuffer, offset[i] - fr.min(), level); 237 | 238 | if (offset[i] - thisB.cirTreeOffset == 48 && offset[i] - fr.min() == 0) 239 | thisB.cachedCirRoot = resultBuffer; 240 | 241 | --outstanding; 242 | if (outstanding == 0) { 243 | resolve(thisB.fetchFeatures(filter, blocksToFetch)); 244 | } 245 | } 246 | } 247 | }); 248 | } 249 | 250 | var cirFobRecur2 = function(cirBlockData, offset, level) { 251 | var ba = new Uint8Array(cirBlockData); 252 | var sa = new Int16Array(cirBlockData); 253 | var la = new Int32Array(cirBlockData); 254 | 255 | var isLeaf = ba[offset]; 256 | var cnt = sa[offset/2 + 1]; 257 | offset += 4; 258 | 259 | if (isLeaf != 0) { 260 | for (var i = 0; i < cnt; ++i) { 261 | var lo = offset/4; 262 | var startChrom = la[lo]; 263 | var startBase = la[lo + 1]; 264 | var endChrom = la[lo + 2]; 265 | var endBase = la[lo + 3]; 266 | var blockOffset = bwg_readOffset(ba, offset+16); 267 | var blockSize = bwg_readOffset(ba, offset+24); 268 | if (((chr < 0 || startChrom < chr) || (startChrom == chr && startBase <= max)) && 269 | ((chr < 0 || endChrom > chr) || (endChrom == chr && endBase >= min))) 270 | { 271 | blocksToFetch.push({offset: blockOffset, size: blockSize}); 272 | } 273 | offset += 32; 274 | } 275 | } else { 276 | var recurOffsets = []; 277 | for (var i = 0; i < cnt; ++i) { 278 | var lo = offset/4; 279 | var startChrom = la[lo]; 280 | var startBase = la[lo + 1]; 281 | var endChrom = la[lo + 2]; 282 | var endBase = la[lo + 3]; 283 | var blockOffset = bwg_readOffset(ba, offset+16); 284 | if ((chr < 0 || startChrom < chr || (startChrom == chr && startBase <= max)) && 285 | (chr < 0 || endChrom > chr || (endChrom == chr && endBase >= min))) 286 | { 287 | recurOffsets.push(blockOffset); 288 | } 289 | offset += 24; 290 | } 291 | if (recurOffsets.length > 0) { 292 | cirFobRecur(recurOffsets, level + 1); 293 | } 294 | } 295 | }; 296 | 297 | cirFobRecur([thisB.cirTreeOffset + 48], 1); 298 | }); 299 | } 300 | 301 | 302 | BBIView.prototype.createFeature = function(chr, fmin, fmax, opts) { 303 | var f = { 304 | _chromId: chr, 305 | seqName: this.bbi.idsToChroms[chr], 306 | min: fmin, 307 | max: fmax 308 | }; 309 | 310 | if (opts) { 311 | for (var k in opts) { 312 | f[k] = opts[k]; 313 | } 314 | } 315 | return f; 316 | } 317 | 318 | BBIView.prototype.fetchFeatures = function(filter, blocks) { 319 | var thisB = this; 320 | 321 | blocks.sort(function(b0, b1) { 322 | return (b0.offset|0) - (b1.offset|0); 323 | }); 324 | 325 | var blocksToFetch = []; 326 | if (blocks.length > 0) { 327 | var current = shallowCopy(blocks[0]); 328 | current.offsets = [current.offset]; 329 | for (var bi = 1; bi < blocks.length; ++bi) { 330 | var b = blocks[bi]; 331 | if (b.offset <= (current.offset + current.size)) { 332 | current.size = b.offset + b.size - current.offset; 333 | current.offsets.push(b.offset); 334 | 335 | } else { 336 | blocksToFetch.push(current); 337 | current = shallowCopy(b); 338 | current.offsets = [current.offset]; 339 | } 340 | } 341 | blocksToFetch.push(current); 342 | } 343 | 344 | var features = []; 345 | var tramp = function(bi) { 346 | if (bi >= blocksToFetch.length) { 347 | return Promise.resolve(features); 348 | } else { 349 | var block = blocksToFetch[bi]; 350 | return thisB.bbi.data.slice(block.offset, block.size).fetch() 351 | .then(function(result) { 352 | for (var oi = 0; oi < block.offsets.length; ++oi) { 353 | if (thisB.bbi.uncompressBufSize > 0) { 354 | var data = jszlib_inflate_buffer(result, block.offsets[oi] - block.offset + 2); 355 | thisB.parseFeatures(data, 0, data.byteLength, filter, features); 356 | } else { 357 | thisB.parseFeatures( 358 | result, 359 | block.offsets[oi] - block.offset, 360 | oi < block.offsets.length - 1 ? block.offsets[oi + 1] - block.offset : result.byteLength, 361 | block.filter, 362 | features); 363 | } 364 | } 365 | return tramp(bi + 1); 366 | }); 367 | } 368 | }; 369 | return tramp(0); 370 | } 371 | 372 | BBIView.prototype.parseFeatures = function(data, offset, limit, filter, features) { 373 | var ba = new Uint8Array(data, offset); 374 | 375 | if (this.isSummary) { 376 | var sa = new Int16Array(data, offset, ((limit - offset) / 2) | 0); 377 | var la = new Int32Array(data, offset, ((limit - offset) / 4) | 0); 378 | var fa = new Float32Array(data, offset, ((limit - offset) / 4) | 0); 379 | 380 | var itemCount = data.byteLength/32; 381 | for (var i = 0; i < itemCount; ++i) { 382 | var chromId = la[(i*8)]; 383 | var start = la[(i*8)+1]; 384 | var end = la[(i*8)+2]; 385 | var validCnt = la[(i*8)+3]; 386 | var minVal = fa[(i*8)+4]; 387 | var maxVal = fa[(i*8)+5]; 388 | var sumData = fa[(i*8)+6]; 389 | var sumSqData = fa[(i*8)+7]; 390 | 391 | if (filter(chromId, start + 1, end)) { 392 | var summaryOpts = {type: 'bigwig', score: sumData/validCnt, maxScore: maxVal}; 393 | if (this.bbi.type == 'bigbed') { 394 | summaryOpts.type = 'density'; 395 | } 396 | features.push(this.createFeature(chromId, start + 1, end, summaryOpts)); 397 | } 398 | } 399 | } else if (this.bbi.type == 'bigwig') { 400 | var sa = new Int16Array(data, offset, ((limit - offset) / 2) | 0); 401 | var la = new Int32Array(data, offset, ((limit - offset) / 4) | 0); 402 | var fa = new Float32Array(data, offset, ((limit - offset) / 4) | 0); 403 | 404 | var chromId = la[0]; 405 | var blockStart = la[1]; 406 | var blockEnd = la[2]; 407 | var itemStep = la[3]; 408 | var itemSpan = la[4]; 409 | var blockType = ba[20]; 410 | var itemCount = sa[11]; 411 | 412 | if (blockType == BIG_WIG_TYPE_FSTEP) { 413 | for (var i = 0; i < itemCount; ++i) { 414 | var score = fa[i + 6]; 415 | var fmin = blockStart + (i*itemStep) + 1, fmax = blockStart + (i*itemStep) + itemSpan; 416 | if (filter(chromId, fmin, fmax)) 417 | features.push(this.createFeature(chromId, fmin, fmax, {score: score})); 418 | } 419 | return 24 + (itemCount * 4); 420 | } else if (blockType == BIG_WIG_TYPE_VSTEP) { 421 | for (var i = 0; i < itemCount; ++i) { 422 | var start = la[(i*2) + 6] + 1; 423 | var end = start + itemSpan - 1; 424 | var score = fa[(i*2) + 7]; 425 | if (filter(chromId, start, end)) 426 | features.push(this.createFeature(chromId, start, end, {score: score})); 427 | } 428 | return 24 + (itemCount * 8); 429 | } else if (blockType == BIG_WIG_TYPE_GRAPH) { 430 | for (var i = 0; i < itemCount; ++i) { 431 | var start = la[(i*3) + 6] + 1; 432 | var end = la[(i*3) + 7]; 433 | var score = fa[(i*3) + 8]; 434 | if (start > end) { 435 | start = end; 436 | } 437 | if (filter(chromId, start, end)) 438 | features.push(this.createFeature(chromId, start, end, {score: score})); 439 | } 440 | } else { 441 | throw Error('Currently not handling bwgType=' + blockType); 442 | } 443 | } else if (this.bbi.type == 'bigbed') { 444 | limit -= offset; 445 | offset = 0; 446 | 447 | var dfc = this.bbi.definedFieldCount; 448 | var schema = this.bbi.schema; 449 | 450 | while (offset < ba.length) { 451 | var chromId = (ba[offset+3]<<24) | (ba[offset+2]<<16) | (ba[offset+1]<<8) | (ba[offset+0]); 452 | var start = (ba[offset+7]<<24) | (ba[offset+6]<<16) | (ba[offset+5]<<8) | (ba[offset+4]); 453 | var end = (ba[offset+11]<<24) | (ba[offset+10]<<16) | (ba[offset+9]<<8) | (ba[offset+8]); 454 | offset += 12; 455 | var rest = ''; 456 | while (true) { 457 | var ch = ba[offset++]; 458 | if (ch != 0) { 459 | rest += String.fromCharCode(ch); 460 | } else { 461 | break; 462 | } 463 | } 464 | 465 | var featureOpts = {}; 466 | 467 | var bedColumns; 468 | if (rest.length > 0) { 469 | bedColumns = rest.split('\t'); 470 | } else { 471 | bedColumns = []; 472 | } 473 | if (bedColumns.length > 0 && dfc > 3) { 474 | featureOpts.label = bedColumns[0]; 475 | } 476 | if (bedColumns.length > 1 && dfc > 4) { 477 | var score = parseInt(bedColumns[1]); 478 | if (!isNaN(score)) 479 | featureOpts.score = score; 480 | } 481 | if (bedColumns.length > 2 && dfc > 5) { 482 | featureOpts.orientation = bedColumns[2]; 483 | } 484 | if (bedColumns.length > 5 && dfc > 8) { 485 | var color = bedColumns[5]; 486 | if (BED_COLOR_REGEXP.test(color)) { 487 | featureOpts.itemRgb = 'rgb(' + color + ')'; 488 | } 489 | } 490 | 491 | if (bedColumns.length > dfc-3 && schema) { 492 | for (var col = dfc - 3; col < bedColumns.length; ++col) { 493 | featureOpts[schema.fields[col+3].name] = bedColumns[col]; 494 | } 495 | } 496 | 497 | if (filter(chromId, start + 1, end, bedColumns)) { 498 | if (dfc < 12) { 499 | features.push(this.createFeature(chromId, start + 1, end, featureOpts)); 500 | } else { 501 | var thickStart = bedColumns[3]|0; 502 | var thickEnd = bedColumns[4]|0; 503 | var blockCount = bedColumns[6]|0; 504 | var blockSizes = bedColumns[7].split(','); 505 | var blockStarts = bedColumns[8].split(','); 506 | 507 | featureOpts.type = 'transcript'; 508 | var grp = {}; 509 | for (var k in featureOpts) { 510 | grp[k] = featureOpts[k]; 511 | } 512 | grp.id = bedColumns[0]; 513 | grp.segment = this.bbi.idsToChroms[chromId]; 514 | grp.min = start + 1; 515 | grp.max = end; 516 | grp.notes = []; 517 | featureOpts.groups = [grp]; 518 | 519 | if (bedColumns.length > 9) { 520 | var geneId = bedColumns[9]; 521 | var geneName = geneId; 522 | if (bedColumns.length > 10) { 523 | geneName = bedColumns[10]; 524 | } 525 | var gg = shallowCopy(grp); 526 | gg.id = geneId; 527 | gg.label = geneName; 528 | gg.type = 'gene'; 529 | featureOpts.groups.push(gg); 530 | } 531 | 532 | var spanList = []; 533 | for (var b = 0; b < blockCount; ++b) { 534 | var bmin = (blockStarts[b]|0) + start; 535 | var bmax = bmin + (blockSizes[b]|0); 536 | var span = new Range(bmin, bmax); 537 | spanList.push(span); 538 | } 539 | var spans = union(spanList); 540 | 541 | var tsList = spans.ranges(); 542 | for (var s = 0; s < tsList.length; ++s) { 543 | var ts = tsList[s]; 544 | features.push(this.createFeature(chromId, ts.min() + 1, ts.max(), featureOpts)); 545 | } 546 | 547 | if (thickEnd > thickStart) { 548 | var tl = intersection(spans, new Range(thickStart, thickEnd)); 549 | if (tl) { 550 | featureOpts.type = 'translation'; 551 | var tlList = tl.ranges(); 552 | for (var s = 0; s < tlList.length; ++s) { 553 | var ts = tlList[s]; 554 | features.push(this.createFeature(chromId, ts.min() + 1, ts.max(), featureOpts)); 555 | } 556 | } 557 | } 558 | } 559 | } 560 | } 561 | } else { 562 | throw Error("Don't know what to do with " + this.bbi.type); 563 | } 564 | } 565 | 566 | module.exports = { 567 | connectBBI: connectBBI, 568 | URLFetchable: bin.URLFetchable 569 | } 570 | --------------------------------------------------------------------------------