import html
import logging
from typing import List, Tuple
import numpy as np
import pandas as pd
from rdkit import Chem
from rdkit.Chem import Draw
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
def _draw_molecule_svg(smiles: str, size: Tuple[int, int]) -> str:
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return f"
{html.escape(smiles)}"
svg = Draw.MolsToGridImage([mol], molsPerRow=1, subImgSize=size, useSVG=True)
return str(svg).replace("", "")
def _draw_static_grid(
result_df: pd.DataFrame, n_cols: int, size: Tuple[int, int]
) -> str:
columns = [column for column in result_df.columns if column != "SMILES"]
cards = []
for _, row in result_df.iterrows():
smiles = str(row["SMILES"])
details = "".join(
f"{html.escape(str(column))}"
f"{html.escape(str(row[column]))}"
for column in columns
)
cards.append(
""
f"{_draw_molecule_svg(smiles, size)}
"
f"{details}
"
""
)
return f"""
{''.join(cards)}
"""
def draw_grid_predict(
sequences: List[str], properties: np.array, property_names: List[str], domain: str
) -> str:
"""
Uses RDKit SVGs to draw a HTML grid for the prediction
Args:
sequences: Sequences for which properties are predicted.
properties: Predicted properties. Array of shape (n_samples, n_properties).
names: List of property names
domain: Domain of the prediction (molecules or proteins).
Returns:
HTML to display
"""
if domain not in ["Molecules", "Proteins"]:
raise ValueError(f"Unsupported domain {domain}")
if domain == "Proteins":
converter = lambda x: Chem.MolToSmiles(Chem.MolFromFASTA(x))
else:
converter = lambda x: x
smiles = []
for sequence in sequences:
try:
seq = converter(sequence)
smiles.append(seq)
except Exception:
logger.warning(f"Could not draw sequence {seq}")
result = pd.DataFrame({"SMILES": smiles})
for i, name in enumerate(property_names):
result[name] = properties[:, i]
n_cols = min(3, len(result))
size = (140, 200) if len(result) > 3 else (600, 700)
return _draw_static_grid(result, n_cols=n_cols, size=size)