• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vanvalenlab / deepcell-label / 4578689396

pending completion
4578689396

Pull #436

github

GitHub
Merge ddb425c30 into 6a993cb7a
Pull Request #436: Model training overhaul: SNGP model, uncertainty visualization, and custom embedding support

462 of 1163 branches covered (39.72%)

Branch coverage included in aggregate %.

20 of 628 new or added lines in 27 files covered. (3.18%)

76 existing lines in 5 files now uncovered.

3248 of 5431 relevant lines covered (59.8%)

543.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.25
/frontend/src/Project/service/labels/channelExpressionMachine.js
1
/** Makes calculations related to channel expression (mean, total, etc.) for
2
 * plotting and cell type clustering.
3
 */
4

5
import { UMAP } from 'umap-js';
6
import { actions, assign, Machine, send } from 'xstate';
7
import { pure } from 'xstate/lib/actions';
8
import Cells from '../../cells';
9
import { fromEventBus } from '../eventBus';
10

11
const { choose } = actions;
20✔
12

13
export function calculateMean(ctx) {
14
  const { t, feature, labeled, raw, cells, numCells } = ctx;
4✔
15
  const width = labeled[0].length;
4✔
16
  const height = labeled.length;
4✔
17
  const numChannels = raw.length;
4✔
18
  const cellStructure = new Cells(cells);
4✔
19
  let valueMapping = {};
4✔
20
  for (let i = 0; i < height; i++) {
4✔
21
    for (let j = 0; j < width; j++) {
8✔
22
      const value = labeled[i][j];
16✔
23
      if (valueMapping[value] === undefined) {
16✔
24
        valueMapping[value] = cellStructure.getCellsForValue(value, t, feature);
6✔
25
      }
26
    }
27
  }
28
  let totalValues = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
29
  let cellSizes = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
30
  let channelMeans = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
31
  for (let c = 0; c < numChannels; c++) {
4✔
32
    for (let i = 0; i < height; i++) {
8✔
33
      for (let j = 0; j < width; j++) {
16✔
34
        const cellList = valueMapping[labeled[i][j]];
32✔
35
        for (const cell of cellList) {
32✔
36
          totalValues[c][cell] = totalValues[c][cell] + raw[c][t][i][j];
18✔
37
          cellSizes[c][cell] = cellSizes[c][cell] + 1;
18✔
38
        }
39
      }
40
    }
41
  }
42
  for (let c = 0; c < numChannels; c++) {
4✔
43
    for (let i = 0; i < numCells; i++) {
8✔
44
      channelMeans[c][i] = totalValues[c][i] / cellSizes[c][i];
8✔
45
    }
46
  }
47
  return channelMeans;
4✔
48
}
49

50
export function calculateMeanWhole(ctx) {
51
  const { feature, labeledFull, raw, cells, numCells } = ctx;
4✔
52
  const width = labeledFull[0][0][0].length;
4✔
53
  const height = labeledFull[0][0].length;
4✔
54
  const numFrames = raw[0].length;
4✔
55
  const numChannels = raw.length;
4✔
56
  const cellStructure = new Cells(cells);
4✔
57
  let valueMappings = [];
4✔
58
  for (let t = 0; t < numFrames; t++) {
4✔
59
    let mapping = {};
6✔
60
    for (let i = 0; i < height; i++) {
6✔
61
      for (let j = 0; j < width; j++) {
12✔
62
        const value = labeledFull[feature][t][i][j];
24✔
63
        if (mapping[value] === undefined) {
24✔
64
          mapping[value] = cellStructure.getCellsForValue(value, t, feature);
10✔
65
        }
66
      }
67
    }
68
    valueMappings.push(mapping);
6✔
69
  }
70
  let totalValues = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
71
  let cellSizes = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
72
  let channelMeans = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
73
  for (let c = 0; c < numChannels; c++) {
4✔
74
    for (let t = 0; t < numFrames; t++) {
8✔
75
      for (let i = 0; i < height; i++) {
12✔
76
        for (let j = 0; j < width; j++) {
24✔
77
          const cellList = valueMappings[t][labeledFull[feature][t][i][j]];
48✔
78
          for (const cell of cellList) {
48✔
79
            totalValues[c][cell] = totalValues[c][cell] + raw[c][t][i][j];
36✔
80
            cellSizes[c][cell] = cellSizes[c][cell] + 1;
36✔
81
          }
82
        }
83
      }
84
    }
85
  }
86
  for (let c = 0; c < numChannels; c++) {
4✔
87
    for (let i = 0; i < numCells; i++) {
8✔
88
      channelMeans[c][i] = totalValues[c][i] / cellSizes[c][i];
8✔
89
    }
90
  }
91
  return channelMeans;
4✔
92
}
93

94
export function calculateTotal(ctx) {
95
  const { t, feature, labeled, raw, cells, numCells } = ctx;
4✔
96
  const width = labeled[0].length;
4✔
97
  const height = labeled.length;
4✔
98
  const numChannels = raw.length;
4✔
99
  const cellStructure = new Cells(cells);
4✔
100
  let valueMapping = {};
4✔
101
  for (let i = 0; i < height; i++) {
4✔
102
    for (let j = 0; j < width; j++) {
8✔
103
      const value = labeled[i][j];
16✔
104
      if (valueMapping[value] === undefined) {
16✔
105
        valueMapping[value] = cellStructure.getCellsForValue(value, t, feature);
6✔
106
      }
107
    }
108
  }
109
  let totalValues = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
110
  let cellSizes = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
111
  for (let c = 0; c < numChannels; c++) {
4✔
112
    for (let i = 0; i < height; i++) {
8✔
113
      for (let j = 0; j < width; j++) {
16✔
114
        const cellList = valueMapping[labeled[i][j]];
32✔
115
        for (const cell of cellList) {
32✔
116
          totalValues[c][cell] = totalValues[c][cell] + raw[c][t][i][j];
18✔
117
          cellSizes[c][cell] = cellSizes[c][cell] + 1;
18✔
118
        }
119
      }
120
    }
121
  }
122
  for (let i = 0; i < numCells; i++) {
4✔
123
    if (cellSizes[0][i] === 0) {
4✔
124
      for (let c = 0; c < numChannels; c++) {
1✔
125
        totalValues[c][i] = NaN;
2✔
126
      }
127
    }
128
  }
129
  return totalValues;
4✔
130
}
131

132
export function calculateTotalWhole(ctx) {
133
  const { feature, labeledFull, raw, cells, numCells } = ctx;
4✔
134
  const width = labeledFull[0][0][0].length;
4✔
135
  const height = labeledFull[0][0].length;
4✔
136
  const numFrames = raw[0].length;
4✔
137
  const numChannels = raw.length;
4✔
138
  const cellStructure = new Cells(cells);
4✔
139
  let valueMappings = [];
4✔
140
  for (let t = 0; t < numFrames; t++) {
4✔
141
    let mapping = {};
6✔
142
    for (let i = 0; i < height; i++) {
6✔
143
      for (let j = 0; j < width; j++) {
12✔
144
        const value = labeledFull[feature][t][i][j];
24✔
145
        if (mapping[value] === undefined) {
24✔
146
          mapping[value] = cellStructure.getCellsForValue(value, t, feature);
10✔
147
        }
148
      }
149
    }
150
    valueMappings.push(mapping);
6✔
151
  }
152
  let totalValues = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
153
  let cellSizes = Array.from({ length: numChannels }, () => new Array(numCells).fill(0));
8✔
154
  for (let c = 0; c < numChannels; c++) {
4✔
155
    for (let t = 0; t < numFrames; t++) {
8✔
156
      for (let i = 0; i < height; i++) {
12✔
157
        for (let j = 0; j < width; j++) {
24✔
158
          const cellList = valueMappings[t][labeledFull[feature][t][i][j]];
48✔
159
          for (const cell of cellList) {
48✔
160
            totalValues[c][cell] = totalValues[c][cell] + raw[c][t][i][j];
36✔
161
            cellSizes[c][cell] = cellSizes[c][cell] + 1;
36✔
162
          }
163
        }
164
      }
165
    }
166
  }
167
  for (let i = 0; i < numCells; i++) {
4✔
168
    if (cellSizes[0][i] === 0) {
4✔
169
      for (let c = 0; c < numChannels; c++) {
1✔
170
        totalValues[c][i] = NaN;
2✔
171
      }
172
    }
173
  }
174
  return totalValues;
4✔
175
}
176

177
export function calculatePosition(ctx) {
178
  const { t, feature, labeled, cells, numCells } = ctx;
4✔
179
  const width = labeled[0].length;
4✔
180
  const height = labeled.length;
4✔
181
  const cellStructure = new Cells(cells);
4✔
182
  let valueMapping = {};
4✔
183
  for (let i = 0; i < height; i++) {
4✔
184
    for (let j = 0; j < width; j++) {
8✔
185
      const value = labeled[i][j];
16✔
186
      if (valueMapping[value] === undefined) {
16✔
187
        valueMapping[value] = cellStructure.getCellsForValue(value, t, feature);
6✔
188
      }
189
    }
190
  }
191
  let totalX = new Array(numCells).fill(0);
4✔
192
  let totalY = new Array(numCells).fill(0);
4✔
193
  let cellSizes = new Array(numCells).fill(0);
4✔
194
  let centroidsX = new Array(numCells).fill(0);
4✔
195
  let centroidsY = new Array(numCells).fill(0);
4✔
196
  for (let i = 0; i < height; i++) {
4✔
197
    for (let j = 0; j < width; j++) {
8✔
198
      const cellList = valueMapping[labeled[i][j]];
16✔
199
      for (const cell of cellList) {
16✔
200
        totalX[cell] = totalX[cell] + j;
9✔
201
        totalY[cell] = totalY[cell] + height - 1 - i;
9✔
202
        cellSizes[cell] = cellSizes[cell] + 1;
9✔
203
      }
204
    }
205
  }
206
  for (let i = 0; i < numCells; i++) {
4✔
207
    centroidsX[i] = totalX[i] / cellSizes[i];
4✔
208
    centroidsY[i] = totalY[i] / cellSizes[i];
4✔
209
  }
210
  return [centroidsX, centroidsY];
4✔
211
}
212

213
const createChannelExpressionMachine = ({ eventBuses }) =>
20✔
214
  Machine(
866✔
215
    {
216
      id: 'channelExpression',
217
      invoke: [
218
        {
219
          id: 'eventBus',
220
          src: fromEventBus('channelExpression', () => eventBuses.channelExpression),
18✔
221
        },
222
        {
223
          id: 'channelExpression',
224
          src: fromEventBus('channelExpression', () => eventBuses.channelExpression, 'CALCULATION'),
18✔
225
        },
226
        {
227
          id: 'arrays',
228
          src: fromEventBus('channelExpression', () => eventBuses.arrays, [
18✔
229
            'LABELED',
230
            'LABELED_FULL',
231
          ]),
232
        },
233
        { id: 'cells', src: fromEventBus('channelExpression', () => eventBuses.cells, 'CELLS') },
18✔
234
        { id: 'load', src: fromEventBus('channelExpression', () => eventBuses.load, 'LOADED') },
18✔
235
        { src: fromEventBus('channelExpression', () => eventBuses.image, 'SET_T') },
18✔
236
        { src: fromEventBus('channelExpression', () => eventBuses.labeled, 'SET_FEATURE') },
18✔
237
      ],
238
      context: {
239
        t: 0,
240
        feature: 0,
241
        labeled: null,
242
        labeledFull: null,
243
        whole: false,
244
        raw: null,
245
        cells: null,
246
        numCells: null,
247
        calculations: null, // Calculations made across all frames
248
        embeddings: null, // Imported calculations / embeddings
249
        reduction: null, // The actual data calculated by the request
250
        calculation: null, // The type of calculation made (eg. Mean, Total)
251
        embeddingColorType: 'label', // Whether to use labels or uncertainty for the embedding plot
252
      },
253
      initial: 'loading',
254
      on: {
255
        LABELED: { actions: 'setLabeled' },
256
        LABELED_FULL: { actions: 'setLabeledFull' },
257
        CELLS: { actions: ['setCells', 'setNumCells'] },
258
        SET_T: { actions: 'setT' },
259
        SET_FEATURE: { actions: 'setFeature' },
260
      },
261
      states: {
262
        loading: {
263
          type: 'parallel',
264
          states: {
265
            getRaw: {
266
              initial: 'waiting',
267
              states: {
268
                waiting: {
269
                  on: {
270
                    LOADED: {
271
                      actions: ['setRaw', 'setLabeledFull', 'setEmbeddings'],
272
                      target: 'done',
273
                    },
274
                  },
275
                },
276
                done: { type: 'final' },
277
              },
278
            },
279
            getLabels: {
280
              initial: 'waiting',
281
              states: {
282
                waiting: {
283
                  on: {
284
                    LABELED: { actions: 'setLabeled', target: 'done' },
285
                  },
286
                },
287
                done: { type: 'final' },
288
              },
289
            },
290
          },
291
          onDone: { target: 'loaded' },
292
        },
293
        loaded: {
294
          initial: 'idle',
295
          states: {
296
            idle: {
297
              on: {
298
                TOGGLE_WHOLE: { actions: 'toggleWhole' },
299
                CALCULATE: { target: 'calculating' },
300
                CALCULATE_UMAP: { target: 'visualizing' },
301
                CHANGE_COLORMAP: {
302
                  actions: 'setColorMap',
303
                },
304
              },
305
            },
306
            calculating: {
307
              entry: choose([
308
                {
309
                  cond: (ctx, evt) => evt.stat === 'Mean' && !ctx.whole,
×
310
                  actions: ['setStat', 'calculateMean'],
311
                },
312
                {
313
                  cond: (ctx, evt) => evt.stat === 'Total' && !ctx.whole,
×
314
                  actions: ['setStat', 'calculateTotal'],
315
                },
316
                {
317
                  cond: (ctx, evt) => evt.stat === 'Mean' && ctx.whole,
×
318
                  actions: ['setStat', 'calculateMeanWhole'],
319
                },
320
                {
321
                  cond: (ctx, evt) => evt.stat === 'Total' && ctx.whole,
×
322
                  actions: ['setStat', 'calculateTotalWhole'],
323
                },
324
                {
325
                  cond: (_, evt) => evt.stat === 'Position',
×
326
                  actions: 'calculatePosition',
327
                },
328
              ]),
329
              always: 'idle',
330
            },
331
            visualizing: {
332
              entry: choose([
333
                {
334
                  cond: (ctx, evt) => evt.stat === 'Mean' && !ctx.whole,
×
335
                  actions: ['setStat', 'calculateMean'],
336
                },
337
                {
338
                  cond: (ctx, evt) => evt.stat === 'Total' && !ctx.whole,
×
339
                  actions: ['setStat', 'calculateTotal'],
340
                },
341
                {
NEW
342
                  cond: (ctx, evt) => evt.stat === 'Imported' && !ctx.whole,
×
343
                  actions: send('VISUALIZE'),
344
                },
345
                {
346
                  cond: (ctx, evt) => evt.stat === 'Mean' && ctx.whole,
×
347
                  actions: ['setStat', 'calculateMeanWhole'],
348
                },
349
                {
350
                  cond: (ctx, evt) => evt.stat === 'Total' && ctx.whole,
×
351
                  actions: ['setStat', 'calculateTotalWhole'],
352
                },
353
              ]),
354
              on: {
355
                CALCULATION: {
356
                  actions: 'calculateUmap',
357
                  target: 'idle',
358
                },
359
                VISUALIZE: {
360
                  actions: 'importUmap',
361
                  target: 'idle',
362
                },
363
              },
364
            },
365
          },
366
        },
367
      },
368
    },
369
    {
370
      actions: {
371
        setRaw: assign({ raw: (_, evt) => evt.raw }),
13✔
372
        setLabeled: assign({ labeled: (_, evt) => evt.labeled }),
13✔
373
        setLabeledFull: assign({ labeledFull: (_, evt) => evt.labeled }),
13✔
374
        setCells: assign({ cells: (_, evt) => evt.cells }),
26✔
375
        setNumCells: assign({ numCells: (_, evt) => new Cells(evt.cells).getNewCell() }),
26✔
376
        setT: assign({ t: (_, evt) => evt.t }),
×
377
        setFeature: assign({ feature: (_, evt) => evt.feature }),
×
378
        setStat: assign({ calculation: (_, evt) => evt.stat }),
×
379
        setEmbeddings: assign({ embeddings: (_, evt) => evt.embeddings }),
13✔
UNCOV
380
        toggleWhole: assign({ whole: (ctx) => !ctx.whole }),
×
NEW
381
        setColorMap: assign({ embeddingColorType: (_, evt) => evt.colorMap }),
×
382
        calculateMean: pure((ctx) => {
383
          const channelMeans = calculateMean(ctx);
×
384
          return [
×
385
            assign({ calculations: channelMeans }),
386
            send({ type: 'CALCULATION', calculations: channelMeans }, { to: 'eventBus' }),
387
          ];
388
        }),
389
        calculateMeanWhole: pure((ctx) => {
390
          const channelMeans = calculateMeanWhole(ctx);
×
391
          return [
×
392
            assign({ calculations: channelMeans }),
393
            send({ type: 'CALCULATION', calculations: channelMeans }, { to: 'eventBus' }),
394
          ];
395
        }),
396
        calculateTotal: pure((ctx) => {
397
          const totalValues = calculateTotal(ctx);
×
398
          return [
×
399
            assign({ calculations: totalValues }),
400
            send({ type: 'CALCULATION', calculations: totalValues }, { to: 'eventBus' }),
401
          ];
402
        }),
403
        calculateTotalWhole: pure((ctx) => {
404
          const totalValues = calculateTotalWhole(ctx);
×
405
          return [
×
406
            assign({ calculations: totalValues }),
407
            send({ type: 'CALCULATION', calculations: totalValues }, { to: 'eventBus' }),
408
          ];
409
        }),
410
        calculatePosition: assign({ reduction: (ctx) => calculatePosition(ctx) }),
×
411
        calculateUmap: assign({
412
          reduction: (ctx) => {
413
            const { raw, calculations } = ctx;
×
414
            const numChannels = raw.length;
×
415
            let vectors = [];
×
416
            let maxes = Array(numChannels).fill(0);
×
NEW
417
            let mins = Array(numChannels).fill(Infinity);
×
418
            for (let i = 0; i < calculations[0].length; i++) {
×
419
              let vector = [];
×
420
              for (let c = 0; c < numChannels; c++) {
×
421
                const calc = calculations[c][i];
×
422
                if (isNaN(calc)) {
×
423
                  vector.push(0);
×
424
                } else {
425
                  if (calc > maxes[c]) {
×
426
                    maxes[c] = calc;
×
427
                  }
NEW
428
                  if (calc < mins[c]) {
×
NEW
429
                    mins[c] = calc;
×
430
                  }
UNCOV
431
                  vector.push(calc);
×
432
                }
433
              }
434
              if (!calculations.every((channel) => isNaN(channel[i]))) {
×
435
                vectors.push(vector);
×
436
              }
437
            }
438
            vectors = vectors.map((vector) =>
×
NEW
439
              vector.map((calc, i) =>
×
NEW
440
                maxes[i] === 0 ? 0 : (calc - mins[i]) / (maxes[i] - mins[i])
×
441
              )
442
            );
443
            const umap = new UMAP();
×
444
            const embeddings = umap.fit(vectors);
×
445
            let x = [];
×
446
            let y = [];
×
447
            let embeddingCount = 0;
×
448
            for (let i = 0; i < calculations[0].length; i++) {
×
449
              if (calculations.every((channel) => isNaN(channel[i]))) {
×
450
                x.push(NaN);
×
451
                y.push(NaN);
×
452
              } else {
453
                x.push(embeddings[embeddingCount][0]);
×
454
                y.push(embeddings[embeddingCount][1]);
×
455
                embeddingCount++;
×
456
              }
457
            }
458
            return [x, y];
×
459
          },
460
        }),
461
        importUmap: assign({
462
          reduction: (ctx) => {
NEW
463
            const { embeddings } = ctx;
×
NEW
464
            const vectors = embeddings.filter((vector) => !vector.every((e) => e === 0));
×
NEW
465
            const umap = new UMAP();
×
NEW
466
            const fitted = umap.fit(vectors);
×
NEW
467
            const x = [];
×
NEW
468
            const y = [];
×
NEW
469
            let vectorIndex = 0;
×
NEW
470
            for (let i = 0; i < embeddings.length; i++) {
×
NEW
471
              if (embeddings[i].every((e) => e === 0)) {
×
NEW
472
                x.push(NaN);
×
NEW
473
                y.push(NaN);
×
474
              } else {
NEW
475
                x.push(fitted[vectorIndex][0]);
×
NEW
476
                y.push(fitted[vectorIndex][1]);
×
NEW
477
                vectorIndex++;
×
478
              }
479
            }
NEW
480
            return [x, y];
×
481
          },
482
        }),
483
      },
484
    }
485
  );
486

487
export default createChannelExpressionMachine;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc