@sjcrh/proteinpaint-server 2.111.0 → 2.112.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.111.0",
3
+ "version": "2.112.1-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.111.0",
64
- "@sjcrh/proteinpaint-shared": "2.111.0",
65
- "@sjcrh/proteinpaint-types": "2.111.0",
63
+ "@sjcrh/proteinpaint-rust": "2.112.0",
64
+ "@sjcrh/proteinpaint-shared": "2.112.1-0",
65
+ "@sjcrh/proteinpaint-types": "2.112.1-0",
66
66
  "@types/express": "^5.0.0",
67
67
  "@types/express-session": "^1.18.1",
68
68
  "better-sqlite3": "^9.4.1",
@@ -4,6 +4,7 @@ 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
6
  import path from "path";
7
+ import { getStdDev } from "#shared/descriptive.stats.js";
7
8
  const minArrayLength = 3;
8
9
  const minSD = 0.05;
9
10
  const api = {
@@ -70,7 +71,7 @@ async function compute(q, ds, genome) {
70
71
  const [acceptedVariables, skippedVariables] = Array.from(vtid2array.values()).reduce(
71
72
  ([accepted, skipped], t) => {
72
73
  const grterThanOne = t.v1.length > minArrayLength && t.v2.length > minArrayLength;
73
- const significantSD = stdDev(t.v1) > minSD && stdDev(t.v2) > minSD;
74
+ const significantSD = getStdDev(t.v1) > minSD && getStdDev(t.v2) > minSD;
74
75
  const v = grterThanOne && significantSD ? accepted : skipped;
75
76
  if (v === accepted)
76
77
  accepted.push(t);
@@ -133,18 +134,7 @@ function validate_correlationVolcano(ds) {
133
134
  throw "unknown cv.variables.type";
134
135
  }
135
136
  }
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
- }
145
137
  export {
146
138
  api,
147
- mean,
148
- stdDev,
149
139
  validate_correlationVolcano
150
140
  };
@@ -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 = "GDC_MAF_MULTIPART_BOUNDARY";
50
- res.setHeader("content-type", `multipart/mixed; boundary=${boundary}`);
49
+ const boundary = "------------------------GDC-MAF-BUILD";
50
+ res.setHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
51
51
  res.write(`--${boundary}`);
52
- res.write("\ncontent-disposition: attachment; filename=cohort.maf.gz");
53
- res.write("\ncontent-type: application/octet-stream\n\n");
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
- const rustStream = stream_rust("gdcmaf", JSON.stringify(arg), emitJson);
56
- rustStream.pipe(res, { end: false });
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.write(`
89
+ if (res.writableEnded)
90
+ return;
91
+ if (data) {
92
+ res.write(`\r
59
93
  --${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}--`);
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();
@@ -8,6 +8,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
10
  import imagesize from "image-size";
11
+ import { get_header_txt } from "#src/utils.js";
11
12
  const api = {
12
13
  endpoint: "DEanalysis",
13
14
  methods: {
@@ -230,10 +231,12 @@ async function run_DE(param, ds, term_results, term_results2) {
230
231
  sample_size1,
231
232
  sample_size2,
232
233
  method: param.method,
233
- ql_image: result2.ql_image,
234
- // QL fit image
235
- mds_image: result2.mds_image
236
- // MDS image
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();
@@ -256,6 +259,38 @@ async function readFileAndDelete(file, key, response) {
256
259
  throw err;
257
260
  });
258
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
+ }
292
+ }
259
293
  export {
260
- api
294
+ api,
295
+ validate_query_rnaseqGeneCount
261
296
  };
@@ -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.reduce((s2, i) => s2 + i, 0) / sortedValues.length;
131
- let s = 0;
132
- for (const v of sortedValues) {
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) },
@@ -148,12 +148,23 @@ 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
+ }
157
168
  if (q.cnv) {
158
169
  q2.cnv = {};
159
170
  for (const k of [
@@ -1,5 +1,5 @@
1
1
  import { descrStatsPayload } from "#types/checkers";
2
- import Summarystats from "#shared/descriptive.stats.js";
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 = Summarystats(values);
51
+ result = summaryStats(values);
52
52
  } else {
53
53
  result = {};
54
54
  }
@@ -1,10 +1,10 @@
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
9
  import { numericBins, parseValues } from "./termdb.boxplot.ts";
10
10
  import serverconfig from "../src/serverconfig.js";
@@ -183,7 +183,7 @@ async function createCanvasImg(q, result, ds) {
183
183
  const plot2Values = {};
184
184
  for (const plot of result.plots)
185
185
  plot2Values[plot.label] = plot.values;
186
- const densities = await getDensities(plot2Values);
186
+ const densities = await getDensities(plot2Values, result.min, result.max);
187
187
  let axisScale;
188
188
  const useLog = q.unit == "log";
189
189
  if (useLog) {
@@ -196,13 +196,14 @@ async function createCanvasImg(q, result, ds) {
196
196
  const arcEndAngle = scaledRadius * Math.PI;
197
197
  for (const plot of result.plots) {
198
198
  plot.density = densities[plot.label];
199
+ const noDensityRendered = plot.density.densityMax == 0;
199
200
  const canvas = createCanvas(width, height);
200
201
  const ctx = canvas.getContext("2d");
201
- const symbolOpacity = plot.density.densityMax == 0 ? 1 : 0.8;
202
+ const symbolOpacity = noDensityRendered ? 1 : 0.6;
202
203
  ctx.strokeStyle = `rgba(0,0,0,${symbolOpacity})`;
203
204
  ctx.lineWidth = q.strokeWidth / q.devicePixelRatio;
204
205
  ctx.globalAlpha = symbolOpacity;
205
- ctx.fillStyle = plot.values.length <= minSampleSize ? "black" : "#ffe6e6";
206
+ ctx.fillStyle = "#ffe6e6";
206
207
  if (q.devicePixelRatio != 1) {
207
208
  ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
208
209
  }
@@ -233,12 +234,13 @@ async function createCanvasImg(q, result, ds) {
233
234
  }
234
235
  }
235
236
  async function getDensity(values) {
236
- const result = await getDensities({ plot: values });
237
+ const [min, max] = extent(values);
238
+ const result = await getDensities({ plot: values }, min, max);
237
239
  return result.plot;
238
240
  }
239
- async function getDensities(plot2Values) {
241
+ async function getDensities(plot2Values, min, max) {
240
242
  const densityScript = path.join(serverconfig.binpath, "utils", "density.R");
241
- const plot2Density = JSON.parse(await run_R(densityScript, JSON.stringify(plot2Values)));
243
+ const plot2Density = JSON.parse(await run_R(densityScript, JSON.stringify({ plot2Values, min, max })));
242
244
  const densities = {};
243
245
  for (const plot in plot2Density) {
244
246
  const result = plot2Density[plot];
@@ -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/:sampleId/zoomify/:TileGroup/:z-:x-:y@1x.jpg`,
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 { sampleId, TileGroup, z, x, y } = req.params;
21
- const url = `${serverconfig.tileServerURL}/tileserver/layer/slide/${sampleId}/zoomify/${TileGroup}/${z}-${x}-${y}@1x.jpg`;
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) {
@@ -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, { SessionData } from "../src/wsisessions/SessionManager.ts";
9
- import crypto from "crypto";
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
- let sessionManager;
44
- let sessionData;
45
- let userSessionId = void 0;
46
- if (serverconfig.redis) {
47
- sessionManager = SessionManager.getInstance(serverconfig.redis.url);
48
- userSessionId = crypto.createHash("sha256").update(crypto.randomBytes(32).toString("hex")).digest("hex");
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.error(e);
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, ds, sampleId) {
73
- await ky.get(`${serverconfig.tileServerURL}/tileserver/session_id`, {
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(`${serverconfig.tileServerURL}/tileserver/session_id`);
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 sampleWsiTileServer = path.join(
82
- `${serverconfig.tileServerMount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
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 manageUserSession(sessionManager, sessionData, wsimage, userId, sessionId) {
98
- if (!sessionData) {
99
- await sessionManager.setSession(wsimage, new SessionData(sessionId, [userId]));
100
- } else if (!sessionData.userSessionIds || !sessionData.userSessionIds.includes(userId)) {
101
- sessionData.userSessionIds = sessionData.userSessionIds || [];
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: [