@sjcrh/proteinpaint-server 2.110.0 → 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.110.0",
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.110.0",
64
- "@sjcrh/proteinpaint-shared": "2.109.1",
65
- "@sjcrh/proteinpaint-types": "2.110.0",
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
  };
@@ -7,6 +7,7 @@ import { get_ds_tdb } from "../src/termdb.js";
7
7
  import run_R from "../src/run_R.js";
8
8
  import { mayLog } from "#src/helpers.ts";
9
9
  import serverconfig from "../src/serverconfig.js";
10
+ import imagesize from "image-size";
10
11
  const api = {
11
12
  endpoint: "DEanalysis",
12
13
  methods: {
@@ -59,6 +60,8 @@ function init({ genomes }) {
59
60
  throw term_results2.error;
60
61
  }
61
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()]";
62
65
  res.send(results);
63
66
  } catch (e) {
64
67
  res.send({ status: "error", error: e.message || e });
@@ -210,9 +213,6 @@ async function run_DE(param, ds, term_results, term_results2) {
210
213
  }
211
214
  const sample_size_limit = 8;
212
215
  if (group1names.length <= sample_size_limit && group2names.length <= sample_size_limit || param.method == "edgeR") {
213
- if (param.method == "edgeR") {
214
- expression_input.VarGenes = param.VarGenes;
215
- }
216
216
  const time12 = (/* @__PURE__ */ new Date()).valueOf();
217
217
  const result2 = JSON.parse(
218
218
  await run_R(path.join(serverconfig.binpath, "utils", "edge.R"), JSON.stringify(expression_input))
@@ -223,16 +223,16 @@ async function run_DE(param, ds, term_results, term_results2) {
223
223
  mayLog("ql_imagePath:", ql_imagePath);
224
224
  const mds_imagePath = path.join(serverconfig.cachedir, result2.edgeR_mds_image_name[0]);
225
225
  mayLog("mds_imagePath:", mds_imagePath);
226
- const mds_base64Image = await readFileAndDelete(mds_imagePath);
227
- const ql_base64Image = await readFileAndDelete(ql_imagePath);
226
+ await readFileAndDelete(mds_imagePath, "mds_image", result2);
227
+ await readFileAndDelete(ql_imagePath, "ql_image", result2);
228
228
  return {
229
229
  data: result2.gene_data,
230
230
  sample_size1,
231
231
  sample_size2,
232
232
  method: param.method,
233
- ql_image: ql_base64Image,
233
+ ql_image: result2.ql_image,
234
234
  // QL fit image
235
- mds_image: mds_base64Image
235
+ mds_image: result2.mds_image
236
236
  // MDS image
237
237
  };
238
238
  }
@@ -242,11 +242,19 @@ async function run_DE(param, ds, term_results, term_results2) {
242
242
  param.method = "wilcoxon";
243
243
  return { data: result, sample_size1, sample_size2, method: param.method };
244
244
  }
245
- async function readFileAndDelete(file) {
246
- const data = await fs.promises.readFile(file);
247
- fs.unlink(file, () => {
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;
248
257
  });
249
- return Buffer.from(data).toString("base64");
250
258
  }
251
259
  export {
252
260
  api
@@ -6,8 +6,10 @@ 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";
11
13
  const minSampleSize = 5;
12
14
  const api = {
13
15
  endpoint: "termdb/violin",
@@ -74,7 +76,7 @@ async function trigger_getViolinPlotData(q, ds, genome) {
74
76
  const valuesObject = divideValues(q, data, sampleType);
75
77
  const result = setResponse(valuesObject, data, q, sampleType);
76
78
  await getWilcoxonData(q.divideTw, result);
77
- createCanvasImg(q, result, ds);
79
+ await createCanvasImg(q, result, ds);
78
80
  return result;
79
81
  }
80
82
  async function getWilcoxonData(divideTw, result) {
@@ -168,7 +170,7 @@ function setResponse(valuesObject, data, q, sampleType) {
168
170
  };
169
171
  return result;
170
172
  }
171
- function createCanvasImg(q, result, ds) {
173
+ async function createCanvasImg(q, result, ds) {
172
174
  if (!q.radius)
173
175
  q.radius = 5;
174
176
  if (q.radius <= 0)
@@ -178,6 +180,10 @@ function createCanvasImg(q, result, ds) {
178
180
  if (!q.strokeWidth)
179
181
  q.strokeWidth = 0.2;
180
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);
181
187
  let axisScale;
182
188
  const useLog = q.unit == "log";
183
189
  if (useLog) {
@@ -189,11 +195,13 @@ function createCanvasImg(q, result, ds) {
189
195
  const scaledRadius = q.radius / q.devicePixelRatio;
190
196
  const arcEndAngle = scaledRadius * Math.PI;
191
197
  for (const plot of result.plots) {
198
+ plot.density = densities[plot.label];
192
199
  const canvas = createCanvas(width, height);
193
200
  const ctx = canvas.getContext("2d");
194
- 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})`;
195
203
  ctx.lineWidth = q.strokeWidth / q.devicePixelRatio;
196
- ctx.globalAlpha = 0.5;
204
+ ctx.globalAlpha = symbolOpacity;
197
205
  ctx.fillStyle = plot.values.length <= minSampleSize ? "black" : "#ffe6e6";
198
206
  if (q.devicePixelRatio != 1) {
199
207
  ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
@@ -221,13 +229,43 @@ function createCanvasImg(q, result, ds) {
221
229
  ctx.stroke();
222
230
  });
223
231
  plot.src = canvas.toDataURL();
224
- plot.density = getBinsDensity(plot, q.isKDE, q.ticks);
225
232
  plot.summaryStats = summaryStats(plot.values).values;
226
- delete plot.values;
227
233
  }
228
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
+ }
229
265
  export {
230
266
  api,
267
+ getDensities,
268
+ getDensity,
231
269
  getWilcoxonData,
232
270
  sortKey2values,
233
271
  trigger_getViolinPlotData