audrey06100 commited on
Commit
18b3426
·
1 Parent(s): d077804
Files changed (2) hide show
  1. app.py +142 -112
  2. app_utils.py +14 -14
app.py CHANGED
@@ -9,46 +9,76 @@ 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
  guide = """
13
 
14
- This tool is designed to assist you with two main tasks:
15
  1. **Channel Mapping**: Align your EEG channels with our template channels to ensure compatibility with our models.
16
  2. **EEG Artifact Removal**: Use our models—**ART**, **IC-U-Net**, **IC-U-Net++**, and **IC-U-Net-Attn**—to denoise your EEG data.
17
 
18
  ## File Requirements and Preparation
19
  - **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.
20
- - **Raw data**: Your data format must be a two-dimensional array (channels, timepoints).
21
- - **Channel requirements**: 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. Additionally, please remove any reference, ECG, EOG, EMG, or other non-EEG channels before uploading your files.
22
 
23
- ## 1. Channel Mapping
24
  The following steps will guide you through the process of mapping your EEG channels to our template channels.
25
 
26
- ### Step1: Initial Matching and Scaling
27
- 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**.
28
  - If your data includes all the 30 template channels, you will be directed to **Mapping Result**.
29
  - 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**.
30
  - If all your channels are included in our template but you have fewer than 30 channels, you will be directed to **Step3**.
31
 
32
- ### Step2: Forwarding Unmatched Channels
33
- 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.
34
  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.
35
  - If all empty template channels are filled by your selections, you will be directed to **Mapping Result**.
36
  - If there are still empty template channels remaining, you will be directed to **Step3**.
37
 
38
- ### Step3: Filling Remaining Template Channels
39
  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:
40
  - **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.
41
- - **Zero** method: All empty template channels are filled with zeros.
42
- Choose the method that best suits your needs, considering that the model's performance may vary depending on the method used.
43
  Once all template channels are filled, you will be directed to **Mapping Result**.
44
 
45
  ### Mapping Result
46
  After completing the previous steps, your channels will be aligned with the template channels required by our models.
47
  - 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.
48
- - 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 <a href="https://github.com/CNElab-Plus/ArtifactRemovalTransformer">source code</a>; otherwise, you can ignore it.
49
 
50
- ## 2. Data Denoising
51
- After clicking on ``Run`` button, we will process your EEG data based on the mapping result. If necessary, your data will be divided into batches and run the models on each batch sequentially, ensuring that all channels are properly processed.
 
52
  """
53
 
54
  icunet = """
@@ -86,11 +116,11 @@ init_js = """
86
 
87
  let selector, attribute;
88
  if(stage1_info.state == "step2-selecting"){
89
- selector = "#radio-group > div:nth-of-type(2)";
90
- attribute = "value";
91
  }else if(stage1_info.state == "step3-2-selecting"){
92
- selector = "#chkbox-group > div:nth-of-type(2)";
93
- attribute = "name";
94
  }else return;
95
 
96
  const div = document.querySelector(selector);
@@ -169,10 +199,10 @@ update_js = """
169
  let selector;
170
  let cnt, name, left, bottom;
171
  if(stage1_info.state == "step2-selecting"){
172
- selector = "#radio-group > div:nth-of-type(2)";
173
- cnt = stage1_info.step2.count;
174
-
175
- // update the radios
176
  const elements = document.querySelectorAll(selector+" > label");
177
  Array.from(elements).forEach( el => {
178
  name = el.querySelector(":scope > input").value;
@@ -181,8 +211,8 @@ update_js = """
181
  el.style.cssText = `left: ${left}%; bottom: ${bottom}%;`;
182
  });
183
  }else if(stage1_info.state == "step3-2-selecting"){
184
- selector = "#chkbox-group > div:nth-of-type(2)";
185
- cnt = stage1_info.step3.count;
186
  }else return;
187
 
188
  // update the indication
@@ -234,11 +264,11 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
234
  stage2_json = gr.JSON({}, visible=False)
235
  channel_json = gr.JSON({}, visible=False)
236
 
237
- #gr.Markdown("# ...")
238
  with gr.Row():
239
 
240
  with gr.Column(variant="panel"):
241
- gr.Markdown("# 1.Channel Mapping")
242
  # ---------------------input---------------------
243
  in_loc_file = gr.File(label="Channel locations (.loc, .locs, .xyz, .sfp, .txt)",
244
  file_types=[".loc", "locs", ".xyz", ".sfp", ".txt"])
@@ -247,13 +277,13 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
247
  desc_md = gr.Markdown(visible=False)
248
  out_result_file = gr.File(visible=False)
249
  # --------------------mapping--------------------
250
- # step1
251
  with gr.Row():
252
  tpl_img = gr.Image("./template_montage.png", label="Template montage", visible=False)
253
  mapped_img = gr.Image(label="Matching result", visible=False)
254
- # step2
255
  radio_group = gr.Radio(elem_id="radio-group", visible=False)
256
- # step3
257
  with gr.Row():
258
  in_fillmode = gr.Dropdown(choices=["mean", "zero"],
259
  value="mean",
@@ -271,7 +301,7 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
271
  # -----------------------------------------------
272
 
273
  with gr.Column(variant="panel"):
274
- gr.Markdown("# 2.Data Denoising")
275
  # ---------------------input---------------------
276
  with gr.Row():
277
  in_data_file = gr.File(label="Raw data (.csv)", file_types=[".csv"])
@@ -334,29 +364,29 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
334
  "fileNames" : {
335
  "inputData" : in_loc,
336
  "originalMontage" : rootpath + stage1_dir + 'input_montage.png',
337
- "mappedMontage" : rootpath + stage1_dir + 'mapped_montage.png',
338
- "outputData" : rootpath + stage1_dir + outputname
339
  },
340
  "state" : "step1-initializing",
341
  "errorFlag" : False,
342
- "step2" : {
343
- "count" : None,
344
  "totalNum" : None
345
- },
346
- "step3" : {
347
- "count" : None,
348
  "totalNum" : None
349
- },
350
- "unassignedInput" : None,
351
- "emptyTemplate" : None,
352
- "batch" : None,
353
- "mappingResult" : [
354
- {
355
  "index" : None,
356
  "isOriginalData" : None
357
  #"channelUsageNum" : None
358
  }
359
- ]
360
  }
361
  stage2_info = {}
362
  channel_info = {}
@@ -393,8 +423,8 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
393
  stage1_info["errorFlag"] = False
394
  return {stage1_json : stage1_info}
395
 
396
- # ========================================step0=========================================
397
- # step0 to step1
398
  if stage1_info["state"] == "step1-initializing":
399
  # match the names
400
  stage1_info, channel_info, tpl_montage, in_montage = app_utils.match_name(stage1_info)
@@ -408,13 +438,13 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
408
  unassigned_num = len(stage1_info["unassignedInput"])
409
  if unassigned_num == 0:
410
  md = """
411
- ### Step1: Initial Matching and Scaling
412
  Below is the result of mapping your channels to our template channels based on their names.
413
  """
414
  else:
415
  md = """
416
- ### Step1: Initial Matching and Scaling
417
- Below is the result of mapping your channels to our template channels based on their names.
418
  - channels highlighted in red are those that do not match any template channels.
419
  """
420
  stage1_info["state"] = "step1-finished"
@@ -426,17 +456,17 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
426
  mapped_img : gr.Image(value=filename2, visible=True),
427
  next_btn : gr.Button(visible=True)}
428
 
429
- # ========================================step1=========================================
430
  elif stage1_info["state"] == "step1-finished":
431
  in_num = len(channel_info["inputNames"])
432
  matched_num = 30 - len(stage1_info["emptyTemplate"])
433
 
434
- # step1 to step4
435
  if matched_num == 30:
436
  md = """
437
  ### Mapping Result
438
- The mapping process has been finished.
439
- Download the file below if you plan to run the models using the <a href="https://github.com/CNElab-Plus/ArtifactRemovalTransformer">source code</a>.
440
  """
441
  # finalize and save the mapping result
442
  outputname = stage1_info["fileNames"]["outputData"]
@@ -451,10 +481,10 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
451
  mapped_img : gr.Image(visible=False),
452
  next_btn : gr.Button(visible=False),
453
  run_btn : gr.Button(interactive=True)}
454
- # step1 to step2
455
  elif in_num > matched_num:
456
  md = """
457
- ### Step2: Forwarding Unmatched Channels
458
  Select one of your unmatched channels to forward its data to the empty template channel
459
  currently indicated in red.
460
  """
@@ -484,10 +514,10 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
484
  clear_btn : gr.Button(visible=True),
485
  step2_btn : gr.Button(visible=True),
486
  next_btn : gr.Button(visible=False)}
487
- # step1 to step3-1
488
  elif in_num == matched_num:
489
  md = """
490
- ### Step3: Filling Remaining Template Channels
491
  Select one of the methods provided below to fill the remaining template channels.
492
  """
493
  stage1_info["state"] = "step3-select-method"
@@ -499,7 +529,7 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
499
  fillmode_btn : gr.Button(visible=True),
500
  next_btn : gr.Button(visible=False)}
501
 
502
- # ========================================step2=========================================
503
  elif stage1_info["state"] == "step2-selecting":
504
 
505
  if sel_radio != []:
@@ -514,16 +544,16 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
514
  channel_info["templateDict"][prev_tpl_name]["matched"] = True
515
  channel_info["inputDict"][sel_radio]["assigned"] = True
516
 
517
- # exclude the tpl_channels filled in step2
518
  stage1_info["emptyTemplate"] = app_utils.get_empty_template(channel_info["templateNames"],
519
  channel_info["templateDict"])
520
 
521
- # step2 to step4
522
  if len(stage1_info["emptyTemplate"]) == 0:
523
  md = """
524
  ### Mapping Result
525
- The mapping process has been finished.
526
- Download the file below if you plan to run the models using the <a href="https://github.com/CNElab-Plus/ArtifactRemovalTransformer">source code</a>.
527
  """
528
  outputname = stage1_info["fileNames"]["outputData"]
529
  stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
@@ -537,10 +567,10 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
537
  clear_btn : gr.Button(visible=False),
538
  next_btn : gr.Button(visible=False),
539
  run_btn : gr.Button(interactive=True)}
540
- # step2 to step3-1
541
  else:
542
  md = """
543
- ### Step3: Filling Remaining Template Channels
544
  Select one of the methods provided below to fill the remaining template channels.
545
  """
546
  stage1_info["state"] = "step3-select-method"
@@ -553,14 +583,14 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
553
  clear_btn : gr.Button(visible=False),
554
  next_btn : gr.Button(visible=False)}
555
 
556
- # =======================================step3-1========================================
557
  elif stage1_info["state"] == "step3-select-method":
558
- # step3-1 to step4
559
  if fillmode == "zero":
560
  md = """
561
  ### Mapping Result
562
- The mapping process has been finished.
563
- Download the file below if you plan to run the models using the <a href="https://github.com/CNElab-Plus/ArtifactRemovalTransformer">source code</a>.
564
  """
565
  outputname = stage1_info["fileNames"]["outputData"]
566
  stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
@@ -573,10 +603,10 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
573
  in_fillmode : gr.Dropdown(visible=False),
574
  fillmode_btn : gr.Button(visible=False),
575
  run_btn : gr.Button(interactive=True)}
576
- # step3-1 to step3-2
577
  elif fillmode == "mean":
578
  md = """
579
- ### Step3: Fill the remaining template channels
580
  The current empty template channel, indicated in red, will be filled with the average
581
  value of the data from the selected channels. (By default, the 4 nearest channels are pre-selected.)
582
  """
@@ -601,23 +631,23 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
601
  # determine which button to display
602
  if stage1_info["step3"]["totalNum"] == 1:
603
  return {stage1_json : stage1_info,
604
- desc_md : gr.Markdown(md),
605
  in_fillmode : gr.Dropdown(visible=False),
606
- fillmode_btn : gr.Button(visible=False),
607
- chkbox_group : gr.CheckboxGroup(choices=channel_info["inputNames"],
608
  value=value, label=label, visible=True),
609
- next_btn : gr.Button(visible=True)}
610
  else:
611
  return {stage1_json : stage1_info,
612
- desc_md : gr.Markdown(md),
613
  in_fillmode : gr.Dropdown(visible=False),
614
- fillmode_btn : gr.Button(visible=False),
615
- chkbox_group : gr.CheckboxGroup(choices=channel_info["inputNames"],
616
  value=value, label=label, visible=True),
617
- step3_btn : gr.Button(visible=True)}
618
 
619
- # =======================================step3-2========================================
620
- # step3-2 to step4
621
  elif stage1_info["state"] == "step3-2-selecting":
622
 
623
  prev_tpl_name = stage1_info["emptyTemplate"][stage1_info["step3"]["count"]-1]
@@ -627,8 +657,8 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
627
 
628
  md = """
629
  ### Mapping Result
630
- The mapping process has been finished.
631
- Download the file below if you plan to run the models using the <a href="https://github.com/CNElab-Plus/ArtifactRemovalTransformer">source code</a>.
632
  """
633
  outputname = stage1_info["fileNames"]["outputData"]
634
  stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
@@ -646,11 +676,11 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
646
  fn = init_next_step,
647
  inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
648
  outputs = [stage1_json, channel_json, desc_md, out_result_file, tpl_img, mapped_img, radio_group,
649
- in_fillmode, fillmode_btn, chkbox_group, clear_btn, step2_btn, step3_btn, next_btn, run_btn]
650
  ).success(
651
- fn = None,
652
- js = init_js,
653
- inputs = [stage1_json, channel_json],
654
  outputs = []
655
  )
656
 
@@ -658,14 +688,14 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
658
  # | Stage1-step0 |
659
  # +========================================================================================+
660
  map_btn.click(
661
- fn = reset_all,
662
- inputs = [session_dir, stage1_json, stage2_json, in_loc_file],
663
- outputs = [stage1_json, stage2_json, channel_json, map_btn, desc_md, out_result_file, tpl_img, mapped_img,
664
- radio_group, in_fillmode, fillmode_btn, chkbox_group, clear_btn, step2_btn, step3_btn, next_btn,
665
- in_data_file, in_samplerate, run_btn, cancel_btn, batch_md, out_data_file]
666
  ).success(
667
- fn = init_next_step,
668
- inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
669
  outputs = [stage1_json, channel_json, map_btn, desc_md, tpl_img, mapped_img, next_btn]
670
  )
671
 
@@ -715,22 +745,22 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
715
  return {stage1_json : stage1_info,
716
  channel_json : channel_info,
717
  radio_group : gr.Radio(choices=stage1_info["unassignedInput"],
718
- value=[], label=label),
719
  step2_btn : gr.Button(visible=False),
720
  next_btn : gr.Button(visible=True)}
721
  else:
722
  return {stage1_json : stage1_info,
723
  channel_json : channel_info,
724
  radio_group : gr.Radio(choices=stage1_info["unassignedInput"],
725
- value=[], label=label)}
726
  step2_btn.click(
727
- fn = update_radio,
728
- inputs = [stage1_json, channel_json, radio_group],
729
- outputs = [stage1_json, channel_json, radio_group, step2_btn, next_btn]
730
  ).success(
731
- fn = None,
732
- js = update_js,
733
- inputs = [stage1_json, channel_json],
734
  outputs = []
735
  )
736
 
@@ -766,25 +796,25 @@ with gr.Blocks(js=js, delete_cache=(3600, 3600)) as demo:
766
  chkbox_group : gr.CheckboxGroup(value=value, label=label)}
767
 
768
  fillmode_btn.click(
769
- fn = init_next_step,
770
  inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
771
  outputs = [stage1_json, channel_json, desc_md, out_result_file, in_fillmode, fillmode_btn,
772
- chkbox_group, step3_btn, next_btn, run_btn]
773
  ).success(
774
- fn = None,
775
- js = init_js,
776
- inputs = [stage1_json, channel_json],
777
  outputs = []
778
  )
779
 
780
  step3_btn.click(
781
- fn = update_chkbox,
782
- inputs = [stage1_json, channel_json, chkbox_group],
783
- outputs = [stage1_json, chkbox_group, step3_btn, next_btn]
784
  ).success(
785
- fn = None,
786
- js = update_js,
787
- inputs = [stage1_json, channel_json],
788
  outputs = []
789
  )
790
 
 
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/paper-arXiv-red'>
30
+ </a>
31
+ <a href='https://github.com/CNElab-Plus/ArtifactRemovalTransformer' target='_blank'>
32
+ <img src='https://img.shields.io/badge/code-GitHub-blue'>
33
+ </a>
34
+ <a href='https://sites.google.com/view/chchuang' target='_blank'>
35
+ <img src='https://img.shields.io/badge/website-CNElab-blueviolet'>
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
+ ❗️❗️❗️**Channel requirements**: 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. Additionally, please remove any reference, ECG, EOG, EMG, or other non-EEG channels before uploading your files.
51
 
52
+ ## Step1. Channel Mapping
53
  The following steps will guide you through the process of mapping your EEG channels to our template channels.
54
 
55
+ ### Step1-1: Initial Matching and Scaling
56
+ 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**.
57
  - If your data includes all the 30 template channels, you will be directed to **Mapping Result**.
58
  - 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**.
59
  - If all your channels are included in our template but you have fewer than 30 channels, you will be directed to **Step3**.
60
 
61
+ ### Step1-2: Forwarding Unmatched Channels
62
+ 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>
63
  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.
64
  - If all empty template channels are filled by your selections, you will be directed to **Mapping Result**.
65
  - If there are still empty template channels remaining, you will be directed to **Step3**.
66
 
67
+ ### Step1-3: Filling Remaining Template Channels
68
  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:
69
  - **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.
70
+ - **Zero** method: All empty template channels are filled with zeros.<br>
71
+ Choose the method that best suits your needs, considering that the model's performance may vary depending on the method used.<br>
72
  Once all template channels are filled, you will be directed to **Mapping Result**.
73
 
74
  ### Mapping Result
75
  After completing the previous steps, your channels will be aligned with the template channels required by our models.
76
  - 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.
77
+ - 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.
78
 
79
+ ## Step2. Data Denoising
80
+ After uploading your EEG data and clicking on `Run` button, we will process your data based on the mapping result.<br>
81
+ (If necessary, your data will be divided into batches and run the models on each batch sequentially, ensuring that all channels are properly processed.)
82
  """
83
 
84
  icunet = """
 
116
 
117
  let selector, attribute;
118
  if(stage1_info.state == "step2-selecting"){
119
+ selector = "#radio-group > div:nth-of-type(2)";
120
+ attribute = "value";
121
  }else if(stage1_info.state == "step3-2-selecting"){
122
+ selector = "#chkbox-group > div:nth-of-type(2)";
123
+ attribute = "name";
124
  }else return;
125
 
126
  const div = document.querySelector(selector);
 
199
  let selector;
200
  let cnt, name, left, bottom;
201
  if(stage1_info.state == "step2-selecting"){
202
+ selector = "#radio-group > div:nth-of-type(2)";
203
+ cnt = stage1_info.step2.count;
204
+
205
+ // update the radios
206
  const elements = document.querySelectorAll(selector+" > label");
207
  Array.from(elements).forEach( el => {
208
  name = el.querySelector(":scope > input").value;
 
211
  el.style.cssText = `left: ${left}%; bottom: ${bottom}%;`;
212
  });
213
  }else if(stage1_info.state == "step3-2-selecting"){
214
+ selector = "#chkbox-group > div:nth-of-type(2)";
215
+ cnt = stage1_info.step3.count;
216
  }else return;
217
 
218
  // update the indication
 
264
  stage2_json = gr.JSON({}, visible=False)
265
  channel_json = gr.JSON({}, visible=False)
266
 
267
+ gr.HTML(title)
268
  with gr.Row():
269
 
270
  with gr.Column(variant="panel"):
271
+ gr.Markdown("## Step1. Channel Mapping")
272
  # ---------------------input---------------------
273
  in_loc_file = gr.File(label="Channel locations (.loc, .locs, .xyz, .sfp, .txt)",
274
  file_types=[".loc", "locs", ".xyz", ".sfp", ".txt"])
 
277
  desc_md = gr.Markdown(visible=False)
278
  out_result_file = gr.File(visible=False)
279
  # --------------------mapping--------------------
280
+ # step1-1
281
  with gr.Row():
282
  tpl_img = gr.Image("./template_montage.png", label="Template montage", visible=False)
283
  mapped_img = gr.Image(label="Matching result", visible=False)
284
+ # step1-2
285
  radio_group = gr.Radio(elem_id="radio-group", visible=False)
286
+ # step1-3
287
  with gr.Row():
288
  in_fillmode = gr.Dropdown(choices=["mean", "zero"],
289
  value="mean",
 
301
  # -----------------------------------------------
302
 
303
  with gr.Column(variant="panel"):
304
+ gr.Markdown("## Step2. Data Denoising")
305
  # ---------------------input---------------------
306
  with gr.Row():
307
  in_data_file = gr.File(label="Raw data (.csv)", file_types=[".csv"])
 
364
  "fileNames" : {
365
  "inputData" : in_loc,
366
  "originalMontage" : rootpath + stage1_dir + 'input_montage.png',
367
+ "mappedMontage" : rootpath + stage1_dir + 'mapped_montage.png',
368
+ "outputData" : rootpath + stage1_dir + outputname
369
  },
370
  "state" : "step1-initializing",
371
  "errorFlag" : False,
372
+ "step2" : {
373
+ "count" : None,
374
  "totalNum" : None
375
+ },
376
+ "step3" : {
377
+ "count" : None,
378
  "totalNum" : None
379
+ },
380
+ "unassignedInput" : None,
381
+ "emptyTemplate" : None,
382
+ "batch" : None,
383
+ "mappingResult" : [
384
+ {
385
  "index" : None,
386
  "isOriginalData" : None
387
  #"channelUsageNum" : None
388
  }
389
+ ]
390
  }
391
  stage2_info = {}
392
  channel_info = {}
 
423
  stage1_info["errorFlag"] = False
424
  return {stage1_json : stage1_info}
425
 
426
+ # =======================================step1-0========================================
427
+ # step1-0 to step1-1
428
  if stage1_info["state"] == "step1-initializing":
429
  # match the names
430
  stage1_info, channel_info, tpl_montage, in_montage = app_utils.match_name(stage1_info)
 
438
  unassigned_num = len(stage1_info["unassignedInput"])
439
  if unassigned_num == 0:
440
  md = """
441
+ ### Step1-1: Initial Matching and Scaling
442
  Below is the result of mapping your channels to our template channels based on their names.
443
  """
444
  else:
445
  md = """
446
+ ### Step1-1: Initial Matching and Scaling
447
+ Below is the result of mapping your channels to our template channels based on their names.<br>
448
  - channels highlighted in red are those that do not match any template channels.
449
  """
450
  stage1_info["state"] = "step1-finished"
 
456
  mapped_img : gr.Image(value=filename2, visible=True),
457
  next_btn : gr.Button(visible=True)}
458
 
459
+ # =======================================step1-1========================================
460
  elif stage1_info["state"] == "step1-finished":
461
  in_num = len(channel_info["inputNames"])
462
  matched_num = 30 - len(stage1_info["emptyTemplate"])
463
 
464
+ # step1-1 to step1-4
465
  if matched_num == 30:
466
  md = """
467
  ### Mapping Result
468
+ The mapping process has been finished.<br>
469
+ Download the file below if you plan to run the models using the source code.
470
  """
471
  # finalize and save the mapping result
472
  outputname = stage1_info["fileNames"]["outputData"]
 
481
  mapped_img : gr.Image(visible=False),
482
  next_btn : gr.Button(visible=False),
483
  run_btn : gr.Button(interactive=True)}
484
+ # step1-1 to step1-2
485
  elif in_num > matched_num:
486
  md = """
487
+ ### Step1-2: Forwarding Unmatched Channels
488
  Select one of your unmatched channels to forward its data to the empty template channel
489
  currently indicated in red.
490
  """
 
514
  clear_btn : gr.Button(visible=True),
515
  step2_btn : gr.Button(visible=True),
516
  next_btn : gr.Button(visible=False)}
517
+ # step1-1 to step1-3-1
518
  elif in_num == matched_num:
519
  md = """
520
+ ### Step1-3: Filling Remaining Template Channels
521
  Select one of the methods provided below to fill the remaining template channels.
522
  """
523
  stage1_info["state"] = "step3-select-method"
 
529
  fillmode_btn : gr.Button(visible=True),
530
  next_btn : gr.Button(visible=False)}
531
 
532
+ # =======================================step1-2========================================
533
  elif stage1_info["state"] == "step2-selecting":
534
 
535
  if sel_radio != []:
 
544
  channel_info["templateDict"][prev_tpl_name]["matched"] = True
545
  channel_info["inputDict"][sel_radio]["assigned"] = True
546
 
547
+ # exclude the tpl_channels filled in step1-2
548
  stage1_info["emptyTemplate"] = app_utils.get_empty_template(channel_info["templateNames"],
549
  channel_info["templateDict"])
550
 
551
+ # step1-2 to step1-4
552
  if len(stage1_info["emptyTemplate"]) == 0:
553
  md = """
554
  ### Mapping Result
555
+ The mapping process has been finished.<br>
556
+ Download the file below if you plan to run the models using the source code.
557
  """
558
  outputname = stage1_info["fileNames"]["outputData"]
559
  stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
 
567
  clear_btn : gr.Button(visible=False),
568
  next_btn : gr.Button(visible=False),
569
  run_btn : gr.Button(interactive=True)}
570
+ # step1-2 to step1-3-1
571
  else:
572
  md = """
573
+ ### Step1-3: Filling Remaining Template Channels
574
  Select one of the methods provided below to fill the remaining template channels.
575
  """
576
  stage1_info["state"] = "step3-select-method"
 
583
  clear_btn : gr.Button(visible=False),
584
  next_btn : gr.Button(visible=False)}
585
 
586
+ # ======================================step1-3-1=======================================
587
  elif stage1_info["state"] == "step3-select-method":
588
+ # step1-3-1 to step1-4
589
  if fillmode == "zero":
590
  md = """
591
  ### Mapping Result
592
+ The mapping process has been finished.<br>
593
+ Download the file below if you plan to run the models using the source code.
594
  """
595
  outputname = stage1_info["fileNames"]["outputData"]
596
  stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
 
603
  in_fillmode : gr.Dropdown(visible=False),
604
  fillmode_btn : gr.Button(visible=False),
605
  run_btn : gr.Button(interactive=True)}
606
+ # step1-3-1 to step1-3-2
607
  elif fillmode == "mean":
608
  md = """
609
+ ### Step1-3: Fill the remaining template channels
610
  The current empty template channel, indicated in red, will be filled with the average
611
  value of the data from the selected channels. (By default, the 4 nearest channels are pre-selected.)
612
  """
 
631
  # determine which button to display
632
  if stage1_info["step3"]["totalNum"] == 1:
633
  return {stage1_json : stage1_info,
634
+ desc_md : gr.Markdown(md),
635
  in_fillmode : gr.Dropdown(visible=False),
636
+ fillmode_btn : gr.Button(visible=False),
637
+ chkbox_group : gr.CheckboxGroup(choices=channel_info["inputNames"],
638
  value=value, label=label, visible=True),
639
+ next_btn : gr.Button(visible=True)}
640
  else:
641
  return {stage1_json : stage1_info,
642
+ desc_md : gr.Markdown(md),
643
  in_fillmode : gr.Dropdown(visible=False),
644
+ fillmode_btn : gr.Button(visible=False),
645
+ chkbox_group : gr.CheckboxGroup(choices=channel_info["inputNames"],
646
  value=value, label=label, visible=True),
647
+ step3_btn : gr.Button(visible=True)}
648
 
649
+ # ======================================step1-3-2=======================================
650
+ # step1-3-2 to step1-4
651
  elif stage1_info["state"] == "step3-2-selecting":
652
 
653
  prev_tpl_name = stage1_info["emptyTemplate"][stage1_info["step3"]["count"]-1]
 
657
 
658
  md = """
659
  ### Mapping Result
660
+ The mapping process has been finished.<br>
661
+ Download the file below if you plan to run the models using the source code.
662
  """
663
  outputname = stage1_info["fileNames"]["outputData"]
664
  stage1_info, channel_info = app_utils.mapping_result(stage1_info, channel_info, outputname)
 
676
  fn = init_next_step,
677
  inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
678
  outputs = [stage1_json, channel_json, desc_md, out_result_file, tpl_img, mapped_img, radio_group,
679
+ in_fillmode, fillmode_btn, chkbox_group, clear_btn, step2_btn, step3_btn, next_btn, run_btn]
680
  ).success(
681
+ fn = None,
682
+ js = init_js,
683
+ inputs = [stage1_json, channel_json],
684
  outputs = []
685
  )
686
 
 
688
  # | Stage1-step0 |
689
  # +========================================================================================+
690
  map_btn.click(
691
+ fn = reset_all,
692
+ inputs = [session_dir, stage1_json, stage2_json, in_loc_file],
693
+ outputs = [stage1_json, stage2_json, channel_json, map_btn, desc_md, out_result_file, tpl_img, mapped_img,
694
+ radio_group, in_fillmode, fillmode_btn, chkbox_group, clear_btn, step2_btn, step3_btn, next_btn,
695
+ in_data_file, in_samplerate, run_btn, cancel_btn, batch_md, out_data_file]
696
  ).success(
697
+ fn = init_next_step,
698
+ inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
699
  outputs = [stage1_json, channel_json, map_btn, desc_md, tpl_img, mapped_img, next_btn]
700
  )
701
 
 
745
  return {stage1_json : stage1_info,
746
  channel_json : channel_info,
747
  radio_group : gr.Radio(choices=stage1_info["unassignedInput"],
748
+ value=[], label=label),
749
  step2_btn : gr.Button(visible=False),
750
  next_btn : gr.Button(visible=True)}
751
  else:
752
  return {stage1_json : stage1_info,
753
  channel_json : channel_info,
754
  radio_group : gr.Radio(choices=stage1_info["unassignedInput"],
755
+ value=[], label=label)}
756
  step2_btn.click(
757
+ fn = update_radio,
758
+ inputs = [stage1_json, channel_json, radio_group],
759
+ outputs = [stage1_json, channel_json, radio_group, step2_btn, next_btn]
760
  ).success(
761
+ fn = None,
762
+ js = update_js,
763
+ inputs = [stage1_json, channel_json],
764
  outputs = []
765
  )
766
 
 
796
  chkbox_group : gr.CheckboxGroup(value=value, label=label)}
797
 
798
  fillmode_btn.click(
799
+ fn = init_next_step,
800
  inputs = [stage1_json, channel_json, in_fillmode, radio_group, chkbox_group],
801
  outputs = [stage1_json, channel_json, desc_md, out_result_file, in_fillmode, fillmode_btn,
802
+ chkbox_group, step3_btn, next_btn, run_btn]
803
  ).success(
804
+ fn = None,
805
+ js = init_js,
806
+ inputs = [stage1_json, channel_json],
807
  outputs = []
808
  )
809
 
810
  step3_btn.click(
811
+ fn = update_chkbox,
812
+ inputs = [stage1_json, channel_json, chkbox_group],
813
+ outputs = [stage1_json, chkbox_group, step3_btn, next_btn]
814
  ).success(
815
+ fn = None,
816
+ js = update_js,
817
+ inputs = [stage1_json, channel_json],
818
  outputs = []
819
  )
820
 
app_utils.py CHANGED
@@ -12,13 +12,13 @@ from scipy.optimize import linear_sum_assignment
12
  from sklearn.neighbors import NearestNeighbors
13
 
14
  def get_matched(tpl_names, tpl_dict):
15
- return [name for name in tpl_names if tpl_dict[name]["matched"]==True]
16
 
17
  def get_empty_template(tpl_names, tpl_dict):
18
- return [name for name in tpl_names if tpl_dict[name]["matched"]==False]
19
 
20
  def get_unassigned_input(in_names, in_dict):
21
- return [name for name in in_names if in_dict[name]["assigned"]==False]
22
 
23
  def read_montage(loc_file):
24
  tpl_montage = read_custom_montage("./template_chanlocs.loc")
@@ -48,7 +48,7 @@ def read_montage(loc_file):
48
  return tpl_montage, in_montage, tpl_dict, in_dict
49
 
50
  def match_name(stage1_info):
51
- # read the location file
52
  loc_file = stage1_info["fileNames"]["inputData"]
53
  tpl_montage, in_montage, tpl_dict, in_dict = read_montage(loc_file)
54
  tpl_names = tpl_montage.ch_names
@@ -78,20 +78,20 @@ def match_name(stage1_info):
78
  tpl_names = tpl_montage.ch_names
79
 
80
  stage1_info.update({
81
- "unassignedInput" : get_unassigned_input(in_names, in_dict),
82
- "emptyTemplate" : get_empty_template(tpl_names, tpl_dict),
83
- "mappingResult" : [
84
  {
85
  "index" : old_idx,
86
  "isOriginalData" : is_orig_data
87
  }
88
- ]
89
  })
90
  channel_info = {
91
  "templateNames" : tpl_names,
92
  "inputNames" : in_names,
93
- "templateDict" : tpl_dict,
94
- "inputDict" : in_dict
95
  }
96
  return stage1_info, channel_info, tpl_montage, in_montage
97
 
@@ -147,8 +147,8 @@ def align_coords(channel_info, tpl_montage, in_montage):
147
  in_dict[name]["coord_3d"] = transformed_in[i].tolist()
148
 
149
  channel_info.update({
150
- "templateDict" : tpl_dict,
151
- "inputDict" : in_dict
152
  })
153
  return channel_info
154
 
@@ -238,7 +238,7 @@ def find_neighbors(channel_info, empty_tpl_names, old_idx):
238
  distances, indices = knn.kneighbors(empty_tpl[i].reshape(1,-1))
239
  idx = tpl_dict[name]["index"]
240
  old_idx[idx] = indices[0].tolist()
241
-
242
  return old_idx
243
 
244
  def optimal_mapping(channel_info):
@@ -284,7 +284,7 @@ def optimal_mapping(channel_info):
284
  # fill the remaining empty tpl_channels
285
  empty_tpl_names = get_empty_template(tpl_names, tpl_dict)
286
  if empty_tpl_names != []:
287
- old_idx = find_neighbors(channel_info, empty_tpl_names, old_idx)
288
 
289
  result = {
290
  "index" : old_idx,
 
12
  from sklearn.neighbors import NearestNeighbors
13
 
14
  def get_matched(tpl_names, tpl_dict):
15
+ return [name for name in tpl_names if tpl_dict[name]["matched"]==True]
16
 
17
  def get_empty_template(tpl_names, tpl_dict):
18
+ return [name for name in tpl_names if tpl_dict[name]["matched"]==False]
19
 
20
  def get_unassigned_input(in_names, in_dict):
21
+ return [name for name in in_names if in_dict[name]["assigned"]==False]
22
 
23
  def read_montage(loc_file):
24
  tpl_montage = read_custom_montage("./template_chanlocs.loc")
 
48
  return tpl_montage, in_montage, tpl_dict, in_dict
49
 
50
  def match_name(stage1_info):
51
+ # read the location file
52
  loc_file = stage1_info["fileNames"]["inputData"]
53
  tpl_montage, in_montage, tpl_dict, in_dict = read_montage(loc_file)
54
  tpl_names = tpl_montage.ch_names
 
78
  tpl_names = tpl_montage.ch_names
79
 
80
  stage1_info.update({
81
+ "unassignedInput" : get_unassigned_input(in_names, in_dict),
82
+ "emptyTemplate" : get_empty_template(tpl_names, tpl_dict),
83
+ "mappingResult" : [
84
  {
85
  "index" : old_idx,
86
  "isOriginalData" : is_orig_data
87
  }
88
+ ]
89
  })
90
  channel_info = {
91
  "templateNames" : tpl_names,
92
  "inputNames" : in_names,
93
+ "templateDict" : tpl_dict,
94
+ "inputDict" : in_dict
95
  }
96
  return stage1_info, channel_info, tpl_montage, in_montage
97
 
 
147
  in_dict[name]["coord_3d"] = transformed_in[i].tolist()
148
 
149
  channel_info.update({
150
+ "templateDict" : tpl_dict,
151
+ "inputDict" : in_dict
152
  })
153
  return channel_info
154
 
 
238
  distances, indices = knn.kneighbors(empty_tpl[i].reshape(1,-1))
239
  idx = tpl_dict[name]["index"]
240
  old_idx[idx] = indices[0].tolist()
241
+
242
  return old_idx
243
 
244
  def optimal_mapping(channel_info):
 
284
  # fill the remaining empty tpl_channels
285
  empty_tpl_names = get_empty_template(tpl_names, tpl_dict)
286
  if empty_tpl_names != []:
287
+ old_idx = find_neighbors(channel_info, empty_tpl_names, old_idx)
288
 
289
  result = {
290
  "index" : old_idx,