🆕 Print Monomer Sequence of HELMS to Console
Problem
You want to inspect a helm file and display a monomer sequence of the
PEPTIDE1 chain in the terminal.
See also
[Zhang-2012] publication
Ingredients
|
Difficulty Level
🌶️ 🌶️
Download
Source Code
helms2console
#!/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.
"""Print monomer list of HELMs to console."""
import argparse
import json
import os
import pathlib
import sys
from typing import NewType
import rich.box
import rich.console
import rich.table
import rich.text
from openeye import oechem
from rich_argparse import HelpPreviewAction, RichHelpFormatter
__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Print monomers to console."
__SCRIPT_TOOLKITS__ = ["oechem"]
__SCRIPT_KEYWORDS__ = ["HELM", "peptide", "sequence", "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]",
)
io_group = parser.add_argument_group("Input options")
io_group.add_argument(
"--helm",
"--helm-file",
metavar="HELM-FILE",
type=str,
required=True,
help="input file of HELM string",
)
monomers_group = parser.add_argument_group("Monomer set options")
_add_monomer_collection(monomers_group)
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()
MonomerSequence = NewType("MonomerSequence", list[str])
def main() -> int:
"""Print monomer list of HELMs to console."""
args = parse_options()
console = rich.console.Console(record=args.save_console_svg)
monomers: oechem.OEMonomerSet = _get_monomer_collection(args)
code_set = args.code_set or monomers.GetPrimaryCodeSet()
helms_with_titles: list[tuple[str, str]] = _read_helms_with_title(args.helm)
has_titles = any(title for _, title in helms_with_titles)
console.print(
f"{len(helms_with_titles)} HELMS read from {pathlib.Path(args.helm).name}!"
)
monomer_sequences_with_titles = _get_monomer_sequences(helms_with_titles)
max_sequence_length = max(len(s) for s, _ in monomer_sequences_with_titles)
monomer_color_dict: dict[str, str] = _get_monomer_colors(
monomer_sequences_with_titles, monomers, code_set
)
table = rich.table.Table(
title=f"[bold]Number of sequences: {len(monomer_sequences_with_titles)}[/bold]",
box=rich.box.SIMPLE,
collapse_padding=True,
pad_edge=False,
)
table.add_column("idx", justify="right")
for pos in range(1, max_sequence_length + 1):
table.add_column(f"P{pos:02d}", justify="center")
if has_titles:
table.add_column("Title", justify="left")
for idx, (sequence, title) in enumerate(monomer_sequences_with_titles, start=1):
sequence_repr: list[rich.text.Text] = [
rich.text.Text(str(idx)),
*_get_colored_sequence(sequence, monomer_color_dict),
rich.text.Text(title),
]
table.add_row(*sequence_repr)
console.print(table)
if args.save_console_svg:
console.save_svg(f"{__SCRIPT_NAME__}.svg", title="output")
return os.EX_OK
def _read_helms_with_title(helm_filename: str) -> list[tuple[str, str]]:
# script will terminate if reading HELM string from file fails
helm_filepath = pathlib.Path(helm_filename)
if not helm_filepath.exists():
oechem.OEThrow.Fatal(f"{helm_filename} file does not exist!")
if helm_filepath.suffix.lower() != ".helm":
oechem.OEThrow.Fatal("Invalid file extension expected .helm!")
helms: list[tuple[str, str]] = []
try:
with helm_filepath.open() as helm_file:
for line in helm_file:
helm, _, title = line.rstrip().partition(" ")
helms.append((helm, title))
except OSError:
oechem.OEThrow.Fatal(f"Can not open {helm_filename} input file!")
if len(helms) == 0:
oechem.OEThrow.Fatal(f"No helm string read from {helm_filename}!")
return []
return helms
def _get_monomer_colors(
monomer_sequences_with_titles: list[tuple[MonomerSequence, str]],
monomers: oechem.OEMonomerSet,
code_set: str,
) -> dict[str, str]:
unique_monomer_codes: set[str] = {
c for s, _ in monomer_sequences_with_titles for c in s
}
monomer_colors: dict[str, str] = {}
for code in unique_monomer_codes:
if (monomer := monomers.GetMonomer(code_set, code)) is not None:
color = _get_monomer_analog_color(monomer)
monomer_colors[code] = color
return monomer_colors
def _get_monomer_analog_color(monomer: oechem.OEMonomer) -> 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 _get_monomer_sequences(
helms_with_title: list[tuple[str, str]],
) -> list[tuple[MonomerSequence, str]]:
monomer_sequences: list[tuple[MonomerSequence, str]] = []
chain_name = "PEPTIDE1"
for helm, title in helms_with_title:
monomer_codes = oechem.OEGetHelmMonomerCodes(helm, chain_name)
if len(monomer_codes) > 0:
monomer_sequences.append((monomer_codes, title))
return monomer_sequences
def _get_colored_sequence(
monomer_sequence: MonomerSequence, monomer_color_dict: dict[str, str]
) -> list[rich.text.Text]:
colored_sequence: list[rich.text.Text] = []
for code in monomer_sequence:
bg_color = monomer_color_dict.get(code, "white")
style = rich.text.Style(color="black", bgcolor=bg_color, bold=True)
colored_sequence.append(rich.text.Text(code, style, justify="center"))
return colored_sequence
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
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
The helms2console will not fully validate or parse the input HELM strings;
rather, it uses the OEGetHelmMonomerCodes function to extract the monomers of
the PEPTIDE1 component of each input HELM.
Usage
See Download section to download the script.
> helms2console --help
By default, the helms2console script uses OEChem TK’s
built-in Standard monomer set to try to assign colors to the monomers.
> helms2console --helm test.helm
The output for the test.helm input file:
- --monomers OpenEye
- --monomers JSON-MONOMER-FILE
> helms2console --helm test-custom.helm --monomers custom-monomers.json
The output for the test-custom.helm input file
with test-custom.helm custom monomer set:
See also in OEChem TK manual
OEGetHelmMonomerCodes function
OEMonomer class
OEMonomerSet class
OEReadMonomerSet, OELoadStandardMonomerSet, and OELoadOpenEyeMonomerSet functions