@sjcrh/proteinpaint-server 2.110.0 → 2.112.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 +2 -2
- package/routes/gdc.mafBuild.js +49 -12
- package/routes/termdb.DE.js +57 -14
- package/routes/termdb.boxplot.js +4 -8
- package/routes/termdb.config.js +15 -1
- package/routes/termdb.descrstats.js +2 -2
- package/routes/termdb.violin.js +50 -10
- package/routes/tileserver.js +36 -4
- package/routes/wsimages.js +34 -37
- package/src/app.js +11122 -10677
- package/utils/density.R +36 -0
- package/utils/edge.R +25 -36
- package/routes/clearwsisession.js +0 -51
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.112.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.112.0",
|
|
64
|
+
"@sjcrh/proteinpaint-shared": "2.112.0",
|
|
65
|
+
"@sjcrh/proteinpaint-types": "2.112.0",
|
|
66
66
|
"@types/express": "^5.0.0",
|
|
67
67
|
"@types/express-session": "^1.18.1",
|
|
68
68
|
"better-sqlite3": "^9.4.1",
|
|
@@ -3,8 +3,8 @@ 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";
|
|
7
|
+
import { getStdDev } from "#shared/descriptive.stats.js";
|
|
8
8
|
const minArrayLength = 3;
|
|
9
9
|
const minSD = 0.05;
|
|
10
10
|
const api = {
|
|
@@ -71,7 +71,7 @@ async function compute(q, ds, genome) {
|
|
|
71
71
|
const [acceptedVariables, skippedVariables] = Array.from(vtid2array.values()).reduce(
|
|
72
72
|
([accepted, skipped], t) => {
|
|
73
73
|
const grterThanOne = t.v1.length > minArrayLength && t.v2.length > minArrayLength;
|
|
74
|
-
const significantSD =
|
|
74
|
+
const significantSD = getStdDev(t.v1) > minSD && getStdDev(t.v2) > minSD;
|
|
75
75
|
const v = grterThanOne && significantSD ? accepted : skipped;
|
|
76
76
|
if (v === accepted)
|
|
77
77
|
accepted.push(t);
|
package/routes/gdc.mafBuild.js
CHANGED
|
@@ -46,22 +46,59 @@ 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 boundary = "
|
|
50
|
-
res.setHeader("
|
|
49
|
+
const boundary = "------------------------GDC-MAF-BUILD";
|
|
50
|
+
res.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
|
|
51
51
|
res.write(`--${boundary}`);
|
|
52
|
-
res.write(
|
|
53
|
-
res.write("\
|
|
52
|
+
res.write('\r\nContent-Disposition: form-data; name="gzfile"; filename="cohort.maf.gz"');
|
|
53
|
+
res.write("\r\nContent-Type: application/gzip\r\n\r\n");
|
|
54
54
|
res.flush();
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
try {
|
|
56
|
+
const streams = stream_rust("gdcmaf", JSON.stringify(arg), emitJson);
|
|
57
|
+
if (streams) {
|
|
58
|
+
const { rustStream, endStream } = streams;
|
|
59
|
+
res.on("close", () => {
|
|
60
|
+
if (res.writableEnded)
|
|
61
|
+
return;
|
|
62
|
+
try {
|
|
63
|
+
console.log("\n-- forced res.end() ---\n");
|
|
64
|
+
res.end();
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.log("error with forced res.end()", e);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
endStream();
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.log("error in calling endStream()", e);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
rustStream.pipe(res, { end: false }).on("error", (e) => {
|
|
75
|
+
console.log("rustStream.pipe().on(error)", e);
|
|
76
|
+
}).on("end", () => {
|
|
77
|
+
if (res.writableEnded)
|
|
78
|
+
return;
|
|
79
|
+
console.log("rustStream.on(end), trigger res.end()");
|
|
80
|
+
res.end();
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
emitJson({ error: "server error: undefined rustStream" });
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.log("error calling stream_rust(gdcmaf)", e);
|
|
87
|
+
}
|
|
57
88
|
function emitJson(data, end = true) {
|
|
58
|
-
res.
|
|
89
|
+
if (res.writableEnded)
|
|
90
|
+
return;
|
|
91
|
+
if (data) {
|
|
92
|
+
res.write(`\r
|
|
59
93
|
--${boundary}`);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
res.write('\r\nContent-Disposition: form-data; name="errors"');
|
|
95
|
+
res.write("\r\nContent-Type: application/x-jsonlines");
|
|
96
|
+
const json = typeof data === "string" ? data : JSON.stringify(data || { ok: true, status: "ok" });
|
|
97
|
+
res.write("\r\n\r\n" + json);
|
|
98
|
+
}
|
|
99
|
+
res.write(`\r
|
|
100
|
+
--${boundary}--\r
|
|
101
|
+
`);
|
|
65
102
|
mayLog("rust gdcmaf", Date.now() - t0);
|
|
66
103
|
if (end)
|
|
67
104
|
res.end();
|
package/routes/termdb.DE.js
CHANGED
|
@@ -7,6 +7,8 @@ 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";
|
|
11
|
+
import { get_header_txt } from "#src/utils.js";
|
|
10
12
|
const api = {
|
|
11
13
|
endpoint: "DEanalysis",
|
|
12
14
|
methods: {
|
|
@@ -59,6 +61,8 @@ function init({ genomes }) {
|
|
|
59
61
|
throw term_results2.error;
|
|
60
62
|
}
|
|
61
63
|
const results = await run_DE(req.query, ds, term_results, term_results2);
|
|
64
|
+
if (!results || !results.data)
|
|
65
|
+
throw "No data [termdb.DE.ts init()]";
|
|
62
66
|
res.send(results);
|
|
63
67
|
} catch (e) {
|
|
64
68
|
res.send({ status: "error", error: e.message || e });
|
|
@@ -210,9 +214,6 @@ async function run_DE(param, ds, term_results, term_results2) {
|
|
|
210
214
|
}
|
|
211
215
|
const sample_size_limit = 8;
|
|
212
216
|
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
217
|
const time12 = (/* @__PURE__ */ new Date()).valueOf();
|
|
217
218
|
const result2 = JSON.parse(
|
|
218
219
|
await run_R(path.join(serverconfig.binpath, "utils", "edge.R"), JSON.stringify(expression_input))
|
|
@@ -223,17 +224,19 @@ async function run_DE(param, ds, term_results, term_results2) {
|
|
|
223
224
|
mayLog("ql_imagePath:", ql_imagePath);
|
|
224
225
|
const mds_imagePath = path.join(serverconfig.cachedir, result2.edgeR_mds_image_name[0]);
|
|
225
226
|
mayLog("mds_imagePath:", mds_imagePath);
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
await readFileAndDelete(mds_imagePath, "mds_image", result2);
|
|
228
|
+
await readFileAndDelete(ql_imagePath, "ql_image", result2);
|
|
228
229
|
return {
|
|
229
230
|
data: result2.gene_data,
|
|
230
231
|
sample_size1,
|
|
231
232
|
sample_size2,
|
|
232
233
|
method: param.method,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
234
|
+
images: [
|
|
235
|
+
result2.mds_image,
|
|
236
|
+
// MDS image
|
|
237
|
+
result2.ql_image
|
|
238
|
+
// QL fit image
|
|
239
|
+
]
|
|
237
240
|
};
|
|
238
241
|
}
|
|
239
242
|
const time1 = (/* @__PURE__ */ new Date()).valueOf();
|
|
@@ -242,12 +245,52 @@ async function run_DE(param, ds, term_results, term_results2) {
|
|
|
242
245
|
param.method = "wilcoxon";
|
|
243
246
|
return { data: result, sample_size1, sample_size2, method: param.method };
|
|
244
247
|
}
|
|
245
|
-
async function readFileAndDelete(file) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
+
async function readFileAndDelete(file, key, response) {
|
|
249
|
+
const plot = await fs.promises.readFile(file);
|
|
250
|
+
const plotBuffer = Buffer.from(plot).toString("base64");
|
|
251
|
+
const obj = {
|
|
252
|
+
src: `data:image/png;base64,${plotBuffer}`,
|
|
253
|
+
size: imagesize(file),
|
|
254
|
+
key
|
|
255
|
+
};
|
|
256
|
+
response[key] = obj;
|
|
257
|
+
fs.unlink(file, (err) => {
|
|
258
|
+
if (err)
|
|
259
|
+
throw err;
|
|
248
260
|
});
|
|
249
|
-
|
|
261
|
+
}
|
|
262
|
+
async function validate_query_rnaseqGeneCount(ds, genome) {
|
|
263
|
+
const q = ds.queries.rnaseqGeneCount;
|
|
264
|
+
if (!q)
|
|
265
|
+
return;
|
|
266
|
+
if (!q.file)
|
|
267
|
+
throw "unknown data type for rnaseqGeneCount";
|
|
268
|
+
q.file = path.join(serverconfig.tpmasterdir, q.file);
|
|
269
|
+
{
|
|
270
|
+
let samples = [];
|
|
271
|
+
if (ds.queries.rnaseqGeneCount.storage_type == "text") {
|
|
272
|
+
samples = (await get_header_txt(q.file, null)).split(" ").slice(4);
|
|
273
|
+
} else if (ds.queries.rnaseqGeneCount.storage_type == "HDF5") {
|
|
274
|
+
const get_samples_from_hdf5 = {
|
|
275
|
+
input_file: q.file,
|
|
276
|
+
data_type: "get_samples"
|
|
277
|
+
};
|
|
278
|
+
const time1 = (/* @__PURE__ */ new Date()).valueOf();
|
|
279
|
+
const result = await run_rust("DEanalysis", JSON.stringify(get_samples_from_hdf5));
|
|
280
|
+
const time2 = (/* @__PURE__ */ new Date()).valueOf();
|
|
281
|
+
samples = result.split(",");
|
|
282
|
+
} else
|
|
283
|
+
throw "unknown storage type:" + ds.queries.rnaseqGeneCount.storage_type;
|
|
284
|
+
q.allSampleSet = new Set(samples);
|
|
285
|
+
const unknownSamples = [];
|
|
286
|
+
for (const n of q.allSampleSet) {
|
|
287
|
+
if (!ds.cohort.termdb.q.sampleName2id(n))
|
|
288
|
+
unknownSamples.push(n);
|
|
289
|
+
}
|
|
290
|
+
console.log(q.allSampleSet.size, `rnaseqGeneCount samples from ${ds.label}`);
|
|
291
|
+
}
|
|
250
292
|
}
|
|
251
293
|
export {
|
|
252
|
-
api
|
|
294
|
+
api,
|
|
295
|
+
validate_query_rnaseqGeneCount
|
|
253
296
|
};
|
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
|
+
import { getMean, getVariance } from "#shared/descriptive.stats.js";
|
|
6
7
|
const minSampleSize = 5;
|
|
7
8
|
const api = {
|
|
8
9
|
endpoint: "termdb/boxplot",
|
|
@@ -127,14 +128,9 @@ function setHiddenPlots(term, plots) {
|
|
|
127
128
|
function setDescrStats(boxplot, sortedValues) {
|
|
128
129
|
if (sortedValues.length < minSampleSize)
|
|
129
130
|
return [{ id: "total", label: "Total", value: sortedValues.length }];
|
|
130
|
-
const mean = sortedValues
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
s += Math.pow(v - mean, 2);
|
|
134
|
-
}
|
|
135
|
-
const sd = Math.sqrt(s / (sortedValues.length - 1));
|
|
136
|
-
const squareDiffs = sortedValues.map((x) => (x - mean) ** 2).reduce((a, b) => a + b, 0);
|
|
137
|
-
const variance = squareDiffs / (sortedValues.length - 1);
|
|
131
|
+
const mean = getMean(sortedValues);
|
|
132
|
+
const variance = getVariance(sortedValues);
|
|
133
|
+
const sd = Math.sqrt(variance);
|
|
138
134
|
return [
|
|
139
135
|
{ id: "total", label: "Total", value: sortedValues.length },
|
|
140
136
|
{ id: "min", label: "Minimum", value: roundValueAuto(sortedValues[0], true) },
|
package/routes/termdb.config.js
CHANGED
|
@@ -148,12 +148,26 @@ function addNonDictionaryQueries(c, ds, genome) {
|
|
|
148
148
|
q2.defaultBlock2GeneMode = q.defaultBlock2GeneMode;
|
|
149
149
|
if (q.snvindel) {
|
|
150
150
|
q2.snvindel = {
|
|
151
|
-
allowSNPs: q.snvindel.allowSNPs
|
|
151
|
+
allowSNPs: q.snvindel.allowSNPs,
|
|
152
|
+
// details{} lists default method for computing variants, can be modified and is part of state
|
|
153
|
+
// some of the stuff here are to provide user-selectable choices
|
|
154
|
+
// e.g. computing methods, info fields, populations.
|
|
155
|
+
details: q.snvindel.details,
|
|
156
|
+
populations: q.snvindel.populations
|
|
152
157
|
};
|
|
153
158
|
}
|
|
159
|
+
if (q.trackLst) {
|
|
160
|
+
q2.trackLst = q.trackLst;
|
|
161
|
+
}
|
|
154
162
|
if (q.svfusion) {
|
|
155
163
|
q2.svfusion = {};
|
|
156
164
|
}
|
|
165
|
+
if (q.ld) {
|
|
166
|
+
q2.ld = JSON.parse(JSON.stringify(q.ld));
|
|
167
|
+
for (const i of q2.ld.tracks) {
|
|
168
|
+
delete i.file;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
157
171
|
if (q.cnv) {
|
|
158
172
|
q2.cnv = {};
|
|
159
173
|
for (const k of [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { descrStatsPayload } from "#types/checkers";
|
|
2
|
-
import
|
|
2
|
+
import { summaryStats } from "#shared/descriptive.stats.js";
|
|
3
3
|
import { getData } from "#src/termdb.matrix.js";
|
|
4
4
|
const api = {
|
|
5
5
|
endpoint: "termdb/descrstats",
|
|
@@ -48,7 +48,7 @@ function init({ genomes }) {
|
|
|
48
48
|
values.push(Number(value));
|
|
49
49
|
}
|
|
50
50
|
if (values.length) {
|
|
51
|
-
result =
|
|
51
|
+
result = summaryStats(values);
|
|
52
52
|
} else {
|
|
53
53
|
result = {};
|
|
54
54
|
}
|
package/routes/termdb.violin.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { violinPayload } from "#types/checkers";
|
|
2
|
-
import { scaleLinear, scaleLog } from "d3";
|
|
2
|
+
import { scaleLinear, scaleLog, extent } from "d3";
|
|
3
3
|
import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
4
4
|
import { getData } from "../src/termdb.matrix.js";
|
|
5
5
|
import { createCanvas } from "canvas";
|
|
6
6
|
import { getOrderedLabels } from "../src/termdb.barchart.js";
|
|
7
|
-
import summaryStats from "#shared/descriptive.stats.js";
|
|
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, result.min, result.max);
|
|
181
187
|
let axisScale;
|
|
182
188
|
const useLog = q.unit == "log";
|
|
183
189
|
if (useLog) {
|
|
@@ -189,12 +195,15 @@ 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];
|
|
199
|
+
const noDensityRendered = plot.density.densityMax == 0;
|
|
192
200
|
const canvas = createCanvas(width, height);
|
|
193
201
|
const ctx = canvas.getContext("2d");
|
|
194
|
-
|
|
202
|
+
const symbolOpacity = noDensityRendered ? 1 : 0.6;
|
|
203
|
+
ctx.strokeStyle = `rgba(0,0,0,${symbolOpacity})`;
|
|
195
204
|
ctx.lineWidth = q.strokeWidth / q.devicePixelRatio;
|
|
196
|
-
ctx.globalAlpha =
|
|
197
|
-
ctx.fillStyle =
|
|
205
|
+
ctx.globalAlpha = symbolOpacity;
|
|
206
|
+
ctx.fillStyle = "#ffe6e6";
|
|
198
207
|
if (q.devicePixelRatio != 1) {
|
|
199
208
|
ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
|
|
200
209
|
}
|
|
@@ -221,13 +230,44 @@ function createCanvasImg(q, result, ds) {
|
|
|
221
230
|
ctx.stroke();
|
|
222
231
|
});
|
|
223
232
|
plot.src = canvas.toDataURL();
|
|
224
|
-
plot.density = getBinsDensity(plot, q.isKDE, q.ticks);
|
|
225
233
|
plot.summaryStats = summaryStats(plot.values).values;
|
|
226
|
-
delete plot.values;
|
|
227
234
|
}
|
|
228
235
|
}
|
|
236
|
+
async function getDensity(values) {
|
|
237
|
+
const [min, max] = extent(values);
|
|
238
|
+
const result = await getDensities({ plot: values }, min, max);
|
|
239
|
+
return result.plot;
|
|
240
|
+
}
|
|
241
|
+
async function getDensities(plot2Values, min, max) {
|
|
242
|
+
const densityScript = path.join(serverconfig.binpath, "utils", "density.R");
|
|
243
|
+
const plot2Density = JSON.parse(await run_R(densityScript, JSON.stringify({ plot2Values, min, max })));
|
|
244
|
+
const densities = {};
|
|
245
|
+
for (const plot in plot2Density) {
|
|
246
|
+
const result = plot2Density[plot];
|
|
247
|
+
const bins = [];
|
|
248
|
+
let densityMin = Infinity;
|
|
249
|
+
let densityMax = -Infinity;
|
|
250
|
+
let xMin = Infinity;
|
|
251
|
+
let xMax = -Infinity;
|
|
252
|
+
for (const [i, x] of Object.entries(result.x)) {
|
|
253
|
+
const density2 = result.y[i];
|
|
254
|
+
xMin = Math.min(xMin, x);
|
|
255
|
+
xMax = Math.max(xMax, x);
|
|
256
|
+
densityMin = Math.min(densityMin, density2);
|
|
257
|
+
densityMax = Math.max(densityMax, density2);
|
|
258
|
+
bins.push({ x0: x, density: density2 });
|
|
259
|
+
}
|
|
260
|
+
bins.unshift({ x0: xMin, density: densityMin });
|
|
261
|
+
bins.push({ x0: xMax, density: densityMin });
|
|
262
|
+
const density = { bins, densityMin, densityMax, minvalue: xMin, maxvalue: xMax };
|
|
263
|
+
densities[plot] = density;
|
|
264
|
+
}
|
|
265
|
+
return densities;
|
|
266
|
+
}
|
|
229
267
|
export {
|
|
230
268
|
api,
|
|
269
|
+
getDensities,
|
|
270
|
+
getDensity,
|
|
231
271
|
getWilcoxonData,
|
|
232
272
|
sortKey2values,
|
|
233
273
|
trigger_getViolinPlotData
|
package/routes/tileserver.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { tilePayload } from "#types/checkers";
|
|
2
2
|
import ky from "ky";
|
|
3
|
+
import { TileServerShardingAlgorithm } from "#src/sharding/TileServerShardingAlgorithm.ts";
|
|
4
|
+
import { ShardManager } from "#src/sharding/ShardManager.ts";
|
|
5
|
+
import SessionManager from "#src/wsisessions/SessionManager.ts";
|
|
3
6
|
import serverconfig from "#src/serverconfig.js";
|
|
7
|
+
import path from "path";
|
|
4
8
|
const api = {
|
|
5
|
-
endpoint: `tileserver/layer/slide/:
|
|
9
|
+
endpoint: `tileserver/layer/slide/:sessionId/zoomify/:TileGroup/:z-:x-:y@1x.jpg`,
|
|
6
10
|
methods: {
|
|
7
11
|
get: {
|
|
8
12
|
...tilePayload,
|
|
@@ -14,12 +18,40 @@ const api = {
|
|
|
14
18
|
}
|
|
15
19
|
}
|
|
16
20
|
};
|
|
17
|
-
function init() {
|
|
21
|
+
function init({ genomes }) {
|
|
18
22
|
return async (req, res) => {
|
|
19
23
|
try {
|
|
20
|
-
const {
|
|
21
|
-
const
|
|
24
|
+
const { sessionId, TileGroup, z, x, y } = req.params;
|
|
25
|
+
const query = req.query;
|
|
26
|
+
const wsiImage = query.wsi_image;
|
|
27
|
+
if (!wsiImage)
|
|
28
|
+
throw new Error("No wsi_image param provided");
|
|
29
|
+
const dslabel = query.dslabel;
|
|
30
|
+
if (!dslabel)
|
|
31
|
+
throw new Error("No dslabel param provided");
|
|
32
|
+
const g = genomes[query.genome];
|
|
33
|
+
if (!g)
|
|
34
|
+
throw new Error("Invalid genome name");
|
|
35
|
+
const ds = g.datasets[query.dslabel];
|
|
36
|
+
if (!ds)
|
|
37
|
+
throw new Error("Invalid dataset name");
|
|
38
|
+
const sampleId = query.sample_id;
|
|
39
|
+
if (!sampleId)
|
|
40
|
+
throw new Error("Invalid sample id");
|
|
41
|
+
const mount = serverconfig.features?.tileserver?.mount;
|
|
42
|
+
if (!mount)
|
|
43
|
+
throw new Error("No mount available for TileServer");
|
|
44
|
+
const wsiImagePath = path.join(`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`, wsiImage);
|
|
45
|
+
if (!wsiImage)
|
|
46
|
+
throw new Error("Invalid wsi_image");
|
|
47
|
+
const shardManager = ShardManager.getInstance();
|
|
48
|
+
const tileServer = await shardManager.shardingAlgorithmsMap?.get(TileServerShardingAlgorithm.TILE_SERVER_SHARDING_KEY)?.getShard(wsiImagePath);
|
|
49
|
+
if (!tileServer) {
|
|
50
|
+
throw new Error("No tile server");
|
|
51
|
+
}
|
|
52
|
+
const url = `${tileServer.url}/tileserver/layer/slide/${sessionId}/zoomify/${TileGroup}/${z}-${x}-${y}@1x.jpg`;
|
|
22
53
|
const response = await ky.get(url, { timeout: 12e4 });
|
|
54
|
+
await SessionManager.getInstance().updateSession(wsiImagePath);
|
|
23
55
|
const buffer = await response.arrayBuffer();
|
|
24
56
|
res.status(response.status).send(Buffer.from(buffer));
|
|
25
57
|
} catch (error) {
|
package/routes/wsimages.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import ky from "ky";
|
|
2
2
|
import qs from "qs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import serverconfig from "#src/serverconfig.js";
|
|
5
4
|
import { CookieJar } from "tough-cookie";
|
|
6
5
|
import { promisify } from "util";
|
|
7
6
|
import { wsImagesPayload } from "#types/checkers";
|
|
8
|
-
import SessionManager
|
|
9
|
-
import
|
|
7
|
+
import SessionManager from "../src/wsisessions/SessionManager.ts";
|
|
8
|
+
import { ShardManager } from "#src/sharding/ShardManager.ts";
|
|
9
|
+
import { TileServerShardingAlgorithm } from "#src/sharding/TileServerShardingAlgorithm.ts";
|
|
10
|
+
import serverconfig from "#src/serverconfig.js";
|
|
10
11
|
const routePath = "wsimages";
|
|
11
12
|
const api = {
|
|
12
13
|
endpoint: `${routePath}`,
|
|
@@ -40,28 +41,20 @@ function init({ genomes }) {
|
|
|
40
41
|
const cookieJar = new CookieJar();
|
|
41
42
|
const setCookie = promisify(cookieJar.setCookie.bind(cookieJar));
|
|
42
43
|
const getCookieString = promisify(cookieJar.getCookieString.bind(cookieJar));
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
sessionData = await sessionManager.getSession(wsimage);
|
|
50
|
-
}
|
|
51
|
-
const sessionId = sessionData ? sessionData.imageSessionId : await getSessionId(cookieJar, getCookieString, setCookie, wsimage, ds, sampleId);
|
|
52
|
-
if (serverconfig.redis && sessionManager) {
|
|
53
|
-
await manageUserSession(sessionManager, sessionData, wsimage, userSessionId, sessionId);
|
|
54
|
-
}
|
|
55
|
-
const getWsiImageResponse = await getWsiImageDimensions(sessionId, getCookieString);
|
|
44
|
+
const mount = serverconfig.features?.tileserver?.mount;
|
|
45
|
+
if (!mount)
|
|
46
|
+
throw new Error("No mount available for TileServer");
|
|
47
|
+
const wsiImagePath = path.join(`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`, wsimage);
|
|
48
|
+
const sessionId = await getSessionId(cookieJar, getCookieString, setCookie, wsiImagePath);
|
|
49
|
+
const getWsiImageResponse = await getWsiImageDimensions(sessionId, getCookieString, wsiImagePath);
|
|
56
50
|
const payload = {
|
|
57
51
|
status: "ok",
|
|
58
52
|
wsiSessionId: sessionId,
|
|
59
|
-
browserImageInstanceId: serverconfig.redis ? userSessionId : void 0,
|
|
60
53
|
slide_dimensions: getWsiImageResponse.slide_dimensions
|
|
61
54
|
};
|
|
62
55
|
res.status(200).json(payload);
|
|
63
56
|
} catch (e) {
|
|
64
|
-
console.
|
|
57
|
+
console.warn(e);
|
|
65
58
|
res.status(500).send({
|
|
66
59
|
status: "error",
|
|
67
60
|
error: e.message || e
|
|
@@ -69,21 +62,28 @@ function init({ genomes }) {
|
|
|
69
62
|
}
|
|
70
63
|
};
|
|
71
64
|
}
|
|
72
|
-
async function getSessionId(cookieJar, getCookieString, setCookie, wsimage
|
|
73
|
-
|
|
65
|
+
async function getSessionId(cookieJar, getCookieString, setCookie, wsimage) {
|
|
66
|
+
const sessionManager = SessionManager.getInstance();
|
|
67
|
+
const invalidateResult = await sessionManager.syncAndInvalidateSessions(wsimage);
|
|
68
|
+
if (!invalidateResult)
|
|
69
|
+
throw new Error("Session invalidation failed");
|
|
70
|
+
const sessionData = await sessionManager.getSession(wsimage);
|
|
71
|
+
if (sessionData) {
|
|
72
|
+
return sessionData.imageSessionId;
|
|
73
|
+
}
|
|
74
|
+
const tileServer = await sessionManager.getTileServerShard(wsimage);
|
|
75
|
+
if (!tileServer)
|
|
76
|
+
throw new Error("No TileServer shard available");
|
|
77
|
+
await ky.get(`${tileServer.url}/tileserver/session_id`, {
|
|
74
78
|
timeout: 5e4,
|
|
75
79
|
hooks: getHooks(cookieJar, getCookieString, setCookie)
|
|
76
80
|
});
|
|
77
|
-
const cookieString = await getCookieString(`${
|
|
81
|
+
const cookieString = await getCookieString(`${tileServer.url}/tileserver/session_id`);
|
|
78
82
|
const sessionId = cookieString.match(/session_id=([^;]*)/)?.[1];
|
|
79
83
|
if (!sessionId)
|
|
80
84
|
throw new Error("session_id not found");
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
wsimage
|
|
84
|
-
);
|
|
85
|
-
const data = qs.stringify({ slide_path: sampleWsiTileServer });
|
|
86
|
-
await ky.put(`${serverconfig.tileServerURL}/tileserver/slide`, {
|
|
85
|
+
const data = qs.stringify({ slide_path: wsimage });
|
|
86
|
+
await ky.put(`${tileServer.url}/tileserver/slide`, {
|
|
87
87
|
body: data,
|
|
88
88
|
timeout: 5e4,
|
|
89
89
|
headers: {
|
|
@@ -92,19 +92,16 @@ async function getSessionId(cookieJar, getCookieString, setCookie, wsimage, ds,
|
|
|
92
92
|
},
|
|
93
93
|
hooks: getHooks(cookieJar, getCookieString, setCookie)
|
|
94
94
|
});
|
|
95
|
+
await sessionManager.setSession(wsimage, sessionId, tileServer);
|
|
95
96
|
return sessionId;
|
|
96
97
|
}
|
|
97
|
-
async function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
sessionData.userSessionIds.push(userId);
|
|
103
|
-
await sessionManager.setSession(wsimage, sessionData);
|
|
98
|
+
async function getWsiImageDimensions(sessionId, getCookieString, wsimage) {
|
|
99
|
+
const shardManager = ShardManager.getInstance();
|
|
100
|
+
const tileServer = await shardManager.shardingAlgorithmsMap?.get(TileServerShardingAlgorithm.TILE_SERVER_SHARDING_KEY)?.getShard(wsimage);
|
|
101
|
+
if (!tileServer) {
|
|
102
|
+
throw new Error("No tile server");
|
|
104
103
|
}
|
|
105
|
-
}
|
|
106
|
-
async function getWsiImageDimensions(sessionId, getCookieString) {
|
|
107
|
-
return await ky.get(`${serverconfig.tileServerURL}/tileserver/slide`, {
|
|
104
|
+
return await ky.get(`${tileServer.url}/tileserver/slide`, {
|
|
108
105
|
timeout: 12e4,
|
|
109
106
|
hooks: {
|
|
110
107
|
beforeRequest: [
|