Introduction

The OpenEye Python Cookbook is a collection of solutions and practical examples for solving cheminformatics and molecule modeling problems using various OpenEye toolkits.

To install OpenEye’s Python package, please see instructions in the Getting Started with OpenEye Python section of the main OpenEye documentation.

We expect that you are familiar with at least some Python and have used OpenEye toolkits before. The main purpose of this documentation is to illustrate that by combining various OpenEye toolkits you can solve a wide range of cheminformatics and molecular modeling problems.

_images/oe-toolkits-blocks.svg

The OpenEye Toolkit Ecosystem

Installation

The oecookbook package requires that you have a valid OpenEye license.

The OpenEye Python Cookbook can be installed via pip using either conda or uv. The oecookbook will install the required dependencies for you, including the compatible OpenEye toolkits.

Conda

> conda create --name oecookbook python=3.12
> conda activate oecookbook
> pip install --extra-index-url https://pypi.anaconda.org/openeye/simple oecookbook

UV

> uv venv oecookbook
> source oecookbook/bin/activate
> uv pip install --extra-index-url https://pypi.anaconda.org/openeye/simple oecookbook

Integration Testing

The OpenEye Python Cookbook ship with a simple set of integration tests to make sure all the libraries function as intended. The test suite is not as exhaustive as the test suite used internally, it is just meant to ensure the OpenEye Python Toolkits infrastructure is working as intended.

Note

Running the integration test requires scripttest and pytest packages. These packages will be installed on-the-fly, if necessary. Again using conda or virtualenv environments will ensure that these packages will not be installed into the global Python environment.

> oecookbook_package_test

Note

Test might fail if you do not have a valid OpenEye license for a specific toolkit or if your license has expired.

Available Scripts

> find_scripts --help
_images/find_scripts-help.svg
> find_scripts 
_images/find_scripts-01-stdout.svg

Outline of Recipes

Each recipe in this documentation is divided into the following sections:

  • Problem - brief description of the problem you are trying to solve

  • Ingredients - the list of the OpenEye toolkits you need to solve this problem

  • Difficulty Level - each recipe is put into one of the following categories:

    🌶️ - novice

    Requires very little prior knowledge.

    🌶️ 🌶️ - intermediate

    Requires a fair understanding of the toolkit libraries that are used to solve the problem.

    🌶️ 🌶️ 🌶️ - expert

    Requires a deep understanding of the toolkit libraries that are used to solve the problem.

  • Download - most recipes provide a self-contained Python script that you can download and run

    Download code

    peptide2img.py

  • Source Code - complete source code listing for the recipe

    peptide2img
    #!/usr/bin/env python3
    # (C) 2026 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.
    
    """Depict peptide."""
    
    import argparse
    import enum
    import io
    import json
    import os
    import pathlib
    import sys
    
    import rich.console
    from openeye import oechem, oedepict, oegrapheme
    from PIL import Image
    from rich_argparse import HelpPreviewAction, RichHelpFormatter
    
    __SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
    __SCRIPT_DESC__ = "Depict peptide."
    __SCRIPT_TOOLKITS__ = ["oechem", "oedepict", "oegrapheme"]
    __SCRIPT_KEYWORDS__ = ["HELM", "monomer", "peptide", "peptide-informatics", "depiction"]
    __SCRIPT_CATEGORIES__ = ["depiction", "peptide-informatics"]
    
    
    class DepictionStyle(enum.Enum):
        """Utility enum class for peptide depiction style."""
    
        MonomerHighlight = "highlight"
        MonomerGraph = "graph"
    
        def __str__(self) -> str:
            """Convert to string representation."""
            return self.value
    
    
    class InteractiveEffect(enum.Enum):
        """Utility enum class for peptide interactive effect."""
    
        none = "none"
        hover = "hover"
        toggle = "toggle"
    
        def __str__(self) -> str:
            """Convert to string representation."""
            return self.value
    
    
    def parse_options() -> argparse.Namespace:
        """Set up command line options."""
        parser = argparse.ArgumentParser(
            add_help=True,
            formatter_class=RichHelpFormatter,
            description="[yellow]"
            + __SCRIPT_DESC__
            + " Supported image formats: svg, png"
            + "[/yellow]",
        )
    
        # input options
        input_group = parser.add_argument_group("Input peptide")
        exclusive_input_group = input_group.add_mutually_exclusive_group(required=True)
        exclusive_input_group.add_argument(
            "--helm",
            metavar="HELM",
            type=str,
            required=False,
            help="input HELM string",
        )
        exclusive_input_group.add_argument(
            "--smiles",
            metavar="SMILES",
            type=str,
            required=False,
            help="input SMILES string",
        )
        exclusive_input_group.add_argument(
            "--mol",
            metavar="MOL-FILE",
            type=str,
            required=False,
            help="input molecule file (oeb, sdf, fasta)",
        )
    
        monomers_group = parser.add_argument_group("Monomer set options")
        _add_monomer_collection(monomers_group)
    
        image_group = parser.add_argument_group("Image options")
        image_group.add_argument(
            "--image",
            type=str,
            required=False,
            metavar="IMAGE-FILE",
            help="output image file (PNG, SVG) (required: %(required)s) -- if no output is provided the image will be displayed on the  screen",
        )
        image_group.add_argument(
            "--width",
            type=int,
            default=800,
            help="width of output image (default: %(default)s)",
        )
        image_group.add_argument(
            "--height",
            type=int,
            default=600,
            help="height of output image (default: %(default)s)",
        )
    
        depiction_group = parser.add_argument_group("Depiction options")
        depiction_group.add_argument(
            "--style",
            type=DepictionStyle,
            default=DepictionStyle.MonomerGraph,
            choices=list(DepictionStyle),
            help="peptide depiction style (default: %(default)s)",
        )
        depiction_group.add_argument(
            "--highlight-backbone",
            default=False,
            action="store_true",
            help="highlight backbone atoms (default: %(default)s)",
        )
        depiction_group.add_argument(
            "--label-backbone-atoms",
            default=False,
            action="store_true",
            help="label backbone atoms (default: %(default)s)",
        )
        depiction_group.add_argument(
            "--interactive",
            type=InteractiveEffect,
            default=InteractiveEffect.none,
            choices=list(InteractiveEffect),
            help="monomers of HELM graph depicted on mouse over or click (SVG-only feature) (default: %(default)s)",
        )
        depiction_group.add_argument(
            "--algorithmic-layout",
            default=False,
            action="store_true",
            help="use algorithmic layout for coordinate generation in highlight mode (default: %(default)s)",
        )
        parser.add_argument("--help-image", action=HelpPreviewAction)
        return parser.parse_args()
    
    
    def main() -> int:
        """Depict peptide."""
        args = parse_options()
    
        console = rich.console.Console()
    
        monomers = _get_monomer_collection(args)
        code_set = monomers.GetPrimaryCodeSet() if args.code_set is None else args.code_set
        if not monomers.HasCodeSet(code_set):
            console.print(
                f"[red]Warning: invalid code set `{code_set}. available sets are: {monomers.GetCodeSets()}`![/red]"
            )
            return os.EX_DATAERR
    
        _check_image_file(args)
    
        mol: oechem.OEMolBase | None = _get_molecule(args, monomers, console)
        if not mol:  # error message already printed
            return os.EX_DATAERR
    
        if not args.helm:
            oechem.OEDetectMonomers(mol, monomers, code_set)
        if args.highlight_backbone or args.label_backbone_atoms:
            oechem.OEPerceivePeptideBackbone(mol)
    
        if oechem.OECount(mol, oechem.OEIsMonomerGroup()) == 0:
            console.print("[red]Warning: No monomer is detected in input molecule![/red]")
            return os.EX_DATAERR
    
        image = oedepict.OEImage(args.width, args.height)
        match args.style:
            case DepictionStyle.MonomerHighlight:
                depict_monomer_highlight(
                    image,
                    mol,
                    args.algorithmic_layout,
                    args.highlight_backbone,
                    args.label_backbone_atoms,
                )
            case DepictionStyle.MonomerGraph:
                interactive = args.interactive
                if args.image is None or pathlib.Path(args.image).suffix != ".svg":
                    interactive = InteractiveEffect.none
    
                if not depict_monomer_graph(image, mol, code_set, interactive):
                    console.print("[red]Failed to draw monomer graph![/red]")
                    return os.EX_DATAERR
            case _:
                console.print("[red]Unknown depiction style![/red]")
                return os.EX_DATAERR
    
        oedepict.OEDrawCurvedBorder(image, oedepict.OELightGreyPen, 10)
    
        if args.image:
            oedepict.OEWriteImage(args.image, image)
        else:
            _img = Image.open(io.BytesIO(oedepict.OEWriteImageToBytes("png", image)))
            _img.show()
    
        return os.EX_OK
    
    
    def depict_monomer_graph(
        image: oedepict.OEImage,
        mol: oechem.OEMolBase,
        code_set: str,
        interactive: InteractiveEffect,
    ) -> bool:
        """Depict peptide in monomer graph style."""
        opts = oegrapheme.OEMonomerGraphDisplayOptions()
        opts.SetMonomerColorFunctor(OEAnalogColor(code_set))
        if interactive == InteractiveEffect.toggle:
            opts.SetInteractiveEffect(oedepict.OEInteractiveEffect_Toggle)
            opts.SetMonomerScale(0.33)
        elif interactive == InteractiveEffect.hover:
            opts.SetInteractiveEffect(oedepict.OEInteractiveEffect_Hover)
    
        if not oegrapheme.OEDrawMonomerGraph(image, mol, opts):
            return False
        if interactive != InteractiveEffect.none:
            oedepict.OEAddInteractiveIcon(image, oedepict.OEIconLocation_Default, 0.5)
        return True
    
    
    def depict_monomer_highlight(
        image: oedepict.OEImageBase,
        mol: oechem.OEMolBase,
        algorithmic_layout: bool,
        highlight_backbone: bool,
        label_backbone_atoms: bool,
    ) -> None:
        """Depict the monomer with highlights for the backbone and labels for backbone atoms."""
        prep_opts = oedepict.OEPrepareDepictionOptions()
        if algorithmic_layout:
            prep_opts.SetOptimizeMacrocycles(True)
        oedepict.OEPrepareDepiction(mol, prep_opts)
        highlight_opts = oegrapheme.OEHighlightMonomerDisplayOptions(
            image.GetWidth(), image.GetHeight(), oedepict.OEScale_AutoScale
        )
        highlight_opts.SetTitleLocation(oedepict.OETitleLocation_Hidden)
        highlight_opts.SetAtomStereoStyle(oedepict.OEAtomStereoStyle_Display_All)
        highlight_opts.SetHighlightUnspecifiedStereo(True)
        if label_backbone_atoms:
            highlight_opts.SetAtomPropertyFunctor(BackboneLabel())
    
        disp = oedepict.OE2DMolDisplay(mol, highlight_opts)
        oegrapheme.OEHighlightMonomers(disp, highlight_opts)
    
        if highlight_backbone:
            backbone = oechem.OEAtomBondSet()
            for atom in disp.GetMolecule().GetAtoms():
                if oechem.OEGetPDBAtomIndex(atom) in [
                    oechem.OEPDBAtomName_C,
                    oechem.OEPDBAtomName_O,
                    oechem.OEPDBAtomName_OXT,
                    oechem.OEPDBAtomName_CA,
                    oechem.OEPDBAtomName_CB,
                    oechem.OEPDBAtomName_CG,
                    oechem.OEPDBAtomName_N,
                ]:
                    backbone.AddAtom(atom)
            for bond in disp.GetMolecule().GetBonds():
                if backbone.HasAtom(bond.GetBgn()) and backbone.HasAtom(bond.GetEnd()):
                    backbone.AddBond(bond)
    
            if backbone.NumBonds() > 0:
                line_width = 2.0
                highlight = oedepict.OEHighlightByColor(oechem.OEDarkSalmon)
                highlight.SetLineWidthScale(line_width)
                oedepict.OEAddHighlighting(disp, highlight, backbone)
    
        oedepict.OERenderMolecule(image, disp)
    
    
    class BackboneLabel(oedepict.OEDisplayAtomPropBase):
        """Functor that assigns backbone label to displayed atoms."""
    
        def __init__(self) -> None:
            """Initialize functor."""
            oedepict.OEDisplayAtomPropBase.__init__(self)
    
        def __call__(self, atom: oechem.OEAtomBase) -> str:
            """Assign label."""
            if oechem.OEGetPDBAtomIndex(atom) not in [
                oechem.OEPDBAtomName_C,
                oechem.OEPDBAtomName_O,
                oechem.OEPDBAtomName_OXT,
                oechem.OEPDBAtomName_CA,
                oechem.OEPDBAtomName_CB,
                oechem.OEPDBAtomName_CG,
                oechem.OEPDBAtomName_N,
            ]:
                return ""
            return atom.GetName()
    
        def CreateCopy(self):  # noqa: ANN201, N802
            """Copy constructor."""
            return BackboneLabel().__disown__()
    
    
    class MonomerSetParameter:  # noqa: PLW1641
        """Utility class to handle both built-in and user defined monomer sets."""
    
        def __init__(self) -> None:  # noqa: D107
            self._monomer_sets = ["Standard", "OpenEye", "JSON-FILENAME"]
    
        def __repr__(self) -> str:  # noqa: D105
            return ",".join(self._monomer_sets)
    
        def __eq__(self, param: object) -> bool:  # noqa: D105
            if not isinstance(param, str):
                return False
            if param in ["Standard", "OpenEye"]:
                return True
    
            console = rich.console.Console()
            monomer_set_filepath = pathlib.Path(param)
            if (
                not monomer_set_filepath.exists()
                or monomer_set_filepath.suffix.lower() != ".json"
            ):
                console.print(f"[red]Invalid monomer set file '{param}' ![/red]")
                return False
            try:
                with monomer_set_filepath.open("r") as json_file:
                    json.load(json_file)
            except json.JSONDecodeError as e:
                console.print(f"[red]Invalid monomer set file '{param}' ![/red]")
                console.print(f"[red]Error decoding JSON: {e} ![/red]")
                return False
            return True
    
    
    def _add_monomer_collection(arg_group: argparse._ArgumentGroup) -> None:
        arg_group.add_argument(
            "-m",
            "--monomers",
            type=str,
            default="Standard",
            choices=[MonomerSetParameter()],
            help="built-in monomer-set type or json file of monomers",
        )
        arg_group.add_argument(
            "--code-set",
            type=str,
            metavar="CODE-SET",
            required=False,
            default=None,
            help="code-set, if not specified primary code-set is used",
        )
    
    
    def _get_monomer_collection(args: argparse.Namespace) -> oechem.OEMonomerSet:
        monomers = oechem.OEMonomerSet()
        match args.monomers:
            case "Standard":
                oechem.OELoadStandardMonomerSet(monomers)
            case "OpenEye":
                oechem.OELoadOpenEyeMonomerSet(monomers)
            case _:
                oechem.OEReadMonomerSet(monomers, args.monomers)
        return monomers
    
    
    def _get_molecule(
        args: argparse.Namespace,
        monomers: oechem.OEMonomerSet,
        console: rich.console.Console,
    ) -> oechem.OEMolBase | None:
        mol = oechem.OEGraphMol()
        if args.smiles:
            if not oechem.OEParseSmiles(mol, args.smiles):
                console.print(f"[red]Failed to parse SMILES: `{args.smiles}`[/red]")
                return None
        elif args.helm:
            result = oechem.OEHelmParsingResult()
            if not oechem.OEHelmToMol(mol, args.helm, monomers, result):
                console.print(f"[red]Failed to parse HELM: `{args.helm}`[/red]")
                console.print(f"[red]Warning: {result.GetWarning()}[/red]")
                console.print(args.helm, markup=False, highlight=False)
                console.print("[red]" + "-" * result.GetErrorPosition() + "^[/red]")
                return None
        elif args.mol:
            ifs = oechem.oemolistream(args.mol)
            if not oechem.OEReadMolecule(ifs, mol):
                console.print(f"[red]Failed to read molecule from '{args.mol}'[/red]")
                return None
        return mol if mol.IsValid() else None
    
    
    class OEAnalogColor(oegrapheme.OEMonomerColorBase):
        """Functor that assigns color to monomer based on its analog."""
    
        def __init__(self, code_set: str) -> None:
            """Initialize functor."""
            oegrapheme.OEMonomerColorBase.__init__(self)
            self._code_set = code_set
            self._colors_by_code: dict[str, oechem.OEColor] = {}
    
        def __call__(self, monomer: oechem.OEMonomerData) -> oechem.OEColor:
            """Assign color to monomer."""
            code: str = monomer.GetCode(self._code_set)
            if code in self._colors_by_code:
                return self._colors_by_code[code]
            color_hex = _get_monomer_analog_color(monomer)
            color = oechem.OEColor(color_hex)
            self._colors_by_code[code] = color
            return color
    
        def CreateCopy(self) -> oegrapheme.OEMonomerColorBase:  # noqa: N802
            """Copy constructor."""
            copy = OEAnalogColor(self._code_set)
            return copy.__disown__()
    
    
    def _get_monomer_analog_color(monomer: oechem.OEMonomerData) -> str:  # noqa: PLR0911
        if monomer.GetPolymerType() != oechem.OEPolymerType_Peptide:
            return "#000000"
        analog = oechem.OEGetStandardAnalog(monomer.GetCanonicalSmiles())
        if analog == oechem.OEResidueIndex_UNK:
            return "#AAAAAA"
        match analog:
            case oechem.OEResidueIndex_CYS | oechem.OEResidueIndex_MET:
                return "#e4e488"
            case (
                oechem.OEResidueIndex_ALA
                | oechem.OEResidueIndex_GLY
                | oechem.OEResidueIndex_ILE
                | oechem.OEResidueIndex_LEU
                | oechem.OEResidueIndex_PRO
                | oechem.OEResidueIndex_VAL
            ):
                return "#c09071"
            case (
                oechem.OEResidueIndex_PHE
                | oechem.OEResidueIndex_TRP
                | oechem.OEResidueIndex_TYR
            ):
                return "#5faf5f"
            case oechem.OEResidueIndex_ASP | oechem.OEResidueIndex_GLU:
                return "#e0ac70"
            case (
                oechem.OEResidueIndex_ARG
                | oechem.OEResidueIndex_HIS
                | oechem.OEResidueIndex_LYS
            ):
                return "#85b4e6"
            case oechem.OEResidueIndex_SER | oechem.OEResidueIndex_THR:
                return "#ff8787"
            case oechem.OEResidueIndex_ASN | oechem.OEResidueIndex_GLN:
                return "#5493EA"
        return "#AAAAAA"
    
    
    def _check_image_file(args: argparse.Namespace) -> None:
        # script will terminate if there is some issues
        if not args.image:
            # image will be displayed on the screen
            return
        ext = pathlib.Path(args.image).suffix[1:].upper()
        if not oedepict.OEIsRegisteredImageFile(ext):
            oechem.OEThrow.Fatal("Unknown image output type!")
    
        ofs = oechem.oeofstream()
        if not ofs.open(args.image):
            oechem.OEThrow.Fatal("Cannot open output image file!")
    
    
    setattr(main, "__SCRIPT_NAME__", __SCRIPT_NAME__)
    setattr(main, "__SCRIPT_DESC__", __SCRIPT_DESC__)
    setattr(main, "__SCRIPT_TOOLKITS__", __SCRIPT_TOOLKITS__)
    setattr(main, "__SCRIPT_KEYWORDS__", __SCRIPT_KEYWORDS__)
    setattr(main, "__SCRIPT_CATEGORIES__", __SCRIPT_CATEGORIES__)
    
    if __name__ == "__main__":
        sys.exit(main())
    
  • Solution - focused code snippets with detailed explanations of how the problem is solved

  • Usage - examples demonstrating how to run and use the provided Python script

  • Discussion - additional context and alternative code snippets for related problems

  • See Also - links and references to other OpenEye toolkit manuals