@sjcrh/proteinpaint-server 2.177.0 → 2.178.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 +23 -2
- package/package.json +6 -6
- package/routes/grin2.js +25 -6
- package/routes/termdb.chat.js +101 -30
- package/routes/termdb.chat2.js +153 -0
- package/routes/termdb.cluster.js +10 -0
- package/routes/termdb.config.js +19 -4
- package/routes/termdb.diffMeth.js +180 -0
- package/routes/termdb.dmr.js +48 -0
- package/routes/termdb.sampleScatter.js +12 -2
- package/routes/termdb.singlecellSamples.js +40 -4
- package/src/app.js +2885 -692
- package/src/serverconfig.js +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { diffMethPayload } from "#types/checkers";
|
|
2
|
+
import { getData } from "../src/termdb.matrix.js";
|
|
3
|
+
import { get_ds_tdb } from "../src/termdb.js";
|
|
4
|
+
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
5
|
+
import { mayLog } from "#src/helpers.ts";
|
|
6
|
+
import { formatElapsedTime } from "#shared";
|
|
7
|
+
const api = {
|
|
8
|
+
endpoint: "termdb/diffMeth",
|
|
9
|
+
methods: {
|
|
10
|
+
get: {
|
|
11
|
+
...diffMethPayload,
|
|
12
|
+
init
|
|
13
|
+
},
|
|
14
|
+
post: {
|
|
15
|
+
...diffMethPayload,
|
|
16
|
+
init
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function init({ genomes }) {
|
|
21
|
+
return async (req, res) => {
|
|
22
|
+
try {
|
|
23
|
+
const q = req.query;
|
|
24
|
+
const genome = genomes[q.genome];
|
|
25
|
+
if (!genome) throw new Error("invalid genome");
|
|
26
|
+
const [ds] = get_ds_tdb(genome, q);
|
|
27
|
+
let term_results = [];
|
|
28
|
+
if (q.tw) {
|
|
29
|
+
term_results = await getData({ filter: q.filter, filter0: q.filter0, terms: [q.tw] }, ds);
|
|
30
|
+
if (term_results.error) throw new Error(term_results.error);
|
|
31
|
+
}
|
|
32
|
+
let term_results2 = [];
|
|
33
|
+
if (q.tw2) {
|
|
34
|
+
term_results2 = await getData({ filter: q.filter, filter0: q.filter0, terms: [q.tw2] }, ds);
|
|
35
|
+
if (term_results2.error) throw new Error(term_results2.error);
|
|
36
|
+
}
|
|
37
|
+
const results = await run_diffMeth(req.query, ds, term_results, term_results2);
|
|
38
|
+
if (!results || !results.data) throw new Error("No data available");
|
|
39
|
+
res.send(results);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
res.send({ status: "error", error: e.message || e });
|
|
42
|
+
if (e instanceof Error && e.stack) console.log(e);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function run_diffMeth(param, ds, term_results, term_results2) {
|
|
47
|
+
if (param.samplelst?.groups?.length != 2) throw new Error(".samplelst.groups.length!=2");
|
|
48
|
+
if (param.samplelst.groups[0].values?.length < 1) throw new Error("samplelst.groups[0].values.length<1");
|
|
49
|
+
if (param.samplelst.groups[1].values?.length < 1) throw new Error("samplelst.groups[1].values.length<1");
|
|
50
|
+
const q = ds.queries.dnaMethylation?.promoter;
|
|
51
|
+
if (!q) throw new Error("ds.queries.dnaMethylation.promoter is not configured");
|
|
52
|
+
if (!q.file) throw new Error("ds.queries.dnaMethylation.promoter.file is missing");
|
|
53
|
+
const group1names = [];
|
|
54
|
+
const conf1_group1 = [];
|
|
55
|
+
const conf2_group1 = [];
|
|
56
|
+
for (const s of param.samplelst.groups[0].values) {
|
|
57
|
+
if (!Number.isInteger(s.sampleId)) continue;
|
|
58
|
+
const n = ds.cohort.termdb.q.id2sampleName(s.sampleId);
|
|
59
|
+
if (!n) continue;
|
|
60
|
+
if (!q.allSampleSet.has(n)) continue;
|
|
61
|
+
if (param.tw && param.tw2) {
|
|
62
|
+
if (term_results.samples[s.sampleId] && term_results2.samples[s.sampleId]) {
|
|
63
|
+
conf1_group1.push(
|
|
64
|
+
param.tw.q.mode == "continuous" ? term_results.samples[s.sampleId][param.tw.$id]["value"] : term_results.samples[s.sampleId][param.tw.$id]["key"]
|
|
65
|
+
);
|
|
66
|
+
conf2_group1.push(
|
|
67
|
+
param.tw2.q.mode == "continuous" ? term_results2.samples[s.sampleId][param.tw2.$id]["value"] : term_results2.samples[s.sampleId][param.tw2.$id]["key"]
|
|
68
|
+
);
|
|
69
|
+
group1names.push(n);
|
|
70
|
+
}
|
|
71
|
+
} else if (param.tw && !param.tw2) {
|
|
72
|
+
if (term_results.samples[s.sampleId]) {
|
|
73
|
+
conf1_group1.push(
|
|
74
|
+
param.tw.q.mode == "continuous" ? term_results.samples[s.sampleId][param.tw.$id]["value"] : term_results.samples[s.sampleId][param.tw.$id]["key"]
|
|
75
|
+
);
|
|
76
|
+
group1names.push(n);
|
|
77
|
+
}
|
|
78
|
+
} else if (!param.tw && param.tw2) {
|
|
79
|
+
if (term_results2.samples[s.sampleId]) {
|
|
80
|
+
conf2_group1.push(
|
|
81
|
+
param.tw2.q.mode == "continuous" ? term_results2.samples[s.sampleId][param.tw2.$id]["value"] : term_results2.samples[s.sampleId][param.tw2.$id]["key"]
|
|
82
|
+
);
|
|
83
|
+
group1names.push(n);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
group1names.push(n);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const group2names = [];
|
|
90
|
+
const conf1_group2 = [];
|
|
91
|
+
const conf2_group2 = [];
|
|
92
|
+
for (const s of param.samplelst.groups[1].values) {
|
|
93
|
+
if (!Number.isInteger(s.sampleId)) continue;
|
|
94
|
+
const n = ds.cohort.termdb.q.id2sampleName(s.sampleId);
|
|
95
|
+
if (!n) continue;
|
|
96
|
+
if (!q.allSampleSet.has(n)) continue;
|
|
97
|
+
if (param.tw && param.tw2) {
|
|
98
|
+
if (term_results.samples[s.sampleId] && term_results2.samples[s.sampleId]) {
|
|
99
|
+
conf1_group2.push(
|
|
100
|
+
param.tw.q.mode == "continuous" ? term_results.samples[s.sampleId][param.tw.$id]["value"] : term_results.samples[s.sampleId][param.tw.$id]["key"]
|
|
101
|
+
);
|
|
102
|
+
conf2_group2.push(
|
|
103
|
+
param.tw2.q.mode == "continuous" ? term_results2.samples[s.sampleId][param.tw2.$id]["value"] : term_results2.samples[s.sampleId][param.tw2.$id]["key"]
|
|
104
|
+
);
|
|
105
|
+
group2names.push(n);
|
|
106
|
+
}
|
|
107
|
+
} else if (param.tw && !param.tw2) {
|
|
108
|
+
if (term_results.samples[s.sampleId]) {
|
|
109
|
+
conf1_group2.push(
|
|
110
|
+
param.tw.q.mode == "continuous" ? term_results.samples[s.sampleId][param.tw.$id]["value"] : term_results.samples[s.sampleId][param.tw.$id]["key"]
|
|
111
|
+
);
|
|
112
|
+
group2names.push(n);
|
|
113
|
+
}
|
|
114
|
+
} else if (!param.tw && param.tw2) {
|
|
115
|
+
if (term_results2.samples[s.sampleId]) {
|
|
116
|
+
conf2_group2.push(
|
|
117
|
+
param.tw2.q.mode == "continuous" ? term_results2.samples[s.sampleId][param.tw2.$id]["value"] : term_results2.samples[s.sampleId][param.tw2.$id]["key"]
|
|
118
|
+
);
|
|
119
|
+
group2names.push(n);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
group2names.push(n);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const sample_size1 = group1names.length;
|
|
126
|
+
const sample_size2 = group2names.length;
|
|
127
|
+
const alerts = validateGroups(sample_size1, sample_size2, group1names, group2names);
|
|
128
|
+
if (param.preAnalysis) {
|
|
129
|
+
const group1Name = param.samplelst.groups[0].name;
|
|
130
|
+
const group2Name = param.samplelst.groups[1].name;
|
|
131
|
+
return {
|
|
132
|
+
data: {
|
|
133
|
+
[group1Name]: sample_size1,
|
|
134
|
+
[group2Name]: sample_size2,
|
|
135
|
+
...alerts.length ? { alert: alerts.join(" | ") } : {}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (alerts.length) throw new Error(alerts.join(" | "));
|
|
140
|
+
const diffMethInput = {
|
|
141
|
+
case: group2names.join(","),
|
|
142
|
+
control: group1names.join(","),
|
|
143
|
+
input_file: q.file,
|
|
144
|
+
min_samples_per_group: param.min_samples_per_group
|
|
145
|
+
};
|
|
146
|
+
if (param.tw) {
|
|
147
|
+
diffMethInput.conf1 = [...conf1_group2, ...conf1_group1];
|
|
148
|
+
diffMethInput.conf1_mode = param.tw.q.mode;
|
|
149
|
+
if (new Set(diffMethInput.conf1).size === 1) {
|
|
150
|
+
throw new Error("Confounding variable 1 has only one value");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (param.tw2) {
|
|
154
|
+
diffMethInput.conf2 = [...conf2_group2, ...conf2_group1];
|
|
155
|
+
diffMethInput.conf2_mode = param.tw2.q.mode;
|
|
156
|
+
if (new Set(diffMethInput.conf2).size === 1) {
|
|
157
|
+
throw new Error("Confounding variable 2 has only one value");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const time1 = Date.now();
|
|
161
|
+
const result = JSON.parse(await run_R("diffMeth.R", JSON.stringify(diffMethInput)));
|
|
162
|
+
mayLog("Time taken to run diffMeth:", formatElapsedTime(Date.now() - time1));
|
|
163
|
+
const output = {
|
|
164
|
+
data: result.promoter_data,
|
|
165
|
+
sample_size1,
|
|
166
|
+
sample_size2
|
|
167
|
+
};
|
|
168
|
+
return output;
|
|
169
|
+
}
|
|
170
|
+
function validateGroups(sample_size1, sample_size2, group1names, group2names) {
|
|
171
|
+
const alerts = [];
|
|
172
|
+
if (sample_size1 < 1) alerts.push("sample size of group1 < 1");
|
|
173
|
+
if (sample_size2 < 1) alerts.push("sample size of group2 < 1");
|
|
174
|
+
const commonnames = group1names.filter((x) => group2names.includes(x));
|
|
175
|
+
if (commonnames.length) alerts.push(`Common elements found between both groups: ${commonnames.join(", ")}`);
|
|
176
|
+
return alerts;
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
api
|
|
180
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TermdbDmrPayload } from "#types/checkers";
|
|
2
|
+
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
3
|
+
import { invalidcoord } from "#shared/common.js";
|
|
4
|
+
const api = {
|
|
5
|
+
endpoint: "termdb/dmr",
|
|
6
|
+
methods: {
|
|
7
|
+
get: {
|
|
8
|
+
...TermdbDmrPayload,
|
|
9
|
+
init
|
|
10
|
+
},
|
|
11
|
+
post: {
|
|
12
|
+
...TermdbDmrPayload,
|
|
13
|
+
init
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function init({ genomes }) {
|
|
18
|
+
return async (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const q = req.query;
|
|
21
|
+
const genome = genomes[q.genome];
|
|
22
|
+
if (!genome) throw new Error("invalid genome");
|
|
23
|
+
const ds = genome.datasets?.[q.dslabel];
|
|
24
|
+
if (!ds) throw new Error("invalid ds");
|
|
25
|
+
if (!ds.queries?.dnaMethylation) throw new Error("not supported");
|
|
26
|
+
if (!Array.isArray(q.group1) || q.group1.length == 0) throw new Error("group1 not non empty array");
|
|
27
|
+
if (!Array.isArray(q.group2) || q.group2.length == 0) throw new Error("group2 not non empty array");
|
|
28
|
+
if (invalidcoord(genome, q.chr, q.start, q.stop)) throw new Error("invalid chr/start/stop");
|
|
29
|
+
const arg = {
|
|
30
|
+
group1: q.group1,
|
|
31
|
+
group2: q.group2,
|
|
32
|
+
file: ds.queries.dnaMethylation.file,
|
|
33
|
+
// todo change file to mValueFile
|
|
34
|
+
chr: q.chr,
|
|
35
|
+
start: q.start,
|
|
36
|
+
stop: q.stop
|
|
37
|
+
};
|
|
38
|
+
const result = JSON.parse(await run_R("dmr.R", JSON.stringify(arg)));
|
|
39
|
+
if (result.error) throw new Error(result.error);
|
|
40
|
+
res.send(result);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
res.send({ error: e.message || e });
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
api
|
|
48
|
+
};
|
|
@@ -119,9 +119,15 @@ function init({ genomes }) {
|
|
|
119
119
|
async function getSingleCellScatter(req, res, ds) {
|
|
120
120
|
const q = req.query;
|
|
121
121
|
const { name, sample } = q.singleCellPlot;
|
|
122
|
-
const tw = q.colorTW;
|
|
123
122
|
try {
|
|
124
|
-
const
|
|
123
|
+
const tw = q.colorTW;
|
|
124
|
+
const arg = { plots: [name], sample };
|
|
125
|
+
if (tw) {
|
|
126
|
+
if (tw.term.type == "singleCellGeneExpression") arg.gene = tw.term.gene;
|
|
127
|
+
else if (tw.term.type == "singleCellCellType") arg.colorBy = tw.term.name;
|
|
128
|
+
else throw new Error("unsupported tw");
|
|
129
|
+
}
|
|
130
|
+
const data = await ds.queries.singleCell.data.get(arg);
|
|
125
131
|
const plot = data.plots[0];
|
|
126
132
|
const cells = [...plot.expCells, ...plot.noExpCells];
|
|
127
133
|
const samples = cells.map((cell) => {
|
|
@@ -464,6 +470,10 @@ async function loadFile(p, ds) {
|
|
|
464
470
|
);
|
|
465
471
|
}
|
|
466
472
|
async function mayInitiateScatterplots(ds) {
|
|
473
|
+
if (ds.scatterplots) {
|
|
474
|
+
ds.cohort.scatterplots = ds.scatterplots;
|
|
475
|
+
delete ds.scatterplots;
|
|
476
|
+
}
|
|
467
477
|
if (!ds.cohort.scatterplots) return;
|
|
468
478
|
if (typeof ds.cohort.scatterplots.get == "function") {
|
|
469
479
|
}
|
|
@@ -9,6 +9,7 @@ import { termdbSingleCellSamplesPayload } from "#types/checkers";
|
|
|
9
9
|
import { validate_query_singleCell_DEgenes } from "./termdb.singlecellDEgenes.ts";
|
|
10
10
|
import { gdc_validate_query_singleCell_data } from "#src/mds3.gdc.js";
|
|
11
11
|
import ky from "ky";
|
|
12
|
+
import { TermTypes } from "#shared/terms.js";
|
|
12
13
|
const api = {
|
|
13
14
|
endpoint: "termdb/singlecellSamples",
|
|
14
15
|
methods: {
|
|
@@ -59,6 +60,7 @@ async function validate_query_singleCell(ds, genome) {
|
|
|
59
60
|
} else {
|
|
60
61
|
throw new Error("unknown singleCell.data.src");
|
|
61
62
|
}
|
|
63
|
+
colorColumn2terms(ds.queries.singleCell.data.plots, ds);
|
|
62
64
|
if (q.geneExpression) {
|
|
63
65
|
if (typeof q.geneExpression != "object") throw new Error("singleCell.geneExpression not object");
|
|
64
66
|
if (q.geneExpression.src == "native") {
|
|
@@ -98,6 +100,7 @@ async function validateSamplesNative(S, D, ds) {
|
|
|
98
100
|
if (sid == void 0) throw new Error(`singlecell.sample: unknown sample name ${sampleName}`);
|
|
99
101
|
samples.set(sid, { sample: sampleName });
|
|
100
102
|
}
|
|
103
|
+
if (!plot.colorColumns || plot.colorColumns.length == 0) continue;
|
|
101
104
|
}
|
|
102
105
|
if (S.sampleColumns) {
|
|
103
106
|
for (const { termid } of S.sampleColumns) {
|
|
@@ -126,11 +129,16 @@ function validateDataNative(D, ds) {
|
|
|
126
129
|
const plots = [];
|
|
127
130
|
let geneExpMap;
|
|
128
131
|
if (ds.queries.singleCell.geneExpression && q.gene) {
|
|
129
|
-
|
|
132
|
+
const sample = q.sample || q.singleCellPlot.sample;
|
|
133
|
+
geneExpMap = await ds.queries.singleCell.geneExpression.get({ sample, gene: q.gene });
|
|
130
134
|
}
|
|
131
135
|
for (const plot of D.plots) {
|
|
132
136
|
if (!q.plots.includes(plot.name)) continue;
|
|
133
|
-
const tsvfile = path.join(
|
|
137
|
+
const tsvfile = path.join(
|
|
138
|
+
serverconfig.tpmasterdir,
|
|
139
|
+
plot.folder,
|
|
140
|
+
(q.sample?.eID || q.sample?.sID) + plot.fileSuffix
|
|
141
|
+
);
|
|
134
142
|
if (!file2Lines[tsvfile]) {
|
|
135
143
|
await file_is_readable(tsvfile);
|
|
136
144
|
const text = await read_file(tsvfile);
|
|
@@ -146,7 +154,7 @@ function validateDataNative(D, ds) {
|
|
|
146
154
|
}
|
|
147
155
|
file2Lines[tsvfile] = lines2;
|
|
148
156
|
}
|
|
149
|
-
const colorColumn = plot.colorColumns.find((c) => c.name == q.colorBy
|
|
157
|
+
const colorColumn = plot.colorColumns.find((c) => c.name == q.colorBy) || plot.colorColumns[0];
|
|
150
158
|
const expCells = [];
|
|
151
159
|
const noExpCells = [];
|
|
152
160
|
for (const l of file2Lines[tsvfile]) {
|
|
@@ -182,7 +190,7 @@ function validateDataNative(D, ds) {
|
|
|
182
190
|
function validateGeneExpressionNative(G) {
|
|
183
191
|
G.sample2gene2expressionBins = {};
|
|
184
192
|
G.get = async (q) => {
|
|
185
|
-
const h5file = path.join(serverconfig.tpmasterdir, G.folder, (q.sample
|
|
193
|
+
const h5file = path.join(serverconfig.tpmasterdir, G.folder, (q.sample?.eID || q.sample?.sID) + ".h5");
|
|
186
194
|
await file_is_readable(h5file);
|
|
187
195
|
const query_gene = q.gene;
|
|
188
196
|
if (!query_gene) {
|
|
@@ -233,6 +241,34 @@ function gdc_validateGeneExpression(G, ds, genome) {
|
|
|
233
241
|
}
|
|
234
242
|
};
|
|
235
243
|
}
|
|
244
|
+
function colorColumn2terms(plots, ds) {
|
|
245
|
+
const termSet = /* @__PURE__ */ new Set();
|
|
246
|
+
for (const plot of plots) {
|
|
247
|
+
const tmpTerms = plot.colorColumns.map((c) => {
|
|
248
|
+
const baseValues = c.colorMap ? Object.keys(c.colorMap) : [];
|
|
249
|
+
return {
|
|
250
|
+
name: c.name,
|
|
251
|
+
isleaf: true,
|
|
252
|
+
/** TODO: possible term may apply to multiple plots.
|
|
253
|
+
* May need to change to plots: [] */
|
|
254
|
+
plot: plot.name,
|
|
255
|
+
type: TermTypes.SINGLECELL_CELLTYPE,
|
|
256
|
+
groupsetting: {},
|
|
257
|
+
values: baseValues.reduce((acc, v) => {
|
|
258
|
+
const alias = c?.aliases?.[v];
|
|
259
|
+
acc[v] = {
|
|
260
|
+
key: v,
|
|
261
|
+
label: alias || v,
|
|
262
|
+
color: c.colorMap?.[v] || "#000000"
|
|
263
|
+
};
|
|
264
|
+
return acc;
|
|
265
|
+
}, {})
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
tmpTerms.forEach((term) => termSet.add(term));
|
|
269
|
+
}
|
|
270
|
+
ds.queries.singleCell.terms = [...termSet];
|
|
271
|
+
}
|
|
236
272
|
export {
|
|
237
273
|
api,
|
|
238
274
|
validate_query_singleCell
|