Package cinfony :: Module indy
[frames] | no frames]

Source Code for Module cinfony.indy

  1  #-*. coding: utf-8 -*- 
  2  ## Copyright (c) 2011, Noel O'Boyle; 2012, Adrià Cereto-Massagué 
  3  ## All rights reserved. 
  4  ## 
  5  ##  This file is part of Cinfony. 
  6  ##  The contents are covered by the terms of the GPL v3 license 
  7  ##  which is included in the file LICENSE_GPLv3.txt. 
  8   
  9   
 10  """ 
 11  indy - A Cinfony module for accessing Indigo from CPython, Jython or IronPython 
 12   
 13  Global variables: 
 14    indigo - the underlying Indigo() object 
 15    informats - a dictionary of supported input formats 
 16    outformats - a dictionary of supported output formats 
 17    fps - a list of supported fingerprint types 
 18  """ 
 19   
 20  import os 
 21  import sys 
 22  import tempfile 
 23   
 24  if sys.platform[:3] == "cli": 
 25      _indigonet = os.environ["INDIGONET"] 
 26      import clr 
 27      clr.AddReference('System.Windows.Forms') 
 28      clr.AddReference('System.Drawing') 
 29      clr.AddReferenceToFileAndPath(_indigonet + "\\indigo-dotnet.dll") 
 30      clr.AddReferenceToFileAndPath(_indigonet + "\\indigo-inchi-dotnet.dll") 
 31      clr.AddReferenceToFileAndPath(_indigonet + "\\indigo-renderer-dotnet.dll") 
 32      from System.Windows.Forms import ( 
 33          Application, DockStyle, Form, PictureBox, PictureBoxSizeMode 
 34          ) 
 35      from System.Drawing import Image, Size 
 36  elif sys.platform[:4] == "java": 
 37      import java, javax 
 38   
 39  if sys.platform[:3] == "cli" or sys.platform[:4] == "java": 
 40      from com.ggasoftware.indigo import Indigo, IndigoException, IndigoRenderer, IndigoInchi 
 41  else: 
 42      from indigo import Indigo, IndigoException 
 43      from indigo_renderer import IndigoRenderer 
 44      from indigo_inchi import IndigoInchi 
 45   
 46  indigo = Indigo() 
 47  indigoInchi = IndigoInchi(indigo) 
 48   
 49  # PIL and Tkinter 
 50  try: 
 51      import Tkinter as tk 
 52      import Image as PIL 
 53      import ImageTk as PILtk 
 54  except: 
 55      PILtk = None 
 56   
 57  fps = ["sim", "sub", "sub-res", "sub-tau", "full"] 
 58  """A list of supported fingerprint types""" 
 59   
 60  _formats = {'smi': "SMILES", 'can': "Canonical SMILES", "rdf": "MDL RDF file", 
 61              'mol': "MDL MOL file", 'sdf': "MDL SDF file", 
 62              'cml': "Chemical Markup Language", 
 63              'inchi': "InChI", 'inchikey': "InChIKey"} 
 64  informats = dict([(_x, _formats[_x]) for _x in ['mol', 'sdf', 'rdf', 'smi', 
 65                                                  'cml', 'inchi']]) 
 66  """A dictionary of supported input formats""" 
 67  outformats = dict([(_x, _formats[_x]) for _x in ['mol', 'sdf', 'smi', 'can', 
 68                                                   'cml', 'inchi', 'inchikey']]) 
 69  """A dictionary of supported output formats""" 
70 71 -def readfile(format, filename):
72 """Iterate over the molecules in a file. 73 74 Required parameters: 75 format - see the informats variable for a list of available 76 input formats 77 filename 78 79 You can access the first molecule in a file using the next() method 80 of the iterator: 81 mol = readfile("smi", "myfile.smi").next() 82 83 You can make a list of the molecules in a file using: 84 mols = list(readfile("smi", "myfile.smi")) 85 86 You can iterate over the molecules in a file as shown in the 87 following code snippet: 88 >>> atomtotal = 0 89 >>> for mol in readfile("sdf", "head.sdf"): 90 ... atomtotal += len(mol.atoms) 91 ... 92 >>> print atomtotal 93 43 94 """ 95 if not os.path.isfile(filename): 96 raise IOError, "No such file: '%s'" % filename 97 format = format.lower() 98 # Eagerly evaluate the supplier functions in order to report 99 # errors in the format and errors in opening the file. 100 # Then switch to an iterator... 101 if format=="sdf": 102 iterator = indigo.iterateSDFile(filename) 103 def sdf_reader(): 104 for mol in iterator: 105 yield Molecule(mol)
106 return sdf_reader() 107 elif format=="rdf": 108 iterator = indigo.iterateRDFile(filename) 109 def rdf_reader(): 110 for mol in iterator: 111 yield Molecule(mol) 112 return rdf_reader() 113 elif format=="mol": 114 def mol_reader(): 115 yield Molecule(indigo.loadMoleculeFromFile(filename)) 116 return mol_reader() 117 elif format=="smi": 118 iterator = iterateSmilesFile(filename) 119 def smi_reader(): 120 for mol in iterator: 121 yield Molecule(mol) 122 return smi_reader() 123 elif format=="cml": 124 iterator = iterateCMLFile(filename) 125 def cml_reader(): 126 for mol in iterator: 127 yield Molecule(mol) 128 return cml_reader() 129 130 else: 131 raise ValueError, "%s is not a recognised Indigo format" % format 132
133 -def readstring(format, string):
134 """Read in a molecule from a string. 135 136 Required parameters: 137 format - see the informats variable for a list of available 138 input formats 139 string 140 141 Example: 142 >>> input = "C1=CC=CS1" 143 >>> mymol = readstring("smi", input) 144 >>> len(mymol.atoms) 145 5 146 """ 147 format = format.lower() 148 if format not in informats: 149 raise ValueError,"%s is not a recognised Indigo format" % format 150 151 module = indigo if format != "inchi" else indigoInchi 152 try: 153 mol = module.loadMolecule(string) 154 except IndigoException: 155 raise IOError, "Failed to convert '%s' to format '%s'" % ( 156 string, format) 157 158 return Molecule(mol)
159
160 161 -class Outputfile(object):
162 """Represent a file to which *output* is to be sent. 163 164 Required parameters: 165 format - see the outformats variable for a list of available 166 output formats 167 filename 168 169 Optional parameters: 170 overwite -- if the output file already exists, should it 171 be overwritten? (default is False) 172 173 Methods: 174 write(molecule) 175 close() 176 """
177 - def __init__(self, format, filename, overwrite=False):
178 self.format = format 179 self.filename = filename 180 if not overwrite and os.path.isfile(self.filename): 181 raise IOError, "%s already exists. Use 'overwrite=True' to overwrite it." % self.filename 182 if self.format in ["sdf", "cml", "rdf", "smi"]: 183 self._writer = indigo.writeFile(self.filename) 184 else: 185 raise ValueError,"%s is not supported for multimolecule output" % format 186 self.total = 0 # The total number of molecules written to the file 187 188 if self.format == "cml": 189 self._writer.cmlHeader() 190 elif self.format == "rdf": 191 self._writer.rdfHeader()
192
193 - def write(self, molecule):
194 """Write a molecule to the output file. 195 196 Required parameters: 197 molecule 198 """ 199 if not self.filename: 200 raise IOError, "Outputfile instance is closed." 201 202 if self.format == "sdf": 203 self._writer.sdfAppend(molecule.Mol) 204 elif self.format == "rdf": 205 self._writer.rdfAppend(molecule.Mol) 206 elif self.format == "cml": 207 self._writer.cmlAppend(molecule.Mol) 208 elif self.format == "smi": 209 self._writer.smilesAppend(molecule.Mol) 210 self.total += 1
211
212 - def close(self):
213 """Close the Outputfile to further writing.""" 214 if self.format == "cml": 215 self._writer.cmlFooter() 216 self._writer.close() 217 self.filename = None 218 del self._writer
219
220 -class Molecule(object):
221 """Represent an Indigo Molecule. 222 223 Required parameter: 224 Mol -- an Indigo Mol or any type of cinfony Molecule 225 226 Attributes: 227 atoms, data, molwt, title 228 229 Methods: 230 addh(), calcfp(), draw(), localopt(), removeh(), 231 write() 232 233 The underlying Indigo Molecule can be accessed using the attribute: 234 Mol 235 """ 236 _cinfony = True 237
238 - def __init__(self, Mol):
239 if hasattr(Mol, "_cinfony"): 240 a, b = Mol._exchange 241 if a == 0: 242 molecule = readstring("smi", b) 243 else: 244 molecule = readstring("mol", b) 245 Mol = molecule.Mol 246 247 self.Mol = Mol
248 249 @property
250 - def atoms(self): return [Atom(atom) for atom in self.Mol.iterateAtoms()]
251 @property
252 - def data(self): return MoleculeData(self.Mol)
253 @property
254 - def formula(self): return self.Mol.grossFormula()
255 @property
256 - def molwt(self): return self.Mol.molecularWeight()
257 - def _gettitle(self):
258 return self.Mol.name()
259 - def _settitle(self, val): self.Mol.setName(val)
260 title = property(_gettitle, _settitle) 261 @property
262 - def _exchange(self):
263 if not self.Mol.hasZCoord(): 264 return (0, self.write("can")) 265 else: # If 3D 266 return (1, self.write("mol"))
267
268 - def addh(self):
269 """Add hydrogens.""" 270 self.Mol.unfoldHydrogens()
271
272 - def removeh(self):
273 """Remove hydrogens.""" 274 self.Mol.foldHydrogens()
275
276 - def write(self, format="smi", filename=None, overwrite=False):
277 """Write the molecule to a file or return a string. 278 279 Optional parameters: 280 format -- see the informats variable for a list of available 281 output formats (default is "smi") 282 filename -- default is None 283 overwite -- if the output file already exists, should it 284 be overwritten? (default is False) 285 286 If a filename is specified, the result is written to a file. 287 Otherwise, a string is returned containing the result. 288 289 To write multiple molecules to the same file you should use 290 the Outputfile class. 291 """ 292 format = format.lower() 293 if filename: 294 if not overwrite and os.path.isfile(filename): 295 raise IOError, "%s already exists. Use 'overwrite=True' to overwrite it." % filename 296 if format=="smi": 297 result = self.Mol.smiles() 298 elif format=="can": 299 result = self.Mol.canonicalSmiles() 300 elif format=="mol": 301 result = self.Mol.molfile() 302 elif format=="inchi": 303 result = indigoInchi.getInchi(self.Mol) 304 elif format=="inchikey": 305 result = indigoInchi.getInchiKey(self.write("inchi")) 306 elif format=="cml": 307 result = self.Mol.cml() 308 elif format=="sdf": 309 # No sdf method so use a writeBuffer() as described by Dmitry 310 buf = indigo.writeBuffer() 311 buf.sdfAppend(self.Mol) 312 result = buf.toString() 313 else: 314 raise ValueError,"%s is not a recognised Indigo format" % format 315 if filename: 316 output = open(filename, "w") 317 output.write(result) 318 output.close() 319 else: 320 return result
321
322 - def __iter__(self):
323 """Iterate over the Atoms of the Molecule. 324 325 This allows constructions such as the following: 326 for atom in mymol: 327 print atom 328 """ 329 return iter(self.atoms)
330
331 - def __str__(self):
332 return self.write()
333 334 ## def calcdesc(self, descnames=[]): 335 ## """Calculate descriptor values. 336 ## 337 ## Optional parameter: 338 ## descnames -- a list of names of descriptors 339 ## 340 ## If descnames is not specified, all available descriptors are 341 ## calculated. See the descs variable for a list of available 342 ## descriptors. 343 ## """ 344 ## if not descnames: 345 ## descnames = descs 346 ## ans = {} 347 ## for descname in descnames: 348 ## try: 349 ## desc = descDict[descname] 350 ## except KeyError: 351 ## raise ValueError, "%s is not a recognised RDKit descriptor type" % descname 352 ## ans[descname] = desc(self.Mol) 353 ## return ans 354
355 - def calcfp(self, fptype="sim"):
356 """Calculate a molecular fingerprint. 357 358 Optional parameters: 359 fptype -- the fingerprint type (default is "sim"). See the 360 fps variable for a list of of available fingerprint 361 types. 362 """ 363 fptype = fptype.lower() 364 if fptype in ["sim", "sub", "sub-res", "sub-tau", "full"]: 365 fp = Fingerprint(self.Mol.fingerprint(fptype)) 366 else: 367 raise ValueError, "%s is not a recognised Indigo Fingerprint type" % fptype 368 return fp
369
370 - def draw(self, show=True, filename=None, update=False, usecoords=False):
371 """Create a 2D depiction of the molecule. 372 373 Optional parameters: 374 show -- display on screen (default is True) 375 filename -- write to file (default is None) 376 update -- update the coordinates of the atoms to those 377 determined by the structure diagram generator 378 (default is False) 379 usecoords -- don't calculate 2D coordinates, just use 380 the current coordinates (default is False) 381 382 Tkinter and Python Imaging Library are required for image display. 383 """ 384 if update: 385 mol = self.Mol 386 else: 387 mol = self.Mol.clone() 388 if not usecoords: 389 mol.layout() 390 if show or filename: 391 renderer = IndigoRenderer(indigo) 392 indigo.setOption("render-output-format", "png") 393 indigo.setOption("render-margins", 10, 10) 394 indigo.setOption("render-coloring", "True") 395 indigo.setOption("render-image-size", 300, 300) 396 indigo.setOption("render-background-color", "1.0, 1.0, 1.0") 397 if self.title: 398 indigo.setOption("render-comment", self.title) 399 if filename: 400 filedes = None 401 else: 402 filedes, filename = tempfile.mkstemp() 403 404 renderer.renderToFile(mol, filename) 405 406 if show: 407 if sys.platform[:4] == "java": 408 image = javax.imageio.ImageIO.read(java.io.File(filename)) 409 frame = javax.swing.JFrame(visible=1) 410 frame.getContentPane().add(javax.swing.JLabel(javax.swing.ImageIcon(image))) 411 frame.setSize(300,300) 412 frame.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE) 413 frame.show() 414 415 elif sys.platform[:3] == "cli": 416 if filedes: 417 errormessage = ("It is only possible to show the molecule if you " 418 "provide a filename. The reason for this is that I kept " 419 "having problems when using temporary files.") 420 raise RuntimeError(errormessage) 421 form = Form() 422 form.ClientSize = Size(300, 300) 423 form.Text = self.title 424 image = Image.FromFile(filename) 425 box = PictureBox() 426 box.SizeMode = PictureBoxSizeMode.StretchImage 427 box.Image = image 428 box.Dock = DockStyle.Fill 429 form.Controls.Add(box) 430 form.Show() 431 Application.Run(form) 432 433 else: 434 if not PILtk: 435 errormessage = ("Tkinter or Python Imaging " 436 "Library not found, but is required for image " 437 "display. See installation instructions for " 438 "more information.") 439 raise ImportError, errormessage 440 441 root = tk.Tk() 442 root.title((hasattr(self, "title") and self.title) 443 or self.__str__().rstrip()) 444 frame = tk.Frame(root, colormap="new", visual='truecolor').pack() 445 image = PIL.open(filename) 446 imagedata = PILtk.PhotoImage(image) 447 label = tk.Label(frame, image=imagedata).pack() 448 quitbutton = tk.Button(root, text="Close", command=root.destroy).pack(fill=tk.X) 449 root.mainloop() 450 451 452 if filedes: 453 os.close(filedes) 454 os.remove(filename)
455
456 -class Atom(object):
457 """Represent an Indigo Atom. 458 459 Required parameters: 460 Atom -- an Indigo Atom 461 462 Attributes: 463 atomicnum, coords, formalcharge 464 465 The original Indigo Atom can be accessed using the attribute: 466 Atom 467 """ 468
469 - def __init__(self, Atom):
470 self.Atom = Atom
471 @property
472 - def atomicnum(self): return self.Atom.atomicNumber()
473 @property
474 - def coords(self):
475 return tuple(self.Atom.xyz())
476 @property
477 - def formalcharge(self): return self.Atom.charge()
478
479 - def __str__(self):
480 if hasattr(self, "coords"): 481 return "Atom: %d (%.2f %.2f %.2f)" % (self.atomicnum, self.coords[0], 482 self.coords[1], self.coords[2]) 483 else: 484 return "Atom: %d (no coords)" % (self.atomicnum)
485
486 -class Smarts(object):
487 """A Smarts Pattern Matcher 488 489 Required parameters: 490 smartspattern 491 492 Methods: 493 findall(molecule) 494 495 Example: 496 >>> mol = readstring("smi","CCN(CC)CC") # triethylamine 497 >>> smarts = Smarts("[#6][#6]") # Matches an ethyl group 498 >>> print smarts.findall(mol) 499 [(0, 1), (3, 4), (5, 6)] 500 501 The numbers returned are the indices (starting from 0) of the atoms 502 that match the SMARTS pattern. In this case, there are three matches 503 for each of the three ethyl groups in the molecule. 504 """
505 - def __init__(self,smartspattern):
506 """Initialise with a SMARTS pattern.""" 507 try: 508 self.smarts = indigo.loadSmarts(smartspattern) 509 except IndigoException: 510 raise IOError, "Invalid SMARTS pattern."
511
512 - def findall(self,molecule):
513 """Find all matches of the SMARTS pattern to a particular molecule. 514 515 Required parameters: 516 molecule 517 """ 518 matcher = indigo.substructureMatcher(molecule.Mol) 519 matches = list(matcher.iterateMatches(self.smarts)) 520 ans = [] 521 for match in matches: 522 a = [] 523 for queryatom in self.smarts.iterateAtoms(): 524 a.append(match.mapAtom(queryatom).index()) 525 ans.append(tuple(a)) 526 return ans
527
528 -class MoleculeData(object):
529 """Store molecule data in a dictionary-type object 530 531 Required parameters: 532 Mol -- an Indigo Mol 533 534 Methods and accessor methods are like those of a dictionary except 535 that the data is retrieved on-the-fly from the underlying Mol. 536 537 Example: 538 >>> mol = readfile("sdf", 'head.sdf').next() 539 >>> data = mol.data 540 >>> print data 541 {'Comment': 'CORINA 2.61 0041 25.10.2001', 'NSC': '1'} 542 >>> print len(data), data.keys(), data.has_key("NSC") 543 2 ['Comment', 'NSC'] True 544 >>> print data['Comment'] 545 CORINA 2.61 0041 25.10.2001 546 >>> data['Comment'] = 'This is a new comment' 547 >>> for k,v in data.iteritems(): 548 ... print k, "-->", v 549 Comment --> This is a new comment 550 NSC --> 1 551 >>> del data['NSC'] 552 >>> print len(data), data.keys(), data.has_key("NSC") 553 1 ['Comment'] False 554 """
555 - def __init__(self, Mol):
556 self._mol = Mol
557 - def _testforkey(self, key):
558 if not self._mol.hasProperty(key): 559 raise KeyError, "'%s'" % key
560 - def keys(self):
561 return [prop.name() for prop in self._mol.iterateProperties()]
562 - def values(self):
563 return [prop.rawData() for prop in self._mol.iterateProperties()]
564 - def items(self):
565 return [(prop.name(), prop.rawData()) 566 for prop in self._mol.iterateProperties()]
567 - def __iter__(self):
568 return iter(self.keys())
569 - def iteritems(self):
570 return iter(self.items())
571 - def __len__(self):
572 return len(self.keys())
573 - def __contains__(self, key):
574 return self._mol.hasProperty(key)
575 - def __delitem__(self, key):
576 self._testforkey(key) 577 self._mol.removeProperty(key)
578 - def clear(self):
579 for key in self: 580 del self[key]
581 - def has_key(self, key):
582 return key in self
583 - def update(self, dictionary):
584 for k, v in dictionary.iteritems(): 585 self[k] = v
586 - def __getitem__(self, key):
587 self._testforkey(key) 588 return self._mol.getProperty(key)
589 - def __setitem__(self, key, value):
590 self._mol.setProperty(key, str(value))
591 - def __repr__(self):
592 return dict(self.iteritems()).__repr__()
593
594 -class Fingerprint(object):
595 """A Molecular Fingerprint. 596 597 Required parameters: 598 fingerprint -- a vector calculated by one of the fingerprint methods 599 600 Attributes: 601 fp -- the underlying fingerprint object 602 bits -- a list of bits set in the Fingerprint 603 604 Methods: 605 The "|" operator can be used to calculate the Tanimoto coeff. For example, 606 given two Fingerprints 'a', and 'b', the Tanimoto coefficient is given by: 607 tanimoto = a | b 608 """
609 - def __init__(self, fingerprint):
610 self.fp = fingerprint
611 - def __or__(self, other):
612 return indigo.similarity(self.fp, other.fp, "tanimoto")
613 - def _buffer_to_int(self):
614 stringrep = self.fp.toString() 615 return [int(stringrep[i:i+1]) for i in range(0, len(stringrep), 1)]
616 @property
617 - def bits(self):
618 return _findbits(self._buffer_to_int(), 8)
619 - def __str__(self):
620 return str(self._buffer_to_int())
621
622 -def _toint(string):
623 """ 624 Some bits sometimes are a character. I haven't found what do they mean, 625 but they break cinfony fingerprints unless taken care of. This functions is just for that. 626 """ 627 if string.isdigit(): 628 return int(string) 629 else: 630 return 0
631
632 -def _findbits(fp, bitsperint):
633 """Find which bits are set in a list/vector. 634 635 This function is used by the Fingerprint class. 636 637 >>> _findbits([13, 71], 8) 638 [1, 3, 4, 9, 10, 11, 15] 639 """ 640 ans = [] 641 start = 1 642 for x in fp: 643 i = start 644 while x > 0: 645 if x % 2: 646 ans.append(i) 647 x >>= 1 648 i += 1 649 start += bitsperint 650 return ans
651
652 653 -def _compressbits(bitvector, wordsize=32):
654 """Compress binary vector into vector of long ints. 655 656 This function is used by the Fingerprint class. 657 658 >>> _compressbits([0, 1, 0, 0, 0, 1], 2) 659 [2, 0, 2] 660 """ 661 ans = [] 662 for start in range(0, len(bitvector), wordsize): 663 compressed = 0 664 for i in range(wordsize): 665 if i + start < len(bitvector) and bitvector[i + start]: 666 compressed += 2**i 667 ans.append(compressed) 668 669 return ans
670 671 if __name__=="__main__": #pragma: no cover 672 import doctest 673 doctest.testmod() 674