@apollo-annotation/shared 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Checks/CDSCheck.js +1 -1
- package/dist/Checks/CDSCheck.js.map +1 -1
- package/dist/GFF3/annotationFeatureToGFF3.d.ts +3 -0
- package/dist/GFF3/annotationFeatureToGFF3.js +209 -0
- package/dist/GFF3/annotationFeatureToGFF3.js.map +1 -0
- package/dist/GFF3/annotationFeatureToGFF3.test.d.ts +1 -0
- package/dist/GFF3/annotationFeatureToGFF3.test.js +156 -0
- package/dist/GFF3/annotationFeatureToGFF3.test.js.map +1 -0
- package/dist/GFF3/gff3ToAnnotationFeature.test.d.ts +2 -1
- package/dist/GFF3/gff3ToAnnotationFeature.test.js +1 -0
- package/dist/GFF3/gff3ToAnnotationFeature.test.js.map +1 -1
- package/dist/GFF3/index.d.ts +1 -0
- package/dist/GFF3/index.js +1 -0
- package/dist/GFF3/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/util.d.ts +0 -3
- package/dist/util.js +0 -90
- package/dist/util.js.map +1 -1
- package/package.json +6 -6
- package/src/Checks/CDSCheck.ts +1 -1
- package/src/GFF3/annotationFeatureToGFF3.test.ts +170 -0
- package/src/GFF3/annotationFeatureToGFF3.ts +257 -0
- package/src/GFF3/gff3ToAnnotationFeature.test.ts +3 -1
- package/src/GFF3/index.ts +1 -0
- package/src/util.ts +0 -102
- package/test_data/gene.json +117 -0
package/dist/util.d.ts
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
|
-
import { AnnotationFeatureSnapshot } from '@apollo-annotation/mst';
|
|
2
|
-
import { GFF3Feature } from '@gmod/gff';
|
|
3
|
-
export declare function makeGFF3Feature(feature: AnnotationFeatureSnapshot, parentId?: string, refSeqNames?: Record<string, string | undefined>): GFF3Feature;
|
|
4
1
|
export declare function splitStringIntoChunks(input: string, chunkSize: number): string[];
|
package/dist/util.js
CHANGED
|
@@ -1,96 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.makeGFF3Feature = makeGFF3Feature;
|
|
5
3
|
exports.splitStringIntoChunks = splitStringIntoChunks;
|
|
6
|
-
function makeGFF3Feature(feature, parentId, refSeqNames) {
|
|
7
|
-
const locations = [{ start: feature.min, end: feature.max }];
|
|
8
|
-
// const locations = feature.discontinuousLocations?.length
|
|
9
|
-
// ? feature.discontinuousLocations
|
|
10
|
-
// : [{ start: feature.start, end: feature.end, phase: feature.phase }]
|
|
11
|
-
const attributes = JSON.parse(JSON.stringify(feature.attributes));
|
|
12
|
-
const ontologyTerms = [];
|
|
13
|
-
const source = feature.attributes?.source?.[0] ?? null;
|
|
14
|
-
delete attributes.source;
|
|
15
|
-
if (parentId) {
|
|
16
|
-
attributes.Parent = [parentId];
|
|
17
|
-
}
|
|
18
|
-
if (attributes._id) {
|
|
19
|
-
attributes.ID = attributes._id;
|
|
20
|
-
delete attributes._id;
|
|
21
|
-
}
|
|
22
|
-
if (attributes.gff_name) {
|
|
23
|
-
attributes.Name = attributes.gff_name;
|
|
24
|
-
delete attributes.gff_name;
|
|
25
|
-
}
|
|
26
|
-
if (attributes.gff_alias) {
|
|
27
|
-
attributes.Alias = attributes.gff_alias;
|
|
28
|
-
delete attributes.gff_alias;
|
|
29
|
-
}
|
|
30
|
-
if (attributes.gff_target) {
|
|
31
|
-
attributes.Target = attributes.gff_target;
|
|
32
|
-
delete attributes.gff_target;
|
|
33
|
-
}
|
|
34
|
-
if (attributes.gff_gap) {
|
|
35
|
-
attributes.Gap = attributes.gff_gap;
|
|
36
|
-
delete attributes.gff_gap;
|
|
37
|
-
}
|
|
38
|
-
if (attributes.gff_derives_from) {
|
|
39
|
-
attributes.Derives_from = attributes.gff_derives_from;
|
|
40
|
-
delete attributes.gff_derives_from;
|
|
41
|
-
}
|
|
42
|
-
if (attributes.gff_note) {
|
|
43
|
-
attributes.Note = attributes.gff_note;
|
|
44
|
-
delete attributes.gff_note;
|
|
45
|
-
}
|
|
46
|
-
if (attributes.gff_dbxref) {
|
|
47
|
-
attributes.Dbxref = attributes.gff_dbxref;
|
|
48
|
-
delete attributes.gff_dbxref;
|
|
49
|
-
}
|
|
50
|
-
if (attributes.gff_is_circular) {
|
|
51
|
-
attributes.Is_circular = attributes.gff_is_circular;
|
|
52
|
-
delete attributes.gff_is_circular;
|
|
53
|
-
}
|
|
54
|
-
if (attributes.gff_ontology_term) {
|
|
55
|
-
ontologyTerms.push(...attributes.gff_ontology_term);
|
|
56
|
-
delete attributes.gff_ontology_term;
|
|
57
|
-
}
|
|
58
|
-
if (attributes['Gene Ontology']) {
|
|
59
|
-
ontologyTerms.push(...attributes['Gene Ontology']);
|
|
60
|
-
delete attributes['Gene Ontology'];
|
|
61
|
-
}
|
|
62
|
-
if (attributes['Sequence Ontology']) {
|
|
63
|
-
ontologyTerms.push(...attributes['Sequence Ontology']);
|
|
64
|
-
delete attributes['Sequence Ontology'];
|
|
65
|
-
}
|
|
66
|
-
if (ontologyTerms.length > 0) {
|
|
67
|
-
attributes.Ontology_term = ontologyTerms;
|
|
68
|
-
}
|
|
69
|
-
return locations.map((location) => ({
|
|
70
|
-
start: location.start + 1,
|
|
71
|
-
end: location.end,
|
|
72
|
-
seq_id: refSeqNames ? refSeqNames[feature.refSeq] ?? null : feature.refSeq,
|
|
73
|
-
source,
|
|
74
|
-
type: feature.type,
|
|
75
|
-
score: null,
|
|
76
|
-
// score: feature.score ?? null,
|
|
77
|
-
strand: feature.strand ? (feature.strand === 1 ? '+' : '-') : null,
|
|
78
|
-
phase: null,
|
|
79
|
-
// phase:
|
|
80
|
-
// location.phase === 0
|
|
81
|
-
// ? '0'
|
|
82
|
-
// : location.phase === 1
|
|
83
|
-
// ? '1'
|
|
84
|
-
// : location.phase === 2
|
|
85
|
-
// ? '2'
|
|
86
|
-
// : null,
|
|
87
|
-
attributes: Object.keys(attributes).length > 0 ? attributes : null,
|
|
88
|
-
derived_features: [],
|
|
89
|
-
child_features: feature.children
|
|
90
|
-
? Object.values(feature.children).map((child) => makeGFF3Feature(child, attributes.ID?.[0], refSeqNames))
|
|
91
|
-
: [],
|
|
92
|
-
}));
|
|
93
|
-
}
|
|
94
4
|
function splitStringIntoChunks(input, chunkSize) {
|
|
95
5
|
const chunks = [];
|
|
96
6
|
for (let i = 0; i < input.length; i += chunkSize) {
|
package/dist/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;AAAA,sDAUC;AAVD,SAAgB,qBAAqB,CACnC,KAAa,EACb,SAAiB;IAEjB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAA;QAC3C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollo-annotation/shared",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "yarn clean && tsc --build",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"test:ci": "NODE_V8_COVERAGE=./coverage glob -c \"tsx --test --test-reporter spec --experimental-test-coverage \" \"**/*.test.ts\""
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@apollo-annotation/common": "^0.2.
|
|
14
|
-
"@apollo-annotation/mst": "^0.2.
|
|
15
|
-
"@apollo-annotation/schemas": "^0.2.
|
|
13
|
+
"@apollo-annotation/common": "^0.2.2",
|
|
14
|
+
"@apollo-annotation/mst": "^0.2.2",
|
|
15
|
+
"@apollo-annotation/schemas": "^0.2.2",
|
|
16
16
|
"@gmod/gff": "1.2.0",
|
|
17
17
|
"@gmod/indexedfasta": "^2.0.4",
|
|
18
18
|
"@jbrowse/core": "^2.13.1",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"chai-exclude": "^3.0.0",
|
|
31
31
|
"glob": "^11.0.0",
|
|
32
32
|
"mobx": "^6.6.1",
|
|
33
|
-
"mobx-state-tree": "^5.
|
|
33
|
+
"mobx-state-tree": "^5.4.0",
|
|
34
34
|
"mongoose": "^6.12.0",
|
|
35
35
|
"react-dom": "^18.2.0",
|
|
36
36
|
"rimraf": "^3.0.2",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@mui/x-data-grid": "^7.0.0",
|
|
44
44
|
"mobx": "^6.6.1",
|
|
45
45
|
"mobx-react": "^7.2.1",
|
|
46
|
-
"mobx-state-tree": "^5.
|
|
46
|
+
"mobx-state-tree": "^5.4.0",
|
|
47
47
|
"prop-types": "^15.8.1",
|
|
48
48
|
"react": "^18.2.0",
|
|
49
49
|
"react-dom": "^18.2.0",
|
package/src/Checks/CDSCheck.ts
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/* eslint-disable prefer-destructuring */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
3
|
+
import { describe, it } from 'node:test'
|
|
4
|
+
import { assert } from 'chai'
|
|
5
|
+
import { readAnnotationFeatureSnapshot } from './gff3ToAnnotationFeature.test'
|
|
6
|
+
import { annotationFeatureToGFF3 } from './annotationFeatureToGFF3'
|
|
7
|
+
import { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
8
|
+
|
|
9
|
+
describe('annotationFeatureToGFF3', () => {
|
|
10
|
+
it('Test mandatory columns', () => {
|
|
11
|
+
const annotationFeature = readAnnotationFeatureSnapshot(
|
|
12
|
+
'test_data/gene.json',
|
|
13
|
+
)
|
|
14
|
+
const [gff3Feature] = annotationFeatureToGFF3(annotationFeature)
|
|
15
|
+
|
|
16
|
+
assert.deepEqual(gff3Feature.seq_id, 'chr1')
|
|
17
|
+
assert.deepEqual(gff3Feature.type, 'gene')
|
|
18
|
+
assert.deepEqual(gff3Feature.start, 1000)
|
|
19
|
+
assert.deepEqual(gff3Feature.end, 9000)
|
|
20
|
+
assert.deepEqual(gff3Feature.strand, '+')
|
|
21
|
+
assert.deepEqual(gff3Feature.score, 123)
|
|
22
|
+
assert.deepEqual(gff3Feature.source, 'test_data')
|
|
23
|
+
})
|
|
24
|
+
it('Feature with no children and no gff_id has no ID attribute', () => {
|
|
25
|
+
const annotationFeature = JSON.parse(`{
|
|
26
|
+
"_id": "66d70e4ccc30b55b65e5f619",
|
|
27
|
+
"refSeq": "chr1",
|
|
28
|
+
"type": "gene",
|
|
29
|
+
"min": 999,
|
|
30
|
+
"max": 9000,
|
|
31
|
+
"strand": 1,
|
|
32
|
+
"attributes": {}
|
|
33
|
+
}`) as AnnotationFeatureSnapshot
|
|
34
|
+
const [gff3Feature] = annotationFeatureToGFF3(annotationFeature)
|
|
35
|
+
assert.isUndefined(gff3Feature.attributes?.ID)
|
|
36
|
+
})
|
|
37
|
+
it('Feature with children and no gff_id has internal _id as ID', () => {
|
|
38
|
+
const annotationFeature = JSON.parse(`{
|
|
39
|
+
"_id": "66d70e4ccc30b55b65e5f619",
|
|
40
|
+
"refSeq": "chr1",
|
|
41
|
+
"type": "gene",
|
|
42
|
+
"min": 999,
|
|
43
|
+
"max": 9000,
|
|
44
|
+
"strand": 1,
|
|
45
|
+
"attributes": {},
|
|
46
|
+
"children": {
|
|
47
|
+
"66d70e4ccc30b55b65e5f618": {
|
|
48
|
+
"_id": "66d70e4ccc30b55b65e5f618",
|
|
49
|
+
"refSeq": "chr1",
|
|
50
|
+
"type": "gene_segment",
|
|
51
|
+
"min": 1049,
|
|
52
|
+
"max": 9000,
|
|
53
|
+
"strand": 1,
|
|
54
|
+
"attributes": {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}`) as AnnotationFeatureSnapshot
|
|
58
|
+
const [gff3Feature] = annotationFeatureToGFF3(annotationFeature)
|
|
59
|
+
assert.deepEqual(gff3Feature.attributes?.ID, ['66d70e4ccc30b55b65e5f619'])
|
|
60
|
+
})
|
|
61
|
+
it('Convert multiple scores', () => {
|
|
62
|
+
const annotationFeature = JSON.parse(`{
|
|
63
|
+
"_id": "66d70e4ccc30b55b65e5f619",
|
|
64
|
+
"refSeq": "chr1",
|
|
65
|
+
"type": "gene",
|
|
66
|
+
"min": 999,
|
|
67
|
+
"max": 9000,
|
|
68
|
+
"strand": 1,
|
|
69
|
+
"attributes": {
|
|
70
|
+
"gff_id": ["gene10001"],
|
|
71
|
+
"gff_score": ["123", "345"]
|
|
72
|
+
}
|
|
73
|
+
}`) as AnnotationFeatureSnapshot
|
|
74
|
+
const [gff3Feature] = annotationFeatureToGFF3(annotationFeature)
|
|
75
|
+
assert.deepEqual(gff3Feature.score, 123)
|
|
76
|
+
})
|
|
77
|
+
it('Convert invalid score', () => {
|
|
78
|
+
const annotationFeature = JSON.parse(`{
|
|
79
|
+
"_id": "66d70e4ccc30b55b65e5f619",
|
|
80
|
+
"refSeq": "chr1",
|
|
81
|
+
"type": "gene",
|
|
82
|
+
"min": 999,
|
|
83
|
+
"max": 9000,
|
|
84
|
+
"strand": 1,
|
|
85
|
+
"attributes": {
|
|
86
|
+
"gff_id": ["gene10001"],
|
|
87
|
+
"gff_score": ["xyz"]
|
|
88
|
+
}
|
|
89
|
+
}`) as AnnotationFeatureSnapshot
|
|
90
|
+
const [gff3Feature] = annotationFeatureToGFF3(annotationFeature)
|
|
91
|
+
assert.deepEqual(gff3Feature.score, null)
|
|
92
|
+
})
|
|
93
|
+
it('Convert one gene test attributes', () => {
|
|
94
|
+
const annotationFeature = readAnnotationFeatureSnapshot(
|
|
95
|
+
'test_data/gene.json',
|
|
96
|
+
)
|
|
97
|
+
const [gff3Feature] = annotationFeatureToGFF3(annotationFeature)
|
|
98
|
+
|
|
99
|
+
assert.deepEqual(gff3Feature.attributes?.Name, ['EDEN'])
|
|
100
|
+
assert.deepEqual(gff3Feature.attributes?.testid, ['t001', 't003'])
|
|
101
|
+
assert.deepEqual(gff3Feature.attributes?.ID, ['gene10001'])
|
|
102
|
+
assert.deepEqual(gff3Feature.attributes?.Ontology_term, [
|
|
103
|
+
'GO1234',
|
|
104
|
+
'GO4567',
|
|
105
|
+
'SO1234',
|
|
106
|
+
])
|
|
107
|
+
assert.deepEqual(gff3Feature.attributes?.Alias, ['myalias'])
|
|
108
|
+
assert.deepEqual(gff3Feature.attributes?.Target, ['mytarget'])
|
|
109
|
+
assert.deepEqual(gff3Feature.attributes?.Gap, ['mygap'])
|
|
110
|
+
assert.deepEqual(gff3Feature.attributes?.Derives_from, ['myderives'])
|
|
111
|
+
assert.deepEqual(gff3Feature.attributes?.Note, ['mynote'])
|
|
112
|
+
assert.deepEqual(gff3Feature.attributes?.Dbxref, ['mydbxref'])
|
|
113
|
+
assert.deepEqual(gff3Feature.attributes?.Is_circular, ['true'])
|
|
114
|
+
})
|
|
115
|
+
it('Convert one gene test children', () => {
|
|
116
|
+
const annotationFeature = readAnnotationFeatureSnapshot(
|
|
117
|
+
'test_data/gene.json',
|
|
118
|
+
)
|
|
119
|
+
const [gff3Feature] = annotationFeatureToGFF3(annotationFeature)
|
|
120
|
+
const [children] = gff3Feature.child_features
|
|
121
|
+
const [mrna] = children
|
|
122
|
+
assert.deepEqual(mrna.type, 'mRNA')
|
|
123
|
+
assert.deepEqual(mrna.attributes?.Parent, ['gene10001'])
|
|
124
|
+
|
|
125
|
+
const [cds] = mrna.child_features[2]
|
|
126
|
+
assert.deepEqual(cds.type, 'CDS')
|
|
127
|
+
assert.deepEqual(cds.attributes?.ID, ['cds10001'])
|
|
128
|
+
assert.deepEqual(cds.attributes?.Parent, ['mRNA10001'])
|
|
129
|
+
})
|
|
130
|
+
it('Convert CDSs', () => {
|
|
131
|
+
const annotationFeature = readAnnotationFeatureSnapshot(
|
|
132
|
+
'test_data/gene.json',
|
|
133
|
+
)
|
|
134
|
+
const [gff3Feature] = annotationFeatureToGFF3(annotationFeature)
|
|
135
|
+
const [children] = gff3Feature.child_features
|
|
136
|
+
const [mrna] = children
|
|
137
|
+
const cds10001 = mrna.child_features.filter((child) => {
|
|
138
|
+
const id = child[0].attributes?.ID
|
|
139
|
+
return id !== undefined && id[0] === 'cds10001'
|
|
140
|
+
})
|
|
141
|
+
assert.deepEqual(cds10001.length, 2)
|
|
142
|
+
|
|
143
|
+
const cds1_1 = cds10001[0][0]
|
|
144
|
+
assert.deepEqual(cds1_1.attributes?.ID, ['cds10001'])
|
|
145
|
+
assert.deepEqual(cds1_1.start, 1201)
|
|
146
|
+
assert.deepEqual(cds1_1.end, 1500)
|
|
147
|
+
assert.deepEqual(cds1_1.phase, '0')
|
|
148
|
+
|
|
149
|
+
const cds1_2 = cds10001[1][0]
|
|
150
|
+
assert.deepEqual(cds1_2.attributes?.ID, ['cds10001'])
|
|
151
|
+
assert.deepEqual(cds1_2.start, 5000)
|
|
152
|
+
assert.deepEqual(cds1_2.end, 5100)
|
|
153
|
+
assert.deepEqual(cds1_2.phase, '0')
|
|
154
|
+
|
|
155
|
+
assert.deepEqual(cds1_1.child_features[0][0].attributes?.ID, [
|
|
156
|
+
'cds_region10001',
|
|
157
|
+
])
|
|
158
|
+
assert.deepEqual(cds1_1.child_features[0][0].start, 1351)
|
|
159
|
+
assert.deepEqual(cds1_1.child_features[0][0].end, 1400)
|
|
160
|
+
assert.deepEqual(cds1_1.child_features[0][0].phase, null)
|
|
161
|
+
|
|
162
|
+
const cds10004 = mrna.child_features.filter((child) => {
|
|
163
|
+
const id = child[0].attributes?.ID
|
|
164
|
+
return id !== undefined && id[0] === 'cds10004'
|
|
165
|
+
})
|
|
166
|
+
assert.deepEqual(cds10004.length, 2)
|
|
167
|
+
const cds4_1 = cds10004[0][0]
|
|
168
|
+
assert.deepEqual(cds4_1.attributes?.ID, ['cds10004'])
|
|
169
|
+
})
|
|
170
|
+
})
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AnnotationFeatureSnapshot,
|
|
5
|
+
TranscriptPartLocation,
|
|
6
|
+
TranscriptPartNonCoding,
|
|
7
|
+
} from '@apollo-annotation/mst'
|
|
8
|
+
import { GFF3Feature } from '@gmod/gff'
|
|
9
|
+
import { intersection2 } from '@jbrowse/core/util'
|
|
10
|
+
|
|
11
|
+
export function annotationFeatureToGFF3(
|
|
12
|
+
feature: AnnotationFeatureSnapshot,
|
|
13
|
+
parentId?: string,
|
|
14
|
+
refSeqNames?: Record<string, string | undefined>,
|
|
15
|
+
): GFF3Feature {
|
|
16
|
+
const attributes: Record<string, string[] | undefined> = JSON.parse(
|
|
17
|
+
JSON.stringify(feature.attributes),
|
|
18
|
+
)
|
|
19
|
+
const ontologyTerms: string[] = []
|
|
20
|
+
const source = feature.attributes?.gff_source?.[0] ?? null
|
|
21
|
+
|
|
22
|
+
delete attributes.gff_source
|
|
23
|
+
if (parentId) {
|
|
24
|
+
attributes.Parent = [parentId]
|
|
25
|
+
}
|
|
26
|
+
if (attributes.gff_id) {
|
|
27
|
+
attributes.ID = attributes.gff_id
|
|
28
|
+
delete attributes.gff_id
|
|
29
|
+
} else if (feature.children) {
|
|
30
|
+
attributes.ID = [feature._id]
|
|
31
|
+
}
|
|
32
|
+
if (attributes.gff_name) {
|
|
33
|
+
attributes.Name = attributes.gff_name
|
|
34
|
+
delete attributes.gff_name
|
|
35
|
+
}
|
|
36
|
+
if (attributes.gff_alias) {
|
|
37
|
+
attributes.Alias = attributes.gff_alias
|
|
38
|
+
delete attributes.gff_alias
|
|
39
|
+
}
|
|
40
|
+
if (attributes.gff_target) {
|
|
41
|
+
attributes.Target = attributes.gff_target
|
|
42
|
+
delete attributes.gff_target
|
|
43
|
+
}
|
|
44
|
+
if (attributes.gff_gap) {
|
|
45
|
+
attributes.Gap = attributes.gff_gap
|
|
46
|
+
delete attributes.gff_gap
|
|
47
|
+
}
|
|
48
|
+
if (attributes.gff_derives_from) {
|
|
49
|
+
attributes.Derives_from = attributes.gff_derives_from
|
|
50
|
+
delete attributes.gff_derives_from
|
|
51
|
+
}
|
|
52
|
+
if (attributes.gff_note) {
|
|
53
|
+
attributes.Note = attributes.gff_note
|
|
54
|
+
delete attributes.gff_note
|
|
55
|
+
}
|
|
56
|
+
if (attributes.gff_dbxref) {
|
|
57
|
+
attributes.Dbxref = attributes.gff_dbxref
|
|
58
|
+
delete attributes.gff_dbxref
|
|
59
|
+
}
|
|
60
|
+
if (attributes.gff_is_circular) {
|
|
61
|
+
attributes.Is_circular = attributes.gff_is_circular
|
|
62
|
+
delete attributes.gff_is_circular
|
|
63
|
+
}
|
|
64
|
+
if (attributes.gff_ontology_term) {
|
|
65
|
+
ontologyTerms.push(...attributes.gff_ontology_term)
|
|
66
|
+
delete attributes.gff_ontology_term
|
|
67
|
+
}
|
|
68
|
+
if (attributes['Gene Ontology']) {
|
|
69
|
+
ontologyTerms.push(...attributes['Gene Ontology'])
|
|
70
|
+
delete attributes['Gene Ontology']
|
|
71
|
+
}
|
|
72
|
+
if (attributes['Sequence Ontology']) {
|
|
73
|
+
ontologyTerms.push(...attributes['Sequence Ontology'])
|
|
74
|
+
delete attributes['Sequence Ontology']
|
|
75
|
+
}
|
|
76
|
+
if (ontologyTerms.length > 0) {
|
|
77
|
+
attributes.Ontology_term = ontologyTerms
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const gff_score = feature.attributes?.gff_score
|
|
81
|
+
let score: number | null = null
|
|
82
|
+
if (gff_score && gff_score.length > 0) {
|
|
83
|
+
if (gff_score[0]) {
|
|
84
|
+
score = Number(gff_score[0])
|
|
85
|
+
if (Number.isNaN(score)) {
|
|
86
|
+
score = null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
delete attributes.gff_score
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const locations = [{ start: feature.min, end: feature.max }]
|
|
93
|
+
|
|
94
|
+
return locations.map((location) => ({
|
|
95
|
+
start: Number(location.start) + 1,
|
|
96
|
+
end: Number(location.end),
|
|
97
|
+
seq_id: refSeqNames ? refSeqNames[feature.refSeq] ?? null : feature.refSeq,
|
|
98
|
+
source,
|
|
99
|
+
type: feature.type,
|
|
100
|
+
score,
|
|
101
|
+
strand: feature.strand ? (feature.strand === 1 ? '+' : '-') : null,
|
|
102
|
+
phase: null,
|
|
103
|
+
attributes: Object.keys(attributes).length > 0 ? attributes : null,
|
|
104
|
+
derived_features: [],
|
|
105
|
+
child_features: prepareChildFeatures(
|
|
106
|
+
feature,
|
|
107
|
+
attributes.ID?.[0],
|
|
108
|
+
refSeqNames,
|
|
109
|
+
),
|
|
110
|
+
}))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function prepareChildFeatures(
|
|
114
|
+
feature: AnnotationFeatureSnapshot,
|
|
115
|
+
parentID?: string,
|
|
116
|
+
refSeqNames?: Record<string, string | undefined>,
|
|
117
|
+
): GFF3Feature[] {
|
|
118
|
+
if (!feature.children) {
|
|
119
|
+
return []
|
|
120
|
+
}
|
|
121
|
+
if (feature.type === 'mRNA') {
|
|
122
|
+
const child_features: GFF3Feature[] = []
|
|
123
|
+
const cdsLocations = getCdsLocations(feature)
|
|
124
|
+
let cds_idx = 0
|
|
125
|
+
for (const child of Object.values(feature.children)) {
|
|
126
|
+
const gffChild = annotationFeatureToGFF3(child, parentID, refSeqNames)
|
|
127
|
+
if (child.type === 'CDS') {
|
|
128
|
+
for (const loc of cdsLocations[cds_idx]) {
|
|
129
|
+
const gffCds = JSON.parse(JSON.stringify(gffChild)) as GFF3Feature
|
|
130
|
+
if (gffCds.length != 1) {
|
|
131
|
+
// Do we need this check?
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Unexpected CDS: ${JSON.stringify(gffCds, null, 2)}`,
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
gffCds[0].start = loc.min + 1
|
|
137
|
+
gffCds[0].end = loc.max
|
|
138
|
+
gffCds[0].phase = loc.phase.toString()
|
|
139
|
+
gffCds[0].type = loc.type // Do we need this?
|
|
140
|
+
child_features.push(gffCds)
|
|
141
|
+
}
|
|
142
|
+
cds_idx++
|
|
143
|
+
} else {
|
|
144
|
+
child_features.push(gffChild)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return child_features
|
|
148
|
+
}
|
|
149
|
+
return Object.values(feature.children).map((child) =>
|
|
150
|
+
annotationFeatureToGFF3(child, parentID, refSeqNames),
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface TranscriptPartCoding extends TranscriptPartLocation {
|
|
155
|
+
type: 'CDS'
|
|
156
|
+
phase: 0 | 1 | 2
|
|
157
|
+
}
|
|
158
|
+
type TranscriptPart = TranscriptPartCoding | TranscriptPartNonCoding
|
|
159
|
+
type TranscriptParts = TranscriptPart[]
|
|
160
|
+
|
|
161
|
+
function getTranscriptParts(
|
|
162
|
+
feature: AnnotationFeatureSnapshot,
|
|
163
|
+
): TranscriptParts[] {
|
|
164
|
+
if (feature.type !== 'mRNA') {
|
|
165
|
+
throw new Error(
|
|
166
|
+
'Only features of type "mRNA" or equivalent can calculate CDS locations',
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
if (!feature.children) {
|
|
170
|
+
throw new Error('no CDS or exons in mRNA')
|
|
171
|
+
}
|
|
172
|
+
// In AnnotationFeatureModel we have `children.values()`
|
|
173
|
+
const children = Object.values(feature.children)
|
|
174
|
+
const cdsChildren = children.filter((child) => child.type === 'CDS')
|
|
175
|
+
if (cdsChildren.length === 0) {
|
|
176
|
+
throw new Error('no CDS in mRNA')
|
|
177
|
+
}
|
|
178
|
+
const transcriptParts: TranscriptParts[] = []
|
|
179
|
+
for (const cds of cdsChildren) {
|
|
180
|
+
const { max: cdsMax, min: cdsMin } = cds
|
|
181
|
+
const parts: TranscriptParts = []
|
|
182
|
+
let hasIntersected = false
|
|
183
|
+
const exonLocations: TranscriptPartLocation[] = []
|
|
184
|
+
for (const child of children) {
|
|
185
|
+
if (child.type === 'exon') {
|
|
186
|
+
exonLocations.push({ min: child.min, max: child.max })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
exonLocations.sort(({ min: a }, { min: b }) => a - b)
|
|
190
|
+
for (const child of exonLocations) {
|
|
191
|
+
const lastPart = parts.at(-1)
|
|
192
|
+
if (lastPart) {
|
|
193
|
+
parts.push({ min: lastPart.max, max: child.min, type: 'intron' })
|
|
194
|
+
}
|
|
195
|
+
const [start, end] = intersection2(cdsMin, cdsMax, child.min, child.max)
|
|
196
|
+
let utrType: 'fivePrimeUTR' | 'threePrimeUTR'
|
|
197
|
+
if (hasIntersected) {
|
|
198
|
+
utrType = feature.strand === 1 ? 'threePrimeUTR' : 'fivePrimeUTR'
|
|
199
|
+
} else {
|
|
200
|
+
utrType = feature.strand === 1 ? 'fivePrimeUTR' : 'threePrimeUTR'
|
|
201
|
+
}
|
|
202
|
+
if (start !== undefined && end !== undefined) {
|
|
203
|
+
hasIntersected = true
|
|
204
|
+
if (start === child.min && end === child.max) {
|
|
205
|
+
parts.push({ min: start, max: end, phase: 0, type: 'CDS' })
|
|
206
|
+
} else if (start === child.min) {
|
|
207
|
+
parts.push(
|
|
208
|
+
{ min: start, max: end, phase: 0, type: 'CDS' },
|
|
209
|
+
{ min: end, max: child.max, type: utrType },
|
|
210
|
+
)
|
|
211
|
+
} else if (end === child.max) {
|
|
212
|
+
parts.push(
|
|
213
|
+
{ min: child.min, max: start, type: utrType },
|
|
214
|
+
{ min: start, max: end, phase: 0, type: 'CDS' },
|
|
215
|
+
)
|
|
216
|
+
} else {
|
|
217
|
+
parts.push(
|
|
218
|
+
{ min: child.min, max: start, type: utrType },
|
|
219
|
+
{ min: start, max: end, phase: 0, type: 'CDS' },
|
|
220
|
+
{
|
|
221
|
+
min: end,
|
|
222
|
+
max: child.max,
|
|
223
|
+
type:
|
|
224
|
+
utrType === 'fivePrimeUTR' ? 'threePrimeUTR' : 'fivePrimeUTR',
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
parts.push({ min: child.min, max: child.max, type: utrType })
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
parts.sort(({ min: a }, { min: b }) => a - b)
|
|
233
|
+
if (feature.strand === -1) {
|
|
234
|
+
parts.reverse()
|
|
235
|
+
}
|
|
236
|
+
let nextPhase: 0 | 1 | 2 = 0
|
|
237
|
+
const phasedParts = parts.map((loc) => {
|
|
238
|
+
if (loc.type !== 'CDS') {
|
|
239
|
+
return loc
|
|
240
|
+
}
|
|
241
|
+
const phase = nextPhase
|
|
242
|
+
nextPhase = ((3 - ((loc.max - loc.min - phase + 3) % 3)) % 3) as 0 | 1 | 2
|
|
243
|
+
return { ...loc, phase }
|
|
244
|
+
})
|
|
245
|
+
transcriptParts.push(phasedParts)
|
|
246
|
+
}
|
|
247
|
+
return transcriptParts
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getCdsLocations(
|
|
251
|
+
feature: AnnotationFeatureSnapshot,
|
|
252
|
+
): TranscriptPartCoding[][] {
|
|
253
|
+
const transcriptParts = getTranscriptParts(feature)
|
|
254
|
+
return transcriptParts.map((transcript) =>
|
|
255
|
+
transcript.filter((transcriptPart) => transcriptPart.type === 'CDS'),
|
|
256
|
+
)
|
|
257
|
+
}
|
|
@@ -120,7 +120,9 @@ function readFeatureFile(fn: string): GFF3Feature[] {
|
|
|
120
120
|
return inGff
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
function readAnnotationFeatureSnapshot(
|
|
123
|
+
export function readAnnotationFeatureSnapshot(
|
|
124
|
+
fn: string,
|
|
125
|
+
): AnnotationFeatureSnapshot {
|
|
124
126
|
const lines = readFileSync(fn).toString()
|
|
125
127
|
return JSON.parse(lines) as AnnotationFeatureSnapshot
|
|
126
128
|
}
|
package/src/GFF3/index.ts
CHANGED