Depicting Atom Properties

Problem

You want to depict arbitrary atom properties calculated by another application or script. See examples in Table 1..

Table 1. Example of depicting partial charges with various styles

atom glyph

property map

molecule surface

../_images/atomprop2img-01.svg ../_images/atomprop2img-02.svg ../_images/atomprop2img-03.svg

Ingredients

Difficulty Level

🌶️ 🌶️

Download

Download code

atomprop2img.py and supplementary scripts: addxlogp.py addpartialcharge.py

See also the Usage subsection.

Source Code

atomprop2img
#!/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.

"""Visualizes the atom properties store in OEB file."""

import argparse
import enum
import io
import os
import pathlib
import re
import sys

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__ = "Visualizes the atom properties (supported image formats: svg, png)."
__SCRIPT_TOOLKITS__ = ["oechem", "oedepict", "oegrapheme"]
__SCRIPT_CATEGORIES__ = ["depiction"]


def parse_options() -> argparse.Namespace:
    """Set up command line options."""
    parser = argparse.ArgumentParser(
        add_help=True,
        formatter_class=RichHelpFormatter,
        description="[yellow]" + __SCRIPT_DESC__ + "[/yellow]",
    )
    parser.add_argument("--help-image", action=HelpPreviewAction)

    # input options
    input_group = parser.add_argument_group("Input ligand-protein complex")
    input_group.add_argument(
        "--mol",
        type=str,
        required=True,
        metavar="OEB-FILE",
        help="input OEB file with atom properties to depict",
    )
    input_group.add_argument(
        "--tag-name",
        type=str,
        required=True,
        metavar="STR",
        help="generic data tag for atom property",
    )
    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=600,
        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(
        "--depiction-style",
        "--style",
        type=DepictionStyle,
        default=DepictionStyle.AtomGlyph,
        choices=list(DepictionStyle),
    )
    depiction_group.add_argument(
        "--negative-color",
        "--n-color",
        type=str,
        default="red",
        choices=[ColorParameter()],
        help="color for negative values (default: %(default)s)",
    )
    depiction_group.add_argument(
        "--positive-color",
        "--p-color",
        type=str,
        default="blue",
        choices=[ColorParameter()],
        help="color for positive values (default: %(default)s)",
    )
    return parser.parse_args()


def main() -> int:
    """Depict atom property."""
    args = parse_options()

    _check_image_file(args)
    mol = _get_molecule(args)

    # check atom properties

    tag: int = oechem.OEGetTag(args.tag_name)
    if not any(atom.HasData(tag) for atom in mol.GetAtoms()):
        oechem.OEThrow.Error(
            f"Cannot find tag {args.tag_name} on atoms of input molecule!"
        )

    # prepare depiction

    clear_coords, suppress_hydrogens = True, True
    oedepict.OEPrepareDepiction(mol, clear_coords, suppress_hydrogens)

    # create image / setup depiction options

    image = oedepict.OEImage(args.width, args.height)
    opts = oedepict.OE2DMolDisplayOptions(
        args.width, args.height, oedepict.OEScale_AutoScale
    )
    opts.SetAtomColorStyle(oedepict.OEAtomColorStyle_WhiteMonochrome)

    negative_color = _get_color(args, "negative_color")
    positive_color = _get_color(args, "positive_color")

    depict_atom_property(
        image,
        mol,
        opts,
        args.tag_name,
        (negative_color, positive_color),
        args.depiction_style,
    )

    oedepict.OEDrawCurvedBorder(image, oedepict.OELightGreyPen, 10.0)

    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_atom_property(
    image: oedepict.OEImageBase,
    mol: oechem.OEMolBase,
    opts: oedepict.OE2DMolDisplayOptions,
    tag_name: str,
    colors: tuple[oechem.OEColor, oechem.OEColor],
    style: str,
) -> None:
    """Depicts atom property using various depiction styles."""
    main_width, main_height = image.GetWidth(), image.GetHeight() * 0.9
    color_width, color_height = image.GetWidth(), image.GetHeight() * 0.1

    main_frame = oedepict.OEImageFrame(
        image, main_width, main_height, oedepict.OE2DPoint(0.0, 0.0)
    )
    color_frame = oedepict.OEImageFrame(
        image, color_width, color_height, oedepict.OE2DPoint(0.0, main_height)
    )

    opts.SetDimensions(main_width, main_height, oedepict.OEScale_AutoScale)
    opts.SetScale(oegrapheme.OEGetMoleculeSurfaceScale(mol, opts))

    int_tag = oechem.OEGetTag(tag_name)
    color_gradient = get_color_gradient(mol, int_tag, colors)

    disp = oedepict.OE2DMolDisplay(mol, opts)

    match style:
        case DepictionStyle.AtomGlyph:
            depict_atom_property_atom_glyph(disp, tag_name, color_gradient)
        case DepictionStyle.PropertyMap:
            depict_atom_property_property_map(disp, tag_name, colors)
        case DepictionStyle.MoleculeSurface:
            depict_atom_property_molecule_surface(disp, tag_name, color_gradient)

    oedepict.OERenderMolecule(main_frame, disp)
    oegrapheme.OEDrawColorGradient(color_frame, color_gradient)

    font = oedepict.OEFont(
        oedepict.OEFontFamily_Default,
        oedepict.OEFontStyle_Default,
        14,
        oedepict.OEAlignment_Left,
        oechem.OEBlack,
    )
    color_frame.DrawText(oedepict.OE2DPoint(10.0, -10.0), tag_name, font)


class ColorParameter:  # noqa: PLW1641
    """Utility class to handle color parameter."""

    def __init__(self) -> None:  # noqa: D107
        self._build_on_colors = ["red", "blue", "green", "'#rrggbb'"]

    def __repr__(self) -> str:  # noqa: D105
        return ",".join(self._build_on_colors)

    def __eq__(self, param: object) -> bool:  # noqa: D105
        if not isinstance(param, str):
            return False
        if param in ["red", "blue", "green"]:
            return True
        return re.match("^#[0-9a-fA-F]{6}", param) is not None


class DepictionStyle(enum.Enum):
    """Property depiction sty;e."""

    AtomGlyph = "atom-glyph"
    PropertyMap = "property-map"
    MoleculeSurface = "molecule-surface"

    def __str__(self) -> str:
        """Convert to string representation."""
        return self.value


def get_color_gradient(
    mol: oechem.OEMolBase, tag: int, colors: tuple[oechem.OEColor, oechem.OEColor]
) -> oechem.OELinearColorGradient:
    """Generate color gradient."""
    min_value = min(
        (atom.GetData(tag) for atom in mol.GetAtoms() if atom.HasData(tag)),
        default=float("inf"),
    )
    max_value = max(
        (atom.GetData(tag) for atom in mol.GetAtoms() if atom.HasData(tag)),
        default=float("-inf"),
    )

    color_gradient = oechem.OELinearColorGradient(
        oechem.OEColorStop(0.0, oechem.OEWhite)
    )
    if min_value < 0.0:
        color_gradient.AddStop(oechem.OEColorStop(min_value, colors[0]))
    if max_value > 0.0:
        color_gradient.AddStop(oechem.OEColorStop(max_value, colors[1]))

    return color_gradient


def depict_atom_property_property_map(
    disp: oedepict.OE2DMolDisplay,
    tag_name: str,
    colors: tuple[oechem.OEColor, oechem.OEColor],
) -> None:
    """Depicts atom property using property map style."""
    opts = disp.GetOptions()
    prop_map = oegrapheme.OE2DPropMap(opts.GetBackgroundColor())
    prop_map.SetLegendLocation(oegrapheme.OELegendLocation_Hidden)
    prop_map.SetNegativeColor(colors[0])
    prop_map.SetPositiveColor(colors[1])
    prop_map.Render(disp, tag_name)


def depict_atom_property_atom_glyph(
    disp: oedepict.OE2DMolDisplay,
    tag_name: str,
    color_gradient: oechem.OELinearColorGradient,
) -> None:
    """Depicts atom property using atom glyph style."""
    tag = oechem.OEGetTag(tag_name)
    mol = disp.GetMolecule()

    for atom in mol.GetAtoms():
        if atom.HasData(tag):
            value = atom.GetDoubleData(tag)
            color = color_gradient.GetColorAt(value)
            pen = oedepict.OEPen(color, color, oedepict.OEFill_Off, 3.0)
            glyph = oegrapheme.OEAtomGlyphCircle(
                pen, oegrapheme.OECircleStyle_Default, 1.2
            )
            oegrapheme.OEAddGlyph(disp, glyph, oechem.OEHasAtomIdx(atom.GetIdx()))


def depict_atom_property_molecule_surface(
    disp: oedepict.OE2DMolDisplay,
    tag_name: str,
    color_gradient: oechem.OELinearColorGradient,
) -> None:
    """Depicts atom property using molecule surface style."""
    tag = oechem.OEGetTag(tag_name)
    mol = disp.GetMolecule()

    for atom in mol.GetAtoms():
        if atom.HasData(tag):
            value = atom.GetDoubleData(tag)
            color = color_gradient.GetColorAt(value)
            pen = oedepict.OEPen(color, color, oedepict.OEFill_Off, 4.0)
            oegrapheme.OESetSurfaceArcFxn(mol, atom, oegrapheme.OEDefaultArcFxn(pen))

    oegrapheme.OEDraw2DSurface(disp)


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!")


def _get_molecule(args: argparse.Namespace) -> oechem.OEMolBase:
    ifs = oechem.oemolistream()
    if not ifs.open(args.mol):
        oechem.OEThrow.Fatal(f"Cannot open {args.mol} input file!")

    if ifs.GetFormat() != oechem.OEFormat_OEB:
        oechem.OEThrow.Fatal("Expected OEB input file!")

    mol = oechem.OEGraphMol()
    if not oechem.OEReadMolecule(ifs, mol):
        oechem.OEThrow.Fatal(f"Cannot read molecule from {args.mol} input file!")

    return mol


def _get_color(args: argparse.Namespace, param_name: str) -> oechem.OEColor:
    color_name = getattr(args, param_name, "xffffff")
    color_dict = {"red": oechem.OERed, "blue": oechem.OEBlue, "green": oechem.OEGreen}
    if color_name in color_dict:
        return color_dict[color_name]

    # color_name has format
    color = oechem.OEColor()
    color.SetText(color_name)
    return color


setattr(main, "__SCRIPT_NAME__", __SCRIPT_NAME__)
setattr(main, "__SCRIPT_DESC__", __SCRIPT_DESC__)
setattr(main, "__SCRIPT_TOOLKITS__", __SCRIPT_TOOLKITS__)
setattr(main, "__SCRIPT_CATEGORIES__", __SCRIPT_CATEGORIES__)

if __name__ == "__main__":
    sys.exit(main())
addxlogp
#!/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.

"""Calculate atom contribution of XLogP of molecules and store them in OEB file."""


import argparse
import os
import pathlib
import sys

from openeye import oechem, oemolprop, oequacpac
from rich_argparse import HelpPreviewAction, RichHelpFormatter

__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Calculates XLogP of molecules."
__SCRIPT_TOOLKITS__ = ["oechem", "oemolprop", "oequacpac"]
__SCRIPT_CATEGORIES__ = ["depiction"]


def parse_options() -> argparse.Namespace:
    """Set up command line options."""
    parser = argparse.ArgumentParser(
        add_help=True,
        formatter_class=RichHelpFormatter,
        description="[yellow]" + __SCRIPT_DESC__ + "[/yellow]",
    )
    parser.add_argument("--help-image", action=HelpPreviewAction)

    io_group = parser.add_argument_group("Input/Output options")
    io_group.add_argument(
        "--in-mol",
        type=str,
        required=True,
        metavar="MOL-FILE",
        help="input molecule file (oeb, sdf)",
    )
    io_group.add_argument(
        "--out-mol",
        type=str,
        required=True,
        metavar="OEB-FILE",
        help="outout molecule file (oeb)",
    )
    io_group.add_argument(
        "--tag-name",
        type=str,
        required=True,
        metavar="STR",
        help="generic data tag for atom property",
    )

    return parser.parse_args()


def main() -> int:
    """Calculate XLogP and output to OEB."""
    args = parse_options()

    # check input/output files

    ifs = oechem.oemolistream()
    if not ifs.open(args.in_mol):
        oechem.OEThrow.Fatal("Cannot open input file!")

    ofs = oechem.oemolostream()
    if not ofs.open(args.out_mol):
        oechem.OEThrow.Fatal("Cannot open output file!")

    if ofs.GetFormat() != oechem.OEFormat_OEB:
        oechem.OEThrow.Fatal("Only works for oeb output file!")

    # read molecules and calculate XLogP

    for mol in ifs.GetOEGraphMols():
        set_xlogp(mol, args.tag_name)
        oechem.OEWriteMolecule(ofs, mol)

    return os.EX_OK


def set_xlogp(mol: oechem.OEMolBase, tag_name: str) -> None:
    """Attache the XLogP atom contribution to each atom with the given tag."""
    oequacpac.OERemoveFormalCharge(mol)

    tag = oechem.OEGetTag(tag_name)
    atom_values = oechem.OEFloatArray(mol.GetMaxAtomIdx())
    oemolprop.OEGetXLogP(mol, atom_values)

    for atom in mol.GetAtoms():
        value = atom_values[atom.GetIdx()]
        atom.SetData(tag, value)


setattr(main, "__SCRIPT_NAME__", __SCRIPT_NAME__)
setattr(main, "__SCRIPT_DESC__", __SCRIPT_DESC__)
setattr(main, "__SCRIPT_TOOLKITS__", __SCRIPT_TOOLKITS__)
setattr(main, "__SCRIPT_CATEGORIES__", __SCRIPT_CATEGORIES__)

if __name__ == "__main__":
    sys.exit(main())
addpartialcharge
#!/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.

"""Calculate atom partial charges of molecules and store them in OEB file."""

import argparse
import os
import pathlib
import sys

from openeye import oechem
from rich_argparse import HelpPreviewAction, RichHelpFormatter

__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Calculates partial charge of molecules."
__SCRIPT_TOOLKITS__ = ["oechem"]
__SCRIPT_CATEGORIES__ = ["cheminformatics"]


def parse_options() -> argparse.Namespace:
    """Set up command line options."""
    parser = argparse.ArgumentParser(
        add_help=True,
        formatter_class=RichHelpFormatter,
        description="[yellow]" + __SCRIPT_DESC__ + "[/yellow]",
    )
    parser.add_argument("--help-image", action=HelpPreviewAction)

    io_group = parser.add_argument_group("Input/Output options")
    io_group.add_argument(
        "--in-mol",
        type=str,
        required=True,
        metavar="MOL-FILE",
        help="input molecule file (oeb, sdf)",
    )
    io_group.add_argument(
        "--out-mol",
        type=str,
        required=True,
        metavar="OEB-FILE",
        help="outout molecule file (oeb)",
    )
    io_group.add_argument(
        "--tag-name",
        type=str,
        required=True,
        metavar="STR",
        help="generic data tag for atom property",
    )

    return parser.parse_args()


def main() -> int:
    """Calculate partial charges and output to OEB."""
    args = parse_options()

    # check input/output files

    ifs = oechem.oemolistream()
    if not ifs.open(args.in_mol):
        oechem.OEThrow.Fatal("Cannot open input file!")

    ofs = oechem.oemolostream()
    if not ofs.open(args.out_mol):
        oechem.OEThrow.Fatal("Cannot open output file!")

    if ofs.GetFormat() != oechem.OEFormat_OEB:
        oechem.OEThrow.Fatal("Only works for oeb output file!")

    # read molecules and calculate partial charge

    for mol in ifs.GetOEGraphMols():
        set_partial_charge(mol, args.tag_name)
        oechem.OEWriteMolecule(ofs, mol)

    return os.EX_OK


def set_partial_charge(mol: oechem.OEMolBase, tag_name: str) -> None:
    """Attache the partial change to each atom with the given tag."""
    oechem.OEMMFFAtomTypes(mol)
    oechem.OEMMFF94PartialCharges(mol)

    tag = oechem.OEGetTag(tag_name)
    for atom in mol.GetAtoms():
        atom.SetData(tag, atom.GetPartialCharge())


setattr(main, "__SCRIPT_NAME__", __SCRIPT_NAME__)
setattr(main, "__SCRIPT_DESC__", __SCRIPT_DESC__)
setattr(main, "__SCRIPT_TOOLKITS__", __SCRIPT_TOOLKITS__)
setattr(main, "__SCRIPT_CATEGORIES__", __SCRIPT_CATEGORIES__)

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

Solution

The OEChem TK provides a framework to associate arbitrary data with objects such as molecules, atoms and bonds. Generic data can be attached to an object by association either with an integer or string, called a tag identifier. When a molecule is written into an OEBinary (oeb) file, the generic data attached to the molecule and its atoms and bonds is written to the binary file as well. This provides a convenient way to transfer data along with the molecules from one application to another.

The depict_atom_property function visualizes properties attached to an atom as generic data. First an image is divided into two frames, one for rendering a molecule with the atom properties and one for depicting the corresponding color gradient that shows the range of the property. The scaling of the depiction options is then adjusted by utilizing the OEGetMoleculeSurfaceScale function. A OELinearColorGradient object is then initialized by calling the get_color_gradient function. This color gradient is used to associate the atom properties with colors that will represent them in the image. After constructing the molecule display, the atom properties are depicted on the molecular graph based on the user defined depiction style. The molecule display and the color gradient are then rendered into the image frames along with drawing the label of the atom property into the image.

def depict_atom_property(
    image: oedepict.OEImageBase,
    mol: oechem.OEMolBase,
    opts: oedepict.OE2DMolDisplayOptions,
    tag_name: str,
    colors: tuple[oechem.OEColor, oechem.OEColor],
    style: str,
) -> None:
    """Depicts atom property using various depiction styles."""
    main_width, main_height = image.GetWidth(), image.GetHeight() * 0.9
    color_width, color_height = image.GetWidth(), image.GetHeight() * 0.1

    main_frame = oedepict.OEImageFrame(
        image, main_width, main_height, oedepict.OE2DPoint(0.0, 0.0)
    )
    color_frame = oedepict.OEImageFrame(
        image, color_width, color_height, oedepict.OE2DPoint(0.0, main_height)
    )

    opts.SetDimensions(main_width, main_height, oedepict.OEScale_AutoScale)
    opts.SetScale(oegrapheme.OEGetMoleculeSurfaceScale(mol, opts))

    int_tag = oechem.OEGetTag(tag_name)
    color_gradient = get_color_gradient(mol, int_tag, colors)

    disp = oedepict.OE2DMolDisplay(mol, opts)

    match style:
        case DepictionStyle.AtomGlyph:
            depict_atom_property_atom_glyph(disp, tag_name, color_gradient)
        case DepictionStyle.PropertyMap:
            depict_atom_property_property_map(disp, tag_name, colors)
        case DepictionStyle.MoleculeSurface:
            depict_atom_property_molecule_surface(disp, tag_name, color_gradient)

    oedepict.OERenderMolecule(main_frame, disp)
    oegrapheme.OEDrawColorGradient(color_frame, color_gradient)

    font = oedepict.OEFont(
        oedepict.OEFontFamily_Default,
        oedepict.OEFontStyle_Default,
        14,
        oedepict.OEAlignment_Left,
        oechem.OEBlack,
    )
    color_frame.DrawText(oedepict.OE2DPoint(10.0, -10.0), tag_name, font)

The get_color_gradient function creates a OELinearColorGradient object based on the minimum and maximum atom property values.

def get_color_gradient(
    mol: oechem.OEMolBase, tag: int, colors: tuple[oechem.OEColor, oechem.OEColor]
) -> oechem.OELinearColorGradient:
    """Generate color gradient."""
    min_value = min(
        (atom.GetData(tag) for atom in mol.GetAtoms() if atom.HasData(tag)),
        default=float("inf"),
    )
    max_value = max(
        (atom.GetData(tag) for atom in mol.GetAtoms() if atom.HasData(tag)),
        default=float("-inf"),
    )

    color_gradient = oechem.OELinearColorGradient(
        oechem.OEColorStop(0.0, oechem.OEWhite)
    )
    if min_value < 0.0:
        color_gradient.AddStop(oechem.OEColorStop(min_value, colors[0]))
    if max_value > 0.0:
        color_gradient.AddStop(oechem.OEColorStop(max_value, colors[1]))

    return color_gradient

The depict_atom_property_property_map function shows how to project atom properties into a property map.

 1def depict_atom_property_property_map(
 2    disp: oedepict.OE2DMolDisplay,
 3    tag_name: str,
 4    colors: tuple[oechem.OEColor, oechem.OEColor],
 5) -> None:
 6    """Depicts atom property using property map style."""
 7    opts = disp.GetOptions()
 8    prop_map = oegrapheme.OE2DPropMap(opts.GetBackgroundColor())
 9    prop_map.SetLegendLocation(oegrapheme.OELegendLocation_Hidden)
10    prop_map.SetNegativeColor(colors[0])
11    prop_map.SetPositiveColor(colors[1])
12    prop_map.Render(disp, tag_name)

See also

The depict_atom_property_atom_glyph function shows how to visualize atom properties using atom glyphs.

def depict_atom_property_atom_glyph(
    disp: oedepict.OE2DMolDisplay,
    tag_name: str,
    color_gradient: oechem.OELinearColorGradient,
) -> None:
    """Depicts atom property using atom glyph style."""
    tag = oechem.OEGetTag(tag_name)
    mol = disp.GetMolecule()

    for atom in mol.GetAtoms():
        if atom.HasData(tag):
            value = atom.GetDoubleData(tag)
            color = color_gradient.GetColorAt(value)
            pen = oedepict.OEPen(color, color, oedepict.OEFill_Off, 3.0)
            glyph = oegrapheme.OEAtomGlyphCircle(
                pen, oegrapheme.OECircleStyle_Default, 1.2
            )
            oegrapheme.OEAddGlyph(disp, glyph, oechem.OEHasAtomIdx(atom.GetIdx()))

See also

The depict_atom_property_molecule_surface function shows how to project atom properties into the molecule surface.

def depict_atom_property_molecule_surface(
    disp: oedepict.OE2DMolDisplay,
    tag_name: str,
    color_gradient: oechem.OELinearColorGradient,
) -> None:
    """Depicts atom property using molecule surface style."""
    tag = oechem.OEGetTag(tag_name)
    mol = disp.GetMolecule()

    for atom in mol.GetAtoms():
        if atom.HasData(tag):
            value = atom.GetDoubleData(tag)
            color = color_gradient.GetColorAt(value)
            pen = oedepict.OEPen(color, color, oedepict.OEFill_Off, 4.0)
            oegrapheme.OESetSurfaceArcFxn(mol, atom, oegrapheme.OEDefaultArcFxn(pen))

    oegrapheme.OEDraw2DSurface(disp)

See also

Discussion

Atom Partial Charge

The set_partial_charge function of the addpartialcharge.py script calculates atom partial charge and attaches the calculated values to each atoms as a generic data.

def set_partial_charge(mol: oechem.OEMolBase, tag_name: str) -> None:
    """Attache the partial change to each atom with the given tag."""
    oechem.OEMMFFAtomTypes(mol)
    oechem.OEMMFF94PartialCharges(mol)

    tag = oechem.OEGetTag(tag_name)
    for atom in mol.GetAtoms():
        atom.SetData(tag, atom.GetPartialCharge())

After running the addpartialcharge.py script, the atom partial charges can be visualized by calling the atomprop2img.py script with the same tag identifier.

Atom XLogP Contribution

The addxlogp function of the addxlogp.py script calculates the xLogP atom contributions and attaches the calculated values to each atoms as a generic data.

def set_xlogp(mol: oechem.OEMolBase, tag_name: str) -> None:
    """Attache the XLogP atom contribution to each atom with the given tag."""
    oequacpac.OERemoveFormalCharge(mol)

    tag = oechem.OEGetTag(tag_name)
    atom_values = oechem.OEFloatArray(mol.GetMaxAtomIdx())
    oemolprop.OEGetXLogP(mol, atom_values)

    for atom in mol.GetAtoms():
        value = atom_values[atom.GetIdx()]
        atom.SetData(tag, value)

After running the addxlogp.py script, the atom contributions of XLogP can be visualized by calling the atomprop2img.py script with the same tag identifier.

Usage

See Download section to download the scripts.

> atomprop2img --help
../_images/atomprop2img-help.svg

Usage (partial charge)

Running the above commands will generate the images shown in Table 1.

> atomprop2img --tag-name partial-charge --mol molecule-charge.oeb --image image.svg
> atomprop2img --tag-name partial-charge --depiction-style property-map --mol molecule-charge.oeb --image image.svg
> atomprop2img --tag-name partial-charge --depiction-style molecule-surface --mol molecule-charge.oeb --image image.svg

Usage (XLogP)

Usage

atomprop2img.py and addxlogp.py

Running the above commands will generate the images shown in Table 2.

> atomprop2img --tag-name XLogP --negative-color '#008000' --positive-color '#5E005E' --mol molecule-xlogp.oeb --image image.svg
> atomprop2img --tag-name XLogP --depiction-style property-map --negative-color '#008000' --positive-color '#5E005E' --mol molecule-xlogp.oeb --image image.svg
> atomprop2img --tag-name XLogP --depiction-style molecule-surface --negative-color '#008000' --positive-color '#5E005E' --mol molecule-xlogp.oeb --image image.svg
Table 2. Example of depicting atom contributions of XLogP with various styles

atom glyph

property map

molecule surface

../_images/atomprop2img-04.svg ../_images/atomprop2img-05.svg ../_images/atomprop2img-06.svg

See also in OEChem TK manual

Theory

API

See also in OEDepict TK manual

Theory

API

See also in GraphemeTM TK manual

Theory

API