@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.
@@ -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 data = await ds.queries.singleCell.data.get({ plots: [name], sample });
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
- geneExpMap = await ds.queries.singleCell.geneExpression.get({ sample: q.sample, gene: q.gene });
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(serverconfig.tpmasterdir, plot.folder, (q.sample.eID || q.sample.sID) + plot.fileSuffix);
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?.[plot.name]) || plot.colorColumns[0];
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.eID || q.sample.sID) + ".h5");
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