@sjcrh/proteinpaint-server 2.132.0 → 2.132.1-1
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/package.json +4 -4
- package/routes/gdc.grin2.run.js +35 -19
- package/routes/profileFilters.js +68 -0
- package/routes/profileFormScores.js +117 -0
- package/routes/profileScores.js +101 -0
- package/routes/termdb.categories.js +1 -0
- package/src/app.js +432 -200
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.132.
|
|
3
|
+
"version": "2.132.1-1",
|
|
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",
|
|
@@ -61,11 +61,11 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@sjcrh/augen": "2.121.0",
|
|
64
|
-
"@sjcrh/proteinpaint-python": "2.
|
|
64
|
+
"@sjcrh/proteinpaint-python": "2.132.1-1",
|
|
65
65
|
"@sjcrh/proteinpaint-r": "2.130.0",
|
|
66
|
-
"@sjcrh/proteinpaint-rust": "2.132.0",
|
|
66
|
+
"@sjcrh/proteinpaint-rust": "2.132.1-0",
|
|
67
67
|
"@sjcrh/proteinpaint-shared": "2.132.0",
|
|
68
|
-
"@sjcrh/proteinpaint-types": "2.132.
|
|
68
|
+
"@sjcrh/proteinpaint-types": "2.132.1-1",
|
|
69
69
|
"@types/express": "^5.0.0",
|
|
70
70
|
"@types/express-session": "^1.18.1",
|
|
71
71
|
"better-sqlite3": "^9.4.1",
|
package/routes/gdc.grin2.run.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { runGRIN2Payload } from "#types/checkers";
|
|
2
2
|
import { stream_rust } from "@sjcrh/proteinpaint-rust";
|
|
3
|
-
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
4
3
|
import serverconfig from "#src/serverconfig.js";
|
|
5
4
|
import path from "path";
|
|
6
5
|
import { formatElapsedTime } from "@sjcrh/proteinpaint-shared/time.js";
|
|
6
|
+
import { run_python } from "@sjcrh/proteinpaint-python";
|
|
7
7
|
const api = {
|
|
8
8
|
endpoint: "gdc/runGRIN2",
|
|
9
9
|
methods: {
|
|
@@ -127,53 +127,69 @@ function init({ genomes }) {
|
|
|
127
127
|
throw new Error("Failed to process MAF files: No result from Rust");
|
|
128
128
|
}
|
|
129
129
|
const parsedRustResult = rustResult;
|
|
130
|
-
let
|
|
130
|
+
let dataForPython = [];
|
|
131
131
|
try {
|
|
132
132
|
if (parsedRustResult.successful_data && Array.isArray(parsedRustResult.successful_data)) {
|
|
133
|
-
|
|
134
|
-
console.log(`[GRIN2] Extracted ${
|
|
133
|
+
dataForPython = parsedRustResult.successful_data.flat();
|
|
134
|
+
console.log(`[GRIN2] Extracted ${dataForPython.length} records for python script`);
|
|
135
135
|
console.log(
|
|
136
136
|
`[GRIN2] Success: ${parsedRustResult.summary.successful_files}, Failed: ${parsedRustResult.summary.failed_files}`
|
|
137
137
|
);
|
|
138
138
|
} else {
|
|
139
139
|
console.warn("[GRIN2] Unexpected Rust result format");
|
|
140
|
-
|
|
140
|
+
dataForPython = [];
|
|
141
141
|
}
|
|
142
142
|
} catch (parseError) {
|
|
143
143
|
console.error("[GRIN2] Error processing Rust result:", parseError);
|
|
144
|
-
|
|
144
|
+
dataForPython = [];
|
|
145
145
|
}
|
|
146
146
|
const genedbfile = path.join(serverconfig.tpmasterdir, g.genedb.dbfile);
|
|
147
|
-
const
|
|
148
|
-
const rInput = JSON.stringify({
|
|
147
|
+
const pyInput = JSON.stringify({
|
|
149
148
|
genedb: genedbfile,
|
|
150
149
|
chromosomelist: g.majorchr,
|
|
151
|
-
|
|
152
|
-
lesion: dataForR
|
|
150
|
+
lesion: dataForPython
|
|
153
151
|
// The mutation string from Rust
|
|
154
152
|
});
|
|
155
|
-
console.log("[GRIN2] Executing
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
console.log("[GRIN2] Executing python script...");
|
|
154
|
+
const grin2AnalysisStart = Date.now();
|
|
155
|
+
let pyResult;
|
|
156
|
+
try {
|
|
157
|
+
pyResult = await run_python("gdcGRIN2.py", pyInput);
|
|
158
|
+
} catch (pyError) {
|
|
159
|
+
console.error("[GRIN2] Python execution failed:", pyError);
|
|
160
|
+
if (pyError && typeof pyError === "object" && "message" in pyError) {
|
|
161
|
+
throw new Error(`Python script failed: ${pyError.message}`);
|
|
162
|
+
} else {
|
|
163
|
+
throw new Error(`Python script failed: ${String(pyError)}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
console.log("[GRIN2] python execution completed");
|
|
167
|
+
console.log(`[GRIN2] Python stderr: ${pyResult.stderr}`);
|
|
168
|
+
const grin2AnalysisTime = formatElapsedTime(Date.now() - grin2AnalysisStart);
|
|
169
|
+
console.log(`[GRIN2] Rust processing took ${grin2AnalysisTime}`);
|
|
160
170
|
let resultData;
|
|
161
171
|
try {
|
|
162
|
-
resultData = JSON.parse(
|
|
163
|
-
console.log("[GRIN2] Finished
|
|
172
|
+
resultData = JSON.parse(pyResult);
|
|
173
|
+
console.log("[GRIN2] Finished python analysis");
|
|
164
174
|
const pngImg = resultData.png[0];
|
|
165
175
|
const topGeneTable = resultData.topGeneTable || null;
|
|
166
176
|
const analysisStats = parsedRustResult.summary || {};
|
|
167
|
-
|
|
177
|
+
const totalProcessTime = formatElapsedTime(Date.now() - downloadStartTime);
|
|
178
|
+
console.log("[GRIN2] Total GRIN2 processing time:", totalProcessTime);
|
|
168
179
|
return res.json({
|
|
169
180
|
pngImg,
|
|
170
181
|
topGeneTable,
|
|
171
182
|
rustResult: parsedRustResult,
|
|
172
183
|
analysisStats,
|
|
184
|
+
timing: {
|
|
185
|
+
rustProcessingTime: downloadTime,
|
|
186
|
+
grin2ProcessingTime: grin2AnalysisTime,
|
|
187
|
+
totalTime: totalProcessTime
|
|
188
|
+
},
|
|
173
189
|
status: "success"
|
|
174
190
|
});
|
|
175
191
|
} catch (parseError) {
|
|
176
|
-
console.error("[GRIN2] Error parsing
|
|
192
|
+
console.error("[GRIN2] Error parsing python result:", parseError);
|
|
177
193
|
}
|
|
178
194
|
} catch (e) {
|
|
179
195
|
console.error("[GRIN2] Error running analysis:", e);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ProfileFiltersPayload } from "#types/checkers";
|
|
2
|
+
import { getData, getSamplesPerFilter } from "../src/termdb.matrix.js";
|
|
3
|
+
const api = {
|
|
4
|
+
endpoint: "profileFilters",
|
|
5
|
+
methods: {
|
|
6
|
+
get: {
|
|
7
|
+
...ProfileFiltersPayload,
|
|
8
|
+
init
|
|
9
|
+
},
|
|
10
|
+
post: {
|
|
11
|
+
...ProfileFiltersPayload,
|
|
12
|
+
init
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function init({ genomes }) {
|
|
17
|
+
return async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const g = genomes[req.query.genome];
|
|
20
|
+
if (!g)
|
|
21
|
+
throw "invalid genome name";
|
|
22
|
+
const ds = g.datasets?.[req.query.dslabel];
|
|
23
|
+
getFilters(req.query, ds, g, res);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.log(e);
|
|
26
|
+
res.send({ status: "error", error: e.message || e });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function getList(samplesPerFilter, filtersData, tw) {
|
|
31
|
+
const values = Object.values(tw.term.values);
|
|
32
|
+
values.sort((v1, v2) => v1.label.localeCompare(v2.label));
|
|
33
|
+
const twSamples = samplesPerFilter[tw.term.id];
|
|
34
|
+
const data = [];
|
|
35
|
+
for (const sample of twSamples) {
|
|
36
|
+
data.push(filtersData.samples[sample]);
|
|
37
|
+
}
|
|
38
|
+
const sampleValues = Array.from(new Set(data.map((sample) => sample[tw.$id]?.value)));
|
|
39
|
+
for (const value of values) {
|
|
40
|
+
value.value = value.label;
|
|
41
|
+
value.disabled = !sampleValues.includes(value.label);
|
|
42
|
+
}
|
|
43
|
+
values.unshift({ label: "", value: "" });
|
|
44
|
+
return values;
|
|
45
|
+
}
|
|
46
|
+
async function getFilters(query, ds, genome, res) {
|
|
47
|
+
try {
|
|
48
|
+
const samplesPerFilter = await getSamplesPerFilter(query, ds);
|
|
49
|
+
const filtersData = await getData(
|
|
50
|
+
{
|
|
51
|
+
terms: query.terms
|
|
52
|
+
},
|
|
53
|
+
ds,
|
|
54
|
+
genome
|
|
55
|
+
);
|
|
56
|
+
const tw2List = {};
|
|
57
|
+
for (const tw of query.terms) {
|
|
58
|
+
tw2List[tw.term.id] = getList(samplesPerFilter, filtersData, tw);
|
|
59
|
+
}
|
|
60
|
+
res.send({ ...tw2List });
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.log(e);
|
|
63
|
+
res.send({ error: e.message || e });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
api
|
|
68
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ProfileFormScoresPayload } from "#types/checkers";
|
|
2
|
+
import { getData } from "../src/termdb.matrix.js";
|
|
3
|
+
const api = {
|
|
4
|
+
endpoint: "profileFormScores",
|
|
5
|
+
methods: {
|
|
6
|
+
get: {
|
|
7
|
+
...ProfileFormScoresPayload,
|
|
8
|
+
init
|
|
9
|
+
},
|
|
10
|
+
post: {
|
|
11
|
+
...ProfileFormScoresPayload,
|
|
12
|
+
init
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function init({ genomes }) {
|
|
17
|
+
return async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const g = genomes[req.query.genome];
|
|
20
|
+
if (!g)
|
|
21
|
+
throw "invalid genome name";
|
|
22
|
+
const ds = g.datasets?.[req.query.dslabel];
|
|
23
|
+
const result = await getScoresDict(req.query, ds, g);
|
|
24
|
+
res.send(result);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.log(e);
|
|
27
|
+
res.send({ status: "error", error: e.message || e });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function getScoresDict(query, ds, genome) {
|
|
32
|
+
const terms = [...query.scoreTerms];
|
|
33
|
+
if (query.scScoreTerms)
|
|
34
|
+
terms.push(...query.scScoreTerms);
|
|
35
|
+
const data = await getData(
|
|
36
|
+
{
|
|
37
|
+
terms,
|
|
38
|
+
filter: query.filter
|
|
39
|
+
//if isRadarFacility and site is specified, do not apply the filter
|
|
40
|
+
},
|
|
41
|
+
ds,
|
|
42
|
+
genome
|
|
43
|
+
);
|
|
44
|
+
const lst = Object.values(data.samples);
|
|
45
|
+
let sites = lst.map((s) => {
|
|
46
|
+
return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
|
|
47
|
+
});
|
|
48
|
+
sites = lst.map((s) => {
|
|
49
|
+
return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
|
|
50
|
+
});
|
|
51
|
+
if (query.userSites) {
|
|
52
|
+
sites = sites.filter((s) => query.userSites.includes(s.label));
|
|
53
|
+
}
|
|
54
|
+
let userSite;
|
|
55
|
+
if (query.userSites) {
|
|
56
|
+
const siteName = query.userSites[0];
|
|
57
|
+
userSite = ds.sampleName2Id.get(siteName);
|
|
58
|
+
if (!userSite) {
|
|
59
|
+
throw `Invalid user site: ${siteName}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const site = query.isAggregate ? query.site : userSite;
|
|
63
|
+
const sampleData = data.samples[site] || null;
|
|
64
|
+
const samples = Object.values(data.samples);
|
|
65
|
+
const term2Score = {};
|
|
66
|
+
for (const d of query.scoreTerms) {
|
|
67
|
+
const samples2 = sampleData ? [sampleData] : Object.values(data.samples);
|
|
68
|
+
const getDictFunc = (sample) => getDict(d.$id, sample);
|
|
69
|
+
const percents = getPercentsDict(getDictFunc, samples2);
|
|
70
|
+
term2Score[d.term.id] = percents;
|
|
71
|
+
}
|
|
72
|
+
if (query.scScoreTerms)
|
|
73
|
+
for (const d of query.scScoreTerms) {
|
|
74
|
+
const samples2 = sampleData ? [sampleData] : Object.values(data.samples);
|
|
75
|
+
const percents = getSCPercentsDict(d, samples2);
|
|
76
|
+
term2Score[d.term.id] = percents;
|
|
77
|
+
}
|
|
78
|
+
const hospital = sampleData?.[query.facilityTW.$id]?.value;
|
|
79
|
+
return { term2Score, sites, hospital, n: samples.length };
|
|
80
|
+
}
|
|
81
|
+
function getDict(key, sample) {
|
|
82
|
+
if (!sample[key])
|
|
83
|
+
return null;
|
|
84
|
+
const termData = sample[key].value;
|
|
85
|
+
return JSON.parse(termData);
|
|
86
|
+
}
|
|
87
|
+
function getPercentsDict(getDictFunc, samples) {
|
|
88
|
+
const percentageDict = {};
|
|
89
|
+
for (const sample of samples) {
|
|
90
|
+
const percents = getDictFunc(sample);
|
|
91
|
+
if (!percents)
|
|
92
|
+
continue;
|
|
93
|
+
for (const key in percents) {
|
|
94
|
+
const value = percents[key];
|
|
95
|
+
if (!percentageDict[key])
|
|
96
|
+
percentageDict[key] = 0;
|
|
97
|
+
percentageDict[key] += value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return percentageDict;
|
|
101
|
+
}
|
|
102
|
+
function getSCPercentsDict(tw, samples) {
|
|
103
|
+
if (!tw)
|
|
104
|
+
throw "tw not defined";
|
|
105
|
+
const percentageDict = {};
|
|
106
|
+
for (const sample of samples) {
|
|
107
|
+
const twData = sample[tw.$id];
|
|
108
|
+
const key = twData?.value;
|
|
109
|
+
if (!percentageDict[key])
|
|
110
|
+
percentageDict[key] = 0;
|
|
111
|
+
percentageDict[key] += 1;
|
|
112
|
+
}
|
|
113
|
+
return percentageDict;
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
api
|
|
117
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ProfileScoresPayload } from "#types/checkers";
|
|
2
|
+
import { getData } from "../src/termdb.matrix.js";
|
|
3
|
+
const api = {
|
|
4
|
+
endpoint: "profileScores",
|
|
5
|
+
methods: {
|
|
6
|
+
get: {
|
|
7
|
+
...ProfileScoresPayload,
|
|
8
|
+
init
|
|
9
|
+
},
|
|
10
|
+
post: {
|
|
11
|
+
...ProfileScoresPayload,
|
|
12
|
+
init
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function init({ genomes }) {
|
|
17
|
+
return async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const g = genomes[req.query.genome];
|
|
20
|
+
if (!g)
|
|
21
|
+
throw "invalid genome name";
|
|
22
|
+
const ds = g.datasets?.[req.query.dslabel];
|
|
23
|
+
const result = await getScores(req.query, ds, g);
|
|
24
|
+
res.send(result);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.log(e);
|
|
27
|
+
res.send({ status: "error", error: e.message || e });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function getScores(query, ds, genome) {
|
|
32
|
+
const isRadarFacility = query.isRadarFacility;
|
|
33
|
+
const terms = [query.facilityTW];
|
|
34
|
+
for (const term of query.scoreTerms) {
|
|
35
|
+
terms.push(term.score);
|
|
36
|
+
if (term.maxScore) {
|
|
37
|
+
terms.push(term.maxScore);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const data = await getData(
|
|
41
|
+
{
|
|
42
|
+
terms,
|
|
43
|
+
filter: isRadarFacility && query.site ? void 0 : query.filter
|
|
44
|
+
//if isRadarFacility and site is specified, do not apply the filter
|
|
45
|
+
},
|
|
46
|
+
ds,
|
|
47
|
+
genome
|
|
48
|
+
);
|
|
49
|
+
const lst = Object.values(data.samples);
|
|
50
|
+
let sites = lst.map((s) => {
|
|
51
|
+
return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
|
|
52
|
+
});
|
|
53
|
+
sites = lst.map((s) => {
|
|
54
|
+
return { label: data.refs.bySampleId[s.sample].label, value: s.sample };
|
|
55
|
+
});
|
|
56
|
+
if (query.userSites) {
|
|
57
|
+
sites = sites.filter((s) => query.userSites.includes(s.label));
|
|
58
|
+
}
|
|
59
|
+
let userSite;
|
|
60
|
+
if (query.userSites) {
|
|
61
|
+
const siteName = query.userSites[0];
|
|
62
|
+
userSite = ds.sampleName2Id.get(siteName);
|
|
63
|
+
if (!userSite) {
|
|
64
|
+
throw `Invalid user site: ${siteName}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
let site;
|
|
68
|
+
if (isRadarFacility)
|
|
69
|
+
site = query.site;
|
|
70
|
+
else
|
|
71
|
+
site = query.isAggregate ? query.site : userSite;
|
|
72
|
+
const sampleData = data.samples[site] || null;
|
|
73
|
+
const samples = Object.values(data.samples);
|
|
74
|
+
const term2Score = {};
|
|
75
|
+
for (const d of query.scoreTerms) {
|
|
76
|
+
term2Score[d.score.term.id] = getPercentage(d, samples, sampleData);
|
|
77
|
+
}
|
|
78
|
+
const hospital = sampleData?.[query.facilityTW.$id]?.value;
|
|
79
|
+
return { term2Score, sites, hospital, n: samples.length };
|
|
80
|
+
}
|
|
81
|
+
function getPercentage(d, samples, sampleData) {
|
|
82
|
+
if (!d)
|
|
83
|
+
return null;
|
|
84
|
+
const isAggregate = sampleData == null;
|
|
85
|
+
if (isAggregate) {
|
|
86
|
+
const maxScore = d.maxScore.term ? samples[0]?.[d.maxScore.$id]?.value : d.maxScore;
|
|
87
|
+
const scores = samples.map((sample) => sample[d.score.$id]?.value / maxScore * 100);
|
|
88
|
+
scores.sort((s1, s2) => s1 - s2);
|
|
89
|
+
const middle = Math.floor(scores.length / 2);
|
|
90
|
+
const score = scores.length % 2 !== 0 ? scores[middle] : (scores[middle - 1] + scores[middle]) / 2;
|
|
91
|
+
return Math.round(score);
|
|
92
|
+
} else {
|
|
93
|
+
const score = sampleData[d.score.$id]?.value;
|
|
94
|
+
const maxScore = d.maxScore.term ? sampleData[d.maxScore.$id]?.value : d.maxScore;
|
|
95
|
+
const percentage = score / maxScore * 100;
|
|
96
|
+
return Math.round(percentage);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
api
|
|
101
|
+
};
|