@sjcrh/proteinpaint-server 2.189.0 → 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.
Files changed (82) hide show
  1. package/package.json +9 -12
  2. package/routes/aiProjectAdmin.js +2 -28
  3. package/routes/aiProjectSelectedWSImages.js +2 -16
  4. package/routes/brainImaging.js +1 -15
  5. package/routes/brainImagingSamples.js +1 -15
  6. package/routes/burden.js +1 -15
  7. package/routes/correlationVolcano.js +1 -15
  8. package/routes/dataset.js +1 -16
  9. package/routes/deleteWSITileSelection.js +2 -12
  10. package/routes/dsdata.js +1 -19
  11. package/routes/gdc.grin2.list.js +1 -15
  12. package/routes/gdc.grin2.run.js +1 -15
  13. package/routes/gdc.maf.js +1 -15
  14. package/routes/gdc.mafBuild.js +1 -15
  15. package/routes/genesetEnrichment.js +129 -97
  16. package/routes/grin2.js +110 -79
  17. package/routes/saveWSIAnnotation.js +2 -13
  18. package/routes/termdb.DE.js +137 -54
  19. package/routes/termdb.categories.js +2 -16
  20. package/routes/termdb.chat.js +169 -1076
  21. package/routes/termdb.cluster.js +5 -16
  22. package/routes/termdb.config.js +12 -17
  23. package/routes/termdb.descrstats.js +2 -16
  24. package/routes/termdb.diffMeth.js +100 -21
  25. package/routes/termdb.geneRanking.js +139 -0
  26. package/routes/termdb.proteome.js +1 -15
  27. package/routes/termdb.runChart.js +16 -30
  28. package/routes/termdb.sampleScatter.js +7 -97
  29. package/routes/termdb.singleCellPlots.js +159 -0
  30. package/routes/termdb.singlecellSamples.js +6 -16
  31. package/routes/termdb.violinBox.js +1 -15
  32. package/routes/wsimages.js +1 -16
  33. package/src/app.js +3984 -4103
  34. package/routes/_template_.js +0 -33
  35. package/routes/aiProjectTrainModel.js +0 -68
  36. package/routes/alphaGenome.js +0 -41
  37. package/routes/alphaGenomeTypes.js +0 -36
  38. package/routes/dzimages.js +0 -55
  39. package/routes/gene2canonicalisoform.js +0 -37
  40. package/routes/genelookup.js +0 -32
  41. package/routes/genesetOverrepresentation.js +0 -49
  42. package/routes/genomes.js +0 -150
  43. package/routes/healthcheck.js +0 -35
  44. package/routes/hicdata.js +0 -74
  45. package/routes/hicgenome.js +0 -75
  46. package/routes/hicstat.js +0 -35
  47. package/routes/img.js +0 -46
  48. package/routes/isoformlst.js +0 -48
  49. package/routes/ntseq.js +0 -36
  50. package/routes/pdomain.js +0 -53
  51. package/routes/profile.barchart2.js +0 -114
  52. package/routes/profile.forms2.js +0 -107
  53. package/routes/profile.polar2.js +0 -101
  54. package/routes/profile.radar2.js +0 -112
  55. package/routes/profile.radarFacility2.js +0 -148
  56. package/routes/sampledzimages.js +0 -48
  57. package/routes/samplewsimages.js +0 -60
  58. package/routes/snp.js +0 -98
  59. package/routes/termdb.chat2.js +0 -217
  60. package/routes/termdb.chat3.js +0 -209
  61. package/routes/termdb.cohort.summary.js +0 -37
  62. package/routes/termdb.cohorts.js +0 -41
  63. package/routes/termdb.dapVolcano.js +0 -80
  64. package/routes/termdb.dmr.js +0 -93
  65. package/routes/termdb.filterTermValues.js +0 -89
  66. package/routes/termdb.isoformAvailability.js +0 -35
  67. package/routes/termdb.numericcategories.js +0 -46
  68. package/routes/termdb.percentile.js +0 -66
  69. package/routes/termdb.profileFormScores.js +0 -92
  70. package/routes/termdb.profileScores.js +0 -113
  71. package/routes/termdb.rootterm.js +0 -39
  72. package/routes/termdb.sampleImages.js +0 -63
  73. package/routes/termdb.singleSampleMutation.js +0 -75
  74. package/routes/termdb.singlecellDEgenes.js +0 -55
  75. package/routes/termdb.singlecellData.js +0 -39
  76. package/routes/termdb.termchildren.js +0 -42
  77. package/routes/termdb.termsbyids.js +0 -50
  78. package/routes/termdb.topMutatedGenes.js +0 -127
  79. package/routes/termdb.topTermsByType.js +0 -96
  80. package/routes/termdb.topVariablyExpressedGenes.js +0 -132
  81. package/routes/tileserver.js +0 -68
  82. package/routes/wsisamples.js +0 -71
@@ -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(q.dataType)) {
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
- api,
453
+ init,
465
454
  validateQueryIsoformExpression,
466
455
  validate_query_geneExpression
467
456
  };
@@ -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
- api,
351
- getDsAllowedTermTypes
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 { diffMethPayload } from "#types/checkers";
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 { readCacheFileOrRecompute, resolveDaContext, resolveDmSampleGroups } from "../src/diffAnalysis.ts";
4
- const api = {
5
- endpoint: "termdb/diffMeth",
6
- methods: {
7
- get: {
8
- ...diffMethPayload,
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 readCacheFileOrRecompute({ daRequest: q, genomes });
36
- if (result.kind !== "DM") throw new Error("expected DM result from readCacheFileOrRecompute");
37
- const rendered = await renderVolcano(result.promoterData, q.volcanoRender);
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
- api
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 { termdbSampleScatterPayload } from "#types/checkers";
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 "../src/serverconfig.js";
3
+ import serverconfig from "#src/serverconfig.js";
5
4
  import { schemeCategory20, getColors, mclass, dt2label, morigin, isNumericTerm } from "#shared";
6
- import { authApi } from "../src/auth.js";
5
+ import { authApi } from "#src/auth.js";
7
6
  import { run_R } from "@sjcrh/proteinpaint-r";
8
- import { read_file } from "../src/utils.js";
9
- import { getDescrStats } from "#routes/termdb.descrstats.ts";
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
- api,
430
+ init,
522
431
  mayInitiateScatterplots,
432
+ refColor,
523
433
  trigger_getLowessCurve
524
434
  };