@sjcrh/proteinpaint-server 2.147.2-0 → 2.148.1
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/dataset/termdb.test.js +0 -4
- package/package.json +4 -4
- package/routes/aiProjectSelectedWSImages.js +5 -1
- package/routes/aiProjectTrainModel.js +1 -0
- package/routes/grin2.js +101 -42
- package/routes/termdb.DE.js +13 -30
- package/routes/termdb.sampleScatter.js +436 -0
- package/routes/termdb.singlecellSamples.js +10 -20
- package/src/app.js +2833 -2747
package/dataset/termdb.test.js
CHANGED
|
@@ -39,8 +39,6 @@ function termdb_test_default() {
|
|
|
39
39
|
displaySampleIds: () => true,
|
|
40
40
|
// allow to display sample-level data
|
|
41
41
|
timeUnit: "years",
|
|
42
|
-
minTimeSinceDx: 5,
|
|
43
|
-
// enrollment in sjlife requires 5 years since cancer diagnosis
|
|
44
42
|
ageEndOffset: 274e-5,
|
|
45
43
|
// number of years to offset ending age of patients
|
|
46
44
|
// for cox outcome with timeScale='age'
|
|
@@ -108,7 +106,6 @@ function termdb_test_default() {
|
|
|
108
106
|
plotConfigByCohort: {
|
|
109
107
|
default: {
|
|
110
108
|
report: {
|
|
111
|
-
filterTWs: [{ id: "diaggrp" }],
|
|
112
109
|
sections: [
|
|
113
110
|
{
|
|
114
111
|
name: "Demographics",
|
|
@@ -332,7 +329,6 @@ function termdb_test_default() {
|
|
|
332
329
|
},
|
|
333
330
|
rnaseqGeneCount: {
|
|
334
331
|
storage_type: "HDF5",
|
|
335
|
-
newformat: true,
|
|
336
332
|
file: "files/hg38/TermdbTest/rnaseq/TermdbTest.geneCounts.new.h5"
|
|
337
333
|
},
|
|
338
334
|
singleCell: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.148.1",
|
|
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",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"combined:coverage": "coverageKey=test c8 --all --src=proteinpaint/server --experimental-monocart -r=v8 -r=html -r=json -r=markdown-summary -r=markdown-details -o=./.coverage tsx ./coverage.js & ",
|
|
24
24
|
"postcombined:coverage": "rm -rf ./cache",
|
|
25
25
|
"spec:coverage": "tsx test/relevant.js",
|
|
26
|
-
"getconf": "../build/getConfigProp.
|
|
26
|
+
"getconf": "../build/getConfigProp.cjs",
|
|
27
27
|
"doc": "../augen/build.sh routes shared/types/routes shared/checkers ../public/docs/server",
|
|
28
28
|
"mjs": "esbuild \"$DIR/*.ts\" --platform=node --outdir=\"$DIR\" --format=esm",
|
|
29
29
|
"cjs": "esbuild \"$DIR/*.ts\" --platform=node --outdir=\"$DIR\" --format=cjs",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"@sjcrh/augen": "2.143.0",
|
|
64
64
|
"@sjcrh/proteinpaint-python": "2.146.0",
|
|
65
65
|
"@sjcrh/proteinpaint-r": "2.146.4-0",
|
|
66
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
66
|
+
"@sjcrh/proteinpaint-rust": "2.148.1",
|
|
67
67
|
"@sjcrh/proteinpaint-shared": "2.147.1",
|
|
68
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
68
|
+
"@sjcrh/proteinpaint-types": "2.148.1",
|
|
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) {
|
package/routes/termdb.DE.js
CHANGED
|
@@ -204,12 +204,7 @@ async function run_DE(param, ds, term_results, term_results2) {
|
|
|
204
204
|
const sample_size_limit = 8;
|
|
205
205
|
if (group1names.length <= sample_size_limit && group2names.length <= sample_size_limit || param.method == "edgeR" || param.method == "limma") {
|
|
206
206
|
const time12 = (/* @__PURE__ */ new Date()).valueOf();
|
|
207
|
-
|
|
208
|
-
if (q.newformat) {
|
|
209
|
-
result2 = JSON.parse(await run_R("edge_newh5.R", JSON.stringify(expression_input)));
|
|
210
|
-
} else {
|
|
211
|
-
result2 = JSON.parse(await run_R("edge.R", JSON.stringify(expression_input)));
|
|
212
|
-
}
|
|
207
|
+
const result2 = JSON.parse(await run_R("edge_newh5.R", JSON.stringify(expression_input)));
|
|
213
208
|
mayLog("Time taken to run edgeR:", formatElapsedTime(Date.now() - time12));
|
|
214
209
|
param.method = "edgeR";
|
|
215
210
|
const ql_imagePath = path.join(serverconfig.cachedir, result2.edgeR_ql_image_name[0]);
|
|
@@ -259,30 +254,18 @@ async function validate_query_rnaseqGeneCount(ds) {
|
|
|
259
254
|
if (ds.queries.rnaseqGeneCount.storage_type == "text") {
|
|
260
255
|
samples = (await get_header_txt(q.file, null)).split(" ").slice(4);
|
|
261
256
|
} else if (ds.queries.rnaseqGeneCount.storage_type == "HDF5") {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
samples = vr.samples;
|
|
275
|
-
} else {
|
|
276
|
-
const get_samples_from_hdf5 = {
|
|
277
|
-
input_file: q.file,
|
|
278
|
-
data_type: "get_samples"
|
|
279
|
-
};
|
|
280
|
-
const time1 = (/* @__PURE__ */ new Date()).valueOf();
|
|
281
|
-
const result = await run_rust("DEanalysis", JSON.stringify(get_samples_from_hdf5));
|
|
282
|
-
const time2 = (/* @__PURE__ */ new Date()).valueOf();
|
|
283
|
-
mayLog("Time taken to query gene expression:", time2 - time1, "ms");
|
|
284
|
-
samples = result.split(",");
|
|
285
|
-
}
|
|
257
|
+
const get_samples_from_hdf5 = {
|
|
258
|
+
hdf5_file: q.file,
|
|
259
|
+
validate: true
|
|
260
|
+
};
|
|
261
|
+
const time1 = (/* @__PURE__ */ new Date()).valueOf();
|
|
262
|
+
const result = await run_rust("readH5", JSON.stringify(get_samples_from_hdf5));
|
|
263
|
+
const time2 = (/* @__PURE__ */ new Date()).valueOf();
|
|
264
|
+
mayLog("Time taken to query gene expression:", time2 - time1, "ms");
|
|
265
|
+
const vr = JSON.parse(result);
|
|
266
|
+
if (vr.status !== "success") throw vr.message;
|
|
267
|
+
if (!Array.isArray(vr.samples)) throw "HDF5 file has no samples, please check file.";
|
|
268
|
+
samples = vr.samples;
|
|
286
269
|
} else throw "unknown storage type:" + ds.queries.rnaseqGeneCount.storage_type;
|
|
287
270
|
q.allSampleSet = new Set(samples);
|
|
288
271
|
const unknownSamples = [];
|