forked from samtools/samtools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bedidx.c
233 lines (208 loc) · 6.52 KB
/
bedidx.c
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <zlib.h>
#ifdef _WIN32
#define drand48() ((double)rand() / RAND_MAX)
#endif
#include "htslib/ksort.h"
KSORT_INIT_GENERIC(uint64_t)
#include "htslib/kseq.h"
KSTREAM_INIT(gzFile, gzread, 8192)
typedef struct {
int n, m;
uint64_t *a;
int *idx;
} bed_reglist_t;
#include "htslib/khash.h"
KHASH_MAP_INIT_STR(reg, bed_reglist_t)
#define LIDX_SHIFT 13
typedef kh_reg_t reghash_t;
void bed_destroy(void *_h);
int *bed_index_core(int n, uint64_t *a, int *n_idx)
{
int i, j, m, *idx;
m = *n_idx = 0; idx = 0;
for (i = 0; i < n; ++i) {
int beg, end;
beg = a[i]>>32 >> LIDX_SHIFT; end = ((uint32_t)a[i]) >> LIDX_SHIFT;
if (m < end + 1) {
int oldm = m;
m = end + 1;
kroundup32(m);
idx = realloc(idx, m * sizeof(int));
for (j = oldm; j < m; ++j) idx[j] = -1;
}
if (beg == end) {
if (idx[beg] < 0) idx[beg] = i;
} else {
for (j = beg; j <= end; ++j)
if (idx[j] < 0) idx[j] = i;
}
*n_idx = end + 1;
}
return idx;
}
void bed_index(void *_h)
{
reghash_t *h = (reghash_t*)_h;
khint_t k;
for (k = 0; k < kh_end(h); ++k) {
if (kh_exist(h, k)) {
bed_reglist_t *p = &kh_val(h, k);
if (p->idx) free(p->idx);
ks_introsort(uint64_t, p->n, p->a);
p->idx = bed_index_core(p->n, p->a, &p->m);
}
}
}
int bed_overlap_core(const bed_reglist_t *p, int beg, int end)
{
int i, min_off;
if (p->n == 0) return 0;
min_off = (beg>>LIDX_SHIFT >= p->n)? p->idx[p->n-1] : p->idx[beg>>LIDX_SHIFT];
if (min_off < 0) { // TODO: this block can be improved, but speed should not matter too much here
int n = beg>>LIDX_SHIFT;
if (n > p->n) n = p->n;
for (i = n - 1; i >= 0; --i)
if (p->idx[i] >= 0) break;
min_off = i >= 0? p->idx[i] : 0;
}
for (i = min_off; i < p->n; ++i) {
if ((int)(p->a[i]>>32) >= end) break; // out of range; no need to proceed
if ((int32_t)p->a[i] > beg && (int32_t)(p->a[i]>>32) < end)
return 1; // find the overlap; return
}
return 0;
}
int bed_overlap(const void *_h, const char *chr, int beg, int end)
{
const reghash_t *h = (const reghash_t*)_h;
khint_t k;
if (!h) return 0;
k = kh_get(reg, h, chr);
if (k == kh_end(h)) return 0;
return bed_overlap_core(&kh_val(h, k), beg, end);
}
/* "BED" file reader, which actually reads two different formats.
BED files contain between three and nine fields per line, of which
only the first three (reference, start, end) are of interest to us.
BED counts positions from base 0, and the end is the base after the
region of interest. While not properly documented in the specification,
it is also possible to have 'browser' and 'track' lines in BED files that
do not follow the standard format and should be ignored. Examination
of the BED file reading code in
http://genome-source.cse.ucsc.edu/gitweb/?p=kent.git shows that BED
files can also have comment lines starting with '#', leading whitespace
is stripped, and that fields are separated by one or more consecutive
whitespace characters.
The alternative format was originally for reading positions in VCF
format. This expects two columns, which indicate the reference and
a position. The position corresponds to a single base, and unlike
BED counts from 1.
Which format is in use is determined based on whether one or two
numbers can be decoded on the line. As this choice is made line-by-line
in this implementation, it is possible (but probably a bad idea) to mix
both formats in the same file. If trying to read a VCF file by this
method, it would be important to ensure that the third column (ID) does
not contain any entries that start with a digit, to avoid the line
erroneously being parsed as a BED file entry.
The BED specification is at http://www.genome.ucsc.edu/FAQ/FAQformat.html
The VCF specification is at https://github.com/samtools/hts-specs
*/
void *bed_read(const char *fn)
{
reghash_t *h = kh_init(reg);
gzFile fp;
kstream_t *ks = NULL;
int dret;
unsigned int line = 0;
kstring_t str = { 0, 0, NULL };
if (NULL == h) return NULL;
// read the list
fp = strcmp(fn, "-")? gzopen(fn, "r") : gzdopen(fileno(stdin), "r");
if (fp == 0) return 0;
ks = ks_init(fp);
if (NULL == ks) goto fail; // In case ks_init ever gets error checking...
while (ks_getuntil(ks, KS_SEP_LINE, &str, &dret) > 0) { // read a line
char *ref = str.s, *ref_end;
unsigned int beg = 0, end = 0;
int num = 0;
khint_t k;
bed_reglist_t *p;
line++;
while (*ref && isspace(*ref)) ref++;
if ('\0' == *ref) continue; // Skip blank lines
if ('#' == *ref) continue; // Skip BED file comments
ref_end = ref; // look for the end of the reference name
while (*ref_end && !isspace(*ref_end)) ref_end++;
if ('\0' != *ref_end) {
*ref_end = '\0'; // terminate ref and look for start, end
num = sscanf(ref_end + 1, "%u %u", &beg, &end);
}
if (1 == num) { // VCF-style format
end = beg--; // Counts from 1 instead of 0 for BED files
}
if (num < 1 || end < beg) {
// These two are special lines that can occur in BED files.
// Check for them here instead of earlier in case someone really
// has called their reference "browser" or "track".
if (0 == strcmp(ref, "browser")) continue;
if (0 == strcmp(ref, "track")) continue;
fprintf(stderr, "[bed_read] Parse error reading %s at line %u\n",
fn, line);
goto fail_no_msg;
}
// Put reg in the hash table if not already there
k = kh_get(reg, h, ref);
if (k == kh_end(h)) { // absent from the hash table
int ret;
char *s = strdup(ref);
if (NULL == s) goto fail;
k = kh_put(reg, h, s, &ret);
if (-1 == ret) {
free(s);
goto fail;
}
memset(&kh_val(h, k), 0, sizeof(bed_reglist_t));
}
p = &kh_val(h, k);
// Add begin,end to the list
if (p->n == p->m) {
p->m = p->m? p->m<<1 : 4;
p->a = realloc(p->a, p->m * 8);
if (NULL == p->a) goto fail;
}
p->a[p->n++] = (uint64_t)beg<<32 | end;
}
// FIXME: Need to check for errors in ks_getuntil. At the moment it
// doesn't look like it can return one. Possibly use gzgets instead?
ks_destroy(ks);
gzclose(fp);
free(str.s);
bed_index(h);
return h;
fail:
fprintf(stderr, "[bed_read] Error reading %s : %s\n", fn, strerror(errno));
fail_no_msg:
if (ks) ks_destroy(ks);
if (fp) gzclose(fp);
free(str.s);
bed_destroy(h);
return NULL;
}
void bed_destroy(void *_h)
{
reghash_t *h = (reghash_t*)_h;
khint_t k;
for (k = 0; k < kh_end(h); ++k) {
if (kh_exist(h, k)) {
free(kh_val(h, k).a);
free(kh_val(h, k).idx);
free((char*)kh_key(h, k));
}
}
kh_destroy(reg, h);
}