@sjcrh/proteinpaint-server 2.184.0 → 2.185.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/dataset/protected.test.js +5 -0
- package/dataset/termdb.test.js +1 -1
- package/package.json +5 -5
- package/routes/brainImagingSamples.js +15 -4
- package/routes/genesetEnrichment.js +101 -42
- package/routes/profile.radar2.js +112 -0
- package/routes/profile.radarFacility2.js +148 -0
- package/routes/saveWSIAnnotation.js +21 -0
- package/routes/termdb.DE.js +31 -238
- package/routes/termdb.chat3.js +191 -0
- package/routes/termdb.cluster.js +44 -9
- package/routes/termdb.config.js +5 -3
- package/routes/termdb.diffMeth.js +4 -2
- package/routes/termdb.proteome.js +28 -20
- package/routes/termdb.singlecellDEgenes.js +2 -1
- package/routes/termdb.singlecellSamples.js +36 -5
- package/src/app.js +3517 -2542
- package/src/serverconfig.js +16 -1
|
@@ -51,9 +51,10 @@ function init({ genomes }) {
|
|
|
51
51
|
__abortSignal: q.__abortSignal
|
|
52
52
|
});
|
|
53
53
|
const controlSampleIds = cohortData.controlSampleIds || /* @__PURE__ */ new Set();
|
|
54
|
+
const prior = assay.cohorts[cohortName].prior;
|
|
54
55
|
for (const entry of cohortData.allEntries || []) {
|
|
55
56
|
const s2v = entry.s2v;
|
|
56
|
-
const stats = getCohortStats(s2v, controlSampleIds);
|
|
57
|
+
const stats = getCohortStats(s2v, controlSampleIds, prior);
|
|
57
58
|
delete entry.s2v;
|
|
58
59
|
entry.foldChange = stats.foldChange;
|
|
59
60
|
entry.pValue = stats.pValue;
|
|
@@ -71,7 +72,7 @@ function init({ genomes }) {
|
|
|
71
72
|
}
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
|
-
function getCohortStats(allS2v, controlSampleIds) {
|
|
75
|
+
function getCohortStats(allS2v, controlSampleIds, prior) {
|
|
75
76
|
if (!allS2v || typeof allS2v != "object") return { foldChange: null, pValue: null, testedN: 0, controlN: 0 };
|
|
76
77
|
const controlValues = [];
|
|
77
78
|
const testedValues = [];
|
|
@@ -84,7 +85,10 @@ function getCohortStats(allS2v, controlSampleIds) {
|
|
|
84
85
|
const controlMean = controlValues?.length ? controlValues.reduce((sum, v) => sum + v, 0) / controlValues.length : null;
|
|
85
86
|
const testedMean = testedValues?.length ? testedValues.reduce((sum, v) => sum + v, 0) / testedValues.length : null;
|
|
86
87
|
const foldChange = testedMean != null && controlMean != null && Number.isFinite(testedMean) && Number.isFinite(controlMean) && controlMean !== 0 ? testedMean / controlMean : null;
|
|
87
|
-
|
|
88
|
+
if (!Number.isFinite(prior?.d0) || prior.d0 <= 0 || !Number.isFinite(prior?.s0sq) || prior.s0sq <= 0) {
|
|
89
|
+
throw "prior with finite positive d0 and s0sq is required for moderated t-test";
|
|
90
|
+
}
|
|
91
|
+
const pValue = getModeratedPValue(testedValues, controlValues, prior);
|
|
88
92
|
return {
|
|
89
93
|
foldChange,
|
|
90
94
|
pValue,
|
|
@@ -92,36 +96,38 @@ function getCohortStats(allS2v, controlSampleIds) {
|
|
|
92
96
|
controlN: controlValues.length
|
|
93
97
|
};
|
|
94
98
|
}
|
|
95
|
-
function
|
|
99
|
+
function getModeratedPValue(a, b, prior) {
|
|
96
100
|
const n1 = a.length;
|
|
97
101
|
const n2 = b.length;
|
|
98
102
|
if (n1 < 2 || n2 < 2) return null;
|
|
99
103
|
const mean1 = a.reduce((s, v) => s + v, 0) / n1;
|
|
100
104
|
const mean2 = b.reduce((s, v) => s + v, 0) / n2;
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
let ss1 = 0;
|
|
106
|
+
for (const v of a) {
|
|
107
|
+
const d = v - mean1;
|
|
108
|
+
ss1 += d * d;
|
|
109
|
+
}
|
|
110
|
+
let ss2 = 0;
|
|
111
|
+
for (const v of b) {
|
|
112
|
+
const d = v - mean2;
|
|
113
|
+
ss2 += d * d;
|
|
114
|
+
}
|
|
115
|
+
const dfResidual = n1 + n2 - 2;
|
|
116
|
+
const pooledVar = (ss1 + ss2) / dfResidual;
|
|
117
|
+
const { d0, s0sq } = prior;
|
|
118
|
+
const sTildeSq = (d0 * s0sq + dfResidual * pooledVar) / (d0 + dfResidual);
|
|
119
|
+
const se = Math.sqrt(sTildeSq * (1 / n1 + 1 / n2));
|
|
120
|
+
if (!(se > 0)) {
|
|
106
121
|
if (mean1 === mean2) return 1;
|
|
107
122
|
return 1e-300;
|
|
108
123
|
}
|
|
109
|
-
const t = (mean1 - mean2) /
|
|
110
|
-
const df =
|
|
124
|
+
const t = (mean1 - mean2) / se;
|
|
125
|
+
const df = d0 + dfResidual;
|
|
111
126
|
if (!Number.isFinite(df) || df < 0.1) return null;
|
|
112
127
|
const p = 2 * tCdfTail(Math.abs(t), df);
|
|
113
128
|
if (!Number.isFinite(p)) return null;
|
|
114
129
|
return Math.max(1e-300, Math.min(1, p));
|
|
115
130
|
}
|
|
116
|
-
function sampleVariance(lst, mean) {
|
|
117
|
-
if (lst.length < 2) return NaN;
|
|
118
|
-
let sumsq = 0;
|
|
119
|
-
for (const v of lst) {
|
|
120
|
-
const d = v - mean;
|
|
121
|
-
sumsq += d * d;
|
|
122
|
-
}
|
|
123
|
-
return sumsq / (lst.length - 1);
|
|
124
|
-
}
|
|
125
131
|
function tCdfTail(t, df) {
|
|
126
132
|
const x = df / (df + t * t);
|
|
127
133
|
return 0.5 * regularizedBetaIncomplete(df / 2, 0.5, x);
|
|
@@ -217,6 +223,8 @@ async function validate_query_proteome(ds) {
|
|
|
217
223
|
if (!cohort.controlFilter)
|
|
218
224
|
throw `Missing controlFilter in queries.proteome.assays.${assayName}.cohorts.${cohortName}`;
|
|
219
225
|
if (!cohort.caseFilter) throw `Missing caseFilter in queries.proteome.assays.${assayName}.cohorts.${cohortName}`;
|
|
226
|
+
if (!cohort.prior?.d0 || !cohort.prior?.s0sq)
|
|
227
|
+
throw `Missing prior.d0 and prior.s0sq in queries.proteome.assays.${assayName}.cohorts.${cohortName}`;
|
|
220
228
|
}
|
|
221
229
|
} else {
|
|
222
230
|
throw `Invalid assay structure for "${assayName}". Must have .cohorts`;
|
|
@@ -25,7 +25,8 @@ function init({ genomes }) {
|
|
|
25
25
|
if (!ds.queries?.singleCell?.DEgenes || !ds.queries.singleCell.DEgenes.get)
|
|
26
26
|
throw new Error("DE genes not supported on this dataset.");
|
|
27
27
|
result = await ds.queries.singleCell.DEgenes.get(q);
|
|
28
|
-
|
|
28
|
+
const isEmpty = !result || !result.data || (Array.isArray(result.data) ? result.data.length === 0 : !result.data.totalRows);
|
|
29
|
+
if (isEmpty) {
|
|
29
30
|
result = {
|
|
30
31
|
status: 404,
|
|
31
32
|
error: !result ? "No data found." : "No differentially expressed genes found."
|
|
@@ -48,11 +48,11 @@ async function validate_query_singleCell(ds, genome) {
|
|
|
48
48
|
const q = ds.queries.singleCell;
|
|
49
49
|
if (!q) return;
|
|
50
50
|
if (typeof q.samples != "object") throw new Error("singleCell.samples{} not object");
|
|
51
|
+
if (typeof q.data != "object") throw new Error("singleCell.data{} not object");
|
|
51
52
|
if (typeof q.samples.get == "function") {
|
|
52
53
|
} else {
|
|
53
|
-
await
|
|
54
|
+
await validateSamples(q, ds);
|
|
54
55
|
}
|
|
55
|
-
if (typeof q.data != "object") throw new Error("singleCell.data{} not object");
|
|
56
56
|
if (q.data.src == "gdcapi") {
|
|
57
57
|
gdc_validate_query_singleCell_data(ds, genome);
|
|
58
58
|
} else if (q.data.src == "native") {
|
|
@@ -85,7 +85,8 @@ function validateImages(images) {
|
|
|
85
85
|
if (!images.label) images.label = "Images";
|
|
86
86
|
if (!images.fileName) throw new Error("images.fileName missing");
|
|
87
87
|
}
|
|
88
|
-
async function
|
|
88
|
+
async function validateSamples(q, ds) {
|
|
89
|
+
const S = q.samples, D = q.data;
|
|
89
90
|
const samples = /* @__PURE__ */ new Map();
|
|
90
91
|
for (const plot of D.plots) {
|
|
91
92
|
for (const fn of await fs.promises.readdir(path.join(serverconfig.tpmasterdir, plot.folder))) {
|
|
@@ -102,6 +103,8 @@ async function validateSamplesNative(S, D, ds) {
|
|
|
102
103
|
}
|
|
103
104
|
if (!plot.colorColumns || plot.colorColumns.length == 0) continue;
|
|
104
105
|
}
|
|
106
|
+
if (samples.size == 0) throw new Error("no scrna samples found");
|
|
107
|
+
console.log(samples.size, "singleCell samples loaded from " + ds.label);
|
|
105
108
|
if (S.sampleColumns) {
|
|
106
109
|
for (const { termid } of S.sampleColumns) {
|
|
107
110
|
const term = ds.cohort.termdb.q.termjsonByOneid(termid);
|
|
@@ -114,7 +117,13 @@ async function validateSamplesNative(S, D, ds) {
|
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
S.get = () => {
|
|
117
|
-
|
|
120
|
+
const re = { samples: [...samples.values()] };
|
|
121
|
+
if (q.metaResults) {
|
|
122
|
+
re.metaResults = q.metaResults.map((i) => {
|
|
123
|
+
return { name: i.name };
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return re;
|
|
118
127
|
};
|
|
119
128
|
}
|
|
120
129
|
function validateDataNative(D, ds) {
|
|
@@ -126,6 +135,28 @@ function validateDataNative(D, ds) {
|
|
|
126
135
|
}
|
|
127
136
|
const file2Lines = {};
|
|
128
137
|
D.get = async (q) => {
|
|
138
|
+
if (q.checkPlotAvailability) {
|
|
139
|
+
const plots2 = [];
|
|
140
|
+
for (const plot of D.plots) {
|
|
141
|
+
if (!q.plots.includes(plot.name)) continue;
|
|
142
|
+
const tsvfile = path.join(
|
|
143
|
+
serverconfig.tpmasterdir,
|
|
144
|
+
plot.folder,
|
|
145
|
+
(q.sample?.eID || q.sample?.sID) + (plot.fileSuffix || "")
|
|
146
|
+
);
|
|
147
|
+
try {
|
|
148
|
+
await file_is_readable(tsvfile);
|
|
149
|
+
plots2.push({
|
|
150
|
+
name: plot.name,
|
|
151
|
+
expCells: [],
|
|
152
|
+
// FIXME avoid breaking client but shouldn't be needed
|
|
153
|
+
noExpCells: []
|
|
154
|
+
});
|
|
155
|
+
} catch (_) {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { plots: plots2 };
|
|
159
|
+
}
|
|
129
160
|
const plots = [];
|
|
130
161
|
let geneExpMap;
|
|
131
162
|
if (ds.queries.singleCell.geneExpression && q.gene) {
|
|
@@ -138,7 +169,7 @@ function validateDataNative(D, ds) {
|
|
|
138
169
|
const tsvfile = path.join(
|
|
139
170
|
serverconfig.tpmasterdir,
|
|
140
171
|
plot.folder,
|
|
141
|
-
(q.sample?.eID || q.sample?.sID) + plot.fileSuffix
|
|
172
|
+
(q.sample?.eID || q.sample?.sID) + (plot.fileSuffix || "")
|
|
142
173
|
);
|
|
143
174
|
if (!file2Lines[tsvfile]) {
|
|
144
175
|
await file_is_readable(tsvfile);
|