@sjcrh/proteinpaint-server 2.186.0 → 2.187.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 +4 -4
- package/routes/genesetEnrichment.js +21 -5
- package/routes/profile.forms2.js +107 -0
- package/routes/termdb.DE.js +11 -13
- package/routes/termdb.config.js +3 -0
- package/routes/termdb.diffMeth.js +26 -164
- package/routes/termdb.proteome.js +1 -0
- package/src/app.js +470 -258
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.187.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",
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@sjcrh/augen": "2.181.1",
|
|
65
|
-
"@sjcrh/proteinpaint-python": "2.
|
|
65
|
+
"@sjcrh/proteinpaint-python": "2.187.0",
|
|
66
66
|
"@sjcrh/proteinpaint-r": "2.181.0",
|
|
67
67
|
"@sjcrh/proteinpaint-rust": "2.186.0",
|
|
68
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
69
|
-
"@sjcrh/proteinpaint-types": "2.
|
|
68
|
+
"@sjcrh/proteinpaint-shared": "2.187.0",
|
|
69
|
+
"@sjcrh/proteinpaint-types": "2.187.0",
|
|
70
70
|
"@types/express": "^5.0.0",
|
|
71
71
|
"@types/express-session": "^1.18.1",
|
|
72
72
|
"better-sqlite3": "^12.4.1",
|
|
@@ -31,6 +31,10 @@ function init({ genomes }) {
|
|
|
31
31
|
res.send(results);
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
if (typeof results === "object" && results.error) {
|
|
35
|
+
res.send({ status: "error", error: results.error });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
34
38
|
if (typeof results != "string") throw new Error("gsea result is not string");
|
|
35
39
|
res.sendFile(results, (err) => {
|
|
36
40
|
fs.unlink(results, () => {
|
|
@@ -77,12 +81,24 @@ async function resolveGseaGenesAndFoldChange({
|
|
|
77
81
|
}) {
|
|
78
82
|
if (q.cacheId) {
|
|
79
83
|
if (!q.daRequest) throw new Error("daCacheMissing");
|
|
80
|
-
const result = await readCacheFileOrRecompute({
|
|
84
|
+
const result = await readCacheFileOrRecompute({
|
|
85
|
+
daRequest: q.daRequest,
|
|
86
|
+
genomes
|
|
87
|
+
});
|
|
81
88
|
if (result.cacheId !== q.cacheId) throw new Error("cacheId does not match daRequest");
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
if (result.kind === "DE") {
|
|
90
|
+
return {
|
|
91
|
+
genes: result.geneData.map((g) => g.gene_name),
|
|
92
|
+
fold_change: result.geneData.map((g) => g.fold_change)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (result.kind === "DM") {
|
|
96
|
+
return {
|
|
97
|
+
genes: result.promoterData.map((p) => p.gene_name),
|
|
98
|
+
fold_change: result.promoterData.map((p) => p.fold_change)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`unexpected result kind: ${result.kind}`);
|
|
86
102
|
}
|
|
87
103
|
if (!q.genes || !q.fold_change) throw new Error("requires genes and fold_change when cacheId is absent");
|
|
88
104
|
return { genes: q.genes, fold_change: q.fold_change };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ProfileForms2ScoresPayload } from "#types/checkers";
|
|
2
|
+
import { getData } from "../src/termdb.matrix.js";
|
|
3
|
+
const api = {
|
|
4
|
+
endpoint: "termdb/profileForms2Scores",
|
|
5
|
+
methods: {
|
|
6
|
+
get: {
|
|
7
|
+
...ProfileForms2ScoresPayload,
|
|
8
|
+
init
|
|
9
|
+
},
|
|
10
|
+
post: {
|
|
11
|
+
...ProfileForms2ScoresPayload,
|
|
12
|
+
init
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function init({ genomes }) {
|
|
17
|
+
return async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const g = genomes[req.query.genome];
|
|
20
|
+
if (!g) throw "invalid genome name";
|
|
21
|
+
const ds = g.datasets?.[req.query.dslabel];
|
|
22
|
+
const result = await getScores(req.query, ds);
|
|
23
|
+
res.send(result);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.log(e);
|
|
26
|
+
res.send({ status: "error", error: e.message || e });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function getScores(query, ds) {
|
|
31
|
+
const { activeCohort, clientAuthResult } = query.__protected__;
|
|
32
|
+
const facilityTermId = ds.cohort.termdb.plotConfigByCohort?.[activeCohort]?.profileForms2?.facilityTW?.id;
|
|
33
|
+
if (!facilityTermId) throw `profileForms2.facilityTW.id missing for cohort '${activeCohort}'`;
|
|
34
|
+
const facilityTW = { term: { id: facilityTermId }, q: {} };
|
|
35
|
+
const terms = [facilityTW, ...query.scoreTerms];
|
|
36
|
+
if (query.scScoreTerms) terms.push(...query.scScoreTerms);
|
|
37
|
+
if (!query.filterByUserSites) query.__protected__.ignoredTermIds.push(facilityTermId);
|
|
38
|
+
const cohortAuth = clientAuthResult[activeCohort];
|
|
39
|
+
const isPublic = !cohortAuth?.role || cohortAuth.role === "public";
|
|
40
|
+
const userSites = cohortAuth?.sites;
|
|
41
|
+
const raw = await getData(
|
|
42
|
+
{
|
|
43
|
+
terms,
|
|
44
|
+
filter: query.filter,
|
|
45
|
+
__protected__: query.__protected__
|
|
46
|
+
},
|
|
47
|
+
ds
|
|
48
|
+
);
|
|
49
|
+
if (raw.error) throw raw.error;
|
|
50
|
+
const samples = Object.values(raw.samples);
|
|
51
|
+
let sites = samples.map((s) => {
|
|
52
|
+
const val = s[facilityTW.$id].value;
|
|
53
|
+
let label = facilityTW.term.values?.[val]?.label || val;
|
|
54
|
+
if (label.length > 50) label = label.slice(0, 47) + "...";
|
|
55
|
+
return { value: val, label };
|
|
56
|
+
});
|
|
57
|
+
if (userSites && query.filterByUserSites) sites = sites.filter((s) => userSites.includes(s.value));
|
|
58
|
+
sites.sort((a, b) => a.label.localeCompare(b.label));
|
|
59
|
+
const eligibleSamples = userSites && query.filterByUserSites ? samples.filter((s) => userSites.includes(s[facilityTW.$id].value)) : samples;
|
|
60
|
+
const term2Score = {};
|
|
61
|
+
for (const d of query.scoreTerms) {
|
|
62
|
+
term2Score[d.term.id] = getPercentsDict((sample) => getDict(d.$id, sample), eligibleSamples);
|
|
63
|
+
}
|
|
64
|
+
if (query.scScoreTerms) {
|
|
65
|
+
for (const d of query.scScoreTerms) {
|
|
66
|
+
term2Score[d.term.id] = getSCPercentsDict(d, eligibleSamples);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
term2Score,
|
|
71
|
+
sites: isPublic ? [] : sites,
|
|
72
|
+
// public never sees site IDs
|
|
73
|
+
n: eligibleSamples.length
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function getDict(key, sample) {
|
|
77
|
+
if (!sample[key]) return null;
|
|
78
|
+
const termData = sample[key].value;
|
|
79
|
+
return JSON.parse(termData);
|
|
80
|
+
}
|
|
81
|
+
function getPercentsDict(getDictFunc, samples) {
|
|
82
|
+
const percentageDict = {};
|
|
83
|
+
for (const sample of samples) {
|
|
84
|
+
const percents = getDictFunc(sample);
|
|
85
|
+
if (!percents) continue;
|
|
86
|
+
for (const key in percents) {
|
|
87
|
+
const value = percents[key];
|
|
88
|
+
if (!percentageDict[key]) percentageDict[key] = 0;
|
|
89
|
+
percentageDict[key] += value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return percentageDict;
|
|
93
|
+
}
|
|
94
|
+
function getSCPercentsDict(tw, samples) {
|
|
95
|
+
if (!tw) throw "tw not defined";
|
|
96
|
+
const percentageDict = {};
|
|
97
|
+
for (const sample of samples) {
|
|
98
|
+
const key = sample[tw.$id]?.value;
|
|
99
|
+
if (key == null) continue;
|
|
100
|
+
if (!percentageDict[key]) percentageDict[key] = 0;
|
|
101
|
+
percentageDict[key] += 1;
|
|
102
|
+
}
|
|
103
|
+
return percentageDict;
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
api
|
|
107
|
+
};
|
package/routes/termdb.DE.js
CHANGED
|
@@ -5,7 +5,7 @@ import serverconfig from "../src/serverconfig.js";
|
|
|
5
5
|
import { get_header_txt } from "#src/utils.js";
|
|
6
6
|
import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
7
7
|
import { renderVolcano } from "../src/renderVolcano.ts";
|
|
8
|
-
import { readCacheFileOrRecompute,
|
|
8
|
+
import { readCacheFileOrRecompute, resolveDaContext, resolveSampleGroups } from "../src/diffAnalysis.ts";
|
|
9
9
|
const api = {
|
|
10
10
|
endpoint: "termdb/DE",
|
|
11
11
|
methods: {
|
|
@@ -24,7 +24,7 @@ function init({ genomes }) {
|
|
|
24
24
|
try {
|
|
25
25
|
const q = req.query;
|
|
26
26
|
if (q.preAnalysis) {
|
|
27
|
-
const { ds, term_results, term_results2 } = await
|
|
27
|
+
const { ds, term_results, term_results2 } = await resolveDaContext(q, genomes);
|
|
28
28
|
const groups = resolveSampleGroups(q, ds, term_results, term_results2);
|
|
29
29
|
const group1Name = q.samplelst.groups[0].name;
|
|
30
30
|
const group2Name = q.samplelst.groups[1].name;
|
|
@@ -37,20 +37,18 @@ function init({ genomes }) {
|
|
|
37
37
|
});
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const rendered = await renderVolcano(geneData, q.volcanoRender);
|
|
45
|
-
rendered.cacheId = cacheId;
|
|
40
|
+
const result = await readCacheFileOrRecompute({ daRequest: q, genomes });
|
|
41
|
+
if (result.kind !== "DE") throw new Error("expected DE result from readCacheFileOrRecompute");
|
|
42
|
+
const rendered = await renderVolcano(result.geneData, q.volcanoRender);
|
|
43
|
+
rendered.cacheId = result.cacheId;
|
|
46
44
|
const output = {
|
|
47
45
|
data: rendered,
|
|
48
|
-
sample_size1,
|
|
49
|
-
sample_size2,
|
|
50
|
-
method,
|
|
51
|
-
images
|
|
46
|
+
sample_size1: result.sample_size1,
|
|
47
|
+
sample_size2: result.sample_size2,
|
|
48
|
+
method: result.method,
|
|
49
|
+
images: result.images
|
|
52
50
|
};
|
|
53
|
-
if (bcv != null) output.bcv = bcv;
|
|
51
|
+
if (result.bcv != null) output.bcv = result.bcv;
|
|
54
52
|
res.send(output);
|
|
55
53
|
} catch (e) {
|
|
56
54
|
res.send({ status: "error", error: e.message || e });
|
package/routes/termdb.config.js
CHANGED
|
@@ -203,6 +203,9 @@ function addNonDictionaryQueries(c, ds, genome) {
|
|
|
203
203
|
if (orgSrc.overlayTerm) {
|
|
204
204
|
q2.proteome.organisms[organism].overlayTerm = JSON.parse(JSON.stringify(orgSrc.overlayTerm));
|
|
205
205
|
}
|
|
206
|
+
if (orgSrc.genomeName) {
|
|
207
|
+
q2.proteome.organisms[organism].genomeName = orgSrc.genomeName;
|
|
208
|
+
}
|
|
206
209
|
if (orgSrc.assays) {
|
|
207
210
|
q2.proteome.organisms[organism].assays = {};
|
|
208
211
|
for (const assay in orgSrc.assays) {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { diffMethPayload } from "#types/checkers";
|
|
2
|
-
import { getData } from "../src/termdb.matrix.js";
|
|
3
|
-
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
4
|
-
import { mayLog } from "#src/helpers.ts";
|
|
5
|
-
import { formatElapsedTime } from "#shared";
|
|
6
2
|
import { renderVolcano } from "../src/renderVolcano.ts";
|
|
3
|
+
import { readCacheFileOrRecompute, resolveDaContext, resolveDmSampleGroups } from "../src/diffAnalysis.ts";
|
|
7
4
|
const api = {
|
|
8
5
|
endpoint: "termdb/diffMeth",
|
|
9
6
|
methods: {
|
|
@@ -21,173 +18,38 @@ function init({ genomes }) {
|
|
|
21
18
|
return async (req, res) => {
|
|
22
19
|
try {
|
|
23
20
|
const q = req.query;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if ("totalRows" in results.data && results.data.totalRows === 0)
|
|
21
|
+
if (q.preAnalysis) {
|
|
22
|
+
const { ds, term_results, term_results2 } = await resolveDaContext(q, genomes);
|
|
23
|
+
const groups = resolveDmSampleGroups(q, ds, term_results, term_results2);
|
|
24
|
+
const group1Name = q.samplelst.groups[0].name;
|
|
25
|
+
const group2Name = q.samplelst.groups[1].name;
|
|
26
|
+
res.send({
|
|
27
|
+
data: {
|
|
28
|
+
[group1Name]: groups.group1names.length,
|
|
29
|
+
[group2Name]: groups.group2names.length,
|
|
30
|
+
...groups.alerts.length ? { alert: groups.alerts.join(" | ") } : {}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const result = await readCacheFileOrRecompute({ daRequest: q, genomes });
|
|
36
|
+
if (result.kind !== "DM") throw new Error("expected DM result from readCacheFileOrRecompute");
|
|
37
|
+
const rendered = await renderVolcano(result.promoterData, q.volcanoRender);
|
|
38
|
+
rendered.cacheId = result.cacheId;
|
|
39
|
+
if (rendered.totalRows === 0)
|
|
44
40
|
throw new Error("No promoters passed filtering. Try relaxing group criteria or selecting more samples.");
|
|
45
|
-
|
|
41
|
+
const output = {
|
|
42
|
+
data: rendered,
|
|
43
|
+
sample_size1: result.sample_size1,
|
|
44
|
+
sample_size2: result.sample_size2
|
|
45
|
+
};
|
|
46
|
+
res.send(output);
|
|
46
47
|
} catch (e) {
|
|
47
48
|
res.send({ status: "error", error: e.message || e });
|
|
48
49
|
if (e instanceof Error && e.stack) console.log(e);
|
|
49
50
|
}
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
|
-
async function run_diffMeth(param, ds, term_results, term_results2) {
|
|
53
|
-
if (param.samplelst?.groups?.length != 2)
|
|
54
|
-
throw new Error("Exactly 2 sample groups are required for differential methylation analysis.");
|
|
55
|
-
if (param.samplelst.groups[0].values?.length < 1)
|
|
56
|
-
throw new Error("Group 1 has no samples. Please select at least one sample.");
|
|
57
|
-
if (param.samplelst.groups[1].values?.length < 1)
|
|
58
|
-
throw new Error("Group 2 has no samples. Please select at least one sample.");
|
|
59
|
-
const q = ds.queries.dnaMethylation?.promoter;
|
|
60
|
-
if (!q) throw new Error("This dataset does not have promoter-level methylation data configured.");
|
|
61
|
-
if (!q.file) throw new Error("Promoter methylation data file is not configured for this dataset.");
|
|
62
|
-
const group1names = [];
|
|
63
|
-
const conf1_group1 = [];
|
|
64
|
-
const conf2_group1 = [];
|
|
65
|
-
for (const s of param.samplelst.groups[0].values) {
|
|
66
|
-
if (!Number.isInteger(s.sampleId)) continue;
|
|
67
|
-
const n = ds.cohort.termdb.q.id2sampleName(s.sampleId);
|
|
68
|
-
if (!n) continue;
|
|
69
|
-
if (!q.allSampleSet.has(n)) continue;
|
|
70
|
-
if (param.tw && param.tw2) {
|
|
71
|
-
if (term_results.samples[s.sampleId] && term_results2.samples[s.sampleId]) {
|
|
72
|
-
conf1_group1.push(
|
|
73
|
-
param.tw.q.mode == "continuous" ? term_results.samples[s.sampleId][param.tw.$id]["value"] : term_results.samples[s.sampleId][param.tw.$id]["key"]
|
|
74
|
-
);
|
|
75
|
-
conf2_group1.push(
|
|
76
|
-
param.tw2.q.mode == "continuous" ? term_results2.samples[s.sampleId][param.tw2.$id]["value"] : term_results2.samples[s.sampleId][param.tw2.$id]["key"]
|
|
77
|
-
);
|
|
78
|
-
group1names.push(n);
|
|
79
|
-
}
|
|
80
|
-
} else if (param.tw && !param.tw2) {
|
|
81
|
-
if (term_results.samples[s.sampleId]) {
|
|
82
|
-
conf1_group1.push(
|
|
83
|
-
param.tw.q.mode == "continuous" ? term_results.samples[s.sampleId][param.tw.$id]["value"] : term_results.samples[s.sampleId][param.tw.$id]["key"]
|
|
84
|
-
);
|
|
85
|
-
group1names.push(n);
|
|
86
|
-
}
|
|
87
|
-
} else if (!param.tw && param.tw2) {
|
|
88
|
-
if (term_results2.samples[s.sampleId]) {
|
|
89
|
-
conf2_group1.push(
|
|
90
|
-
param.tw2.q.mode == "continuous" ? term_results2.samples[s.sampleId][param.tw2.$id]["value"] : term_results2.samples[s.sampleId][param.tw2.$id]["key"]
|
|
91
|
-
);
|
|
92
|
-
group1names.push(n);
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
group1names.push(n);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const group2names = [];
|
|
99
|
-
const conf1_group2 = [];
|
|
100
|
-
const conf2_group2 = [];
|
|
101
|
-
for (const s of param.samplelst.groups[1].values) {
|
|
102
|
-
if (!Number.isInteger(s.sampleId)) continue;
|
|
103
|
-
const n = ds.cohort.termdb.q.id2sampleName(s.sampleId);
|
|
104
|
-
if (!n) continue;
|
|
105
|
-
if (!q.allSampleSet.has(n)) continue;
|
|
106
|
-
if (param.tw && param.tw2) {
|
|
107
|
-
if (term_results.samples[s.sampleId] && term_results2.samples[s.sampleId]) {
|
|
108
|
-
conf1_group2.push(
|
|
109
|
-
param.tw.q.mode == "continuous" ? term_results.samples[s.sampleId][param.tw.$id]["value"] : term_results.samples[s.sampleId][param.tw.$id]["key"]
|
|
110
|
-
);
|
|
111
|
-
conf2_group2.push(
|
|
112
|
-
param.tw2.q.mode == "continuous" ? term_results2.samples[s.sampleId][param.tw2.$id]["value"] : term_results2.samples[s.sampleId][param.tw2.$id]["key"]
|
|
113
|
-
);
|
|
114
|
-
group2names.push(n);
|
|
115
|
-
}
|
|
116
|
-
} else if (param.tw && !param.tw2) {
|
|
117
|
-
if (term_results.samples[s.sampleId]) {
|
|
118
|
-
conf1_group2.push(
|
|
119
|
-
param.tw.q.mode == "continuous" ? term_results.samples[s.sampleId][param.tw.$id]["value"] : term_results.samples[s.sampleId][param.tw.$id]["key"]
|
|
120
|
-
);
|
|
121
|
-
group2names.push(n);
|
|
122
|
-
}
|
|
123
|
-
} else if (!param.tw && param.tw2) {
|
|
124
|
-
if (term_results2.samples[s.sampleId]) {
|
|
125
|
-
conf2_group2.push(
|
|
126
|
-
param.tw2.q.mode == "continuous" ? term_results2.samples[s.sampleId][param.tw2.$id]["value"] : term_results2.samples[s.sampleId][param.tw2.$id]["key"]
|
|
127
|
-
);
|
|
128
|
-
group2names.push(n);
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
group2names.push(n);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const sample_size1 = group1names.length;
|
|
135
|
-
const sample_size2 = group2names.length;
|
|
136
|
-
const alerts = validateGroups(sample_size1, sample_size2, group1names, group2names);
|
|
137
|
-
if (param.preAnalysis) {
|
|
138
|
-
const group1Name = param.samplelst.groups[0].name;
|
|
139
|
-
const group2Name = param.samplelst.groups[1].name;
|
|
140
|
-
return {
|
|
141
|
-
data: {
|
|
142
|
-
[group1Name]: sample_size1,
|
|
143
|
-
[group2Name]: sample_size2,
|
|
144
|
-
...alerts.length ? { alert: alerts.join(" | ") } : {}
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (alerts.length) throw new Error(alerts.join(" | "));
|
|
149
|
-
const diffMethInput = {
|
|
150
|
-
case: group2names.join(","),
|
|
151
|
-
control: group1names.join(","),
|
|
152
|
-
input_file: q.file,
|
|
153
|
-
min_samples_per_group: param.min_samples_per_group
|
|
154
|
-
};
|
|
155
|
-
if (param.tw) {
|
|
156
|
-
diffMethInput.conf1 = [...conf1_group2, ...conf1_group1];
|
|
157
|
-
diffMethInput.conf1_mode = param.tw.q.mode;
|
|
158
|
-
if (new Set(diffMethInput.conf1).size === 1) {
|
|
159
|
-
throw new Error("Confounding variable 1 has only one value");
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (param.tw2) {
|
|
163
|
-
diffMethInput.conf2 = [...conf2_group2, ...conf2_group1];
|
|
164
|
-
diffMethInput.conf2_mode = param.tw2.q.mode;
|
|
165
|
-
if (new Set(diffMethInput.conf2).size === 1) {
|
|
166
|
-
throw new Error("Confounding variable 2 has only one value");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
const time1 = Date.now();
|
|
170
|
-
const result = JSON.parse(await run_R("diffMeth.R", JSON.stringify(diffMethInput)));
|
|
171
|
-
mayLog("Time taken to run diffMeth:", formatElapsedTime(Date.now() - time1));
|
|
172
|
-
const rendered = await renderVolcano(result.promoter_data, param.volcanoRender);
|
|
173
|
-
const output = {
|
|
174
|
-
data: rendered,
|
|
175
|
-
sample_size1,
|
|
176
|
-
sample_size2
|
|
177
|
-
};
|
|
178
|
-
return output;
|
|
179
|
-
}
|
|
180
|
-
function validateGroups(sample_size1, sample_size2, group1names, group2names) {
|
|
181
|
-
const alerts = [];
|
|
182
|
-
if (sample_size1 < 1) alerts.push("No samples in group 1 have methylation data available.");
|
|
183
|
-
if (sample_size2 < 1) alerts.push("No samples in group 2 have methylation data available.");
|
|
184
|
-
const commonnames = group1names.filter((x) => group2names.includes(x));
|
|
185
|
-
if (commonnames.length)
|
|
186
|
-
alerts.push(
|
|
187
|
-
`${commonnames.length} sample(s) appear in both groups: ${commonnames.join(", ")}. Please remove duplicates.`
|
|
188
|
-
);
|
|
189
|
-
return alerts;
|
|
190
|
-
}
|
|
191
53
|
export {
|
|
192
54
|
api
|
|
193
55
|
};
|
|
@@ -64,6 +64,7 @@ function init({ genomes }) {
|
|
|
64
64
|
entry.testedN = stats.testedN;
|
|
65
65
|
entry.controlN = stats.controlN;
|
|
66
66
|
if (assay.mclassOverride) entry.mclassOverride = assay.mclassOverride;
|
|
67
|
+
if (organism.genomeName) entry.genomeName = organism.genomeName;
|
|
67
68
|
cohorts.push(entry);
|
|
68
69
|
}
|
|
69
70
|
}
|