rdkit_api / app.py
Vaishnav14220
Add atom labels to 3D molecule viewer
1ee8400
raw
history blame
7.73 kB
import gradio as gr
from rdkit import Chem
from rdkit.Chem import Descriptors, Draw, AllChem
import cirpy
# RDKit API with multiple endpoints
def _mol_from_smiles(smiles: str):
mol = Chem.MolFromSmiles(smiles)
if mol is None:
raise gr.Error("Invalid SMILES string.")
return mol
def smiles_to_canonical(smiles: str) -> str:
mol = _mol_from_smiles(smiles)
return Chem.MolToSmiles(mol)
def molecular_weight(smiles: str) -> float:
mol = _mol_from_smiles(smiles)
return float(Descriptors.MolWt(mol))
def logp(smiles: str) -> float:
mol = _mol_from_smiles(smiles)
return float(Descriptors.MolLogP(mol))
def tpsa(smiles: str) -> float:
mol = _mol_from_smiles(smiles)
return float(Descriptors.TPSA(mol))
def mol_image(smiles: str):
mol = _mol_from_smiles(smiles)
return Draw.MolToImage(mol)
def name_to_smiles(name: str) -> str:
"""Convert chemical name to SMILES using Chemical Identifier Resolver (CIR)"""
try:
smiles = cirpy.resolve(name, 'smiles')
if smiles is None:
raise gr.Error(f"Could not find SMILES for chemical name: {name}")
return smiles
except Exception as e:
raise gr.Error(f"Error converting name to SMILES: {str(e)}")
def name_to_3d_molecule(name: str) -> tuple:
"""Convert chemical name to 3D molecule SDF and 2D visualization"""
try:
# Convert name to SMILES
smiles = cirpy.resolve(name, 'smiles')
if smiles is None:
raise gr.Error(f"Could not find SMILES for chemical name: {name}")
# Create molecule from SMILES
mol = Chem.MolFromSmiles(smiles)
if mol is None:
raise gr.Error(f"Could not create molecule from SMILES: {smiles}")
# Add hydrogens for better 3D structure
mol = Chem.AddHs(mol)
# Generate 3D coordinates
success = AllChem.EmbedMolecule(mol, AllChem.ETKDG())
if success == -1:
raise gr.Error(f"Could not generate 3D coordinates for: {name}")
# Optimize geometry
AllChem.MMFFOptimizeMolecule(mol)
# Generate SDF content
sdf_content = Chem.MolToMolBlock(mol)
# Generate 2D image for preview
mol_2d = Chem.MolFromSmiles(smiles) # Get 2D version
img = Draw.MolToImage(mol_2d, size=(400, 400))
# Convert image to base64
import io
import base64
import json
buffered = io.BytesIO()
img.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
# Get SMILES string
canonical_smiles = Chem.MolToSmiles(mol_2d)
# Prepare SDF content for JavaScript (escape backticks)
sdf_content_escaped = sdf_content.replace('`', '\\`')
# Create data URI for the 3D viewer HTML
viewer_html = f"""
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://3dmol.csb.pitt.edu/build/3Dmol-min.js"></script>
<style>
body {{ margin: 0; padding: 0; background: white; }}
#viewer {{ width: 100%; height: 100vh; }}
</style>
</head>
<body>
<div id="viewer"></div>
<script>
$(function() {{
let viewer = $3Dmol.createViewer("viewer", {{backgroundColor: 'white'}});
let sdfData = `{sdf_content_escaped}`;
viewer.addModel(sdfData, "sdf");
viewer.setStyle({{}}, {{stick: {{radius: 0.15, colorscheme: 'Jmol'}}, sphere: {{radius: 0.4, colorscheme: 'Jmol'}}}});
viewer.addLabels({{elem: true}}, {{fontSize: 12, fontColor: 'black', backgroundColor: 'white', backgroundOpacity: 0.7}});
viewer.zoomTo();
viewer.render();
}});
</script>
</body>
</html>
"""
viewer_data_uri = "data:text/html;base64," + base64.b64encode(viewer_html.encode()).decode()
# Create HTML with 2D and 3D viewers side by side
html_content = f"""
<div style="padding: 20px;">
<h3 style="text-align: center;">{name}</h3>
<p style="text-align: center;"><strong>SMILES:</strong> {canonical_smiles}</p>
<div style="display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin-top: 20px;">
<div style="text-align: center;">
<h4>2D Structure</h4>
<img src="data:image/png;base64,{img_str}" style="max-width: 400px; border: 1px solid #ddd; border-radius: 8px; padding: 10px; background: white;">
</div>
<div style="text-align: center;">
<h4>3D Interactive Model</h4>
<iframe src="{viewer_data_uri}" style="width: 400px; height: 400px; border: 1px solid #ddd; border-radius: 8px;"></iframe>
<p style="margin-top: 10px; font-size: 12px; color: #666;">Drag to rotate, scroll to zoom</p>
</div>
</div>
</div>
"""
return html_content, sdf_content
except Exception as e:
raise gr.Error(f"Error creating molecule: {str(e)}")
smiles_interface = gr.Interface(
fn=smiles_to_canonical,
inputs=gr.Textbox(label="SMILES"),
outputs=gr.Textbox(label="Canonical SMILES"),
api_name="smiles_to_mol",
description="Convert an input SMILES string to its canonical form.",
)
name_interface = gr.Interface(
fn=name_to_smiles,
inputs=gr.Textbox(label="Chemical Name", placeholder="e.g., aspirin, caffeine, benzene"),
outputs=gr.Textbox(label="SMILES"),
api_name="name_to_smiles",
description="Convert a chemical name to SMILES notation.",
examples=[["aspirin"], ["caffeine"], ["benzene"], ["ethanol"]],
)
mw_interface = gr.Interface(
fn=molecular_weight,
inputs=gr.Textbox(label="SMILES"),
outputs=gr.Number(label="Molecular Weight (g/mol)"),
api_name="molecular_weight",
description="Compute the molecular weight from a SMILES string.",
)
logp_interface = gr.Interface(
fn=logp,
inputs=gr.Textbox(label="SMILES"),
outputs=gr.Number(label="logP"),
api_name="logp",
description="Calculate the octanol/water partition coefficient (logP).",
)
tpsa_interface = gr.Interface(
fn=tpsa,
inputs=gr.Textbox(label="SMILES"),
outputs=gr.Number(label="TPSA"),
api_name="tpsa",
description="Calculate the topological polar surface area (TPSA).",
)
molecule_3d_interface = gr.Interface(
fn=name_to_3d_molecule,
inputs=gr.Textbox(label="Chemical Name", placeholder="e.g., benzene, aspirin, caffeine, glucose"),
outputs=[
gr.HTML(label="2D and 3D Molecular Viewer"),
gr.Textbox(label="3D SDF Content (optional - for external viewers)", lines=10, max_lines=20, visible=False)
],
api_name="name_to_molecule",
description="View 2D structure and interactive 3D molecule. Drag to rotate the 3D model, scroll to zoom.",
cache_examples=False,
)
demo = gr.TabbedInterface(
[name_interface, molecule_3d_interface, smiles_interface, mw_interface, logp_interface, tpsa_interface],
[
"Name to SMILES",
"Molecule Viewer",
"SMILES to Canonical",
"Molecular Weight",
"LogP",
"TPSA",
],
title="RDKit API",
css=".gradio-container {max-width: 800px; margin: auto;}",
)
if __name__ == "__main__":
demo.queue().launch(server_name="0.0.0.0", server_port=7860)