Files changed (1) hide show
  1. app.py +844 -324
app.py CHANGED
@@ -1,399 +1,919 @@
1
- import gradio as gr
2
- import numpy as np
3
- import os
4
- import random
5
  import utils
6
- from channel_mapping import mapping, reorder_data
7
-
8
- import mne
9
- from mne.channels import read_custom_montage
10
-
11
- quickstart = """
12
- # Quickstart
13
 
14
- ## 1. Channel mapping
 
 
15
 
16
- ### Raw data
17
- 1. The data need to be a two-dimensional array (channel, timepoint).
18
- 2. Make sure you have **resampled** your data to **256 Hz**.
19
- 3. Upload your EEG data in `.csv` format.
20
 
21
- ### Channel locations
22
- Upload your data's channel locations in `.loc` format, which can be obtained using **EEGLAB**.
23
- >If you cannot obtain it, we recommend you to download the standard montage <a href="">here</a>. If the channels in those files doesn't match yours, you can use **EEGLAB** to modify them to your needed montage.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- ### Imputation
26
- The models was trained using the EEG signals of 30 channels, including: `Fp1, Fp2, F7, F3, Fz, F4, F8, FT7, FC3, FCz, FC4, FT8, T7, C3, Cz, C4, T8, TP7, CP3, CPz, CP4, TP8, P7, P3, Pz, P4, P8, O1, Oz, O2`.
27
- We expect your input data to include these channels as well.
28
- If your data doesn't contain all of the mentioned channels, there are 3 imputation ways you can choose from:
29
 
30
- <u>Manually</u>:
31
- - **mean**: select the channels you wish to use for imputing the required one, and we will average their values. If you select nothing, zeros will be imputed. For example, you didn't have **FCZ** and you choose **FC1, FC2, FZ, CZ** to impute it(depending on the channels you have), we will compute the mean of these 4 channels and assign this new value to **FCZ**.
 
32
 
33
- <u>Automatically</u>:
34
- Firstly, we will attempt to find neighboring channel to use as alternative. For instance, if the required channel is **FC3** but you only have **FC1**, we will use it as a replacement for **FC3**.
35
- Then, depending on the **Imputation** way you chose, we will:
36
- - **zero**: fill the missing channels with zeros.
37
- - **adjacent**: fill the missing channels using neighboring channels which are located closer to the center. For example, if the required channel is **FC3** but you only have **F3, C3**, then we will choose **C3** as the imputing value for **FC3**.
38
- >Note: The imputed channels **need to be removed** after the data being reconstructed.
39
 
40
- ### Mapping result
41
- Once the mapping process is finished, the **template montage** and the **input montage**(with the channels choosen by the mapping function displaying their names) will be shown.
42
 
43
- ### Missing channels
44
- The channels displayed here are those for which the template didn't find suitable channels to use, and utilized **Imputation** to fill the missing values.
45
- Therefore, you need to
46
- <span style="color:red">**remove these channels**</span>
47
- after you download the denoised data.
48
 
49
- ### Template location file
50
- You need to use this as the **new location file** for the denoised data.
 
 
 
51
 
52
- ## 2. Decode data
 
 
 
 
 
53
 
54
- ### Model
55
- Select the model you want to use.
56
- The detailed description of the models can be found in other pages.
 
57
 
 
 
 
58
  """
59
 
60
  icunet = """
61
- # IC-U-Net
62
  ### Abstract
63
  Electroencephalography (EEG) signals are often contaminated with artifacts. It is imperative to develop a practical and reliable artifact removal method to prevent the misinterpretation of neural signals and the underperformance of brain–computer interfaces. Based on the U-Net architecture, we developed a new artifact removal model, IC-U-Net, for removing pervasive EEG artifacts and reconstructing brain signals. IC-U-Net was trained using mixtures of brain and non-brain components decomposed by independent component analysis. It uses an ensemble of loss functions to model complex signal fluctuations in EEG recordings. The effectiveness of the proposed method in recovering brain activities and removing various artifacts (e.g., eye blinks/movements, muscle activities, and line/channel noise) was demonstrated in a simulation study and four real-world EEG experiments. IC-U-Net can reconstruct a multi-channel EEG signal and is applicable to most artifact types, offering a promising end-to-end solution for automatically removing artifacts from EEG recordings. It also meets the increasing need to image natural brain dynamics in a mobile setting.
64
  """
65
- unetpp = """
66
- # IC-U-Net++
67
- ### Abstract
68
- Electroencephalographic (EEG) data is considered contaminated with various types of artifacts. Deep learning has been successfully applied to developing EEG artifact removal techniques to increase the signal-to-noise ratio (SNR) and enhance brain-computer interface performance. Recently, our research team has proposed an end-to-end UNet-based EEG artifact removal technique, IC-U-Net, which can reconstruct signals against various artifacts. However, this model suffers from being prone to overfitting with a limited training dataset size and demanding a high computational cost. To address these issues, this study attempted to leverage the architecture of UNet++ to improve the practicability of IC-U-Net by introducing dense skip connections in the encoder-decoder architecture. Results showed that this proposed model obtained superior SNR to the original model with half the number of parameters. Also, this proposed model achieved comparable convergency using a quarter of the training data size.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  """
70
 
71
- chkbox_js = """
72
- (state_json) => {
73
- state_json = JSON.parse(JSON.stringify(state_json));
74
- if(state_json.state == "finished") return;
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- document.querySelector("#chs-chkbox>div:nth-of-type(2)").style.cssText = `
 
77
  position: relative;
78
- width: 560px;
79
- height: 560px;
80
- background: url("file=${state_json.files.raw_montage}");
 
 
81
  `;
82
-
83
- let all_chkbox = document.querySelectorAll("#chs-chkbox> div:nth-of-type(2)> label");
84
- all_chkbox = Array.apply(null, all_chkbox);
85
 
86
- all_chkbox.forEach((item, index) => {
87
- let channel = state_json.inputByIndex[index];
88
- let left = state_json.inputByName[channel].css_position[0];
89
- let bottom = state_json.inputByName[channel].css_position[1];
90
- //console.log(`left: ${left}, bottom: ${bottom}`);
 
 
91
 
92
- item.style.cssText = `
93
- position: absolute;
94
- left: ${left};
95
- bottom: ${bottom};
96
- `;
97
- item.className = "";
98
- item.querySelector("span").innerText = "";
99
  });
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
102
  """
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- with gr.Blocks() as demo:
106
-
107
- state_json = gr.JSON(elem_id="state", visible=False)
 
 
108
 
 
109
  with gr.Row():
110
- gr.Markdown(
111
- """
112
-
113
- """
114
- )
115
- with gr.Row():
116
- with gr.Column():
117
- gr.Markdown(
118
- """
119
- # 1.Channel Mapping
120
- """
121
- )
122
  with gr.Row():
123
- in_raw_data = gr.File(label="Raw data (.csv)", file_types=[".csv"])
124
- in_raw_loc = gr.File(label="Channel locations (.loc, .locs)", file_types=[".loc", "locs"])
 
 
 
125
  with gr.Row():
126
- in_fill_mode = gr.Dropdown(choices=["zero",
127
- ("adjacent channel", "adjacent"),
128
- ("mean (manually select channels)", "mean")],
129
- value="zero",
130
- label="Imputation",
131
- scale=2)
132
- map_btn = gr.Button("Mapping", scale=1)
133
- channels_json = gr.JSON(visible=False)
134
- res_md = gr.Markdown(
135
- """
136
- ### Mapping result:
137
- """,
138
- visible=False
139
- )
140
  with gr.Row():
141
- tpl_montage = gr.Image("./template_montage.png", label="Template montage", visible=False)
142
- map_montage = gr.Image(label="Choosen channels", visible=False)
143
- chs_chkbox = gr.CheckboxGroup(elem_id="chs-chkbox", label="", visible=False)
144
- next_btn = gr.Button("Next", interactive=False, visible=False)
145
- miss_txtbox = gr.Textbox(label="Missing channels", visible=False)
146
- tpl_loc_file = gr.File("./template_chanlocs.loc", show_label=False, visible=False)
147
- with gr.Column():
148
- gr.Markdown(
149
- """
150
- # 2.Decode Data
151
- """
152
- )
153
  with gr.Row():
154
- in_model_name = gr.Dropdown(choices=["IC-U-Net", "IC-U-Net++", "IC-U-Net-Attn", "ART", "(mapped data)"],
155
- value="IC-U-Net",
156
- label="Model",
157
- scale=2)
158
- run_btn = gr.Button(scale=1, interactive=False)
159
- out_denoised_data = gr.File(label="Denoised data")
160
-
161
-
 
 
 
 
 
 
 
 
 
162
  with gr.Row():
 
 
163
  with gr.Tab("ART"):
164
  gr.Markdown()
165
  with gr.Tab("IC-U-Net"):
166
  gr.Markdown(icunet)
167
  with gr.Tab("IC-U-Net++"):
168
- gr.Markdown(unetpp)
169
- with gr.Tab("IC-U-Net-Att"):
170
  gr.Markdown()
171
- with gr.Tab("QuickStart"):
172
- gr.Markdown(quickstart)
173
-
174
- #demo.load(js=js)
 
 
 
175
 
176
- def reset_layout(raw_data):
177
- # establish temp folder
178
- filepath = os.path.dirname(str(raw_data))
179
- try:
180
- os.mkdir(filepath+"/temp_data/")
181
- except OSError as e:
182
- utils.dataDelete(filepath+"/temp_data/")
183
- os.mkdir(filepath+"/temp_data/")
184
- #print(e)
185
- state_obj = {
186
- "filepath": filepath+"/temp_data/",
187
- "files": {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  }
189
- return {state_json : state_obj,
190
- chs_chkbox : gr.CheckboxGroup(choices=[], value=[], label="", visible=False), # choices, value ???
191
- next_btn : gr.Button("Next", interactive=False, visible=False),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  run_btn : gr.Button(interactive=False),
193
- tpl_montage : gr.Image(visible=False),
194
- map_montage : gr.Image(value=None, visible=False),
195
- miss_txtbox : gr.Textbox(visible=False),
196
- res_md : gr.Markdown(visible=False),
197
- tpl_loc_file : gr.File(visible=False)}
198
-
199
- def mapping_result(state_obj, channels_obj, raw_data, fill_mode):
200
- state_obj.update(channels_obj)
 
 
 
201
 
202
- if fill_mode=="mean" and channels_obj["missingChannelsIndex"]!=[]:
203
- state_obj.update({
204
- "state" : "initializing",
205
- "fillingCount" : 0,
206
- "totalFillingNum" : len(channels_obj["missingChannelsIndex"])-1
207
- })
208
- #print("Missing channels:", state_obj["missingChannelsIndex"])
209
- return {state_json : state_obj,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  next_btn : gr.Button(visible=True)}
211
- else:
212
- reorder_data(raw_data, channels_obj["newOrder"], fill_mode, state_obj)
213
-
214
- missing_channels = [state_obj["templateByIndex"][idx] for idx in state_obj["missingChannelsIndex"]]
215
- missing_channels = ', '.join(missing_channels)
216
-
217
- state_obj.update({
218
- "state" : "finished",
219
- #"fillingCount" : -1,
220
- #"totalFillingNum" : -1
221
- })
222
- return {state_json : state_obj,
223
- res_md : gr.Markdown(visible=True),
224
- miss_txtbox : gr.Textbox(value=missing_channels, visible=True),
225
- tpl_loc_file : gr.File(visible=True),
226
- run_btn : gr.Button(interactive=True)}
227
-
228
- def show_montage(state_obj, raw_loc):
229
- filepath = state_obj["filepath"]
230
- raw_montage = read_custom_montage(raw_loc)
231
 
232
- # convert all channel names to uppercase
233
- for i in range(len(raw_montage.ch_names)):
234
- channel = raw_montage.ch_names[i]
235
- raw_montage.rename_channels({channel: str.upper(channel)})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
- if state_obj["state"] == "initializing":
238
- filename = filepath+"raw_montage_"+str(random.randint(1,10000))+".png"
239
- state_obj["files"]["raw_montage"] = filename
240
- raw_fig = raw_montage.plot()
241
- raw_fig.set_size_inches(5.6, 5.6)
242
- raw_fig.savefig(filename, pad_inches=0)
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- return {state_json : state_obj}#,
245
- #tpl_montage : gr.Image(visible=True),
246
- #in_montage : gr.Image(value=filename, visible=True),
247
- #map_montage : gr.Image(visible=False)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
- elif state_obj["state"] == "finished":
250
- # didn't find any way to hide the dark points...
251
- # tmp
252
- filename = filepath+"mapped_montage_"+str(random.randint(1,10000))+".png"
253
- state_obj["files"]["map_montage"] = filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
- show_names= []
256
- for channel in state_obj["inputByName"]:
257
- if state_obj["inputByName"][channel]["used"]:
258
- if channel=='CZ' and state_obj["CZImputed"]:
259
- continue
260
- show_names.append(channel)
261
- mapped_fig = raw_montage.plot(show_names=show_names)
262
- mapped_fig.set_size_inches(5.6, 5.6)
263
- mapped_fig.savefig(filename, pad_inches=0)
264
 
265
- return {state_json : state_obj,
266
- tpl_montage : gr.Image(visible=True),
267
- map_montage : gr.Image(value=filename, visible=True)}
268
-
269
- elif state_obj["state"] == "selecting":
270
- # update in_montage here ?
271
- #return {in_montage : gr.Image()}
272
- return {state_json : state_obj}
273
-
274
- def generate_chkbox(state_obj):
275
- if state_obj["state"] == "initializing":
276
- in_channels = [channel for channel in state_obj["inputByName"]]
277
- state_obj["state"] = "selecting"
278
 
279
- first_idx = state_obj["missingChannelsIndex"][0]
280
- first_name = state_obj["templateByIndex"][first_idx]
281
- chkbox_label = first_name+' (1/'+str(state_obj["totalFillingNum"]+1)+')'
282
- return {state_json : state_obj,
283
- chs_chkbox : gr.CheckboxGroup(choices=in_channels, label=chkbox_label, visible=True),
284
- next_btn : gr.Button(interactive=True)}
285
- else:
286
- return {state_json : state_obj}
287
-
 
 
 
 
 
 
 
 
 
 
 
288
 
 
 
 
289
  map_btn.click(
290
- fn = reset_layout,
291
- inputs = in_raw_data,
292
- outputs = [state_json, chs_chkbox, next_btn, run_btn, tpl_montage, map_montage, miss_txtbox,
293
- res_md, tpl_loc_file]
294
-
295
  ).success(
296
- fn = mapping,
297
- inputs = [in_raw_data, in_raw_loc, in_fill_mode],
298
- outputs = channels_json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- ).success(
301
- fn = mapping_result,
302
- inputs = [state_json, channels_json, in_raw_data, in_fill_mode],
303
- outputs = [state_json, chs_chkbox, next_btn, miss_txtbox, res_md, tpl_loc_file, run_btn]
304
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  ).success(
306
- fn = show_montage,
307
- inputs = [state_json, in_raw_loc],
308
- outputs = [state_json, tpl_montage, map_montage]
 
 
 
 
 
 
 
 
309
 
310
- ).success(
311
- fn = generate_chkbox,
312
- inputs = state_json,
313
- outputs = [state_json, chs_chkbox, next_btn]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  ).success(
315
  fn = None,
316
- js = chkbox_js,
317
- inputs = state_json,
318
  outputs = []
319
  )
320
-
321
-
322
- def check_next(state_obj, selected, raw_data, fill_mode):
323
- if state_obj["state"] == "selecting":
324
-
325
- # save info before clicking on next_btn
326
- prev_target_idx = state_obj["missingChannelsIndex"][state_obj["fillingCount"]]
327
- prev_target_name = state_obj["templateByIndex"][prev_target_idx]
328
-
329
- selected_idx = [state_obj["inputByName"][channel]["index"] for channel in selected]
330
- state_obj["newOrder"][prev_target_idx] = selected_idx
331
-
332
- if len(selected)==1 and state_obj["inputByName"][selected[0]]["used"]==False:
333
- state_obj["inputByName"][selected[0]]["used"] = True
334
- state_obj["missingChannelsIndex"][state_obj["fillingCount"]] = -1
335
-
336
- print('Selection for missing channel "{}"({}): {}'.format(prev_target_name, prev_target_idx, selected))
337
-
338
- # update next round
339
- state_obj["fillingCount"] += 1
340
- if state_obj["fillingCount"] <= state_obj["totalFillingNum"]:
341
- target_idx = state_obj["missingChannelsIndex"][state_obj["fillingCount"]]
342
- target_name = state_obj["templateByIndex"][target_idx]
343
- chkbox_label = target_name+' ('+str(state_obj["fillingCount"]+1)+'/'+str(state_obj["totalFillingNum"]+1)+')'
344
- btn_label = "Submit" if state_obj["fillingCount"]==state_obj["totalFillingNum"] else "Next"
345
-
346
- return {state_json : state_obj,
347
- chs_chkbox : gr.CheckboxGroup(value=[], label=chkbox_label),
348
- next_btn : gr.Button(btn_label)}
349
- else:
350
- state_obj["state"] = "finished"
351
- reorder_data(raw_data, state_obj["newOrder"], fill_mode, state_obj)
352
-
353
- missing_channels = []
354
- for idx in state_obj["missingChannelsIndex"]:
355
- if idx != -1:
356
- missing_channels.append(state_obj["templateByIndex"][idx])
357
- missing_channels = ', '.join(missing_channels)
358
-
359
- return {state_json : state_obj,
360
- chs_chkbox : gr.CheckboxGroup(visible=False),
361
- next_btn : gr.Button(visible=False),
362
- res_md : gr.Markdown(visible=True),
363
- miss_txtbox : gr.Textbox(value=missing_channels, visible=True),
364
- tpl_loc_file : gr.File(visible=True),
365
- run_btn : gr.Button(interactive=True)}
366
 
367
- next_btn.click(
368
- fn = check_next,
369
- inputs = [state_json, chs_chkbox, in_raw_data, in_fill_mode],
370
- outputs = [state_json, chs_chkbox, next_btn, run_btn, res_md, miss_txtbox, tpl_loc_file]
371
-
372
  ).success(
373
- fn = show_montage,
374
- inputs = [state_json, in_raw_loc],
375
- outputs = [state_json, tpl_montage, map_montage]
 
376
  )
377
-
378
-
379
- @run_btn.click(inputs=[state_json, in_raw_data, in_model_name], outputs=out_denoised_data)
380
- def run_model(state_obj, raw_file, model_name):
381
- filepath = state_obj["filepath"]
382
-
383
- input_name = os.path.basename(str(raw_file))
384
- output_name = os.path.splitext(input_name)[0]+'_'+model_name+'.csv'
 
 
 
 
 
 
 
 
 
 
 
385
 
386
- if model_name == "(mapped data)":
387
- return filepath + 'mapped.csv'
388
-
389
- # step1: Data preprocessing
390
- total_file_num = utils.preprocessing(filepath, 'mapped.csv', 256)
 
391
 
392
- # step2: Signal reconstruction
393
- utils.reconstruct(model_name, total_file_num, filepath, output_name)
394
 
395
- return filepath + output_name
396
-
397
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  if __name__ == "__main__":
399
- demo.launch()
 
 
 
 
 
 
1
  import utils
2
+ import app_utils
3
+ import os
4
+ import uuid
5
+ import tempfile
6
+ import gradio as gr
 
 
7
 
8
+ gradio_temp_dir = os.path.join(tempfile.gettempdir(), 'gradio')
9
+ os.makedirs(gradio_temp_dir, exist_ok=True)
10
+ os.environ['GRADIO_TEMP_DIR'] = gradio_temp_dir
11
 
 
 
 
 
12
 
13
+ title = """
14
+ <div>
15
+ <div style="display: flex; justify-content: center; text-align: center; font-size: 2rem;">
16
+ <b>Artifact Removal Transformer 🤗 Gradio Demo</b>
17
+ </div>
18
+ <br>
19
+ <div style="display: flex; justify-content: center; text-align: center;">
20
+ <p>
21
+ <b>ART: Artifact Removal Transformer for Reconstructing Noise-Free Multichannel Electroencephalographic Signals</b>
22
+ <br>
23
+ Chun-Hsiang Chuang, Kong-Yi Chang, Chih-Sheng Huang, Anne-Mei Bessas
24
+ </p>
25
+ </div>
26
+ <br>
27
+ <div style="display: flex; justify-content: center; column-gap: 4px;">
28
+ <a href='https://arxiv.org/abs/2409.07326' target='_blank'">
29
+ <img src='https://img.shields.io/badge/arXiv-paper-red'>
30
+ </a>
31
+ <a href='https://github.com/CNElab-Plus/ArtifactRemovalTransformer' target='_blank'>
32
+ <img src='https://img.shields.io/badge/GitHub-code-blue'>
33
+ </a>
34
+ <a href='https://sites.google.com/view/chchuang' target='_blank'>
35
+ <img src='https://img.shields.io/badge/CNElab-contact-9b27b1'>
36
+ </a>
37
+ </div>
38
+ </div>
39
+ """
40
 
41
+ guide = """
 
 
 
42
 
43
+ This 🤗 Gradio Demo is designed to assist you with two main tasks:
44
+ 1. **Channel Mapping**: Align your EEG channels with our template channels to ensure compatibility with our models.
45
+ 2. **EEG Artifact Removal**: Use our models—**ART**, **IC-U-Net**, **IC-U-Net++**, and **IC-U-Net-Attn**—to denoise your EEG data.
46
 
47
+ ## File Requirements and Preparation
48
+ - **Channel locations**: If you don't have the channel location file, we recommend you to download the standard montage <a href="">here</a>. If the channels in those files don't match yours, you can use **EEGLAB** to adjust them to your required montage.
49
+ - **Raw data**: Your data format must be a two-dimensional array (channels, timepoints).<br>
50
+ ❗️❗️❗️Your data must include some channels that correspond to our template channels, which include: **Fp1, Fp2, F7, F3, Fz, F4, F8, FT7, FC3, FCz, FC4, FT8, T7, C3, Cz, C4, T8, TP7, CP3, CPz, CP4, TP8, P7, P3, Pz, P4, P8, O1, Oz, O2**. At least some of them need to be present for successful mapping.<br>
51
+ ❗️❗️❗️Please remove any reference, ECG, EOG, EMG, or other non-EEG channels before uploading your files.
 
52
 
53
+ ## Step1. Channel Mapping
54
+ The following steps will guide you through the process of mapping your EEG channels to our template channels.
55
 
56
+ ### Step1-1: Initial Matching and Scaling
57
+ After clicking on `Map` button, we will first match your channels to our template channels by their names. Using the matched channels as reference points, we will apply Thin Plate Spline (TPS) transformation to scale your montage to align with our template's dimensions. The template montage and your scaled montage will be displayed side by side for comparison. Channels that do not have a match in our template will be **highlighted in red**.
58
+ - If your data includes all the 30 template channels, you will be directed to **Mapping Result**.
59
+ - If your data doesn't include all the 30 template channels and you have some channels that do not match the template, you will be directed to **Step2**.
60
+ - If all your channels are included in our template but you have fewer than 30 channels, you will be directed to **Step3**.
61
 
62
+ ### Step1-2: Forwarding Unmatched Channels
63
+ In this step, you will handle the channels that didn't have a direct match with our template, by manually assigning them to the template channels that are still empty, ensuring the most efficient use of your data.<br>
64
+ Your unmatched channels, previously highlighted in red, will be shown on your montage with a radio button displayed above each. You can choose to forward the data from these unmatched channels to the empty template channels. The interface will display each empty template channel in sequence, allowing you to select which of your unmatched channels to forward.
65
+ - If all empty template channels are filled by your selections, you will be directed to **Mapping Result**.
66
+ - If there are still empty template channels remaining, you will be directed to **Step3**.
67
 
68
+ ### Step1-3: Filling Remaining Template Channels
69
+ To run the models successfully, we need to ensure that all 30 template channels are filled. In this step, you are required to select one of the methods provided below to fill the remaining empty template channels:
70
+ - **Mean** method: Each empty template channel is filled with the average value of data from the nearest input channels. By default, the 4 closest input channels (determined after aligning your montage to the template's scale using TPS) are selected for this averaging process. On the interface, you will see checkboxes displayed above each of your channel. The 4 nearest channels are pre-selected by default for each empty template channel, but you can modify these selections as needed. If you uncheck all the checkboxes for a particular template channel, it will be filled with zeros.
71
+ - **Zero** method: All empty template channels are filled with zeros.<br>
72
+ Choose the method that best suits your needs, considering that the model's performance may vary depending on the method used.<br>
73
+ Once all template channels are filled, you will be directed to **Mapping Result**.
74
 
75
+ ### Mapping Result
76
+ After completing the previous steps, your channels will be aligned with the template channels required by our models.
77
+ - In case there are still some channels that haven't been mapped, we will automatically batch and optimally assign them to the template. This ensures that even channels not initially mapped will still be included in the final result.
78
+ - Once the mapping process is completed, a JSON file containing the mapping result will be generated. This file is necessary only if you plan to run the models using the source code; otherwise, you can ignore it.
79
 
80
+ ## Step2. Data Denoising
81
+ After uploading your EEG data and clicking on `Run` button, we will process your data based on the mapping result.<br>
82
+ - If necessary, your data will be divided into batches and run the models on each batch sequentially, ensuring that all channels are properly processed.
83
  """
84
 
85
  icunet = """
86
+ ## IC-U-Net
87
  ### Abstract
88
  Electroencephalography (EEG) signals are often contaminated with artifacts. It is imperative to develop a practical and reliable artifact removal method to prevent the misinterpretation of neural signals and the underperformance of brain–computer interfaces. Based on the U-Net architecture, we developed a new artifact removal model, IC-U-Net, for removing pervasive EEG artifacts and reconstructing brain signals. IC-U-Net was trained using mixtures of brain and non-brain components decomposed by independent component analysis. It uses an ensemble of loss functions to model complex signal fluctuations in EEG recordings. The effectiveness of the proposed method in recovering brain activities and removing various artifacts (e.g., eye blinks/movements, muscle activities, and line/channel noise) was demonstrated in a simulation study and four real-world EEG experiments. IC-U-Net can reconstruct a multi-channel EEG signal and is applicable to most artifact types, offering a promising end-to-end solution for automatically removing artifacts from EEG recordings. It also meets the increasing need to image natural brain dynamics in a mobile setting.
89
  """
90
+
91
+ js = """
92
+ () => {
93
+ const styleSheet = document.styleSheets[0];
94
+ styleSheet.insertRule(`
95
+ .channel-box {
96
+ position: absolute;
97
+ z-index: 2;
98
+ width: 2.5%;
99
+ height: 2.5%;
100
+ transform: translate(-50%, 50%);
101
+ }
102
+ `, styleSheet.cssRules.length);
103
+ styleSheet.insertRule(`
104
+ .channel-input {
105
+ display: block !important;
106
+ width: 100% !important;
107
+ height: 100% !important;
108
+ }
109
+ `, styleSheet.cssRules.length);
110
+ }
111
  """
112
 
113
+ init_js = """
114
+ (stage1_info, channel_info) => {
115
+ stage1_info = JSON.parse(JSON.stringify(stage1_info));
116
+ channel_info = JSON.parse(JSON.stringify(channel_info));
117
+
118
+ let selector, attribute;
119
+ if(stage1_info.state == "step2-selecting"){
120
+ selector = "#radio-group > div:nth-of-type(2)";
121
+ attribute = "value";
122
+ }else if(stage1_info.state == "step3-2-selecting"){
123
+ selector = "#chkbox-group > div:nth-of-type(2)";
124
+ attribute = "name";
125
+ }else return;
126
+
127
+ const div = document.querySelector(selector);
128
 
129
+ // add figure of the input montage
130
+ div.style.cssText = `
131
  position: relative;
132
+ width: 100%;
133
+ aspect-ratio: 1;
134
+ background-image: url("file=${stage1_info.fileNames.originalMontage}");
135
+ background-position: left bottom;
136
+ background-size: 100%;
137
  `;
 
 
 
138
 
139
+ // move the radios/checkboxes
140
+ let name, left, bottom;
141
+ const elements = div.querySelectorAll(":scope > label");
142
+ Array.from(elements).forEach( el => {
143
+ name = el.querySelector(":scope > input").getAttribute(attribute);
144
+ left = channel_info.inputDict[name].css_position[0];
145
+ bottom = channel_info.inputDict[name].css_position[1];
146
 
147
+ el.className = "channel-box";
148
+ el.style.cssText = `left: ${left}%; bottom: ${bottom}%;`;
149
+ el.querySelector(":scope > input").classList.add("channel-input");
150
+ el.querySelector(":scope > span").innerText = "";
 
 
 
151
  });
152
 
153
+ // add indication for the first empty tpl_channel
154
+ name = stage1_info.emptyTemplate[0];
155
+ left = channel_info.templateDict[name].css_position[0];
156
+ bottom = channel_info.templateDict[name].css_position[1];
157
+ const dotRule = `
158
+ ${selector}::before {
159
+ content: "";
160
+ position: absolute;
161
+ left: ${left}%;
162
+ bottom: ${bottom}%;
163
+ width: 2%;
164
+ height: 2%;
165
+ border-radius: 50%;
166
+ background-color: red;
167
+ }
168
+ `;
169
+ const textRule = `
170
+ ${selector}::after {
171
+ content: "${name}";
172
+ position: absolute;
173
+ z-index: 1;
174
+ left: ${left+2.7}%;
175
+ bottom: ${bottom}%;
176
+ font-size: 1em;
177
+ font-weight: 900;
178
+ color: red;
179
+ }
180
+ `;
181
+ // check if indicator already exist
182
+ const styleSheet = document.styleSheets[0];
183
+ for(let i=0; i<styleSheet.cssRules.length; i++){
184
+ let tmp = styleSheet.cssRules[i].selectorText;
185
+ if(tmp==selector+"::before" || tmp==selector+"::after"){
186
+ styleSheet.deleteRule(i);
187
+ i--;
188
+ }
189
+ }
190
+ styleSheet.insertRule(dotRule, styleSheet.cssRules.length);
191
+ styleSheet.insertRule(textRule, styleSheet.cssRules.length);
192
  }
193
  """
194
 
195
+ update_js = """
196
+ (stage1_info, channel_info) => {
197
+ stage1_info = JSON.parse(JSON.stringify(stage1_info));
198
+ channel_info = JSON.parse(JSON.stringify(channel_info));
199
+
200
+ let selector;
201
+ let cnt, name, left, bottom;
202
+ if(stage1_info.state == "step2-selecting"){
203
+ selector = "#radio-group > div:nth-of-type(2)";
204
+ cnt = stage1_info.step2.count;
205
+
206
+ // update the radios
207
+ const elements = document.querySelectorAll(selector+" > label");
208
+ Array.from(elements).forEach( el => {
209
+ name = el.querySelector(":scope > input").value;
210
+ left = channel_info.inputDict[name].css_position[0];
211
+ bottom = channel_info.inputDict[name].css_position[1];
212
+ el.style.cssText = `left: ${left}%; bottom: ${bottom}%;`;
213
+ });
214
+ }else if(stage1_info.state == "step3-2-selecting"){
215
+ selector = "#chkbox-group > div:nth-of-type(2)";
216
+ cnt = stage1_info.step3.count;
217
+ }else return;
218
+
219
+ // update the indication
220
+ name = stage1_info.emptyTemplate[cnt-1];
221
+ left = channel_info.templateDict[name].css_position[0];
222
+ bottom = channel_info.templateDict[name].css_position[1];
223
+ const dotRule = `
224
+ ${selector}::before {
225
+ content: "";
226
+ position: absolute;
227
+ left: ${left}%;
228
+ bottom: ${bottom}%;
229
+ width: 2%;
230
+ height: 2%;
231
+ border-radius: 50%;
232
+ background-color: red;
233
+ }
234
+ `;
235
+ const textRule = `
236
+ ${selector}::after {
237
+ content: "${name}";
238
+ position: absolute;
239
+ z-index: 1;
240
+ left: ${left+2.7}%;
241
+ bottom: ${bottom}%;
242
+ font-size: 1em;
243
+ font-weight: 900;
244
+ color: red;
245
+ }
246
+ `;
247
+
248
+ // update the rules
249
+ const styleSheet = document.styleSheets[0];
250
+ for(let i=0; i<styleSheet.cssRules.length; i++){
251
+ let tmp = styleSheet.cssRules[i].selectorText;
252
+ if(tmp==selector+"::before" || tmp==selector+"::after"){
253
+ styleSheet.deleteRule(i);
254
+ i--;
255
+ }
256
+ }
257
+ styleSheet.insertRule(dotRule, styleSheet.cssRules.length);
258
+ styleSheet.insertRule(textRule, styleSheet.cssRules.length);
259
+ }
260
+ """
261
 
262
+ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
263
+ session_dir = gr.State("")
264
+ stage1_json = gr.JSON({}, visible=False)
265
+ stage2_json = gr.JSON({}, visible=False)
266
+ channel_json = gr.JSON({}, visible=False)
267
 
268
+ gr.HTML(title)
269
  with gr.Row():
270
+
271
+ with gr.Column(variant="panel"):
272
+ gr.Markdown("## Step1. Channel Mapping")
273
+ # ---------------------input---------------------
274
+ in_loc_file = gr.File(label="Channel locations (.loc, .locs, .xyz, .sfp, .txt)",
275
+ file_types=[".loc", "locs", ".xyz", ".sfp", ".txt"])
276
+ map_btn = gr.Button("Map")
277
+ # ---------------------output--------------------
278
+ desc_md = gr.Markdown(visible=False)
279
+ out_result_file = gr.File(visible=False)
280
+ # --------------------mapping--------------------
281
+ # step1-1
282
  with gr.Row():
283
+ tpl_img = gr.Image("./template_montage.png", label="Template montage", visible=False)
284
+ mapped_img = gr.Image(label="Matching result", visible=False)
285
+ # step1-2
286
+ radio_group = gr.Radio(elem_id="radio-group", visible=False)
287
+ # step1-3
288
  with gr.Row():
289
+ in_fillmode = gr.Dropdown(choices=["mean", "zero"],
290
+ value="mean",
291
+ label="Filling method",
292
+ visible=False,
293
+ scale=2)
294
+ fillmode_btn = gr.Button("OK", visible=False, scale=1)
295
+ chkbox_group = gr.CheckboxGroup(elem_id="chkbox-group", visible=False)
296
+
 
 
 
 
 
 
297
  with gr.Row():
298
+ clear_btn = gr.Button("Clear", visible=False)
299
+ step2_btn = gr.Button("Next", visible=False)
300
+ step3_btn = gr.Button("Next", visible=False)
301
+ next_btn = gr.Button("Next step", visible=False)
302
+ # -----------------------------------------------
303
+
304
+ with gr.Column(variant="panel"):
305
+ gr.Markdown("## Step2. Data Denoising")
306
+ # ---------------------input---------------------
 
 
 
307
  with gr.Row():
308
+ in_data_file = gr.File(label="Raw data (.csv)", file_types=[".csv"])
309
+ with gr.Column():
310
+ in_samplerate = gr.Textbox(label="Sampling rate (Hz)")
311
+ in_modelname = gr.Dropdown(choices=[
312
+ ("ART", "ART"),
313
+ ("IC-U-Net", "ICUNet"),
314
+ ("IC-U-Net++", "ICUNet++"),
315
+ ("IC-U-Net-Attn", "ICUNet_attn")],
316
+ value="ART",
317
+ label="Model")
318
+ run_btn = gr.Button("Run", interactive=False)
319
+ cancel_btn = gr.Button("Cancel", visible=False)
320
+ # ---------------------output--------------------
321
+ batch_md = gr.Markdown(visible=False)
322
+ out_data_file = gr.File(label="Denoised data", visible=False)
323
+ # -----------------------------------------------
324
+
325
  with gr.Row():
326
+ with gr.Tab("User Guide"):
327
+ gr.Markdown(guide)
328
  with gr.Tab("ART"):
329
  gr.Markdown()
330
  with gr.Tab("IC-U-Net"):
331
  gr.Markdown(icunet)
332
  with gr.Tab("IC-U-Net++"):
 
 
333
  gr.Markdown()
334
+ with gr.Tab("IC-U-Net-Attn"):
335
+ gr.Markdown()
336
+
337
+ def create_dir(req: gr.Request):
338
+ os.mkdir(gradio_temp_dir+'/'+req.session_hash+'/')
339
+ return gradio_temp_dir+'/'+req.session_hash+'/'
340
+ demo.load(create_dir, inputs=[], outputs=session_dir)
341
 
342
+ # +========================================================================================+
343
+ # | Stage1: channel mapping |
344
+ # +========================================================================================+
345
+ def reset_all(rootpath, stage1_info, stage2_info, in_loc):
346
+ if in_loc == None:
347
+ gr.Warning("Please upload a file.")
348
+ stage1_info["errorFlag"] = True
349
+ return {stage1_json : stage1_info}
350
+
351
+ # delete the previous folder of Stage1, 2
352
+ if "filePath" in stage1_info:
353
+ utils.dataDelete(stage1_info["filePath"])
354
+ if "filePath" in stage2_info and stage2_info.get("state")!="stopped":
355
+ utils.dataDelete(stage2_info["filePath"])
356
+ # establish a new folder
357
+ stage1_dir = uuid.uuid4().hex + '_stage1/'
358
+ os.mkdir(rootpath + stage1_dir)
359
+
360
+ inputname = os.path.basename(str(in_loc))
361
+ outputname = inputname[:-4] + '_mapping_result.json'
362
+
363
+ stage1_info = {
364
+ "filePath" : rootpath + stage1_dir,
365
+ "fileNames" : {
366
+ "inputData" : in_loc,
367
+ "originalMontage" : rootpath + stage1_dir + 'input_montage.png',
368
+ "mappedMontage" : rootpath + stage1_dir + 'mapped_montage.png',
369
+ "outputData" : rootpath + stage1_dir + outputname
370
+ },
371
+ "state" : "step1-initializing",
372
+ "errorFlag" : False,
373
+ "step2" : {
374
+ "count" : None,
375
+ "totalNum" : None
376
+ },
377
+ "step3" : {
378
+ "count" : None,
379
+ "totalNum" : None
380
+ },
381
+ "unassignedInput" : None,
382
+ "emptyTemplate" : None,
383
+ "batch" : None,
384
+ "mappingResult" : [
385
+ {
386
+ "index" : None,
387
+ "isOriginalData" : None
388
+ #"channelUsageNum" : None
389
+ }
390
+ ]
391
  }
392
+ stage2_info = {}
393
+ channel_info = {}
394
+ return {stage1_json : stage1_info,
395
+ stage2_json : stage2_info,
396
+ channel_json : channel_info,
397
+ # --------------------Stage1-------------------------
398
+ map_btn : gr.Button(interactive=False),
399
+ desc_md : gr.Markdown("", visible=True),
400
+ out_result_file : gr.File(value=None, visible=False),
401
+ tpl_img : gr.Image(visible=False),
402
+ mapped_img : gr.Image(value=None, visible=False),
403
+ radio_group : gr.Radio(choices=[], value=[], label="", visible=False),
404
+ in_fillmode : gr.Dropdown(value="mean", visible=False),
405
+ fillmode_btn : gr.Button(visible=False),
406
+ chkbox_group : gr.CheckboxGroup(choices=[], value=[], label='', visible=False),
407
+ clear_btn : gr.Button(visible=False),
408
+ step2_btn : gr.Button(visible=False),
409
+ step3_btn : gr.Button(visible=False),
410
+ next_btn : gr.Button(visible=False),
411
+ # --------------------Stage2-------------------------
412
+ in_data_file : gr.File(value=None),
413
+ in_samplerate : gr.Textbox(value=None),
414
  run_btn : gr.Button(interactive=False),
415
+ cancel_btn : gr.Button(interactive=False),
416
+ batch_md : gr.Markdown("", visible=False),
417
+ out_data_file : gr.File(value=None, visible=False)}
418
+
419
+ # +========================================================================================+
420
+ # | step transition |
421
+ # +========================================================================================+
422
+ def init_next_step(stage1_info, channel_info, fillmode, sel_radio, sel_chkbox):
423
+ if stage1_info["errorFlag"] == True:
424
+ stage1_info["errorFlag"] = False
425
+ return {stage1_json : stage1_info}
426
 
427
+ # =======================================step1-0========================================
428
+ # step1-0 to step1-1
429
+ if stage1_info["state"] == "step1-initializing":
430
+ # match the names
431
+ stage1_info, channel_info, tpl_montage, in_montage = app_utils.match_name(stage1_info)
432
+ # scale the coordinates
433
+ channel_info = app_utils.align_coords(channel_info, tpl_montage, in_montage)
434
+ # generate and save figures of the montages
435
+ filename1 = stage1_info["fileNames"]["originalMontage"]
436
+ filename2 = stage1_info["fileNames"]["mappedMontage"]
437
+ channel_info = app_utils.save_figure(channel_info, tpl_montage, filename1, filename2)
438
+
439
+ unassigned_num = len(stage1_info["unassignedInput"])
440
+ if unassigned_num == 0:
441
+ md = """
442
+ ### Step1-1: Initial Matching and Scaling
443
+ Below is the result of mapping your channels to our template channels based on their names.
444
+ """
445
+ else:
446
+ md = """
447
+ ### Step1-1: Initial Matching and Scaling
448
+ Below is the result of mapping your channels to our template channels based on their names.<br>
449
+ - channels highlighted in red are those that do not match any template channels.
450
+ """
451
+ stage1_info["state"] = "step1-finished"
452
+ return {stage1_json : stage1_info,
453
+ channel_json : channel_info,
454
+ map_btn : gr.Button(interactive=True),
455
+ desc_md : gr.Markdown(md),
456
+ tpl_img : gr.Image(visible=True),
457
+ mapped_img : gr.Image(value=filename2, visible=True),
458
  next_btn : gr.Button(visible=True)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
 
460
+ # =======================================step1-1========================================
461
+ elif stage1_info["state"] == "step1-finished":
462
+ in_num = len(channel_info["inputNames"])
463
+ matched_num = 30 - len(stage1_info["emptyTemplate"])
464
+
465
+ # step1-1 to step1-4
466
+ if matched_num == 30:
467
+ md = """
468
+ ### Mapping Result
469
+ The mapping process has been finished.<br>
470
+ Download the file below if you plan to run the models using the source code.
471
+ """
472
+ # finalize and save the mapping result
473
+ outputname = stage1_info["fileNames"]["outputData"]
474
+ stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
475
+
476
+ stage1_info["state"] = "finished"
477
+ return {stage1_json : stage1_info,
478
+ channel_json : channel_info,
479
+ desc_md : gr.Markdown(md),
480
+ out_result_file : gr.File(outputname, visible=True),
481
+ tpl_img : gr.Image(visible=False),
482
+ mapped_img : gr.Image(visible=False),
483
+ next_btn : gr.Button(visible=False),
484
+ run_btn : gr.Button(interactive=True)}
485
+ # step1-1 to step1-2
486
+ elif in_num > matched_num:
487
+ md = """
488
+ ### Step1-2: Forwarding Unmatched Channels
489
+ Select one of your unmatched channels to forward its data to the empty template channel
490
+ currently indicated in red.
491
+ """
492
+ # initialize the progress indication label
493
+ stage1_info["step2"] = {
494
+ "count" : 1,
495
+ "totalNum" : len(stage1_info["emptyTemplate"])
496
+ }
497
+ tpl_name = stage1_info["emptyTemplate"][0]
498
+ label = '{} (1/{})'.format(tpl_name, stage1_info["step2"]["totalNum"])
499
+
500
+ stage1_info["state"] = "step2-selecting"
501
+ # determine which button to display
502
+ if stage1_info["step2"]["totalNum"] == 1:
503
+ return {stage1_json : stage1_info,
504
+ desc_md : gr.Markdown(md),
505
+ tpl_img : gr.Image(visible=False),
506
+ mapped_img : gr.Image(visible=False),
507
+ radio_group : gr.Radio(choices=stage1_info["unassignedInput"], value=[], label=label, visible=True),
508
+ clear_btn : gr.Button(visible=True)}
509
+ else:
510
+ return {stage1_json : stage1_info,
511
+ desc_md : gr.Markdown(md),
512
+ tpl_img : gr.Image(visible=False),
513
+ mapped_img : gr.Image(visible=False),
514
+ radio_group : gr.Radio(choices=stage1_info["unassignedInput"], value=[], label=label, visible=True),
515
+ clear_btn : gr.Button(visible=True),
516
+ step2_btn : gr.Button(visible=True),
517
+ next_btn : gr.Button(visible=False)}
518
+ # step1-1 to step1-3-1
519
+ elif in_num == matched_num:
520
+ md = """
521
+ ### Step1-3: Filling Remaining Template Channels
522
+ Select one of the methods provided below to fill the remaining template channels.
523
+ """
524
+ stage1_info["state"] = "step3-select-method"
525
+ return {stage1_json : stage1_info,
526
+ desc_md : gr.Markdown(md),
527
+ tpl_img : gr.Image(visible=False),
528
+ mapped_img : gr.Image(visible=False),
529
+ in_fillmode : gr.Dropdown(visible=True),
530
+ fillmode_btn : gr.Button(visible=True),
531
+ next_btn : gr.Button(visible=False)}
532
 
533
+ # =======================================step1-2========================================
534
+ elif stage1_info["state"] == "step2-selecting":
535
+
536
+ if sel_radio != []:
537
+ stage1_info["unassignedInput"].remove(sel_radio)
538
+
539
+ prev_tpl_name = stage1_info["emptyTemplate"][stage1_info["step2"]["count"]-1]
540
+ prev_tpl_idx = channel_info["templateDict"][prev_tpl_name]["index"]
541
+ sel_idx = channel_info["inputDict"][sel_radio]["index"]
542
+
543
+ stage1_info["mappingResult"][0]["index"][prev_tpl_idx] = [sel_idx]
544
+ stage1_info["mappingResult"][0]["isOriginalData"][prev_tpl_idx] = True
545
+ channel_info["templateDict"][prev_tpl_name]["matched"] = True
546
+ channel_info["inputDict"][sel_radio]["assigned"] = True
547
+
548
+ # exclude the tpl_channels filled in step1-2
549
+ stage1_info["emptyTemplate"] = app_utils.get_empty_template(channel_info["templateNames"],
550
+ channel_info["templateDict"])
551
 
552
+ # step1-2 to step1-4
553
+ if len(stage1_info["emptyTemplate"]) == 0:
554
+ md = """
555
+ ### Mapping Result
556
+ The mapping process has been finished.<br>
557
+ Download the file below if you plan to run the models using the source code.
558
+ """
559
+ outputname = stage1_info["fileNames"]["outputData"]
560
+ stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
561
+
562
+ stage1_info["state"] = "finished"
563
+ return {stage1_json : stage1_info,
564
+ channel_json : channel_info,
565
+ desc_md : gr.Markdown(md),
566
+ out_result_file : gr.File(outputname, visible=True),
567
+ radio_group : gr.Radio(visible=False),
568
+ clear_btn : gr.Button(visible=False),
569
+ next_btn : gr.Button(visible=False),
570
+ run_btn : gr.Button(interactive=True)}
571
+ # step1-2 to step1-3-1
572
+ else:
573
+ md = """
574
+ ### Step1-3: Filling Remaining Template Channels
575
+ Select one of the methods provided below to fill the remaining template channels.
576
+ """
577
+ stage1_info["state"] = "step3-select-method"
578
+ return {stage1_json : stage1_info,
579
+ channel_json : channel_info,
580
+ desc_md : gr.Markdown(md),
581
+ radio_group : gr.Radio(visible=False),
582
+ in_fillmode : gr.Dropdown(visible=True),
583
+ fillmode_btn : gr.Button(visible=True),
584
+ clear_btn : gr.Button(visible=False),
585
+ next_btn : gr.Button(visible=False)}
586
 
587
+ # ======================================step1-3-1=======================================
588
+ elif stage1_info["state"] == "step3-select-method":
589
+ # step1-3-1 to step1-4
590
+ if fillmode == "zero":
591
+ md = """
592
+ ### Mapping Result
593
+ The mapping process has been finished.<br>
594
+ Download the file below if you plan to run the models using the source code.
595
+ """
596
+ outputname = stage1_info["fileNames"]["outputData"]
597
+ stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
598
+
599
+ stage1_info["state"] = "finished"
600
+ return {stage1_json : stage1_info,
601
+ channel_json : channel_info,
602
+ desc_md : gr.Markdown(md),
603
+ out_result_file : gr.File(outputname, visible=True),
604
+ in_fillmode : gr.Dropdown(visible=False),
605
+ fillmode_btn : gr.Button(visible=False),
606
+ run_btn : gr.Button(interactive=True)}
607
+ # step1-3-1 to step1-3-2
608
+ elif fillmode == "mean":
609
+ md = """
610
+ ### Step1-3: Fill the remaining template channels
611
+ The current empty template channel, indicated in red, will be filled with the average
612
+ value of the data from the selected channels. (By default, the 4 nearest channels are pre-selected.)
613
+ """
614
+ # find the 4 nearest in_channels for each unmatched tpl_channel
615
+ stage1_info["mappingResult"][0]["index"] = app_utils.find_neighbors(
616
+ channel_info,
617
+ stage1_info["emptyTemplate"],
618
+ stage1_info["mappingResult"][0]["index"])
619
+ # initialize the progress indication label
620
+ stage1_info["step3"] = {
621
+ "count" : 1,
622
+ "totalNum" : len(stage1_info["emptyTemplate"])
623
+ }
624
+ tpl_name = stage1_info["emptyTemplate"][0]
625
+ label = '{} (1/{})'.format(tpl_name, stage1_info["step3"]["totalNum"])
626
+
627
+ tpl_idx = channel_info["templateDict"][tpl_name]["index"]
628
+ value = stage1_info["mappingResult"][0]["index"][tpl_idx]
629
+ value = [channel_info["inputNames"][i] for i in value]
630
+
631
+ stage1_info["state"] = "step3-2-selecting"
632
+ # determine which button to display
633
+ if stage1_info["step3"]["totalNum"] == 1:
634
+ return {stage1_json : stage1_info,
635
+ desc_md : gr.Markdown(md),
636
+ in_fillmode : gr.Dropdown(visible=False),
637
+ fillmode_btn : gr.Button(visible=False),
638
+ chkbox_group : gr.CheckboxGroup(choices=channel_info["inputNames"],
639
+ value=value, label=label, visible=True),
640
+ next_btn : gr.Button(visible=True)}
641
+ else:
642
+ return {stage1_json : stage1_info,
643
+ desc_md : gr.Markdown(md),
644
+ in_fillmode : gr.Dropdown(visible=False),
645
+ fillmode_btn : gr.Button(visible=False),
646
+ chkbox_group : gr.CheckboxGroup(choices=channel_info["inputNames"],
647
+ value=value, label=label, visible=True),
648
+ step3_btn : gr.Button(visible=True)}
649
+
650
+ # ======================================step1-3-2=======================================
651
+ # step1-3-2 to step1-4
652
+ elif stage1_info["state"] == "step3-2-selecting":
653
 
654
+ prev_tpl_name = stage1_info["emptyTemplate"][stage1_info["step3"]["count"]-1]
655
+ prev_tpl_idx = channel_info["templateDict"][prev_tpl_name]["index"]
656
+ sel_idx = [channel_info["inputDict"][name]["index"] for name in sel_chkbox]
657
+ stage1_info["mappingResult"][0]["index"][prev_tpl_idx] = sel_idx if sel_idx!=[] else [None]
 
 
 
 
 
658
 
659
+ md = """
660
+ ### Mapping Result
661
+ The mapping process has been finished.<br>
662
+ Download the file below if you plan to run the models using the source code.
663
+ """
664
+ outputname = stage1_info["fileNames"]["outputData"]
665
+ stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
 
 
 
 
 
 
666
 
667
+ stage1_info["state"] = "finished"
668
+ return {stage1_json : stage1_info,
669
+ channel_json : channel_info,
670
+ desc_md : gr.Markdown(md),
671
+ out_result_file : gr.File(outputname, visible=True),
672
+ chkbox_group : gr.CheckboxGroup(visible=False),
673
+ next_btn : gr.Button(visible=False),
674
+ run_btn : gr.Button(interactive=True)}
675
+
676
+ next_btn.click(
677
+ fn = init_next_step,
678
+ inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
679
+ outputs = [stage1_json, channel_json, desc_md, out_result_file, tpl_img, mapped_img, radio_group,
680
+ in_fillmode, fillmode_btn, chkbox_group, clear_btn, step2_btn, step3_btn, next_btn, run_btn]
681
+ ).success(
682
+ fn = None,
683
+ js = init_js,
684
+ inputs = [stage1_json, channel_json],
685
+ outputs = []
686
+ )
687
 
688
+ # +========================================================================================+
689
+ # | Stage1-step0 |
690
+ # +========================================================================================+
691
  map_btn.click(
692
+ fn = reset_all,
693
+ inputs = [session_dir, stage1_json, stage2_json, in_loc_file],
694
+ outputs = [stage1_json, stage2_json, channel_json, map_btn, desc_md, out_result_file, tpl_img, mapped_img,
695
+ radio_group, in_fillmode, fillmode_btn, chkbox_group, clear_btn, step2_btn, step3_btn, next_btn,
696
+ in_data_file, in_samplerate, run_btn, cancel_btn, batch_md, out_data_file]
697
  ).success(
698
+ fn = init_next_step,
699
+ inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
700
+ outputs = [stage1_json, channel_json, map_btn, desc_md, tpl_img, mapped_img, next_btn]
701
+ )
702
+
703
+ # +========================================================================================+
704
+ # | Stage1-step2 |
705
+ # +========================================================================================+
706
+ @radio_group.select(inputs = stage1_json, outputs = [step2_btn, next_btn])
707
+ def determine_button(stage1_info):
708
+ if len(stage1_info["unassignedInput"]) == 1:
709
+ return {step2_btn : gr.Button(visible=False),
710
+ next_btn : gr.Button(visible=True)}
711
+ else:
712
+ return {step2_btn : gr.Button()}
713
+ # clear the selected value and reset the buttons
714
+ @clear_btn.click(inputs = stage1_json, outputs = [radio_group, step2_btn, next_btn])
715
+ def clear_value(stage1_info):
716
+ if len(stage1_info["unassignedInput"])==1 and stage1_info["step2"]["count"]<stage1_info["step2"]["totalNum"]:
717
+ return {radio_group : gr.Radio(value=[]),
718
+ step2_btn : gr.Button(visible=True),
719
+ next_btn : gr.Button(visible=False)}
720
+ else:
721
+ return {radio_group : gr.Radio(value=[])}
722
+
723
+ def update_radio(stage1_info, channel_info, sel_name):
724
+ step2 = stage1_info["step2"]
725
+ # check if the user has selected an in_channel to forward to the previous target tpl_channel
726
+ if sel_name != []:
727
+ stage1_info["unassignedInput"].remove(sel_name)
728
+
729
+ prev_tpl_name = stage1_info["emptyTemplate"][step2["count"]-1]
730
+ prev_tpl_idx = channel_info["templateDict"][prev_tpl_name]["index"]
731
+ sel_idx = channel_info["inputDict"][sel_name]["index"]
732
+
733
+ stage1_info["mappingResult"][0]["index"][prev_tpl_idx] = [sel_idx]
734
+ stage1_info["mappingResult"][0]["isOriginalData"][prev_tpl_idx] = True
735
+ channel_info["templateDict"][prev_tpl_name]["matched"] = True
736
+ channel_info["inputDict"][sel_name]["assigned"] = True
737
 
738
+ # update the new round
739
+ step2["count"] += 1
740
+ tpl_name = stage1_info["emptyTemplate"][step2["count"]-1]
741
+ label = '{} ({}/{})'.format(tpl_name, step2["count"], step2["totalNum"])
742
 
743
+ stage1_info["step2"] = step2
744
+ # determine which button to display
745
+ if step2["count"] == step2["totalNum"]:
746
+ return {stage1_json : stage1_info,
747
+ channel_json : channel_info,
748
+ radio_group : gr.Radio(choices=stage1_info["unassignedInput"],
749
+ value=[], label=label),
750
+ step2_btn : gr.Button(visible=False),
751
+ next_btn : gr.Button(visible=True)}
752
+ else:
753
+ return {stage1_json : stage1_info,
754
+ channel_json : channel_info,
755
+ radio_group : gr.Radio(choices=stage1_info["unassignedInput"],
756
+ value=[], label=label)}
757
+ step2_btn.click(
758
+ fn = update_radio,
759
+ inputs = [stage1_json, channel_json, radio_group],
760
+ outputs = [stage1_json, channel_json, radio_group, step2_btn, next_btn]
761
  ).success(
762
+ fn = None,
763
+ js = update_js,
764
+ inputs = [stage1_json, channel_json],
765
+ outputs = []
766
+ )
767
+
768
+ # +========================================================================================+
769
+ # | Stage1-step3 |
770
+ # +========================================================================================+
771
+ def update_chkbox(stage1_info, channel_info, sel_name):
772
+ step3 = stage1_info["step3"]
773
 
774
+ prev_tpl_name = stage1_info["emptyTemplate"][step3["count"]-1]
775
+ prev_tpl_idx = channel_info["templateDict"][prev_tpl_name]["index"]
776
+ sel_idx = [channel_info["inputDict"][name]["index"] for name in sel_name]
777
+ stage1_info["mappingResult"][0]["index"][prev_tpl_idx] = sel_idx if sel_idx!=[] else [None]
778
+
779
+ # update the new round
780
+ step3["count"] += 1
781
+ tpl_name = stage1_info["emptyTemplate"][step3["count"]-1]
782
+ label = '{} ({}/{})'.format(tpl_name, step3["count"], step3["totalNum"])
783
+
784
+ tpl_idx = channel_info["templateDict"][tpl_name]["index"]
785
+ value = stage1_info["mappingResult"][0]["index"][tpl_idx]
786
+ value = [channel_info["inputNames"][i] for i in value]
787
+
788
+ stage1_info["step3"] = step3
789
+ # determine which button to display
790
+ if step3["count"] == step3["totalNum"]:
791
+ return {stage1_json : stage1_info,
792
+ chkbox_group : gr.CheckboxGroup(value=value, label=label),
793
+ step3_btn : gr.Button(visible=False),
794
+ next_btn : gr.Button(visible=True)}
795
+ else:
796
+ return {stage1_json : stage1_info,
797
+ chkbox_group : gr.CheckboxGroup(value=value, label=label)}
798
+
799
+ fillmode_btn.click(
800
+ fn = init_next_step,
801
+ inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
802
+ outputs = [stage1_json, channel_json, desc_md, out_result_file, in_fillmode, fillmode_btn,
803
+ chkbox_group, step3_btn, next_btn, run_btn]
804
  ).success(
805
  fn = None,
806
+ js = init_js,
807
+ inputs = [stage1_json, channel_json],
808
  outputs = []
809
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
810
 
811
+ step3_btn.click(
812
+ fn = update_chkbox,
813
+ inputs = [stage1_json, channel_json, chkbox_group],
814
+ outputs = [stage1_json, chkbox_group, step3_btn, next_btn]
 
815
  ).success(
816
+ fn = None,
817
+ js = update_js,
818
+ inputs = [stage1_json, channel_json],
819
+ outputs = []
820
  )
821
+
822
+ # +========================================================================================+
823
+ # | Stage2: data denoising |
824
+ # +========================================================================================+
825
+ @cancel_btn.click(inputs = stage2_json, outputs = [stage2_json, cancel_btn, batch_md])
826
+ def stop_stage2(stage2_info):
827
+ utils.dataDelete(stage2_info["filePath"])
828
+ stage2_info["state"] = "stopped"
829
+ return stage2_info, gr.Button(interactive=False), gr.Markdown(visible=False)
830
+
831
+ def reset_stage2(rootpath, stage2_info, in_data, samplerate, modelname):
832
+ if in_data==None or samplerate=="":
833
+ gr.Warning("Please upload a file and enter the sampling rate.")
834
+ stage2_info["errorFlag"] = True
835
+ return {stage2_json : stage2_info}
836
+ elif samplerate.isdigit() == False:
837
+ gr.Warning("The sampling rate must be an integer.")
838
+ stage2_info["errorFlag"] = True
839
+ return {stage2_json : stage2_info}
840
 
841
+ # delete the previous folder of Stage2
842
+ if "filePath" in stage2_info and stage2_info.get("state")=="finished":
843
+ utils.dataDelete(stage2_info["filePath"])
844
+ # establish a new folder
845
+ stage2_dir = uuid.uuid4().hex + '_stage2/'
846
+ os.mkdir(rootpath + stage2_dir)
847
 
848
+ inputname = os.path.basename(str(in_data))
849
+ outputname = modelname + '_'+inputname[:-4] + '.csv'
850
 
851
+ stage2_info = {
852
+ "filePath" : rootpath + stage2_dir,
853
+ "fileNames" : {
854
+ "inputData" : in_data,
855
+ "outputData" : rootpath + stage2_dir + outputname
856
+ },
857
+ "state" : "running",
858
+ "errorFlag" : False
859
+ }
860
+ return {stage2_json : stage2_info,
861
+ run_btn : gr.Button(visible=False),
862
+ cancel_btn : gr.Button(visible=True, interactive=True),
863
+ batch_md : gr.Markdown("", visible=True),
864
+ out_data_file : gr.File(value=None, visible=False)}
865
+
866
+ def run_model(stage1_info, stage2_info, channel_info, samplerate, modelname):
867
+ if stage2_info["errorFlag"] == True:
868
+ stage2_info["errorFlag"] = False
869
+ yield {stage2_json : stage2_info}
870
+
871
+ else:
872
+ filepath = stage2_info["filePath"]
873
+ inputname = stage2_info["fileNames"]["inputData"]
874
+ outputname = stage2_info["fileNames"]["outputData"]
875
+ channel_num = len(channel_info["inputNames"])
876
+ mapping_result = stage1_info["mappingResult"]
877
+
878
+ break_flag = False
879
+ for i in range(stage1_info["batch"]):
880
+ yield {batch_md : gr.Markdown('Running model({}/{})...'.format(i+1, stage1_info["batch"]))}
881
+ try:
882
+ # step1: Data preprocessing
883
+ preprocess_data = utils.preprocessing(filepath, inputname, int(samplerate), mapping_result[i])
884
+ # step2: Signal reconstruction
885
+ reconstructed_data = utils.reconstruct(modelname, preprocess_data, filepath, i)
886
+ # step3: Data postprocessing
887
+ utils.postprocessing(reconstructed_data, int(samplerate), outputname, mapping_result[i], i, channel_num)
888
+ except FileNotFoundError:
889
+ print('stop!!')
890
+ break_flag = True
891
+ break
892
+
893
+ if break_flag == False:
894
+ stage2_info["state"] = "finished"
895
+ yield {stage2_json : stage2_info,
896
+ run_btn : gr.Button(visible=True),
897
+ cancel_btn : gr.Button(visible=False),
898
+ batch_md : gr.Markdown(visible=False),
899
+ out_data_file : gr.File(outputname, visible=True)}
900
+ else:
901
+ yield {run_btn : gr.Button(visible=True),
902
+ cancel_btn : gr.Button(visible=False)}
903
+ run_btn.click(
904
+ fn = reset_stage2,
905
+ inputs = [session_dir, stage2_json, in_data_file, in_samplerate, in_modelname],
906
+ outputs = [stage2_json, run_btn, cancel_btn, batch_md, out_data_file]
907
+ ).success(
908
+ fn = run_model,
909
+ inputs = [stage1_json, stage2_json, channel_json, in_samplerate, in_modelname],
910
+ outputs = [stage2_json, run_btn, cancel_btn, batch_md, out_data_file]
911
+ )
912
+
913
+ def delete_dir(req: gr.Request):
914
+ utils.dataDelete(gradio_temp_dir+'/'+req.session_hash)
915
+ demo.unload(delete_dir)
916
+
917
  if __name__ == "__main__":
918
+ demo.launch()
919
+