🆕 Convert Peptides File to XLSX (Excel) with Monomer Sequence
Problem
You want to convert peptides into molecule sequences and export them to an xlsx Excel file.
Figure 1. Example of XLSX generated with peptides2xlsx
See also
Helm Generation section
Ingredients
|
Difficulty Level
🌶️ 🌶️
Download
Source Code
peptides2xlsx
#!/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.
"""Convert a peptide file into EXCEL XLSX file."""
import argparse
import json
import os
import pathlib
import sys
from typing import NamedTuple
import rich.console
import xlsxwriter
from openeye import oechem
from rich.progress import BarColumn, Progress, TextColumn
from rich_argparse import HelpPreviewAction, RichHelpFormatter
__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Convert peptide file into EXCEL XLSX file."
__SCRIPT_TOOLKITS__ = ["oechem"]
__SCRIPT_KEYWORDS__ = ["HELM", "peptide", "sequence", "XLSX", "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]",
)
input_group = parser.add_argument_group("Input options")
input_group.add_argument(
"--mol",
metavar="MOL-FILE",
type=str,
required=False,
help="input molecule file of peptides (oeb, sdf, fasta)",
)
# output options
output_group = parser.add_argument_group("Output options")
output_group.add_argument(
"--xlsx",
type=str,
required=True,
metavar="XLSX-FILE",
help="output XLSX file (.xlsx)",
)
monomers_group = parser.add_argument_group("Monomer set options")
_add_monomer_collection(monomers_group)
helm_gen_group = parser.add_argument_group("HELM generation options")
_add_helm_generation_options(helm_gen_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()
class Sequence(NamedTuple):
"""Represents a peptide sequence."""
title: str
helm: str
warning: str
monomer_sequence: list[str]
def main() -> int:
"""Convert peptide file into EXCEL XLSX file."""
args = parse_options()
console = rich.console.Console(record=args.save_console_svg)
monomers = _get_monomer_collection(args)
code_set = args.code_set or monomers.GetPrimaryCodeSet()
xlsx_file = pathlib.Path(args.xlsx)
if xlsx_file.suffix.lower() != ".xlsx":
oechem.OEThrow.Fatal("Invalid file extension expected .xlsx!")
mol_database = oechem.OEMolDatabase()
if not mol_database.Open(args.mol):
console.print(f"[red]Error: Unable to open molecule file '{args.mol}'![/red]")
return os.EX_DATAERR
options = oechem.OEHelmGenerationOptions(code_set)
options.SetAllowUnspecifiedStereo(args.allow_unspecified_stereo)
options.SetAllowUnmatchedFragments(args.allow_unmatched_fragments)
sequences: list[Sequence] = _generate_sequences(
mol_database, monomers, options, console
)
num_warnings = sum(1 for s in sequences if s.warning)
console.print(
f"{len(sequences)} molecules have been read from {pathlib.Path(args.mol).name} with {num_warnings} HELM generation issues!"
)
write_xlsx_file(args.xlsx, sequences, monomers, code_set)
if args.save_console_svg:
console.save_svg(f"{__SCRIPT_NAME__}.svg", title="output")
return os.EX_OK
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 write_xlsx_file(
output_filename: str,
sequences: list[Sequence],
monomers: oechem.OEMonomerSet,
code_set: str,
) -> None:
"""Write peptide sequences to an XLSX file."""
workbook = xlsxwriter.Workbook(output_filename)
worksheet = workbook.add_worksheet()
head_format = _add_head_format(workbook)
monomer_formats: dict[str, xlsxwriter.format] = _add_monomer_formats(
workbook, sequences, monomers, code_set
)
# generate header
row, col = 0, 0
worksheet.set_row(row, None, head_format)
worksheet.set_column(col, col, 30)
worksheet.write(row, col, "Molecule Titles")
max_sequence_length = max(len(s.monomer_sequence) for s in sequences)
for pos in range(1, max_sequence_length + 1):
worksheet.write(row, pos, f"P{pos}")
has_warnings = any(seq.warning for seq in sequences)
if has_warnings:
cell_format = workbook.add_format(
{
"color": "red",
"bold": True,
"align": "left",
"valign": "vcenter",
"size": 18,
}
)
worksheet.write(row, max_sequence_length + 1, "Warnings", cell_format)
worksheet.set_column(max_sequence_length + 1, max_sequence_length + 2, 100)
for seq in sequences:
row += 1
worksheet.write(row, col, seq.title)
for pos, monomer_code in enumerate(seq.monomer_sequence, start=1):
monomer_format = monomer_formats.get(monomer_code)
worksheet.write(row, pos, monomer_code, monomer_format)
if seq.warning:
worksheet.write(row, max_sequence_length + 1, seq.warning)
workbook.close()
def _add_head_format(
workbook: xlsxwriter.Workbook,
) -> xlsxwriter.format:
"""Add header format to the workbook."""
head_format = workbook.add_format(
{"bold": True, "align": "center", "valign": "vcenter", "size": 18}
)
head_format.set_bg_color("#F4F4F4")
head_format.set_border_color("#DDDDDD")
head_format.set_border()
return head_format
def _add_monomer_formats(
workbook: xlsxwriter.Workbook,
sequences: list[Sequence],
monomers: oechem.OEMonomerSet,
code_set: str,
) -> dict[str, xlsxwriter.format]:
"""Add monomer formats to the workbook."""
unique_monomer_codes: set[str] = {c for s in sequences for c in s.monomer_sequence}
monomer_colors: dict[str, xlsxwriter.format] = {}
for code in unique_monomer_codes:
if (monomer := monomers.GetMonomer(code_set, code)) is not None:
color = _get_monomer_analog_color(monomer)
data_format = workbook.add_format(
{"bold": False, "align": "center", "valign": "vcenter", "size": 12}
)
data_format.set_bg_color(color)
data_format.set_shrink()
monomer_colors[code] = data_format
return monomer_colors
def _generate_sequences(
mol_database: oechem.OEMolDatabase,
monomers: oechem.OEMonomerSet,
options: oechem.OEHelmGenerationOptions,
console: rich.console.Console,
) -> list[Sequence]:
sequences: list[Sequence] = []
num_molecules = mol_database.NumMols()
with (
Progress(
TextColumn("{task.description}"),
BarColumn(bar_width=60),
TextColumn("{task.percentage:3.1f}%"),
transient=True,
disable=num_molecules < 10, # noqa: PLR2004
console=console,
) as progress,
):
conversion = progress.add_task("[blue]HELM generation", total=num_molecules)
for idx in range(num_molecules):
mol = oechem.OEGraphMol()
if not mol_database.GetMolecule(mol, idx):
console.print(
f"[red]Error: Unable to get molecule at index {idx}![/red]"
)
continue
result = oechem.OEHelmGenerationResult()
if helm := oechem.OEMolToHelm(mol, monomers, options, result):
monomer_sequence = oechem.OEGetHelmMonomerCodes(helm, "PEPTIDE1")
sequences.append(Sequence(mol.GetTitle(), helm, "", monomer_sequence))
else:
sequences.append(Sequence(mol.GetTitle(), "", result.GetWarning(), []))
progress.update(conversion, advance=1)
return sequences
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 _add_helm_generation_options(arg_group: argparse._ArgumentGroup) -> None:
arg_group.add_argument(
"--allow-unspecified-stereo",
default=False,
action="store_true",
help="allow unspecified stereo in input molecule (default: %(default)s)",
)
arg_group.add_argument(
"--allow-unmatched-fragments",
default=False,
action="store_true",
help="allow unmatched fragments in input molecule -- embedded SMILES in HELM (default: %(default)s)",
)
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.
> peptides2xlsx --help
By default, the peptides2xlsx script uses OEChem TK’s
built-in Standard monomer set for the conversions
(peptides.ism).
> peptides2xlsx --mol peptides.ism --xlsx peptides.xlsx
The script will display the number of successful and failed conversions.
The command generates a peptides.xlsx file
containing the HELM strings for the three peptides whose sequences can be fully described
using only the 20 standard amino acids. Conversion attempts for all other peptides are reported
as failures when using the default monomer set and options.
Monomer Set Options
- --monomers OpenEye
- --code-set CODE-SET
OEChem TK’s built-in OpenEye monomer-set can be used with the --monomers OpenEye
parameter
(peptides.ism).
> peptides2xlsx --mol peptides.ism --xlsx peptides.xlsx --monomers OpenEye
converts (peptides.ism) to (peptides.xlsx)
Since the OpenEye monomer set contains multiple code-sets (OpenEye - default, Standard, PDB, ChEMBL), the
--code-set parameter can be used to generate different versions of the HELM string.
> peptides2xlsx --mol peptides.ism --xlsx peptides.xlsx --monomers OpenEye --code-set ChEMBL
converts (peptides.ism) to (peptides.xlsx)
- --monomers JSON-MONOMER-FILE
The following example shows how to convert a HELM file that uses custom monomers
defined in a json file
(custom-monomers.json)
> peptides2xlsx --mol peptides.ism --xlsx peptides.xlsx --monomers custom-monomers.json
converts (peptides.ism) to (peptides.xlsx)
Helm Generation Options
- --allow-unspecified-stereo
- --allow-unmatched-fragments
In the previous example, three molecules could not be converted into HELM representations due to
either having unspecified stereo or unmatched fragments.
The --allow-unspecified-stereo and --allow-unmatched-fragments options can be used to
relax these constraints and generate HELM representations for these molecules with embedded SMILES
encoding unmatched fragments.
See also Helm Generation Options section of smiles2helm
for more explanation and examples.
> peptides2xlsx --mol peptides.ism --xlsx peptides.xlsx --monomers OpenEye --allow-unspecified-stereo --allow-unmatched-fragments
converts (peptides.ism) to
(peptides.xlsx)
Structure 07 |
Structure 10 |
Structure 11 |
|---|---|---|
unmatched fragment |
unspecified stereo; unmatched fragment |
unmatched fragment |
See also in OEChem TK manual
API
OEMonomerSet class
OEReadMonomerSet, OELoadStandardMonomerSet, and OELoadOpenEyeMonomerSet functions
OEHelmGenerationOptions class
OEHelmGenerationResult class
OEHelmGenerationReturnCode namespace
OEMolToHelm function