Gurveer05 commited on
Commit
1da2a5a
1 Parent(s): 46bc94b

Added interface

Browse files
__init__.py ADDED
File without changes
data/test.txt CHANGED
@@ -1 +1,2 @@
1
- ATGGACAAACTCTAGTAACGGT
 
 
1
+ ATGGACAAACTCTAGTAACGGT
2
+ ATGGACAAACTCTAGTAACGGTATGGACAAACTCTAGTAACGGT
data/test_1.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ ATGGACAAACTCTAGTAACGGT
2
+ ATGGACAAACTCTAGTAACGGTATGGACAAACTCTAGTAACGGT
data/test_2.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ GGCTGGGAGCTCCTCCATTCCATACCTTTTGATCCATTGATCGGATGGTCATGGCCGAGAGAGATGCAATCTGAAACGTCCTTTATACATTAGAGTTATATTGAATGATCACCCTATTAGGTCGAAGAGCCGGTGACTTGCATCAGGCTTAGTGCATATGCTTTTGGTTCGTCAACCTTCACCAAAAGGAAGGGGGCATATGTTGAATAGCAAAAATGACACCCGGACCGTCCGCCCTAGGGCCGGATGGTCCGCGGTCCGAACTATCCACGGTGGCGACACAGACGGTCTACACGTACGCAGAATCAGATAGGGTTCCGAATTTCTAGCGGGATTTGTTAGCAAAGCATGTAGGAATAACTTGATGACCGACTTGTAACAGGTCTAGACCTCCACCTTTTATCTGGATGAAGGGGTACAACCGATTGAACCTCTCACAATCGATCCAATCAAATCTACTTATCAATTACCTTATTTGCATCATTCTTCTAATTCCTAGGAGTAGGAGTAATTTTGCCTTAGGTTTAGATCTAGTTTAGTCTTCCACAACAACCTCTTCTTTGACTCTACGCCGATTAGAGGAGCACCAGGCGGCCTGCCGACCCAGAGCACGCCTTGGAACTCTCCCCCTCGATGGGATCTCTCCCGTGGCGAGTTCTAGGATTCTCCACGGAGATGAAGACCCTCTCCACCACTGCGGACCGTCCGGCCCTAGGCGCGGACACATCCGAAAGCCTGCATAGGAGGATCCGCTCTTGTGCCCTGTGCCGCGGACCGTTCGCGCCTCCGTAGAGAGCACACCGCGCAGGTGGTTCGTTTCAGTGGTTGGCATCCAGATCGGCGCCAACAGGGTGATTTGGTTGTGTGAATTTTACAACGTTTAAACTGATGGGTGTATAGATATAGATATAGATTTTAAAAAACAAAAGAAATAAAAAAATGCCCAACGGGGAAGGTAGTATTTGGTCTGTACTGTTTCCTTGCGTGATGGACATGGACT
2
+ GGCCCAGCGCACAGCTGTGATAGTGTGGCATTGTCCGGGCGCGCGCGCCCGCTCTCTCTCATCGTACCGTACGTCCGGGCCTTCTTCTCTCTCCGTGCTGTTGCCTCCTCCCTCCACCGATCTACCACGTACGACTACTGTACATTCTTGCCTTGTGAATTTACAGTGAATTGTATATATAAATATATATACATACATACAGTAGGTGTAGATATATCAATATATATATACAGAGATGGAGAGGGAGGGGGCATGAGGCATGCATATTCTGCAAAAAAAAAACATTTTATTTTGTCAGAGCTTCATGCAATTTGGGCCTACTAGCTGGCAGGCTAGCTAGCTAGGGTGGATGCGATCGTCGACCAGGGCTGGCAGCGACAAACCACCTGTGGTGGCAGTCTGCCTCCCCCCTTTGTTTTCTCCTGCCCCCCTGCTCTTCTTCCCCAAGTAGCTAGCAGGGAGTAGAGTAATATGCGTACCCTACGCAGTAATTAATCTTCACTGGATTAAATTGCGTGTATATATATAGAGAGAGATGCGTGAGCTGTCTACGATTATTGTCACAGACGTGCTGTAGCCAAATAATCCTAGCGACCGCGAGGGTGGAGCGGACTACGTACGGTCTACGTCTACGTCTACGGCGGTGGGTGATTAGAATTTCCTCCGATCCACTGATTACCAGCCGGCCTGTACATGTACATCTCTGATTCTCTGTGCGTGTTTAATTTTATTACTGCTGACTTGAGGAGCATGGAGGAGGATATGCATGCGTGCGTACATACGTCTGTCTGTCGTCGTCCATCACCGCGCGCCGCCCTCGCCTCTCTTTCTCACATCATCTTCTTGCATTGGTCCTCATCTAGCCTGCTGCCTGCCATACCTAGCAGCGTTTCACTTCACTTCACCGCCGCCGATGGATGCCCATGCTCACCGGTCTTCCCCCGCCCCCTCACCGCCCTATTCTAGCTATTTAGTGCTGCTAGCCTAGCTCTACTGCTAC
module/__init__.py ADDED
File without changes
module/metrics.py CHANGED
@@ -40,6 +40,8 @@ def get_predictions(
40
  outputs = model(**inputs)
41
  del inputs # to free up space on GPU
42
  logits = outputs[0]
43
- pred_labels.append([round(e, 4) for e in logits.cpu().tolist()[0]])
 
 
44
 
45
  return pred_labels
 
40
  outputs = model(**inputs)
41
  del inputs # to free up space on GPU
42
  logits = outputs[0]
43
+ logits = logits.cpu().tolist()
44
+ for i in range(len(logits)):
45
+ pred_labels.append([round(e, 4) for e in logits[i]])
46
 
47
  return pred_labels
module/utils.py CHANGED
@@ -11,7 +11,8 @@ from pathlib import PosixPath, Path
11
  import importlib as im
12
  import json
13
  import pickle
14
-
 
15
  import pandas as pd
16
  import numpy as np
17
  from IPython.display import display
@@ -41,21 +42,21 @@ def preprocess_genex(genex_data: pd.DataFrame, settings: dict) -> pd.DataFrame:
41
  return genex_data
42
 
43
  def get_args(
44
- data_dir=data_final / "transformer" / "seq",
45
- train_data="all_seqs_train.txt",
46
- eval_data=None,
47
- test_data="all_seqs_test.txt",
48
- output_dir=models / "transformer" / "language-model",
49
- model_name=None,
50
- pretrained_model=None,
51
- tokenizer_dir=None,
52
- log_offset=None,
53
- preprocessor=None,
54
- filter_empty=False,
55
- hyperparam_search_metrics=None,
56
- hyperparam_search_trials=None,
57
- transformation=None,
58
- output_mode=None,
59
  ) -> argparse.Namespace:
60
  """Use Python's ArgumentParser to create a namespace from (optional) user input
61
 
@@ -207,7 +208,7 @@ def get_args(
207
  default=None,
208
  )
209
  parser.add_argument("--batch-norm", action="store_true", default=False)
210
- args = parser.parse_args()
211
 
212
  if args.pretrained_model and not args.pretrained_model.startswith("/"):
213
  args.pretrained_model = str(Path.cwd() / args.pretrained_model)
 
11
  import importlib as im
12
  import json
13
  import pickle
14
+ from pydantic import *
15
+ from typing import List
16
  import pandas as pd
17
  import numpy as np
18
  from IPython.display import display
 
42
  return genex_data
43
 
44
  def get_args(
45
+ data_dir: DirectoryPath = data_final / "transformer" / "seq",
46
+ train_data: FilePath = "all_seqs_train.txt",
47
+ eval_data: FilePath = None,
48
+ test_data: FilePath = "all_seqs_test.txt",
49
+ output_dir: DirectoryPath = models / "transformer" / "language-model",
50
+ model_name: str = None,
51
+ pretrained_model: FilePath = None,
52
+ tokenizer_dir: DirectoryPath = None,
53
+ log_offset: int = None,
54
+ preprocessor: str = None,
55
+ filter_empty: bool = False,
56
+ hyperparam_search_metrics: List[str] = None,
57
+ hyperparam_search_trials: int = None,
58
+ transformation: str = None,
59
+ output_mode: str = None,
60
  ) -> argparse.Namespace:
61
  """Use Python's ArgumentParser to create a namespace from (optional) user input
62
 
 
208
  default=None,
209
  )
210
  parser.add_argument("--batch-norm", action="store_true", default=False)
211
+ args, unknown = parser.parse_known_args()
212
 
213
  if args.pretrained_model and not args.pretrained_model.startswith("/"):
214
  args.pretrained_model = str(Path.cwd() / args.pretrained_model)
scripts/__init__.py ADDED
File without changes
scripts/app.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile
2
+ from fastapi.responses import HTMLResponse, JSONResponse
3
+ # from fastapi.staticfiles import StaticFiles
4
+ from starlette.requests import Request
5
+ # from fastapi.templating import Jinja2Templates
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import *
8
+
9
+ from FloraBERT.module import config, transformers_utility as tr, utils, metrics, dataio
10
+ # from prettytable import PrettyTable
11
+ import numpy as np
12
+
13
+ app = FastAPI()
14
+ app.add_middleware(
15
+ CORSMiddleware,
16
+ allow_origins=["*"],
17
+ allow_credentials=True,
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ )
21
+ # app.mount("FloraBERT.static", StaticFiles(directory="FloraBERT.static"), name="static")
22
+ # templates = Jinja2Templates(directory="templates")
23
+
24
+ # table = PrettyTable()
25
+ TOKENIZER_DIR = config.models / "byte-level-bpe-tokenizer"
26
+ PRETRAINED_MODEL = config.models / "transformer" / "prediction-model" / "saved_model.pth"
27
+ DATA_DIR = config.data
28
+
29
+ def load_model(args, settings):
30
+ return tr.load_model(
31
+ args.model_name,
32
+ args.tokenizer_dir,
33
+ pretrained_model=args.pretrained_model,
34
+ log_offset=args.log_offset,
35
+ **settings,
36
+ )
37
+
38
+ @app.get("/", response_class=HTMLResponse)
39
+ def read_root(request: Request):
40
+ return templates.TemplateResponse("index.html", {"request": request})
41
+
42
+ @app.post("/uploadfile/")
43
+ async def create_upload_file(file: UploadFile = File(...)):
44
+ file_path = DATA_DIR / file.filename
45
+ with open(file_path, "wb") as f:
46
+ f.write(file.file.read())
47
+ return {"filename": file.filename}
48
+
49
+ @app.get("/process/{filename}", response_class=HTMLResponse)
50
+ def process_file(request: Request, filename: str):
51
+ file_path = DATA_DIR / filename
52
+ preds = main(
53
+ data_dir=DATA_DIR,
54
+ train_data=file_path,
55
+ test_data=file_path,
56
+ pretrained_model=PRETRAINED_MODEL,
57
+ tokenizer_dir=TOKENIZER_DIR,
58
+ model_name="roberta-pred-mean-pool",
59
+ )
60
+ predictions = []
61
+ for i in range(len(preds)):
62
+ predictions.append([{"tissue": config.tissues[j], "prediction": preds[i][j] } for j in range(8)])
63
+ # print(predictions)
64
+ return JSONResponse(content=predictions)
65
+
66
+ def main(data_dir: str, train_data: str, test_data: str, pretrained_model: str, tokenizer_dir: str, model_name: str):
67
+ args = utils.get_args(
68
+ data_dir=data_dir,
69
+ train_data=train_data,
70
+ test_data=test_data,
71
+ pretrained_model=pretrained_model,
72
+ tokenizer_dir=tokenizer_dir,
73
+ model_name=model_name,
74
+ )
75
+
76
+ settings = utils.get_model_settings(config.settings, args)
77
+ if args.output_mode:
78
+ settings["output_mode"] = args.output_mode
79
+ if args.tissue_subset is not None:
80
+ settings["num_labels"] = len(args.tissue_subset)
81
+
82
+ print("Loading model...")
83
+ config_obj, tokenizer, model = load_model(args, settings)
84
+
85
+ print("Loading data...")
86
+ datasets = dataio.load_datasets(
87
+ tokenizer,
88
+ args.train_data,
89
+ eval_data=args.eval_data,
90
+ test_data=args.test_data,
91
+ seq_key="text",
92
+ file_type="text",
93
+ filter_empty=args.filter_empty,
94
+ shuffle=False,
95
+ )
96
+ dataset_test = datasets["train"]
97
+
98
+ print("Getting predictions:")
99
+ preds = np.exp(np.array(metrics.get_predictions(model, dataset_test))) - 1
100
+ # print(preds)
101
+ # for e in preds:
102
+ # table.add_row(e)
103
+ # print(table)
104
+
105
+ return preds.tolist()
prediction.py → scripts/prediction.py RENAMED
File without changes
templates/index.html ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/index.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <title>File Upload</title>
6
+ <!-- Add Bootstrap CDN link for styling -->
7
+ <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
8
+ <!-- Add these links to the head section of your HTML -->
9
+ <link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet">
10
+ <link href="https://fonts.googleapis.com/css?family=Lora:400,700" rel="stylesheet">
11
+
12
+ <style>
13
+ /* Custom CSS for fade-in transition effect */
14
+ .card {
15
+ opacity: 0;
16
+ transition: opacity 0.5s ease-in-out;
17
+ }
18
+
19
+ .card.show {
20
+ opacity: 1;
21
+ }
22
+ body {
23
+ font-family: 'Lora', sans-serif;
24
+ background-color: #eaf4ea; /* Light green background */
25
+ color: #333; /* Dark text color */
26
+ }
27
+
28
+ h1, h2, h3, h4, h5, h6 {
29
+ font-family: 'Roboto', serif;
30
+ font-weight: bold;
31
+ }
32
+
33
+ .container {
34
+ background-color: rgba(255, 255, 255, 0.9); /* Semi-transparent white background for content */
35
+ border-radius: 15px;
36
+ padding: 20px;
37
+ margin-top: 50px;
38
+ }
39
+
40
+ h1, h2 {
41
+ color: #4CAF50; /* Dark green title color */
42
+ }
43
+
44
+ button.btn-primary {
45
+ background-color: #4CAF50; /* Dark green button color */
46
+ border: none;
47
+ }
48
+
49
+ button.btn-primary:hover {
50
+ background-color: #45a049; /* Slightly darker green on hover */
51
+ }
52
+
53
+ .card {
54
+ background-color: #f8f9fa; /* Light gray background for cards */
55
+ border: 1px solid #ddd; /* Light gray border */
56
+ border-radius: 10px;
57
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow */
58
+ }
59
+
60
+ .card-header {
61
+ background-color: #4CAF50; /* Dark green header color for cards */
62
+ color: white;
63
+ border-radius: 10px 10px 0 0;
64
+ }
65
+
66
+ .card-body {
67
+ padding: 15px;
68
+ }
69
+
70
+ .list-group-item {
71
+ border: none;
72
+ }
73
+
74
+ .btn-success {
75
+ background-color: #28a745; /* Dark green for success button */
76
+ border: none;
77
+ }
78
+
79
+ .btn-success:hover {
80
+ background-color: #218838; /* Slightly darker green on hover */
81
+ }
82
+
83
+ #predictionsList:hover::before {
84
+ content: "Tables displaying TPM values corresponding to plant tissues";
85
+ position: absolute;
86
+ bottom: 100%; /* Position the tooltip above the text */
87
+ left: 50%;
88
+ transform: translateX(-50%);
89
+ background-color: #333;
90
+ color: #fff;
91
+ padding: 5px;
92
+ border-radius: 5px;
93
+ font-size: 12px;
94
+ }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <div class="container">
99
+ <h1 class="mb-4">
100
+ <a href="https://huggingface.co/Gurveer05/FloraBERT" target="_blank" rel="noopener noreferrer" style="color: #218838;">FloraBERT</a>
101
+ </h1>
102
+ <form>
103
+ <div class="form-group">
104
+ <label for="fileInput">Select .txt files:</label>
105
+ <div class="input-group">
106
+ <div class="custom-file">
107
+ <input type="file" class="custom-file-input" id="fileInput" accept=".txt" multiple onchange="displaySelectedFiles()">
108
+ <label class="custom-file-label" for="fileInput">Choose files</label>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ <button type="button" class="btn btn-primary" id="uploadButton" onclick="processFiles()">Upload</button>
113
+ <p id="uploadMessage" class="text-muted mt-2"></p>
114
+ <div id="selectedFiles" class="mt-2"></div>
115
+ </form>
116
+
117
+ <h2 class="mt-5" id="predictionsList" title="Table displaying TPM values corresponding to plant tissues">Predictions</h2>
118
+ <div id="loadingIcon" class="d-none">
119
+ <p class="text-muted">Processing... <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span></p>
120
+ </div>
121
+ <div id="predictionsList" class="mt-3"></div>
122
+
123
+ <button type="button" class="btn btn-success mt-3" onclick="downloadAllPredictions()">Download All Predictions</button>
124
+ </div>
125
+
126
+ <script>
127
+ // Maintain a cache for predictions
128
+ const predictionsCache = {};
129
+
130
+ function displaySelectedFiles() {
131
+ const fileInput = document.getElementById('fileInput');
132
+ const selectedFilesContainer = document.getElementById('selectedFiles');
133
+ const files = fileInput.files;
134
+
135
+ selectedFilesContainer.innerHTML = '';
136
+
137
+ for (let i = 0; i < files.length; i++) {
138
+ const fileName = files[i].name;
139
+ const fileLabel = document.createElement('span');
140
+ fileLabel.innerText = fileName;
141
+
142
+ if (i > 0) {
143
+ selectedFilesContainer.appendChild(document.createTextNode(', '));
144
+ }
145
+
146
+ selectedFilesContainer.appendChild(fileLabel);
147
+ }
148
+ }
149
+
150
+ async function processFiles() {
151
+ const fileInput = document.getElementById('fileInput');
152
+ const files = fileInput.files;
153
+
154
+ const uploadButton = document.getElementById('uploadButton');
155
+ const uploadMessage = document.getElementById('uploadMessage');
156
+ const loadingIcon = document.getElementById('loadingIcon');
157
+ const predictionsList = document.getElementById('predictionsList');
158
+ const selectedFilesContainer = document.getElementById('selectedFiles');
159
+
160
+ // Disable the upload button
161
+ uploadButton.disabled = true;
162
+
163
+ uploadMessage.innerText = ''; // Clear previous messages
164
+
165
+ if (files.length === 0) {
166
+ // Update upload message for no file selected
167
+ uploadMessage.innerText = 'Please choose at least one file.';
168
+ // Enable the upload button
169
+ uploadButton.disabled = false;
170
+ return;
171
+ }
172
+
173
+ const url = 'http://127.0.0.1:8000';
174
+
175
+ loadingIcon.classList.remove('d-none');
176
+ // predictionsList.innerHTML = ''; // Do not clear previous cards
177
+
178
+ for (let i = 0; i < files.length; i++) {
179
+ const file = files[i];
180
+ const formData = new FormData();
181
+ formData.append('file', file);
182
+
183
+ const response = await fetch(`${url}/uploadfile/`, {
184
+ method: 'POST',
185
+ body: formData
186
+ });
187
+
188
+ if (response.ok) {
189
+ const result = await response.json();
190
+ const filename = result.filename;
191
+
192
+ const predictionsResponse = await fetch(`${url}/process/${filename}`);
193
+ const predictions = await predictionsResponse.json();
194
+
195
+ predictionsCache[filename] = predictions;
196
+
197
+ // Display predictions as cards (newest on top)
198
+ if (!document.getElementById(`card-${filename}`)) {
199
+ const cardHtml = `
200
+ <div id="card-${filename}" class="card mt-3">
201
+ <div class="card-header">
202
+ ${filename}
203
+ </div>
204
+ <div class="card-body">
205
+ <ul class="list-group list-group-flush">
206
+ ${predictions.map((predictionList, index) => `
207
+ <li class="list-group-item">
208
+ <strong>Sequence ${index + 1}</strong>
209
+ <table class="table table-bordered mt-2">
210
+ <thead>
211
+ <tr>
212
+ <th scope="col">Tissue</th>
213
+ <th scope="col">Prediction</th>
214
+ </tr>
215
+ </thead>
216
+ <tbody>
217
+ ${predictionList.map(prediction => `
218
+ <tr>
219
+ <td>${prediction.tissue}</td>
220
+ <td>${prediction.prediction}</td>
221
+ </tr>
222
+ `).join('')}
223
+ </tbody>
224
+ </table>
225
+ </li>
226
+ `).join('')}
227
+ </ul>
228
+ </div>
229
+ </div>
230
+ `;
231
+ predictionsList.innerHTML += cardHtml;
232
+
233
+ // Trigger fade-in effect
234
+ setTimeout(() => {
235
+ document.querySelectorAll('.card').forEach(card => card.classList.add('show'));
236
+ }, 100);
237
+ }
238
+
239
+ // Update upload message immediately
240
+ uploadMessage.innerText = `Uploaded file ${i + 1} of ${files.length}.`;
241
+
242
+ // Clear the message after a short delay (e.g., 2000 milliseconds)
243
+ setTimeout(() => {
244
+ uploadMessage.innerText = '';
245
+ }, 2000);
246
+ } else {
247
+ console.error('Failed to upload file:', file.name);
248
+ uploadMessage.innerText = `Failed to upload file ${i + 1} of ${files.length}.`;
249
+ }
250
+ }
251
+
252
+ // Enable the upload button after all files are processed
253
+ uploadButton.disabled = false;
254
+
255
+ // Hide processing icon after all files are processed
256
+ loadingIcon.classList.add('d-none');
257
+
258
+ // Clear displayed selected files after processing
259
+ selectedFilesContainer.innerHTML = '';
260
+ }
261
+
262
+ function downloadAllPredictions() {
263
+ // Convert predictionsCache to a JSON string
264
+ const predictionsJSON = JSON.stringify(predictionsCache, null, 2);
265
+
266
+ // Create a Blob containing the JSON data
267
+ const blob = new Blob([predictionsJSON], { type: 'application/json' });
268
+
269
+ // Create a link element and trigger a click to download the file
270
+ const a = document.createElement('a');
271
+ a.href = URL.createObjectURL(blob);
272
+ a.download = 'all_predictions.json';
273
+ a.click();
274
+ }
275
+ </script>
276
+
277
+ <!-- Add Bootstrap JS and Popper.js CDN links -->
278
+ <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
279
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
280
+ <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
281
+ </body>
282
+ </html>