@sjcrh/proteinpaint-server 2.118.3-2 → 2.119.0-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.
@@ -155,11 +155,11 @@ function termdb_test_default() {
155
155
  file: "files/hg38/TermdbTest/TermdbTest_CNV_gene.gz"
156
156
  },
157
157
  /*
158
- on the fly cnv calls from gene body probe signals are no longer used
159
- probe2cnv:{
160
- file: 'files/hg19/pnet/PNET.probesignals.gz'
161
- }
162
- */
158
+ on the fly cnv calls from gene body probe signals are no longer used
159
+ probe2cnv:{
160
+ file: 'files/hg19/pnet/PNET.probesignals.gz'
161
+ }
162
+ */
163
163
  singleSampleMutation: {
164
164
  src: "native",
165
165
  sample_id_key: "sample_id",
@@ -197,6 +197,10 @@ function termdb_test_default() {
197
197
  topVariablyExpressedGenes: {
198
198
  src: "native"
199
199
  },
200
+ rnaseqGeneCount: {
201
+ storage_type: "HDF5",
202
+ file: "files/hg38/TermdbTest/TermdbTest.geneCounts.h5"
203
+ },
200
204
  WSImages: {
201
205
  type: "H&E",
202
206
  imageBySampleFolder: "files/hg38/TermdbTest/wsimages"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.118.3-2",
3
+ "version": "2.119.0-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",
@@ -65,11 +65,11 @@
65
65
  "typescript": "^5.6.3"
66
66
  },
67
67
  "dependencies": {
68
- "@sjcrh/augen": "2.118.3-2",
68
+ "@sjcrh/augen": "2.119.0-0",
69
69
  "@sjcrh/proteinpaint-python": "2.118.0",
70
- "@sjcrh/proteinpaint-rust": "2.117.0",
71
- "@sjcrh/proteinpaint-shared": "2.118.3-2",
72
- "@sjcrh/proteinpaint-types": "2.118.3-2",
70
+ "@sjcrh/proteinpaint-rust": "2.119.0-0",
71
+ "@sjcrh/proteinpaint-shared": "2.119.0-0",
72
+ "@sjcrh/proteinpaint-types": "2.119.0-0",
73
73
  "@types/express": "^5.0.0",
74
74
  "@types/express-session": "^1.18.1",
75
75
  "better-sqlite3": "^9.4.1",
@@ -23,8 +23,16 @@ function init({ genomes }) {
23
23
  if (!ds)
24
24
  throw "invalid dataset name";
25
25
  const sampleId = query.sample_id;
26
- const images = await ds.queries.WSImages.getWSImages(sampleId);
27
- res.send({ sampleWSImages: images });
26
+ const wsimages = await ds.queries.WSImages.getWSImages(sampleId);
27
+ if (ds.queries.WSImages.getWSIAnnotations) {
28
+ for (const wsimage of wsimages) {
29
+ const annotations = await ds.queries.WSImages.getWSIAnnotations(sampleId, wsimage.filename);
30
+ if (annotations) {
31
+ wsimage.overlays = annotations;
32
+ }
33
+ }
34
+ }
35
+ res.send({ sampleWSImages: wsimages });
28
36
  } catch (e) {
29
37
  console.log(e);
30
38
  res.status(404).send("Sample images not found");
@@ -9,6 +9,7 @@ import { mayLog } from "#src/helpers.ts";
9
9
  import serverconfig from "../src/serverconfig.js";
10
10
  import imagesize from "image-size";
11
11
  import { get_header_txt } from "#src/utils.js";
12
+ import { formatElapsedTime } from "@sjcrh/proteinpaint-shared/time.js";
12
13
  const api = {
13
14
  endpoint: "DEanalysis",
14
15
  methods: {
@@ -186,6 +187,10 @@ async function run_DE(param, ds, term_results, term_results2) {
186
187
  throw "sample size of group1 < 1";
187
188
  if (sample_size2 < 1)
188
189
  throw "sample size of group2 < 1";
190
+ const commonnames = group1names.filter((element) => group2names.includes(element));
191
+ if (commonnames.length > 0) {
192
+ throw "Common elements found between both groups:" + commonnames.map((i) => i).join(",");
193
+ }
189
194
  const cases_string = group1names.map((i) => i).join(",");
190
195
  const controls_string = group2names.map((i) => i).join(",");
191
196
  const expression_input = {
@@ -221,7 +226,7 @@ async function run_DE(param, ds, term_results, term_results2) {
221
226
  const result2 = JSON.parse(
222
227
  await run_R(path.join(serverconfig.binpath, "utils", "edge.R"), JSON.stringify(expression_input))
223
228
  );
224
- mayLog("Time taken to run edgeR:", Date.now() - time12, "ms");
229
+ mayLog("Time taken to run edgeR:", formatElapsedTime(Date.now() - time12));
225
230
  param.method = "edgeR";
226
231
  const ql_imagePath = path.join(serverconfig.cachedir, result2.edgeR_ql_image_name[0]);
227
232
  mayLog("ql_imagePath:", ql_imagePath);
@@ -244,7 +249,7 @@ async function run_DE(param, ds, term_results, term_results2) {
244
249
  }
245
250
  const time1 = (/* @__PURE__ */ new Date()).valueOf();
246
251
  const result = JSON.parse(await run_rust("DEanalysis", JSON.stringify(expression_input)));
247
- mayLog("Time taken to run rust DE pipeline:", Date.now() - time1, "ms");
252
+ mayLog("Time taken to run rust DE pipeline:", formatElapsedTime(Date.now() - time1));
248
253
  param.method = "wilcoxon";
249
254
  return { data: result, sample_size1, sample_size2, method: param.method };
250
255
  }
@@ -12,6 +12,7 @@ import { getData } from "#src/termdb.matrix.js";
12
12
  import { termType2label } from "#shared/terms.js";
13
13
  import { mayLog } from "#src/helpers.ts";
14
14
  import { formatElapsedTime } from "#shared/time.js";
15
+ import { getResult as getResultGene } from "#src/gene.js";
15
16
  const api = {
16
17
  endpoint: "termdb/cluster",
17
18
  methods: {
@@ -219,7 +220,7 @@ async function validate_query_geneExpression(ds, genome) {
219
220
  return;
220
221
  }
221
222
  if (q.src == "native") {
222
- await validateNative(q, ds);
223
+ await validateNative(q, ds, genome);
223
224
  return;
224
225
  }
225
226
  throw "unknown queries.geneExpression.src";
@@ -255,7 +256,7 @@ async function queryGeneExpression(hdf5_file, geneNames) {
255
256
  throw error;
256
257
  }
257
258
  }
258
- async function validateNative(q, ds) {
259
+ async function validateNative(q, ds, genome) {
259
260
  q.file = path.join(serverconfig.tpmasterdir, q.file);
260
261
  q.samples = [];
261
262
  await utils.file_is_readable(q.file);
@@ -271,9 +272,102 @@ async function validateNative(q, ds) {
271
272
  throw "unknown sample from HDF5: " + sn;
272
273
  q.samples.push(si);
273
274
  }
274
- console.log(`${ds.label}: HDF5 file validated. Format: ${vr.format}, Samples:`, vr.sampleNames.length);
275
+ console.log(
276
+ `${ds.label}: geneExpression HDF5 file validated. Format: ${vr.format}, Samples:`,
277
+ vr.sampleNames.length
278
+ );
275
279
  } catch (error) {
276
- throw `${ds.label}: Failed to validate HDF5 file: ${error}`;
280
+ console.log(`${ds.label}: geneExpression HDF5 validation failed, falling back to tabix handling: ${error}`);
281
+ try {
282
+ q.samples = [];
283
+ await utils.validate_tabixfile(q.file);
284
+ q.nochr = await utils.tabix_is_nochr(q.file, null, genome);
285
+ const lines = await utils.get_header_tabix(q.file);
286
+ if (!lines[0])
287
+ throw "Header line missing from " + q.file;
288
+ const l = lines[0].split(" ");
289
+ if (l.slice(0, 4).join(" ") != "#chr start stop gene") {
290
+ throw "Header line has wrong content for columns 1-4";
291
+ }
292
+ for (let i = 4; i < l.length; i++) {
293
+ const id = ds.cohort.termdb.q.sampleName2id(l[i]);
294
+ if (id == void 0) {
295
+ throw "queries.geneExpression: unknown sample from header: " + l[i];
296
+ }
297
+ q.samples.push(id);
298
+ }
299
+ } catch (e) {
300
+ throw `${ds.label} geneExpression file cannot be validated as tabix file`;
301
+ }
302
+ console.log(`${ds.label}: Tabix file successfully initialized. Samples: ${q.samples.length}`);
303
+ q.get = async (param) => {
304
+ const limitSamples = await mayLimitSamples(param, q.samples, ds);
305
+ if (limitSamples?.size == 0) {
306
+ return { term2sample2value: /* @__PURE__ */ new Map(), byTermId: {}, bySampleId: {} };
307
+ }
308
+ const bySampleId = {};
309
+ const samples = q.samples || [];
310
+ if (limitSamples) {
311
+ for (const sid of limitSamples) {
312
+ bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
313
+ }
314
+ } else {
315
+ for (const sid of samples) {
316
+ bySampleId[sid] = { label: ds.cohort.termdb.q.id2sampleName(sid) };
317
+ }
318
+ }
319
+ const term2sample2value = /* @__PURE__ */ new Map();
320
+ const byTermId = {};
321
+ for (const geneTerm of param.terms) {
322
+ if (!geneTerm.gene)
323
+ continue;
324
+ if (!geneTerm.chr || !Number.isInteger(geneTerm.start) || !Number.isInteger(geneTerm.stop)) {
325
+ const re = getResultGene(genome, { input: geneTerm.gene, deep: 1 });
326
+ if (!re.gmlst || re.gmlst.length == 0) {
327
+ console.warn("Unknown gene:" + geneTerm.gene);
328
+ continue;
329
+ }
330
+ const i = re.gmlst.find((i2) => i2.isdefault) || re.gmlst[0];
331
+ geneTerm.start = i.start;
332
+ geneTerm.stop = i.stop;
333
+ geneTerm.chr = i.chr;
334
+ }
335
+ if (!geneTerm.chr || !Number.isInteger(geneTerm.start) || !Number.isInteger(geneTerm.stop)) {
336
+ throw "Missing chr/start/stop";
337
+ }
338
+ const s2v = {};
339
+ await utils.get_lines_bigfile({
340
+ args: [
341
+ q.file,
342
+ (q.nochr ? geneTerm.chr.replace("chr", "") : geneTerm.chr) + ":" + geneTerm.start + "-" + geneTerm.stop
343
+ ],
344
+ callback: (line) => {
345
+ const l = line.split(" ");
346
+ if (l[3].toLowerCase() != geneTerm.gene.toLowerCase())
347
+ return;
348
+ for (let i = 4; i < l.length; i++) {
349
+ const sampleId = samples[i - 4];
350
+ if (limitSamples && !limitSamples.has(sampleId))
351
+ continue;
352
+ if (!l[i])
353
+ continue;
354
+ const v = Number(l[i]);
355
+ if (Number.isNaN(v))
356
+ throw "Expression value not number";
357
+ s2v[sampleId] = v;
358
+ }
359
+ }
360
+ });
361
+ if (Object.keys(s2v).length) {
362
+ term2sample2value.set(geneTerm.gene, s2v);
363
+ }
364
+ }
365
+ if (term2sample2value.size == 0) {
366
+ throw "No data available for the input " + param.terms?.map((g) => g.gene).join(", ");
367
+ }
368
+ return { term2sample2value, byTermId, bySampleId };
369
+ };
370
+ return;
277
371
  }
278
372
  q.get = async (param) => {
279
373
  const limitSamples = await mayLimitSamples(param, q.samples, ds);
@@ -6,7 +6,7 @@ import SessionManager from "#src/wsisessions/SessionManager.ts";
6
6
  import serverconfig from "#src/serverconfig.js";
7
7
  import path from "path";
8
8
  const api = {
9
- endpoint: `tileserver/layer/slide/:sessionId/zoomify/:TileGroup/:z-:x-:y@1x.jpg`,
9
+ endpoint: `tileserver/layer/:type/:sessionId/zoomify/:TileGroup/:z-:x-:y@1x.jpg`,
10
10
  methods: {
11
11
  get: {
12
12
  ...tilePayload,
@@ -21,7 +21,7 @@ const api = {
21
21
  function init({ genomes }) {
22
22
  return async (req, res) => {
23
23
  try {
24
- const { sessionId, TileGroup, z, x, y } = req.params;
24
+ const { type, sessionId, TileGroup, z, x, y } = req.params;
25
25
  const query = req.query;
26
26
  const wsiImage = query.wsi_image;
27
27
  if (!wsiImage)
@@ -49,7 +49,7 @@ function init({ genomes }) {
49
49
  if (!tileServer) {
50
50
  throw new Error("No tile server");
51
51
  }
52
- const url = `${tileServer.url}/tileserver/layer/slide/${sessionId}/zoomify/${TileGroup}/${z}-${x}-${y}@1x.jpg`;
52
+ const url = `${tileServer.url}/tileserver/layer/${type}/${sessionId}/zoomify/${TileGroup}/${z}-${x}-${y}@1x.jpg`;
53
53
  const response = await ky.get(url, { timeout: 12e4 });
54
54
  await SessionManager.getInstance().updateSession(wsiImagePath);
55
55
  const buffer = await response.arrayBuffer();
@@ -45,7 +45,7 @@ function init({ genomes }) {
45
45
  if (!mount)
46
46
  throw new Error("No mount available for TileServer");
47
47
  const wsiImagePath = path.join(`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`, wsimage);
48
- const sessionId = await getSessionId(cookieJar, getCookieString, setCookie, wsiImagePath);
48
+ const sessionId = await getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie, wsiImagePath);
49
49
  const getWsiImageResponse = await getWsiImageDimensions(sessionId, getCookieString, wsiImagePath);
50
50
  const payload = {
51
51
  status: "ok",
@@ -62,7 +62,7 @@ function init({ genomes }) {
62
62
  }
63
63
  };
64
64
  }
65
- async function getSessionId(cookieJar, getCookieString, setCookie, wsimage) {
65
+ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie, wsimage) {
66
66
  const sessionManager = SessionManager.getInstance();
67
67
  const invalidateResult = await sessionManager.syncAndInvalidateSessions(wsimage);
68
68
  if (!invalidateResult)
@@ -93,6 +93,49 @@ async function getSessionId(cookieJar, getCookieString, setCookie, wsimage) {
93
93
  hooks: getHooks(cookieJar, getCookieString, setCookie)
94
94
  });
95
95
  await sessionManager.setSession(wsimage, sessionId, tileServer);
96
+ if (ds.queries.WSImages.getWSIAnnotations) {
97
+ const annotationFiles = await ds.queries.WSImages.getWSIAnnotations(sampleId, wsimage);
98
+ if (!annotationFiles)
99
+ throw new Error("No annotations files found");
100
+ const mount = serverconfig.features?.tileserver?.mount;
101
+ if (!mount)
102
+ throw new Error("No mount available for TileServer");
103
+ if (annotationFiles.length > 0) {
104
+ for (const annotationFile of annotationFiles) {
105
+ const annotationsFilePath = path.join(
106
+ `${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
107
+ annotationFile
108
+ );
109
+ const annotationsData = qs.stringify({
110
+ overlay_path: annotationsFilePath
111
+ });
112
+ await ky.put(`${tileServer.url}/tileserver/overlay`, {
113
+ body: annotationsData,
114
+ timeout: 5e4,
115
+ headers: {
116
+ "Content-Type": "application/x-www-form-urlencoded",
117
+ Cookie: `session_id=${sessionId}`
118
+ },
119
+ hooks: getHooks(cookieJar, getCookieString, setCookie)
120
+ });
121
+ }
122
+ const cmapData = qs.stringify({
123
+ cmap: JSON.stringify({
124
+ keys: ["annotation"],
125
+ values: [ds.queries.WSImages.annotationsColor]
126
+ })
127
+ });
128
+ await ky.put(`${tileServer.url}/tileserver/cmap`, {
129
+ body: cmapData,
130
+ timeout: 5e4,
131
+ headers: {
132
+ "Content-Type": "application/x-www-form-urlencoded",
133
+ Cookie: `session_id=${sessionId}`
134
+ },
135
+ hooks: getHooks(cookieJar, getCookieString, setCookie)
136
+ });
137
+ }
138
+ }
96
139
  return sessionId;
97
140
  }
98
141
  async function getWsiImageDimensions(sessionId, getCookieString, wsimage) {