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

vanvalenlab / deepcell-label / 4697120321

pending completion
4697120321

push

github-actions

GitHub
Add log y scale button for histogram and save layout changes (#443)

456 of 1151 branches covered (39.62%)

Branch coverage included in aggregate %.

0 of 19 new or added lines in 3 files covered. (0.0%)

1 existing line in 1 file now uncovered.

3193 of 5381 relevant lines covered (59.34%)

543.34 hits per line

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

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

493
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