Files changed (1) hide show
  1. app.py +190 -77
app.py CHANGED
@@ -1,6 +1,3 @@
1
- # give some time reference to the user
2
- print('Importing Gradio app packages... (first launch takes about 3-5 minutes)')
3
-
4
  import gradio as gr
5
  import yaml
6
  import skimage
@@ -334,42 +331,111 @@ def viz_cluster_positive(marker, percentile_threshold, cytof_img, cytof_cohort):
334
  return fig, cytof_img, cytof_cohort
335
 
336
  # Gradio App template
337
- with gr.Blocks() as demo:
338
- cytof_state = gr.State(CytofImage())
339
-
340
- # used in scenrios where users define/remove channels multiple times
341
- cytof_original_state = gr.State(CytofImage())
342
-
343
- gr.Markdown("# Step 1. Upload images")
344
- gr.Markdown('You may upload one or two files depending on your use case.')
345
- gr.Markdown('Case 1: A single TXT or CSV file that contains information about antibodies, rare heavy metal isotopes, and image channel names. Make sure files are following the CyTOF, IMC, or multiplex data convention. Leave the `Marker File` upload section blank.')
346
- gr.Markdown('Case 2: Multiple file uploads required. First, a TIFF file containing Regions of Interest (ROIs) stored as multiplexed images. Then, upload a `Marker File` listing the channels to identify the antibodies.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
- with gr.Row(): # first row where 1) asks for TIFF upload and 2) displays marker info
349
- img_path = gr.File(file_types=[".tiff", '.tif', '.txt', '.csv'], label='(Required) A file containing Regions of Interest (ROIs) of multiplexed imaging slides.')
350
- img_info = gr.Textbox(label='Marker information', info='Ensure the number of markers displayed below matches the expected number.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
  with gr.Row(equal_height=True): # second row where 1) asks for marker file upload and 2) displays the visualization of individual channels
353
- with gr.Column():
354
- marker_path = gr.File(file_types=['.txt'], label='(Optional) Marker File. A list used to identify the antibodies in each TIFF layer. Upload one TXT file.')
 
 
355
  with gr.Row():
356
  clear_btn = gr.Button("Clear")
357
  submit_btn = gr.Button("Upload")
358
- img_viz = gr.Plot(label="Visualization of individual channels")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
 
360
- gr.Markdown("# Step 2. Modify existing channels")
361
- gr.Markdown("After visualizing the individual channels, did you notice any that should not be included in the next steps? Remove those if so.")
362
- gr.Markdown("Define channels designed to visualize nuclei. Optionally, define channels designed to visualize membranes.")
363
-
364
  with gr.Row(equal_height=True): # third row selects nuclei channels
365
- with gr.Column():
366
- selected_unwanted_channel = gr.Dropdown(label='(Optional) Select the unwanted channel', interactive=True)
367
  selected_nuclei = gr.Dropdown(label='(Required) Select the nuclei channel', interactive=True)
 
368
  selected_membrane = gr.Dropdown(label='(Optional) Select the membrane channel', interactive=True)
369
-
370
  define_btn = gr.Button('Modify channels')
371
-
372
- channel_feedback = gr.Textbox(label='Channels info update')
373
 
374
  # upload the file, and gather channel info. Then populate to the unwanted_channel, nuclei, and membrane components
375
  submit_btn.click(
@@ -386,85 +452,132 @@ with gr.Blocks() as demo:
386
  # modifies the channels per user input
387
  define_btn.click(fn=modify_channels, inputs=[cytof_original_state, selected_unwanted_channel, selected_nuclei, selected_membrane], outputs=[channel_feedback, cytof_state])
388
 
389
- gr.Markdown('# Step 3. Perform cell segmentation based on the defined nuclei and membrane channels')
 
 
390
 
391
  with gr.Row(): # This row defines cell radius and performs segmentation
392
- with gr.Column():
393
- cell_radius = gr.Number(value=5, precision=0, label='Cell size', info='Please enter the desired radius for cell segmentation (in pixels; default value: 5)')
 
394
  seg_btn = gr.Button("Segment")
395
- seg_viz = gr.Plot(label="Visualization of the segmentation. Hover over graph to zoom, pan, save, etc.")
 
 
 
396
  seg_btn.click(fn=cell_seg, inputs=[cytof_state, cell_radius], outputs=[seg_viz, cytof_state])
397
-
398
- gr.Markdown('# Step 4. Extract cell features')
 
 
399
 
400
  cohort_state = gr.State(CytofCohort())
401
  with gr.Row(): # feature extraction related functinos
402
- with gr.Column():
403
- gr.CheckboxGroup(choices=['Yes', 'Yes', 'Yes'], label='Note: This step will take significantly longer than the previous ones. A 130MB IMC file takes about 14 minutes to compute. Did you read this note?')
404
  norm_percentile = gr.Slider(minimum=50, maximum=99, step=1, value=75, interactive=True, label='Normalized quantification percentile')
405
  extract_btn = gr.Button('Extract')
406
- feat_df = gr.DataFrame(headers=['id','coordinate_x','coordinate_y','area_nuclei'], label='Feature extraction summary')
 
407
 
408
  extract_btn.click(fn=feature_extraction, inputs=[cytof_state, cohort_state, norm_percentile],
409
  outputs=[cytof_state, cohort_state, feat_df])
410
 
411
- gr.Markdown('# Step 5. Downstream analysis')
412
-
 
 
413
  with gr.Row(): # show co-expression and spatial analysis
414
- with gr.Column():
415
- co_exp_viz = gr.Plot(label="Visualization of cell coexpression of markers")
416
  co_exp_btn = gr.Button('Run co-expression analysis')
 
 
 
 
417
 
418
- with gr.Column():
419
- spatial_viz = gr.Plot(label="Visualization of cell spatial interaction of markers")
420
- cluster_method = gr.Radio(label='Select the clustering method', value='k-neighbor', choices=['k-neighbor', 'distance'], info='K-neighbor: classifies the threshold number of surrounding cells as neighborhood pairs. Distance: classifies cells within threshold distance as neighborhood pairs.')
421
- cluster_threshold = gr.Slider(minimum=1, maximum=100, step=1, value=30, interactive=True, label='Clustering threshold')
422
 
423
- spatial_btn = gr.Button('Run spatial interaction analysis')
424
-
425
- co_exp_btn.click(fn=co_expression, inputs=[cytof_state, norm_percentile], outputs=[co_exp_viz, cytof_state])
426
- # spatial_btn logic is in step6. This is populate the marker positive dropdown options
 
427
 
428
- gr.Markdown('# Step 6. Visualize positive markers')
429
- gr.Markdown('Select two markers for side-by-side comparison to visualize their positive states in cells. This serves two purposes. 1) Validate the co-expression analysis results. High expression level should mean a similar number of positive markers within the two slides, whereas low expression level mean a large difference of in the number of positive markers. 2) Validate the spatial interaction analysis results. High interaction means the two positive markers are in close proximity of each other (proximity is previously defined in `clustering threshold`), and vice versa.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
431
  with gr.Row(): # two marker positive visualization - dropdown options
432
- selected_marker1 = gr.Dropdown(label='Select one marker', info='Select a marker to visualize', interactive=True)
433
- selected_marker2 = gr.Dropdown(label='Select another marker', info='Selecting the same marker as the previous one is allowed', interactive=True)
434
- pos_viz_btn = gr.Button('Visualize these two markers')
435
-
 
 
 
 
436
 
437
- with gr.Row(): # two marker positive visualization - visualization
438
- marker_pos_viz = gr.Plot(label="Visualization of the two markers. Hover over graph to zoom, pan, save, etc.")
439
-
440
- spatial_btn.click(
441
- fn=spatial_interaction, inputs=[cytof_state, norm_percentile, cluster_method, cluster_threshold], outputs=[spatial_viz, cytof_state]
442
- ).success(
443
- fn=get_marker_pos_options, inputs=[cytof_state], outputs=[selected_marker1, selected_marker2]
444
- )
445
- pos_viz_btn.click(fn=viz_pos_marker_pair, inputs=[cytof_state, selected_marker1, selected_marker2, norm_percentile], outputs=[marker_pos_viz])
446
 
447
-
448
- gr.Markdown('# Step 7. Phenogrpah Clustering')
449
- gr.Markdown('Cells can be clustered into sub-groups based on the extracted single-cell data. Time reference: a 300MB IMC file takes about 2 minutes to compute.')
450
 
451
  with gr.Row(): # add two plots to visualize phenograph results
452
- phenograph_umap = gr.Plot(label="UMAP results")
453
- cluster_interaction = gr.Plot(label="Spatial interaction of clusters")
 
 
 
 
 
454
 
455
-
456
- with gr.Row(equal_height=False): # action components
457
- umap_btn = gr.Button('Run Phenograph clustering')
458
- cluster_interact_btn = gr.Button('Run clustering interaction')
 
 
 
 
459
  cluster_interact_btn.click(cluster_interaction_fn, inputs=[cytof_state, cohort_state], outputs=[cluster_interaction, cytof_state, cohort_state])
460
 
 
 
 
461
  with gr.Row():
462
- with gr.Column():
463
  selected_cluster_marker = gr.Dropdown(label='Select one marker', info='Select a marker to visualize', interactive=True)
464
  cluster_positive_btn = gr.Button('Compare clusters and positive markers')
465
-
466
- with gr.Column():
467
- cluster_v_positive = gr.Plot(label="Cluster assignment vs. positive cells. Hover over graph to zoom, pan, save, etc.")
 
468
 
469
 
470
  umap_btn.click(
 
 
 
 
1
  import gradio as gr
2
  import yaml
3
  import skimage
 
331
  return fig, cytof_img, cytof_cohort
332
 
333
  # Gradio App template
334
+ custom_css = """
335
+ <style>
336
+ .h-1 {
337
+ font-size: 40px !important;
338
+ }
339
+ .h-2 {
340
+ font-size: 20px !important;
341
+ }
342
+ .h-3 {
343
+ font-size: 20px !important;
344
+ }
345
+ .mb-10 {
346
+ margin-bottom: 10px !important;
347
+ }
348
+ .no-label label {
349
+ display: none !important;
350
+ }
351
+ .cell-no-label span {
352
+ display: none !important;
353
+ }
354
+ .no-border {
355
+ border-width: 0 !important;
356
+ }
357
+ hr {
358
+ margin: 1.5rem 0 0 0 !important;
359
+ padding-bottom: 10px !important;
360
+ }
361
+ .input-choices {
362
+ padding: 10px 0 !important;
363
+ }
364
+ .input-choices > span {
365
+ display: none;
366
+ }
367
+ .form:has(.input-choices) {
368
+ border-width: 0 !important;
369
+ box-shadow: none !important;
370
+ }
371
+ .bold {
372
+ font-weight: 600 !important;
373
+ }
374
+ </style>
375
+ """
376
 
377
+ with gr.Blocks() as demo:
378
+ gr.HTML(custom_css)
379
+
380
+ gr.Markdown('<div class="h-1">Step 1. Upload images</div>')
381
+ gr.Markdown('<div class="h-2">You may upload one or two files depending on your use case.</div>')
382
+ gr.Markdown('<div class="h-2 bold">Case 1: &nbsp; Upload a single file</div>')
383
+ gr.Markdown('<div class="h-2"><ul><li>upload a TXT or CSV file that contains information about antibodies, rare heavy metal isotopes, and image channel names.</li>'
384
+ '<li>files are following the CyTOF, IMC, or multiplex data convention.</li>'
385
+ '</ul></div>')
386
+ gr.Markdown('<div class="h-2 bold">Case 2: &nbsp; Upload multiple files</div>')
387
+ gr.Markdown('<div class="h-2"><ul><li>upload a TIFF file containing Regions of Interest (ROIs) stored as multiplexed images. <a href="https://qbrc.swmed.edu/labs/xiaoxie/download/multiplex/example_image.tiff" download target="_blank">Example ROI</a></li>'
388
+ '<li>upload a Marker File listing the channels to identify the antibodies. <a href="https://github.com/QBRC/multiTAP/blob/main/example_data/markers_labels.txt" download target="_blank">Example Marker File</a></li>'
389
+ '</ul></div><hr>')
390
+
391
+ gr.Markdown('<div class="h-2">Select Input Case:</div>')
392
+
393
+ choices = gr.Radio(["Case 1", "Case 2"], value="Case 1", label="Choose Input Case", elem_classes='input-choices')
394
+
395
+ def toggle_file_input(choice):
396
+ if choice == "Case 1":
397
+ return (
398
+ gr.update(visible=True, file_types=['.txt', '.csv'], label="TXT or CSV File"),
399
+ gr.update(visible=False)
400
+ )
401
+ else:
402
+ return (
403
+ gr.update(visible=True, file_types=[".tiff", '.tif'], label="TIFF File"),
404
+ gr.update(visible=True)
405
+ )
406
 
407
  with gr.Row(equal_height=True): # second row where 1) asks for marker file upload and 2) displays the visualization of individual channels
408
+ with gr.Column(scale=2):
409
+ gr.Markdown('<div class="h-2">File Input:</div>')
410
+ img_path = gr.File(file_types=['.txt', '.csv'], label='TXT or CSV File')
411
+ marker_path = gr.File(file_types=['.txt'], label='Marker File', visible=False)
412
  with gr.Row():
413
  clear_btn = gr.Button("Clear")
414
  submit_btn = gr.Button("Upload")
415
+ with gr.Column(scale=3):
416
+ gr.Markdown('<div class="h-2">Marker Information:</div>')
417
+ img_info = gr.Textbox(label='Ensure the number of markers displayed below matches the expected number.')
418
+ gr.Markdown('<div class="h-3">Visualization of individual channels:</div>')
419
+ with gr.Accordion("", open=True):
420
+ img_viz = gr.Plot(elem_classes='no-label no-border')
421
+
422
+ choices.change(fn=toggle_file_input, inputs=choices, outputs=[img_path, marker_path])
423
+
424
+ # img_viz = gr.Plot(label="Visualization of individual channels")
425
+ gr.Markdown('<br>')
426
+ gr.Markdown('<div class="h-1">Step 2. Modify Existing Channels</div>')
427
+ gr.Markdown('<div class="h-2">(Required) Define channels designed to visualize nuclei. </div>')
428
+ gr.Markdown('<div class="h-2">(Optional) Remove unwanted channel after visualizing the individual channels. </div>')
429
+ gr.Markdown('<div class="h-2">(Optional) Define channels degisned to visualize membranes.</div><hr>')
430
 
 
 
 
 
431
  with gr.Row(equal_height=True): # third row selects nuclei channels
432
+ with gr.Column(scale=2):
 
433
  selected_nuclei = gr.Dropdown(label='(Required) Select the nuclei channel', interactive=True)
434
+ selected_unwanted_channel = gr.Dropdown(label='(Optional) Select the unwanted channel', interactive=True)
435
  selected_membrane = gr.Dropdown(label='(Optional) Select the membrane channel', interactive=True)
 
436
  define_btn = gr.Button('Modify channels')
437
+ with gr.Column(scale=3):
438
+ channel_feedback = gr.Textbox(label='Channels info update')
439
 
440
  # upload the file, and gather channel info. Then populate to the unwanted_channel, nuclei, and membrane components
441
  submit_btn.click(
 
452
  # modifies the channels per user input
453
  define_btn.click(fn=modify_channels, inputs=[cytof_original_state, selected_unwanted_channel, selected_nuclei, selected_membrane], outputs=[channel_feedback, cytof_state])
454
 
455
+ gr.Markdown('<br>')
456
+ gr.Markdown('<div class="h-1">Step 3. Perform Cell Segmentation</div>')
457
+ gr.Markdown('<div class="h-2">In this step, we perform cell segmentation based on the defined nuclei and membrane channels</div><hr>')
458
 
459
  with gr.Row(): # This row defines cell radius and performs segmentation
460
+ with gr.Column(scale=2):
461
+ gr.Markdown('<div class="h-2">Cell Size:</div>')
462
+ cell_radius = gr.Number(value=5, precision=0, label='Cell size', info='Please enter the desired radius for cell segmentation (in pixels; default value: 5)', elem_classes='cell-no-label')
463
  seg_btn = gr.Button("Segment")
464
+ with gr.Column(scale=3):
465
+ gr.Markdown('<div class="h-2">Visualization of the segmentation: </div>')
466
+ with gr.Accordion("Hover over graph to zoom, pan, save, etc.", open=True):
467
+ seg_viz = gr.Plot(label="Hover over graph to zoom, pan, save, etc.", elem_classes='no-border no-label')
468
  seg_btn.click(fn=cell_seg, inputs=[cytof_state, cell_radius], outputs=[seg_viz, cytof_state])
469
+
470
+ gr.Markdown('<br>')
471
+ gr.Markdown('<div class="h-1">Step 4. Extract cell features</div>')
472
+ gr.Markdown('<div class="h-2"><span class="bold">Note</span>: This step will take significantly longer than the previous ones. (A 300MB IMC file takes about 7 minutes to compute.)</div><hr>')
473
 
474
  cohort_state = gr.State(CytofCohort())
475
  with gr.Row(): # feature extraction related functinos
476
+ with gr.Column(scale=2):
477
+ # gr.CheckboxGroup(choices=['Yes', 'Yes', 'Yes'], label='')
478
  norm_percentile = gr.Slider(minimum=50, maximum=99, step=1, value=75, interactive=True, label='Normalized quantification percentile')
479
  extract_btn = gr.Button('Extract')
480
+ with gr.Column(scale=3):
481
+ feat_df = gr.DataFrame(headers=['id','coordinate_x','coordinate_y','area_nuclei'],col_count=(4, "fixed"))
482
 
483
  extract_btn.click(fn=feature_extraction, inputs=[cytof_state, cohort_state, norm_percentile],
484
  outputs=[cytof_state, cohort_state, feat_df])
485
 
486
+ gr.Markdown('<br>')
487
+ gr.Markdown('<div class="h-1">Step 5. Downstream analysis</div><hr>')
488
+
489
+ gr.Markdown('<div class="h-2 bold">(1) Co-expression Analysis</div>')
490
  with gr.Row(): # show co-expression and spatial analysis
491
+ with gr.Column(scale=2):
492
+ gr.Markdown('<div class="h-2">This analysis measures the level of co-expression for each pair of biomarkers by calculating the odds ratio between the observed co-occurrence and the expected expressing even</div>')
493
  co_exp_btn = gr.Button('Run co-expression analysis')
494
+ with gr.Column(scale=3):
495
+ gr.Markdown('<div class="h-2">Visualization of cell coexpression of markers</div>')
496
+ with gr.Accordion("", open=True):
497
+ co_exp_viz = gr.Plot(elem_classes='no-label no-border')
498
 
499
+ gr.Markdown('<div class="h-2 bold">(2) Spatial Interactoin Analysis</div>')
 
 
 
500
 
501
+ def update_info_text(choice):
502
+ if choice == "k-neighbor":
503
+ return 'K-neighbor: classifies the threshold number of surrounding cells as neighborhood pairs.'
504
+ else:
505
+ return 'Distance: classifies cells within threshold distance as neighborhood pairs.'
506
 
507
+ with gr.Row():
508
+ with gr.Column(scale=2):
509
+ gr.Markdown('<div class="h-2">This analysis measures the degree of co-expression within a pair of neighborhoods.</div>')
510
+ gr.Markdown('<div class="h-2">Select the clustering method:</div>')
511
+ info_text = gr.Markdown(update_info_text('k-neighbor'))
512
+ cluster_method = gr.Radio(['k-neighbor', 'distance'], value='k-neighbor', elem_classes='test', label='')
513
+ cluster_threshold = gr.Slider(minimum=1, maximum=100, step=1, value=30, interactive=True, label='Clustering threshold')
514
+ spatial_btn = gr.Button('Run spatial interaction analysis')
515
+ with gr.Column(scale=3):
516
+ gr.Markdown('<div class="h-2">Visualization of spatial interaction of markers</div>')
517
+ with gr.Accordion("", open=True):
518
+ spatial_viz = gr.Plot(elem_classes='no-label no-border')
519
+
520
+ cluster_method.change(fn=update_info_text, inputs=cluster_method, outputs=info_text)
521
+ co_exp_btn.click(fn=co_expression, inputs=[cytof_state, norm_percentile], outputs=[co_exp_viz, cytof_state])
522
+ # spatial_btn logic is in step6. This is populate the marker positive dropdown options
523
+
524
+ gr.Markdown('<br>')
525
+ gr.Markdown('<div class="h-1">Step 6. Visualize positive markers</div>')
526
+ gr.Markdown('<div class="h-2">Select two markers for side-by-side comparison to visualize their positive states in cells. This serves two purposes: </div>')
527
+ gr.Markdown('<div class="h-2 bold">(1) Validate the co-expression analysis results. (2) Validate teh spatial interaction analysis results.</div>')
528
+
529
 
530
  with gr.Row(): # two marker positive visualization - dropdown options
531
+ with gr.Column(scale=2):
532
+ selected_marker1 = gr.Dropdown(label='Select one marker', info='Select a marker to visualize', interactive=True)
533
+ selected_marker2 = gr.Dropdown(label='Select another marker', info='Selecting the same marker as the previous one is allowed', interactive=True)
534
+ pos_viz_btn = gr.Button('Visualize these two markers')
535
+ with gr.Column(scale=3):
536
+ gr.Markdown('<div class="h-2">Visualization of the two markers.</div>')
537
+ with gr.Accordion("Hover over graph to zoom, pan, save, etc.", open=True):
538
+ marker_pos_viz = gr.Plot(elem_classes='no-label no-border')
539
 
540
+ spatial_btn.click(
541
+ fn=spatial_interaction, inputs=[cytof_state, norm_percentile, cluster_method, cluster_threshold], outputs=[spatial_viz, cytof_state]
542
+ ).success(
543
+ fn=get_marker_pos_options, inputs=[cytof_state], outputs=[selected_marker1, selected_marker2]
544
+ )
545
+ pos_viz_btn.click(fn=viz_pos_marker_pair, inputs=[cytof_state, selected_marker1, selected_marker2, norm_percentile], outputs=[marker_pos_viz])
 
 
 
546
 
547
+ gr.Markdown('<br>')
548
+ gr.Markdown('<div class="h-1">Step 7. Phenogrpah Clustering</div>')
549
+ gr.Markdown('<div class="h-2">Cells can be clustered into sub-groups based on the extracted single-cell data. (A 300MB IMC file takes about 2 minutes to compute.)</div><hr>')
550
 
551
  with gr.Row(): # add two plots to visualize phenograph results
552
+ with gr.Column(scale=2):
553
+ gr.Markdown('<div class="h-2">We used UMAP to project the high-dimensional data onto a 2-D space.</div>')
554
+ umap_btn = gr.Button('Run Phenograph clustering')
555
+ with gr.Column(scale=3):
556
+ gr.Markdown('<div class="h-2">UMAP Results</div>')
557
+ with gr.Accordion("", open=True):
558
+ phenograph_umap = gr.Plot(elem_classes='no-label no-border')
559
 
560
+ with gr.Row(): # add two plots to visualize phenograph results
561
+ with gr.Column(scale=2):
562
+ gr.Markdown('<div class="h-2">The previously assigned clusters are also reflected in this figure.</div>')
563
+ cluster_interact_btn = gr.Button('Run clustering interaction')
564
+ with gr.Column(scale=3):
565
+ gr.Markdown('<div class="h-2">Spatial interaction of clusters</div>')
566
+ with gr.Accordion("", open=True):
567
+ cluster_interaction = gr.Plot(elem_classes='no-label no-border')
568
  cluster_interact_btn.click(cluster_interaction_fn, inputs=[cytof_state, cohort_state], outputs=[cluster_interaction, cytof_state, cohort_state])
569
 
570
+ gr.Markdown('<br>')
571
+ gr.Markdown('<div class="h-2">In additional, you could visualizing the cluster assignments against the positive markers to oberve any patterns:</div><hr>')
572
+
573
  with gr.Row():
574
+ with gr.Column(scale=2):
575
  selected_cluster_marker = gr.Dropdown(label='Select one marker', info='Select a marker to visualize', interactive=True)
576
  cluster_positive_btn = gr.Button('Compare clusters and positive markers')
577
+ with gr.Column(scale=3):
578
+ gr.Markdown('<div class="h-2">Cluster assignment vs. positive cells</div>')
579
+ with gr.Accordion("Hover over graph to zoom, pan, save, etc.", open=True):
580
+ cluster_v_positive = gr.Plot(elem_classes='no-label no-border')
581
 
582
 
583
  umap_btn.click(