@sjcrh/proteinpaint-server 2.30.4 → 2.31.0
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/genome/hg19.js +3 -3
- package/genome/hg38.js +3 -3
- package/genome/hg38.test.js +3 -3
- package/package.json +2 -2
- package/routes/burden.ts +5 -2
- package/routes/gdc.maf.ts +39 -26
- package/routes/gdc.mafBuild.ts +24 -30
- package/routes/gdc.topVariablyExpressedGenes.ts +39 -23
- package/routes/hicdata.ts +113 -0
- package/routes/hicstat.ts +55 -0
- package/routes/termdb.categories.ts +30 -6
- package/routes/termdb.getpercentile.ts +117 -0
- package/routes/termdb.singlecellSamples.ts +45 -0
- package/routes/termdb.termbyid.ts +5 -1
- package/server.js +1 -1
- package/shared/common.js +10 -12
- package/src/serverconfig.js +0 -19
- package/cards/2dmaf.json +0 -37
- package/cards/README.md +0 -15
- package/cards/ai.json +0 -37
- package/cards/arc.json +0 -34
- package/cards/ase.json +0 -99
- package/cards/bam.json +0 -264
- package/cards/bampile.json +0 -22
- package/cards/bedj.json +0 -304
- package/cards/bigwig.json +0 -70
- package/cards/citations.json +0 -39
- package/cards/civicBtn.json +0 -15
- package/cards/databrowser.json +0 -276
- package/cards/disco.json +0 -69
- package/cards/dnanexusTips.txt +0 -221
- package/cards/exprank.json +0 -64
- package/cards/featuredDatasets.json +0 -84
- package/cards/fusioneditor.json +0 -34
- package/cards/gdcbam.json +0 -23
- package/cards/genefusion.json +0 -57
- package/cards/genomepaint.json +0 -122
- package/cards/hic.json +0 -79
- package/cards/index.json +0 -327
- package/cards/junction.json +0 -98
- package/cards/lollipop.json +0 -327
- package/cards/maf.timeline.json +0 -19
- package/cards/mavb.json +0 -47
- package/cards/nciGdcBtn.json +0 -21
- package/cards/pcmBtn.json +0 -16
- package/cards/ped2Btn.json +0 -16
- package/cards/pgv.json +0 -59
- package/cards/scatterplot.json +0 -31
- package/cards/singlecell.json +0 -25
- package/cards/study.json +0 -34
- package/cards/survivorBtn.json +0 -18
- package/cards/svview.txt +0 -101
- package/cards/tkFeatures.json +0 -92
- package/cards/tklist.json +0 -27
package/genome/hg19.js
CHANGED
|
@@ -44,9 +44,9 @@ exports.default = {
|
|
|
44
44
|
vpad: 4,
|
|
45
45
|
categories: {
|
|
46
46
|
coding: { color: '#004D99', label: 'Coding gene' },
|
|
47
|
-
nonCoding: { color: '#
|
|
48
|
-
problem: { color: '#
|
|
49
|
-
pseudo: { color: '#
|
|
47
|
+
nonCoding: { color: '#008833', label: 'Noncoding gene' },
|
|
48
|
+
problem: { color: '#CC3300', label: 'Problem' },
|
|
49
|
+
pseudo: { color: '#CC00CC', label: 'Pseudogene' }
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
{
|
package/genome/hg38.js
CHANGED
|
@@ -48,9 +48,9 @@ exports.default = {
|
|
|
48
48
|
translatecoding: true,
|
|
49
49
|
categories: {
|
|
50
50
|
coding: { color: '#004D99', label: 'Coding gene' },
|
|
51
|
-
nonCoding: { color: '#
|
|
52
|
-
problem: { color: '#
|
|
53
|
-
pseudo: { color: '#
|
|
51
|
+
nonCoding: { color: '#008833', label: 'Noncoding gene' },
|
|
52
|
+
problem: { color: '#CC3300', label: 'Problem' },
|
|
53
|
+
pseudo: { color: '#CC00CC', label: 'Pseudogene' }
|
|
54
54
|
},
|
|
55
55
|
type: 'bedj',
|
|
56
56
|
stackheight: 16,
|
package/genome/hg38.test.js
CHANGED
|
@@ -24,9 +24,9 @@ var genome = {
|
|
|
24
24
|
translatecoding: true,
|
|
25
25
|
categories: {
|
|
26
26
|
coding: { color: '#004D99', label: 'Coding gene' },
|
|
27
|
-
nonCoding: { color: '#
|
|
28
|
-
problem: { color: '#
|
|
29
|
-
pseudo: { color: '#
|
|
27
|
+
nonCoding: { color: '#008833', label: 'Noncoding gene' },
|
|
28
|
+
problem: { color: '#CC3300', label: 'Problem' },
|
|
29
|
+
pseudo: { color: '#CC00CC', label: 'Pseudogene' }
|
|
30
30
|
},
|
|
31
31
|
type: 'bedj',
|
|
32
32
|
name: 'GENCODE v41',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.0",
|
|
4
4
|
"description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": "start.js",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@sjcrh/augen": "2.27.0",
|
|
59
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
59
|
+
"@sjcrh/proteinpaint-rust": "2.31.0",
|
|
60
60
|
"better-sqlite3": "^7.5.3",
|
|
61
61
|
"body-parser": "^1.15.2",
|
|
62
62
|
"canvas": "~2.9.3",
|
package/routes/burden.ts
CHANGED
|
@@ -69,7 +69,10 @@ export const api = {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
async function getBurdenEstimates(
|
|
72
|
+
async function getBurdenEstimates(
|
|
73
|
+
q: { query: { [x: string]: any } },
|
|
74
|
+
ds: { cohort: { cumburden: { files: { fit: any; surv: any; sample: any } } } }
|
|
75
|
+
) {
|
|
73
76
|
const infile = path.join(serverconfig.cachedir, Math.random().toString() + '.json')
|
|
74
77
|
for (const k in q.query) {
|
|
75
78
|
q.query[k] = Number(q.query[k])
|
|
@@ -91,7 +94,7 @@ async function getBurdenEstimates(q, ds) {
|
|
|
91
94
|
return estimates
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
function formatPayload(estimates) {
|
|
97
|
+
function formatPayload(estimates: object[]) {
|
|
95
98
|
const rawKeys = Object.keys(estimates[0])
|
|
96
99
|
const outKeys = [] as string[]
|
|
97
100
|
const keys = [] as string[]
|
package/routes/gdc.maf.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GdcMafResponse, File } from '#shared/types/routes/gdc.maf.ts'
|
|
1
|
+
import { GdcMafRequest, GdcMafResponse, File } from '#shared/types/routes/gdc.maf.ts'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import got from 'got'
|
|
4
4
|
import serverconfig from '#src/serverconfig.js'
|
|
@@ -6,36 +6,51 @@ import serverconfig from '#src/serverconfig.js'
|
|
|
6
6
|
const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov'
|
|
7
7
|
const maxFileNumber = 1000
|
|
8
8
|
const allowedWorkflowType = 'Aliquot Ensemble Somatic Variant Merging and Masking'
|
|
9
|
-
const allowedExpStrategy = new Set(['WXS', 'Targeted Sequencing'])
|
|
10
9
|
const maxTotalSizeCompressed = serverconfig.features.gdcMafMaxFileSize || 50000000 // 50Mb
|
|
11
10
|
|
|
12
11
|
export const api = {
|
|
13
12
|
endpoint: 'gdc/maf',
|
|
14
13
|
methods: {
|
|
15
14
|
get: {
|
|
16
|
-
init
|
|
17
|
-
return async (req: any, res: any): Promise<void> => {
|
|
18
|
-
try {
|
|
19
|
-
const g = genomes.hg38
|
|
20
|
-
if (!g) throw 'hg38 missing'
|
|
21
|
-
const ds = g.datasets.GDC
|
|
22
|
-
if (!ds) throw 'hg38 GDC missing'
|
|
23
|
-
const payload = await listMafFiles(req)
|
|
24
|
-
res.send(payload)
|
|
25
|
-
} catch (e: any) {
|
|
26
|
-
res.send({ status: 'error', error: e.message || e })
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
},
|
|
15
|
+
init,
|
|
30
16
|
request: {
|
|
31
|
-
typeId:
|
|
32
|
-
//valid: default to type checker
|
|
17
|
+
typeId: 'GdcMafRequest'
|
|
33
18
|
},
|
|
34
19
|
response: {
|
|
35
20
|
typeId: 'GdcMafResponse'
|
|
36
21
|
// will combine this with type checker
|
|
37
22
|
//valid: (t) => {}
|
|
38
|
-
}
|
|
23
|
+
},
|
|
24
|
+
examples: [
|
|
25
|
+
{
|
|
26
|
+
request: {
|
|
27
|
+
body: {
|
|
28
|
+
experimentalStrategy: 'WXS',
|
|
29
|
+
embedder: 'localhost'
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
response: {
|
|
33
|
+
header: { status: 200 }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function init({ genomes }) {
|
|
42
|
+
return async (req: any, res: any): Promise<void> => {
|
|
43
|
+
try {
|
|
44
|
+
// g and ds are not used right now, but could be later
|
|
45
|
+
const g = genomes.hg38
|
|
46
|
+
if (!g) throw 'hg38 missing'
|
|
47
|
+
const ds = g.datasets.GDC
|
|
48
|
+
if (!ds) throw 'hg38 GDC missing'
|
|
49
|
+
|
|
50
|
+
const payload = await listMafFiles(req.query as GdcMafRequest)
|
|
51
|
+
res.send(payload)
|
|
52
|
+
} catch (e: any) {
|
|
53
|
+
res.send({ status: 'error', error: e.message || e })
|
|
39
54
|
}
|
|
40
55
|
}
|
|
41
56
|
}
|
|
@@ -52,21 +67,19 @@ ds {
|
|
|
52
67
|
}
|
|
53
68
|
}
|
|
54
69
|
*/
|
|
55
|
-
async function listMafFiles(
|
|
56
|
-
if (!allowedExpStrategy.has(req.query.experimentalStrategy)) throw 'invalid req.query.experimentalStrategy'
|
|
57
|
-
|
|
70
|
+
async function listMafFiles(q: GdcMafRequest) {
|
|
58
71
|
const filters = {
|
|
59
72
|
op: 'and',
|
|
60
73
|
content: [
|
|
61
74
|
{ op: '=', content: { field: 'data_format', value: 'MAF' } },
|
|
62
|
-
{ op: '=', content: { field: 'experimental_strategy', value:
|
|
75
|
+
{ op: '=', content: { field: 'experimental_strategy', value: q.experimentalStrategy } },
|
|
63
76
|
{ op: '=', content: { field: 'analysis.workflow_type', value: allowedWorkflowType } },
|
|
64
77
|
{ op: '=', content: { field: 'access', value: 'open' } } // delete if later to support controlled files
|
|
65
78
|
]
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
if (
|
|
69
|
-
filters.content.push(
|
|
81
|
+
if (q.filter0) {
|
|
82
|
+
filters.content.push(q.filter0)
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
const headers = { 'Content-Type': 'application/json', Accept: 'application/json' }
|
|
@@ -152,7 +165,7 @@ async function listMafFiles(req: any) {
|
|
|
152
165
|
|
|
153
166
|
file.case_submitter_id = c.submitter_id
|
|
154
167
|
if (c.samples) {
|
|
155
|
-
file.sample_types = c.samples.map(i => i.sample_type).sort()
|
|
168
|
+
file.sample_types = c.samples.map((i: { sample_type: any }) => i.sample_type).sort()
|
|
156
169
|
// sort to show sample type names in consistent alphabetical order
|
|
157
170
|
// otherwise one file shows 'Blood, Primary' and another shows 'Primary, Blood'
|
|
158
171
|
// FIXME this includes samples not associated with current maf file
|
package/routes/gdc.mafBuild.ts
CHANGED
|
@@ -3,6 +3,8 @@ import path from 'path'
|
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import { run_rust } from '@sjcrh/proteinpaint-rust'
|
|
5
5
|
import serverconfig from '#src/serverconfig.js'
|
|
6
|
+
import Readable from 'stream'
|
|
7
|
+
import { GdcMafBuildRequest } from '#shared/types/routes/gdc.mafBuild.ts'
|
|
6
8
|
|
|
7
9
|
const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov'
|
|
8
10
|
const maxTotalSizeCompressed = serverconfig.features.gdcMafMaxFileSize || 50000000 // 50Mb
|
|
@@ -11,58 +13,50 @@ export const api = {
|
|
|
11
13
|
endpoint: 'gdc/mafBuild',
|
|
12
14
|
methods: {
|
|
13
15
|
all: {
|
|
14
|
-
init
|
|
15
|
-
return async (req: any, res: any): Promise<void> => {
|
|
16
|
-
try {
|
|
17
|
-
await buildMaf(req, res)
|
|
18
|
-
} catch (e) {
|
|
19
|
-
if (e.stack) console.log(e.stack)
|
|
20
|
-
res.send({ status: 'error', error: e.message || e })
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
},
|
|
16
|
+
init,
|
|
24
17
|
request: {
|
|
25
|
-
typeId:
|
|
26
|
-
//valid: default to type checker
|
|
18
|
+
typeId: 'GdcMafBuildRequest'
|
|
27
19
|
},
|
|
28
20
|
response: {
|
|
29
|
-
typeId: 'GdcMafBuildResponse'
|
|
30
|
-
// will combine this with type checker
|
|
31
|
-
//valid: (t) => {}
|
|
21
|
+
typeId: null // 'GdcMafBuildResponse'
|
|
32
22
|
}
|
|
33
23
|
}
|
|
34
24
|
}
|
|
35
25
|
}
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
req
|
|
39
|
-
|
|
27
|
+
function init({ genomes }) {
|
|
28
|
+
return async (req: any, res: any): Promise<void> => {
|
|
29
|
+
try {
|
|
30
|
+
await buildMaf(req.query as GdcMafBuildRequest, res)
|
|
31
|
+
} catch (e: any) {
|
|
32
|
+
if (e.stack) console.log(e.stack)
|
|
33
|
+
res.send({ status: 'error', error: e.message || e })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
40
36
|
}
|
|
41
37
|
|
|
38
|
+
/*
|
|
39
|
+
q{}
|
|
42
40
|
res{}
|
|
43
41
|
*/
|
|
44
|
-
async function buildMaf(
|
|
45
|
-
const t0 =
|
|
46
|
-
|
|
47
|
-
const fileLst2 = (await getFileLstUnderSizeLimit(req.query.fileIdLst)) as string[]
|
|
48
|
-
console.log('test gdc maf sizes', new Date() - t0)
|
|
42
|
+
async function buildMaf(q: GdcMafBuildRequest, res: any) {
|
|
43
|
+
const t0 = Date.now()
|
|
49
44
|
|
|
50
|
-
const
|
|
45
|
+
const fileLst2 = (await getFileLstUnderSizeLimit(q.fileIdLst)) as string[]
|
|
46
|
+
console.log('test gdc maf sizes', Date.now() - t0)
|
|
51
47
|
|
|
52
48
|
const arg = {
|
|
53
49
|
fileIdLst: fileLst2,
|
|
54
|
-
host: path.join(apihost, 'data')
|
|
55
|
-
outFile
|
|
50
|
+
host: path.join(apihost, 'data') // must use the /data/ endpoint from current host
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
await run_rust('gdcmaf', JSON.stringify(arg))
|
|
53
|
+
const dataGzipped = await run_rust('gdcmaf', JSON.stringify(arg))
|
|
59
54
|
|
|
60
|
-
console.log('rust gdcmaf',
|
|
55
|
+
console.log('rust gdcmaf', Date.now() - t0)
|
|
61
56
|
|
|
62
|
-
const data =
|
|
57
|
+
const data = JSON.parse(dataGzipped)
|
|
63
58
|
|
|
64
59
|
// by directly returning a blob, it won't tell client how many files are used
|
|
65
|
-
|
|
66
60
|
res.writeHead(200, {
|
|
67
61
|
'Content-Type': 'application/octet-stream',
|
|
68
62
|
'Content-Disposition': 'attachment; filename=cohort.maf.gz',
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { GdcTopVariablyExpressedGenesResponse } from '#shared/types/routes/gdc.topVariablyExpressedGenes.ts'
|
|
2
2
|
import { getCasesWithExressionDataFromCohort } from '../src/mds3.gdc.js'
|
|
3
|
-
|
|
3
|
+
import path from 'path'
|
|
4
4
|
import got from 'got'
|
|
5
5
|
import serverconfig from '#src/serverconfig.js'
|
|
6
6
|
|
|
7
7
|
// TODO change when api is released to prod
|
|
8
8
|
//const apihost = process.env.PP_GDC_HOST || 'https://api.gdc.cancer.gov'
|
|
9
9
|
const apihost = 'https://uat-portal.gdc.cancer.gov/auth/api/v0/gene_expression/gene_selection'
|
|
10
|
+
// temporarily hardcode to use the direct API URL,
|
|
11
|
+
// previously hardcoded to use 'https://uat-portal.gdc.cancer.gov/auth/api/v0/'
|
|
12
|
+
const geneExpHost = 'https://uat-api.gdc.cancer.gov'
|
|
10
13
|
|
|
11
14
|
const gdcGenome = 'hg38'
|
|
12
15
|
const gdcDslabel = 'GDC'
|
|
@@ -23,7 +26,7 @@ export const api = {
|
|
|
23
26
|
if (!genome) throw 'hg38 genome missing'
|
|
24
27
|
const ds = genome.datasets?.[gdcDslabel]
|
|
25
28
|
if (!ds) throw 'gdc dataset missing'
|
|
26
|
-
const genes = await getGenes(req.query, ds)
|
|
29
|
+
const genes = await getGenes(req.query, ds, genome)
|
|
27
30
|
const payload = { genes } as GdcTopVariablyExpressedGenesResponse
|
|
28
31
|
res.send(payload)
|
|
29
32
|
} catch (e: any) {
|
|
@@ -53,12 +56,16 @@ req.query {
|
|
|
53
56
|
|
|
54
57
|
ds { } // server-side ds object
|
|
55
58
|
|
|
59
|
+
genome {}
|
|
56
60
|
*/
|
|
57
|
-
async function getGenes(q: any, ds: any) {
|
|
61
|
+
async function getGenes(q: any, ds: any, genome: any) {
|
|
58
62
|
if (serverconfig.features.gdcGenes) {
|
|
59
63
|
// for testing only; delete when api issue is resolved
|
|
60
64
|
return serverconfig.features.gdcGenes as string[]
|
|
61
65
|
}
|
|
66
|
+
if (!ds.__gdc.doneCaching) {
|
|
67
|
+
throw `The server has not finished caching the case IDs: try again in ~2 minutes`
|
|
68
|
+
}
|
|
62
69
|
|
|
63
70
|
// based on current cohort, get list of cases with exp data, as input of next api query
|
|
64
71
|
const caseLst = await getCasesWithExressionDataFromCohort(q, ds)
|
|
@@ -68,30 +75,39 @@ async function getGenes(q: any, ds: any) {
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
// change to this when api is available on prod
|
|
71
|
-
|
|
78
|
+
const url = path.join(geneExpHost, '/gene_expression/gene_selection')
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
try {
|
|
81
|
+
const response = await got.post(url, {
|
|
82
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
// !!! temporarily limit the case_ids length, otherwise the request times out !!!
|
|
85
|
+
case_ids: caseLst.slice(0, 20),
|
|
86
|
+
//gene_ids: [] // this should not be a required parameter
|
|
87
|
+
gene_type: 'protein_coding',
|
|
88
|
+
selection_size: Number(q.maxGenes || 100)
|
|
89
|
+
})
|
|
79
90
|
})
|
|
80
|
-
})
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
const re = JSON.parse(response.body)
|
|
93
|
+
// {"gene_selection":[{"gene_id":"ENSG00000141510","log2_uqfpkm_median":3.103430497010492,"log2_uqfpkm_stddev":0.8692021350485105,"symbol":"TP53"}, ... ]}
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
const genes = [] as string[]
|
|
96
|
+
if (!Array.isArray(re.gene_selection)) throw 're.gene_selection[] is not array'
|
|
97
|
+
for (const i of re.gene_selection) {
|
|
98
|
+
if (i.gene_id && typeof i.gene_id == 'string') {
|
|
99
|
+
// is ensg, convert to symbol
|
|
100
|
+
const t = genome.genedb.getNameByAlias.get(i.gene_id)
|
|
101
|
+
if (t) genes.push(t.name) // ensg
|
|
102
|
+
} else if (i.symbol && typeof i.symbol == 'string') {
|
|
103
|
+
genes.push(i.symbol)
|
|
104
|
+
} else {
|
|
105
|
+
throw 'one of re.gene_selection[] is missing both gene_id and symbol'
|
|
106
|
+
}
|
|
94
107
|
}
|
|
108
|
+
return genes
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.log(e.stack || e)
|
|
111
|
+
throw e
|
|
95
112
|
}
|
|
96
|
-
return genes
|
|
97
113
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { HicdataRequest, HicdataResponse, Item } from '#shared/types/routes/hicdata.ts'
|
|
2
|
+
import { fileurl } from '#src/utils.js'
|
|
3
|
+
import { spawn } from 'child_process'
|
|
4
|
+
import readline from 'readline'
|
|
5
|
+
import serverconfig from '#src/serverconfig.js'
|
|
6
|
+
|
|
7
|
+
export const api: any = {
|
|
8
|
+
endpoint: 'hicdata',
|
|
9
|
+
methods: {
|
|
10
|
+
get: {
|
|
11
|
+
init,
|
|
12
|
+
request: {
|
|
13
|
+
typeId: 'HicdataRequest'
|
|
14
|
+
},
|
|
15
|
+
response: {
|
|
16
|
+
typeId: 'HicdataResponse'
|
|
17
|
+
}
|
|
18
|
+
/*
|
|
19
|
+
examples: [
|
|
20
|
+
{
|
|
21
|
+
request: {
|
|
22
|
+
body: {
|
|
23
|
+
genome: 'hg38-test',
|
|
24
|
+
dslabel: 'TermdbTest',
|
|
25
|
+
embedder: 'localhost',
|
|
26
|
+
gettermbyid: 'subcohort'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
response: {
|
|
30
|
+
header: { status: 200 }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
*/
|
|
35
|
+
},
|
|
36
|
+
post: {
|
|
37
|
+
alternativeFor: 'get',
|
|
38
|
+
init
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function init({ genomes }) {
|
|
44
|
+
return async (req: any, res: any): Promise<void> => {
|
|
45
|
+
try {
|
|
46
|
+
const payload = await handle_hicdata(req.query as HicdataRequest)
|
|
47
|
+
res.send(payload)
|
|
48
|
+
} catch (e) {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
res.send({ error: e?.message || e })
|
|
52
|
+
if (e instanceof Error && e.stack) console.log(e)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function handle_hicdata(q: HicdataRequest) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const [e, file, isurl] = fileurl({ query: q })
|
|
60
|
+
if (e) reject({ error: 'illegal file name' })
|
|
61
|
+
|
|
62
|
+
const par = [
|
|
63
|
+
// TODO add option for observed/oe
|
|
64
|
+
q.nmeth || 'NONE',
|
|
65
|
+
file,
|
|
66
|
+
q.pos1,
|
|
67
|
+
q.pos2,
|
|
68
|
+
q.isfrag ? 'FRAG' : 'BP',
|
|
69
|
+
q.resolution
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
const ps = spawn(serverconfig.hicstraw, par)
|
|
73
|
+
const rl = readline.createInterface({ input: ps.stdout })
|
|
74
|
+
|
|
75
|
+
const items = [] as Item[]
|
|
76
|
+
const erroutput = [] as string[]
|
|
77
|
+
let linenot3fields = 0
|
|
78
|
+
let fieldnotnumerical = 0
|
|
79
|
+
|
|
80
|
+
rl.on('line', line => {
|
|
81
|
+
// straw output: pos1 \t pos2 \t value
|
|
82
|
+
const l = line.split('\t')
|
|
83
|
+
if (l.length != 3) {
|
|
84
|
+
linenot3fields++
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
const n1 = Number.parseInt(l[0])
|
|
88
|
+
const n2 = Number.parseInt(l[1])
|
|
89
|
+
const v = Number.parseFloat(l[2])
|
|
90
|
+
if (Number.isNaN(n1) || Number.isNaN(n2) || Number.isNaN(v)) {
|
|
91
|
+
fieldnotnumerical++
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (q.mincutoff != undefined && v <= q.mincutoff) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
items.push([n1, n2, v] as Item)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
ps.stderr.on('data', i => erroutput.push(i))
|
|
101
|
+
ps.on('close', () => {
|
|
102
|
+
const err = erroutput.join('')
|
|
103
|
+
if (err) reject({ error: err })
|
|
104
|
+
|
|
105
|
+
if (linenot3fields) reject({ error: linenot3fields + ' lines have other than 3 fields' })
|
|
106
|
+
|
|
107
|
+
if (fieldnotnumerical)
|
|
108
|
+
reject({ error: fieldnotnumerical + ' lines have non-numerical values in any of the 3 fields' })
|
|
109
|
+
|
|
110
|
+
resolve({ items })
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { fileurl, file_is_readable } from '#src/utils.js'
|
|
2
|
+
import { do_hicstat } from '#src/hicstat.ts'
|
|
3
|
+
import { HicstatRequestWithValidation } from '#shared/types/routes/hicstat.ts'
|
|
4
|
+
|
|
5
|
+
export const api = {
|
|
6
|
+
endpoint: 'hicstat',
|
|
7
|
+
methods: {
|
|
8
|
+
get: {
|
|
9
|
+
init,
|
|
10
|
+
request: {
|
|
11
|
+
typeId: 'HicstatRequest'
|
|
12
|
+
},
|
|
13
|
+
response: {
|
|
14
|
+
typeId: 'HicstatResponse'
|
|
15
|
+
},
|
|
16
|
+
examples: [
|
|
17
|
+
{
|
|
18
|
+
request: {
|
|
19
|
+
body: {
|
|
20
|
+
genome: 'hg19',
|
|
21
|
+
file: 'proteinpaint_demo/hg19/hic/hic_demo.hic',
|
|
22
|
+
embedder: 'localhost'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
response: {
|
|
26
|
+
header: { status: 200 }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
post: {
|
|
32
|
+
alternativeFor: 'get',
|
|
33
|
+
init
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function init() {
|
|
39
|
+
return async (req: HicstatRequestWithValidation, res: any): Promise<void> => {
|
|
40
|
+
try {
|
|
41
|
+
const [e, file, isurl] = fileurl(req)
|
|
42
|
+
if (e) throw 'illegal file name'
|
|
43
|
+
if (!isurl) {
|
|
44
|
+
await file_is_readable(file)
|
|
45
|
+
}
|
|
46
|
+
const out = await do_hicstat(file, isurl)
|
|
47
|
+
res.send({ out })
|
|
48
|
+
} catch (e: any) {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
res.send({ error: e?.message || e })
|
|
52
|
+
if (e instanceof Error && e.stack) console.log(e)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -87,7 +87,13 @@ function init({ genomes }) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
async function trigger_getcategories(
|
|
90
|
+
async function trigger_getcategories(
|
|
91
|
+
q: { tid: string | number; type: string; filter: any; term1_q: any; currentGeneNames: any },
|
|
92
|
+
res: { send: (arg0: { lst: any[]; orderedLabels: any }) => void },
|
|
93
|
+
tdb: { q: { termjsonByOneid: (arg0: any) => any } },
|
|
94
|
+
ds: { assayAvailability: { byDt: { [s: string]: any } | ArrayLike<any> } },
|
|
95
|
+
genome: any
|
|
96
|
+
) {
|
|
91
97
|
// thin wrapper of get_summary
|
|
92
98
|
// works for all types of terms
|
|
93
99
|
if (!q.tid) throw '.tid missing'
|
|
@@ -105,9 +111,9 @@ async function trigger_getcategories(q, res, tdb, ds, genome) {
|
|
|
105
111
|
const data = await getData(arg, ds, genome)
|
|
106
112
|
if (data.error) throw data.error
|
|
107
113
|
|
|
108
|
-
const lst = []
|
|
114
|
+
const lst = [] as any[]
|
|
109
115
|
if (q.type == 'geneVariant') {
|
|
110
|
-
const samples = data.samples
|
|
116
|
+
const samples = data.samples as { [sampleId: string]: any }
|
|
111
117
|
const dtClassMap = new Map()
|
|
112
118
|
if (ds.assayAvailability?.byDt) {
|
|
113
119
|
for (const [dtType, dtValue] of Object.entries(ds.assayAvailability.byDt)) {
|
|
@@ -116,7 +122,7 @@ async function trigger_getcategories(q, res, tdb, ds, genome) {
|
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
|
-
const sampleCountedFor = new Set() // if the sample is
|
|
125
|
+
const sampleCountedFor = new Set() // if the sample is counted
|
|
120
126
|
for (const [sampleId, sampleData] of Object.entries(samples)) {
|
|
121
127
|
const values = sampleData[q.tid].values
|
|
122
128
|
sampleCountedFor.clear()
|
|
@@ -175,7 +181,9 @@ async function trigger_getcategories(q, res, tdb, ds, genome) {
|
|
|
175
181
|
samplecount: count,
|
|
176
182
|
key,
|
|
177
183
|
label:
|
|
178
|
-
data.refs?.byTermId?.[q.tid]?.events?.find(e => e.event === key).label ||
|
|
184
|
+
data.refs?.byTermId?.[q.tid]?.events?.find((e: { event: any }) => e.event === key).label ||
|
|
185
|
+
term?.values?.[key]?.label ||
|
|
186
|
+
key
|
|
179
187
|
})
|
|
180
188
|
}
|
|
181
189
|
}
|
|
@@ -195,7 +203,23 @@ async function trigger_getcategories(q, res, tdb, ds, genome) {
|
|
|
195
203
|
})
|
|
196
204
|
}
|
|
197
205
|
|
|
198
|
-
function getDefaultQ(
|
|
206
|
+
function getDefaultQ(
|
|
207
|
+
term: { type: string; bins: { default: any } },
|
|
208
|
+
q: {
|
|
209
|
+
mode?: any
|
|
210
|
+
breaks?: any
|
|
211
|
+
bar_by_grade?: any
|
|
212
|
+
bar_by_children?: any
|
|
213
|
+
value_by_max_grade?: any
|
|
214
|
+
value_by_most_recent?: any
|
|
215
|
+
value_by_computable_grade?: any
|
|
216
|
+
tid?: string | number
|
|
217
|
+
type?: string
|
|
218
|
+
filter?: any
|
|
219
|
+
term1_q?: any
|
|
220
|
+
currentGeneNames?: any
|
|
221
|
+
}
|
|
222
|
+
) {
|
|
199
223
|
if (term.type == 'categorical') return {}
|
|
200
224
|
if (term.type == 'survival') return {}
|
|
201
225
|
if (term.type == 'integer' || term.type == 'float') return term.bins.default
|