@sjcrh/proteinpaint-server 2.113.0 → 2.115.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/package.json +7 -6
- package/routes/correlationVolcano.js +4 -1
- package/routes/samplewsimages.js +23 -35
- package/routes/termdb.cluster.js +182 -74
- package/routes/termdb.config.js +2 -0
- package/routes/termdb.topMutatedGenes.js +51 -0
- package/routes/wsisamples.js +74 -0
- package/src/app.js +834 -780
- package/src/serverconfig.js +1 -1
- package/utils/edge.R +202 -202
- package/utils/regression.utils.R +2 -1
- package/routes/gdc.topMutatedGenes.js +0 -275
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.115.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",
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
"start": "tsx watch . /start.js",
|
|
20
20
|
"test:unit": "tsx emitImports.js unit > serverTests.js && c8 tsx serverTests.js && rm -rf ./cache",
|
|
21
21
|
"precombined:coverage": "tsx emitImports.js unit > serverTests.js",
|
|
22
|
-
"combined:coverage": "
|
|
22
|
+
"combined:coverage": "coverageKey=test c8 --all --src=proteinpaint/server --experimental-monocart -r=v8 -r=html -r=json -r=markdown-summary -r=markdown-details -o=./.coverage tsx ./coverage.js & ",
|
|
23
23
|
"postcombined:coverage": "rm -rf ./cache",
|
|
24
|
+
"spec:coverage": "tsx test/relevant.js",
|
|
24
25
|
"getconf": "../build/getConfigProp.js",
|
|
25
26
|
"doc": "../augen/build.sh routes shared/types/routes shared/checkers ../public/docs/server",
|
|
26
27
|
"mjs": "esbuild \"$DIR/*.ts\" --platform=node --outdir=\"$DIR\" --format=esm",
|
|
@@ -64,10 +65,10 @@
|
|
|
64
65
|
"typescript": "^5.6.3"
|
|
65
66
|
},
|
|
66
67
|
"dependencies": {
|
|
67
|
-
"@sjcrh/augen": "2.
|
|
68
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
69
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
70
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
68
|
+
"@sjcrh/augen": "2.115.0",
|
|
69
|
+
"@sjcrh/proteinpaint-rust": "2.114.0",
|
|
70
|
+
"@sjcrh/proteinpaint-shared": "2.115.0",
|
|
71
|
+
"@sjcrh/proteinpaint-types": "2.115.0",
|
|
71
72
|
"@types/express": "^5.0.0",
|
|
72
73
|
"@types/express-session": "^1.18.1",
|
|
73
74
|
"better-sqlite3": "^9.4.1",
|
|
@@ -5,6 +5,7 @@ import serverconfig from "../src/serverconfig.js";
|
|
|
5
5
|
import { mayLog } from "#src/helpers.ts";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import { getStdDev } from "#shared/descriptive.stats.js";
|
|
8
|
+
import { formatElapsedTime } from "#shared/time.js";
|
|
8
9
|
const minArrayLength = 3;
|
|
9
10
|
const minSD = 0.05;
|
|
10
11
|
const api = {
|
|
@@ -92,7 +93,9 @@ async function compute(q, ds, genome) {
|
|
|
92
93
|
const output = {
|
|
93
94
|
terms: JSON.parse(await run_R(path.join(serverconfig.binpath, "utils", "corr.R"), JSON.stringify(input)))
|
|
94
95
|
};
|
|
95
|
-
|
|
96
|
+
const elapsedMs = Date.now() - time1;
|
|
97
|
+
const formattedTime = formatElapsedTime(elapsedMs);
|
|
98
|
+
mayLog("Time taken to run correlation analysis:", formattedTime);
|
|
96
99
|
for (const t of output.terms) {
|
|
97
100
|
const t2 = {
|
|
98
101
|
tw$id: t.id,
|
package/routes/samplewsimages.js
CHANGED
|
@@ -23,18 +23,7 @@ function init({ genomes }) {
|
|
|
23
23
|
if (!ds)
|
|
24
24
|
throw "invalid dataset name";
|
|
25
25
|
const sampleId = query.sample_id;
|
|
26
|
-
|
|
27
|
-
const images2 = [];
|
|
28
|
-
if (ds.queries.WSImages.sources) {
|
|
29
|
-
images2.push({
|
|
30
|
-
filename: sampleId + "_fsspec.json",
|
|
31
|
-
metadata: ""
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
res.send({ sampleWSImages: images2 });
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const images = await ds.queries.WSImages.getWSImages({ sampleId });
|
|
26
|
+
const images = await ds.queries.WSImages.getWSImages(sampleId);
|
|
38
27
|
res.send({ sampleWSImages: images });
|
|
39
28
|
} catch (e) {
|
|
40
29
|
console.log(e);
|
|
@@ -42,32 +31,31 @@ function init({ genomes }) {
|
|
|
42
31
|
}
|
|
43
32
|
};
|
|
44
33
|
}
|
|
45
|
-
function validate_query_getSampleWSImages(ds) {
|
|
46
|
-
|
|
47
|
-
if (!q)
|
|
34
|
+
async function validate_query_getSampleWSImages(ds) {
|
|
35
|
+
if (!ds.queries?.WSImages)
|
|
48
36
|
return;
|
|
49
|
-
|
|
37
|
+
validateQuery(ds);
|
|
50
38
|
}
|
|
51
|
-
function
|
|
52
|
-
ds.queries.WSImages.getWSImages
|
|
53
|
-
return
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
async function getWSImages(ds, sampleName) {
|
|
57
|
-
const sql = `SELECT wsimages.filename as filename, wsimages.metadata as metadata
|
|
58
|
-
FROM wsimages
|
|
59
|
-
INNER JOIN sampleidmap
|
|
60
|
-
ON wsimages.sample = sampleidmap.id
|
|
61
|
-
WHERE sampleidmap.name = ?`;
|
|
62
|
-
const rows = ds.cohort.db.connection.prepare(sql).all(sampleName);
|
|
63
|
-
const images = [];
|
|
64
|
-
for (const row of rows) {
|
|
65
|
-
images.push({
|
|
66
|
-
filename: row.filename,
|
|
67
|
-
metadata: row.metadata
|
|
68
|
-
});
|
|
39
|
+
function validateQuery(ds) {
|
|
40
|
+
if (typeof ds.queries.WSImages.getWSImages == "function") {
|
|
41
|
+
return;
|
|
69
42
|
}
|
|
70
|
-
|
|
43
|
+
ds.queries.WSImages.getWSImages = async (sampleName) => {
|
|
44
|
+
const sql = `SELECT wsimages.filename as filename, wsimages.metadata as metadata
|
|
45
|
+
FROM wsimages
|
|
46
|
+
INNER JOIN sampleidmap
|
|
47
|
+
ON wsimages.sample = sampleidmap.id
|
|
48
|
+
WHERE sampleidmap.name = ?`;
|
|
49
|
+
const rows = ds.cohort.db.connection.prepare(sql).all(sampleName);
|
|
50
|
+
const images = [];
|
|
51
|
+
for (const row of rows) {
|
|
52
|
+
images.push({
|
|
53
|
+
filename: row.filename,
|
|
54
|
+
metadata: row.metadata
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return images;
|
|
58
|
+
};
|
|
71
59
|
}
|
|
72
60
|
export {
|
|
73
61
|
api,
|
package/routes/termdb.cluster.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import run_R from "#src/run_R.js";
|
|
3
|
+
import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
3
4
|
import { termdbClusterPayload } from "#types/checkers";
|
|
4
5
|
import * as utils from "#src/utils.js";
|
|
5
6
|
import serverconfig from "#src/serverconfig.js";
|
|
@@ -223,92 +224,199 @@ async function validate_query_geneExpression(ds, genome) {
|
|
|
223
224
|
}
|
|
224
225
|
throw "unknown queries.geneExpression.src";
|
|
225
226
|
}
|
|
227
|
+
async function validateHDF5File(filePath) {
|
|
228
|
+
try {
|
|
229
|
+
const jsonInput = JSON.stringify({
|
|
230
|
+
hdf5_file: filePath
|
|
231
|
+
});
|
|
232
|
+
const result = await run_rust("validateHDF5", jsonInput);
|
|
233
|
+
return JSON.parse(result);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error(`Error validating file: ${error}`);
|
|
236
|
+
return {
|
|
237
|
+
status: "error",
|
|
238
|
+
message: `Validation error`
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function queryGeneExpression(hdf5_file, geneName) {
|
|
243
|
+
const jsonInput = JSON.stringify({
|
|
244
|
+
hdf5_file,
|
|
245
|
+
gene: geneName
|
|
246
|
+
});
|
|
247
|
+
try {
|
|
248
|
+
const result = await run_rust("readHDF5", jsonInput);
|
|
249
|
+
if (!result || Object.keys(result).length === 0) {
|
|
250
|
+
throw new Error("Failed to retrieve expression data: Empty or missing response");
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error(`Error querying gene expression for ${geneName}`);
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
226
258
|
async function validateNative(q, ds, genome) {
|
|
227
|
-
if (
|
|
259
|
+
if (q.hdf5File === true) {
|
|
228
260
|
q.file = path.join(serverconfig.tpmasterdir, q.file);
|
|
229
|
-
if (!q.samples)
|
|
230
261
|
q.samples = [];
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (id == void 0)
|
|
244
|
-
throw "queries.geneExpression: unknown sample from header: " + l[i];
|
|
245
|
-
q.samples.push(id);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
q.get = async (param) => {
|
|
249
|
-
const limitSamples = await mayLimitSamples(param, q.samples, ds);
|
|
250
|
-
if (limitSamples?.size == 0) {
|
|
251
|
-
return { term2sample2value: /* @__PURE__ */ new Map(), byTermId: {}, bySampleId: {} };
|
|
252
|
-
}
|
|
253
|
-
const bySampleId = {};
|
|
254
|
-
const samples = q.samples || [];
|
|
255
|
-
if (limitSamples) {
|
|
256
|
-
for (const sid of limitSamples) {
|
|
257
|
-
bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
for (const sid of samples) {
|
|
261
|
-
bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
|
|
262
|
+
await utils.file_is_readable(q.file);
|
|
263
|
+
try {
|
|
264
|
+
const vr = await validateHDF5File(q.file);
|
|
265
|
+
if (vr.status !== "success")
|
|
266
|
+
throw vr.message;
|
|
267
|
+
if (!vr.sampleNames?.length)
|
|
268
|
+
throw "HDF5 file has no samples";
|
|
269
|
+
for (const sn of vr.sampleNames) {
|
|
270
|
+
const si = ds.cohort.termdb.q.sampleName2id(sn);
|
|
271
|
+
if (si == void 0)
|
|
272
|
+
throw "unknown sample from HDF5: " + sn;
|
|
273
|
+
q.samples.push(si);
|
|
262
274
|
}
|
|
275
|
+
console.log(`${ds.label}: HDF5 file validated. Format: ${vr.format}, Samples:`, vr.sampleNames.length);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
throw `${ds.label}: Failed to validate HDF5 file: ${error}`;
|
|
263
278
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
279
|
+
q.get = async (param) => {
|
|
280
|
+
const limitSamples = await mayLimitSamples(param, q.samples, ds);
|
|
281
|
+
if (limitSamples?.size == 0) {
|
|
282
|
+
return { term2sample2value: /* @__PURE__ */ new Map(), byTermId: {}, bySampleId: {} };
|
|
283
|
+
}
|
|
284
|
+
const bySampleId = {};
|
|
285
|
+
const samples = q.samples || [];
|
|
286
|
+
if (limitSamples) {
|
|
287
|
+
for (const sid of limitSamples) {
|
|
288
|
+
bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
for (const sid of samples) {
|
|
292
|
+
bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
|
|
273
293
|
}
|
|
274
|
-
const i = re.gmlst.find((i2) => i2.isdefault) || re.gmlst[0];
|
|
275
|
-
geneTerm.start = i.start;
|
|
276
|
-
geneTerm.stop = i.stop;
|
|
277
|
-
geneTerm.chr = i.chr;
|
|
278
294
|
}
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
(q.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const sampleId = samples[i - 4];
|
|
293
|
-
if (limitSamples && !limitSamples.has(sampleId))
|
|
295
|
+
const term2sample2value = /* @__PURE__ */ new Map();
|
|
296
|
+
const byTermId = {};
|
|
297
|
+
for (const geneTerm of param.terms) {
|
|
298
|
+
if (!geneTerm.gene)
|
|
299
|
+
continue;
|
|
300
|
+
try {
|
|
301
|
+
const geneQuery = await queryGeneExpression(q.file, geneTerm.gene);
|
|
302
|
+
const geneData = JSON.parse(geneQuery);
|
|
303
|
+
const samplesData = geneData.samples || {};
|
|
304
|
+
const s2v = {};
|
|
305
|
+
for (const [sampleName, value] of Object.entries(samplesData)) {
|
|
306
|
+
const sampleId = ds.cohort.termdb.q.sampleName2id(sampleName);
|
|
307
|
+
if (!sampleId)
|
|
294
308
|
continue;
|
|
295
|
-
if (!
|
|
309
|
+
if (limitSamples && !limitSamples.has(sampleId))
|
|
296
310
|
continue;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
311
|
+
s2v[sampleId] = Number(value);
|
|
312
|
+
}
|
|
313
|
+
console.log(`Gene ${geneTerm.gene} has ${Object.keys(s2v).length} samples with data`);
|
|
314
|
+
if (Object.keys(s2v).length) {
|
|
315
|
+
term2sample2value.set(geneTerm.gene, s2v);
|
|
301
316
|
}
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.warn(`Error processing gene ${geneTerm.gene}:`, error);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (term2sample2value.size == 0) {
|
|
323
|
+
throw "No data available for the input " + param.terms?.map((g) => g.gene).join(", ");
|
|
324
|
+
}
|
|
325
|
+
return { term2sample2value, byTermId, bySampleId };
|
|
326
|
+
};
|
|
327
|
+
} else {
|
|
328
|
+
if (!q.file.startsWith(serverconfig.tpmasterdir)) {
|
|
329
|
+
q.file = path.join(serverconfig.tpmasterdir, q.file);
|
|
330
|
+
}
|
|
331
|
+
if (!q.samples)
|
|
332
|
+
q.samples = [];
|
|
333
|
+
await utils.validate_tabixfile(q.file);
|
|
334
|
+
q.nochr = await utils.tabix_is_nochr(q.file, null, genome);
|
|
335
|
+
q.samples = [];
|
|
336
|
+
{
|
|
337
|
+
const lines = await utils.get_header_tabix(q.file);
|
|
338
|
+
if (!lines[0])
|
|
339
|
+
throw "Header line missing from " + q.file;
|
|
340
|
+
const l = lines[0].split(" ");
|
|
341
|
+
if (l.slice(0, 4).join(" ") != "#chr start stop gene") {
|
|
342
|
+
throw "Header line has wrong content for columns 1-4";
|
|
343
|
+
}
|
|
344
|
+
for (let i = 4; i < l.length; i++) {
|
|
345
|
+
const id = ds.cohort.termdb.q.sampleName2id(l[i]);
|
|
346
|
+
if (id == void 0) {
|
|
347
|
+
throw "queries.geneExpression: unknown sample from header: " + l[i];
|
|
302
348
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
term2sample2value.set(geneTerm.gene, s2v);
|
|
349
|
+
q.samples.push(id);
|
|
350
|
+
}
|
|
306
351
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
352
|
+
q.get = async (param) => {
|
|
353
|
+
const limitSamples = await mayLimitSamples(param, q.samples, ds);
|
|
354
|
+
if (limitSamples?.size == 0) {
|
|
355
|
+
return { term2sample2value: /* @__PURE__ */ new Map(), byTermId: {}, bySampleId: {} };
|
|
356
|
+
}
|
|
357
|
+
const bySampleId = {};
|
|
358
|
+
const samples = q.samples || [];
|
|
359
|
+
if (limitSamples) {
|
|
360
|
+
for (const sid of limitSamples) {
|
|
361
|
+
bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
for (const sid of samples) {
|
|
365
|
+
bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const term2sample2value = /* @__PURE__ */ new Map();
|
|
369
|
+
for (const geneTerm of param.terms) {
|
|
370
|
+
if (!geneTerm.gene)
|
|
371
|
+
continue;
|
|
372
|
+
if (!geneTerm.chr || !Number.isInteger(geneTerm.start) || !Number.isInteger(geneTerm.stop)) {
|
|
373
|
+
const re = getResultGene(genome, { input: geneTerm.gene, deep: 1 });
|
|
374
|
+
if (!re.gmlst || re.gmlst.length == 0) {
|
|
375
|
+
console.warn("Unknown gene:" + geneTerm.gene);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const i = re.gmlst.find((i2) => i2.isdefault) || re.gmlst[0];
|
|
379
|
+
geneTerm.start = i.start;
|
|
380
|
+
geneTerm.stop = i.stop;
|
|
381
|
+
geneTerm.chr = i.chr;
|
|
382
|
+
}
|
|
383
|
+
const s2v = {};
|
|
384
|
+
if (!geneTerm.chr || !Number.isInteger(geneTerm.start) || !Number.isInteger(geneTerm.stop)) {
|
|
385
|
+
throw "Missing chr/start/stop";
|
|
386
|
+
}
|
|
387
|
+
await utils.get_lines_bigfile({
|
|
388
|
+
args: [
|
|
389
|
+
q.file,
|
|
390
|
+
(q.nochr ? geneTerm.chr.replace("chr", "") : geneTerm.chr) + ":" + geneTerm.start + "-" + geneTerm.stop
|
|
391
|
+
],
|
|
392
|
+
callback: (line) => {
|
|
393
|
+
const l = line.split(" ");
|
|
394
|
+
if (l[3].toLowerCase() != geneTerm.gene.toLowerCase())
|
|
395
|
+
return;
|
|
396
|
+
for (let i = 4; i < l.length; i++) {
|
|
397
|
+
const sampleId = samples[i - 4];
|
|
398
|
+
if (limitSamples && !limitSamples.has(sampleId))
|
|
399
|
+
continue;
|
|
400
|
+
if (!l[i])
|
|
401
|
+
continue;
|
|
402
|
+
const v = Number(l[i]);
|
|
403
|
+
if (Number.isNaN(v))
|
|
404
|
+
throw "Expression value not number";
|
|
405
|
+
s2v[sampleId] = v;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
if (Object.keys(s2v).length) {
|
|
410
|
+
term2sample2value.set(geneTerm.gene, s2v);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const byTermId = {};
|
|
414
|
+
if (term2sample2value.size == 0) {
|
|
415
|
+
throw "No data available for the input " + param.terms?.map((g) => g.gene).join(", ");
|
|
416
|
+
}
|
|
417
|
+
return { term2sample2value, byTermId, bySampleId };
|
|
418
|
+
};
|
|
419
|
+
}
|
|
312
420
|
}
|
|
313
421
|
export {
|
|
314
422
|
api,
|
package/routes/termdb.config.js
CHANGED
|
@@ -155,6 +155,8 @@ function addNonDictionaryQueries(c, ds, genome) {
|
|
|
155
155
|
details: q.snvindel.details,
|
|
156
156
|
populations: q.snvindel.populations
|
|
157
157
|
};
|
|
158
|
+
if (q.snvindel.byisoform?.processTwsInOneQuery)
|
|
159
|
+
q2.snvindel.byisoform = { processTwsInOneQuery: true };
|
|
158
160
|
}
|
|
159
161
|
if (q.trackLst) {
|
|
160
162
|
q2.trackLst = q.trackLst;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { topMutatedGenePayload } from "#types/checkers";
|
|
2
|
+
const api = {
|
|
3
|
+
endpoint: "termdb/topMutatedGenes",
|
|
4
|
+
methods: {
|
|
5
|
+
get: {
|
|
6
|
+
init,
|
|
7
|
+
...topMutatedGenePayload
|
|
8
|
+
},
|
|
9
|
+
post: {
|
|
10
|
+
init,
|
|
11
|
+
...topMutatedGenePayload
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function init({ genomes }) {
|
|
16
|
+
return async (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const q = req.query;
|
|
19
|
+
const g = genomes[q.genome];
|
|
20
|
+
if (!g)
|
|
21
|
+
throw "genome missing";
|
|
22
|
+
const ds = g.datasets?.[q.dslabel];
|
|
23
|
+
if (!ds)
|
|
24
|
+
throw "ds missing";
|
|
25
|
+
if (!ds.queries?.topMutatedGenes)
|
|
26
|
+
throw "not supported by ds";
|
|
27
|
+
const genes = await ds.queries.topMutatedGenes.get(q);
|
|
28
|
+
const payload = { genes };
|
|
29
|
+
res.send(payload);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
res.send({ status: "error", error: e.message || e });
|
|
32
|
+
if (e.stack)
|
|
33
|
+
console.log(e.stack);
|
|
34
|
+
else
|
|
35
|
+
console.trace(e);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function validate_query_getTopMutatedGenes(ds, genome) {
|
|
40
|
+
const q = ds.queries?.topMutatedGenes;
|
|
41
|
+
if (!q)
|
|
42
|
+
return;
|
|
43
|
+
if (typeof q.get == "function")
|
|
44
|
+
return;
|
|
45
|
+
q.get = async (param) => {
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
api,
|
|
50
|
+
validate_query_getTopMutatedGenes
|
|
51
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { wsiSamplesPayload } from "#types/checkers";
|
|
2
|
+
const routePath = "wsisamples";
|
|
3
|
+
const api = {
|
|
4
|
+
endpoint: `${routePath}`,
|
|
5
|
+
methods: {
|
|
6
|
+
get: {
|
|
7
|
+
...wsiSamplesPayload,
|
|
8
|
+
init
|
|
9
|
+
},
|
|
10
|
+
post: {
|
|
11
|
+
...wsiSamplesPayload,
|
|
12
|
+
init
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function init({ genomes }) {
|
|
17
|
+
return async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const query = req.query;
|
|
20
|
+
const g = genomes[query.genome];
|
|
21
|
+
if (!g)
|
|
22
|
+
throw new Error("Invalid genome name");
|
|
23
|
+
const ds = g.datasets[query.dslabel];
|
|
24
|
+
if (!ds)
|
|
25
|
+
throw "Invalid dslabel";
|
|
26
|
+
const images = await ds.queries.WSImages.getSamples();
|
|
27
|
+
const payload = {
|
|
28
|
+
samples: images
|
|
29
|
+
};
|
|
30
|
+
res.status(200).json(payload);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.warn(e);
|
|
33
|
+
res.status(500).send({
|
|
34
|
+
status: "error",
|
|
35
|
+
error: e.message || e
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function validate_query_getWSISamples(ds) {
|
|
41
|
+
const q = ds.queries?.WSImages;
|
|
42
|
+
if (!q)
|
|
43
|
+
return;
|
|
44
|
+
validateQuery(ds);
|
|
45
|
+
}
|
|
46
|
+
function validateQuery(ds) {
|
|
47
|
+
if (typeof ds.queries.WSImages.getSamples == "function") {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
ds.queries.WSImages.getSamples = async () => {
|
|
51
|
+
const sql = `SELECT wsimages.sample as sample, wsimages.filename as filename, wsimages.metadata as metadata, sampleidmap.name as sampleName
|
|
52
|
+
FROM wsimages
|
|
53
|
+
INNER JOIN sampleidmap
|
|
54
|
+
ON wsimages.sample = sampleidmap.id`;
|
|
55
|
+
try {
|
|
56
|
+
const rows = ds.cohort.db.connection.prepare(sql).all();
|
|
57
|
+
const sampleMap = {};
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
if (!sampleMap[row.sample]) {
|
|
60
|
+
sampleMap[row.sample] = { sampleId: row.sampleName, wsimages: [] };
|
|
61
|
+
}
|
|
62
|
+
sampleMap[row.sample].wsimages.push(row.filename);
|
|
63
|
+
}
|
|
64
|
+
return Object.values(sampleMap);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("Error fetching samples:", error);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
api,
|
|
73
|
+
validate_query_getWSISamples
|
|
74
|
+
};
|