@sjcrh/proteinpaint-server 2.133.2 → 2.133.3-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 +2 -2
- package/routes/sampleWsiAiApi.js +33 -0
- package/routes/samplewsimages.js +39 -2
- package/routes/wsimages.js +82 -21
- package/src/app.js +594 -429
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.133.
|
|
3
|
+
"version": "2.133.3-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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@sjcrh/proteinpaint-r": "2.130.0",
|
|
66
66
|
"@sjcrh/proteinpaint-rust": "2.133.0",
|
|
67
67
|
"@sjcrh/proteinpaint-shared": "2.132.1-2",
|
|
68
|
-
"@sjcrh/proteinpaint-types": "2.133.0",
|
|
68
|
+
"@sjcrh/proteinpaint-types": "2.133.3-0",
|
|
69
69
|
"@types/express": "^5.0.0",
|
|
70
70
|
"@types/express-session": "^1.18.1",
|
|
71
71
|
"better-sqlite3": "^9.4.1",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { sampleWsiAiApiPayload } from "#types/checkers";
|
|
2
|
+
const routePath = "sampleWsiAiApi";
|
|
3
|
+
const api = {
|
|
4
|
+
endpoint: `${routePath}`,
|
|
5
|
+
methods: {
|
|
6
|
+
get: {
|
|
7
|
+
...sampleWsiAiApiPayload,
|
|
8
|
+
init
|
|
9
|
+
},
|
|
10
|
+
post: {
|
|
11
|
+
...sampleWsiAiApiPayload,
|
|
12
|
+
init
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function init() {
|
|
17
|
+
return async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const request = req.query;
|
|
20
|
+
console.log("sample wsi api request:", request);
|
|
21
|
+
res.status(200).send({ testKey: "completed" });
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.warn(e);
|
|
24
|
+
res.status(500).send({
|
|
25
|
+
status: "error",
|
|
26
|
+
error: e.message || e
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
api
|
|
33
|
+
};
|
package/routes/samplewsimages.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { sampleWSImagesPayload } from "#types/checkers";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import serverconfig from "#src/serverconfig.js";
|
|
2
5
|
const api = {
|
|
3
6
|
endpoint: "samplewsimages",
|
|
4
7
|
methods: {
|
|
@@ -26,14 +29,40 @@ function init({ genomes }) {
|
|
|
26
29
|
const wsimages = await ds.queries.WSImages.getWSImages(sampleId);
|
|
27
30
|
if (ds.queries.WSImages.getWSIAnnotations) {
|
|
28
31
|
for (const wsimage of wsimages) {
|
|
32
|
+
if (ds.queries.WSImages.makeGeoJson) {
|
|
33
|
+
await ds.queries.WSImages.makeGeoJson(sampleId, wsimage);
|
|
34
|
+
}
|
|
29
35
|
const annotations = await ds.queries.WSImages.getWSIAnnotations(sampleId, wsimage.filename);
|
|
30
|
-
if (annotations) {
|
|
36
|
+
if (annotations && annotations.length > 0) {
|
|
31
37
|
wsimage.overlays = annotations;
|
|
38
|
+
const annotationFilePath = path.join(
|
|
39
|
+
serverconfig.tpmasterdir,
|
|
40
|
+
ds.queries.WSImages.imageBySampleFolder,
|
|
41
|
+
sampleId,
|
|
42
|
+
annotations[0]
|
|
43
|
+
);
|
|
44
|
+
const annotationData = JSON.parse(fs.readFileSync(annotationFilePath, "utf8"));
|
|
45
|
+
if (!annotationData.features && !ds.queries.WSImages?.classes?.length) {
|
|
46
|
+
throw new Error(`No classes found for WSImage annotations in dataset ${ds.label}`);
|
|
47
|
+
}
|
|
48
|
+
wsimage.annotationsData = annotationData.features.map((d) => {
|
|
49
|
+
const featClass = ds.queries.WSImages?.classes?.find((f) => f.id == d.properties.class)?.label || d.properties.class;
|
|
50
|
+
return {
|
|
51
|
+
zoomCoordinates: d.properties.zoomCoordinates,
|
|
52
|
+
type: d.properties.type,
|
|
53
|
+
uncertainty: d.properties.uncertainty,
|
|
54
|
+
class: featClass
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
wsimage.classes = ds.queries?.WSImages?.classes;
|
|
58
|
+
wsimage.uncertainty = ds.queries?.WSImages?.uncertainty;
|
|
59
|
+
wsimage.activePatchColor = ds.queries?.WSImages?.activePatchColor || "#00e62a";
|
|
32
60
|
}
|
|
33
61
|
if (ds.queries.WSImages.getZoomInPoints) {
|
|
34
62
|
const zoomInPoints = await ds.queries.WSImages.getZoomInPoints(
|
|
35
63
|
sampleId,
|
|
36
|
-
wsimage.filename
|
|
64
|
+
wsimage.filename,
|
|
65
|
+
query.index
|
|
37
66
|
);
|
|
38
67
|
if (zoomInPoints) {
|
|
39
68
|
wsimage.zoomInPoints = zoomInPoints;
|
|
@@ -41,6 +70,14 @@ function init({ genomes }) {
|
|
|
41
70
|
}
|
|
42
71
|
}
|
|
43
72
|
}
|
|
73
|
+
if (ds.queries.WSImages.getWSIPredictionOverlay) {
|
|
74
|
+
for (const wsimage of wsimages) {
|
|
75
|
+
const predictionOverlay = await ds.queries.WSImages.getWSIPredictionOverlay(sampleId, wsimage.filename);
|
|
76
|
+
if (predictionOverlay) {
|
|
77
|
+
wsimage.predictionLayers = [predictionOverlay];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
44
81
|
res.send({ sampleWSImages: wsimages });
|
|
45
82
|
} catch (e) {
|
|
46
83
|
console.log(e);
|
package/routes/wsimages.js
CHANGED
|
@@ -3,6 +3,7 @@ import qs from "qs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { CookieJar } from "tough-cookie";
|
|
5
5
|
import { promisify } from "util";
|
|
6
|
+
import { PredictionOverlayType } from "#types";
|
|
6
7
|
import { wsImagesPayload } from "#types/checkers";
|
|
7
8
|
import SessionManager from "../src/wsisessions/SessionManager.ts";
|
|
8
9
|
import { ShardManager } from "#src/sharding/ShardManager.ts";
|
|
@@ -45,11 +46,16 @@ function init({ genomes }) {
|
|
|
45
46
|
if (!mount)
|
|
46
47
|
throw new Error("No mount available for TileServer");
|
|
47
48
|
const wsiImagePath = path.join(`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`, wsimage);
|
|
48
|
-
const
|
|
49
|
-
const getWsiImageResponse = await getWsiImageDimensions(
|
|
49
|
+
const session = await getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie, wsiImagePath);
|
|
50
|
+
const getWsiImageResponse = await getWsiImageDimensions(
|
|
51
|
+
session.imageSessionId,
|
|
52
|
+
getCookieString,
|
|
53
|
+
wsiImagePath
|
|
54
|
+
);
|
|
50
55
|
const payload = {
|
|
51
56
|
status: "ok",
|
|
52
|
-
wsiSessionId:
|
|
57
|
+
wsiSessionId: session.imageSessionId,
|
|
58
|
+
overlays: session.overlays,
|
|
53
59
|
slide_dimensions: getWsiImageResponse.slide_dimensions
|
|
54
60
|
};
|
|
55
61
|
res.status(200).json(payload);
|
|
@@ -67,9 +73,9 @@ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie,
|
|
|
67
73
|
const invalidateResult = await sessionManager.syncAndInvalidateSessions(wsimage);
|
|
68
74
|
if (!invalidateResult)
|
|
69
75
|
throw new Error("Session invalidation failed");
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
return
|
|
76
|
+
const session = await sessionManager.getSession(wsimage);
|
|
77
|
+
if (session) {
|
|
78
|
+
return session;
|
|
73
79
|
}
|
|
74
80
|
const tileServer = await sessionManager.getTileServerShard(wsimage);
|
|
75
81
|
if (!tileServer)
|
|
@@ -82,6 +88,7 @@ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie,
|
|
|
82
88
|
const sessionId = cookieString.match(/session_id=([^;]*)/)?.[1];
|
|
83
89
|
if (!sessionId)
|
|
84
90
|
throw new Error("session_id not found");
|
|
91
|
+
const overlays = [];
|
|
85
92
|
const data = qs.stringify({ slide_path: wsimage });
|
|
86
93
|
await ky.put(`${tileServer.url}/tileserver/slide`, {
|
|
87
94
|
body: data,
|
|
@@ -92,25 +99,79 @@ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie,
|
|
|
92
99
|
},
|
|
93
100
|
hooks: getHooks(cookieJar, getCookieString, setCookie)
|
|
94
101
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
if (ds.queries.WSImages.getWSIPredictionOverlay) {
|
|
103
|
+
const predictionOverlay = await ds.queries.WSImages.getWSIPredictionOverlay(sampleId, wsimage);
|
|
104
|
+
if (predictionOverlay) {
|
|
105
|
+
const mount = serverconfig.features?.tileserver?.mount;
|
|
106
|
+
const annotationsFilePath = path.join(
|
|
107
|
+
`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
|
|
108
|
+
predictionOverlay
|
|
109
|
+
);
|
|
110
|
+
const annotationsData = qs.stringify({
|
|
111
|
+
overlay_path: annotationsFilePath
|
|
112
|
+
});
|
|
113
|
+
const layerNumber = await ky.put(`${tileServer.url}/tileserver/overlay`, {
|
|
114
|
+
body: annotationsData,
|
|
115
|
+
timeout: 5e4,
|
|
116
|
+
headers: {
|
|
117
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
118
|
+
Cookie: `session_id=${sessionId}`
|
|
119
|
+
},
|
|
120
|
+
hooks: getHooks(cookieJar, getCookieString, setCookie)
|
|
121
|
+
}).json();
|
|
122
|
+
const overlay = {
|
|
123
|
+
layerNumber,
|
|
124
|
+
predictionOverlayType: PredictionOverlayType.PREDICTION
|
|
125
|
+
};
|
|
126
|
+
overlays.push(overlay);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (ds.queries.WSImages.getWSIUncertaintyOverlay) {
|
|
130
|
+
const uncertaintyOverlay = await ds.queries.WSImages.getWSIUncertaintyOverlay(sampleId, wsimage);
|
|
131
|
+
if (uncertaintyOverlay) {
|
|
132
|
+
const mount = serverconfig.features?.tileserver?.mount;
|
|
133
|
+
const annotationsFilePath = path.join(
|
|
134
|
+
`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
|
|
135
|
+
uncertaintyOverlay
|
|
136
|
+
);
|
|
137
|
+
const annotationsData = qs.stringify({
|
|
138
|
+
overlay_path: annotationsFilePath
|
|
139
|
+
});
|
|
140
|
+
const layerNumber = await ky.put(`${tileServer.url}/tileserver/overlay`, {
|
|
141
|
+
body: annotationsData,
|
|
142
|
+
timeout: 5e4,
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
145
|
+
Cookie: `session_id=${sessionId}`
|
|
146
|
+
},
|
|
147
|
+
hooks: getHooks(cookieJar, getCookieString, setCookie)
|
|
148
|
+
}).json();
|
|
149
|
+
const overlay = {
|
|
150
|
+
layerNumber,
|
|
151
|
+
predictionOverlayType: PredictionOverlayType.UNCERTAINTY
|
|
152
|
+
};
|
|
153
|
+
overlays.push(overlay);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const sessionData = await sessionManager.setSession(wsimage, sessionId, tileServer, overlays);
|
|
157
|
+
if (ds.queries.WSImages.getWSIPredictionPatches) {
|
|
158
|
+
const predictionPatches = await ds.queries.WSImages.getWSIPredictionPatches(sampleId, wsimage);
|
|
159
|
+
if (!predictionPatches)
|
|
160
|
+
throw new Error("No prediction files found");
|
|
100
161
|
const mount = serverconfig.features?.tileserver?.mount;
|
|
101
162
|
if (!mount)
|
|
102
163
|
throw new Error("No mount available for TileServer");
|
|
103
|
-
if (
|
|
104
|
-
for (const
|
|
105
|
-
const
|
|
164
|
+
if (predictionPatches.length > 0) {
|
|
165
|
+
for (const predictionPatch of predictionPatches) {
|
|
166
|
+
const predictionFilePath = path.join(
|
|
106
167
|
`${mount}/${ds.queries.WSImages.imageBySampleFolder}/${sampleId}`,
|
|
107
|
-
|
|
168
|
+
predictionPatch
|
|
108
169
|
);
|
|
109
|
-
const
|
|
110
|
-
overlay_path:
|
|
170
|
+
const predictionsData = qs.stringify({
|
|
171
|
+
overlay_path: predictionFilePath
|
|
111
172
|
});
|
|
112
173
|
await ky.put(`${tileServer.url}/tileserver/overlay`, {
|
|
113
|
-
body:
|
|
174
|
+
body: predictionsData,
|
|
114
175
|
timeout: 5e4,
|
|
115
176
|
headers: {
|
|
116
177
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
@@ -121,8 +182,8 @@ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie,
|
|
|
121
182
|
}
|
|
122
183
|
const cmapData = qs.stringify({
|
|
123
184
|
cmap: JSON.stringify({
|
|
124
|
-
keys: ["annotation"],
|
|
125
|
-
values: [ds.queries.WSImages.annotationsColor]
|
|
185
|
+
keys: ["prediction", "annotation"],
|
|
186
|
+
values: [ds.queries.WSImages.predictionColor, ds.queries.WSImages.annotationsColor]
|
|
126
187
|
})
|
|
127
188
|
});
|
|
128
189
|
await ky.put(`${tileServer.url}/tileserver/cmap`, {
|
|
@@ -136,7 +197,7 @@ async function getSessionId(ds, sampleId, cookieJar, getCookieString, setCookie,
|
|
|
136
197
|
});
|
|
137
198
|
}
|
|
138
199
|
}
|
|
139
|
-
return
|
|
200
|
+
return sessionData;
|
|
140
201
|
}
|
|
141
202
|
async function getWsiImageDimensions(sessionId, getCookieString, wsimage) {
|
|
142
203
|
const shardManager = ShardManager.getInstance();
|