🆕 Convert Monomers to OpenEye JSON Format
Problem
You want to convert your existing HELM monomer dictionary in Pistoia format to the more compact OpenEye json format.
{
"monomerType": "Backbone",
"smiles": "C[C@H](N[H:1])C([OH:2])=O",
"name": "Alanine",
"author": "Pistoia Alliance HELM project",
"molfile": "HELM Core Monomer library\n RDKit 2D\n\n 7 6 0 0 1 0 0 0 0 0999 V2000\n 2.0625 0.7145 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 1.2375 0.7145 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.8250 1.4289 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0\n 1.2375 2.1434 0.0000 R# 0 0 0 0 0 0 0 0 0 0 0 0\n 0.8250 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 1.2375 -0.7145 0.0000 R# 0 0 0 0 0 0 0 0 0 0 0 0\n 0.0000 0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 2 1 1 1\n 2 3 1 0\n 3 4 1 0\n 2 5 1 0\n 5 6 1 0\n 5 7 2 0\nM RGP 2 4 1 6 2\nM END\n",
"naturalAnalog": "A",
"rgroups": [
{
"capGroupSmiles": "[*:1][H]",
"alternateId": "R1-H",
"capGroupName": "H",
"label": "R1"
},
{
"capGroupSmiles": "O[*:2]",
"alternateId": "R2-OH",
"capGroupName": "OH",
"label": "R2"
}
],
"createDate": null,
"id": 0,
"polymerType": "PEPTIDE",
"symbol": "A"
}
{
"smiles": "[H:1]N[C@@H](C)C(=O)[OH:2]",
"name": "Alanine",
"codes": {
"OpenEye": "A"
}
}
Note
In the OpenEye format, the polymer type Peptide and the monomer type Backbone are considered as defaults
and are only included in the JSON output when the monomer has properties different from the default values.
Ingredients
|
Difficulty Level
🌶️ 🌶️
Download
Source Code
monomers2json
#!/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.
"""Convert thirst-party monomer format to OpenEye's json format."""
import argparse
import json
import os
import pathlib
import sys
import rich.console
from openeye import oechem
from rich_argparse import HelpPreviewAction, RichHelpFormatter
__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Convert monomers to OpenEye's json format."
__SCRIPT_TOOLKITS__ = ["oechem"]
__SCRIPT_KEYWORDS__ = ["monomer", "peptide", "peptide-informatics"]
__SCRIPT_CATEGORIES__ = ["peptide-informatics"]
def parse_options() -> argparse.Namespace:
"""Set up command line options."""
parser = argparse.ArgumentParser(
add_help=True,
formatter_class=RichHelpFormatter,
description="[yellow]" + __SCRIPT_DESC__ + "[/yellow]",
)
monomers_group = parser.add_argument_group("Monomer set options")
monomers_group.add_argument(
"--code-set",
type=str,
default="CUSTOM",
help="name of the code set in the generated monomer set (default: %(default)s)",
)
monomers_group.add_argument(
"--monomer-set-version",
metavar="X.Y.Z",
type=str,
default="1.0.0",
help="version of generated monomer set (default: %(default)s)",
)
io_group = parser.add_argument_group("Input/Output options")
io_group.add_argument(
"--in-json",
metavar="JSON-FILE",
type=str,
required=True,
help="input monomer file Pistoia format (json)",
)
io_group.add_argument(
"--out-json",
metavar="JSON-FILE",
type=str,
required=True,
help="output molecule set file OpenEye format (json)",
)
parser.add_argument("--help-image", action=HelpPreviewAction)
parser.add_argument(
"--save-console-svg",
default=False,
action="store_true",
help=f"run command and capture console output in {__SCRIPT_NAME__}.svg file",
)
return parser.parse_args()
def main() -> int:
"""Convert thirst-party monomer format to OpenEye's json format."""
args = parse_options()
console = rich.console.Console(record=args.save_console_svg, highlight=False)
_check_file(
pathlib.Path(args.out_json), check_exists=False, expected_extension=".json"
)
code_set = args.code_set
monomers = oechem.OEMonomerSet()
monomers.SetVersion(args.monomer_set_version)
monomers.AddCodeSet(code_set)
console.print(f"[blue]Adding monomers to '{code_set}' code set![/blue]")
if pathlib.Path(args.out_json).suffix.lower() != ".json":
oechem.OEThrow.Fatal(
f"Output monomer set file {args.out_json} requires json extension!"
)
monomer_json_data: None | list[dict] = _load_monomer_json_data(
pathlib.Path(args.in_json), console
)
if monomer_json_data is None or len(monomer_json_data) == 0:
# warning/error was already thrown
return os.EX_DATAERR
console.print(
f"{len(monomer_json_data)} monomer definitions are loaded for processing!"
)
num_RNAs = [ # noqa: N806
m.get(POLYMER_TYPE_TAG, "") for m in monomer_json_data
].count("RNA")
console.print(f"{num_RNAs} RNA monomers are ignored!")
for idx, m_json in enumerate(monomer_json_data):
if (
monomer_data := convert_to_monomer_data(idx, m_json, code_set, console)
) is not None:
code = monomer_data.GetCode(code_set)
can_smiles = monomer_data.GetCanonicalSmiles()
if monomers.HasCodeInCodeSet(code_set, code):
console.print(
f"[red]{idx:3} ❌ monomer code '{code}' already exists in '{code_set}'[/red]!"
)
continue
if monomers.HasMonomer(monomer_data):
console.print(
f"[red]{idx:3} ❌ monomer '{code}' {can_smiles} already exists in monomer set![/red]"
)
continue
if monomers.AddMonomer(monomer_data):
polymer_type = oechem.OEPolymerTypeToString(
monomer_data.GetPolymerType()
)
console.print(
f"{idx:3} ✅ {code:20s} as {polymer_type:8s} {monomer_data.GetSmiles()}"
)
else:
console.print(
f"[red]{idx:3} ❌ adding monomer {can_smiles} to set is failed!s[/red]"
)
oechem.OEWriteMonomerSet(monomers, args.out_json)
if args.save_console_svg:
console.save_svg(f"{__SCRIPT_NAME__}.svg", title="output")
return os.EX_OK
def _check_file(
filepath: pathlib.Path, check_exists: bool, expected_extension: str
) -> None:
if check_exists and not filepath.exists():
oechem.OEThrow.Fatal(f"Can not open file {filepath!s}!")
if filepath.suffix.lower() != expected_extension:
oechem.OEThrow.Fatal(
f"Invalid file extension {filepath.suffix} (expected {expected_extension})!"
)
MONOMER_SMILES_TAG = "smiles"
MONOMER_SYMBOL_TAG = "symbol"
POLYMER_TYPE_TAG = "polymerType" # expected values PEPTIDE, CHEM, RNA
MONOMER_TYPE_TAG = "monomerType"
REQUIRED_TAGS = [
MONOMER_SMILES_TAG,
MONOMER_SYMBOL_TAG,
POLYMER_TYPE_TAG,
MONOMER_TYPE_TAG,
]
MONOMER_NAME_TAG = "name"
OPTIONAL_TAGS = [MONOMER_NAME_TAG]
def convert_to_monomer_data( # noqa: C901, PLR0911, PLR0912
idx: int, monomer_def: dict, code_set: str, console: rich.console.Console
) -> None | oechem.OEMonomerData:
"""Convert PistoiaHELM json format to oechem.OEMonomerData."""
if not isinstance(monomer_def, dict):
console.print(f"[red]{idx:2} Invalid monomer definition format![red]")
return None
for tag_name in REQUIRED_TAGS:
if (monomer_def.get(tag_name, "")) == "":
console.print(f"[red] {idx:2d} missing required {tag_name}[/red]!")
return None
code = monomer_def[MONOMER_SYMBOL_TAG]
polymer_type = monomer_def[POLYMER_TYPE_TAG]
if polymer_type not in ["PEPTIDE", "CHEM"]:
return None
monomer_type = monomer_def[MONOMER_TYPE_TAG]
if monomer_type not in ["Backbone", "Terminal"]:
console.print(
f"[red] {idx:2d} ❌ {code:10s} monomer with '{monomer_type}' monomer type is not supported in OEChem[/red]!"
)
return None
smiles = monomer_def[MONOMER_SMILES_TAG]
monomer_mol = oechem.OEGraphMol()
if not oechem.OESmilesToMol(monomer_mol, smiles):
console.print(f"[red] {idx:2d} problem parsing {smiles} [/red]!")
oechem_polymer_type = oechem.OEGetPolymerType(monomer_mol)
oechem_monomer_type = oechem.OEGetMonomerType(monomer_mol)
if oechem_polymer_type not in [
oechem.OEPolymerType_Peptide,
oechem.OEPolymerType_Chem,
]:
console.print(
f"[red]{idx:3} ❌ {code:10s} {smiles} is rejected due to wrong polymer type![/red]"
)
return None
monomer_data = oechem.OEMonomerData(smiles)
monomer_data.AddCode(code_set, code)
monomer_data.SetPolymerType(oechem_polymer_type)
monomer_data.SetMonomerType(oechem_monomer_type)
if (name := monomer_def[MONOMER_NAME_TAG]) != "":
monomer_data.SetName(name)
result = oechem.OEMonomerValidationResult()
if not oechem.OEIsValidMonomerData(monomer_data, result):
if result.GetReturnCode() in [
oechem.OEMonomerValidationReturnCode_UnspecifiedAtomStereo,
oechem.OEMonomerValidationReturnCode_UnspecifiedBondStereo,
]:
return None
console.print(
f"[red]{idx:3} ❌ {code:10s} {smiles} is rejected due to {result.GetWarning()}[/red]"
)
return None
oechem_str = oechem.OEPolymerTypeToString(oechem_polymer_type)
if (
oechem_polymer_type == oechem.OEPolymerType_Peptide
and polymer_type != "PEPTIDE"
):
console.print(
f"[#ff8787]{idx:3} {code:10s} is supported as {oechem_str} rather than {polymer_type} {smiles} ![/#ff8787]"
)
if oechem_polymer_type == oechem.OEPolymerType_Chem and polymer_type != "CHEM":
console.print(
f"[#ff8787]{idx:3} {code:10s} is supported as {oechem_str} rather than {polymer_type} {smiles}![/#ff8787]"
)
oechem_str = oechem.OEMonomerTypeToString(oechem_monomer_type)
if (
oechem_monomer_type == oechem.OEMonomerType_Backbone
and monomer_type != "Backbone"
):
console.print(
f"[#ff8787]{idx:3} {code:10s} is supported as {oechem_str} rather than {monomer_type} {smiles} ![/#ff8787]"
)
if (
oechem_monomer_type == oechem.OEMonomerType_Terminal
and monomer_type != "Terminal"
):
console.print(
f"[#ff8787]{idx:3} {code:10s} is supported as {oechem_str} rather than {monomer_type} {smiles}![/#ff8787]"
)
return monomer_data
def _load_monomer_json_data(
filepath: pathlib.Path, console: rich.console.Console
) -> None | list:
if not filepath.exists():
console.print(f"[red] Can not open file {filepath!s} file for reading![red]")
return None
if filepath.suffix.lower() != ".json":
oechem.OEThrow.Fatal(f"Invalid file extension {filepath.suffix}!")
return None
try:
with pathlib.Path(filepath).open() as in_json_file:
monomer_json_data = json.load(in_json_file)
except json.JSONDecodeError:
console.print(
f"Failed to decode JSON from the file '{filepath!s}'. Check if the file has valid JSON format."
)
return None
if not isinstance(monomer_json_data, list):
console.print("[red]Invalid monomer json file format![red]")
return None
for m in monomer_json_data:
if not isinstance(m, dict):
console.print("[red]Invalid monomer json file format![red]")
return None
return monomer_json_data
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())
Usage
See Download section to download the script.
> monomers2json --help
The following example shows how to convert in.json
monomers stored in Pistoia format to OpenEye format out.json
> monomers2json --in-json in.json --out-json out.json --code-set Pistoia
The output file out.json
can be loaded with the OEReadMonomerSet function and used in any script in the
🆕 Peptide Informatics section.
See also in OEChem TK manual
API
OEIsValidMonomerData function
OEMonomerData class
OEMonomerSet class
OEMonomerType namespace
OEPolymerType namespace
OEPerceivePeptideMonomerConnections function
OEWriteMonomerSet function