@sjcrh/proteinpaint-server 2.190.2 → 2.191.1

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.
@@ -1,226 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import serverconfig from "#src/serverconfig.js";
4
- import { run_python } from "@sjcrh/proteinpaint-python";
5
- import { run_rust } from "@sjcrh/proteinpaint-rust";
6
- import { mayLog } from "#src/helpers.ts";
7
- import { formatElapsedTime } from "#shared";
8
- import { getDeCacheResult } from "../routes/termdb.DE.ts";
9
- import { getDmCacheResult } from "../routes/termdb.diffMeth.ts";
10
- import { cacheOrRecompute } from "#src/utils/cacheOrRecompute.ts";
11
- import { get_ds_tdb } from "#src/termdb.js";
12
- function init({ genomes }) {
13
- return async (req, res) => {
14
- try {
15
- const q = req.query;
16
- const results = await run_genesetEnrichment_analysis(q, genomes);
17
- if (!q.geneset_name) {
18
- if (typeof results != "object") throw new Error("gsea result is not object");
19
- res.send(results);
20
- return;
21
- }
22
- if (typeof results === "object" && results.error) {
23
- res.send({ status: "error", error: results.error });
24
- return;
25
- }
26
- if (typeof results != "string") throw new Error("gsea result is not string");
27
- res.sendFile(results, (err) => {
28
- fs.unlink(results, () => {
29
- });
30
- if (err) {
31
- res.status(404).send("Image not found");
32
- }
33
- });
34
- } catch (e) {
35
- res.status(e.status || 500).send({ status: "error", error: e.message || e, code: e.code });
36
- if (e.stack) console.log(e.stack);
37
- }
38
- };
39
- }
40
- async function run_genesetEnrichment_analysis(q, genomes) {
41
- if (!genomes[q.genome].termdbs) throw new Error("termdb database is not available for " + q.genome);
42
- if (q.fetchDE) {
43
- const { genes, fold_change } = await resolveGseaGenesAndFoldChange({ q, genomes });
44
- return { data: { genes, fold_change } };
45
- }
46
- if (q.method == "blitzgsea") {
47
- if (q.geneset_name) return await computeGseaImage({ q, genomes });
48
- return await computeGseaInitial({ q, genomes });
49
- }
50
- if (q.method == "cerno") {
51
- const { genes, fold_change } = await resolveGseaGenesAndFoldChange({ q, genomes });
52
- const genesetenrichment_input = {
53
- genes,
54
- fold_change,
55
- db: genomes[q.genome].termdbs.msigdb.cohort.db.connection.name,
56
- geneset_group: q.geneSetGroup,
57
- genedb: path.join(serverconfig.tpmasterdir, genomes[q.genome].genedb.dbfile),
58
- filter_non_coding_genes: q.filter_non_coding_genes
59
- };
60
- const time1 = (/* @__PURE__ */ new Date()).valueOf();
61
- const gsea_output = JSON.parse(await run_rust("cerno", JSON.stringify(genesetenrichment_input)));
62
- mayLog("Time taken to run CERNO:", formatElapsedTime(Date.now() - time1));
63
- return gsea_output;
64
- }
65
- throw new Error("Unknown method:" + q.method);
66
- }
67
- function gseaKeyInputs(q, genes, fold_change) {
68
- return {
69
- genes,
70
- fold_change,
71
- geneSetGroup: q.geneSetGroup,
72
- num_permutations: q.num_permutations,
73
- filter_non_coding_genes: q.filter_non_coding_genes
74
- };
75
- }
76
- async function getGseaCacheResult({
77
- q,
78
- genomes
79
- }) {
80
- const { genes, fold_change } = await resolveGseaGenesAndFoldChange({ q, genomes });
81
- const cacheArg = gseaKeyInputs(q, genes, fold_change);
82
- const { result, cacheId } = await cacheOrRecompute({
83
- computeArgument: cacheArg,
84
- cacheSubdir: "gsea",
85
- computeFresh: async () => {
86
- const { table, pickleB64 } = await runGseaPythonForTable({ q, genomes, cacheArg });
87
- const cacheResult = { table, pickleB64 };
88
- return cacheResult;
89
- }
90
- });
91
- return { result, cacheId };
92
- }
93
- async function computeGseaInitial({
94
- q,
95
- genomes
96
- }) {
97
- const { result } = await getGseaCacheResult({ q, genomes });
98
- return result.table;
99
- }
100
- const pendingImageRequests = /* @__PURE__ */ new Map();
101
- async function computeGseaImage({
102
- q,
103
- genomes
104
- }) {
105
- const { result, cacheId } = await getGseaCacheResult({ q, genomes });
106
- const dedupKey = `${cacheId}:${q.geneset_name}`;
107
- const inFlight = pendingImageRequests.get(dedupKey);
108
- if (inFlight) return inFlight;
109
- const work = runGseaPythonForImage({
110
- q,
111
- genomes,
112
- pickleB64: result.pickleB64
113
- }).finally(() => pendingImageRequests.delete(dedupKey));
114
- pendingImageRequests.set(dedupKey, work);
115
- return work;
116
- }
117
- async function runGseaPythonForTable({
118
- q,
119
- genomes,
120
- cacheArg
121
- }) {
122
- const pyInput = buildPyInput({ ...q, geneset_name: void 0 }, genomes, cacheArg);
123
- const time1 = (/* @__PURE__ */ new Date()).valueOf();
124
- const gsea_output = await run_python("gsea.py", "/" + JSON.stringify(pyInput));
125
- mayLog("Time taken to run blitzgsea:", formatElapsedTime(Date.now() - time1));
126
- for (const line of gsea_output.split("\n")) {
127
- if (line.startsWith("result: ")) {
128
- const parsed = JSON.parse(line.replace("result: ", ""));
129
- if (parsed?.error) throw new Error(parsed.error);
130
- const { pickle_b64, ...table } = parsed;
131
- if (!pickle_b64) throw new Error("gsea.py result missing pickle_b64");
132
- return { table, pickleB64: pickle_b64 };
133
- }
134
- mayLog(line);
135
- }
136
- throw new Error("gsea.py did not emit a result line on the initial path");
137
- }
138
- async function runGseaPythonForImage({
139
- q,
140
- genomes,
141
- pickleB64
142
- }) {
143
- const { genes, fold_change } = await resolveGseaGenesAndFoldChange({ q, genomes });
144
- const cacheArg = gseaKeyInputs(q, genes, fold_change);
145
- const pyInput = buildPyInput(q, genomes, cacheArg, pickleB64);
146
- const time1 = (/* @__PURE__ */ new Date()).valueOf();
147
- const gsea_output = await run_python("gsea.py", "/" + JSON.stringify(pyInput));
148
- mayLog("Time taken to render gsea image:", formatElapsedTime(Date.now() - time1));
149
- for (const line of gsea_output.split("\n")) {
150
- if (line.startsWith("image: ")) {
151
- const parsed = JSON.parse(line.replace("image: ", ""));
152
- return path.join(serverconfig.cachedir, "gsea", parsed.image_file);
153
- }
154
- if (line.startsWith("result: ")) {
155
- const parsed = JSON.parse(line.replace("result: ", ""));
156
- if (parsed?.error) return { error: parsed.error };
157
- }
158
- mayLog(line);
159
- }
160
- throw new Error("gsea.py did not emit an image line on the detail path");
161
- }
162
- function buildPyInput(q, genomes, cacheArg, pickleB64) {
163
- return {
164
- genes: cacheArg.genes,
165
- fold_change: cacheArg.fold_change,
166
- db: genomes[q.genome].termdbs.msigdb.cohort.db.connection.name,
167
- geneset_group: cacheArg.geneSetGroup,
168
- genedb: path.join(serverconfig.tpmasterdir, genomes[q.genome].genedb.dbfile),
169
- filter_non_coding_genes: cacheArg.filter_non_coding_genes,
170
- cachedir: path.join(serverconfig.cachedir, "gsea"),
171
- geneset_name: q.geneset_name,
172
- num_permutations: cacheArg.num_permutations,
173
- ...pickleB64 ? { pickle_b64: pickleB64 } : {}
174
- };
175
- }
176
- async function resolveGseaGenesAndFoldChange({
177
- q,
178
- genomes
179
- }) {
180
- if (q.cacheId) {
181
- if (!q.daRequest) throw new Error("daCacheMissing");
182
- const kind = q.daRequest.kind;
183
- if (kind !== "DE" && kind !== "DM") throw new Error('daRequest.kind must be "DE" or "DM"');
184
- if (kind === "DE") {
185
- const { result: result2, cacheId: cacheId2 } = await getDeCacheResult(q.daRequest, genomes);
186
- if (cacheId2 !== q.cacheId) throw new Error("cacheId does not match daRequest");
187
- return {
188
- genes: result2.geneRows.map((g) => g.gene_name),
189
- fold_change: result2.geneRows.map((g) => g.fold_change)
190
- };
191
- }
192
- const { result, cacheId } = await getDmCacheResult(q.daRequest, genomes);
193
- if (cacheId !== q.cacheId) throw new Error("cacheId does not match daRequest");
194
- return {
195
- genes: result.promoterRows.map((p) => p.gene_name),
196
- fold_change: result.promoterRows.map((p) => p.fold_change)
197
- };
198
- }
199
- if (q.dapParams) {
200
- const genome = genomes[q.genome];
201
- if (!genome) throw new Error("invalid genome");
202
- const [ds] = get_ds_tdb(genome, q);
203
- const { organism, assay, cohort } = q.dapParams;
204
- const cohortConfig = ds.queries?.proteome?.organisms?.[organism]?.assays?.[assay]?.cohorts?.[cohort];
205
- if (!cohortConfig?.DAPfile) throw new Error("DAP file not configured for this cohort");
206
- const filePath = path.join(serverconfig.tpmasterdir, cohortConfig.DAPfile);
207
- const content = await fs.promises.readFile(filePath, "utf8");
208
- const lines = content.trim().split("\n");
209
- const genes = [];
210
- const fold_change = [];
211
- for (let i = 1; i < lines.length; i++) {
212
- const parts = lines[i].split(" ");
213
- if (parts.length < 4) continue;
214
- const fc = Number(parts[2]);
215
- if (!Number.isFinite(fc)) continue;
216
- genes.push(parts[1]);
217
- fold_change.push(fc);
218
- }
219
- return { genes, fold_change };
220
- }
221
- if (!q.genes || !q.fold_change) throw new Error("requires genes and fold_change when cacheId is absent");
222
- return { genes: q.genes, fold_change: q.fold_change };
223
- }
224
- export {
225
- init
226
- };