samsl commited on
Commit
cd1d940
Β·
1 Parent(s): 01bc174

Initial app

Browse files
Files changed (5) hide show
  1. .python-version +1 -0
  2. README.md +5 -4
  3. app.py +487 -0
  4. pyproject.toml +39 -0
  5. requirements.txt +29 -0
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.11
README.md CHANGED
@@ -1,14 +1,15 @@
1
  ---
2
  title: Rocketshp
3
- emoji: πŸ“‰
4
- colorFrom: green
5
  colorTo: blue
6
  sdk: gradio
7
  sdk_version: 5.49.1
 
8
  app_file: app.py
9
- pinned: false
10
  license: mit
11
  short_description: Fast structural heterogeneity estimation
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Rocketshp
3
+ emoji: πŸš€
4
+ colorFrom: purple
5
  colorTo: blue
6
  sdk: gradio
7
  sdk_version: 5.49.1
8
+ python_version: 3.11
9
  app_file: app.py
10
+ pinned: true
11
  license: mit
12
  short_description: Fast structural heterogeneity estimation
13
  ---
14
 
15
+ Check out the configuration reference at <https://huggingface.co/docs/hub/spaces-config-reference>
app.py ADDED
@@ -0,0 +1,487 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+
4
+ from matplotlib.path import Path
5
+
6
+ import gradio as gr
7
+ from gradio_molecule3d import Molecule3D
8
+ import numpy as np
9
+ import json
10
+ import torch
11
+ import networkx as nx
12
+ import matplotlib.pyplot as plt
13
+ import matplotlib.patches as mpatches
14
+ import matplotlib.colors as mcolors
15
+ from matplotlib.cm import ScalarMappable
16
+ from matplotlib.colors import Normalize
17
+ from biotite.sequence import io as seqio
18
+ from biotite.structure import io, to_sequence, spread_residue_wise, filter_amino_acids
19
+ from biotite.database import rcsb
20
+ from rocketshp import RocketSHP, load_sequence, load_structure
21
+ from rocketshp.network import (
22
+ build_allosteric_network,
23
+ cluster_network,
24
+ calculate_centrality,
25
+ )
26
+
27
+
28
+ def plot_predictions(
29
+ rmsf: np.ndarray,
30
+ gcc_lmi: np.ndarray,
31
+ shp: np.ndarray,
32
+ title: str = "RocketSHP Predictions",
33
+ font_scale: float = 1.0,
34
+ ):
35
+ with plt.style.context(
36
+ {
37
+ "font.size": 12 * font_scale,
38
+ "legend.fontsize": 12 * font_scale,
39
+ "axes.labelsize": 12 * font_scale,
40
+ "axes.titlesize": 12 * font_scale,
41
+ }
42
+ ):
43
+ plot_file = tempfile.NamedTemporaryFile(mode="wb", delete=False, suffix=".png")
44
+
45
+ fig = plt.figure(figsize=(6, 6))
46
+ gs = fig.add_gridspec(2, 2)
47
+ ax1 = fig.add_subplot(gs[0, 0])
48
+ ax2 = fig.add_subplot(gs[0, 1])
49
+ ax3 = fig.add_subplot(gs[1, :])
50
+
51
+ fig.suptitle(title)
52
+
53
+ ax1.plot(rmsf, label="RMSF")
54
+ ax1.set_title("RMSF")
55
+ ax1.set_xlabel("Residue Index")
56
+ ax1.set_ylabel("RMSF (Γ…)")
57
+ ax1.spines["top"].set_visible(False)
58
+ ax1.spines["right"].set_visible(False)
59
+
60
+ ax2.imshow(gcc_lmi, cmap="viridis", aspect="equal", vmin=0, vmax=1)
61
+ ax2.set_title("GCC-LMI")
62
+ ax2.set_xlabel("Residue Index")
63
+ ax2.set_ylabel("Residue Index")
64
+
65
+ ax3.imshow(shp.T, cmap="binary", vmin=0, vmax=1, interpolation="none")
66
+ ax3.set_title("SHP")
67
+ ax3.set_xlabel("Residue Index")
68
+ ax3.set_ylabel("Structure Token\nIndex")
69
+ ax3.set_ylim(21, -1)
70
+
71
+ plt.tight_layout()
72
+ plt.savefig(plot_file.name)
73
+ return fig, plot_file.name
74
+
75
+
76
+ def download_predictions(job_name, rmsf, gcc_lmi, shp):
77
+ outfile = tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".json")
78
+
79
+ json_content = {
80
+ "model": job_name,
81
+ "rmsf": rmsf.tolist(),
82
+ "gcc_lmi": gcc_lmi.tolist(),
83
+ "shp": shp.tolist(),
84
+ }
85
+
86
+ outfile.write(json.dumps(json_content))
87
+
88
+ return outfile.name
89
+
90
+
91
+ def toggle_inputs(model):
92
+ if "seq" in model or "mini" in model:
93
+ return (
94
+ gr.update(visible=True), # sequence input
95
+ gr.update(visible=True), # fasta upload
96
+ gr.update(visible=False), # structure input
97
+ gr.update(visible=False), # structure upload
98
+ gr.update(visible=False), # structure output
99
+ )
100
+ return (
101
+ gr.update(visible=False), # sequence input
102
+ gr.update(visible=False), # fasta upload
103
+ gr.update(visible=True), # structure input
104
+ gr.update(visible=True), # structure upload
105
+ gr.update(visible=True), # structure output
106
+ )
107
+
108
+
109
+ def predict_rocketshp(
110
+ model_variant: str,
111
+ sequence: str | None,
112
+ sequence_file: str | None,
113
+ structure_code: str | None,
114
+ structure_file: str | None,
115
+ ):
116
+ print(f"sequence text: {sequence}")
117
+ print(f"sequence file: {sequence_file}")
118
+ print(f"structure code: {structure_code}")
119
+ print(f"structure file: {structure_file}")
120
+ print(f"model variant: {model_variant}")
121
+
122
+ device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
123
+ is_sequence_model = "seq" in model_variant or "mini" in model_variant
124
+
125
+ if is_sequence_model:
126
+ if sequence_file is not None:
127
+ if sequence != "":
128
+ gr.Warning("Sequence file provided, ignoring text box.")
129
+
130
+ sequence = str(seqio.load_sequence(sequence_file))
131
+ print(sequence)
132
+
133
+ elif sequence == "":
134
+ raise gr.Error("Sequence input is required for the selected model.")
135
+
136
+ struct_features = None
137
+ seq_features = load_sequence(sequence, device=device)
138
+
139
+ else:
140
+ if structure_file is None:
141
+ if structure_code == "":
142
+ raise gr.Error("Structure input is required for the selected model.")
143
+
144
+ structure_tmp_dir = tempfile.TemporaryDirectory()
145
+ structure_file = rcsb.fetch(
146
+ structure_code,
147
+ "pdb",
148
+ target_path=structure_tmp_dir.name,
149
+ )
150
+ print(structure_tmp_dir)
151
+ print(structure_file)
152
+ elif structure_code != "":
153
+ gr.Warning(f"PDB file provided, ignoring PDB code {structure_code}.")
154
+
155
+ structure = io.load_structure(structure_file)
156
+ structure = structure[filter_amino_acids(structure)]
157
+ chain_id = structure.chain_id[0]
158
+ structure = structure[structure.chain_id == chain_id]
159
+
160
+ struct_features = load_structure(structure, device=device)
161
+ sequence = str(to_sequence(structure)[0][0])
162
+ seq_features = load_sequence(sequence, device=device)
163
+
164
+ # Load the model
165
+ model = RocketSHP.load_from_checkpoint(model_variant).to(device)
166
+
167
+ # Make predictions
168
+ with torch.no_grad():
169
+ try:
170
+ dynamics_pred = model(
171
+ {
172
+ "seq_feats": seq_features,
173
+ "struct_feats": struct_features,
174
+ }
175
+ )
176
+ except Exception as e:
177
+ raise gr.Error(f"Error during model prediction: {str(e)}")
178
+
179
+ # Extract predictions
180
+ rmsf = dynamics_pred["rmsf"].squeeze().cpu().numpy()
181
+ gcc_lmi = dynamics_pred["gcc_lmi"].squeeze().cpu().numpy()
182
+ shp = dynamics_pred["shp"].squeeze().cpu().numpy()
183
+ ca_dist = dynamics_pred["ca_dist"].squeeze().cpu().numpy()
184
+
185
+ fig, plot_file_name = plot_predictions(
186
+ rmsf,
187
+ gcc_lmi,
188
+ shp,
189
+ title=f"RocketSHP Predictions (model={model_variant})",
190
+ )
191
+
192
+ json_file_name = download_predictions(model_variant, rmsf, gcc_lmi, shp)
193
+
194
+ if is_sequence_model:
195
+ out_structure_file_name = None
196
+ else:
197
+ out_structure_file = tempfile.NamedTemporaryFile(
198
+ mode="w+", delete=False, suffix=".pdb"
199
+ )
200
+ bfactors = spread_residue_wise(structure, rmsf)
201
+ structure.set_annotation("b_factor", bfactors)
202
+ io.save_structure(out_structure_file.name, structure)
203
+
204
+ out_structure_file_name = out_structure_file.name
205
+
206
+ seq_display_tuples = [*zip(list(sequence), rmsf)]
207
+
208
+ return (
209
+ rmsf,
210
+ gcc_lmi,
211
+ shp,
212
+ ca_dist,
213
+ sequence,
214
+ json_file_name,
215
+ plot_file_name,
216
+ fig,
217
+ out_structure_file_name,
218
+ seq_display_tuples,
219
+ )
220
+
221
+
222
+ def visualize_network(
223
+ sequence: str,
224
+ gcc_lmi: np.ndarray,
225
+ ca_dist: np.ndarray,
226
+ ca_threshold: float = 12.0,
227
+ cluster_k: int = 5,
228
+ progress=gr.Progress(),
229
+ ):
230
+ if sequence == "!=" or not len(gcc_lmi):
231
+ raise gr.Error(
232
+ "No valid GCC-LMI data available for network visualization, please run RocketSHP first."
233
+ )
234
+
235
+ # Build network from GCC-LMI predictions and distance mask
236
+ progress(0.1, desc="Building allosteric network...")
237
+ network = build_allosteric_network(gcc_lmi, ca_dist, distance_cutoff=ca_threshold)
238
+
239
+ # Apply clustering to identify communities
240
+ progress(0.2, desc="Clustering network...")
241
+ communities = cluster_network(network, k=cluster_k)
242
+
243
+ # Calculate betweenness centrality
244
+ progress(0.8, desc="Calculating centrality...")
245
+ centralities = calculate_centrality(network)
246
+ betweenness_centrality = centralities["betweenness"]
247
+
248
+ progress(0.9, desc="Generating plot...")
249
+ fig, ax = plt.subplots(2, 1, figsize=(10, 8))
250
+
251
+ pos = nx.spring_layout(network)
252
+
253
+ cmap = plt.cm.tab10 # or whatever colormap you're using
254
+ cluster_color = []
255
+ cluster_label = []
256
+ for i, (cluster, color) in enumerate(zip(communities, cmap.colors, strict=False)):
257
+ hex_color = mcolors.to_hex(color)
258
+ cluster_color.extend([hex_color] * len(cluster))
259
+ cluster_label.extend([i] * len(cluster))
260
+
261
+ nx.draw(
262
+ network,
263
+ pos,
264
+ with_labels=True,
265
+ node_color=betweenness_centrality,
266
+ edge_color="gray",
267
+ ax=ax[0],
268
+ cmap="coolwarm",
269
+ )
270
+ nx.draw(
271
+ network,
272
+ pos,
273
+ with_labels=True,
274
+ node_color=cluster_color,
275
+ edge_color="gray",
276
+ ax=ax[1],
277
+ )
278
+
279
+ # For ax[0] - Betweenness Centrality
280
+ ax[0].set_title("Betweenness Centrality")
281
+ norm = Normalize(vmin=min(betweenness_centrality), vmax=max(betweenness_centrality))
282
+ sm = ScalarMappable(cmap="coolwarm", norm=norm)
283
+ sm.set_array([]) # Required for colorbar
284
+ plt.colorbar(sm, ax=ax[0])
285
+
286
+ # For ax[1] - Clusters
287
+ ax[1].set_title("Network Clusters")
288
+ unique_clusters = [cmap.colors[i] for i in range(cluster_k)]
289
+ legend_elements = [
290
+ mpatches.Patch(facecolor=color, label=f"Cluster {i + 1}")
291
+ for i, color in enumerate(unique_clusters)
292
+ ]
293
+ ax[1].legend(handles=legend_elements)
294
+
295
+ plt.tight_layout()
296
+ progress(1.0, desc="Done")
297
+
298
+ normalize_centrality = (betweenness_centrality - betweenness_centrality.min()) / (
299
+ betweenness_centrality.max() - betweenness_centrality.min()
300
+ )
301
+
302
+ comm_highlight = [
303
+ (aa, f"Cluster {i + 1}") for aa, i in zip(list(sequence), cluster_label)
304
+ ]
305
+ bc_highlight = [*zip(list(sequence), normalize_centrality)]
306
+
307
+ out_cluster_file = tempfile.NamedTemporaryFile(
308
+ mode="w+", delete=False, suffix=".csv"
309
+ )
310
+ out_cluster_file.write("Residue_Index,Amino_Acid,Cluster,Betweenness Centrality\n")
311
+ for i, (aa, cluster_id, bet) in enumerate(
312
+ zip(list(sequence), cluster_label, betweenness_centrality)
313
+ ):
314
+ out_cluster_file.write(f"{i + 1},{aa},Cluster_{cluster_id + 1},{bet}\n")
315
+
316
+ out_cluster_file_name = out_cluster_file.name
317
+
318
+ return fig, bc_highlight, comm_highlight, out_cluster_file_name
319
+
320
+
321
+ reps = [
322
+ {
323
+ "model": 0,
324
+ "chain": "",
325
+ "resname": "",
326
+ "style": "cartoon",
327
+ "color": """
328
+ function(atom) {
329
+ var b = atom.b || 0;
330
+ // Map B-factor to color (adjust min/max as needed)
331
+ var min_b = 0;
332
+ var max_b = 100;
333
+ var normalized = (b - min_b) / (max_b - min_b);
334
+
335
+ // Blue (low) to Red (high)
336
+ var r = Math.floor(normalized * 255);
337
+ var b_color = Math.floor((1 - normalized) * 255);
338
+ return 'rgb(' + r + ', 0, ' + b_color + ')';
339
+ }
340
+ """,
341
+ # "residue_range": "",
342
+ "around": 0,
343
+ "byres": False,
344
+ # "visible": False,
345
+ "opacity": 1,
346
+ }
347
+ ]
348
+
349
+ rocketshp_gradio = gr.Blocks(title="RocketSHP")
350
+ # , theme=gr.themes.Monochrome())
351
+
352
+ with rocketshp_gradio:
353
+ gr.Markdown("""
354
+
355
+ # RocketSHP πŸš€
356
+
357
+ RocketSHP enables ultra-fast prediction of protein dynamics and flexibility from amino acid sequences and/or protein structures. Trained on thousands of molecular dynamics trajectories, it predicts multiple dynamics-related features simultaneously:
358
+
359
+ - Root-Mean-Square Fluctuations (RMSF)
360
+ - Generalized Correlation Coefficients with Linear Mutual Information (GCC-LMI)
361
+ - Structural Heterogeneity Profiles (SHP)
362
+
363
+ This approach bridges the gap between static structural biology and dynamic functional understanding, providing a computational tool that complements experimental approaches at unprecedented speed and scale.
364
+
365
+ - πŸ“„: [Paper](https://www.biorxiv.org/content/10.1101/2025.06.12.659353v1)
366
+ - πŸ’»: [GitHub](https://github.com/flatironinstitute/RocketSHP/tree/main)
367
+
368
+ """)
369
+
370
+ rmsf = gr.State([])
371
+ gcc = gr.State([])
372
+ shp = gr.State([])
373
+ ca_dist = gr.State([])
374
+ sequence = gr.State([])
375
+
376
+ model_variant = gr.Dropdown(
377
+ label="Select RocketSHP Model",
378
+ choices=["latest", "v1_seq", "v1_mini"],
379
+ value="latest",
380
+ )
381
+
382
+ structure_input = gr.Textbox(label="Enter PDB ID")
383
+ structure_upload = gr.File(
384
+ label="Upload Structure File (PDB or MMCIF)",
385
+ file_types=[".pdb", ".cif"],
386
+ )
387
+
388
+ sequence_input = gr.Textbox(label="Paste FASTA Sequence", visible=False)
389
+ sequence_upload = gr.File(
390
+ label="Upload FASTA File",
391
+ file_types=[".fasta", ".fa"],
392
+ visible=False,
393
+ )
394
+
395
+ predict_button = gr.Button("Run RocketSHP")
396
+
397
+ with gr.Tabs():
398
+ with gr.Tab("View Results"):
399
+ seq_display = gr.HighlightedText(label="RMSF per Residue")
400
+
401
+ mol_display = Molecule3D(
402
+ confidenceLabel="RMSF",
403
+ label="Structure",
404
+ reps=reps,
405
+ show_label=True,
406
+ )
407
+
408
+ fig_display = gr.Plot(label="Prediction Plots")
409
+
410
+ with gr.Tab("Allosteric Network"):
411
+ ca_threshold = gr.Slider(
412
+ label="CΞ± Distance Cutoff (Γ…)",
413
+ minimum=4.0,
414
+ maximum=12.0,
415
+ step=0.1,
416
+ value=8.0,
417
+ )
418
+ cluster_k = gr.Slider(
419
+ label="Number of Clusters (k)",
420
+ minimum=2,
421
+ maximum=10,
422
+ step=1,
423
+ value=5,
424
+ )
425
+ network_button = gr.Button("Visualize Network")
426
+
427
+ net_fig = gr.Plot(label="Allosteric Network")
428
+
429
+ htext_cmap = {
430
+ f"Cluster {i + 1}": mcolors.to_hex(color)
431
+ for i, color in enumerate(plt.cm.tab10.colors)
432
+ }
433
+
434
+ seq_betweenness = gr.HighlightedText(label="Betweenness Centrality")
435
+ seq_clusters = gr.HighlightedText(
436
+ label="Network Clusters", combine_adjacent=True, color_map=htext_cmap
437
+ )
438
+
439
+ with gr.Tab("Downloads"):
440
+ download_file = gr.File(label="Download Results")
441
+ fig_file = gr.File(label="Download Plot")
442
+ clusters_file = gr.File(label="Download Network Clusters")
443
+
444
+ model_variant.change(
445
+ toggle_inputs,
446
+ inputs=model_variant,
447
+ outputs=[
448
+ sequence_input,
449
+ sequence_upload,
450
+ structure_input,
451
+ structure_upload,
452
+ mol_display,
453
+ ],
454
+ )
455
+
456
+ predict_button.click(
457
+ predict_rocketshp,
458
+ inputs=[
459
+ model_variant,
460
+ sequence_input,
461
+ sequence_upload,
462
+ structure_input,
463
+ structure_upload,
464
+ ],
465
+ outputs=[
466
+ rmsf,
467
+ gcc,
468
+ shp,
469
+ ca_dist,
470
+ sequence,
471
+ download_file,
472
+ fig_file,
473
+ fig_display,
474
+ mol_display,
475
+ seq_display,
476
+ ],
477
+ )
478
+
479
+ network_button.click(
480
+ visualize_network,
481
+ inputs=[sequence, gcc, ca_dist, ca_threshold, cluster_k],
482
+ outputs=[net_fig, seq_betweenness, seq_clusters, clusters_file],
483
+ )
484
+
485
+
486
+ if __name__ == "__main__":
487
+ rocketshp_gradio.launch(share=False)
pyproject.toml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "rocketshp-space"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "biopython>=1.79",
9
+ "biotite==0.41.2",
10
+ "datasets>=4.3.0",
11
+ "esm==3.1.3",
12
+ "gradio>=5.49.1",
13
+ "gradio-molecule3d>=0.0.7",
14
+ "h5py>=3.15.1",
15
+ "huggingface-hub>=0.36.0",
16
+ "lightning>=2.4.0",
17
+ "loguru>=0.7.3",
18
+ "matplotlib>=3.10.7",
19
+ "mdanalysis>=2.9.0",
20
+ "mdanalysisdata>=0.9.0",
21
+ "neptune>=1.13.0",
22
+ "nglview>=4.0",
23
+ "numpy>=1.23.5",
24
+ "numpy-indexed>=0.3.7",
25
+ "omegaconf>=2.3.0",
26
+ "openpyxl>=3.1.5",
27
+ "pandas>=2.3.3",
28
+ "python-dateutil>=2.9.0.post0",
29
+ "python-dotenv>=1.1.1",
30
+ "scikit-learn>=1.7.2",
31
+ "scipy>=1.16.2",
32
+ "seaborn>=0.13.2",
33
+ "statsmodels>=0.14.5",
34
+ "tokenizers>=0.20.3",
35
+ "torchmetrics>=1.8.2",
36
+ "tqdm>=4.67.1",
37
+ "transformers>=4.46.3",
38
+ "typer>=0.20.0",
39
+ ]
requirements.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ biopython>=1.79
2
+ biotite==0.41.2
3
+ datasets
4
+ esm==3.1.3
5
+ h5py
6
+ huggingface_hub
7
+ lightning>=2.4.0
8
+ loguru
9
+ matplotlib
10
+ neptune>=1.13.0
11
+ nglview
12
+ numpy>=1.23.5
13
+ numpy-indexed>=0.3.7
14
+ omegaconf
15
+ pandas
16
+ python-dateutil
17
+ python-dotenv
18
+ scikit_learn
19
+ scipy
20
+ seaborn
21
+ statsmodels
22
+ tokenizers
23
+ torchmetrics
24
+ transformers
25
+ tqdm
26
+ typer
27
+ mdanalysis>=2.9.0
28
+ mdanalysisdata>=0.9.0
29
+ openpyxl>=3.1.5