Source code for cnotebook.render

import logging
import base64
from .context import CNotebookContext, pass_cnotebook_context
from openeye import oechem, oedepict

log = logging.getLogger("cnotebook")


########################################################################################################################
# Renderers for specific types
########################################################################################################################

[docs] def create_img_tag( width: float, height: float, image_mime_type: str, image_bytes: bytes, wrap_svg: bool = True ) -> str: """ Create the <img> HTML tag for rendering image bytes. This could be either plain text (in the case of SVG) or base64 encoded (in binary format cases). :param width: Image width :param height: Image height :param image_mime_type: Image MIME type :param image_bytes: Image bytes :param wrap_svg: Wrap SVG in a specifically sized <div> tag for maximum control of size :return: Image tag """ if image_mime_type == "image/svg+xml": if wrap_svg: return '<div style=\'width:{}px;max-width:{}px;height:{}px;max-height:{}px\'>\n\t{}\n</div>'.format( int(width), int(width), int(height), int(height), image_bytes.decode("utf-8") ) else: return image_bytes.decode("utf-8") return '<img src=\'data:{};base64,{}\' style=\'width:{}px;max-width:{}px;height:{}px;max-height:{}px\' />'.format( image_mime_type, base64.b64encode(image_bytes).decode("utf-8"), int(width), int(width), int(height), int(height) )
[docs] @pass_cnotebook_context def oedisp_to_html( disp: oedepict.OE2DMolDisplay, *, ctx: CNotebookContext ) -> str: """ Convert an OpenEye 2D molecule display object to HTML :param ctx: Current molecule render context :param disp: OpenEye 2D molecule display object :return: HTML image tag """ # Convert the display object to an <img> tag image = oedepict.OEImage(disp.GetWidth(), disp.GetHeight()) oedepict.OERenderMolecule(image, disp) image_bytes = oedepict.OEWriteImageToString(ctx.image_format, image) return create_img_tag( disp.GetWidth(), disp.GetHeight(), image_mime_type=ctx.image_mime_type, image_bytes=image_bytes, wrap_svg=ctx.structure_scale != oedepict.OEScale_AutoScale )
[docs] def render_empty_molecule(*, ctx: CNotebookContext) -> str: """ Render an image that says Empty Molecule :param ctx: Render context :return: Image tag """ image = oedepict.OEImage(ctx.min_width, ctx.min_height) image.DrawText( oedepict.OE2DPoint(ctx.min_width / 2, ctx.min_height / 2), "Empty Molecule", oedepict.OEFont( oedepict.OEFontFamily_Arial, oedepict.OEFontStyle_Normal, 14, oedepict.OEAlignment_Center, oechem.OEDarkBlue ) ) return create_img_tag( ctx.min_width, ctx.min_height, image_mime_type=ctx.image_mime_type, image_bytes=oedepict.OEWriteImageToString(ctx.image_format, image), wrap_svg=ctx.structure_scale != oedepict.OEScale_AutoScale )
[docs] def render_invalid_molecule(*, ctx: CNotebookContext) -> str: """ Render an image that says Empty Molecule :param ctx: Render context :return: Image tag """ image = oedepict.OEImage(ctx.min_width, ctx.min_height) image.DrawText( oedepict.OE2DPoint(ctx.min_width / 2, ctx.min_height / 2), "Invalid Molecule", oedepict.OEFont( oedepict.OEFontFamily_Arial, oedepict.OEFontStyle_Normal, 14, oedepict.OEAlignment_Center, oechem.OERed ) ) return create_img_tag( ctx.min_width, ctx.min_height, image_mime_type=ctx.image_mime_type, image_bytes=oedepict.OEWriteImageToString(ctx.image_format, image), wrap_svg=ctx.structure_scale != oedepict.OEScale_AutoScale )
def _create_exceeds_heavy_atoms_image( mol: oechem.OEMolBase, *, ctx: CNotebookContext ) -> oedepict.OEImage: """Create a placeholder OEImage for molecules that exceed the heavy atom limit. Draws "Exceeds Max Heavy Atoms" and "for Rendering" centered on two lines, and optionally the molecule title below if ``ctx.title`` is enabled. :param mol: Molecule (used to retrieve the title). :param ctx: Render context. :returns: Placeholder image. """ image = oedepict.OEImage(ctx.min_width, ctx.min_height) center_x = ctx.min_width / 2 center_y = ctx.min_height / 2 font = oedepict.OEFont( oedepict.OEFontFamily_Arial, oedepict.OEFontStyle_Normal, 14, oedepict.OEAlignment_Center, oechem.OEDarkBlue ) image.DrawText( oedepict.OE2DPoint(center_x, center_y - 10), "Exceeds Max Heavy Atoms", font ) image.DrawText( oedepict.OE2DPoint(center_x, center_y + 10), "for Rendering", font ) if ctx.title: mol_title = mol.GetTitle() if mol_title: title_font = oedepict.OEFont( oedepict.OEFontFamily_Arial, oedepict.OEFontStyle_Normal, 14, oedepict.OEAlignment_Center, oechem.OEBlack ) image.DrawText( oedepict.OE2DPoint(center_x, center_y + 35), mol_title, title_font ) return image
[docs] def render_exceeds_max_heavy_atoms( mol: oechem.OEMolBase, *, ctx: CNotebookContext ) -> str: """Render a placeholder image for molecules that exceed the heavy atom limit. :param mol: Molecule (used to retrieve the title). :param ctx: Render context. :returns: HTML image tag. """ image = _create_exceeds_heavy_atoms_image(mol, ctx=ctx) return create_img_tag( ctx.min_width, ctx.min_height, image_mime_type=ctx.image_mime_type, image_bytes=oedepict.OEWriteImageToString(ctx.image_format, image), wrap_svg=ctx.structure_scale != oedepict.OEScale_AutoScale )
[docs] def oemol_to_disp( mol: oechem.OEMolBase, *, ctx: CNotebookContext ) -> oedepict.OE2DMolDisplay: """ Convert a valid OpenEye molecule object to a display object for depiction. Note that it is highly recommended to test that the molecule is valid first before calling this function. :param ctx: Render context :param mol: Molecule to convert :return: Display object for depiction """ # Only recalculate coordinates if we don't have a 2D structure if mol.GetDimension() == 2: oedepict.OEPrepareDepiction(mol, False) else: oedepict.OEPrepareDepiction(mol, True) return ctx.create_molecule_display(mol)
[docs] def oemol_to_image( mol: oechem.OEMolBase, *, ctx: CNotebookContext ) -> oedepict.OEImage: """Convert an OpenEye molecule to an OEImage. Handles valid, empty, and invalid molecules. For valid molecules the molecule is rendered via :func:`oemol_to_disp`. Empty and invalid molecules produce placeholder images with descriptive text. :param mol: Molecule to convert. :param ctx: Render context. :returns: Rendered image. """ if mol.IsValid(): if (ctx.max_heavy_atoms is not None and oechem.OECount(mol, oechem.OEIsHeavy()) > ctx.max_heavy_atoms): return _create_exceeds_heavy_atoms_image(mol, ctx=ctx) disp = oemol_to_disp(mol, ctx=ctx) image = oedepict.OEImage(disp.GetWidth(), disp.GetHeight()) oedepict.OERenderMolecule(image, disp) return image if mol.NumAtoms() == 0: image = oedepict.OEImage(ctx.min_width, ctx.min_height) image.DrawText( oedepict.OE2DPoint(ctx.min_width / 2, ctx.min_height / 2), "Empty Molecule", oedepict.OEFont( oedepict.OEFontFamily_Arial, oedepict.OEFontStyle_Normal, 14, oedepict.OEAlignment_Center, oechem.OEDarkBlue ) ) return image # Invalid molecule with atoms image = oedepict.OEImage(ctx.min_width, ctx.min_height) image.DrawText( oedepict.OE2DPoint(ctx.min_width / 2, ctx.min_height / 2), "Invalid Molecule", oedepict.OEFont( oedepict.OEFontFamily_Arial, oedepict.OEFontStyle_Normal, 14, oedepict.OEAlignment_Center, oechem.OERed ) ) return image
def _draw_du_label(image: oedepict.OEImageBase) -> None: """Draw a small semi-transparent "OEDesignUnit" label in the upper right. :param image: Image to draw the label on. """ padding = 4 font = oedepict.OEFont( oedepict.OEFontFamily_Arial, oedepict.OEFontStyle_Bold, 14, oedepict.OEAlignment_Right, oechem.OEColor(180, 180, 180, 128) ) image.DrawText( oedepict.OE2DPoint(image.GetWidth() - padding, image.GetHeight() - padding), # + 12 "OEDesignUnit", font )
[docs] def oedu_to_disp( du: oechem.OEDesignUnit, *, ctx: CNotebookContext ) -> tuple[oedepict.OE2DMolDisplay, oechem.OEGraphMol] | None: """Convert an OEDesignUnit to a 2D molecule display. Extracts the ligand from the design unit and renders it as a 2D display. Returns ``None`` if the design unit has no ligand (apo structure). .. important:: The returned ligand molecule **must** be kept alive as long as the display is in use. The ``OE2DMolDisplay`` holds an internal C++ reference to the molecule; if the molecule is garbage-collected the display will crash when rendered. :param du: Design unit to render. :param ctx: Render context. :returns: Tuple of (display, ligand), or ``None`` if no ligand. """ lig = oechem.OEGraphMol() du.GetLigand(lig) if lig.NumAtoms() == 0: return None disp = oemol_to_disp(lig, ctx=ctx) return disp, lig
[docs] def oedu_to_image( du: oechem.OEDesignUnit, *, ctx: CNotebookContext ) -> oedepict.OEImage: """Convert an OEDesignUnit to an OEImage. If the design unit has a ligand, renders the ligand with a small "OEDesignUnit" label. If no ligand is present (apo structure), renders a placeholder image with "Apo DesignUnit" text. :param du: Design unit to render. :param ctx: Render context. :returns: Rendered image. """ result = oedu_to_disp(du, ctx=ctx) if result is not None: disp, _lig = result # _lig must stay alive while disp is rendered width, height = disp.GetWidth(), disp.GetHeight() image = oedepict.OEImage(width, height) oedepict.OERenderMolecule(image, disp) _draw_du_label(image) return image # Apo case: no ligand image = oedepict.OEImage(ctx.min_width, ctx.min_height) _draw_du_label(image) image.DrawText( oedepict.OE2DPoint(ctx.min_width / 2, ctx.min_height / 2), "Apo DesignUnit", oedepict.OEFont( oedepict.OEFontFamily_Arial, oedepict.OEFontStyle_Bold, 14, oedepict.OEAlignment_Center, oechem.OEDarkBlue ) ) return image
[docs] @pass_cnotebook_context def oemol_to_html(mol: oechem.OEMolBase, *, ctx: CNotebookContext) -> str: """Convert an OpenEye molecule to HTML. :param mol: Molecule to convert. :param ctx: Render context. :returns: HTML image tag. """ return oeimage_to_html(oemol_to_image(mol, ctx=ctx), ctx=ctx)
[docs] @pass_cnotebook_context def oedu_to_html(du: oechem.OEDesignUnit, *, ctx: CNotebookContext) -> str: """Convert an OEDesignUnit to HTML. If the design unit has a ligand, renders the ligand with a small "OEDesignUnit" label. If no ligand is present (apo structure), renders a placeholder image with "Apo DesignUnit" text. :param du: Design unit to render. :param ctx: Render context. :returns: HTML image tag. """ return oeimage_to_html(oedu_to_image(du, ctx=ctx), ctx=ctx)
[docs] @pass_cnotebook_context def oeimage_to_html(image: oedepict.OEImage, *, ctx: CNotebookContext) -> str: """ Convert an OEImage to HTML :param ctx: Render context :param image: Image to render :return: HTML string """ # Convert the image to an <img> tag image_bytes = oedepict.OEWriteImageToString(ctx.image_format, image) return create_img_tag( image.GetWidth(), image.GetHeight(), image_mime_type=ctx.image_mime_type, image_bytes=image_bytes, wrap_svg=ctx.structure_scale != oedepict.OEScale_AutoScale )