import os import sys import logging from typing import Optional from rdkit import Chem from rdkit.Chem import Draw if 'ipykernel' in sys.modules: from IPython.display import SVG from .chemoinformatics import get_atom_idx_at_attachment, canonize def safe_display(*args): """Displays content only if running in a Jupyter notebook.""" if 'ipykernel' in sys.modules: display(*args) else: logging.warning(*args) def display_mol( mol: Chem.Mol, w: int = 800, h: int = 300, legend: Optional[str] = None, use_smiles_as_legend: bool = True, display_svg: bool = True, ): """ Display a molecule in a Jupyter notebook. Useful for having """ if mol is None: logging.warning('Molecule is None') return None if use_smiles_as_legend and legend is None: legend = Chem.MolToSmiles(mol) if display_svg: mol.SetProp("_Name", Chem.MolToSmiles(mol, canonical=True)) d = Draw.rdMolDraw2D.MolDraw2DSVG(w, h, noFreetype=True) font_path = '/System/Library/Fonts/Supplemental/Arial.ttf' if os.path.exists(font_path): d.fontFile = font_path d.DrawMolecule(mol, legend=legend) d.FinishDrawing() svg = d.GetDrawingText() # Check if in Jupyter notebook if sys.modules.get('ipykernel', None): from IPython.display import SVG safe_display(SVG(svg)) else: img = Draw.MolToImage(mol, size=(w, h)) safe_display(img) def get_mapped_protac_img( protac_smiles: str, poi_smiles: str, linker_smiles: str, e3_smiles: str, w: int = 1000, h: int = 1000, useSVG: bool = False, display_image: bool = False, legend: Optional[str] = None, show_bond_indices: bool = False, ): """ Display a PROTAC molecule with the POI, linker, and E3 ligase highlighted. If `useSVG` is True, then the POI-Linker bond is highlighted in purple, whereas the E3-Linker bond is highlighted in green. If `useSVG` is False, then both splitting points are highlighted in purple. Args: protac_smiles: The SMILES string of the PROTAC. poi_smiles: The SMILES string of the POI. linker_smiles: The SMILES string of the linker. e3_smiles: The SMILES string of the E3 ligase. w: The width of the image. h: The height of the image. useSVG: Whether to use SVG format. display_image: Whether to display the image. legend: The legend to display. show_bond_indices: Whether to show bond indices in the image. """ protac_smiles = canonize(protac_smiles) e3_smiles = canonize(e3_smiles) poi_smiles = canonize(poi_smiles) linker_smiles = canonize(linker_smiles) # Check if any of the canonicalized SMILES is None if None in [protac_smiles, e3_smiles, poi_smiles, linker_smiles]: return None protac_mol = Chem.MolFromSmiles(protac_smiles) e3_mol = Chem.MolFromSmiles(e3_smiles) poi_mol = Chem.MolFromSmiles(poi_smiles) linker_mol = Chem.MolFromSmiles(linker_smiles) if None in [protac_mol, e3_mol, poi_mol, linker_mol]: return None if linker_smiles in ['[*:1][*:2]', '[*:2][*:1]']: logging.warning('WARNING. Linker is empty.') poi_attachment_idx = get_atom_idx_at_attachment(protac_mol, poi_mol, e3_mol) e3_attachment_idx = get_atom_idx_at_attachment(protac_mol, e3_mol, poi_mol) else: poi_attachment_idx = get_atom_idx_at_attachment(protac_mol, poi_mol, linker_mol) e3_attachment_idx = get_atom_idx_at_attachment(protac_mol, e3_mol, linker_mol) cyan = (0, 1, 1, 0.5) red = (1, 0, 0, 0.5) green = (0, 1, 0, 0.5) blue = (0, 0, 1, 0.5) purple = (1, 0, 1, 0.3) highlight_atoms = [] highlight_bonds = [] atom_colors = {} bond_colors = {} if poi_attachment_idx is not None: if len(poi_attachment_idx) != 2: if linker_smiles in ['[*:1][*:2]', '[*:2][*:1]']: logging.warning(f'WARNING. Linker is empty, no highlighting will be showed for the POI.') else: logging.warning(f'WARNING. POI attachment points must be only two, got instead: {poi_attachment_idx}') else: poi_bond_idx = protac_mol.GetBondBetweenAtoms(*poi_attachment_idx).GetIdx() highlight_atoms += poi_attachment_idx highlight_bonds.append(poi_bond_idx) atom_colors[poi_attachment_idx[0]] = purple atom_colors[poi_attachment_idx[1]] = purple bond_colors[poi_bond_idx] = purple if e3_attachment_idx is not None: if len(e3_attachment_idx) != 2: if linker_smiles in ['[*:1][*:2]', '[*:2][*:1]']: logging.warning(f'WARNING. Linker is empty, no highlighting will be showed for the E3.') else: logging.warning(f'WARNING. E3 attachment points must be only two, got instead: {e3_attachment_idx}') else: e3_bond_idx = protac_mol.GetBondBetweenAtoms(*e3_attachment_idx).GetIdx() highlight_atoms += e3_attachment_idx highlight_bonds.append(e3_bond_idx) atom_colors[e3_attachment_idx[0]] = green atom_colors[e3_attachment_idx[1]] = green bond_colors[e3_bond_idx] = green if useSVG: drawer = Draw.rdMolDraw2D.MolDraw2DSVG(w, h, noFreetype=True) options = drawer.drawOptions() options.fontFile = '/System/Library/Fonts/Supplemental/Arial.ttf' if legend is None: # legend = '.'.join([e3_smiles, linker_smiles, poi_smiles]) legend = "" drawer.DrawMolecule( protac_mol, legend=legend, highlightAtoms=highlight_atoms, highlightBonds=highlight_bonds, highlightAtomColors=atom_colors, highlightBondColors=bond_colors, ) # Add bond indices as text in the center of each bond if show_bond_indices: # Needs coordinates; ensure 2D coords present Chem.rdDepictor.Compute2DCoords(protac_mol) for bond in protac_mol.GetBonds(): idx = bond.GetIdx() begin = bond.GetBeginAtomIdx() end = bond.GetEndAtomIdx() begin_pos = drawer.GetDrawCoords(begin) end_pos = drawer.GetDrawCoords(end) mid_y = (begin_pos.y + end_pos.y) / 2 mid_x = (begin_pos.x + end_pos.x) / 2 drawer.DrawString(f"{idx}", Chem.rdGeometry.Point2D(mid_x, mid_y), rawCoords=True) drawer.FinishDrawing() svg_text = drawer.GetDrawingText() if display_image: safe_display(SVG(svg_text)) return svg_text else: img = Draw.MolToImage( protac_mol, size=(w, h), highlightColor=purple, highlightAtoms=highlight_atoms, highlightBonds=highlight_bonds, highlightAtomColors=atom_colors, highlightBondColors=bond_colors, ) if display_image: safe_display(img) return img