m7n commited on
Commit
755230a
·
verified ·
1 Parent(s): fa8d38f

Changed to self contained example

Browse files
Files changed (1) hide show
  1. app.py +30 -634
app.py CHANGED
@@ -1,655 +1,51 @@
1
- import time
2
- print(f"Starting up: {time.strftime('%Y-%m-%d %H:%M:%S')}")
3
-
4
- # Standard library imports
5
  import os
6
  from pathlib import Path
7
- from datetime import datetime
8
- from itertools import chain
9
-
10
- # Third-party imports
11
- import numpy as np
12
- import pandas as pd
13
- import torch
14
  import gradio as gr
15
-
16
- print(f"Gradio version: {gr.__version__}")
17
-
18
  from fastapi import FastAPI
19
  from fastapi.staticfiles import StaticFiles
20
  import uvicorn
21
- import matplotlib.pyplot as plt
22
- import tqdm
23
- import colormaps
24
- import matplotlib.colors as mcolors
25
- from matplotlib.colors import Normalize
26
-
27
-
28
-
29
- import opinionated # for fonts
30
- plt.style.use("opinionated_rc")
31
-
32
- from sklearn.neighbors import NearestNeighbors
33
-
34
-
35
- def is_running_in_hf_space():
36
- return "SPACE_ID" in os.environ
37
-
38
- if is_running_in_hf_space():
39
- import spaces # necessary to run on Zero.
40
-
41
- import datamapplot
42
- import pyalex
43
-
44
- # Local imports
45
- from openalex_utils import (
46
- openalex_url_to_pyalex_query,
47
- get_field,
48
- process_records_to_df,
49
- openalex_url_to_filename
50
- )
51
- from styles import DATAMAP_CUSTOM_CSS
52
- from data_setup import (
53
- download_required_files,
54
- setup_basemap_data,
55
- setup_mapper,
56
- setup_embedding_model,
57
-
58
- )
59
 
60
- from network_utils import create_citation_graph, draw_citation_graph
61
-
62
-
63
-
64
-
65
- # Configure OpenAlex
66
- pyalex.config.email = "[email protected]"
67
-
68
- print(f"Imports completed: {time.strftime('%Y-%m-%d %H:%M:%S')}")
69
-
70
- # FastAPI setup
71
  app = FastAPI()
72
- static_dir = Path('./static')
73
- static_dir.mkdir(parents=True, exist_ok=True)
74
- app.mount("/static", StaticFiles(directory=static_dir), name="static")
75
-
76
- # Gradio configuration
77
- gr.set_static_paths(paths=["static/"])
78
-
79
- # Resource configuration
80
- REQUIRED_FILES = {
81
- "100k_filtered_OA_sample_cluster_and_positions_supervised.pkl":
82
- "https://huggingface.co/datasets/m7n/intermediate_sci_pickle/resolve/main/100k_filtered_OA_sample_cluster_and_positions_supervised.pkl",
83
- "umap_mapper_250k_random_OA_discipline_tuned_specter_2_params.pkl":
84
- "https://huggingface.co/datasets/m7n/intermediate_sci_pickle/resolve/main/umap_mapper_250k_random_OA_discipline_tuned_specter_2_params.pkl"
85
- }
86
- BASEMAP_PATH = "100k_filtered_OA_sample_cluster_and_positions_supervised.pkl"
87
- MAPPER_PARAMS_PATH = "umap_mapper_250k_random_OA_discipline_tuned_specter_2_params.pkl"
88
- MODEL_NAME = "m7n/discipline-tuned_specter_2_024"
89
-
90
- # Initialize models and data
91
- start_time = time.time()
92
- print("Initializing resources...")
93
-
94
- download_required_files(REQUIRED_FILES)
95
- basedata_df = setup_basemap_data(BASEMAP_PATH)
96
- mapper = setup_mapper(MAPPER_PARAMS_PATH)
97
- model = setup_embedding_model(MODEL_NAME)
98
-
99
- print(f"Resources initialized in {time.time() - start_time:.2f} seconds")
100
-
101
-
102
-
103
- # Setting up decorators for embedding on HF-Zero:
104
- def no_op_decorator(func):
105
- """A no-op (no operation) decorator that simply returns the function."""
106
- def wrapper(*args, **kwargs):
107
- # Do nothing special
108
- return func(*args, **kwargs)
109
- return wrapper
110
-
111
- # Decide which decorator to use based on environment
112
- decorator_to_use = spaces.GPU() if is_running_in_hf_space() else no_op_decorator
113
- #duration=120
114
-
115
- @decorator_to_use
116
- def create_embeddings(texts_to_embedd):
117
- """Create embeddings for the input texts using the loaded model."""
118
- return model.encode(texts_to_embedd, show_progress_bar=True, batch_size=192)
119
-
120
-
121
- def predict(text_input, sample_size_slider, reduce_sample_checkbox, sample_reduction_method,
122
- plot_time_checkbox, locally_approximate_publication_date_checkbox,
123
- download_csv_checkbox, download_png_checkbox,citation_graph_checkbox, progress=gr.Progress()):
124
- """
125
- Main prediction pipeline that processes OpenAlex queries and creates visualizations.
126
-
127
- Args:
128
- text_input (str): OpenAlex query URL
129
- sample_size_slider (int): Maximum number of samples to process
130
- reduce_sample_checkbox (bool): Whether to reduce sample size
131
- sample_reduction_method (str): Method for sample reduction ("Random" or "Order of Results")
132
- plot_time_checkbox (bool): Whether to color points by publication date
133
- locally_approximate_publication_date_checkbox (bool): Whether to approximate publication date locally before plotting.
134
- progress (gr.Progress): Gradio progress tracker
135
-
136
- Returns:
137
- tuple: (link to visualization, iframe HTML)
138
- """
139
- # Check if input is empty or whitespace
140
- print(f"Input: {text_input}")
141
- if not text_input or text_input.isspace():
142
- error_message = "Error: Please enter a valid OpenAlex URL in the 'OpenAlex-search URL'-field"
143
- return [
144
- error_message, # iframe HTML
145
- gr.DownloadButton(label="Download Interactive Visualization", value='html_file_path', visible=False), # html download
146
- gr.DownloadButton(label="Download CSV Data", value='csv_file_path', visible=False), # csv download
147
- gr.DownloadButton(label="Download Static Plot", value='png_file_path', visible=False), # png download
148
- gr.Button(visible=False) # cancel button state
149
- ]
150
-
151
-
152
- # Check if the input is a valid OpenAlex URL
153
-
154
-
155
-
156
- start_time = time.time()
157
- print('Starting data projection pipeline')
158
- progress(0.1, desc="Starting...")
159
-
160
- # Split input into multiple URLs if present
161
- urls = [url.strip() for url in text_input.split(';')]
162
- records = []
163
- total_query_length = 0
164
-
165
- # Use first URL for filename
166
- first_query, first_params = openalex_url_to_pyalex_query(urls[0])
167
- filename = openalex_url_to_filename(urls[0])
168
- print(f"Filename: {filename}")
169
-
170
- # Process each URL
171
- for i, url in enumerate(urls):
172
- query, params = openalex_url_to_pyalex_query(url)
173
- query_length = query.count()
174
- total_query_length += query_length
175
- print(f'Requesting {query_length} entries from query {i+1}/{len(urls)}...')
176
-
177
- target_size = sample_size_slider if reduce_sample_checkbox and sample_reduction_method == "First n samples" else query_length
178
- records_per_query = 0
179
-
180
- should_break = False
181
- for page in query.paginate(per_page=200, n_max=None):
182
- for record in page:
183
- records.append(record)
184
- records_per_query += 1
185
- progress(0.1 + (0.2 * len(records) / (total_query_length)),
186
- desc=f"Getting data from query {i+1}/{len(urls)}...")
187
-
188
- if reduce_sample_checkbox and sample_reduction_method == "First n samples" and records_per_query >= target_size:
189
- should_break = True
190
- break
191
- if should_break:
192
- break
193
- if should_break:
194
- break
195
- print(f"Query completed in {time.time() - start_time:.2f} seconds")
196
-
197
- # Process records
198
- processing_start = time.time()
199
- records_df = process_records_to_df(records)
200
-
201
- if reduce_sample_checkbox and sample_reduction_method != "All":
202
- sample_size = min(sample_size_slider, len(records_df))
203
- if sample_reduction_method == "n random samples":
204
- records_df = records_df.sample(sample_size)
205
- elif sample_reduction_method == "First n samples":
206
- records_df = records_df.iloc[:sample_size]
207
- print(f"Records processed in {time.time() - processing_start:.2f} seconds")
208
-
209
- # Create embeddings
210
- embedding_start = time.time()
211
- progress(0.3, desc="Embedding Data...")
212
- texts_to_embedd = [f"{title} {abstract}" for title, abstract
213
- in zip(records_df['title'], records_df['abstract'])]
214
- embeddings = create_embeddings(texts_to_embedd)
215
- print(f"Embeddings created in {time.time() - embedding_start:.2f} seconds")
216
-
217
- # Project embeddings
218
- projection_start = time.time()
219
- progress(0.5, desc="Project into UMAP-embedding...")
220
- umap_embeddings = mapper.transform(embeddings)
221
- records_df[['x','y']] = umap_embeddings
222
- print(f"Projection completed in {time.time() - projection_start:.2f} seconds")
223
-
224
- # Prepare visualization data
225
- viz_prep_start = time.time()
226
- progress(0.6, desc="Preparing visualization data...")
227
-
228
- basedata_df['color'] = '#ced4d211'
229
-
230
- if not plot_time_checkbox:
231
- records_df['color'] = '#5e2784'
232
- else:
233
- cmap = colormaps.haline
234
- if not locally_approximate_publication_date_checkbox:
235
- # Create color mapping based on publication years
236
- years = pd.to_numeric(records_df['publication_year'])
237
- norm = mcolors.Normalize(vmin=years.min(), vmax=years.max())
238
- records_df['color'] = [mcolors.to_hex(cmap(norm(year))) for year in years]
239
-
240
- else:
241
- n_neighbors = 10 # Adjust this value to control smoothing
242
- nn = NearestNeighbors(n_neighbors=n_neighbors)
243
- nn.fit(umap_embeddings)
244
- distances, indices = nn.kneighbors(umap_embeddings)
245
-
246
- # Calculate local average publication year for each point
247
- local_years = np.array([
248
- np.mean(records_df['publication_year'].iloc[idx])
249
- for idx in indices
250
- ])
251
- norm = mcolors.Normalize(vmin=local_years.min(), vmax=local_years.max())
252
- records_df['color'] = [mcolors.to_hex(cmap(norm(year))) for year in local_years]
253
-
254
-
255
-
256
- stacked_df = pd.concat([basedata_df, records_df], axis=0, ignore_index=True)
257
- stacked_df = stacked_df.fillna("Unlabelled")
258
- stacked_df['parsed_field'] = [get_field(row) for ix, row in stacked_df.iterrows()]
259
- extra_data = pd.DataFrame(stacked_df['doi'])
260
- print(f"Visualization data prepared in {time.time() - viz_prep_start:.2f} seconds")
261
- if citation_graph_checkbox:
262
- citation_graph_start = time.time()
263
- citation_graph = create_citation_graph(records_df)
264
- graph_file_name = f"{filename}_citation_graph.jpg"
265
- graph_file_path = static_dir / graph_file_name
266
- draw_citation_graph(citation_graph,path=graph_file_path,bundle_edges=True,
267
- min_max_coordinates=[np.min(stacked_df['x']),np.max(stacked_df['x']),np.min(stacked_df['y']),np.max(stacked_df['y'])])
268
- print(f"Citation graph created and saved in {time.time() - citation_graph_start:.2f} seconds")
269
-
270
-
271
-
272
-
273
- # Create and save plot
274
- plot_start = time.time()
275
- progress(0.7, desc="Creating interactive plot...")
276
- # Create a solid black colormap
277
- black_cmap = mcolors.LinearSegmentedColormap.from_list('black', ['#000000', '#000000'])
278
-
279
-
280
- plot = datamapplot.create_interactive_plot(
281
- stacked_df[['x','y']].values,
282
- np.array(stacked_df['cluster_2_labels']),
283
- np.array(['Unlabelled' if pd.isna(x) else x for x in stacked_df['parsed_field']]),
284
-
285
- hover_text=[str(row['title']) for ix, row in stacked_df.iterrows()],
286
- marker_color_array=stacked_df['color'],
287
- use_medoids=False, # Switch back once efficient mediod caclulation comes out!
288
- width=1000,
289
- height=1000,
290
- point_radius_min_pixels=1,
291
- text_outline_width=5,
292
- point_hover_color='#5e2784',
293
- point_radius_max_pixels=7,
294
- cmap=black_cmap,
295
- background_image=graph_file_name if citation_graph_checkbox else None,
296
- #color_label_text=False,
297
- font_family="Roboto Condensed",
298
- font_weight=600,
299
- tooltip_font_weight=600,
300
- tooltip_font_family="Roboto Condensed",
301
- extra_point_data=extra_data,
302
- on_click="window.open(`{doi}`)",
303
- custom_css=DATAMAP_CUSTOM_CSS,
304
- initial_zoom_fraction=.8,
305
- enable_search=False,
306
- offline_mode=False
307
- )
308
-
309
- # Save plot
310
- html_file_name = f"{filename}.html"
311
- html_file_path = static_dir / html_file_name
312
- plot.save(html_file_path)
313
- print(f"Plot created and saved in {time.time() - plot_start:.2f} seconds")
314
-
315
-
316
-
317
- # Save additional files if requested
318
- csv_file_path = static_dir / f"{filename}.csv"
319
- png_file_path = static_dir / f"{filename}.png"
320
-
321
- if download_csv_checkbox:
322
- # Export relevant column
323
- export_df = records_df[['title', 'abstract', 'doi', 'publication_year', 'x', 'y','id','primary_topic']]
324
- export_df['parsed_field'] = [get_field(row) for ix, row in export_df.iterrows()]
325
- export_df['referenced_works'] = [', '.join(x) for x in records_df['referenced_works']]
326
- export_df.to_csv(csv_file_path, index=False)
327
-
328
- if download_png_checkbox:
329
- png_start_time = time.time()
330
- print("Starting PNG generation...")
331
-
332
- # Sample and prepare data
333
- sample_prep_start = time.time()
334
- sample_to_plot = basedata_df#.sample(20000)
335
- labels1 = np.array(sample_to_plot['cluster_2_labels'])
336
- labels2 = np.array(['Unlabelled' if pd.isna(x) else x for x in sample_to_plot['parsed_field']])
337
-
338
- ratio = 0.6
339
- mask = np.random.random(size=len(labels1)) < ratio
340
- combined_labels = np.where(mask, labels1, labels2)
341
-
342
- # Get the 30 most common labels
343
- unique_labels, counts = np.unique(combined_labels, return_counts=True)
344
- top_30_labels = set(unique_labels[np.argsort(counts)[-50:]])
345
-
346
- # Replace less common labels with 'Unlabelled'
347
- combined_labels = np.array(['Unlabelled' if label not in top_30_labels else label for label in combined_labels])
348
- #combined_labels = np.array(['Unlabelled' for label in combined_labels])
349
- #if label not in top_30_labels else label
350
- colors_base = ['#536878' for _ in range(len(labels1))]
351
- print(f"Sample preparation completed in {time.time() - sample_prep_start:.2f} seconds")
352
-
353
- # Create main plot
354
- print(labels1)
355
- print(labels2)
356
- print(sample_to_plot[['x','y']].values)
357
- print(combined_labels)
358
-
359
- main_plot_start = time.time()
360
- fig, ax = datamapplot.create_plot(
361
- sample_to_plot[['x','y']].values,
362
- combined_labels,
363
- label_wrap_width=12,
364
- label_over_points=True,
365
- dynamic_label_size=True,
366
- use_medoids=False, # Switch back once efficient mediod caclulation comes out!
367
- point_size=2,
368
- marker_color_array=colors_base,
369
- force_matplotlib=True,
370
- max_font_size=12,
371
- min_font_size=4,
372
- min_font_weight=100,
373
- max_font_weight=300,
374
- font_family="Roboto Condensed",
375
- color_label_text=False, add_glow=False,
376
- highlight_labels=list(np.unique(labels1)),
377
- label_font_size=8,
378
- highlight_label_keywords={"fontsize": 12, "fontweight": "bold", "bbox":{"boxstyle":"circle", "pad":0.75,'alpha':0.}},
379
- )
380
- print(f"Main plot creation completed in {time.time() - main_plot_start:.2f} seconds")
381
-
382
-
383
- if citation_graph_checkbox:
384
-
385
- # Read and add the graph image
386
- graph_img = plt.imread(graph_file_path)
387
- ax.imshow(graph_img, extent=[np.min(stacked_df['x']),np.max(stacked_df['x']),np.min(stacked_df['y']),np.max(stacked_df['y'])],
388
- alpha=0.9, aspect='auto')
389
-
390
-
391
-
392
- # Time-based visualization
393
- scatter_start = time.time()
394
- if plot_time_checkbox:
395
- if locally_approximate_publication_date_checkbox:
396
- scatter = plt.scatter(
397
- umap_embeddings[:,0],
398
- umap_embeddings[:,1],
399
- c=local_years,
400
- cmap=colormaps.haline,
401
- alpha=0.8,
402
- s=5
403
- )
404
- else:
405
- years = pd.to_numeric(records_df['publication_year'])
406
- scatter = plt.scatter(
407
- umap_embeddings[:,0],
408
- umap_embeddings[:,1],
409
- c=years,
410
- cmap=colormaps.haline,
411
- alpha=0.8,
412
- s=5
413
- )
414
- plt.colorbar(scatter, shrink=0.5, format='%d')
415
- else:
416
- scatter = plt.scatter(
417
- umap_embeddings[:,0],
418
- umap_embeddings[:,1],
419
- c=records_df['color'],
420
- alpha=0.8,
421
- s=5
422
- )
423
- print(f"Scatter plot creation completed in {time.time() - scatter_start:.2f} seconds")
424
-
425
- # Save plot
426
- save_start = time.time()
427
- plt.axis('off')
428
- png_file_path = static_dir / f"{filename}.png"
429
- plt.savefig(png_file_path, dpi=300, bbox_inches='tight')
430
- plt.close()
431
- print(f"Plot saving completed in {time.time() - save_start:.2f} seconds")
432
-
433
- print(f"Total PNG generation completed in {time.time() - png_start_time:.2f} seconds")
434
-
435
-
436
-
437
-
438
-
439
-
440
-
441
- progress(1.0, desc="Done!")
442
- print(f"Total pipeline completed in {time.time() - start_time:.2f} seconds")
443
-
444
- iframe = f"""<iframe src="/static/{html_file_name}" width="100%" height="1000px"></iframe>"""
445
-
446
- # Return iframe and download buttons with appropriate visibility
447
- return [
448
- iframe,
449
- gr.DownloadButton(label="Download Interactive Visualization", value=html_file_path, visible=True, variant='secondary'),
450
- gr.DownloadButton(label="Download CSV Data", value=csv_file_path, visible=download_csv_checkbox, variant='secondary'),
451
- gr.DownloadButton(label="Download Static Plot", value=png_file_path, visible=download_png_checkbox, variant='secondary'),
452
- gr.Button(visible=False) # Return hidden state for cancel button
453
- ]
454
-
455
-
456
- predict.zerogpu = True
457
-
458
-
459
 
460
- theme = gr.themes.Monochrome(
461
- font=[gr.themes.GoogleFont("Roboto Condensed"), "ui-sans-serif", "system-ui", "sans-serif"],
462
- text_size="lg",
463
- ).set(
464
- button_secondary_background_fill="white",
465
- button_secondary_background_fill_hover="#f3f4f6",
466
- button_secondary_border_color="black",
467
- button_secondary_text_color="black",
468
- button_border_width="2px",
469
- )
470
-
471
-
472
- # Gradio interface setup
473
- with gr.Blocks(theme=theme, css="""
474
- .gradio-container a {
475
- color: black !important;
476
- text-decoration: none !important; /* Force remove default underline */
477
- font-weight: bold;
478
- transition: color 0.2s ease-in-out, border-bottom-color 0.2s ease-in-out;
479
- display: inline-block; /* Enable proper spacing for descenders */
480
- line-height: 1.1; /* Adjust line height */
481
- padding-bottom: 2px; /* Add space for descenders */
482
- }
483
- .gradio-container a:hover {
484
- color: #b23310 !important;
485
- border-bottom: 3px solid #b23310; /* Wider underline, only on hover */
486
- }
487
- """) as demo:
488
- gr.Markdown("""
489
- <div style="max-width: 100%; margin: 0 auto;">
490
- <br>
491
-
492
- # OpenAlex Mapper
493
-
494
- OpenAlex Mapper is a way of projecting search queries from the amazing OpenAlex database on a background map of randomly sampled papers from OpenAlex, which allows you to easily investigate interdisciplinary connections. OpenAlex Mapper was developed by [Maximilian Noichl](https://maxnoichl.eu) and [Andrea Loettgers](https://unige.academia.edu/AndreaLoettgers) at the [Possible Life project](http://www.possiblelife.eu/).
495
-
496
- To use OpenAlex Mapper, first head over to [OpenAlex](https://openalex.org/) and search for something that interests you. For example, you could search for all the papers that make use of the [Kuramoto model](https://openalex.org/works?page=1&filter=default.search%3A%22Kuramoto%20Model%22), for all the papers that were published by researchers at [Utrecht University in 2019](https://openalex.org/works?page=1&filter=authorships.institutions.lineage%3Ai193662353,publication_year%3A2019), or for all the papers that cite Wittgenstein's [Philosophical Investigations](https://openalex.org/works?page=1&filter=cites%3Aw4251395411). Then you copy the URL to that search query into the OpenAlex search URL box below and click "Run Query." It will download all of these records from OpenAlex and embed them on our interactive map. As the embedding step is a little expensive, computationally, it's often a good idea to play around with smaller samples, before running a larger analysis. After a little time, that map will appear and be available for you to interact with and download. You can find more explanations in the FAQs below.
497
- </div>
498
- """)
499
-
500
-
501
- with gr.Row():
502
- with gr.Column(scale=1):
503
- with gr.Row():
504
- run_btn = gr.Button("Run Query", variant='primary')
505
- cancel_btn = gr.Button("Cancel", visible=False, variant='secondary')
506
-
507
- # Create separate download buttons
508
- html_download = gr.DownloadButton("Download Interactive Visualization", visible=False, variant='secondary')
509
- csv_download = gr.DownloadButton("Download CSV Data", visible=False, variant='secondary')
510
- png_download = gr.DownloadButton("Download Static Plot", visible=False, variant='secondary')
511
-
512
- text_input = gr.Textbox(label="OpenAlex-search URL",
513
- info="Enter the URL to an OpenAlex-search.")
514
-
515
- gr.Markdown("### Sample Settings")
516
- reduce_sample_checkbox = gr.Checkbox(
517
- label="Reduce Sample Size",
518
- value=True,
519
- info="Reduce sample size."
520
- )
521
- sample_reduction_method = gr.Dropdown(
522
- ["All", "First n samples", "n random samples"],
523
- label="Sample Selection Method",
524
- value="First n samples",
525
- info="How to choose the samples to keep."
526
- )
527
- sample_size_slider = gr.Slider(
528
- label="Sample Size",
529
- minimum=500,
530
- maximum=20000,
531
- step=10,
532
- value=1000,
533
- info="How many samples to keep.",
534
- visible=True
535
- )
536
-
537
- gr.Markdown("### Plot Settings")
538
- plot_time_checkbox = gr.Checkbox(
539
- label="Plot Time",
540
- value=True,
541
- info="Colour points by their publication date."
542
- )
543
- locally_approximate_publication_date_checkbox = gr.Checkbox(
544
- label="Locally Approximate Publication Date",
545
- value=True,
546
- info="Colour points by the average publication date in their area."
547
- )
548
-
549
- gr.Markdown("### Download Options")
550
- download_csv_checkbox = gr.Checkbox(
551
- label="Generate CSV Export",
552
- value=False,
553
- info="Export the data as CSV file"
554
- )
555
- download_png_checkbox = gr.Checkbox(
556
- label="Generate Static PNG Plot",
557
- value=False,
558
- info="Export a static PNG visualization. This will make things slower!"
559
- )
560
-
561
- gr.Markdown("### Citation graph")
562
- citation_graph_checkbox = gr.Checkbox(
563
- label="Add Citation Graph",
564
- value=False,
565
- info="Adds a citation graph of the sample to the plot."
566
- )
567
-
568
-
569
-
570
- with gr.Column(scale=2):
571
- html = gr.HTML(
572
- value='<div style="width: 100%; height: 1000px; display: flex; justify-content: center; align-items: center; border: 1px solid #ccc; background-color: #f8f9fa;"><p style="font-size: 1.2em; color: #666;">The visualization map will appear here after running a query</p></div>',
573
- label="",
574
- show_label=False
575
- )
576
- gr.Markdown("""
577
- <div style="max-width: 100%; margin: 0 auto;">
578
-
579
- # FAQs
580
-
581
- ## Who made this?
582
-
583
- This project was developed by [Maximilian Noichl](https://maxnoichl.eu) (Utrecht University), in cooperation with Andrea Loettger and Tarja Knuuttila at the [Possible Life project](http://www.possiblelife.eu/), at the University of Vienna. If this project is useful in any way for your research, we would appreciate citation of **...**
584
-
585
- This project received funding from the European Research Council under the European Union's Horizon 2020 research and innovation programme (LIFEMODE project, grant agreement No. 818772).
586
-
587
- ## How does it work?
588
-
589
- The base map for this project is developed by randomly downloading 250,000 articles from OpenAlex, then embedding their abstracts using our [fine-tuned](https://huggingface.co/m7n/discipline-tuned_specter_2_024) version of the [specter-2](https://huggingface.co/allenai/specter2_aug2023refresh_base) language model, running these embeddings through [UMAP](https://umap-learn.readthedocs.io/en/latest/) to give us a two-dimensional representation, and displaying that in an interactive window using [datamapplot](https://datamapplot.readthedocs.io/en/latest/index.html). After the data for your query is downloaded from OpenAlex, it then undergoes the exact same process, but the pre-trained UMAP model from earlier is used to project your new data points onto this original map, showing where they would show up if they were included in the original sample. For more details, you can take a look at the method section of this paper: **...**
590
-
591
- ## I want to add multiple queries at once!
592
-
593
- That can be a good idea, e. g. if your interested in a specific paper, as well as all the papers that cite it. Just add the queries to the query box and separate them with a ";" without any spaces in between!
594
-
595
- ## I think I found a mistake in the map.
596
 
597
- There are various considerations to take into account when working with this map:
 
598
 
599
- 1. The language model we use is fine-tuned to separate disciplines from each other, but of course, disciplines are weird, partially subjective social categories, so what the model has learned might not always correspond perfectly to what you would expect to see.
 
600
 
601
- 2. When pressing down a really high-dimensional space into a low-dimensional one, there will be trade-offs. For example, we see this big ring structure of the sciences on the map, but in the middle of the map there is a overly stretchedstring of bioinformaticsthat stretches from computer science at the bottom up to the life sciences clusters at the top. This is one of the areas where the UMAP algorithm had trouble pressing our high-dimensional dataset into a low-dimensional space. For more information on how to read a UMAP plot, I recommend looking into ["Understanding UMAP"](https://pair-code.github.io/understanding-umap/) by Andy Coenen & Adam Pearce.
 
 
 
 
 
602
 
603
- 3. Finally, the labels we're using for the regions of this plot are created from OpenAlex's own labels of sub-disciplines. They give a rough indication of the papers that could be expected in this broad area of the map, but they are not necessarily the perfect label for the articles that are precisely below them. They are just located at the median point of a usually much larger, much broader, and fuzzier category, so they should always be taken with quite a big grain of salt.
604
-
605
- </div>
606
- """)
607
 
608
- def update_slider_visibility(method):
609
- return gr.Slider(visible=(method != "All"))
610
 
611
- sample_reduction_method.change(
612
- fn=update_slider_visibility,
613
- inputs=[sample_reduction_method],
614
- outputs=[sample_size_slider]
615
- )
616
-
617
- def show_cancel_button():
618
- return gr.Button(visible=True)
619
 
620
- def hide_cancel_button():
621
- return gr.Button(visible=False)
622
-
623
- show_cancel_button.zerogpu = True
624
- hide_cancel_button.zerogpu = True
625
- predict.zerogpu = True
626
-
627
- # Update the run button click event
628
- run_event = run_btn.click(
629
- fn=show_cancel_button,
630
- outputs=cancel_btn,
631
- queue=False
632
- ).then(
633
- fn=predict,
634
- inputs=[text_input, sample_size_slider, reduce_sample_checkbox,
635
- sample_reduction_method, plot_time_checkbox,
636
- locally_approximate_publication_date_checkbox,
637
- download_csv_checkbox, download_png_checkbox,citation_graph_checkbox],
638
- outputs=[html, html_download, csv_download, png_download, cancel_btn]
639
- )
640
-
641
- # Add cancel button click event
642
- cancel_btn.click(
643
- fn=hide_cancel_button,
644
- outputs=cancel_btn,
645
- cancels=[run_event],
646
- queue=False # Important to make the button hide immediately
647
  )
648
 
 
 
649
 
650
-
651
- # Mount and run app
652
- app = gr.mount_gradio_app(app, demo, path="/",ssr_mode=False)
653
-
654
  if __name__ == "__main__":
655
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
1
  import os
2
  from pathlib import Path
 
 
 
 
 
 
 
3
  import gradio as gr
 
 
 
4
  from fastapi import FastAPI
5
  from fastapi.staticfiles import StaticFiles
6
  import uvicorn
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ # Create FastAPI app
 
 
 
 
 
 
 
 
 
 
9
  app = FastAPI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ # Create and configure static directory
12
+ static_dir = Path("./static")
13
+ static_dir.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ # Mount static directory to FastAPI
16
+ app.mount("/static", StaticFiles(directory="static"), name="static")
17
 
18
+ # Tell Gradio which paths are allowed to be served
19
+ os.environ["GRADIO_ALLOWED_PATHS"] = str(static_dir.resolve())
20
 
21
+ def process_and_save(text):
22
+ """Simple function that saves text to a file and returns file path"""
23
+ # Save text to a file in static directory
24
+ file_path = static_dir / "output.txt"
25
+ with open(file_path, "w") as f:
26
+ f.write(text)
27
 
28
+ # Return file path for download
29
+ return gr.File(value=file_path)
 
 
30
 
31
+ # Mark function as not requiring GPU
32
+ process_and_save.zerogpu = True
33
 
34
+ # Create Gradio interface
35
+ with gr.Blocks() as demo:
36
+ text_input = gr.Textbox(label="Enter some text")
37
+ submit_btn = gr.Button("Save and Download")
38
+ output = gr.File(label="Download File")
 
 
 
39
 
40
+ submit_btn.click(
41
+ fn=process_and_save,
42
+ inputs=text_input,
43
+ outputs=output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  )
45
 
46
+ # Mount Gradio app to FastAPI
47
+ app = gr.mount_gradio_app(app, demo, path="/")
48
 
49
+ # Run server
 
 
 
50
  if __name__ == "__main__":
51
  uvicorn.run(app, host="0.0.0.0", port=7860)