@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.
@@ -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
- const pValue = getWelchPValue(testedValues, controlValues);
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 getWelchPValue(a, b) {
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
- const var1 = sampleVariance(a, mean1);
102
- const var2 = sampleVariance(b, mean2);
103
- if (!Number.isFinite(var1) || !Number.isFinite(var2)) return null;
104
- const se2 = var1 / n1 + var2 / n2;
105
- if (!(se2 > 0)) {
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) / Math.sqrt(se2);
110
- const df = se2 * se2 / ((var1 / n1) ** 2 / (n1 - 1) + (var2 / n2) ** 2 / (n2 - 1));
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
- if (!result || !result.data || !result?.data?.length) {
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 validateSamplesNative(q.samples, q.data, ds);
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 validateSamplesNative(S, D, ds) {
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
- return { samples: [...samples.values()] };
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);