@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.
Files changed (65) hide show
  1. package/dataset/protected.test.js +1 -2
  2. package/dataset/termdb.test.js +3 -2
  3. package/package.json +9 -8
  4. package/routes/aiProjectAdmin.js +46 -60
  5. package/routes/aiProjectSelectedWSImages.js +161 -0
  6. package/routes/brainImaging.js +9 -18
  7. package/routes/brainImagingSamples.js +2 -4
  8. package/routes/burden.js +13 -26
  9. package/routes/correlationVolcano.js +18 -36
  10. package/routes/dataset.js +6 -12
  11. package/routes/deleteWSIAnnotation.js +75 -0
  12. package/routes/dsdata.js +7 -14
  13. package/routes/dzimages.js +4 -8
  14. package/routes/gdc.grin2.list.js +13 -26
  15. package/routes/gdc.grin2.run.js +3 -6
  16. package/routes/gdc.maf.js +8 -16
  17. package/routes/gdc.mafBuild.js +14 -28
  18. package/routes/gene2canonicalisoform.js +4 -8
  19. package/routes/genelookup.js +2 -4
  20. package/routes/genesetEnrichment.js +6 -12
  21. package/routes/genesetOverrepresentation.js +1 -2
  22. package/routes/genomes.js +1 -2
  23. package/routes/grin2.js +13 -17
  24. package/routes/healthcheck.js +3 -6
  25. package/routes/hicdata.js +4 -8
  26. package/routes/hicgenome.js +4 -8
  27. package/routes/hicstat.js +2 -4
  28. package/routes/img.js +1 -2
  29. package/routes/isoformlst.js +6 -12
  30. package/routes/ntseq.js +4 -8
  31. package/routes/pdomain.js +5 -10
  32. package/routes/sampledzimages.js +2 -4
  33. package/routes/samplewsimages.js +3 -67
  34. package/routes/saveWSIAnnotation.js +100 -0
  35. package/routes/snp.js +9 -18
  36. package/routes/termdb.DE.js +23 -46
  37. package/routes/termdb.boxplot.js +84 -84
  38. package/routes/termdb.categories.js +9 -18
  39. package/routes/termdb.cluster.js +23 -46
  40. package/routes/termdb.cohort.summary.js +3 -6
  41. package/routes/termdb.cohorts.js +4 -8
  42. package/routes/termdb.config.js +32 -64
  43. package/routes/termdb.descrstats.js +6 -12
  44. package/routes/termdb.filterTermValues.js +4 -8
  45. package/routes/termdb.numericcategories.js +5 -10
  46. package/routes/termdb.percentile.js +6 -12
  47. package/routes/termdb.profileFormScores.js +12 -24
  48. package/routes/termdb.profileScores.js +7 -14
  49. package/routes/termdb.rootterm.js +4 -8
  50. package/routes/termdb.sampleImages.js +4 -8
  51. package/routes/termdb.singleSampleMutation.js +9 -18
  52. package/routes/termdb.singlecellDEgenes.js +4 -8
  53. package/routes/termdb.singlecellData.js +4 -8
  54. package/routes/termdb.singlecellSamples.js +28 -56
  55. package/routes/termdb.termchildren.js +5 -10
  56. package/routes/termdb.termsbyids.js +4 -8
  57. package/routes/termdb.topMutatedGenes.js +15 -30
  58. package/routes/termdb.topTermsByType.js +9 -18
  59. package/routes/termdb.topVariablyExpressedGenes.js +13 -26
  60. package/routes/termdb.violin.js +124 -135
  61. package/routes/tileserver.js +14 -15
  62. package/routes/wsimages.js +42 -46
  63. package/routes/wsisamples.js +3 -6
  64. package/src/app.js +4345 -6708
  65. package/routes/sampleWsiAiApi.js +0 -33
@@ -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
- q.tw.q.mode = "continuous";
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.divideTw)
54
- terms.push(q.divideTw);
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
- throw data.error;
68
- if (q.divideTw && data.refs.byTermId[q.divideTw.$id]) {
69
- data.refs.byTermId[q.divideTw.$id].orderedLabels = getOrderedLabels(
70
- q.divideTw,
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.divideTw.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, sampleType);
80
- await getWilcoxonData(q.divideTw, result);
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(divideTw, result) {
85
- if (!divideTw)
86
- return;
87
- const numPlots = result.plots.length;
88
- if (numPlots < 2)
89
- return;
90
- const wilcoxInput = [];
91
- for (let i = 0; i < numPlots; i++) {
92
- const group1_id = result.plots[i].label;
93
- const group1_values = result.plots[i].values;
94
- for (let j = i + 1; j < numPlots; j++) {
95
- const group2_id = result.plots[j].label;
96
- const group2_values = result.plots[j].values;
97
- wilcoxInput.push({ group1_id, group1_values, group2_id, group2_values });
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
- const wilcoxOutput = JSON.parse(await run_rust("wilcoxon", JSON.stringify(wilcoxInput)));
101
- for (const test of wilcoxOutput) {
102
- if (test.pvalue == null || test.pvalue == "null") {
103
- result.pvalues.push([{ value: test.group1_id }, { value: test.group2_id }, { html: "NA" }]);
104
- } else {
105
- result.pvalues.push([{ value: test.group1_id }, { value: test.group2_id }, { html: test.pvalue.toPrecision(4) }]);
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
- continue;
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.divideTw;
113
+ const overlayTerm = q.overlayTw;
114
+ const divideTerm = q.divideTw;
124
115
  const useLog = q.unit == "log";
125
- const { absMax, absMin, key2values, uncomputableValues } = parseValues(q, data, sampleType, useLog, overlayTerm);
116
+ const { absMax, absMin, chart2plot2values, uncomputableValues } = parseValues(
117
+ q,
118
+ data,
119
+ sampleType,
120
+ useLog,
121
+ overlayTerm,
122
+ divideTerm
123
+ );
126
124
  return {
127
- key2values,
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 sortKey2values(data, key2values, overlayTerm) {
134
+ function sortPlot2Values(data, plot2values, overlayTerm) {
137
135
  const orderedLabels = overlayTerm?.$id ? data.refs.byTermId[overlayTerm.$id]?.keyOrder : void 0;
138
- key2values = new Map(
139
- [...key2values].sort(
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 key2values;
141
+ return plot2values;
144
142
  }
145
- function setResponse(valuesObject, data, q, sampleType) {
146
- const overlayTerm = q.divideTw;
147
- const plots = [];
148
- for (const [key, values] of sortKey2values(data, valuesObject.key2values, overlayTerm)) {
149
- if (overlayTerm) {
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?.[key]?.label || key,
150
+ label: overlayTerm?.term?.values?.[plot]?.label || plot,
152
151
  values,
153
- seriesId: key,
152
+ seriesId: plot,
154
153
  plotValueCount: values?.length,
155
- color: overlayTerm?.term?.values?.[key]?.color || null,
156
- divideTwBins: isNumericTerm(overlayTerm.term) ? numericBins(overlayTerm, data) : null
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
- plots,
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
- q.radius = 5;
178
- if (q.radius <= 0)
179
- throw "q.radius is not a number";
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 plot2Values = {};
186
- for (const plot of result.plots)
187
- plot2Values[plot.label] = plot.values;
188
- const densities = await getDensities(plot2Values, result.min, result.max);
189
- let axisScale;
190
- const useLog = q.unit == "log";
191
- if (useLog) {
192
- axisScale = scaleLog().base(ds.cohort.termdb.logscaleBase2 ? 2 : 10).domain([result.min, result.max]).range(q.orientation === "horizontal" ? [0, q.svgw] : [q.svgw, 0]);
193
- } else {
194
- axisScale = scaleLinear().domain([result.min, result.max]).range(q.orientation === "horizontal" ? [0, q.svgw] : [q.svgw, 0]);
195
- }
196
- const [width, height] = q.orientation == "horizontal" ? [q.svgw * q.devicePixelRatio, refSize * q.devicePixelRatio] : [refSize * q.devicePixelRatio, q.svgw * q.devicePixelRatio];
197
- const scaledRadius = q.radius / q.devicePixelRatio;
198
- const arcEndAngle = scaledRadius * Math.PI;
199
- for (const plot of result.plots) {
200
- plot.density = densities[plot.label];
201
- const noDensityRendered = plot.density.densityMax == 0;
202
- const canvas = createCanvas(width, height);
203
- const ctx = canvas.getContext("2d");
204
- const symbolOpacity = noDensityRendered ? 1 : 0.6;
205
- ctx.strokeStyle = `rgba(0,0,0,${symbolOpacity})`;
206
- ctx.lineWidth = q.strokeWidth / q.devicePixelRatio;
207
- ctx.globalAlpha = symbolOpacity;
208
- ctx.fillStyle = "#ffe6e6";
209
- if (q.devicePixelRatio != 1) {
210
- ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
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
- sortKey2values,
262
+ sortPlot2Values,
274
263
  trigger_getViolinPlotData
275
264
  };
@@ -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
- if (!sampleId)
40
- throw new Error("Invalid sample id");
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
- 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");
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) {
@@ -25,27 +25,22 @@ const api = {
25
25
  function init({ genomes }) {
26
26
  return async (req, res) => {
27
27
  try {
28
- const query = req.query;
29
- const g = genomes[query.genome];
30
- if (!g)
31
- throw new Error("Invalid genome name");
32
- const ds = g.datasets[query.dslabel];
33
- if (!ds)
34
- throw new Error("Invalid dataset name");
35
- const sampleId = query.sampleId;
36
- if (!sampleId)
37
- throw new Error("Invalid sampleId");
38
- const wsimage = query.wsimage;
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 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 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 getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie, wsimage) {
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(sampleId, wsimage);
106
+ const predictionOverlay = await ds.queries.WSImages.getWSIPredictionOverlay(wsimage);
103
107
  if (predictionOverlay) {
104
108
  const mount = serverconfig.features?.tileserver?.mount;
105
- const annotationsFilePath = path.join(
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: annotationsFilePath
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(sampleId, wsimage);
130
+ const uncertaintyOverlay = await ds.queries.WSImages.getWSIUncertaintyOverlay(wsimage);
130
131
  if (uncertaintyOverlay) {
131
132
  const mount = serverconfig.features?.tileserver?.mount;
132
- const annotationsFilePath = path.join(
133
- `${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
133
+ const uncertaintyOverlayFilePath = path.join(
134
+ `${mount}/${ds.queries.WSImages.aiToolImageFolder}/`,
134
135
  uncertaintyOverlay
135
136
  );
136
- const annotationsData = qs.stringify({
137
- overlay_path: annotationsFilePath
137
+ const uncertaintyData = qs.stringify({
138
+ overlay_path: uncertaintyOverlayFilePath
138
139
  });
139
140
  const layerNumber = await ky.put(`${tileServer.url}/tileserver/overlay`, {
140
- body: annotationsData,
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(sampleId, wsimage);
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
  });
@@ -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) {