๐Ÿ†• Generate HELM in Interactive Web Application๏ƒ

Problem๏ƒ

You want to generate HELM from SMILES in an interactive web application.

../_images/helm_generator_app-screenshot.png

See also

Ingredients๏ƒ

Difficulty Level๏ƒ

๐ŸŒถ๏ธ ๐ŸŒถ๏ธ

Source Code๏ƒ

helm_generator
#!/usr/bin/env python3
# (C) 2023 Cadence Design Systems, Inc. (Cadence)
# All rights reserved.
# TERMS FOR USE OF SAMPLE CODE The software below ("Sample Code") is
# provided to current licensees or subscribers of Cadence products or
# SaaS offerings (each a "Customer").
# Customer is hereby permitted to use, copy, and modify the Sample Code,
# subject to these terms. Cadence claims no rights to Customer's
# modifications. Modification of Sample Code is at Customer's sole and
# exclusive risk. Sample Code may require Customer to have a then
# current license or subscription to the applicable Cadence offering.
# THE SAMPLE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED.  OPENEYE DISCLAIMS ALL WARRANTIES, INCLUDING, BUT
# NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. In no event shall Cadence be
# liable for any damages or liability in connection with the Sample Code
# or its use.

"""Simple streamlit app to HELM generation."""

import base64
import enum
import os
import sys

import streamlit as st
from openeye import oechem, oedepict, oegrapheme


class DepictionStyle(enum.Enum):
    """Utility class for depiction style."""

    MonomerHighlight = "Monomer Highlight"
    MonomerGraph = "Monomer Graph"


@st.cache_resource
def load_openeye_monomers() -> oechem.OEMonomerSet:
    """Load OpenEye monomer set."""
    monomers = oechem.OEMonomerSet()
    oechem.OELoadOpenEyeMonomerSet(monomers)
    return monomers


def main() -> int:
    """Generate streamlit main application."""
    st.set_page_config(
        page_title="HELM Generation",
        page_icon=":material/image:",
        layout="wide",
        initial_sidebar_state="collapsed",
    )

    st.title("HELM Generation with OpenEye Monomer Set!")

    monomers = load_openeye_monomers()
    # input smiles
    mol: None | oechem.OEMolBase = _get_molecule_from_smiles()

    # image / helm generation options
    image_width, image_height, depiction_style = _generate_depiction_options_section()
    depict_options = _generate_depiction_option(depiction_style)

    code_set, allow_unspecified_stereo, allow_unmatched_fragment = (
        _generate_helm_generation_options_section(monomers)
    )

    helm_options = oechem.OEHelmGenerationOptions(
        code_set, allow_unspecified_stereo, allow_unmatched_fragment
    )

    result = oechem.OEHelmGenerationResult()
    helm: str = oechem.OEMolToHelm(mol, monomers, helm_options, result)

    # perceives monomers even when helm generation fails peptide can still be depicted
    oechem.OEDetectMonomers(mol, monomers, code_set)

    st.divider()

    if mol is not None and st.button(
        "Generate HELM and image!", icon=":material/image:"
    ):
        if not helm:
            st.error(f"HELM generation failed: {result.GetWarning()}")
        else:
            st.markdown(
                f"""### Generated HELM
```text
{helm}
```
"""
            )

        image = oedepict.OEImage(image_width, image_height)
        if depiction_style == DepictionStyle.MonomerHighlight:
            oedepict.OEPrepareDepiction(mol)
            oegrapheme.OEHighlightMonomers(image, mol, depict_options)
        else:
            oegrapheme.OEDrawMonomerGraph(image, mol, depict_options)
        oedepict.OEDrawCurvedBorder(image, oedepict.OELightGreyPen, 10)

        image_str = oedepict.OEWriteImageToString("svg", image)
        image_str = image_str.decode("utf-8")

        svg_b64 = base64.b64encode(image_str.encode("utf-8")).decode("utf-8")
        data_uri = f"data:image/svg+xml;base64,{svg_b64}"
        st.components.v1.html(
            f'<object data="{data_uri}" type="image/svg+xml" '
            f'width="{image_width}" height="{image_height}"></object>',
            height=image_height + 20,
        )

        st.download_button(
            label="Download image",
            data=image_str,
            file_name="helm.svg",
            mime="image/svg+xml",
            icon=":material/download:",
        )

    return os.EX_OK


def _generate_depiction_options_section() -> tuple[int, int, DepictionStyle]:
    with st.expander("Depiction Options", width=1200):
        col_width, col_height, style = st.columns(3, border=True)
        with col_width:
            image_width = st.slider(
                label="Width", min_value=200, max_value=1000, step=25, value=600
            )
        with col_height:
            image_height = st.slider(
                label="Height", min_value=200, max_value=1000, step=25, value=400
            )
        with style:
            depict_style = DepictionStyle(
                st.selectbox(
                    "Select depiction style", [t.value for t in DepictionStyle]
                )
            )

    return (image_width, image_height, depict_style)


def _generate_helm_generation_options_section(
    monomers: oechem.OEMonomerSet,
) -> tuple[str, bool, bool]:
    with st.expander("Helm Generation Options", width=1200):
        (
            code_set_widget,
            options_widget,
        ) = st.columns(2, border=True)

        code_sets = [monomers.GetPrimaryCodeSet()] + [
            c for c in monomers.GetCodeSets() if c != monomers.GetPrimaryCodeSet()
        ]
        with code_set_widget:
            code_set = st.selectbox("Select code-set", list(code_sets))
        with options_widget:
            allow_unspecified_stereo = st.toggle("Allow unspecified stereochemistry")
            allow_unmatched_fragment = st.toggle("Allow unmatched fragment(s)")
        # with unmatched_fragment_widget:

    return (code_set, allow_unspecified_stereo, allow_unmatched_fragment)


def _get_molecule_from_smiles() -> None | oechem.OEMolBase:
    """Get a molecule by parsing SMILES."""
    mol = oechem.OEGraphMol()
    smiles = st.text_input(
        "Enter valid SMILES string of a peptide!",
        value="CC[C@H](C)[C@@H](C(=O)N[C@@H](CC(=O)O)C(=O)N[C@@H](CCC(=O)O)C(=O)O)NC(=O)[C@H]"
        "([C@@H](C)O)NC(=O)[C@@H]1CCCN1C(=O)[C@H](CCC(=O)O)NC(=O)[C@@H]2CCCN2",
    )

    if not oechem.OESmilesToMol(mol, smiles):
        st.error(f"Can not parse: {smiles}")
    return mol


def _generate_depiction_option(
    style: DepictionStyle,
) -> (
    oegrapheme.OEHighlightMonomerDisplayOptions
    | oegrapheme.OEMonomerGraphDisplayOptions
):
    option: (
        oegrapheme.OEHighlightMonomerDisplayOptions
        | oegrapheme.OEMonomerGraphDisplayOptions
    )

    if style == DepictionStyle.MonomerHighlight:
        option = oegrapheme.OEHighlightMonomerDisplayOptions()
        option.SetTitleLocation(oedepict.OETitleLocation_Hidden)
        option.SetAtomStereoStyle(oedepict.OEAtomStereoStyle_Display_All)
        option.SetHighlightUnspecifiedStereo(True)
    else:
        option = oegrapheme.OEMonomerGraphDisplayOptions()
        option.SetInteractiveEffect(oedepict.OEInteractiveEffect_Hover)

    return option


if __name__ == "__main__":
    sys.exit(main())

Usage๏ƒ

> helm_generator_app

The Streamlit app will be available at:

You can now view your Streamlit app in your browser.

Local URL: http://localhost:8501
...

The script will automatically load OpenEyeโ€™s built-in monomer sets. It generates HELM from the input SMILES and generates an image of the resulting peptide structure.

The app allows you to: * Input a SMILES string and generate the corresponding HELM. * Visualize the generated HELM as a 2D image in two different styles (monomer-graph and highlight). * Download the generated image in SVG format.

See also

See also in OEChem TK manual๏ƒ

API

See also in OEGrapheme TK manual๏ƒ

API