@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 +4 -4
- package/routes/correlationVolcano.js +11 -1
- package/routes/gdc.mafBuild.js +20 -13
- package/routes/termdb.DE.js +42 -12
- package/routes/termdb.boxplot.js +2 -1
- package/routes/termdb.violin.js +47 -8
- package/src/app.js +5013 -4927
- package/utils/density.R +29 -0
- package/utils/edge.R +75 -55
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
64
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
65
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
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
|
};
|
package/routes/gdc.mafBuild.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ky from "ky";
|
|
2
2
|
import { joinUrl } from "#shared/joinUrl.js";
|
|
3
|
-
import {
|
|
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
|
|
50
|
-
res.setHeader("
|
|
51
|
-
res.
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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)
|
package/routes/termdb.DE.js
CHANGED
|
@@ -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
|
-
|
|
213
|
-
|
|
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() -
|
|
220
|
+
mayLog("Time taken to run edgeR:", Date.now() - time12, "ms");
|
|
220
221
|
param.method = "edgeR";
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
mayLog("
|
|
225
|
-
|
|
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
|
};
|
package/routes/termdb.boxplot.js
CHANGED
|
@@ -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 <
|
|
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;
|
package/routes/termdb.violin.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|