import json
import re
import xml.etree.ElementTree as ET
import pyunitwizard as puw
from openpharmacophore import PharmacophoricPoint, Pharmacophore
# MOL2 Files
[docs]def read_mol2(file_name):
""" Loads pharmacophores from a mol2 file.
Parameters
----------
file_name : str
Name of the file containing the pharmacophore.
Returns
-------
pharmacophores : list[Pharmacophore]
A list of pharmacophores.
"""
feature_names = {
"AR": "aromatic ring",
"HYD": "hydrophobicity",
"ACC": "hb acceptor",
"DON": "hb donor",
"CAT": "positive charge",
"ANI": "negative charge",
}
pharmacophores = []
pattern = r"[A-Z]{2,3} "
reading_props = False
with open(file_name, "r") as f:
pharma = Pharmacophore()
for line in f.readlines():
match = re.search(pattern, line)
if "@<TRIPOS>PHARMACOPHORE" in line:
reading_props = False
if len(pharma) > 0:
pharmacophores.append(pharma)
pharma = Pharmacophore()
elif match:
point_line = line.split()
feat_type = feature_names[point_line[1]]
center = [float(coord) for coord in point_line[2: 5]]
center = puw.quantity(center, "angstroms")
radius = puw.quantity(float(point_line[-1]), "angstroms")
point = PharmacophoricPoint(
feat_type=feat_type,
center=center,
radius=radius)
pharma.add(point)
elif "@<TRIPOS>PROPERTIES" in line:
reading_props = True
elif reading_props:
prop_name, value = line.split()
if prop_name == "score":
pharma.score = float(value)
elif prop_name == "ref_mol":
pharma.ref_mol = int(value)
elif prop_name == "ref_struct":
pharma.ref_struct = int(value)
else:
pharma.props[prop_name] = value
# Append the last pharmacophore
if len(pharma) > 0:
pharmacophores.append(pharma)
return [ph for ph in pharmacophores if len(ph) > 0]
# PH4 Files
[docs]def read_ph4(file_name: str):
""" Loads a pharmacophore from a MOE ph4 file.
Parameters
----------
file_name : str
Name of the file containing the pharmacophore.
Returns
-------
points : Pharmacophore
A pharmacophore.
"""
moe_to_oph = {
"Cat": "positive charge",
"Ani": "negative charge",
"Don": "hb donor",
"Acc": "hb acceptor",
"Hyd": "hydrophobicity",
"Aro": "aromatic ring",
}
points = []
with open(file_name, "r") as f:
moe_ph4_string = f.read()
# pieces = re.split("\s", moe_ph4_string)
pieces = moe_ph4_string.split()
pattern = r"Don|Acc|Aro|Cat|Ani|Hyd"
# Index where the excluded volumes spheres coordinates, if any, start
exclusion_inx = None
for i, piece in enumerate(pieces):
if re.match(pattern, piece):
if len(piece) == 7:
# Double features have seven characters i.e. Acc|Aro
feat_name_1 = piece[0: 3]
feat_name_2 = piece[4:]
# Coordinates are always two items after the feature name
x = float(pieces[i + 2])
y = float(pieces[i + 3])
z = float(pieces[i + 4])
radius = float(pieces[i + 5])
point_1 = PharmacophoricPoint(
feat_type=moe_to_oph[feat_name_1],
center=puw.quantity([x, y, z], "angstroms"),
radius=puw.quantity(radius, "angstroms")
)
point_2 = PharmacophoricPoint(
feat_type=moe_to_oph[feat_name_2],
center=puw.quantity([x, y, z], "angstroms"),
radius=puw.quantity(radius, "angstroms")
)
points.append(point_1)
points.append(point_2)
else:
# Regular features and
# features with weird names i.e. Cat$mDon, Acc2
feat_name = piece[0:3]
# Coordinates are always two items after the feature name
x = float(pieces[i + 2])
y = float(pieces[i + 3])
z = float(pieces[i + 4])
radius = float(pieces[i + 5])
point = PharmacophoricPoint(
feat_type=moe_to_oph[feat_name],
center=puw.quantity([x, y, z], "angstroms"),
radius=puw.quantity(radius, "angstroms")
)
points.append(point)
if piece == "#volumesphere":
# Excluded volume spheres are 10 items after #volumesphere
exclusion_inx = i + 10
break
if exclusion_inx:
count = 1
for p in pieces[exclusion_inx:]:
if p == "#volume":
break
if count == 1:
x = float(p)
count += 1
elif count == 2:
y = float(p)
count += 1
elif count == 3:
z = float(p)
count += 1
elif count == 4:
radius = float(p)
excluded_sphere = PharmacophoricPoint(
feat_type="excluded volume",
center=puw.quantity([x, y, z], "angstroms"),
radius=puw.quantity(radius, "angstroms")
)
points.append(excluded_sphere)
count = 1
return Pharmacophore(points)
# JSON Files
def _get_pharmer_element_properties(element, direction=False):
center = puw.quantity([element['x'], element['y'], element['z']], 'angstroms')
radius = puw.quantity(element['radius'], 'angstroms')
if direction:
direction = [element['svector']['x'], element['svector']['y'], element['svector']['z']]
return center, radius, direction
return center, radius
[docs]def read_json(pharmacophore_file, load_mol_sys=False):
""" Loads a pharmacophore from a json file
Parameters
----------
pharmacophore_file : str
name of the file containing the pharmacophore
load_mol_sys : bool
If true loads the molecular system associated to the pharmacophore (Default: False).
Returns
-------
points : Pharmacophore
A pharmacophore.
"""
points = []
with open(pharmacophore_file, "r") as fff:
pharmacophore = json.load(fff)
feature_name = {
"Aromatic": "aromatic ring",
"Hydrophobic": "hydrophobicity",
"HydrogenAcceptor": "hb acceptor",
"HydrogenDonor": "hb donor",
"InclusionSphere": "included volume",
"ExclusionSphere": "excluded volume",
"PositiveIon": "positive charge",
"NegativeIon": "negative charge",
}
direction_points = ["Aromatic", "Hydrophobic", "HydrogenAcceptor", "HydrogenDonor"]
for pharmer_element in pharmacophore['points']:
pharmer_feature_name = pharmer_element['name']
if pharmer_feature_name in direction_points:
center, radius, direction = _get_pharmer_element_properties(pharmer_element, direction=True)
element = PharmacophoricPoint(feature_name[pharmer_feature_name], center, radius, direction)
else:
center, radius = _get_pharmer_element_properties(pharmer_element, direction=False)
element = PharmacophoricPoint(feature_name[pharmer_feature_name], center, radius)
points.append(element)
return Pharmacophore(points)
# PML (LigandScout) Files
_ligandscout_to_oph = {
"PI": "positive charge",
"NI": "negative charge",
"HBD": "hb donor",
"HBA": "hb acceptor",
"H": "hydrophobicity",
"AR": "aromatic ring",
"exclusion": "excluded volume"
}
def _pharmacophoric_point_from_point_or_volume(element):
""" Creates a pharmacophoric point from a ligandscout point or volume
Returns
-------
PharmacophoricPoint
The point
"""
if element.tag == "point":
feat_name = element.attrib["name"]
else:
feat_name = element.attrib["type"]
coords = element[0].attrib
x = float(coords["x3"])
y = float(coords["y3"])
z = float(coords["z3"])
radius = float(coords["tolerance"])
return PharmacophoricPoint(_ligandscout_to_oph[feat_name],
puw.quantity([x, y, z], "angstroms"),
puw.quantity(radius, "angstroms"))
def _pharmacophoric_point_from_vector_or_plane(element):
""" Creates a pharmacophoric point from a ligandscout plane or vector
Returns
-------
PharmacophoricPoint
The point
"""
feat_name = element.attrib["name"]
if element.tag == "vector":
tags = ["origin", "target"]
else:
tags = ["position", "normal"]
for position in element:
if position.tag == tags[0]:
coords_1 = position.attrib
x_1 = float(coords_1["x3"])
y_1 = float(coords_1["y3"])
z_1 = float(coords_1["z3"])
radius_1 = float(coords_1["tolerance"])
elif position.tag == tags[1]:
coords_2 = position.attrib
x_2 = float(coords_2["x3"])
y_2 = float(coords_2["y3"])
z_2 = float(coords_2["z3"])
return PharmacophoricPoint(_ligandscout_to_oph[feat_name],
puw.quantity([x_1, y_1, z_1], "angstroms"),
puw.quantity(radius_1, "angstroms"),
direction=[x_2, y_2, z_2])
[docs]def read_ligandscout(file_name):
""" Reads a ligandscout pharmacophore (pml) file.
Parameters
----------
file_name : str
Name of the file containing the pharmacophore.
Returns
-------
points : Pharmacophore
A pharmacophore.
"""
tree = ET.parse(file_name)
pharmacophore = tree.getroot()
points = []
for element in pharmacophore:
if element.tag == "point" or element.tag == "volume":
point = _pharmacophoric_point_from_point_or_volume(element)
elif element.tag == "vector" or element.tag == "plane":
point = _pharmacophoric_point_from_vector_or_plane(element)
else:
continue
points.append(point)
return Pharmacophore(points)