-
Notifications
You must be signed in to change notification settings - Fork 60
/
HicAdapter.ts
152 lines (134 loc) · 4.08 KB
/
HicAdapter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import {
BaseFeatureDataAdapter,
BaseOptions,
} from '@jbrowse/core/data_adapters/BaseAdapter'
import { Region, FileLocation } from '@jbrowse/core/util/types'
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
import { openLocation } from '@jbrowse/core/util/io'
import type { GenericFilehandle } from 'generic-filehandle'
import HicStraw from 'hic-straw'
import PluginManager from '@jbrowse/core/PluginManager'
import { getSubAdapterType } from '@jbrowse/core/data_adapters/dataAdapterCache'
import { AnyConfigurationModel } from '@jbrowse/core/configuration'
interface ContactRecord {
bin1: number
bin2: number
counts: number
}
interface HicMetadata {
chromosomes: {
name: string
length: number
id: number
}[]
resolutions: number[]
}
interface Ref {
chr: string
start: number
end: number
}
interface HicOptions extends BaseOptions {
resolution?: number
bpPerPx?: number
}
// wraps generic-filehandle so the read function only takes a position and
// length in some ways, generic-filehandle wishes it was just this but it has
// to adapt to the node.js fs promises API
class GenericFilehandleWrapper {
constructor(private filehandle: GenericFilehandle) {}
async read(position: number, length: number) {
const { buffer } = await this.filehandle.read(
Buffer.alloc(length),
0,
length,
position,
)
return buffer.buffer.slice(
buffer.byteOffset,
buffer.byteOffset + buffer.byteLength,
)
}
}
export function openFilehandleWrapper(
location: FileLocation,
pluginManager?: PluginManager,
) {
return new GenericFilehandleWrapper(openLocation(location, pluginManager))
}
interface HicParser {
getContactRecords: (
normalize: string,
ref: Ref,
ref2: Ref,
units: string,
binsize: number,
) => Promise<ContactRecord[]>
getMetaData: () => Promise<HicMetadata>
}
export default class HicAdapter extends BaseFeatureDataAdapter {
private hic: HicParser
public constructor(
config: AnyConfigurationModel,
getSubAdapter?: getSubAdapterType,
pluginManager?: PluginManager,
) {
super(config, getSubAdapter, pluginManager)
const hicLocation = this.getConf('hicLocation')
this.hic = new HicStraw({
file: openFilehandleWrapper(hicLocation, this.pluginManager),
})
}
private async setup(opts?: BaseOptions) {
const { statusCallback = () => {} } = opts || {}
statusCallback('Downloading .hic header')
const result = await this.hic.getMetaData()
statusCallback('')
return result
}
public async getHeader(opts?: BaseOptions) {
const ret = await this.setup(opts)
const { chromosomes, ...rest } = ret
return rest
}
async getRefNames(opts?: BaseOptions) {
const metadata = await this.setup(opts)
return metadata.chromosomes.map(chr => chr.name)
}
async getResolution(bpPerPx: number, opts?: BaseOptions) {
const { resolutions } = await this.setup(opts)
let chosenResolution = resolutions.at(-1)!
for (let i = resolutions.length - 1; i >= 0; i -= 1) {
const r = resolutions[i]
if (r <= 2 * bpPerPx) {
chosenResolution = r
}
}
return chosenResolution
}
getFeatures(region: Region, opts: HicOptions = {}) {
return ObservableCreate<ContactRecord>(async observer => {
const { refName: chr, start, end } = region
const { resolution, bpPerPx = 1, statusCallback = () => {} } = opts
const res = await this.getResolution(bpPerPx / (resolution || 1000), opts)
statusCallback('Downloading .hic data')
const records = await this.hic.getContactRecords(
'KR',
{ start, chr, end },
{ start, chr, end },
'BP',
res,
)
for (const record of records) {
observer.next(record)
}
statusCallback('')
observer.complete()
}, opts.signal) as any // eslint-disable-line @typescript-eslint/no-explicit-any
}
// don't do feature stats estimation, similar to bigwigadapter
async getMultiRegionFeatureDensityStats(_regions: Region[]) {
return { featureDensity: 0 }
}
freeResources(/* { region } */): void {}
}