@sjcrh/proteinpaint-server 2.204.0 → 2.205.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 -8
- package/routes/termdb.diffMeth.js +3 -3
- package/routes/termdb.proteome.js +132 -253
- package/src/app.js +1392 -3974
package/dataset/termdb.test.js
CHANGED
|
@@ -437,10 +437,6 @@ function termdb_test_default() {
|
|
|
437
437
|
folder: "files/hg38/TermdbTest/scrna/geneExpHdf5"
|
|
438
438
|
}
|
|
439
439
|
},
|
|
440
|
-
WSImages: {
|
|
441
|
-
type: "H&E",
|
|
442
|
-
imageBySampleFolder: "files/hg38/TermdbTest/wsimages"
|
|
443
|
-
},
|
|
444
440
|
// w2 plot. folder: spatial images, one per subfolder of the sample's
|
|
445
441
|
// directory (folder/<sample>/<imageName>/), files inside found by the
|
|
446
442
|
// *FileSuffix fields. wsiFolder: plain whole-slide images, laid out as
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.205.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",
|
|
@@ -59,30 +59,26 @@
|
|
|
59
59
|
"@sjcrh/augen": "2.204.0",
|
|
60
60
|
"@sjcrh/proteinpaint-python": "2.204.0",
|
|
61
61
|
"@sjcrh/proteinpaint-r": "2.204.0",
|
|
62
|
-
"@sjcrh/proteinpaint-rust": "2.
|
|
63
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
64
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
62
|
+
"@sjcrh/proteinpaint-rust": "2.205.1",
|
|
63
|
+
"@sjcrh/proteinpaint-shared": "2.205.0",
|
|
64
|
+
"@sjcrh/proteinpaint-types": "2.205.0",
|
|
65
65
|
"@types/express": "^5.0.0",
|
|
66
66
|
"@types/express-session": "^1.18.1",
|
|
67
67
|
"better-sqlite3": "^12.4.1",
|
|
68
68
|
"body-parser": "^1.15.2",
|
|
69
69
|
"canvas": "~3.2.0",
|
|
70
70
|
"compression": "^1.6.2",
|
|
71
|
-
"connect-redis": "^6.1.3",
|
|
72
71
|
"cookie-parser": "^1.4.5",
|
|
73
72
|
"d3": "^7.6.1",
|
|
74
73
|
"deep-object-diff": "^1.1.0",
|
|
75
74
|
"express": "^4.17.1",
|
|
76
75
|
"express-basic-auth": "^1.1.5",
|
|
77
76
|
"express-session": "^1.18.1",
|
|
78
|
-
"got": "^14.2.0",
|
|
79
77
|
"jsonwebtoken": "^9.0.3",
|
|
80
78
|
"ky": "^1.2.1",
|
|
81
79
|
"micromatch": "^4.0.5",
|
|
82
80
|
"minimatch": "^10.0.1",
|
|
83
|
-
"node-fetch": "^2.6.1",
|
|
84
81
|
"partjson": "^0.58.2",
|
|
85
|
-
"redis": "^4.7.0",
|
|
86
82
|
"skia-canvas": "~3.0.8",
|
|
87
83
|
"tiny-async-pool": "^1.2.0",
|
|
88
84
|
"tough-cookie": "^4.1.4"
|
|
@@ -20,9 +20,9 @@ function init({ genomes }) {
|
|
|
20
20
|
res.send({
|
|
21
21
|
data: {
|
|
22
22
|
[group1Name]: groups.group1names.length,
|
|
23
|
-
[group2Name]: groups.group2names.length
|
|
24
|
-
|
|
25
|
-
}
|
|
23
|
+
[group2Name]: groups.group2names.length
|
|
24
|
+
},
|
|
25
|
+
...groups.alerts.length ? { alert: groups.alerts.join(" | ") } : {}
|
|
26
26
|
});
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
import path from "path";
|
|
1
2
|
import { get_ds_tdb } from "#src/termdb.js";
|
|
2
3
|
import * as utils from "#src/utils.js";
|
|
3
4
|
import { mayLimitSamples } from "#src/mds3.filter.js";
|
|
5
|
+
import serverconfig from "#src/serverconfig.js";
|
|
6
|
+
import { readGeneRows } from "../src/routes/termdb.bubbleHeatmap.ts";
|
|
4
7
|
function baseUniProtAcc(acc) {
|
|
5
8
|
if (!acc) return "";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
if (acc.includes("|")) {
|
|
10
|
+
const parts2 = acc.split("|");
|
|
11
|
+
const id = parts2.length >= 2 ? parts2[1] : acc;
|
|
12
|
+
const dash = id.indexOf("-");
|
|
13
|
+
return dash > 0 ? id.slice(0, dash) : id;
|
|
14
|
+
}
|
|
15
|
+
const parts = acc.split(".");
|
|
16
|
+
if (parts.length >= 3 && (parts[0] === "sp" || parts[0] === "tr")) return parts[1];
|
|
17
|
+
return acc;
|
|
10
18
|
}
|
|
19
|
+
const missingDapWarned = /* @__PURE__ */ new Set();
|
|
11
20
|
function init({ genomes }) {
|
|
12
21
|
return async (req, res) => {
|
|
13
22
|
const q = req.query;
|
|
@@ -15,63 +24,99 @@ function init({ genomes }) {
|
|
|
15
24
|
const genome = genomes[q.genome];
|
|
16
25
|
if (!genome) throw "invalid genome";
|
|
17
26
|
const [ds] = get_ds_tdb(genome, q);
|
|
18
|
-
if (!ds.queries?.proteome?.
|
|
27
|
+
if (!ds.queries?.proteome?.organisms) throw "queries.proteome not configured";
|
|
19
28
|
const term = q.term?.term || q.term;
|
|
20
29
|
if (!term?.name) throw "term.name missing";
|
|
21
30
|
const cohorts = [];
|
|
22
31
|
const brConfig = ds.queries.proteome.brainRegions;
|
|
23
32
|
const regionRemap = brConfig?.regionValueRemap || {};
|
|
24
33
|
const sampleRegions = {};
|
|
34
|
+
const identifierAnno = /* @__PURE__ */ new Map();
|
|
25
35
|
for (const organismName in ds.queries.proteome.organisms) {
|
|
26
36
|
const organism = ds.queries.proteome.organisms[organismName];
|
|
27
37
|
for (const assayName in organism.assays) {
|
|
28
38
|
const assay = organism.assays[assayName];
|
|
29
39
|
for (const cohortName in assay.cohorts || {}) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const cohortCfg = assay.cohorts[cohortName];
|
|
41
|
+
const dapPath = path.join(serverconfig.tpmasterdir, cohortCfg.DAPfile);
|
|
42
|
+
const rows = await readGeneRows(dapPath, String(term.name).toLowerCase());
|
|
43
|
+
if (!rows) {
|
|
44
|
+
if (!missingDapWarned.has(dapPath)) {
|
|
45
|
+
missingDapWarned.add(dapPath);
|
|
46
|
+
console.warn(
|
|
47
|
+
`proteome: DAPfile missing or unreadable for ${organismName}/${assayName}/${cohortName}: ${dapPath}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!rows.length) continue;
|
|
53
|
+
const organismFilter = [{ columnIdx: organism.columnIdx, columnValue: organism.columnValue }];
|
|
54
|
+
const assayFilter = [{ columnIdx: assay.columnIdx, columnValue: assay.columnValue }];
|
|
55
|
+
let caseSamples = [];
|
|
56
|
+
let controlSamples = [];
|
|
57
|
+
try {
|
|
58
|
+
caseSamples = listCohortSamples(ds.queries.proteome.db, [
|
|
59
|
+
...organismFilter,
|
|
60
|
+
...assayFilter,
|
|
61
|
+
...cohortCfg.caseFilter
|
|
62
|
+
]);
|
|
63
|
+
controlSamples = listCohortSamples(ds.queries.proteome.db, [
|
|
64
|
+
...organismFilter,
|
|
65
|
+
...assayFilter,
|
|
66
|
+
...cohortCfg.controlFilter
|
|
67
|
+
]);
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
const annoKey = `${organismName}|${assayName}`;
|
|
71
|
+
if (!identifierAnno.has(annoKey)) {
|
|
72
|
+
let m = /* @__PURE__ */ new Map();
|
|
73
|
+
try {
|
|
74
|
+
m = listIdentifierAnnotations(ds.queries.proteome.db, term.name, [...organismFilter, ...assayFilter]);
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
identifierAnno.set(annoKey, m);
|
|
78
|
+
}
|
|
79
|
+
const anno = identifierAnno.get(annoKey);
|
|
80
|
+
const sampleIds = brConfig ? [...caseSamples, ...controlSamples] : [];
|
|
81
|
+
if (brConfig) {
|
|
82
|
+
const regionOf = (filters) => {
|
|
83
|
+
const f = filters.find((f2) => f2.columnIdx === brConfig.regionColumnIdx);
|
|
84
|
+
if (!f) return void 0;
|
|
85
|
+
const code = regionRemap[String(f.columnValue)] ?? String(f.columnValue);
|
|
86
|
+
return brConfig.regions[code] !== void 0 ? code : void 0;
|
|
87
|
+
};
|
|
88
|
+
const caseRegion = regionOf(cohortCfg.caseFilter);
|
|
89
|
+
const controlRegion = regionOf(cohortCfg.controlFilter);
|
|
90
|
+
if (caseRegion) for (const sid of caseSamples) sampleRegions[sid] = caseRegion;
|
|
91
|
+
if (controlRegion) for (const sid of controlSamples) sampleRegions[sid] = controlRegion;
|
|
92
|
+
}
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
const entry = {
|
|
95
|
+
organism: organismName,
|
|
96
|
+
assayName,
|
|
97
|
+
cohortName,
|
|
98
|
+
uniqueIdentifier: row.identifier,
|
|
99
|
+
proteinAccession: row.acc,
|
|
100
|
+
geneName: term.name,
|
|
101
|
+
// client computes log2(foldChange); DAP stores log2FC directly
|
|
102
|
+
foldChange: Math.pow(2, row.fc),
|
|
103
|
+
// significance is the DAP file's FDR (BH-adjusted p), consistent with
|
|
104
|
+
// the other DAP-driven tools. pValue is a deprecated alias of fdr kept
|
|
105
|
+
// for existing clients; new code should read fdr.
|
|
106
|
+
fdr: row.fdr,
|
|
107
|
+
pValue: row.fdr,
|
|
108
|
+
testedN: caseSamples.length,
|
|
109
|
+
controlN: controlSamples.length
|
|
110
|
+
};
|
|
111
|
+
const a = anno.get(row.identifier);
|
|
112
|
+
if (a?.isoform) entry.isoform = a.isoform;
|
|
113
|
+
if (assay.PTMType) {
|
|
114
|
+
entry.PTMType = assay.PTMType;
|
|
115
|
+
if (a?.modsite) entry.modSites = a.modsite;
|
|
42
116
|
}
|
|
43
|
-
};
|
|
44
|
-
const cohortData = await ds.queries.proteome.get({
|
|
45
|
-
terms: [tw],
|
|
46
|
-
dataTypeDetails: details,
|
|
47
|
-
filter: q.filter,
|
|
48
|
-
filter0: q.filter0,
|
|
49
|
-
for: "proteinView",
|
|
50
|
-
__abortSignal: q.__abortSignal
|
|
51
|
-
});
|
|
52
|
-
const controlSampleIds = cohortData.controlSampleIds || /* @__PURE__ */ new Set();
|
|
53
|
-
const prior = assay.cohorts[cohortName].prior;
|
|
54
|
-
for (const entry of cohortData.allEntries || []) {
|
|
55
|
-
const s2v = entry.s2v;
|
|
56
|
-
const sampleRegionsRaw = entry.sampleRegionsRaw || {};
|
|
57
|
-
const stats = getCohortStats(s2v, controlSampleIds, prior);
|
|
58
|
-
delete entry.s2v;
|
|
59
|
-
delete entry.sampleRegionsRaw;
|
|
60
|
-
entry.foldChange = stats.foldChange;
|
|
61
|
-
entry.pValue = stats.pValue;
|
|
62
|
-
entry.testedN = stats.testedN;
|
|
63
|
-
entry.controlN = stats.controlN;
|
|
64
117
|
if (assay.mclassOverride) entry.mclassOverride = assay.mclassOverride;
|
|
65
118
|
if (organism.genomeName) entry.genomeName = organism.genomeName;
|
|
66
|
-
if (brConfig)
|
|
67
|
-
entry.sampleIds = Object.keys(s2v);
|
|
68
|
-
for (const sid in sampleRegionsRaw) {
|
|
69
|
-
const raw = sampleRegionsRaw[sid];
|
|
70
|
-
if (raw == null) continue;
|
|
71
|
-
const code = regionRemap[String(raw)] ?? String(raw);
|
|
72
|
-
if (brConfig.regions[code] !== void 0) sampleRegions[sid] = code;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
119
|
+
if (brConfig) entry.sampleIds = sampleIds.slice();
|
|
75
120
|
cohorts.push(entry);
|
|
76
121
|
}
|
|
77
122
|
}
|
|
@@ -104,133 +149,6 @@ function init({ genomes }) {
|
|
|
104
149
|
}
|
|
105
150
|
};
|
|
106
151
|
}
|
|
107
|
-
function getCohortStats(allS2v, controlSampleIds, prior) {
|
|
108
|
-
if (!allS2v || typeof allS2v != "object") return { foldChange: null, pValue: null, testedN: 0, controlN: 0 };
|
|
109
|
-
const controlValues = [];
|
|
110
|
-
const testedValues = [];
|
|
111
|
-
for (const sampleId in allS2v) {
|
|
112
|
-
const v = Number(allS2v[sampleId]);
|
|
113
|
-
if (!Number.isFinite(v)) continue;
|
|
114
|
-
if (controlSampleIds.has(String(sampleId))) controlValues.push(v);
|
|
115
|
-
else testedValues.push(v);
|
|
116
|
-
}
|
|
117
|
-
const controlMean = controlValues?.length ? controlValues.reduce((sum, v) => sum + v, 0) / controlValues.length : null;
|
|
118
|
-
const testedMean = testedValues?.length ? testedValues.reduce((sum, v) => sum + v, 0) / testedValues.length : null;
|
|
119
|
-
const foldChange = testedMean != null && controlMean != null && Number.isFinite(testedMean) && Number.isFinite(controlMean) && controlMean !== 0 ? testedMean / controlMean : null;
|
|
120
|
-
if (!Number.isFinite(prior?.d0) || prior.d0 <= 0 || !Number.isFinite(prior?.s0sq) || prior.s0sq <= 0) {
|
|
121
|
-
throw "prior with finite positive d0 and s0sq is required for moderated t-test";
|
|
122
|
-
}
|
|
123
|
-
const pValue = getModeratedPValue(testedValues, controlValues, prior);
|
|
124
|
-
return {
|
|
125
|
-
foldChange,
|
|
126
|
-
pValue,
|
|
127
|
-
testedN: testedValues.length,
|
|
128
|
-
controlN: controlValues.length
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
function getModeratedPValue(a, b, prior) {
|
|
132
|
-
const n1 = a.length;
|
|
133
|
-
const n2 = b.length;
|
|
134
|
-
if (n1 < 2 || n2 < 2) return null;
|
|
135
|
-
const mean1 = a.reduce((s, v) => s + v, 0) / n1;
|
|
136
|
-
const mean2 = b.reduce((s, v) => s + v, 0) / n2;
|
|
137
|
-
let ss1 = 0;
|
|
138
|
-
for (const v of a) {
|
|
139
|
-
const d = v - mean1;
|
|
140
|
-
ss1 += d * d;
|
|
141
|
-
}
|
|
142
|
-
let ss2 = 0;
|
|
143
|
-
for (const v of b) {
|
|
144
|
-
const d = v - mean2;
|
|
145
|
-
ss2 += d * d;
|
|
146
|
-
}
|
|
147
|
-
const dfResidual = n1 + n2 - 2;
|
|
148
|
-
const pooledVar = (ss1 + ss2) / dfResidual;
|
|
149
|
-
const { d0, s0sq } = prior;
|
|
150
|
-
const sTildeSq = (d0 * s0sq + dfResidual * pooledVar) / (d0 + dfResidual);
|
|
151
|
-
const se = Math.sqrt(sTildeSq * (1 / n1 + 1 / n2));
|
|
152
|
-
if (!(se > 0)) {
|
|
153
|
-
if (mean1 === mean2) return 1;
|
|
154
|
-
return 1e-300;
|
|
155
|
-
}
|
|
156
|
-
const t = (mean1 - mean2) / se;
|
|
157
|
-
const df = d0 + dfResidual;
|
|
158
|
-
if (!Number.isFinite(df) || df < 0.1) return null;
|
|
159
|
-
const p = 2 * tCdfTail(Math.abs(t), df);
|
|
160
|
-
if (!Number.isFinite(p)) return null;
|
|
161
|
-
return Math.max(1e-300, Math.min(1, p));
|
|
162
|
-
}
|
|
163
|
-
function tCdfTail(t, df) {
|
|
164
|
-
const x = df / (df + t * t);
|
|
165
|
-
return 0.5 * regularizedBetaIncomplete(df / 2, 0.5, x);
|
|
166
|
-
}
|
|
167
|
-
function regularizedBetaIncomplete(a, b, x) {
|
|
168
|
-
if (x <= 0) return 0;
|
|
169
|
-
if (x >= 1) return 1;
|
|
170
|
-
if (x > (a + 1) / (a + b + 2)) {
|
|
171
|
-
return 1 - regularizedBetaIncomplete(b, a, 1 - x);
|
|
172
|
-
}
|
|
173
|
-
const lnPrefactor = lnBetaPrefactor(a, b, x);
|
|
174
|
-
const maxIter = 200;
|
|
175
|
-
const eps = 1e-14;
|
|
176
|
-
let f = 1e-30;
|
|
177
|
-
let C = 1e-30;
|
|
178
|
-
let D = 0;
|
|
179
|
-
for (let m = 0; m <= maxIter; m++) {
|
|
180
|
-
let numerator;
|
|
181
|
-
if (m === 0) {
|
|
182
|
-
numerator = 1;
|
|
183
|
-
} else {
|
|
184
|
-
const k = m;
|
|
185
|
-
if (k % 2 === 1) {
|
|
186
|
-
const i = (k - 1) / 2;
|
|
187
|
-
numerator = -(a + i) * (a + b + i) * x / ((a + 2 * i) * (a + 2 * i + 1));
|
|
188
|
-
} else {
|
|
189
|
-
const i = k / 2;
|
|
190
|
-
numerator = i * (b - i) * x / ((a + 2 * i - 1) * (a + 2 * i));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
D = 1 + numerator * D;
|
|
194
|
-
if (Math.abs(D) < 1e-30) D = 1e-30;
|
|
195
|
-
D = 1 / D;
|
|
196
|
-
C = 1 + numerator / C;
|
|
197
|
-
if (Math.abs(C) < 1e-30) C = 1e-30;
|
|
198
|
-
const delta = C * D;
|
|
199
|
-
f *= delta;
|
|
200
|
-
if (m > 0 && Math.abs(delta - 1) < eps) break;
|
|
201
|
-
}
|
|
202
|
-
return Math.exp(lnPrefactor) * f / a;
|
|
203
|
-
}
|
|
204
|
-
function lnBetaPrefactor(a, b, x) {
|
|
205
|
-
return a * Math.log(x) + b * Math.log(1 - x) - lnBeta(a, b);
|
|
206
|
-
}
|
|
207
|
-
function lnBeta(a, b) {
|
|
208
|
-
return lnGamma(a) + lnGamma(b) - lnGamma(a + b);
|
|
209
|
-
}
|
|
210
|
-
function lnGamma(z) {
|
|
211
|
-
if (z < 0.5) {
|
|
212
|
-
return Math.log(Math.PI / Math.sin(Math.PI * z)) - lnGamma(1 - z);
|
|
213
|
-
}
|
|
214
|
-
z -= 1;
|
|
215
|
-
const g = 7;
|
|
216
|
-
const coef = [
|
|
217
|
-
0.9999999999998099,
|
|
218
|
-
676.5203681218851,
|
|
219
|
-
-1259.1392167224028,
|
|
220
|
-
771.3234287776531,
|
|
221
|
-
-176.6150291621406,
|
|
222
|
-
12.507343278686905,
|
|
223
|
-
-0.13857109526572012,
|
|
224
|
-
9984369578019572e-21,
|
|
225
|
-
15056327351493116e-23
|
|
226
|
-
];
|
|
227
|
-
let x = coef[0];
|
|
228
|
-
for (let i = 1; i < coef.length; i++) {
|
|
229
|
-
x += coef[i] / (z + i);
|
|
230
|
-
}
|
|
231
|
-
const t = z + g + 0.5;
|
|
232
|
-
return 0.5 * Math.log(2 * Math.PI) + (z + 0.5) * Math.log(t) - t + Math.log(x);
|
|
233
|
-
}
|
|
234
152
|
async function validate_query_proteome(ds) {
|
|
235
153
|
const q = ds.queries.proteome;
|
|
236
154
|
if (!q) return;
|
|
@@ -264,14 +182,15 @@ async function validate_query_proteome(ds) {
|
|
|
264
182
|
throw `Missing controlFilter in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
|
|
265
183
|
if (!cohort.caseFilter)
|
|
266
184
|
throw `Missing caseFilter in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
|
|
267
|
-
if (!cohort.DAPfile
|
|
268
|
-
throw `Missing
|
|
185
|
+
if (!cohort.DAPfile)
|
|
186
|
+
throw `Missing DAPfile in queries.proteome.organisms.${organismName}.assays.${assayName}.cohorts.${cohortName}`;
|
|
269
187
|
}
|
|
270
188
|
} else {
|
|
271
189
|
throw `Invalid assay structure for "${assayName}". Must have .cohorts`;
|
|
272
190
|
}
|
|
273
191
|
}
|
|
274
192
|
}
|
|
193
|
+
const geneIndexHint = q.db.prepare(`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'proteome_abundance_gene'`).get() ? " INDEXED BY proteome_abundance_gene" : "";
|
|
275
194
|
q.find = async (arg) => {
|
|
276
195
|
const proteins = arg?.proteins;
|
|
277
196
|
if (!Array.isArray(proteins) || proteins.length == 0) throw "queries.proteome.find arg.proteins[] missing";
|
|
@@ -307,7 +226,7 @@ async function validate_query_proteome(ds) {
|
|
|
307
226
|
const rawRows = [];
|
|
308
227
|
if (filters?.length) {
|
|
309
228
|
const { conditions, params } = buildFilterClause(filters);
|
|
310
|
-
const sql = `SELECT DISTINCT gene, identifier FROM proteome_abundance WHERE gene >= ? COLLATE NOCASE AND gene < ? COLLATE NOCASE AND ${conditions.join(
|
|
229
|
+
const sql = `SELECT DISTINCT gene, identifier FROM proteome_abundance${geneIndexHint} WHERE gene >= ? COLLATE NOCASE AND gene < ? COLLATE NOCASE AND ${conditions.join(
|
|
311
230
|
" AND "
|
|
312
231
|
)} LIMIT ${MAX_FIND_RESULTS}`;
|
|
313
232
|
rawRows.push(...q.db.prepare(sql).all(token, upperToken, ...params));
|
|
@@ -356,23 +275,36 @@ function buildFilterClause(filters) {
|
|
|
356
275
|
}
|
|
357
276
|
return { conditions, params };
|
|
358
277
|
}
|
|
359
|
-
function
|
|
360
|
-
if (!filters?.length) throw "
|
|
278
|
+
function listCohortSamples(db, filters) {
|
|
279
|
+
if (!filters?.length) throw "listCohortSamples: filters must not be empty";
|
|
280
|
+
let perDb = cohortSampleCache.get(db);
|
|
281
|
+
if (!perDb) cohortSampleCache.set(db, perDb = /* @__PURE__ */ new Map());
|
|
282
|
+
const key = JSON.stringify(filters);
|
|
283
|
+
const hit = perDb.get(key);
|
|
284
|
+
if (hit) return hit;
|
|
361
285
|
const { conditions, params } = buildFilterClause(filters);
|
|
362
|
-
const
|
|
363
|
-
|
|
286
|
+
const samples = db.prepare(`SELECT DISTINCT sample FROM proteome_abundance WHERE ${conditions.join(" AND ")}`).all(...params).map((r) => String(r.sample));
|
|
287
|
+
perDb.set(key, samples);
|
|
288
|
+
return samples;
|
|
364
289
|
}
|
|
365
|
-
|
|
290
|
+
const cohortSampleCache = /* @__PURE__ */ new WeakMap();
|
|
291
|
+
function listIdentifierAnnotations(db, gene, filters) {
|
|
366
292
|
const { conditions, params } = buildFilterClause(filters);
|
|
367
|
-
const
|
|
293
|
+
const rows = db.prepare(
|
|
294
|
+
`SELECT identifier, modsite, isoform FROM proteome_abundance WHERE gene = ? COLLATE NOCASE${conditions.length ? " AND " + conditions.join(" AND ") : ""} GROUP BY identifier`
|
|
295
|
+
).all(gene, ...params);
|
|
296
|
+
return new Map(rows.map((r) => [r.identifier, { modsite: r.modsite, isoform: r.isoform }]));
|
|
297
|
+
}
|
|
298
|
+
function queryDbRows(db, identifier, filters) {
|
|
299
|
+
const { conditions, params } = buildFilterClause(filters);
|
|
300
|
+
const allConditions = [`identifier = ? COLLATE NOCASE`, ...conditions];
|
|
368
301
|
const sql = `SELECT organism, disease, identifier, protein_accession, isoform, modsite, gene, sample, value, brain_region
|
|
369
302
|
FROM proteome_abundance
|
|
370
303
|
WHERE ${allConditions.join(" AND ")}`;
|
|
371
|
-
return db.prepare(sql).all(
|
|
304
|
+
return db.prepare(sql).all(identifier, ...params);
|
|
372
305
|
}
|
|
373
306
|
async function getProteomeValuesFromCohort(ds, param, q) {
|
|
374
307
|
const db = ds.queries.proteome.db;
|
|
375
|
-
const hasBrainRegions = !!q.brainRegions;
|
|
376
308
|
const { assay, cohort, organism } = param.dataTypeDetails;
|
|
377
309
|
const organismConfig = q.organisms?.[organism];
|
|
378
310
|
if (!organismConfig) throw `queries.proteome invalid organism: ${organism}`;
|
|
@@ -380,7 +312,6 @@ async function getProteomeValuesFromCohort(ds, param, q) {
|
|
|
380
312
|
const organismColumnValue = organismConfig.columnValue;
|
|
381
313
|
const assayConfig = organismConfig.assays?.[assay];
|
|
382
314
|
if (!assayConfig) throw `queries.proteome.get invalid assay: ${assay}`;
|
|
383
|
-
const PTMType = assayConfig.PTMType;
|
|
384
315
|
const assayColumnIdx = assayConfig.columnIdx;
|
|
385
316
|
const assayColumnValue = assayConfig.columnValue;
|
|
386
317
|
const cohortConfig = assayConfig?.cohorts?.[cohort];
|
|
@@ -390,27 +321,16 @@ async function getProteomeValuesFromCohort(ds, param, q) {
|
|
|
390
321
|
const organismFilter = [{ columnIdx: organismColumnIdx, columnValue: organismColumnValue }];
|
|
391
322
|
const assayFilter = [{ columnIdx: assayColumnIdx, columnValue: assayColumnValue }];
|
|
392
323
|
const term2sample2value = /* @__PURE__ */ new Map();
|
|
393
|
-
const allEntries = [];
|
|
394
324
|
const controlSampleIds = /* @__PURE__ */ new Set();
|
|
395
325
|
for (const tw of param.terms) {
|
|
396
326
|
if (!tw) continue;
|
|
397
327
|
const fullGeneName = tw.term.name;
|
|
398
328
|
const identifier = fullGeneName.split(":")[1]?.trim();
|
|
399
329
|
const geneName = fullGeneName.split(":")[0]?.trim();
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
throw "invalid term name for proteome query, must be in format geneName: uniqueIdentifier";
|
|
405
|
-
}
|
|
406
|
-
const matchColumn = param.for === "proteinView" ? "gene" : "identifier";
|
|
407
|
-
const matchValue = param.for === "proteinView" ? geneName : identifier;
|
|
408
|
-
const caseRows = queryDbRows(db, matchColumn, matchValue, [...organismFilter, ...assayFilter, ...cohortCaseFilter]);
|
|
409
|
-
const controlRows = queryDbRows(db, matchColumn, matchValue, [
|
|
410
|
-
...organismFilter,
|
|
411
|
-
...assayFilter,
|
|
412
|
-
...cohortControlFilter
|
|
413
|
-
]);
|
|
330
|
+
if (!identifier || !geneName)
|
|
331
|
+
throw "invalid term name for proteome query, must be in format geneName: uniqueIdentifier";
|
|
332
|
+
const caseRows = queryDbRows(db, identifier, [...organismFilter, ...assayFilter, ...cohortCaseFilter]);
|
|
333
|
+
const controlRows = queryDbRows(db, identifier, [...organismFilter, ...assayFilter, ...cohortControlFilter]);
|
|
414
334
|
for (const row of controlRows) {
|
|
415
335
|
const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
|
|
416
336
|
if (sid !== void 0) controlSampleIds.add(String(sid));
|
|
@@ -426,59 +346,18 @@ async function getProteomeValuesFromCohort(ds, param, q) {
|
|
|
426
346
|
if (allowedSampleIds?.size == 0) {
|
|
427
347
|
return { term2sample2value: /* @__PURE__ */ new Map(), byTermId: {}, bySampleId: {} };
|
|
428
348
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (!entryMap.has(row.identifier)) {
|
|
436
|
-
entryMap.set(row.identifier, {
|
|
437
|
-
organism: row.organism,
|
|
438
|
-
disease: row.disease,
|
|
439
|
-
uniqueIdentifier: row.identifier,
|
|
440
|
-
assayName: assay,
|
|
441
|
-
cohortName: cohort,
|
|
442
|
-
PTMType,
|
|
443
|
-
modSites: PTMType ? row.modsite || void 0 : void 0,
|
|
444
|
-
proteinAccession: row.protein_accession,
|
|
445
|
-
isoform: row.isoform,
|
|
446
|
-
// refSeq transcript ID mapped from protein_accession
|
|
447
|
-
geneName: row.gene,
|
|
448
|
-
s2v: {},
|
|
449
|
-
// raw brain_region per sample id (only when brainRegions is configured);
|
|
450
|
-
// remapped + finalized into entry.sampleRegions in the route's init().
|
|
451
|
-
sampleRegionsRaw: hasBrainRegions ? {} : void 0
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
entryMap.get(row.identifier).s2v[sid] = row.value;
|
|
455
|
-
if (hasBrainRegions) entryMap.get(row.identifier).sampleRegionsRaw[sid] = row.brain_region;
|
|
456
|
-
}
|
|
457
|
-
for (const entry of entryMap.values()) allEntries.push(entry);
|
|
458
|
-
} else {
|
|
459
|
-
const s2v = {};
|
|
460
|
-
for (const row of allRows) {
|
|
461
|
-
const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
|
|
462
|
-
if (sid === void 0) continue;
|
|
463
|
-
if (allowedSampleIds && !allowedSampleIds.has(sid)) continue;
|
|
464
|
-
s2v[sid] = row.value;
|
|
465
|
-
}
|
|
466
|
-
if (Object.keys(s2v).length) {
|
|
467
|
-
term2sample2value.set(tw.$id, s2v);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
const bySampleId = {};
|
|
472
|
-
if (param.for === "proteinView") {
|
|
473
|
-
const sampleIds = /* @__PURE__ */ new Set();
|
|
474
|
-
for (const entry of allEntries) {
|
|
475
|
-
for (const sid of Object.keys(entry.s2v)) sampleIds.add(Number(sid));
|
|
349
|
+
const s2v = {};
|
|
350
|
+
for (const row of allRows) {
|
|
351
|
+
const sid = ds.cohort.termdb.q.sampleName2id(row.sample);
|
|
352
|
+
if (sid === void 0) continue;
|
|
353
|
+
if (allowedSampleIds && !allowedSampleIds.has(sid)) continue;
|
|
354
|
+
s2v[sid] = row.value;
|
|
476
355
|
}
|
|
477
|
-
|
|
478
|
-
|
|
356
|
+
if (Object.keys(s2v).length) {
|
|
357
|
+
term2sample2value.set(tw.$id, s2v);
|
|
479
358
|
}
|
|
480
|
-
return { allEntries, controlSampleIds, bySampleId };
|
|
481
359
|
}
|
|
360
|
+
const bySampleId = {};
|
|
482
361
|
if (term2sample2value.size == 0) {
|
|
483
362
|
throw `No data available for: ${param.terms?.map((t) => t.term.name).join(", ")}`;
|
|
484
363
|
}
|
|
@@ -490,9 +369,9 @@ async function getProteomeValuesFromCohort(ds, param, q) {
|
|
|
490
369
|
return { term2sample2value, controlSampleIds, bySampleId };
|
|
491
370
|
}
|
|
492
371
|
export {
|
|
493
|
-
countDistinctSamples,
|
|
494
|
-
getCohortStats,
|
|
495
372
|
init,
|
|
373
|
+
listCohortSamples,
|
|
374
|
+
listIdentifierAnnotations,
|
|
496
375
|
queryDbRows,
|
|
497
376
|
validate_query_proteome
|
|
498
377
|
};
|