UNPKG

@jbrowse/plugin-wiggle

Version:

JBrowse 2 wiggle adapters, tracks, etc.

182 lines (181 loc) 6.78 kB
import { ArrayFeatureView, BigWig } from '@gmod/bbi'; import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'; import { aggregateQuantitativeStats, blankStats, } from '@jbrowse/core/data_adapters/BaseAdapter/stats'; import { updateStatus } from '@jbrowse/core/util'; import { openLocation } from '@jbrowse/core/util/io'; import { ObservableCreate } from '@jbrowse/core/util/rxjs'; import { rectifyStats } from '@jbrowse/core/util/stats'; function computeStatsFromView(view, targetStart, targetEnd) { const len = view.length; if (len === 0) { return { scoreMin: 0, scoreMax: 0, scoreSum: 0, scoreSumSquares: 0, scoreMean: 0, scoreStdDev: 0, featureCount: 0, basesCovered: targetEnd - targetStart, featureDensity: 0, }; } let scoreMin = Number.MAX_VALUE; let scoreMax = Number.MIN_VALUE; let scoreMeanMin = Number.MAX_VALUE; let scoreMeanMax = Number.MIN_VALUE; let scoreSum = 0; let scoreSumSquares = 0; let featureCount = 0; for (let i = 0; i < len; i++) { const featureStart = view.start(i); const featureEnd = view.end(i); if (featureEnd <= targetStart || featureStart >= targetEnd) { continue; } const score = view.score(i); const min = view.minScore(i) ?? score; const max = view.maxScore(i) ?? score; scoreMin = Math.min(scoreMin, min); scoreMax = Math.max(scoreMax, max); scoreMeanMin = Math.min(scoreMeanMin, score); scoreMeanMax = Math.max(scoreMeanMax, score); scoreSum += score; scoreSumSquares += score * score; featureCount++; } if (featureCount === 0) { return { scoreMin: 0, scoreMax: 0, scoreSum: 0, scoreSumSquares: 0, scoreMean: 0, scoreStdDev: 0, featureCount: 0, basesCovered: targetEnd - targetStart, featureDensity: 0, }; } const scoreMean = scoreSum / featureCount; const scoreStdDev = Math.sqrt(scoreSumSquares / featureCount - scoreMean * scoreMean); return { scoreMin, scoreMax, scoreMeanMin, scoreMeanMax, scoreSum, scoreSumSquares, scoreMean, scoreStdDev, featureCount, basesCovered: targetEnd - targetStart, featureDensity: featureCount / (targetEnd - targetStart), }; } export default class BigWigAdapter extends BaseFeatureDataAdapter { setupP; static capabilities = [ 'hasResolution', 'hasLocalStats', 'hasGlobalStats', ]; async setupPre(opts) { const { statusCallback = () => { } } = opts || {}; const pluginManager = this.pluginManager; const bigwig = new BigWig({ filehandle: openLocation(this.getConf('bigWigLocation'), pluginManager), }); return { bigwig, header: await updateStatus('Downloading bigwig header', statusCallback, () => bigwig.getHeader(opts)), }; } async setup(opts) { if (!this.setupP) { this.setupP = this.setupPre(opts).catch((e) => { this.setupP = undefined; throw e; }); } return this.setupP; } async getRefNames(opts) { const { header } = await this.setup(opts); return Object.keys(header.refsByName); } async refIdToName(refId) { const { header } = await this.setup(); return header.refsByNumber[refId]?.name; } async getGlobalStats(opts) { const { header } = await this.setup(opts); return rectifyStats(header.totalSummary); } getFeatures(region, opts = {}) { const { refName, start, end } = region; const { bpPerPx = 0, resolution = 1, stopToken, statusCallback = () => { }, } = opts; const source = this.getConf('source'); const resolutionMultiplier = this.getConf('resolutionMultiplier'); return ObservableCreate(async (observer) => { const { bigwig } = await this.setup(opts); const arrays = await updateStatus('Downloading bigwig data', statusCallback, () => bigwig.getFeaturesAsArrays(refName, start, end, { ...opts, basesPerSpan: (bpPerPx / resolution) * resolutionMultiplier, })); const view = new ArrayFeatureView(arrays, source, refName); for (let i = 0; i < view.length; i++) { const uniqueId = view.id(i); const idx = i; observer.next({ get: (str) => view.get(idx, str), id: () => uniqueId, toJSON: () => ({ start: view.start(idx), end: view.end(idx), score: view.score(idx), refName, source, uniqueId, summary: view.isSummary, minScore: view.minScore(idx), maxScore: view.maxScore(idx), }), }); } observer.complete(); }, stopToken); } async getArrayFeatureView(region, opts = {}) { const { refName, start, end } = region; const { bpPerPx = 0, resolution = 1, statusCallback = () => { } } = opts; const resolutionMultiplier = this.getConf('resolutionMultiplier'); const source = this.getConf('source'); const { bigwig } = await this.setup(opts); const arrays = await updateStatus('Downloading bigwig data', statusCallback, () => bigwig.getFeaturesAsArrays(refName, start, end, { ...opts, basesPerSpan: (bpPerPx / resolution) * resolutionMultiplier, })); return new ArrayFeatureView(arrays, source, refName); } async getRegionQuantitativeStats(region, opts) { const { start, end } = region; const view = await this.getArrayFeatureView(region, { ...opts, bpPerPx: (end - start) / 1000, }); return computeStatsFromView(view, start, end); } async getMultiRegionFeatureDensityStats(_regions) { return { featureDensity: 0, }; } async getMultiRegionQuantitativeStats(regions = [], opts = {}) { if (!regions.length) { return blankStats(); } const stats = await Promise.all(regions.map(region => this.getRegionQuantitativeStats(region, opts))); return aggregateQuantitativeStats(stats); } }