@sjcrh/proteinpaint-server 2.139.0 → 2.140.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 +3 -3
- package/routes/grin2.js +22 -132
- package/src/app.js +38 -138
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.140.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,11 +60,11 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@sjcrh/augen": "2.136.0",
|
|
63
|
-
"@sjcrh/proteinpaint-python": "2.139.
|
|
63
|
+
"@sjcrh/proteinpaint-python": "2.139.1",
|
|
64
64
|
"@sjcrh/proteinpaint-r": "2.137.2-0",
|
|
65
65
|
"@sjcrh/proteinpaint-rust": "2.138.3-7",
|
|
66
66
|
"@sjcrh/proteinpaint-shared": "2.138.3-7",
|
|
67
|
-
"@sjcrh/proteinpaint-types": "2.139.
|
|
67
|
+
"@sjcrh/proteinpaint-types": "2.139.1",
|
|
68
68
|
"@types/express": "^5.0.0",
|
|
69
69
|
"@types/express-session": "^1.18.1",
|
|
70
70
|
"better-sqlite3": "^9.4.1",
|
package/routes/grin2.js
CHANGED
|
@@ -60,7 +60,7 @@ async function runGrin2(g, ds, request) {
|
|
|
60
60
|
}
|
|
61
61
|
mayLog("[GRIN2] Processing sample data...");
|
|
62
62
|
const processingStartTime = Date.now();
|
|
63
|
-
const {
|
|
63
|
+
const { lesions, processingSummary } = await processSampleData(samples, ds, request);
|
|
64
64
|
const processingTime = Date.now() - processingStartTime;
|
|
65
65
|
const processingTimeToPrint = Math.round(processingTime / 1e3);
|
|
66
66
|
mayLog(`[GRIN2] Data processing took ${processingTimeToPrint} seconds`);
|
|
@@ -70,13 +70,13 @@ async function runGrin2(g, ds, request) {
|
|
|
70
70
|
if (processingSummary && processingSummary.failedSamples > 0) {
|
|
71
71
|
mayLog(`[GRIN2] Warning: ${processingSummary.failedSamples} samples failed to process`);
|
|
72
72
|
}
|
|
73
|
-
if (
|
|
73
|
+
if (lesions.length === 0) {
|
|
74
74
|
throw new Error("No lesions found after processing all samples. Check filter criteria and input data.");
|
|
75
75
|
}
|
|
76
76
|
const pyInput = {
|
|
77
77
|
genedb: path.join(serverconfig.tpmasterdir, g.genedb.dbfile),
|
|
78
78
|
chromosomelist: {},
|
|
79
|
-
lesion: JSON.stringify(
|
|
79
|
+
lesion: JSON.stringify(lesions)
|
|
80
80
|
};
|
|
81
81
|
for (const c in g.majorchr) {
|
|
82
82
|
if (ds.queries.singleSampleMutation.discoPlot?.skipChrM) {
|
|
@@ -85,7 +85,7 @@ async function runGrin2(g, ds, request) {
|
|
|
85
85
|
}
|
|
86
86
|
pyInput.chromosomelist[c] = g.majorchr[c];
|
|
87
87
|
}
|
|
88
|
-
mayLog(`[GRIN2] Prepared ${
|
|
88
|
+
mayLog(`[GRIN2] Prepared ${lesions.length.toLocaleString()} lesions for analysis`);
|
|
89
89
|
const grin2AnalysisStart = Date.now();
|
|
90
90
|
const pyResult = await run_python("grin2PpWrapper.py", JSON.stringify(pyInput));
|
|
91
91
|
if (pyResult.stderr?.trim()) {
|
|
@@ -126,35 +126,15 @@ async function processSampleData(samples, ds, request) {
|
|
|
126
126
|
failedSamples: 0,
|
|
127
127
|
failedFiles: []
|
|
128
128
|
};
|
|
129
|
-
const dataTypeCounts = {
|
|
130
|
-
[dtsnvindel]: { samples: 0, entries: 0, processedLesions: 0 },
|
|
131
|
-
[dtcnv]: { samples: 0, entries: 0, processedLesions: 0 },
|
|
132
|
-
[dtfusionrna]: { samples: 0, entries: 0, processedLesions: 0 },
|
|
133
|
-
unknown: /* @__PURE__ */ new Map()
|
|
134
|
-
};
|
|
135
129
|
mayLog(`[GRIN2] Processing JSON files for ${samples.length.toLocaleString()} samples`);
|
|
136
130
|
for (const sample of samples) {
|
|
137
131
|
try {
|
|
138
132
|
const filepath = path.join(serverconfig.tpmasterdir, ds.queries.singleSampleMutation.folder, sample.name);
|
|
139
133
|
await file_is_readable(filepath);
|
|
140
134
|
const mlst = JSON.parse(await read_file(filepath));
|
|
141
|
-
const { sampleLesions
|
|
135
|
+
const { sampleLesions } = await processSampleMlst(sample.name, mlst, lesionId, request);
|
|
142
136
|
lesions.push(...sampleLesions);
|
|
143
137
|
lesionId += sampleLesions.length;
|
|
144
|
-
for (const [dt, counts] of Object.entries(sampleDataTypeCounts.known)) {
|
|
145
|
-
if (counts.entries > 0) {
|
|
146
|
-
dataTypeCounts[dt].samples++;
|
|
147
|
-
dataTypeCounts[dt].entries += counts.entries;
|
|
148
|
-
dataTypeCounts[dt].processedLesions += counts.processedLesions;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
for (const [dt, counts] of sampleDataTypeCounts.unknown) {
|
|
152
|
-
if (!dataTypeCounts.unknown.has(dt)) {
|
|
153
|
-
dataTypeCounts.unknown.set(dt, { samples: /* @__PURE__ */ new Set(), entries: 0 });
|
|
154
|
-
}
|
|
155
|
-
dataTypeCounts.unknown.get(dt).samples.add(sample.name);
|
|
156
|
-
dataTypeCounts.unknown.get(dt).entries += counts;
|
|
157
|
-
}
|
|
158
138
|
processingSummary.successfulSamples++;
|
|
159
139
|
} catch (error) {
|
|
160
140
|
processingSummary.failedSamples++;
|
|
@@ -164,176 +144,86 @@ async function processSampleData(samples, ds, request) {
|
|
|
164
144
|
);
|
|
165
145
|
}
|
|
166
146
|
}
|
|
167
|
-
mayLog(`[GRIN2] === DATA TYPE ANALYSIS SUMMARY ===`);
|
|
168
|
-
mayLog(
|
|
169
|
-
`[GRIN2] SNV/Indel (dt=${dtsnvindel}): ${dataTypeCounts[dtsnvindel].samples} samples, ${dataTypeCounts[dtsnvindel].entries} entries, ${dataTypeCounts[dtsnvindel].processedLesions} lesions`
|
|
170
|
-
);
|
|
171
|
-
mayLog(
|
|
172
|
-
`[GRIN2] CNV (dt=${dtcnv}): ${dataTypeCounts[dtcnv].samples} samples, ${dataTypeCounts[dtcnv].entries} entries, ${dataTypeCounts[dtcnv].processedLesions} lesions`
|
|
173
|
-
);
|
|
174
|
-
mayLog(
|
|
175
|
-
`[GRIN2] Fusion (dt=${dtfusionrna}): ${dataTypeCounts[dtfusionrna].samples} samples, ${dataTypeCounts[dtfusionrna].entries} entries, ${dataTypeCounts[dtfusionrna].processedLesions} lesions`
|
|
176
|
-
);
|
|
177
|
-
if (dataTypeCounts.unknown.size > 0) {
|
|
178
|
-
mayLog(`[GRIN2] Unknown data types:`);
|
|
179
|
-
for (const [dt, counts] of dataTypeCounts.unknown) {
|
|
180
|
-
mayLog(`[GRIN2] dt="${dt}": ${counts.samples.size} samples, ${counts.entries} entries`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
147
|
mayLog(`[GRIN2] Total lesions processed: ${lesions.length.toLocaleString()}`);
|
|
184
148
|
return {
|
|
185
|
-
|
|
149
|
+
lesions,
|
|
186
150
|
processingSummary
|
|
187
151
|
};
|
|
188
152
|
}
|
|
189
153
|
async function processSampleMlst(sampleName, mlst, startId, request) {
|
|
190
154
|
const lesions = [];
|
|
191
|
-
const sampleDataTypeCounts = {
|
|
192
|
-
known: {
|
|
193
|
-
[dtsnvindel]: { entries: 0, processedLesions: 0 },
|
|
194
|
-
[dtcnv]: { entries: 0, processedLesions: 0 },
|
|
195
|
-
[dtfusionrna]: { entries: 0, processedLesions: 0 }
|
|
196
|
-
},
|
|
197
|
-
unknown: /* @__PURE__ */ new Map()
|
|
198
|
-
};
|
|
199
155
|
for (const m of mlst) {
|
|
200
156
|
switch (m.dt) {
|
|
201
157
|
case dtsnvindel: {
|
|
202
|
-
sampleDataTypeCounts.known[dtsnvindel].entries++;
|
|
203
158
|
if (!request.snvindelOptions)
|
|
204
159
|
break;
|
|
205
160
|
const snvIndelLesion = filterAndConvertSnvIndel(sampleName, m, request.snvindelOptions);
|
|
206
161
|
if (snvIndelLesion) {
|
|
207
162
|
lesions.push(snvIndelLesion);
|
|
208
|
-
sampleDataTypeCounts.known[dtsnvindel].processedLesions++;
|
|
209
163
|
}
|
|
210
164
|
break;
|
|
211
165
|
}
|
|
212
166
|
case dtcnv: {
|
|
213
|
-
sampleDataTypeCounts.known[dtcnv].entries++;
|
|
214
167
|
if (!request.cnvOptions)
|
|
215
168
|
break;
|
|
216
169
|
const cnvLesion = filterAndConvertCnv(sampleName, m, request.cnvOptions);
|
|
217
170
|
if (cnvLesion) {
|
|
218
171
|
lesions.push(cnvLesion);
|
|
219
|
-
sampleDataTypeCounts.known[dtcnv].processedLesions++;
|
|
220
172
|
}
|
|
221
173
|
break;
|
|
222
174
|
}
|
|
223
175
|
case dtfusionrna: {
|
|
224
|
-
sampleDataTypeCounts.known[dtfusionrna].entries++;
|
|
225
176
|
if (!request.fusionOptions)
|
|
226
177
|
break;
|
|
227
178
|
const fusionLesion = filterAndConvertFusion(sampleName, m, request.fusionOptions);
|
|
228
179
|
if (fusionLesion) {
|
|
229
180
|
lesions.push(fusionLesion);
|
|
230
|
-
sampleDataTypeCounts.known[dtfusionrna].processedLesions++;
|
|
231
181
|
}
|
|
232
182
|
break;
|
|
233
183
|
}
|
|
234
184
|
default: {
|
|
235
|
-
const currentCount = sampleDataTypeCounts.unknown.get(m.dt) || 0;
|
|
236
|
-
sampleDataTypeCounts.unknown.set(m.dt, currentCount + 1);
|
|
237
185
|
break;
|
|
238
186
|
}
|
|
239
187
|
}
|
|
240
188
|
}
|
|
241
189
|
return {
|
|
242
|
-
sampleLesions: lesions
|
|
243
|
-
sampleDataTypeCounts
|
|
190
|
+
sampleLesions: lesions
|
|
244
191
|
};
|
|
245
192
|
}
|
|
246
193
|
function filterAndConvertSnvIndel(sampleName, entry, options) {
|
|
247
|
-
|
|
248
|
-
minTotalDepth: options?.minTotalDepth ?? 10,
|
|
249
|
-
minAltAlleleCount: options?.minAltAlleleCount ?? 2,
|
|
250
|
-
consequences: options?.consequences ?? [],
|
|
251
|
-
hyperMutator: options?.hyperMutator ?? 1e3
|
|
252
|
-
};
|
|
253
|
-
if (opts.consequences.length > 0 && entry.class && !opts.consequences.includes(entry.class)) {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
const chromosome = entry.chromosome || entry.chr;
|
|
257
|
-
const position = entry.start || entry.position || entry.pos;
|
|
258
|
-
if (!chromosome) {
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
if (position === void 0 || position === null || position === "undefined") {
|
|
194
|
+
if (!options?.consequences) {
|
|
262
195
|
return null;
|
|
263
196
|
}
|
|
264
|
-
|
|
265
|
-
if (isNaN(numPosition)) {
|
|
197
|
+
if (options.consequences.length > 0 && entry.class && !options.consequences.includes(entry.class)) {
|
|
266
198
|
return null;
|
|
267
199
|
}
|
|
268
|
-
|
|
269
|
-
const numEndPosition = parseInt(String(endPosition));
|
|
270
|
-
if (isNaN(numEndPosition)) {
|
|
200
|
+
if (!Number.isInteger(entry.pos)) {
|
|
271
201
|
return null;
|
|
272
202
|
}
|
|
273
|
-
return [sampleName,
|
|
203
|
+
return [sampleName, entry.chr, entry.pos, entry.pos, "mutation"];
|
|
274
204
|
}
|
|
275
205
|
function filterAndConvertCnv(sampleName, entry, options) {
|
|
276
|
-
|
|
277
|
-
lossThreshold: options?.lossThreshold ?? -0.4,
|
|
278
|
-
gainThreshold: options?.gainThreshold ?? 0.3,
|
|
279
|
-
maxSegLength: options?.maxSegLength ?? 0,
|
|
280
|
-
minSegLength: options?.minSegLength ?? 0,
|
|
281
|
-
hyperMutator: options?.hyperMutator ?? 500
|
|
282
|
-
};
|
|
283
|
-
const log2Ratio = entry.log2Ratio ?? entry.value ?? entry.ratio;
|
|
284
|
-
if (log2Ratio === void 0 || log2Ratio === null) {
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
const numLog2Ratio = parseFloat(String(log2Ratio));
|
|
288
|
-
if (isNaN(numLog2Ratio)) {
|
|
206
|
+
if (!options || options.gainThreshold === void 0 || options.lossThreshold === void 0 || options.maxSegLength === void 0) {
|
|
289
207
|
return null;
|
|
290
208
|
}
|
|
291
|
-
|
|
292
|
-
const isLoss = numLog2Ratio <= opts.lossThreshold;
|
|
293
|
-
if (!isGain && !isLoss) {
|
|
209
|
+
if (!Number.isInteger(entry.start)) {
|
|
294
210
|
return null;
|
|
295
211
|
}
|
|
296
|
-
|
|
297
|
-
const start = entry.start || entry.begin;
|
|
298
|
-
const end = entry.end || entry.stop;
|
|
299
|
-
if (!chromosome) {
|
|
212
|
+
if (!Number.isInteger(entry.stop)) {
|
|
300
213
|
return null;
|
|
301
214
|
}
|
|
302
|
-
if (
|
|
215
|
+
if (options.maxSegLength > 0 && entry.stop - entry.start > options.maxSegLength) {
|
|
303
216
|
return null;
|
|
304
217
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const numStart = parseInt(String(start));
|
|
309
|
-
const numEnd = parseInt(String(end));
|
|
310
|
-
if (isNaN(numStart) || isNaN(numEnd)) {
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
const lesionType = numLog2Ratio >= opts.gainThreshold ? "gain" : "loss";
|
|
314
|
-
return [sampleName, normalizeChromosome(chromosome), String(numStart), String(numEnd), lesionType];
|
|
315
|
-
}
|
|
316
|
-
function filterAndConvertFusion(sampleName, entry, options) {
|
|
317
|
-
const opts = {
|
|
318
|
-
fusionTypes: options?.fusionTypes ?? ["gene-gene", "gene-intergenic", "readthrough"],
|
|
319
|
-
minConfidence: options?.minConfidence ?? 0.7
|
|
320
|
-
};
|
|
321
|
-
if (entry.fusionType && !opts.fusionTypes.includes(entry.fusionType))
|
|
322
|
-
return null;
|
|
323
|
-
if (entry.confidence && entry.confidence < opts.minConfidence)
|
|
218
|
+
const isGain = entry.value >= options.gainThreshold;
|
|
219
|
+
const isLoss = entry.value <= options.lossThreshold;
|
|
220
|
+
if (!isGain && !isLoss)
|
|
324
221
|
return null;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
normalizeChromosome(entry.chromosome || entry.chr),
|
|
328
|
-
String(entry.start || entry.position),
|
|
329
|
-
String(entry.end || entry.position),
|
|
330
|
-
"fusion"
|
|
331
|
-
];
|
|
222
|
+
const lesionType = isGain ? "gain" : "loss";
|
|
223
|
+
return [sampleName, entry.chr, entry.start, entry.stop, lesionType];
|
|
332
224
|
}
|
|
333
|
-
function
|
|
334
|
-
|
|
335
|
-
return "chr?";
|
|
336
|
-
return chrom.startsWith("chr") ? chrom : `chr${chrom}`;
|
|
225
|
+
function filterAndConvertFusion(sampleName, entry, _options) {
|
|
226
|
+
return [sampleName, entry.chrA, entry.posA, entry.posB, "fusion"];
|
|
337
227
|
}
|
|
338
228
|
export {
|
|
339
229
|
api
|