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

vanvalenlab / deepcell-label / 5074034006

pending completion
5074034006

Pull #468

github

GitHub
Merge 7f6b0d199 into 8a49c03f6
Pull Request #468: Export channels on submit/download

445 of 1181 branches covered (37.68%)

Branch coverage included in aggregate %.

3 of 7 new or added lines in 3 files covered. (42.86%)

155 existing lines in 5 files now uncovered.

2376 of 4421 relevant lines covered (53.74%)

661.14 hits per line

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

23.73
/frontend/src/Project/service/exportMachine.js
1
/** Collects all labeled data to be repackaged by the server for exporting. */
2

3
import * as zip from '@zip.js/zip.js';
4
import { flattenDeep } from 'lodash';
5
import { assign, Machine, send } from 'xstate';
6
import { fromEventBus } from './eventBus';
7

8
/** Creates a blob for a zip file with all project data. */
9
async function makeExportZip(context) {
NEW
10
  const { raw, labeled, channels, cells, cellTypes, divisions, spots } = context;
×
11
  const dimensions = {
×
12
    width: raw[0][0][0].length,
13
    height: raw[0][0].length,
14
    duration: raw[0].length,
15
    numChannels: raw.length,
16
    numFeatures: labeled.length,
17
    dtype: raw[0][0][0].constructor.name,
18
  };
19
  const zipWriter = new zip.ZipWriter(new zip.BlobWriter('application/zip'));
×
20
  await zipWriter.add('dimensions.json', new zip.TextReader(JSON.stringify(dimensions)));
×
21
  await zipWriter.add('labeled.dat', new zip.BlobReader(new Blob(flattenDeep(labeled))));
×
22
  await zipWriter.add('raw.dat', new zip.BlobReader(new Blob(flattenDeep(raw))));
×
NEW
23
  await zipWriter.add('channels.json', new zip.TextReader(JSON.stringify(channels)));
×
24
  await zipWriter.add('cells.json', new zip.TextReader(JSON.stringify(cells)));
×
25
  await zipWriter.add('cellTypes.json', new zip.TextReader(JSON.stringify(cellTypes)));
×
26
  await zipWriter.add('divisions.json', new zip.TextReader(JSON.stringify(divisions)));
×
27
  if (spots) {
×
28
    await zipWriter.add(
×
29
      'spots.csv',
30
      new zip.TextReader('x,y\n' + spots.map((e) => e.join(',')).join('\n'))
×
31
    );
32
  }
33

34
  const zipBlob = await zipWriter.close();
×
35
  return zipBlob;
×
36
}
37

38
/** Sends a zip to the DeepCell Label API to be repackaged for upload to an S3 bucket. */
39
async function upload(context) {
40
  const { projectId, bucket } = context;
×
41
  const form = new FormData();
×
42
  const zipBlob = await makeExportZip(context);
×
43
  form.append('labels', zipBlob, 'labels.zip');
×
44
  form.append('id', projectId);
×
45
  form.append('bucket', bucket);
×
46

47
  const options = {
×
48
    method: 'POST',
49
    body: form,
50
    'Content-Type': 'multipart/form-data',
51
  };
52
  return fetch(`${document.location.origin}/api/upload`, options).then(checkResponseCode);
×
53
}
54

55
/** Sends a zip to the DeepCell Label API to be repackaged for download by the user.
56
 * @return {Promise.<URL>} A promise that resolves to an object URL for the repackaged zipfile.
57
 */
58
async function download(context) {
59
  const { projectId } = context;
×
60
  const form = new FormData();
×
61
  const zipBlob = await makeExportZip(context);
×
62
  form.append('labels', zipBlob, 'labels.zip');
×
63
  form.append('id', projectId);
×
64

65
  const options = {
×
66
    method: 'POST',
67
    body: form,
68
    'Content-Type': 'multipart/form-data',
69
  };
70
  return fetch(`${document.location.origin}/api/download`, options)
×
71
    .then(checkResponseCode)
72
    .then((response) => response.blob())
×
73
    .then((blob) => URL.createObjectURL(blob));
×
74
}
75

76
function checkResponseCode(response) {
77
  return response.ok ? response : Promise.reject(response);
×
78
}
79

80
const createExportMachine = ({ projectId, eventBuses }) =>
19✔
81
  Machine(
866✔
82
    {
83
      id: 'export',
84
      invoke: [
85
        { id: 'arrays', src: fromEventBus('export', () => eventBuses.arrays, 'ARRAYS') },
18✔
86
        { id: 'raw', src: fromEventBus('export', () => eventBuses.raw, 'CHANNELS') },
18✔
87
        { id: 'cells', src: fromEventBus('export', () => eventBuses.cells, 'CELLS') },
18✔
88
        { id: 'divisions', src: fromEventBus('export', () => eventBuses.divisions, 'DIVISIONS') },
18✔
89
        { id: 'cellTypes', src: fromEventBus('export', () => eventBuses.cellTypes, 'CELLTYPES') },
18✔
90
        { id: 'spots', src: fromEventBus('export', () => eventBuses.spots, 'SPOTS') },
18✔
91
      ],
92
      context: {
93
        projectId,
94
        bucket:
95
          new URLSearchParams(window.location.search).get('bucket') ?? 'deepcell-label-output',
1,732✔
96
        raw: null,
97
        labeled: null,
98
        cells: null,
99
        cellTypes: null,
100
        divisions: null,
101
        spots: null,
102
      },
103
      on: {
104
        CELLS: { actions: 'setCells' },
105
        CELLTYPES: { actions: 'setCellTypes' },
106
        DIVISIONS: { actions: 'setDivisions' },
107
        SPOTS: { actions: 'setSpots' },
108
      },
109
      initial: 'idle',
110
      states: {
111
        idle: {
112
          on: {
113
            UPLOAD: 'uploading',
114
            DOWNLOAD: 'downloading',
115
          },
116
        },
117
        uploading: {
118
          initial: 'getArrays',
119
          states: {
120
            getArrays: {
121
              entry: 'getArrays',
122
              on: { ARRAYS: { target: 'getChannels', actions: 'setArrays' } },
123
            },
124
            getChannels: {
125
              entry: 'getChannels',
126
              on: { CHANNELS: { target: 'upload', actions: 'setChannels' } },
127
            },
128
            upload: {
129
              invoke: {
130
                src: upload,
131
                onDone: 'uploaded',
132
              },
133
            },
134
            uploaded: { type: 'final' },
135
          },
136
          onDone: 'idle',
137
        },
138
        downloading: {
139
          initial: 'getArrays',
140
          states: {
141
            getArrays: {
142
              entry: 'getArrays',
143
              on: { ARRAYS: { target: 'getChannels', actions: 'setArrays' } },
144
            },
145
            getChannels: {
146
              entry: 'getChannels',
147
              on: { CHANNELS: { target: 'download', actions: 'setChannels' } },
148
            },
149
            download: {
150
              invoke: {
151
                src: download,
152
                onDone: { target: 'done', actions: 'download' },
153
                onError: { target: 'done', actions: (ctx, evt) => console.log(ctx, evt) },
×
154
              },
155
            },
156
            done: { type: 'final' },
157
          },
158
          onDone: 'idle',
159
        },
160
      },
161
    },
162
    {
163
      actions: {
164
        download: (ctx, event) => {
165
          const url = event.data;
×
166
          const link = document.createElement('a');
×
167
          link.href = url;
×
168
          link.download = `${ctx.projectId}.zip`;
×
169
          link.click();
×
170
        },
171
        getArrays: send('GET_ARRAYS', { to: 'arrays' }),
172
        getChannels: send('GET_CHANNELS', { to: 'raw' }),
UNCOV
173
        setArrays: assign((ctx, evt) => ({
×
174
          raw: evt.raw,
175
          labeled: evt.labeled,
176
        })),
NEW
177
        setChannels: assign((_, evt) => ({
×
178
          channels: evt.channels,
179
        })),
180
        setCells: assign({ cells: (_, evt) => evt.cells }),
26✔
181
        setCellTypes: assign({ cellTypes: (_, evt) => evt.cellTypes }),
25✔
182
        setDivisions: assign({ divisions: (_, evt) => evt.divisions }),
13✔
183
        setSpots: assign({ spots: (_, evt) => evt.spots }),
13✔
184
      },
185
    }
186
  );
187

188
export default createExportMachine;
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