@sjcrh/proteinpaint-server 2.149.0 → 2.151.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.
@@ -281,7 +281,8 @@ function termdb_test_default() {
281
281
  svfusion: {
282
282
  byrange: {
283
283
  file: "files/hg38/TermdbTest/TermdbTest_Fusion.gz"
284
- }
284
+ },
285
+ dtLst: [2, 5]
285
286
  },
286
287
  cnv: {
287
288
  file: "files/hg38/TermdbTest/TermdbTest_CNV_gene.gz"
@@ -367,6 +368,20 @@ function termdb_test_default() {
367
368
  trackLst: {
368
369
  jsonFile: "files/hg38/TermdbTest/trackLst/facet.json",
369
370
  activeTracks: ["bw 1", "bed 1"]
371
+ },
372
+ chat: {},
373
+ alphaGenome: {
374
+ default: {
375
+ gene: "FLT3",
376
+ chromosome: "chr13",
377
+ position: 28034105,
378
+ reference: "A",
379
+ alternate: "AACTCCCATTTGAGATCATACC",
380
+ ontologyTerm: "EFO:0000572",
381
+ // lymphoblast
382
+ outputType: 4
383
+ //RNA SEQ
384
+ }
370
385
  }
371
386
  }
372
387
  };
package/genome/hg38.js CHANGED
@@ -55,8 +55,8 @@ var hg38_default = {
55
55
  },
56
56
  {
57
57
  __isgene: true,
58
- name: "GENCODE v47",
59
- file: "anno/gencode.v47.hg38.gz",
58
+ name: "GENCODE v48",
59
+ file: "anno/gencode.v48.hg38.gz",
60
60
  translatecoding: true,
61
61
  categories: {
62
62
  coding: { color: "#004D99", label: "Coding gene" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.149.0",
3
+ "version": "2.151.0",
4
4
  "type": "module",
5
5
  "description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
6
6
  "main": "src/app.js",
@@ -61,11 +61,11 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@sjcrh/augen": "2.143.0",
64
- "@sjcrh/proteinpaint-python": "2.146.0",
64
+ "@sjcrh/proteinpaint-python": "2.151.0",
65
65
  "@sjcrh/proteinpaint-r": "2.149.0",
66
- "@sjcrh/proteinpaint-rust": "2.149.0",
67
- "@sjcrh/proteinpaint-shared": "2.149.0",
68
- "@sjcrh/proteinpaint-types": "2.149.0",
66
+ "@sjcrh/proteinpaint-rust": "2.150.0",
67
+ "@sjcrh/proteinpaint-shared": "2.150.0",
68
+ "@sjcrh/proteinpaint-types": "2.150.0",
69
69
  "@types/express": "^5.0.0",
70
70
  "@types/express-session": "^1.18.1",
71
71
  "better-sqlite3": "^9.4.1",
@@ -0,0 +1,41 @@
1
+ import { alphaGenomePayload } from "#types/checkers";
2
+ import { run_python } from "@sjcrh/proteinpaint-python";
3
+ import serverconfig from "#src/serverconfig.js";
4
+ const api = {
5
+ endpoint: "alphaGenome",
6
+ methods: {
7
+ get: {
8
+ ...alphaGenomePayload,
9
+ init
10
+ },
11
+ post: {
12
+ ...alphaGenomePayload,
13
+ init
14
+ }
15
+ }
16
+ };
17
+ function init() {
18
+ return async (req, res) => {
19
+ try {
20
+ const query = req.query;
21
+ const params = {
22
+ API_KEY: serverconfig.alphaGenome.API_KEY,
23
+ chromosome: query.chromosome,
24
+ position: query.position,
25
+ reference: query.reference,
26
+ alternate: query.alternate,
27
+ interval: query.interval
28
+ };
29
+ if (query.ontologyTerms) params["ontologyTerms"] = query.ontologyTerms;
30
+ if (query.outputType) params["outputType"] = Number(query.outputType);
31
+ const url = await run_python("alphaGenome.py", JSON.stringify(params));
32
+ res.send({ plotImage: url });
33
+ } catch (e) {
34
+ console.log(e);
35
+ res.status(404).send({ error: e });
36
+ }
37
+ };
38
+ }
39
+ export {
40
+ api
41
+ };
@@ -0,0 +1,36 @@
1
+ import { alphaGenomeTypesPayload } from "#types/checkers";
2
+ import { run_python } from "@sjcrh/proteinpaint-python";
3
+ import serverconfig from "#src/serverconfig.js";
4
+ const api = {
5
+ endpoint: "AlphaGenomeTypes",
6
+ methods: {
7
+ get: {
8
+ ...alphaGenomeTypesPayload,
9
+ init
10
+ },
11
+ post: {
12
+ ...alphaGenomeTypesPayload,
13
+ init
14
+ }
15
+ }
16
+ };
17
+ function init() {
18
+ return async (req, res) => {
19
+ try {
20
+ const params = { API_KEY: serverconfig.alphaGenome.API_KEY };
21
+ const result = await run_python("AlphaGenomeTypes.py", JSON.stringify(params));
22
+ const { ontologyTerms, outputTypes, intervals } = JSON.parse(result);
23
+ res.send({
24
+ ontologyTerms: ontologyTerms.sort((a, b) => a.label.localeCompare(b.label)),
25
+ outputTypes,
26
+ intervals
27
+ });
28
+ } catch (e) {
29
+ console.log(e);
30
+ res.status(404).send({ error: e });
31
+ }
32
+ };
33
+ }
34
+ export {
35
+ api
36
+ };
package/routes/grin2.js CHANGED
@@ -5,7 +5,8 @@ import { run_python } from "@sjcrh/proteinpaint-python";
5
5
  import { mayLog } from "#src/helpers.ts";
6
6
  import { get_samples } from "#src/termdb.sql.js";
7
7
  import { read_file, file_is_readable } from "#src/utils.js";
8
- import { dtsnvindel, dtcnv, dtfusionrna } from "#shared/common.js";
8
+ import { dtsnvindel, dtcnv, dtfusionrna, dtsv } from "#shared/common.js";
9
+ import crypto from "crypto";
9
10
  const MAX_LESIONS_PER_TYPE = 5e4;
10
11
  const api = {
11
12
  endpoint: "grin2",
@@ -41,6 +42,20 @@ function init({ genomes }) {
41
42
  }
42
43
  };
43
44
  }
45
+ function generateCacheFileName() {
46
+ const randomHex = crypto.randomBytes(16).toString("hex");
47
+ const cacheFileName = `grin2_results_${randomHex}.txt`;
48
+ return path.join(serverconfig.cachedir, "grin2", cacheFileName);
49
+ }
50
+ function getAvailableDataTypes(request) {
51
+ const availableOptions = [];
52
+ for (const key in request) {
53
+ if (key.endsWith("Options")) {
54
+ availableOptions.push(key);
55
+ }
56
+ }
57
+ return availableOptions;
58
+ }
44
59
  async function runGrin2(g, ds, request) {
45
60
  const startTime = Date.now();
46
61
  const samples = await get_samples(
@@ -76,7 +91,10 @@ async function runGrin2(g, ds, request) {
76
91
  devicePixelRatio: request.devicePixelRatio,
77
92
  pngDotRadius: request.pngDotRadius,
78
93
  width: request.width,
79
- height: request.height
94
+ height: request.height,
95
+ cacheFileName: generateCacheFileName(),
96
+ availableDataTypes: getAvailableDataTypes(request),
97
+ maxGenesToShow: request.maxGenesToShow
80
98
  };
81
99
  for (const c in g.majorchr) {
82
100
  if (ds.queries.singleSampleMutation.discoPlot?.skipChrM) {
@@ -112,7 +130,8 @@ async function runGrin2(g, ds, request) {
112
130
  grin2Time: grin2AnalysisTimeToPrint,
113
131
  totalTime
114
132
  },
115
- processingSummary
133
+ processingSummary,
134
+ cacheFileName: resultData.cacheFileName
116
135
  };
117
136
  return response;
118
137
  }
@@ -121,6 +140,7 @@ function getLesionTracker(req) {
121
140
  if (req.snvindelOptions) currentTypes.push(dtsnvindel);
122
141
  if (req.cnvOptions) currentTypes.push(dtcnv);
123
142
  if (req.fusionOptions) currentTypes.push(dtfusionrna);
143
+ if (req.svOptions) currentTypes.push(dtsv);
124
144
  const track = /* @__PURE__ */ new Map();
125
145
  for (const t of currentTypes) track.set(t, { count: 0 });
126
146
  return track;
@@ -188,6 +208,9 @@ async function processSampleData(samples, ds, request, tracker) {
188
208
  case dtfusionrna:
189
209
  label = "fusion";
190
210
  break;
211
+ case dtsv:
212
+ label = "structural variant";
213
+ break;
191
214
  default:
192
215
  label = `type ${type}`;
193
216
  break;
@@ -237,8 +260,43 @@ async function processSampleMlst(sampleName, mlst, request, tracker) {
237
260
  }
238
261
  const les = filterAndConvertFusion(sampleName, m, request.fusionOptions);
239
262
  if (les && fusion) {
240
- fusion.count++;
241
- sampleLesions.push(les);
263
+ const lesionsToAdd = Array.isArray(les[0]) ? les.length : 1;
264
+ if (fusion.count + lesionsToAdd > MAX_LESIONS_PER_TYPE) {
265
+ break;
266
+ }
267
+ if (Array.isArray(les[0])) {
268
+ for (const lesion of les) {
269
+ sampleLesions.push(lesion);
270
+ fusion.count++;
271
+ }
272
+ } else {
273
+ sampleLesions.push(les);
274
+ fusion.count++;
275
+ }
276
+ }
277
+ break;
278
+ }
279
+ case dtsv: {
280
+ if (!request.svOptions) break;
281
+ const sv = tracker.get(dtsv);
282
+ if (sv && sv.count >= MAX_LESIONS_PER_TYPE) {
283
+ break;
284
+ }
285
+ const les = filterAndConvertSV(sampleName, m, request.svOptions);
286
+ if (les && sv) {
287
+ const lesionsToAdd = Array.isArray(les[0]) ? les.length : 1;
288
+ if (sv.count + lesionsToAdd > MAX_LESIONS_PER_TYPE) {
289
+ break;
290
+ }
291
+ if (Array.isArray(les[0])) {
292
+ for (const lesion of les) {
293
+ sampleLesions.push(lesion);
294
+ sv.count++;
295
+ }
296
+ } else {
297
+ sampleLesions.push(les);
298
+ sv.count++;
299
+ }
242
300
  }
243
301
  break;
244
302
  }
@@ -258,7 +316,20 @@ function filterAndConvertSnvIndel(sampleName, entry, options) {
258
316
  if (!Number.isInteger(entry.pos)) {
259
317
  return null;
260
318
  }
261
- return [sampleName, entry.chr, entry.pos, entry.pos, "mutation"];
319
+ if (options.minAltAlleleCount !== void 0 && options.minAltAlleleCount > 0) {
320
+ if (!entry.altCount || entry.altCount < options.minAltAlleleCount) {
321
+ return null;
322
+ }
323
+ }
324
+ if (options.minTotalDepth !== void 0 && options.minTotalDepth > 0) {
325
+ const totalDepth = (entry.refCount || 0) + (entry.altCount || 0);
326
+ if (totalDepth < options.minTotalDepth) {
327
+ return null;
328
+ }
329
+ }
330
+ const start = entry.pos;
331
+ const end = entry.pos;
332
+ return [sampleName, entry.chr, start, end, "mutation"];
262
333
  }
263
334
  function filterAndConvertCnv(sampleName, entry, options) {
264
335
  if (!options || options.gainThreshold === void 0 || options.lossThreshold === void 0 || options.maxSegLength === void 0) {
@@ -277,10 +348,39 @@ function filterAndConvertCnv(sampleName, entry, options) {
277
348
  const isLoss = entry.value <= options.lossThreshold;
278
349
  if (!isGain && !isLoss) return null;
279
350
  const lesionType = isGain ? "gain" : "loss";
280
- return [sampleName, entry.chr, entry.start, entry.stop, lesionType];
351
+ const start = entry.start;
352
+ const end = entry.stop;
353
+ return [sampleName, entry.chr, start, end, lesionType];
281
354
  }
282
355
  function filterAndConvertFusion(sampleName, entry, _options) {
283
- return [sampleName, entry.chrA, entry.posA, entry.posB, "fusion"];
356
+ if (!entry.chrA || entry.posA === void 0) {
357
+ return null;
358
+ }
359
+ const startA = entry.posA;
360
+ const endA = entry.posA;
361
+ const lesionA = [sampleName, entry.chrA, startA, endA, "fusion"];
362
+ if (entry.chrB && entry.posB !== void 0) {
363
+ const startB = entry.posB;
364
+ const endB = entry.posB;
365
+ const lesionB = [sampleName, entry.chrB, startB, endB, "fusion"];
366
+ return [lesionA, lesionB];
367
+ }
368
+ return lesionA;
369
+ }
370
+ function filterAndConvertSV(sampleName, entry, _options) {
371
+ if (!entry.chrA || entry.posA === void 0) {
372
+ return null;
373
+ }
374
+ const startA = entry.posA;
375
+ const endA = entry.posA;
376
+ const lesionA = [sampleName, entry.chrA, startA, endA, "sv"];
377
+ if (entry.chrB && entry.posB !== void 0) {
378
+ const startB = entry.posB;
379
+ const endB = entry.posB;
380
+ const lesionB = [sampleName, entry.chrB, startB, endB, "sv"];
381
+ return [lesionA, lesionB];
382
+ }
383
+ return lesionA;
284
384
  }
285
385
  export {
286
386
  api
@@ -166,17 +166,24 @@ async function run_DE(param, ds, term_results, term_results2) {
166
166
  }
167
167
  const sample_size1 = group1names.length;
168
168
  const sample_size2 = group2names.length;
169
- if (sample_size1 < 1) throw "sample size of group1 < 1";
170
- if (sample_size2 < 1) throw "sample size of group2 < 1";
171
- const commonnames = group1names.filter((element) => group2names.includes(element));
172
- if (commonnames.length > 0) {
173
- throw "Common elements found between both groups:" + commonnames.map((i) => i).join(",");
169
+ const alerts = validateGroups(sample_size1, sample_size2, group1names, group2names);
170
+ if (param.preAnalysis) {
171
+ const group1Name = param.samplelst.groups[0].name;
172
+ const group2Name = param.samplelst.groups[1].name;
173
+ return {
174
+ data: {
175
+ [group1Name]: sample_size1,
176
+ [group2Name]: sample_size2,
177
+ ...alerts.length ? { alert: alerts.join(" | ") } : {}
178
+ }
179
+ };
174
180
  }
175
- const cases_string = group1names.map((i) => i).join(",");
176
- const controls_string = group2names.map((i) => i).join(",");
181
+ if (alerts.length) throw alerts.join(" | ");
182
+ const cases_string = group2names.map((i) => i).join(",");
183
+ const controls_string = group1names.map((i) => i).join(",");
177
184
  const expression_input = {
178
- case: controls_string,
179
- control: cases_string,
185
+ case: cases_string,
186
+ control: controls_string,
180
187
  data_type: "do_DE",
181
188
  input_file: q.file,
182
189
  cachedir: serverconfig.cachedir,
@@ -236,6 +243,14 @@ async function run_DE(param, ds, term_results, term_results2) {
236
243
  param.method = "wilcoxon";
237
244
  return { data: result, sample_size1, sample_size2, method: param.method };
238
245
  }
246
+ function validateGroups(sample_size1, sample_size2, group1names, group2names) {
247
+ const alerts = [];
248
+ if (sample_size1 < 1) alerts.push("sample size of group1 < 1");
249
+ if (sample_size2 < 1) alerts.push("sample size of group2 < 1");
250
+ const commonnames = group1names.filter((x) => group2names.includes(x));
251
+ if (commonnames.length) alerts.push(`Common elements found between both groups: ${commonnames.join(", ")}`);
252
+ return alerts;
253
+ }
239
254
  async function readFileAndDelete(file, key, response) {
240
255
  const plot = await fs.promises.readFile(file);
241
256
  const plotBuffer = Buffer.from(plot).toString("base64");
@@ -23,6 +23,12 @@ function init({ genomes }) {
23
23
  if (!g) throw "invalid genome";
24
24
  const ds = g.datasets?.[q.dslabel];
25
25
  if (!ds) throw "invalid dslabel";
26
+ console.log("serverconfig:", serverconfig);
27
+ const serverconfig_ds_entries = serverconfig.genomes.find((genome) => genome.name == q.genome).datasets.find((dslabel) => dslabel.name == ds.label);
28
+ console.log("serverconfig_ds_entries:", serverconfig_ds_entries);
29
+ if (!serverconfig_ds_entries.aifiles) {
30
+ throw "aifiles are missing for chatbot to work";
31
+ }
26
32
  let apilink;
27
33
  let comp_model_name;
28
34
  let embedding_model_name;
@@ -41,11 +47,13 @@ function init({ genomes }) {
41
47
  // Just hardcoding variables here, these will later be defined in more appropriate places
42
48
  user_input: q.prompt,
43
49
  apilink,
44
- dataset_db: serverconfig.tpmasterdir + "/" + ds.cohort.db.file,
50
+ tpmasterdir: serverconfig.tpmasterdir,
45
51
  comp_model_name,
46
52
  embedding_model_name,
47
- llm_backend_name: serverconfig.llm_backend
53
+ llm_backend_name: serverconfig.llm_backend,
48
54
  // The type of backend (engine) used for running the embedding and completion model. Currently "SJ" and "Ollama" are supported
55
+ aifiles: serverconfig_ds_entries.aifiles,
56
+ binpath: serverconfig.binpath
49
57
  };
50
58
  const time1 = (/* @__PURE__ */ new Date()).valueOf();
51
59
  const ai_output_data = await run_rust("aichatbot", JSON.stringify(chatbot_input));
@@ -73,6 +73,7 @@ function make(q, req, res, ds, genome) {
73
73
  if (tdb.excludedTermtypeByTarget) c.excludedTermtypeByTarget = tdb.excludedTermtypeByTarget;
74
74
  if (tdb.survival) c.survival = tdb.survival;
75
75
  if (tdb.regression) c.regression = tdb.regression;
76
+ if (tdb.uiLabels) c.uiLabels = tdb.uiLabels;
76
77
  if (ds.assayAvailability) c.assayAvailability = ds.assayAvailability;
77
78
  if (ds.cohort.correlationVolcano) c.correlationVolcano = ds.cohort.correlationVolcano;
78
79
  addRestrictAncestries(c, tdb);
@@ -135,7 +136,7 @@ function addNonDictionaryQueries(c, ds, genome) {
135
136
  if (q.snvindel.byisoform?.processTwsInOneQuery) q2.snvindel.byisoform = { processTwsInOneQuery: true };
136
137
  }
137
138
  if (q.svfusion) {
138
- q2.svfusion = {};
139
+ q2.svfusion = { dtLst: q.svfusion.dtLst };
139
140
  }
140
141
  if (q.cnv) {
141
142
  q2.cnv = {};
@@ -180,6 +181,7 @@ function addNonDictionaryQueries(c, ds, genome) {
180
181
  if (q.chat) {
181
182
  q2.chat = {};
182
183
  }
184
+ if (q.alphaGenome) q2.alphaGenome = q.alphaGenome;
183
185
  if (q.NIdata && serverconfig.features.showBrainImaging) {
184
186
  q2.NIdata = {};
185
187
  for (const k in q.NIdata) {