Romain Graux commited on
Commit
8ce4530
β€’
1 Parent(s): 1a5c8e7

Upgrade gradio

Browse files
Files changed (3) hide show
  1. README.md +1 -1
  2. app.py +207 -153
  3. requirements.txt +4 -4
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: πŸš€
4
  colorFrom: purple
5
  colorTo: blue
6
  sdk: gradio
7
- sdk_version: 3.21.0
8
  python_version: 3.10.9
9
  app_file: app.py
10
  pinned: true
 
4
  colorFrom: purple
5
  colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 4.44.0
8
  python_version: 3.10.9
9
  app_file: app.py
10
  pinned: true
app.py CHANGED
@@ -10,6 +10,7 @@
10
  # TODO : add the description of the settings
11
 
12
 
 
13
  import gradio as gr
14
  import json
15
  import numpy as np
@@ -18,7 +19,7 @@ import sys
18
  import tempfile
19
  import torch
20
 
21
- from PIL import Image, ImageDraw
22
  from app.dl_inference import inference_fn
23
  from app.knn import knn, segment_image, bokeh_plot_knn, color_palette
24
  from app.tiff_utils import extract_physical_metadata
@@ -45,7 +46,7 @@ def inf(img, n_species, threshold, architecture):
45
  img, results = inference_fn(architecture, img, threshold, n_species=n_species)
46
  draw = ImageDraw.Draw(img)
47
  for (k, v), color in zip(results["species"].items(), color_palette):
48
- color = "#" + "".join([f"{int(255 * x):02x}" for x in color])
49
  draw.text((5, 5 + 15 * k), f"species {k}", fill=color)
50
  for x, y in v["coords"]:
51
  draw.ellipse(
@@ -62,47 +63,68 @@ def batch_fn(
62
  progress(0, desc="Starting...")
63
  block_state = {}
64
  if not files:
65
- raise ValueError("No files were uploaded")
 
 
 
66
 
67
  gallery = []
 
 
68
  for file_idx, file in enumerate(files):
69
  base_progress = file_idx / len(files)
70
- display_progress = lambda value, text=None: progress(
71
- base_progress + (1 / len(files)) * value,
72
- desc=f"Processing image {file_idx+1}/{len(files)}{' - ' + text if text else '...'}",
73
- )
74
- display_progress(0)
 
 
 
75
  display_progress(0.1, "Extracting metadata...")
76
- error_physical_metadata = None
77
  try:
78
  physical_metadata = extract_physical_metadata(file.name)
79
  if physical_metadata.unit != "nm":
80
  raise ValueError(f"Unit of {file.name} is not nm, cannot process it")
81
  except Exception as e:
82
- error_physical_metadata = e
83
- physical_metadata = None
84
- original_file_name = file.name.split("/")[-1]
85
- display_progress(0.2, "Inference...")
86
- img, results = inf(file.name, n_species, threshold, architecture)
87
- display_progress(0.8, "Segmentation...")
88
- mask = segment_image(file.name)
89
- gallery.append((img, original_file_name))
90
- display_progress(1, "Done")
91
-
92
- if physical_metadata is not None:
93
- factor = 1.0 - np.mean(mask)
94
- scale = physical_metadata.pixel_width
95
- edge = physical_metadata.pixel_width * physical_metadata.width
96
- knn_results = {
97
- k: knn(results["species"][k]["coords"], scale, factor, edge)
98
- for k in results["species"]
99
- }
100
- else:
101
- knn_results = None
102
 
103
- block_state[original_file_name] = block_state_entry(
104
- results, knn_results, physical_metadata
105
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
  knn_args = [
108
  (
@@ -115,21 +137,30 @@ def batch_fn(
115
  for original_file_name in block_state
116
  if block_state[original_file_name].knn_results is not None
117
  ]
118
- if len(knn_args) > 0:
119
- bokeh_plot = gr.update(
120
- value=bokeh_plot_knn(knn_args, with_cumulative=True), visible=True
121
- )
122
- else:
123
- bokeh_plot = gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
124
  return (
125
  gallery,
126
  block_state,
127
- gr.update(visible=True),
 
 
128
  bokeh_plot,
129
- gr.HTML.update(
130
- value=f"<p style='width:fit-content; background-color:rgba(255, 0, 0, 0.75); border-radius:5px; padding:5px; color:white;'>{error_physical_metadata}</p>",
131
- visible=bool(error_physical_metadata),
132
- ),
133
  )
134
 
135
 
@@ -153,14 +184,13 @@ def batch_export_files(gallery, block_state):
153
  with ZipFile(
154
  f"{tmpdir}/all_results_{datetime.now().isoformat()}.zip", "w"
155
  ) as zipObj:
156
- # Add all metatada
157
- for data_dict, original_file_name in gallery:
158
  file_name = original_file_name.split(".")[0]
159
 
160
- # Save the image
161
  pred_map_path = f"{tmpdir}/pred_map_{file_name}.png"
162
- file_path = data_dict["name"]
163
- shutil.copy(file_path, pred_map_path)
164
  zipObj.write(pred_map_path, arcname=f"{file_name}/pred_map.png")
165
  files.append(pred_map_path)
166
 
@@ -204,29 +234,33 @@ def batch_export_files(gallery, block_state):
204
  CSS = """
205
  .header {
206
  display: flex;
207
- justify-content: center;
208
  align-items: center;
209
- padding: var(--block-padding);
210
- border-radius: var(--block-radius);
211
- background: var(--button-secondary-background-hover);
 
 
212
  }
213
 
214
- img {
215
  width: 150px;
216
- margin-right: 40px;
217
  }
218
 
219
  .title {
220
  text-align: left;
221
  }
222
 
223
- h1 {
224
- font-size: 36px;
225
- margin-bottom: 10px;
226
  }
227
 
228
- p {
229
  font-size: 18px;
 
 
230
  }
231
 
232
  input {
@@ -246,68 +280,101 @@ CSS = """
246
  """
247
 
248
 
249
- with gr.Blocks(css=CSS) as block:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  block_state = gr.State({})
251
- gr.HTML(
 
252
  """
253
  <div class="header">
254
- <a href="https://www.nccr-catalysis.ch/" target="_blank">
255
- <img src="https://www.nccr-catalysis.ch/site/assets/files/1/nccr_catalysis_logo.svg" alt="NCCR Catalysis">
256
- </a>
257
  <div class="title">
258
  <h1>Atom Detection</h1>
259
- <p>Quantitative description of metal center organization in single-atom catalysts</p>
260
  </div>
 
261
  </div>
262
  """
263
  )
 
264
  with gr.Row():
265
  with gr.Column():
266
- with gr.Row():
267
- n_species = gr.Number(
268
- label="Number of species",
269
- value=1,
270
- precision=0,
271
- visible=True,
272
- )
273
- threshold = gr.Slider(
274
- minimum=0.0,
275
- maximum=1.0,
276
- value=0.8,
277
- label="Threshold",
278
- visible=True,
279
- )
280
- architecture = gr.Dropdown(
281
- label="Architecture",
282
- choices=[
283
- ModelArgs.BASICCNN,
284
- # ModelArgs.RESNET18,
285
- ],
286
- value=ModelArgs.BASICCNN,
287
- visible=False,
288
- )
289
- files = gr.Files(
290
- label="Images",
291
- file_types=[".tif", ".tiff"],
292
- type="file",
293
- interactive=True,
294
  )
295
- button = gr.Button(value="Run")
 
296
  with gr.Column():
297
- with gr.Tab("Masked prediction") as masked_tab:
298
- masked_prediction_gallery = gr.Gallery(label="Masked predictions")
299
- with gr.Tab("Nearest neighbors") as nn_tab:
300
- bokeh_plot = gr.Plot(show_label=False)
301
- error_html = gr.HTML(visible=False)
302
- export_btn = gr.Button(value="Export files", visible=False)
 
 
303
  exported_files = gr.File(
304
  label="Exported files",
305
  file_count="multiple",
306
- type="file",
307
  interactive=False,
308
  visible=False,
309
  )
310
- button.click(
 
311
  batch_fn,
312
  inputs=[files, n_species, threshold, architecture, block_state],
313
  outputs=[
@@ -318,65 +385,52 @@ with gr.Blocks(css=CSS) as block:
318
  error_html,
319
  ],
320
  )
 
321
  export_btn.click(
322
- batch_export_files, [masked_prediction_gallery, block_state], [exported_files]
 
 
323
  )
324
- with gr.Accordion(label="How to ✨", open=True):
325
- gr.HTML(
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  """
327
- <div style="font-size: 14px;">
328
- <ol>
329
- <li>Select one or multiple microscopy images as <b>.tiff files</b> πŸ“·πŸ”¬</li>
330
- <li>Upload individual or multiple .tif images for processing πŸ“€πŸ”’</li>
331
- <li>Export the output files. The generated zip archive will contain:
332
- <ul>
333
- <li>An image with overlayed atomic positions πŸŒŸπŸ”</li>
334
- <li>A table of atomic positions (in px) along with their probability πŸ“ŠπŸ’Ž</li>
335
- <li>Physical metadata of the respective images πŸ“„πŸ”</li>
336
- <li>JSON-formatted plot data πŸ“ŠπŸ“</li>
337
- </ul>
338
- </li>
339
- </ol>
340
- <details style="padding: 5px; border-radius: 5px; background: var(--button-secondary-background-hover); font-size: 14px;">
341
- <summary>Note</summary>
342
- <ul style="padding-left: 10px;">
343
- <li>
344
- Structural descriptors beyond pixel-wise atom detections are available as outputs only if images present an embedded real-space calibration (e.g., inΒ <a href="https://imagej.nih.gov/ij/docs/guide/146-30.html#sub:Set-Scale...">nm px-1</a>) πŸ“·πŸ”¬
345
- </li>
346
- <li>
347
- 32-bit images will be processed correctly, but appear as mostly white in the image preview window
348
- </li>
349
- </ul>
350
- </details>
351
- </div>
352
- """
353
  )
354
- with gr.Accordion(label="Disclaimer and License", open=False):
355
- gr.HTML(
 
356
  """
357
- <div class="acknowledgments">
358
- <h3>Disclaimer</h3>
359
- <p>NCCR licenses the Atom Detection Web-App utilisation β€œas is” with no express or implied warranty of any kind. NCCR specifically disclaims all express or implied warranties to the fullest extent allowed by applicable law, including without limitation all implied warranties of merchantability, title or fitness for any particular purpose or non-infringement. No oral or written information or advice given by the authors shall create or form the basis of any warranty of any kind.</p>
360
- <h3>License</h3>
361
- <p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the β€œSoftware”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
362
- <br>
363
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
364
- <br>
365
- The software is provided β€œas is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.</p>
366
- </div>
367
  """
368
  )
369
- gr.HTML(
 
 
 
370
  """
371
- <div style="background-color: var(--secondary-100); border-radius: 5px; padding: 10px;">
372
- <p style='font-size: 14px; color: black'>To reference the use of this web app in a publication, please refer to the Atom Detection web app and the development described in this publication: K. Rossi et al. Adv. Mater. 2023, <a href="https://doi.org/10.1002/adma.202307991">doi:10.1002/adma.202307991</a>.</p>
373
- </div>
374
- """
375
  )
376
 
377
- block.launch(
378
- share=False,
379
- show_error=True,
380
- server_name="0.0.0.0",
381
- enable_queue=True,
382
- )
 
10
  # TODO : add the description of the settings
11
 
12
 
13
+ import os
14
  import gradio as gr
15
  import json
16
  import numpy as np
 
19
  import tempfile
20
  import torch
21
 
22
+ from PIL import ImageDraw
23
  from app.dl_inference import inference_fn
24
  from app.knn import knn, segment_image, bokeh_plot_knn, color_palette
25
  from app.tiff_utils import extract_physical_metadata
 
46
  img, results = inference_fn(architecture, img, threshold, n_species=n_species)
47
  draw = ImageDraw.Draw(img)
48
  for (k, v), color in zip(results["species"].items(), color_palette):
49
+ color = "#{:02x}{:02x}{:02x}".format(*[int(255 * x) for x in color])
50
  draw.text((5, 5 + 15 * k), f"species {k}", fill=color)
51
  for x, y in v["coords"]:
52
  draw.ellipse(
 
63
  progress(0, desc="Starting...")
64
  block_state = {}
65
  if not files:
66
+ raise gr.Error("No files were uploaded")
67
+
68
+ if any(not file.name.lower().endswith(('.tif', '.tiff')) for file in files):
69
+ raise gr.Error("Only TIFF images are supported")
70
 
71
  gallery = []
72
+ error_messages = []
73
+
74
  for file_idx, file in enumerate(files):
75
  base_progress = file_idx / len(files)
76
+
77
+ def display_progress(value, text=None):
78
+ progress(
79
+ base_progress + (1 / len(files)) * value,
80
+ desc=f"Processing image {file_idx+1}/{len(files)}{' - ' + text if text else '...'}",
81
+ )
82
+
83
+
84
  display_progress(0.1, "Extracting metadata...")
85
+ physical_metadata = None
86
  try:
87
  physical_metadata = extract_physical_metadata(file.name)
88
  if physical_metadata.unit != "nm":
89
  raise ValueError(f"Unit of {file.name} is not nm, cannot process it")
90
  except Exception as e:
91
+ error_messages.append(f"Error processing {file.name}: {str(e)}")
92
+ continue # Skip to the next file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ original_file_name = os.path.basename(file.name)
95
+ sanitized_file_name = original_file_name.replace(" ", "_")
96
+ temp_file_path = os.path.join(tempfile.gettempdir(), sanitized_file_name)
97
+
98
+ try:
99
+ shutil.copy2(file.name, temp_file_path)
100
+
101
+ display_progress(0.2, "Inference...")
102
+ img, results = inf(temp_file_path, n_species, threshold, architecture)
103
+
104
+ display_progress(0.8, "Segmentation...")
105
+ mask = segment_image(temp_file_path)
106
+ gallery.append((img, original_file_name))
107
+
108
+ if physical_metadata is not None:
109
+ factor = 1.0 - np.mean(mask)
110
+ scale = physical_metadata.pixel_width
111
+ edge = physical_metadata.pixel_width * physical_metadata.width
112
+ knn_results = {
113
+ k: knn(results["species"][k]["coords"], scale, factor, edge)
114
+ for k in results["species"]
115
+ }
116
+ else:
117
+ knn_results = None
118
+
119
+ block_state[original_file_name] = block_state_entry(
120
+ results, knn_results, physical_metadata
121
+ )
122
+ display_progress(1, "Done")
123
+ except Exception as e:
124
+ error_messages.append(f"Error processing {file.name}: {str(e)}")
125
+ finally:
126
+ if os.path.exists(temp_file_path):
127
+ os.remove(temp_file_path)
128
 
129
  knn_args = [
130
  (
 
137
  for original_file_name in block_state
138
  if block_state[original_file_name].knn_results is not None
139
  ]
140
+
141
+ bokeh_plot = gr.update(
142
+ value=bokeh_plot_knn(knn_args, with_cumulative=True) if knn_args else None,
143
+ visible=bool(knn_args),
144
+ )
145
+
146
+ error_html = gr.update(
147
+ value="<br>".join(
148
+ [
149
+ f"<p style='width:fit-content; background-color:rgba(255, 0, 0, 0.75); border-radius:5px; padding:5px; color:white;'>{msg}</p>"
150
+ for msg in error_messages
151
+ ]
152
+ ),
153
+ visible=bool(error_messages),
154
+ )
155
+
156
  return (
157
  gallery,
158
  block_state,
159
+ gr.update(
160
+ visible=bool(gallery)
161
+ ), # Show export button only if there are results
162
  bokeh_plot,
163
+ error_html,
 
 
 
164
  )
165
 
166
 
 
184
  with ZipFile(
185
  f"{tmpdir}/all_results_{datetime.now().isoformat()}.zip", "w"
186
  ) as zipObj:
187
+ # Add all metadata
188
+ for img_path, original_file_name in gallery:
189
  file_name = original_file_name.split(".")[0]
190
 
191
+ # Copy the image
192
  pred_map_path = f"{tmpdir}/pred_map_{file_name}.png"
193
+ shutil.copy2(img_path, pred_map_path)
 
194
  zipObj.write(pred_map_path, arcname=f"{file_name}/pred_map.png")
195
  files.append(pred_map_path)
196
 
 
234
  CSS = """
235
  .header {
236
  display: flex;
237
+ flex-direction: row;
238
  align-items: center;
239
+ justify-content: flex-start;
240
+ padding: 12px;
241
+ gap: 12px;
242
+ border-radius: 4px;
243
+ background: var(--block-background-fill);
244
  }
245
 
246
+ .header img {
247
  width: 150px;
248
+ height: auto;
249
  }
250
 
251
  .title {
252
  text-align: left;
253
  }
254
 
255
+ .title h1 {
256
+ font-size: 28px;
257
+ margin-bottom: 5px;
258
  }
259
 
260
+ .title h2 {
261
  font-size: 18px;
262
+ font-weight: normal;
263
+ margin-top: 0;
264
  }
265
 
266
  input {
 
280
  """
281
 
282
 
283
+ COLORS = {
284
+ "primary": {
285
+ "name": "nccr-catalysis-primary",
286
+ "c50": "#faecef",
287
+ "c100": "#f4d9df",
288
+ "c200": "#e9b3bf",
289
+ "c300": "#de8d9f",
290
+ "c400": "#d3677f",
291
+ "c500": "#c8415f",
292
+ "c600": "#a0344c",
293
+ "c700": "#782739",
294
+ "c800": "#501a26",
295
+ "c900": "#280d13",
296
+ "c950": "#14060a",
297
+ },
298
+ "secondary": {
299
+ "name": "nccr-catalysis-secondary",
300
+ "c50": "#fff8ed",
301
+ "c100": "#fef0da",
302
+ "c200": "#fde1b5",
303
+ "c300": "#fcd290",
304
+ "c400": "#fbc36b",
305
+ "c500": "#fab446",
306
+ "c600": "#c89038",
307
+ "c700": "#966c2a",
308
+ "c800": "#64481c",
309
+ "c900": "#32240e",
310
+ "c950": "#191207",
311
+ },
312
+ }
313
+
314
+ with gr.Blocks(
315
+ theme=gr.themes.Soft(
316
+ primary_hue=gr.themes.colors.Color(**COLORS["primary"]),
317
+ secondary_hue=gr.themes.colors.Color(**COLORS["secondary"]),
318
+ spacing_size=gr.themes.sizes.spacing_sm,
319
+ radius_size=gr.themes.sizes.radius_sm,
320
+ text_size=gr.themes.sizes.text_sm,
321
+ ),
322
+ css=CSS,
323
+ ) as block:
324
  block_state = gr.State({})
325
+
326
+ gr.Markdown(
327
  """
328
  <div class="header">
329
+ <img src="https://www.nccr-catalysis.ch/site/assets/files/1/nccr_catalysis_logo.svg" alt="NCCR Catalysis">
 
 
330
  <div class="title">
331
  <h1>Atom Detection</h1>
332
+ <h2>Quantitative description of metal center organization in single-atom catalysts</h2>
333
  </div>
334
+
335
  </div>
336
  """
337
  )
338
+
339
  with gr.Row():
340
  with gr.Column():
341
+ n_species = gr.Number(label="Number of species", value=1, precision=0)
342
+ threshold = gr.Slider(
343
+ minimum=0.0,
344
+ maximum=1.0,
345
+ value=0.8,
346
+ label="Threshold",
347
+ info="Threshold for the confidence of the prediction",
348
+ )
349
+ architecture = gr.Dropdown(
350
+ label="Architecture",
351
+ choices=[ModelArgs.BASICCNN],
352
+ value=ModelArgs.BASICCNN,
353
+ visible=False,
354
+ )
355
+ files = gr.File(
356
+ label="Images", file_types=[".tif", ".tiff"], file_count="multiple"
 
 
 
 
 
 
 
 
 
 
 
 
357
  )
358
+ run_button = gr.Button("Run", variant="primary")
359
+
360
  with gr.Column():
361
+ with gr.Tabs():
362
+ with gr.TabItem("Masked prediction"):
363
+ masked_prediction_gallery = gr.Gallery(label="Masked predictions")
364
+ with gr.TabItem("Nearest neighbors"):
365
+ bokeh_plot = gr.Plot(show_label=False)
366
+ error_html = gr.HTML(visible=False)
367
+
368
+ export_btn = gr.Button("Export files", visible=False)
369
  exported_files = gr.File(
370
  label="Exported files",
371
  file_count="multiple",
372
+ type="filepath",
373
  interactive=False,
374
  visible=False,
375
  )
376
+
377
+ run_button.click(
378
  batch_fn,
379
  inputs=[files, n_species, threshold, architecture, block_state],
380
  outputs=[
 
385
  error_html,
386
  ],
387
  )
388
+
389
  export_btn.click(
390
+ batch_export_files,
391
+ inputs=[masked_prediction_gallery, block_state],
392
+ outputs=[exported_files],
393
  )
394
+
395
+ with gr.Accordion("How to ✨", open=True):
396
+ gr.Markdown(
397
+ """
398
+ 1. Select one or multiple microscopy images as **.tiff files** πŸ“·πŸ”¬
399
+ 2. Upload individual or multiple .tif images for processing πŸ“€πŸ”’
400
+ 3. Export the output files. The generated zip archive will contain:
401
+ - An image with overlayed atomic positions πŸŒŸπŸ”
402
+ - A table of atomic positions (in px) along with their probability πŸ“ŠπŸ’Ž
403
+ - Physical metadata of the respective images πŸ“„πŸ”
404
+ - JSON-formatted plot data πŸ“ŠπŸ“
405
+
406
+ > **Note:**
407
+ > - Structural descriptors beyond pixel-wise atom detections are available as outputs only if images present an embedded real-space calibration (e.g., in [nm px-1](https://imagej.nih.gov/ij/docs/guide/146-30.html#sub:Set-Scale...)) πŸ“·πŸ”¬
408
+ > - 32-bit images will be processed correctly, but appear as mostly white in the image preview window
409
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  )
411
+
412
+ with gr.Accordion("Disclaimer and License", open=False):
413
+ gr.Markdown(
414
  """
415
+ ### Disclaimer
416
+ NCCR licenses the Atom Detection Web-App utilisation "as is" with no express or implied warranty of any kind. NCCR specifically disclaims all express or implied warranties to the fullest extent allowed by applicable law, including without limitation all implied warranties of merchantability, title or fitness for any particular purpose or non-infringement. No oral or written information or advice given by the authors shall create or form the basis of any warranty of any kind.
417
+
418
+ ### License
419
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
420
+
421
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
422
+
423
+ The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.
 
424
  """
425
  )
426
+
427
+ gr.Markdown(
428
+ """
429
+ > To reference the use of this web app in a publication, please refer to the Atom Detection web app and the development described in this publication: K. Rossi et al. Adv. Mater. 2023, [doi:10.1002/adma.202307991](https://doi.org/10.1002/adma.202307991).
430
  """
 
 
 
 
431
  )
432
 
433
+ if __name__ == "__main__":
434
+ block.launch(
435
+ server_port=5001,
436
+ )
 
 
requirements.txt CHANGED
@@ -17,8 +17,8 @@ beautifulsoup4==4.11.2
17
  black==23.1.0
18
  bleach==6.0.0
19
  bokeh==3.1.1
20
- boto3==1.26.89
21
- botocore==1.29.89
22
  cffi==1.15.1
23
  cfgv==3.4.0
24
  charset-normalizer==3.1.0
@@ -43,8 +43,8 @@ fonttools==4.39.0
43
  fqdn==1.5.1
44
  frozenlist==1.3.3
45
  fsspec==2023.3.0
46
- gradio==3.21.0
47
- gradio_client==0.2.1
48
  h11==0.14.0
49
  httpcore==0.16.3
50
  httpx==0.23.3
 
17
  black==23.1.0
18
  bleach==6.0.0
19
  bokeh==3.1.1
20
+ boto3==1.35.18
21
+ botocore==1.35.18
22
  cffi==1.15.1
23
  cfgv==3.4.0
24
  charset-normalizer==3.1.0
 
43
  fqdn==1.5.1
44
  frozenlist==1.3.3
45
  fsspec==2023.3.0
46
+ gradio==4.44.0
47
+ gradio_client==1.3.0
48
  h11==0.14.0
49
  httpcore==0.16.3
50
  httpx==0.23.3