comparison cheminfolib.py @ 16:a638d8d13bb3 draft default tip

planemo upload for repository https://github.com/bgruening/galaxytools/tree/master/chemicaltoolbox/openbabel commit d9c51279c061a1da948a2582d5b502ca7573adbf
author bgruening
date Thu, 15 Aug 2024 11:02:57 +0000
parents 2cd8aee0d830
children
comparison
equal deleted inserted replaced
15:01ab890bf0ad 16:a638d8d13bb3
9 import subprocess 9 import subprocess
10 import sys 10 import sys
11 import tempfile 11 import tempfile
12 from multiprocessing import Pool 12 from multiprocessing import Pool
13 13
14
15 try: 14 try:
16 from galaxy import eggs 15 from galaxy import eggs
17 eggs.require('psycopg2') 16
17 eggs.require("psycopg2")
18 except ImportError: 18 except ImportError:
19 psycopg2 = None 19 psycopg2 = None
20 print('psycopg2 is not available. It is currently used in the pgchem wrappers, that are not shipped with default CTB') 20 print(
21 "psycopg2 is not available. It is currently used in the pgchem wrappers, that are not shipped with default CTB"
22 )
21 23
22 try: 24 try:
23 from openbabel import openbabel, pybel 25 from openbabel import openbabel, pybel
26
24 openbabel.obErrorLog.StopLogging() 27 openbabel.obErrorLog.StopLogging()
25 except ImportError: 28 except ImportError:
26 openbabel, pybel = None, None 29 openbabel, pybel = None, None
27 print('OpenBabel could not be found. A few functions are not available without OpenBabel.') 30 print(
31 "OpenBabel could not be found. A few functions are not available without OpenBabel."
32 )
28 33
29 34
30 def CountLines(path): 35 def CountLines(path):
31 out = subprocess.Popen(['wc', '-l', path], 36 out = subprocess.Popen(
32 stdout=subprocess.PIPE, 37 ["wc", "-l", path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
33 stderr=subprocess.STDOUT 38 ).communicate()[0]
34 ).communicate()[0] 39 return int(out.partition(b" ")[0])
35 return int(out.partition(b' ')[0])
36 40
37 41
38 def grep(pattern, file_obj): 42 def grep(pattern, file_obj):
39 grepper = re.compile(pattern) 43 grepper = re.compile(pattern)
40 for line in file_obj: 44 for line in file_obj:
47 mol = False 51 mol = False
48 possible_inchi = True 52 possible_inchi = True
49 for line_counter, line in enumerate(open(filepath)): 53 for line_counter, line in enumerate(open(filepath)):
50 if line_counter > 10000: 54 if line_counter > 10000:
51 break 55 break
52 if line.find('$$$$') != -1: 56 if line.find("$$$$") != -1:
53 return 'sdf' 57 return "sdf"
54 elif line.find('@<TRIPOS>MOLECULE') != -1: 58 elif line.find("@<TRIPOS>MOLECULE") != -1:
55 return 'mol2' 59 return "mol2"
56 elif line.find('ligand id') != -1: 60 elif line.find("ligand id") != -1:
57 return 'drf' 61 return "drf"
58 elif possible_inchi and re.findall('^InChI=', line): 62 elif possible_inchi and re.findall("^InChI=", line):
59 return 'inchi' 63 return "inchi"
60 elif re.findall(r'^M\s+END', line): 64 elif re.findall(r"^M\s+END", line):
61 mol = True 65 mol = True
62 # first line is not an InChI, so it can't be an InChI file 66 # first line is not an InChI, so it can't be an InChI file
63 possible_inchi = False 67 possible_inchi = False
64 68
65 if mol: 69 if mol:
66 # END can occures before $$$$, so and SDF file will 70 # END can occures before $$$$, so and SDF file will
67 # be recognised as mol, if you not using this hack' 71 # be recognised as mol, if you not using this hack'
68 return 'mol' 72 return "mol"
69 return 'smi' 73 return "smi"
70 74
71 75
72 def db_connect(args): 76 def db_connect(args):
73 try: 77 try:
74 db_conn = psycopg2.connect("dbname=%s user=%s host=%s password=%s" % (args.dbname, args.dbuser, args.dbhost, args.dbpasswd)) 78 db_conn = psycopg2.connect(
79 "dbname=%s user=%s host=%s password=%s"
80 % (args.dbname, args.dbuser, args.dbhost, args.dbpasswd)
81 )
75 return db_conn 82 return db_conn
76 except psycopg2.Error: 83 except psycopg2.Error:
77 sys.exit('Unable to connect to the db') 84 sys.exit("Unable to connect to the db")
78 85
79 86
80 ColumnNames = { 87 ColumnNames = {
81 'can_smiles': 'Canonical SMILES', 88 "can_smiles": "Canonical SMILES",
82 'can': 'Canonical SMILES', 89 "can": "Canonical SMILES",
83 'inchi': 'InChI', 90 "inchi": "InChI",
84 'inchi_key': 'InChI key', 91 "inchi_key": "InChI key",
85 'inchi_key_first': 'InChI key first', 92 "inchi_key_first": "InChI key first",
86 'inchi_key_last': 'InChI key last', 93 "inchi_key_last": "InChI key last",
87 'molwt': 'Molecular weight', 94 "molwt": "Molecular weight",
88 'hbd': 'Hydrogen-bond donors', 95 "hbd": "Hydrogen-bond donors",
89 'donors': 'Hydrogen-bond donors', 96 "donors": "Hydrogen-bond donors",
90 'hba': 'Hydrogen-bond acceptors', 97 "hba": "Hydrogen-bond acceptors",
91 'acceptors': 'Hydrogen-bond acceptors', 98 "acceptors": "Hydrogen-bond acceptors",
92 'rotbonds': 'Rotatable bonds', 99 "rotbonds": "Rotatable bonds",
93 'logp': 'logP', 100 "logp": "logP",
94 'psa': 'Polar surface area', 101 "psa": "Polar surface area",
95 'mr': 'Molecular refractivity', 102 "mr": "Molecular refractivity",
96 'atoms': 'Number of heavy atoms', 103 "atoms": "Number of heavy atoms",
97 'rings': 'Number of rings', 104 "rings": "Number of rings",
98 'set_bits': 'FP2 bits', 105 "set_bits": "FP2 bits",
99 'id': 'Internal identifier', 106 "id": "Internal identifier",
100 'tani': 'Tanimoto coefficient', 107 "tani": "Tanimoto coefficient",
101 'spectrophore': 'Spectrophores(TM)', 108 "spectrophore": "Spectrophores(TM)",
102 'dist_spectrophore': 'Spectrophores(TM) distance to target', 109 "dist_spectrophore": "Spectrophores(TM) distance to target",
103 'synonym': 'Entry id', 110 "synonym": "Entry id",
104 } 111 }
105 112
106 OBDescriptor = { 113 OBDescriptor = {
107 'atoms': ["atoms", "Number of atoms"], 114 "atoms": ["atoms", "Number of atoms"],
108 'hatoms': ["hatoms", "Number of heavy atoms"], # self defined tag hatoms in plugindefines.txt 115 "hatoms": [
109 'can_smiles': ["cansmi", "Canonical SMILES"], 116 "hatoms",
110 'can_smilesNS': ["cansmiNS", "Canonical SMILES without isotopes or stereo"], 117 "Number of heavy atoms",
118 ], # self defined tag hatoms in plugindefines.txt
119 "can_smiles": ["cansmi", "Canonical SMILES"],
120 "can_smilesNS": ["cansmiNS", "Canonical SMILES without isotopes or stereo"],
111 # ["abonds", "Number of aromatic bonds"], 121 # ["abonds", "Number of aromatic bonds"],
112 # ["bonds", "Number of bonds"], 122 # ["bonds", "Number of bonds"],
113 # ["dbonds", "Number of double bonds"], 123 # ["dbonds", "Number of double bonds"],
114 # ["formula", "Chemical formula"], 124 # ["formula", "Chemical formula"],
115 'hba': ["HBA1", "Number of Hydrogen Bond Acceptors 1 (JoelLib)"], 125 "hba": ["HBA1", "Number of Hydrogen Bond Acceptors 1 (JoelLib)"],
116 'hba2': ["HBA2", "Number of Hydrogen Bond Acceptors 2 (JoelLib)"], 126 "hba2": ["HBA2", "Number of Hydrogen Bond Acceptors 2 (JoelLib)"],
117 'hbd': ["HBD", "Number of Hydrogen Bond Donors (JoelLib)"], 127 "hbd": ["HBD", "Number of Hydrogen Bond Donors (JoelLib)"],
118 'inchi': ["InChI", "IUPAC InChI identifier"], 128 "inchi": ["InChI", "IUPAC InChI identifier"],
119 'inchi_key': ["InChIKey", "InChIKey"], 129 "inchi_key": ["InChIKey", "InChIKey"],
120 # ["L5", "Lipinski Rule of Five"], 130 # ["L5", "Lipinski Rule of Five"],
121 'logp': ["logP", "octanol/water partition coefficient"], 131 "logp": ["logP", "octanol/water partition coefficient"],
122 'mr': ["MR", "molar refractivity"], 132 "mr": ["MR", "molar refractivity"],
123 'molwt': ["MW", "Molecular Weight filter"], 133 "molwt": ["MW", "Molecular Weight filter"],
124 # ["nF", "Number of Fluorine Atoms"], 134 # ["nF", "Number of Fluorine Atoms"],
125 # ["s", "SMARTS filter"], 135 # ["s", "SMARTS filter"],
126 # ["sbonds", "Number of single bonds"], 136 # ["sbonds", "Number of single bonds"],
127 # ["smarts", "SMARTS filter"], 137 # ["smarts", "SMARTS filter"],
128 # ["tbonds", "Number of triple bonds"], 138 # ["tbonds", "Number of triple bonds"],
129 # ["title", "For comparing a molecule's title"], 139 # ["title", "For comparing a molecule's title"],
130 'psa': ["TPSA", "topological polar surface area"], 140 "psa": ["TPSA", "topological polar surface area"],
131 'rotbonds': ['ROTATABLE_BOND', 'rotatable bonds'], 141 "rotbonds": ["ROTATABLE_BOND", "rotatable bonds"],
132 } 142 }
133 143
134 144
135 def print_output(args, rows): 145 def print_output(args, rows):
136 if args.oformat == 'table': 146 if args.oformat == "table":
137 outfile = open(args.output, 'w') 147 outfile = open(args.output, "w")
138 requested_fields = (filter(lambda x: x not in ["[", "]", "'"], args.fetch)).split(', ') 148 requested_fields = (
149 filter(lambda x: x not in ["[", "]", "'"], args.fetch)
150 ).split(", ")
139 if args.header: 151 if args.header:
140 outfile.write('Identifier\t' + '\t'.join([ColumnNames[key] for key in requested_fields]) + '\n') 152 outfile.write(
153 "Identifier\t"
154 + "\t".join([ColumnNames[key] for key in requested_fields])
155 + "\n"
156 )
141 for row in rows: 157 for row in rows:
142 outfile.write(row['synonym'] + '\t' + '\t'.join([str(row[key]) for key in requested_fields]) + '\n') 158 outfile.write(
143 159 row["synonym"]
144 elif args.oformat in ['sdf', 'mol2']: 160 + "\t"
161 + "\t".join([str(row[key]) for key in requested_fields])
162 + "\n"
163 )
164
165 elif args.oformat in ["sdf", "mol2"]:
145 outfile = pybel.Outputfile(args.oformat, args.output, overwrite=True) 166 outfile = pybel.Outputfile(args.oformat, args.output, overwrite=True)
146 for row in rows: 167 for row in rows:
147 try: 168 try:
148 mol = pybel.readstring('sdf', row['mol']) 169 mol = pybel.readstring("sdf", row["mol"])
149 if args.oformat == 'sdf': 170 if args.oformat == "sdf":
150 keys = filter(lambda x: x not in ["[", "]", "'"], args.fetch).split(', ') 171 keys = filter(lambda x: x not in ["[", "]", "'"], args.fetch).split(
151 mol.data.update({ColumnNames['synonym']: row['synonym']}) 172 ", "
152 if 'inchi_key' in keys: 173 )
153 keys = (', '.join(keys).replace("inchi_key", "inchi_key_first, inchi_key_last")).split(', ') 174 mol.data.update({ColumnNames["synonym"]: row["synonym"]})
154 [mol.data.update({ColumnNames[key]: row[key]}) for key in keys if key] 175 if "inchi_key" in keys:
176 keys = (
177 ", ".join(keys).replace(
178 "inchi_key", "inchi_key_first, inchi_key_last"
179 )
180 ).split(", ")
181 [
182 mol.data.update({ColumnNames[key]: row[key]})
183 for key in keys
184 if key
185 ]
155 outfile.write(mol) 186 outfile.write(mol)
156 except OSError: 187 except OSError:
157 pass 188 pass
158 else: 189 else:
159 outfile = open(args.output, 'w') 190 outfile = open(args.output, "w")
160 outfile.write('\n'.join(['%s\t%s' % (row[args.oformat], row['synonym']) for row in rows])) 191 outfile.write(
192 "\n".join(["%s\t%s" % (row[args.oformat], row["synonym"]) for row in rows])
193 )
161 outfile.close() 194 outfile.close()
162 195
163 196
164 def pybel_stop_logging(): 197 def pybel_stop_logging():
165 openbabel.obErrorLog.StopLogging() 198 openbabel.obErrorLog.StopLogging()
166 199
167 200
168 def get_properties_ext(mol): 201 def get_properties_ext(mol):
169 HBD = pybel.Smarts("[!#6;!H0]") 202 HBD = pybel.Smarts("[!#6;!H0]")
170 HBA = pybel.Smarts(("[$([$([#8,#16]);!$(*=N~O);" 203 HBA = pybel.Smarts(
171 "!$(*~N=O);X1,X2]),$([#7;v3;" 204 (
172 "!$([nH]);!$(*(-a)-a)])]" 205 "[$([$([#8,#16]);!$(*=N~O);"
173 )) 206 "!$(*~N=O);X1,X2]),$([#7;v3;"
207 "!$([nH]);!$(*(-a)-a)])]"
208 )
209 )
174 calc_desc_dict = mol.calcdesc() 210 calc_desc_dict = mol.calcdesc()
175 211
176 try: 212 try:
177 logp = calc_desc_dict['logP'] 213 logp = calc_desc_dict["logP"]
178 except KeyError: 214 except KeyError:
179 logp = calc_desc_dict['LogP'] 215 logp = calc_desc_dict["LogP"]
180 216
181 return {"molwt": mol.molwt, 217 return {
182 "logp": logp, 218 "molwt": mol.molwt,
183 "donors": len(HBD.findall(mol)), 219 "logp": logp,
184 "acceptors": len(HBA.findall(mol)), 220 "donors": len(HBD.findall(mol)),
185 "psa": calc_desc_dict['TPSA'], 221 "acceptors": len(HBA.findall(mol)),
186 "mr": calc_desc_dict['MR'], 222 "psa": calc_desc_dict["TPSA"],
187 "rotbonds": mol.OBMol.NumRotors(), 223 "mr": calc_desc_dict["MR"],
188 "can": mol.write("can").split()[0].strip(), # tthis one works fine for both zinc and chembl (no ZINC code added after can descriptor string) 224 "rotbonds": mol.OBMol.NumRotors(),
189 "inchi": mol.write("inchi").strip(), 225 "can": mol.write("can")
190 "inchi_key": get_inchikey(mol).strip(), 226 .split()[0]
191 "rings": len(mol.sssr), 227 .strip(), # tthis one works fine for both zinc and chembl (no ZINC code added after can descriptor string)
192 "atoms": mol.OBMol.NumHvyAtoms(), 228 "inchi": mol.write("inchi").strip(),
193 "spectrophore": OBspectrophore(mol), 229 "inchi_key": get_inchikey(mol).strip(),
194 } 230 "rings": len(mol.sssr),
231 "atoms": mol.OBMol.NumHvyAtoms(),
232 "spectrophore": OBspectrophore(mol),
233 }
195 234
196 235
197 def get_inchikey(mol): 236 def get_inchikey(mol):
198 conv = openbabel.OBConversion() 237 conv = openbabel.OBConversion()
199 conv.SetInAndOutFormats("mol", "inchi") 238 conv.SetInAndOutFormats("mol", "inchi")
204 243
205 def OBspectrophore(mol): 244 def OBspectrophore(mol):
206 spectrophore = pybel.ob.OBSpectrophore() 245 spectrophore = pybel.ob.OBSpectrophore()
207 # Parameters: rotation angle = 20, normalization for mean and sd, accuracy = 3.0 A and non-stereospecific cages. 246 # Parameters: rotation angle = 20, normalization for mean and sd, accuracy = 3.0 A and non-stereospecific cages.
208 spectrophore.SetNormalization(spectrophore.NormalizationTowardsZeroMeanAndUnitStd) 247 spectrophore.SetNormalization(spectrophore.NormalizationTowardsZeroMeanAndUnitStd)
209 return ', '.join(["%.3f" % value for value in spectrophore.GetSpectrophore(mol.OBMol)]) 248 return ", ".join(
210 249 ["%.3f" % value for value in spectrophore.GetSpectrophore(mol.OBMol)]
211 250 )
212 def split_library(lib_path, lib_format='sdf', package_size=None): 251
252
253 def split_library(lib_path, lib_format="sdf", package_size=None):
213 """ 254 """
214 Split a library of compounds. Usage: split_library(lib_path, lib_format, package_size) 255 Split a library of compounds. Usage: split_library(lib_path, lib_format, package_size)
215 IT currently ONLY WORKS FOR SD-Files 256 IT currently ONLY WORKS FOR SD-Files
216 """ 257 """
217 pack = 1 258 pack = 1
218 mol_counter = 0 259 mol_counter = 0
219 260
220 outfile = open('/%s/%s_pack_%i.%s' % ('/'.join(lib_path.split('/')[:-1]), lib_path.split('/')[-1].split('.')[0], pack, 'sdf'), 'w') 261 outfile = open(
221 262 "/%s/%s_pack_%i.%s"
222 for line in open(lib_path, 'r'): 263 % (
264 "/".join(lib_path.split("/")[:-1]),
265 lib_path.split("/")[-1].split(".")[0],
266 pack,
267 "sdf",
268 ),
269 "w",
270 )
271
272 for line in open(lib_path, "r"):
223 outfile.write(line) 273 outfile.write(line)
224 if line.strip() == '$$$$': 274 if line.strip() == "$$$$":
225 mol_counter += 1 275 mol_counter += 1
226 if mol_counter % package_size == 0: 276 if mol_counter % package_size == 0:
227 outfile.close() 277 outfile.close()
228 pack += 1 278 pack += 1
229 outfile = open('/%s/%s_pack_%i.%s' % ('/'.join(lib_path.split('/')[:-1]), lib_path.split('/')[-1].split('.')[0], pack, 'sdf'), 'w') 279 outfile = open(
280 "/%s/%s_pack_%i.%s"
281 % (
282 "/".join(lib_path.split("/")[:-1]),
283 lib_path.split("/")[-1].split(".")[0],
284 pack,
285 "sdf",
286 ),
287 "w",
288 )
230 if mol_counter * 10 % package_size == 0: 289 if mol_counter * 10 % package_size == 0:
231 print('%i molecules parsed, starting pack nr. %i' % (mol_counter, pack - 1)) 290 print(
291 "%i molecules parsed, starting pack nr. %i"
292 % (mol_counter, pack - 1)
293 )
232 outfile.close() 294 outfile.close()
233 295
234 return True 296 return True
235 297
236 298
240 Usage: split_smi_library(smiles_file, 10) 302 Usage: split_smi_library(smiles_file, 10)
241 """ 303 """
242 output_files = [] 304 output_files = []
243 tfile = tempfile.NamedTemporaryFile(delete=False) 305 tfile = tempfile.NamedTemporaryFile(delete=False)
244 306
245 smiles_handle = open(smiles_file, 'r') 307 smiles_handle = open(smiles_file, "r")
246 for count, line in enumerate(smiles_handle): 308 for count, line in enumerate(smiles_handle):
247 if count % structures_in_one_file == 0 and count != 0: 309 if count % structures_in_one_file == 0 and count != 0:
248 tfile.close() 310 tfile.close()
249 output_files.append(tfile.name) 311 output_files.append(tfile.name)
250 tfile = tempfile.NamedTemporaryFile(delete=False) 312 tfile = tempfile.NamedTemporaryFile(delete=False)
255 return output_files 317 return output_files
256 318
257 319
258 def mp_run(input_path, regex, PROCESSES, function_to_call): 320 def mp_run(input_path, regex, PROCESSES, function_to_call):
259 paths = [] 321 paths = []
260 [paths.append(compound_file) for compound_file in glob.glob(str(input_path) + str(regex))] 322 [
323 paths.append(compound_file)
324 for compound_file in glob.glob(str(input_path) + str(regex))
325 ]
261 paths.sort() 326 paths.sort()
262 327
263 pool = Pool(processes=PROCESSES) 328 pool = Pool(processes=PROCESSES)
264 print('Process initialized with', PROCESSES, 'processors') 329 print("Process initialized with", PROCESSES, "processors")
265 result = pool.map_async(function_to_call, paths) 330 result = pool.map_async(function_to_call, paths)
266 result.get() 331 result.get()
267 332
268 return paths 333 return paths
269 334
270 335
271 if __name__ == '__main__': 336 if __name__ == "__main__":
272 print(check_filetype(sys.argv[1])) 337 print(check_filetype(sys.argv[1]))