Skip to content

Commit

Permalink
Merge pull request #1624 from GMOD/gccontent_adapter
Browse files Browse the repository at this point in the history
GCContent adapter
  • Loading branch information
rbuels authored Jan 22, 2021
2 parents 8c5d290 + e424990 commit 8a29c3d
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugins/sequence/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"peerDependencies": {
"@jbrowse/core": "^1.0.0",
"@jbrowse/plugin-linear-genome-view": "^1.0.0",
"@jbrowse/plugin-wiggle": "^1.0.0",
"@material-ui/core": "^4.9.13",
"mobx-react": "^6.0.0",
"mobx-state-tree": "3.14.1",
Expand Down
174 changes: 174 additions & 0 deletions plugins/sequence/src/GCContentAdapter/GCContentAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {
BaseFeatureDataAdapter,
RegionsAdapter,
BaseOptions,
} from '@jbrowse/core/data_adapters/BaseAdapter'
import { Region, NoAssemblyRegion } from '@jbrowse/core/util/types'
import { getSubAdapterType } from '@jbrowse/core/data_adapters/dataAdapterCache'
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
import SimpleFeature, { Feature } from '@jbrowse/core/util/simpleFeature'
import { readConfObject } from '@jbrowse/core/configuration'
import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema'
import { toArray } from 'rxjs/operators'

import { blankStats, rectifyStats, scoresToStats } from '@jbrowse/plugin-wiggle'

export default class extends BaseFeatureDataAdapter implements RegionsAdapter {
private sequenceAdapter: BaseFeatureDataAdapter

private windowSize = 1000

private windowDelta = 1000

private gcMode = 'content'

public static capabilities = ['hasLocalStats']

public constructor(
config: AnyConfigurationModel,
getSubAdapter?: getSubAdapterType,
) {
super(config)
// instantiate the sequence adapter
const sequenceAdapterType = readConfObject(config, [
'sequenceAdapter',
'type',
])

const dataAdapter = getSubAdapter?.(
readConfObject(config, 'sequenceAdapter'),
).dataAdapter
if (dataAdapter instanceof BaseFeatureDataAdapter) {
this.sequenceAdapter = dataAdapter
} else {
throw new Error(
`Feature adapters cannot use sequence adapters of type '${sequenceAdapterType}'`,
)
}
}

public getRefNames() {
return this.sequenceAdapter.getRefNames()
}

public async getRegions(): Promise<NoAssemblyRegion[]> {
// @ts-ignore
return this.sequenceAdapter.getRegions()
}

// Taken from bigwigadapter
public getRegionStats(region: Region, opts: BaseOptions) {
const feats = this.getFeatures(region, opts)
return scoresToStats(region, feats)
}

// Taken from bigwigadapter
public async getMultiRegionStats(regions: Region[] = [], opts: BaseOptions) {
if (!regions.length) {
return blankStats()
}
const feats = await Promise.all(
regions.map(region => this.getRegionStats(region, opts)),
)

const scoreMax = feats
.map(s => s.scoreMax)
.reduce((acc, curr) => Math.max(acc, curr))
const scoreMin = feats
.map(s => s.scoreMin)
.reduce((acc, curr) => Math.min(acc, curr))
const scoreSum = feats.map(s => s.scoreSum).reduce((a, b) => a + b, 0)
const scoreSumSquares = feats
.map(s => s.scoreSumSquares)
.reduce((a, b) => a + b, 0)
const featureCount = feats
.map(s => s.featureCount)
.reduce((a, b) => a + b, 0)
const basesCovered = feats
.map(s => s.basesCovered)
.reduce((a, b) => a + b, 0)

return rectifyStats({
scoreMin,
scoreMax,
featureCount,
basesCovered,
scoreSumSquares,
scoreSum,
})
}

/**
* Fetch features for a certain region
* @param param -
* @returns Observable of Feature objects in the region
*/
public getFeatures(query: Region, opts: BaseOptions) {
this.windowSize = 1000
this.windowDelta = 1000
this.gcMode = 'content'
return ObservableCreate<Feature>(async observer => {
const hw = this.windowSize === 1 ? 1 : this.windowSize / 2 // Half the window size
const f = this.windowSize === 1

let { start: queryStart, end: queryEnd } = query
queryStart = Math.max(0, queryStart - hw)
queryEnd += hw

if (queryEnd < 0 || queryStart > queryEnd) {
observer.complete()
return
}

const ret = this.sequenceAdapter.getFeatures(
{ ...query, start: queryStart, end: queryEnd },
opts,
)
const [feat] = await ret.pipe(toArray()).toPromise()
const residues = feat.get('seq')

for (let i = hw; i < residues.length - hw; i += this.windowDelta) {
const r = f ? residues[i] : residues.slice(i - hw, i + hw)
let nc = 0
let ng = 0
let len = 0
for (let j = 0; j < r.length; j++) {
if (r[j] === 'c' || r[j] === 'C') {
nc++
} else if (r[j] === 'g' || r[j] === 'G') {
ng++
}
if (r[j] !== 'N') {
len++
}
}
const pos = queryStart
let score
if (this.gcMode === 'content') {
score = (ng + nc) / (len || 1)
} else if (this.gcMode === 'skew') {
score = (ng - nc) / (ng + nc || 1)
}

// if (r[Math.floor(r.length / 2)] !== 'N') {
observer.next(
new SimpleFeature({
uniqueId: `${this.id}_${pos + i}`,
start: pos + i,
end: pos + i + this.windowDelta,
score,
}),
)
// }
}
observer.complete()
})
}

/**
* called to provide a hint that data tied to a certain region
* will not be needed for the forseeable future and can be purged
* from caches, etc
*/
public freeResources(/* { region } */): void {}
}
12 changes: 12 additions & 0 deletions plugins/sequence/src/GCContentAdapter/configSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import PluginManager from '@jbrowse/core/PluginManager'
import { ConfigurationSchema } from '@jbrowse/core/configuration'

export default (pluginManager: PluginManager) => {
return ConfigurationSchema(
'GCContentAdapter',
{
sequenceAdapter: pluginManager.pluggableConfigSchemaType('adapter'),
},
{ explicitlyTyped: true },
)
}
10 changes: 10 additions & 0 deletions plugins/sequence/src/GCContentAdapter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import PluginManager from '@jbrowse/core/PluginManager'
import configSchemaF from './configSchema'
import AdapterClass from './GCContentAdapter'

export default (pluginManager: PluginManager) => {
return {
configSchema: pluginManager.load(configSchemaF),
AdapterClass,
}
}
8 changes: 8 additions & 0 deletions plugins/sequence/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
AdapterClass as TwoBitAdapterClass,
configSchema as twoBitAdapterConfigSchema,
} from './TwoBitAdapter'
import GCContentAdapterF from './GCContentAdapter'
import { createReferenceSeqTrackConfig } from './referenceSeqTrackConfig'

/* adjust in both directions */
Expand Down Expand Up @@ -83,6 +84,13 @@ export default class SequencePlugin extends Plugin {
}),
)

pluginManager.addAdapterType(
() =>
new AdapterType({
name: 'GCContentAdapter',
...pluginManager.load(GCContentAdapterF),
}),
)
pluginManager.addTrackType(() => {
const configSchema = createReferenceSeqTrackConfig(pluginManager)

Expand Down
1 change: 1 addition & 0 deletions plugins/wiggle/src/LinearWiggleDisplay/models/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ const stateModelFactory = (
},
)) as FeatureStats
const { scoreMin, scoreMean, scoreStdDev } = results

// localsd uses heuristic to avoid unnecessary scoreMin<0
// if the scoreMin is never less than 0
// helps with most coverage bigwigs just being >0
Expand Down
22 changes: 22 additions & 0 deletions test_data/config_demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,28 @@
}
}
},
{
"type": "QuantitativeTrack",
"trackId": "gccontent_hg19",
"name": "GCContent",
"assemblyNames": ["hg19"],
"adapter": {
"type": "GCContentAdapter",
"sequenceAdapter": {
"type": "BgzipFastaAdapter",
"fastaLocation": {
"uri": "https://jbrowse.org/genomes/hg19/fasta/hg19.fa.gz"
},
"faiLocation": {
"uri": "https://jbrowse.org/genomes/hg19/fasta/hg19.fa.gz.fai"
},
"gziLocation": {
"uri": "https://jbrowse.org/genomes/hg19/fasta/hg19.fa.gz.gzi"
}
}
}
},

{
"type": "AlignmentsTrack",
"trackId": "illumina_hg002",
Expand Down

0 comments on commit 8a29c3d

Please sign in to comment.