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)