@sjcrh/proteinpaint-server 2.116.1-2 → 2.118.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/dataset/termdb.test.js +2 -1
- package/package.json +5 -4
- package/routes/brainImaging.js +15 -26
- package/routes/brainImagingSamples.js +1 -55
- package/routes/genesetEnrichment.js +2 -56
- package/routes/termdb.cluster.js +32 -16
- package/routes/termdb.config.js +1 -0
- package/routes/termdb.topVariablyExpressedGenes.js +3 -2
- package/src/app.js +542 -619
- package/src/serverconfig.js +1 -1
- package/utils/gsea.py +0 -145
- package/utils/plotBrainImaging.py +0 -112
package/dataset/termdb.test.js
CHANGED
|
@@ -194,7 +194,8 @@ var termdb_test_default = {
|
|
|
194
194
|
},
|
|
195
195
|
geneExpression: {
|
|
196
196
|
src: "native",
|
|
197
|
-
|
|
197
|
+
hdf5File: true,
|
|
198
|
+
file: "files/hg38/TermdbTest/TermdbTest.fpkm.matrix.h5"
|
|
198
199
|
},
|
|
199
200
|
topVariablyExpressedGenes: {
|
|
200
201
|
src: "native"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.118.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
|
|
6
6
|
"main": "src/app.js",
|
|
@@ -66,9 +66,10 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@sjcrh/augen": "2.116.0",
|
|
69
|
-
"@sjcrh/proteinpaint-
|
|
70
|
-
"@sjcrh/proteinpaint-
|
|
71
|
-
"@sjcrh/proteinpaint-
|
|
69
|
+
"@sjcrh/proteinpaint-python": "2.118.0",
|
|
70
|
+
"@sjcrh/proteinpaint-rust": "2.117.0",
|
|
71
|
+
"@sjcrh/proteinpaint-shared": "2.118.0",
|
|
72
|
+
"@sjcrh/proteinpaint-types": "2.117.0",
|
|
72
73
|
"@types/express": "^5.0.0",
|
|
73
74
|
"@types/express-session": "^1.18.1",
|
|
74
75
|
"better-sqlite3": "^9.4.1",
|
package/routes/brainImaging.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import serverconfig from "#src/serverconfig.js";
|
|
3
3
|
import { brainImagingPayload } from "#types/checkers";
|
|
4
|
-
import { spawn } from "child_process";
|
|
5
4
|
import { getData } from "../src/termdb.matrix.js";
|
|
6
5
|
import { isNumericTerm } from "@sjcrh/proteinpaint-shared/terms.js";
|
|
7
6
|
import { getColors } from "#shared/common.js";
|
|
7
|
+
import { run_python } from "@sjcrh/proteinpaint-python";
|
|
8
8
|
const api = {
|
|
9
9
|
endpoint: "brainImaging",
|
|
10
10
|
methods: {
|
|
@@ -124,7 +124,20 @@ async function getBrainImage(query, genomes, plane, index) {
|
|
|
124
124
|
if (!legend[category])
|
|
125
125
|
legend[category] = { color: filesByCat[category].color, maxLength };
|
|
126
126
|
}
|
|
127
|
-
const
|
|
127
|
+
const arg = {
|
|
128
|
+
refFile,
|
|
129
|
+
plane,
|
|
130
|
+
index,
|
|
131
|
+
maxLength,
|
|
132
|
+
filesByCat: JSON.stringify(filesByCat)
|
|
133
|
+
};
|
|
134
|
+
let url;
|
|
135
|
+
try {
|
|
136
|
+
url = await run_python("plotBrainImaging.py", JSON.stringify(arg));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
const errmsg = "Error running Python script:" + error;
|
|
139
|
+
throw new Error(errmsg);
|
|
140
|
+
}
|
|
128
141
|
brainImageDict[dcategory] = { url, catNum };
|
|
129
142
|
}
|
|
130
143
|
if (query.legendFilter) {
|
|
@@ -141,30 +154,6 @@ async function getBrainImage(query, genomes, plane, index) {
|
|
|
141
154
|
throw "no reference or sample files";
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
|
-
async function generateBrainImage(refFile, plane, index, maxLength, filesJson) {
|
|
145
|
-
return new Promise((resolve, reject) => {
|
|
146
|
-
const cmd = [`${serverconfig.binpath}/utils/plotBrainImaging.py`, refFile, plane, index, maxLength, filesJson];
|
|
147
|
-
const ps = spawn(serverconfig.python, cmd);
|
|
148
|
-
const imgData = [];
|
|
149
|
-
ps.stdout.on("data", (data) => {
|
|
150
|
-
imgData.push(data);
|
|
151
|
-
});
|
|
152
|
-
ps.stderr.on("data", (data) => {
|
|
153
|
-
console.error(`stderr: ${data}`);
|
|
154
|
-
reject(new Error(`Python script filed: ${data}`));
|
|
155
|
-
});
|
|
156
|
-
ps.on("close", (code) => {
|
|
157
|
-
if (code === 0) {
|
|
158
|
-
const imageBuffer = Buffer.concat(imgData);
|
|
159
|
-
const base64Data = imageBuffer.toString("base64");
|
|
160
|
-
const imgUrl = `data:image/png;base64,${base64Data}`;
|
|
161
|
-
resolve(imgUrl);
|
|
162
|
-
} else {
|
|
163
|
-
reject(new Error(`Python script exited with code ${code}`));
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
157
|
export {
|
|
169
158
|
api
|
|
170
159
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import serverconfig from "#src/serverconfig.js";
|
|
4
|
-
import { spawn } from "child_process";
|
|
5
4
|
const api = {
|
|
6
5
|
endpoint: "brainImagingSamples",
|
|
7
6
|
methods: {
|
|
@@ -62,59 +61,6 @@ async function getBrainImageSamples(query, genomes) {
|
|
|
62
61
|
throw "no reference or sample files";
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
|
-
async function validate_query_NIdata(ds) {
|
|
66
|
-
const q = ds.queries.NIdata;
|
|
67
|
-
if (!q || !serverconfig.features?.showBrainImaging)
|
|
68
|
-
return;
|
|
69
|
-
for (const key in q) {
|
|
70
|
-
if (q[key].referenceFile && q[key].samples) {
|
|
71
|
-
q[key].get = async (sampleName, l, f, t) => {
|
|
72
|
-
const refFile = path.join(serverconfig.tpmasterdir, q[key].referenceFile);
|
|
73
|
-
const sampleFile = path.join(serverconfig.tpmasterdir, q[key].samples, sampleName);
|
|
74
|
-
try {
|
|
75
|
-
await fs.promises.stat(sampleFile);
|
|
76
|
-
} catch (e) {
|
|
77
|
-
if (e.code == "EACCES")
|
|
78
|
-
throw "cannot read file, permission denied";
|
|
79
|
-
if (e.code == "ENOENT")
|
|
80
|
-
throw "no data for this sample";
|
|
81
|
-
throw "failed to load data";
|
|
82
|
-
}
|
|
83
|
-
return new Promise((resolve, reject) => {
|
|
84
|
-
const ps = spawn(serverconfig.python, [
|
|
85
|
-
`${serverconfig.binpath}/utils/plotBrainImaging.py`,
|
|
86
|
-
refFile,
|
|
87
|
-
sampleFile,
|
|
88
|
-
l,
|
|
89
|
-
f,
|
|
90
|
-
t
|
|
91
|
-
]);
|
|
92
|
-
const imgData = [];
|
|
93
|
-
ps.stdout.on("data", (data) => {
|
|
94
|
-
imgData.push(data);
|
|
95
|
-
});
|
|
96
|
-
ps.stderr.on("data", (data) => {
|
|
97
|
-
console.error(`stderr: ${data}`);
|
|
98
|
-
reject(new Error(`Python script filed: ${data}`));
|
|
99
|
-
});
|
|
100
|
-
ps.on("close", (code) => {
|
|
101
|
-
if (code === 0) {
|
|
102
|
-
const imageBuffer = Buffer.concat(imgData);
|
|
103
|
-
const base64Data = imageBuffer.toString("base64");
|
|
104
|
-
const imgUrl = `data:image/png;base64,${base64Data}`;
|
|
105
|
-
resolve(imgUrl);
|
|
106
|
-
} else {
|
|
107
|
-
reject(new Error(`Python script exited with code ${code}`));
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
} else {
|
|
113
|
-
throw "no reference or sample files";
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
64
|
export {
|
|
118
|
-
api
|
|
119
|
-
validate_query_NIdata
|
|
65
|
+
api
|
|
120
66
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { genesetEnrichmentPayload } from "#types/checkers";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { spawn } from "child_process";
|
|
5
|
-
import { Readable } from "stream";
|
|
6
4
|
import serverconfig from "#src/serverconfig.js";
|
|
5
|
+
import { run_python } from "@sjcrh/proteinpaint-python";
|
|
7
6
|
const api = {
|
|
8
7
|
endpoint: "genesetEnrichment",
|
|
9
8
|
methods: {
|
|
@@ -60,11 +59,7 @@ async function run_genesetEnrichment_analysis(q, genomes) {
|
|
|
60
59
|
genedb: path.join(serverconfig.tpmasterdir, genomes[q.genome].genedb.dbfile),
|
|
61
60
|
filter_non_coding_genes: q.filter_non_coding_genes
|
|
62
61
|
};
|
|
63
|
-
const gsea_output = await
|
|
64
|
-
`${serverconfig.binpath}/utils/gsea.py`,
|
|
65
|
-
"/" + JSON.stringify(genesetenrichment_input)
|
|
66
|
-
// "/" is needed for python to accept the bracket "{" as a bracket
|
|
67
|
-
);
|
|
62
|
+
const gsea_output = await run_python("gsea.py", "/" + JSON.stringify(genesetenrichment_input));
|
|
68
63
|
let result;
|
|
69
64
|
let data_found = false;
|
|
70
65
|
let image_found = false;
|
|
@@ -88,55 +83,6 @@ async function run_genesetEnrichment_analysis(q, genomes) {
|
|
|
88
83
|
throw ``;
|
|
89
84
|
}
|
|
90
85
|
}
|
|
91
|
-
async function run_gsea(path2, data) {
|
|
92
|
-
try {
|
|
93
|
-
await fs.promises.stat(path2);
|
|
94
|
-
} catch (_) {
|
|
95
|
-
throw `${path2} does not exist`;
|
|
96
|
-
}
|
|
97
|
-
return new Promise((resolve, reject) => {
|
|
98
|
-
const _stdout = [];
|
|
99
|
-
const _stderr = [];
|
|
100
|
-
const sp = spawn(serverconfig.python, [path2]);
|
|
101
|
-
if (data) {
|
|
102
|
-
try {
|
|
103
|
-
const input = data.endsWith("\n") ? data : data + "\n";
|
|
104
|
-
Readable.from(input).pipe(sp.stdin);
|
|
105
|
-
} catch (e) {
|
|
106
|
-
sp.kill();
|
|
107
|
-
let errmsg = e;
|
|
108
|
-
const stderr = _stderr.join("").trim();
|
|
109
|
-
if (stderr)
|
|
110
|
-
errmsg += `
|
|
111
|
-
python stderr: ${stderr}`;
|
|
112
|
-
reject(errmsg);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
sp.stdout.on("data", (data2) => _stdout.push(data2));
|
|
116
|
-
sp.stderr.on("data", (data2) => _stderr.push(data2));
|
|
117
|
-
sp.on("error", (err) => reject(err));
|
|
118
|
-
sp.on("close", (code) => {
|
|
119
|
-
const stdout = _stdout.join("").trim();
|
|
120
|
-
const stderr = _stderr.join("").trim();
|
|
121
|
-
if (code !== 0) {
|
|
122
|
-
let errmsg = `python process exited with non-zero status code=${code}`;
|
|
123
|
-
if (stdout)
|
|
124
|
-
errmsg += `
|
|
125
|
-
python stdout: ${stdout}`;
|
|
126
|
-
if (stderr)
|
|
127
|
-
errmsg += `
|
|
128
|
-
python stderr: ${stderr}`;
|
|
129
|
-
reject(errmsg);
|
|
130
|
-
}
|
|
131
|
-
if (stderr) {
|
|
132
|
-
const errmsg = `python process emitted standard error
|
|
133
|
-
python stderr: ${stderr}`;
|
|
134
|
-
reject(errmsg);
|
|
135
|
-
}
|
|
136
|
-
resolve(stdout);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
86
|
export {
|
|
141
87
|
api
|
|
142
88
|
};
|
package/routes/termdb.cluster.js
CHANGED
|
@@ -12,6 +12,7 @@ import { TermTypes, NUMERIC_DICTIONARY_TERM } from "#shared/terms.js";
|
|
|
12
12
|
import { getData } from "#src/termdb.matrix.js";
|
|
13
13
|
import { termType2label } from "#shared/terms.js";
|
|
14
14
|
import { mayLog } from "#src/helpers.ts";
|
|
15
|
+
import { formatElapsedTime } from "#shared/time.js";
|
|
15
16
|
const api = {
|
|
16
17
|
endpoint: "termdb/cluster",
|
|
17
18
|
methods: {
|
|
@@ -102,7 +103,7 @@ async function getResult(q, ds, genome) {
|
|
|
102
103
|
}
|
|
103
104
|
const t = Date.now();
|
|
104
105
|
const clustering = await doClustering(term2sample2value, q, Object.keys(bySampleId).length);
|
|
105
|
-
mayLog("clustering done:", Date.now() - t
|
|
106
|
+
mayLog("clustering done:", formatElapsedTime(Date.now() - t));
|
|
106
107
|
const result = { clustering, byTermId, bySampleId };
|
|
107
108
|
if (removedHierClusterTerms.length)
|
|
108
109
|
result.removedHierClusterTerms = removedHierClusterTerms;
|
|
@@ -239,19 +240,19 @@ async function validateHDF5File(filePath) {
|
|
|
239
240
|
};
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
|
-
async function queryGeneExpression(hdf5_file,
|
|
243
|
+
async function queryGeneExpression(hdf5_file, geneNames) {
|
|
243
244
|
const jsonInput = JSON.stringify({
|
|
244
245
|
hdf5_file,
|
|
245
|
-
gene:
|
|
246
|
+
gene: geneNames
|
|
246
247
|
});
|
|
247
248
|
try {
|
|
248
249
|
const result = await run_rust("readHDF5", jsonInput);
|
|
249
|
-
if (!result ||
|
|
250
|
+
if (!result || result.length === 0) {
|
|
250
251
|
throw new Error("Failed to retrieve expression data: Empty or missing response");
|
|
251
252
|
}
|
|
252
253
|
return result;
|
|
253
254
|
} catch (error) {
|
|
254
|
-
console.error(`Error querying gene expression for ${
|
|
255
|
+
console.error(`Error querying gene expression for ${geneNames}`);
|
|
255
256
|
throw error;
|
|
256
257
|
}
|
|
257
258
|
}
|
|
@@ -294,13 +295,30 @@ async function validateNative(q, ds, genome) {
|
|
|
294
295
|
}
|
|
295
296
|
const term2sample2value = /* @__PURE__ */ new Map();
|
|
296
297
|
const byTermId = {};
|
|
298
|
+
const geneNames = [];
|
|
297
299
|
for (const geneTerm of param.terms) {
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
300
|
+
if (geneTerm.gene) {
|
|
301
|
+
geneNames.push(geneTerm.gene);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (geneNames.length === 0) {
|
|
305
|
+
console.log("No genes to query");
|
|
306
|
+
return { term2sample2value, byTermId };
|
|
307
|
+
}
|
|
308
|
+
const time1 = Date.now();
|
|
309
|
+
try {
|
|
310
|
+
const geneData = JSON.parse(await queryGeneExpression(q.file, geneNames));
|
|
311
|
+
mayLog("Time taken to run gene query:", formatElapsedTime(Date.now() - time1));
|
|
312
|
+
const genesData = geneData.genes || { [geneNames[0]]: geneData };
|
|
313
|
+
for (const geneTerm of param.terms) {
|
|
314
|
+
if (!geneTerm.gene)
|
|
315
|
+
continue;
|
|
316
|
+
const geneResult = genesData[geneTerm.gene];
|
|
317
|
+
if (!geneResult) {
|
|
318
|
+
console.warn(`No data found for gene ${geneTerm.gene} in the response`);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const samplesData = geneResult.samples || {};
|
|
304
322
|
const s2v = {};
|
|
305
323
|
for (const [sampleName, value] of Object.entries(samplesData)) {
|
|
306
324
|
const sampleId = ds.cohort.termdb.q.sampleName2id(sampleName);
|
|
@@ -308,16 +326,14 @@ async function validateNative(q, ds, genome) {
|
|
|
308
326
|
continue;
|
|
309
327
|
if (limitSamples && !limitSamples.has(sampleId))
|
|
310
328
|
continue;
|
|
311
|
-
s2v[sampleId] =
|
|
329
|
+
s2v[sampleId] = value;
|
|
312
330
|
}
|
|
313
|
-
console.log(`Gene ${geneTerm.gene} has ${Object.keys(s2v).length} samples with data`);
|
|
314
331
|
if (Object.keys(s2v).length) {
|
|
315
332
|
term2sample2value.set(geneTerm.gene, s2v);
|
|
316
333
|
}
|
|
317
|
-
} catch (error) {
|
|
318
|
-
console.warn(`Error processing gene ${geneTerm.gene}:`, error);
|
|
319
|
-
continue;
|
|
320
334
|
}
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error(`Error processing batch gene query:`, error);
|
|
321
337
|
}
|
|
322
338
|
if (term2sample2value.size == 0) {
|
|
323
339
|
throw "No data available for the input " + param.terms?.map((g) => g.gene).join(", ");
|
package/routes/termdb.config.js
CHANGED
|
@@ -41,6 +41,7 @@ function make(q, req, res, ds, genome) {
|
|
|
41
41
|
const c = {
|
|
42
42
|
selectCohort: getSelectCohort(ds, req),
|
|
43
43
|
supportedChartTypes: tdb.q?.getSupportedChartTypes(req),
|
|
44
|
+
hiddenTermIds: tdb.hiddenTermIds,
|
|
44
45
|
renamedChartTypes: ds.cohort.renamedChartTypes,
|
|
45
46
|
allowedTermTypes: getAllowedTermTypes(ds),
|
|
46
47
|
termMatch2geneSet: tdb.termMatch2geneSet,
|
|
@@ -5,6 +5,8 @@ import { get_samples } from "#src/termdb.sql.js";
|
|
|
5
5
|
import { makeFilter } from "#src/mds3.gdc.js";
|
|
6
6
|
import { cachedFetch } from "#src/utils.js";
|
|
7
7
|
import { joinUrl } from "#shared/joinUrl.js";
|
|
8
|
+
import { formatElapsedTime } from "#shared/time.js";
|
|
9
|
+
import { mayLog } from "#src/helpers.ts";
|
|
8
10
|
const api = {
|
|
9
11
|
endpoint: "termdb/topVariablyExpressedGenes",
|
|
10
12
|
methods: {
|
|
@@ -35,8 +37,7 @@ function init({ genomes }) {
|
|
|
35
37
|
result = {
|
|
36
38
|
genes: await ds.queries.topVariablyExpressedGenes.getGenes(q)
|
|
37
39
|
};
|
|
38
|
-
|
|
39
|
-
console.log("topVariablyExpressedGenes", Date.now() - t, "ms");
|
|
40
|
+
mayLog("topVariablyExpressedGenes", formatElapsedTime(Date.now() - t));
|
|
40
41
|
} catch (e) {
|
|
41
42
|
result = { status: e.status || 400, error: e.message || e };
|
|
42
43
|
}
|