@sjcrh/proteinpaint-server 2.138.3-5 → 2.139.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 +4 -3
- package/package.json +5 -5
- package/routes/grin2.js +122 -27
- package/routes/termdb.config.js +4 -2
- package/routes/termdb.filterTermValues.js +16 -20
- package/routes/termdb.percentile.js +19 -13
- package/src/app.js +341 -160
package/dataset/termdb.test.js
CHANGED
|
@@ -114,10 +114,11 @@ function termdb_test_default() {
|
|
|
114
114
|
name: "Demographics",
|
|
115
115
|
plots: [
|
|
116
116
|
{
|
|
117
|
-
chartType: "
|
|
118
|
-
settings: {
|
|
117
|
+
chartType: "violin",
|
|
118
|
+
settings: { violin: { showStats: false } },
|
|
119
119
|
term: {
|
|
120
|
-
id: "agedx"
|
|
120
|
+
id: "agedx",
|
|
121
|
+
q: { mode: "continuous" }
|
|
121
122
|
}
|
|
122
123
|
},
|
|
123
124
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.139.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",
|
|
@@ -60,11 +60,11 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@sjcrh/augen": "2.136.0",
|
|
63
|
-
"@sjcrh/proteinpaint-python": "2.
|
|
63
|
+
"@sjcrh/proteinpaint-python": "2.139.0",
|
|
64
64
|
"@sjcrh/proteinpaint-r": "2.137.2-0",
|
|
65
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
66
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
67
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
65
|
+
"@sjcrh/proteinpaint-rust": "2.138.3-7",
|
|
66
|
+
"@sjcrh/proteinpaint-shared": "2.138.3-7",
|
|
67
|
+
"@sjcrh/proteinpaint-types": "2.139.0",
|
|
68
68
|
"@types/express": "^5.0.0",
|
|
69
69
|
"@types/express-session": "^1.18.1",
|
|
70
70
|
"better-sqlite3": "^9.4.1",
|
package/routes/grin2.js
CHANGED
|
@@ -23,6 +23,7 @@ function init({ genomes }) {
|
|
|
23
23
|
return async (req, res) => {
|
|
24
24
|
try {
|
|
25
25
|
const request = req.query;
|
|
26
|
+
console.log("[GRIN2] request:", request);
|
|
26
27
|
const g = genomes[request.genome];
|
|
27
28
|
if (!g)
|
|
28
29
|
throw new Error("genome missing");
|
|
@@ -96,7 +97,7 @@ async function runGrin2(g, ds, request) {
|
|
|
96
97
|
const grin2AnalysisTime = Date.now() - grin2AnalysisStart;
|
|
97
98
|
const grin2AnalysisTimeToPrint = Math.round(grin2AnalysisTime / 1e3);
|
|
98
99
|
mayLog(`[GRIN2] Python processing took ${grin2AnalysisTimeToPrint} seconds`);
|
|
99
|
-
const resultData = JSON.parse(pyResult
|
|
100
|
+
const resultData = JSON.parse(pyResult);
|
|
100
101
|
if (!resultData?.png?.[0]) {
|
|
101
102
|
throw new Error("Invalid Python output: missing PNG data");
|
|
102
103
|
}
|
|
@@ -125,21 +126,60 @@ async function processSampleData(samples, ds, request) {
|
|
|
125
126
|
failedSamples: 0,
|
|
126
127
|
failedFiles: []
|
|
127
128
|
};
|
|
129
|
+
const dataTypeCounts = {
|
|
130
|
+
[dtsnvindel]: { samples: 0, entries: 0, processedLesions: 0 },
|
|
131
|
+
[dtcnv]: { samples: 0, entries: 0, processedLesions: 0 },
|
|
132
|
+
[dtfusionrna]: { samples: 0, entries: 0, processedLesions: 0 },
|
|
133
|
+
unknown: /* @__PURE__ */ new Map()
|
|
134
|
+
};
|
|
128
135
|
mayLog(`[GRIN2] Processing JSON files for ${samples.length.toLocaleString()} samples`);
|
|
129
136
|
for (const sample of samples) {
|
|
130
137
|
try {
|
|
131
138
|
const filepath = path.join(serverconfig.tpmasterdir, ds.queries.singleSampleMutation.folder, sample.name);
|
|
132
139
|
await file_is_readable(filepath);
|
|
133
140
|
const mlst = JSON.parse(await read_file(filepath));
|
|
134
|
-
const sampleLesions = await processSampleMlst(sample.name, mlst, lesionId, request);
|
|
141
|
+
const { sampleLesions, sampleDataTypeCounts } = await processSampleMlst(sample.name, mlst, lesionId, request);
|
|
135
142
|
lesions.push(...sampleLesions);
|
|
136
143
|
lesionId += sampleLesions.length;
|
|
144
|
+
for (const [dt, counts] of Object.entries(sampleDataTypeCounts.known)) {
|
|
145
|
+
if (counts.entries > 0) {
|
|
146
|
+
dataTypeCounts[dt].samples++;
|
|
147
|
+
dataTypeCounts[dt].entries += counts.entries;
|
|
148
|
+
dataTypeCounts[dt].processedLesions += counts.processedLesions;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const [dt, counts] of sampleDataTypeCounts.unknown) {
|
|
152
|
+
if (!dataTypeCounts.unknown.has(dt)) {
|
|
153
|
+
dataTypeCounts.unknown.set(dt, { samples: /* @__PURE__ */ new Set(), entries: 0 });
|
|
154
|
+
}
|
|
155
|
+
dataTypeCounts.unknown.get(dt).samples.add(sample.name);
|
|
156
|
+
dataTypeCounts.unknown.get(dt).entries += counts;
|
|
157
|
+
}
|
|
158
|
+
processingSummary.successfulSamples++;
|
|
137
159
|
} catch (error) {
|
|
160
|
+
processingSummary.failedSamples++;
|
|
161
|
+
processingSummary.failedFiles?.push(sample.name);
|
|
138
162
|
mayLog(
|
|
139
163
|
`[GRIN2] Error processing sample ${sample.name}: ${typeof error === "object" && error !== null && "message" in error ? error.message : String(error)}`
|
|
140
164
|
);
|
|
141
165
|
}
|
|
142
166
|
}
|
|
167
|
+
mayLog(`[GRIN2] === DATA TYPE ANALYSIS SUMMARY ===`);
|
|
168
|
+
mayLog(
|
|
169
|
+
`[GRIN2] SNV/Indel (dt=${dtsnvindel}): ${dataTypeCounts[dtsnvindel].samples} samples, ${dataTypeCounts[dtsnvindel].entries} entries, ${dataTypeCounts[dtsnvindel].processedLesions} lesions`
|
|
170
|
+
);
|
|
171
|
+
mayLog(
|
|
172
|
+
`[GRIN2] CNV (dt=${dtcnv}): ${dataTypeCounts[dtcnv].samples} samples, ${dataTypeCounts[dtcnv].entries} entries, ${dataTypeCounts[dtcnv].processedLesions} lesions`
|
|
173
|
+
);
|
|
174
|
+
mayLog(
|
|
175
|
+
`[GRIN2] Fusion (dt=${dtfusionrna}): ${dataTypeCounts[dtfusionrna].samples} samples, ${dataTypeCounts[dtfusionrna].entries} entries, ${dataTypeCounts[dtfusionrna].processedLesions} lesions`
|
|
176
|
+
);
|
|
177
|
+
if (dataTypeCounts.unknown.size > 0) {
|
|
178
|
+
mayLog(`[GRIN2] Unknown data types:`);
|
|
179
|
+
for (const [dt, counts] of dataTypeCounts.unknown) {
|
|
180
|
+
mayLog(`[GRIN2] dt="${dt}": ${counts.samples.size} samples, ${counts.entries} entries`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
143
183
|
mayLog(`[GRIN2] Total lesions processed: ${lesions.length.toLocaleString()}`);
|
|
144
184
|
return {
|
|
145
185
|
lesionData: lesions,
|
|
@@ -148,38 +188,60 @@ async function processSampleData(samples, ds, request) {
|
|
|
148
188
|
}
|
|
149
189
|
async function processSampleMlst(sampleName, mlst, startId, request) {
|
|
150
190
|
const lesions = [];
|
|
191
|
+
const sampleDataTypeCounts = {
|
|
192
|
+
known: {
|
|
193
|
+
[dtsnvindel]: { entries: 0, processedLesions: 0 },
|
|
194
|
+
[dtcnv]: { entries: 0, processedLesions: 0 },
|
|
195
|
+
[dtfusionrna]: { entries: 0, processedLesions: 0 }
|
|
196
|
+
},
|
|
197
|
+
unknown: /* @__PURE__ */ new Map()
|
|
198
|
+
};
|
|
151
199
|
for (const m of mlst) {
|
|
152
200
|
switch (m.dt) {
|
|
153
201
|
case dtsnvindel: {
|
|
202
|
+
sampleDataTypeCounts.known[dtsnvindel].entries++;
|
|
154
203
|
if (!request.snvindelOptions)
|
|
155
204
|
break;
|
|
156
205
|
const snvIndelLesion = filterAndConvertSnvIndel(sampleName, m, request.snvindelOptions);
|
|
157
|
-
if (snvIndelLesion)
|
|
206
|
+
if (snvIndelLesion) {
|
|
158
207
|
lesions.push(snvIndelLesion);
|
|
208
|
+
sampleDataTypeCounts.known[dtsnvindel].processedLesions++;
|
|
209
|
+
}
|
|
159
210
|
break;
|
|
160
211
|
}
|
|
161
212
|
case dtcnv: {
|
|
213
|
+
sampleDataTypeCounts.known[dtcnv].entries++;
|
|
162
214
|
if (!request.cnvOptions)
|
|
163
215
|
break;
|
|
164
216
|
const cnvLesion = filterAndConvertCnv(sampleName, m, request.cnvOptions);
|
|
165
|
-
if (cnvLesion)
|
|
217
|
+
if (cnvLesion) {
|
|
166
218
|
lesions.push(cnvLesion);
|
|
219
|
+
sampleDataTypeCounts.known[dtcnv].processedLesions++;
|
|
220
|
+
}
|
|
167
221
|
break;
|
|
168
222
|
}
|
|
169
223
|
case dtfusionrna: {
|
|
224
|
+
sampleDataTypeCounts.known[dtfusionrna].entries++;
|
|
170
225
|
if (!request.fusionOptions)
|
|
171
226
|
break;
|
|
172
227
|
const fusionLesion = filterAndConvertFusion(sampleName, m, request.fusionOptions);
|
|
173
|
-
if (fusionLesion)
|
|
228
|
+
if (fusionLesion) {
|
|
174
229
|
lesions.push(fusionLesion);
|
|
230
|
+
sampleDataTypeCounts.known[dtfusionrna].processedLesions++;
|
|
231
|
+
}
|
|
175
232
|
break;
|
|
176
233
|
}
|
|
177
|
-
default:
|
|
178
|
-
|
|
234
|
+
default: {
|
|
235
|
+
const currentCount = sampleDataTypeCounts.unknown.get(m.dt) || 0;
|
|
236
|
+
sampleDataTypeCounts.unknown.set(m.dt, currentCount + 1);
|
|
179
237
|
break;
|
|
238
|
+
}
|
|
180
239
|
}
|
|
181
240
|
}
|
|
182
|
-
return
|
|
241
|
+
return {
|
|
242
|
+
sampleLesions: lesions,
|
|
243
|
+
sampleDataTypeCounts
|
|
244
|
+
};
|
|
183
245
|
}
|
|
184
246
|
function filterAndConvertSnvIndel(sampleName, entry, options) {
|
|
185
247
|
const opts = {
|
|
@@ -188,16 +250,27 @@ function filterAndConvertSnvIndel(sampleName, entry, options) {
|
|
|
188
250
|
consequences: options?.consequences ?? [],
|
|
189
251
|
hyperMutator: options?.hyperMutator ?? 1e3
|
|
190
252
|
};
|
|
191
|
-
if (opts.consequences.length > 0 && entry.
|
|
253
|
+
if (opts.consequences.length > 0 && entry.class && !opts.consequences.includes(entry.class)) {
|
|
192
254
|
return null;
|
|
193
255
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
256
|
+
const chromosome = entry.chromosome || entry.chr;
|
|
257
|
+
const position = entry.start || entry.position || entry.pos;
|
|
258
|
+
if (!chromosome) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
if (position === void 0 || position === null || position === "undefined") {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const numPosition = parseInt(String(position));
|
|
265
|
+
if (isNaN(numPosition)) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const endPosition = entry.end || position;
|
|
269
|
+
const numEndPosition = parseInt(String(endPosition));
|
|
270
|
+
if (isNaN(numEndPosition)) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return [sampleName, normalizeChromosome(chromosome), String(numPosition), String(numEndPosition), "mutation"];
|
|
201
274
|
}
|
|
202
275
|
function filterAndConvertCnv(sampleName, entry, options) {
|
|
203
276
|
const opts = {
|
|
@@ -207,18 +280,38 @@ function filterAndConvertCnv(sampleName, entry, options) {
|
|
|
207
280
|
minSegLength: options?.minSegLength ?? 0,
|
|
208
281
|
hyperMutator: options?.hyperMutator ?? 500
|
|
209
282
|
};
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
if (!isGain && !isLoss)
|
|
283
|
+
const log2Ratio = entry.log2Ratio ?? entry.value ?? entry.ratio;
|
|
284
|
+
if (log2Ratio === void 0 || log2Ratio === null) {
|
|
213
285
|
return null;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
286
|
+
}
|
|
287
|
+
const numLog2Ratio = parseFloat(String(log2Ratio));
|
|
288
|
+
if (isNaN(numLog2Ratio)) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
const isGain = numLog2Ratio >= opts.gainThreshold;
|
|
292
|
+
const isLoss = numLog2Ratio <= opts.lossThreshold;
|
|
293
|
+
if (!isGain && !isLoss) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const chromosome = entry.chromosome || entry.chr;
|
|
297
|
+
const start = entry.start || entry.begin;
|
|
298
|
+
const end = entry.end || entry.stop;
|
|
299
|
+
if (!chromosome) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
if (start === void 0 || start === null || start === "undefined") {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
if (end === void 0 || end === null || end === "undefined") {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
const numStart = parseInt(String(start));
|
|
309
|
+
const numEnd = parseInt(String(end));
|
|
310
|
+
if (isNaN(numStart) || isNaN(numEnd)) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const lesionType = numLog2Ratio >= opts.gainThreshold ? "gain" : "loss";
|
|
314
|
+
return [sampleName, normalizeChromosome(chromosome), String(numStart), String(numEnd), lesionType];
|
|
222
315
|
}
|
|
223
316
|
function filterAndConvertFusion(sampleName, entry, options) {
|
|
224
317
|
const opts = {
|
|
@@ -238,6 +331,8 @@ function filterAndConvertFusion(sampleName, entry, options) {
|
|
|
238
331
|
];
|
|
239
332
|
}
|
|
240
333
|
function normalizeChromosome(chrom) {
|
|
334
|
+
if (!chrom)
|
|
335
|
+
return "chr?";
|
|
241
336
|
return chrom.startsWith("chr") ? chrom : `chr${chrom}`;
|
|
242
337
|
}
|
|
243
338
|
export {
|
package/routes/termdb.config.js
CHANGED
|
@@ -265,10 +265,12 @@ function getAllowedTermTypes(ds) {
|
|
|
265
265
|
for (const t of ds.cohort.termdb.allowedTermTypes)
|
|
266
266
|
typeSet.add(t);
|
|
267
267
|
}
|
|
268
|
-
if (ds
|
|
268
|
+
if (ds.queries?.geneExpression)
|
|
269
269
|
typeSet.add(TermTypes.GENE_EXPRESSION);
|
|
270
|
-
if (ds
|
|
270
|
+
if (ds.queries?.metaboliteIntensity)
|
|
271
271
|
typeSet.add(TermTypes.METABOLITE_INTENSITY);
|
|
272
|
+
if (ds.queries?.ssGSEA)
|
|
273
|
+
typeSet.add(TermTypes.SSGSEA);
|
|
272
274
|
return [...typeSet];
|
|
273
275
|
}
|
|
274
276
|
function getSelectCohort(ds, req) {
|
|
@@ -21,34 +21,30 @@ function init({ genomes }) {
|
|
|
21
21
|
if (!g)
|
|
22
22
|
throw "invalid genome name";
|
|
23
23
|
const ds = g.datasets?.[req.query.dslabel];
|
|
24
|
-
getFilters(req.query, ds
|
|
24
|
+
res.send(await getFilters(req.query, ds));
|
|
25
25
|
} catch (e) {
|
|
26
|
-
|
|
26
|
+
if (e.stack)
|
|
27
|
+
console.log(e.stack);
|
|
27
28
|
res.send({ status: "error", error: e.message || e });
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
|
-
async function getFilters(query, ds
|
|
32
|
+
async function getFilters(query, ds) {
|
|
32
33
|
if (!query.filterByUserSites)
|
|
33
34
|
authApi.mayAdjustFilter(query, ds, query.terms);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
tw2List[tw.term.id] = getList(samplesPerFilter, filtersData, tw, query.showAll);
|
|
46
|
-
}
|
|
47
|
-
res.send({ ...tw2List });
|
|
48
|
-
} catch (e) {
|
|
49
|
-
console.log(e);
|
|
50
|
-
res.send({ error: e.message || e });
|
|
35
|
+
const samplesPerFilter = await getSamplesPerFilter(query, ds);
|
|
36
|
+
const filtersData = await getData(
|
|
37
|
+
{
|
|
38
|
+
terms: query.terms,
|
|
39
|
+
__protected__: query.__protected__
|
|
40
|
+
},
|
|
41
|
+
ds
|
|
42
|
+
);
|
|
43
|
+
const tw2List = {};
|
|
44
|
+
for (const tw of query.terms) {
|
|
45
|
+
tw2List[tw.term.id] = getList(samplesPerFilter, filtersData, tw, query.showAll);
|
|
51
46
|
}
|
|
47
|
+
return { ...tw2List };
|
|
52
48
|
}
|
|
53
49
|
function getList(samplesPerFilter, filtersData, tw, showAll) {
|
|
54
50
|
const values = Object.values(tw.term.values);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { percentilePayload } from "#types/checkers";
|
|
2
|
-
import
|
|
2
|
+
import { isNumericTerm } from "#shared/terms.js";
|
|
3
|
+
import { getData } from "#src/termdb.matrix.js";
|
|
3
4
|
import computePercentile from "#shared/compute.percentile.js";
|
|
4
5
|
const api = {
|
|
5
6
|
endpoint: "termdb/getpercentile",
|
|
@@ -33,20 +34,23 @@ function init({ genomes }) {
|
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
async function trigger_getpercentile(q, res, ds) {
|
|
36
|
-
const term =
|
|
37
|
+
const term = q.term;
|
|
37
38
|
if (!term)
|
|
38
|
-
throw "
|
|
39
|
-
if (term
|
|
40
|
-
throw "not
|
|
41
|
-
const
|
|
42
|
-
const
|
|
39
|
+
throw "term is missing";
|
|
40
|
+
if (!isNumericTerm(term))
|
|
41
|
+
throw "not numeric term";
|
|
42
|
+
const tw = { $id: "_", term, q: { mode: "continuous" } };
|
|
43
|
+
const data = await getData({ filter: q.filter, filter0: q.filter0, terms: [tw], __protected__: q.__protected__ }, ds);
|
|
44
|
+
if (data.error)
|
|
45
|
+
throw data.error;
|
|
43
46
|
const values = [];
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
for (const key in data.samples) {
|
|
48
|
+
const sample = data.samples[key];
|
|
49
|
+
const v = sample[tw.$id];
|
|
50
|
+
if (!v?.value) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const value = v.value;
|
|
50
54
|
if (term.values && term.values[value] && term.values[value].uncomputable) {
|
|
51
55
|
continue;
|
|
52
56
|
}
|
|
@@ -55,6 +59,8 @@ async function trigger_getpercentile(q, res, ds) {
|
|
|
55
59
|
}
|
|
56
60
|
values.push(Number(value));
|
|
57
61
|
}
|
|
62
|
+
const percentile_lst = q.getpercentile;
|
|
63
|
+
const perc_values = [];
|
|
58
64
|
for (const percentile of percentile_lst) {
|
|
59
65
|
const perc_value = computePercentile(values, percentile);
|
|
60
66
|
perc_values.push(perc_value);
|