@sjcrh/proteinpaint-server 2.109.1 → 2.111.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.109.1",
3
+ "version": "2.111.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",
@@ -60,9 +60,9 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@sjcrh/augen": "2.109.1-0",
63
- "@sjcrh/proteinpaint-rust": "2.99.0",
64
- "@sjcrh/proteinpaint-shared": "2.109.1",
65
- "@sjcrh/proteinpaint-types": "2.109.1",
63
+ "@sjcrh/proteinpaint-rust": "2.111.0",
64
+ "@sjcrh/proteinpaint-shared": "2.111.0",
65
+ "@sjcrh/proteinpaint-types": "2.111.0",
66
66
  "@types/express": "^5.0.0",
67
67
  "@types/express-session": "^1.18.1",
68
68
  "better-sqlite3": "^9.4.1",
@@ -3,7 +3,6 @@ import { getData } from "../src/termdb.matrix.js";
3
3
  import run_R from "../src/run_R.js";
4
4
  import serverconfig from "../src/serverconfig.js";
5
5
  import { mayLog } from "#src/helpers.ts";
6
- import { stdDev } from "#shared/violin.bins.js";
7
6
  import path from "path";
8
7
  const minArrayLength = 3;
9
8
  const minSD = 0.05;
@@ -134,7 +133,18 @@ function validate_correlationVolcano(ds) {
134
133
  throw "unknown cv.variables.type";
135
134
  }
136
135
  }
136
+ function mean(data) {
137
+ return data.reduce((sum, value) => sum + value, 0) / data.length;
138
+ }
139
+ function stdDev(data) {
140
+ const meanValue = mean(data);
141
+ const squaredDifferences = data.map((value) => Math.pow(value - meanValue, 2));
142
+ const variance = squaredDifferences.reduce((sum, value) => sum + value, 0) / data.length;
143
+ return Math.sqrt(variance);
144
+ }
137
145
  export {
138
146
  api,
147
+ mean,
148
+ stdDev,
139
149
  validate_correlationVolcano
140
150
  };
@@ -1,6 +1,6 @@
1
1
  import ky from "ky";
2
2
  import { joinUrl } from "#shared/joinUrl.js";
3
- import { run_rust_stream } from "@sjcrh/proteinpaint-rust";
3
+ import { stream_rust } from "@sjcrh/proteinpaint-rust";
4
4
  import { gdcMafPayload } from "#types/checkers";
5
5
  import { maxTotalSizeCompressed } from "./gdc.maf.ts";
6
6
  import { mayLog } from "#src/helpers.ts";
@@ -46,19 +46,26 @@ async function buildMaf(q, res, ds) {
46
46
  host: joinUrl(host.rest, "data")
47
47
  // must use the /data/ endpoint from current host
48
48
  };
49
- const rustStream = run_rust_stream("gdcmaf", JSON.stringify(arg));
50
- res.setHeader("Content-Type", "application/octet-stream");
51
- res.setHeader("Content-Disposition", "attachment; filename=cohort.maf.gz");
52
- rustStream.pipe(res);
53
- rustStream.on("end", () => {
49
+ const boundary = "GDC_MAF_MULTIPART_BOUNDARY";
50
+ res.setHeader("content-type", `multipart/mixed; boundary=${boundary}`);
51
+ res.write(`--${boundary}`);
52
+ res.write("\ncontent-disposition: attachment; filename=cohort.maf.gz");
53
+ res.write("\ncontent-type: application/octet-stream\n\n");
54
+ res.flush();
55
+ const rustStream = stream_rust("gdcmaf", JSON.stringify(arg), emitJson);
56
+ rustStream.pipe(res, { end: false });
57
+ function emitJson(data, end = true) {
58
+ res.write(`
59
+ --${boundary}`);
60
+ res.write("\ncontent-type: application/json");
61
+ const json = typeof data === "string" ? data : JSON.stringify(data || { ok: true, status: "ok" });
62
+ res.write("\n\n" + json);
63
+ res.write(`
64
+ --${boundary}--`);
54
65
  mayLog("rust gdcmaf", Date.now() - t0);
55
- res.end();
56
- });
57
- rustStream.on("error", (err) => {
58
- console.error(err);
59
- res.statusCode = 500;
60
- res.end("Internal Server Error");
61
- });
66
+ if (end)
67
+ res.end();
68
+ }
62
69
  }
63
70
  async function getFileLstUnderSizeLimit(lst, host) {
64
71
  if (lst.length == 0)
@@ -1,3 +1,4 @@
1
+ import fs from "fs";
1
2
  import path from "path";
2
3
  import { diffExpPayload } from "#types/checkers";
3
4
  import { run_rust } from "@sjcrh/proteinpaint-rust";
@@ -6,6 +7,7 @@ import { get_ds_tdb } from "../src/termdb.js";
6
7
  import run_R from "../src/run_R.js";
7
8
  import { mayLog } from "#src/helpers.ts";
8
9
  import serverconfig from "../src/serverconfig.js";
10
+ import imagesize from "image-size";
9
11
  const api = {
10
12
  endpoint: "DEanalysis",
11
13
  methods: {
@@ -58,6 +60,8 @@ function init({ genomes }) {
58
60
  throw term_results2.error;
59
61
  }
60
62
  const results = await run_DE(req.query, ds, term_results, term_results2);
63
+ if (!results || !results.data)
64
+ throw "No data [termdb.DE.ts init()]";
61
65
  res.send(results);
62
66
  } catch (e) {
63
67
  res.send({ status: "error", error: e.message || e });
@@ -188,6 +192,7 @@ async function run_DE(param, ds, term_results, term_results2) {
188
192
  control: cases_string,
189
193
  data_type: "do_DE",
190
194
  input_file: q.file,
195
+ cachedir: serverconfig.cachedir,
191
196
  min_count: param.min_count,
192
197
  min_total_count: param.min_total_count,
193
198
  storage_type: param.storage_type
@@ -207,25 +212,50 @@ async function run_DE(param, ds, term_results, term_results2) {
207
212
  }
208
213
  }
209
214
  const sample_size_limit = 8;
210
- let result;
211
215
  if (group1names.length <= sample_size_limit && group2names.length <= sample_size_limit || param.method == "edgeR") {
212
- if (param.method == "edgeR") {
213
- expression_input.VarGenes = param.VarGenes;
214
- }
215
- const time1 = (/* @__PURE__ */ new Date()).valueOf();
216
- result = JSON.parse(
216
+ const time12 = (/* @__PURE__ */ new Date()).valueOf();
217
+ const result2 = JSON.parse(
217
218
  await run_R(path.join(serverconfig.binpath, "utils", "edge.R"), JSON.stringify(expression_input))
218
219
  );
219
- mayLog("Time taken to run edgeR:", Date.now() - time1, "ms");
220
+ mayLog("Time taken to run edgeR:", Date.now() - time12, "ms");
220
221
  param.method = "edgeR";
221
- } else {
222
- const time1 = (/* @__PURE__ */ new Date()).valueOf();
223
- result = JSON.parse(await run_rust("DEanalysis", JSON.stringify(expression_input)));
224
- mayLog("Time taken to run rust DE pipeline:", Date.now() - time1, "ms");
225
- param.method = "wilcoxon";
222
+ const ql_imagePath = path.join(serverconfig.cachedir, result2.edgeR_ql_image_name[0]);
223
+ mayLog("ql_imagePath:", ql_imagePath);
224
+ const mds_imagePath = path.join(serverconfig.cachedir, result2.edgeR_mds_image_name[0]);
225
+ mayLog("mds_imagePath:", mds_imagePath);
226
+ await readFileAndDelete(mds_imagePath, "mds_image", result2);
227
+ await readFileAndDelete(ql_imagePath, "ql_image", result2);
228
+ return {
229
+ data: result2.gene_data,
230
+ sample_size1,
231
+ sample_size2,
232
+ method: param.method,
233
+ ql_image: result2.ql_image,
234
+ // QL fit image
235
+ mds_image: result2.mds_image
236
+ // MDS image
237
+ };
226
238
  }
239
+ const time1 = (/* @__PURE__ */ new Date()).valueOf();
240
+ const result = JSON.parse(await run_rust("DEanalysis", JSON.stringify(expression_input)));
241
+ mayLog("Time taken to run rust DE pipeline:", Date.now() - time1, "ms");
242
+ param.method = "wilcoxon";
227
243
  return { data: result, sample_size1, sample_size2, method: param.method };
228
244
  }
245
+ async function readFileAndDelete(file, key, response) {
246
+ const plot = await fs.promises.readFile(file);
247
+ const plotBuffer = Buffer.from(plot).toString("base64");
248
+ const obj = {
249
+ src: `data:image/png;base64,${plotBuffer}`,
250
+ size: imagesize(file),
251
+ key
252
+ };
253
+ response[key] = obj;
254
+ fs.unlink(file, (err) => {
255
+ if (err)
256
+ throw err;
257
+ });
258
+ }
229
259
  export {
230
260
  api
231
261
  };
@@ -3,6 +3,7 @@ import { getData } from "../src/termdb.matrix.js";
3
3
  import { boxplot_getvalue } from "../src/utils.js";
4
4
  import { sortKey2values } from "./termdb.violin.ts";
5
5
  import { roundValueAuto } from "#shared/roundValue.js";
6
+ const minSampleSize = 5;
6
7
  const api = {
7
8
  endpoint: "termdb/boxplot",
8
9
  methods: {
@@ -124,7 +125,7 @@ function setHiddenPlots(term, plots) {
124
125
  return plots;
125
126
  }
126
127
  function setDescrStats(boxplot, sortedValues) {
127
- if (sortedValues.length < 5)
128
+ if (sortedValues.length < minSampleSize)
128
129
  return [{ id: "total", label: "Total", value: sortedValues.length }];
129
130
  const mean = sortedValues.reduce((s2, i) => s2 + i, 0) / sortedValues.length;
130
131
  let s = 0;
@@ -6,8 +6,11 @@ import { createCanvas } from "canvas";
6
6
  import { getOrderedLabels } from "../src/termdb.barchart.js";
7
7
  import summaryStats from "#shared/descriptive.stats.js";
8
8
  import { isNumericTerm } from "#shared/terms.js";
9
- import { getBinsDensity } from "#shared/violin.bins.js";
10
9
  import { numericBins, parseValues } from "./termdb.boxplot.ts";
10
+ import serverconfig from "../src/serverconfig.js";
11
+ import run_R from "../src/run_R.js";
12
+ import path from "path";
13
+ const minSampleSize = 5;
11
14
  const api = {
12
15
  endpoint: "termdb/violin",
13
16
  methods: {
@@ -73,7 +76,7 @@ async function trigger_getViolinPlotData(q, ds, genome) {
73
76
  const valuesObject = divideValues(q, data, sampleType);
74
77
  const result = setResponse(valuesObject, data, q, sampleType);
75
78
  await getWilcoxonData(q.divideTw, result);
76
- createCanvasImg(q, result, ds);
79
+ await createCanvasImg(q, result, ds);
77
80
  return result;
78
81
  }
79
82
  async function getWilcoxonData(divideTw, result) {
@@ -167,7 +170,7 @@ function setResponse(valuesObject, data, q, sampleType) {
167
170
  };
168
171
  return result;
169
172
  }
170
- function createCanvasImg(q, result, ds) {
173
+ async function createCanvasImg(q, result, ds) {
171
174
  if (!q.radius)
172
175
  q.radius = 5;
173
176
  if (q.radius <= 0)
@@ -177,6 +180,10 @@ function createCanvasImg(q, result, ds) {
177
180
  if (!q.strokeWidth)
178
181
  q.strokeWidth = 0.2;
179
182
  const refSize = q.radius * 4;
183
+ const plot2Values = {};
184
+ for (const plot of result.plots)
185
+ plot2Values[plot.label] = plot.values;
186
+ const densities = await getDensities(plot2Values);
180
187
  let axisScale;
181
188
  const useLog = q.unit == "log";
182
189
  if (useLog) {
@@ -188,12 +195,14 @@ function createCanvasImg(q, result, ds) {
188
195
  const scaledRadius = q.radius / q.devicePixelRatio;
189
196
  const arcEndAngle = scaledRadius * Math.PI;
190
197
  for (const plot of result.plots) {
198
+ plot.density = densities[plot.label];
191
199
  const canvas = createCanvas(width, height);
192
200
  const ctx = canvas.getContext("2d");
193
- ctx.strokeStyle = "rgba(0,0,0,0.8)";
201
+ const symbolOpacity = plot.density.densityMax == 0 ? 1 : 0.8;
202
+ ctx.strokeStyle = `rgba(0,0,0,${symbolOpacity})`;
194
203
  ctx.lineWidth = q.strokeWidth / q.devicePixelRatio;
195
- ctx.globalAlpha = 0.5;
196
- ctx.fillStyle = "#ffe6e6";
204
+ ctx.globalAlpha = symbolOpacity;
205
+ ctx.fillStyle = plot.values.length <= minSampleSize ? "black" : "#ffe6e6";
197
206
  if (q.devicePixelRatio != 1) {
198
207
  ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
199
208
  }
@@ -220,13 +229,43 @@ function createCanvasImg(q, result, ds) {
220
229
  ctx.stroke();
221
230
  });
222
231
  plot.src = canvas.toDataURL();
223
- plot.density = getBinsDensity(plot, q.isKDE, q.ticks);
224
232
  plot.summaryStats = summaryStats(plot.values).values;
225
- delete plot.values;
226
233
  }
227
234
  }
235
+ async function getDensity(values) {
236
+ const result = await getDensities({ plot: values });
237
+ return result.plot;
238
+ }
239
+ async function getDensities(plot2Values) {
240
+ const densityScript = path.join(serverconfig.binpath, "utils", "density.R");
241
+ const plot2Density = JSON.parse(await run_R(densityScript, JSON.stringify(plot2Values)));
242
+ const densities = {};
243
+ for (const plot in plot2Density) {
244
+ const result = plot2Density[plot];
245
+ const bins = [];
246
+ let densityMin = Infinity;
247
+ let densityMax = -Infinity;
248
+ let xMin = Infinity;
249
+ let xMax = -Infinity;
250
+ for (const [i, x] of Object.entries(result.x)) {
251
+ const density2 = result.y[i];
252
+ xMin = Math.min(xMin, x);
253
+ xMax = Math.max(xMax, x);
254
+ densityMin = Math.min(densityMin, density2);
255
+ densityMax = Math.max(densityMax, density2);
256
+ bins.push({ x0: x, density: density2 });
257
+ }
258
+ bins.unshift({ x0: xMin, density: densityMin });
259
+ bins.push({ x0: xMax, density: densityMin });
260
+ const density = { bins, densityMin, densityMax, minvalue: xMin, maxvalue: xMax };
261
+ densities[plot] = density;
262
+ }
263
+ return densities;
264
+ }
228
265
  export {
229
266
  api,
267
+ getDensities,
268
+ getDensity,
230
269
  getWilcoxonData,
231
270
  sortKey2values,
232
271
  trigger_getViolinPlotData