@sjcrh/proteinpaint-server 2.188.1 → 2.190.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 +9 -12
- package/routes/aiProjectAdmin.js +2 -28
- package/routes/aiProjectSelectedWSImages.js +2 -17
- package/routes/brainImaging.js +1 -15
- package/routes/brainImagingSamples.js +1 -15
- package/routes/burden.js +1 -15
- package/routes/correlationVolcano.js +1 -15
- package/routes/dataset.js +1 -16
- package/routes/deleteWSITileSelection.js +2 -12
- package/routes/dsdata.js +1 -19
- package/routes/gdc.grin2.list.js +1 -15
- package/routes/gdc.grin2.run.js +1 -15
- package/routes/gdc.maf.js +1 -15
- package/routes/gdc.mafBuild.js +1 -15
- package/routes/genesetEnrichment.js +129 -97
- package/routes/grin2.js +110 -79
- package/routes/saveWSIAnnotation.js +2 -13
- package/routes/termdb.DE.js +137 -54
- package/routes/termdb.categories.js +2 -16
- package/routes/termdb.chat.js +169 -1076
- package/routes/termdb.cluster.js +5 -16
- package/routes/termdb.config.js +12 -17
- package/routes/termdb.descrstats.js +2 -16
- package/routes/termdb.diffMeth.js +100 -21
- package/routes/termdb.geneRanking.js +139 -0
- package/routes/termdb.proteome.js +1 -15
- package/routes/termdb.runChart.js +16 -30
- package/routes/termdb.sampleScatter.js +7 -97
- package/routes/termdb.singleCellPlots.js +159 -0
- package/routes/termdb.singlecellSamples.js +6 -16
- package/routes/termdb.violinBox.js +1 -15
- package/routes/wsimages.js +1 -16
- package/src/app.js +4014 -4127
- package/routes/_template_.js +0 -33
- package/routes/aiProjectTrainModel.js +0 -68
- package/routes/alphaGenome.js +0 -41
- package/routes/alphaGenomeTypes.js +0 -36
- package/routes/dzimages.js +0 -55
- package/routes/gene2canonicalisoform.js +0 -37
- package/routes/genelookup.js +0 -32
- package/routes/genesetOverrepresentation.js +0 -49
- package/routes/genomes.js +0 -150
- package/routes/healthcheck.js +0 -35
- package/routes/hicdata.js +0 -74
- package/routes/hicgenome.js +0 -75
- package/routes/hicstat.js +0 -35
- package/routes/img.js +0 -46
- package/routes/isoformlst.js +0 -48
- package/routes/ntseq.js +0 -36
- package/routes/pdomain.js +0 -53
- package/routes/profile.barchart2.js +0 -114
- package/routes/profile.forms2.js +0 -107
- package/routes/profile.polar2.js +0 -101
- package/routes/profile.radar2.js +0 -112
- package/routes/profile.radarFacility2.js +0 -148
- package/routes/sampledzimages.js +0 -48
- package/routes/samplewsimages.js +0 -60
- package/routes/snp.js +0 -98
- package/routes/termdb.chat2.js +0 -217
- package/routes/termdb.chat3.js +0 -209
- package/routes/termdb.cohort.summary.js +0 -37
- package/routes/termdb.cohorts.js +0 -41
- package/routes/termdb.dapVolcano.js +0 -80
- package/routes/termdb.dmr.js +0 -93
- package/routes/termdb.filterTermValues.js +0 -89
- package/routes/termdb.isoformAvailability.js +0 -35
- package/routes/termdb.numericcategories.js +0 -46
- package/routes/termdb.percentile.js +0 -66
- package/routes/termdb.profileFormScores.js +0 -92
- package/routes/termdb.profileScores.js +0 -113
- package/routes/termdb.rootterm.js +0 -39
- package/routes/termdb.sampleImages.js +0 -63
- package/routes/termdb.singleSampleMutation.js +0 -75
- package/routes/termdb.singlecellDEgenes.js +0 -55
- package/routes/termdb.singlecellData.js +0 -39
- package/routes/termdb.termchildren.js +0 -42
- package/routes/termdb.termsbyids.js +0 -50
- package/routes/termdb.topMutatedGenes.js +0 -127
- package/routes/termdb.topTermsByType.js +0 -96
- package/routes/termdb.topVariablyExpressedGenes.js +0 -132
- package/routes/tileserver.js +0 -68
- package/routes/wsisamples.js +0 -71
package/routes/termdb.cluster.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
3
3
|
import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
4
|
-
import { termdbClusterPayload } from "#types/checkers";
|
|
5
4
|
import * as utils from "#src/utils.js";
|
|
6
5
|
import serverconfig from "#src/serverconfig.js";
|
|
7
6
|
import { gdc_validate_query_geneExpression } from "#src/mds3.gdc.js";
|
|
@@ -14,22 +13,10 @@ import {
|
|
|
14
13
|
NUMERIC_DICTIONARY_TERM,
|
|
15
14
|
termType2label,
|
|
16
15
|
ISOFORM_EXPRESSION,
|
|
16
|
+
SSGSEA,
|
|
17
17
|
PROTEOME_ABUNDANCE
|
|
18
18
|
} from "#shared/terms.js";
|
|
19
19
|
import { formatElapsedTime } from "#shared/time.js";
|
|
20
|
-
const api = {
|
|
21
|
-
endpoint: "termdb/cluster",
|
|
22
|
-
methods: {
|
|
23
|
-
get: {
|
|
24
|
-
...termdbClusterPayload,
|
|
25
|
-
init
|
|
26
|
-
},
|
|
27
|
-
post: {
|
|
28
|
-
...termdbClusterPayload,
|
|
29
|
-
init
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
20
|
function init({ genomes }) {
|
|
34
21
|
return async (req, res) => {
|
|
35
22
|
const q = req.query;
|
|
@@ -41,7 +28,9 @@ function init({ genomes }) {
|
|
|
41
28
|
if (!ds) throw "invalid dataset name";
|
|
42
29
|
if (ds.label === "GDC" && !ds.__gdc?.doneCaching)
|
|
43
30
|
throw "The server has not finished caching the case IDs: try again in about 2 minutes.";
|
|
44
|
-
if ([GENE_EXPRESSION, ISOFORM_EXPRESSION, METABOLITE_INTENSITY, NUMERIC_DICTIONARY_TERM].includes(
|
|
31
|
+
if ([GENE_EXPRESSION, SSGSEA, ISOFORM_EXPRESSION, METABOLITE_INTENSITY, NUMERIC_DICTIONARY_TERM].includes(
|
|
32
|
+
q.dataType
|
|
33
|
+
)) {
|
|
45
34
|
if (!ds.queries?.[q.dataType] && q.dataType !== NUMERIC_DICTIONARY_TERM)
|
|
46
35
|
throw `no ${q.dataType} data on this dataset`;
|
|
47
36
|
if (!q.terms) throw `missing gene list`;
|
|
@@ -461,7 +450,7 @@ async function validateNativeIsoform(q, ds) {
|
|
|
461
450
|
};
|
|
462
451
|
}
|
|
463
452
|
export {
|
|
464
|
-
|
|
453
|
+
init,
|
|
465
454
|
validateQueryIsoformExpression,
|
|
466
455
|
validate_query_geneExpression
|
|
467
456
|
};
|
package/routes/termdb.config.js
CHANGED
|
@@ -12,20 +12,6 @@ import {
|
|
|
12
12
|
DNA_METHYLATION,
|
|
13
13
|
SSGSEA
|
|
14
14
|
} from "#shared/terms.js";
|
|
15
|
-
const api = {
|
|
16
|
-
endpoint: "termdb/config",
|
|
17
|
-
methods: {
|
|
18
|
-
get: {
|
|
19
|
-
init,
|
|
20
|
-
request: {
|
|
21
|
-
typeId: "any"
|
|
22
|
-
},
|
|
23
|
-
response: {
|
|
24
|
-
typeId: "any"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
15
|
function init({ genomes }) {
|
|
30
16
|
return async (req, res) => {
|
|
31
17
|
const q = req.query;
|
|
@@ -69,7 +55,7 @@ function make(q, req, res, ds, genome) {
|
|
|
69
55
|
defaultTw4correlationPlot: tdb.defaultTw4correlationPlot,
|
|
70
56
|
authFilter: req.query.filter
|
|
71
57
|
};
|
|
72
|
-
if (tdb.plotConfigByCohort) c.plotConfigByCohort = tdb.plotConfigByCohort;
|
|
58
|
+
if (tdb.plotConfigByCohort) c.plotConfigByCohort = structuredClone(tdb.plotConfigByCohort);
|
|
73
59
|
if (tdb.multipleTestingCorrection) c.multipleTestingCorrection = tdb.multipleTestingCorrection;
|
|
74
60
|
if (tdb.helpPages) c.helpPages = tdb.helpPages;
|
|
75
61
|
if (tdb.minTimeSinceDx) c.minTimeSinceDx = tdb.minTimeSinceDx;
|
|
@@ -97,6 +83,7 @@ function make(q, req, res, ds, genome) {
|
|
|
97
83
|
if (tdb.displaySampleIds) c.displaySampleIds = tdb.displaySampleIds(c.clientAuthResult);
|
|
98
84
|
c.authFilter = req.query.filter;
|
|
99
85
|
addScatterplots(c, ds, info);
|
|
86
|
+
tdb.pruneTermdbConfig?.(c, q, ds);
|
|
100
87
|
res.send({ termdbConfig: c });
|
|
101
88
|
}
|
|
102
89
|
function addRestrictAncestries(c, tdb) {
|
|
@@ -235,6 +222,14 @@ function addNonDictionaryQueries(c, ds, genome) {
|
|
|
235
222
|
}
|
|
236
223
|
}
|
|
237
224
|
}
|
|
225
|
+
if (q.geneRanking) {
|
|
226
|
+
q2.geneRanking = {
|
|
227
|
+
rankings: Object.fromEntries(Object.keys(q.geneRanking.rankings).map((k) => [k, true])),
|
|
228
|
+
modalities: q.geneRanking.modalities,
|
|
229
|
+
description: q.geneRanking.description,
|
|
230
|
+
appName: q.geneRanking.appName
|
|
231
|
+
};
|
|
232
|
+
}
|
|
238
233
|
if (q.dnaMethylation) {
|
|
239
234
|
q2.dnaMethylation = { unit: q.dnaMethylation.unit };
|
|
240
235
|
if (q.dnaMethylation.promoter) {
|
|
@@ -347,6 +342,6 @@ function getSelectCohort(ds, req) {
|
|
|
347
342
|
return copy;
|
|
348
343
|
}
|
|
349
344
|
export {
|
|
350
|
-
|
|
351
|
-
|
|
345
|
+
getDsAllowedTermTypes,
|
|
346
|
+
init
|
|
352
347
|
};
|
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
import { descrStatsPayload } from "#types/checkers";
|
|
2
1
|
import { getData } from "#src/termdb.matrix.js";
|
|
3
2
|
import computePercentile from "#shared/compute.percentile.js";
|
|
4
3
|
import { roundValueAuto } from "#shared/roundValue.js";
|
|
5
|
-
const api = {
|
|
6
|
-
endpoint: "termdb/descrstats",
|
|
7
|
-
methods: {
|
|
8
|
-
get: {
|
|
9
|
-
...descrStatsPayload,
|
|
10
|
-
init
|
|
11
|
-
},
|
|
12
|
-
post: {
|
|
13
|
-
...descrStatsPayload,
|
|
14
|
-
init
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
4
|
function init({ genomes }) {
|
|
19
5
|
return async (req, res) => {
|
|
20
6
|
const q = req.query;
|
|
@@ -121,9 +107,9 @@ function getStdDev(data) {
|
|
|
121
107
|
return Math.sqrt(variance);
|
|
122
108
|
}
|
|
123
109
|
export {
|
|
124
|
-
api,
|
|
125
110
|
getDescrStats,
|
|
126
111
|
getMean,
|
|
127
112
|
getStdDev,
|
|
128
|
-
getVariance
|
|
113
|
+
getVariance,
|
|
114
|
+
init
|
|
129
115
|
};
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mayLog } from "#src/helpers.ts";
|
|
2
|
+
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
3
|
+
import { formatElapsedTime } from "#shared";
|
|
2
4
|
import { renderVolcano } from "../src/renderVolcano.ts";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
init
|
|
10
|
-
},
|
|
11
|
-
post: {
|
|
12
|
-
...diffMethPayload,
|
|
13
|
-
init
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
};
|
|
5
|
+
import { cacheOrRecompute } from "#src/utils/cacheOrRecompute.ts";
|
|
6
|
+
import {
|
|
7
|
+
buildGroupValues,
|
|
8
|
+
canonicalizeSamplelst,
|
|
9
|
+
resolveDaContext
|
|
10
|
+
} from "#src/utils/sampleGroups.ts";
|
|
17
11
|
function init({ genomes }) {
|
|
18
12
|
return async (req, res) => {
|
|
19
13
|
try {
|
|
@@ -32,10 +26,9 @@ function init({ genomes }) {
|
|
|
32
26
|
});
|
|
33
27
|
return;
|
|
34
28
|
}
|
|
35
|
-
const result = await
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
rendered.cacheId = result.cacheId;
|
|
29
|
+
const { result, cacheId } = await getDmCacheResult(q, genomes);
|
|
30
|
+
const rendered = await renderVolcano(result.promoterRows, q.volcanoRender);
|
|
31
|
+
rendered.cacheId = cacheId;
|
|
39
32
|
if (rendered.totalRows === 0)
|
|
40
33
|
throw new Error("No promoters passed filtering. Try relaxing group criteria or selecting more samples.");
|
|
41
34
|
const output = {
|
|
@@ -45,11 +38,97 @@ function init({ genomes }) {
|
|
|
45
38
|
};
|
|
46
39
|
res.send(output);
|
|
47
40
|
} catch (e) {
|
|
48
|
-
res.send({ status: "error", error: e.message || e });
|
|
41
|
+
res.status(e.status || 500).send({ status: "error", error: e.message || e, code: e.code });
|
|
49
42
|
if (e instanceof Error && e.stack) console.log(e);
|
|
50
43
|
}
|
|
51
44
|
};
|
|
52
45
|
}
|
|
46
|
+
function dmKeyInputs(req) {
|
|
47
|
+
return {
|
|
48
|
+
genome: req.genome,
|
|
49
|
+
dslabel: req.dslabel,
|
|
50
|
+
samplelst: canonicalizeSamplelst(req.samplelst),
|
|
51
|
+
min_samples_per_group: req.min_samples_per_group ?? null,
|
|
52
|
+
tw: req.tw ?? null,
|
|
53
|
+
tw2: req.tw2 ?? null,
|
|
54
|
+
filter: req.filter ?? null,
|
|
55
|
+
filter0: req.filter0 ?? null
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function getDmCacheResult(req, genomes) {
|
|
59
|
+
const { result, cacheId } = await cacheOrRecompute({
|
|
60
|
+
computeArgument: dmKeyInputs(req),
|
|
61
|
+
cacheSubdir: "dm",
|
|
62
|
+
computeFresh: async () => {
|
|
63
|
+
const { ds, term_results, term_results2 } = await resolveDaContext(req, genomes);
|
|
64
|
+
return runDmFresh(req, ds, term_results, term_results2);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return { result, cacheId };
|
|
68
|
+
}
|
|
69
|
+
async function runDmFresh(param, ds, term_results, term_results2) {
|
|
70
|
+
const groups = resolveDmSampleGroups(param, ds, term_results, term_results2);
|
|
71
|
+
if (groups.alerts.length) throw new Error(groups.alerts.join(" | "));
|
|
72
|
+
const q = ds.queries.dnaMethylation.promoter;
|
|
73
|
+
const diffMethInput = {
|
|
74
|
+
// Group 1 is control, group 2 is case (same convention as DE).
|
|
75
|
+
case: groups.group2names.join(","),
|
|
76
|
+
control: groups.group1names.join(","),
|
|
77
|
+
input_file: q.file,
|
|
78
|
+
min_samples_per_group: param.min_samples_per_group
|
|
79
|
+
};
|
|
80
|
+
if (param.tw) {
|
|
81
|
+
diffMethInput.conf1 = [...groups.conf1_group2, ...groups.conf1_group1];
|
|
82
|
+
diffMethInput.conf1_mode = param.tw.q.mode;
|
|
83
|
+
if (new Set(diffMethInput.conf1).size === 1) throw new Error("Confounding variable 1 has only one value");
|
|
84
|
+
}
|
|
85
|
+
if (param.tw2) {
|
|
86
|
+
diffMethInput.conf2 = [...groups.conf2_group2, ...groups.conf2_group1];
|
|
87
|
+
diffMethInput.conf2_mode = param.tw2.q.mode;
|
|
88
|
+
if (new Set(diffMethInput.conf2).size === 1) throw new Error("Confounding variable 2 has only one value");
|
|
89
|
+
}
|
|
90
|
+
const time1 = Date.now();
|
|
91
|
+
const result = JSON.parse(await run_R("diffMeth.R", JSON.stringify(diffMethInput)));
|
|
92
|
+
mayLog("Time taken to run diffMeth:", formatElapsedTime(Date.now() - time1));
|
|
93
|
+
const cacheResult = {
|
|
94
|
+
promoterRows: result.promoter_data,
|
|
95
|
+
sample_size1: groups.group1names.length,
|
|
96
|
+
sample_size2: groups.group2names.length
|
|
97
|
+
};
|
|
98
|
+
return cacheResult;
|
|
99
|
+
}
|
|
100
|
+
function resolveDmSampleGroups(param, ds, term_results, term_results2) {
|
|
101
|
+
if (param.samplelst?.groups?.length != 2)
|
|
102
|
+
throw new Error("Exactly 2 sample groups are required for differential methylation analysis.");
|
|
103
|
+
if (param.samplelst.groups[0].values?.length < 1)
|
|
104
|
+
throw new Error("Group 1 has no samples. Please select at least one sample.");
|
|
105
|
+
if (param.samplelst.groups[1].values?.length < 1)
|
|
106
|
+
throw new Error("Group 2 has no samples. Please select at least one sample.");
|
|
107
|
+
const q = ds.queries.dnaMethylation?.promoter;
|
|
108
|
+
if (!q) throw new Error("This dataset does not have promoter-level methylation data configured.");
|
|
109
|
+
if (!q.file) throw new Error("Promoter methylation data file is not configured for this dataset.");
|
|
110
|
+
const g1 = buildGroupValues(param.samplelst.groups[0].values, q, ds, param.tw, param.tw2, term_results, term_results2);
|
|
111
|
+
const g2 = buildGroupValues(param.samplelst.groups[1].values, q, ds, param.tw, param.tw2, term_results, term_results2);
|
|
112
|
+
const alerts = [];
|
|
113
|
+
if (g1.names.length < 1) alerts.push("No samples in group 1 have methylation data available.");
|
|
114
|
+
if (g2.names.length < 1) alerts.push("No samples in group 2 have methylation data available.");
|
|
115
|
+
const commonnames = g1.names.filter((x) => g2.names.includes(x));
|
|
116
|
+
if (commonnames.length)
|
|
117
|
+
alerts.push(
|
|
118
|
+
`${commonnames.length} sample(s) appear in both groups: ${commonnames.join(", ")}. Please remove duplicates.`
|
|
119
|
+
);
|
|
120
|
+
return {
|
|
121
|
+
group1names: g1.names,
|
|
122
|
+
group2names: g2.names,
|
|
123
|
+
conf1_group1: g1.conf1,
|
|
124
|
+
conf1_group2: g2.conf1,
|
|
125
|
+
conf2_group1: g1.conf2,
|
|
126
|
+
conf2_group2: g2.conf2,
|
|
127
|
+
alerts
|
|
128
|
+
};
|
|
129
|
+
}
|
|
53
130
|
export {
|
|
54
|
-
|
|
131
|
+
getDmCacheResult,
|
|
132
|
+
init,
|
|
133
|
+
resolveDmSampleGroups
|
|
55
134
|
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
4
|
+
import serverconfig from "#src/serverconfig.js";
|
|
5
|
+
import { clusterMethodLst, distanceMethodLst } from "#shared/clustering.js";
|
|
6
|
+
function init({ genomes }) {
|
|
7
|
+
return async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const q = (req.method === "POST" ? req.body : req.query) || {};
|
|
10
|
+
if (q.for === "cluster") {
|
|
11
|
+
await handleCluster(q, res);
|
|
12
|
+
} else {
|
|
13
|
+
await handleData(q, res, genomes);
|
|
14
|
+
}
|
|
15
|
+
} catch (e) {
|
|
16
|
+
if (e instanceof Error && e.stack) console.log(e);
|
|
17
|
+
res.send({ error: e?.message || String(e) });
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
22
|
+
function stripQuotes(s) {
|
|
23
|
+
if (s.length >= 2 && s[0] === '"' && s[s.length - 1] === '"') return s.slice(1, -1);
|
|
24
|
+
return s;
|
|
25
|
+
}
|
|
26
|
+
async function parseTsv(absPath) {
|
|
27
|
+
const text = await fs.promises.readFile(absPath, "utf8");
|
|
28
|
+
const lines = text.split(/\r?\n/).filter((l) => l.length > 0);
|
|
29
|
+
if (lines.length === 0) return { columns: [], rows: [] };
|
|
30
|
+
const columns = lines[0].split(" ").map(stripQuotes);
|
|
31
|
+
const rows = [];
|
|
32
|
+
for (let i = 1; i < lines.length; i++) {
|
|
33
|
+
const fields = lines[i].split(" ").map(stripQuotes);
|
|
34
|
+
const normalizedFields = fields.length < columns.length ? fields.concat(Array(columns.length - fields.length).fill("")) : fields.slice(0, columns.length);
|
|
35
|
+
const row = normalizedFields.map((v) => {
|
|
36
|
+
if (v === "" || v === "NA" || v === "NaN") return null;
|
|
37
|
+
const n = Number(v);
|
|
38
|
+
return Number.isFinite(n) && v.trim() !== "" ? n : v;
|
|
39
|
+
});
|
|
40
|
+
rows.push(row);
|
|
41
|
+
}
|
|
42
|
+
return { columns, rows };
|
|
43
|
+
}
|
|
44
|
+
function zscorePerColumnIgnoringNull(matrix, ncol) {
|
|
45
|
+
const out = matrix.map((r) => [...r]);
|
|
46
|
+
for (let c = 0; c < ncol; c++) {
|
|
47
|
+
const vals = [];
|
|
48
|
+
for (const r of matrix) {
|
|
49
|
+
const v = r[c];
|
|
50
|
+
if (v !== null && v !== void 0 && Number.isFinite(v)) vals.push(v);
|
|
51
|
+
}
|
|
52
|
+
if (vals.length === 0) continue;
|
|
53
|
+
const mean = vals.reduce((s, v) => s + v, 0) / vals.length;
|
|
54
|
+
const sd = Math.sqrt(vals.reduce((s, v) => s + (v - mean) ** 2, 0) / vals.length);
|
|
55
|
+
for (let r = 0; r < matrix.length; r++) {
|
|
56
|
+
const v = matrix[r][c];
|
|
57
|
+
if (v === null || v === void 0 || !Number.isFinite(v)) out[r][c] = null;
|
|
58
|
+
else out[r][c] = sd === 0 ? 0 : (v - mean) / sd;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
async function handleData(q, res, genomes) {
|
|
64
|
+
const genome = genomes[q.genome];
|
|
65
|
+
if (!genome) throw "invalid genome";
|
|
66
|
+
const ds = genome.datasets[q.dslabel];
|
|
67
|
+
if (!ds) throw "invalid dslabel";
|
|
68
|
+
const cfg = ds.queries?.geneRanking;
|
|
69
|
+
if (!cfg || !cfg.rankings) throw "geneRanking not configured for this dataset";
|
|
70
|
+
if (!q.key) {
|
|
71
|
+
res.send({ keys: Object.keys(cfg.rankings) });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const relPath = cfg.rankings[q.key];
|
|
75
|
+
if (!relPath) throw "invalid key";
|
|
76
|
+
if (path.isAbsolute(relPath) || relPath.split(/[\\/]/).includes("..")) throw "invalid file path";
|
|
77
|
+
const absPath = path.resolve(serverconfig.tpmasterdir, relPath);
|
|
78
|
+
const tpRoot = path.resolve(serverconfig.tpmasterdir) + path.sep;
|
|
79
|
+
if (!absPath.startsWith(tpRoot)) throw "invalid file path";
|
|
80
|
+
const stat = await fs.promises.stat(absPath);
|
|
81
|
+
const cacheKey = `${q.genome}|${q.dslabel}|${q.key}`;
|
|
82
|
+
let entry = fileCache.get(cacheKey);
|
|
83
|
+
if (!entry || entry.mtimeMs !== stat.mtimeMs) {
|
|
84
|
+
entry = { parsed: await parseTsv(absPath), mtimeMs: stat.mtimeMs };
|
|
85
|
+
fileCache.set(cacheKey, entry);
|
|
86
|
+
}
|
|
87
|
+
res.send({ columns: entry.parsed.columns, rows: entry.parsed.rows });
|
|
88
|
+
}
|
|
89
|
+
async function handleCluster(q, res) {
|
|
90
|
+
const { row_names, col_names } = q;
|
|
91
|
+
if (!Array.isArray(q.matrix) || !Array.isArray(row_names) || !Array.isArray(col_names)) {
|
|
92
|
+
throw "matrix, row_names, and col_names are required";
|
|
93
|
+
}
|
|
94
|
+
if (q.matrix.length !== row_names.length) throw "matrix.length must equal row_names.length";
|
|
95
|
+
if (col_names.length < 2) throw "need at least 2 modalities to cluster";
|
|
96
|
+
const minAssays = Math.max(2, q.minAssays ?? 3);
|
|
97
|
+
const keptRows = [];
|
|
98
|
+
const keptNames = [];
|
|
99
|
+
for (let i = 0; i < q.matrix.length; i++) {
|
|
100
|
+
const row = q.matrix[i];
|
|
101
|
+
if (!Array.isArray(row) || row.length !== col_names.length) continue;
|
|
102
|
+
const nonNull = row.filter((v) => v !== null && v !== void 0 && Number.isFinite(v)).length;
|
|
103
|
+
if (nonNull < minAssays) continue;
|
|
104
|
+
keptRows.push(row.map((v) => v === null || v === void 0 || !Number.isFinite(v) ? null : v));
|
|
105
|
+
keptNames.push(row_names[i]);
|
|
106
|
+
}
|
|
107
|
+
if (keptRows.length < 3) throw `need at least 3 rows with \u2265${minAssays} non-null values for clustering`;
|
|
108
|
+
const zMatrix = zscorePerColumnIgnoringNull(keptRows, col_names.length);
|
|
109
|
+
const matrixForR = zMatrix.map((row) => row.map((v) => v === null ? 0 : v));
|
|
110
|
+
const clusterMethod = q.clusterMethod || "average";
|
|
111
|
+
const distanceMethod = q.distanceMethod || "euclidean";
|
|
112
|
+
if (!clusterMethodLst.find((i) => i.value == clusterMethod)) throw "Invalid cluster method";
|
|
113
|
+
if (!distanceMethodLst.find((i) => i.value == distanceMethod)) throw "Invalid distance method";
|
|
114
|
+
const inputData = {
|
|
115
|
+
matrix: matrixForR,
|
|
116
|
+
row_names: keptNames,
|
|
117
|
+
col_names,
|
|
118
|
+
cluster_method: clusterMethod,
|
|
119
|
+
distance_method: distanceMethod,
|
|
120
|
+
plot_image: false
|
|
121
|
+
};
|
|
122
|
+
const Routput = JSON.parse(await run_R("hclust.R", JSON.stringify(inputData)));
|
|
123
|
+
const rowOrderIdx = Routput.RowOrder.map((row) => keptNames.indexOf(row.name));
|
|
124
|
+
const orderedMatrix = rowOrderIdx.map((i) => zMatrix[i]);
|
|
125
|
+
res.send({
|
|
126
|
+
row: {
|
|
127
|
+
merge: Routput.RowMerge,
|
|
128
|
+
height: Routput.RowHeight,
|
|
129
|
+
order: Routput.RowOrder,
|
|
130
|
+
inputOrder: keptNames
|
|
131
|
+
},
|
|
132
|
+
usedRowNames: keptNames,
|
|
133
|
+
usedColNames: col_names,
|
|
134
|
+
matrix: orderedMatrix
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
init
|
|
139
|
+
};
|
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
import { termdbProteomePayload } from "#types/checkers";
|
|
2
1
|
import { get_ds_tdb } from "#src/termdb.js";
|
|
3
2
|
import * as utils from "#src/utils.js";
|
|
4
3
|
import { mayLimitSamples } from "#src/mds3.filter.js";
|
|
5
|
-
const api = {
|
|
6
|
-
endpoint: "termdb/proteome",
|
|
7
|
-
methods: {
|
|
8
|
-
get: {
|
|
9
|
-
...termdbProteomePayload,
|
|
10
|
-
init
|
|
11
|
-
},
|
|
12
|
-
post: {
|
|
13
|
-
...termdbProteomePayload,
|
|
14
|
-
init
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
4
|
function init({ genomes }) {
|
|
19
5
|
return async (req, res) => {
|
|
20
6
|
const q = req.query;
|
|
@@ -458,7 +444,7 @@ async function getProteomeValuesFromCohort(ds, param, q) {
|
|
|
458
444
|
return { term2sample2value, controlSampleIds, bySampleId };
|
|
459
445
|
}
|
|
460
446
|
export {
|
|
461
|
-
api,
|
|
462
447
|
countDistinctSamples,
|
|
448
|
+
init,
|
|
463
449
|
validate_query_proteome
|
|
464
450
|
};
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import { runChartPayload } from "#types/checkers";
|
|
2
1
|
import { getDateFromNumber, getNumberFromDate } from "#shared/terms.js";
|
|
2
|
+
function init({ genomes }) {
|
|
3
|
+
return async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const q = req.query;
|
|
6
|
+
const genome = genomes[q.genome];
|
|
7
|
+
if (!genome) throw new Error("invalid genome name");
|
|
8
|
+
const ds = genome.datasets?.[q.dslabel];
|
|
9
|
+
if (!ds) throw new Error("invalid ds");
|
|
10
|
+
const result = await getRunChart(q, ds);
|
|
11
|
+
res.send(result);
|
|
12
|
+
} catch (e) {
|
|
13
|
+
res.send(runChartErrorPayload(e.message || e));
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
3
17
|
function decimalYearToYearMonth(xRaw) {
|
|
4
18
|
const date = getDateFromNumber(xRaw);
|
|
5
19
|
const t = date.getTime();
|
|
@@ -26,19 +40,6 @@ function decimalYearToYearMonth(xRaw) {
|
|
|
26
40
|
if (!Number.isFinite(month) || month < 1 || month > 12) return null;
|
|
27
41
|
return { yearNum: year, monthNum: month };
|
|
28
42
|
}
|
|
29
|
-
const api = {
|
|
30
|
-
endpoint: "termdb/runChart",
|
|
31
|
-
methods: {
|
|
32
|
-
get: {
|
|
33
|
-
...runChartPayload,
|
|
34
|
-
init
|
|
35
|
-
},
|
|
36
|
-
post: {
|
|
37
|
-
...runChartPayload,
|
|
38
|
-
init
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
43
|
async function getRunChart(q, ds) {
|
|
43
44
|
const xTermId = q.xtw["$id"] ?? q.xtw.term?.id;
|
|
44
45
|
const yTermId = q.ytw ? q.ytw["$id"] ?? q.ytw.term?.id : void 0;
|
|
@@ -307,26 +308,11 @@ function buildOneSeries(aggregation, xTermId, yTermId, data) {
|
|
|
307
308
|
function runChartErrorPayload(message) {
|
|
308
309
|
return { error: String(message), series: [] };
|
|
309
310
|
}
|
|
310
|
-
function init({ genomes }) {
|
|
311
|
-
return async (req, res) => {
|
|
312
|
-
try {
|
|
313
|
-
const q = req.query;
|
|
314
|
-
const genome = genomes[q.genome];
|
|
315
|
-
if (!genome) throw new Error("invalid genome name");
|
|
316
|
-
const ds = genome.datasets?.[q.dslabel];
|
|
317
|
-
if (!ds) throw new Error("invalid ds");
|
|
318
|
-
const result = await getRunChart(q, ds);
|
|
319
|
-
res.send(result);
|
|
320
|
-
} catch (e) {
|
|
321
|
-
res.send(runChartErrorPayload(e.message || e));
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
311
|
export {
|
|
326
|
-
api,
|
|
327
312
|
buildFrequencyFromData,
|
|
328
313
|
buildRunChartFromData,
|
|
329
314
|
decimalYearToYearMonth,
|
|
330
315
|
getRunChart,
|
|
316
|
+
init,
|
|
331
317
|
runChartErrorPayload
|
|
332
318
|
};
|
|
@@ -1,26 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getData } from "../src/termdb.matrix.js";
|
|
1
|
+
import { getData } from "#src/termdb.matrix.js";
|
|
3
2
|
import path from "path";
|
|
4
|
-
import serverconfig from "
|
|
3
|
+
import serverconfig from "#src/serverconfig.js";
|
|
5
4
|
import { schemeCategory20, getColors, mclass, dt2label, morigin, isNumericTerm } from "#shared";
|
|
6
|
-
import { authApi } from "
|
|
5
|
+
import { authApi } from "#src/auth.js";
|
|
7
6
|
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
8
|
-
import { read_file } from "
|
|
9
|
-
import { getDescrStats } from "
|
|
10
|
-
import { isSingleCellTerm, SINGLECELL_GENE_EXPRESSION, SINGLECELL_CELLTYPE } from "#shared/terms.js";
|
|
11
|
-
const api = {
|
|
12
|
-
endpoint: "termdb/sampleScatter",
|
|
13
|
-
methods: {
|
|
14
|
-
get: {
|
|
15
|
-
...termdbSampleScatterPayload,
|
|
16
|
-
init
|
|
17
|
-
},
|
|
18
|
-
post: {
|
|
19
|
-
...termdbSampleScatterPayload,
|
|
20
|
-
init
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
};
|
|
7
|
+
import { read_file } from "#src/utils.js";
|
|
8
|
+
import { getDescrStats } from "./termdb.descrstats.ts";
|
|
24
9
|
const refColor = "#F5F5DC";
|
|
25
10
|
function init({ genomes }) {
|
|
26
11
|
return async function(req, res) {
|
|
@@ -31,8 +16,6 @@ function init({ genomes }) {
|
|
|
31
16
|
}
|
|
32
17
|
const g = genomes[q.genome];
|
|
33
18
|
const ds = g.datasets[q.dslabel];
|
|
34
|
-
if (q.singleCellPlot)
|
|
35
|
-
return getSingleCellScatter(req, res, ds);
|
|
36
19
|
let refSamples = [], cohortSamples;
|
|
37
20
|
const terms = [];
|
|
38
21
|
if (q.colorTW) terms.push(q.colorTW);
|
|
@@ -121,80 +104,6 @@ function init({ genomes }) {
|
|
|
121
104
|
}
|
|
122
105
|
};
|
|
123
106
|
}
|
|
124
|
-
async function getSingleCellScatter(req, res, ds) {
|
|
125
|
-
const q = req.query;
|
|
126
|
-
const { name, sample } = q.singleCellPlot;
|
|
127
|
-
try {
|
|
128
|
-
const tw = q.colorTW;
|
|
129
|
-
if (!tw || !isSingleCellTerm(tw.term))
|
|
130
|
-
throw new Error("colorTW must be provided and be a single cell term for single cell scatter plot");
|
|
131
|
-
const arg = { plots: [name], sample };
|
|
132
|
-
if (tw.term.type == SINGLECELL_GENE_EXPRESSION) arg.gene = tw.term.gene;
|
|
133
|
-
else if (tw.term.type == SINGLECELL_CELLTYPE) arg.colorBy = tw.term.name;
|
|
134
|
-
else throw new Error(`unsupported single cell term type: ${tw.term.type}`);
|
|
135
|
-
const data = await ds.queries.singleCell.data.get(arg);
|
|
136
|
-
const plot = data.plots[0];
|
|
137
|
-
const cells = [...plot.expCells, ...plot.noExpCells];
|
|
138
|
-
const groups = tw.q?.customset?.groups;
|
|
139
|
-
const cat2GrpName = /* @__PURE__ */ new Map();
|
|
140
|
-
if (groups) {
|
|
141
|
-
for (const group of groups) {
|
|
142
|
-
for (const value of Object.values(group.values)) {
|
|
143
|
-
cat2GrpName.set(value.key, group.name);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
const samples = cells.map((cell) => {
|
|
148
|
-
let category = cell.category;
|
|
149
|
-
const groupName = cat2GrpName.get(category);
|
|
150
|
-
if (groupName !== void 0) category = groupName;
|
|
151
|
-
const hidden = {
|
|
152
|
-
category: tw?.q?.hiddenValues ? category in tw.q.hiddenValues : false
|
|
153
|
-
};
|
|
154
|
-
return {
|
|
155
|
-
sample: cell.cellId,
|
|
156
|
-
sampleId: cell.cellId,
|
|
157
|
-
x: cell.x,
|
|
158
|
-
y: cell.y,
|
|
159
|
-
z: 0,
|
|
160
|
-
category,
|
|
161
|
-
shape: "Ref",
|
|
162
|
-
hidden,
|
|
163
|
-
geneExp: cell.geneExp
|
|
164
|
-
};
|
|
165
|
-
});
|
|
166
|
-
const [xMin, xMax, yMin, yMax] = samples.reduce(
|
|
167
|
-
(s, d) => [d.x < s[0] ? d.x : s[0], d.x > s[1] ? d.x : s[1], d.y < s[2] ? d.y : s[2], d.y > s[3] ? d.y : s[3]],
|
|
168
|
-
[samples[0].x, samples[0].x, samples[0].y, samples[0].y]
|
|
169
|
-
);
|
|
170
|
-
const categories = new Set(samples.map((s) => s.category));
|
|
171
|
-
const colorMap = {};
|
|
172
|
-
if (tw.term.type != SINGLECELL_GENE_EXPRESSION) {
|
|
173
|
-
const defaultK2c = getColors(categories.size);
|
|
174
|
-
const k2c = (category) => {
|
|
175
|
-
const dsTerm = ds.queries.singleCell?.terms ? ds.queries.singleCell.terms.find((t) => t.name == tw.term.name) : void 0;
|
|
176
|
-
return tw.term.values?.[category]?.color || dsTerm?.values?.[category]?.color || defaultK2c(category);
|
|
177
|
-
};
|
|
178
|
-
for (const category of categories) {
|
|
179
|
-
const color = k2c(category);
|
|
180
|
-
colorMap[category] = {
|
|
181
|
-
sampleCount: samples.filter((s) => s.category == category).length,
|
|
182
|
-
color,
|
|
183
|
-
key: category
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
const shapeLegend = [["Ref", { sampleCount: samples.length, shape: 0, key: "Ref" }]];
|
|
188
|
-
const colorLegend = Object.entries(colorMap);
|
|
189
|
-
res.send({
|
|
190
|
-
result: { Default: { samples, colorLegend, shapeLegend } },
|
|
191
|
-
range: { xMin, xMax, yMin, yMax }
|
|
192
|
-
});
|
|
193
|
-
} catch (e) {
|
|
194
|
-
console.log(e);
|
|
195
|
-
res.send({ error: e.message || e });
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
107
|
async function getSamples(ds, plot) {
|
|
199
108
|
if (!plot.filterableSamples) await loadFile(plot, ds);
|
|
200
109
|
return [readSamples(plot.referenceSamples), readSamples(plot.filterableSamples)];
|
|
@@ -518,7 +427,8 @@ async function trigger_getLowessCurve(q, res) {
|
|
|
518
427
|
return res.send(lowessCurve);
|
|
519
428
|
}
|
|
520
429
|
export {
|
|
521
|
-
|
|
430
|
+
init,
|
|
522
431
|
mayInitiateScatterplots,
|
|
432
|
+
refColor,
|
|
523
433
|
trigger_getLowessCurve
|
|
524
434
|
};
|