@sjcrh/proteinpaint-server 2.142.0 → 2.143.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/dataset/protected.test.js +1 -2
- package/dataset/termdb.test.js +3 -2
- package/package.json +9 -8
- package/routes/aiProjectAdmin.js +46 -60
- package/routes/aiProjectSelectedWSImages.js +161 -0
- package/routes/brainImaging.js +9 -18
- package/routes/brainImagingSamples.js +2 -4
- package/routes/burden.js +13 -26
- package/routes/correlationVolcano.js +18 -36
- package/routes/dataset.js +6 -12
- package/routes/deleteWSIAnnotation.js +75 -0
- package/routes/dsdata.js +7 -14
- package/routes/dzimages.js +4 -8
- package/routes/gdc.grin2.list.js +13 -26
- package/routes/gdc.grin2.run.js +3 -6
- package/routes/gdc.maf.js +8 -16
- package/routes/gdc.mafBuild.js +14 -28
- package/routes/gene2canonicalisoform.js +4 -8
- package/routes/genelookup.js +2 -4
- package/routes/genesetEnrichment.js +6 -12
- package/routes/genesetOverrepresentation.js +1 -2
- package/routes/genomes.js +1 -2
- package/routes/grin2.js +13 -17
- package/routes/healthcheck.js +3 -6
- package/routes/hicdata.js +4 -8
- package/routes/hicgenome.js +4 -8
- package/routes/hicstat.js +2 -4
- package/routes/img.js +1 -2
- package/routes/isoformlst.js +6 -12
- package/routes/ntseq.js +4 -8
- package/routes/pdomain.js +5 -10
- package/routes/sampledzimages.js +2 -4
- package/routes/samplewsimages.js +3 -67
- package/routes/saveWSIAnnotation.js +100 -0
- package/routes/snp.js +9 -18
- package/routes/termdb.DE.js +23 -46
- package/routes/termdb.boxplot.js +84 -84
- package/routes/termdb.categories.js +9 -18
- package/routes/termdb.cluster.js +23 -46
- package/routes/termdb.cohort.summary.js +3 -6
- package/routes/termdb.cohorts.js +4 -8
- package/routes/termdb.config.js +32 -64
- package/routes/termdb.descrstats.js +6 -12
- package/routes/termdb.filterTermValues.js +4 -8
- package/routes/termdb.numericcategories.js +5 -10
- package/routes/termdb.percentile.js +6 -12
- package/routes/termdb.profileFormScores.js +12 -24
- package/routes/termdb.profileScores.js +7 -14
- package/routes/termdb.rootterm.js +4 -8
- package/routes/termdb.sampleImages.js +4 -8
- package/routes/termdb.singleSampleMutation.js +9 -18
- package/routes/termdb.singlecellDEgenes.js +4 -8
- package/routes/termdb.singlecellData.js +4 -8
- package/routes/termdb.singlecellSamples.js +28 -56
- package/routes/termdb.termchildren.js +5 -10
- package/routes/termdb.termsbyids.js +4 -8
- package/routes/termdb.topMutatedGenes.js +15 -30
- package/routes/termdb.topTermsByType.js +9 -18
- package/routes/termdb.topVariablyExpressedGenes.js +13 -26
- package/routes/termdb.violin.js +124 -135
- package/routes/tileserver.js +14 -15
- package/routes/wsimages.js +42 -46
- package/routes/wsisamples.js +3 -6
- package/src/app.js +4345 -6708
- package/routes/sampleWsiAiApi.js +0 -33
package/routes/termdb.violin.js
CHANGED
|
@@ -27,31 +27,25 @@ function init({ genomes }) {
|
|
|
27
27
|
let data;
|
|
28
28
|
try {
|
|
29
29
|
const g = genomes[q.genome];
|
|
30
|
-
if (!g)
|
|
31
|
-
throw "invalid genome name";
|
|
30
|
+
if (!g) throw "invalid genome name";
|
|
32
31
|
const ds = g.datasets?.[q.dslabel];
|
|
33
|
-
if (!ds)
|
|
34
|
-
throw "invalid ds";
|
|
32
|
+
if (!ds) throw "invalid ds";
|
|
35
33
|
data = await trigger_getViolinPlotData(q, ds);
|
|
36
34
|
} catch (e) {
|
|
37
35
|
data = { error: e?.message || e };
|
|
38
|
-
if (e instanceof Error && e.stack)
|
|
39
|
-
console.log(e);
|
|
36
|
+
if (e instanceof Error && e.stack) console.log(e);
|
|
40
37
|
}
|
|
41
38
|
res.send(data);
|
|
42
39
|
};
|
|
43
40
|
}
|
|
44
41
|
async function trigger_getViolinPlotData(q, ds) {
|
|
45
|
-
if (typeof q.tw?.term != "object" || typeof q.tw?.q != "object")
|
|
46
|
-
throw "q.tw not of {term,q}";
|
|
42
|
+
if (typeof q.tw?.term != "object" || typeof q.tw?.q != "object") throw "q.tw not of {term,q}";
|
|
47
43
|
const term = q.tw.term;
|
|
48
|
-
if (!q.tw.q.mode)
|
|
49
|
-
|
|
50
|
-
if (!isNumericTerm(term) && term.type !== "survival")
|
|
51
|
-
throw "term type is not numeric or survival";
|
|
44
|
+
if (!q.tw.q.mode) q.tw.q.mode = "continuous";
|
|
45
|
+
if (!isNumericTerm(term) && term.type !== "survival") throw "term type is not numeric or survival";
|
|
52
46
|
const terms = [q.tw];
|
|
53
|
-
if (q.
|
|
54
|
-
|
|
47
|
+
if (q.overlayTw) terms.push(q.overlayTw);
|
|
48
|
+
if (q.divideTw) terms.push(q.divideTw);
|
|
55
49
|
const data = await getData(
|
|
56
50
|
{
|
|
57
51
|
terms,
|
|
@@ -63,68 +57,72 @@ async function trigger_getViolinPlotData(q, ds) {
|
|
|
63
57
|
ds
|
|
64
58
|
);
|
|
65
59
|
const sampleType = `All ${data.sampleType?.plural_name || "samples"}`;
|
|
66
|
-
if (data.error)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
q.
|
|
71
|
-
data.refs.byTermId[q.divideTw.$id]?.bins,
|
|
60
|
+
if (data.error) throw data.error;
|
|
61
|
+
if (q.overlayTw && data.refs.byTermId[q.overlayTw.$id]) {
|
|
62
|
+
data.refs.byTermId[q.overlayTw.$id].orderedLabels = getOrderedLabels(
|
|
63
|
+
q.overlayTw,
|
|
64
|
+
data.refs.byTermId[q.overlayTw.$id]?.bins,
|
|
72
65
|
void 0,
|
|
73
|
-
q.
|
|
66
|
+
q.overlayTw.q
|
|
74
67
|
);
|
|
75
68
|
}
|
|
76
|
-
if (q.scale)
|
|
77
|
-
setScaleData(q, data, q.tw);
|
|
69
|
+
if (q.scale) setScaleData(q, data, q.tw);
|
|
78
70
|
const valuesObject = divideValues(q, data, sampleType);
|
|
79
|
-
const result = setResponse(valuesObject, data, q
|
|
80
|
-
|
|
71
|
+
const result = setResponse(valuesObject, data, q);
|
|
72
|
+
if (q.overlayTw) await getWilcoxonData(result);
|
|
81
73
|
await createCanvasImg(q, result, ds);
|
|
82
74
|
return result;
|
|
83
75
|
}
|
|
84
|
-
async function getWilcoxonData(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
76
|
+
async function getWilcoxonData(result) {
|
|
77
|
+
for (const k of Object.keys(result.charts)) {
|
|
78
|
+
const chart = result.charts[k];
|
|
79
|
+
const numPlots = chart.plots.length;
|
|
80
|
+
if (numPlots < 2) continue;
|
|
81
|
+
const wilcoxInput = [];
|
|
82
|
+
for (let i = 0; i < numPlots; i++) {
|
|
83
|
+
const group1_id = chart.plots[i].label;
|
|
84
|
+
const group1_values = chart.plots[i].values;
|
|
85
|
+
for (let j = i + 1; j < numPlots; j++) {
|
|
86
|
+
const group2_id = chart.plots[j].label;
|
|
87
|
+
const group2_values = chart.plots[j].values;
|
|
88
|
+
wilcoxInput.push({ group1_id, group1_values, group2_id, group2_values });
|
|
89
|
+
}
|
|
98
90
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
const wilcoxOutput = JSON.parse(await run_rust("wilcoxon", JSON.stringify(wilcoxInput)));
|
|
92
|
+
chart.pvalues = [];
|
|
93
|
+
for (const test of wilcoxOutput) {
|
|
94
|
+
if (test.pvalue == null || test.pvalue == "null") {
|
|
95
|
+
chart.pvalues.push([{ value: test.group1_id }, { value: test.group2_id }, { html: "NA" }]);
|
|
96
|
+
} else {
|
|
97
|
+
chart.pvalues.push([{ value: test.group1_id }, { value: test.group2_id }, { html: test.pvalue.toPrecision(4) }]);
|
|
98
|
+
}
|
|
106
99
|
}
|
|
107
100
|
}
|
|
108
101
|
}
|
|
109
102
|
function setScaleData(q, data, tw) {
|
|
110
|
-
if (!q.scale)
|
|
111
|
-
return;
|
|
103
|
+
if (!q.scale) return;
|
|
112
104
|
const scale = Number(q.scale);
|
|
113
105
|
for (const val of Object.values(data.samples)) {
|
|
114
|
-
if (!tw.$id || !val[tw.$id])
|
|
115
|
-
|
|
116
|
-
if (tw.term.values?.[val[tw.$id]?.value]?.uncomputable)
|
|
117
|
-
continue;
|
|
106
|
+
if (!tw.$id || !val[tw.$id]) continue;
|
|
107
|
+
if (tw.term.values?.[val[tw.$id]?.value]?.uncomputable) continue;
|
|
118
108
|
val[tw.$id].key = val[tw.$id].key / scale;
|
|
119
109
|
val[tw.$id].value = val[tw.$id].value / scale;
|
|
120
110
|
}
|
|
121
111
|
}
|
|
122
112
|
function divideValues(q, data, sampleType) {
|
|
123
|
-
const overlayTerm = q.
|
|
113
|
+
const overlayTerm = q.overlayTw;
|
|
114
|
+
const divideTerm = q.divideTw;
|
|
124
115
|
const useLog = q.unit == "log";
|
|
125
|
-
const { absMax, absMin,
|
|
116
|
+
const { absMax, absMin, chart2plot2values, uncomputableValues } = parseValues(
|
|
117
|
+
q,
|
|
118
|
+
data,
|
|
119
|
+
sampleType,
|
|
120
|
+
useLog,
|
|
121
|
+
overlayTerm,
|
|
122
|
+
divideTerm
|
|
123
|
+
);
|
|
126
124
|
return {
|
|
127
|
-
|
|
125
|
+
chart2plot2values,
|
|
128
126
|
min: absMin,
|
|
129
127
|
max: absMax,
|
|
130
128
|
uncomputableValues: sortObj(uncomputableValues)
|
|
@@ -133,106 +131,97 @@ function divideValues(q, data, sampleType) {
|
|
|
133
131
|
function sortObj(object) {
|
|
134
132
|
return Object.fromEntries(Object.entries(object).sort(([, a], [, b]) => a - b));
|
|
135
133
|
}
|
|
136
|
-
function
|
|
134
|
+
function sortPlot2Values(data, plot2values, overlayTerm) {
|
|
137
135
|
const orderedLabels = overlayTerm?.$id ? data.refs.byTermId[overlayTerm.$id]?.keyOrder : void 0;
|
|
138
|
-
|
|
139
|
-
[...
|
|
136
|
+
plot2values = new Map(
|
|
137
|
+
[...plot2values].sort(
|
|
140
138
|
orderedLabels ? (a, b) => orderedLabels.indexOf(a[0]) - orderedLabels.indexOf(b[0]) : overlayTerm?.term?.type === "categorical" ? (a, b) => b[1].length - a[1].length : overlayTerm?.term?.type === "condition" ? (a, b) => Number(a[0]) - Number(b[0]) : (a, b) => a.toString().replace(/[^a-zA-Z0-9<]/g, "").localeCompare(b.toString().replace(/[^a-zA-Z0-9<]/g, ""), void 0, { numeric: true })
|
|
141
139
|
)
|
|
142
140
|
);
|
|
143
|
-
return
|
|
141
|
+
return plot2values;
|
|
144
142
|
}
|
|
145
|
-
function setResponse(valuesObject, data, q
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
for (const [
|
|
149
|
-
|
|
143
|
+
function setResponse(valuesObject, data, q) {
|
|
144
|
+
const charts = {};
|
|
145
|
+
const overlayTerm = q.overlayTw;
|
|
146
|
+
for (const [chart, plot2values] of valuesObject.chart2plot2values) {
|
|
147
|
+
const plots = [];
|
|
148
|
+
for (const [plot, values] of sortPlot2Values(data, plot2values, overlayTerm)) {
|
|
150
149
|
plots.push({
|
|
151
|
-
label: overlayTerm?.term?.values?.[
|
|
150
|
+
label: overlayTerm?.term?.values?.[plot]?.label || plot,
|
|
152
151
|
values,
|
|
153
|
-
seriesId:
|
|
152
|
+
seriesId: plot,
|
|
154
153
|
plotValueCount: values?.length,
|
|
155
|
-
color: overlayTerm?.term?.values?.[
|
|
156
|
-
|
|
157
|
-
});
|
|
158
|
-
} else {
|
|
159
|
-
plots.push({
|
|
160
|
-
label: sampleType,
|
|
161
|
-
values,
|
|
162
|
-
plotValueCount: values.length
|
|
154
|
+
color: overlayTerm?.term?.values?.[plot]?.color || null,
|
|
155
|
+
overlayTwBins: isNumericTerm(overlayTerm?.term) ? numericBins(overlayTerm, data) : null
|
|
163
156
|
});
|
|
164
157
|
}
|
|
158
|
+
charts[chart] = { chartId: chart, plots };
|
|
165
159
|
}
|
|
166
160
|
const result = {
|
|
167
161
|
min: valuesObject.min,
|
|
168
162
|
max: valuesObject.max,
|
|
169
|
-
|
|
170
|
-
pvalues: [],
|
|
163
|
+
charts,
|
|
171
164
|
uncomputableValues: Object.keys(valuesObject.uncomputableValues).length > 0 ? valuesObject.uncomputableValues : null
|
|
172
165
|
};
|
|
173
166
|
return result;
|
|
174
167
|
}
|
|
175
168
|
async function createCanvasImg(q, result, ds) {
|
|
176
|
-
if (!q.radius)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
else
|
|
181
|
-
q.radius = +q.radius;
|
|
182
|
-
if (!q.strokeWidth)
|
|
183
|
-
q.strokeWidth = 0.2;
|
|
169
|
+
if (!q.radius) q.radius = 5;
|
|
170
|
+
if (q.radius <= 0) throw "q.radius is not a number";
|
|
171
|
+
else q.radius = +q.radius;
|
|
172
|
+
if (!q.strokeWidth) q.strokeWidth = 0.2;
|
|
184
173
|
const refSize = q.radius * 4;
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
plot2Values
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
plot
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
174
|
+
for (const k of Object.keys(result.charts)) {
|
|
175
|
+
const chart = result.charts[k];
|
|
176
|
+
const plot2Values = {};
|
|
177
|
+
for (const plot of chart.plots) plot2Values[plot.label] = plot.values;
|
|
178
|
+
const densities = await getDensities(plot2Values, result.min, result.max);
|
|
179
|
+
let axisScale;
|
|
180
|
+
const useLog = q.unit == "log";
|
|
181
|
+
if (useLog) {
|
|
182
|
+
axisScale = scaleLog().base(ds.cohort.termdb.logscaleBase2 ? 2 : 10).domain([result.min, result.max]).range(q.orientation === "horizontal" ? [0, q.svgw] : [q.svgw, 0]);
|
|
183
|
+
} else {
|
|
184
|
+
axisScale = scaleLinear().domain([result.min, result.max]).range(q.orientation === "horizontal" ? [0, q.svgw] : [q.svgw, 0]);
|
|
185
|
+
}
|
|
186
|
+
const [width, height] = q.orientation == "horizontal" ? [q.svgw * q.devicePixelRatio, refSize * q.devicePixelRatio] : [refSize * q.devicePixelRatio, q.svgw * q.devicePixelRatio];
|
|
187
|
+
const scaledRadius = q.radius / q.devicePixelRatio;
|
|
188
|
+
const arcEndAngle = scaledRadius * Math.PI;
|
|
189
|
+
for (const plot of chart.plots) {
|
|
190
|
+
plot.density = densities[plot.label];
|
|
191
|
+
const noDensityRendered = plot.density.densityMax == 0;
|
|
192
|
+
const canvas = createCanvas(width, height);
|
|
193
|
+
const ctx = canvas.getContext("2d");
|
|
194
|
+
const symbolOpacity = noDensityRendered ? 1 : 0.6;
|
|
195
|
+
ctx.strokeStyle = `rgba(0,0,0,${symbolOpacity})`;
|
|
196
|
+
ctx.lineWidth = q.strokeWidth / q.devicePixelRatio;
|
|
197
|
+
ctx.globalAlpha = symbolOpacity;
|
|
198
|
+
ctx.fillStyle = "#ffe6e6";
|
|
199
|
+
if (q.devicePixelRatio != 1) {
|
|
200
|
+
ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
|
|
201
|
+
}
|
|
202
|
+
if (q.datasymbol === "rug")
|
|
203
|
+
plot.values.forEach((i) => {
|
|
204
|
+
ctx.beginPath();
|
|
205
|
+
if (q.orientation == "horizontal") {
|
|
206
|
+
ctx.moveTo(+axisScale(i), 0);
|
|
207
|
+
ctx.lineTo(+axisScale(i), scaledRadius * 2);
|
|
208
|
+
} else {
|
|
209
|
+
ctx.moveTo(0, +axisScale(i));
|
|
210
|
+
ctx.lineTo(scaledRadius * 2, +axisScale(i));
|
|
211
|
+
}
|
|
212
|
+
ctx.stroke();
|
|
213
|
+
});
|
|
214
|
+
else if (q.datasymbol === "bean")
|
|
215
|
+
plot.values.forEach((i) => {
|
|
216
|
+
ctx.beginPath();
|
|
217
|
+
if (q.orientation === "horizontal") ctx.arc(+axisScale(i), q.radius, scaledRadius, 0, arcEndAngle);
|
|
218
|
+
else ctx.arc(q.radius, +axisScale(i), scaledRadius, 0, arcEndAngle);
|
|
219
|
+
ctx.fill();
|
|
220
|
+
ctx.stroke();
|
|
221
|
+
});
|
|
222
|
+
plot.src = canvas.toDataURL();
|
|
223
|
+
plot.summaryStats = summaryStats(plot.values).values;
|
|
211
224
|
}
|
|
212
|
-
if (q.datasymbol === "rug")
|
|
213
|
-
plot.values.forEach((i) => {
|
|
214
|
-
ctx.beginPath();
|
|
215
|
-
if (q.orientation == "horizontal") {
|
|
216
|
-
ctx.moveTo(+axisScale(i), 0);
|
|
217
|
-
ctx.lineTo(+axisScale(i), scaledRadius * 2);
|
|
218
|
-
} else {
|
|
219
|
-
ctx.moveTo(0, +axisScale(i));
|
|
220
|
-
ctx.lineTo(scaledRadius * 2, +axisScale(i));
|
|
221
|
-
}
|
|
222
|
-
ctx.stroke();
|
|
223
|
-
});
|
|
224
|
-
else if (q.datasymbol === "bean")
|
|
225
|
-
plot.values.forEach((i) => {
|
|
226
|
-
ctx.beginPath();
|
|
227
|
-
if (q.orientation === "horizontal")
|
|
228
|
-
ctx.arc(+axisScale(i), q.radius, scaledRadius, 0, arcEndAngle);
|
|
229
|
-
else
|
|
230
|
-
ctx.arc(q.radius, +axisScale(i), scaledRadius, 0, arcEndAngle);
|
|
231
|
-
ctx.fill();
|
|
232
|
-
ctx.stroke();
|
|
233
|
-
});
|
|
234
|
-
plot.src = canvas.toDataURL();
|
|
235
|
-
plot.summaryStats = summaryStats(plot.values).values;
|
|
236
225
|
}
|
|
237
226
|
}
|
|
238
227
|
async function getDensity(values) {
|
|
@@ -270,6 +259,6 @@ export {
|
|
|
270
259
|
getDensities,
|
|
271
260
|
getDensity,
|
|
272
261
|
getWilcoxonData,
|
|
273
|
-
|
|
262
|
+
sortPlot2Values,
|
|
274
263
|
trigger_getViolinPlotData
|
|
275
264
|
};
|
package/routes/tileserver.js
CHANGED
|
@@ -24,26 +24,25 @@ function init({ genomes }) {
|
|
|
24
24
|
const { type, sessionId, TileGroup, z, x, y } = req.params;
|
|
25
25
|
const query = req.query;
|
|
26
26
|
const wsiImage = query.wsi_image;
|
|
27
|
-
if (!wsiImage)
|
|
28
|
-
throw new Error("No wsi_image param provided");
|
|
27
|
+
if (!wsiImage) throw new Error("No wsi_image param provided");
|
|
29
28
|
const dslabel = query.dslabel;
|
|
30
|
-
if (!dslabel)
|
|
31
|
-
throw new Error("No dslabel param provided");
|
|
29
|
+
if (!dslabel) throw new Error("No dslabel param provided");
|
|
32
30
|
const g = genomes[query.genome];
|
|
33
|
-
if (!g)
|
|
34
|
-
throw new Error("Invalid genome name");
|
|
31
|
+
if (!g) throw new Error("Invalid genome name");
|
|
35
32
|
const ds = g.datasets[query.dslabel];
|
|
36
|
-
if (!ds)
|
|
37
|
-
throw new Error("Invalid dataset name");
|
|
33
|
+
if (!ds) throw new Error("Invalid dataset name");
|
|
38
34
|
const sampleId = query.sample_id;
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
const projectId = query.ai_project_id;
|
|
36
|
+
if (!sampleId && !projectId) throw new Error("Either sample_id or project_id must be provided");
|
|
41
37
|
const mount = serverconfig.features?.tileserver?.mount;
|
|
42
|
-
if (!mount)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
if (!mount) throw new Error("No mount available for TileServer");
|
|
39
|
+
let wsiImagePath;
|
|
40
|
+
if (sampleId) {
|
|
41
|
+
wsiImagePath = path.join(`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`, wsiImage);
|
|
42
|
+
} else {
|
|
43
|
+
wsiImagePath = path.join(`${mount}/${ds.queries.WSImages.aiToolImageFolder}/`, wsiImage);
|
|
44
|
+
}
|
|
45
|
+
if (!wsiImage) throw new Error("Invalid wsi_image");
|
|
47
46
|
const shardManager = ShardManager.getInstance();
|
|
48
47
|
const tileServer = await shardManager.shardingAlgorithmsMap?.get(TileServerShardingAlgorithm.TILE_SERVER_SHARDING_KEY)?.getShard(wsiImagePath);
|
|
49
48
|
if (!tileServer) {
|
package/routes/wsimages.js
CHANGED
|
@@ -25,27 +25,22 @@ const api = {
|
|
|
25
25
|
function init({ genomes }) {
|
|
26
26
|
return async (req, res) => {
|
|
27
27
|
try {
|
|
28
|
-
const
|
|
29
|
-
const g = genomes[
|
|
30
|
-
if (!g)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
if (!sampleId)
|
|
37
|
-
throw new Error("Invalid sampleId");
|
|
38
|
-
|
|
39
|
-
if (!wsimage)
|
|
40
|
-
throw new Error("Invalid wsimage");
|
|
28
|
+
const wSImagesRequest = req.query;
|
|
29
|
+
const g = genomes[wSImagesRequest.genome];
|
|
30
|
+
if (!g) throw new Error("Invalid genome name");
|
|
31
|
+
const ds = g.datasets[wSImagesRequest.dslabel];
|
|
32
|
+
if (!ds) throw new Error("Invalid dataset name");
|
|
33
|
+
const sampleId = wSImagesRequest.sampleId;
|
|
34
|
+
const wsimage = wSImagesRequest.wsimage;
|
|
35
|
+
const aiProjectId = wSImagesRequest.aiProjectId;
|
|
36
|
+
if (!sampleId && (!wsimage || !aiProjectId)) {
|
|
37
|
+
throw new Error("Invalid parameters: sampleId or both wsimage and aiProjectId must be provided");
|
|
38
|
+
}
|
|
41
39
|
const cookieJar = new CookieJar();
|
|
42
40
|
const setCookie = promisify(cookieJar.setCookie.bind(cookieJar));
|
|
43
41
|
const getCookieString = promisify(cookieJar.getCookieString.bind(cookieJar));
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
throw new Error("No mount available for TileServer");
|
|
47
|
-
const wsiImagePath = path.join(`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`, wsimage);
|
|
48
|
-
const session = await getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie, wsiImagePath);
|
|
42
|
+
const wsiImagePath = await getWSImagePath(ds, wSImagesRequest);
|
|
43
|
+
const session = await getSessionId(ds, cookieJar, getCookieString, setCookie, wsiImagePath, aiProjectId);
|
|
49
44
|
const getWsiImageResponse = await getWsiImageDimensions(
|
|
50
45
|
session.imageSessionId,
|
|
51
46
|
getCookieString,
|
|
@@ -67,26 +62,35 @@ function init({ genomes }) {
|
|
|
67
62
|
}
|
|
68
63
|
};
|
|
69
64
|
}
|
|
70
|
-
async function
|
|
65
|
+
async function getWSImagePath(ds, wSImagesRequest) {
|
|
66
|
+
const mount = serverconfig.features?.tileserver?.mount;
|
|
67
|
+
if (!mount) throw new Error("No mount available for TileServer");
|
|
68
|
+
if (wSImagesRequest.sampleId) {
|
|
69
|
+
return path.join(
|
|
70
|
+
`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${wSImagesRequest.sampleId}`,
|
|
71
|
+
wSImagesRequest.wsimage
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
return path.join(`${mount}/${ds.queries.WSImages.aiToolImageFolder}/`, wSImagesRequest.wsimage);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function getSessionId(ds, cookieJar, getCookieString, setCookie, wsimage, aiProjectId) {
|
|
71
78
|
const sessionManager = SessionManager.getInstance();
|
|
72
79
|
const invalidateResult = await sessionManager.syncAndInvalidateSessions(wsimage);
|
|
73
|
-
if (!invalidateResult)
|
|
74
|
-
throw new Error("Session invalidation failed");
|
|
80
|
+
if (!invalidateResult) throw new Error("Session invalidation failed");
|
|
75
81
|
const session = await sessionManager.getSession(wsimage);
|
|
76
82
|
if (session) {
|
|
77
83
|
return session;
|
|
78
84
|
}
|
|
79
85
|
const tileServer = await sessionManager.getTileServerShard(wsimage);
|
|
80
|
-
if (!tileServer)
|
|
81
|
-
throw new Error("No TileServer shard available");
|
|
86
|
+
if (!tileServer) throw new Error("No TileServer shard available");
|
|
82
87
|
await ky.get(`${tileServer.url}/tileserver/session_id`, {
|
|
83
88
|
timeout: 5e4,
|
|
84
89
|
hooks: getHooks(cookieJar, getCookieString, setCookie)
|
|
85
90
|
});
|
|
86
91
|
const cookieString = await getCookieString(`${tileServer.url}/tileserver/session_id`);
|
|
87
92
|
const sessionId = cookieString.match(/session_id=([^;]*)/)?.[1];
|
|
88
|
-
if (!sessionId)
|
|
89
|
-
throw new Error("session_id not found");
|
|
93
|
+
if (!sessionId) throw new Error("session_id not found");
|
|
90
94
|
const overlays = [];
|
|
91
95
|
const data = qs.stringify({ slide_path: wsimage });
|
|
92
96
|
await ky.put(`${tileServer.url}/tileserver/slide`, {
|
|
@@ -99,15 +103,12 @@ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie,
|
|
|
99
103
|
hooks: getHooks(cookieJar, getCookieString, setCookie)
|
|
100
104
|
});
|
|
101
105
|
if (ds.queries.WSImages.getWSIPredictionOverlay) {
|
|
102
|
-
const predictionOverlay = await ds.queries.WSImages.getWSIPredictionOverlay(
|
|
106
|
+
const predictionOverlay = await ds.queries.WSImages.getWSIPredictionOverlay(wsimage);
|
|
103
107
|
if (predictionOverlay) {
|
|
104
108
|
const mount = serverconfig.features?.tileserver?.mount;
|
|
105
|
-
const
|
|
106
|
-
`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
|
|
107
|
-
predictionOverlay
|
|
108
|
-
);
|
|
109
|
+
const overlayFilePath = path.join(`${mount}/${ds.queries.WSImages.aiToolImageFolder}/`, predictionOverlay);
|
|
109
110
|
const annotationsData = qs.stringify({
|
|
110
|
-
overlay_path:
|
|
111
|
+
overlay_path: overlayFilePath
|
|
111
112
|
});
|
|
112
113
|
const layerNumber = await ky.put(`${tileServer.url}/tileserver/overlay`, {
|
|
113
114
|
body: annotationsData,
|
|
@@ -126,18 +127,18 @@ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie,
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
if (ds.queries.WSImages.getWSIUncertaintyOverlay) {
|
|
129
|
-
const uncertaintyOverlay = await ds.queries.WSImages.getWSIUncertaintyOverlay(
|
|
130
|
+
const uncertaintyOverlay = await ds.queries.WSImages.getWSIUncertaintyOverlay(wsimage);
|
|
130
131
|
if (uncertaintyOverlay) {
|
|
131
132
|
const mount = serverconfig.features?.tileserver?.mount;
|
|
132
|
-
const
|
|
133
|
-
`${mount}/${ds.queries.WSImages.
|
|
133
|
+
const uncertaintyOverlayFilePath = path.join(
|
|
134
|
+
`${mount}/${ds.queries.WSImages.aiToolImageFolder}/`,
|
|
134
135
|
uncertaintyOverlay
|
|
135
136
|
);
|
|
136
|
-
const
|
|
137
|
-
overlay_path:
|
|
137
|
+
const uncertaintyData = qs.stringify({
|
|
138
|
+
overlay_path: uncertaintyOverlayFilePath
|
|
138
139
|
});
|
|
139
140
|
const layerNumber = await ky.put(`${tileServer.url}/tileserver/overlay`, {
|
|
140
|
-
body:
|
|
141
|
+
body: uncertaintyData,
|
|
141
142
|
timeout: 5e4,
|
|
142
143
|
headers: {
|
|
143
144
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
@@ -154,18 +155,13 @@ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie,
|
|
|
154
155
|
}
|
|
155
156
|
const sessionData = await sessionManager.setSession(wsimage, sessionId, tileServer, overlays);
|
|
156
157
|
if (ds.queries.WSImages.getWSIPredictionPatches) {
|
|
157
|
-
const predictionPatches = await ds.queries.WSImages.getWSIPredictionPatches(
|
|
158
|
-
if (!predictionPatches)
|
|
159
|
-
throw new Error("No prediction files found");
|
|
158
|
+
const predictionPatches = await ds.queries.WSImages.getWSIPredictionPatches(aiProjectId, wsimage);
|
|
159
|
+
if (!predictionPatches) throw new Error("No prediction files found");
|
|
160
160
|
const mount = serverconfig.features?.tileserver?.mount;
|
|
161
|
-
if (!mount)
|
|
162
|
-
throw new Error("No mount available for TileServer");
|
|
161
|
+
if (!mount) throw new Error("No mount available for TileServer");
|
|
163
162
|
if (predictionPatches.length > 0) {
|
|
164
163
|
for (const predictionPatch of predictionPatches) {
|
|
165
|
-
const predictionFilePath = path.join(
|
|
166
|
-
`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
|
|
167
|
-
predictionPatch
|
|
168
|
-
);
|
|
164
|
+
const predictionFilePath = path.join(`${mount}/${ds.queries.WSImages.aiToolImageFolder}/`, predictionPatch);
|
|
169
165
|
const predictionsData = qs.stringify({
|
|
170
166
|
overlay_path: predictionFilePath
|
|
171
167
|
});
|
package/routes/wsisamples.js
CHANGED
|
@@ -18,11 +18,9 @@ function init({ genomes }) {
|
|
|
18
18
|
try {
|
|
19
19
|
const query = req.query;
|
|
20
20
|
const g = genomes[query.genome];
|
|
21
|
-
if (!g)
|
|
22
|
-
throw new Error("Invalid genome name");
|
|
21
|
+
if (!g) throw new Error("Invalid genome name");
|
|
23
22
|
const ds = g.datasets[query.dslabel];
|
|
24
|
-
if (!ds)
|
|
25
|
-
throw "Invalid dslabel";
|
|
23
|
+
if (!ds) throw "Invalid dslabel";
|
|
26
24
|
const images = await ds.queries.WSImages.getSamples();
|
|
27
25
|
const payload = {
|
|
28
26
|
samples: images
|
|
@@ -39,8 +37,7 @@ function init({ genomes }) {
|
|
|
39
37
|
}
|
|
40
38
|
async function validate_query_getWSISamples(ds) {
|
|
41
39
|
const q = ds.queries?.WSImages;
|
|
42
|
-
if (!q)
|
|
43
|
-
return;
|
|
40
|
+
if (!q) return;
|
|
44
41
|
validateQuery(ds);
|
|
45
42
|
}
|
|
46
43
|
function validateQuery(ds) {
|