IAmKarthik commited on
Commit
3e1cff0
·
verified ·
1 Parent(s): 3862be5

Deploy ESM-2, MoLFormer, and affinity ONNX application

Browse files
Files changed (3) hide show
  1. Dockerfile +1 -0
  2. app.py +133 -22
  3. requirements.txt +1 -1
Dockerfile CHANGED
@@ -12,6 +12,7 @@ RUN apt-get update \
12
  libsm6 \
13
  libxext6 \
14
  libxrender1 \
 
15
  && rm -rf /var/lib/apt/lists/*
16
 
17
  COPY requirements.txt /tmp/requirements.txt
 
12
  libsm6 \
13
  libxext6 \
14
  libxrender1 \
15
+ libexpat1 \
16
  && rm -rf /var/lib/apt/lists/*
17
 
18
  COPY requirements.txt /tmp/requirements.txt
app.py CHANGED
@@ -6,8 +6,9 @@ from functools import lru_cache
6
  from pathlib import Path
7
 
8
  import gradio as gr
9
- import py3Dmol
10
  from huggingface_hub import snapshot_download
 
11
  from rdkit import Chem
12
  from rdkit.Chem import AllChem, Descriptors, Draw, Lipinski
13
 
@@ -29,6 +30,18 @@ RESIDUE_COLORS = {
29
  "W": "#22c55e",
30
  "Y": "#22c55e",
31
  }
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
 
34
  def validate_protein(sequence: str) -> str:
@@ -105,11 +118,9 @@ def predict(sequence: str, smiles: str):
105
  raise gr.Error(f"Inference failed: {error}") from error
106
  try:
107
  conformer = molecule_3d(smiles)
108
- except Exception:
109
- conformer = (
110
- "<div class='render-note'>A 3D conformer could not be generated for "
111
- "this compound. The affinity prediction is still valid.</div>"
112
- )
113
  return (
114
  prediction_card(prediction),
115
  molecule_2d(smiles),
@@ -179,29 +190,129 @@ def molecule_2d(smiles: str):
179
  return Draw.MolToImage(Chem.MolFromSmiles(validate_smiles(smiles)), size=(700, 450))
180
 
181
 
182
- def molecule_3d(smiles: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  molecule = Chem.AddHs(Chem.MolFromSmiles(validate_smiles(smiles)))
184
  parameters = AllChem.ETKDGv3()
185
  parameters.randomSeed = 42
186
  if AllChem.EmbedMolecule(molecule, parameters) != 0:
187
  raise gr.Error("RDKit could not generate a conformer for this molecule.")
188
- AllChem.MMFFOptimizeMolecule(molecule, maxIters=500)
189
- viewer = py3Dmol.view(width=800, height=500)
190
- viewer.addModel(Chem.MolToMolBlock(molecule), "mol")
191
- viewer.setStyle({"stick": {}, "sphere": {"scale": 0.25}})
192
- viewer.zoomTo()
193
- return viewer._make_html()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
 
196
- def protein_3d(pdb_file) -> str:
197
  if pdb_file is None:
198
  raise gr.Error("Upload a PDB file. A sequence alone has no 3D coordinates.")
199
- pdb_text = Path(pdb_file).read_text(encoding="utf-8", errors="replace")
200
- viewer = py3Dmol.view(width=800, height=600)
201
- viewer.addModel(pdb_text, "pdb")
202
- viewer.setStyle({"cartoon": {"color": "spectrum"}})
203
- viewer.zoomTo()
204
- return viewer._make_html()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
 
207
  EXAMPLE_PROTEIN = "MAVMKNYLLPILVLFLAYYYYSTNEEFRPEMLQGKKVIVTGASKGIGREMAYHLSKMGAHVVLTARSEEGLQK"
@@ -262,7 +373,7 @@ with gr.Blocks(title="Protein-Compound Affinity Explorer", css=CSS) as demo:
262
  gr.Markdown("## Compound")
263
  with gr.Row():
264
  image = gr.Image(label="2D structure", height=420)
265
- view_3d = gr.HTML(label="Generated 3D conformer")
266
  molecule_data = gr.JSON(label="Molecule descriptors")
267
 
268
  gr.Markdown("## Protein")
@@ -281,7 +392,7 @@ with gr.Blocks(title="Protein-Compound Affinity Explorer", css=CSS) as demo:
281
  "to inspect an experimentally determined or predicted structure."
282
  )
283
  pdb = gr.File(label="PDB file", file_types=[".pdb"], type="filepath")
284
- pdb_view = gr.HTML()
285
  gr.Button("Render PDB structure").click(protein_3d, pdb, pdb_view)
286
  gr.Markdown(
287
  html.escape(
 
6
  from pathlib import Path
7
 
8
  import gradio as gr
9
+ import numpy as np
10
  from huggingface_hub import snapshot_download
11
+ from PIL import Image, ImageDraw
12
  from rdkit import Chem
13
  from rdkit.Chem import AllChem, Descriptors, Draw, Lipinski
14
 
 
30
  "W": "#22c55e",
31
  "Y": "#22c55e",
32
  }
33
+ ATOM_COLORS = {
34
+ "C": "#334155",
35
+ "H": "#cbd5e1",
36
+ "N": "#2563eb",
37
+ "O": "#dc2626",
38
+ "F": "#16a34a",
39
+ "P": "#ea580c",
40
+ "S": "#ca8a04",
41
+ "CL": "#16a34a",
42
+ "BR": "#92400e",
43
+ "I": "#7e22ce",
44
+ }
45
 
46
 
47
  def validate_protein(sequence: str) -> str:
 
118
  raise gr.Error(f"Inference failed: {error}") from error
119
  try:
120
  conformer = molecule_3d(smiles)
121
+ except Exception as error:
122
+ gr.Warning(f"Could not generate the 3D conformer: {error}")
123
+ conformer = None
 
 
124
  return (
125
  prediction_card(prediction),
126
  molecule_2d(smiles),
 
190
  return Draw.MolToImage(Chem.MolFromSmiles(validate_smiles(smiles)), size=(700, 450))
191
 
192
 
193
+ def _project_coordinates(
194
+ coordinates: np.ndarray,
195
+ width: int,
196
+ height: int,
197
+ padding: int = 50,
198
+ ) -> tuple[np.ndarray, np.ndarray]:
199
+ angle_y = np.deg2rad(-28)
200
+ angle_x = np.deg2rad(18)
201
+ rotate_y = np.array(
202
+ [
203
+ [np.cos(angle_y), 0, np.sin(angle_y)],
204
+ [0, 1, 0],
205
+ [-np.sin(angle_y), 0, np.cos(angle_y)],
206
+ ],
207
+ dtype=np.float32,
208
+ )
209
+ rotate_x = np.array(
210
+ [
211
+ [1, 0, 0],
212
+ [0, np.cos(angle_x), -np.sin(angle_x)],
213
+ [0, np.sin(angle_x), np.cos(angle_x)],
214
+ ],
215
+ dtype=np.float32,
216
+ )
217
+ rotated = (coordinates - coordinates.mean(axis=0)) @ rotate_y.T @ rotate_x.T
218
+ xy = rotated[:, :2]
219
+ span = np.maximum(np.ptp(xy, axis=0), 1e-6)
220
+ scale = min((width - 2 * padding) / span[0], (height - 2 * padding) / span[1])
221
+ projected = xy * scale
222
+ projected[:, 0] += width / 2
223
+ projected[:, 1] = height / 2 - projected[:, 1]
224
+ return projected, rotated[:, 2]
225
+
226
+
227
+ def molecule_3d(smiles: str) -> Image.Image:
228
  molecule = Chem.AddHs(Chem.MolFromSmiles(validate_smiles(smiles)))
229
  parameters = AllChem.ETKDGv3()
230
  parameters.randomSeed = 42
231
  if AllChem.EmbedMolecule(molecule, parameters) != 0:
232
  raise gr.Error("RDKit could not generate a conformer for this molecule.")
233
+ if AllChem.MMFFHasAllMoleculeParams(molecule):
234
+ AllChem.MMFFOptimizeMolecule(molecule, maxIters=500)
235
+ else:
236
+ AllChem.UFFOptimizeMolecule(molecule, maxIters=500)
237
+
238
+ conformer = molecule.GetConformer()
239
+ coordinates = np.array(
240
+ [
241
+ [
242
+ conformer.GetAtomPosition(index).x,
243
+ conformer.GetAtomPosition(index).y,
244
+ conformer.GetAtomPosition(index).z,
245
+ ]
246
+ for index in range(molecule.GetNumAtoms())
247
+ ],
248
+ dtype=np.float32,
249
+ )
250
+ width, height = 760, 460
251
+ points, depth = _project_coordinates(coordinates, width, height)
252
+ image = Image.new("RGB", (width, height), "#f8fafc")
253
+ drawing = ImageDraw.Draw(image)
254
+
255
+ for bond in molecule.GetBonds():
256
+ start = tuple(map(float, points[bond.GetBeginAtomIdx()]))
257
+ end = tuple(map(float, points[bond.GetEndAtomIdx()]))
258
+ drawing.line([start, end], fill="#64748b", width=4)
259
+
260
+ depth_range = max(float(np.ptp(depth)), 1e-6)
261
+ for index in np.argsort(depth):
262
+ atom = molecule.GetAtomWithIdx(int(index))
263
+ x, y = points[index]
264
+ relative_depth = (float(depth[index]) - float(depth.min())) / depth_range
265
+ radius = int(7 + 5 * relative_depth)
266
+ color = ATOM_COLORS.get(atom.GetSymbol().upper(), "#64748b")
267
+ drawing.ellipse(
268
+ (x - radius, y - radius, x + radius, y + radius),
269
+ fill=color,
270
+ outline="#ffffff",
271
+ width=2,
272
+ )
273
+ if atom.GetSymbol() != "H":
274
+ drawing.text((x + radius + 2, y - radius), atom.GetSymbol(), fill="#0f172a")
275
+ return image
276
 
277
 
278
+ def protein_3d(pdb_file) -> Image.Image:
279
  if pdb_file is None:
280
  raise gr.Error("Upload a PDB file. A sequence alone has no 3D coordinates.")
281
+ coordinates = []
282
+ for line in Path(pdb_file).read_text(encoding="utf-8", errors="replace").splitlines():
283
+ if line.startswith(("ATOM ", "HETATM")) and line[12:16].strip() == "CA":
284
+ try:
285
+ coordinates.append(
286
+ [float(line[30:38]), float(line[38:46]), float(line[46:54])]
287
+ )
288
+ except ValueError:
289
+ continue
290
+ if len(coordinates) < 2:
291
+ raise gr.Error("The PDB file does not contain enough alpha-carbon coordinates.")
292
+
293
+ width, height = 900, 600
294
+ points, _ = _project_coordinates(
295
+ np.asarray(coordinates, dtype=np.float32), width, height, padding=60
296
+ )
297
+ image = Image.new("RGB", (width, height), "#f8fafc")
298
+ drawing = ImageDraw.Draw(image)
299
+ denominator = max(len(points) - 1, 1)
300
+ for index in range(len(points) - 1):
301
+ fraction = index / denominator
302
+ color = (
303
+ int(37 + 202 * fraction),
304
+ int(99 + 20 * (1 - fraction)),
305
+ int(235 - 160 * fraction),
306
+ )
307
+ drawing.line(
308
+ [
309
+ tuple(map(float, points[index])),
310
+ tuple(map(float, points[index + 1])),
311
+ ],
312
+ fill=color,
313
+ width=5,
314
+ )
315
+ return image
316
 
317
 
318
  EXAMPLE_PROTEIN = "MAVMKNYLLPILVLFLAYYYYSTNEEFRPEMLQGKKVIVTGASKGIGREMAYHLSKMGAHVVLTARSEEGLQK"
 
373
  gr.Markdown("## Compound")
374
  with gr.Row():
375
  image = gr.Image(label="2D structure", height=420)
376
+ view_3d = gr.Image(label="Generated 3D conformer projection", height=420)
377
  molecule_data = gr.JSON(label="Molecule descriptors")
378
 
379
  gr.Markdown("## Protein")
 
392
  "to inspect an experimentally determined or predicted structure."
393
  )
394
  pdb = gr.File(label="PDB file", file_types=[".pdb"], type="filepath")
395
+ pdb_view = gr.Image(label="Protein backbone projection", height=520)
396
  gr.Button("Render PDB structure").click(protein_3d, pdb, pdb_view)
397
  gr.Markdown(
398
  html.escape(
requirements.txt CHANGED
@@ -2,6 +2,6 @@ gradio>=5,<7
2
  huggingface-hub>=0.24
3
  numpy>=1.26,<3
4
  onnxruntime>=1.18
5
- py3Dmol>=2.2
6
  rdkit>=2024.3
7
  transformers>=4.57.3,<5
 
2
  huggingface-hub>=0.24
3
  numpy>=1.26,<3
4
  onnxruntime>=1.18
5
+ pillow>=10
6
  rdkit>=2024.3
7
  transformers>=4.57.3,<5