@apollo-annotation/shared 0.3.9 → 0.3.11
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/Changes/AddFeatureChange.js +13 -2
- package/dist/Changes/AddFeatureChange.js.map +1 -1
- package/dist/Changes/DeleteFeatureChange.js +19 -0
- package/dist/Changes/DeleteFeatureChange.js.map +1 -1
- package/dist/Changes/FeatureAttributeChange.js +11 -0
- package/dist/Changes/FeatureAttributeChange.js.map +1 -1
- package/dist/Changes/FromFileBaseChange.d.ts +2 -0
- package/dist/Changes/FromFileBaseChange.js +20 -5
- package/dist/Changes/FromFileBaseChange.js.map +1 -1
- package/dist/GFF3/annotationFeatureToGFF3.test.js +19 -5
- package/dist/GFF3/annotationFeatureToGFF3.test.js.map +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.d.ts +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.js +8 -11
- package/dist/GFF3/gff3ToAnnotationFeature.js.map +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.test.d.ts +1 -2
- package/dist/GFF3/gff3ToAnnotationFeature.test.js +23 -96
- package/dist/GFF3/gff3ToAnnotationFeature.test.js.map +1 -1
- package/dist/GFF3/testUtil.d.ts +6 -0
- package/dist/GFF3/testUtil.js +24 -0
- package/dist/GFF3/testUtil.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/Changes/AddFeatureChange.ts +14 -2
- package/src/Changes/DeleteFeatureChange.ts +18 -0
- package/src/Changes/FeatureAttributeChange.ts +13 -0
- package/src/Changes/FromFileBaseChange.ts +22 -10
- package/src/GFF3/annotationFeatureToGFF3.test.ts +24 -2
- package/src/GFF3/gff3ToAnnotationFeature.test.ts +24 -95
- package/src/GFF3/gff3ToAnnotationFeature.ts +6 -15
- package/src/GFF3/testUtil.ts +25 -0
- package/test_data/gene_with_two_cds.gff3 +6 -0
- package/test_data/gene_with_two_cds.json +69 -0
- package/test_data/single_feature_no_children.gff3 +1 -0
- package/test_data/single_feature_no_children.json +14 -0
- package/test_data/single_feature_two_children.gff3 +3 -0
- package/test_data/single_feature_two_children.json +44 -0
- package/test_data/two_cds.gff3 +0 -9
- package/test_data/two_cds.json +0 -67
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollo-annotation/shared",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
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.3.
|
|
14
|
-
"@apollo-annotation/mst": "^0.3.
|
|
15
|
-
"@apollo-annotation/schemas": "^0.3.
|
|
13
|
+
"@apollo-annotation/common": "^0.3.11",
|
|
14
|
+
"@apollo-annotation/mst": "^0.3.11",
|
|
15
|
+
"@apollo-annotation/schemas": "^0.3.11",
|
|
16
16
|
"@gmod/gff": "^2.0.0",
|
|
17
17
|
"@gmod/indexedfasta": "^2.0.4",
|
|
18
18
|
"@jbrowse/core": "^3.6.5",
|
|
@@ -89,6 +89,12 @@ export class AddFeatureChange extends FeatureChange {
|
|
|
89
89
|
let featureCnt = 0
|
|
90
90
|
logger.debug?.(`changes: ${JSON.stringify(changes)}`)
|
|
91
91
|
|
|
92
|
+
const { INDEXED_IDS } = process.env
|
|
93
|
+
let idsToIndex: string[] | undefined
|
|
94
|
+
if (INDEXED_IDS) {
|
|
95
|
+
idsToIndex = INDEXED_IDS.split(',')
|
|
96
|
+
}
|
|
97
|
+
|
|
92
98
|
// Loop the changes
|
|
93
99
|
for (const change of changes) {
|
|
94
100
|
logger.debug?.(`change: ${JSON.stringify(change)}`)
|
|
@@ -106,9 +112,10 @@ export class AddFeatureChange extends FeatureChange {
|
|
|
106
112
|
|
|
107
113
|
// CopyFeature is called from CopyFeature.tsx
|
|
108
114
|
if (copyFeature) {
|
|
115
|
+
const indexedIds = this.getIndexedIds(addedFeature, idsToIndex)
|
|
109
116
|
// Add into Mongo
|
|
110
117
|
const [newFeatureDoc] = await featureModel.create(
|
|
111
|
-
[{ ...addedFeature, allIds, status: -1, user }],
|
|
118
|
+
[{ ...addedFeature, allIds, indexedIds, status: -1, user }],
|
|
112
119
|
{ session },
|
|
113
120
|
)
|
|
114
121
|
logger.debug?.(
|
|
@@ -116,6 +123,7 @@ export class AddFeatureChange extends FeatureChange {
|
|
|
116
123
|
)
|
|
117
124
|
featureCnt++
|
|
118
125
|
} else {
|
|
126
|
+
const indexedIds = this.getIndexedIds(addedFeature, idsToIndex)
|
|
119
127
|
// Adding new child feature
|
|
120
128
|
if (parentFeatureId) {
|
|
121
129
|
const topLevelFeature = await featureModel
|
|
@@ -139,12 +147,16 @@ export class AddFeatureChange extends FeatureChange {
|
|
|
139
147
|
this.addChild(parentFeature, addedFeature)
|
|
140
148
|
const childIds = this.getChildFeatureIds(addedFeature)
|
|
141
149
|
topLevelFeature.allIds.push(_id, ...childIds)
|
|
150
|
+
if (indexedIds.length > 0 && !topLevelFeature.indexedIds) {
|
|
151
|
+
topLevelFeature.indexedIds = []
|
|
152
|
+
}
|
|
153
|
+
topLevelFeature.indexedIds?.push(...indexedIds)
|
|
142
154
|
await topLevelFeature.save()
|
|
143
155
|
} else {
|
|
144
156
|
const childIds = this.getChildFeatureIds(addedFeature)
|
|
145
157
|
const allIdsV2 = [_id, ...childIds]
|
|
146
158
|
const [newFeatureDoc] = await featureModel.create(
|
|
147
|
-
[{ allIds: allIdsV2, status: 0, ...addedFeature }],
|
|
159
|
+
[{ allIds: allIdsV2, indexedIds, status: 0, ...addedFeature }],
|
|
148
160
|
{ session },
|
|
149
161
|
)
|
|
150
162
|
logger.verbose?.(`Added docId "${newFeatureDoc._id}"`)
|
|
@@ -67,6 +67,12 @@ export class DeleteFeatureChange extends FeatureChange {
|
|
|
67
67
|
const { featureModel, session } = backend
|
|
68
68
|
const { changes, logger } = this
|
|
69
69
|
|
|
70
|
+
const { INDEXED_IDS } = process.env
|
|
71
|
+
let idsToIndex: string[] | undefined
|
|
72
|
+
if (INDEXED_IDS) {
|
|
73
|
+
idsToIndex = INDEXED_IDS.split(',')
|
|
74
|
+
}
|
|
75
|
+
|
|
70
76
|
// Loop the changes
|
|
71
77
|
for (const change of changes) {
|
|
72
78
|
const { deletedFeature, parentFeatureId } = change
|
|
@@ -105,6 +111,18 @@ export class DeleteFeatureChange extends FeatureChange {
|
|
|
105
111
|
featureDoc.allIds = featureDoc.allIds.filter(
|
|
106
112
|
(id) => !deletedIds.includes(id),
|
|
107
113
|
)
|
|
114
|
+
const indexedIds = this.getIndexedIds(featureDoc, idsToIndex)
|
|
115
|
+
if (featureDoc.indexedIds) {
|
|
116
|
+
if (indexedIds.length > 0) {
|
|
117
|
+
featureDoc.indexedIds = indexedIds
|
|
118
|
+
} else {
|
|
119
|
+
delete featureDoc.indexedIds
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
if (indexedIds.length > 0) {
|
|
123
|
+
featureDoc.indexedIds = indexedIds
|
|
124
|
+
}
|
|
125
|
+
}
|
|
108
126
|
// Save updated document in Mongo
|
|
109
127
|
featureDoc.markModified('children') // Mark as modified. Without this save() -method is not updating data in database
|
|
110
128
|
try {
|
|
@@ -100,11 +100,24 @@ export class FeatureAttributeChange extends FeatureChange {
|
|
|
100
100
|
featuresForChanges.push({ feature: foundFeature, topLevelFeature })
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
const { INDEXED_IDS } = process.env
|
|
104
|
+
let idsToIndex: string[] | undefined
|
|
105
|
+
if (INDEXED_IDS) {
|
|
106
|
+
idsToIndex = INDEXED_IDS.split(',')
|
|
107
|
+
}
|
|
103
108
|
// Let's update objects
|
|
104
109
|
for (const [idx, change] of changes.entries()) {
|
|
105
110
|
const { newAttributes } = change
|
|
106
111
|
const { feature, topLevelFeature } = featuresForChanges[idx]
|
|
112
|
+
const indexedIdsChanged = idsToIndex?.some(
|
|
113
|
+
(id) => id in newAttributes || id in (feature?.attributes ?? {}),
|
|
114
|
+
)
|
|
107
115
|
feature.attributes = newAttributes
|
|
116
|
+
if (indexedIdsChanged) {
|
|
117
|
+
const indexedIds = this.getIndexedIds(topLevelFeature, idsToIndex)
|
|
118
|
+
topLevelFeature.indexedIds = indexedIds
|
|
119
|
+
topLevelFeature.markModified('indexedIds')
|
|
120
|
+
}
|
|
108
121
|
if (topLevelFeature._id.equals(feature._id)) {
|
|
109
122
|
topLevelFeature.markModified('attributes') // Mark as modified. Without this save() -method is not updating data in database
|
|
110
123
|
} else {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
AssemblySpecificChange,
|
|
5
5
|
type ServerDataStore,
|
|
6
6
|
} from '@apollo-annotation/common'
|
|
7
|
+
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
7
8
|
import {
|
|
8
9
|
type FileDocument,
|
|
9
10
|
type RefSeqDocument,
|
|
@@ -183,8 +184,13 @@ export abstract class FromFileBaseChange extends AssemblySpecificChange {
|
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
async addFeatureIntoDb(gff3Feature: GFF3Feature, backend: ServerDataStore) {
|
|
186
|
-
const {
|
|
187
|
+
const { INDEXED_IDS } = process.env
|
|
188
|
+
let idsToIndex: string[] | undefined
|
|
189
|
+
if (INDEXED_IDS) {
|
|
190
|
+
idsToIndex = INDEXED_IDS.split(',')
|
|
191
|
+
}
|
|
187
192
|
const { assembly, refSeqCache } = this
|
|
193
|
+
const { featureModel, refSeqModel, user } = backend
|
|
188
194
|
|
|
189
195
|
const [{ seq_id: refName }] = gff3Feature
|
|
190
196
|
if (!refName) {
|
|
@@ -206,19 +212,25 @@ export abstract class FromFileBaseChange extends AssemblySpecificChange {
|
|
|
206
212
|
`RefSeq was not found by assembly "${assembly}" and seq_id "${refName}" not found`,
|
|
207
213
|
)
|
|
208
214
|
}
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
const newFeature = gff3ToAnnotationFeature(
|
|
213
|
-
gff3Feature,
|
|
214
|
-
refSeqDoc._id,
|
|
215
|
-
featureIds,
|
|
216
|
-
)
|
|
215
|
+
const newFeature = gff3ToAnnotationFeature(gff3Feature, refSeqDoc._id)
|
|
216
|
+
const allIds = this.getAllIds(newFeature)
|
|
217
|
+
const indexedIds = this.getIndexedIds(newFeature, idsToIndex)
|
|
217
218
|
|
|
218
219
|
// Add into Mongo
|
|
219
220
|
// We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
|
|
220
221
|
await featureModel.create([
|
|
221
|
-
{ allIds
|
|
222
|
+
{ allIds, indexedIds, ...newFeature, user, status: -1 },
|
|
222
223
|
])
|
|
223
224
|
}
|
|
225
|
+
|
|
226
|
+
getAllIds(feature: AnnotationFeatureSnapshot): string[] {
|
|
227
|
+
const allIds = [feature._id]
|
|
228
|
+
if (feature.children) {
|
|
229
|
+
for (const child of Object.values(feature.children)) {
|
|
230
|
+
const childIds = this.getAllIds(child)
|
|
231
|
+
allIds.push(...childIds)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return allIds
|
|
235
|
+
}
|
|
224
236
|
}
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
/* eslint-disable prefer-destructuring */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
3
4
|
import { describe, it } from 'node:test'
|
|
4
5
|
|
|
5
6
|
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
6
|
-
import {
|
|
7
|
+
import { formatSync } from '@gmod/gff'
|
|
8
|
+
import { assert, expect } from 'chai'
|
|
7
9
|
|
|
8
10
|
import { annotationFeatureToGFF3 } from './annotationFeatureToGFF3'
|
|
9
|
-
import { readAnnotationFeatureSnapshot } from './
|
|
11
|
+
import { readAnnotationFeatureSnapshot, testCases } from './testUtil'
|
|
12
|
+
|
|
13
|
+
describe('Converts AnnotationFeatureSnapshot JSON to GFF3 when', () => {
|
|
14
|
+
for (const testCase of testCases) {
|
|
15
|
+
const { filenameStem, description } = testCase
|
|
16
|
+
it(description, () => {
|
|
17
|
+
const annotationFeatures = JSON.parse(
|
|
18
|
+
readFileSync(`test_data/${filenameStem}.json`, 'utf8'),
|
|
19
|
+
) as AnnotationFeatureSnapshot[]
|
|
20
|
+
const expectedGFF3 = readFileSync(
|
|
21
|
+
`test_data/${filenameStem}.gff3`,
|
|
22
|
+
'utf8',
|
|
23
|
+
)
|
|
24
|
+
const gffFeatures = annotationFeatures.map((annotationFeature) =>
|
|
25
|
+
annotationFeatureToGFF3(annotationFeature),
|
|
26
|
+
)
|
|
27
|
+
const gff3 = formatSync(gffFeatures)
|
|
28
|
+
expect(gff3).to.equal(expectedGFF3)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
})
|
|
10
32
|
|
|
11
33
|
describe('annotationFeatureToGFF3', () => {
|
|
12
34
|
it('Test mandatory columns', () => {
|
|
@@ -8,77 +8,10 @@ import { assert, use } from 'chai'
|
|
|
8
8
|
import chaiExclude from 'chai-exclude'
|
|
9
9
|
|
|
10
10
|
import { gff3ToAnnotationFeature } from './gff3ToAnnotationFeature'
|
|
11
|
+
import { readAnnotationFeatureSnapshot, testCases } from './testUtil'
|
|
11
12
|
|
|
12
13
|
use(chaiExclude)
|
|
13
14
|
|
|
14
|
-
const testCases: [string, string, AnnotationFeatureSnapshot][] = [
|
|
15
|
-
[
|
|
16
|
-
'a feature with no children',
|
|
17
|
-
'ctgA example remark 1000 2000 . . . Name=Remark:hga;Alias=hga\n',
|
|
18
|
-
{
|
|
19
|
-
_id: '66c51f3e002c683eaf98a223',
|
|
20
|
-
refSeq: 'ctgA',
|
|
21
|
-
type: 'remark',
|
|
22
|
-
min: 999,
|
|
23
|
-
max: 2000,
|
|
24
|
-
attributes: {
|
|
25
|
-
gff_source: ['example'],
|
|
26
|
-
gff_name: ['Remark:hga'],
|
|
27
|
-
gff_alias: ['hga'],
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
[
|
|
32
|
-
'a feature with two children',
|
|
33
|
-
`ctgA est EST_match 1050 3202 . + . ID=Match1;Name=agt830.5;Target=agt830.5 1 654
|
|
34
|
-
ctgA est match_part 1050 1500 . + . Parent=Match1;Name=agt830.5;Target=agt830.5 1 451
|
|
35
|
-
ctgA est match_part 3000 3202 . + . Parent=Match1;Name=agt830.5;Target=agt830.5 452 654
|
|
36
|
-
`,
|
|
37
|
-
{
|
|
38
|
-
_id: '66cf9fbb4e947fa2c27d3d6a',
|
|
39
|
-
refSeq: 'ctgA',
|
|
40
|
-
type: 'EST_match',
|
|
41
|
-
min: 1049,
|
|
42
|
-
max: 3202,
|
|
43
|
-
strand: 1,
|
|
44
|
-
children: {
|
|
45
|
-
'66cf9fbb4e947fa2c27d3d68': {
|
|
46
|
-
_id: '66cf9fbb4e947fa2c27d3d68',
|
|
47
|
-
refSeq: 'ctgA',
|
|
48
|
-
type: 'match_part',
|
|
49
|
-
min: 1049,
|
|
50
|
-
max: 1500,
|
|
51
|
-
strand: 1,
|
|
52
|
-
attributes: {
|
|
53
|
-
gff_source: ['est'],
|
|
54
|
-
gff_name: ['agt830.5'],
|
|
55
|
-
gff_target: ['agt830.5 1 451'],
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
'66cf9fbb4e947fa2c27d3d69': {
|
|
59
|
-
_id: '66cf9fbb4e947fa2c27d3d69',
|
|
60
|
-
refSeq: 'ctgA',
|
|
61
|
-
type: 'match_part',
|
|
62
|
-
min: 2999,
|
|
63
|
-
max: 3202,
|
|
64
|
-
strand: 1,
|
|
65
|
-
attributes: {
|
|
66
|
-
gff_source: ['est'],
|
|
67
|
-
gff_name: ['agt830.5'],
|
|
68
|
-
gff_target: ['agt830.5 452 654'],
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
attributes: {
|
|
73
|
-
gff_source: ['est'],
|
|
74
|
-
gff_id: ['Match1'],
|
|
75
|
-
gff_name: ['agt830.5'],
|
|
76
|
-
gff_target: ['agt830.5 1 654'],
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
]
|
|
81
|
-
|
|
82
15
|
interface AnnotationFeatureSnapshotWithChildrenArray
|
|
83
16
|
extends Omit<AnnotationFeatureSnapshot, 'children'> {
|
|
84
17
|
children?: AnnotationFeatureSnapshotWithChildrenArray[]
|
|
@@ -108,6 +41,29 @@ function compareFeatures(
|
|
|
108
41
|
)
|
|
109
42
|
}
|
|
110
43
|
|
|
44
|
+
describe('Converts GFF3 to AnnotationFeatureSnapshot JSON when', () => {
|
|
45
|
+
for (const testCase of testCases) {
|
|
46
|
+
const { filenameStem, description } = testCase
|
|
47
|
+
it(description, () => {
|
|
48
|
+
const fileText = readFileSync(`test_data/${filenameStem}.gff3`, 'utf8')
|
|
49
|
+
const gffFeatures = parseStringSync(fileText, { parseSequences: false })
|
|
50
|
+
const annotationFeatures = gffFeatures.map((gff3Feature) =>
|
|
51
|
+
gff3ToAnnotationFeature(gff3Feature),
|
|
52
|
+
)
|
|
53
|
+
const annotationFeaturesExpected = JSON.parse(
|
|
54
|
+
readFileSync(`test_data/${filenameStem}.json`, 'utf8'),
|
|
55
|
+
) as AnnotationFeatureSnapshot[]
|
|
56
|
+
for (const [
|
|
57
|
+
i,
|
|
58
|
+
annotationFeatureExpected,
|
|
59
|
+
] of annotationFeaturesExpected.entries()) {
|
|
60
|
+
const annotationFeature = annotationFeatures[i]
|
|
61
|
+
compareFeatures(annotationFeature, annotationFeatureExpected)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
111
67
|
function readFeatureFile(fn: string): GFF3Feature[] {
|
|
112
68
|
const lines = readFileSync(fn).toString().split('\n')
|
|
113
69
|
const feature: string[] = []
|
|
@@ -120,13 +76,6 @@ function readFeatureFile(fn: string): GFF3Feature[] {
|
|
|
120
76
|
return inGff
|
|
121
77
|
}
|
|
122
78
|
|
|
123
|
-
export function readAnnotationFeatureSnapshot(
|
|
124
|
-
fn: string,
|
|
125
|
-
): AnnotationFeatureSnapshot {
|
|
126
|
-
const lines = readFileSync(fn).toString()
|
|
127
|
-
return JSON.parse(lines) as AnnotationFeatureSnapshot
|
|
128
|
-
}
|
|
129
|
-
|
|
130
79
|
const [ex1, , ex2, , ex3, , ex4] = readFeatureFile(
|
|
131
80
|
'test_data/gene_representations.gff3',
|
|
132
81
|
)
|
|
@@ -139,13 +88,6 @@ describe('gff3ToAnnotationFeature examples', () => {
|
|
|
139
88
|
const expected = readAnnotationFeatureSnapshot('test_data/one_cds.json')
|
|
140
89
|
compareFeatures(actual, expected)
|
|
141
90
|
})
|
|
142
|
-
it('Convert two CDSs', () => {
|
|
143
|
-
const actual = gff3ToAnnotationFeature(
|
|
144
|
-
readFeatureFile('test_data/two_cds.gff3')[0],
|
|
145
|
-
)
|
|
146
|
-
const expected = readAnnotationFeatureSnapshot('test_data/two_cds.json')
|
|
147
|
-
compareFeatures(actual, expected)
|
|
148
|
-
})
|
|
149
91
|
it('Convert example 1', () => {
|
|
150
92
|
const actual = gff3ToAnnotationFeature(ex1)
|
|
151
93
|
const txt = JSON.stringify(actual, null, 2)
|
|
@@ -228,19 +170,6 @@ describe('CDS without exons', () => {
|
|
|
228
170
|
})
|
|
229
171
|
})
|
|
230
172
|
|
|
231
|
-
describe('gff3ToAnnotationFeature', () => {
|
|
232
|
-
for (const testCase of testCases) {
|
|
233
|
-
const [description, featureLine, convertedFeature] = testCase
|
|
234
|
-
it(`converts ${description}`, () => {
|
|
235
|
-
const gff3Feature = parseStringSync(featureLine, {
|
|
236
|
-
parseSequences: false,
|
|
237
|
-
})
|
|
238
|
-
const feature = gff3ToAnnotationFeature(gff3Feature[0])
|
|
239
|
-
compareFeatures(convertedFeature, feature)
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
})
|
|
243
|
-
|
|
244
173
|
describe('Source and score', () => {
|
|
245
174
|
it('Convert score and source', () => {
|
|
246
175
|
const gffFeature: GFF3Feature = [
|
|
@@ -8,7 +8,6 @@ import { gffToInternal, isGFFReservedAttribute } from './gffReservedKeys'
|
|
|
8
8
|
export function gff3ToAnnotationFeature(
|
|
9
9
|
gff3Feature: GFF3Feature,
|
|
10
10
|
refSeq?: string,
|
|
11
|
-
featureIds?: string[],
|
|
12
11
|
): AnnotationFeatureSnapshot {
|
|
13
12
|
const [firstFeature] = gff3Feature
|
|
14
13
|
const { end, seq_id: refName, start, strand, type } = firstFeature
|
|
@@ -35,7 +34,7 @@ export function gff3ToAnnotationFeature(
|
|
|
35
34
|
|
|
36
35
|
const [min, max] = getFeatureMinMax(gff3Feature)
|
|
37
36
|
|
|
38
|
-
const convertedChildren = convertChildren(gff3Feature, refSeq
|
|
37
|
+
const convertedChildren = convertChildren(gff3Feature, refSeq)
|
|
39
38
|
|
|
40
39
|
const convertedAttributes = convertFeatureAttributes(gff3Feature)
|
|
41
40
|
|
|
@@ -61,9 +60,6 @@ export function gff3ToAnnotationFeature(
|
|
|
61
60
|
if (convertedAttributes) {
|
|
62
61
|
feature.attributes = convertedAttributes
|
|
63
62
|
}
|
|
64
|
-
if (featureIds) {
|
|
65
|
-
featureIds.push(feature._id)
|
|
66
|
-
}
|
|
67
63
|
return feature
|
|
68
64
|
}
|
|
69
65
|
|
|
@@ -182,13 +178,13 @@ function convertChildren(
|
|
|
182
178
|
if (firstChildFeatureLocation.type === 'CDS') {
|
|
183
179
|
cdsFeatures.push(childFeature)
|
|
184
180
|
} else {
|
|
185
|
-
const child = gff3ToAnnotationFeature(childFeature, refSeq
|
|
181
|
+
const child = gff3ToAnnotationFeature(childFeature, refSeq)
|
|
186
182
|
convertedChildren[child._id] = child
|
|
187
183
|
}
|
|
188
184
|
}
|
|
189
185
|
|
|
190
186
|
if (cdsFeatures.length > 0) {
|
|
191
|
-
const processedCDS = processCDS(cdsFeatures, refSeq
|
|
187
|
+
const processedCDS = processCDS(cdsFeatures, refSeq)
|
|
192
188
|
|
|
193
189
|
for (const cds of processedCDS) {
|
|
194
190
|
convertedChildren[cds._id] = cds
|
|
@@ -372,16 +368,13 @@ function mergeAnnotationFeatures(
|
|
|
372
368
|
function processCDS(
|
|
373
369
|
cdsFeatures: GFF3Feature[],
|
|
374
370
|
refSeq?: string,
|
|
375
|
-
featureIds?: string[],
|
|
376
371
|
): AnnotationFeatureSnapshot[] {
|
|
377
372
|
const locationCounts = cdsFeatures.map((cds) => cds.length)
|
|
378
373
|
// If any CDS have multiple locations, assume it really is multiple CDS
|
|
379
374
|
// (e.g. the mRNA has multiple alternative translational start sites)
|
|
380
375
|
// and process normally.
|
|
381
376
|
if (locationCounts.some((count) => count > 1)) {
|
|
382
|
-
return cdsFeatures.map((cds) =>
|
|
383
|
-
gff3ToAnnotationFeature(cds, refSeq, featureIds),
|
|
384
|
-
)
|
|
377
|
+
return cdsFeatures.map((cds) => gff3ToAnnotationFeature(cds, refSeq))
|
|
385
378
|
}
|
|
386
379
|
// If all CDS have a single location, we guess that this GFF3 represented CDS
|
|
387
380
|
// as multiple features instead of a single feature with multiple locations.
|
|
@@ -402,7 +395,7 @@ function processCDS(
|
|
|
402
395
|
})
|
|
403
396
|
// If no overlaps, assume it's a single CDS feature
|
|
404
397
|
if (!overlapping) {
|
|
405
|
-
return [gff3ToAnnotationFeature(sortedCDSLocations, refSeq
|
|
398
|
+
return [gff3ToAnnotationFeature(sortedCDSLocations, refSeq)]
|
|
406
399
|
}
|
|
407
400
|
// Some CDS locations overlap, the best we can do is use the original order to
|
|
408
401
|
// guess how to group the locations into features
|
|
@@ -430,7 +423,5 @@ function processCDS(
|
|
|
430
423
|
lastGroup.push(location)
|
|
431
424
|
}
|
|
432
425
|
}
|
|
433
|
-
return groupedLocations.map((group) =>
|
|
434
|
-
gff3ToAnnotationFeature(group, refSeq, featureIds),
|
|
435
|
-
)
|
|
426
|
+
return groupedLocations.map((group) => gff3ToAnnotationFeature(group, refSeq))
|
|
436
427
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
4
|
+
|
|
5
|
+
export function readAnnotationFeatureSnapshot(
|
|
6
|
+
fn: string,
|
|
7
|
+
): AnnotationFeatureSnapshot {
|
|
8
|
+
const lines = readFileSync(fn).toString()
|
|
9
|
+
return JSON.parse(lines) as AnnotationFeatureSnapshot
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const testCases: { filenameStem: string; description: string }[] = [
|
|
13
|
+
{
|
|
14
|
+
filenameStem: 'single_feature_no_children',
|
|
15
|
+
description: 'there is a single feature with no children',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
filenameStem: 'single_feature_two_children',
|
|
19
|
+
description: 'there is a single feature with two children',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
filenameStem: 'gene_with_two_cds',
|
|
23
|
+
description: 'Gene with two CDS',
|
|
24
|
+
},
|
|
25
|
+
]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
chr1 . gene 1000 9000 . + . testid=t003;ID=gene10001;Name=EDEN
|
|
2
|
+
chr1 . mRNA 1050 9000 . + . testid=t004,t001,t004;Parent=gene10001;ID=mRNA10001;Name=EDEN.1
|
|
3
|
+
chr1 . exon 1050 1500 . + . testid=t007;Parent=mRNA10001;ID=exon10001
|
|
4
|
+
chr1 . exon 5000 5500 . + . testid=t010;Parent=mRNA10001;ID=exon10004
|
|
5
|
+
chr1 . CDS 1201 1500 . + 0 testid=t012,t013,t014;Parent=mRNA10001;ID=cds10001;Name=edenprotein.1
|
|
6
|
+
chr1 . CDS 5000 5000 . + 0 testid=t012,t013,t014;Parent=mRNA10001;ID=cds10001;Name=edenprotein.1
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"_id": "66d70f3b9c7a7460925687a3",
|
|
4
|
+
"refSeq": "chr1",
|
|
5
|
+
"type": "gene",
|
|
6
|
+
"min": 999,
|
|
7
|
+
"max": 9000,
|
|
8
|
+
"strand": 1,
|
|
9
|
+
"children": {
|
|
10
|
+
"66d70f3b9c7a7460925687a2": {
|
|
11
|
+
"_id": "66d70f3b9c7a7460925687a2",
|
|
12
|
+
"refSeq": "chr1",
|
|
13
|
+
"type": "mRNA",
|
|
14
|
+
"min": 1049,
|
|
15
|
+
"max": 9000,
|
|
16
|
+
"strand": 1,
|
|
17
|
+
"children": {
|
|
18
|
+
"66d70f3b9c7a74609256879f": {
|
|
19
|
+
"_id": "66d70f3b9c7a74609256879f",
|
|
20
|
+
"refSeq": "chr1",
|
|
21
|
+
"type": "exon",
|
|
22
|
+
"min": 1049,
|
|
23
|
+
"max": 1500,
|
|
24
|
+
"strand": 1,
|
|
25
|
+
"attributes": {
|
|
26
|
+
"testid": ["t007"],
|
|
27
|
+
"gff_id": ["exon10001"]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"66d70f3b9c7a7460925687a0": {
|
|
31
|
+
"_id": "66d70f3b9c7a7460925687a0",
|
|
32
|
+
"refSeq": "chr1",
|
|
33
|
+
"type": "exon",
|
|
34
|
+
"min": 4999,
|
|
35
|
+
"max": 5500,
|
|
36
|
+
"strand": 1,
|
|
37
|
+
"attributes": {
|
|
38
|
+
"testid": ["t010"],
|
|
39
|
+
"gff_id": ["exon10004"]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"66d70f3b9c7a7460925687a1": {
|
|
43
|
+
"_id": "66d70f3b9c7a7460925687a1",
|
|
44
|
+
"refSeq": "chr1",
|
|
45
|
+
"type": "CDS",
|
|
46
|
+
"min": 1200,
|
|
47
|
+
"max": 5000,
|
|
48
|
+
"strand": 1,
|
|
49
|
+
"attributes": {
|
|
50
|
+
"testid": ["t012", "t013", "t014"],
|
|
51
|
+
"gff_id": ["cds10001"],
|
|
52
|
+
"gff_name": ["edenprotein.1"]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"attributes": {
|
|
57
|
+
"testid": ["t004", "t001", "t004"],
|
|
58
|
+
"gff_id": ["mRNA10001"],
|
|
59
|
+
"gff_name": ["EDEN.1"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"attributes": {
|
|
64
|
+
"testid": ["t003"],
|
|
65
|
+
"gff_id": ["gene10001"],
|
|
66
|
+
"gff_name": ["EDEN"]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ctgA example remark 1000 2000 . . . Name=Remark:hga;Alias=hga
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"_id": "6931fc4f88722ca882ba334c",
|
|
4
|
+
"refSeq": "ctgA",
|
|
5
|
+
"type": "EST_match",
|
|
6
|
+
"min": 1049,
|
|
7
|
+
"max": 3202,
|
|
8
|
+
"strand": 1,
|
|
9
|
+
"children": {
|
|
10
|
+
"6931fc4f88722ca882ba334a": {
|
|
11
|
+
"_id": "6931fc4f88722ca882ba334a",
|
|
12
|
+
"refSeq": "ctgA",
|
|
13
|
+
"type": "match_part",
|
|
14
|
+
"min": 1049,
|
|
15
|
+
"max": 1500,
|
|
16
|
+
"strand": 1,
|
|
17
|
+
"attributes": {
|
|
18
|
+
"gff_source": ["est"],
|
|
19
|
+
"gff_name": ["agt830.5"],
|
|
20
|
+
"gff_target": ["agt830.5 1 451"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"6931fc4f88722ca882ba334b": {
|
|
24
|
+
"_id": "6931fc4f88722ca882ba334b",
|
|
25
|
+
"refSeq": "ctgA",
|
|
26
|
+
"type": "match_part",
|
|
27
|
+
"min": 2999,
|
|
28
|
+
"max": 3202,
|
|
29
|
+
"strand": 1,
|
|
30
|
+
"attributes": {
|
|
31
|
+
"gff_source": ["est"],
|
|
32
|
+
"gff_name": ["agt830.5"],
|
|
33
|
+
"gff_target": ["agt830.5 452 654"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"attributes": {
|
|
38
|
+
"gff_source": ["est"],
|
|
39
|
+
"gff_id": ["Match1"],
|
|
40
|
+
"gff_name": ["agt830.5"],
|
|
41
|
+
"gff_target": ["agt830.5 1 654"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
]
|
package/test_data/two_cds.gff3
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
##gff-version 3
|
|
2
|
-
##sequence-region chr1 1000 9000
|
|
3
|
-
#example01
|
|
4
|
-
chr1 . gene 1000 9000 . + . ID=gene10001;Name=EDEN;testid=t003
|
|
5
|
-
chr1 . mRNA 1050 9000 . + . ID=mRNA10001;Parent=gene10001;Name=EDEN.1;testid=t004,t001,t004
|
|
6
|
-
chr1 . exon 1050 1500 . + . ID=exon10001;Parent=mRNA10001;testid=t007
|
|
7
|
-
chr1 . exon 5000 5500 . + . ID=exon10004;Parent=mRNA10001;testid=t010
|
|
8
|
-
chr1 . CDS 1201 1500 . + 0 ID=cds10001;Parent=mRNA10001;Name=edenprotein.1;testid=t012,t013,t014
|
|
9
|
-
chr1 . CDS 5000 5000 . + 0 ID=cds10001;Parent=mRNA10001;Name=edenprotein.1;testid=t014
|