@sjcrh/proteinpaint-server 2.71.0 → 2.73.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 -2
- package/routes/samplewsimages.js +51 -0
- package/routes/termdb.categories.js +1 -1
- package/routes/termdb.cluster.js +5 -5
- package/routes/termdb.config.js +6 -1
- package/routes/termdb.getdescrstats.js +5 -1
- package/routes/termdb.termsbyids.js +1 -1
- package/routes/termdb.topVariablyExpressedGenes.js +75 -10
- package/routes/tileserver.js +42 -0
- package/routes/wsimages.js +115 -0
- package/src/app.js +1088 -403
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.73.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",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@babel/preset-typescript": "^7.21.4",
|
|
45
45
|
"@babel/register": "^7.14.5",
|
|
46
46
|
"@types/node": "^20.11.24",
|
|
47
|
+
"@types/tough-cookie": "^4.0.5",
|
|
47
48
|
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
|
48
49
|
"babel-loader": "^8.2.2",
|
|
49
50
|
"esbuild": "^0.19.12",
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
},
|
|
62
63
|
"dependencies": {
|
|
63
64
|
"@sjcrh/augen": "2.46.0",
|
|
64
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
65
|
+
"@sjcrh/proteinpaint-rust": "2.73.0",
|
|
65
66
|
"better-sqlite3": "^9.4.1",
|
|
66
67
|
"body-parser": "^1.15.2",
|
|
67
68
|
"canvas": "~2.11.2",
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
"node-fetch": "^2.6.1",
|
|
83
84
|
"partjson": "^0.58.2",
|
|
84
85
|
"tiny-async-pool": "^1.2.0",
|
|
86
|
+
"tough-cookie": "^4.1.4",
|
|
85
87
|
"typedoc-plugin-missing-exports": "^2.0.1"
|
|
86
88
|
},
|
|
87
89
|
"repository": {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import serverconfig from "#src/serverconfig.js";
|
|
4
|
+
const api = {
|
|
5
|
+
endpoint: "samplewsimages",
|
|
6
|
+
methods: {
|
|
7
|
+
get: {
|
|
8
|
+
init,
|
|
9
|
+
request: {
|
|
10
|
+
typeId: "GetSampleWSImagesRequest"
|
|
11
|
+
},
|
|
12
|
+
response: {
|
|
13
|
+
typeId: "GetSampleWSImagesResponse"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
post: {
|
|
17
|
+
alternativeFor: "get",
|
|
18
|
+
init
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function init({ genomes }) {
|
|
23
|
+
return async (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const query = req.query;
|
|
26
|
+
const g = genomes[query.genome];
|
|
27
|
+
if (!g)
|
|
28
|
+
throw "invalid genome name";
|
|
29
|
+
const ds = g.datasets[query.dslabel];
|
|
30
|
+
if (!ds)
|
|
31
|
+
throw "invalid dataset name";
|
|
32
|
+
const sampleId = query.sample_id;
|
|
33
|
+
const sampleWSImagesPath = path.join(
|
|
34
|
+
`${serverconfig.tpmasterdir}/${ds.queries.WSImages.imageBySampleFolder}`,
|
|
35
|
+
sampleId
|
|
36
|
+
);
|
|
37
|
+
const sampleWSImages = await getWSImages(sampleWSImagesPath);
|
|
38
|
+
res.send({ sampleWSImages });
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.log(e);
|
|
41
|
+
res.status(404).send("Sample images not found");
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function getWSImages(sampleImagesPath) {
|
|
46
|
+
const files = await fs.promises.readdir(sampleImagesPath);
|
|
47
|
+
return files.filter((file) => [".svs", ".mrxs", ".scn", ".ndpi", ".tiff"].includes(path.extname(file)));
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
api
|
|
51
|
+
};
|
|
@@ -92,7 +92,7 @@ async function trigger_getcategories(q, res, tdb, ds, genome) {
|
|
|
92
92
|
if (data.error)
|
|
93
93
|
throw data.error;
|
|
94
94
|
const lst = [];
|
|
95
|
-
if (q.tw.term.type == "geneVariant" &&
|
|
95
|
+
if (q.tw.term.type == "geneVariant" && q.tw.q.type != "predefined-groupset" && q.tw.q.type != "custom-groupset") {
|
|
96
96
|
const samples = data.samples;
|
|
97
97
|
const dtClassMap = /* @__PURE__ */ new Map();
|
|
98
98
|
if (ds.assayAvailability?.byDt) {
|
package/routes/termdb.cluster.js
CHANGED
|
@@ -192,11 +192,10 @@ async function validateNative(q, ds, genome) {
|
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
const term2sample2value = /* @__PURE__ */ new Map();
|
|
195
|
-
for (const
|
|
196
|
-
const geneTerm = g;
|
|
195
|
+
for (const geneTerm of param.terms) {
|
|
197
196
|
if (!geneTerm.gene)
|
|
198
197
|
continue;
|
|
199
|
-
if (!geneTerm.chr) {
|
|
198
|
+
if (!geneTerm.chr || !Number.isInteger(geneTerm.start) || !Number.isInteger(geneTerm.stop)) {
|
|
200
199
|
const re = getResultGene(genome, { input: geneTerm.gene, deep: 1 });
|
|
201
200
|
if (!re.gmlst || re.gmlst.length == 0) {
|
|
202
201
|
console.warn("unknown gene:" + geneTerm.gene);
|
|
@@ -208,12 +207,13 @@ async function validateNative(q, ds, genome) {
|
|
|
208
207
|
geneTerm.chr = i.chr;
|
|
209
208
|
}
|
|
210
209
|
const s2v = {};
|
|
210
|
+
if (!geneTerm.chr || !Number.isInteger(geneTerm.start) || !Number.isInteger(geneTerm.stop))
|
|
211
|
+
throw "missing chr/start/stop";
|
|
211
212
|
await utils.get_lines_bigfile({
|
|
212
213
|
args: [
|
|
213
214
|
q.file,
|
|
214
|
-
(q.nochr ? geneTerm.chr
|
|
215
|
+
(q.nochr ? geneTerm.chr.replace("chr", "") : geneTerm.chr) + ":" + geneTerm.start + "-" + geneTerm.stop
|
|
215
216
|
],
|
|
216
|
-
// must do g.chr?.replace to avoid tsc error
|
|
217
217
|
callback: (line) => {
|
|
218
218
|
const l = line.split(" ");
|
|
219
219
|
if (l[3].toLowerCase() != geneTerm.gene.toLowerCase())
|
package/routes/termdb.config.js
CHANGED
|
@@ -177,11 +177,16 @@ function addNonDictionaryQueries(c, ds, genome) {
|
|
|
177
177
|
q2.NIdata[k] = JSON.parse(JSON.stringify(q.NIdata[k]));
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
-
if (q.DZImages) {
|
|
180
|
+
if (q.DZImages && serverconfig.features.showDZImages) {
|
|
181
181
|
q2.DZImages = {
|
|
182
182
|
type: q.DZImages.type
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
|
+
if (q.WSImages && serverconfig.features.showWSImages) {
|
|
186
|
+
q2.WSImages = {
|
|
187
|
+
type: q.WSImages.type
|
|
188
|
+
};
|
|
189
|
+
}
|
|
185
190
|
if (q.singleSampleGbtk) {
|
|
186
191
|
q2.singleSampleGbtk = {};
|
|
187
192
|
for (const k in q.singleSampleGbtk) {
|
|
@@ -84,7 +84,11 @@ function init({ genomes }) {
|
|
|
84
84
|
}
|
|
85
85
|
values.push(Number(value));
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
if (values.length) {
|
|
88
|
+
result = Summarystats(values);
|
|
89
|
+
} else {
|
|
90
|
+
result = {};
|
|
91
|
+
}
|
|
88
92
|
} catch (e) {
|
|
89
93
|
if (e instanceof Error && e.stack)
|
|
90
94
|
console.log(e);
|
|
@@ -45,7 +45,7 @@ async function trigger_gettermsbyid(q, res, tdb) {
|
|
|
45
45
|
for (const id of q.ids) {
|
|
46
46
|
const term = tdb.q.termjsonByOneid(id);
|
|
47
47
|
if (term) {
|
|
48
|
-
if (term.type == "categorical" && !term.values
|
|
48
|
+
if (term.type == "categorical" && !term.values) {
|
|
49
49
|
term.values = {};
|
|
50
50
|
term.samplecount = {};
|
|
51
51
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
3
|
-
import got from "got";
|
|
4
3
|
import serverconfig from "#src/serverconfig.js";
|
|
5
4
|
import { get_samples } from "#src/termdb.sql.js";
|
|
6
5
|
import { makeFilter } from "#src/mds3.gdc.js";
|
|
6
|
+
import { cachedFetch } from "#src/utils.js";
|
|
7
7
|
const api = {
|
|
8
8
|
endpoint: "termdb/topVariablyExpressedGenes",
|
|
9
9
|
methods: {
|
|
@@ -61,6 +61,7 @@ function nativeValidateQuery(ds) {
|
|
|
61
61
|
throw "topVariablyExpressedGenes query given but geneExpression missing";
|
|
62
62
|
if (gE.src != "native")
|
|
63
63
|
throw "topVariablyExpressedGenes is native but geneExpression.src is not native";
|
|
64
|
+
addTopVEarg(ds.queries.topVariablyExpressedGenes);
|
|
64
65
|
ds.queries.topVariablyExpressedGenes.getGenes = async (q) => {
|
|
65
66
|
const samples = [];
|
|
66
67
|
if (q.filter) {
|
|
@@ -81,18 +82,81 @@ function nativeValidateQuery(ds) {
|
|
|
81
82
|
samples.push(n);
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
|
-
const genes = await computeGenes4nativeDs(q,
|
|
85
|
+
const genes = await computeGenes4nativeDs(q, gE.file, samples);
|
|
85
86
|
return genes;
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
+
function addTopVEarg(q) {
|
|
90
|
+
const arglst = [
|
|
91
|
+
{ id: "maxGenes", label: "Gene Count", type: "number", value: 100 },
|
|
92
|
+
{
|
|
93
|
+
id: "filter_extreme_values",
|
|
94
|
+
label: "Filter Extreme Values",
|
|
95
|
+
type: "boolean",
|
|
96
|
+
value: true,
|
|
97
|
+
options: [
|
|
98
|
+
{
|
|
99
|
+
id: "min_count",
|
|
100
|
+
label: "Min count",
|
|
101
|
+
type: "number",
|
|
102
|
+
value: 10
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "min_total_count",
|
|
106
|
+
label: "Min total count",
|
|
107
|
+
type: "number",
|
|
108
|
+
value: 15
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "rank_type",
|
|
114
|
+
label: "Rank by:",
|
|
115
|
+
type: "radio",
|
|
116
|
+
options: [
|
|
117
|
+
/** The param option in input JSON is very important.
|
|
118
|
+
* It instructs what method will be used to calculate variation in the counts for a particular gene.
|
|
119
|
+
* It supports variance as well as interquartile region.
|
|
120
|
+
* This is based on the recommendation of this article:
|
|
121
|
+
* https://www.frontiersin.org/articles/10.3389/fgene.2021.632620/full.
|
|
122
|
+
* This article recommends using interquartile region over variance.*/
|
|
123
|
+
{
|
|
124
|
+
type: "boolean",
|
|
125
|
+
label: "Variance",
|
|
126
|
+
value: "var"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: "boolean",
|
|
130
|
+
label: "Interquartile Region",
|
|
131
|
+
value: "iqr"
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
if (q.arguments) {
|
|
137
|
+
for (const a of q.arguments) {
|
|
138
|
+
if (!a.id)
|
|
139
|
+
throw "missing id of topVE.arguments[]";
|
|
140
|
+
const item = arglst.find((i) => i.id == a.id);
|
|
141
|
+
if (!item)
|
|
142
|
+
throw "unknown id of topVE.arguments[]";
|
|
143
|
+
Object.assign(item, a);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
q.arguments = arglst;
|
|
147
|
+
}
|
|
148
|
+
async function computeGenes4nativeDs(q, matrixFile, samples) {
|
|
89
149
|
const input_json = {
|
|
90
150
|
input_file: matrixFile,
|
|
91
151
|
samples: samples.join(","),
|
|
92
|
-
filter_extreme_values:
|
|
152
|
+
filter_extreme_values: q.filter_extreme_values,
|
|
93
153
|
num_genes: q.maxGenes,
|
|
94
|
-
|
|
154
|
+
rank_type: q.rank_type?.type
|
|
95
155
|
};
|
|
156
|
+
if (q.filter_extreme_values == 1) {
|
|
157
|
+
input_json["min_count"] = q.min_count;
|
|
158
|
+
input_json["min_total_count"] = q.min_total_count;
|
|
159
|
+
}
|
|
96
160
|
const rust_output = await run_rust("topGeneByExpressionVariance", JSON.stringify(input_json));
|
|
97
161
|
const rust_output_list = rust_output.split("\n");
|
|
98
162
|
let output_json;
|
|
@@ -109,7 +173,7 @@ async function computeGenes4nativeDs(q, ds, matrixFile, samples) {
|
|
|
109
173
|
function gdcValidateQuery(ds, genome) {
|
|
110
174
|
ds.queries.topVariablyExpressedGenes.getGenes = async (q) => {
|
|
111
175
|
if (serverconfig.features.gdcGenes) {
|
|
112
|
-
console.
|
|
176
|
+
console.error(
|
|
113
177
|
"!!GDC!! using serverconfig.features.gdcGenes[] but not live api query. only use this on DEV and never on PROD!"
|
|
114
178
|
);
|
|
115
179
|
return serverconfig.features.gdcGenes;
|
|
@@ -120,11 +184,12 @@ function gdcValidateQuery(ds, genome) {
|
|
|
120
184
|
const { host, headers } = ds.getHostHeaders(q);
|
|
121
185
|
const url = path.join(host.geneExp, "/gene_expression/gene_selection");
|
|
122
186
|
try {
|
|
123
|
-
const response = await
|
|
187
|
+
const response = await cachedFetch(url, {
|
|
188
|
+
method: "POST",
|
|
124
189
|
headers,
|
|
125
|
-
body:
|
|
190
|
+
body: getGeneSelectionArg(q)
|
|
126
191
|
});
|
|
127
|
-
const re =
|
|
192
|
+
const re = response.body;
|
|
128
193
|
const genes = [];
|
|
129
194
|
if (!Array.isArray(re.gene_selection))
|
|
130
195
|
throw "re.gene_selection[] is not array";
|
|
@@ -141,7 +206,7 @@ function gdcValidateQuery(ds, genome) {
|
|
|
141
206
|
}
|
|
142
207
|
return genes;
|
|
143
208
|
} catch (e) {
|
|
144
|
-
console.
|
|
209
|
+
console.error(e.stack || e);
|
|
145
210
|
throw e;
|
|
146
211
|
}
|
|
147
212
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import ky from "ky";
|
|
2
|
+
import serverconfig from "#src/serverconfig.js";
|
|
3
|
+
const routePath = "tileserver";
|
|
4
|
+
const api = {
|
|
5
|
+
endpoint: `${routePath}/layer/slide/:sampleId/zoomify/:TileGroup/:z-:x-:y@1x.jpg`,
|
|
6
|
+
methods: {
|
|
7
|
+
get: {
|
|
8
|
+
init,
|
|
9
|
+
request: {
|
|
10
|
+
typeId: "any"
|
|
11
|
+
},
|
|
12
|
+
response: {
|
|
13
|
+
typeId: "any"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
post: {
|
|
17
|
+
alternativeFor: "get",
|
|
18
|
+
init
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function init() {
|
|
23
|
+
return async (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const { sampleId, TileGroup, z, x, y } = req.params;
|
|
26
|
+
const url = `${serverconfig.tileServerURL}/tileserver/layer/slide/${sampleId}/zoomify/${TileGroup}/${z}-${x}-${y}@1x.jpg`;
|
|
27
|
+
const response = await ky.get(url);
|
|
28
|
+
const buffer = await response.arrayBuffer();
|
|
29
|
+
res.status(response.status).send(Buffer.from(buffer));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.response) {
|
|
32
|
+
const errorBody = await error.response.arrayBuffer();
|
|
33
|
+
res.status(error.response.status).send(Buffer.from(errorBody));
|
|
34
|
+
} else {
|
|
35
|
+
res.status(500).send("Internal Server Error");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
api
|
|
42
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import ky from "ky";
|
|
2
|
+
import qs from "qs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import serverconfig from "#src/serverconfig.js";
|
|
5
|
+
import { CookieJar } from "tough-cookie";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
const routePath = "wsimages";
|
|
8
|
+
const api = {
|
|
9
|
+
endpoint: `${routePath}`,
|
|
10
|
+
methods: {
|
|
11
|
+
get: {
|
|
12
|
+
init,
|
|
13
|
+
request: {
|
|
14
|
+
typeId: "any"
|
|
15
|
+
},
|
|
16
|
+
response: {
|
|
17
|
+
typeId: "any"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
post: {
|
|
21
|
+
alternativeFor: "get",
|
|
22
|
+
init
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
function init({ genomes }) {
|
|
27
|
+
return async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const g = genomes[req.query.genome];
|
|
30
|
+
if (!g)
|
|
31
|
+
throw "invalid genome name";
|
|
32
|
+
const ds = g.datasets[req.query.dslabel];
|
|
33
|
+
if (!ds)
|
|
34
|
+
throw "invalid dataset name";
|
|
35
|
+
const sampleId = req.query.sampleId;
|
|
36
|
+
if (!sampleId)
|
|
37
|
+
throw "invalid sampleId";
|
|
38
|
+
const wsimage = req.query.wsimage;
|
|
39
|
+
if (!wsimage)
|
|
40
|
+
throw "invalid wsimage";
|
|
41
|
+
const cookieJar = new CookieJar();
|
|
42
|
+
const setCookie = promisify(cookieJar.setCookie.bind(cookieJar));
|
|
43
|
+
const getCookieString = promisify(cookieJar.getCookieString.bind(cookieJar));
|
|
44
|
+
await ky.get(`${serverconfig.tileServerURL}/tileserver/session_id`, {
|
|
45
|
+
hooks: {
|
|
46
|
+
beforeRequest: [
|
|
47
|
+
async (request) => {
|
|
48
|
+
const cookie = await getCookieString(request.url);
|
|
49
|
+
request.headers.set("Cookie", cookie);
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
afterResponse: [
|
|
53
|
+
async (request, options, response) => {
|
|
54
|
+
const setCookieHeader = response.headers.get("set-cookie");
|
|
55
|
+
if (setCookieHeader) {
|
|
56
|
+
await setCookie(setCookieHeader, request.url);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const cookieString = await getCookieString(`${serverconfig.tileServerURL}/tileserver/session_id`);
|
|
63
|
+
const sessionId = cookieString.match(/session_id=([^;]*)/)?.[1];
|
|
64
|
+
const sampleWsiTileServer = path.join(
|
|
65
|
+
`${serverconfig.tileServerMount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
|
|
66
|
+
wsimage
|
|
67
|
+
);
|
|
68
|
+
const data = qs.stringify({ slide_path: sampleWsiTileServer });
|
|
69
|
+
await ky.put(`${serverconfig.tileServerURL}/tileserver/slide`, {
|
|
70
|
+
body: data,
|
|
71
|
+
headers: {
|
|
72
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
73
|
+
Cookie: `session_id=${sessionId}`
|
|
74
|
+
// Include the session_id in the headers
|
|
75
|
+
},
|
|
76
|
+
hooks: {
|
|
77
|
+
beforeRequest: [
|
|
78
|
+
async (request) => {
|
|
79
|
+
const cookie = await getCookieString(request.url);
|
|
80
|
+
request.headers.set("Cookie", cookie);
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
afterResponse: [
|
|
84
|
+
async (request, options, response) => {
|
|
85
|
+
const setCookieHeader = response.headers.get("set-cookie");
|
|
86
|
+
if (setCookieHeader) {
|
|
87
|
+
await setCookie(setCookieHeader, request.url);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
const getWsiImageResponse = await ky.get(`${serverconfig.tileServerURL}/tileserver/slide`, {
|
|
94
|
+
hooks: {
|
|
95
|
+
beforeRequest: [
|
|
96
|
+
async (request) => {
|
|
97
|
+
const cookie = await getCookieString(request.url);
|
|
98
|
+
request.headers.set("Cookie", cookie);
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}).json();
|
|
103
|
+
res.status(200).json({ sessionId, slide_dimensions: getWsiImageResponse.slide_dimensions });
|
|
104
|
+
} catch (e) {
|
|
105
|
+
console.log(e);
|
|
106
|
+
res.send({
|
|
107
|
+
status: "error",
|
|
108
|
+
error: e.error || e
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
api
|
|
115
|
+
};
|