@sjcrh/proteinpaint-server 2.147.2-0 → 2.148.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/aiProjectSelectedWSImages.js +5 -1
- package/routes/aiProjectTrainModel.js +1 -0
- package/routes/grin2.js +101 -42
- package/src/app.js +2309 -2238
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.148.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.146.4-0",
|
|
66
66
|
"@sjcrh/proteinpaint-rust": "2.146.4-1",
|
|
67
67
|
"@sjcrh/proteinpaint-shared": "2.147.1",
|
|
68
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
68
|
+
"@sjcrh/proteinpaint-types": "2.148.0",
|
|
69
69
|
"@types/express": "^5.0.0",
|
|
70
70
|
"@types/express-session": "^1.18.1",
|
|
71
71
|
"better-sqlite3": "^9.4.1",
|
|
@@ -35,7 +35,11 @@ function init({ genomes }) {
|
|
|
35
35
|
wsimage.activePatchColor = ds.queries?.WSImages?.activePatchColor;
|
|
36
36
|
if (ds.queries.WSImages.getWSIPredictionPatches) {
|
|
37
37
|
const predictions = await ds.queries.WSImages.getWSIPredictionPatches(projectId, wsimageFilename);
|
|
38
|
-
wsimage.
|
|
38
|
+
const classMap = new Map((wsimage.classes || []).map((c) => [c.id, c.label]));
|
|
39
|
+
wsimage.predictions = (predictions || []).map((p) => {
|
|
40
|
+
const label = classMap.get(p.class) ?? p.class;
|
|
41
|
+
return { ...p, class: label };
|
|
42
|
+
});
|
|
39
43
|
}
|
|
40
44
|
wsimages.push(wsimage);
|
|
41
45
|
}
|
package/routes/grin2.js
CHANGED
|
@@ -6,6 +6,7 @@ import { mayLog } from "#src/helpers.ts";
|
|
|
6
6
|
import { get_samples } from "#src/termdb.sql.js";
|
|
7
7
|
import { read_file, file_is_readable } from "#src/utils.js";
|
|
8
8
|
import { dtsnvindel, dtcnv, dtfusionrna } from "#shared/common.js";
|
|
9
|
+
const MAX_LESIONS_PER_TYPE = 5e4;
|
|
9
10
|
const api = {
|
|
10
11
|
endpoint: "grin2",
|
|
11
12
|
methods: {
|
|
@@ -23,7 +24,6 @@ function init({ genomes }) {
|
|
|
23
24
|
return async (req, res) => {
|
|
24
25
|
try {
|
|
25
26
|
const request = req.query;
|
|
26
|
-
console.log("[GRIN2] request:", request);
|
|
27
27
|
const g = genomes[request.genome];
|
|
28
28
|
if (!g) throw new Error("genome missing");
|
|
29
29
|
const ds = g.datasets?.[request.dslabel];
|
|
@@ -43,7 +43,6 @@ function init({ genomes }) {
|
|
|
43
43
|
}
|
|
44
44
|
async function runGrin2(g, ds, request) {
|
|
45
45
|
const startTime = Date.now();
|
|
46
|
-
mayLog("[GRIN2] Getting samples from cohort filter...");
|
|
47
46
|
const samples = await get_samples(
|
|
48
47
|
request,
|
|
49
48
|
ds,
|
|
@@ -55,16 +54,16 @@ async function runGrin2(g, ds, request) {
|
|
|
55
54
|
if (samples.length === 0) {
|
|
56
55
|
throw new Error("No samples found matching the provided filter criteria");
|
|
57
56
|
}
|
|
58
|
-
|
|
57
|
+
const tracker = getLesionTracker(request);
|
|
59
58
|
const processingStartTime = Date.now();
|
|
60
|
-
const { lesions, processingSummary } = await processSampleData(samples, ds, request);
|
|
59
|
+
const { lesions, processingSummary } = await processSampleData(samples, ds, request, tracker);
|
|
61
60
|
const processingTime = Date.now() - processingStartTime;
|
|
62
61
|
const processingTimeToPrint = Math.round(processingTime / 1e3);
|
|
63
62
|
mayLog(`[GRIN2] Data processing took ${processingTimeToPrint} seconds`);
|
|
64
63
|
mayLog(
|
|
65
|
-
`[GRIN2] Processing summary: ${processingSummary?.
|
|
64
|
+
`[GRIN2] Processing summary: ${processingSummary?.processedSamples ?? 0}/${processingSummary?.totalSamples ?? samples.length} samples processed successfully`
|
|
66
65
|
);
|
|
67
|
-
if (processingSummary && processingSummary.failedSamples > 0) {
|
|
66
|
+
if (processingSummary?.failedSamples !== void 0 && processingSummary.failedSamples > 0) {
|
|
68
67
|
mayLog(`[GRIN2] Warning: ${processingSummary.failedSamples} samples failed to process`);
|
|
69
68
|
}
|
|
70
69
|
if (lesions.length === 0) {
|
|
@@ -79,14 +78,12 @@ async function runGrin2(g, ds, request) {
|
|
|
79
78
|
width: request.width,
|
|
80
79
|
height: request.height
|
|
81
80
|
};
|
|
82
|
-
mayLog("[GRIN2] Prepared input for Python script:", { ...pyInput });
|
|
83
81
|
for (const c in g.majorchr) {
|
|
84
82
|
if (ds.queries.singleSampleMutation.discoPlot?.skipChrM) {
|
|
85
83
|
if (c.toLowerCase() == "chrm") continue;
|
|
86
84
|
}
|
|
87
85
|
pyInput.chromosomelist[c] = g.majorchr[c];
|
|
88
86
|
}
|
|
89
|
-
mayLog(`[GRIN2] Prepared ${lesions.length.toLocaleString()} lesions for analysis`);
|
|
90
87
|
const grin2AnalysisStart = Date.now();
|
|
91
88
|
const pyResult = await run_python("grin2PpWrapper.py", JSON.stringify(pyInput));
|
|
92
89
|
if (pyResult.stderr?.trim()) {
|
|
@@ -119,75 +116,137 @@ async function runGrin2(g, ds, request) {
|
|
|
119
116
|
};
|
|
120
117
|
return response;
|
|
121
118
|
}
|
|
122
|
-
|
|
119
|
+
function getLesionTracker(req) {
|
|
120
|
+
const currentTypes = [];
|
|
121
|
+
if (req.snvindelOptions) currentTypes.push(dtsnvindel);
|
|
122
|
+
if (req.cnvOptions) currentTypes.push(dtcnv);
|
|
123
|
+
if (req.fusionOptions) currentTypes.push(dtfusionrna);
|
|
124
|
+
const track = /* @__PURE__ */ new Map();
|
|
125
|
+
for (const t of currentTypes) track.set(t, { count: 0 });
|
|
126
|
+
return track;
|
|
127
|
+
}
|
|
128
|
+
function allTypesCapped(tracker) {
|
|
129
|
+
for (const value of tracker.values()) {
|
|
130
|
+
if (value.count < MAX_LESIONS_PER_TYPE) return false;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
async function processSampleData(samples, ds, request, tracker) {
|
|
123
135
|
const lesions = [];
|
|
124
|
-
let lesionId = 1;
|
|
125
136
|
const processingSummary = {
|
|
126
137
|
totalSamples: samples.length,
|
|
127
|
-
|
|
138
|
+
processedSamples: 0,
|
|
128
139
|
failedSamples: 0,
|
|
129
|
-
failedFiles: []
|
|
140
|
+
failedFiles: [],
|
|
141
|
+
totalLesions: 0,
|
|
142
|
+
processedLesions: 0,
|
|
143
|
+
unprocessedSamples: 0
|
|
130
144
|
};
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
outer: for (let i = 0; i < samples.length; i++) {
|
|
146
|
+
if (allTypesCapped(tracker)) {
|
|
147
|
+
const remaining = samples.length - i;
|
|
148
|
+
if (remaining > 0) processingSummary.unprocessedSamples += remaining;
|
|
149
|
+
mayLog("[GRIN2] All enabled per-type caps reached; stopping early.");
|
|
150
|
+
break outer;
|
|
151
|
+
}
|
|
152
|
+
const sample = samples[i];
|
|
153
|
+
const filepath = path.join(serverconfig.tpmasterdir, ds.queries.singleSampleMutation.folder, sample.name);
|
|
133
154
|
try {
|
|
134
|
-
const filepath = path.join(serverconfig.tpmasterdir, ds.queries.singleSampleMutation.folder, sample.name);
|
|
135
155
|
await file_is_readable(filepath);
|
|
136
156
|
const mlst = JSON.parse(await read_file(filepath));
|
|
137
|
-
const { sampleLesions } = await processSampleMlst(sample.name, mlst,
|
|
157
|
+
const { sampleLesions } = await processSampleMlst(sample.name, mlst, request, tracker);
|
|
138
158
|
lesions.push(...sampleLesions);
|
|
139
|
-
|
|
140
|
-
processingSummary.
|
|
159
|
+
processingSummary.processedSamples += 1;
|
|
160
|
+
processingSummary.totalLesions += sampleLesions.length;
|
|
161
|
+
if (allTypesCapped(tracker)) {
|
|
162
|
+
const remaining = samples.length - 1 - i;
|
|
163
|
+
if (remaining > 0) processingSummary.unprocessedSamples += remaining;
|
|
164
|
+
mayLog("[GRIN2] All enabled per-type caps reached; stopping early.");
|
|
165
|
+
break outer;
|
|
166
|
+
}
|
|
141
167
|
} catch (error) {
|
|
142
|
-
processingSummary.failedSamples
|
|
143
|
-
processingSummary.failedFiles
|
|
168
|
+
processingSummary.failedSamples += 1;
|
|
169
|
+
processingSummary.failedFiles.push({
|
|
170
|
+
sampleName: sample.name,
|
|
171
|
+
filePath: filepath,
|
|
172
|
+
error: error instanceof Error ? error.message || "Unknown error" : String(error)
|
|
173
|
+
});
|
|
174
|
+
mayLog(`[GRIN2] Error processing sample ${sample.name}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
processingSummary.processedLesions = lesions.length;
|
|
178
|
+
for (const [type, info] of tracker.entries()) {
|
|
179
|
+
if (info.count >= MAX_LESIONS_PER_TYPE) {
|
|
180
|
+
let label;
|
|
181
|
+
switch (type) {
|
|
182
|
+
case dtsnvindel:
|
|
183
|
+
label = "mutation";
|
|
184
|
+
break;
|
|
185
|
+
case dtcnv:
|
|
186
|
+
label = "CNV (gain/loss)";
|
|
187
|
+
break;
|
|
188
|
+
case dtfusionrna:
|
|
189
|
+
label = "fusion";
|
|
190
|
+
break;
|
|
191
|
+
default:
|
|
192
|
+
label = `type ${type}`;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
144
195
|
mayLog(
|
|
145
|
-
`[GRIN2]
|
|
196
|
+
`[GRIN2] Warning: ${label} lesions reached the per-type cap of ${MAX_LESIONS_PER_TYPE.toLocaleString()}. No further ${label} lesions were processed.`
|
|
146
197
|
);
|
|
147
198
|
}
|
|
148
199
|
}
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
lesions,
|
|
152
|
-
processingSummary
|
|
153
|
-
};
|
|
200
|
+
return { lesions, processingSummary };
|
|
154
201
|
}
|
|
155
|
-
async function processSampleMlst(sampleName, mlst,
|
|
156
|
-
const
|
|
202
|
+
async function processSampleMlst(sampleName, mlst, request, tracker) {
|
|
203
|
+
const sampleLesions = [];
|
|
157
204
|
for (const m of mlst) {
|
|
158
205
|
switch (m.dt) {
|
|
159
206
|
case dtsnvindel: {
|
|
160
207
|
if (!request.snvindelOptions) break;
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
163
|
-
|
|
208
|
+
const entry = tracker.get(dtsnvindel);
|
|
209
|
+
if (entry && entry.count >= MAX_LESIONS_PER_TYPE) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
const les = filterAndConvertSnvIndel(sampleName, m, request.snvindelOptions);
|
|
213
|
+
if (les && entry) {
|
|
214
|
+
entry.count++;
|
|
215
|
+
sampleLesions.push(les);
|
|
164
216
|
}
|
|
165
217
|
break;
|
|
166
218
|
}
|
|
167
219
|
case dtcnv: {
|
|
168
220
|
if (!request.cnvOptions) break;
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
171
|
-
|
|
221
|
+
const cnv = tracker.get(dtcnv);
|
|
222
|
+
if (cnv && cnv.count >= MAX_LESIONS_PER_TYPE) {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
const les = filterAndConvertCnv(sampleName, m, request.cnvOptions);
|
|
226
|
+
if (les && cnv) {
|
|
227
|
+
cnv.count++;
|
|
228
|
+
sampleLesions.push(les);
|
|
172
229
|
}
|
|
173
230
|
break;
|
|
174
231
|
}
|
|
175
232
|
case dtfusionrna: {
|
|
176
233
|
if (!request.fusionOptions) break;
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
|
|
234
|
+
const fusion = tracker.get(dtfusionrna);
|
|
235
|
+
if (fusion && fusion.count >= MAX_LESIONS_PER_TYPE) {
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
const les = filterAndConvertFusion(sampleName, m, request.fusionOptions);
|
|
239
|
+
if (les && fusion) {
|
|
240
|
+
fusion.count++;
|
|
241
|
+
sampleLesions.push(les);
|
|
180
242
|
}
|
|
181
243
|
break;
|
|
182
244
|
}
|
|
183
|
-
default:
|
|
245
|
+
default:
|
|
184
246
|
break;
|
|
185
|
-
}
|
|
186
247
|
}
|
|
187
248
|
}
|
|
188
|
-
return {
|
|
189
|
-
sampleLesions: lesions
|
|
190
|
-
};
|
|
249
|
+
return { sampleLesions };
|
|
191
250
|
}
|
|
192
251
|
function filterAndConvertSnvIndel(sampleName, entry, options) {
|
|
193
252
|
if (!options?.consequences) {
|